Lomiri
Loading...
Searching...
No Matches
IndicatorMenuItemFactory.qml
1/*
2 * Copyright 2013-2016 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.15
18import QtQuick.Window 2.2
19import Lomiri.Settings.Menus 0.1 as Menus
20import Lomiri.Settings.Components 0.1
21import QMenuModel 1.0
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
27
28Item {
29 id: menuFactory
30
31 property string indicator
32 property var rootModel: null
33 property var menuModel: null
34
35 property var _userMap: null
36 readonly property var _typeToComponent: {
37 "default": {
38 "lomiri.widgets.systemsettings.tablet.volumecontrol" : sliderMenu,
39 "lomiri.widgets.systemsettings.tablet.switch" : switchMenu,
40
41 "com.canonical.indicator.button" : buttonMenu,
42 "com.canonical.indicator.div" : separatorMenu,
43 "com.canonical.indicator.section" : sectionMenu,
44 "com.canonical.indicator.progress" : progressMenu,
45 "com.canonical.indicator.slider" : sliderMenu,
46 "com.canonical.indicator.switch" : switchMenu,
47 "com.canonical.indicator.alarm" : alarmMenu,
48 "com.canonical.indicator.appointment" : appointmentMenu,
49 "com.canonical.indicator.transfer" : transferMenu,
50 "com.canonical.indicator.button-section" : buttonSectionMenu,
51 "com.canonical.indicator.link" : linkMenu,
52
53 "com.canonical.indicator.messages.messageitem" : messageItem,
54 "com.canonical.indicator.messages.sourceitem" : groupedMessage,
55
56 "com.canonical.lomiri.slider" : sliderMenu,
57 "com.canonical.lomiri.switch" : switchMenu,
58
59 "com.canonical.lomiri.media-player" : mediaPayerMenu,
60 "com.canonical.lomiri.playback-item" : playbackItemMenu,
61
62 "lomiri.widgets.systemsettings.tablet.wifisection" : wifiSection,
63 "lomiri.widgets.systemsettings.tablet.accesspoint" : accessPoint,
64 "com.lomiri.indicator.network.modeminfoitem" : modeminfoitem,
65
66 "com.canonical.indicator.calendar": calendarMenu,
67 "com.canonical.indicator.location": timezoneMenu,
68
69 "com.lomiri.indicator.transfer" : transferMenu,
70
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,
82
83 "org.ayatana.indicator.messages.messageitem" : messageItem,
84 "org.ayatana.indicator.messages.sourceitem" : groupedMessage,
85
86 "org.ayatana.indicator.slider" : sliderMenu,
87 "org.ayatana.indicator.switch" : switchMenu,
88
89 "org.ayatana.indicator.media-player" : mediaPayerMenu,
90 "org.ayatana.indicator.playback-item" : playbackItemMenu,
91
92 "org.ayatana.indicator.network.modeminfoitem" : modeminfoitem,
93
94 "org.ayatana.indicator.calendar": calendarMenu,
95 "org.ayatana.indicator.location": timezoneMenu,
96 "org.ayatana.indicator.level" : levelMenu,
97 },
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
102 },
103 "indicator-messages": {
104 "com.canonical.indicator.button": messagesButtonMenu
105 },
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
110 },
111 "ayatana-indicator-messages": {
112 "org.ayatana.indicator.button": messagesButtonMenu
113 }
114 }
115
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
122 },
123 "indicator-keyboard": {
124 "indicator.map": null,
125 "indicator.chart": null
126 },
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
132 },
133 "ayatana-indicator-keyboard": {
134 "indicator.map": null,
135 "indicator.chart": null
136 }
137 }
138
139 function getComponentForIndicatorEntryType(type) {
140 var component = undefined;
141 var map = _userMap || _typeToComponent
142 var indicatorComponents = map[indicator];
143
144 if (type === undefined || type === "") {
145 return component
146 }
147
148 if (indicatorComponents !== undefined) {
149 component = indicatorComponents[type];
150 }
151
152 if (component === undefined) {
153 component = map["default"][type];
154 }
155
156 if (component === undefined) {
157 console.debug("Don't know how to make " + type + " for " + indicator);
158 }
159
160 return component
161 }
162
163 function getComponentForIndicatorEntryAction(action) {
164 var component = undefined;
165 var indicatorFilter = _action_filter_map[indicator]
166
167 if (action === undefined || action === "") {
168 return component
169 }
170
171 if (indicatorFilter !== undefined) {
172 component = indicatorFilter[action];
173 }
174 return component
175 }
176
177 function getExtendedProperty(object, propertyName, defaultValue) {
178 if (object && object.hasOwnProperty(propertyName)) {
179 return object[propertyName];
180 }
181 return defaultValue;
182 }
183
184 Component {
185 id: separatorMenu;
186
187 Menus.SeparatorMenu {
188 objectName: "separatorMenu"
189 }
190 }
191
192 Component {
193 id: sliderMenu;
194
195 Menus.SliderMenu {
196 id: sliderItem
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)
203
204 text: menuData && menuData.label || ""
205 minIcon: getExtendedProperty(extendedData, "minIcon", "")
206 maxIcon: getExtendedProperty(extendedData, "maxIcon", "")
207
208 minimumValue: getExtendedProperty(extendedData, "minValue", 0.0)
209 maximumValue: {
210 var maximum = getExtendedProperty(extendedData, "maxValue", 1.0);
211 if (maximum <= minimumValue) {
212 return minimumValue + 1;
213 }
214 return maximum;
215 }
216 enabled: menuData && menuData.sensitive || false
217 highlightWhenPressed: false
218
219 onMenuModelChanged: {
220 loadAttributes();
221 }
222 onMenuIndexChanged: {
223 loadAttributes();
224 }
225
226 function loadAttributes() {
227 if (!menuModel || menuIndex == -1) return;
228 menuModel.loadExtendedAttributes(menuIndex, {'min-value': 'double',
229 'max-value': 'double',
230 'min-icon': 'icon',
231 'max-icon': 'icon',
232 'x-ayatana-sync-action': 'string'});
233 }
234
235 ServerPropertySynchroniser {
236 id: sliderPropertySync
237 objectName: "sync"
238 syncTimeout: Utils.Constants.indicatorValueTimeout
239 bufferedSyncTimeout: true
240 maximumWaitBufferInterval: 16
241
242 serverTarget: sliderItem
243 serverProperty: "serverValue"
244 userTarget: sliderItem
245 userProperty: "value"
246
247 onSyncTriggered: menuModel.changeState(menuIndex, value)
248 }
249
250 AyatanaMenuAction {
251 model: menuModel
252 index: menuIndex
253 name: getExtendedProperty(extendedData, "xAyatanaSyncAction", "")
254 onStateChanged: {
255 sliderPropertySync.reset();
256 sliderPropertySync.updateUserValue();
257 }
258 }
259 }
260 }
261
262 Component {
263 id: buttonMenu;
264
265 Menus.ButtonMenu {
266 objectName: "buttonMenu"
267 property QtObject menuData: null
268 property var menuModel: menuFactory.menuModel
269 property int menuIndex: -1
270
271 buttonText: menuData && menuData.label || ""
272 enabled: menuData && menuData.sensitive || false
273 highlightWhenPressed: false
274
275 onTriggered: {
276 menuModel.activate(menuIndex);
277 }
278 }
279 }
280
281 Component {
282 id: messagesButtonMenu;
283
284 Menus.BaseLayoutMenu {
285 objectName: "messagesButtonMenu"
286 property QtObject menuData: null
287 property var menuModel: menuFactory.menuModel
288 property int menuIndex: -1
289
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
296
297 onClicked: menuModel.activate(menuIndex);
298 }
299 }
300
301 Component {
302 id: sectionMenu;
303
304 Menus.SectionMenu {
305 objectName: "sectionMenu"
306 property QtObject menuData: null
307 property var menuIndex: undefined
308
309 text: menuData && menuData.label || ""
310 busy: false
311 }
312 }
313
314 Component {
315 id: progressMenu;
316
317 Menus.ProgressValueMenu {
318 objectName: "progressMenu"
319 property QtObject menuData: null
320 property int menuIndex: -1
321
322 text: menuData && menuData.label || ""
323 iconSource: menuData && menuData.icon || ""
324 value : menuData && menuData.actionState || 0.0
325 enabled: menuData && menuData.sensitive || false
326 }
327 }
328
329 Component {
330 id: levelMenu;
331
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
339
340 text: menuData && menuData.label || ""
341 iconSource: menuData && menuData.icon || ""
342 value : extendedData && extendedData.xAyatanaLevel || 0.0
343 enabled: menuData && menuData.sensitive || false
344
345 onMenuModelChanged: {
346 loadAttributes();
347 }
348 onMenuIndexChanged: {
349 loadAttributes();
350 }
351
352 function loadAttributes() {
353 if (!menuModel || menuIndex == -1) return;
354 menuModel.loadExtendedAttributes(menuIndex, {'x-ayatana-level': 'uint16'});
355 }
356 }
357 }
358
359 Component {
360 id: standardMenu;
361
362 Menus.StandardMenu {
363 objectName: "standardMenu"
364 property QtObject menuData: null
365 property int menuIndex: -1
366
367 text: menuData && menuData.label || ""
368 iconSource: menuData && menuData.icon || ""
369 enabled: menuData && menuData.sensitive || false
370 highlightWhenPressed: false
371
372 onTriggered: {
373 menuModel.activate(menuIndex);
374 }
375 }
376 }
377
378 Component {
379 id: linkMenu;
380
381 Menus.BaseLayoutMenu {
382 objectName: "linkMenu"
383 property QtObject menuData: null
384 property int menuIndex: -1
385
386 text: menuData && menuData.label || ""
387 enabled: menuData && menuData.sensitive || false
388 backColor: Qt.rgba(1,1,1,0.07)
389 highlightWhenPressed: false
390
391 onTriggered: {
392 menuModel.activate(menuIndex);
393 }
394
395 slots: Icon {
396 source: {
397 if (menuData) {
398 if (menuData.icon && menuData.icon != "") {
399 return menuData.icon
400 } else if (menuData.action.indexOf("settings") > -1) {
401 return "image://theme/settings"
402 }
403 }
404 return ""
405 }
406 height: units.gu(3)
407 width: height
408 color: theme.palette.normal.backgroundText
409 SlotsLayout.position: SlotsLayout.Trailing
410 }
411 }
412 }
413
414 Component {
415 id: checkableMenu;
416
417 Menus.CheckableMenu {
418 id: checkItem
419 objectName: "checkableMenu"
420 property QtObject menuData: null
421 property int menuIndex: -1
422 property bool serverChecked: menuData && menuData.isToggled || false
423
424 text: menuData && menuData.label || ""
425 enabled: menuData && menuData.sensitive || false
426 checked: serverChecked
427 highlightWhenPressed: false
428
429 ServerPropertySynchroniser {
430 objectName: "sync"
431 syncTimeout: Utils.Constants.indicatorValueTimeout
432
433 serverTarget: checkItem
434 serverProperty: "serverChecked"
435 userTarget: checkItem
436 userProperty: "checked"
437
438 onSyncTriggered: menuModel.activate(checkItem.menuIndex)
439 }
440 }
441 }
442
443 Component {
444 id: radioMenu;
445
446 Menus.RadioMenu {
447 id: radioItem
448 objectName: "radioMenu"
449 property QtObject menuData: null
450 property int menuIndex: -1
451 property bool serverChecked: menuData && menuData.isToggled || false
452
453 text: menuData && menuData.label || ""
454 enabled: menuData && menuData.sensitive || false
455 checked: serverChecked
456 highlightWhenPressed: false
457
458 ServerPropertySynchroniser {
459 objectName: "sync"
460 syncTimeout: Utils.Constants.indicatorValueTimeout
461
462 serverTarget: radioItem
463 serverProperty: "serverChecked"
464 userTarget: radioItem
465 userProperty: "checked"
466
467 onSyncTriggered: menuModel.activate(radioItem.menuIndex)
468 }
469 }
470 }
471
472 Component {
473 id: switchMenu;
474
475 Menus.SwitchMenu {
476 id: switchItem
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
483
484 text: menuData && menuData.label || ""
485 iconSource: menuData && menuData.icon || ""
486 enabled: menuData && menuData.sensitive || false
487 checked: serverChecked
488 highlightWhenPressed: false
489
490 property var subtitleAction: AyatanaMenuAction {
491 model: menuModel
492 index: menuIndex
493 name: getExtendedProperty(extendedData, "xAyatanaSubtitleAction", "")
494 }
495 subtitle.text: subtitleAction.valid ? subtitleAction.state : ""
496
497 onMenuModelChanged: {
498 loadAttributes();
499 }
500 onMenuIndexChanged: {
501 loadAttributes();
502 }
503
504 function loadAttributes() {
505 if (!menuModel || menuIndex == -1) return;
506 menuModel.loadExtendedAttributes(menuIndex, {
507 'x-ayatana-subtitle-action': 'string',
508 'target': 'bool',
509 });
510 }
511
512 ServerPropertySynchroniser {
513 objectName: "sync"
514 syncTimeout: Utils.Constants.indicatorValueTimeout
515
516 serverTarget: switchItem
517 serverProperty: "serverChecked"
518 userTarget: switchItem
519 userProperty: "checked"
520
521 onSyncTriggered: {
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
529 *
530 * If activate() requires a parameter but menu doesn't
531 * specify a target, the menu will be broken in a different
532 * way anyway.
533 */
534 if (extendedData.hasOwnProperty('target')) {
535 menuModel.activate(switchItem.menuIndex, switchItem.checked);
536 } else {
537 menuModel.activate(switchItem.menuIndex);
538 }
539 }
540 }
541 }
542 }
543
544 Component {
545 id: alarmMenu;
546
547 Menus.EventMenu {
548 id: alarmItem
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
554
555 readonly property date serverTime: new Date(getExtendedProperty(extendedData, "xAyatanaTime", 0) * 1000)
556 LiveTimer {
557 frequency: LiveTimer.Relative
558 relativeTime: alarmItem.serverTime
559 onTrigger: alarmItem.time = i18n.relativeDateTime(alarmItem.serverTime)
560 }
561
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
567
568 onMenuModelChanged: {
569 loadAttributes();
570 }
571 onMenuIndexChanged: {
572 loadAttributes();
573 }
574 onTriggered: {
575 menuModel.activate(menuIndex);
576 }
577
578 function loadAttributes() {
579 if (!menuModel || menuIndex == -1) return;
580 menuModel.loadExtendedAttributes(menuIndex, {'x-ayatana-time': 'int64'});
581 }
582 }
583 }
584
585 Component {
586 id: appointmentMenu;
587
588 Menus.EventMenu {
589 id: appointmentItem
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
595
596 readonly property date serverTime: new Date(getExtendedProperty(extendedData, "xAyatanaTime", 0) * 1000)
597
598 LiveTimer {
599 frequency: LiveTimer.Relative
600 relativeTime: appointmentItem.serverTime
601 onTrigger: appointmentItem.time = i18n.relativeDateTime(appointmentItem.serverTime)
602 }
603
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
610
611 onMenuModelChanged: {
612 loadAttributes();
613 }
614 onMenuIndexChanged: {
615 loadAttributes();
616 }
617 onTriggered: {
618 menuModel.activate(menuIndex);
619 }
620
621 function loadAttributes() {
622 if (!menuModel || menuIndex == -1) return;
623 menuModel.loadExtendedAttributes(menuIndex, {'x-ayatana-color': 'string',
624 'x-ayatana-time': 'int64'});
625 }
626 }
627 }
628
629 Component {
630 id: userMenuItem
631
632 Menus.UserSessionMenu {
633 objectName: "userSessionMenu"
634 highlightWhenPressed: false
635
636 property QtObject menuData: null
637 property var menuModel: menuFactory.menuModel
638 property int menuIndex: -1
639
640 name: menuData && menuData.label || "" // label is the user's real name
641 iconSource: menuData && menuData.icon || ""
642
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
646
647 onTriggered: {
648 menuModel.activate(menuIndex);
649 }
650 }
651 }
652
653 Component {
654 id: calendarMenu
655
656 Menus.CalendarMenu {
657 id: calendarItem
658 objectName: "calendarMenu"
659 focus: true
660
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
666
667 showWeekNumbers: getExtendedProperty(actionState, "show-week-numbers", false)
668 eventDays: getExtendedProperty(actionState, "appointment-days", [])
669
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
678 }
679 }
680
681 Connections {
682 id: modelUpdateConnections
683 property bool enabled: true
684 target: (enabled && calendarItem.visible) ? calendarItem : null
685
686 function onSelectedDateChanged() {
687 menuModel.activate(menuIndex, selectedDate.getTime() / 1000 | 0)
688 }
689 }
690 }
691 }
692
693 Component {
694 id: timezoneMenu
695
696 Menus.TimeZoneMenu {
697 id: tzMenuItem
698 objectName: "timezoneMenu"
699
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 {
706 repeat: true
707 running: tzMenuItem.visible // only run when we're open
708 onTriggered: tzMenuItem.time = Utils.TimezoneFormatter.currentTimeInTimezone(tzMenuItem.tz)
709 }
710
711 city: menuData && menuData.label || ""
712 time: Utils.TimezoneFormatter.currentTimeInTimezone(tz)
713 enabled: menuData && menuData.sensitive || false
714
715 onMenuModelChanged: {
716 loadAttributes();
717 }
718 onMenuIndexChanged: {
719 loadAttributes();
720 }
721 onTriggered: {
722 tzActionGroup.setLocation.activate(tz);
723 }
724
725 QDBusActionGroup {
726 id: tzActionGroup
727 busType: DBus.SessionBus
728 busName: "org.ayatana.indicator.datetime"
729 objectPath: "/org/ayatana/indicator/datetime"
730
731 property variant setLocation: action("set-location")
732
733 Component.onCompleted: tzActionGroup.start()
734 }
735
736 function loadAttributes() {
737 if (!menuModel || menuIndex == -1) return;
738 menuModel.loadExtendedAttributes(menuIndex, {'x-ayatana-timezone': 'string'});
739 }
740 }
741 }
742
743 Component {
744 id: wifiSection;
745
746 Menus.SectionMenu {
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
752
753 text: menuData && menuData.label || ""
754 busy: getExtendedProperty(extendedData, "xCanonicalBusyAction", false)
755
756 onMenuModelChanged: {
757 loadAttributes();
758 }
759 onMenuIndexChanged: {
760 loadAttributes();
761 }
762
763 function loadAttributes() {
764 if (!menuModel || menuIndex == -1) return;
765 menuModel.loadExtendedAttributes(menuIndex, {'x-canonical-busy-action': 'bool'})
766 }
767 }
768 }
769
770 Component {
771 id: accessPoint;
772
773 Menus.AccessPointMenu {
774 id: apItem
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
781
782 property var strengthAction: AyatanaMenuAction {
783 model: menuModel
784 index: menuIndex
785 name: getExtendedProperty(extendedData, "xAyatanaWifiApStrengthAction", "")
786 }
787
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)
793 signalStrength: {
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();
799 }
800 return state;
801 }
802 return 0;
803 }
804 highlightWhenPressed: false
805
806 onMenuModelChanged: {
807 loadAttributes();
808 }
809 onMenuIndexChanged: {
810 loadAttributes();
811 }
812
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'});
818 }
819
820 ServerPropertySynchroniser {
821 objectName: "sync"
822 syncTimeout: Utils.Constants.indicatorValueTimeout
823
824 serverTarget: apItem
825 serverProperty: "serverChecked"
826 userTarget: apItem
827 userProperty: "active"
828 userTrigger: "onTriggered"
829
830 onSyncTriggered: menuModel.activate(apItem.menuIndex)
831 }
832 }
833 }
834
835 Component {
836 id: modeminfoitem;
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
844
845 property var statusLabelAction: AyatanaMenuAction {
846 model: menuModel
847 index: menuIndex
848 name: getExtendedProperty(extendedData, "xLomiriModemStatusLabelAction", "")
849 }
850 statusText: statusLabelAction.valid ? statusLabelAction.state : ""
851
852 property var statusIconAction: AyatanaMenuAction {
853 model: menuModel
854 index: menuIndex
855 name: getExtendedProperty(extendedData, "xLomiriModemStatusIconAction", "")
856 }
857 statusIcon: statusIconAction.valid ? statusIconAction.state : ""
858
859 property var connectivityIconAction: AyatanaMenuAction {
860 model: menuModel
861 index: menuIndex
862 name: getExtendedProperty(extendedData, "xLomiriModemConnectivityIconAction", "")
863 }
864 connectivityIcon: connectivityIconAction.valid ? connectivityIconAction.state : ""
865
866 property var simIdentifierLabelAction: AyatanaMenuAction {
867 model: menuModel
868 index: menuIndex
869 name: getExtendedProperty(extendedData, "xLomiriModemSimIdentifierLabelAction", "")
870 }
871 simIdentifierText: simIdentifierLabelAction.valid ? simIdentifierLabelAction.state : ""
872
873 property var roamingAction: AyatanaMenuAction {
874 model: menuModel
875 index: menuIndex
876 name: getExtendedProperty(extendedData, "xLomiriModemRoamingAction", "")
877 }
878 roaming: roamingAction.valid ? roamingAction.state : false
879
880 property var unlockAction: AyatanaMenuAction {
881 model: menuModel
882 index: menuIndex
883 name: getExtendedProperty(extendedData, "xLomiriModemLockedAction", "")
884 }
885 onUnlock: {
886 unlockAction.activate();
887 }
888 locked: unlockAction.valid ? unlockAction.state : false
889
890 property var imsIconAction: AyatanaMenuAction {
891 model: menuModel
892 index: menuIndex
893 name: getExtendedProperty(extendedData, "xLomiriModemImsIconAction", "")
894 }
895 imsIcon: imsIconAction.valid ? imsIconAction.state : ""
896
897 onMenuModelChanged: {
898 loadAttributes();
899 }
900 onMenuIndexChanged: {
901 loadAttributes();
902 }
903
904 function loadAttributes() {
905 if (!menuModel || menuIndex == -1) return;
906 menuModel.loadExtendedAttributes(menuIndex, {'x-lomiri-modem-status-label-action': 'string',
907 'x-lomiri-modem-status-icon-action': 'string',
908 'x-lomiri-modem-connectivity-icon-action': 'string',
909 'x-lomiri-modem-sim-identifier-label-action': 'string',
910 'x-lomiri-modem-roaming-action': 'string',
911 'x-lomiri-modem-locked-action': 'string',
912 'x-lomiri-modem-ims-icon-action': 'string'});
913 }
914 }
915 }
916
917 Component {
918 id: messageItem
919
920 MessageMenuItemFactory {
921 objectName: "messageItem"
922 menuModel: menuFactory.menuModel
923 }
924 }
925
926 Component {
927 id: groupedMessage
928
929 Menus.GroupedMessageMenu {
930 objectName: "groupedMessage"
931 property QtObject menuData: null
932 property var menuModel: menuFactory.menuModel
933 property int menuIndex: -1
934 property var extendedData: menuData && menuData.ext || undefined
935
936 text: menuData && menuData.label || ""
937 iconSource: getExtendedProperty(extendedData, "icon", "image://theme/message")
938 count: menuData && menuData.actionState.length > 0 ? menuData.actionState[0] : "0"
939 enabled: menuData && menuData.sensitive || false
940 highlightWhenPressed: false
941 removable: true
942
943 onMenuModelChanged: {
944 loadAttributes();
945 }
946 onMenuIndexChanged: {
947 loadAttributes();
948 }
949 onClicked: {
950 menuModel.activate(menuIndex, true);
951 }
952 onDismissed: {
953 menuModel.activate(menuIndex, false);
954 }
955
956 function loadAttributes() {
957 if (!menuModel || menuIndex == -1) return;
958 menuModel.loadExtendedAttributes(modelIndex, {'icon': 'icon'});
959 }
960 }
961 }
962
963 Component {
964 id: mediaPayerMenu;
965
966 Menus.MediaPlayerMenu {
967 objectName: "mediaPayerMenu"
968 property QtObject menuData: null
969 property var menuModel: menuFactory.menuModel
970 property int menuIndex: -1
971 property var actionState: menuData && menuData.actionState || undefined
972 property bool running: getExtendedProperty(actionState, "running", false)
973
974 playerIcon: menuData && menuData.icon || "image://theme/stock_music"
975 playerName: menuData && menuData.label || i18n.tr("Nothing is playing")
976
977 albumArt: getExtendedProperty(actionState, "art-url", "image://theme/stock_music")
978 song: getExtendedProperty(actionState, "title", "")
979 artist: getExtendedProperty(actionState, "artist", "")
980 album: getExtendedProperty(actionState, "album", "")
981 showTrack: running && (state == "Playing" || state == "Paused")
982 state: getExtendedProperty(actionState, "state", "")
983 enabled: menuData && menuData.sensitive || false
984 highlightWhenPressed: false
985
986 onTriggered: {
987 model.activate(modelIndex);
988 }
989 }
990 }
991
992 Component {
993 id: playbackItemMenu;
994
995 Menus.PlaybackItemMenu {
996 objectName: "playbackItemMenu"
997 property QtObject menuData: null
998 property var menuModel: menuFactory.menuModel
999 property int menuIndex: -1
1000 property var extendedData: menuData && menuData.ext || undefined
1001
1002 property var playAction: AyatanaMenuAction {
1003 model: menuModel
1004 index: menuIndex
1005 name: getExtendedProperty(extendedData, "xAyatanaPlayAction", "")
1006 }
1007 property var nextAction: AyatanaMenuAction {
1008 model: menuModel
1009 index: menuIndex
1010 name: getExtendedProperty(extendedData, "xAyatanaNextAction", "")
1011 }
1012 property var previousAction: AyatanaMenuAction {
1013 model: menuModel
1014 index: menuIndex
1015 name: getExtendedProperty(extendedData, "xAyatanaPreviousAction", "")
1016 }
1017
1018 playing: playAction.state === "Playing"
1019 canPlay: playAction.valid
1020 canGoNext: nextAction.valid
1021 canGoPrevious: previousAction.valid
1022 enabled: menuData && menuData.sensitive || false
1023 highlightWhenPressed: false
1024
1025 onPlay: {
1026 playAction.activate();
1027 }
1028 onNext: {
1029 nextAction.activate();
1030 }
1031 onPrevious: {
1032 previousAction.activate();
1033 }
1034 onMenuModelChanged: {
1035 loadAttributes();
1036 }
1037 onMenuIndexChanged: {
1038 loadAttributes();
1039 }
1040
1041 function loadAttributes() {
1042 if (!menuModel || menuIndex == -1) return;
1043 menuModel.loadExtendedAttributes(modelIndex, {'x-ayatana-play-action': 'string',
1044 'x-ayatana-next-action': 'string',
1045 'x-ayatana-previous-action': 'string'});
1046 }
1047 }
1048 }
1049
1050 Component {
1051 id: transferMenu
1052
1053 Menus.TransferMenu {
1054 objectName: "transferMenu"
1055 id: transfer
1056 property QtObject menuData: null
1057 property var menuModel: menuFactory.menuModel
1058 property int menuIndex: -1
1059 property var extendedData: menuData && menuData.ext || undefined
1060 property var uid: getExtendedProperty(extendedData, "xAyatanaUid", undefined)
1061
1062 text: menuData && menuData.label || ""
1063 iconSource: menuData && menuData.icon || "image://theme/transfer-none"
1064 maximum: 1.0
1065 enabled: menuData && menuData.sensitive || false
1066 highlightWhenPressed: false
1067 removable: true
1068 confirmRemoval: true
1069
1070 QDBusActionGroup {
1071 id: actionGroup
1072 busType: 1
1073 busName: menuFactory.rootModel.busName
1074 objectPath: menuFactory.rootModel.actions["indicator"]
1075
1076 property var activateAction: action("activate-transfer")
1077 property var cancelAction: action("cancel-transfer")
1078 property var transferStateAction: uid !== undefined ? action("transfer-state."+uid) : null
1079
1080 Component.onCompleted: actionGroup.start()
1081 }
1082
1083 property var transferState: {
1084 if (actionGroup.transferStateAction === null) return undefined;
1085 return actionGroup.transferStateAction.valid ? actionGroup.transferStateAction.state : undefined
1086 }
1087
1088 property var runningState : transferState !== undefined ? transferState["state"] : undefined
1089 property var runningStateLabel : transferState !== undefined ? transferState["state-label"] : undefined
1090 property var secondsLeft : transferState !== undefined ? transferState["seconds-left"] : undefined
1091
1092 active: runningState !== undefined && runningState !== Menus.TransferState.Finished
1093 progress: transferState !== undefined ? transferState["percent"] : 0.0
1094
1095 // TODO - Should be in the SDK
1096 property var timeRemaining: {
1097 if (secondsLeft === undefined) return undefined;
1098
1099 var remaining = "";
1100 var hours = Math.floor(secondsLeft / (60 * 60));
1101 var minutes = Math.floor(secondsLeft / 60) % 60;
1102 var seconds = secondsLeft % 60;
1103 if (hours > 0) {
1104 remaining += i18n.tr("%1 hour", "%1 hours", hours).arg(hours)
1105 }
1106 if (minutes > 0) {
1107 if (remaining != "") remaining += ", ";
1108 remaining += i18n.tr("%1 minute", "%1 minutes", minutes).arg(minutes)
1109 }
1110 // don't include seconds if hours > 0
1111 if (hours == 0 && minutes < 5 && seconds > 0) {
1112 if (remaining != "") remaining += ", ";
1113 remaining += i18n.tr("%1 second", "%1 seconds", seconds).arg(seconds)
1114 }
1115 if (remaining == "")
1116 remaining = i18n.tr("0 seconds");
1117 // Translators: String like "1 hour, 2 minutes, 3 seconds remaining"
1118 return i18n.tr("%1 remaining").arg(remaining);
1119 }
1120
1121 stateText: {
1122
1123 if (runningStateLabel !== undefined && runningStateLabel !== "") {
1124 return runningStateLabel;
1125 }
1126
1127 switch (runningState) {
1128 case Menus.TransferState.Queued:
1129 return i18n.tr("In queue…");
1130 case Menus.TransferState.Hashing:
1131 case Menus.TransferState.Processing:
1132 case Menus.TransferState.Running:
1133 return timeRemaining === undefined ? i18n.tr("Downloading") : timeRemaining;
1134 case Menus.TransferState.Paused:
1135 return i18n.tr("Paused, tap to resume");
1136 case Menus.TransferState.Canceled:
1137 return i18n.tr("Canceled");
1138 case Menus.TransferState.Finished:
1139 return i18n.tr("Finished");
1140 case Menus.TransferState.Error:
1141 return i18n.tr("Failed, tap to retry");
1142 }
1143 return "";
1144 }
1145
1146 onMenuModelChanged: {
1147 loadAttributes();
1148 }
1149 onMenuIndexChanged: {
1150 loadAttributes();
1151 }
1152 onTriggered: {
1153 actionGroup.activateAction.activate(uid);
1154 }
1155 onItemRemoved: {
1156 actionGroup.cancelAction.activate(uid);
1157 }
1158
1159 function loadAttributes() {
1160 if (!menuModel || menuIndex == -1) return;
1161 menuModel.loadExtendedAttributes(menuIndex, {'x-ayatana-uid': 'string'});
1162 }
1163 }
1164 }
1165
1166 Component {
1167 id: buttonSectionMenu;
1168
1169 Menus.ButtonMenu {
1170 objectName: "buttonSectionMenu"
1171 property QtObject menuData: null
1172 property var menuModel: menuFactory.menuModel
1173 property int menuIndex: -1
1174 property var extendedData: menuData && menuData.ext || undefined
1175
1176 iconSource: menuData && menuData.icon || ""
1177 enabled: menuData && menuData.sensitive || false
1178 highlightWhenPressed: false
1179 text: menuData && menuData.label || ""
1180 foregroundColor: theme.palette.normal.backgroundText
1181 buttonText: getExtendedProperty(extendedData, "xAyatanaExtraLabel", "")
1182
1183 onMenuModelChanged: {
1184 loadAttributes();
1185 }
1186 onMenuIndexChanged: {
1187 loadAttributes();
1188 }
1189 function loadAttributes() {
1190 if (!menuModel || menuIndex == -1) return;
1191 menuModel.loadExtendedAttributes(menuIndex, {'x-ayatana-extra-label': 'string'});
1192 }
1193
1194 onButtonClicked: menuModel.activate(menuIndex);
1195 }
1196 }
1197
1198 function load(modelData) {
1199 var component = getComponentForIndicatorEntryAction(modelData.action)
1200 if (component !== undefined) {
1201 return component
1202 }
1203
1204 component = getComponentForIndicatorEntryType(modelData.type)
1205 if (component !== undefined) {
1206 return component;
1207 }
1208
1209 if (modelData.isCheck) {
1210 return checkableMenu;
1211 }
1212 if (modelData.isRadio) {
1213 return radioMenu;
1214 }
1215 if (modelData.isSeparator) {
1216 return separatorMenu;
1217 }
1218 if (modelData.action !== undefined && modelData.action.indexOf("settings") > -1) {
1219 // FIXME : At the moment, the indicators aren't using
1220 // org.ayatana.indicators.link for settings menu. Need to fudge it.
1221 return linkMenu;
1222 }
1223 return standardMenu;
1224 }
1225}