on xenial help sign in
Notifications visual update, close button on mouse hover Fixes: #1564343
83d0918Lukáš Tinkl
qml/Notifications/Notification.qml+129−119
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013, 2015 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 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
@@ -36,44 +36,40 @@ StyledItem {
36 property var type
36 property var type
37 property var hints
37 property var hints
38 property var notification
38 property var notification
39− property color color
39+ property color color: theme.palette.normal.background
40 property bool fullscreen: false
40 property bool fullscreen: false
41 property int maxHeight
41 property int maxHeight
42− property int margins
42+ property int margins: units.gu(1)
43− readonly property bool draggable: (type === Notification.SnapDecision && state === "contracted") || type === Notification.Interactive || type === Notification.Ephemeral
43+
44− readonly property bool darkOnBright: inverseMode || type === Notification.SnapDecision
44+ readonly property real defaultOpacity: 1.0
45− readonly property color sdLightGrey: "#eaeaea"
46− readonly property real contentSpacing: units.gu(2)
47− readonly property bool canBeClosed: type === Notification.Ephemeral
48 property bool hasMouse
45 property bool hasMouse
49− property bool inverseMode
50 property url background: ""
46 property url background: ""
51
47
52 objectName: "background"
48 objectName: "background"
53− implicitHeight: type !== Notification.PlaceHolder ? (fullscreen ? maxHeight : outterColumn.height - shapedBack.anchors.topMargin + contentSpacing * 2) : 0
49+ implicitHeight: type !== Notification.PlaceHolder ? (fullscreen ? maxHeight : outterColumn.height + shapedBack.anchors.topMargin + margins * 2) : 0
54
50
55− color: (type === Notification.Confirmation && notificationList.useModal && !greeter.shown) || darkOnBright ? sdLightGrey : Qt.rgba(0.132, 0.117, 0.109, 0.97)
56− opacity: 1 - (x / notification.width) // FIXME: non-zero initially because of LP: #1354406 workaround, we want this to start at 0 upon creation eventually
51+ // FIXME: non-zero initially because of LP: #1354406 workaround, we want this to start at 0 upon creation eventually
52+ opacity: defaultOpacity - Math.abs(x / notification.width)
57
53
58 theme: ThemeSettings {
54 theme: ThemeSettings {
59− name: darkOnBright ? "Ubuntu.Components.Themes.Ambiance" : "Ubuntu.Components.Themes.SuruDark"
55+ name: "Ubuntu.Components.Themes.Ambiance"
60 }
56 }
61
57
62− state: {
58+ readonly property bool expanded: {
63− var result = "";
59+ var result = false;
64
60
65− if (type == Notification.SnapDecision) {
61+ if (type === Notification.SnapDecision) {
66− if (ListView.view.currentIndex == index) {
62+ if (ListView.view.currentIndex === index) {
67− result = "expanded";
63+ result = true;
68 } else {
64 } else {
69 if (ListView.view.count > 2) {
65 if (ListView.view.count > 2) {
70− if (ListView.view.currentIndex == -1 && index == 1) {
66+ if (ListView.view.currentIndex === -1 && index == 1) {
71− result = "expanded";
67+ result = true;
72 } else {
68 } else {
73− result = "contracted";
69+ result = false;
74 }
70 }
75 } else {
71 } else {
76− result = "expanded";
72+ result = true;
77 }
73 }
78 }
74 }
79 }
75 }
@@ -90,72 +86,66 @@ StyledItem {
90 Component.onCompleted: {
86 Component.onCompleted: {
91 // Turn on screen as needed (Powerd.Notification means the screen
87 // Turn on screen as needed (Powerd.Notification means the screen
92 // stays on for a shorter amount of time)
88 // stays on for a shorter amount of time)
93− if (type == Notification.SnapDecision) {
89+ if (type === Notification.SnapDecision) {
94 Powerd.setStatus(Powerd.On, Powerd.SnapDecision);
90 Powerd.setStatus(Powerd.On, Powerd.SnapDecision);
95− } else if (type != Notification.Confirmation) {
91+ } else if (type !== Notification.Confirmation) {
96 Powerd.setStatus(Powerd.On, Powerd.Notification);
92 Powerd.setStatus(Powerd.On, Powerd.Notification);
97 }
93 }
98
94
99 // FIXME: using onCompleted because of LP: #1354406 workaround, has to be onOpacityChanged really
95 // FIXME: using onCompleted because of LP: #1354406 workaround, has to be onOpacityChanged really
100− if (opacity == 1.0 && hints["suppress-sound"] !== "true" && sound.source !== "") {
96+ if (opacity == defaultOpacity && hints["suppress-sound"] !== "true" && sound.source !== "") {
101 sound.play();
97 sound.play();
102 }
98 }
103 }
99 }
104
100
105− Behavior on x {
101+ function closeNotification() {
106− id: normalXBehavior
102+ if (notification.actions.count > 1) { // perform the "reject" action
107−
103+ notification.notification.invokeAction(notification.actions.data(1, ActionModel.RoleActionId));
108− enabled: draggable
109− UbuntuNumberAnimation {
110− duration: UbuntuAnimation.FastDuration
111− easing.type: Easing.OutBounce
112 }
104 }
105+ notification.notification.close();
106+ }
107+
108+ Behavior on x {
109+ UbuntuNumberAnimation { easing.type: Easing.OutBounce }
113 }
110 }
114
111
115 onHintsChanged: {
112 onHintsChanged: {
116− if (type === Notification.Confirmation && opacity == 1.0 && hints["suppress-sound"] !== "true" && sound.source !== "") {
113+ if (type === Notification.Confirmation && opacity == defaultOpacity && hints["suppress-sound"] !== "true" && sound.source !== "") {
117 sound.play();
114 sound.play();
118 }
115 }
119 }
116 }
120
117
121 Behavior on height {
118 Behavior on height {
122− id: normalHeightBehavior
123−
124− //enabled: menuItemFactory.progress == 1
125− enabled: true
126 UbuntuNumberAnimation {
119 UbuntuNumberAnimation {
127 duration: UbuntuAnimation.SnapDuration
120 duration: UbuntuAnimation.SnapDuration
128 }
121 }
129 }
122 }
130
123
131− states:[
124+ visible: type !== Notification.PlaceHolder
132− State {
133− name: "contracted"
134− PropertyChanges {target: notification; height: units.gu(10)}
135− },
136− State {
137− name: "expanded"
138− PropertyChanges {target: notification; height: implicitHeight}
139− }
140− ]
141−
142− clip: fullscreen ? false : true
143
125
144− visible: type != Notification.PlaceHolder
126+ BorderImage {
127+ anchors {
128+ fill: contents
129+ margins: shapedBack.visible ? -units.gu(1) : -units.gu(1.5)
130+ }
131+ source: "../Stages/graphics/dropshadow2gu.sci"
132+ opacity: notification.opacity * 0.5
133+ enabled: !fullscreen
134+ }
145
135
146 UbuntuShape {
136 UbuntuShape {
147 id: shapedBack
137 id: shapedBack
138+ objectName: "shapedBack"
148
139
149 visible: !fullscreen
140 visible: !fullscreen
150 anchors {
141 anchors {
151 fill: parent
142 fill: parent
152 leftMargin: notification.margins
143 leftMargin: notification.margins
153 rightMargin: notification.margins
144 rightMargin: notification.margins
154− topMargin: type === Notification.Confirmation ? units.gu(.5) : 0
145+ topMargin: index == 0 ? notification.margins : 0
155 }
146 }
156 backgroundColor: parent.color
147 backgroundColor: parent.color
157− opacity: parent.opacity
158− radius: "medium"
148+ radius: "small"
159 aspect: UbuntuShape.Flat
149 aspect: UbuntuShape.Flat
160 }
150 }
161
151
@@ -165,12 +155,11 @@ StyledItem {
165 visible: fullscreen
155 visible: fullscreen
166 anchors.fill: parent
156 anchors.fill: parent
167 color: parent.color
157 color: parent.color
168− opacity: parent.opacity
169 }
158 }
170
159
171 onXChanged: {
160 onXChanged: {
172− if (draggable && notification.x > 0.75 * notification.width) {
161+ if (Math.abs(notification.x) > 0.75 * notification.width) {
173− notification.notification.close()
162+ closeNotification();
174 }
163 }
175 }
164 }
176
165
@@ -210,22 +199,21 @@ StyledItem {
210 anchors.fill: parent
199 anchors.fill: parent
211 objectName: "interactiveArea"
200 objectName: "interactiveArea"
212
201
213− drag.target: draggable ? notification : undefined
202+ drag.target: notification
214 drag.axis: Drag.XAxis
203 drag.axis: Drag.XAxis
215− drag.minimumX: 0
204+ drag.minimumX: -notification.width
216 drag.maximumX: notification.width
205 drag.maximumX: notification.width
206+ hoverEnabled: true
217
207
218 onClicked: {
208 onClicked: {
219− if (notification.type == Notification.Interactive) {
209+ if (notification.type === Notification.Interactive) {
220 notification.notification.invokeAction(actionRepeater.itemAt(0).actionId)
210 notification.notification.invokeAction(actionRepeater.itemAt(0).actionId)
221− } else if (hasMouse && canBeClosed) {
222− notification.notification.close()
223 } else {
211 } else {
224 notificationList.currentIndex = index;
212 notificationList.currentIndex = index;
225 }
213 }
226 }
214 }
227 onReleased: {
215 onReleased: {
228− if (notification.x < notification.width / 2) {
216+ if (Math.abs(notification.x) < notification.width / 2) {
229 notification.x = 0
217 notification.x = 0
230 } else {
218 } else {
231 notification.x = notification.width
219 notification.x = notification.width
@@ -233,44 +221,61 @@ StyledItem {
233 }
221 }
234 }
222 }
235
223
224+ NotificationButton {
225+ objectName: "closeButton"
226+ width: units.gu(2)
227+ height: width
228+ radius: width / 2
229+ visible: hasMouse && (containsMouse || interactiveArea.containsMouse)
230+ iconName: "close"
231+ outline: false
232+ hoverEnabled: true
233+ color: theme.palette.normal.negative
234+ anchors.horizontalCenter: parent.left
235+ anchors.horizontalCenterOffset: notification.parent.state === "narrow" ? notification.margins / 2 : 0
236+ anchors.verticalCenter: parent.top
237+ anchors.verticalCenterOffset: notification.parent.state === "narrow" ? notification.margins / 2 : 0
238+
239+ onClicked: closeNotification();
240+ }
241+
236 Column {
242 Column {
237 id: outterColumn
243 id: outterColumn
244+ objectName: "outterColumn"
238
245
239 anchors {
246 anchors {
240 left: parent.left
247 left: parent.left
241 right: parent.right
248 right: parent.right
242 top: parent.top
249 top: parent.top
243− margins: 0
250+ margins: !fullscreen ? notification.margins : 0
244− topMargin: fullscreen ? 0 : type === Notification.Confirmation ? units.gu(1) : units.gu(2)
245 }
251 }
246
252
247− spacing: type === Notification.Confirmation ? units.gu(1) : units.gu(2)
253+ spacing: notification.margins
248
254
249 Row {
255 Row {
250 id: topRow
256 id: topRow
251
257
252− spacing: contentSpacing
258+ spacing: notification.margins
253 anchors {
259 anchors {
254 left: parent.left
260 left: parent.left
255 right: parent.right
261 right: parent.right
256− margins: contentSpacing
257 }
262 }
258
263
259 ShapedIcon {
264 ShapedIcon {
260 id: icon
265 id: icon
261
266
262 objectName: "icon"
267 objectName: "icon"
263− width: type == Notification.Ephemeral && !bodyLabel.visible ? units.gu(3) : units.gu(6)
268+ width: units.gu(6)
264 height: width
269 height: width
265− shaped: notification.hints["x-canonical-non-shaped-icon"] == "true" ? false : true
270+ shaped: notification.hints["x-canonical-non-shaped-icon"] !== "true"
266− visible: iconSource !== undefined && iconSource !== "" && type !== Notification.Confirmation
271+ visible: iconSource !== "" && type !== Notification.Confirmation
267 }
272 }
268
273
269 Column {
274 Column {
270 id: labelColumn
275 id: labelColumn
271− width: secondaryIcon.visible ? parent.width - x - units.gu(4.5) : parent.width - x
276+ width: secondaryIcon.visible ? parent.width - x - units.gu(3) : parent.width - x
272−
273 anchors.verticalCenter: (icon.visible && !bodyLabel.visible) ? icon.verticalCenter : undefined
277 anchors.verticalCenter: (icon.visible && !bodyLabel.visible) ? icon.verticalCenter : undefined
278+ spacing: units.gu(.4)
274
279
275 Label {
280 Label {
276 id: summaryLabel
281 id: summaryLabel
@@ -282,6 +287,8 @@ StyledItem {
282 }
287 }
283 visible: type !== Notification.Confirmation
288 visible: type !== Notification.Confirmation
284 fontSize: "medium"
289 fontSize: "medium"
290+ font.weight: Font.Light
291+ color: theme.palette.normal.backgroundSecondaryText
285 elide: Text.ElideRight
292 elide: Text.ElideRight
286 textFormat: Text.PlainText
293 textFormat: Text.PlainText
287 }
294 }
@@ -296,10 +303,13 @@ StyledItem {
296 }
303 }
297 visible: body != "" && type !== Notification.Confirmation
304 visible: body != "" && type !== Notification.Confirmation
298 fontSize: "small"
305 fontSize: "small"
306+ font.weight: Font.Light
307+ color: theme.palette.normal.backgroundTertiaryText
299 wrapMode: Text.Wrap
308 wrapMode: Text.Wrap
300− maximumLineCount: type == Notification.SnapDecision ? 12 : 2
309+ maximumLineCount: type === Notification.SnapDecision ? 12 : 2
301 elide: Text.ElideRight
310 elide: Text.ElideRight
302 textFormat: Text.PlainText
311 textFormat: Text.PlainText
312+ lineHeight: 1.1
303 }
313 }
304 }
314 }
305
315
@@ -307,25 +317,34 @@ StyledItem {
307 id: secondaryIcon
317 id: secondaryIcon
308
318
309 objectName: "secondaryIcon"
319 objectName: "secondaryIcon"
310− width: units.gu(3)
320+ width: units.gu(2)
311− height: units.gu(3)
321+ height: width
312 visible: status === Image.Ready
322 visible: status === Image.Ready
313 fillMode: Image.PreserveAspectCrop
323 fillMode: Image.PreserveAspectCrop
314 }
324 }
315 }
325 }
316
326
317 ListItem.ThinDivider {
327 ListItem.ThinDivider {
318− visible: type == Notification.SnapDecision
328+ visible: type === Notification.SnapDecision && notification.expanded
329+ }
330+
331+ Icon {
332+ name: "toolkit_chevron-down_3gu"
333+ visible: type === Notification.SnapDecision && !notification.expanded
334+ width: units.gu(2)
335+ height: width
336+ anchors.horizontalCenter: parent.horizontalCenter
337+ color: theme.palette.normal.base
319 }
338 }
320
339
321 ShapedIcon {
340 ShapedIcon {
322 id: centeredIcon
341 id: centeredIcon
323 objectName: "centeredIcon"
342 objectName: "centeredIcon"
324− width: units.gu(5)
343+ width: units.gu(4)
325 height: width
344 height: width
326− shaped: notification.hints["x-canonical-non-shaped-icon"] == "true" ? false : true
345+ shaped: notification.hints["x-canonical-non-shaped-icon"] !== "true"
327 fileSource: icon.fileSource
346 fileSource: icon.fileSource
328− visible: fileSource !== undefined && fileSource !== "" && type === Notification.Confirmation
347+ visible: fileSource !== "" && type === Notification.Confirmation
329 anchors.horizontalCenter: parent.horizontalCenter
348 anchors.horizontalCenter: parent.horizontalCenter
330 }
349 }
331
350
@@ -336,44 +355,32 @@ StyledItem {
336 anchors.horizontalCenter: parent.horizontalCenter
355 anchors.horizontalCenter: parent.horizontalCenter
337 visible: type === Notification.Confirmation && body !== ""
356 visible: type === Notification.Confirmation && body !== ""
338 fontSize: "medium"
357 fontSize: "medium"
358+ font.weight: Font.Light
359+ color: theme.palette.normal.backgroundSecondaryText
339 wrapMode: Text.WordWrap
360 wrapMode: Text.WordWrap
340 maximumLineCount: 1
361 maximumLineCount: 1
341 elide: Text.ElideRight
362 elide: Text.ElideRight
342 textFormat: Text.PlainText
363 textFormat: Text.PlainText
343 }
364 }
344
365
345− UbuntuShape {
366+ ProgressBar {
346 id: valueIndicator
367 id: valueIndicator
347 objectName: "valueIndicator"
368 objectName: "valueIndicator"
348 visible: type === Notification.Confirmation
369 visible: type === Notification.Confirmation
349− property double value
370+ minimumValue: 0
350−
371+ maximumValue: 100
372+ showProgressPercentage: false
351 anchors {
373 anchors {
352 left: parent.left
374 left: parent.left
353 right: parent.right
375 right: parent.right
354− margins: contentSpacing
355 }
376 }
356−
357 height: units.gu(1)
377 height: units.gu(1)
358− backgroundColor: theme.palette.normal.background
359− aspect: UbuntuShape.Flat
360− radius: "small"
361−
362− UbuntuShape {
363− id: innerBar
364− objectName: "innerBar"
365− width: valueIndicator.width * valueIndicator.value / 100
366− height: units.gu(1)
367− backgroundColor: notification.hints["x-canonical-value-bar-tint"] === "true" ? theme.palette.normal.activity : theme.palette.highlighted.foreground
368− aspect: UbuntuShape.Flat
369− radius: "small"
370− }
371 }
378 }
372
379
373 Column {
380 Column {
374 id: dialogColumn
381 id: dialogColumn
375 objectName: "dialogListView"
382 objectName: "dialogListView"
376− spacing: units.gu(2)
383+ spacing: notification.margins
377
384
378 visible: count > 0
385 visible: count > 0
379
386
@@ -417,12 +424,11 @@ StyledItem {
417 anchors {
424 anchors {
418 left: parent.left
425 left: parent.left
419 right: parent.right
426 right: parent.right
420− margins: contentSpacing
421 }
427 }
422
428
423− spacing: contentSpacing
429+ spacing: notification.margins
424
430
425− visible: notification.type === Notification.SnapDecision && oneOverTwoRepeaterTop.count === 3
431+ visible: notification.type === Notification.SnapDecision && oneOverTwoRepeaterTop.count === 3 && notification.expanded
426
432
427 Repeater {
433 Repeater {
428 id: oneOverTwoRepeaterTop
434 id: oneOverTwoRepeaterTop
@@ -437,11 +443,13 @@ StyledItem {
437 Component {
443 Component {
438 id: oneOverTwoButtonTop
444 id: oneOverTwoButtonTop
439
445
440− Button {
446+ NotificationButton {
441 objectName: "notify_oot_button" + index
447 objectName: "notify_oot_button" + index
442 width: oneOverTwoCase.width
448 width: oneOverTwoCase.width
443 text: oneOverTwoLoaderTop.actionLabel
449 text: oneOverTwoLoaderTop.actionLabel
450+ outline: notification.hints["x-canonical-private-affirmative-tint"] !== "true"
444− color: notification.hints["x-canonical-private-affirmative-tint"] == "true" ? UbuntuColors.green : theme.palette.normal.baseText
451+ color: notification.hints["x-canonical-private-affirmative-tint"] === "true" ? theme.palette.normal.positive
452+ : theme.palette.normal.foreground
445 onClicked: notification.notification.invokeAction(oneOverTwoLoaderTop.actionId)
453 onClicked: notification.notification.invokeAction(oneOverTwoLoaderTop.actionId)
446 }
454 }
447 }
455 }
@@ -450,7 +458,7 @@ StyledItem {
450 }
458 }
451
459
452 Row {
460 Row {
453− spacing: contentSpacing
461+ spacing: notification.margins
454
462
455 Repeater {
463 Repeater {
456 id: oneOverTwoRepeaterBottom
464 id: oneOverTwoRepeaterBottom
@@ -465,11 +473,13 @@ StyledItem {
465 Component {
473 Component {
466 id: oneOverTwoButtonBottom
474 id: oneOverTwoButtonBottom
467
475
468− Button {
476+ NotificationButton {
469 objectName: "notify_oot_button" + index
477 objectName: "notify_oot_button" + index
470− width: oneOverTwoCase.width / 2 - spacing * 2
478+ width: oneOverTwoCase.width / 2 - spacing / 2
471 text: oneOverTwoLoaderBottom.actionLabel
479 text: oneOverTwoLoaderBottom.actionLabel
480+ outline: notification.hints["x-canonical-private-rejection-tint"] !== "true"
472− color: index == 1 && notification.hints["x-canonical-private-rejection-tint"] == "true" ? UbuntuColors.red : theme.palette.normal.baseText
481+ color: index == 1 && notification.hints["x-canonical-private-rejection-tint"] === "true" ? theme.palette.normal.negative
482+ : theme.palette.normal.foreground
473 onClicked: notification.notification.invokeAction(oneOverTwoLoaderBottom.actionId)
483 onClicked: notification.notification.invokeAction(oneOverTwoLoaderBottom.actionId)
474 }
484 }
475 }
485 }
@@ -486,10 +496,9 @@ StyledItem {
486 anchors {
496 anchors {
487 left: parent.left
497 left: parent.left
488 right: parent.right
498 right: parent.right
489− margins: contentSpacing
490 }
499 }
491− visible: notification.type === Notification.SnapDecision && actionRepeater.count > 0 && !oneOverTwoCase.visible
500+ visible: notification.type === Notification.SnapDecision && actionRepeater.count > 0 && !oneOverTwoCase.visible && notification.expanded
492− spacing: contentSpacing
501+ spacing: notification.margins
493 layoutDirection: Qt.RightToLeft
502 layoutDirection: Qt.RightToLeft
494
503
495 Loader {
504 Loader {
@@ -525,17 +534,19 @@ StyledItem {
525 Component {
534 Component {
526 id: actionButton
535 id: actionButton
527
536
528− Button {
537+ NotificationButton {
529 objectName: "notify_button" + index
538 objectName: "notify_button" + index
530− width: buttonRow.width / 2 - spacing * 2
539+ width: buttonRow.width / 2 - spacing / 2
531 text: loader.actionLabel
540 text: loader.actionLabel
541+ outline: (index == 0 && notification.hints["x-canonical-private-affirmative-tint"] !== "true") ||
542+ (index == 1 && notification.hints["x-canonical-private-rejection-tint"] !== "true")
532 color: {
543 color: {
533− var result = theme.palette.normal.baseText;
544+ var result = theme.palette.normal.foreground;
534− if (index == 0 && notification.hints["x-canonical-private-affirmative-tint"] == "true") {
545+ if (index == 0 && notification.hints["x-canonical-private-affirmative-tint"] === "true") {
535− result = UbuntuColors.green;
546+ result = theme.palette.normal.positive;
536 }
547 }
537− if (index == 1 && notification.hints["x-canonical-private-rejection-tint"] == "true") {
548+ if (index == 1 && notification.hints["x-canonical-private-rejection-tint"] === "true") {
538− result = UbuntuColors.red;
549+ result = theme.palette.normal.negative;
539 }
550 }
540 return result;
551 return result;
541 }
552 }
@@ -554,10 +565,9 @@ StyledItem {
554 anchors {
565 anchors {
555 left: parent.left
566 left: parent.left
556 right: parent.right
567 right: parent.right
557− margins: contentSpacing
558 }
568 }
559
569
560− visible: notification.type == Notification.SnapDecision && actionRepeater.count > 3 && !oneOverTwoCase.visible
570+ visible: notification.type === Notification.SnapDecision && actionRepeater.count > 3 && !oneOverTwoCase.visible && notification.expanded
561 model: notification.actions
571 model: notification.actions
562 expanded: false
572 expanded: false
563 startIndex: 2
573 startIndex: 2
qml/Notifications/NotificationButton.qmlnew file+72
@@ -0,0 +1,72 @@
1+/*
2+ * Copyright 2016 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import Ubuntu.Components 1.3
19+
20+Rectangle {
21+ id: root
22+ height: units.gu(4)
23+ radius: units.gu(0.6)
24+
25+ // to be read from outside
26+ readonly property alias containsMouse: mouseArea.containsMouse
27+
28+ // to be set from outside
29+ property bool outline: true
30+ property alias text: label.text
31+ property alias iconName: icon.name
32+ property bool hoverEnabled: false
33+
34+ signal clicked()
35+
36+ Label {
37+ id: label
38+ fontSize: "medium"
39+ font.weight: Font.Light
40+ anchors.centerIn: root
41+ color: outline ? theme.palette.normal.backgroundSecondaryText : "white"
42+ visible: text !== ""
43+ }
44+
45+ Icon {
46+ id: icon
47+ height: root.height * 2 / 3
48+ width: height
49+ anchors.centerIn: root
50+ color: "white"
51+ visible: !label.visible
52+ }
53+
54+ MouseArea {
55+ id: mouseArea
56+ anchors.fill: root
57+ hoverEnabled: root.hoverEnabled
58+ onClicked: {
59+ Haptics.play();
60+ root.clicked();
61+ }
62+ }
63+
64+ transformOrigin: Item.Top
65+ scale: mouseArea.pressed ? 0.98 : 1.0
66+ Behavior on scale {
67+ ScaleAnimator {
68+ duration: UbuntuAnimation.SnapDuration
69+ easing.type: Easing.Linear
70+ }
71+ }
72+}
qml/Notifications/NotificationMenuItemFactory.qml+5−11
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright 2013 Canonical Ltd.
2+ * Copyright 2013-2016 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 Lesser General Public License as published by
5 * it under the terms of the GNU Lesser General Public License as published by
@@ -12,9 +12,6 @@
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− * Nick Dedekind <nick.dedekind@canonical.com>
18 */
15 */
19
16
20import QtQuick 2.4
17import QtQuick 2.4
@@ -71,7 +68,7 @@ Loader {
71 id: textfield
68 id: textfield
72
69
73 Column {
70 Column {
74− spacing: units.gu(2)
71+ spacing: notification.margins
75
72
76 anchors {
73 anchors {
77 left: parent.left
74 left: parent.left
@@ -87,12 +84,10 @@ Loader {
87
84
88 Label {
85 Label {
89 text: menuData.label
86 text: menuData.label
90− color: notification.sdFontColor
87+ color: theme.palette.normal.backgroundSecondaryText
91 }
88 }
92
89
93 TextField {
90 TextField {
94− id: textfield
95−
96 // TODO using Qt.ImhNoPredictiveText here until lp #1291575 is fixed for ubuntu-ui-toolkit
91 // TODO using Qt.ImhNoPredictiveText here until lp #1291575 is fixed for ubuntu-ui-toolkit
97 inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText
92 inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText
98 anchors {
93 anchors {
@@ -115,7 +110,7 @@ Loader {
115 Row {
110 Row {
116 id: checkBoxRow
111 id: checkBoxRow
117
112
118− spacing: units.gu(.5)
113+ spacing: notification.margins
119
114
120 CheckBox {
115 CheckBox {
121 id: checkBox
116 id: checkBox
@@ -127,7 +122,7 @@ Loader {
127 Label {
122 Label {
128 anchors.verticalCenter: checkBox.verticalCenter
123 anchors.verticalCenter: checkBox.verticalCenter
129 text: i18n.tr("Show password")
124 text: i18n.tr("Show password")
130− color: notification.sdFontColor
125+ color: theme.palette.normal.backgroundSecondaryText
131
126
132 MouseArea {
127 MouseArea {
133 anchors.fill: parent
128 anchors.fill: parent
@@ -151,7 +146,6 @@ Loader {
151 errorText: errorAction.valid ? errorAction.state : ""
146 errorText: errorAction.valid ? errorAction.state : ""
152 retryText: notification.body
147 retryText: notification.body
153 background: menuFactory.background
148 background: menuFactory.background
154− darkenBackground: 0.4
155
149
156 onEntered: {
150 onEntered: {
157 menuModel.changeState(menuIndex, passphrase);
151 menuModel.changeState(menuIndex, passphrase);
qml/Notifications/Notifications.qml+4−5
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 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
@@ -27,11 +27,11 @@ ListView {
27 interactive: false
27 interactive: false
28
28
29 property real margin
29 property real margin
30− property bool useModal: snapDecisionProxyModel.count > 0
31 property bool hasMouse
30 property bool hasMouse
32− property bool inverseMode
33 property url background: ""
31 property url background: ""
34
32
33+ readonly property bool useModal: snapDecisionProxyModel.count > 0
34+
35 UnitySortFilterProxyModel {
35 UnitySortFilterProxyModel {
36 id: snapDecisionProxyModel
36 id: snapDecisionProxyModel
37 objectName: "snapDecisionProxyModel"
37 objectName: "snapDecisionProxyModel"
@@ -42,7 +42,7 @@ ListView {
42 }
42 }
43
43
44 property bool topmostIsFullscreen: false
44 property bool topmostIsFullscreen: false
45− spacing: topmostIsFullscreen ? 0 : units.gu(.5)
45+ spacing: topmostIsFullscreen ? 0 : units.gu(1)
46
46
47 currentIndex: count > 1 ? 1 : -1
47 currentIndex: count > 1 ? 1 : -1
48
48
@@ -62,7 +62,6 @@ ListView {
62 maxHeight: notificationList.height
62 maxHeight: notificationList.height
63 margins: notificationList.margin
63 margins: notificationList.margin
64 hasMouse: notificationList.hasMouse
64 hasMouse: notificationList.hasMouse
65− inverseMode: notificationList.inverseMode
66 background: notificationList.background
65 background: notificationList.background
67
66
68 // make sure there's no opacity-difference between the several
67 // make sure there's no opacity-difference between the several
qml/Notifications/OptionToggle.qml+12−10
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2014,2015 Canonical, Ltd.
2+ * Copyright (C) 2014-2016 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
@@ -19,22 +19,23 @@ import Ubuntu.Components 1.3
19import Ubuntu.Components.ListItems 1.3 as ListItem
19import Ubuntu.Components.ListItems 1.3 as ListItem
20import Ubuntu.Components.Popups 1.3
20import Ubuntu.Components.Popups 1.3
21
21
22−UbuntuShape {
22+Rectangle {
23 id: optionToggle
23 id: optionToggle
24
24
25 property bool expanded
25 property bool expanded
26 property var model
26 property var model
27 property int startIndex
27 property int startIndex
28− readonly property double itemHeight: units.gu(5)
28+ readonly property double itemHeight: units.gu(4)
29
29
30 signal triggered(string id)
30 signal triggered(string id)
31
31
32− backgroundColor: theme.palette.normal.base
32+ color: theme.palette.normal.background
33− aspect: UbuntuShape.Flat
34 height: expanded ? (optionToggleRepeater.count - startIndex) * itemHeight : itemHeight
33 height: expanded ? (optionToggleRepeater.count - startIndex) * itemHeight : itemHeight
35 width: parent.width
34 width: parent.width
36− radius: "medium"
35+ radius: units.gu(0.6)
37 clip: true
36 clip: true
37+ border.width: units.dp(1)
38+ border.color: theme.palette.normal.base
38
39
39 Column {
40 Column {
40 id: optionToggleContent
41 id: optionToggleContent
@@ -75,7 +76,7 @@ UbuntuShape {
75 id: delegateIcon
76 id: delegateIcon
76 anchors {
77 anchors {
77 left: parent.left
78 left: parent.left
78− leftMargin: units.gu(2)
79+ leftMargin: units.gu(1)
79 verticalCenter: parent.verticalCenter
80 verticalCenter: parent.verticalCenter
80 }
81 }
81 visible: index !== startIndex
82 visible: index !== startIndex
@@ -90,14 +91,15 @@ UbuntuShape {
90 left: delegateIcon.visible ? delegateIcon.right : parent.left
91 left: delegateIcon.visible ? delegateIcon.right : parent.left
91 leftMargin: delegateIcon.visible ? units.gu(1) : units.gu(2)
92 leftMargin: delegateIcon.visible ? units.gu(1) : units.gu(2)
92 right: parent.right
93 right: parent.right
93− rightMargin: units.gu(2)
94+ rightMargin: units.gu(1)
94 verticalCenter: delegateIcon.visible ? delegateIcon.verticalCenter : parent.verticalCenter
95 verticalCenter: delegateIcon.visible ? delegateIcon.verticalCenter : parent.verticalCenter
95 }
96 }
96
97
97 width: parent.width
98 width: parent.width
98 text: splitLabel[3]
99 text: splitLabel[3]
99− color: theme.palette.normal.backgroundText
100+ color: theme.palette.normal.backgroundSecondaryText
100 fontSize: "small"
101 fontSize: "small"
102+ font.weight: Font.Light
101 maximumLineCount: 1
103 maximumLineCount: 1
102 elide: Text.ElideRight
104 elide: Text.ElideRight
103 }
105 }
@@ -105,7 +107,7 @@ UbuntuShape {
105 Icon {
107 Icon {
106 anchors {
108 anchors {
107 right: parent.right
109 right: parent.right
108− rightMargin: units.gu(2)
110+ rightMargin: units.gu(1)
109 verticalCenter: delegateIcon.verticalCenter
111 verticalCenter: delegateIcon.verticalCenter
110 }
112 }
111
113
qml/Notifications/ShapedIcon.qml+1
@@ -27,6 +27,7 @@ Item {
27 visible: shaped
27 visible: shaped
28 source: realImage
28 source: realImage
29 sourceFillMode: UbuntuShape.PreserveAspectCrop
29 sourceFillMode: UbuntuShape.PreserveAspectCrop
30+ aspect: UbuntuShape.Flat
30 }
31 }
31
32
32 Image {
33 Image {
qml/Notifications/SwipeToAct.qml+16−20
@@ -20,7 +20,7 @@ import Ubuntu.Components 1.3
20Item {
20Item {
21 id: swipeToAct
21 id: swipeToAct
22
22
23− height: clickToAct ? leftButton.height : childrenRect.height
23+ height: clickToAct ? leftButton.height : row.height
24
24
25 signal leftTriggered()
25 signal leftTriggered()
26 signal rightTriggered()
26 signal rightTriggered()
@@ -36,8 +36,8 @@ Item {
36 property double opacityDelta
36 property double opacityDelta
37 readonly property double sliderHeight: units.gu(8)
37 readonly property double sliderHeight: units.gu(8)
38 readonly property double gap: units.gu(1)
38 readonly property double gap: units.gu(1)
39− readonly property color sliderMainColor: "#b2b2b2"
39+ readonly property color sliderMainColor: theme.palette.normal.base
40− readonly property color sliderBGColor: "#f4f4f4"
40+ readonly property color sliderBGColor: theme.palette.normal.background
41 readonly property double halfWay: mouseArea.drag.maximumX / 2
41 readonly property double halfWay: mouseArea.drag.maximumX / 2
42
42
43 UbuntuNumberAnimation on opacityDelta {
43 UbuntuNumberAnimation on opacityDelta {
@@ -66,32 +66,30 @@ Item {
66 }
66 }
67 }
67 }
68
68
69− Button {
69+ NotificationButton {
70 id: leftButton
70 id: leftButton
71 objectName: "leftButton"
71 objectName: "leftButton"
72 anchors.verticalCenter: parent.verticalCenter
72 anchors.verticalCenter: parent.verticalCenter
73 anchors.left: parent.left
73 anchors.left: parent.left
74 iconName: leftIconName
74 iconName: leftIconName
75 visible: clickToAct
75 visible: clickToAct
76+ outline: false
76− width: (parent.width / 2) - priv.gap
77+ width: (parent.width / 2) - priv.gap / 2
77− color: UbuntuColors.red
78+ color: theme.palette.normal.negative
78− onClicked: {
79− leftTriggered()
79+ onClicked: leftTriggered()
80− }
81 }
80 }
82
81
83− Button {
82+ NotificationButton {
84 id: rightButton
83 id: rightButton
85 objectName: "rightButton"
84 objectName: "rightButton"
86 anchors.verticalCenter: parent.verticalCenter
85 anchors.verticalCenter: parent.verticalCenter
87 anchors.right: parent.right
86 anchors.right: parent.right
88 iconName: rightIconName
87 iconName: rightIconName
89 visible: clickToAct
88 visible: clickToAct
89+ outline: false
90− width: (parent.width / 2) - priv.gap
90+ width: (parent.width / 2) - priv.gap / 2
91− color: UbuntuColors.green
91+ color: theme.palette.normal.positive
92− onClicked: {
93− rightTriggered()
92+ onClicked: rightTriggered()
94− }
95 }
93 }
96
94
97 UbuntuShape {
95 UbuntuShape {
@@ -108,10 +106,9 @@ Item {
108 anchors.top: parent.top
106 anchors.top: parent.top
109 anchors.left: parent.left
107 anchors.left: parent.left
110 anchors.margins: priv.gap
108 anchors.margins: priv.gap
111− backgroundColor: UbuntuColors.red
109+ backgroundColor: theme.palette.normal.negative
112 aspect: UbuntuShape.Flat
110 aspect: UbuntuShape.Flat
113
111
114− state: "normal"
115 height: units.gu(6)
112 height: units.gu(6)
116 width: units.gu(6)
113 width: units.gu(6)
117 radius: "medium"
114 radius: "medium"
@@ -195,7 +192,7 @@ Item {
195 Icon {
192 Icon {
196 anchors.fill: parent
193 anchors.fill: parent
197 anchors.margins: units.gu(1.5)
194 anchors.margins: units.gu(1.5)
198− source: "grip-large.svg"
195+ name: "grip-large"
199 color: "white"
196 color: "white"
200 }
197 }
201 }
198 }
@@ -226,10 +223,9 @@ Item {
226 anchors.top: parent.top
223 anchors.top: parent.top
227 anchors.right: parent.right
224 anchors.right: parent.right
228 anchors.margins: priv.gap
225 anchors.margins: priv.gap
229− backgroundColor: UbuntuColors.green
226+ backgroundColor: theme.palette.normal.positive
230 aspect: UbuntuShape.Flat
227 aspect: UbuntuShape.Flat
231
228
232− state: "normal"
233 height: units.gu(6)
229 height: units.gu(6)
234 width: units.gu(6)
230 width: units.gu(6)
235 radius: "medium"
231 radius: "medium"
qml/Notifications/grip-large.svgdeleted−150
@@ -1,150 +0,0 @@
1−<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2−<!-- Created with Inkscape (http://www.inkscape.org/) -->
3−
4−<svg
5− xmlns:dc="http://purl.org/dc/elements/1.1/"
6− xmlns:cc="http://creativecommons.org/ns#"
7− xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
8− xmlns:svg="http://www.w3.org/2000/svg"
9− xmlns="http://www.w3.org/2000/svg"
10− xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
11− xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
12− width="90"
13− height="90"
14− id="svg4874"
15− version="1.1"
16− inkscape:version="0.91+devel r"
17− viewBox="0 0 90 90.000001"
18− sodipodi:docname="grip-large01.svg">
19− <defs
20− id="defs4876" />
21− <sodipodi:namedview
22− id="base"
23− pagecolor="#ffffff"
24− bordercolor="#666666"
25− borderopacity="1.0"
26− inkscape:pageopacity="0.0"
27− inkscape:pageshadow="2"
28− inkscape:zoom="7.9580786"
29− inkscape:cx="13.632541"
30− inkscape:cy="49.591116"
31− inkscape:document-units="px"
32− inkscape:current-layer="g1708"
33− showgrid="true"
34− showborder="true"
35− fit-margin-top="0"
36− fit-margin-left="0"
37− fit-margin-right="0"
38− fit-margin-bottom="0"
39− inkscape:snap-bbox="true"
40− inkscape:bbox-paths="true"
41− inkscape:bbox-nodes="true"
42− inkscape:snap-bbox-edge-midpoints="true"
43− inkscape:snap-bbox-midpoints="true"
44− inkscape:object-paths="true"
45− inkscape:snap-intersection-paths="true"
46− inkscape:object-nodes="true"
47− inkscape:snap-smooth-nodes="true"
48− inkscape:snap-midpoints="true"
49− inkscape:snap-object-midpoints="true"
50− inkscape:snap-center="true"
51− showguides="true"
52− inkscape:guide-bbox="true">
53− <inkscape:grid
54− type="xygrid"
55− id="grid5451"
56− empspacing="6" />
57− <sodipodi:guide
58− orientation="1,0"
59− position="6,77"
60− id="guide4063" />
61− <sodipodi:guide
62− orientation="1,0"
63− position="3,78"
64− id="guide4065" />
65− <sodipodi:guide
66− orientation="0,1"
67− position="55,84"
68− id="guide4067" />
69− <sodipodi:guide
70− orientation="0,1"
71− position="53,87"
72− id="guide4069" />
73− <sodipodi:guide
74− orientation="0,1"
75− position="20,3"
76− id="guide4071" />
77− <sodipodi:guide
78− orientation="0,1"
79− position="20,6"
80− id="guide4073" />
81− <sodipodi:guide
82− orientation="1,0"
83− position="87,7"
84− id="guide4075" />
85− <sodipodi:guide
86− orientation="1,0"
87− position="84,7"
88− id="guide4077" />
89− <sodipodi:guide
90− orientation="0,1"
91− position="58,81"
92− id="guide4074" />
93− <sodipodi:guide
94− orientation="1,0"
95− position="9,74"
96− id="guide4076" />
97− <sodipodi:guide
98− orientation="0,1"
99− position="21,9"
100− id="guide4078" />
101− <sodipodi:guide
102− orientation="1,0"
103− position="81,4"
104− id="guide4080" />
105− </sodipodi:namedview>
106− <metadata
107− id="metadata4879">
108− <rdf:RDF>
109− <cc:Work
110− rdf:about="">
111− <dc:format>image/svg+xml</dc:format>
112− <dc:type
113− rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
114− <dc:title></dc:title>
115− </cc:Work>
116− </rdf:RDF>
117− </metadata>
118− <g
119− inkscape:label="Layer 1"
120− inkscape:groupmode="layer"
121− id="layer1"
122− transform="translate(67.857146,-84.50504)">
123− <g
124− transform="matrix(0,-1,-1,0,373.50506,516.50504)"
125− id="g4845"
126− style="display:inline">
127− <g
128− inkscape:label="Layer 1"
129− id="g1708"
130− transform="matrix(0,-1,-1,0,1394.3622,441.36221)"
131− inkscape:export-filename="add01.png"
132− inkscape:export-xdpi="90"
133− inkscape:export-ydpi="90">
134− <rect
135− style="color:#000000;fill:none;stroke:none;stroke-width:7.5;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
136− id="rect1710"
137− width="90"
138− height="90"
139− x="0"
140− y="962.36218" />
141− <path
142− style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
143− d="m 9.000004,971.3622 c -2.209139,0 -4,1.79086 -4,4 0,2.20914 1.790861,4 4,4 2.209139,0 4,-1.79086 4,-4 0,-2.20914 -1.790861,-4 -4,-4 z m 18.199219,0 c -2.209139,0 -4,1.79086 -4,4 0,2.20914 1.790861,4 4,4 2.209139,0 4,-1.79086 4,-4 0,-2.20914 -1.790861,-4 -4,-4 z m 17.800781,0 c -2.209139,0 -4,1.79086 -4,4 0,2.20914 1.790861,4 4,4 2.209139,0 4,-1.79086 4,-4 0,-2.20914 -1.790861,-4 -4,-4 z m 18.199219,0 c -2.209139,0 -4,1.79086 -4,4 0,2.20914 1.790861,4 4,4 2.209139,0 4,-1.79086 4,-4 0,-2.20914 -1.790861,-4 -4,-4 z m 17.800781,0 c -2.209139,0 -4,1.79086 -4,4 0,2.20914 1.790861,4 4,4 2.209139,0 4,-1.79086 4,-4 0,-2.20914 -1.790861,-4 -4,-4 z m -72,16 c -2.209139,0 -4,1.79086 -4,4 0,2.20914 1.790861,4 4,4 2.209139,0 4,-1.79086 4,-4 0,-2.20914 -1.790861,-4 -4,-4 z m 18.199219,0 c -2.209139,0 -4,1.79086 -4,4 0,2.20914 1.790861,4 4,4 2.209139,0 4,-1.79086 4,-4 0,-2.20914 -1.790861,-4 -4,-4 z m 17.800781,0 c -2.209139,0 -4,1.79086 -4,4 0,2.20914 1.790861,4 4,4 2.209139,0 4,-1.79086 4,-4 0,-2.20914 -1.790861,-4 -4,-4 z m 18.199219,0 c -2.209139,0 -4,1.79086 -4,4 0,2.20914 1.790861,4 4,4 2.209139,0 4,-1.79086 4,-4 0,-2.20914 -1.790861,-4 -4,-4 z m 17.800781,0 c -2.209139,0 -4,1.79086 -4,4 0,2.20914 1.790861,4 4,4 2.209139,0 4,-1.79086 4,-4 0,-2.20914 -1.790861,-4 -4,-4 z m -72,16 c -2.209139,0 -4,1.7909 -4,4 0,2.2091 1.790861,4 4,4 2.209139,0 4,-1.7909 4,-4 0,-2.2091 -1.790861,-4 -4,-4 z m 18.199219,0 c -2.209139,0 -4,1.7909 -4,4 0,2.2091 1.790861,4 4,4 2.209139,0 4,-1.7909 4,-4 0,-2.2091 -1.790861,-4 -4,-4 z m 17.800781,0 c -2.209139,0 -4,1.7909 -4,4 0,2.2091 1.790861,4 4,4 2.209139,0 4,-1.7909 4,-4 0,-2.2091 -1.790861,-4 -4,-4 z m 18.199219,0 c -2.209139,0 -4,1.7909 -4,4 0,2.2091 1.790861,4 4,4 2.209139,0 4,-1.7909 4,-4 0,-2.2091 -1.790861,-4 -4,-4 z m 17.800781,0 c -2.209139,0 -4,1.7909 -4,4 0,2.2091 1.790861,4 4,4 2.209139,0 4,-1.7909 4,-4 0,-2.2091 -1.790861,-4 -4,-4 z m -72,16 c -2.209139,0 -4,1.7909 -4,4 0,2.2091 1.790861,4 4,4 2.209139,0 4,-1.7909 4,-4 0,-2.2091 -1.790861,-4 -4,-4 z m 18.199219,0 c -2.209139,0 -4,1.7909 -4,4 0,2.2091 1.790861,4 4,4 2.209139,0 4,-1.7909 4,-4 0,-2.2091 -1.790861,-4 -4,-4 z m 17.800781,0 c -2.209139,0 -4,1.7909 -4,4 0,2.2091 1.790861,4 4,4 2.209139,0 4,-1.7909 4,-4 0,-2.2091 -1.790861,-4 -4,-4 z m 18.199219,0 c -2.209139,0 -4,1.7909 -4,4 0,2.2091 1.790861,4 4,4 2.209139,0 4,-1.7909 4,-4 0,-2.2091 -1.790861,-4 -4,-4 z m 17.800781,0 c -2.209139,0 -4,1.7909 -4,4 0,2.2091 1.790861,4 4,4 2.209139,0 4,-1.7909 4,-4 0,-2.2091 -1.790861,-4 -4,-4 z m -72,16 c -2.209139,0 -4,1.7909 -4,4 0,2.2091 1.790861,4 4,4 2.209139,0 4,-1.7909 4,-4 0,-2.2091 -1.790861,-4 -4,-4 z m 18.199219,0 c -2.209139,0 -4,1.7909 -4,4 0,2.2091 1.790861,4 4,4 2.209139,0 4,-1.7909 4,-4 0,-2.2091 -1.790861,-4 -4,-4 z m 17.800781,0 c -2.209139,0 -4,1.7909 -4,4 0,2.2091 1.790861,4 4,4 2.209139,0 4,-1.7909 4,-4 0,-2.2091 -1.790861,-4 -4,-4 z m 18.199219,0 c -2.209139,0 -4,1.7909 -4,4 0,2.2091 1.790861,4 4,4 2.209139,0 4,-1.7909 4,-4 0,-2.2091 -1.790861,-4 -4,-4 z m 17.800781,0 c -2.209139,0 -4,1.7909 -4,4 0,2.2091 1.790861,4 4,4 2.209139,0 4,-1.7909 4,-4 0,-2.2091 -1.790861,-4 -4,-4 z"
144− id="path4169"
145− inkscape:connector-curvature="0"
146− sodipodi:nodetypes="sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss" />
147− </g>
148− </g>
149− </g>
150−</svg>
qml/Shell.qml+3−11
@@ -639,17 +639,10 @@ Item {
639 onActiveChanged: unlockWhenDoneWithWizard()
639 onActiveChanged: unlockWhenDoneWithWizard()
640 }
640 }
641
641
642− Rectangle {
642+ MouseArea { // modal notifications prevent interacting with other contents
643− id: modalNotificationBackground
644−
645− visible: notifications.useModal
646− color: "#000000"
647 anchors.fill: parent
643 anchors.fill: parent
648− opacity: 0.9
644+ visible: notifications.useModal
649−
645+ enabled: visible
650− MouseArea {
651− anchors.fill: parent
652− }
653 }
646 }
654
647
655 Notifications {
648 Notifications {
@@ -658,7 +651,6 @@ Item {
658 model: NotificationBackend.Model
651 model: NotificationBackend.Model
659 margin: units.gu(1)
652 margin: units.gu(1)
660 hasMouse: shell.hasMouse
653 hasMouse: shell.hasMouse
661− inverseMode: panel.indicators.shown
662 background: wallpaperResolver.background
654 background: wallpaperResolver.background
663
655
664 y: topmostIsFullscreen ? 0 : panel.panelHeight
656 y: topmostIsFullscreen ? 0 : panel.panelHeight
tests/mocks/Unity/Notifications/MockNotificationModel.cpp+6−11
@@ -90,6 +90,7 @@ QVariant MockNotificationModel::data(const QModelIndex &index, int role) const {
90}
90}
91
91
92void MockNotificationModel::append(MockNotification* n) {
92void MockNotificationModel::append(MockNotification* n) {
93+ QQmlEngine::setObjectOwnership(n, QQmlEngine::CppOwnership);
93 int location = m_queue.size();
94 int location = m_queue.size();
94 QModelIndex insertionPoint = QModelIndex();
95 QModelIndex insertionPoint = QModelIndex();
95 beginInsertRows(insertionPoint, location, location);
96 beginInsertRows(insertionPoint, location, location);
@@ -98,9 +99,10 @@ void MockNotificationModel::append(MockNotification* n) {
98}
99}
99
100
100MockNotification* MockNotificationModel::getNotification(int id) const {
101MockNotification* MockNotificationModel::getNotification(int id) const {
101− for(int i=0; i < m_queue.size(); i++) {
102+ for(int i = 0; i < m_queue.size(); i++) {
103+ MockNotification* n = m_queue.at(i);
102− if(m_queue[i]->getID() == id) {
104+ if(n && n->getID() == id) {
103− return m_queue[i];
105+ return n;
104 }
106 }
105 }
107 }
106
108
@@ -130,14 +132,7 @@ void MockNotificationModel::removeInternal(int loc) {
130}
132}
131
133
132MockNotification* MockNotificationModel::getRaw(const int notificationId) const {
134MockNotification* MockNotificationModel::getRaw(const int notificationId) const {
133− for(int i = 0; i < m_queue.size(); i++) {
134− if(m_queue[i]->getID() == notificationId) {
135+ return getNotification(notificationId);
135− MockNotification* n = m_queue[i];
136− return n;
137− }
138− }
139−
140− return nullptr;
141}
136}
142
137
143int MockNotificationModel::queued() const {
138int MockNotificationModel::queued() const {
tests/qmltests/Notifications/tst_Notifications.qml+50−54
@@ -31,26 +31,26 @@ Item {
31 height: notificationsRect.height
31 height: notificationsRect.height
32 property int index: 0
32 property int index: 0
33
33
34+ // add the default/PlaceHolder notification to the model
35+ Component.onCompleted: {
36+ var component = Qt.createComponent("Notification.qml")
37+ var n = component.createObject("notification", {"nid": index++,
38+ "type": Notification.PlaceHolder,
39+ "hints": {},
40+ "summary": "",
41+ "body": "",
42+ "icon": "",
43+ "secondaryIcon": "",
44+ "rawActions": []})
45+ n.completed.connect(mockModel.onCompleted)
46+ mockModel.append(n)
47+ }
48+
34 Row {
49 Row {
35 id: rootRow
50 id: rootRow
36
51
37 NotificationModel {
52 NotificationModel {
38 id: mockModel
53 id: mockModel
39−
40− // add the default/PlaceHolder notification to the model
41− Component.onCompleted: {
42− var component = Qt.createComponent("Notification.qml")
43− var n = component.createObject("notification", {"nid": index++,
44− "type": Notification.PlaceHolder,
45− "hints": {},
46− "summary": "",
47− "body": "",
48− "icon": "",
49− "secondaryIcon": "",
50− "rawActions": []})
51− n.completed.connect(mockModel.onCompleted)
52− append(n)
53− }
54 }
54 }
55
55
56 function add2over1SnapDecisionNotification() {
56 function add2over1SnapDecisionNotification() {
@@ -188,7 +188,6 @@ Item {
188 anchors.fill: parent
188 anchors.fill: parent
189 model: mockModel
189 model: mockModel
190 hasMouse: fakeMouseCB.checked
190 hasMouse: fakeMouseCB.checked
191− inverseMode: inverseModeCB.checked
192 }
191 }
193 }
192 }
194
193
@@ -269,15 +268,9 @@ Item {
269 }
268 }
270 }
269 }
271
270
272− RowLayout {
271+ MouseTouchEmulationCheckbox {
273− Layout.fillWidth: true
272+ id: mouseEmulation
274− CheckBox {
273+ checked: false
275− id: inverseModeCB
276− }
277− Label {
278− text: "Inverse mode"
279− anchors.verticalCenter: parent.verticalCenter
280− }
281 }
274 }
282 }
275 }
283 }
276 }
@@ -562,6 +555,12 @@ Item {
562 signalName: "actionInvoked"
555 signalName: "actionInvoked"
563 }
556 }
564
557
558+ function init() {
559+ while (mockModel.count > 0) {
560+ mockModel.remove(0);
561+ }
562+ }
563+
565 function cleanup() {
564 function cleanup() {
566 clickThroughSpy.clear()
565 clickThroughSpy.clear()
567 actionSpy.clear()
566 actionSpy.clear()
@@ -582,10 +581,9 @@ Item {
582 waitForRendering(notifications);
581 waitForRendering(notifications);
583
582
584 var notification = findChild(notifications, "notification" + (mockModel.count - 1))
583 var notification = findChild(notifications, "notification" + (mockModel.count - 1))
585− verify(notification !== undefined, "notification wasn't found");
586−
587 waitForRendering(notification);
584 waitForRendering(notification);
585+ verify(notification, "notification wasn't found");
588− tryCompare(notification, "height", notification.state === "contracted" ? units.gu(10) : notification.implicitHeight);
586+ tryCompare(notification, "height", notification.implicitHeight);
589
587
590 var icon = findChild(notification, "icon")
588 var icon = findChild(notification, "icon")
591 var centeredIcon = findChild(notification, "centeredIcon")
589 var centeredIcon = findChild(notification, "centeredIcon")
@@ -596,7 +594,6 @@ Item {
596 var buttonRow = findChild(notification, "buttonRow")
594 var buttonRow = findChild(notification, "buttonRow")
597 var valueIndicator = findChild(notification, "valueIndicator")
595 var valueIndicator = findChild(notification, "valueIndicator")
598 var valueLabel = findChild(notification, "valueLabel")
596 var valueLabel = findChild(notification, "valueLabel")
599− var innerBar = findChild(notification, "innerBar")
600
597
601 compare(icon.visible, data.iconVisible, "avatar-icon visibility is incorrect")
598 compare(icon.visible, data.iconVisible, "avatar-icon visibility is incorrect")
602 if (icon.visible) {
599 if (icon.visible) {
@@ -607,9 +604,6 @@ Item {
607 compare(centeredIcon.shaped, data.shaped, "shaped-status is incorrect")
604 compare(centeredIcon.shaped, data.shaped, "shaped-status is incorrect")
608 }
605 }
609 compare(valueIndicator.visible, data.valueVisible, "value-indicator visibility is incorrect")
606 compare(valueIndicator.visible, data.valueVisible, "value-indicator visibility is incorrect")
610− if (valueIndicator.visible) {
611− verify(innerBar.color === data.valueTinted ? UbuntuColors.orange : "white", "value-bar has the wrong color-tint")
612− }
613 compare(valueLabel.visible, data.valueLabelVisible, "value-label visibility is incorrect")
607 compare(valueLabel.visible, data.valueLabelVisible, "value-label visibility is incorrect")
614
608
615 // test input does not fall through
609 // test input does not fall through
@@ -629,7 +623,12 @@ Item {
629 // for their height animations to finish before continuing
623 // for their height animations to finish before continuing
630 for (var i = 0; i < mockModel.count; ++i) {
624 for (var i = 0; i < mockModel.count; ++i) {
631 var n = findChild(notifications, "notification" + i)
625 var n = findChild(notifications, "notification" + i)
632− tryCompare(n, "height", n.state === "contracted" ? units.gu(10) : n.implicitHeight);
626+ if (n.type === Notification.PlaceHolder)
627+ continue;
628+ waitForRendering(n);
629+ var outterColumn = findChild(n, "outterColumn");
630+ var shapedBack = findChild(n, "shapedBack");
631+ tryCompare(n, "height", outterColumn.height + n.margins * 2 + shapedBack.anchors.topMargin);
633 }
632 }
634
633
635 if (data.hasSound) {
634 if (data.hasSound) {
@@ -662,7 +661,7 @@ Item {
662 tryCompareFunction(function() { return comboButton.expanded === false; }, true);
661 tryCompareFunction(function() { return comboButton.expanded === false; }, true);
663
662
664 // click to expand
663 // click to expand
665− tryCompareFunction(function() { mouseClick(comboButton, comboButton.width / 2, comboButton.height / 2); return comboButton.expanded === true; }, true);
664+ tryCompareFunction(function() { mouseClick(comboButton, comboButton.width / 2, comboButton.height / 2); return comboButton.expanded; }, true);
666
665
667 // try clicking on choices in expanded comboList
666 // try clicking on choices in expanded comboList
668 var choiceButton1 = findChild(notification, "notify_button3")
667 var choiceButton1 = findChild(notification, "notify_button3")
@@ -676,7 +675,7 @@ Item {
676 actionSpy.clear()
675 actionSpy.clear()
677
676
678 // click to collapse
677 // click to collapse
679− tryCompareFunction(function() { mouseClick(comboButton, comboButton.width / 2, comboButton.height / 2); return comboButton.expanded == false; }, true);
678+ tryCompareFunction(function() { mouseClick(comboButton, comboButton.width / 2, comboButton.height / 2); return !comboButton.expanded; }, true);
680 } else {
679 } else {
681 mouseClick(buttonCancel)
680 mouseClick(buttonCancel)
682 compare(actionSpy.signalArguments[0][0], data.n.actions.data(1, ActionModel.RoleActionId), "got wrong id for negative action")
681 compare(actionSpy.signalArguments[0][0], data.n.actions.data(1, ActionModel.RoleActionId), "got wrong id for negative action")
@@ -691,19 +690,18 @@ Item {
691 var dragY = notification.height / 2;
690 var dragY = notification.height / 2;
692 touchFlick(notification, dragStart, dragY, dragEnd, dragY)
691 touchFlick(notification, dragStart, dragY, dragEnd, dragY)
693 waitForRendering(notification)
692 waitForRendering(notification)
694− if ((data.n.type === Notification.SnapDecision && notification.state === "expanded") || data.n.type === Notification.Confirmation) {
695− tryCompare(mockModel, "count", before)
696− } else {
697− tryCompare(mockModel, "count", before - 1)
693+ tryCompare(mockModel, "count", before - 1)
698− }
699 }
694 }
700
695
701− function test_clickToClose_data() { // reuse the data
696+ function test_closeButton_data() { // reuse the data
702 notifications.hasMouse = true;
697 notifications.hasMouse = true;
703 return test_NotificationRenderer_data();
698 return test_NotificationRenderer_data();
704 }
699 }
705
700
706− function test_clickToClose(data) {
701+ function test_closeButton(data) {
702+ // hook up notification's completed-signal with model's onCompleted-slot, so that remove() (model) happens on close() (notification)
703+ data.n.completed.connect(mockModel.onCompleted)
704+
707 // populate model with some mock notifications
705 // populate model with some mock notifications
708 mockModel.append(data.n)
706 mockModel.append(data.n)
709
707
@@ -712,21 +710,19 @@ Item {
712 waitForRendering(notifications);
710 waitForRendering(notifications);
713
711
714 var notification = findChild(notifications, "notification" + (mockModel.count - 1))
712 var notification = findChild(notifications, "notification" + (mockModel.count - 1))
713+ waitForRendering(notification);
715 verify(!!notification, "notification wasn't found");
714 verify(!!notification, "notification wasn't found");
716
715
717− // click-to-dismiss check
716+ // close button check
718− waitForRendering(notification);
717+ mouseMove(notification, notification.width/2, notification.height/2);
718+ var closeButton = findChild(notification, "closeButton");
719+ tryCompare(closeButton, "visible", true);
719 var before = mockModel.count;
720 var before = mockModel.count;
720− var canBeClosed = notification.canBeClosed;
721− mouseClick(notification);
721+ mouseClick(closeButton);
722−
722+ waitForRendering(notification);
723− if (canBeClosed) {
723+
724− // closing the notification, the model count should be one less
724+ // closing the notification, the model count should be one less
725− tryCompare(mockModel, "count", before - 1)
725+ tryCompare(mockModel, "count", before - 1)
726− } else {
727− // not closing, model stays the same
728− tryCompare(mockModel, "count", before)
729− }
730 }
726 }
731
727
732 function cleanupTestCase() {
728 function cleanupTestCase() {
tests/qmltests/Notifications/tst_OptionToggle.qml+10−1
@@ -36,6 +36,10 @@ Row {
36 console.log("Action invoked", actionId);
36 console.log("Action invoked", actionId);
37 mockModel.actionInvoked(actionId)
37 mockModel.actionInvoked(actionId)
38 }
38 }
39+
40+ function close() {
41+ console.log("Close notification");
42+ }
39 }
43 }
40 }
44 }
41
45
@@ -96,7 +100,7 @@ Row {
96
100
97 width: units.gu(40)
101 width: units.gu(40)
98 height: units.gu(81)
102 height: units.gu(81)
99− color: "#cccccc"
103+ color: theme.palette.normal.background
100
104
101 MouseArea{
105 MouseArea{
102 id: clickThroughCatcher
106 id: clickThroughCatcher
@@ -143,6 +147,11 @@ Row {
143 text: "clear model"
147 text: "clear model"
144 onClicked: clearNotifications()
148 onClicked: clearNotifications()
145 }
149 }
150+
151+ MouseTouchEmulationCheckbox {
152+ id: mouseEmulation
153+ checked: false
154+ }
146 }
155 }
147 }
156 }
148
157
tests/qmltests/Notifications/tst_SwipeToAct.qml+11−1
@@ -37,6 +37,10 @@ Item {
37 function invokeAction(actionId) {
37 function invokeAction(actionId) {
38 mockModel.actionInvoked(actionId)
38 mockModel.actionInvoked(actionId)
39 }
39 }
40+
41+ function close() {
42+ console.log("Close notification");
43+ }
40 }
44 }
41 }
45 }
42
46
@@ -73,7 +77,7 @@ Item {
73 summary: "Incoming call",
77 summary: "Incoming call",
74 body: "Frank Zappa\n+44 (0)7736 027340",
78 body: "Frank Zappa\n+44 (0)7736 027340",
75 icon: "../../tests/graphics/avatars/amanda.png",
79 icon: "../../tests/graphics/avatars/amanda.png",
76− secondaryIcon: "incoming-call",
80+ secondaryIcon: "image://theme/incoming-call",
77 actions: [{ id: "ok_id", label: "Ok"},
81 actions: [{ id: "ok_id", label: "Ok"},
78 { id: "cancel_id", label: "Cancel"},
82 { id: "cancel_id", label: "Cancel"},
79 { id: "foo_id", label: "Foo"},
83 { id: "foo_id", label: "Foo"},
@@ -162,6 +166,11 @@ Item {
162 anchors.verticalCenter: parent.verticalCenter
166 anchors.verticalCenter: parent.verticalCenter
163 }
167 }
164 }
168 }
169+
170+ MouseTouchEmulationCheckbox {
171+ id: mouseEmulation
172+ checked: false
173+ }
165 }
174 }
166 }
175 }
167
176
@@ -301,6 +310,7 @@ Item {
301
310
302 // add a mock mouse, test clicking the left/right buttons
311 // add a mock mouse, test clicking the left/right buttons
303 notifications.hasMouse = true;
312 notifications.hasMouse = true;
313+ waitForRendering(notification);
304 var leftButton = findChild(swipeButton, "leftButton");
314 var leftButton = findChild(swipeButton, "leftButton");
305 var rightButton = findChild(swipeButton, "rightButton");
315 var rightButton = findChild(swipeButton, "rightButton");
306
316
tests/qmltests/Notifications/tst_VisualSnapDecisionsQueue.qml+15−6
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2016 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
@@ -32,6 +32,10 @@ Row {
32 function invokeAction(actionId) {
32 function invokeAction(actionId) {
33 mockModel.actionInvoked(actionId)
33 mockModel.actionInvoked(actionId)
34 }
34 }
35+
36+ function close() {
37+ console.log("Close notification");
38+ }
35 }
39 }
36 }
40 }
37
41
@@ -180,6 +184,11 @@ Row {
180 text: "clear model"
184 text: "clear model"
181 onClicked: clearNotifications()
185 onClicked: clearNotifications()
182 }
186 }
187+
188+ MouseTouchEmulationCheckbox {
189+ id: mouseEmulation
190+ checked: false
191+ }
183 }
192 }
184 }
193 }
185
194
@@ -207,9 +216,9 @@ Row {
207 }
216 }
208
217
209 // check initial states once all five snap-decisions were appended to the model
218 // check initial states once all five snap-decisions were appended to the model
210− compare(snap_decision[0].state, "expanded", "state of first snap-decision is not expanded");
219+ compare(snap_decision[0].expanded, true, "state of first snap-decision is not expanded");
211 for (var index = 1; index < snap_decision.length; index++) {
220 for (var index = 1; index < snap_decision.length; index++) {
212− compare(snap_decision[index].state, "contracted", "state of "+ index + ".snap-decision is not contracted");
221+ compare(snap_decision[index].expanded, false, "state of "+ index + ".snap-decision is not contracted");
213 }
222 }
214
223
215 // click/tap on each snap-decision and verify only one is in expanded-state at any time
224 // click/tap on each snap-decision and verify only one is in expanded-state at any time
@@ -217,9 +226,9 @@ Row {
217 mouseClick(snap_decision[index])
226 mouseClick(snap_decision[index])
218 for (var kindex = 0; kindex < snap_decision.length; kindex++) {
227 for (var kindex = 0; kindex < snap_decision.length; kindex++) {
219 if (kindex == index) {
228 if (kindex == index) {
220− compare(snap_decision[kindex].state, "expanded", "state of "+ kindex + ".snap-decision is not expanded");
229+ compare(snap_decision[kindex].expanded, true, "state of "+ kindex + ".snap-decision is not expanded");
221 } else {
230 } else {
222− compare(snap_decision[kindex].state, "contracted", "state of "+ kindex + ".snap-decision is not contracted");
231+ compare(snap_decision[kindex].expanded, false, "state of "+ kindex + ".snap-decision is not contracted");
223 }
232 }
224 }
233 }
225 }
234 }
@@ -232,7 +241,7 @@ Row {
232
241
233 for (var index = 1; index < snap_decision.length; index++) {
242 for (var index = 1; index < snap_decision.length; index++) {
234 removeTopMostNotification();
243 removeTopMostNotification();
235− compare(snap_decision[index].state, "expanded", "state of " + index + ". snap-decision is not expanded");
244+ compare(snap_decision[index].expanded, true, "state of " + index + ". snap-decision is not expanded");
236 }
245 }
237 }
246 }
238 }
247 }