eric6/WebBrowser/WebBrowserTabWidget.py

changeset 6942
2602857055c5
parent 6656
273a2130adbe
child 6999
f23e546a7eb9
diff -r f99d60d6b59b -r 2602857055c5 eric6/WebBrowser/WebBrowserTabWidget.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/WebBrowser/WebBrowserTabWidget.py	Sun Apr 14 15:09:21 2019 +0200
@@ -0,0 +1,1285 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2010 - 2019 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the central widget showing the web pages.
+"""
+
+from __future__ import unicode_literals
+
+import os
+
+from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QUrl, QDir, QFile, \
+    QFileDevice, QTemporaryFile
+from PyQt5.QtGui import QIcon, QPixmap, QPainter
+from PyQt5.QtWidgets import QWidget, QHBoxLayout, QMenu, QToolButton, \
+    QDialog, QApplication
+from PyQt5.QtPrintSupport import QPrinter, QPrintDialog, QAbstractPrintDialog
+
+from E5Gui.E5TabWidget import E5TabWidget
+from E5Gui import E5MessageBox
+from E5Gui.E5Application import e5App
+
+from .WebBrowserView import WebBrowserView
+from .WebBrowserPage import WebBrowserPage
+from .Tools import WebBrowserTools
+from .Tools import FilePrinter
+from . import WebInspector
+
+import UI.PixmapCache
+
+import Utilities
+import Preferences
+import Globals
+from Globals import qVersionTuple
+
+from eric6config import getConfig
+
+
+class WebBrowserTabWidget(E5TabWidget):
+    """
+    Class implementing the central widget showing the web pages.
+    
+    @signal sourceChanged(WebBrowserView, QUrl) emitted after the URL of a
+        browser has changed
+    @signal currentUrlChanged(QUrl) emitted after the URL of the current
+        browser has changed
+    @signal titleChanged(WebBrowserView, str) emitted after the title of a
+        browser has changed
+    @signal showMessage(str) emitted to show a message in the main window
+        status bar
+    @signal browserOpened(QWidget) emitted after a new browser was created
+    @signal browserClosed(QWidget) emitted after a browser was closed
+    @signal browserZoomValueChanged(int) emitted to signal a change of the
+        current browser's zoom level
+    """
+    sourceChanged = pyqtSignal(WebBrowserView, QUrl)
+    currentUrlChanged = pyqtSignal(QUrl)
+    titleChanged = pyqtSignal(WebBrowserView, str)
+    showMessage = pyqtSignal(str)
+    browserOpened = pyqtSignal(QWidget)
+    browserClosed = pyqtSignal(QWidget)
+    browserZoomValueChanged = pyqtSignal(int)
+    
+    def __init__(self, parent):
+        """
+        Constructor
+        
+        @param parent reference to the parent widget (QWidget)
+        """
+        super(WebBrowserTabWidget, self).__init__(parent, dnd=True)
+        
+        from .WebBrowserTabBar import WebBrowserTabBar
+        self.__tabBar = WebBrowserTabBar(self)
+        self.setCustomTabBar(True, self.__tabBar)
+        
+        self.__mainWindow = parent
+        
+        self.setUsesScrollButtons(True)
+        self.setDocumentMode(True)
+        self.setElideMode(Qt.ElideNone)
+        
+        from .ClosedTabsManager import ClosedTabsManager
+        self.__closedTabsManager = ClosedTabsManager(self)
+        self.__closedTabsManager.closedTabAvailable.connect(
+            self.__closedTabAvailable)
+        
+        from .UrlBar.StackedUrlBar import StackedUrlBar
+        self.__stackedUrlBar = StackedUrlBar(self)
+        self.__tabBar.tabMoved.connect(self.__stackedUrlBar.moveBar)
+        
+        self.__tabContextMenuIndex = -1
+        self.currentChanged[int].connect(self.__currentChanged)
+        self.setTabContextMenuPolicy(Qt.CustomContextMenu)
+        self.customTabContextMenuRequested.connect(self.__showContextMenu)
+        
+        self.__rightCornerWidget = QWidget(self)
+        self.__rightCornerWidgetLayout = QHBoxLayout(self.__rightCornerWidget)
+        self.__rightCornerWidgetLayout.setContentsMargins(0, 0, 0, 0)
+        self.__rightCornerWidgetLayout.setSpacing(0)
+        
+        self.__navigationMenu = QMenu(self)
+        self.__navigationMenu.aboutToShow.connect(self.__showNavigationMenu)
+        self.__navigationMenu.triggered.connect(self.__navigationMenuTriggered)
+        
+        self.__navigationButton = QToolButton(self)
+        self.__navigationButton.setIcon(
+            UI.PixmapCache.getIcon("1downarrow.png"))
+        self.__navigationButton.setToolTip(
+            self.tr("Show a navigation menu"))
+        self.__navigationButton.setPopupMode(QToolButton.InstantPopup)
+        self.__navigationButton.setMenu(self.__navigationMenu)
+        self.__navigationButton.setEnabled(False)
+        self.__rightCornerWidgetLayout.addWidget(self.__navigationButton)
+        
+        self.__closedTabsMenu = QMenu(self)
+        self.__closedTabsMenu.aboutToShow.connect(
+            self.__aboutToShowClosedTabsMenu)
+        
+        self.__closedTabsButton = QToolButton(self)
+        self.__closedTabsButton.setIcon(UI.PixmapCache.getIcon("trash.png"))
+        self.__closedTabsButton.setToolTip(
+            self.tr("Show a navigation menu for closed tabs"))
+        self.__closedTabsButton.setPopupMode(QToolButton.InstantPopup)
+        self.__closedTabsButton.setMenu(self.__closedTabsMenu)
+        self.__closedTabsButton.setEnabled(False)
+        self.__rightCornerWidgetLayout.addWidget(self.__closedTabsButton)
+        
+        self.__closeButton = QToolButton(self)
+        self.__closeButton.setIcon(UI.PixmapCache.getIcon("close.png"))
+        self.__closeButton.setToolTip(
+            self.tr("Close the current web browser"))
+        self.__closeButton.setEnabled(False)
+        self.__closeButton.clicked.connect(self.closeBrowser)
+        self.__rightCornerWidgetLayout.addWidget(self.__closeButton)
+        if Preferences.getUI("SingleCloseButton") or \
+           not hasattr(self, 'setTabsClosable'):
+            self.__closeButton.show()
+        else:
+            self.setTabsClosable(True)
+            self.tabCloseRequested.connect(self.closeBrowserAt)
+            self.__closeButton.hide()
+        
+        self.setCornerWidget(self.__rightCornerWidget, Qt.TopRightCorner)
+        
+        self.__newTabButton = QToolButton(self)
+        self.__newTabButton.setIcon(UI.PixmapCache.getIcon("plus.png"))
+        self.__newTabButton.setToolTip(
+            self.tr("Open a new web browser tab"))
+        self.setCornerWidget(self.__newTabButton, Qt.TopLeftCorner)
+        self.__newTabButton.clicked.connect(self.__newBrowser)
+        
+        self.__initTabContextMenu()
+        
+        self.__historyCompleter = None
+        
+        self.__pdfPrinter = None
+    
+    def __initTabContextMenu(self):
+        """
+        Private method to create the tab context menu.
+        """
+        self.__tabContextMenu = QMenu(self)
+        self.tabContextNewAct = self.__tabContextMenu.addAction(
+            UI.PixmapCache.getIcon("tabNew.png"),
+            self.tr('New Tab'), self.newBrowser)
+        self.__tabContextMenu.addSeparator()
+        self.leftMenuAct = self.__tabContextMenu.addAction(
+            UI.PixmapCache.getIcon("1leftarrow.png"),
+            self.tr('Move Left'), self.__tabContextMenuMoveLeft)
+        self.rightMenuAct = self.__tabContextMenu.addAction(
+            UI.PixmapCache.getIcon("1rightarrow.png"),
+            self.tr('Move Right'), self.__tabContextMenuMoveRight)
+        self.__tabContextMenu.addSeparator()
+        self.tabContextCloneAct = self.__tabContextMenu.addAction(
+            self.tr("Duplicate Page"), self.__tabContextMenuClone)
+        self.__tabContextMenu.addSeparator()
+        self.tabContextCloseAct = self.__tabContextMenu.addAction(
+            UI.PixmapCache.getIcon("tabClose.png"),
+            self.tr('Close'), self.__tabContextMenuClose)
+        self.tabContextCloseOthersAct = self.__tabContextMenu.addAction(
+            UI.PixmapCache.getIcon("tabCloseOther.png"),
+            self.tr("Close Others"), self.__tabContextMenuCloseOthers)
+        self.__tabContextMenu.addAction(
+            self.tr('Close All'), self.closeAllBrowsers)
+        self.__tabContextMenu.addSeparator()
+        if qVersionTuple() >= (5, 8, 0) or (
+           not Globals.isWindowsPlatform() and qVersionTuple() < (5, 7, 0)):
+            self.__tabContextMenu.addAction(
+                UI.PixmapCache.getIcon("printPreview.png"),
+                self.tr('Print Preview'), self.__tabContextMenuPrintPreview)
+        if qVersionTuple() >= (5, 8, 0) or (
+           not Globals.isWindowsPlatform() or qVersionTuple() >= (5, 7, 0)):
+            self.__tabContextMenu.addAction(
+                UI.PixmapCache.getIcon("print.png"),
+                self.tr('Print'), self.__tabContextMenuPrint)
+        if Globals.isLinuxPlatform() or qVersionTuple() >= (5, 7, 0):
+            self.__tabContextMenu.addAction(
+                UI.PixmapCache.getIcon("printPdf.png"),
+                self.tr('Print as PDF'), self.__tabContextMenuPrintPdf)
+        self.__tabContextMenu.addSeparator()
+        if hasattr(WebBrowserPage, "isAudioMuted"):
+            self.__audioAct = self.__tabContextMenu.addAction(
+                "", self.__tabContextMenuAudioMute)
+            self.__tabContextMenu.addSeparator()
+        else:
+            self.__audioAct = None
+        self.__tabContextMenu.addAction(
+            UI.PixmapCache.getIcon("reload.png"),
+            self.tr('Reload All'), self.reloadAllBrowsers)
+        self.__tabContextMenu.addSeparator()
+        self.__tabContextMenu.addAction(
+            UI.PixmapCache.getIcon("addBookmark.png"),
+            self.tr('Bookmark All Tabs'), self.__mainWindow.bookmarkAll)
+        
+        self.__tabBackContextMenu = QMenu(self)
+        self.__tabBackContextMenu.addAction(
+            self.tr('Close All'), self.closeAllBrowsers)
+        self.__tabBackContextMenu.addAction(
+            UI.PixmapCache.getIcon("reload.png"),
+            self.tr('Reload All'), self.reloadAllBrowsers)
+        self.__tabBackContextMenu.addAction(
+            UI.PixmapCache.getIcon("addBookmark.png"),
+            self.tr('Bookmark All Tabs'), self.__mainWindow.bookmarkAll)
+        self.__tabBackContextMenu.addSeparator()
+        self.__restoreClosedTabAct = self.__tabBackContextMenu.addAction(
+            UI.PixmapCache.getIcon("trash.png"),
+            self.tr('Restore Closed Tab'))
+        self.__restoreClosedTabAct.setEnabled(False)
+        self.__restoreClosedTabAct.setData(0)
+        self.__restoreClosedTabAct.triggered.connect(
+            lambda: self.restoreClosedTab(self.__restoreClosedTabAct))
+    
+    def __showContextMenu(self, coord, index):
+        """
+        Private slot to show the tab context menu.
+        
+        @param coord the position of the mouse pointer (QPoint)
+        @param index index of the tab the menu is requested for (integer)
+        """
+        coord = self.mapToGlobal(coord)
+        if index == -1:
+            self.__tabBackContextMenu.popup(coord)
+        else:
+            self.__tabContextMenuIndex = index
+            self.leftMenuAct.setEnabled(index > 0)
+            self.rightMenuAct.setEnabled(index < self.count() - 1)
+            
+            self.tabContextCloseOthersAct.setEnabled(self.count() > 1)
+            
+            if self.__audioAct is not None:
+                if self.widget(self.__tabContextMenuIndex).page()\
+                        .isAudioMuted():
+                    self.__audioAct.setText(self.tr("Unmute Tab"))
+                    self.__audioAct.setIcon(
+                        UI.PixmapCache.getIcon("audioVolumeHigh.png"))
+                else:
+                    self.__audioAct.setText(self.tr("Mute Tab"))
+                    self.__audioAct.setIcon(
+                        UI.PixmapCache.getIcon("audioVolumeMuted.png"))
+            
+            self.__tabContextMenu.popup(coord)
+    
+    def __tabContextMenuMoveLeft(self):
+        """
+        Private method to move a tab one position to the left.
+        """
+        self.moveTab(self.__tabContextMenuIndex,
+                     self.__tabContextMenuIndex - 1)
+    
+    def __tabContextMenuMoveRight(self):
+        """
+        Private method to move a tab one position to the right.
+        """
+        self.moveTab(self.__tabContextMenuIndex,
+                     self.__tabContextMenuIndex + 1)
+    
+    def __tabContextMenuClone(self):
+        """
+        Private method to clone the selected tab.
+        """
+        idx = self.__tabContextMenuIndex
+        if idx < 0:
+            idx = self.currentIndex()
+        if idx < 0 or idx > self.count():
+            return
+        
+        url = self.widget(idx).url()
+        self.newBrowser(url)
+    
+    def __tabContextMenuClose(self):
+        """
+        Private method to close the selected tab.
+        """
+        self.closeBrowserAt(self.__tabContextMenuIndex)
+    
+    def __tabContextMenuCloseOthers(self):
+        """
+        Private slot to close all other tabs.
+        """
+        index = self.__tabContextMenuIndex
+        for i in list(range(self.count() - 1, index, -1)) + \
+                list(range(index - 1, -1, -1)):
+            self.closeBrowserAt(i)
+    
+    def __tabContextMenuPrint(self):
+        """
+        Private method to print the selected tab.
+        """
+        browser = self.widget(self.__tabContextMenuIndex)
+        self.printBrowser(browser)
+    
+    def __tabContextMenuPrintPdf(self):
+        """
+        Private method to print the selected tab as PDF.
+        """
+        browser = self.widget(self.__tabContextMenuIndex)
+        self.printBrowserPdf(browser)
+    
+    def __tabContextMenuPrintPreview(self):
+        """
+        Private method to show a print preview of the selected tab.
+        """
+        browser = self.widget(self.__tabContextMenuIndex)
+        self.printPreviewBrowser(browser)
+    
+    def __tabContextMenuAudioMute(self):
+        """
+        Private method to mute or unmute the selected tab.
+        """
+        page = self.widget(self.__tabContextMenuIndex).page()
+        muted = page.isAudioMuted()
+        page.setAudioMuted(not muted)
+    
+    @pyqtSlot(bool)
+    def __recentlyAudibleChanged(self, recentlyAudible, page):
+        """
+        Private slot to react on the audible state of a page.
+        
+        @param recentlyAudible flag indicating the new audible state
+        @type bool
+        @param page reference to the web page
+        @type WebBrowserPage
+        """
+        browser = page.view()
+        if browser is None:
+            return
+        
+        index = self.indexOf(browser)
+        icon = page.icon()
+        
+        if page.isAudioMuted() or (
+                not page.isAudioMuted() and recentlyAudible):
+            pix = QPixmap(32, 32)
+            pix.fill(Qt.transparent)
+            painter = QPainter(pix)
+            icon.paint(painter, 0, 0, 22, 22)
+            if page.isAudioMuted():
+                audioIcon = UI.PixmapCache.getIcon("audioMuted.png")
+            else:
+                audioIcon = UI.PixmapCache.getIcon("audioPlaying.png")
+            audioIcon.paint(painter, 13, 13, 18, 18)
+            painter.end()
+            self.setTabIcon(index, QIcon(pix))
+        else:
+            self.setTabIcon(index, icon)
+    
+    @pyqtSlot()
+    def __newBrowser(self):
+        """
+        Private slot to open a new browser tab.
+        """
+        self.newBrowser()
+    
+    def newBrowser(self, link=None, position=-1, background=False,
+                   restoreSession=False):
+        """
+        Public method to create a new web browser tab.
+        
+        @param link link to be shown
+        @type str or QUrl
+        @keyparam position position to create the new tab at or -1 to add it
+            to the end
+        @type int
+        @keyparam background flag indicating to open the tab in the
+            background
+        @type bool
+        @keyparam restoreSession flag indicating a restore session action
+        @type bool
+        @return reference to the new browser
+        @rtype WebBrowserView
+        """
+        if link is None:
+            linkName = ""
+        elif isinstance(link, QUrl):
+            linkName = link.toString()
+        else:
+            linkName = link
+        
+        from .UrlBar.UrlBar import UrlBar
+        urlbar = UrlBar(self.__mainWindow, self)
+        if self.__historyCompleter is None:
+            import WebBrowser.WebBrowserWindow
+            from .History.HistoryCompleter import HistoryCompletionModel, \
+                HistoryCompleter
+            self.__historyCompletionModel = HistoryCompletionModel(self)
+            self.__historyCompletionModel.setSourceModel(
+                WebBrowser.WebBrowserWindow.WebBrowserWindow.historyManager()
+                .historyFilterModel())
+            self.__historyCompleter = HistoryCompleter(
+                self.__historyCompletionModel, self)
+            self.__historyCompleter.activated[str].connect(self.__pathSelected)
+        urlbar.setCompleter(self.__historyCompleter)
+        urlbar.returnPressed.connect(
+            lambda: self.__lineEditReturnPressed(urlbar))
+        if position == -1:
+            self.__stackedUrlBar.addWidget(urlbar)
+        else:
+            self.__stackedUrlBar.insertWidget(position, urlbar)
+        
+        browser = WebBrowserView(self.__mainWindow, self)
+        urlbar.setBrowser(browser)
+        
+        browser.sourceChanged.connect(
+            lambda url: self.__sourceChanged(url, browser))
+        browser.titleChanged.connect(
+            lambda title: self.__titleChanged(title, browser))
+        browser.highlighted.connect(self.showMessage)
+        browser.backwardAvailable.connect(
+            self.__mainWindow.setBackwardAvailable)
+        browser.forwardAvailable.connect(self.__mainWindow.setForwardAvailable)
+        browser.loadStarted.connect(
+            lambda: self.__loadStarted(browser))
+        browser.loadFinished.connect(
+            lambda ok: self.__loadFinished(ok, browser))
+        browser.faviconChanged.connect(
+            lambda: self.__iconChanged(browser))
+        browser.search.connect(self.newBrowser)
+        browser.page().windowCloseRequested.connect(
+            lambda: self.__windowCloseRequested(browser.page()))
+        browser.zoomValueChanged.connect(self.browserZoomValueChanged)
+        if hasattr(WebBrowserPage, "recentlyAudibleChanged"):
+            browser.page().recentlyAudibleChanged.connect(
+                lambda audible: self.__recentlyAudibleChanged(
+                    audible, browser.page()))
+        try:
+            browser.page().printRequested.connect(
+                lambda: self.printBrowser(browser))
+        except AttributeError:
+            # pre Qt 5.12
+            pass
+        
+        if position == -1:
+            index = self.addTab(browser, self.tr("..."))
+        else:
+            index = self.insertTab(position, browser, self.tr("..."))
+        if not background:
+            self.setCurrentIndex(index)
+        
+        self.__mainWindow.closeAct.setEnabled(True)
+        self.__mainWindow.closeAllAct.setEnabled(True)
+        self.__closeButton.setEnabled(True)
+        self.__navigationButton.setEnabled(True)
+        
+        if not restoreSession:
+            if not linkName:
+                if Preferences.getWebBrowser("NewTabBehavior") == 0:
+                    linkName = "about:blank"
+                elif Preferences.getWebBrowser("NewTabBehavior") == 1:
+                    linkName = Preferences.getWebBrowser("HomePage")
+                elif Preferences.getWebBrowser("NewTabBehavior") == 2:
+                    linkName = "eric:speeddial"
+        
+        if linkName == "eric:blank":
+            linkName = "about:blank"
+        
+        if linkName:
+            browser.setSource(QUrl(linkName))
+            if not browser.documentTitle():
+                self.setTabText(index, self.__elide(linkName, Qt.ElideMiddle))
+                self.setTabToolTip(index, linkName)
+            else:
+                self.setTabText(
+                    index,
+                    self.__elide(browser.documentTitle().replace("&", "&&")))
+                self.setTabToolTip(index, browser.documentTitle())
+        
+        self.browserOpened.emit(browser)
+        
+        return browser
+    
+    def newBrowserAfter(self, browser, link=None, background=False):
+        """
+        Public method to create a new web browser tab after a given one.
+        
+        @param browser reference to the browser to add after (WebBrowserView)
+        @param link link to be shown (string or QUrl)
+        @keyparam background flag indicating to open the tab in the
+            background (bool)
+        @return reference to the new browser
+        @rtype WebBrowserView
+        """
+        if browser:
+            position = self.indexOf(browser) + 1
+        else:
+            position = -1
+        return self.newBrowser(link, position, background)
+    
+    def __showNavigationMenu(self):
+        """
+        Private slot to show the navigation button menu.
+        """
+        self.__navigationMenu.clear()
+        for index in range(self.count()):
+            act = self.__navigationMenu.addAction(
+                self.tabIcon(index), self.tabText(index))
+            act.setData(index)
+    
+    def __navigationMenuTriggered(self, act):
+        """
+        Private slot called to handle the navigation button menu selection.
+        
+        @param act reference to the selected action (QAction)
+        """
+        index = act.data()
+        if index is not None:
+            self.setCurrentIndex(index)
+    
+    def __windowCloseRequested(self, page):
+        """
+        Private slot to handle the windowCloseRequested signal of a browser.
+        
+        @param page reference to the web page
+        @type WebBrowserPage
+        """
+        browser = page.view()
+        if browser is None:
+            return
+        
+        index = self.indexOf(browser)
+        self.closeBrowserAt(index)
+    
+    def reloadAllBrowsers(self):
+        """
+        Public slot to reload all browsers.
+        """
+        for index in range(self.count()):
+            browser = self.widget(index)
+            browser and browser.reload()
+    
+    @pyqtSlot()
+    def closeBrowser(self):
+        """
+        Public slot called to handle the close action.
+        """
+        self.closeBrowserAt(self.currentIndex())
+    
+    def closeAllBrowsers(self, shutdown=False):
+        """
+        Public slot called to handle the close all action.
+        
+        @param shutdown flag indicating a shutdown action
+        @type bool
+        """
+        for index in range(self.count() - 1, -1, -1):
+            self.closeBrowserAt(index, shutdown=shutdown)
+    
+    def closeBrowserView(self, browser):
+        """
+        Public method to close the given browser.
+        
+        @param browser reference to the web browser view to be closed
+        @type WebBrowserView
+        """
+        index = self.indexOf(browser)
+        self.closeBrowserAt(index)
+    
+    def closeBrowserAt(self, index, shutdown=False):
+        """
+        Public slot to close a browser based on its index.
+        
+        @param index index of browser to close
+        @type int
+        @param shutdown flag indicating a shutdown action
+        @type bool
+        """
+        browser = self.widget(index)
+        if browser is None:
+            return
+        
+        urlbar = self.__stackedUrlBar.widget(index)
+        self.__stackedUrlBar.removeWidget(urlbar)
+        urlbar.deleteLater()
+        del urlbar
+        
+        self.__closedTabsManager.recordBrowser(browser, index)
+        
+        browser.closeWebInspector()
+        WebInspector.unregisterView(browser)
+        self.removeTab(index)
+        self.browserClosed.emit(browser)
+        browser.deleteLater()
+        del browser
+        
+        if self.count() == 0 and not shutdown:
+            self.newBrowser()
+        else:
+            self.currentChanged[int].emit(self.currentIndex())
+    
+    def currentBrowser(self):
+        """
+        Public method to get a reference to the current browser.
+        
+        @return reference to the current browser (WebBrowserView)
+        """
+        return self.currentWidget()
+    
+    def browserAt(self, index):
+        """
+        Public method to get a reference to the browser with the given index.
+        
+        @param index index of the browser to get (integer)
+        @return reference to the indexed browser (WebBrowserView)
+        """
+        return self.widget(index)
+    
+    def browsers(self):
+        """
+        Public method to get a list of references to all browsers.
+        
+        @return list of references to browsers (list of WebBrowserView)
+        """
+        li = []
+        for index in range(self.count()):
+            li.append(self.widget(index))
+        return li
+    
+    @pyqtSlot()
+    def printBrowser(self, browser=None):
+        """
+        Public slot called to print the displayed page.
+        
+        @param browser reference to the browser to be printed (WebBrowserView)
+        """
+        if browser is None:
+            browser = self.currentBrowser()
+        
+        printer = QPrinter(mode=QPrinter.HighResolution)
+        if Preferences.getPrinter("ColorMode"):
+            printer.setColorMode(QPrinter.Color)
+        else:
+            printer.setColorMode(QPrinter.GrayScale)
+        if Preferences.getPrinter("FirstPageFirst"):
+            printer.setPageOrder(QPrinter.FirstPageFirst)
+        else:
+            printer.setPageOrder(QPrinter.LastPageFirst)
+        printer.setPageMargins(
+            Preferences.getPrinter("LeftMargin") * 10,
+            Preferences.getPrinter("TopMargin") * 10,
+            Preferences.getPrinter("RightMargin") * 10,
+            Preferences.getPrinter("BottomMargin") * 10,
+            QPrinter.Millimeter
+        )
+        printerName = Preferences.getPrinter("PrinterName")
+        if printerName:
+            printer.setPrinterName(printerName)
+        printer.setResolution(Preferences.getPrinter("Resolution"))
+        documentName = WebBrowserTools.getFileNameFromUrl(browser.url())
+        printer.setDocName(documentName)
+        
+        printDialog = QPrintDialog(printer, self)
+        printDialog.setOptions(QAbstractPrintDialog.PrintToFile |
+                               QAbstractPrintDialog.PrintShowPageSize)
+        if not Globals.isWindowsPlatform():
+            if FilePrinter.isCupsAvailable():
+                printDialog.setOption(QAbstractPrintDialog.PrintCollateCopies)
+            printDialog.setOption(QAbstractPrintDialog.PrintPageRange)
+        if printDialog.exec_() == QDialog.Accepted:
+            if hasattr(browser.page(), "print"):
+                # Qt >= 5.8.0
+                browser.page().execPrintPage(printer, 10 * 1000)
+            elif hasattr(browser.page(), "printToPdf"):
+                # Qt >= 5.7.0
+                if printer.outputFormat() == QPrinter.PdfFormat:
+                    # print to PDF file selected
+                    browser.page().printToPdf(
+                        lambda pdf: self.__pdfGeneratedForSave(
+                            printer.outputFileName(), pdf),
+                        printer.pageLayout())
+                else:
+                    # print to printer
+                    self.__pdfPrinter = printer
+                    browser.page().printToPdf(
+                        self.__pdfGeneratedForPrinting,
+                        printer.pageLayout())
+            else:
+                browser.render(printer)
+    
+    @pyqtSlot()
+    def printBrowserPdf(self, browser=None):
+        """
+        Public slot called to print the displayed page to PDF.
+        
+        @param browser reference to the browser to be printed (HelpBrowser)
+        """
+        if browser is None:
+            browser = self.currentBrowser()
+        
+        name = WebBrowserTools.getFileNameFromUrl(browser.url())
+        if name:
+            name = name.rsplit('.', 1)[0]
+            name += '.pdf'
+        if hasattr(browser.page(), "printToPdf"):
+            from .Tools.PrintToPdfDialog import PrintToPdfDialog
+            if not name:
+                name = "printout.pdf"
+            dlg = PrintToPdfDialog(name, self)
+            if dlg.exec_() == QDialog.Accepted:
+                filePath, pageLayout = dlg.getData()
+                if filePath:
+                    if os.path.exists(filePath):
+                        res = E5MessageBox.warning(
+                            self,
+                            self.tr("Print to PDF"),
+                            self.tr("""<p>The file <b>{0}</b> exists"""
+                                    """ already. Shall it be"""
+                                    """ overwritten?</p>""").format(filePath),
+                            E5MessageBox.StandardButtons(
+                                E5MessageBox.No |
+                                E5MessageBox.Yes),
+                            E5MessageBox.No)
+                        if res == E5MessageBox.No:
+                            return
+                    browser.page().printToPdf(
+                        lambda pdf: self.__pdfGeneratedForSave(filePath, pdf),
+                        pageLayout)
+        elif Globals.isLinuxPlatform():
+            printer = QPrinter(mode=QPrinter.HighResolution)
+            if Preferences.getPrinter("ColorMode"):
+                printer.setColorMode(QPrinter.Color)
+            else:
+                printer.setColorMode(QPrinter.GrayScale)
+            printerName = Preferences.getPrinter("PrinterName")
+            if printerName:
+                printer.setPrinterName(printerName)
+            printer.setOutputFormat(QPrinter.PdfFormat)
+            if name:
+                printer.setOutputFileName(name)
+            printer.setResolution(Preferences.getPrinter("Resolution"))
+            
+            printDialog = QPrintDialog(printer, self)
+            if printDialog.exec_() == QDialog.Accepted:
+                browser.render(printer)
+    
+    def __pdfGeneratedForSave(self, filePath, pdfData):
+        """
+        Private slot to save the generated PDF data to a file.
+        
+        @param filePath path to save the PDF to
+        @type str
+        @param pdfData generated PDF document
+        @type QByteArray
+        """
+        if pdfData.size() == 0:
+            return
+        
+        pdfFile = QFile(filePath)
+        if pdfFile.open(QFile.WriteOnly):
+            pdfFile.write(pdfData)
+            pdfFile.close()
+        if pdfFile.error() != QFileDevice.NoError:
+            E5MessageBox.critical(
+                self,
+                self.tr("Print to PDF"),
+                self.tr("""<p>The PDF could not be written to file <b>{0}"""
+                        """</b>.</p><p><b>Error:</b> {1}</p>""").format(
+                    filePath, pdfFile.errorString()),
+                E5MessageBox.StandardButtons(
+                    E5MessageBox.Ok))
+    
+    def __pdfGeneratedForPrinting(self, pdfData):
+        """
+        Private slot to print the generated PDF data.
+        
+        @param pdfData generated PDF document
+        @type QByteArray
+        """
+        if self.__pdfPrinter is None or pdfData.isEmpty():
+            return
+        
+        tempFile = QTemporaryFile(QDir.tempPath() + "/ericBrowserXXXXXX.pdf")
+        tempFile.setAutoRemove(False)
+        if tempFile.open():
+            bytesWritten = tempFile.write(pdfData)
+            tempFile.close()
+            if bytesWritten == pdfData.size():
+                if Globals.isWindowsPlatform():
+                    printerName = self.__pdfPrinter.printerName()
+                    import ctypes
+                    ctypes.windll.shell32.ShellExecuteW(
+                        None, "printto", tempFile.fileName(),
+                        '"{0}"'.format(printerName), None, 0)
+                else:
+                    FilePrinter.printFile(
+                        self.__pdfPrinter, tempFile.fileName(),
+                        FilePrinter.FilePrinter.SystemDeletesFiles,
+                        FilePrinter.FilePrinter.SystemSelectsPages)
+            else:
+                tempFile.remove()
+        
+        self.__pdfPrinter = None
+    
+    @pyqtSlot()
+    def printPreviewBrowser(self, browser=None):
+        """
+        Public slot called to show a print preview of the displayed file.
+        
+        @param browser reference to the browser to be printed (WebBrowserView)
+        """
+        from PyQt5.QtPrintSupport import QPrintPreviewDialog
+        
+        if browser is None:
+            browser = self.currentBrowser()
+        
+        printer = QPrinter(mode=QPrinter.HighResolution)
+        if Preferences.getPrinter("ColorMode"):
+            printer.setColorMode(QPrinter.Color)
+        else:
+            printer.setColorMode(QPrinter.GrayScale)
+        if Preferences.getPrinter("FirstPageFirst"):
+            printer.setPageOrder(QPrinter.FirstPageFirst)
+        else:
+            printer.setPageOrder(QPrinter.LastPageFirst)
+        printer.setPageMargins(
+            Preferences.getPrinter("LeftMargin") * 10,
+            Preferences.getPrinter("TopMargin") * 10,
+            Preferences.getPrinter("RightMargin") * 10,
+            Preferences.getPrinter("BottomMargin") * 10,
+            QPrinter.Millimeter
+        )
+        printerName = Preferences.getPrinter("PrinterName")
+        if printerName:
+            printer.setPrinterName(printerName)
+        printer.setResolution(Preferences.getPrinter("Resolution"))
+        
+        preview = QPrintPreviewDialog(printer, self)
+        preview.resize(800, 750)
+        if qVersionTuple() >= (5, 8, 0):
+            preview.paintRequested.connect(
+                lambda p: self.__printPreviewRequested(p, browser))
+        else:
+            preview.paintRequested.connect(lambda p: browser.render(p))
+        preview.exec_()
+    
+    def __printPreviewRequested(self, printer, browser):
+        """
+        Private slot to generate the print preview.
+        
+        @param printer reference to the printer object
+        @type QPrinter
+        @param browser reference to the browser to be printed
+        @type WebBrowserView
+        """
+        QApplication.setOverrideCursor(Qt.WaitCursor)
+        browser.page().execPrintPage(printer, 10 * 1000)
+        QApplication.restoreOverrideCursor()
+    
+    def __sourceChanged(self, url, browser):
+        """
+        Private slot to handle a change of a browsers source.
+        
+        @param url URL of the new site
+        @type QUrl
+        @param browser reference to the web browser
+        @type WebBrowserView
+        """
+        self.sourceChanged.emit(browser, url)
+        
+        if browser == self.currentBrowser():
+            self.currentUrlChanged.emit(url)
+    
+    def __titleChanged(self, title, browser):
+        """
+        Private slot to handle a change of a browsers title.
+        
+        @param title new title
+        @type str
+        @param browser reference to the web browser
+        @type WebBrowserView
+        """
+        index = self.indexOf(browser)
+        if title == "":
+            title = browser.url().toString()
+        
+        self.setTabText(index, self.__elide(title.replace("&", "&&")))
+        self.setTabToolTip(index, title)
+    
+        self.titleChanged.emit(browser, title)
+    
+    def __elide(self, txt, mode=Qt.ElideRight, length=40):
+        """
+        Private method to elide some text.
+        
+        @param txt text to be elided (string)
+        @keyparam mode elide mode (Qt.TextElideMode)
+        @keyparam length amount of characters to be used (integer)
+        @return the elided text (string)
+        """
+        if mode == Qt.ElideNone or len(txt) < length:
+            return txt
+        elif mode == Qt.ElideLeft:
+            return "...{0}".format(txt[-length:])
+        elif mode == Qt.ElideMiddle:
+            return "{0}...{1}".format(txt[:length // 2], txt[-(length // 2):])
+        elif mode == Qt.ElideRight:
+            return "{0}...".format(txt[:length])
+        else:
+            # just in case
+            return txt
+    
+    def preferencesChanged(self):
+        """
+        Public slot to handle a change of preferences.
+        """
+        for browser in self.browsers():
+            browser.preferencesChanged()
+        
+        for urlbar in self.__stackedUrlBar.urlBars():
+            urlbar.preferencesChanged()
+        
+        if Preferences.getUI("SingleCloseButton"):
+            self.setTabsClosable(False)
+            try:
+                self.tabCloseRequested.disconnect(self.closeBrowserAt)
+            except TypeError:
+                pass
+            self.__closeButton.show()
+        else:
+            self.setTabsClosable(True)
+            self.tabCloseRequested.connect(self.closeBrowserAt)
+            self.__closeButton.hide()
+    
+    def __loadStarted(self, browser):
+        """
+        Private method to handle the loadStarted signal.
+        
+        @param browser reference to the web browser
+        @type WebBrowserView
+        """
+        index = self.indexOf(browser)
+        anim = self.animationLabel(
+            index, os.path.join(getConfig("ericPixDir"), "loading.gif"),
+            100)
+        if not anim:
+            loading = QIcon(os.path.join(getConfig("ericPixDir"),
+                            "loading.gif"))
+            self.setTabIcon(index, loading)
+        else:
+            self.setTabIcon(index, QIcon())
+        self.setTabText(index, self.tr("Loading..."))
+        self.setTabToolTip(index, self.tr("Loading..."))
+        self.showMessage.emit(self.tr("Loading..."))
+        
+        self.__mainWindow.setLoadingActions(True)
+    
+    def __loadFinished(self, ok, browser):
+        """
+        Private method to handle the loadFinished signal.
+        
+        @param ok flag indicating the result
+        @type bool
+        @param browser reference to the web browser
+        @type WebBrowserView
+        """
+        import WebBrowser.WebBrowserWindow
+        index = self.indexOf(browser)
+        self.resetAnimation(index)
+        self.setTabIcon(
+            index, WebBrowser.WebBrowserWindow.WebBrowserWindow.icon(
+                browser.url()))
+        if ok:
+            self.showMessage.emit(self.tr("Finished loading"))
+        else:
+            self.showMessage.emit(self.tr("Failed to load"))
+        
+        self.__mainWindow.setLoadingActions(False)
+    
+    def __iconChanged(self, browser):
+        """
+        Private slot to handle a change of the web site icon.
+        
+        @param browser reference to the web browser
+        @type WebBrowserView
+        """
+        self.setTabIcon(
+            self.indexOf(browser),
+            browser.icon())
+        self.__mainWindow.bookmarksManager().faviconChanged(browser.url())
+    
+    def getSourceFileList(self):
+        """
+        Public method to get a list of all opened Qt help files.
+        
+        @return dictionary with tab id as key and host/namespace as value
+        """
+        sourceList = {}
+        for i in range(self.count()):
+            browser = self.widget(i)
+            if browser is not None and \
+               browser.source().isValid():
+                sourceList[i] = browser.source().host()
+        
+        return sourceList
+    
+    def shallShutDown(self):
+        """
+        Public method to check, if the application should be shut down.
+        
+        @return flag indicating a shut down (boolean)
+        """
+        if self.count() > 1 and Preferences.getWebBrowser(
+                "WarnOnMultipleClose"):
+            mb = E5MessageBox.E5MessageBox(
+                E5MessageBox.Information,
+                self.tr("Are you sure you want to close the window?"),
+                self.tr("""Are you sure you want to close the window?\n"""
+                        """You have %n tab(s) open.""", "", self.count()),
+                modal=True,
+                parent=self)
+            quitButton = mb.addButton(
+                self.tr("&Quit"), E5MessageBox.AcceptRole)
+            quitButton.setIcon(UI.PixmapCache.getIcon("exit.png"))
+            closeTabButton = mb.addButton(
+                self.tr("C&lose Current Tab"), E5MessageBox.AcceptRole)
+            closeTabButton.setIcon(UI.PixmapCache.getIcon("tabClose.png"))
+            mb.addButton(E5MessageBox.Cancel)
+            mb.exec_()
+            if mb.clickedButton() == quitButton:
+                return True
+            else:
+                if mb.clickedButton() == closeTabButton:
+                    self.closeBrowser()
+                return False
+        
+        return True
+    
+    def stackedUrlBar(self):
+        """
+        Public method to get a reference to the stacked url bar.
+        
+        @return reference to the stacked url bar (StackedUrlBar)
+        """
+        return self.__stackedUrlBar
+    
+    def currentUrlBar(self):
+        """
+        Public method to get a reference to the current url bar.
+        
+        @return reference to the current url bar (UrlBar)
+        """
+        return self.__stackedUrlBar.currentWidget()
+    
+    def urlBarForView(self, view):
+        """
+        Public method to get a reference to the UrlBar associated with the
+        given view.
+        
+        @param view reference to the view to get the urlbar for
+        @type WebBrowserView
+        @return reference to the associated urlbar
+        @rtype UrlBar
+        """
+        for urlbar in self.__stackedUrlBar.urlBars():
+            if urlbar.browser() is view:
+                return urlbar
+        
+        return None
+    
+    def __lineEditReturnPressed(self, edit):
+        """
+        Private slot to handle the entering of an URL.
+        
+        @param edit reference to the line edit
+        @type UrlBar
+        """
+        url = self.__guessUrlFromPath(edit.text())
+        if e5App().keyboardModifiers() == Qt.AltModifier:
+            self.newBrowser(url)
+        else:
+            self.currentBrowser().setSource(url)
+            self.currentBrowser().setFocus()
+    
+    def __pathSelected(self, path):
+        """
+        Private slot called when a URL is selected from the completer.
+        
+        @param path path to be shown (string)
+        """
+        url = self.__guessUrlFromPath(path)
+        self.currentBrowser().setSource(url)
+    
+    def __guessUrlFromPath(self, path):
+        """
+        Private method to guess an URL given a path string.
+        
+        @param path path string to guess an URL for (string)
+        @return guessed URL (QUrl)
+        """
+        manager = self.__mainWindow.openSearchManager()
+        path = Utilities.fromNativeSeparators(path)
+        url = manager.convertKeywordSearchToUrl(path)
+        if url.isValid():
+            return url
+        
+        try:
+            url = QUrl.fromUserInput(path)
+        except AttributeError:
+            url = QUrl(path)
+        
+        if url.scheme() == "about" and \
+           url.path() == "home":
+            url = QUrl("eric:home")
+        
+        if url.scheme() in ["s", "search"]:
+            url = manager.currentEngine().searchUrl(url.path().strip())
+        
+        if url.scheme() != "" and \
+           (url.host() != "" or url.path() != ""):
+            return url
+        
+        urlString = Preferences.getWebBrowser("DefaultScheme") + path.strip()
+        url = QUrl.fromEncoded(urlString.encode("utf-8"), QUrl.TolerantMode)
+        
+        return url
+    
+    def __currentChanged(self, index):
+        """
+        Private slot to handle an index change.
+        
+        @param index new index (integer)
+        """
+        self.__stackedUrlBar.setCurrentIndex(index)
+        
+        browser = self.browserAt(index)
+        if browser is not None:
+            if browser.url() == "" and browser.hasFocus():
+                self.__stackedUrlBar.currentWidget.setFocus()
+            elif browser.url() != "":
+                browser.setFocus()
+    
+    def restoreClosedTab(self, act):
+        """
+        Public slot to restore the most recently closed tab.
+        
+        @param act reference to the action that triggered
+        @type QAction
+        """
+        if not self.canRestoreClosedTab():
+            return
+        
+        tab = self.__closedTabsManager.getClosedTabAt(act.data())
+        
+        self.newBrowser(tab.url.toString(), position=tab.position)
+    
+    def canRestoreClosedTab(self):
+        """
+        Public method to check, if closed tabs can be restored.
+        
+        @return flag indicating that closed tabs can be restored (boolean)
+        """
+        return self.__closedTabsManager.isClosedTabAvailable()
+    
+    def restoreAllClosedTabs(self):
+        """
+        Public slot to restore all closed tabs.
+        """
+        if not self.canRestoreClosedTab():
+            return
+        
+        for tab in self.__closedTabsManager.allClosedTabs():
+            self.newBrowser(tab.url.toString(), position=tab.position)
+        self.__closedTabsManager.clearList()
+    
+    def clearClosedTabsList(self):
+        """
+        Public slot to clear the list of closed tabs.
+        """
+        self.__closedTabsManager.clearList()
+    
+    def __aboutToShowClosedTabsMenu(self):
+        """
+        Private slot to populate the closed tabs menu.
+        """
+        fm = self.__closedTabsMenu.fontMetrics()
+        maxWidth = fm.width('m') * 40
+        
+        self.__closedTabsMenu.clear()
+        index = 0
+        for tab in self.__closedTabsManager.allClosedTabs():
+            title = fm.elidedText(tab.title, Qt.ElideRight, maxWidth)
+            act = self.__closedTabsMenu.addAction(
+                self.__mainWindow.icon(tab.url), title)
+            act.setData(index)
+            act.triggered.connect(lambda: self.restoreClosedTab(act))
+            index += 1
+        self.__closedTabsMenu.addSeparator()
+        self.__closedTabsMenu.addAction(
+            self.tr("Restore All Closed Tabs"), self.restoreAllClosedTabs)
+        self.__closedTabsMenu.addAction(
+            self.tr("Clear List"), self.clearClosedTabsList)
+    
+    def closedTabsManager(self):
+        """
+        Public slot to get a reference to the closed tabs manager.
+        
+        @return reference to the closed tabs manager (ClosedTabsManager)
+        """
+        return self.__closedTabsManager
+    
+    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.__closedTabsButton.setEnabled(avail)
+        self.__restoreClosedTabAct.setEnabled(avail)
+    
+    ####################################################
+    ## Methods below implement session related functions
+    ####################################################
+    
+    def getSessionData(self):
+        """
+        Public method to populate the session data.
+        
+        @return dictionary containing the session data
+        @rtype dict
+        """
+        sessionData = {}
+        
+        # 1. current index
+        sessionData["CurrentTabIndex"] = self.currentIndex()
+        
+        # 2. tab data
+        sessionData["Tabs"] = []
+        for index in range(self.count()):
+            browser = self.widget(index)
+            data = browser.getSessionData()
+            sessionData["Tabs"].append(data)
+        
+        return sessionData
+    
+    def loadFromSessionData(self, sessionData):
+        """
+        Public method to load the session data.
+        
+        @param sessionData dictionary containing the session data as
+            generated by getSessionData()
+        @type dict
+        """
+        tabCount = self.count()
+        
+        # 1. load tab data
+        if "Tabs" in sessionData:
+            loadTabOnActivate = \
+                Preferences.getWebBrowser("LoadTabOnActivation")
+            for data in sessionData["Tabs"]:
+                browser = self.newBrowser(restoreSession=True)
+                if loadTabOnActivate:
+                    browser.storeSessionData(data)
+                    title, urlStr, icon = browser.extractSessionMetaData(data)
+                    index = self.indexOf(browser)
+                    self.setTabText(index, title)
+                    self.setTabIcon(index, icon)
+                else:
+                    browser.loadFromSessionData(data)
+        
+        # 2. set tab index
+        if "CurrentTabIndex" in sessionData and \
+                sessionData["CurrentTabIndex"] >= 0:
+            index = tabCount + sessionData["CurrentTabIndex"]
+            self.setCurrentIndex(index)
+            self.browserAt(index).activateSession()

eric ide

mercurial