WebBrowser/Tools/FilePrinter.py

changeset 5182
e2782c9a43d4
child 5253
57276f763bf6
diff -r 1948b27d7b21 -r e2782c9a43d4 WebBrowser/Tools/FilePrinter.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebBrowser/Tools/FilePrinter.py	Wed Sep 28 20:04:44 2016 +0200
@@ -0,0 +1,576 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2016 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing an object for printing of files.
+"""
+
+#
+# This code is inspired by and ported from Qupzilla.
+# Original Copyright (C) 2016 by Kevin Kofler <kevin.kofler@chello.at>
+#
+
+from __future__ import unicode_literals
+
+from PyQt5.QtCore import qVersion, QFile, QStandardPaths, QProcess
+from PyQt5.QtPrintSupport import QPrinter, QPrintEngine
+
+import Globals
+
+
+_FilePrintJobs = []
+
+class FilePrinter(object):
+    """
+    Class implementing methods for printing on *nix systems.
+    """
+    #
+    # Whether file(s) get deleted by the application or by the print system.
+    #
+    ApplicationDeletesFiles = 0
+    SystemDeletesFiles = 1
+    
+    #
+    # Whether pages to be printed are selected by the application or the print
+    # system.
+    #
+    # If application side, then the generated file will only contain those
+    # pages selected by the user, so FilePrinter will print all the pages in
+    # the file.
+    #
+    #  If system side, then the file will contain all the pages in the
+    # document, and the print system will print the users selected print range
+    # from out of the file.
+    #
+    # Note: system side only works in CUPS, not LPR.
+    #
+    ApplicationSelectsPages = 0
+    SystemSelectsPages = 1
+    
+    def __init__(self):
+        """
+        Constructor
+        """
+        self.__paperSizesMap = {
+            QPrinter.A0: "A0",
+            QPrinter.A1: "A1",
+            QPrinter.A2: "A2",
+            QPrinter.A3: "A3",
+            QPrinter.A4: "A4",
+            QPrinter.A5: "A5",
+            QPrinter.A6: "A6",
+            QPrinter.A7: "A7",
+            QPrinter.A8: "A8",
+            QPrinter.A9: "A9",
+            QPrinter.B0: "B0",
+            QPrinter.B1: "B1",
+            QPrinter.B2: "B2",
+            QPrinter.B3: "B3",
+            QPrinter.B4: "B4",
+            QPrinter.B5: "B5",
+            QPrinter.B6: "B6",
+            QPrinter.B7: "B7",
+            QPrinter.B8: "B8",
+            QPrinter.B9: "B9",
+            QPrinter.B10: "B10",
+            QPrinter.C5E: "C5",
+            QPrinter.Comm10E: "Comm10",
+            QPrinter.DLE: "DL",
+            QPrinter.Executive: "Executive",
+            QPrinter.Folio: "Folio",
+            QPrinter.Ledger: "Ledger",
+            QPrinter.Legal: "Legal",
+            QPrinter.Letter: "Letter",
+            QPrinter.Tabloid: "Tabloid",
+        }
+        
+        self.__paperSourcesMap = {
+            QPrinter.Auto: "",
+            QPrinter.Cassette: "Cassette",
+            QPrinter.Envelope: "Envelope",
+            QPrinter.EnvelopeManual: "EnvelopeManual",
+            QPrinter.FormSource: "FormSource",
+            QPrinter.LargeCapacity: "LargeCapacity",
+            QPrinter.LargeFormat: "LargeFormat",
+            QPrinter.Lower: "Lower",
+            QPrinter.MaxPageSource: "MaxPageSource",
+            QPrinter.Middle: "Middle",
+            QPrinter.Manual: "Manual",
+            QPrinter.OnlyOne: "OnlyOne",
+            QPrinter.Tractor: "Tractor",
+            QPrinter.SmallFormat: "SmallFormat",
+        }
+        
+        self.__process = None
+        self.__doDeleteFile = FilePrinter.ApplicationDeletesFiles
+        self.__fileName = ""
+    
+    def _doPrintFile(self, printer, fileName, fileDeletePolicy,
+                     pageSelectPolicy, pageRange):
+        """
+        Protected method to print a file
+        
+        @param printer reference to the printer to print to
+        @type QPrinter
+        @param fileName name (path) of the file to be printed
+        @type str
+        @param fileDeletePolicy policy determining who deletes the file to be
+            printed (application or system)
+        @type int (0 or 1)
+        @param pageSelectPolicy policy determining who selects the pages to be
+            printed (application or system)
+        @type int (0 or 1)
+        @param pageRange string determining the page range(s) to be printed, if
+            SystemSelectsPages was given for pageSelectPolicy and user chose
+            Selection in print dialog
+        @type str
+        @return flag indicating successful print job submission
+        @rtype bool
+        """
+        if not QFile.exists(fileName):
+            return False
+        
+        self.__fileName = fileName
+        self.__doDeleteFile = (
+            fileDeletePolicy == FilePrinter.SystemDeletesFiles)
+        
+        if printer.printerState() in [QPrinter.Aborted, QPrinter.Error]:
+            if self.__doDeleteFile:
+                QFile.remove(fileName)
+            return False
+        
+        #
+        # Print via lpr/lp command
+        #
+        
+        #
+        # Decide what executable to use to print with, need the CUPS version
+        # of lpr if available. Some distros name the CUPS version of lpr as
+        # lpr-cups or lpr.cups so try those first before default to lpr, or
+        # failing that to lp.
+        if QStandardPaths.findExecutable("lpr-cups"):
+            exe = "lpr-cups"
+        elif QStandardPaths.findExecutable("lpr.cups"):
+            exe = "lpr.cups"
+        elif QStandardPaths.findExecutable("lpr"):
+            exe = "lpr"
+        elif QStandardPaths.findExecutable("lp"):
+            exe = "lp"
+        else:
+            if self.__doDeleteFile:
+                QFile.remove(fileName)
+            return False
+        
+        useCupsOptions = isCupsAvailable()
+        argsList = self._printArguments(
+            printer, fileDeletePolicy, pageSelectPolicy, useCupsOptions,
+            pageRange, exe)
+        argsList.append(fileName)
+        
+        self.__process = QProcess()
+        if qVersion() < "5.6.0":
+            self.__process.error.connect(self.__processError)
+        else:
+            self.__process.errorOccurred.connect(self.__processError)
+        self.__process.finished.connect(self.__processFinished)
+        self.__process.start(exe, argsList)
+        if not self.__process.waitForStarted(10000):
+            # it failed to start
+            self.__doCleanup(self.__doDeleteFile)
+            return False
+        
+        return True
+    
+    def __doCleanup(self, deleteFile):
+        """
+        Private method to perform some internal cleanup actions.
+        
+        @param deleteFile flag indicating to delete the print file
+        @type bool
+        """
+        if deleteFile:
+            QFile.remove(self.__fileName)
+        
+        self.__process.deleteLater()
+        self.__process = None
+        
+        if self in _FilePrintJobs:
+            _FilePrintJobs.remove(self)
+    
+    def __processError(self, error):
+        """
+        Private slot handling process errors.
+        
+        @param error error value
+        @type QProcess.ProcessError
+        """
+        self.__doCleanup(self.__doDeleteFile)
+    
+    def __processFinished(self, exitCode, exitStatus):
+        """
+        Private slot handling the end of the process.
+        
+        @param exitCode exit code of the process
+        @type int
+        @param exitStatus exit status of the process
+        @type QProcess.ExitStatus
+        """
+        self.__doCleanup(self.__doDeleteFile and (
+            exitStatus != QProcess.NormalExit or exitCode != 0))
+    
+    def _printArguments(self, printer, fileDeletePolicy, pageSelectPolicy,
+                        useCupsOptions, pageRange, variant):
+        """
+        Protected method to assemble the command line arguments for the print
+        command.
+        
+        @param printer reference to the printer to print to
+        @type QPrinter
+        @param fileDeletePolicy policy determining who deletes the file to be
+            printed (application or system)
+        @type int (0 or 1)
+        @param pageSelectPolicy policy determining who selects the pages to be
+            printed (application or system)
+        @type int (0 or 1)
+        @param useCupsOptions flag indicating to assemble the arguments for
+            CUPS
+        @type bool
+        @param pageRange string determining the page range(s) to be printed, if
+            SystemSelectsPages was given for pageSelectPolicy and user chose
+            Selection in print dialog
+        @type str
+        @param variant string identifying the print command variant
+        @type str
+        @return assembled command line arguments for the print command
+        @rtype list of str
+        """
+        if variant.startswith("lpr"):
+            variant = "lpr"
+        
+        args = []
+        args.extend(self._destination(printer, variant))
+        args.extend(self._copies(printer, variant))
+        args.extend(self._jobname(printer, variant))
+        args.extend(self._pages(printer, pageSelectPolicy, pageRange,
+                    useCupsOptions, variant))
+        if useCupsOptions:
+            args.extend(self._cupsOptions(printer))
+        args.extend(self._deleteFile(printer, fileDeletePolicy, variant))
+        if variant == "lp":
+            args.append("--")
+        
+        return args
+    
+    def _destination(self, printer, variant):
+        """
+        Protected method to assemble the printer destination arguments.
+        
+        @param printer reference to the printer to print to
+        @type QPrinter
+        @param variant string identifying the print command variant
+        @type str
+        @return assembled printer destination arguments
+        @rtype list of str
+        """
+        if variant == "lp":
+            return ["-d", printer.printerName()]
+        elif variant == "lpr":
+            return ["-P", printer.printerName()]
+        else:
+            return []
+    
+    def _copies(self, printer, variant):
+        """
+        Protected method to assemble the number of copies arguments.
+        
+        @param printer reference to the printer to print to
+        @type QPrinter
+        @param variant string identifying the print command variant
+        @type str
+        @return assembled number of copies arguments
+        @rtype list of str
+        """
+        copies = printer.copyCount()
+        if variant == "lp":
+            return ["-n", str(copies)]
+        elif variant == "lpr":
+            return ["-#{0}".format(copies)]
+        else:
+            return []
+    
+    def _jobname(self, printer, variant):
+        """
+        Protected method to assemble the jobname arguments.
+        
+        @param printer reference to the printer to print to
+        @type QPrinter
+        @param variant string identifying the print command variant
+        @type str
+        @return assembled jobname arguments
+        @rtype list of str
+        """
+        if printer.docName():
+            if variant == "lp":
+                return ["-t", printer.docName()]
+            elif variant == "lpr":
+                shortenedDocName = printer.docName()[:255]
+                return ["-J", shortenedDocName]
+        
+        return []
+    
+    def _deleteFile(self, printer, fileDeletePolicy, variant):
+        """
+        Protected method to assemble the jobname arguments.
+        
+        @param printer reference to the printer to print to
+        @type QPrinter
+        @param fileDeletePolicy policy determining who deletes the file to be
+            printed (application or system)
+        @type int (0 or 1)
+        @param variant string identifying the print command variant
+        @type str
+        @return assembled jobname arguments
+        @rtype list of str
+        """
+        if fileDeletePolicy == FilePrinter.SystemDeletesFiles and \
+                variant == "lpr":
+            return ["-r"]
+        else:
+            return []
+    
+    def _pages(self, printer, pageSelectPolicy, pageRange, useCupsOptions,
+               variant):
+        """
+        Protected method to assemble the page range(s) arguments.
+        
+        @param printer reference to the printer to print to
+        @type QPrinter
+        @param pageSelectPolicy policy determining who selects the pages to be
+            printed (application or system)
+        @type int (0 or 1)
+        @param pageRange string determining the page range(s) to be printed, if
+            SystemSelectsPages was given for pageSelectPolicy and user chose
+            Selection in print dialog
+        @type str
+        @param useCupsOptions flag indicating to assemble the arguments for
+            CUPS
+        @type bool
+        @param variant string identifying the print command variant
+        @type str
+        @return assembled page range(s) arguments
+        @rtype list of str
+        """
+        if pageSelectPolicy == FilePrinter.SystemSelectsPages:
+            if printer.printRange() == QPrinter.Selection and bool(pageRange):
+                if variant == "lp":
+                    return ["-P", pageRange]
+                elif variant == "lpr":
+                    return ["-o", "page-ranges={0}".format(pageRange)]
+            
+            if printer.printRange() == QPrinter.PageRange:
+                if variant == "lp":
+                    return ["-P", "{0}-{1}".format(
+                        printer.fromPage(), printer.toPage())]
+                elif variant == "lpr":
+                    return ["-o", "page-ranges={0}-{1}".format(
+                        printer.fromPage(), printer.toPage())]
+        
+        return []       # all pages
+    
+    def _cupsOptions(self, printer):
+        """
+        Protected method to assemble the CUPS specific arguments.
+        
+        @param printer reference to the printer to print to
+        @type QPrinter
+        @return assembled CUPS arguments
+        @rtype list of str
+        """
+        options = []
+        options.extend(self._optionMedia(printer))
+        options.extend(self._optionDoubleSidedPrinting(printer))
+        options.extend(self._optionPageOrder(printer))
+        options.extend(self._optionCollateCopies(printer))
+        options.extend(self._optionCupsProperties(printer))
+        
+        return options
+    
+    def _optionMedia(self, printer):
+        """
+        Protected method to assemble the print media arguments.
+        
+        @param printer reference to the printer to print to
+        @type QPrinter
+        @return assembled print media arguments
+        @rtype list of str
+        """
+        pageSize = self._mediaPageSize(printer)
+        paperSource = self._mediaPaperSource(printer)
+        
+        if pageSize and paperSource:
+            return ["-o", "media={0},{1}".format(pageSize, paperSource)]
+        
+        elif pageSize:
+            return ["-o", "media={0}".format(pageSize)]
+        
+        elif paperSource:
+            return ["-o", "media={0}".format(paperSource)]
+        
+        return []
+    
+    def _mediaPageSize(self, printer):
+        """
+        Protected method to get the page size argument.
+        
+        @param printer reference to the printer to print to
+        @type QPrinter
+        @return page size argument
+        @rtype str
+        """
+        pageSize = printer.pageSize()
+        if pageSize in self.__paperSizesMap:
+            return self.__paperSizesMap[pageSize]
+        else:
+            return ""
+    
+    def _mediaPaperSource(self, printer):
+        """
+        Protected method to get the paper source argument.
+        
+        @param printer reference to the printer to print to
+        @type QPrinter
+        @return paper source argument
+        @rtype str
+        """
+        paperSource = printer.paperSource()
+        if paperSource in self.__paperSourcesMap:
+            return self.__paperSourcesMap[paperSource]
+        else:
+            return ""
+    
+    def _optionDoubleSidedPrinting(self, printer):
+        """
+        Protected method to assemble the double sided printing arguments.
+        
+        @param printer reference to the printer to print to
+        @type QPrinter
+        @return assembled double sided printing arguments
+        @rtype list of str
+        """
+        duplex = printer.duplex()
+        
+        if duplex == QPrinter.DuplexNone:
+            return ["-o", "sides=one-sided"]
+        elif duplex == QPrinter.DuplexAuto:
+            if printer.orientation() == QPrinter.Landscape:
+                return ["-o", "sides=two-sided-short-edge"]
+            else:
+                return ["-o", "sides=two-sided-long-edge"]
+        elif duplex == QPrinter.DuplexLongSide:
+            return ["-o", "sides=two-sided-long-edge"]
+        elif duplex == QPrinter.DuplexShortSide:
+            return ["-o", "sides=two-sided-short-edge"]
+        else:
+            return []       # use printer default
+    
+    def _optionPageOrder(self, printer):
+        """
+        Protected method to assemble the page order arguments.
+        
+        @param printer reference to the printer to print to
+        @type QPrinter
+        @return assembled page order arguments
+        @rtype list of str
+        """
+        if printer.pageOrder() == QPrinter.LastPageFirst:
+            return ["-o", "outputorder=reverse"]
+        else:
+            return ["-o", "outputorder=normal"]
+    
+    def _optionCollateCopies(self, printer):
+        """
+        Protected method to assemble the collate copies arguments.
+        
+        @param printer reference to the printer to print to
+        @type QPrinter
+        @return assembled collate copies arguments
+        @rtype list of str
+        """
+        if printer.collateCopies():
+            return ["-o", "Collate=True"]
+        else:
+            return ["-o", "Collate=False"]
+    
+    def _optionCupsProperties(self, printer):
+        """
+        Protected method to assemble the CUPS properties arguments.
+        
+        @param printer reference to the printer to print to
+        @type QPrinter
+        @return assembled CUPS properties arguments
+        @rtype list of str
+        """
+        options = Globals.toList(printer.printEngine().property(
+            QPrintEngine.PrintEnginePropertyKey(0xfe00)))
+        
+        cupsOptions = []
+        index = 0
+        while index < len(options):
+            if options[index + 1]:
+                cupsOptions.extend(["-o", "{0}={1}".format(
+                    options[index], options[index + 1])])
+            else:
+                cupsOptions.extend(["-o", options[index]])
+            index += 2
+        
+        return cupsOptions
+
+
+def isCupsAvailable():
+    """
+    Static method to test the availability of CUPS.
+    
+    @return flag indicating the availability of CUPS
+    @rtype bool
+    """
+    if Globals.isMacPlatform():
+        # OS X/MacOS always have CUPS
+        return True
+    elif Globals.isLinuxPlatform():
+        testPrinter = QPrinter()
+        return testPrinter.supportsMultipleCopies()
+    else:
+        return False
+
+
+def printFile(printer, fileName,
+              fileDeletePolicy=FilePrinter.ApplicationDeletesFiles,
+              pageSelectPolicy=FilePrinter.ApplicationSelectsPages,
+              pageRange=""):
+    """
+    Static method to print a file.
+    
+    Note: Only CUPS and LPR on *nix systems is supported.
+    
+    @param printer reference to the printer to print to
+    @type QPrinter
+    @param fileName name (path) of the file to be printed
+    @type str
+    @param fileDeletePolicy policy determining who deletes the file to be
+        printed (application or system)
+    @type int (0 or 1)
+    @param pageSelectPolicy policy determining who selects the pages to be
+        printed (application or system)
+    @type int (0 or 1)
+    @param pageRange string determining the page range(s) to be printed, if
+        SystemSelectsPages was given for pageSelectPolicy and user chose
+        Selection in print dialog
+    @type str
+    """
+    fp = FilePrinter()
+    if fp._doPrintFile(printer, fileName, fileDeletePolicy, pageSelectPolicy,
+                       pageRange):
+        _FilePrintJobs.append(fp)
+

eric ide

mercurial