eric6/WebBrowser/Bookmarks/BookmarksManager.py

changeset 6942
2602857055c5
parent 6645
ad476851d7e0
child 7229
53054eb5b15a
diff -r f99d60d6b59b -r 2602857055c5 eric6/WebBrowser/Bookmarks/BookmarksManager.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/WebBrowser/Bookmarks/BookmarksManager.py	Sun Apr 14 15:09:21 2019 +0200
@@ -0,0 +1,641 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2009 - 2019 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the bookmarks manager.
+"""
+
+from __future__ import unicode_literals
+
+import os
+
+from PyQt5.QtCore import pyqtSignal, QT_TRANSLATE_NOOP, QObject, QFile, \
+    QIODevice, QXmlStreamReader, QDateTime, QFileInfo, QUrl, \
+    QCoreApplication
+from PyQt5.QtWidgets import QUndoStack, QUndoCommand, QDialog
+
+from E5Gui import E5MessageBox, E5FileDialog
+
+from .BookmarkNode import BookmarkNode
+
+from Utilities.AutoSaver import AutoSaver
+import Utilities
+
+BOOKMARKBAR = QT_TRANSLATE_NOOP("BookmarksManager", "Bookmarks Bar")
+BOOKMARKMENU = QT_TRANSLATE_NOOP("BookmarksManager", "Bookmarks Menu")
+
+StartRoot = 0
+StartMenu = 1
+StartToolBar = 2
+
+
+class BookmarksManager(QObject):
+    """
+    Class implementing the bookmarks manager.
+    
+    @signal entryAdded(BookmarkNode) emitted after a bookmark node has been
+        added
+    @signal entryRemoved(BookmarkNode, int, BookmarkNode) emitted after a
+        bookmark node has been removed
+    @signal entryChanged(BookmarkNode) emitted after a bookmark node has been
+        changed
+    @signal bookmarksSaved() emitted after the bookmarks were saved
+    @signal bookmarksReloaded() emitted after the bookmarks were reloaded
+    """
+    entryAdded = pyqtSignal(BookmarkNode)
+    entryRemoved = pyqtSignal(BookmarkNode, int, BookmarkNode)
+    entryChanged = pyqtSignal(BookmarkNode)
+    bookmarksSaved = pyqtSignal()
+    bookmarksReloaded = pyqtSignal()
+    
+    def __init__(self, parent=None):
+        """
+        Constructor
+        
+        @param parent reference to the parent object (QObject)
+        """
+        super(BookmarksManager, self).__init__(parent)
+        
+        self.__saveTimer = AutoSaver(self, self.save)
+        self.entryAdded.connect(self.__saveTimer.changeOccurred)
+        self.entryRemoved.connect(self.__saveTimer.changeOccurred)
+        self.entryChanged.connect(self.__saveTimer.changeOccurred)
+        
+        self.__initialize()
+    
+    def __initialize(self):
+        """
+        Private method to initialize some data.
+        """
+        self.__loaded = False
+        self.__bookmarkRootNode = None
+        self.__toolbar = None
+        self.__menu = None
+        self.__bookmarksModel = None
+        self.__commands = QUndoStack()
+    
+    @classmethod
+    def getFileName(cls):
+        """
+        Class method to get the file name of the bookmark file.
+        
+        @return name of the bookmark file (string)
+        """
+        return os.path.join(Utilities.getConfigDir(), "web_browser",
+                            "bookmarks.xbel")
+    
+    def close(self):
+        """
+        Public method to close the bookmark manager.
+        """
+        self.__saveTimer.saveIfNeccessary()
+    
+    def undoRedoStack(self):
+        """
+        Public method to get a reference to the undo stack.
+        
+        @return reference to the undo stack (QUndoStack)
+        """
+        return self.__commands
+    
+    def changeExpanded(self):
+        """
+        Public method to handle a change of the expanded state.
+        """
+        self.__saveTimer.changeOccurred()
+    
+    def reload(self):
+        """
+        Public method used to initiate a reloading of the bookmarks.
+        """
+        self.__initialize()
+        self.load()
+        self.bookmarksReloaded.emit()
+    
+    def load(self):
+        """
+        Public method to load the bookmarks.
+        
+        @exception RuntimeError raised to indicate an error loading the
+            bookmarks
+        """
+        if self.__loaded:
+            return
+        
+        self.__loaded = True
+        
+        bookmarkFile = self.getFileName()
+        if not QFile.exists(bookmarkFile):
+            from . import DefaultBookmarks_rc       # __IGNORE_WARNING__
+            bookmarkFile = QFile(":/DefaultBookmarks.xbel")
+            bookmarkFile.open(QIODevice.ReadOnly)
+        
+        from .XbelReader import XbelReader
+        reader = XbelReader()
+        self.__bookmarkRootNode = reader.read(bookmarkFile)
+        if reader.error() != QXmlStreamReader.NoError:
+            E5MessageBox.warning(
+                None,
+                self.tr("Loading Bookmarks"),
+                self.tr(
+                    """Error when loading bookmarks on line {0},"""
+                    """ column {1}:\n {2}""")
+                .format(reader.lineNumber(),
+                        reader.columnNumber(),
+                        reader.errorString()))
+        
+        others = []
+        for index in range(
+                len(self.__bookmarkRootNode.children()) - 1, -1, -1):
+            node = self.__bookmarkRootNode.children()[index]
+            if node.type() == BookmarkNode.Folder:
+                if (node.title == self.tr("Toolbar Bookmarks") or
+                    node.title == BOOKMARKBAR) and \
+                   self.__toolbar is None:
+                    node.title = self.tr(BOOKMARKBAR)
+                    self.__toolbar = node
+                
+                if (node.title == self.tr("Menu") or
+                    node.title == BOOKMARKMENU) and \
+                   self.__menu is None:
+                    node.title = self.tr(BOOKMARKMENU)
+                    self.__menu = node
+            else:
+                others.append(node)
+            self.__bookmarkRootNode.remove(node)
+        
+        if len(self.__bookmarkRootNode.children()) > 0:
+            raise RuntimeError("Error loading bookmarks.")
+        
+        if self.__toolbar is None:
+            self.__toolbar = BookmarkNode(BookmarkNode.Folder,
+                                          self.__bookmarkRootNode)
+            self.__toolbar.title = self.tr(BOOKMARKBAR)
+        else:
+            self.__bookmarkRootNode.add(self.__toolbar)
+        
+        if self.__menu is None:
+            self.__menu = BookmarkNode(BookmarkNode.Folder,
+                                       self.__bookmarkRootNode)
+            self.__menu.title = self.tr(BOOKMARKMENU)
+        else:
+            self.__bookmarkRootNode.add(self.__menu)
+        
+        for node in others:
+            self.__menu.add(node)
+    
+    def save(self):
+        """
+        Public method to save the bookmarks.
+        """
+        if not self.__loaded:
+            return
+        
+        from .XbelWriter import XbelWriter
+        writer = XbelWriter()
+        bookmarkFile = self.getFileName()
+        
+        # save root folder titles in English (i.e. not localized)
+        self.__menu.title = BOOKMARKMENU
+        self.__toolbar.title = BOOKMARKBAR
+        if not writer.write(bookmarkFile, self.__bookmarkRootNode):
+            E5MessageBox.warning(
+                None,
+                self.tr("Saving Bookmarks"),
+                self.tr("""Error saving bookmarks to <b>{0}</b>.""")
+                .format(bookmarkFile))
+        
+        # restore localized titles
+        self.__menu.title = self.tr(BOOKMARKMENU)
+        self.__toolbar.title = self.tr(BOOKMARKBAR)
+        
+        self.bookmarksSaved.emit()
+    
+    def addBookmark(self, parent, node, row=-1):
+        """
+        Public method to add a bookmark.
+        
+        @param parent reference to the node to add to (BookmarkNode)
+        @param node reference to the node to add (BookmarkNode)
+        @param row row number (integer)
+        """
+        if not self.__loaded:
+            return
+        
+        self.setTimestamp(node, BookmarkNode.TsAdded,
+                          QDateTime.currentDateTime())
+        
+        command = InsertBookmarksCommand(self, parent, node, row)
+        self.__commands.push(command)
+    
+    def removeBookmark(self, node):
+        """
+        Public method to remove a bookmark.
+        
+        @param node reference to the node to be removed (BookmarkNode)
+        """
+        if not self.__loaded:
+            return
+        
+        parent = node.parent()
+        row = parent.children().index(node)
+        command = RemoveBookmarksCommand(self, parent, row)
+        self.__commands.push(command)
+    
+    def setTitle(self, node, newTitle):
+        """
+        Public method to set the title of a bookmark.
+        
+        @param node reference to the node to be changed (BookmarkNode)
+        @param newTitle title to be set (string)
+        """
+        if not self.__loaded:
+            return
+        
+        command = ChangeBookmarkCommand(self, node, newTitle, True)
+        self.__commands.push(command)
+    
+    def setUrl(self, node, newUrl):
+        """
+        Public method to set the URL of a bookmark.
+        
+        @param node reference to the node to be changed (BookmarkNode)
+        @param newUrl URL to be set (string)
+        """
+        if not self.__loaded:
+            return
+        
+        command = ChangeBookmarkCommand(self, node, newUrl, False)
+        self.__commands.push(command)
+    
+    def setNodeChanged(self, node):
+        """
+        Public method to signal changes of bookmarks other than title, URL
+        or timestamp.
+        
+        @param node reference to the bookmark (BookmarkNode)
+        """
+        self.__saveTimer.changeOccurred()
+    
+    def setTimestamp(self, node, timestampType, timestamp):
+        """
+        Public method to set the URL of a bookmark.
+        
+        @param node reference to the node to be changed (BookmarkNode)
+        @param timestampType type of the timestamp to set
+            (BookmarkNode.TsAdded, BookmarkNode.TsModified,
+            BookmarkNode.TsVisited)
+        @param timestamp timestamp to set (QDateTime)
+        """
+        if not self.__loaded:
+            return
+        
+        assert timestampType in [BookmarkNode.TsAdded,
+                                 BookmarkNode.TsModified,
+                                 BookmarkNode.TsVisited]
+        
+        if timestampType == BookmarkNode.TsAdded:
+            node.added = timestamp
+        elif timestampType == BookmarkNode.TsModified:
+            node.modified = timestamp
+        elif timestampType == BookmarkNode.TsVisited:
+            node.visited = timestamp
+        self.__saveTimer.changeOccurred()
+    
+    def incVisitCount(self, node):
+        """
+        Public method to increment the visit count of a bookmark.
+        
+        @param node reference to the node to be changed (BookmarkNode)
+        """
+        if not self.__loaded:
+            return
+        
+        if node:
+            node.visitCount += 1
+            self.__saveTimer.changeOccurred()
+    
+    def setVisitCount(self, node, count):
+        """
+        Public method to set the visit count of a bookmark.
+        
+        @param node reference to the node to be changed (BookmarkNode)
+        @param count visit count to be set (int or str)
+        """
+        try:
+            node.visitCount = int(count)
+            self.__saveTimer.changeOccurred()
+        except ValueError:
+            # ignore invalid values
+            pass
+    
+    def bookmarks(self):
+        """
+        Public method to get a reference to the root bookmark node.
+        
+        @return reference to the root bookmark node (BookmarkNode)
+        """
+        if not self.__loaded:
+            self.load()
+        
+        return self.__bookmarkRootNode
+    
+    def menu(self):
+        """
+        Public method to get a reference to the bookmarks menu node.
+        
+        @return reference to the bookmarks menu node (BookmarkNode)
+        """
+        if not self.__loaded:
+            self.load()
+        
+        return self.__menu
+    
+    def toolbar(self):
+        """
+        Public method to get a reference to the bookmarks toolbar node.
+        
+        @return reference to the bookmarks toolbar node (BookmarkNode)
+        """
+        if not self.__loaded:
+            self.load()
+        
+        return self.__toolbar
+    
+    def bookmarksModel(self):
+        """
+        Public method to get a reference to the bookmarks model.
+        
+        @return reference to the bookmarks model (BookmarksModel)
+        """
+        if self.__bookmarksModel is None:
+            from .BookmarksModel import BookmarksModel
+            self.__bookmarksModel = BookmarksModel(self, self)
+        return self.__bookmarksModel
+    
+    def importBookmarks(self):
+        """
+        Public method to import bookmarks.
+        """
+        from .BookmarksImportDialog import BookmarksImportDialog
+        dlg = BookmarksImportDialog()
+        if dlg.exec_() == QDialog.Accepted:
+            importRootNode = dlg.getImportedBookmarks()
+            if importRootNode is not None:
+                self.addBookmark(self.menu(), importRootNode)
+    
+    def exportBookmarks(self):
+        """
+        Public method to export the bookmarks.
+        """
+        fileName, selectedFilter = E5FileDialog.getSaveFileNameAndFilter(
+            None,
+            self.tr("Export Bookmarks"),
+            "eric6_bookmarks.xbel",
+            self.tr("XBEL bookmarks (*.xbel);;"
+                    "XBEL bookmarks (*.xml);;"
+                    "HTML Bookmarks (*.html)"))
+        if not fileName:
+            return
+        
+        ext = QFileInfo(fileName).suffix()
+        if not ext:
+            ex = selectedFilter.split("(*")[1].split(")")[0]
+            if ex:
+                fileName += ex
+        
+        ext = QFileInfo(fileName).suffix()
+        if ext == "html":
+            from .NsHtmlWriter import NsHtmlWriter
+            writer = NsHtmlWriter()
+        else:
+            from .XbelWriter import XbelWriter
+            writer = XbelWriter()
+        if not writer.write(fileName, self.__bookmarkRootNode):
+            E5MessageBox.critical(
+                None,
+                self.tr("Exporting Bookmarks"),
+                self.tr("""Error exporting bookmarks to <b>{0}</b>.""")
+                .format(fileName))
+    
+    def faviconChanged(self, url):
+        """
+        Public slot to update the icon image for an URL.
+        
+        @param url URL of the icon to update (QUrl or string)
+        """
+        if isinstance(url, QUrl):
+            url = url.toString()
+        nodes = self.bookmarksForUrl(url)
+        for node in nodes:
+            self.bookmarksModel().entryChanged(node)
+    
+    def bookmarkForUrl(self, url, start=StartRoot):
+        """
+        Public method to get a bookmark node for a given URL.
+        
+        @param url URL of the bookmark to search for (QUrl or string)
+        @keyparam start indicator for the start of the search
+            (StartRoot, StartMenu, StartToolBar)
+        @return bookmark node for the given url (BookmarkNode)
+        """
+        if start == StartMenu:
+            startNode = self.__menu
+        elif start == StartToolBar:
+            startNode = self.__toolbar
+        else:
+            startNode = self.__bookmarkRootNode
+        if startNode is None:
+            return None
+        
+        if isinstance(url, QUrl):
+            url = url.toString()
+        
+        return self.__searchBookmark(url, startNode)
+    
+    def __searchBookmark(self, url, startNode):
+        """
+        Private method get a bookmark node for a given URL.
+        
+        @param url URL of the bookmark to search for (string)
+        @param startNode reference to the node to start searching
+            (BookmarkNode)
+        @return bookmark node for the given url (BookmarkNode)
+        """
+        bm = None
+        for node in startNode.children():
+            if node.type() == BookmarkNode.Folder:
+                bm = self.__searchBookmark(url, node)
+            elif node.type() == BookmarkNode.Bookmark:
+                if node.url == url:
+                    bm = node
+            if bm is not None:
+                return bm
+        return None
+    
+    def bookmarksForUrl(self, url, start=StartRoot):
+        """
+        Public method to get a list of bookmark nodes for a given URL.
+        
+        @param url URL of the bookmarks to search for (QUrl or string)
+        @keyparam start indicator for the start of the search
+            (StartRoot, StartMenu, StartToolBar)
+        @return list of bookmark nodes for the given url (list of BookmarkNode)
+        """
+        if start == StartMenu:
+            startNode = self.__menu
+        elif start == StartToolBar:
+            startNode = self.__toolbar
+        else:
+            startNode = self.__bookmarkRootNode
+        if startNode is None:
+            return []
+        
+        if isinstance(url, QUrl):
+            url = url.toString()
+        
+        return self.__searchBookmarks(url, startNode)
+    
+    def __searchBookmarks(self, url, startNode):
+        """
+        Private method get a list of bookmark nodes for a given URL.
+        
+        @param url URL of the bookmarks to search for (string)
+        @param startNode reference to the node to start searching
+            (BookmarkNode)
+        @return list of bookmark nodes for the given url (list of BookmarkNode)
+        """
+        bm = []
+        for node in startNode.children():
+            if node.type() == BookmarkNode.Folder:
+                bm.extend(self.__searchBookmarks(url, node))
+            elif node.type() == BookmarkNode.Bookmark:
+                if node.url == url:
+                    bm.append(node)
+        return bm
+
+
+class RemoveBookmarksCommand(QUndoCommand):
+    """
+    Class implementing the Remove undo command.
+    """
+    def __init__(self, bookmarksManager, parent, row):
+        """
+        Constructor
+        
+        @param bookmarksManager reference to the bookmarks manager
+            (BookmarksManager)
+        @param parent reference to the parent node (BookmarkNode)
+        @param row row number of bookmark (integer)
+        """
+        super(RemoveBookmarksCommand, self).__init__(
+            QCoreApplication.translate("BookmarksManager", "Remove Bookmark"))
+        
+        self._row = row
+        self._bookmarksManager = bookmarksManager
+        try:
+            self._node = parent.children()[row]
+        except IndexError:
+            self._node = BookmarkNode()
+        self._parent = parent
+    
+    def undo(self):
+        """
+        Public slot to perform the undo action.
+        """
+        self._parent.add(self._node, self._row)
+        self._bookmarksManager.entryAdded.emit(self._node)
+    
+    def redo(self):
+        """
+        Public slot to perform the redo action.
+        """
+        self._parent.remove(self._node)
+        self._bookmarksManager.entryRemoved.emit(
+            self._parent, self._row, self._node)
+
+
+class InsertBookmarksCommand(RemoveBookmarksCommand):
+    """
+    Class implementing the Insert undo command.
+    """
+    def __init__(self, bookmarksManager, parent, node, row):
+        """
+        Constructor
+        
+        @param bookmarksManager reference to the bookmarks manager
+            (BookmarksManager)
+        @param parent reference to the parent node (BookmarkNode)
+        @param node reference to the node to be inserted (BookmarkNode)
+        @param row row number of bookmark (integer)
+        """
+        RemoveBookmarksCommand.__init__(self, bookmarksManager, parent, row)
+        self.setText(QCoreApplication.translate(
+            "BookmarksManager", "Insert Bookmark"))
+        self._node = node
+    
+    def undo(self):
+        """
+        Public slot to perform the undo action.
+        """
+        RemoveBookmarksCommand.redo(self)
+    
+    def redo(self):
+        """
+        Public slot to perform the redo action.
+        """
+        RemoveBookmarksCommand.undo(self)
+
+
+class ChangeBookmarkCommand(QUndoCommand):
+    """
+    Class implementing the Insert undo command.
+    """
+    def __init__(self, bookmarksManager, node, newValue, title):
+        """
+        Constructor
+        
+        @param bookmarksManager reference to the bookmarks manager
+            (BookmarksManager)
+        @param node reference to the node to be changed (BookmarkNode)
+        @param newValue new value to be set (string)
+        @param title flag indicating a change of the title (True) or
+            the URL (False) (boolean)
+        """
+        super(ChangeBookmarkCommand, self).__init__()
+        
+        self._bookmarksManager = bookmarksManager
+        self._title = title
+        self._newValue = newValue
+        self._node = node
+        
+        if self._title:
+            self._oldValue = self._node.title
+            self.setText(QCoreApplication.translate(
+                "BookmarksManager", "Name Change"))
+        else:
+            self._oldValue = self._node.url
+            self.setText(QCoreApplication.translate(
+                "BookmarksManager", "Address Change"))
+    
+    def undo(self):
+        """
+        Public slot to perform the undo action.
+        """
+        if self._title:
+            self._node.title = self._oldValue
+        else:
+            self._node.url = self._oldValue
+        self._bookmarksManager.entryChanged.emit(self._node)
+    
+    def redo(self):
+        """
+        Public slot to perform the redo action.
+        """
+        if self._title:
+            self._node.title = self._newValue
+        else:
+            self._node.url = self._newValue
+        self._bookmarksManager.entryChanged.emit(self._node)

eric ide

mercurial