Tue, 04 Jan 2022 17:13:35 +0100
Help Viewer
- added bookmarks to the internal help viewer
# -*- 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