eric6/Helpviewer/History/HistoryMenu.py

changeset 6942
2602857055c5
parent 6645
ad476851d7e0
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 newUrl(QUrl, str) emitted to open a URL in a new tab
268 """
269 openUrl = pyqtSignal(QUrl, str)
270 newUrl = pyqtSignal(QUrl, str)
271
272 def __init__(self, parent=None, tabWidget=None):
273 """
274 Constructor
275
276 @param parent reference to the parent widget (QWidget)
277 @param tabWidget reference to the tab widget managing the browser
278 tabs (HelpTabWidget
279 """
280 E5ModelMenu.__init__(self, parent)
281
282 self.__tabWidget = tabWidget
283
284 self.__historyManager = None
285 self.__historyMenuModel = None
286 self.__initialActions = []
287 self.__mostVisitedMenu = None
288
289 self.__closedTabsMenu = QMenu(self.tr("Closed Tabs"))
290 self.__closedTabsMenu.aboutToShow.connect(
291 self.__aboutToShowClosedTabsMenu)
292 self.__tabWidget.closedTabsManager().closedTabAvailable.connect(
293 self.__closedTabAvailable)
294
295 self.setMaxRows(7)
296
297 self.activated.connect(self.__activated)
298 self.setStatusBarTextRole(HistoryModel.UrlStringRole)
299
300 def __activated(self, idx):
301 """
302 Private slot handling the activated signal.
303
304 @param idx index of the activated item (QModelIndex)
305 """
306 if self._keyboardModifiers & Qt.ControlModifier:
307 self.newUrl.emit(
308 idx.data(HistoryModel.UrlRole),
309 idx.data(HistoryModel.TitleRole))
310 else:
311 self.openUrl.emit(
312 idx.data(HistoryModel.UrlRole),
313 idx.data(HistoryModel.TitleRole))
314
315 def prePopulated(self):
316 """
317 Public method to add any actions before the tree.
318
319 @return flag indicating if any actions were added (boolean)
320 """
321 if self.__historyManager is None:
322 import Helpviewer.HelpWindow
323 self.__historyManager = \
324 Helpviewer.HelpWindow.HelpWindow.historyManager()
325 self.__historyMenuModel = HistoryMenuModel(
326 self.__historyManager.historyTreeModel(), self)
327 self.setModel(self.__historyMenuModel)
328
329 # initial actions
330 for act in self.__initialActions:
331 self.addAction(act)
332 if len(self.__initialActions) != 0:
333 self.addSeparator()
334 self.setFirstSeparator(self.__historyMenuModel.bumpedRows())
335
336 return False
337
338 def postPopulated(self):
339 """
340 Public method to add any actions after the tree.
341 """
342 if len(self.__historyManager.history()) > 0:
343 self.addSeparator()
344
345 if self.__mostVisitedMenu is None:
346 self.__mostVisitedMenu = HistoryMostVisitedMenu(10, self)
347 self.__mostVisitedMenu.setTitle(self.tr("Most Visited"))
348 self.__mostVisitedMenu.openUrl.connect(self.openUrl)
349 self.__mostVisitedMenu.newUrl.connect(self.newUrl)
350 self.addMenu(self.__mostVisitedMenu)
351 act = self.addMenu(self.__closedTabsMenu)
352 act.setIcon(UI.PixmapCache.getIcon("trash.png"))
353 act.setEnabled(self.__tabWidget.canRestoreClosedTab())
354 self.addSeparator()
355
356 act = self.addAction(UI.PixmapCache.getIcon("history.png"),
357 self.tr("Show All History..."))
358 act.triggered.connect(self.__showHistoryDialog)
359 act = self.addAction(UI.PixmapCache.getIcon("historyClear.png"),
360 self.tr("Clear History..."))
361 act.triggered.connect(self.__clearHistoryDialog)
362
363 def setInitialActions(self, actions):
364 """
365 Public method to set the list of actions that should appear first in
366 the menu.
367
368 @param actions list of initial actions (list of QAction)
369 """
370 self.__initialActions = actions[:]
371 for act in self.__initialActions:
372 self.addAction(act)
373
374 def __showHistoryDialog(self):
375 """
376 Private slot to show the history dialog.
377 """
378 from .HistoryDialog import HistoryDialog
379 dlg = HistoryDialog(self)
380 dlg.newUrl.connect(self.newUrl)
381 dlg.openUrl.connect(self.openUrl)
382 dlg.show()
383
384 def __clearHistoryDialog(self):
385 """
386 Private slot to clear the history.
387 """
388 if self.__historyManager is not None and E5MessageBox.yesNo(
389 self,
390 self.tr("Clear History"),
391 self.tr("""Do you want to clear the history?""")):
392 self.__historyManager.clear()
393 self.__tabWidget.clearClosedTabsList()
394
395 def __aboutToShowClosedTabsMenu(self):
396 """
397 Private slot to populate the closed tabs menu.
398 """
399 fm = self.__closedTabsMenu.fontMetrics()
400 maxWidth = fm.width('m') * 40
401
402 import Helpviewer.HelpWindow
403 self.__closedTabsMenu.clear()
404 index = 0
405 for tab in self.__tabWidget.closedTabsManager().allClosedTabs():
406 title = fm.elidedText(tab.title, Qt.ElideRight, maxWidth)
407 act = self.__closedTabsMenu.addAction(
408 Helpviewer.HelpWindow.HelpWindow.icon(tab.url), title)
409 act.setData(index)
410 act.triggered.connect(
411 lambda: self.__tabWidget.restoreClosedTab(act))
412 index += 1
413 self.__closedTabsMenu.addSeparator()
414 self.__closedTabsMenu.addAction(
415 self.tr("Restore All Closed Tabs"),
416 self.__tabWidget.restoreAllClosedTabs)
417 self.__closedTabsMenu.addAction(
418 self.tr("Clear List"),
419 self.__tabWidget.clearClosedTabsList)
420
421 def __closedTabAvailable(self, avail):
422 """
423 Private slot to handle changes of the availability of closed tabs.
424
425 @param avail flag indicating the availability of closed tabs (boolean)
426 """
427 self.__closedTabsMenu.setEnabled(avail)
428
429
430 class HistoryMostVisitedMenu(E5ModelMenu):
431 """
432 Class implementing the most visited history menu.
433
434 @signal openUrl(QUrl, str) emitted to open a URL in the current tab
435 @signal newUrl(QUrl, str) emitted to open a URL in a new tab
436 """
437 openUrl = pyqtSignal(QUrl, str)
438 newUrl = pyqtSignal(QUrl, str)
439
440 def __init__(self, count, parent=None):
441 """
442 Constructor
443
444 @param count maximum number of entries to be shown (integer)
445 @param parent reference to the parent widget (QWidget)
446 """
447 E5ModelMenu.__init__(self, parent)
448
449 self.__historyMenuModel = None
450
451 self.setMaxRows(count + 1)
452
453 self.activated.connect(self.__activated)
454 self.setStatusBarTextRole(HistoryModel.UrlStringRole)
455
456 def __activated(self, idx):
457 """
458 Private slot handling the activated signal.
459
460 @param idx index of the activated item (QModelIndex)
461 """
462 if self._keyboardModifiers & Qt.ControlModifier:
463 self.newUrl.emit(
464 idx.data(HistoryModel.UrlRole),
465 idx.data(HistoryModel.TitleRole))
466 else:
467 self.openUrl.emit(
468 idx.data(HistoryModel.UrlRole),
469 idx.data(HistoryModel.TitleRole))
470
471 def prePopulated(self):
472 """
473 Public method to add any actions before the tree.
474
475 @return flag indicating if any actions were added (boolean)
476 """
477 if self.__historyMenuModel is None:
478 import Helpviewer.HelpWindow
479 historyManager = Helpviewer.HelpWindow.HelpWindow.historyManager()
480 self.__historyMenuModel = HistoryMostVisitedMenuModel(
481 historyManager.historyFilterModel(), self)
482 self.setModel(self.__historyMenuModel)
483 self.__historyMenuModel.sort(0)
484
485 return False

eric ide

mercurial