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