Merge lp:~jonas-drange/ubuntu-ui-extras/add-loadprinter into lp:ubuntu-ui-extras

Proposed by Jonas G. Drange
Status: Superseded
Proposed branch: lp:~jonas-drange/ubuntu-ui-extras/add-loadprinter
Merge into: lp:ubuntu-ui-extras
Diff against target: 9629 lines (+9277/-4)
55 files modified
debian/control (+3/-0)
modules/Ubuntu/Components/Extras/CMakeLists.txt (+1/-0)
modules/Ubuntu/Components/Extras/Example/PrinterQueue.qml (+117/-0)
modules/Ubuntu/Components/Extras/Example/Printers.qml (+603/-0)
modules/Ubuntu/Components/Extras/Printers/CMakeLists.txt (+68/-0)
modules/Ubuntu/Components/Extras/Printers/backend/backend.cpp (+272/-0)
modules/Ubuntu/Components/Extras/Printers/backend/backend.h (+201/-0)
modules/Ubuntu/Components/Extras/Printers/backend/backend_cups.cpp (+709/-0)
modules/Ubuntu/Components/Extras/Printers/backend/backend_cups.h (+131/-0)
modules/Ubuntu/Components/Extras/Printers/backend/backend_pdf.cpp (+119/-0)
modules/Ubuntu/Components/Extras/Printers/backend/backend_pdf.h (+47/-0)
modules/Ubuntu/Components/Extras/Printers/cups/ippclient.cpp (+988/-0)
modules/Ubuntu/Components/Extras/Printers/cups/ippclient.h (+121/-0)
modules/Ubuntu/Components/Extras/Printers/cups/printerdriverloader.cpp (+127/-0)
modules/Ubuntu/Components/Extras/Printers/cups/printerdriverloader.h (+61/-0)
modules/Ubuntu/Components/Extras/Printers/cups/printerloader.cpp (+53/-0)
modules/Ubuntu/Components/Extras/Printers/cups/printerloader.h (+49/-0)
modules/Ubuntu/Components/Extras/Printers/enums.h (+106/-0)
modules/Ubuntu/Components/Extras/Printers/i18n.cpp (+42/-0)
modules/Ubuntu/Components/Extras/Printers/i18n.h (+29/-0)
modules/Ubuntu/Components/Extras/Printers/models/drivermodel.cpp (+175/-0)
modules/Ubuntu/Components/Extras/Printers/models/drivermodel.h (+82/-0)
modules/Ubuntu/Components/Extras/Printers/models/jobmodel.cpp (+390/-0)
modules/Ubuntu/Components/Extras/Printers/models/jobmodel.h (+125/-0)
modules/Ubuntu/Components/Extras/Printers/models/printermodel.cpp (+503/-0)
modules/Ubuntu/Components/Extras/Printers/models/printermodel.h (+154/-0)
modules/Ubuntu/Components/Extras/Printers/org.cups.cupsd.Notifier.xml (+146/-0)
modules/Ubuntu/Components/Extras/Printers/plugin.cpp (+58/-0)
modules/Ubuntu/Components/Extras/Printers/plugin.h (+33/-0)
modules/Ubuntu/Components/Extras/Printers/printer/printer.cpp (+315/-0)
modules/Ubuntu/Components/Extras/Printers/printer/printer.h (+96/-0)
modules/Ubuntu/Components/Extras/Printers/printer/printerjob.cpp (+514/-0)
modules/Ubuntu/Components/Extras/Printers/printer/printerjob.h (+174/-0)
modules/Ubuntu/Components/Extras/Printers/printer/printersignalhandler.cpp (+69/-0)
modules/Ubuntu/Components/Extras/Printers/printer/printersignalhandler.h (+55/-0)
modules/Ubuntu/Components/Extras/Printers/printers/printers.cpp (+243/-0)
modules/Ubuntu/Components/Extras/Printers/printers/printers.h (+105/-0)
modules/Ubuntu/Components/Extras/Printers/printers_global.h (+23/-0)
modules/Ubuntu/Components/Extras/Printers/qmldir (+2/-0)
modules/Ubuntu/Components/Extras/Printers/structs.h (+97/-0)
modules/Ubuntu/Components/Extras/Printers/utils.h (+110/-0)
po/CMakeLists.txt (+2/-1)
po/ubuntu-ui-extras.pot (+27/-3)
tests/unittests/CMakeLists.txt (+2/-0)
tests/unittests/Printers/CMakeLists.txt (+48/-0)
tests/unittests/Printers/mockbackend.h (+390/-0)
tests/unittests/Printers/tst_drivermodel.cpp (+136/-0)
tests/unittests/Printers/tst_jobfilter.cpp (+55/-0)
tests/unittests/Printers/tst_jobmodel.cpp (+120/-0)
tests/unittests/Printers/tst_printer.cpp (+284/-0)
tests/unittests/Printers/tst_printerfilter.cpp (+126/-0)
tests/unittests/Printers/tst_printerjob.cpp (+318/-0)
tests/unittests/Printers/tst_printermodel.cpp (+161/-0)
tests/unittests/Printers/tst_printers.cpp (+248/-0)
tests/unittests/Printers/tst_signalhandler.cpp (+44/-0)
To merge this branch: bzr merge lp:~jonas-drange/ubuntu-ui-extras/add-loadprinter
Reviewer Review Type Date Requested Status
Ubuntu Phablet Team Pending
Review via email: mp+318214@code.launchpad.net

Commit message

adds loadPrinter method on Printers

Description of the change

adds loadPrinter method on Printers

To post a comment you must log in.

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/control'
2--- debian/control 2016-08-24 15:46:52 +0000
3+++ debian/control 2017-02-24 12:21:53 +0000
4@@ -3,8 +3,10 @@
5 Priority: optional
6 Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
7 Build-Depends: cmake (>= 2.8.9),
8+ cmake-extras (>= 0.10),
9 debhelper (>= 9),
10 gettext,
11+ libcups2-dev,
12 pkg-config,
13 python:any,
14 qt5-default,
15@@ -27,6 +29,7 @@
16 Architecture: any
17 Depends: ${misc:Depends},
18 ${shlibs:Depends},
19+ libqt5printsupport5,
20 qml-module-qtquick2,
21 qml-module-ubuntu-components,
22 qml-module-qtquick-window2,
23
24=== modified file 'modules/Ubuntu/Components/Extras/CMakeLists.txt'
25--- modules/Ubuntu/Components/Extras/CMakeLists.txt 2016-12-08 14:26:41 +0000
26+++ modules/Ubuntu/Components/Extras/CMakeLists.txt 2017-02-24 12:21:53 +0000
27@@ -13,4 +13,5 @@
28 add_subdirectory(plugin)
29 add_subdirectory(Example)
30 add_subdirectory(PhotoEditor)
31+add_subdirectory(Printers)
32 add_subdirectory(TabsBar)
33
34=== added file 'modules/Ubuntu/Components/Extras/Example/PrinterQueue.qml'
35--- modules/Ubuntu/Components/Extras/Example/PrinterQueue.qml 1970-01-01 00:00:00 +0000
36+++ modules/Ubuntu/Components/Extras/Example/PrinterQueue.qml 2017-02-24 12:21:53 +0000
37@@ -0,0 +1,117 @@
38+/*
39+ * Copyright 2017 Canonical Ltd.
40+ *
41+ * This program is free software; you can redistribute it and/or modify
42+ * it under the terms of the GNU Lesser General Public License as published by
43+ * the Free Software Foundation; version 3.
44+ *
45+ * This program is distributed in the hope that it will be useful,
46+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
47+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
48+ * GNU Lesser General Public License for more details.
49+ *
50+ * You should have received a copy of the GNU Lesser General Public License
51+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
52+ *
53+ * Authored by Jonas G. Drange <jonas.drange@canonical.com>
54+ * Andrew Hayzen <andrew.hayzen@canonical.com>
55+ */
56+
57+import QtQuick 2.4
58+import QtQuick.Layouts 1.1
59+import Ubuntu.Components 1.3
60+import Ubuntu.Components.ListItems 1.3 as ListItems
61+import Ubuntu.Components.Extras.Printers 0.1
62+
63+MainView {
64+ width: units.gu(50)
65+ height: units.gu(90)
66+
67+ Component {
68+ id: queuePage
69+
70+ Page {
71+ header: PageHeader {
72+ title: "Queue: " + printer.name
73+ flickable: queueView
74+ }
75+ visible: false
76+
77+ property var printer
78+
79+ ListView {
80+ id: queueView
81+ anchors {
82+ fill: parent
83+ }
84+ delegate: ListItem {
85+ height: modelLayout.height + (divider.visible ? divider.height : 0)
86+ ListItemLayout {
87+ id: modelLayout
88+ title.text: displayName
89+ subtitle.text: "Job: " + model.id + " State: " + model.state
90+ + " Color: " + model.colorModel + " CreationTime: "
91+ + model.creationTime + " PageRange: "
92+ + model.printRange + " Messages: " + model.messages;
93+ subtitle.wrapMode: Text.WrapAtWordBoundaryOrAnywhere
94+ subtitle.maximumLineCount: 3
95+ }
96+ onClicked: {
97+ console.debug("Cancel:", printer.name, model.id);
98+ Printers.cancelJob(printer.name, model.id);
99+ }
100+ }
101+ model: printer.jobs
102+
103+ Label {
104+ anchors {
105+ centerIn: parent
106+ }
107+ text: "Empty queue"
108+ visible: queueView.count === 0
109+ }
110+ }
111+ }
112+ }
113+
114+ PageStack {
115+ id: pageStack
116+
117+ Page {
118+ id: printersPage
119+ header: PageHeader {
120+ title: "Printers"
121+ flickable: printerList
122+ }
123+ visible: false
124+
125+ ListView {
126+ id: printerList
127+ anchors { fill: parent }
128+ model: Printers.allPrintersWithPdf
129+ delegate: ListItem {
130+ height: modelLayout.height + (divider.visible ? divider.height : 0)
131+ ListItemLayout {
132+ id: modelLayout
133+ title.text: displayName
134+ title.font.bold: model.default
135+ subtitle.text: description
136+
137+ Icon {
138+ id: icon
139+ width: height
140+ height: units.gu(2.5)
141+ name: "printer-symbolic"
142+ SlotsLayout.position: SlotsLayout.First
143+ }
144+
145+ ProgressionSlot {}
146+ }
147+ onClicked: pageStack.push(queuePage, { printer: model })
148+ }
149+ }
150+ }
151+
152+ Component.onCompleted: push(printersPage)
153+ }
154+}
155
156=== added file 'modules/Ubuntu/Components/Extras/Example/Printers.qml'
157--- modules/Ubuntu/Components/Extras/Example/Printers.qml 1970-01-01 00:00:00 +0000
158+++ modules/Ubuntu/Components/Extras/Example/Printers.qml 2017-02-24 12:21:53 +0000
159@@ -0,0 +1,603 @@
160+/*
161+ * Copyright 2017 Canonical Ltd.
162+ *
163+ * This program is free software; you can redistribute it and/or modify
164+ * it under the terms of the GNU Lesser General Public License as published by
165+ * the Free Software Foundation; version 3.
166+ *
167+ * This program is distributed in the hope that it will be useful,
168+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
169+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
170+ * GNU Lesser General Public License for more details.
171+ *
172+ * You should have received a copy of the GNU Lesser General Public License
173+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
174+ *
175+ * Authored by Jonas G. Drange <jonas.drange@canonical.com>
176+ */
177+
178+import QtQuick 2.4
179+import QtQuick.Layouts 1.1
180+import Ubuntu.Components 1.3
181+import Ubuntu.Components.Popups 1.3
182+import Ubuntu.Components.ListItems 1.3 as ListItems
183+import Ubuntu.Components.Extras.Printers 0.1
184+
185+MainView {
186+ width: units.gu(50)
187+ height: units.gu(90)
188+
189+ Component {
190+ id: printerPage
191+
192+ Page {
193+ visible: false
194+ property var printer
195+ header: PageHeader {
196+ id: printerPageHeader
197+ title: printer.name
198+ flickable: printerFlickable
199+ }
200+
201+ Component {
202+ id: printerPageNotYetLoaded
203+
204+ Item {
205+ anchors.fill: parent
206+ ActivityIndicator {
207+ anchors.centerIn: parent
208+ running: true
209+ }
210+ }
211+ }
212+
213+ Component.onCompleted: {
214+ printer.description;
215+ }
216+
217+ Flickable {
218+ id: printerFlickable
219+ anchors.fill: parent
220+
221+ Loader {
222+ id: printerPageBitsLoader
223+ anchors.fill: parent
224+ sourceComponent: printer.isLoaded ? printerPageLoaded : printerPageNotYetLoaded
225+ }
226+ }
227+
228+ Component {
229+ id: printerPageLoaded
230+
231+ Column {
232+ spacing: units.gu(2)
233+ anchors {
234+ top: parent.top
235+ topMargin: units.gu(2)
236+ left: parent.left
237+ right: parent.right
238+ }
239+
240+ ListItems.Standard {
241+ anchors {
242+ left: parent.left
243+ right: parent.right
244+ }
245+ text: "Enabled"
246+
247+ control: Switch {
248+ checked: printer.printerEnabled
249+ onCheckedChanged: printer.printerEnabled = checked
250+ }
251+ }
252+
253+ ListItems.Standard {
254+ anchors {
255+ left: parent.left
256+ right: parent.right
257+ }
258+ text: "Accepting jobs"
259+
260+ control: Switch {
261+ checked: printer.acceptJobs
262+ onCheckedChanged: printer.acceptJobs = checked
263+ }
264+ }
265+
266+ ListItems.Standard {
267+ anchors {
268+ left: parent.left
269+ right: parent.right
270+ }
271+ text: "Jobs"
272+ progression: true
273+ onClicked: pageStack.push(jobPage, { printer: printer })
274+ }
275+
276+ Label {
277+ anchors {
278+ left: parent.left
279+ right: parent.right
280+ margins: units.gu(2)
281+ }
282+ text: "Description"
283+ }
284+
285+ ListItems.SingleControl {
286+ anchors {
287+ left: parent.left
288+ right: parent.right
289+ }
290+
291+ control: TextField {
292+ anchors {
293+ margins: units.gu(1)
294+ left: parent.left
295+ right: parent.right
296+
297+ }
298+ text: printer.description
299+ onTextChanged: printer.description = text
300+ }
301+ }
302+
303+
304+ ListItems.ValueSelector {
305+ anchors {
306+ left: parent.left
307+ right: parent.right
308+ }
309+ enabled: values.length > 1
310+ text: "Duplex"
311+ values: printer.supportedDuplexModes
312+ onSelectedIndexChanged: printer.duplexMode = selectedIndex
313+ Component.onCompleted: {
314+ if (enabled) {
315+ selectedIndex = printer.duplexMode
316+ }
317+ }
318+ }
319+
320+ ListItems.ValueSelector {
321+ anchors {
322+ left: parent.left
323+ right: parent.right
324+ }
325+ text: "Page size"
326+ values: printer.supportedPageSizes
327+ onSelectedIndexChanged: printer.pageSize = selectedIndex
328+ Component.onCompleted: selectedIndex = printer.supportedPageSizes.indexOf(printer.pageSize)
329+ }
330+
331+ ListItems.ValueSelector {
332+ anchors {
333+ left: parent.left
334+ right: parent.right
335+ }
336+ visible: printer.supportedColorModels.length
337+ text: "Color model"
338+ values: printer.supportedColorModels
339+ enabled: values.length > 1
340+ onSelectedIndexChanged: printer.colorModel = selectedIndex
341+ Component.onCompleted: {
342+ if (enabled)
343+ selectedIndex = printer.colorModel
344+ }
345+ }
346+
347+ ListItems.ValueSelector {
348+ anchors {
349+ left: parent.left
350+ right: parent.right
351+ }
352+ visible: printer.supportedPrintQualities.length
353+ text: "Quality"
354+ values: printer.supportedPrintQualities
355+ enabled: values.length > 1
356+ onSelectedIndexChanged: printer.printQuality = selectedIndex
357+ Component.onCompleted: {
358+ if (enabled)
359+ selectedIndex = printer.printQuality
360+ }
361+ }
362+ }
363+ }
364+ }
365+ }
366+
367+ Component {
368+ id: jobPage
369+ Page {
370+ property var printer
371+ header: PageHeader {
372+ id: jobPageHeader
373+ title: "%1 (%2 jobs)".arg(printer.name).arg(jobList.count)
374+ flickable: jobList
375+ }
376+
377+ ListView {
378+ id: jobList
379+ anchors.fill: parent
380+ model: printer.jobs
381+ delegate: ListItem {
382+ height: jobLayout.height + (divider.visible ? divider.height : 0)
383+ ListItemLayout {
384+ id: jobLayout
385+ title.text: displayName
386+
387+ Icon {
388+ id: icon
389+ width: height
390+ height: units.gu(2.5)
391+ name: "stock_document"
392+ SlotsLayout.position: SlotsLayout.First
393+ }
394+ }
395+ }
396+ }
397+ }
398+ }
399+
400+
401+ Component {
402+ id: allJobsPage
403+ Page {
404+ header: PageHeader {
405+ id: allJobsHeader
406+ title: "Printer jobs"
407+ flickable: jobsList
408+ }
409+
410+ ListView {
411+ id: jobsList
412+ anchors.fill: parent
413+ model: Printers.printJobs
414+ delegate: ListItem {
415+ height: jobsLayout.height + (divider.visible ? divider.height : 0)
416+ ListItemLayout {
417+ id: jobsLayout
418+ title.text: displayName
419+
420+ Icon {
421+ id: icon
422+ width: height
423+ height: units.gu(2.5)
424+ name: "stock_document"
425+ SlotsLayout.position: SlotsLayout.First
426+ }
427+ }
428+ }
429+ }
430+ }
431+ }
432+
433+
434+ PageStack {
435+ id: pageStack
436+
437+ Component.onCompleted: push(printersPage)
438+
439+ Page {
440+ id: printersPage
441+ header: PageHeader {
442+ title: "Printers"
443+ flickable: printerList
444+ trailingActionBar {
445+ actions: [
446+ Action {
447+ iconName: "add"
448+ text: "Add printer"
449+ onTriggered: pageStack.push(addPrinterPageComponent)
450+ },
451+ Action {
452+ iconName: "document-print"
453+ text: "Printer jobs"
454+ onTriggered: pageStack.push(allJobsPage)
455+ }
456+ ]
457+ }
458+ }
459+ visible: false
460+
461+ ListView {
462+ id: printerList
463+ anchors { fill: parent }
464+ model: Printers.allPrintersWithPdf
465+ delegate: ListItem {
466+ height: modelLayout.height + (divider.visible ? divider.height : 0)
467+ trailingActions: ListItemActions {
468+ actions: [
469+ Action {
470+ iconName: "delete"
471+ onTriggered: {
472+ if (!Printers.removePrinter(model.name)) {
473+ console.error('failed to remove printer', Printers.lastMessage);
474+ }
475+ }
476+ },
477+ Action {
478+ iconName: model.default ? "starred" : "non-starred"
479+ enabled: !model.default
480+ onTriggered: Printers.defaultPrinterName = model.name
481+ }
482+
483+ ]
484+ }
485+ ListItemLayout {
486+ id: modelLayout
487+ title.text: displayName
488+ title.font.bold: model.default
489+
490+ Icon {
491+ id: icon
492+ width: height
493+ height: units.gu(2.5)
494+ name: "printer-symbolic"
495+ SlotsLayout.position: SlotsLayout.First
496+ }
497+
498+ ProgressionSlot {}
499+ }
500+ onClicked: pageStack.push(printerPage, { printer: model })
501+ }
502+ }
503+ }
504+ }
505+
506+ Component {
507+ id: addPrinterPageComponent
508+ Page {
509+ id: addPrinterPage
510+ states: [
511+ State {
512+ name: "success"
513+ PropertyChanges {
514+ target: okAction
515+ enabled: false
516+ }
517+ PropertyChanges {
518+ target: closeAction
519+ enabled: false
520+ }
521+ PropertyChanges {
522+ target: addPrinterCol
523+ enabled: false
524+ }
525+ StateChangeScript {
526+ script: okTimer.start()
527+ }
528+ },
529+ State {
530+ name: "failure"
531+ PropertyChanges {
532+ target: errorMessageContainer
533+ visible: true
534+ }
535+ }
536+ ]
537+ header: PageHeader {
538+ title: "Add printer"
539+ flickable: addPrinterFlickable
540+ leadingActionBar.actions: [
541+ Action {
542+ id: closeAction
543+ iconName: "close"
544+ text: "Abort"
545+ onTriggered: pageStack.pop()
546+ }
547+ ]
548+ trailingActionBar {
549+ actions: [
550+ Action {
551+ id: okAction
552+ iconName: "ok"
553+ text: "Complete"
554+ onTriggered: {
555+ var ret;
556+ if (driverSelector.selectedIndex == 0) {
557+ ret = Printers.addPrinter(
558+ printerName.text,
559+ driversView.selectedDriver,
560+ printerUri.text,
561+ printerDescription.text,
562+ printerLocation.text
563+ );
564+ } else {
565+ ret = Printers.addPrinterWithPpdFile(
566+ printerName.text,
567+ printerPpd.text,
568+ printerUri.text,
569+ printerDescription.text,
570+ printerLocation.text
571+ );
572+ }
573+ if (ret) {
574+ addPrinterPage.state = "success"
575+ } else {
576+ errorMessage.text = Printers.lastMessage;
577+ addPrinterPage.state = "failure"
578+ }
579+ }
580+ }
581+ ]
582+ }
583+ }
584+
585+ Component.onCompleted: {
586+ Printers.prepareToAddPrinter();
587+ }
588+
589+ Timer {
590+ id: okTimer
591+ interval: 2000
592+ onTriggered: pageStack.pop();
593+ }
594+
595+ Flickable {
596+ id: addPrinterFlickable
597+ anchors.fill: parent
598+
599+ Column {
600+ id: addPrinterCol
601+ property bool enabled: true
602+ anchors {
603+ left: parent.left
604+ right: parent.right
605+ }
606+
607+ Item {
608+ id: errorMessageContainer
609+ visible: false
610+ anchors {
611+ left: parent.left
612+ right: parent.right
613+ margins: units.gu(2)
614+ }
615+ height: units.gu(6)
616+ Label {
617+ id: errorMessage
618+ anchors {
619+ top: parent.top
620+ topMargin: units.gu(2)
621+ horizontalCenter: parent.horizontalCenter
622+ }
623+ }
624+
625+ }
626+
627+ ListItems.Standard {
628+ text: "Device URI"
629+ control: TextField {
630+ id: printerUri
631+ placeholderText: "ipp://server.local/my-queue"
632+ }
633+ enabled: parent.enabled
634+ }
635+
636+ ListItems.ValueSelector {
637+ id: driverSelector
638+ anchors {
639+ left: parent.left
640+ right: parent.right
641+ }
642+ text: "Choose driver"
643+ values: [
644+ "Select printer from database",
645+ "Provide PPD file"
646+ ]
647+ enabled: parent.enabled
648+ }
649+
650+ ListItems.Standard {
651+ anchors {
652+ left: parent.left
653+ right: parent.right
654+ }
655+ text: "Filter drivers"
656+ control: TextField {
657+ id: driverFilter
658+ onTextChanged: Printers.driverFilter = text
659+ }
660+ visible: driverSelector.selectedIndex == 0
661+ enabled: parent.enabled
662+ }
663+
664+ ListView {
665+ id: driversView
666+ property string selectedDriver
667+ property bool loading: true
668+ visible: driverSelector.selectedIndex == 0
669+ model: Printers.drivers
670+ anchors { left: parent.left; right: parent.right }
671+ height: units.gu(30)
672+ clip: true
673+ enabled: parent.enabled
674+ highlightFollowsCurrentItem: false
675+ highlight: Rectangle {
676+ z: 0
677+ y: driversView.currentItem.y
678+ width: driversView.currentItem.width
679+ height: driversView.currentItem.height
680+ color: theme.palette.selected.background
681+ }
682+ delegate: ListItem {
683+ height: driverLayout.height + (divider.visible ? divider.height : 0)
684+ ListItemLayout {
685+ id: driverLayout
686+ title.text: displayName
687+ subtitle.text: name
688+ summary.text: deviceId
689+ }
690+ onClicked: {
691+ driversView.selectedDriver = name
692+ driversView.currentIndex = index
693+ }
694+ }
695+
696+ ActivityIndicator {
697+ anchors.centerIn: parent
698+ running: parent.loading
699+ }
700+
701+ Connections {
702+ target: driversView
703+ onCountChanged: {
704+ target = null;
705+ driversView.loading = false;
706+ }
707+ }
708+ }
709+
710+ ListItems.Standard {
711+ text: "PPD File"
712+ visible: driverSelector.selectedIndex == 1
713+ control: TextField {
714+ id: printerPpd
715+ placeholderText: "/usr/share/cups/foo.ppd"
716+ }
717+ enabled: parent.enabled
718+ }
719+
720+ ListItems.Standard {
721+ anchors {
722+ left: parent.left
723+ right: parent.right
724+ }
725+ text: "Printer name"
726+ control: TextField {
727+ id: printerName
728+ placeholderText: "laserjet"
729+ }
730+ enabled: parent.enabled
731+ }
732+
733+ ListItems.Standard {
734+ anchors {
735+ left: parent.left
736+ right: parent.right
737+ }
738+ text: "Description (optional)"
739+ control: TextField {
740+ id: printerDescription
741+ placeholderText: "HP Laserjet with Duplexer"
742+ }
743+ enabled: parent.enabled
744+ }
745+
746+ ListItems.Standard {
747+ anchors {
748+ left: parent.left
749+ right: parent.right
750+ }
751+ text: "Location (optional)"
752+ control: TextField {
753+ id: printerLocation
754+ placeholderText: "Lab 1"
755+ }
756+ enabled: parent.enabled
757+ }
758+ }
759+ }
760+ }
761+ }
762+}
763
764=== added directory 'modules/Ubuntu/Components/Extras/Printers'
765=== added file 'modules/Ubuntu/Components/Extras/Printers/CMakeLists.txt'
766--- modules/Ubuntu/Components/Extras/Printers/CMakeLists.txt 1970-01-01 00:00:00 +0000
767+++ modules/Ubuntu/Components/Extras/Printers/CMakeLists.txt 2017-02-24 12:21:53 +0000
768@@ -0,0 +1,68 @@
769+project(UbuntuComponentsExtrasPrintersQml)
770+
771+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fno-permissive -pedantic -Wall -Wextra")
772+
773+add_definitions(-DUBUNTUCOMPONENTSEXTRASPRINTERS_LIBRARY)
774+
775+include(FindCups)
776+
777+include_directories(${CMAKE_CURRENT_BINARY_DIR} ${CUPS_INCLUDE_DIR})
778+
779+find_package(Qt5Gui REQUIRED)
780+find_package(Qt5PrintSupport REQUIRED)
781+find_package(Qt5Qml REQUIRED)
782+find_package(Qt5DBus REQUIRED)
783+find_package(Qt5Concurrent REQUIRED)
784+
785+if(NOT CUPS_FOUND)
786+message(FATAL_ERROR "Could not find cups.")
787+endif()
788+
789+qt5_add_dbus_interface(
790+ GEN_SOURCES
791+ ${CMAKE_CURRENT_SOURCE_DIR}/org.cups.cupsd.Notifier.xml
792+ cupsdnotifier)
793+
794+add_library(UbuntuComponentsExtrasPrintersQml SHARED
795+ ${GEN_SOURCES}
796+ backend/backend.cpp
797+ backend/backend_cups.cpp
798+ backend/backend_pdf.cpp
799+
800+ cups/ippclient.cpp
801+ cups/printerdriverloader.cpp
802+ cups/printerloader.cpp
803+
804+ models/drivermodel.cpp
805+ models/jobmodel.cpp
806+ models/printermodel.cpp
807+
808+ printer/printer.cpp
809+ printer/printerjob.cpp
810+ printer/printersignalhandler.cpp
811+ printers/printers.cpp
812+
813+ enums.h
814+ i18n.cpp
815+ plugin.cpp
816+ structs.h
817+ utils.h
818+)
819+
820+target_link_libraries(UbuntuComponentsExtrasPrintersQml
821+ Qt5::DBus
822+ Qt5::Gui
823+ Qt5::PrintSupport
824+ Qt5::Qml
825+ Qt5::Concurrent
826+ ${CUPS_LIBRARIES}
827+)
828+
829+find_package(QmlPlugins)
830+
831+macro(add_plugin PLUGIN VERSION PATH)
832+ export_qmlfiles(${PLUGIN} ${PATH} DESTINATION ${QT_IMPORTS_DIR} ${ARGN})
833+ export_qmlplugin(${PLUGIN} ${VERSION} ${PATH} DESTINATION ${QT_IMPORTS_DIR} ${ARGN})
834+endmacro()
835+
836+add_plugin(Ubuntu.Components.Extras.Printers 0.1 Ubuntu/Components/Extras/Printers TARGETS UbuntuComponentsExtrasPrintersQml)
837
838=== added directory 'modules/Ubuntu/Components/Extras/Printers/backend'
839=== added file 'modules/Ubuntu/Components/Extras/Printers/backend/backend.cpp'
840--- modules/Ubuntu/Components/Extras/Printers/backend/backend.cpp 1970-01-01 00:00:00 +0000
841+++ modules/Ubuntu/Components/Extras/Printers/backend/backend.cpp 2017-02-24 12:21:53 +0000
842@@ -0,0 +1,272 @@
843+/*
844+ * Copyright (C) 2017 Canonical, Ltd.
845+ *
846+ * This program is free software; you can redistribute it and/or modify
847+ * it under the terms of the GNU Lesser General Public License as published by
848+ * the Free Software Foundation; version 3.
849+ *
850+ * This program is distributed in the hope that it will be useful,
851+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
852+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
853+ * GNU Lesser General Public License for more details.
854+ *
855+ * You should have received a copy of the GNU Lesser General Public License
856+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
857+ */
858+
859+#include "backend/backend.h"
860+
861+PrinterBackend::PrinterBackend(const QString &printerName, QObject *parent)
862+ : QObject(parent)
863+ , m_printerName(printerName)
864+ , m_type(PrinterEnum::PrinterType::ProxyType)
865+{
866+}
867+
868+PrinterBackend::~PrinterBackend()
869+{
870+}
871+
872+bool PrinterBackend::holdsDefinition() const
873+{
874+ return false;
875+}
876+
877+QString PrinterBackend::printerAdd(const QString &name,
878+ const QString &uri,
879+ const QString &ppdFile,
880+ const QString &info,
881+ const QString &location)
882+{
883+ Q_UNUSED(name);
884+ Q_UNUSED(uri);
885+ Q_UNUSED(ppdFile);
886+ Q_UNUSED(info);
887+ Q_UNUSED(location);
888+ return QString();
889+}
890+
891+QString PrinterBackend::printerAddWithPpd(const QString &name,
892+ const QString &uri,
893+ const QString &ppdFileName,
894+ const QString &info,
895+ const QString &location)
896+{
897+ Q_UNUSED(name);
898+ Q_UNUSED(uri);
899+ Q_UNUSED(ppdFileName);
900+ Q_UNUSED(info);
901+ Q_UNUSED(location);
902+ return QString();
903+}
904+
905+QString PrinterBackend::printerDelete(const QString &name)
906+{
907+ Q_UNUSED(name);
908+ return QString();
909+}
910+
911+QString PrinterBackend::printerSetDefault(const QString &name)
912+{
913+ Q_UNUSED(name);
914+ return QString();
915+}
916+
917+QString PrinterBackend::printerSetEnabled(const QString &name,
918+ const bool enabled)
919+{
920+ Q_UNUSED(name);
921+ Q_UNUSED(enabled);
922+ return QString();
923+}
924+
925+QString PrinterBackend::printerSetAcceptJobs(
926+ const QString &name,
927+ const bool enabled,
928+ const QString &reason)
929+{
930+ Q_UNUSED(name);
931+ Q_UNUSED(enabled);
932+ Q_UNUSED(reason);
933+ return QString();
934+}
935+
936+QString PrinterBackend::printerSetInfo(const QString &name,
937+ const QString &info)
938+{
939+ Q_UNUSED(name);
940+ Q_UNUSED(info);
941+ return QString();
942+}
943+
944+QString PrinterBackend::printerAddOption(const QString &name,
945+ const QString &option,
946+ const QStringList &values)
947+{
948+ Q_UNUSED(name);
949+ Q_UNUSED(option);
950+ Q_UNUSED(values);
951+ return QString();
952+}
953+
954+QVariant PrinterBackend::printerGetOption(const QString &name,
955+ const QString &option) const
956+{
957+ Q_UNUSED(name);
958+ Q_UNUSED(option);
959+ return QVariant();
960+}
961+
962+QMap<QString, QVariant> PrinterBackend::printerGetOptions(
963+ const QString &name, const QStringList &options) const
964+{
965+ Q_UNUSED(name);
966+ Q_UNUSED(options);
967+ return QMap<QString, QVariant>();
968+}
969+
970+cups_dest_t* PrinterBackend::makeDest(const QString &name,
971+ const PrinterJob *options)
972+{
973+ Q_UNUSED(name);
974+ Q_UNUSED(options);
975+ return Q_NULLPTR;
976+}
977+
978+void PrinterBackend::cancelJob(const QString &name, const int jobId)
979+{
980+ Q_UNUSED(jobId);
981+ Q_UNUSED(name);
982+}
983+
984+int PrinterBackend::printFileToDest(const QString &filepath,
985+ const QString &title,
986+ const cups_dest_t *dest)
987+{
988+ Q_UNUSED(filepath);
989+ Q_UNUSED(title);
990+ Q_UNUSED(dest);
991+ return -1;
992+}
993+
994+QList<QSharedPointer<PrinterJob>> PrinterBackend::printerGetJobs()
995+{
996+ return QList<QSharedPointer<PrinterJob>>{};
997+}
998+
999+QMap<QString, QVariant> PrinterBackend::printerGetJobAttributes(
1000+ const QString &name, const int jobId)
1001+{
1002+ Q_UNUSED(name);
1003+ Q_UNUSED(jobId);
1004+ return QMap<QString, QVariant>();
1005+}
1006+
1007+QString PrinterBackend::printerName() const
1008+{
1009+ return m_printerName;
1010+}
1011+
1012+QString PrinterBackend::description() const
1013+{
1014+ return QString();
1015+}
1016+
1017+QString PrinterBackend::location() const
1018+{
1019+ return QString();
1020+}
1021+
1022+QString PrinterBackend::makeAndModel() const
1023+{
1024+ return QString();
1025+}
1026+
1027+PrinterEnum::State PrinterBackend::state() const
1028+{
1029+ return PrinterEnum::State::IdleState;
1030+}
1031+
1032+QList<QPageSize> PrinterBackend::supportedPageSizes() const
1033+{
1034+ return QList<QPageSize>();
1035+}
1036+
1037+QPageSize PrinterBackend::defaultPageSize() const
1038+{
1039+ return QPageSize();
1040+}
1041+
1042+bool PrinterBackend::supportsCustomPageSizes() const
1043+{
1044+ return false;
1045+}
1046+
1047+QPageSize PrinterBackend::minimumPhysicalPageSize() const
1048+{
1049+ return QPageSize();
1050+}
1051+
1052+QPageSize PrinterBackend::maximumPhysicalPageSize() const
1053+{
1054+ return QPageSize();
1055+}
1056+
1057+QList<int> PrinterBackend::supportedResolutions() const
1058+{
1059+ return QList<int>();
1060+}
1061+
1062+PrinterEnum::DuplexMode PrinterBackend::defaultDuplexMode() const
1063+{
1064+ return PrinterEnum::DuplexMode::DuplexNone;
1065+}
1066+
1067+QList<PrinterEnum::DuplexMode> PrinterBackend::supportedDuplexModes() const
1068+{
1069+ return QList<PrinterEnum::DuplexMode>();
1070+}
1071+
1072+QList<QSharedPointer<Printer>> PrinterBackend::availablePrinters()
1073+{
1074+ return QList<QSharedPointer<Printer>>();
1075+}
1076+
1077+QStringList PrinterBackend::availablePrinterNames()
1078+{
1079+ return QStringList();
1080+}
1081+
1082+QSharedPointer<Printer> PrinterBackend::getPrinter(const QString &printerName)
1083+{
1084+ Q_UNUSED(printerName);
1085+ return QSharedPointer<Printer>(Q_NULLPTR);
1086+}
1087+
1088+QString PrinterBackend::defaultPrinterName()
1089+{
1090+ return QString();
1091+}
1092+
1093+void PrinterBackend::requestPrinterDrivers()
1094+{
1095+}
1096+
1097+void PrinterBackend::requestPrinter(const QString &printerName)
1098+{
1099+ Q_UNUSED(printerName);
1100+}
1101+
1102+PrinterEnum::PrinterType PrinterBackend::type() const
1103+{
1104+ return m_type;
1105+}
1106+
1107+void PrinterBackend::setPrinterNameInternal(const QString &printerName)
1108+{
1109+ m_printerName = printerName;
1110+}
1111+
1112+void PrinterBackend::refresh()
1113+{
1114+}
1115
1116=== added file 'modules/Ubuntu/Components/Extras/Printers/backend/backend.h'
1117--- modules/Ubuntu/Components/Extras/Printers/backend/backend.h 1970-01-01 00:00:00 +0000
1118+++ modules/Ubuntu/Components/Extras/Printers/backend/backend.h 2017-02-24 12:21:53 +0000
1119@@ -0,0 +1,201 @@
1120+/*
1121+ * Copyright (C) 2017 Canonical, Ltd.
1122+ *
1123+ * This program is free software; you can redistribute it and/or modify
1124+ * it under the terms of the GNU Lesser General Public License as published by
1125+ * the Free Software Foundation; version 3.
1126+ *
1127+ * This program is distributed in the hope that it will be useful,
1128+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1129+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1130+ * GNU Lesser General Public License for more details.
1131+ *
1132+ * You should have received a copy of the GNU Lesser General Public License
1133+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1134+ */
1135+
1136+#ifndef USC_PRINTERS_BACKEND_H
1137+#define USC_PRINTERS_BACKEND_H
1138+
1139+#include "printer/printer.h"
1140+#include "printer/printerjob.h"
1141+
1142+// TODO: remove cups specific things from this API
1143+#include <cups/cups.h>
1144+
1145+#include <QObject>
1146+#include <QPageSize>
1147+#include <QList>
1148+#include <QString>
1149+#include <QStringList>
1150+
1151+class Printer;
1152+class PrinterJob;
1153+class PRINTERS_DECL_EXPORT PrinterBackend : public QObject
1154+{
1155+ Q_OBJECT
1156+public:
1157+ explicit PrinterBackend(QObject *parent = Q_NULLPTR);
1158+ explicit PrinterBackend(const QString &printerName,
1159+ QObject *parent = Q_NULLPTR);
1160+ virtual ~PrinterBackend();
1161+
1162+ virtual bool holdsDefinition() const;
1163+
1164+ // Add a printer using an already existing ppd.
1165+ virtual QString printerAdd(const QString &name,
1166+ const QString &uri,
1167+ const QString &ppdFile,
1168+ const QString &info,
1169+ const QString &location);
1170+
1171+ // Add a printer and provide a ppd file.
1172+ virtual QString printerAddWithPpd(const QString &name,
1173+ const QString &uri,
1174+ const QString &ppdFileName,
1175+ const QString &info,
1176+ const QString &location);
1177+ virtual QString printerDelete(const QString &name);
1178+ virtual QString printerSetDefault(const QString &name);
1179+ virtual QString printerSetEnabled(const QString &name,
1180+ const bool enabled);
1181+ virtual QString printerSetAcceptJobs(
1182+ const QString &name,
1183+ const bool accept,
1184+ const QString &reason = QString::null);
1185+ virtual QString printerSetInfo(const QString &name,
1186+ const QString &info);
1187+ virtual QString printerAddOption(const QString &name,
1188+ const QString &option,
1189+ const QStringList &values);
1190+
1191+ virtual QVariant printerGetOption(const QString &name,
1192+ const QString &option) const;
1193+ virtual QMap<QString, QVariant> printerGetOptions(
1194+ const QString &name, const QStringList &options) const;
1195+ virtual cups_dest_t* makeDest(const QString &name,
1196+ const PrinterJob *options);
1197+
1198+ virtual void cancelJob(const QString &name, const int jobId);
1199+ virtual int printFileToDest(const QString &filepath,
1200+ const QString &title,
1201+ const cups_dest_t *dest);
1202+ virtual QList<QSharedPointer<PrinterJob>> printerGetJobs();
1203+ virtual QMap<QString, QVariant> printerGetJobAttributes(
1204+ const QString &name, const int jobId);
1205+
1206+ virtual QString printerName() const;
1207+ virtual QString description() const;
1208+ virtual QString location() const;
1209+ virtual QString makeAndModel() const;
1210+
1211+ virtual PrinterEnum::State state() const;
1212+ virtual QList<QPageSize> supportedPageSizes() const;
1213+ virtual QPageSize defaultPageSize() const;
1214+ virtual bool supportsCustomPageSizes() const;
1215+
1216+ virtual QPageSize minimumPhysicalPageSize() const;
1217+ virtual QPageSize maximumPhysicalPageSize() const;
1218+ virtual QList<int> supportedResolutions() const;
1219+ virtual PrinterEnum::DuplexMode defaultDuplexMode() const;
1220+ virtual QList<PrinterEnum::DuplexMode> supportedDuplexModes() const;
1221+
1222+ virtual QList<QSharedPointer<Printer>> availablePrinters();
1223+ virtual QStringList availablePrinterNames();
1224+ virtual QSharedPointer<Printer> getPrinter(const QString &printerName);
1225+ virtual QString defaultPrinterName();
1226+
1227+ virtual void requestPrinterDrivers();
1228+ virtual void requestPrinter(const QString &printerName);
1229+
1230+ virtual PrinterEnum::PrinterType type() const;
1231+
1232+ virtual void setPrinterNameInternal(const QString &printerName);
1233+
1234+public Q_SLOTS:
1235+ virtual void refresh();
1236+
1237+Q_SIGNALS:
1238+ void printerDriversLoaded(const QList<PrinterDriver> &drivers);
1239+ void printerDriversFailedToLoad(const QString &errorMessage);
1240+
1241+ void printerLoaded(QSharedPointer<Printer> printers);
1242+
1243+ void jobCompleted(
1244+ const QString &text,
1245+ const QString &printerUri,
1246+ const QString &printerName,
1247+ uint printerState,
1248+ const QString &printerStateReason,
1249+ bool acceptingJobs,
1250+ uint jobId,
1251+ uint jobState,
1252+ const QString &jobStateReason,
1253+ const QString &job_name,
1254+ uint jobImpressionsCompleted
1255+ );
1256+ void jobCreated(
1257+ const QString &text,
1258+ const QString &printerUri,
1259+ const QString &printerName,
1260+ uint printerState,
1261+ const QString &printerStateReason,
1262+ bool acceptingJobs,
1263+ uint jobId,
1264+ uint jobState,
1265+ const QString &jobStateReason,
1266+ const QString &job_name,
1267+ uint jobImpressionsCompleted
1268+ );
1269+ void jobState(
1270+ const QString &text,
1271+ const QString &printerUri,
1272+ const QString &printerName,
1273+ uint printerState,
1274+ const QString &printerStateReason,
1275+ bool acceptingJobs,
1276+ uint jobId,
1277+ uint jobState,
1278+ const QString &jobStateReason,
1279+ const QString &job_name,
1280+ uint jobImpressionsCompleted
1281+ );
1282+ void printerAdded(
1283+ const QString &text,
1284+ const QString &printerUri,
1285+ const QString &printerName,
1286+ uint printerState,
1287+ const QString &printerStateReason,
1288+ bool acceptingJobs
1289+ );
1290+ void printerDeleted(
1291+ const QString &text,
1292+ const QString &printerUri,
1293+ const QString &printerName,
1294+ uint printerState,
1295+ const QString &printerStateReason,
1296+ bool acceptingJobs
1297+ );
1298+ void printerModified(
1299+ const QString &text,
1300+ const QString &printerUri,
1301+ const QString &printerName,
1302+ uint printerState,
1303+ const QString &printerStateReason,
1304+ bool acceptingJobs
1305+ );
1306+ void printerStateChanged(
1307+ const QString &text,
1308+ const QString &printerUri,
1309+ const QString &printerName,
1310+ uint printerState,
1311+ const QString &printerStateReason,
1312+ bool acceptingJobs
1313+ );
1314+
1315+protected:
1316+ QString m_printerName;
1317+ PrinterEnum::PrinterType m_type;
1318+};
1319+
1320+#endif // USC_PRINTERS_BACKEND_H
1321
1322=== added file 'modules/Ubuntu/Components/Extras/Printers/backend/backend_cups.cpp'
1323--- modules/Ubuntu/Components/Extras/Printers/backend/backend_cups.cpp 1970-01-01 00:00:00 +0000
1324+++ modules/Ubuntu/Components/Extras/Printers/backend/backend_cups.cpp 2017-02-24 12:21:53 +0000
1325@@ -0,0 +1,709 @@
1326+/*
1327+ * Copyright (C) 2017 Canonical, Ltd.
1328+ *
1329+ * This program is free software; you can redistribute it and/or modify
1330+ * it under the terms of the GNU Lesser General Public License as published by
1331+ * the Free Software Foundation; version 3.
1332+ *
1333+ * This program is distributed in the hope that it will be useful,
1334+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1335+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1336+ * GNU Lesser General Public License for more details.
1337+ *
1338+ * You should have received a copy of the GNU Lesser General Public License
1339+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1340+ */
1341+
1342+#include "backend/backend_cups.h"
1343+#include "cups/printerdriverloader.h"
1344+#include "cups/printerloader.h"
1345+#include "utils.h"
1346+
1347+#include <cups/http.h>
1348+#include <cups/ipp.h>
1349+#include <cups/ppd.h>
1350+
1351+#include <QLocale>
1352+#include <QThread>
1353+#include <QTimeZone>
1354+
1355+#define __CUPS_ADD_OPTION(dest, name, value) dest->num_options = \
1356+ cupsAddOption(name, value, dest->num_options, &dest->options);
1357+
1358+#define __CUPS_ATTR_EXISTS(map, attr, type) map.contains(attr) \
1359+ && map.value(attr).canConvert<type>()
1360+
1361+PrinterCupsBackend::PrinterCupsBackend(IppClient *client, QPrinterInfo info,
1362+ OrgCupsCupsdNotifierInterface *notifier,
1363+ QObject *parent)
1364+ : PrinterBackend(info.printerName(), parent)
1365+ , m_knownQualityOptions({
1366+ "Quality", "PrintQuality", "HPPrintQuality", "StpQuality",
1367+ "OutputMode",})
1368+ , m_client(client)
1369+ , m_info(info)
1370+ , m_notifier(notifier)
1371+ , m_cupsSubscriptionId(-1)
1372+{
1373+ m_type = PrinterEnum::PrinterType::CupsType;
1374+ connect(m_notifier, SIGNAL(JobCompleted(const QString&, const QString&,
1375+ const QString&, uint,
1376+ const QString&, bool, uint, uint,
1377+ const QString&, const QString&, uint)),
1378+ this, SIGNAL(jobCompleted(const QString&, const QString&,
1379+ const QString&, uint, const QString&,
1380+ bool, uint, uint, const QString&,
1381+ const QString&, uint)));
1382+ connect(m_notifier, SIGNAL(JobCreated(const QString&, const QString&,
1383+ const QString&, uint, const QString&,
1384+ bool, uint, uint, const QString&,
1385+ const QString&, uint)),
1386+ this, SIGNAL(jobCreated(const QString&, const QString&,
1387+ const QString&, uint, const QString&, bool,
1388+ uint, uint, const QString&, const QString&,
1389+ uint)));
1390+ connect(m_notifier, SIGNAL(JobState(const QString&, const QString&,
1391+ const QString&, uint, const QString&,
1392+ bool, uint, uint, const QString&,
1393+ const QString&, uint)),
1394+ this, SIGNAL(jobState(const QString&, const QString&,
1395+ const QString&, uint, const QString&, bool,
1396+ uint, uint, const QString&, const QString&,
1397+ uint)));
1398+ connect(m_notifier, SIGNAL(PrinterAdded(const QString&, const QString&,
1399+ const QString&, uint,
1400+ const QString&, bool)),
1401+ this, SIGNAL(printerAdded(const QString&, const QString&,
1402+ const QString&, uint,
1403+ const QString&, bool)));
1404+ connect(m_notifier, SIGNAL(PrinterDeleted(const QString&, const QString&,
1405+ const QString&, uint,
1406+ const QString&, bool)),
1407+ this, SIGNAL(printerDeleted(const QString&, const QString&,
1408+ const QString&, uint,
1409+ const QString&, bool)));
1410+ connect(m_notifier, SIGNAL(PrinterModified(const QString&, const QString&,
1411+ const QString&, uint,
1412+ const QString&, bool)),
1413+ this, SIGNAL(printerModified(const QString&, const QString&,
1414+ const QString&, uint,
1415+ const QString&, bool)));
1416+ connect(m_notifier, SIGNAL(PrinterStateChanged(const QString&,
1417+ const QString&,
1418+ const QString&, uint,
1419+ const QString&, bool)),
1420+ this, SIGNAL(printerStateChanged(const QString&, const QString&,
1421+ const QString&, uint,
1422+ const QString&, bool)));
1423+
1424+}
1425+
1426+PrinterCupsBackend::~PrinterCupsBackend()
1427+{
1428+ Q_FOREACH(auto dest, m_dests) {
1429+ if (dest)
1430+ cupsFreeDests(1, dest);
1431+ }
1432+ Q_FOREACH(auto ppd, m_ppds) {
1433+ if (ppd)
1434+ ppdClose(ppd);
1435+ }
1436+
1437+ cancelSubscription();
1438+ Q_EMIT cancelWorkers();
1439+}
1440+
1441+QString PrinterCupsBackend::printerAdd(const QString &name,
1442+ const QString &uri,
1443+ const QString &ppdFile,
1444+ const QString &info,
1445+ const QString &location)
1446+{
1447+ if (!m_client->printerAdd(name, uri, ppdFile, info, location)) {
1448+ return m_client->getLastError();
1449+ }
1450+ return QString();
1451+}
1452+
1453+QString PrinterCupsBackend::printerAddWithPpd(const QString &name,
1454+ const QString &uri,
1455+ const QString &ppdFileName,
1456+ const QString &info,
1457+ const QString &location)
1458+{
1459+ if (!m_client->printerAddWithPpdFile(name, uri, ppdFileName, info, location)) {
1460+ return m_client->getLastError();
1461+ }
1462+ return QString();
1463+}
1464+
1465+bool PrinterCupsBackend::holdsDefinition() const
1466+{
1467+ return !m_info.isNull();
1468+}
1469+
1470+QString PrinterCupsBackend::printerDelete(const QString &name)
1471+{
1472+ if (!m_client->printerDelete(name)) {
1473+ return m_client->getLastError();
1474+ }
1475+ return QString();
1476+}
1477+
1478+QString PrinterCupsBackend::printerSetDefault(const QString &name)
1479+{
1480+ if (!m_client->printerSetDefault(name)) {
1481+ return m_client->getLastError();
1482+ }
1483+ return QString();
1484+}
1485+
1486+QString PrinterCupsBackend::printerSetEnabled(const QString &name,
1487+ const bool enabled)
1488+{
1489+ if (!m_client->printerSetEnabled(name, enabled)) {
1490+ return m_client->getLastError();
1491+ }
1492+ return QString();
1493+}
1494+
1495+QString PrinterCupsBackend::printerSetAcceptJobs(
1496+ const QString &name,
1497+ const bool accept,
1498+ const QString &reason)
1499+{
1500+ if (!m_client->printerSetAcceptJobs(name, accept, reason)) {
1501+ return m_client->getLastError();
1502+ }
1503+ return QString();
1504+}
1505+
1506+QString PrinterCupsBackend::printerSetInfo(const QString &name,
1507+ const QString &info)
1508+{
1509+ if (!m_client->printerClassSetInfo(name, info)) {
1510+ return m_client->getLastError();
1511+ }
1512+ return QString();
1513+}
1514+
1515+QString PrinterCupsBackend::printerAddOption(const QString &name,
1516+ const QString &option,
1517+ const QStringList &values)
1518+{
1519+ if (!m_client->printerClassSetOption(name, option, values)) {
1520+ return m_client->getLastError();
1521+ }
1522+
1523+ return QString();
1524+}
1525+
1526+QVariant PrinterCupsBackend::printerGetOption(const QString &name,
1527+ const QString &option) const
1528+{
1529+ auto res = printerGetOptions(name, QStringList({option}));
1530+ return res[option];
1531+}
1532+
1533+QMap<QString, QVariant> PrinterCupsBackend::printerGetOptions(
1534+ const QString &name, const QStringList &options) const
1535+{
1536+ QMap<QString, QVariant> ret;
1537+
1538+ cups_dest_t *dest = getDest(name);
1539+ ppd_file_t* ppd = getPpd(name);
1540+
1541+ if (!dest || !ppd) {
1542+ return ret;
1543+ }
1544+
1545+ Q_FOREACH(const QString &option, options) {
1546+ if (option == QStringLiteral("DefaultColorModel")) {
1547+ ColorModel model;
1548+ ppd_option_t *ppdColorModel = ppdFindOption(ppd, "ColorModel");
1549+ if (ppdColorModel) {
1550+ ppd_choice_t* def = ppdFindChoice(ppdColorModel,
1551+ ppdColorModel->defchoice);
1552+ if (def) {
1553+ model = Utils::parsePpdColorModel(def->choice,
1554+ def->text,
1555+ "ColorModel");
1556+ }
1557+ }
1558+ ret[option] = QVariant::fromValue(model);
1559+ } else if (option == QStringLiteral("DefaultPrintQuality")) {
1560+ PrintQuality quality;
1561+ Q_FOREACH(const QString opt, m_knownQualityOptions) {
1562+ ppd_option_t *ppdQuality = ppdFindOption(ppd, opt.toUtf8());
1563+ if (ppdQuality) {
1564+ ppd_choice_t* def = ppdFindChoice(ppdQuality,
1565+ ppdQuality->defchoice);
1566+ if (def) {
1567+ quality = Utils::parsePpdPrintQuality(def->choice,
1568+ def->text, opt);
1569+ }
1570+ }
1571+ }
1572+ ret[option] = QVariant::fromValue(quality);
1573+ } else if (option == QStringLiteral("SupportedPrintQualities")) {
1574+ QList<PrintQuality> qualities;
1575+ Q_FOREACH(const QString &opt, m_knownQualityOptions) {
1576+ ppd_option_t *qualityOpt = ppdFindOption(ppd, opt.toUtf8());
1577+ if (qualityOpt) {
1578+ for (int i = 0; i < qualityOpt->num_choices; ++i) {
1579+ qualities.append(
1580+ Utils::parsePpdPrintQuality(
1581+ qualityOpt->choices[i].choice,
1582+ qualityOpt->choices[i].text,
1583+ opt
1584+ )
1585+ );
1586+ }
1587+ }
1588+ }
1589+ ret[option] = QVariant::fromValue(qualities);
1590+ } else if (option == QStringLiteral("SupportedColorModels")) {
1591+ QList<ColorModel> models;
1592+ ppd_option_t *colorModels = ppdFindOption(ppd, "ColorModel");
1593+ if (colorModels) {
1594+ for (int i = 0; i < colorModels->num_choices; ++i) {
1595+ models.append(
1596+ Utils::parsePpdColorModel(
1597+ colorModels->choices[i].choice,
1598+ colorModels->choices[i].text,
1599+ QStringLiteral("ColorModel")
1600+ )
1601+ );
1602+ }
1603+ }
1604+ ret[option] = QVariant::fromValue(models);
1605+ } else if (option == QStringLiteral("AcceptJobs")) {
1606+ // "true" if the destination is accepting new jobs, "false" if not.
1607+ QString res = cupsGetOption("printer-is-accepting-jobs",
1608+ dest->num_options, dest->options);
1609+ ret[option] = res.contains("true");
1610+ } else {
1611+ ppd_option_t *val = ppdFindOption(ppd, option.toUtf8());
1612+
1613+ if (val) {
1614+ qWarning() << "asking for" << option << "returns" << val->text;
1615+ } else {
1616+ qWarning() << "option" << option << "yielded no option";
1617+ }
1618+ }
1619+ }
1620+ return ret;
1621+}
1622+
1623+cups_dest_t* PrinterCupsBackend::makeDest(const QString &name,
1624+ const PrinterJob *options)
1625+{
1626+ cups_dest_t *dest = getDest(name);
1627+
1628+ if (options->collate()) {
1629+ __CUPS_ADD_OPTION(dest, "Collate", "True");
1630+ } else {
1631+ __CUPS_ADD_OPTION(dest, "Collate", "False");
1632+ }
1633+
1634+ if (options->copies() > 1) {
1635+ __CUPS_ADD_OPTION(dest, "copies", QString::number(options->copies()).toLocal8Bit());
1636+ }
1637+
1638+ __CUPS_ADD_OPTION(dest, "ColorModel", options->getColorModel().name.toLocal8Bit());
1639+ __CUPS_ADD_OPTION(dest, "Duplex", Utils::duplexModeToPpdChoice(options->getDuplexMode()).toLocal8Bit());
1640+
1641+ if (options->landscape()) {
1642+ __CUPS_ADD_OPTION(dest, "landscape", "");
1643+ }
1644+
1645+ if (options->printRangeMode() == PrinterEnum::PrintRange::PageRange
1646+ && !options->printRange().isEmpty()) {
1647+ __CUPS_ADD_OPTION(dest, "page-ranges", options->printRange().toLocal8Bit());
1648+ }
1649+
1650+ PrintQuality quality = options->getPrintQuality();
1651+ __CUPS_ADD_OPTION(dest, quality.originalOption.toLocal8Bit(),
1652+ quality.name.toLocal8Bit());
1653+
1654+ if (options->reverse()) {
1655+ __CUPS_ADD_OPTION(dest, "OutputOrder", "Reverse");
1656+ } else {
1657+ __CUPS_ADD_OPTION(dest, "OutputOrder", "Normal");
1658+ }
1659+
1660+ // Always scale to fit the page for now
1661+ __CUPS_ADD_OPTION(dest, "fit-to-page", "True");
1662+
1663+ return dest;
1664+}
1665+
1666+void PrinterCupsBackend::cancelJob(const QString &name, const int jobId)
1667+{
1668+ int ret = cupsCancelJob(name.toLocal8Bit(), jobId);
1669+
1670+ if (!ret) {
1671+ qWarning() << "Failed to cancel job:" << jobId << "for" << name;
1672+ }
1673+}
1674+
1675+int PrinterCupsBackend::printFileToDest(const QString &filepath,
1676+ const QString &title,
1677+ const cups_dest_t *dest)
1678+{
1679+ qDebug() << "Printing:" << filepath << title << dest->name << dest->num_options;
1680+ return cupsPrintFile(dest->name,
1681+ filepath.toLocal8Bit(),
1682+ title.toLocal8Bit(),
1683+ dest->num_options,
1684+ dest->options);
1685+}
1686+
1687+
1688+QList<cups_job_t *> PrinterCupsBackend::getCupsJobs(const QString &name)
1689+{
1690+ QList<cups_job_t *> list;
1691+ cups_job_t *jobs;
1692+
1693+ // Get a list of the jobs that are 'mine' and only active ones
1694+ // https://www.cups.org/doc/api-cups.html#cupsGetJobs
1695+ int count;
1696+ if (name.isEmpty()) {
1697+ count = cupsGetJobs(&jobs, NULL, 1, CUPS_WHICHJOBS_ACTIVE);
1698+ } else {
1699+ count = cupsGetJobs(&jobs, name.toLocal8Bit(), 1, CUPS_WHICHJOBS_ACTIVE);
1700+ }
1701+
1702+ for (int i=0; i < count; i++) {
1703+ list.append(&jobs[i]);
1704+ }
1705+
1706+ return list;
1707+}
1708+
1709+QMap<QString, QVariant> PrinterCupsBackend::printerGetJobAttributes(
1710+ const QString &name, const int jobId)
1711+{
1712+ Q_UNUSED(name);
1713+ QMap<QString, QVariant> rawMap = m_client->printerGetJobAttributes(jobId);
1714+ QMap<QString, QVariant> map;
1715+
1716+ // Filter attributes to know values
1717+ // Do this here so we can use things such as m_knownQualityOptions
1718+
1719+ if (__CUPS_ATTR_EXISTS(rawMap, "Collate", bool)) {
1720+ map.insert("Collate", rawMap.value("Collate"));
1721+ } else {
1722+ map.insert("Collate", QVariant(true));
1723+ }
1724+
1725+ if (__CUPS_ATTR_EXISTS(rawMap, "copies", int)) {
1726+ map.insert("copies", rawMap.value("copies"));
1727+ } else {
1728+ map.insert("copies", QVariant(1));
1729+ }
1730+
1731+ if (__CUPS_ATTR_EXISTS(rawMap, "ColorModel", QString)) {
1732+ map.insert("ColorModel", rawMap.value("ColorModel"));
1733+ } else {
1734+ map.insert("ColorModel", QVariant(""));
1735+ }
1736+
1737+ if (__CUPS_ATTR_EXISTS(rawMap, "Duplex", QString)) {
1738+ map.insert("Duplex", rawMap.value("Duplex"));
1739+ } else {
1740+ map.insert("Duplex", QVariant(""));
1741+ }
1742+
1743+ if (__CUPS_ATTR_EXISTS(rawMap, "landscape", bool)) {
1744+ map.insert("landscape", rawMap.value("landscape"));
1745+ } else {
1746+ map.insert("landscape", QVariant(false));
1747+ }
1748+
1749+ if (__CUPS_ATTR_EXISTS(rawMap, "page-ranges", QList<QVariant>)) {
1750+ QList<QVariant> range = rawMap.value("page-ranges").toList();
1751+ QStringList rangeStrings;
1752+
1753+ Q_FOREACH(QVariant var, range) {
1754+ rangeStrings << var.toString();
1755+ }
1756+
1757+ map.insert("page-ranges", QVariant(rangeStrings));
1758+ } else {
1759+ map.insert("page-ranges", QVariant(QStringList()));
1760+ }
1761+
1762+ Q_FOREACH(QString qualityOption, m_knownQualityOptions) {
1763+ if (rawMap.contains(qualityOption)
1764+ && rawMap.value(qualityOption).canConvert<QString>()) {
1765+ map.insert("quality", rawMap.value(qualityOption).toString());
1766+ }
1767+ }
1768+
1769+ if (!map.contains("quality")) {
1770+ map.insert("quality", QVariant(""));
1771+ }
1772+
1773+ if (__CUPS_ATTR_EXISTS(rawMap, "OutputOrder", QString)) {
1774+ map.insert("OutputOrder", rawMap.value("OutputOrder"));
1775+ } else {
1776+ map.insert("OutputOrder", "Normal");
1777+ }
1778+
1779+ // Generate a list of messages
1780+ // TODO: for now just using job-printer-state-message, are there others?
1781+ QStringList messages;
1782+
1783+ if (__CUPS_ATTR_EXISTS(rawMap, "job-printer-state-message", QString)) {
1784+ messages << rawMap.value("job-printer-state-message").toString();
1785+ }
1786+
1787+ map.insert("messages", QVariant(messages));
1788+
1789+ return map;
1790+}
1791+
1792+
1793+QList<QSharedPointer<PrinterJob>> PrinterCupsBackend::printerGetJobs()
1794+{
1795+ auto jobs = getCupsJobs();
1796+ QList<QSharedPointer<PrinterJob>> list;
1797+
1798+ Q_FOREACH(auto job, jobs) {
1799+ auto newJob = QSharedPointer<PrinterJob>(
1800+ new PrinterJob(QString::fromUtf8(job->dest), this, job->id)
1801+ );
1802+
1803+ // Extract the times
1804+ QDateTime completedTime;
1805+ completedTime.setTimeZone(QTimeZone::systemTimeZone());
1806+ completedTime.setTime_t(job->completed_time);
1807+
1808+ QDateTime creationTime;
1809+ creationTime.setTimeZone(QTimeZone::systemTimeZone());
1810+ creationTime.setTime_t(job->creation_time);
1811+
1812+ QDateTime processingTime;
1813+ processingTime.setTimeZone(QTimeZone::systemTimeZone());
1814+ processingTime.setTime_t(job->processing_time);
1815+
1816+ // Load the information from the cups struct
1817+ newJob->setCompletedTime(completedTime);
1818+ newJob->setCreationTime(creationTime);
1819+ newJob->setProcessingTime(processingTime);
1820+ newJob->setSize(job->size);
1821+ newJob->setState(static_cast<PrinterEnum::JobState>(job->state));
1822+ newJob->setTitle(QString::fromLocal8Bit(job->title));
1823+ newJob->setUser(QString::fromLocal8Bit(job->user));
1824+
1825+ list.append(newJob);
1826+ }
1827+ if (!list.isEmpty())
1828+ cupsFreeJobs(list.size(), jobs.first());
1829+
1830+ return list;
1831+}
1832+
1833+QString PrinterCupsBackend::printerName() const
1834+{
1835+ return m_printerName;
1836+}
1837+
1838+QString PrinterCupsBackend::description() const
1839+{
1840+ return m_info.description();
1841+}
1842+
1843+QString PrinterCupsBackend::location() const
1844+{
1845+ return m_info.location();
1846+}
1847+
1848+QString PrinterCupsBackend::makeAndModel() const
1849+{
1850+ return m_info.makeAndModel();
1851+}
1852+
1853+PrinterEnum::State PrinterCupsBackend::state() const
1854+{
1855+ switch (m_info.state()) {
1856+ case QPrinter::Active:
1857+ return PrinterEnum::State::ActiveState;
1858+ case QPrinter::Aborted:
1859+ return PrinterEnum::State::AbortedState;
1860+ case QPrinter::Error:
1861+ return PrinterEnum::State::ErrorState;
1862+ case QPrinter::Idle:
1863+ default:
1864+ return PrinterEnum::State::IdleState;
1865+ }
1866+}
1867+
1868+QList<QPageSize> PrinterCupsBackend::supportedPageSizes() const
1869+{
1870+ return m_info.supportedPageSizes();
1871+}
1872+
1873+QPageSize PrinterCupsBackend::defaultPageSize() const
1874+{
1875+ return m_info.defaultPageSize();
1876+}
1877+
1878+bool PrinterCupsBackend::supportsCustomPageSizes() const
1879+{
1880+ return m_info.supportsCustomPageSizes();
1881+}
1882+
1883+QPageSize PrinterCupsBackend::minimumPhysicalPageSize() const
1884+{
1885+ return m_info.minimumPhysicalPageSize();
1886+}
1887+
1888+QPageSize PrinterCupsBackend::maximumPhysicalPageSize() const
1889+{
1890+ return m_info.maximumPhysicalPageSize();
1891+}
1892+
1893+QList<int> PrinterCupsBackend::supportedResolutions() const
1894+{
1895+ return m_info.supportedResolutions();
1896+}
1897+
1898+PrinterEnum::DuplexMode PrinterCupsBackend::defaultDuplexMode() const
1899+{
1900+ return Utils::qDuplexModeToDuplexMode(m_info.defaultDuplexMode());
1901+}
1902+
1903+QList<PrinterEnum::DuplexMode> PrinterCupsBackend::supportedDuplexModes() const
1904+{
1905+ QList<PrinterEnum::DuplexMode> list;
1906+ Q_FOREACH(const QPrinter::DuplexMode mode, m_info.supportedDuplexModes()) {
1907+ if (mode != QPrinter::DuplexAuto) {
1908+ list.append(Utils::qDuplexModeToDuplexMode(mode));
1909+ }
1910+ }
1911+
1912+ if (list.isEmpty())
1913+ list.append(PrinterEnum::DuplexMode::DuplexNone);
1914+
1915+ return list;
1916+}
1917+
1918+QList<QSharedPointer<Printer>> PrinterCupsBackend::availablePrinters()
1919+{
1920+ return QList<QSharedPointer<Printer>>();
1921+}
1922+
1923+QStringList PrinterCupsBackend::availablePrinterNames()
1924+{
1925+ return QPrinterInfo::availablePrinterNames();
1926+}
1927+
1928+QSharedPointer<Printer> PrinterCupsBackend::getPrinter(const QString &printerName)
1929+{
1930+ QPrinterInfo info = QPrinterInfo::printerInfo(printerName);
1931+ return QSharedPointer<Printer>(new Printer(new PrinterCupsBackend(m_client, info, m_notifier)));
1932+}
1933+
1934+QString PrinterCupsBackend::defaultPrinterName()
1935+{
1936+ return QPrinterInfo::defaultPrinterName();
1937+}
1938+
1939+void PrinterCupsBackend::requestPrinter(const QString &printerName)
1940+{
1941+ auto thread = new QThread;
1942+ auto loader = new PrinterLoader(printerName, m_client, m_notifier);
1943+ loader->moveToThread(thread);
1944+ connect(thread, SIGNAL(started()), loader, SLOT(load()));
1945+ connect(loader, SIGNAL(finished()), thread, SLOT(quit()));
1946+ connect(loader, SIGNAL(finished()), loader, SLOT(deleteLater()));
1947+ connect(loader, SIGNAL(loaded(QSharedPointer<Printer>)),
1948+ this, SIGNAL(printerLoaded(QSharedPointer<Printer>)));
1949+ connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
1950+ thread->start();
1951+}
1952+
1953+void PrinterCupsBackend::requestPrinterDrivers()
1954+{
1955+ auto thread = new QThread;
1956+ auto loader = new PrinterDriverLoader();
1957+ loader->moveToThread(thread);
1958+ connect(loader, SIGNAL(error(const QString&)),
1959+ this, SIGNAL(printerDriversFailedToLoad(const QString&)));
1960+ connect(this, SIGNAL(requestPrinterDriverCancel()), loader, SLOT(cancel()));
1961+ connect(thread, SIGNAL(started()), loader, SLOT(process()));
1962+ connect(loader, SIGNAL(finished()), thread, SLOT(quit()));
1963+ connect(loader, SIGNAL(finished()), loader, SLOT(deleteLater()));
1964+ connect(loader, SIGNAL(loaded(const QList<PrinterDriver>&)),
1965+ this, SIGNAL(printerDriversLoaded(const QList<PrinterDriver>&)));
1966+ connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
1967+ thread->start();
1968+}
1969+
1970+void PrinterCupsBackend::cancelPrinterDriverRequest()
1971+{
1972+ Q_EMIT requestPrinterDriverCancel();
1973+}
1974+
1975+void PrinterCupsBackend::refresh()
1976+{
1977+ if (m_printerName.isEmpty()) {
1978+ throw std::invalid_argument("Trying to refresh unnamed printer.");
1979+ } else {
1980+ m_info = QPrinterInfo::printerInfo(m_printerName);
1981+ }
1982+}
1983+
1984+void PrinterCupsBackend::createSubscription()
1985+{
1986+ m_cupsSubscriptionId = m_client->createSubscription();;
1987+}
1988+
1989+void PrinterCupsBackend::cancelSubscription()
1990+{
1991+ if (m_cupsSubscriptionId > 0)
1992+ m_client->cancelSubscription(m_cupsSubscriptionId);
1993+}
1994+
1995+QString PrinterCupsBackend::getPrinterInstance(const QString &name) const
1996+{
1997+ const auto parts = name.splitRef(QLatin1Char('/'));
1998+ QString instance;
1999+ if (parts.size() > 1)
2000+ instance = parts.at(1).toString();
2001+
2002+ return instance;
2003+}
2004+
2005+QString PrinterCupsBackend::getPrinterName(const QString &name) const
2006+{
2007+ return name.splitRef(QLatin1Char('/')).first().toString();
2008+}
2009+
2010+cups_dest_t* PrinterCupsBackend::getDest(const QString &name) const
2011+{
2012+ QString printerName = getPrinterName(name);
2013+ QString instance = getPrinterInstance(name);
2014+
2015+ if (m_dests.contains(name)) {
2016+ return m_dests[name];
2017+ } else {
2018+ m_dests[name] = m_client->getDest(printerName, instance);
2019+ return m_dests[name];
2020+ }
2021+}
2022+
2023+ppd_file_t* PrinterCupsBackend::getPpd(const QString &name) const
2024+{
2025+ QString printerName = getPrinterName(name);
2026+ QString instance = getPrinterInstance(name);
2027+
2028+ if (m_ppds.contains(name)) {
2029+ return m_ppds[name];
2030+ } else {
2031+ m_ppds[name] = m_client->getPpdFile(printerName, instance);
2032+ return m_ppds[name];
2033+ }
2034+}
2035
2036=== added file 'modules/Ubuntu/Components/Extras/Printers/backend/backend_cups.h'
2037--- modules/Ubuntu/Components/Extras/Printers/backend/backend_cups.h 1970-01-01 00:00:00 +0000
2038+++ modules/Ubuntu/Components/Extras/Printers/backend/backend_cups.h 2017-02-24 12:21:53 +0000
2039@@ -0,0 +1,131 @@
2040+/*
2041+ * Copyright (C) 2017 Canonical, Ltd.
2042+ *
2043+ * This program is free software; you can redistribute it and/or modify
2044+ * it under the terms of the GNU Lesser General Public License as published by
2045+ * the Free Software Foundation; version 3.
2046+ *
2047+ * This program is distributed in the hope that it will be useful,
2048+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2049+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2050+ * GNU Lesser General Public License for more details.
2051+ *
2052+ * You should have received a copy of the GNU Lesser General Public License
2053+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2054+ */
2055+
2056+#ifndef USC_PRINTERS_CUPS_BACKEND_H
2057+#define USC_PRINTERS_CUPS_BACKEND_H
2058+
2059+#include "backend/backend.h"
2060+#include "cups/ippclient.h"
2061+#include "cupsdnotifier.h" // Note: this file was generated.
2062+
2063+#include <cups/cups.h>
2064+
2065+#include <QPrinterInfo>
2066+
2067+class PRINTERS_DECL_EXPORT PrinterCupsBackend : public PrinterBackend
2068+{
2069+ Q_OBJECT
2070+public:
2071+ explicit PrinterCupsBackend(IppClient *client, QPrinterInfo info,
2072+ OrgCupsCupsdNotifierInterface* notifier,
2073+ QObject *parent = Q_NULLPTR);
2074+ virtual ~PrinterCupsBackend() override;
2075+
2076+ virtual bool holdsDefinition() const override;
2077+
2078+ virtual QString printerAdd(const QString &name,
2079+ const QString &uri,
2080+ const QString &ppdFile,
2081+ const QString &info,
2082+ const QString &location) override;
2083+ virtual QString printerAddWithPpd(const QString &name,
2084+ const QString &uri,
2085+ const QString &ppdFileName,
2086+ const QString &info,
2087+ const QString &location) override;
2088+ virtual QString printerDelete(const QString &name) override;
2089+ virtual QString printerSetDefault(const QString &name) override;
2090+ virtual QString printerSetEnabled(const QString &name,
2091+ const bool enabled) override;
2092+ virtual QString printerSetAcceptJobs(
2093+ const QString &name,
2094+ const bool accept,
2095+ const QString &reason = QString::null) override;
2096+ virtual QString printerSetInfo(const QString &name,
2097+ const QString &info) override;
2098+ virtual QString printerAddOption(const QString &name,
2099+ const QString &option,
2100+ const QStringList &values) override;
2101+
2102+ virtual QVariant printerGetOption(const QString &name,
2103+ const QString &option) const override;
2104+ virtual QMap<QString, QVariant> printerGetOptions(
2105+ const QString &name, const QStringList &options
2106+ ) const override;
2107+ // FIXME: maybe have a PrinterDest iface that has a CupsDest impl?
2108+ virtual cups_dest_t* makeDest(const QString &name,
2109+ const PrinterJob *options) override;
2110+
2111+ virtual void cancelJob(const QString &name, const int jobId) override;
2112+ virtual int printFileToDest(const QString &filepath,
2113+ const QString &title,
2114+ const cups_dest_t *dest) override;
2115+ virtual QList<QSharedPointer<PrinterJob>> printerGetJobs() override;
2116+
2117+ virtual QString printerName() const override;
2118+ virtual QString description() const override;
2119+ virtual QString location() const override;
2120+ virtual QString makeAndModel() const override;
2121+
2122+ virtual PrinterEnum::State state() const override;
2123+ virtual QList<QPageSize> supportedPageSizes() const override;
2124+ virtual QPageSize defaultPageSize() const override;
2125+ virtual bool supportsCustomPageSizes() const override;
2126+
2127+ virtual QPageSize minimumPhysicalPageSize() const override;
2128+ virtual QPageSize maximumPhysicalPageSize() const override;
2129+ virtual QList<int> supportedResolutions() const override;
2130+ virtual PrinterEnum::DuplexMode defaultDuplexMode() const override;
2131+ virtual QList<PrinterEnum::DuplexMode> supportedDuplexModes() const override;
2132+
2133+ virtual QList<QSharedPointer<Printer>> availablePrinters() override;
2134+ virtual QStringList availablePrinterNames() override;
2135+ virtual QSharedPointer<Printer> getPrinter(const QString &printerName) override;
2136+ virtual QString defaultPrinterName() override;
2137+ virtual void requestPrinterDrivers() override;
2138+ virtual void requestPrinter(const QString &printerName) override;
2139+ virtual QMap<QString, QVariant> printerGetJobAttributes(
2140+ const QString &name, const int jobId) override;
2141+
2142+public Q_SLOTS:
2143+ virtual void refresh() override;
2144+ void createSubscription();
2145+
2146+Q_SIGNALS:
2147+ void cancelWorkers();
2148+ void printerDriversLoaded(const QList<PrinterDriver> &drivers);
2149+ void printerDriversFailedToLoad(const QString &errorMessage);
2150+ void requestPrinterDriverCancel();
2151+
2152+private:
2153+ void cancelSubscription();
2154+ void cancelPrinterDriverRequest();
2155+ QList<cups_job_t *> getCupsJobs(const QString &name = QStringLiteral());
2156+
2157+ QString getPrinterName(const QString &name) const;
2158+ QString getPrinterInstance(const QString &name) const;
2159+ cups_dest_t* getDest(const QString &name) const;
2160+ ppd_file_t* getPpd(const QString &name) const;
2161+ const QStringList m_knownQualityOptions;
2162+ IppClient *m_client;
2163+ QPrinterInfo m_info;
2164+ OrgCupsCupsdNotifierInterface *m_notifier;
2165+ int m_cupsSubscriptionId;
2166+ mutable QMap<QString, cups_dest_t*> m_dests; // Printer name, dest.
2167+ mutable QMap<QString, ppd_file_t*> m_ppds; // Printer name, ppd.
2168+};
2169+
2170+#endif // USC_PRINTERS_CUPS_BACKEND_H
2171
2172=== added file 'modules/Ubuntu/Components/Extras/Printers/backend/backend_pdf.cpp'
2173--- modules/Ubuntu/Components/Extras/Printers/backend/backend_pdf.cpp 1970-01-01 00:00:00 +0000
2174+++ modules/Ubuntu/Components/Extras/Printers/backend/backend_pdf.cpp 2017-02-24 12:21:53 +0000
2175@@ -0,0 +1,119 @@
2176+/*
2177+ * Copyright (C) 2017 Canonical, Ltd.
2178+ *
2179+ * This program is free software; you can redistribute it and/or modify
2180+ * it under the terms of the GNU Lesser General Public License as published by
2181+ * the Free Software Foundation; version 3.
2182+ *
2183+ * This program is distributed in the hope that it will be useful,
2184+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2185+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2186+ * GNU Lesser General Public License for more details.
2187+ *
2188+ * You should have received a copy of the GNU Lesser General Public License
2189+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2190+ */
2191+
2192+#include "i18n.h"
2193+#include "backend/backend_pdf.h"
2194+
2195+PrinterPdfBackend::PrinterPdfBackend(const QString &printerName,
2196+ QObject *parent)
2197+ : PrinterBackend(printerName, parent)
2198+{
2199+ m_type = PrinterEnum::PrinterType::PdfType;
2200+}
2201+
2202+QVariant PrinterPdfBackend::printerGetOption(const QString &name,
2203+ const QString &option) const
2204+{
2205+ auto res = printerGetOptions(name, QStringList({option}));
2206+ return res[option];
2207+}
2208+
2209+QMap<QString, QVariant> PrinterPdfBackend::printerGetOptions(
2210+ const QString &name, const QStringList &options) const
2211+{
2212+ Q_UNUSED(name);
2213+
2214+ QMap<QString, QVariant> ret;
2215+
2216+ ColorModel rgb;
2217+ rgb.colorType = PrinterEnum::ColorModelType::ColorType;
2218+ rgb.name = "RGB";
2219+ rgb.text = __("Color");
2220+
2221+ PrintQuality quality;
2222+ quality.name = __("Normal");
2223+
2224+ Q_FOREACH(const QString &option, options) {
2225+ if (option == QLatin1String("DefaultColorModel")) {
2226+ ret[option] = QVariant::fromValue(rgb);
2227+ } else if (option == QLatin1String("DefaultPrintQuality")) {
2228+ ret[option] = QVariant::fromValue(quality);
2229+ } else if (option == QLatin1String("SupportedPrintQualities")) {
2230+ auto qualities = QList<PrintQuality>({quality});
2231+ ret[option] = QVariant::fromValue(qualities);
2232+ } else if (option == QLatin1String("SupportedColorModels")) {
2233+ auto models = QList<ColorModel>{rgb};
2234+ ret[option] = QVariant::fromValue(models);
2235+ } else if (option == QLatin1String("AcceptJobs")) {
2236+ ret[option] = true;
2237+ } else {
2238+ throw std::invalid_argument("Invalid value for PDF printer: " + option.toStdString());
2239+ }
2240+ }
2241+
2242+ return ret;
2243+}
2244+
2245+QString PrinterPdfBackend::printerName() const
2246+{
2247+ return m_printerName;
2248+}
2249+
2250+PrinterEnum::State PrinterPdfBackend::state() const
2251+{
2252+ return PrinterEnum::State::IdleState;
2253+}
2254+
2255+QList<QPageSize> PrinterPdfBackend::supportedPageSizes() const
2256+{
2257+ return QList<QPageSize>{QPageSize(QPageSize::A4)};
2258+}
2259+
2260+QPageSize PrinterPdfBackend::defaultPageSize() const
2261+{
2262+ return QPageSize(QPageSize::A4);
2263+}
2264+
2265+bool PrinterPdfBackend::supportsCustomPageSizes() const
2266+{
2267+ return false;
2268+}
2269+
2270+QPageSize PrinterPdfBackend::minimumPhysicalPageSize() const
2271+{
2272+ return QPageSize(QPageSize::A4);
2273+}
2274+
2275+QPageSize PrinterPdfBackend::maximumPhysicalPageSize() const
2276+{
2277+ return QPageSize(QPageSize::A4);
2278+}
2279+
2280+QList<int> PrinterPdfBackend::supportedResolutions() const
2281+{
2282+ return QList<int>{};
2283+}
2284+
2285+PrinterEnum::DuplexMode PrinterPdfBackend::defaultDuplexMode() const
2286+{
2287+ return PrinterEnum::DuplexMode::DuplexNone;
2288+}
2289+
2290+QList<PrinterEnum::DuplexMode> PrinterPdfBackend::supportedDuplexModes() const
2291+{
2292+ return QList<PrinterEnum::DuplexMode>{PrinterEnum::DuplexMode::DuplexNone};
2293+}
2294+
2295
2296=== added file 'modules/Ubuntu/Components/Extras/Printers/backend/backend_pdf.h'
2297--- modules/Ubuntu/Components/Extras/Printers/backend/backend_pdf.h 1970-01-01 00:00:00 +0000
2298+++ modules/Ubuntu/Components/Extras/Printers/backend/backend_pdf.h 2017-02-24 12:21:53 +0000
2299@@ -0,0 +1,47 @@
2300+/*
2301+ * Copyright (C) 2017 Canonical, Ltd.
2302+ *
2303+ * This program is free software; you can redistribute it and/or modify
2304+ * it under the terms of the GNU Lesser General Public License as published by
2305+ * the Free Software Foundation; version 3.
2306+ *
2307+ * This program is distributed in the hope that it will be useful,
2308+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2309+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2310+ * GNU Lesser General Public License for more details.
2311+ *
2312+ * You should have received a copy of the GNU Lesser General Public License
2313+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2314+ */
2315+
2316+#ifndef USC_PRINTERS_PDF_BACKEND_H
2317+#define USC_PRINTERS_PDF_BACKEND_H
2318+
2319+#include "backend/backend.h"
2320+
2321+class PRINTERS_DECL_EXPORT PrinterPdfBackend : public PrinterBackend
2322+{
2323+public:
2324+ explicit PrinterPdfBackend(const QString &printerName,
2325+ QObject *parent = Q_NULLPTR);
2326+ virtual QVariant printerGetOption(const QString &name,
2327+ const QString &option) const override;
2328+ virtual QMap<QString, QVariant> printerGetOptions(
2329+ const QString &name, const QStringList &options
2330+ ) const override;
2331+
2332+ virtual QString printerName() const override;
2333+
2334+ virtual PrinterEnum::State state() const override;
2335+ virtual QList<QPageSize> supportedPageSizes() const override;
2336+ virtual QPageSize defaultPageSize() const override;
2337+ virtual bool supportsCustomPageSizes() const override;
2338+
2339+ virtual QPageSize minimumPhysicalPageSize() const override;
2340+ virtual QPageSize maximumPhysicalPageSize() const override;
2341+ virtual QList<int> supportedResolutions() const override;
2342+ virtual PrinterEnum::DuplexMode defaultDuplexMode() const override;
2343+ virtual QList<PrinterEnum::DuplexMode> supportedDuplexModes() const override;
2344+};
2345+
2346+#endif // USC_PRINTERS_PDF_BACKEND_H
2347
2348=== added directory 'modules/Ubuntu/Components/Extras/Printers/cups'
2349=== added file 'modules/Ubuntu/Components/Extras/Printers/cups/ippclient.cpp'
2350--- modules/Ubuntu/Components/Extras/Printers/cups/ippclient.cpp 1970-01-01 00:00:00 +0000
2351+++ modules/Ubuntu/Components/Extras/Printers/cups/ippclient.cpp 2017-02-24 12:21:53 +0000
2352@@ -0,0 +1,988 @@
2353+/*
2354+ * Copyright (C) 2017 Canonical, Ltd.
2355+ * Copyright (C) 2014 John Layt <jlayt@kde.org>
2356+ * Copyright (C) 2002, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2013, 2014 Red Hat, Inc.
2357+ * Copyright (C) 2008 Novell, Inc.
2358+ *
2359+ * This program is free software; you can redistribute it and/or modify
2360+ * it under the terms of the GNU Lesser General Public License as published by
2361+ * the Free Software Foundation; version 3.
2362+ *
2363+ * This program is distributed in the hope that it will be useful,
2364+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2365+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2366+ * GNU Lesser General Public License for more details.
2367+ *
2368+ * You should have received a copy of the GNU Lesser General Public License
2369+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2370+ */
2371+
2372+#include "cups/ippclient.h"
2373+
2374+#include <errno.h>
2375+#include <string.h>
2376+#include <unistd.h>
2377+
2378+#include <QDebug>
2379+#include <QDateTime>
2380+#include <QTimeZone>
2381+#include <QUrl>
2382+
2383+IppClient::IppClient()
2384+ : m_connection(httpConnectEncrypt(cupsServer(),
2385+ ippPort(),
2386+ cupsEncryption()))
2387+{
2388+ if (!m_connection) {
2389+ qCritical("Failed to connect to cupsd");
2390+ } else {
2391+ qDebug("Successfully connected to cupsd.");
2392+ }
2393+}
2394+
2395+IppClient::~IppClient()
2396+{
2397+ if (m_connection)
2398+ httpClose(m_connection);
2399+}
2400+
2401+bool IppClient::printerDelete(const QString &printerName)
2402+{
2403+ return sendNewSimpleRequest(CUPS_DELETE_PRINTER, printerName.toUtf8(),
2404+ CupsResource::CupsResourceAdmin);
2405+}
2406+
2407+bool IppClient::printerAdd(const QString &printerName,
2408+ const QString &printerUri,
2409+ const QString &ppdFile,
2410+ const QString &info,
2411+ const QString &location)
2412+{
2413+ ipp_t *request;
2414+
2415+ if (!isPrinterNameValid(printerName)) {
2416+ setInternalStatus(QString("%1 is not a valid printer name.").arg(printerName));
2417+ return false;
2418+ }
2419+
2420+ if (!isStringValid(info)) {
2421+ setInternalStatus(QString("%1 is not a valid description.").arg(info));
2422+ return false;
2423+ }
2424+
2425+ if (!isStringValid(location)) {
2426+ setInternalStatus(QString("%1 is not a valid location.").arg(location));
2427+ return false;
2428+ }
2429+
2430+ if (!isStringValid(ppdFile)) {
2431+ setInternalStatus(QString("%1 is not a valid ppd file.").arg(ppdFile));
2432+ return false;
2433+ }
2434+
2435+ if (!isStringValid(printerUri)) {
2436+ setInternalStatus(QString("%1 is not a valid printer uri.").arg(printerUri));
2437+ return false;
2438+ }
2439+
2440+
2441+ request = ippNewRequest (CUPS_ADD_MODIFY_PRINTER);
2442+ addPrinterUri(request, printerName);
2443+ addRequestingUsername(request, NULL);
2444+
2445+ ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_NAME,
2446+ "printer-name", NULL, printerName.toUtf8());
2447+
2448+ if (!ppdFile.isEmpty()) {
2449+ ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_NAME,
2450+ "ppd-name", NULL, ppdFile.toUtf8());
2451+ }
2452+ if (!printerUri.isEmpty()) {
2453+ ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_URI,
2454+ "device-uri", NULL, printerUri.toUtf8());
2455+ }
2456+ if (!info.isEmpty()) {
2457+ ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_TEXT,
2458+ "printer-info", NULL, info.toUtf8());
2459+ }
2460+ if (!location.isEmpty()) {
2461+ ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_TEXT,
2462+ "printer-location", NULL, location.toUtf8());
2463+ }
2464+
2465+ return sendRequest(request, CupsResourceAdmin);
2466+}
2467+
2468+bool IppClient::printerAddWithPpdFile(const QString &printerName,
2469+ const QString &printerUri,
2470+ const QString &ppdFileName,
2471+ const QString &info,
2472+ const QString &location)
2473+{
2474+ ipp_t *request;
2475+
2476+ if (!isPrinterNameValid(printerName)) {
2477+ setInternalStatus(QString("%1 is not a valid printer name.").arg(printerName));
2478+ return false;
2479+ }
2480+
2481+ if (!isStringValid(info)) {
2482+ setInternalStatus(QString("%1 is not a valid description.").arg(info));
2483+ return false;
2484+ }
2485+
2486+ if (!isStringValid(location)) {
2487+ setInternalStatus(QString("%1 is not a valid location.").arg(location));
2488+ return false;
2489+ }
2490+
2491+ if (!isStringValid(ppdFileName)) {
2492+ setInternalStatus(QString("%1 is not a valid ppd file name.").arg(ppdFileName));
2493+ return false;
2494+ }
2495+
2496+ if (!isStringValid(printerUri)) {
2497+ setInternalStatus(QString("%1 is not a valid printer uri.").arg(printerUri));
2498+ return false;
2499+ }
2500+
2501+ request = ippNewRequest(CUPS_ADD_MODIFY_PRINTER);
2502+ addPrinterUri(request, printerName);
2503+ addRequestingUsername(request, NULL);
2504+
2505+ ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_NAME,
2506+ "printer-name", NULL, printerName.toUtf8());
2507+
2508+ /* In this specific case of ADD_MODIFY, the URI can be NULL/empty since
2509+ * we provide a complete PPD. And cups fails if we pass an empty
2510+ * string. */
2511+ if (!printerUri.isEmpty()) {
2512+ ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_URI,
2513+ "device-uri", NULL, printerUri.toUtf8());
2514+ }
2515+
2516+ if (!info.isEmpty()) {
2517+ ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_TEXT,
2518+ "printer-info", NULL, info.toUtf8());
2519+ }
2520+ if (!location.isEmpty()) {
2521+ ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_TEXT,
2522+ "printer-location", NULL, location.toUtf8());
2523+ }
2524+
2525+ return postRequest(request, ppdFileName.toUtf8(), CupsResourceAdmin);
2526+}
2527+
2528+bool IppClient::printerSetDefault(const QString &printerName)
2529+{
2530+ return sendNewSimpleRequest(CUPS_SET_DEFAULT, printerName.toUtf8(),
2531+ CupsResource::CupsResourceAdmin);
2532+}
2533+
2534+bool IppClient::printerSetEnabled(const QString &printerName,
2535+ const bool enabled)
2536+{
2537+ ipp_op_t op;
2538+ op = enabled ? IPP_RESUME_PRINTER : IPP_PAUSE_PRINTER;
2539+ return sendNewSimpleRequest(op, printerName, CupsResourceAdmin);
2540+}
2541+
2542+/* reason must be empty if accept is true */
2543+bool IppClient::printerSetAcceptJobs(const QString &printerName,
2544+ const bool accept,
2545+ const QString &reason)
2546+{
2547+ ipp_t *request;
2548+
2549+ if (accept && !reason.isEmpty()) {
2550+ setInternalStatus("Accepting jobs does not take a reason.");
2551+ return false;
2552+ }
2553+
2554+ if (!isPrinterNameValid(printerName)) {
2555+ setInternalStatus(QString("%1 is not a valid printer name.").arg(printerName));
2556+ return false;
2557+ }
2558+
2559+ if (!isStringValid(reason)) {
2560+ setInternalStatus(QString("%1 is not a valid reason.").arg(reason));
2561+ return false;
2562+ }
2563+
2564+ if (accept) {
2565+ return sendNewSimpleRequest(CUPS_ACCEPT_JOBS, printerName.toUtf8(),
2566+ CupsResourceAdmin);
2567+ } else {
2568+ request = ippNewRequest(CUPS_REJECT_JOBS);
2569+ addPrinterUri(request, printerName);
2570+ addRequestingUsername(request, NULL);
2571+
2572+ if (!reason.isEmpty())
2573+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_TEXT,
2574+ "printer-state-message", NULL, reason.toUtf8());
2575+
2576+ return sendRequest(request, CupsResourceAdmin);
2577+ }
2578+}
2579+
2580+
2581+bool IppClient::printerClassSetInfo(const QString &name,
2582+ const QString &info)
2583+{
2584+ if (!isPrinterNameValid(name)) {
2585+ setInternalStatus(QString("%1 is not a valid printer name.").arg(name));
2586+ return false;
2587+ }
2588+
2589+ if (!isStringValid(info)) {
2590+ setInternalStatus(QString("%1 is not a valid description.").arg(info));
2591+ return false;
2592+ }
2593+
2594+ return sendNewPrinterClassRequest(name, IPP_TAG_PRINTER, IPP_TAG_TEXT,
2595+ "printer-info", info);
2596+}
2597+
2598+bool IppClient::printerClassSetOption(const QString &name,
2599+ const QString &option,
2600+ const QStringList &values)
2601+{
2602+ bool isClass;
2603+ int length = 0;
2604+ ipp_t *request;
2605+ ipp_attribute_t *attr;
2606+ QString newPpdFile;
2607+ bool retval;
2608+
2609+ if (!isPrinterNameValid(name)) {
2610+ setInternalStatus(QString("%1 is not a valid printer name.").arg(name));
2611+ return false;
2612+ }
2613+
2614+ if (!isStringValid(option)) {
2615+ setInternalStatus(QString("%1 is not a valid option.").arg(option));
2616+ return false;
2617+ }
2618+
2619+ Q_FOREACH(const QString &val, values) {
2620+ if (!isStringValid(val)) {
2621+ setInternalStatus(QString("%1 is not a valid value.").arg(val));
2622+ return false;
2623+ }
2624+ length++;
2625+ }
2626+
2627+ if (length == 0) {
2628+ setInternalStatus("No valid values.");
2629+ return false;
2630+ }
2631+
2632+ isClass = printerIsClass(name);
2633+
2634+ /* We permit only one value to change in PPD file because we are setting
2635+ * default value in it. */
2636+ if (!isClass && length == 1) {
2637+ cups_option_t *options = NULL;
2638+ int numOptions = 0;
2639+ QString ppdfile;
2640+
2641+ numOptions = cupsAddOption(option.toUtf8(),
2642+ values[0].toUtf8(),
2643+ numOptions, &options);
2644+
2645+ ppdfile = QString(cupsGetPPD(name.toUtf8()));
2646+
2647+ newPpdFile = preparePpdForOptions(ppdfile.toUtf8(),
2648+ options, numOptions).toLatin1().data();
2649+
2650+ unlink(ppdfile.toUtf8());
2651+ cupsFreeOptions(numOptions, options);
2652+ }
2653+
2654+ if (isClass) {
2655+ request = ippNewRequest(CUPS_ADD_MODIFY_CLASS);
2656+ addClassUri(request, name);
2657+ } else {
2658+ request = ippNewRequest(CUPS_ADD_MODIFY_PRINTER);
2659+ addPrinterUri(request, name);
2660+ }
2661+
2662+ addRequestingUsername(request, NULL);
2663+
2664+ if (length == 1) {
2665+ ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_NAME,
2666+ option.toUtf8(),
2667+ NULL,
2668+ values[0].toUtf8());
2669+ } else {
2670+ int i;
2671+
2672+ attr = ippAddStrings(request, IPP_TAG_PRINTER, IPP_TAG_NAME,
2673+ option.toUtf8(), length, NULL, NULL);
2674+
2675+ for (i = 0; i < length; i++)
2676+ ippSetString(request, &attr, i, values[i].toUtf8());
2677+ }
2678+
2679+ if (!newPpdFile.isEmpty()) {
2680+ retval = postRequest(request, newPpdFile, CupsResourceAdmin);
2681+
2682+ unlink(newPpdFile.toUtf8());
2683+ // TODO: fix leak here.
2684+ } else {
2685+ retval = sendRequest(request, CupsResourceAdmin);
2686+ }
2687+
2688+ return retval;
2689+}
2690+
2691+QMap<QString, QVariant> IppClient::printerGetJobAttributes(const int jobId)
2692+{
2693+ ipp_t *request;
2694+ QMap<QString, QVariant> map;
2695+
2696+ // Construct request
2697+ request = ippNewRequest(IPP_GET_JOB_ATTRIBUTES);
2698+ QString uri = QStringLiteral("ipp://localhost/jobs/") + QString::number(jobId);
2699+ qDebug() << "URI:" << uri;
2700+
2701+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri", NULL, uri.toStdString().data());
2702+
2703+
2704+ // Send request and construct reply
2705+ ipp_t *reply;
2706+ const QString resourceChar = getResource(CupsResourceRoot);
2707+ reply = cupsDoRequest(m_connection, request,
2708+ resourceChar.toUtf8());
2709+
2710+ // Check if the reply is OK
2711+ if (isReplyOk(reply, false)) {
2712+ // Loop through the attributes
2713+ ipp_attribute_t *attr;
2714+
2715+ for (attr = ippFirstAttribute(reply); attr; attr = ippNextAttribute(reply)) {
2716+ QVariant value = getAttributeValue(attr);
2717+ map.insert(ippGetName(attr), value);
2718+ }
2719+ } else {
2720+ qWarning() << "Not able to get attributes of job:" << jobId;
2721+ }
2722+
2723+ // Destruct the reply if valid
2724+ if (reply) {
2725+ ippDelete(reply);
2726+ }
2727+
2728+ return map;
2729+}
2730+
2731+
2732+/* This function sets given options to specified values in file 'ppdfile'.
2733+ * This needs to be done because of applications which use content of PPD files
2734+ * instead of IPP attributes.
2735+ * CUPS doesn't do this automatically (but hopefully will starting with 1.6) */
2736+QString IppClient::preparePpdForOptions(const QString &ppdfile,
2737+ cups_option_t *options, int numOptions)
2738+{
2739+ auto ppdfile_c = ppdfile.toUtf8();
2740+ ppd_file_t *ppd;
2741+ bool ppdchanged = false;
2742+ QString result;
2743+ QString error;
2744+ char newppdfile[PATH_MAX];
2745+ cups_file_t *in = NULL;
2746+ cups_file_t *out = NULL;
2747+ char line[CPH_STR_MAXLEN];
2748+ char keyword[CPH_STR_MAXLEN];
2749+ char *keyptr;
2750+ ppd_choice_t *choice;
2751+ QString value;
2752+ QLatin1String defaultStr("*Default");
2753+
2754+ ppd = ppdOpenFile(ppdfile_c);
2755+ if (!ppd) {
2756+ error = QString("Unable to open PPD file \"%1\": %2")
2757+ .arg(ppdfile).arg(strerror(errno));
2758+ setInternalStatus(error);
2759+ goto out;
2760+ }
2761+
2762+ in = cupsFileOpen(ppdfile_c, "r");
2763+ if (!in) {
2764+ error = QString("Unable to open PPD file \"%1\": %2")
2765+ .arg(ppdfile).arg(strerror(errno));
2766+ setInternalStatus(error);
2767+ goto out;
2768+ }
2769+
2770+ out = cupsTempFile2(newppdfile, sizeof(newppdfile));
2771+ if (!out) {
2772+ setInternalStatus("Unable to create temporary file");
2773+ goto out;
2774+ }
2775+
2776+ /* Mark default values and values of options we are changing. */
2777+ ppdMarkDefaults(ppd);
2778+ cupsMarkOptions(ppd, numOptions, options);
2779+
2780+ while (cupsFileGets(in, line, sizeof(line))) {
2781+ QString line_qs(line);
2782+ if (!line_qs.startsWith(defaultStr)) {
2783+ cupsFilePrintf(out, "%s\n", line);
2784+ } else {
2785+ /* This part parses lines with *Default on their
2786+ * beginning. For instance:
2787+ * "*DefaultResolution: 1200dpi" becomes:
2788+ * - keyword: Resolution
2789+ * - keyptr: 1200dpi
2790+ */
2791+ strncpy(keyword, line + defaultStr.size(), sizeof(keyword));
2792+
2793+ for (keyptr = keyword; *keyptr; keyptr++)
2794+ if (*keyptr == ':' || isspace (*keyptr & 255))
2795+ break;
2796+
2797+ *keyptr++ = '\0';
2798+ while (isspace (*keyptr & 255))
2799+ keyptr++;
2800+
2801+ QString keyword_sq(keyword);
2802+ QString keyptr_qs(keyptr);
2803+
2804+ /* We have to change PageSize if any of PageRegion,
2805+ * PageSize, PaperDimension or ImageableArea changes.
2806+ * We change PageRegion if PageSize is not available. */
2807+ if (keyword_sq == "PageRegion" ||
2808+ keyword_sq == "PageSize" ||
2809+ keyword_sq == "PaperDimension" ||
2810+ keyword_sq == "ImageableArea") {
2811+
2812+ choice = ppdFindMarkedChoice(ppd, "PageSize");
2813+ if (!choice)
2814+ choice = ppdFindMarkedChoice(ppd, "PageRegion");
2815+ } else {
2816+ choice = ppdFindMarkedChoice(ppd, keyword);
2817+ }
2818+
2819+
2820+ QString choice_qs;
2821+ if (choice) {
2822+ choice_qs = choice->choice;
2823+ }
2824+
2825+ if (choice && choice_qs != keyptr_qs) {
2826+ /* We have to set the value in PPD manually if
2827+ * a custom value was passed in:
2828+ * cupsMarkOptions() marks the choice as
2829+ * "Custom". We want to set this value with our
2830+ * input. */
2831+ if (choice_qs != "Custom") {
2832+ cupsFilePrintf(out,
2833+ "*Default%s: %s\n",
2834+ keyword,
2835+ choice->choice);
2836+ ppdchanged = true;
2837+ } else {
2838+ value = cupsGetOption(keyword, numOptions, options);
2839+ if (!value.isEmpty()) {
2840+ cupsFilePrintf(out,
2841+ "*Default%s: %s\n",
2842+ keyword,
2843+ value.toStdString().c_str());
2844+ ppdchanged = true;
2845+ } else {
2846+ cupsFilePrintf(out, "%s\n", line);
2847+ }
2848+ }
2849+ } else {
2850+ cupsFilePrintf(out, "%s\n", line);
2851+ }
2852+ }
2853+ }
2854+
2855+ if (ppdchanged)
2856+ result = QString::fromUtf8(newppdfile);
2857+ else
2858+ unlink(newppdfile);
2859+
2860+out:
2861+ if (in)
2862+ cupsFileClose(in);
2863+ if (out)
2864+ cupsFileClose(out);
2865+ if (ppd)
2866+ ppdClose(ppd);
2867+
2868+ return result;
2869+}
2870+
2871+
2872+bool IppClient::sendNewPrinterClassRequest(const QString &printerName,
2873+ ipp_tag_t group, ipp_tag_t type,
2874+ const QString &name,
2875+ const QString &value)
2876+{
2877+ ipp_t *request;
2878+
2879+ request = ippNewRequest(CUPS_ADD_MODIFY_PRINTER);
2880+ addPrinterUri(request, printerName);
2881+ addRequestingUsername(request, QString());
2882+ ippAddString(request, group, type, name.toUtf8(), NULL,
2883+ value.toUtf8());
2884+
2885+ if (sendRequest(request, CupsResource::CupsResourceAdmin))
2886+ return true;
2887+
2888+ // it failed, maybe it was a class?
2889+ if (m_lastStatus != IPP_NOT_POSSIBLE) {
2890+ return false;
2891+ }
2892+
2893+ // TODO: implement class modification <here>.
2894+ return false;
2895+}
2896+
2897+void IppClient::addPrinterUri(ipp_t *request, const QString &name)
2898+{
2899+ QUrl uri(QString("ipp://localhost/printers/%1").arg(name));
2900+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI,
2901+ "printer-uri", NULL, uri.toEncoded().data());
2902+}
2903+
2904+void IppClient::addRequestingUsername(ipp_t *request, const QString &username)
2905+{
2906+ if (!username.isEmpty())
2907+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
2908+ "requesting-user-name", NULL,
2909+ username.toUtf8());
2910+ else
2911+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
2912+ "requesting-user-name", NULL, cupsUser());
2913+}
2914+
2915+QString IppClient::getLastError() const
2916+{
2917+ return m_internalStatus;
2918+}
2919+
2920+const QString IppClient::getResource(const IppClient::CupsResource &resource)
2921+{
2922+ switch (resource) {
2923+ case CupsResourceRoot:
2924+ return "/";
2925+ case CupsResourceAdmin:
2926+ return "/admin/";
2927+ case CupsResourceJobs:
2928+ return "/jobs/";
2929+ default:
2930+ qCritical("Asking for a resource with no match.");
2931+ return "/";
2932+ }
2933+}
2934+
2935+bool IppClient::isPrinterNameValid(const QString &name)
2936+{
2937+ int i;
2938+ int len;
2939+
2940+ /* Quoting the lpadmin man page:
2941+ * CUPS allows printer names to contain any printable character
2942+ * except SPACE, TAB, "/", or "#".
2943+ * On top of that, validate_name() in lpadmin.c (from cups) checks that
2944+ * the string is 127 characters long, or shorter. */
2945+
2946+ /* no empty string */
2947+ if (name.isEmpty())
2948+ return false;
2949+
2950+ len = name.size();
2951+ /* no string that is too long; see comment at the beginning of the
2952+ * validation code block */
2953+ if (len > 127)
2954+ return false;
2955+
2956+ /* only printable characters, no space, no /, no # */
2957+ for (i = 0; i < len; i++) {
2958+ const QChar c = name.at(i);
2959+ if (!c.isPrint())
2960+ return false;
2961+ if (c.isSpace())
2962+ return false;
2963+ if (c == '/' || c == '#')
2964+ return false;
2965+ }
2966+ return true;
2967+}
2968+
2969+bool IppClient::isStringValid(const QString &string, const bool checkNull,
2970+ const int maxLength)
2971+{
2972+ if (isStringPrintable(string, checkNull, maxLength))
2973+ return true;
2974+ return false;
2975+}
2976+
2977+bool IppClient::isStringPrintable(const QString &string, const bool checkNull,
2978+ const int maxLength)
2979+{
2980+ int i;
2981+ int len;
2982+
2983+ /* no null string */
2984+ if (string.isNull())
2985+ return !checkNull;
2986+
2987+ len = string.size();
2988+ if (maxLength > 0 && len > maxLength)
2989+ return false;
2990+
2991+ /* only printable characters */
2992+ for (i = 0; i < len; i++) {
2993+ const QChar c = string.at(i);
2994+ if (!c.isPrint())
2995+ return false;
2996+ }
2997+ return true;
2998+}
2999+
3000+void IppClient::setInternalStatus(const QString &status)
3001+{
3002+ if (!m_internalStatus.isNull()) {
3003+ m_internalStatus = QString::null;
3004+ }
3005+
3006+ if (status.isNull()) {
3007+ m_internalStatus = QString::null;
3008+ } else {
3009+ m_internalStatus = status;
3010+
3011+ // Only used for errors for now.
3012+ qCritical() << status;
3013+ }
3014+}
3015+
3016+bool IppClient::postRequest(ipp_t *request, const QString &file,
3017+ const CupsResource &resource)
3018+{
3019+ ipp_t *reply;
3020+ QString resourceChar;
3021+
3022+ resourceChar = getResource(resource);
3023+
3024+ if (!file.isEmpty())
3025+ reply = cupsDoFileRequest(m_connection, request, resourceChar.toUtf8(),
3026+ file.toUtf8());
3027+ else
3028+ reply = cupsDoFileRequest(m_connection, request, resourceChar.toUtf8(),
3029+ NULL);
3030+
3031+ return handleReply(reply);
3032+}
3033+
3034+
3035+bool IppClient::sendRequest(ipp_t *request, const CupsResource &resource)
3036+{
3037+ ipp_t *reply;
3038+ const QString resourceChar = getResource(resource);
3039+ reply = cupsDoRequest(m_connection, request,
3040+ resourceChar.toUtf8());
3041+ return handleReply(reply);
3042+}
3043+
3044+bool IppClient::sendNewSimpleRequest(ipp_op_t op, const QString &printerName,
3045+ const IppClient::CupsResource &resource)
3046+{
3047+ ipp_t *request;
3048+
3049+ if (!isPrinterNameValid(printerName))
3050+ return false;
3051+
3052+ request = ippNewRequest(op);
3053+ addPrinterUri(request, printerName);
3054+ addRequestingUsername(request, NULL);
3055+
3056+ return sendRequest(request, resource);
3057+}
3058+
3059+bool IppClient::handleReply(ipp_t *reply)
3060+{
3061+ bool retval;
3062+ retval = isReplyOk(reply, false);
3063+ if (reply)
3064+ ippDelete(reply);
3065+
3066+ return retval;
3067+}
3068+
3069+bool IppClient::isReplyOk(ipp_t *reply, bool deleteIfReplyNotOk)
3070+{
3071+ /* reset the internal status: we'll use the cups status */
3072+ m_lastStatus = IPP_STATUS_CUPS_INVALID;
3073+
3074+ if (reply && ippGetStatusCode(reply) <= IPP_OK_CONFLICT) {
3075+ m_lastStatus = IPP_OK;
3076+ return true;
3077+ } else {
3078+ setErrorFromReply(reply);
3079+ qWarning() << Q_FUNC_INFO << "Cups HTTP error:" << cupsLastErrorString();
3080+
3081+ if (deleteIfReplyNotOk && reply)
3082+ ippDelete(reply);
3083+
3084+ return false;
3085+ }
3086+}
3087+
3088+void IppClient::setErrorFromReply(ipp_t *reply)
3089+{
3090+ if (reply)
3091+ m_lastStatus = ippGetStatusCode(reply);
3092+ else
3093+ m_lastStatus = cupsLastError();
3094+}
3095+
3096+bool IppClient::printerIsClass(const QString &name)
3097+{
3098+ const char * const attrs[1] = { "member-names" };
3099+ ipp_t *request;
3100+ QString resource;
3101+ ipp_t *reply;
3102+ bool retval;
3103+
3104+ // Class/Printer name validation is equal.
3105+ if (!isPrinterNameValid(name)) {
3106+ setInternalStatus(QString("%1 is not a valid printer name.").arg(name));
3107+ return false;
3108+ }
3109+
3110+ request = ippNewRequest(IPP_GET_PRINTER_ATTRIBUTES);
3111+ addClassUri(request, name);
3112+ addRequestingUsername(request, QString());
3113+ ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
3114+ "requested-attributes", 1, NULL, attrs);
3115+
3116+ resource = getResource(CupsResource::CupsResourceRoot);
3117+ reply = cupsDoRequest(m_connection, request, resource.toUtf8());
3118+
3119+ if (!isReplyOk(reply, true))
3120+ return true;
3121+
3122+ /* Note: we need to look if the attribute is there, since we get a
3123+ * reply if the name is a printer name and not a class name. The
3124+ * attribute is the only way to distinguish the two cases. */
3125+ retval = ippFindAttribute(reply, attrs[0], IPP_TAG_NAME) != NULL;
3126+
3127+ if (reply)
3128+ ippDelete(reply);
3129+
3130+ return retval;
3131+}
3132+
3133+void IppClient::addClassUri(ipp_t *request, const QString &name)
3134+{
3135+ QUrl uri(QString("ipp://localhost/printers/%1").arg(name));
3136+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI,
3137+ "printer-uri", NULL, uri.toEncoded().data());
3138+}
3139+
3140+ppd_file_t* IppClient::getPpdFile(const QString &name,
3141+ const QString &instance) const
3142+{
3143+ Q_UNUSED(instance);
3144+
3145+ ppd_file_t* file = 0;
3146+ const char *ppdFile = cupsGetPPD(name.toUtf8());
3147+ if (ppdFile) {
3148+ file = ppdOpenFile(ppdFile);
3149+ unlink(ppdFile);
3150+ }
3151+ if (file) {
3152+ ppdMarkDefaults(file);
3153+ } else {
3154+ file = 0;
3155+ }
3156+
3157+ return file;
3158+}
3159+
3160+cups_dest_t* IppClient::getDest(const QString &name,
3161+ const QString &instance) const
3162+{
3163+ cups_dest_t *dest = 0;
3164+
3165+ if (instance.isEmpty()) {
3166+ dest = cupsGetNamedDest(m_connection, name.toUtf8(), NULL);
3167+ } else {
3168+ dest = cupsGetNamedDest(m_connection, name.toUtf8(), instance.toUtf8());
3169+ }
3170+ return dest;
3171+}
3172+
3173+ipp_t* IppClient::createPrinterDriversRequest(
3174+ const QString &deviceId, const QString &language, const QString &makeModel,
3175+ const QString &product, const QStringList &includeSchemes,
3176+ const QStringList &excludeSchemes
3177+)
3178+{
3179+ Q_UNUSED(includeSchemes);
3180+ Q_UNUSED(excludeSchemes);
3181+
3182+ ipp_t *request;
3183+
3184+ request = ippNewRequest(CUPS_GET_PPDS);
3185+
3186+ if (!deviceId.isEmpty())
3187+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_TEXT, "ppd-device-id",
3188+ NULL, deviceId.toUtf8());
3189+ if (!language.isEmpty())
3190+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE, "ppd-language",
3191+ NULL, language.toUtf8());
3192+ if (!makeModel.isEmpty())
3193+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_TEXT, "ppd-make-and-model",
3194+ NULL, makeModel.toUtf8());
3195+ if (!product.isEmpty())
3196+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_TEXT, "ppd-product",
3197+ NULL, product.toUtf8());
3198+
3199+ // Do the request and get return the response.
3200+ const QString resourceChar = getResource(CupsResourceRoot);
3201+ return cupsDoRequest(m_connection, request,
3202+ resourceChar.toUtf8());
3203+}
3204+
3205+int IppClient::createSubscription()
3206+{
3207+ ipp_t *req;
3208+ ipp_t *resp;
3209+ ipp_attribute_t *attr;
3210+ int subscriptionId = -1;
3211+
3212+ req = ippNewRequest(IPP_CREATE_PRINTER_SUBSCRIPTION);
3213+ ippAddString(req, IPP_TAG_OPERATION, IPP_TAG_URI,
3214+ "printer-uri", NULL, "/");
3215+ ippAddString(req, IPP_TAG_SUBSCRIPTION, IPP_TAG_KEYWORD,
3216+ "notify-events", NULL, "all");
3217+ ippAddString(req, IPP_TAG_SUBSCRIPTION, IPP_TAG_URI,
3218+ "notify-recipient-uri", NULL, "dbus://");
3219+ ippAddInteger(req, IPP_TAG_SUBSCRIPTION, IPP_TAG_INTEGER,
3220+ "notify-lease-duration", 0);
3221+
3222+ resp = cupsDoRequest(m_connection, req,
3223+ getResource(CupsResourceRoot).toUtf8());
3224+ if (!isReplyOk(resp, true)) {
3225+ return subscriptionId;
3226+ }
3227+
3228+ attr = ippFindAttribute(resp, "notify-subscription-id", IPP_TAG_INTEGER);
3229+
3230+ if (!attr) {
3231+ qWarning() << "ipp-create-printer-subscription response doesn't"
3232+ " contain subscription id.";
3233+ } else {
3234+ subscriptionId = ippGetInteger(attr, 0);
3235+ }
3236+
3237+ ippDelete (resp);
3238+
3239+ return subscriptionId;
3240+}
3241+
3242+void IppClient::cancelSubscription(const int &subscriptionId)
3243+{
3244+ ipp_t *req;
3245+ ipp_t *resp;
3246+
3247+ if (subscriptionId <= 0) {
3248+ return;
3249+ }
3250+
3251+ req = ippNewRequest(IPP_CANCEL_SUBSCRIPTION);
3252+ ippAddString(req, IPP_TAG_OPERATION, IPP_TAG_URI,
3253+ "printer-uri", NULL, "/");
3254+ ippAddInteger(req, IPP_TAG_OPERATION, IPP_TAG_INTEGER,
3255+ "notify-subscription-id", subscriptionId);
3256+
3257+ resp = cupsDoRequest(m_connection, req,
3258+ getResource(CupsResourceRoot).toUtf8());
3259+ if (!isReplyOk(resp, true)) {
3260+ return;
3261+ }
3262+
3263+ ippDelete(resp);
3264+}
3265+
3266+QVariant IppClient::getAttributeValue(ipp_attribute_t *attr, int index) const
3267+{
3268+ QVariant var;
3269+
3270+ if (ippGetCount(attr) > 1 && index < 0) {
3271+ QList<QVariant> list;
3272+
3273+ for (int i=0; i < ippGetCount(attr); i++) {
3274+ list.append(getAttributeValue(attr, i));
3275+ }
3276+
3277+ var = QVariant::fromValue<QList<QVariant>>(list);
3278+ } else {
3279+ if (index == -1) {
3280+ index = 0;
3281+ }
3282+
3283+ switch (ippGetValueTag(attr)) {
3284+ case IPP_TAG_NAME:
3285+ case IPP_TAG_TEXT:
3286+ case IPP_TAG_KEYWORD:
3287+ case IPP_TAG_URI:
3288+ case IPP_TAG_CHARSET:
3289+ case IPP_TAG_MIMETYPE:
3290+ case IPP_TAG_LANGUAGE:
3291+ var = QVariant::fromValue<QString>(ippGetString(attr, index, NULL));
3292+ break;
3293+ case IPP_TAG_INTEGER:
3294+ case IPP_TAG_ENUM:
3295+ var = QVariant::fromValue<int>(ippGetInteger(attr, index));
3296+ break;
3297+ case IPP_TAG_BOOLEAN:
3298+ var = QVariant::fromValue<bool>(ippGetBoolean(attr, index));
3299+ break;
3300+ case IPP_TAG_RANGE: {
3301+ QString range;
3302+ int upper;
3303+ int lower = ippGetRange(attr, index, &upper);
3304+
3305+ // Build a string similar to "1-3" "5-" "8" "-4"
3306+ if (lower != INT_MIN) {
3307+ range += QString::number(lower);
3308+ }
3309+
3310+ if (lower != upper) {
3311+ range += QStringLiteral("-");
3312+
3313+ if (upper != INT_MAX) {
3314+ range += QString::number(upper);
3315+ }
3316+ }
3317+
3318+ var = QVariant(range);
3319+ break;
3320+ }
3321+ case IPP_TAG_NOVALUE:
3322+ var = QVariant();
3323+ break;
3324+ case IPP_TAG_DATE: {
3325+ time_t time = ippDateToTime(ippGetDate(attr, index));
3326+ QDateTime datetime;
3327+ datetime.setTimeZone(QTimeZone::systemTimeZone());
3328+ datetime.setTime_t(time);
3329+
3330+ var = QVariant::fromValue<QDateTime>(datetime);
3331+ break;
3332+ }
3333+ default:
3334+ qWarning() << "Unknown IPP value tab 0x" << ippGetValueTag(attr);
3335+ break;
3336+ }
3337+ }
3338+
3339+ return var;
3340+}
3341
3342=== added file 'modules/Ubuntu/Components/Extras/Printers/cups/ippclient.h'
3343--- modules/Ubuntu/Components/Extras/Printers/cups/ippclient.h 1970-01-01 00:00:00 +0000
3344+++ modules/Ubuntu/Components/Extras/Printers/cups/ippclient.h 2017-02-24 12:21:53 +0000
3345@@ -0,0 +1,121 @@
3346+/*
3347+ * Copyright (C) 2017 Canonical, Ltd.
3348+ *
3349+ * This program is free software; you can redistribute it and/or modify
3350+ * it under the terms of the GNU Lesser General Public License as published by
3351+ * the Free Software Foundation; version 3.
3352+ *
3353+ * This program is distributed in the hope that it will be useful,
3354+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3355+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3356+ * GNU Lesser General Public License for more details.
3357+ *
3358+ * You should have received a copy of the GNU Lesser General Public License
3359+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3360+ */
3361+
3362+#ifndef USC_PRINTERS_CUPS_IPPCLIENT_H
3363+#define USC_PRINTERS_CUPS_IPPCLIENT_H
3364+
3365+#include "structs.h"
3366+
3367+#include <cups/cups.h>
3368+#include <cups/http.h>
3369+#include <cups/ipp.h>
3370+#include <cups/ppd.h>
3371+
3372+#include <QString>
3373+#include <QStringList>
3374+
3375+/* From https://bugzilla.novell.com/show_bug.cgi?id=447444#c5
3376+ * We need to define a maximum length for strings to avoid cups
3377+ * thinking there are multiple lines.
3378+ */
3379+#define CPH_STR_MAXLEN 512
3380+
3381+class IppClient
3382+{
3383+public:
3384+ explicit IppClient();
3385+ ~IppClient();
3386+
3387+ bool printerDelete(const QString &printerName);
3388+ bool printerAdd(const QString &printerName,
3389+ const QString &printerUri,
3390+ const QString &ppdFile,
3391+ const QString &info,
3392+ const QString &location);
3393+
3394+ bool printerAddWithPpdFile(const QString &printerName,
3395+ const QString &printerUri,
3396+ const QString &ppdFileName,
3397+ const QString &info,
3398+ const QString &location);
3399+ bool printerSetDefault(const QString &printerName);
3400+ bool printerSetEnabled(const QString &printerName, const bool enabled);
3401+ bool printerSetAcceptJobs(const QString &printerName, const bool accept,
3402+ const QString &reason);
3403+ bool printerClassSetInfo(const QString &name, const QString &info);
3404+ bool printerClassSetOption(const QString &name, const QString &option,
3405+ const QStringList &values);
3406+ ppd_file_t* getPpdFile(const QString &name, const QString &instance) const;
3407+ cups_dest_t* getDest(const QString &name, const QString &instance) const;
3408+
3409+ QMap<QString, QVariant> printerGetJobAttributes(const int jobId);
3410+
3411+ QString getLastError() const;
3412+
3413+ // Note: This response needs to be free by the caller.
3414+ ipp_t* createPrinterDriversRequest(
3415+ const QString &deviceId, const QString &language,
3416+ const QString &makeModel, const QString &product,
3417+ const QStringList &includeSchemes, const QStringList &excludeSchemes
3418+ );
3419+ int createSubscription();
3420+ void cancelSubscription(const int &subscriptionId);
3421+
3422+private:
3423+ enum CupsResource
3424+ {
3425+ CupsResourceRoot = 0,
3426+ CupsResourceAdmin,
3427+ CupsResourceJobs,
3428+ };
3429+
3430+ bool sendNewPrinterClassRequest(const QString &printerName,
3431+ ipp_tag_t group,
3432+ ipp_tag_t type,
3433+ const QString &name,
3434+ const QString &value);
3435+ static void addPrinterUri(ipp_t *request, const QString &name);
3436+ static void addRequestingUsername(ipp_t *request, const QString &username);
3437+ static const QString getResource(const CupsResource &resource);
3438+ static bool isPrinterNameValid(const QString &name);
3439+ static void addClassUri(ipp_t *request, const QString &name);
3440+ static bool isStringValid(const QString &string,
3441+ const bool checkNull = false,
3442+ const int maxLength = 512);
3443+ static bool isStringPrintable(const QString &string, const bool checkNull,
3444+ const int maxLength);
3445+ QString preparePpdForOptions(const QString &ppdfile,
3446+ cups_option_t *options,
3447+ int numOptions);
3448+ bool printerIsClass(const QString &name);
3449+ void setInternalStatus(const QString &status);
3450+ bool postRequest(ipp_t *request, const QString &file,
3451+ const CupsResource &resource);
3452+ bool sendRequest(ipp_t *request, const CupsResource &resource);
3453+ bool sendNewSimpleRequest(ipp_op_t op, const QString &printerName,
3454+ const CupsResource &resource);
3455+ bool handleReply(ipp_t *reply);
3456+ bool isReplyOk(ipp_t *reply, bool deleteIfReplyNotOk);
3457+ void setErrorFromReply(ipp_t *reply);
3458+ QVariant getAttributeValue(ipp_attribute_t *attr, int index=-1) const;
3459+
3460+ http_t *m_connection;
3461+ ipp_status_t m_lastStatus = IPP_OK;
3462+ mutable QString m_internalStatus = QString::null;
3463+};
3464+
3465+
3466+#endif // USC_PRINTERS_CUPS_IPPCLIENT_H
3467
3468=== added file 'modules/Ubuntu/Components/Extras/Printers/cups/printerdriverloader.cpp'
3469--- modules/Ubuntu/Components/Extras/Printers/cups/printerdriverloader.cpp 1970-01-01 00:00:00 +0000
3470+++ modules/Ubuntu/Components/Extras/Printers/cups/printerdriverloader.cpp 2017-02-24 12:21:53 +0000
3471@@ -0,0 +1,127 @@
3472+/*
3473+ * Copyright (C) 2017 Canonical, Ltd.
3474+ *
3475+ * This program is free software; you can redistribute it and/or modify
3476+ * it under the terms of the GNU Lesser General Public License as published by
3477+ * the Free Software Foundation; version 3.
3478+ *
3479+ * This program is distributed in the hope that it will be useful,
3480+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3481+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3482+ * GNU Lesser General Public License for more details.
3483+ *
3484+ * You should have received a copy of the GNU Lesser General Public License
3485+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3486+ */
3487+
3488+#include "printerdriverloader.h"
3489+
3490+PrinterDriverLoader::PrinterDriverLoader(
3491+ const QString &deviceId, const QString &language,
3492+ const QString &makeModel, const QString &product,
3493+ const QStringList &includeSchemes, const QStringList &excludeSchemes)
3494+ : m_deviceId(deviceId)
3495+ , m_language(language)
3496+ , m_makeModel(makeModel)
3497+ , m_product(product)
3498+ , m_includeSchemes(includeSchemes)
3499+ , m_excludeSchemes(excludeSchemes)
3500+{
3501+}
3502+
3503+PrinterDriverLoader::~PrinterDriverLoader()
3504+{
3505+}
3506+
3507+void PrinterDriverLoader::process()
3508+{
3509+ m_running = true;
3510+
3511+ ipp_t* response = client.createPrinterDriversRequest(
3512+ m_deviceId, m_language, m_makeModel, m_product, m_includeSchemes,
3513+ m_excludeSchemes
3514+ );
3515+
3516+ // Note: if the response somehow fails, we return.
3517+ if (!response || ippGetStatusCode(response) > IPP_OK_CONFLICT) {
3518+ QString err(cupsLastErrorString());
3519+ qWarning() << Q_FUNC_INFO << "Cups HTTP error:" << err;
3520+
3521+ if (response)
3522+ ippDelete(response);
3523+
3524+ Q_EMIT error(err);
3525+ Q_EMIT finished();
3526+ return;
3527+ }
3528+
3529+ ipp_attribute_t *attr;
3530+ QByteArray ppdDeviceId;
3531+ QByteArray ppdLanguage;
3532+ QByteArray ppdMakeModel;
3533+ QByteArray ppdName;
3534+
3535+ QList<PrinterDriver> drivers;
3536+
3537+ for (attr = ippFirstAttribute(response); attr != NULL && m_running; attr = ippNextAttribute(response)) {
3538+
3539+ while (attr != NULL && ippGetGroupTag(attr) != IPP_TAG_PRINTER)
3540+ attr = ippNextAttribute(response);
3541+
3542+ if (attr == NULL)
3543+ break;
3544+
3545+ // Pull the needed attributes from this PPD...
3546+ ppdDeviceId = "NONE";
3547+ ppdLanguage.clear();
3548+ ppdMakeModel.clear();
3549+ ppdName.clear();
3550+
3551+ while (attr != NULL && ippGetGroupTag(attr) == IPP_TAG_PRINTER) {
3552+ if (!strcmp(ippGetName(attr), "ppd-device-id") &&
3553+ ippGetValueTag(attr) == IPP_TAG_TEXT) {
3554+ ppdDeviceId = ippGetString(attr, 0, NULL);
3555+ } else if (!strcmp(ippGetName(attr), "ppd-natural-language") &&
3556+ ippGetValueTag(attr) == IPP_TAG_LANGUAGE) {
3557+ ppdLanguage = ippGetString(attr, 0, NULL);
3558+
3559+ } else if (!strcmp(ippGetName(attr), "ppd-make-and-model") &&
3560+ ippGetValueTag(attr) == IPP_TAG_TEXT) {
3561+ ppdMakeModel = ippGetString(attr, 0, NULL);
3562+ } else if (!strcmp(ippGetName(attr), "ppd-name") &&
3563+ ippGetValueTag(attr) == IPP_TAG_NAME) {
3564+
3565+ ppdName = ippGetString(attr, 0, NULL);
3566+ }
3567+
3568+ attr = ippNextAttribute(response);
3569+ }
3570+
3571+ // See if we have everything needed...
3572+ if (ppdLanguage.isEmpty() || ppdMakeModel.isEmpty() ||
3573+ ppdName.isEmpty()) {
3574+ if (attr == NULL)
3575+ break;
3576+ else
3577+ continue;
3578+ }
3579+
3580+ PrinterDriver m;
3581+ m.name = ppdName;
3582+ m.deviceId = ppdDeviceId;
3583+ m.makeModel = ppdMakeModel;
3584+ m.language = ppdLanguage;
3585+
3586+ drivers.append(m);
3587+ }
3588+
3589+ ippDelete(response);
3590+
3591+ Q_EMIT loaded(drivers);
3592+ Q_EMIT finished();
3593+}
3594+
3595+void PrinterDriverLoader::cancel()
3596+{
3597+ m_running = false;
3598+}
3599
3600=== added file 'modules/Ubuntu/Components/Extras/Printers/cups/printerdriverloader.h'
3601--- modules/Ubuntu/Components/Extras/Printers/cups/printerdriverloader.h 1970-01-01 00:00:00 +0000
3602+++ modules/Ubuntu/Components/Extras/Printers/cups/printerdriverloader.h 2017-02-24 12:21:53 +0000
3603@@ -0,0 +1,61 @@
3604+/*
3605+ * Copyright (C) 2017 Canonical, Ltd.
3606+ *
3607+ * This program is free software; you can redistribute it and/or modify
3608+ * it under the terms of the GNU Lesser General Public License as published by
3609+ * the Free Software Foundation; version 3.
3610+ *
3611+ * This program is distributed in the hope that it will be useful,
3612+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3613+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3614+ * GNU Lesser General Public License for more details.
3615+ *
3616+ * You should have received a copy of the GNU Lesser General Public License
3617+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3618+ */
3619+
3620+#ifndef USC_PRINTERS_CUPS_DRIVERLOADER_H
3621+#define USC_PRINTERS_CUPS_DRIVERLOADER_H
3622+
3623+#include "ippclient.h"
3624+#include "structs.h"
3625+
3626+#include <QObject>
3627+#include <QString>
3628+#include <QStringList>
3629+
3630+class PrinterDriverLoader : public QObject
3631+{
3632+ Q_OBJECT
3633+public:
3634+ PrinterDriverLoader(
3635+ const QString &deviceId = "",
3636+ const QString &language = "",
3637+ const QString &makeModel = "",
3638+ const QString &product = "",
3639+ const QStringList &includeSchemes = QStringList(),
3640+ const QStringList &excludeSchemes = QStringList());
3641+ ~PrinterDriverLoader();
3642+
3643+public Q_SLOTS:
3644+ void process();
3645+ void cancel();
3646+
3647+Q_SIGNALS:
3648+ void finished();
3649+ void loaded(const QList<PrinterDriver> &drivers);
3650+ void error(const QString &error);
3651+
3652+private:
3653+ QString m_deviceId = QString::null;
3654+ QString m_language = QString::null;
3655+ QString m_makeModel = QString::null;
3656+ QString m_product = QString::null;
3657+ QStringList m_includeSchemes;
3658+ QStringList m_excludeSchemes;
3659+
3660+ bool m_running = false;
3661+ IppClient client;
3662+};
3663+
3664+#endif // USC_PRINTERS_CUPS_DRIVERLOADER_H
3665
3666=== added file 'modules/Ubuntu/Components/Extras/Printers/cups/printerloader.cpp'
3667--- modules/Ubuntu/Components/Extras/Printers/cups/printerloader.cpp 1970-01-01 00:00:00 +0000
3668+++ modules/Ubuntu/Components/Extras/Printers/cups/printerloader.cpp 2017-02-24 12:21:53 +0000
3669@@ -0,0 +1,53 @@
3670+/*
3671+ * Copyright (C) 2017 Canonical, Ltd.
3672+ *
3673+ * This program is free software; you can redistribute it and/or modify
3674+ * it under the terms of the GNU Lesser General Public License as published by
3675+ * the Free Software Foundation; version 3.
3676+ *
3677+ * This program is distributed in the hope that it will be useful,
3678+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3679+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3680+ * GNU Lesser General Public License for more details.
3681+ *
3682+ * You should have received a copy of the GNU Lesser General Public License
3683+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3684+ */
3685+
3686+#include "backend/backend_pdf.h"
3687+#include "backend/backend_cups.h"
3688+#include "printerloader.h"
3689+
3690+#include <QPrinterInfo>
3691+
3692+class PrinterCupsBackend;
3693+PrinterLoader::PrinterLoader(const QString &printerName,
3694+ IppClient *client,
3695+ OrgCupsCupsdNotifierInterface* notifier,
3696+ QObject *parent)
3697+ : QObject(parent)
3698+ , m_printerName(printerName)
3699+ , m_client(client)
3700+ , m_notifier(notifier)
3701+{
3702+}
3703+
3704+PrinterLoader::~PrinterLoader()
3705+{
3706+}
3707+
3708+void PrinterLoader::load()
3709+{
3710+ QPrinterInfo info = QPrinterInfo::printerInfo(m_printerName);
3711+ auto backend = new PrinterCupsBackend(m_client, info, m_notifier);
3712+
3713+ // Dest or PPD was null, but we know it's name so we will use it.
3714+ if (info.printerName().isEmpty()) {
3715+ backend->setPrinterNameInternal(m_printerName);
3716+ }
3717+
3718+ auto p = QSharedPointer<Printer>(new Printer(backend));
3719+
3720+ Q_EMIT loaded(p);
3721+ Q_EMIT finished();
3722+}
3723
3724=== added file 'modules/Ubuntu/Components/Extras/Printers/cups/printerloader.h'
3725--- modules/Ubuntu/Components/Extras/Printers/cups/printerloader.h 1970-01-01 00:00:00 +0000
3726+++ modules/Ubuntu/Components/Extras/Printers/cups/printerloader.h 2017-02-24 12:21:53 +0000
3727@@ -0,0 +1,49 @@
3728+/*
3729+ * Copyright (C) 2017 Canonical, Ltd.
3730+ *
3731+ * This program is free software; you can redistribute it and/or modify
3732+ * it under the terms of the GNU Lesser General Public License as published by
3733+ * the Free Software Foundation; version 3.
3734+ *
3735+ * This program is distributed in the hope that it will be useful,
3736+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3737+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3738+ * GNU Lesser General Public License for more details.
3739+ *
3740+ * You should have received a copy of the GNU Lesser General Public License
3741+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3742+ */
3743+
3744+#ifndef USC_PRINTERS_CUPS_PRINTERLOADER_H
3745+#define USC_PRINTERS_CUPS_PRINTERLOADER_H
3746+
3747+#include "cups/ippclient.h"
3748+#include "cupsdnotifier.h" // Note: this file was generated.
3749+#include "printer/printer.h"
3750+
3751+#include <QList>
3752+#include <QObject>
3753+#include <QSharedPointer>
3754+
3755+class PrinterLoader : public QObject
3756+{
3757+ Q_OBJECT
3758+ const QString m_printerName;
3759+ IppClient *m_client;
3760+ OrgCupsCupsdNotifierInterface *m_notifier;
3761+public:
3762+ explicit PrinterLoader(const QString &printerName,
3763+ IppClient *client,
3764+ OrgCupsCupsdNotifierInterface* notifier,
3765+ QObject *parent = Q_NULLPTR);
3766+ ~PrinterLoader();
3767+
3768+public Q_SLOTS:
3769+ void load();
3770+
3771+Q_SIGNALS:
3772+ void finished();
3773+ void loaded(QSharedPointer<Printer> printer);
3774+};
3775+
3776+#endif // USC_PRINTERS_CUPS_PRINTERLOADER_H
3777
3778=== added file 'modules/Ubuntu/Components/Extras/Printers/enums.h'
3779--- modules/Ubuntu/Components/Extras/Printers/enums.h 1970-01-01 00:00:00 +0000
3780+++ modules/Ubuntu/Components/Extras/Printers/enums.h 2017-02-24 12:21:53 +0000
3781@@ -0,0 +1,106 @@
3782+/*
3783+ * Copyright (C) 2017 Canonical, Ltd.
3784+ *
3785+ * This program is free software; you can redistribute it and/or modify
3786+ * it under the terms of the GNU Lesser General Public License as published by
3787+ * the Free Software Foundation; version 3.
3788+ *
3789+ * This program is distributed in the hope that it will be useful,
3790+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3791+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3792+ * GNU Lesser General Public License for more details.
3793+ *
3794+ * You should have received a copy of the GNU Lesser General Public License
3795+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3796+ */
3797+
3798+#ifndef USC_PRINTERS_ENUMS_H
3799+#define USC_PRINTERS_ENUMS_H
3800+
3801+#include "printers_global.h"
3802+
3803+#include <QtCore/QObject>
3804+
3805+class PRINTERS_DECL_EXPORT PrinterEnum
3806+{
3807+ Q_GADGET
3808+
3809+public:
3810+ enum class AccessControl
3811+ {
3812+ AccessAllow = 0,
3813+ AccessDeny,
3814+ };
3815+ Q_ENUM(AccessControl)
3816+
3817+ enum class ColorModelType
3818+ {
3819+ GrayType = 0,
3820+ ColorType,
3821+ UnknownType,
3822+ };
3823+ Q_ENUM(ColorModelType)
3824+
3825+ enum class DuplexMode
3826+ {
3827+ DuplexNone = 0,
3828+ DuplexLongSide,
3829+ DuplexShortSide,
3830+ };
3831+ Q_ENUM(DuplexMode)
3832+
3833+ enum class ErrorPolicy
3834+ {
3835+ RetryOnError = 0,
3836+ AbortOnError,
3837+ StopPrinterOnError,
3838+ RetryCurrentOnError,
3839+ };
3840+ Q_ENUM(ErrorPolicy)
3841+
3842+ // Match enums from ipp_jstate_t
3843+ enum class JobState
3844+ {
3845+ Pending = 3,
3846+ Held,
3847+ Processing,
3848+ Stopped,
3849+ Canceled,
3850+ Aborted,
3851+ Complete,
3852+ };
3853+ Q_ENUM(JobState)
3854+
3855+ enum class OperationPolicy
3856+ {
3857+ DefaultOperation = 0,
3858+ AuthenticatedOperation,
3859+ };
3860+ Q_ENUM(OperationPolicy)
3861+
3862+ enum class PrintRange
3863+ {
3864+ AllPages = 0,
3865+ PageRange,
3866+ };
3867+ Q_ENUM(PrintRange)
3868+
3869+ enum class State
3870+ {
3871+ IdleState = 0,
3872+ ActiveState,
3873+ AbortedState,
3874+ ErrorState,
3875+ };
3876+ Q_ENUM(State)
3877+
3878+ enum class PrinterType
3879+ {
3880+ ProxyType = 0, // Represents a printer not yet loaded.
3881+ CupsType,
3882+ PdfType,
3883+ };
3884+ Q_ENUM(PrinterType)
3885+};
3886+
3887+#endif // USC_PRINTERS_ENUMS_H
3888
3889=== added file 'modules/Ubuntu/Components/Extras/Printers/i18n.cpp'
3890--- modules/Ubuntu/Components/Extras/Printers/i18n.cpp 1970-01-01 00:00:00 +0000
3891+++ modules/Ubuntu/Components/Extras/Printers/i18n.cpp 2017-02-24 12:21:53 +0000
3892@@ -0,0 +1,42 @@
3893+/*
3894+ * Copyright (C) 2014, 2017 Canonical, Ltd.
3895+ *
3896+ * This program is free software; you can redistribute it and/or modify
3897+ * it under the terms of the GNU Lesser General Public License as published by
3898+ * the Free Software Foundation; version 3.
3899+ *
3900+ * This program is distributed in the hope that it will be useful,
3901+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3902+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3903+ * GNU Lesser General Public License for more details.
3904+ *
3905+ * You should have received a copy of the GNU Lesser General Public License
3906+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3907+ *
3908+ * Authored by: Ken VanDine <ken.vandine@canonical.com>
3909+ * Andrew Hayzen <andrew.hayzen@canonical.com>
3910+ */
3911+
3912+#define NO_TR_OVERRIDE
3913+#include "i18n.h"
3914+
3915+#include <libintl.h>
3916+
3917+const char *thisDomain = "";
3918+
3919+void initTr(const char *domain, const char *localeDir)
3920+{
3921+ // Don't bind the domain or set textdomain as it changes the Apps domain
3922+ // as well. Instead store the domain and use it in the lookups
3923+ Q_UNUSED(localeDir);
3924+
3925+ thisDomain = domain;
3926+}
3927+
3928+QString __(const char *text, const char *domain)
3929+{
3930+ Q_UNUSED(domain);
3931+
3932+ // Use the stored domain
3933+ return QString::fromUtf8(dgettext(thisDomain, text));
3934+}
3935
3936=== added file 'modules/Ubuntu/Components/Extras/Printers/i18n.h'
3937--- modules/Ubuntu/Components/Extras/Printers/i18n.h 1970-01-01 00:00:00 +0000
3938+++ modules/Ubuntu/Components/Extras/Printers/i18n.h 2017-02-24 12:21:53 +0000
3939@@ -0,0 +1,29 @@
3940+/*
3941+ * Copyright (C) 2014, 2017 Canonical, Ltd.
3942+ *
3943+ * This program is free software; you can redistribute it and/or modify
3944+ * it under the terms of the GNU Lesser General Public License as published by
3945+ * the Free Software Foundation; version 3.
3946+ *
3947+ * This program is distributed in the hope that it will be useful,
3948+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3949+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3950+ * GNU Lesser General Public License for more details.
3951+ *
3952+ * You should have received a copy of the GNU Lesser General Public License
3953+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3954+ *
3955+ * Authored by: Ken VanDine <ken.vandine@canonical.com>
3956+ * Andrew Hayzen <andrew.hayzen@canonical.com>
3957+ */
3958+
3959+#ifndef I18N_H
3960+#define I18N_H
3961+
3962+#include <QtCore/QString>
3963+
3964+void initTr(const char *domain, const char *localeDir);
3965+QString __(const char *text, const char *domain = 0);
3966+
3967+#endif // I18N_H
3968+
3969
3970=== added directory 'modules/Ubuntu/Components/Extras/Printers/models'
3971=== added file 'modules/Ubuntu/Components/Extras/Printers/models/drivermodel.cpp'
3972--- modules/Ubuntu/Components/Extras/Printers/models/drivermodel.cpp 1970-01-01 00:00:00 +0000
3973+++ modules/Ubuntu/Components/Extras/Printers/models/drivermodel.cpp 2017-02-24 12:21:53 +0000
3974@@ -0,0 +1,175 @@
3975+/*
3976+ * Copyright (C) 2017 Canonical, Ltd.
3977+ *
3978+ * This program is free software; you can redistribute it and/or modify
3979+ * it under the terms of the GNU Lesser General Public License as published by
3980+ * the Free Software Foundation; version 3.
3981+ *
3982+ * This program is distributed in the hope that it will be useful,
3983+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3984+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3985+ * GNU Lesser General Public License for more details.
3986+ *
3987+ * You should have received a copy of the GNU Lesser General Public License
3988+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3989+ */
3990+
3991+#include "backend/backend_cups.h"
3992+#include "models/drivermodel.h"
3993+
3994+#include <QDebug>
3995+#include <QtConcurrent>
3996+
3997+DriverModel::DriverModel(PrinterBackend *backend, QObject *parent)
3998+ : QAbstractListModel(parent)
3999+ , m_backend(backend)
4000+{
4001+ connect(m_backend, SIGNAL(printerDriversLoaded(const QList<PrinterDriver>&)),
4002+ this, SLOT(printerDriversLoaded(const QList<PrinterDriver>&)));
4003+
4004+ QObject::connect(&m_watcher,
4005+ &QFutureWatcher<PrinterDriver>::finished,
4006+ this,
4007+ &DriverModel::filterFinished);
4008+
4009+}
4010+
4011+DriverModel::~DriverModel()
4012+{
4013+ cancel();
4014+}
4015+
4016+int DriverModel::rowCount(const QModelIndex &parent) const
4017+{
4018+ Q_UNUSED(parent);
4019+ return m_drivers.size();
4020+}
4021+
4022+int DriverModel::count() const
4023+{
4024+ return rowCount();
4025+}
4026+
4027+QVariant DriverModel::data(const QModelIndex &index, int role) const
4028+{
4029+ QVariant ret;
4030+
4031+ if ((0 <= index.row()) && (index.row() < m_drivers.size())) {
4032+
4033+ auto driver = m_drivers[index.row()];
4034+
4035+ switch (role) {
4036+ case Qt::DisplayRole:
4037+ ret = driver.toString();
4038+ break;
4039+ case NameRole:
4040+ ret = driver.name;
4041+ break;
4042+ case DeviceIdRole:
4043+ ret = driver.deviceId;
4044+ break;
4045+ case LanguageRole:
4046+ ret = driver.language;
4047+ break;
4048+ case MakeModelRole:
4049+ ret = driver.makeModel;
4050+ break;
4051+ }
4052+ }
4053+
4054+ return ret;
4055+}
4056+
4057+QHash<int, QByteArray> DriverModel::roleNames() const
4058+{
4059+ static QHash<int,QByteArray> names;
4060+
4061+ if (Q_UNLIKELY(names.empty())) {
4062+ names[Qt::DisplayRole] = "displayName";
4063+ names[NameRole] = "name";
4064+ names[DeviceIdRole] = "deviceId";
4065+ names[LanguageRole] = "language";
4066+ names[MakeModelRole] = "makeModel";
4067+ }
4068+
4069+ return names;
4070+}
4071+
4072+void DriverModel::setFilter(const QString& pattern)
4073+{
4074+ QList<QByteArray> needles;
4075+ Q_FOREACH(const QString patternPart, pattern.toLower().split(" ")) {
4076+ needles.append(patternPart.toUtf8());
4077+ }
4078+ QList<PrinterDriver> list;
4079+
4080+ if (m_watcher.isRunning())
4081+ m_watcher.cancel();
4082+
4083+ if (pattern.isEmpty()) {
4084+ setModel(m_originalDrivers);
4085+ m_filter = pattern;
4086+ return;
4087+ }
4088+
4089+ if (!m_filter.isEmpty() && !m_drivers.isEmpty() &&
4090+ pattern.startsWith(m_filter))
4091+ list = m_drivers; // search in the smaller list
4092+ else
4093+ list = m_originalDrivers; //search in the whole list
4094+
4095+ m_filter = pattern;
4096+
4097+ QFuture<PrinterDriver> future(QtConcurrent::filtered(list,
4098+ [needles] (const PrinterDriver &driver) {
4099+ QByteArray haystack = driver.makeModel.toLower();
4100+ Q_FOREACH(const QByteArray needle, needles) {
4101+ if (!haystack.contains(needle)) {
4102+ return false;
4103+ }
4104+ }
4105+ return true;
4106+ }
4107+ )
4108+ );
4109+
4110+ Q_EMIT filterBegin();
4111+
4112+ m_watcher.setFuture(future);
4113+}
4114+
4115+QString DriverModel::filter() const
4116+{
4117+ return m_filter;
4118+}
4119+
4120+void DriverModel::filterFinished()
4121+{
4122+ setModel(m_watcher.future().results());
4123+}
4124+
4125+void DriverModel::load()
4126+{
4127+ m_backend->requestPrinterDrivers();
4128+}
4129+
4130+void DriverModel::cancel()
4131+{
4132+ if (m_watcher.isRunning())
4133+ m_watcher.cancel();
4134+}
4135+
4136+void DriverModel::printerDriversLoaded(const QList<PrinterDriver> &drivers)
4137+{
4138+ m_originalDrivers = drivers;
4139+ setModel(m_originalDrivers);
4140+}
4141+
4142+void DriverModel::setModel(const QList<PrinterDriver> &drivers)
4143+{
4144+ beginResetModel();
4145+ m_drivers = drivers;
4146+ endResetModel();
4147+
4148+ Q_EMIT filterComplete();
4149+}
4150
4151=== added file 'modules/Ubuntu/Components/Extras/Printers/models/drivermodel.h'
4152--- modules/Ubuntu/Components/Extras/Printers/models/drivermodel.h 1970-01-01 00:00:00 +0000
4153+++ modules/Ubuntu/Components/Extras/Printers/models/drivermodel.h 2017-02-24 12:21:53 +0000
4154@@ -0,0 +1,82 @@
4155+/*
4156+ * Copyright (C) 2017 Canonical, Ltd.
4157+ *
4158+ * This program is free software; you can redistribute it and/or modify
4159+ * it under the terms of the GNU Lesser General Public License as published by
4160+ * the Free Software Foundation; version 3.
4161+ *
4162+ * This program is distributed in the hope that it will be useful,
4163+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4164+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4165+ * GNU Lesser General Public License for more details.
4166+ *
4167+ * You should have received a copy of the GNU Lesser General Public License
4168+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4169+ */
4170+
4171+#ifndef USC_PRINTER_DRIVERMODEL_H
4172+#define USC_PRINTER_DRIVERMODEL_H
4173+
4174+#include "printers_global.h"
4175+
4176+#include "structs.h"
4177+
4178+#include <QAbstractListModel>
4179+#include <QFutureWatcher>
4180+#include <QModelIndex>
4181+#include <QObject>
4182+#include <QVariant>
4183+
4184+class PRINTERS_DECL_EXPORT DriverModel : public QAbstractListModel
4185+{
4186+ Q_OBJECT
4187+ Q_PROPERTY(int count READ count NOTIFY countChanged)
4188+public:
4189+ explicit DriverModel(PrinterBackend *backend, QObject *parent = Q_NULLPTR);
4190+ ~DriverModel();
4191+
4192+ enum Roles
4193+ {
4194+ // Qt::DisplayRole holds driver name
4195+ NameRole = Qt::UserRole,
4196+ DeviceIdRole,
4197+ LanguageRole,
4198+ MakeModelRole,
4199+ LastRole = MakeModelRole,
4200+ };
4201+
4202+ virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override;
4203+ virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
4204+ virtual QHash<int, QByteArray> roleNames() const override;
4205+
4206+ int count() const;
4207+
4208+ QString filter() const;
4209+ void setFilter(const QString& pattern);
4210+
4211+public Q_SLOTS:
4212+ // Start loading the model.
4213+ void load();
4214+
4215+ // Cancel loading of the model.
4216+ void cancel();
4217+
4218+private Q_SLOTS:
4219+ void printerDriversLoaded(const QList<PrinterDriver> &drivers);
4220+ void filterFinished();
4221+
4222+Q_SIGNALS:
4223+ void countChanged();
4224+ void filterBegin();
4225+ void filterComplete();
4226+
4227+private:
4228+ void setModel(const QList<PrinterDriver> &drivers);
4229+ PrinterBackend *m_backend;
4230+ QList<PrinterDriver> m_drivers;
4231+ QList<PrinterDriver> m_originalDrivers;
4232+ QString m_filter;
4233+ QFutureWatcher<PrinterDriver> m_watcher;
4234+};
4235+
4236+#endif // USC_PRINTER_DRIVERMODEL_H
4237
4238=== added file 'modules/Ubuntu/Components/Extras/Printers/models/jobmodel.cpp'
4239--- modules/Ubuntu/Components/Extras/Printers/models/jobmodel.cpp 1970-01-01 00:00:00 +0000
4240+++ modules/Ubuntu/Components/Extras/Printers/models/jobmodel.cpp 2017-02-24 12:21:53 +0000
4241@@ -0,0 +1,390 @@
4242+/*
4243+ * Copyright (C) 2017 Canonical, Ltd.
4244+ *
4245+ * This program is free software; you can redistribute it and/or modify
4246+ * it under the terms of the GNU Lesser General Public License as published by
4247+ * the Free Software Foundation; version 3.
4248+ *
4249+ * This program is distributed in the hope that it will be useful,
4250+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4251+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4252+ * GNU Lesser General Public License for more details.
4253+ *
4254+ * You should have received a copy of the GNU Lesser General Public License
4255+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4256+ */
4257+
4258+#include "utils.h"
4259+
4260+#include "backend/backend_cups.h"
4261+
4262+#include "models/jobmodel.h"
4263+
4264+#include <QDebug>
4265+
4266+JobModel::JobModel(QObject *parent) : QAbstractListModel(parent)
4267+{
4268+}
4269+
4270+JobModel::JobModel(PrinterBackend *backend,
4271+ QObject *parent)
4272+ : QAbstractListModel(parent)
4273+ , m_backend(backend)
4274+{
4275+ update();
4276+
4277+ QObject::connect(m_backend, &PrinterBackend::jobCreated,
4278+ this, &JobModel::jobSignalCatchAll);
4279+ QObject::connect(m_backend, &PrinterBackend::jobState,
4280+ this, &JobModel::jobSignalCatchAll);
4281+ QObject::connect(m_backend, &PrinterBackend::jobCompleted,
4282+ this, &JobModel::jobSignalCatchAll);
4283+}
4284+
4285+JobModel::~JobModel()
4286+{
4287+}
4288+
4289+void JobModel::jobSignalCatchAll(
4290+ const QString &text, const QString &printer_uri,
4291+ const QString &printer_name, uint printer_state,
4292+ const QString &printer_state_reasons, bool printer_is_accepting_jobs,
4293+ uint job_id, uint job_state, const QString &job_state_reasons,
4294+ const QString &job_name, uint job_impressions_completed)
4295+{
4296+ Q_UNUSED(text);
4297+ Q_UNUSED(printer_uri);
4298+ Q_UNUSED(printer_name);
4299+ Q_UNUSED(printer_state);
4300+ Q_UNUSED(printer_state_reasons);
4301+ Q_UNUSED(printer_is_accepting_jobs);
4302+ Q_UNUSED(job_id);
4303+ Q_UNUSED(job_state);
4304+ Q_UNUSED(job_state_reasons);
4305+ Q_UNUSED(job_name);
4306+
4307+ auto job = getJobById(job_id);
4308+ if (job)
4309+ job->setImpressionsCompleted(job_impressions_completed);
4310+
4311+ update();
4312+}
4313+
4314+void JobModel::update()
4315+{
4316+ // Store the old count and get the new printers
4317+ int oldCount = m_jobs.size();
4318+ QList<QSharedPointer<PrinterJob>> newJobs = m_backend->printerGetJobs();
4319+
4320+ // Go through the old model
4321+ for (int i=0; i < m_jobs.count(); i++) {
4322+ // Determine if the old printer exists in the new model
4323+ bool exists = false;
4324+
4325+ Q_FOREACH(QSharedPointer<PrinterJob> p, newJobs) {
4326+ if (p->jobId() == m_jobs.at(i)->jobId()) {
4327+ exists = true;
4328+
4329+ // Ensure the other properties of the job are up to date
4330+ if (!m_jobs.at(i)->deepCompare(p)) {
4331+ m_jobs.at(i)->updateFrom(p);
4332+
4333+ Q_EMIT dataChanged(index(i), index(i));
4334+ }
4335+
4336+ break;
4337+ }
4338+ }
4339+
4340+ // If it doesn't exist then remove it from the old model
4341+ if (!exists) {
4342+ beginRemoveRows(QModelIndex(), i, i);
4343+ QSharedPointer<PrinterJob> p = m_jobs.takeAt(i);
4344+ endRemoveRows();
4345+
4346+ i--; // as we have removed an item decrement
4347+ }
4348+ }
4349+
4350+ // Go through the new model
4351+ for (int i=0; i < newJobs.count(); i++) {
4352+ // Determine if the new printer exists in the old model
4353+ bool exists = false;
4354+ int j;
4355+
4356+ for (j=0; j < m_jobs.count(); j++) {
4357+ if (m_jobs.at(j)->jobId() == newJobs.at(i)->jobId()) {
4358+ exists = true;
4359+ break;
4360+ }
4361+ }
4362+
4363+ if (exists) {
4364+ if (j == i) { // New printer exists and in correct position
4365+ continue;
4366+ } else {
4367+ // New printer does exist but needs to be moved in old model
4368+ beginMoveRows(QModelIndex(), j, 1, QModelIndex(), i);
4369+ m_jobs.move(j, i);
4370+ endMoveRows();
4371+ }
4372+ } else {
4373+ // New printer does not exist insert into model
4374+ beginInsertRows(QModelIndex(), i, i);
4375+ m_jobs.insert(i, newJobs.at(i));
4376+ endInsertRows();
4377+ }
4378+ }
4379+
4380+ if (oldCount != m_jobs.size()) {
4381+ Q_EMIT countChanged();
4382+ }
4383+}
4384+
4385+int JobModel::rowCount(const QModelIndex &parent) const
4386+{
4387+ Q_UNUSED(parent);
4388+ return m_jobs.size();
4389+}
4390+
4391+int JobModel::count() const
4392+{
4393+ return rowCount();
4394+}
4395+
4396+QVariant JobModel::data(const QModelIndex &index, int role) const
4397+{
4398+ QVariant ret;
4399+
4400+ if ((0<=index.row()) && (index.row()<m_jobs.size())) {
4401+
4402+ auto job = m_jobs[index.row()];
4403+
4404+ switch (role) {
4405+ case CollateRole:
4406+ ret = job->collate();
4407+ break;
4408+ case ColorModelRole: {
4409+ if (job->printer()) {
4410+ ret = job->printer()->supportedColorModels().at(job->colorModel()).text;
4411+ } else {
4412+ qWarning() << "Printer is undefined, no colorModel";
4413+ ret = "";
4414+ }
4415+ break;
4416+ }
4417+ case CompletedTimeRole:
4418+ ret = job->completedTime().toString(QLocale::system().dateTimeFormat());
4419+ break;
4420+ case CopiesRole:
4421+ ret = job->copies();
4422+ break;
4423+ case CreationTimeRole:
4424+ ret = job->creationTime().toString(QLocale::system().dateTimeFormat());
4425+ break;
4426+ case DuplexRole: {
4427+ if (job->printer()) {
4428+ ret = job->printer()->supportedDuplexStrings().at(job->duplexMode());
4429+ } else {
4430+ qWarning() << "Printer is undefined, no duplexMode";
4431+ ret = "";
4432+ }
4433+ break;
4434+ }
4435+ case IdRole:
4436+ ret = job->jobId();
4437+ break;
4438+ case ImpressionsCompletedRole:
4439+ ret = job->impressionsCompleted();
4440+ break;
4441+ case LandscapeRole:
4442+ ret = job->landscape();
4443+ break;
4444+ case MessagesRole:
4445+ ret = job->messages();
4446+ break;
4447+ case PrinterNameRole:
4448+ ret = job->printerName();
4449+ break;
4450+ case PrintRangeRole:
4451+ ret = job->printRange();
4452+ break;
4453+ case PrintRangeModeRole:
4454+ ret = QVariant::fromValue<PrinterEnum::PrintRange>(job->printRangeMode());
4455+ break;
4456+ case ProcessingTimeRole:
4457+ ret = job->processingTime().toString(QLocale::system().dateTimeFormat());
4458+ break;
4459+ case QualityRole: {
4460+ if (job->printer()) {
4461+ ret = job->printer()->supportedPrintQualities().at(job->quality()).text;
4462+ } else {
4463+ qWarning() << "Printer is undefined, no quality";
4464+ ret = "";
4465+ }
4466+ break;
4467+ }
4468+ case ReverseRole:
4469+ ret = job->reverse();
4470+ break;
4471+ case SizeRole:
4472+ ret = job->size();
4473+ break;
4474+ case StateRole:
4475+ // TODO: improve, for now have a switch
4476+ switch (job->state()) {
4477+ case PrinterEnum::JobState::Aborted:
4478+ ret = "Aborted";
4479+ break;
4480+ case PrinterEnum::JobState::Canceled:
4481+ ret = "Canceled";
4482+ break;
4483+ case PrinterEnum::JobState::Complete:
4484+ ret = "Compelete";
4485+ break;
4486+ case PrinterEnum::JobState::Held:
4487+ ret = "Held";
4488+ break;
4489+ case PrinterEnum::JobState::Pending:
4490+ ret = "Pending";
4491+ break;
4492+ case PrinterEnum::JobState::Processing:
4493+ ret = "Processing";
4494+ break;
4495+ case PrinterEnum::JobState::Stopped:
4496+ ret = "Stopped";
4497+ break;
4498+ }
4499+ break;
4500+ case Qt::DisplayRole:
4501+ case TitleRole:
4502+ ret = job->title();
4503+ break;
4504+ case UserRole:
4505+ ret = job->user();
4506+ break;
4507+ }
4508+ }
4509+
4510+ return ret;
4511+}
4512+
4513+QHash<int, QByteArray> JobModel::roleNames() const
4514+{
4515+ static QHash<int,QByteArray> names;
4516+
4517+ if (Q_UNLIKELY(names.empty())) {
4518+ names[Qt::DisplayRole] = "displayName";
4519+ names[IdRole] = "id";
4520+ names[CollateRole] = "collate";
4521+ names[ColorModelRole] = "colorModel";
4522+ names[CompletedTimeRole] = "completedTime";
4523+ names[CopiesRole] = "copies";
4524+ names[CreationTimeRole] = "creationTime";
4525+ names[DuplexRole] = "duplexMode";
4526+ names[ImpressionsCompletedRole] = "impressionsCompleted";
4527+ names[LandscapeRole] = "landscape";
4528+ names[MessagesRole] = "messages";
4529+ names[PrinterNameRole] = "printerName";
4530+ names[PrintRangeRole] = "printRange";
4531+ names[PrintRangeModeRole] = "printRangeMode";
4532+ names[ProcessingTimeRole] = "processingTime";
4533+ names[QualityRole] = "quality";
4534+ names[ReverseRole] = "reverse";
4535+ names[SizeRole] = "size";
4536+ names[StateRole] = "state";
4537+ names[TitleRole] = "title";
4538+ names[UserRole] = "user";
4539+ names[LastStateMessageRole] = "lastStateMessage";
4540+ }
4541+
4542+ return names;
4543+}
4544+
4545+QVariantMap JobModel::get(const int row) const
4546+{
4547+ QHashIterator<int, QByteArray> iterator(roleNames());
4548+ QVariantMap result;
4549+ QModelIndex modelIndex = index(row, 0);
4550+
4551+ while (iterator.hasNext()) {
4552+ iterator.next();
4553+ result[iterator.value()] = modelIndex.data(iterator.key());
4554+ }
4555+
4556+ return result;
4557+}
4558+
4559+QSharedPointer<PrinterJob> JobModel::getJobById(const int &id)
4560+{
4561+ Q_FOREACH(auto job, m_jobs) {
4562+ if (job->jobId() == id) {
4563+ return job;
4564+ }
4565+ }
4566+ return QSharedPointer<PrinterJob>(Q_NULLPTR);
4567+}
4568+
4569+
4570+JobFilter::JobFilter(QObject *parent) : QSortFilterProxyModel(parent)
4571+{
4572+ connect(this, SIGNAL(sourceModelChanged()), SLOT(onSourceModelChanged()));
4573+}
4574+
4575+JobFilter::~JobFilter()
4576+{
4577+}
4578+
4579+QVariantMap JobFilter::get(const int row) const
4580+{
4581+ QHashIterator<int, QByteArray> iterator(roleNames());
4582+ QVariantMap result;
4583+ QModelIndex modelIndex = index(row, 0);
4584+
4585+ while (iterator.hasNext()) {
4586+ iterator.next();
4587+ result[iterator.value()] = modelIndex.data(iterator.key());
4588+ }
4589+
4590+ return result;
4591+}
4592+
4593+void JobFilter::onSourceModelChanged()
4594+{
4595+ connect((JobModel*) sourceModel(),
4596+ SIGNAL(countChanged()),
4597+ this,
4598+ SIGNAL(countChanged()));
4599+}
4600+
4601+void JobFilter::onSourceModelCountChanged()
4602+{
4603+ Q_EMIT countChanged();
4604+}
4605+
4606+int JobFilter::count() const
4607+{
4608+ return rowCount();
4609+}
4610+
4611+void JobFilter::filterOnPrinterName(const QString &name)
4612+{
4613+ m_printerName = name;
4614+ m_printerNameFilterEnabled = true;
4615+ invalidate();
4616+}
4617+
4618+bool JobFilter::filterAcceptsRow(int sourceRow,
4619+ const QModelIndex &sourceParent) const
4620+{
4621+ bool accepts = true;
4622+ QModelIndex childIndex = sourceModel()->index(sourceRow, 0, sourceParent);
4623+
4624+ if (accepts && m_printerNameFilterEnabled) {
4625+ QString printerName = childIndex.model()->data(
4626+ childIndex, JobModel::PrinterNameRole).toString();
4627+ accepts = m_printerName == printerName;
4628+ }
4629+
4630+ return accepts;
4631+}
4632
4633=== added file 'modules/Ubuntu/Components/Extras/Printers/models/jobmodel.h'
4634--- modules/Ubuntu/Components/Extras/Printers/models/jobmodel.h 1970-01-01 00:00:00 +0000
4635+++ modules/Ubuntu/Components/Extras/Printers/models/jobmodel.h 2017-02-24 12:21:53 +0000
4636@@ -0,0 +1,125 @@
4637+/*
4638+ * Copyright (C) 2017 Canonical, Ltd.
4639+ *
4640+ * This program is free software; you can redistribute it and/or modify
4641+ * it under the terms of the GNU Lesser General Public License as published by
4642+ * the Free Software Foundation; version 3.
4643+ *
4644+ * This program is distributed in the hope that it will be useful,
4645+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4646+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4647+ * GNU Lesser General Public License for more details.
4648+ *
4649+ * You should have received a copy of the GNU Lesser General Public License
4650+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4651+ */
4652+
4653+#ifndef USC_JOB_MODEL_H
4654+#define USC_JOB_MODEL_H
4655+
4656+#include "printers_global.h"
4657+#include "backend/backend.h"
4658+#include "printer/printerjob.h"
4659+
4660+#include <QAbstractListModel>
4661+#include <QByteArray>
4662+#include <QModelIndex>
4663+#include <QObject>
4664+#include <QSharedPointer>
4665+#include <QSortFilterProxyModel>
4666+#include <QTimer>
4667+#include <QVariant>
4668+
4669+class PRINTERS_DECL_EXPORT JobModel : public QAbstractListModel
4670+{
4671+ Q_OBJECT
4672+
4673+ Q_PROPERTY(int count READ count NOTIFY countChanged)
4674+public:
4675+ explicit JobModel(QObject *parent = Q_NULLPTR);
4676+ explicit JobModel(PrinterBackend *backend,
4677+ QObject *parent = Q_NULLPTR);
4678+ ~JobModel();
4679+
4680+ enum Roles
4681+ {
4682+ // Qt::DisplayRole holds job title
4683+ IdRole = Qt::UserRole,
4684+ CollateRole,
4685+ ColorModelRole,
4686+ CompletedTimeRole,
4687+ CopiesRole,
4688+ CreationTimeRole,
4689+ DuplexRole,
4690+ ImpressionsCompletedRole,
4691+ LandscapeRole,
4692+ MessagesRole,
4693+ PrinterNameRole,
4694+ PrintRangeRole,
4695+ PrintRangeModeRole,
4696+ ProcessingTimeRole,
4697+ QualityRole,
4698+ ReverseRole,
4699+ SizeRole,
4700+ StateRole,
4701+ TitleRole,
4702+ UserRole,
4703+ LastStateMessageRole,
4704+ LastRole = LastStateMessageRole,
4705+ };
4706+
4707+ virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override;
4708+ virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
4709+ virtual QHash<int, QByteArray> roleNames() const override;
4710+
4711+ int count() const;
4712+
4713+ Q_INVOKABLE QVariantMap get(const int row) const;
4714+ QSharedPointer<PrinterJob> getJobById(const int &id);
4715+private:
4716+ PrinterBackend *m_backend;
4717+
4718+ QList<QSharedPointer<PrinterJob>> m_jobs;
4719+private Q_SLOTS:
4720+ void update();
4721+ void jobSignalCatchAll(const QString &text, const QString &printer_uri,
4722+ const QString &printer_name, uint printer_state,
4723+ const QString &printer_state_reasons,
4724+ bool printer_is_accepting_jobs, uint job_id,
4725+ uint job_state, const QString &job_state_reasons,
4726+ const QString &job_name,
4727+ uint job_impressions_completed);
4728+
4729+Q_SIGNALS:
4730+ void countChanged();
4731+};
4732+
4733+class PRINTERS_DECL_EXPORT JobFilter : public QSortFilterProxyModel
4734+{
4735+ Q_OBJECT
4736+ Q_PROPERTY(int count READ count NOTIFY countChanged)
4737+public:
4738+ explicit JobFilter(QObject *parent = Q_NULLPTR);
4739+ ~JobFilter();
4740+
4741+ Q_INVOKABLE QVariantMap get(const int row) const;
4742+
4743+ void filterOnPrinterName(const QString &name);
4744+ int count() const;
4745+protected:
4746+ virtual bool filterAcceptsRow(
4747+ int sourceRow, const QModelIndex &sourceParent) const override;
4748+
4749+Q_SIGNALS:
4750+ void countChanged();
4751+
4752+private Q_SLOTS:
4753+ void onSourceModelChanged();
4754+ void onSourceModelCountChanged();
4755+
4756+private:
4757+ QString m_printerName = QString::null;
4758+ bool m_printerNameFilterEnabled = false;
4759+};
4760+
4761+#endif // USC_JOB_MODEL_H
4762
4763=== added file 'modules/Ubuntu/Components/Extras/Printers/models/printermodel.cpp'
4764--- modules/Ubuntu/Components/Extras/Printers/models/printermodel.cpp 1970-01-01 00:00:00 +0000
4765+++ modules/Ubuntu/Components/Extras/Printers/models/printermodel.cpp 2017-02-24 12:21:53 +0000
4766@@ -0,0 +1,503 @@
4767+/*
4768+ * Copyright (C) 2017 Canonical, Ltd.
4769+ *
4770+ * This program is free software; you can redistribute it and/or modify
4771+ * it under the terms of the GNU Lesser General Public License as published by
4772+ * the Free Software Foundation; version 3.
4773+ *
4774+ * This program is distributed in the hope that it will be useful,
4775+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4776+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4777+ * GNU Lesser General Public License for more details.
4778+ *
4779+ * You should have received a copy of the GNU Lesser General Public License
4780+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4781+ */
4782+
4783+#include "backend/backend_cups.h"
4784+#include "backend/backend_pdf.h"
4785+#include "i18n.h"
4786+#include "models/jobmodel.h"
4787+#include "models/printermodel.h"
4788+#include "utils.h"
4789+
4790+#include <QDebug>
4791+
4792+PrinterModel::PrinterModel(PrinterBackend *backend, QObject *parent)
4793+ : QAbstractListModel(parent)
4794+ , m_backend(backend)
4795+{
4796+
4797+ QObject::connect(m_backend, &PrinterBackend::printerAdded,
4798+ this, &PrinterModel::printerAdded);
4799+ QObject::connect(m_backend, &PrinterBackend::printerModified,
4800+ &m_signalHandler, &PrinterSignalHandler::onPrinterModified);
4801+ QObject::connect(m_backend, &PrinterBackend::printerStateChanged,
4802+ &m_signalHandler, &PrinterSignalHandler::onPrinterModified);
4803+ QObject::connect(m_backend, &PrinterBackend::printerDeleted,
4804+ this, &PrinterModel::printerDeleted);
4805+
4806+ connect(&m_signalHandler, SIGNAL(printerModified(const QString&)),
4807+ this, SLOT(printerModified(const QString&)));
4808+ connect(m_backend, SIGNAL(printerLoaded(QSharedPointer<Printer>)),
4809+ this, SLOT(printerLoaded(QSharedPointer<Printer>)));
4810+
4811+ // Create printer proxies for every printerName.
4812+ Q_FOREACH(auto printerName, m_backend->availablePrinterNames()) {
4813+ auto p = QSharedPointer<Printer>(new Printer(new PrinterBackend(printerName)));
4814+ addPrinter(p, CountChangeSignal::Defer);
4815+ }
4816+
4817+ // Add a PDF printer.
4818+ auto pdfPrinter = QSharedPointer<Printer>(
4819+ new Printer(new PrinterPdfBackend(__("Create PDF")))
4820+ );
4821+ addPrinter(pdfPrinter, CountChangeSignal::Defer);
4822+
4823+ Q_EMIT countChanged();
4824+}
4825+
4826+PrinterModel::~PrinterModel()
4827+{
4828+}
4829+
4830+void PrinterModel::printerLoaded(QSharedPointer<Printer> printer)
4831+{
4832+ // Find and possibly replace an old printer.
4833+ for (int i=0; i < m_printers.count(); i++) {
4834+ auto oldPrinter = m_printers.at(i);
4835+ if (printer->name() == oldPrinter->name()) {
4836+ if (!oldPrinter->deepCompare(printer)) {
4837+ updatePrinter(oldPrinter, printer);
4838+ }
4839+
4840+ // We're done.
4841+ return;
4842+ }
4843+ }
4844+
4845+ addPrinter(printer, CountChangeSignal::Emit);
4846+}
4847+
4848+void PrinterModel::printerModified(const QString &printerName)
4849+{
4850+ // These signals might be emitted of a now deleted printer.
4851+ if (getPrinterByName(printerName))
4852+ m_backend->requestPrinter(printerName);
4853+}
4854+
4855+void PrinterModel::printerAdded(
4856+ const QString &text, const QString &printerUri,
4857+ const QString &printerName, uint printerState,
4858+ const QString &printerStateReason, bool acceptingJobs)
4859+{
4860+ Q_UNUSED(text);
4861+ Q_UNUSED(printerUri);
4862+ Q_UNUSED(printerState);
4863+ Q_UNUSED(printerStateReason);
4864+ Q_UNUSED(acceptingJobs);
4865+
4866+ m_backend->requestPrinter(printerName);
4867+}
4868+
4869+void PrinterModel::printerDeleted(
4870+ const QString &text, const QString &printerUri,
4871+ const QString &printerName, uint printerState,
4872+ const QString &printerStateReason, bool acceptingJobs)
4873+{
4874+ Q_UNUSED(text);
4875+ Q_UNUSED(printerUri);
4876+ Q_UNUSED(printerState);
4877+ Q_UNUSED(printerStateReason);
4878+ Q_UNUSED(acceptingJobs);
4879+
4880+ auto printer = getPrinterByName(printerName);
4881+ if (printer) {
4882+ removePrinter(printer, CountChangeSignal::Emit);
4883+ }
4884+}
4885+
4886+QSharedPointer<Printer> PrinterModel::getPrinterByName(const QString &printerName)
4887+{
4888+ Q_FOREACH(auto p, m_printers) {
4889+ if (p->name() == printerName)
4890+ return p;
4891+ }
4892+ return QSharedPointer<Printer>(Q_NULLPTR);
4893+}
4894+
4895+void PrinterModel::movePrinter(const int &from, const int &to)
4896+{
4897+ int size = m_printers.size();
4898+ if (from < 0 || to < 0 || from >= size || to >= size) {
4899+ qWarning() << Q_FUNC_INFO << "Illegal move operation from"
4900+ << from << "to" << to << ". Size was" << size;
4901+ return;
4902+ }
4903+ if (!beginMoveRows(QModelIndex(), from, from, QModelIndex(), to)) {
4904+ qWarning() << Q_FUNC_INFO << "failed to move rows.";
4905+ return;
4906+ }
4907+ m_printers.move(from, to);
4908+ endMoveRows();
4909+}
4910+
4911+void PrinterModel::removePrinter(QSharedPointer<Printer> printer, const CountChangeSignal &notify)
4912+{
4913+ int idx = m_printers.indexOf(printer);
4914+ beginRemoveRows(QModelIndex(), idx, idx);
4915+ m_printers.removeAt(idx);
4916+ endRemoveRows();
4917+
4918+ if (notify == CountChangeSignal::Emit)
4919+ Q_EMIT countChanged();
4920+}
4921+
4922+void PrinterModel::updatePrinter(QSharedPointer<Printer> old,
4923+ QSharedPointer<Printer> newPrinter)
4924+{
4925+ int i = m_printers.indexOf(old);
4926+ QModelIndex idx = index(i);
4927+ old->updateFrom(newPrinter);
4928+ Q_EMIT dataChanged(idx, idx);
4929+}
4930+
4931+void PrinterModel::addPrinter(QSharedPointer<Printer> printer, const CountChangeSignal &notify)
4932+{
4933+ int i = m_printers.size();
4934+ beginInsertRows(QModelIndex(), i, i);
4935+ m_printers.append(printer);
4936+ endInsertRows();
4937+
4938+ if (notify == CountChangeSignal::Emit)
4939+ Q_EMIT countChanged();
4940+}
4941+
4942+int PrinterModel::rowCount(const QModelIndex &parent) const
4943+{
4944+ Q_UNUSED(parent);
4945+ return m_printers.size();
4946+}
4947+
4948+int PrinterModel::count() const
4949+{
4950+ return rowCount();
4951+}
4952+
4953+QVariant PrinterModel::data(const QModelIndex &index, int role) const
4954+{
4955+ QVariant ret;
4956+
4957+ if ((0<=index.row()) && (index.row()<m_printers.size())) {
4958+
4959+ auto printer = m_printers[index.row()];
4960+
4961+
4962+ /* If printer is a proxy (not loaded), determine if the requested role
4963+ is something async and that we need to request the data. */
4964+ if (printer->type() == PrinterEnum::PrinterType::ProxyType) {
4965+ switch (role) {
4966+ case Qt::DisplayRole:
4967+ case NameRole:
4968+ case DefaultPrinterRole:
4969+ case PrinterRole:
4970+ case IsPdfRole:
4971+ case IsLoadedRole:
4972+ break; // All of these can be inferred from the name (lazily).
4973+ default:
4974+ m_backend->requestPrinter(printer->name());
4975+ }
4976+ }
4977+
4978+ switch (role) {
4979+ case NameRole:
4980+ case Qt::DisplayRole:
4981+ ret = printer->name();
4982+ break;
4983+ case ColorModelRole:
4984+ ret = printer->supportedColorModels().indexOf(printer->defaultColorModel());
4985+ break;
4986+ case SupportedColorModelsRole: {
4987+ QStringList models;
4988+ Q_FOREACH(const ColorModel &m, printer->supportedColorModels()) {
4989+ models.append(m.text.isEmpty() ? m.name : m.text);
4990+ }
4991+ ret = models;
4992+ }
4993+ break;
4994+ case DefaultPrinterRole:
4995+ ret = printer->name() == m_backend->defaultPrinterName();
4996+ break;
4997+ case DuplexRole:
4998+ ret = printer->supportedDuplexModes().indexOf(printer->defaultDuplexMode());
4999+ break;
5000+ case SupportedDuplexModesRole:
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches