|
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 |