Tue, 04 Jan 2022 17:13:35 +0100
Help Viewer
- added bookmarks to the internal help viewer
--- a/docs/changelog Tue Jan 04 15:52:14 2022 +0100 +++ b/docs/changelog Tue Jan 04 17:13:35 2022 +0100 @@ -1,5 +1,13 @@ Change Log ---------- +Version 22.2: +- bug fixes +- Help Viewer + -- added bookmarks to the internal help viewer + +Version 22.1.1: +- bug fix + Version 22.1: - bug fixes - Code Style Checker
--- a/eric7.epj Tue Jan 04 15:52:14 2022 +0100 +++ b/eric7.epj Tue Jan 04 17:13:35 2022 +0100 @@ -1,7 +1,7 @@ { "header": { "comment": "eric project file for project eric7", - "copyright": "Copyright (C) 2021 Detlev Offenbach, detlev@die-offenbachs.de" + "copyright": "Copyright (C) 2022 Detlev Offenbach, detlev@die-offenbachs.de" }, "project": { "AUTHOR": "Detlev Offenbach", @@ -715,7 +715,8 @@ "eric7/UI/FindLocationWidget.ui", "eric7/JediInterface/RefactoringPreviewDialog.ui", "eric7/QScintilla/EditorOutlineSizesDialog.ui", - "eric7/Preferences/ConfigurationPages/InterfaceLightPage.ui" + "eric7/Preferences/ConfigurationPages/InterfaceLightPage.ui", + "eric7/HelpViewer/HelpBookmarkPropertiesDialog.ui" ], "HASH": "df7daa8781250f7664e6ecaeaf1361fa2efd39ee", "IDLPARAMS": { @@ -2305,7 +2306,9 @@ "eric7/Plugins/CheckerPlugins/CodeStyleChecker/Imports/ImportsEnums.py", "eric7/Plugins/CheckerPlugins/CodeStyleChecker/Imports/ImportNode.py", "eric7/Preferences/ThemeManager.py", - "eric7/Preferences/ConfigurationPages/InterfaceLightPage.py" + "eric7/Preferences/ConfigurationPages/InterfaceLightPage.py", + "eric7/HelpViewer/HelpBookmarksWidget.py", + "eric7/HelpViewer/HelpBookmarkPropertiesDialog.py" ], "SPELLEXCLUDES": "Dictionaries/excludes.dic", "SPELLLANGUAGE": "en_US",
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eric7/HelpViewer/HelpBookmarkPropertiesDialog.py Tue Jan 04 17:13:35 2022 +0100 @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2022 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a dialog to edit the bookmark properties. +""" + +from PyQt6.QtCore import pyqtSlot +from PyQt6.QtWidgets import QDialog, QDialogButtonBox + +from .Ui_HelpBookmarkPropertiesDialog import Ui_HelpBookmarkPropertiesDialog + + +class HelpBookmarkPropertiesDialog(QDialog, Ui_HelpBookmarkPropertiesDialog): + """ + Class implementing a dialog to edit the bookmark properties. + """ + def __init__(self, title="", url="", parent=None): + """ + Constructor + + @param title title for the bookmark (defaults to "") + @type str (optional) + @param url URL for the bookmark (defaults to "") + @type str (optional) + @param parent reference to the parent widget (defaults to None) + @type QWidget (optional) + """ + super().__init__(parent) + self.setupUi(self) + + self.titleEdit.textChanged.connect(self.__updateOkButton) + self.urlEdit.textChanged.connect(self.__updateOkButton) + + self.titleEdit.setText(title) + self.urlEdit.setText(url) + + msh = self.minimumSizeHint() + self.resize(max(self.width(), msh.width()), msh.height()) + + @pyqtSlot() + def __updateOkButton(self): + """ + Private method to set the enabled state of the OK button. + """ + self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled( + bool(self.titleEdit.text().strip()) and + bool(self.urlEdit.text().strip()) + ) + + def getData(self): + """ + Public method to retrieve the entered data + + @return tuple containing the title and URL for the bookmark + @rtype tuple of (str, str) + """ + return ( + self.titleEdit.text().strip(), + self.urlEdit.text().strip(), + )
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eric7/HelpViewer/HelpBookmarkPropertiesDialog.ui Tue Jan 04 17:13:35 2022 +0100 @@ -0,0 +1,107 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>HelpBookmarkPropertiesDialog</class> + <widget class="QDialog" name="HelpBookmarkPropertiesDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>500</width> + <height>108</height> + </rect> + </property> + <property name="windowTitle"> + <string>Bookmark</string> + </property> + <property name="sizeGripEnabled"> + <bool>true</bool> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Titel:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLineEdit" name="titleEdit"> + <property name="toolTip"> + <string>Enter the title for the bookmark</string> + </property> + <property name="placeholderText"> + <string>Enter Bookmark Title</string> + </property> + <property name="clearButtonEnabled"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>URL:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLineEdit" name="urlEdit"> + <property name="toolTip"> + <string>Enter the URL for the bookmark</string> + </property> + <property name="placeholderText"> + <string>Enter Bookmark URL</string> + </property> + <property name="clearButtonEnabled"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="2" column="0" colspan="2"> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>HelpBookmarkPropertiesDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>HelpBookmarkPropertiesDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eric7/HelpViewer/HelpBookmarksWidget.py Tue Jan 04 17:13:35 2022 +0100 @@ -0,0 +1,374 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2022 Detlev Offenbach <detlev@die-offenbachs.de> +# + +import contextlib +import json + +from PyQt6.QtCore import pyqtSignal, pyqtSlot, Qt, QPoint, QUrl +from PyQt6.QtGui import QClipboard, QGuiApplication +from PyQt6.QtWidgets import ( + QAbstractItemView, QApplication, QDialog, QListWidget, QListWidgetItem, QMenu +) + +import Preferences + +from .HelpBookmarkPropertiesDialog import HelpBookmarkPropertiesDialog + +class HelpBookmarksWidget(QListWidget): + """ + Class implementing a widget showing the list of bookmarks. + + @signal escapePressed() emitted when the ESC key was pressed + @signal openUrl(QUrl, str) emitted to open an entry in the current tab + @signal newTab(QUrl, str) emitted to open an entry in a new tab + @signal newBackgroundTab(QUrl, str) emitted to open an entry in a + new background tab + """ + escapePressed = pyqtSignal() + openUrl = pyqtSignal(QUrl) + newTab = pyqtSignal(QUrl) + newBackgroundTab = pyqtSignal(QUrl) + + UrlRole = Qt.ItemDataRole.UserRole + 1 + + def __init__(self, parent=None): + """ + Constructor + + @param parent reference to the parent widget (defaults to None) + @type QWidget (optional) + """ + super().__init__(parent) + self.setObjectName("HelpBookmarksWidget") + + self.__helpViewer = parent + + self.setAlternatingRowColors(True) + self.setSelectionMode( + QAbstractItemView.SelectionMode.ExtendedSelection) + self.setSortingEnabled(True) + + self.setContextMenuPolicy( + Qt.ContextMenuPolicy.CustomContextMenu) + self.customContextMenuRequested.connect( + self.__showContextMenu) + + self.__bookmarks = [] + self.__loadBookmarks() + + self.itemDoubleClicked.connect(self.__bookmarkActivated) + + @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 + """ + selectedItemsCount = len(self.selectedItems()) + if selectedItemsCount == 0: + # background menu + self.__showBackgroundMenu(point) + elif selectedItemsCount == 1: + # single bookmark menu + self.__showBookmarkContextMenu(point) + else: + # multiple selected bookmarks + self.__showBookmarksContextMenu(point) + + @pyqtSlot(QPoint) + def __showBackgroundMenu(self, point): + """ + Private slot to show the background menu (i.e. no selection). + + @param point position to open the menu at + @type QPoint + """ + menu = QMenu() + openBookmarks = menu.addAction(self.tr("Open All Bookmarks")) + menu.addSeparator() + newBookmark = menu.addAction(self.tr("New Bookmark")) + addBookmark = menu.addAction(self.tr("Bookmark Page")) + menu.addSeparator() + deleteBookmarks = menu.addAction(self.tr("Delete All Bookmark")) + + act = menu.exec(self.mapToGlobal(point)) + if act == openBookmarks: + self.__openBookmarks(selected=False) + elif act == newBookmark: + self.__newBookmark() + elif act == addBookmark: + self.__bookmarkCurrentPage() + elif act == deleteBookmarks: + self.__deleteBookmarks([ + self.item(row) for row in range(self.count()) + ]) + + @pyqtSlot(QPoint) + def __showBookmarkContextMenu(self, point): + """ + Private slot to show the context menu for a bookmark. + + @param point position to open the menu at + @type QPoint + """ + itm = self.selectedItems()[0] + url = itm.data(self.UrlRole) + validUrl = ( + url is not None and not url.isEmpty() and url.isValid() + ) + + menu = QMenu() + curPage = menu.addAction(self.tr("Open Link")) + curPage.setEnabled(validUrl) + newPage = menu.addAction(self.tr("Open Link in New Page")) + newPage.setEnabled(validUrl) + newBackgroundPage = menu.addAction( + self.tr("Open Link in Background Page")) + newBackgroundPage.setEnabled(validUrl) + menu.addSeparator() + copyUrl = menu.addAction(self.tr("Copy URL to Clipboard")) + copyUrl.setEnabled(validUrl) + menu.addSeparator() + newBookmark = menu.addAction(self.tr("New Bookmark")) + addBookmark = menu.addAction(self.tr("Bookmark Page")) + menu.addSeparator() + editBookmark = menu.addAction(self.tr("Edit Bookmark")) + menu.addSeparator() + deleteBookmark = menu.addAction(self.tr("Delete Bookmark")) + + act = menu.exec(self.mapToGlobal(point)) + if act == curPage: + self.openUrl.emit(url) + elif act == newPage: + self.newTab.emit(url) + elif act == newBackgroundPage: + self.newBackgroundTab.emit(url) + elif act == copyUrl: + # copy the URL to both clipboard areas + QGuiApplication.clipboard().setText( + url.toString(), QClipboard.Mode.Clipboard) + QGuiApplication.clipboard().setText( + url.toString(), QClipboard.Mode.Selection) + elif act == newBookmark: + self.__newBookmark() + elif act == addBookmark: + self.__bookmarkCurrentPage() + elif act == editBookmark: + self.__editBookmark(itm) + elif act == deleteBookmark: + self.__deleteBookmarks([itm]) + + @pyqtSlot(QPoint) + def __showBookmarksContextMenu(self, point): + """ + Private slot to show the context menu for multiple bookmark. + + @param point position to open the menu at + @type QPoint + """ + menu = QMenu() + openBookmarks = menu.addAction(self.tr("Open Selected Bookmarks")) + menu.addSeparator() + deleteBookmarks = menu.addAction(self.tr("Delete Selected Bookmarks")) + + act = menu.exec(self.mapToGlobal(point)) + if act == openBookmarks: + self.__openBookmarks(selected=True) + elif act == deleteBookmarks: + self.__deleteBookmarks(self.selectedItems()) + + @pyqtSlot(str, str) + def __addBookmark(self, title, url): + """ + Private slot to add a bookmark entry. + + @param title title for the bookmark + @type str + @param url URL for the bookmark + @type str + """ + url = url.strip() + + itm = QListWidgetItem(title, self) + itm.setData(self.UrlRole, QUrl(url)) + itm.setToolTip(url) + + @pyqtSlot(str, QUrl) + def addBookmark(self, title, url): + """ + Public slot to add a bookmark with given data. + + @param title title for the bookmark + @type str + @param url URL for the bookmark + @type QUrl + """ + dlg = HelpBookmarkPropertiesDialog(title, url.toString(), self) + if dlg.exec() == QDialog.DialogCode.Accepted: + title, url = dlg.getData() + self.__addBookmark(title, url) + self.sortItems() + self.__saveBookmarks() + + @pyqtSlot() + def __bookmarkCurrentPage(self): + """ + Private slot to bookmark the current page. + """ + currentViewer = self.__helpViewer.currentViewer() + title = currentViewer.pageTitle() + url = currentViewer.link() + self.addBookmark(title, url) + + @pyqtSlot() + def __newBookmark(self): + """ + Private slot to create a new bookmark. + """ + dlg = HelpBookmarkPropertiesDialog(parent=self) + if dlg.exec() == QDialog.DialogCode.Accepted: + title, url = dlg.getData() + self.__addBookmark(title, url) + self.sortItems() + self.__saveBookmarks() + + @pyqtSlot() + def __editBookmark(self, itm): + """ + Private slot to edit a bookmark. + + @param itm reference to the bookmark item to be edited + @type QListWidgetItem + """ + dlg = HelpBookmarkPropertiesDialog( + itm.text(), itm.data(self.UrlRole).toString(), self) + if dlg.exec() == QDialog.DialogCode.Accepted: + title, url = dlg.getData() + itm.setText(title) + itm.setData(self.UrlRole, QUrl(url)) + itm.setToolTip(url) + self.sortItems() + self.__saveBookmarks() + + @pyqtSlot(QListWidgetItem) + def __bookmarkActivated(self, itm): + """ + Private slot handling the activation of a bookmark. + + @param itm reference to the activated item + @type QListWidgetItem + """ + url = itm.data(self.UrlRole) + if url and not url.isEmpty() and url.isValid(): + buttons = QApplication.mouseButtons() + modifiers = QApplication.keyboardModifiers() + + if buttons & Qt.MouseButton.MiddleButton: + self.newTab.emit(url) + else: + if ( + modifiers & ( + Qt.KeyboardModifier.ControlModifier | + Qt.KeyboardModifier.ShiftModifier + ) == ( + Qt.KeyboardModifier.ControlModifier | + Qt.KeyboardModifier.ShiftModifier + ) + ): + self.newBackgroundTab.emit(url) + elif modifiers & Qt.KeyboardModifier.ControlModifier: + self.newTab.emit(url) + elif ( + modifiers & Qt.KeyboardModifier.ShiftModifier and + not self.__internal + ): + self.newWindow.emit(url) + else: + self.openUrl.emit(url) + + def __openBookmarks(self, selected=False): + """ + Private method to open all or selected bookmarks. + + @param selected flag indicating to open the selected bookmarks + (defaults to False) + @type bool (optional) + """ + items = ( + self.selectedItems() + if selected else + [self.item(row) for row in range(self.count())] + ) + + for itm in items: + url = itm.data(self.UrlRole) + if url is not None and not url.isEmpty() and url.isValid(): + self.newTab.emit(url) + + def __deleteBookmarks(self, items): + """ + Private method to delete the given bookmark items. + + @param items list of bookmarks to be deleted + @type list of QListWidgetItem + """ + from UI.DeleteFilesConfirmationDialog import ( + DeleteFilesConfirmationDialog + ) + dlg = DeleteFilesConfirmationDialog( + self, + self.tr("Delete Bookmarks"), + self.tr("Shall these bookmarks really be deleted?"), + [itm.text() for itm in items] + ) + if dlg.exec() == QDialog.DialogCode.Accepted: + for itm in items: + self.takeItem(self.row(itm)) + del itm + self.__saveBookmarks() + + def __loadBookmarks(self): + """ + Private method to load the defined bookmarks. + """ + bookmarksStr = Preferences.getHelp("Bookmarks") + with contextlib.suppress(ValueError): + bookmarks = json.loads(bookmarksStr) + + self.clear() + for bookmark in bookmarks: + self.__addBookmark(bookmark["title"], bookmark["url"]) + self.sortItems() + + def __saveBookmarks(self): + """ + Private method to save the defined bookmarks. + """ + bookmarks = [] + for row in range(self.count()): + itm = self.item(row) + bookmarks.append({ + "title": itm.text(), + "url": itm.data(self.UrlRole).toString(), + }) + Preferences.setHelp("Bookmarks", json.dumps(bookmarks)) + + def __exportBookmarks(self): + """ + Private method to export the bookmarks into a JSON file. + """ + # TODO: not yet implemented + + def __importBookmarks(self): + """ + Private method to import bookmarks from a JSON file. + """ + # TODO: not yet implemented + # 1. read file + # 2. check, if exported from eric and compatible format version + # 3. process each entry
--- a/eric7/HelpViewer/HelpViewerImplQTB.py Tue Jan 04 15:52:14 2022 +0100 +++ b/eric7/HelpViewer/HelpViewerImplQTB.py Tue Jan 04 17:13:35 2022 +0100 @@ -487,6 +487,16 @@ act.triggered.connect( functools.partial(self.__copyLink, act)) + act = self.__menu.addAction( + UI.PixmapCache.getIcon("bookmark22"), + self.tr("Bookmark Page")) + act.setData({ + "title": self.pageTitle(), + "url": self.link() + }) + act.triggered.connect( + functools.partial(self.__bookmarkPage, act)) + self.__menu.addSeparator() act = self.__menu.addAction( @@ -596,6 +606,21 @@ self.__helpViewerWidget.openUrlNewBackgroundPage(url) + def __bookmarkPage(self, act): + """ + Private method called by the context menu to bookmark the page. + + @param act reference to the action that triggered + @type QAction + """ + data = act.data() + if data: + with contextlib.suppress(KeyError): + url = data["url"] + title = data["title"] + + self.__helpViewerWidget.bookmarkPage(title, url) + def __copyLink(self, act): """ Private method called by the context menu to copy a link to the
--- a/eric7/HelpViewer/HelpViewerImplQWE.py Tue Jan 04 15:52:14 2022 +0100 +++ b/eric7/HelpViewer/HelpViewerImplQWE.py Tue Jan 04 17:13:35 2022 +0100 @@ -7,6 +7,7 @@ Module implementing the help viewer base class. """ +import contextlib import functools from PyQt6.QtCore import pyqtSlot, Qt, QEvent, QTimer, QUrl, QPoint @@ -612,6 +613,16 @@ act.triggered.connect( functools.partial(self.__copyLink, act)) + act = menu.addAction( + UI.PixmapCache.getIcon("bookmark22"), + self.tr("Bookmark Page")) + act.setData({ + "title": self.pageTitle(), + "url": self.link() + }) + act.triggered.connect( + functools.partial(self.__bookmarkPage, act)) + menu.addSeparator() act = menu.addAction( @@ -719,6 +730,21 @@ self.__helpViewerWidget.openUrlNewBackgroundPage(url) + def __bookmarkPage(self, act): + """ + Private method called by the context menu to bookmark the page. + + @param act reference to the action that triggered + @type QAction + """ + data = act.data() + if data: + with contextlib.suppress(KeyError): + url = data["url"] + title = data["title"] + + self.__helpViewerWidget.bookmarkPage(title, url) + def __copyLink(self, act): """ Private method called by the context menu to copy a link to the
--- a/eric7/HelpViewer/HelpViewerWidget.py Tue Jan 04 15:52:14 2022 +0100 +++ b/eric7/HelpViewer/HelpViewerWidget.py Tue Jan 04 17:13:35 2022 +0100 @@ -33,13 +33,13 @@ import Preferences from .OpenPagesWidget import OpenPagesWidget +from .HelpBookmarksWidget import HelpBookmarksWidget from WebBrowser.QtHelp.HelpTocWidget import HelpTocWidget from WebBrowser.QtHelp.HelpIndexWidget import HelpIndexWidget from WebBrowser.QtHelp.HelpSearchWidget import HelpSearchWidget -# TODO: add support for bookmarks class HelpViewerWidget(QWidget): """ Class implementing an embedded viewer for QtHelp and local HTML files. @@ -260,7 +260,9 @@ "helpIndex", self.tr("Show the help document index")) self.__helpSearchButton = self.__addNavigationButton( "documentFind", self.tr("Show the help search window")) - self.__openPagesButton.setChecked(True) + self.__bookmarksButton = self.__addNavigationButton( + "bookmark22", self.tr("Show list of bookmarks")) +# self.__openPagesButton.setChecked(True) self.__buttonLayout.addStretch() @@ -320,7 +322,8 @@ """ # Open Pages self.__openPagesList = OpenPagesWidget(self.__helpStack, self) - self.__openPagesList.currentPageChanged.connect(self.__currentPageChanged) + self.__openPagesList.currentPageChanged.connect( + self.__currentPageChanged) self.__helpNavigationStack.addWidget(self.__openPagesList) # QtHelp TOC widget @@ -362,6 +365,15 @@ self.__helpSearchWidget.newBackgroundTab.connect( self.openUrlNewBackgroundPage) self.__helpNavigationStack.addWidget(self.__helpSearchWidget) + + # Bookmarks widget + self.__bookmarksList = HelpBookmarksWidget(self) + self.__bookmarksList.escapePressed.connect(self.__activateCurrentPage) + self.__bookmarksList.openUrl.connect(self.openUrl) + self.__bookmarksList.newTab.connect(self.openUrlNewPage) + self.__bookmarksList.newBackgroundTab.connect( + self.openUrlNewBackgroundPage) + self.__helpNavigationStack.addWidget(self.__bookmarksList) @pyqtSlot(QAbstractButton) def __selectNavigationWidget(self, button): @@ -383,6 +395,9 @@ elif button == self.__helpSearchButton: self.__helpNavigationStack.setCurrentWidget( self.__helpSearchWidget) + elif button == self.__bookmarksButton: + self.__helpNavigationStack.setCurrentWidget( + self.__bookmarksList) def __populateHelpSelector(self): """ @@ -595,6 +610,17 @@ """ return self.__helpStack.currentWidget() + def bookmarkPage(self, title, url): + """ + Public method to bookmark a page with the given data. + + @param title title of the page + @type str + @param url URL of the page + @type QUrl + """ + self.__bookmarksList.addBookmark(title, url) + ####################################################################### ## QtHelp related code below #######################################################################
--- a/eric7/Preferences/__init__.py Tue Jan 04 15:52:14 2022 +0100 +++ b/eric7/Preferences/__init__.py Tue Jan 04 17:13:35 2022 +0100 @@ -913,6 +913,7 @@ "PySide6DocDir": "", "EricDocDir": "", "HelpViewerType": 0, # internal help viewer + "Bookmarks": "[]", # empty JSON list } # defaults for the web browser settings