Merge lp:~jonas-drange/ubuntu-ui-extras/printerloader-thread-affinity into lp:ubuntu-ui-extras

Proposed by Jonas G. Drange
Status: Superseded
Proposed branch: lp:~jonas-drange/ubuntu-ui-extras/printerloader-thread-affinity
Merge into: lp:ubuntu-ui-extras
Diff against target: 6179 lines (+3849/-439)
45 files modified
modules/Ubuntu/Components/Extras/Example/PrinterQueue.qml (+24/-8)
modules/Ubuntu/Components/Extras/Example/Printers.qml (+192/-44)
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 (+55/-19)
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 (+149/-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 (+132/-7)
modules/Ubuntu/Components/Extras/Printers/printers/printers.h (+18/-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:~jonas-drange/ubuntu-ui-extras/printerloader-thread-affinity
Reviewer Review Type Date Requested Status
Ubuntu Phablet Team Pending
Review via email: mp+320354@code.launchpad.net

Commit message

corrects thread affinity for printerloaded printers, as well as any qobject children it might have

Description of the change

corrects thread affinity for printerloaded printers, as well as any qobject children it might have

To post a comment you must log in.

Unmerged revisions

Preview Diff

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

Subscribers

People subscribed via source and target branches