eric7/HelpViewer/HelpBookmarksWidget.py

Tue, 04 Jan 2022 17:13:35 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Tue, 04 Jan 2022 17:13:35 +0100
branch
eric7
changeset 8900
9c153ce17d74
child 8902
ba9b8c6e4928
permissions
-rw-r--r--

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

eric ide

mercurial