on xenial help sign in
Merged trunk changes.
commitb5bbc36d01f87cb59f301bb9b1c595a2454b9b4b authorPawel Stolowski <pawel.stolowski@canonical.com> dateThu 13 Jun 2013, 11:27 (13 years ago) parent04e6eb6fe7ed42
49 files changed+1929−183
Components/Stage.qml+2−1 Components/WrongPasswordAnimation.qml+46 Dash/Dash.qml+2 Dash/DashContent.qml+6−15 Dash/ScopeDelegateMapper.qml+34 Greeter/Lockscreen.qml+149 Greeter/LoginList.qml+3−23 Greeter/PassphraseLockscreen.qml+87 Greeter/PinLockscreen.qml+111 Greeter/PinPadButton.qml+59 Launcher/Launcher.qml+3−2 Shell.qml+60−24 plugins/LightDM/CMakeLists.txt+1−1 plugins/Ubuntu/Gestures/AxisVelocityCalculator.cpp+1−1 plugins/Ubuntu/Gestures/AxisVelocityCalculator.h+7−2 plugins/Ubuntu/Gestures/CMakeLists.txt+3 plugins/Ubuntu/Gestures/DirectionalDragArea.cpp+121 plugins/Ubuntu/Gestures/DirectionalDragArea.h+60 run+17−1 run_on_device+18−4 tests/autopilot/unity8/emulators/main_window.py+12 tests/autopilot/unity8/tests/testcases.py → tests/autopilot/unity8/tests/testhud.py+2−2 tests/autopilot/unity8/tests/testlockscreen.py+118 tests/mocks/LightDM/CMakeLists.txt+2 tests/mocks/LightDM/full/GreeterPrivate.cpp+7−1 tests/mocks/LightDM/full/UsersModelPrivate.cpp+1 tests/mocks/LightDM/single-passphrase/CMakeLists.txt+21 tests/mocks/LightDM/single-passphrase/GreeterPrivate.cpp+53 tests/mocks/LightDM/single-passphrase/InfographicModelPrivate.cpp+61 tests/mocks/LightDM/single-passphrase/UsersModelPrivate.cpp+33 tests/mocks/LightDM/single-pin/CMakeLists.txt+21 tests/mocks/LightDM/single-pin/GreeterPrivate.cpp+53 tests/mocks/LightDM/single-pin/InfographicModelPrivate.cpp+61 tests/mocks/LightDM/single-pin/UsersModelPrivate.cpp+33 tests/plugins/Ubuntu/Gestures/CMakeLists.txt+5−3 tests/plugins/Ubuntu/Gestures/RightwardsLauncher.qml+1 tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp+203−16 tests/qmltests/CMakeLists.txt+3 tests/qmltests/Dash/qml/FakeScopeView.qml+65−38 tests/qmltests/Dash/qml/fake_generic_scopeView.qml+1−3 tests/qmltests/Dash/qml/fake_scopeView1.qml+1−3 tests/qmltests/Dash/qml/fake_scopeView2.qml+1−3 tests/qmltests/Dash/qml/fake_scopeView3.qml+1−3 tests/qmltests/Dash/qml/fake_scopeView4.qml+1−3 tests/qmltests/Dash/tst_Dash.qml+133 tests/qmltests/Dash/tst_DashContent.qml+41−34 tests/qmltests/Dash/tst_GenericLensView.qml → tests/qmltests/Dash/tst_GenericScopeView.qml tests/qmltests/Dash/tst_LensView.qml → tests/qmltests/Dash/tst_ScopeView.qml tests/qmltests/Greeter/tst_Lockscreen.qml+205
Components/Stage.qml+2−1
@@ -441,7 +441,8 @@ Showable {
441441 direction: DirectionalDragArea.Leftwards
442442 maxDeviation: units.gu(1)
443443 wideningAngle: 20
444− distanceThreshold: units.gu(2)
444+ distanceThreshold: units.gu(3)
445+ minSpeed: units.gu(5)
445446
446447 onDistanceChanged: {
447448 if (status !== DirectionalDragArea.Recognized)
Components/WrongPasswordAnimation.qmlnew file+46
@@ -0,0 +1,46 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU 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 General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+
19+SequentialAnimation {
20+ id: root
21+
22+ property var target
23+
24+ NumberAnimation {
25+ target: root.target
26+ property: "anchors.horizontalCenterOffset"
27+ duration: 50
28+ easing.type: Easing.InQuad
29+ to: units.gu(2)
30+ }
31+ NumberAnimation {
32+ target: root.target
33+ property: "anchors.horizontalCenterOffset"
34+ duration: 100
35+ easing.type: Easing.InOutQuad
36+ to: -units.gu(2)
37+ }
38+ NumberAnimation {
39+ target: root.target
40+ easing.type: Easing.OutElastic
41+ properties: "anchors.horizontalCenterOffset"
42+ to: 0
43+ duration: 400
44+ easing.overshoot: units.gu(2)
45+ }
46+}
Dash/Dash.qml+2
@@ -59,6 +59,7 @@ Showable {
5959
6060 DashContent {
6161 id: dashContent
62+ objectName: "dashContent"
6263 anchors.fill: parent
6364 model: filteredScopes
6465 scopes: scopes
@@ -79,6 +80,7 @@ Showable {
7980
8081 DashBar {
8182 id: dashbar
83+ objectName: "dashbar"
8284 anchors.bottom: parent.bottom
8385 anchors.left: parent.left
8486 anchors.right: parent.right
Dash/DashContent.qml+6−15
@@ -28,6 +28,8 @@ Item {
2828 property real contentProgress: Math.max(0, Math.min(dashContentList.contentX / (dashContentList.contentWidth - dashContentList.width), units.dp(1)))
2929 property alias currentIndex: dashContentList.currentIndex
3030
31+ property ScopeDelegateMapper scopeMapper : ScopeDelegateMapper {}
32+
3133 signal movementStarted()
3234 signal movementEnded()
3335 signal contentFlickStarted()
@@ -79,13 +81,6 @@ Item {
7981 }
8082 }
8183
82− property var scopeDelegateMapping: {"mockmusicmaster.scope": "DashMusic.qml",
83− "applications.scope": "DashApps.qml",
84− "home.scope": "DashHome.qml",
85− "mockvideosmaster.scope": "DashVideos.qml",
86− }
87− property string genericScope: "GenericScopeView.qml"
88−
8984 ListView {
9085 id: dashContentList
9186 objectName: "dashContentList"
@@ -120,14 +115,8 @@ Item {
120115 width: ListView.view.width
121116 height: ListView.view.height
122117 asynchronous: true
123− source: {
124− var customScope = scopeDelegateMapping[scope.id]
125− if (customScope) {
126− return customScope
127− } else {
128− return genericScope
129− }
130− }
118+ source: scopeMapper.map(scope.id)
119+
131120 onLoaded: {
132121 item.scope = Qt.binding(function() { return scope })
133122 item.isCurrent = Qt.binding(function() { return ListView.isCurrentItem })
@@ -149,6 +138,8 @@ Item {
149138 }
150139 }
151140 }
141+
142+ Component.onDestruction: active = false
152143 }
153144 }
154145}
Dash/ScopeDelegateMapper.qmlnew file+34
@@ -0,0 +1,34 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU 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 General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+
19+QtObject {
20+ property var scopeDelegateMapping: {"mockmusicmaster.scope": "DashMusic.qml",
21+ "applications.scope": "DashApps.qml",
22+ "home.scope": "DashHome.qml",
23+ "mockvideosmaster.scope": "DashVideos.qml"
24+ }
25+ property string genericScope: "GenericScopeView.qml"
26+
27+ function map(scopeId) {
28+ var customScope = scopeDelegateMapping[scopeId]
29+ if (customScope != undefined) {
30+ return customScope
31+ }
32+ return genericScope
33+ }
34+}
Greeter/Lockscreen.qmlnew file+149
@@ -0,0 +1,149 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU 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 General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import Ubuntu.Components 0.1
19+import "../Components"
20+import LightDM 0.1 as LightDM
21+
22+Showable {
23+ id: root
24+
25+ // Determine if a numeric or alphanumeric pad is used.
26+ property bool alphaNumeric: false
27+
28+ // Placeholder text
29+ property string placeholderText: ""
30+
31+ // In case the Lockscreen can show a greeter message, this is the username
32+ property string username: ""
33+
34+ // As the numeric PIN pad doesn't have a OK button by design, we need to auto-confirm on a certain length.
35+ // This is ignored by the alphaNumeric lockscreen as that one is confirmed with pressing enter on the OSK.
36+ property int pinLength: 4
37+
38+ property url background: ""
39+
40+ signal unlocked()
41+ signal cancel()
42+ signal emergencyCall()
43+
44+ function reset() {
45+ pinPadLoader.resetting = true;
46+ pinPadLoader.resetting = false;
47+ }
48+
49+ Image {
50+ anchors.fill: parent
51+ source: root.required ? root.background : ""
52+ }
53+
54+ MouseArea {
55+ anchors.fill: root
56+ }
57+
58+ Loader {
59+ id: pinPadLoader
60+ objectName: "pinPadLoader"
61+ anchors {
62+ left: parent.left
63+ right: parent.right
64+ verticalCenter: parent.verticalCenter
65+ verticalCenterOffset: root.alphaNumeric ? -units.gu(10) : 0
66+ }
67+ property bool resetting: false
68+
69+ source: (!resetting && root.required) ? (root.alphaNumeric ? "PassphraseLockscreen.qml" : "PinLockscreen.qml") : ""
70+
71+ Connections {
72+ target: pinPadLoader.item
73+
74+ onEntered: {
75+ LightDM.Greeter.respond(passphrase);
76+ }
77+
78+ onCancel: {
79+ root.cancel()
80+ }
81+ }
82+
83+ Binding {
84+ target: pinPadLoader.item
85+ property: "pinLength"
86+ value: root.pinLength
87+ }
88+ Binding {
89+ target: pinPadLoader.item
90+ property: "placeholderText"
91+ value: root.placeholderText
92+ }
93+ Binding {
94+ target: pinPadLoader.item
95+ property: "username"
96+ value: root.username
97+ }
98+ }
99+
100+ Column {
101+ anchors {
102+ left: parent.left
103+ top: pinPadLoader.bottom
104+ topMargin: units.gu(4)
105+ right: parent.right
106+ bottom: parent.bottom
107+ }
108+ spacing: units.gu(1)
109+
110+ Icon {
111+ objectName: "emergencyCallIcon"
112+ height: units.gu(3)
113+ width: height
114+ anchors.horizontalCenter: parent.horizontalCenter
115+ name: "phone-app-call-symbolic"
116+ color: "white"
117+ opacity: 0.9
118+
119+ MouseArea {
120+ anchors.fill: parent
121+ onClicked: root.emergencyCall()
122+ }
123+ }
124+
125+ Label {
126+ text: i18n.tr("Emergency call")
127+ color: "white"
128+ opacity: 0.9
129+ fontSize: "medium"
130+ anchors.horizontalCenter: parent.horizontalCenter
131+ }
132+ }
133+
134+ Connections {
135+ target: LightDM.Greeter
136+
137+ onAuthenticationComplete: {
138+ if (LightDM.Greeter.promptless) {
139+ return;
140+ }
141+ if (LightDM.Greeter.authenticated) {
142+ root.unlocked();
143+ pinPadLoader.item.clear(false);
144+ } else {
145+ pinPadLoader.item.clear(true);
146+ }
147+ }
148+ }
149+}
Greeter/LoginList.qml+3−23
@@ -17,6 +17,7 @@
1717import QtQuick 2.0
1818import Ubuntu.Components 0.1
1919import LightDM 0.1 as LightDM
20+import "../Components"
2021
2122Item {
2223 id: root
@@ -218,30 +219,9 @@ Item {
218219 source: "graphics/icon_arrow.png"
219220 }
220221
221− SequentialAnimation {
222+ WrongPasswordAnimation {
222223 id: wrongPasswordAnimation
223− NumberAnimation {
224− target: passwordInput
225− property: "anchors.horizontalCenterOffset"
226− duration: 50
227− easing.type: Easing.InQuad
228− to: units.gu(2)
229− }
230− NumberAnimation {
231− target: passwordInput
232− property: "anchors.horizontalCenterOffset"
233− duration: 100
234− easing.type: Easing.InOutQuad
235− to: -units.gu(2)
236− }
237− NumberAnimation {
238− target: passwordInput
239− easing.type: Easing.OutElastic
240− properties: "anchors.horizontalCenterOffset"
241− to: 0
242− duration: 400
243− easing.overshoot: units.gu(2)
244− }
224+ target: passwordInput
245225 }
246226
247227 Connections {
Greeter/PassphraseLockscreen.qmlnew file+87
@@ -0,0 +1,87 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU 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 General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import Ubuntu.Components 0.1
19+import "../Components"
20+
21+Item {
22+ id: root
23+ height: highlightItem.height
24+
25+ property alias placeholderText: pinentryField.placeholderText
26+ property string username: ""
27+
28+ signal entered(string passphrase)
29+ signal cancel()
30+
31+ function clear(playAnimation) {
32+ pinentryField.text = "";
33+ if (playAnimation) {
34+ wrongPasswordAnimation.start();
35+ } else {
36+ pinentryField.focus = false
37+ }
38+ }
39+
40+ Rectangle {
41+ id: highlightItem
42+ width: units.gu(32)
43+ height: units.gu(10)
44+ anchors.centerIn: parent
45+ color: Qt.rgba(0.1, 0.1, 0.1, 0.4)
46+ border.color: Qt.rgba(0.4, 0.4, 0.4, 0.4)
47+ border.width: units.dp(1)
48+ radius: units.gu(1.5)
49+ antialiasing: true
50+
51+ Label {
52+ objectName: "greeterLabel"
53+ anchors {
54+ left: parent.left
55+ top: parent.top
56+ right: parent.right
57+ margins: units.gu(1.5)
58+ }
59+ text: root.username.length > 0 ? i18n.tr("Hello %1").arg(root.username) : i18n.tr("Hello")
60+ color: "white"
61+ }
62+
63+ TextField {
64+ id: pinentryField
65+ objectName: "pinentryField"
66+ anchors {
67+ horizontalCenter: parent.horizontalCenter
68+ bottom: parent.bottom
69+ margins: units.gu(1)
70+ }
71+ height: units.gu(4.5)
72+ width: parent.width - units.gu(2)
73+ echoMode: TextInput.Password
74+ opacity: 0.9
75+ hasClearButton: false
76+
77+ onAccepted: {
78+ root.entered(pinentryField.text);
79+ }
80+ }
81+ }
82+
83+ WrongPasswordAnimation {
84+ id: wrongPasswordAnimation
85+ target: pinentryField
86+ }
87+}
Greeter/PinLockscreen.qmlnew file+111
@@ -0,0 +1,111 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU 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 General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import Ubuntu.Components 0.1
19+import "../Components"
20+
21+Column {
22+ id: root
23+ anchors.centerIn: parent
24+ spacing: units.gu(2.5)
25+
26+ property alias placeholderText: pinentryField.placeholderText
27+ property int padWidth: units.gu(32)
28+ property int pinLength: 4
29+
30+ signal entered(string passphrase)
31+ signal cancel()
32+
33+ function clear(playAnimation) {
34+ pinentryField.text = "";
35+ if (playAnimation) {
36+ wrongPasswordAnimation.start();
37+ }
38+ }
39+
40+ TextField {
41+ id: pinentryField
42+ objectName: "pinentryField"
43+ anchors.horizontalCenter: parent.horizontalCenter
44+ width: units.gu(32)
45+ height: units.gu(5)
46+ echoMode: TextInput.Password
47+ font.pixelSize: units.dp(44)
48+ color: "white"
49+ opacity: 0.9
50+ hasClearButton: false
51+ horizontalAlignment: Text.AlignHCenter
52+
53+ onTextChanged: {
54+ if (pinentryField.text.length === root.pinLength) {
55+ root.entered(pinentryField.text);
56+ }
57+ }
58+
59+ // Using a MouseArea to eat clicks. We don't want to disable the TextField for styling reasons
60+ MouseArea {
61+ anchors.fill: parent
62+ }
63+ }
64+
65+ Grid {
66+ anchors {
67+ left: parent.left
68+ right: parent.right
69+ margins: (parent.width - root.padWidth) / 2
70+ }
71+
72+ columns: 3
73+ spacing: units.gu(1)
74+
75+ Repeater {
76+ model: 9
77+
78+ PinPadButton {
79+ objectName: "pinPadButton" + (index + 1)
80+ text: index + 1
81+
82+ onClicked: {
83+ pinentryField.text = pinentryField.text + text
84+ }
85+ }
86+ }
87+
88+ PinPadButton {
89+ objectName: "pinPadButtonBack"
90+ iconName: "back"
91+ onClicked: root.cancel();
92+
93+ }
94+ PinPadButton {
95+ objectName: "pinPadButton0"
96+ text: "0"
97+ onClicked: pinentryField.text = pinentryField.text + text
98+
99+ }
100+ PinPadButton {
101+ objectName: "pinPadButtonErase"
102+ iconName: "erase"
103+ onClicked: pinentryField.text = pinentryField.text.substring(0, pinentryField.text.length-1)
104+ }
105+ }
106+
107+ WrongPasswordAnimation {
108+ id: wrongPasswordAnimation
109+ target: pinentryField
110+ }
111+}
Greeter/PinPadButton.qmlnew file+59
@@ -0,0 +1,59 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU 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 General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import Ubuntu.Components 0.1
19+
20+UbuntuShape {
21+ id: root
22+ width: units.gu(10)
23+ height: units.gu(7)
24+ radius: "medium"
25+
26+ property alias text: label.text
27+ property alias iconName: icon.name
28+
29+ signal clicked()
30+
31+ Behavior on color {
32+ ColorAnimation { duration: 100 }
33+ }
34+
35+ Label {
36+ id: label
37+ anchors.centerIn: parent
38+ color: "white"
39+ fontSize: "x-large"
40+ font.weight: Font.Light
41+ opacity: 0.9
42+ }
43+
44+ Icon {
45+ id: icon
46+ height: units.gu(3)
47+ width: height
48+ anchors.centerIn: parent
49+ color: "white"
50+ opacity: 0.9
51+ }
52+
53+ MouseArea {
54+ anchors.fill: parent
55+ onClicked: root.clicked()
56+ onPressed: root.color = Qt.rgba(0, 0, 0, 0.3)
57+ onReleased: root.color = Qt.rgba(0, 0, 0, 0)
58+ }
59+}
Launcher/Launcher.qml+3−2
@@ -188,9 +188,10 @@ Item {
188188
189189 // values to be tweaked and later removed from here and set in stone as defaults
190190 // once we are confident it's all good.
191− maxDeviation: units.gu(1.5)
191+ maxDeviation: units.gu(1)
192192 wideningAngle: 30
193− distanceThreshold: units.gu(4)
193+ distanceThreshold: units.gu(3)
194+ minSpeed: units.gu(5)
194195
195196 enabled: root.available && root.state !== "visible"
196197 width: root.dragAreaWidth
Shell.qml+60−24
@@ -67,7 +67,7 @@ FocusScope {
6767 }
6868
6969 readonly property bool fullscreenMode: {
70− if (greeter.shown) {
70+ if (greeter.shown || lockscreen.shown) {
7171 return false;
7272 } else if (mainStage.usingScreenshots) { // Window Manager animating so want to re-evaluate fullscreen mode
7373 return mainStage.switchingFromFullscreenToFullscreen;
@@ -124,7 +124,6 @@ FocusScope {
124124
125125 Item {
126126 id: underlay
127−
128127 anchors.fill: parent
129128 visible: !(panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth)
130129 && (stages.fullyHidden
@@ -148,7 +147,7 @@ FocusScope {
148147 Dash {
149148 id: dash
150149
151− available: !greeter.shown
150+ available: !greeter.shown && !lockscreen.shown
152151 hides: [stages, launcher, panel.indicators]
153152 shown: disappearingAnimationProgress !== 1.0
154153 enabled: disappearingAnimationProgress === 0.0
@@ -219,26 +218,18 @@ FocusScope {
219218 Connections {
220219 target: shell.applicationManager
221220 onMainStageFocusedApplicationChanged: {
222− handleFocusedApplicationChange(mainStage, shell.applicationManager.mainStageFocusedApplication);
221+ if (shell.applicationManager.mainStageFocusedApplication) {
222+ mainStage.show();
223+ stages.show();
224+ }
223225 }
224226 onSideStageFocusedApplicationChanged: {
225− handleFocusedApplicationChange(sideStage, shell.applicationManager.sideStageFocusedApplication);
226− }
227− ignoreUnknownSignals: true
228−
229− function handleFocusedApplicationChange(stage, application) {
230− if (stages.shown) {
231− if (application) {
232− stage.show();
233− stages.show();
234− }
235− } else {
236− // focus changed while shell in foreground, ensure app remains unfocused
237− if (application) {
238− shell.applicationManager.unfocusCurrentApplication();
239− }
227+ if (shell.applicationManager.sideStageFocusedApplication) {
228+ sideStage.show();
229+ stages.show();
240230 }
241231 }
232+ ignoreUnknownSignals: true
242233 }
243234
244235
@@ -301,7 +292,7 @@ FocusScope {
301292 && sideStage[sideStageRevealer.boundProperty] == sideStageRevealer.openedValue
302293 shouldUseScreenshots: !fullyShown || mainStage.usingScreenshots || sideStageRevealer.pressed
303294
304− available: !greeter.shown && enabled
295+ available: !greeter.shown && !lockscreen.shown && enabled
305296 hides: [launcher, panel.indicators]
306297 shown: false
307298 showAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.openedValue; easing.type: Easing.OutQuint }
@@ -352,6 +343,46 @@ FocusScope {
352343 orientation: Qt.Horizontal
353344 }
354345
346+ Lockscreen {
347+ id: lockscreen
348+ hides: [launcher, panel.indicators, hud]
349+ shown: false
350+ enabled: true
351+ showAnimation: StandardAnimation { property: "opacity"; to: 1 }
352+ hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
353+ y: panel.panelHeight
354+ x: required ? 0 : - width
355+ width: parent.width
356+ height: parent.height - panel.panelHeight
357+ background: shell.background
358+
359+ onUnlocked: lockscreen.hide()
360+ onCancel: greeter.show()
361+
362+ Component.onCompleted: {
363+ if (LightDM.Users.count == 1) {
364+ LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
365+ }
366+ }
367+ }
368+
369+ Connections {
370+ target: LightDM.Greeter
371+
372+ onShowPrompt: {
373+ if (LightDM.Users.count == 1) {
374+ // TODO: There's no better way for now to determine if its a PIN or a passphrase.
375+ if (text == "PIN") {
376+ lockscreen.alphaNumeric = false
377+ } else {
378+ lockscreen.alphaNumeric = true
379+ }
380+ lockscreen.placeholderText = i18n.tr("Please enter %1:").arg(text);
381+ lockscreen.show();
382+ }
383+ }
384+ }
385+
355386 Greeter {
356387 id: greeter
357388
@@ -360,7 +391,6 @@ FocusScope {
360391 shown: true
361392 showAnimation: StandardAnimation { property: "x"; to: greeterRevealer.openedValue }
362393 hideAnimation: StandardAnimation { property: "x"; to: greeterRevealer.closedValue }
363−
364394 y: panel.panelHeight
365395 width: parent.width
366396 height: parent.height - panel.panelHeight
@@ -370,6 +400,12 @@ FocusScope {
370400
371401 onShownChanged: {
372402 if (shown) {
403+ lockscreen.reset();
404+ // If there is only one user, we start authenticating with that one here.
405+ // If there are more users, the Greeter will handle that
406+ if (LightDM.Users.count == 1) {
407+ LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
408+ }
373409 greeter.forceActiveFocus();
374410 // FIXME: *FocusedApplication are not updated when unfocused, hence the need to check whether
375411 // the stage was actually shown
@@ -397,7 +433,7 @@ FocusScope {
397433
398434 InputFilterArea {
399435 anchors.fill: parent
400− blockInput: greeter.shown
436+ blockInput: greeter.shown || lockscreen.shown
401437 }
402438
403439 Revealer {
@@ -426,7 +462,7 @@ FocusScope {
426462 hides: [launcher]
427463 }
428464 fullscreenMode: shell.fullscreenMode
429− searchVisible: !greeter.shown
465+ searchVisible: !greeter.shown && !lockscreen.shown
430466
431467 InputFilterArea {
432468 anchors.fill: parent
@@ -440,7 +476,7 @@ FocusScope {
440476 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
441477 height: parent.height
442478
443− available: !greeter.shown && !panel.indicators.shown
479+ available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown
444480 shown: false
445481 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
446482 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
plugins/LightDM/CMakeLists.txt+1−1
@@ -40,4 +40,4 @@ install(TARGETS LightDM-qml
4040
4141install(FILES qmldir
4242 DESTINATION ${SHELL_APP_DIR}/plugins/LightDM
43− )
43+ )
plugins/Ubuntu/Gestures/AxisVelocityCalculator.cpp+1−1
@@ -85,7 +85,7 @@ void AxisVelocityCalculator::processMovement(qreal movement)
8585
8686qreal AxisVelocityCalculator::calculate() const
8787{
88− if (numSamples() < 2) {
88+ if (numSamples() < MIN_SAMPLES_NEEDED) {
8989 return 0.0;
9090 }
9191
plugins/Ubuntu/Gestures/AxisVelocityCalculator.h+7−2
@@ -95,6 +95,13 @@ public:
9595 */
9696 Q_INVOKABLE void reset();
9797
98+ int numSamples() const;
99+
100+ /*
101+ The minimum amount of samples needed for a velocity calculation.
102+ */
103+ static const int MIN_SAMPLES_NEEDED = 2;
104+
98105 /*
99106 Maximum number of movement samples stored
100107 */
@@ -115,8 +122,6 @@ private:
115122 */
116123 void processMovement(qreal movement);
117124
118− int numSamples() const;
119−
120125 class Sample
121126 {
122127 public:
plugins/Ubuntu/Gestures/CMakeLists.txt+3
@@ -16,6 +16,9 @@ add_library(UbuntuGestureQml MODULE ${UbuntuGestureQml_SOURCES})
1616
1717qt5_use_modules(UbuntuGestureQml Core Quick)
1818
19+# So that Foo.cpp can #include "Foo.moc"
20+include_directories(${CMAKE_CURRENT_BINARY_DIR})
21+
1922# copy files into build directory for shadow builds
2023add_custom_target(UbuntuGestureQmlDirFile ALL
2124 COMMAND cp "${CMAKE_CURRENT_SOURCE_DIR}/qmldir" ${CMAKE_CURRENT_BINARY_DIR}
plugins/Ubuntu/Gestures/DirectionalDragArea.cpp+121
@@ -17,6 +17,25 @@
1717#include "DirectionalDragArea.h"
1818
1919#include <QtCore/qmath.h>
20+#include <QtCore/QTimer>
21+
22+// Essentially a QTimer wrapper
23+class RecognitionTimer : public UbuntuGestures::AbstractTimer
24+{
25+ Q_OBJECT
26+public:
27+ RecognitionTimer(QObject *parent) : UbuntuGestures::AbstractTimer(parent) {
28+ m_timer.setSingleShot(false);
29+ connect(&m_timer, &QTimer::timeout,
30+ this, &UbuntuGestures::AbstractTimer::timeout);
31+ }
32+ virtual int interval() const { return m_timer.interval(); }
33+ virtual void setInterval(int msecs) { m_timer.setInterval(msecs); }
34+ virtual void start() { m_timer.start(); UbuntuGestures::AbstractTimer::start(); }
35+ virtual void stop() { m_timer.stop(); UbuntuGestures::AbstractTimer::stop(); }
36+private:
37+ QTimer m_timer;
38+};
2039
2140DirectionalDragArea::DirectionalDragArea(QQuickItem *parent)
2241 : QQuickItem(parent)
@@ -26,7 +45,17 @@ DirectionalDragArea::DirectionalDragArea(QQuickItem *parent)
2645 , m_wideningAngle(0)
2746 , m_wideningFactor(0)
2847 , m_distanceThreshold(0)
48+ , m_minSpeed(0)
49+ , m_maxSilenceTime(200)
50+ , m_silenceTime(0)
51+ , m_numSamplesOnLastSpeedCheck(0)
52+ , m_recognitionTimer(0)
53+ , m_velocityCalculator(0)
2954{
55+ setRecognitionTimer(new RecognitionTimer(this));
56+ m_recognitionTimer->setInterval(60);
57+
58+ m_velocityCalculator = new AxisVelocityCalculator(this);
3059}
3160
3261DirectionalDragArea::Direction DirectionalDragArea::direction() const
@@ -73,6 +102,53 @@ void DirectionalDragArea::setDistanceThreshold(qreal value)
73102 }
74103}
75104
105+void DirectionalDragArea::setMinSpeed(qreal value)
106+{
107+ if (m_minSpeed != value) {
108+ m_minSpeed = value;
109+ Q_EMIT minSpeedChanged(value);
110+ }
111+}
112+
113+void DirectionalDragArea::setMaxSilenceTime(int value)
114+{
115+ if (m_maxSilenceTime != value) {
116+ m_maxSilenceTime = value;
117+ Q_EMIT maxSilenceTimeChanged(value);
118+ }
119+}
120+
121+void DirectionalDragArea::setRecognitionTimer(UbuntuGestures::AbstractTimer *timer)
122+{
123+ int interval = 0;
124+ bool timerWasRunning = false;
125+
126+ // can be null when called from the constructor
127+ if (m_recognitionTimer) {
128+ interval = m_recognitionTimer->interval();
129+ timerWasRunning = m_recognitionTimer->isRunning();
130+ if (m_recognitionTimer->parent() == this) {
131+ delete m_recognitionTimer;
132+ }
133+ }
134+
135+ m_recognitionTimer = timer;
136+ timer->setInterval(interval);
137+ connect(timer, &UbuntuGestures::AbstractTimer::timeout,
138+ this, &DirectionalDragArea::checkSpeed);
139+ if (timerWasRunning) {
140+ m_recognitionTimer->start();
141+ }
142+}
143+
144+void DirectionalDragArea::setAxisVelocityCalculator(AxisVelocityCalculator *newVelCalc)
145+{
146+ if (m_velocityCalculator->parent() == this)
147+ delete m_velocityCalculator;
148+
149+ m_velocityCalculator = newVelCalc;
150+}
151+
76152qreal DirectionalDragArea::distance() const
77153{
78154 if (directionIsHorizontal()) {
@@ -122,6 +198,10 @@ void DirectionalDragArea::touchEvent_absent(QTouchEvent *event)
122198 m_startPos = event->touchPoints()[0].pos();
123199 m_touchId = event->touchPoints()[0].id();
124200 m_dampedPos.reset(m_startPos);
201+ updateVelocityCalculator(m_startPos);
202+ m_velocityCalculator->reset();
203+ m_numSamplesOnLastSpeedCheck = 0;
204+ m_silenceTime = 0;
125205 setPreviousPos(m_startPos);
126206 setStatus(Undecided);
127207 }
@@ -148,6 +228,7 @@ void DirectionalDragArea::touchEvent_undecided(QTouchEvent *event)
148228 m_previousDampedPos.setX(m_dampedPos.x());
149229 m_previousDampedPos.setY(m_dampedPos.y());
150230 m_dampedPos.update(touchPos);
231+ updateVelocityCalculator(touchPos);
151232
152233 if (!pointInsideAllowedArea()) {
153234 setStatus(Rejected);
@@ -238,6 +319,29 @@ bool DirectionalDragArea::movedFarEnough(const QPointF &point) const
238319 return qFabs(point.y() - m_startPos.y()) > m_distanceThreshold;
239320}
240321
322+void DirectionalDragArea::checkSpeed()
323+{
324+ if (m_velocityCalculator->numSamples() >= AxisVelocityCalculator::MIN_SAMPLES_NEEDED) {
325+ qreal speed = qFabs(m_velocityCalculator->calculate());
326+ qreal minSpeedMsecs = m_minSpeed / 1000.0;
327+
328+ if (speed < minSpeedMsecs) {
329+ setStatus(Rejected);
330+ }
331+ }
332+
333+ if (m_velocityCalculator->numSamples() == m_numSamplesOnLastSpeedCheck) {
334+ m_silenceTime += m_recognitionTimer->interval();
335+
336+ if (m_silenceTime > m_maxSilenceTime) {
337+ setStatus(Rejected);
338+ }
339+ } else {
340+ m_silenceTime = 0;
341+ }
342+ m_numSamplesOnLastSpeedCheck = m_velocityCalculator->numSamples();
343+}
344+
241345void DirectionalDragArea::setStatus(DirectionalDragArea::Status newStatus)
242346{
243347 if (newStatus == m_status)
@@ -245,6 +349,10 @@ void DirectionalDragArea::setStatus(DirectionalDragArea::Status newStatus)
245349
246350 DirectionalDragArea::Status oldStatus = m_status;
247351
352+ if (oldStatus == Undecided) {
353+ m_recognitionTimer->stop();
354+ }
355+
248356 m_status = newStatus;
249357 Q_EMIT statusChanged(m_status);
250358
@@ -255,6 +363,7 @@ void DirectionalDragArea::setStatus(DirectionalDragArea::Status newStatus)
255363 }
256364 break;
257365 case Undecided:
366+ m_recognitionTimer->start();
258367 Q_EMIT draggingChanged(true);
259368 break;
260369 case Rejected:
@@ -288,6 +397,15 @@ void DirectionalDragArea::setPreviousPos(QPointF point)
288397 }
289398}
290399
400+void DirectionalDragArea::updateVelocityCalculator(QPointF point)
401+{
402+ if (directionIsHorizontal()) {
403+ m_velocityCalculator->setTrackedPosition(point.x());
404+ } else {
405+ m_velocityCalculator->setTrackedPosition(point.y());
406+ }
407+}
408+
291409bool DirectionalDragArea::directionIsHorizontal() const
292410{
293411 return m_direction == Leftwards || m_direction == Rightwards;
@@ -297,3 +415,6 @@ bool DirectionalDragArea::directionIsVertical() const
297415{
298416 return m_direction == Upwards || m_direction == Downwards;
299417}
418+
419+// Because we are defining a new QObject-based class (RecognitionTimer) here.
420+#include "DirectionalDragArea.moc"
plugins/Ubuntu/Gestures/DirectionalDragArea.h+60
@@ -18,9 +18,28 @@
1818#define DIRECTIONAL_DRAG_AREA_H
1919
2020#include <QtQuick/QQuickItem>
21+#include "AxisVelocityCalculator.h"
2122#include "UbuntuGesturesGlobal.h"
2223#include "Damper.h"
2324
25+namespace UbuntuGestures {
26+/* Defines an interface for a Timer. */
27+class UBUNTUGESTURES_EXPORT AbstractTimer : public QObject {
28+ Q_OBJECT
29+public:
30+ AbstractTimer(QObject *parent) : QObject(parent), m_isRunning(false) {}
31+ virtual int interval() const = 0;
32+ virtual void setInterval(int msecs) = 0;
33+ virtual void start() { m_isRunning = true; };
34+ virtual void stop() { m_isRunning = false; }
35+ bool isRunning() const { return m_isRunning; }
36+Q_SIGNALS:
37+ void timeout();
38+private:
39+ bool m_isRunning;
40+};
41+}
42+
2443/*
2544 An area that detects axis-aligned single-finger drag gestures
2645
@@ -74,6 +93,21 @@ class UBUNTUGESTURES_EXPORT DirectionalDragArea : public QQuickItem {
7493 Q_PROPERTY(qreal distanceThreshold READ distanceThreshold WRITE setDistanceThreshold
7594 NOTIFY distanceThresholdChanged)
7695
96+ // Minimum speed a gesture needs to have in order to be recognized as a
97+ // directional drag.
98+ // In pixels per second
99+ Q_PROPERTY(qreal minSpeed READ minSpeed WRITE setMinSpeed NOTIFY minSpeedChanged)
100+
101+ // A gesture will be rejected if more than maxSilenceTime milliseconds has
102+ // passed since we last got an input event from it (during Undecided state).
103+ //
104+ // Silence (i.e., lack of new input events) doesn't necessarily mean that the user's
105+ // finger is still (zero drag speed). In some cases the finger might be moving but
106+ // the driver's high noise filtering might cause those silence periods, specially
107+ // in the moments succeeding a press (talking about Galaxy Nexus here).
108+ Q_PROPERTY(int maxSilenceTime READ maxSilenceTime
109+ WRITE setMaxSilenceTime
110+ NOTIFY maxSilenceTimeChanged)
77111 //
78112 /////
79113
@@ -130,6 +164,20 @@ public:
130164 qreal distanceThreshold() const { return m_distanceThreshold; }
131165 void setDistanceThreshold(qreal value);
132166
167+ qreal minSpeed() const { return m_minSpeed; }
168+ void setMinSpeed(qreal value);
169+
170+ int maxSilenceTime() const { return m_maxSilenceTime; }
171+ void setMaxSilenceTime(int value);
172+
173+ // Replaces the existing Timer with the given one.
174+ //
175+ // Useful for providing a fake timer when testing.
176+ void setRecognitionTimer(UbuntuGestures::AbstractTimer *timer);
177+
178+ // Useful for testing, where a calculator with a fake time source can be supplied
179+ void setAxisVelocityCalculator(AxisVelocityCalculator *velCalc);
180+
133181Q_SIGNALS:
134182 void directionChanged(Direction direction);
135183 void statusChanged(Status value);
@@ -138,12 +186,17 @@ Q_SIGNALS:
138186 void maxDeviationChanged(qreal value);
139187 void wideningAngleChanged(qreal value);
140188 void distanceThresholdChanged(qreal value);
189+ void minSpeedChanged(qreal value);
190+ void maxSilenceTimeChanged(int value);
141191 void touchXChanged(qreal value);
142192 void touchYChanged(qreal value);
143193
144194protected:
145195 virtual void touchEvent(QTouchEvent *event);
146196
197+private Q_SLOTS:
198+ void checkSpeed();
199+
147200private:
148201 void touchEvent_absent(QTouchEvent *event);
149202 void touchEvent_undecided(QTouchEvent *event);
@@ -155,6 +208,7 @@ private:
155208 const QTouchEvent::TouchPoint *fetchTargetTouchPoint(QTouchEvent *event);
156209 void setStatus(Status newStatus);
157210 void setPreviousPos(QPointF point);
211+ void updateVelocityCalculator(QPointF point);
158212
159213 // convenience functions
160214 bool directionIsHorizontal() const;
@@ -175,6 +229,12 @@ private:
175229 qreal m_wideningAngle; // in degrees
176230 qreal m_wideningFactor; // it's tan(degreesToRadian(m_wideningAngle))
177231 qreal m_distanceThreshold;
232+ qreal m_minSpeed;
233+ int m_maxSilenceTime; // in milliseconds
234+ int m_silenceTime; // in milliseconds
235+ int m_numSamplesOnLastSpeedCheck;
236+ UbuntuGestures::AbstractTimer *m_recognitionTimer;
237+ AxisVelocityCalculator *m_velocityCalculator;
178238};
179239
180240#endif // DIRECTIONAL_DRAG_AREA_H
run+17−1
@@ -4,6 +4,8 @@ QML_PHONE_SHELL_PATH=./builddir/unity8
44export LD_LIBRARY_PATH=$PWD/../unity_build/build/lib
55GDB=false
66FAKE=false
7+PINLOCK=false
8+KEYLOCK=false
79MOUSE_TOUCH=true
810
911usage() {
@@ -11,6 +13,8 @@ usage() {
1113 echo "Script to run the shell.\n" >&2
1214 echo "OPTIONS:" >&2
1315 echo " -f, --fake Force use of fake Qml modules." >&2
16+ echo " -p, --pinlock Use a pin protected user." >&2
17+ echo " -k, --keylock Use a passphrase protected user." >&2
1418 echo " -g, --gdb Run through gdb." >&2
1519 echo " -h, --help Show this help." >&2
1620 echo " -m, --nomousetouch Run without -mousetouch argument." >&2
@@ -18,7 +22,7 @@ usage() {
1822 exit 1
1923}
2024
21−ARGS=`getopt -n$0 -u -a --longoptions="fake,gdb,help,nomousetouch" -o "fghm" -- "$@"`
25+ARGS=`getopt -n$0 -u -a --longoptions="fake,pinlock,keylock,gdb,help,nomousetouch" -o "fpkghm" -- "$@"`
2226[ $? -ne 0 ] && usage
2327eval set -- "$ARGS"
2428
@@ -26,6 +30,8 @@ while [ $# -gt 0 ]
2630do
2731 case "$1" in
2832 -f|--fake) FAKE=true;;
33+ -p|--pinlock) PINLOCK=true;;
34+ -k|--keylock) KEYLOCK=true;;
2935 -g|--gdb) GDB=true;;
3036 -h|--help) usage;;
3137 -m|--nomousetouch) MOUSE_TOUCH=false;;
@@ -39,6 +45,16 @@ if $FAKE; then
3945 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/builddir/tests/mocks/LightDM/single
4046fi
4147
48+if $PINLOCK; then
49+ export QML2_IMPORT_PATH=$PWD/builddir/tests/mocks:$PWD/builddir/plugins
50+ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/builddir/tests/mocks/LightDM/single-pin
51+fi
52+
53+if $KEYLOCK; then
54+ export QML2_IMPORT_PATH=$PWD/builddir/tests/mocks:$PWD/builddir/plugins
55+ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/builddir/tests/mocks/LightDM/single-passphrase
56+fi
57+
4258# Force icon theme if running on the desktop, otherwise gnome theme (if running
4359# on Ubuntu Desktop) will be used and icons won't be found
4460if [ -n "$DESKTOP_SESSION" ]; then
run_on_device+18−4
@@ -11,6 +11,8 @@ TARGET_DEBUG_PORT=3768
1111RUN_OPTIONS=-qmljsdebugger=port:$TARGET_DEBUG_PORT
1212SETUP=false
1313GDB=false
14+PINLOCK=false
15+KEYLOCK=false
1416SUDO="echo $PASSWORD | sudo -S"
1517NUM_JOBS='$(( `grep -c ^processor /proc/cpuinfo` + 1 ))'
1618
@@ -19,6 +21,8 @@ usage() {
1921 echo "Script to setup a build environment for the shell and sync build and run it on the device\n"
2022 echo "OPTIONS:"
2123 echo " -s, --setup Setup the build environment"
24+ echo " -p, --pinlock Enable a PIN lock screen when running"
25+ echo " -k, --keylock Enable a Keyboard lock screen when running"
2226 echo ""
2327 echo "IMPORTANT:"
2428 echo " * Make sure to have the networking and PPAs setup on the device beforehand (phablet-deploy-networking && phablet-ppa-fetch)."
@@ -86,11 +90,19 @@ run() {
8690 fi
8791 adb shell pkill qml-phone-shell
8892 adb shell pkill $BINARY
93+ ARGS="--nomousetouch"
8994 if $GDB; then
90− exec_with_ssh "cd $CODE_DIR/ && ./run --gdb --nomousetouch -- $RUN_OPTIONS"
91− else
92− exec_with_ssh "cd $CODE_DIR && ./run --nomousetouch -- $RUN_OPTIONS"
95+ ARGS="$ARGS --gdb"
9396 fi
97+ if $PINLOCK; then
98+ ARGS="$ARGS -p"
99+ fi
100+ if $KEYLOCK; then
101+ ARGS="$ARGS -k"
102+ fi
103+
104+ exec_with_ssh "cd $CODE_DIR/ && ./run $ARGS -- $RUN_OPTIONS"
105+
94106 if [ $CONTAINS_SHELL -eq 0 ]; then
95107 echo "Restoring $SERVICEFILE on device from $SERVICEFILE.backup"
96108 adb shell cp $SERVICEFILE.backup $SERVICEFILE
@@ -98,7 +110,7 @@ run() {
98110 fi
99111}
100112
101−set -- `getopt -n$0 -u -a --longoptions="setup,gdb,help" "sgh" "$@"`
113+set -- `getopt -n$0 -u -a --longoptions="setup,gdb,pinlock,keylock,help" "sgpkh" "$@"`
102114
103115# FIXME: giving incorrect arguments does not call usage and exit
104116while [ $# -gt 0 ]
@@ -106,6 +118,8 @@ do
106118 case "$1" in
107119 -s|--setup) SETUP=true;;
108120 -g|--gdb) GDB=true;;
121+ -p|--pinlock) PINLOCK=true;;
122+ -k|--keylock) KEYLOCK=true;;
109123 -h|--help) usage;;
110124 --) shift;break;;
111125 esac
tests/autopilot/unity8/emulators/main_window.py+12
@@ -47,3 +47,15 @@ class MainWindow(object):
4747
4848 def get_launcher(self):
4949 return self.app.select_single("Launcher")
50+
51+ def get_pinPadLoader(self):
52+ return self.app.select_single("QQuickLoader", objectName="pinPadLoader")
53+
54+ def get_pinPadButton(self, buttonId):
55+ return self.app.select_single("PinPadButton", objectName="pinPadButton%i" % buttonId)
56+
57+ def get_lockscreen(self):
58+ return self.app.select_single("Lockscreen")
59+
60+ def get_pinentryField(self):
61+ return self.app.select_single("TextField", objectName="pinentryField")
tests/autopilot/unity8/tests/testcases.py → tests/autopilot/unity8/tests/testhud.pyrenamed+2−2
@@ -40,7 +40,7 @@ import os
4040from os import path
4141
4242
43−class TestShell(ShellTestCase, TestShellHelpers):
43+class TestHud(ShellTestCase, TestShellHelpers):
4444
4545 """Tests the Shell"""
4646
@@ -70,7 +70,7 @@ class TestShell(ShellTestCase, TestShellHelpers):
7070 divisor = 1
7171 while (sg.get_screen_width() < self.app_width / divisor or sg.get_screen_height() < self.app_height / divisor):
7272 divisor = divisor * 2
73− super(TestShell, self).setUp("%sx%s" % (self.app_width / divisor, self.app_height / divisor), "%s" % (self.grid_unit_px / divisor))
73+ super(TestHud, self).setUp("%sx%s" % (self.app_width / divisor, self.app_height / divisor), "%s" % (self.grid_unit_px / divisor))
7474
7575 dash = self.main_window.get_dash()
7676 self.assertThat(dash.showScopeOnLoaded, Eventually(Equals(""), timeout=30))
tests/autopilot/unity8/tests/testlockscreen.pynew file+118
@@ -0,0 +1,118 @@
1+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2+# Copyright 2013 Canonical
3+#
4+# This program is free software: you can redistribute it and/or modify it
5+# under the terms of the GNU General Public License version 3, as published
6+# by the Free Software Foundation.
7+
8+
9+# This file contains general purpose test cases for Unity.
10+# Each test written in this file will be executed for a variety of
11+# configurations, such as Phone, Tablet or Desktop form factors.
12+#
13+# Sometimes there is the need to disable a certain test for a particular
14+# configuration. To do so, add this in a new line directly below your test:
15+#
16+# test_testname.blacklist = (FormFactors.Tablet, FormFactors.Desktop,)
17+#
18+# Available form factors are:
19+# FormFactors.Phone
20+# FormFactors.Tablet
21+# FormFactors.Desktop
22+
23+
24+"""Tests for the Shell"""
25+
26+from __future__ import absolute_import
27+
28+from unity8.tests import ShellTestCase, FormFactors
29+from unity8.tests.helpers import TestShellHelpers
30+
31+from autopilot.input import Mouse, Touch, Pointer
32+from testtools.matchers import Equals, NotEquals, GreaterThan, MismatchError
33+from autopilot.matchers import Eventually
34+from autopilot.display import Display
35+from autopilot.platform import model
36+
37+import unittest
38+import time
39+import os
40+from os import path
41+
42+
43+class TestLockscreens(ShellTestCase, TestShellHelpers):
44+
45+ """Tests the Lockscreens"""
46+
47+ # Scenarios:
48+ # Fill in the scenarios to run the whole test suite with multiple configurations.
49+ # Use app_width, app_height and grid_unit_px to set the apps geometry.
50+ # Set app_width and app_height to 0 to use fullscreen.
51+ # Set grid_unit_px to 0 to use the current system environment.
52+
53+ if model() == 'Desktop':
54+ scenarios = [
55+ ('Pinlock', dict(app_width=768, app_height=1280, grid_unit_px=18, lightdm_mock="single-pin")),
56+ ('Keylock', dict(app_width=768, app_height=1280, grid_unit_px=18, lightdm_mock="single-passphrase")),
57+ ]
58+ else:
59+ scenarios = [
60+ ('Pinlock', dict(app_width=0, app_height=0, grid_unit_px=0, lightdm_mock="single-pin")),
61+ ('Keylock', dict(app_width=0, app_height=0, grid_unit_px=0, lightdm_mock="single-key")),
62+ ]
63+
64+ def setUp(self):
65+ self.touch = Touch.create()
66+
67+ sg = Display().create()
68+ divisor = 1
69+ while (sg.get_screen_width() < self.app_width / divisor or sg.get_screen_height() < self.app_height / divisor):
70+ divisor = divisor * 2
71+ super(TestLockscreens, self).setUp("%sx%s" % (self.app_width / divisor, self.app_height / divisor), "%s" % (self.grid_unit_px / divisor))
72+
73+ dash = self.main_window.get_dash()
74+ self.assertThat(dash.showScopeOnLoaded, Eventually(Equals(""), timeout=30))
75+
76+ def test_unlock(self):
77+ self.unlock_greeter()
78+
79+ pinPadLoader = self.main_window.get_pinPadLoader();
80+ self.assertThat(pinPadLoader.progress, Eventually(Equals(1)))
81+ lockscreen = self.main_window.get_lockscreen();
82+ self.assertThat(lockscreen.shown, Eventually(Equals(True)))
83+
84+ if self.lightdm_mock == "single-pin":
85+ self.touch.tap_object(self.main_window.get_pinPadButton(1))
86+ self.touch.tap_object(self.main_window.get_pinPadButton(2))
87+ self.touch.tap_object(self.main_window.get_pinPadButton(3))
88+ self.touch.tap_object(self.main_window.get_pinPadButton(4))
89+ self.assertThat(lockscreen.shown, Eventually(Equals(False)))
90+ else:
91+ pinentryField = self.main_window.get_pinentryField()
92+ self.touch.tap_object(pinentryField)
93+ self.keyboard.type("password\n")
94+ self.assertThat(lockscreen.shown, Eventually(Equals(False)))
95+
96+ def test_unlock_wrong(self):
97+ self.unlock_greeter()
98+
99+ pinPadLoader = self.main_window.get_pinPadLoader();
100+ self.assertThat(pinPadLoader.progress, Eventually(Equals(1)))
101+ lockscreen = self.main_window.get_lockscreen();
102+ self.assertThat(lockscreen.shown, Eventually(Equals(True)))
103+ pinentryField = self.main_window.get_pinentryField()
104+
105+ if self.lightdm_mock == "single-pin":
106+ self.touch.tap_object(self.main_window.get_pinPadButton(4))
107+ self.touch.tap_object(self.main_window.get_pinPadButton(3))
108+ self.touch.tap_object(self.main_window.get_pinPadButton(2))
109+ self.assertThat(pinentryField.text, Eventually(Equals("432")))
110+ self.touch.tap_object(self.main_window.get_pinPadButton(1))
111+
112+ self.assertThat(pinentryField.text, Eventually(Equals("")))
113+ self.assertThat(lockscreen.shown, Eventually(Equals(True)))
114+ else:
115+ self.touch.tap_object(pinentryField)
116+ self.keyboard.type("foobar\n")
117+ self.assertThat(pinentryField.text, Eventually(Equals("")))
118+ self.assertThat(lockscreen.shown, Eventually(Equals(True)))
tests/mocks/LightDM/CMakeLists.txt+2
@@ -7,6 +7,8 @@
77add_subdirectory(demo)
88add_subdirectory(full)
99add_subdirectory(single)
10+add_subdirectory(single-pin)
11+add_subdirectory(single-passphrase)
1012
1113include_directories(
1214 ${CMAKE_CURRENT_SOURCE_DIR}
tests/mocks/LightDM/full/GreeterPrivate.cpp+7−1
@@ -53,6 +53,8 @@ void GreeterPrivate::handleAuthenticate()
5353 if (authenticationUser == "no-password") {
5454 authenticated = true;
5555 Q_EMIT q->authenticationComplete();
56+ } else if (authenticationUser == "has-pin"){
57+ Q_EMIT q->showPrompt("PIN", Greeter::PromptTypeSecret);
5658 } else if (authenticationUser == "auth-error") {
5759 authenticated = false;
5860 Q_EMIT q->authenticationComplete();
@@ -85,7 +87,11 @@ void GreeterPrivate::handleRespond(const QString &response)
8587 return;
8688 }
8789
88− authenticated = (response == "password");
90+ if (authenticationUser == "has-pin") {
91+ authenticated = (response == "1234");
92+ } else {
93+ authenticated = (response == "password");
94+ }
8995 Q_EMIT q->authenticationComplete();
9096}
9197
tests/mocks/LightDM/full/UsersModelPrivate.cpp+1
@@ -27,6 +27,7 @@ UsersModelPrivate::UsersModelPrivate(UsersModel* parent)
2727 entries =
2828 {
2929 { "has-password", "Has Password", 0, 0, false, false, 0, 0 },
30+ { "has-pin", "Has PIN", 0, 0, false, false, 0, 0 },
3031 { "different-prompt", "Different Prompt", 0, 0, false, false, 0, 0 },
3132 { "no-password", "No Password", 0, 0, false, false, 0, 0 },
3233 { "auth-error", "Auth Error", 0, 0, false, false, 0, 0 },
tests/mocks/LightDM/single-passphrase/CMakeLists.txtnew file+21
@@ -0,0 +1,21 @@
1+set(LibLightDM_SOURCES
2+ ../Greeter.cpp
3+ ../UsersModel.cpp
4+ ../InfographicModel.cpp
5+ GreeterPrivate.cpp
6+ UsersModelPrivate.cpp
7+ InfographicModelPrivate.cpp
8+ ${CMAKE_SOURCE_DIR}/plugins/Utils/qvariantlistmodel.cpp
9+ )
10+
11+
12+add_library(MockLightDM-single-passphrase SHARED ${LibLightDM_SOURCES})
13+
14+qt5_use_modules(MockLightDM-single-passphrase Gui)
15+
16+set_target_properties(MockLightDM-single-passphrase PROPERTIES
17+ OUTPUT_NAME lightdm-qt5-2)
18+
19+install(TARGETS MockLightDM-single-passphrase
20+ DESTINATION ${SHELL_APP_DIR}/plugins/mocks/LightDM/single-passphrase
21+ )
tests/mocks/LightDM/single-passphrase/GreeterPrivate.cppnew file+53
@@ -0,0 +1,53 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU 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 General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Author: Michael Terry <michael.terry@canonical.com>
17+ */
18+
19+#include "../Greeter.h"
20+#include "../GreeterPrivate.h"
21+
22+#include <QDebug>
23+
24+namespace QLightDM
25+{
26+
27+GreeterPrivate::GreeterPrivate(Greeter* parent)
28+ : authenticated(false),
29+ authenticationUser(),
30+ q_ptr(parent)
31+{
32+}
33+
34+void GreeterPrivate::handleAuthenticate()
35+{
36+ Q_Q(Greeter);
37+
38+ qDebug() << "handleAuthentication called!" << authenticationUser;
39+
40+ Q_EMIT q->showPrompt("Password:", Greeter::PromptTypeSecret);
41+}
42+
43+void GreeterPrivate::handleRespond(const QString &response)
44+{
45+ Q_Q(Greeter);
46+
47+
48+ authenticated = (response == "password");
49+ qDebug() << "responding" << response << authenticated;
50+ Q_EMIT q->authenticationComplete();
51+}
52+
53+}
tests/mocks/LightDM/single-passphrase/InfographicModelPrivate.cppnew file+61
@@ -0,0 +1,61 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU 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 General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Author: Pete Woods <pete.woods@canonical.com>
17+ */
18+
19+#include "../InfographicModelPrivate.h"
20+
21+namespace QLightDM
22+{
23+
24+void InfographicModelPrivate::generateFakeData()
25+{
26+ std::default_random_engine generator;
27+ std::normal_distribution<qreal> distribution(0.5, 0.2);
28+ auto rand = std::bind(distribution, generator);
29+
30+ QColor orange = QColor::fromRgbF(0.9, 0.3, 0.1, 1.0);
31+ QColor yellow = QColor::fromRgbF(1.0, 0.6, 0.0, 1.0);
32+ QColor red = QColor::fromRgbF(0.8, 0.0, 0.0, 1.0);
33+ QColor darkPurple = QColor::fromRgbF(0.5, 0.2, 0.3, 1.0);
34+ QColor lightPurple = QColor::fromRgbF(0.8, 0.1, 0.8, 1.0);
35+ QColor pink(QColor::fromRgbF(0.75, 0.13, 0.75));
36+
37+ InfographicColorTheme orangeTheme(orange, orange, orange);
38+ InfographicColorTheme yellowTheme(yellow, yellow, yellow);
39+ InfographicColorTheme redTheme(red, red, red);
40+ InfographicColorTheme darkPurpleTheme(darkPurple, darkPurple, darkPurple);
41+ InfographicColorTheme lightPurpleTheme(lightPurple, lightPurple,
42+ lightPurple);
43+ InfographicColorTheme pinkTheme(pink, pink, pink);
44+
45+ {
46+ QVariantList firstMonth;
47+ while (firstMonth.size() < 17)
48+ firstMonth.push_back(QVariant(rand()));
49+ while (firstMonth.size() < 31)
50+ firstMonth.push_back(QVariant());
51+ QVariantList secondMonth;
52+ while (secondMonth.size() < 31)
53+ secondMonth.push_back(QVariant(rand()));
54+ QSharedPointer<InfographicData> data(
55+ new InfographicData("<b>5km</b> travelled", yellowTheme,
56+ firstMonth, orangeTheme, secondMonth, this));
57+ m_fakeData.insert("has-pin", data);
58+ }
59+}
60+
61+}
tests/mocks/LightDM/single-passphrase/UsersModelPrivate.cppnew file+33
@@ -0,0 +1,33 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU 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 General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Author: Michael Terry <michael.terry@canonical.com>
17+ */
18+
19+#include "../UsersModelPrivate.h"
20+
21+namespace QLightDM
22+{
23+
24+UsersModelPrivate::UsersModelPrivate(UsersModel* parent)
25+ : q_ptr(parent)
26+{
27+ entries =
28+ {
29+ { "has-pin", "Has PIN", 0, 0, false, false, 0, 0 },
30+ };
31+}
32+
33+}
tests/mocks/LightDM/single-pin/CMakeLists.txtnew file+21
@@ -0,0 +1,21 @@
1+set(LibLightDM_SOURCES
2+ ../Greeter.cpp
3+ ../UsersModel.cpp
4+ ../InfographicModel.cpp
5+ GreeterPrivate.cpp
6+ UsersModelPrivate.cpp
7+ InfographicModelPrivate.cpp
8+ ${CMAKE_SOURCE_DIR}/plugins/Utils/qvariantlistmodel.cpp
9+ )
10+
11+
12+add_library(MockLightDM-single-pin SHARED ${LibLightDM_SOURCES})
13+
14+qt5_use_modules(MockLightDM-single-pin Gui)
15+
16+set_target_properties(MockLightDM-single-pin PROPERTIES
17+ OUTPUT_NAME lightdm-qt5-2)
18+
19+install(TARGETS MockLightDM-single-pin
20+ DESTINATION ${SHELL_APP_DIR}/plugins/mocks/LightDM/single-pin
21+ )
tests/mocks/LightDM/single-pin/GreeterPrivate.cppnew file+53
@@ -0,0 +1,53 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU 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 General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Author: Michael Terry <michael.terry@canonical.com>
17+ */
18+
19+#include "../Greeter.h"
20+#include "../GreeterPrivate.h"
21+
22+#include <QDebug>
23+
24+namespace QLightDM
25+{
26+
27+GreeterPrivate::GreeterPrivate(Greeter* parent)
28+ : authenticated(false),
29+ authenticationUser(),
30+ q_ptr(parent)
31+{
32+}
33+
34+void GreeterPrivate::handleAuthenticate()
35+{
36+ Q_Q(Greeter);
37+
38+ qDebug() << "handleAuthentication called!" << authenticationUser;
39+
40+ Q_EMIT q->showPrompt("PIN:", Greeter::PromptTypeSecret);
41+}
42+
43+void GreeterPrivate::handleRespond(const QString &response)
44+{
45+ Q_Q(Greeter);
46+
47+
48+ authenticated = (response == "1234");
49+ qDebug() << "responding" << response << authenticated;
50+ Q_EMIT q->authenticationComplete();
51+}
52+
53+}
tests/mocks/LightDM/single-pin/InfographicModelPrivate.cppnew file+61
@@ -0,0 +1,61 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU 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 General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Author: Pete Woods <pete.woods@canonical.com>
17+ */
18+
19+#include "../InfographicModelPrivate.h"
20+
21+namespace QLightDM
22+{
23+
24+void InfographicModelPrivate::generateFakeData()
25+{
26+ std::default_random_engine generator;
27+ std::normal_distribution<qreal> distribution(0.5, 0.2);
28+ auto rand = std::bind(distribution, generator);
29+
30+ QColor orange = QColor::fromRgbF(0.9, 0.3, 0.1, 1.0);
31+ QColor yellow = QColor::fromRgbF(1.0, 0.6, 0.0, 1.0);
32+ QColor red = QColor::fromRgbF(0.8, 0.0, 0.0, 1.0);
33+ QColor darkPurple = QColor::fromRgbF(0.5, 0.2, 0.3, 1.0);
34+ QColor lightPurple = QColor::fromRgbF(0.8, 0.1, 0.8, 1.0);
35+ QColor pink(QColor::fromRgbF(0.75, 0.13, 0.75));
36+
37+ InfographicColorTheme orangeTheme(orange, orange, orange);
38+ InfographicColorTheme yellowTheme(yellow, yellow, yellow);
39+ InfographicColorTheme redTheme(red, red, red);
40+ InfographicColorTheme darkPurpleTheme(darkPurple, darkPurple, darkPurple);
41+ InfographicColorTheme lightPurpleTheme(lightPurple, lightPurple,
42+ lightPurple);
43+ InfographicColorTheme pinkTheme(pink, pink, pink);
44+
45+ {
46+ QVariantList firstMonth;
47+ while (firstMonth.size() < 17)
48+ firstMonth.push_back(QVariant(rand()));
49+ while (firstMonth.size() < 31)
50+ firstMonth.push_back(QVariant());
51+ QVariantList secondMonth;
52+ while (secondMonth.size() < 31)
53+ secondMonth.push_back(QVariant(rand()));
54+ QSharedPointer<InfographicData> data(
55+ new InfographicData("<b>5km</b> travelled", yellowTheme,
56+ firstMonth, orangeTheme, secondMonth, this));
57+ m_fakeData.insert("has-pin", data);
58+ }
59+}
60+
61+}
tests/mocks/LightDM/single-pin/UsersModelPrivate.cppnew file+33
@@ -0,0 +1,33 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU 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 General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Author: Michael Terry <michael.terry@canonical.com>
17+ */
18+
19+#include "../UsersModelPrivate.h"
20+
21+namespace QLightDM
22+{
23+
24+UsersModelPrivate::UsersModelPrivate(UsersModel* parent)
25+ : q_ptr(parent)
26+{
27+ entries =
28+ {
29+ { "has-pin", "Has PIN", 0, 0, false, false, 0, 0 },
30+ };
31+}
32+
33+}
tests/plugins/Ubuntu/Gestures/CMakeLists.txt+5−3
@@ -11,9 +11,9 @@ set(testCommand
1111 -o ${CMAKE_BINARY_DIR}/DirectionalDragAreaTest.xml,xunitxml
1212 -o -,txt)
1313
14−add_custom_target(DirectionalDragAreaTest ${testCommand})
15−add_dependencies(qmluitests DirectionalDragAreaTest)
16−add_dependencies(DirectionalDragAreaTest DirectionalDragAreaTestExec UbutunGesturesTestQmlFiles)
14+add_custom_target(testDirectionalDragArea ${testCommand})
15+add_dependencies(qmluitests testDirectionalDragArea)
16+add_dependencies(testDirectionalDragArea DirectionalDragAreaTestExec UbutunGesturesTestQmlFiles)
1717
1818add_executable(DirectionalDragAreaTestExec tst_DirectionalDragArea.cpp)
1919qt5_use_modules(DirectionalDragAreaTestExec Test Core Qml Gui Quick)
@@ -27,6 +27,8 @@ add_custom_target(UbuntuGesturesTestQmlFiles ALL
2727 DEPENDS ${qmlFiles}
2828)
2929
30+########## other tests
31+
3032macro(add_gesture_test CLASSNAME)
3133 set(testCommand
3234 ${CLASSNAME}TestExec -o ${CMAKE_BINARY_DIR}/test${CLASSNAME}.xml,xunitxml -o -,txt)
tests/plugins/Ubuntu/Gestures/RightwardsLauncher.qml+1
@@ -54,6 +54,7 @@ Item {
5454 maxDeviation: units.gu(2)
5555 wideningAngle: 10
5656 distanceThreshold: units.gu(4)
57+ minSpeed: 50
5758
5859 onStatusChanged: {
5960 switch (status) {
tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp+203−16
@@ -22,6 +22,30 @@
2222
2323#include <DirectionalDragArea.h>
2424
25+class FakeTimer : public UbuntuGestures::AbstractTimer
26+{
27+ Q_OBJECT
28+public:
29+ FakeTimer(QObject *parent = 0)
30+ : UbuntuGestures::AbstractTimer(parent)
31+ {}
32+
33+ virtual int interval() const { return m_duration; }
34+ virtual void setInterval(int msecs) { m_duration = msecs; }
35+
36+ void emitTimeout() {Q_EMIT timeout();}
37+private:
38+ int m_duration;
39+};
40+
41+class FakeTimeSource : public AxisVelocityCalculator::TimeSource
42+{
43+public:
44+ FakeTimeSource() { m_msecsSinceReference = 0; }
45+ virtual qint64 msecsSinceReference() {return m_msecsSinceReference;}
46+ qint64 m_msecsSinceReference;
47+};
48+
2549class tst_DirectionalDragArea: public QObject
2650{
2751 Q_OBJECT
@@ -31,13 +55,24 @@ private Q_SLOTS:
3155 void initTestCase(); // will be called before the first test function is executed
3256 void cleanupTestCase(); // will be called after the last test function was executed.
3357
58+ void init(); // called right before each and every test function is executed
59+ void cleanup(); // called right after each and every test function is executed
60+
3461 void edgeDrag();
3562 void edgeDrag_data();
3663 void dragWithShortDirectionChange();
64+ void minSpeed();
65+ void minSpeed_data();
66+ void recognitionTimerUsage();
67+ void maxSilenceTime();
3768
3869private:
3970 QQuickView *createView();
71+ QQuickView *view;
4072 QTouchDevice *device;
73+ FakeTimer *fakeTimer;
74+ FakeTimeSource *fakeTimeSource;
75+ AxisVelocityCalculator *velCalcWithFakeTiming;
4176};
4277
4378void tst_DirectionalDragArea::initTestCase()
@@ -47,12 +82,42 @@ void tst_DirectionalDragArea::initTestCase()
4782 device->setType(QTouchDevice::TouchScreen);
4883 QWindowSystemInterface::registerTouchDevice(device);
4984 }
85+
86+ view = 0;
5087}
5188
5289void tst_DirectionalDragArea::cleanupTestCase()
5390{
5491}
5592
93+void tst_DirectionalDragArea::init()
94+{
95+ view = createView();
96+ view->setSource(QUrl::fromLocalFile("edgeDragExample.qml"));
97+ view->show();
98+ QVERIFY(QTest::qWaitForWindowExposed(view));
99+ QVERIFY(view->rootObject() != 0);
100+ qApp->processEvents();
101+
102+ fakeTimer = new FakeTimer;
103+ fakeTimeSource = new FakeTimeSource;
104+ velCalcWithFakeTiming = new AxisVelocityCalculator(fakeTimeSource);
105+}
106+
107+void tst_DirectionalDragArea::cleanup()
108+{
109+ delete view;
110+ view = 0;
111+
112+ delete fakeTimer;
113+ fakeTimer = 0;
114+
115+ // will bring fakeTimeSource down with it
116+ delete velCalcWithFakeTiming;
117+ fakeTimeSource = 0;
118+ velCalcWithFakeTiming = 0;
119+}
120+
56121QQuickView *tst_DirectionalDragArea::createView()
57122{
58123 QQuickView *window = new QQuickView(0);
@@ -119,17 +184,11 @@ void tst_DirectionalDragArea::edgeDrag()
119184 QFETCH(qreal, dragDistanceFactor);
120185 QFETCH(int, expectedGestureRecognition);
121186
122− QQuickView *view = createView();
123− QScopedPointer<QQuickView> scope(view);
124− view->setSource(QUrl::fromLocalFile("edgeDragExample.qml"));
125− view->show();
126− QVERIFY(QTest::qWaitForWindowExposed(view));
127− QVERIFY(view->rootObject() != 0);
128− qApp->processEvents();
129−
130187 DirectionalDragArea *edgeDragArea =
131188 view->rootObject()->findChild<DirectionalDragArea*>(dragAreaObjectName);
132189 QVERIFY(edgeDragArea != 0);
190+ edgeDragArea->setRecognitionTimer(fakeTimer);
191+ edgeDragArea->setAxisVelocityCalculator(velCalcWithFakeTiming);
133192
134193 QSignalSpy draggingSpy(edgeDragArea, SIGNAL(draggingChanged(bool)));
135194
@@ -230,17 +289,11 @@ void tst_DirectionalDragArea::edgeDrag_data()
230289 */
231290void tst_DirectionalDragArea::dragWithShortDirectionChange()
232291{
233− QQuickView *view = createView();
234− QScopedPointer<QQuickView> scope(view);
235− view->setSource(QUrl::fromLocalFile("edgeDragExample.qml"));
236− view->show();
237− QVERIFY(QTest::qWaitForWindowExposed(view));
238− QVERIFY(view->rootObject() != 0);
239− qApp->processEvents();
240−
241292 DirectionalDragArea *edgeDragArea =
242293 view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
243294 QVERIFY(edgeDragArea != 0);
295+ edgeDragArea->setRecognitionTimer(fakeTimer);
296+ edgeDragArea->setAxisVelocityCalculator(velCalcWithFakeTiming);
244297
245298 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, view);
246299 QPointF touchPoint = initialTouchPos;
@@ -272,6 +325,140 @@ void tst_DirectionalDragArea::dragWithShortDirectionChange()
272325 QTest::touchEvent(view, device).release(0, touchPoint.toPoint());
273326}
274327
328+/*
329+ Checks that a gesture will be rejected if it's slower than minSpeed while
330+ status is Undecided.
331+ */
332+void tst_DirectionalDragArea::minSpeed()
333+{
334+ QFETCH(int, minSpeedMsecsDeviation);
335+ QFETCH(int, expectedStatusAfterSpeedCheck);
336+
337+ DirectionalDragArea *edgeDragArea =
338+ view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
339+ QVERIFY(edgeDragArea != 0);
340+ edgeDragArea->setRecognitionTimer(fakeTimer);
341+ edgeDragArea->setAxisVelocityCalculator(velCalcWithFakeTiming);
342+
343+ QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, view);
344+ QPointF touchPoint = initialTouchPos;
345+
346+ QPointF dragDirectionVector(1.0, 0.0);
347+ qreal distanceStep = edgeDragArea->distanceThreshold() * 0.1f;
348+ QPointF touchMovement = dragDirectionVector * distanceStep;
349+ qreal minSpeedMsecs = edgeDragArea->minSpeed() / 1000.0;
350+ qint64 timeStepMsecs = qFloor(distanceStep / minSpeedMsecs) + minSpeedMsecsDeviation;
351+
352+ // if it fails it means the params set in the QML file are not in harmony with the values
353+ // used in this test
354+ Q_ASSERT(timeStepMsecs > 0);
355+
356+ fakeTimeSource->m_msecsSinceReference = 0;
357+ QTest::touchEvent(view, device).press(0, touchPoint.toPoint());
358+
359+ // Move a bit in the proper direction
360+ for (int i=0; i < 4; ++i) {
361+ touchPoint += touchMovement;
362+ fakeTimeSource->m_msecsSinceReference += timeStepMsecs;
363+ QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
364+ }
365+
366+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
367+
368+ // Force the periodic speed check.
369+ fakeTimer->emitTimeout();
370+
371+ QCOMPARE((int)edgeDragArea->status(), expectedStatusAfterSpeedCheck);
372+}
373+
374+void tst_DirectionalDragArea::minSpeed_data()
375+{
376+ QTest::addColumn<int>("minSpeedMsecsDeviation");
377+ QTest::addColumn<int>("expectedStatusAfterSpeedCheck");
378+
379+ QTest::newRow("slower than minSpeed") << 20 << (int)DirectionalDragArea::Rejected;
380+ QTest::newRow("faster than minSpeed") << -20 << (int)DirectionalDragArea::Undecided;
381+}
382+
383+/*
384+ Checks that the recognition timer is started and stopped appropriately.
385+ I.e., check that it's running only while gesture recognition is taking place
386+ (status == Undecided)
387+ */
388+void tst_DirectionalDragArea::recognitionTimerUsage()
389+{
390+ DirectionalDragArea *edgeDragArea =
391+ view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
392+ QVERIFY(edgeDragArea != 0);
393+ edgeDragArea->setRecognitionTimer(fakeTimer);
394+ edgeDragArea->setAxisVelocityCalculator(velCalcWithFakeTiming);
395+
396+ QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, view);
397+ QPointF touchPoint = initialTouchPos;
398+
399+ QPointF dragDirectionVector(1.0, 0.0);
400+ QPointF touchMovement = dragDirectionVector * (edgeDragArea->distanceThreshold() * 0.2f);
401+
402+ QVERIFY(!fakeTimer->isRunning());
403+
404+ QTest::touchEvent(view, device).press(0, touchPoint.toPoint());
405+
406+ QVERIFY(fakeTimer->isRunning());
407+
408+ // Move a bit in the proper direction
409+ for (int i=0; i < 3; ++i) {
410+ touchPoint += touchMovement;
411+ QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
412+ }
413+
414+ QVERIFY(fakeTimer->isRunning());
415+
416+ // Move beyond distance threshold
417+ touchPoint += 3*touchMovement;
418+ QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
419+
420+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
421+
422+ QVERIFY(!fakeTimer->isRunning());
423+}
424+
425+/*
426+ A gesture should be rejected if too much time has passed without any new input
427+ events from it.
428+ */
429+void tst_DirectionalDragArea::maxSilenceTime()
430+{
431+ DirectionalDragArea *edgeDragArea =
432+ view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
433+ QVERIFY(edgeDragArea != 0);
434+ edgeDragArea->setRecognitionTimer(fakeTimer);
435+ edgeDragArea->setAxisVelocityCalculator(velCalcWithFakeTiming);
436+
437+ QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, view);
438+ QPointF touchPoint = initialTouchPos;
439+
440+ fakeTimeSource->m_msecsSinceReference = 0;
441+ QTest::touchEvent(view, device).press(0, touchPoint.toPoint());
442+
443+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
444+
445+ int neededTimeouts = qCeil((qreal)edgeDragArea->maxSilenceTime() / (qreal)fakeTimer->interval());
446+ if ((edgeDragArea->maxSilenceTime() % fakeTimer->interval()) == 0) {
447+ ++neededTimeouts;
448+ }
449+
450+ // Force the periodic speed check.
451+ for (int i = 0; i < neededTimeouts; ++i) {
452+ fakeTimer->emitTimeout();
453+
454+ if (i < neededTimeouts - 1) {
455+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
456+ }
457+ }
458+
459+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Rejected);
460+}
461+
275462QTEST_MAIN(tst_DirectionalDragArea)
276463
277464#include "tst_DirectionalDragArea.moc"
tests/qmltests/CMakeLists.txt+3
@@ -37,6 +37,7 @@ add_qml_test(Components Stage IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS}
3737 ${CMAKE_BINARY_DIR}/plugins)
3838add_qml_test(Components Tile)
3939add_qml_test(Components PageHeader)
40+add_qml_test(Dash Dash IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})
4041add_qml_test(Dash DashBar IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${CMAKE_CURRENT_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})
4142add_qml_test(Dash DashContent IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS})
4243add_qml_test(Dash DashPreview)
@@ -47,6 +48,8 @@ add_qml_test(Dash FilterGrids IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMA
4748 ${CMAKE_BINARY_DIR}/tests/mocks)
4849add_qml_test(Dash/Apps RunningApplicationsGrid IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/tests/mocks)
4950add_qml_test(Dash/People PeopleFilterGrid IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${CMAKE_CURRENT_SOURCE_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})
51+add_qml_test(Greeter Lockscreen IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/plugins ${CMAKE_BINARY_DIR}/tests/mocks
52+ PROPERTIES ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/LightDM/full")
5053add_qml_test(Greeter Tablet IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/plugins ${CMAKE_BINARY_DIR}/tests/mocks
5154 ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/LightDM/full")
5255add_qml_test(Greeter Phone IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/plugins ${CMAKE_BINARY_DIR}/tests/mocks
tests/qmltests/Dash/qml/FakeScopeView.qml+65−38
@@ -22,16 +22,14 @@ FocusScope {
2222 property Scope scope : null
2323 property bool isCurrent : false
2424
25− property alias rect_color1 : rect1.color
26− property alias rect_color2 : rect2.color
27− property alias rect_color3 : rect3.color
28−
2925 property ListModel searchHistory
3026
3127 signal endReached
3228 signal movementStarted
3329 signal positionedAtBeginning
3430
31+ property alias backColor : back.color
32+
3533 onEndReached: {
3634 if (shell != undefined && shell.scope_status != undefined) {
3735 shell.scope_status[scope.id].endReached++;
@@ -43,50 +41,79 @@ FocusScope {
4341 }
4442 }
4543 onPositionedAtBeginning: {
46− if (shell != undefined && shell.scope_status != undefined) {
47− shell.scope_status[scope.id].positionedAtBeginning++;
44+ if (shell != undefined && shell.scopeStatus != undefined) {
45+ shell.scopeStatus[scope.id].positionedAtBeginning++;
4846 }
4947 }
5048
49+ Rectangle {
50+ id: back
51+ anchors.fill: parent
52+ color: "grey"
53+ }
54+
55+ function randomBg()
56+ {
57+ var hex1=new Array("4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F")
58+ var bg="#"+hex1[Math.floor(Math.random()*hex1.length)]+
59+ hex1[Math.floor(Math.random()*hex1.length)]+
60+ hex1[Math.floor(Math.random()*hex1.length)]+
61+ hex1[Math.floor(Math.random()*hex1.length)]+
62+ hex1[Math.floor(Math.random()*hex1.length)]+
63+ hex1[Math.floor(Math.random()*hex1.length)]
64+ return bg
65+ }
66+
5167
52− Column {
68+ ListView {
69+ id: listView
5370 anchors.fill: parent
54− Rectangle {
55− id: rect1
56− height: parent.height/3; width: parent.width;
71+ model: scope ? scope.categories : null
72+ orientation: ListView.Vertical
5773
58− Text {
59− id: label1
60− anchors.fill: parent
61− verticalAlignment: Text.AlignVCenter
62− horizontalAlignment: Text.AlignHCenter
63− text: scope ? scope.id : ""
64− }
65− }
74+ delegate: Column {
75+ id: column
76+ width: listView.width
77+ height: childrenRect.height
6678
67− Rectangle {
68− id: rect2
69− height: parent.height/3; width: parent.width
79+ Rectangle {
80+ width: listView.width
81+ height: units.gu(3)
82+ color: randomBg()
7083
71− Text {
72− id: label2
73− anchors.fill: parent
74− verticalAlignment: Text.AlignVCenter
75− horizontalAlignment: Text.AlignHCenter
76− text: scope ? scope.id : ""
84+ Text {
85+ text: name
86+ font.family: "Ubuntu"
87+ font.weight: Font.Bold
88+ font.pixelSize: 20
89+ anchors.horizontalCenter: parent.horizontalCenter
90+ }
7791 }
78− }
79−
80− Rectangle {
81− id: rect3
82− height: parent.height/3; width: parent.width
92+ GridView {
93+ id: resultsGrid
94+ model: results
95+ cellWidth: units.gu(10); cellHeight: units.gu(10)
96+ height: childrenRect.height
97+ width: listView.width
98+ interactive: false
8399
84− Text {
85− id: label3
86− anchors.fill: parent
87− verticalAlignment: Text.AlignVCenter
88− horizontalAlignment: Text.AlignHCenter
89− text: scope ? scope.id : ""
100+ delegate: Component {
101+ id: resultDelegate
102+ Item {
103+ width: resultsGrid.cellWidth; height: resultsGrid.cellHeight
104+ Column {
105+ anchors.verticalCenter: parent.verticalCenter
106+ anchors.horizontalCenter: parent.horizontalCenter
107+ Image {
108+ width: units.gu(5)
109+ height: units.gu(5)
110+ source: column_1
111+ anchors.horizontalCenter: parent.horizontalCenter
112+ }
113+ Text { text: column_4; anchors.horizontalCenter: parent.horizontalCenter }
114+ }
115+ }
116+ }
90117 }
91118 }
92119 }
tests/qmltests/Dash/qml/fake_generic_scopeView.qml+1−3
@@ -20,7 +20,5 @@ FakeScopeView {
2020 id: fake_generic_scopeView
2121 objectName: "fake_generic_scopeView"
2222
23− rect_color1: "blue"
24− rect_color2: "orange"
25− rect_color3: "teal"
23+ backColor: "#D0D0D0"
2624}
tests/qmltests/Dash/qml/fake_scopeView1.qml+1−3
@@ -20,7 +20,5 @@ FakeScopeView {
2020 id: fake_scopeView1
2121 objectName: "fake_scopeView1"
2222
23− rect_color1: "red"
24− rect_color2: "green"
25− rect_color3: "blue"
23+ backColor: "#909090"
2624}
tests/qmltests/Dash/qml/fake_scopeView2.qml+1−3
@@ -20,7 +20,5 @@ FakeScopeView {
2020 id: fake_scopeView2
2121 objectName: "fake_scopeView2"
2222
23− rect_color1: "orange"
24− rect_color2: "grey"
25− rect_color3: "purple"
23+ backColor: "#A0A0A0"
2624}
tests/qmltests/Dash/qml/fake_scopeView3.qml+1−3
@@ -20,7 +20,5 @@ FakeScopeView {
2020 id: fake_scopeView3
2121 objectName: "fake_scopeView3"
2222
23− rect_color1: "yellow"
24− rect_color2: "cyan"
25− rect_color3: "white"
23+ backColor: "#B0B0B0"
2624}
tests/qmltests/Dash/qml/fake_scopeView4.qml+1−3
@@ -20,7 +20,5 @@ FakeScopeView {
2020 id: fake_scopeView4
2121 objectName: "fake_scopeView4"
2222
23− rect_color1: "grey"
24− rect_color2: "purple"
25− rect_color3: "red"
23+ backColor: "#C0C0C0"
2624}
tests/qmltests/Dash/tst_Dash.qmlnew file+133
@@ -0,0 +1,133 @@
1+/*
2+ * Copyright 2013 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU 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 General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import QtTest 1.0
19+import "../../../Dash"
20+import Ubuntu.Components 0.1
21+import Unity.Test 0.1 as UT
22+
23+Item {
24+ id: shell
25+ width: units.gu(40)
26+ height: units.gu(80)
27+
28+ property ListModel searchHistory : ListModel {}
29+
30+ Dash {
31+ id: dash
32+ anchors.fill: parent
33+ showScopeOnLoaded: "MockScope2"
34+ }
35+
36+ ScopeDelegateMapper {
37+ id: scopeDelegateMapper
38+ scopeDelegateMapping: {
39+ "MockScope1": "../tests/qmltests/Dash/qml/fake_scopeView1.qml",
40+ "MockScope2": "../tests/qmltests/Dash/qml/fake_scopeView2.qml",
41+ "MockScope3": "../tests/qmltests/Dash/qml/fake_scopeView3.qml",
42+ "MockScope4": "../tests/qmltests/Dash/qml/fake_scopeView4.qml"
43+ }
44+ genericScope: "../tests/qmltests/Dash/qml/fake_generic_scopeView.qml"
45+ }
46+
47+ UT.UnityTestCase {
48+ name: "Dash"
49+ when: windowShown
50+
51+ property var scopes
52+
53+ Component.onCompleted: {
54+ var dashContent = findChild(dash, "dashContent");
55+ dashContent.scopeMapper = scopeDelegateMapper;
56+ scopes = dashContent.scopes;
57+ }
58+
59+ function init() {
60+ // clear and reload the scopes.
61+ scopes.clear();
62+ var dashContentList = findChild(dash, "dashContentList");
63+ verify(dashContentList != undefined);
64+ tryCompare(dashContentList, "count", 0);
65+ scopes.load();
66+ }
67+
68+ function get_scope_data() {
69+ return [
70+ { tag: "MockScope1", visualIndex: 0, shouldBeVisible: true },
71+ { tag: "MockScope2", visualIndex: -1, shouldBeVisible: false },
72+ { tag: "MockScope3", visualIndex: 1, shouldBeVisible: true },
73+ { tag: "MockScope4", visualIndex: 2, shouldBeVisible: true },
74+ { tag: "MockScope5", visualIndex: 3, shouldBeVisible: true },
75+ ]
76+ }
77+
78+ function test_set_current_scope_data() {
79+ return get_scope_data()
80+ }
81+
82+ function test_set_current_scope(data) {
83+ // wait for scopes to load
84+ tryCompare(scopes, "loaded", true);
85+
86+ var dashbar = findChild(dash, "dashbar");
87+ verify(dashbar != undefined)
88+ var dashContent = findChild(dash, "dashContent");
89+ var current_index = dashContent.currentIndex;
90+
91+ dash.setCurrentScope(data.tag, true /* animate */, false /* reset */);
92+ compare(dashContent.currentIndex, data.shouldBeVisible ? data.visualIndex : current_index);
93+ compare(dashbar.currentIndex, data.shouldBeVisible ? data.visualIndex : current_index);
94+ }
95+
96+ function test_show_scope_on_load_data() {
97+ return get_scope_data()
98+ }
99+
100+ function test_show_scope_on_load(data) {
101+ if (data.shouldBeVisible == false) {
102+ console.log("Not testing " + data.tag + ": not visible");
103+ return;
104+ }
105+ dash.showScopeOnLoaded = data.tag
106+
107+ var dashContentList = findChild(dash, "dashContentList");
108+ verify(dashContentList != undefined);
109+ tryCompare(dashContentList, "currentIndex", data.visualIndex);
110+ }
111+
112+ function test_dash_bar_set_index_connection_data() {
113+ return get_scope_data()
114+ }
115+
116+ function test_dash_bar_set_index_connection(data) {
117+ if (data.shouldBeVisible == false) {
118+ console.log("Not testing " + data.tag + ": not visible");
119+ return;
120+ }
121+ // wait for scopes to load
122+ tryCompare(scopes, "loaded", true);
123+
124+ var dashbar = findChild(dash, "dashbar");
125+ verify(dashbar != undefined)
126+ var dashContent = findChild(dash, "dashContent");
127+ var current_index = dashContent.currentIndex;
128+
129+ dashbar.itemSelected(data.visualIndex);
130+ compare(dashContent.currentIndex, data.shouldBeVisible ? data.visualIndex : current_index);
131+ }
132+ }
133+}
tests/qmltests/Dash/tst_DashContent.qml+41−34
@@ -28,7 +28,7 @@ Item {
2828
2929 property ListModel searchHistory: ListModel {}
3030
31− property var scope_status: {
31+ property var scopeStatus: {
3232 'MockScope1': { 'movementStarted': 0, 'positionedAtBeginning': 0 },
3333 'MockScope2': { 'movementStarted': 0, 'positionedAtBeginning': 0 },
3434 'MockScope3': { 'movementStarted': 0, 'positionedAtBeginning': 0 },
@@ -41,19 +41,24 @@ Item {
4141 }
4242
4343 DashContent {
44− id: dash_content
44+ id: dashContent
4545 anchors.fill: parent
4646
4747 model: scopesModel
4848 scopes : scopesModel
4949
50− scopeDelegateMapping: { "MockScope3" : "../tests/qmltests/Dash/qml/fake_scopeView3.qml",
51− "MockScope2" : "../tests/qmltests/Dash/qml/fake_scopeView2.qml",
52− "MockScope1" : "../tests/qmltests/Dash/qml/fake_scopeView1.qml",
53− "MockScope4" : "../tests/qmltests/Dash/qml/fake_scopeView4.qml"
54− }
50+ lensMapper : lensDelegateMapper
51+ }
5552
56− genericScope: "../tests/qmltests/Dash/qml/fake_generic_scopeView.qml"
53+ LensDelegateMapper {
54+ id: lensDelegateMapper
55+ lensDelegateMapping: {
56+ "MockLens1": "../tests/qmltests/Dash/qml/fake_lensView1.qml",
57+ "MockLens2": "../tests/qmltests/Dash/qml/fake_lensView2.qml",
58+ "MockLens3": "../tests/qmltests/Dash/qml/fake_lensView3.qml",
59+ "MockLens4": "../tests/qmltests/Dash/qml/fake_lensView4.qml"
60+ }
61+ genericLens: "../tests/qmltests/Dash/qml/fake_generic_lensView.qml"
5762 }
5863
5964 function clear_scope_status() {
@@ -74,20 +79,20 @@ Item {
7479 }
7580
7681 SignalSpy {
77− id: scopeLoaded_spy
78− target: dash_content
82+ id: scopeLoadedSpy
83+ target: dashContent
7984 signalName: "scopeLoaded"
8085 }
8186
8287 SignalSpy {
83− id: movementStarted_spy
84− target: dash_content
88+ id: movementStartedSpy
89+ target: dashContent
8590 signalName: "movementStarted"
8691 }
8792
8893 SignalSpy {
89− id: contentEndReached_spy
90− target: dash_content
94+ id: contentEndReachedSpy
95+ target: dashContent
9196 signalName: "contentEndReached"
9297 }
9398
@@ -96,13 +101,13 @@ Item {
96101 when: windowShown
97102
98103 function init() {
99− scopeLoaded_spy.clear();
100− movementStarted_spy.clear();
101− contentEndReached_spy.clear()
104+ scopeLoadedSpy.clear();
105+ movementStartedSpy.clear();
106+ contentEndReachedSpy.clear()
102107 clear_scope_status();
103108
104− // clear, wait for dahs to empty and load scopes.
105− var dashContentList = findChild(dash_content, "dashContentList");
109+ // clear, wait for dahs to empty and load lenses.
110+ var dashContentList = findChild(dashContent, "dashContentList");
106111 verify(dashContentList != undefined)
107112 scopesModel.clear();
108113 tryCompare(dashContentList, "count", 0);
@@ -112,12 +117,14 @@ Item {
112117 function test_movement_started_signal() {
113118 dash_content.setCurrentScopeAtIndex(3, true, false);
114119
115− var dashContentList = findChild(dash_content, "dashContentList");
120+ var dashContentList = findChild(dashContent, "dashContentList");
116121 verify(dashContentList != undefined)
117− tryCompare(scopeLoaded_spy, "count", 5);
122+
123+ tryCompare(scopeLoadedSpy, "count", 5);
118124
119125 dashContentList.movementStarted();
120− compare(movementStarted_spy.count, 1, "DashContent should have emitted movementStarted signal when content list did.");
126+
127+ compare(movementStartedSpy.count, 1, "DashContent should have emitted movementStarted signal when content list did.");
121128 compare(scope_status["MockScope1"].movementStarted, 1, "MockScope1 should have emitted movementStarted signal when content list did.");
122129 compare(scope_status["MockScope2"].movementStarted, 1, "MockScope2 should have emitted movementStarted signal when content list did.");
123130 compare(scope_status["MockScope3"].movementStarted, 1, "MockScope3 should have emitted movementStarted signal when content list did.");
@@ -128,7 +135,7 @@ Item {
128135 function test_positioned_at_beginning_signal() {
129136 dash_content.setCurrentScopeAtIndex(3, true, false);
130137
131− tryCompare(scopeLoaded_spy, "count", 5);
138+ tryCompare(scopeLoadedSpy, "count", 5);
132139
133140 dash_content.positionedAtBeginning();
134141 compare(scope_status["MockScope1"].positionedAtBeginning, 1, "MockScope1 should have emitted positionedAtBeginning signal when DashContent did.");
@@ -139,18 +146,18 @@ Item {
139146 }
140147
141148 function test_scope_loaded() {
142− tryCompare(scopeLoaded_spy, "count", 5);
149+ tryCompare(scopeLoadedSpy, "count", 5);
143150 }
144151
145152 function test_content_end_reached() {
146− var dashContentList = findChild(dash_content, "dashContentList");
153+ var dashContentList = findChild(dashContent, "dashContentList");
147154 verify(dashContentList != undefined);
148− tryCompare(scopeLoaded_spy, "count", 5);
149155
156+ tryCompare(scopeLoadedSpy, "count", 5);
150157 dash_content.setCurrentScopeAtIndex(0, true, false);
151158 dashContentList.currentItem.item.endReached();
152159
153− compare(contentEndReached_spy.count, 1);
160+ compare(contentEndReachedSpy.count, 1);
154161 }
155162
156163 // This tests that setting the current scope index will end up at the correct index even if
@@ -159,19 +166,19 @@ Item {
159166 verify(scopesModel.loaded == false);
160167
161168 // next index is 1 if current is -1, otherwise it's current + 1
162− var next_index = ((dash_content.currentIndex == -1 ? 0 : dash_content.currentIndex) + 1) % 5
169+ var next_index = ((dashContent.currentIndex == -1 ? 0 : dashContent.currentIndex) + 1) % 5
163170
164− dash_content.setCurrentScopeAtIndex(next_index, true, false);
165− tryCompare(dash_content, "currentIndex", next_index);
171+ dashContent.setCurrentScopeAtIndex(next_index, true, false);
172+ tryCompare(dashContent, "currentIndex", next_index);
166173 verify(scopesModel.loaded == true);
167174
168175 // test greater than scope count.
169− dash_content.setCurrentScopeAtIndex(scopesModel.count, true, false);
170− compare(dash_content.currentIndex, 4);
176+ dashContent.setCurrentScopeAtIndex(scopesModel.count, true, false);
177+ compare(dashContent.currentIndex, 4);
171178 }
172179
173180 function get_current_item_object_name() {
174− var dashContentList = findChild(dash_content, "dashContentList");
181+ var dashContentList = findChild(dashContent, "dashContentList");
175182 verify(dashContentList != undefined);
176183
177184 if (dashContentList.currentItem != undefined) {
@@ -193,7 +200,7 @@ Item {
193200 }
194201
195202 function test_scope_mapping(data) {
196− dash_content.setCurrentScopeAtIndex(data.index, true, false);
203+ dashContent.setCurrentLensAtIndex(data.index, true, false);
197204 tryCompareFunction(get_current_item_object_name, data.objectName)
198205 }
199206
tests/qmltests/Dash/tst_GenericLensView.qml → tests/qmltests/Dash/tst_GenericScopeView.qmlrenamed

The content is the same.

tests/qmltests/Dash/tst_LensView.qml → tests/qmltests/Dash/tst_ScopeView.qmlrenamed

The content is the same.

tests/qmltests/Greeter/tst_Lockscreen.qmlnew file+205
@@ -0,0 +1,205 @@
1+/*
2+ * Copyright 2013 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU 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 General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import QtTest 1.0
19+import ".."
20+import "../../../Greeter"
21+import Ubuntu.Components 0.1
22+import LightDM 0.1 as LightDM
23+import Unity.Test 0.1 as UT
24+
25+Rectangle {
26+ width: units.gu(60)
27+ height: units.gu(80)
28+ color: "orange"
29+
30+ Lockscreen {
31+ id: lockscreen
32+ anchors.fill: parent
33+ anchors.rightMargin: units.gu(18)
34+ placeholderText: "Please enter your PIN"
35+ alphaNumeric: pinPadCheckBox.checked
36+ pinLength: pinLengthTextField.text
37+ username: "Lola"
38+ }
39+
40+ Connections {
41+ target: lockscreen
42+
43+ onEmergencyCall: emergencyCheckBox.checked = true
44+ onUnlocked: unlockedCheckBox.checked = true
45+ }
46+
47+ Connections {
48+ target: LightDM.Greeter
49+
50+ onShowPrompt: {
51+ if (text.indexOf("PIN") >= 0) {
52+ lockscreen.alphaNumeric = false
53+ } else {
54+ lockscreen.alphaNumeric = true
55+ }
56+ lockscreen.placeholderText = text;
57+ }
58+ }
59+
60+ Rectangle {
61+ anchors.fill: parent
62+ anchors.leftMargin: lockscreen.width
63+ color: "lightgray"
64+
65+ Column {
66+ anchors.fill: parent
67+ anchors.margins: units.gu(1)
68+
69+ Row {
70+ CheckBox {
71+ id: pinPadCheckBox
72+ }
73+ Label {
74+ text: "Alphanumeric"
75+ anchors.verticalCenter: parent.verticalCenter
76+ }
77+ }
78+ Row {
79+ CheckBox {
80+ id: emergencyCheckBox
81+ }
82+ Label {
83+ text: "Emergency Call"
84+ anchors.verticalCenter: parent.verticalCenter
85+ }
86+ }
87+ Row {
88+ TextField {
89+ id: pinLengthTextField
90+ width: units.gu(7)
91+ text: "4"
92+ }
93+ Label {
94+ text: "PIN length"
95+ }
96+ }
97+ Label {
98+ id: pinLabel
99+ anchors.verticalCenter: parent.verticalCenter
100+ }
101+ Row {
102+ CheckBox {
103+ id: unlockedCheckBox
104+ }
105+ Label {
106+ text: "Unlocked signal"
107+ anchors.verticalCenter: parent.verticalCenter
108+ }
109+ }
110+ Button {
111+ text: "start auth (1234)"
112+ onClicked: LightDM.Greeter.authenticate("has-pin")
113+ }
114+ Button {
115+ text: "start auth (password)"
116+ onClicked: LightDM.Greeter.authenticate("has-password")
117+ }
118+ }
119+ }
120+
121+ UT.UnityTestCase {
122+ name: "Lockscreen"
123+ when: windowShown
124+
125+ function test_loading_data() {
126+ return [
127+ {tag: "numeric", alphanumeric: false, pinPadAvailable: true },
128+ {tag: "alphanumeric", alphanumeric: true, pinPadAvailable: false }
129+ ]
130+ }
131+
132+ function test_loading(data) {
133+ lockscreen.alphaNumeric = data.alphanumeric
134+ waitForRendering(lockscreen)
135+ if (data.pinPadAvailable) {
136+ compare(findChild(lockscreen, "pinPadButton8").text, "8", "Could not find number 8 on PinPad")
137+ } else {
138+ compare(findChild(lockscreen, "pinPadButton8"), undefined, "Could find number 8 on PinPad even though it should be only OSK")
139+ }
140+ }
141+
142+ function test_emergency_call_data() {
143+ return [
144+ {tag: "numeric", alphanumeric: false },
145+ {tag: "alphanumeric", alphanumeric: true }
146+ ]
147+ }
148+
149+ function test_emergency_call(data) {
150+ emergencyCheckBox.checked = false
151+ lockscreen.alphaNumeric = data.alphanumeric
152+ waitForRendering(lockscreen)
153+ var emergencyButton = findChild(lockscreen, "emergencyCallIcon")
154+ mouseClick(emergencyButton, units.gu(1), units.gu(1))
155+ tryCompare(emergencyCheckBox, "checked", true)
156+
157+ }
158+
159+ function test_labels_data() {
160+ return [
161+ {tag: "numeric", alphanumeric: false, placeholderText: "Please enter your PIN", username: "foobar" },
162+ {tag: "alphanumeric", alphanumeric: true, placeholderText: "Please enter your password", username: "Lola" }
163+ ]
164+ }
165+
166+ function test_labels(data) {
167+ lockscreen.alphaNumeric = data.alphanumeric
168+ lockscreen.placeholderText = data.placeholderText
169+ waitForRendering(lockscreen)
170+ compare(findChild(lockscreen, "pinentryField").placeholderText, data.placeholderText, "Placeholdertext is not what it should be")
171+ if (data.alphanumeric) {
172+ compare(findChild(lockscreen, "greeterLabel").text, "Hello " + data.username, "Greeter is not set correctly")
173+ }
174+ }
175+
176+
177+ function test_unlock_data() {
178+ return [
179+ {tag: "numeric", alphanumeric: false, username: "has-pin", password: "1234", unlockedSignal: true},
180+ {tag: "alphanumeric", alphanumeric: true, username: "has-password", password: "password", unlockedSignal: true},
181+ {tag: "numeric (wrong)", alphanumeric: false, username: "has-pin", password: "4321", unlockedSignal: false},
182+ {tag: "alphanumeric (wrong)", alphanumeric: true, username: "has-password", password: "drowssap", unlockedSignal: false},
183+ ]
184+ }
185+
186+ function test_unlock(data) {
187+ unlockedCheckBox.checked = false
188+ LightDM.Greeter.authenticate(data.username)
189+
190+ if (data.alphanumeric) {
191+ var inputField = findChild(lockscreen, "pinentryField")
192+ mouseClick(inputField, units.gu(1), units.gu(1))
193+ typeString(data.password)
194+ keyClick(Qt.Key_Enter)
195+ } else {
196+ for (var i = 0; i < data.password.length; ++i) {
197+ var character = data.password.charAt(i)
198+ var button = findChild(lockscreen, "pinPadButton" + character)
199+ mouseClick(button, units.gu(1), units.gu(1))
200+ }
201+ }
202+ tryCompare(unlockedCheckBox, "checked", data.unlockedSignal)
203+ }
204+ }
205+}