Unity 8
 All Classes Functions
Notification.qml
1 /*
2  * Copyright (C) 2013 Canonical, Ltd.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; version 3.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  */
16 
17 import QtQuick 2.0
18 import QtMultimedia 5.0
19 import Ubuntu.Components 1.1
20 import Unity.Notifications 1.0
21 import QMenuModel 0.1
22 import Utils 0.1
23 import "../Components/Flickables" as Flickables
24 
25 import Ubuntu.Components.ListItems 0.1 as ListItem
26 
27 Item {
28  id: notification
29 
30  property alias iconSource: icon.fileSource
31  property alias secondaryIconSource: secondaryIcon.source
32  property alias summary: summaryLabel.text
33  property alias body: bodyLabel.text
34  property alias value: valueIndicator.value
35  property var actions
36  property var notificationId
37  property var type
38  property var hints
39  property var notification
40  property color color
41  property bool fullscreen: false
42  property int maxHeight
43  property int margins
44  readonly property bool darkOnBright: panel.indicators.shown || type === Notification.SnapDecision
45  readonly property color red: "#fc4949"
46  readonly property color green: "#3fb24f"
47  readonly property color sdLightGrey: "#eaeaea"
48  readonly property color sdDarkGrey: "#dddddd"
49  readonly property color sdFontColor: "#5d5d5d"
50  readonly property real contentSpacing: units.gu(2)
51 
52  objectName: "background"
53  implicitHeight: type !== Notification.PlaceHolder ? (fullscreen ? maxHeight : outterColumn.height - shapedBack.anchors.topMargin + contentSpacing * 2) : 0
54 
55  color: (type === Notification.Confirmation && notificationList.useModal && !greeter.shown) || darkOnBright ? sdLightGrey : Qt.rgba(0.132, 0.117, 0.109, 0.97)
56  opacity: 1 // FIXME: 1 because of LP: #1354406 workaround, has to be 0 really
57 
58  state: {
59  var result = "";
60 
61  if (type == Notification.SnapDecision) {
62  if (ListView.view.currentIndex == index) {
63  result = "expanded";
64  } else {
65  if (ListView.view.count > 2) {
66  if (ListView.view.currentIndex == -1 && index == 1) {
67  result = "expanded";
68  } else {
69  result = "contracted";
70  }
71  } else {
72  result = "expanded";
73  }
74  }
75  }
76 
77  return result;
78  }
79 
80  Audio {
81  id: sound
82  objectName: "sound"
83  audioRole: MediaPlayer.alert
84  source: hints["suppress-sound"] != "true" && hints["sound-file"] != undefined ? hints["sound-file"] : ""
85  }
86 
87  // FIXME: using onCompleted because of LP: #1354406 workaround, has to be onOpacityChanged really
88  Component.onCompleted: {
89  if (opacity == 1.0 && hints["suppress-sound"] != "true" && sound.source != "") {
90  sound.play();
91  }
92  }
93 
94  onHintsChanged: {
95  if (type === Notification.Confirmation && opacity == 1.0 && hints["suppress-sound"] != "true" && sound.source != "") {
96  sound.play();
97  }
98  }
99 
100  Behavior on height {
101  id: normalHeightBehavior
102 
103  //enabled: menuItemFactory.progress == 1
104  enabled: true
105  SequentialAnimation {
106  PauseAnimation {
107  duration: UbuntuAnimation.SnapDuration
108  }
109  UbuntuNumberAnimation {
110  duration: UbuntuAnimation.SnapDuration
111  }
112  }
113  }
114 
115  states:[
116  State {
117  name: "contracted"
118  PropertyChanges {target: notification; height: units.gu(10)}
119  },
120  State {
121  name: "expanded"
122  PropertyChanges {target: notification; height: implicitHeight}
123  }
124  ]
125 
126  clip: fullscreen ? false : true
127 
128  visible: type != Notification.PlaceHolder
129 
130  UbuntuShape {
131  id: shapedBack
132 
133  visible: !fullscreen
134  anchors {
135  fill: parent
136  leftMargin: notification.margins
137  rightMargin: notification.margins
138  topMargin: type === Notification.Confirmation ? units.gu(.5) : 0
139  }
140  color: parent.color
141  opacity: parent.opacity
142  radius: "medium"
143  borderSource: "none"
144  }
145 
146  Rectangle {
147  id: nonShapedBack
148 
149  visible: fullscreen
150  anchors.fill: parent
151  color: parent.color
152  opacity: parent.opacity
153  }
154 
155  Item {
156  id: contents
157  anchors.fill: fullscreen ? nonShapedBack : shapedBack
158 
159  UnityMenuModelPaths {
160  id: paths
161 
162  source: hints["x-canonical-private-menu-model"]
163 
164  busNameHint: "busName"
165  actionsHint: "actions"
166  menuObjectPathHint: "menuPath"
167  }
168 
169  UnityMenuModel {
170  id: unityMenuModel
171 
172  property string lastNameOwner: ""
173 
174  busName: paths.busName
175  actions: paths.actions
176  menuObjectPath: paths.menuObjectPath
177  onNameOwnerChanged: {
178  if (lastNameOwner != "" && nameOwner == "" && notification.notification != undefined) {
179  notification.notification.close()
180  }
181  lastNameOwner = nameOwner
182  }
183  }
184 
185  Behavior on implicitHeight {
186  id: heightBehavior
187 
188  enabled: false
189  UbuntuNumberAnimation {
190  duration: UbuntuAnimation.SnapDuration
191  }
192  }
193 
194  // delay enabling height behavior until the add transition is complete
195  onOpacityChanged: if (opacity == 1) heightBehavior.enabled = true
196 
197  MouseArea {
198  id: interactiveArea
199 
200  anchors.fill: parent
201  objectName: "interactiveArea"
202  onClicked: {
203  if (notification.type == Notification.Interactive) {
204  notification.notification.invokeAction(actionRepeater.itemAt(0).actionId)
205  } else {
206  notificationList.currentIndex = index;
207  }
208  }
209  }
210 
211  Column {
212  id: outterColumn
213 
214  anchors {
215  left: parent.left
216  right: parent.right
217  top: parent.top
218  margins: 0
219  topMargin: fullscreen ? 0 : type === Notification.Confirmation ? units.gu(1) : units.gu(2)
220  }
221 
222  spacing: type === Notification.Confirmation ? units.gu(1) : units.gu(2)
223 
224  Row {
225  id: topRow
226 
227  spacing: contentSpacing
228  anchors {
229  left: parent.left
230  right: parent.right
231  margins: contentSpacing
232  }
233 
234  ShapedIcon {
235  id: icon
236 
237  objectName: "icon"
238  width: type == Notification.Ephemeral && !bodyLabel.visible ? units.gu(3) : units.gu(6)
239  height: width
240  shaped: notification.hints["x-canonical-non-shaped-icon"] == "true" ? false : true
241  visible: iconSource !== undefined && iconSource !== "" && type !== Notification.Confirmation
242  }
243 
244  Column {
245  id: labelColumn
246  width: secondaryIcon.visible ? parent.width - x - units.gu(4.5) : parent.width - x
247 
248  anchors.verticalCenter: (icon.visible && !bodyLabel.visible) ? icon.verticalCenter : undefined
249 
250  Label {
251  id: summaryLabel
252 
253  objectName: "summaryLabel"
254  anchors {
255  left: parent.left
256  right: parent.right
257  }
258  visible: type !== Notification.Confirmation
259  fontSize: "medium"
260  color: darkOnBright ? sdFontColor : Theme.palette.selected.backgroundText
261  elide: Text.ElideRight
262  textFormat: Text.PlainText
263  }
264 
265  Label {
266  id: bodyLabel
267 
268  objectName: "bodyLabel"
269  anchors {
270  left: parent.left
271  right: parent.right
272  }
273  visible: body != "" && type !== Notification.Confirmation
274  fontSize: "small"
275  color: darkOnBright ? sdFontColor : Theme.palette.selected.backgroundText
276  wrapMode: Text.WordWrap
277  maximumLineCount: type == Notification.SnapDecision ? 12 : 2
278  elide: Text.ElideRight
279  textFormat: Text.PlainText
280  }
281  }
282 
283  Image {
284  id: secondaryIcon
285 
286  objectName: "secondaryIcon"
287  width: units.gu(3)
288  height: units.gu(3)
289  visible: status === Image.Ready
290  fillMode: Image.PreserveAspectCrop
291  }
292  }
293 
294  ListItem.ThinDivider {
295  visible: type == Notification.SnapDecision
296  }
297 
298  ShapedIcon {
299  id: centeredIcon
300  objectName: "centeredIcon"
301  width: units.gu(5)
302  height: width
303  shaped: notification.hints["x-canonical-non-shaped-icon"] == "true" ? false : true
304  fileSource: icon.fileSource
305  visible: fileSource !== undefined && fileSource !== "" && type === Notification.Confirmation
306  anchors.horizontalCenter: parent.horizontalCenter
307  }
308 
309  Label {
310  id: valueLabel
311  objectName: "valueLabel"
312  text: body
313  anchors.horizontalCenter: parent.horizontalCenter
314  visible: type === Notification.Confirmation && body !== ""
315  fontSize: "medium"
316  color: darkOnBright ? sdFontColor : Theme.palette.selected.backgroundText
317  wrapMode: Text.WordWrap
318  maximumLineCount: 1
319  elide: Text.ElideRight
320  textFormat: Text.PlainText
321  }
322 
323  UbuntuShape {
324  id: valueIndicator
325  objectName: "valueIndicator"
326  visible: type === Notification.Confirmation
327  property double value
328 
329  anchors {
330  left: parent.left
331  right: parent.right
332  margins: contentSpacing
333  }
334 
335  height: units.gu(1)
336  color: darkOnBright ? UbuntuColors.darkGrey : UbuntuColors.lightGrey
337  borderSource: "none"
338  radius: "small"
339 
340  UbuntuShape {
341  id: innerBar
342  objectName: "innerBar"
343  width: valueIndicator.width * valueIndicator.value / 100
344  height: units.gu(1)
345  color: notification.hints["x-canonical-value-bar-tint"] === "true" ? UbuntuColors.orange : darkOnBright ? UbuntuColors.lightGrey : "white"
346  borderSource: "none"
347  radius: "small"
348  }
349  }
350 
351  Column {
352  id: dialogColumn
353  objectName: "dialogListView"
354  spacing: units.gu(2)
355 
356  visible: count > 0
357 
358  anchors {
359  left: parent.left
360  right: parent.right
361  top: fullscreen ? parent.top : undefined
362  bottom: fullscreen ? parent.bottom : undefined
363  }
364 
365  Repeater {
366  model: unityMenuModel
367 
368  NotificationMenuItemFactory {
369  id: menuItemFactory
370 
371  anchors {
372  left: dialogColumn.left
373  right: dialogColumn.right
374  }
375 
376  menuModel: unityMenuModel
377  menuData: model
378  menuIndex: index
379  maxHeight: notification.maxHeight
380 
381  onLoaded: {
382  notification.fullscreen = Qt.binding(function() { return fullscreen; });
383  }
384  onAccepted: {
385  notification.notification.invokeAction(actionRepeater.itemAt(0).actionId)
386  }
387  }
388  }
389  }
390 
391  Column {
392  id: oneOverTwoCase
393 
394  anchors {
395  left: parent.left
396  right: parent.right
397  margins: contentSpacing
398  }
399 
400  spacing: contentSpacing
401 
402  visible: notification.type == Notification.SnapDecision && oneOverTwoRepeaterTop.count == 3
403 
404  Repeater {
405  id: oneOverTwoRepeaterTop
406 
407  model: notification.actions
408  delegate: Loader {
409  id: oneOverTwoLoaderTop
410 
411  property string actionId: id
412  property string actionLabel: label
413 
414  Component {
415  id: oneOverTwoButtonTop
416 
417  Button {
418  objectName: "notify_oot_button" + index
419  width: oneOverTwoCase.width
420  text: oneOverTwoLoaderTop.actionLabel
421  color: notification.hints["x-canonical-private-affirmative-tint"] == "true" ? green : sdDarkGrey
422  onClicked: notification.notification.invokeAction(oneOverTwoLoaderTop.actionId)
423  }
424  }
425  sourceComponent: index == 0 ? oneOverTwoButtonTop : undefined
426  }
427  }
428 
429  Row {
430  spacing: contentSpacing
431 
432  Repeater {
433  id: oneOverTwoRepeaterBottom
434 
435  model: notification.actions
436  delegate: Loader {
437  id: oneOverTwoLoaderBottom
438 
439  property string actionId: id
440  property string actionLabel: label
441 
442  Component {
443  id: oneOverTwoButtonBottom
444 
445  Button {
446  objectName: "notify_oot_button" + index
447  width: oneOverTwoCase.width / 2 - spacing * 2
448  text: oneOverTwoLoaderBottom.actionLabel
449  color: index == 1 && notification.hints["x-canonical-private-rejection-tint"] == "true" ? red : sdDarkGrey
450  onClicked: notification.notification.invokeAction(oneOverTwoLoaderBottom.actionId)
451  }
452  }
453  sourceComponent: (index == 1 || index == 2) ? oneOverTwoButtonBottom : undefined
454  }
455  }
456  }
457  }
458 
459  Row {
460  id: buttonRow
461 
462  objectName: "buttonRow"
463  anchors {
464  left: parent.left
465  right: parent.right
466  margins: contentSpacing
467  }
468  visible: notification.type == Notification.SnapDecision && actionRepeater.count > 0 && !oneOverTwoCase.visible
469  spacing: units.gu(2)
470  layoutDirection: Qt.RightToLeft
471 
472  Repeater {
473  id: actionRepeater
474 
475  model: notification.actions
476  delegate: Loader {
477  id: loader
478 
479  property string actionId: id
480  property string actionLabel: label
481 
482  Component {
483  id: actionButton
484 
485  Button {
486  objectName: "notify_button" + index
487  width: buttonRow.width / 2 - spacing*2
488  text: loader.actionLabel
489  color: {
490  var result = sdDarkGrey;
491  if (index == 0 && notification.hints["x-canonical-private-affirmative-tint"] == "true") {
492  result = green;
493  }
494  if (index == 1 && notification.hints["x-canonical-private-rejection-tint"] == "true") {
495  result = red;
496  }
497  return result;
498  }
499  onClicked: notification.notification.invokeAction(loader.actionId)
500  }
501  }
502  sourceComponent: (index == 0 || index == 1) ? actionButton : undefined
503  }
504  }
505  }
506 
507  ComboButton {
508  id: comboButton
509 
510  objectName: "notify_button2"
511  width: parent.width
512  anchors {
513  left: parent.left
514  right: parent.right
515  margins: contentSpacing
516  }
517 
518  visible: notification.type == Notification.SnapDecision && actionRepeater.count > 3 && !oneOverTwoCase.visible
519  color: sdDarkGrey
520  onClicked: notification.notification.invokeAction(comboRepeater.itemAt(2).actionId)
521  expanded: false
522  expandedHeight: (comboRepeater.count - 2) * units.gu(4) + units.gu(.5)
523  comboList: Flickables.Flickable {
524  // this has to be wrapped inside a flickable
525  // to work around a feature/bug? of the
526  // ComboButton SDK-element, making a regular
527  // unwrapped Column item flickable
528  // see LP: #1332590
529  interactive: false
530  Column {
531  Repeater {
532  id: comboRepeater
533 
534  onVisibleChanged: {
535  comboButton.text = comboRepeater.count >= 3 ? comboRepeater.itemAt(2).actionLabel : ""
536  }
537 
538  model: notification.actions
539  delegate: Loader {
540  id: comboLoader
541 
542  asynchronous: true
543  visible: status == Loader.Ready
544  property string actionId: id
545  property string actionLabel: label
546  readonly property var splitLabel: actionLabel.match(/(^([-a-z0-9]+):)?(.*)$/)
547  Component {
548  id: comboEntry
549 
550  MouseArea {
551  id: comboInputArea
552 
553  objectName: "notify_button" + index
554  width: comboButton.width
555  height: comboIcon.height + units.gu(2)
556 
557  onClicked: {
558  notification.notification.invokeAction(actionId)
559  }
560 
561  ListItem.ThinDivider {
562  visible: index > 3
563  }
564 
565  Icon {
566  id: comboIcon
567 
568  anchors {
569  left: parent.left
570  leftMargin: units.gu(.5)
571  verticalCenter: parent.verticalCenter
572  }
573  width: units.gu(2)
574  height: units.gu(2)
575  color: sdFontColor
576  name: splitLabel[2]
577  }
578 
579  Label {
580  id: comboLabel
581 
582  anchors {
583  left: comboIcon.right
584  leftMargin: units.gu(1)
585  verticalCenter: comboIcon.verticalCenter
586  }
587  fontSize: "small"
588  color: sdFontColor
589  text: splitLabel[3]
590  }
591  }
592  }
593  sourceComponent: (index > 2) ? comboEntry : undefined
594  }
595  }
596  }
597  }
598  }
599  }
600  }
601 }