eric6/WebBrowser/History/HistoryMenu.py

changeset 6942
2602857055c5
parent 6645
ad476851d7e0
child 7229
53054eb5b15a
diff -r f99d60d6b59b -r 2602857055c5 eric6/WebBrowser/History/HistoryMenu.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/WebBrowser/History/HistoryMenu.py	Sun Apr 14 15:09:21 2019 +0200
@@ -0,0 +1,518 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2009 - 2019 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the history menu.
+"""
+
+from __future__ import unicode_literals
+
+import sys
+
+from PyQt5.QtCore import pyqtSignal, Qt, QMimeData, QUrl, QModelIndex, \
+    QSortFilterProxyModel, QAbstractProxyModel
+from PyQt5.QtWidgets import QMenu
+
+from E5Gui.E5ModelMenu import E5ModelMenu
+from E5Gui import E5MessageBox
+
+from .HistoryModel import HistoryModel
+
+import UI.PixmapCache
+
+
+class HistoryMenuModel(QAbstractProxyModel):
+    """
+    Class implementing a model for the history menu.
+    
+    It maps the first bunch of items of the source model to the root.
+    """
+    MOVEDROWS = 15
+    
+    def __init__(self, sourceModel, parent=None):
+        """
+        Constructor
+        
+        @param sourceModel reference to the source model (QAbstractItemModel)
+        @param parent reference to the parent object (QObject)
+        """
+        super(HistoryMenuModel, self).__init__(parent)
+        
+        self.__treeModel = sourceModel
+        
+        self.setSourceModel(sourceModel)
+    
+    def bumpedRows(self):
+        """
+        Public method to determine the number of rows moved to the root.
+        
+        @return number of rows moved to the root (integer)
+        """
+        first = self.__treeModel.index(0, 0)
+        if not first.isValid():
+            return 0
+        return min(self.__treeModel.rowCount(first), self.MOVEDROWS)
+    
+    def columnCount(self, parent=None):
+        """
+        Public method to get the number of columns.
+        
+        @param parent index of parent (QModelIndex)
+        @return number of columns (integer)
+        """
+        if parent is None:
+            parent = QModelIndex()
+        
+        return self.__treeModel.columnCount(self.mapToSource(parent))
+    
+    def rowCount(self, parent=None):
+        """
+        Public method to determine the number of rows.
+        
+        @param parent index of parent (QModelIndex)
+        @return number of rows (integer)
+        """
+        if parent is None:
+            parent = QModelIndex()
+        
+        if parent.column() > 0:
+            return 0
+        
+        if not parent.isValid():
+            folders = self.sourceModel().rowCount()
+            bumpedItems = self.bumpedRows()
+            if bumpedItems <= self.MOVEDROWS and \
+                    bumpedItems == self.sourceModel().rowCount(
+                        self.sourceModel().index(0, 0)):
+                folders -= 1
+            return bumpedItems + folders
+        
+        if parent.internalId() == sys.maxsize:
+            if parent.row() < self.bumpedRows():
+                return 0
+        
+        idx = self.mapToSource(parent)
+        defaultCount = self.sourceModel().rowCount(idx)
+        if idx == self.sourceModel().index(0, 0):
+            return defaultCount - self.bumpedRows()
+        
+        return defaultCount
+    
+    def mapFromSource(self, sourceIndex):
+        """
+        Public method to map an index to the proxy model index.
+        
+        @param sourceIndex reference to a source model index (QModelIndex)
+        @return proxy model index (QModelIndex)
+        """
+        sourceRow = self.__treeModel.mapToSource(sourceIndex).row()
+        return self.createIndex(
+            sourceIndex.row(), sourceIndex.column(), sourceRow)
+    
+    def mapToSource(self, proxyIndex):
+        """
+        Public method to map an index to the source model index.
+        
+        @param proxyIndex reference to a proxy model index (QModelIndex)
+        @return source model index (QModelIndex)
+        """
+        if not proxyIndex.isValid():
+            return QModelIndex()
+        
+        if proxyIndex.internalId() == sys.maxsize:
+            bumpedItems = self.bumpedRows()
+            if proxyIndex.row() < bumpedItems:
+                return self.__treeModel.index(
+                    proxyIndex.row(), proxyIndex.column(),
+                    self.__treeModel.index(0, 0))
+            if bumpedItems <= self.MOVEDROWS and \
+                    bumpedItems == self.sourceModel().rowCount(
+                        self.__treeModel.index(0, 0)):
+                bumpedItems -= 1
+            return self.__treeModel.index(proxyIndex.row() - bumpedItems,
+                                          proxyIndex.column())
+        
+        historyIndex = self.__treeModel.sourceModel()\
+            .index(proxyIndex.internalId(), proxyIndex.column())
+        treeIndex = self.__treeModel.mapFromSource(historyIndex)
+        return treeIndex
+    
+    def index(self, row, column, parent=None):
+        """
+        Public method to create an index.
+        
+        @param row row number for the index (integer)
+        @param column column number for the index (integer)
+        @param parent index of the parent item (QModelIndex)
+        @return requested index (QModelIndex)
+        """
+        if parent is None:
+            parent = QModelIndex()
+        
+        if row < 0 or \
+           column < 0 or \
+           column >= self.columnCount(parent) or \
+           parent.column() > 0:
+            return QModelIndex()
+        
+        if not parent.isValid():
+            return self.createIndex(row, column, sys.maxsize)
+        
+        treeIndexParent = self.mapToSource(parent)
+        
+        bumpedItems = 0
+        if treeIndexParent == self.sourceModel().index(0, 0):
+            bumpedItems = self.bumpedRows()
+        treeIndex = self.__treeModel.index(
+            row + bumpedItems, column, treeIndexParent)
+        historyIndex = self.__treeModel.mapToSource(treeIndex)
+        historyRow = historyIndex.row()
+        if historyRow == -1:
+            historyRow = treeIndex.row()
+        return self.createIndex(row, column, historyRow)
+
+    def parent(self, index):
+        """
+        Public method to get the parent index.
+        
+        @param index index of item to get parent (QModelIndex)
+        @return index of parent (QModelIndex)
+        """
+        offset = index.internalId()
+        if offset == sys.maxsize or not index.isValid():
+            return QModelIndex()
+        
+        historyIndex = self.__treeModel.sourceModel().index(
+            index.internalId(), 0)
+        treeIndex = self.__treeModel.mapFromSource(historyIndex)
+        treeIndexParent = treeIndex.parent()
+        
+        sourceRow = self.sourceModel().mapToSource(treeIndexParent).row()
+        bumpedItems = self.bumpedRows()
+        if bumpedItems <= self.MOVEDROWS and \
+                bumpedItems == self.sourceModel().rowCount(
+                    self.sourceModel().index(0, 0)):
+            bumpedItems -= 1
+        
+        return self.createIndex(bumpedItems + treeIndexParent.row(),
+                                treeIndexParent.column(),
+                                sourceRow)
+    
+    def mimeData(self, indexes):
+        """
+        Public method to return the mime data.
+        
+        @param indexes list of indexes (QModelIndexList)
+        @return mime data (QMimeData)
+        """
+        urls = []
+        for index in indexes:
+            url = index.data(HistoryModel.UrlRole)
+            urls.append(url)
+        
+        mdata = QMimeData()
+        mdata.setUrls(urls)
+        return mdata
+
+
+class HistoryMostVisitedMenuModel(QSortFilterProxyModel):
+    """
+    Class implementing a model to show the most visited history entries.
+    """
+    def __init__(self, sourceModel, parent=None):
+        """
+        Constructor
+        
+        @param sourceModel reference to the source model (QAbstractItemModel)
+        @param parent reference to the parent object (QObject)
+        """
+        super(HistoryMostVisitedMenuModel, self).__init__(parent)
+        
+        self.setDynamicSortFilter(True)
+        self.setSourceModel(sourceModel)
+    
+    def lessThan(self, left, right):
+        """
+        Public method used to sort the displayed items.
+        
+        @param left index of left item (QModelIndex)
+        @param right index of right item (QModelIndex)
+        @return true, if left is less than right (boolean)
+        """
+        from .HistoryFilterModel import HistoryFilterModel
+        frequency_L = \
+            self.sourceModel().data(left, HistoryFilterModel.FrequencyRole)
+        dateTime_L = \
+            self.sourceModel().data(left, HistoryModel.DateTimeRole)
+        frequency_R = \
+            self.sourceModel().data(right, HistoryFilterModel.FrequencyRole)
+        dateTime_R = \
+            self.sourceModel().data(right, HistoryModel.DateTimeRole)
+        
+        # Sort results in descending frequency-derived score. If frequencies
+        # are equal, sort on most recently viewed
+        if frequency_R == frequency_L:
+            return dateTime_R < dateTime_L
+        
+        return frequency_R < frequency_L
+
+
+class HistoryMenu(E5ModelMenu):
+    """
+    Class implementing the history menu.
+    
+    @signal openUrl(QUrl, str) emitted to open a URL in the current tab
+    @signal newTab(QUrl, str) emitted to open a URL in a new tab
+    @signal newBackgroundTab(QUrl, str) emitted to open a URL in a new
+        background tab
+    @signal newWindow(QUrl, str) emitted to open a URL in a new window
+    @signal newPrivateWindow(QUrl, str) emitted to open a URL in a new
+        private window
+    """
+    openUrl = pyqtSignal(QUrl, str)
+    newTab = pyqtSignal(QUrl, str)
+    newBackgroundTab = pyqtSignal(QUrl, str)
+    newWindow = pyqtSignal(QUrl, str)
+    newPrivateWindow = pyqtSignal(QUrl, str)
+    
+    def __init__(self, parent=None, tabWidget=None):
+        """
+        Constructor
+        
+        @param parent reference to the parent widget (QWidget)
+        @param tabWidget reference to the tab widget managing the browser
+            tabs (HelpTabWidget
+        """
+        E5ModelMenu.__init__(self, parent)
+        
+        self.__tabWidget = tabWidget
+        self.__mw = parent
+        
+        self.__historyManager = None
+        self.__historyMenuModel = None
+        self.__initialActions = []
+        self.__mostVisitedMenu = None
+        
+        self.__closedTabsMenu = QMenu(self.tr("Closed Tabs"))
+        self.__closedTabsMenu.aboutToShow.connect(
+            self.__aboutToShowClosedTabsMenu)
+        self.__tabWidget.closedTabsManager().closedTabAvailable.connect(
+            self.__closedTabAvailable)
+        
+        self.setMaxRows(7)
+        
+        self.activated.connect(self.__activated)
+        self.setStatusBarTextRole(HistoryModel.UrlStringRole)
+    
+    def __activated(self, idx):
+        """
+        Private slot handling the activated signal.
+        
+        @param idx index of the activated item (QModelIndex)
+        """
+        if self._keyboardModifiers & Qt.ControlModifier:
+            self.newTab.emit(
+                idx.data(HistoryModel.UrlRole),
+                idx.data(HistoryModel.TitleRole))
+        elif self._keyboardModifiers & Qt.ShiftModifier:
+            self.newWindow.emit(
+                idx.data(HistoryModel.UrlRole),
+                idx.data(HistoryModel.TitleRole))
+        else:
+            self.openUrl.emit(
+                idx.data(HistoryModel.UrlRole),
+                idx.data(HistoryModel.TitleRole))
+    
+    def prePopulated(self):
+        """
+        Public method to add any actions before the tree.
+       
+        @return flag indicating if any actions were added (boolean)
+        """
+        if self.__historyManager is None:
+            from WebBrowser.WebBrowserWindow import WebBrowserWindow
+            self.__historyManager = WebBrowserWindow.historyManager()
+            self.__historyMenuModel = HistoryMenuModel(
+                self.__historyManager.historyTreeModel(), self)
+            self.setModel(self.__historyMenuModel)
+        
+        # initial actions
+        for act in self.__initialActions:
+            self.addAction(act)
+        if len(self.__initialActions) != 0:
+            self.addSeparator()
+        self.setFirstSeparator(self.__historyMenuModel.bumpedRows())
+        
+        return False
+    
+    def postPopulated(self):
+        """
+        Public method to add any actions after the tree.
+        """
+        if len(self.__historyManager.history()) > 0:
+            self.addSeparator()
+        
+        if self.__mostVisitedMenu is None:
+            self.__mostVisitedMenu = HistoryMostVisitedMenu(10, self)
+            self.__mostVisitedMenu.setTitle(self.tr("Most Visited"))
+            self.__mostVisitedMenu.openUrl.connect(self.openUrl)
+            self.__mostVisitedMenu.newTab.connect(self.newTab)
+            self.__mostVisitedMenu.newBackgroundTab.connect(
+                self.newBackgroundTab)
+            self.__mostVisitedMenu.newWindow.connect(self.newWindow)
+            self.__mostVisitedMenu.newPrivateWindow.connect(
+                self.newPrivateWindow)
+        self.addMenu(self.__mostVisitedMenu)
+        act = self.addMenu(self.__closedTabsMenu)
+        act.setIcon(UI.PixmapCache.getIcon("trash.png"))
+        act.setEnabled(self.__tabWidget.canRestoreClosedTab())
+        self.addSeparator()
+        
+        act = self.addAction(UI.PixmapCache.getIcon("history.png"),
+                             self.tr("Show All History..."))
+        act.triggered.connect(self.showHistoryDialog)
+        act = self.addAction(UI.PixmapCache.getIcon("historyClear.png"),
+                             self.tr("Clear History..."))
+        act.triggered.connect(self.__clearHistoryDialog)
+    
+    def setInitialActions(self, actions):
+        """
+        Public method to set the list of actions that should appear first in
+        the menu.
+        
+        @param actions list of initial actions (list of QAction)
+        """
+        self.__initialActions = actions[:]
+        for act in self.__initialActions:
+            self.addAction(act)
+    
+    def showHistoryDialog(self):
+        """
+        Public slot to show the history dialog.
+        """
+        from .HistoryDialog import HistoryDialog
+        dlg = HistoryDialog(self.__mw)
+        dlg.openUrl.connect(self.openUrl)
+        dlg.newTab.connect(self.newTab)
+        dlg.newBackgroundTab.connect(self.newBackgroundTab)
+        dlg.newWindow.connect(self.newWindow)
+        dlg.newPrivateWindow.connect(self.newPrivateWindow)
+        dlg.show()
+    
+    def __clearHistoryDialog(self):
+        """
+        Private slot to clear the history.
+        """
+        if self.__historyManager is not None and E5MessageBox.yesNo(
+                self,
+                self.tr("Clear History"),
+                self.tr("""Do you want to clear the history?""")):
+            self.__historyManager.clear()
+            self.__tabWidget.clearClosedTabsList()
+    
+    def __aboutToShowClosedTabsMenu(self):
+        """
+        Private slot to populate the closed tabs menu.
+        """
+        fm = self.__closedTabsMenu.fontMetrics()
+        maxWidth = fm.width('m') * 40
+        
+        import WebBrowser.WebBrowserWindow
+        self.__closedTabsMenu.clear()
+        index = 0
+        for tab in self.__tabWidget.closedTabsManager().allClosedTabs():
+            title = fm.elidedText(tab.title, Qt.ElideRight, maxWidth)
+            act = self.__closedTabsMenu.addAction(
+                WebBrowser.WebBrowserWindow.WebBrowserWindow.icon(tab.url),
+                title)
+            act.setData(index)
+            act.triggered.connect(
+                lambda: self.__tabWidget.restoreClosedTab(act))
+            index += 1
+        self.__closedTabsMenu.addSeparator()
+        self.__closedTabsMenu.addAction(
+            self.tr("Restore All Closed Tabs"),
+            self.__tabWidget.restoreAllClosedTabs)
+        self.__closedTabsMenu.addAction(
+            self.tr("Clear List"),
+            self.__tabWidget.clearClosedTabsList)
+    
+    def __closedTabAvailable(self, avail):
+        """
+        Private slot to handle changes of the availability of closed tabs.
+        
+        @param avail flag indicating the availability of closed tabs (boolean)
+        """
+        self.__closedTabsMenu.setEnabled(avail)
+
+
+class HistoryMostVisitedMenu(E5ModelMenu):
+    """
+    Class implementing the most visited history menu.
+    
+    @signal openUrl(QUrl, str) emitted to open a URL in the current tab
+    @signal newTab(QUrl, str) emitted to open a URL in a new tab
+    @signal newBackgroundTab(QUrl, str) emitted to open a URL in a new
+        background tab
+    @signal newWindow(QUrl, str) emitted to open a URL in a new window
+    @signal newPrivateWindow(QUrl, str) emitted to open a URL in a new
+        private window
+    """
+    openUrl = pyqtSignal(QUrl, str)
+    newTab = pyqtSignal(QUrl, str)
+    newBackgroundTab = pyqtSignal(QUrl, str)
+    newWindow = pyqtSignal(QUrl, str)
+    newPrivateWindow = pyqtSignal(QUrl, str)
+    
+    def __init__(self, count, parent=None):
+        """
+        Constructor
+        
+        @param count maximum number of entries to be shown (integer)
+        @param parent reference to the parent widget (QWidget)
+        """
+        E5ModelMenu.__init__(self, parent)
+        
+        self.__historyMenuModel = None
+        
+        self.setMaxRows(count + 1)
+        
+##        self.activated.connect(self.__activated)
+        self.setStatusBarTextRole(HistoryModel.UrlStringRole)
+    
+    def __activated(self, idx):
+        """
+        Private slot handling the activated signal.
+        
+        @param idx index of the activated item (QModelIndex)
+        """
+        if self._keyboardModifiers & Qt.ControlModifier:
+            self.newTab.emit(
+                idx.data(HistoryModel.UrlRole),
+                idx.data(HistoryModel.TitleRole))
+        elif self._keyboardModifiers & Qt.ShiftModifier:
+            self.newWindow.emit(
+                idx.data(HistoryModel.UrlRole),
+                idx.data(HistoryModel.TitleRole))
+        else:
+            self.openUrl.emit(
+                idx.data(HistoryModel.UrlRole),
+                idx.data(HistoryModel.TitleRole))
+    
+    def prePopulated(self):
+        """
+        Public method to add any actions before the tree.
+       
+        @return flag indicating if any actions were added (boolean)
+        """
+        if self.__historyMenuModel is None:
+            from WebBrowser.WebBrowserWindow import WebBrowserWindow
+            historyManager = WebBrowserWindow.historyManager()
+            self.__historyMenuModel = HistoryMostVisitedMenuModel(
+                historyManager.historyFilterModel(), self)
+            self.setModel(self.__historyMenuModel)
+        self.__historyMenuModel.sort(0)
+        
+        return False

eric ide

mercurial