Unity 8
MenuItemFactory.qml
1 /*
2  * Copyright 2013,2015 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 QtQuick.Window 2.2
19 import Ubuntu.Settings.Menus 0.1 as Menus
20 import Ubuntu.Settings.Components 0.1
21 import QMenuModel 0.1
22 import Utils 0.1 as Utils
23 import Ubuntu.Components.ListItems 1.3 as ListItems
24 import Ubuntu.Components 1.3
25 import Unity.Session 0.1
26 import Unity.Platform 1.0
27 
28 Item {
29  id: menuFactory
30 
31  property var rootModel: null
32  property var menuModel: null
33 
34  property var _map: {
35  "default": {
36  "unity.widgets.systemsettings.tablet.volumecontrol" : sliderMenu,
37  "unity.widgets.systemsettings.tablet.switch" : switchMenu,
38 
39  "com.canonical.indicator.button" : buttonMenu,
40  "com.canonical.indicator.div" : separatorMenu,
41  "com.canonical.indicator.section" : sectionMenu,
42  "com.canonical.indicator.progress" : progressMenu,
43  "com.canonical.indicator.slider" : sliderMenu,
44  "com.canonical.indicator.switch" : switchMenu,
45  "com.canonical.indicator.alarm" : alarmMenu,
46  "com.canonical.indicator.appointment" : appointmentMenu,
47  "com.canonical.indicator.transfer" : transferMenu,
48  "com.canonical.indicator.button-section" : buttonSectionMenu,
49  "com.canonical.indicator.link" : linkMenu,
50 
51  "com.canonical.indicator.messages.messageitem" : messageItem,
52  "com.canonical.indicator.messages.sourceitem" : groupedMessage,
53 
54  "com.canonical.unity.slider" : sliderMenu,
55  "com.canonical.unity.switch" : switchMenu,
56 
57  "com.canonical.unity.media-player" : mediaPayerMenu,
58  "com.canonical.unity.playback-item" : playbackItemMenu,
59 
60  "unity.widgets.systemsettings.tablet.wifisection" : wifiSection,
61  "unity.widgets.systemsettings.tablet.accesspoint" : accessPoint,
62  "com.canonical.indicator.network.modeminfoitem" : modeminfoitem,
63 
64  "com.canonical.indicator.calendar": calendarMenu,
65  "com.canonical.indicator.location": timezoneMenu,
66  },
67  "indicator-session": {
68  "indicator.user-menu-item": Platform.isPC ? userMenuItem : null,
69  "indicator.guest-menu-item": Platform.isPC ? userMenuItem : null,
70  "com.canonical.indicator.switch": Math.min(Screen.width, Screen.height) > units.gu(60) ? switchMenu : null // Desktop mode switch
71  },
72  "indicator-messages" : {
73  "com.canonical.indicator.button" : messagesButtonMenu
74  }
75  }
76 
77  function getExtendedProperty(object, propertyName, defaultValue) {
78  if (object && object.hasOwnProperty(propertyName)) {
79  return object[propertyName];
80  }
81  return defaultValue;
82  }
83 
84  Component {
85  id: separatorMenu;
86 
87  Menus.SeparatorMenu {
88  objectName: "separatorMenu"
89  }
90  }
91 
92  Component {
93  id: sliderMenu;
94 
95  Menus.SliderMenu {
96  id: sliderItem
97  objectName: "sliderMenu"
98  property QtObject menuData: null
99  property var menuModel: menuFactory.menuModel
100  property int menuIndex: -1
101  property var extendedData: menuData && menuData.ext || undefined
102  property var serverValue: getExtendedProperty(menuData, "actionState", undefined)
103 
104  text: menuData && menuData.label || ""
105  iconSource: menuData && menuData.icon || ""
106  minIcon: getExtendedProperty(extendedData, "minIcon", "")
107  maxIcon: getExtendedProperty(extendedData, "maxIcon", "")
108 
109  minimumValue: getExtendedProperty(extendedData, "minValue", 0.0)
110  maximumValue: {
111  var maximum = getExtendedProperty(extendedData, "maxValue", 1.0);
112  if (maximum <= minimumValue) {
113  return minimumValue + 1;
114  }
115  return maximum;
116  }
117  enabled: menuData && menuData.sensitive || false
118  highlightWhenPressed: false
119 
120  onMenuModelChanged: {
121  loadAttributes();
122  }
123  onMenuIndexChanged: {
124  loadAttributes();
125  }
126 
127  function loadAttributes() {
128  if (!menuModel || menuIndex == -1) return;
129  menuModel.loadExtendedAttributes(menuIndex, {'min-value': 'double',
130  'max-value': 'double',
131  'min-icon': 'icon',
132  'max-icon': 'icon',
133  'x-canonical-sync-action': 'string'});
134  }
135 
136  ServerPropertySynchroniser {
137  id: sliderPropertySync
138  objectName: "sync"
139  syncTimeout: Utils.Constants.indicatorValueTimeout
140  bufferedSyncTimeout: true
141  maximumWaitBufferInterval: 16
142 
143  serverTarget: sliderItem
144  serverProperty: "serverValue"
145  userTarget: sliderItem
146  userProperty: "value"
147 
148  onSyncTriggered: menuModel.changeState(menuIndex, value)
149  }
150 
151  UnityMenuAction {
152  model: menuModel
153  index: menuIndex
154  name: getExtendedProperty(extendedData, "xCanonicalSyncAction", "")
155  onStateChanged: {
156  sliderPropertySync.reset();
157  sliderPropertySync.updateUserValue();
158  }
159  }
160  }
161  }
162 
163  Component {
164  id: buttonMenu;
165 
166  Menus.ButtonMenu {
167  objectName: "buttonMenu"
168  property QtObject menuData: null
169  property var menuModel: menuFactory.menuModel
170  property int menuIndex: -1
171 
172  buttonText: menuData && menuData.label || ""
173  enabled: menuData && menuData.sensitive || false
174  highlightWhenPressed: false
175 
176  onTriggered: {
177  menuModel.activate(menuIndex);
178  }
179  }
180  }
181 
182  Component {
183  id: messagesButtonMenu;
184 
185  Item {
186  objectName: "messagesButtonMenu"
187  property QtObject menuData: null
188  property var menuModel: menuFactory.menuModel
189  property int menuIndex: -1
190 
191  implicitHeight: units.gu(5)
192  enabled: menuData && menuData.sensitive || false
193 
194  Label {
195  id: buttonMenuLabel
196  text: menuData && menuData.label || ""
197  anchors.centerIn: parent
198  font.bold: true
199  }
200 
201  MouseArea {
202  anchors {
203  fill: buttonMenuLabel
204  margins: units.gu(-1)
205  }
206  onClicked: menuModel.activate(menuIndex);
207  }
208  }
209  }
210 
211  Component {
212  id: sectionMenu;
213 
214  Menus.SectionMenu {
215  objectName: "sectionMenu"
216  property QtObject menuData: null
217  property var menuIndex: undefined
218 
219  text: menuData && menuData.label || ""
220  busy: false
221  }
222  }
223 
224  Component {
225  id: progressMenu;
226 
227  Menus.ProgressValueMenu {
228  objectName: "progressMenu"
229  property QtObject menuData: null
230  property int menuIndex: -1
231 
232  text: menuData && menuData.label || ""
233  iconSource: menuData && menuData.icon || ""
234  value : menuData && menuData.actionState || 0.0
235  enabled: menuData && menuData.sensitive || false
236  // FIXME: Because of this bug, setting it to the theme foreground color (white)
237  // currently doesn't work. Let's hack it to be "close enough"
238  // https://bugs.launchpad.net/ubuntu/+source/ubuntu-ui-toolkit/+bug/1555784
239  foregroundColor: "#fffffe"
240  highlightWhenPressed: false
241  }
242  }
243 
244  Component {
245  id: standardMenu;
246 
247  Menus.StandardMenu {
248  objectName: "standardMenu"
249  property QtObject menuData: null
250  property int menuIndex: -1
251 
252  text: menuData && menuData.label || ""
253  iconSource: menuData && menuData.icon || ""
254  enabled: menuData && menuData.sensitive || false
255  highlightWhenPressed: false
256 
257  onTriggered: {
258  menuModel.activate(menuIndex);
259  }
260 
261  // FIXME : At the moment, the indicators aren't using
262  // com.canonical.indicators.link for settings menu. Need to fudge it.
263  property bool settingsMenu: menuData && menuData.action.indexOf("settings") > -1 || false
264  backColor: settingsMenu ? Qt.rgba(1,1,1,0.07) : "transparent"
265  component: settingsMenu ? buttonForSettings : undefined
266  Component {
267  id: buttonForSettings
268  Icon {
269  name: "settings"
270  height: units.gu(3)
271  width: height
272  color: theme.palette.normal.backgroundText
273  }
274  }
275  }
276  }
277 
278  Component {
279  id: linkMenu;
280 
281  Menus.StandardMenu {
282  objectName: "linkMenu"
283  property QtObject menuData: null
284  property int menuIndex: -1
285 
286  text: menuData && menuData.label || ""
287  iconSource: menuData && menuData.icon || ""
288  enabled: menuData && menuData.sensitive || false
289  highlightWhenPressed: false
290 
291  onTriggered: {
292  menuModel.activate(menuIndex);
293  }
294 
295  backColor: Qt.rgba(1,1,1,0.07)
296 
297  component: menuData.icon ? icon : undefined
298  Component {
299  id: icon
300  Icon {
301  source: menuData.icon
302  height: units.gu(3)
303  width: height
304  color: theme.palette.normal.backgroundText
305  }
306  }
307  }
308  }
309 
310  Component {
311  id: checkableMenu;
312 
313  Menus.CheckableMenu {
314  id: checkItem
315  objectName: "checkableMenu"
316  property QtObject menuData: null
317  property int menuIndex: -1
318  property bool serverChecked: menuData && menuData.isToggled || false
319 
320  text: menuData && menuData.label || ""
321  enabled: menuData && menuData.sensitive || false
322  checked: serverChecked
323  highlightWhenPressed: false
324 
325  ServerPropertySynchroniser {
326  objectName: "sync"
327  syncTimeout: Utils.Constants.indicatorValueTimeout
328 
329  serverTarget: checkItem
330  serverProperty: "serverChecked"
331  userTarget: checkItem
332  userProperty: "checked"
333 
334  onSyncTriggered: menuModel.activate(checkItem.menuIndex)
335  }
336  }
337  }
338 
339  Component {
340  id: switchMenu;
341 
342  Menus.SwitchMenu {
343  id: switchItem
344  objectName: "switchMenu"
345  property QtObject menuData: null
346  property int menuIndex: -1
347  property bool serverChecked: menuData && menuData.isToggled || false
348 
349  text: menuData && menuData.label || ""
350  iconSource: menuData && menuData.icon || ""
351  enabled: menuData && menuData.sensitive || false
352  checked: serverChecked
353  highlightWhenPressed: false
354 
355  ServerPropertySynchroniser {
356  objectName: "sync"
357  syncTimeout: Utils.Constants.indicatorValueTimeout
358 
359  serverTarget: switchItem
360  serverProperty: "serverChecked"
361  userTarget: switchItem
362  userProperty: "checked"
363 
364  onSyncTriggered: menuModel.activate(switchItem.menuIndex);
365  }
366  }
367  }
368 
369  Component {
370  id: alarmMenu;
371 
372  Menus.EventMenu {
373  id: alarmItem
374  objectName: "alarmMenu"
375  property QtObject menuData: null
376  property var menuModel: menuFactory.menuModel
377  property int menuIndex: -1
378  property var extendedData: menuData && menuData.ext || undefined
379 
380  property date serverTime: new Date(getExtendedProperty(extendedData, "xCanonicalTime", 0) * 1000)
381  LiveTimer {
382  frequency: LiveTimer.Relative
383  relativeTime: alarmItem.serverTime
384  onTrigger: alarmItem.serverTime = new Date(getExtendedProperty(extendedData, "xCanonicalTime", 0) * 1000)
385  }
386 
387  text: menuData && menuData.label || ""
388  iconSource: menuData && menuData.icon || "image://theme/alarm-clock"
389  time: i18n.relativeDateTime(serverTime)
390  enabled: menuData && menuData.sensitive || false
391  highlightWhenPressed: false
392 
393  onMenuModelChanged: {
394  loadAttributes();
395  }
396  onMenuIndexChanged: {
397  loadAttributes();
398  }
399  onTriggered: {
400  menuModel.activate(menuIndex);
401  }
402 
403  function loadAttributes() {
404  if (!menuModel || menuIndex == -1) return;
405  menuModel.loadExtendedAttributes(menuIndex, {'x-canonical-time': 'int64'});
406  }
407  }
408  }
409 
410  Component {
411  id: appointmentMenu;
412 
413  Menus.EventMenu {
414  id: appointmentItem
415  objectName: "appointmentMenu"
416  property QtObject menuData: null
417  property var menuModel: menuFactory.menuModel
418  property int menuIndex: -1
419  property var extendedData: menuData && menuData.ext || undefined
420 
421  property date serverTime: new Date(getExtendedProperty(extendedData, "xCanonicalTime", 0) * 1000)
422  LiveTimer {
423  frequency: LiveTimer.Relative
424  relativeTime: appointmentItem.serverTime
425  onTrigger: appointmentItem.serverTime = new Date(getExtendedProperty(extendedData, "xCanonicalTime", 0) * 1000)
426  }
427 
428  text: menuData && menuData.label || ""
429  iconSource: menuData && menuData.icon || "image://theme/calendar"
430  time: i18n.relativeDateTime(serverTime)
431  eventColor: getExtendedProperty(extendedData, "xCanonicalColor", Qt.rgba(0.0, 0.0, 0.0, 0.0))
432  enabled: menuData && menuData.sensitive || false
433  highlightWhenPressed: false
434 
435  onMenuModelChanged: {
436  loadAttributes();
437  }
438  onMenuIndexChanged: {
439  loadAttributes();
440  }
441  onTriggered: {
442  menuModel.activate(menuIndex);
443  }
444 
445  function loadAttributes() {
446  if (!menuModel || menuIndex == -1) return;
447  menuModel.loadExtendedAttributes(menuIndex, {'x-canonical-color': 'string',
448  'x-canonical-time': 'int64'});
449  }
450  }
451  }
452 
453  Component {
454  id: userMenuItem
455 
456  Menus.UserSessionMenu {
457  objectName: "userSessionMenu"
458  highlightWhenPressed: false
459 
460  property QtObject menuData: null
461  property var menuModel: menuFactory.menuModel
462  property int menuIndex: -1
463 
464  name: menuData && menuData.label || "" // label is the user's real name
465  iconSource: menuData && menuData.icon || ""
466 
467  // would be better to compare with the logname but sadly the indicator doesn't expose that
468  active: DBusUnitySessionService.RealName() !== "" ? DBusUnitySessionService.RealName() == name
469  : DBusUnitySessionService.UserName() == name
470 
471  onTriggered: {
472  menuModel.activate(menuIndex);
473  }
474  }
475  }
476 
477  Component {
478  id: calendarMenu
479 
480  Menus.CalendarMenu {
481  objectName: "calendarMenu"
482  highlightWhenPressed: false
483  focus: true
484  }
485  }
486 
487  Component {
488  id: timezoneMenu
489 
490  Menus.TimeZoneMenu {
491  id: tzMenuItem
492  objectName: "timezoneMenu"
493 
494  property QtObject menuData: null
495  property var menuModel: menuFactory.menuModel
496  property int menuIndex: -1
497  property var extendedData: menuData && menuData.ext || undefined
498  readonly property string tz: getExtendedProperty(extendedData, "xCanonicalTimezone", "UTC")
499  property var updateTimer: Timer {
500  repeat: true
501  running: tzMenuItem.visible // only run when we're open
502  onTriggered: tzMenuItem.time = Utils.TimezoneFormatter.currentTimeInTimezone(tzMenuItem.tz)
503  }
504 
505  city: menuData && menuData.label || ""
506  time: Utils.TimezoneFormatter.currentTimeInTimezone(tz)
507  enabled: menuData && menuData.sensitive || false
508 
509  onMenuModelChanged: {
510  loadAttributes();
511  }
512  onMenuIndexChanged: {
513  loadAttributes();
514  }
515  onTriggered: {
516  tzActionGroup.setLocation.activate(tz);
517  }
518 
519  QDBusActionGroup {
520  id: tzActionGroup
521  busType: DBus.SessionBus
522  busName: "com.canonical.indicator.datetime"
523  objectPath: "/com/canonical/indicator/datetime"
524 
525  property variant setLocation: action("set-location")
526 
527  Component.onCompleted: tzActionGroup.start()
528  }
529 
530  function loadAttributes() {
531  if (!menuModel || menuIndex == -1) return;
532  menuModel.loadExtendedAttributes(menuIndex, {'x-canonical-timezone': 'string'});
533  }
534  }
535  }
536 
537  Component {
538  id: wifiSection;
539 
540  Menus.SectionMenu {
541  objectName: "wifiSection"
542  property QtObject menuData: null
543  property var menuModel: menuFactory.menuModel
544  property int menuIndex: -1
545  property var extendedData: menuData && menuData.ext || undefined
546 
547  text: menuData && menuData.label || ""
548  busy: getExtendedProperty(extendedData, "xCanonicalBusyAction", false)
549 
550  onMenuModelChanged: {
551  loadAttributes();
552  }
553  onMenuIndexChanged: {
554  loadAttributes();
555  }
556 
557  function loadAttributes() {
558  if (!menuModel || menuIndex == -1) return;
559  menuModel.loadExtendedAttributes(menuIndex, {'x-canonical-busy-action': 'bool'})
560  }
561  }
562  }
563 
564  Component {
565  id: accessPoint;
566 
567  Menus.AccessPointMenu {
568  id: apItem
569  objectName: "accessPoint"
570  property QtObject menuData: null
571  property var menuModel: menuFactory.menuModel
572  property int menuIndex: -1
573  property var extendedData: menuData && menuData.ext || undefined
574  property bool serverChecked: menuData && menuData.isToggled || false
575 
576  property var strengthAction: UnityMenuAction {
577  model: menuModel
578  index: menuIndex
579  name: getExtendedProperty(extendedData, "xCanonicalWifiApStrengthAction", "")
580  }
581 
582  text: menuData && menuData.label || ""
583  enabled: menuData && menuData.sensitive || false
584  active: serverChecked
585  secure: getExtendedProperty(extendedData, "xCanonicalWifiApIsSecure", false)
586  adHoc: getExtendedProperty(extendedData, "xCanonicalWifiApIsAdhoc", false)
587  signalStrength: {
588  if (strengthAction.valid) {
589  var state = strengthAction.state; // handle both int and uchar
590  // FIXME remove the special casing when we switch to indicator-network completely
591  if (typeof state == "string") {
592  return state.charCodeAt();
593  }
594  return state;
595  }
596  return 0;
597  }
598  highlightWhenPressed: false
599 
600  onMenuModelChanged: {
601  loadAttributes();
602  }
603  onMenuIndexChanged: {
604  loadAttributes();
605  }
606 
607  function loadAttributes() {
608  if (!menuModel || menuIndex == -1) return;
609  menuModel.loadExtendedAttributes(menuIndex, {'x-canonical-wifi-ap-is-adhoc': 'bool',
610  'x-canonical-wifi-ap-is-secure': 'bool',
611  'x-canonical-wifi-ap-strength-action': 'string'});
612  }
613 
614  ServerPropertySynchroniser {
615  objectName: "sync"
616  syncTimeout: Utils.Constants.indicatorValueTimeout
617 
618  serverTarget: apItem
619  serverProperty: "serverChecked"
620  userTarget: apItem
621  userProperty: "active"
622  userTrigger: "onTriggered"
623 
624  onSyncTriggered: menuModel.activate(apItem.menuIndex)
625  }
626  }
627  }
628 
629  Component {
630  id: modeminfoitem;
631  Menus.ModemInfoItem {
632  objectName: "modemInfoItem"
633  property QtObject menuData: null
634  property var menuModel: menuFactory.menuModel
635  property int menuIndex: -1
636  property var extendedData: menuData && menuData.ext || undefined
637  highlightWhenPressed: false
638 
639  property var statusLabelAction: UnityMenuAction {
640  model: menuModel
641  index: menuIndex
642  name: getExtendedProperty(extendedData, "xCanonicalModemStatusLabelAction", "")
643  }
644  statusText: statusLabelAction.valid ? statusLabelAction.state : ""
645 
646  property var statusIconAction: UnityMenuAction {
647  model: menuModel
648  index: menuIndex
649  name: getExtendedProperty(extendedData, "xCanonicalModemStatusIconAction", "")
650  }
651  statusIcon: statusIconAction.valid ? statusIconAction.state : ""
652 
653  property var connectivityIconAction: UnityMenuAction {
654  model: menuModel
655  index: menuIndex
656  name: getExtendedProperty(extendedData, "xCanonicalModemConnectivityIconAction", "")
657  }
658  connectivityIcon: connectivityIconAction.valid ? connectivityIconAction.state : ""
659 
660  property var simIdentifierLabelAction: UnityMenuAction {
661  model: menuModel
662  index: menuIndex
663  name: getExtendedProperty(extendedData, "xCanonicalModemSimIdentifierLabelAction", "")
664  }
665  simIdentifierText: simIdentifierLabelAction.valid ? simIdentifierLabelAction.state : ""
666 
667  property var roamingAction: UnityMenuAction {
668  model: menuModel
669  index: menuIndex
670  name: getExtendedProperty(extendedData, "xCanonicalModemRoamingAction", "")
671  }
672  roaming: roamingAction.valid ? roamingAction.state : false
673 
674  property var unlockAction: UnityMenuAction {
675  model: menuModel
676  index: menuIndex
677  name: getExtendedProperty(extendedData, "xCanonicalModemLockedAction", "")
678  }
679  onUnlock: {
680  unlockAction.activate();
681  }
682  locked: unlockAction.valid ? unlockAction.state : false
683 
684  onMenuModelChanged: {
685  loadAttributes();
686  }
687  onMenuIndexChanged: {
688  loadAttributes();
689  }
690 
691  function loadAttributes() {
692  if (!menuModel || menuIndex == -1) return;
693  menuModel.loadExtendedAttributes(menuIndex, {'x-canonical-modem-status-label-action': 'string',
694  'x-canonical-modem-status-icon-action': 'string',
695  'x-canonical-modem-connectivity-icon-action': 'string',
696  'x-canonical-modem-sim-identifier-label-action': 'string',
697  'x-canonical-modem-roaming-action': 'string',
698  'x-canonical-modem-locked-action': 'string'});
699  }
700  }
701  }
702 
703  Component {
704  id: messageItem
705 
706  MessageMenuItemFactory {
707  objectName: "messageItem"
708  menuModel: menuFactory.menuModel
709  }
710  }
711 
712  Component {
713  id: groupedMessage
714 
715  Menus.GroupedMessageMenu {
716  objectName: "groupedMessage"
717  property QtObject menuData: null
718  property var menuModel: menuFactory.menuModel
719  property int menuIndex: -1
720  property var extendedData: menuData && menuData.ext || undefined
721 
722  text: menuData && menuData.label || ""
723  iconSource: getExtendedProperty(extendedData, "icon", "image://theme/message")
724  count: menuData && menuData.actionState.length > 0 ? menuData.actionState[0] : "0"
725  enabled: menuData && menuData.sensitive || false
726  highlightWhenPressed: false
727  removable: true
728 
729  onMenuModelChanged: {
730  loadAttributes();
731  }
732  onMenuIndexChanged: {
733  loadAttributes();
734  }
735  onClicked: {
736  menuModel.activate(menuIndex, true);
737  }
738  onDismissed: {
739  menuModel.activate(menuIndex, false);
740  }
741 
742  function loadAttributes() {
743  if (!menuModel || menuIndex == -1) return;
744  menuModel.loadExtendedAttributes(modelIndex, {'icon': 'icon'});
745  }
746  }
747  }
748 
749  Component {
750  id: mediaPayerMenu;
751 
752  Menus.MediaPlayerMenu {
753  objectName: "mediaPayerMenu"
754  property QtObject menuData: null
755  property var menuModel: menuFactory.menuModel
756  property int menuIndex: -1
757  property var actionState: menuData && menuData.actionState || undefined
758  property bool running: getExtendedProperty(actionState, "running", false)
759 
760  playerIcon: menuData && menuData.icon || "image://theme/stock_music"
761  playerName: menuData && menuData.label || i18n.tr("Nothing is playing")
762 
763  albumArt: getExtendedProperty(actionState, "art-url", "image://theme/stock_music")
764  song: getExtendedProperty(actionState, "title", "")
765  artist: getExtendedProperty(actionState, "artist", "")
766  album: getExtendedProperty(actionState, "album", "")
767  showTrack: running && (state == "Playing" || state == "Paused")
768  state: getExtendedProperty(actionState, "state", "")
769  enabled: menuData && menuData.sensitive || false
770  highlightWhenPressed: false
771  showDivider: false
772 
773  onTriggered: {
774  model.activate(modelIndex);
775  }
776  }
777  }
778 
779  Component {
780  id: playbackItemMenu;
781 
782  Menus.PlaybackItemMenu {
783  objectName: "playbackItemMenu"
784  property QtObject menuData: null
785  property var menuModel: menuFactory.menuModel
786  property int menuIndex: -1
787  property var extendedData: menuData && menuData.ext || undefined
788 
789  property var playAction: UnityMenuAction {
790  model: menuModel
791  index: menuIndex
792  name: getExtendedProperty(extendedData, "xCanonicalPlayAction", "")
793  }
794  property var nextAction: UnityMenuAction {
795  model: menuModel
796  index: menuIndex
797  name: getExtendedProperty(extendedData, "xCanonicalNextAction", "")
798  }
799  property var previousAction: UnityMenuAction {
800  model: menuModel
801  index: menuIndex
802  name: getExtendedProperty(extendedData, "xCanonicalPreviousAction", "")
803  }
804 
805  playing: playAction.state === "Playing"
806  canPlay: playAction.valid
807  canGoNext: nextAction.valid
808  canGoPrevious: previousAction.valid
809  enabled: menuData && menuData.sensitive || false
810  highlightWhenPressed: false
811 
812  onPlay: {
813  playAction.activate();
814  }
815  onNext: {
816  nextAction.activate();
817  }
818  onPrevious: {
819  previousAction.activate();
820  }
821  onMenuModelChanged: {
822  loadAttributes();
823  }
824  onMenuIndexChanged: {
825  loadAttributes();
826  }
827 
828  function loadAttributes() {
829  if (!menuModel || menuIndex == -1) return;
830  menuModel.loadExtendedAttributes(modelIndex, {'x-canonical-play-action': 'string',
831  'x-canonical-next-action': 'string',
832  'x-canonical-previous-action': 'string'});
833  }
834  }
835  }
836 
837  Component {
838  id: transferMenu
839 
840  Menus.TransferMenu {
841  objectName: "transferMenu"
842  id: transfer
843  property QtObject menuData: null
844  property var menuModel: menuFactory.menuModel
845  property int menuIndex: -1
846  property var extendedData: menuData && menuData.ext || undefined
847  property var uid: getExtendedProperty(extendedData, "xCanonicalUid", undefined)
848 
849  text: menuData && menuData.label || ""
850  iconSource: menuData && menuData.icon || "image://theme/transfer-none"
851  maximum: 1.0
852  enabled: menuData && menuData.sensitive || false
853  highlightWhenPressed: false
854  removable: true
855  confirmRemoval: true
856 
857  QDBusActionGroup {
858  id: actionGroup
859  busType: 1
860  busName: rootModel.busName
861  objectPath: rootModel.actions["indicator"]
862 
863  property var activateAction: action("activate-transfer")
864  property var cancelAction: action("cancel-transfer")
865  property var transferStateAction: uid !== undefined ? action("transfer-state."+uid) : null
866 
867  Component.onCompleted: actionGroup.start()
868  }
869 
870  property var transferState: {
871  if (actionGroup.transferStateAction === null) return undefined;
872  return actionGroup.transferStateAction.valid ? actionGroup.transferStateAction.state : undefined
873  }
874 
875  property var runningState : transferState !== undefined ? transferState["state"] : undefined
876  property var secondsLeft : transferState !== undefined ? transferState["seconds-left"] : undefined
877 
878  active: runningState !== undefined && runningState !== Menus.TransferState.Finished
879  progress: transferState !== undefined ? transferState["percent"] : 0.0
880 
881  // TODO - Should be in the SDK
882  property var timeRemaining: {
883  if (secondsLeft === undefined) return undefined;
884 
885  var remaining = "";
886  var hours = Math.floor(secondsLeft / (60 * 60));
887  var minutes = Math.floor(secondsLeft / 60) % 60;
888  var seconds = secondsLeft % 60;
889  if (hours > 0) {
890  remaining += i18n.tr("%1 hour", "%1 hours", hours).arg(hours)
891  }
892  if (minutes > 0) {
893  if (remaining != "") remaining += ", ";
894  remaining += i18n.tr("%1 minute", "%1 minutes", minutes).arg(minutes)
895  }
896  // don't include seconds if hours > 0
897  if (hours == 0 && minutes < 5 && seconds > 0) {
898  if (remaining != "") remaining += ", ";
899  remaining += i18n.tr("%1 second", "%1 seconds", seconds).arg(seconds)
900  }
901  if (remaining == "")
902  remaining = i18n.tr("0 seconds");
903  // Translators: String like "1 hour, 2 minutes, 3 seconds remaining"
904  return i18n.tr("%1 remaining").arg(remaining);
905  }
906 
907  stateText: {
908  switch (runningState) {
909  case Menus.TransferState.Queued:
910  return i18n.tr("In queue…");
911  case Menus.TransferState.Hashing:
912  case Menus.TransferState.Processing:
913  case Menus.TransferState.Running:
914  return timeRemaining === undefined ? i18n.tr("Downloading") : timeRemaining;
915  case Menus.TransferState.Paused:
916  return i18n.tr("Paused, tap to resume");
917  case Menus.TransferState.Canceled:
918  return i18n.tr("Canceled");
919  case Menus.TransferState.Finished:
920  return i18n.tr("Finished");
921  case Menus.TransferState.Error:
922  return i18n.tr("Failed, tap to retry");
923  }
924  return "";
925  }
926 
927  onMenuModelChanged: {
928  loadAttributes();
929  }
930  onMenuIndexChanged: {
931  loadAttributes();
932  }
933  onTriggered: {
934  actionGroup.activateAction.activate(uid);
935  }
936  onItemRemoved: {
937  actionGroup.cancelAction.activate(uid);
938  }
939 
940  function loadAttributes() {
941  if (!menuModel || menuIndex == -1) return;
942  menuModel.loadExtendedAttributes(menuIndex, {'x-canonical-uid': 'string'});
943  }
944  }
945  }
946 
947  Component {
948  id: buttonSectionMenu;
949 
950  Menus.StandardMenu {
951  objectName: "buttonSectionMenu"
952  property QtObject menuData: null
953  property var menuModel: menuFactory.menuModel
954  property int menuIndex: -1
955  property var extendedData: menuData && menuData.ext || undefined
956 
957  iconSource: menuData && menuData.icon || ""
958  enabled: menuData && menuData.sensitive || false
959  highlightWhenPressed: false
960  text: menuData && menuData.label || ""
961  foregroundColor: theme.palette.normal.backgroundText
962 
963  onMenuModelChanged: {
964  loadAttributes();
965  }
966  onMenuIndexChanged: {
967  loadAttributes();
968  }
969  function loadAttributes() {
970  if (!menuModel || menuIndex == -1) return;
971  menuModel.loadExtendedAttributes(menuIndex, {'x-canonical-extra-label': 'string'});
972  }
973 
974  component: Component {
975  Button {
976  objectName: "buttonSectionMenuControl"
977  text: getExtendedProperty(extendedData, "xCanonicalExtraLabel", "")
978 
979  onClicked: {
980  menuModel.activate(menuIndex);
981  }
982  }
983  }
984  }
985  }
986 
987  function load(modelData, context) {
988  // tweak indicator-session items
989  if (context === "indicator-session") {
990  if ((modelData.action === "indicator.logout" || modelData.action === "indicator.suspend" || modelData.action === "indicator.hibernate" ||
991  modelData.action === "indicator.reboot")
992  && !Platform.isPC) {
993  return null; // logout, suspend and hibernate hidden on devices
994  }
995  }
996 
997  if (modelData.type !== undefined && modelData.type !== "") {
998  var component = undefined;
999 
1000  var contextComponents = _map[context];
1001  if (contextComponents !== undefined) {
1002  component = contextComponents[modelData.type];
1003  }
1004 
1005  if (component === undefined) {
1006  component = _map["default"][modelData.type];
1007  }
1008  if (component !== undefined) {
1009  return component;
1010  }
1011  console.debug("Don't know how to make " + modelData.type + " for " + context);
1012  }
1013  if (modelData.isCheck || modelData.isRadio) {
1014  return checkableMenu;
1015  }
1016  if (modelData.isSeparator) {
1017  return separatorMenu;
1018  }
1019  return standardMenu;
1020  }
1021 }