eric6/WebBrowser/History/HistoryMenu.py

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

eric ide

mercurial