Merge lp:ubuntu-ui-extras/staging into lp:ubuntu-ui-extras

Proposed by Jonas G. Drange
Status: Merged
Approved by: Andrew Hayzen
Approved revision: no longer in the source branch.
Merged at revision: 131
Proposed branch: lp:ubuntu-ui-extras/staging
Merge into: lp:ubuntu-ui-extras
Diff against target: 6506 lines (+3970/-590)
46 files modified
debian/changelog (+67/-0)
modules/Ubuntu/Components/Extras/Example/PrinterQueue.qml (+0/-117)
modules/Ubuntu/Components/Extras/Example/Printers.qml (+232/-75)
modules/Ubuntu/Components/Extras/Printers/CMakeLists.txt (+4/-1)
modules/Ubuntu/Components/Extras/Printers/backend/backend.cpp (+48/-0)
modules/Ubuntu/Components/Extras/Printers/backend/backend.h (+13/-0)
modules/Ubuntu/Components/Extras/Printers/backend/backend_cups.cpp (+225/-44)
modules/Ubuntu/Components/Extras/Printers/backend/backend_cups.h (+25/-1)
modules/Ubuntu/Components/Extras/Printers/backend/backend_pdf.cpp (+10/-0)
modules/Ubuntu/Components/Extras/Printers/cups/devicesearcher.cpp (+70/-0)
modules/Ubuntu/Components/Extras/Printers/cups/devicesearcher.h (+57/-0)
modules/Ubuntu/Components/Extras/Printers/cups/ippclient.cpp (+163/-9)
modules/Ubuntu/Components/Extras/Printers/cups/ippclient.h (+13/-1)
modules/Ubuntu/Components/Extras/Printers/cups/jobloader.cpp (+50/-0)
modules/Ubuntu/Components/Extras/Printers/cups/jobloader.h (+47/-0)
modules/Ubuntu/Components/Extras/Printers/cups/printerloader.cpp (+3/-0)
modules/Ubuntu/Components/Extras/Printers/enums.h (+23/-0)
modules/Ubuntu/Components/Extras/Printers/models/devicemodel.cpp (+149/-0)
modules/Ubuntu/Components/Extras/Printers/models/devicemodel.h (+78/-0)
modules/Ubuntu/Components/Extras/Printers/models/jobmodel.cpp (+267/-126)
modules/Ubuntu/Components/Extras/Printers/models/jobmodel.h (+53/-11)
modules/Ubuntu/Components/Extras/Printers/models/printermodel.cpp (+70/-30)
modules/Ubuntu/Components/Extras/Printers/models/printermodel.h (+13/-4)
modules/Ubuntu/Components/Extras/Printers/plugin.cpp (+2/-0)
modules/Ubuntu/Components/Extras/Printers/printer/printer.cpp (+150/-24)
modules/Ubuntu/Components/Extras/Printers/printer/printer.h (+29/-3)
modules/Ubuntu/Components/Extras/Printers/printer/printerjob.cpp (+108/-60)
modules/Ubuntu/Components/Extras/Printers/printer/printerjob.h (+14/-11)
modules/Ubuntu/Components/Extras/Printers/printer/signalratelimiter.cpp (+28/-6)
modules/Ubuntu/Components/Extras/Printers/printer/signalratelimiter.h (+8/-6)
modules/Ubuntu/Components/Extras/Printers/printers/printers.cpp (+152/-7)
modules/Ubuntu/Components/Extras/Printers/printers/printers.h (+20/-0)
modules/Ubuntu/Components/Extras/Printers/structs.h (+84/-0)
po/ubuntu-ui-extras.pot (+26/-2)
tests/unittests/Printers/CMakeLists.txt (+13/-3)
tests/unittests/Printers/mockbackend.h (+127/-4)
tests/unittests/Printers/tst_jobfilter.cpp (+4/-2)
tests/unittests/Printers/tst_jobmodel.cpp (+446/-29)
tests/unittests/Printers/tst_printer.cpp (+96/-0)
tests/unittests/Printers/tst_printerdevice.cpp (+131/-0)
tests/unittests/Printers/tst_printerdevicemodel.cpp (+162/-0)
tests/unittests/Printers/tst_printerfilter.cpp (+30/-0)
tests/unittests/Printers/tst_printerjob.cpp (+53/-0)
tests/unittests/Printers/tst_printermodel.cpp (+489/-1)
tests/unittests/Printers/tst_printers.cpp (+92/-7)
tests/unittests/Printers/tst_signalratelimiter.cpp (+26/-6)
To merge this branch: bzr merge lp:ubuntu-ui-extras/staging
Reviewer Review Type Date Requested Status
Andrew Hayzen (community) Approve
Jonas G. Drange (community) Approve
Review via email: mp+320797@code.launchpad.net

Description of the change

Please see the changelog entry for details.

Testing:
1. Build it
2. qmlscene -I <path/to/build/folder/modules/ <path/to/workding-dir>/modules/Ubuntu/Components/Extras/Example/Printers.qml
3. Add a printer
4. Explore configured printers
5. Change configured printers

To post a comment you must log in.
Revision history for this message
Jonas G. Drange (jonas-drange) wrote :

I approve all the changes made by Andrew, which is this whole branch minus my own changes.

review: Approve
Revision history for this message
Andrew Hayzen (ahayzen) wrote :

I approve all the changes made by Jonas, which is this whole branch minus my own changes.

review: Approve
lp:ubuntu-ui-extras/staging updated
131. By CI Train Bot Account

* Adds devices as an model on Printers, and re-instates remote printer model on Printers.
* makes sure the backend is refreshed on enable/disable
* If printerAdded is called ensure there is always a proxy printer
      corrects thread affinity for printerloaded printers, as well as any qobject children it might have
* Remove PrinterQueue.qml example and add missing job methods to Printers.qml example, so we have only one example
* improves the deviceName by dropping the CMD (note that the full string remains accessible from device.id)
* Connect job-impressions-completed from cups to PrinterJob::impressionsCompleted()
* In the job model listen to printerStateChanged as changes t job-impressions-completed causes that signal
* Improve the PrinterSignalHandler to limit the maximum wait time of unprocessed signals to four times the timeout
* Add unit test for SignalRateLimiter to check it does perform a flush
* Rename PrinterSignalHandler to SignalRateLimiter
* Add a JobLoader for loading a specific jobId for a printer and loading the extended attributes
* Move the loading of creationTime, completedTime, processingTime, size and user to extended attributes as signals don't give us those
* Add method for PrinterBackend for getting a specific job
* Split up JobModel::update so there is jobCreated, jobState and jobCompleted which then call addJob, removeJob and updateJob
* Improve Printer::updateFrom to not call loadAttributes as this results in a possible UI block
* Add missing comparisions for PrinterJob deepCompare and updateFrom
* Change PrinterJob::setPrinter to not call loadDefaults and instead explicitly call it
* Change Printers to trigger requestJobExtendedAttributes which triggers a background thread rather than doing in foreground
* exposes copies attribute on the printer
* includes cups/adminutils.h into which the cups device callback was moved in >= libcups2-dev 2.2.2
* allows browsing on local and remote printers
* Add filters for active, paused and queued
* Add sorting by creationTime and then falling back to id
* Change time related roles in JobModel to return QDateTime, not QString, so sorting doesn't break
* Update tests
* Change printerGetJobAttributes to use printer-uri and job-id (the same as holdJob and releaseJob
* Give printerGetJobAttributes printerName so we can ensure we get the correct job
* adds read/write of the shared property
* adds extended attribute fetching (IppClient::printerGetAttributes)
* refactors attribute retrieval from cups
* implements deviceUri and lastMessage on Printer
* adds Printers.printTestPage
* Add tests for JobModel roles
* Add tests for make and location in Printer
* Fix JobModel color and quality roles to use fallback if no text
* Add tests for all roles in PrinterModel
* Remove PdfRole it is not used (IsPdfRole instead)
* Add holdJob and releaseJob methods to Printers
* Add HeldRole to JobModel
* Add tests for hold and releasing a job
* Update example to show how hold and release work
* Update StateRole to return just the state, not text
* Remove PrinterJob friends and make setters public as they aren't exposed to QML anyway
* adds make and location to printermodel and printer
* Pull of trunk
* Remove use of bitewise or as it fails in CI
* guards against excessive loading of printers and drivers
* adds loadPrinter method on Printers
* drops a lot of code that was not used, and some TODOS/FIXMES that aren't necessary
* Fix/add commented or empty tests
* Implement tests that had QSKIP
* removes empty brackets
* adds cmake extras, required by printer stuff
* Remove remaining fixme's
* Rename UbuntuSettingsComponentsPrintersPlugin to UbuntuComponentsExtrasPrintersPlugin
* Remove import Ubuntu.Settings.Components 0.1 from example qml imports
* Rename definition UBUNTUSETTINGSPRINTERS_LIBRARY to UBUNTUCOMPONENTSEXTRASPRINTERS_LIBRARY
* Rename UbuntuSettingsPrintersQml to UbuntuComponentsExtrasPrintersQml
* Rename translation domain to ubuntu-ui-extras
* Fix for printsupport being build-depends rather than depends

Preview Diff

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

Subscribers

People subscribed via source and target branches

to all changes: