2 * Copyright 2013-2016 Canonical Ltd.
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.
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.
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/>.
18 import QtQuick.Window 2.2
19 import Lomiri.Settings.Menus 0.1 as Menus
20 import Lomiri.Settings.Components 0.1
22 import Utils 0.1 as Utils
23 import Lomiri.Components.ListItems 1.3 as ListItems
24 import Lomiri.Components 1.3
25 import Lomiri.Session 0.1
26 import Lomiri.Platform 1.0
31 property string indicator
32 property var rootModel: null
33 property var menuModel: null
35 property var _userMap: null
36 readonly property var _typeToComponent: {
38 "lomiri.widgets.systemsettings.tablet.volumecontrol" : sliderMenu,
39 "lomiri.widgets.systemsettings.tablet.switch" : switchMenu,
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,
53 "com.canonical.indicator.messages.messageitem" : messageItem,
54 "com.canonical.indicator.messages.sourceitem" : groupedMessage,
56 "com.canonical.lomiri.slider" : sliderMenu,
57 "com.canonical.lomiri.switch" : switchMenu,
59 "com.canonical.lomiri.media-player" : mediaPayerMenu,
60 "com.canonical.lomiri.playback-item" : playbackItemMenu,
62 "lomiri.widgets.systemsettings.tablet.wifisection" : wifiSection,
63 "lomiri.widgets.systemsettings.tablet.accesspoint" : accessPoint,
64 "com.lomiri.indicator.network.modeminfoitem" : modeminfoitem,
66 "com.canonical.indicator.calendar": calendarMenu,
67 "com.canonical.indicator.location": timezoneMenu,
69 "org.ayatana.indicator.button" : buttonMenu,
70 "org.ayatana.indicator.div" : separatorMenu,
71 "org.ayatana.indicator.section" : sectionMenu,
72 "org.ayatana.indicator.progress" : progressMenu,
73 "org.ayatana.indicator.slider" : sliderMenu,
74 "org.ayatana.indicator.switch" : switchMenu,
75 "org.ayatana.indicator.alarm" : alarmMenu,
76 "org.ayatana.indicator.appointment" : appointmentMenu,
77 "org.ayatana.indicator.transfer" : transferMenu,
78 "org.ayatana.indicator.button-section" : buttonSectionMenu,
79 "org.ayatana.indicator.link" : linkMenu,
81 "org.ayatana.indicator.messages.messageitem" : messageItem,
82 "org.ayatana.indicator.messages.sourceitem" : groupedMessage,
84 "org.ayatana.indicator.slider" : sliderMenu,
85 "org.ayatana.indicator.switch" : switchMenu,
87 "org.ayatana.indicator.media-player" : mediaPayerMenu,
88 "org.ayatana.indicator.playback-item" : playbackItemMenu,
90 "org.ayatana.indicator.network.modeminfoitem" : modeminfoitem,
92 "org.ayatana.indicator.calendar": calendarMenu,
93 "org.ayatana.indicator.location": timezoneMenu,
95 "indicator-session": {
96 "indicator.user-menu-item": Platform.isPC ? userMenuItem : null,
97 "indicator.guest-menu-item": Platform.isPC ? userMenuItem : null,
98 "com.canonical.indicator.switch": Math.min(Screen.width, Screen.height) > units.gu(60) ? switchMenu : null // Desktop mode switch
100 "indicator-messages": {
101 "com.canonical.indicator.button": messagesButtonMenu
103 "ayatana-indicator-session": {
104 "org.ayatana.indicator.user-menu-item": Platform.isPC ? userMenuItem : null,
105 "org.ayatana.indicator.guest-menu-item": Platform.isPC ? userMenuItem : null,
106 "org.ayatana.indicator.switch": Math.min(Screen.width, Screen.height) > units.gu(60) ? switchMenu : null // Desktop mode switch
108 "ayatana-indicator-messages": {
109 "org.ayatana.indicator.button": messagesButtonMenu
113 readonly property var _action_filter_map: {
114 "indicator-session": {
115 "indicator.logout": Platform.isPC ? undefined : null,
116 "indicator.suspend": Platform.isPC ? undefined : null,
117 "indicator.hibernate": Platform.isPC ? undefined : null,
118 "indicator.reboot": Platform.isPC ? undefined : null
120 "indicator-keyboard": {
121 "indicator.map": null,
122 "indicator.chart": null
124 "ayatana-indicator-session": {
125 "indicator.logout": Platform.isPC ? undefined : null,
126 "indicator.suspend": Platform.isPC ? undefined : null,
127 "indicator.hibernate": Platform.isPC ? undefined : null,
128 "indicator.reboot": Platform.isPC ? undefined : null
130 "ayatana-indicator-keyboard": {
131 "indicator.map": null,
132 "indicator.chart": null
136 function getComponentForIndicatorEntryType(type) {
137 var component = undefined;
138 var map = _userMap || _typeToComponent
139 var indicatorComponents = map[indicator];
141 if (type === undefined || type === "") {
145 if (indicatorComponents !== undefined) {
146 component = indicatorComponents[type];
149 if (component === undefined) {
150 component = map["default"][type];
153 if (component === undefined) {
154 console.debug("Don't know how to make " + type + " for " + indicator);
160 function getComponentForIndicatorEntryAction(action) {
161 var component = undefined;
162 var indicatorFilter = _action_filter_map[indicator]
164 if (action === undefined || action === "") {
168 if (indicatorFilter !== undefined) {
169 component = indicatorFilter[action];
174 function getExtendedProperty(object, propertyName, defaultValue) {
175 if (object && object.hasOwnProperty(propertyName)) {
176 return object[propertyName];
184 Menus.SeparatorMenu {
185 objectName: "separatorMenu"
194 objectName: "sliderMenu"
195 property QtObject menuData: null
196 property var menuModel: menuFactory.menuModel
197 property int menuIndex: -1
198 property var extendedData: menuData && menuData.ext || undefined
199 property var serverValue: getExtendedProperty(menuData, "actionState", undefined)
201 text: menuData && menuData.label || ""
202 minIcon: getExtendedProperty(extendedData, "minIcon", "")
203 maxIcon: getExtendedProperty(extendedData, "maxIcon", "")
205 minimumValue: getExtendedProperty(extendedData, "minValue", 0.0)
207 var maximum = getExtendedProperty(extendedData, "maxValue", 1.0);
208 if (maximum <= minimumValue) {
209 return minimumValue + 1;
213 enabled: menuData && menuData.sensitive || false
214 highlightWhenPressed: false
216 onMenuModelChanged: {
219 onMenuIndexChanged: {
223 function loadAttributes() {
224 if (!menuModel || menuIndex == -1) return;
225 menuModel.loadExtendedAttributes(menuIndex, {'min-value': 'double',
226 'max-value': 'double',
229 'x-canonical-sync-action': 'string'});
232 ServerPropertySynchroniser {
233 id: sliderPropertySync
235 syncTimeout: Utils.Constants.indicatorValueTimeout
236 bufferedSyncTimeout: true
237 maximumWaitBufferInterval: 16
239 serverTarget: sliderItem
240 serverProperty: "serverValue"
241 userTarget: sliderItem
242 userProperty: "value"
244 onSyncTriggered: menuModel.changeState(menuIndex, value)
250 name: getExtendedProperty(extendedData, "xCanonicalSyncAction", "")
252 sliderPropertySync.reset();
253 sliderPropertySync.updateUserValue();
263 objectName: "buttonMenu"
264 property QtObject menuData: null
265 property var menuModel: menuFactory.menuModel
266 property int menuIndex: -1
268 buttonText: menuData && menuData.label || ""
269 enabled: menuData && menuData.sensitive || false
270 highlightWhenPressed: false
273 menuModel.activate(menuIndex);
279 id: messagesButtonMenu;
281 Menus.BaseLayoutMenu {
282 objectName: "messagesButtonMenu"
283 property QtObject menuData: null
284 property var menuModel: menuFactory.menuModel
285 property int menuIndex: -1
287 highlightWhenPressed: false
288 enabled: menuData && menuData.sensitive || false
289 text: menuData && menuData.label || ""
290 title.color: theme.palette.selected.backgroundText
291 title.horizontalAlignment: Text.AlignHCenter
292 title.font.bold: true
294 onClicked: menuModel.activate(menuIndex);
302 objectName: "sectionMenu"
303 property QtObject menuData: null
304 property var menuIndex: undefined
306 text: menuData && menuData.label || ""
314 Menus.ProgressValueMenu {
315 objectName: "progressMenu"
316 property QtObject menuData: null
317 property int menuIndex: -1
319 text: menuData && menuData.label || ""
320 iconSource: menuData && menuData.icon || ""
321 value : menuData && menuData.actionState || 0.0
322 enabled: menuData && menuData.sensitive || false
330 objectName: "standardMenu"
331 property QtObject menuData: null
332 property int menuIndex: -1
334 text: menuData && menuData.label || ""
335 iconSource: menuData && menuData.icon || ""
336 enabled: menuData && menuData.sensitive || false
337 highlightWhenPressed: false
340 menuModel.activate(menuIndex);
348 Menus.BaseLayoutMenu {
349 objectName: "linkMenu"
350 property QtObject menuData: null
351 property int menuIndex: -1
353 text: menuData && menuData.label || ""
354 enabled: menuData && menuData.sensitive || false
355 backColor: Qt.rgba(1,1,1,0.07)
356 highlightWhenPressed: false
359 menuModel.activate(menuIndex);
365 if (menuData.icon && menuData.icon != "") {
367 } else if (menuData.action.indexOf("settings") > -1) {
368 return "image://theme/settings"
375 color: theme.palette.normal.backgroundText
376 SlotsLayout.position: SlotsLayout.Trailing
384 Menus.CheckableMenu {
386 objectName: "checkableMenu"
387 property QtObject menuData: null
388 property int menuIndex: -1
389 property bool serverChecked: menuData && menuData.isToggled || false
391 text: menuData && menuData.label || ""
392 enabled: menuData && menuData.sensitive || false
393 checked: serverChecked
394 highlightWhenPressed: false
396 ServerPropertySynchroniser {
398 syncTimeout: Utils.Constants.indicatorValueTimeout
400 serverTarget: checkItem
401 serverProperty: "serverChecked"
402 userTarget: checkItem
403 userProperty: "checked"
405 onSyncTriggered: menuModel.activate(checkItem.menuIndex)
415 objectName: "radioMenu"
416 property QtObject menuData: null
417 property int menuIndex: -1
418 property bool serverChecked: menuData && menuData.isToggled || false
420 text: menuData && menuData.label || ""
421 enabled: menuData && menuData.sensitive || false
422 checked: serverChecked
423 highlightWhenPressed: false
425 ServerPropertySynchroniser {
427 syncTimeout: Utils.Constants.indicatorValueTimeout
429 serverTarget: radioItem
430 serverProperty: "serverChecked"
431 userTarget: radioItem
432 userProperty: "checked"
434 onSyncTriggered: menuModel.activate(radioItem.menuIndex)
444 objectName: "switchMenu"
445 property QtObject menuData: null
446 property var menuModel: menuFactory.menuModel
447 property int menuIndex: -1
448 property var extendedData: menuData && menuData.ext || undefined
449 property bool serverChecked: menuData && menuData.isToggled || false
451 text: menuData && menuData.label || ""
452 iconSource: menuData && menuData.icon || ""
453 enabled: menuData && menuData.sensitive || false
454 checked: serverChecked
455 highlightWhenPressed: false
457 property var subtitleAction: AyatanaMenuAction {
460 name: getExtendedProperty(extendedData, "xCanonicalSubtitleAction", "")
462 subtitle.text: subtitleAction.valid ? subtitleAction.state : ""
464 onMenuModelChanged: {
467 onMenuIndexChanged: {
471 function loadAttributes() {
472 if (!menuModel || menuIndex == -1) return;
473 menuModel.loadExtendedAttributes(menuIndex, {'x-canonical-subtitle-action': 'string'});
476 ServerPropertySynchroniser {
478 syncTimeout: Utils.Constants.indicatorValueTimeout
480 serverTarget: switchItem
481 serverProperty: "serverChecked"
482 userTarget: switchItem
483 userProperty: "checked"
486 if (menuData && menuData.type === 'org.ayatana.indicator.switch') {
487 // Workaround action change for Ayatana Indicators.
488 // https://github.com/AyatanaIndicators/qmenumodel/issues/21
489 // https://gitlab.com/ubports/development/core/lomiri/-/issues/17
490 // FIXME: when the permanent fix is merged, look at this again.
491 menuModel.activate(switchItem.menuIndex, switchItem.checked);
493 menuModel.activate(switchItem.menuIndex);
505 objectName: "alarmMenu"
506 property QtObject menuData: null
507 property var menuModel: menuFactory.menuModel
508 property int menuIndex: -1
509 property var extendedData: menuData && menuData.ext || undefined
511 readonly property date serverTime: new Date(getExtendedProperty(extendedData, "xCanonicalTime", 0) * 1000)
513 frequency: LiveTimer.Relative
514 relativeTime: alarmItem.serverTime
515 onTrigger: alarmItem.time = i18n.relativeDateTime(alarmItem.serverTime)
518 text: menuData && menuData.label || ""
519 iconSource: menuData && menuData.icon || "image://theme/alarm-clock"
520 time: i18n.relativeDateTime(serverTime)
521 enabled: menuData && menuData.sensitive || false
522 highlightWhenPressed: false
524 onMenuModelChanged: {
527 onMenuIndexChanged: {
531 menuModel.activate(menuIndex);
534 function loadAttributes() {
535 if (!menuModel || menuIndex == -1) return;
536 menuModel.loadExtendedAttributes(menuIndex, {'x-canonical-time': 'int64'});
546 objectName: "appointmentMenu"
547 property QtObject menuData: null
548 property var menuModel: menuFactory.menuModel
549 property int menuIndex: -1
550 property var extendedData: menuData && menuData.ext || undefined
552 readonly property date serverTime: new Date(getExtendedProperty(extendedData, "xCanonicalTime", 0) * 1000)
555 frequency: LiveTimer.Relative
556 relativeTime: appointmentItem.serverTime
557 onTrigger: appointmentItem.time = i18n.relativeDateTime(appointmentItem.serverTime)
560 text: menuData && menuData.label || ""
561 iconSource: menuData && menuData.icon || "image://theme/calendar"
562 time: i18n.relativeDateTime(serverTime)
563 eventColor: getExtendedProperty(extendedData, "xCanonicalColor", Qt.rgba(0.0, 0.0, 0.0, 0.0))
564 enabled: menuData && menuData.sensitive || false
565 highlightWhenPressed: false
567 onMenuModelChanged: {
570 onMenuIndexChanged: {
574 menuModel.activate(menuIndex);
577 function loadAttributes() {
578 if (!menuModel || menuIndex == -1) return;
579 menuModel.loadExtendedAttributes(menuIndex, {'x-canonical-color': 'string',
580 'x-canonical-time': 'int64'});
588 Menus.UserSessionMenu {
589 objectName: "userSessionMenu"
590 highlightWhenPressed: false
592 property QtObject menuData: null
593 property var menuModel: menuFactory.menuModel
594 property int menuIndex: -1
596 name: menuData && menuData.label || "" // label is the user's real name
597 iconSource: menuData && menuData.icon || ""
599 // would be better to compare with the logname but sadly the indicator doesn't expose that
600 active: DBusLomiriSessionService.RealName() !== "" ? DBusLomiriSessionService.RealName() == name
601 : DBusLomiriSessionService.UserName() == name
604 menuModel.activate(menuIndex);
614 objectName: "calendarMenu"
617 property QtObject menuData: null
618 property var menuModel: menuFactory.menuModel
619 property var actionState: menuData && menuData.actionState || null
620 property real calendarDay: getExtendedProperty(actionState, "calendar-day", 0)
621 property int menuIndex: -1
623 showWeekNumbers: getExtendedProperty(actionState, "show-week-numbers", false)
624 eventDays: getExtendedProperty(actionState, "appointment-days", [])
626 onCalendarDayChanged: {
627 if (calendarDay > 0) {
628 // This would trigger a selectionDateChanged signal, thus
629 // we've to avoid that the subsequent model activation
630 // would cause an infinite loop
631 modelUpdateConnections.enabled = false
632 currentDate = new Date(calendarDay * 1000)
633 modelUpdateConnections.enabled = true
638 id: modelUpdateConnections
639 property bool enabled: true
640 target: (enabled && calendarItem.visible) ? calendarItem : null
642 onSelectedDateChanged: {
643 menuModel.activate(menuIndex, selectedDate.getTime() / 1000 | 0)
654 objectName: "timezoneMenu"
656 property QtObject menuData: null
657 property var menuModel: menuFactory.menuModel
658 property int menuIndex: -1
659 property var extendedData: menuData && menuData.ext || undefined
660 readonly property string tz: getExtendedProperty(extendedData, "xCanonicalTimezone", "UTC")
661 property var updateTimer: Timer {
663 running: tzMenuItem.visible // only run when we're open
664 onTriggered: tzMenuItem.time = Utils.TimezoneFormatter.currentTimeInTimezone(tzMenuItem.tz)
667 city: menuData && menuData.label || ""
668 time: Utils.TimezoneFormatter.currentTimeInTimezone(tz)
669 enabled: menuData && menuData.sensitive || false
671 onMenuModelChanged: {
674 onMenuIndexChanged: {
678 tzActionGroup.setLocation.activate(tz);
683 busType: DBus.SessionBus
684 busName: "org.ayatana.indicator.datetime"
685 objectPath: "/org/ayatana/indicator/datetime"
687 property variant setLocation: action("set-location")
689 Component.onCompleted: tzActionGroup.start()
692 function loadAttributes() {
693 if (!menuModel || menuIndex == -1) return;
694 menuModel.loadExtendedAttributes(menuIndex, {'x-canonical-timezone': 'string'});
703 objectName: "wifiSection"
704 property QtObject menuData: null
705 property var menuModel: menuFactory.menuModel
706 property int menuIndex: -1
707 property var extendedData: menuData && menuData.ext || undefined
709 text: menuData && menuData.label || ""
710 busy: getExtendedProperty(extendedData, "xCanonicalBusyAction", false)
712 onMenuModelChanged: {
715 onMenuIndexChanged: {
719 function loadAttributes() {
720 if (!menuModel || menuIndex == -1) return;
721 menuModel.loadExtendedAttributes(menuIndex, {'x-canonical-busy-action': 'bool'})
729 Menus.AccessPointMenu {
731 objectName: "accessPoint"
732 property QtObject menuData: null
733 property var menuModel: menuFactory.menuModel
734 property int menuIndex: -1
735 property var extendedData: menuData && menuData.ext || undefined
736 property bool serverChecked: menuData && menuData.isToggled || false
738 property var strengthAction: AyatanaMenuAction {
741 name: getExtendedProperty(extendedData, "xCanonicalWifiApStrengthAction", "")
744 text: menuData && menuData.label || ""
745 enabled: menuData && menuData.sensitive || false
746 active: serverChecked
747 secure: getExtendedProperty(extendedData, "xCanonicalWifiApIsSecure", false)
748 adHoc: getExtendedProperty(extendedData, "xCanonicalWifiApIsAdhoc", false)
750 if (strengthAction.valid) {
751 var state = strengthAction.state; // handle both int and uchar
752 // FIXME remove the special casing when we switch to indicator-network completely
753 if (typeof state == "string") {
754 return state.charCodeAt();
760 highlightWhenPressed: false
762 onMenuModelChanged: {
765 onMenuIndexChanged: {
769 function loadAttributes() {
770 if (!menuModel || menuIndex == -1) return;
771 menuModel.loadExtendedAttributes(menuIndex, {'x-canonical-wifi-ap-is-adhoc': 'bool',
772 'x-canonical-wifi-ap-is-secure': 'bool',
773 'x-canonical-wifi-ap-strength-action': 'string'});
776 ServerPropertySynchroniser {
778 syncTimeout: Utils.Constants.indicatorValueTimeout
781 serverProperty: "serverChecked"
783 userProperty: "active"
784 userTrigger: "onTriggered"
786 onSyncTriggered: menuModel.activate(apItem.menuIndex)
793 Menus.ModemInfoItem {
794 objectName: "modemInfoItem"
795 property QtObject menuData: null
796 property var menuModel: menuFactory.menuModel
797 property int menuIndex: -1
798 property var extendedData: menuData && menuData.ext || undefined
799 highlightWhenPressed: false
801 property var statusLabelAction: AyatanaMenuAction {
804 name: getExtendedProperty(extendedData, "xLomiriModemStatusLabelAction", "")
806 statusText: statusLabelAction.valid ? statusLabelAction.state : ""
808 property var statusIconAction: AyatanaMenuAction {
811 name: getExtendedProperty(extendedData, "xLomiriModemStatusIconAction", "")
813 statusIcon: statusIconAction.valid ? statusIconAction.state : ""
815 property var connectivityIconAction: AyatanaMenuAction {
818 name: getExtendedProperty(extendedData, "xLomiriModemConnectivityIconAction", "")
820 connectivityIcon: connectivityIconAction.valid ? connectivityIconAction.state : ""
822 property var simIdentifierLabelAction: AyatanaMenuAction {
825 name: getExtendedProperty(extendedData, "xLomiriModemSimIdentifierLabelAction", "")
827 simIdentifierText: simIdentifierLabelAction.valid ? simIdentifierLabelAction.state : ""
829 property var roamingAction: AyatanaMenuAction {
832 name: getExtendedProperty(extendedData, "xLomiriModemRoamingAction", "")
834 roaming: roamingAction.valid ? roamingAction.state : false
836 property var unlockAction: AyatanaMenuAction {
839 name: getExtendedProperty(extendedData, "xLomiriModemLockedAction", "")
842 unlockAction.activate();
844 locked: unlockAction.valid ? unlockAction.state : false
846 onMenuModelChanged: {
849 onMenuIndexChanged: {
853 function loadAttributes() {
854 if (!menuModel || menuIndex == -1) return;
855 menuModel.loadExtendedAttributes(menuIndex, {'x-lomiri-modem-status-label-action': 'string',
856 'x-lomiri-modem-status-icon-action': 'string',
857 'x-lomiri-modem-connectivity-icon-action': 'string',
858 'x-lomiri-modem-sim-identifier-label-action': 'string',
859 'x-lomiri-modem-roaming-action': 'string',
860 'x-lomiri-modem-locked-action': 'string'});
868 MessageMenuItemFactory {
869 objectName: "messageItem"
870 menuModel: menuFactory.menuModel
877 Menus.GroupedMessageMenu {
878 objectName: "groupedMessage"
879 property QtObject menuData: null
880 property var menuModel: menuFactory.menuModel
881 property int menuIndex: -1
882 property var extendedData: menuData && menuData.ext || undefined
884 text: menuData && menuData.label || ""
885 iconSource: getExtendedProperty(extendedData, "icon", "image://theme/message")
886 count: menuData && menuData.actionState.length > 0 ? menuData.actionState[0] : "0"
887 enabled: menuData && menuData.sensitive || false
888 highlightWhenPressed: false
891 onMenuModelChanged: {
894 onMenuIndexChanged: {
898 menuModel.activate(menuIndex, true);
901 menuModel.activate(menuIndex, false);
904 function loadAttributes() {
905 if (!menuModel || menuIndex == -1) return;
906 menuModel.loadExtendedAttributes(modelIndex, {'icon': 'icon'});
914 Menus.MediaPlayerMenu {
915 objectName: "mediaPayerMenu"
916 property QtObject menuData: null
917 property var menuModel: menuFactory.menuModel
918 property int menuIndex: -1
919 property var actionState: menuData && menuData.actionState || undefined
920 property bool running: getExtendedProperty(actionState, "running", false)
922 playerIcon: menuData && menuData.icon || "image://theme/stock_music"
923 playerName: menuData && menuData.label || i18n.tr("Nothing is playing")
925 albumArt: getExtendedProperty(actionState, "art-url", "image://theme/stock_music")
926 song: getExtendedProperty(actionState, "title", "")
927 artist: getExtendedProperty(actionState, "artist", "")
928 album: getExtendedProperty(actionState, "album", "")
929 showTrack: running && (state == "Playing" || state == "Paused")
930 state: getExtendedProperty(actionState, "state", "")
931 enabled: menuData && menuData.sensitive || false
932 highlightWhenPressed: false
935 model.activate(modelIndex);
941 id: playbackItemMenu;
943 Menus.PlaybackItemMenu {
944 objectName: "playbackItemMenu"
945 property QtObject menuData: null
946 property var menuModel: menuFactory.menuModel
947 property int menuIndex: -1
948 property var extendedData: menuData && menuData.ext || undefined
950 property var playAction: AyatanaMenuAction {
953 name: getExtendedProperty(extendedData, "xCanonicalPlayAction", "")
955 property var nextAction: AyatanaMenuAction {
958 name: getExtendedProperty(extendedData, "xCanonicalNextAction", "")
960 property var previousAction: AyatanaMenuAction {
963 name: getExtendedProperty(extendedData, "xCanonicalPreviousAction", "")
966 playing: playAction.state === "Playing"
967 canPlay: playAction.valid
968 canGoNext: nextAction.valid
969 canGoPrevious: previousAction.valid
970 enabled: menuData && menuData.sensitive || false
971 highlightWhenPressed: false
974 playAction.activate();
977 nextAction.activate();
980 previousAction.activate();
982 onMenuModelChanged: {
985 onMenuIndexChanged: {
989 function loadAttributes() {
990 if (!menuModel || menuIndex == -1) return;
991 menuModel.loadExtendedAttributes(modelIndex, {'x-canonical-play-action': 'string',
992 'x-canonical-next-action': 'string',
993 'x-canonical-previous-action': 'string'});
1001 Menus.TransferMenu {
1002 objectName: "transferMenu"
1004 property QtObject menuData: null
1005 property var menuModel: menuFactory.menuModel
1006 property int menuIndex: -1
1007 property var extendedData: menuData && menuData.ext || undefined
1008 property var uid: getExtendedProperty(extendedData, "xCanonicalUid", undefined)
1010 text: menuData && menuData.label || ""
1011 iconSource: menuData && menuData.icon || "image://theme/transfer-none"
1013 enabled: menuData && menuData.sensitive || false
1014 highlightWhenPressed: false
1016 confirmRemoval: true
1021 busName: menuFactory.rootModel.busName
1022 objectPath: menuFactory.rootModel.actions["indicator"]
1024 property var activateAction: action("activate-transfer")
1025 property var cancelAction: action("cancel-transfer")
1026 property var transferStateAction: uid !== undefined ? action("transfer-state."+uid) : null
1028 Component.onCompleted: actionGroup.start()
1031 property var transferState: {
1032 if (actionGroup.transferStateAction === null) return undefined;
1033 return actionGroup.transferStateAction.valid ? actionGroup.transferStateAction.state : undefined
1036 property var runningState : transferState !== undefined ? transferState["state"] : undefined
1037 property var secondsLeft : transferState !== undefined ? transferState["seconds-left"] : undefined
1039 active: runningState !== undefined && runningState !== Menus.TransferState.Finished
1040 progress: transferState !== undefined ? transferState["percent"] : 0.0
1042 // TODO - Should be in the SDK
1043 property var timeRemaining: {
1044 if (secondsLeft === undefined) return undefined;
1047 var hours = Math.floor(secondsLeft / (60 * 60));
1048 var minutes = Math.floor(secondsLeft / 60) % 60;
1049 var seconds = secondsLeft % 60;
1051 remaining += i18n.tr("%1 hour", "%1 hours", hours).arg(hours)
1054 if (remaining != "") remaining += ", ";
1055 remaining += i18n.tr("%1 minute", "%1 minutes", minutes).arg(minutes)
1057 // don't include seconds if hours > 0
1058 if (hours == 0 && minutes < 5 && seconds > 0) {
1059 if (remaining != "") remaining += ", ";
1060 remaining += i18n.tr("%1 second", "%1 seconds", seconds).arg(seconds)
1062 if (remaining == "")
1063 remaining = i18n.tr("0 seconds");
1064 // Translators: String like "1 hour, 2 minutes, 3 seconds remaining"
1065 return i18n.tr("%1 remaining").arg(remaining);
1069 switch (runningState) {
1070 case Menus.TransferState.Queued:
1071 return i18n.tr("In queue…");
1072 case Menus.TransferState.Hashing:
1073 case Menus.TransferState.Processing:
1074 case Menus.TransferState.Running:
1075 return timeRemaining === undefined ? i18n.tr("Downloading") : timeRemaining;
1076 case Menus.TransferState.Paused:
1077 return i18n.tr("Paused, tap to resume");
1078 case Menus.TransferState.Canceled:
1079 return i18n.tr("Canceled");
1080 case Menus.TransferState.Finished:
1081 return i18n.tr("Finished");
1082 case Menus.TransferState.Error:
1083 return i18n.tr("Failed, tap to retry");
1088 onMenuModelChanged: {
1091 onMenuIndexChanged: {
1095 actionGroup.activateAction.activate(uid);
1098 actionGroup.cancelAction.activate(uid);
1101 function loadAttributes() {
1102 if (!menuModel || menuIndex == -1) return;
1103 menuModel.loadExtendedAttributes(menuIndex, {'x-canonical-uid': 'string'});
1109 id: buttonSectionMenu;
1112 objectName: "buttonSectionMenu"
1113 property QtObject menuData: null
1114 property var menuModel: menuFactory.menuModel
1115 property int menuIndex: -1
1116 property var extendedData: menuData && menuData.ext || undefined
1118 iconSource: menuData && menuData.icon || ""
1119 enabled: menuData && menuData.sensitive || false
1120 highlightWhenPressed: false
1121 text: menuData && menuData.label || ""
1122 foregroundColor: theme.palette.normal.backgroundText
1123 buttonText: getExtendedProperty(extendedData, "xCanonicalExtraLabel", "")
1125 onMenuModelChanged: {
1128 onMenuIndexChanged: {
1131 function loadAttributes() {
1132 if (!menuModel || menuIndex == -1) return;
1133 menuModel.loadExtendedAttributes(menuIndex, {'x-canonical-extra-label': 'string'});
1136 onButtonClicked: menuModel.activate(menuIndex);
1140 function load(modelData) {
1141 var component = getComponentForIndicatorEntryAction(modelData.action)
1142 if (component !== undefined) {
1146 component = getComponentForIndicatorEntryType(modelData.type)
1147 if (component !== undefined) {
1151 if (modelData.isCheck) {
1152 return checkableMenu;
1154 if (modelData.isRadio) {
1157 if (modelData.isSeparator) {
1158 return separatorMenu;
1160 if (modelData.action !== undefined && modelData.action.indexOf("settings") > -1) {
1161 // FIXME : At the moment, the indicators aren't using
1162 // org.ayatana.indicators.link for settings menu. Need to fudge it.
1165 return standardMenu;