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.12
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 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 onMenuModelChanged: {
891 loadAttributes();
892 }
893 onMenuIndexChanged: {
894 loadAttributes();
895 }
896
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'});
905 }
906 }
907 }
908
909 Component {
910 id: messageItem
911
912 MessageMenuItemFactory {
913 objectName: "messageItem"
914 menuModel: menuFactory.menuModel
915 }
916 }
917
918 Component {
919 id: groupedMessage
920
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
927
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
933 removable: true
934
935 onMenuModelChanged: {
936 loadAttributes();
937 }
938 onMenuIndexChanged: {
939 loadAttributes();
940 }
941 onClicked: {
942 menuModel.activate(menuIndex, true);
943 }
944 onDismissed: {
945 menuModel.activate(menuIndex, false);
946 }
947
948 function loadAttributes() {
949 if (!menuModel || menuIndex == -1) return;
950 menuModel.loadExtendedAttributes(modelIndex, {'icon': 'icon'});
951 }
952 }
953 }
954
955 Component {
956 id: mediaPayerMenu;
957
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)
965
966 playerIcon: menuData && menuData.icon || "image://theme/stock_music"
967 playerName: menuData && menuData.label || i18n.tr("Nothing is playing")
968
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
977
978 onTriggered: {
979 model.activate(modelIndex);
980 }
981 }
982 }
983
984 Component {
985 id: playbackItemMenu;
986
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
993
994 property var playAction: AyatanaMenuAction {
995 model: menuModel
996 index: menuIndex
997 name: getExtendedProperty(extendedData, "xAyatanaPlayAction", "")
998 }
999 property var nextAction: AyatanaMenuAction {
1000 model: menuModel
1001 index: menuIndex
1002 name: getExtendedProperty(extendedData, "xAyatanaNextAction", "")
1003 }
1004 property var previousAction: AyatanaMenuAction {
1005 model: menuModel
1006 index: menuIndex
1007 name: getExtendedProperty(extendedData, "xAyatanaPreviousAction", "")
1008 }
1009
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
1016
1017 onPlay: {
1018 playAction.activate();
1019 }
1020 onNext: {
1021 nextAction.activate();
1022 }
1023 onPrevious: {
1024 previousAction.activate();
1025 }
1026 onMenuModelChanged: {
1027 loadAttributes();
1028 }
1029 onMenuIndexChanged: {
1030 loadAttributes();
1031 }
1032
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'});
1038 }
1039 }
1040 }
1041
1042 Component {
1043 id: transferMenu
1044
1045 Menus.TransferMenu {
1046 objectName: "transferMenu"
1047 id: transfer
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)
1053
1054 text: menuData && menuData.label || ""
1055 iconSource: menuData && menuData.icon || "image://theme/transfer-none"
1056 maximum: 1.0
1057 enabled: menuData && menuData.sensitive || false
1058 highlightWhenPressed: false
1059 removable: true
1060 confirmRemoval: true
1061
1062 QDBusActionGroup {
1063 id: actionGroup
1064 busType: 1
1065 busName: menuFactory.rootModel.busName
1066 objectPath: menuFactory.rootModel.actions["indicator"]
1067
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
1071
1072 Component.onCompleted: actionGroup.start()
1073 }
1074
1075 property var transferState: {
1076 if (actionGroup.transferStateAction === null) return undefined;
1077 return actionGroup.transferStateAction.valid ? actionGroup.transferStateAction.state : undefined
1078 }
1079
1080 property var runningState : transferState !== undefined ? transferState["state"] : undefined
1081 property var secondsLeft : transferState !== undefined ? transferState["seconds-left"] : undefined
1082
1083 active: runningState !== undefined && runningState !== Menus.TransferState.Finished
1084 progress: transferState !== undefined ? transferState["percent"] : 0.0
1085
1086 // TODO - Should be in the SDK
1087 property var timeRemaining: {
1088 if (secondsLeft === undefined) return undefined;
1089
1090 var remaining = "";
1091 var hours = Math.floor(secondsLeft / (60 * 60));
1092 var minutes = Math.floor(secondsLeft / 60) % 60;
1093 var seconds = secondsLeft % 60;
1094 if (hours > 0) {
1095 remaining += i18n.tr("%1 hour", "%1 hours", hours).arg(hours)
1096 }
1097 if (minutes > 0) {
1098 if (remaining != "") remaining += ", ";
1099 remaining += i18n.tr("%1 minute", "%1 minutes", minutes).arg(minutes)
1100 }
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)
1105 }
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);
1110 }
1111
1112 stateText: {
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");
1128 }
1129 return "";
1130 }
1131
1132 onMenuModelChanged: {
1133 loadAttributes();
1134 }
1135 onMenuIndexChanged: {
1136 loadAttributes();
1137 }
1138 onTriggered: {
1139 actionGroup.activateAction.activate(uid);
1140 }
1141 onItemRemoved: {
1142 actionGroup.cancelAction.activate(uid);
1143 }
1144
1145 function loadAttributes() {
1146 if (!menuModel || menuIndex == -1) return;
1147 menuModel.loadExtendedAttributes(menuIndex, {'x-ayatana-uid': 'string'});
1148 }
1149 }
1150 }
1151
1152 Component {
1153 id: buttonSectionMenu;
1154
1155 Menus.ButtonMenu {
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
1161
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", "")
1168
1169 onMenuModelChanged: {
1170 loadAttributes();
1171 }
1172 onMenuIndexChanged: {
1173 loadAttributes();
1174 }
1175 function loadAttributes() {
1176 if (!menuModel || menuIndex == -1) return;
1177 menuModel.loadExtendedAttributes(menuIndex, {'x-ayatana-extra-label': 'string'});
1178 }
1179
1180 onButtonClicked: menuModel.activate(menuIndex);
1181 }
1182 }
1183
1184 function load(modelData) {
1185 var component = getComponentForIndicatorEntryAction(modelData.action)
1186 if (component !== undefined) {
1187 return component
1188 }
1189
1190 component = getComponentForIndicatorEntryType(modelData.type)
1191 if (component !== undefined) {
1192 return component;
1193 }
1194
1195 if (modelData.isCheck) {
1196 return checkableMenu;
1197 }
1198 if (modelData.isRadio) {
1199 return radioMenu;
1200 }
1201 if (modelData.isSeparator) {
1202 return separatorMenu;
1203 }
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.
1207 return linkMenu;
1208 }
1209 return standardMenu;
1210 }
1211}