WebBrowser/WebBrowserView.py

branch
QtWebEngine
changeset 4710
370a38e03efe
parent 4631
5c1a96925da4
child 4711
0a9162801b8d
equal deleted inserted replaced
4709:8612533a223f 4710:370a38e03efe
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2008 - 2016 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6
7 """
8 Module implementing the helpbrowser using QWebView.
9 """
10
11 from __future__ import unicode_literals
12 try:
13 str = unicode
14 except NameError:
15 pass
16
17 from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject, QT_TRANSLATE_NOOP, \
18 QUrl, QBuffer, QIODevice, QFileInfo, Qt, QTimer, QEvent, \
19 QRect, QFile, QPoint, QByteArray, qVersion
20 from PyQt5.QtGui import QDesktopServices, QClipboard, QMouseEvent, QColor, \
21 QPalette
22 from PyQt5.QtWidgets import qApp, QStyle, QMenu, QApplication, QInputDialog, \
23 QLineEdit, QLabel, QToolTip, QFrame, QDialog
24 from PyQt5.QtPrintSupport import QPrinter, QPrintDialog
25 from PyQt5.QtWebKit import QWebSettings
26 from PyQt5.QtWebKitWidgets import QWebView, QWebPage
27 try:
28 from PyQt5.QtWebKit import QWebElement
29 except ImportError:
30 pass
31 from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
32 import sip
33
34 from E5Gui import E5MessageBox, E5FileDialog
35
36 import Helpviewer
37
38 import Preferences
39 import UI.PixmapCache
40 import Globals
41
42 try:
43 from PyQt5.QtNetwork import QSslCertificate
44 SSL_AVAILABLE = True
45 except ImportError:
46 SSL_AVAILABLE = False
47
48 ###############################################################################
49
50
51 class JavaScriptExternalObject(QObject):
52 """
53 Class implementing an external javascript object to add search providers.
54 """
55 def __init__(self, mw, parent=None):
56 """
57 Constructor
58
59 @param mw reference to the main window 8HelpWindow)
60 @param parent reference to the parent object (QObject)
61 """
62 super(JavaScriptExternalObject, self).__init__(parent)
63
64 self.__mw = mw
65
66 @pyqtSlot(str)
67 def AddSearchProvider(self, url):
68 """
69 Public slot to add a search provider.
70
71 @param url url of the XML file defining the search provider (string)
72 """
73 self.__mw.openSearchManager().addEngine(QUrl(url))
74
75
76 class LinkedResource(object):
77 """
78 Class defining a data structure for linked resources.
79 """
80 def __init__(self):
81 """
82 Constructor
83 """
84 self.rel = ""
85 self.type_ = ""
86 self.href = ""
87 self.title = ""
88
89 ###############################################################################
90
91
92 class JavaScriptEricObject(QObject):
93 """
94 Class implementing an external javascript object to search via the
95 startpage.
96 """
97 # these must be in line with the strings used by the javascript part of
98 # the start page
99 translations = [
100 QT_TRANSLATE_NOOP("JavaScriptEricObject",
101 "Welcome to eric6 Web Browser!"),
102 QT_TRANSLATE_NOOP("JavaScriptEricObject", "eric6 Web Browser"),
103 QT_TRANSLATE_NOOP("JavaScriptEricObject", "Search!"),
104 QT_TRANSLATE_NOOP("JavaScriptEricObject", "About eric6"),
105 ]
106
107 def __init__(self, mw, parent=None):
108 """
109 Constructor
110
111 @param mw reference to the main window 8HelpWindow)
112 @param parent reference to the parent object (QObject)
113 """
114 super(JavaScriptEricObject, self).__init__(parent)
115
116 self.__mw = mw
117
118 @pyqtSlot(str, result=str)
119 def translate(self, trans):
120 """
121 Public method to translate the given string.
122
123 @param trans string to be translated (string)
124 @return translation (string)
125 """
126 if trans == "QT_LAYOUT_DIRECTION":
127 # special handling to detect layout direction
128 if qApp.isLeftToRight():
129 return "LTR"
130 else:
131 return "RTL"
132
133 return self.tr(trans)
134
135 @pyqtSlot(result=str)
136 def providerString(self):
137 """
138 Public method to get a string for the search provider.
139
140 @return string for the search provider (string)
141 """
142 return self.tr("Search results provided by {0}")\
143 .format(self.__mw.openSearchManager().currentEngineName())
144
145 @pyqtSlot(str, result=str)
146 def searchUrl(self, searchStr):
147 """
148 Public method to get the search URL for the given search term.
149
150 @param searchStr search term (string)
151 @return search URL (string)
152 """
153 return bytes(
154 self.__mw.openSearchManager().currentEngine()
155 .searchUrl(searchStr).toEncoded()).decode()
156
157 ###############################################################################
158
159
160 class HelpWebPage(QWebPage):
161 """
162 Class implementing an enhanced web page.
163 """
164 _webPluginFactory = None
165
166 def __init__(self, parent=None):
167 """
168 Constructor
169
170 @param parent parent widget of this window (QWidget)
171 """
172 super(HelpWebPage, self).__init__(parent)
173
174 self.setPluginFactory(self.webPluginFactory())
175
176 self.__lastRequest = None
177 self.__lastRequestType = QWebPage.NavigationTypeOther
178
179 import Helpviewer.HelpWindow
180 from .Network.NetworkAccessManagerProxy import \
181 NetworkAccessManagerProxy
182 self.__proxy = NetworkAccessManagerProxy(self)
183 self.__proxy.setWebPage(self)
184 self.__proxy.setPrimaryNetworkAccessManager(
185 Helpviewer.HelpWindow.HelpWindow.networkAccessManager())
186 self.setNetworkAccessManager(self.__proxy)
187
188 self.__sslConfiguration = None
189 self.__proxy.finished.connect(self.__managerFinished)
190
191 self.__adBlockedEntries = []
192 self.loadStarted.connect(self.__loadStarted)
193
194 self.saveFrameStateRequested.connect(
195 self.__saveFrameStateRequested)
196 self.restoreFrameStateRequested.connect(
197 self.__restoreFrameStateRequested)
198
199 def acceptNavigationRequest(self, frame, request, type_):
200 """
201 Public method to determine, if a request may be accepted.
202
203 @param frame reference to the frame sending the request (QWebFrame)
204 @param request reference to the request object (QNetworkRequest)
205 @param type_ type of the navigation request (QWebPage.NavigationType)
206 @return flag indicating acceptance (boolean)
207 """
208 self.__lastRequest = request
209 if self.__lastRequest.url() != request.url() or \
210 type_ != QWebPage.NavigationTypeOther:
211 self.__lastRequestType = type_
212
213 scheme = request.url().scheme()
214 if scheme == "mailto":
215 QDesktopServices.openUrl(request.url())
216 return False
217
218 if type_ == QWebPage.NavigationTypeFormResubmitted:
219 res = E5MessageBox.yesNo(
220 self.view(),
221 self.tr("Resending POST request"),
222 self.tr(
223 """In order to display the site, the request along with"""
224 """ all the data must be sent once again, which may lead"""
225 """ to some unexpected behaviour of the site e.g. the"""
226 """ same action might be performed once again. Do you"""
227 """ want to continue anyway?"""),
228 icon=E5MessageBox.Warning)
229 if not res:
230 return False
231
232 return QWebPage.acceptNavigationRequest(self, frame, request, type_)
233
234 def populateNetworkRequest(self, request):
235 """
236 Public method to add data to a network request.
237
238 @param request reference to the network request object
239 (QNetworkRequest)
240 """
241 try:
242 request.setAttribute(QNetworkRequest.User + 100, self)
243 if self.__lastRequest.url() == request.url():
244 request.setAttribute(QNetworkRequest.User + 101,
245 self.__lastRequestType)
246 if self.__lastRequestType == \
247 QWebPage.NavigationTypeLinkClicked:
248 request.setRawHeader(b"X-Eric6-UserLoadAction",
249 QByteArray(b"1"))
250 except TypeError:
251 pass
252
253 def pageAttributeId(self):
254 """
255 Public method to get the attribute id of the page attribute.
256
257 @return attribute id of the page attribute (integer)
258 """
259 return QNetworkRequest.User + 100
260
261 def supportsExtension(self, extension):
262 """
263 Public method to check the support for an extension.
264
265 @param extension extension to test for (QWebPage.Extension)
266 @return flag indicating the support of extension (boolean)
267 """
268 try:
269 if extension in [QWebPage.ErrorPageExtension,
270 QWebPage.ChooseMultipleFilesExtension]:
271 return True
272 except AttributeError:
273 pass
274
275 return QWebPage.supportsExtension(self, extension)
276
277 def extension(self, extension, option, output):
278 """
279 Public method to implement a specific extension.
280
281 @param extension extension to be executed (QWebPage.Extension)
282 @param option provides input to the extension
283 (QWebPage.ExtensionOption)
284 @param output stores the output results (QWebPage.ExtensionReturn)
285 @return flag indicating a successful call of the extension (boolean)
286 """
287 if extension == QWebPage.ChooseMultipleFilesExtension:
288 info = sip.cast(option,
289 QWebPage.ChooseMultipleFilesExtensionOption)
290 files = sip.cast(output,
291 QWebPage.ChooseMultipleFilesExtensionReturn)
292 if info is None or files is None:
293 return super(HelpWebPage, self).extension(
294 extension, option, output)
295
296 suggestedFileName = ""
297 if info.suggestedFileNames:
298 suggestedFileName = info.suggestedFileNames[0]
299
300 files.fileNames = E5FileDialog.getOpenFileNames(
301 None,
302 self.tr("Select files to upload..."),
303 suggestedFileName)
304 return True
305
306 if extension == QWebPage.ErrorPageExtension:
307 info = sip.cast(option, QWebPage.ErrorPageExtensionOption)
308
309 errorPage = sip.cast(output, QWebPage.ErrorPageExtensionReturn)
310 urlString = bytes(info.url.toEncoded()).decode()
311 errorPage.baseUrl = info.url
312 if info.domain == QWebPage.QtNetwork and \
313 info.error == QNetworkReply.ProtocolUnknownError:
314 url = QUrl(info.url)
315 res = E5MessageBox.yesNo(
316 None,
317 self.tr("Protocol Error"),
318 self.tr("""Open external application for {0}-link?\n"""
319 """URL: {1}""").format(
320 url.scheme(), url.toString(
321 QUrl.PrettyDecoded | QUrl.RemovePassword)),
322 yesDefault=True)
323
324 if res:
325 QDesktopServices.openUrl(url)
326 return True
327 elif info.domain == QWebPage.QtNetwork and \
328 info.error == QNetworkReply.ContentAccessDenied and \
329 info.errorString.startswith("AdBlockRule:"):
330 if info.frame != info.frame.page().mainFrame():
331 # content in <iframe>
332 docElement = info.frame.page().mainFrame()\
333 .documentElement()
334 for element in docElement.findAll("iframe"):
335 src = element.attribute("src")
336 if src in info.url.toString():
337 element.setAttribute("style", "display:none;")
338 return False
339 else:
340 # the whole page is blocked
341 rule = info.errorString.replace("AdBlockRule:", "")
342 title = self.tr("Content blocked by AdBlock Plus")
343 message = self.tr(
344 "Blocked by rule: <i>{0}</i>").format(rule)
345
346 htmlFile = QFile(":/html/adblockPage.html")
347 htmlFile.open(QFile.ReadOnly)
348 html = htmlFile.readAll()
349 html = html.replace(
350 "@FAVICON@", "qrc:icons/adBlockPlus16.png")
351 html = html.replace(
352 "@IMAGE@", "qrc:icons/adBlockPlus64.png")
353 html = html.replace("@TITLE@", title.encode("utf8"))
354 html = html.replace("@MESSAGE@", message.encode("utf8"))
355 errorPage.content = html
356 return True
357
358 if info.domain == QWebPage.QtNetwork and \
359 info.error == QNetworkReply.OperationCanceledError and \
360 info.errorString == "eric6:No Error":
361 return False
362
363 if info.domain == QWebPage.WebKit and info.error == 203:
364 # "Loading is handled by the media engine"
365 return False
366
367 title = self.tr("Error loading page: {0}").format(urlString)
368 htmlFile = QFile(":/html/notFoundPage.html")
369 htmlFile.open(QFile.ReadOnly)
370 html = htmlFile.readAll()
371 pixmap = qApp.style()\
372 .standardIcon(QStyle.SP_MessageBoxWarning).pixmap(48, 48)
373 imageBuffer = QBuffer()
374 imageBuffer.open(QIODevice.ReadWrite)
375 if pixmap.save(imageBuffer, "PNG"):
376 html = html.replace("@IMAGE@", imageBuffer.buffer().toBase64())
377 pixmap = qApp.style()\
378 .standardIcon(QStyle.SP_MessageBoxWarning).pixmap(16, 16)
379 imageBuffer = QBuffer()
380 imageBuffer.open(QIODevice.ReadWrite)
381 if pixmap.save(imageBuffer, "PNG"):
382 html = html.replace(
383 "@FAVICON@", imageBuffer.buffer().toBase64())
384 html = html.replace("@TITLE@", title.encode("utf8"))
385 html = html.replace("@H1@", info.errorString.encode("utf8"))
386 html = html.replace(
387 "@H2@", self.tr("When connecting to: {0}.")
388 .format(urlString).encode("utf8"))
389 html = html.replace(
390 "@LI-1@",
391 self.tr("Check the address for errors such as "
392 "<b>ww</b>.example.org instead of "
393 "<b>www</b>.example.org").encode("utf8"))
394 html = html.replace(
395 "@LI-2@",
396 self.tr(
397 "If the address is correct, try checking the network "
398 "connection.").encode("utf8"))
399 html = html.replace(
400 "@LI-3@",
401 self.tr(
402 "If your computer or network is protected by a firewall "
403 "or proxy, make sure that the browser is permitted to "
404 "access the network.").encode("utf8"))
405 html = html.replace(
406 "@LI-4@",
407 self.tr("If your cache policy is set to offline browsing,"
408 "only pages in the local cache are available.")
409 .encode("utf8"))
410 html = html.replace(
411 "@BUTTON@", self.tr("Try Again").encode("utf8"))
412 errorPage.content = html
413 return True
414
415 return QWebPage.extension(self, extension, option, output)
416
417 def __loadStarted(self):
418 """
419 Private method to handle the loadStarted signal.
420 """
421 self.__adBlockedEntries = []
422
423 def addAdBlockRule(self, rule, url):
424 """
425 Public slot to add an AdBlock rule to the page.
426
427 @param rule AdBlock rule to add (AdBlockRule)
428 @param url URL that matched the rule (QUrl)
429 """
430 from .AdBlock.AdBlockPage import AdBlockedPageEntry
431 entry = AdBlockedPageEntry(rule, url)
432 if entry not in self.__adBlockedEntries:
433 self.__adBlockedEntries.append(entry)
434
435 def getAdBlockedPageEntries(self):
436 """
437 Public method to get the list of AdBlock page entries.
438
439 @return list of AdBlock page entries (list of AdBlockedPageEntry)
440 """
441 return self.__adBlockedEntries
442
443 def url(self):
444 """
445 Public method to get the URL of the page.
446
447 @return URL of the page (QUrl)
448 """
449 return self.mainFrame().url()
450
451 def userAgent(self, resolveEmpty=False):
452 """
453 Public method to get the global user agent setting.
454
455 @param resolveEmpty flag indicating to resolve an empty
456 user agent (boolean)
457 @return user agent string (string)
458 """
459 agent = Preferences.getHelp("UserAgent")
460 if agent == "" and resolveEmpty:
461 agent = self.userAgentForUrl(QUrl())
462 return agent
463
464 def setUserAgent(self, agent):
465 """
466 Public method to set the global user agent string.
467
468 @param agent new current user agent string (string)
469 """
470 Preferences.setHelp("UserAgent", agent)
471
472 def userAgentForUrl(self, url):
473 """
474 Public method to determine the user agent for the given URL.
475
476 @param url URL to determine user agent for (QUrl)
477 @return user agent string (string)
478 """
479 import Helpviewer.HelpWindow
480 agent = Helpviewer.HelpWindow.HelpWindow.userAgentsManager()\
481 .userAgentForUrl(url)
482 if agent == "":
483 # no agent string specified for the given host -> use global one
484 agent = Preferences.getHelp("UserAgent")
485 if agent == "":
486 # no global agent string specified -> use default one
487 agent = QWebPage.userAgentForUrl(self, url)
488 return agent
489
490 def __managerFinished(self, reply):
491 """
492 Private slot to handle a finished reply.
493
494 This slot is used to get SSL related information for a reply.
495
496 @param reply reference to the finished reply (QNetworkReply)
497 """
498 try:
499 frame = reply.request().originatingObject()
500 except AttributeError:
501 frame = None
502
503 mainFrameRequest = frame == self.mainFrame()
504
505 if mainFrameRequest and \
506 self.__sslConfiguration is not None and \
507 reply.url() == self.mainFrame().url():
508 self.__sslConfiguration = None
509
510 if reply.error() == QNetworkReply.NoError and \
511 mainFrameRequest and \
512 self.__sslConfiguration is None and \
513 reply.url().scheme().lower() == "https" and \
514 reply.url() == self.mainFrame().url():
515 self.__sslConfiguration = reply.sslConfiguration()
516 self.__sslConfiguration.url = QUrl(reply.url())
517
518 if reply.error() == QNetworkReply.NoError and \
519 mainFrameRequest and \
520 reply.url() == self.mainFrame().url():
521 modified = reply.header(QNetworkRequest.LastModifiedHeader)
522 if modified and modified.isValid():
523 import Helpviewer.HelpWindow
524 manager = Helpviewer.HelpWindow.HelpWindow.bookmarksManager()
525 from .Bookmarks.BookmarkNode import BookmarkNode
526 for bookmark in manager.bookmarksForUrl(reply.url()):
527 manager.setTimestamp(bookmark, BookmarkNode.TsModified,
528 modified)
529
530 def getSslCertificate(self):
531 """
532 Public method to get a reference to the SSL certificate.
533
534 @return amended SSL certificate (QSslCertificate)
535 """
536 if self.__sslConfiguration is None:
537 return None
538
539 sslInfo = self.__sslConfiguration.peerCertificate()
540 sslInfo.url = QUrl(self.__sslConfiguration.url)
541 return sslInfo
542
543 def getSslCertificateChain(self):
544 """
545 Public method to get a reference to the SSL certificate chain.
546
547 @return SSL certificate chain (list of QSslCertificate)
548 """
549 if self.__sslConfiguration is None:
550 return []
551
552 chain = self.__sslConfiguration.peerCertificateChain()
553 return chain
554
555 def getSslConfiguration(self):
556 """
557 Public method to return a reference to the current SSL configuration.
558
559 @return reference to the SSL configuration in use (QSslConfiguration)
560 """
561 return self.__sslConfiguration
562
563 def showSslInfo(self, pos):
564 """
565 Public slot to show some SSL information for the loaded page.
566
567 @param pos position to show the info at (QPoint)
568 """
569 if SSL_AVAILABLE and self.__sslConfiguration is not None:
570 from E5Network.E5SslInfoWidget import E5SslInfoWidget
571 widget = E5SslInfoWidget(
572 self.mainFrame().url(), self.__sslConfiguration, self.view())
573 widget.showAt(pos)
574 else:
575 E5MessageBox.warning(
576 self.view(),
577 self.tr("SSL Info"),
578 self.tr("""This site does not contain SSL information."""))
579
580 def hasValidSslInfo(self):
581 """
582 Public method to check, if the page has a valid SSL certificate.
583
584 @return flag indicating a valid SSL certificate (boolean)
585 """
586 if self.__sslConfiguration is None:
587 return False
588
589 certList = self.__sslConfiguration.peerCertificateChain()
590 if not certList:
591 return False
592
593 certificateDict = Globals.toDict(
594 Preferences.Prefs.settings.value("Ssl/CaCertificatesDict"))
595 for server in certificateDict:
596 localCAList = QSslCertificate.fromData(certificateDict[server])
597 for cert in certList:
598 if cert in localCAList:
599 return True
600
601 if qVersion() >= "5.0.0":
602 for cert in certList:
603 if cert.isBlacklisted():
604 return False
605 else:
606 for cert in certList:
607 if not cert.isValid():
608 return False
609
610 return True
611
612 @classmethod
613 def webPluginFactory(cls):
614 """
615 Class method to get a reference to the web plug-in factory
616 instance.
617
618 @return reference to the web plug-in factory instance (WebPluginFactory
619 """
620 if cls._webPluginFactory is None:
621 from .WebPlugins.WebPluginFactory import WebPluginFactory
622 cls._webPluginFactory = WebPluginFactory()
623
624 return cls._webPluginFactory
625
626 def event(self, evt):
627 """
628 Public method implementing the event handler.
629
630 @param evt reference to the event (QEvent)
631 @return flag indicating that the event was handled (boolean)
632 """
633 if evt.type() == QEvent.Leave:
634 # Fake a mouse move event just outside of the widget to trigger
635 # the WebKit event handler's mouseMoved function. This implements
636 # the interesting mouse-out behavior like invalidating scrollbars.
637 fakeEvent = QMouseEvent(QEvent.MouseMove, QPoint(0, -1),
638 Qt.NoButton, Qt.NoButton, Qt.NoModifier)
639 return super(HelpWebPage, self).event(fakeEvent)
640
641 return super(HelpWebPage, self).event(evt)
642
643 def __saveFrameStateRequested(self, frame, itm):
644 """
645 Private slot to save the page state (i.e. zoom level and scroll
646 position).
647
648 Note: Code is based on qutebrowser.
649
650 @param frame frame to be saved
651 @type QWebFrame
652 @param itm web history item to be saved
653 @type QWebHistoryItem
654 """
655 try:
656 if frame != self.mainFrame():
657 return
658 except RuntimeError:
659 # With Qt 5.2.1 (Ubuntu Trusty) we get this when closing a tab:
660 # RuntimeError: wrapped C/C++ object of type BrowserPage has
661 # been deleted
662 # Since the information here isn't that important for closing web
663 # views anyways, we ignore this error.
664 return
665 data = {
666 'zoom': frame.zoomFactor(),
667 'scrollPos': frame.scrollPosition(),
668 }
669 itm.setUserData(data)
670
671 def __restoreFrameStateRequested(self, frame):
672 """
673 Private slot to restore scroll position and zoom level from
674 history.
675
676 Note: Code is based on qutebrowser.
677
678 @param frame frame to be restored
679 @type QWebFrame
680 """
681 if frame != self.mainFrame():
682 return
683
684 data = self.history().currentItem().userData()
685 if data is None:
686 return
687
688 if 'zoom' in data:
689 frame.page().view().setZoomValue(int(data['zoom'] * 100),
690 saveValue=False)
691
692 if 'scrollPos' in data and frame.scrollPosition() == QPoint(0, 0):
693 frame.setScrollPosition(data['scrollPos'])
694
695 ###############################################################################
696
697
698 class HelpBrowser(QWebView):
699 """
700 Class implementing the helpbrowser widget.
701
702 This is a subclass of the Qt QWebView to implement an
703 interface compatible with the QTextBrowser based variant.
704
705 @signal sourceChanged(QUrl) emitted after the current URL has changed
706 @signal forwardAvailable(bool) emitted after the current URL has changed
707 @signal backwardAvailable(bool) emitted after the current URL has changed
708 @signal highlighted(str) emitted, when the mouse hovers over a link
709 @signal search(QUrl) emitted, when a search is requested
710 @signal zoomValueChanged(int) emitted to signal a change of the zoom value
711 """
712 sourceChanged = pyqtSignal(QUrl)
713 forwardAvailable = pyqtSignal(bool)
714 backwardAvailable = pyqtSignal(bool)
715 highlighted = pyqtSignal(str)
716 search = pyqtSignal(QUrl)
717 zoomValueChanged = pyqtSignal(int)
718
719 ZoomLevels = [
720 30, 50, 67, 80, 90,
721 100,
722 110, 120, 133, 150, 170, 200, 240, 300,
723 ]
724 ZoomLevelDefault = 100
725
726 def __init__(self, mainWindow, parent=None, name=""):
727 """
728 Constructor
729
730 @param mainWindow reference to the main window (HelpWindow)
731 @param parent parent widget of this window (QWidget)
732 @param name name of this window (string)
733 """
734 super(HelpBrowser, self).__init__(parent)
735 self.setObjectName(name)
736 self.setWhatsThis(self.tr(
737 """<b>Help Window</b>"""
738 """<p>This window displays the selected help information.</p>"""
739 ))
740
741 import Helpviewer.HelpWindow
742 self.__speedDial = Helpviewer.HelpWindow.HelpWindow.speedDial()
743
744 self.__page = HelpWebPage(self)
745 self.setPage(self.__page)
746
747 self.mw = mainWindow
748 self.ctrlPressed = False
749 self.__isLoading = False
750 self.__progress = 0
751
752 self.__currentZoom = 100
753 self.__zoomLevels = HelpBrowser.ZoomLevels[:]
754
755 self.__javaScriptBinding = None
756 self.__javaScriptEricObject = None
757
758 self.mw.zoomTextOnlyChanged.connect(self.__applyZoom)
759
760 self.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks)
761 self.linkClicked.connect(self.setSource)
762
763 self.urlChanged.connect(self.__urlChanged)
764 self.statusBarMessage.connect(self.__statusBarMessage)
765 self.page().linkHovered.connect(self.__linkHovered)
766
767 self.loadStarted.connect(self.__loadStarted)
768 self.loadProgress.connect(self.__loadProgress)
769 self.loadFinished.connect(self.__loadFinished)
770
771 self.page().setForwardUnsupportedContent(True)
772 self.page().unsupportedContent.connect(self.__unsupportedContent)
773
774 self.page().featurePermissionRequested.connect(
775 self.__featurePermissionRequested)
776
777 self.page().downloadRequested.connect(self.__downloadRequested)
778 self.page().frameCreated.connect(self.__addExternalBinding)
779 self.__addExternalBinding(self.page().mainFrame())
780
781 self.page().databaseQuotaExceeded.connect(self.__databaseQuotaExceeded)
782
783 self.mw.openSearchManager().currentEngineChanged.connect(
784 self.__currentEngineChanged)
785
786 self.setAcceptDrops(True)
787
788 self.__enableAccessKeys = Preferences.getHelp("AccessKeysEnabled")
789 self.__accessKeysPressed = False
790 self.__accessKeyLabels = []
791 self.__accessKeyNodes = {}
792
793 self.page().loadStarted.connect(self.__hideAccessKeys)
794 self.page().scrollRequested.connect(self.__hideAccessKeys)
795
796 self.__rss = []
797
798 self.__clickedFrame = None
799
800 self.mw.personalInformationManager().connectPage(self.page())
801 self.mw.greaseMonkeyManager().connectPage(self.page())
802
803 self.__inspector = None
804
805 self.grabGesture(Qt.PinchGesture)
806
807 def __addExternalBinding(self, frame=None):
808 """
809 Private slot to add javascript bindings for adding search providers.
810
811 @param frame reference to the web frame (QWebFrame)
812 """
813 self.page().settings().setAttribute(QWebSettings.JavascriptEnabled,
814 True)
815 if self.__javaScriptBinding is None:
816 self.__javaScriptBinding = JavaScriptExternalObject(self.mw, self)
817
818 if frame is None:
819 # called from QWebFrame.javaScriptWindowObjectCleared
820 frame = self.sender()
821 if isinstance(frame, HelpWebPage):
822 frame = frame.mainFrame()
823 if frame.url().scheme() == "eric" and frame.url().path() == "home":
824 if self.__javaScriptEricObject is None:
825 self.__javaScriptEricObject = JavaScriptEricObject(
826 self.mw, self)
827 frame.addToJavaScriptWindowObject(
828 "eric", self.__javaScriptEricObject)
829 elif frame.url().scheme() == "eric" and \
830 frame.url().path() == "speeddial":
831 frame.addToJavaScriptWindowObject(
832 "speeddial", self.__speedDial)
833 self.__speedDial.addWebFrame(frame)
834 else:
835 # called from QWebPage.frameCreated
836 frame.javaScriptWindowObjectCleared.connect(
837 self.__addExternalBinding)
838 frame.addToJavaScriptWindowObject("external", self.__javaScriptBinding)
839
840 def linkedResources(self, relation=""):
841 """
842 Public method to extract linked resources.
843
844 @param relation relation to extract (string)
845 @return list of linked resources (list of LinkedResource)
846 """
847 resources = []
848
849 baseUrl = self.page().mainFrame().baseUrl()
850
851 linkElements = self.page().mainFrame().findAllElements(
852 "html > head > link")
853
854 for linkElement in linkElements.toList():
855 rel = linkElement.attribute("rel")
856 href = linkElement.attribute("href")
857 type_ = linkElement.attribute("type")
858 title = linkElement.attribute("title")
859
860 if href == "" or type_ == "":
861 continue
862 if relation and rel != relation:
863 continue
864
865 resource = LinkedResource()
866 resource.rel = rel
867 resource.type_ = type_
868 resource.href = baseUrl.resolved(
869 QUrl.fromEncoded(href.encode("utf-8")))
870 resource.title = title
871
872 resources.append(resource)
873
874 return resources
875
876 def __currentEngineChanged(self):
877 """
878 Private slot to track a change of the current search engine.
879 """
880 if self.url().toString() == "eric:home":
881 self.reload()
882
883 def setSource(self, name, requestData=None):
884 """
885 Public method used to set the source to be displayed.
886
887 @param name filename to be shown (QUrl)
888 @param requestData tuple containing the request data (QNetworkRequest,
889 QNetworkAccessManager.Operation, QByteArray)
890 """
891 if (name is None or not name.isValid()) and requestData is None:
892 return
893
894 if name is None and requestData is not None:
895 name = requestData[0].url()
896
897 if self.ctrlPressed:
898 # open in a new window
899 self.mw.newTab(name)
900 self.ctrlPressed = False
901 return
902
903 if not name.scheme():
904 name.setUrl(Preferences.getHelp("DefaultScheme") + name.toString())
905
906 if len(name.scheme()) == 1 or \
907 name.scheme() == "file":
908 # name is a local file
909 if name.scheme() and len(name.scheme()) == 1:
910 # it is a local path on win os
911 name = QUrl.fromLocalFile(name.toString())
912
913 if not QFileInfo(name.toLocalFile()).exists():
914 E5MessageBox.critical(
915 self,
916 self.tr("eric6 Web Browser"),
917 self.tr(
918 """<p>The file <b>{0}</b> does not exist.</p>""")
919 .format(name.toLocalFile()))
920 return
921
922 if name.toLocalFile().endswith(".pdf") or \
923 name.toLocalFile().endswith(".PDF") or \
924 name.toLocalFile().endswith(".chm") or \
925 name.toLocalFile().endswith(".CHM"):
926 started = QDesktopServices.openUrl(name)
927 if not started:
928 E5MessageBox.critical(
929 self,
930 self.tr("eric6 Web Browser"),
931 self.tr(
932 """<p>Could not start a viewer"""
933 """ for file <b>{0}</b>.</p>""")
934 .format(name.path()))
935 return
936 elif name.scheme() in ["mailto"]:
937 started = QDesktopServices.openUrl(name)
938 if not started:
939 E5MessageBox.critical(
940 self,
941 self.tr("eric6 Web Browser"),
942 self.tr(
943 """<p>Could not start an application"""
944 """ for URL <b>{0}</b>.</p>""")
945 .format(name.toString()))
946 return
947 elif name.scheme() == "javascript":
948 scriptSource = QUrl.fromPercentEncoding(name.toString(
949 QUrl.FormattingOptions(QUrl.TolerantMode | QUrl.RemoveScheme)))
950 self.page().mainFrame().evaluateJavaScript(scriptSource)
951 return
952 else:
953 if name.toString().endswith(".pdf") or \
954 name.toString().endswith(".PDF") or \
955 name.toString().endswith(".chm") or \
956 name.toString().endswith(".CHM"):
957 started = QDesktopServices.openUrl(name)
958 if not started:
959 E5MessageBox.critical(
960 self,
961 self.tr("eric6 Web Browser"),
962 self.tr(
963 """<p>Could not start a viewer"""
964 """ for file <b>{0}</b>.</p>""")
965 .format(name.path()))
966 return
967
968 if requestData is not None:
969 self.load(*requestData)
970 else:
971 self.load(name)
972
973 def source(self):
974 """
975 Public method to return the URL of the loaded page.
976
977 @return URL loaded in the help browser (QUrl)
978 """
979 return self.url()
980
981 def documentTitle(self):
982 """
983 Public method to return the title of the loaded page.
984
985 @return title (string)
986 """
987 return self.title()
988
989 def backward(self):
990 """
991 Public slot to move backwards in history.
992 """
993 self.triggerPageAction(QWebPage.Back)
994 self.__urlChanged(self.history().currentItem().url())
995
996 def forward(self):
997 """
998 Public slot to move forward in history.
999 """
1000 self.triggerPageAction(QWebPage.Forward)
1001 self.__urlChanged(self.history().currentItem().url())
1002
1003 def home(self):
1004 """
1005 Public slot to move to the first page loaded.
1006 """
1007 homeUrl = QUrl(Preferences.getHelp("HomePage"))
1008 self.setSource(homeUrl)
1009 self.__urlChanged(self.history().currentItem().url())
1010
1011 def reload(self):
1012 """
1013 Public slot to reload the current page.
1014 """
1015 self.triggerPageAction(QWebPage.Reload)
1016
1017 def copy(self):
1018 """
1019 Public slot to copy the selected text.
1020 """
1021 self.triggerPageAction(QWebPage.Copy)
1022
1023 def isForwardAvailable(self):
1024 """
1025 Public method to determine, if a forward move in history is possible.
1026
1027 @return flag indicating move forward is possible (boolean)
1028 """
1029 return self.history().canGoForward()
1030
1031 def isBackwardAvailable(self):
1032 """
1033 Public method to determine, if a backwards move in history is possible.
1034
1035 @return flag indicating move backwards is possible (boolean)
1036 """
1037 return self.history().canGoBack()
1038
1039 def __levelForZoom(self, zoom):
1040 """
1041 Private method determining the zoom level index given a zoom factor.
1042
1043 @param zoom zoom factor (integer)
1044 @return index of zoom factor (integer)
1045 """
1046 try:
1047 index = self.__zoomLevels.index(zoom)
1048 except ValueError:
1049 for index in range(len(self.__zoomLevels)):
1050 if zoom <= self.__zoomLevels[index]:
1051 break
1052 return index
1053
1054 def __applyZoom(self):
1055 """
1056 Private slot to apply the current zoom factor.
1057 """
1058 self.setZoomValue(self.__currentZoom)
1059
1060 def setZoomValue(self, value, saveValue=True):
1061 """
1062 Public method to set the zoom value.
1063
1064 @param value zoom value (integer)
1065 @keyparam saveValue flag indicating to save the zoom value with the
1066 zoom manager
1067 @type bool
1068 """
1069 if value != self.zoomValue():
1070 try:
1071 self.setZoomFactor(value / 100.0)
1072 except AttributeError:
1073 self.setTextSizeMultiplier(value / 100.0)
1074 self.__currentZoom = value
1075 if saveValue:
1076 Helpviewer.HelpWindow.HelpWindow.zoomManager().setZoomValue(
1077 self.url(), value)
1078 self.zoomValueChanged.emit(value)
1079
1080 def zoomValue(self):
1081 """
1082 Public method to get the current zoom value.
1083
1084 @return zoom value (integer)
1085 """
1086 try:
1087 val = self.zoomFactor() * 100
1088 except AttributeError:
1089 val = self.textSizeMultiplier() * 100
1090 return int(val)
1091
1092 def zoomIn(self):
1093 """
1094 Public slot to zoom into the page.
1095 """
1096 index = self.__levelForZoom(self.__currentZoom)
1097 if index < len(self.__zoomLevels) - 1:
1098 self.__currentZoom = self.__zoomLevels[index + 1]
1099 self.__applyZoom()
1100
1101 def zoomOut(self):
1102 """
1103 Public slot to zoom out of the page.
1104 """
1105 index = self.__levelForZoom(self.__currentZoom)
1106 if index > 0:
1107 self.__currentZoom = self.__zoomLevels[index - 1]
1108 self.__applyZoom()
1109
1110 def zoomReset(self):
1111 """
1112 Public method to reset the zoom factor.
1113 """
1114 index = self.__levelForZoom(HelpBrowser.ZoomLevelDefault)
1115 self.__currentZoom = self.__zoomLevels[index]
1116 self.__applyZoom()
1117
1118 def hasSelection(self):
1119 """
1120 Public method to determine, if there is some text selected.
1121
1122 @return flag indicating text has been selected (boolean)
1123 """
1124 return self.selectedText() != ""
1125
1126 def findNextPrev(self, txt, case, backwards, wrap, highlightAll):
1127 """
1128 Public slot to find the next occurrence of a text.
1129
1130 @param txt text to search for (string)
1131 @param case flag indicating a case sensitive search (boolean)
1132 @param backwards flag indicating a backwards search (boolean)
1133 @param wrap flag indicating to wrap around (boolean)
1134 @param highlightAll flag indicating to highlight all occurrences
1135 (boolean)
1136 @return flag indicating that a match was found (boolean)
1137 """
1138 findFlags = QWebPage.FindFlags()
1139 if case:
1140 findFlags |= QWebPage.FindCaseSensitively
1141 if backwards:
1142 findFlags |= QWebPage.FindBackward
1143 if wrap:
1144 findFlags |= QWebPage.FindWrapsAroundDocument
1145 try:
1146 if highlightAll:
1147 findFlags |= QWebPage.HighlightAllOccurrences
1148 except AttributeError:
1149 pass
1150
1151 return self.findText(txt, findFlags)
1152
1153 def __isMediaElement(self, element):
1154 """
1155 Private method to check, if the given element is a media element.
1156
1157 @param element element to be checked (QWebElement)
1158 @return flag indicating a media element (boolean)
1159 """
1160 return element.tagName().lower() in ["video", "audio"]
1161
1162 def contextMenuEvent(self, evt):
1163 """
1164 Protected method called to create a context menu.
1165
1166 This method is overridden from QWebView.
1167
1168 @param evt reference to the context menu event object
1169 (QContextMenuEvent)
1170 """
1171 from .UserAgent.UserAgentMenu import UserAgentMenu
1172 menu = QMenu(self)
1173
1174 frameAtPos = self.page().frameAt(evt.pos())
1175 hit = self.page().mainFrame().hitTestContent(evt.pos())
1176 if not hit.linkUrl().isEmpty():
1177 menu.addAction(
1178 UI.PixmapCache.getIcon("openNewTab.png"),
1179 self.tr("Open Link in New Tab\tCtrl+LMB"),
1180 self.__openLinkInNewTab).setData(hit.linkUrl())
1181 menu.addSeparator()
1182 menu.addAction(
1183 UI.PixmapCache.getIcon("download.png"),
1184 self.tr("Save Lin&k"), self.__downloadLink)
1185 menu.addAction(
1186 UI.PixmapCache.getIcon("bookmark22.png"),
1187 self.tr("Bookmark this Link"), self.__bookmarkLink)\
1188 .setData(hit.linkUrl())
1189 menu.addSeparator()
1190 menu.addAction(
1191 UI.PixmapCache.getIcon("editCopy.png"),
1192 self.tr("Copy Link to Clipboard"), self.__copyLink)
1193 menu.addAction(
1194 UI.PixmapCache.getIcon("mailSend.png"),
1195 self.tr("Send Link"),
1196 self.__sendLink).setData(hit.linkUrl())
1197 if Preferences.getHelp("VirusTotalEnabled") and \
1198 Preferences.getHelp("VirusTotalServiceKey") != "":
1199 menu.addAction(
1200 UI.PixmapCache.getIcon("virustotal.png"),
1201 self.tr("Scan Link with VirusTotal"),
1202 self.__virusTotal).setData(hit.linkUrl())
1203
1204 if not hit.imageUrl().isEmpty():
1205 if not menu.isEmpty():
1206 menu.addSeparator()
1207 menu.addAction(
1208 UI.PixmapCache.getIcon("openNewTab.png"),
1209 self.tr("Open Image in New Tab"),
1210 self.__openLinkInNewTab).setData(hit.imageUrl())
1211 menu.addSeparator()
1212 menu.addAction(
1213 UI.PixmapCache.getIcon("download.png"),
1214 self.tr("Save Image"), self.__downloadImage)
1215 menu.addAction(
1216 self.tr("Copy Image to Clipboard"), self.__copyImage)
1217 menu.addAction(
1218 UI.PixmapCache.getIcon("editCopy.png"),
1219 self.tr("Copy Image Location to Clipboard"),
1220 self.__copyLocation).setData(hit.imageUrl().toString())
1221 menu.addAction(
1222 UI.PixmapCache.getIcon("mailSend.png"),
1223 self.tr("Send Image Link"),
1224 self.__sendLink).setData(hit.imageUrl())
1225 menu.addSeparator()
1226 menu.addAction(
1227 UI.PixmapCache.getIcon("adBlockPlus.png"),
1228 self.tr("Block Image"), self.__blockImage)\
1229 .setData(hit.imageUrl().toString())
1230 if Preferences.getHelp("VirusTotalEnabled") and \
1231 Preferences.getHelp("VirusTotalServiceKey") != "":
1232 menu.addAction(
1233 UI.PixmapCache.getIcon("virustotal.png"),
1234 self.tr("Scan Image with VirusTotal"),
1235 self.__virusTotal).setData(hit.imageUrl())
1236
1237 element = hit.element()
1238 if not element.isNull():
1239 if self.__isMediaElement(element):
1240 if not menu.isEmpty():
1241 menu.addSeparator()
1242
1243 self.__clickedMediaElement = element
1244
1245 paused = element.evaluateJavaScript("this.paused")
1246 muted = element.evaluateJavaScript("this.muted")
1247 videoUrl = QUrl(element.evaluateJavaScript("this.currentSrc"))
1248
1249 if paused:
1250 menu.addAction(
1251 UI.PixmapCache.getIcon("mediaPlaybackStart.png"),
1252 self.tr("Play"), self.__pauseMedia)
1253 else:
1254 menu.addAction(
1255 UI.PixmapCache.getIcon("mediaPlaybackPause.png"),
1256 self.tr("Pause"), self.__pauseMedia)
1257 if muted:
1258 menu.addAction(
1259 UI.PixmapCache.getIcon("audioVolumeHigh.png"),
1260 self.tr("Unmute"), self.__muteMedia)
1261 else:
1262 menu.addAction(
1263 UI.PixmapCache.getIcon("audioVolumeMuted.png"),
1264 self.tr("Mute"), self.__muteMedia)
1265 menu.addSeparator()
1266 menu.addAction(
1267 UI.PixmapCache.getIcon("editCopy.png"),
1268 self.tr("Copy Media Address to Clipboard"),
1269 self.__copyLocation).setData(videoUrl.toString())
1270 menu.addAction(
1271 UI.PixmapCache.getIcon("mailSend.png"),
1272 self.tr("Send Media Address"), self.__sendLink)\
1273 .setData(videoUrl)
1274 menu.addAction(
1275 UI.PixmapCache.getIcon("download.png"),
1276 self.tr("Save Media"), self.__downloadMedia)\
1277 .setData(videoUrl)
1278
1279 if element.tagName().lower() in ["input", "textarea"]:
1280 if menu.isEmpty():
1281 pageMenu = self.page().createStandardContextMenu()
1282 directionFound = False
1283 # used to detect double direction entry
1284 for act in pageMenu.actions():
1285 if act.isSeparator():
1286 menu.addSeparator()
1287 continue
1288 if act.menu():
1289 if self.pageAction(
1290 QWebPage.SetTextDirectionDefault) in \
1291 act.menu().actions():
1292 if directionFound:
1293 act.setVisible(False)
1294 directionFound = True
1295 elif self.pageAction(QWebPage.ToggleBold) in \
1296 act.menu().actions():
1297 act.setVisible(False)
1298 elif act == self.pageAction(QWebPage.InspectElement):
1299 # we have our own inspect entry
1300 act.setVisible(False)
1301 menu.addAction(act)
1302 pageMenu = None
1303
1304 if not menu.isEmpty():
1305 menu.addSeparator()
1306
1307 self.mw.personalInformationManager().createSubMenu(menu, self, hit)
1308
1309 menu.addAction(self.mw.newTabAct)
1310 menu.addAction(self.mw.newAct)
1311 menu.addSeparator()
1312 menu.addAction(self.mw.saveAsAct)
1313 menu.addSeparator()
1314
1315 if frameAtPos and self.page().mainFrame() != frameAtPos:
1316 self.__clickedFrame = frameAtPos
1317 fmenu = QMenu(self.tr("This Frame"))
1318 frameUrl = self.__clickedFrame.url()
1319 if frameUrl.isValid():
1320 fmenu.addAction(
1321 self.tr("Show &only this frame"),
1322 self.__loadClickedFrame)
1323 fmenu.addAction(
1324 UI.PixmapCache.getIcon("openNewTab.png"),
1325 self.tr("Show in new &tab"),
1326 self.__openLinkInNewTab).setData(self.__clickedFrame.url())
1327 fmenu.addSeparator()
1328 fmenu.addAction(
1329 UI.PixmapCache.getIcon("print.png"),
1330 self.tr("&Print"), self.__printClickedFrame)
1331 fmenu.addAction(
1332 UI.PixmapCache.getIcon("printPreview.png"),
1333 self.tr("Print Preview"), self.__printPreviewClickedFrame)
1334 fmenu.addAction(
1335 UI.PixmapCache.getIcon("printPdf.png"),
1336 self.tr("Print as PDF"), self.__printPdfClickedFrame)
1337 fmenu.addSeparator()
1338 fmenu.addAction(
1339 UI.PixmapCache.getIcon("zoomIn.png"),
1340 self.tr("Zoom &in"), self.__zoomInClickedFrame)
1341 fmenu.addAction(
1342 UI.PixmapCache.getIcon("zoomReset.png"),
1343 self.tr("Zoom &reset"), self.__zoomResetClickedFrame)
1344 fmenu.addAction(
1345 UI.PixmapCache.getIcon("zoomOut.png"),
1346 self.tr("Zoom &out"), self.__zoomOutClickedFrame)
1347 fmenu.addSeparator()
1348 fmenu.addAction(
1349 self.tr("Show frame so&urce"),
1350 self.__showClickedFrameSource)
1351
1352 menu.addMenu(fmenu)
1353 menu.addSeparator()
1354
1355 menu.addAction(
1356 UI.PixmapCache.getIcon("bookmark22.png"),
1357 self.tr("Bookmark this Page"), self.addBookmark)
1358 menu.addAction(
1359 UI.PixmapCache.getIcon("mailSend.png"),
1360 self.tr("Send Page Link"), self.__sendLink).setData(self.url())
1361 menu.addSeparator()
1362 self.__userAgentMenu = UserAgentMenu(self.tr("User Agent"),
1363 url=self.url())
1364 menu.addMenu(self.__userAgentMenu)
1365 menu.addSeparator()
1366 menu.addAction(self.mw.backAct)
1367 menu.addAction(self.mw.forwardAct)
1368 menu.addAction(self.mw.homeAct)
1369 menu.addSeparator()
1370 menu.addAction(self.mw.zoomInAct)
1371 menu.addAction(self.mw.zoomResetAct)
1372 menu.addAction(self.mw.zoomOutAct)
1373 menu.addSeparator()
1374 if self.selectedText():
1375 menu.addAction(self.mw.copyAct)
1376 menu.addAction(
1377 UI.PixmapCache.getIcon("mailSend.png"),
1378 self.tr("Send Text"),
1379 self.__sendLink).setData(self.selectedText())
1380 menu.addAction(self.mw.findAct)
1381 menu.addSeparator()
1382 if self.selectedText():
1383 self.__searchMenu = menu.addMenu(self.tr("Search with..."))
1384
1385 from .OpenSearch.OpenSearchEngineAction import \
1386 OpenSearchEngineAction
1387 engineNames = self.mw.openSearchManager().allEnginesNames()
1388 for engineName in engineNames:
1389 engine = self.mw.openSearchManager().engine(engineName)
1390 act = OpenSearchEngineAction(engine, self.__searchMenu)
1391 act.setData(engineName)
1392 self.__searchMenu.addAction(act)
1393 self.__searchMenu.triggered.connect(self.__searchRequested)
1394
1395 menu.addSeparator()
1396
1397 from .HelpLanguagesDialog import HelpLanguagesDialog
1398 languages = Preferences.toList(
1399 Preferences.Prefs.settings.value(
1400 "Help/AcceptLanguages",
1401 HelpLanguagesDialog.defaultAcceptLanguages()))
1402 if languages:
1403 language = languages[0]
1404 langCode = language.split("[")[1][:2]
1405 googleTranslatorUrl = QUrl(
1406 "http://translate.google.com/#auto|{0}|{1}".format(
1407 langCode, self.selectedText()))
1408 menu.addAction(
1409 UI.PixmapCache.getIcon("translate.png"),
1410 self.tr("Google Translate"), self.__openLinkInNewTab)\
1411 .setData(googleTranslatorUrl)
1412 wiktionaryUrl = QUrl(
1413 "http://{0}.wiktionary.org/wiki/Special:Search?search={1}"
1414 .format(langCode, self.selectedText()))
1415 menu.addAction(
1416 UI.PixmapCache.getIcon("wikipedia.png"),
1417 self.tr("Dictionary"), self.__openLinkInNewTab)\
1418 .setData(wiktionaryUrl)
1419 menu.addSeparator()
1420
1421 guessedUrl = QUrl.fromUserInput(self.selectedText().strip())
1422 if self.__isUrlValid(guessedUrl):
1423 menu.addAction(
1424 self.tr("Go to web address"),
1425 self.__openLinkInNewTab).setData(guessedUrl)
1426 menu.addSeparator()
1427
1428 element = hit.element()
1429 if not element.isNull() and \
1430 element.tagName().lower() == "input" and \
1431 element.attribute("type", "text") == "text":
1432 menu.addAction(self.tr("Add to web search toolbar"),
1433 self.__addSearchEngine).setData(element)
1434 menu.addSeparator()
1435
1436 menu.addAction(
1437 UI.PixmapCache.getIcon("webInspector.png"),
1438 self.tr("Web Inspector..."), self.__webInspector)
1439
1440 menu.exec_(evt.globalPos())
1441
1442 def __isUrlValid(self, url):
1443 """
1444 Private method to check a URL for validity.
1445
1446 @param url URL to be checked (QUrl)
1447 @return flag indicating a valid URL (boolean)
1448 """
1449 return url.isValid() and \
1450 bool(url.host()) and \
1451 bool(url.scheme()) and \
1452 "." in url.host()
1453
1454 def __openLinkInNewTab(self):
1455 """
1456 Private method called by the context menu to open a link in a new
1457 window.
1458 """
1459 act = self.sender()
1460 url = act.data()
1461 if url.isEmpty():
1462 return
1463
1464 self.ctrlPressed = True
1465 self.setSource(url)
1466 self.ctrlPressed = False
1467
1468 def __bookmarkLink(self):
1469 """
1470 Private slot to bookmark a link via the context menu.
1471 """
1472 act = self.sender()
1473 url = act.data()
1474 if url.isEmpty():
1475 return
1476
1477 from .Bookmarks.AddBookmarkDialog import AddBookmarkDialog
1478 dlg = AddBookmarkDialog()
1479 dlg.setUrl(bytes(url.toEncoded()).decode())
1480 dlg.exec_()
1481
1482 def __sendLink(self):
1483 """
1484 Private slot to send a link via email.
1485 """
1486 act = self.sender()
1487 data = act.data()
1488 if isinstance(data, QUrl) and data.isEmpty():
1489 return
1490
1491 if isinstance(data, QUrl):
1492 data = data.toString()
1493 QDesktopServices.openUrl(QUrl("mailto:?body=" + data))
1494
1495 def __downloadLink(self):
1496 """
1497 Private slot to download a link and save it to disk.
1498 """
1499 self.pageAction(QWebPage.DownloadLinkToDisk).trigger()
1500
1501 def __copyLink(self):
1502 """
1503 Private slot to copy a link to the clipboard.
1504 """
1505 self.pageAction(QWebPage.CopyLinkToClipboard).trigger()
1506
1507 def __downloadImage(self):
1508 """
1509 Private slot to download an image and save it to disk.
1510 """
1511 self.pageAction(QWebPage.DownloadImageToDisk).trigger()
1512
1513 def __copyImage(self):
1514 """
1515 Private slot to copy an image to the clipboard.
1516 """
1517 self.pageAction(QWebPage.CopyImageToClipboard).trigger()
1518
1519 def __copyLocation(self):
1520 """
1521 Private slot to copy an image or media location to the clipboard.
1522 """
1523 act = self.sender()
1524 url = act.data()
1525 QApplication.clipboard().setText(url)
1526
1527 def __blockImage(self):
1528 """
1529 Private slot to add a block rule for an image URL.
1530 """
1531 import Helpviewer.HelpWindow
1532 act = self.sender()
1533 url = act.data()
1534 dlg = Helpviewer.HelpWindow.HelpWindow.adBlockManager().showDialog()
1535 dlg.addCustomRule(url)
1536
1537 def __downloadMedia(self):
1538 """
1539 Private slot to download a media and save it to disk.
1540 """
1541 act = self.sender()
1542 url = act.data()
1543 self.mw.downloadManager().download(url, True, mainWindow=self.mw)
1544
1545 def __pauseMedia(self):
1546 """
1547 Private slot to pause or play the selected media.
1548 """
1549 paused = self.__clickedMediaElement.evaluateJavaScript("this.paused")
1550
1551 if paused:
1552 self.__clickedMediaElement.evaluateJavaScript("this.play()")
1553 else:
1554 self.__clickedMediaElement.evaluateJavaScript("this.pause()")
1555
1556 def __muteMedia(self):
1557 """
1558 Private slot to (un)mute the selected media.
1559 """
1560 muted = self.__clickedMediaElement.evaluateJavaScript("this.muted")
1561
1562 if muted:
1563 self.__clickedMediaElement.evaluateJavaScript("this.muted = false")
1564 else:
1565 self.__clickedMediaElement.evaluateJavaScript("this.muted = true")
1566
1567 def __virusTotal(self):
1568 """
1569 Private slot to scan the selected URL with VirusTotal.
1570 """
1571 act = self.sender()
1572 url = act.data()
1573 self.mw.requestVirusTotalScan(url)
1574
1575 def __searchRequested(self, act):
1576 """
1577 Private slot to search for some text with a selected search engine.
1578
1579 @param act reference to the action that triggered this slot (QAction)
1580 """
1581 searchText = self.selectedText()
1582
1583 if not searchText:
1584 return
1585
1586 engineName = act.data()
1587 if engineName:
1588 engine = self.mw.openSearchManager().engine(engineName)
1589 self.search.emit(engine.searchUrl(searchText))
1590
1591 def __addSearchEngine(self):
1592 """
1593 Private slot to add a new search engine.
1594 """
1595 act = self.sender()
1596 if act is None:
1597 return
1598
1599 element = act.data()
1600 elementName = element.attribute("name")
1601 formElement = QWebElement(element)
1602 while formElement.tagName().lower() != "form":
1603 formElement = formElement.parent()
1604
1605 if formElement.isNull() or \
1606 formElement.attribute("action") == "":
1607 return
1608
1609 method = formElement.attribute("method", "get").lower()
1610 if method != "get":
1611 E5MessageBox.warning(
1612 self,
1613 self.tr("Method not supported"),
1614 self.tr(
1615 """{0} method is not supported.""").format(method.upper()))
1616 return
1617
1618 searchUrl = QUrl(self.page().mainFrame().baseUrl().resolved(
1619 QUrl(formElement.attribute("action"))))
1620 if searchUrl.scheme() != "http":
1621 return
1622
1623 if qVersion() >= "5.0.0":
1624 from PyQt5.QtCore import QUrlQuery
1625 searchUrlQuery = QUrlQuery(searchUrl)
1626 searchEngines = {}
1627 inputFields = formElement.findAll("input")
1628 for inputField in inputFields.toList():
1629 type_ = inputField.attribute("type", "text")
1630 name = inputField.attribute("name")
1631 value = inputField.evaluateJavaScript("this.value")
1632
1633 if type_ == "submit":
1634 searchEngines[value] = name
1635 elif type_ == "text":
1636 if inputField == element:
1637 value = "{searchTerms}"
1638 if qVersion() >= "5.0.0":
1639 searchUrlQuery.addQueryItem(name, value)
1640 else:
1641 searchUrl.addQueryItem(name, value)
1642 elif type_ == "checkbox" or type_ == "radio":
1643 if inputField.evaluateJavaScript("this.checked"):
1644 if qVersion() >= "5.0.0":
1645 searchUrlQuery.addQueryItem(name, value)
1646 else:
1647 searchUrl.addQueryItem(name, value)
1648 elif type_ == "hidden":
1649 if qVersion() >= "5.0.0":
1650 searchUrlQuery.addQueryItem(name, value)
1651 else:
1652 searchUrl.addQueryItem(name, value)
1653
1654 selectFields = formElement.findAll("select")
1655 for selectField in selectFields.toList():
1656 name = selectField.attribute("name")
1657 selectedIndex = selectField.evaluateJavaScript(
1658 "this.selectedIndex")
1659 if selectedIndex == -1:
1660 continue
1661
1662 options = selectField.findAll("option")
1663 value = options.at(selectedIndex).toPlainText()
1664 if qVersion() >= "5.0.0":
1665 searchUrlQuery.addQueryItem(name, value)
1666 else:
1667 searchUrl.addQueryItem(name, value)
1668
1669 ok = True
1670 if len(searchEngines) > 1:
1671 searchEngine, ok = QInputDialog.getItem(
1672 self,
1673 self.tr("Search engine"),
1674 self.tr("Choose the desired search engine"),
1675 sorted(searchEngines.keys()), 0, False)
1676
1677 if not ok:
1678 return
1679
1680 if searchEngines[searchEngine] != "":
1681 if qVersion() >= "5.0.0":
1682 searchUrlQuery.addQueryItem(
1683 searchEngines[searchEngine], searchEngine)
1684 else:
1685 searchUrl.addQueryItem(
1686 searchEngines[searchEngine], searchEngine)
1687 engineName = ""
1688 labels = formElement.findAll('label[for="{0}"]'.format(elementName))
1689 if labels.count() > 0:
1690 engineName = labels.at(0).toPlainText()
1691
1692 engineName, ok = QInputDialog.getText(
1693 self,
1694 self.tr("Engine name"),
1695 self.tr("Enter a name for the engine"),
1696 QLineEdit.Normal,
1697 engineName)
1698 if not ok:
1699 return
1700
1701 if qVersion() >= "5.0.0":
1702 searchUrl.setQuery(searchUrlQuery)
1703
1704 from .OpenSearch.OpenSearchEngine import OpenSearchEngine
1705 engine = OpenSearchEngine()
1706 engine.setName(engineName)
1707 engine.setDescription(engineName)
1708 engine.setSearchUrlTemplate(searchUrl.toString())
1709 engine.setImage(self.icon().pixmap(16, 16).toImage())
1710
1711 self.mw.openSearchManager().addEngine(engine)
1712
1713 def __webInspector(self):
1714 """
1715 Private slot to show the web inspector window.
1716 """
1717 if self.__inspector is None:
1718 from .HelpInspector import HelpInspector
1719 self.__inspector = HelpInspector()
1720 self.__inspector.setPage(self.page())
1721 self.__inspector.show()
1722 elif self.__inspector.isVisible():
1723 self.__inspector.hide()
1724 else:
1725 self.__inspector.show()
1726
1727 def closeWebInspector(self):
1728 """
1729 Public slot to close the web inspector.
1730 """
1731 if self.__inspector is not None:
1732 if self.__inspector.isVisible():
1733 self.__inspector.hide()
1734 self.__inspector.deleteLater()
1735 self.__inspector = None
1736
1737 def addBookmark(self):
1738 """
1739 Public slot to bookmark the current page.
1740 """
1741 from .Bookmarks.AddBookmarkDialog import AddBookmarkDialog
1742 dlg = AddBookmarkDialog()
1743 dlg.setUrl(bytes(self.url().toEncoded()).decode())
1744 dlg.setTitle(self.title())
1745 meta = self.page().mainFrame().metaData()
1746 if "description" in meta:
1747 dlg.setDescription(meta["description"][0])
1748 dlg.exec_()
1749
1750 def dragEnterEvent(self, evt):
1751 """
1752 Protected method called by a drag enter event.
1753
1754 @param evt reference to the drag enter event (QDragEnterEvent)
1755 """
1756 evt.acceptProposedAction()
1757
1758 def dragMoveEvent(self, evt):
1759 """
1760 Protected method called by a drag move event.
1761
1762 @param evt reference to the drag move event (QDragMoveEvent)
1763 """
1764 evt.ignore()
1765 if evt.source() != self:
1766 if len(evt.mimeData().urls()) > 0:
1767 evt.acceptProposedAction()
1768 else:
1769 url = QUrl(evt.mimeData().text())
1770 if url.isValid():
1771 evt.acceptProposedAction()
1772
1773 if not evt.isAccepted():
1774 super(HelpBrowser, self).dragMoveEvent(evt)
1775
1776 def dropEvent(self, evt):
1777 """
1778 Protected method called by a drop event.
1779
1780 @param evt reference to the drop event (QDropEvent)
1781 """
1782 super(HelpBrowser, self).dropEvent(evt)
1783 if not evt.isAccepted() and \
1784 evt.source() != self and \
1785 evt.possibleActions() & Qt.CopyAction:
1786 url = QUrl()
1787 if len(evt.mimeData().urls()) > 0:
1788 url = evt.mimeData().urls()[0]
1789 if not url.isValid():
1790 url = QUrl(evt.mimeData().text())
1791 if url.isValid():
1792 self.setSource(url)
1793 evt.acceptProposedAction()
1794
1795 def mousePressEvent(self, evt):
1796 """
1797 Protected method called by a mouse press event.
1798
1799 @param evt reference to the mouse event (QMouseEvent)
1800 """
1801 self.mw.setEventMouseButtons(evt.buttons())
1802 self.mw.setEventKeyboardModifiers(evt.modifiers())
1803
1804 if evt.button() == Qt.XButton1:
1805 self.pageAction(QWebPage.Back).trigger()
1806 elif evt.button() == Qt.XButton2:
1807 self.pageAction(QWebPage.Forward).trigger()
1808 else:
1809 super(HelpBrowser, self).mousePressEvent(evt)
1810
1811 def mouseReleaseEvent(self, evt):
1812 """
1813 Protected method called by a mouse release event.
1814
1815 @param evt reference to the mouse event (QMouseEvent)
1816 """
1817 accepted = evt.isAccepted()
1818 self.__page.event(evt)
1819 if not evt.isAccepted() and \
1820 self.mw.eventMouseButtons() & Qt.MidButton:
1821 url = QUrl(QApplication.clipboard().text(QClipboard.Selection))
1822 if not url.isEmpty() and \
1823 url.isValid() and \
1824 url.scheme() != "":
1825 self.mw.setEventMouseButtons(Qt.NoButton)
1826 self.mw.setEventKeyboardModifiers(Qt.NoModifier)
1827 self.setSource(url)
1828 evt.setAccepted(accepted)
1829
1830 def wheelEvent(self, evt):
1831 """
1832 Protected method to handle wheel events.
1833
1834 @param evt reference to the wheel event (QWheelEvent)
1835 """
1836 if qVersion() >= "5.0.0":
1837 delta = evt.angleDelta().y()
1838 else:
1839 delta = evt.delta()
1840 if evt.modifiers() & Qt.ControlModifier:
1841 if delta < 0:
1842 self.zoomOut()
1843 else:
1844 self.zoomIn()
1845 evt.accept()
1846 return
1847
1848 if evt.modifiers() & Qt.ShiftModifier:
1849 if delta < 0:
1850 self.backward()
1851 else:
1852 self.forward()
1853 evt.accept()
1854 return
1855
1856 super(HelpBrowser, self).wheelEvent(evt)
1857
1858 def keyPressEvent(self, evt):
1859 """
1860 Protected method called by a key press.
1861
1862 @param evt reference to the key event (QKeyEvent)
1863 """
1864 if self.mw.personalInformationManager().viewKeyPressEvent(self, evt):
1865 return
1866
1867 if self.__enableAccessKeys:
1868 self.__accessKeysPressed = (
1869 evt.modifiers() == Qt.ControlModifier and
1870 evt.key() == Qt.Key_Control)
1871 if not self.__accessKeysPressed:
1872 if self.__checkForAccessKey(evt):
1873 self.__hideAccessKeys()
1874 evt.accept()
1875 return
1876 self.__hideAccessKeys()
1877 else:
1878 QTimer.singleShot(300, self.__accessKeyShortcut)
1879
1880 self.ctrlPressed = (evt.key() == Qt.Key_Control)
1881 super(HelpBrowser, self).keyPressEvent(evt)
1882
1883 def keyReleaseEvent(self, evt):
1884 """
1885 Protected method called by a key release.
1886
1887 @param evt reference to the key event (QKeyEvent)
1888 """
1889 if self.__enableAccessKeys:
1890 self.__accessKeysPressed = evt.key() == Qt.Key_Control
1891
1892 self.ctrlPressed = False
1893 super(HelpBrowser, self).keyReleaseEvent(evt)
1894
1895 def focusOutEvent(self, evt):
1896 """
1897 Protected method called by a focus out event.
1898
1899 @param evt reference to the focus event (QFocusEvent)
1900 """
1901 if self.__accessKeysPressed:
1902 self.__hideAccessKeys()
1903 self.__accessKeysPressed = False
1904
1905 super(HelpBrowser, self).focusOutEvent(evt)
1906
1907 def event(self, evt):
1908 """
1909 Public method handling events.
1910
1911 @param evt reference to the event (QEvent)
1912 @return flag indicating, if the event was handled (boolean)
1913 """
1914 if evt.type() == QEvent.Gesture:
1915 self.gestureEvent(evt)
1916 return True
1917
1918 return super(HelpBrowser, self).event(evt)
1919
1920 def gestureEvent(self, evt):
1921 """
1922 Protected method handling gesture events.
1923
1924 @param evt reference to the gesture event (QGestureEvent
1925 """
1926 pinch = evt.gesture(Qt.PinchGesture)
1927 if pinch:
1928 if pinch.state() == Qt.GestureStarted:
1929 pinch.setScaleFactor(self.__currentZoom / 100.0)
1930 else:
1931 scaleFactor = pinch.scaleFactor()
1932 self.__currentZoom = int(scaleFactor * 100)
1933 self.__applyZoom()
1934 evt.accept()
1935
1936 def clearHistory(self):
1937 """
1938 Public slot to clear the history.
1939 """
1940 self.history().clear()
1941 self.__urlChanged(self.history().currentItem().url())
1942
1943 ###########################################################################
1944 ## Signal converters below
1945 ###########################################################################
1946
1947 def __urlChanged(self, url):
1948 """
1949 Private slot to handle the urlChanged signal.
1950
1951 @param url the new url (QUrl)
1952 """
1953 self.sourceChanged.emit(url)
1954
1955 self.forwardAvailable.emit(self.isForwardAvailable())
1956 self.backwardAvailable.emit(self.isBackwardAvailable())
1957
1958 def __statusBarMessage(self, text):
1959 """
1960 Private slot to handle the statusBarMessage signal.
1961
1962 @param text text to be shown in the status bar (string)
1963 """
1964 self.mw.statusBar().showMessage(text)
1965
1966 def __linkHovered(self, link, title, textContent):
1967 """
1968 Private slot to handle the linkHovered signal.
1969
1970 @param link the URL of the link (string)
1971 @param title the link title (string)
1972 @param textContent text content of the link (string)
1973 """
1974 self.highlighted.emit(link)
1975
1976 ###########################################################################
1977 ## Signal handlers below
1978 ###########################################################################
1979
1980 def __loadStarted(self):
1981 """
1982 Private method to handle the loadStarted signal.
1983 """
1984 self.__isLoading = True
1985 self.__progress = 0
1986
1987 def __loadProgress(self, progress):
1988 """
1989 Private method to handle the loadProgress signal.
1990
1991 @param progress progress value (integer)
1992 """
1993 self.__progress = progress
1994
1995 def __loadFinished(self, ok):
1996 """
1997 Private method to handle the loadFinished signal.
1998
1999 @param ok flag indicating the result (boolean)
2000 """
2001 self.__isLoading = False
2002 self.__progress = 0
2003
2004 if Preferences.getHelp("ClickToFlashEnabled"):
2005 # this is a hack to make the ClickToFlash button appear
2006 self.zoomIn()
2007 self.zoomOut()
2008
2009 zoomValue = Helpviewer.HelpWindow.HelpWindow.zoomManager()\
2010 .zoomValue(self.url())
2011 self.setZoomValue(zoomValue)
2012
2013 if ok:
2014 self.mw.adBlockManager().page().hideBlockedPageEntries(self.page())
2015 self.mw.passwordManager().fill(self.page())
2016
2017 def isLoading(self):
2018 """
2019 Public method to get the loading state.
2020
2021 @return flag indicating the loading state (boolean)
2022 """
2023 return self.__isLoading
2024
2025 def progress(self):
2026 """
2027 Public method to get the load progress.
2028
2029 @return load progress (integer)
2030 """
2031 return self.__progress
2032
2033 def saveAs(self):
2034 """
2035 Public method to save the current page to a file.
2036 """
2037 url = self.url()
2038 if url.isEmpty():
2039 return
2040
2041 self.mw.downloadManager().download(url, True, mainWindow=self.mw)
2042
2043 def __unsupportedContent(self, reply, requestFilename=None,
2044 download=False):
2045 """
2046 Private slot to handle the unsupportedContent signal.
2047
2048 @param reply reference to the reply object (QNetworkReply)
2049 @keyparam requestFilename indicating to ask for a filename
2050 (boolean or None). If it is None, the behavior is determined
2051 by a configuration option.
2052 @keyparam download flag indicating a download operation (boolean)
2053 """
2054 if reply is None:
2055 return
2056
2057 replyUrl = reply.url()
2058
2059 if replyUrl.scheme() == "abp":
2060 return
2061
2062 if reply.error() == QNetworkReply.NoError:
2063 if reply.header(QNetworkRequest.ContentTypeHeader):
2064 self.mw.downloadManager().handleUnsupportedContent(
2065 reply, webPage=self.page(), mainWindow=self.mw)
2066 return
2067
2068 replyUrl = reply.url()
2069 if replyUrl.isEmpty():
2070 return
2071
2072 notFoundFrame = self.page().mainFrame()
2073 if notFoundFrame is None:
2074 return
2075
2076 if reply.header(QNetworkRequest.ContentTypeHeader):
2077 data = reply.readAll()
2078 if contentSniff(data):
2079 notFoundFrame.setHtml(str(data, encoding="utf-8"), replyUrl)
2080 return
2081
2082 urlString = bytes(replyUrl.toEncoded()).decode()
2083 title = self.tr("Error loading page: {0}").format(urlString)
2084 htmlFile = QFile(":/html/notFoundPage.html")
2085 htmlFile.open(QFile.ReadOnly)
2086 html = htmlFile.readAll()
2087 pixmap = qApp.style()\
2088 .standardIcon(QStyle.SP_MessageBoxWarning).pixmap(48, 48)
2089 imageBuffer = QBuffer()
2090 imageBuffer.open(QIODevice.ReadWrite)
2091 if pixmap.save(imageBuffer, "PNG"):
2092 html = html.replace("@IMAGE@", imageBuffer.buffer().toBase64())
2093 pixmap = qApp.style()\
2094 .standardIcon(QStyle.SP_MessageBoxWarning).pixmap(16, 16)
2095 imageBuffer = QBuffer()
2096 imageBuffer.open(QIODevice.ReadWrite)
2097 if pixmap.save(imageBuffer, "PNG"):
2098 html = html.replace("@FAVICON@", imageBuffer.buffer().toBase64())
2099 html = html.replace("@TITLE@", title.encode("utf8"))
2100 html = html.replace("@H1@", reply.errorString().encode("utf8"))
2101 html = html.replace(
2102 "@H2@", self.tr("When connecting to: {0}.")
2103 .format(urlString).encode("utf8"))
2104 html = html.replace(
2105 "@LI-1@",
2106 self.tr("Check the address for errors such as "
2107 "<b>ww</b>.example.org instead of "
2108 "<b>www</b>.example.org").encode("utf8"))
2109 html = html.replace(
2110 "@LI-2@",
2111 self.tr("If the address is correct, try checking the network "
2112 "connection.").encode("utf8"))
2113 html = html.replace(
2114 "@LI-3@",
2115 self.tr(
2116 "If your computer or network is protected by a firewall "
2117 "or proxy, make sure that the browser is permitted to "
2118 "access the network.").encode("utf8"))
2119 html = html.replace(
2120 "@LI-4@",
2121 self.tr("If your cache policy is set to offline browsing,"
2122 "only pages in the local cache are available.")
2123 .encode("utf8"))
2124 html = html.replace(
2125 "@BUTTON@", self.tr("Try Again").encode("utf8"))
2126 notFoundFrame.setHtml(bytes(html).decode("utf8"), replyUrl)
2127 self.mw.historyManager().removeHistoryEntry(replyUrl, self.title())
2128 self.loadFinished.emit(False)
2129
2130 def __featurePermissionRequested(self, frame, feature):
2131 """
2132 Private slot handling a feature permission request.
2133
2134 @param frame frame sending the request
2135 @type QWebFrame
2136 @param feature requested feature
2137 @type QWebPage.Feature
2138 """
2139 manager = Helpviewer.HelpWindow.HelpWindow.featurePermissionManager()
2140 manager.requestFeaturePermission(self.page(), frame, feature)
2141
2142 def __downloadRequested(self, request):
2143 """
2144 Private slot to handle a download request.
2145
2146 @param request reference to the request object (QNetworkRequest)
2147 """
2148 self.mw.downloadManager().download(request, mainWindow=self.mw)
2149
2150 def __databaseQuotaExceeded(self, frame, databaseName):
2151 """
2152 Private slot to handle the case, where the database quota is exceeded.
2153
2154 @param frame reference to the frame (QWebFrame)
2155 @param databaseName name of the web database (string)
2156 """
2157 securityOrigin = frame.securityOrigin()
2158 if securityOrigin.databaseQuota() > 0 and \
2159 securityOrigin.databaseUsage() == 0:
2160 # cope with a strange behavior of Qt 4.6, if a database is
2161 # accessed for the first time
2162 return
2163
2164 res = E5MessageBox.yesNo(
2165 self,
2166 self.tr("Web Database Quota"),
2167 self.tr(
2168 """<p>The database quota of <strong>{0}</strong> has"""
2169 """ been exceeded while accessing database <strong>{1}"""
2170 """</strong>.</p><p>Shall it be changed?</p>""")
2171 .format(self.__dataString(securityOrigin.databaseQuota()),
2172 databaseName),
2173 yesDefault=True)
2174 if res:
2175 newQuota, ok = QInputDialog.getInt(
2176 self,
2177 self.tr("New Web Database Quota"),
2178 self.tr(
2179 "Enter the new quota in MB (current = {0}, used = {1}; "
2180 "step size = 5 MB):"
2181 .format(
2182 self.__dataString(securityOrigin.databaseQuota()),
2183 self.__dataString(securityOrigin.databaseUsage()))),
2184 securityOrigin.databaseQuota() // (1024 * 1024),
2185 0, 2147483647, 5)
2186 if ok:
2187 securityOrigin.setDatabaseQuota(newQuota * 1024 * 1024)
2188
2189 def __dataString(self, size):
2190 """
2191 Private method to generate a formatted data string.
2192
2193 @param size size to be formatted (integer)
2194 @return formatted data string (string)
2195 """
2196 unit = ""
2197 if size < 1024:
2198 unit = self.tr("bytes")
2199 elif size < 1024 * 1024:
2200 size /= 1024
2201 unit = self.tr("kB")
2202 else:
2203 size /= 1024 * 1024
2204 unit = self.tr("MB")
2205 return "{0:.1f} {1}".format(size, unit)
2206
2207 ###########################################################################
2208 ## Access key related methods below
2209 ###########################################################################
2210
2211 def __accessKeyShortcut(self):
2212 """
2213 Private slot to switch the display of access keys.
2214 """
2215 if not self.hasFocus() or \
2216 not self.__accessKeysPressed or \
2217 not self.__enableAccessKeys:
2218 return
2219
2220 if self.__accessKeyLabels:
2221 self.__hideAccessKeys()
2222 else:
2223 self.__showAccessKeys()
2224
2225 self.__accessKeysPressed = False
2226
2227 def __checkForAccessKey(self, evt):
2228 """
2229 Private method to check the existence of an access key and activate the
2230 corresponding link.
2231
2232 @param evt reference to the key event (QKeyEvent)
2233 @return flag indicating, if the event was handled (boolean)
2234 """
2235 if not self.__accessKeyLabels:
2236 return False
2237
2238 text = evt.text()
2239 if not text:
2240 return False
2241
2242 key = text[0].upper()
2243 handled = False
2244 if key in self.__accessKeyNodes:
2245 element = self.__accessKeyNodes[key]
2246 p = element.geometry().center()
2247 frame = element.webFrame()
2248 p -= frame.scrollPosition()
2249 frame = frame.parentFrame()
2250 while frame and frame != self.page().mainFrame():
2251 p -= frame.scrollPosition()
2252 frame = frame.parentFrame()
2253 pevent = QMouseEvent(
2254 QEvent.MouseButtonPress, p, Qt.LeftButton,
2255 Qt.MouseButtons(Qt.NoButton),
2256 Qt.KeyboardModifiers(Qt.NoModifier))
2257 qApp.sendEvent(self, pevent)
2258 revent = QMouseEvent(
2259 QEvent.MouseButtonRelease, p, Qt.LeftButton,
2260 Qt.MouseButtons(Qt.NoButton),
2261 Qt.KeyboardModifiers(Qt.NoModifier))
2262 qApp.sendEvent(self, revent)
2263 handled = True
2264
2265 return handled
2266
2267 def __hideAccessKeys(self):
2268 """
2269 Private slot to hide the access key labels.
2270 """
2271 if self.__accessKeyLabels:
2272 for label in self.__accessKeyLabels:
2273 label.hide()
2274 label.deleteLater()
2275 self.__accessKeyLabels = []
2276 self.__accessKeyNodes = {}
2277 self.update()
2278
2279 def __showAccessKeys(self):
2280 """
2281 Private method to show the access key labels.
2282 """
2283 supportedElements = [
2284 "input", "a", "area", "button", "label", "legend", "textarea",
2285 ]
2286 unusedKeys = "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z" \
2287 " 0 1 2 3 4 5 6 7 8 9".split()
2288
2289 viewport = QRect(self.__page.mainFrame().scrollPosition(),
2290 self.__page.viewportSize())
2291 # Priority first goes to elements with accesskey attributes
2292 alreadyLabeled = []
2293 for elementType in supportedElements:
2294 result = self.page().mainFrame().findAllElements(elementType)\
2295 .toList()
2296 for element in result:
2297 geometry = element.geometry()
2298 if geometry.size().isEmpty() or \
2299 not viewport.contains(geometry.topLeft()):
2300 continue
2301
2302 accessKeyAttribute = element.attribute("accesskey").upper()
2303 if not accessKeyAttribute:
2304 continue
2305
2306 accessKey = ""
2307 i = 0
2308 while i < len(accessKeyAttribute):
2309 if accessKeyAttribute[i] in unusedKeys:
2310 accessKey = accessKeyAttribute[i]
2311 break
2312 i += 2
2313 if accessKey == "":
2314 continue
2315 unusedKeys.remove(accessKey)
2316 self.__makeAccessLabel(accessKey, element)
2317 alreadyLabeled.append(element)
2318
2319 # Pick an access key first from the letters in the text and then
2320 # from the list of unused access keys
2321 for elementType in supportedElements:
2322 result = self.page().mainFrame().findAllElements(elementType)\
2323 .toList()
2324 for element in result:
2325 geometry = element.geometry()
2326 if not unusedKeys or \
2327 element in alreadyLabeled or \
2328 geometry.size().isEmpty() or \
2329 not viewport.contains(geometry.topLeft()):
2330 continue
2331
2332 accessKey = ""
2333 text = element.toPlainText().upper()
2334 for c in text:
2335 if c in unusedKeys:
2336 accessKey = c
2337 break
2338 if accessKey == "":
2339 accessKey = unusedKeys[0]
2340 unusedKeys.remove(accessKey)
2341 self.__makeAccessLabel(accessKey, element)
2342
2343 def __makeAccessLabel(self, accessKey, element):
2344 """
2345 Private method to generate the access label for an element.
2346
2347 @param accessKey access key to generate the label for (str)
2348 @param element reference to the web element to create the label for
2349 (QWebElement)
2350 """
2351 label = QLabel(self)
2352 label.setText("<qt><b>{0}</b></qt>".format(accessKey))
2353
2354 p = QToolTip.palette()
2355 color = QColor(Qt.yellow).lighter(150)
2356 color.setAlpha(175)
2357 p.setColor(QPalette.Window, color)
2358 label.setPalette(p)
2359 label.setAutoFillBackground(True)
2360 label.setFrameStyle(QFrame.Box | QFrame.Plain)
2361 point = element.geometry().center()
2362 point -= self.__page.mainFrame().scrollPosition()
2363 label.move(point)
2364 label.show()
2365 point.setX(point.x() - label.width() // 2)
2366 label.move(point)
2367 self.__accessKeyLabels.append(label)
2368 self.__accessKeyNodes[accessKey] = element
2369
2370 ###########################################################################
2371 ## Miscellaneous methods below
2372 ###########################################################################
2373
2374 def createWindow(self, windowType):
2375 """
2376 Public method called, when a new window should be created.
2377
2378 @param windowType type of the requested window (QWebPage.WebWindowType)
2379 @return reference to the created browser window (HelpBrowser)
2380 """
2381 self.mw.newTab(addNextTo=self)
2382 return self.mw.currentBrowser()
2383
2384 def preferencesChanged(self):
2385 """
2386 Public method to indicate a change of the settings.
2387 """
2388 self.__enableAccessKeys = Preferences.getHelp("AccessKeysEnabled")
2389 if not self.__enableAccessKeys:
2390 self.__hideAccessKeys()
2391
2392 self.reload()
2393
2394 ###########################################################################
2395 ## RSS related methods below
2396 ###########################################################################
2397
2398 def checkRSS(self):
2399 """
2400 Public method to check, if the loaded page contains feed links.
2401
2402 @return flag indicating the existence of feed links (boolean)
2403 """
2404 self.__rss = []
2405
2406 frame = self.page().mainFrame()
2407 linkElementsList = frame.findAllElements("link").toList()
2408
2409 for linkElement in linkElementsList:
2410 # only atom+xml and rss+xml will be processed
2411 if linkElement.attribute("rel") != "alternate" or \
2412 (linkElement.attribute("type") != "application/rss+xml" and
2413 linkElement.attribute("type") != "application/atom+xml"):
2414 continue
2415
2416 title = linkElement.attribute("title")
2417 href = linkElement.attribute("href")
2418 if href == "" or title == "":
2419 continue
2420 self.__rss.append((title, href))
2421
2422 return len(self.__rss) > 0
2423
2424 def getRSS(self):
2425 """
2426 Public method to get the extracted RSS feeds.
2427
2428 @return list of RSS feeds (list of tuples of two strings)
2429 """
2430 return self.__rss
2431
2432 def hasRSS(self):
2433 """
2434 Public method to check, if the loaded page has RSS links.
2435
2436 @return flag indicating the presence of RSS links (boolean)
2437 """
2438 return len(self.__rss) > 0
2439
2440 ###########################################################################
2441 ## Clicked Frame slots
2442 ###########################################################################
2443
2444 def __loadClickedFrame(self):
2445 """
2446 Private slot to load the selected frame only.
2447 """
2448 self.setSource(self.__clickedFrame.url())
2449
2450 def __printClickedFrame(self):
2451 """
2452 Private slot to print the selected frame.
2453 """
2454 printer = QPrinter(mode=QPrinter.HighResolution)
2455 if Preferences.getPrinter("ColorMode"):
2456 printer.setColorMode(QPrinter.Color)
2457 else:
2458 printer.setColorMode(QPrinter.GrayScale)
2459 if Preferences.getPrinter("FirstPageFirst"):
2460 printer.setPageOrder(QPrinter.FirstPageFirst)
2461 else:
2462 printer.setPageOrder(QPrinter.LastPageFirst)
2463 printer.setPageMargins(
2464 Preferences.getPrinter("LeftMargin") * 10,
2465 Preferences.getPrinter("TopMargin") * 10,
2466 Preferences.getPrinter("RightMargin") * 10,
2467 Preferences.getPrinter("BottomMargin") * 10,
2468 QPrinter.Millimeter
2469 )
2470 printerName = Preferences.getPrinter("PrinterName")
2471 if printerName:
2472 printer.setPrinterName(printerName)
2473
2474 printDialog = QPrintDialog(printer, self)
2475 if printDialog.exec_() == QDialog.Accepted:
2476 try:
2477 self.__clickedFrame.print_(printer)
2478 except AttributeError:
2479 E5MessageBox.critical(
2480 self,
2481 self.tr("eric6 Web Browser"),
2482 self.tr(
2483 """<p>Printing is not available due to a bug in"""
2484 """ PyQt5. Please upgrade.</p>"""))
2485
2486 def __printPreviewClickedFrame(self):
2487 """
2488 Private slot to show a print preview of the clicked frame.
2489 """
2490 from PyQt5.QtPrintSupport import QPrintPreviewDialog
2491
2492 printer = QPrinter(mode=QPrinter.HighResolution)
2493 if Preferences.getPrinter("ColorMode"):
2494 printer.setColorMode(QPrinter.Color)
2495 else:
2496 printer.setColorMode(QPrinter.GrayScale)
2497 if Preferences.getPrinter("FirstPageFirst"):
2498 printer.setPageOrder(QPrinter.FirstPageFirst)
2499 else:
2500 printer.setPageOrder(QPrinter.LastPageFirst)
2501 printer.setPageMargins(
2502 Preferences.getPrinter("LeftMargin") * 10,
2503 Preferences.getPrinter("TopMargin") * 10,
2504 Preferences.getPrinter("RightMargin") * 10,
2505 Preferences.getPrinter("BottomMargin") * 10,
2506 QPrinter.Millimeter
2507 )
2508 printerName = Preferences.getPrinter("PrinterName")
2509 if printerName:
2510 printer.setPrinterName(printerName)
2511
2512 preview = QPrintPreviewDialog(printer, self)
2513 preview.paintRequested.connect(self.__generatePrintPreviewClickedFrame)
2514 preview.exec_()
2515
2516 def __generatePrintPreviewClickedFrame(self, printer):
2517 """
2518 Private slot to generate a print preview of the clicked frame.
2519
2520 @param printer reference to the printer object (QPrinter)
2521 """
2522 try:
2523 self.__clickedFrame.print_(printer)
2524 except AttributeError:
2525 E5MessageBox.critical(
2526 self,
2527 self.tr("eric6 Web Browser"),
2528 self.tr(
2529 """<p>Printing is not available due to a bug in PyQt5."""
2530 """Please upgrade.</p>"""))
2531 return
2532
2533 def __printPdfClickedFrame(self):
2534 """
2535 Private slot to print the selected frame to PDF.
2536 """
2537 printer = QPrinter(mode=QPrinter.HighResolution)
2538 if Preferences.getPrinter("ColorMode"):
2539 printer.setColorMode(QPrinter.Color)
2540 else:
2541 printer.setColorMode(QPrinter.GrayScale)
2542 printerName = Preferences.getPrinter("PrinterName")
2543 if printerName:
2544 printer.setPrinterName(printerName)
2545 printer.setOutputFormat(QPrinter.PdfFormat)
2546 name = self.__clickedFrame.url().path().rsplit('/', 1)[-1]
2547 if name:
2548 name = name.rsplit('.', 1)[0]
2549 name += '.pdf'
2550 printer.setOutputFileName(name)
2551
2552 printDialog = QPrintDialog(printer, self)
2553 if printDialog.exec_() == QDialog.Accepted:
2554 try:
2555 self.__clickedFrame.print_(printer)
2556 except AttributeError:
2557 E5MessageBox.critical(
2558 self,
2559 self.tr("eric6 Web Browser"),
2560 self.tr(
2561 """<p>Printing is not available due to a bug in"""
2562 """ PyQt5. Please upgrade.</p>"""))
2563 return
2564
2565 def __zoomInClickedFrame(self):
2566 """
2567 Private slot to zoom into the clicked frame.
2568 """
2569 index = self.__levelForZoom(
2570 int(self.__clickedFrame.zoomFactor() * 100))
2571 if index < len(self.__zoomLevels) - 1:
2572 self.__clickedFrame.setZoomFactor(
2573 self.__zoomLevels[index + 1] / 100)
2574
2575 def __zoomResetClickedFrame(self):
2576 """
2577 Private slot to reset the zoom factor of the clicked frame.
2578 """
2579 self.__clickedFrame.setZoomFactor(self.__currentZoom / 100)
2580
2581 def __zoomOutClickedFrame(self):
2582 """
2583 Private slot to zoom out of the clicked frame.
2584 """
2585 index = self.__levelForZoom(
2586 int(self.__clickedFrame.zoomFactor() * 100))
2587 if index > 0:
2588 self.__clickedFrame.setZoomFactor(
2589 self.__zoomLevels[index - 1] / 100)
2590
2591 def __showClickedFrameSource(self):
2592 """
2593 Private slot to show the source of the clicked frame.
2594 """
2595 from QScintilla.MiniEditor import MiniEditor
2596 src = self.__clickedFrame.toHtml()
2597 editor = MiniEditor(parent=self)
2598 editor.setText(src, "Html")
2599 editor.setLanguage("dummy.html")
2600 editor.show()
2601
2602
2603 def contentSniff(data):
2604 """
2605 Module function to do some content sniffing to check, if the data is HTML.
2606
2607 @param data data block to sniff at (string)
2608 @return flag indicating HTML content (boolean)
2609 """
2610 if data.contains("<!doctype") or \
2611 data.contains("<script") or \
2612 data.contains("<html") or \
2613 data.contains("<!--") or \
2614 data.contains("<head") or \
2615 data.contains("<iframe") or \
2616 data.contains("<h1") or \
2617 data.contains("<div") or \
2618 data.contains("<font") or \
2619 data.contains("<table") or \
2620 data.contains("<a") or \
2621 data.contains("<style") or \
2622 data.contains("<title") or \
2623 data.contains("<b") or \
2624 data.contains("<body") or \
2625 data.contains("<br") or \
2626 data.contains("<p"):
2627 return True
2628
2629 return False

eric ide

mercurial