Merge lp:~jonas-drange/ubuntu-ui-extras/consolidated-devices into lp:ubuntu-ui-extras/staging
- consolidated-devices
- Merge into staging
Status: | Needs review |
---|---|
Proposed branch: | lp:~jonas-drange/ubuntu-ui-extras/consolidated-devices |
Merge into: | lp:ubuntu-ui-extras/staging |
Diff against target: |
2545 lines (+1738/-170) 30 files modified
debian/control (+1/-0) 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 (+85/-2) modules/Ubuntu/Components/Extras/Printers/cups/devicesearcher.h (+15/-0) modules/Ubuntu/Components/Extras/Printers/cups/ppdutils.cpp (+345/-0) modules/Ubuntu/Components/Extras/Printers/cups/ppdutils.h (+87/-0) modules/Ubuntu/Components/Extras/Printers/models/devicemodel.cpp (+266/-2) modules/Ubuntu/Components/Extras/Printers/models/devicemodel.h (+82/-1) modules/Ubuntu/Components/Extras/Printers/models/drivermodel.cpp (+14/-0) modules/Ubuntu/Components/Extras/Printers/models/drivermodel.h (+1/-0) 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 (+34/-29) modules/Ubuntu/Components/Extras/Printers/utils.h (+60/-53) po/ubuntu-ui-extras.pot (+9/-5) tests/unittests/Printers/CMakeLists.txt (+16/-0) tests/unittests/Printers/mockbackend.h (+3/-3) tests/unittests/Printers/tst_consolidateddevicemodel.cpp (+186/-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 (+3/-3) tests/unittests/Printers/tst_printerdevice.cpp (+19/-22) tests/unittests/Printers/tst_printerdevicemodel.cpp (+58/-10) 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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
system-apps-ci-bot | continuous-integration | Approve | |
Andrew Hayzen (community) | Approve | ||
Review via email: mp+321497@code.launchpad.net |
This proposal supersedes a proposal from 2017-03-22.
Commit message
Adds ConsolidatedDev
Description of the change
Adds ConsolidatedDev
Andrew Hayzen (ahayzen) wrote : Posted in a previous version of this proposal | # |
Andrew Hayzen (ahayzen) wrote : Posted in a previous version of this proposal | # |
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 ppdChoiceToDupl
Jonas G. Drange (jonas-drange) wrote : Posted in a previous version of this proposal | # |
Good point. Yeah, I think that should be doable.
Jonas G. Drange (jonas-drange) wrote : | # |
I'm proposing it against staging, as we want to release this before a printer-staging -> staging dance.
system-apps-ci-bot (system-apps-ci-bot) wrote : | # |
PASSED: Continuous integration, rev:164
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
Click here to trigger a rebuild:
https:/
Andrew Hayzen (ahayzen) wrote : | # |
From the silo building...
/<<BUILDDIR>
/<<BUILDDIR>
Guess this needs pulling of staging to include my changes, and then change to use PpdUtils.
Andrew Hayzen (ahayzen) wrote : | # |
Looks good so far, just 3 inline comments.
1) It seems like this should be set?
Otherwise further down this if will be false
if (makeLower == "hp") {
...
}
2) Same here
3) Should this then do
makeLower = make.toLower();
Andrew Hayzen (ahayzen) wrote : | # |
All tests pass, feedback fixed. LGTM :-)
system-apps-ci-bot (system-apps-ci-bot) wrote : | # |
PASSED: Continuous integration, rev:169
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
Click here to trigger a rebuild:
https:/
system-apps-ci-bot (system-apps-ci-bot) wrote : | # |
FAILED: Autolanding.
More details in the following jenkins job:
https:/
Executed test runs:
FAILURE: https:/
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
FAILURE: https:/
FAILURE: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
Andrew Hayzen (ahayzen) wrote : | # |
The changes look good.
One inline comment:
1) Looks like this is missing
proc.start(program, arguments);
?
Also I think ubuntu-ui-extras should now depends or at least recommend the package hplip.
- 173. By Jonas G. Drange
-
drop the device id for fax
system-apps-ci-bot (system-apps-ci-bot) wrote : | # |
PASSED: Continuous integration, rev:172
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
Click here to trigger a rebuild:
https:/
Andrew Hayzen (ahayzen) wrote : | # |
This LGTM now, all it misses is usb fax's. We can improve that later if required.
system-apps-ci-bot (system-apps-ci-bot) wrote : | # |
PASSED: Continuous integration, rev:173
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
Click here to trigger a rebuild:
https:/
- 174. By Jonas G. Drange
-
uses info as a last resort
system-apps-ci-bot (system-apps-ci-bot) wrote : | # |
PASSED: Continuous integration, rev:174
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
Click here to trigger a rebuild:
https:/
Andrew Hayzen (ahayzen) wrote : | # |
Changes look good, and there is a test :-)
Only thing is if you want todo what we discussed about having a way of adding printers that are "unknown". So that you can pick the name/uri and it fills the fields it knows but lets the user pick the driver etc.
- 175. By Jonas G. Drange
-
make sure non-consolidateable devices are kept
system-apps-ci-bot (system-apps-ci-bot) wrote : | # |
FAILED: Continuous integration, rev:175
https:/
Executed test runs:
FAILURE: https:/
SUCCESS: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
Click here to trigger a rebuild:
https:/
- 176. By Jonas G. Drange
-
fixes test that now broke, but had to change, and add .get to drivermodel
system-apps-ci-bot (system-apps-ci-bot) wrote : | # |
PASSED: Continuous integration, rev:176
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
Click here to trigger a rebuild:
https:/
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
1 | === modified file 'debian/control' |
2 | --- debian/control 2017-02-24 20:38:24 +0000 |
3 | +++ debian/control 2017-04-07 12:51:46 +0000 |
4 | @@ -37,6 +37,7 @@ |
5 | qml-module-qtquick2, |
6 | qml-module-ubuntu-components, |
7 | qml-module-qtquick-window2, |
8 | +Suggests: hplip, |
9 | Provides: qtdeclarative5-ubuntu-ui-extras0.1 |
10 | Conflicts: qtdeclarative5-ubuntu-ui-extras0.1 |
11 | Replaces: qtdeclarative5-ubuntu-ui-extras0.1 |
12 | |
13 | === modified file 'modules/Ubuntu/Components/Extras/Example/Printers.qml' |
14 | --- modules/Ubuntu/Components/Extras/Example/Printers.qml 2017-03-21 23:02:42 +0000 |
15 | +++ modules/Ubuntu/Components/Extras/Example/Printers.qml 2017-04-07 12:51:46 +0000 |
16 | @@ -685,7 +685,7 @@ |
17 | verticalCenter: parent.verticalCenter |
18 | } |
19 | property var target |
20 | - Component.onCompleted: target = Printers.devices |
21 | + Component.onCompleted: target = Printers.consolidatedDevices |
22 | running: target.searching |
23 | } |
24 | } |
25 | @@ -699,27 +699,12 @@ |
26 | topMargin: units.gu(2) |
27 | } |
28 | height: contentItem.childrenRect.height |
29 | - model: Printers.devices |
30 | + model: Printers.consolidatedDevices |
31 | delegate: ListItem { |
32 | height: modelLayout.height + (divider.visible ? divider.height : 0) |
33 | ListItemLayout { |
34 | id: modelLayout |
35 | title.text: displayName |
36 | - subtitle.text: { |
37 | - if (type == PrinterEnum.LPDType) return "LPD"; |
38 | - if (type == PrinterEnum.IppSType) return "IppS"; |
39 | - if (type == PrinterEnum.Ipp14Type) return "Ipp14"; |
40 | - if (type == PrinterEnum.HttpType) return "Http"; |
41 | - if (type == PrinterEnum.BehType) return "Beh"; |
42 | - if (type == PrinterEnum.SocketType) return "Socket"; |
43 | - if (type == PrinterEnum.HttpsType) return "Https"; |
44 | - if (type == PrinterEnum.IppType) return "Ipp"; |
45 | - if (type == PrinterEnum.HPType) return "HP"; |
46 | - if (type == PrinterEnum.USBType) return "USB"; |
47 | - if (type == PrinterEnum.HPFaxType) return "HPFax"; |
48 | - if (type == PrinterEnum.DNSSDType) return "DNSSD"; |
49 | - else return "Unknown protocol"; |
50 | - } |
51 | |
52 | Icon { |
53 | id: icon |
54 | @@ -729,17 +714,22 @@ |
55 | SlotsLayout.position: SlotsLayout.First |
56 | } |
57 | |
58 | - Button { |
59 | - text: "Select printer" |
60 | - onClicked: { |
61 | - var suggestedPrinterName = (" " + displayName).slice(1); |
62 | - suggestedPrinterName = suggestedPrinterName.replace(/\ /g, "\-"); |
63 | - printerUri.text = uri; |
64 | - printerName.text = suggestedPrinterName; |
65 | - printerDescription.text = info; |
66 | - printerLocation.text = location; |
67 | - } |
68 | - } |
69 | + ProgressionSlot {} |
70 | + } |
71 | + onClicked: { |
72 | + var p = pageStack.push(chooseConnectionPage, { |
73 | + consolidatedDevice: model |
74 | + }); |
75 | + p.onConnectionChosen.connect(function (conn) { |
76 | + pageStack.pop(); |
77 | + |
78 | + var suggestedPrinterName = (" " + conn.displayName).slice(1); |
79 | + suggestedPrinterName = suggestedPrinterName.replace(/\ /g, "\-"); |
80 | + printerUri.text = conn.uri; |
81 | + printerName.text = suggestedPrinterName; |
82 | + printerDescription.text = conn.info; |
83 | + printerLocation.text = conn.location; |
84 | + }); |
85 | } |
86 | } |
87 | } |
88 | @@ -757,4 +747,59 @@ |
89 | } |
90 | } |
91 | } |
92 | + |
93 | + Component { |
94 | + id: chooseConnectionPage |
95 | + |
96 | + Page { |
97 | + visible: false |
98 | + property var consolidatedDevice |
99 | + signal connectionChosen(var conn) |
100 | + header: PageHeader { |
101 | + id: chooseConnectionPageHeader |
102 | + title: "Choose connection" |
103 | + flickable: connectionsList |
104 | + } |
105 | + |
106 | + ListView { |
107 | + id: connectionsList |
108 | + anchors.fill: parent |
109 | + model: consolidatedDevice.devices |
110 | + delegate: ListItem { |
111 | + height: modelLayout.height + (divider.visible ? divider.height : 0) |
112 | + ListItemLayout { |
113 | + id: modelLayout |
114 | + title.text: info |
115 | + subtitle.text: { |
116 | + if (type == PrinterEnum.LPDType) return "LPD"; |
117 | + if (type == PrinterEnum.IppSType) return "IppS"; |
118 | + if (type == PrinterEnum.Ipp14Type) return "Ipp14"; |
119 | + if (type == PrinterEnum.HttpType) return "Http"; |
120 | + if (type == PrinterEnum.BehType) return "Beh"; |
121 | + if (type == PrinterEnum.SocketType) return "Socket"; |
122 | + if (type == PrinterEnum.HttpsType) return "Https"; |
123 | + if (type == PrinterEnum.IppType) return "Ipp"; |
124 | + if (type == PrinterEnum.HPType) return "HP"; |
125 | + if (type == PrinterEnum.USBType) return "USB"; |
126 | + if (type == PrinterEnum.HPFaxType) return "HPFax"; |
127 | + if (type == PrinterEnum.DNSSDType) return "DNSSD"; |
128 | + else return "Unknown protocol"; |
129 | + } |
130 | + summary.text: uri |
131 | + |
132 | + Icon { |
133 | + id: icon |
134 | + width: height |
135 | + height: units.gu(2.5) |
136 | + name: "network-printer-symbolic" |
137 | + SlotsLayout.position: SlotsLayout.First |
138 | + } |
139 | + |
140 | + ProgressionSlot {} |
141 | + } |
142 | + onClicked: connectionChosen(model) |
143 | + } |
144 | + } |
145 | + } |
146 | + } |
147 | } |
148 | |
149 | === modified file 'modules/Ubuntu/Components/Extras/Printers/CMakeLists.txt' |
150 | --- modules/Ubuntu/Components/Extras/Printers/CMakeLists.txt 2017-03-13 12:51:16 +0000 |
151 | +++ modules/Ubuntu/Components/Extras/Printers/CMakeLists.txt 2017-04-07 12:51:46 +0000 |
152 | @@ -34,6 +34,7 @@ |
153 | cups/jobloader.cpp |
154 | cups/printerdriverloader.cpp |
155 | cups/printerloader.cpp |
156 | + cups/ppdutils.cpp |
157 | |
158 | models/devicemodel.cpp |
159 | models/drivermodel.cpp |
160 | |
161 | === modified file 'modules/Ubuntu/Components/Extras/Printers/backend/backend_cups.cpp' |
162 | --- modules/Ubuntu/Components/Extras/Printers/backend/backend_cups.cpp 2017-03-15 17:42:21 +0000 |
163 | +++ modules/Ubuntu/Components/Extras/Printers/backend/backend_cups.cpp 2017-04-07 12:51:46 +0000 |
164 | @@ -17,6 +17,7 @@ |
165 | #include "backend/backend_cups.h" |
166 | #include "cups/devicesearcher.h" |
167 | #include "cups/jobloader.h" |
168 | +#include "cups/ppdutils.h" |
169 | #include "cups/printerdriverloader.h" |
170 | #include "cups/printerloader.h" |
171 | #include "utils.h" |
172 | @@ -264,9 +265,9 @@ |
173 | ppd_choice_t* def = ppdFindChoice(ppdColorModel, |
174 | ppdColorModel->defchoice); |
175 | if (def) { |
176 | - model = Utils::parsePpdColorModel(def->choice, |
177 | - def->text, |
178 | - "ColorModel"); |
179 | + model = PpdUtils::parsePpdColorModel(def->choice, |
180 | + def->text, |
181 | + "ColorModel"); |
182 | } |
183 | } |
184 | ret[option] = QVariant::fromValue(model); |
185 | @@ -278,8 +279,8 @@ |
186 | ppd_choice_t* def = ppdFindChoice(ppdQuality, |
187 | ppdQuality->defchoice); |
188 | if (def) { |
189 | - quality = Utils::parsePpdPrintQuality(def->choice, |
190 | - def->text, opt); |
191 | + quality = PpdUtils::parsePpdPrintQuality(def->choice, |
192 | + def->text, opt); |
193 | } |
194 | } |
195 | } |
196 | @@ -291,7 +292,7 @@ |
197 | if (qualityOpt) { |
198 | for (int i = 0; i < qualityOpt->num_choices; ++i) { |
199 | qualities.append( |
200 | - Utils::parsePpdPrintQuality( |
201 | + PpdUtils::parsePpdPrintQuality( |
202 | qualityOpt->choices[i].choice, |
203 | qualityOpt->choices[i].text, |
204 | opt |
205 | @@ -307,7 +308,7 @@ |
206 | if (colorModels) { |
207 | for (int i = 0; i < colorModels->num_choices; ++i) { |
208 | models.append( |
209 | - Utils::parsePpdColorModel( |
210 | + PpdUtils::parsePpdColorModel( |
211 | colorModels->choices[i].choice, |
212 | colorModels->choices[i].text, |
213 | QStringLiteral("ColorModel") |
214 | @@ -357,7 +358,7 @@ |
215 | |
216 | __CUPS_ADD_OPTION(dest, "copies", QString::number(options->copies()).toLocal8Bit()); |
217 | __CUPS_ADD_OPTION(dest, "ColorModel", options->getColorModel().name.toLocal8Bit()); |
218 | - __CUPS_ADD_OPTION(dest, "Duplex", Utils::duplexModeToPpdChoice(options->getDuplexMode()).toLocal8Bit()); |
219 | + __CUPS_ADD_OPTION(dest, "Duplex", PpdUtils::duplexModeToPpdChoice(options->getDuplexMode()).toLocal8Bit()); |
220 | |
221 | if (options->landscape()) { |
222 | __CUPS_ADD_OPTION(dest, "landscape", ""); |
223 | |
224 | === modified file 'modules/Ubuntu/Components/Extras/Printers/cups/devicesearcher.cpp' |
225 | --- modules/Ubuntu/Components/Extras/Printers/cups/devicesearcher.cpp 2017-03-08 14:47:16 +0000 |
226 | +++ modules/Ubuntu/Components/Extras/Printers/cups/devicesearcher.cpp 2017-04-07 12:51:46 +0000 |
227 | @@ -1,5 +1,10 @@ |
228 | /* |
229 | * Copyright (C) 2017 Canonical, Ltd. |
230 | + * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 Red Hat, Inc. |
231 | + * Authors: |
232 | + * Tim Waugh <twaugh@redhat.com> |
233 | + * Florian Festi <ffesti@redhat.com> |
234 | + * Jonas G. Drange <jonas.drange@canonical.com> |
235 | * |
236 | * This program is free software; you can redistribute it and/or modify |
237 | * it under the terms of the GNU Lesser General Public License as published by |
238 | @@ -16,8 +21,10 @@ |
239 | |
240 | #include "cups/ippclient.h" |
241 | #include "devicesearcher.h" |
242 | +#include "i18n.h" |
243 | |
244 | -#include <QUrl> |
245 | +#include <QProcess> |
246 | +#include <QRegularExpression> |
247 | |
248 | DeviceSearcher::DeviceSearcher(IppClient *client, QObject *parent) |
249 | : QObject(parent) |
250 | @@ -54,12 +61,19 @@ |
251 | } |
252 | |
253 | Device d; |
254 | + |
255 | d.cls = deviceClass; |
256 | d.id = deviceId; |
257 | d.info = deviceInfo; |
258 | - d.makeModel = deviceMakeAndModel; |
259 | + |
260 | d.uri = deviceUri; |
261 | d.location = deviceLocation; |
262 | + d.makeModel = deviceMakeAndModel; |
263 | + |
264 | + // If this is a HP device, create hp devices from it. |
265 | + Q_FOREACH(auto hpDevice, DeviceSearcher::createHpDevices(d)) { |
266 | + searcher->deviceFound(hpDevice); |
267 | + } |
268 | |
269 | searcher->deviceFound(d); |
270 | } |
271 | @@ -68,3 +82,72 @@ |
272 | { |
273 | Q_EMIT loaded(device); |
274 | } |
275 | + |
276 | +QList<Device> DeviceSearcher::createHpDevices(const Device &device) |
277 | +{ |
278 | + QList<Device> ret; |
279 | + |
280 | + auto deviceHost = device.host(); |
281 | + auto makeModelLower = device.makeModel.toLower(); |
282 | + bool isHpType = device.type() == PrinterEnum::DeviceType::HPType; |
283 | + bool isNetwork = device.cls == QStringLiteral("network"); |
284 | + bool haveMakeModel = !device.makeModel.isEmpty(); |
285 | + bool unknownMake = device.makeModel == QStringLiteral("Unknown"); |
286 | + bool startsWithHp = (makeModelLower.startsWith(QStringLiteral("hp")) || |
287 | + makeModelLower.startsWith(QStringLiteral("hewlett"))); |
288 | + |
289 | + if (!isHpType && isNetwork && (!haveMakeModel || unknownMake || |
290 | + startsWithHp)) { |
291 | + auto uri = hplipUri(deviceHost, HpPrinterMode::HpPrinter); |
292 | + |
293 | + // We couldn't get anything useful from the hplip tools. |
294 | + if (uri.isEmpty()) { |
295 | + return ret; |
296 | + } |
297 | + |
298 | + // We now have a hp printer device we want to add to the list. |
299 | + Device printerDev(device); |
300 | + printerDev.uri = uri; |
301 | + ret << printerDev; |
302 | + |
303 | + // TODO: Check if it's able to scan. |
304 | + |
305 | + auto faxUri = hplipUri(deviceHost, HpPrinterMode::HpFax); |
306 | + if (!faxUri.isEmpty()) { |
307 | + Device faxDev(device); |
308 | + faxDev.uri = faxUri; |
309 | + faxDev.info = __("Fax"); |
310 | + ret << faxDev; |
311 | + } |
312 | + } |
313 | + |
314 | + return ret; |
315 | +} |
316 | + |
317 | +QString DeviceSearcher::hplipUri(const QString &host, |
318 | + const HpPrinterMode &mode) |
319 | +{ |
320 | + QString program = "hp-makeuri"; |
321 | + QStringList arguments; |
322 | + QString ret; |
323 | + |
324 | + switch (mode) { |
325 | + default: |
326 | + case HpPrinterMode::HpPrinter: |
327 | + arguments << "-c"; |
328 | + break; |
329 | + case HpPrinterMode::HpFax: |
330 | + arguments << "-f"; |
331 | + break; |
332 | + } |
333 | + |
334 | + arguments << host; |
335 | + |
336 | + QProcess proc; |
337 | + proc.start(program, arguments); |
338 | + |
339 | + if (proc.waitForFinished() && proc.exitStatus() == QProcess::NormalExit) { |
340 | + ret = QString(proc.readAllStandardOutput()).trimmed(); |
341 | + } |
342 | + return ret; |
343 | +} |
344 | |
345 | === modified file 'modules/Ubuntu/Components/Extras/Printers/cups/devicesearcher.h' |
346 | --- modules/Ubuntu/Components/Extras/Printers/cups/devicesearcher.h 2017-03-08 11:29:01 +0000 |
347 | +++ modules/Ubuntu/Components/Extras/Printers/cups/devicesearcher.h 2017-04-07 12:51:46 +0000 |
348 | @@ -1,5 +1,10 @@ |
349 | /* |
350 | * Copyright (C) 2017 Canonical, Ltd. |
351 | + * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 Red Hat, Inc. |
352 | + * Authors: |
353 | + * Tim Waugh <twaugh@redhat.com> |
354 | + * Florian Festi <ffesti@redhat.com> |
355 | + * Jonas G. Drange <jonas.drange@canonical.com> |
356 | * |
357 | * This program is free software; you can redistribute it and/or modify |
358 | * it under the terms of the GNU Lesser General Public License as published by |
359 | @@ -38,6 +43,12 @@ |
360 | void load(); |
361 | |
362 | private: |
363 | + enum class HpPrinterMode |
364 | + { |
365 | + HpPrinter, |
366 | + HpFax, |
367 | + HpScanner, |
368 | + }; |
369 | static void deviceCallBack( |
370 | const char *cls, |
371 | const char *id, |
372 | @@ -48,6 +59,10 @@ |
373 | void *context); |
374 | void deviceFound(const Device &device); |
375 | |
376 | + static QList<Device> createHpDevices(const Device &device); |
377 | + static QString hplipUri(const QString &host, |
378 | + const HpPrinterMode &mode); |
379 | + |
380 | Q_SIGNALS: |
381 | void loaded(const Device &device); |
382 | void failed(const QString &errorMessage); |
383 | |
384 | === added file 'modules/Ubuntu/Components/Extras/Printers/cups/ppdutils.cpp' |
385 | --- modules/Ubuntu/Components/Extras/Printers/cups/ppdutils.cpp 1970-01-01 00:00:00 +0000 |
386 | +++ modules/Ubuntu/Components/Extras/Printers/cups/ppdutils.cpp 2017-04-07 12:51:46 +0000 |
387 | @@ -0,0 +1,345 @@ |
388 | +/* |
389 | + * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2014, 2015 Red Hat, Inc. |
390 | + * Copyright (C) 2006 Florian Festi <ffesti@redhat.com> |
391 | + * Copyright (C) 2006, 2007, 2008, 2009 Tim Waugh <twaugh@redhat.com> |
392 | + * Copyright (C) 2017 Canonical, Ltd. |
393 | + * |
394 | + * This program is free software; you can redistribute it and/or modify |
395 | + * it under the terms of the GNU Lesser General Public License as published by |
396 | + * the Free Software Foundation; version 3. |
397 | + * |
398 | + * This program is distributed in the hope that it will be useful, |
399 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
400 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
401 | + * GNU Lesser General Public License for more details. |
402 | + * |
403 | + * You should have received a copy of the GNU Lesser General Public License |
404 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
405 | + */ |
406 | + |
407 | +#include "ppdutils.h" |
408 | + |
409 | +#include <QSet> |
410 | + |
411 | +const QRegularExpression PpdUtils::versionNumbers = QRegularExpression( |
412 | + " v(?:er\\.)?\\d(?:\\d*\\.\\d+)?(?: |$)" |
413 | +); |
414 | +const QRegularExpression PpdUtils::ignoredSuffixes = QRegularExpression( |
415 | + "," |
416 | + "| hpijs" |
417 | + "| foomatic/" |
418 | + "| - " |
419 | + "| w/" |
420 | + "| \\(" |
421 | + "| postscript" |
422 | + "| ps" |
423 | + "| ps3" |
424 | + "| pdf" |
425 | + "| pxl" |
426 | + "| zjs" |
427 | + "| zxs" |
428 | + "| pcl3" |
429 | + "| printer" |
430 | + "|_bt" |
431 | + "| pcl" |
432 | + "| ufr ii" |
433 | + "| br-script" |
434 | +); |
435 | +const QRegularExpression PpdUtils::ignoreSeries = QRegularExpression( |
436 | + " series| all-in-one", |
437 | + QRegularExpression::CaseInsensitiveOption |
438 | +); |
439 | +const QList<QPair<QString, QRegularExpression>> PpdUtils::manufacturerByModels |
440 | + = QList<QPair<QString, QRegularExpression>>({ |
441 | + QPair<QString, QRegularExpression>( |
442 | + QStringLiteral("HP"), QRegularExpression( |
443 | + "deskjet" |
444 | + "|dj[ 0-9]?" |
445 | + "|laserjet" |
446 | + "|lj" |
447 | + "|color laserjet" |
448 | + "|color lj" |
449 | + "|designjet" |
450 | + "|officejet" |
451 | + "|oj" |
452 | + "|photosmart" |
453 | + "|ps " |
454 | + "|psc" |
455 | + "|edgeline" |
456 | + ) |
457 | + ), |
458 | + QPair<QString, QRegularExpression>( |
459 | + QStringLiteral("Epson"), QRegularExpression("stylus|aculaser") |
460 | + ), |
461 | + QPair<QString, QRegularExpression>( |
462 | + QStringLiteral("Apple"), QRegularExpression( |
463 | + "stylewriter" |
464 | + "|imagewriter" |
465 | + "|deskwriter" |
466 | + "|laserwriter" |
467 | + ) |
468 | + ), |
469 | + QPair<QString, QRegularExpression>( |
470 | + QStringLiteral("Canon"), QRegularExpression( |
471 | + "pixus" |
472 | + "|pixma" |
473 | + "|selphy" |
474 | + "|imagerunner" |
475 | + "|bj" |
476 | + "|lbp" |
477 | + ) |
478 | + ), |
479 | + QPair<QString, QRegularExpression>( |
480 | + QStringLiteral("Brother"), QRegularExpression("hl|dcp|mfc") |
481 | + ), |
482 | + QPair<QString, QRegularExpression>( |
483 | + QStringLiteral("Xerox"), QRegularExpression( |
484 | + "docuprint" |
485 | + "|docupage" |
486 | + "|phaser" |
487 | + "|workcentre" |
488 | + "|homecentre" |
489 | + ) |
490 | + ), |
491 | + QPair<QString, QRegularExpression>( |
492 | + QStringLiteral("Lexmark"), |
493 | + QRegularExpression("optra|(:color )?jetprinter") |
494 | + ), |
495 | + QPair<QString, QRegularExpression>( |
496 | + QStringLiteral("KONICA MINOLTA"), QRegularExpression( |
497 | + "magicolor" |
498 | + "|pageworks" |
499 | + "|pagepro" |
500 | + ) |
501 | + ), |
502 | + QPair<QString, QRegularExpression>( |
503 | + QStringLiteral("Kyocera"), QRegularExpression( |
504 | + "fs-" |
505 | + "|km-" |
506 | + "|taskalfa" |
507 | + ) |
508 | + ), |
509 | + QPair<QString, QRegularExpression>( |
510 | + QStringLiteral("Ricoh"), QRegularExpression("aficio") |
511 | + ), |
512 | + QPair<QString, QRegularExpression>( |
513 | + QStringLiteral("Oce"), QRegularExpression("varioprint") |
514 | + ), |
515 | + QPair<QString, QRegularExpression>( |
516 | + QStringLiteral("Oki"), QRegularExpression("okipage|microline") |
517 | + ), |
518 | +}); |
519 | + |
520 | +const QMap<QString, QString> PpdUtils::hpByModel = QMap<QString, QString>{ |
521 | + {"dj", "DeskJet"}, |
522 | + {"lj", "LaserJet"}, |
523 | + {"laserjet", "LaserJet"}, |
524 | + {"oj", "OfficeJet"}, |
525 | + {"officejet", "OfficeJet"}, |
526 | + {"color lj", "Color LaserJet"}, |
527 | + {"ps ", "PhotoSmart"}, |
528 | + {"hp ", ""}, |
529 | +}; |
530 | + |
531 | +QString PpdUtils::dedupeMakeModel(const QString &str) |
532 | +{ |
533 | + QSet<QString> stringSet; |
534 | + QStringList deduped; |
535 | + Q_FOREACH(auto m, str.split(" ")) { |
536 | + if (!stringSet.contains(m)) { |
537 | + deduped << m; |
538 | + } |
539 | + stringSet << m; |
540 | + } |
541 | + return deduped.join(" "); |
542 | +} |
543 | + |
544 | +QPair<QString, QString> PpdUtils::splitMakeModel(const QString &makeModel) |
545 | +{ |
546 | + const QString mm = dedupeMakeModel(makeModel); |
547 | + const QString makeModelLower = mm.toLower(); |
548 | + |
549 | + QString make; |
550 | + QString model; |
551 | + |
552 | + /* Informs us whether or not the make part was produced in such a way that |
553 | + it might need cleaning. I.e. we've made a guess at what the make is. */ |
554 | + bool cleanupMake = false; |
555 | + |
556 | + /* If the string starts with a known model name (like "LaserJet") assume |
557 | + that the manufacturer name is missing and add the manufacturer name |
558 | + corresponding to the model name */ |
559 | + Q_FOREACH(auto manufacturerByModel, PpdUtils::manufacturerByModels) { |
560 | + if (manufacturerByModel.second.match(makeModelLower).hasMatch()) { |
561 | + make = manufacturerByModel.first; |
562 | + model = mm; |
563 | + } |
564 | + } |
565 | + |
566 | + /* Take the first word as the name of the manufacturer. |
567 | + |
568 | + Note that we have skipped a bunch of special cases where the first word |
569 | + isn't necessarily the manufacturer, hence the next TODO. |
570 | + |
571 | + TODO: Handle special cases for TurboPrint, lexmark international, |
572 | + fuji xerox, etc. */ |
573 | + int firstSpace = mm.indexOf(" "); |
574 | + if (make.isEmpty() && firstSpace > 0) { |
575 | + make = mm.left(firstSpace); |
576 | + model = mm.mid(make.size() + 1); |
577 | + |
578 | + /* We've picked the first part as the make, but it may be that we've |
579 | + split "hewlett packard" and are left with "hewlett". */ |
580 | + cleanupMake = true; |
581 | + } |
582 | + |
583 | + // Standardised names for manufacturers. |
584 | + QString makeLower = make.toLower(); |
585 | + if (cleanupMake) { |
586 | + if (makeLower.startsWith("hewlett") && makeLower.endsWith("packard")) { |
587 | + make = "HP"; |
588 | + makeLower = "hp"; |
589 | + } else if (makeLower.startsWith("konica") && |
590 | + makeLower.endsWith("minolta")) { |
591 | + make = "KONICA MINOLTA"; |
592 | + makeLower = "konica minolta"; |
593 | + } else { |
594 | + // Fix case errors. |
595 | + Q_FOREACH(auto manufacturerByModel, manufacturerByModels) { |
596 | + if (makeLower == manufacturerByModel.first.toLower()) { |
597 | + make = manufacturerByModel.first; |
598 | + makeLower = make.toLower(); |
599 | + break; |
600 | + } |
601 | + } |
602 | + } |
603 | + } |
604 | + |
605 | + // Remove the word "Series" if present. |
606 | + model = model.remove(ignoreSeries); |
607 | + |
608 | + /* Find a version number and cut the model from there. Note that some |
609 | + models may legitimately look like version numbers. So we cut only if the |
610 | + version number has max one digit, or a dot with digits before and after. */ |
611 | + QString modelLower = model.toLower(); |
612 | + int versionAt = modelLower.indexOf(" v"); |
613 | + if (versionAt >= 0) { |
614 | + // Look for v or ver. followed by digits and dots. |
615 | + auto match = versionNumbers.match(modelLower); |
616 | + if (match.hasMatch()) { |
617 | + int matchAt = match.capturedStart(); |
618 | + model.truncate(matchAt); |
619 | + modelLower.truncate(matchAt); |
620 | + } |
621 | + } |
622 | + |
623 | + // Remove ignored suffixes. |
624 | + auto suffixMatch = ignoredSuffixes.match(modelLower); |
625 | + if (suffixMatch.hasMatch()) { |
626 | + int matchAt = suffixMatch.capturedStart(); |
627 | + model.truncate(matchAt); |
628 | + modelLower.truncate(matchAt); |
629 | + } |
630 | + |
631 | + // Replace lj or dj with LaserJet and DeskJet respectively, etc. |
632 | + if (makeLower == "hp") { |
633 | + Q_FOREACH(auto name, hpByModel.keys()) { |
634 | + if (modelLower.contains(name)) { |
635 | + int start = modelLower.indexOf(name); |
636 | + model.remove(start, name.size()); |
637 | + model.insert(start, hpByModel[name]); |
638 | + modelLower = model.toLower(); |
639 | + } |
640 | + } |
641 | + } |
642 | + |
643 | + return QPair<QString, QString>(make, model); |
644 | +} |
645 | + |
646 | +QString PpdUtils::normalize(const QString &str) |
647 | +{ |
648 | + QString strLower = str.trimmed().toLower(); |
649 | + QString normalized; |
650 | + |
651 | + const int BLANK = 0; |
652 | + const int ALPHA = 1; |
653 | + const int DIGIT = 2; |
654 | + int lastchar = BLANK; |
655 | + |
656 | + bool alnumfound = false; |
657 | + |
658 | + for (int i = 0; i < strLower.size(); i++) { |
659 | + if (strLower[i].isLetter()) { |
660 | + if (lastchar != ALPHA && alnumfound) { |
661 | + normalized += " "; |
662 | + } |
663 | + lastchar = ALPHA; |
664 | + } else if (strLower[i].isDigit()) { |
665 | + if (lastchar != DIGIT && alnumfound) { |
666 | + normalized += " "; |
667 | + } |
668 | + lastchar = DIGIT; |
669 | + } else { |
670 | + lastchar = BLANK; |
671 | + } |
672 | + |
673 | + if (strLower[i].isLetterOrNumber()) { |
674 | + normalized += strLower[i]; |
675 | + alnumfound = true; |
676 | + } |
677 | + } |
678 | + return normalized; |
679 | +} |
680 | + |
681 | +PrinterEnum::DuplexMode PpdUtils::ppdChoiceToDuplexMode(const QString &choice) |
682 | +{ |
683 | + if (choice == "DuplexTumble") |
684 | + return PrinterEnum::DuplexMode::DuplexShortSide; |
685 | + else if (choice == "DuplexNoTumble") |
686 | + return PrinterEnum::DuplexMode::DuplexLongSide; |
687 | + else |
688 | + return PrinterEnum::DuplexMode::DuplexNone; |
689 | +} |
690 | + |
691 | +const QString PpdUtils::duplexModeToPpdChoice( |
692 | + const PrinterEnum::DuplexMode &mode) |
693 | +{ |
694 | + switch (mode) { |
695 | + case PrinterEnum::DuplexMode::DuplexShortSide: |
696 | + return "DuplexTumble"; |
697 | + case PrinterEnum::DuplexMode::DuplexLongSide: |
698 | + return "DuplexNoTumble"; |
699 | + case PrinterEnum::DuplexMode::DuplexNone: |
700 | + default: |
701 | + return "None"; |
702 | + } |
703 | +} |
704 | + |
705 | + |
706 | +ColorModel PpdUtils::parsePpdColorModel(const QString &name, |
707 | + const QString &text, |
708 | + const QString &optionName) |
709 | +{ |
710 | + ColorModel ret; |
711 | + ret.name = name; |
712 | + ret.text = text; |
713 | + ret.originalOption = optionName; |
714 | + |
715 | + if (ret.name.contains("Gray") || ret.name.contains("Black")) { |
716 | + ret.colorType = PrinterEnum::ColorModelType::GrayType; |
717 | + } else { |
718 | + ret.colorType = PrinterEnum::ColorModelType::ColorType; |
719 | + } |
720 | + return ret; |
721 | +} |
722 | + |
723 | +PrintQuality PpdUtils::parsePpdPrintQuality(const QString &choice, |
724 | + const QString &text, |
725 | + const QString &optionName) |
726 | +{ |
727 | + PrintQuality quality; |
728 | + quality.name = choice; |
729 | + quality.text = text; |
730 | + quality.originalOption = optionName; |
731 | + return quality; |
732 | +} |
733 | |
734 | === added file 'modules/Ubuntu/Components/Extras/Printers/cups/ppdutils.h' |
735 | --- modules/Ubuntu/Components/Extras/Printers/cups/ppdutils.h 1970-01-01 00:00:00 +0000 |
736 | +++ modules/Ubuntu/Components/Extras/Printers/cups/ppdutils.h 2017-04-07 12:51:46 +0000 |
737 | @@ -0,0 +1,87 @@ |
738 | +/* |
739 | + * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2014, 2015 Red Hat, Inc. |
740 | + * Copyright (C) 2006 Florian Festi <ffesti@redhat.com> |
741 | + * Copyright (C) 2006, 2007, 2008, 2009 Tim Waugh <twaugh@redhat.com> |
742 | + * Copyright (C) 2017 Canonical, Ltd. |
743 | + * |
744 | + * This program is free software; you can redistribute it and/or modify |
745 | + * it under the terms of the GNU Lesser General Public License as published by |
746 | + * the Free Software Foundation; version 3. |
747 | + * |
748 | + * This program is distributed in the hope that it will be useful, |
749 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
750 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
751 | + * GNU Lesser General Public License for more details. |
752 | + * |
753 | + * You should have received a copy of the GNU Lesser General Public License |
754 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
755 | + */ |
756 | + |
757 | +#ifndef USC_PRINTERS_CUPS_PPDUTILS_H |
758 | +#define USC_PRINTERS_CUPS_PPDUTILS_H |
759 | + |
760 | +#include "enums.h" |
761 | +#include "i18n.h" |
762 | +#include "printers_global.h" |
763 | +#include "structs.h" |
764 | + |
765 | +#include <cups/ppd.h> |
766 | + |
767 | +#include <QList> |
768 | +#include <QMap> |
769 | +#include <QPair> |
770 | +#include <QRegularExpression> |
771 | +#include <QString> |
772 | + |
773 | +struct PRINTERS_DECL_EXPORT PpdUtils |
774 | +{ |
775 | +private: |
776 | + static const QList<QPair<QString, QRegularExpression>> manufacturerByModels; |
777 | + static const QRegularExpression ignoredSuffixes; |
778 | + static const QRegularExpression versionNumbers; |
779 | + static const QRegularExpression ignoreSeries; |
780 | + static const QMap<QString, QString> hpByModel; |
781 | + static QString dedupeMakeModel(const QString &str); |
782 | +public: |
783 | + |
784 | + /** |
785 | + Split a ppd-make-and-model string into a canonical make and model pair. |
786 | + Returns a string pair representing the make and the model. |
787 | + */ |
788 | + static QPair<QString, QString> splitMakeModel(const QString &makeModel); |
789 | + |
790 | + /** |
791 | + This function normalizes manufacturer and model names for comparing. |
792 | + The string is turned to lower case and leading and trailing white |
793 | + space is removed. After that each sequence of non-alphanumeric |
794 | + characters (including white space) is replaced by a single space and |
795 | + also at each change between letters and numbers a single space is added. |
796 | + This makes the comparison only done by alphanumeric characters and the |
797 | + words formed from them. So mostly two strings which sound the same when |
798 | + you pronounce them are considered equal. Printer manufacturers do not |
799 | + market two models whose names sound the same but differ only by |
800 | + upper/lower case, spaces, dashes, ..., but in printer drivers names can |
801 | + be easily supplied with these details of the name written in the wrong |
802 | + way, especially if the IEEE-1284 device ID of the printer is not known. |
803 | + This way we get a very reliable matching of printer model names. |
804 | + Examples: |
805 | + - Epson PM-A820 -> epson pm a 820 |
806 | + - Epson PM A820 -> epson pm a 820 |
807 | + - HP PhotoSmart C 8100 -> hp photosmart c 8100 |
808 | + - hp Photosmart C8100 -> hp photosmart c 8100 |
809 | + */ |
810 | + static QString normalize(const QString &str); |
811 | + |
812 | + static PrinterEnum::DuplexMode ppdChoiceToDuplexMode( |
813 | + const QString &choice); |
814 | + static const QString duplexModeToPpdChoice( |
815 | + const PrinterEnum::DuplexMode &mode); |
816 | + static ColorModel parsePpdColorModel(const QString &name, |
817 | + const QString &text, |
818 | + const QString &optionName); |
819 | + static PrintQuality parsePpdPrintQuality(const QString &choice, |
820 | + const QString &text, |
821 | + const QString &optionName); |
822 | +}; |
823 | + |
824 | +#endif // USC_PRINTERS_CUPS_PPDUTILS_H |
825 | |
826 | === modified file 'modules/Ubuntu/Components/Extras/Printers/models/devicemodel.cpp' |
827 | --- modules/Ubuntu/Components/Extras/Printers/models/devicemodel.cpp 2017-03-09 14:34:05 +0000 |
828 | +++ modules/Ubuntu/Components/Extras/Printers/models/devicemodel.cpp 2017-04-07 12:51:46 +0000 |
829 | @@ -15,9 +15,14 @@ |
830 | */ |
831 | |
832 | #include "backend/backend_cups.h" |
833 | +#include "cups/ppdutils.h" |
834 | #include "models/devicemodel.h" |
835 | +#include "utils.h" |
836 | |
837 | #include <QDebug> |
838 | +#include <QPair> |
839 | +#include <QQmlEngine> |
840 | +#include <QSet> |
841 | |
842 | DeviceModel::DeviceModel(PrinterBackend *backend, QObject *parent) |
843 | : QAbstractListModel(parent) |
844 | @@ -104,10 +109,35 @@ |
845 | return; |
846 | } |
847 | |
848 | - if (!m_devices.contains(device)) { |
849 | + // Need to make a copy as this device came from some thread. |
850 | + Device dev(device); |
851 | + |
852 | + // Try to "fix" makeModel as much as possible before inserting it. |
853 | + QString canidateMakeModel; |
854 | + if (dev.id.isEmpty()) { |
855 | + canidateMakeModel = dev.makeModel; |
856 | + } else { |
857 | + auto idDict = Utils::parseDeviceId(dev.id); |
858 | + canidateMakeModel = QString("%1 %2").arg(idDict["MFG"], idDict["MDL"]); |
859 | + } |
860 | + |
861 | + // Candidate is unknown, so try to use info as a last resort. |
862 | + if (canidateMakeModel.toLower() == QStringLiteral("unknown")) { |
863 | + canidateMakeModel = dev.info; |
864 | + } |
865 | + |
866 | + auto split = PpdUtils::splitMakeModel(canidateMakeModel); |
867 | + |
868 | + // Check if the pair is non-empty to avoid producing " ". |
869 | + if (!split.first.isEmpty() && !split.second.isEmpty()) { |
870 | + canidateMakeModel = QString("%1 %2").arg(split.first, split.second); |
871 | + } |
872 | + dev.makeModel = canidateMakeModel; |
873 | + |
874 | + if (!m_devices.contains(dev)) { |
875 | int i = m_devices.size(); |
876 | beginInsertRows(QModelIndex(), i, i); |
877 | - m_devices.append(device); |
878 | + m_devices.append(dev); |
879 | endInsertRows(); |
880 | |
881 | Q_EMIT countChanged(); |
882 | @@ -116,6 +146,7 @@ |
883 | |
884 | bool DeviceModel::deviceWanted(const Device &device) |
885 | { |
886 | + // We'll drop devices without URIs because they have no worth. |
887 | auto parts = device.uri.split(":", QString::SkipEmptyParts); |
888 | return parts.size() > 1; |
889 | } |
890 | @@ -147,3 +178,236 @@ |
891 | m_isSearching = false; |
892 | Q_EMIT searchingChanged(); |
893 | } |
894 | + |
895 | +bool DeviceModel::searching() |
896 | +{ |
897 | + return m_isSearching; |
898 | +} |
899 | + |
900 | +DeviceFilter::DeviceFilter(QObject *parent) : QSortFilterProxyModel(parent) |
901 | +{ |
902 | + connect(this, SIGNAL(sourceModelChanged()), SLOT(onSourceModelChanged())); |
903 | +} |
904 | + |
905 | +DeviceFilter::~DeviceFilter() |
906 | +{ |
907 | +} |
908 | + |
909 | +void DeviceFilter::onSourceModelChanged() |
910 | +{ |
911 | + connect( |
912 | + (DeviceModel*) sourceModel(), SIGNAL(countChanged()), |
913 | + this, SIGNAL(countChanged()) |
914 | + ); |
915 | +} |
916 | + |
917 | +void DeviceFilter::onSourceModelCountChanged() |
918 | +{ |
919 | + Q_EMIT countChanged(); |
920 | +} |
921 | + |
922 | +int DeviceFilter::count() const |
923 | +{ |
924 | + return rowCount(); |
925 | +} |
926 | + |
927 | +void DeviceFilter::filterOnNormalizedMakeModel( |
928 | + const QString &normalizedMakeModel) |
929 | +{ |
930 | + m_normalizedMakeModel = normalizedMakeModel; |
931 | + m_normalizedMakeModelFilterEnabled = true; |
932 | + invalidate(); |
933 | +} |
934 | + |
935 | +void DeviceFilter::filterOnUri(const QString &uri) |
936 | +{ |
937 | + m_uri = uri; |
938 | + m_uriFilterEnabled = true; |
939 | + invalidate(); |
940 | +} |
941 | + |
942 | +bool DeviceFilter::filterAcceptsRow(int sourceRow, |
943 | + const QModelIndex &sourceParent) const |
944 | +{ |
945 | + bool accepts = true; |
946 | + QModelIndex childIndex = sourceModel()->index(sourceRow, 0, sourceParent); |
947 | + |
948 | + if (accepts && m_normalizedMakeModelFilterEnabled) { |
949 | + QString makeModel = childIndex.model()->data( |
950 | + childIndex, DeviceModel::MakeModelRole).toString(); |
951 | + accepts = m_normalizedMakeModel == PpdUtils::normalize(makeModel); |
952 | + } |
953 | + |
954 | + if (accepts && m_uriFilterEnabled) { |
955 | + QString uri = childIndex.model()->data( |
956 | + childIndex, DeviceModel::UriRole).toString(); |
957 | + accepts = m_uri == uri; |
958 | + } |
959 | + |
960 | + return accepts; |
961 | +} |
962 | + |
963 | +QVariantMap DeviceFilter::get(const int row) const |
964 | +{ |
965 | + QHashIterator<int, QByteArray> iterator(roleNames()); |
966 | + QVariantMap result; |
967 | + QModelIndex modelIndex = index(row, 0); |
968 | + |
969 | + while (iterator.hasNext()) { |
970 | + iterator.next(); |
971 | + result[iterator.value()] = modelIndex.data(iterator.key()); |
972 | + } |
973 | + |
974 | + return result; |
975 | +} |
976 | + |
977 | +ConsolidatedDeviceModel::ConsolidatedDeviceModel(DeviceModel *deviceModel, |
978 | + QObject *parent) |
979 | + : QAbstractListModel(parent) |
980 | + , m_deviceModel(deviceModel) |
981 | +{ |
982 | + connect(m_deviceModel, SIGNAL(searchingChanged()), |
983 | + this, SLOT(deviceModelSearchChanged())); |
984 | +} |
985 | + |
986 | +ConsolidatedDeviceModel::~ConsolidatedDeviceModel() |
987 | +{ |
988 | +} |
989 | + |
990 | +int ConsolidatedDeviceModel::rowCount(const QModelIndex &parent) const |
991 | +{ |
992 | + Q_UNUSED(parent); |
993 | + return m_consolidatedDevices.size(); |
994 | +} |
995 | + |
996 | +int ConsolidatedDeviceModel::count() const |
997 | +{ |
998 | + return rowCount(); |
999 | +} |
1000 | + |
1001 | +bool ConsolidatedDeviceModel::searching() |
1002 | +{ |
1003 | + return m_deviceModel->searching(); |
1004 | +} |
1005 | + |
1006 | +bool ConsolidatedDeviceModel::makeModelIsConsolidateable( |
1007 | + const QString &makeModel) const |
1008 | +{ |
1009 | + return !makeModel.isEmpty() && makeModel.toLower() != QStringLiteral("unknown"); |
1010 | +} |
1011 | + |
1012 | +void ConsolidatedDeviceModel::deviceModelSearchChanged() |
1013 | +{ |
1014 | + int oldCount = rowCount(); |
1015 | + |
1016 | + beginResetModel(); |
1017 | + m_consolidatedDevices.clear(); |
1018 | + |
1019 | + /* Device model has finished searching, so let's sync our model with it. |
1020 | + Note that if the deviceModel is searching, we simply dump our model. */ |
1021 | + if (!m_deviceModel->searching()) { |
1022 | + // A set of normalized make-models. |
1023 | + QSet<QString> normalizedMakeModels; |
1024 | + |
1025 | + // A list of devices that cannot be consolidated. |
1026 | + QList<QString> unconsolidatableDeviceUris; |
1027 | + |
1028 | + // Used to preserve makeModels after it has been normalized. |
1029 | + QMap<QString, QString> normalizedToMakeModel; |
1030 | + for (int i = 0; i < m_deviceModel->rowCount(); i++) { |
1031 | + QModelIndex idx = m_deviceModel->index(i, 0); |
1032 | + |
1033 | + QString makeModel = m_deviceModel->data( |
1034 | + idx, DeviceModel::MakeModelRole |
1035 | + ).toString(); |
1036 | + |
1037 | + QString uri = m_deviceModel->data( |
1038 | + idx, DeviceModel::UriRole |
1039 | + ).toString(); |
1040 | + |
1041 | + if (makeModelIsConsolidateable(makeModel)) { |
1042 | + QString normalized = PpdUtils::normalize(makeModel); |
1043 | + normalizedMakeModels << normalized; |
1044 | + normalizedToMakeModel[normalized] = makeModel; |
1045 | + |
1046 | + } else { |
1047 | + /* We can't consolidate this device, but we keep the URI to |
1048 | + create a singular consolidated device. */ |
1049 | + unconsolidatableDeviceUris << uri; |
1050 | + } |
1051 | + } |
1052 | + |
1053 | + /* For every make-and-model, create a proxy model, and install a filter |
1054 | + on it. Then do the same for those consolidated under URI (i.e. not |
1055 | + consolidated). |
1056 | + Note that a parent is set on each filter so that it is cleaned up when |
1057 | + we the consolidated model is destroyed. */ |
1058 | + Q_FOREACH(auto mm, normalizedMakeModels) { |
1059 | + auto filter = new DeviceFilter(this); |
1060 | + filter->setSourceModel(m_deviceModel); |
1061 | + filter->filterOnNormalizedMakeModel(mm); |
1062 | + |
1063 | + QPair<QString, DeviceFilter*> p(mm, filter); |
1064 | + m_consolidatedDevices << p; |
1065 | + } |
1066 | + Q_FOREACH(auto uri, unconsolidatableDeviceUris) { |
1067 | + auto filter = new DeviceFilter(this); |
1068 | + filter->setSourceModel(m_deviceModel); |
1069 | + filter->filterOnUri(uri); |
1070 | + |
1071 | + QPair<QString, DeviceFilter*> p(uri, filter); |
1072 | + m_consolidatedDevices << p; |
1073 | + } |
1074 | + } |
1075 | + |
1076 | + endResetModel(); |
1077 | + |
1078 | + if (oldCount != rowCount()) { |
1079 | + Q_EMIT countChanged(); |
1080 | + } |
1081 | + Q_EMIT searchingChanged(); |
1082 | +} |
1083 | + |
1084 | +QVariant ConsolidatedDeviceModel::data(const QModelIndex &index, int role) const |
1085 | +{ |
1086 | + QVariant ret; |
1087 | + |
1088 | + if ((0 <= index.row()) && (index.row() < m_consolidatedDevices.size())) { |
1089 | + |
1090 | + auto consolidated = m_consolidatedDevices[index.row()]; |
1091 | + auto mm = consolidated.first; |
1092 | + auto filter = consolidated.second; |
1093 | + |
1094 | + switch (role) { |
1095 | + case Qt::DisplayRole: |
1096 | + case ConsolidatedNameRole: |
1097 | + ret = filter->data( |
1098 | + filter->index(0, 0), DeviceModel::Roles::MakeModelRole |
1099 | + ).toString(); |
1100 | + break; |
1101 | + case DevicesRole: { |
1102 | + ret = QVariant::fromValue(filter); |
1103 | + break; |
1104 | + case ConnectionsCountRole: |
1105 | + ret = filter->count(); |
1106 | + break; |
1107 | + } |
1108 | + } |
1109 | + } |
1110 | + |
1111 | + return ret; |
1112 | +} |
1113 | + |
1114 | +QHash<int, QByteArray> ConsolidatedDeviceModel::roleNames() const |
1115 | +{ |
1116 | + static QHash<int,QByteArray> names; |
1117 | + |
1118 | + if (Q_UNLIKELY(names.empty())) { |
1119 | + names[Qt::DisplayRole] = "displayName"; |
1120 | + names[ConsolidatedNameRole] = "consolidatedName"; |
1121 | + names[DevicesRole] = "devices"; |
1122 | + names[ConnectionsCountRole] = "connectionsCount"; |
1123 | + } |
1124 | + |
1125 | + return names; |
1126 | +} |
1127 | |
1128 | === modified file 'modules/Ubuntu/Components/Extras/Printers/models/devicemodel.h' |
1129 | --- modules/Ubuntu/Components/Extras/Printers/models/devicemodel.h 2017-03-09 14:34:05 +0000 |
1130 | +++ modules/Ubuntu/Components/Extras/Printers/models/devicemodel.h 2017-04-07 12:51:46 +0000 |
1131 | @@ -31,7 +31,7 @@ |
1132 | { |
1133 | Q_OBJECT |
1134 | Q_PROPERTY(int count READ count NOTIFY countChanged) |
1135 | - Q_PROPERTY(bool searching MEMBER m_isSearching NOTIFY searchingChanged) |
1136 | + Q_PROPERTY(bool searching READ searching NOTIFY searchingChanged) |
1137 | public: |
1138 | explicit DeviceModel(PrinterBackend *backend, QObject *parent = Q_NULLPTR); |
1139 | ~DeviceModel(); |
1140 | @@ -56,6 +56,7 @@ |
1141 | int count() const; |
1142 | void load(); |
1143 | void clear(); |
1144 | + bool searching(); |
1145 | |
1146 | private Q_SLOTS: |
1147 | void deviceLoaded(const Device &device); |
1148 | @@ -75,4 +76,84 @@ |
1149 | bool m_isSearching; |
1150 | }; |
1151 | |
1152 | + |
1153 | +class PRINTERS_DECL_EXPORT DeviceFilter : public QSortFilterProxyModel |
1154 | +{ |
1155 | + Q_OBJECT |
1156 | + Q_PROPERTY(int count READ count NOTIFY countChanged) |
1157 | +public: |
1158 | + explicit DeviceFilter(QObject *parent = Q_NULLPTR); |
1159 | + ~DeviceFilter(); |
1160 | + |
1161 | + void filterOnNormalizedMakeModel(const QString &normalizedMakeModel); |
1162 | + void filterOnUri(const QString &uri); |
1163 | + int count() const; |
1164 | + Q_INVOKABLE QVariantMap get(const int row) const; |
1165 | + |
1166 | +protected: |
1167 | + virtual bool filterAcceptsRow( |
1168 | + int sourceRow, const QModelIndex &sourceParent) const override; |
1169 | + |
1170 | +Q_SIGNALS: |
1171 | + void countChanged(); |
1172 | + |
1173 | +private Q_SLOTS: |
1174 | + void onSourceModelChanged(); |
1175 | + void onSourceModelCountChanged(); |
1176 | + |
1177 | +private: |
1178 | + QString m_normalizedMakeModel = QString::null; |
1179 | + bool m_normalizedMakeModelFilterEnabled = false; |
1180 | + |
1181 | + QString m_uri = QString::null; |
1182 | + bool m_uriFilterEnabled = false; |
1183 | +}; |
1184 | + |
1185 | +/* The ConsolidatedDeviceModel is a model of ConsolidatedDevices. A |
1186 | +ConsolidatedDevice is a set of Devices that share a printer name (assuming |
1187 | +unique printer names). |
1188 | + |
1189 | +TODO: The DeviceModel should implement the consolidation by use of e.g. a |
1190 | +table model, or tree model. This model can then be deleted. */ |
1191 | +class PRINTERS_DECL_EXPORT ConsolidatedDeviceModel : public QAbstractListModel |
1192 | +{ |
1193 | + Q_OBJECT |
1194 | + Q_PROPERTY(int count READ count NOTIFY countChanged) |
1195 | + Q_PROPERTY(bool searching READ searching NOTIFY searchingChanged) |
1196 | +public: |
1197 | + explicit ConsolidatedDeviceModel(DeviceModel *deviceModel, |
1198 | + QObject *parent = Q_NULLPTR); |
1199 | + ~ConsolidatedDeviceModel(); |
1200 | + |
1201 | + enum Roles |
1202 | + { |
1203 | + // Qt::DisplayRole holds consolidated device name |
1204 | + DevicesRole = Qt::UserRole, |
1205 | + ConsolidatedNameRole, |
1206 | + ConnectionsCountRole, |
1207 | + LastRole = ConnectionsCountRole, |
1208 | + }; |
1209 | + |
1210 | + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; |
1211 | + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; |
1212 | + virtual QHash<int, QByteArray> roleNames() const override; |
1213 | + |
1214 | + int count() const; |
1215 | + void load(); |
1216 | + bool searching(); |
1217 | + bool makeModelIsConsolidateable(const QString &makeModel) const; |
1218 | + |
1219 | +private Q_SLOTS: |
1220 | + void deviceModelSearchChanged(); |
1221 | + |
1222 | +Q_SIGNALS: |
1223 | + void countChanged(); |
1224 | + void searchingChanged(); |
1225 | + |
1226 | +private: |
1227 | + DeviceModel *m_deviceModel; |
1228 | + |
1229 | + QList<QPair<QString, DeviceFilter*>> m_consolidatedDevices; |
1230 | +}; |
1231 | + |
1232 | #endif // USC_PRINTER_DEVICEMODEL_H |
1233 | |
1234 | === modified file 'modules/Ubuntu/Components/Extras/Printers/models/drivermodel.cpp' |
1235 | --- modules/Ubuntu/Components/Extras/Printers/models/drivermodel.cpp 2017-02-21 10:46:29 +0000 |
1236 | +++ modules/Ubuntu/Components/Extras/Printers/models/drivermodel.cpp 2017-04-07 12:51:46 +0000 |
1237 | @@ -159,6 +159,20 @@ |
1238 | m_watcher.cancel(); |
1239 | } |
1240 | |
1241 | +QVariantMap DriverModel::get(const int row) const |
1242 | +{ |
1243 | + QHashIterator<int, QByteArray> iterator(roleNames()); |
1244 | + QVariantMap result; |
1245 | + QModelIndex modelIndex = index(row, 0); |
1246 | + |
1247 | + while (iterator.hasNext()) { |
1248 | + iterator.next(); |
1249 | + result[iterator.value()] = modelIndex.data(iterator.key()); |
1250 | + } |
1251 | + |
1252 | + return result; |
1253 | +} |
1254 | + |
1255 | void DriverModel::printerDriversLoaded(const QList<PrinterDriver> &drivers) |
1256 | { |
1257 | m_originalDrivers = drivers; |
1258 | |
1259 | === modified file 'modules/Ubuntu/Components/Extras/Printers/models/drivermodel.h' |
1260 | --- modules/Ubuntu/Components/Extras/Printers/models/drivermodel.h 2017-02-21 10:46:29 +0000 |
1261 | +++ modules/Ubuntu/Components/Extras/Printers/models/drivermodel.h 2017-04-07 12:51:46 +0000 |
1262 | @@ -60,6 +60,7 @@ |
1263 | |
1264 | // Cancel loading of the model. |
1265 | void cancel(); |
1266 | + QVariantMap get(const int row) const; |
1267 | |
1268 | private Q_SLOTS: |
1269 | void printerDriversLoaded(const QList<PrinterDriver> &drivers); |
1270 | |
1271 | === modified file 'modules/Ubuntu/Components/Extras/Printers/printer/printer.cpp' |
1272 | --- modules/Ubuntu/Components/Extras/Printers/printer/printer.cpp 2017-03-31 10:09:01 +0000 |
1273 | +++ modules/Ubuntu/Components/Extras/Printers/printer/printer.cpp 2017-04-07 12:51:46 +0000 |
1274 | @@ -14,6 +14,7 @@ |
1275 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1276 | */ |
1277 | |
1278 | +#include "cups/ppdutils.h" |
1279 | #include "i18n.h" |
1280 | #include "utils.h" |
1281 | |
1282 | @@ -291,7 +292,7 @@ |
1283 | return; |
1284 | } |
1285 | |
1286 | - QStringList vals({Utils::duplexModeToPpdChoice(duplexMode)}); |
1287 | + QStringList vals({PpdUtils::duplexModeToPpdChoice(duplexMode)}); |
1288 | m_backend->printerAddOption(name(), "Duplex", vals); |
1289 | } |
1290 | |
1291 | |
1292 | === modified file 'modules/Ubuntu/Components/Extras/Printers/printer/printerjob.cpp' |
1293 | --- modules/Ubuntu/Components/Extras/Printers/printer/printerjob.cpp 2017-04-03 11:56:59 +0000 |
1294 | +++ modules/Ubuntu/Components/Extras/Printers/printer/printerjob.cpp 2017-04-07 12:51:46 +0000 |
1295 | @@ -18,9 +18,9 @@ |
1296 | #include <QUrl> |
1297 | |
1298 | #include "backend/backend_cups.h" |
1299 | +#include "cups/ppdutils.h" |
1300 | #include "models/printermodel.h" |
1301 | #include "printer/printerjob.h" |
1302 | -#include "utils.h" |
1303 | |
1304 | PrinterJob::PrinterJob(QString printerName, |
1305 | PrinterBackend *backend, |
1306 | @@ -166,7 +166,7 @@ |
1307 | |
1308 | // No duplexMode will result in PrinterJob using defaultDuplexMode |
1309 | QString duplex = attributes.value("Duplex").toString(); |
1310 | - PrinterEnum::DuplexMode duplexMode = Utils::ppdChoiceToDuplexMode(duplex); |
1311 | + PrinterEnum::DuplexMode duplexMode = PpdUtils::ppdChoiceToDuplexMode(duplex); |
1312 | for (int i=0; i < m_printer->supportedDuplexModes().length(); i++) { |
1313 | if (m_printer->supportedDuplexModes().at(i) == duplexMode) { |
1314 | setDuplexMode(i); |
1315 | |
1316 | === modified file 'modules/Ubuntu/Components/Extras/Printers/printers/printers.cpp' |
1317 | --- modules/Ubuntu/Components/Extras/Printers/printers/printers.cpp 2017-04-04 10:57:41 +0000 |
1318 | +++ modules/Ubuntu/Components/Extras/Printers/printers/printers.cpp 2017-04-07 12:51:46 +0000 |
1319 | @@ -35,6 +35,7 @@ |
1320 | : QObject(parent) |
1321 | , m_backend(backend) |
1322 | , m_devices(backend) |
1323 | + , m_consolidatedDevices(&m_devices) |
1324 | , m_drivers(backend) |
1325 | , m_model(backend) |
1326 | , m_jobs(backend) |
1327 | @@ -259,6 +260,14 @@ |
1328 | m_devices.load(); |
1329 | } |
1330 | |
1331 | +QAbstractItemModel* Printers::consolidatedDevices() |
1332 | +{ |
1333 | + m_devices.load(); |
1334 | + auto ret = &m_consolidatedDevices; |
1335 | + QQmlEngine::setObjectOwnership(ret, QQmlEngine::CppOwnership); |
1336 | + return ret; |
1337 | +} |
1338 | + |
1339 | bool Printers::addPrinter(const QString &name, const QString &ppd, |
1340 | const QString &device, const QString &description, |
1341 | const QString &location) |
1342 | |
1343 | === modified file 'modules/Ubuntu/Components/Extras/Printers/printers/printers.h' |
1344 | --- modules/Ubuntu/Components/Extras/Printers/printers/printers.h 2017-04-04 10:57:41 +0000 |
1345 | +++ modules/Ubuntu/Components/Extras/Printers/printers/printers.h 2017-04-07 12:51:46 +0000 |
1346 | @@ -42,6 +42,7 @@ |
1347 | Q_PROPERTY(QAbstractItemModel* printJobs READ printJobs CONSTANT) |
1348 | Q_PROPERTY(QAbstractItemModel* drivers READ drivers CONSTANT) |
1349 | Q_PROPERTY(QAbstractItemModel* devices READ devices CONSTANT) |
1350 | + Q_PROPERTY(QAbstractItemModel* consolidatedDevices READ consolidatedDevices CONSTANT) |
1351 | Q_PROPERTY(QString driverFilter READ driverFilter WRITE setDriverFilter NOTIFY driverFilterChanged) |
1352 | Q_PROPERTY(QString defaultPrinterName READ defaultPrinterName WRITE setDefaultPrinterName NOTIFY defaultPrinterNameChanged) |
1353 | Q_PROPERTY(QString lastMessage READ lastMessage CONSTANT) |
1354 | @@ -60,6 +61,7 @@ |
1355 | QAbstractItemModel* printJobs(); |
1356 | QAbstractItemModel* drivers(); |
1357 | QAbstractItemModel* devices(); |
1358 | + QAbstractItemModel* consolidatedDevices(); |
1359 | QString driverFilter() const; |
1360 | QString defaultPrinterName() const; |
1361 | QString lastMessage() const; |
1362 | @@ -111,6 +113,7 @@ |
1363 | void provisionPrinter(const QString &name, const bool setAsDefault); |
1364 | PrinterBackend *m_backend; |
1365 | DeviceModel m_devices; |
1366 | + ConsolidatedDeviceModel m_consolidatedDevices; |
1367 | DriverModel m_drivers; |
1368 | PrinterModel m_model; |
1369 | JobModel m_jobs; |
1370 | |
1371 | === modified file 'modules/Ubuntu/Components/Extras/Printers/structs.h' |
1372 | --- modules/Ubuntu/Components/Extras/Printers/structs.h 2017-04-03 11:43:30 +0000 |
1373 | +++ modules/Ubuntu/Components/Extras/Printers/structs.h 2017-04-07 12:51:46 +0000 |
1374 | @@ -21,6 +21,7 @@ |
1375 | #include "i18n.h" |
1376 | |
1377 | #include <QtCore/QMap> |
1378 | +#include <QtCore/QUrl> |
1379 | #include <QDebug> |
1380 | #include <QMetaType> |
1381 | |
1382 | @@ -95,7 +96,18 @@ |
1383 | QString makeModel; |
1384 | QString uri; |
1385 | QString location; |
1386 | - PrinterEnum::DeviceType type() |
1387 | + |
1388 | + Device() {} |
1389 | + Device(const Device &other) { |
1390 | + this->cls = QString::fromStdString(other.cls.toStdString()); |
1391 | + this->id = QString::fromStdString(other.id.toStdString()); |
1392 | + this->info = QString::fromStdString(other.info.toStdString()); |
1393 | + this->makeModel = QString::fromStdString(other.makeModel.toStdString()); |
1394 | + this->uri = QString::fromStdString(other.uri.toStdString()); |
1395 | + this->location = QString::fromStdString(other.location.toStdString()); |
1396 | + } |
1397 | + |
1398 | + PrinterEnum::DeviceType type() const |
1399 | { |
1400 | auto parts = uri.split(":", QString::SkipEmptyParts); |
1401 | QString scheme = parts.size() > 0 ? parts[0] : QStringLiteral(""); |
1402 | @@ -127,34 +139,29 @@ |
1403 | return PrinterEnum::DeviceType::UnknownType; |
1404 | } |
1405 | |
1406 | - QString toString() const { |
1407 | - /* 1. Split the id, which is of format "KEY:VAL; … KEYN:VALN;" into |
1408 | - ["KEY:VAL", …, "KEYN:VALN"] |
1409 | - 2. Split each pair into |
1410 | - ["KEY", "VAL"] … ["KEYN", "VALN"]*/ |
1411 | - QMap<QString, QString> idMap; |
1412 | - auto pairs = id.split(";"); |
1413 | - Q_FOREACH(const QString &pair, pairs) { |
1414 | - auto keyValue = pair.split(":"); |
1415 | + QString toString() const |
1416 | + { |
1417 | + return makeModel; |
1418 | + } |
1419 | |
1420 | - /* Sometimes key,val pairs are not terminated by ";". We just |
1421 | - use the first value in that case. E.g.: |
1422 | - "MFG:HP MDL:Laserfjert;" |
1423 | - Will give "HP" as MFG, and "" as MDL. */ |
1424 | - if (keyValue.size() >= 2) { |
1425 | - idMap[keyValue[0]] = keyValue[1]; |
1426 | + QString host() const |
1427 | + { |
1428 | + auto ret = QString(); |
1429 | + switch (type()) { |
1430 | + case PrinterEnum::DeviceType::SocketType: |
1431 | + case PrinterEnum::DeviceType::LPDType: |
1432 | + case PrinterEnum::DeviceType::IppSType: |
1433 | + case PrinterEnum::DeviceType::Ipp14Type: |
1434 | + case PrinterEnum::DeviceType::IppType: |
1435 | + { |
1436 | + auto url = QUrl(uri); |
1437 | + ret = url.host(); |
1438 | } |
1439 | - } |
1440 | - auto mfg = idMap.value("MFG", ""); |
1441 | - auto mdl = idMap.value("MDL", ""); |
1442 | - |
1443 | - /* If the MDL field contains CMD, somebody forgot to terminate, and we |
1444 | - remove it. */ |
1445 | - if (mdl.contains("CMD")) { |
1446 | - mdl = mdl.split("CMD")[0]; |
1447 | - } |
1448 | - |
1449 | - return QString("%1 %2").arg(mfg).arg(mdl); |
1450 | + break; |
1451 | + default: |
1452 | + break; |
1453 | + } |
1454 | + return ret; |
1455 | } |
1456 | |
1457 | bool operator==(const Device &other) |
1458 | @@ -165,8 +172,6 @@ |
1459 | } |
1460 | }; |
1461 | |
1462 | - |
1463 | - |
1464 | Q_DECLARE_TYPEINFO(ColorModel, Q_PRIMITIVE_TYPE); |
1465 | Q_DECLARE_METATYPE(ColorModel) |
1466 | |
1467 | |
1468 | === modified file 'modules/Ubuntu/Components/Extras/Printers/utils.h' |
1469 | --- modules/Ubuntu/Components/Extras/Printers/utils.h 2017-02-21 10:46:29 +0000 |
1470 | +++ modules/Ubuntu/Components/Extras/Printers/utils.h 2017-04-07 12:51:46 +0000 |
1471 | @@ -19,39 +19,14 @@ |
1472 | |
1473 | #include "enums.h" |
1474 | #include "i18n.h" |
1475 | -#include "structs.h" |
1476 | - |
1477 | -#include <cups/ppd.h> |
1478 | - |
1479 | + |
1480 | +#include <QMap> |
1481 | +#include <QPrinter> |
1482 | #include <QString> |
1483 | -#include <QPrinter> |
1484 | |
1485 | class Utils |
1486 | { |
1487 | public: |
1488 | - static PrinterEnum::DuplexMode ppdChoiceToDuplexMode(const QString &choice) |
1489 | - { |
1490 | - if (choice == "DuplexTumble") |
1491 | - return PrinterEnum::DuplexMode::DuplexShortSide; |
1492 | - else if (choice == "DuplexNoTumble") |
1493 | - return PrinterEnum::DuplexMode::DuplexLongSide; |
1494 | - else |
1495 | - return PrinterEnum::DuplexMode::DuplexNone; |
1496 | - } |
1497 | - |
1498 | - static const QString duplexModeToPpdChoice(const PrinterEnum::DuplexMode &mode) |
1499 | - { |
1500 | - switch (mode) { |
1501 | - case PrinterEnum::DuplexMode::DuplexShortSide: |
1502 | - return "DuplexTumble"; |
1503 | - case PrinterEnum::DuplexMode::DuplexLongSide: |
1504 | - return "DuplexNoTumble"; |
1505 | - case PrinterEnum::DuplexMode::DuplexNone: |
1506 | - default: |
1507 | - return "None"; |
1508 | - } |
1509 | - } |
1510 | - |
1511 | static const QString duplexModeToUIString(const PrinterEnum::DuplexMode &mode) |
1512 | { |
1513 | switch (mode) { |
1514 | @@ -79,31 +54,63 @@ |
1515 | } |
1516 | } |
1517 | |
1518 | - static ColorModel parsePpdColorModel(const QString &name, const QString &text, |
1519 | - const QString &optionName) |
1520 | - { |
1521 | - ColorModel ret; |
1522 | - ret.name = name; |
1523 | - ret.text = text; |
1524 | - ret.originalOption = optionName; |
1525 | - |
1526 | - if (ret.name.contains("Gray") || ret.name.contains("Black")) { |
1527 | - ret.colorType = PrinterEnum::ColorModelType::GrayType; |
1528 | - } else { |
1529 | - ret.colorType = PrinterEnum::ColorModelType::ColorType; |
1530 | - } |
1531 | - return ret; |
1532 | - } |
1533 | - |
1534 | - static PrintQuality parsePpdPrintQuality(const QString &choice, |
1535 | - const QString &text, |
1536 | - const QString &optionName) |
1537 | - { |
1538 | - PrintQuality quality; |
1539 | - quality.name = choice; |
1540 | - quality.text = text; |
1541 | - quality.originalOption = optionName; |
1542 | - return quality; |
1543 | + // Parse an IEEE 1284 Device ID, so that it may be indexed by field name. |
1544 | + static QMap<QString, QString> parseDeviceId(const QString &did) |
1545 | + { |
1546 | + QMap<QString, QString> idDict; |
1547 | + const auto parts = did.split(";"); |
1548 | + const auto col = QStringLiteral(":"); |
1549 | + |
1550 | + Q_FOREACH(auto part, parts) { |
1551 | + if (!part.contains(col)) { |
1552 | + continue; |
1553 | + } |
1554 | + auto nameValue = part.split(col); |
1555 | + idDict[nameValue[0].trimmed()] = nameValue[1].trimmed(); |
1556 | + } |
1557 | + |
1558 | + const auto man = QStringLiteral("MANUFACTURER"); |
1559 | + const auto mfg = QStringLiteral("MFG"); |
1560 | + if (idDict.contains(man)) { |
1561 | + idDict.insert(mfg, idDict.value(mfg, idDict.value(man))); |
1562 | + } |
1563 | + |
1564 | + const auto mod = QStringLiteral("MODEL"); |
1565 | + const auto mdl = QStringLiteral("MDL"); |
1566 | + if (idDict.contains(mod)) { |
1567 | + idDict.insert(mdl, idDict.value(mdl, idDict.value(mod))); |
1568 | + } |
1569 | + |
1570 | + const auto com = QStringLiteral("COMMAND SET"); |
1571 | + const auto cmd = QStringLiteral("CMD"); |
1572 | + if (idDict.contains(com)) { |
1573 | + idDict.insert(cmd, idDict.value(cmd, idDict.value(com))); |
1574 | + } |
1575 | + |
1576 | + const auto defaultNames = QStringList({ |
1577 | + mfg, mdl, cmd, |
1578 | + QStringLiteral("CLS"), |
1579 | + QStringLiteral("DES"), |
1580 | + QStringLiteral("SN"), |
1581 | + QStringLiteral("S"), |
1582 | + QStringLiteral("P"), |
1583 | + QStringLiteral("J"), |
1584 | + }); |
1585 | + Q_FOREACH(auto name, defaultNames) { |
1586 | + idDict.insert(name, idDict.value(name, QString::null)); |
1587 | + } |
1588 | + |
1589 | + /* Fix a seemingly very common mistake. Maybe make this general, i.e.: |
1590 | + TODO: for every defaultName that appears in another default name, |
1591 | + remove it. */ |
1592 | + if (idDict.contains(mfg) && idDict.value(mfg).contains(cmd)) { |
1593 | + QString dropCmd = idDict.value(mfg); |
1594 | + idDict.insert(mfg, dropCmd.replace(cmd, "")); |
1595 | + } |
1596 | + |
1597 | + // TODO: We'd make an array of CMD, but we don't currently need it. |
1598 | + |
1599 | + return idDict; |
1600 | } |
1601 | }; |
1602 | |
1603 | |
1604 | === modified file 'po/ubuntu-ui-extras.pot' |
1605 | --- po/ubuntu-ui-extras.pot 2017-04-04 10:57:41 +0000 |
1606 | +++ po/ubuntu-ui-extras.pot 2017-04-07 12:51:46 +0000 |
1607 | @@ -8,7 +8,7 @@ |
1608 | msgstr "" |
1609 | "Project-Id-Version: ubuntu-ui-extras\n" |
1610 | "Report-Msgid-Bugs-To: \n" |
1611 | -"POT-Creation-Date: 2017-04-04 12:56+0200\n" |
1612 | +"POT-Creation-Date: 2017-04-07 14:43+0200\n" |
1613 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" |
1614 | "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" |
1615 | "Language-Team: LANGUAGE <LL@li.org>\n" |
1616 | @@ -51,11 +51,15 @@ |
1617 | msgid "Enhancing photo..." |
1618 | msgstr "" |
1619 | |
1620 | +#: modules/Ubuntu/Components/Extras/Printers/cups/devicesearcher.cpp:119 |
1621 | +msgid "Fax" |
1622 | +msgstr "" |
1623 | + |
1624 | #: modules/Ubuntu/Components/Extras/Example/Printers.qml:104 |
1625 | msgid "Idle" |
1626 | msgstr "" |
1627 | |
1628 | -#: modules/Ubuntu/Components/Extras/Printers/utils.h:61 |
1629 | +#: modules/Ubuntu/Components/Extras/Printers/utils.h:36 |
1630 | msgid "Long Edge (Standard)" |
1631 | msgstr "" |
1632 | |
1633 | @@ -67,7 +71,7 @@ |
1634 | msgid "Normal" |
1635 | msgstr "" |
1636 | |
1637 | -#: modules/Ubuntu/Components/Extras/Printers/utils.h:64 |
1638 | +#: modules/Ubuntu/Components/Extras/Printers/utils.h:39 |
1639 | msgid "One Sided" |
1640 | msgstr "" |
1641 | |
1642 | @@ -91,7 +95,7 @@ |
1643 | msgid "Rotate" |
1644 | msgstr "" |
1645 | |
1646 | -#: modules/Ubuntu/Components/Extras/Printers/utils.h:59 |
1647 | +#: modules/Ubuntu/Components/Extras/Printers/utils.h:34 |
1648 | msgid "Short Edge (Flip)" |
1649 | msgstr "" |
1650 | |
1651 | @@ -99,7 +103,7 @@ |
1652 | msgid "Stopped" |
1653 | msgstr "" |
1654 | |
1655 | -#: modules/Ubuntu/Components/Extras/Printers/printers/printers.cpp:395 |
1656 | +#: modules/Ubuntu/Components/Extras/Printers/printers/printers.cpp:404 |
1657 | msgid "Test page" |
1658 | msgstr "" |
1659 | |
1660 | |
1661 | === modified file 'tests/unittests/Printers/CMakeLists.txt' |
1662 | --- tests/unittests/Printers/CMakeLists.txt 2017-03-10 10:28:20 +0000 |
1663 | +++ tests/unittests/Printers/CMakeLists.txt 2017-04-07 12:51:46 +0000 |
1664 | @@ -56,3 +56,19 @@ |
1665 | add_executable(testPrintersDeviceModel tst_printerdevicemodel.cpp ${MOCK_SOURCES}) |
1666 | target_link_libraries(testPrintersDeviceModel UbuntuComponentsExtrasPrintersQml Qt5::Test Qt5::Gui) |
1667 | add_test(tst_printerdevicemodel testPrintersDeviceModel) |
1668 | + |
1669 | +add_executable(testPrintersPpdUtils tst_ppdutils.cpp) |
1670 | +target_link_libraries(testPrintersPpdUtils UbuntuComponentsExtrasPrintersQml Qt5::Test Qt5::Gui) |
1671 | +add_test(tst_ppdutils testPrintersPpdUtils) |
1672 | + |
1673 | +add_executable(testPrintersUtils tst_utils.cpp) |
1674 | +target_link_libraries(testPrintersUtils UbuntuComponentsExtrasPrintersQml Qt5::Test Qt5::Gui) |
1675 | +add_test(tst_utils testPrintersUtils) |
1676 | + |
1677 | +add_executable(testPrintersDeviceFilter tst_devicefilter.cpp ${MOCK_SOURCES}) |
1678 | +target_link_libraries(testPrintersDeviceFilter UbuntuComponentsExtrasPrintersQml Qt5::Test Qt5::Gui) |
1679 | +add_test(tst_devicefilter testPrintersDeviceFilter) |
1680 | + |
1681 | +add_executable(testPrintersConsolidatedDeviceModel tst_consolidateddevicemodel.cpp ${MOCK_SOURCES}) |
1682 | +target_link_libraries(testPrintersConsolidatedDeviceModel UbuntuComponentsExtrasPrintersQml Qt5::Test Qt5::Gui) |
1683 | +add_test(tst_consolidateddevicemodel testPrintersConsolidatedDeviceModel) |
1684 | |
1685 | === modified file 'tests/unittests/Printers/mockbackend.h' |
1686 | --- tests/unittests/Printers/mockbackend.h 2017-03-15 17:42:21 +0000 |
1687 | +++ tests/unittests/Printers/mockbackend.h 2017-04-07 12:51:46 +0000 |
1688 | @@ -18,7 +18,7 @@ |
1689 | #define USC_PRINTERS_MOCK_BACKEND_H |
1690 | |
1691 | #include "backend/backend.h" |
1692 | -#include "utils.h" |
1693 | +#include "cups/ppdutils.h" |
1694 | |
1695 | class MockPrinterBackend : public PrinterBackend |
1696 | { |
1697 | @@ -235,7 +235,7 @@ |
1698 | attributes.insert("ColorModel", job->getColorModel().name); |
1699 | attributes.insert("CompletedTime", job->completedTime()); |
1700 | attributes.insert("CreationTime", job->creationTime()); |
1701 | - attributes.insert("Duplex", Utils::duplexModeToPpdChoice(job->getDuplexMode())); |
1702 | + attributes.insert("Duplex", PpdUtils::duplexModeToPpdChoice(job->getDuplexMode())); |
1703 | attributes.insert("impressionsCompleted", job->impressionsCompleted()); |
1704 | attributes.insert("landscape", job->landscape()); |
1705 | attributes.insert("messages", job->messages()); |
1706 | @@ -322,7 +322,7 @@ |
1707 | virtual PrinterEnum::DuplexMode defaultDuplexMode() const override |
1708 | { |
1709 | auto ppdMode = printerGetOption(printerName(), "Duplex").toString(); |
1710 | - return Utils::ppdChoiceToDuplexMode(ppdMode); |
1711 | + return PpdUtils::ppdChoiceToDuplexMode(ppdMode); |
1712 | } |
1713 | |
1714 | virtual QList<PrinterEnum::DuplexMode> supportedDuplexModes() const override |
1715 | |
1716 | === added file 'tests/unittests/Printers/tst_consolidateddevicemodel.cpp' |
1717 | --- tests/unittests/Printers/tst_consolidateddevicemodel.cpp 1970-01-01 00:00:00 +0000 |
1718 | +++ tests/unittests/Printers/tst_consolidateddevicemodel.cpp 2017-04-07 12:51:46 +0000 |
1719 | @@ -0,0 +1,186 @@ |
1720 | +/* |
1721 | + * Copyright (C) 2017 Canonical, Ltd. |
1722 | + * |
1723 | + * This program is free software; you can redistribute it and/or modify |
1724 | + * it under the terms of the GNU Lesser General Public License as published by |
1725 | + * the Free Software Foundation; version 3. |
1726 | + * |
1727 | + * This program is distributed in the hope that it will be useful, |
1728 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1729 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1730 | + * GNU Lesser General Public License for more details. |
1731 | + * |
1732 | + * You should have received a copy of the GNU Lesser General Public License |
1733 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1734 | + */ |
1735 | + |
1736 | +#include "mockbackend.h" |
1737 | + |
1738 | +#include "backend/backend.h" |
1739 | +#include "models/devicemodel.h" |
1740 | +#include "structs.h" |
1741 | + |
1742 | +#include <QDebug> |
1743 | +#include <QObject> |
1744 | +#include <QSignalSpy> |
1745 | +#include <QTest> |
1746 | + |
1747 | +Q_DECLARE_METATYPE(ConsolidatedDeviceModel::Roles) |
1748 | + |
1749 | +class TestConsolidatedDeviceModel : public QObject |
1750 | +{ |
1751 | + Q_OBJECT |
1752 | +private Q_SLOTS: |
1753 | + void init() |
1754 | + { |
1755 | + m_backend = new MockPrinterBackend(); |
1756 | + m_devicesModel = new DeviceModel(m_backend); |
1757 | + } |
1758 | + void cleanup() |
1759 | + { |
1760 | + QSignalSpy destroyedSpy(m_devicesModel, SIGNAL(destroyed(QObject*))); |
1761 | + m_devicesModel->deleteLater(); |
1762 | + QTRY_COMPARE(destroyedSpy.count(), 1); |
1763 | + delete m_backend; |
1764 | + } |
1765 | + void testConsolidation_data() |
1766 | + { |
1767 | + QTest::addColumn<QList<Device>>("devices"); |
1768 | + QTest::addColumn<int>("targetConsolidatedDeviceCount"); |
1769 | + |
1770 | + { |
1771 | + Device a; a.uri = "dnssd://foo/bar"; a.makeModel = "Hp DeskJet"; |
1772 | + Device b; b.uri = "ipps://bar/qux"; b.makeModel = "Foo BazJet"; |
1773 | + |
1774 | + QList<Device> devs({a, b}); |
1775 | + QTest::newRow("no consolidation") << devs << 2; |
1776 | + } |
1777 | + { |
1778 | + Device a; a.uri = "dnssd://foo/bar"; a.makeModel = "Foo BazJet"; |
1779 | + Device b; b.uri = "ipps://foo/bar"; b.makeModel = "Foo BazJet"; |
1780 | + |
1781 | + QList<Device> devs({a, b}); |
1782 | + QTest::newRow("consolidation") << devs << 1; |
1783 | + } |
1784 | + { |
1785 | + Device a; a.uri = "dnssd://foo/bar"; a.makeModel = "Foo BazJet"; |
1786 | + Device b; b.uri = "ipps://foo/bar"; b.makeModel = "Foo BazJet"; |
1787 | + Device c; c.uri = "socket://baz/qux"; c.makeModel = "Bar LaserFjert"; |
1788 | + |
1789 | + QList<Device> devs({a, b, c}); |
1790 | + QTest::newRow("mixed") << devs << 2; |
1791 | + } |
1792 | + { |
1793 | + Device a; a.uri = "dnssd://foo/bar"; a.makeModel = "foo"; |
1794 | + Device b; b.uri = "ipps://foo/bar"; b.makeModel = ""; |
1795 | + Device c; c.uri = "socket://baz/qux"; c.makeModel = ""; |
1796 | + |
1797 | + QList<Device> devs({a, b, c}); |
1798 | + QTest::newRow("don't consolidate make-and-model-less devices") << devs << 3; |
1799 | + } |
1800 | + { |
1801 | + Device a; a.uri = "dnssd://foo/bar"; a.makeModel = "Unknown"; |
1802 | + Device b; b.uri = "ipps://foo/bar"; b.makeModel = "Unknown"; |
1803 | + Device c; c.uri = "socket://baz/qux"; c.makeModel = "Unknown"; |
1804 | + |
1805 | + QList<Device> devs({a, b, c}); |
1806 | + QTest::newRow("Unknown devices aren't consolidated.") << devs << 3; |
1807 | + } |
1808 | + { |
1809 | + Device a; a.uri = "dnssd://a"; a.makeModel = "Ricoh Ricoh Aficio MP 4500"; |
1810 | + a.id = "MFG:Ricoh;MDL:Aficio MP 4500CMD:PDF,PS,JPEG,PNG,PWG;"; |
1811 | + |
1812 | + Device b; b.uri = "dnssd://b"; b.makeModel = "Ricoh Ricoh Aficio MP 4500"; |
1813 | + b.id = "MFG:Ricoh;MDL:Aficio MP 4500CMD:PDF,PS,JPEG,PNG,PWG;"; |
1814 | + |
1815 | + Device c; c.uri = "dnssd://c"; c.makeModel = "RICOH RICOH Aficio MP C4500 PS3"; |
1816 | + c.id = "MFG:RICOH;MDL:Aficio MP C4500 PS3CMD:PDF,PS,JPEG,PNG,PWG;"; |
1817 | + |
1818 | + QList<Device> devs({a, b, c}); |
1819 | + QTest::newRow("Ricoh as it appears in uni of Bergen") << devs << 2; |
1820 | + } |
1821 | + { |
1822 | + Device a; a.uri = "dnssd://a"; a.makeModel = "unknown"; |
1823 | + a.id = ""; a.info = "HP Color LaserJet 2500 @ some-laptop"; |
1824 | + |
1825 | + Device b; b.uri = "dnssd://b"; b.makeModel = "unknown"; |
1826 | + b.id = ""; b.info = "HP Color LaserJet 2500 plz don't spam @ other-laptop"; |
1827 | + |
1828 | + QList<Device> devs({a, b}); |
1829 | + QTest::newRow("Buggy cups") << devs << 2; |
1830 | + |
1831 | + } |
1832 | + } |
1833 | + void testConsolidation() |
1834 | + { |
1835 | + QFETCH(QList<Device>, devices); |
1836 | + QFETCH(int, targetConsolidatedDeviceCount); |
1837 | + |
1838 | + ConsolidatedDeviceModel model(m_devicesModel); |
1839 | + |
1840 | + Q_FOREACH(auto device, devices) { |
1841 | + m_backend->mockDeviceFound(device); |
1842 | + } |
1843 | + m_backend->deviceSearchFinished(); |
1844 | + |
1845 | + QCOMPARE(model.rowCount(), targetConsolidatedDeviceCount); |
1846 | + } |
1847 | + void testRoles_data() |
1848 | + { |
1849 | + QTest::addColumn<ConsolidatedDeviceModel::Roles>("role"); |
1850 | + QTest::addColumn<QVariant>("value"); |
1851 | + QTest::addColumn<QList<Device>>("devices"); |
1852 | + |
1853 | + { |
1854 | + Device a; a.uri = "ipp://foo/bar"; a.makeModel = "HP LaserFjert 4500"; |
1855 | + Device b; a.uri = "ipp://foo/bar"; b.makeModel = "HP LaserFjert 4500"; |
1856 | + |
1857 | + QList<Device> devs({a, b}); |
1858 | + |
1859 | + ConsolidatedDeviceModel::Roles role( |
1860 | + ConsolidatedDeviceModel::ConsolidatedNameRole |
1861 | + ); |
1862 | + QVariant value(QString("HP LaserFjert 4500")); |
1863 | + QTest::newRow("ConsolidatedNameRole") << role << value << devs; |
1864 | + } |
1865 | + } |
1866 | + void testRoles() |
1867 | + { |
1868 | + QFETCH(ConsolidatedDeviceModel::Roles, role); |
1869 | + QFETCH(QVariant, value); |
1870 | + QFETCH(QList<Device>, devices); |
1871 | + |
1872 | + ConsolidatedDeviceModel model(m_devicesModel); |
1873 | + |
1874 | + Q_FOREACH(auto device, devices) { |
1875 | + m_backend->mockDeviceFound(device); |
1876 | + } |
1877 | + m_backend->deviceSearchFinished(); |
1878 | + |
1879 | + QCOMPARE(model.data(model.index(0), role), value); |
1880 | + } |
1881 | + void testDevicesRole() |
1882 | + { |
1883 | + Device a; a.uri = "ipp://foo/bar"; a.makeModel = "foo"; |
1884 | + Device b; b.uri = "dnssd://foo/bar"; b.makeModel = "foo"; |
1885 | + |
1886 | + ConsolidatedDeviceModel model(m_devicesModel); |
1887 | + |
1888 | + m_backend->mockDeviceFound(a); |
1889 | + m_backend->mockDeviceFound(b); |
1890 | + m_backend->deviceSearchFinished(); |
1891 | + |
1892 | + auto filter = model.data( |
1893 | + model.index(0), ConsolidatedDeviceModel::Roles::DevicesRole |
1894 | + ).value<DeviceFilter*>(); |
1895 | + |
1896 | + QCOMPARE(filter->count(), 2); |
1897 | + } |
1898 | +private: |
1899 | + MockPrinterBackend *m_backend; |
1900 | + DeviceModel *m_devicesModel; |
1901 | +}; |
1902 | + |
1903 | +QTEST_GUILESS_MAIN(TestConsolidatedDeviceModel) |
1904 | +#include "tst_consolidateddevicemodel.moc" |
1905 | + |
1906 | |
1907 | === added file 'tests/unittests/Printers/tst_devicefilter.cpp' |
1908 | --- tests/unittests/Printers/tst_devicefilter.cpp 1970-01-01 00:00:00 +0000 |
1909 | +++ tests/unittests/Printers/tst_devicefilter.cpp 2017-04-07 12:51:46 +0000 |
1910 | @@ -0,0 +1,106 @@ |
1911 | +/* |
1912 | + * Copyright (C) 2017 Canonical, Ltd. |
1913 | + * |
1914 | + * This program is free software; you can redistribute it and/or modify |
1915 | + * it under the terms of the GNU Lesser General Public License as published by |
1916 | + * the Free Software Foundation; version 3. |
1917 | + * |
1918 | + * This program is distributed in the hope that it will be useful, |
1919 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1920 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1921 | + * GNU Lesser General Public License for more details. |
1922 | + * |
1923 | + * You should have received a copy of the GNU Lesser General Public License |
1924 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1925 | + */ |
1926 | + |
1927 | +#include "mockbackend.h" |
1928 | + |
1929 | +#include "cups/ppdutils.h" |
1930 | +#include "models/devicemodel.h" |
1931 | + |
1932 | +#include <QObject> |
1933 | +#include <QSignalSpy> |
1934 | +#include <QScopedPointer> |
1935 | +#include <QTest> |
1936 | + |
1937 | +class TestDeviceFilter : public QObject |
1938 | +{ |
1939 | + Q_OBJECT |
1940 | +private Q_SLOTS: |
1941 | + void testFilterOnMakeModel() |
1942 | + { |
1943 | + QScopedPointer<MockPrinterBackend> backend(new MockPrinterBackend); |
1944 | + DeviceModel model(backend.data()); |
1945 | + |
1946 | + Device dev1; dev1.uri = "ipp://foo/bar"; |
1947 | + dev1.makeModel = "Foo BarJet"; |
1948 | + Device dev2; dev2.uri = "dnssd://bar/qux"; |
1949 | + dev2.makeModel = "Bar QuxJet"; |
1950 | + |
1951 | + backend->mockDeviceFound(dev1); |
1952 | + backend->mockDeviceFound(dev2); |
1953 | + |
1954 | + DeviceFilter filter; |
1955 | + filter.setSourceModel(&model); |
1956 | + |
1957 | + QCOMPARE(filter.count(), 2); |
1958 | + |
1959 | + // Install filter. |
1960 | + filter.filterOnNormalizedMakeModel("foo barjet"); |
1961 | + QCOMPARE(filter.count(), 1); |
1962 | + } |
1963 | + |
1964 | + /* Test that "Foo BazJet-5000" and "Foo BazJet 5000" are accepted in same |
1965 | + filter. */ |
1966 | + void testNormalizedFiltering() |
1967 | + { |
1968 | + QScopedPointer<MockPrinterBackend> backend(new MockPrinterBackend); |
1969 | + DeviceModel model(backend.data()); |
1970 | + |
1971 | + Device dev1; dev1.uri = "ipp://foo/bar"; |
1972 | + dev1.makeModel = "Foo BazJet 5000"; |
1973 | + Device dev2; dev2.uri = "dnssd://bar/qux"; |
1974 | + dev2.makeModel = "Foo BazJet-5000"; |
1975 | + Device dev3; dev3.uri = "dnssd://bar/baz"; |
1976 | + dev3.makeModel = "Foo DeskQux 350"; |
1977 | + |
1978 | + backend->mockDeviceFound(dev1); |
1979 | + backend->mockDeviceFound(dev2); |
1980 | + backend->mockDeviceFound(dev3); |
1981 | + |
1982 | + DeviceFilter filter; |
1983 | + filter.setSourceModel(&model); |
1984 | + |
1985 | + // Install filter. |
1986 | + filter.filterOnNormalizedMakeModel( |
1987 | + PpdUtils::normalize("Foo BazJet 5000")); |
1988 | + QCOMPARE(filter.count(), 2); |
1989 | + } |
1990 | + void testFilterOnUri() |
1991 | + { |
1992 | + QScopedPointer<MockPrinterBackend> backend(new MockPrinterBackend); |
1993 | + DeviceModel model(backend.data()); |
1994 | + |
1995 | + Device dev1; dev1.uri = "ipp://foo/bar"; |
1996 | + dev1.id = "foo"; |
1997 | + Device dev2; dev2.uri = "dnssd://bar/qux"; |
1998 | + dev2.id = "bar"; |
1999 | + |
2000 | + backend->mockDeviceFound(dev1); |
2001 | + backend->mockDeviceFound(dev2); |
2002 | + |
2003 | + DeviceFilter filter; |
2004 | + filter.setSourceModel(&model); |
2005 | + |
2006 | + QCOMPARE(filter.count(), 2); |
2007 | + |
2008 | + // Install filter. |
2009 | + filter.filterOnUri("ipp://foo/bar"); |
2010 | + QCOMPARE(filter.count(), 1); |
2011 | + } |
2012 | +}; |
2013 | + |
2014 | +QTEST_GUILESS_MAIN(TestDeviceFilter) |
2015 | +#include "tst_devicefilter.moc" |
2016 | + |
2017 | |
2018 | === modified file 'tests/unittests/Printers/tst_jobmodel.cpp' |
2019 | --- tests/unittests/Printers/tst_jobmodel.cpp 2017-03-15 17:30:21 +0000 |
2020 | +++ tests/unittests/Printers/tst_jobmodel.cpp 2017-04-07 12:51:46 +0000 |
2021 | @@ -19,6 +19,7 @@ |
2022 | #include "backend/backend.h" |
2023 | #include "models/jobmodel.h" |
2024 | #include "printers/printers.h" |
2025 | +#include "utils.h" |
2026 | |
2027 | #include <QDebug> |
2028 | #include <QObject> |
2029 | |
2030 | === added file 'tests/unittests/Printers/tst_ppdutils.cpp' |
2031 | --- tests/unittests/Printers/tst_ppdutils.cpp 1970-01-01 00:00:00 +0000 |
2032 | +++ tests/unittests/Printers/tst_ppdutils.cpp 2017-04-07 12:51:46 +0000 |
2033 | @@ -0,0 +1,138 @@ |
2034 | +/* |
2035 | + * Copyright (C) 2017 Canonical, Ltd. |
2036 | + * |
2037 | + * This program is free software; you can redistribute it and/or modify |
2038 | + * it under the terms of the GNU Lesser General Public License as published by |
2039 | + * the Free Software Foundation; version 3. |
2040 | + * |
2041 | + * This program is distributed in the hope that it will be useful, |
2042 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
2043 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2044 | + * GNU Lesser General Public License for more details. |
2045 | + * |
2046 | + * You should have received a copy of the GNU Lesser General Public License |
2047 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
2048 | + */ |
2049 | + |
2050 | +#include "cups/ppdutils.h" |
2051 | + |
2052 | +#include <QObject> |
2053 | +#include <QTest> |
2054 | + |
2055 | +typedef QPair<QString, QString> MakeModel; |
2056 | +Q_DECLARE_METATYPE(MakeModel) |
2057 | + |
2058 | +class TstPpdUtils : public QObject |
2059 | +{ |
2060 | + Q_OBJECT |
2061 | +private Q_SLOTS: |
2062 | + void testSplitMakeModel_data() |
2063 | + { |
2064 | + QTest::addColumn<QString>("makeModel"); |
2065 | + QTest::addColumn<MakeModel>("expected"); |
2066 | + |
2067 | + { |
2068 | + QString actual("HP HP Officejet Pro 8610"); |
2069 | + auto expected = MakeModel("HP", "OfficeJet Pro 8610"); |
2070 | + QTest::newRow("duplicate hp") << actual << expected; |
2071 | + } |
2072 | + { |
2073 | + QString actual("Officejet Officejet Pro 8610"); |
2074 | + auto expected = MakeModel("HP", "OfficeJet Pro 8610"); |
2075 | + QTest::newRow("officejet officejet (yes it actually happens)") << actual << expected; |
2076 | + } |
2077 | + { |
2078 | + QString actual("laserjet 4500"); |
2079 | + auto expected = MakeModel("HP", "LaserJet 4500"); |
2080 | + QTest::newRow("just a hp model") << actual << expected; |
2081 | + } |
2082 | + { |
2083 | + QString actual("HP OfficeJet Pro 8600"); |
2084 | + auto expected = MakeModel("HP", "OfficeJet Pro 8600"); |
2085 | + QTest::newRow("a sane hp officejet") << actual << expected; |
2086 | + } |
2087 | + { |
2088 | + QString actual("HP Foo All-In-One"); |
2089 | + auto expected = MakeModel("HP", "Foo"); |
2090 | + QTest::newRow("remove all-in-one") << actual << expected; |
2091 | + } |
2092 | + { |
2093 | + QString actual("Hewlett-Packard SmartJet 50 v0.10"); |
2094 | + auto expected = MakeModel("HP", "SmartJet 50"); |
2095 | + QTest::newRow("Hewlett-Packard SmartJet 50 v0.10") << actual << expected; |
2096 | + } |
2097 | + { |
2098 | + QString actual("Foobar Bazjet 9000"); |
2099 | + auto expected = MakeModel("Foobar", "Bazjet 9000"); |
2100 | + QTest::newRow("the excellent foobar bazjet 9000") << actual << expected; |
2101 | + } |
2102 | + { |
2103 | + QString actual("HP LaserJet 4 Plus v2013.111 Postscript (recommended)"); |
2104 | + auto expected = MakeModel("HP", "LaserJet 4 Plus"); |
2105 | + QTest::newRow("example #1") << actual << expected; |
2106 | + } |
2107 | + { |
2108 | + QString actual("Canon MG4100 series Ver.3.90"); |
2109 | + auto expected = MakeModel("Canon", "MG4100"); |
2110 | + QTest::newRow("example #2") << actual << expected; |
2111 | + } |
2112 | + { |
2113 | + QString actual("Xerox Foo Printer"); |
2114 | + auto expected = MakeModel("Xerox", "Foo"); |
2115 | + QTest::newRow("remove ignored suffix 'printer'") << actual << expected; |
2116 | + } |
2117 | + { |
2118 | + QString actual("hp lj 4500"); |
2119 | + auto expected = MakeModel("HP", "LaserJet 4500"); |
2120 | + QTest::newRow("shortened hp model name") << actual << expected; |
2121 | + } |
2122 | + { |
2123 | + QString actual("okipage foobar"); |
2124 | + auto expected = MakeModel("Oki", "okipage foobar"); |
2125 | + QTest::newRow("some okipage printer") << actual << expected; |
2126 | + } |
2127 | + } |
2128 | + void testSplitMakeModel() |
2129 | + { |
2130 | + QFETCH(QString, makeModel); |
2131 | + QFETCH(MakeModel, expected); |
2132 | + auto actual = PpdUtils::splitMakeModel(makeModel); |
2133 | + QCOMPARE(actual, expected); |
2134 | + } |
2135 | + void testNormalize_data() |
2136 | + { |
2137 | + QTest::addColumn<QString>("actual"); |
2138 | + QTest::addColumn<QString>("expected"); |
2139 | + |
2140 | + { |
2141 | + QString actual("Epson PM-A820"); |
2142 | + QString expected("epson pm a 820"); |
2143 | + QTest::newRow("epson with dash") << actual << expected; |
2144 | + } |
2145 | + { |
2146 | + QString actual("Epson PM A820"); |
2147 | + QString expected("epson pm a 820"); |
2148 | + QTest::newRow("epson without dash") << actual << expected; |
2149 | + } |
2150 | + { |
2151 | + QString actual("HP PhotoSmart C 8100"); |
2152 | + QString expected("hp photosmart c 8100"); |
2153 | + QTest::newRow("C 8100") << actual << expected; |
2154 | + } |
2155 | + { |
2156 | + QString actual("HP PhotoSmart C8100"); |
2157 | + QString expected("hp photosmart c 8100"); |
2158 | + QTest::newRow("C8100") << actual << expected; |
2159 | + } |
2160 | + } |
2161 | + void testNormalize() |
2162 | + { |
2163 | + QFETCH(QString, actual); |
2164 | + QFETCH(QString, expected); |
2165 | + actual = PpdUtils::normalize(actual); |
2166 | + QCOMPARE(actual, expected); |
2167 | + } |
2168 | +}; |
2169 | + |
2170 | +QTEST_GUILESS_MAIN(TstPpdUtils) |
2171 | +#include "tst_ppdutils.moc" |
2172 | |
2173 | === modified file 'tests/unittests/Printers/tst_printer.cpp' |
2174 | --- tests/unittests/Printers/tst_printer.cpp 2017-03-31 11:03:15 +0000 |
2175 | +++ tests/unittests/Printers/tst_printer.cpp 2017-04-07 12:51:46 +0000 |
2176 | @@ -14,12 +14,12 @@ |
2177 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
2178 | */ |
2179 | |
2180 | -#include "utils.h" |
2181 | |
2182 | #include "mockbackend.h" |
2183 | |
2184 | #include "backend/backend.h" |
2185 | #include "backend/backend_pdf.h" |
2186 | +#include "cups/ppdutils.h" |
2187 | #include "printer/printer.h" |
2188 | |
2189 | #include <QDebug> |
2190 | @@ -161,7 +161,7 @@ |
2191 | QStringList duplexVals = duplexVar.toStringList(); |
2192 | QCOMPARE( |
2193 | duplexVals.at(0), |
2194 | - (QString) Utils::duplexModeToPpdChoice(PrinterEnum::DuplexMode::DuplexLongSide) |
2195 | + (QString) PpdUtils::duplexModeToPpdChoice(PrinterEnum::DuplexMode::DuplexLongSide) |
2196 | ); |
2197 | } |
2198 | void testSetDefaultPageSize_data() |
2199 | @@ -429,7 +429,7 @@ |
2200 | |
2201 | backend->m_supportedDuplexModes = duplexModes; |
2202 | backend->printerOptions[printerName].insert( |
2203 | - "Duplex", Utils::duplexModeToPpdChoice(newDefaultDuplexMode)); |
2204 | + "Duplex", PpdUtils::duplexModeToPpdChoice(newDefaultDuplexMode)); |
2205 | |
2206 | QSharedPointer<Printer> p = QSharedPointer<Printer>(new Printer(backend)); |
2207 | |
2208 | |
2209 | === modified file 'tests/unittests/Printers/tst_printerdevice.cpp' |
2210 | --- tests/unittests/Printers/tst_printerdevice.cpp 2017-03-16 15:34:02 +0000 |
2211 | +++ tests/unittests/Printers/tst_printerdevice.cpp 2017-04-07 12:51:46 +0000 |
2212 | @@ -93,37 +93,34 @@ |
2213 | |
2214 | QCOMPARE(device.type(), expected); |
2215 | } |
2216 | - void testToString_data() |
2217 | + void testHost_data() |
2218 | { |
2219 | - QTest::addColumn<QString>("id"); |
2220 | + QTest::addColumn<Device>("device"); |
2221 | QTest::addColumn<QString>("expected"); |
2222 | |
2223 | { |
2224 | - Device d; |
2225 | - QTest::newRow("a hp printer (remove CMD)") |
2226 | - << "MFG:HP;MDL:Color LaserJet 4500CMD:PDF,PS,JPEG,PNG,PWG,URF" |
2227 | - << "HP Color LaserJet 4500"; |
2228 | - } |
2229 | - { |
2230 | - Device d; ; |
2231 | - QTest::newRow("Andrew's hp printer") |
2232 | - << "MFG:HP;MDL:Officejet 5740 series;CMD:PCL,JPEG,URF,PWG;" |
2233 | - << "HP Officejet 5740 series"; |
2234 | - } |
2235 | - { |
2236 | - Device d; ; |
2237 | - QTest::newRow("Base case with A as manufacturer, B as model.") |
2238 | - << "MFG:A;MDL:B" |
2239 | - << "A B"; |
2240 | + Device d; d.uri = "usb"; |
2241 | + QTest::newRow("local type, no host") << d << ""; |
2242 | + } |
2243 | + { |
2244 | + Device d; d.uri = "dnssd://foo/bar"; |
2245 | + QTest::newRow("dnssd urls have no hostname") << d << ""; |
2246 | + } |
2247 | + { |
2248 | + Device d; d.uri = "socket://192.168.1.7:9100"; |
2249 | + QTest::newRow("a socket") << d << "192.168.1.7"; |
2250 | + } |
2251 | + { |
2252 | + Device d; d.uri = "ipp://my-domain.com/printer"; |
2253 | + QTest::newRow("ipp") << d << "my-domain.com"; |
2254 | } |
2255 | } |
2256 | - void testToString() |
2257 | + void testHost() |
2258 | { |
2259 | - QFETCH(QString, id); |
2260 | + QFETCH(Device, device); |
2261 | QFETCH(QString, expected); |
2262 | |
2263 | - Device d; d.id = id; |
2264 | - QCOMPARE(d.toString(), expected); |
2265 | + QCOMPARE(device.host(), expected); |
2266 | } |
2267 | }; |
2268 | |
2269 | |
2270 | === modified file 'tests/unittests/Printers/tst_printerdevicemodel.cpp' |
2271 | --- tests/unittests/Printers/tst_printerdevicemodel.cpp 2017-03-08 16:13:02 +0000 |
2272 | +++ tests/unittests/Printers/tst_printerdevicemodel.cpp 2017-04-07 12:51:46 +0000 |
2273 | @@ -54,12 +54,18 @@ |
2274 | } |
2275 | { |
2276 | Device d; d.uri = "dnssd://foo-bar"; |
2277 | + d.makeModel = "BazJet"; |
2278 | QTest::newRow("non-empty uri") << d << true; |
2279 | } |
2280 | { |
2281 | Device d; d.uri = "dnssd:"; |
2282 | QTest::newRow("non-empty uri, malformed") << d << false; |
2283 | } |
2284 | + { |
2285 | + Device d; d.uri = "dnssd://foo/bar"; |
2286 | + d.makeModel = "Unknown"; |
2287 | + QTest::newRow("non-empty uri, but Unknown make/model") << d << true; |
2288 | + } |
2289 | } |
2290 | void testAcceptedDevices() |
2291 | { |
2292 | @@ -75,6 +81,7 @@ |
2293 | QSignalSpy insertSpy(m_model, SIGNAL(rowsInserted(const QModelIndex&, int, int))); |
2294 | |
2295 | Device d; d.uri = "dnssd://foo-bar"; |
2296 | + d.makeModel = "BazJet"; |
2297 | m_backend->mockDeviceFound(d); |
2298 | |
2299 | QCOMPARE(m_model->count(), 1); |
2300 | @@ -87,9 +94,11 @@ |
2301 | QSignalSpy insertSpy(m_model, SIGNAL(rowsInserted(const QModelIndex&, int, int))); |
2302 | |
2303 | Device d; d.uri = "dnssd://foo-bar"; |
2304 | + d.makeModel = "BazJet"; |
2305 | m_backend->mockDeviceFound(d); |
2306 | |
2307 | Device d1; d1.uri = "dnssd://foo-bar"; |
2308 | + d1.makeModel = "BazJet"; |
2309 | m_backend->mockDeviceFound(d1); |
2310 | |
2311 | QCOMPARE(m_model->count(), 1); |
2312 | @@ -104,40 +113,49 @@ |
2313 | |
2314 | { |
2315 | Device d; d.uri = "ipp://foo/bar"; |
2316 | - d.id = "foo"; |
2317 | + d.id = "foo"; d.makeModel = "BazJet"; |
2318 | DeviceModel::Roles role(DeviceModel::IdRole); |
2319 | QVariant value(d.id); |
2320 | QTest::newRow("DisplayRole") << role << value << d; |
2321 | } |
2322 | { |
2323 | Device d; d.uri = "ipp://foo/bar"; |
2324 | - d.info = "foo"; |
2325 | + d.info = "foo"; d.makeModel = "BazJet"; |
2326 | DeviceModel::Roles role(DeviceModel::InfoRole); |
2327 | QVariant value(d.info); |
2328 | QTest::newRow("InfoRole") << role << value << d; |
2329 | } |
2330 | { |
2331 | Device d; d.uri = "ipp://foo/bar"; |
2332 | + d.makeModel = "BazJet"; |
2333 | DeviceModel::Roles role(DeviceModel::UriRole); |
2334 | QVariant value(d.uri); |
2335 | QTest::newRow("UriRole") << role << value << d; |
2336 | } |
2337 | { |
2338 | Device d; d.uri = "ipp://foo/bar"; |
2339 | - d.location = "home"; |
2340 | + d.location = "home"; d.makeModel = "BazJet"; |
2341 | DeviceModel::Roles role(DeviceModel::LocationRole); |
2342 | QVariant value(d.location); |
2343 | QTest::newRow("LocationRole") << role << value << d; |
2344 | } |
2345 | { |
2346 | Device d; d.uri = "ipp://foo/bar"; |
2347 | - d.makeModel = "hp"; |
2348 | - DeviceModel::Roles role(DeviceModel::MakeModelRole); |
2349 | - QVariant value(d.makeModel); |
2350 | - QTest::newRow("MakeModelRole") << role << value << d; |
2351 | - } |
2352 | - { |
2353 | - Device d; d.uri = "ipp://foo/bar"; |
2354 | + d.makeModel = "Bar BazJet"; |
2355 | + DeviceModel::Roles role(DeviceModel::MakeModelRole); |
2356 | + QVariant value("Bar BazJet"); |
2357 | + QTest::newRow("MakeModelRole") << role << value << d; |
2358 | + } |
2359 | + { |
2360 | + Device d; d.uri = "ipp://foo/bar"; |
2361 | + d.makeModel = "HP LaserJet 4500"; |
2362 | + DeviceModel::Roles role(DeviceModel::MakeModelRole); |
2363 | + QVariant value("HP LaserJet 4500"); |
2364 | + QTest::newRow("MakeModelRole") << role << value << d; |
2365 | + } |
2366 | + { |
2367 | + Device d; d.uri = "ipp://foo/bar"; |
2368 | + d.makeModel = "BazJet"; |
2369 | DeviceModel::Roles role(DeviceModel::TypeRole); |
2370 | QVariant value = QVariant::fromValue(d.type()); |
2371 | QTest::newRow("TypeRole") << role << value << d; |
2372 | @@ -152,6 +170,36 @@ |
2373 | m_backend->mockDeviceFound(device); |
2374 | QCOMPARE(m_model->data(m_model->index(0), role), value); |
2375 | } |
2376 | + void testFiltering_data() |
2377 | + { |
2378 | + QTest::addColumn<QList<Device>>("devices"); |
2379 | + QTest::addColumn<QString>("filterId"); |
2380 | + QTest::addColumn<int>("targetCount"); |
2381 | + QTest::addColumn<QStringList>("targetIds"); |
2382 | + |
2383 | + { |
2384 | + Device a; a.uri = "dnssd://foo/bar"; a.id = "foo"; |
2385 | + Device b; b.uri = "ipps://bar/qux"; b.id = "bar"; |
2386 | + |
2387 | + QList<Device> devs({a, b}); |
2388 | + QTest::newRow("no filtering") << devs << "" << 2 << QStringList({"foo", "bar"}); |
2389 | + } |
2390 | + } |
2391 | + void testFiltering() |
2392 | + { |
2393 | + QFETCH(QList<Device>, devices); |
2394 | + QFETCH(QString, filterId); |
2395 | + QFETCH(int, targetCount); |
2396 | + QFETCH(QStringList, targetIds); |
2397 | + |
2398 | + Q_FOREACH(auto dev, devices) { |
2399 | + m_backend->mockDeviceFound(dev); |
2400 | + } |
2401 | + |
2402 | + DeviceFilter filter; |
2403 | + filter.setSourceModel(m_model); |
2404 | + QCOMPARE(filter.count(), targetCount); |
2405 | + } |
2406 | private: |
2407 | MockPrinterBackend *m_backend; |
2408 | DeviceModel *m_model; |
2409 | |
2410 | === modified file 'tests/unittests/Printers/tst_printermodel.cpp' |
2411 | --- tests/unittests/Printers/tst_printermodel.cpp 2017-03-10 13:15:27 +0000 |
2412 | +++ tests/unittests/Printers/tst_printermodel.cpp 2017-04-07 12:51:46 +0000 |
2413 | @@ -17,9 +17,11 @@ |
2414 | #include "mockbackend.h" |
2415 | |
2416 | #include "backend/backend.h" |
2417 | +#include "cups/ppdutils.h" |
2418 | #include "models/printermodel.h" |
2419 | #include "printer/printer.h" |
2420 | #include "printer/printerjob.h" |
2421 | +#include "utils.h" |
2422 | |
2423 | #include <QDebug> |
2424 | #include <QObject> |
2425 | @@ -231,7 +233,7 @@ |
2426 | PrinterBackend* backend = new MockPrinterBackend("a-printer"); |
2427 | ((MockPrinterBackend*) backend)->m_supportedDuplexModes = modes; |
2428 | ((MockPrinterBackend*) backend)->printerOptions["a-printer"].insert( |
2429 | - "Duplex", QVariant::fromValue(Utils::duplexModeToPpdChoice(PrinterEnum::DuplexMode::DuplexLongSide)) |
2430 | + "Duplex", QVariant::fromValue(PpdUtils::duplexModeToPpdChoice(PrinterEnum::DuplexMode::DuplexLongSide)) |
2431 | ); |
2432 | |
2433 | auto printerA = QSharedPointer<Printer>(new Printer(backend)); |
2434 | |
2435 | === added file 'tests/unittests/Printers/tst_utils.cpp' |
2436 | --- tests/unittests/Printers/tst_utils.cpp 1970-01-01 00:00:00 +0000 |
2437 | +++ tests/unittests/Printers/tst_utils.cpp 2017-04-07 12:51:46 +0000 |
2438 | @@ -0,0 +1,107 @@ |
2439 | +/* |
2440 | + * Copyright (C) 2017 Canonical, Ltd. |
2441 | + * |
2442 | + * This program is free software; you can redistribute it and/or modify |
2443 | + * it under the terms of the GNU Lesser General Public License as published by |
2444 | + * the Free Software Foundation; version 3. |
2445 | + * |
2446 | + * This program is distributed in the hope that it will be useful, |
2447 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
2448 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2449 | + * GNU Lesser General Public License for more details. |
2450 | + * |
2451 | + * You should have received a copy of the GNU Lesser General Public License |
2452 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
2453 | + */ |
2454 | + |
2455 | +#include "utils.h" |
2456 | + |
2457 | +#include <QObject> |
2458 | +#include <QTest> |
2459 | + |
2460 | +typedef QMap<QString, QString> IdDict; |
2461 | +Q_DECLARE_METATYPE(IdDict) |
2462 | + |
2463 | +class TstPrinterUtils : public QObject |
2464 | +{ |
2465 | + Q_OBJECT |
2466 | +private Q_SLOTS: |
2467 | + void tstParseDeviceId_data() |
2468 | + { |
2469 | + QTest::addColumn<QString>("deviceId"); |
2470 | + QTest::addColumn<IdDict>("expected"); |
2471 | + |
2472 | + { |
2473 | + 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;"); |
2474 | + auto expected = QMap<QString, QString>{ |
2475 | + {"MFG", "Hewlett-Packard"}, |
2476 | + {"MDL", "HP Color LaserJet CM3530 MFP"}, |
2477 | + {"CMD", "PJL,BIDI-ECP,PCLXL,PCL,PDF,PJL,POSTSCRIPT"}, |
2478 | + {"CLS", "PRINTER"}, |
2479 | + {"DES", "Hewlett-Packard Color LaserJet CM3530 MFP"}, |
2480 | + {"SN", QString::null}, |
2481 | + {"S", QString::null}, |
2482 | + {"P", QString::null}, |
2483 | + {"J", QString::null}, |
2484 | + }; |
2485 | + QTest::newRow("duplicate hp") << actual << expected; |
2486 | + } |
2487 | + { |
2488 | + QString actual("MFG:HP;MDL:HP Officejet Pro 8610;CMD:PCL,JPEG,URF;"); |
2489 | + auto expected = QMap<QString, QString>{ |
2490 | + {"MFG", "HP"}, |
2491 | + {"MDL", "HP Officejet Pro 8610"}, |
2492 | + {"CMD", "PCL,JPEG,URF"}, |
2493 | + {"CLS", QString::null}, |
2494 | + {"DES", QString::null}, |
2495 | + {"SN", QString::null}, |
2496 | + {"S", QString::null}, |
2497 | + {"P", QString::null}, |
2498 | + {"J", QString::null}, |
2499 | + }; |
2500 | + QTest::newRow("actual hp device id") << actual << expected; |
2501 | + } |
2502 | + { |
2503 | + 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;"); |
2504 | + auto expected = QMap<QString, QString>{ |
2505 | + {"MFG", "HP"}, |
2506 | + {"MDL", "Officejet Pro 8600"}, |
2507 | + {"CMD", "PCL3GUI,PCL3,PJL,JPEG,PCLM,URF,DW-PCL,802.11,802.3,DESKJET,DYN"}, |
2508 | + {"CLS", "PRINTER"}, |
2509 | + {"DES", "CM749A"}, |
2510 | + {"SN", "CN371DWH1V05KC"}, |
2511 | + {"S", "038080C484201021005a00800004518005a4418003c4618005a4118005a"}, |
2512 | + {"P", QString::null}, |
2513 | + {"J", QString::null}, |
2514 | + {"Z", "0102,05000009000008000008000008000008,0600,0700000000000000000000,0b0000000000000000000099ba0000000099b50000000099b90000000099b6,0c0,0e00000000000000000000,0f00000000000000000000,10000002000008000008000008000008,110,12002,150,17000000000000000000000000000000,181"}, |
2515 | + {"CID", "HPIJVIPAV2"}, |
2516 | + {"LEDMDIS", "USB#FF#CC#00,USB#07#01#02"} |
2517 | + }; |
2518 | + QTest::newRow("actual, but crazy, hp device id") << actual << expected; |
2519 | + } |
2520 | + { |
2521 | + QString actual("MFG:EPSON;CMD:ESCPL2,BDC,D4;MDL:Stylus Photo RX420;CLS:PRINTER;DES:EPSON Stylus Photo RX420;"); |
2522 | + auto expected = QMap<QString, QString>{ |
2523 | + {"MFG", "EPSON"}, |
2524 | + {"MDL", "Stylus Photo RX420"}, |
2525 | + {"CMD", "ESCPL2,BDC,D4"}, |
2526 | + {"CLS", "PRINTER"}, |
2527 | + {"DES", "EPSON Stylus Photo RX420"}, |
2528 | + {"SN", QString::null}, |
2529 | + {"S", QString::null}, |
2530 | + {"P", QString::null}, |
2531 | + {"J", QString::null}, |
2532 | + }; |
2533 | + QTest::newRow("some epson stylus") << actual << expected; |
2534 | + } |
2535 | + } |
2536 | + void tstParseDeviceId() |
2537 | + { |
2538 | + QFETCH(QString, deviceId); |
2539 | + QFETCH(IdDict, expected); |
2540 | + QCOMPARE(Utils::parseDeviceId(deviceId), expected); |
2541 | + } |
2542 | +}; |
2543 | + |
2544 | +QTEST_GUILESS_MAIN(TstPrinterUtils) |
2545 | +#include "tst_utils.moc" |
Getting a crash when using this http:// pastebin. ubuntu. com/24228896/