WebBrowser/Tools/FilePrinter.py

changeset 5182
e2782c9a43d4
child 5253
57276f763bf6
equal deleted inserted replaced
5181:1948b27d7b21 5182:e2782c9a43d4
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2016 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing an object for printing of files.
8 """
9
10 #
11 # This code is inspired by and ported from Qupzilla.
12 # Original Copyright (C) 2016 by Kevin Kofler <kevin.kofler@chello.at>
13 #
14
15 from __future__ import unicode_literals
16
17 from PyQt5.QtCore import qVersion, QFile, QStandardPaths, QProcess
18 from PyQt5.QtPrintSupport import QPrinter, QPrintEngine
19
20 import Globals
21
22
23 _FilePrintJobs = []
24
25 class FilePrinter(object):
26 """
27 Class implementing methods for printing on *nix systems.
28 """
29 #
30 # Whether file(s) get deleted by the application or by the print system.
31 #
32 ApplicationDeletesFiles = 0
33 SystemDeletesFiles = 1
34
35 #
36 # Whether pages to be printed are selected by the application or the print
37 # system.
38 #
39 # If application side, then the generated file will only contain those
40 # pages selected by the user, so FilePrinter will print all the pages in
41 # the file.
42 #
43 # If system side, then the file will contain all the pages in the
44 # document, and the print system will print the users selected print range
45 # from out of the file.
46 #
47 # Note: system side only works in CUPS, not LPR.
48 #
49 ApplicationSelectsPages = 0
50 SystemSelectsPages = 1
51
52 def __init__(self):
53 """
54 Constructor
55 """
56 self.__paperSizesMap = {
57 QPrinter.A0: "A0",
58 QPrinter.A1: "A1",
59 QPrinter.A2: "A2",
60 QPrinter.A3: "A3",
61 QPrinter.A4: "A4",
62 QPrinter.A5: "A5",
63 QPrinter.A6: "A6",
64 QPrinter.A7: "A7",
65 QPrinter.A8: "A8",
66 QPrinter.A9: "A9",
67 QPrinter.B0: "B0",
68 QPrinter.B1: "B1",
69 QPrinter.B2: "B2",
70 QPrinter.B3: "B3",
71 QPrinter.B4: "B4",
72 QPrinter.B5: "B5",
73 QPrinter.B6: "B6",
74 QPrinter.B7: "B7",
75 QPrinter.B8: "B8",
76 QPrinter.B9: "B9",
77 QPrinter.B10: "B10",
78 QPrinter.C5E: "C5",
79 QPrinter.Comm10E: "Comm10",
80 QPrinter.DLE: "DL",
81 QPrinter.Executive: "Executive",
82 QPrinter.Folio: "Folio",
83 QPrinter.Ledger: "Ledger",
84 QPrinter.Legal: "Legal",
85 QPrinter.Letter: "Letter",
86 QPrinter.Tabloid: "Tabloid",
87 }
88
89 self.__paperSourcesMap = {
90 QPrinter.Auto: "",
91 QPrinter.Cassette: "Cassette",
92 QPrinter.Envelope: "Envelope",
93 QPrinter.EnvelopeManual: "EnvelopeManual",
94 QPrinter.FormSource: "FormSource",
95 QPrinter.LargeCapacity: "LargeCapacity",
96 QPrinter.LargeFormat: "LargeFormat",
97 QPrinter.Lower: "Lower",
98 QPrinter.MaxPageSource: "MaxPageSource",
99 QPrinter.Middle: "Middle",
100 QPrinter.Manual: "Manual",
101 QPrinter.OnlyOne: "OnlyOne",
102 QPrinter.Tractor: "Tractor",
103 QPrinter.SmallFormat: "SmallFormat",
104 }
105
106 self.__process = None
107 self.__doDeleteFile = FilePrinter.ApplicationDeletesFiles
108 self.__fileName = ""
109
110 def _doPrintFile(self, printer, fileName, fileDeletePolicy,
111 pageSelectPolicy, pageRange):
112 """
113 Protected method to print a file
114
115 @param printer reference to the printer to print to
116 @type QPrinter
117 @param fileName name (path) of the file to be printed
118 @type str
119 @param fileDeletePolicy policy determining who deletes the file to be
120 printed (application or system)
121 @type int (0 or 1)
122 @param pageSelectPolicy policy determining who selects the pages to be
123 printed (application or system)
124 @type int (0 or 1)
125 @param pageRange string determining the page range(s) to be printed, if
126 SystemSelectsPages was given for pageSelectPolicy and user chose
127 Selection in print dialog
128 @type str
129 @return flag indicating successful print job submission
130 @rtype bool
131 """
132 if not QFile.exists(fileName):
133 return False
134
135 self.__fileName = fileName
136 self.__doDeleteFile = (
137 fileDeletePolicy == FilePrinter.SystemDeletesFiles)
138
139 if printer.printerState() in [QPrinter.Aborted, QPrinter.Error]:
140 if self.__doDeleteFile:
141 QFile.remove(fileName)
142 return False
143
144 #
145 # Print via lpr/lp command
146 #
147
148 #
149 # Decide what executable to use to print with, need the CUPS version
150 # of lpr if available. Some distros name the CUPS version of lpr as
151 # lpr-cups or lpr.cups so try those first before default to lpr, or
152 # failing that to lp.
153 if QStandardPaths.findExecutable("lpr-cups"):
154 exe = "lpr-cups"
155 elif QStandardPaths.findExecutable("lpr.cups"):
156 exe = "lpr.cups"
157 elif QStandardPaths.findExecutable("lpr"):
158 exe = "lpr"
159 elif QStandardPaths.findExecutable("lp"):
160 exe = "lp"
161 else:
162 if self.__doDeleteFile:
163 QFile.remove(fileName)
164 return False
165
166 useCupsOptions = isCupsAvailable()
167 argsList = self._printArguments(
168 printer, fileDeletePolicy, pageSelectPolicy, useCupsOptions,
169 pageRange, exe)
170 argsList.append(fileName)
171
172 self.__process = QProcess()
173 if qVersion() < "5.6.0":
174 self.__process.error.connect(self.__processError)
175 else:
176 self.__process.errorOccurred.connect(self.__processError)
177 self.__process.finished.connect(self.__processFinished)
178 self.__process.start(exe, argsList)
179 if not self.__process.waitForStarted(10000):
180 # it failed to start
181 self.__doCleanup(self.__doDeleteFile)
182 return False
183
184 return True
185
186 def __doCleanup(self, deleteFile):
187 """
188 Private method to perform some internal cleanup actions.
189
190 @param deleteFile flag indicating to delete the print file
191 @type bool
192 """
193 if deleteFile:
194 QFile.remove(self.__fileName)
195
196 self.__process.deleteLater()
197 self.__process = None
198
199 if self in _FilePrintJobs:
200 _FilePrintJobs.remove(self)
201
202 def __processError(self, error):
203 """
204 Private slot handling process errors.
205
206 @param error error value
207 @type QProcess.ProcessError
208 """
209 self.__doCleanup(self.__doDeleteFile)
210
211 def __processFinished(self, exitCode, exitStatus):
212 """
213 Private slot handling the end of the process.
214
215 @param exitCode exit code of the process
216 @type int
217 @param exitStatus exit status of the process
218 @type QProcess.ExitStatus
219 """
220 self.__doCleanup(self.__doDeleteFile and (
221 exitStatus != QProcess.NormalExit or exitCode != 0))
222
223 def _printArguments(self, printer, fileDeletePolicy, pageSelectPolicy,
224 useCupsOptions, pageRange, variant):
225 """
226 Protected method to assemble the command line arguments for the print
227 command.
228
229 @param printer reference to the printer to print to
230 @type QPrinter
231 @param fileDeletePolicy policy determining who deletes the file to be
232 printed (application or system)
233 @type int (0 or 1)
234 @param pageSelectPolicy policy determining who selects the pages to be
235 printed (application or system)
236 @type int (0 or 1)
237 @param useCupsOptions flag indicating to assemble the arguments for
238 CUPS
239 @type bool
240 @param pageRange string determining the page range(s) to be printed, if
241 SystemSelectsPages was given for pageSelectPolicy and user chose
242 Selection in print dialog
243 @type str
244 @param variant string identifying the print command variant
245 @type str
246 @return assembled command line arguments for the print command
247 @rtype list of str
248 """
249 if variant.startswith("lpr"):
250 variant = "lpr"
251
252 args = []
253 args.extend(self._destination(printer, variant))
254 args.extend(self._copies(printer, variant))
255 args.extend(self._jobname(printer, variant))
256 args.extend(self._pages(printer, pageSelectPolicy, pageRange,
257 useCupsOptions, variant))
258 if useCupsOptions:
259 args.extend(self._cupsOptions(printer))
260 args.extend(self._deleteFile(printer, fileDeletePolicy, variant))
261 if variant == "lp":
262 args.append("--")
263
264 return args
265
266 def _destination(self, printer, variant):
267 """
268 Protected method to assemble the printer destination arguments.
269
270 @param printer reference to the printer to print to
271 @type QPrinter
272 @param variant string identifying the print command variant
273 @type str
274 @return assembled printer destination arguments
275 @rtype list of str
276 """
277 if variant == "lp":
278 return ["-d", printer.printerName()]
279 elif variant == "lpr":
280 return ["-P", printer.printerName()]
281 else:
282 return []
283
284 def _copies(self, printer, variant):
285 """
286 Protected method to assemble the number of copies arguments.
287
288 @param printer reference to the printer to print to
289 @type QPrinter
290 @param variant string identifying the print command variant
291 @type str
292 @return assembled number of copies arguments
293 @rtype list of str
294 """
295 copies = printer.copyCount()
296 if variant == "lp":
297 return ["-n", str(copies)]
298 elif variant == "lpr":
299 return ["-#{0}".format(copies)]
300 else:
301 return []
302
303 def _jobname(self, printer, variant):
304 """
305 Protected method to assemble the jobname arguments.
306
307 @param printer reference to the printer to print to
308 @type QPrinter
309 @param variant string identifying the print command variant
310 @type str
311 @return assembled jobname arguments
312 @rtype list of str
313 """
314 if printer.docName():
315 if variant == "lp":
316 return ["-t", printer.docName()]
317 elif variant == "lpr":
318 shortenedDocName = printer.docName()[:255]
319 return ["-J", shortenedDocName]
320
321 return []
322
323 def _deleteFile(self, printer, fileDeletePolicy, variant):
324 """
325 Protected method to assemble the jobname arguments.
326
327 @param printer reference to the printer to print to
328 @type QPrinter
329 @param fileDeletePolicy policy determining who deletes the file to be
330 printed (application or system)
331 @type int (0 or 1)
332 @param variant string identifying the print command variant
333 @type str
334 @return assembled jobname arguments
335 @rtype list of str
336 """
337 if fileDeletePolicy == FilePrinter.SystemDeletesFiles and \
338 variant == "lpr":
339 return ["-r"]
340 else:
341 return []
342
343 def _pages(self, printer, pageSelectPolicy, pageRange, useCupsOptions,
344 variant):
345 """
346 Protected method to assemble the page range(s) arguments.
347
348 @param printer reference to the printer to print to
349 @type QPrinter
350 @param pageSelectPolicy policy determining who selects the pages to be
351 printed (application or system)
352 @type int (0 or 1)
353 @param pageRange string determining the page range(s) to be printed, if
354 SystemSelectsPages was given for pageSelectPolicy and user chose
355 Selection in print dialog
356 @type str
357 @param useCupsOptions flag indicating to assemble the arguments for
358 CUPS
359 @type bool
360 @param variant string identifying the print command variant
361 @type str
362 @return assembled page range(s) arguments
363 @rtype list of str
364 """
365 if pageSelectPolicy == FilePrinter.SystemSelectsPages:
366 if printer.printRange() == QPrinter.Selection and bool(pageRange):
367 if variant == "lp":
368 return ["-P", pageRange]
369 elif variant == "lpr":
370 return ["-o", "page-ranges={0}".format(pageRange)]
371
372 if printer.printRange() == QPrinter.PageRange:
373 if variant == "lp":
374 return ["-P", "{0}-{1}".format(
375 printer.fromPage(), printer.toPage())]
376 elif variant == "lpr":
377 return ["-o", "page-ranges={0}-{1}".format(
378 printer.fromPage(), printer.toPage())]
379
380 return [] # all pages
381
382 def _cupsOptions(self, printer):
383 """
384 Protected method to assemble the CUPS specific arguments.
385
386 @param printer reference to the printer to print to
387 @type QPrinter
388 @return assembled CUPS arguments
389 @rtype list of str
390 """
391 options = []
392 options.extend(self._optionMedia(printer))
393 options.extend(self._optionDoubleSidedPrinting(printer))
394 options.extend(self._optionPageOrder(printer))
395 options.extend(self._optionCollateCopies(printer))
396 options.extend(self._optionCupsProperties(printer))
397
398 return options
399
400 def _optionMedia(self, printer):
401 """
402 Protected method to assemble the print media arguments.
403
404 @param printer reference to the printer to print to
405 @type QPrinter
406 @return assembled print media arguments
407 @rtype list of str
408 """
409 pageSize = self._mediaPageSize(printer)
410 paperSource = self._mediaPaperSource(printer)
411
412 if pageSize and paperSource:
413 return ["-o", "media={0},{1}".format(pageSize, paperSource)]
414
415 elif pageSize:
416 return ["-o", "media={0}".format(pageSize)]
417
418 elif paperSource:
419 return ["-o", "media={0}".format(paperSource)]
420
421 return []
422
423 def _mediaPageSize(self, printer):
424 """
425 Protected method to get the page size argument.
426
427 @param printer reference to the printer to print to
428 @type QPrinter
429 @return page size argument
430 @rtype str
431 """
432 pageSize = printer.pageSize()
433 if pageSize in self.__paperSizesMap:
434 return self.__paperSizesMap[pageSize]
435 else:
436 return ""
437
438 def _mediaPaperSource(self, printer):
439 """
440 Protected method to get the paper source argument.
441
442 @param printer reference to the printer to print to
443 @type QPrinter
444 @return paper source argument
445 @rtype str
446 """
447 paperSource = printer.paperSource()
448 if paperSource in self.__paperSourcesMap:
449 return self.__paperSourcesMap[paperSource]
450 else:
451 return ""
452
453 def _optionDoubleSidedPrinting(self, printer):
454 """
455 Protected method to assemble the double sided printing arguments.
456
457 @param printer reference to the printer to print to
458 @type QPrinter
459 @return assembled double sided printing arguments
460 @rtype list of str
461 """
462 duplex = printer.duplex()
463
464 if duplex == QPrinter.DuplexNone:
465 return ["-o", "sides=one-sided"]
466 elif duplex == QPrinter.DuplexAuto:
467 if printer.orientation() == QPrinter.Landscape:
468 return ["-o", "sides=two-sided-short-edge"]
469 else:
470 return ["-o", "sides=two-sided-long-edge"]
471 elif duplex == QPrinter.DuplexLongSide:
472 return ["-o", "sides=two-sided-long-edge"]
473 elif duplex == QPrinter.DuplexShortSide:
474 return ["-o", "sides=two-sided-short-edge"]
475 else:
476 return [] # use printer default
477
478 def _optionPageOrder(self, printer):
479 """
480 Protected method to assemble the page order arguments.
481
482 @param printer reference to the printer to print to
483 @type QPrinter
484 @return assembled page order arguments
485 @rtype list of str
486 """
487 if printer.pageOrder() == QPrinter.LastPageFirst:
488 return ["-o", "outputorder=reverse"]
489 else:
490 return ["-o", "outputorder=normal"]
491
492 def _optionCollateCopies(self, printer):
493 """
494 Protected method to assemble the collate copies arguments.
495
496 @param printer reference to the printer to print to
497 @type QPrinter
498 @return assembled collate copies arguments
499 @rtype list of str
500 """
501 if printer.collateCopies():
502 return ["-o", "Collate=True"]
503 else:
504 return ["-o", "Collate=False"]
505
506 def _optionCupsProperties(self, printer):
507 """
508 Protected method to assemble the CUPS properties arguments.
509
510 @param printer reference to the printer to print to
511 @type QPrinter
512 @return assembled CUPS properties arguments
513 @rtype list of str
514 """
515 options = Globals.toList(printer.printEngine().property(
516 QPrintEngine.PrintEnginePropertyKey(0xfe00)))
517
518 cupsOptions = []
519 index = 0
520 while index < len(options):
521 if options[index + 1]:
522 cupsOptions.extend(["-o", "{0}={1}".format(
523 options[index], options[index + 1])])
524 else:
525 cupsOptions.extend(["-o", options[index]])
526 index += 2
527
528 return cupsOptions
529
530
531 def isCupsAvailable():
532 """
533 Static method to test the availability of CUPS.
534
535 @return flag indicating the availability of CUPS
536 @rtype bool
537 """
538 if Globals.isMacPlatform():
539 # OS X/MacOS always have CUPS
540 return True
541 elif Globals.isLinuxPlatform():
542 testPrinter = QPrinter()
543 return testPrinter.supportsMultipleCopies()
544 else:
545 return False
546
547
548 def printFile(printer, fileName,
549 fileDeletePolicy=FilePrinter.ApplicationDeletesFiles,
550 pageSelectPolicy=FilePrinter.ApplicationSelectsPages,
551 pageRange=""):
552 """
553 Static method to print a file.
554
555 Note: Only CUPS and LPR on *nix systems is supported.
556
557 @param printer reference to the printer to print to
558 @type QPrinter
559 @param fileName name (path) of the file to be printed
560 @type str
561 @param fileDeletePolicy policy determining who deletes the file to be
562 printed (application or system)
563 @type int (0 or 1)
564 @param pageSelectPolicy policy determining who selects the pages to be
565 printed (application or system)
566 @type int (0 or 1)
567 @param pageRange string determining the page range(s) to be printed, if
568 SystemSelectsPages was given for pageSelectPolicy and user chose
569 Selection in print dialog
570 @type str
571 """
572 fp = FilePrinter()
573 if fp._doPrintFile(printer, fileName, fileDeletePolicy, pageSelectPolicy,
574 pageRange):
575 _FilePrintJobs.append(fp)
576

eric ide

mercurial