on xenial help sign in
Using InputInfo, determine if we need swipe or click to dismiss the notifications (e.g. incoming phone call)
38784d8Lukáš Tinkl
qml/Notifications/Notification.qml+30−25
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013, 2015 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -17,11 +17,11 @@
17import QtQuick 2.0
17import QtQuick 2.0
18import QtMultimedia 5.0
18import QtMultimedia 5.0
19import Powerd 0.1
19import Powerd 0.1
20−import Ubuntu.Components 1.1
20+import Ubuntu.Components 1.2
21import Unity.Notifications 1.0
21import Unity.Notifications 1.0
22import QMenuModel 0.1
22import QMenuModel 0.1
23import Utils 0.1
23import Utils 0.1
24−
24+import "../Components/UnityInputInfo"
25import Ubuntu.Components.ListItems 0.1 as ListItem
25import Ubuntu.Components.ListItems 0.1 as ListItem
26
26
27Item {
27Item {
@@ -49,6 +49,8 @@ Item {
49 readonly property color sdDarkGrey: "#dddddd"
49 readonly property color sdDarkGrey: "#dddddd"
50 readonly property color sdFontColor: "#5d5d5d"
50 readonly property color sdFontColor: "#5d5d5d"
51 readonly property real contentSpacing: units.gu(2)
51 readonly property real contentSpacing: units.gu(2)
52+ readonly property bool canBeClosed: type === Notification.Ephemeral
53+ property bool clickToClose
52
54
53 objectName: "background"
55 objectName: "background"
54 implicitHeight: type !== Notification.PlaceHolder ? (fullscreen ? maxHeight : outterColumn.height - shapedBack.anchors.topMargin + contentSpacing * 2) : 0
56 implicitHeight: type !== Notification.PlaceHolder ? (fullscreen ? maxHeight : outterColumn.height - shapedBack.anchors.topMargin + contentSpacing * 2) : 0
@@ -59,12 +61,12 @@ Item {
59 state: {
61 state: {
60 var result = "";
62 var result = "";
61
63
62− if (type == Notification.SnapDecision) {
64+ if (type === Notification.SnapDecision) {
63− if (ListView.view.currentIndex == index) {
65+ if (ListView.view.currentIndex === index) {
64 result = "expanded";
66 result = "expanded";
65 } else {
67 } else {
66 if (ListView.view.count > 2) {
68 if (ListView.view.count > 2) {
67− if (ListView.view.currentIndex == -1 && index == 1) {
69+ if (ListView.view.currentIndex === -1 && index == 1) {
68 result = "expanded";
70 result = "expanded";
69 } else {
71 } else {
70 result = "contracted";
72 result = "contracted";
@@ -82,7 +84,7 @@ Item {
82 id: sound
84 id: sound
83 objectName: "sound"
85 objectName: "sound"
84 audioRole: MediaPlayer.alert
86 audioRole: MediaPlayer.alert
85− source: hints["suppress-sound"] !== "true" && hints["sound-file"] !== undefined ? hints["sound-file"] : ""
87+ source: hints["suppress-sound"] !== "true" && !!hints["sound-file"] ? hints["sound-file"] : ""
86 }
88 }
87
89
88 Component.onCompleted: {
90 Component.onCompleted: {
@@ -139,7 +141,7 @@ Item {
139
141
140 clip: fullscreen ? false : true
142 clip: fullscreen ? false : true
141
143
142− visible: type != Notification.PlaceHolder
144+ visible: type !== Notification.PlaceHolder
143
145
144 UbuntuShape {
146 UbuntuShape {
145 id: shapedBack
147 id: shapedBack
@@ -195,7 +197,7 @@ Item {
195 actions: paths.actions
197 actions: paths.actions
196 menuObjectPath: paths.menuObjectPath
198 menuObjectPath: paths.menuObjectPath
197 onNameOwnerChanged: {
199 onNameOwnerChanged: {
198− if (lastNameOwner !== "" && nameOwner === "" && notification.notification !== undefined) {
200+ if (lastNameOwner !== "" && nameOwner === "" && !!notification.notification) {
199 notification.notification.close()
201 notification.notification.close()
200 }
202 }
201 lastNameOwner = nameOwner
203 lastNameOwner = nameOwner
@@ -214,8 +216,10 @@ Item {
214 drag.maximumX: notification.width
216 drag.maximumX: notification.width
215
217
216 onClicked: {
218 onClicked: {
217− if (notification.type == Notification.Interactive) {
219+ if (notification.type === Notification.Interactive) {
218 notification.notification.invokeAction(actionRepeater.itemAt(0).actionId)
220 notification.notification.invokeAction(actionRepeater.itemAt(0).actionId)
221+ } else if (clickToClose && canBeClosed) {
222+ notification.notification.close()
219 } else {
223 } else {
220 notificationList.currentIndex = index;
224 notificationList.currentIndex = index;
221 }
225 }
@@ -256,10 +260,10 @@ Item {
256 id: icon
260 id: icon
257
261
258 objectName: "icon"
262 objectName: "icon"
259− width: type == Notification.Ephemeral && !bodyLabel.visible ? units.gu(3) : units.gu(6)
263+ width: type === Notification.Ephemeral && !bodyLabel.visible ? units.gu(3) : units.gu(6)
260 height: width
264 height: width
261− shaped: notification.hints["x-canonical-non-shaped-icon"] == "true" ? false : true
265+ shaped: notification.hints["x-canonical-non-shaped-icon"] === "true" ? false : true
262− visible: iconSource !== undefined && iconSource !== "" && type !== Notification.Confirmation
266+ visible: !!iconSource && iconSource !== "" && type !== Notification.Confirmation
263 }
267 }
264
268
265 Column {
269 Column {
@@ -280,7 +284,7 @@ Item {
280 fontSize: "medium"
284 fontSize: "medium"
281 color: darkOnBright ? sdFontColor : Theme.palette.selected.backgroundText
285 color: darkOnBright ? sdFontColor : Theme.palette.selected.backgroundText
282 elide: Text.ElideRight
286 elide: Text.ElideRight
283− textFormat: Text.PlainText
287+ textFormat: Text.StyledText
284 }
288 }
285
289
286 Label {
290 Label {
@@ -295,9 +299,9 @@ Item {
295 fontSize: "small"
299 fontSize: "small"
296 color: darkOnBright ? sdFontColor : Theme.palette.selected.backgroundText
300 color: darkOnBright ? sdFontColor : Theme.palette.selected.backgroundText
297 wrapMode: Text.WordWrap
301 wrapMode: Text.WordWrap
298− maximumLineCount: type == Notification.SnapDecision ? 12 : 2
302+ maximumLineCount: type === Notification.SnapDecision ? 12 : 2
299 elide: Text.ElideRight
303 elide: Text.ElideRight
300− textFormat: Text.PlainText
304+ textFormat: Text.StyledText
301 }
305 }
302 }
306 }
303
307
@@ -313,7 +317,7 @@ Item {
313 }
317 }
314
318
315 ListItem.ThinDivider {
319 ListItem.ThinDivider {
316− visible: type == Notification.SnapDecision
320+ visible: type === Notification.SnapDecision
317 }
321 }
318
322
319 ShapedIcon {
323 ShapedIcon {
@@ -321,9 +325,9 @@ Item {
321 objectName: "centeredIcon"
325 objectName: "centeredIcon"
322 width: units.gu(5)
326 width: units.gu(5)
323 height: width
327 height: width
324− shaped: notification.hints["x-canonical-non-shaped-icon"] == "true" ? false : true
328+ shaped: notification.hints["x-canonical-non-shaped-icon"] === "true" ? false : true
325 fileSource: icon.fileSource
329 fileSource: icon.fileSource
326− visible: fileSource !== undefined && fileSource !== "" && type === Notification.Confirmation
330+ visible: !!fileSource && fileSource !== "" && type === Notification.Confirmation
327 anchors.horizontalCenter: parent.horizontalCenter
331 anchors.horizontalCenter: parent.horizontalCenter
328 }
332 }
329
333
@@ -439,7 +443,7 @@ Item {
439 objectName: "notify_oot_button" + index
443 objectName: "notify_oot_button" + index
440 width: oneOverTwoCase.width
444 width: oneOverTwoCase.width
441 text: oneOverTwoLoaderTop.actionLabel
445 text: oneOverTwoLoaderTop.actionLabel
442− color: notification.hints["x-canonical-private-affirmative-tint"] == "true" ? green : sdDarkGrey
446+ color: notification.hints["x-canonical-private-affirmative-tint"] === "true" ? green : sdDarkGrey
443 onClicked: notification.notification.invokeAction(oneOverTwoLoaderTop.actionId)
447 onClicked: notification.notification.invokeAction(oneOverTwoLoaderTop.actionId)
444 }
448 }
445 }
449 }
@@ -467,11 +471,11 @@ Item {
467 objectName: "notify_oot_button" + index
471 objectName: "notify_oot_button" + index
468 width: oneOverTwoCase.width / 2 - spacing * 2
472 width: oneOverTwoCase.width / 2 - spacing * 2
469 text: oneOverTwoLoaderBottom.actionLabel
473 text: oneOverTwoLoaderBottom.actionLabel
470− color: index == 1 && notification.hints["x-canonical-private-rejection-tint"] == "true" ? red : sdDarkGrey
474+ color: index == 1 && notification.hints["x-canonical-private-rejection-tint"] === "true" ? red : sdDarkGrey
471 onClicked: notification.notification.invokeAction(oneOverTwoLoaderBottom.actionId)
475 onClicked: notification.notification.invokeAction(oneOverTwoLoaderBottom.actionId)
472 }
476 }
473 }
477 }
474− sourceComponent: (index == 1 || index == 2) ? oneOverTwoButtonBottom : undefined
478+ sourceComponent: (index == 1 || index == 2) ? oneOverTwoButtonBottom : undefined
475 }
479 }
476 }
480 }
477 }
481 }
@@ -499,6 +503,7 @@ Item {
499 width: buttonRow.width
503 width: buttonRow.width
500 leftIconName: "call-end"
504 leftIconName: "call-end"
501 rightIconName: "call-start"
505 rightIconName: "call-start"
506+ clickToAct: notification.clickToClose
502 onRightTriggered: {
507 onRightTriggered: {
503 notification.notification.invokeAction(notification.actions.data(0, ActionModel.RoleActionId))
508 notification.notification.invokeAction(notification.actions.data(0, ActionModel.RoleActionId))
504 }
509 }
@@ -528,10 +533,10 @@ Item {
528 text: loader.actionLabel
533 text: loader.actionLabel
529 color: {
534 color: {
530 var result = sdDarkGrey;
535 var result = sdDarkGrey;
531− if (index == 0 && notification.hints["x-canonical-private-affirmative-tint"] == "true") {
536+ if (index == 0 && notification.hints["x-canonical-private-affirmative-tint"] === "true") {
532 result = green;
537 result = green;
533 }
538 }
534− if (index == 1 && notification.hints["x-canonical-private-rejection-tint"] == "true") {
539+ if (index == 1 && notification.hints["x-canonical-private-rejection-tint"] === "true") {
535 result = red;
540 result = red;
536 }
541 }
537 return result;
542 return result;
@@ -554,7 +559,7 @@ Item {
554 margins: contentSpacing
559 margins: contentSpacing
555 }
560 }
556
561
557− visible: notification.type == Notification.SnapDecision && actionRepeater.count > 3 && !oneOverTwoCase.visible
562+ visible: notification.type === Notification.SnapDecision && actionRepeater.count > 3 && !oneOverTwoCase.visible
558 model: notification.actions
563 model: notification.actions
559 expanded: false
564 expanded: false
560 startIndex: 2
565 startIndex: 2
qml/Notifications/Notifications.qml+3−1
@@ -28,6 +28,7 @@ ListView {
28
28
29 property real margin
29 property real margin
30 property bool useModal: snapDecisionProxyModel.count > 0
30 property bool useModal: snapDecisionProxyModel.count > 0
31+ property bool clickToCloseNotifications
31
32
32 UnitySortFilterProxyModel {
33 UnitySortFilterProxyModel {
33 id: snapDecisionProxyModel
34 id: snapDecisionProxyModel
@@ -52,12 +53,13 @@ ListView {
52 secondaryIconSource: model.secondaryIcon
53 secondaryIconSource: model.secondaryIcon
53 summary: model.summary
54 summary: model.summary
54 body: model.body
55 body: model.body
55− value: model.value
56+ value: model.value ? model.value : -1
56 actions: model.actions
57 actions: model.actions
57 notificationId: model.id
58 notificationId: model.id
58 notification: notificationList.model.getRaw(notificationId)
59 notification: notificationList.model.getRaw(notificationId)
59 maxHeight: notificationList.height
60 maxHeight: notificationList.height
60 margins: notificationList.margin
61 margins: notificationList.margin
62+ clickToClose: notificationList.clickToCloseNotifications
61
63
62 // make sure there's no opacity-difference between the several
64 // make sure there's no opacity-difference between the several
63 // elements in a notification
65 // elements in a notification
qml/Notifications/SwipeToAct.qml+42−4
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 20014-2015 Canonical, Ltd.
2+ * Copyright (C) 2014-2015 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -15,7 +15,8 @@
15 */
15 */
16
16
17import QtQuick 2.3
17import QtQuick 2.3
18−import Ubuntu.Components 1.1
18+import Ubuntu.Components 1.2
19+import "../Components/UnityInputInfo"
19
20
20Item {
21Item {
21 id: swipeToAct
22 id: swipeToAct
@@ -28,6 +29,8 @@ Item {
28 property string leftIconName
29 property string leftIconName
29 property string rightIconName
30 property string rightIconName
30
31
32+ property bool clickToAct
33+
31 QtObject {
34 QtObject {
32 id: priv
35 id: priv
33
36
@@ -64,15 +67,47 @@ Item {
64 }
67 }
65 }
68 }
66
69
70+ Button {
71+ id: leftButton
72+ objectName: "leftButton"
73+ anchors.verticalCenter: parent.verticalCenter
74+ anchors.left: parent.left
75+ iconName: leftIconName
76+ visible: clickToAct
77+ width: (parent.width / 2) - priv.gap
78+ height: leftShape.height
79+ color: UbuntuColors.red
80+ onClicked: {
81+ leftTriggered()
82+ }
83+ }
84+
85+ Button {
86+ id: rightButton
87+ objectName: "rightButton"
88+ anchors.verticalCenter: parent.verticalCenter
89+ anchors.right: parent.right
90+ iconName: rightIconName
91+ visible: clickToAct
92+ width: (parent.width / 2) - priv.gap
93+ height: rightShape.height
94+ color: UbuntuColors.green
95+ onClicked: {
96+ rightTriggered()
97+ }
98+ }
99+
67 UbuntuShape {
100 UbuntuShape {
68 id: row
101 id: row
69 width: parent.width
102 width: parent.width
70 height: priv.sliderHeight
103 height: priv.sliderHeight
71 color: priv.sliderBGColor
104 color: priv.sliderBGColor
72 borderSource: "none"
105 borderSource: "none"
106+ visible: !clickToAct
73
107
74 UbuntuShape {
108 UbuntuShape {
75 id: leftShape
109 id: leftShape
110+ objectName: "leftArea"
76 anchors.top: parent.top
111 anchors.top: parent.top
77 anchors.left: parent.left
112 anchors.left: parent.left
78 anchors.margins: priv.gap
113 anchors.margins: priv.gap
@@ -91,6 +126,7 @@ Item {
91 height: units.gu(3.5)
126 height: units.gu(3.5)
92 name: leftIconName
127 name: leftIconName
93 color: "white"
128 color: "white"
129+
94 }
130 }
95 }
131 }
96
132
@@ -142,8 +178,7 @@ Item {
142
178
143 onXChanged: {
179 onXChanged: {
144 var factor
180 var factor
145− if (slider.x <= priv.gap + leftShape.width)
181+ if (slider.x <= priv.gap + leftShape.width) {
146− {
147 factor = (slider.x - priv.gap) / leftShape.width
182 factor = (slider.x - priv.gap) / leftShape.width
148 slider.color = priv.interpolate(leftShape.color, priv.sliderMainColor, factor)
183 slider.color = priv.interpolate(leftShape.color, priv.sliderMainColor, factor)
149 } else if (slider.x >= rightShape.x - slider.width) {
184 } else if (slider.x >= rightShape.x - slider.width) {
@@ -167,6 +202,7 @@ Item {
167 color: "white"
202 color: "white"
168 }
203 }
169 }
204 }
205+
170 Row {
206 Row {
171 anchors.verticalCenter: parent.verticalCenter
207 anchors.verticalCenter: parent.verticalCenter
172 anchors.left: slider.right
208 anchors.left: slider.right
@@ -189,6 +225,7 @@ Item {
189
225
190 UbuntuShape {
226 UbuntuShape {
191 id: rightShape
227 id: rightShape
228+ objectName: "rightArea"
192 anchors.top: parent.top
229 anchors.top: parent.top
193 anchors.right: parent.right
230 anchors.right: parent.right
194 anchors.margins: priv.gap
231 anchors.margins: priv.gap
@@ -214,6 +251,7 @@ Item {
214 MouseArea {
251 MouseArea {
215 id: mouseArea
252 id: mouseArea
216 objectName: "swipeMouseArea"
253 objectName: "swipeMouseArea"
254+ enabled: !clickToAct
217
255
218 anchors.fill: row
256 anchors.fill: row
219 drag.target: slider
257 drag.target: slider
qml/OrientedShell.qml+1−1
@@ -44,7 +44,6 @@ Rectangle {
44 name: applicationArguments.deviceName
44 name: applicationArguments.deviceName
45 }
45 }
46
46
47−
48 // to be overwritten by tests
47 // to be overwritten by tests
49 property var unity8Settings: Unity8Settings {}
48 property var unity8Settings: Unity8Settings {}
50 property var oskSettings: GSettings { schema.id: "com.canonical.keyboard.maliit" }
49 property var oskSettings: GSettings { schema.id: "com.canonical.keyboard.maliit" }
@@ -155,6 +154,7 @@ Rectangle {
155 nativeWidth: root.width
154 nativeWidth: root.width
156 nativeHeight: root.height
155 nativeHeight: root.height
157 mode: applicationArguments.mode
156 mode: applicationArguments.mode
157+ clickToCloseNotifications: UnityInputInfo.mice > deviceConfiguration.ignoredMice
158
158
159 // TODO: Factor in the connected input devices (eg: physical keyboard, mouse, touchscreen),
159 // TODO: Factor in the connected input devices (eg: physical keyboard, mouse, touchscreen),
160 // what's the output device (eg: big TV, desktop monitor, phone display), etc.
160 // what's the output device (eg: big TV, desktop monitor, phone display), etc.
qml/Shell.qml+2
@@ -63,6 +63,7 @@ Item {
63 function updateFocusedAppOrientationAnimated() {
63 function updateFocusedAppOrientationAnimated() {
64 applicationsDisplayLoader.item.updateFocusedAppOrientationAnimated();
64 applicationsDisplayLoader.item.updateFocusedAppOrientationAnimated();
65 }
65 }
66+ property bool clickToCloseNotifications
66
67
67 // to be read from outside
68 // to be read from outside
68 readonly property int mainAppWindowOrientationAngle:
69 readonly property int mainAppWindowOrientationAngle:
@@ -636,6 +637,7 @@ Item {
636
637
637 model: NotificationBackend.Model
638 model: NotificationBackend.Model
638 margin: units.gu(1)
639 margin: units.gu(1)
640+ clickToCloseNotifications: shell.clickToCloseNotifications
639
641
640 y: topmostIsFullscreen ? 0 : panel.panelHeight
642 y: topmostIsFullscreen ? 0 : panel.panelHeight
641 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
643 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
tests/mocks/Unity/Notifications/MockActionModel.cpp+1−1
@@ -59,7 +59,7 @@ QHash<int, QByteArray> ActionModel::roleNames() const {
59 return roles;
59 return roles;
60}
60}
61
61
62−Q_INVOKABLE QVariant ActionModel::data(int row, int role) const
62+QVariant ActionModel::data(int row, int role) const
63{
63{
64 return data(index(row, 0), role);
64 return data(index(row, 0), role);
65}
65}
tests/mocks/Unity/Notifications/MockActionModel.h+1−1
@@ -33,7 +33,7 @@ public:
33 virtual ~ActionModel();
33 virtual ~ActionModel();
34
34
35 int rowCount(const QModelIndex &index) const override;
35 int rowCount(const QModelIndex &index) const override;
36− QVariant data(const QModelIndex &index, int role) const override;
36+ Q_INVOKABLE QVariant data(const QModelIndex &index, int role) const override; // invokable for testing
37 QHash<int, QByteArray> roleNames() const override;
37 QHash<int, QByteArray> roleNames() const override;
38
38
39 Q_ENUMS(ActionsRoles)
39 Q_ENUMS(ActionsRoles)
tests/mocks/Unity/Notifications/MockNotification.cpp+1
@@ -39,6 +39,7 @@ MockNotification::MockNotification(QObject *parent) : QObject(parent), p(new Moc
39}
39}
40
40
41MockNotification::~MockNotification() {
41MockNotification::~MockNotification() {
42+ delete p->actionsModel;
42}
43}
43
44
44QString MockNotification::getSummary() const {
45QString MockNotification::getSummary() const {
tests/mocks/Unity/Notifications/MockNotification.h+1−1
@@ -30,7 +30,7 @@ struct MockNotificationPrivate;
30
30
31class MockNotification : public QObject {
31class MockNotification : public QObject {
32 Q_OBJECT
32 Q_OBJECT
33− Q_ENUMS(Type)
33+ Q_ENUMS(Type Urgency)
34 Q_PROPERTY(QString summary READ getSummary WRITE setSummary NOTIFY summaryChanged)
34 Q_PROPERTY(QString summary READ getSummary WRITE setSummary NOTIFY summaryChanged)
35 Q_PROPERTY(QString body READ getBody WRITE setBody NOTIFY bodyChanged)
35 Q_PROPERTY(QString body READ getBody WRITE setBody NOTIFY bodyChanged)
36 Q_PROPERTY(int nid READ getID WRITE setID NOTIFY idChanged)
36 Q_PROPERTY(int nid READ getID WRITE setID NOTIFY idChanged)
tests/mocks/Unity/Notifications/MockNotificationModel.cpp+5−1
@@ -35,6 +35,10 @@ MockNotificationModel::MockNotificationModel(QObject *parent) : QAbstractListMod
35}
35}
36
36
37MockNotificationModel::~MockNotificationModel() {
37MockNotificationModel::~MockNotificationModel() {
38+ Q_FOREACH(MockNotification *n, m_queue) {
39+ n->deleteLater();
40+ }
41+ m_queue.clear();
38}
42}
39
43
40int MockNotificationModel::rowCount(const QModelIndex &) const {
44int MockNotificationModel::rowCount(const QModelIndex &) const {
@@ -47,7 +51,7 @@ int MockNotificationModel::getCount() const {
47
51
48QVariant MockNotificationModel::data(const QModelIndex &index, int role) const {
52QVariant MockNotificationModel::data(const QModelIndex &index, int role) const {
49 if (!index.isValid())
53 if (!index.isValid())
50− return QVariant();
54+ return QVariant();
51
55
52 switch(role) {
56 switch(role) {
53 case ModelInterface::RoleType:
57 case ModelInterface::RoleType:
tests/qmltests/Notifications/tst_Notifications.qml+63−10
@@ -12,16 +12,15 @@
12 *
12 *
13 * You should have received a copy of the GNU Lesser General Public License
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/>.
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− *
16− * Authors:
17− * Mirco Mueller <mirco.mueller@canonical.com>
18 */
15 */
19
16
20−import QtQuick 2.0
17+import QtQuick 2.4
18+import QtQuick.Layouts 1.1
21import QtTest 1.0
19import QtTest 1.0
22import ".."
20import ".."
23import "../../../qml/Notifications"
21import "../../../qml/Notifications"
22+import "../../../qml/Components/UnityInputInfo"
24−import Ubuntu.Components 0.1
23+import Ubuntu.Components 1.2
25import Unity.Test 0.1
24import Unity.Test 0.1
26import Unity.Notifications 1.0
25import Unity.Notifications 1.0
27import QtMultimedia 5.0
26import QtMultimedia 5.0
@@ -77,7 +76,7 @@ Item {
77 "type": Notification.Ephemeral,
76 "type": Notification.Ephemeral,
78 "hints": {},
77 "hints": {},
79 "summary": "Cole Raby",
78 "summary": "Cole Raby",
80− "body": "I did not expect it to be that late.",
79+ "body": "I did not expect it to be <b>that</b> late.",
81 "icon": "../graphics/avatars/amanda.png",
80 "icon": "../graphics/avatars/amanda.png",
82 "secondaryIcon": "../graphics/applicationIcons/facebook.png",
81 "secondaryIcon": "../graphics/applicationIcons/facebook.png",
83 "rawActions": ["reply_id", "Dummy"]})
82 "rawActions": ["reply_id", "Dummy"]})
@@ -91,7 +90,7 @@ Item {
91 "type": Notification.Ephemeral,
90 "type": Notification.Ephemeral,
92 "hints": {"x-canonical-non-shaped-icon": "true"},
91 "hints": {"x-canonical-non-shaped-icon": "true"},
93 "summary": "Contacts",
92 "summary": "Contacts",
94− "body": "Synchronised contacts-database with cloud-storage.",
93+ "body": "Synchronised contacts-database &amp; cloud-storage.",
95 "icon": "../graphics/applicationIcons/contacts-app.png",
94 "icon": "../graphics/applicationIcons/contacts-app.png",
96 "secondaryIcon": "",
95 "secondaryIcon": "",
97 "rawActions": ["reply_id", "Dummy"]})
96 "rawActions": ["reply_id", "Dummy"]})
@@ -189,6 +188,7 @@ Item {
189
188
190 anchors.fill: parent
189 anchors.fill: parent
191 model: mockModel
190 model: mockModel
191+ clickToCloseNotifications: UnityInputInfo.mice > 0 // for testing, we add/remove only a mock mouse
192 }
192 }
193 }
193 }
194
194
@@ -218,7 +218,7 @@ Item {
218
218
219 Button {
219 Button {
220 width: parent.width
220 width: parent.width
221− text: "add an non-shaped-icon-summary-body"
221+ text: "add a non-shaped-icon-summary-body"
222 onClicked: rootRow.addEphemeralNonShapedIconNotification()
222 onClicked: rootRow.addEphemeralNonShapedIconNotification()
223 }
223 }
224
224
@@ -257,6 +257,24 @@ Item {
257 text: "clear model"
257 text: "clear model"
258 onClicked: rootRow.clearNotifications()
258 onClicked: rootRow.clearNotifications()
259 }
259 }
260+
261+ RowLayout {
262+ Layout.fillWidth: true
263+ CheckBox {
264+ id: fakeMouseCB
265+ onClicked: {
266+ if (checked) {
267+ UnityInputInfo.inputInfo.addMockMouse();
268+ } else {
269+ UnityInputInfo.inputInfo.removeMockMouse();
270+ }
271+ }
272+ }
273+ Label {
274+ text: "With fake mouse"
275+ anchors.verticalCenter: parent.verticalCenter
276+ }
277+ }
260 }
278 }
261 }
279 }
262
280
@@ -560,7 +578,7 @@ Item {
560 waitForRendering(notifications);
578 waitForRendering(notifications);
561
579
562 var notification = findChild(notifications, "notification" + (mockModel.count - 1))
580 var notification = findChild(notifications, "notification" + (mockModel.count - 1))
563− verify(notification !== undefined, "notification wasn't found");
581+ verify(!!notification, "notification wasn't found");
564
582
565 waitForRendering(notification);
583 waitForRendering(notification);
566 tryCompare(notification, "height", notification.state === "contracted" ? units.gu(10) : notification.implicitHeight);
584 tryCompare(notification, "height", notification.state === "contracted" ? units.gu(10) : notification.implicitHeight);
@@ -654,7 +672,7 @@ Item {
654 actionSpy.clear()
672 actionSpy.clear()
655
673
656 // click to collapse
674 // click to collapse
657− tryCompareFunction(function() { mouseClick(comboButton, comboButton.width / 2, comboButton.height / 2); return comboButton.expanded == false; }, true);
675+ tryCompareFunction(function() { mouseClick(comboButton, comboButton.width / 2, comboButton.height / 2); return comboButton.expanded === false; }, true);
658 } else {
676 } else {
659 mouseClick(buttonCancel)
677 mouseClick(buttonCancel)
660 compare(actionSpy.signalArguments[0][0], data.n.actions.data(1, ActionModel.RoleActionId), "got wrong id for negative action")
678 compare(actionSpy.signalArguments[0][0], data.n.actions.data(1, ActionModel.RoleActionId), "got wrong id for negative action")
@@ -675,6 +693,41 @@ Item {
675 tryCompare(mockModel, "count", before - 1)
693 tryCompare(mockModel, "count", before - 1)
676 }
694 }
677 }
695 }
696+
697+ function test_clickToClose_data() { // reuse the data
698+ UnityInputInfo.inputInfo.addMockMouse();
699+ return test_NotificationRenderer_data();
700+ }
701+
702+ function test_clickToClose(data) {
703+ // populate model with some mock notifications
704+ mockModel.append(data.n)
705+
706+ // make sure the view is properly updated before going on
707+ notifications.forceLayout();
708+ waitForRendering(notifications);
709+
710+ var notification = findChild(notifications, "notification" + (mockModel.count - 1))
711+ verify(!!notification, "notification wasn't found");
712+
713+ // click-to-dismiss check
714+ waitForRendering(notification);
715+ var before = mockModel.count;
716+ var canBeClosed = notification.canBeClosed;
717+ mouseClick(notification);
718+
719+ if (canBeClosed) {
720+ // closing the notification, the model count should be one less
721+ tryCompare(mockModel, "count", before - 1)
722+ } else {
723+ // not closing, model stays the same
724+ tryCompare(mockModel, "count", before)
725+ }
726+ }
727+
728+ function cleanupTestCase() {
729+ UnityInputInfo.inputInfo.removeMockMouse();
730+ }
678 }
731 }
679 }
732 }
680}
733}
tests/qmltests/Notifications/tst_SwipeToAct.qml+56−15
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014, 2015 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -14,10 +14,12 @@
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
15 */
16
16
17−import QtQuick 2.0
17+import QtQuick 2.4
18+import QtQuick.Layouts 1.1
18import QtTest 1.0
19import QtTest 1.0
19import ".."
20import ".."
20import "../../../qml/Notifications"
21import "../../../qml/Notifications"
22+import "../../../qml/Components/UnityInputInfo"
21import Ubuntu.Components 0.1
23import Ubuntu.Components 0.1
22import Unity.Test 0.1
24import Unity.Test 0.1
23import Unity.Notifications 1.0
25import Unity.Notifications 1.0
@@ -97,7 +99,6 @@ Item {
97
99
98 MouseArea{
100 MouseArea{
99 id: clickThroughCatcher
101 id: clickThroughCatcher
100−
101 anchors.fill: parent
102 anchors.fill: parent
102 }
103 }
103
104
@@ -108,6 +109,7 @@ Item {
108
109
109 anchors.fill: parent
110 anchors.fill: parent
110 model: mockModel
111 model: mockModel
112+ clickToCloseNotifications: UnityInputInfo.mice > 0 // for testing, we add/remove only a mock mouse
111 }
113 }
112 }
114 }
113
115
@@ -140,6 +142,24 @@ Item {
140 text: "clear model"
142 text: "clear model"
141 onClicked: rootRow.clearNotifications()
143 onClicked: rootRow.clearNotifications()
142 }
144 }
145+
146+ RowLayout {
147+ Layout.fillWidth: true
148+ CheckBox {
149+ id: fakeMouseCB
150+ onClicked: {
151+ if (checked) {
152+ UnityInputInfo.inputInfo.addMockMouse();
153+ } else {
154+ UnityInputInfo.inputInfo.removeMockMouse();
155+ }
156+ }
157+ }
158+ Label {
159+ text: "With fake mouse"
160+ anchors.verticalCenter: parent.verticalCenter
161+ }
162+ }
143 }
163 }
144 }
164 }
145
165
@@ -227,7 +247,7 @@ Item {
227 waitForRendering(notifications);
247 waitForRendering(notifications);
228
248
229 var notification = findChild(notifications, "notification" + (mockModel.count - 1))
249 var notification = findChild(notifications, "notification" + (mockModel.count - 1))
230− verify(notification !== undefined, "notification wasn't found");
250+ verify(!!notification, "notification wasn't found");
231
251
232 waitForRendering(notification);
252 waitForRendering(notification);
233
253
@@ -245,7 +265,7 @@ Item {
245
265
246 // test input does not fall through
266 // test input does not fall through
247 mouseClick(notification)
267 mouseClick(notification)
248− if(data.type == Notification.Interactive) {
268+ if(data.type === Notification.Interactive) {
249 actionSpy.wait()
269 actionSpy.wait()
250 compare(actionSpy.signalArguments[0][0], data.actions[0]["id"], "got wrong id for interactive action")
270 compare(actionSpy.signalArguments[0][0], data.actions[0]["id"], "got wrong id for interactive action")
251 }
271 }
@@ -258,21 +278,42 @@ Item {
258
278
259 if(data.buttonRowVisible) {
279 if(data.buttonRowVisible) {
260 var swipeButton = findChild(buttonRow, "notify_swipe_button")
280 var swipeButton = findChild(buttonRow, "notify_swipe_button")
281+
282+ if (!swipeButton.clickToAct) { // don't run if there's a real mouse
261− var slider = findChild(swipeButton, "slider")
283+ var slider = findChild(swipeButton, "slider")
262− var swipeMouseArea = findChild(swipeButton, "swipeMouseArea")
284+ var swipeMouseArea = findChild(swipeButton, "swipeMouseArea")
263− var x = swipeMouseArea.width / 2
285+ var x = swipeMouseArea.width / 2
264− var y = swipeMouseArea.height / 2
286+ var y = swipeMouseArea.height / 2
287+
288+ if(data.checkSwipeToActAccept) {
289+ tryCompareFunction(function() { mouseDrag(slider, x, y, (swipeMouseArea.width / 2) - slider.width, 0); return actionSpy.signalArguments.length > 0; }, true);
290+ compare(actionSpy.signalArguments[0][0], data.actions.data(0, ActionModel.RoleActionId), "got wrong id for positive action");
291+ actionSpy.clear();
292+ }
293+ if(data.checkSwipeToActReject) {
294+ tryCompareFunction(function() { mouseDrag(slider, x, y, -(swipeMouseArea.width / 2), 0); return actionSpy.signalArguments.length > 0; }, true);
295+ compare(actionSpy.signalArguments[0][0], data.actions.data(1, ActionModel.RoleActionId), "got wrong id for negative action");
296+ actionSpy.clear();
297+ }
298+ }
299+
300+ // add a mock mouse, test clicking the left/right buttons
301+ UnityInputInfo.inputInfo.addMockMouse();
302+ var leftButton = findChild(swipeButton, "leftButton");
303+ var rightButton = findChild(swipeButton, "rightButton");
265
304
266 if(data.checkSwipeToActAccept) {
305 if(data.checkSwipeToActAccept) {
267− tryCompareFunction(function() { mouseDrag(slider, x, y, (swipeMouseArea.width / 2) - slider.width, 0); return actionSpy.signalArguments.length > 0; }, true);
306+ mouseClick(rightButton);
268− compare(actionSpy.signalArguments[0][0], data.actions.data(0, ActionModel.RoleActionId), "got wrong id for positive action")
307+ compare(actionSpy.signalArguments[0][0], data.actions.data(0, ActionModel.RoleActionId), "got wrong id for positive action");
269− actionSpy.clear()
308+ actionSpy.clear();
270 }
309 }
310+
271 if(data.checkSwipeToActReject) {
311 if(data.checkSwipeToActReject) {
272− tryCompareFunction(function() { mouseDrag(slider, x, y, -(swipeMouseArea.width / 2), 0); return actionSpy.signalArguments.length > 0; }, true);
312+ mouseClick(leftButton);
273− compare(actionSpy.signalArguments[0][0], data.actions.data(1, ActionModel.RoleActionId), "got wrong id for negative action")
313+ compare(actionSpy.signalArguments[0][0], data.actions.data(1, ActionModel.RoleActionId), "got wrong id for negative action");
274− actionSpy.clear()
314+ actionSpy.clear();
275 }
315 }
316+ UnityInputInfo.inputInfo.removeMockMouse();
276 }
317 }
277 }
318 }
278 }
319 }