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