Continued implementing the embedded help viewer widget. Added the QtHelp Table of Contents widget. eric7

Wed, 13 Oct 2021 18:15:30 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Wed, 13 Oct 2021 18:15:30 +0200
branch
eric7
changeset 8683
e8a907801549
parent 8682
04e80d1aaebf
child 8685
b0669ce1066d

Continued implementing the embedded help viewer widget. Added the QtHelp Table of Contents widget.

eric7/HelpViewer/HelpViewerImpl.py file | annotate | diff | comparison | revisions
eric7/HelpViewer/HelpViewerImpl_qtb.py file | annotate | diff | comparison | revisions
eric7/HelpViewer/HelpViewerWidget.py file | annotate | diff | comparison | revisions
eric7/HelpViewer/OpenPagesWidget.py file | annotate | diff | comparison | revisions
eric7/WebBrowser/QtHelp/HelpTocWidget.py file | annotate | diff | comparison | revisions
--- a/eric7/HelpViewer/HelpViewerImpl.py	Tue Oct 12 21:55:56 2021 +0200
+++ b/eric7/HelpViewer/HelpViewerImpl.py	Wed Oct 13 18:15:30 2021 +0200
@@ -50,12 +50,23 @@
         """
         Public method to set the URL of the document to be shown.
         
-        @param url source of the document
+        @param url URL of the document
         @type QUrl
         @exception RuntimeError raised when not implemented
         """
         raise RuntimeError("Not implemented")
     
+    def url(self):
+        """
+        Public method to get the URL of the shown document.
+        
+        @return url URL of the document
+        @rtype QUrl
+        @exception RuntimeError raised when not implemented
+        """
+        raise RuntimeError("Not implemented")
+        return None
+    
     def getData(self, url):
         """
         Public method to get the data to be shown.
--- a/eric7/HelpViewer/HelpViewerImpl_qtb.py	Tue Oct 12 21:55:56 2021 +0200
+++ b/eric7/HelpViewer/HelpViewerImpl_qtb.py	Wed Oct 13 18:15:30 2021 +0200
@@ -43,6 +43,15 @@
         """
         self.setSource(url)
     
+    def url(self):
+        """
+        Public method to get the URL of the shown document.
+        
+        @return url URL of the document
+        @rtype QUrl
+        """
+        return self.source()
+    
     def doSetSource(self, url, type):
         """
         Public method to load the data and show it.
--- a/eric7/HelpViewer/HelpViewerWidget.py	Tue Oct 12 21:55:56 2021 +0200
+++ b/eric7/HelpViewer/HelpViewerWidget.py	Wed Oct 13 18:15:30 2021 +0200
@@ -9,7 +9,7 @@
 
 import os
 
-from PyQt6.QtCore import pyqtSlot, QUrl
+from PyQt6.QtCore import pyqtSlot, Qt, QUrl
 from PyQt6.QtGui import QAction
 from PyQt6.QtHelp import QHelpEngine
 from PyQt6.QtWidgets import (
@@ -25,6 +25,8 @@
 
 from .OpenPagesWidget import OpenPagesWidget
 
+from WebBrowser.QtHelp.HelpTocWidget import HelpTocWidget
+
 
 class HelpViewerWidget(QWidget):
     """
@@ -42,6 +44,8 @@
         
         self.__ui = parent
         
+        self.__initHelpEngine()
+        
         self.__layout = QVBoxLayout()
         self.__layout.setObjectName("MainLayout")
         self.__layout.setContentsMargins(0, 3, 0, 0)
@@ -109,7 +113,6 @@
         self.__buttonLine1.setFrameShadow(QFrame.Shadow.Sunken)
         self.__navButtonsLayout.addWidget(self.__buttonLine1)
         
-        # TODO: add zoom in button
         self.__zoomInButton = QToolButton(self)
         self.__zoomInButton.setIcon(UI.PixmapCache.getIcon("zoomIn"))
         self.__zoomInButton.setToolTip(
@@ -117,7 +120,6 @@
         self.__zoomInButton.clicked.connect(self.__zoomIn)
         self.__navButtonsLayout.addWidget(self.__zoomInButton)
         
-        # TODO: add zoom out button
         self.__zoomOutButton = QToolButton(self)
         self.__zoomOutButton.setIcon(UI.PixmapCache.getIcon("zoomOut"))
         self.__zoomOutButton.setToolTip(
@@ -125,7 +127,6 @@
         self.__zoomOutButton.clicked.connect(self.__zoomOut)
         self.__navButtonsLayout.addWidget(self.__zoomOutButton)
         
-        # TODO: add zoom reset button
         self.__zoomResetButton = QToolButton(self)
         self.__zoomResetButton.setIcon(UI.PixmapCache.getIcon("zoomReset"))
         self.__zoomResetButton.setToolTip(
@@ -177,6 +178,9 @@
         
         self.__openPagesButton = self.__addNavigationButton(
             "fileMisc", self.tr("Show list of open pages"))
+        self.__helpTocButton = self.__addNavigationButton(
+            "tableOfContents", self.tr("Show table of contents"))
+        self.__openPagesButton.setChecked(True)
         
         # TODO: add buttons for the QHelp related widgets
         
@@ -223,10 +227,20 @@
         """
         Private method to populate the stack of navigation widgets.
         """
+        # Open Pages
         self.__openPagesList = OpenPagesWidget(self.__helpStack, self)
         self.__openPagesList.currentChanged.connect(self.__checkActionButtons)
         self.__helpNavigationStack.addWidget(self.__openPagesList)
         
+        # QtHelp TOC
+        self.__tocWidget = HelpTocWidget(self.__helpEngine, internal=True)
+        self.__tocWidget.escapePressed.connect(self.__activateCurrentPage)
+        self.__tocWidget.openUrl.connect(self.openUrl)
+        self.__tocWidget.newTab.connect(self.openUrlNewPage)
+        self.__tocWidget.newBackgroundTab.connect(
+            self.openUrlNewBackgroundPage)
+        self.__helpNavigationStack.addWidget(self.__tocWidget)
+        
         # TODO: not yet implemented
     
     @pyqtSlot(QAbstractButton)
@@ -239,6 +253,8 @@
         """
         if button == self.__openPagesButton:
             self.__helpNavigationStack.setCurrentWidget(self.__openPagesList)
+        elif button == self.__helpTocButton:
+            self.__helpNavigationStack.setCurrentWidget(self.__tocWidget)
         
         # TODO: not yet implemented
     
@@ -290,7 +306,9 @@
         @param searchWord word to search for (defaults to None)
         @type str (optional)
         """
-        # TODO: not yet implemented
+        cv = self.currentViewer()
+        if cv:
+            cv.setFocus(Qt.FocusReason.OtherFocusReason)
         
         if searchWord:
             self.searchQtHelp(searchWord)
@@ -309,18 +327,71 @@
         if htmlFile:
             self.currentViewer().setUrl(QUrl.fromLocalFile(htmlFile))
     
-    def addPage(self, url=QUrl("about:blank")):
+    def addPage(self, url=QUrl("about:blank"), background=False):
         """
         Public method to add a new help page with the given URL.
         
         @param url requested URL (defaults to QUrl("about:blank"))
         @type QUrl (optional)
+        @param background flag indicating to open the page in the background
+            (defaults to False)
+        @type bool (optional)
         """
         viewer = self.__newViewer()
         viewer.setUrl(url)
         
+        if background:
+            cv = self.currentViewer()
+            if cv:
+                index = self.__helpStack.indexOf(cv) + 1
+                self.__helpStack.insertWidget(index, viewer)
+                self.__openPagesList.insertPage(
+                    index, viewer, background=background)
+                return
+        
         self.__helpStack.addWidget(viewer)
-        self.__openPagesList.addPage(viewer)
+        self.__openPagesList.addPage(viewer, background=background)
+
+    @pyqtSlot(QUrl)
+    def openUrl(self, url):
+        """
+        Public slot to load a URL in the current page.
+        
+        @param url URL to be opened
+        @type QUrl
+        """
+        cv = self.currentViewer()
+        if cv:
+            cv.setUrl(url)
+    
+    @pyqtSlot(QUrl)
+    def openUrlNewPage(self, url):
+        """
+        Public slot to load a URL in a new page.
+        
+        @param url URL to be opened
+        @type QUrl
+        """
+        self.addPage(url=url)
+    
+    @pyqtSlot(QUrl)
+    def openUrlNewBackgroundPage(self, url):
+        """
+        Public slot to load a URL in a new background page.
+        
+        @param url URL to be opened
+        @type QUrl
+        """
+        self.addPage(url=url, background=True)
+    
+    @pyqtSlot()
+    def __activateCurrentPage(self):
+        """
+        Private slot to activate the current page.
+        """
+        cv = self.currentViewer()
+        if cv:
+            cv.setFocus()
     
     def __newViewer(self):
         """
@@ -474,10 +545,16 @@
         Private slot to set the enabled state of the action buttons.
         """
         cv = self.currentViewer()
-        self.__backwardButton.setEnabled(cv and cv.isBackwardAvailable())
-        self.__forwardButton.setEnabled(cv and cv.isForwardAvailable())
-        self.__zoomInButton.setEnabled(cv and cv.isScaleUpAvailable())
-        self.__zoomOutButton.setEnabled(cv and cv.isScaleDownAvailable())
+        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):
         """
@@ -529,7 +606,8 @@
         cv = self.currentViewer()
         if cv:
             index = act.data()
-            cv.gotoHistory(index)
+            if index is not None:
+                cv.gotoHistory(index)
     
     def __clearHistory(self):
         """
--- a/eric7/HelpViewer/OpenPagesWidget.py	Tue Oct 12 21:55:56 2021 +0200
+++ b/eric7/HelpViewer/OpenPagesWidget.py	Wed Oct 13 18:15:30 2021 +0200
@@ -7,12 +7,14 @@
 Module implementing a widget showing the list of open pages.
 """
 
-from PyQt6.QtCore import pyqtSlot, pyqtSignal
+from PyQt6.QtCore import pyqtSlot, pyqtSignal, Qt, QPoint
+from PyQt6.QtGui import QGuiApplication, QClipboard
 from PyQt6.QtWidgets import (
-    QWidget, QLabel, QListWidget, QListWidgetItem, QVBoxLayout,
-    QAbstractItemView
+    QWidget, QLabel, QListWidget, QVBoxLayout, QAbstractItemView, QMenu
 )
 
+import UI.PixmapCache
+
 
 class OpenPagesWidget(QWidget):
     """
@@ -33,6 +35,8 @@
         super().__init__(parent)
         self.setObjectName("OpenPagesWidget")
         
+        self.__helpViewer = parent
+        
         self.__layout = QVBoxLayout()
         self.__layout.setContentsMargins(0, 0, 0, 0)
         
@@ -42,9 +46,13 @@
         self.__openPagesList = QListWidget(self)
         self.__openPagesList.setAlternatingRowColors(True)
         self.__openPagesList.setSelectionMode(
-            QAbstractItemView.SelectionMode.ExtendedSelection)
+            QAbstractItemView.SelectionMode.SingleSelection)
+        self.__openPagesList.setContextMenuPolicy(
+            Qt.ContextMenuPolicy.CustomContextMenu)
         self.__openPagesList.currentRowChanged.connect(
             self.__currentRowChanged)
+        self.__openPagesList.customContextMenuRequested.connect(
+            self.__showContextMenu)
         self.__layout.addWidget(self.__openPagesList)
         
         self.setLayout(self.__layout)
@@ -52,10 +60,44 @@
         self.__stack = stack
         self.__stack.currentChanged.connect(self.__currentPageChanged)
         
+        self.__initContextMenu()
+        
         self.__defaultFont = self.__openPagesList.font()
         self.__boldFont = self.__openPagesList.font()
         self.__boldFont.setBold(True)
     
+    def __initContextMenu(self):
+        """
+        Private method to initialize the context menu.
+        """
+        self.__menu = QMenu(self)
+        self.__menu.addAction(
+            UI.PixmapCache.getIcon("tabClose"),
+            self.tr('Close'), self.__contextMenuClose)
+        self.closeOthersMenuAct = self.__menu.addAction(
+            UI.PixmapCache.getIcon("tabCloseOther"),
+            self.tr("Close Others"),
+            self.__contextMenuCloseOthers)
+        self.__menu.addAction(
+            self.tr('Close All'), self.__contextMenuCloseAll)
+        self.__menu.addSeparator()
+        self.__copyUrlAct = self.__menu.addAction(
+            self.tr("Copy URL to Clipboard"),
+            self.__contextMenuCopyUrlToClipboard)
+    
+    @pyqtSlot(QPoint)
+    def __showContextMenu(self, point):
+        """
+        Private slot to handle the customContextMenuRequested signal of
+        the viewlist.
+        
+        @param point position to open the menu at
+        @type QPoint
+        """
+        itm = self.__openPagesList.itemAt(point)
+        self.__copyUrlAct.setEnabled(itm and itm.text() != "about:blank")
+        self.__menu.popup(self.__openPagesList.mapToGlobal(point))
+    
     @pyqtSlot(int)
     def __currentPageChanged(self, index):
         """
@@ -65,7 +107,7 @@
         @type int
         """
         for row in range(self.__openPagesList.count()):
-            itm = self.__openPagesList.item(index)
+            itm = self.__openPagesList.item(row)
             itm.setFont(
                 self.__boldFont if row == index else self.__defaultFont
             )
@@ -81,18 +123,47 @@
         self.__stack.setCurrentIndex(row)
         self.currentChanged.emit(row)
     
-    def addPage(self, viewer):
+    def addPage(self, viewer, background=False):
         """
         Public method to add a viewer page to our list.
         
         @param viewer reference to the viewer object
         @type HelpViewerImpl
+        @param background flag indicating to not change the current page
+            (defaults to False)
+        @type bool (optional)
         """
-        QListWidgetItem(viewer.title(), self.__openPagesList)
+        self.__openPagesList.addItem(viewer.title())
         viewer.titleChanged.connect(
             lambda: self.__viewerTitleChanged(viewer))
         
-        self.__currentPageChanged(self.__openPagesList.count() - 1)
+        if not background:
+            self.__openPagesList.setCurrentRow(
+                self.__openPagesList.count() - 1)
+        if self.__openPagesList.count() == 1:
+            self.__currentPageChanged(0)
+    
+    def insertPage(self, index, viewer, background=False):
+        """
+        Public method to insert a viewer page into our list.
+        
+        @param index index to insert at
+        @type int
+        @param viewer reference to the viewer object
+        @type HelpViewerImpl
+        @param background flag indicating to not change the current page
+            (defaults to False)
+        @type bool (optional)
+        """
+        currentRow = self.__openPagesList.currentRow()
+        self.__openPagesList.insertItem(index, viewer.title())
+        viewer.titleChanged.connect(
+            lambda: self.__viewerTitleChanged(viewer))
+        
+        if not background:
+            self.__openPagesList.setCurrentRow(index)
+        else:
+            self.__openPagesList.setCurrentRow(currentRow)
     
     def __viewerTitleChanged(self, viewer):
         """
@@ -105,3 +176,68 @@
         itm = self.__openPagesList.item(index)
         itm.setText(viewer.title())
         self.currentChanged.emit(index)
+    
+    #######################################################################
+    ## Context menu action methods
+    #######################################################################
+    
+    @pyqtSlot()
+    def __contextMenuClose(self):
+        """
+        Private slot to close a page.
+        """
+        row = self.__openPagesList.currentRow()
+        self.__removeViewer(row)
+        
+        if self.__openPagesList.count() == 0:
+            self.__helpViewer.addPage()
+    
+    @pyqtSlot()
+    def __contextMenuCloseOthers(self):
+        """
+        Private slot to close all other pages.
+        """
+        currentRow = self.__openPagesList.currentRow()
+        for row in range(self.__openPagesList.count() - 1, -1, -1):
+            if row != currentRow:
+                self.__removeViewer(row)
+    
+    @pyqtSlot()
+    def __contextMenuCloseAll(self):
+        """
+        Private slot to close all pages.
+        """
+        while self.__openPagesList.count() != 0:
+            self.__removeViewer(0)
+        self.__helpViewer.addPage()
+    
+    @pyqtSlot()
+    def __contextMenuCopyUrlToClipboard(self):
+        """
+        Private slot to copy the URL to the clipboard.
+        """
+        row = self.__openPagesList.currentRow()
+        viewer = self.__stack.widget(row)
+        url = viewer.url()
+        if url.isValid():
+            urlStr = url.toString()
+            
+            # copy the URL to both clipboard areas
+            QGuiApplication.clipboard().setText(
+                urlStr, QClipboard.Mode.Clipboard)
+            QGuiApplication.clipboard().setText(
+                urlStr, QClipboard.Mode.Selection)
+    
+    def __removeViewer(self, row):
+        """
+        Private method to remove a viewer page.
+        
+        @param row row associated with the viewer
+        @type int
+        """
+        viewer = self.__stack.widget(row)
+        self.__stack.removeWidget(viewer)
+        viewer.deleteLater()
+        
+        itm = self.__openPagesList.takeItem(row)
+        del itm
--- a/eric7/WebBrowser/QtHelp/HelpTocWidget.py	Tue Oct 12 21:55:56 2021 +0200
+++ b/eric7/WebBrowser/QtHelp/HelpTocWidget.py	Wed Oct 13 18:15:30 2021 +0200
@@ -28,18 +28,24 @@
     newBackgroundTab = pyqtSignal(QUrl)
     newWindow = pyqtSignal(QUrl)
     
-    def __init__(self, engine, parent=None):
+    def __init__(self, engine, internal=False, parent=None):
         """
         Constructor
         
-        @param engine reference to the help engine (QHelpEngine)
-        @param parent reference to the parent widget (QWidget)
+        @param engine reference to the help engine
+        @type QHelpEngine
+        @param internal flag indicating the internal help viewer
+        @type bool
+        @param parent reference to the parent widget
+        @type QWidget
         """
         super().__init__(parent)
         
         self.__engine = engine
         self.__expandDepth = -2
         
+        self.__internal = internal
+        
         self.__tocWidget = self.__engine.contentWidget()
         self.__tocWidget.setContextMenuPolicy(
             Qt.ContextMenuPolicy.CustomContextMenu)
@@ -47,6 +53,7 @@
         
         self.__layout = QVBoxLayout(self)
         self.__layout.addWidget(self.__tocWidget)
+        self.__layout.setContentsMargins(0, 0, 0, 0)
         
         self.__tocWidget.customContextMenuRequested.connect(
             self.__showContextMenu)
@@ -82,7 +89,10 @@
                     self.newBackgroundTab.emit(url)
                 elif modifiers & Qt.KeyboardModifier.ControlModifier:
                     self.newTab.emit(url)
-                elif modifiers & Qt.KeyboardModifier.ShiftModifier:
+                elif (
+                    modifiers & Qt.KeyboardModifier.ShiftModifier and
+                    not self.__internal
+                ):
                     self.newWindow.emit(url)
                 else:
                     self.openUrl.emit(url)
@@ -162,10 +172,15 @@
         
         menu = QMenu()
         curTab = menu.addAction(self.tr("Open Link"))
-        newTab = menu.addAction(self.tr("Open Link in New Tab"))
-        newBackgroundTab = menu.addAction(
-            self.tr("Open Link in Background Tab"))
-        newWindow = menu.addAction(self.tr("Open Link in New Window"))
+        if self.__internal:
+            newTab = menu.addAction(self.tr("Open Link in New Page"))
+            newBackgroundTab = menu.addAction(
+                self.tr("Open Link in Background Page"))
+        else:
+            newTab = menu.addAction(self.tr("Open Link in New Tab"))
+            newBackgroundTab = menu.addAction(
+                self.tr("Open Link in Background Tab"))
+            newWindow = menu.addAction(self.tr("Open Link in New Window"))
         menu.move(self.__tocWidget.mapToGlobal(pos))
         
         act = menu.exec()
@@ -175,5 +190,5 @@
             self.newTab.emit(link)
         elif act == newBackgroundTab:
             self.newBackgroundTab.emit(link)
-        elif act == newWindow:
+        elif not self.__internal and act == newWindow:
             self.newWindow.emit(link)

eric ide

mercurial