eric7/WebBrowser/History/HistoryMenu.py

branch
eric7
changeset 8312
800c432b34c8
parent 8227
349308e84eeb
child 8318
962bce857696
equal deleted inserted replaced
8311:4e8b98454baa 8312:800c432b34c8
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2009 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the history menu.
8 """
9
10 import sys
11 import functools
12
13 from PyQt5.QtCore import (
14 pyqtSignal, Qt, QMimeData, QUrl, QModelIndex, QSortFilterProxyModel,
15 QAbstractProxyModel
16 )
17 from PyQt5.QtWidgets import QMenu
18
19 from E5Gui.E5ModelMenu import E5ModelMenu
20 from E5Gui import E5MessageBox
21
22 from .HistoryModel import HistoryModel
23
24 import UI.PixmapCache
25
26
27 class HistoryMenuModel(QAbstractProxyModel):
28 """
29 Class implementing a model for the history menu.
30
31 It maps the first bunch of items of the source model to the root.
32 """
33 MOVEDROWS = 15
34
35 def __init__(self, sourceModel, parent=None):
36 """
37 Constructor
38
39 @param sourceModel reference to the source model (QAbstractItemModel)
40 @param parent reference to the parent object (QObject)
41 """
42 super().__init__(parent)
43
44 self.__treeModel = sourceModel
45
46 self.setSourceModel(sourceModel)
47
48 def bumpedRows(self):
49 """
50 Public method to determine the number of rows moved to the root.
51
52 @return number of rows moved to the root (integer)
53 """
54 first = self.__treeModel.index(0, 0)
55 if not first.isValid():
56 return 0
57 return min(self.__treeModel.rowCount(first), self.MOVEDROWS)
58
59 def columnCount(self, parent=None):
60 """
61 Public method to get the number of columns.
62
63 @param parent index of parent (QModelIndex)
64 @return number of columns (integer)
65 """
66 if parent is None:
67 parent = QModelIndex()
68
69 return self.__treeModel.columnCount(self.mapToSource(parent))
70
71 def rowCount(self, parent=None):
72 """
73 Public method to determine the number of rows.
74
75 @param parent index of parent (QModelIndex)
76 @return number of rows (integer)
77 """
78 if parent is None:
79 parent = QModelIndex()
80
81 if parent.column() > 0:
82 return 0
83
84 if not parent.isValid():
85 folders = self.sourceModel().rowCount()
86 bumpedItems = self.bumpedRows()
87 if (
88 bumpedItems <= self.MOVEDROWS and
89 bumpedItems == self.sourceModel().rowCount(
90 self.sourceModel().index(0, 0))
91 ):
92 folders -= 1
93 return bumpedItems + folders
94
95 if (
96 parent.internalId() == sys.maxsize and
97 parent.row() < self.bumpedRows()
98 ):
99 return 0
100
101 idx = self.mapToSource(parent)
102 defaultCount = self.sourceModel().rowCount(idx)
103 if idx == self.sourceModel().index(0, 0):
104 return defaultCount - self.bumpedRows()
105
106 return defaultCount
107
108 def mapFromSource(self, sourceIndex):
109 """
110 Public method to map an index to the proxy model index.
111
112 @param sourceIndex reference to a source model index (QModelIndex)
113 @return proxy model index (QModelIndex)
114 """
115 sourceRow = self.__treeModel.mapToSource(sourceIndex).row()
116 return self.createIndex(
117 sourceIndex.row(), sourceIndex.column(), sourceRow)
118
119 def mapToSource(self, proxyIndex):
120 """
121 Public method to map an index to the source model index.
122
123 @param proxyIndex reference to a proxy model index (QModelIndex)
124 @return source model index (QModelIndex)
125 """
126 if not proxyIndex.isValid():
127 return QModelIndex()
128
129 if proxyIndex.internalId() == sys.maxsize:
130 bumpedItems = self.bumpedRows()
131 if proxyIndex.row() < bumpedItems:
132 return self.__treeModel.index(
133 proxyIndex.row(), proxyIndex.column(),
134 self.__treeModel.index(0, 0))
135 if (
136 bumpedItems <= self.MOVEDROWS and
137 bumpedItems == self.sourceModel().rowCount(
138 self.__treeModel.index(0, 0))
139 ):
140 bumpedItems -= 1
141 return self.__treeModel.index(proxyIndex.row() - bumpedItems,
142 proxyIndex.column())
143
144 historyIndex = self.__treeModel.sourceModel().index(
145 proxyIndex.internalId(), proxyIndex.column())
146 treeIndex = self.__treeModel.mapFromSource(historyIndex)
147 return treeIndex
148
149 def index(self, row, column, parent=None):
150 """
151 Public method to create an index.
152
153 @param row row number for the index (integer)
154 @param column column number for the index (integer)
155 @param parent index of the parent item (QModelIndex)
156 @return requested index (QModelIndex)
157 """
158 if parent is None:
159 parent = QModelIndex()
160
161 if (
162 row < 0 or
163 column < 0 or
164 column >= self.columnCount(parent) or
165 parent.column() > 0
166 ):
167 return QModelIndex()
168
169 if not parent.isValid():
170 return self.createIndex(row, column, sys.maxsize)
171
172 treeIndexParent = self.mapToSource(parent)
173
174 bumpedItems = 0
175 if treeIndexParent == self.sourceModel().index(0, 0):
176 bumpedItems = self.bumpedRows()
177 treeIndex = self.__treeModel.index(
178 row + bumpedItems, column, treeIndexParent)
179 historyIndex = self.__treeModel.mapToSource(treeIndex)
180 historyRow = historyIndex.row()
181 if historyRow == -1:
182 historyRow = treeIndex.row()
183 return self.createIndex(row, column, historyRow)
184
185 def parent(self, index):
186 """
187 Public method to get the parent index.
188
189 @param index index of item to get parent (QModelIndex)
190 @return index of parent (QModelIndex)
191 """
192 offset = index.internalId()
193 if offset == sys.maxsize or not index.isValid():
194 return QModelIndex()
195
196 historyIndex = self.__treeModel.sourceModel().index(
197 index.internalId(), 0)
198 treeIndex = self.__treeModel.mapFromSource(historyIndex)
199 treeIndexParent = treeIndex.parent()
200
201 sourceRow = self.sourceModel().mapToSource(treeIndexParent).row()
202 bumpedItems = self.bumpedRows()
203 if (
204 bumpedItems <= self.MOVEDROWS and
205 bumpedItems == self.sourceModel().rowCount(
206 self.sourceModel().index(0, 0))
207 ):
208 bumpedItems -= 1
209
210 return self.createIndex(bumpedItems + treeIndexParent.row(),
211 treeIndexParent.column(),
212 sourceRow)
213
214 def mimeData(self, indexes):
215 """
216 Public method to return the mime data.
217
218 @param indexes list of indexes (QModelIndexList)
219 @return mime data (QMimeData)
220 """
221 urls = []
222 for index in indexes:
223 url = index.data(HistoryModel.UrlRole)
224 urls.append(url)
225
226 mdata = QMimeData()
227 mdata.setUrls(urls)
228 return mdata
229
230
231 class HistoryMostVisitedMenuModel(QSortFilterProxyModel):
232 """
233 Class implementing a model to show the most visited history entries.
234 """
235 def __init__(self, sourceModel, parent=None):
236 """
237 Constructor
238
239 @param sourceModel reference to the source model (QAbstractItemModel)
240 @param parent reference to the parent object (QObject)
241 """
242 super().__init__(parent)
243
244 self.setDynamicSortFilter(True)
245 self.setSourceModel(sourceModel)
246
247 def lessThan(self, left, right):
248 """
249 Public method used to sort the displayed items.
250
251 @param left index of left item (QModelIndex)
252 @param right index of right item (QModelIndex)
253 @return true, if left is less than right (boolean)
254 """
255 from .HistoryFilterModel import HistoryFilterModel
256 frequency_L = self.sourceModel().data(
257 left, HistoryFilterModel.FrequencyRole)
258 dateTime_L = self.sourceModel().data(
259 left, HistoryModel.DateTimeRole)
260 frequency_R = self.sourceModel().data(
261 right, HistoryFilterModel.FrequencyRole)
262 dateTime_R = self.sourceModel().data(
263 right, HistoryModel.DateTimeRole)
264
265 # Sort results in descending frequency-derived score. If frequencies
266 # are equal, sort on most recently viewed
267 if frequency_R == frequency_L:
268 return dateTime_R < dateTime_L
269
270 return frequency_R < frequency_L
271
272
273 class HistoryMenu(E5ModelMenu):
274 """
275 Class implementing the history menu.
276
277 @signal openUrl(QUrl, str) emitted to open a URL in the current tab
278 @signal newTab(QUrl, str) emitted to open a URL in a new tab
279 @signal newBackgroundTab(QUrl, str) emitted to open a URL in a new
280 background tab
281 @signal newWindow(QUrl, str) emitted to open a URL in a new window
282 @signal newPrivateWindow(QUrl, str) emitted to open a URL in a new
283 private window
284 """
285 openUrl = pyqtSignal(QUrl, str)
286 newTab = pyqtSignal(QUrl, str)
287 newBackgroundTab = pyqtSignal(QUrl, str)
288 newWindow = pyqtSignal(QUrl, str)
289 newPrivateWindow = pyqtSignal(QUrl, str)
290
291 def __init__(self, parent=None, tabWidget=None):
292 """
293 Constructor
294
295 @param parent reference to the parent widget (QWidget)
296 @param tabWidget reference to the tab widget managing the browser
297 tabs (HelpTabWidget
298 """
299 E5ModelMenu.__init__(self, parent)
300
301 self.__tabWidget = tabWidget
302 self.__mw = parent
303
304 self.__historyManager = None
305 self.__historyMenuModel = None
306 self.__initialActions = []
307 self.__mostVisitedMenu = None
308
309 self.__closedTabsMenu = QMenu(self.tr("Closed Tabs"))
310 self.__closedTabsMenu.aboutToShow.connect(
311 self.__aboutToShowClosedTabsMenu)
312 self.__tabWidget.closedTabsManager().closedTabAvailable.connect(
313 self.__closedTabAvailable)
314
315 self.setMaxRows(7)
316
317 self.activated.connect(self.__activated)
318 self.setStatusBarTextRole(HistoryModel.UrlStringRole)
319
320 def __activated(self, idx):
321 """
322 Private slot handling the activated signal.
323
324 @param idx index of the activated item (QModelIndex)
325 """
326 if self._keyboardModifiers & Qt.KeyboardModifier.ControlModifier:
327 self.newTab.emit(
328 idx.data(HistoryModel.UrlRole),
329 idx.data(HistoryModel.TitleRole))
330 elif self._keyboardModifiers & Qt.KeyboardModifier.ShiftModifier:
331 self.newWindow.emit(
332 idx.data(HistoryModel.UrlRole),
333 idx.data(HistoryModel.TitleRole))
334 else:
335 self.openUrl.emit(
336 idx.data(HistoryModel.UrlRole),
337 idx.data(HistoryModel.TitleRole))
338
339 def prePopulated(self):
340 """
341 Public method to add any actions before the tree.
342
343 @return flag indicating if any actions were added (boolean)
344 """
345 if self.__historyManager is None:
346 from WebBrowser.WebBrowserWindow import WebBrowserWindow
347 self.__historyManager = WebBrowserWindow.historyManager()
348 self.__historyMenuModel = HistoryMenuModel(
349 self.__historyManager.historyTreeModel(), self)
350 self.setModel(self.__historyMenuModel)
351
352 # initial actions
353 for act in self.__initialActions:
354 self.addAction(act)
355 if len(self.__initialActions) != 0:
356 self.addSeparator()
357 self.setFirstSeparator(self.__historyMenuModel.bumpedRows())
358
359 return False
360
361 def postPopulated(self):
362 """
363 Public method to add any actions after the tree.
364 """
365 if len(self.__historyManager.history()) > 0:
366 self.addSeparator()
367
368 if self.__mostVisitedMenu is None:
369 self.__mostVisitedMenu = HistoryMostVisitedMenu(10, self)
370 self.__mostVisitedMenu.setTitle(self.tr("Most Visited"))
371 self.__mostVisitedMenu.openUrl.connect(self.openUrl)
372 self.__mostVisitedMenu.newTab.connect(self.newTab)
373 self.__mostVisitedMenu.newBackgroundTab.connect(
374 self.newBackgroundTab)
375 self.__mostVisitedMenu.newWindow.connect(self.newWindow)
376 self.__mostVisitedMenu.newPrivateWindow.connect(
377 self.newPrivateWindow)
378 self.addMenu(self.__mostVisitedMenu)
379 act = self.addMenu(self.__closedTabsMenu)
380 act.setIcon(UI.PixmapCache.getIcon("trash"))
381 act.setEnabled(self.__tabWidget.canRestoreClosedTab())
382 self.addSeparator()
383
384 act = self.addAction(UI.PixmapCache.getIcon("history"),
385 self.tr("Show All History..."))
386 act.triggered.connect(self.showHistoryDialog)
387 act = self.addAction(UI.PixmapCache.getIcon("historyClear"),
388 self.tr("Clear History..."))
389 act.triggered.connect(self.__clearHistoryDialog)
390
391 def setInitialActions(self, actions):
392 """
393 Public method to set the list of actions that should appear first in
394 the menu.
395
396 @param actions list of initial actions (list of QAction)
397 """
398 self.__initialActions = actions[:]
399 for act in self.__initialActions:
400 self.addAction(act)
401
402 def showHistoryDialog(self):
403 """
404 Public slot to show the history dialog.
405 """
406 from .HistoryDialog import HistoryDialog
407 dlg = HistoryDialog(self.__mw)
408 dlg.openUrl.connect(self.openUrl)
409 dlg.newTab.connect(self.newTab)
410 dlg.newBackgroundTab.connect(self.newBackgroundTab)
411 dlg.newWindow.connect(self.newWindow)
412 dlg.newPrivateWindow.connect(self.newPrivateWindow)
413 dlg.show()
414
415 def __clearHistoryDialog(self):
416 """
417 Private slot to clear the history.
418 """
419 if self.__historyManager is not None and E5MessageBox.yesNo(
420 self,
421 self.tr("Clear History"),
422 self.tr("""Do you want to clear the history?""")):
423 self.__historyManager.clear()
424 self.__tabWidget.clearClosedTabsList()
425
426 def __aboutToShowClosedTabsMenu(self):
427 """
428 Private slot to populate the closed tabs menu.
429 """
430 fm = self.__closedTabsMenu.fontMetrics()
431 try:
432 maxWidth = fm.horizontalAdvance('m') * 40
433 except AttributeError:
434 maxWidth = fm.width('m') * 40
435
436 import WebBrowser.WebBrowserWindow
437 self.__closedTabsMenu.clear()
438 for index, tab in enumerate(
439 self.__tabWidget.closedTabsManager().allClosedTabs()
440 ):
441 title = fm.elidedText(tab.title, Qt.TextElideMode.ElideRight,
442 maxWidth)
443 act = self.__closedTabsMenu.addAction(
444 WebBrowser.WebBrowserWindow.WebBrowserWindow.icon(tab.url),
445 title)
446 act.setData(index)
447 act.triggered.connect(
448 functools.partial(self.__tabWidget.restoreClosedTab, act))
449 self.__closedTabsMenu.addSeparator()
450 self.__closedTabsMenu.addAction(
451 self.tr("Restore All Closed Tabs"),
452 self.__tabWidget.restoreAllClosedTabs)
453 self.__closedTabsMenu.addAction(
454 self.tr("Clear List"),
455 self.__tabWidget.clearClosedTabsList)
456
457 def __closedTabAvailable(self, avail):
458 """
459 Private slot to handle changes of the availability of closed tabs.
460
461 @param avail flag indicating the availability of closed tabs (boolean)
462 """
463 self.__closedTabsMenu.setEnabled(avail)
464
465
466 class HistoryMostVisitedMenu(E5ModelMenu):
467 """
468 Class implementing the most visited history menu.
469
470 @signal openUrl(QUrl, str) emitted to open a URL in the current tab
471 @signal newTab(QUrl, str) emitted to open a URL in a new tab
472 @signal newBackgroundTab(QUrl, str) emitted to open a URL in a new
473 background tab
474 @signal newWindow(QUrl, str) emitted to open a URL in a new window
475 @signal newPrivateWindow(QUrl, str) emitted to open a URL in a new
476 private window
477 """
478 openUrl = pyqtSignal(QUrl, str)
479 newTab = pyqtSignal(QUrl, str)
480 newBackgroundTab = pyqtSignal(QUrl, str)
481 newWindow = pyqtSignal(QUrl, str)
482 newPrivateWindow = pyqtSignal(QUrl, str)
483
484 def __init__(self, count, parent=None):
485 """
486 Constructor
487
488 @param count maximum number of entries to be shown (integer)
489 @param parent reference to the parent widget (QWidget)
490 """
491 E5ModelMenu.__init__(self, parent)
492
493 self.__historyMenuModel = None
494
495 self.setMaxRows(count + 1)
496
497 self.setStatusBarTextRole(HistoryModel.UrlStringRole)
498
499 def __activated(self, idx):
500 """
501 Private slot handling the activated signal.
502
503 @param idx index of the activated item (QModelIndex)
504 """
505 if self._keyboardModifiers & Qt.KeyboardModifier.ControlModifier:
506 self.newTab.emit(
507 idx.data(HistoryModel.UrlRole),
508 idx.data(HistoryModel.TitleRole))
509 elif self._keyboardModifiers & Qt.KeyboardModifier.ShiftModifier:
510 self.newWindow.emit(
511 idx.data(HistoryModel.UrlRole),
512 idx.data(HistoryModel.TitleRole))
513 else:
514 self.openUrl.emit(
515 idx.data(HistoryModel.UrlRole),
516 idx.data(HistoryModel.TitleRole))
517
518 def prePopulated(self):
519 """
520 Public method to add any actions before the tree.
521
522 @return flag indicating if any actions were added (boolean)
523 """
524 if self.__historyMenuModel is None:
525 from WebBrowser.WebBrowserWindow import WebBrowserWindow
526 historyManager = WebBrowserWindow.historyManager()
527 self.__historyMenuModel = HistoryMostVisitedMenuModel(
528 historyManager.historyFilterModel(), self)
529 self.setModel(self.__historyMenuModel)
530 self.__historyMenuModel.sort(0)
531
532 return False

eric ide

mercurial