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