|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2008 - 2019 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 |
|
7 """ |
|
8 Module implementing the web browser using QWebEngineView. |
|
9 """ |
|
10 |
|
11 from __future__ import unicode_literals |
|
12 try: |
|
13 str = unicode # __IGNORE_EXCEPTION__ |
|
14 except NameError: |
|
15 pass |
|
16 |
|
17 import os |
|
18 |
|
19 from PyQt5.QtCore import pyqtSignal, pyqtSlot, PYQT_VERSION, Qt, QUrl, \ |
|
20 QFileInfo, QTimer, QEvent, QPoint, QPointF, QDateTime, QStandardPaths, \ |
|
21 QByteArray, QIODevice, QDataStream |
|
22 from PyQt5.QtGui import QDesktopServices, QClipboard, QIcon, \ |
|
23 QContextMenuEvent, QPixmap, QCursor |
|
24 from PyQt5.QtWidgets import qApp, QStyle, QMenu, QApplication, QDialog |
|
25 from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage, \ |
|
26 QWebEngineDownloadItem |
|
27 |
|
28 from E5Gui import E5MessageBox, E5FileDialog |
|
29 |
|
30 from WebBrowser.WebBrowserWindow import WebBrowserWindow |
|
31 from .WebBrowserPage import WebBrowserPage |
|
32 |
|
33 from .Tools.WebIconLoader import WebIconLoader |
|
34 from .Tools import Scripts |
|
35 |
|
36 from . import WebInspector |
|
37 from .Tools.WebBrowserTools import readAllFileContents, pixmapToDataUrl |
|
38 |
|
39 import Preferences |
|
40 import UI.PixmapCache |
|
41 import Utilities |
|
42 from Globals import qVersionTuple |
|
43 |
|
44 |
|
45 class WebBrowserView(QWebEngineView): |
|
46 """ |
|
47 Class implementing the web browser view widget. |
|
48 |
|
49 @signal sourceChanged(QUrl) emitted after the current URL has changed |
|
50 @signal forwardAvailable(bool) emitted after the current URL has changed |
|
51 @signal backwardAvailable(bool) emitted after the current URL has changed |
|
52 @signal highlighted(str) emitted, when the mouse hovers over a link |
|
53 @signal search(QUrl) emitted, when a search is requested |
|
54 @signal zoomValueChanged(int) emitted to signal a change of the zoom value |
|
55 @signal faviconChanged() emitted to signal a changed web site icon |
|
56 @signal safeBrowsingAbort() emitted to indicate an abort due to a safe |
|
57 browsing event |
|
58 @signal safeBrowsingBad(threatType, threatMessages) emitted to indicate a |
|
59 malicious web site as determined by safe browsing |
|
60 """ |
|
61 sourceChanged = pyqtSignal(QUrl) |
|
62 forwardAvailable = pyqtSignal(bool) |
|
63 backwardAvailable = pyqtSignal(bool) |
|
64 highlighted = pyqtSignal(str) |
|
65 search = pyqtSignal(QUrl) |
|
66 zoomValueChanged = pyqtSignal(int) |
|
67 faviconChanged = pyqtSignal() |
|
68 safeBrowsingAbort = pyqtSignal() |
|
69 safeBrowsingBad = pyqtSignal(str, str) |
|
70 |
|
71 ZoomLevels = [ |
|
72 30, 40, 50, 67, 80, 90, |
|
73 100, |
|
74 110, 120, 133, 150, 170, 200, 220, 233, 250, 270, 285, 300, |
|
75 ] |
|
76 ZoomLevelDefault = 100 |
|
77 |
|
78 def __init__(self, mainWindow, parent=None, name=""): |
|
79 """ |
|
80 Constructor |
|
81 |
|
82 @param mainWindow reference to the main window (WebBrowserWindow) |
|
83 @param parent parent widget of this window (QWidget) |
|
84 @param name name of this window (string) |
|
85 """ |
|
86 super(WebBrowserView, self).__init__(parent) |
|
87 self.setObjectName(name) |
|
88 |
|
89 self.__rwhvqt = None |
|
90 self.installEventFilter(self) |
|
91 |
|
92 self.__speedDial = WebBrowserWindow.speedDial() |
|
93 |
|
94 self.__page = None |
|
95 self.__createNewPage() |
|
96 |
|
97 self.__mw = mainWindow |
|
98 self.__tabWidget = parent |
|
99 self.__isLoading = False |
|
100 self.__progress = 0 |
|
101 self.__siteIconLoader = None |
|
102 self.__siteIcon = QIcon() |
|
103 self.__menu = QMenu(self) |
|
104 self.__clickedPos = QPoint() |
|
105 self.__firstLoad = False |
|
106 self.__preview = QPixmap() |
|
107 |
|
108 self.__currentZoom = 100 |
|
109 self.__zoomLevels = WebBrowserView.ZoomLevels[:] |
|
110 |
|
111 self.iconUrlChanged.connect(self.__iconUrlChanged) |
|
112 self.urlChanged.connect(self.__urlChanged) |
|
113 self.page().linkHovered.connect(self.__linkHovered) |
|
114 |
|
115 self.loadStarted.connect(self.__loadStarted) |
|
116 self.loadProgress.connect(self.__loadProgress) |
|
117 self.loadFinished.connect(self.__loadFinished) |
|
118 self.renderProcessTerminated.connect(self.__renderProcessTerminated) |
|
119 |
|
120 self.__mw.openSearchManager().currentEngineChanged.connect( |
|
121 self.__currentEngineChanged) |
|
122 |
|
123 self.setAcceptDrops(True) |
|
124 |
|
125 self.__rss = [] |
|
126 |
|
127 self.__clickedFrame = None |
|
128 |
|
129 self.__mw.personalInformationManager().connectPage(self.page()) |
|
130 |
|
131 self.__inspector = None |
|
132 WebInspector.registerView(self) |
|
133 |
|
134 self.__restoreData = None |
|
135 |
|
136 if qVersionTuple() >= (5, 8, 0): |
|
137 if self.parentWidget() is not None: |
|
138 self.parentWidget().installEventFilter(self) |
|
139 |
|
140 if qVersionTuple() >= (5, 8, 0) and qVersionTuple() < (5, 11, 0): |
|
141 lay = self.layout() |
|
142 lay.currentChanged.connect( |
|
143 lambda: QTimer.singleShot(0, self.__setRwhvqt)) |
|
144 self.__setRwhvqt() |
|
145 |
|
146 self.grabGesture(Qt.PinchGesture) |
|
147 |
|
148 def __createNewPage(self): |
|
149 """ |
|
150 Private method to create a new page object. |
|
151 """ |
|
152 self.__page = WebBrowserPage(self) |
|
153 self.setPage(self.__page) |
|
154 |
|
155 self.__page.safeBrowsingAbort.connect(self.safeBrowsingAbort) |
|
156 self.__page.safeBrowsingBad.connect(self.safeBrowsingBad) |
|
157 self.__page.printPageRequested.connect(self.__printPage) |
|
158 try: |
|
159 self.__page.quotaRequested.connect(self.__quotaRequested) |
|
160 # The registerProtocolHandlerRequested signal is handled in |
|
161 # WebBrowserPage. |
|
162 except AttributeError: |
|
163 # pre Qt 5.11 |
|
164 pass |
|
165 try: |
|
166 self.__page.selectClientCertificate.connect( |
|
167 self.__selectClientCertificate) |
|
168 except AttributeError: |
|
169 # pre Qt 5.12 |
|
170 pass |
|
171 |
|
172 def __setRwhvqt(self): |
|
173 """ |
|
174 Private slot to set widget that receives input events. |
|
175 """ |
|
176 self.grabGesture(Qt.PinchGesture) |
|
177 self.__rwhvqt = self.focusProxy() |
|
178 if self.__rwhvqt: |
|
179 self.__rwhvqt.grabGesture(Qt.PinchGesture) |
|
180 self.__rwhvqt.installEventFilter(self) |
|
181 else: |
|
182 print("Focus proxy is null!") # __IGNORE_WARNING_M801__ |
|
183 |
|
184 def __currentEngineChanged(self): |
|
185 """ |
|
186 Private slot to track a change of the current search engine. |
|
187 """ |
|
188 if self.url().toString() == "eric:home": |
|
189 self.reload() |
|
190 |
|
191 def mainWindow(self): |
|
192 """ |
|
193 Public method to get a reference to the main window. |
|
194 |
|
195 @return reference to the main window |
|
196 @rtype WebBrowserWindow |
|
197 """ |
|
198 return self.__mw |
|
199 |
|
200 def tabWidget(self): |
|
201 """ |
|
202 Public method to get a reference to the tab widget containing this |
|
203 view. |
|
204 |
|
205 @return reference to the tab widget |
|
206 @rtype WebBrowserTabWidget |
|
207 """ |
|
208 return self.__tabWidget |
|
209 |
|
210 def load(self, url): |
|
211 """ |
|
212 Public method to load a web site. |
|
213 |
|
214 @param url URL to be loaded |
|
215 @type QUrl |
|
216 """ |
|
217 if self.__page is not None and \ |
|
218 not self.__page.acceptNavigationRequest( |
|
219 url, QWebEnginePage.NavigationTypeTyped, True): |
|
220 return |
|
221 |
|
222 super(WebBrowserView, self).load(url) |
|
223 |
|
224 if not self.__firstLoad: |
|
225 self.__firstLoad = True |
|
226 WebInspector.pushView(self) |
|
227 |
|
228 def setSource(self, name, newTab=False): |
|
229 """ |
|
230 Public method used to set the source to be displayed. |
|
231 |
|
232 @param name filename to be shown (QUrl) |
|
233 @param newTab flag indicating to open the URL in a new tab (bool) |
|
234 """ |
|
235 if name is None or not name.isValid(): |
|
236 return |
|
237 |
|
238 if newTab: |
|
239 # open in a new tab |
|
240 self.__mw.newTab(name) |
|
241 return |
|
242 |
|
243 if not name.scheme(): |
|
244 if not os.path.exists(name.toString()): |
|
245 name.setScheme(Preferences.getWebBrowser("DefaultScheme")) |
|
246 else: |
|
247 if Utilities.isWindowsPlatform(): |
|
248 name.setUrl("file:///" + Utilities.fromNativeSeparators( |
|
249 name.toString())) |
|
250 else: |
|
251 name.setUrl("file://" + name.toString()) |
|
252 |
|
253 if len(name.scheme()) == 1 or \ |
|
254 name.scheme() == "file": |
|
255 # name is a local file |
|
256 if name.scheme() and len(name.scheme()) == 1: |
|
257 # it is a local path on win os |
|
258 name = QUrl.fromLocalFile(name.toString()) |
|
259 |
|
260 if not QFileInfo(name.toLocalFile()).exists(): |
|
261 E5MessageBox.critical( |
|
262 self, |
|
263 self.tr("eric6 Web Browser"), |
|
264 self.tr( |
|
265 """<p>The file <b>{0}</b> does not exist.</p>""") |
|
266 .format(name.toLocalFile())) |
|
267 return |
|
268 |
|
269 if name.toLocalFile().lower().endswith((".pdf", ".chm")): |
|
270 started = QDesktopServices.openUrl(name) |
|
271 if not started: |
|
272 E5MessageBox.critical( |
|
273 self, |
|
274 self.tr("eric6 Web Browser"), |
|
275 self.tr( |
|
276 """<p>Could not start a viewer""" |
|
277 """ for file <b>{0}</b>.</p>""") |
|
278 .format(name.path())) |
|
279 return |
|
280 elif name.scheme() in ["mailto"]: |
|
281 started = QDesktopServices.openUrl(name) |
|
282 if not started: |
|
283 E5MessageBox.critical( |
|
284 self, |
|
285 self.tr("eric6 Web Browser"), |
|
286 self.tr( |
|
287 """<p>Could not start an application""" |
|
288 """ for URL <b>{0}</b>.</p>""") |
|
289 .format(name.toString())) |
|
290 return |
|
291 else: |
|
292 if name.toString().lower().endswith((".pdf", ".chm")): |
|
293 started = QDesktopServices.openUrl(name) |
|
294 if not started: |
|
295 E5MessageBox.critical( |
|
296 self, |
|
297 self.tr("eric6 Web Browser"), |
|
298 self.tr( |
|
299 """<p>Could not start a viewer""" |
|
300 """ for file <b>{0}</b>.</p>""") |
|
301 .format(name.path())) |
|
302 return |
|
303 |
|
304 self.load(name) |
|
305 |
|
306 def source(self): |
|
307 """ |
|
308 Public method to return the URL of the loaded page. |
|
309 |
|
310 @return URL loaded in the help browser (QUrl) |
|
311 """ |
|
312 return self.url() |
|
313 |
|
314 def documentTitle(self): |
|
315 """ |
|
316 Public method to return the title of the loaded page. |
|
317 |
|
318 @return title (string) |
|
319 """ |
|
320 return self.title() |
|
321 |
|
322 def backward(self): |
|
323 """ |
|
324 Public slot to move backwards in history. |
|
325 """ |
|
326 self.triggerPageAction(QWebEnginePage.Back) |
|
327 self.__urlChanged(self.history().currentItem().url()) |
|
328 |
|
329 def forward(self): |
|
330 """ |
|
331 Public slot to move forward in history. |
|
332 """ |
|
333 self.triggerPageAction(QWebEnginePage.Forward) |
|
334 self.__urlChanged(self.history().currentItem().url()) |
|
335 |
|
336 def home(self): |
|
337 """ |
|
338 Public slot to move to the first page loaded. |
|
339 """ |
|
340 homeUrl = QUrl(Preferences.getWebBrowser("HomePage")) |
|
341 self.setSource(homeUrl) |
|
342 self.__urlChanged(self.history().currentItem().url()) |
|
343 |
|
344 def reload(self): |
|
345 """ |
|
346 Public slot to reload the current page. |
|
347 """ |
|
348 self.triggerPageAction(QWebEnginePage.Reload) |
|
349 |
|
350 def reloadBypassingCache(self): |
|
351 """ |
|
352 Public slot to reload the current page bypassing the cache. |
|
353 """ |
|
354 self.triggerPageAction(QWebEnginePage.ReloadAndBypassCache) |
|
355 |
|
356 def copy(self): |
|
357 """ |
|
358 Public slot to copy the selected text. |
|
359 """ |
|
360 self.triggerPageAction(QWebEnginePage.Copy) |
|
361 |
|
362 def cut(self): |
|
363 """ |
|
364 Public slot to cut the selected text. |
|
365 """ |
|
366 self.triggerPageAction(QWebEnginePage.Cut) |
|
367 |
|
368 def paste(self): |
|
369 """ |
|
370 Public slot to paste text from the clipboard. |
|
371 """ |
|
372 self.triggerPageAction(QWebEnginePage.Paste) |
|
373 |
|
374 def undo(self): |
|
375 """ |
|
376 Public slot to undo the last edit action. |
|
377 """ |
|
378 self.triggerPageAction(QWebEnginePage.Undo) |
|
379 |
|
380 def redo(self): |
|
381 """ |
|
382 Public slot to redo the last edit action. |
|
383 """ |
|
384 self.triggerPageAction(QWebEnginePage.Redo) |
|
385 |
|
386 def selectAll(self): |
|
387 """ |
|
388 Public slot to select all text. |
|
389 """ |
|
390 self.triggerPageAction(QWebEnginePage.SelectAll) |
|
391 |
|
392 def unselect(self): |
|
393 """ |
|
394 Public slot to clear the current selection. |
|
395 """ |
|
396 try: |
|
397 self.triggerPageAction(QWebEnginePage.Unselect) |
|
398 except AttributeError: |
|
399 # prior to 5.7.0 |
|
400 self.page().runJavaScript( |
|
401 "window.getSelection().empty()", |
|
402 WebBrowserPage.SafeJsWorld) |
|
403 |
|
404 def isForwardAvailable(self): |
|
405 """ |
|
406 Public method to determine, if a forward move in history is possible. |
|
407 |
|
408 @return flag indicating move forward is possible (boolean) |
|
409 """ |
|
410 return self.history().canGoForward() |
|
411 |
|
412 def isBackwardAvailable(self): |
|
413 """ |
|
414 Public method to determine, if a backwards move in history is possible. |
|
415 |
|
416 @return flag indicating move backwards is possible (boolean) |
|
417 """ |
|
418 return self.history().canGoBack() |
|
419 |
|
420 def __levelForZoom(self, zoom): |
|
421 """ |
|
422 Private method determining the zoom level index given a zoom factor. |
|
423 |
|
424 @param zoom zoom factor (integer) |
|
425 @return index of zoom factor (integer) |
|
426 """ |
|
427 try: |
|
428 index = self.__zoomLevels.index(zoom) |
|
429 except ValueError: |
|
430 for index in range(len(self.__zoomLevels)): |
|
431 if zoom <= self.__zoomLevels[index]: |
|
432 break |
|
433 return index |
|
434 |
|
435 def setZoomValue(self, value, saveValue=True): |
|
436 """ |
|
437 Public method to set the zoom value. |
|
438 |
|
439 @param value zoom value (integer) |
|
440 @keyparam saveValue flag indicating to save the zoom value with the |
|
441 zoom manager |
|
442 @type bool |
|
443 """ |
|
444 if value != self.__currentZoom: |
|
445 self.setZoomFactor(value / 100.0) |
|
446 self.__currentZoom = value |
|
447 if saveValue and not self.__mw.isPrivate(): |
|
448 from .ZoomManager import ZoomManager |
|
449 ZoomManager.instance().setZoomValue(self.url(), value) |
|
450 self.zoomValueChanged.emit(value) |
|
451 |
|
452 def zoomValue(self): |
|
453 """ |
|
454 Public method to get the current zoom value. |
|
455 |
|
456 @return zoom value (integer) |
|
457 """ |
|
458 val = self.zoomFactor() * 100 |
|
459 return int(val) |
|
460 |
|
461 def zoomIn(self): |
|
462 """ |
|
463 Public slot to zoom into the page. |
|
464 """ |
|
465 index = self.__levelForZoom(self.__currentZoom) |
|
466 if index < len(self.__zoomLevels) - 1: |
|
467 self.setZoomValue(self.__zoomLevels[index + 1]) |
|
468 |
|
469 def zoomOut(self): |
|
470 """ |
|
471 Public slot to zoom out of the page. |
|
472 """ |
|
473 index = self.__levelForZoom(self.__currentZoom) |
|
474 if index > 0: |
|
475 self.setZoomValue(self.__zoomLevels[index - 1]) |
|
476 |
|
477 def zoomReset(self): |
|
478 """ |
|
479 Public method to reset the zoom factor. |
|
480 """ |
|
481 index = self.__levelForZoom(WebBrowserView.ZoomLevelDefault) |
|
482 self.setZoomValue(self.__zoomLevels[index]) |
|
483 |
|
484 def mapToViewport(self, pos): |
|
485 """ |
|
486 Public method to map a position to the viewport. |
|
487 |
|
488 @param pos position to be mapped |
|
489 @type QPoint |
|
490 @return viewport position |
|
491 @rtype QPoint |
|
492 """ |
|
493 return self.page().mapToViewport(pos) |
|
494 |
|
495 def hasSelection(self): |
|
496 """ |
|
497 Public method to determine, if there is some text selected. |
|
498 |
|
499 @return flag indicating text has been selected (boolean) |
|
500 """ |
|
501 return self.selectedText() != "" |
|
502 |
|
503 def findNextPrev(self, txt, case, backwards, callback): |
|
504 """ |
|
505 Public slot to find the next occurrence of a text. |
|
506 |
|
507 @param txt text to search for (string) |
|
508 @param case flag indicating a case sensitive search (boolean) |
|
509 @param backwards flag indicating a backwards search (boolean) |
|
510 @param callback reference to a function with a bool parameter |
|
511 @type function(bool) or None |
|
512 """ |
|
513 findFlags = QWebEnginePage.FindFlags() |
|
514 if case: |
|
515 findFlags |= QWebEnginePage.FindCaseSensitively |
|
516 if backwards: |
|
517 findFlags |= QWebEnginePage.FindBackward |
|
518 |
|
519 if callback is None: |
|
520 self.findText(txt, findFlags) |
|
521 else: |
|
522 self.findText(txt, findFlags, callback) |
|
523 |
|
524 def contextMenuEvent(self, evt): |
|
525 """ |
|
526 Protected method called to create a context menu. |
|
527 |
|
528 This method is overridden from QWebEngineView. |
|
529 |
|
530 @param evt reference to the context menu event object |
|
531 (QContextMenuEvent) |
|
532 """ |
|
533 pos = evt.pos() |
|
534 reason = evt.reason() |
|
535 QTimer.singleShot( |
|
536 0, |
|
537 lambda: self._contextMenuEvent(QContextMenuEvent(reason, pos))) |
|
538 # needs to be done this way because contextMenuEvent is blocking |
|
539 # the main loop |
|
540 |
|
541 def _contextMenuEvent(self, evt): |
|
542 """ |
|
543 Protected method called to create a context menu. |
|
544 |
|
545 This method is overridden from QWebEngineView. |
|
546 |
|
547 @param evt reference to the context menu event object |
|
548 (QContextMenuEvent) |
|
549 """ |
|
550 self.__menu.clear() |
|
551 |
|
552 hitTest = self.page().hitTestContent(evt.pos()) |
|
553 |
|
554 self.__createContextMenu(self.__menu, hitTest) |
|
555 |
|
556 if not hitTest.isContentEditable() and not hitTest.isContentSelected(): |
|
557 self.__menu.addSeparator() |
|
558 self.__menu.addAction(self.__mw.adBlockIcon().menuAction()) |
|
559 |
|
560 if qVersionTuple() >= (5, 11, 0) or \ |
|
561 Preferences.getWebBrowser("WebInspectorEnabled"): |
|
562 self.__menu.addSeparator() |
|
563 self.__menu.addAction( |
|
564 UI.PixmapCache.getIcon("webInspector.png"), |
|
565 self.tr("Inspect Element..."), self.__webInspector) |
|
566 |
|
567 if not self.__menu.isEmpty(): |
|
568 pos = evt.globalPos() |
|
569 self.__menu.popup(QPoint(pos.x(), pos.y() + 1)) |
|
570 |
|
571 def __createContextMenu(self, menu, hitTest): |
|
572 """ |
|
573 Private method to populate the context menu. |
|
574 |
|
575 @param menu reference to the menu to be populated |
|
576 @type QMenu |
|
577 @param hitTest reference to the hit test object |
|
578 @type WebHitTestResult |
|
579 """ |
|
580 spellCheckActionCount = 0 |
|
581 if qVersionTuple() >= (5, 7, 0) and PYQT_VERSION >= 0x50700: |
|
582 contextMenuData = self.page().contextMenuData() |
|
583 hitTest.updateWithContextMenuData(contextMenuData) |
|
584 |
|
585 if qVersionTuple() >= (5, 8, 0) and PYQT_VERSION >= 0x50800 and \ |
|
586 bool(contextMenuData.misspelledWord()): |
|
587 boldFont = menu.font() |
|
588 boldFont.setBold(True) |
|
589 |
|
590 for suggestion in contextMenuData.spellCheckerSuggestions(): |
|
591 act = menu.addAction(suggestion) |
|
592 act.setFont(boldFont) |
|
593 act.triggered.connect( |
|
594 lambda: self.__replaceMisspelledWord)(act) |
|
595 |
|
596 if not bool(menu.actions()): |
|
597 menu.addAction(self.tr("No suggestions")).setEnabled(False) |
|
598 |
|
599 menu.addSeparator() |
|
600 spellCheckActionCount = len(menu.actions()) |
|
601 |
|
602 if not hitTest.linkUrl().isEmpty() and \ |
|
603 hitTest.linkUrl().scheme() != "javascript": |
|
604 self.__createLinkContextMenu(menu, hitTest) |
|
605 |
|
606 if not hitTest.imageUrl().isEmpty(): |
|
607 self.__createImageContextMenu(menu, hitTest) |
|
608 |
|
609 if not hitTest.mediaUrl().isEmpty(): |
|
610 self.__createMediaContextMenu(menu, hitTest) |
|
611 |
|
612 if hitTest.isContentEditable(): |
|
613 # check, if only spell checker actions were added |
|
614 if len(menu.actions()) == spellCheckActionCount: |
|
615 menu.addAction(self.__mw.undoAct) |
|
616 menu.addAction(self.__mw.redoAct) |
|
617 menu.addSeparator() |
|
618 menu.addAction(self.__mw.cutAct) |
|
619 menu.addAction(self.__mw.copyAct) |
|
620 menu.addAction(self.__mw.pasteAct) |
|
621 menu.addSeparator() |
|
622 self.__mw.personalInformationManager().createSubMenu( |
|
623 menu, self, hitTest) |
|
624 |
|
625 if hitTest.tagName() == "input": |
|
626 menu.addSeparator() |
|
627 act = menu.addAction("") |
|
628 act.setVisible(False) |
|
629 self.__checkForForm(act, hitTest.pos()) |
|
630 |
|
631 if self.selectedText(): |
|
632 self.__createSelectedTextContextMenu(menu, hitTest) |
|
633 |
|
634 if self.__menu.isEmpty(): |
|
635 self.__createPageContextMenu(menu) |
|
636 |
|
637 def __createLinkContextMenu(self, menu, hitTest): |
|
638 """ |
|
639 Private method to populate the context menu for URLs. |
|
640 |
|
641 @param menu reference to the menu to be populated |
|
642 @type QMenu |
|
643 @param hitTest reference to the hit test object |
|
644 @type WebHitTestResult |
|
645 """ |
|
646 if not menu.isEmpty(): |
|
647 menu.addSeparator() |
|
648 |
|
649 act = menu.addAction( |
|
650 UI.PixmapCache.getIcon("openNewTab.png"), |
|
651 self.tr("Open Link in New Tab\tCtrl+LMB")) |
|
652 act.setData(hitTest.linkUrl()) |
|
653 act.triggered.connect( |
|
654 lambda: self.__openLinkInNewTab(act)) |
|
655 act = menu.addAction( |
|
656 UI.PixmapCache.getIcon("newWindow.png"), |
|
657 self.tr("Open Link in New Window")) |
|
658 act.setData(hitTest.linkUrl()) |
|
659 act.triggered.connect( |
|
660 lambda: self.__openLinkInNewWindow(act)) |
|
661 act = menu.addAction( |
|
662 UI.PixmapCache.getIcon("privateMode.png"), |
|
663 self.tr("Open Link in New Private Window")) |
|
664 act.setData(hitTest.linkUrl()) |
|
665 act.triggered.connect( |
|
666 lambda: self.__openLinkInNewPrivateWindow(act)) |
|
667 menu.addSeparator() |
|
668 menu.addAction( |
|
669 UI.PixmapCache.getIcon("download.png"), |
|
670 self.tr("Save Lin&k"), self.__downloadLink) |
|
671 act = menu.addAction( |
|
672 UI.PixmapCache.getIcon("bookmark22.png"), |
|
673 self.tr("Bookmark this Link")) |
|
674 act.setData(hitTest.linkUrl()) |
|
675 act.triggered.connect( |
|
676 lambda: self.__bookmarkLink(act)) |
|
677 menu.addSeparator() |
|
678 act = menu.addAction( |
|
679 UI.PixmapCache.getIcon("editCopy.png"), |
|
680 self.tr("Copy Link to Clipboard")) |
|
681 act.setData(hitTest.linkUrl()) |
|
682 act.triggered.connect( |
|
683 lambda: self.__copyLink(act)) |
|
684 act = menu.addAction( |
|
685 UI.PixmapCache.getIcon("mailSend.png"), |
|
686 self.tr("Send Link")) |
|
687 act.setData(hitTest.linkUrl()) |
|
688 act.triggered.connect( |
|
689 lambda: self.__sendLink(act)) |
|
690 if Preferences.getWebBrowser("VirusTotalEnabled") and \ |
|
691 Preferences.getWebBrowser("VirusTotalServiceKey") != "": |
|
692 act = menu.addAction( |
|
693 UI.PixmapCache.getIcon("virustotal.png"), |
|
694 self.tr("Scan Link with VirusTotal")) |
|
695 act.setData(hitTest.linkUrl()) |
|
696 act.triggered.connect( |
|
697 lambda: self.__virusTotal(act)) |
|
698 |
|
699 def __createImageContextMenu(self, menu, hitTest): |
|
700 """ |
|
701 Private method to populate the context menu for images. |
|
702 |
|
703 @param menu reference to the menu to be populated |
|
704 @type QMenu |
|
705 @param hitTest reference to the hit test object |
|
706 @type WebHitTestResult |
|
707 """ |
|
708 if not menu.isEmpty(): |
|
709 menu.addSeparator() |
|
710 |
|
711 act = menu.addAction( |
|
712 UI.PixmapCache.getIcon("openNewTab.png"), |
|
713 self.tr("Open Image in New Tab")) |
|
714 act.setData(hitTest.imageUrl()) |
|
715 act.triggered.connect( |
|
716 lambda: self.__openLinkInNewTab(act)) |
|
717 menu.addSeparator() |
|
718 menu.addAction( |
|
719 UI.PixmapCache.getIcon("download.png"), |
|
720 self.tr("Save Image"), self.__downloadImage) |
|
721 menu.addAction( |
|
722 self.tr("Copy Image to Clipboard"), self.__copyImage) |
|
723 act = menu.addAction( |
|
724 UI.PixmapCache.getIcon("editCopy.png"), |
|
725 self.tr("Copy Image Location to Clipboard")) |
|
726 act.setData(hitTest.imageUrl()) |
|
727 act.triggered.connect( |
|
728 lambda: self.__copyLink(act)) |
|
729 act = menu.addAction( |
|
730 UI.PixmapCache.getIcon("mailSend.png"), |
|
731 self.tr("Send Image Link")) |
|
732 act.setData(hitTest.imageUrl()) |
|
733 act.triggered.connect( |
|
734 lambda: self.__sendLink(act)) |
|
735 |
|
736 if hitTest.imageUrl().scheme() in ["http", "https"]: |
|
737 menu.addSeparator() |
|
738 engine = WebBrowserWindow.imageSearchEngine() |
|
739 searchEngineName = engine.searchEngine() |
|
740 act = menu.addAction( |
|
741 UI.PixmapCache.getIcon("{0}.png".format( |
|
742 searchEngineName.lower())), |
|
743 self.tr("Search image in {0}").format(searchEngineName)) |
|
744 act.setData(engine.getSearchQuery(hitTest.imageUrl())) |
|
745 act.triggered.connect( |
|
746 lambda: self.__searchImage(act)) |
|
747 self.__imageSearchMenu = menu.addMenu( |
|
748 self.tr("Search image with...")) |
|
749 for searchEngineName in engine.searchEngineNames(): |
|
750 act = self.__imageSearchMenu.addAction( |
|
751 UI.PixmapCache.getIcon("{0}.png".format( |
|
752 searchEngineName.lower())), |
|
753 self.tr("Search image in {0}").format(searchEngineName)) |
|
754 act.setData(engine.getSearchQuery( |
|
755 hitTest.imageUrl(), searchEngineName)) |
|
756 act.triggered.connect( |
|
757 lambda: self.__searchImage(act)) |
|
758 |
|
759 menu.addSeparator() |
|
760 act = menu.addAction( |
|
761 UI.PixmapCache.getIcon("adBlockPlus.png"), |
|
762 self.tr("Block Image")) |
|
763 act.setData(hitTest.imageUrl().toString()) |
|
764 act.triggered.connect( |
|
765 lambda: self.__blockImage(act)) |
|
766 if Preferences.getWebBrowser("VirusTotalEnabled") and \ |
|
767 Preferences.getWebBrowser("VirusTotalServiceKey") != "": |
|
768 act = menu.addAction( |
|
769 UI.PixmapCache.getIcon("virustotal.png"), |
|
770 self.tr("Scan Image with VirusTotal")) |
|
771 act.setData(hitTest.imageUrl()) |
|
772 act.triggered.connect( |
|
773 lambda: self.__virusTotal(act)) |
|
774 |
|
775 def __createMediaContextMenu(self, menu, hitTest): |
|
776 """ |
|
777 Private method to populate the context menu for media elements. |
|
778 |
|
779 @param menu reference to the menu to be populated |
|
780 @type QMenu |
|
781 @param hitTest reference to the hit test object |
|
782 @type WebHitTestResult |
|
783 """ |
|
784 if not menu.isEmpty(): |
|
785 menu.addSeparator() |
|
786 |
|
787 if hitTest.mediaPaused(): |
|
788 menu.addAction( |
|
789 UI.PixmapCache.getIcon("mediaPlaybackStart.png"), |
|
790 self.tr("Play"), self.__pauseMedia) |
|
791 else: |
|
792 menu.addAction( |
|
793 UI.PixmapCache.getIcon("mediaPlaybackPause.png"), |
|
794 self.tr("Pause"), self.__pauseMedia) |
|
795 if hitTest.mediaMuted(): |
|
796 menu.addAction( |
|
797 UI.PixmapCache.getIcon("audioVolumeHigh.png"), |
|
798 self.tr("Unmute"), self.__muteMedia) |
|
799 else: |
|
800 menu.addAction( |
|
801 UI.PixmapCache.getIcon("audioVolumeMuted.png"), |
|
802 self.tr("Mute"), self.__muteMedia) |
|
803 menu.addSeparator() |
|
804 act = menu.addAction( |
|
805 UI.PixmapCache.getIcon("editCopy.png"), |
|
806 self.tr("Copy Media Address to Clipboard")) |
|
807 act.setData(hitTest.mediaUrl()) |
|
808 act.triggered.connect( |
|
809 lambda: self.__copyLink(act)) |
|
810 act = menu.addAction( |
|
811 UI.PixmapCache.getIcon("mailSend.png"), |
|
812 self.tr("Send Media Address")) |
|
813 act.setData(hitTest.mediaUrl()) |
|
814 act.triggered.connect( |
|
815 lambda: self.__sendLink(act)) |
|
816 menu.addAction( |
|
817 UI.PixmapCache.getIcon("download.png"), |
|
818 self.tr("Save Media"), self.__downloadMedia) |
|
819 |
|
820 def __createSelectedTextContextMenu(self, menu, hitTest): |
|
821 """ |
|
822 Private method to populate the context menu for selected text. |
|
823 |
|
824 @param menu reference to the menu to be populated |
|
825 @type QMenu |
|
826 @param hitTest reference to the hit test object |
|
827 @type WebHitTestResult |
|
828 """ |
|
829 if not menu.isEmpty(): |
|
830 menu.addSeparator() |
|
831 |
|
832 menu.addAction(self.__mw.copyAct) |
|
833 menu.addSeparator() |
|
834 act = menu.addAction( |
|
835 UI.PixmapCache.getIcon("mailSend.png"), |
|
836 self.tr("Send Text")) |
|
837 act.setData(self.selectedText()) |
|
838 act.triggered.connect( |
|
839 lambda: self.__sendLink(act)) |
|
840 |
|
841 engineName = self.__mw.openSearchManager().currentEngineName() |
|
842 if engineName: |
|
843 menu.addAction(self.tr("Search with '{0}'").format(engineName), |
|
844 self.__searchDefaultRequested) |
|
845 |
|
846 from .OpenSearch.OpenSearchEngineAction import \ |
|
847 OpenSearchEngineAction |
|
848 |
|
849 self.__searchMenu = menu.addMenu(self.tr("Search with...")) |
|
850 engineNames = self.__mw.openSearchManager().allEnginesNames() |
|
851 for engineName in engineNames: |
|
852 engine = self.__mw.openSearchManager().engine(engineName) |
|
853 act = OpenSearchEngineAction(engine, self.__searchMenu) |
|
854 act.setData(engineName) |
|
855 self.__searchMenu.addAction(act) |
|
856 self.__searchMenu.triggered.connect(self.__searchRequested) |
|
857 |
|
858 menu.addSeparator() |
|
859 |
|
860 from .WebBrowserLanguagesDialog import WebBrowserLanguagesDialog |
|
861 languages = Preferences.toList( |
|
862 Preferences.Prefs.settings.value( |
|
863 "WebBrowser/AcceptLanguages", |
|
864 WebBrowserLanguagesDialog.defaultAcceptLanguages())) |
|
865 if languages: |
|
866 language = languages[0] |
|
867 langCode = language.split("[")[1][:2] |
|
868 googleTranslatorUrl = QUrl( |
|
869 "http://translate.google.com/#auto/{0}/{1}".format( |
|
870 langCode, self.selectedText())) |
|
871 act = menu.addAction( |
|
872 UI.PixmapCache.getIcon("translate.png"), |
|
873 self.tr("Google Translate")) |
|
874 act.setData(googleTranslatorUrl) |
|
875 act.triggered.connect( |
|
876 lambda: self.__openLinkInNewTab(act)) |
|
877 wiktionaryUrl = QUrl( |
|
878 "http://{0}.wiktionary.org/wiki/Special:Search?search={1}" |
|
879 .format(langCode, self.selectedText())) |
|
880 act = menu.addAction( |
|
881 UI.PixmapCache.getIcon("wikipedia.png"), |
|
882 self.tr("Dictionary")) |
|
883 act.setData(wiktionaryUrl) |
|
884 act.triggered.connect( |
|
885 lambda: self.__openLinkInNewTab(act)) |
|
886 menu.addSeparator() |
|
887 |
|
888 guessedUrl = QUrl.fromUserInput(self.selectedText().strip()) |
|
889 if self.__isUrlValid(guessedUrl): |
|
890 act = menu.addAction(self.tr("Go to web address")) |
|
891 act.setData(guessedUrl) |
|
892 act.triggered.connect( |
|
893 lambda: self.__openLinkInNewTab(act)) |
|
894 |
|
895 def __createPageContextMenu(self, menu): |
|
896 """ |
|
897 Private method to populate the basic context menu. |
|
898 |
|
899 @param menu reference to the menu to be populated |
|
900 @type QMenu |
|
901 """ |
|
902 menu.addAction(self.__mw.newTabAct) |
|
903 menu.addAction(self.__mw.newAct) |
|
904 menu.addSeparator() |
|
905 if self.__mw.saveAsAct is not None: |
|
906 menu.addAction(self.__mw.saveAsAct) |
|
907 menu.addAction(self.__mw.saveVisiblePageScreenAct) |
|
908 menu.addSeparator() |
|
909 |
|
910 if self.url().toString() == "eric:speeddial": |
|
911 # special menu for the spedd dial page |
|
912 menu.addAction(self.__mw.backAct) |
|
913 menu.addAction(self.__mw.forwardAct) |
|
914 menu.addSeparator() |
|
915 menu.addAction( |
|
916 UI.PixmapCache.getIcon("plus.png"), |
|
917 self.tr("Add New Page"), self.__addSpeedDial) |
|
918 menu.addAction( |
|
919 UI.PixmapCache.getIcon("preferences-general.png"), |
|
920 self.tr("Configure Speed Dial"), self.__configureSpeedDial) |
|
921 menu.addSeparator() |
|
922 menu.addAction( |
|
923 UI.PixmapCache.getIcon("reload.png"), |
|
924 self.tr("Reload All Dials"), self.__reloadAllSpeedDials) |
|
925 menu.addSeparator() |
|
926 menu.addAction( |
|
927 self.tr("Reset to Default Dials"), self.__resetSpeedDials) |
|
928 return |
|
929 |
|
930 menu.addAction( |
|
931 UI.PixmapCache.getIcon("bookmark22.png"), |
|
932 self.tr("Bookmark this Page"), self.addBookmark) |
|
933 act = menu.addAction( |
|
934 UI.PixmapCache.getIcon("editCopy.png"), |
|
935 self.tr("Copy Page Link")) |
|
936 act.setData(self.url()) |
|
937 act.triggered.connect( |
|
938 lambda: self.__copyLink(act)) |
|
939 act = menu.addAction( |
|
940 UI.PixmapCache.getIcon("mailSend.png"), |
|
941 self.tr("Send Page Link")) |
|
942 act.setData(self.url()) |
|
943 act.triggered.connect( |
|
944 lambda: self.__sendLink(act)) |
|
945 menu.addSeparator() |
|
946 |
|
947 from .UserAgent.UserAgentMenu import UserAgentMenu |
|
948 self.__userAgentMenu = UserAgentMenu(self.tr("User Agent"), |
|
949 url=self.url()) |
|
950 menu.addMenu(self.__userAgentMenu) |
|
951 menu.addSeparator() |
|
952 menu.addAction(self.__mw.backAct) |
|
953 menu.addAction(self.__mw.forwardAct) |
|
954 menu.addAction(self.__mw.homeAct) |
|
955 menu.addAction(self.__mw.reloadAct) |
|
956 menu.addAction(self.__mw.stopAct) |
|
957 menu.addSeparator() |
|
958 menu.addAction(self.__mw.zoomInAct) |
|
959 menu.addAction(self.__mw.zoomResetAct) |
|
960 menu.addAction(self.__mw.zoomOutAct) |
|
961 menu.addSeparator() |
|
962 menu.addAction(self.__mw.selectAllAct) |
|
963 menu.addSeparator() |
|
964 menu.addAction(self.__mw.findAct) |
|
965 menu.addSeparator() |
|
966 menu.addAction(self.__mw.pageSourceAct) |
|
967 menu.addSeparator() |
|
968 menu.addAction(self.__mw.siteInfoAct) |
|
969 if self.url().scheme() in ["http", "https"]: |
|
970 menu.addSeparator() |
|
971 |
|
972 w3url = QUrl.fromEncoded( |
|
973 b"http://validator.w3.org/check?uri=" + |
|
974 QUrl.toPercentEncoding(bytes(self.url().toEncoded()).decode())) |
|
975 act = menu.addAction( |
|
976 UI.PixmapCache.getIcon("w3.png"), |
|
977 self.tr("Validate Page")) |
|
978 act.setData(w3url) |
|
979 act.triggered.connect( |
|
980 lambda: self.__openLinkInNewTab(act)) |
|
981 |
|
982 from .WebBrowserLanguagesDialog import WebBrowserLanguagesDialog |
|
983 languages = Preferences.toList( |
|
984 Preferences.Prefs.settings.value( |
|
985 "WebBrowser/AcceptLanguages", |
|
986 WebBrowserLanguagesDialog.defaultAcceptLanguages())) |
|
987 if languages: |
|
988 language = languages[0] |
|
989 langCode = language.split("[")[1][:2] |
|
990 googleTranslatorUrl = QUrl.fromEncoded( |
|
991 b"http://translate.google.com/translate?sl=auto&tl=" + |
|
992 langCode.encode() + |
|
993 b"&u=" + |
|
994 QUrl.toPercentEncoding( |
|
995 bytes(self.url().toEncoded()).decode())) |
|
996 act = menu.addAction( |
|
997 UI.PixmapCache.getIcon("translate.png"), |
|
998 self.tr("Google Translate")) |
|
999 act.setData(googleTranslatorUrl) |
|
1000 act.triggered.connect( |
|
1001 lambda: self.__openLinkInNewTab(act)) |
|
1002 |
|
1003 def __checkForForm(self, act, pos): |
|
1004 """ |
|
1005 Private method to check the given position for an open search form. |
|
1006 |
|
1007 @param act reference to the action to be populated upon success |
|
1008 @type QAction |
|
1009 @param pos position to be tested |
|
1010 @type QPoint |
|
1011 """ |
|
1012 self.__clickedPos = self.mapToViewport(pos) |
|
1013 |
|
1014 from .Tools import Scripts |
|
1015 script = Scripts.getFormData(self.__clickedPos) |
|
1016 self.page().runJavaScript( |
|
1017 script, |
|
1018 WebBrowserPage.SafeJsWorld, |
|
1019 lambda res: self.__checkForFormCallback(res, act)) |
|
1020 |
|
1021 def __checkForFormCallback(self, res, act): |
|
1022 """ |
|
1023 Private method handling the __checkForForm result. |
|
1024 |
|
1025 @param res result dictionary generated by JavaScript |
|
1026 @type dict |
|
1027 @param act reference to the action to be populated upon success |
|
1028 @type QAction |
|
1029 """ |
|
1030 if act is None or not bool(res): |
|
1031 return |
|
1032 |
|
1033 url = QUrl(res["action"]) |
|
1034 method = res["method"] |
|
1035 |
|
1036 if not url.isEmpty() and method in ["get", "post"]: |
|
1037 act.setVisible(True) |
|
1038 act.setText(self.tr("Add to web search toolbar")) |
|
1039 act.triggered.connect(self.__addSearchEngine) |
|
1040 |
|
1041 def __isUrlValid(self, url): |
|
1042 """ |
|
1043 Private method to check a URL for validity. |
|
1044 |
|
1045 @param url URL to be checked (QUrl) |
|
1046 @return flag indicating a valid URL (boolean) |
|
1047 """ |
|
1048 return url.isValid() and \ |
|
1049 bool(url.host()) and \ |
|
1050 bool(url.scheme()) and \ |
|
1051 "." in url.host() |
|
1052 |
|
1053 def __replaceMisspelledWord(self, act): |
|
1054 """ |
|
1055 Private slot to replace a misspelled word under the context menu. |
|
1056 |
|
1057 @param act reference to the action that triggered |
|
1058 @type QAction |
|
1059 """ |
|
1060 suggestion = act.text() |
|
1061 self.page().replaceMisspelledWord(suggestion) |
|
1062 |
|
1063 def __openLinkInNewTab(self, act): |
|
1064 """ |
|
1065 Private method called by the context menu to open a link in a new |
|
1066 tab. |
|
1067 |
|
1068 @param act reference to the action that triggered |
|
1069 @type QAction |
|
1070 """ |
|
1071 url = act.data() |
|
1072 if url.isEmpty(): |
|
1073 return |
|
1074 |
|
1075 self.setSource(url, newTab=True) |
|
1076 |
|
1077 def __openLinkInNewWindow(self, act): |
|
1078 """ |
|
1079 Private slot called by the context menu to open a link in a new |
|
1080 window. |
|
1081 |
|
1082 @param act reference to the action that triggered |
|
1083 @type QAction |
|
1084 """ |
|
1085 url = act.data() |
|
1086 if url.isEmpty(): |
|
1087 return |
|
1088 |
|
1089 self.__mw.newWindow(url) |
|
1090 |
|
1091 def __openLinkInNewPrivateWindow(self, act): |
|
1092 """ |
|
1093 Private slot called by the context menu to open a link in a new |
|
1094 private window. |
|
1095 |
|
1096 @param act reference to the action that triggered |
|
1097 @type QAction |
|
1098 """ |
|
1099 url = act.data() |
|
1100 if url.isEmpty(): |
|
1101 return |
|
1102 |
|
1103 self.__mw.newPrivateWindow(url) |
|
1104 |
|
1105 def __bookmarkLink(self, act): |
|
1106 """ |
|
1107 Private slot to bookmark a link via the context menu. |
|
1108 |
|
1109 @param act reference to the action that triggered |
|
1110 @type QAction |
|
1111 """ |
|
1112 url = act.data() |
|
1113 if url.isEmpty(): |
|
1114 return |
|
1115 |
|
1116 from .Bookmarks.AddBookmarkDialog import AddBookmarkDialog |
|
1117 dlg = AddBookmarkDialog() |
|
1118 dlg.setUrl(bytes(url.toEncoded()).decode()) |
|
1119 dlg.exec_() |
|
1120 |
|
1121 def __sendLink(self, act): |
|
1122 """ |
|
1123 Private slot to send a link via email. |
|
1124 |
|
1125 @param act reference to the action that triggered |
|
1126 @type QAction |
|
1127 """ |
|
1128 data = act.data() |
|
1129 if isinstance(data, QUrl) and data.isEmpty(): |
|
1130 return |
|
1131 |
|
1132 if isinstance(data, QUrl): |
|
1133 data = data.toString() |
|
1134 QDesktopServices.openUrl(QUrl("mailto:?body=" + data)) |
|
1135 |
|
1136 def __copyLink(self, act): |
|
1137 """ |
|
1138 Private slot to copy a link to the clipboard. |
|
1139 |
|
1140 @param act reference to the action that triggered |
|
1141 @type QAction |
|
1142 """ |
|
1143 data = act.data() |
|
1144 if isinstance(data, QUrl) and data.isEmpty(): |
|
1145 return |
|
1146 |
|
1147 if isinstance(data, QUrl): |
|
1148 data = data.toString() |
|
1149 QApplication.clipboard().setText(data) |
|
1150 |
|
1151 def __downloadLink(self): |
|
1152 """ |
|
1153 Private slot to download a link and save it to disk. |
|
1154 """ |
|
1155 self.triggerPageAction(QWebEnginePage.DownloadLinkToDisk) |
|
1156 |
|
1157 def __downloadImage(self): |
|
1158 """ |
|
1159 Private slot to download an image and save it to disk. |
|
1160 """ |
|
1161 self.triggerPageAction(QWebEnginePage.DownloadImageToDisk) |
|
1162 |
|
1163 def __copyImage(self): |
|
1164 """ |
|
1165 Private slot to copy an image to the clipboard. |
|
1166 """ |
|
1167 self.triggerPageAction(QWebEnginePage.CopyImageToClipboard) |
|
1168 |
|
1169 def __blockImage(self, act): |
|
1170 """ |
|
1171 Private slot to add a block rule for an image URL. |
|
1172 |
|
1173 @param act reference to the action that triggered |
|
1174 @type QAction |
|
1175 """ |
|
1176 url = act.data() |
|
1177 dlg = WebBrowserWindow.adBlockManager().showDialog() |
|
1178 dlg.addCustomRule(url) |
|
1179 |
|
1180 def __searchImage(self, act): |
|
1181 """ |
|
1182 Private slot to search for an image URL. |
|
1183 |
|
1184 @param act reference to the action that triggered |
|
1185 @type QAction |
|
1186 """ |
|
1187 url = act.data() |
|
1188 self.setSource(url, newTab=True) |
|
1189 |
|
1190 def __downloadMedia(self): |
|
1191 """ |
|
1192 Private slot to download a media and save it to disk. |
|
1193 """ |
|
1194 self.triggerPageAction(QWebEnginePage.DownloadMediaToDisk) |
|
1195 |
|
1196 def __pauseMedia(self): |
|
1197 """ |
|
1198 Private slot to pause or play the selected media. |
|
1199 """ |
|
1200 self.triggerPageAction(QWebEnginePage.ToggleMediaPlayPause) |
|
1201 |
|
1202 def __muteMedia(self): |
|
1203 """ |
|
1204 Private slot to (un)mute the selected media. |
|
1205 """ |
|
1206 self.triggerPageAction(QWebEnginePage.ToggleMediaMute) |
|
1207 |
|
1208 def __virusTotal(self, act): |
|
1209 """ |
|
1210 Private slot to scan the selected URL with VirusTotal. |
|
1211 |
|
1212 @param act reference to the action that triggered |
|
1213 @type QAction |
|
1214 """ |
|
1215 url = act.data() |
|
1216 self.__mw.requestVirusTotalScan(url) |
|
1217 |
|
1218 def __searchDefaultRequested(self): |
|
1219 """ |
|
1220 Private slot to search for some text with the current search engine. |
|
1221 """ |
|
1222 searchText = self.selectedText() |
|
1223 |
|
1224 if not searchText: |
|
1225 return |
|
1226 |
|
1227 engine = self.__mw.openSearchManager().currentEngine() |
|
1228 if engine: |
|
1229 self.search.emit(engine.searchUrl(searchText)) |
|
1230 |
|
1231 def __searchRequested(self, act): |
|
1232 """ |
|
1233 Private slot to search for some text with a selected search engine. |
|
1234 |
|
1235 @param act reference to the action that triggered this slot (QAction) |
|
1236 """ |
|
1237 searchText = self.selectedText() |
|
1238 |
|
1239 if not searchText: |
|
1240 return |
|
1241 |
|
1242 engineName = act.data() |
|
1243 if engineName: |
|
1244 engine = self.__mw.openSearchManager().engine(engineName) |
|
1245 else: |
|
1246 engine = self.__mw.openSearchManager().currentEngine() |
|
1247 if engine: |
|
1248 self.search.emit(engine.searchUrl(searchText)) |
|
1249 |
|
1250 def __addSearchEngine(self): |
|
1251 """ |
|
1252 Private slot to add a new search engine. |
|
1253 """ |
|
1254 from .Tools import Scripts |
|
1255 script = Scripts.getFormData(self.__clickedPos) |
|
1256 self.page().runJavaScript( |
|
1257 script, |
|
1258 WebBrowserPage.SafeJsWorld, |
|
1259 lambda res: self.__mw.openSearchManager().addEngineFromForm( |
|
1260 res, self)) |
|
1261 |
|
1262 def __webInspector(self): |
|
1263 """ |
|
1264 Private slot to show the web inspector window. |
|
1265 """ |
|
1266 from .WebInspector import WebInspector |
|
1267 if WebInspector.isEnabled(): |
|
1268 if self.__inspector is None: |
|
1269 self.__inspector = WebInspector() |
|
1270 self.__inspector.setView(self, True) |
|
1271 self.__inspector.inspectorClosed.connect( |
|
1272 self.closeWebInspector) |
|
1273 self.__inspector.show() |
|
1274 else: |
|
1275 self.closeWebInspector() |
|
1276 |
|
1277 def closeWebInspector(self): |
|
1278 """ |
|
1279 Public slot to close the web inspector. |
|
1280 """ |
|
1281 if self.__inspector is not None: |
|
1282 if self.__inspector.isVisible(): |
|
1283 self.__inspector.hide() |
|
1284 WebInspector.unregisterView(self.__inspector) |
|
1285 self.__inspector.deleteLater() |
|
1286 self.__inspector = None |
|
1287 |
|
1288 def addBookmark(self): |
|
1289 """ |
|
1290 Public slot to bookmark the current page. |
|
1291 """ |
|
1292 from .Tools import Scripts |
|
1293 script = Scripts.getAllMetaAttributes() |
|
1294 self.page().runJavaScript( |
|
1295 script, WebBrowserPage.SafeJsWorld, self.__addBookmarkCallback) |
|
1296 |
|
1297 def __addBookmarkCallback(self, res): |
|
1298 """ |
|
1299 Private callback method of __addBookmark(). |
|
1300 |
|
1301 @param res reference to the result list containing all |
|
1302 meta attributes |
|
1303 @type list |
|
1304 """ |
|
1305 description = "" |
|
1306 for meta in res: |
|
1307 if meta["name"] == "description": |
|
1308 description = meta["content"] |
|
1309 |
|
1310 from .Bookmarks.AddBookmarkDialog import AddBookmarkDialog |
|
1311 dlg = AddBookmarkDialog() |
|
1312 dlg.setUrl(bytes(self.url().toEncoded()).decode()) |
|
1313 dlg.setTitle(self.title()) |
|
1314 dlg.setDescription(description) |
|
1315 dlg.exec_() |
|
1316 |
|
1317 def dragEnterEvent(self, evt): |
|
1318 """ |
|
1319 Protected method called by a drag enter event. |
|
1320 |
|
1321 @param evt reference to the drag enter event (QDragEnterEvent) |
|
1322 """ |
|
1323 evt.acceptProposedAction() |
|
1324 |
|
1325 def dragMoveEvent(self, evt): |
|
1326 """ |
|
1327 Protected method called by a drag move event. |
|
1328 |
|
1329 @param evt reference to the drag move event (QDragMoveEvent) |
|
1330 """ |
|
1331 evt.ignore() |
|
1332 if evt.source() != self: |
|
1333 if len(evt.mimeData().urls()) > 0: |
|
1334 evt.acceptProposedAction() |
|
1335 else: |
|
1336 url = QUrl(evt.mimeData().text()) |
|
1337 if url.isValid(): |
|
1338 evt.acceptProposedAction() |
|
1339 |
|
1340 if not evt.isAccepted(): |
|
1341 super(WebBrowserView, self).dragMoveEvent(evt) |
|
1342 |
|
1343 def dropEvent(self, evt): |
|
1344 """ |
|
1345 Protected method called by a drop event. |
|
1346 |
|
1347 @param evt reference to the drop event (QDropEvent) |
|
1348 """ |
|
1349 super(WebBrowserView, self).dropEvent(evt) |
|
1350 if not evt.isAccepted() and \ |
|
1351 evt.source() != self and \ |
|
1352 evt.possibleActions() & Qt.CopyAction: |
|
1353 url = QUrl() |
|
1354 if len(evt.mimeData().urls()) > 0: |
|
1355 url = evt.mimeData().urls()[0] |
|
1356 if not url.isValid(): |
|
1357 url = QUrl(evt.mimeData().text()) |
|
1358 if url.isValid(): |
|
1359 self.setSource(url) |
|
1360 evt.acceptProposedAction() |
|
1361 |
|
1362 def _mousePressEvent(self, evt): |
|
1363 """ |
|
1364 Protected method called by a mouse press event. |
|
1365 |
|
1366 @param evt reference to the mouse event (QMouseEvent) |
|
1367 """ |
|
1368 if WebBrowserWindow.autoScroller().mousePress(self, evt): |
|
1369 evt.accept() |
|
1370 return |
|
1371 |
|
1372 self.__mw.setEventMouseButtons(evt.buttons()) |
|
1373 self.__mw.setEventKeyboardModifiers(evt.modifiers()) |
|
1374 |
|
1375 if evt.button() == Qt.XButton1: |
|
1376 self.pageAction(QWebEnginePage.Back).trigger() |
|
1377 evt.accept() |
|
1378 elif evt.button() == Qt.XButton2: |
|
1379 self.pageAction(QWebEnginePage.Forward).trigger() |
|
1380 evt.accept() |
|
1381 |
|
1382 def _mouseReleaseEvent(self, evt): |
|
1383 """ |
|
1384 Protected method called by a mouse release event. |
|
1385 |
|
1386 @param evt reference to the mouse event (QMouseEvent) |
|
1387 """ |
|
1388 if WebBrowserWindow.autoScroller().mouseRelease(evt): |
|
1389 evt.accept() |
|
1390 return |
|
1391 |
|
1392 accepted = evt.isAccepted() |
|
1393 self.__page.event(evt) |
|
1394 if not evt.isAccepted() and \ |
|
1395 self.__mw.eventMouseButtons() & Qt.MidButton: |
|
1396 url = QUrl(QApplication.clipboard().text(QClipboard.Selection)) |
|
1397 if not url.isEmpty() and \ |
|
1398 url.isValid() and \ |
|
1399 url.scheme() != "": |
|
1400 self.__mw.setEventMouseButtons(Qt.NoButton) |
|
1401 self.__mw.setEventKeyboardModifiers(Qt.NoModifier) |
|
1402 self.setSource(url) |
|
1403 evt.setAccepted(accepted) |
|
1404 |
|
1405 def _mouseMoveEvent(self, evt): |
|
1406 """ |
|
1407 Protected method to handle mouse move events. |
|
1408 |
|
1409 @param evt reference to the mouse event (QMouseEvent) |
|
1410 """ |
|
1411 if self.__mw and self.__mw.isFullScreen(): |
|
1412 if self.__mw.isFullScreenNavigationVisible(): |
|
1413 self.__mw.hideFullScreenNavigation() |
|
1414 elif evt.y() < 10: |
|
1415 # mouse is within 10px to the top |
|
1416 self.__mw.showFullScreenNavigation() |
|
1417 |
|
1418 if WebBrowserWindow.autoScroller().mouseMove(evt): |
|
1419 evt.accept() |
|
1420 |
|
1421 def _wheelEvent(self, evt): |
|
1422 """ |
|
1423 Protected method to handle wheel events. |
|
1424 |
|
1425 @param evt reference to the wheel event (QWheelEvent) |
|
1426 """ |
|
1427 if WebBrowserWindow.autoScroller().wheel(): |
|
1428 evt.accept() |
|
1429 return |
|
1430 |
|
1431 delta = evt.angleDelta().y() |
|
1432 if evt.modifiers() & Qt.ControlModifier: |
|
1433 if delta < 0: |
|
1434 self.zoomOut() |
|
1435 elif delta > 0: |
|
1436 self.zoomIn() |
|
1437 evt.accept() |
|
1438 |
|
1439 elif evt.modifiers() & Qt.ShiftModifier: |
|
1440 if delta < 0: |
|
1441 self.backward() |
|
1442 elif delta > 0: |
|
1443 self.forward() |
|
1444 evt.accept() |
|
1445 |
|
1446 def _keyPressEvent(self, evt): |
|
1447 """ |
|
1448 Protected method called by a key press. |
|
1449 |
|
1450 @param evt reference to the key event (QKeyEvent) |
|
1451 """ |
|
1452 if self.__mw.personalInformationManager().viewKeyPressEvent(self, evt): |
|
1453 evt.accept() |
|
1454 return |
|
1455 |
|
1456 if evt.key() == Qt.Key_ZoomIn: |
|
1457 self.zoomIn() |
|
1458 evt.accept() |
|
1459 elif evt.key() == Qt.Key_ZoomOut: |
|
1460 self.zoomOut() |
|
1461 evt.accept() |
|
1462 elif evt.key() == Qt.Key_Plus: |
|
1463 if evt.modifiers() & Qt.ControlModifier: |
|
1464 self.zoomIn() |
|
1465 evt.accept() |
|
1466 elif evt.key() == Qt.Key_Minus: |
|
1467 if evt.modifiers() & Qt.ControlModifier: |
|
1468 self.zoomOut() |
|
1469 evt.accept() |
|
1470 elif evt.key() == Qt.Key_0: |
|
1471 if evt.modifiers() & Qt.ControlModifier: |
|
1472 self.zoomReset() |
|
1473 evt.accept() |
|
1474 elif evt.key() == Qt.Key_M: |
|
1475 if evt.modifiers() & Qt.ControlModifier: |
|
1476 self.__muteMedia() |
|
1477 evt.accept() |
|
1478 elif evt.key() == Qt.Key_Backspace: |
|
1479 pos = QCursor.pos() |
|
1480 pos = self.mapFromGlobal(pos) |
|
1481 hitTest = self.page().hitTestContent(pos) |
|
1482 if not hitTest.isContentEditable(): |
|
1483 self.pageAction(QWebEnginePage.Back).trigger() |
|
1484 evt.accept() |
|
1485 |
|
1486 def _keyReleaseEvent(self, evt): |
|
1487 """ |
|
1488 Protected method called by a key release. |
|
1489 |
|
1490 @param evt reference to the key event (QKeyEvent) |
|
1491 """ |
|
1492 if evt.key() == Qt.Key_Escape: |
|
1493 if self.isFullScreen(): |
|
1494 self.triggerPageAction(QWebEnginePage.ExitFullScreen) |
|
1495 evt.accept() |
|
1496 self.requestFullScreen(False) |
|
1497 |
|
1498 def _gestureEvent(self, evt): |
|
1499 """ |
|
1500 Protected method handling gesture events. |
|
1501 |
|
1502 @param evt reference to the gesture event (QGestureEvent |
|
1503 """ |
|
1504 pinch = evt.gesture(Qt.PinchGesture) |
|
1505 if pinch: |
|
1506 if pinch.state() == Qt.GestureStarted: |
|
1507 pinch.setTotalScaleFactor(self.__currentZoom / 100.0) |
|
1508 elif pinch.state() == Qt.GestureUpdated: |
|
1509 scaleFactor = pinch.totalScaleFactor() |
|
1510 self.setZoomValue(int(scaleFactor * 100)) |
|
1511 evt.accept() |
|
1512 |
|
1513 def eventFilter(self, obj, evt): |
|
1514 """ |
|
1515 Public method to process event for other objects. |
|
1516 |
|
1517 @param obj reference to object to process events for |
|
1518 @type QObject |
|
1519 @param evt reference to event to be processed |
|
1520 @type QEvent |
|
1521 @return flag indicating that the event should be filtered out |
|
1522 @rtype bool |
|
1523 """ |
|
1524 if obj is self and evt.type() == QEvent.ParentChange and \ |
|
1525 self.parentWidget() is not None: |
|
1526 self.parentWidget().installEventFilter(self) |
|
1527 |
|
1528 # find the render widget receiving events for the web page |
|
1529 if obj is self and evt.type() == QEvent.ChildAdded: |
|
1530 if qVersionTuple() < (5, 8, 0): |
|
1531 child = evt.child() |
|
1532 if child and child.inherits( |
|
1533 "QtWebEngineCore::" |
|
1534 "RenderWidgetHostViewQtDelegateWidget"): |
|
1535 self.__rwhvqt = child |
|
1536 self.grabGesture(Qt.PinchGesture) |
|
1537 self.__rwhvqt.grabGesture(Qt.PinchGesture) |
|
1538 self.__rwhvqt.installEventFilter(self) |
|
1539 elif qVersionTuple() >= (5, 11, 0): |
|
1540 QTimer.singleShot(0, self.__setRwhvqt) |
|
1541 |
|
1542 # forward events to WebBrowserView |
|
1543 if obj is self.__rwhvqt and \ |
|
1544 evt.type() in [QEvent.KeyPress, QEvent.KeyRelease, |
|
1545 QEvent.MouseButtonPress, QEvent.MouseButtonRelease, |
|
1546 QEvent.MouseMove, QEvent.Wheel, QEvent.Gesture]: |
|
1547 wasAccepted = evt.isAccepted() |
|
1548 evt.setAccepted(False) |
|
1549 if evt.type() == QEvent.KeyPress: |
|
1550 self._keyPressEvent(evt) |
|
1551 elif evt.type() == QEvent.KeyRelease: |
|
1552 self._keyReleaseEvent(evt) |
|
1553 elif evt.type() == QEvent.MouseButtonPress: |
|
1554 self._mousePressEvent(evt) |
|
1555 elif evt.type() == QEvent.MouseButtonRelease: |
|
1556 self._mouseReleaseEvent(evt) |
|
1557 elif evt.type() == QEvent.MouseMove: |
|
1558 self._mouseMoveEvent(evt) |
|
1559 elif evt.type() == QEvent.Wheel: |
|
1560 self._wheelEvent(evt) |
|
1561 elif evt.type() == QEvent.Gesture: |
|
1562 self._gestureEvent(evt) |
|
1563 ret = evt.isAccepted() |
|
1564 evt.setAccepted(wasAccepted) |
|
1565 return ret |
|
1566 |
|
1567 if obj is self.parentWidget() and \ |
|
1568 evt.type() in [QEvent.KeyPress, QEvent.KeyRelease]: |
|
1569 wasAccepted = evt.isAccepted() |
|
1570 evt.setAccepted(False) |
|
1571 if evt.type() == QEvent.KeyPress: |
|
1572 self._keyPressEvent(evt) |
|
1573 elif evt.type() == QEvent.KeyRelease: |
|
1574 self._keyReleaseEvent(evt) |
|
1575 ret = evt.isAccepted() |
|
1576 evt.setAccepted(wasAccepted) |
|
1577 return ret |
|
1578 |
|
1579 # block already handled events |
|
1580 if obj is self: |
|
1581 if evt.type() in [QEvent.KeyPress, QEvent.KeyRelease, |
|
1582 QEvent.MouseButtonPress, |
|
1583 QEvent.MouseButtonRelease, |
|
1584 QEvent.MouseMove, QEvent.Wheel, QEvent.Gesture]: |
|
1585 return True |
|
1586 |
|
1587 elif evt.type() == QEvent.Hide: |
|
1588 if self.isFullScreen(): |
|
1589 self.triggerPageAction(QWebEnginePage.ExitFullScreen) |
|
1590 |
|
1591 return super(WebBrowserView, self).eventFilter(obj, evt) |
|
1592 |
|
1593 def event(self, evt): |
|
1594 """ |
|
1595 Public method handling events. |
|
1596 |
|
1597 @param evt reference to the event (QEvent) |
|
1598 @return flag indicating, if the event was handled (boolean) |
|
1599 """ |
|
1600 if evt.type() == QEvent.Gesture: |
|
1601 self._gestureEvent(evt) |
|
1602 return True |
|
1603 |
|
1604 return super(WebBrowserView, self).event(evt) |
|
1605 |
|
1606 def inputWidget(self): |
|
1607 """ |
|
1608 Public method to get a reference to the render widget. |
|
1609 |
|
1610 @return reference to the render widget |
|
1611 @rtype QWidget |
|
1612 """ |
|
1613 return self.__rwhvqt |
|
1614 |
|
1615 def clearHistory(self): |
|
1616 """ |
|
1617 Public slot to clear the history. |
|
1618 """ |
|
1619 self.history().clear() |
|
1620 self.__urlChanged(self.history().currentItem().url()) |
|
1621 |
|
1622 ########################################################################### |
|
1623 ## Signal converters below |
|
1624 ########################################################################### |
|
1625 |
|
1626 def __urlChanged(self, url): |
|
1627 """ |
|
1628 Private slot to handle the urlChanged signal. |
|
1629 |
|
1630 @param url the new url (QUrl) |
|
1631 """ |
|
1632 self.sourceChanged.emit(url) |
|
1633 |
|
1634 self.forwardAvailable.emit(self.isForwardAvailable()) |
|
1635 self.backwardAvailable.emit(self.isBackwardAvailable()) |
|
1636 |
|
1637 def __iconUrlChanged(self, url): |
|
1638 """ |
|
1639 Private slot to handle the iconUrlChanged signal. |
|
1640 |
|
1641 @param url URL to get web site icon from |
|
1642 @type QUrl |
|
1643 """ |
|
1644 self.__siteIcon = QIcon() |
|
1645 if self.__siteIconLoader is not None: |
|
1646 self.__siteIconLoader.deleteLater() |
|
1647 self.__siteIconLoader = WebIconLoader(url, self) |
|
1648 self.__siteIconLoader.iconLoaded.connect(self.__iconLoaded) |
|
1649 |
|
1650 def __iconLoaded(self, icon): |
|
1651 """ |
|
1652 Private slot handling the loaded web site icon. |
|
1653 |
|
1654 @param icon web site icon |
|
1655 @type QIcon |
|
1656 """ |
|
1657 self.__siteIcon = icon |
|
1658 |
|
1659 from .Tools import WebIconProvider |
|
1660 WebIconProvider.instance().saveIcon(self) |
|
1661 |
|
1662 self.faviconChanged.emit() |
|
1663 |
|
1664 def icon(self): |
|
1665 """ |
|
1666 Public method to get the web site icon. |
|
1667 |
|
1668 @return web site icon |
|
1669 @rtype QIcon |
|
1670 """ |
|
1671 if not self.__siteIcon.isNull(): |
|
1672 return QIcon(self.__siteIcon) |
|
1673 |
|
1674 from .Tools import WebIconProvider |
|
1675 return WebIconProvider.instance().iconForUrl(self.url()) |
|
1676 |
|
1677 def title(self): |
|
1678 """ |
|
1679 Public method to get the view title. |
|
1680 |
|
1681 @return view title |
|
1682 @rtype str |
|
1683 """ |
|
1684 titleStr = super(WebBrowserView, self).title() |
|
1685 if not titleStr: |
|
1686 if self.url().isEmpty(): |
|
1687 url = self.__page.requestedUrl() |
|
1688 else: |
|
1689 url = self.url() |
|
1690 |
|
1691 titleStr = url.host() |
|
1692 if not titleStr: |
|
1693 titleStr = url.toString(QUrl.RemoveFragment) |
|
1694 |
|
1695 if not titleStr or titleStr == "about:blank": |
|
1696 titleStr = self.tr("Empty Page") |
|
1697 |
|
1698 return titleStr |
|
1699 |
|
1700 def __linkHovered(self, link): |
|
1701 """ |
|
1702 Private slot to handle the linkHovered signal. |
|
1703 |
|
1704 @param link the URL of the link (string) |
|
1705 """ |
|
1706 self.highlighted.emit(link) |
|
1707 |
|
1708 ########################################################################### |
|
1709 ## Signal handlers below |
|
1710 ########################################################################### |
|
1711 |
|
1712 def __renderProcessTerminated(self, status, exitCode): |
|
1713 """ |
|
1714 Private slot handling a crash of the web page render process. |
|
1715 |
|
1716 @param status termination status |
|
1717 @type QWebEnginePage.RenderProcessTerminationStatus |
|
1718 @param exitCode exit code of the process |
|
1719 @type int |
|
1720 """ |
|
1721 if status == QWebEnginePage.NormalTerminationStatus: |
|
1722 return |
|
1723 |
|
1724 QTimer.singleShot(0, lambda: self.__showTabCrashPage(status)) |
|
1725 |
|
1726 def __showTabCrashPage(self, status): |
|
1727 """ |
|
1728 Private slot to show the tab crash page. |
|
1729 |
|
1730 @param status termination status |
|
1731 @type QWebEnginePage.RenderProcessTerminationStatus |
|
1732 """ |
|
1733 self.page().deleteLater() |
|
1734 self.__createNewPage() |
|
1735 |
|
1736 html = readAllFileContents(":/html/tabCrashPage.html") |
|
1737 html = html.replace("@IMAGE@", pixmapToDataUrl( |
|
1738 qApp.style().standardIcon(QStyle.SP_MessageBoxWarning).pixmap( |
|
1739 48, 48)).toString()) |
|
1740 html = html.replace("@FAVICON@", pixmapToDataUrl( |
|
1741 qApp.style() .standardIcon(QStyle.SP_MessageBoxWarning).pixmap( |
|
1742 16, 16)).toString()) |
|
1743 html = html.replace( |
|
1744 "@TITLE@", self.tr("Render Process terminated abnormally")) |
|
1745 html = html.replace( |
|
1746 "@H1@", self.tr("Render Process terminated abnormally")) |
|
1747 if status == QWebEnginePage.CrashedTerminationStatus: |
|
1748 msg = self.tr("The render process crashed while" |
|
1749 " loading this page.") |
|
1750 elif status == QWebEnginePage.KilledTerminationStatus: |
|
1751 msg = self.tr("The render process was killed.") |
|
1752 else: |
|
1753 msg = self.tr("The render process terminated while" |
|
1754 " loading this page.") |
|
1755 html = html.replace("@LI-1@", msg) |
|
1756 html = html.replace( |
|
1757 "@LI-2@", |
|
1758 self.tr( |
|
1759 "Try reloading the page or closing some tabs to make more" |
|
1760 " memory available.")) |
|
1761 self.page().setHtml(html, self.url()) |
|
1762 |
|
1763 def __loadStarted(self): |
|
1764 """ |
|
1765 Private method to handle the loadStarted signal. |
|
1766 """ |
|
1767 # reset search |
|
1768 self.findText("") |
|
1769 self.__isLoading = True |
|
1770 self.__progress = 0 |
|
1771 |
|
1772 def __loadProgress(self, progress): |
|
1773 """ |
|
1774 Private method to handle the loadProgress signal. |
|
1775 |
|
1776 @param progress progress value (integer) |
|
1777 """ |
|
1778 self.__progress = progress |
|
1779 |
|
1780 def __loadFinished(self, ok): |
|
1781 """ |
|
1782 Private method to handle the loadFinished signal. |
|
1783 |
|
1784 @param ok flag indicating the result (boolean) |
|
1785 """ |
|
1786 self.__isLoading = False |
|
1787 self.__progress = 0 |
|
1788 |
|
1789 QApplication.processEvents() |
|
1790 QTimer.singleShot(200, self.__renderPreview) |
|
1791 |
|
1792 from .ZoomManager import ZoomManager |
|
1793 zoomValue = ZoomManager.instance().zoomValue(self.url()) |
|
1794 self.setZoomValue(zoomValue) |
|
1795 |
|
1796 if ok: |
|
1797 self.__mw.historyManager().addHistoryEntry(self) |
|
1798 self.__mw.adBlockManager().page().hideBlockedPageEntries( |
|
1799 self.page()) |
|
1800 self.__mw.passwordManager().completePage(self.page()) |
|
1801 |
|
1802 self.page().runJavaScript( |
|
1803 "document.lastModified", WebBrowserPage.SafeJsWorld, |
|
1804 lambda res: self.__adjustBookmark(res)) |
|
1805 |
|
1806 def __adjustBookmark(self, lastModified): |
|
1807 """ |
|
1808 Private slot to adjust the 'lastModified' value of bookmarks. |
|
1809 |
|
1810 @param lastModified last modified value |
|
1811 @type str |
|
1812 """ |
|
1813 modified = QDateTime.fromString(lastModified, "MM/dd/yyyy hh:mm:ss") |
|
1814 if modified.isValid(): |
|
1815 from WebBrowser.WebBrowserWindow import WebBrowserWindow |
|
1816 from .Bookmarks.BookmarkNode import BookmarkNode |
|
1817 manager = WebBrowserWindow.bookmarksManager() |
|
1818 for bookmark in manager.bookmarksForUrl(self.url()): |
|
1819 manager.setTimestamp(bookmark, BookmarkNode.TsModified, |
|
1820 modified) |
|
1821 |
|
1822 def isLoading(self): |
|
1823 """ |
|
1824 Public method to get the loading state. |
|
1825 |
|
1826 @return flag indicating the loading state (boolean) |
|
1827 """ |
|
1828 return self.__isLoading |
|
1829 |
|
1830 def progress(self): |
|
1831 """ |
|
1832 Public method to get the load progress. |
|
1833 |
|
1834 @return load progress (integer) |
|
1835 """ |
|
1836 return self.__progress |
|
1837 |
|
1838 def __renderPreview(self): |
|
1839 """ |
|
1840 Private slot to render a preview pixmap after the page was loaded. |
|
1841 """ |
|
1842 from .WebBrowserSnap import renderTabPreview |
|
1843 w = 600 # some default width, the preview gets scaled when shown |
|
1844 h = int(w * self.height() / self.width()) |
|
1845 self.__preview = renderTabPreview(self, w, h) |
|
1846 |
|
1847 def getPreview(self): |
|
1848 """ |
|
1849 Public method to get the preview pixmap. |
|
1850 |
|
1851 @return preview pixmap |
|
1852 @rtype QPixmap |
|
1853 """ |
|
1854 return self.__preview |
|
1855 |
|
1856 def saveAs(self): |
|
1857 """ |
|
1858 Public method to save the current page to a file. |
|
1859 """ |
|
1860 url = self.url() |
|
1861 if url.isEmpty(): |
|
1862 return |
|
1863 |
|
1864 if qVersionTuple() >= (5, 8, 0) and PYQT_VERSION >= 0x50800: |
|
1865 # since Qt 5.8.0 |
|
1866 fileName, savePageFormat = self.__getSavePageFileNameAndFormat() |
|
1867 if fileName: |
|
1868 self.page().save(fileName, savePageFormat) |
|
1869 else: |
|
1870 self.triggerPageAction(QWebEnginePage.SavePage) |
|
1871 |
|
1872 def __getSavePageFileNameAndFormat(self): |
|
1873 """ |
|
1874 Private method to get the file name to save the page to. |
|
1875 |
|
1876 @return tuple containing the file name to save to and the |
|
1877 save page format |
|
1878 @rtype tuple of (str, QWebEngineDownloadItem.SavePageFormat) |
|
1879 """ |
|
1880 documentLocation = QStandardPaths.writableLocation( |
|
1881 QStandardPaths.DocumentsLocation) |
|
1882 filterList = [ |
|
1883 self.tr("Web Archive (*.mhtml *.mht)"), |
|
1884 self.tr("HTML File (*.html *.htm)"), |
|
1885 self.tr("HTML File with all resources (*.html *.htm)"), |
|
1886 ] |
|
1887 extensionsList = [ |
|
1888 # tuple of extensions for *nix and Windows |
|
1889 # keep in sync with filters list |
|
1890 (".mhtml", ".mht"), |
|
1891 (".html", ".htm"), |
|
1892 (".html", ".htm"), |
|
1893 ] |
|
1894 if self.url().fileName(): |
|
1895 defaultFileName = os.path.join(documentLocation, |
|
1896 self.url().fileName()) |
|
1897 else: |
|
1898 defaultFileName = os.path.join(documentLocation, |
|
1899 self.page().title()) |
|
1900 if Utilities.isWindowsPlatform(): |
|
1901 defaultFileName += ".mht" |
|
1902 else: |
|
1903 defaultFileName += ".mhtml" |
|
1904 |
|
1905 fileName = "" |
|
1906 saveFormat = QWebEngineDownloadItem.MimeHtmlSaveFormat |
|
1907 |
|
1908 fileName, selectedFilter = E5FileDialog.getSaveFileNameAndFilter( |
|
1909 None, |
|
1910 self.tr("Save Web Page"), |
|
1911 defaultFileName, |
|
1912 ";;".join(filterList), |
|
1913 None) |
|
1914 if fileName: |
|
1915 index = filterList.index(selectedFilter) |
|
1916 if index == 0: |
|
1917 saveFormat = QWebEngineDownloadItem.MimeHtmlSaveFormat |
|
1918 elif index == 1: |
|
1919 saveFormat = QWebEngineDownloadItem.SingleHtmlSaveFormat |
|
1920 else: |
|
1921 saveFormat = QWebEngineDownloadItem.CompleteHtmlSaveFormat |
|
1922 |
|
1923 extension = os.path.splitext(fileName)[1] |
|
1924 if not extension: |
|
1925 # add the platform specific default extension |
|
1926 if Utilities.isWindowsPlatform(): |
|
1927 extensionsIndex = 1 |
|
1928 else: |
|
1929 extensionsIndex = 0 |
|
1930 extensions = extensionsList[index] |
|
1931 fileName += extensions[extensionsIndex] |
|
1932 |
|
1933 return fileName, saveFormat |
|
1934 |
|
1935 ########################################################################### |
|
1936 ## Miscellaneous methods below |
|
1937 ########################################################################### |
|
1938 |
|
1939 def createWindow(self, windowType): |
|
1940 """ |
|
1941 Public method called, when a new window should be created. |
|
1942 |
|
1943 @param windowType type of the requested window |
|
1944 (QWebEnginePage.WebWindowType) |
|
1945 @return reference to the created browser window (WebBrowserView) |
|
1946 """ |
|
1947 if windowType in [QWebEnginePage.WebBrowserTab, |
|
1948 QWebEnginePage.WebDialog]: |
|
1949 return self.__mw.newTab(addNextTo=self) |
|
1950 elif windowType == QWebEnginePage.WebBrowserWindow: |
|
1951 return self.__mw.newWindow().currentBrowser() |
|
1952 else: |
|
1953 return self.__mw.newTab(addNextTo=self, background=True) |
|
1954 |
|
1955 def preferencesChanged(self): |
|
1956 """ |
|
1957 Public method to indicate a change of the settings. |
|
1958 """ |
|
1959 self.reload() |
|
1960 |
|
1961 ########################################################################### |
|
1962 ## RSS related methods below |
|
1963 ########################################################################### |
|
1964 |
|
1965 def checkRSS(self): |
|
1966 """ |
|
1967 Public method to check, if the loaded page contains feed links. |
|
1968 |
|
1969 @return flag indicating the existence of feed links (boolean) |
|
1970 """ |
|
1971 self.__rss = [] |
|
1972 |
|
1973 script = Scripts.getFeedLinks() |
|
1974 feeds = self.page().execJavaScript(script) |
|
1975 |
|
1976 if feeds is not None: |
|
1977 for feed in feeds: |
|
1978 if feed["url"] and feed["title"]: |
|
1979 self.__rss.append((feed["title"], feed["url"])) |
|
1980 |
|
1981 return len(self.__rss) > 0 |
|
1982 |
|
1983 def getRSS(self): |
|
1984 """ |
|
1985 Public method to get the extracted RSS feeds. |
|
1986 |
|
1987 @return list of RSS feeds (list of tuples of two strings) |
|
1988 """ |
|
1989 return self.__rss |
|
1990 |
|
1991 def hasRSS(self): |
|
1992 """ |
|
1993 Public method to check, if the loaded page has RSS links. |
|
1994 |
|
1995 @return flag indicating the presence of RSS links (boolean) |
|
1996 """ |
|
1997 return len(self.__rss) > 0 |
|
1998 |
|
1999 ########################################################################### |
|
2000 ## Full Screen handling below |
|
2001 ########################################################################### |
|
2002 |
|
2003 def isFullScreen(self): |
|
2004 """ |
|
2005 Public method to check, if full screen mode is active. |
|
2006 |
|
2007 @return flag indicating full screen mode |
|
2008 @rtype bool |
|
2009 """ |
|
2010 return self.__mw.isFullScreen() |
|
2011 |
|
2012 def requestFullScreen(self, enable): |
|
2013 """ |
|
2014 Public method to request full screen mode. |
|
2015 |
|
2016 @param enable flag indicating full screen mode on or off |
|
2017 @type bool |
|
2018 """ |
|
2019 if enable: |
|
2020 self.__mw.enterHtmlFullScreen() |
|
2021 else: |
|
2022 self.__mw.showNormal() |
|
2023 |
|
2024 ########################################################################### |
|
2025 ## Speed Dial slots below |
|
2026 ########################################################################### |
|
2027 |
|
2028 def __addSpeedDial(self): |
|
2029 """ |
|
2030 Private slot to add a new speed dial. |
|
2031 """ |
|
2032 self.__page.runJavaScript("addSpeedDial();", |
|
2033 WebBrowserPage.SafeJsWorld) |
|
2034 |
|
2035 def __configureSpeedDial(self): |
|
2036 """ |
|
2037 Private slot to configure the speed dial. |
|
2038 """ |
|
2039 self.page().runJavaScript("configureSpeedDial();", |
|
2040 WebBrowserPage.SafeJsWorld) |
|
2041 |
|
2042 def __reloadAllSpeedDials(self): |
|
2043 """ |
|
2044 Private slot to reload all speed dials. |
|
2045 """ |
|
2046 self.page().runJavaScript("reloadAll();", WebBrowserPage.SafeJsWorld) |
|
2047 |
|
2048 def __resetSpeedDials(self): |
|
2049 """ |
|
2050 Private slot to reset all speed dials to the default pages. |
|
2051 """ |
|
2052 self.__speedDial.resetDials() |
|
2053 |
|
2054 ########################################################################### |
|
2055 ## Methods below implement session related functions |
|
2056 ########################################################################### |
|
2057 |
|
2058 def storeSessionData(self, data): |
|
2059 """ |
|
2060 Public method to store session data to be restored later on. |
|
2061 |
|
2062 @param data dictionary with session data to be restored |
|
2063 @type dict |
|
2064 """ |
|
2065 self.__restoreData = data |
|
2066 |
|
2067 def __showEventSlot(self): |
|
2068 """ |
|
2069 Private slot to perform actions when the view is shown and the event |
|
2070 loop is running. |
|
2071 """ |
|
2072 if self.__restoreData: |
|
2073 sessionData, self.__restoreData = self.__restoreData, None |
|
2074 self.loadFromSessionData(sessionData) |
|
2075 |
|
2076 def showEvent(self, evt): |
|
2077 """ |
|
2078 Protected method to handle show events. |
|
2079 |
|
2080 @param evt reference to the show event object |
|
2081 @type QShowEvent |
|
2082 """ |
|
2083 super(WebBrowserView, self).showEvent(evt) |
|
2084 self.activateSession() |
|
2085 |
|
2086 def activateSession(self): |
|
2087 """ |
|
2088 Public slot to activate a restored session. |
|
2089 """ |
|
2090 if self.__restoreData and not self.__mw.isClosing(): |
|
2091 QTimer.singleShot(0, self.__showEventSlot) |
|
2092 |
|
2093 def getSessionData(self): |
|
2094 """ |
|
2095 Public method to populate the session data. |
|
2096 |
|
2097 @return dictionary containing the session data |
|
2098 @rtype dict |
|
2099 """ |
|
2100 if self.__restoreData: |
|
2101 # page has not been shown yet |
|
2102 return self.__restoreData |
|
2103 |
|
2104 sessionData = {} |
|
2105 page = self.page() |
|
2106 |
|
2107 # 1. zoom factor |
|
2108 sessionData["ZoomFactor"] = page.zoomFactor() |
|
2109 |
|
2110 # 2. scroll position |
|
2111 scrollPos = page.scrollPosition() |
|
2112 sessionData["ScrollPosition"] = { |
|
2113 "x": scrollPos.x(), |
|
2114 "y": scrollPos.y(), |
|
2115 } |
|
2116 |
|
2117 # 3. page history |
|
2118 historyArray = QByteArray() |
|
2119 stream = QDataStream(historyArray, QIODevice.WriteOnly) |
|
2120 stream << page.history() |
|
2121 sessionData["History"] = str( |
|
2122 historyArray.toBase64(QByteArray.Base64UrlEncoding), |
|
2123 encoding="ascii") |
|
2124 sessionData["HistoryIndex"] = page.history().currentItemIndex() |
|
2125 |
|
2126 # 4. current URL and title |
|
2127 sessionData["Url"] = self.url().toString() |
|
2128 sessionData["Title"] = self.title() |
|
2129 |
|
2130 # 5. web icon |
|
2131 iconArray = QByteArray() |
|
2132 stream = QDataStream(iconArray, QIODevice.WriteOnly) |
|
2133 stream << page.icon() |
|
2134 sessionData["Icon"] = str(iconArray.toBase64(), encoding="ascii") |
|
2135 |
|
2136 return sessionData |
|
2137 |
|
2138 def loadFromSessionData(self, sessionData): |
|
2139 """ |
|
2140 Public method to load the session data. |
|
2141 |
|
2142 @param sessionData dictionary containing the session data as |
|
2143 generated by getSessionData() |
|
2144 @type dict |
|
2145 """ |
|
2146 page = self.page() |
|
2147 # blank the page |
|
2148 page.setUrl(QUrl("about:blank")) |
|
2149 |
|
2150 # 1. page history |
|
2151 if "History" in sessionData: |
|
2152 historyArray = QByteArray.fromBase64( |
|
2153 sessionData["History"].encode("ascii"), |
|
2154 QByteArray.Base64UrlEncoding) |
|
2155 stream = QDataStream(historyArray, QIODevice.ReadOnly) |
|
2156 stream >> page.history() |
|
2157 |
|
2158 if "HistoryIndex" in sessionData: |
|
2159 item = page.history().itemAt(sessionData["HistoryIndex"]) |
|
2160 if item is not None: |
|
2161 page.history().goToItem(item) |
|
2162 |
|
2163 # 2. zoom factor |
|
2164 if "ZoomFactor" in sessionData: |
|
2165 page.setZoomFactor(sessionData["ZoomFactor"]) |
|
2166 |
|
2167 # 3. scroll position |
|
2168 if "ScrollPosition" in sessionData: |
|
2169 scrollPos = sessionData["ScrollPosition"] |
|
2170 page.scrollTo(QPointF(scrollPos["x"], scrollPos["y"])) |
|
2171 |
|
2172 def extractSessionMetaData(self, sessionData): |
|
2173 """ |
|
2174 Public method to extract some session meta data elements needed by the |
|
2175 tab widget in case of deferred loading. |
|
2176 |
|
2177 @param sessionData dictionary containing the session data as |
|
2178 generated by getSessionData() |
|
2179 @type dict |
|
2180 @return tuple containing the title, URL and web icon |
|
2181 @rtype tuple of (str, str, QIcon) |
|
2182 """ |
|
2183 if "Title" in sessionData: |
|
2184 title = sessionData["Title"] |
|
2185 else: |
|
2186 title = "" |
|
2187 |
|
2188 if "Url" in sessionData: |
|
2189 urlStr = sessionData["Url"] |
|
2190 else: |
|
2191 urlStr = "" |
|
2192 |
|
2193 if "Icon" in sessionData: |
|
2194 iconArray = QByteArray.fromBase64( |
|
2195 sessionData["Icon"].encode("ascii")) |
|
2196 stream = QDataStream(iconArray, QIODevice.ReadOnly) |
|
2197 icon = QIcon() |
|
2198 stream >> icon |
|
2199 else: |
|
2200 from .Tools import WebIconProvider |
|
2201 icon = WebIconProvider.instance().iconForUrl( |
|
2202 QUrl.fromUserInput(urlStr)) |
|
2203 |
|
2204 return title, urlStr, icon |
|
2205 |
|
2206 ########################################################################### |
|
2207 ## Methods below implement safe browsing related functions |
|
2208 ########################################################################### |
|
2209 |
|
2210 def getSafeBrowsingStatus(self): |
|
2211 """ |
|
2212 Public method to get the safe browsing status of the current page. |
|
2213 |
|
2214 @return flag indicating a safe site |
|
2215 @rtype bool |
|
2216 """ |
|
2217 if self.__page: |
|
2218 return self.__page.getSafeBrowsingStatus() |
|
2219 else: |
|
2220 return True |
|
2221 |
|
2222 ########################################################################### |
|
2223 ## Methods below implement print support from the page |
|
2224 ########################################################################### |
|
2225 |
|
2226 @pyqtSlot() |
|
2227 def __printPage(self): |
|
2228 """ |
|
2229 Private slot to support printing from the web page. |
|
2230 """ |
|
2231 self.__mw.tabWidget.printBrowser(browser=self) |
|
2232 |
|
2233 ########################################################################### |
|
2234 ## Methods below implement slots for Qt 5.11+ |
|
2235 ########################################################################### |
|
2236 |
|
2237 if qVersionTuple() >= (5, 11, 0): |
|
2238 @pyqtSlot("QWebEngineQuotaRequest") |
|
2239 def __quotaRequested(self, quotaRequest): |
|
2240 """ |
|
2241 Private slot to handle quota requests of the web page. |
|
2242 |
|
2243 @param quotaRequest reference to the quota request object |
|
2244 @type QWebEngineQuotaRequest |
|
2245 """ |
|
2246 acceptRequest = Preferences.getWebBrowser("AcceptQuotaRequest") |
|
2247 # yes/no/ask (0, 1, 2) |
|
2248 if acceptRequest == 0: |
|
2249 # always yes |
|
2250 ok = True |
|
2251 elif acceptRequest == 1: |
|
2252 # always no |
|
2253 ok = False |
|
2254 else: |
|
2255 # ask user |
|
2256 from .Download.DownloadUtilities import dataString |
|
2257 sizeStr = dataString(quotaRequest.requestedSize()) |
|
2258 |
|
2259 ok = E5MessageBox.yesNo( |
|
2260 self, |
|
2261 self.tr("Quota Request"), |
|
2262 self.tr("""<p> Allow the website at <b>{0}</b> to use""" |
|
2263 """ <b>{1}</b> of persistent storage?</p>""") |
|
2264 .format(quotaRequest.origin().host(), sizeStr) |
|
2265 ) |
|
2266 |
|
2267 if ok: |
|
2268 quotaRequest.accept() |
|
2269 else: |
|
2270 quotaRequest.reject() |
|
2271 |
|
2272 ########################################################################### |
|
2273 ## Methods below implement slots for Qt 5.12+ |
|
2274 ########################################################################### |
|
2275 |
|
2276 if qVersionTuple() >= (5, 12, 0): |
|
2277 @pyqtSlot("QWebEngineClientCertificateSelection") |
|
2278 def __selectClientCertificate(self, clientCertificateSelection): |
|
2279 """ |
|
2280 Private slot to handle the client certificate selection request. |
|
2281 |
|
2282 @param clientCertificateSelection list of client SSL certificates |
|
2283 found in system's client certificate store |
|
2284 @type QWebEngineClientCertificateSelection |
|
2285 """ |
|
2286 certificates = clientCertificateSelection.certificates() |
|
2287 if len(certificates) == 0: |
|
2288 clientCertificateSelection.selectNone() |
|
2289 elif len(certificates) == 1: |
|
2290 clientCertificateSelection.select(certificates[0]) |
|
2291 else: |
|
2292 certificate = None |
|
2293 from E5Network.E5SslCertificateSelectionDialog import \ |
|
2294 E5SslCertificateSelectionDialog |
|
2295 dlg = E5SslCertificateSelectionDialog(certificates, self) |
|
2296 if dlg.exec_() == QDialog.Accepted: |
|
2297 certificate = dlg.getSelectedCertificate() |
|
2298 |
|
2299 if certificate is None: |
|
2300 clientCertificateSelection.selectNone() |
|
2301 else: |
|
2302 clientCertificateSelection.select(certificate) |