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

Proposed by Jonas G. Drange
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
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 ConsolidatedDeviceModel to make sure printers with multiple connections to it (e.g. dnssd, socket, etc.) are presented as one.

Description of the change

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

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

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

review: Needs Fixing
Revision history for this message
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 ppdChoiceToDuplexMode. Or are we not able to move them because they are used in the models and not specifically the backend?

review: Needs Information
Revision history for this message
Jonas G. Drange (jonas-drange) wrote : Posted in a previous version of this proposal

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

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

I'm proposing it against staging, as we want to release this before a printer-staging -> staging dance.

Revision history for this message
system-apps-ci-bot (system-apps-ci-bot) wrote :

PASSED: Continuous integration, rev:164
https://jenkins.canonical.com/system-apps/job/lp-ubuntu-ui-extras-staging-ci/5/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build/2370
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-0-fetch/2370
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/2186
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/2186/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=zesty/2186
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=zesty/2186/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/2186
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/2186/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=zesty/2186
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=zesty/2186/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/2186
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/2186/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=zesty/2186
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=zesty/2186/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/system-apps/job/lp-ubuntu-ui-extras-staging-ci/5/rebuild

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

From the silo building...
/<<BUILDDIR>>/ubuntu-ui-extras-0.3+16.04.20170331/tests/unittests/Printers/tst_printer.cpp: In member function ‘void TestPrinter::testUpdateFrom()’:
/<<BUILDDIR>>/ubuntu-ui-extras-0.3+16.04.20170331/tests/unittests/Printers/tst_printer.cpp:432:23: error: ‘Utils’ has not been declared
             "Duplex", Utils::duplexModeToPpdChoice(newDefaultDuplexMode));

Guess this needs pulling of staging to include my changes, and then change to use PpdUtils.

review: Needs Fixing
Revision history for this message
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();

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

All tests pass, feedback fixed. LGTM :-)

review: Approve
Revision history for this message
system-apps-ci-bot (system-apps-ci-bot) wrote :

PASSED: Continuous integration, rev:169
https://jenkins.canonical.com/system-apps/job/lp-ubuntu-ui-extras-staging-ci/10/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build/2405
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-0-fetch/2405
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/2217
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/2217/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=zesty/2217
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=zesty/2217/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/2217
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/2217/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=zesty/2217
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=zesty/2217/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/2217
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/2217/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=zesty/2217
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=zesty/2217/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/system-apps/job/lp-ubuntu-ui-extras-staging-ci/10/rebuild

review: Approve (continuous-integration)
Revision history for this message
system-apps-ci-bot (system-apps-ci-bot) wrote :

FAILED: Autolanding.
More details in the following jenkins job:
https://jenkins.canonical.com/system-apps/job/lp-ubuntu-ui-extras-staging-autoland/31/
Executed test runs:
    FAILURE: https://jenkins.canonical.com/system-apps/job/build/2423/console
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-0-fetch/2423
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/2234
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/2234/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=zesty/2234
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=zesty/2234/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/2234/console
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=zesty/2234/console
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/2234
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/2234/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=zesty/2234
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=zesty/2234/artifact/output/*zip*/output.zip

review: Needs Fixing (continuous-integration)
Revision history for this message
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.

review: Needs Fixing
173. By Jonas G. Drange

drop the device id for fax

Revision history for this message
system-apps-ci-bot (system-apps-ci-bot) wrote :

PASSED: Continuous integration, rev:172
https://jenkins.canonical.com/system-apps/job/lp-ubuntu-ui-extras-staging-ci/13/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build/2473
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-0-fetch/2473
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/2284
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/2284/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=zesty/2284
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=zesty/2284/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/2284
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/2284/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=zesty/2284
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=zesty/2284/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/2284
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/2284/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=zesty/2284
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=zesty/2284/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/system-apps/job/lp-ubuntu-ui-extras-staging-ci/13/rebuild

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

This LGTM now, all it misses is usb fax's. We can improve that later if required.

review: Approve
Revision history for this message
system-apps-ci-bot (system-apps-ci-bot) wrote :

PASSED: Continuous integration, rev:173
https://jenkins.canonical.com/system-apps/job/lp-ubuntu-ui-extras-staging-ci/14/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build/2475
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-0-fetch/2475
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/2286
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/2286/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=zesty/2286
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=zesty/2286/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/2286
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/2286/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=zesty/2286
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=zesty/2286/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/2286
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/2286/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=zesty/2286
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=zesty/2286/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/system-apps/job/lp-ubuntu-ui-extras-staging-ci/14/rebuild

review: Approve (continuous-integration)
174. By Jonas G. Drange

uses info as a last resort

Revision history for this message
system-apps-ci-bot (system-apps-ci-bot) wrote :

PASSED: Continuous integration, rev:174
https://jenkins.canonical.com/system-apps/job/lp-ubuntu-ui-extras-staging-ci/15/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build/2477
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-0-fetch/2477
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/2288
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/2288/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=zesty/2288
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=zesty/2288/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/2288
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/2288/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=zesty/2288
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=zesty/2288/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/2288
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/2288/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=zesty/2288
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=zesty/2288/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/system-apps/job/lp-ubuntu-ui-extras-staging-ci/15/rebuild

review: Approve (continuous-integration)
Revision history for this message
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

Revision history for this message
system-apps-ci-bot (system-apps-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
176. By Jonas G. Drange

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

Revision history for this message
system-apps-ci-bot (system-apps-ci-bot) wrote :

PASSED: Continuous integration, rev:176
https://jenkins.canonical.com/system-apps/job/lp-ubuntu-ui-extras-staging-ci/17/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build/2481
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-0-fetch/2481
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/2292
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/2292/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=zesty/2292
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=zesty/2292/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/2292
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/2292/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=zesty/2292
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=zesty/2292/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/2292
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/2292/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=zesty/2292
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=zesty/2292/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/system-apps/job/lp-ubuntu-ui-extras-staging-ci/17/rebuild

review: Approve (continuous-integration)

Unmerged revisions

176. By Jonas G. Drange

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

175. By Jonas G. Drange

make sure non-consolidateable devices are kept

174. By Jonas G. Drange

uses info as a last resort

173. By Jonas G. Drange

drop the device id for fax

172. By Jonas G. Drange

actually start the process, suggest hplip

171. By Jonas G. Drange

sets the faxid on any fax

170. By Jonas G. Drange

creates hp devices for hplip capable printers

169. By Jonas G. Drange

syncs with staging

168. By Jonas G. Drange

uses makeLower

167. By Jonas G. Drange

syncs with staging and fixes a test that broke

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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"

Subscribers

People subscribed via source and target branches