Merge lp:~jonas-drange/ubuntu-ui-extras/dedupe-requests into lp:ubuntu-ui-extras

Proposed by Jonas G. Drange
Status: Superseded
Proposed branch: lp:~jonas-drange/ubuntu-ui-extras/dedupe-requests
Merge into: lp:ubuntu-ui-extras
Diff against target: 9609 lines (+9257/-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 (+135/-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 (+232/-0)
modules/Ubuntu/Components/Extras/Printers/printers/printers.h (+102/-0)
modules/Ubuntu/Components/Extras/Printers/printers_global.h (+23/-0)
modules/Ubuntu/Components/Extras/Printers/qmldir (+2/-0)
modules/Ubuntu/Components/Extras/Printers/structs.h (+97/-0)
modules/Ubuntu/Components/Extras/Printers/utils.h (+110/-0)
po/CMakeLists.txt (+2/-1)
po/ubuntu-ui-extras.pot (+27/-3)
tests/unittests/CMakeLists.txt (+2/-0)
tests/unittests/Printers/CMakeLists.txt (+48/-0)
tests/unittests/Printers/mockbackend.h (+382/-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 (+233/-0)
tests/unittests/Printers/tst_signalhandler.cpp (+44/-0)
To merge this branch: bzr merge lp:~jonas-drange/ubuntu-ui-extras/dedupe-requests
Reviewer Review Type Date Requested Status
Ubuntu Phablet Team Pending
Review via email: mp+318217@code.launchpad.net

Commit message

guards against excessive loading of printers and drivers

Description of the change

guards against excessive loading of printers and drivers

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

qlist->qset

Unmerged revisions

Preview Diff

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

Subscribers

People subscribed via source and target branches