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

Subscribers

People subscribed via source and target branches