Merge lp:~unity-team/unity8/rtm-20150213 into lp:unity8/rtm-14.09

Proposed by Michał Sawicz
Status: Merged
Approved by: Michael Zanetti
Approved revision: 1425
Merged at revision: 1418
Proposed branch: lp:~unity-team/unity8/rtm-20150213
Merge into: lp:unity8/rtm-14.09
Diff against target: 2193 lines (+1176/-382)
27 files modified
debian/changelog (+26/-0)
plugins/Dash/CardAttributes.qml (+2/-0)
plugins/Dash/CardCreator.js (+5/-1)
qml/Components/Dialogs.qml (+4/-24)
qml/Components/PhysicalKeysMapper.qml (+113/-0)
qml/Components/VolumeKeyFilter.qml (+0/-63)
qml/Notifications/Notification.qml (+36/-6)
qml/Notifications/Notifications.qml (+2/-6)
qml/Panel/IndicatorItem.qml (+2/-0)
qml/Shell.qml (+12/-39)
tests/mocks/Unity/Notifications/CMakeLists.txt (+2/-1)
tests/mocks/Unity/Notifications/MockActionModel.cpp (+6/-4)
tests/mocks/Unity/Notifications/MockActionModel.h (+4/-2)
tests/mocks/Unity/Notifications/MockNotification.cpp (+156/-4)
tests/mocks/Unity/Notifications/MockNotification.h (+63/-5)
tests/mocks/Unity/Notifications/MockNotificationModel.cpp (+172/-0)
tests/mocks/Unity/Notifications/MockNotificationModel.h (+72/-0)
tests/mocks/Unity/Notifications/plugin.cpp (+5/-3)
tests/mocks/Unity/Notifications/plugin.h (+1/-2)
tests/qmltests/CMakeLists.txt (+1/-0)
tests/qmltests/Components/tst_PhysicalKeysMapper.qml (+119/-0)
tests/qmltests/Dash/tst_Card.qml (+9/-3)
tests/qmltests/Notifications/Notification.qml (+31/-0)
tests/qmltests/Notifications/tst_Notifications.qml (+271/-213)
tests/qmltests/Notifications/tst_OptionToggle.qml (+11/-6)
tests/qmltests/Notifications/tst_SwipeToAct.qml (+4/-0)
tests/qmltests/tst_ShellWithPin.qml (+47/-0)
To merge this branch: bzr merge lp:~unity-team/unity8/rtm-20150213
Reviewer Review Type Date Requested Status
Unity Team Pending
Review via email: mp+249680@code.launchpad.net
To post a comment you must log in.
lp:~unity-team/unity8/rtm-20150213 updated
1423. By Michał Sawicz

Add changelog

1424. By Josh Arenson

Create a PhysicalKeyMapper to handle all physical/hardware keys

In addition, change the key combination for taking a screenshot from (Volume Up + Volume Down) to (Power + Volume Down).

1425. By Michał Sawicz

Update changelog

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'debian/changelog'
--- debian/changelog 2015-01-22 20:47:05 +0000
+++ debian/changelog 2015-02-16 22:20:44 +0000
@@ -1,3 +1,29 @@
1unity8 (8.02+15.04.20150213~rtm-0ubuntu1) UNRELEASED; urgency=medium
2
3 [ Albert astals ]
4 * Preserve the aspect ratio for the card attribute images
5
6 [ Andrea Cimitan ]
7 * support background on horizontal cards with summary (LP: #1393008)
8 * Background needs to be specified to be visible in horizontal cards
9 (LP: #1411748)
10
11 [ Daniel d'Andrada ]
12 * Don't show() the lockscreen if it's already being shown
13
14 [ Joshua Arenson ]
15 * Create a PhysicalKeyMapper to handle all physical/hardware keys
16
17 [ Michał Sawicz ]
18 * Create a PhysicalKeyMapper to handle all physical/hardware keys
19
20 [ Mirco Müller ]
21 * Allow swipe-to-dismiss for contracted snap-decision notifications,
22 interactive notifications and ephemeral notifications. (LP:
23 #1355422, #1334855)
24
25 -- Michał Sawicz <michal.sawicz@canonical.com> Fri, 13 Feb 2015 17:23:13 +0100
26
1unity8 (8.02+15.04.20150122.2~rtm-0ubuntu1) 14.09; urgency=medium27unity8 (8.02+15.04.20150122.2~rtm-0ubuntu1) 14.09; urgency=medium
228
3 [ Michael Terry ]29 [ Michael Terry ]
430
=== modified file 'plugins/Dash/CardAttributes.qml'
--- plugins/Dash/CardAttributes.qml 2014-10-14 15:40:59 +0000
+++ plugins/Dash/CardAttributes.qml 2015-02-16 22:20:44 +0000
@@ -51,6 +51,8 @@
51 id: icon51 id: icon
52 height: units.gu(2)52 height: units.gu(2)
53 sets: ["actions", "status", "apps"]53 sets: ["actions", "status", "apps"]
54 // FIXME Workaround for bug https://bugs.launchpad.net/ubuntu/+source/ubuntu-ui-toolkit/+bug/1421293
55 width: implicitWidth > 0 && implicitHeight > 0 ? (implicitWidth / implicitHeight * height) : implicitWidth
54 source: "icon" in modelData && modelData["icon"] || ""56 source: "icon" in modelData && modelData["icon"] || ""
55 color: grid.color57 color: grid.color
56 }58 }
5759
=== modified file 'plugins/Dash/CardCreator.js'
--- plugins/Dash/CardCreator.js 2015-01-08 12:51:33 +0000
+++ plugins/Dash/CardCreator.js 2015-02-16 22:20:44 +0000
@@ -224,6 +224,8 @@
224224
225// %1 is used as extra anchors of emblemIcon225// %1 is used as extra anchors of emblemIcon
226// %2 is used as color of emblemIcon226// %2 is used as color of emblemIcon
227// FIXME The width code is a
228// Workaround for bug https://bugs.launchpad.net/ubuntu/+source/ubuntu-ui-toolkit/+bug/1421293
227var kEmblemIconCode = 'StatusIcon { \n\229var kEmblemIconCode = 'StatusIcon { \n\
228 id: emblemIcon; \n\230 id: emblemIcon; \n\
229 objectName: "emblemIcon"; \n\231 objectName: "emblemIcon"; \n\
@@ -235,6 +237,7 @@
235 source: cardData && cardData["emblem"] || ""; \n\237 source: cardData && cardData["emblem"] || ""; \n\
236 color: %2; \n\238 color: %2; \n\
237 height: source != "" ? titleLabel.font.pixelSize : 0; \n\239 height: source != "" ? titleLabel.font.pixelSize : 0; \n\
240 width: implicitWidth > 0 && implicitHeight > 0 ? (implicitWidth / implicitHeight * height) : implicitWidth; \n\
238 }\n';241 }\n';
239242
240// %1 is used as anchors of touchdown effect243// %1 is used as anchors of touchdown effect
@@ -320,7 +323,8 @@
320 var hasSummary = components["summary"] || false;323 var hasSummary = components["summary"] || false;
321 var artAndSummary = hasArt && hasSummary;324 var artAndSummary = hasArt && hasSummary;
322 var isHorizontal = template["card-layout"] === "horizontal";325 var isHorizontal = template["card-layout"] === "horizontal";
323 var hasBackground = !isHorizontal && (template["card-background"] || components["background"] || artAndSummary);326 var hasBackground = (!isHorizontal && (template["card-background"] || components["background"] || artAndSummary)) ||
327 (hasSummary && (template["card-background"] || components["background"]));
324 var hasTitle = components["title"] || false;328 var hasTitle = components["title"] || false;
325 var hasMascot = components["mascot"] || false;329 var hasMascot = components["mascot"] || false;
326 var hasEmblem = components["emblem"] && !(hasMascot && template["card-size"] === "small") || false;330 var hasEmblem = components["emblem"] && !(hasMascot && template["card-size"] === "small") || false;
327331
=== modified file 'qml/Components/Dialogs.qml'
--- qml/Components/Dialogs.qml 2015-01-14 09:09:08 +0000
+++ qml/Components/Dialogs.qml 2015-02-16 22:20:44 +0000
@@ -28,21 +28,12 @@
28 // in other applications like the welcome wizard.28 // in other applications like the welcome wizard.
29 readonly property string domain: "unity8"29 readonly property string domain: "unity8"
3030
31 function onPowerKeyPressed() {
32 // FIXME: event.isAutoRepeat is always false on Nexus 4.
33 // So we use powerKeyTimer.running to avoid the PowerOff key repeat
34 // https://launchpad.net/bugs/1349416
35 if (!powerKeyTimer.running) {
36 powerKeyTimer.restart();
37 }
38 }
39
40 function onPowerKeyReleased() {
41 powerKeyTimer.stop();
42 }
43
44 signal powerOffClicked();31 signal powerOffClicked();
4532
33 function showPowerDialog() {
34 d.showPowerDialog();
35 }
36
46 QtObject {37 QtObject {
47 id: d // private stuff38 id: d // private stuff
4839
@@ -56,17 +47,6 @@
56 }47 }
57 }48 }
5849
59 Timer {
60 id: powerKeyTimer
61 interval: 2000
62 repeat: false
63 triggeredOnStart: false
64
65 onTriggered: {
66 d.showPowerDialog();
67 }
68 }
69
70 Component {50 Component {
71 id: logoutDialog51 id: logoutDialog
72 Dialog {52 Dialog {
7353
=== added file 'qml/Components/PhysicalKeysMapper.qml'
--- qml/Components/PhysicalKeysMapper.qml 1970-01-01 00:00:00 +0000
+++ qml/Components/PhysicalKeysMapper.qml 2015-02-16 22:20:44 +0000
@@ -0,0 +1,113 @@
1/*
2 * Copyright (C) 2014-2015 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
17import QtQuick 2.0
18import Powerd 0.1
19
20/*!
21 \brief A mapper for the physical keys on the device
22
23 A mapper to handle events triggered by pressing physical keys on a device.
24 Keys included are
25 * Volume Decrease
26 * Volume Increase
27 * Power
28
29 This allows for handling the following events
30 * Power dialog
31 * Volume Decreases/Increases
32 * Screenshots
33
34*/
35
36Item {
37 id: root
38
39 signal powerKeyLongPressed;
40 signal volumeDownTriggered;
41 signal volumeUpTriggered;
42 signal screenshotTriggered;
43
44 QtObject {
45 id: d
46
47 property bool volumeDownKeyPressed: false
48 property bool volumeUpKeyPressed: false
49 property bool ignoreVolumeEvents: false
50 }
51
52 Timer {
53 id: powerKeyLongPressTimer
54
55 interval: 2000
56 onTriggered: root.powerKeyLongPressed();
57 }
58
59
60 function onKeyPressed(event) {
61 if ((event.key == Qt.Key_PowerDown || event.key == Qt.Key_PowerOff)
62 && !event.isAutoRepeat) {
63
64 // FIXME: We only consider power key presses if the screen is
65 // on because of bugs 1410830/1409003. The theory is that when
66 // those bugs are encountered, there is a >2s delay between the
67 // power press event and the power release event, which causes
68 // the shutdown dialog to appear on resume. So to avoid that
69 // symptom while we investigate the root cause, we simply won't
70 // initiate any dialogs when the screen is off.
71 if (Powerd.status === Powerd.On) {
72 powerKeyLongPressTimer.restart();
73 }
74 event.accepted = true;
75 } else if ((event.key == Qt.Key_MediaTogglePlayPause || event.key == Qt.Key_MediaPlay)
76 && !event.isAutoRepeat) {
77 event.accepted = callManager.handleMediaKey(false);
78 } else if (event.key == Qt.Key_VolumeDown) {
79 if (event.isAutoRepeat && !d.ignoreVolumeEvents) root.volumeDownTriggered();
80 else if (!event.isAutoRepeat) {
81 if (d.volumeUpKeyPressed) {
82 if (Powerd.status === Powerd.On) root.screenshotTriggered();
83 d.ignoreVolumeEvents = true;
84 }
85 d.volumeDownKeyPressed = true;
86 }
87 } else if (event.key == Qt.Key_VolumeUp) {
88 if (event.isAutoRepeat && !d.ignoreVolumeEvents) root.volumeUpTriggered();
89 else if (!event.isAutoRepeat) {
90 if (d.volumeDownKeyPressed) {
91 if (Powerd.status === Powerd.On) root.screenshotTriggered();
92 d.ignoreVolumeEvents = true;
93 }
94 d.volumeUpKeyPressed = true;
95 }
96 }
97 }
98
99 function onKeyReleased(event) {
100 if (event.key == Qt.Key_PowerDown || event.key == Qt.Key_PowerOff) {
101 powerKeyLongPressTimer.stop();
102 event.accepted = true;
103 } else if (event.key == Qt.Key_VolumeDown) {
104 if (!d.ignoreVolumeEvents) root.volumeDownTriggered();
105 d.volumeDownKeyPressed = false;
106 if (!d.volumeUpKeyPressed) d.ignoreVolumeEvents = false;
107 } else if (event.key == Qt.Key_VolumeUp) {
108 if (!d.ignoreVolumeEvents) root.volumeUpTriggered();
109 d.volumeUpKeyPressed = false;
110 if (!d.volumeDownKeyPressed) d.ignoreVolumeEvents = false;
111 }
112 }
113}
0114
=== removed file 'qml/Components/VolumeKeyFilter.qml'
--- qml/Components/VolumeKeyFilter.qml 2015-01-14 10:24:49 +0000
+++ qml/Components/VolumeKeyFilter.qml 1970-01-01 00:00:00 +0000
@@ -1,63 +0,0 @@
1/*
2 * Copyright (C) 2014 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
17import QtQuick 2.0
18/*!
19 \brief A filter for volume keys
20
21A filter which treats volume keys as single tri-state key with the states:
22VolumeUp Pressed, VolumeDown Pressed or Volume Up+Down pressed
23*/
24QtObject {
25 id: root
26
27 signal volumeUpPressed()
28 signal volumeDownPressed()
29 signal bothVolumeKeysPressed()
30
31 property bool volumeUpKeyPressed: false
32 property bool volumeDownKeyPressed: false
33 property bool aVolumeKeyWasReleased: true
34
35 function onKeyPressed(key) {
36 if (key == Qt.Key_VolumeUp)
37 volumeUpKeyPressed = true;
38 else if (key == Qt.Key_VolumeDown)
39 volumeDownKeyPressed = true;
40
41 if (volumeDownKeyPressed && volumeUpKeyPressed) {
42 //avoids sending a signal repeatedly if both keys are held
43 //instead one of the keys must have been previously released
44 if (aVolumeKeyWasReleased)
45 bothVolumeKeysPressed();
46 aVolumeKeyWasReleased = false;
47 } else if (volumeDownKeyPressed) {
48 volumeDownPressed();
49 } else if (volumeUpKeyPressed) {
50 volumeUpPressed();
51 }
52 }
53
54 function onKeyReleased(key) {
55 if (key == Qt.Key_VolumeUp) {
56 volumeUpKeyPressed = false;
57 aVolumeKeyWasReleased = true;
58 } else if (key == Qt.Key_VolumeDown) {
59 volumeDownKeyPressed = false;
60 aVolumeKeyWasReleased = true;
61 }
62 }
63}
640
=== modified file 'qml/Notifications/Notification.qml'
--- qml/Notifications/Notification.qml 2014-11-10 09:14:30 +0000
+++ qml/Notifications/Notification.qml 2015-02-16 22:20:44 +0000
@@ -40,6 +40,7 @@
40 property bool fullscreen: false40 property bool fullscreen: false
41 property int maxHeight41 property int maxHeight
42 property int margins42 property int margins
43 readonly property bool draggable: (type === Notification.SnapDecision && state === "contracted") || type === Notification.Interactive || type === Notification.Ephemeral
43 readonly property bool darkOnBright: panel.indicators.shown || type === Notification.SnapDecision44 readonly property bool darkOnBright: panel.indicators.shown || type === Notification.SnapDecision
44 readonly property color red: "#fc4949"45 readonly property color red: "#fc4949"
45 readonly property color green: "#3fb24f"46 readonly property color green: "#3fb24f"
@@ -52,7 +53,7 @@
52 implicitHeight: type !== Notification.PlaceHolder ? (fullscreen ? maxHeight : outterColumn.height - shapedBack.anchors.topMargin + contentSpacing * 2) : 053 implicitHeight: type !== Notification.PlaceHolder ? (fullscreen ? maxHeight : outterColumn.height - shapedBack.anchors.topMargin + contentSpacing * 2) : 0
5354
54 color: (type === Notification.Confirmation && notificationList.useModal && !greeter.shown) || darkOnBright ? sdLightGrey : Qt.rgba(0.132, 0.117, 0.109, 0.97)55 color: (type === Notification.Confirmation && notificationList.useModal && !greeter.shown) || darkOnBright ? sdLightGrey : Qt.rgba(0.132, 0.117, 0.109, 0.97)
55 opacity: 1 // FIXME: 1 because of LP: #1354406 workaround, has to be 0 really56 opacity: 1 - (x / notification.width) // FIXME: non-zero initially because of LP: #1354406 workaround, we want this to start at 0 upon creation eventually
5657
57 state: {58 state: {
58 var result = "";59 var result = "";
@@ -80,18 +81,28 @@
80 id: sound81 id: sound
81 objectName: "sound"82 objectName: "sound"
82 audioRole: MediaPlayer.alert83 audioRole: MediaPlayer.alert
83 source: hints["suppress-sound"] != "true" && hints["sound-file"] != undefined ? hints["sound-file"] : ""84 source: hints["suppress-sound"] !== "true" && hints["sound-file"] !== undefined ? hints["sound-file"] : ""
84 }85 }
8586
86 // FIXME: using onCompleted because of LP: #1354406 workaround, has to be onOpacityChanged really87 // FIXME: using onCompleted because of LP: #1354406 workaround, has to be onOpacityChanged really
87 Component.onCompleted: {88 Component.onCompleted: {
88 if (opacity == 1.0 && hints["suppress-sound"] != "true" && sound.source != "") {89 if (opacity == 1.0 && hints["suppress-sound"] !== "true" && sound.source !== "") {
89 sound.play();90 sound.play();
90 }91 }
91 }92 }
9293
94 Behavior on x {
95 id: normalXBehavior
96
97 enabled: draggable
98 UbuntuNumberAnimation {
99 duration: UbuntuAnimation.FastDuration
100 easing.type: Easing.OutBounce
101 }
102 }
103
93 onHintsChanged: {104 onHintsChanged: {
94 if (type === Notification.Confirmation && opacity == 1.0 && hints["suppress-sound"] != "true" && sound.source != "") {105 if (type === Notification.Confirmation && opacity == 1.0 && hints["suppress-sound"] !== "true" && sound.source !== "") {
95 sound.play();106 sound.play();
96 }107 }
97 }108 }
@@ -146,6 +157,12 @@
146 opacity: parent.opacity157 opacity: parent.opacity
147 }158 }
148159
160 onXChanged: {
161 if (draggable && notification.x > 0.75 * notification.width) {
162 notification.notification.close()
163 }
164 }
165
149 Item {166 Item {
150 id: contents167 id: contents
151 anchors.fill: fullscreen ? nonShapedBack : shapedBack168 anchors.fill: fullscreen ? nonShapedBack : shapedBack
@@ -169,7 +186,7 @@
169 actions: paths.actions186 actions: paths.actions
170 menuObjectPath: paths.menuObjectPath187 menuObjectPath: paths.menuObjectPath
171 onNameOwnerChanged: {188 onNameOwnerChanged: {
172 if (lastNameOwner != "" && nameOwner == "" && notification.notification != undefined) {189 if (lastNameOwner !== "" && nameOwner === "" && notification.notification !== undefined) {
173 notification.notification.close()190 notification.notification.close()
174 }191 }
175 lastNameOwner = nameOwner192 lastNameOwner = nameOwner
@@ -181,6 +198,12 @@
181198
182 anchors.fill: parent199 anchors.fill: parent
183 objectName: "interactiveArea"200 objectName: "interactiveArea"
201
202 drag.target: draggable ? notification : undefined
203 drag.axis: Drag.XAxis
204 drag.minimumX: 0
205 drag.maximumX: notification.width
206
184 onClicked: {207 onClicked: {
185 if (notification.type == Notification.Interactive) {208 if (notification.type == Notification.Interactive) {
186 notification.notification.invokeAction(actionRepeater.itemAt(0).actionId)209 notification.notification.invokeAction(actionRepeater.itemAt(0).actionId)
@@ -188,6 +211,13 @@
188 notificationList.currentIndex = index;211 notificationList.currentIndex = index;
189 }212 }
190 }213 }
214 onReleased: {
215 if (notification.x < notification.width / 2) {
216 notification.x = 0
217 } else {
218 notification.x = notification.width
219 }
220 }
191 }221 }
192222
193 Column {223 Column {
@@ -447,7 +477,7 @@
447 right: parent.right477 right: parent.right
448 margins: contentSpacing478 margins: contentSpacing
449 }479 }
450 visible: notification.type == Notification.SnapDecision && actionRepeater.count > 0 && !oneOverTwoCase.visible480 visible: notification.type === Notification.SnapDecision && actionRepeater.count > 0 && !oneOverTwoCase.visible
451 spacing: units.gu(2)481 spacing: units.gu(2)
452 layoutDirection: Qt.RightToLeft482 layoutDirection: Qt.RightToLeft
453483
454484
=== modified file 'qml/Notifications/Notifications.qml'
--- qml/Notifications/Notifications.qml 2014-10-30 21:42:32 +0000
+++ qml/Notifications/Notifications.qml 2015-02-16 22:20:44 +0000
@@ -41,15 +41,11 @@
41 property bool topmostIsFullscreen: false41 property bool topmostIsFullscreen: false
42 spacing: topmostIsFullscreen ? 0 : units.gu(.5)42 spacing: topmostIsFullscreen ? 0 : units.gu(.5)
4343
44 // FIXME: This doesn't make any sense and results in a binding loop44 currentIndex: count > 1 ? 1 : -1
45 currentIndex: (currentIndex < 1 && count > 1) ? 1 : -1
4645
47 delegate: Notification {46 delegate: Notification {
48 objectName: "notification" + index47 objectName: "notification" + index
49 anchors {48 width: parent.width
50 left: parent.left
51 right: parent.right
52 }
53 type: model.type49 type: model.type
54 hints: model.hints50 hints: model.hints
55 iconSource: model.icon51 iconSource: model.icon
5652
=== modified file 'qml/Panel/IndicatorItem.qml'
--- qml/Panel/IndicatorItem.qml 2015-01-14 10:17:12 +0000
+++ qml/Panel/IndicatorItem.qml 2015-02-16 22:20:44 +0000
@@ -95,6 +95,8 @@
95 id: itemImage95 id: itemImage
96 objectName: "icon"+index96 objectName: "icon"+index
97 height: iconHeight97 height: iconHeight
98 // FIXME Workaround for bug https://bugs.launchpad.net/ubuntu/+source/ubuntu-ui-toolkit/+bug/1421293
99 width: implicitWidth > 0 && implicitHeight > 0 ? (implicitWidth / implicitHeight * height) : implicitWidth;
98 source: modelData100 source: modelData
99 sets: ["status", "actions"]101 sets: ["status", "actions"]
100 color: root.color102 color: root.color
101103
=== modified file 'qml/Shell.qml'
--- qml/Shell.qml 2015-01-22 02:41:55 +0000
+++ qml/Shell.qml 2015-02-16 22:20:44 +0000
@@ -136,6 +136,15 @@
136 objectName: "dashCommunicator"136 objectName: "dashCommunicator"
137 }137 }
138138
139 PhysicalKeysMapper {
140 id: physicalKeysMapper
141
142 onPowerKeyLongPressed: dialogs.showPowerDialog()
143 onVolumeDownTriggered: volumeControl.volumeDown();
144 onVolumeUpTriggered: volumeControl.volumeUp();
145 onScreenshotTriggered: screenGrabber.capture();
146 }
147
139 ScreenGrabber {148 ScreenGrabber {
140 id: screenGrabber149 id: screenGrabber
141 z: edgeDemo.z + 10150 z: edgeDemo.z + 10
@@ -148,45 +157,9 @@
148 value: launcher.shown || launcher.dashSwipe157 value: launcher.shown || launcher.dashSwipe
149 }158 }
150159
151 VolumeKeyFilter {
152 id: volumeKeyFilter
153 onVolumeDownPressed: volumeControl.volumeDown()
154 onVolumeUpPressed: volumeControl.volumeUp()
155 onBothVolumeKeysPressed: screenGrabber.capture()
156 }
157
158 WindowKeysFilter {160 WindowKeysFilter {
159 Keys.onPressed: {161 Keys.onPressed: physicalKeysMapper.onKeyPressed(event);
160 // Nokia earpieces give TogglePlayPause, while the iPhone's earpiece gives Play162 Keys.onReleased: physicalKeysMapper.onKeyReleased(event);
161 if (event.key == Qt.Key_MediaTogglePlayPause || event.key == Qt.Key_MediaPlay) {
162 event.accepted = callManager.handleMediaKey(false);
163 } else if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
164 // FIXME: We only consider power key presses if the screen is
165 // on because of bugs 1410830/1409003. The theory is that when
166 // those bugs are encountered, there is a >2s delay between the
167 // power press event and the power release event, which causes
168 // the shutdown dialog to appear on resume. So to avoid that
169 // symptom while we investigate the root cause, we simply won't
170 // initiate any dialogs when the screen is off.
171 if (Powerd.status === Powerd.On) {
172 dialogs.onPowerKeyPressed();
173 }
174 event.accepted = true;
175 } else {
176 volumeKeyFilter.onKeyPressed(event.key);
177 event.accepted = false;
178 }
179 }
180
181 Keys.onReleased: {
182 if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
183 dialogs.onPowerKeyReleased();
184 event.accepted = true;
185 } else {
186 volumeKeyFilter.onKeyReleased(event.key);
187 event.accepted = false;
188 }
189 }
190 }163 }
191164
192 Item {165 Item {
@@ -364,7 +337,7 @@
364 onShownChanged: if (shown) greeter.lockedApp = ""337 onShownChanged: if (shown) greeter.lockedApp = ""
365338
366 function maybeShow() {339 function maybeShow() {
367 if (!shell.forcedUnlock) {340 if (!shell.forcedUnlock && !shown) {
368 showNow();341 showNow();
369 }342 }
370 }343 }
371344
=== modified file 'tests/mocks/Unity/Notifications/CMakeLists.txt'
--- tests/mocks/Unity/Notifications/CMakeLists.txt 2014-11-05 00:17:50 +0000
+++ tests/mocks/Unity/Notifications/CMakeLists.txt 2015-02-16 22:20:44 +0000
@@ -4,7 +4,8 @@
44
5set(MockNotificationsPlugin_SOURCES5set(MockNotificationsPlugin_SOURCES
6 plugin.cpp6 plugin.cpp
7 MockNotificationTypes.cpp7 MockNotification.cpp
8 MockNotificationModel.cpp
8 MockActionModel.cpp9 MockActionModel.cpp
9)10)
1011
1112
=== modified file 'tests/mocks/Unity/Notifications/MockActionModel.cpp'
--- tests/mocks/Unity/Notifications/MockActionModel.cpp 2014-11-05 00:17:50 +0000
+++ tests/mocks/Unity/Notifications/MockActionModel.cpp 2015-02-16 22:20:44 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright 2014 Canonical Ltd.2 * Copyright 2015 Canonical Ltd.
3 *3 *
4 * This program is free software; you can redistribute it and/or modify4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by5 * it under the terms of the GNU Lesser General Public License as published by
@@ -25,8 +25,6 @@
25};25};
2626
27ActionModel::ActionModel(QObject *parent) : QStringListModel(parent), p(new ActionModelPrivate) {27ActionModel::ActionModel(QObject *parent) : QStringListModel(parent), p(new ActionModelPrivate) {
28 insertAction("ok_id", "Ok");
29 insertAction("cancel_id", "Cancel");
30}28}
3129
32ActionModel::~ActionModel() {30ActionModel::~ActionModel() {
@@ -66,7 +64,11 @@
66 return data(index(row, 0), role);64 return data(index(row, 0), role);
67}65}
6866
69void ActionModel::insertAction(const QString &id, const QString &label) {67void ActionModel::append(const QString &id, const QString &label) {
70 p->ids.push_back(id);68 p->ids.push_back(id);
71 p->labels.push_back(label);69 p->labels.push_back(label);
72}70}
71
72int ActionModel::getCount() const {
73 return p->labels.size();
74}
7375
=== modified file 'tests/mocks/Unity/Notifications/MockActionModel.h'
--- tests/mocks/Unity/Notifications/MockActionModel.h 2014-11-05 00:17:50 +0000
+++ tests/mocks/Unity/Notifications/MockActionModel.h 2015-02-16 22:20:44 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright 2014 Canonical Ltd.2 * Copyright 2015 Canonical Ltd.
3 *3 *
4 * This program is free software; you can redistribute it and/or modify4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by5 * it under the terms of the GNU Lesser General Public License as published by
@@ -26,6 +26,7 @@
2626
27class ActionModel : public QStringListModel {27class ActionModel : public QStringListModel {
28 Q_OBJECT28 Q_OBJECT
29 Q_PROPERTY(int count READ getCount)
2930
30public:31public:
31 ActionModel(QObject *parent=nullptr);32 ActionModel(QObject *parent=nullptr);
@@ -42,7 +43,8 @@
42 };43 };
43 Q_INVOKABLE QVariant data(int row, int role) const;44 Q_INVOKABLE QVariant data(int row, int role) const;
4445
45 void insertAction(const QString &id, const QString &label);46 Q_INVOKABLE void append(const QString &id, const QString &label);
47 int getCount() const;
4648
47private:49private:
48 QScopedPointer<ActionModelPrivate> p;50 QScopedPointer<ActionModelPrivate> p;
4951
=== renamed file 'tests/mocks/Unity/Notifications/MockNotificationTypes.cpp' => 'tests/mocks/Unity/Notifications/MockNotification.cpp'
--- tests/mocks/Unity/Notifications/MockNotificationTypes.cpp 2014-11-05 00:17:50 +0000
+++ tests/mocks/Unity/Notifications/MockNotification.cpp 2015-02-16 22:20:44 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright 2014 Canonical Ltd.2 * Copyright 2015 Canonical Ltd.
3 *3 *
4 * This program is free software; you can redistribute it and/or modify4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by5 * it under the terms of the GNU Lesser General Public License as published by
@@ -17,10 +17,162 @@
17 * Mirco Mueller <mirco.mueller@canonical.com>17 * Mirco Mueller <mirco.mueller@canonical.com>
18 */18 */
1919
20#include "MockNotificationTypes.h"20#include "MockNotification.h"
2121
22MockNotification::MockNotification(QObject *parent) : QObject(parent) {22#include <QDebug>
23
24struct MockNotificationPrivate {
25 int id;
26 QString summary;
27 QString body;
28 int value;
29 MockNotification::Type type;
30 QString icon;
31 QString secondaryIcon;
32 QStringList actions;
33 ActionModel* actionsModel;
34 QVariantMap hints;
35};
36
37MockNotification::MockNotification(QObject *parent) : QObject(parent), p(new MockNotificationPrivate()) {
38 p->actionsModel = new ActionModel();
23}39}
2440
25MockNotification::~MockNotification() {41MockNotification::~MockNotification() {
26}42}
43
44QString MockNotification::getSummary() const {
45 return p->summary;
46}
47
48void MockNotification::setSummary(const QString &summary) {
49 if(p->summary != summary) {
50 p->summary = summary;
51 Q_EMIT summaryChanged(p->summary);
52 Q_EMIT dataChanged(p->id);
53 }
54}
55
56QString MockNotification::getBody() const {
57 return p->body;
58}
59
60void MockNotification::setBody(const QString &body) {
61 if(p->body != body) {
62 p->body = body;
63 Q_EMIT bodyChanged(p->body);
64 Q_EMIT dataChanged(p->id);
65 }
66}
67
68int MockNotification::getID() const {
69 return p->id;
70}
71
72void MockNotification::setID(const int id) {
73 p->id = id;
74}
75
76int MockNotification::getValue() const {
77 return p->value;
78}
79
80void MockNotification::setValue(int value) {
81 if(p->value != value) {
82 p->value = value;
83 Q_EMIT valueChanged(p->value);
84 Q_EMIT dataChanged(p->id);
85 }
86}
87
88QString MockNotification::getIcon() const {
89 return p->icon;
90}
91
92void MockNotification::setIcon(const QString &icon) {
93 if (icon.startsWith(" ") || icon.size() == 0) {
94 p->icon = nullptr;
95 }
96 else {
97 p->icon = icon;
98
99 if (icon.indexOf("/") == -1) {
100 p->icon.prepend("image://theme/");
101 }
102 }
103
104 Q_EMIT iconChanged(p->icon);
105 Q_EMIT dataChanged(p->id);
106}
107
108QString MockNotification::getSecondaryIcon() const {
109 return p->secondaryIcon;
110}
111
112void MockNotification::setSecondaryIcon(const QString &secondaryIcon) {
113 if (secondaryIcon.startsWith(" ") || secondaryIcon.size() == 0) {
114 p->secondaryIcon = nullptr;
115 }
116 else {
117 p->secondaryIcon = secondaryIcon;
118
119 if (secondaryIcon.indexOf("/") == -1) {
120 p->secondaryIcon.prepend("image://theme/");
121 }
122 }
123
124 Q_EMIT secondaryIconChanged(p->secondaryIcon);
125 Q_EMIT dataChanged(p->id);
126}
127
128MockNotification::Type MockNotification::getType() const {
129 return p->type;
130}
131
132void MockNotification::setType(Type type) {
133 if(p->type != type) {
134 p->type = type;
135 Q_EMIT typeChanged(p->type);
136 }
137}
138
139ActionModel* MockNotification::getActions() const {
140 return p->actionsModel;
141}
142
143void MockNotification::setActions(const QStringList &actions) {
144 if(p->actions != actions) {
145 p->actions = actions;
146 Q_EMIT actionsChanged(p->actions);
147
148 for (int i = 0; i < p->actions.size(); i += 2) {
149 p->actionsModel->append(p->actions[i], p->actions[i+1]);
150 }
151 }
152}
153
154QVariantMap MockNotification::getHints() const {
155 return p->hints;
156}
157
158void MockNotification::setHints(const QVariantMap& hints) {
159 if (p->hints != hints) {
160 p->hints = hints;
161 Q_EMIT hintsChanged(p->hints);
162 }
163}
164
165void MockNotification::invokeAction(const QString &action) {
166 for(int i=0; i<p->actions.size(); i++) {
167 if(p->actions[i] == action) {
168 Q_EMIT actionInvoked(action);
169 qDebug() << "Info: invoked action" << action;
170 return;
171 }
172 }
173 fprintf(stderr, "Error: tried to invoke action not in actionList.\n");
174}
175
176void MockNotification::close() {
177 Q_EMIT completed(p->id);
178}
27179
=== renamed file 'tests/mocks/Unity/Notifications/MockNotificationTypes.h' => 'tests/mocks/Unity/Notifications/MockNotification.h'
--- tests/mocks/Unity/Notifications/MockNotificationTypes.h 2014-11-05 00:17:50 +0000
+++ tests/mocks/Unity/Notifications/MockNotification.h 2015-02-16 22:20:44 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright 2014 Canonical Ltd.2 * Copyright 2015 Canonical Ltd.
3 *3 *
4 * This program is free software; you can redistribute it and/or modify4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by5 * it under the terms of the GNU Lesser General Public License as published by
@@ -17,20 +17,78 @@
17 * Mirco Mueller <mirco.mueller@canonical.com>17 * Mirco Mueller <mirco.mueller@canonical.com>
18 */18 */
1919
20#ifndef MOCK_NOTIFICATION_TYPES_H20#ifndef MOCK_NOTIFICATION_H
21#define MOCK_NOTIFICATION_TYPES_H21#define MOCK_NOTIFICATION_H
2222
23#include "MockActionModel.h"
23#include <QObject>24#include <QObject>
25#include <QString>
26#include <QStringList>
27#include <QScopedPointer>
28
29struct MockNotificationPrivate;
2430
25class MockNotification : public QObject {31class MockNotification : public QObject {
26 Q_OBJECT32 Q_OBJECT
27 Q_ENUMS(Type)33 Q_ENUMS(Type)
34 Q_PROPERTY(QString summary READ getSummary WRITE setSummary NOTIFY summaryChanged)
35 Q_PROPERTY(QString body READ getBody WRITE setBody NOTIFY bodyChanged)
36 Q_PROPERTY(int nid READ getID WRITE setID NOTIFY idChanged)
37 Q_PROPERTY(int value READ getValue WRITE setValue NOTIFY valueChanged)
38 Q_PROPERTY(QString icon READ getIcon WRITE setIcon NOTIFY iconChanged)
39 Q_PROPERTY(QString secondaryIcon READ getSecondaryIcon WRITE setSecondaryIcon NOTIFY secondaryIconChanged)
40 Q_PROPERTY(Type type READ getType WRITE setType NOTIFY typeChanged)
41 Q_PROPERTY(QStringList rawActions WRITE setActions)
42 Q_PROPERTY(ActionModel* actions READ getActions NOTIFY actionsChanged)
43 Q_PROPERTY(QVariantMap hints READ getHints WRITE setHints NOTIFY hintsChanged)
44
45private:
46 QScopedPointer<MockNotificationPrivate> p;
47
48public:
49 enum Urgency { Low, Normal, Critical };
50 enum Type { PlaceHolder, Confirmation, Ephemeral, Interactive, SnapDecision };
51
52Q_SIGNALS:
53 void summaryChanged(const QString &summary);
54 void bodyChanged(const QString &body);
55 void idChanged(const int id);
56 void valueChanged(int value);
57 void iconChanged(const QString &icon);
58 void secondaryIconChanged(const QString &secondaryIcon);
59 void typeChanged(Type type);
60 void actionsChanged(const QStringList &actions);
61 void hintsChanged(const QVariantMap& hints);
62
63 void dataChanged(int nid);
64 void completed(int nid);
65 void actionInvoked(const QString &action);
2866
29public:67public:
30 MockNotification(QObject *parent=nullptr);68 MockNotification(QObject *parent=nullptr);
31 virtual ~MockNotification();69 virtual ~MockNotification();
3270
33 enum Type { PlaceHolder, Confirmation, Ephemeral, Interactive, SnapDecision };71 QString getSummary() const;
72 void setSummary(const QString &summary);
73 QString getBody() const;
74 void setBody(const QString &body);
75 int getID() const;
76 void setID(const int id);
77 int getValue() const;
78 void setValue(int value);
79 QString getIcon() const;
80 void setIcon(const QString &icon);
81 QString getSecondaryIcon() const;
82 void setSecondaryIcon(const QString &secondaryIcon);
83 Type getType() const;
84 void setType(Type type);
85 ActionModel* getActions() const;
86 void setActions(const QStringList &actions);
87 QVariantMap getHints() const;
88 void setHints(const QVariantMap& hints);
89
90 Q_INVOKABLE void invokeAction(const QString &action);
91 Q_INVOKABLE void close();
34};92};
3593
36#endif // MOCK_NOTIFICATION_TYPES_H94#endif // MOCK_NOTIFICATION_H
3795
=== added file 'tests/mocks/Unity/Notifications/MockNotificationModel.cpp'
--- tests/mocks/Unity/Notifications/MockNotificationModel.cpp 1970-01-01 00:00:00 +0000
+++ tests/mocks/Unity/Notifications/MockNotificationModel.cpp 2015-02-16 22:20:44 +0000
@@ -0,0 +1,172 @@
1/*
2 * Copyright 2015 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authors:
17 * Mirco Mueller <mirco.mueller@canonical.com>
18 */
19
20#include "MockNotificationModel.h"
21#include "MockNotification.h"
22
23#include <unity/shell/notifications/ModelInterface.h>
24
25#include <QTimer>
26#include <QList>
27#include <QVector>
28#include <QMap>
29#include <QStringListModel>
30#include <QQmlEngine>
31
32using namespace unity::shell::notifications;
33
34MockNotificationModel::MockNotificationModel(QObject *parent) : QAbstractListModel(parent) {
35}
36
37MockNotificationModel::~MockNotificationModel() {
38}
39
40int MockNotificationModel::rowCount(const QModelIndex &parent) const {
41 return m_queue.size();
42}
43
44int MockNotificationModel::getCount() const {
45 return m_queue.size();
46}
47
48QVariant MockNotificationModel::data(const QModelIndex &index, int role) const {
49 if (!index.isValid())
50 return QVariant();
51
52 switch(role) {
53 case ModelInterface::RoleType:
54 return QVariant(m_queue[index.row()]->getType());
55
56 case ModelInterface::RoleId:
57 return QVariant(m_queue[index.row()]->getID());
58
59 case ModelInterface::RoleSummary:
60 return QVariant(m_queue[index.row()]->getSummary());
61
62 case ModelInterface::RoleBody:
63 return QVariant(m_queue[index.row()]->getBody());
64
65 case ModelInterface::RoleValue:
66 return QVariant(m_queue[index.row()]->getValue());
67
68 case ModelInterface::RoleIcon:
69 return QVariant(m_queue[index.row()]->getIcon());
70
71 case ModelInterface::RoleSecondaryIcon:
72 return QVariant(m_queue[index.row()]->getSecondaryIcon());
73
74 case ModelInterface::RoleActions:
75 return QVariant::fromValue(m_queue[index.row()]->getActions());
76
77 case ModelInterface::RoleHints:
78 return QVariant(m_queue[index.row()]->getHints());
79
80 case ModelInterface::RoleNotification:
81 return QVariant::fromValue(m_queue[index.row()]);
82
83 default:
84 return QVariant();
85 }
86}
87
88void MockNotificationModel::append(MockNotification* n) {
89 int location = m_queue.size();
90 QModelIndex insertionPoint = QModelIndex();
91 beginInsertRows(insertionPoint, location, location);
92 m_queue.insert(location, n);
93 endInsertRows();
94}
95
96MockNotification* MockNotificationModel::getNotification(int id) const {
97 for(int i=0; i < m_queue.size(); i++) {
98 if(m_queue[i]->getID() == id) {
99 return m_queue[i];
100 }
101 }
102
103 return nullptr;
104}
105
106void MockNotificationModel::remove(const int id) {
107 for(int i = 0; i < m_queue.size(); i++) {
108 if(m_queue[i]->getID() == id) {
109 removeInternal(i);
110 return;
111 }
112 }
113}
114
115void MockNotificationModel::removeSecond() {
116 if(m_queue.size() < 2)
117 return;
118 removeInternal(1);
119}
120
121void MockNotificationModel::removeInternal(int loc) {
122 QModelIndex deletePoint = QModelIndex();
123 beginRemoveRows(deletePoint, loc, loc);
124 m_queue.erase(m_queue.begin() + loc);
125 endRemoveRows();
126}
127
128MockNotification* MockNotificationModel::getRaw(const int notificationId) const {
129 for(int i = 0; i < m_queue.size(); i++) {
130 if(m_queue[i]->getID() == notificationId) {
131 MockNotification* n = m_queue[i];
132 return n;
133 }
134 }
135
136 return nullptr;
137}
138
139int MockNotificationModel::queued() const {
140 return m_queue.size();
141}
142
143QHash<int, QByteArray> MockNotificationModel::roleNames() const {
144 QHash<int, QByteArray> roles;
145
146 roles.insert(ModelInterface::RoleType, "type");
147 roles.insert(ModelInterface::RoleUrgency, "urgency");
148 roles.insert(ModelInterface::RoleId, "id");
149 roles.insert(ModelInterface::RoleSummary, "summary");
150 roles.insert(ModelInterface::RoleBody, "body");
151 roles.insert(ModelInterface::RoleValue, "value");
152 roles.insert(ModelInterface::RoleIcon, "icon");
153 roles.insert(ModelInterface::RoleSecondaryIcon, "secondaryIcon");
154 roles.insert(ModelInterface::RoleActions, "actions");
155 roles.insert(ModelInterface::RoleHints, "hints");
156 roles.insert(ModelInterface::RoleNotification, "notification");
157
158 return roles;
159}
160
161void MockNotificationModel::onCompleted(int id) {
162 remove(id);
163}
164
165void MockNotificationModel::onDataChanged(int id) {
166 for(int i = 0; i < m_queue.size(); i++) {
167 if(m_queue[i]->getID() == id) {
168 Q_EMIT dataChanged(index(i, 0), index(i, 0));
169 break;
170 }
171 }
172}
0173
=== added file 'tests/mocks/Unity/Notifications/MockNotificationModel.h'
--- tests/mocks/Unity/Notifications/MockNotificationModel.h 1970-01-01 00:00:00 +0000
+++ tests/mocks/Unity/Notifications/MockNotificationModel.h 2015-02-16 22:20:44 +0000
@@ -0,0 +1,72 @@
1/*
2 * Copyright 2015 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authors:
17 * Mirco Mueller <mirco.mueller@canonical.com>
18 */
19
20#ifndef MOCK_NOTIFICATION_MODEL_H
21#define MOCK_NOTIFICATION_MODEL_H
22
23#include <QAbstractListModel>
24#include <QSharedPointer>
25#include <QScopedPointer>
26#include "MockNotification.h"
27
28class MockNotification;
29
30class MockNotificationModel : public QAbstractListModel {
31 Q_OBJECT
32 Q_PROPERTY(int count READ getCount)
33
34public:
35 MockNotificationModel(QObject *parent=nullptr);
36 virtual ~MockNotificationModel();
37
38 virtual int rowCount(const QModelIndex &parent) const;
39 virtual QVariant data(const QModelIndex &index, int role) const;
40 virtual QHash<int, QByteArray> roleNames() const;
41
42 Q_INVOKABLE void append(MockNotification* n);
43 MockNotification* getNotification(int id) const;
44
45 // getRaw() is only meant to be used from QML, since QML cannot handle
46 // QSharedPointers... on C++-side only use getNotification()
47 Q_INVOKABLE MockNotification* getRaw(const int notificationId) const;
48
49 Q_INVOKABLE int queued() const;
50 Q_INVOKABLE void remove(const int id);
51 Q_INVOKABLE void removeSecond();
52
53 int getCount() const;
54
55Q_SIGNALS:
56 void actionInvoked(const QString &action);
57
58public Q_SLOTS:
59 void onCompleted(int id);
60
61private Q_SLOTS:
62 void onDataChanged(int id);
63
64Q_SIGNALS:
65 void queueSizeChanged(int newSize);
66
67private:
68 QList<MockNotification*> m_queue;
69 void removeInternal(int loc);
70};
71
72#endif
073
=== modified file 'tests/mocks/Unity/Notifications/plugin.cpp'
--- tests/mocks/Unity/Notifications/plugin.cpp 2014-11-05 00:17:50 +0000
+++ tests/mocks/Unity/Notifications/plugin.cpp 2015-02-16 22:20:44 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright 2014 Canonical Ltd.2 * Copyright 2015 Canonical Ltd.
3 *3 *
4 * This program is free software; you can redistribute it and/or modify4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by5 * it under the terms of the GNU Lesser General Public License as published by
@@ -19,13 +19,15 @@
1919
20#include "plugin.h"20#include "plugin.h"
21#include "MockActionModel.h"21#include "MockActionModel.h"
22#include "MockNotificationTypes.h"22#include "MockNotification.h"
23#include "MockNotificationModel.h"
2324
24#include <QtQml/qqml.h>25#include <QtQml/qqml.h>
2526
26void TestNotificationPlugin::registerTypes(const char* uri)27void TestNotificationPlugin::registerTypes(const char* uri)
27{28{
28 // @uri Unity.Notifications29 // @uri Unity.Notifications
29 qmlRegisterUncreatableType<MockNotification>(uri, 1, 0, "Notification", "Notification objects can only be created by the plugin");30 qmlRegisterType<MockNotification>(uri, 1, 0, "Notification");
31 qmlRegisterType<MockNotificationModel>(uri, 1, 0, "NotificationModel");
30 qmlRegisterType<ActionModel>(uri, 1, 0, "ActionModel");32 qmlRegisterType<ActionModel>(uri, 1, 0, "ActionModel");
31}33}
3234
=== modified file 'tests/mocks/Unity/Notifications/plugin.h'
--- tests/mocks/Unity/Notifications/plugin.h 2014-11-05 00:17:50 +0000
+++ tests/mocks/Unity/Notifications/plugin.h 2015-02-16 22:20:44 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright 2014 Canonical Ltd.2 * Copyright 2015 Canonical Ltd.
3 *3 *
4 * This program is free software; you can redistribute it and/or modify4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by5 * it under the terms of the GNU Lesser General Public License as published by
@@ -17,7 +17,6 @@
17 * Mirco Mueller <mirco.mueller@canonical.com>17 * Mirco Mueller <mirco.mueller@canonical.com>
18 */18 */
1919
20
21#ifndef TESTNOTIFICATION_PLUGIN_H20#ifndef TESTNOTIFICATION_PLUGIN_H
22#define TESTNOTIFICATION_PLUGIN_H21#define TESTNOTIFICATION_PLUGIN_H
2322
2423
=== modified file 'tests/qmltests/CMakeLists.txt'
--- tests/qmltests/CMakeLists.txt 2015-01-14 10:17:12 +0000
+++ tests/qmltests/CMakeLists.txt 2015-02-16 22:20:44 +0000
@@ -13,6 +13,7 @@
13set(qmltest_DEFAULT_NO_ADD_TEST FALSE)13set(qmltest_DEFAULT_NO_ADD_TEST FALSE)
14set(qmltest_DEFAULT_PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=minimal")14set(qmltest_DEFAULT_PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=minimal")
15add_qml_test(utils/Unity/Test UnityTest)15add_qml_test(utils/Unity/Test UnityTest)
16add_qml_test(Components PhysicalKeysMapper)
1617
17set(qmltest_DEFAULT_TARGETS qmluitests)18set(qmltest_DEFAULT_TARGETS qmluitests)
18set(qmltest_DEFAULT_NO_ADD_TEST TRUE)19set(qmltest_DEFAULT_NO_ADD_TEST TRUE)
1920
=== added file 'tests/qmltests/Components/tst_PhysicalKeysMapper.qml'
--- tests/qmltests/Components/tst_PhysicalKeysMapper.qml 1970-01-01 00:00:00 +0000
+++ tests/qmltests/Components/tst_PhysicalKeysMapper.qml 2015-02-16 22:20:44 +0000
@@ -0,0 +1,119 @@
1/*
2 * Copyright (C) 2014-2015 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
17import QtQuick 2.0
18import QtTest 1.0
19import "../../../qml/Components"
20
21TestCase {
22 name: "PhysicalKeysMapper"
23
24 Loader {
25 // Using a Loader here to make sure mapper state is coherent
26 // regardless of unmatched KeyPress and KeyRelease events
27 id: loader
28 active: false
29 sourceComponent: PhysicalKeysMapper { }
30 }
31
32 SignalSpy {
33 id: powerSpy
34 target: loader.item
35 signalName: "powerKeyLongPressed"
36 }
37
38 SignalSpy {
39 id: volumeDownSpy
40 target: loader.item
41 signalName: "volumeDownTriggered"
42 }
43
44 SignalSpy {
45 id: volumeUpSpy
46 target: loader.item
47 signalName: "volumeUpTriggered"
48 }
49
50 SignalSpy {
51 id: screenshotSpy
52 target: loader.item
53 signalName: "screenshotTriggered"
54 }
55
56 function init() {
57 loader.active = true;
58 tryCompare(loader.status == Loader.Ready);
59 }
60
61 function cleanup() {
62 loader.active = false;
63 powerSpy.clear();
64 volumeDownSpy.clear();
65 volumeUpSpy.clear();
66 screenshotSpy.clear();
67 }
68
69 function test_LongPressPowerButton() {
70 loader.item.onKeyPressed({ key: Qt.Key_PowerDown });
71
72 expectFailContinue("", "Power signal should not be emitted within a second");
73 powerSpy.wait(1000);
74 powerSpy.clear();
75
76 loader.item.onKeyPressed({ key: Qt.Key_PowerDown, isAutoRepeat: true });
77 powerSpy.wait(1500);
78 }
79
80 function test_ScreenshotButtons_data() {
81 return [
82 { tag: "UpFirst", first: Qt.Key_VolumeUp, second: Qt.Key_VolumeDown },
83 { tag: "DownFirst", first: Qt.Key_VolumeDown, second: Qt.Key_VolumeUp },
84 ];
85 }
86
87 function test_ScreenshotButtons(data) {
88 loader.item.onKeyPressed({ key: data.first });
89 loader.item.onKeyPressed({ key: data.second });
90 screenshotSpy.wait();
91 loader.item.onKeyReleased({ key: data.first });
92 loader.item.onKeyReleased({ key: data.second });
93 expectFailContinue("", "VolumeUp signal should not fire");
94 volumeUpSpy.wait(100);
95 expectFailContinue("", "VolumeDown signal should not fire");
96 volumeDownSpy.wait(100);
97 }
98
99 function test_VolumeButton_data() {
100 return [
101 { tag: "Down", key: Qt.Key_VolumeDown, spy: volumeDownSpy },
102 { tag: "Up", key: Qt.Key_VolumeUp, spy: volumeUpSpy },
103 ];
104 }
105
106 function test_VolumeButton(data) {
107 loader.item.onKeyPressed({ key: data.key });
108 expectFailContinue("", "Signal should not fire on press");
109 data.spy.wait(100);
110 data.spy.clear();
111
112 loader.item.onKeyReleased({ key: data.key });
113 data.spy.wait();
114
115 loader.item.onKeyPressed({ key: data.key });
116 loader.item.onKeyPressed({ key: data.key, isAutoRepeat: true });
117 data.spy.wait();
118 }
119}
0120
=== modified file 'tests/qmltests/Dash/tst_Card.qml'
--- tests/qmltests/Dash/tst_Card.qml 2015-01-08 12:51:33 +0000
+++ tests/qmltests/Dash/tst_Card.qml 2015-02-16 22:20:44 +0000
@@ -60,8 +60,8 @@
60 "layout": { "components": Helpers.update(JSON.parse(Helpers.fullMapping), { "art": { "aspect-ratio": 0.7 } }) }60 "layout": { "components": Helpers.update(JSON.parse(Helpers.fullMapping), { "art": { "aspect-ratio": 0.7 } }) }
61 },61 },
62 {62 {
63 "name": "Art, header, summary - horizontal",63 "name": "Art, header, summary, background - horizontal",
64 "layout": { "template": { "card-layout": "horizontal" },64 "layout": { "template": { "card-layout": "horizontal", "card-background": { "type": "gradient", "elements": ["grey", "white"] } },
65 "components": JSON.parse(Helpers.fullMapping) }65 "components": JSON.parse(Helpers.fullMapping) }
66 },66 },
67 {67 {
@@ -101,6 +101,11 @@
101 "layout": { "template": { "overlay": true },101 "layout": { "template": { "overlay": true },
102 "components": { "art": "art", "title": "title" } }102 "components": { "art": "art", "title": "title" } }
103 },103 },
104 {
105 "name": "Art, header, summary - horizontal",
106 "layout": { "template": { "card-layout": "horizontal" },
107 "components": JSON.parse(Helpers.fullMapping) }
108 },
104 ]109 ]
105110
106 CardTool {111 CardTool {
@@ -407,7 +412,7 @@
407 return [412 return [
408 { tag: "Art and summary", visible: true, color: "#ffffff", index: 0 },413 { tag: "Art and summary", visible: true, color: "#ffffff", index: 0 },
409 { tag: "No Summary", visible: false, index: 6 },414 { tag: "No Summary", visible: false, index: 6 },
410 { tag: "Horizontal", visible: false, index: 5 },415 { tag: "Horizontal", visible: true, color: "#808080", index: 5 },
411 { tag: "Grey background", visible: true, color: "#808080", index: 10 },416 { tag: "Grey background", visible: true, color: "#808080", index: 10 },
412 { tag: "Overriden Gradient background", visible: true, color: "#808080", gradientColor: "#ffffff",417 { tag: "Overriden Gradient background", visible: true, color: "#808080", gradientColor: "#ffffff",
413 background: {type: "color", elements: ["grey", "white"]}, index: 10 },418 background: {type: "color", elements: ["grey", "white"]}, index: 10 },
@@ -415,6 +420,7 @@
415 background: Qt.resolvedUrl("artwork/checkers.png"), index: 10 },420 background: Qt.resolvedUrl("artwork/checkers.png"), index: 10 },
416 { tag: "Gradient background", visible: true, color: "#808080", gradientColor: "#ffffff", index: 11 },421 { tag: "Gradient background", visible: true, color: "#808080", gradientColor: "#ffffff", index: 11 },
417 { tag: "Image background", visible: true, image: Qt.resolvedUrl("artwork/checkers.png"), index: 12 },422 { tag: "Image background", visible: true, image: Qt.resolvedUrl("artwork/checkers.png"), index: 12 },
423 { tag: "Horizontal no background", visible: false, index: 14 },
418 ];424 ];
419 }425 }
420426
421427
=== added file 'tests/qmltests/Notifications/Notification.qml'
--- tests/qmltests/Notifications/Notification.qml 1970-01-01 00:00:00 +0000
+++ tests/qmltests/Notifications/Notification.qml 2015-02-16 22:20:44 +0000
@@ -0,0 +1,31 @@
1/*
2 * Copyright 2015 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authors:
17 * Mirco Mueller <mirco.mueller@canonical.com>
18 */
19
20import Unity.Notifications 1.0
21
22Notification {
23 nid: 0
24 type: Notification.PlaceHolder
25 summary: ""
26 body: ""
27 icon: ""
28 secondaryIcon: ""
29 value: 0
30 rawActions: []
31}
032
=== modified file 'tests/qmltests/Notifications/tst_Notifications.qml'
--- tests/qmltests/Notifications/tst_Notifications.qml 2014-11-10 09:14:30 +0000
+++ tests/qmltests/Notifications/tst_Notifications.qml 2015-02-16 22:20:44 +0000
@@ -1,17 +1,20 @@
1/*1/*
2 * Copyright (C) 2013 Canonical, Ltd.2 * Copyright 2015 Canonical Ltd.
3 *3 *
4 * This program is free software; you can redistribute it and/or modify4 * 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 by5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.6 * the Free Software Foundation; version 3.
7 *7 *
8 * This program is distributed in the hope that it will be useful,8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.11 * GNU Lesser General Public License for more details.
12 *12 *
13 * You should have received a copy of the GNU General Public License13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authors:
17 * Mirco Mueller <mirco.mueller@canonical.com>
15 */18 */
1619
17import QtQuick 2.020import QtQuick 2.0
@@ -24,146 +27,134 @@
24import QtMultimedia 5.027import QtMultimedia 5.0
2528
26Item {29Item {
30 id: foobar
31
27 width: notificationsRect.width + interactiveControls.width32 width: notificationsRect.width + interactiveControls.width
28 height: notificationsRect.height33 height: notificationsRect.height
34 property int index: 0
2935
30 Row {36 Row {
31 id: rootRow37 id: rootRow
3238
33 Component {39 NotificationModel {
34 id: mockNotification
35
36 QtObject {
37 function invokeAction(actionId) {
38 mockModel.actionInvoked(actionId)
39 }
40 }
41 }
42
43 ListModel {
44 id: mockModel40 id: mockModel
45 dynamicRoles: true
46
47 signal actionInvoked(string actionId)
48
49 function getRaw(id) {
50 return mockNotification.createObject(mockModel)
51 }
5241
53 // add the default/PlaceHolder notification to the model42 // add the default/PlaceHolder notification to the model
54 Component.onCompleted: {43 Component.onCompleted: {
55 var n = {44 var component = Qt.createComponent("Notification.qml")
56 type: Notification.PlaceHolder,45 var n = component.createObject("notification", {"nid": index++,
57 hints: {},46 "type": Notification.PlaceHolder,
58 summary: "",47 "hints": {},
59 body: "",48 "summary": "",
60 icon: "",49 "body": "",
61 secondaryIcon: "",50 "icon": "",
62 actions: []51 "secondaryIcon": "",
63 }52 "rawActions": []})
6453 n.completed.connect(mockModel.onCompleted)
65 append(n)54 append(n)
66 }55 }
67 }56 }
6857
69 function add2over1SnapDecisionNotification() {58 function add2over1SnapDecisionNotification() {
70 var n = {59 var component = Qt.createComponent("Notification.qml")
71 type: Notification.SnapDecision,60 var n = component.createObject("notification", {"nid": index++,
72 hints: {"x-canonical-private-affirmative-tint": "true"},61 "type": Notification.SnapDecision,
73 summary: "Theatre at Ferria Stadium",62 "hints": {"x-canonical-private-affirmative-tint": "true"},
74 body: "at Ferria Stadium in Bilbao, Spain\n07578545317",63 "summary": "Theatre at Ferria Stadium",
75 icon: "",64 "body": "at Ferria Stadium in Bilbao, Spain\n07578545317",
76 secondaryIcon: "",65 "icon": "",
77 actions: [{ id: "ok_id", label: "Ok"},66 "secondaryIcon": "",
78 { id: "snooze_id", label: "Snooze"},67 "rawActions": ["ok_id", "Ok",
79 { id: "view_id", label: "View"}]68 "snooze_id", "Snooze",
80 }69 "view_id", "View"]})
8170 n.completed.connect(mockModel.onCompleted)
82 mockModel.append(n)71 mockModel.append(n)
83 }72 }
8473
85 function addEphemeralNotification() {74 function addEphemeralNotification() {
86 var n = {75 var component = Qt.createComponent("Notification.qml")
87 type: Notification.Ephemeral,76 var n = component.createObject("notification", {"nid": index++,
88 summary: "Cole Raby",77 "type": Notification.Ephemeral,
89 body: "I did not expect it to be that late.",78 "hints": {},
90 icon: "../graphics/avatars/amanda.png",79 "summary": "Cole Raby",
91 secondaryIcon: "../graphics/applicationIcons/facebook.png",80 "body": "I did not expect it to be that late.",
92 actions: []81 "icon": "../graphics/avatars/amanda.png",
93 }82 "secondaryIcon": "../graphics/applicationIcons/facebook.png",
9483 "rawActions": ["reply_id", "Dummy"]})
84 n.completed.connect(mockModel.onCompleted)
95 mockModel.append(n)85 mockModel.append(n)
96 }86 }
9787
98 function addEphemeralNonShapedIconNotification() {88 function addEphemeralNonShapedIconNotification() {
99 var n = {89 var component = Qt.createComponent("Notification.qml")
100 type: Notification.Ephemeral,90 var n = component.createObject("notification", {"nid": index++,
101 hints: {"x-canonical-non-shaped-icon": "true"},91 "type": Notification.Ephemeral,
102 summary: "Contacts",92 "hints": {"x-canonical-non-shaped-icon": "true"},
103 body: "Synchronised contacts-database with cloud-storage.",93 "summary": "Contacts",
104 icon: "../graphics/applicationIcons/contacts-app.png",94 "body": "Synchronised contacts-database with cloud-storage.",
105 secondaryIcon: "",95 "icon": "../graphics/applicationIcons/contacts-app.png",
106 actions: []96 "secondaryIcon": "",
107 }97 "rawActions": ["reply_id", "Dummy"]})
10898 n.completed.connect(mockModel.onCompleted)
109 mockModel.append(n)99 mockModel.append(n)
110 }100 }
111101
112 function addEphemeralIconSummaryNotification() {102 function addEphemeralIconSummaryNotification() {
113 var n = {103 var component = Qt.createComponent("Notification.qml")
114 type: Notification.Ephemeral,104 var n = component.createObject("notification", {"nid": index++,
115 hints: {"x-canonical-non-shaped-icon": "false"},105 "type": Notification.Ephemeral,
116 summary: "Photo upload completed",106 "hints": {"x-canonical-non-shaped-icon": "false"},
117 body: "",107 "summary": "Photo upload completed",
118 icon: "../graphics/applicationIcons/facebook.png",108 "body": "",
119 secondaryIcon: "",109 "icon": "../graphics/applicationIcons/facebook.png",
120 actions: []110 "secondaryIcon": "",
121 }111 "rawActions": ["reply_id", "Dummy"]})
122112 n.completed.connect(mockModel.onCompleted)
123 mockModel.append(n)113 mockModel.append(n)
124 }114 }
125115
126 function addInteractiveNotification() {116 function addInteractiveNotification() {
127 var n = {117 var component = Qt.createComponent("Notification.qml")
128 type: Notification.Interactive,118 var n = component.createObject("notification", {"nid": index++,
129 summary: "Interactive notification",119 "type": Notification.Interactive,
130 body: "This is a notification that can be clicked",120 "hints": {},
131 icon: "../graphics/avatars/anna_olsson.png",121 "summary": "Interactive notification",
132 secondaryIcon: "",122 "body": "This is a notification that can be clicked",
133 actions: [{ id: "reply_id", label: "Dummy"}],123 "icon": "../graphics/avatars/anna_olsson.png",
134 }124 "secondaryIcon": "",
135125 "rawActions": ["reply_id", "Dummy"]})
126 n.completed.connect(mockModel.onCompleted)
136 mockModel.append(n)127 mockModel.append(n)
137 }128 }
138129
139 function addConfirmationNotification() {130 function addConfirmationNotification() {
140 var n = {131 var component = Qt.createComponent("Notification.qml")
141 type: Notification.Confirmation,132 var n = component.createObject("notification", {"nid": index++,
142 hints: {"x-canonical-non-shaped-icon": "true"},133 "type": Notification.Confirmation,
143 summary: "Confirmation notification",134 "hints": {"x-canonical-non-shaped-icon": "true"},
144 body: "",135 "summary": "Confirmation notification",
145 icon: "image://theme/audio-volume-medium",136 "body": "",
146 secondaryIcon: "",137 "icon": "image://theme/audio-volume-medium",
147 value: 50,138 "secondaryIcon": "",
148 actions: [],139 "value": 50,
149 }140 "rawActions": ["reply_id", "Dummy"]})
150141 n.completed.connect(mockModel.onCompleted)
151 mockModel.append(n)142 mockModel.append(n)
152 }143 }
153144
154 function add2ndConfirmationNotification() {145 function add2ndConfirmationNotification() {
155 var n = {146 var component = Qt.createComponent("Notification.qml")
156 type: Notification.Confirmation,147 var n = component.createObject("notification", {"nid": index++,
157 hints: {"x-canonical-non-shaped-icon": "true",148 "type": Notification.Confirmation,
158 "x-canonical-value-bar-tint": "true"},149 "hints": {"x-canonical-non-shaped-icon": "true",
159 summary: "Confirmation notification",150 "x-canonical-value-bar-tint": "true"},
160 body: "High Volume",151 "summary": "Confirmation notification",
161 icon: "image://theme/audio-volume-high",152 "body": "High Volume",
162 secondaryIcon: "",153 "icon": "image://theme/audio-volume-high",
163 value: 85,154 "secondaryIcon": "",
164 actions: [],155 "value": 85,
165 }156 "rawActions": ["reply_id", "Dummy"]})
166157 n.completed.connect(mockModel.onCompleted)
167 mockModel.append(n)158 mockModel.append(n)
168 }159 }
169160
@@ -174,8 +165,9 @@
174 }165 }
175166
176 function remove1stNotification() {167 function remove1stNotification() {
177 if (mockModel.count > 1)168 if (mockModel.count > 1) {
178 mockModel.remove(1)169 mockModel.removeSecond()
170 }
179 }171 }
180172
181 Rectangle {173 Rectangle {
@@ -273,41 +265,122 @@
273 name: "NotificationRendererTest"265 name: "NotificationRendererTest"
274 when: windowShown266 when: windowShown
275267
268 property list<Notification> nlist: [
269 Notification {
270 nid: 1
271 type: Notification.Ephemeral
272 summary: "Photo upload completed"
273 body: ""
274 icon: "../graphics/applicationIcons/facebook.png"
275 secondaryIcon: ""
276 value: 0
277 rawActions: []
278 },
279 Notification {
280 nid: 2
281 type: Notification.Ephemeral
282 hints: {"x-canonical-private-affirmative-tint": "false",
283 "sound-file": "dummy.ogg",
284 "suppress-sound": "true"}
285 summary: "New comment successfully published"
286 body: ""
287 icon: ""
288 secondaryIcon: "../graphics/applicationIcons/facebook.png"
289 value: 0
290 rawActions: []
291 },
292 Notification {
293 nid: 3
294 type: Notification.Interactive
295 hints: {"x-canonical-private-affirmative-tint": "false",
296 "sound-file": "dummy.ogg"}
297 summary: "Interactive notification"
298 body: "This is a notification that can be clicked"
299 icon: "../graphics/avatars/amanda.png"
300 secondaryIcon: ""
301 value: 0
302 rawActions: ["reply_id", "Dummy"]
303 },
304 Notification {
305 nid: 4
306 type: Notification.SnapDecision
307 hints: {"x-canonical-private-affirmative-tint": "false",
308 "sound-file": "dummy.ogg"}
309 summary: "Bro Coly"
310 body: "At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet."
311 icon: "../graphics/avatars/anna_olsson.png"
312 secondaryIcon: ""
313 value: 0
314 rawActions: ["accept_id", "Accept",
315 "reject_id", "Reject"]
316 },
317 Notification {
318 nid: 5
319 type: Notification.Ephemeral
320 hints: {"x-canonical-private-affirmative-tint": "false",
321 "sound-file": "dummy.ogg"}
322 summary: "Cole Raby"
323 body: "I did not expect it to be that late."
324 icon: "../graphics/avatars/funky.png"
325 secondaryIcon: "../graphics/applicationIcons/facebook.png"
326 value: 0
327 rawActions: []
328 },
329 Notification {
330 nid: 6
331 type: Notification.Ephemeral
332 hints: {"x-canonical-private-affirmative-tint": "false",
333 "x-canonical-non-shaped-icon": "true"}
334 summary: "Contacts"
335 body: "Synchronised contacts-database with cloud-storage."
336 icon: "image://theme/contacts-app"
337 secondaryIcon: ""
338 value: 0
339 rawActions: []
340 },
341 Notification {
342 nid: 7
343 type: Notification.Confirmation
344 hints: {"x-canonical-non-shaped-icon": "true"}
345 summary: ""
346 body: ""
347 icon: "image://theme/audio-volume-medium"
348 secondaryIcon: ""
349 value: 50
350 rawActions: []
351 },
352 Notification {
353 nid: 8
354 type: Notification.Confirmation
355 hints: {"x-canonical-non-shaped-icon": "true",
356 "x-canonical-value-bar-tint" : "true"}
357 summary: ""
358 body: "High Volume"
359 icon: "image://theme/audio-volume-high"
360 secondaryIcon: ""
361 value: 85
362 rawActions: []
363 },
364 Notification {
365 nid: 9
366 type: Notification.SnapDecision
367 hints: {"x-canonical-private-affirmative-tint": "true"}
368 summary: "Theatre at Ferria Stadium"
369 body: "at Ferria Stadium in Bilbao, Spain\n07578545317"
370 icon: ""
371 secondaryIcon: ""
372 value: 0
373 rawActions: ["ok_id", "Ok",
374 "snooze_id", "Snooze",
375 "view_id", "View"]
376 }
377 ]
378
276 function test_NotificationRenderer_data() {379 function test_NotificationRenderer_data() {
277 return [380 return [
278 {381 {
279 tag: "2-over-1 Snap Decision with button-tint",
280 type: Notification.SnapDecision,
281 hints: {"x-canonical-private-affirmative-tint": "true"},
282 summary: "Theatre at Ferria Stadium",
283 body: "at Ferria Stadium in Bilbao, Spain\n07578545317",
284 icon: "",
285 secondaryIcon: "",
286 actions: [{ id: "ok_id", label: "Ok"},
287 { id: "snooze_id", label: "Snooze"},
288 { id: "view_id", label: "View"}],
289 summaryVisible: true,
290 bodyVisible: true,
291 iconVisible: false,
292 centeredIconVisible: false,
293 shaped: false,
294 secondaryIconVisible: false,
295 buttonRowVisible: false,
296 buttonTinted: true,
297 hasSound: false,
298 valueVisible: false,
299 valueLabelVisible: false,
300 valueTinted: false
301 },
302 {
303 tag: "Ephemeral notification - icon-summary layout",382 tag: "Ephemeral notification - icon-summary layout",
304 type: Notification.Ephemeral,383 n: nlist[0],
305 hints: {},
306 summary: "Photo upload completed",
307 body: "",
308 icon: "../graphics/applicationIcons/facebook.png",
309 secondaryIcon: "",
310 actions: [],
311 summaryVisible: true,384 summaryVisible: true,
312 bodyVisible: false,385 bodyVisible: false,
313 iconVisible: true,386 iconVisible: true,
@@ -323,15 +396,7 @@
323 },396 },
324 {397 {
325 tag: "Ephemeral notification - check suppression of secondary icon for icon-summary layout",398 tag: "Ephemeral notification - check suppression of secondary icon for icon-summary layout",
326 type: Notification.Ephemeral,399 n: nlist[1],
327 hints: {"x-canonical-private-affirmative-tint": "false",
328 "sound-file": "dummy.ogg",
329 "suppress-sound": "true"},
330 summary: "New comment successfully published",
331 body: "",
332 icon: "",
333 secondaryIcon: "../graphics/applicationIcons/facebook.png",
334 actions: [],
335 summaryVisible: true,400 summaryVisible: true,
336 bodyVisible: false,401 bodyVisible: false,
337 interactiveAreaEnabled: false,402 interactiveAreaEnabled: false,
@@ -348,14 +413,7 @@
348 },413 },
349 {414 {
350 tag: "Interactive notification",415 tag: "Interactive notification",
351 type: Notification.Interactive,416 n: nlist[2],
352 hints: {"x-canonical-private-affirmative-tint": "false",
353 "sound-file": "dummy.ogg"},
354 summary: "Interactive notification",
355 body: "This is a notification that can be clicked",
356 icon: "../graphics/avatars/amanda.png",
357 secondaryIcon: "",
358 actions: [{ id: "reply_id", label: "Dummy"}],
359 summaryVisible: true,417 summaryVisible: true,
360 bodyVisible: true,418 bodyVisible: true,
361 iconVisible: true,419 iconVisible: true,
@@ -371,15 +429,7 @@
371 },429 },
372 {430 {
373 tag: "Snap Decision without secondary icon and no button-tint",431 tag: "Snap Decision without secondary icon and no button-tint",
374 type: Notification.SnapDecision,432 n: nlist[3],
375 hints: {"x-canonical-private-affirmative-tint": "false",
376 "sound-file": "dummy.ogg"},
377 summary: "Bro Coly",
378 body: "At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.",
379 icon: "../graphics/avatars/anna_olsson.png",
380 secondaryIcon: "",
381 actions: [{ id: "accept_id", label: "Accept"},
382 { id: "reject_id", label: "Reject"}],
383 summaryVisible: true,433 summaryVisible: true,
384 bodyVisible: true,434 bodyVisible: true,
385 iconVisible: true,435 iconVisible: true,
@@ -395,14 +445,7 @@
395 },445 },
396 {446 {
397 tag: "Ephemeral notification",447 tag: "Ephemeral notification",
398 type: Notification.Ephemeral,448 n: nlist[4],
399 hints: {"x-canonical-private-affirmative-tint": "false",
400 "sound-file": "dummy.ogg"},
401 summary: "Cole Raby",
402 body: "I did not expect it to be that late.",
403 icon: "../graphics/avatars/funky.png",
404 secondaryIcon: "../graphics/applicationIcons/facebook.png",
405 actions: [],
406 summaryVisible: true,449 summaryVisible: true,
407 bodyVisible: true,450 bodyVisible: true,
408 iconVisible: true,451 iconVisible: true,
@@ -418,14 +461,7 @@
418 },461 },
419 {462 {
420 tag: "Ephemeral notification with non-shaped icon",463 tag: "Ephemeral notification with non-shaped icon",
421 type: Notification.Ephemeral,464 n: nlist[5],
422 hints: {"x-canonical-private-affirmative-tint": "false",
423 "x-canonical-non-shaped-icon": "true"},
424 summary: "Contacts",
425 body: "Synchronised contacts-database with cloud-storage.",
426 icon: "image://theme/contacts-app",
427 secondaryIcon: "",
428 actions: [],
429 summaryVisible: true,465 summaryVisible: true,
430 bodyVisible: true,466 bodyVisible: true,
431 iconVisible: true,467 iconVisible: true,
@@ -441,14 +477,7 @@
441 },477 },
442 {478 {
443 tag: "Confirmation notification with value",479 tag: "Confirmation notification with value",
444 type: Notification.Confirmation,480 n: nlist[6],
445 hints: {"x-canonical-non-shaped-icon": "true"},
446 summary: "",
447 body: "",
448 icon: "image://theme/audio-volume-medium",
449 secondaryIcon: "",
450 value: 50,
451 actions: [],
452 summaryVisible: false,481 summaryVisible: false,
453 bodyVisible: false,482 bodyVisible: false,
454 iconVisible: false,483 iconVisible: false,
@@ -464,15 +493,7 @@
464 },493 },
465 {494 {
466 tag: "Confirmation notification with value, label and tint",495 tag: "Confirmation notification with value, label and tint",
467 type: Notification.Confirmation,496 n: nlist[7],
468 hints: {"x-canonical-non-shaped-icon": "true",
469 "x-canonical-value-bar-tint" : "true"},
470 summary: "",
471 body: "High Volume",
472 icon: "image://theme/audio-volume-high",
473 secondaryIcon: "",
474 value: 85,
475 actions: [],
476 summaryVisible: false,497 summaryVisible: false,
477 bodyVisible: false,498 bodyVisible: false,
478 iconVisible: false,499 iconVisible: false,
@@ -485,6 +506,22 @@
485 valueVisible: true,506 valueVisible: true,
486 valueLabelVisible: true,507 valueLabelVisible: true,
487 valueTinted: true508 valueTinted: true
509 },
510 {
511 tag: "2-over-1 Snap Decision with button-tint",
512 n: nlist[8],
513 summaryVisible: true,
514 bodyVisible: true,
515 iconVisible: false,
516 centeredIconVisible: false,
517 shaped: false,
518 secondaryIconVisible: false,
519 buttonRowVisible: false,
520 buttonTinted: true,
521 hasSound: false,
522 valueVisible: false,
523 valueLabelVisible: false,
524 valueTinted: false
488 }525 }
489 ]526 ]
490 }527 }
@@ -509,8 +546,14 @@
509 }546 }
510547
511 function test_NotificationRenderer(data) {548 function test_NotificationRenderer(data) {
549 // make sure the clicks on mocked notifications can be checked against by "actionSpy" (mimicking the NotificationServer component)
550 data.n.actionInvoked.connect(mockModel.actionInvoked)
551
552 // hook up notification's completed-signal with model's onCompleted-slot, so that remove() (model) happens on close() (notification)
553 data.n.completed.connect(mockModel.onCompleted)
554
512 // populate model with some mock notifications555 // populate model with some mock notifications
513 mockModel.append(data)556 mockModel.append(data.n)
514557
515 // make sure the view is properly updated before going on558 // make sure the view is properly updated before going on
516 notifications.forceLayout();559 notifications.forceLayout();
@@ -548,9 +591,9 @@
548591
549 // test input does not fall through592 // test input does not fall through
550 mouseClick(notification, notification.width / 2, notification.height / 2)593 mouseClick(notification, notification.width / 2, notification.height / 2)
551 if(data.type == Notification.Interactive) {594 if(data.n.type === Notification.Interactive) {
552 actionSpy.wait()595 actionSpy.wait()
553 compare(actionSpy.signalArguments[0][0], data.actions[0]["id"], "got wrong id for interactive action")596 compare(actionSpy.signalArguments[0][0], data.n.actions.data(0, ActionModel.RoleActionId), "got wrong id for interactive action")
554 }597 }
555 compare(clickThroughSpy.count, 0, "click on notification fell through")598 compare(clickThroughSpy.count, 0, "click on notification fell through")
556599
@@ -559,17 +602,19 @@
559 compare(bodyLabel.visible, data.bodyVisible, "body-text visibility is incorrect")602 compare(bodyLabel.visible, data.bodyVisible, "body-text visibility is incorrect")
560 compare(buttonRow.visible, data.buttonRowVisible, "button visibility is incorrect")603 compare(buttonRow.visible, data.buttonRowVisible, "button visibility is incorrect")
561604
562 var audioItem = findInvisibleChild(notification, "sound")605 if (data.hasSound) {
563 compare(audioItem.playbackState, data.hasSound ? Audio.PlayingState : Audio.StoppedState, "Audio has wrong state")606 var audioItem = findInvisibleChild(notification, "sound")
607 compare(audioItem.playbackState, data.hasSound ? Audio.PlayingState : Audio.StoppedState, "Audio has wrong state")
608 }
564609
565 if(data.buttonRowVisible) {610 if(data.buttonRowVisible) {
566 var buttonCancel = findChild(buttonRow, "notify_button1")611 var buttonCancel = findChild(buttonRow, "notify_button1")
567 var buttonAccept = findChild(buttonRow, "notify_button0")612 var buttonAccept = findChild(buttonRow, "notify_button0")
568613
569 // only test the left/cancel-button if two actions have been passed in614 // only test the left/cancel-button if two actions have been passed in
570 if (data.actions.length == 2) {615 if (data.n.actions.count === 2) {
571 tryCompareFunction(function() { mouseClick(buttonCancel, buttonCancel.width / 2, buttonCancel.height / 2); return actionSpy.signalArguments.length > 0; }, true);616 tryCompareFunction(function() { mouseClick(buttonCancel, buttonCancel.width / 2, buttonCancel.height / 2); return actionSpy.signalArguments.length > 0; }, true);
572 compare(actionSpy.signalArguments[0][0], data.actions[1]["id"], "got wrong id for negative action")617 compare(actionSpy.signalArguments[0][0], data.n.actions.data(1, ActionModel.RoleActionId), "got wrong id for negative action")
573 actionSpy.clear()618 actionSpy.clear()
574 }619 }
575620
@@ -578,36 +623,49 @@
578623
579 // click the positive/right button624 // click the positive/right button
580 tryCompareFunction(function() { mouseClick(buttonAccept, buttonAccept.width / 2, buttonAccept.height / 2); return actionSpy.signalArguments.length > 0; }, true);625 tryCompareFunction(function() { mouseClick(buttonAccept, buttonAccept.width / 2, buttonAccept.height / 2); return actionSpy.signalArguments.length > 0; }, true);
581 compare(actionSpy.signalArguments[0][0], data.actions[0]["id"], "got wrong id positive action")626 compare(actionSpy.signalArguments[0][0], data.n.actions.data(0, ActionModel.RoleActionId), "got wrong id positive action")
582 actionSpy.clear()627 actionSpy.clear()
583 waitForRendering (notification)
584628
585 // check if there's a ComboButton created due to more actions being passed629 // check if there's a ComboButton created due to more actions being passed
586 if (data.actions.length > 2) {630 if (data.n.actions.count > 3) {
587 var comboButton = findChild(notification, "notify_button2")631 var comboButton = findChild(notification, "notify_button2")
588 tryCompareFunction(function() { return comboButton.expanded == false; }, true);632 tryCompareFunction(function() { return comboButton.expanded === false; }, true);
589633
590 // click to expand634 // click to expand
591 tryCompareFunction(function() { mouseClick(comboButton, comboButton.width - comboButton.__styleInstance.dropDownWidth / 2, comboButton.height / 2); return comboButton.expanded == true; }, true);635 tryCompareFunction(function() { mouseClick(comboButton, comboButton.width / 2, comboButton.height / 2); return comboButton.expanded === true; }, true);
592636
593 // try clicking on choices in expanded comboList637 // try clicking on choices in expanded comboList
594 var choiceButton1 = findChild(notification, "notify_button3")638 var choiceButton1 = findChild(notification, "notify_button3")
595 tryCompareFunction(function() { mouseClick(choiceButton1, choiceButton1.width / 2, choiceButton1.height / 2); return actionSpy.signalArguments.length > 0; }, true);639 tryCompareFunction(function() { mouseClick(choiceButton1, choiceButton1.width / 2, choiceButton1.height / 2); return actionSpy.signalArguments.length > 0; }, true);
596 compare(actionSpy.signalArguments[0][0], data.actions[3]["id"], "got wrong id choice action 1")640 compare(actionSpy.signalArguments[0][0], data.n.actions.data(3, ActionModel.RoleActionId), "got wrong id choice action 1")
597 actionSpy.clear()641 actionSpy.clear()
598642
599 var choiceButton2 = findChild(notification, "notify_button4")643 var choiceButton2 = findChild(notification, "notify_button4")
600 tryCompareFunction(function() { mouseClick(choiceButton2, choiceButton2.width / 2, choiceButton2.height / 2); return actionSpy.signalArguments.length > 0; }, true);644 tryCompareFunction(function() { mouseClick(choiceButton2, choiceButton2.width / 2, choiceButton2.height / 2); return actionSpy.signalArguments.length > 0; }, true);
601 compare(actionSpy.signalArguments[0][0], data.actions[4]["id"], "got wrong id choice action 2")645 compare(actionSpy.signalArguments[0][0], data.n.actions.data(4, ActionModel.RoleActionId), "got wrong id choice action 2")
602 actionSpy.clear()646 actionSpy.clear()
603647
604 // click to collapse648 // click to collapse
605 //tryCompareFunction(function() { mouseClick(comboButton, comboButton.width - comboButton.__styleInstance.dropDownWidth / 2, comboButton.height / 2); return comboButton.expanded == false; }, true);649 tryCompareFunction(function() { mouseClick(comboButton, comboButton.width / 2, comboButton.height / 2); return comboButton.expanded == false; }, true);
606 } else {650 } else {
607 mouseClick(buttonCancel, buttonCancel.width / 2, buttonCancel.height / 2)651 mouseClick(buttonCancel, buttonCancel.width / 2, buttonCancel.height / 2)
608 compare(actionSpy.signalArguments[0][0], data.actions[1]["id"], "got wrong id for negative action")652 compare(actionSpy.signalArguments[0][0], data.n.actions.data(1, ActionModel.RoleActionId), "got wrong id for negative action")
609 }653 }
610 }654 }
655
656 // swipe-to-dismiss check
657 waitForRendering(notification)
658 var before = mockModel.count
659 var dragStart = notification.width * 0.25;
660 var dragEnd = notification.width;
661 var dragY = notification.height / 2;
662 touchFlick(notification, dragStart, dragY, dragEnd, dragY)
663 waitForRendering(notification)
664 if ((data.n.type === Notification.SnapDecision && notification.state === "expanded") || data.n.type === Notification.Confirmation) {
665 tryCompare(mockModel, "count", before)
666 } else {
667 tryCompare(mockModel, "count", before - 1)
668 }
611 }669 }
612 }670 }
613 }671 }
614672
=== modified file 'tests/qmltests/Notifications/tst_OptionToggle.qml'
--- tests/qmltests/Notifications/tst_OptionToggle.qml 2014-11-10 09:14:30 +0000
+++ tests/qmltests/Notifications/tst_OptionToggle.qml 2015-02-16 22:20:44 +0000
@@ -1,17 +1,20 @@
1/*1/*
2 * Copyright (C) 2014 Canonical, Ltd.2 * Copyright 2015 Canonical Ltd.
3 *3 *
4 * This program is free software; you can redistribute it and/or modify4 * 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 by5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.6 * the Free Software Foundation; version 3.
7 *7 *
8 * This program is distributed in the hope that it will be useful,8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.11 * GNU Lesser General Public License for more details.
12 *12 *
13 * You should have received a copy of the GNU General Public License13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authors:
17 * Mirco Mueller <mirco.mueller@canonical.com>
15 */18 */
1619
17import QtQuick 2.020import QtQuick 2.0
@@ -235,8 +238,10 @@
235 compare(bodyLabel.visible, data.bodyVisible, "body-text visibility is incorrect")238 compare(bodyLabel.visible, data.bodyVisible, "body-text visibility is incorrect")
236 compare(buttonRow.visible, data.buttonRowVisible, "button visibility is incorrect")239 compare(buttonRow.visible, data.buttonRowVisible, "button visibility is incorrect")
237240
238 var audioItem = findInvisibleChild(notification, "sound")241 if (data.hasSound) {
239 compare(audioItem.playbackState, data.hasSound ? Audio.PlayingState : Audio.StoppedState, "Audio has wrong state")242 var audioItem = findInvisibleChild(notification, "sound")
243 compare(audioItem.playbackState, data.hasSound ? Audio.PlayingState : Audio.StoppedState, "Audio has wrong state")
244 }
240245
241 if(data.buttonRowVisible) {246 if(data.buttonRowVisible) {
242 var buttonCancel = findChild(buttonRow, "notify_button1")247 var buttonCancel = findChild(buttonRow, "notify_button1")
243248
=== modified file 'tests/qmltests/Notifications/tst_SwipeToAct.qml'
--- tests/qmltests/Notifications/tst_SwipeToAct.qml 2014-11-05 14:37:11 +0000
+++ tests/qmltests/Notifications/tst_SwipeToAct.qml 2015-02-16 22:20:44 +0000
@@ -218,6 +218,10 @@
218 // populate model with some mock notifications218 // populate model with some mock notifications
219 mockModel.append(data)219 mockModel.append(data)
220220
221 // add actions to action-model to test against
222 myActionModel.append("ok_id", "Ok")
223 myActionModel.append("cancel_id", "Cancel")
224
221 // make sure the view is properly updated before going on225 // make sure the view is properly updated before going on
222 notifications.forceLayout();226 notifications.forceLayout();
223 waitForRendering(notifications);227 waitForRendering(notifications);
224228
=== modified file 'tests/qmltests/tst_ShellWithPin.qml'
--- tests/qmltests/tst_ShellWithPin.qml 2014-12-05 16:27:27 +0000
+++ tests/qmltests/tst_ShellWithPin.qml 2015-02-16 22:20:44 +0000
@@ -472,5 +472,52 @@
472472
473 }473 }
474474
475 /*
476 Regression test for https://bugs.launchpad.net/ubuntu/+source/unity8/+bug/1393447
477
478 Do a left edge drag that is long enough to start displacing the greeter
479 but short engough so that the greeter comes back into place once the
480 finger is lifted.
481
482 In this situation the launcher should remaing fully shown and hide itself
483 only after its idle timeout is triggered.
484 */
485 function test_shortLeftEdgeSwipeMakesLauncherStayVisible() {
486 var greeter = testCase.findChild(shell, "greeter")
487 greeter.show();
488 tryCompare(greeter, "showProgress", 1);
489
490 var launcher = testCase.findChild(shell, "launcher")
491 {
492 var dismissTimer = testCase.findInvisibleChild(launcher, "dismissTimer");
493 // effectively disable the dismiss timer
494 dismissTimer.interval = 24 * 60 * 60 * 1000 // 24 hours
495 }
496 var launcherPanel = testCase.findChild(launcher, "launcherPanel");
497
498 var toX = shell.width * 0.45;
499 touchFlick(shell,
500 1 /* fromX */, shell.height * 0.5 /* fromY */,
501 toX /* toX */, shell.height * 0.5 /* toY */,
502 true /* beginTouch */, false /* endTouch */,
503 50, 100);
504
505 // Launcher must be fully shown by now
506 tryCompare(launcherPanel, "x", 0);
507
508 // Greeter should be displaced
509 tryCompareFunction(function() { return greeter.mapToItem(shell, 0, 0).x > shell.width*0.2; }, true);
510
511 touchRelease(shell, toX, shell.height * 0.5);
512
513 // Upon release the greeter should have slid back into full view
514 tryCompareFunction(function() { return greeter.mapToItem(shell, 0, 0).x === 0; }, true);
515
516 // And the launcher should stay fully shown
517 for (var i = 0; i < 10; ++i) {
518 wait(50);
519 compare(launcherPanel.x, 0);
520 }
521 }
475 }522 }
476}523}

Subscribers

People subscribed via source and target branches

to all changes: