Sat, 16 Oct 2021 20:38:23 +0200
Continued implementing the embedded help viewer widget. Implemented most part of the QWebEngine based help viewer.
--- a/eric7.epj Sat Oct 16 20:37:32 2021 +0200 +++ b/eric7.epj Sat Oct 16 20:38:23 2021 +0200 @@ -2284,7 +2284,8 @@ "eric7/HelpViewer/HelpViewerWidget.py", "eric7/HelpViewer/OpenPagesWidget.py", "eric7/HelpViewer/HelpViewerImpl.py", - "eric7/HelpViewer/HelpViewerImpl_qtb.py" + "eric7/HelpViewer/HelpViewerImpl_qtb.py", + "eric7/HelpViewer/HelpViewerImpl_qwe.py" ], "SPELLEXCLUDES": "Dictionaries/excludes.dic", "SPELLLANGUAGE": "en_US",
--- a/eric7/HelpViewer/HelpViewerImpl.py Sat Oct 16 20:37:32 2021 +0200 +++ b/eric7/HelpViewer/HelpViewerImpl.py Sat Oct 16 20:38:23 2021 +0200 @@ -123,6 +123,26 @@ """ raise RuntimeError("Not implemented") + def isBackwardAvailable(self): + """ + Public method to check, if stepping backward through the history is + available. + + @exception RuntimeError raised when not implemented + """ + raise RuntimeError("Not implemented") + return False + + def isForwardAvailable(self): + """ + Public method to check, if stepping forward through the history is + available. + + @exception RuntimeError raised when not implemented + """ + raise RuntimeError("Not implemented") + return False + def scaleUp(self): """ Public method to zoom in. @@ -139,6 +159,16 @@ """ raise RuntimeError("Not implemented") + def setScale(self, scale): + """ + Public method to set the zoom level. + + @param scale zoom level to set + @type int + @exception RuntimeError raised when not implemented + """ + raise RuntimeError("Not implemented") + def resetScale(self): """ Public method to reset the zoom level.
--- a/eric7/HelpViewer/HelpViewerImpl_qtb.py Sat Oct 16 20:37:32 2021 +0200 +++ b/eric7/HelpViewer/HelpViewerImpl_qtb.py Sat Oct 16 20:38:23 2021 +0200 @@ -82,7 +82,19 @@ @return page title @rtype str """ - return self.documentTitle() + titleStr = self.documentTitle() + if not titleStr: + url = self.url() + + titleStr = url.host() + if not titleStr: + titleStr = url.toString( + QUrl.UrlFormattingOption.RemoveFragment) + + if not titleStr or titleStr == "about:blank": + titleStr = self.tr("Empty Page") + + return titleStr def loadResource(self, type_, name): """ @@ -137,6 +149,20 @@ for ind in range(index): self.forward() + def isBackwardAvailable(self): + """ + Public method to check, if stepping backward through the history is + available. + """ + return QTextBrowser.isBackwardAvailable(self) + + def isForwardAvailable(self): + """ + Public method to check, if stepping forward through the history is + available. + """ + return QTextBrowser.isForwardAvailable(self) + def scaleUp(self): """ Public method to zoom in. @@ -210,12 +236,21 @@ @param evt reference to the event object @type QWheelEvent """ + delta = evt.angleDelta().y() if evt.modifiers() == Qt.KeyboardModifier.ControlModifier: - if evt.angleDelta().y() > 0: + if delta > 0: self.scaleUp() else: self.scaleDown() evt.accept() + + elif evt.modifiers() & Qt.KeyboardModifier.ShiftModifier: + if delta < 0: + self.backward() + elif delta > 0: + self.forward() + evt.accept() + else: QTextBrowser.wheelEvent(self, evt) @@ -287,10 +322,13 @@ evt.accept() # TODO: implement context menu + # - Backward + # - Forward + # - Reload # - Open Link # - Open Link in New Page # - Open Link in Background Page # - Copy - # - Copy Link LOcation + # - Copy Link Location # - Select All # TODO: add Ctrl+LMB action (open link in new page)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eric7/HelpViewer/HelpViewerImpl_qwe.py Sat Oct 16 20:38:23 2021 +0200 @@ -0,0 +1,688 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2021 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the help viewer base class. +""" + +from PyQt6.QtCore import pyqtSlot, Qt, QEvent, QTimer, QUrl, QPoint +from PyQt6.QtGui import QGuiApplication, QClipboard, QContextMenuEvent +from PyQt6.QtWidgets import QMenu +from PyQt6.QtWebEngineWidgets import QWebEngineView +from PyQt6.QtWebEngineCore import QWebEnginePage + +from .HelpViewerWidget import HelpViewerWidget +from .HelpViewerImpl import HelpViewerImpl + +import UI.PixmapCache + +class HelpViewerImpl_qwe(HelpViewerImpl, QWebEngineView): + """ + Class implementing the QTextBrowser based help viewer class. + """ + ZoomLevels = [ + 30, 40, 50, 67, 80, 90, + 100, + 110, 120, 133, 150, 170, 200, 220, 233, 250, 270, 285, 300, + ] + ZoomLevelDefault = 100 + + def __init__(self, engine, parent=None): + """ + Constructor + + @param engine reference to the help engine + @type QHelpEngine + @param parent reference to the parent widget + @type QWidget + """ + QWebEngineView.__init__(self, parent=parent) + HelpViewerImpl.__init__(self, engine) + + self.__helpViewerWidget = parent + + self.__rwhvqt = None + self.installEventFilter(self) + + self.__page = None + self.__createNewPage() + + self.__currentScale = 100 + + self.__menu = QMenu(self) + + def __createNewPage(self): + """ + Private method to create a new page object. + """ + self.__page = QWebEnginePage(self.__helpViewerWidget.webProfile()) + self.setPage(self.__page) + + self.__page.titleChanged.connect(self.__titleChanged) + self.__page.urlChanged.connect(self.__titleChanged) + + def __setRwhvqt(self): + """ + Private slot to set widget that receives input events. + """ + self.grabGesture(Qt.GestureType.PinchGesture) + self.__rwhvqt = self.focusProxy() + if self.__rwhvqt: + self.__rwhvqt.grabGesture(Qt.GestureType.PinchGesture) + self.__rwhvqt.installEventFilter(self) + else: + print("Focus proxy is null!") # __IGNORE_WARNING_M801__ + + def setUrl(self, url): + """ + Public method to set the URL of the document to be shown. + + @param url URL of the document + @type QUrl + """ + QWebEngineView.setUrl(self, url) + + def url(self): + """ + Public method to get the URL of the shown document. + + @return url URL of the document + @rtype QUrl + """ + return QWebEngineView.url(self) + + @pyqtSlot() + def __titleChanged(self): + """ + Private method to handle a change of the web page title. + """ + super().titleChanged.emit() + + def title(self): + """ + Public method get the page title. + + @return page title + @rtype str + """ + titleStr = QWebEngineView.title(self) + if not titleStr: + if self.url().isEmpty(): + url = self.__page.requestedUrl() + else: + url = self.url() + + titleStr = url.host() + if not titleStr: + titleStr = url.toString( + QUrl.UrlFormattingOption.RemoveFragment) + + if not titleStr or titleStr == "about:blank": + titleStr = self.tr("Empty Page") + + return titleStr + + def isBackwardAvailable(self): + """ + Public method to check, if stepping backward through the history is + available. + """ + return self.history().canGoBack() + + def isForwardAvailable(self): + """ + Public method to check, if stepping forward through the history is + available. + """ + return self.history().canGoForward() + + def backward(self): + """ + Public slot to move backwards in history. + """ + self.triggerPageAction(QWebEnginePage.WebAction.Back) + + def forward(self): + """ + Public slot to move forward in history. + """ + self.triggerPageAction(QWebEnginePage.WebAction.Forward) + + def reload(self): + """ + Public slot to reload the current page. + """ + self.triggerPageAction(QWebEnginePage.WebAction.Reload) + + def backwardHistoryCount(self): + """ + Public method to get the number of available back history items. + + Note: For performance reasons this is limited to the maximum number of + history items the help viewer is interested in. + + @return count of available back history items + @rtype int + """ + history = self.history() + return len(history.backItems(HelpViewerWidget.MaxHistoryItems)) + + def forwardHistoryCount(self): + """ + Public method to get the number of available forward history items. + + Note: For performance reasons this is limited to the maximum number of + history items the help viewer is interested in. + + @return count of available forward history items + @rtype int + """ + history = self.history() + return len(history.forwardItems(HelpViewerWidget.MaxHistoryItems)) + + def historyTitle(self, offset): + """ + Public method to get the title of a history item. + + @param offset offset of the item with respect to the current page + @type int + @return title of the requeted item in history + @rtype str + """ + history = self.history() + currentIndex = history.currentItemIndex() + itm = self.history().itemAt(currentIndex + offset) + return itm.title() + + def gotoHistory(self, offset): + """ + Public method to go to ahistory item + + @param offset offset of the item with respect to the current page + @type int + """ + history = self.history() + currentIndex = history.currentItemIndex() + itm = self.history().itemAt(currentIndex + offset) + history.goToItem(itm) + + def clearHistory(self): + """ + Public method to clear the history. + """ + self.history().clear() + + ####################################################################### + + def __levelForScale(self, scale): + """ + Private method determining the zoom level index given a zoom factor. + + @param zoom zoom factor + @type int + @return index of zoom factor + @rtype int + """ + try: + index = self.ZoomLevels.index(scale) + except ValueError: + for index in range(len(self.ZoomLevels)): + if scale <= self.ZoomLevels[scale]: + break + return index + + def scaleUp(self): + """ + Public method to zoom in. + """ + index = self.__levelForScale(self.__currentScale) + if index < len(self.ZoomLevels) - 1: + self.setScale(self.ZoomLevels[index + 1]) + + def scaleDown(self): + """ + Public method to zoom out. + """ + index = self.__levelForScale(self.__currentScale) + if index > 0: + self.setScale(self.ZoomLevels[index - 1]) + + def setScale(self, scale): + """ + Public method to set the zoom level. + + @param scale zoom level to set + @type int + """ + if scale != self.__currentScale: + self.setZoomFactor(scale / 100.0) + self.__currentScale = scale + self.zoomChanged.emit() + + def resetScale(self): + """ + Public method to reset the zoom level. + """ + index = self.__levelForScale(self.ZoomLevelDefault) + self.setScale(self.ZoomLevels[index]) + + def scale(self): + """ + Public method to get the zoom level. + + @return current zoom level + @rtype int + """ + return self.__currentScale + + def isScaleUpAvailable(self): + """ + Public method to check, if the max. zoom level is reached. + + @return flag indicating scale up is available + @rtype bool + """ + index = self.__levelForScale(self.__currentScale) + return index < len(self.ZoomLevels) - 1 + + def isScaleDownAvailable(self): + """ + Public method to check, if the min. zoom level is reached. + + @return flag indicating scale down is available + @rtype bool + """ + index = self.__levelForScale(self.__currentScale) + return index > 0 + + ####################################################################### + ## Event handlers below + ####################################################################### + + def eventFilter(self, obj, evt): + """ + Public method to process event for other objects. + + @param obj reference to object to process events for + @type QObject + @param evt reference to event to be processed + @type QEvent + @return flag indicating that the event should be filtered out + @rtype bool + """ + if ( + obj is self and + evt.type() == QEvent.Type.ParentChange and + self.parentWidget() is not None + ): + self.parentWidget().installEventFilter(self) + + # find the render widget receiving events for the web page + if obj is self and evt.type() == QEvent.Type.ChildAdded: + QTimer.singleShot(0, self.__setRwhvqt) + + # forward events to WebBrowserView + if ( + obj is self.__rwhvqt and + evt.type() in [QEvent.Type.KeyPress, + QEvent.Type.MouseButtonRelease, + QEvent.Type.Wheel, + QEvent.Type.Gesture] + ): + wasAccepted = evt.isAccepted() + evt.setAccepted(False) + if evt.type() == QEvent.Type.KeyPress: + self._keyPressEvent(evt) + elif evt.type() == QEvent.Type.MouseButtonRelease: + self._mouseReleaseEvent(evt) + elif evt.type() == QEvent.Type.Wheel: + self._wheelEvent(evt) + elif evt.type() == QEvent.Type.Gesture: + self._gestureEvent(evt) + ret = evt.isAccepted() + evt.setAccepted(wasAccepted) + return ret + + if ( + obj is self.parentWidget() and + evt.type() in [QEvent.Type.KeyPress, QEvent.Type.KeyRelease] + ): + wasAccepted = evt.isAccepted() + evt.setAccepted(False) + if evt.type() == QEvent.Type.KeyPress: + self._keyPressEvent(evt) + ret = evt.isAccepted() + evt.setAccepted(wasAccepted) + return ret + + # block already handled events + if obj is self: + if evt.type() in [QEvent.Type.KeyPress, + QEvent.Type.MouseButtonRelease, + QEvent.Type.Wheel, + QEvent.Type.Gesture]: + return True + + return super().eventFilter(obj, evt) + + def _keyPressEvent(self, evt): + """ + Protected method called by a key press. + + @param evt reference to the key event + @type QKeyEvent + """ + if evt.key() == Qt.Key.Key_ZoomIn: + self.scaleUp() + evt.accept() + elif evt.key() == Qt.Key.Key_ZoomOut: + self.scaleDown() + evt.accept() + elif evt.key() == Qt.Key.Key_Plus: + if evt.modifiers() & Qt.KeyboardModifier.ControlModifier: + self.scaleUp() + evt.accept() + elif evt.key() == Qt.Key.Key_Minus: + if evt.modifiers() & Qt.KeyboardModifier.ControlModifier: + self.scaleDown() + evt.accept() + elif evt.key() == Qt.Key.Key_0: + if evt.modifiers() & Qt.KeyboardModifier.ControlModifier: + self.resetScale() + evt.accept() + elif evt.key() == Qt.Key.Key_Backspace: + self.pageAction(QWebEnginePage.WebAction.Back).trigger() + evt.accept() + + def _mouseReleaseEvent(self, evt): + """ + Protected method called by a mouse release event. + + @param evt reference to the mouse event + @type QMouseEvent + """ + accepted = evt.isAccepted() + self.__page.event(evt) + if ( + not evt.isAccepted() and + evt.button() == Qt.MouseButton.MiddleButton + ): + url = QUrl(QGuiApplication.clipboard().text( + QClipboard.Mode.Selection)) + if ( + not url.isEmpty() and + url.isValid() and + url.scheme() != "" + ): + self.setUrl(url) + accepted = True + evt.setAccepted(accepted) + + def _wheelEvent(self, evt): + """ + Protected method to handle wheel events. + + @param evt reference to the wheel event + @type QWheelEvent + """ + delta = evt.angleDelta().y() + if evt.modifiers() & Qt.KeyboardModifier.ControlModifier: + if delta < 0: + self.scaleDown() + elif delta > 0: + self.scaleUp() + evt.accept() + + elif evt.modifiers() & Qt.KeyboardModifier.ShiftModifier: + if delta < 0: + self.backward() + elif delta > 0: + self.forward() + evt.accept() + + def _gestureEvent(self, evt): + """ + Protected method handling gesture events. + + @param evt reference to the gesture event + @type QGestureEvent + """ + pinch = evt.gesture(Qt.GestureType.PinchGesture) + if pinch: + if pinch.state() == Qt.GestureState.GestureStarted: + pinch.setTotalScaleFactor(self.__currentScale / 100.0) + elif pinch.state() == Qt.GestureState.GestureUpdated: + scaleFactor = pinch.totalScaleFactor() + self.setScale(int(scaleFactor * 100)) + evt.accept() + + def event(self, evt): + """ + Public method handling events. + + @param evt reference to the event (QEvent) + @return flag indicating, if the event was handled (boolean) + """ + if evt.type() == QEvent.Type.Gesture: + self._gestureEvent(evt) + return True + + return super().event(evt) + + ####################################################################### + ## Context menu related methods below + ####################################################################### + # TODO: implement context menu + + def contextMenuEvent(self, evt): + """ + Protected method called to create a context menu. + + This method is overridden from QWebEngineView. + + @param evt reference to the context menu event object + @type QContextMenuEvent + """ + pos = evt.pos() + reason = evt.reason() + QTimer.singleShot( + 0, + lambda: self._contextMenuEvent(QContextMenuEvent(reason, pos))) + # needs to be done this way because contextMenuEvent is blocking + # the main loop + + def _contextMenuEvent(self, evt): + """ + Protected method called to create a context menu. + + @param evt reference to the context menu event object + (QContextMenuEvent) + """ + self.__menu.clear() + + self.__createContextMenu(self.__menu) + + if not self.__menu.isEmpty(): + pos = evt.globalPos() + self.__menu.popup(QPoint(pos.x(), pos.y() + 1)) + + def __createContextMenu(self, menu): + """ + Private method to populate the context menu. + + @param menu reference to the menu to be populated + @type QMenu + """ + contextMenuData = self.lastContextMenuRequest() + + act = menu.addAction( + UI.PixmapCache.getIcon("back"), + self.tr("Backward"), + self.backward) + act.setEnabled(self.isBackwardAvailable()) + + act = menu.addAction( + UI.PixmapCache.getIcon("forward"), + self.tr("Forward"), + self.forward) + act.setEnabled(self.isForwardAvailable()) + + act = menu.addAction( + UI.PixmapCache.getIcon("reload"), + self.tr("Reload"), + self.reload) + + if ( + not contextMenuData.linkUrl().isEmpty() and + contextMenuData.linkUrl().scheme() != "javascript" + ): + self.__createLinkContextMenu(menu, contextMenuData) + + menu.addSeparator() + + act = menu.addAction( + UI.PixmapCache.getIcon("editCopy"), + self.tr("Copy Page URL to Clipboard")) + act.setData(self.url()) + act.triggered.connect( + lambda: self.__copyLink(act)) + + menu.addSeparator() + + act = menu.addAction( + UI.PixmapCache.getIcon("zoomIn"), + self.tr("Zoom in"), + self.scaleUp) + act.setEnabled(self.isScaleUpAvailable) + + act = menu.addAction( + UI.PixmapCache.getIcon("zoomOut"), + self.tr("Zoom out"), + self.scaleDown) + act.setEnabled(self.isScaleDownAvailable()) + + menu.addAction( + UI.PixmapCache.getIcon("zoomReset"), + self.tr("Zoom reset"), + self.resetScale) + + menu.addSeparator() + + act = menu.addAction( + UI.PixmapCache.getIcon("editCopy"), + self.tr("Copy")) + act.setData(contextMenuData.selectedText()) + act.triggered.connect( lambda: self.__copyText(act)) + act.setEnabled(bool(contextMenuData.selectedText())) + + menu.addAction( + UI.PixmapCache.getIcon("editSelectAll"), + self.tr("Select All"), + self.__selectAll) + + menu.addSeparator() + + menu.addAction( + UI.PixmapCache.getIcon("tabClose"), + self.tr('Close'), + self.__closePage) + + act = menu.addAction( + UI.PixmapCache.getIcon("tabCloseOther"), + self.tr("Close Others"), + self.__closeOtherPages) + act.setEnabled(self.__helpViewerWidget.openPagesCount() > 1) + + def __createLinkContextMenu(self, menu, contextMenuData): + """ + Private method to populate the context menu for URLs. + + @param menu reference to the menu to be populated + @type QMenu + @param contextMenuData data of the last context menu request + @type QWebEngineContextMenuRequest + """ + if not menu.isEmpty(): + menu.addSeparator() + + act = menu.addAction( + UI.PixmapCache.getIcon("openNewTab"), + self.tr("Open Link in New Page")) + act.setData(contextMenuData.linkUrl()) + act.triggered.connect( + lambda: self.__openLinkInNewPage(act)) + + act = menu.addAction( + UI.PixmapCache.getIcon("newWindow"), + self.tr("Open Link in Background Page")) + act.setData(contextMenuData.linkUrl()) + act.triggered.connect( + lambda: self.__openLinkInBackgroundPage(act)) + + menu.addSeparator() + + act = menu.addAction( + UI.PixmapCache.getIcon("editCopy"), + self.tr("Copy URL to Clipboard")) + act.setData(contextMenuData.linkUrl()) + act.triggered.connect( + lambda: self.__copyLink(act)) + + def __openLinkInNewPage(self, act): + """ + Private method called by the context menu to open a link in a new page. + + @param act reference to the action that triggered + @type QAction + """ + # TODO: not yet implemented + + def __openLinkInBackgroundPage(self, act): + """ + Private method called by the context menu to open a link in a + background page. + + @param act reference to the action that triggered + @type QAction + """ + # TODO: not yet implemented + + def __copyLink(self, act): + """ + Private method called by the context menu to copy a link to the + clipboard. + + @param act reference to the action that triggered + @type QAction + """ + # TODO: not yet implemented + + def __copyText(self, act): + """ + Private method called by the context menu to copy selected text to the + clipboard. + + @param act reference to the action that triggered + @type QAction + """ + # TODO: not yet implemented + + def __selectAll(self): + """ + Private method called by the context menu to select all text. + """ + # TODO: not yet implemented + + def __closePage(self): + """ + Private method called by the context menu to close the current page. + """ + # TODO: not yet implemented + + def __closeOtherPages(self): + """ + Private method called by the context menu to close all other pages. + """ + # TODO: not yet implemented
--- a/eric7/HelpViewer/HelpViewerWidget.py Sat Oct 16 20:37:32 2021 +0200 +++ b/eric7/HelpViewer/HelpViewerWidget.py Sat Oct 16 20:38:23 2021 +0200 @@ -9,7 +9,7 @@ import os -from PyQt6.QtCore import pyqtSlot, Qt, QUrl, QTimer +from PyQt6.QtCore import pyqtSlot, Qt, QUrl, QTimer, QByteArray from PyQt6.QtGui import QAction, QFont, QFontMetrics from PyQt6.QtHelp import QHelpEngine from PyQt6.QtWidgets import ( @@ -17,6 +17,11 @@ QToolButton, QButtonGroup, QAbstractButton, QMenu, QFrame, QLabel, QProgressBar ) +try: + from PyQt6.QtWebEngineCore import QWebEngineProfile, QWebEngineSettings + WEBENGINE_AVAILABLE = True +except ImportError: + WEBENGINE_AVAILABLE = True from EricWidgets import EricFileDialog, EricMessageBox @@ -35,6 +40,8 @@ """ Class implementing an embedded viewer for QtHelp and local HTML files. """ + MaxHistoryItems = 20 # max. number of history items to be shown + def __init__(self, parent=None): """ Constructor @@ -62,8 +69,7 @@ QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred) self.__selectorLayout.addWidget(self.__helpSelector) self.__populateHelpSelector() - self.__helpSelector.currentIndexChanged.connect( - self.__helpTopicSelected) + self.__helpSelector.activated.connect(self.__helpTopicSelected) self.__openButton = QToolButton(self) self.__openButton.setIcon(UI.PixmapCache.getIcon("open")) @@ -137,6 +143,14 @@ self.__zoomResetButton.clicked.connect(self.__zoomReset) self.__navButtonsLayout.addWidget(self.__zoomResetButton) + self.__buttonLine2 = QFrame(self) + self.__buttonLine2.setFrameShape(QFrame.Shape.VLine) + self.__buttonLine2.setFrameShadow(QFrame.Shadow.Sunken) + self.__navButtonsLayout.addWidget(self.__buttonLine2) + + # TODO: add plus button to open a new page (about:blank) + # TODO: add minus button to close the current page + self.__navButtonsLayout.addStretch() self.__layout.addLayout(self.__navButtonsLayout) @@ -161,6 +175,10 @@ ################################################################### + # TODO: addd a search widget (EricTextEditSearchWidget) + + ################################################################### + self.__helpNavigationStack = QStackedWidget(self) self.__helpNavigationStack.setSizePolicy( QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred) @@ -210,6 +228,10 @@ self.__initActionsMenu() + if WEBENGINE_AVAILABLE: + self.__initQWebEngine() + self.__ui.preferencesChanged.connect(self.__initQWebEngineSettings) + self.addPage() self.__checkActionButtons() @@ -241,7 +263,7 @@ """ # Open Pages self.__openPagesList = OpenPagesWidget(self.__helpStack, self) - self.__openPagesList.currentChanged.connect(self.__checkActionButtons) + self.__openPagesList.currentChanged.connect(self.__currentPageChanged) self.__helpNavigationStack.addWidget(self.__openPagesList) # QtHelp TOC widget @@ -335,7 +357,7 @@ urlStr = self.__helpSelector.currentData() if urlStr: url = QUrl(urlStr) - self.currentViewer().setUrl(url) + self.openUrl(url) def activate(self, searchWord=None): """ @@ -394,11 +416,13 @@ self.__helpStack.insertWidget(index, viewer) self.__openPagesList.insertPage( index, viewer, background=background) + cv.setFocus(Qt.FocusReason.OtherFocusReason) return self.__helpStack.addWidget(viewer) self.__openPagesList.addPage(viewer, background=background) - + viewer.setFocus(Qt.FocusReason.OtherFocusReason) + @pyqtSlot(QUrl) def openUrl(self, url): """ @@ -410,6 +434,7 @@ cv = self.currentViewer() if cv: cv.setUrl(url) + cv.setFocus(Qt.FocusReason.OtherFocusReason) @pyqtSlot(QUrl) def openUrlNewPage(self, url): @@ -447,10 +472,10 @@ @return help viewer @rtype HelpViewerImpl """ - try: + if WEBENGINE_AVAILABLE: from .HelpViewerImpl_qwe import HelpViewerImpl_qwe viewer = HelpViewerImpl_qwe(self.__helpEngine, self) - except ImportError: + else: from .HelpViewerImpl_qtb import HelpViewerImpl_qtb viewer = HelpViewerImpl_qtb(self.__helpEngine, self) @@ -624,23 +649,6 @@ if cv: cv.reload() - @pyqtSlot() - def __checkActionButtons(self): - """ - Private slot to set the enabled state of the action buttons. - """ - cv = self.currentViewer() - if cv: - self.__backwardButton.setEnabled(cv.isBackwardAvailable()) - self.__forwardButton.setEnabled(cv.isForwardAvailable()) - self.__zoomInButton.setEnabled(cv.isScaleUpAvailable()) - self.__zoomOutButton.setEnabled(cv.isScaleDownAvailable()) - else: - self.__backwardButton.setEnabled(False) - self.__forwardButton.setEnabled(False) - self.__zoomInButton.setEnabled(False) - self.__zoomOutButton.setEnabled(False) - def __showBackMenu(self): """ Private slot showing the backward navigation menu. @@ -648,8 +656,8 @@ cv = self.currentViewer() if cv: self.__backMenu.clear() - backwardHistoryCount = min(cv.backwardHistoryCount(), 20) - # show max. 20 items + backwardHistoryCount = min(cv.backwardHistoryCount(), + HelpViewerWidget.MaxHistoryItems) for index in range(1, backwardHistoryCount + 1): act = QAction(self) @@ -668,8 +676,8 @@ cv = self.currentViewer() if cv: self.__forwardMenu.clear() - forwardHistoryCount = min(cv.forwardHistoryCount(), 20) - # show max. 20 items + forwardHistoryCount = min(cv.forwardHistoryCount(), + HelpViewerWidget.MaxHistoryItems) for index in range(1, forwardHistoryCount + 1): act = QAction(self) @@ -704,6 +712,37 @@ self.__checkActionButtons() ####################################################################### + ## Page navigation related methods below + ####################################################################### + + @pyqtSlot() + def __checkActionButtons(self): + """ + Private slot to set the enabled state of the action buttons. + """ + cv = self.currentViewer() + if cv: + self.__backwardButton.setEnabled(cv.isBackwardAvailable()) + self.__forwardButton.setEnabled(cv.isForwardAvailable()) + self.__zoomInButton.setEnabled(cv.isScaleUpAvailable()) + self.__zoomOutButton.setEnabled(cv.isScaleDownAvailable()) + else: + self.__backwardButton.setEnabled(False) + self.__forwardButton.setEnabled(False) + self.__zoomInButton.setEnabled(False) + self.__zoomOutButton.setEnabled(False) + + @pyqtSlot() + def __currentPageChanged(self): + """ + Private slot handling the selection of another page. + """ + self.__checkActionButtons() + cv = self.currentViewer() + if cv: + cv.setFocus(Qt.FocusReason.OtherFocusReason) + + ####################################################################### ## Zoom related methods below ####################################################################### @@ -878,3 +917,184 @@ if index < 0: index = 0 self.__helpFilterCombo.setCurrentIndex(index) + + ####################################################################### + ## QWebEngine related code below + ####################################################################### + + def __initQWebEngine(self): + """ + Private method to initialize global QWebEngine related objects. + """ + self.__webProfile = QWebEngineProfile.defaultProfile() + self.__webProfile.setHttpCacheType( + QWebEngineProfile.HttpCacheType.MemoryHttpCache) + self.__webProfile.setHttpCacheMaximumSize(0) + + self.__initQWebEngineSettings() + + from WebBrowser.Network.QtHelpSchemeHandler import QtHelpSchemeHandler + self.__qtHelpSchemeHandler = QtHelpSchemeHandler(self.__helpEngine) + self.__webProfile.installUrlSchemeHandler( + QByteArray(b"qthelp"), self.__qtHelpSchemeHandler) + + def webProfile(self): + """ + Public method to get a reference to the global web profile object. + + @return reference to the global web profile object + @rtype QWebEngineProfile + """ + return self.__webProfile + + def webSettings(self): + """ + Public method to get the web settings of the current profile. + + @return web settings of the current profile + @rtype QWebEngineSettings + """ + return self.webProfile().settings() + + def __initQWebEngineSettings(self): + """ + Private method to set the global web settings. + """ + settings = self.webSettings() + + settings.setFontFamily( + QWebEngineSettings.FontFamily.StandardFont, + Preferences.getWebBrowser("StandardFontFamily")) + settings.setFontFamily( + QWebEngineSettings.FontFamily.FixedFont, + Preferences.getWebBrowser("FixedFontFamily")) + settings.setFontFamily( + QWebEngineSettings.FontFamily.SerifFont, + Preferences.getWebBrowser("SerifFontFamily")) + settings.setFontFamily( + QWebEngineSettings.FontFamily.SansSerifFont, + Preferences.getWebBrowser("SansSerifFontFamily")) + settings.setFontFamily( + QWebEngineSettings.FontFamily.CursiveFont, + Preferences.getWebBrowser("CursiveFontFamily")) + settings.setFontFamily( + QWebEngineSettings.FontFamily.FantasyFont, + Preferences.getWebBrowser("FantasyFontFamily")) + + settings.setFontSize( + QWebEngineSettings.FontSize.DefaultFontSize, + Preferences.getWebBrowser("DefaultFontSize")) + settings.setFontSize( + QWebEngineSettings.FontSize.DefaultFixedFontSize, + Preferences.getWebBrowser("DefaultFixedFontSize")) + settings.setFontSize( + QWebEngineSettings.FontSize.MinimumFontSize, + Preferences.getWebBrowser("MinimumFontSize")) + settings.setFontSize( + QWebEngineSettings.FontSize.MinimumLogicalFontSize, + Preferences.getWebBrowser("MinimumLogicalFontSize")) + + settings.setAttribute( + QWebEngineSettings.WebAttribute.AutoLoadImages, + Preferences.getWebBrowser("AutoLoadImages")) + settings.setAttribute( + QWebEngineSettings.WebAttribute.JavascriptEnabled, + True) + # JavaScript is needed for the web browser functionality + settings.setAttribute( + QWebEngineSettings.WebAttribute.JavascriptCanOpenWindows, + Preferences.getWebBrowser("JavaScriptCanOpenWindows")) + settings.setAttribute( + QWebEngineSettings.WebAttribute.JavascriptCanAccessClipboard, + Preferences.getWebBrowser("JavaScriptCanAccessClipboard")) + settings.setAttribute( + QWebEngineSettings.WebAttribute.PluginsEnabled, + False) + + settings.setAttribute( + QWebEngineSettings.WebAttribute.LocalStorageEnabled, + False) + settings.setDefaultTextEncoding( + Preferences.getWebBrowser("DefaultTextEncoding")) + + settings.setAttribute( + QWebEngineSettings.WebAttribute.SpatialNavigationEnabled, + Preferences.getWebBrowser("SpatialNavigationEnabled")) + settings.setAttribute( + QWebEngineSettings.WebAttribute.LinksIncludedInFocusChain, + Preferences.getWebBrowser("LinksIncludedInFocusChain")) + settings.setAttribute( + QWebEngineSettings.WebAttribute.LocalContentCanAccessRemoteUrls, + Preferences.getWebBrowser("LocalContentCanAccessRemoteUrls")) + settings.setAttribute( + QWebEngineSettings.WebAttribute.LocalContentCanAccessFileUrls, + Preferences.getWebBrowser("LocalContentCanAccessFileUrls")) + settings.setAttribute( + QWebEngineSettings.WebAttribute.XSSAuditingEnabled, + Preferences.getWebBrowser("XSSAuditingEnabled")) + settings.setAttribute( + QWebEngineSettings.WebAttribute.ScrollAnimatorEnabled, + Preferences.getWebBrowser("ScrollAnimatorEnabled")) + settings.setAttribute( + QWebEngineSettings.WebAttribute.ErrorPageEnabled, + Preferences.getWebBrowser("ErrorPageEnabled")) + settings.setAttribute( + QWebEngineSettings.WebAttribute.FullScreenSupportEnabled, + False) + settings.setAttribute( + QWebEngineSettings.WebAttribute.ScreenCaptureEnabled, + Preferences.getWebBrowser("ScreenCaptureEnabled")) + settings.setAttribute( + QWebEngineSettings.WebAttribute.WebGLEnabled, + Preferences.getWebBrowser("WebGLEnabled")) + settings.setAttribute( + QWebEngineSettings.WebAttribute.FocusOnNavigationEnabled, + Preferences.getWebBrowser("FocusOnNavigationEnabled")) + settings.setAttribute( + QWebEngineSettings.WebAttribute.PrintElementBackgrounds, + Preferences.getWebBrowser("PrintElementBackgrounds")) + settings.setAttribute( + QWebEngineSettings.WebAttribute.AllowRunningInsecureContent, + Preferences.getWebBrowser("AllowRunningInsecureContent")) + settings.setAttribute( + QWebEngineSettings.WebAttribute.AllowGeolocationOnInsecureOrigins, + Preferences.getWebBrowser("AllowGeolocationOnInsecureOrigins")) + settings.setAttribute( + QWebEngineSettings.WebAttribute + .AllowWindowActivationFromJavaScript, + Preferences.getWebBrowser( + "AllowWindowActivationFromJavaScript")) + settings.setAttribute( + QWebEngineSettings.WebAttribute.ShowScrollBars, + Preferences.getWebBrowser("ShowScrollBars")) + settings.setAttribute( + QWebEngineSettings.WebAttribute.PlaybackRequiresUserGesture, + Preferences.getWebBrowser( + "PlaybackRequiresUserGesture")) + settings.setAttribute( + QWebEngineSettings.WebAttribute.JavascriptCanPaste, + Preferences.getWebBrowser( + "JavaScriptCanPaste")) + settings.setAttribute( + QWebEngineSettings.WebAttribute.WebRTCPublicInterfacesOnly, + False) + settings.setAttribute( + QWebEngineSettings.WebAttribute.DnsPrefetchEnabled, + False) + settings.setAttribute( + QWebEngineSettings.WebAttribute.PdfViewerEnabled, + Preferences.getWebBrowser( + "PdfViewerEnabled")) + + ####################################################################### + ## Utility methods below + ####################################################################### + + def openPagesCount(self): + """ + Public method to get the count of open pages. + + @return count of open pages + @rtype int + """ + return self.__helpStack.count()
--- a/eric7/WebBrowser/QtHelp/HelpIndexWidget.py Sat Oct 16 20:37:32 2021 +0200 +++ b/eric7/WebBrowser/QtHelp/HelpIndexWidget.py Sat Oct 16 20:38:23 2021 +0200 @@ -241,6 +241,7 @@ newBackgroundTab = menu.addAction( self.tr("Open Link in Background Tab")) newWindow = menu.addAction(self.tr("Open Link in New Window")) + # TODO: add link to copy the URL menu.move(self.__index.mapToGlobal(pos)) act = menu.exec()
--- a/eric7/WebBrowser/QtHelp/HelpSearchWidget.py Sat Oct 16 20:37:32 2021 +0200 +++ b/eric7/WebBrowser/QtHelp/HelpSearchWidget.py Sat Oct 16 20:38:23 2021 +0200 @@ -170,6 +170,7 @@ newBackgroundTab = menu.addAction( self.tr("Open Link in Background Tab")) newWindow = menu.addAction(self.tr("Open Link in New Window")) + # TODO: add link to copy the URL menu.move(evt.globalPos()) act = menu.exec()
--- a/eric7/WebBrowser/QtHelp/HelpTocWidget.py Sat Oct 16 20:37:32 2021 +0200 +++ b/eric7/WebBrowser/QtHelp/HelpTocWidget.py Sat Oct 16 20:38:23 2021 +0200 @@ -183,6 +183,7 @@ newBackgroundTab = menu.addAction( self.tr("Open Link in Background Tab")) newWindow = menu.addAction(self.tr("Open Link in New Window")) + # TODO: add link to copy the URL menu.move(self.__tocWidget.mapToGlobal(pos)) act = menu.exec()
--- a/eric7/eric7.py Sat Oct 16 20:37:32 2021 +0200 +++ b/eric7/eric7.py Sat Oct 16 20:38:23 2021 +0200 @@ -42,9 +42,12 @@ " it is installed and accessible.") sys.exit(100) -with contextlib.suppress(ImportError): - from PyQt6 import QtWebEngineWidgets - # __IGNORE_WARNING__ __IGNORE_EXCEPTION__ +try: + from PyQt6 import QtWebEngineWidgets # __IGNORE_WARNING__ + from PyQt6.QtWebEngineCore import QWebEngineUrlScheme + WEBENGINE_AVAILABLE = True +except ImportError: + WEBENGINE_AVAILABLE = False # some global variables needed to start the application args = None @@ -292,6 +295,12 @@ # set the library paths for plugins Startup.setLibraryPaths() + if WEBENGINE_AVAILABLE: + scheme = QWebEngineUrlScheme(b"qthelp") + scheme.setSyntax(QWebEngineUrlScheme.Syntax.Path) + scheme.setFlags(QWebEngineUrlScheme.Flag.SecureScheme) + QWebEngineUrlScheme.registerScheme(scheme) + app = EricApplication(sys.argv) ddindex = Startup.handleArgs(sys.argv, appinfo)