class BookmarksManager(QObject):
    """
    Class implementing the bookmarks manager.
    
    @signal entryAdded emitted after a bookmark node has been added
    @signal entryRemoved emitted after a bookmark
        node has been removed
    @signal entryChanged emitted after a bookmark node has been changed
    """
    def __init__(self, parent = None):
        """
        Constructor
        
        @param parent reference to the parent object (QObject)
        """
        QObject.__init__(self, parent)
        
        self.__loaded = False
        self.__saveTimer = AutoSaver(self, self.save)
        self.__bookmarkRootNode = None
        self.__toolbar = None
        self.__menu = None
        self.__bookmarksModel = None
        self.__commands = QUndoStack()
        
        self.connect(self, SIGNAL("entryAdded"), 
                     self.__saveTimer.changeOccurred)
        self.connect(self, SIGNAL("entryRemoved"), 
                     self.__saveTimer.changeOccurred)
        self.connect(self, SIGNAL("entryChanged"), 
                     self.__saveTimer.changeOccurred)
    
    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 load(self):
        """
        Public method to load the bookmarks.
        """
        if self.__loaded:
            return
        
        self.__loaded = True
        
        bookmarkFile = os.path.join(Utilities.getConfigDir(), "browser", "bookmarks.xbel")
        if not QFile.exists(bookmarkFile):
            ba = QByteArray(DefaultBookmarks)
            bookmarkFile = QBuffer(ba)
            bookmarkFile.open(QIODevice.ReadOnly)
        
        reader = XbelReader()
        self.__bookmarkRootNode = reader.read(bookmarkFile)
        if reader.error() != QXmlStreamReader.NoError:
            KQMessageBox.warning(None,
                self.trUtf8("Loading Bookmarks"),
                self.trUtf8("""Error when loading bookmarks on line %1, column %2:\n"""
                            """%3""")\
                    .arg(reader.lineNumber())\
                    .arg(reader.columnNumber())\
                    .arg(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.trUtf8("Toolbar Bookmarks") or \
                    node.title == BOOKMARKBAR) and \
                   self.__toolbar is None:
                    node.title = self.trUtf8(BOOKMARKBAR)
                    self.__toolbar = node
                
                if (node.title == self.trUtf8("Menu") or \
                    node.title == BOOKMARKMENU) and \
                   self.__menu is None:
                    node.title = self.trUtf8(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.trUtf8(BOOKMARKBAR)
        else:
            self.__bookmarkRootNode.add(self.__toolbar)
        
        if self.__menu is None:
            self.__menu = BookmarkNode(BookmarkNode.Folder, self.__bookmarkRootNode)
            self.__menu.title = self.trUtf8(BOOKMARKMENU)
        else:
            self.__bookmarkRootNode.add(self.__menu)
        
        for node in others:
            self.__menu.add(node)
        
        self.__convertFromOldBookmarks()
    
    def save(self):
        """
        Public method to save the bookmarks.
        """
        if not self.__loaded:
            return
        
        writer = XbelWriter()
        bookmarkFile = os.path.join(Utilities.getConfigDir(), "browser", "bookmarks.xbel")
        
        # 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):
            KQMessageBox.warning(None,
                self.trUtf8("Saving Bookmarks"),
                self.trUtf8("""Error saving bookmarks to <b>%1</b>.""").arg(bookmarkFile))
        
        # restore localized titles
        self.__menu.title = self.trUtf8(BOOKMARKMENU)
        self.__toolbar.title = self.trUtf8(BOOKMARKBAR)
    
    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
        
        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 (QString)
        """
        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 (QString)
        """
        if not self.__loaded:
            return
        
        command = ChangeBookmarkCommand(self, node, newUrl, False)
        self.__commands.push(command)
    
    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:
            self.__bookmarksModel = BookmarksModel(self, self)
        return self.__bookmarksModel
    
    def importBookmarks(self):
        """
        Public method to import bookmarks.
        """
        supportedFormats = QStringList() \
            << self.trUtf8("XBEL bookmarks").append(" (*.xbel *.xml)") \
            << self.trUtf8("HTML Netscape bookmarks").append(" (*.html *.htm)")
        
        fileName = KQFileDialog.getOpenFileName(\
            None,
            self.trUtf8("Import Bookmarks"),
            QString(),
            supportedFormats.join(";;"),
            None)
        if fileName.isEmpty():
            return
        
        reader = XbelReader()
        importRootNode = None
        if fileName.endsWith(".html"):
            inFile = QFile(fileName)
            inFile.open(QIODevice.ReadOnly)
            if inFile.openMode == QIODevice.NotOpen:
                KQMessageBox.warning(None,
                    self.trUtf8("Import Bookmarks"),
                    self.trUtf8("""Error opening bookmarks file <b>%1</b>.""")\
                        .arg(fileName))
                return
            
            webpage = QWebPage()
            webpage.mainFrame().setHtml(QString(inFile.readAll()))
            result = webpage.mainFrame().evaluateJavaScript(extract_js).toByteArray()
            buffer_ = QBuffer(result)
            buffer_.open(QIODevice.ReadOnly)
            importRootNode = reader.read(buffer_)
        else:
            importRootNode = reader.read(fileName)
        
        if reader.error() != QXmlStreamReader.NoError:
            KQMessageBox.warning(None,
                self.trUtf8("Import Bookmarks"),
                self.trUtf8("""Error when importing bookmarks on line %1, column %2:\n"""
                            """%3""")\
                    .arg(reader.lineNumber())\
                    .arg(reader.columnNumber())\
                    .arg(reader.errorString()))
            return
        
        importRootNode.setType(BookmarkNode.Folder)
        importRootNode.title = self.trUtf8("Imported %1")\
            .arg(QDate.currentDate().toString(Qt.SystemLocaleShortDate))
        self.addBookmark(self.menu(), importRootNode)
    
    def exportBookmarks(self):
        """
        Public method to export the bookmarks.
        """
        fileName = KQFileDialog.getSaveFileName(\
            None,
            self.trUtf8("Export Bookmarks"),
            "eric4_bookmarks.xbel",
            self.trUtf8("XBEL bookmarks").append(" (*.xbel *.xml)"))
        if fileName.isEmpty():
            return
        
        writer = XbelWriter()
        if not writer.write(fileName, self.__bookmarkRootNode):
            KQMessageBox.critical(None,
                self.trUtf8("Exporting Bookmarks"),
                self.trUtf8("""Error exporting bookmarks to <b>%1</b>.""")\
                    .arg(fileName))
    
    def __convertFromOldBookmarks(self):
        """
        Private method to convert the old bookmarks into the new ones.
        """
        bmNames = Preferences.Prefs.settings.value('Bookmarks/Names')
        bmFiles = Preferences.Prefs.settings.value('Bookmarks/Files')
        
        if bmNames.isValid() and bmFiles.isValid():
            bmNames = bmNames.toStringList()
            bmFiles = bmFiles.toStringList()
            if len(bmNames) == len(bmFiles):
                convertedRootNode = BookmarkNode(BookmarkNode.Folder)
                convertedRootNode.title = self.trUtf8("Converted %1")\
                    .arg(QDate.currentDate().toString(Qt.SystemLocaleShortDate))
                for i in range(len(bmNames)):
                    node = BookmarkNode(BookmarkNode.Bookmark, convertedRootNode)
                    node.title = bmNames[i]
                    url = QUrl(bmFiles[i])
                    if url.scheme().isEmpty():
                        url.setScheme("file")
                    node.url = url.toString()
                self.addBookmark(self.menu(), convertedRootNode)
                
                Preferences.Prefs.settings.remove('Bookmarks')