|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2021 - 2022 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the help viewer base class. |
|
8 """ |
|
9 |
|
10 import contextlib |
|
11 import functools |
|
12 |
|
13 from PyQt6.QtCore import pyqtSlot, Qt, QEvent, QTimer, QUrl, QPoint |
|
14 from PyQt6.QtGui import QGuiApplication, QClipboard, QContextMenuEvent |
|
15 from PyQt6.QtWidgets import QMenu |
|
16 from PyQt6.QtWebEngineWidgets import QWebEngineView |
|
17 from PyQt6.QtWebEngineCore import QWebEnginePage, QWebEngineNewWindowRequest |
|
18 |
|
19 from .HelpViewerWidget import HelpViewerWidget |
|
20 from .HelpViewerImpl import HelpViewerImpl |
|
21 |
|
22 import UI.PixmapCache |
|
23 |
|
24 |
|
25 class HelpViewerImplQWE(HelpViewerImpl, QWebEngineView): |
|
26 """ |
|
27 Class implementing the QTextBrowser based help viewer class. |
|
28 """ |
|
29 ZoomLevels = [ |
|
30 30, 40, 50, 67, 80, 90, |
|
31 100, |
|
32 110, 120, 133, 150, 170, 200, 220, 233, 250, 270, 285, 300, |
|
33 ] |
|
34 ZoomLevelDefault = 100 |
|
35 |
|
36 def __init__(self, engine, parent=None): |
|
37 """ |
|
38 Constructor |
|
39 |
|
40 @param engine reference to the help engine |
|
41 @type QHelpEngine |
|
42 @param parent reference to the parent widget |
|
43 @type QWidget |
|
44 """ |
|
45 QWebEngineView.__init__(self, parent=parent) |
|
46 HelpViewerImpl.__init__(self, engine) |
|
47 |
|
48 self.__helpViewerWidget = parent |
|
49 |
|
50 self.__rwhvqt = None |
|
51 self.installEventFilter(self) |
|
52 |
|
53 self.__page = None |
|
54 self.__createNewPage() |
|
55 |
|
56 self.__currentScale = 100 |
|
57 |
|
58 self.__menu = QMenu(self) |
|
59 |
|
60 def __createNewPage(self): |
|
61 """ |
|
62 Private method to create a new page object. |
|
63 """ |
|
64 self.__page = QWebEnginePage(self.__helpViewerWidget.webProfile()) |
|
65 self.setPage(self.__page) |
|
66 |
|
67 self.__page.titleChanged.connect(self.__titleChanged) |
|
68 self.__page.urlChanged.connect(self.__titleChanged) |
|
69 self.__page.newWindowRequested.connect(self.__newWindowRequested) |
|
70 |
|
71 def __newWindowRequested(self, request): |
|
72 """ |
|
73 Private slot handling new window requests of the web page. |
|
74 |
|
75 @param request reference to the new window request |
|
76 @type QWebEngineNewWindowRequest |
|
77 """ |
|
78 background = ( |
|
79 request.destination() == |
|
80 QWebEngineNewWindowRequest.DestinationType.InNewBackgroundTab |
|
81 ) |
|
82 newViewer = self.__helpViewerWidget.addPage(background=background) |
|
83 request.openIn(newViewer.page()) |
|
84 |
|
85 def __setRwhvqt(self): |
|
86 """ |
|
87 Private slot to set widget that receives input events. |
|
88 """ |
|
89 self.grabGesture(Qt.GestureType.PinchGesture) |
|
90 self.__rwhvqt = self.focusProxy() |
|
91 if self.__rwhvqt: |
|
92 self.__rwhvqt.grabGesture(Qt.GestureType.PinchGesture) |
|
93 self.__rwhvqt.installEventFilter(self) |
|
94 else: |
|
95 print("Focus proxy is null!") # __IGNORE_WARNING_M801__ |
|
96 |
|
97 def setLink(self, url): |
|
98 """ |
|
99 Public method to set the URL of the document to be shown. |
|
100 |
|
101 @param url URL of the document |
|
102 @type QUrl |
|
103 """ |
|
104 if url.toString() == "about:blank": |
|
105 self.setHtml(self.__helpViewerWidget.emptyDocument()) |
|
106 else: |
|
107 super().setUrl(url) |
|
108 |
|
109 def link(self): |
|
110 """ |
|
111 Public method to get the URL of the shown document. |
|
112 |
|
113 @return url URL of the document |
|
114 @rtype QUrl |
|
115 """ |
|
116 return super().url() |
|
117 |
|
118 @pyqtSlot() |
|
119 def __titleChanged(self): |
|
120 """ |
|
121 Private method to handle a change of the web page title. |
|
122 """ |
|
123 super().titleChanged.emit() |
|
124 |
|
125 def pageTitle(self): |
|
126 """ |
|
127 Public method get the page title. |
|
128 |
|
129 @return page title |
|
130 @rtype str |
|
131 """ |
|
132 titleStr = super().title() |
|
133 if not titleStr: |
|
134 if self.link().isEmpty(): |
|
135 url = self.__page.requestedUrl() |
|
136 else: |
|
137 url = self.link() |
|
138 |
|
139 titleStr = url.host() |
|
140 if not titleStr: |
|
141 titleStr = url.toString( |
|
142 QUrl.UrlFormattingOption.RemoveFragment) |
|
143 |
|
144 if not titleStr or titleStr == "about:blank": |
|
145 titleStr = self.tr("Empty Page") |
|
146 |
|
147 return titleStr |
|
148 |
|
149 def isEmptyPage(self): |
|
150 """ |
|
151 Public method to check, if the current page is the empty page. |
|
152 |
|
153 @return flag indicating an empty page is loaded |
|
154 @rtype bool |
|
155 """ |
|
156 return self.pageTitle() == self.tr("Empty Page") |
|
157 |
|
158 ####################################################################### |
|
159 ## History related methods below |
|
160 ####################################################################### |
|
161 |
|
162 def isBackwardAvailable(self): |
|
163 """ |
|
164 Public method to check, if stepping backward through the history is |
|
165 available. |
|
166 |
|
167 @return flag indicating backward stepping is available |
|
168 @rtype bool |
|
169 """ |
|
170 return self.history().canGoBack() |
|
171 |
|
172 def isForwardAvailable(self): |
|
173 """ |
|
174 Public method to check, if stepping forward through the history is |
|
175 available. |
|
176 |
|
177 @return flag indicating forward stepping is available |
|
178 @rtype bool |
|
179 """ |
|
180 return self.history().canGoForward() |
|
181 |
|
182 def backward(self): |
|
183 """ |
|
184 Public slot to move backwards in history. |
|
185 """ |
|
186 self.triggerPageAction(QWebEnginePage.WebAction.Back) |
|
187 |
|
188 def forward(self): |
|
189 """ |
|
190 Public slot to move forward in history. |
|
191 """ |
|
192 self.triggerPageAction(QWebEnginePage.WebAction.Forward) |
|
193 |
|
194 def reload(self): |
|
195 """ |
|
196 Public slot to reload the current page. |
|
197 """ |
|
198 self.triggerPageAction(QWebEnginePage.WebAction.Reload) |
|
199 |
|
200 def backwardHistoryCount(self): |
|
201 """ |
|
202 Public method to get the number of available back history items. |
|
203 |
|
204 Note: For performance reasons this is limited to the maximum number of |
|
205 history items the help viewer is interested in. |
|
206 |
|
207 @return count of available back history items |
|
208 @rtype int |
|
209 """ |
|
210 history = self.history() |
|
211 return len(history.backItems(HelpViewerWidget.MaxHistoryItems)) |
|
212 |
|
213 def forwardHistoryCount(self): |
|
214 """ |
|
215 Public method to get the number of available forward history items. |
|
216 |
|
217 Note: For performance reasons this is limited to the maximum number of |
|
218 history items the help viewer is interested in. |
|
219 |
|
220 @return count of available forward history items |
|
221 @rtype int |
|
222 """ |
|
223 history = self.history() |
|
224 return len(history.forwardItems(HelpViewerWidget.MaxHistoryItems)) |
|
225 |
|
226 def historyTitle(self, offset): |
|
227 """ |
|
228 Public method to get the title of a history item. |
|
229 |
|
230 @param offset offset of the item with respect to the current page |
|
231 @type int |
|
232 @return title of the requeted item in history |
|
233 @rtype str |
|
234 """ |
|
235 history = self.history() |
|
236 currentIndex = history.currentItemIndex() |
|
237 itm = self.history().itemAt(currentIndex + offset) |
|
238 return itm.title() |
|
239 |
|
240 def gotoHistory(self, offset): |
|
241 """ |
|
242 Public method to go to a history item. |
|
243 |
|
244 @param offset offset of the item with respect to the current page |
|
245 @type int |
|
246 """ |
|
247 history = self.history() |
|
248 currentIndex = history.currentItemIndex() |
|
249 itm = self.history().itemAt(currentIndex + offset) |
|
250 history.goToItem(itm) |
|
251 |
|
252 def clearHistory(self): |
|
253 """ |
|
254 Public method to clear the history. |
|
255 """ |
|
256 self.history().clear() |
|
257 |
|
258 ####################################################################### |
|
259 ## Zoom related methods below |
|
260 ####################################################################### |
|
261 |
|
262 def __levelForScale(self, scale): |
|
263 """ |
|
264 Private method determining the zoom level index given a zoom factor. |
|
265 |
|
266 @param scale zoom factor |
|
267 @type int |
|
268 @return index of zoom factor |
|
269 @rtype int |
|
270 """ |
|
271 try: |
|
272 index = self.ZoomLevels.index(scale) |
|
273 except ValueError: |
|
274 for _index in range(len(self.ZoomLevels)): |
|
275 if scale <= self.ZoomLevels[scale]: |
|
276 break |
|
277 return index |
|
278 |
|
279 def scaleUp(self): |
|
280 """ |
|
281 Public method to zoom in. |
|
282 """ |
|
283 index = self.__levelForScale(self.__currentScale) |
|
284 if index < len(self.ZoomLevels) - 1: |
|
285 self.setScale(self.ZoomLevels[index + 1]) |
|
286 |
|
287 def scaleDown(self): |
|
288 """ |
|
289 Public method to zoom out. |
|
290 """ |
|
291 index = self.__levelForScale(self.__currentScale) |
|
292 if index > 0: |
|
293 self.setScale(self.ZoomLevels[index - 1]) |
|
294 |
|
295 def setScale(self, scale): |
|
296 """ |
|
297 Public method to set the zoom level. |
|
298 |
|
299 @param scale zoom level to set |
|
300 @type int |
|
301 """ |
|
302 if scale != self.__currentScale: |
|
303 self.setZoomFactor(scale / 100.0) |
|
304 self.__currentScale = scale |
|
305 self.zoomChanged.emit() |
|
306 |
|
307 def resetScale(self): |
|
308 """ |
|
309 Public method to reset the zoom level. |
|
310 """ |
|
311 index = self.__levelForScale(self.ZoomLevelDefault) |
|
312 self.setScale(self.ZoomLevels[index]) |
|
313 |
|
314 def scale(self): |
|
315 """ |
|
316 Public method to get the zoom level. |
|
317 |
|
318 @return current zoom level |
|
319 @rtype int |
|
320 """ |
|
321 return self.__currentScale |
|
322 |
|
323 def isScaleUpAvailable(self): |
|
324 """ |
|
325 Public method to check, if the max. zoom level is reached. |
|
326 |
|
327 @return flag indicating scale up is available |
|
328 @rtype bool |
|
329 """ |
|
330 index = self.__levelForScale(self.__currentScale) |
|
331 return index < len(self.ZoomLevels) - 1 |
|
332 |
|
333 def isScaleDownAvailable(self): |
|
334 """ |
|
335 Public method to check, if the min. zoom level is reached. |
|
336 |
|
337 @return flag indicating scale down is available |
|
338 @rtype bool |
|
339 """ |
|
340 index = self.__levelForScale(self.__currentScale) |
|
341 return index > 0 |
|
342 |
|
343 ####################################################################### |
|
344 ## Event handlers below |
|
345 ####################################################################### |
|
346 |
|
347 def eventFilter(self, obj, evt): |
|
348 """ |
|
349 Public method to process event for other objects. |
|
350 |
|
351 @param obj reference to object to process events for |
|
352 @type QObject |
|
353 @param evt reference to event to be processed |
|
354 @type QEvent |
|
355 @return flag indicating that the event should be filtered out |
|
356 @rtype bool |
|
357 """ |
|
358 if ( |
|
359 obj is self and |
|
360 evt.type() == QEvent.Type.ParentChange and |
|
361 self.parentWidget() is not None |
|
362 ): |
|
363 self.parentWidget().installEventFilter(self) |
|
364 |
|
365 # find the render widget receiving events for the web page |
|
366 if obj is self and evt.type() == QEvent.Type.ChildAdded: |
|
367 QTimer.singleShot(0, self.__setRwhvqt) |
|
368 |
|
369 # forward events to WebBrowserView |
|
370 if ( |
|
371 obj is self.__rwhvqt and |
|
372 evt.type() in [QEvent.Type.KeyPress, |
|
373 QEvent.Type.MouseButtonRelease, |
|
374 QEvent.Type.Wheel, |
|
375 QEvent.Type.Gesture] |
|
376 ): |
|
377 wasAccepted = evt.isAccepted() |
|
378 evt.setAccepted(False) |
|
379 if evt.type() == QEvent.Type.KeyPress: |
|
380 self._keyPressEvent(evt) |
|
381 elif evt.type() == QEvent.Type.MouseButtonRelease: |
|
382 self._mouseReleaseEvent(evt) |
|
383 elif evt.type() == QEvent.Type.Wheel: |
|
384 self._wheelEvent(evt) |
|
385 elif evt.type() == QEvent.Type.Gesture: |
|
386 self._gestureEvent(evt) |
|
387 ret = evt.isAccepted() |
|
388 evt.setAccepted(wasAccepted) |
|
389 return ret |
|
390 |
|
391 if ( |
|
392 obj is self.parentWidget() and |
|
393 evt.type() in [QEvent.Type.KeyPress, QEvent.Type.KeyRelease] |
|
394 ): |
|
395 wasAccepted = evt.isAccepted() |
|
396 evt.setAccepted(False) |
|
397 if evt.type() == QEvent.Type.KeyPress: |
|
398 self._keyPressEvent(evt) |
|
399 ret = evt.isAccepted() |
|
400 evt.setAccepted(wasAccepted) |
|
401 return ret |
|
402 |
|
403 # block already handled events |
|
404 if ( |
|
405 obj is self and |
|
406 evt.type() in [QEvent.Type.KeyPress, |
|
407 QEvent.Type.MouseButtonRelease, |
|
408 QEvent.Type.Wheel, |
|
409 QEvent.Type.Gesture] |
|
410 ): |
|
411 return True |
|
412 |
|
413 return super().eventFilter(obj, evt) |
|
414 |
|
415 def _keyPressEvent(self, evt): |
|
416 """ |
|
417 Protected method called by a key press. |
|
418 |
|
419 @param evt reference to the key event |
|
420 @type QKeyEvent |
|
421 """ |
|
422 key = evt.key() |
|
423 isControlModifier = ( |
|
424 evt.modifiers() == Qt.KeyboardModifier.ControlModifier |
|
425 ) |
|
426 |
|
427 if ( |
|
428 key == Qt.Key.Key_ZoomIn or |
|
429 (key == Qt.Key.Key_Plus and isControlModifier) |
|
430 ): |
|
431 self.scaleUp() |
|
432 evt.accept() |
|
433 elif ( |
|
434 key == Qt.Key.Key_ZoomOut or |
|
435 (key == Qt.Key.Key_Minus and isControlModifier) |
|
436 ): |
|
437 self.scaleDown() |
|
438 evt.accept() |
|
439 elif key == Qt.Key.Key_0 and isControlModifier: |
|
440 self.resetScale() |
|
441 evt.accept() |
|
442 elif ( |
|
443 key == Qt.Key.Key_Backspace or |
|
444 (key == Qt.Key.Key_Left and isControlModifier) |
|
445 ): |
|
446 self.backward() |
|
447 evt.accept() |
|
448 elif key == Qt.Key.Key_Right and isControlModifier: |
|
449 self.forward() |
|
450 evt.accept() |
|
451 elif key == Qt.Key.Key_F and isControlModifier: |
|
452 self.__helpViewerWidget.showHideSearch(True) |
|
453 evt.accept() |
|
454 elif ( |
|
455 key == Qt.Key.Key_F3 and |
|
456 evt.modifiers() == Qt.KeyboardModifier.NoModifier |
|
457 ): |
|
458 self.__helpViewerWidget.searchNext() |
|
459 evt.accept() |
|
460 elif ( |
|
461 key == Qt.Key.Key_F3 and |
|
462 evt.modifiers() == Qt.KeyboardModifier.ShiftModifier |
|
463 ): |
|
464 self.__helpViewerWidget.searchPrev() |
|
465 evt.accept() |
|
466 |
|
467 def _mouseReleaseEvent(self, evt): |
|
468 """ |
|
469 Protected method called by a mouse release event. |
|
470 |
|
471 @param evt reference to the mouse event |
|
472 @type QMouseEvent |
|
473 """ |
|
474 accepted = evt.isAccepted() |
|
475 self.__page.event(evt) |
|
476 if ( |
|
477 not evt.isAccepted() and |
|
478 evt.button() == Qt.MouseButton.MiddleButton |
|
479 ): |
|
480 url = QUrl(QGuiApplication.clipboard().text( |
|
481 QClipboard.Mode.Selection)) |
|
482 if ( |
|
483 not url.isEmpty() and |
|
484 url.isValid() and |
|
485 url.scheme() != "" |
|
486 ): |
|
487 self.setLink(url) |
|
488 accepted = True |
|
489 evt.setAccepted(accepted) |
|
490 |
|
491 def _wheelEvent(self, evt): |
|
492 """ |
|
493 Protected method to handle wheel events. |
|
494 |
|
495 @param evt reference to the wheel event |
|
496 @type QWheelEvent |
|
497 """ |
|
498 delta = evt.angleDelta().y() |
|
499 if evt.modifiers() & Qt.KeyboardModifier.ControlModifier: |
|
500 if delta < 0: |
|
501 self.scaleDown() |
|
502 elif delta > 0: |
|
503 self.scaleUp() |
|
504 evt.accept() |
|
505 |
|
506 elif evt.modifiers() & Qt.KeyboardModifier.ShiftModifier: |
|
507 if delta < 0: |
|
508 self.backward() |
|
509 elif delta > 0: |
|
510 self.forward() |
|
511 evt.accept() |
|
512 |
|
513 def _gestureEvent(self, evt): |
|
514 """ |
|
515 Protected method handling gesture events. |
|
516 |
|
517 @param evt reference to the gesture event |
|
518 @type QGestureEvent |
|
519 """ |
|
520 pinch = evt.gesture(Qt.GestureType.PinchGesture) |
|
521 if pinch: |
|
522 if pinch.state() == Qt.GestureState.GestureStarted: |
|
523 pinch.setTotalScaleFactor(self.__currentScale / 100.0) |
|
524 elif pinch.state() == Qt.GestureState.GestureUpdated: |
|
525 scaleFactor = pinch.totalScaleFactor() |
|
526 self.setScale(int(scaleFactor * 100)) |
|
527 evt.accept() |
|
528 |
|
529 def event(self, evt): |
|
530 """ |
|
531 Public method handling events. |
|
532 |
|
533 @param evt reference to the event (QEvent) |
|
534 @return flag indicating, if the event was handled (boolean) |
|
535 """ |
|
536 if evt.type() == QEvent.Type.Gesture: |
|
537 self._gestureEvent(evt) |
|
538 return True |
|
539 |
|
540 return super().event(evt) |
|
541 |
|
542 ####################################################################### |
|
543 ## Context menu related methods below |
|
544 ####################################################################### |
|
545 |
|
546 def contextMenuEvent(self, evt): |
|
547 """ |
|
548 Protected method called to create a context menu. |
|
549 |
|
550 This method is overridden from QWebEngineView. |
|
551 |
|
552 @param evt reference to the context menu event object |
|
553 @type QContextMenuEvent |
|
554 """ |
|
555 pos = evt.pos() |
|
556 reason = evt.reason() |
|
557 QTimer.singleShot( |
|
558 0, |
|
559 lambda: self._contextMenuEvent(QContextMenuEvent(reason, pos))) |
|
560 # needs to be done this way because contextMenuEvent is blocking |
|
561 # the main loop |
|
562 |
|
563 def _contextMenuEvent(self, evt): |
|
564 """ |
|
565 Protected method called to create a context menu. |
|
566 |
|
567 @param evt reference to the context menu event object |
|
568 (QContextMenuEvent) |
|
569 """ |
|
570 self.__menu.clear() |
|
571 |
|
572 self.__createContextMenu(self.__menu) |
|
573 |
|
574 if not self.__menu.isEmpty(): |
|
575 pos = evt.globalPos() |
|
576 self.__menu.popup(QPoint(pos.x(), pos.y() + 1)) |
|
577 |
|
578 def __createContextMenu(self, menu): |
|
579 """ |
|
580 Private method to populate the context menu. |
|
581 |
|
582 @param menu reference to the menu to be populated |
|
583 @type QMenu |
|
584 """ |
|
585 contextMenuData = self.lastContextMenuRequest() |
|
586 |
|
587 act = menu.addAction( |
|
588 UI.PixmapCache.getIcon("back"), |
|
589 self.tr("Backward"), |
|
590 self.backward) |
|
591 act.setEnabled(self.isBackwardAvailable()) |
|
592 |
|
593 act = menu.addAction( |
|
594 UI.PixmapCache.getIcon("forward"), |
|
595 self.tr("Forward"), |
|
596 self.forward) |
|
597 act.setEnabled(self.isForwardAvailable()) |
|
598 |
|
599 act = menu.addAction( |
|
600 UI.PixmapCache.getIcon("reload"), |
|
601 self.tr("Reload"), |
|
602 self.reload) |
|
603 |
|
604 if ( |
|
605 not contextMenuData.linkUrl().isEmpty() and |
|
606 contextMenuData.linkUrl().scheme() != "javascript" |
|
607 ): |
|
608 self.__createLinkContextMenu(menu, contextMenuData) |
|
609 |
|
610 menu.addSeparator() |
|
611 |
|
612 act = menu.addAction( |
|
613 UI.PixmapCache.getIcon("editCopy"), |
|
614 self.tr("Copy Page URL to Clipboard")) |
|
615 act.setData(self.link()) |
|
616 act.triggered.connect( |
|
617 functools.partial(self.__copyLink, act)) |
|
618 |
|
619 act = menu.addAction( |
|
620 UI.PixmapCache.getIcon("bookmark22"), |
|
621 self.tr("Bookmark Page")) |
|
622 act.setData({ |
|
623 "title": self.pageTitle(), |
|
624 "url": self.link() |
|
625 }) |
|
626 act.triggered.connect( |
|
627 functools.partial(self.__bookmarkPage, act)) |
|
628 |
|
629 menu.addSeparator() |
|
630 |
|
631 act = menu.addAction( |
|
632 UI.PixmapCache.getIcon("zoomIn"), |
|
633 self.tr("Zoom in"), |
|
634 self.scaleUp) |
|
635 act.setEnabled(self.isScaleUpAvailable()) |
|
636 |
|
637 act = menu.addAction( |
|
638 UI.PixmapCache.getIcon("zoomOut"), |
|
639 self.tr("Zoom out"), |
|
640 self.scaleDown) |
|
641 act.setEnabled(self.isScaleDownAvailable()) |
|
642 |
|
643 menu.addAction( |
|
644 UI.PixmapCache.getIcon("zoomReset"), |
|
645 self.tr("Zoom reset"), |
|
646 self.resetScale) |
|
647 |
|
648 menu.addSeparator() |
|
649 |
|
650 act = menu.addAction( |
|
651 UI.PixmapCache.getIcon("editCopy"), |
|
652 self.tr("Copy"), |
|
653 self.__copyText) |
|
654 act.setEnabled(bool(contextMenuData.selectedText())) |
|
655 |
|
656 menu.addAction( |
|
657 UI.PixmapCache.getIcon("editSelectAll"), |
|
658 self.tr("Select All"), |
|
659 self.__selectAll) |
|
660 |
|
661 menu.addSeparator() |
|
662 |
|
663 menu.addAction( |
|
664 UI.PixmapCache.getIcon("tabClose"), |
|
665 self.tr('Close'), |
|
666 self.__closePage) |
|
667 |
|
668 act = menu.addAction( |
|
669 UI.PixmapCache.getIcon("tabCloseOther"), |
|
670 self.tr("Close Others"), |
|
671 self.__closeOtherPages) |
|
672 act.setEnabled(self.__helpViewerWidget.openPagesCount() > 1) |
|
673 |
|
674 def __createLinkContextMenu(self, menu, contextMenuData): |
|
675 """ |
|
676 Private method to populate the context menu for URLs. |
|
677 |
|
678 @param menu reference to the menu to be populated |
|
679 @type QMenu |
|
680 @param contextMenuData data of the last context menu request |
|
681 @type QWebEngineContextMenuRequest |
|
682 """ |
|
683 if not menu.isEmpty(): |
|
684 menu.addSeparator() |
|
685 |
|
686 act = menu.addAction( |
|
687 UI.PixmapCache.getIcon("openNewTab"), |
|
688 self.tr("Open Link in New Page")) |
|
689 act.setData(contextMenuData.linkUrl()) |
|
690 act.triggered.connect( |
|
691 functools.partial(self.__openLinkInNewPage, act)) |
|
692 |
|
693 act = menu.addAction( |
|
694 UI.PixmapCache.getIcon("newWindow"), |
|
695 self.tr("Open Link in Background Page")) |
|
696 act.setData(contextMenuData.linkUrl()) |
|
697 act.triggered.connect( |
|
698 functools.partial(self.__openLinkInBackgroundPage, act)) |
|
699 |
|
700 menu.addSeparator() |
|
701 |
|
702 act = menu.addAction( |
|
703 UI.PixmapCache.getIcon("editCopy"), |
|
704 self.tr("Copy URL to Clipboard")) |
|
705 act.setData(contextMenuData.linkUrl()) |
|
706 act.triggered.connect( |
|
707 functools.partial(self.__copyLink, act)) |
|
708 |
|
709 def __openLinkInNewPage(self, act): |
|
710 """ |
|
711 Private method called by the context menu to open a link in a new page. |
|
712 |
|
713 @param act reference to the action that triggered |
|
714 @type QAction |
|
715 """ |
|
716 url = act.data() |
|
717 if url.isEmpty(): |
|
718 return |
|
719 |
|
720 self.__helpViewerWidget.openUrlNewPage(url) |
|
721 |
|
722 def __openLinkInBackgroundPage(self, act): |
|
723 """ |
|
724 Private method called by the context menu to open a link in a |
|
725 background page. |
|
726 |
|
727 @param act reference to the action that triggered |
|
728 @type QAction |
|
729 """ |
|
730 url = act.data() |
|
731 if url.isEmpty(): |
|
732 return |
|
733 |
|
734 self.__helpViewerWidget.openUrlNewBackgroundPage(url) |
|
735 |
|
736 def __bookmarkPage(self, act): |
|
737 """ |
|
738 Private method called by the context menu to bookmark the page. |
|
739 |
|
740 @param act reference to the action that triggered |
|
741 @type QAction |
|
742 """ |
|
743 data = act.data() |
|
744 if data: |
|
745 with contextlib.suppress(KeyError): |
|
746 url = data["url"] |
|
747 title = data["title"] |
|
748 |
|
749 self.__helpViewerWidget.bookmarkPage(title, url) |
|
750 |
|
751 def __copyLink(self, act): |
|
752 """ |
|
753 Private method called by the context menu to copy a link to the |
|
754 clipboard. |
|
755 |
|
756 @param act reference to the action that triggered |
|
757 @type QAction |
|
758 """ |
|
759 data = act.data() |
|
760 if isinstance(data, QUrl) and data.isEmpty(): |
|
761 return |
|
762 |
|
763 if isinstance(data, QUrl): |
|
764 data = data.toString() |
|
765 |
|
766 # copy the URL to both clipboard areas |
|
767 QGuiApplication.clipboard().setText(data, QClipboard.Mode.Clipboard) |
|
768 QGuiApplication.clipboard().setText(data, QClipboard.Mode.Selection) |
|
769 |
|
770 def __copyText(self): |
|
771 """ |
|
772 Private method called by the context menu to copy selected text to the |
|
773 clipboard. |
|
774 """ |
|
775 self.triggerPageAction(QWebEnginePage.WebAction.Copy) |
|
776 |
|
777 def __selectAll(self): |
|
778 """ |
|
779 Private method called by the context menu to select all text. |
|
780 """ |
|
781 self.triggerPageAction(QWebEnginePage.WebAction.SelectAll) |
|
782 |
|
783 def __closePage(self): |
|
784 """ |
|
785 Private method called by the context menu to close the current page. |
|
786 """ |
|
787 self.__helpViewerWidget.closeCurrentPage() |
|
788 |
|
789 def __closeOtherPages(self): |
|
790 """ |
|
791 Private method called by the context menu to close all other pages. |
|
792 """ |
|
793 self.__helpViewerWidget.closeOtherPages() |