Wed, 13 Oct 2021 18:15:30 +0200
Continued implementing the embedded help viewer widget. Added the QtHelp Table of Contents widget.
--- 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)