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