--- /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()