Merge lp:~jonas-drange/ubuntu-ui-extras/force-serial-test-run into lp:ubuntu-ui-extras

Proposed by Jonas G. Drange
Status: Superseded
Proposed branch: lp:~jonas-drange/ubuntu-ui-extras/force-serial-test-run
Merge into: lp:ubuntu-ui-extras
Diff against target: 9662 lines (+9310/-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 (+315/-0)
modules/Ubuntu/Components/Extras/Printers/printer/printer.h (+96/-0)
modules/Ubuntu/Components/Extras/Printers/printer/printerjob.cpp (+514/-0)
modules/Ubuntu/Components/Extras/Printers/printer/printerjob.h (+174/-0)
modules/Ubuntu/Components/Extras/Printers/printer/printersignalhandler.cpp (+69/-0)
modules/Ubuntu/Components/Extras/Printers/printer/printersignalhandler.h (+55/-0)
modules/Ubuntu/Components/Extras/Printers/printers/printers.cpp (+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 (+61/-0)
tests/unittests/Printers/mockbackend.h (+390/-0)
tests/unittests/Printers/tst_drivermodel.cpp (+136/-0)
tests/unittests/Printers/tst_jobfilter.cpp (+55/-0)
tests/unittests/Printers/tst_jobmodel.cpp (+120/-0)
tests/unittests/Printers/tst_printer.cpp (+284/-0)
tests/unittests/Printers/tst_printerfilter.cpp (+126/-0)
tests/unittests/Printers/tst_printerjob.cpp (+318/-0)
tests/unittests/Printers/tst_printermodel.cpp (+161/-0)
tests/unittests/Printers/tst_printers.cpp (+248/-0)
tests/unittests/Printers/tst_signalhandler.cpp (+44/-0)
To merge this branch: bzr merge lp:~jonas-drange/ubuntu-ui-extras/force-serial-test-run
Reviewer Review Type Date Requested Status
Ubuntu Phablet Team Pending
Review via email: mp+318222@code.launchpad.net

Commit message

forces serial test run of printer tests

Description of the change

forces serial test run of printer tests

To post a comment you must log in.

Unmerged revisions

135. By Jonas G. Drange

forces serial test run of printer stuff

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/control'
2--- debian/control 2016-08-24 15:46:52 +0000
3+++ debian/control 2017-02-24 13:40:38 +0000
4@@ -3,8 +3,10 @@
5 Priority: optional
6 Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
7 Build-Depends: cmake (>= 2.8.9),
8+ cmake-extras (>= 0.10),
9 debhelper (>= 9),
10 gettext,
11+ libcups2-dev,
12 pkg-config,
13 python:any,
14 qt5-default,
15@@ -27,6 +29,7 @@
16 Architecture: any
17 Depends: ${misc:Depends},
18 ${shlibs:Depends},
19+ libqt5printsupport5,
20 qml-module-qtquick2,
21 qml-module-ubuntu-components,
22 qml-module-qtquick-window2,
23
24=== modified file 'modules/Ubuntu/Components/Extras/CMakeLists.txt'
25--- modules/Ubuntu/Components/Extras/CMakeLists.txt 2016-12-08 14:26:41 +0000
26+++ modules/Ubuntu/Components/Extras/CMakeLists.txt 2017-02-24 13:40:38 +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 13:40:38 +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 13:40:38 +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 13:40:38 +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 13:40:38 +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 13:40:38 +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 13:40:38 +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 13:40:38 +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 13:40:38 +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 13:40:38 +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 13:40:38 +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 13:40:38 +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 13:40:38 +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 13:40:38 +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 13:40:38 +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 13:40:38 +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 13:40:38 +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 13:40:38 +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 13:40:38 +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 13:40:38 +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 13:40:38 +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 13:40:38 +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 13:40:38 +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 13:40:38 +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