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
1=== modified file 'debian/changelog'
2--- debian/changelog 2015-01-22 20:47:05 +0000
3+++ debian/changelog 2015-02-16 22:20:44 +0000
4@@ -1,3 +1,29 @@
5+unity8 (8.02+15.04.20150213~rtm-0ubuntu1) UNRELEASED; urgency=medium
6+
7+ [ Albert astals ]
8+ * Preserve the aspect ratio for the card attribute images
9+
10+ [ Andrea Cimitan ]
11+ * support background on horizontal cards with summary (LP: #1393008)
12+ * Background needs to be specified to be visible in horizontal cards
13+ (LP: #1411748)
14+
15+ [ Daniel d'Andrada ]
16+ * Don't show() the lockscreen if it's already being shown
17+
18+ [ Joshua Arenson ]
19+ * Create a PhysicalKeyMapper to handle all physical/hardware keys
20+
21+ [ Michał Sawicz ]
22+ * Create a PhysicalKeyMapper to handle all physical/hardware keys
23+
24+ [ Mirco Müller ]
25+ * Allow swipe-to-dismiss for contracted snap-decision notifications,
26+ interactive notifications and ephemeral notifications. (LP:
27+ #1355422, #1334855)
28+
29+ -- Michał Sawicz <michal.sawicz@canonical.com> Fri, 13 Feb 2015 17:23:13 +0100
30+
31 unity8 (8.02+15.04.20150122.2~rtm-0ubuntu1) 14.09; urgency=medium
32
33 [ Michael Terry ]
34
35=== modified file 'plugins/Dash/CardAttributes.qml'
36--- plugins/Dash/CardAttributes.qml 2014-10-14 15:40:59 +0000
37+++ plugins/Dash/CardAttributes.qml 2015-02-16 22:20:44 +0000
38@@ -51,6 +51,8 @@
39 id: icon
40 height: units.gu(2)
41 sets: ["actions", "status", "apps"]
42+ // FIXME Workaround for bug https://bugs.launchpad.net/ubuntu/+source/ubuntu-ui-toolkit/+bug/1421293
43+ width: implicitWidth > 0 && implicitHeight > 0 ? (implicitWidth / implicitHeight * height) : implicitWidth
44 source: "icon" in modelData && modelData["icon"] || ""
45 color: grid.color
46 }
47
48=== modified file 'plugins/Dash/CardCreator.js'
49--- plugins/Dash/CardCreator.js 2015-01-08 12:51:33 +0000
50+++ plugins/Dash/CardCreator.js 2015-02-16 22:20:44 +0000
51@@ -224,6 +224,8 @@
52
53 // %1 is used as extra anchors of emblemIcon
54 // %2 is used as color of emblemIcon
55+// FIXME The width code is a
56+// Workaround for bug https://bugs.launchpad.net/ubuntu/+source/ubuntu-ui-toolkit/+bug/1421293
57 var kEmblemIconCode = 'StatusIcon { \n\
58 id: emblemIcon; \n\
59 objectName: "emblemIcon"; \n\
60@@ -235,6 +237,7 @@
61 source: cardData && cardData["emblem"] || ""; \n\
62 color: %2; \n\
63 height: source != "" ? titleLabel.font.pixelSize : 0; \n\
64+ width: implicitWidth > 0 && implicitHeight > 0 ? (implicitWidth / implicitHeight * height) : implicitWidth; \n\
65 }\n';
66
67 // %1 is used as anchors of touchdown effect
68@@ -320,7 +323,8 @@
69 var hasSummary = components["summary"] || false;
70 var artAndSummary = hasArt && hasSummary;
71 var isHorizontal = template["card-layout"] === "horizontal";
72- var hasBackground = !isHorizontal && (template["card-background"] || components["background"] || artAndSummary);
73+ var hasBackground = (!isHorizontal && (template["card-background"] || components["background"] || artAndSummary)) ||
74+ (hasSummary && (template["card-background"] || components["background"]));
75 var hasTitle = components["title"] || false;
76 var hasMascot = components["mascot"] || false;
77 var hasEmblem = components["emblem"] && !(hasMascot && template["card-size"] === "small") || false;
78
79=== modified file 'qml/Components/Dialogs.qml'
80--- qml/Components/Dialogs.qml 2015-01-14 09:09:08 +0000
81+++ qml/Components/Dialogs.qml 2015-02-16 22:20:44 +0000
82@@ -28,21 +28,12 @@
83 // in other applications like the welcome wizard.
84 readonly property string domain: "unity8"
85
86- function onPowerKeyPressed() {
87- // FIXME: event.isAutoRepeat is always false on Nexus 4.
88- // So we use powerKeyTimer.running to avoid the PowerOff key repeat
89- // https://launchpad.net/bugs/1349416
90- if (!powerKeyTimer.running) {
91- powerKeyTimer.restart();
92- }
93- }
94-
95- function onPowerKeyReleased() {
96- powerKeyTimer.stop();
97- }
98-
99 signal powerOffClicked();
100
101+ function showPowerDialog() {
102+ d.showPowerDialog();
103+ }
104+
105 QtObject {
106 id: d // private stuff
107
108@@ -56,17 +47,6 @@
109 }
110 }
111
112- Timer {
113- id: powerKeyTimer
114- interval: 2000
115- repeat: false
116- triggeredOnStart: false
117-
118- onTriggered: {
119- d.showPowerDialog();
120- }
121- }
122-
123 Component {
124 id: logoutDialog
125 Dialog {
126
127=== added file 'qml/Components/PhysicalKeysMapper.qml'
128--- qml/Components/PhysicalKeysMapper.qml 1970-01-01 00:00:00 +0000
129+++ qml/Components/PhysicalKeysMapper.qml 2015-02-16 22:20:44 +0000
130@@ -0,0 +1,113 @@
131+/*
132+ * Copyright (C) 2014-2015 Canonical, Ltd.
133+ *
134+ * This program is free software; you can redistribute it and/or modify
135+ * it under the terms of the GNU General Public License as published by
136+ * the Free Software Foundation; version 3.
137+ *
138+ * This program is distributed in the hope that it will be useful,
139+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
140+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
141+ * GNU General Public License for more details.
142+ *
143+ * You should have received a copy of the GNU General Public License
144+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
145+ */
146+
147+import QtQuick 2.0
148+import Powerd 0.1
149+
150+/*!
151+ \brief A mapper for the physical keys on the device
152+
153+ A mapper to handle events triggered by pressing physical keys on a device.
154+ Keys included are
155+ * Volume Decrease
156+ * Volume Increase
157+ * Power
158+
159+ This allows for handling the following events
160+ * Power dialog
161+ * Volume Decreases/Increases
162+ * Screenshots
163+
164+*/
165+
166+Item {
167+ id: root
168+
169+ signal powerKeyLongPressed;
170+ signal volumeDownTriggered;
171+ signal volumeUpTriggered;
172+ signal screenshotTriggered;
173+
174+ QtObject {
175+ id: d
176+
177+ property bool volumeDownKeyPressed: false
178+ property bool volumeUpKeyPressed: false
179+ property bool ignoreVolumeEvents: false
180+ }
181+
182+ Timer {
183+ id: powerKeyLongPressTimer
184+
185+ interval: 2000
186+ onTriggered: root.powerKeyLongPressed();
187+ }
188+
189+
190+ function onKeyPressed(event) {
191+ if ((event.key == Qt.Key_PowerDown || event.key == Qt.Key_PowerOff)
192+ && !event.isAutoRepeat) {
193+
194+ // FIXME: We only consider power key presses if the screen is
195+ // on because of bugs 1410830/1409003. The theory is that when
196+ // those bugs are encountered, there is a >2s delay between the
197+ // power press event and the power release event, which causes
198+ // the shutdown dialog to appear on resume. So to avoid that
199+ // symptom while we investigate the root cause, we simply won't
200+ // initiate any dialogs when the screen is off.
201+ if (Powerd.status === Powerd.On) {
202+ powerKeyLongPressTimer.restart();
203+ }
204+ event.accepted = true;
205+ } else if ((event.key == Qt.Key_MediaTogglePlayPause || event.key == Qt.Key_MediaPlay)
206+ && !event.isAutoRepeat) {
207+ event.accepted = callManager.handleMediaKey(false);
208+ } else if (event.key == Qt.Key_VolumeDown) {
209+ if (event.isAutoRepeat && !d.ignoreVolumeEvents) root.volumeDownTriggered();
210+ else if (!event.isAutoRepeat) {
211+ if (d.volumeUpKeyPressed) {
212+ if (Powerd.status === Powerd.On) root.screenshotTriggered();
213+ d.ignoreVolumeEvents = true;
214+ }
215+ d.volumeDownKeyPressed = true;
216+ }
217+ } else if (event.key == Qt.Key_VolumeUp) {
218+ if (event.isAutoRepeat && !d.ignoreVolumeEvents) root.volumeUpTriggered();
219+ else if (!event.isAutoRepeat) {
220+ if (d.volumeDownKeyPressed) {
221+ if (Powerd.status === Powerd.On) root.screenshotTriggered();
222+ d.ignoreVolumeEvents = true;
223+ }
224+ d.volumeUpKeyPressed = true;
225+ }
226+ }
227+ }
228+
229+ function onKeyReleased(event) {
230+ if (event.key == Qt.Key_PowerDown || event.key == Qt.Key_PowerOff) {
231+ powerKeyLongPressTimer.stop();
232+ event.accepted = true;
233+ } else if (event.key == Qt.Key_VolumeDown) {
234+ if (!d.ignoreVolumeEvents) root.volumeDownTriggered();
235+ d.volumeDownKeyPressed = false;
236+ if (!d.volumeUpKeyPressed) d.ignoreVolumeEvents = false;
237+ } else if (event.key == Qt.Key_VolumeUp) {
238+ if (!d.ignoreVolumeEvents) root.volumeUpTriggered();
239+ d.volumeUpKeyPressed = false;
240+ if (!d.volumeDownKeyPressed) d.ignoreVolumeEvents = false;
241+ }
242+ }
243+}
244
245=== removed file 'qml/Components/VolumeKeyFilter.qml'
246--- qml/Components/VolumeKeyFilter.qml 2015-01-14 10:24:49 +0000
247+++ qml/Components/VolumeKeyFilter.qml 1970-01-01 00:00:00 +0000
248@@ -1,63 +0,0 @@
249-/*
250- * Copyright (C) 2014 Canonical, Ltd.
251- *
252- * This program is free software; you can redistribute it and/or modify
253- * it under the terms of the GNU General Public License as published by
254- * the Free Software Foundation; version 3.
255- *
256- * This program is distributed in the hope that it will be useful,
257- * but WITHOUT ANY WARRANTY; without even the implied warranty of
258- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
259- * GNU General Public License for more details.
260- *
261- * You should have received a copy of the GNU General Public License
262- * along with this program. If not, see <http://www.gnu.org/licenses/>.
263- */
264-
265-import QtQuick 2.0
266-/*!
267- \brief A filter for volume keys
268-
269-A filter which treats volume keys as single tri-state key with the states:
270-VolumeUp Pressed, VolumeDown Pressed or Volume Up+Down pressed
271-*/
272-QtObject {
273- id: root
274-
275- signal volumeUpPressed()
276- signal volumeDownPressed()
277- signal bothVolumeKeysPressed()
278-
279- property bool volumeUpKeyPressed: false
280- property bool volumeDownKeyPressed: false
281- property bool aVolumeKeyWasReleased: true
282-
283- function onKeyPressed(key) {
284- if (key == Qt.Key_VolumeUp)
285- volumeUpKeyPressed = true;
286- else if (key == Qt.Key_VolumeDown)
287- volumeDownKeyPressed = true;
288-
289- if (volumeDownKeyPressed && volumeUpKeyPressed) {
290- //avoids sending a signal repeatedly if both keys are held
291- //instead one of the keys must have been previously released
292- if (aVolumeKeyWasReleased)
293- bothVolumeKeysPressed();
294- aVolumeKeyWasReleased = false;
295- } else if (volumeDownKeyPressed) {
296- volumeDownPressed();
297- } else if (volumeUpKeyPressed) {
298- volumeUpPressed();
299- }
300- }
301-
302- function onKeyReleased(key) {
303- if (key == Qt.Key_VolumeUp) {
304- volumeUpKeyPressed = false;
305- aVolumeKeyWasReleased = true;
306- } else if (key == Qt.Key_VolumeDown) {
307- volumeDownKeyPressed = false;
308- aVolumeKeyWasReleased = true;
309- }
310- }
311-}
312
313=== modified file 'qml/Notifications/Notification.qml'
314--- qml/Notifications/Notification.qml 2014-11-10 09:14:30 +0000
315+++ qml/Notifications/Notification.qml 2015-02-16 22:20:44 +0000
316@@ -40,6 +40,7 @@
317 property bool fullscreen: false
318 property int maxHeight
319 property int margins
320+ readonly property bool draggable: (type === Notification.SnapDecision && state === "contracted") || type === Notification.Interactive || type === Notification.Ephemeral
321 readonly property bool darkOnBright: panel.indicators.shown || type === Notification.SnapDecision
322 readonly property color red: "#fc4949"
323 readonly property color green: "#3fb24f"
324@@ -52,7 +53,7 @@
325 implicitHeight: type !== Notification.PlaceHolder ? (fullscreen ? maxHeight : outterColumn.height - shapedBack.anchors.topMargin + contentSpacing * 2) : 0
326
327 color: (type === Notification.Confirmation && notificationList.useModal && !greeter.shown) || darkOnBright ? sdLightGrey : Qt.rgba(0.132, 0.117, 0.109, 0.97)
328- opacity: 1 // FIXME: 1 because of LP: #1354406 workaround, has to be 0 really
329+ opacity: 1 - (x / notification.width) // FIXME: non-zero initially because of LP: #1354406 workaround, we want this to start at 0 upon creation eventually
330
331 state: {
332 var result = "";
333@@ -80,18 +81,28 @@
334 id: sound
335 objectName: "sound"
336 audioRole: MediaPlayer.alert
337- source: hints["suppress-sound"] != "true" && hints["sound-file"] != undefined ? hints["sound-file"] : ""
338+ source: hints["suppress-sound"] !== "true" && hints["sound-file"] !== undefined ? hints["sound-file"] : ""
339 }
340
341 // FIXME: using onCompleted because of LP: #1354406 workaround, has to be onOpacityChanged really
342 Component.onCompleted: {
343- if (opacity == 1.0 && hints["suppress-sound"] != "true" && sound.source != "") {
344+ if (opacity == 1.0 && hints["suppress-sound"] !== "true" && sound.source !== "") {
345 sound.play();
346 }
347 }
348
349+ Behavior on x {
350+ id: normalXBehavior
351+
352+ enabled: draggable
353+ UbuntuNumberAnimation {
354+ duration: UbuntuAnimation.FastDuration
355+ easing.type: Easing.OutBounce
356+ }
357+ }
358+
359 onHintsChanged: {
360- if (type === Notification.Confirmation && opacity == 1.0 && hints["suppress-sound"] != "true" && sound.source != "") {
361+ if (type === Notification.Confirmation && opacity == 1.0 && hints["suppress-sound"] !== "true" && sound.source !== "") {
362 sound.play();
363 }
364 }
365@@ -146,6 +157,12 @@
366 opacity: parent.opacity
367 }
368
369+ onXChanged: {
370+ if (draggable && notification.x > 0.75 * notification.width) {
371+ notification.notification.close()
372+ }
373+ }
374+
375 Item {
376 id: contents
377 anchors.fill: fullscreen ? nonShapedBack : shapedBack
378@@ -169,7 +186,7 @@
379 actions: paths.actions
380 menuObjectPath: paths.menuObjectPath
381 onNameOwnerChanged: {
382- if (lastNameOwner != "" && nameOwner == "" && notification.notification != undefined) {
383+ if (lastNameOwner !== "" && nameOwner === "" && notification.notification !== undefined) {
384 notification.notification.close()
385 }
386 lastNameOwner = nameOwner
387@@ -181,6 +198,12 @@
388
389 anchors.fill: parent
390 objectName: "interactiveArea"
391+
392+ drag.target: draggable ? notification : undefined
393+ drag.axis: Drag.XAxis
394+ drag.minimumX: 0
395+ drag.maximumX: notification.width
396+
397 onClicked: {
398 if (notification.type == Notification.Interactive) {
399 notification.notification.invokeAction(actionRepeater.itemAt(0).actionId)
400@@ -188,6 +211,13 @@
401 notificationList.currentIndex = index;
402 }
403 }
404+ onReleased: {
405+ if (notification.x < notification.width / 2) {
406+ notification.x = 0
407+ } else {
408+ notification.x = notification.width
409+ }
410+ }
411 }
412
413 Column {
414@@ -447,7 +477,7 @@
415 right: parent.right
416 margins: contentSpacing
417 }
418- visible: notification.type == Notification.SnapDecision && actionRepeater.count > 0 && !oneOverTwoCase.visible
419+ visible: notification.type === Notification.SnapDecision && actionRepeater.count > 0 && !oneOverTwoCase.visible
420 spacing: units.gu(2)
421 layoutDirection: Qt.RightToLeft
422
423
424=== modified file 'qml/Notifications/Notifications.qml'
425--- qml/Notifications/Notifications.qml 2014-10-30 21:42:32 +0000
426+++ qml/Notifications/Notifications.qml 2015-02-16 22:20:44 +0000
427@@ -41,15 +41,11 @@
428 property bool topmostIsFullscreen: false
429 spacing: topmostIsFullscreen ? 0 : units.gu(.5)
430
431- // FIXME: This doesn't make any sense and results in a binding loop
432- currentIndex: (currentIndex < 1 && count > 1) ? 1 : -1
433+ currentIndex: count > 1 ? 1 : -1
434
435 delegate: Notification {
436 objectName: "notification" + index
437- anchors {
438- left: parent.left
439- right: parent.right
440- }
441+ width: parent.width
442 type: model.type
443 hints: model.hints
444 iconSource: model.icon
445
446=== modified file 'qml/Panel/IndicatorItem.qml'
447--- qml/Panel/IndicatorItem.qml 2015-01-14 10:17:12 +0000
448+++ qml/Panel/IndicatorItem.qml 2015-02-16 22:20:44 +0000
449@@ -95,6 +95,8 @@
450 id: itemImage
451 objectName: "icon"+index
452 height: iconHeight
453+ // FIXME Workaround for bug https://bugs.launchpad.net/ubuntu/+source/ubuntu-ui-toolkit/+bug/1421293
454+ width: implicitWidth > 0 && implicitHeight > 0 ? (implicitWidth / implicitHeight * height) : implicitWidth;
455 source: modelData
456 sets: ["status", "actions"]
457 color: root.color
458
459=== modified file 'qml/Shell.qml'
460--- qml/Shell.qml 2015-01-22 02:41:55 +0000
461+++ qml/Shell.qml 2015-02-16 22:20:44 +0000
462@@ -136,6 +136,15 @@
463 objectName: "dashCommunicator"
464 }
465
466+ PhysicalKeysMapper {
467+ id: physicalKeysMapper
468+
469+ onPowerKeyLongPressed: dialogs.showPowerDialog()
470+ onVolumeDownTriggered: volumeControl.volumeDown();
471+ onVolumeUpTriggered: volumeControl.volumeUp();
472+ onScreenshotTriggered: screenGrabber.capture();
473+ }
474+
475 ScreenGrabber {
476 id: screenGrabber
477 z: edgeDemo.z + 10
478@@ -148,45 +157,9 @@
479 value: launcher.shown || launcher.dashSwipe
480 }
481
482- VolumeKeyFilter {
483- id: volumeKeyFilter
484- onVolumeDownPressed: volumeControl.volumeDown()
485- onVolumeUpPressed: volumeControl.volumeUp()
486- onBothVolumeKeysPressed: screenGrabber.capture()
487- }
488-
489 WindowKeysFilter {
490- Keys.onPressed: {
491- // Nokia earpieces give TogglePlayPause, while the iPhone's earpiece gives Play
492- if (event.key == Qt.Key_MediaTogglePlayPause || event.key == Qt.Key_MediaPlay) {
493- event.accepted = callManager.handleMediaKey(false);
494- } else if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
495- // FIXME: We only consider power key presses if the screen is
496- // on because of bugs 1410830/1409003. The theory is that when
497- // those bugs are encountered, there is a >2s delay between the
498- // power press event and the power release event, which causes
499- // the shutdown dialog to appear on resume. So to avoid that
500- // symptom while we investigate the root cause, we simply won't
501- // initiate any dialogs when the screen is off.
502- if (Powerd.status === Powerd.On) {
503- dialogs.onPowerKeyPressed();
504- }
505- event.accepted = true;
506- } else {
507- volumeKeyFilter.onKeyPressed(event.key);
508- event.accepted = false;
509- }
510- }
511-
512- Keys.onReleased: {
513- if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
514- dialogs.onPowerKeyReleased();
515- event.accepted = true;
516- } else {
517- volumeKeyFilter.onKeyReleased(event.key);
518- event.accepted = false;
519- }
520- }
521+ Keys.onPressed: physicalKeysMapper.onKeyPressed(event);
522+ Keys.onReleased: physicalKeysMapper.onKeyReleased(event);
523 }
524
525 Item {
526@@ -364,7 +337,7 @@
527 onShownChanged: if (shown) greeter.lockedApp = ""
528
529 function maybeShow() {
530- if (!shell.forcedUnlock) {
531+ if (!shell.forcedUnlock && !shown) {
532 showNow();
533 }
534 }
535
536=== modified file 'tests/mocks/Unity/Notifications/CMakeLists.txt'
537--- tests/mocks/Unity/Notifications/CMakeLists.txt 2014-11-05 00:17:50 +0000
538+++ tests/mocks/Unity/Notifications/CMakeLists.txt 2015-02-16 22:20:44 +0000
539@@ -4,7 +4,8 @@
540
541 set(MockNotificationsPlugin_SOURCES
542 plugin.cpp
543- MockNotificationTypes.cpp
544+ MockNotification.cpp
545+ MockNotificationModel.cpp
546 MockActionModel.cpp
547 )
548
549
550=== modified file 'tests/mocks/Unity/Notifications/MockActionModel.cpp'
551--- tests/mocks/Unity/Notifications/MockActionModel.cpp 2014-11-05 00:17:50 +0000
552+++ tests/mocks/Unity/Notifications/MockActionModel.cpp 2015-02-16 22:20:44 +0000
553@@ -1,5 +1,5 @@
554 /*
555- * Copyright 2014 Canonical Ltd.
556+ * Copyright 2015 Canonical Ltd.
557 *
558 * This program is free software; you can redistribute it and/or modify
559 * it under the terms of the GNU Lesser General Public License as published by
560@@ -25,8 +25,6 @@
561 };
562
563 ActionModel::ActionModel(QObject *parent) : QStringListModel(parent), p(new ActionModelPrivate) {
564- insertAction("ok_id", "Ok");
565- insertAction("cancel_id", "Cancel");
566 }
567
568 ActionModel::~ActionModel() {
569@@ -66,7 +64,11 @@
570 return data(index(row, 0), role);
571 }
572
573-void ActionModel::insertAction(const QString &id, const QString &label) {
574+void ActionModel::append(const QString &id, const QString &label) {
575 p->ids.push_back(id);
576 p->labels.push_back(label);
577 }
578+
579+int ActionModel::getCount() const {
580+ return p->labels.size();
581+}
582
583=== modified file 'tests/mocks/Unity/Notifications/MockActionModel.h'
584--- tests/mocks/Unity/Notifications/MockActionModel.h 2014-11-05 00:17:50 +0000
585+++ tests/mocks/Unity/Notifications/MockActionModel.h 2015-02-16 22:20:44 +0000
586@@ -1,5 +1,5 @@
587 /*
588- * Copyright 2014 Canonical Ltd.
589+ * Copyright 2015 Canonical Ltd.
590 *
591 * This program is free software; you can redistribute it and/or modify
592 * it under the terms of the GNU Lesser General Public License as published by
593@@ -26,6 +26,7 @@
594
595 class ActionModel : public QStringListModel {
596 Q_OBJECT
597+ Q_PROPERTY(int count READ getCount)
598
599 public:
600 ActionModel(QObject *parent=nullptr);
601@@ -42,7 +43,8 @@
602 };
603 Q_INVOKABLE QVariant data(int row, int role) const;
604
605- void insertAction(const QString &id, const QString &label);
606+ Q_INVOKABLE void append(const QString &id, const QString &label);
607+ int getCount() const;
608
609 private:
610 QScopedPointer<ActionModelPrivate> p;
611
612=== renamed file 'tests/mocks/Unity/Notifications/MockNotificationTypes.cpp' => 'tests/mocks/Unity/Notifications/MockNotification.cpp'
613--- tests/mocks/Unity/Notifications/MockNotificationTypes.cpp 2014-11-05 00:17:50 +0000
614+++ tests/mocks/Unity/Notifications/MockNotification.cpp 2015-02-16 22:20:44 +0000
615@@ -1,5 +1,5 @@
616 /*
617- * Copyright 2014 Canonical Ltd.
618+ * Copyright 2015 Canonical Ltd.
619 *
620 * This program is free software; you can redistribute it and/or modify
621 * it under the terms of the GNU Lesser General Public License as published by
622@@ -17,10 +17,162 @@
623 * Mirco Mueller <mirco.mueller@canonical.com>
624 */
625
626-#include "MockNotificationTypes.h"
627-
628-MockNotification::MockNotification(QObject *parent) : QObject(parent) {
629+#include "MockNotification.h"
630+
631+#include <QDebug>
632+
633+struct MockNotificationPrivate {
634+ int id;
635+ QString summary;
636+ QString body;
637+ int value;
638+ MockNotification::Type type;
639+ QString icon;
640+ QString secondaryIcon;
641+ QStringList actions;
642+ ActionModel* actionsModel;
643+ QVariantMap hints;
644+};
645+
646+MockNotification::MockNotification(QObject *parent) : QObject(parent), p(new MockNotificationPrivate()) {
647+ p->actionsModel = new ActionModel();
648 }
649
650 MockNotification::~MockNotification() {
651 }
652+
653+QString MockNotification::getSummary() const {
654+ return p->summary;
655+}
656+
657+void MockNotification::setSummary(const QString &summary) {
658+ if(p->summary != summary) {
659+ p->summary = summary;
660+ Q_EMIT summaryChanged(p->summary);
661+ Q_EMIT dataChanged(p->id);
662+ }
663+}
664+
665+QString MockNotification::getBody() const {
666+ return p->body;
667+}
668+
669+void MockNotification::setBody(const QString &body) {
670+ if(p->body != body) {
671+ p->body = body;
672+ Q_EMIT bodyChanged(p->body);
673+ Q_EMIT dataChanged(p->id);
674+ }
675+}
676+
677+int MockNotification::getID() const {
678+ return p->id;
679+}
680+
681+void MockNotification::setID(const int id) {
682+ p->id = id;
683+}
684+
685+int MockNotification::getValue() const {
686+ return p->value;
687+}
688+
689+void MockNotification::setValue(int value) {
690+ if(p->value != value) {
691+ p->value = value;
692+ Q_EMIT valueChanged(p->value);
693+ Q_EMIT dataChanged(p->id);
694+ }
695+}
696+
697+QString MockNotification::getIcon() const {
698+ return p->icon;
699+}
700+
701+void MockNotification::setIcon(const QString &icon) {
702+ if (icon.startsWith(" ") || icon.size() == 0) {
703+ p->icon = nullptr;
704+ }
705+ else {
706+ p->icon = icon;
707+
708+ if (icon.indexOf("/") == -1) {
709+ p->icon.prepend("image://theme/");
710+ }
711+ }
712+
713+ Q_EMIT iconChanged(p->icon);
714+ Q_EMIT dataChanged(p->id);
715+}
716+
717+QString MockNotification::getSecondaryIcon() const {
718+ return p->secondaryIcon;
719+}
720+
721+void MockNotification::setSecondaryIcon(const QString &secondaryIcon) {
722+ if (secondaryIcon.startsWith(" ") || secondaryIcon.size() == 0) {
723+ p->secondaryIcon = nullptr;
724+ }
725+ else {
726+ p->secondaryIcon = secondaryIcon;
727+
728+ if (secondaryIcon.indexOf("/") == -1) {
729+ p->secondaryIcon.prepend("image://theme/");
730+ }
731+ }
732+
733+ Q_EMIT secondaryIconChanged(p->secondaryIcon);
734+ Q_EMIT dataChanged(p->id);
735+}
736+
737+MockNotification::Type MockNotification::getType() const {
738+ return p->type;
739+}
740+
741+void MockNotification::setType(Type type) {
742+ if(p->type != type) {
743+ p->type = type;
744+ Q_EMIT typeChanged(p->type);
745+ }
746+}
747+
748+ActionModel* MockNotification::getActions() const {
749+ return p->actionsModel;
750+}
751+
752+void MockNotification::setActions(const QStringList &actions) {
753+ if(p->actions != actions) {
754+ p->actions = actions;
755+ Q_EMIT actionsChanged(p->actions);
756+
757+ for (int i = 0; i < p->actions.size(); i += 2) {
758+ p->actionsModel->append(p->actions[i], p->actions[i+1]);
759+ }
760+ }
761+}
762+
763+QVariantMap MockNotification::getHints() const {
764+ return p->hints;
765+}
766+
767+void MockNotification::setHints(const QVariantMap& hints) {
768+ if (p->hints != hints) {
769+ p->hints = hints;
770+ Q_EMIT hintsChanged(p->hints);
771+ }
772+}
773+
774+void MockNotification::invokeAction(const QString &action) {
775+ for(int i=0; i<p->actions.size(); i++) {
776+ if(p->actions[i] == action) {
777+ Q_EMIT actionInvoked(action);
778+ qDebug() << "Info: invoked action" << action;
779+ return;
780+ }
781+ }
782+ fprintf(stderr, "Error: tried to invoke action not in actionList.\n");
783+}
784+
785+void MockNotification::close() {
786+ Q_EMIT completed(p->id);
787+}
788
789=== renamed file 'tests/mocks/Unity/Notifications/MockNotificationTypes.h' => 'tests/mocks/Unity/Notifications/MockNotification.h'
790--- tests/mocks/Unity/Notifications/MockNotificationTypes.h 2014-11-05 00:17:50 +0000
791+++ tests/mocks/Unity/Notifications/MockNotification.h 2015-02-16 22:20:44 +0000
792@@ -1,5 +1,5 @@
793 /*
794- * Copyright 2014 Canonical Ltd.
795+ * Copyright 2015 Canonical Ltd.
796 *
797 * This program is free software; you can redistribute it and/or modify
798 * it under the terms of the GNU Lesser General Public License as published by
799@@ -17,20 +17,78 @@
800 * Mirco Mueller <mirco.mueller@canonical.com>
801 */
802
803-#ifndef MOCK_NOTIFICATION_TYPES_H
804-#define MOCK_NOTIFICATION_TYPES_H
805+#ifndef MOCK_NOTIFICATION_H
806+#define MOCK_NOTIFICATION_H
807
808+#include "MockActionModel.h"
809 #include <QObject>
810+#include <QString>
811+#include <QStringList>
812+#include <QScopedPointer>
813+
814+struct MockNotificationPrivate;
815
816 class MockNotification : public QObject {
817 Q_OBJECT
818 Q_ENUMS(Type)
819+ Q_PROPERTY(QString summary READ getSummary WRITE setSummary NOTIFY summaryChanged)
820+ Q_PROPERTY(QString body READ getBody WRITE setBody NOTIFY bodyChanged)
821+ Q_PROPERTY(int nid READ getID WRITE setID NOTIFY idChanged)
822+ Q_PROPERTY(int value READ getValue WRITE setValue NOTIFY valueChanged)
823+ Q_PROPERTY(QString icon READ getIcon WRITE setIcon NOTIFY iconChanged)
824+ Q_PROPERTY(QString secondaryIcon READ getSecondaryIcon WRITE setSecondaryIcon NOTIFY secondaryIconChanged)
825+ Q_PROPERTY(Type type READ getType WRITE setType NOTIFY typeChanged)
826+ Q_PROPERTY(QStringList rawActions WRITE setActions)
827+ Q_PROPERTY(ActionModel* actions READ getActions NOTIFY actionsChanged)
828+ Q_PROPERTY(QVariantMap hints READ getHints WRITE setHints NOTIFY hintsChanged)
829+
830+private:
831+ QScopedPointer<MockNotificationPrivate> p;
832+
833+public:
834+ enum Urgency { Low, Normal, Critical };
835+ enum Type { PlaceHolder, Confirmation, Ephemeral, Interactive, SnapDecision };
836+
837+Q_SIGNALS:
838+ void summaryChanged(const QString &summary);
839+ void bodyChanged(const QString &body);
840+ void idChanged(const int id);
841+ void valueChanged(int value);
842+ void iconChanged(const QString &icon);
843+ void secondaryIconChanged(const QString &secondaryIcon);
844+ void typeChanged(Type type);
845+ void actionsChanged(const QStringList &actions);
846+ void hintsChanged(const QVariantMap& hints);
847+
848+ void dataChanged(int nid);
849+ void completed(int nid);
850+ void actionInvoked(const QString &action);
851
852 public:
853 MockNotification(QObject *parent=nullptr);
854 virtual ~MockNotification();
855
856- enum Type { PlaceHolder, Confirmation, Ephemeral, Interactive, SnapDecision };
857+ QString getSummary() const;
858+ void setSummary(const QString &summary);
859+ QString getBody() const;
860+ void setBody(const QString &body);
861+ int getID() const;
862+ void setID(const int id);
863+ int getValue() const;
864+ void setValue(int value);
865+ QString getIcon() const;
866+ void setIcon(const QString &icon);
867+ QString getSecondaryIcon() const;
868+ void setSecondaryIcon(const QString &secondaryIcon);
869+ Type getType() const;
870+ void setType(Type type);
871+ ActionModel* getActions() const;
872+ void setActions(const QStringList &actions);
873+ QVariantMap getHints() const;
874+ void setHints(const QVariantMap& hints);
875+
876+ Q_INVOKABLE void invokeAction(const QString &action);
877+ Q_INVOKABLE void close();
878 };
879
880-#endif // MOCK_NOTIFICATION_TYPES_H
881+#endif // MOCK_NOTIFICATION_H
882
883=== added file 'tests/mocks/Unity/Notifications/MockNotificationModel.cpp'
884--- tests/mocks/Unity/Notifications/MockNotificationModel.cpp 1970-01-01 00:00:00 +0000
885+++ tests/mocks/Unity/Notifications/MockNotificationModel.cpp 2015-02-16 22:20:44 +0000
886@@ -0,0 +1,172 @@
887+/*
888+ * Copyright 2015 Canonical Ltd.
889+ *
890+ * This program is free software; you can redistribute it and/or modify
891+ * it under the terms of the GNU Lesser General Public License as published by
892+ * the Free Software Foundation; version 3.
893+ *
894+ * This program is distributed in the hope that it will be useful,
895+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
896+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
897+ * GNU Lesser General Public License for more details.
898+ *
899+ * You should have received a copy of the GNU Lesser General Public License
900+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
901+ *
902+ * Authors:
903+ * Mirco Mueller <mirco.mueller@canonical.com>
904+ */
905+
906+#include "MockNotificationModel.h"
907+#include "MockNotification.h"
908+
909+#include <unity/shell/notifications/ModelInterface.h>
910+
911+#include <QTimer>
912+#include <QList>
913+#include <QVector>
914+#include <QMap>
915+#include <QStringListModel>
916+#include <QQmlEngine>
917+
918+using namespace unity::shell::notifications;
919+
920+MockNotificationModel::MockNotificationModel(QObject *parent) : QAbstractListModel(parent) {
921+}
922+
923+MockNotificationModel::~MockNotificationModel() {
924+}
925+
926+int MockNotificationModel::rowCount(const QModelIndex &parent) const {
927+ return m_queue.size();
928+}
929+
930+int MockNotificationModel::getCount() const {
931+ return m_queue.size();
932+}
933+
934+QVariant MockNotificationModel::data(const QModelIndex &index, int role) const {
935+ if (!index.isValid())
936+ return QVariant();
937+
938+ switch(role) {
939+ case ModelInterface::RoleType:
940+ return QVariant(m_queue[index.row()]->getType());
941+
942+ case ModelInterface::RoleId:
943+ return QVariant(m_queue[index.row()]->getID());
944+
945+ case ModelInterface::RoleSummary:
946+ return QVariant(m_queue[index.row()]->getSummary());
947+
948+ case ModelInterface::RoleBody:
949+ return QVariant(m_queue[index.row()]->getBody());
950+
951+ case ModelInterface::RoleValue:
952+ return QVariant(m_queue[index.row()]->getValue());
953+
954+ case ModelInterface::RoleIcon:
955+ return QVariant(m_queue[index.row()]->getIcon());
956+
957+ case ModelInterface::RoleSecondaryIcon:
958+ return QVariant(m_queue[index.row()]->getSecondaryIcon());
959+
960+ case ModelInterface::RoleActions:
961+ return QVariant::fromValue(m_queue[index.row()]->getActions());
962+
963+ case ModelInterface::RoleHints:
964+ return QVariant(m_queue[index.row()]->getHints());
965+
966+ case ModelInterface::RoleNotification:
967+ return QVariant::fromValue(m_queue[index.row()]);
968+
969+ default:
970+ return QVariant();
971+ }
972+}
973+
974+void MockNotificationModel::append(MockNotification* n) {
975+ int location = m_queue.size();
976+ QModelIndex insertionPoint = QModelIndex();
977+ beginInsertRows(insertionPoint, location, location);
978+ m_queue.insert(location, n);
979+ endInsertRows();
980+}
981+
982+MockNotification* MockNotificationModel::getNotification(int id) const {
983+ for(int i=0; i < m_queue.size(); i++) {
984+ if(m_queue[i]->getID() == id) {
985+ return m_queue[i];
986+ }
987+ }
988+
989+ return nullptr;
990+}
991+
992+void MockNotificationModel::remove(const int id) {
993+ for(int i = 0; i < m_queue.size(); i++) {
994+ if(m_queue[i]->getID() == id) {
995+ removeInternal(i);
996+ return;
997+ }
998+ }
999+}
1000+
1001+void MockNotificationModel::removeSecond() {
1002+ if(m_queue.size() < 2)
1003+ return;
1004+ removeInternal(1);
1005+}
1006+
1007+void MockNotificationModel::removeInternal(int loc) {
1008+ QModelIndex deletePoint = QModelIndex();
1009+ beginRemoveRows(deletePoint, loc, loc);
1010+ m_queue.erase(m_queue.begin() + loc);
1011+ endRemoveRows();
1012+}
1013+
1014+MockNotification* MockNotificationModel::getRaw(const int notificationId) const {
1015+ for(int i = 0; i < m_queue.size(); i++) {
1016+ if(m_queue[i]->getID() == notificationId) {
1017+ MockNotification* n = m_queue[i];
1018+ return n;
1019+ }
1020+ }
1021+
1022+ return nullptr;
1023+}
1024+
1025+int MockNotificationModel::queued() const {
1026+ return m_queue.size();
1027+}
1028+
1029+QHash<int, QByteArray> MockNotificationModel::roleNames() const {
1030+ QHash<int, QByteArray> roles;
1031+
1032+ roles.insert(ModelInterface::RoleType, "type");
1033+ roles.insert(ModelInterface::RoleUrgency, "urgency");
1034+ roles.insert(ModelInterface::RoleId, "id");
1035+ roles.insert(ModelInterface::RoleSummary, "summary");
1036+ roles.insert(ModelInterface::RoleBody, "body");
1037+ roles.insert(ModelInterface::RoleValue, "value");
1038+ roles.insert(ModelInterface::RoleIcon, "icon");
1039+ roles.insert(ModelInterface::RoleSecondaryIcon, "secondaryIcon");
1040+ roles.insert(ModelInterface::RoleActions, "actions");
1041+ roles.insert(ModelInterface::RoleHints, "hints");
1042+ roles.insert(ModelInterface::RoleNotification, "notification");
1043+
1044+ return roles;
1045+}
1046+
1047+void MockNotificationModel::onCompleted(int id) {
1048+ remove(id);
1049+}
1050+
1051+void MockNotificationModel::onDataChanged(int id) {
1052+ for(int i = 0; i < m_queue.size(); i++) {
1053+ if(m_queue[i]->getID() == id) {
1054+ Q_EMIT dataChanged(index(i, 0), index(i, 0));
1055+ break;
1056+ }
1057+ }
1058+}
1059
1060=== added file 'tests/mocks/Unity/Notifications/MockNotificationModel.h'
1061--- tests/mocks/Unity/Notifications/MockNotificationModel.h 1970-01-01 00:00:00 +0000
1062+++ tests/mocks/Unity/Notifications/MockNotificationModel.h 2015-02-16 22:20:44 +0000
1063@@ -0,0 +1,72 @@
1064+/*
1065+ * Copyright 2015 Canonical Ltd.
1066+ *
1067+ * This program is free software; you can redistribute it and/or modify
1068+ * it under the terms of the GNU Lesser General Public License as published by
1069+ * the Free Software Foundation; version 3.
1070+ *
1071+ * This program is distributed in the hope that it will be useful,
1072+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1073+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1074+ * GNU Lesser General Public License for more details.
1075+ *
1076+ * You should have received a copy of the GNU Lesser General Public License
1077+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1078+ *
1079+ * Authors:
1080+ * Mirco Mueller <mirco.mueller@canonical.com>
1081+ */
1082+
1083+#ifndef MOCK_NOTIFICATION_MODEL_H
1084+#define MOCK_NOTIFICATION_MODEL_H
1085+
1086+#include <QAbstractListModel>
1087+#include <QSharedPointer>
1088+#include <QScopedPointer>
1089+#include "MockNotification.h"
1090+
1091+class MockNotification;
1092+
1093+class MockNotificationModel : public QAbstractListModel {
1094+ Q_OBJECT
1095+ Q_PROPERTY(int count READ getCount)
1096+
1097+public:
1098+ MockNotificationModel(QObject *parent=nullptr);
1099+ virtual ~MockNotificationModel();
1100+
1101+ virtual int rowCount(const QModelIndex &parent) const;
1102+ virtual QVariant data(const QModelIndex &index, int role) const;
1103+ virtual QHash<int, QByteArray> roleNames() const;
1104+
1105+ Q_INVOKABLE void append(MockNotification* n);
1106+ MockNotification* getNotification(int id) const;
1107+
1108+ // getRaw() is only meant to be used from QML, since QML cannot handle
1109+ // QSharedPointers... on C++-side only use getNotification()
1110+ Q_INVOKABLE MockNotification* getRaw(const int notificationId) const;
1111+
1112+ Q_INVOKABLE int queued() const;
1113+ Q_INVOKABLE void remove(const int id);
1114+ Q_INVOKABLE void removeSecond();
1115+
1116+ int getCount() const;
1117+
1118+Q_SIGNALS:
1119+ void actionInvoked(const QString &action);
1120+
1121+public Q_SLOTS:
1122+ void onCompleted(int id);
1123+
1124+private Q_SLOTS:
1125+ void onDataChanged(int id);
1126+
1127+Q_SIGNALS:
1128+ void queueSizeChanged(int newSize);
1129+
1130+private:
1131+ QList<MockNotification*> m_queue;
1132+ void removeInternal(int loc);
1133+};
1134+
1135+#endif
1136
1137=== modified file 'tests/mocks/Unity/Notifications/plugin.cpp'
1138--- tests/mocks/Unity/Notifications/plugin.cpp 2014-11-05 00:17:50 +0000
1139+++ tests/mocks/Unity/Notifications/plugin.cpp 2015-02-16 22:20:44 +0000
1140@@ -1,5 +1,5 @@
1141 /*
1142- * Copyright 2014 Canonical Ltd.
1143+ * Copyright 2015 Canonical Ltd.
1144 *
1145 * This program is free software; you can redistribute it and/or modify
1146 * it under the terms of the GNU Lesser General Public License as published by
1147@@ -19,13 +19,15 @@
1148
1149 #include "plugin.h"
1150 #include "MockActionModel.h"
1151-#include "MockNotificationTypes.h"
1152+#include "MockNotification.h"
1153+#include "MockNotificationModel.h"
1154
1155 #include <QtQml/qqml.h>
1156
1157 void TestNotificationPlugin::registerTypes(const char* uri)
1158 {
1159 // @uri Unity.Notifications
1160- qmlRegisterUncreatableType<MockNotification>(uri, 1, 0, "Notification", "Notification objects can only be created by the plugin");
1161+ qmlRegisterType<MockNotification>(uri, 1, 0, "Notification");
1162+ qmlRegisterType<MockNotificationModel>(uri, 1, 0, "NotificationModel");
1163 qmlRegisterType<ActionModel>(uri, 1, 0, "ActionModel");
1164 }
1165
1166=== modified file 'tests/mocks/Unity/Notifications/plugin.h'
1167--- tests/mocks/Unity/Notifications/plugin.h 2014-11-05 00:17:50 +0000
1168+++ tests/mocks/Unity/Notifications/plugin.h 2015-02-16 22:20:44 +0000
1169@@ -1,5 +1,5 @@
1170 /*
1171- * Copyright 2014 Canonical Ltd.
1172+ * Copyright 2015 Canonical Ltd.
1173 *
1174 * This program is free software; you can redistribute it and/or modify
1175 * it under the terms of the GNU Lesser General Public License as published by
1176@@ -17,7 +17,6 @@
1177 * Mirco Mueller <mirco.mueller@canonical.com>
1178 */
1179
1180-
1181 #ifndef TESTNOTIFICATION_PLUGIN_H
1182 #define TESTNOTIFICATION_PLUGIN_H
1183
1184
1185=== modified file 'tests/qmltests/CMakeLists.txt'
1186--- tests/qmltests/CMakeLists.txt 2015-01-14 10:17:12 +0000
1187+++ tests/qmltests/CMakeLists.txt 2015-02-16 22:20:44 +0000
1188@@ -13,6 +13,7 @@
1189 set(qmltest_DEFAULT_NO_ADD_TEST FALSE)
1190 set(qmltest_DEFAULT_PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=minimal")
1191 add_qml_test(utils/Unity/Test UnityTest)
1192+add_qml_test(Components PhysicalKeysMapper)
1193
1194 set(qmltest_DEFAULT_TARGETS qmluitests)
1195 set(qmltest_DEFAULT_NO_ADD_TEST TRUE)
1196
1197=== added file 'tests/qmltests/Components/tst_PhysicalKeysMapper.qml'
1198--- tests/qmltests/Components/tst_PhysicalKeysMapper.qml 1970-01-01 00:00:00 +0000
1199+++ tests/qmltests/Components/tst_PhysicalKeysMapper.qml 2015-02-16 22:20:44 +0000
1200@@ -0,0 +1,119 @@
1201+/*
1202+ * Copyright (C) 2014-2015 Canonical, Ltd.
1203+ *
1204+ * This program is free software; you can redistribute it and/or modify
1205+ * it under the terms of the GNU General Public License as published by
1206+ * the Free Software Foundation; version 3.
1207+ *
1208+ * This program is distributed in the hope that it will be useful,
1209+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1210+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1211+ * GNU General Public License for more details.
1212+ *
1213+ * You should have received a copy of the GNU General Public License
1214+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1215+ */
1216+
1217+import QtQuick 2.0
1218+import QtTest 1.0
1219+import "../../../qml/Components"
1220+
1221+TestCase {
1222+ name: "PhysicalKeysMapper"
1223+
1224+ Loader {
1225+ // Using a Loader here to make sure mapper state is coherent
1226+ // regardless of unmatched KeyPress and KeyRelease events
1227+ id: loader
1228+ active: false
1229+ sourceComponent: PhysicalKeysMapper { }
1230+ }
1231+
1232+ SignalSpy {
1233+ id: powerSpy
1234+ target: loader.item
1235+ signalName: "powerKeyLongPressed"
1236+ }
1237+
1238+ SignalSpy {
1239+ id: volumeDownSpy
1240+ target: loader.item
1241+ signalName: "volumeDownTriggered"
1242+ }
1243+
1244+ SignalSpy {
1245+ id: volumeUpSpy
1246+ target: loader.item
1247+ signalName: "volumeUpTriggered"
1248+ }
1249+
1250+ SignalSpy {
1251+ id: screenshotSpy
1252+ target: loader.item
1253+ signalName: "screenshotTriggered"
1254+ }
1255+
1256+ function init() {
1257+ loader.active = true;
1258+ tryCompare(loader.status == Loader.Ready);
1259+ }
1260+
1261+ function cleanup() {
1262+ loader.active = false;
1263+ powerSpy.clear();
1264+ volumeDownSpy.clear();
1265+ volumeUpSpy.clear();
1266+ screenshotSpy.clear();
1267+ }
1268+
1269+ function test_LongPressPowerButton() {
1270+ loader.item.onKeyPressed({ key: Qt.Key_PowerDown });
1271+
1272+ expectFailContinue("", "Power signal should not be emitted within a second");
1273+ powerSpy.wait(1000);
1274+ powerSpy.clear();
1275+
1276+ loader.item.onKeyPressed({ key: Qt.Key_PowerDown, isAutoRepeat: true });
1277+ powerSpy.wait(1500);
1278+ }
1279+
1280+ function test_ScreenshotButtons_data() {
1281+ return [
1282+ { tag: "UpFirst", first: Qt.Key_VolumeUp, second: Qt.Key_VolumeDown },
1283+ { tag: "DownFirst", first: Qt.Key_VolumeDown, second: Qt.Key_VolumeUp },
1284+ ];
1285+ }
1286+
1287+ function test_ScreenshotButtons(data) {
1288+ loader.item.onKeyPressed({ key: data.first });
1289+ loader.item.onKeyPressed({ key: data.second });
1290+ screenshotSpy.wait();
1291+ loader.item.onKeyReleased({ key: data.first });
1292+ loader.item.onKeyReleased({ key: data.second });
1293+ expectFailContinue("", "VolumeUp signal should not fire");
1294+ volumeUpSpy.wait(100);
1295+ expectFailContinue("", "VolumeDown signal should not fire");
1296+ volumeDownSpy.wait(100);
1297+ }
1298+
1299+ function test_VolumeButton_data() {
1300+ return [
1301+ { tag: "Down", key: Qt.Key_VolumeDown, spy: volumeDownSpy },
1302+ { tag: "Up", key: Qt.Key_VolumeUp, spy: volumeUpSpy },
1303+ ];
1304+ }
1305+
1306+ function test_VolumeButton(data) {
1307+ loader.item.onKeyPressed({ key: data.key });
1308+ expectFailContinue("", "Signal should not fire on press");
1309+ data.spy.wait(100);
1310+ data.spy.clear();
1311+
1312+ loader.item.onKeyReleased({ key: data.key });
1313+ data.spy.wait();
1314+
1315+ loader.item.onKeyPressed({ key: data.key });
1316+ loader.item.onKeyPressed({ key: data.key, isAutoRepeat: true });
1317+ data.spy.wait();
1318+ }
1319+}
1320
1321=== modified file 'tests/qmltests/Dash/tst_Card.qml'
1322--- tests/qmltests/Dash/tst_Card.qml 2015-01-08 12:51:33 +0000
1323+++ tests/qmltests/Dash/tst_Card.qml 2015-02-16 22:20:44 +0000
1324@@ -60,8 +60,8 @@
1325 "layout": { "components": Helpers.update(JSON.parse(Helpers.fullMapping), { "art": { "aspect-ratio": 0.7 } }) }
1326 },
1327 {
1328- "name": "Art, header, summary - horizontal",
1329- "layout": { "template": { "card-layout": "horizontal" },
1330+ "name": "Art, header, summary, background - horizontal",
1331+ "layout": { "template": { "card-layout": "horizontal", "card-background": { "type": "gradient", "elements": ["grey", "white"] } },
1332 "components": JSON.parse(Helpers.fullMapping) }
1333 },
1334 {
1335@@ -101,6 +101,11 @@
1336 "layout": { "template": { "overlay": true },
1337 "components": { "art": "art", "title": "title" } }
1338 },
1339+ {
1340+ "name": "Art, header, summary - horizontal",
1341+ "layout": { "template": { "card-layout": "horizontal" },
1342+ "components": JSON.parse(Helpers.fullMapping) }
1343+ },
1344 ]
1345
1346 CardTool {
1347@@ -407,7 +412,7 @@
1348 return [
1349 { tag: "Art and summary", visible: true, color: "#ffffff", index: 0 },
1350 { tag: "No Summary", visible: false, index: 6 },
1351- { tag: "Horizontal", visible: false, index: 5 },
1352+ { tag: "Horizontal", visible: true, color: "#808080", index: 5 },
1353 { tag: "Grey background", visible: true, color: "#808080", index: 10 },
1354 { tag: "Overriden Gradient background", visible: true, color: "#808080", gradientColor: "#ffffff",
1355 background: {type: "color", elements: ["grey", "white"]}, index: 10 },
1356@@ -415,6 +420,7 @@
1357 background: Qt.resolvedUrl("artwork/checkers.png"), index: 10 },
1358 { tag: "Gradient background", visible: true, color: "#808080", gradientColor: "#ffffff", index: 11 },
1359 { tag: "Image background", visible: true, image: Qt.resolvedUrl("artwork/checkers.png"), index: 12 },
1360+ { tag: "Horizontal no background", visible: false, index: 14 },
1361 ];
1362 }
1363
1364
1365=== added file 'tests/qmltests/Notifications/Notification.qml'
1366--- tests/qmltests/Notifications/Notification.qml 1970-01-01 00:00:00 +0000
1367+++ tests/qmltests/Notifications/Notification.qml 2015-02-16 22:20:44 +0000
1368@@ -0,0 +1,31 @@
1369+/*
1370+ * Copyright 2015 Canonical Ltd.
1371+ *
1372+ * This program is free software; you can redistribute it and/or modify
1373+ * it under the terms of the GNU Lesser General Public License as published by
1374+ * the Free Software Foundation; version 3.
1375+ *
1376+ * This program is distributed in the hope that it will be useful,
1377+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1378+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1379+ * GNU Lesser General Public License for more details.
1380+ *
1381+ * You should have received a copy of the GNU Lesser General Public License
1382+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1383+ *
1384+ * Authors:
1385+ * Mirco Mueller <mirco.mueller@canonical.com>
1386+ */
1387+
1388+import Unity.Notifications 1.0
1389+
1390+Notification {
1391+ nid: 0
1392+ type: Notification.PlaceHolder
1393+ summary: ""
1394+ body: ""
1395+ icon: ""
1396+ secondaryIcon: ""
1397+ value: 0
1398+ rawActions: []
1399+}
1400
1401=== modified file 'tests/qmltests/Notifications/tst_Notifications.qml'
1402--- tests/qmltests/Notifications/tst_Notifications.qml 2014-11-10 09:14:30 +0000
1403+++ tests/qmltests/Notifications/tst_Notifications.qml 2015-02-16 22:20:44 +0000
1404@@ -1,17 +1,20 @@
1405 /*
1406- * Copyright (C) 2013 Canonical, Ltd.
1407+ * Copyright 2015 Canonical Ltd.
1408 *
1409 * This program is free software; you can redistribute it and/or modify
1410- * it under the terms of the GNU General Public License as published by
1411+ * it under the terms of the GNU Lesser General Public License as published by
1412 * the Free Software Foundation; version 3.
1413 *
1414 * This program is distributed in the hope that it will be useful,
1415 * but WITHOUT ANY WARRANTY; without even the implied warranty of
1416 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1417- * GNU General Public License for more details.
1418+ * GNU Lesser General Public License for more details.
1419 *
1420- * You should have received a copy of the GNU General Public License
1421+ * You should have received a copy of the GNU Lesser General Public License
1422 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1423+ *
1424+ * Authors:
1425+ * Mirco Mueller <mirco.mueller@canonical.com>
1426 */
1427
1428 import QtQuick 2.0
1429@@ -24,146 +27,134 @@
1430 import QtMultimedia 5.0
1431
1432 Item {
1433+ id: foobar
1434+
1435 width: notificationsRect.width + interactiveControls.width
1436 height: notificationsRect.height
1437+ property int index: 0
1438
1439 Row {
1440 id: rootRow
1441
1442- Component {
1443- id: mockNotification
1444-
1445- QtObject {
1446- function invokeAction(actionId) {
1447- mockModel.actionInvoked(actionId)
1448- }
1449- }
1450- }
1451-
1452- ListModel {
1453+ NotificationModel {
1454 id: mockModel
1455- dynamicRoles: true
1456-
1457- signal actionInvoked(string actionId)
1458-
1459- function getRaw(id) {
1460- return mockNotification.createObject(mockModel)
1461- }
1462
1463 // add the default/PlaceHolder notification to the model
1464 Component.onCompleted: {
1465- var n = {
1466- type: Notification.PlaceHolder,
1467- hints: {},
1468- summary: "",
1469- body: "",
1470- icon: "",
1471- secondaryIcon: "",
1472- actions: []
1473- }
1474-
1475+ var component = Qt.createComponent("Notification.qml")
1476+ var n = component.createObject("notification", {"nid": index++,
1477+ "type": Notification.PlaceHolder,
1478+ "hints": {},
1479+ "summary": "",
1480+ "body": "",
1481+ "icon": "",
1482+ "secondaryIcon": "",
1483+ "rawActions": []})
1484+ n.completed.connect(mockModel.onCompleted)
1485 append(n)
1486 }
1487 }
1488
1489 function add2over1SnapDecisionNotification() {
1490- var n = {
1491- type: Notification.SnapDecision,
1492- hints: {"x-canonical-private-affirmative-tint": "true"},
1493- summary: "Theatre at Ferria Stadium",
1494- body: "at Ferria Stadium in Bilbao, Spain\n07578545317",
1495- icon: "",
1496- secondaryIcon: "",
1497- actions: [{ id: "ok_id", label: "Ok"},
1498- { id: "snooze_id", label: "Snooze"},
1499- { id: "view_id", label: "View"}]
1500- }
1501-
1502+ var component = Qt.createComponent("Notification.qml")
1503+ var n = component.createObject("notification", {"nid": index++,
1504+ "type": Notification.SnapDecision,
1505+ "hints": {"x-canonical-private-affirmative-tint": "true"},
1506+ "summary": "Theatre at Ferria Stadium",
1507+ "body": "at Ferria Stadium in Bilbao, Spain\n07578545317",
1508+ "icon": "",
1509+ "secondaryIcon": "",
1510+ "rawActions": ["ok_id", "Ok",
1511+ "snooze_id", "Snooze",
1512+ "view_id", "View"]})
1513+ n.completed.connect(mockModel.onCompleted)
1514 mockModel.append(n)
1515 }
1516
1517 function addEphemeralNotification() {
1518- var n = {
1519- type: Notification.Ephemeral,
1520- summary: "Cole Raby",
1521- body: "I did not expect it to be that late.",
1522- icon: "../graphics/avatars/amanda.png",
1523- secondaryIcon: "../graphics/applicationIcons/facebook.png",
1524- actions: []
1525- }
1526-
1527+ var component = Qt.createComponent("Notification.qml")
1528+ var n = component.createObject("notification", {"nid": index++,
1529+ "type": Notification.Ephemeral,
1530+ "hints": {},
1531+ "summary": "Cole Raby",
1532+ "body": "I did not expect it to be that late.",
1533+ "icon": "../graphics/avatars/amanda.png",
1534+ "secondaryIcon": "../graphics/applicationIcons/facebook.png",
1535+ "rawActions": ["reply_id", "Dummy"]})
1536+ n.completed.connect(mockModel.onCompleted)
1537 mockModel.append(n)
1538 }
1539
1540 function addEphemeralNonShapedIconNotification() {
1541- var n = {
1542- type: Notification.Ephemeral,
1543- hints: {"x-canonical-non-shaped-icon": "true"},
1544- summary: "Contacts",
1545- body: "Synchronised contacts-database with cloud-storage.",
1546- icon: "../graphics/applicationIcons/contacts-app.png",
1547- secondaryIcon: "",
1548- actions: []
1549- }
1550-
1551+ var component = Qt.createComponent("Notification.qml")
1552+ var n = component.createObject("notification", {"nid": index++,
1553+ "type": Notification.Ephemeral,
1554+ "hints": {"x-canonical-non-shaped-icon": "true"},
1555+ "summary": "Contacts",
1556+ "body": "Synchronised contacts-database with cloud-storage.",
1557+ "icon": "../graphics/applicationIcons/contacts-app.png",
1558+ "secondaryIcon": "",
1559+ "rawActions": ["reply_id", "Dummy"]})
1560+ n.completed.connect(mockModel.onCompleted)
1561 mockModel.append(n)
1562 }
1563
1564 function addEphemeralIconSummaryNotification() {
1565- var n = {
1566- type: Notification.Ephemeral,
1567- hints: {"x-canonical-non-shaped-icon": "false"},
1568- summary: "Photo upload completed",
1569- body: "",
1570- icon: "../graphics/applicationIcons/facebook.png",
1571- secondaryIcon: "",
1572- actions: []
1573- }
1574-
1575+ var component = Qt.createComponent("Notification.qml")
1576+ var n = component.createObject("notification", {"nid": index++,
1577+ "type": Notification.Ephemeral,
1578+ "hints": {"x-canonical-non-shaped-icon": "false"},
1579+ "summary": "Photo upload completed",
1580+ "body": "",
1581+ "icon": "../graphics/applicationIcons/facebook.png",
1582+ "secondaryIcon": "",
1583+ "rawActions": ["reply_id", "Dummy"]})
1584+ n.completed.connect(mockModel.onCompleted)
1585 mockModel.append(n)
1586 }
1587
1588 function addInteractiveNotification() {
1589- var n = {
1590- type: Notification.Interactive,
1591- summary: "Interactive notification",
1592- body: "This is a notification that can be clicked",
1593- icon: "../graphics/avatars/anna_olsson.png",
1594- secondaryIcon: "",
1595- actions: [{ id: "reply_id", label: "Dummy"}],
1596- }
1597-
1598+ var component = Qt.createComponent("Notification.qml")
1599+ var n = component.createObject("notification", {"nid": index++,
1600+ "type": Notification.Interactive,
1601+ "hints": {},
1602+ "summary": "Interactive notification",
1603+ "body": "This is a notification that can be clicked",
1604+ "icon": "../graphics/avatars/anna_olsson.png",
1605+ "secondaryIcon": "",
1606+ "rawActions": ["reply_id", "Dummy"]})
1607+ n.completed.connect(mockModel.onCompleted)
1608 mockModel.append(n)
1609 }
1610
1611 function addConfirmationNotification() {
1612- var n = {
1613- type: Notification.Confirmation,
1614- hints: {"x-canonical-non-shaped-icon": "true"},
1615- summary: "Confirmation notification",
1616- body: "",
1617- icon: "image://theme/audio-volume-medium",
1618- secondaryIcon: "",
1619- value: 50,
1620- actions: [],
1621- }
1622-
1623+ var component = Qt.createComponent("Notification.qml")
1624+ var n = component.createObject("notification", {"nid": index++,
1625+ "type": Notification.Confirmation,
1626+ "hints": {"x-canonical-non-shaped-icon": "true"},
1627+ "summary": "Confirmation notification",
1628+ "body": "",
1629+ "icon": "image://theme/audio-volume-medium",
1630+ "secondaryIcon": "",
1631+ "value": 50,
1632+ "rawActions": ["reply_id", "Dummy"]})
1633+ n.completed.connect(mockModel.onCompleted)
1634 mockModel.append(n)
1635 }
1636
1637 function add2ndConfirmationNotification() {
1638- var n = {
1639- type: Notification.Confirmation,
1640- hints: {"x-canonical-non-shaped-icon": "true",
1641- "x-canonical-value-bar-tint": "true"},
1642- summary: "Confirmation notification",
1643- body: "High Volume",
1644- icon: "image://theme/audio-volume-high",
1645- secondaryIcon: "",
1646- value: 85,
1647- actions: [],
1648- }
1649-
1650+ var component = Qt.createComponent("Notification.qml")
1651+ var n = component.createObject("notification", {"nid": index++,
1652+ "type": Notification.Confirmation,
1653+ "hints": {"x-canonical-non-shaped-icon": "true",
1654+ "x-canonical-value-bar-tint": "true"},
1655+ "summary": "Confirmation notification",
1656+ "body": "High Volume",
1657+ "icon": "image://theme/audio-volume-high",
1658+ "secondaryIcon": "",
1659+ "value": 85,
1660+ "rawActions": ["reply_id", "Dummy"]})
1661+ n.completed.connect(mockModel.onCompleted)
1662 mockModel.append(n)
1663 }
1664
1665@@ -174,8 +165,9 @@
1666 }
1667
1668 function remove1stNotification() {
1669- if (mockModel.count > 1)
1670- mockModel.remove(1)
1671+ if (mockModel.count > 1) {
1672+ mockModel.removeSecond()
1673+ }
1674 }
1675
1676 Rectangle {
1677@@ -273,41 +265,122 @@
1678 name: "NotificationRendererTest"
1679 when: windowShown
1680
1681+ property list<Notification> nlist: [
1682+ Notification {
1683+ nid: 1
1684+ type: Notification.Ephemeral
1685+ summary: "Photo upload completed"
1686+ body: ""
1687+ icon: "../graphics/applicationIcons/facebook.png"
1688+ secondaryIcon: ""
1689+ value: 0
1690+ rawActions: []
1691+ },
1692+ Notification {
1693+ nid: 2
1694+ type: Notification.Ephemeral
1695+ hints: {"x-canonical-private-affirmative-tint": "false",
1696+ "sound-file": "dummy.ogg",
1697+ "suppress-sound": "true"}
1698+ summary: "New comment successfully published"
1699+ body: ""
1700+ icon: ""
1701+ secondaryIcon: "../graphics/applicationIcons/facebook.png"
1702+ value: 0
1703+ rawActions: []
1704+ },
1705+ Notification {
1706+ nid: 3
1707+ type: Notification.Interactive
1708+ hints: {"x-canonical-private-affirmative-tint": "false",
1709+ "sound-file": "dummy.ogg"}
1710+ summary: "Interactive notification"
1711+ body: "This is a notification that can be clicked"
1712+ icon: "../graphics/avatars/amanda.png"
1713+ secondaryIcon: ""
1714+ value: 0
1715+ rawActions: ["reply_id", "Dummy"]
1716+ },
1717+ Notification {
1718+ nid: 4
1719+ type: Notification.SnapDecision
1720+ hints: {"x-canonical-private-affirmative-tint": "false",
1721+ "sound-file": "dummy.ogg"}
1722+ summary: "Bro Coly"
1723+ 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."
1724+ icon: "../graphics/avatars/anna_olsson.png"
1725+ secondaryIcon: ""
1726+ value: 0
1727+ rawActions: ["accept_id", "Accept",
1728+ "reject_id", "Reject"]
1729+ },
1730+ Notification {
1731+ nid: 5
1732+ type: Notification.Ephemeral
1733+ hints: {"x-canonical-private-affirmative-tint": "false",
1734+ "sound-file": "dummy.ogg"}
1735+ summary: "Cole Raby"
1736+ body: "I did not expect it to be that late."
1737+ icon: "../graphics/avatars/funky.png"
1738+ secondaryIcon: "../graphics/applicationIcons/facebook.png"
1739+ value: 0
1740+ rawActions: []
1741+ },
1742+ Notification {
1743+ nid: 6
1744+ type: Notification.Ephemeral
1745+ hints: {"x-canonical-private-affirmative-tint": "false",
1746+ "x-canonical-non-shaped-icon": "true"}
1747+ summary: "Contacts"
1748+ body: "Synchronised contacts-database with cloud-storage."
1749+ icon: "image://theme/contacts-app"
1750+ secondaryIcon: ""
1751+ value: 0
1752+ rawActions: []
1753+ },
1754+ Notification {
1755+ nid: 7
1756+ type: Notification.Confirmation
1757+ hints: {"x-canonical-non-shaped-icon": "true"}
1758+ summary: ""
1759+ body: ""
1760+ icon: "image://theme/audio-volume-medium"
1761+ secondaryIcon: ""
1762+ value: 50
1763+ rawActions: []
1764+ },
1765+ Notification {
1766+ nid: 8
1767+ type: Notification.Confirmation
1768+ hints: {"x-canonical-non-shaped-icon": "true",
1769+ "x-canonical-value-bar-tint" : "true"}
1770+ summary: ""
1771+ body: "High Volume"
1772+ icon: "image://theme/audio-volume-high"
1773+ secondaryIcon: ""
1774+ value: 85
1775+ rawActions: []
1776+ },
1777+ Notification {
1778+ nid: 9
1779+ type: Notification.SnapDecision
1780+ hints: {"x-canonical-private-affirmative-tint": "true"}
1781+ summary: "Theatre at Ferria Stadium"
1782+ body: "at Ferria Stadium in Bilbao, Spain\n07578545317"
1783+ icon: ""
1784+ secondaryIcon: ""
1785+ value: 0
1786+ rawActions: ["ok_id", "Ok",
1787+ "snooze_id", "Snooze",
1788+ "view_id", "View"]
1789+ }
1790+ ]
1791+
1792 function test_NotificationRenderer_data() {
1793 return [
1794 {
1795- tag: "2-over-1 Snap Decision with button-tint",
1796- type: Notification.SnapDecision,
1797- hints: {"x-canonical-private-affirmative-tint": "true"},
1798- summary: "Theatre at Ferria Stadium",
1799- body: "at Ferria Stadium in Bilbao, Spain\n07578545317",
1800- icon: "",
1801- secondaryIcon: "",
1802- actions: [{ id: "ok_id", label: "Ok"},
1803- { id: "snooze_id", label: "Snooze"},
1804- { id: "view_id", label: "View"}],
1805- summaryVisible: true,
1806- bodyVisible: true,
1807- iconVisible: false,
1808- centeredIconVisible: false,
1809- shaped: false,
1810- secondaryIconVisible: false,
1811- buttonRowVisible: false,
1812- buttonTinted: true,
1813- hasSound: false,
1814- valueVisible: false,
1815- valueLabelVisible: false,
1816- valueTinted: false
1817- },
1818- {
1819 tag: "Ephemeral notification - icon-summary layout",
1820- type: Notification.Ephemeral,
1821- hints: {},
1822- summary: "Photo upload completed",
1823- body: "",
1824- icon: "../graphics/applicationIcons/facebook.png",
1825- secondaryIcon: "",
1826- actions: [],
1827+ n: nlist[0],
1828 summaryVisible: true,
1829 bodyVisible: false,
1830 iconVisible: true,
1831@@ -323,15 +396,7 @@
1832 },
1833 {
1834 tag: "Ephemeral notification - check suppression of secondary icon for icon-summary layout",
1835- type: Notification.Ephemeral,
1836- hints: {"x-canonical-private-affirmative-tint": "false",
1837- "sound-file": "dummy.ogg",
1838- "suppress-sound": "true"},
1839- summary: "New comment successfully published",
1840- body: "",
1841- icon: "",
1842- secondaryIcon: "../graphics/applicationIcons/facebook.png",
1843- actions: [],
1844+ n: nlist[1],
1845 summaryVisible: true,
1846 bodyVisible: false,
1847 interactiveAreaEnabled: false,
1848@@ -348,14 +413,7 @@
1849 },
1850 {
1851 tag: "Interactive notification",
1852- type: Notification.Interactive,
1853- hints: {"x-canonical-private-affirmative-tint": "false",
1854- "sound-file": "dummy.ogg"},
1855- summary: "Interactive notification",
1856- body: "This is a notification that can be clicked",
1857- icon: "../graphics/avatars/amanda.png",
1858- secondaryIcon: "",
1859- actions: [{ id: "reply_id", label: "Dummy"}],
1860+ n: nlist[2],
1861 summaryVisible: true,
1862 bodyVisible: true,
1863 iconVisible: true,
1864@@ -371,15 +429,7 @@
1865 },
1866 {
1867 tag: "Snap Decision without secondary icon and no button-tint",
1868- type: Notification.SnapDecision,
1869- hints: {"x-canonical-private-affirmative-tint": "false",
1870- "sound-file": "dummy.ogg"},
1871- summary: "Bro Coly",
1872- 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.",
1873- icon: "../graphics/avatars/anna_olsson.png",
1874- secondaryIcon: "",
1875- actions: [{ id: "accept_id", label: "Accept"},
1876- { id: "reject_id", label: "Reject"}],
1877+ n: nlist[3],
1878 summaryVisible: true,
1879 bodyVisible: true,
1880 iconVisible: true,
1881@@ -395,14 +445,7 @@
1882 },
1883 {
1884 tag: "Ephemeral notification",
1885- type: Notification.Ephemeral,
1886- hints: {"x-canonical-private-affirmative-tint": "false",
1887- "sound-file": "dummy.ogg"},
1888- summary: "Cole Raby",
1889- body: "I did not expect it to be that late.",
1890- icon: "../graphics/avatars/funky.png",
1891- secondaryIcon: "../graphics/applicationIcons/facebook.png",
1892- actions: [],
1893+ n: nlist[4],
1894 summaryVisible: true,
1895 bodyVisible: true,
1896 iconVisible: true,
1897@@ -418,14 +461,7 @@
1898 },
1899 {
1900 tag: "Ephemeral notification with non-shaped icon",
1901- type: Notification.Ephemeral,
1902- hints: {"x-canonical-private-affirmative-tint": "false",
1903- "x-canonical-non-shaped-icon": "true"},
1904- summary: "Contacts",
1905- body: "Synchronised contacts-database with cloud-storage.",
1906- icon: "image://theme/contacts-app",
1907- secondaryIcon: "",
1908- actions: [],
1909+ n: nlist[5],
1910 summaryVisible: true,
1911 bodyVisible: true,
1912 iconVisible: true,
1913@@ -441,14 +477,7 @@
1914 },
1915 {
1916 tag: "Confirmation notification with value",
1917- type: Notification.Confirmation,
1918- hints: {"x-canonical-non-shaped-icon": "true"},
1919- summary: "",
1920- body: "",
1921- icon: "image://theme/audio-volume-medium",
1922- secondaryIcon: "",
1923- value: 50,
1924- actions: [],
1925+ n: nlist[6],
1926 summaryVisible: false,
1927 bodyVisible: false,
1928 iconVisible: false,
1929@@ -464,15 +493,7 @@
1930 },
1931 {
1932 tag: "Confirmation notification with value, label and tint",
1933- type: Notification.Confirmation,
1934- hints: {"x-canonical-non-shaped-icon": "true",
1935- "x-canonical-value-bar-tint" : "true"},
1936- summary: "",
1937- body: "High Volume",
1938- icon: "image://theme/audio-volume-high",
1939- secondaryIcon: "",
1940- value: 85,
1941- actions: [],
1942+ n: nlist[7],
1943 summaryVisible: false,
1944 bodyVisible: false,
1945 iconVisible: false,
1946@@ -485,6 +506,22 @@
1947 valueVisible: true,
1948 valueLabelVisible: true,
1949 valueTinted: true
1950+ },
1951+ {
1952+ tag: "2-over-1 Snap Decision with button-tint",
1953+ n: nlist[8],
1954+ summaryVisible: true,
1955+ bodyVisible: true,
1956+ iconVisible: false,
1957+ centeredIconVisible: false,
1958+ shaped: false,
1959+ secondaryIconVisible: false,
1960+ buttonRowVisible: false,
1961+ buttonTinted: true,
1962+ hasSound: false,
1963+ valueVisible: false,
1964+ valueLabelVisible: false,
1965+ valueTinted: false
1966 }
1967 ]
1968 }
1969@@ -509,8 +546,14 @@
1970 }
1971
1972 function test_NotificationRenderer(data) {
1973+ // make sure the clicks on mocked notifications can be checked against by "actionSpy" (mimicking the NotificationServer component)
1974+ data.n.actionInvoked.connect(mockModel.actionInvoked)
1975+
1976+ // hook up notification's completed-signal with model's onCompleted-slot, so that remove() (model) happens on close() (notification)
1977+ data.n.completed.connect(mockModel.onCompleted)
1978+
1979 // populate model with some mock notifications
1980- mockModel.append(data)
1981+ mockModel.append(data.n)
1982
1983 // make sure the view is properly updated before going on
1984 notifications.forceLayout();
1985@@ -548,9 +591,9 @@
1986
1987 // test input does not fall through
1988 mouseClick(notification, notification.width / 2, notification.height / 2)
1989- if(data.type == Notification.Interactive) {
1990+ if(data.n.type === Notification.Interactive) {
1991 actionSpy.wait()
1992- compare(actionSpy.signalArguments[0][0], data.actions[0]["id"], "got wrong id for interactive action")
1993+ compare(actionSpy.signalArguments[0][0], data.n.actions.data(0, ActionModel.RoleActionId), "got wrong id for interactive action")
1994 }
1995 compare(clickThroughSpy.count, 0, "click on notification fell through")
1996
1997@@ -559,17 +602,19 @@
1998 compare(bodyLabel.visible, data.bodyVisible, "body-text visibility is incorrect")
1999 compare(buttonRow.visible, data.buttonRowVisible, "button visibility is incorrect")
2000
2001- var audioItem = findInvisibleChild(notification, "sound")
2002- compare(audioItem.playbackState, data.hasSound ? Audio.PlayingState : Audio.StoppedState, "Audio has wrong state")
2003+ if (data.hasSound) {
2004+ var audioItem = findInvisibleChild(notification, "sound")
2005+ compare(audioItem.playbackState, data.hasSound ? Audio.PlayingState : Audio.StoppedState, "Audio has wrong state")
2006+ }
2007
2008 if(data.buttonRowVisible) {
2009 var buttonCancel = findChild(buttonRow, "notify_button1")
2010 var buttonAccept = findChild(buttonRow, "notify_button0")
2011
2012 // only test the left/cancel-button if two actions have been passed in
2013- if (data.actions.length == 2) {
2014+ if (data.n.actions.count === 2) {
2015 tryCompareFunction(function() { mouseClick(buttonCancel, buttonCancel.width / 2, buttonCancel.height / 2); return actionSpy.signalArguments.length > 0; }, true);
2016- compare(actionSpy.signalArguments[0][0], data.actions[1]["id"], "got wrong id for negative action")
2017+ compare(actionSpy.signalArguments[0][0], data.n.actions.data(1, ActionModel.RoleActionId), "got wrong id for negative action")
2018 actionSpy.clear()
2019 }
2020
2021@@ -578,36 +623,49 @@
2022
2023 // click the positive/right button
2024 tryCompareFunction(function() { mouseClick(buttonAccept, buttonAccept.width / 2, buttonAccept.height / 2); return actionSpy.signalArguments.length > 0; }, true);
2025- compare(actionSpy.signalArguments[0][0], data.actions[0]["id"], "got wrong id positive action")
2026+ compare(actionSpy.signalArguments[0][0], data.n.actions.data(0, ActionModel.RoleActionId), "got wrong id positive action")
2027 actionSpy.clear()
2028- waitForRendering (notification)
2029
2030 // check if there's a ComboButton created due to more actions being passed
2031- if (data.actions.length > 2) {
2032+ if (data.n.actions.count > 3) {
2033 var comboButton = findChild(notification, "notify_button2")
2034- tryCompareFunction(function() { return comboButton.expanded == false; }, true);
2035+ tryCompareFunction(function() { return comboButton.expanded === false; }, true);
2036
2037 // click to expand
2038- tryCompareFunction(function() { mouseClick(comboButton, comboButton.width - comboButton.__styleInstance.dropDownWidth / 2, comboButton.height / 2); return comboButton.expanded == true; }, true);
2039+ tryCompareFunction(function() { mouseClick(comboButton, comboButton.width / 2, comboButton.height / 2); return comboButton.expanded === true; }, true);
2040
2041 // try clicking on choices in expanded comboList
2042 var choiceButton1 = findChild(notification, "notify_button3")
2043 tryCompareFunction(function() { mouseClick(choiceButton1, choiceButton1.width / 2, choiceButton1.height / 2); return actionSpy.signalArguments.length > 0; }, true);
2044- compare(actionSpy.signalArguments[0][0], data.actions[3]["id"], "got wrong id choice action 1")
2045+ compare(actionSpy.signalArguments[0][0], data.n.actions.data(3, ActionModel.RoleActionId), "got wrong id choice action 1")
2046 actionSpy.clear()
2047
2048 var choiceButton2 = findChild(notification, "notify_button4")
2049 tryCompareFunction(function() { mouseClick(choiceButton2, choiceButton2.width / 2, choiceButton2.height / 2); return actionSpy.signalArguments.length > 0; }, true);
2050- compare(actionSpy.signalArguments[0][0], data.actions[4]["id"], "got wrong id choice action 2")
2051+ compare(actionSpy.signalArguments[0][0], data.n.actions.data(4, ActionModel.RoleActionId), "got wrong id choice action 2")
2052 actionSpy.clear()
2053
2054 // click to collapse
2055- //tryCompareFunction(function() { mouseClick(comboButton, comboButton.width - comboButton.__styleInstance.dropDownWidth / 2, comboButton.height / 2); return comboButton.expanded == false; }, true);
2056+ tryCompareFunction(function() { mouseClick(comboButton, comboButton.width / 2, comboButton.height / 2); return comboButton.expanded == false; }, true);
2057 } else {
2058 mouseClick(buttonCancel, buttonCancel.width / 2, buttonCancel.height / 2)
2059- compare(actionSpy.signalArguments[0][0], data.actions[1]["id"], "got wrong id for negative action")
2060+ compare(actionSpy.signalArguments[0][0], data.n.actions.data(1, ActionModel.RoleActionId), "got wrong id for negative action")
2061 }
2062 }
2063+
2064+ // swipe-to-dismiss check
2065+ waitForRendering(notification)
2066+ var before = mockModel.count
2067+ var dragStart = notification.width * 0.25;
2068+ var dragEnd = notification.width;
2069+ var dragY = notification.height / 2;
2070+ touchFlick(notification, dragStart, dragY, dragEnd, dragY)
2071+ waitForRendering(notification)
2072+ if ((data.n.type === Notification.SnapDecision && notification.state === "expanded") || data.n.type === Notification.Confirmation) {
2073+ tryCompare(mockModel, "count", before)
2074+ } else {
2075+ tryCompare(mockModel, "count", before - 1)
2076+ }
2077 }
2078 }
2079 }
2080
2081=== modified file 'tests/qmltests/Notifications/tst_OptionToggle.qml'
2082--- tests/qmltests/Notifications/tst_OptionToggle.qml 2014-11-10 09:14:30 +0000
2083+++ tests/qmltests/Notifications/tst_OptionToggle.qml 2015-02-16 22:20:44 +0000
2084@@ -1,17 +1,20 @@
2085 /*
2086- * Copyright (C) 2014 Canonical, Ltd.
2087+ * Copyright 2015 Canonical Ltd.
2088 *
2089 * This program is free software; you can redistribute it and/or modify
2090- * it under the terms of the GNU General Public License as published by
2091+ * it under the terms of the GNU Lesser General Public License as published by
2092 * the Free Software Foundation; version 3.
2093 *
2094 * This program is distributed in the hope that it will be useful,
2095 * but WITHOUT ANY WARRANTY; without even the implied warranty of
2096 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2097- * GNU General Public License for more details.
2098+ * GNU Lesser General Public License for more details.
2099 *
2100- * You should have received a copy of the GNU General Public License
2101+ * You should have received a copy of the GNU Lesser General Public License
2102 * along with this program. If not, see <http://www.gnu.org/licenses/>.
2103+ *
2104+ * Authors:
2105+ * Mirco Mueller <mirco.mueller@canonical.com>
2106 */
2107
2108 import QtQuick 2.0
2109@@ -235,8 +238,10 @@
2110 compare(bodyLabel.visible, data.bodyVisible, "body-text visibility is incorrect")
2111 compare(buttonRow.visible, data.buttonRowVisible, "button visibility is incorrect")
2112
2113- var audioItem = findInvisibleChild(notification, "sound")
2114- compare(audioItem.playbackState, data.hasSound ? Audio.PlayingState : Audio.StoppedState, "Audio has wrong state")
2115+ if (data.hasSound) {
2116+ var audioItem = findInvisibleChild(notification, "sound")
2117+ compare(audioItem.playbackState, data.hasSound ? Audio.PlayingState : Audio.StoppedState, "Audio has wrong state")
2118+ }
2119
2120 if(data.buttonRowVisible) {
2121 var buttonCancel = findChild(buttonRow, "notify_button1")
2122
2123=== modified file 'tests/qmltests/Notifications/tst_SwipeToAct.qml'
2124--- tests/qmltests/Notifications/tst_SwipeToAct.qml 2014-11-05 14:37:11 +0000
2125+++ tests/qmltests/Notifications/tst_SwipeToAct.qml 2015-02-16 22:20:44 +0000
2126@@ -218,6 +218,10 @@
2127 // populate model with some mock notifications
2128 mockModel.append(data)
2129
2130+ // add actions to action-model to test against
2131+ myActionModel.append("ok_id", "Ok")
2132+ myActionModel.append("cancel_id", "Cancel")
2133+
2134 // make sure the view is properly updated before going on
2135 notifications.forceLayout();
2136 waitForRendering(notifications);
2137
2138=== modified file 'tests/qmltests/tst_ShellWithPin.qml'
2139--- tests/qmltests/tst_ShellWithPin.qml 2014-12-05 16:27:27 +0000
2140+++ tests/qmltests/tst_ShellWithPin.qml 2015-02-16 22:20:44 +0000
2141@@ -472,5 +472,52 @@
2142
2143 }
2144
2145+ /*
2146+ Regression test for https://bugs.launchpad.net/ubuntu/+source/unity8/+bug/1393447
2147+
2148+ Do a left edge drag that is long enough to start displacing the greeter
2149+ but short engough so that the greeter comes back into place once the
2150+ finger is lifted.
2151+
2152+ In this situation the launcher should remaing fully shown and hide itself
2153+ only after its idle timeout is triggered.
2154+ */
2155+ function test_shortLeftEdgeSwipeMakesLauncherStayVisible() {
2156+ var greeter = testCase.findChild(shell, "greeter")
2157+ greeter.show();
2158+ tryCompare(greeter, "showProgress", 1);
2159+
2160+ var launcher = testCase.findChild(shell, "launcher")
2161+ {
2162+ var dismissTimer = testCase.findInvisibleChild(launcher, "dismissTimer");
2163+ // effectively disable the dismiss timer
2164+ dismissTimer.interval = 24 * 60 * 60 * 1000 // 24 hours
2165+ }
2166+ var launcherPanel = testCase.findChild(launcher, "launcherPanel");
2167+
2168+ var toX = shell.width * 0.45;
2169+ touchFlick(shell,
2170+ 1 /* fromX */, shell.height * 0.5 /* fromY */,
2171+ toX /* toX */, shell.height * 0.5 /* toY */,
2172+ true /* beginTouch */, false /* endTouch */,
2173+ 50, 100);
2174+
2175+ // Launcher must be fully shown by now
2176+ tryCompare(launcherPanel, "x", 0);
2177+
2178+ // Greeter should be displaced
2179+ tryCompareFunction(function() { return greeter.mapToItem(shell, 0, 0).x > shell.width*0.2; }, true);
2180+
2181+ touchRelease(shell, toX, shell.height * 0.5);
2182+
2183+ // Upon release the greeter should have slid back into full view
2184+ tryCompareFunction(function() { return greeter.mapToItem(shell, 0, 0).x === 0; }, true);
2185+
2186+ // And the launcher should stay fully shown
2187+ for (var i = 0; i < 10; ++i) {
2188+ wait(50);
2189+ compare(launcherPanel.x, 0);
2190+ }
2191+ }
2192 }
2193 }

Subscribers

People subscribed via source and target branches

to all changes: