Merge lp:~jonas-drange/ubuntu-settings-components/printer-add into lp:~phablet-team/ubuntu-settings-components/printer-components

Proposed by Jonas G. Drange
Status: Merged
Merged at revision: 221
Proposed branch: lp:~jonas-drange/ubuntu-settings-components/printer-add
Merge into: lp:~phablet-team/ubuntu-settings-components/printer-components
Diff against target: 1814 lines (+1229/-73)
21 files modified
examples/Printers.qml (+268/-3)
plugins/Ubuntu/Settings/Printers/CMakeLists.txt (+3/-0)
plugins/Ubuntu/Settings/Printers/backend/backend.cpp (+7/-3)
plugins/Ubuntu/Settings/Printers/backend/backend.h (+11/-3)
plugins/Ubuntu/Settings/Printers/backend/backend_cups.cpp (+15/-8)
plugins/Ubuntu/Settings/Printers/backend/backend_cups.h (+4/-3)
plugins/Ubuntu/Settings/Printers/cups/cupsfacade.cpp (+150/-14)
plugins/Ubuntu/Settings/Printers/cups/cupsfacade.h (+54/-4)
plugins/Ubuntu/Settings/Printers/cups/cupspkhelper.cpp (+153/-0)
plugins/Ubuntu/Settings/Printers/cups/cupspkhelper.h (+31/-6)
plugins/Ubuntu/Settings/Printers/models/drivermodel.cpp (+181/-0)
plugins/Ubuntu/Settings/Printers/models/drivermodel.h (+83/-0)
plugins/Ubuntu/Settings/Printers/plugin.cpp (+4/-1)
plugins/Ubuntu/Settings/Printers/printer/printerjob.cpp (+1/-0)
plugins/Ubuntu/Settings/Printers/printers/printers.cpp (+57/-15)
plugins/Ubuntu/Settings/Printers/printers/printers.h (+23/-10)
plugins/Ubuntu/Settings/Printers/structs.h (+18/-0)
tests/unittests/Printers/CMakeLists.txt (+4/-0)
tests/unittests/Printers/mockbackend.h (+17/-3)
tests/unittests/Printers/tst_drivermodel.cpp (+136/-0)
tests/unittests/Printers/tst_printers.cpp (+9/-0)
To merge this branch: bzr merge lp:~jonas-drange/ubuntu-settings-components/printer-add
Reviewer Review Type Date Requested Status
Andrew Hayzen (community) Approve
Review via email: mp+315692@code.launchpad.net

Commit message

* allows creation of printers in example qml, and by extension the API
* create printers by either providing a PPD file, or select a PPD from the database
* adds a DriverModel that holds printer drivers, which can be filtered
* adds testing of DriverModel

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

hides filter if providing PPD

228. By Jonas G. Drange

renames var to haystack, to fit with the narrative

229. By Jonas G. Drange

removes arbitrary size constraint on filter

230. By Jonas G. Drange

connects filterComplete to driversFilterChanged

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

This works by manually specifying the device URI, picking the right driver, setting a name.

In the future it'd be good to have network discovery, auto selection of drivers from an IP address etc

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'examples/Printers.qml'
2--- examples/Printers.qml 2017-01-22 18:34:20 +0000
3+++ examples/Printers.qml 2017-01-30 11:37:26 +0000
4@@ -35,8 +35,8 @@
5 visible: false
6 property var printer
7 header: PageHeader {
8- title: printer.name
9- flickable: printerFlickable
10+ title: printer.name
11+ flickable: printerFlickable
12 }
13
14 Flickable {
15@@ -153,6 +153,15 @@
16 header: PageHeader {
17 title: "Printers"
18 flickable: printerList
19+ trailingActionBar {
20+ actions: [
21+ Action {
22+ iconName: "add"
23+ text: "Add printer"
24+ onTriggered: pageStack.push(addPrinterPageComponent)
25+ }
26+ ]
27+ }
28 }
29 visible: false
30
31@@ -179,7 +188,263 @@
32 ProgressionSlot {}
33 }
34 onClicked: pageStack.push(printerPage, { printer: model })
35- Component.onCompleted: console.log("printer", model.name)
36+ }
37+ }
38+ }
39+ }
40+
41+ Component {
42+ id: addPrinterPageComponent
43+ Page {
44+ id: addPrinterPage
45+ states: [
46+ State {
47+ name: "success"
48+ PropertyChanges {
49+ target: okAction
50+ enabled: false
51+ }
52+ PropertyChanges {
53+ target: closeAction
54+ enabled: false
55+ }
56+ PropertyChanges {
57+ target: addPrinterCol
58+ enabled: false
59+ }
60+ StateChangeScript {
61+ script: okTimer.start()
62+ }
63+ },
64+ State {
65+ name: "failure"
66+ PropertyChanges {
67+ target: errorMessageContainer
68+ visible: true
69+ }
70+ }
71+ ]
72+ header: PageHeader {
73+ title: "Add printer"
74+ flickable: addPrinterFlickable
75+ leadingActionBar.actions: [
76+ Action {
77+ id: closeAction
78+ iconName: "close"
79+ text: "Abort"
80+ onTriggered: pageStack.pop()
81+ }
82+ ]
83+ trailingActionBar {
84+ actions: [
85+ Action {
86+ id: okAction
87+ iconName: "ok"
88+ text: "Complete"
89+ onTriggered: {
90+ var ret;
91+ if (driverSelector.selectedIndex == 0) {
92+ ret = Printers.addPrinter(
93+ printerName.text,
94+ driversView.selectedDriver,
95+ printerUri.text,
96+ printerDescription.text,
97+ printerLocation.text
98+ );
99+ } else {
100+ ret = Printers.addPrinterWithPpdFile(
101+ printerName.text,
102+ printerPpd.text,
103+ printerUri.text,
104+ printerDescription.text,
105+ printerLocation.text
106+ );
107+ }
108+ if (ret) {
109+ addPrinterPage.state = "success"
110+ } else {
111+ errorMessage.text = Printers.lastMessage;
112+ addPrinterPage.state = "failure"
113+ }
114+ }
115+ }
116+ ]
117+ }
118+ }
119+
120+ Component.onCompleted: {
121+ Printers.prepareToAddPrinter();
122+ }
123+
124+ Timer {
125+ id: okTimer
126+ interval: 2000
127+ onTriggered: pageStack.pop();
128+ }
129+
130+ Flickable {
131+ id: addPrinterFlickable
132+ anchors.fill: parent
133+
134+ Column {
135+ id: addPrinterCol
136+ property bool enabled: true
137+ anchors {
138+ left: parent.left
139+ right: parent.right
140+ }
141+
142+ Item {
143+ id: errorMessageContainer
144+ visible: false
145+ anchors {
146+ left: parent.left
147+ right: parent.right
148+ margins: units.gu(2)
149+ }
150+ height: units.gu(6)
151+ Label {
152+ id: errorMessage
153+ anchors {
154+ top: parent.top
155+ topMargin: units.gu(2)
156+ horizontalCenter: parent.horizontalCenter
157+ }
158+ }
159+
160+ }
161+
162+ ListItems.Standard {
163+ text: "Device URI"
164+ control: TextField {
165+ id: printerUri
166+ placeholderText: "ipp://server.local/my-queue"
167+ }
168+ enabled: parent.enabled
169+ }
170+
171+ ListItems.ValueSelector {
172+ id: driverSelector
173+ anchors {
174+ left: parent.left
175+ right: parent.right
176+ }
177+ text: "Choose driver"
178+ values: [
179+ "Select printer from database",
180+ "Provide PPD file"
181+ ]
182+ enabled: parent.enabled
183+ }
184+
185+ ListItems.Standard {
186+ anchors {
187+ left: parent.left
188+ right: parent.right
189+ }
190+ text: "Filter drivers"
191+ control: TextField {
192+ id: driverFilter
193+ onTextChanged: Printers.driverFilter = text
194+ }
195+ visible: driverSelector.selectedIndex == 0
196+ enabled: parent.enabled
197+ }
198+
199+ ListView {
200+ id: driversView
201+ property string selectedDriver
202+ property bool loading: true
203+ visible: driverSelector.selectedIndex == 0
204+ model: Printers.drivers
205+ anchors { left: parent.left; right: parent.right }
206+ height: units.gu(30)
207+ clip: true
208+ enabled: parent.enabled
209+ highlightFollowsCurrentItem: false
210+ highlight: Rectangle {
211+ z: 0
212+ y: driversView.currentItem.y
213+ width: driversView.currentItem.width
214+ height: driversView.currentItem.height
215+ color: theme.palette.selected.background
216+ }
217+ delegate: ListItem {
218+ height: driverLayout.height + (divider.visible ? divider.height : 0)
219+ ListItemLayout {
220+ id: driverLayout
221+ title.text: displayName
222+ subtitle.text: name
223+ summary.text: deviceId
224+ }
225+ onClicked: {
226+ driversView.selectedDriver = name
227+ driversView.currentIndex = index
228+ }
229+ }
230+
231+ ActivityIndicator {
232+ anchors.centerIn: parent
233+ running: parent.loading
234+ }
235+
236+ Connections {
237+ target: driversView
238+ onCountChanged: {
239+ target = null;
240+ driversView.loading = false;
241+ }
242+ }
243+ }
244+
245+ ListItems.Standard {
246+ text: "PPD File"
247+ visible: driverSelector.selectedIndex == 1
248+ control: TextField {
249+ id: printerPpd
250+ placeholderText: "/usr/share/cups/foo.ppd"
251+ }
252+ enabled: parent.enabled
253+ }
254+
255+ ListItems.Standard {
256+ anchors {
257+ left: parent.left
258+ right: parent.right
259+ }
260+ text: "Printer name"
261+ control: TextField {
262+ id: printerName
263+ placeholderText: "laserjet"
264+ }
265+ enabled: parent.enabled
266+ }
267+
268+ ListItems.Standard {
269+ anchors {
270+ left: parent.left
271+ right: parent.right
272+ }
273+ text: "Description (optional)"
274+ control: TextField {
275+ id: printerDescription
276+ placeholderText: "HP Laserjet with Duplexer"
277+ }
278+ enabled: parent.enabled
279+ }
280+
281+ ListItems.Standard {
282+ anchors {
283+ left: parent.left
284+ right: parent.right
285+ }
286+ text: "Location (optional)"
287+ control: TextField {
288+ id: printerLocation
289+ placeholderText: "Lab 1"
290+ }
291+ enabled: parent.enabled
292+ }
293 }
294 }
295 }
296
297=== modified file 'plugins/Ubuntu/Settings/Printers/CMakeLists.txt'
298--- plugins/Ubuntu/Settings/Printers/CMakeLists.txt 2017-01-24 12:34:23 +0000
299+++ plugins/Ubuntu/Settings/Printers/CMakeLists.txt 2017-01-30 11:37:26 +0000
300@@ -7,6 +7,7 @@
301 find_package(Qt5Gui REQUIRED)
302 find_package(Qt5PrintSupport REQUIRED)
303 find_package(Qt5Qml REQUIRED)
304+find_package(Qt5Concurrent REQUIRED)
305
306 add_library(UbuntuSettingsPrintersQml SHARED
307 backend/backend.cpp
308@@ -16,6 +17,7 @@
309 cups/cupspkhelper.cpp
310 enums.h
311 i18n.cpp
312+ models/drivermodel.cpp
313 models/printermodel.cpp
314 printer/printer.cpp
315 printer/printerjob.cpp
316@@ -29,6 +31,7 @@
317 Qt5::Gui
318 Qt5::PrintSupport
319 Qt5::Qml
320+ Qt5::Concurrent
321 ${CUPS_LIBRARIES}
322 )
323
324
325=== modified file 'plugins/Ubuntu/Settings/Printers/backend/backend.cpp'
326--- plugins/Ubuntu/Settings/Printers/backend/backend.cpp 2017-01-22 14:21:11 +0000
327+++ plugins/Ubuntu/Settings/Printers/backend/backend.cpp 2017-01-30 11:37:26 +0000
328@@ -37,8 +37,8 @@
329 }
330
331 QString PrinterBackend::printerAdd(const QString &name,
332- const QUrl &uri,
333- const QUrl &ppdFile,
334+ const QString &uri,
335+ const QString &ppdFile,
336 const QString &info,
337 const QString &location)
338 {
339@@ -46,7 +46,7 @@
340 }
341
342 QString PrinterBackend::printerAddWithPpd(const QString &name,
343- const QUrl &uri,
344+ const QString &uri,
345 const QString &ppdFileName,
346 const QString &info,
347 const QString &location)
348@@ -281,6 +281,10 @@
349 return QString();
350 }
351
352+void PrinterBackend::requestAvailablePrinterDrivers()
353+{
354+}
355+
356 PrinterBackend::BackendType PrinterBackend::backendType() const
357 {
358 return BackendType::DefaultType;
359
360=== modified file 'plugins/Ubuntu/Settings/Printers/backend/backend.h'
361--- plugins/Ubuntu/Settings/Printers/backend/backend.h 2017-01-23 14:06:49 +0000
362+++ plugins/Ubuntu/Settings/Printers/backend/backend.h 2017-01-30 11:37:26 +0000
363@@ -50,13 +50,16 @@
364
365 virtual bool holdsDefinition() const;
366
367+ // Add a printer using an already existing ppd.
368 virtual QString printerAdd(const QString &name,
369- const QUrl &uri,
370- const QUrl &ppdFile,
371+ const QString &uri,
372+ const QString &ppdFile,
373 const QString &info,
374 const QString &location);
375+
376+ // Add a printer and provide a ppd file.
377 virtual QString printerAddWithPpd(const QString &name,
378- const QUrl &uri,
379+ const QString &uri,
380 const QString &ppdFileName,
381 const QString &info,
382 const QString &location);
383@@ -135,6 +138,8 @@
384 virtual Printer* getPrinter(const QString &printerName);
385 virtual QString defaultPrinterName();
386
387+ virtual void requestAvailablePrinterDrivers();
388+
389 virtual BackendType backendType() const;
390
391 public Q_SLOTS:
392@@ -146,6 +151,9 @@
393 void printerDeleted(const QString &name);
394 void printerStateChanged(const QString &name);
395
396+ void printerDriversLoaded(const QList<PrinterDriver> &drivers);
397+ void printerDriversFailedToLoad(const QString &errorMessage);
398+
399 protected:
400 const QString m_printerName;
401 };
402
403=== modified file 'plugins/Ubuntu/Settings/Printers/backend/backend_cups.cpp'
404--- plugins/Ubuntu/Settings/Printers/backend/backend_cups.cpp 2017-01-24 12:42:52 +0000
405+++ plugins/Ubuntu/Settings/Printers/backend/backend_cups.cpp 2017-01-30 11:37:26 +0000
406@@ -19,14 +19,17 @@
407 #include "i18n.h"
408 #include "utils.h"
409
410-#include <exception>
411-#include <stdexcept>
412
413 PrinterCupsBackend::PrinterCupsBackend(QObject *parent)
414 : PrinterCupsBackend(new CupsFacade(), QPrinterInfo(), parent)
415 {
416 // If we create the CupsFacade, we're in charge of RAII.
417 m_cups->setParent(this);
418+
419+ connect(m_cups, SIGNAL(printerDriversLoaded(const QList<PrinterDriver>&)),
420+ this, SIGNAL(printerDriversLoaded(const QList<PrinterDriver>&)));
421+ connect(m_cups, SIGNAL(printerDriversFailedToLoad(const QString&)),
422+ this, SIGNAL(printerDriversFailedToLoad(const QString&)));
423 }
424
425 PrinterCupsBackend::PrinterCupsBackend(CupsFacade *cups, QPrinterInfo info,
426@@ -39,25 +42,24 @@
427
428 PrinterCupsBackend::~PrinterCupsBackend()
429 {
430-
431 }
432
433 QString PrinterCupsBackend::printerAdd(const QString &name,
434- const QUrl &uri,
435- const QUrl &ppdFile,
436+ const QString &uri,
437+ const QString &ppdFile,
438 const QString &info,
439 const QString &location)
440 {
441-
442+ return m_cups->printerAdd(name, uri, ppdFile, info, location);
443 }
444
445 QString PrinterCupsBackend::printerAddWithPpd(const QString &name,
446- const QUrl &uri,
447+ const QString &uri,
448 const QString &ppdFileName,
449 const QString &info,
450 const QString &location)
451 {
452-
453+ return m_cups->printerAddWithPpd(name, uri, ppdFileName, info, location);
454 }
455
456 bool PrinterCupsBackend::holdsDefinition() const
457@@ -312,6 +314,11 @@
458 return QPrinterInfo::defaultPrinterName();
459 }
460
461+void PrinterCupsBackend::requestAvailablePrinterDrivers()
462+{
463+ return m_cups->requestPrinterDrivers();
464+}
465+
466 PrinterBackend::BackendType PrinterCupsBackend::backendType() const
467 {
468 return PrinterBackend::BackendType::CupsType;
469
470=== modified file 'plugins/Ubuntu/Settings/Printers/backend/backend_cups.h'
471--- plugins/Ubuntu/Settings/Printers/backend/backend_cups.h 2017-01-23 14:06:49 +0000
472+++ plugins/Ubuntu/Settings/Printers/backend/backend_cups.h 2017-01-30 11:37:26 +0000
473@@ -33,12 +33,12 @@
474 virtual bool holdsDefinition() const override;
475
476 virtual QString printerAdd(const QString &name,
477- const QUrl &uri,
478- const QUrl &ppdFile,
479+ const QString &uri,
480+ const QString &ppdFile,
481 const QString &info,
482 const QString &location) override;
483 virtual QString printerAddWithPpd(const QString &name,
484- const QUrl &uri,
485+ const QString &uri,
486 const QString &ppdFileName,
487 const QString &info,
488 const QString &location) override;
489@@ -116,6 +116,7 @@
490 virtual QStringList availablePrinterNames() override;
491 virtual Printer* getPrinter(const QString &printerName) override;
492 virtual QString defaultPrinterName() override;
493+ virtual void requestAvailablePrinterDrivers() override;
494
495 virtual PrinterBackend::BackendType backendType() const override;
496
497
498=== modified file 'plugins/Ubuntu/Settings/Printers/cups/cupsfacade.cpp'
499--- plugins/Ubuntu/Settings/Printers/cups/cupsfacade.cpp 2017-01-25 15:37:42 +0000
500+++ plugins/Ubuntu/Settings/Printers/cups/cupsfacade.cpp 2017-01-30 11:37:26 +0000
501@@ -23,6 +23,7 @@
502 #include <cups/ppd.h>
503
504 #include <QDebug>
505+#include <QThread>
506
507 #define __CUPS_ADD_OPTION(dest, name, value) dest->num_options = \
508 cupsAddOption(name, value, dest->num_options, &dest->options);
509@@ -33,34 +34,30 @@
510
511 CupsFacade::~CupsFacade()
512 {
513-
514+ cancelPrinterDriverRequest();
515 }
516
517 QString CupsFacade::printerAdd(const QString &name,
518- const QUrl &uri,
519- const QUrl &ppdFile,
520+ const QString &uri,
521+ const QString &ppdFile,
522 const QString &info,
523 const QString &location)
524 {
525- Q_UNUSED(name);
526- Q_UNUSED(uri);
527- Q_UNUSED(ppdFile);
528- Q_UNUSED(info);
529- Q_UNUSED(location);
530+ if (!helper.printerAdd(name, uri, ppdFile, info, location)) {
531+ return helper.getLastError();
532+ }
533 return QString();
534 }
535
536 QString CupsFacade::printerAddWithPpd(const QString &name,
537- const QUrl &uri,
538+ const QString &uri,
539 const QString &ppdFileName,
540 const QString &info,
541 const QString &location)
542 {
543- Q_UNUSED(name);
544- Q_UNUSED(uri);
545- Q_UNUSED(ppdFileName);
546- Q_UNUSED(info);
547- Q_UNUSED(location);
548+ if (!helper.printerAddWithPpdFile(name, uri, ppdFileName, info, location)) {
549+ return helper.getLastError();
550+ }
551 return QString();
552 }
553
554@@ -377,3 +374,142 @@
555 dest->num_options,
556 dest->options);
557 }
558+
559+void CupsFacade::requestPrinterDrivers(
560+ const QString &deviceId, const QString &language, const QString &makeModel,
561+ const QString &product, const QStringList &includeSchemes,
562+ const QStringList &excludeSchemes
563+)
564+{
565+ auto thread = new QThread;
566+ auto loader = new PrinterDriverLoader(deviceId, language, makeModel,
567+ product, includeSchemes,
568+ excludeSchemes);
569+ loader->moveToThread(thread);
570+ connect(loader, SIGNAL(error(const QString&)),
571+ this, SIGNAL(printerDriversFailedToLoad(const QString&)));
572+ connect(this, SIGNAL(requestPrinterDriverCancel()), loader, SLOT(cancel()));
573+ connect(thread, SIGNAL(started()), loader, SLOT(process()));
574+ connect(loader, SIGNAL(finished()), thread, SLOT(quit()));
575+ connect(loader, SIGNAL(finished()), loader, SLOT(deleteLater()));
576+ connect(loader, SIGNAL(loaded(const QList<PrinterDriver>&)),
577+ this, SIGNAL(printerDriversLoaded(const QList<PrinterDriver>&)));
578+ connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
579+ thread->start();
580+ }
581+
582+void CupsFacade::cancelPrinterDriverRequest()
583+{
584+ Q_EMIT requestPrinterDriverCancel();
585+}
586+
587+PrinterDriverLoader::PrinterDriverLoader(
588+ const QString &deviceId, const QString &language,
589+ const QString &makeModel, const QString &product,
590+ const QStringList &includeSchemes, const QStringList &excludeSchemes)
591+ : m_deviceId(deviceId)
592+ , m_language(language)
593+ , m_makeModel(makeModel)
594+ , m_product(product)
595+ , m_includeSchemes(includeSchemes)
596+ , m_excludeSchemes(excludeSchemes)
597+{
598+}
599+
600+PrinterDriverLoader::~PrinterDriverLoader()
601+{
602+}
603+
604+void PrinterDriverLoader::process()
605+{
606+ m_running = true;
607+
608+ ipp_t* response = helper.createPrinterDriversRequest(
609+ m_deviceId, m_language, m_makeModel, m_product, m_includeSchemes,
610+ m_excludeSchemes
611+ );
612+
613+ // Note: if the response somehow fails, we return.
614+ if (!response || ippGetStatusCode(response) > IPP_OK_CONFLICT) {
615+ QString err(cupsLastErrorString());
616+ qWarning() << __PRETTY_FUNCTION__ << "Cups HTTP error:" << err;
617+
618+ if (response)
619+ ippDelete(response);
620+
621+ Q_EMIT error(err);
622+ Q_EMIT finished();
623+ return;
624+ }
625+
626+ ipp_attribute_t *attr;
627+ QByteArray ppdDeviceId;
628+ QByteArray ppdLanguage;
629+ QByteArray ppdMakeModel;
630+ QByteArray ppdName;
631+
632+ // cups_option_t option;
633+ QList<PrinterDriver> drivers;
634+
635+ for (attr = ippFirstAttribute(response); attr != NULL && m_running; attr = ippNextAttribute(response)) {
636+
637+ while (attr != NULL && ippGetGroupTag(attr) != IPP_TAG_PRINTER)
638+ attr = ippNextAttribute(response);
639+
640+ if (attr == NULL)
641+ break;
642+
643+ // Pull the needed attributes from this PPD...
644+ ppdDeviceId = "NONE";
645+ ppdLanguage.clear();
646+ ppdMakeModel.clear();
647+ ppdName.clear();
648+
649+ while (attr != NULL && ippGetGroupTag(attr) == IPP_TAG_PRINTER) {
650+ if (!strcmp(ippGetName(attr), "ppd-device-id") &&
651+ ippGetValueTag(attr) == IPP_TAG_TEXT) {
652+ ppdDeviceId = ippGetString(attr, 0, NULL);
653+ } else if (!strcmp(ippGetName(attr), "ppd-natural-language") &&
654+ ippGetValueTag(attr) == IPP_TAG_LANGUAGE) {
655+ ppdLanguage = ippGetString(attr, 0, NULL);
656+
657+ } else if (!strcmp(ippGetName(attr), "ppd-make-and-model") &&
658+ ippGetValueTag(attr) == IPP_TAG_TEXT) {
659+ ppdMakeModel = ippGetString(attr, 0, NULL);
660+ } else if (!strcmp(ippGetName(attr), "ppd-name") &&
661+ ippGetValueTag(attr) == IPP_TAG_NAME) {
662+
663+ ppdName = ippGetString(attr, 0, NULL);
664+ }
665+
666+ attr = ippNextAttribute(response);
667+ }
668+
669+ // See if we have everything needed...
670+ if (ppdLanguage.isEmpty() || ppdMakeModel.isEmpty() ||
671+ ppdName.isEmpty()) {
672+ if (attr == NULL)
673+ break;
674+ else
675+ continue;
676+ }
677+
678+ PrinterDriver m;
679+ m.name = ppdName;
680+ m.deviceId = ppdDeviceId;
681+ m.makeModel = ppdMakeModel;
682+ m.language = ppdLanguage;
683+
684+ drivers.append(m);
685+ }
686+
687+ ippDelete(response);
688+
689+ Q_EMIT loaded(drivers);
690+ Q_EMIT finished();
691+}
692+
693+void PrinterDriverLoader::cancel()
694+{
695+ m_running = false;
696+}
697
698=== modified file 'plugins/Ubuntu/Settings/Printers/cups/cupsfacade.h'
699--- plugins/Ubuntu/Settings/Printers/cups/cupsfacade.h 2017-01-22 14:21:11 +0000
700+++ plugins/Ubuntu/Settings/Printers/cups/cupsfacade.h 2017-01-30 11:37:26 +0000
701@@ -30,7 +30,6 @@
702 #include <QObject>
703 #include <QString>
704 #include <QStringList>
705-#include <QUrl>
706 #include <QVariant>
707
708 class PrinterJob;
709@@ -41,12 +40,12 @@
710 explicit CupsFacade(QObject *parent = Q_NULLPTR);
711 ~CupsFacade();
712 QString printerAdd(const QString &name,
713- const QUrl &uri,
714- const QUrl &ppdFile,
715+ const QString &uri,
716+ const QString &ppdFile,
717 const QString &info,
718 const QString &location);
719 QString printerAddWithPpd(const QString &name,
720- const QUrl &uri,
721+ const QString &uri,
722 const QString &ppdFileName,
723 const QString &info,
724 const QString &location);
725@@ -90,12 +89,28 @@
726 QList<PrintQuality> printerGetSupportedQualities(const QString &name) const;
727 int printFileToDest(const QString &filepath, const QString &title,
728 const cups_dest_t *dest);
729+
730+public Q_SLOTS:
731+ void requestPrinterDrivers(
732+ const QString &deviceId = "",
733+ const QString &language = "",
734+ const QString &makeModel = "",
735+ const QString &product = "",
736+ const QStringList &includeSchemes = QStringList(),
737+ const QStringList &excludeSchemes = QStringList()
738+ );
739+ void cancelPrinterDriverRequest();
740+
741 Q_SIGNALS:
742 void printerAdded(const QString &name);
743 void printerModified(const QString &name, const bool ppdChanged);
744 void printerDeleted(const QString &name);
745 void printerStateChanged(const QString &name);
746
747+ void requestPrinterDriverCancel();
748+ void printerDriversLoaded(const QList<PrinterDriver> &drivers);
749+ void printerDriversFailedToLoad(const QString &errorMessage);
750+
751 private:
752 QString getPrinterName(const QString &name) const;
753 QString getPrinterInstance(const QString &name) const;
754@@ -107,4 +122,39 @@
755 CupsPkHelper helper;
756 };
757
758+class PrinterDriverLoader : public QObject
759+{
760+ Q_OBJECT
761+public:
762+ PrinterDriverLoader(
763+ const QString &deviceId = "",
764+ const QString &language = "",
765+ const QString &makeModel = "",
766+ const QString &product = "",
767+ const QStringList &includeSchemes = QStringList(),
768+ const QStringList &excludeSchemes = QStringList());
769+ ~PrinterDriverLoader();
770+
771+public Q_SLOTS:
772+ void process();
773+ void cancel();
774+
775+Q_SIGNALS:
776+ void finished();
777+ void loaded(const QList<PrinterDriver> &drivers);
778+ void error(const QString &error);
779+
780+private:
781+ QString m_deviceId = QString::null;
782+ QString m_language = QString::null;
783+ QString m_makeModel = QString::null;
784+ QString m_product = QString::null;
785+ QStringList m_includeSchemes;
786+ QStringList m_excludeSchemes;
787+
788+ bool m_running = false;
789+ CupsPkHelper helper;
790+};
791+
792+
793 #endif // USC_PRINTERS_CUPSFACADE_H
794
795=== modified file 'plugins/Ubuntu/Settings/Printers/cups/cupspkhelper.cpp'
796--- plugins/Ubuntu/Settings/Printers/cups/cupspkhelper.cpp 2017-01-17 15:14:25 +0000
797+++ plugins/Ubuntu/Settings/Printers/cups/cupspkhelper.cpp 2017-01-30 11:37:26 +0000
798@@ -44,6 +44,127 @@
799 httpClose(m_connection);
800 }
801
802+bool CupsPkHelper::printerAdd(const QString &printerName,
803+ const QString &printerUri,
804+ const QString &ppdFile,
805+ const QString &info,
806+ const QString &location)
807+{
808+ ipp_t *request;
809+
810+ if (!isPrinterNameValid(printerName)) {
811+ setInternalStatus(QString("%1 is not a valid printer name.").arg(printerName));
812+ return false;
813+ }
814+
815+ if (!isStringValid(info)) {
816+ setInternalStatus(QString("%1 is not a valid description.").arg(info));
817+ return false;
818+ }
819+
820+ if (!isStringValid(location)) {
821+ setInternalStatus(QString("%1 is not a valid location.").arg(location));
822+ return false;
823+ }
824+
825+ if (!isStringValid(ppdFile)) {
826+ setInternalStatus(QString("%1 is not a valid ppd file.").arg(ppdFile));
827+ return false;
828+ }
829+
830+ if (!isStringValid(printerUri)) {
831+ setInternalStatus(QString("%1 is not a valid printer uri.").arg(printerUri));
832+ return false;
833+ }
834+
835+
836+ request = ippNewRequest (CUPS_ADD_MODIFY_PRINTER);
837+ addPrinterUri(request, printerName);
838+ addRequestingUsername(request, NULL);
839+
840+ ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_NAME,
841+ "printer-name", NULL, printerName.toUtf8());
842+
843+ if (!ppdFile.isEmpty()) {
844+ ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_NAME,
845+ "ppd-name", NULL, ppdFile.toUtf8());
846+ }
847+ if (!printerUri.isEmpty()) {
848+ ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_URI,
849+ "device-uri", NULL, printerUri.toUtf8());
850+ }
851+ if (!info.isEmpty()) {
852+ ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_TEXT,
853+ "printer-info", NULL, info.toUtf8());
854+ }
855+ if (!location.isEmpty()) {
856+ ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_TEXT,
857+ "printer-location", NULL, location.toUtf8());
858+ }
859+
860+ return sendRequest(request, CphResourceAdmin);
861+}
862+
863+bool CupsPkHelper::printerAddWithPpdFile(const QString &printerName,
864+ const QString &printerUri,
865+ const QString &ppdFileName,
866+ const QString &info,
867+ const QString &location)
868+{
869+ ipp_t *request;
870+
871+ if (!isPrinterNameValid(printerName)) {
872+ setInternalStatus(QString("%1 is not a valid printer name.").arg(printerName));
873+ return false;
874+ }
875+
876+ if (!isStringValid(info)) {
877+ setInternalStatus(QString("%1 is not a valid description.").arg(info));
878+ return false;
879+ }
880+
881+ if (!isStringValid(location)) {
882+ setInternalStatus(QString("%1 is not a valid location.").arg(location));
883+ return false;
884+ }
885+
886+ if (!isStringValid(ppdFileName)) {
887+ setInternalStatus(QString("%1 is not a valid ppd file name.").arg(ppdFileName));
888+ return false;
889+ }
890+
891+ if (!isStringValid(printerUri)) {
892+ setInternalStatus(QString("%1 is not a valid printer uri.").arg(printerUri));
893+ return false;
894+ }
895+
896+ request = ippNewRequest(CUPS_ADD_MODIFY_PRINTER);
897+ addPrinterUri(request, printerName);
898+ addRequestingUsername(request, NULL);
899+
900+ ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_NAME,
901+ "printer-name", NULL, printerName.toUtf8());
902+
903+ /* In this specific case of ADD_MODIFY, the URI can be NULL/empty since
904+ * we provide a complete PPD. And cups fails if we pass an empty
905+ * string. */
906+ if (!printerUri.isEmpty()) {
907+ ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_URI,
908+ "device-uri", NULL, printerUri.toUtf8());
909+ }
910+
911+ if (!info.isEmpty()) {
912+ ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_TEXT,
913+ "printer-info", NULL, info.toUtf8());
914+ }
915+ if (!location.isEmpty()) {
916+ ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_TEXT,
917+ "printer-location", NULL, location.toUtf8());
918+ }
919+
920+ return postRequest(request, ppdFileName.toUtf8(), CphResourceAdmin);
921+}
922+
923 bool CupsPkHelper::printerClassSetInfo(const QString &name,
924 const QString &info)
925 {
926@@ -582,3 +703,35 @@
927 instance.toUtf8());
928 return dest;
929 }
930+
931+ipp_t* CupsPkHelper::createPrinterDriversRequest(
932+ const QString &deviceId, const QString &language, const QString &makeModel,
933+ const QString &product, const QStringList &includeSchemes,
934+ const QStringList &excludeSchemes
935+)
936+{
937+ Q_UNUSED(includeSchemes);
938+ Q_UNUSED(excludeSchemes);
939+
940+ ipp_t *request;
941+
942+ request = ippNewRequest(CUPS_GET_PPDS);
943+
944+ if (!deviceId.isEmpty())
945+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_TEXT, "ppd-device-id",
946+ NULL, deviceId.toUtf8());
947+ if (!language.isEmpty())
948+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE, "ppd-language",
949+ NULL, language.toUtf8());
950+ if (!makeModel.isEmpty())
951+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_TEXT, "ppd-make-and-model",
952+ NULL, makeModel.toUtf8());
953+ if (!product.isEmpty())
954+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_TEXT, "ppd-product",
955+ NULL, product.toUtf8());
956+
957+ // Do the request and get return the response.
958+ const QString resourceChar = getResource(CphResourceRoot);
959+ return cupsDoRequest(m_connection, request,
960+ resourceChar.toUtf8());
961+}
962
963=== modified file 'plugins/Ubuntu/Settings/Printers/cups/cupspkhelper.h'
964--- plugins/Ubuntu/Settings/Printers/cups/cupspkhelper.h 2017-01-17 15:14:25 +0000
965+++ plugins/Ubuntu/Settings/Printers/cups/cupspkhelper.h 2017-01-30 11:37:26 +0000
966@@ -14,12 +14,16 @@
967 * along with this program. If not, see <http://www.gnu.org/licenses/>.
968 */
969
970+#ifndef USC_PRINTERS_CUPSPKHELPER_H
971+#define USC_PRINTERS_CUPSPKHELPER_H
972+
973+#include "structs.h"
974+
975 #include <cups/cups.h>
976 #include <cups/http.h>
977 #include <cups/ipp.h>
978 #include <cups/ppd.h>
979
980-
981 #include <QString>
982 #include <QStringList>
983
984@@ -31,11 +35,10 @@
985
986 /* This code is only a shim for systems not running the daemon provided by
987 cups-pk-helper. Once provided on all platforms, this code should be replaced
988-by proper dbus bindings, and subsequently be set on fire. */
989+by proper dbus bindings, and subsequently be set on fire.
990
991-/* TODO: rename to CupsPkHelperShim to emphasize its transient nature.
992- FIXME: set m_internalStatus to mutable and make most of the "is..." methods
993- const.
994+TODO: rename to CupsPkHelperShim to emphasize its transient nature.
995+FIXME: make most of the "is..." methods const.
996 */
997 class CupsPkHelper
998 {
999@@ -43,6 +46,16 @@
1000 explicit CupsPkHelper();
1001 ~CupsPkHelper();
1002
1003+ bool printerAdd(const QString &printerName,
1004+ const QString &printerUri,
1005+ const QString &ppdFile,
1006+ const QString &info,
1007+ const QString &location);
1008+ bool printerAddWithPpdFile(const QString &printerName,
1009+ const QString &printerUri,
1010+ const QString &ppdFileName,
1011+ const QString &info,
1012+ const QString &location);
1013 bool printerClassSetInfo(const QString &name, const QString &info);
1014 bool printerClassSetOption(const QString &name, const QString &option,
1015 const QStringList &values);
1016@@ -51,6 +64,16 @@
1017
1018 QString getLastError() const;
1019
1020+ // This response needs to be free by the caller.
1021+ ipp_t* createPrinterDriversRequest(
1022+ const QString &deviceId = "",
1023+ const QString &language = "",
1024+ const QString &makeModel = "",
1025+ const QString &product = "",
1026+ const QStringList &includeSchemes = QStringList(),
1027+ const QStringList &excludeSchemes = QStringList()
1028+ );
1029+
1030 private:
1031 enum CphResource
1032 {
1033@@ -74,7 +97,6 @@
1034 const int maxLength = 512);
1035 static bool isStringPrintable(const QString &string, const bool checkNull,
1036 const int maxLength);
1037-
1038 QString preparePpdForOptions(const QString &ppdfile,
1039 cups_option_t *options,
1040 int numOptions);
1041@@ -91,3 +113,6 @@
1042 ipp_status_t m_lastStatus = IPP_OK;
1043 mutable QString m_internalStatus = QString::null;
1044 };
1045+
1046+
1047+#endif // USC_PRINTERS_CUPSPKHELPER_H
1048
1049=== added file 'plugins/Ubuntu/Settings/Printers/models/drivermodel.cpp'
1050--- plugins/Ubuntu/Settings/Printers/models/drivermodel.cpp 1970-01-01 00:00:00 +0000
1051+++ plugins/Ubuntu/Settings/Printers/models/drivermodel.cpp 2017-01-30 11:37:26 +0000
1052@@ -0,0 +1,181 @@
1053+/*
1054+ * Copyright (C) 2017 Canonical, Ltd.
1055+ *
1056+ * This program is free software; you can redistribute it and/or modify
1057+ * it under the terms of the GNU Lesser General Public License as published by
1058+ * the Free Software Foundation; version 3.
1059+ *
1060+ * This program is distributed in the hope that it will be useful,
1061+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1062+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1063+ * GNU Lesser General Public License for more details.
1064+ *
1065+ * You should have received a copy of the GNU Lesser General Public License
1066+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1067+ */
1068+
1069+#include "backend/backend_cups.h"
1070+#include "cups/cupsfacade.h"
1071+#include "models/drivermodel.h"
1072+
1073+#include <QDebug>
1074+#include <QtConcurrent>
1075+
1076+DriverModel::DriverModel(QObject *parent)
1077+ : DriverModel(new PrinterCupsBackend, parent)
1078+{
1079+}
1080+
1081+DriverModel::DriverModel(PrinterBackend *backend, QObject *parent)
1082+ : QAbstractListModel(parent)
1083+ , m_backend(backend)
1084+{
1085+ connect(m_backend, SIGNAL(printerDriversLoaded(const QList<PrinterDriver>&)),
1086+ this, SLOT(printerDriversLoaded(const QList<PrinterDriver>&)));
1087+
1088+ QObject::connect(&m_watcher,
1089+ &QFutureWatcher<PrinterDriver>::finished,
1090+ this,
1091+ &DriverModel::filterFinished);
1092+
1093+}
1094+
1095+DriverModel::~DriverModel()
1096+{
1097+ cancel();
1098+}
1099+
1100+int DriverModel::rowCount(const QModelIndex &parent) const
1101+{
1102+ Q_UNUSED(parent);
1103+ return m_drivers.size();
1104+}
1105+
1106+int DriverModel::count() const
1107+{
1108+ return rowCount();
1109+}
1110+
1111+QVariant DriverModel::data(const QModelIndex &index, int role) const
1112+{
1113+ QVariant ret;
1114+
1115+ if ((0 <= index.row()) && (index.row() < m_drivers.size())) {
1116+
1117+ auto driver = m_drivers[index.row()];
1118+
1119+ switch (role) {
1120+ case Qt::DisplayRole:
1121+ ret = driver.toString();
1122+ break;
1123+ case NameRole:
1124+ ret = driver.name;
1125+ break;
1126+ case DeviceIdRole:
1127+ ret = driver.deviceId;
1128+ break;
1129+ case LanguageRole:
1130+ ret = driver.language;
1131+ break;
1132+ case MakeModelRole:
1133+ ret = driver.makeModel;
1134+ break;
1135+ }
1136+ }
1137+
1138+ return ret;
1139+}
1140+
1141+QHash<int, QByteArray> DriverModel::roleNames() const
1142+{
1143+ static QHash<int,QByteArray> names;
1144+
1145+ if (Q_UNLIKELY(names.empty())) {
1146+ names[Qt::DisplayRole] = "displayName";
1147+ names[NameRole] = "name";
1148+ names[DeviceIdRole] = "deviceId";
1149+ names[LanguageRole] = "language";
1150+ names[MakeModelRole] = "makeModel";
1151+ }
1152+
1153+ return names;
1154+}
1155+
1156+void DriverModel::setFilter(const QString& pattern)
1157+{
1158+ QList<QByteArray> needles;
1159+ Q_FOREACH(const QString patternPart, pattern.toLower().split(" ")) {
1160+ needles.append(patternPart.toUtf8());
1161+ }
1162+ QList<PrinterDriver> list;
1163+
1164+ if (m_watcher.isRunning())
1165+ m_watcher.cancel();
1166+
1167+ if (pattern.isEmpty()) {
1168+ setModel(m_originalDrivers);
1169+ m_filter = pattern;
1170+ return;
1171+ }
1172+
1173+ if (!m_filter.isEmpty() && !m_drivers.isEmpty() &&
1174+ pattern.startsWith(m_filter))
1175+ list = m_drivers; // search in the smaller list
1176+ else
1177+ list = m_originalDrivers; //search in the whole list
1178+
1179+ m_filter = pattern;
1180+
1181+ QFuture<PrinterDriver> future(QtConcurrent::filtered(list,
1182+ [needles] (const PrinterDriver &driver) {
1183+ QByteArray haystack = driver.makeModel.toLower();
1184+ Q_FOREACH(const QByteArray needle, needles) {
1185+ if (!haystack.contains(needle)) {
1186+ return false;
1187+ }
1188+ }
1189+ return true;
1190+ }
1191+ )
1192+ );
1193+
1194+ Q_EMIT filterBegin();
1195+
1196+ m_watcher.setFuture(future);
1197+}
1198+
1199+QString DriverModel::filter() const
1200+{
1201+ return m_filter;
1202+}
1203+
1204+void DriverModel::filterFinished()
1205+{
1206+ setModel(m_watcher.future().results());
1207+}
1208+
1209+void DriverModel::load()
1210+{
1211+ m_backend->requestAvailablePrinterDrivers();
1212+}
1213+
1214+void DriverModel::cancel()
1215+{
1216+ if (m_watcher.isRunning())
1217+ m_watcher.cancel();
1218+}
1219+
1220+void DriverModel::printerDriversLoaded(const QList<PrinterDriver> &drivers)
1221+{
1222+ m_originalDrivers = drivers;
1223+ setModel(m_originalDrivers);
1224+}
1225+
1226+void DriverModel::setModel(const QList<PrinterDriver> &drivers)
1227+{
1228+ beginResetModel();
1229+ m_drivers = drivers;
1230+ endResetModel();
1231+
1232+ Q_EMIT filterComplete();
1233+}
1234
1235=== added file 'plugins/Ubuntu/Settings/Printers/models/drivermodel.h'
1236--- plugins/Ubuntu/Settings/Printers/models/drivermodel.h 1970-01-01 00:00:00 +0000
1237+++ plugins/Ubuntu/Settings/Printers/models/drivermodel.h 2017-01-30 11:37:26 +0000
1238@@ -0,0 +1,83 @@
1239+/*
1240+ * Copyright (C) 2017 Canonical, Ltd.
1241+ *
1242+ * This program is free software; you can redistribute it and/or modify
1243+ * it under the terms of the GNU Lesser General Public License as published by
1244+ * the Free Software Foundation; version 3.
1245+ *
1246+ * This program is distributed in the hope that it will be useful,
1247+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1248+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1249+ * GNU Lesser General Public License for more details.
1250+ *
1251+ * You should have received a copy of the GNU Lesser General Public License
1252+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1253+ */
1254+
1255+#ifndef USC_PRINTER_DRIVERMODEL_H
1256+#define USC_PRINTER_DRIVERMODEL_H
1257+
1258+#include "printers_global.h"
1259+
1260+#include "structs.h"
1261+
1262+#include <QAbstractListModel>
1263+#include <QFutureWatcher>
1264+#include <QModelIndex>
1265+#include <QObject>
1266+#include <QVariant>
1267+
1268+class PRINTERS_DECL_EXPORT DriverModel : public QAbstractListModel
1269+{
1270+ Q_OBJECT
1271+ Q_PROPERTY(int count READ count NOTIFY countChanged)
1272+public:
1273+ explicit DriverModel(QObject *parent = Q_NULLPTR);
1274+ explicit DriverModel(PrinterBackend *backend, QObject *parent = Q_NULLPTR);
1275+ ~DriverModel();
1276+
1277+ enum Roles
1278+ {
1279+ // Qt::DisplayRole holds driver name
1280+ NameRole = Qt::UserRole,
1281+ DeviceIdRole,
1282+ LanguageRole,
1283+ MakeModelRole,
1284+ LastRole = MakeModelRole,
1285+ };
1286+
1287+ virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override;
1288+ virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
1289+ virtual QHash<int, QByteArray> roleNames() const override;
1290+
1291+ int count() const;
1292+
1293+ QString filter() const;
1294+ void setFilter(const QString& pattern);
1295+
1296+public Q_SLOTS:
1297+ // Start loading the model.
1298+ void load();
1299+
1300+ // Cancel loading of the model.
1301+ void cancel();
1302+
1303+private Q_SLOTS:
1304+ void printerDriversLoaded(const QList<PrinterDriver> &drivers);
1305+ void filterFinished();
1306+
1307+Q_SIGNALS:
1308+ void countChanged();
1309+ void filterBegin();
1310+ void filterComplete();
1311+
1312+private:
1313+ void setModel(const QList<PrinterDriver> &drivers);
1314+ PrinterBackend *m_backend;
1315+ QList<PrinterDriver> m_drivers;
1316+ QList<PrinterDriver> m_originalDrivers;
1317+ QString m_filter;
1318+ QFutureWatcher<PrinterDriver> m_watcher;
1319+};
1320+
1321+#endif // USC_PRINTER_DRIVERMODEL_H
1322
1323=== modified file 'plugins/Ubuntu/Settings/Printers/plugin.cpp'
1324--- plugins/Ubuntu/Settings/Printers/plugin.cpp 2017-01-24 12:34:23 +0000
1325+++ plugins/Ubuntu/Settings/Printers/plugin.cpp 2017-01-30 11:37:26 +0000
1326@@ -18,13 +18,15 @@
1327
1328 #include "enums.h"
1329 #include "i18n.h"
1330+#include "structs.h"
1331+
1332 // #include "models/printermodel.h"
1333 #include "printer/printer.h"
1334 #include "printer/printerjob.h"
1335 #include "printers/printers.h"
1336
1337 #include <QtQml/qqml.h>
1338-#include <QSharedPointer>
1339+#include <QList>
1340
1341 #define I18N_DOMAIN "ubuntu-settings-components"
1342
1343@@ -49,4 +51,5 @@
1344 qmlRegisterType<PrinterJob>(uri, 0, 1, "PrinterJob");
1345
1346 qmlRegisterUncreatableType<PrinterEnum>(uri, 0, 1, "PrinterEnum", "Is an enum");
1347+ qRegisterMetaType<QList<PrinterDriver>>("QList<PrinterDriver>");
1348 }
1349
1350=== modified file 'plugins/Ubuntu/Settings/Printers/printer/printerjob.cpp'
1351--- plugins/Ubuntu/Settings/Printers/printer/printerjob.cpp 2017-01-26 16:19:54 +0000
1352+++ plugins/Ubuntu/Settings/Printers/printer/printerjob.cpp 2017-01-30 11:37:26 +0000
1353@@ -15,6 +15,7 @@
1354 */
1355
1356 #include <QtCore/QDebug>
1357+#include <QUrl>
1358
1359 #include "backend/backend_cups.h"
1360 #include "models/printermodel.h"
1361
1362=== modified file 'plugins/Ubuntu/Settings/Printers/printers/printers.cpp'
1363--- plugins/Ubuntu/Settings/Printers/printers/printers.cpp 2017-01-22 19:31:39 +0000
1364+++ plugins/Ubuntu/Settings/Printers/printers/printers.cpp 2017-01-30 11:37:26 +0000
1365@@ -28,6 +28,7 @@
1366 QObject *parent)
1367 : QObject(parent)
1368 , m_backend(backend)
1369+ , m_drivers(backend)
1370 , m_model(backend, printerUpdateIntervalMSecs)
1371 {
1372 m_allPrinters.setSourceModel(&m_model);
1373@@ -41,6 +42,9 @@
1374
1375 // Let Qt be in charge of RAII.
1376 m_backend->setParent(this);
1377+
1378+ connect(&m_drivers, SIGNAL(filterComplete()),
1379+ this, SIGNAL(driverFilterChanged()));
1380 }
1381
1382 Printers::~Printers()
1383@@ -71,11 +75,33 @@
1384
1385 }
1386
1387+QAbstractItemModel* Printers::drivers()
1388+{
1389+ auto ret = &m_drivers;
1390+ QQmlEngine::setObjectOwnership(ret, QQmlEngine::CppOwnership);
1391+ return ret;
1392+}
1393+
1394+QString Printers::driverFilter() const
1395+{
1396+ return m_drivers.filter();
1397+}
1398+
1399+void Printers::setDriverFilter(const QString &filter)
1400+{
1401+ m_drivers.setFilter(filter);
1402+}
1403+
1404 QString Printers::defaultPrinterName() const
1405 {
1406
1407 }
1408
1409+QString Printers::lastMessage() const
1410+{
1411+ return m_lastMessage;
1412+}
1413+
1414 void Printers::setDefaultPrinterName(const QString &name)
1415 {
1416
1417@@ -90,21 +116,37 @@
1418
1419 }
1420
1421-QSharedPointer<Printer> Printers::addPrinter(const QString &name,
1422- const QUrl &ppd,
1423- const QUrl &device,
1424- const QString &description,
1425- const QString &location)
1426-{
1427-
1428-}
1429-
1430-QSharedPointer<Printer> Printers::addPrinter(const QString &name,
1431- const QUrl &device,
1432- const QString &description,
1433- const QString &location)
1434-{
1435-
1436+void Printers::prepareToAddPrinter()
1437+{
1438+ m_drivers.load();
1439+}
1440+
1441+bool Printers::addPrinter(const QString &name, const QString &ppd,
1442+ const QString &device, const QString &description,
1443+ const QString &location)
1444+{
1445+ QString reply = m_backend->printerAdd(name, device, ppd, description,
1446+ location);
1447+ if (!reply.isEmpty()) {
1448+ m_lastMessage = reply;
1449+ return false;
1450+ }
1451+ return true;
1452+}
1453+
1454+bool Printers::addPrinterWithPpdFile(const QString &name,
1455+ const QString &ppdFileName,
1456+ const QString &device,
1457+ const QString &description,
1458+ const QString &location)
1459+{
1460+ QString reply = m_backend->printerAddWithPpd(name, device, ppdFileName,
1461+ description, location);
1462+ if (!reply.isEmpty()) {
1463+ m_lastMessage = reply;
1464+ return false;
1465+ }
1466+ return true;
1467 }
1468
1469 bool Printers::removePrinter(const QString &name)
1470
1471=== modified file 'plugins/Ubuntu/Settings/Printers/printers/printers.h'
1472--- plugins/Ubuntu/Settings/Printers/printers/printers.h 2017-01-22 19:31:39 +0000
1473+++ plugins/Ubuntu/Settings/Printers/printers/printers.h 2017-01-30 11:37:26 +0000
1474@@ -20,6 +20,7 @@
1475 #include "printers_global.h"
1476
1477 #include "cups/cupsfacade.h"
1478+#include "models/drivermodel.h"
1479 #include "models/printermodel.h"
1480 #include "printer/printer.h"
1481
1482@@ -28,7 +29,6 @@
1483 #include <QScopedPointer>
1484 #include <QSharedPointer>
1485 #include <QString>
1486-#include <QUrl>
1487
1488 class PRINTERS_DECL_EXPORT Printers : public QObject
1489 {
1490@@ -37,7 +37,10 @@
1491 Q_PROPERTY(QAbstractItemModel* allPrintersWithPdf READ allPrintersWithPdf CONSTANT)
1492 Q_PROPERTY(QAbstractItemModel* recentPrinters READ recentPrinters CONSTANT)
1493 Q_PROPERTY(QAbstractItemModel* printJobs READ printJobs CONSTANT)
1494+ Q_PROPERTY(QAbstractItemModel* drivers READ drivers CONSTANT)
1495+ Q_PROPERTY (QString driverFilter READ driverFilter WRITE setDriverFilter NOTIFY driverFilterChanged)
1496 Q_PROPERTY(QString defaultPrinterName READ defaultPrinterName WRITE setDefaultPrinterName NOTIFY defaultPrinterNameChanged)
1497+ Q_PROPERTY(QString lastMessage READ lastMessage CONSTANT)
1498
1499 public:
1500 explicit Printers(int printerUpdateIntervalMSecs = 5000, QObject *parent = nullptr);
1501@@ -52,36 +55,46 @@
1502 QAbstractItemModel* allPrintersWithPdf();
1503 QAbstractItemModel* recentPrinters();
1504 QAbstractItemModel* printJobs();
1505+ QAbstractItemModel* drivers();
1506+ QString driverFilter() const;
1507 QString defaultPrinterName() const;
1508+ QString lastMessage() const;
1509
1510 void setDefaultPrinterName(const QString &name);
1511+ void setDriverFilter(const QString &filter);
1512
1513 public Q_SLOTS:
1514 QSharedPointer<Printer> getPrinterByName(const QString &name);
1515 QSharedPointer<Printer> getJobOwner(const int &jobId);
1516
1517- QSharedPointer<Printer> addPrinter(const QString &name,
1518- const QUrl &ppd,
1519- const QUrl &device,
1520- const QString &description,
1521- const QString &location);
1522+ /* Instructs us to start loading drivers and what have you. In most cases,
1523+ the user is likely to merely configure existing printers/jobs. Loading
1524+ (at least) 12.000 drivers isn't relevant to those scenarios, so in order to
1525+ add printers, this method should be called first. */
1526+ void prepareToAddPrinter();
1527
1528- QSharedPointer<Printer> addPrinter(const QString &name,
1529- const QUrl &device,
1530- const QString &description,
1531- const QString &location);
1532+ bool addPrinter(const QString &name, const QString &ppd,
1533+ const QString &device, const QString &description,
1534+ const QString &location);
1535+ bool addPrinterWithPpdFile(const QString &name, const QString &ppdFileName,
1536+ const QString &device,
1537+ const QString &description,
1538+ const QString &location);
1539
1540 bool removePrinter(const QString &name);
1541
1542 Q_SIGNALS:
1543 void defaultPrinterNameChanged();
1544+ void driverFilterChanged();
1545
1546 private:
1547 PrinterBackend *m_backend;
1548+ DriverModel m_drivers;
1549 PrinterModel m_model;
1550 PrinterFilter m_allPrinters;
1551 PrinterFilter m_allPrintersWithPdf;
1552 PrinterFilter m_recentPrinters;
1553+ QString m_lastMessage;
1554 };
1555
1556 #endif // USC_PRINTERS_H
1557
1558=== modified file 'plugins/Ubuntu/Settings/Printers/structs.h'
1559--- plugins/Ubuntu/Settings/Printers/structs.h 2017-01-24 12:34:23 +0000
1560+++ plugins/Ubuntu/Settings/Printers/structs.h 2017-01-30 11:37:26 +0000
1561@@ -72,10 +72,28 @@
1562 }
1563 };
1564
1565+struct PrinterDriver
1566+{
1567+public:
1568+ QByteArray name;
1569+ QByteArray deviceId;
1570+ QByteArray language;
1571+ QByteArray makeModel;
1572+
1573+ QString toString() const {
1574+ return QString("%1 [%2]").arg(QString::fromUtf8(makeModel))
1575+ .arg(QString::fromUtf8(language));
1576+ }
1577+};
1578+
1579 Q_DECLARE_TYPEINFO(ColorModel, Q_PRIMITIVE_TYPE);
1580 Q_DECLARE_METATYPE(ColorModel)
1581
1582 Q_DECLARE_TYPEINFO(PrintQuality, Q_PRIMITIVE_TYPE);
1583 Q_DECLARE_METATYPE(PrintQuality)
1584
1585+Q_DECLARE_TYPEINFO(PrinterDriver, Q_MOVABLE_TYPE);
1586+Q_DECLARE_METATYPE(PrinterDriver)
1587+Q_DECLARE_METATYPE(QList<PrinterDriver>)
1588+
1589 #endif // USC_PRINTERS_STRUCTS_H
1590
1591=== modified file 'tests/unittests/Printers/CMakeLists.txt'
1592--- tests/unittests/Printers/CMakeLists.txt 2017-01-22 14:21:11 +0000
1593+++ tests/unittests/Printers/CMakeLists.txt 2017-01-30 11:37:26 +0000
1594@@ -28,3 +28,7 @@
1595 add_executable(testPrintersPrinterFilter tst_printerfilter.cpp ${MOCK_SOURCES})
1596 target_link_libraries(testPrintersPrinterFilter UbuntuSettingsPrintersQml Qt5::Test Qt5::Gui)
1597 add_test(tst_printerfilter testPrintersPrinterFilter)
1598+
1599+add_executable(testPrintersDriverModel tst_drivermodel.cpp ${MOCK_SOURCES})
1600+target_link_libraries(testPrintersDriverModel UbuntuSettingsPrintersQml Qt5::Test Qt5::Gui)
1601+add_test(tst_drivermodel testPrintersDriverModel)
1602
1603=== modified file 'tests/unittests/Printers/mockbackend.h'
1604--- tests/unittests/Printers/mockbackend.h 2017-01-23 19:41:11 +0000
1605+++ tests/unittests/Printers/mockbackend.h 2017-01-30 11:37:26 +0000
1606@@ -35,8 +35,8 @@
1607 }
1608
1609 virtual QString printerAdd(const QString &name,
1610- const QUrl &uri,
1611- const QUrl &ppdFile,
1612+ const QString &uri,
1613+ const QString &ppdFile,
1614 const QString &info,
1615 const QString &location) override
1616 {
1617@@ -49,7 +49,7 @@
1618 }
1619
1620 virtual QString printerAddWithPpd(const QString &name,
1621- const QUrl &uri,
1622+ const QString &uri,
1623 const QString &ppdFileName,
1624 const QString &info,
1625 const QString &location) override
1626@@ -309,6 +309,10 @@
1627 return m_defaultPrinterName;
1628 }
1629
1630+ virtual void requestAvailablePrinterDrivers() override
1631+ {
1632+ }
1633+
1634 virtual BackendType backendType() const override
1635 {
1636 return m_backendType;
1637@@ -335,6 +339,16 @@
1638 Q_EMIT printerStateChanged(name);
1639 }
1640
1641+ void mockDriversLoaded(const QList<PrinterDriver> &drivers)
1642+ {
1643+ Q_EMIT printerDriversLoaded(drivers);
1644+ }
1645+
1646+ void mockDriversLoaded(const QString &errorMessage)
1647+ {
1648+ Q_EMIT printerDriversFailedToLoad(errorMessage);
1649+ }
1650+
1651 QString returnValue = QString::null;
1652
1653 // Map from printer to key/val.
1654
1655=== added file 'tests/unittests/Printers/tst_drivermodel.cpp'
1656--- tests/unittests/Printers/tst_drivermodel.cpp 1970-01-01 00:00:00 +0000
1657+++ tests/unittests/Printers/tst_drivermodel.cpp 2017-01-30 11:37:26 +0000
1658@@ -0,0 +1,136 @@
1659+/*
1660+ * Copyright (C) 2017 Canonical, Ltd.
1661+ *
1662+ * This program is free software; you can redistribute it and/or modify
1663+ * it under the terms of the GNU Lesser General Public License as published by
1664+ * the Free Software Foundation; version 3.
1665+ *
1666+ * This program is distributed in the hope that it will be useful,
1667+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1668+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1669+ * GNU Lesser General Public License for more details.
1670+ *
1671+ * You should have received a copy of the GNU Lesser General Public License
1672+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1673+ */
1674+
1675+#include "mockbackend.h"
1676+
1677+#include "backend/backend.h"
1678+#include "models/drivermodel.h"
1679+
1680+#include <QDebug>
1681+#include <QObject>
1682+#include <QSignalSpy>
1683+#include <QTest>
1684+
1685+class TestDriverModel : public QObject
1686+{
1687+ Q_OBJECT
1688+private Q_SLOTS:
1689+ void init()
1690+ {
1691+ m_backend = new MockPrinterBackend;
1692+ m_model = new DriverModel(m_backend);
1693+ }
1694+ void cleanup()
1695+ {
1696+ QSignalSpy destroyedSpy(m_model, SIGNAL(destroyed(QObject*)));
1697+ m_model->deleteLater();
1698+ QTRY_COMPARE(destroyedSpy.count(), 1);
1699+ delete m_backend;
1700+ }
1701+ void testDrivers_data()
1702+ {
1703+ QTest::addColumn<QList<PrinterDriver>>("drivers");
1704+ QTest::addColumn<int>("expectedCount");
1705+ {
1706+ QList<PrinterDriver> drivers;
1707+ QTest::newRow("none") << drivers << 0;
1708+ }
1709+ {
1710+ QList<PrinterDriver> drivers({PrinterDriver(), PrinterDriver()});
1711+ QTest::newRow("some") << drivers << 2;
1712+ }
1713+ }
1714+ void testDrivers()
1715+ {
1716+ QFETCH(QList<PrinterDriver>, drivers);
1717+ QFETCH(int, expectedCount);
1718+
1719+ m_model->load();
1720+ getBackend()->mockDriversLoaded(drivers);
1721+ QCOMPARE(m_model->rowCount(), expectedCount);
1722+ }
1723+ void testFiltering_data()
1724+ {
1725+ QTest::addColumn<QList<PrinterDriver>>("drivers");
1726+ QTest::addColumn<QList<PrinterDriver>>("expectedDrivers");
1727+ QTest::addColumn<QString>("filter");
1728+
1729+ {
1730+ QList<PrinterDriver> drivers;
1731+ QList<PrinterDriver> expectedDrivers;
1732+
1733+ PrinterDriver canon;
1734+ canon.makeModel = "Canon Foojet";
1735+
1736+ PrinterDriver hp;
1737+ hp.makeModel = "HP Laserfjert";
1738+
1739+ drivers << canon << hp;
1740+ expectedDrivers << hp;
1741+
1742+ QTest::newRow("filter hp") << drivers << expectedDrivers << "hp";
1743+ }
1744+ {
1745+ QList<PrinterDriver> drivers;
1746+ QList<PrinterDriver> expectedDrivers;
1747+
1748+ PrinterDriver canon;
1749+ canon.makeModel = "Canon 4500 Foojet";
1750+
1751+ PrinterDriver canon2;
1752+ canon2.makeModel = "rabble rabble canon 4500 masterjet";
1753+
1754+ PrinterDriver hp;
1755+ hp.makeModel = "HP Laserfjert";
1756+
1757+ drivers << canon << canon2 << hp;
1758+ expectedDrivers << canon << canon2;
1759+
1760+ QTest::newRow("filter canon 4500 printers") << drivers << expectedDrivers << "canon 4500";
1761+ }
1762+ }
1763+ void testFiltering()
1764+ {
1765+ QFETCH(QList<PrinterDriver>, drivers);
1766+ QFETCH(QList<PrinterDriver>, expectedDrivers);
1767+ QFETCH(QString, filter);
1768+
1769+ m_model->load();
1770+ getBackend()->mockDriversLoaded(drivers);
1771+
1772+ QSignalSpy filterCompleteSpy(m_model, SIGNAL(filterComplete()));
1773+ m_model->setFilter(filter);
1774+ QTRY_COMPARE(filterCompleteSpy.count(), 1);
1775+
1776+ QCOMPARE(m_model->rowCount(), expectedDrivers.size());
1777+ for (int i = 0; i < m_model->rowCount(); i++) {
1778+ QCOMPARE(
1779+ expectedDrivers.at(i).makeModel,
1780+ m_model->data(m_model->index(i), DriverModel::Roles::MakeModelRole).toByteArray()
1781+ );
1782+ }
1783+ }
1784+private:
1785+ PrinterBackend *m_backend;
1786+ DriverModel *m_model;
1787+ MockPrinterBackend* getBackend()
1788+ {
1789+ return (MockPrinterBackend*) m_backend;
1790+ }
1791+};
1792+
1793+QTEST_GUILESS_MAIN(TestDriverModel)
1794+#include "tst_drivermodel.moc"
1795
1796=== modified file 'tests/unittests/Printers/tst_printers.cpp'
1797--- tests/unittests/Printers/tst_printers.cpp 2017-01-22 14:21:11 +0000
1798+++ tests/unittests/Printers/tst_printers.cpp 2017-01-30 11:37:26 +0000
1799@@ -99,6 +99,15 @@
1800 );
1801 }
1802 }
1803+ void testPrinterDrivers()
1804+ {
1805+ QString targetFilter("foo");
1806+ Printers printers(new MockPrinterBackend);
1807+ printers.setDriverFilter(targetFilter);
1808+
1809+ DriverModel *drivers = (DriverModel*) printers.drivers();
1810+ QCOMPARE(drivers->filter(), targetFilter);
1811+ }
1812 };
1813
1814 QTEST_GUILESS_MAIN(TestPrinters)

Subscribers

People subscribed via source and target branches