eric6/Helpviewer/HelpBrowserWV.py

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

eric ide

mercurial