Lomiri
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 },
97 "indicator-session": {
98 "indicator.user-menu-item": Platform.isPC ? userMenuItem : null,
99 "indicator.guest-menu-item": Platform.isPC ? userMenuItem : null,
100 "com.canonical.indicator.switch": Math.min(Screen.width, Screen.height) > units.gu(60) ? switchMenu : null // Desktop mode switch
101 },
102 "indicator-messages": {
103 "com.canonical.indicator.button": messagesButtonMenu
104 },
105 "ayatana-indicator-session": {
106 "org.ayatana.indicator.user-menu-item": Platform.isPC ? userMenuItem : null,
107 "org.ayatana.indicator.guest-menu-item": Platform.isPC ? userMenuItem : null,
108 "org.ayatana.indicator.switch": Math.min(Screen.width, Screen.height) > units.gu(60) ? switchMenu : null // Desktop mode switch
109 },
110 "ayatana-indicator-messages": {
111 "org.ayatana.indicator.button": messagesButtonMenu
112 }
113 }
114
115 readonly property var _action_filter_map: {
116 "indicator-session": {
117 "indicator.logout": Platform.isPC ? undefined : null,
118 "indicator.suspend": Platform.isPC ? undefined : null,
119 "indicator.hibernate": Platform.isPC ? undefined : null,
120 "indicator.reboot": Platform.isPC ? undefined : null
121 },
122 "indicator-keyboard": {
123 "indicator.map": null,
124 "indicator.chart": null
125 },
126 "ayatana-indicator-session": {
127 "indicator.logout": Platform.isPC ? undefined : null,
128 "indicator.suspend": Platform.isPC ? undefined : null,
129 "indicator.hibernate": Platform.isPC ? undefined : null,
130 "indicator.reboot": Platform.isPC ? undefined : null
131 },
132 "ayatana-indicator-keyboard": {
133 "indicator.map": null,
134 "indicator.chart": null
135 }
136 }
137
138 function getComponentForIndicatorEntryType(type) {
139 var component = undefined;
140 var map = _userMap || _typeToComponent
141 var indicatorComponents = map[indicator];
142
143 if (type === undefined || type === "") {
144 return component
145 }
146
147 if (indicatorComponents !== undefined) {
148 component = indicatorComponents[type];
149 }
150
151 if (component === undefined) {
152 component = map["default"][type];
153 }
154
155 if (component === undefined) {
156 console.debug("Don't know how to make " + type + " for " + indicator);
157 }
158
159 return component
160 }
161
162 function getComponentForIndicatorEntryAction(action) {
163 var component = undefined;
164 var indicatorFilter = _action_filter_map[indicator]
165
166 if (action === undefined || action === "") {
167 return component
168 }
169
170 if (indicatorFilter !== undefined) {
171 component = indicatorFilter[action];
172 }
173 return component
174 }
175
176 function getExtendedProperty(object, propertyName, defaultValue) {
177 if (object && object.hasOwnProperty(propertyName)) {
178 return object[propertyName];
179 }
180 return defaultValue;
181 }
182
183 Component {
184 id: separatorMenu;
185
186 Menus.SeparatorMenu {
187 objectName: "separatorMenu"
188 }
189 }
190
191 Component {
192 id: sliderMenu;
193
194 Menus.SliderMenu {
195 id: sliderItem
196 objectName: "sliderMenu"
197 property QtObject menuData: null
198 property var menuModel: menuFactory.menuModel
199 property int menuIndex: -1
200 property var extendedData: menuData && menuData.ext || undefined
201 property var serverValue: getExtendedProperty(menuData, "actionState", undefined)
202
203 text: menuData && menuData.label || ""
204 minIcon: getExtendedProperty(extendedData, "minIcon", "")
205 maxIcon: getExtendedProperty(extendedData, "maxIcon", "")
206
207 minimumValue: getExtendedProperty(extendedData, "minValue", 0.0)
208 maximumValue: {
209 var maximum = getExtendedProperty(extendedData, "maxValue", 1.0);
210 if (maximum <= minimumValue) {
211 return minimumValue + 1;
212 }
213 return maximum;
214 }
215 enabled: menuData && menuData.sensitive || false
216 highlightWhenPressed: false
217
218 onMenuModelChanged: {
219 loadAttributes();
220 }
221 onMenuIndexChanged: {
222 loadAttributes();
223 }
224
225 function loadAttributes() {
226 if (!menuModel || menuIndex == -1) return;
227 menuModel.loadExtendedAttributes(menuIndex, {'min-value': 'double',
228 'max-value': 'double',
229 'min-icon': 'icon',
230 'max-icon': 'icon',
231 'x-ayatana-sync-action': 'string'});
232 }
233
234 ServerPropertySynchroniser {
235 id: sliderPropertySync
236 objectName: "sync"
237 syncTimeout: Utils.Constants.indicatorValueTimeout
238 bufferedSyncTimeout: true
239 maximumWaitBufferInterval: 16
240
241 serverTarget: sliderItem
242 serverProperty: "serverValue"
243 userTarget: sliderItem
244 userProperty: "value"
245
246 onSyncTriggered: menuModel.changeState(menuIndex, value)
247 }
248
249 AyatanaMenuAction {
250 model: menuModel
251 index: menuIndex
252 name: getExtendedProperty(extendedData, "xAyatanaSyncAction", "")
253 onStateChanged: {
254 sliderPropertySync.reset();
255 sliderPropertySync.updateUserValue();
256 }
257 }
258 }
259 }
260
261 Component {
262 id: buttonMenu;
263
264 Menus.ButtonMenu {
265 objectName: "buttonMenu"
266 property QtObject menuData: null
267 property var menuModel: menuFactory.menuModel
268 property int menuIndex: -1
269
270 buttonText: menuData && menuData.label || ""
271 enabled: menuData && menuData.sensitive || false
272 highlightWhenPressed: false
273
274 onTriggered: {
275 menuModel.activate(menuIndex);
276 }
277 }
278 }
279
280 Component {
281 id: messagesButtonMenu;
282
283 Menus.BaseLayoutMenu {
284 objectName: "messagesButtonMenu"
285 property QtObject menuData: null
286 property var menuModel: menuFactory.menuModel
287 property int menuIndex: -1
288
289 highlightWhenPressed: false
290 enabled: menuData && menuData.sensitive || false
291 text: menuData && menuData.label || ""
292 title.color: theme.palette.selected.backgroundText
293 title.horizontalAlignment: Text.AlignHCenter
294 title.font.bold: true
295
296 onClicked: menuModel.activate(menuIndex);
297 }
298 }
299
300 Component {
301 id: sectionMenu;
302
303 Menus.SectionMenu {
304 objectName: "sectionMenu"
305 property QtObject menuData: null
306 property var menuIndex: undefined
307
308 text: menuData && menuData.label || ""
309 busy: false
310 }
311 }
312
313 Component {
314 id: progressMenu;
315
316 Menus.ProgressValueMenu {
317 objectName: "progressMenu"
318 property QtObject menuData: null
319 property int menuIndex: -1
320
321 text: menuData && menuData.label || ""
322 iconSource: menuData && menuData.icon || ""
323 value : menuData && menuData.actionState || 0.0
324 enabled: menuData && menuData.sensitive || false
325 }
326 }
327
328 Component {
329 id: standardMenu;
330
331 Menus.StandardMenu {
332 objectName: "standardMenu"
333 property QtObject menuData: null
334 property int menuIndex: -1
335
336 text: menuData && menuData.label || ""
337 iconSource: menuData && menuData.icon || ""
338 enabled: menuData && menuData.sensitive || false
339 highlightWhenPressed: false
340
341 onTriggered: {
342 menuModel.activate(menuIndex);
343 }
344 }
345 }
346
347 Component {
348 id: linkMenu;
349
350 Menus.BaseLayoutMenu {
351 objectName: "linkMenu"
352 property QtObject menuData: null
353 property int menuIndex: -1
354
355 text: menuData && menuData.label || ""
356 enabled: menuData && menuData.sensitive || false
357 backColor: Qt.rgba(1,1,1,0.07)
358 highlightWhenPressed: false
359
360 onTriggered: {
361 menuModel.activate(menuIndex);
362 }
363
364 slots: Icon {
365 source: {
366 if (menuData) {
367 if (menuData.icon && menuData.icon != "") {
368 return menuData.icon
369 } else if (menuData.action.indexOf("settings") > -1) {
370 return "image://theme/settings"
371 }
372 }
373 return ""
374 }
375 height: units.gu(3)
376 width: height
377 color: theme.palette.normal.backgroundText
378 SlotsLayout.position: SlotsLayout.Trailing
379 }
380 }
381 }
382
383 Component {
384 id: checkableMenu;
385
386 Menus.CheckableMenu {
387 id: checkItem
388 objectName: "checkableMenu"
389 property QtObject menuData: null
390 property int menuIndex: -1
391 property bool serverChecked: menuData && menuData.isToggled || false
392
393 text: menuData && menuData.label || ""
394 enabled: menuData && menuData.sensitive || false
395 checked: serverChecked
396 highlightWhenPressed: false
397
398 ServerPropertySynchroniser {
399 objectName: "sync"
400 syncTimeout: Utils.Constants.indicatorValueTimeout
401
402 serverTarget: checkItem
403 serverProperty: "serverChecked"
404 userTarget: checkItem
405 userProperty: "checked"
406
407 onSyncTriggered: menuModel.activate(checkItem.menuIndex)
408 }
409 }
410 }
411
412 Component {
413 id: radioMenu;
414
415 Menus.RadioMenu {
416 id: radioItem
417 objectName: "radioMenu"
418 property QtObject menuData: null
419 property int menuIndex: -1
420 property bool serverChecked: menuData && menuData.isToggled || false
421
422 text: menuData && menuData.label || ""
423 enabled: menuData && menuData.sensitive || false
424 checked: serverChecked
425 highlightWhenPressed: false
426
427 ServerPropertySynchroniser {
428 objectName: "sync"
429 syncTimeout: Utils.Constants.indicatorValueTimeout
430
431 serverTarget: radioItem
432 serverProperty: "serverChecked"
433 userTarget: radioItem
434 userProperty: "checked"
435
436 onSyncTriggered: menuModel.activate(radioItem.menuIndex)
437 }
438 }
439 }
440
441 Component {
442 id: switchMenu;
443
444 Menus.SwitchMenu {
445 id: switchItem
446 objectName: "switchMenu"
447 property QtObject menuData: null
448 property var menuModel: menuFactory.menuModel
449 property int menuIndex: -1
450 property var extendedData: menuData && menuData.ext || undefined
451 property bool serverChecked: menuData && menuData.isToggled || false
452
453 text: menuData && menuData.label || ""
454 iconSource: menuData && menuData.icon || ""
455 enabled: menuData && menuData.sensitive || false
456 checked: serverChecked
457 highlightWhenPressed: false
458
459 property var subtitleAction: AyatanaMenuAction {
460 model: menuModel
461 index: menuIndex
462 name: getExtendedProperty(extendedData, "xAyatanaSubtitleAction", "")
463 }
464 subtitle.text: subtitleAction.valid ? subtitleAction.state : ""
465
466 onMenuModelChanged: {
467 loadAttributes();
468 }
469 onMenuIndexChanged: {
470 loadAttributes();
471 }
472
473 function loadAttributes() {
474 if (!menuModel || menuIndex == -1) return;
475 menuModel.loadExtendedAttributes(menuIndex, {
476 'x-ayatana-subtitle-action': 'string',
477 'target': 'bool',
478 });
479 }
480
481 ServerPropertySynchroniser {
482 objectName: "sync"
483 syncTimeout: Utils.Constants.indicatorValueTimeout
484
485 serverTarget: switchItem
486 serverProperty: "serverChecked"
487 userTarget: switchItem
488 userProperty: "checked"
489
490 onSyncTriggered: {
491 /* Figures out if the action's activate() requires a
492 * parameter or not. Works with:
493 * - com.canonical.indicator.switch
494 * - org.ayatana.indicator.switch (with fix)
495 * - com.canonical.indicator.switch mis-labled as
496 * org.ayatana.indicator.switch
497 * https://gitlab.com/ubports/development/core/lomiri-indicator-network/-/issues/87#note_1206883970
498 *
499 * If activate() requires a parameter but menu doesn't
500 * specify a target, the menu will be broken in a different
501 * way anyway.
502 */
503 if (extendedData.hasOwnProperty('target')) {
504 menuModel.activate(switchItem.menuIndex, switchItem.checked);
505 } else {
506 menuModel.activate(switchItem.menuIndex);
507 }
508 }
509 }
510 }
511 }
512
513 Component {
514 id: alarmMenu;
515
516 Menus.EventMenu {
517 id: alarmItem
518 objectName: "alarmMenu"
519 property QtObject menuData: null
520 property var menuModel: menuFactory.menuModel
521 property int menuIndex: -1
522 property var extendedData: menuData && menuData.ext || undefined
523
524 readonly property date serverTime: new Date(getExtendedProperty(extendedData, "xAyatanaTime", 0) * 1000)
525 LiveTimer {
526 frequency: LiveTimer.Relative
527 relativeTime: alarmItem.serverTime
528 onTrigger: alarmItem.time = i18n.relativeDateTime(alarmItem.serverTime)
529 }
530
531 text: menuData && menuData.label || ""
532 iconSource: menuData && menuData.icon || "image://theme/alarm-clock"
533 time: i18n.relativeDateTime(serverTime)
534 enabled: menuData && menuData.sensitive || false
535 highlightWhenPressed: false
536
537 onMenuModelChanged: {
538 loadAttributes();
539 }
540 onMenuIndexChanged: {
541 loadAttributes();
542 }
543 onTriggered: {
544 menuModel.activate(menuIndex);
545 }
546
547 function loadAttributes() {
548 if (!menuModel || menuIndex == -1) return;
549 menuModel.loadExtendedAttributes(menuIndex, {'x-ayatana-time': 'int64'});
550 }
551 }
552 }
553
554 Component {
555 id: appointmentMenu;
556
557 Menus.EventMenu {
558 id: appointmentItem
559 objectName: "appointmentMenu"
560 property QtObject menuData: null
561 property var menuModel: menuFactory.menuModel
562 property int menuIndex: -1
563 property var extendedData: menuData && menuData.ext || undefined
564
565 readonly property date serverTime: new Date(getExtendedProperty(extendedData, "xAyatanaTime", 0) * 1000)
566
567 LiveTimer {
568 frequency: LiveTimer.Relative
569 relativeTime: appointmentItem.serverTime
570 onTrigger: appointmentItem.time = i18n.relativeDateTime(appointmentItem.serverTime)
571 }
572
573 text: menuData && menuData.label || ""
574 iconSource: menuData && menuData.icon || "image://theme/calendar"
575 time: i18n.relativeDateTime(serverTime)
576 eventColor: getExtendedProperty(extendedData, "xAyatanaColor", Qt.rgba(0.0, 0.0, 0.0, 0.0))
577 enabled: menuData && menuData.sensitive || false
578 highlightWhenPressed: false
579
580 onMenuModelChanged: {
581 loadAttributes();
582 }
583 onMenuIndexChanged: {
584 loadAttributes();
585 }
586 onTriggered: {
587 menuModel.activate(menuIndex);
588 }
589
590 function loadAttributes() {
591 if (!menuModel || menuIndex == -1) return;
592 menuModel.loadExtendedAttributes(menuIndex, {'x-ayatana-color': 'string',
593 'x-ayatana-time': 'int64'});
594 }
595 }
596 }
597
598 Component {
599 id: userMenuItem
600
601 Menus.UserSessionMenu {
602 objectName: "userSessionMenu"
603 highlightWhenPressed: false
604
605 property QtObject menuData: null
606 property var menuModel: menuFactory.menuModel
607 property int menuIndex: -1
608
609 name: menuData && menuData.label || "" // label is the user's real name
610 iconSource: menuData && menuData.icon || ""
611
612 // would be better to compare with the logname but sadly the indicator doesn't expose that
613 active: DBusLomiriSessionService.RealName() !== "" ? DBusLomiriSessionService.RealName() == name
614 : DBusLomiriSessionService.UserName() == name
615
616 onTriggered: {
617 menuModel.activate(menuIndex);
618 }
619 }
620 }
621
622 Component {
623 id: calendarMenu
624
625 Menus.CalendarMenu {
626 id: calendarItem
627 objectName: "calendarMenu"
628 focus: true
629
630 property QtObject menuData: null
631 property var menuModel: menuFactory.menuModel
632 property var actionState: menuData && menuData.actionState || null
633 property real calendarDay: getExtendedProperty(actionState, "calendar-day", 0)
634 property int menuIndex: -1
635
636 showWeekNumbers: getExtendedProperty(actionState, "show-week-numbers", false)
637 eventDays: getExtendedProperty(actionState, "appointment-days", [])
638
639 onCalendarDayChanged: {
640 if (calendarDay > 0) {
641 // This would trigger a selectionDateChanged signal, thus
642 // we've to avoid that the subsequent model activation
643 // would cause an infinite loop
644 modelUpdateConnections.enabled = false
645 currentDate = new Date(calendarDay * 1000)
646 modelUpdateConnections.enabled = true
647 }
648 }
649
650 Connections {
651 id: modelUpdateConnections
652 property bool enabled: true
653 target: (enabled && calendarItem.visible) ? calendarItem : null
654
655 onSelectedDateChanged: {
656 menuModel.activate(menuIndex, selectedDate.getTime() / 1000 | 0)
657 }
658 }
659 }
660 }
661
662 Component {
663 id: timezoneMenu
664
665 Menus.TimeZoneMenu {
666 id: tzMenuItem
667 objectName: "timezoneMenu"
668
669 property QtObject menuData: null
670 property var menuModel: menuFactory.menuModel
671 property int menuIndex: -1
672 property var extendedData: menuData && menuData.ext || undefined
673 readonly property string tz: getExtendedProperty(extendedData, "xAyatanaTimezone", "UTC")
674 property var updateTimer: Timer {
675 repeat: true
676 running: tzMenuItem.visible // only run when we're open
677 onTriggered: tzMenuItem.time = Utils.TimezoneFormatter.currentTimeInTimezone(tzMenuItem.tz)
678 }
679
680 city: menuData && menuData.label || ""
681 time: Utils.TimezoneFormatter.currentTimeInTimezone(tz)
682 enabled: menuData && menuData.sensitive || false
683
684 onMenuModelChanged: {
685 loadAttributes();
686 }
687 onMenuIndexChanged: {
688 loadAttributes();
689 }
690 onTriggered: {
691 tzActionGroup.setLocation.activate(tz);
692 }
693
694 QDBusActionGroup {
695 id: tzActionGroup
696 busType: DBus.SessionBus
697 busName: "org.ayatana.indicator.datetime"
698 objectPath: "/org/ayatana/indicator/datetime"
699
700 property variant setLocation: action("set-location")
701
702 Component.onCompleted: tzActionGroup.start()
703 }
704
705 function loadAttributes() {
706 if (!menuModel || menuIndex == -1) return;
707 menuModel.loadExtendedAttributes(menuIndex, {'x-ayatana-timezone': 'string'});
708 }
709 }
710 }
711
712 Component {
713 id: wifiSection;
714
715 Menus.SectionMenu {
716 objectName: "wifiSection"
717 property QtObject menuData: null
718 property var menuModel: menuFactory.menuModel
719 property int menuIndex: -1
720 property var extendedData: menuData && menuData.ext || undefined
721
722 text: menuData && menuData.label || ""
723 busy: getExtendedProperty(extendedData, "xCanonicalBusyAction", false)
724
725 onMenuModelChanged: {
726 loadAttributes();
727 }
728 onMenuIndexChanged: {
729 loadAttributes();
730 }
731
732 function loadAttributes() {
733 if (!menuModel || menuIndex == -1) return;
734 menuModel.loadExtendedAttributes(menuIndex, {'x-canonical-busy-action': 'bool'})
735 }
736 }
737 }
738
739 Component {
740 id: accessPoint;
741
742 Menus.AccessPointMenu {
743 id: apItem
744 objectName: "accessPoint"
745 property QtObject menuData: null
746 property var menuModel: menuFactory.menuModel
747 property int menuIndex: -1
748 property var extendedData: menuData && menuData.ext || undefined
749 property bool serverChecked: menuData && menuData.isToggled || false
750
751 property var strengthAction: AyatanaMenuAction {
752 model: menuModel
753 index: menuIndex
754 name: getExtendedProperty(extendedData, "xAyatanaWifiApStrengthAction", "")
755 }
756
757 text: menuData && menuData.label || ""
758 enabled: menuData && menuData.sensitive || false
759 active: serverChecked
760 secure: getExtendedProperty(extendedData, "xAyatanaWifiApIsSecure", false)
761 adHoc: getExtendedProperty(extendedData, "xAyatanaWifiApIsAdhoc", false)
762 signalStrength: {
763 if (strengthAction.valid) {
764 var state = strengthAction.state; // handle both int and uchar
765 // FIXME remove the special casing when we switch to indicator-network completely
766 if (typeof state == "string") {
767 return state.charCodeAt();
768 }
769 return state;
770 }
771 return 0;
772 }
773 highlightWhenPressed: false
774
775 onMenuModelChanged: {
776 loadAttributes();
777 }
778 onMenuIndexChanged: {
779 loadAttributes();
780 }
781
782 function loadAttributes() {
783 if (!menuModel || menuIndex == -1) return;
784 menuModel.loadExtendedAttributes(menuIndex, {'x-ayatana-wifi-ap-is-adhoc': 'bool',
785 'x-ayatana-wifi-ap-is-secure': 'bool',
786 'x-ayatana-wifi-ap-strength-action': 'string'});
787 }
788
789 ServerPropertySynchroniser {
790 objectName: "sync"
791 syncTimeout: Utils.Constants.indicatorValueTimeout
792
793 serverTarget: apItem
794 serverProperty: "serverChecked"
795 userTarget: apItem
796 userProperty: "active"
797 userTrigger: "onTriggered"
798
799 onSyncTriggered: menuModel.activate(apItem.menuIndex)
800 }
801 }
802 }
803
804 Component {
805 id: modeminfoitem;
806 Menus.ModemInfoItem {
807 objectName: "modemInfoItem"
808 property QtObject menuData: null
809 property var menuModel: menuFactory.menuModel
810 property int menuIndex: -1
811 property var extendedData: menuData && menuData.ext || undefined
812 highlightWhenPressed: false
813
814 property var statusLabelAction: AyatanaMenuAction {
815 model: menuModel
816 index: menuIndex
817 name: getExtendedProperty(extendedData, "xLomiriModemStatusLabelAction", "")
818 }
819 statusText: statusLabelAction.valid ? statusLabelAction.state : ""
820
821 property var statusIconAction: AyatanaMenuAction {
822 model: menuModel
823 index: menuIndex
824 name: getExtendedProperty(extendedData, "xLomiriModemStatusIconAction", "")
825 }
826 statusIcon: statusIconAction.valid ? statusIconAction.state : ""
827
828 property var connectivityIconAction: AyatanaMenuAction {
829 model: menuModel
830 index: menuIndex
831 name: getExtendedProperty(extendedData, "xLomiriModemConnectivityIconAction", "")
832 }
833 connectivityIcon: connectivityIconAction.valid ? connectivityIconAction.state : ""
834
835 property var simIdentifierLabelAction: AyatanaMenuAction {
836 model: menuModel
837 index: menuIndex
838 name: getExtendedProperty(extendedData, "xLomiriModemSimIdentifierLabelAction", "")
839 }
840 simIdentifierText: simIdentifierLabelAction.valid ? simIdentifierLabelAction.state : ""
841
842 property var roamingAction: AyatanaMenuAction {
843 model: menuModel
844 index: menuIndex
845 name: getExtendedProperty(extendedData, "xLomiriModemRoamingAction", "")
846 }
847 roaming: roamingAction.valid ? roamingAction.state : false
848
849 property var unlockAction: AyatanaMenuAction {
850 model: menuModel
851 index: menuIndex
852 name: getExtendedProperty(extendedData, "xLomiriModemLockedAction", "")
853 }
854 onUnlock: {
855 unlockAction.activate();
856 }
857 locked: unlockAction.valid ? unlockAction.state : false
858
859 onMenuModelChanged: {
860 loadAttributes();
861 }
862 onMenuIndexChanged: {
863 loadAttributes();
864 }
865
866 function loadAttributes() {
867 if (!menuModel || menuIndex == -1) return;
868 menuModel.loadExtendedAttributes(menuIndex, {'x-lomiri-modem-status-label-action': 'string',
869 'x-lomiri-modem-status-icon-action': 'string',
870 'x-lomiri-modem-connectivity-icon-action': 'string',
871 'x-lomiri-modem-sim-identifier-label-action': 'string',
872 'x-lomiri-modem-roaming-action': 'string',
873 'x-lomiri-modem-locked-action': 'string'});
874 }
875 }
876 }
877
878 Component {
879 id: messageItem
880
881 MessageMenuItemFactory {
882 objectName: "messageItem"
883 menuModel: menuFactory.menuModel
884 }
885 }
886
887 Component {
888 id: groupedMessage
889
890 Menus.GroupedMessageMenu {
891 objectName: "groupedMessage"
892 property QtObject menuData: null
893 property var menuModel: menuFactory.menuModel
894 property int menuIndex: -1
895 property var extendedData: menuData && menuData.ext || undefined
896
897 text: menuData && menuData.label || ""
898 iconSource: getExtendedProperty(extendedData, "icon", "image://theme/message")
899 count: menuData && menuData.actionState.length > 0 ? menuData.actionState[0] : "0"
900 enabled: menuData && menuData.sensitive || false
901 highlightWhenPressed: false
902 removable: true
903
904 onMenuModelChanged: {
905 loadAttributes();
906 }
907 onMenuIndexChanged: {
908 loadAttributes();
909 }
910 onClicked: {
911 menuModel.activate(menuIndex, true);
912 }
913 onDismissed: {
914 menuModel.activate(menuIndex, false);
915 }
916
917 function loadAttributes() {
918 if (!menuModel || menuIndex == -1) return;
919 menuModel.loadExtendedAttributes(modelIndex, {'icon': 'icon'});
920 }
921 }
922 }
923
924 Component {
925 id: mediaPayerMenu;
926
927 Menus.MediaPlayerMenu {
928 objectName: "mediaPayerMenu"
929 property QtObject menuData: null
930 property var menuModel: menuFactory.menuModel
931 property int menuIndex: -1
932 property var actionState: menuData && menuData.actionState || undefined
933 property bool running: getExtendedProperty(actionState, "running", false)
934
935 playerIcon: menuData && menuData.icon || "image://theme/stock_music"
936 playerName: menuData && menuData.label || i18n.tr("Nothing is playing")
937
938 albumArt: getExtendedProperty(actionState, "art-url", "image://theme/stock_music")
939 song: getExtendedProperty(actionState, "title", "")
940 artist: getExtendedProperty(actionState, "artist", "")
941 album: getExtendedProperty(actionState, "album", "")
942 showTrack: running && (state == "Playing" || state == "Paused")
943 state: getExtendedProperty(actionState, "state", "")
944 enabled: menuData && menuData.sensitive || false
945 highlightWhenPressed: false
946
947 onTriggered: {
948 model.activate(modelIndex);
949 }
950 }
951 }
952
953 Component {
954 id: playbackItemMenu;
955
956 Menus.PlaybackItemMenu {
957 objectName: "playbackItemMenu"
958 property QtObject menuData: null
959 property var menuModel: menuFactory.menuModel
960 property int menuIndex: -1
961 property var extendedData: menuData && menuData.ext || undefined
962
963 property var playAction: AyatanaMenuAction {
964 model: menuModel
965 index: menuIndex
966 name: getExtendedProperty(extendedData, "xAyatanaPlayAction", "")
967 }
968 property var nextAction: AyatanaMenuAction {
969 model: menuModel
970 index: menuIndex
971 name: getExtendedProperty(extendedData, "xAyatanaNextAction", "")
972 }
973 property var previousAction: AyatanaMenuAction {
974 model: menuModel
975 index: menuIndex
976 name: getExtendedProperty(extendedData, "xAyatanaPreviousAction", "")
977 }
978
979 playing: playAction.state === "Playing"
980 canPlay: playAction.valid
981 canGoNext: nextAction.valid
982 canGoPrevious: previousAction.valid
983 enabled: menuData && menuData.sensitive || false
984 highlightWhenPressed: false
985
986 onPlay: {
987 playAction.activate();
988 }
989 onNext: {
990 nextAction.activate();
991 }
992 onPrevious: {
993 previousAction.activate();
994 }
995 onMenuModelChanged: {
996 loadAttributes();
997 }
998 onMenuIndexChanged: {
999 loadAttributes();
1000 }
1001
1002 function loadAttributes() {
1003 if (!menuModel || menuIndex == -1) return;
1004 menuModel.loadExtendedAttributes(modelIndex, {'x-ayatana-play-action': 'string',
1005 'x-ayatana-next-action': 'string',
1006 'x-ayatana-previous-action': 'string'});
1007 }
1008 }
1009 }
1010
1011 Component {
1012 id: transferMenu
1013
1014 Menus.TransferMenu {
1015 objectName: "transferMenu"
1016 id: transfer
1017 property QtObject menuData: null
1018 property var menuModel: menuFactory.menuModel
1019 property int menuIndex: -1
1020 property var extendedData: menuData && menuData.ext || undefined
1021 property var uid: getExtendedProperty(extendedData, "xAyatanaUid", undefined)
1022
1023 text: menuData && menuData.label || ""
1024 iconSource: menuData && menuData.icon || "image://theme/transfer-none"
1025 maximum: 1.0
1026 enabled: menuData && menuData.sensitive || false
1027 highlightWhenPressed: false
1028 removable: true
1029 confirmRemoval: true
1030
1031 QDBusActionGroup {
1032 id: actionGroup
1033 busType: 1
1034 busName: menuFactory.rootModel.busName
1035 objectPath: menuFactory.rootModel.actions["indicator"]
1036
1037 property var activateAction: action("activate-transfer")
1038 property var cancelAction: action("cancel-transfer")
1039 property var transferStateAction: uid !== undefined ? action("transfer-state."+uid) : null
1040
1041 Component.onCompleted: actionGroup.start()
1042 }
1043
1044 property var transferState: {
1045 if (actionGroup.transferStateAction === null) return undefined;
1046 return actionGroup.transferStateAction.valid ? actionGroup.transferStateAction.state : undefined
1047 }
1048
1049 property var runningState : transferState !== undefined ? transferState["state"] : undefined
1050 property var secondsLeft : transferState !== undefined ? transferState["seconds-left"] : undefined
1051
1052 active: runningState !== undefined && runningState !== Menus.TransferState.Finished
1053 progress: transferState !== undefined ? transferState["percent"] : 0.0
1054
1055 // TODO - Should be in the SDK
1056 property var timeRemaining: {
1057 if (secondsLeft === undefined) return undefined;
1058
1059 var remaining = "";
1060 var hours = Math.floor(secondsLeft / (60 * 60));
1061 var minutes = Math.floor(secondsLeft / 60) % 60;
1062 var seconds = secondsLeft % 60;
1063 if (hours > 0) {
1064 remaining += i18n.tr("%1 hour", "%1 hours", hours).arg(hours)
1065 }
1066 if (minutes > 0) {
1067 if (remaining != "") remaining += ", ";
1068 remaining += i18n.tr("%1 minute", "%1 minutes", minutes).arg(minutes)
1069 }
1070 // don't include seconds if hours > 0
1071 if (hours == 0 && minutes < 5 && seconds > 0) {
1072 if (remaining != "") remaining += ", ";
1073 remaining += i18n.tr("%1 second", "%1 seconds", seconds).arg(seconds)
1074 }
1075 if (remaining == "")
1076 remaining = i18n.tr("0 seconds");
1077 // Translators: String like "1 hour, 2 minutes, 3 seconds remaining"
1078 return i18n.tr("%1 remaining").arg(remaining);
1079 }
1080
1081 stateText: {
1082 switch (runningState) {
1083 case Menus.TransferState.Queued:
1084 return i18n.tr("In queue…");
1085 case Menus.TransferState.Hashing:
1086 case Menus.TransferState.Processing:
1087 case Menus.TransferState.Running:
1088 return timeRemaining === undefined ? i18n.tr("Downloading") : timeRemaining;
1089 case Menus.TransferState.Paused:
1090 return i18n.tr("Paused, tap to resume");
1091 case Menus.TransferState.Canceled:
1092 return i18n.tr("Canceled");
1093 case Menus.TransferState.Finished:
1094 return i18n.tr("Finished");
1095 case Menus.TransferState.Error:
1096 return i18n.tr("Failed, tap to retry");
1097 }
1098 return "";
1099 }
1100
1101 onMenuModelChanged: {
1102 loadAttributes();
1103 }
1104 onMenuIndexChanged: {
1105 loadAttributes();
1106 }
1107 onTriggered: {
1108 actionGroup.activateAction.activate(uid);
1109 }
1110 onItemRemoved: {
1111 actionGroup.cancelAction.activate(uid);
1112 }
1113
1114 function loadAttributes() {
1115 if (!menuModel || menuIndex == -1) return;
1116 menuModel.loadExtendedAttributes(menuIndex, {'x-ayatana-uid': 'string'});
1117 }
1118 }
1119 }
1120
1121 Component {
1122 id: buttonSectionMenu;
1123
1124 Menus.ButtonMenu {
1125 objectName: "buttonSectionMenu"
1126 property QtObject menuData: null
1127 property var menuModel: menuFactory.menuModel
1128 property int menuIndex: -1
1129 property var extendedData: menuData && menuData.ext || undefined
1130
1131 iconSource: menuData && menuData.icon || ""
1132 enabled: menuData && menuData.sensitive || false
1133 highlightWhenPressed: false
1134 text: menuData && menuData.label || ""
1135 foregroundColor: theme.palette.normal.backgroundText
1136 buttonText: getExtendedProperty(extendedData, "xAyatanaExtraLabel", "")
1137
1138 onMenuModelChanged: {
1139 loadAttributes();
1140 }
1141 onMenuIndexChanged: {
1142 loadAttributes();
1143 }
1144 function loadAttributes() {
1145 if (!menuModel || menuIndex == -1) return;
1146 menuModel.loadExtendedAttributes(menuIndex, {'x-ayatana-extra-label': 'string'});
1147 }
1148
1149 onButtonClicked: menuModel.activate(menuIndex);
1150 }
1151 }
1152
1153 function load(modelData) {
1154 var component = getComponentForIndicatorEntryAction(modelData.action)
1155 if (component !== undefined) {
1156 return component
1157 }
1158
1159 component = getComponentForIndicatorEntryType(modelData.type)
1160 if (component !== undefined) {
1161 return component;
1162 }
1163
1164 if (modelData.isCheck) {
1165 return checkableMenu;
1166 }
1167 if (modelData.isRadio) {
1168 return radioMenu;
1169 }
1170 if (modelData.isSeparator) {
1171 return separatorMenu;
1172 }
1173 if (modelData.action !== undefined && modelData.action.indexOf("settings") > -1) {
1174 // FIXME : At the moment, the indicators aren't using
1175 // org.ayatana.indicators.link for settings menu. Need to fudge it.
1176 return linkMenu;
1177 }
1178 return standardMenu;
1179 }
1180}