Merge lp:~phablet-team/ubuntu-ui-extras/printer-components into lp:ubuntu-ui-extras

Proposed by Andrew Hayzen
Status: Merged
Approved by: Florian Boucault
Approved revision: 136
Merged at revision: 128
Proposed branch: lp:~phablet-team/ubuntu-ui-extras/printer-components
Merge into: lp:ubuntu-ui-extras
Diff against target: 9641 lines (+9289/-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 (+722/-0)
modules/Ubuntu/Components/Extras/Printers/backend/backend_cups.h (+136/-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 (+311/-0)
modules/Ubuntu/Components/Extras/Printers/printer/printer.h (+96/-0)
modules/Ubuntu/Components/Extras/Printers/printer/printerjob.cpp (+510/-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 (+245/-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:~phablet-team/ubuntu-ui-extras/printer-components
Reviewer Review Type Date Requested Status
Florian Boucault (community) Approve
system-apps-ci-bot continuous-integration Pending
Review via email: mp+317827@code.launchpad.net

Commit message

* Add printer-components from ubuntu-settings-components to ubuntu-ui-extras (original branch https://code.launchpad.net/~phablet-team/ubuntu-settings-components/printer-components)
* Add plugin module to Extras/Printers
* Add translation support for cpp/h
* Add tests for Printers
* Add debian depends

Description of the change

* Add printer-components from ubuntu-settings-components to ubuntu-ui-extras (original branch https://code.launchpad.net/~phablet-team/ubuntu-settings-components/printer-components)
* Add plugin module to Extras/Printers
* Add translation support for cpp/h
* Add tests for Printers
* Add debian depends

To post a comment you must log in.
133. By Jonas G. Drange

adds loadPrinter method on Printers

134. By Jonas G. Drange

guards against excessive loading of printers and drivers

135. By Andrew Hayzen

* Remove use of bitewise or as it fails in CI

136. By Andrew Hayzen

* Pull of trunk

Revision history for this message
Florian Boucault (fboucault) :
review: Approve

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 2017-02-22 10:51:22 +0000
3+++ debian/control 2017-02-24 20:39:42 +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 libexiv2-dev,
13 pkg-config,
14 python:any,
15@@ -30,6 +32,7 @@
16 Architecture: any
17 Depends: ${misc:Depends},
18 ${shlibs:Depends},
19+ libqt5printsupport5,
20 qml-module-qtqml-models2,
21 qml-module-qtquick2,
22 qml-module-ubuntu-components,
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 20:39:42 +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 20:39:42 +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 20:39:42 +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 20:39:42 +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 20:39:42 +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 20:39:42 +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 20:39:42 +0000
1325@@ -0,0 +1,722 @@
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+ if (m_activeRequests.contains(printerName)) {
1942+ return;
1943+ }
1944+
1945+ auto thread = new QThread;
1946+ auto loader = new PrinterLoader(printerName, m_client, m_notifier);
1947+ loader->moveToThread(thread);
1948+ connect(thread, SIGNAL(started()), loader, SLOT(load()));
1949+ connect(loader, SIGNAL(finished()), thread, SLOT(quit()));
1950+ connect(loader, SIGNAL(finished()), loader, SLOT(deleteLater()));
1951+ connect(loader, SIGNAL(loaded(QSharedPointer<Printer>)),
1952+ this, SIGNAL(printerLoaded(QSharedPointer<Printer>)));
1953+ connect(loader, SIGNAL(loaded(QSharedPointer<Printer>)),
1954+ this, SLOT(onPrinterLoaded(QSharedPointer<Printer>)));
1955+ connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
1956+ thread->start();
1957+
1958+ m_activeRequests << printerName;
1959+}
1960+
1961+void PrinterCupsBackend::requestPrinterDrivers()
1962+{
1963+ auto thread = new QThread;
1964+ auto loader = new PrinterDriverLoader();
1965+ loader->moveToThread(thread);
1966+ connect(loader, SIGNAL(error(const QString&)),
1967+ this, SIGNAL(printerDriversFailedToLoad(const QString&)));
1968+ connect(this, SIGNAL(requestPrinterDriverCancel()), loader, SLOT(cancel()));
1969+ connect(thread, SIGNAL(started()), loader, SLOT(process()));
1970+ connect(loader, SIGNAL(finished()), thread, SLOT(quit()));
1971+ connect(loader, SIGNAL(finished()), loader, SLOT(deleteLater()));
1972+ connect(loader, SIGNAL(loaded(const QList<PrinterDriver>&)),
1973+ this, SIGNAL(printerDriversLoaded(const QList<PrinterDriver>&)));
1974+ connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
1975+ thread->start();
1976+}
1977+
1978+void PrinterCupsBackend::cancelPrinterDriverRequest()
1979+{
1980+ Q_EMIT requestPrinterDriverCancel();
1981+}
1982+
1983+void PrinterCupsBackend::refresh()
1984+{
1985+ if (m_printerName.isEmpty()) {
1986+ throw std::invalid_argument("Trying to refresh unnamed printer.");
1987+ } else {
1988+ m_info = QPrinterInfo::printerInfo(m_printerName);
1989+ }
1990+}
1991+
1992+void PrinterCupsBackend::createSubscription()
1993+{
1994+ m_cupsSubscriptionId = m_client->createSubscription();;
1995+}
1996+
1997+void PrinterCupsBackend::cancelSubscription()
1998+{
1999+ if (m_cupsSubscriptionId > 0)
2000+ m_client->cancelSubscription(m_cupsSubscriptionId);
2001+}
2002+
2003+QString PrinterCupsBackend::getPrinterInstance(const QString &name) const
2004+{
2005+ const auto parts = name.splitRef(QLatin1Char('/'));
2006+ QString instance;
2007+ if (parts.size() > 1)
2008+ instance = parts.at(1).toString();
2009+
2010+ return instance;
2011+}
2012+
2013+QString PrinterCupsBackend::getPrinterName(const QString &name) const
2014+{
2015+ return name.splitRef(QLatin1Char('/')).first().toString();
2016+}
2017+
2018+cups_dest_t* PrinterCupsBackend::getDest(const QString &name) const
2019+{
2020+ QString printerName = getPrinterName(name);
2021+ QString instance = getPrinterInstance(name);
2022+
2023+ if (m_dests.contains(name)) {
2024+ return m_dests[name];
2025+ } else {
2026+ m_dests[name] = m_client->getDest(printerName, instance);
2027+ return m_dests[name];
2028+ }
2029+}
2030+
2031+ppd_file_t* PrinterCupsBackend::getPpd(const QString &name) const
2032+{
2033+ QString printerName = getPrinterName(name);
2034+ QString instance = getPrinterInstance(name);
2035+
2036+ if (m_ppds.contains(name)) {
2037+ return m_ppds[name];
2038+ } else {
2039+ m_ppds[name] = m_client->getPpdFile(printerName, instance);
2040+ return m_ppds[name];
2041+ }
2042+}
2043+
2044+void PrinterCupsBackend::onPrinterLoaded(QSharedPointer<Printer> printer)
2045+{
2046+ m_activeRequests.remove(printer->name());
2047+}
2048
2049=== added file 'modules/Ubuntu/Components/Extras/Printers/backend/backend_cups.h'
2050--- modules/Ubuntu/Components/Extras/Printers/backend/backend_cups.h 1970-01-01 00:00:00 +0000
2051+++ modules/Ubuntu/Components/Extras/Printers/backend/backend_cups.h 2017-02-24 20:39:42 +0000
2052@@ -0,0 +1,136 @@
2053+/*
2054+ * Copyright (C) 2017 Canonical, Ltd.
2055+ *
2056+ * This program is free software; you can redistribute it and/or modify
2057+ * it under the terms of the GNU Lesser General Public License as published by
2058+ * the Free Software Foundation; version 3.
2059+ *
2060+ * This program is distributed in the hope that it will be useful,
2061+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2062+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2063+ * GNU Lesser General Public License for more details.
2064+ *
2065+ * You should have received a copy of the GNU Lesser General Public License
2066+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2067+ */
2068+
2069+#ifndef USC_PRINTERS_CUPS_BACKEND_H
2070+#define USC_PRINTERS_CUPS_BACKEND_H
2071+
2072+#include "backend/backend.h"
2073+#include "cups/ippclient.h"
2074+#include "cupsdnotifier.h" // Note: this file was generated.
2075+
2076+#include <cups/cups.h>
2077+
2078+#include <QPrinterInfo>
2079+#include <QSet>
2080+
2081+class PRINTERS_DECL_EXPORT PrinterCupsBackend : public PrinterBackend
2082+{
2083+ Q_OBJECT
2084+public:
2085+ explicit PrinterCupsBackend(IppClient *client, QPrinterInfo info,
2086+ OrgCupsCupsdNotifierInterface* notifier,
2087+ QObject *parent = Q_NULLPTR);
2088+ virtual ~PrinterCupsBackend() override;
2089+
2090+ virtual bool holdsDefinition() const override;
2091+
2092+ virtual QString printerAdd(const QString &name,
2093+ const QString &uri,
2094+ const QString &ppdFile,
2095+ const QString &info,
2096+ const QString &location) override;
2097+ virtual QString printerAddWithPpd(const QString &name,
2098+ const QString &uri,
2099+ const QString &ppdFileName,
2100+ const QString &info,
2101+ const QString &location) override;
2102+ virtual QString printerDelete(const QString &name) override;
2103+ virtual QString printerSetDefault(const QString &name) override;
2104+ virtual QString printerSetEnabled(const QString &name,
2105+ const bool enabled) override;
2106+ virtual QString printerSetAcceptJobs(
2107+ const QString &name,
2108+ const bool accept,
2109+ const QString &reason = QString::null) override;
2110+ virtual QString printerSetInfo(const QString &name,
2111+ const QString &info) override;
2112+ virtual QString printerAddOption(const QString &name,
2113+ const QString &option,
2114+ const QStringList &values) override;
2115+
2116+ virtual QVariant printerGetOption(const QString &name,
2117+ const QString &option) const override;
2118+ virtual QMap<QString, QVariant> printerGetOptions(
2119+ const QString &name, const QStringList &options
2120+ ) const override;
2121+ // FIXME: maybe have a PrinterDest iface that has a CupsDest impl?
2122+ virtual cups_dest_t* makeDest(const QString &name,
2123+ const PrinterJob *options) override;
2124+
2125+ virtual void cancelJob(const QString &name, const int jobId) override;
2126+ virtual int printFileToDest(const QString &filepath,
2127+ const QString &title,
2128+ const cups_dest_t *dest) override;
2129+ virtual QList<QSharedPointer<PrinterJob>> printerGetJobs() override;
2130+
2131+ virtual QString printerName() const override;
2132+ virtual QString description() const override;
2133+ virtual QString location() const override;
2134+ virtual QString makeAndModel() const override;
2135+
2136+ virtual PrinterEnum::State state() const override;
2137+ virtual QList<QPageSize> supportedPageSizes() const override;
2138+ virtual QPageSize defaultPageSize() const override;
2139+ virtual bool supportsCustomPageSizes() const override;
2140+
2141+ virtual QPageSize minimumPhysicalPageSize() const override;
2142+ virtual QPageSize maximumPhysicalPageSize() const override;
2143+ virtual QList<int> supportedResolutions() const override;
2144+ virtual PrinterEnum::DuplexMode defaultDuplexMode() const override;
2145+ virtual QList<PrinterEnum::DuplexMode> supportedDuplexModes() const override;
2146+
2147+ virtual QList<QSharedPointer<Printer>> availablePrinters() override;
2148+ virtual QStringList availablePrinterNames() override;
2149+ virtual QSharedPointer<Printer> getPrinter(const QString &printerName) override;
2150+ virtual QString defaultPrinterName() override;
2151+ virtual void requestPrinterDrivers() override;
2152+ virtual void requestPrinter(const QString &printerName) override;
2153+ virtual QMap<QString, QVariant> printerGetJobAttributes(
2154+ const QString &name, const int jobId) override;
2155+
2156+public Q_SLOTS:
2157+ virtual void refresh() override;
2158+ void createSubscription();
2159+
2160+Q_SIGNALS:
2161+ void cancelWorkers();
2162+ void printerDriversLoaded(const QList<PrinterDriver> &drivers);
2163+ void printerDriversFailedToLoad(const QString &errorMessage);
2164+ void requestPrinterDriverCancel();
2165+
2166+private:
2167+ void cancelSubscription();
2168+ void cancelPrinterDriverRequest();
2169+ QList<cups_job_t *> getCupsJobs(const QString &name = QStringLiteral());
2170+
2171+ QString getPrinterName(const QString &name) const;
2172+ QString getPrinterInstance(const QString &name) const;
2173+ cups_dest_t* getDest(const QString &name) const;
2174+ ppd_file_t* getPpd(const QString &name) const;
2175+ const QStringList m_knownQualityOptions;
2176+ IppClient *m_client;
2177+ QPrinterInfo m_info;
2178+ OrgCupsCupsdNotifierInterface *m_notifier;
2179+ int m_cupsSubscriptionId;
2180+ mutable QMap<QString, cups_dest_t*> m_dests; // Printer name, dest.
2181+ mutable QMap<QString, ppd_file_t*> m_ppds; // Printer name, ppd.
2182+ QSet<QString> m_activeRequests;
2183+
2184+private Q_SLOTS:
2185+ void onPrinterLoaded(QSharedPointer<Printer> printer);
2186+};
2187+
2188+#endif // USC_PRINTERS_CUPS_BACKEND_H
2189
2190=== added file 'modules/Ubuntu/Components/Extras/Printers/backend/backend_pdf.cpp'
2191--- modules/Ubuntu/Components/Extras/Printers/backend/backend_pdf.cpp 1970-01-01 00:00:00 +0000
2192+++ modules/Ubuntu/Components/Extras/Printers/backend/backend_pdf.cpp 2017-02-24 20:39:42 +0000
2193@@ -0,0 +1,119 @@
2194+/*
2195+ * Copyright (C) 2017 Canonical, Ltd.
2196+ *
2197+ * This program is free software; you can redistribute it and/or modify
2198+ * it under the terms of the GNU Lesser General Public License as published by
2199+ * the Free Software Foundation; version 3.
2200+ *
2201+ * This program is distributed in the hope that it will be useful,
2202+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2203+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2204+ * GNU Lesser General Public License for more details.
2205+ *
2206+ * You should have received a copy of the GNU Lesser General Public License
2207+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2208+ */
2209+
2210+#include "i18n.h"
2211+#include "backend/backend_pdf.h"
2212+
2213+PrinterPdfBackend::PrinterPdfBackend(const QString &printerName,
2214+ QObject *parent)
2215+ : PrinterBackend(printerName, parent)
2216+{
2217+ m_type = PrinterEnum::PrinterType::PdfType;
2218+}
2219+
2220+QVariant PrinterPdfBackend::printerGetOption(const QString &name,
2221+ const QString &option) const
2222+{
2223+ auto res = printerGetOptions(name, QStringList({option}));
2224+ return res[option];
2225+}
2226+
2227+QMap<QString, QVariant> PrinterPdfBackend::printerGetOptions(
2228+ const QString &name, const QStringList &options) const
2229+{
2230+ Q_UNUSED(name);
2231+
2232+ QMap<QString, QVariant> ret;
2233+
2234+ ColorModel rgb;
2235+ rgb.colorType = PrinterEnum::ColorModelType::ColorType;
2236+ rgb.name = "RGB";
2237+ rgb.text = __("Color");
2238+
2239+ PrintQuality quality;
2240+ quality.name = __("Normal");
2241+
2242+ Q_FOREACH(const QString &option, options) {
2243+ if (option == QLatin1String("DefaultColorModel")) {
2244+ ret[option] = QVariant::fromValue(rgb);
2245+ } else if (option == QLatin1String("DefaultPrintQuality")) {
2246+ ret[option] = QVariant::fromValue(quality);
2247+ } else if (option == QLatin1String("SupportedPrintQualities")) {
2248+ auto qualities = QList<PrintQuality>({quality});
2249+ ret[option] = QVariant::fromValue(qualities);
2250+ } else if (option == QLatin1String("SupportedColorModels")) {
2251+ auto models = QList<ColorModel>{rgb};
2252+ ret[option] = QVariant::fromValue(models);
2253+ } else if (option == QLatin1String("AcceptJobs")) {
2254+ ret[option] = true;
2255+ } else {
2256+ throw std::invalid_argument("Invalid value for PDF printer: " + option.toStdString());
2257+ }
2258+ }
2259+
2260+ return ret;
2261+}
2262+
2263+QString PrinterPdfBackend::printerName() const
2264+{
2265+ return m_printerName;
2266+}
2267+
2268+PrinterEnum::State PrinterPdfBackend::state() const
2269+{
2270+ return PrinterEnum::State::IdleState;
2271+}
2272+
2273+QList<QPageSize> PrinterPdfBackend::supportedPageSizes() const
2274+{
2275+ return QList<QPageSize>{QPageSize(QPageSize::A4)};
2276+}
2277+
2278+QPageSize PrinterPdfBackend::defaultPageSize() const
2279+{
2280+ return QPageSize(QPageSize::A4);
2281+}
2282+
2283+bool PrinterPdfBackend::supportsCustomPageSizes() const
2284+{
2285+ return false;
2286+}
2287+
2288+QPageSize PrinterPdfBackend::minimumPhysicalPageSize() const
2289+{
2290+ return QPageSize(QPageSize::A4);
2291+}
2292+
2293+QPageSize PrinterPdfBackend::maximumPhysicalPageSize() const
2294+{
2295+ return QPageSize(QPageSize::A4);
2296+}
2297+
2298+QList<int> PrinterPdfBackend::supportedResolutions() const
2299+{
2300+ return QList<int>{};
2301+}
2302+
2303+PrinterEnum::DuplexMode PrinterPdfBackend::defaultDuplexMode() const
2304+{
2305+ return PrinterEnum::DuplexMode::DuplexNone;
2306+}
2307+
2308+QList<PrinterEnum::DuplexMode> PrinterPdfBackend::supportedDuplexModes() const
2309+{
2310+ return QList<PrinterEnum::DuplexMode>{PrinterEnum::DuplexMode::DuplexNone};
2311+}
2312+
2313
2314=== added file 'modules/Ubuntu/Components/Extras/Printers/backend/backend_pdf.h'
2315--- modules/Ubuntu/Components/Extras/Printers/backend/backend_pdf.h 1970-01-01 00:00:00 +0000
2316+++ modules/Ubuntu/Components/Extras/Printers/backend/backend_pdf.h 2017-02-24 20:39:42 +0000
2317@@ -0,0 +1,47 @@
2318+/*
2319+ * Copyright (C) 2017 Canonical, Ltd.
2320+ *
2321+ * This program is free software; you can redistribute it and/or modify
2322+ * it under the terms of the GNU Lesser General Public License as published by
2323+ * the Free Software Foundation; version 3.
2324+ *
2325+ * This program is distributed in the hope that it will be useful,
2326+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2327+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2328+ * GNU Lesser General Public License for more details.
2329+ *
2330+ * You should have received a copy of the GNU Lesser General Public License
2331+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2332+ */
2333+
2334+#ifndef USC_PRINTERS_PDF_BACKEND_H
2335+#define USC_PRINTERS_PDF_BACKEND_H
2336+
2337+#include "backend/backend.h"
2338+
2339+class PRINTERS_DECL_EXPORT PrinterPdfBackend : public PrinterBackend
2340+{
2341+public:
2342+ explicit PrinterPdfBackend(const QString &printerName,
2343+ QObject *parent = Q_NULLPTR);
2344+ virtual QVariant printerGetOption(const QString &name,
2345+ const QString &option) const override;
2346+ virtual QMap<QString, QVariant> printerGetOptions(
2347+ const QString &name, const QStringList &options
2348+ ) const override;
2349+
2350+ virtual QString printerName() const override;
2351+
2352+ virtual PrinterEnum::State state() const override;
2353+ virtual QList<QPageSize> supportedPageSizes() const override;
2354+ virtual QPageSize defaultPageSize() const override;
2355+ virtual bool supportsCustomPageSizes() const override;
2356+
2357+ virtual QPageSize minimumPhysicalPageSize() const override;
2358+ virtual QPageSize maximumPhysicalPageSize() const override;
2359+ virtual QList<int> supportedResolutions() const override;
2360+ virtual PrinterEnum::DuplexMode defaultDuplexMode() const override;
2361+ virtual QList<PrinterEnum::DuplexMode> supportedDuplexModes() const override;
2362+};
2363+
2364+#endif // USC_PRINTERS_PDF_BACKEND_H
2365
2366=== added directory 'modules/Ubuntu/Components/Extras/Printers/cups'
2367=== added file 'modules/Ubuntu/Components/Extras/Printers/cups/ippclient.cpp'
2368--- modules/Ubuntu/Components/Extras/Printers/cups/ippclient.cpp 1970-01-01 00:00:00 +0000
2369+++ modules/Ubuntu/Components/Extras/Printers/cups/ippclient.cpp 2017-02-24 20:39:42 +0000
2370@@ -0,0 +1,988 @@
2371+/*
2372+ * Copyright (C) 2017 Canonical, Ltd.
2373+ * Copyright (C) 2014 John Layt <jlayt@kde.org>
2374+ * Copyright (C) 2002, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2013, 2014 Red Hat, Inc.
2375+ * Copyright (C) 2008 Novell, Inc.
2376+ *
2377+ * This program is free software; you can redistribute it and/or modify
2378+ * it under the terms of the GNU Lesser General Public License as published by
2379+ * the Free Software Foundation; version 3.
2380+ *
2381+ * This program is distributed in the hope that it will be useful,
2382+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2383+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2384+ * GNU Lesser General Public License for more details.
2385+ *
2386+ * You should have received a copy of the GNU Lesser General Public License
2387+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2388+ */
2389+
2390+#include "cups/ippclient.h"
2391+
2392+#include <errno.h>
2393+#include <string.h>
2394+#include <unistd.h>
2395+
2396+#include <QDebug>
2397+#include <QDateTime>
2398+#include <QTimeZone>
2399+#include <QUrl>
2400+
2401+IppClient::IppClient()
2402+ : m_connection(httpConnectEncrypt(cupsServer(),
2403+ ippPort(),
2404+ cupsEncryption()))
2405+{
2406+ if (!m_connection) {
2407+ qCritical("Failed to connect to cupsd");
2408+ } else {
2409+ qDebug("Successfully connected to cupsd.");
2410+ }
2411+}
2412+
2413+IppClient::~IppClient()
2414+{
2415+ if (m_connection)
2416+ httpClose(m_connection);
2417+}
2418+
2419+bool IppClient::printerDelete(const QString &printerName)
2420+{
2421+ return sendNewSimpleRequest(CUPS_DELETE_PRINTER, printerName.toUtf8(),
2422+ CupsResource::CupsResourceAdmin);
2423+}
2424+
2425+bool IppClient::printerAdd(const QString &printerName,
2426+ const QString &printerUri,
2427+ const QString &ppdFile,
2428+ const QString &info,
2429+ const QString &location)
2430+{
2431+ ipp_t *request;
2432+
2433+ if (!isPrinterNameValid(printerName)) {
2434+ setInternalStatus(QString("%1 is not a valid printer name.").arg(printerName));
2435+ return false;
2436+ }
2437+
2438+ if (!isStringValid(info)) {
2439+ setInternalStatus(QString("%1 is not a valid description.").arg(info));
2440+ return false;
2441+ }
2442+
2443+ if (!isStringValid(location)) {
2444+ setInternalStatus(QString("%1 is not a valid location.").arg(location));
2445+ return false;
2446+ }
2447+
2448+ if (!isStringValid(ppdFile)) {
2449+ setInternalStatus(QString("%1 is not a valid ppd file.").arg(ppdFile));
2450+ return false;
2451+ }
2452+
2453+ if (!isStringValid(printerUri)) {
2454+ setInternalStatus(QString("%1 is not a valid printer uri.").arg(printerUri));
2455+ return false;
2456+ }
2457+
2458+
2459+ request = ippNewRequest (CUPS_ADD_MODIFY_PRINTER);
2460+ addPrinterUri(request, printerName);
2461+ addRequestingUsername(request, NULL);
2462+
2463+ ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_NAME,
2464+ "printer-name", NULL, printerName.toUtf8());
2465+
2466+ if (!ppdFile.isEmpty()) {
2467+ ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_NAME,
2468+ "ppd-name", NULL, ppdFile.toUtf8());
2469+ }
2470+ if (!printerUri.isEmpty()) {
2471+ ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_URI,
2472+ "device-uri", NULL, printerUri.toUtf8());
2473+ }
2474+ if (!info.isEmpty()) {
2475+ ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_TEXT,
2476+ "printer-info", NULL, info.toUtf8());
2477+ }
2478+ if (!location.isEmpty()) {
2479+ ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_TEXT,
2480+ "printer-location", NULL, location.toUtf8());
2481+ }
2482+
2483+ return sendRequest(request, CupsResourceAdmin);
2484+}
2485+
2486+bool IppClient::printerAddWithPpdFile(const QString &printerName,
2487+ const QString &printerUri,
2488+ const QString &ppdFileName,
2489+ const QString &info,
2490+ const QString &location)
2491+{
2492+ ipp_t *request;
2493+
2494+ if (!isPrinterNameValid(printerName)) {
2495+ setInternalStatus(QString("%1 is not a valid printer name.").arg(printerName));
2496+ return false;
2497+ }
2498+
2499+ if (!isStringValid(info)) {
2500+ setInternalStatus(QString("%1 is not a valid description.").arg(info));
2501+ return false;
2502+ }
2503+
2504+ if (!isStringValid(location)) {
2505+ setInternalStatus(QString("%1 is not a valid location.").arg(location));
2506+ return false;
2507+ }
2508+
2509+ if (!isStringValid(ppdFileName)) {
2510+ setInternalStatus(QString("%1 is not a valid ppd file name.").arg(ppdFileName));
2511+ return false;
2512+ }
2513+
2514+ if (!isStringValid(printerUri)) {
2515+ setInternalStatus(QString("%1 is not a valid printer uri.").arg(printerUri));
2516+ return false;
2517+ }
2518+
2519+ request = ippNewRequest(CUPS_ADD_MODIFY_PRINTER);
2520+ addPrinterUri(request, printerName);
2521+ addRequestingUsername(request, NULL);
2522+
2523+ ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_NAME,
2524+ "printer-name", NULL, printerName.toUtf8());
2525+
2526+ /* In this specific case of ADD_MODIFY, the URI can be NULL/empty since
2527+ * we provide a complete PPD. And cups fails if we pass an empty
2528+ * string. */
2529+ if (!printerUri.isEmpty()) {
2530+ ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_URI,
2531+ "device-uri", NULL, printerUri.toUtf8());
2532+ }
2533+
2534+ if (!info.isEmpty()) {
2535+ ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_TEXT,
2536+ "printer-info", NULL, info.toUtf8());
2537+ }
2538+ if (!location.isEmpty()) {
2539+ ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_TEXT,
2540+ "printer-location", NULL, location.toUtf8());
2541+ }
2542+
2543+ return postRequest(request, ppdFileName.toUtf8(), CupsResourceAdmin);
2544+}
2545+
2546+bool IppClient::printerSetDefault(const QString &printerName)
2547+{
2548+ return sendNewSimpleRequest(CUPS_SET_DEFAULT, printerName.toUtf8(),
2549+ CupsResource::CupsResourceAdmin);
2550+}
2551+
2552+bool IppClient::printerSetEnabled(const QString &printerName,
2553+ const bool enabled)
2554+{
2555+ ipp_op_t op;
2556+ op = enabled ? IPP_RESUME_PRINTER : IPP_PAUSE_PRINTER;
2557+ return sendNewSimpleRequest(op, printerName, CupsResourceAdmin);
2558+}
2559+
2560+/* reason must be empty if accept is true */
2561+bool IppClient::printerSetAcceptJobs(const QString &printerName,
2562+ const bool accept,
2563+ const QString &reason)
2564+{
2565+ ipp_t *request;
2566+
2567+ if (accept && !reason.isEmpty()) {
2568+ setInternalStatus("Accepting jobs does not take a reason.");
2569+ return false;
2570+ }
2571+
2572+ if (!isPrinterNameValid(printerName)) {
2573+ setInternalStatus(QString("%1 is not a valid printer name.").arg(printerName));
2574+ return false;
2575+ }
2576+
2577+ if (!isStringValid(reason)) {
2578+ setInternalStatus(QString("%1 is not a valid reason.").arg(reason));
2579+ return false;
2580+ }
2581+
2582+ if (accept) {
2583+ return sendNewSimpleRequest(CUPS_ACCEPT_JOBS, printerName.toUtf8(),
2584+ CupsResourceAdmin);
2585+ } else {
2586+ request = ippNewRequest(CUPS_REJECT_JOBS);
2587+ addPrinterUri(request, printerName);
2588+ addRequestingUsername(request, NULL);
2589+
2590+ if (!reason.isEmpty())
2591+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_TEXT,
2592+ "printer-state-message", NULL, reason.toUtf8());
2593+
2594+ return sendRequest(request, CupsResourceAdmin);
2595+ }
2596+}
2597+
2598+
2599+bool IppClient::printerClassSetInfo(const QString &name,
2600+ const QString &info)
2601+{
2602+ if (!isPrinterNameValid(name)) {
2603+ setInternalStatus(QString("%1 is not a valid printer name.").arg(name));
2604+ return false;
2605+ }
2606+
2607+ if (!isStringValid(info)) {
2608+ setInternalStatus(QString("%1 is not a valid description.").arg(info));
2609+ return false;
2610+ }
2611+
2612+ return sendNewPrinterClassRequest(name, IPP_TAG_PRINTER, IPP_TAG_TEXT,
2613+ "printer-info", info);
2614+}
2615+
2616+bool IppClient::printerClassSetOption(const QString &name,
2617+ const QString &option,
2618+ const QStringList &values)
2619+{
2620+ bool isClass;
2621+ int length = 0;
2622+ ipp_t *request;
2623+ ipp_attribute_t *attr;
2624+ QString newPpdFile;
2625+ bool retval;
2626+
2627+ if (!isPrinterNameValid(name)) {
2628+ setInternalStatus(QString("%1 is not a valid printer name.").arg(name));
2629+ return false;
2630+ }
2631+
2632+ if (!isStringValid(option)) {
2633+ setInternalStatus(QString("%1 is not a valid option.").arg(option));
2634+ return false;
2635+ }
2636+
2637+ Q_FOREACH(const QString &val, values) {
2638+ if (!isStringValid(val)) {
2639+ setInternalStatus(QString("%1 is not a valid value.").arg(val));
2640+ return false;
2641+ }
2642+ length++;
2643+ }
2644+
2645+ if (length == 0) {
2646+ setInternalStatus("No valid values.");
2647+ return false;
2648+ }
2649+
2650+ isClass = printerIsClass(name);
2651+
2652+ /* We permit only one value to change in PPD file because we are setting
2653+ * default value in it. */
2654+ if (!isClass && length == 1) {
2655+ cups_option_t *options = NULL;
2656+ int numOptions = 0;
2657+ QString ppdfile;
2658+
2659+ numOptions = cupsAddOption(option.toUtf8(),
2660+ values[0].toUtf8(),
2661+ numOptions, &options);
2662+
2663+ ppdfile = QString(cupsGetPPD(name.toUtf8()));
2664+
2665+ newPpdFile = preparePpdForOptions(ppdfile.toUtf8(),
2666+ options, numOptions).toLatin1().data();
2667+
2668+ unlink(ppdfile.toUtf8());
2669+ cupsFreeOptions(numOptions, options);
2670+ }
2671+
2672+ if (isClass) {
2673+ request = ippNewRequest(CUPS_ADD_MODIFY_CLASS);
2674+ addClassUri(request, name);
2675+ } else {
2676+ request = ippNewRequest(CUPS_ADD_MODIFY_PRINTER);
2677+ addPrinterUri(request, name);
2678+ }
2679+
2680+ addRequestingUsername(request, NULL);
2681+
2682+ if (length == 1) {
2683+ ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_NAME,
2684+ option.toUtf8(),
2685+ NULL,
2686+ values[0].toUtf8());
2687+ } else {
2688+ int i;
2689+
2690+ attr = ippAddStrings(request, IPP_TAG_PRINTER, IPP_TAG_NAME,
2691+ option.toUtf8(), length, NULL, NULL);
2692+
2693+ for (i = 0; i < length; i++)
2694+ ippSetString(request, &attr, i, values[i].toUtf8());
2695+ }
2696+
2697+ if (!newPpdFile.isEmpty()) {
2698+ retval = postRequest(request, newPpdFile, CupsResourceAdmin);
2699+
2700+ unlink(newPpdFile.toUtf8());
2701+ // TODO: fix leak here.
2702+ } else {
2703+ retval = sendRequest(request, CupsResourceAdmin);
2704+ }
2705+
2706+ return retval;
2707+}
2708+
2709+QMap<QString, QVariant> IppClient::printerGetJobAttributes(const int jobId)
2710+{
2711+ ipp_t *request;
2712+ QMap<QString, QVariant> map;
2713+
2714+ // Construct request
2715+ request = ippNewRequest(IPP_GET_JOB_ATTRIBUTES);
2716+ QString uri = QStringLiteral("ipp://localhost/jobs/") + QString::number(jobId);
2717+ qDebug() << "URI:" << uri;
2718+
2719+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri", NULL, uri.toStdString().data());
2720+
2721+
2722+ // Send request and construct reply
2723+ ipp_t *reply;
2724+ const QString resourceChar = getResource(CupsResourceRoot);
2725+ reply = cupsDoRequest(m_connection, request,
2726+ resourceChar.toUtf8());
2727+
2728+ // Check if the reply is OK
2729+ if (isReplyOk(reply, false)) {
2730+ // Loop through the attributes
2731+ ipp_attribute_t *attr;
2732+
2733+ for (attr = ippFirstAttribute(reply); attr; attr = ippNextAttribute(reply)) {
2734+ QVariant value = getAttributeValue(attr);
2735+ map.insert(ippGetName(attr), value);
2736+ }
2737+ } else {
2738+ qWarning() << "Not able to get attributes of job:" << jobId;
2739+ }
2740+
2741+ // Destruct the reply if valid
2742+ if (reply) {
2743+ ippDelete(reply);
2744+ }
2745+
2746+ return map;
2747+}
2748+
2749+
2750+/* This function sets given options to specified values in file 'ppdfile'.
2751+ * This needs to be done because of applications which use content of PPD files
2752+ * instead of IPP attributes.
2753+ * CUPS doesn't do this automatically (but hopefully will starting with 1.6) */
2754+QString IppClient::preparePpdForOptions(const QString &ppdfile,
2755+ cups_option_t *options, int numOptions)
2756+{
2757+ auto ppdfile_c = ppdfile.toUtf8();
2758+ ppd_file_t *ppd;
2759+ bool ppdchanged = false;
2760+ QString result;
2761+ QString error;
2762+ char newppdfile[PATH_MAX];
2763+ cups_file_t *in = NULL;
2764+ cups_file_t *out = NULL;
2765+ char line[CPH_STR_MAXLEN];
2766+ char keyword[CPH_STR_MAXLEN];
2767+ char *keyptr;
2768+ ppd_choice_t *choice;
2769+ QString value;
2770+ QLatin1String defaultStr("*Default");
2771+
2772+ ppd = ppdOpenFile(ppdfile_c);
2773+ if (!ppd) {
2774+ error = QString("Unable to open PPD file \"%1\": %2")
2775+ .arg(ppdfile).arg(strerror(errno));
2776+ setInternalStatus(error);
2777+ goto out;
2778+ }
2779+
2780+ in = cupsFileOpen(ppdfile_c, "r");
2781+ if (!in) {
2782+ error = QString("Unable to open PPD file \"%1\": %2")
2783+ .arg(ppdfile).arg(strerror(errno));
2784+ setInternalStatus(error);
2785+ goto out;
2786+ }
2787+
2788+ out = cupsTempFile2(newppdfile, sizeof(newppdfile));
2789+ if (!out) {
2790+ setInternalStatus("Unable to create temporary file");
2791+ goto out;
2792+ }
2793+
2794+ /* Mark default values and values of options we are changing. */
2795+ ppdMarkDefaults(ppd);
2796+ cupsMarkOptions(ppd, numOptions, options);
2797+
2798+ while (cupsFileGets(in, line, sizeof(line))) {
2799+ QString line_qs(line);
2800+ if (!line_qs.startsWith(defaultStr)) {
2801+ cupsFilePrintf(out, "%s\n", line);
2802+ } else {
2803+ /* This part parses lines with *Default on their
2804+ * beginning. For instance:
2805+ * "*DefaultResolution: 1200dpi" becomes:
2806+ * - keyword: Resolution
2807+ * - keyptr: 1200dpi
2808+ */
2809+ strncpy(keyword, line + defaultStr.size(), sizeof(keyword));
2810+
2811+ for (keyptr = keyword; *keyptr; keyptr++)
2812+ if (*keyptr == ':' || isspace (*keyptr & 255))
2813+ break;
2814+
2815+ *keyptr++ = '\0';
2816+ while (isspace (*keyptr & 255))
2817+ keyptr++;
2818+
2819+ QString keyword_sq(keyword);
2820+ QString keyptr_qs(keyptr);
2821+
2822+ /* We have to change PageSize if any of PageRegion,
2823+ * PageSize, PaperDimension or ImageableArea changes.
2824+ * We change PageRegion if PageSize is not available. */
2825+ if (keyword_sq == "PageRegion" ||
2826+ keyword_sq == "PageSize" ||
2827+ keyword_sq == "PaperDimension" ||
2828+ keyword_sq == "ImageableArea") {
2829+
2830+ choice = ppdFindMarkedChoice(ppd, "PageSize");
2831+ if (!choice)
2832+ choice = ppdFindMarkedChoice(ppd, "PageRegion");
2833+ } else {
2834+ choice = ppdFindMarkedChoice(ppd, keyword);
2835+ }
2836+
2837+
2838+ QString choice_qs;
2839+ if (choice) {
2840+ choice_qs = choice->choice;
2841+ }
2842+
2843+ if (choice && choice_qs != keyptr_qs) {
2844+ /* We have to set the value in PPD manually if
2845+ * a custom value was passed in:
2846+ * cupsMarkOptions() marks the choice as
2847+ * "Custom". We want to set this value with our
2848+ * input. */
2849+ if (choice_qs != "Custom") {
2850+ cupsFilePrintf(out,
2851+ "*Default%s: %s\n",
2852+ keyword,
2853+ choice->choice);
2854+ ppdchanged = true;
2855+ } else {
2856+ value = cupsGetOption(keyword, numOptions, options);
2857+ if (!value.isEmpty()) {
2858+ cupsFilePrintf(out,
2859+ "*Default%s: %s\n",
2860+ keyword,
2861+ value.toStdString().c_str());
2862+ ppdchanged = true;
2863+ } else {
2864+ cupsFilePrintf(out, "%s\n", line);
2865+ }
2866+ }
2867+ } else {
2868+ cupsFilePrintf(out, "%s\n", line);
2869+ }
2870+ }
2871+ }
2872+
2873+ if (ppdchanged)
2874+ result = QString::fromUtf8(newppdfile);
2875+ else
2876+ unlink(newppdfile);
2877+
2878+out:
2879+ if (in)
2880+ cupsFileClose(in);
2881+ if (out)
2882+ cupsFileClose(out);
2883+ if (ppd)
2884+ ppdClose(ppd);
2885+
2886+ return result;
2887+}
2888+
2889+
2890+bool IppClient::sendNewPrinterClassRequest(const QString &printerName,
2891+ ipp_tag_t group, ipp_tag_t type,
2892+ const QString &name,
2893+ const QString &value)
2894+{
2895+ ipp_t *request;
2896+
2897+ request = ippNewRequest(CUPS_ADD_MODIFY_PRINTER);
2898+ addPrinterUri(request, printerName);
2899+ addRequestingUsername(request, QString());
2900+ ippAddString(request, group, type, name.toUtf8(), NULL,
2901+ value.toUtf8());
2902+
2903+ if (sendRequest(request, CupsResource::CupsResourceAdmin))
2904+ return true;
2905+
2906+ // it failed, maybe it was a class?
2907+ if (m_lastStatus != IPP_NOT_POSSIBLE) {
2908+ return false;
2909+ }
2910+
2911+ // TODO: implement class modification <here>.
2912+ return false;
2913+}
2914+
2915+void IppClient::addPrinterUri(ipp_t *request, const QString &name)
2916+{
2917+ QUrl uri(QString("ipp://localhost/printers/%1").arg(name));
2918+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI,
2919+ "printer-uri", NULL, uri.toEncoded().data());
2920+}
2921+
2922+void IppClient::addRequestingUsername(ipp_t *request, const QString &username)
2923+{
2924+ if (!username.isEmpty())
2925+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
2926+ "requesting-user-name", NULL,
2927+ username.toUtf8());
2928+ else
2929+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
2930+ "requesting-user-name", NULL, cupsUser());
2931+}
2932+
2933+QString IppClient::getLastError() const
2934+{
2935+ return m_internalStatus;
2936+}
2937+
2938+const QString IppClient::getResource(const IppClient::CupsResource &resource)
2939+{
2940+ switch (resource) {
2941+ case CupsResourceRoot:
2942+ return "/";
2943+ case CupsResourceAdmin:
2944+ return "/admin/";
2945+ case CupsResourceJobs:
2946+ return "/jobs/";
2947+ default:
2948+ qCritical("Asking for a resource with no match.");
2949+ return "/";
2950+ }
2951+}
2952+
2953+bool IppClient::isPrinterNameValid(const QString &name)
2954+{
2955+ int i;
2956+ int len;
2957+
2958+ /* Quoting the lpadmin man page:
2959+ * CUPS allows printer names to contain any printable character
2960+ * except SPACE, TAB, "/", or "#".
2961+ * On top of that, validate_name() in lpadmin.c (from cups) checks that
2962+ * the string is 127 characters long, or shorter. */
2963+
2964+ /* no empty string */
2965+ if (name.isEmpty())
2966+ return false;
2967+
2968+ len = name.size();
2969+ /* no string that is too long; see comment at the beginning of the
2970+ * validation code block */
2971+ if (len > 127)
2972+ return false;
2973+
2974+ /* only printable characters, no space, no /, no # */
2975+ for (i = 0; i < len; i++) {
2976+ const QChar c = name.at(i);
2977+ if (!c.isPrint())
2978+ return false;
2979+ if (c.isSpace())
2980+ return false;
2981+ if (c == '/' || c == '#')
2982+ return false;
2983+ }
2984+ return true;
2985+}
2986+
2987+bool IppClient::isStringValid(const QString &string, const bool checkNull,
2988+ const int maxLength)
2989+{
2990+ if (isStringPrintable(string, checkNull, maxLength))
2991+ return true;
2992+ return false;
2993+}
2994+
2995+bool IppClient::isStringPrintable(const QString &string, const bool checkNull,
2996+ const int maxLength)
2997+{
2998+ int i;
2999+ int len;
3000+
3001+ /* no null string */
3002+ if (string.isNull())
3003+ return !checkNull;
3004+
3005+ len = string.size();
3006+ if (maxLength > 0 && len > maxLength)
3007+ return false;
3008+
3009+ /* only printable characters */
3010+ for (i = 0; i < len; i++) {
3011+ const QChar c = string.at(i);
3012+ if (!c.isPrint())
3013+ return false;
3014+ }
3015+ return true;
3016+}
3017+
3018+void IppClient::setInternalStatus(const QString &status)
3019+{
3020+ if (!m_internalStatus.isNull()) {
3021+ m_internalStatus = QString::null;
3022+ }
3023+
3024+ if (status.isNull()) {
3025+ m_internalStatus = QString::null;
3026+ } else {
3027+ m_internalStatus = status;
3028+
3029+ // Only used for errors for now.
3030+ qCritical() << status;
3031+ }
3032+}
3033+
3034+bool IppClient::postRequest(ipp_t *request, const QString &file,
3035+ const CupsResource &resource)
3036+{
3037+ ipp_t *reply;
3038+ QString resourceChar;
3039+
3040+ resourceChar = getResource(resource);
3041+
3042+ if (!file.isEmpty())
3043+ reply = cupsDoFileRequest(m_connection, request, resourceChar.toUtf8(),
3044+ file.toUtf8());
3045+ else
3046+ reply = cupsDoFileRequest(m_connection, request, resourceChar.toUtf8(),
3047+ NULL);
3048+
3049+ return handleReply(reply);
3050+}
3051+
3052+
3053+bool IppClient::sendRequest(ipp_t *request, const CupsResource &resource)
3054+{
3055+ ipp_t *reply;
3056+ const QString resourceChar = getResource(resource);
3057+ reply = cupsDoRequest(m_connection, request,
3058+ resourceChar.toUtf8());
3059+ return handleReply(reply);
3060+}
3061+
3062+bool IppClient::sendNewSimpleRequest(ipp_op_t op, const QString &printerName,
3063+ const IppClient::CupsResource &resource)
3064+{
3065+ ipp_t *request;
3066+
3067+ if (!isPrinterNameValid(printerName))
3068+ return false;
3069+
3070+ request = ippNewRequest(op);
3071+ addPrinterUri(request, printerName);
3072+ addRequestingUsername(request, NULL);
3073+
3074+ return sendRequest(request, resource);
3075+}
3076+
3077+bool IppClient::handleReply(ipp_t *reply)
3078+{
3079+ bool retval;
3080+ retval = isReplyOk(reply, false);
3081+ if (reply)
3082+ ippDelete(reply);
3083+
3084+ return retval;
3085+}
3086+
3087+bool IppClient::isReplyOk(ipp_t *reply, bool deleteIfReplyNotOk)
3088+{
3089+ /* reset the internal status: we'll use the cups status */
3090+ m_lastStatus = IPP_STATUS_CUPS_INVALID;
3091+
3092+ if (reply && ippGetStatusCode(reply) <= IPP_OK_CONFLICT) {
3093+ m_lastStatus = IPP_OK;
3094+ return true;
3095+ } else {
3096+ setErrorFromReply(reply);
3097+ qWarning() << Q_FUNC_INFO << "Cups HTTP error:" << cupsLastErrorString();
3098+
3099+ if (deleteIfReplyNotOk && reply)
3100+ ippDelete(reply);
3101+
3102+ return false;
3103+ }
3104+}
3105+
3106+void IppClient::setErrorFromReply(ipp_t *reply)
3107+{
3108+ if (reply)
3109+ m_lastStatus = ippGetStatusCode(reply);
3110+ else
3111+ m_lastStatus = cupsLastError();
3112+}
3113+
3114+bool IppClient::printerIsClass(const QString &name)
3115+{
3116+ const char * const attrs[1] = { "member-names" };
3117+ ipp_t *request;
3118+ QString resource;
3119+ ipp_t *reply;
3120+ bool retval;
3121+
3122+ // Class/Printer name validation is equal.
3123+ if (!isPrinterNameValid(name)) {
3124+ setInternalStatus(QString("%1 is not a valid printer name.").arg(name));
3125+ return false;
3126+ }
3127+
3128+ request = ippNewRequest(IPP_GET_PRINTER_ATTRIBUTES);
3129+ addClassUri(request, name);
3130+ addRequestingUsername(request, QString());
3131+ ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
3132+ "requested-attributes", 1, NULL, attrs);
3133+
3134+ resource = getResource(CupsResource::CupsResourceRoot);
3135+ reply = cupsDoRequest(m_connection, request, resource.toUtf8());
3136+
3137+ if (!isReplyOk(reply, true))
3138+ return true;
3139+
3140+ /* Note: we need to look if the attribute is there, since we get a
3141+ * reply if the name is a printer name and not a class name. The
3142+ * attribute is the only way to distinguish the two cases. */
3143+ retval = ippFindAttribute(reply, attrs[0], IPP_TAG_NAME) != NULL;
3144+
3145+ if (reply)
3146+ ippDelete(reply);
3147+
3148+ return retval;
3149+}
3150+
3151+void IppClient::addClassUri(ipp_t *request, const QString &name)
3152+{
3153+ QUrl uri(QString("ipp://localhost/printers/%1").arg(name));
3154+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI,
3155+ "printer-uri", NULL, uri.toEncoded().data());
3156+}
3157+
3158+ppd_file_t* IppClient::getPpdFile(const QString &name,
3159+ const QString &instance) const
3160+{
3161+ Q_UNUSED(instance);
3162+
3163+ ppd_file_t* file = 0;
3164+ const char *ppdFile = cupsGetPPD(name.toUtf8());
3165+ if (ppdFile) {
3166+ file = ppdOpenFile(ppdFile);
3167+ unlink(ppdFile);
3168+ }
3169+ if (file) {
3170+ ppdMarkDefaults(file);
3171+ } else {
3172+ file = 0;
3173+ }
3174+
3175+ return file;
3176+}
3177+
3178+cups_dest_t* IppClient::getDest(const QString &name,
3179+ const QString &instance) const
3180+{
3181+ cups_dest_t *dest = 0;
3182+
3183+ if (instance.isEmpty()) {
3184+ dest = cupsGetNamedDest(m_connection, name.toUtf8(), NULL);
3185+ } else {
3186+ dest = cupsGetNamedDest(m_connection, name.toUtf8(), instance.toUtf8());
3187+ }
3188+ return dest;
3189+}
3190+
3191+ipp_t* IppClient::createPrinterDriversRequest(
3192+ const QString &deviceId, const QString &language, const QString &makeModel,
3193+ const QString &product, const QStringList &includeSchemes,
3194+ const QStringList &excludeSchemes
3195+)
3196+{
3197+ Q_UNUSED(includeSchemes);
3198+ Q_UNUSED(excludeSchemes);
3199+
3200+ ipp_t *request;
3201+
3202+ request = ippNewRequest(CUPS_GET_PPDS);
3203+
3204+ if (!deviceId.isEmpty())
3205+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_TEXT, "ppd-device-id",
3206+ NULL, deviceId.toUtf8());
3207+ if (!language.isEmpty())
3208+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE, "ppd-language",
3209+ NULL, language.toUtf8());
3210+ if (!makeModel.isEmpty())
3211+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_TEXT, "ppd-make-and-model",
3212+ NULL, makeModel.toUtf8());
3213+ if (!product.isEmpty())
3214+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_TEXT, "ppd-product",
3215+ NULL, product.toUtf8());
3216+
3217+ // Do the request and get return the response.
3218+ const QString resourceChar = getResource(CupsResourceRoot);
3219+ return cupsDoRequest(m_connection, request,
3220+ resourceChar.toUtf8());
3221+}
3222+
3223+int IppClient::createSubscription()
3224+{
3225+ ipp_t *req;
3226+ ipp_t *resp;
3227+ ipp_attribute_t *attr;
3228+ int subscriptionId = -1;
3229+
3230+ req = ippNewRequest(IPP_CREATE_PRINTER_SUBSCRIPTION);
3231+ ippAddString(req, IPP_TAG_OPERATION, IPP_TAG_URI,
3232+ "printer-uri", NULL, "/");
3233+ ippAddString(req, IPP_TAG_SUBSCRIPTION, IPP_TAG_KEYWORD,
3234+ "notify-events", NULL, "all");
3235+ ippAddString(req, IPP_TAG_SUBSCRIPTION, IPP_TAG_URI,
3236+ "notify-recipient-uri", NULL, "dbus://");
3237+ ippAddInteger(req, IPP_TAG_SUBSCRIPTION, IPP_TAG_INTEGER,
3238+ "notify-lease-duration", 0);
3239+
3240+ resp = cupsDoRequest(m_connection, req,
3241+ getResource(CupsResourceRoot).toUtf8());
3242+ if (!isReplyOk(resp, true)) {
3243+ return subscriptionId;
3244+ }
3245+
3246+ attr = ippFindAttribute(resp, "notify-subscription-id", IPP_TAG_INTEGER);
3247+
3248+ if (!attr) {
3249+ qWarning() << "ipp-create-printer-subscription response doesn't"
3250+ " contain subscription id.";
3251+ } else {
3252+ subscriptionId = ippGetInteger(attr, 0);
3253+ }
3254+
3255+ ippDelete (resp);
3256+
3257+ return subscriptionId;
3258+}
3259+
3260+void IppClient::cancelSubscription(const int &subscriptionId)
3261+{
3262+ ipp_t *req;
3263+ ipp_t *resp;
3264+
3265+ if (subscriptionId <= 0) {
3266+ return;
3267+ }
3268+
3269+ req = ippNewRequest(IPP_CANCEL_SUBSCRIPTION);
3270+ ippAddString(req, IPP_TAG_OPERATION, IPP_TAG_URI,
3271+ "printer-uri", NULL, "/");
3272+ ippAddInteger(req, IPP_TAG_OPERATION, IPP_TAG_INTEGER,
3273+ "notify-subscription-id", subscriptionId);
3274+
3275+ resp = cupsDoRequest(m_connection, req,
3276+ getResource(CupsResourceRoot).toUtf8());
3277+ if (!isReplyOk(resp, true)) {
3278+ return;
3279+ }
3280+
3281+ ippDelete(resp);
3282+}
3283+
3284+QVariant IppClient::getAttributeValue(ipp_attribute_t *attr, int index) const
3285+{
3286+ QVariant var;
3287+
3288+ if (ippGetCount(attr) > 1 && index < 0) {
3289+ QList<QVariant> list;
3290+
3291+ for (int i=0; i < ippGetCount(attr); i++) {
3292+ list.append(getAttributeValue(attr, i));
3293+ }
3294+
3295+ var = QVariant::fromValue<QList<QVariant>>(list);
3296+ } else {
3297+ if (index == -1) {
3298+ index = 0;
3299+ }
3300+
3301+ switch (ippGetValueTag(attr)) {
3302+ case IPP_TAG_NAME:
3303+ case IPP_TAG_TEXT:
3304+ case IPP_TAG_KEYWORD:
3305+ case IPP_TAG_URI:
3306+ case IPP_TAG_CHARSET:
3307+ case IPP_TAG_MIMETYPE:
3308+ case IPP_TAG_LANGUAGE:
3309+ var = QVariant::fromValue<QString>(ippGetString(attr, index, NULL));
3310+ break;
3311+ case IPP_TAG_INTEGER:
3312+ case IPP_TAG_ENUM:
3313+ var = QVariant::fromValue<int>(ippGetInteger(attr, index));
3314+ break;
3315+ case IPP_TAG_BOOLEAN:
3316+ var = QVariant::fromValue<bool>(ippGetBoolean(attr, index));
3317+ break;
3318+ case IPP_TAG_RANGE: {
3319+ QString range;
3320+ int upper;
3321+ int lower = ippGetRange(attr, index, &upper);
3322+
3323+ // Build a string similar to "1-3" "5-" "8" "-4"
3324+ if (lower != INT_MIN) {
3325+ range += QString::number(lower);
3326+ }
3327+
3328+ if (lower != upper) {
3329+ range += QStringLiteral("-");
3330+
3331+ if (upper != INT_MAX) {
3332+ range += QString::number(upper);
3333+ }
3334+ }
3335+
3336+ var = QVariant(range);
3337+ break;
3338+ }
3339+ case IPP_TAG_NOVALUE:
3340+ var = QVariant();
3341+ break;
3342+ case IPP_TAG_DATE: {
3343+ time_t time = ippDateToTime(ippGetDate(attr, index));
3344+ QDateTime datetime;
3345+ datetime.setTimeZone(QTimeZone::systemTimeZone());
3346+ datetime.setTime_t(time);
3347+
3348+ var = QVariant::fromValue<QDateTime>(datetime);
3349+ break;
3350+ }
3351+ default:
3352+ qWarning() << "Unknown IPP value tab 0x" << ippGetValueTag(attr);
3353+ break;
3354+ }
3355+ }
3356+
3357+ return var;
3358+}
3359
3360=== added file 'modules/Ubuntu/Components/Extras/Printers/cups/ippclient.h'
3361--- modules/Ubuntu/Components/Extras/Printers/cups/ippclient.h 1970-01-01 00:00:00 +0000
3362+++ modules/Ubuntu/Components/Extras/Printers/cups/ippclient.h 2017-02-24 20:39:42 +0000
3363@@ -0,0 +1,121 @@
3364+/*
3365+ * Copyright (C) 2017 Canonical, Ltd.
3366+ *
3367+ * This program is free software; you can redistribute it and/or modify
3368+ * it under the terms of the GNU Lesser General Public License as published by
3369+ * the Free Software Foundation; version 3.
3370+ *
3371+ * This program is distributed in the hope that it will be useful,
3372+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3373+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3374+ * GNU Lesser General Public License for more details.
3375+ *
3376+ * You should have received a copy of the GNU Lesser General Public License
3377+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3378+ */
3379+
3380+#ifndef USC_PRINTERS_CUPS_IPPCLIENT_H
3381+#define USC_PRINTERS_CUPS_IPPCLIENT_H
3382+
3383+#include "structs.h"
3384+
3385+#include <cups/cups.h>
3386+#include <cups/http.h>
3387+#include <cups/ipp.h>
3388+#include <cups/ppd.h>
3389+
3390+#include <QString>
3391+#include <QStringList>
3392+
3393+/* From https://bugzilla.novell.com/show_bug.cgi?id=447444#c5
3394+ * We need to define a maximum length for strings to avoid cups
3395+ * thinking there are multiple lines.
3396+ */
3397+#define CPH_STR_MAXLEN 512
3398+
3399+class IppClient
3400+{
3401+public:
3402+ explicit IppClient();
3403+ ~IppClient();
3404+
3405+ bool printerDelete(const QString &printerName);
3406+ bool printerAdd(const QString &printerName,
3407+ const QString &printerUri,
3408+ const QString &ppdFile,
3409+ const QString &info,
3410+ const QString &location);
3411+
3412+ bool printerAddWithPpdFile(const QString &printerName,
3413+ const QString &printerUri,
3414+ const QString &ppdFileName,
3415+ const QString &info,
3416+ const QString &location);
3417+ bool printerSetDefault(const QString &printerName);
3418+ bool printerSetEnabled(const QString &printerName, const bool enabled);
3419+ bool printerSetAcceptJobs(const QString &printerName, const bool accept,
3420+ const QString &reason);
3421+ bool printerClassSetInfo(const QString &name, const QString &info);
3422+ bool printerClassSetOption(const QString &name, const QString &option,
3423+ const QStringList &values);
3424+ ppd_file_t* getPpdFile(const QString &name, const QString &instance) const;
3425+ cups_dest_t* getDest(const QString &name, const QString &instance) const;
3426+
3427+ QMap<QString, QVariant> printerGetJobAttributes(const int jobId);
3428+
3429+ QString getLastError() const;
3430+
3431+ // Note: This response needs to be free by the caller.
3432+ ipp_t* createPrinterDriversRequest(
3433+ const QString &deviceId, const QString &language,
3434+ const QString &makeModel, const QString &product,
3435+ const QStringList &includeSchemes, const QStringList &excludeSchemes
3436+ );
3437+ int createSubscription();
3438+ void cancelSubscription(const int &subscriptionId);
3439+
3440+private:
3441+ enum CupsResource
3442+ {
3443+ CupsResourceRoot = 0,
3444+ CupsResourceAdmin,
3445+ CupsResourceJobs,
3446+ };
3447+
3448+ bool sendNewPrinterClassRequest(const QString &printerName,
3449+ ipp_tag_t group,
3450+ ipp_tag_t type,
3451+ const QString &name,
3452+ const QString &value);
3453+ static void addPrinterUri(ipp_t *request, const QString &name);
3454+ static void addRequestingUsername(ipp_t *request, const QString &username);
3455+ static const QString getResource(const CupsResource &resource);
3456+ static bool isPrinterNameValid(const QString &name);
3457+ static void addClassUri(ipp_t *request, const QString &name);
3458+ static bool isStringValid(const QString &string,
3459+ const bool checkNull = false,
3460+ const int maxLength = 512);
3461+ static bool isStringPrintable(const QString &string, const bool checkNull,
3462+ const int maxLength);
3463+ QString preparePpdForOptions(const QString &ppdfile,
3464+ cups_option_t *options,
3465+ int numOptions);
3466+ bool printerIsClass(const QString &name);
3467+ void setInternalStatus(const QString &status);
3468+ bool postRequest(ipp_t *request, const QString &file,
3469+ const CupsResource &resource);
3470+ bool sendRequest(ipp_t *request, const CupsResource &resource);
3471+ bool sendNewSimpleRequest(ipp_op_t op, const QString &printerName,
3472+ const CupsResource &resource);
3473+ bool handleReply(ipp_t *reply);
3474+ bool isReplyOk(ipp_t *reply, bool deleteIfReplyNotOk);
3475+ void setErrorFromReply(ipp_t *reply);
3476+ QVariant getAttributeValue(ipp_attribute_t *attr, int index=-1) const;
3477+
3478+ http_t *m_connection;
3479+ ipp_status_t m_lastStatus = IPP_OK;
3480+ mutable QString m_internalStatus = QString::null;
3481+};
3482+
3483+
3484+#endif // USC_PRINTERS_CUPS_IPPCLIENT_H
3485
3486=== added file 'modules/Ubuntu/Components/Extras/Printers/cups/printerdriverloader.cpp'
3487--- modules/Ubuntu/Components/Extras/Printers/cups/printerdriverloader.cpp 1970-01-01 00:00:00 +0000
3488+++ modules/Ubuntu/Components/Extras/Printers/cups/printerdriverloader.cpp 2017-02-24 20:39:42 +0000
3489@@ -0,0 +1,127 @@
3490+/*
3491+ * Copyright (C) 2017 Canonical, Ltd.
3492+ *
3493+ * This program is free software; you can redistribute it and/or modify
3494+ * it under the terms of the GNU Lesser General Public License as published by
3495+ * the Free Software Foundation; version 3.
3496+ *
3497+ * This program is distributed in the hope that it will be useful,
3498+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3499+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3500+ * GNU Lesser General Public License for more details.
3501+ *
3502+ * You should have received a copy of the GNU Lesser General Public License
3503+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3504+ */
3505+
3506+#include "printerdriverloader.h"
3507+
3508+PrinterDriverLoader::PrinterDriverLoader(
3509+ const QString &deviceId, const QString &language,
3510+ const QString &makeModel, const QString &product,
3511+ const QStringList &includeSchemes, const QStringList &excludeSchemes)
3512+ : m_deviceId(deviceId)
3513+ , m_language(language)
3514+ , m_makeModel(makeModel)
3515+ , m_product(product)
3516+ , m_includeSchemes(includeSchemes)
3517+ , m_excludeSchemes(excludeSchemes)
3518+{
3519+}
3520+
3521+PrinterDriverLoader::~PrinterDriverLoader()
3522+{
3523+}
3524+
3525+void PrinterDriverLoader::process()
3526+{
3527+ m_running = true;
3528+
3529+ ipp_t* response = client.createPrinterDriversRequest(
3530+ m_deviceId, m_language, m_makeModel, m_product, m_includeSchemes,
3531+ m_excludeSchemes
3532+ );
3533+
3534+ // Note: if the response somehow fails, we return.
3535+ if (!response || ippGetStatusCode(response) > IPP_OK_CONFLICT) {
3536+ QString err(cupsLastErrorString());
3537+ qWarning() << Q_FUNC_INFO << "Cups HTTP error:" << err;
3538+
3539+ if (response)
3540+ ippDelete(response);
3541+
3542+ Q_EMIT error(err);
3543+ Q_EMIT finished();
3544+ return;
3545+ }
3546+
3547+ ipp_attribute_t *attr;
3548+ QByteArray ppdDeviceId;
3549+ QByteArray ppdLanguage;
3550+ QByteArray ppdMakeModel;
3551+ QByteArray ppdName;
3552+
3553+ QList<PrinterDriver> drivers;
3554+
3555+ for (attr = ippFirstAttribute(response); attr != NULL && m_running; attr = ippNextAttribute(response)) {
3556+
3557+ while (attr != NULL && ippGetGroupTag(attr) != IPP_TAG_PRINTER)
3558+ attr = ippNextAttribute(response);
3559+
3560+ if (attr == NULL)
3561+ break;
3562+
3563+ // Pull the needed attributes from this PPD...
3564+ ppdDeviceId = "NONE";
3565+ ppdLanguage.clear();
3566+ ppdMakeModel.clear();
3567+ ppdName.clear();
3568+
3569+ while (attr != NULL && ippGetGroupTag(attr) == IPP_TAG_PRINTER) {
3570+ if (!strcmp(ippGetName(attr), "ppd-device-id") &&
3571+ ippGetValueTag(attr) == IPP_TAG_TEXT) {
3572+ ppdDeviceId = ippGetString(attr, 0, NULL);
3573+ } else if (!strcmp(ippGetName(attr), "ppd-natural-language") &&
3574+ ippGetValueTag(attr) == IPP_TAG_LANGUAGE) {
3575+ ppdLanguage = ippGetString(attr, 0, NULL);
3576+
3577+ } else if (!strcmp(ippGetName(attr), "ppd-make-and-model") &&
3578+ ippGetValueTag(attr) == IPP_TAG_TEXT) {
3579+ ppdMakeModel = ippGetString(attr, 0, NULL);
3580+ } else if (!strcmp(ippGetName(attr), "ppd-name") &&
3581+ ippGetValueTag(attr) == IPP_TAG_NAME) {
3582+
3583+ ppdName = ippGetString(attr, 0, NULL);
3584+ }
3585+
3586+ attr = ippNextAttribute(response);
3587+ }
3588+
3589+ // See if we have everything needed...
3590+ if (ppdLanguage.isEmpty() || ppdMakeModel.isEmpty() ||
3591+ ppdName.isEmpty()) {
3592+ if (attr == NULL)
3593+ break;
3594+ else
3595+ continue;
3596+ }
3597+
3598+ PrinterDriver m;
3599+ m.name = ppdName;
3600+ m.deviceId = ppdDeviceId;
3601+ m.makeModel = ppdMakeModel;
3602+ m.language = ppdLanguage;
3603+
3604+ drivers.append(m);
3605+ }
3606+
3607+ ippDelete(response);
3608+
3609+ Q_EMIT loaded(drivers);
3610+ Q_EMIT finished();
3611+}
3612+
3613+void PrinterDriverLoader::cancel()
3614+{
3615+ m_running = false;
3616+}
3617
3618=== added file 'modules/Ubuntu/Components/Extras/Printers/cups/printerdriverloader.h'
3619--- modules/Ubuntu/Components/Extras/Printers/cups/printerdriverloader.h 1970-01-01 00:00:00 +0000
3620+++ modules/Ubuntu/Components/Extras/Printers/cups/printerdriverloader.h 2017-02-24 20:39:42 +0000
3621@@ -0,0 +1,61 @@
3622+/*
3623+ * Copyright (C) 2017 Canonical, Ltd.
3624+ *
3625+ * This program is free software; you can redistribute it and/or modify
3626+ * it under the terms of the GNU Lesser General Public License as published by
3627+ * the Free Software Foundation; version 3.
3628+ *
3629+ * This program is distributed in the hope that it will be useful,
3630+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3631+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3632+ * GNU Lesser General Public License for more details.
3633+ *
3634+ * You should have received a copy of the GNU Lesser General Public License
3635+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3636+ */
3637+
3638+#ifndef USC_PRINTERS_CUPS_DRIVERLOADER_H
3639+#define USC_PRINTERS_CUPS_DRIVERLOADER_H
3640+
3641+#include "ippclient.h"
3642+#include "structs.h"
3643+
3644+#include <QObject>
3645+#include <QString>
3646+#include <QStringList>
3647+
3648+class PrinterDriverLoader : public QObject
3649+{
3650+ Q_OBJECT
3651+public:
3652+ PrinterDriverLoader(
3653+ const QString &deviceId = "",
3654+ const QString &language = "",
3655+ const QString &makeModel = "",
3656+ const QString &product = "",
3657+ const QStringList &includeSchemes = QStringList(),
3658+ const QStringList &excludeSchemes = QStringList());
3659+ ~PrinterDriverLoader();
3660+
3661+public Q_SLOTS:
3662+ void process();
3663+ void cancel();
3664+
3665+Q_SIGNALS:
3666+ void finished();
3667+ void loaded(const QList<PrinterDriver> &drivers);
3668+ void error(const QString &error);
3669+
3670+private:
3671+ QString m_deviceId = QString::null;
3672+ QString m_language = QString::null;
3673+ QString m_makeModel = QString::null;
3674+ QString m_product = QString::null;
3675+ QStringList m_includeSchemes;
3676+ QStringList m_excludeSchemes;
3677+
3678+ bool m_running = false;
3679+ IppClient client;
3680+};
3681+
3682+#endif // USC_PRINTERS_CUPS_DRIVERLOADER_H
3683
3684=== added file 'modules/Ubuntu/Components/Extras/Printers/cups/printerloader.cpp'
3685--- modules/Ubuntu/Components/Extras/Printers/cups/printerloader.cpp 1970-01-01 00:00:00 +0000
3686+++ modules/Ubuntu/Components/Extras/Printers/cups/printerloader.cpp 2017-02-24 20:39:42 +0000
3687@@ -0,0 +1,53 @@
3688+/*
3689+ * Copyright (C) 2017 Canonical, Ltd.
3690+ *
3691+ * This program is free software; you can redistribute it and/or modify
3692+ * it under the terms of the GNU Lesser General Public License as published by
3693+ * the Free Software Foundation; version 3.
3694+ *
3695+ * This program is distributed in the hope that it will be useful,
3696+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3697+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3698+ * GNU Lesser General Public License for more details.
3699+ *
3700+ * You should have received a copy of the GNU Lesser General Public License
3701+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3702+ */
3703+
3704+#include "backend/backend_pdf.h"
3705+#include "backend/backend_cups.h"
3706+#include "printerloader.h"
3707+
3708+#include <QPrinterInfo>
3709+
3710+class PrinterCupsBackend;
3711+PrinterLoader::PrinterLoader(const QString &printerName,
3712+ IppClient *client,
3713+ OrgCupsCupsdNotifierInterface* notifier,
3714+ QObject *parent)
3715+ : QObject(parent)
3716+ , m_printerName(printerName)
3717+ , m_client(client)
3718+ , m_notifier(notifier)
3719+{
3720+}
3721+
3722+PrinterLoader::~PrinterLoader()
3723+{
3724+}
3725+
3726+void PrinterLoader::load()
3727+{
3728+ QPrinterInfo info = QPrinterInfo::printerInfo(m_printerName);
3729+ auto backend = new PrinterCupsBackend(m_client, info, m_notifier);
3730+
3731+ // Dest or PPD was null, but we know it's name so we will use it.
3732+ if (info.printerName().isEmpty()) {
3733+ backend->setPrinterNameInternal(m_printerName);
3734+ }
3735+
3736+ auto p = QSharedPointer<Printer>(new Printer(backend));
3737+
3738+ Q_EMIT loaded(p);
3739+ Q_EMIT finished();
3740+}
3741
3742=== added file 'modules/Ubuntu/Components/Extras/Printers/cups/printerloader.h'
3743--- modules/Ubuntu/Components/Extras/Printers/cups/printerloader.h 1970-01-01 00:00:00 +0000
3744+++ modules/Ubuntu/Components/Extras/Printers/cups/printerloader.h 2017-02-24 20:39:42 +0000
3745@@ -0,0 +1,49 @@
3746+/*
3747+ * Copyright (C) 2017 Canonical, Ltd.
3748+ *
3749+ * This program is free software; you can redistribute it and/or modify
3750+ * it under the terms of the GNU Lesser General Public License as published by
3751+ * the Free Software Foundation; version 3.
3752+ *
3753+ * This program is distributed in the hope that it will be useful,
3754+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3755+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3756+ * GNU Lesser General Public License for more details.
3757+ *
3758+ * You should have received a copy of the GNU Lesser General Public License
3759+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3760+ */
3761+
3762+#ifndef USC_PRINTERS_CUPS_PRINTERLOADER_H
3763+#define USC_PRINTERS_CUPS_PRINTERLOADER_H
3764+
3765+#include "cups/ippclient.h"
3766+#include "cupsdnotifier.h" // Note: this file was generated.
3767+#include "printer/printer.h"
3768+
3769+#include <QList>
3770+#include <QObject>
3771+#include <QSharedPointer>
3772+
3773+class PrinterLoader : public QObject
3774+{
3775+ Q_OBJECT
3776+ const QString m_printerName;
3777+ IppClient *m_client;
3778+ OrgCupsCupsdNotifierInterface *m_notifier;
3779+public:
3780+ explicit PrinterLoader(const QString &printerName,
3781+ IppClient *client,
3782+ OrgCupsCupsdNotifierInterface* notifier,
3783+ QObject *parent = Q_NULLPTR);
3784+ ~PrinterLoader();
3785+
3786+public Q_SLOTS:
3787+ void load();
3788+
3789+Q_SIGNALS:
3790+ void finished();
3791+ void loaded(QSharedPointer<Printer> printer);
3792+};
3793+
3794+#endif // USC_PRINTERS_CUPS_PRINTERLOADER_H
3795
3796=== added file 'modules/Ubuntu/Components/Extras/Printers/enums.h'
3797--- modules/Ubuntu/Components/Extras/Printers/enums.h 1970-01-01 00:00:00 +0000
3798+++ modules/Ubuntu/Components/Extras/Printers/enums.h 2017-02-24 20:39:42 +0000
3799@@ -0,0 +1,106 @@
3800+/*
3801+ * Copyright (C) 2017 Canonical, Ltd.
3802+ *
3803+ * This program is free software; you can redistribute it and/or modify
3804+ * it under the terms of the GNU Lesser General Public License as published by
3805+ * the Free Software Foundation; version 3.
3806+ *
3807+ * This program is distributed in the hope that it will be useful,
3808+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3809+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3810+ * GNU Lesser General Public License for more details.
3811+ *
3812+ * You should have received a copy of the GNU Lesser General Public License
3813+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3814+ */
3815+
3816+#ifndef USC_PRINTERS_ENUMS_H
3817+#define USC_PRINTERS_ENUMS_H
3818+
3819+#include "printers_global.h"
3820+
3821+#include <QtCore/QObject>
3822+
3823+class PRINTERS_DECL_EXPORT PrinterEnum
3824+{
3825+ Q_GADGET
3826+
3827+public:
3828+ enum class AccessControl
3829+ {
3830+ AccessAllow = 0,
3831+ AccessDeny,
3832+ };
3833+ Q_ENUM(AccessControl)
3834+
3835+ enum class ColorModelType
3836+ {
3837+ GrayType = 0,
3838+ ColorType,
3839+ UnknownType,
3840+ };
3841+ Q_ENUM(ColorModelType)
3842+
3843+ enum class DuplexMode
3844+ {
3845+ DuplexNone = 0,
3846+ DuplexLongSide,
3847+ DuplexShortSide,
3848+ };
3849+ Q_ENUM(DuplexMode)
3850+
3851+ enum class ErrorPolicy
3852+ {
3853+ RetryOnError = 0,
3854+ AbortOnError,
3855+ StopPrinterOnError,
3856+ RetryCurrentOnError,
3857+ };
3858+ Q_ENUM(ErrorPolicy)
3859+
3860+ // Match enums from ipp_jstate_t
3861+ enum class JobState
3862+ {
3863+ Pending = 3,
3864+ Held,
3865+ Processing,
3866+ Stopped,
3867+ Canceled,
3868+ Aborted,
3869+ Complete,
3870+ };
3871+ Q_ENUM(JobState)
3872+
3873+ enum class OperationPolicy
3874+ {
3875+ DefaultOperation = 0,
3876+ AuthenticatedOperation,
3877+ };
3878+ Q_ENUM(OperationPolicy)
3879+
3880+ enum class PrintRange
3881+ {
3882+ AllPages = 0,
3883+ PageRange,
3884+ };
3885+ Q_ENUM(PrintRange)
3886+
3887+ enum class State
3888+ {
3889+ IdleState = 0,
3890+ ActiveState,
3891+ AbortedState,
3892+ ErrorState,
3893+ };
3894+ Q_ENUM(State)
3895+
3896+ enum class PrinterType
3897+ {
3898+ ProxyType = 0, // Represents a printer not yet loaded.
3899+ CupsType,
3900+ PdfType,
3901+ };
3902+ Q_ENUM(PrinterType)
3903+};
3904+
3905+#endif // USC_PRINTERS_ENUMS_H
3906
3907=== added file 'modules/Ubuntu/Components/Extras/Printers/i18n.cpp'
3908--- modules/Ubuntu/Components/Extras/Printers/i18n.cpp 1970-01-01 00:00:00 +0000
3909+++ modules/Ubuntu/Components/Extras/Printers/i18n.cpp 2017-02-24 20:39:42 +0000
3910@@ -0,0 +1,42 @@
3911+/*
3912+ * Copyright (C) 2014, 2017 Canonical, Ltd.
3913+ *
3914+ * This program is free software; you can redistribute it and/or modify
3915+ * it under the terms of the GNU Lesser General Public License as published by
3916+ * the Free Software Foundation; version 3.
3917+ *
3918+ * This program is distributed in the hope that it will be useful,
3919+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3920+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3921+ * GNU Lesser General Public License for more details.
3922+ *
3923+ * You should have received a copy of the GNU Lesser General Public License
3924+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3925+ *
3926+ * Authored by: Ken VanDine <ken.vandine@canonical.com>
3927+ * Andrew Hayzen <andrew.hayzen@canonical.com>
3928+ */
3929+
3930+#define NO_TR_OVERRIDE
3931+#include "i18n.h"
3932+
3933+#include <libintl.h>
3934+
3935+const char *thisDomain = "";
3936+
3937+void initTr(const char *domain, const char *localeDir)
3938+{
3939+ // Don't bind the domain or set textdomain as it changes the Apps domain
3940+ // as well. Instead store the domain and use it in the lookups
3941+ Q_UNUSED(localeDir);
3942+
3943+ thisDomain = domain;
3944+}
3945+
3946+QString __(const char *text, const char *domain)
3947+{
3948+ Q_UNUSED(domain);
3949+
3950+ // Use the stored domain
3951+ return QString::fromUtf8(dgettext(thisDomain, text));
3952+}
3953
3954=== added file 'modules/Ubuntu/Components/Extras/Printers/i18n.h'
3955--- modules/Ubuntu/Components/Extras/Printers/i18n.h 1970-01-01 00:00:00 +0000
3956+++ modules/Ubuntu/Components/Extras/Printers/i18n.h 2017-02-24 20:39:42 +0000
3957@@ -0,0 +1,29 @@
3958+/*
3959+ * Copyright (C) 2014, 2017 Canonical, Ltd.
3960+ *
3961+ * This program is free software; you can redistribute it and/or modify
3962+ * it under the terms of the GNU Lesser General Public License as published by
3963+ * the Free Software Foundation; version 3.
3964+ *
3965+ * This program is distributed in the hope that it will be useful,
3966+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3967+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3968+ * GNU Lesser General Public License for more details.
3969+ *
3970+ * You should have received a copy of the GNU Lesser General Public License
3971+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3972+ *
3973+ * Authored by: Ken VanDine <ken.vandine@canonical.com>
3974+ * Andrew Hayzen <andrew.hayzen@canonical.com>
3975+ */
3976+
3977+#ifndef I18N_H
3978+#define I18N_H
3979+
3980+#include <QtCore/QString>
3981+
3982+void initTr(const char *domain, const char *localeDir);
3983+QString __(const char *text, const char *domain = 0);
3984+
3985+#endif // I18N_H
3986+
3987
3988=== added directory 'modules/Ubuntu/Components/Extras/Printers/models'
3989=== added file 'modules/Ubuntu/Components/Extras/Printers/models/drivermodel.cpp'
3990--- modules/Ubuntu/Components/Extras/Printers/models/drivermodel.cpp 1970-01-01 00:00:00 +0000
3991+++ modules/Ubuntu/Components/Extras/Printers/models/drivermodel.cpp 2017-02-24 20:39:42 +0000
3992@@ -0,0 +1,175 @@
3993+/*
3994+ * Copyright (C) 2017 Canonical, Ltd.
3995+ *
3996+ * This program is free software; you can redistribute it and/or modify
3997+ * it under the terms of the GNU Lesser General Public License as published by
3998+ * the Free Software Foundation; version 3.
3999+ *
4000+ * This program is distributed in the hope that it will be useful,
4001+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4002+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4003+ * GNU Lesser General Public License for more details.
4004+ *
4005+ * You should have received a copy of the GNU Lesser General Public License
4006+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4007+ */
4008+
4009+#include "backend/backend_cups.h"
4010+#include "models/drivermodel.h"
4011+
4012+#include <QDebug>
4013+#include <QtConcurrent>
4014+
4015+DriverModel::DriverModel(PrinterBackend *backend, QObject *parent)
4016+ : QAbstractListModel(parent)
4017+ , m_backend(backend)
4018+{
4019+ connect(m_backend, SIGNAL(printerDriversLoaded(const QList<PrinterDriver>&)),
4020+ this, SLOT(printerDriversLoaded(const QList<PrinterDriver>&)));
4021+
4022+ QObject::connect(&m_watcher,
4023+ &QFutureWatcher<PrinterDriver>::finished,
4024+ this,
4025+ &DriverModel::filterFinished);
4026+
4027+}
4028+
4029+DriverModel::~DriverModel()
4030+{
4031+ cancel();
4032+}
4033+
4034+int DriverModel::rowCount(const QModelIndex &parent) const
4035+{
4036+ Q_UNUSED(parent);
4037+ return m_drivers.size();
4038+}
4039+
4040+int DriverModel::count() const
4041+{
4042+ return rowCount();
4043+}
4044+
4045+QVariant DriverModel::data(const QModelIndex &index, int role) const
4046+{
4047+ QVariant ret;
4048+
4049+ if ((0 <= index.row()) && (index.row() < m_drivers.size())) {
4050+
4051+ auto driver = m_drivers[index.row()];
4052+
4053+ switch (role) {
4054+ case Qt::DisplayRole:
4055+ ret = driver.toString();
4056+ break;
4057+ case NameRole:
4058+ ret = driver.name;
4059+ break;
4060+ case DeviceIdRole:
4061+ ret = driver.deviceId;
4062+ break;
4063+ case LanguageRole:
4064+ ret = driver.language;
4065+ break;
4066+ case MakeModelRole:
4067+ ret = driver.makeModel;
4068+ break;
4069+ }
4070+ }
4071+
4072+ return ret;
4073+}
4074+
4075+QHash<int, QByteArray> DriverModel::roleNames() const
4076+{
4077+ static QHash<int,QByteArray> names;
4078+
4079+ if (Q_UNLIKELY(names.empty())) {
4080+ names[Qt::DisplayRole] = "displayName";
4081+ names[NameRole] = "name";
4082+ names[DeviceIdRole] = "deviceId";
4083+ names[LanguageRole] = "language";
4084+ names[MakeModelRole] = "makeModel";
4085+ }
4086+
4087+ return names;
4088+}
4089+
4090+void DriverModel::setFilter(const QString& pattern)
4091+{
4092+ QList<QByteArray> needles;
4093+ Q_FOREACH(const QString patternPart, pattern.toLower().split(" ")) {
4094+ needles.append(patternPart.toUtf8());
4095+ }
4096+ QList<PrinterDriver> list;
4097+
4098+ if (m_watcher.isRunning())
4099+ m_watcher.cancel();
4100+
4101+ if (pattern.isEmpty()) {
4102+ setModel(m_originalDrivers);
4103+ m_filter = pattern;
4104+ return;
4105+ }
4106+
4107+ if (!m_filter.isEmpty() && !m_drivers.isEmpty() &&
4108+ pattern.startsWith(m_filter))
4109+ list = m_drivers; // search in the smaller list
4110+ else
4111+ list = m_originalDrivers; //search in the whole list
4112+
4113+ m_filter = pattern;
4114+
4115+ QFuture<PrinterDriver> future(QtConcurrent::filtered(list,
4116+ [needles] (const PrinterDriver &driver) {
4117+ QByteArray haystack = driver.makeModel.toLower();
4118+ Q_FOREACH(const QByteArray needle, needles) {
4119+ if (!haystack.contains(needle)) {
4120+ return false;
4121+ }
4122+ }
4123+ return true;
4124+ }
4125+ )
4126+ );
4127+
4128+ Q_EMIT filterBegin();
4129+
4130+ m_watcher.setFuture(future);
4131+}
4132+
4133+QString DriverModel::filter() const
4134+{
4135+ return m_filter;
4136+}
4137+
4138+void DriverModel::filterFinished()
4139+{
4140+ setModel(m_watcher.future().results());
4141+}
4142+
4143+void DriverModel::load()
4144+{
4145+ m_backend->requestPrinterDrivers();
4146+}
4147+
4148+void DriverModel::cancel()
4149+{
4150+ if (m_watcher.isRunning())
4151+ m_watcher.cancel();
4152+}
4153+
4154+void DriverModel::printerDriversLoaded(const QList<PrinterDriver> &drivers)
4155+{
4156+ m_originalDrivers = drivers;
4157+ setModel(m_originalDrivers);
4158+}
4159+
4160+void DriverModel::setModel(const QList<PrinterDriver> &drivers)
4161+{
4162+ beginResetModel();
4163+ m_drivers = drivers;
4164+ endResetModel();
4165+
4166+ Q_EMIT filterComplete();
4167+}
4168
4169=== added file 'modules/Ubuntu/Components/Extras/Printers/models/drivermodel.h'
4170--- modules/Ubuntu/Components/Extras/Printers/models/drivermodel.h 1970-01-01 00:00:00 +0000
4171+++ modules/Ubuntu/Components/Extras/Printers/models/drivermodel.h 2017-02-24 20:39:42 +0000
4172@@ -0,0 +1,82 @@
4173+/*
4174+ * Copyright (C) 2017 Canonical, Ltd.
4175+ *
4176+ * This program is free software; you can redistribute it and/or modify
4177+ * it under the terms of the GNU Lesser General Public License as published by
4178+ * the Free Software Foundation; version 3.
4179+ *
4180+ * This program is distributed in the hope that it will be useful,
4181+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4182+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4183+ * GNU Lesser General Public License for more details.
4184+ *
4185+ * You should have received a copy of the GNU Lesser General Public License
4186+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4187+ */
4188+
4189+#ifndef USC_PRINTER_DRIVERMODEL_H
4190+#define USC_PRINTER_DRIVERMODEL_H
4191+
4192+#include "printers_global.h"
4193+
4194+#include "structs.h"
4195+
4196+#include <QAbstractListModel>
4197+#include <QFutureWatcher>
4198+#include <QModelIndex>
4199+#include <QObject>
4200+#include <QVariant>
4201+
4202+class PRINTERS_DECL_EXPORT DriverModel : public QAbstractListModel
4203+{
4204+ Q_OBJECT
4205+ Q_PROPERTY(int count READ count NOTIFY countChanged)
4206+public:
4207+ explicit DriverModel(PrinterBackend *backend, QObject *parent = Q_NULLPTR);
4208+ ~DriverModel();
4209+
4210+ enum Roles
4211+ {
4212+ // Qt::DisplayRole holds driver name
4213+ NameRole = Qt::UserRole,
4214+ DeviceIdRole,
4215+ LanguageRole,
4216+ MakeModelRole,
4217+ LastRole = MakeModelRole,
4218+ };
4219+
4220+ virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override;
4221+ virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
4222+ virtual QHash<int, QByteArray> roleNames() const override;
4223+
4224+ int count() const;
4225+
4226+ QString filter() const;
4227+ void setFilter(const QString& pattern);
4228+
4229+public Q_SLOTS:
4230+ // Start loading the model.
4231+ void load();
4232+
4233+ // Cancel loading of the model.
4234+ void cancel();
4235+
4236+private Q_SLOTS:
4237+ void printerDriversLoaded(const QList<PrinterDriver> &drivers);
4238+ void filterFinished();
4239+
4240+Q_SIGNALS:
4241+ void countChanged();
4242+ void filterBegin();
4243+ void filterComplete();
4244+
4245+private:
4246+ void setModel(const QList<PrinterDriver> &drivers);
4247+ PrinterBackend *m_backend;
4248+ QList<PrinterDriver> m_drivers;
4249+ QList<PrinterDriver> m_originalDrivers;
4250+ QString m_filter;
4251+ QFutureWatcher<PrinterDriver> m_watcher;
4252+};
4253+
4254+#endif // USC_PRINTER_DRIVERMODEL_H
4255
4256=== added file 'modules/Ubuntu/Components/Extras/Printers/models/jobmodel.cpp'
4257--- modules/Ubuntu/Components/Extras/Printers/models/jobmodel.cpp 1970-01-01 00:00:00 +0000
4258+++ modules/Ubuntu/Components/Extras/Printers/models/jobmodel.cpp 2017-02-24 20:39:42 +0000
4259@@ -0,0 +1,390 @@
4260+/*
4261+ * Copyright (C) 2017 Canonical, Ltd.
4262+ *
4263+ * This program is free software; you can redistribute it and/or modify
4264+ * it under the terms of the GNU Lesser General Public License as published by
4265+ * the Free Software Foundation; version 3.
4266+ *
4267+ * This program is distributed in the hope that it will be useful,
4268+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4269+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4270+ * GNU Lesser General Public License for more details.
4271+ *
4272+ * You should have received a copy of the GNU Lesser General Public License
4273+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4274+ */
4275+
4276+#include "utils.h"
4277+
4278+#include "backend/backend_cups.h"
4279+
4280+#include "models/jobmodel.h"
4281+
4282+#include <QDebug>
4283+
4284+JobModel::JobModel(QObject *parent) : QAbstractListModel(parent)
4285+{
4286+}
4287+
4288+JobModel::JobModel(PrinterBackend *backend,
4289+ QObject *parent)
4290+ : QAbstractListModel(parent)
4291+ , m_backend(backend)
4292+{
4293+ update();
4294+
4295+ QObject::connect(m_backend, &PrinterBackend::jobCreated,
4296+ this, &JobModel::jobSignalCatchAll);
4297+ QObject::connect(m_backend, &PrinterBackend::jobState,
4298+ this, &JobModel::jobSignalCatchAll);
4299+ QObject::connect(m_backend, &PrinterBackend::jobCompleted,
4300+ this, &JobModel::jobSignalCatchAll);
4301+}
4302+
4303+JobModel::~JobModel()
4304+{
4305+}
4306+
4307+void JobModel::jobSignalCatchAll(
4308+ const QString &text, const QString &printer_uri,
4309+ const QString &printer_name, uint printer_state,
4310+ const QString &printer_state_reasons, bool printer_is_accepting_jobs,
4311+ uint job_id, uint job_state, const QString &job_state_reasons,
4312+ const QString &job_name, uint job_impressions_completed)
4313+{
4314+ Q_UNUSED(text);
4315+ Q_UNUSED(printer_uri);
4316+ Q_UNUSED(printer_name);
4317+ Q_UNUSED(printer_state);
4318+ Q_UNUSED(printer_state_reasons);
4319+ Q_UNUSED(printer_is_accepting_jobs);
4320+ Q_UNUSED(job_id);
4321+ Q_UNUSED(job_state);
4322+ Q_UNUSED(job_state_reasons);
4323+ Q_UNUSED(job_name);
4324+
4325+ auto job = getJobById(job_id);
4326+ if (job)
4327+ job->setImpressionsCompleted(job_impressions_completed);
4328+
4329+ update();
4330+}
4331+
4332+void JobModel::update()
4333+{
4334+ // Store the old count and get the new printers
4335+ int oldCount = m_jobs.size();
4336+ QList<QSharedPointer<PrinterJob>> newJobs = m_backend->printerGetJobs();
4337+
4338+ // Go through the old model
4339+ for (int i=0; i < m_jobs.count(); i++) {
4340+ // Determine if the old printer exists in the new model
4341+ bool exists = false;
4342+
4343+ Q_FOREACH(QSharedPointer<PrinterJob> p, newJobs) {
4344+ if (p->jobId() == m_jobs.at(i)->jobId()) {
4345+ exists = true;
4346+
4347+ // Ensure the other properties of the job are up to date
4348+ if (!m_jobs.at(i)->deepCompare(p)) {
4349+ m_jobs.at(i)->updateFrom(p);
4350+
4351+ Q_EMIT dataChanged(index(i), index(i));
4352+ }
4353+
4354+ break;
4355+ }
4356+ }
4357+
4358+ // If it doesn't exist then remove it from the old model
4359+ if (!exists) {
4360+ beginRemoveRows(QModelIndex(), i, i);
4361+ QSharedPointer<PrinterJob> p = m_jobs.takeAt(i);
4362+ endRemoveRows();
4363+
4364+ i--; // as we have removed an item decrement
4365+ }
4366+ }
4367+
4368+ // Go through the new model
4369+ for (int i=0; i < newJobs.count(); i++) {
4370+ // Determine if the new printer exists in the old model
4371+ bool exists = false;
4372+ int j;
4373+
4374+ for (j=0; j < m_jobs.count(); j++) {
4375+ if (m_jobs.at(j)->jobId() == newJobs.at(i)->jobId()) {
4376+ exists = true;
4377+ break;
4378+ }
4379+ }
4380+
4381+ if (exists) {
4382+ if (j == i) { // New printer exists and in correct position
4383+ continue;
4384+ } else {
4385+ // New printer does exist but needs to be moved in old model
4386+ beginMoveRows(QModelIndex(), j, 1, QModelIndex(), i);
4387+ m_jobs.move(j, i);
4388+ endMoveRows();
4389+ }
4390+ } else {
4391+ // New printer does not exist insert into model
4392+ beginInsertRows(QModelIndex(), i, i);
4393+ m_jobs.insert(i, newJobs.at(i));
4394+ endInsertRows();
4395+ }
4396+ }
4397+
4398+ if (oldCount != m_jobs.size()) {
4399+ Q_EMIT countChanged();
4400+ }
4401+}
4402+
4403+int JobModel::rowCount(const QModelIndex &parent) const
4404+{
4405+ Q_UNUSED(parent);
4406+ return m_jobs.size();
4407+}
4408+
4409+int JobModel::count() const
4410+{
4411+ return rowCount();
4412+}
4413+
4414+QVariant JobModel::data(const QModelIndex &index, int role) const
4415+{
4416+ QVariant ret;
4417+
4418+ if ((0<=index.row()) && (index.row()<m_jobs.size())) {
4419+
4420+ auto job = m_jobs[index.row()];
4421+
4422+ switch (role) {
4423+ case CollateRole:
4424+ ret = job->collate();
4425+ break;
4426+ case ColorModelRole: {
4427+ if (job->printer()) {
4428+ ret = job->printer()->supportedColorModels().at(job->colorModel()).text;
4429+ } else {
4430+ qWarning() << "Printer is undefined, no colorModel";
4431+ ret = "";
4432+ }
4433+ break;
4434+ }
4435+ case CompletedTimeRole:
4436+ ret = job->completedTime().toString(QLocale::system().dateTimeFormat());
4437+ break;
4438+ case CopiesRole:
4439+ ret = job->copies();
4440+ break;
4441+ case CreationTimeRole:
4442+ ret = job->creationTime().toString(QLocale::system().dateTimeFormat());
4443+ break;
4444+ case DuplexRole: {
4445+ if (job->printer()) {
4446+ ret = job->printer()->supportedDuplexStrings().at(job->duplexMode());
4447+ } else {
4448+ qWarning() << "Printer is undefined, no duplexMode";
4449+ ret = "";
4450+ }
4451+ break;
4452+ }
4453+ case IdRole:
4454+ ret = job->jobId();
4455+ break;
4456+ case ImpressionsCompletedRole:
4457+ ret = job->impressionsCompleted();
4458+ break;
4459+ case LandscapeRole:
4460+ ret = job->landscape();
4461+ break;
4462+ case MessagesRole:
4463+ ret = job->messages();
4464+ break;
4465+ case PrinterNameRole:
4466+ ret = job->printerName();
4467+ break;
4468+ case PrintRangeRole:
4469+ ret = job->printRange();
4470+ break;
4471+ case PrintRangeModeRole:
4472+ ret = QVariant::fromValue<PrinterEnum::PrintRange>(job->printRangeMode());
4473+ break;
4474+ case ProcessingTimeRole:
4475+ ret = job->processingTime().toString(QLocale::system().dateTimeFormat());
4476+ break;
4477+ case QualityRole: {
4478+ if (job->printer()) {
4479+ ret = job->printer()->supportedPrintQualities().at(job->quality()).text;
4480+ } else {
4481+ qWarning() << "Printer is undefined, no quality";
4482+ ret = "";
4483+ }
4484+ break;
4485+ }
4486+ case ReverseRole:
4487+ ret = job->reverse();
4488+ break;
4489+ case SizeRole:
4490+ ret = job->size();
4491+ break;
4492+ case StateRole:
4493+ // TODO: improve, for now have a switch
4494+ switch (job->state()) {
4495+ case PrinterEnum::JobState::Aborted:
4496+ ret = "Aborted";
4497+ break;
4498+ case PrinterEnum::JobState::Canceled:
4499+ ret = "Canceled";
4500+ break;
4501+ case PrinterEnum::JobState::Complete:
4502+ ret = "Compelete";
4503+ break;
4504+ case PrinterEnum::JobState::Held:
4505+ ret = "Held";
4506+ break;
4507+ case PrinterEnum::JobState::Pending:
4508+ ret = "Pending";
4509+ break;
4510+ case PrinterEnum::JobState::Processing:
4511+ ret = "Processing";
4512+ break;
4513+ case PrinterEnum::JobState::Stopped:
4514+ ret = "Stopped";
4515+ break;
4516+ }
4517+ break;
4518+ case Qt::DisplayRole:
4519+ case TitleRole:
4520+ ret = job->title();
4521+ break;
4522+ case UserRole:
4523+ ret = job->user();
4524+ break;
4525+ }
4526+ }
4527+
4528+ return ret;
4529+}
4530+
4531+QHash<int, QByteArray> JobModel::roleNames() const
4532+{
4533+ static QHash<int,QByteArray> names;
4534+
4535+ if (Q_UNLIKELY(names.empty())) {
4536+ names[Qt::DisplayRole] = "displayName";
4537+ names[IdRole] = "id";
4538+ names[CollateRole] = "collate";
4539+ names[ColorModelRole] = "colorModel";
4540+ names[CompletedTimeRole] = "completedTime";
4541+ names[CopiesRole] = "copies";
4542+ names[CreationTimeRole] = "creationTime";
4543+ names[DuplexRole] = "duplexMode";
4544+ names[ImpressionsCompletedRole] = "impressionsCompleted";
4545+ names[LandscapeRole] = "landscape";
4546+ names[MessagesRole] = "messages";
4547+ names[PrinterNameRole] = "printerName";
4548+ names[PrintRangeRole] = "printRange";
4549+ names[PrintRangeModeRole] = "printRangeMode";
4550+ names[ProcessingTimeRole] = "processingTime";
4551+ names[QualityRole] = "quality";
4552+ names[ReverseRole] = "reverse";
4553+ names[SizeRole] = "size";
4554+ names[StateRole] = "state";
4555+ names[TitleRole] = "title";
4556+ names[UserRole] = "user";
4557+ names[LastStateMessageRole] = "lastStateMessage";
4558+ }
4559+
4560+ return names;
4561+}
4562+
4563+QVariantMap JobModel::get(const int row) const
4564+{
4565+ QHashIterator<int, QByteArray> iterator(roleNames());
4566+ QVariantMap result;
4567+ QModelIndex modelIndex = index(row, 0);
4568+
4569+ while (iterator.hasNext()) {
4570+ iterator.next();
4571+ result[iterator.value()] = modelIndex.data(iterator.key());
4572+ }
4573+
4574+ return result;
4575+}
4576+
4577+QSharedPointer<PrinterJob> JobModel::getJobById(const int &id)
4578+{
4579+ Q_FOREACH(auto job, m_jobs) {
4580+ if (job->jobId() == id) {
4581+ return job;
4582+ }
4583+ }
4584+ return QSharedPointer<PrinterJob>(Q_NULLPTR);
4585+}
4586+
4587+
4588+JobFilter::JobFilter(QObject *parent) : QSortFilterProxyModel(parent)
4589+{
4590+ connect(this, SIGNAL(sourceModelChanged()), SLOT(onSourceModelChanged()));
4591+}
4592+
4593+JobFilter::~JobFilter()
4594+{
4595+}
4596+
4597+QVariantMap JobFilter::get(const int row) const
4598+{
4599+ QHashIterator<int, QByteArray> iterator(roleNames());
4600+ QVariantMap result;
4601+ QModelIndex modelIndex = index(row, 0);
4602+
4603+ while (iterator.hasNext()) {
4604+ iterator.next();
4605+ result[iterator.value()] = modelIndex.data(iterator.key());
4606+ }
4607+
4608+ return result;
4609+}
4610+
4611+void JobFilter::onSourceModelChanged()
4612+{
4613+ connect((JobModel*) sourceModel(),
4614+ SIGNAL(countChanged()),
4615+ this,
4616+ SIGNAL(countChanged()));
4617+}
4618+
4619+void JobFilter::onSourceModelCountChanged()
4620+{
4621+ Q_EMIT countChanged();
4622+}
4623+
4624+int JobFilter::count() const
4625+{
4626+ return rowCount();
4627+}
4628+
4629+void JobFilter::filterOnPrinterName(const QString &name)
4630+{
4631+ m_printerName = name;
4632+ m_printerNameFilterEnabled = true;
4633+ invalidate();
4634+}
4635+
4636+bool JobFilter::filterAcceptsRow(int sourceRow,
4637+ const QModelIndex &sourceParent) const
4638+{
4639+ bool accepts = true;
4640+ QModelIndex childIndex = sourceModel()->index(sourceRow, 0, sourceParent);
4641+
4642+ if (accepts && m_printerNameFilterEnabled) {
4643+ QString printerName = childIndex.model()->data(
4644+ childIndex, JobModel::PrinterNameRole).toString();
4645+ accepts = m_printerName == printerName;
4646+ }
4647+
4648+ return accepts;
4649+}
4650
4651=== added file 'modules/Ubuntu/Components/Extras/Printers/models/jobmodel.h'
4652--- modules/Ubuntu/Components/Extras/Printers/models/jobmodel.h 1970-01-01 00:00:00 +0000
4653+++ modules/Ubuntu/Components/Extras/Printers/models/jobmodel.h 2017-02-24 20:39:42 +0000
4654@@ -0,0 +1,125 @@
4655+/*
4656+ * Copyright (C) 2017 Canonical, Ltd.
4657+ *
4658+ * This program is free software; you can redistribute it and/or modify
4659+ * it under the terms of the GNU Lesser General Public License as published by
4660+ * the Free Software Foundation; version 3.
4661+ *
4662+ * This program is distributed in the hope that it will be useful,
4663+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4664+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4665+ * GNU Lesser General Public License for more details.
4666+ *
4667+ * You should have received a copy of the GNU Lesser General Public License
4668+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4669+ */
4670+
4671+#ifndef USC_JOB_MODEL_H
4672+#define USC_JOB_MODEL_H
4673+
4674+#include "printers_global.h"
4675+#include "backend/backend.h"
4676+#include "printer/printerjob.h"
4677+
4678+#include <QAbstractListModel>
4679+#include <QByteArray>
4680+#include <QModelIndex>
4681+#include <QObject>
4682+#include <QSharedPointer>
4683+#include <QSortFilterProxyModel>
4684+#include <QTimer>
4685+#include <QVariant>
4686+
4687+class PRINTERS_DECL_EXPORT JobModel : public QAbstractListModel
4688+{
4689+ Q_OBJECT
4690+
4691+ Q_PROPERTY(int count READ count NOTIFY countChanged)
4692+public:
4693+ explicit JobModel(QObject *parent = Q_NULLPTR);
4694+ explicit JobModel(PrinterBackend *backend,
4695+ QObject *parent = Q_NULLPTR);
4696+ ~JobModel();
4697+
4698+ enum Roles
4699+ {
4700+ // Qt::DisplayRole holds job title
4701+ IdRole = Qt::UserRole,
4702+ CollateRole,
4703+ ColorModelRole,
4704+ CompletedTimeRole,
4705+ CopiesRole,
4706+ CreationTimeRole,
4707+ DuplexRole,
4708+ ImpressionsCompletedRole,
4709+ LandscapeRole,
4710+ MessagesRole,
4711+ PrinterNameRole,
4712+ PrintRangeRole,
4713+ PrintRangeModeRole,
4714+ ProcessingTimeRole,
4715+ QualityRole,
4716+ ReverseRole,
4717+ SizeRole,
4718+ StateRole,
4719+ TitleRole,
4720+ UserRole,
4721+ LastStateMessageRole,
4722+ LastRole = LastStateMessageRole,
4723+ };
4724+
4725+ virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override;
4726+ virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
4727+ virtual QHash<int, QByteArray> roleNames() const override;
4728+
4729+ int count() const;
4730+
4731+ Q_INVOKABLE QVariantMap get(const int row) const;
4732+ QSharedPointer<PrinterJob> getJobById(const int &id);
4733+private:
4734+ PrinterBackend *m_backend;
4735+
4736+ QList<QSharedPointer<PrinterJob>> m_jobs;
4737+private Q_SLOTS:
4738+ void update();
4739+ void jobSignalCatchAll(const QString &text, const QString &printer_uri,
4740+ const QString &printer_name, uint printer_state,
4741+ const QString &printer_state_reasons,
4742+ bool printer_is_accepting_jobs, uint job_id,
4743+ uint job_state, const QString &job_state_reasons,
4744+ const QString &job_name,
4745+ uint job_impressions_completed);
4746+
4747+Q_SIGNALS:
4748+ void countChanged();
4749+};
4750+
4751+class PRINTERS_DECL_EXPORT JobFilter : public QSortFilterProxyModel
4752+{
4753+ Q_OBJECT
4754+ Q_PROPERTY(int count READ count NOTIFY countChanged)
4755+public:
4756+ explicit JobFilter(QObject *parent = Q_NULLPTR);
4757+ ~JobFilter();
4758+
4759+ Q_INVOKABLE QVariantMap get(const int row) const;
4760+
4761+ void filterOnPrinterName(const QString &name);
4762+ int count() const;
4763+protected:
4764+ virtual bool filterAcceptsRow(
4765+ int sourceRow, const QModelIndex &sourceParent) const override;
4766+
4767+Q_SIGNALS:
4768+ void countChanged();
4769+
4770+private Q_SLOTS:
4771+ void onSourceModelChanged();
4772+ void onSourceModelCountChanged();
4773+
4774+private:
4775+ QString m_printerName = QString::null;
4776+ bool m_printerNameFilterEnabled = false;
4777+};
4778+
4779+#endif // USC_JOB_MODEL_H
4780
4781=== added file 'modules/Ubuntu/Components/Extras/Printers/models/printermodel.cpp'
4782--- modules/Ubuntu/Components/Extras/Printers/models/printermodel.cpp 1970-01-01 00:00:00 +0000
4783+++ modules/Ubuntu/Components/Extras/Printers/models/printermodel.cpp 2017-02-24 20:39:42 +0000
4784@@ -0,0 +1,503 @@
4785+/*
4786+ * Copyright (C) 2017 Canonical, Ltd.
4787+ *
4788+ * This program is free software; you can redistribute it and/or modify
4789+ * it under the terms of the GNU Lesser General Public License as published by
4790+ * the Free Software Foundation; version 3.
4791+ *
4792+ * This program is distributed in the hope that it will be useful,
4793+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4794+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4795+ * GNU Lesser General Public License for more details.
4796+ *
4797+ * You should have received a copy of the GNU Lesser General Public License
4798+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4799+ */
4800+
4801+#include "backend/backend_cups.h"
4802+#include "backend/backend_pdf.h"
4803+#include "i18n.h"
4804+#include "models/jobmodel.h"
4805+#include "models/printermodel.h"
4806+#include "utils.h"
4807+
4808+#include <QDebug>
4809+
4810+PrinterModel::PrinterModel(PrinterBackend *backend, QObject *parent)
4811+ : QAbstractListModel(parent)
4812+ , m_backend(backend)
4813+{
4814+
4815+ QObject::connect(m_backend, &PrinterBackend::printerAdded,
4816+ this, &PrinterModel::printerAdded);
4817+ QObject::connect(m_backend, &PrinterBackend::printerModified,
4818+ &m_signalHandler, &PrinterSignalHandler::onPrinterModified);
4819+ QObject::connect(m_backend, &PrinterBackend::printerStateChanged,
4820+ &m_signalHandler, &PrinterSignalHandler::onPrinterModified);
4821+ QObject::connect(m_backend, &PrinterBackend::printerDeleted,
4822+ this, &PrinterModel::printerDeleted);
4823+
4824+ connect(&m_signalHandler, SIGNAL(printerModified(const QString&)),
4825+ this, SLOT(printerModified(const QString&)));
4826+ connect(m_backend, SIGNAL(printerLoaded(QSharedPointer<Printer>)),
4827+ this, SLOT(printerLoaded(QSharedPointer<Printer>)));
4828+
4829+ // Create printer proxies for every printerName.
4830+ Q_FOREACH(auto printerName, m_backend->availablePrinterNames()) {
4831+ auto p = QSharedPointer<Printer>(new Printer(new PrinterBackend(printerName)));
4832+ addPrinter(p, CountChangeSignal::Defer);
4833+ }
4834+
4835+ // Add a PDF printer.
4836+ auto pdfPrinter = QSharedPointer<Printer>(
4837+ new Printer(new PrinterPdfBackend(__("Create PDF")))
4838+ );
4839+ addPrinter(pdfPrinter, CountChangeSignal::Defer);
4840+
4841+ Q_EMIT countChanged();
4842+}
4843+
4844+PrinterModel::~PrinterModel()
4845+{
4846+}
4847+
4848+void PrinterModel::printerLoaded(QSharedPointer<Printer> printer)
4849+{
4850+ // Find and possibly replace an old printer.
4851+ for (int i=0; i < m_printers.count(); i++) {
4852+ auto oldPrinter = m_printers.at(i);
4853+ if (printer->name() == oldPrinter->name()) {
4854+ if (!oldPrinter->deepCompare(printer)) {
4855+ updatePrinter(oldPrinter, printer);
4856+ }
4857+
4858+ // We're done.
4859+ return;
4860+ }
4861+ }
4862+
4863+ addPrinter(printer, CountChangeSignal::Emit);
4864+}
4865+
4866+void PrinterModel::printerModified(const QString &printerName)
4867+{
4868+ // These signals might be emitted of a now deleted printer.
4869+ if (getPrinterByName(printerName))
4870+ m_backend->requestPrinter(printerName);
4871+}
4872+
4873+void PrinterModel::printerAdded(
4874+ const QString &text, const QString &printerUri,
4875+ const QString &printerName, uint printerState,
4876+ const QString &printerStateReason, bool acceptingJobs)
4877+{
4878+ Q_UNUSED(text);
4879+ Q_UNUSED(printerUri);
4880+ Q_UNUSED(printerState);
4881+ Q_UNUSED(printerStateReason);
4882+ Q_UNUSED(acceptingJobs);
4883+
4884+ m_backend->requestPrinter(printerName);
4885+}
4886+
4887+void PrinterModel::printerDeleted(
4888+ const QString &text, const QString &printerUri,
4889+ const QString &printerName, uint printerState,
4890+ const QString &printerStateReason, bool acceptingJobs)
4891+{
4892+ Q_UNUSED(text);
4893+ Q_UNUSED(printerUri);
4894+ Q_UNUSED(printerState);
4895+ Q_UNUSED(printerStateReason);
4896+ Q_UNUSED(acceptingJobs);
4897+
4898+ auto printer = getPrinterByName(printerName);
4899+ if (printer) {
4900+ removePrinter(printer, CountChangeSignal::Emit);
4901+ }
4902+}
4903+
4904+QSharedPointer<Printer> PrinterModel::getPrinterByName(const QString &printerName)
4905+{
4906+ Q_FOREACH(auto p, m_printers) {
4907+ if (p->name() == printerName)
4908+ return p;
4909+ }
4910+ return QSharedPointer<Printer>(Q_NULLPTR);
4911+}
4912+
4913+void PrinterModel::movePrinter(const int &from, const int &to)
4914+{
4915+ int size = m_printers.size();
4916+ if (from < 0 || to < 0 || from >= size || to >= size) {
4917+ qWarning() << Q_FUNC_INFO << "Illegal move operation from"
4918+ << from << "to" << to << ". Size was" << size;
4919+ return;
4920+ }
4921+ if (!beginMoveRows(QModelIndex(), from, from, QModelIndex(), to)) {
4922+ qWarning() << Q_FUNC_INFO << "failed to move rows.";
4923+ return;
4924+ }
4925+ m_printers.move(from, to);
4926+ endMoveRows();
4927+}
4928+
4929+void PrinterModel::removePrinter(QSharedPointer<Printer> printer, const CountChangeSignal &notify)
4930+{
4931+ int idx = m_printers.indexOf(printer);
4932+ beginRemoveRows(QModelIndex(), idx, idx);
4933+ m_printers.removeAt(idx);
4934+ endRemoveRows();
4935+
4936+ if (notify == CountChangeSignal::Emit)
4937+ Q_EMIT countChanged();
4938+}
4939+
4940+void PrinterModel::updatePrinter(QSharedPointer<Printer> old,
4941+ QSharedPointer<Printer> newPrinter)
4942+{
4943+ int i = m_printers.indexOf(old);
4944+ QModelIndex idx = index(i);
4945+ old->updateFrom(newPrinter);
4946+ Q_EMIT dataChanged(idx, idx);
4947+}
4948+
4949+void PrinterModel::addPrinter(QSharedPointer<Printer> printer, const CountChangeSignal &notify)
4950+{
4951+ int i = m_printers.size();
4952+ beginInsertRows(QModelIndex(), i, i);
4953+ m_printers.append(printer);
4954+ endInsertRows();
4955+
4956+ if (notify == CountChangeSignal::Emit)
4957+ Q_EMIT countChanged();
4958+}
4959+
4960+int PrinterModel::rowCount(const QModelIndex &parent) const
4961+{
4962+ Q_UNUSED(parent);
4963+ return m_printers.size();
4964+}
4965+
4966+int PrinterModel::count() const
4967+{
4968+ return rowCount();
4969+}
4970+
4971+QVariant PrinterModel::data(const QModelIndex &index, int role) const
4972+{
4973+ QVariant ret;
4974+
4975+ if ((0<=index.row()) && (index.row()<m_printers.size())) {
4976+
4977+ auto printer = m_printers[index.row()];
4978+
4979+
4980+ /* If printer is a proxy (not loaded), determine if the requested role
4981+ is something async and that we need to request the data. */
4982+ if (printer->type() == PrinterEnum::PrinterType::ProxyType) {
4983+ switch (role) {
4984+ case Qt::DisplayRole:
4985+ case NameRole:
4986+ case DefaultPrinterRole:
4987+ case PrinterRole:
4988+ case IsPdfRole:
4989+ case IsLoadedRole:
4990+ break; // All of these can be inferred from the name (lazily).
4991+ default:
4992+ m_backend->requestPrinter(printer->name());
4993+ }
4994+ }
4995+
4996+ switch (role) {
4997+ case NameRole:
4998+ case Qt::DisplayRole:
4999+ ret = printer->name();
5000+ break;
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches

to all changes: