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/>.
18import QtQuick.Window 2.2
19import Lomiri.Settings.Menus 0.1 as Menus
20import Lomiri.Settings.Components 0.1
22import Utils 0.1 as Utils
23import Lomiri.Components.ListItems 1.3 as ListItems
24import Lomiri.Components 1.3
25import Lomiri.Session 0.1
26import 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 "com.lomiri.indicator.transfer" : transferMenu,
71 "org.ayatana.indicator.button" : buttonMenu,
72 "org.ayatana.indicator.div" : separatorMenu,
73 "org.ayatana.indicator.section" : sectionMenu,
74 "org.ayatana.indicator.progress" : progressMenu,
75 "org.ayatana.indicator.slider" : sliderMenu,
76 "org.ayatana.indicator.switch" : switchMenu,
77 "org.ayatana.indicator.alarm" : alarmMenu,
78 "org.ayatana.indicator.appointment" : appointmentMenu,
79 "org.ayatana.indicator.transfer" : transferMenu,
80 "org.ayatana.indicator.button-section" : buttonSectionMenu,
81 "org.ayatana.indicator.link" : linkMenu,
83 "org.ayatana.indicator.messages.messageitem" : messageItem,
84 "org.ayatana.indicator.messages.sourceitem" : groupedMessage,
86 "org.ayatana.indicator.slider" : sliderMenu,
87 "org.ayatana.indicator.switch" : switchMenu,
89 "org.ayatana.indicator.media-player" : mediaPayerMenu,
90 "org.ayatana.indicator.playback-item" : playbackItemMenu,
92 "org.ayatana.indicator.network.modeminfoitem" : modeminfoitem,
94 "org.ayatana.indicator.calendar": calendarMenu,
95 "org.ayatana.indicator.location": timezoneMenu,
96 "org.ayatana.indicator.level" : levelMenu,
98 "indicator-session": {
99 "indicator.user-menu-item": Platform.isPC ? userMenuItem : null,
100 "indicator.guest-menu-item": Platform.isPC ? userMenuItem : null,
101 "com.canonical.indicator.switch": Math.min(Screen.width, Screen.height) > units.gu(60) ? switchMenu : null // Desktop mode switch
103 "indicator-messages": {
104 "com.canonical.indicator.button": messagesButtonMenu
106 "ayatana-indicator-session": {
107 "org.ayatana.indicator.user-menu-item": Platform.isPC ? userMenuItem : null,
108 "org.ayatana.indicator.guest-menu-item": Platform.isPC ? userMenuItem : null,
109 "org.ayatana.indicator.switch": Math.min(Screen.width, Screen.height) > units.gu(60) ? switchMenu : null // Desktop mode switch
111 "ayatana-indicator-messages": {
112 "org.ayatana.indicator.button": messagesButtonMenu
116 readonly property var _action_filter_map: {
117 "indicator-session": {
118 "indicator.logout": Platform.isPC ? undefined : null,
119 "indicator.suspend": Platform.isPC ? undefined : null,
120 "indicator.hibernate": Platform.isPC ? undefined : null,
121 "indicator.reboot": Platform.isPC ? undefined : null
123 "indicator-keyboard": {
124 "indicator.map": null,
125 "indicator.chart": null
127 "ayatana-indicator-session": {
128 "indicator.logout": Platform.isPC ? undefined : null,
129 "indicator.suspend": Platform.isPC ? undefined : null,
130 "indicator.hibernate": Platform.isPC ? undefined : null,
131 "indicator.reboot": Platform.isPC ? undefined : null
133 "ayatana-indicator-keyboard": {
134 "indicator.map": null,
135 "indicator.chart": null
139 function getComponentForIndicatorEntryType(type) {
140 var component = undefined;
141 var map = _userMap || _typeToComponent
142 var indicatorComponents = map[indicator];
144 if (type === undefined || type === "") {
148 if (indicatorComponents !== undefined) {
149 component = indicatorComponents[type];
152 if (component === undefined) {
153 component = map["default"][type];
156 if (component === undefined) {
157 console.debug("Don't know how to make " + type + " for " + indicator);
163 function getComponentForIndicatorEntryAction(action) {
164 var component = undefined;
165 var indicatorFilter = _action_filter_map[indicator]
167 if (action === undefined || action === "") {
171 if (indicatorFilter !== undefined) {
172 component = indicatorFilter[action];
177 function getExtendedProperty(object, propertyName, defaultValue) {
178 if (object && object.hasOwnProperty(propertyName)) {
179 return object[propertyName];
187 Menus.SeparatorMenu {
188 objectName: "separatorMenu"
197 objectName: "sliderMenu"
198 property QtObject menuData: null
199 property var menuModel: menuFactory.menuModel
200 property int menuIndex: -1
201 property var extendedData: menuData && menuData.ext || undefined
202 property var serverValue: getExtendedProperty(menuData, "actionState", undefined)
204 text: menuData && menuData.label || ""
205 minIcon: getExtendedProperty(extendedData, "minIcon", "")
206 maxIcon: getExtendedProperty(extendedData, "maxIcon", "")
208 minimumValue: getExtendedProperty(extendedData, "minValue", 0.0)
210 var maximum = getExtendedProperty(extendedData, "maxValue", 1.0);
211 if (maximum <= minimumValue) {
212 return minimumValue + 1;
216 enabled: menuData && menuData.sensitive || false
217 highlightWhenPressed: false
219 onMenuModelChanged: {
222 onMenuIndexChanged: {
226 function loadAttributes() {
227 if (!menuModel || menuIndex == -1) return;
228 menuModel.loadExtendedAttributes(menuIndex, {'min-value': 'double',
229 'max-value': 'double',
232 'x-ayatana-sync-action': 'string'});
235 ServerPropertySynchroniser {
236 id: sliderPropertySync
238 syncTimeout: Utils.Constants.indicatorValueTimeout
239 bufferedSyncTimeout: true
240 maximumWaitBufferInterval: 16
242 serverTarget: sliderItem
243 serverProperty: "serverValue"
244 userTarget: sliderItem
245 userProperty: "value"
247 onSyncTriggered: menuModel.changeState(menuIndex, value)
253 name: getExtendedProperty(extendedData, "xAyatanaSyncAction", "")
255 sliderPropertySync.reset();
256 sliderPropertySync.updateUserValue();
266 objectName: "buttonMenu"
267 property QtObject menuData: null
268 property var menuModel: menuFactory.menuModel
269 property int menuIndex: -1
271 buttonText: menuData && menuData.label || ""
272 enabled: menuData && menuData.sensitive || false
273 highlightWhenPressed: false
276 menuModel.activate(menuIndex);
282 id: messagesButtonMenu;
284 Menus.BaseLayoutMenu {
285 objectName: "messagesButtonMenu"
286 property QtObject menuData: null
287 property var menuModel: menuFactory.menuModel
288 property int menuIndex: -1
290 highlightWhenPressed: false
291 enabled: menuData && menuData.sensitive || false
292 text: menuData && menuData.label || ""
293 title.color: theme.palette.selected.backgroundText
294 title.horizontalAlignment: Text.AlignHCenter
295 title.font.bold: true
297 onClicked: menuModel.activate(menuIndex);
305 objectName: "sectionMenu"
306 property QtObject menuData: null
307 property var menuIndex: undefined
309 text: menuData && menuData.label || ""
317 Menus.ProgressValueMenu {
318 objectName: "progressMenu"
319 property QtObject menuData: null
320 property int menuIndex: -1
322 text: menuData && menuData.label || ""
323 iconSource: menuData && menuData.icon || ""
324 value : menuData && menuData.actionState || 0.0
325 enabled: menuData && menuData.sensitive || false
332 /* Use the same UI as progressMenu for now. */
333 Menus.ProgressValueMenu {
334 objectName: "levelMenu"
335 property QtObject menuData: null
336 property var menuModel: menuFactory.menuModel
337 property int menuIndex: -1
338 property var extendedData: menuData && menuData.ext || undefined
340 text: menuData && menuData.label || ""
341 iconSource: menuData && menuData.icon || ""
342 value : extendedData && extendedData.xAyatanaLevel || 0.0
343 enabled: menuData && menuData.sensitive || false
345 onMenuModelChanged: {
348 onMenuIndexChanged: {
352 function loadAttributes() {
353 if (!menuModel || menuIndex == -1) return;
354 menuModel.loadExtendedAttributes(menuIndex, {'x-ayatana-level': 'uint16'});
363 objectName: "standardMenu"
364 property QtObject menuData: null
365 property int menuIndex: -1
367 text: menuData && menuData.label || ""
368 iconSource: menuData && menuData.icon || ""
369 enabled: menuData && menuData.sensitive || false
370 highlightWhenPressed: false
373 menuModel.activate(menuIndex);
381 Menus.BaseLayoutMenu {
382 objectName: "linkMenu"
383 property QtObject menuData: null
384 property int menuIndex: -1
386 text: menuData && menuData.label || ""
387 enabled: menuData && menuData.sensitive || false
388 backColor: Qt.rgba(1,1,1,0.07)
389 highlightWhenPressed: false
392 menuModel.activate(menuIndex);
398 if (menuData.icon && menuData.icon != "") {
400 } else if (menuData.action.indexOf("settings") > -1) {
401 return "image://theme/settings"
408 color: theme.palette.normal.backgroundText
409 SlotsLayout.position: SlotsLayout.Trailing
417 Menus.CheckableMenu {
419 objectName: "checkableMenu"
420 property QtObject menuData: null
421 property int menuIndex: -1
422 property bool serverChecked: menuData && menuData.isToggled || false
424 text: menuData && menuData.label || ""
425 enabled: menuData && menuData.sensitive || false
426 checked: serverChecked
427 highlightWhenPressed: false
429 ServerPropertySynchroniser {
431 syncTimeout: Utils.Constants.indicatorValueTimeout
433 serverTarget: checkItem
434 serverProperty: "serverChecked"
435 userTarget: checkItem
436 userProperty: "checked"
438 onSyncTriggered: menuModel.activate(checkItem.menuIndex)
448 objectName: "radioMenu"
449 property QtObject menuData: null
450 property int menuIndex: -1
451 property bool serverChecked: menuData && menuData.isToggled || false
453 text: menuData && menuData.label || ""
454 enabled: menuData && menuData.sensitive || false
455 checked: serverChecked
456 highlightWhenPressed: false
458 ServerPropertySynchroniser {
460 syncTimeout: Utils.Constants.indicatorValueTimeout
462 serverTarget: radioItem
463 serverProperty: "serverChecked"
464 userTarget: radioItem
465 userProperty: "checked"
467 onSyncTriggered: menuModel.activate(radioItem.menuIndex)
477 objectName: "switchMenu"
478 property QtObject menuData: null
479 property var menuModel: menuFactory.menuModel
480 property int menuIndex: -1
481 property var extendedData: menuData && menuData.ext || undefined
482 property bool serverChecked: menuData && menuData.isToggled || false
484 text: menuData && menuData.label || ""
485 iconSource: menuData && menuData.icon || ""
486 enabled: menuData && menuData.sensitive || false
487 checked: serverChecked
488 highlightWhenPressed: false
490 property var subtitleAction: AyatanaMenuAction {
493 name: getExtendedProperty(extendedData, "xAyatanaSubtitleAction", "")
495 subtitle.text: subtitleAction.valid ? subtitleAction.state : ""
497 onMenuModelChanged: {
500 onMenuIndexChanged: {
504 function loadAttributes() {
505 if (!menuModel || menuIndex == -1) return;
506 menuModel.loadExtendedAttributes(menuIndex, {
507 'x-ayatana-subtitle-action': 'string',
512 ServerPropertySynchroniser {
514 syncTimeout: Utils.Constants.indicatorValueTimeout
516 serverTarget: switchItem
517 serverProperty: "serverChecked"
518 userTarget: switchItem
519 userProperty: "checked"
522 /* Figures out if the action's activate() requires a
523 * parameter or not. Works with:
524 * - com.canonical.indicator.switch
525 * - org.ayatana.indicator.switch (with fix)
526 * - com.canonical.indicator.switch mis-labled as
527 * org.ayatana.indicator.switch
528 * https://gitlab.com/ubports/development/core/lomiri-indicator-network/-/issues/87#note_1206883970
530 * If activate() requires a parameter but menu doesn't
531 * specify a target, the menu will be broken in a different
534 if (extendedData.hasOwnProperty('target')) {
535 menuModel.activate(switchItem.menuIndex, switchItem.checked);
537 menuModel.activate(switchItem.menuIndex);
549 objectName: "alarmMenu"
550 property QtObject menuData: null
551 property var menuModel: menuFactory.menuModel
552 property int menuIndex: -1
553 property var extendedData: menuData && menuData.ext || undefined
555 readonly property date serverTime: new Date(getExtendedProperty(extendedData, "xAyatanaTime", 0) * 1000)
557 frequency: LiveTimer.Relative
558 relativeTime: alarmItem.serverTime
559 onTrigger: alarmItem.time = i18n.relativeDateTime(alarmItem.serverTime)
562 text: menuData && menuData.label || ""
563 iconSource: menuData && menuData.icon || "image://theme/alarm-clock"
564 time: i18n.relativeDateTime(serverTime)
565 enabled: menuData && menuData.sensitive || false
566 highlightWhenPressed: false
568 onMenuModelChanged: {
571 onMenuIndexChanged: {
575 menuModel.activate(menuIndex);
578 function loadAttributes() {
579 if (!menuModel || menuIndex == -1) return;
580 menuModel.loadExtendedAttributes(menuIndex, {'x-ayatana-time': 'int64'});
590 objectName: "appointmentMenu"
591 property QtObject menuData: null
592 property var menuModel: menuFactory.menuModel
593 property int menuIndex: -1
594 property var extendedData: menuData && menuData.ext || undefined
596 readonly property date serverTime: new Date(getExtendedProperty(extendedData, "xAyatanaTime", 0) * 1000)
599 frequency: LiveTimer.Relative
600 relativeTime: appointmentItem.serverTime
601 onTrigger: appointmentItem.time = i18n.relativeDateTime(appointmentItem.serverTime)
604 text: menuData && menuData.label || ""
605 iconSource: menuData && menuData.icon || "image://theme/calendar"
606 time: i18n.relativeDateTime(serverTime)
607 eventColor: getExtendedProperty(extendedData, "xAyatanaColor", Qt.rgba(0.0, 0.0, 0.0, 0.0))
608 enabled: menuData && menuData.sensitive || false
609 highlightWhenPressed: false
611 onMenuModelChanged: {
614 onMenuIndexChanged: {
618 menuModel.activate(menuIndex);
621 function loadAttributes() {
622 if (!menuModel || menuIndex == -1) return;
623 menuModel.loadExtendedAttributes(menuIndex, {'x-ayatana-color': 'string',
624 'x-ayatana-time': 'int64'});
632 Menus.UserSessionMenu {
633 objectName: "userSessionMenu"
634 highlightWhenPressed: false
636 property QtObject menuData: null
637 property var menuModel: menuFactory.menuModel
638 property int menuIndex: -1
640 name: menuData && menuData.label || "" // label is the user's real name
641 iconSource: menuData && menuData.icon || ""
643 // would be better to compare with the logname but sadly the indicator doesn't expose that
644 active: DBusLomiriSessionService.RealName() !== "" ? DBusLomiriSessionService.RealName() == name
645 : DBusLomiriSessionService.UserName() == name
648 menuModel.activate(menuIndex);
658 objectName: "calendarMenu"
661 property QtObject menuData: null
662 property var menuModel: menuFactory.menuModel
663 property var actionState: menuData && menuData.actionState || null
664 property real calendarDay: getExtendedProperty(actionState, "calendar-day", 0)
665 property int menuIndex: -1
667 showWeekNumbers: getExtendedProperty(actionState, "show-week-numbers", false)
668 eventDays: getExtendedProperty(actionState, "appointment-days", [])
670 onCalendarDayChanged: {
671 if (calendarDay > 0) {
672 // This would trigger a selectionDateChanged signal, thus
673 // we've to avoid that the subsequent model activation
674 // would cause an infinite loop
675 modelUpdateConnections.enabled = false
676 currentDate = new Date(calendarDay * 1000)
677 modelUpdateConnections.enabled = true
682 id: modelUpdateConnections
683 property bool enabled: true
684 target: (enabled && calendarItem.visible) ? calendarItem : null
686 onSelectedDateChanged: {
687 menuModel.activate(menuIndex, selectedDate.getTime() / 1000 | 0)
698 objectName: "timezoneMenu"
700 property QtObject menuData: null
701 property var menuModel: menuFactory.menuModel
702 property int menuIndex: -1
703 property var extendedData: menuData && menuData.ext || undefined
704 readonly property string tz: getExtendedProperty(extendedData, "xAyatanaTimezone", "UTC")
705 property var updateTimer: Timer {
707 running: tzMenuItem.visible // only run when we're open
708 onTriggered: tzMenuItem.time = Utils.TimezoneFormatter.currentTimeInTimezone(tzMenuItem.tz)
711 city: menuData && menuData.label || ""
712 time: Utils.TimezoneFormatter.currentTimeInTimezone(tz)
713 enabled: menuData && menuData.sensitive || false
715 onMenuModelChanged: {
718 onMenuIndexChanged: {
722 tzActionGroup.setLocation.activate(tz);
727 busType: DBus.SessionBus
728 busName: "org.ayatana.indicator.datetime"
729 objectPath: "/org/ayatana/indicator/datetime"
731 property variant setLocation: action("set-location")
733 Component.onCompleted: tzActionGroup.start()
736 function loadAttributes() {
737 if (!menuModel || menuIndex == -1) return;
738 menuModel.loadExtendedAttributes(menuIndex, {'x-ayatana-timezone': 'string'});
747 objectName: "wifiSection"
748 property QtObject menuData: null
749 property var menuModel: menuFactory.menuModel
750 property int menuIndex: -1
751 property var extendedData: menuData && menuData.ext || undefined
753 text: menuData && menuData.label || ""
754 busy: getExtendedProperty(extendedData, "xCanonicalBusyAction", false)
756 onMenuModelChanged: {
759 onMenuIndexChanged: {
763 function loadAttributes() {
764 if (!menuModel || menuIndex == -1) return;
765 menuModel.loadExtendedAttributes(menuIndex, {'x-canonical-busy-action': 'bool'})
773 Menus.AccessPointMenu {
775 objectName: "accessPoint"
776 property QtObject menuData: null
777 property var menuModel: menuFactory.menuModel
778 property int menuIndex: -1
779 property var extendedData: menuData && menuData.ext || undefined
780 property bool serverChecked: menuData && menuData.isToggled || false
782 property var strengthAction: AyatanaMenuAction {
785 name: getExtendedProperty(extendedData, "xAyatanaWifiApStrengthAction", "")
788 text: menuData && menuData.label || ""
789 enabled: menuData && menuData.sensitive || false
790 active: serverChecked
791 secure: getExtendedProperty(extendedData, "xAyatanaWifiApIsSecure", false)
792 adHoc: getExtendedProperty(extendedData, "xAyatanaWifiApIsAdhoc", false)
794 if (strengthAction.valid) {
795 var state = strengthAction.state; // handle both int and uchar
796 // FIXME remove the special casing when we switch to indicator-network completely
797 if (typeof state == "string") {
798 return state.charCodeAt();
804 highlightWhenPressed: false
806 onMenuModelChanged: {
809 onMenuIndexChanged: {
813 function loadAttributes() {
814 if (!menuModel || menuIndex == -1) return;
815 menuModel.loadExtendedAttributes(menuIndex, {'x-ayatana-wifi-ap-is-adhoc': 'bool',
816 'x-ayatana-wifi-ap-is-secure': 'bool',
817 'x-ayatana-wifi-ap-strength-action': 'string'});
820 ServerPropertySynchroniser {
822 syncTimeout: Utils.Constants.indicatorValueTimeout
825 serverProperty: "serverChecked"
827 userProperty: "active"
828 userTrigger: "onTriggered"
830 onSyncTriggered: menuModel.activate(apItem.menuIndex)
837 Menus.ModemInfoItem {
838 objectName: "modemInfoItem"
839 property QtObject menuData: null
840 property var menuModel: menuFactory.menuModel
841 property int menuIndex: -1
842 property var extendedData: menuData && menuData.ext || undefined
843 highlightWhenPressed: false
845 property var statusLabelAction: AyatanaMenuAction {
848 name: getExtendedProperty(extendedData, "xLomiriModemStatusLabelAction", "")
850 statusText: statusLabelAction.valid ? statusLabelAction.state : ""
852 property var statusIconAction: AyatanaMenuAction {
855 name: getExtendedProperty(extendedData, "xLomiriModemStatusIconAction", "")
857 statusIcon: statusIconAction.valid ? statusIconAction.state : ""
859 property var connectivityIconAction: AyatanaMenuAction {
862 name: getExtendedProperty(extendedData, "xLomiriModemConnectivityIconAction", "")
864 connectivityIcon: connectivityIconAction.valid ? connectivityIconAction.state : ""
866 property var simIdentifierLabelAction: AyatanaMenuAction {
869 name: getExtendedProperty(extendedData, "xLomiriModemSimIdentifierLabelAction", "")
871 simIdentifierText: simIdentifierLabelAction.valid ? simIdentifierLabelAction.state : ""
873 property var roamingAction: AyatanaMenuAction {
876 name: getExtendedProperty(extendedData, "xLomiriModemRoamingAction", "")
878 roaming: roamingAction.valid ? roamingAction.state : false
880 property var unlockAction: AyatanaMenuAction {
883 name: getExtendedProperty(extendedData, "xLomiriModemLockedAction", "")
886 unlockAction.activate();
888 locked: unlockAction.valid ? unlockAction.state : false
890 onMenuModelChanged: {
893 onMenuIndexChanged: {
897 function loadAttributes() {
898 if (!menuModel || menuIndex == -1) return;
899 menuModel.loadExtendedAttributes(menuIndex, {'x-lomiri-modem-status-label-action': 'string',
900 'x-lomiri-modem-status-icon-action': 'string',
901 'x-lomiri-modem-connectivity-icon-action': 'string',
902 'x-lomiri-modem-sim-identifier-label-action': 'string',
903 'x-lomiri-modem-roaming-action': 'string',
904 'x-lomiri-modem-locked-action': 'string'});
912 MessageMenuItemFactory {
913 objectName: "messageItem"
914 menuModel: menuFactory.menuModel
921 Menus.GroupedMessageMenu {
922 objectName: "groupedMessage"
923 property QtObject menuData: null
924 property var menuModel: menuFactory.menuModel
925 property int menuIndex: -1
926 property var extendedData: menuData && menuData.ext || undefined
928 text: menuData && menuData.label || ""
929 iconSource: getExtendedProperty(extendedData, "icon", "image://theme/message")
930 count: menuData && menuData.actionState.length > 0 ? menuData.actionState[0] : "0"
931 enabled: menuData && menuData.sensitive || false
932 highlightWhenPressed: false
935 onMenuModelChanged: {
938 onMenuIndexChanged: {
942 menuModel.activate(menuIndex, true);
945 menuModel.activate(menuIndex, false);
948 function loadAttributes() {
949 if (!menuModel || menuIndex == -1) return;
950 menuModel.loadExtendedAttributes(modelIndex, {'icon': 'icon'});
958 Menus.MediaPlayerMenu {
959 objectName: "mediaPayerMenu"
960 property QtObject menuData: null
961 property var menuModel: menuFactory.menuModel
962 property int menuIndex: -1
963 property var actionState: menuData && menuData.actionState || undefined
964 property bool running: getExtendedProperty(actionState, "running", false)
966 playerIcon: menuData && menuData.icon || "image://theme/stock_music"
967 playerName: menuData && menuData.label || i18n.tr("Nothing is playing")
969 albumArt: getExtendedProperty(actionState, "art-url", "image://theme/stock_music")
970 song: getExtendedProperty(actionState, "title", "")
971 artist: getExtendedProperty(actionState, "artist", "")
972 album: getExtendedProperty(actionState, "album", "")
973 showTrack: running && (state == "Playing" || state == "Paused")
974 state: getExtendedProperty(actionState, "state", "")
975 enabled: menuData && menuData.sensitive || false
976 highlightWhenPressed: false
979 model.activate(modelIndex);
985 id: playbackItemMenu;
987 Menus.PlaybackItemMenu {
988 objectName: "playbackItemMenu"
989 property QtObject menuData: null
990 property var menuModel: menuFactory.menuModel
991 property int menuIndex: -1
992 property var extendedData: menuData && menuData.ext || undefined
994 property var playAction: AyatanaMenuAction {
997 name: getExtendedProperty(extendedData, "xAyatanaPlayAction", "")
999 property var nextAction: AyatanaMenuAction {
1002 name: getExtendedProperty(extendedData, "xAyatanaNextAction", "")
1004 property var previousAction: AyatanaMenuAction {
1007 name: getExtendedProperty(extendedData, "xAyatanaPreviousAction", "")
1010 playing: playAction.state === "Playing"
1011 canPlay: playAction.valid
1012 canGoNext: nextAction.valid
1013 canGoPrevious: previousAction.valid
1014 enabled: menuData && menuData.sensitive || false
1015 highlightWhenPressed: false
1018 playAction.activate();
1021 nextAction.activate();
1024 previousAction.activate();
1026 onMenuModelChanged: {
1029 onMenuIndexChanged: {
1033 function loadAttributes() {
1034 if (!menuModel || menuIndex == -1) return;
1035 menuModel.loadExtendedAttributes(modelIndex, {'x-ayatana-play-action': 'string',
1036 'x-ayatana-next-action': 'string',
1037 'x-ayatana-previous-action': 'string'});
1045 Menus.TransferMenu {
1046 objectName: "transferMenu"
1048 property QtObject menuData: null
1049 property var menuModel: menuFactory.menuModel
1050 property int menuIndex: -1
1051 property var extendedData: menuData && menuData.ext || undefined
1052 property var uid: getExtendedProperty(extendedData, "xAyatanaUid", undefined)
1054 text: menuData && menuData.label || ""
1055 iconSource: menuData && menuData.icon || "image://theme/transfer-none"
1057 enabled: menuData && menuData.sensitive || false
1058 highlightWhenPressed: false
1060 confirmRemoval: true
1065 busName: menuFactory.rootModel.busName
1066 objectPath: menuFactory.rootModel.actions["indicator"]
1068 property var activateAction: action("activate-transfer")
1069 property var cancelAction: action("cancel-transfer")
1070 property var transferStateAction: uid !== undefined ? action("transfer-state."+uid) : null
1072 Component.onCompleted: actionGroup.start()
1075 property var transferState: {
1076 if (actionGroup.transferStateAction === null) return undefined;
1077 return actionGroup.transferStateAction.valid ? actionGroup.transferStateAction.state : undefined
1080 property var runningState : transferState !== undefined ? transferState["state"] : undefined
1081 property var secondsLeft : transferState !== undefined ? transferState["seconds-left"] : undefined
1083 active: runningState !== undefined && runningState !== Menus.TransferState.Finished
1084 progress: transferState !== undefined ? transferState["percent"] : 0.0
1086 // TODO - Should be in the SDK
1087 property var timeRemaining: {
1088 if (secondsLeft === undefined) return undefined;
1091 var hours = Math.floor(secondsLeft / (60 * 60));
1092 var minutes = Math.floor(secondsLeft / 60) % 60;
1093 var seconds = secondsLeft % 60;
1095 remaining += i18n.tr("%1 hour", "%1 hours", hours).arg(hours)
1098 if (remaining != "") remaining += ", ";
1099 remaining += i18n.tr("%1 minute", "%1 minutes", minutes).arg(minutes)
1101 // don't include seconds if hours > 0
1102 if (hours == 0 && minutes < 5 && seconds > 0) {
1103 if (remaining != "") remaining += ", ";
1104 remaining += i18n.tr("%1 second", "%1 seconds", seconds).arg(seconds)
1106 if (remaining == "")
1107 remaining = i18n.tr("0 seconds");
1108 // Translators: String like "1 hour, 2 minutes, 3 seconds remaining"
1109 return i18n.tr("%1 remaining").arg(remaining);
1113 switch (runningState) {
1114 case Menus.TransferState.Queued:
1115 return i18n.tr("In queue…");
1116 case Menus.TransferState.Hashing:
1117 case Menus.TransferState.Processing:
1118 case Menus.TransferState.Running:
1119 return timeRemaining === undefined ? i18n.tr("Downloading") : timeRemaining;
1120 case Menus.TransferState.Paused:
1121 return i18n.tr("Paused, tap to resume");
1122 case Menus.TransferState.Canceled:
1123 return i18n.tr("Canceled");
1124 case Menus.TransferState.Finished:
1125 return i18n.tr("Finished");
1126 case Menus.TransferState.Error:
1127 return i18n.tr("Failed, tap to retry");
1132 onMenuModelChanged: {
1135 onMenuIndexChanged: {
1139 actionGroup.activateAction.activate(uid);
1142 actionGroup.cancelAction.activate(uid);
1145 function loadAttributes() {
1146 if (!menuModel || menuIndex == -1) return;
1147 menuModel.loadExtendedAttributes(menuIndex, {'x-ayatana-uid': 'string'});
1153 id: buttonSectionMenu;
1156 objectName: "buttonSectionMenu"
1157 property QtObject menuData: null
1158 property var menuModel: menuFactory.menuModel
1159 property int menuIndex: -1
1160 property var extendedData: menuData && menuData.ext || undefined
1162 iconSource: menuData && menuData.icon || ""
1163 enabled: menuData && menuData.sensitive || false
1164 highlightWhenPressed: false
1165 text: menuData && menuData.label || ""
1166 foregroundColor: theme.palette.normal.backgroundText
1167 buttonText: getExtendedProperty(extendedData, "xAyatanaExtraLabel", "")
1169 onMenuModelChanged: {
1172 onMenuIndexChanged: {
1175 function loadAttributes() {
1176 if (!menuModel || menuIndex == -1) return;
1177 menuModel.loadExtendedAttributes(menuIndex, {'x-ayatana-extra-label': 'string'});
1180 onButtonClicked: menuModel.activate(menuIndex);
1184 function load(modelData) {
1185 var component = getComponentForIndicatorEntryAction(modelData.action)
1186 if (component !== undefined) {
1190 component = getComponentForIndicatorEntryType(modelData.type)
1191 if (component !== undefined) {
1195 if (modelData.isCheck) {
1196 return checkableMenu;
1198 if (modelData.isRadio) {
1201 if (modelData.isSeparator) {
1202 return separatorMenu;
1204 if (modelData.action !== undefined && modelData.action.indexOf("settings") > -1) {
1205 // FIXME : At the moment, the indicators aren't using
1206 // org.ayatana.indicators.link for settings menu. Need to fudge it.
1209 return standardMenu;