--- a/src/eric7/WebBrowser/WebBrowserView.py Thu Oct 12 17:03:41 2023 +0200 +++ b/src/eric7/WebBrowser/WebBrowserView.py Thu Oct 12 17:05:08 2023 +0200 @@ -18,7 +18,9 @@ QDataStream, QDateTime, QEvent, + QEventLoop, QIODevice, + QMarginsF, QPoint, QPointF, QStandardPaths, @@ -28,7 +30,20 @@ pyqtSignal, pyqtSlot, ) -from PyQt6.QtGui import QClipboard, QCursor, QDesktopServices, QIcon, QPixmap +from PyQt6.QtGui import ( + QClipboard, + QCursor, + QDesktopServices, + QIcon, + QPageLayout, + QPixmap, +) +from PyQt6.QtPrintSupport import ( + QAbstractPrintDialog, + QPrintDialog, + QPrinter, + QPrintPreviewDialog, +) from PyQt6.QtWebEngineCore import QWebEngineDownloadRequest, QWebEnginePage from PyQt6.QtWebEngineWidgets import QWebEngineView from PyQt6.QtWidgets import QApplication, QDialog, QMenu, QStyle @@ -38,16 +53,34 @@ from eric7.EricWidgets import EricFileDialog, EricMessageBox from eric7.EricWidgets.EricApplication import ericApp from eric7.SystemUtilities import FileSystemUtilities, OSUtilities +from eric7.UI.Info import Homepage, VersionOnly from eric7.WebBrowser.WebBrowserWindow import WebBrowserWindow from eric7.WebBrowser.ZoomManager import ZoomManager from . import WebInspector -from .Tools import Scripts +from .Tools import Scripts, WebBrowserTools from .Tools.WebBrowserTools import getHtmlPage, pixmapToDataUrl from .Tools.WebIconLoader import WebIconLoader from .WebBrowserPage import WebBrowserPage +def isCupsAvailable(): + """ + Static method to test the availability of CUPS. + + @return flag indicating the availability of CUPS + @rtype bool + """ + if OSUtilities.isMacPlatform(): + # OS X/MacOS always have CUPS + return True + elif OSUtilities.isLinuxPlatform(): + testPrinter = QPrinter() + return testPrinter.supportsMultipleCopies() + else: + return False + + class WebBrowserView(QWebEngineView): """ Class implementing the web browser view widget. @@ -130,6 +163,8 @@ self.__clickedPos = QPoint() self.__firstLoad = False self.__preview = QPixmap() + self.__currentPrinter = None + self.__printPreviewLoop = None self.__currentZoom = 100 self.__zoomLevels = WebBrowserView.ZoomLevels[:] @@ -146,6 +181,10 @@ self.loadFinished.connect(self.__loadFinished) self.renderProcessTerminated.connect(self.__renderProcessTerminated) + self.printRequested.connect(self.printPage) + self.printFinished.connect(self.__printPageFinished) + self.pdfPrintingFinished.connect(self.__printPageToPdfFinished) + self.__mw.openSearchManager().currentEngineChanged.connect( self.__currentEngineChanged ) @@ -177,7 +216,6 @@ self.__page.safeBrowsingAbort.connect(self.safeBrowsingAbort) self.__page.safeBrowsingBad.connect(self.safeBrowsingBad) - self.__page.printPageRequested.connect(self.__printPage) with contextlib.suppress(AttributeError): # deprecated with Qt 6.5+ self.__page.quotaRequested.connect(self.__quotaRequested) @@ -2299,15 +2337,185 @@ return True ########################################################################### - ## Methods below implement print support from the page + ## Methods below implement print support ########################################################################### + def __setupPrinter(self, filePath=None): + """ + Private method to create and initialize a QPrinter object. + + @param filePath name of the output file for the printer (defaults to None) + @type str (optional) + @return initialized QPrinter object + @rtype QPrinter + """ + printer = QPrinter(mode=QPrinter.PrinterMode.HighResolution) + if Preferences.getPrinter("ColorMode"): + printer.setColorMode(QPrinter.ColorMode.Color) + else: + printer.setColorMode(QPrinter.ColorMode.GrayScale) + if Preferences.getPrinter("FirstPageFirst"): + printer.setPageOrder(QPrinter.PageOrder.FirstPageFirst) + else: + printer.setPageOrder(QPrinter.PageOrder.LastPageFirst) + printer.setPageMargins( + QMarginsF( + Preferences.getPrinter("LeftMargin") * 10, + Preferences.getPrinter("TopMargin") * 10, + Preferences.getPrinter("RightMargin") * 10, + Preferences.getPrinter("BottomMargin") * 10, + ), + QPageLayout.Unit.Millimeter, + ) + printerName = Preferences.getPrinter("PrinterName") + if printerName: + printer.setPrinterName(printerName) + printer.setResolution(Preferences.getPrinter("Resolution")) + documentName = WebBrowserTools.getFileNameFromUrl(self.url()) + printer.setDocName(documentName) + documentsPath = QStandardPaths.writableLocation( + QStandardPaths.StandardLocation.DocumentsLocation + ) + if filePath is None: + filePath = "{0}.pdf".format(documentName) + filePath = ( + os.path.join(documentsPath, filePath) + if documentsPath + else os.path.abspath(filePath) + ) + printer.setOutputFileName(filePath) + printer.setCreator(self.tr("eric7 {0} ({1})").format(VersionOnly, Homepage)) + return printer + + @pyqtSlot() + def printPage(self): + """ + Public slot to print the current page. + """ + if self.__currentPrinter is not None: + EricMessageBox.warning( + self, + self.tr("Print Page"), + self.tr( + "There is already a print job in progress. Printing is temporarily" + " disabled until the current job is finished." + ), + ) + return + + printer = self.__setupPrinter() + + printDialog = QPrintDialog(printer, self) + printDialog.setOptions( + QAbstractPrintDialog.PrintDialogOption.PrintToFile + | QAbstractPrintDialog.PrintDialogOption.PrintShowPageSize + ) + if not OSUtilities.isWindowsPlatform(): + if isCupsAvailable(): + printDialog.setOption( + QAbstractPrintDialog.PrintDialogOption.PrintCollateCopies + ) + printDialog.setOption(QAbstractPrintDialog.PrintDialogOption.PrintPageRange) + if printDialog.exec() == QDialog.DialogCode.Accepted: + if printer.outputFormat() == QPrinter.OutputFormat.PdfFormat: + self.printToPdf( + printer.outputFileName(), printer.pageLayout(), printer.pageRanges() + ) + else: + self.__currentPrinter = printer + self.print(printer) + @pyqtSlot() - def __printPage(self): + def printPageToPdf(self): + """ + Public slot to save the current page as a PDF file. + """ + from .Tools.PrintToPdfDialog import PrintToPdfDialog + + name = WebBrowserTools.getFileNameFromUrl(self.url()) + name = name.rsplit(".", 1)[0] + ".pdf" if name else "printout.pdf" + dlg = PrintToPdfDialog(self.__setupPrinter(filePath=name), self) + if dlg.exec() == QDialog.DialogCode.Accepted: + filePath, pageLayout = dlg.getData() + if filePath: + if os.path.exists(filePath): + res = EricMessageBox.warning( + self, + self.tr("Print to PDF"), + self.tr( + """<p>The file <b>{0}</b> exists""" + """ already. Shall it be""" + """ overwritten?</p>""" + ).format(filePath), + EricMessageBox.No | EricMessageBox.Yes, + EricMessageBox.No, + ) + if res == EricMessageBox.No: + return + self.printToPdf(filePath, pageLayout) + + @pyqtSlot() + def printPreviewPage(self): + """ + Public slot to create a print preview of the current page. + """ + printer = self.__setupPrinter() + preview = QPrintPreviewDialog(printer, self) + preview.resize(800, 750) + preview.paintRequested.connect(self.__printPreviewRequested) + preview.exec() + + @pyqtSlot(QPrinter) + def __printPreviewRequested(self, printer): """ - Private slot to support printing from the web page. + Private slot to generate the print preview. + + @param printer reference to the printer object + @type QPrinter + """ + # This needs to run its own event loop to prevent a premature return from + # the method. + self.__printPreviewLoop = QEventLoop() + + self.print(printer) + + self.__printPreviewLoop.exec() + self.__printPreviewLoop = None + + @pyqtSlot(bool) + def __printPageFinished(self, success): + """ + Private slot to handle the finishing of a print job. + + @param success flag indicating success (not used) + @type bool """ - self.__mw.tabWidget.printBrowser(browser=self) + if self.__printPreviewLoop is not None: + # The print preview was created, now stop the print preview loop. + self.__printPreviewLoop.quit() + return + + # we printed to a real printer + self.__currentPrinter = None + + @pyqtSlot(str, bool) + def __printPageToPdfFinished(self, filepath, success): + """ + Private slot to handle the finishing of a PDF print job. + + @param filepath path of the output PDF file + @type str + @param success flag indicating success + @type bool + """ + if not success: + EricMessageBox.critical( + self, + self.tr("Print to PDF"), + self.tr( + """<p>The PDF file <b>{0}</b> could not be generated.</p>""" + ).format(filepath), + ) ########################################################################### ## Methods below implement slots for Qt 6.0 to 6.4