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  readonly property date serverTime: new Date(getExtendedProperty(extendedData, "xCanonicalTime", 0) * 1000)
381  LiveTimer {
382  frequency: LiveTimer.Relative
383  relativeTime: alarmItem.serverTime
384  onTrigger: alarmItem.time = i18n.relativeDateTime(alarmItem.serverTime)
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  readonly property date serverTime: new Date(getExtendedProperty(extendedData, "xCanonicalTime", 0) * 1000)
422 
423  LiveTimer {
424  frequency: LiveTimer.Relative
425  relativeTime: appointmentItem.serverTime
426  onTrigger: appointmentItem.time = i18n.relativeDateTime(appointmentItem.serverTime)
427  }
428 
429  text: menuData && menuData.label || ""
430  iconSource: menuData && menuData.icon || "image://theme/calendar"
431  time: i18n.relativeDateTime(serverTime)
432  eventColor: getExtendedProperty(extendedData, "xCanonicalColor", Qt.rgba(0.0, 0.0, 0.0, 0.0))
433  enabled: menuData && menuData.sensitive || false
434  highlightWhenPressed: false
435 
436  onMenuModelChanged: {
437  loadAttributes();
438  }
439  onMenuIndexChanged: {
440  loadAttributes();
441  }
442  onTriggered: {
443  menuModel.activate(menuIndex);
444  }
445 
446  function loadAttributes() {
447  if (!menuModel || menuIndex == -1) return;
448  menuModel.loadExtendedAttributes(menuIndex, {'x-canonical-color': 'string',
449  'x-canonical-time': 'int64'});
450  }
451  }
452  }
453 
454  Component {
455  id: userMenuItem
456 
457  Menus.UserSessionMenu {
458  objectName: "userSessionMenu"
459  highlightWhenPressed: false
460 
461  property QtObject menuData: null
462  property var menuModel: menuFactory.menuModel
463  property int menuIndex: -1
464 
465  name: menuData && menuData.label || "" // label is the user's real name
466  iconSource: menuData && menuData.icon || ""
467 
468  // would be better to compare with the logname but sadly the indicator doesn't expose that
469  active: DBusUnitySessionService.RealName() !== "" ? DBusUnitySessionService.RealName() == name
470  : DBusUnitySessionService.UserName() == name
471 
472  onTriggered: {
473  menuModel.activate(menuIndex);
474  }
475  }
476  }
477 
478  Component {
479  id: calendarMenu
480 
481  Menus.CalendarMenu {
482  objectName: "calendarMenu"
483  highlightWhenPressed: false
484  focus: true
485  }
486  }
487 
488  Component {
489  id: timezoneMenu
490 
491  Menus.TimeZoneMenu {
492  id: tzMenuItem
493  objectName: "timezoneMenu"
494 
495  property QtObject menuData: null
496  property var menuModel: menuFactory.menuModel
497  property int menuIndex: -1
498  property var extendedData: menuData && menuData.ext || undefined
499  readonly property string tz: getExtendedProperty(extendedData, "xCanonicalTimezone", "UTC")
500  property var updateTimer: Timer {
501  repeat: true
502  running: tzMenuItem.visible // only run when we're open
503  onTriggered: tzMenuItem.time = Utils.TimezoneFormatter.currentTimeInTimezone(tzMenuItem.tz)
504  }
505 
506  city: menuData && menuData.label || ""
507  time: Utils.TimezoneFormatter.currentTimeInTimezone(tz)
508  enabled: menuData && menuData.sensitive || false
509 
510  onMenuModelChanged: {
511  loadAttributes();
512  }
513  onMenuIndexChanged: {
514  loadAttributes();
515  }
516  onTriggered: {
517  tzActionGroup.setLocation.activate(tz);
518  }
519 
520  QDBusActionGroup {
521  id: tzActionGroup
522  busType: DBus.SessionBus
523  busName: "com.canonical.indicator.datetime"
524  objectPath: "/com/canonical/indicator/datetime"
525 
526  property variant setLocation: action("set-location")
527 
528  Component.onCompleted: tzActionGroup.start()
529  }
530 
531  function loadAttributes() {
532  if (!menuModel || menuIndex == -1) return;
533  menuModel.loadExtendedAttributes(menuIndex, {'x-canonical-timezone': 'string'});
534  }
535  }
536  }
537 
538  Component {
539  id: wifiSection;
540 
541  Menus.SectionMenu {
542  objectName: "wifiSection"
543  property QtObject menuData: null
544  property var menuModel: menuFactory.menuModel
545  property int menuIndex: -1
546  property var extendedData: menuData && menuData.ext || undefined
547 
548  text: menuData && menuData.label || ""
549  busy: getExtendedProperty(extendedData, "xCanonicalBusyAction", false)
550 
551  onMenuModelChanged: {
552  loadAttributes();
553  }
554  onMenuIndexChanged: {
555  loadAttributes();
556  }
557 
558  function loadAttributes() {
559  if (!menuModel || menuIndex == -1) return;
560  menuModel.loadExtendedAttributes(menuIndex, {'x-canonical-busy-action': 'bool'})
561  }
562  }
563  }
564 
565  Component {
566  id: accessPoint;
567 
568  Menus.AccessPointMenu {
569  id: apItem
570  objectName: "accessPoint"
571  property QtObject menuData: null
572  property var menuModel: menuFactory.menuModel
573  property int menuIndex: -1
574  property var extendedData: menuData && menuData.ext || undefined
575  property bool serverChecked: menuData && menuData.isToggled || false
576 
577  property var strengthAction: UnityMenuAction {
578  model: menuModel
579  index: menuIndex
580  name: getExtendedProperty(extendedData, "xCanonicalWifiApStrengthAction", "")
581  }
582 
583  text: menuData && menuData.label || ""
584  enabled: menuData && menuData.sensitive || false
585  active: serverChecked
586  secure: getExtendedProperty(extendedData, "xCanonicalWifiApIsSecure", false)
587  adHoc: getExtendedProperty(extendedData, "xCanonicalWifiApIsAdhoc", false)
588  signalStrength: {
589  if (strengthAction.valid) {
590  var state = strengthAction.state; // handle both int and uchar
591  // FIXME remove the special casing when we switch to indicator-network completely
592  if (typeof state == "string") {
593  return state.charCodeAt();
594  }
595  return state;
596  }
597  return 0;
598  }
599  highlightWhenPressed: false
600 
601  onMenuModelChanged: {
602  loadAttributes();
603  }
604  onMenuIndexChanged: {
605  loadAttributes();
606  }
607 
608  function loadAttributes() {
609  if (!menuModel || menuIndex == -1) return;
610  menuModel.loadExtendedAttributes(menuIndex, {'x-canonical-wifi-ap-is-adhoc': 'bool',
611  'x-canonical-wifi-ap-is-secure': 'bool',
612  'x-canonical-wifi-ap-strength-action': 'string'});
613  }
614 
615  ServerPropertySynchroniser {
616  objectName: "sync"
617  syncTimeout: Utils.Constants.indicatorValueTimeout
618 
619  serverTarget: apItem
620  serverProperty: "serverChecked"
621  userTarget: apItem
622  userProperty: "active"
623  userTrigger: "onTriggered"
624 
625  onSyncTriggered: menuModel.activate(apItem.menuIndex)
626  }
627  }
628  }
629 
630  Component {
631  id: modeminfoitem;
632  Menus.ModemInfoItem {
633  objectName: "modemInfoItem"
634  property QtObject menuData: null
635  property var menuModel: menuFactory.menuModel
636  property int menuIndex: -1
637  property var extendedData: menuData && menuData.ext || undefined
638  highlightWhenPressed: false
639 
640  property var statusLabelAction: UnityMenuAction {
641  model: menuModel
642  index: menuIndex
643  name: getExtendedProperty(extendedData, "xCanonicalModemStatusLabelAction", "")
644  }
645  statusText: statusLabelAction.valid ? statusLabelAction.state : ""
646 
647  property var statusIconAction: UnityMenuAction {
648  model: menuModel
649  index: menuIndex
650  name: getExtendedProperty(extendedData, "xCanonicalModemStatusIconAction", "")
651  }
652  statusIcon: statusIconAction.valid ? statusIconAction.state : ""
653 
654  property var connectivityIconAction: UnityMenuAction {
655  model: menuModel
656  index: menuIndex
657  name: getExtendedProperty(extendedData, "xCanonicalModemConnectivityIconAction", "")
658  }
659  connectivityIcon: connectivityIconAction.valid ? connectivityIconAction.state : ""
660 
661  property var simIdentifierLabelAction: UnityMenuAction {
662  model: menuModel
663  index: menuIndex
664  name: getExtendedProperty(extendedData, "xCanonicalModemSimIdentifierLabelAction", "")
665  }
666  simIdentifierText: simIdentifierLabelAction.valid ? simIdentifierLabelAction.state : ""
667 
668  property var roamingAction: UnityMenuAction {
669  model: menuModel
670  index: menuIndex
671  name: getExtendedProperty(extendedData, "xCanonicalModemRoamingAction", "")
672  }
673  roaming: roamingAction.valid ? roamingAction.state : false
674 
675  property var unlockAction: UnityMenuAction {
676  model: menuModel
677  index: menuIndex
678  name: getExtendedProperty(extendedData, "xCanonicalModemLockedAction", "")
679  }
680  onUnlock: {
681  unlockAction.activate();
682  }
683  locked: unlockAction.valid ? unlockAction.state : false
684 
685  onMenuModelChanged: {
686  loadAttributes();
687  }
688  onMenuIndexChanged: {
689  loadAttributes();
690  }
691 
692  function loadAttributes() {
693  if (!menuModel || menuIndex == -1) return;
694  menuModel.loadExtendedAttributes(menuIndex, {'x-canonical-modem-status-label-action': 'string',
695  'x-canonical-modem-status-icon-action': 'string',
696  'x-canonical-modem-connectivity-icon-action': 'string',
697  'x-canonical-modem-sim-identifier-label-action': 'string',
698  'x-canonical-modem-roaming-action': 'string',
699  'x-canonical-modem-locked-action': 'string'});
700  }
701  }
702  }
703 
704  Component {
705  id: messageItem
706 
707  MessageMenuItemFactory {
708  objectName: "messageItem"
709  menuModel: menuFactory.menuModel
710  }
711  }
712 
713  Component {
714  id: groupedMessage
715 
716  Menus.GroupedMessageMenu {
717  objectName: "groupedMessage"
718  property QtObject menuData: null
719  property var menuModel: menuFactory.menuModel
720  property int menuIndex: -1
721  property var extendedData: menuData && menuData.ext || undefined
722 
723  text: menuData && menuData.label || ""
724  iconSource: getExtendedProperty(extendedData, "icon", "image://theme/message")
725  count: menuData && menuData.actionState.length > 0 ? menuData.actionState[0] : "0"
726  enabled: menuData && menuData.sensitive || false
727  highlightWhenPressed: false
728  removable: true
729 
730  onMenuModelChanged: {
731  loadAttributes();
732  }
733  onMenuIndexChanged: {
734  loadAttributes();
735  }
736  onClicked: {
737  menuModel.activate(menuIndex, true);
738  }
739  onDismissed: {
740  menuModel.activate(menuIndex, false);
741  }
742 
743  function loadAttributes() {
744  if (!menuModel || menuIndex == -1) return;
745  menuModel.loadExtendedAttributes(modelIndex, {'icon': 'icon'});
746  }
747  }
748  }
749 
750  Component {
751  id: mediaPayerMenu;
752 
753  Menus.MediaPlayerMenu {
754  objectName: "mediaPayerMenu"
755  property QtObject menuData: null
756  property var menuModel: menuFactory.menuModel
757  property int menuIndex: -1
758  property var actionState: menuData && menuData.actionState || undefined
759  property bool running: getExtendedProperty(actionState, "running", false)
760 
761  playerIcon: menuData && menuData.icon || "image://theme/stock_music"
762  playerName: menuData && menuData.label || i18n.tr("Nothing is playing")
763 
764  albumArt: getExtendedProperty(actionState, "art-url", "image://theme/stock_music")
765  song: getExtendedProperty(actionState, "title", "")
766  artist: getExtendedProperty(actionState, "artist", "")
767  album: getExtendedProperty(actionState, "album", "")
768  showTrack: running && (state == "Playing" || state == "Paused")
769  state: getExtendedProperty(actionState, "state", "")
770  enabled: menuData && menuData.sensitive || false
771  highlightWhenPressed: false
772  showDivider: false
773 
774  onTriggered: {
775  model.activate(modelIndex);
776  }
777  }
778  }
779 
780  Component {
781  id: playbackItemMenu;
782 
783  Menus.PlaybackItemMenu {
784  objectName: "playbackItemMenu"
785  property QtObject menuData: null
786  property var menuModel: menuFactory.menuModel
787  property int menuIndex: -1
788  property var extendedData: menuData && menuData.ext || undefined
789 
790  property var playAction: UnityMenuAction {
791  model: menuModel
792  index: menuIndex
793  name: getExtendedProperty(extendedData, "xCanonicalPlayAction", "")
794  }
795  property var nextAction: UnityMenuAction {
796  model: menuModel
797  index: menuIndex
798  name: getExtendedProperty(extendedData, "xCanonicalNextAction", "")
799  }
800  property var previousAction: UnityMenuAction {
801  model: menuModel
802  index: menuIndex
803  name: getExtendedProperty(extendedData, "xCanonicalPreviousAction", "")
804  }
805 
806  playing: playAction.state === "Playing"
807  canPlay: playAction.valid
808  canGoNext: nextAction.valid
809  canGoPrevious: previousAction.valid
810  enabled: menuData && menuData.sensitive || false
811  highlightWhenPressed: false
812 
813  onPlay: {
814  playAction.activate();
815  }
816  onNext: {
817  nextAction.activate();
818  }
819  onPrevious: {
820  previousAction.activate();
821  }
822  onMenuModelChanged: {
823  loadAttributes();
824  }
825  onMenuIndexChanged: {
826  loadAttributes();
827  }
828 
829  function loadAttributes() {
830  if (!menuModel || menuIndex == -1) return;
831  menuModel.loadExtendedAttributes(modelIndex, {'x-canonical-play-action': 'string',
832  'x-canonical-next-action': 'string',
833  'x-canonical-previous-action': 'string'});
834  }
835  }
836  }
837 
838  Component {
839  id: transferMenu
840 
841  Menus.TransferMenu {
842  objectName: "transferMenu"
843  id: transfer
844  property QtObject menuData: null
845  property var menuModel: menuFactory.menuModel
846  property int menuIndex: -1
847  property var extendedData: menuData && menuData.ext || undefined
848  property var uid: getExtendedProperty(extendedData, "xCanonicalUid", undefined)
849 
850  text: menuData && menuData.label || ""
851  iconSource: menuData && menuData.icon || "image://theme/transfer-none"
852  maximum: 1.0
853  enabled: menuData && menuData.sensitive || false
854  highlightWhenPressed: false
855  removable: true
856  confirmRemoval: true
857 
858  QDBusActionGroup {
859  id: actionGroup
860  busType: 1
861  busName: rootModel.busName
862  objectPath: rootModel.actions["indicator"]
863 
864  property var activateAction: action("activate-transfer")
865  property var cancelAction: action("cancel-transfer")
866  property var transferStateAction: uid !== undefined ? action("transfer-state."+uid) : null
867 
868  Component.onCompleted: actionGroup.start()
869  }
870 
871  property var transferState: {
872  if (actionGroup.transferStateAction === null) return undefined;
873  return actionGroup.transferStateAction.valid ? actionGroup.transferStateAction.state : undefined
874  }
875 
876  property var runningState : transferState !== undefined ? transferState["state"] : undefined
877  property var secondsLeft : transferState !== undefined ? transferState["seconds-left"] : undefined
878 
879  active: runningState !== undefined && runningState !== Menus.TransferState.Finished
880  progress: transferState !== undefined ? transferState["percent"] : 0.0
881 
882  // TODO - Should be in the SDK
883  property var timeRemaining: {
884  if (secondsLeft === undefined) return undefined;
885 
886  var remaining = "";
887  var hours = Math.floor(secondsLeft / (60 * 60));
888  var minutes = Math.floor(secondsLeft / 60) % 60;
889  var seconds = secondsLeft % 60;
890  if (hours > 0) {
891  remaining += i18n.tr("%1 hour", "%1 hours", hours).arg(hours)
892  }
893  if (minutes > 0) {
894  if (remaining != "") remaining += ", ";
895  remaining += i18n.tr("%1 minute", "%1 minutes", minutes).arg(minutes)
896  }
897  // don't include seconds if hours > 0
898  if (hours == 0 && minutes < 5 && seconds > 0) {
899  if (remaining != "") remaining += ", ";
900  remaining += i18n.tr("%1 second", "%1 seconds", seconds).arg(seconds)
901  }
902  if (remaining == "")
903  remaining = i18n.tr("0 seconds");
904  // Translators: String like "1 hour, 2 minutes, 3 seconds remaining"
905  return i18n.tr("%1 remaining").arg(remaining);
906  }
907 
908  stateText: {
909  switch (runningState) {
910  case Menus.TransferState.Queued:
911  return i18n.tr("In queue…");
912  case Menus.TransferState.Hashing:
913  case Menus.TransferState.Processing:
914  case Menus.TransferState.Running:
915  return timeRemaining === undefined ? i18n.tr("Downloading") : timeRemaining;
916  case Menus.TransferState.Paused:
917  return i18n.tr("Paused, tap to resume");
918  case Menus.TransferState.Canceled:
919  return i18n.tr("Canceled");
920  case Menus.TransferState.Finished:
921  return i18n.tr("Finished");
922  case Menus.TransferState.Error:
923  return i18n.tr("Failed, tap to retry");
924  }
925  return "";
926  }
927 
928  onMenuModelChanged: {
929  loadAttributes();
930  }
931  onMenuIndexChanged: {
932  loadAttributes();
933  }
934  onTriggered: {
935  actionGroup.activateAction.activate(uid);
936  }
937  onItemRemoved: {
938  actionGroup.cancelAction.activate(uid);
939  }
940 
941  function loadAttributes() {
942  if (!menuModel || menuIndex == -1) return;
943  menuModel.loadExtendedAttributes(menuIndex, {'x-canonical-uid': 'string'});
944  }
945  }
946  }
947 
948  Component {
949  id: buttonSectionMenu;
950 
951  Menus.StandardMenu {
952  objectName: "buttonSectionMenu"
953  property QtObject menuData: null
954  property var menuModel: menuFactory.menuModel
955  property int menuIndex: -1
956  property var extendedData: menuData && menuData.ext || undefined
957 
958  iconSource: menuData && menuData.icon || ""
959  enabled: menuData && menuData.sensitive || false
960  highlightWhenPressed: false
961  text: menuData && menuData.label || ""
962  foregroundColor: theme.palette.normal.backgroundText
963 
964  onMenuModelChanged: {
965  loadAttributes();
966  }
967  onMenuIndexChanged: {
968  loadAttributes();
969  }
970  function loadAttributes() {
971  if (!menuModel || menuIndex == -1) return;
972  menuModel.loadExtendedAttributes(menuIndex, {'x-canonical-extra-label': 'string'});
973  }
974 
975  component: Component {
976  Button {
977  objectName: "buttonSectionMenuControl"
978  text: getExtendedProperty(extendedData, "xCanonicalExtraLabel", "")
979 
980  onClicked: {
981  menuModel.activate(menuIndex);
982  }
983  }
984  }
985  }
986  }
987 
988  Component {
989  id: keymapMenu;
990 
991  // FIXME this should use a "radio button" menu, once we have it in the SDK
992  ListItems.Empty {
993  id: checkItem
994  objectName: "keymapMenu"
995  property QtObject menuData: null
996  property int menuIndex: -1
997  readonly property bool serverChecked: menuData && menuData.isToggled || false
998 
999  enabled: menuData && menuData.sensitive || false
1000  highlightWhenPressed: false
1001  __acceptEvents: !serverChecked
1002 
1003  CheckBox {
1004  id: checkbox
1005  __acceptEvents: !checkItem.serverChecked
1006  anchors {
1007  left: parent.left
1008  leftMargin: checkItem.__contentsMargins
1009  verticalCenter: parent.verticalCenter
1010  }
1011  }
1012 
1013  ServerPropertySynchroniser {
1014  objectName: "sync"
1015  syncTimeout: Utils.Constants.indicatorValueTimeout
1016 
1017  serverTarget: checkItem
1018  serverProperty: "serverChecked"
1019  userTarget: checkbox
1020  userProperty: "checked"
1021 
1022  onSyncTriggered: {
1023  menuModel.activate(checkItem.menuIndex);
1024  }
1025  }
1026 
1027  Label {
1028  id: label
1029  anchors {
1030  left: checkbox.right
1031  leftMargin: checkItem.__contentsMargins
1032  right: parent.right
1033  rightMargin: checkItem.__contentsMargins
1034  verticalCenter: parent.verticalCenter
1035  }
1036  elide: Text.ElideRight
1037  text: checkItem.menuData && checkItem.menuData.label || ""
1038  }
1039 
1040  onClicked: menuModel.activate(menuIndex);
1041  }
1042  }
1043 
1044  function load(modelData, context) {
1045  // tweak indicator-session items
1046  if (context === "indicator-session") {
1047  if ((modelData.action === "indicator.logout" || modelData.action === "indicator.suspend" || modelData.action === "indicator.hibernate" ||
1048  modelData.action === "indicator.reboot")
1049  && !Platform.isPC) {
1050  return null; // logout, suspend and hibernate hidden on devices
1051  }
1052  }
1053 
1054  // specialize for indicator-keyboard
1055  if (context === "indicator-keyboard") {
1056  if (modelData.isRadio) {
1057  return keymapMenu;
1058  } else if (modelData.action === "indicator.map" || modelData.action === "indicator.chart") {
1059  return null; // map and chart not available
1060  }
1061  }
1062 
1063  if (modelData.type !== undefined && modelData.type !== "") {
1064  var component = undefined;
1065 
1066  var contextComponents = _map[context];
1067  if (contextComponents !== undefined) {
1068  component = contextComponents[modelData.type];
1069  }
1070 
1071  if (component === undefined) {
1072  component = _map["default"][modelData.type];
1073  }
1074  if (component !== undefined) {
1075  return component;
1076  }
1077  console.debug("Don't know how to make " + modelData.type + " for " + context);
1078  }
1079  if (modelData.isCheck || modelData.isRadio) {
1080  return checkableMenu;
1081  }
1082  if (modelData.isSeparator) {
1083  return separatorMenu;
1084  }
1085  return standardMenu;
1086  }
1087 }