eric7/HelpViewer/HelpViewerWidget.py

Tue, 12 Oct 2021 19:54:03 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Tue, 12 Oct 2021 19:54:03 +0200
branch
eric7
changeset 8681
6285e8374d99
parent 8680
85503ff2fce9
child 8683
e8a907801549
permissions
-rw-r--r--

Continued implementing the embedded help viewer widget.

# -*- coding: utf-8 -*-

# Copyright (c) 2021 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing an embedded viewer for QtHelp and local HTML files.
"""

import os

from PyQt6.QtCore import pyqtSlot, QUrl
from PyQt6.QtGui import QAction
from PyQt6.QtHelp import QHelpEngine
from PyQt6.QtWidgets import (
    QWidget, QHBoxLayout, QVBoxLayout, QComboBox, QSizePolicy, QStackedWidget,
    QToolButton, QButtonGroup, QAbstractButton, QMenu, QFrame
)

from EricWidgets import EricFileDialog, EricMessageBox

import UI.PixmapCache
import Utilities
import Preferences

from .OpenPagesWidget import OpenPagesWidget


class HelpViewerWidget(QWidget):
    """
    Class implementing an embedded viewer for QtHelp and local HTML files.
    """
    def __init__(self, parent=None):
        """
        Constructor
        
        @param parent reference to the parent widget (defaults to None)
        @type QWidget (optional)
        """
        super().__init__(parent)
        self.setObjectName("HelpViewerWidget")
        
        self.__ui = parent
        
        self.__layout = QVBoxLayout()
        self.__layout.setObjectName("MainLayout")
        self.__layout.setContentsMargins(0, 3, 0, 0)
        
        ###################################################################
        
        self.__selectorLayout = QHBoxLayout()
        
        self.__helpSelector = QComboBox(self)
        self.__helpSelector.setSizePolicy(
            QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred)
        self.__selectorLayout.addWidget(self.__helpSelector)
        self.__populateHelpSelector()
        self.__helpSelector.currentIndexChanged.connect(
            self.__helpTopicSelected)
        
        self.__openButton = QToolButton(self)
        self.__openButton.setIcon(UI.PixmapCache.getIcon("open"))
        self.__openButton.setToolTip(self.tr("Open a local file"))
        self.__openButton.clicked.connect(self.__openFile)
        self.__selectorLayout.addWidget(self.__openButton)
        
        self.__actionsButton = QToolButton(self)
        self.__actionsButton.setIcon(
            UI.PixmapCache.getIcon("actionsToolButton"))
        self.__actionsButton.setToolTip(
            self.tr("Select action from menu"))
        self.__actionsButton.setPopupMode(
            QToolButton.ToolButtonPopupMode.InstantPopup)
        self.__selectorLayout.addWidget(self.__actionsButton)
        
        self.__layout.addLayout(self.__selectorLayout)
        
        ###################################################################
        
        self.__navButtonsLayout = QHBoxLayout()
        
        self.__navButtonsLayout.addStretch()
        
        self.__backwardButton = QToolButton(self)
        self.__backwardButton.setIcon(UI.PixmapCache.getIcon("back"))
        self.__backwardButton.setToolTip(self.tr("Move one page backward"))
        self.__backwardButton.clicked.connect(self.__backward)
        
        self.__forwardButton = QToolButton(self)
        self.__forwardButton.setIcon(UI.PixmapCache.getIcon("forward"))
        self.__forwardButton.setToolTip(self.tr("Move one page forward"))
        self.__forwardButton.clicked.connect(self.__forward)
        
        self.__backForButtonLayout = QHBoxLayout()
        self.__backForButtonLayout.setContentsMargins(0, 0, 0, 0)
        self.__backForButtonLayout.setSpacing(0)
        self.__backForButtonLayout.addWidget(self.__backwardButton)
        self.__backForButtonLayout.addWidget(self.__forwardButton)
        self.__navButtonsLayout.addLayout(self.__backForButtonLayout)
        
        self.__reloadButton = QToolButton(self)
        self.__reloadButton.setIcon(UI.PixmapCache.getIcon("reload"))
        self.__reloadButton.setToolTip(self.tr("Reload the current page"))
        self.__reloadButton.clicked.connect(self.__reload)
        self.__navButtonsLayout.addWidget(self.__reloadButton)
        
        self.__buttonLine1 = QFrame(self)
        self.__buttonLine1.setFrameShape(QFrame.Shape.VLine)
        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(
            self.tr("Zoom in on the current page"))
        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(
            self.tr("Zoom out on the current page"))
        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(
            self.tr("Reset the zoom level of the current page"))
        self.__zoomResetButton.clicked.connect(self.__zoomReset)
        self.__navButtonsLayout.addWidget(self.__zoomResetButton)
        
        self.__navButtonsLayout.addStretch()
        
        self.__layout.addLayout(self.__navButtonsLayout)
                
        self.__backMenu = QMenu(self)
        self.__backMenu.triggered.connect(self.__navigationMenuActionTriggered)
        self.__backwardButton.setMenu(self.__backMenu)
        self.__backMenu.aboutToShow.connect(self.__showBackMenu)
        
        self.__forwardMenu = QMenu(self)
        self.__forwardMenu.triggered.connect(
            self.__navigationMenuActionTriggered)
        self.__forwardButton.setMenu(self.__forwardMenu)
        self.__forwardMenu.aboutToShow.connect(self.__showForwardMenu)

        ###################################################################
        
        self.__helpStack = QStackedWidget(self)
        self.__helpStack.setSizePolicy(
            QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
        self.__layout.addWidget(self.__helpStack)
        
        ###################################################################
        
        self.__helpNavigationStack = QStackedWidget(self)
        self.__helpNavigationStack.setSizePolicy(
            QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred)
        self.__helpNavigationStack.setMaximumHeight(200)
        self.__layout.addWidget(self.__helpNavigationStack)
        self.__populateNavigationStack()
        
        ###################################################################
        
        self.__buttonLayout = QHBoxLayout()
        
        self.__buttonGroup = QButtonGroup(self)
        self.__buttonGroup.setExclusive(True)
        self.__buttonGroup.buttonClicked.connect(
            self.__selectNavigationWidget)
        
        self.__buttonLayout.addStretch()
        
        self.__openPagesButton = self.__addNavigationButton(
            "fileMisc", self.tr("Show list of open pages"))
        
        # TODO: add buttons for the QHelp related widgets
        
        self.__buttonLayout.addStretch()
        
        self.__layout.addLayout(self.__buttonLayout)
        
        ###################################################################
        
        self.setLayout(self.__layout)
        
        self.__openPagesButton.setChecked(True)
        
        self.__initHelpEngine()
        
        self.__ui.preferencesChanged.connect(self.__populateHelpSelector)
        
        self.__initActionsMenu()
        
        self.addPage()
        self.__checkActionButtons()
    
    def __addNavigationButton(self, iconName, toolTip):
        """
        Private method to create and add a navigation button.
        
        @param iconName name of the icon
        @type str
        @param toolTip tooltip to be shown
        @type str
        @return reference to the created button
        @rtype QToolButton
        """
        button = QToolButton(self)
        button.setIcon(UI.PixmapCache.getIcon(iconName))
        button.setToolTip(toolTip)
        button.setCheckable(True)
        self.__buttonGroup.addButton(button)
        self.__buttonLayout.addWidget(button)
        
        return button
    
    def __populateNavigationStack(self):
        """
        Private method to populate the stack of navigation widgets.
        """
        self.__openPagesList = OpenPagesWidget(self.__helpStack, self)
        self.__openPagesList.currentChanged.connect(self.__checkActionButtons)
        self.__helpNavigationStack.addWidget(self.__openPagesList)
        
        # TODO: not yet implemented
    
    @pyqtSlot(QAbstractButton)
    def __selectNavigationWidget(self, button):
        """
        Private slot to select the navigation widget.
        
        @param button reference to the clicked button
        @type QAbstractButton
        """
        if button == self.__openPagesButton:
            self.__helpNavigationStack.setCurrentWidget(self.__openPagesList)
        
        # TODO: not yet implemented
    
    def __populateHelpSelector(self):
        """
        Private method to populate the help selection combo box.
        """
        self.__helpSelector.clear()
        
        self.__helpSelector.addItem("", "")
        
        for key, topic in [
            ("EricDocDir", self.tr("eric API Documentation")),
            ("PythonDocDir", self.tr("Python 3 Documentation")),
            ("Qt5DocDir", self.tr("Qt5 Documentation")),
            ("Qt6DocDir", self.tr("Qt6 Documentation")),
            ("PyQt5DocDir", self.tr("PyQt5 Documentation")),
            ("PyQt6DocDir", self.tr("PyQt6 Documentation")),
            ("PySide2DocDir", self.tr("PySide2 Documentation")),
            ("PySide6DocDir", self.tr("PySide6 Documentation")),
        ]:
            urlStr = Preferences.getHelp(key)
            if urlStr:
                self.__helpSelector.addItem(topic, urlStr)
    
    @pyqtSlot()
    def __helpTopicSelected(self):
        """
        Private slot handling the selection of a new help topic.
        """
        urlStr = self.__helpSelector.currentData()
        if urlStr:
            url = QUrl(urlStr)
            self.currentViewer().setUrl(url)
    
    def searchQtHelp(self, searchExpression):
        """
        Public method to search for a given search expression.
        
        @param searchExpression expression to search for
        @type str
        """
        # TODO: not yet implemented
    
    def activate(self, searchWord=None):
        """
        Public method to activate the widget and search for a given word.
        
        @param searchWord word to search for (defaults to None)
        @type str (optional)
        """
        # TODO: not yet implemented
        
        if searchWord:
            self.searchQtHelp(searchWord)
    
    @pyqtSlot()
    def __openFile(self):
        """
        Private slot to open a local help file (*.html).
        """
        htmlFile = EricFileDialog.getOpenFileName(
            self,
            self.tr("Open HTML File"),
            "",
            self.tr("HTML Files (*.htm *.html);;All Files (*)")
        )
        if htmlFile:
            self.currentViewer().setUrl(QUrl.fromLocalFile(htmlFile))
    
    def addPage(self, url=QUrl("about:blank")):
        """
        Public method to add a new help page with the given URL.
        
        @param url requested URL (defaults to QUrl("about:blank"))
        @type QUrl (optional)
        """
        viewer = self.__newViewer()
        viewer.setUrl(url)
        
        self.__helpStack.addWidget(viewer)
        self.__openPagesList.addPage(viewer)
    
    def __newViewer(self):
        """
        Private method to create a new help viewer.
        
        @return help viewer
        @rtype HelpViewerImpl
        """
        try:
            from .HelpViewerImpl_qwe import HelpViewerImpl_qwe
            viewer = HelpViewerImpl_qwe(self.__helpEngine, self)
        except ImportError:
            from .HelpViewerImpl_qtb import HelpViewerImpl_qtb
            viewer = HelpViewerImpl_qtb(self.__helpEngine, self)
        
        viewer.zoomChanged.connect(self.__checkActionButtons)
        
        return viewer
    
    def currentViewer(self):
        """
        Public method to get the active viewer.
        
        @return reference to the active help viewer
        @rtype HelpViewerImpl
        """
        return self.__helpStack.currentWidget()
    
    #######################################################################
    ## QtHelp related code below
    #######################################################################
    
    def __initHelpEngine(self):
        """
        Private method to initialize the QtHelp related stuff.
        """
        self.__helpEngine = QHelpEngine(
            self.__getQtHelpCollectionFileName(),
            self)
        self.__helpEngine.setReadOnly(False)
        self.__helpEngine.setupData()
        self.__helpEngine.setUsesFilterEngine(True)
        self.__removeOldDocumentation()
        self.__helpEngine.warning.connect(self.__warning)
    
    def __getQtHelpCollectionFileName(cls):
        """
        Private method to determine the name of the QtHelp collection file.
        
        @return path of the QtHelp collection file
        @rtype str
        """
        qthelpDir = os.path.join(Utilities.getConfigDir(), "qthelp")
        if not os.path.exists(qthelpDir):
            os.makedirs(qthelpDir)
        return os.path.join(qthelpDir, "eric7help.qhc")
    
    @pyqtSlot(str)
    def __warning(self, msg):
        """
        Private slot handling warnings of the help engine.
        
        @param msg message sent by the help  engine
        @type str
        """
        EricMessageBox.warning(
            self,
            self.tr("Help Engine"), msg)
    
    @pyqtSlot()
    def __removeOldDocumentation(self):
        """
        Private slot to remove non-existing documentation from the help engine.
        """
        for namespace in self.__helpEngine.registeredDocumentations():
            docFile = self.__helpEngine.documentationFileName(namespace)
            if not os.path.exists(docFile):
                self.__helpEngine.unregisterDocumentation(namespace)
    
    @pyqtSlot()
    def __manageQtHelpDocuments(self):
        """
        Private slot to manage the QtHelp documentation database.
        """
        from WebBrowser.QtHelp.QtHelpDocumentationConfigurationDialog import (
            QtHelpDocumentationConfigurationDialog
        )
        dlg = QtHelpDocumentationConfigurationDialog(
            self.__helpEngine, self)
        dlg.exec()
    
    @pyqtSlot()
    def __reindexDocumentation(self):
        """
        Private slot 
        """
    
    #######################################################################
    ## Actions Menu related methods
    #######################################################################
    
    def __initActionsMenu(self):
        """
        Private method to initialize the actions menu.
        """
        self.__actionsMenu = QMenu()
        self.__actionsMenu.setToolTipsVisible(True)
        
        self.__actionsMenu.addAction(self.tr("Manage QtHelp Documents"),
                                     self.__manageQtHelpDocuments)
        act = self.__actionsMenu.addAction(self.tr("Reindex Documentation"),
                                           self.__reindexDocumentation)
##        act.triggered.connect(self.__searchEngine.reindexDocumentation)
        
        self.__actionsButton.setMenu(self.__actionsMenu)
    
    #######################################################################
    ## Navigation related methods below
    #######################################################################
    
    @pyqtSlot()
    def __backward(self):
        """
        Private slot to move one page backward.
        """
        cv = self.currentViewer()
        if cv:
            cv.backward()
    
    @pyqtSlot()
    def __forward(self):
        """
        Private slot to move one page foreward.
        """
        cv = self.currentViewer()
        if cv:
            cv.forward()
    
    @pyqtSlot()
    def __reload(self):
        """
        Private slot to reload the current page.
        """
        cv = self.currentViewer()
        if cv:
            cv.reload()
    
    @pyqtSlot()
    def __checkActionButtons(self):
        """
        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())
    
    def __showBackMenu(self):
        """
        Private slot showing the backward navigation menu.
        """
        cv = self.currentViewer()
        if cv:
            self.__backMenu.clear()
            backwardHistoryCount = min(cv.backwardHistoryCount(), 20)
            # show max. 20 items
            
            for index in range(1, backwardHistoryCount + 1):
                act = QAction(self)
                act.setData(-index)
                act.setText(cv.historyTitle(-index))
                self.__backMenu.addAction(act)
            
            self.__backMenu.addSeparator()
            self.__backMenu.addAction(self.tr("Clear History"),
                                      self.__clearHistory)
    
    def __showForwardMenu(self):
        """
        Private slot showing the forward navigation menu.
        """
        cv = self.currentViewer()
        if cv:
            self.__forwardMenu.clear()
            forwardHistoryCount = min(cv.forwardHistoryCount(), 20)
            # show max. 20 items
        
            for index in range(1, forwardHistoryCount + 1):
                act = QAction(self)
                act.setData(index)
                act.setText(cv.historyTitle(index))
                self.__forwardMenu.addAction(act)
            
            self.__forwardMenu.addSeparator()
            self.__forwardMenu.addAction(self.tr("Clear History"),
                                         self.__clearHistory)
    
    def __navigationMenuActionTriggered(self, act):
        """
        Private slot to go to the selected page.
        
        @param act reference to the action selected in the navigation menu
        @type QAction
        """
        cv = self.currentViewer()
        if cv:
            index = act.data()
            cv.gotoHistory(index)
    
    def __clearHistory(self):
        """
        Private slot to clear the history of the current viewer.
        """
        cv = self.currentViewer()
        if cv:
            cv.clearHistory()
            self.__checkActionButtons()
    
    #######################################################################
    ## Zoom related methods below
    #######################################################################
    
    @pyqtSlot()
    def __zoomIn(self):
        """
        Private slot to zoom in.
        """
        cv = self.currentViewer()
        if cv:
            cv.scaleUp()
    
    @pyqtSlot()
    def __zoomOut(self):
        """
        Private slot to zoom out.
        """
        cv = self.currentViewer()
        if cv:
            cv.scaleDown()
    
    @pyqtSlot()
    def __zoomReset(self):
        """
        Private slot to reset the zoom level.
        """
        cv = self.currentViewer()
        if cv:
            cv.resetScale()

eric ide

mercurial