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