Merge lp:~jonas-drange/ubuntu-system-settings/filepicker into lp:ubuntu-system-settings

Proposed by Jonas G. Drange
Status: Approved
Approved by: Ken VanDine
Approved revision: 1681
Proposed branch: lp:~jonas-drange/ubuntu-system-settings/filepicker
Merge into: lp:ubuntu-system-settings
Prerequisite: lp:~timo-jyrinki/ubuntu-system-settings/stop_depending_on_transitional_packages
Diff against target: 1077 lines (+619/-151)
18 files modified
debian/control (+2/-2)
plugins/wifi/CMakeLists.txt (+0/-1)
plugins/wifi/CertDialog.qml (+39/-10)
plugins/wifi/CertPicker.qml (+0/-58)
plugins/wifi/OtherNetwork.qml (+57/-13)
plugins/wifi/certhandler.cpp (+24/-22)
plugins/wifi/certhandler.h (+3/-4)
tests/autopilot/ubuntu_system_settings/__init__.py (+0/-32)
tests/autopilot/ubuntu_system_settings/tests/test_vpn.py (+0/-9)
tests/mocks/CMakeLists.txt (+1/-0)
tests/mocks/Qt/CMakeLists.txt (+1/-0)
tests/mocks/Qt/labs/CMakeLists.txt (+1/-0)
tests/mocks/Qt/labs/folderlistmodel/CMakeLists.txt (+20/-0)
tests/mocks/Qt/labs/folderlistmodel/MockFolderListModel.cpp (+241/-0)
tests/mocks/Qt/labs/folderlistmodel/MockFolderListModel.h (+171/-0)
tests/mocks/Qt/labs/folderlistmodel/plugin.cpp (+26/-0)
tests/mocks/Qt/labs/folderlistmodel/plugin.h (+31/-0)
tests/mocks/Qt/labs/folderlistmodel/qmldir (+2/-0)
To merge this branch: bzr merge lp:~jonas-drange/ubuntu-system-settings/filepicker
Reviewer Review Type Date Requested Status
Ken VanDine Approve
system-apps-ci-bot continuous-integration Needs Fixing
Review via email: mp+308520@code.launchpad.net

This proposal supersedes a proposal from 2016-07-27.

Commit message

use the vpn filepicker, and make the cert handling more robust

Description of the change

This change is a prerequisite for the upcoming WAPI landing. It retires the old content hub file picker, and uses the one from USC (same one used for VPN).

It also introduces some bits in the Wi-Fi panel that we will need for supporting WAPI.

To post a comment you must log in.
Revision history for this message
system-apps-ci-bot (system-apps-ci-bot) wrote : Posted in a previous version of this proposal

FAILED: Continuous integration, rev:1674
https://jenkins.canonical.com/system-apps/job/lp-ubuntu-system-settings-ci/54/
Executed test runs:
    FAILURE: https://jenkins.canonical.com/system-apps/job/build/1044/console
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-0-fetch/1044
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-1-sourcepkg/release=vivid+overlay/941
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-1-sourcepkg/release=xenial+overlay/941
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-1-sourcepkg/release=yakkety/941
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=vivid+overlay/932/console
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/932/console
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=yakkety/932/console
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=vivid+overlay/932/console
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/932/console
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=yakkety/932/console
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=vivid+overlay/932/console
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/932/console
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=yakkety/932/console

Click here to trigger a rebuild:
https://jenkins.canonical.com/system-apps/job/lp-ubuntu-system-settings-ci/54/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Ken VanDine (ken-vandine) wrote : Posted in a previous version of this proposal

Looks good

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

FAILED: Continuous integration, rev:1680
https://jenkins.canonical.com/system-apps/job/lp-ubuntu-system-settings-ci/289/
Executed test runs:
    FAILURE: https://jenkins.canonical.com/system-apps/job/build/2290/console
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-0-fetch/2293
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=vivid+overlay/2118/console
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/2118/console
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=zesty/2118/console
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=vivid+overlay/2118/console
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/2118/console
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=zesty/2118/console
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=vivid+overlay/2118/console
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/2118/console
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=zesty/2118/console

Click here to trigger a rebuild:
https://jenkins.canonical.com/system-apps/job/lp-ubuntu-system-settings-ci/289/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Ken VanDine (ken-vandine) wrote :

Looks good

review: Approve
Revision history for this message
Ken VanDine (ken-vandine) wrote :

Looks good

review: Approve

Unmerged revisions

1681. By Jonas G. Drange

selects cert in selector when selected from disk (\!)

1680. By Jonas G. Drange

adds mock folder list model, used implicitly by otherNetwork

1679. By Jonas G. Drange

adds test dep on newer usc

1678. By Jonas G. Drange

syncs with trunk

1677. By Jonas G. Drange

syncs with trunk

1676. By Jonas G. Drange

merge prereq

1675. By Jonas G. Drange

sync trunk

1674. By Jonas G. Drange

use filepicker from USC, implement some wapi bits that will be needed down the line

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/control'
2--- debian/control 2016-12-16 13:32:09 +0000
3+++ debian/control 2017-02-06 15:08:32 +0000
4@@ -46,7 +46,7 @@
5 qml-module-qttest,
6 qml-module-qtquick2,
7 qml-module-ubuntu-components | qml-module-ubuntu-components-gles,
8- qtdeclarative5-ubuntu-settings-components (>= 0.8),
9+ qtdeclarative5-ubuntu-settings-components (>= 0.13),
10 libubuntuoneauth-2.0-dev,
11 libqtdbusmock1-dev (>= 0.2+14.04.20140724),
12 libqtdbustest1-dev,
13@@ -95,7 +95,7 @@
14 qml-module-ofono (>=0.90~),
15 qml-module-qtsysteminfo,
16 qtdeclarative5-ubuntu-content1,
17- qtdeclarative5-ubuntu-settings-components (>= 0.12),
18+ qtdeclarative5-ubuntu-settings-components (>= 0.13),
19 qml-module-ubuntu-components (>= 1.3.1584) | qml-module-ubuntu-components-gles (>= 1.3.1584),
20 suru-icon-theme (>= 14.04+15.04.20150813~),
21 whoopsie-preferences (>= 0.9),
22
23=== modified file 'plugins/wifi/CMakeLists.txt'
24--- plugins/wifi/CMakeLists.txt 2016-08-16 23:50:33 +0000
25+++ plugins/wifi/CMakeLists.txt 2017-02-06 15:08:32 +0000
26@@ -1,7 +1,6 @@
27 set(QML_SOURCES
28 AccessPoint.qml
29 BaseMenuItem.qml
30-CertPicker.qml
31 CertDialog.qml
32 Common.qml
33 DivMenuItem.qml
34
35=== modified file 'plugins/wifi/CertDialog.qml'
36--- plugins/wifi/CertDialog.qml 2015-08-10 13:31:45 +0000
37+++ plugins/wifi/CertDialog.qml 2017-02-06 15:08:32 +0000
38@@ -14,6 +14,7 @@
39 property var fileName;
40
41 signal updateSignal(var update);
42+ signal certSaved(string file);
43
44 anchors.fill: parent
45
46@@ -27,6 +28,30 @@
47 }
48 }
49
50+ Component {
51+ id: failedToImportComponent
52+ Dialog {
53+ id: failedToImportDialog
54+ title: {
55+ if (certType === 0) { // certificate
56+ return i18n.tr("Could not save certificate.")
57+ } else if (certType === 1) { // privatekey
58+ return i18n.tr("Could not save key.")
59+ } else if (certType === 2) { // pacFile
60+ return i18n.tr("Could not save pack file.")
61+ }
62+ }
63+
64+ Button {
65+ text: i18n.tr("OK")
66+ onClicked: {
67+ PopupUtils.close(failedToImportDialog);
68+ PopupUtils.close(certDialog);
69+ }
70+ }
71+ }
72+ }
73+
74 FileHandler {
75 id: fileHandler
76 }
77@@ -64,7 +89,6 @@
78 Layout.fillWidth: true
79 text: i18n.tr("Cancel")
80 onClicked: {
81- fileHandler.removeFile(certDialog.fileName);
82 PopupUtils.close(certDialog);
83 }
84 }
85@@ -74,19 +98,24 @@
86 text: i18n.tr("Save")
87 Layout.fillWidth: true
88 enabled: (certDialog.certContent.text !== "")
89- onClicked: { if (certType === 0) { // certificate
90- fileHandler.moveCertFile(certDialog.fileName);
91+ onClicked: {
92+ var ret;
93+ if (certType === 0) { // certificate
94+ ret = fileHandler.copyCertFile(certDialog.fileName);
95 } else if (certType === 1) { // privatekey
96- fileHandler.moveKeyFile(certDialog.fileName);
97+ ret = fileHandler.copyKeyFile(certDialog.fileName);
98 } else if (certType === 2) { // pacFile
99- fileHandler.movePacFile(certDialog.fileName);
100+ ret = fileHandler.copyPacFile(certDialog.fileName);
101 }
102
103- /* Just to be sure source file will be deleted if move was
104- not successfull */
105- fileHandler.removeFile(certDialog.fileName);
106- certDialog.updateSignal(true);
107- PopupUtils.close(certDialog);
108+ // If the cert/key/pac doesn't compute, warn.
109+ if (!ret) {
110+ PopupUtils.open(failedToImportComponent);
111+ } else {
112+ certDialog.updateSignal(true);
113+ certDialog.certSaved(ret);
114+ PopupUtils.close(certDialog);
115+ }
116 }
117 }
118 }
119
120=== removed file 'plugins/wifi/CertPicker.qml'
121--- plugins/wifi/CertPicker.qml 2016-03-16 16:26:18 +0000
122+++ plugins/wifi/CertPicker.qml 1970-01-01 00:00:00 +0000
123@@ -1,58 +0,0 @@
124-import QtQuick 2.4
125-import QtQuick.Layouts 1.1
126-import Ubuntu.Components 1.3
127-import Ubuntu.Components.Popups 1.3
128-import Ubuntu.Content 1.3
129-
130-PopupBase {
131- id: picker
132-
133- signal fileImportSignal (var file)
134- property var activeTransfer
135-
136- Rectangle {
137- anchors.fill: parent
138-
139- ContentTransferHint {
140- id: transferHint
141- anchors.fill: parent
142- activeTransfer: picker.activeTransfer
143- }
144-
145- ContentStore {
146- id: appStore
147- scope: ContentScope.App
148- }
149-
150- ContentPeerPicker {
151- id: peerPicker
152- anchors.fill: parent
153- visible: true
154- contentType: ContentType.Documents
155- handler: ContentHandler.Source
156- onPeerSelected: {
157- peer.selectionType = ContentTransfer.Single;
158- picker.activeTransfer = peer.request(appStore);
159- }
160- onCancelPressed: PopupUtils.close(picker)
161- }
162- }
163-
164- Connections {
165- target: picker.activeTransfer ? picker.activeTransfer : null
166- onStateChanged: {
167- if (picker.activeTransfer.state === ContentTransfer.Charged) {
168- if (picker.activeTransfer.items.length > 0) {
169- var fileUrl = picker.activeTransfer.items[0].url;
170- picker.fileImportSignal(
171- fileUrl.toString().replace("file://", "")
172- );
173- PopupUtils.close(picker);
174- }
175- } else if (picker.activeTransfer.state === ContentTransfer.Aborted){
176- picker.fileImportSignal(false);
177- PopupUtils.close(picker);
178- }
179- }
180- }
181-}
182
183=== modified file 'plugins/wifi/OtherNetwork.qml'
184--- plugins/wifi/OtherNetwork.qml 2017-01-10 12:40:13 +0000
185+++ plugins/wifi/OtherNetwork.qml 2017-02-06 15:08:32 +0000
186@@ -20,18 +20,25 @@
187 import Ubuntu.Components 1.3
188 import Ubuntu.Components.ListItems 1.3 as ListItems
189 import Ubuntu.Components.Popups 1.3
190+import Ubuntu.Settings.Components 0.1
191 import Ubuntu.SystemSettings.Wifi 1.0
192
193 Component {
194
195 Dialog {
196
197+ Component {
198+ id: filePickerComponent
199+ FilePicker {}
200+ }
201+
202 id: otherNetworkDialog
203 objectName: "otherNetworkDialog"
204 anchorToKeyboard: true
205
206 property string ssid
207 property string bssid
208+ property string keyMgmt
209
210 function settingsValid () {
211 if (networkname.length === 0) {
212@@ -59,11 +66,10 @@
213 var pickerDialog;
214 var certDialog;
215
216- pickerDialog = PopupUtils.open(
217- Qt.resolvedUrl("./CertPicker.qml")
218- );
219- pickerDialog.fileImportSignal.connect(function (file) {
220- if (!file === false) {
221+ pickerDialog = PopupUtils.open(filePickerComponent);
222+ pickerDialog.accept.connect(function (file) {
223+ PopupUtils.close(pickerDialog);
224+ if (file) {
225 certDialogLoader.source = Qt.resolvedUrl(
226 "./CertDialog.qml"
227 );
228@@ -79,17 +85,40 @@
229 } else if (update && type === 1) {
230 privatekeyListModel.dataupdate();
231 } else if (update && type === 2) {
232- pacFileListModeL.dataupdate();
233+ pacFileListModel.dataupdate();
234+ }
235+ });
236+
237+ // Updates the selected index of a selector.
238+ certDialog.certSaved.connect(function (certFile) {
239+ var model;
240+ var selector;
241+ if (update && type === 0) {
242+ selector = cacertSelector;
243+ model = cacertListModel;
244+ } else if (update && type === 1) {
245+ selector = privateKeySelector;
246+ model = privatekeyListModel;
247+ } else if (update && type === 2) {
248+ selector = pacFileSelector;
249+ model = pacFileListModel;
250+ }
251+
252+ for (var i = 0; i < model.rowCount(); i++) {
253+ if (model.getfileName(i) === certFile) {
254+ selector.selectedIndex = i;
255+ }
256 }
257 });
258 }
259 });
260+ pickerDialog.reject.connect(function () {
261+ PopupUtils.close(pickerDialog);
262+ });
263 }
264
265 title: ssid ?
266- /* TODO(jgdx): Hack to avoid breaking string freeze. This will be
267- changed to i18n.tr("Connect to %1").arg(ssid) per spec. */
268- i18n.tr("Connect to Wi‑Fi") + " " + ssid :
269+ i18n.tr("Connect to %1").arg(ssid) :
270 i18n.tr("Connect to Hidden Network")
271 text: feedback.enabled ? feedback.text : "";
272
273@@ -322,7 +351,22 @@
274 i18n.tr("Dynamic WEP (802.1x)"), // index: 4
275 i18n.tr("LEAP"), // index: 5
276 ]
277- selectedIndex: 1
278+ selectedIndex: {
279+ switch(keyMgmt) {
280+ case 'none': // WEP
281+ return 0;
282+ case 'wpa-eap': // WPA-Enterprise
283+ return 2;
284+ case 'wep': // WEP
285+ return 3;
286+ case 'ieee8021x': // Dynamic WEP
287+ return 4;
288+ case 'wpa-none': // Ad-Hoc WPA-PSK
289+ case 'wpa-psk': // infrastructure WPA-PSK
290+ default: // Default is WPA
291+ return 1;
292+ }
293+ }
294 }
295
296 Label {
297@@ -357,7 +401,6 @@
298 visible: securityList.selectedIndex === 2 ||
299 securityList.selectedIndex === 4
300 }
301-
302 Label {
303 id: p2authListLabel
304 text : i18n.tr("Inner authentication")
305@@ -791,7 +834,7 @@
306 font.bold: false
307 color: Theme.palette.normal.baseText
308 elide: Text.ElideRight
309- visible: securityList.selectedIndex !== 0
310+ visible: password.visible
311 }
312
313 TextField {
314@@ -799,6 +842,7 @@
315 objectName: "password"
316 width: parent.width
317 visible: securityList.selectedIndex !== 0
318+
319 echoMode: passwordVisibleSwitch.checked ?
320 TextInput.Normal : TextInput.Password
321 inputMethodHints: Qt.ImhNoPredictiveText
322@@ -809,7 +853,7 @@
323 id: passwordVisiblityRow
324 layoutDirection: Qt.LeftToRight
325 spacing: units.gu(2)
326- visible: securityList.selectedIndex !== 0
327+ visible: password.visible
328
329 CheckBox {
330 id: passwordVisibleSwitch
331
332=== modified file 'plugins/wifi/certhandler.cpp'
333--- plugins/wifi/certhandler.cpp 2015-06-15 19:28:23 +0000
334+++ plugins/wifi/certhandler.cpp 2017-02-06 15:08:32 +0000
335@@ -32,19 +32,19 @@
336 }
337 }
338
339-QString FileHandler::moveCertFile(QString filename){
340+QString FileHandler::copyCertFile(QString filename){
341 QDir certPath(CERTS_PATH);
342 if (!certPath.exists(CERTS_PATH)){
343 certPath.mkpath(CERTS_PATH);
344 }
345- QFile file(filename);
346 QByteArray certificate = getCertContent(filename);
347 QList<QSslCertificate> SslCertificateList = QSslCertificate::fromData(certificate, QSsl::Pem);
348 if ( !SslCertificateList.isEmpty() ){
349 QStringList subject = SslCertificateList[0].subjectInfo(QSslCertificate::CommonName);
350 QString modFileName = CERTS_PATH+subject[0]+".pem";
351- if(file.rename(modFileName.replace(" ", "_"))){
352- return file.fileName();
353+ modFileName = modFileName.replace(" ", "_");
354+ if(QFile::copy(filename, modFileName)){
355+ return modFileName;
356 } else {
357 return "";
358 }
359@@ -52,7 +52,7 @@
360 return "";
361 }
362
363-QString FileHandler::moveKeyFile(QString filename){
364+QString FileHandler::copyKeyFile(QString filename){
365 QDir keyPath(KEYS_PATH);
366 if (!keyPath.exists(KEYS_PATH)){
367 keyPath.mkpath(KEYS_PATH);
368@@ -64,8 +64,8 @@
369 if ( !checkKey.isNull() ){
370 QFileInfo fileInfo(file);
371 QString modFileName = KEYS_PATH + fileInfo.fileName().replace(" ", "_");
372- if(file.rename(modFileName)){
373- return file.fileName();
374+ if(QFile::copy(filename, modFileName)){
375+ return modFileName;
376 } else {
377 return "" ;
378 }
379@@ -73,7 +73,7 @@
380 return "";
381 }
382
383-QString FileHandler::movePacFile(QString filename){
384+QString FileHandler::copyPacFile(QString filename){
385 QDir keyPath(PACS_PATH);
386 if (!keyPath.exists(PACS_PATH)){
387 keyPath.mkpath(PACS_PATH);
388@@ -81,17 +81,12 @@
389 QFile file(filename);
390 QFileInfo fileInfo(file);
391 QString modFileName = PACS_PATH + fileInfo.baseName().replace(" ", "_") + ".pac";
392- if(file.rename(modFileName)){
393- return file.fileName();
394+ if(QFile::copy(filename, modFileName)){
395+ return modFileName;
396 }
397 return "" ;
398 }
399
400-bool FileHandler::removeFile(QString filename){
401- QFile file(filename);
402- return file.remove();
403-}
404-
405 struct CertificateListModel::Private {
406 QStringList data;
407 };
408@@ -144,8 +139,9 @@
409 }
410
411 QVariant CertificateListModel::data(const QModelIndex &index, int role) const {
412+ QVariant rv;
413 if(!index.isValid() || index.row() >= ( p->data.size()) ) {
414- return QVariant();
415+ return rv;
416 } else if (index.row() == 0){
417 const QString &row0 = p->data[index.row()];
418
419@@ -168,13 +164,19 @@
420 const QString &row = CERTS_PATH+p->data[index.row()];
421 QList<QSslCertificate> certificate = QSslCertificate::fromPath(row, QSsl::Pem, QRegExp::Wildcard);
422
423+ if (certificate.size() == 0) {
424+ return rv;
425+ }
426+
427 switch(role) {
428-
429- case CNRole : return certificate[0].subjectInfo(QSslCertificate::CommonName)[0];
430- case ORole : return certificate[0].subjectInfo(QSslCertificate::Organization)[0];
431- case expDateRole : return certificate[0].expiryDate().toString("dd.MM.yyyy");
432-
433- default : return QVariant();
434+ case CNRole:
435+ return certificate[0].subjectInfo(QSslCertificate::CommonName).value(0, "");
436+ case ORole:
437+ return certificate[0].subjectInfo(QSslCertificate::Organization).value(0, "");
438+ case expDateRole:
439+ return certificate[0].expiryDate().toString("dd.MM.yyyy");
440+ default:
441+ return rv;
442 }
443 }
444
445
446=== modified file 'plugins/wifi/certhandler.h'
447--- plugins/wifi/certhandler.h 2015-06-03 19:35:09 +0000
448+++ plugins/wifi/certhandler.h 2017-02-06 15:08:32 +0000
449@@ -11,10 +11,9 @@
450 Q_OBJECT
451 public:
452 Q_INVOKABLE QByteArray getCertContent(QString filename);
453- Q_INVOKABLE QString moveCertFile(QString filename);
454- Q_INVOKABLE QString moveKeyFile(QString filename);
455- Q_INVOKABLE QString movePacFile(QString filename);
456- Q_INVOKABLE bool removeFile(QString filename);
457+ Q_INVOKABLE QString copyCertFile(QString filename);
458+ Q_INVOKABLE QString copyKeyFile(QString filename);
459+ Q_INVOKABLE QString copyPacFile(QString filename);
460 };
461
462
463
464=== modified file 'tests/autopilot/ubuntu_system_settings/__init__.py'
465--- tests/autopilot/ubuntu_system_settings/__init__.py 2016-12-01 13:50:03 +0000
466+++ tests/autopilot/ubuntu_system_settings/__init__.py 2017-02-06 15:08:32 +0000
467@@ -1747,38 +1747,6 @@
468 self._openvpn_port_field.write(port)
469
470 @autopilot.logging.log_action(logger.debug)
471- def set_openvpn_ca(self, paths):
472- self.set_openvpn_file(self._openvpn_ca_field, paths)
473-
474- @autopilot.logging.log_action(logger.debug)
475- def set_openvpn_file(self, field, paths):
476- utils.dismiss_osk()
477- self.get_root_instance().main_view.scroll_to_and_click(field)
478-
479- # Wait for expanded animation.
480- sleep(0.5)
481-
482- # file = field.wait_select_single(objectName='vpnFileSelectorItem0')
483- choose = field.wait_select_single(objectName='vpnFileSelectorItem1')
484- self.pointing_device.click_object(choose)
485- self.get_root_instance().main_view.scroll_to_and_click(choose)
486- file_dialog = self.get_root_instance().wait_select_single(
487- objectName='vpnDialogFile'
488- )
489-
490- # Go to root /
491- root = file_dialog.wait_select_single(objectName='vpnFilePathItem_/')
492- self.pointing_device.click_object(root)
493-
494- for path in paths:
495- list_view = file_dialog.wait_select_single(
496- 'QQuickListView', objectName='vpnFileList'
497- )
498- list_view.click_element('vpnFileItem_%s' % path)
499- accept = file_dialog.wait_select_single(objectName='vpnFileAccept')
500- self.pointing_device.click_object(accept)
501-
502- @autopilot.logging.log_action(logger.debug)
503 def openvpn_okay(self):
504 utils.dismiss_osk()
505 self.get_root_instance().main_view.scroll_to_and_click(
506
507=== modified file 'tests/autopilot/ubuntu_system_settings/tests/test_vpn.py'
508--- tests/autopilot/ubuntu_system_settings/tests/test_vpn.py 2016-03-09 14:03:57 +0000
509+++ tests/autopilot/ubuntu_system_settings/tests/test_vpn.py 2017-02-06 15:08:32 +0000
510@@ -35,10 +35,6 @@
511 page.set_openvpn_server('vpn.ubuntu.com')
512 page.set_openvpn_custom_port('1000')
513
514- page.set_openvpn_ca(
515- # Any file will do.
516- ['etc', 'apt', 'sources.list']
517- )
518 page.openvpn_okay()
519
520 self.assertThat(
521@@ -47,11 +43,6 @@
522 )
523
524 self.assertThat(
525- lambda: conn_obj.Get(VPN_CONN_OPENVPN_IFACE, 'ca'),
526- Eventually(Equals('/etc/apt/sources.list'))
527- )
528-
529- self.assertThat(
530 lambda: conn_obj.Get(VPN_CONN_OPENVPN_IFACE, 'portSet'),
531 Eventually(Equals(True))
532 )
533
534=== modified file 'tests/mocks/CMakeLists.txt'
535--- tests/mocks/CMakeLists.txt 2016-12-05 13:05:27 +0000
536+++ tests/mocks/CMakeLists.txt 2017-02-06 15:08:32 +0000
537@@ -35,3 +35,4 @@
538 add_subdirectory(QMenuModel)
539 add_subdirectory(SystemSettings)
540 add_subdirectory(Ubuntu)
541+add_subdirectory(Qt)
542
543=== added directory 'tests/mocks/Qt'
544=== added file 'tests/mocks/Qt/CMakeLists.txt'
545--- tests/mocks/Qt/CMakeLists.txt 1970-01-01 00:00:00 +0000
546+++ tests/mocks/Qt/CMakeLists.txt 2017-02-06 15:08:32 +0000
547@@ -0,0 +1,1 @@
548+add_subdirectory(labs)
549
550=== added directory 'tests/mocks/Qt/labs'
551=== added file 'tests/mocks/Qt/labs/CMakeLists.txt'
552--- tests/mocks/Qt/labs/CMakeLists.txt 1970-01-01 00:00:00 +0000
553+++ tests/mocks/Qt/labs/CMakeLists.txt 2017-02-06 15:08:32 +0000
554@@ -0,0 +1,1 @@
555+add_subdirectory(folderlistmodel)
556
557=== added directory 'tests/mocks/Qt/labs/folderlistmodel'
558=== added file 'tests/mocks/Qt/labs/folderlistmodel/CMakeLists.txt'
559--- tests/mocks/Qt/labs/folderlistmodel/CMakeLists.txt 1970-01-01 00:00:00 +0000
560+++ tests/mocks/Qt/labs/folderlistmodel/CMakeLists.txt 2017-02-06 15:08:32 +0000
561@@ -0,0 +1,20 @@
562+include_directories(
563+ ${CMAKE_CURRENT_BINARY_DIR}
564+ ${Qt5Core_INCLUDE_DIRS}
565+ ${Qt5Quick_INCLUDE_DIRS}
566+)
567+
568+set(Qtlabs_folderlistmodel_SRCS
569+ MockFolderListModel.cpp
570+ plugin.cpp
571+)
572+
573+add_library(QtLabsFolderListModelQml MODULE ${Qtlabs_folderlistmodel_SRCS})
574+target_link_libraries(QtLabsFolderListModelQml
575+ ${Qt5Core_LIBRARIES}
576+ ${Qt5Quick_LIBRARIES}
577+)
578+
579+qt5_use_modules(QtLabsFolderListModelQml Qml)
580+
581+add_uss_mock(Qt.labs.folderlistmodel 2.1 Qt.labs.folderlistmodel TARGETS QtLabsFolderListModelQml)
582
583=== added file 'tests/mocks/Qt/labs/folderlistmodel/MockFolderListModel.cpp'
584--- tests/mocks/Qt/labs/folderlistmodel/MockFolderListModel.cpp 1970-01-01 00:00:00 +0000
585+++ tests/mocks/Qt/labs/folderlistmodel/MockFolderListModel.cpp 2017-02-06 15:08:32 +0000
586@@ -0,0 +1,241 @@
587+/*
588+ * Copyright (C) 2016 Canonical, Ltd.
589+ *
590+ * This program is free software; you can redistribute it and/or modify
591+ * it under the terms of the GNU General Public License as published by
592+ * the Free Software Foundation; version 3.
593+ *
594+ * This program is distributed in the hope that it will be useful,
595+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
596+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
597+ * GNU General Public License for more details.
598+ *
599+ * You should have received a copy of the GNU General Public License
600+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
601+ */
602+
603+#include "MockFolderListModel.h"
604+
605+#include <QDateTime>
606+
607+MockFolderListModel::MockFolderListModel(QObject *parent)
608+ : QAbstractListModel(parent)
609+ , m_files()
610+{
611+}
612+
613+QUrl MockFolderListModel::folder() const { return m_folder; }
614+bool MockFolderListModel::caseSensitive() const { return m_caseSensitive; }
615+int MockFolderListModel::count() const { return m_count; }
616+QStringList MockFolderListModel::nameFilters() const { return m_nameFilters; }
617+QUrl MockFolderListModel::parentFolder() const { return m_parentFolder; }
618+QUrl MockFolderListModel::rootFolder() const { return m_rootFolder; }
619+bool MockFolderListModel::showDirs() const { return m_showDirs; }
620+bool MockFolderListModel::showDirsFirst() const { return m_showDirsFirst; }
621+bool MockFolderListModel::showDotAndDotDot() const { return m_showDotAndDotDot; }
622+bool MockFolderListModel::showFiles() const { return m_showFiles; }
623+bool MockFolderListModel::showHidden() const { return m_showHidden; }
624+bool MockFolderListModel::showOnlyReadable() const { return m_showOnlyReadable; }
625+MockFolderListModel::Sort MockFolderListModel::sortField() const { return m_sortField; }
626+bool MockFolderListModel::sortReversed() const { return m_sortReversed; }
627+
628+void MockFolderListModel::setCaseSensitive(const bool caseSensitive)
629+{
630+ if (caseSensitive != m_caseSensitive) {
631+ m_caseSensitive = caseSensitive;
632+ Q_EMIT caseSensitiveChanged();
633+ }
634+}
635+
636+void MockFolderListModel::setCount(const int &count)
637+{
638+ if (count != m_count) {
639+ m_count = count;
640+ Q_EMIT countChanged();
641+ }
642+}
643+
644+void MockFolderListModel::setFolder(const QUrl &folder)
645+{
646+ if (folder != m_folder) {
647+ m_folder = folder;
648+ Q_EMIT folderChanged();
649+ }
650+}
651+
652+void MockFolderListModel::setNameFilters(const QStringList &nameFilters)
653+{
654+ if (nameFilters != m_nameFilters) {
655+ m_nameFilters = nameFilters;
656+ Q_EMIT nameFiltersChanged();
657+ }
658+}
659+
660+void MockFolderListModel::setParentFolder(const QUrl &parentFolder)
661+{
662+ if (parentFolder != m_parentFolder) {
663+ m_parentFolder = parentFolder;
664+ Q_EMIT parentFolderChanged();
665+ }
666+}
667+
668+void MockFolderListModel::setRootFolder(const QUrl &rootFolder)
669+{
670+ if (rootFolder != m_rootFolder) {
671+ m_rootFolder = rootFolder;
672+ Q_EMIT rootFolderChanged();
673+ }
674+}
675+
676+void MockFolderListModel::setShowDirs(const bool showDirs)
677+{
678+ if (showDirs != m_showDirs) {
679+ m_showDirs = showDirs;
680+ Q_EMIT showDirsChanged();
681+ }
682+}
683+
684+void MockFolderListModel::setShowDirsFirst(const bool showDirsFirst)
685+{
686+ if (showDirsFirst != m_showDirsFirst) {
687+ m_showDirsFirst = showDirsFirst;
688+ Q_EMIT showDirsFirstChanged();
689+ }
690+}
691+
692+void MockFolderListModel::setShowDotAndDotDot(const bool showDotAndDotDot)
693+{
694+ if (showDotAndDotDot != m_showDotAndDotDot) {
695+ m_showDotAndDotDot = showDotAndDotDot;
696+ Q_EMIT showDotAndDotDotChanged();
697+ }
698+}
699+
700+void MockFolderListModel::setShowFiles(const bool showFiles)
701+{
702+ if (showFiles != m_showFiles) {
703+ m_showFiles = showFiles;
704+ Q_EMIT showFilesChanged();
705+ }
706+}
707+
708+void MockFolderListModel::setShowHidden(const bool showHidden)
709+{
710+ if (showHidden != m_showHidden) {
711+ m_showHidden = showHidden;
712+ Q_EMIT showHiddenChanged();
713+ }
714+}
715+
716+void MockFolderListModel::setShowOnlyReadable(const bool showOnlyReadable)
717+{
718+ if (showOnlyReadable != m_showOnlyReadable) {
719+ m_showOnlyReadable = showOnlyReadable;
720+ Q_EMIT showOnlyReadableChanged();
721+ }
722+}
723+
724+void MockFolderListModel::setSortField(const Sort &sortField)
725+{
726+ if (sortField != m_sortField) {
727+ m_sortField = sortField;
728+ Q_EMIT sortFieldChanged();
729+ }
730+}
731+
732+void MockFolderListModel::setSortReversed(const bool sortReversed)
733+{
734+ if (sortReversed != m_sortReversed) {
735+ m_sortReversed = sortReversed;
736+ Q_EMIT sortReversedChanged();
737+ }
738+}
739+
740+QHash<int, QByteArray> MockFolderListModel::roleNames() const
741+{
742+ QHash<int, QByteArray> names;
743+
744+ names[Roles::FileNameRole] = "fileName";
745+ names[Roles::FilePathRole] = "filePath";
746+ names[Roles::FileURLRole] = "fileURL";
747+ names[Roles::FileBaseNameRole] = "fileBaseName";
748+ names[Roles::FileSuffixRole] = "fileSuffix";
749+ names[Roles::FileSizeRole] = "fileSize";
750+ names[Roles::FileModifiedRole] = "fileModified";
751+ names[Roles::FileAccessedRole] = "fileAccessed";
752+ names[Roles::FileIsDirRole] = "fileIsDir";
753+
754+ return names;
755+}
756+
757+
758+int MockFolderListModel::rowCount(const QModelIndex&) const
759+{
760+ return m_files.count();
761+}
762+
763+QVariant MockFolderListModel::data(const QModelIndex &index, int role) const
764+{
765+ QVariant rv;
766+
767+ if (index.row() >= m_files.size())
768+ return rv;
769+
770+ switch (role)
771+ {
772+ case Roles::FileNameRole:
773+ rv = m_files.at(index.row()).fileName;
774+ break;
775+ case Roles::FilePathRole:
776+ rv = m_files.at(index.row()).filePath;
777+ break;
778+ case Roles::FileBaseNameRole:
779+ rv = m_files.at(index.row()).baseName;
780+ break;
781+ case Roles::FileSuffixRole:
782+ rv = m_files.at(index.row()).suffix;
783+ break;
784+ case Roles::FileSizeRole:
785+ rv = m_files.at(index.row()).size;
786+ break;
787+ case Roles::FileModifiedRole:
788+ rv = QDateTime();
789+ break;
790+ case Roles::FileAccessedRole:
791+ rv = QDateTime();
792+ break;
793+ case Roles::FileIsDirRole:
794+ rv = m_files.at(index.row()).isDir;
795+ break;
796+ case Roles::FileURLRole:
797+ rv = QUrl::fromLocalFile(m_files.at(index.row()).filePath);
798+ break;
799+ default:
800+ break;
801+ }
802+ return rv;
803+}
804+
805+QModelIndex MockFolderListModel::index(int row, int column, const QModelIndex&) const
806+{
807+ return createIndex(row, column);
808+}
809+
810+void MockFolderListModel::mockAddFile(const QString &fileName,
811+ const QString &filePath,
812+ const QString &baseName,
813+ const qint64 &size,
814+ const QString &suffix,
815+ const bool isDir,
816+ const bool isFile)
817+{
818+ MockFile m;
819+ m.fileName = fileName;
820+ m.filePath = filePath;
821+ m.baseName = baseName;
822+ m.size = size;
823+ m.suffix = suffix;
824+ m.isDir = isDir;
825+ m.isFile = isFile;
826+ m_files.append(m);
827+}
828
829=== added file 'tests/mocks/Qt/labs/folderlistmodel/MockFolderListModel.h'
830--- tests/mocks/Qt/labs/folderlistmodel/MockFolderListModel.h 1970-01-01 00:00:00 +0000
831+++ tests/mocks/Qt/labs/folderlistmodel/MockFolderListModel.h 2017-02-06 15:08:32 +0000
832@@ -0,0 +1,171 @@
833+/*
834+ * Copyright (C) 2016 Canonical, Ltd.
835+ *
836+ * This program is free software; you can redistribute it and/or modify
837+ * it under the terms of the GNU General Public License as published by
838+ * the Free Software Foundation; version 3.
839+ *
840+ * This program is distributed in the hope that it will be useful,
841+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
842+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
843+ * GNU General Public License for more details.
844+ *
845+ * You should have received a copy of the GNU General Public License
846+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
847+ */
848+
849+#ifndef MOCK_FOLDERLISTMODEL_H
850+#define MOCK_FOLDERLISTMODEL_H
851+
852+#include <QAbstractListModel>
853+#include <QObject>
854+#include <QString>
855+#include <QUrl>
856+#include <QStringList>
857+
858+struct MockFile {
859+ QString fileName;
860+ QString filePath;
861+ QString baseName;
862+ qint64 size;
863+ QString suffix;
864+ bool isDir;
865+ bool isFile;
866+};
867+
868+class MockFolderListModel : public QAbstractListModel
869+{
870+ Q_OBJECT
871+ Q_ENUMS(Sort)
872+ Q_PROPERTY(QUrl folder READ folder WRITE setFolder NOTIFY folderChanged)
873+ Q_PROPERTY(bool caseSensitive READ caseSensitive
874+ WRITE setCaseSensitive NOTIFY caseSensitiveChanged)
875+ Q_PROPERTY(int count READ count
876+ WRITE setCount NOTIFY countChanged)
877+ Q_PROPERTY(QStringList nameFilters READ nameFilters
878+ WRITE setNameFilters NOTIFY nameFiltersChanged)
879+ Q_PROPERTY(QUrl parentFolder READ parentFolder
880+ WRITE setParentFolder NOTIFY parentFolderChanged)
881+ Q_PROPERTY(QUrl rootFolder READ rootFolder
882+ WRITE setRootFolder NOTIFY rootFolderChanged)
883+ Q_PROPERTY(bool showDirs READ showDirs
884+ WRITE setShowDirs NOTIFY showDirsChanged)
885+ Q_PROPERTY(bool showDirsFirst READ showDirsFirst
886+ WRITE setShowDirsFirst NOTIFY showDirsFirstChanged)
887+ Q_PROPERTY(bool showDotAndDotDot READ showDotAndDotDot
888+ WRITE setShowDotAndDotDot NOTIFY showDotAndDotDotChanged)
889+ Q_PROPERTY(bool showFiles READ showFiles
890+ WRITE setShowFiles NOTIFY showFilesChanged)
891+ Q_PROPERTY(bool showHidden READ showHidden
892+ WRITE setShowHidden NOTIFY showHiddenChanged)
893+ Q_PROPERTY(bool showOnlyReadable READ showOnlyReadable
894+ WRITE setShowOnlyReadable NOTIFY showOnlyReadableChanged)
895+ Q_PROPERTY(Sort sortField READ sortField
896+ WRITE setSortField NOTIFY sortFieldChanged)
897+ Q_PROPERTY(bool sortReversed READ sortReversed
898+ WRITE setSortReversed NOTIFY sortReversedChanged)
899+
900+public:
901+ explicit MockFolderListModel(QObject *parent = 0);
902+
903+ enum Roles {
904+ FileNameRole = Qt::DisplayRole + 1,
905+ FilePathRole,
906+ FileURLRole,
907+ FileBaseNameRole,
908+ FileSuffixRole,
909+ FileSizeRole,
910+ FileModifiedRole,
911+ FileAccessedRole,
912+ FileIsDirRole
913+ };
914+
915+ enum class Sort : uint
916+ {
917+ Unsorted = 0,
918+ Name,
919+ Time,
920+ Size,
921+ Type
922+ };
923+
924+ QUrl folder() const;
925+ bool caseSensitive() const;
926+ int count() const;
927+ QStringList nameFilters() const;
928+ QUrl parentFolder() const;
929+ QUrl rootFolder() const;
930+ bool showDirs() const;
931+ bool showDirsFirst() const;
932+ bool showDotAndDotDot() const;
933+ bool showFiles() const;
934+ bool showHidden() const;
935+ bool showOnlyReadable() const;
936+ Sort sortField() const;
937+ bool sortReversed() const;
938+
939+ void setCaseSensitive(const QUrl &folder);
940+ void setCaseSensitive(const bool caseSensitive);
941+ void setCount(const int &count);
942+ void setFolder(const QUrl &folder);
943+ void setNameFilters(const QStringList &nameFilters);
944+ void setParentFolder(const QUrl &parentFolder);
945+ void setRootFolder(const QUrl &rootFolder);
946+ void setShowDirs(const bool showDirs);
947+ void setShowDirsFirst(const bool showDirsFirst);
948+ void setShowDotAndDotDot(const bool showDotAndDotDot);
949+ void setShowFiles(const bool showFiles);
950+ void setShowHidden(const bool showHidden);
951+ void setShowOnlyReadable(const bool showOnlyReadable);
952+ void setSortField(const Sort &sortField);
953+ void setSortReversed(const bool sortReversed);
954+
955+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
956+ QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const override;
957+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
958+ QHash<int, QByteArray> roleNames() const override;
959+
960+ Q_INVOKABLE void mockAddFile(const QString &fileName,
961+ const QString &filePath,
962+ const QString &baseName,
963+ const qint64 &size,
964+ const QString &suffix,
965+ const bool isDir,
966+ const bool isFile);
967+
968+Q_SIGNALS:
969+ void caseSensitiveChanged();
970+ void countChanged();
971+ void folderChanged();
972+ void nameFiltersChanged();
973+ void parentFolderChanged();
974+ void rootFolderChanged();
975+ void showDirsChanged();
976+ void showDirsFirstChanged();
977+ void showDotAndDotDotChanged();
978+ void showFilesChanged();
979+ void showHiddenChanged();
980+ void showOnlyReadableChanged();
981+ void sortFieldChanged();
982+ void sortReversedChanged();
983+
984+private:
985+ QUrl m_folder = QUrl();
986+ bool m_caseSensitive = false;
987+ int m_count = false;
988+ QStringList m_nameFilters = QStringList();
989+ QUrl m_parentFolder = QUrl();
990+ QUrl m_rootFolder = QUrl();
991+ bool m_showDirs = false;
992+ bool m_showDirsFirst = false;
993+ bool m_showDotAndDotDot = false;
994+ bool m_showFiles = false;
995+ bool m_showHidden = false;
996+ bool m_showOnlyReadable = false;
997+ Sort m_sortField = Sort::Unsorted;
998+ bool m_sortReversed = false;
999+
1000+ QList<MockFile> m_files;
1001+};
1002+
1003+#endif // MOCK_FOLDERLISTMODEL_H
1004
1005=== added file 'tests/mocks/Qt/labs/folderlistmodel/plugin.cpp'
1006--- tests/mocks/Qt/labs/folderlistmodel/plugin.cpp 1970-01-01 00:00:00 +0000
1007+++ tests/mocks/Qt/labs/folderlistmodel/plugin.cpp 2017-02-06 15:08:32 +0000
1008@@ -0,0 +1,26 @@
1009+/*
1010+ * Copyright (C) 2016 Canonical, Ltd.
1011+ *
1012+ * This program is free software; you can redistribute it and/or modify
1013+ * it under the terms of the GNU General Public License as published by
1014+ * the Free Software Foundation; version 3.
1015+ *
1016+ * This program is distributed in the hope that it will be useful,
1017+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1018+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1019+ * GNU General Public License for more details.
1020+ *
1021+ * You should have received a copy of the GNU General Public License
1022+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1023+ */
1024+
1025+#include "plugin.h"
1026+#include "MockFolderListModel.h"
1027+
1028+#include <QtQml/qqml.h>
1029+
1030+void MockQtLabsFolderListModelPlugin::registerTypes(const char *uri)
1031+{
1032+ Q_ASSERT(uri == QLatin1String("Qt.labs.folderlistmodel"));
1033+ qmlRegisterType<MockFolderListModel>(uri, 2, 1, "FolderListModel");
1034+}
1035
1036=== added file 'tests/mocks/Qt/labs/folderlistmodel/plugin.h'
1037--- tests/mocks/Qt/labs/folderlistmodel/plugin.h 1970-01-01 00:00:00 +0000
1038+++ tests/mocks/Qt/labs/folderlistmodel/plugin.h 2017-02-06 15:08:32 +0000
1039@@ -0,0 +1,31 @@
1040+/*
1041+ * Copyright (C) 2016 Canonical, Ltd.
1042+ *
1043+ * This program is free software; you can redistribute it and/or modify
1044+ * it under the terms of the GNU General Public License as published by
1045+ * the Free Software Foundation; version 3.
1046+ *
1047+ * This program is distributed in the hope that it will be useful,
1048+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1049+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1050+ * GNU General Public License for more details.
1051+ *
1052+ * You should have received a copy of the GNU General Public License
1053+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1054+ */
1055+
1056+#ifndef QTLABS_FOLDERLISTMODEL_PLUGIN_H
1057+#define QTLABS_FOLDERLISTMODEL_PLUGIN_H
1058+
1059+#include <QtQml/QQmlExtensionPlugin>
1060+
1061+class MockQtLabsFolderListModelPlugin : public QQmlExtensionPlugin
1062+{
1063+ Q_OBJECT
1064+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
1065+
1066+public:
1067+ void registerTypes(const char *uri) override;
1068+};
1069+
1070+#endif // QTLABS_FOLDERLISTMODEL_PLUGIN_H
1071
1072=== added file 'tests/mocks/Qt/labs/folderlistmodel/qmldir'
1073--- tests/mocks/Qt/labs/folderlistmodel/qmldir 1970-01-01 00:00:00 +0000
1074+++ tests/mocks/Qt/labs/folderlistmodel/qmldir 2017-02-06 15:08:32 +0000
1075@@ -0,0 +1,2 @@
1076+module Qt.labs.folderlistmodel
1077+plugin QtLabsFolderListModelQml

Subscribers

People subscribed via source and target branches