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