Merge lp:~jonas-drange/ubuntu-ui-extras/consolidated-devices into lp:~phablet-team/ubuntu-ui-extras/printer-staging

Proposed by Jonas G. Drange
Status: Superseded
Proposed branch: lp:~jonas-drange/ubuntu-ui-extras/consolidated-devices
Merge into: lp:~phablet-team/ubuntu-ui-extras/printer-staging
Diff against target: 2069 lines (+1492/-112)
26 files modified
modules/Ubuntu/Components/Extras/Example/Printers.qml (+73/-28)
modules/Ubuntu/Components/Extras/Printers/CMakeLists.txt (+1/-0)
modules/Ubuntu/Components/Extras/Printers/backend/backend_cups.cpp (+9/-8)
modules/Ubuntu/Components/Extras/Printers/cups/devicesearcher.cpp (+36/-5)
modules/Ubuntu/Components/Extras/Printers/cups/devicesearcher.h (+1/-0)
modules/Ubuntu/Components/Extras/Printers/cups/ppdutils.cpp (+343/-0)
modules/Ubuntu/Components/Extras/Printers/cups/ppdutils.h (+87/-0)
modules/Ubuntu/Components/Extras/Printers/models/devicemodel.cpp (+216/-1)
modules/Ubuntu/Components/Extras/Printers/models/devicemodel.h (+80/-1)
modules/Ubuntu/Components/Extras/Printers/printer/printer.cpp (+2/-1)
modules/Ubuntu/Components/Extras/Printers/printer/printerjob.cpp (+2/-2)
modules/Ubuntu/Components/Extras/Printers/printers/printers.cpp (+9/-0)
modules/Ubuntu/Components/Extras/Printers/printers/printers.h (+3/-0)
modules/Ubuntu/Components/Extras/Printers/structs.h (+0/-2)
modules/Ubuntu/Components/Extras/Printers/utils.h (+52/-53)
po/ubuntu-ui-extras.pot (+5/-5)
tests/unittests/Printers/CMakeLists.txt (+16/-0)
tests/unittests/Printers/mockbackend.h (+3/-3)
tests/unittests/Printers/tst_consolidateddevicemodel.cpp (+162/-0)
tests/unittests/Printers/tst_devicefilter.cpp (+106/-0)
tests/unittests/Printers/tst_jobmodel.cpp (+1/-0)
tests/unittests/Printers/tst_ppdutils.cpp (+138/-0)
tests/unittests/Printers/tst_printer.cpp (+2/-2)
tests/unittests/Printers/tst_printerdevicemodel.cpp (+35/-0)
tests/unittests/Printers/tst_printermodel.cpp (+3/-1)
tests/unittests/Printers/tst_utils.cpp (+107/-0)
To merge this branch: bzr merge lp:~jonas-drange/ubuntu-ui-extras/consolidated-devices
Reviewer Review Type Date Requested Status
Andrew Hayzen (community) Needs Information
Review via email: mp+320637@code.launchpad.net

This proposal has been superseded by a proposal from 2017-03-30.

Commit message

Adds ConsolidatedDeviceModel to make sure printers with multiple connections to it (e.g. dnssd, socket, etc.) are presented as one.

Description of the change

Adds ConsolidatedDeviceModel to make sure printers with multiple connections to it (e.g. dnssd, socket, etc.) are presented as one.

To post a comment you must log in.
Revision history for this message
Andrew Hayzen (ahayzen) wrote :

Getting a crash when using this http://pastebin.ubuntu.com/24228896/

review: Needs Fixing
159. By Jonas G. Drange

add ppdutils

160. By Jonas G. Drange

matching now works well for hp devices

161. By Jonas G. Drange

adds ppdutils, a IEEE 1284 parser, and other fixes to consolidate devices

162. By Jonas G. Drange

make fixmakemodel static

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

This is looking much better than before (it doesn't crash :-) ).

Now we have Utils and PpdUtils, would it make sense to move some of the methods from Utils into PpdUtils, eg ppdChoiceToDuplexMode. Or are we not able to move them because they are used in the models and not specifically the backend?

review: Needs Information
Revision history for this message
Jonas G. Drange (jonas-drange) wrote :

Good point. Yeah, I think that should be doable.

163. By Jonas G. Drange

do RAAI via parent->child semantics, and be more aggressive when to clear out the consolidated model

164. By Jonas G. Drange

pushes ppd specific utils from Utils into PpdUtils

165. By Jonas G. Drange

smaller refactor making device makemodel fixes easily testable

166. By Jonas G. Drange

fixes broken test after changing the test data

167. By Jonas G. Drange

syncs with staging and fixes a test that broke

168. By Jonas G. Drange

uses makeLower

169. By Jonas G. Drange

syncs with staging

170. By Jonas G. Drange

creates hp devices for hplip capable printers

171. By Jonas G. Drange

sets the faxid on any fax

172. By Jonas G. Drange

actually start the process, suggest hplip

173. By Jonas G. Drange

drop the device id for fax

174. By Jonas G. Drange

uses info as a last resort

175. By Jonas G. Drange

make sure non-consolidateable devices are kept

176. By Jonas G. Drange

fixes test that now broke, but had to change, and add .get to drivermodel

Unmerged revisions

176. By Jonas G. Drange

fixes test that now broke, but had to change, and add .get to drivermodel

175. By Jonas G. Drange

make sure non-consolidateable devices are kept

174. By Jonas G. Drange

uses info as a last resort

173. By Jonas G. Drange

drop the device id for fax

172. By Jonas G. Drange

actually start the process, suggest hplip

171. By Jonas G. Drange

sets the faxid on any fax

170. By Jonas G. Drange

creates hp devices for hplip capable printers

169. By Jonas G. Drange

syncs with staging

168. By Jonas G. Drange

uses makeLower

167. By Jonas G. Drange

syncs with staging and fixes a test that broke

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'modules/Ubuntu/Components/Extras/Example/Printers.qml'
--- modules/Ubuntu/Components/Extras/Example/Printers.qml 2017-03-21 23:02:42 +0000
+++ modules/Ubuntu/Components/Extras/Example/Printers.qml 2017-03-30 13:23:27 +0000
@@ -685,7 +685,7 @@
685 verticalCenter: parent.verticalCenter685 verticalCenter: parent.verticalCenter
686 }686 }
687 property var target687 property var target
688 Component.onCompleted: target = Printers.devices688 Component.onCompleted: target = Printers.consolidatedDevices
689 running: target.searching689 running: target.searching
690 }690 }
691 }691 }
@@ -699,27 +699,12 @@
699 topMargin: units.gu(2)699 topMargin: units.gu(2)
700 }700 }
701 height: contentItem.childrenRect.height701 height: contentItem.childrenRect.height
702 model: Printers.devices702 model: Printers.consolidatedDevices
703 delegate: ListItem {703 delegate: ListItem {
704 height: modelLayout.height + (divider.visible ? divider.height : 0)704 height: modelLayout.height + (divider.visible ? divider.height : 0)
705 ListItemLayout {705 ListItemLayout {
706 id: modelLayout706 id: modelLayout
707 title.text: displayName707 title.text: displayName
708 subtitle.text: {
709 if (type == PrinterEnum.LPDType) return "LPD";
710 if (type == PrinterEnum.IppSType) return "IppS";
711 if (type == PrinterEnum.Ipp14Type) return "Ipp14";
712 if (type == PrinterEnum.HttpType) return "Http";
713 if (type == PrinterEnum.BehType) return "Beh";
714 if (type == PrinterEnum.SocketType) return "Socket";
715 if (type == PrinterEnum.HttpsType) return "Https";
716 if (type == PrinterEnum.IppType) return "Ipp";
717 if (type == PrinterEnum.HPType) return "HP";
718 if (type == PrinterEnum.USBType) return "USB";
719 if (type == PrinterEnum.HPFaxType) return "HPFax";
720 if (type == PrinterEnum.DNSSDType) return "DNSSD";
721 else return "Unknown protocol";
722 }
723708
724 Icon {709 Icon {
725 id: icon710 id: icon
@@ -729,17 +714,22 @@
729 SlotsLayout.position: SlotsLayout.First714 SlotsLayout.position: SlotsLayout.First
730 }715 }
731716
732 Button {717 ProgressionSlot {}
733 text: "Select printer"718 }
734 onClicked: {719 onClicked: {
735 var suggestedPrinterName = (" " + displayName).slice(1);720 var p = pageStack.push(chooseConnectionPage, {
736 suggestedPrinterName = suggestedPrinterName.replace(/\ /g, "\-");721 consolidatedDevice: model
737 printerUri.text = uri;722 });
738 printerName.text = suggestedPrinterName;723 p.onConnectionChosen.connect(function (conn) {
739 printerDescription.text = info;724 pageStack.pop();
740 printerLocation.text = location;725
741 }726 var suggestedPrinterName = (" " + conn.displayName).slice(1);
742 }727 suggestedPrinterName = suggestedPrinterName.replace(/\ /g, "\-");
728 printerUri.text = conn.uri;
729 printerName.text = suggestedPrinterName;
730 printerDescription.text = conn.info;
731 printerLocation.text = conn.location;
732 });
743 }733 }
744 }734 }
745 }735 }
@@ -757,4 +747,59 @@
757 }747 }
758 }748 }
759 }749 }
750
751 Component {
752 id: chooseConnectionPage
753
754 Page {
755 visible: false
756 property var consolidatedDevice
757 signal connectionChosen(var conn)
758 header: PageHeader {
759 id: chooseConnectionPageHeader
760 title: "Choose connection"
761 flickable: connectionsList
762 }
763
764 ListView {
765 id: connectionsList
766 anchors.fill: parent
767 model: consolidatedDevice.devices
768 delegate: ListItem {
769 height: modelLayout.height + (divider.visible ? divider.height : 0)
770 ListItemLayout {
771 id: modelLayout
772 title.text: info
773 subtitle.text: {
774 if (type == PrinterEnum.LPDType) return "LPD";
775 if (type == PrinterEnum.IppSType) return "IppS";
776 if (type == PrinterEnum.Ipp14Type) return "Ipp14";
777 if (type == PrinterEnum.HttpType) return "Http";
778 if (type == PrinterEnum.BehType) return "Beh";
779 if (type == PrinterEnum.SocketType) return "Socket";
780 if (type == PrinterEnum.HttpsType) return "Https";
781 if (type == PrinterEnum.IppType) return "Ipp";
782 if (type == PrinterEnum.HPType) return "HP";
783 if (type == PrinterEnum.USBType) return "USB";
784 if (type == PrinterEnum.HPFaxType) return "HPFax";
785 if (type == PrinterEnum.DNSSDType) return "DNSSD";
786 else return "Unknown protocol";
787 }
788 summary.text: uri
789
790 Icon {
791 id: icon
792 width: height
793 height: units.gu(2.5)
794 name: "network-printer-symbolic"
795 SlotsLayout.position: SlotsLayout.First
796 }
797
798 ProgressionSlot {}
799 }
800 onClicked: connectionChosen(model)
801 }
802 }
803 }
804 }
760}805}
761806
=== modified file 'modules/Ubuntu/Components/Extras/Printers/CMakeLists.txt'
--- modules/Ubuntu/Components/Extras/Printers/CMakeLists.txt 2017-03-13 12:51:16 +0000
+++ modules/Ubuntu/Components/Extras/Printers/CMakeLists.txt 2017-03-30 13:23:27 +0000
@@ -34,6 +34,7 @@
34 cups/jobloader.cpp34 cups/jobloader.cpp
35 cups/printerdriverloader.cpp35 cups/printerdriverloader.cpp
36 cups/printerloader.cpp36 cups/printerloader.cpp
37 cups/ppdutils.cpp
3738
38 models/devicemodel.cpp39 models/devicemodel.cpp
39 models/drivermodel.cpp40 models/drivermodel.cpp
4041
=== modified file 'modules/Ubuntu/Components/Extras/Printers/backend/backend_cups.cpp'
--- modules/Ubuntu/Components/Extras/Printers/backend/backend_cups.cpp 2017-03-15 17:42:21 +0000
+++ modules/Ubuntu/Components/Extras/Printers/backend/backend_cups.cpp 2017-03-30 13:23:27 +0000
@@ -17,6 +17,7 @@
17#include "backend/backend_cups.h"17#include "backend/backend_cups.h"
18#include "cups/devicesearcher.h"18#include "cups/devicesearcher.h"
19#include "cups/jobloader.h"19#include "cups/jobloader.h"
20#include "cups/ppdutils.h"
20#include "cups/printerdriverloader.h"21#include "cups/printerdriverloader.h"
21#include "cups/printerloader.h"22#include "cups/printerloader.h"
22#include "utils.h"23#include "utils.h"
@@ -264,9 +265,9 @@
264 ppd_choice_t* def = ppdFindChoice(ppdColorModel,265 ppd_choice_t* def = ppdFindChoice(ppdColorModel,
265 ppdColorModel->defchoice);266 ppdColorModel->defchoice);
266 if (def) {267 if (def) {
267 model = Utils::parsePpdColorModel(def->choice,268 model = PpdUtils::parsePpdColorModel(def->choice,
268 def->text,269 def->text,
269 "ColorModel");270 "ColorModel");
270 }271 }
271 }272 }
272 ret[option] = QVariant::fromValue(model);273 ret[option] = QVariant::fromValue(model);
@@ -278,8 +279,8 @@
278 ppd_choice_t* def = ppdFindChoice(ppdQuality,279 ppd_choice_t* def = ppdFindChoice(ppdQuality,
279 ppdQuality->defchoice);280 ppdQuality->defchoice);
280 if (def) {281 if (def) {
281 quality = Utils::parsePpdPrintQuality(def->choice,282 quality = PpdUtils::parsePpdPrintQuality(def->choice,
282 def->text, opt);283 def->text, opt);
283 }284 }
284 }285 }
285 }286 }
@@ -291,7 +292,7 @@
291 if (qualityOpt) {292 if (qualityOpt) {
292 for (int i = 0; i < qualityOpt->num_choices; ++i) {293 for (int i = 0; i < qualityOpt->num_choices; ++i) {
293 qualities.append(294 qualities.append(
294 Utils::parsePpdPrintQuality(295 PpdUtils::parsePpdPrintQuality(
295 qualityOpt->choices[i].choice,296 qualityOpt->choices[i].choice,
296 qualityOpt->choices[i].text,297 qualityOpt->choices[i].text,
297 opt298 opt
@@ -307,7 +308,7 @@
307 if (colorModels) {308 if (colorModels) {
308 for (int i = 0; i < colorModels->num_choices; ++i) {309 for (int i = 0; i < colorModels->num_choices; ++i) {
309 models.append(310 models.append(
310 Utils::parsePpdColorModel(311 PpdUtils::parsePpdColorModel(
311 colorModels->choices[i].choice,312 colorModels->choices[i].choice,
312 colorModels->choices[i].text,313 colorModels->choices[i].text,
313 QStringLiteral("ColorModel")314 QStringLiteral("ColorModel")
@@ -357,7 +358,7 @@
357358
358 __CUPS_ADD_OPTION(dest, "copies", QString::number(options->copies()).toLocal8Bit());359 __CUPS_ADD_OPTION(dest, "copies", QString::number(options->copies()).toLocal8Bit());
359 __CUPS_ADD_OPTION(dest, "ColorModel", options->getColorModel().name.toLocal8Bit());360 __CUPS_ADD_OPTION(dest, "ColorModel", options->getColorModel().name.toLocal8Bit());
360 __CUPS_ADD_OPTION(dest, "Duplex", Utils::duplexModeToPpdChoice(options->getDuplexMode()).toLocal8Bit());361 __CUPS_ADD_OPTION(dest, "Duplex", PpdUtils::duplexModeToPpdChoice(options->getDuplexMode()).toLocal8Bit());
361362
362 if (options->landscape()) {363 if (options->landscape()) {
363 __CUPS_ADD_OPTION(dest, "landscape", "");364 __CUPS_ADD_OPTION(dest, "landscape", "");
364365
=== modified file 'modules/Ubuntu/Components/Extras/Printers/cups/devicesearcher.cpp'
--- modules/Ubuntu/Components/Extras/Printers/cups/devicesearcher.cpp 2017-03-08 14:47:16 +0000
+++ modules/Ubuntu/Components/Extras/Printers/cups/devicesearcher.cpp 2017-03-30 13:23:27 +0000
@@ -15,9 +15,9 @@
15 */15 */
1616
17#include "cups/ippclient.h"17#include "cups/ippclient.h"
18#include "cups/ppdutils.h"
18#include "devicesearcher.h"19#include "devicesearcher.h"
1920#include "utils.h"
20#include <QUrl>
2121
22DeviceSearcher::DeviceSearcher(IppClient *client, QObject *parent)22DeviceSearcher::DeviceSearcher(IppClient *client, QObject *parent)
23 : QObject(parent)23 : QObject(parent)
@@ -54,14 +54,45 @@
54 }54 }
5555
56 Device d;56 Device d;
57
57 d.cls = deviceClass;58 d.cls = deviceClass;
58 d.id = deviceId;59 d.id = deviceId;
59 d.info = deviceInfo;60 d.info = deviceInfo;
60 d.makeModel = deviceMakeAndModel;61
61 d.uri = deviceUri;62 d.uri = deviceUri;
62 d.location = deviceLocation;63 d.location = deviceLocation;
6364 d.makeModel = deviceMakeAndModel;
64 searcher->deviceFound(d);65
66 DeviceSearcher::fixMakeModel(d);
67
68 if (!d.makeModel.isEmpty()) {
69 searcher->deviceFound(d);
70 }
71}
72
73void DeviceSearcher::fixMakeModel(Device &device)
74{
75 // We can't set make-model on devices without enough info.
76 if (device.id.isEmpty() &&
77 (device.makeModel.isEmpty() ||
78 device.makeModel == QStringLiteral("Unknown"))) {
79 device.makeModel = QString::null;
80
81 return;
82 }
83
84 QString canidateMakeModel;
85
86 if (!device.id.isEmpty()) {
87 auto idDict = Utils::parseDeviceId(device.id);
88 canidateMakeModel = QString("%1 %2").arg(idDict["MFG"], idDict["MDL"]);
89 } else if (!device.makeModel.isEmpty()) {
90 canidateMakeModel = device.makeModel;
91 }
92
93 auto split = PpdUtils::splitMakeModel(canidateMakeModel);
94 canidateMakeModel = QString("%1 %2").arg(split.first, split.second);
95 device.makeModel = canidateMakeModel;
65}96}
6697
67void DeviceSearcher::deviceFound(const Device &device)98void DeviceSearcher::deviceFound(const Device &device)
6899
=== modified file 'modules/Ubuntu/Components/Extras/Printers/cups/devicesearcher.h'
--- modules/Ubuntu/Components/Extras/Printers/cups/devicesearcher.h 2017-03-08 11:29:01 +0000
+++ modules/Ubuntu/Components/Extras/Printers/cups/devicesearcher.h 2017-03-30 13:23:27 +0000
@@ -47,6 +47,7 @@
47 const char *location,47 const char *location,
48 void *context);48 void *context);
49 void deviceFound(const Device &device);49 void deviceFound(const Device &device);
50 static void fixMakeModel(Device &device);
5051
51Q_SIGNALS:52Q_SIGNALS:
52 void loaded(const Device &device);53 void loaded(const Device &device);
5354
=== added file 'modules/Ubuntu/Components/Extras/Printers/cups/ppdutils.cpp'
--- modules/Ubuntu/Components/Extras/Printers/cups/ppdutils.cpp 1970-01-01 00:00:00 +0000
+++ modules/Ubuntu/Components/Extras/Printers/cups/ppdutils.cpp 2017-03-30 13:23:27 +0000
@@ -0,0 +1,343 @@
1/*
2 * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2014, 2015 Red Hat, Inc.
3 * Copyright (C) 2006 Florian Festi <ffesti@redhat.com>
4 * Copyright (C) 2006, 2007, 2008, 2009 Tim Waugh <twaugh@redhat.com>
5 * Copyright (C) 2017 Canonical, Ltd.
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU Lesser General Public License as published by
9 * the Free Software Foundation; version 3.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#include "ppdutils.h"
21
22#include <QSet>
23
24const QRegularExpression PpdUtils::versionNumbers = QRegularExpression(
25 " v(?:er\\.)?\\d(?:\\d*\\.\\d+)?(?: |$)"
26);
27const QRegularExpression PpdUtils::ignoredSuffixes = QRegularExpression(
28 ","
29 "| hpijs"
30 "| foomatic/"
31 "| - "
32 "| w/"
33 "| \\("
34 "| postscript"
35 "| ps"
36 "| pdf"
37 "| pxl"
38 "| zjs"
39 "| zxs"
40 "| pcl3"
41 "| printer"
42 "|_bt"
43 "| pcl"
44 "| ufr ii"
45 "| br-script"
46);
47const QRegularExpression PpdUtils::ignoreSeries = QRegularExpression(
48 " series| all-in-one",
49 QRegularExpression::CaseInsensitiveOption
50);
51const QList<QPair<QString, QRegularExpression>> PpdUtils::manufacturerByModels
52 = QList<QPair<QString, QRegularExpression>>({
53 QPair<QString, QRegularExpression>(
54 QStringLiteral("HP"), QRegularExpression(
55 "deskjet"
56 "|dj[ 0-9]?"
57 "|laserjet"
58 "|lj"
59 "|color laserjet"
60 "|color lj"
61 "|designjet"
62 "|officejet"
63 "|oj"
64 "|photosmart"
65 "|ps "
66 "|psc"
67 "|edgeline"
68 )
69 ),
70 QPair<QString, QRegularExpression>(
71 QStringLiteral("Epson"), QRegularExpression("stylus|aculaser")
72 ),
73 QPair<QString, QRegularExpression>(
74 QStringLiteral("Apple"), QRegularExpression(
75 "stylewriter"
76 "|imagewriter"
77 "|deskwriter"
78 "|laserwriter"
79 )
80 ),
81 QPair<QString, QRegularExpression>(
82 QStringLiteral("Canon"), QRegularExpression(
83 "pixus"
84 "|pixma"
85 "|selphy"
86 "|imagerunner"
87 "|bj"
88 "|lbp"
89 )
90 ),
91 QPair<QString, QRegularExpression>(
92 QStringLiteral("Brother"), QRegularExpression("hl|dcp|mfc")
93 ),
94 QPair<QString, QRegularExpression>(
95 QStringLiteral("Xerox"), QRegularExpression(
96 "docuprint"
97 "|docupage"
98 "|phaser"
99 "|workcentre"
100 "|homecentre"
101 )
102 ),
103 QPair<QString, QRegularExpression>(
104 QStringLiteral("Lexmark"),
105 QRegularExpression("optra|(:color )?jetprinter")
106 ),
107 QPair<QString, QRegularExpression>(
108 QStringLiteral("KONICA MINOLTA"), QRegularExpression(
109 "magicolor"
110 "|pageworks"
111 "|pagepro"
112 )
113 ),
114 QPair<QString, QRegularExpression>(
115 QStringLiteral("Kyocera"), QRegularExpression(
116 "fs-"
117 "|km-"
118 "|taskalfa"
119 )
120 ),
121 QPair<QString, QRegularExpression>(
122 QStringLiteral("Ricoh"), QRegularExpression("aficio")
123 ),
124 QPair<QString, QRegularExpression>(
125 QStringLiteral("Oce"), QRegularExpression("varioprint")
126 ),
127 QPair<QString, QRegularExpression>(
128 QStringLiteral("Oki"), QRegularExpression("okipage|microline")
129 ),
130});
131
132const QMap<QString, QString> PpdUtils::hpByModel = QMap<QString, QString>{
133 {"dj", "DeskJet"},
134 {"lj", "LaserJet"},
135 {"laserjet", "LaserJet"},
136 {"oj", "OfficeJet"},
137 {"officejet", "OfficeJet"},
138 {"color lj", "Color LaserJet"},
139 {"ps ", "PhotoSmart"},
140 {"hp ", ""},
141};
142
143QString PpdUtils::dedupeMakeModel(const QString &str)
144{
145 QSet<QString> stringSet;
146 QStringList deduped;
147 Q_FOREACH(auto m, str.split(" ")) {
148 if (!stringSet.contains(m)) {
149 deduped << m;
150 }
151 stringSet << m;
152 }
153 return deduped.join(" ");
154}
155
156QPair<QString, QString> PpdUtils::splitMakeModel(const QString &makeModel)
157{
158 const QString mm = dedupeMakeModel(makeModel);
159 const QString makeModelLower = mm.toLower();
160
161 QString make;
162 QString model;
163
164 /* Informs us whether or not the make part was produced in such a way that
165 it might need cleaning. I.e. we've made a guess at what the make is. */
166 bool cleanupMake = false;
167
168 /* If the string starts with a known model name (like "LaserJet") assume
169 that the manufacturer name is missing and add the manufacturer name
170 corresponding to the model name */
171 Q_FOREACH(auto manufacturerByModel, PpdUtils::manufacturerByModels) {
172 if (manufacturerByModel.second.match(makeModelLower).hasMatch()) {
173 make = manufacturerByModel.first;
174 model = mm;
175 }
176 }
177
178 /* Take the first word as the name of the manufacturer.
179
180 Note that we have skipped a bunch of special cases where the first word
181 isn't necessarily the manufacturer, hence the next TODO.
182
183 TODO: Handle special cases for TurboPrint, lexmark international,
184 fuji xerox, etc. */
185 int firstSpace = mm.indexOf(" ");
186 if (make.isEmpty() && firstSpace > 0) {
187 make = mm.left(firstSpace);
188 model = mm.mid(make.size() + 1);
189
190 /* We've picked the first part as the make, but it may be that we've
191 split "hewlett packard" and are left with "hewlett". */
192 cleanupMake = true;
193 }
194
195 // Standardised names for manufacturers.
196 const QString makeLower = make.toLower();
197 if (cleanupMake) {
198 if (makeLower.startsWith("hewlett") && makeLower.endsWith("packard")) {
199 make = "HP";
200 // makeLower = "hp"
201 } else if (makeLower.startsWith("konica") &&
202 makeLower.endsWith("minolta")) {
203 make = "KONICA MINOLTA";
204 // makeLower = "konica minolta"
205 } else {
206 // Fix case errors.
207 Q_FOREACH(auto manufacturerByModel, manufacturerByModels) {
208 if (makeLower == manufacturerByModel.first.toLower()) {
209 make = manufacturerByModel.first;
210 break;
211 }
212 }
213 }
214 }
215
216 // Remove the word "Series" if present.
217 model = model.remove(ignoreSeries);
218
219 /* Find a version number and cut the model from there. Note that some
220 models may legitimately look like version numbers. So we cut only if the
221 version number has max one digit, or a dot with digits before and after. */
222 QString modelLower = model.toLower();
223 int versionAt = modelLower.indexOf(" v");
224 if (versionAt >= 0) {
225 // Look for v or ver. followed by digits and dots.
226 auto match = versionNumbers.match(modelLower);
227 if (match.hasMatch()) {
228 int matchAt = match.capturedStart();
229 model.truncate(matchAt);
230 modelLower.truncate(matchAt);
231 }
232 }
233
234 // Remove ignored suffixes.
235 auto suffixMatch = ignoredSuffixes.match(modelLower);
236 if (suffixMatch.hasMatch()) {
237 int matchAt = suffixMatch.capturedStart();
238 model.truncate(matchAt);
239 modelLower.truncate(matchAt);
240 }
241
242 // Replace lj or dj with LaserJet and DeskJet respectively, etc.
243 if (makeLower == "hp") {
244 Q_FOREACH(auto name, hpByModel.keys()) {
245 if (modelLower.contains(name)) {
246 int start = modelLower.indexOf(name);
247 model.remove(start, name.size());
248 model.insert(start, hpByModel[name]);
249 modelLower = model.toLower();
250 }
251 }
252 }
253
254 return QPair<QString, QString>(make, model);
255}
256
257QString PpdUtils::normalize(const QString &str)
258{
259 QString strLower = str.trimmed().toLower();
260 QString normalized;
261
262 const int BLANK = 0;
263 const int ALPHA = 1;
264 const int DIGIT = 2;
265 int lastchar = BLANK;
266
267 bool alnumfound = false;
268
269 for (int i = 0; i < strLower.size(); i++) {
270 if (strLower[i].isLetter()) {
271 if (lastchar != ALPHA && alnumfound) {
272 normalized += " ";
273 }
274 lastchar = ALPHA;
275 } else if (strLower[i].isDigit()) {
276 if (lastchar != DIGIT && alnumfound) {
277 normalized += " ";
278 }
279 lastchar = DIGIT;
280 } else {
281 lastchar = BLANK;
282 }
283
284 if (strLower[i].isLetterOrNumber()) {
285 normalized += strLower[i];
286 alnumfound = true;
287 }
288 }
289 return normalized;
290}
291
292PrinterEnum::DuplexMode PpdUtils::ppdChoiceToDuplexMode(const QString &choice)
293{
294 if (choice == "DuplexTumble")
295 return PrinterEnum::DuplexMode::DuplexShortSide;
296 else if (choice == "DuplexNoTumble")
297 return PrinterEnum::DuplexMode::DuplexLongSide;
298 else
299 return PrinterEnum::DuplexMode::DuplexNone;
300}
301
302const QString PpdUtils::duplexModeToPpdChoice(
303 const PrinterEnum::DuplexMode &mode)
304{
305 switch (mode) {
306 case PrinterEnum::DuplexMode::DuplexShortSide:
307 return "DuplexTumble";
308 case PrinterEnum::DuplexMode::DuplexLongSide:
309 return "DuplexNoTumble";
310 case PrinterEnum::DuplexMode::DuplexNone:
311 default:
312 return "None";
313 }
314}
315
316
317ColorModel PpdUtils::parsePpdColorModel(const QString &name,
318 const QString &text,
319 const QString &optionName)
320{
321 ColorModel ret;
322 ret.name = name;
323 ret.text = text;
324 ret.originalOption = optionName;
325
326 if (ret.name.contains("Gray") || ret.name.contains("Black")) {
327 ret.colorType = PrinterEnum::ColorModelType::GrayType;
328 } else {
329 ret.colorType = PrinterEnum::ColorModelType::ColorType;
330 }
331 return ret;
332}
333
334PrintQuality PpdUtils::parsePpdPrintQuality(const QString &choice,
335 const QString &text,
336 const QString &optionName)
337{
338 PrintQuality quality;
339 quality.name = choice;
340 quality.text = text;
341 quality.originalOption = optionName;
342 return quality;
343}
0344
=== added file 'modules/Ubuntu/Components/Extras/Printers/cups/ppdutils.h'
--- modules/Ubuntu/Components/Extras/Printers/cups/ppdutils.h 1970-01-01 00:00:00 +0000
+++ modules/Ubuntu/Components/Extras/Printers/cups/ppdutils.h 2017-03-30 13:23:27 +0000
@@ -0,0 +1,87 @@
1/*
2 * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2014, 2015 Red Hat, Inc.
3 * Copyright (C) 2006 Florian Festi <ffesti@redhat.com>
4 * Copyright (C) 2006, 2007, 2008, 2009 Tim Waugh <twaugh@redhat.com>
5 * Copyright (C) 2017 Canonical, Ltd.
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU Lesser General Public License as published by
9 * the Free Software Foundation; version 3.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#ifndef USC_PRINTERS_CUPS_PPDUTILS_H
21#define USC_PRINTERS_CUPS_PPDUTILS_H
22
23#include "enums.h"
24#include "i18n.h"
25#include "printers_global.h"
26#include "structs.h"
27
28#include <cups/ppd.h>
29
30#include <QList>
31#include <QMap>
32#include <QPair>
33#include <QRegularExpression>
34#include <QString>
35
36struct PRINTERS_DECL_EXPORT PpdUtils
37{
38private:
39 static const QList<QPair<QString, QRegularExpression>> manufacturerByModels;
40 static const QRegularExpression ignoredSuffixes;
41 static const QRegularExpression versionNumbers;
42 static const QRegularExpression ignoreSeries;
43 static const QMap<QString, QString> hpByModel;
44 static QString dedupeMakeModel(const QString &str);
45public:
46
47 /**
48 Split a ppd-make-and-model string into a canonical make and model pair.
49 Returns a string pair representing the make and the model.
50 */
51 static QPair<QString, QString> splitMakeModel(const QString &makeModel);
52
53 /**
54 This function normalizes manufacturer and model names for comparing.
55 The string is turned to lower case and leading and trailing white
56 space is removed. After that each sequence of non-alphanumeric
57 characters (including white space) is replaced by a single space and
58 also at each change between letters and numbers a single space is added.
59 This makes the comparison only done by alphanumeric characters and the
60 words formed from them. So mostly two strings which sound the same when
61 you pronounce them are considered equal. Printer manufacturers do not
62 market two models whose names sound the same but differ only by
63 upper/lower case, spaces, dashes, ..., but in printer drivers names can
64 be easily supplied with these details of the name written in the wrong
65 way, especially if the IEEE-1284 device ID of the printer is not known.
66 This way we get a very reliable matching of printer model names.
67 Examples:
68 - Epson PM-A820 -> epson pm a 820
69 - Epson PM A820 -> epson pm a 820
70 - HP PhotoSmart C 8100 -> hp photosmart c 8100
71 - hp Photosmart C8100 -> hp photosmart c 8100
72 */
73 static QString normalize(const QString &str);
74
75 static PrinterEnum::DuplexMode ppdChoiceToDuplexMode(
76 const QString &choice);
77 static const QString duplexModeToPpdChoice(
78 const PrinterEnum::DuplexMode &mode);
79 static ColorModel parsePpdColorModel(const QString &name,
80 const QString &text,
81 const QString &optionName);
82 static PrintQuality parsePpdPrintQuality(const QString &choice,
83 const QString &text,
84 const QString &optionName);
85};
86
87#endif // USC_PRINTERS_CUPS_PPDUTILS_H
088
=== modified file 'modules/Ubuntu/Components/Extras/Printers/models/devicemodel.cpp'
--- modules/Ubuntu/Components/Extras/Printers/models/devicemodel.cpp 2017-03-09 14:34:05 +0000
+++ modules/Ubuntu/Components/Extras/Printers/models/devicemodel.cpp 2017-03-30 13:23:27 +0000
@@ -15,9 +15,13 @@
15 */15 */
1616
17#include "backend/backend_cups.h"17#include "backend/backend_cups.h"
18#include "cups/ppdutils.h"
18#include "models/devicemodel.h"19#include "models/devicemodel.h"
1920
20#include <QDebug>21#include <QDebug>
22#include <QPair>
23#include <QQmlEngine>
24#include <QSet>
2125
22DeviceModel::DeviceModel(PrinterBackend *backend, QObject *parent)26DeviceModel::DeviceModel(PrinterBackend *backend, QObject *parent)
23 : QAbstractListModel(parent)27 : QAbstractListModel(parent)
@@ -116,8 +120,16 @@
116120
117bool DeviceModel::deviceWanted(const Device &device)121bool DeviceModel::deviceWanted(const Device &device)
118{122{
123 bool wanted = true;
124
125 // Skip non-uris.
119 auto parts = device.uri.split(":", QString::SkipEmptyParts);126 auto parts = device.uri.split(":", QString::SkipEmptyParts);
120 return parts.size() > 1;127 wanted = wanted && parts.size() > 1;
128
129 // Skip "Unknown" make-models.
130 wanted = wanted && device.makeModel != QStringLiteral("Unknown");
131
132 return wanted;
121}133}
122134
123void DeviceModel::clear()135void DeviceModel::clear()
@@ -147,3 +159,206 @@
147 m_isSearching = false;159 m_isSearching = false;
148 Q_EMIT searchingChanged();160 Q_EMIT searchingChanged();
149}161}
162
163bool DeviceModel::searching()
164{
165 return m_isSearching;
166}
167
168DeviceFilter::DeviceFilter(QObject *parent) : QSortFilterProxyModel(parent)
169{
170 connect(this, SIGNAL(sourceModelChanged()), SLOT(onSourceModelChanged()));
171}
172
173DeviceFilter::~DeviceFilter()
174{
175}
176
177void DeviceFilter::onSourceModelChanged()
178{
179 connect(
180 (DeviceModel*) sourceModel(), SIGNAL(countChanged()),
181 this, SIGNAL(countChanged())
182 );
183}
184
185void DeviceFilter::onSourceModelCountChanged()
186{
187 Q_EMIT countChanged();
188}
189
190int DeviceFilter::count() const
191{
192 return rowCount();
193}
194
195void DeviceFilter::filterOnNormalizedMakeModel(
196 const QString &normalizedMakeModel)
197{
198 m_normalizedMakeModel = normalizedMakeModel;
199 m_normalizedMakeModelFilterEnabled = true;
200 invalidate();
201}
202
203void DeviceFilter::filterOnUri(const QString &uri)
204{
205 m_uri = uri;
206 m_uriFilterEnabled = true;
207 invalidate();
208}
209
210bool DeviceFilter::filterAcceptsRow(int sourceRow,
211 const QModelIndex &sourceParent) const
212{
213 bool accepts = true;
214 QModelIndex childIndex = sourceModel()->index(sourceRow, 0, sourceParent);
215
216 if (accepts && m_normalizedMakeModelFilterEnabled) {
217 QString makeModel = childIndex.model()->data(
218 childIndex, DeviceModel::MakeModelRole).toString();
219 accepts = m_normalizedMakeModel == PpdUtils::normalize(makeModel);
220 }
221
222 if (accepts && m_uriFilterEnabled) {
223 QString uri = childIndex.model()->data(
224 childIndex, DeviceModel::UriRole).toString();
225 accepts = m_uri == uri;
226 }
227
228 return accepts;
229}
230
231ConsolidatedDeviceModel::ConsolidatedDeviceModel(DeviceModel *deviceModel,
232 QObject *parent)
233 : QAbstractListModel(parent)
234 , m_deviceModel(deviceModel)
235{
236 connect(m_deviceModel, SIGNAL(searchingChanged()),
237 this, SLOT(deviceModelSearchChanged()));
238}
239
240ConsolidatedDeviceModel::~ConsolidatedDeviceModel()
241{
242}
243
244int ConsolidatedDeviceModel::rowCount(const QModelIndex &parent) const
245{
246 Q_UNUSED(parent);
247 return m_consolidatedDevices.size();
248}
249
250int ConsolidatedDeviceModel::count() const
251{
252 return rowCount();
253}
254
255bool ConsolidatedDeviceModel::searching()
256{
257 return m_deviceModel->searching();
258}
259
260bool ConsolidatedDeviceModel::makeModelIsConsolidateable(
261 const QString &makeModel) const
262{
263 return !makeModel.isEmpty() && makeModel != QStringLiteral("Unknown");
264}
265
266void ConsolidatedDeviceModel::deviceModelSearchChanged()
267{
268 int oldCount = rowCount();
269
270 beginResetModel();
271 m_consolidatedDevices.clear();
272
273 /* Device model has finished searching, so let's sync our model with it.
274 Note that if the deviceModel is searching, we simply dump our model. */
275 if (!m_deviceModel->searching()) {
276 // A set of normalized make-models.
277 QSet<QString> normalizedMakeModels;
278
279 // Used to preserve makeModels after it has been normalized.
280 QMap<QString, QString> normalizedToMakeModel;
281 for (int i = 0; i < m_deviceModel->rowCount(); i++) {
282 QModelIndex idx = m_deviceModel->index(i, 0);
283 QString makeModel = m_deviceModel->data(
284 idx, DeviceModel::MakeModelRole
285 ).toString();
286
287 // We can't do anything about a make-model-less device.
288 if (makeModel.isEmpty()) {
289 continue;
290 }
291
292 if (makeModelIsConsolidateable(makeModel)) {
293 QString normalized = PpdUtils::normalize(makeModel);
294 normalizedMakeModels << normalized;
295 normalizedToMakeModel[normalized] = makeModel;
296 }
297 }
298
299 /* For every make-and-model, create a proxy model, and install a filter
300 on it. Then do the same for those consolidated under URI (i.e. not
301 consolidated).
302 Note that a parent is set on each filter so that it is cleaned up when
303 we the consolidated model is destroyed. */
304 Q_FOREACH(auto mm, normalizedMakeModels) {
305 auto filter = new DeviceFilter(this);
306 filter->setSourceModel(m_deviceModel);
307 filter->filterOnNormalizedMakeModel(mm);
308
309 QPair<QString, DeviceFilter*> p(mm, filter);
310 m_consolidatedDevices << p;
311 }
312 }
313
314 endResetModel();
315
316 if (oldCount != rowCount()) {
317 Q_EMIT countChanged();
318 }
319 Q_EMIT searchingChanged();
320}
321
322QVariant ConsolidatedDeviceModel::data(const QModelIndex &index, int role) const
323{
324 QVariant ret;
325
326 if ((0 <= index.row()) && (index.row() < m_consolidatedDevices.size())) {
327
328 auto consolidated = m_consolidatedDevices[index.row()];
329 auto mm = consolidated.first;
330 auto filter = consolidated.second;
331
332 switch (role) {
333 case Qt::DisplayRole:
334 case ConsolidatedNameRole:
335 ret = filter->data(
336 filter->index(0, 0), DeviceModel::Roles::MakeModelRole
337 ).toString();
338 break;
339 case DevicesRole: {
340 ret = QVariant::fromValue(filter);
341 break;
342 case ConnectionsCountRole:
343 ret = filter->count();
344 break;
345 }
346 }
347 }
348
349 return ret;
350}
351
352QHash<int, QByteArray> ConsolidatedDeviceModel::roleNames() const
353{
354 static QHash<int,QByteArray> names;
355
356 if (Q_UNLIKELY(names.empty())) {
357 names[Qt::DisplayRole] = "displayName";
358 names[ConsolidatedNameRole] = "consolidatedName";
359 names[DevicesRole] = "devices";
360 names[ConnectionsCountRole] = "connectionsCount";
361 }
362
363 return names;
364}
150365
=== modified file 'modules/Ubuntu/Components/Extras/Printers/models/devicemodel.h'
--- modules/Ubuntu/Components/Extras/Printers/models/devicemodel.h 2017-03-09 14:34:05 +0000
+++ modules/Ubuntu/Components/Extras/Printers/models/devicemodel.h 2017-03-30 13:23:27 +0000
@@ -31,7 +31,7 @@
31{31{
32 Q_OBJECT32 Q_OBJECT
33 Q_PROPERTY(int count READ count NOTIFY countChanged)33 Q_PROPERTY(int count READ count NOTIFY countChanged)
34 Q_PROPERTY(bool searching MEMBER m_isSearching NOTIFY searchingChanged)34 Q_PROPERTY(bool searching READ searching NOTIFY searchingChanged)
35public:35public:
36 explicit DeviceModel(PrinterBackend *backend, QObject *parent = Q_NULLPTR);36 explicit DeviceModel(PrinterBackend *backend, QObject *parent = Q_NULLPTR);
37 ~DeviceModel();37 ~DeviceModel();
@@ -56,6 +56,7 @@
56 int count() const;56 int count() const;
57 void load();57 void load();
58 void clear();58 void clear();
59 bool searching();
5960
60private Q_SLOTS:61private Q_SLOTS:
61 void deviceLoaded(const Device &device);62 void deviceLoaded(const Device &device);
@@ -75,4 +76,82 @@
75 bool m_isSearching;76 bool m_isSearching;
76};77};
7778
79
80class PRINTERS_DECL_EXPORT DeviceFilter : public QSortFilterProxyModel
81{
82 Q_OBJECT
83 Q_PROPERTY(int count READ count NOTIFY countChanged)
84public:
85 explicit DeviceFilter(QObject *parent = Q_NULLPTR);
86 ~DeviceFilter();
87
88 void filterOnNormalizedMakeModel(const QString &normalizedMakeModel);
89 void filterOnUri(const QString &uri);
90 int count() const;
91protected:
92 virtual bool filterAcceptsRow(
93 int sourceRow, const QModelIndex &sourceParent) const override;
94
95Q_SIGNALS:
96 void countChanged();
97
98private Q_SLOTS:
99 void onSourceModelChanged();
100 void onSourceModelCountChanged();
101
102private:
103 QString m_normalizedMakeModel = QString::null;
104 bool m_normalizedMakeModelFilterEnabled = false;
105
106 QString m_uri = QString::null;
107 bool m_uriFilterEnabled = false;
108};
109
110/* The ConsolidatedDeviceModel is a model of ConsolidatedDevices. A
111ConsolidatedDevice is a set of Devices that share a printer name (assuming
112unique printer names).
113
114TODO: The DeviceModel should implement the consolidation by use of e.g. a
115table model, or tree model. This model can then be deleted. */
116class PRINTERS_DECL_EXPORT ConsolidatedDeviceModel : public QAbstractListModel
117{
118 Q_OBJECT
119 Q_PROPERTY(int count READ count NOTIFY countChanged)
120 Q_PROPERTY(bool searching READ searching NOTIFY searchingChanged)
121public:
122 explicit ConsolidatedDeviceModel(DeviceModel *deviceModel,
123 QObject *parent = Q_NULLPTR);
124 ~ConsolidatedDeviceModel();
125
126 enum Roles
127 {
128 // Qt::DisplayRole holds consolidated device name
129 DevicesRole = Qt::UserRole,
130 ConsolidatedNameRole,
131 ConnectionsCountRole,
132 LastRole = ConnectionsCountRole,
133 };
134
135 virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override;
136 virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
137 virtual QHash<int, QByteArray> roleNames() const override;
138
139 int count() const;
140 void load();
141 bool searching();
142 bool makeModelIsConsolidateable(const QString &makeModel) const;
143
144private Q_SLOTS:
145 void deviceModelSearchChanged();
146
147Q_SIGNALS:
148 void countChanged();
149 void searchingChanged();
150
151private:
152 DeviceModel *m_deviceModel;
153
154 QList<QPair<QString, DeviceFilter*>> m_consolidatedDevices;
155};
156
78#endif // USC_PRINTER_DEVICEMODEL_H157#endif // USC_PRINTER_DEVICEMODEL_H
79158
=== modified file 'modules/Ubuntu/Components/Extras/Printers/printer/printer.cpp'
--- modules/Ubuntu/Components/Extras/Printers/printer/printer.cpp 2017-03-21 13:01:42 +0000
+++ modules/Ubuntu/Components/Extras/Printers/printer/printer.cpp 2017-03-30 13:23:27 +0000
@@ -14,6 +14,7 @@
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */15 */
1616
17#include "cups/ppdutils.h"
17#include "i18n.h"18#include "i18n.h"
18#include "utils.h"19#include "utils.h"
1920
@@ -291,7 +292,7 @@
291 return;292 return;
292 }293 }
293294
294 QStringList vals({Utils::duplexModeToPpdChoice(duplexMode)});295 QStringList vals({PpdUtils::duplexModeToPpdChoice(duplexMode)});
295 m_backend->printerAddOption(name(), "Duplex", vals);296 m_backend->printerAddOption(name(), "Duplex", vals);
296}297}
297298
298299
=== modified file 'modules/Ubuntu/Components/Extras/Printers/printer/printerjob.cpp'
--- modules/Ubuntu/Components/Extras/Printers/printer/printerjob.cpp 2017-03-16 15:47:11 +0000
+++ modules/Ubuntu/Components/Extras/Printers/printer/printerjob.cpp 2017-03-30 13:23:27 +0000
@@ -18,9 +18,9 @@
18#include <QUrl>18#include <QUrl>
1919
20#include "backend/backend_cups.h"20#include "backend/backend_cups.h"
21#include "cups/ppdutils.h"
21#include "models/printermodel.h"22#include "models/printermodel.h"
22#include "printer/printerjob.h"23#include "printer/printerjob.h"
23#include "utils.h"
2424
25PrinterJob::PrinterJob(QString printerName,25PrinterJob::PrinterJob(QString printerName,
26 PrinterBackend *backend,26 PrinterBackend *backend,
@@ -166,7 +166,7 @@
166166
167 // No duplexMode will result in PrinterJob using defaultDuplexMode167 // No duplexMode will result in PrinterJob using defaultDuplexMode
168 QString duplex = attributes.value("Duplex").toString();168 QString duplex = attributes.value("Duplex").toString();
169 PrinterEnum::DuplexMode duplexMode = Utils::ppdChoiceToDuplexMode(duplex);169 PrinterEnum::DuplexMode duplexMode = PpdUtils::ppdChoiceToDuplexMode(duplex);
170 for (int i=0; i < m_printer->supportedDuplexModes().length(); i++) {170 for (int i=0; i < m_printer->supportedDuplexModes().length(); i++) {
171 if (m_printer->supportedDuplexModes().at(i) == duplexMode) {171 if (m_printer->supportedDuplexModes().at(i) == duplexMode) {
172 setDuplexMode(i);172 setDuplexMode(i);
173173
=== modified file 'modules/Ubuntu/Components/Extras/Printers/printers/printers.cpp'
--- modules/Ubuntu/Components/Extras/Printers/printers/printers.cpp 2017-03-21 14:51:38 +0000
+++ modules/Ubuntu/Components/Extras/Printers/printers/printers.cpp 2017-03-30 13:23:27 +0000
@@ -35,6 +35,7 @@
35 : QObject(parent)35 : QObject(parent)
36 , m_backend(backend)36 , m_backend(backend)
37 , m_devices(backend)37 , m_devices(backend)
38 , m_consolidatedDevices(&m_devices)
38 , m_drivers(backend)39 , m_drivers(backend)
39 , m_model(backend)40 , m_model(backend)
40 , m_jobs(backend)41 , m_jobs(backend)
@@ -259,6 +260,14 @@
259 m_devices.load();260 m_devices.load();
260}261}
261262
263QAbstractItemModel* Printers::consolidatedDevices()
264{
265 m_devices.load();
266 auto ret = &m_consolidatedDevices;
267 QQmlEngine::setObjectOwnership(ret, QQmlEngine::CppOwnership);
268 return ret;
269}
270
262bool Printers::addPrinter(const QString &name, const QString &ppd,271bool Printers::addPrinter(const QString &name, const QString &ppd,
263 const QString &device, const QString &description,272 const QString &device, const QString &description,
264 const QString &location)273 const QString &location)
265274
=== modified file 'modules/Ubuntu/Components/Extras/Printers/printers/printers.h'
--- modules/Ubuntu/Components/Extras/Printers/printers/printers.h 2017-03-21 14:51:38 +0000
+++ modules/Ubuntu/Components/Extras/Printers/printers/printers.h 2017-03-30 13:23:27 +0000
@@ -42,6 +42,7 @@
42 Q_PROPERTY(QAbstractItemModel* printJobs READ printJobs CONSTANT)42 Q_PROPERTY(QAbstractItemModel* printJobs READ printJobs CONSTANT)
43 Q_PROPERTY(QAbstractItemModel* drivers READ drivers CONSTANT)43 Q_PROPERTY(QAbstractItemModel* drivers READ drivers CONSTANT)
44 Q_PROPERTY(QAbstractItemModel* devices READ devices CONSTANT)44 Q_PROPERTY(QAbstractItemModel* devices READ devices CONSTANT)
45 Q_PROPERTY(QAbstractItemModel* consolidatedDevices READ consolidatedDevices CONSTANT)
45 Q_PROPERTY(QString driverFilter READ driverFilter WRITE setDriverFilter NOTIFY driverFilterChanged)46 Q_PROPERTY(QString driverFilter READ driverFilter WRITE setDriverFilter NOTIFY driverFilterChanged)
46 Q_PROPERTY(QString defaultPrinterName READ defaultPrinterName WRITE setDefaultPrinterName NOTIFY defaultPrinterNameChanged)47 Q_PROPERTY(QString defaultPrinterName READ defaultPrinterName WRITE setDefaultPrinterName NOTIFY defaultPrinterNameChanged)
47 Q_PROPERTY(QString lastMessage READ lastMessage CONSTANT)48 Q_PROPERTY(QString lastMessage READ lastMessage CONSTANT)
@@ -60,6 +61,7 @@
60 QAbstractItemModel* printJobs();61 QAbstractItemModel* printJobs();
61 QAbstractItemModel* drivers();62 QAbstractItemModel* drivers();
62 QAbstractItemModel* devices();63 QAbstractItemModel* devices();
64 QAbstractItemModel* consolidatedDevices();
63 QString driverFilter() const;65 QString driverFilter() const;
64 QString defaultPrinterName() const;66 QString defaultPrinterName() const;
65 QString lastMessage() const;67 QString lastMessage() const;
@@ -111,6 +113,7 @@
111 void provisionPrinter(const QString &name);113 void provisionPrinter(const QString &name);
112 PrinterBackend *m_backend;114 PrinterBackend *m_backend;
113 DeviceModel m_devices;115 DeviceModel m_devices;
116 ConsolidatedDeviceModel m_consolidatedDevices;
114 DriverModel m_drivers;117 DriverModel m_drivers;
115 PrinterModel m_model;118 PrinterModel m_model;
116 JobModel m_jobs;119 JobModel m_jobs;
117120
=== modified file 'modules/Ubuntu/Components/Extras/Printers/structs.h'
--- modules/Ubuntu/Components/Extras/Printers/structs.h 2017-03-16 15:34:02 +0000
+++ modules/Ubuntu/Components/Extras/Printers/structs.h 2017-03-30 13:23:27 +0000
@@ -163,8 +163,6 @@
163 }163 }
164};164};
165165
166
167
168Q_DECLARE_TYPEINFO(ColorModel, Q_PRIMITIVE_TYPE);166Q_DECLARE_TYPEINFO(ColorModel, Q_PRIMITIVE_TYPE);
169Q_DECLARE_METATYPE(ColorModel)167Q_DECLARE_METATYPE(ColorModel)
170168
171169
=== modified file 'modules/Ubuntu/Components/Extras/Printers/utils.h'
--- modules/Ubuntu/Components/Extras/Printers/utils.h 2017-02-21 10:46:29 +0000
+++ modules/Ubuntu/Components/Extras/Printers/utils.h 2017-03-30 13:23:27 +0000
@@ -19,39 +19,14 @@
1919
20#include "enums.h"20#include "enums.h"
21#include "i18n.h"21#include "i18n.h"
22#include "structs.h"22
2323#include <QMap>
24#include <cups/ppd.h>24#include <QPrinter>
25
26#include <QString>25#include <QString>
27#include <QPrinter>
2826
29class Utils27class Utils
30{28{
31public:29public:
32 static PrinterEnum::DuplexMode ppdChoiceToDuplexMode(const QString &choice)
33 {
34 if (choice == "DuplexTumble")
35 return PrinterEnum::DuplexMode::DuplexShortSide;
36 else if (choice == "DuplexNoTumble")
37 return PrinterEnum::DuplexMode::DuplexLongSide;
38 else
39 return PrinterEnum::DuplexMode::DuplexNone;
40 }
41
42 static const QString duplexModeToPpdChoice(const PrinterEnum::DuplexMode &mode)
43 {
44 switch (mode) {
45 case PrinterEnum::DuplexMode::DuplexShortSide:
46 return "DuplexTumble";
47 case PrinterEnum::DuplexMode::DuplexLongSide:
48 return "DuplexNoTumble";
49 case PrinterEnum::DuplexMode::DuplexNone:
50 default:
51 return "None";
52 }
53 }
54
55 static const QString duplexModeToUIString(const PrinterEnum::DuplexMode &mode)30 static const QString duplexModeToUIString(const PrinterEnum::DuplexMode &mode)
56 {31 {
57 switch (mode) {32 switch (mode) {
@@ -79,31 +54,55 @@
79 }54 }
80 }55 }
8156
82 static ColorModel parsePpdColorModel(const QString &name, const QString &text,57 // Parse an IEEE 1284 Device ID, so that it may be indexed by field name.
83 const QString &optionName)58 static QMap<QString, QString> parseDeviceId(const QString &did)
84 {59 {
85 ColorModel ret;60 QMap<QString, QString> idDict;
86 ret.name = name;61 const auto parts = did.split(";");
87 ret.text = text;62 const auto col = QStringLiteral(":");
88 ret.originalOption = optionName;63
8964 Q_FOREACH(auto part, parts) {
90 if (ret.name.contains("Gray") || ret.name.contains("Black")) {65 if (!part.contains(col)) {
91 ret.colorType = PrinterEnum::ColorModelType::GrayType;66 continue;
92 } else {67 }
93 ret.colorType = PrinterEnum::ColorModelType::ColorType;68 auto nameValue = part.split(col);
94 }69 idDict[nameValue[0].trimmed()] = nameValue[1].trimmed();
95 return ret;70 }
96 }71
9772 const auto man = QStringLiteral("MANUFACTURER");
98 static PrintQuality parsePpdPrintQuality(const QString &choice,73 const auto mfg = QStringLiteral("MFG");
99 const QString &text,74 if (idDict.contains(man)) {
100 const QString &optionName)75 idDict.insert(mfg, idDict.value(mfg, idDict.value(man)));
101 {76 }
102 PrintQuality quality;77
103 quality.name = choice;78 const auto mod = QStringLiteral("MODEL");
104 quality.text = text;79 const auto mdl = QStringLiteral("MDL");
105 quality.originalOption = optionName;80 if (idDict.contains(mod)) {
106 return quality;81 idDict.insert(mdl, idDict.value(mdl, idDict.value(mod)));
82 }
83
84 const auto com = QStringLiteral("COMMAND SET");
85 const auto cmd = QStringLiteral("CMD");
86 if (idDict.contains(com)) {
87 idDict.insert(cmd, idDict.value(cmd, idDict.value(com)));
88 }
89
90 const auto defaultNames = QStringList({
91 mfg, mdl, cmd,
92 QStringLiteral("CLS"),
93 QStringLiteral("DES"),
94 QStringLiteral("SN"),
95 QStringLiteral("S"),
96 QStringLiteral("P"),
97 QStringLiteral("J"),
98 });
99 Q_FOREACH(auto name, defaultNames) {
100 idDict.insert(name, idDict.value(name, QString::null));
101 }
102
103 // TODO: We'd make an array of CMD, but we don't currently need it.
104
105 return idDict;
107 }106 }
108};107};
109108
110109
=== modified file 'po/ubuntu-ui-extras.pot'
--- po/ubuntu-ui-extras.pot 2017-03-21 14:51:38 +0000
+++ po/ubuntu-ui-extras.pot 2017-03-30 13:23:27 +0000
@@ -8,7 +8,7 @@
8msgstr ""8msgstr ""
9"Project-Id-Version: ubuntu-ui-extras\n"9"Project-Id-Version: ubuntu-ui-extras\n"
10"Report-Msgid-Bugs-To: \n"10"Report-Msgid-Bugs-To: \n"
11"POT-Creation-Date: 2017-03-21 15:33+0100\n"11"POT-Creation-Date: 2017-03-30 15:15+0200\n"
12"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"12"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"13"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14"Language-Team: LANGUAGE <LL@li.org>\n"14"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -55,7 +55,7 @@
55msgid "Idle"55msgid "Idle"
56msgstr ""56msgstr ""
5757
58#: modules/Ubuntu/Components/Extras/Printers/utils.h:6158#: modules/Ubuntu/Components/Extras/Printers/utils.h:36
59msgid "Long Edge (Standard)"59msgid "Long Edge (Standard)"
60msgstr ""60msgstr ""
6161
@@ -67,7 +67,7 @@
67msgid "Normal"67msgid "Normal"
68msgstr ""68msgstr ""
6969
70#: modules/Ubuntu/Components/Extras/Printers/utils.h:6470#: modules/Ubuntu/Components/Extras/Printers/utils.h:39
71msgid "One Sided"71msgid "One Sided"
72msgstr ""72msgstr ""
7373
@@ -91,7 +91,7 @@
91msgid "Rotate"91msgid "Rotate"
92msgstr ""92msgstr ""
9393
94#: modules/Ubuntu/Components/Extras/Printers/utils.h:5994#: modules/Ubuntu/Components/Extras/Printers/utils.h:34
95msgid "Short Edge (Flip)"95msgid "Short Edge (Flip)"
96msgstr ""96msgstr ""
9797
@@ -99,7 +99,7 @@
99msgid "Stopped"99msgid "Stopped"
100msgstr ""100msgstr ""
101101
102#: modules/Ubuntu/Components/Extras/Printers/printers/printers.cpp:387102#: modules/Ubuntu/Components/Extras/Printers/printers/printers.cpp:396
103msgid "Test page"103msgid "Test page"
104msgstr ""104msgstr ""
105105
106106
=== modified file 'tests/unittests/Printers/CMakeLists.txt'
--- tests/unittests/Printers/CMakeLists.txt 2017-03-10 10:28:20 +0000
+++ tests/unittests/Printers/CMakeLists.txt 2017-03-30 13:23:27 +0000
@@ -56,3 +56,19 @@
56add_executable(testPrintersDeviceModel tst_printerdevicemodel.cpp ${MOCK_SOURCES})56add_executable(testPrintersDeviceModel tst_printerdevicemodel.cpp ${MOCK_SOURCES})
57target_link_libraries(testPrintersDeviceModel UbuntuComponentsExtrasPrintersQml Qt5::Test Qt5::Gui)57target_link_libraries(testPrintersDeviceModel UbuntuComponentsExtrasPrintersQml Qt5::Test Qt5::Gui)
58add_test(tst_printerdevicemodel testPrintersDeviceModel)58add_test(tst_printerdevicemodel testPrintersDeviceModel)
59
60add_executable(testPrintersPpdUtils tst_ppdutils.cpp)
61target_link_libraries(testPrintersPpdUtils UbuntuComponentsExtrasPrintersQml Qt5::Test Qt5::Gui)
62add_test(tst_ppdutils testPrintersPpdUtils)
63
64add_executable(testPrintersUtils tst_utils.cpp)
65target_link_libraries(testPrintersUtils UbuntuComponentsExtrasPrintersQml Qt5::Test Qt5::Gui)
66add_test(tst_utils testPrintersUtils)
67
68add_executable(testPrintersDeviceFilter tst_devicefilter.cpp ${MOCK_SOURCES})
69target_link_libraries(testPrintersDeviceFilter UbuntuComponentsExtrasPrintersQml Qt5::Test Qt5::Gui)
70add_test(tst_devicefilter testPrintersDeviceFilter)
71
72add_executable(testPrintersConsolidatedDeviceModel tst_consolidateddevicemodel.cpp ${MOCK_SOURCES})
73target_link_libraries(testPrintersConsolidatedDeviceModel UbuntuComponentsExtrasPrintersQml Qt5::Test Qt5::Gui)
74add_test(tst_consolidateddevicemodel testPrintersConsolidatedDeviceModel)
5975
=== modified file 'tests/unittests/Printers/mockbackend.h'
--- tests/unittests/Printers/mockbackend.h 2017-03-15 17:42:21 +0000
+++ tests/unittests/Printers/mockbackend.h 2017-03-30 13:23:27 +0000
@@ -18,7 +18,7 @@
18#define USC_PRINTERS_MOCK_BACKEND_H18#define USC_PRINTERS_MOCK_BACKEND_H
1919
20#include "backend/backend.h"20#include "backend/backend.h"
21#include "utils.h"21#include "cups/ppdutils.h"
2222
23class MockPrinterBackend : public PrinterBackend23class MockPrinterBackend : public PrinterBackend
24{24{
@@ -235,7 +235,7 @@
235 attributes.insert("ColorModel", job->getColorModel().name);235 attributes.insert("ColorModel", job->getColorModel().name);
236 attributes.insert("CompletedTime", job->completedTime());236 attributes.insert("CompletedTime", job->completedTime());
237 attributes.insert("CreationTime", job->creationTime());237 attributes.insert("CreationTime", job->creationTime());
238 attributes.insert("Duplex", Utils::duplexModeToPpdChoice(job->getDuplexMode()));238 attributes.insert("Duplex", PpdUtils::duplexModeToPpdChoice(job->getDuplexMode()));
239 attributes.insert("impressionsCompleted", job->impressionsCompleted());239 attributes.insert("impressionsCompleted", job->impressionsCompleted());
240 attributes.insert("landscape", job->landscape());240 attributes.insert("landscape", job->landscape());
241 attributes.insert("messages", job->messages());241 attributes.insert("messages", job->messages());
@@ -322,7 +322,7 @@
322 virtual PrinterEnum::DuplexMode defaultDuplexMode() const override322 virtual PrinterEnum::DuplexMode defaultDuplexMode() const override
323 {323 {
324 auto ppdMode = printerGetOption(printerName(), "Duplex").toString();324 auto ppdMode = printerGetOption(printerName(), "Duplex").toString();
325 return Utils::ppdChoiceToDuplexMode(ppdMode);325 return PpdUtils::ppdChoiceToDuplexMode(ppdMode);
326 }326 }
327327
328 virtual QList<PrinterEnum::DuplexMode> supportedDuplexModes() const override328 virtual QList<PrinterEnum::DuplexMode> supportedDuplexModes() const override
329329
=== added file 'tests/unittests/Printers/tst_consolidateddevicemodel.cpp'
--- tests/unittests/Printers/tst_consolidateddevicemodel.cpp 1970-01-01 00:00:00 +0000
+++ tests/unittests/Printers/tst_consolidateddevicemodel.cpp 2017-03-30 13:23:27 +0000
@@ -0,0 +1,162 @@
1/*
2 * Copyright (C) 2017 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#include "mockbackend.h"
18
19#include "backend/backend.h"
20#include "models/devicemodel.h"
21#include "structs.h"
22
23#include <QDebug>
24#include <QObject>
25#include <QSignalSpy>
26#include <QTest>
27
28Q_DECLARE_METATYPE(ConsolidatedDeviceModel::Roles)
29
30class TestConsolidatedDeviceModel : public QObject
31{
32 Q_OBJECT
33private Q_SLOTS:
34 void init()
35 {
36 m_backend = new MockPrinterBackend();
37 m_devicesModel = new DeviceModel(m_backend);
38 }
39 void cleanup()
40 {
41 QSignalSpy destroyedSpy(m_devicesModel, SIGNAL(destroyed(QObject*)));
42 m_devicesModel->deleteLater();
43 QTRY_COMPARE(destroyedSpy.count(), 1);
44 delete m_backend;
45 }
46 void testConsolidation_data()
47 {
48 QTest::addColumn<QList<Device>>("devices");
49 QTest::addColumn<int>("targetConsolidatedDeviceCount");
50
51 {
52 Device a; a.uri = "dnssd://foo/bar"; a.makeModel = "foo";
53 Device b; b.uri = "ipps://bar/qux"; b.makeModel = "bar";
54
55 QList<Device> devs({a, b});
56 QTest::newRow("no consolidation") << devs << 2;
57 }
58 {
59 Device a; a.uri = "dnssd://foo/bar"; a.makeModel = "foo";
60 Device b; b.uri = "ipps://foo/bar"; b.makeModel = "foo";
61
62 QList<Device> devs({a, b});
63 QTest::newRow("consolidation") << devs << 1;
64 }
65 {
66 Device a; a.uri = "dnssd://foo/bar"; a.makeModel = "foo";
67 Device b; b.uri = "ipps://foo/bar"; b.makeModel = "foo";
68 Device c; c.uri = "socket://baz/qux"; c.makeModel = "baz";
69
70 QList<Device> devs({a, b, c});
71 QTest::newRow("mixed") << devs << 2;
72 }
73 {
74 Device a; a.uri = "dnssd://foo/bar"; a.makeModel = "foo";
75 Device b; b.uri = "ipps://foo/bar"; b.makeModel = "";
76 Device c; c.uri = "socket://baz/qux"; c.makeModel = "";
77
78 QList<Device> devs({a, b, c});
79 QTest::newRow("don't consolidate make-and-model-less devices") << devs << 1;
80 }
81 {
82 Device a; a.uri = "dnssd://foo/bar"; a.makeModel = "Unknown";
83 Device b; b.uri = "ipps://foo/bar"; b.makeModel = "Unknown";
84 Device c; c.uri = "socket://baz/qux"; c.makeModel = "Unknown";
85
86 QList<Device> devs({a, b, c});
87 QTest::newRow("Unknown devices aren't even considered.") << devs << 0;
88 }
89 }
90 void testConsolidation()
91 {
92 QFETCH(QList<Device>, devices);
93 QFETCH(int, targetConsolidatedDeviceCount);
94
95 ConsolidatedDeviceModel model(m_devicesModel);
96
97 Q_FOREACH(auto device, devices) {
98 m_backend->mockDeviceFound(device);
99 }
100 m_backend->deviceSearchFinished();
101
102 QCOMPARE(model.rowCount(), targetConsolidatedDeviceCount);
103 }
104 void testRoles_data()
105 {
106 QTest::addColumn<ConsolidatedDeviceModel::Roles>("role");
107 QTest::addColumn<QVariant>("value");
108 QTest::addColumn<QList<Device>>("devices");
109
110 {
111 Device a; a.uri = "ipp://foo/bar"; a.makeModel = "HP LaserFjert";
112 Device b; a.uri = "ipp://foo/bar"; b.makeModel = "HP LaserFjert";
113
114 QList<Device> devs({a, b});
115
116 ConsolidatedDeviceModel::Roles role(
117 ConsolidatedDeviceModel::ConsolidatedNameRole
118 );
119 QVariant value(QString("HP LaserFjert"));
120 QTest::newRow("ConsolidatedNameRole") << role << value << devs;
121 }
122 }
123 void testRoles()
124 {
125 QFETCH(ConsolidatedDeviceModel::Roles, role);
126 QFETCH(QVariant, value);
127 QFETCH(QList<Device>, devices);
128
129 ConsolidatedDeviceModel model(m_devicesModel);
130
131 Q_FOREACH(auto device, devices) {
132 m_backend->mockDeviceFound(device);
133 }
134 m_backend->deviceSearchFinished();
135
136 QCOMPARE(model.data(model.index(0), role), value);
137 }
138 void testDevicesRole()
139 {
140 Device a; a.uri = "ipp://foo/bar"; a.makeModel = "foo";
141 Device b; b.uri = "dnssd://foo/bar"; b.makeModel = "foo";
142
143 ConsolidatedDeviceModel model(m_devicesModel);
144
145 m_backend->mockDeviceFound(a);
146 m_backend->mockDeviceFound(b);
147 m_backend->deviceSearchFinished();
148
149 auto filter = model.data(
150 model.index(0), ConsolidatedDeviceModel::Roles::DevicesRole
151 ).value<DeviceFilter*>();
152
153 QCOMPARE(filter->count(), 2);
154 }
155private:
156 MockPrinterBackend *m_backend;
157 DeviceModel *m_devicesModel;
158};
159
160QTEST_GUILESS_MAIN(TestConsolidatedDeviceModel)
161#include "tst_consolidateddevicemodel.moc"
162
0163
=== added file 'tests/unittests/Printers/tst_devicefilter.cpp'
--- tests/unittests/Printers/tst_devicefilter.cpp 1970-01-01 00:00:00 +0000
+++ tests/unittests/Printers/tst_devicefilter.cpp 2017-03-30 13:23:27 +0000
@@ -0,0 +1,106 @@
1/*
2 * Copyright (C) 2017 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#include "mockbackend.h"
18
19#include "cups/ppdutils.h"
20#include "models/devicemodel.h"
21
22#include <QObject>
23#include <QSignalSpy>
24#include <QScopedPointer>
25#include <QTest>
26
27class TestDeviceFilter : public QObject
28{
29 Q_OBJECT
30private Q_SLOTS:
31 void testFilterOnMakeModel()
32 {
33 QScopedPointer<MockPrinterBackend> backend(new MockPrinterBackend);
34 DeviceModel model(backend.data());
35
36 Device dev1; dev1.uri = "ipp://foo/bar";
37 dev1.makeModel = "foo";
38 Device dev2; dev2.uri = "dnssd://bar/qux";
39 dev2.makeModel = "bar";
40
41 backend->mockDeviceFound(dev1);
42 backend->mockDeviceFound(dev2);
43
44 DeviceFilter filter;
45 filter.setSourceModel(&model);
46
47 QCOMPARE(filter.count(), 2);
48
49 // Install filter.
50 filter.filterOnNormalizedMakeModel("foo");
51 QCOMPARE(filter.count(), 1);
52 }
53
54 /* Test that "Foo BazJet-5000" and "Foo BazJet 5000" are accepted in same
55 filter. */
56 void testNormalizedFiltering()
57 {
58 QScopedPointer<MockPrinterBackend> backend(new MockPrinterBackend);
59 DeviceModel model(backend.data());
60
61 Device dev1; dev1.uri = "ipp://foo/bar";
62 dev1.makeModel = "Foo BazJet 5000";
63 Device dev2; dev2.uri = "dnssd://bar/qux";
64 dev2.makeModel = "Foo BazJet-5000";
65 Device dev3; dev3.uri = "dnssd://bar/baz";
66 dev3.makeModel = "Foo DeskQux 350";
67
68 backend->mockDeviceFound(dev1);
69 backend->mockDeviceFound(dev2);
70 backend->mockDeviceFound(dev3);
71
72 DeviceFilter filter;
73 filter.setSourceModel(&model);
74
75 // Install filter.
76 filter.filterOnNormalizedMakeModel(
77 PpdUtils::normalize("Foo BazJet 5000"));
78 QCOMPARE(filter.count(), 2);
79 }
80 void testFilterOnUri()
81 {
82 QScopedPointer<MockPrinterBackend> backend(new MockPrinterBackend);
83 DeviceModel model(backend.data());
84
85 Device dev1; dev1.uri = "ipp://foo/bar";
86 dev1.id = "foo";
87 Device dev2; dev2.uri = "dnssd://bar/qux";
88 dev2.id = "bar";
89
90 backend->mockDeviceFound(dev1);
91 backend->mockDeviceFound(dev2);
92
93 DeviceFilter filter;
94 filter.setSourceModel(&model);
95
96 QCOMPARE(filter.count(), 2);
97
98 // Install filter.
99 filter.filterOnUri("ipp://foo/bar");
100 QCOMPARE(filter.count(), 1);
101 }
102};
103
104QTEST_GUILESS_MAIN(TestDeviceFilter)
105#include "tst_devicefilter.moc"
106
0107
=== modified file 'tests/unittests/Printers/tst_jobmodel.cpp'
--- tests/unittests/Printers/tst_jobmodel.cpp 2017-03-15 17:30:21 +0000
+++ tests/unittests/Printers/tst_jobmodel.cpp 2017-03-30 13:23:27 +0000
@@ -19,6 +19,7 @@
19#include "backend/backend.h"19#include "backend/backend.h"
20#include "models/jobmodel.h"20#include "models/jobmodel.h"
21#include "printers/printers.h"21#include "printers/printers.h"
22#include "utils.h"
2223
23#include <QDebug>24#include <QDebug>
24#include <QObject>25#include <QObject>
2526
=== added file 'tests/unittests/Printers/tst_ppdutils.cpp'
--- tests/unittests/Printers/tst_ppdutils.cpp 1970-01-01 00:00:00 +0000
+++ tests/unittests/Printers/tst_ppdutils.cpp 2017-03-30 13:23:27 +0000
@@ -0,0 +1,138 @@
1/*
2 * Copyright (C) 2017 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#include "cups/ppdutils.h"
18
19#include <QObject>
20#include <QTest>
21
22typedef QPair<QString, QString> MakeModel;
23Q_DECLARE_METATYPE(MakeModel)
24
25class TstPpdUtils : public QObject
26{
27 Q_OBJECT
28private Q_SLOTS:
29 void testSplitMakeModel_data()
30 {
31 QTest::addColumn<QString>("makeModel");
32 QTest::addColumn<MakeModel>("expected");
33
34 {
35 QString actual("HP HP Officejet Pro 8610");
36 auto expected = MakeModel("HP", "OfficeJet Pro 8610");
37 QTest::newRow("duplicate hp") << actual << expected;
38 }
39 {
40 QString actual("Officejet Officejet Pro 8610");
41 auto expected = MakeModel("HP", "OfficeJet Pro 8610");
42 QTest::newRow("officejet officejet (yes it actually happens)") << actual << expected;
43 }
44 {
45 QString actual("laserjet 4500");
46 auto expected = MakeModel("HP", "LaserJet 4500");
47 QTest::newRow("just a hp model") << actual << expected;
48 }
49 {
50 QString actual("HP OfficeJet Pro 8600");
51 auto expected = MakeModel("HP", "OfficeJet Pro 8600");
52 QTest::newRow("a sane hp officejet") << actual << expected;
53 }
54 {
55 QString actual("HP Foo All-In-One");
56 auto expected = MakeModel("HP", "Foo");
57 QTest::newRow("remove all-in-one") << actual << expected;
58 }
59 {
60 QString actual("Hewlett-Packard SmartJet 50 v0.10");
61 auto expected = MakeModel("HP", "SmartJet 50");
62 QTest::newRow("Hewlett-Packard SmartJet 50 v0.10") << actual << expected;
63 }
64 {
65 QString actual("Foobar Bazjet 9000");
66 auto expected = MakeModel("Foobar", "Bazjet 9000");
67 QTest::newRow("the excellent foobar bazjet 9000") << actual << expected;
68 }
69 {
70 QString actual("HP LaserJet 4 Plus v2013.111 Postscript (recommended)");
71 auto expected = MakeModel("HP", "LaserJet 4 Plus");
72 QTest::newRow("example #1") << actual << expected;
73 }
74 {
75 QString actual("Canon MG4100 series Ver.3.90");
76 auto expected = MakeModel("Canon", "MG4100");
77 QTest::newRow("example #2") << actual << expected;
78 }
79 {
80 QString actual("Xerox Foo Printer");
81 auto expected = MakeModel("Xerox", "Foo");
82 QTest::newRow("remove ignored suffix 'printer'") << actual << expected;
83 }
84 {
85 QString actual("hp lj 4500");
86 auto expected = MakeModel("HP", "LaserJet 4500");
87 QTest::newRow("shortened hp model name") << actual << expected;
88 }
89 {
90 QString actual("okipage foobar");
91 auto expected = MakeModel("Oki", "okipage foobar");
92 QTest::newRow("some okipage printer") << actual << expected;
93 }
94 }
95 void testSplitMakeModel()
96 {
97 QFETCH(QString, makeModel);
98 QFETCH(MakeModel, expected);
99 auto actual = PpdUtils::splitMakeModel(makeModel);
100 QCOMPARE(actual, expected);
101 }
102 void testNormalize_data()
103 {
104 QTest::addColumn<QString>("actual");
105 QTest::addColumn<QString>("expected");
106
107 {
108 QString actual("Epson PM-A820");
109 QString expected("epson pm a 820");
110 QTest::newRow("epson with dash") << actual << expected;
111 }
112 {
113 QString actual("Epson PM A820");
114 QString expected("epson pm a 820");
115 QTest::newRow("epson without dash") << actual << expected;
116 }
117 {
118 QString actual("HP PhotoSmart C 8100");
119 QString expected("hp photosmart c 8100");
120 QTest::newRow("C 8100") << actual << expected;
121 }
122 {
123 QString actual("HP PhotoSmart C8100");
124 QString expected("hp photosmart c 8100");
125 QTest::newRow("C8100") << actual << expected;
126 }
127 }
128 void testNormalize()
129 {
130 QFETCH(QString, actual);
131 QFETCH(QString, expected);
132 actual = PpdUtils::normalize(actual);
133 QCOMPARE(actual, expected);
134 }
135};
136
137QTEST_GUILESS_MAIN(TstPpdUtils)
138#include "tst_ppdutils.moc"
0139
=== modified file 'tests/unittests/Printers/tst_printer.cpp'
--- tests/unittests/Printers/tst_printer.cpp 2017-03-20 13:31:55 +0000
+++ tests/unittests/Printers/tst_printer.cpp 2017-03-30 13:23:27 +0000
@@ -14,12 +14,12 @@
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */15 */
1616
17#include "utils.h"
1817
19#include "mockbackend.h"18#include "mockbackend.h"
2019
21#include "backend/backend.h"20#include "backend/backend.h"
22#include "backend/backend_pdf.h"21#include "backend/backend_pdf.h"
22#include "cups/ppdutils.h"
23#include "printer/printer.h"23#include "printer/printer.h"
2424
25#include <QDebug>25#include <QDebug>
@@ -158,7 +158,7 @@
158 QStringList duplexVals = duplexVar.toStringList();158 QStringList duplexVals = duplexVar.toStringList();
159 QCOMPARE(159 QCOMPARE(
160 duplexVals.at(0),160 duplexVals.at(0),
161 (QString) Utils::duplexModeToPpdChoice(PrinterEnum::DuplexMode::DuplexLongSide)161 (QString) PpdUtils::duplexModeToPpdChoice(PrinterEnum::DuplexMode::DuplexLongSide)
162 );162 );
163 }163 }
164 void testSetDefaultPageSize_data()164 void testSetDefaultPageSize_data()
165165
=== modified file 'tests/unittests/Printers/tst_printerdevicemodel.cpp'
--- tests/unittests/Printers/tst_printerdevicemodel.cpp 2017-03-08 16:13:02 +0000
+++ tests/unittests/Printers/tst_printerdevicemodel.cpp 2017-03-30 13:23:27 +0000
@@ -60,6 +60,11 @@
60 Device d; d.uri = "dnssd:";60 Device d; d.uri = "dnssd:";
61 QTest::newRow("non-empty uri, malformed") << d << false;61 QTest::newRow("non-empty uri, malformed") << d << false;
62 }62 }
63 {
64 Device d; d.uri = "dnssd://foo/bar";
65 d.makeModel = "Unknown";
66 QTest::newRow("non-empty uri, but Unknown make/model") << d << false;
67 }
63 }68 }
64 void testAcceptedDevices()69 void testAcceptedDevices()
65 {70 {
@@ -152,6 +157,36 @@
152 m_backend->mockDeviceFound(device);157 m_backend->mockDeviceFound(device);
153 QCOMPARE(m_model->data(m_model->index(0), role), value);158 QCOMPARE(m_model->data(m_model->index(0), role), value);
154 }159 }
160 void testFiltering_data()
161 {
162 QTest::addColumn<QList<Device>>("devices");
163 QTest::addColumn<QString>("filterId");
164 QTest::addColumn<int>("targetCount");
165 QTest::addColumn<QStringList>("targetIds");
166
167 {
168 Device a; a.uri = "dnssd://foo/bar"; a.id = "foo";
169 Device b; b.uri = "ipps://bar/qux"; b.id = "bar";
170
171 QList<Device> devs({a, b});
172 QTest::newRow("no filtering") << devs << "" << 2 << QStringList({"foo", "bar"});
173 }
174 }
175 void testFiltering()
176 {
177 QFETCH(QList<Device>, devices);
178 QFETCH(QString, filterId);
179 QFETCH(int, targetCount);
180 QFETCH(QStringList, targetIds);
181
182 Q_FOREACH(auto dev, devices) {
183 m_backend->mockDeviceFound(dev);
184 }
185
186 DeviceFilter filter;
187 filter.setSourceModel(m_model);
188 QCOMPARE(filter.count(), targetCount);
189 }
155private:190private:
156 MockPrinterBackend *m_backend;191 MockPrinterBackend *m_backend;
157 DeviceModel *m_model;192 DeviceModel *m_model;
158193
=== modified file 'tests/unittests/Printers/tst_printermodel.cpp'
--- tests/unittests/Printers/tst_printermodel.cpp 2017-03-10 13:15:27 +0000
+++ tests/unittests/Printers/tst_printermodel.cpp 2017-03-30 13:23:27 +0000
@@ -17,9 +17,11 @@
17#include "mockbackend.h"17#include "mockbackend.h"
1818
19#include "backend/backend.h"19#include "backend/backend.h"
20#include "cups/ppdutils.h"
20#include "models/printermodel.h"21#include "models/printermodel.h"
21#include "printer/printer.h"22#include "printer/printer.h"
22#include "printer/printerjob.h"23#include "printer/printerjob.h"
24#include "utils.h"
2325
24#include <QDebug>26#include <QDebug>
25#include <QObject>27#include <QObject>
@@ -231,7 +233,7 @@
231 PrinterBackend* backend = new MockPrinterBackend("a-printer");233 PrinterBackend* backend = new MockPrinterBackend("a-printer");
232 ((MockPrinterBackend*) backend)->m_supportedDuplexModes = modes;234 ((MockPrinterBackend*) backend)->m_supportedDuplexModes = modes;
233 ((MockPrinterBackend*) backend)->printerOptions["a-printer"].insert(235 ((MockPrinterBackend*) backend)->printerOptions["a-printer"].insert(
234 "Duplex", QVariant::fromValue(Utils::duplexModeToPpdChoice(PrinterEnum::DuplexMode::DuplexLongSide))236 "Duplex", QVariant::fromValue(PpdUtils::duplexModeToPpdChoice(PrinterEnum::DuplexMode::DuplexLongSide))
235 );237 );
236238
237 auto printerA = QSharedPointer<Printer>(new Printer(backend));239 auto printerA = QSharedPointer<Printer>(new Printer(backend));
238240
=== added file 'tests/unittests/Printers/tst_utils.cpp'
--- tests/unittests/Printers/tst_utils.cpp 1970-01-01 00:00:00 +0000
+++ tests/unittests/Printers/tst_utils.cpp 2017-03-30 13:23:27 +0000
@@ -0,0 +1,107 @@
1/*
2 * Copyright (C) 2017 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#include "utils.h"
18
19#include <QObject>
20#include <QTest>
21
22typedef QMap<QString, QString> IdDict;
23Q_DECLARE_METATYPE(IdDict)
24
25class TstPrinterUtils : public QObject
26{
27 Q_OBJECT
28private Q_SLOTS:
29 void tstParseDeviceId_data()
30 {
31 QTest::addColumn<QString>("deviceId");
32 QTest::addColumn<IdDict>("expected");
33
34 {
35 QString actual("MFG:Hewlett-Packard;CMD:PJL,BIDI-ECP,PCLXL,PCL,PDF,PJL,POSTSCRIPT;MDL:HP Color LaserJet CM3530 MFP;CLS:PRINTER;DES:Hewlett-Packard Color LaserJet CM3530 MFP;");
36 auto expected = QMap<QString, QString>{
37 {"MFG", "Hewlett-Packard"},
38 {"MDL", "HP Color LaserJet CM3530 MFP"},
39 {"CMD", "PJL,BIDI-ECP,PCLXL,PCL,PDF,PJL,POSTSCRIPT"},
40 {"CLS", "PRINTER"},
41 {"DES", "Hewlett-Packard Color LaserJet CM3530 MFP"},
42 {"SN", QString::null},
43 {"S", QString::null},
44 {"P", QString::null},
45 {"J", QString::null},
46 };
47 QTest::newRow("duplicate hp") << actual << expected;
48 }
49 {
50 QString actual("MFG:HP;MDL:HP Officejet Pro 8610;CMD:PCL,JPEG,URF;");
51 auto expected = QMap<QString, QString>{
52 {"MFG", "HP"},
53 {"MDL", "HP Officejet Pro 8610"},
54 {"CMD", "PCL,JPEG,URF"},
55 {"CLS", QString::null},
56 {"DES", QString::null},
57 {"SN", QString::null},
58 {"S", QString::null},
59 {"P", QString::null},
60 {"J", QString::null},
61 };
62 QTest::newRow("actual hp device id") << actual << expected;
63 }
64 {
65 QString actual("MFG:HP;MDL:Officejet Pro 8600;CMD:PCL3GUI,PCL3,PJL,JPEG,PCLM,URF,DW-PCL,802.11,802.3,DESKJET,DYN;CLS:PRINTER;DES:CM749A;CID:HPIJVIPAV2;LEDMDIS:USB#FF#CC#00,USB#07#01#02;SN:CN371DWH1V05KC;S:038080C484201021005a00800004518005a4418003c4618005a4118005a;Z:0102,05000009000008000008000008000008,0600,0700000000000000000000,0b0000000000000000000099ba0000000099b50000000099b90000000099b6,0c0,0e00000000000000000000,0f00000000000000000000,10000002000008000008000008000008,110,12002,150,17000000000000000000000000000000,181;");
66 auto expected = QMap<QString, QString>{
67 {"MFG", "HP"},
68 {"MDL", "Officejet Pro 8600"},
69 {"CMD", "PCL3GUI,PCL3,PJL,JPEG,PCLM,URF,DW-PCL,802.11,802.3,DESKJET,DYN"},
70 {"CLS", "PRINTER"},
71 {"DES", "CM749A"},
72 {"SN", "CN371DWH1V05KC"},
73 {"S", "038080C484201021005a00800004518005a4418003c4618005a4118005a"},
74 {"P", QString::null},
75 {"J", QString::null},
76 {"Z", "0102,05000009000008000008000008000008,0600,0700000000000000000000,0b0000000000000000000099ba0000000099b50000000099b90000000099b6,0c0,0e00000000000000000000,0f00000000000000000000,10000002000008000008000008000008,110,12002,150,17000000000000000000000000000000,181"},
77 {"CID", "HPIJVIPAV2"},
78 {"LEDMDIS", "USB#FF#CC#00,USB#07#01#02"}
79 };
80 QTest::newRow("actual, but crazy, hp device id") << actual << expected;
81 }
82 {
83 QString actual("MFG:EPSON;CMD:ESCPL2,BDC,D4;MDL:Stylus Photo RX420;CLS:PRINTER;DES:EPSON Stylus Photo RX420;");
84 auto expected = QMap<QString, QString>{
85 {"MFG", "EPSON"},
86 {"MDL", "Stylus Photo RX420"},
87 {"CMD", "ESCPL2,BDC,D4"},
88 {"CLS", "PRINTER"},
89 {"DES", "EPSON Stylus Photo RX420"},
90 {"SN", QString::null},
91 {"S", QString::null},
92 {"P", QString::null},
93 {"J", QString::null},
94 };
95 QTest::newRow("some epson stylus") << actual << expected;
96 }
97 }
98 void tstParseDeviceId()
99 {
100 QFETCH(QString, deviceId);
101 QFETCH(IdDict, expected);
102 QCOMPARE(Utils::parseDeviceId(deviceId), expected);
103 }
104};
105
106QTEST_GUILESS_MAIN(TstPrinterUtils)
107#include "tst_utils.moc"

Subscribers

People subscribed via source and target branches