WebBrowser/TabManager/TabManagerWidget.py

changeset 4979
7ac0959842f9
child 5039
200f12184a43
equal deleted inserted replaced
4978:27fba2b81749 4979:7ac0959842f9
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2016 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a window for managing the web browser tabs.
8 """
9
10 #
11 # Modeled after the tab manager plug-in of Qupzilla
12 # Copyright (C) 2013 S. Razi Alavizadeh <s.r.alavizadeh@gmail.com>
13 #
14
15 from __future__ import unicode_literals
16
17 import os
18 import collections
19
20 from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QPoint, QTimer, QRect
21 from PyQt5.QtWidgets import QWidget, QVBoxLayout, QTreeWidget, \
22 QTreeWidgetItem, QMenu, QStyle, QAction
23
24 import E5Network
25 from E5Network import E5TldExtractor
26
27 from E5Gui.E5Application import e5App
28 from E5Gui.E5ClickableLabel import E5ClickableLabel
29
30 import Utilities
31 import UI.PixmapCache
32 import Preferences
33
34
35 class TabManagerWidget(QWidget):
36 """
37 Class implementing a window for managing the web browser tabs.
38
39 @signal groupTypeChanged(int) emitted when the 'Group By' value was changed
40 """
41 GroupByWindow = 0
42 GroupByDomain = 1
43 GroupByHost = 2
44
45 WebBrowserRole = Qt.UserRole + 1
46 WebWindowRole = Qt.UserRole + 2
47
48 groupTypeChanged = pyqtSignal(int)
49
50 _tldExtractor = None
51
52 def __init__(self, mainWindow, parent=None, defaultWidget=False):
53 """
54 Constructor
55
56 @param mainWindow reference to the main window
57 @type WebBrowserWindow
58 @param parent reference to the parent widget
59 @type QWidget
60 @param defaultWidget flag indicating the default widget
61 @type bool
62 """
63 super(TabManagerWidget, self).__init__(parent)
64 self.setWindowFlags(Qt.Window)
65
66 self.__layout = QVBoxLayout(self)
67 self.__layout.setContentsMargins(0, 0, 0, 0)
68 self.__tree = QTreeWidget(self)
69 self.__tree.setHeaderHidden(True)
70 self.__tree.setExpandsOnDoubleClick(False)
71 self.__tree.setContextMenuPolicy(Qt.CustomContextMenu)
72 self.__layout.addWidget(self.__tree)
73
74 self.setWindowTitle(self.tr("Tab Manager"))
75
76 self.__mw = mainWindow
77 self.__page = None
78
79 self.__isRefreshing = False
80 self.__refreshBlocked = False
81 self.__waitForRefresh = False
82 self.__isDefaultWidget = defaultWidget
83 self.__groupType = Preferences.getWebBrowser("TabManagerGroupByType")
84
85 if TabManagerWidget._tldExtractor is None:
86 TabManagerWidget._tldExtractor = E5TldExtractor.instance()
87 TabManagerWidget._tldExtractor.setDataSearchPaths([
88 os.path.join(Utilities.getConfigDir(), "web_browser")])
89
90 self.__tree.itemDoubleClicked.connect(self.__itemDoubleClicked)
91 self.__tree.customContextMenuRequested.connect(
92 self.__customContextMenuRequested)
93
94 self.resize(400, 600)
95
96 def closeSelectedBrowsers(self, browsersDict):
97 """
98 Public method to close the selected browsers.
99
100 @param browsersDict dictionary containing the browsers per window
101 @type dict with WebBrowserWindow as key and list of WebBrowserView
102 as value
103 """
104 if not browsersDict:
105 return
106
107 for mainWindow in browsersDict:
108 tabWidget = mainWindow.tabWidget()
109 for browser in browsersDict[mainWindow]:
110 tabWidget.closeBrowserAt(tabWidget.indexOf(browser))
111
112 def bookmarkSelectedBrowsers(self, browsersDict):
113 """
114 Public method to bookmark the selected browsers.
115
116 @param browsersDict dictionary containing the browsers per window
117 @type dict with WebBrowserWindow as key and list of WebBrowserView
118 as value
119 """
120 if not browsersDict:
121 return
122
123 from ..Bookmarks.BookmarkNode import BookmarkNode
124 from ..Bookmarks.AddBookmarkDialog import AddBookmarkDialog
125
126 dlg = AddBookmarkDialog()
127 dlg.setFolder(True)
128 dlg.setTitle(self.tr("Saved Tabs"))
129 dlg.exec_()
130
131 folder = dlg.addedNode()
132 if folder is None:
133 return
134
135 for mainWin in browsersDict:
136 for browser in browsersDict[mainWin]:
137 if not browser.url().isEmpty() and \
138 not browser.url().scheme() == "eric":
139 bookmark = BookmarkNode(BookmarkNode.Bookmark)
140 bookmark.url = bytes(browser.url().toEncoded()).decode()
141 bookmark.title = browser.title()
142
143 self.__mw.bookmarksManager().addBookmark(folder, bookmark)
144
145 def __setGroupType(self, type):
146 """
147 Private method to set the 'Group By' type.
148
149 @param type 'Group By' type to be set
150 @type int (0 - 2)
151 """
152 self.__groupType = type
153 Preferences.setWebBrowser("TabManagerGroupByType", type)
154
155 def domainFromUrl(self, url, useHostName=False):
156 """
157 Public method to extract the domain from an URL.
158
159 @param url URL to extract the domain from
160 @type QUrl
161 @param useHostName flag indicating to use the host name
162 @type bool
163 @return domain name
164 @rtype str
165 """
166 appendStr = ":"
167 urlString = url.toString()
168
169 if url.scheme() == "file":
170 return self.tr("Local File System:")
171 elif url.scheme() == "eric" or not urlString:
172 return self.tr("eric Web Browser:")
173 elif url.scheme() == "ftp":
174 appendStr = self.tr(" [FTP]:")
175
176 host = url.host()
177 if not host:
178 return urlString + appendStr
179
180 if useHostName or E5Network.isValidAddress(host):
181 if host.lower().startswith("www."):
182 host = host[4:]
183 else:
184 registeredDomain = \
185 TabManagerWidget._tldExtractor.registrableDomain(host)
186 if registeredDomain:
187 host = registeredDomain
188
189 return host + appendStr
190
191 def delayedRefreshTree(self, page=None):
192 """
193 Public slot to do a delyed refresh of the tree.
194
195 @param page reference to the web page
196 @type WebBrowserPage
197 """
198 if self.__refreshBlocked or self.__waitForRefresh:
199 return
200
201 if self.__isRefreshing and not page:
202 return
203
204 self.__page = page
205 self.__waitForRefresh = True
206 QTimer.singleShot(50, self.__refreshTree)
207
208 def changeGroupType(self, act):
209 """
210 Public slot to change the 'Group By' type.
211
212 @param act reference to the action that was triggered
213 @type QAction
214 """
215 if act is None:
216 return
217
218 type = act.data()
219 if self.__groupType != type:
220 self.__setGroupType(type)
221 self.delayedRefreshTree()
222 self.groupTypeChanged.emit(self.__groupType)
223
224 def __createEmptyItem(self, parent=None, addToTree=True):
225 """
226 Private method to create an empty tree item.
227
228 @param parent reference to the parent item
229 @type QTreeWidgetItem or QTreeWidget
230 @param addToTree flag indicating to add the item to the tree
231 @type bool
232 @return created item
233 @rtype QTreeWidgetItem
234 """
235 if addToTree:
236 if parent:
237 parentItem = parent
238 else:
239 parentItem = self.__tree.invisibleRootItem()
240 else:
241 parentItem = None
242 itm = QTreeWidgetItem(parentItem)
243 flags = itm.flags()
244 if parent:
245 flags |= Qt.ItemIsUserCheckable
246 else:
247 flags |= Qt.ItemIsUserCheckable | Qt.ItemIsTristate
248 itm.setFlags(itm.flags() | flags)
249 itm.setCheckState(0, Qt.Unchecked)
250
251 return itm
252
253 def __groupByDomainName(self, useHostName=False):
254 """
255 Private method to group the tree items by domain name.
256
257 @param useHostName flag indicating to use the host name
258 @type bool
259 """
260 windows = self.__mw.mainWindows()
261
262 tabsGroupedByDomain = {}
263
264 for mainWin in windows:
265 for browser in mainWin.tabWidget().browsers():
266 if self.__page == browser.page():
267 self.__page = None
268 continue
269 domain = self.domainFromUrl(browser.url(), useHostName)
270
271 if domain not in tabsGroupedByDomain:
272 groupItem = self.__createEmptyItem(None, False)
273 groupItem.setText(0, domain)
274 groupItem.setToolTip(0, domain)
275 font = groupItem.font(0)
276 font.setBold(True)
277 groupItem.setFont(0, font)
278 tabsGroupedByDomain[domain] = groupItem
279 groupItem = tabsGroupedByDomain[domain]
280
281 tabItem = self.__createEmptyItem(groupItem)
282 if browser == mainWin.tabWidget().currentBrowser():
283 font = tabItem.font(0)
284 font.setBold(True)
285 tabItem.setFont(0, font)
286 if not browser.isLoading():
287 tabItem.setIcon(0, browser.icon())
288 else:
289 tabItem.setIcon(0, UI.PixmapCache.getIcon("loading.png"))
290 tabItem.setText(0, browser.title())
291 tabItem.setToolTip(0, browser.title())
292
293 tabItem.setData(0, TabManagerWidget.WebBrowserRole, browser)
294 tabItem.setData(0, TabManagerWidget.WebWindowRole, mainWin)
295
296 self.__makeWebBrowserViewConnections(browser)
297
298 self.__tree.insertTopLevelItems(0, tabsGroupedByDomain.values())
299
300 def __groupByWindow(self):
301 """
302 Private method to group the tree items by window.
303 """
304 windows = self.__mw.mainWindows()
305
306 self.__isRefreshing = True
307
308 winCount = 0
309 for mainWin in windows:
310 winCount += 1
311 winItem = self.__createEmptyItem()
312 winItem.setText(0, self.tr("Window {0}").format(winCount))
313 winItem.setToolTip(0, self.tr("Double click to switch"))
314 if mainWin == self.__mw:
315 font = winItem.font(0)
316 font.setBold(True)
317 winItem.setFont(0, font)
318 winItem.setData(0, TabManagerWidget.WebWindowRole, mainWin)
319
320 for browser in mainWin.tabWidget().browsers():
321 if self.__page == browser.page():
322 self.__page = None
323 continue
324
325 tabItem = self.__createEmptyItem(winItem)
326 if browser == mainWin.tabWidget().currentBrowser():
327 font = tabItem.font(0)
328 font.setBold(True)
329 tabItem.setFont(0, font)
330 if not browser.isLoading():
331 tabItem.setIcon(0, browser.icon())
332 else:
333 tabItem.setIcon(0, UI.PixmapCache.getIcon("loading.png"))
334 tabItem.setText(0, browser.title())
335 tabItem.setToolTip(0, browser.title())
336
337 tabItem.setData(0, TabManagerWidget.WebBrowserRole, browser)
338 tabItem.setData(0, TabManagerWidget.WebWindowRole, mainWin)
339
340 self.__makeWebBrowserViewConnections(browser)
341
342 def __makeWebBrowserViewConnections(self, view):
343 """
344 Private method to create the signal connections to the web view.
345
346 @param view reference to the web view
347 @type WebBrowserView
348 """
349 if view:
350 view.page().loadFinished.connect(self.delayedRefreshTree)
351 view.page().loadStarted.connect(self.delayedRefreshTree)
352 view.titleChanged.connect(self.delayedRefreshTree)
353 view.iconChanged.connect(self.delayedRefreshTree)
354
355 @pyqtSlot()
356 def __refreshTree(self):
357 """
358 Private slot to referesh the tree.
359 """
360 if self.__refreshBlocked:
361 return
362
363 if self.__isRefreshing and not self.__page:
364 return
365
366 # store selected items
367 selectedBrowsers = []
368 for index in range(self.__tree.topLevelItemCount()):
369 winItem = self.__tree.topLevelItem(index)
370 if winItem.checkState(0) == Qt.Unchecked:
371 continue
372
373 for row in range(winItem.childCount()):
374 tabItem = winItem.child(row)
375 if tabItem.checkState(0) == Qt.Unchecked:
376 continue
377 selectedBrowsers.append(
378 tabItem.data(0, TabManagerWidget.WebBrowserRole))
379
380 self.__tree.clear()
381
382 if self.__groupType == TabManagerWidget.GroupByHost:
383 self.__groupByDomainName(True)
384 elif self.__groupType == TabManagerWidget.GroupByDomain:
385 self.__groupByDomainName()
386 else:
387 # default is group by window
388 self.__setGroupType(TabManagerWidget.GroupByWindow)
389 self.__groupByWindow()
390
391 # restore selected items
392 for index in range(self.__tree.topLevelItemCount()):
393 winItem = self.__tree.topLevelItem(index)
394
395 for row in range(winItem.childCount()):
396 tabItem = winItem.child(row)
397 if tabItem.data(0, TabManagerWidget.WebBrowserRole) in \
398 selectedBrowsers:
399 tabItem.setCheckState(0, Qt.Checked)
400
401 self.__tree.expandAll()
402 self.__isRefreshing = False
403 self.__waitForRefresh = False
404
405 @pyqtSlot()
406 def __processActions(self):
407 """
408 Private slot to process the actions.
409 """
410 if self.sender() is None:
411 return
412
413 self.__refreshBlocked = True
414
415 selectedBrowsers = collections.defaultdict(list)
416
417 command = self.sender().objectName()
418
419 for index in range(self.__tree.topLevelItemCount()):
420 winItem = self.__tree.topLevelItem(index)
421 if winItem.checkState(0) == Qt.Unchecked:
422 continue
423
424 for row in range(winItem.childCount()):
425 tabItem = winItem.child(row)
426 if tabItem.checkState(0) == Qt.Unchecked:
427 continue
428
429 mainWin = tabItem.data(0, TabManagerWidget.WebWindowRole)
430 browser = tabItem.data(0, TabManagerWidget.WebBrowserRole)
431
432 selectedBrowsers[mainWin].append(browser)
433
434 winItem.setCheckState(0, Qt.Unchecked)
435
436 if selectedBrowsers:
437 if command == "closeSelection":
438 self.closeSelectedBrowsers(selectedBrowsers)
439 elif command == "bookmarkSelection":
440 self.bookmarkSelectedBrowsers(selectedBrowsers)
441
442 self.__refreshBlocked = False
443 self.delayedRefreshTree()
444
445 @pyqtSlot(QTreeWidgetItem, int)
446 def __itemDoubleClicked(self, itm, column):
447 """
448 Private slot to handle double clicking a tree item.
449
450 @param itm reference to the item having been double clicked
451 @type QTreeWidgetItem
452 @param column column of the double click
453 @type int
454 """
455 if not itm:
456 return
457
458 mainWin = itm.data(0, TabManagerWidget.WebWindowRole)
459 browser = itm.data(0, TabManagerWidget.WebBrowserRole)
460
461 if not mainWin:
462 return
463
464 if mainWin.isMinimized():
465 mainWin.showNormal()
466 else:
467 mainWin.show()
468 mainWin.activateWindow()
469 mainWin.raise_()
470 mainWin.setFocus()
471
472 tabWidget = mainWin.tabWidget()
473 if browser and browser != tabWidget.currentWidget():
474 tabWidget.setCurrentWidget(browser)
475 browser.setFocus()
476
477 @pyqtSlot()
478 def __isBrowserSelected(self):
479 """
480 Private slot to check, if any browser entry is selected.
481
482 @return flag indicating the existence of a selected entry
483 @rtype bool
484 """
485 selected = False
486 for topRow in range(self.__tree.topLevelItemCount()):
487 topItm = self.__tree.topLevelItem(topRow)
488 if topItm.checkState(0) != Qt.Unchecked:
489 selected = True
490 break
491
492 return selected
493
494 @pyqtSlot(QPoint)
495 def __customContextMenuRequested(self, pos):
496 """
497 Private slot to show the context menu.
498
499 @param pos position the menu should be shown at
500 @type QPoint
501 """
502 menu = QMenu()
503 groupTypeSubMenu = QMenu(self.tr("Group by"))
504 act = groupTypeSubMenu.addAction(self.tr("&Window"))
505 act.setData(TabManagerWidget.GroupByWindow)
506 act.setCheckable(True)
507 act.setChecked(self.__groupType == TabManagerWidget.GroupByWindow)
508
509 act = groupTypeSubMenu.addAction(self.tr("&Domain"))
510 act.setData(TabManagerWidget.GroupByDomain)
511 act.setCheckable(True)
512 act.setChecked(self.__groupType == TabManagerWidget.GroupByDomain)
513
514 act = groupTypeSubMenu.addAction(self.tr("&Host"))
515 act.setData(TabManagerWidget.GroupByHost)
516 act.setCheckable(True)
517 act.setChecked(self.__groupType == TabManagerWidget.GroupByHost)
518 groupTypeSubMenu.triggered.connect(self.changeGroupType)
519
520 menu.addMenu(groupTypeSubMenu)
521
522 menu.addSeparator()
523
524 if self.__isBrowserSelected():
525 menu.addAction(
526 UI.PixmapCache.getIcon("bookmark22.png"),
527 self.tr("&Bookmark checked tabs"),
528 self.__processActions).setObjectName("bookmarkSelection")
529 menu.addAction(
530 UI.PixmapCache.getIcon("tabClose.png"),
531 self.tr("&Close checked tabs"),
532 self.__processActions).setObjectName("closeSelection")
533
534 menu.exec_(self.__tree.viewport().mapToGlobal(pos))
535
536 def mainWindowCreated(self, mainWin, refresh=True):
537 """
538 Public method to act on the creation of a new web browser window.
539
540 @param mainWin reference to the web browser window
541 @type WebBrowserWindow
542 @param refresh flag indicating to refresh the widget
543 @type bool
544 """
545 mainWin.webBrowserWindowClosed.connect(self.delayedRefreshTree)
546 mainWin.webBrowserWindowOpened.connect(self.mainWindowCreated)
547 mainWin.webBrowserOpened.connect(self.delayedRefreshTree)
548 mainWin.webBrowserClosed.connect(self.delayedRefreshTree)
549 mainWin.tabWidget().currentUrlChanged.connect(self.delayedRefreshTree)
550 mainWin.tabWidget().currentChanged.connect(self.delayedRefreshTree)
551
552 def createStatusBarIcon(self):
553 """
554 Public method to create a status bar icon.
555
556 @return generated icon
557 @rtype E5ClickableLabel
558 """
559 icon = E5ClickableLabel()
560 icon.setPixmap(
561 UI.PixmapCache.getPixmap("tabManager.png").scaled(16, 16))
562 icon.setToolTip(self.tr("Show Tab Manager"))
563 icon.clicked.connect(self.raiseTabManager)
564
565 return icon
566
567 def raiseTabManager(self):
568 """
569 Public slot to show the tab manager.
570 """
571 window = None
572 icon = self.sender()
573 if icon:
574 if isinstance(icon, E5ClickableLabel):
575 window = icon.window()
576 elif isinstance(icon, QAction):
577 window = icon.parentWidget()
578
579 if window is not None:
580 titleBarHeight = self.style().pixelMetric(QStyle.PM_TitleBarHeight)
581
582 y = max(0, window.frameGeometry().top() + titleBarHeight + 1)
583
584 desktop = e5App().desktop()
585 desktopGeometry = desktop.availableGeometry(self)
586 windowFrameGeometry = window.frameGeometry()
587 if (desktopGeometry.width() - windowFrameGeometry.right() - 1 >
588 self.frameGeometry().width()):
589 x = windowFrameGeometry.right() + 1
590 else:
591 x = windowFrameGeometry.x() - 1 \
592 - self.frameGeometry().width()
593
594 newGeo = QRect(x, y, self.width(), window.height())
595 self.setGeometry(newGeo)
596
597 self.activateWindow()
598 self.showNormal()
599 self.raise_()

eric ide

mercurial