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