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, {'x-ayatana-subtitle-action': 'string'});
476 }
477
478 ServerPropertySynchroniser {
479 objectName: "sync"
480 syncTimeout: Utils.Constants.indicatorValueTimeout
481
482 serverTarget: switchItem
483 serverProperty: "serverChecked"
484 userTarget: switchItem
485 userProperty: "checked"
486
487 onSyncTriggered: {
488 /* Works with:
489 * - com.canonical.indicator.switch
490 * - org.ayatana.indicator.switch (with fix)
491 * - com.canonical.indicator.switch mis-labled as
492 * org.ayatana.indicator.switch
493 * https://gitlab.com/ubports/development/core/lomiri-indicator-network/-/issues/87#note_1206883970
494 */
495 menuModel.changeState(switchItem.menuIndex, switchItem.checked);
496 }
497 }
498 }
499 }
500
501 Component {
502 id: alarmMenu;
503
504 Menus.EventMenu {
505 id: alarmItem
506 objectName: "alarmMenu"
507 property QtObject menuData: null
508 property var menuModel: menuFactory.menuModel
509 property int menuIndex: -1
510 property var extendedData: menuData && menuData.ext || undefined
511
512 readonly property date serverTime: new Date(getExtendedProperty(extendedData, "xAyatanaTime", 0) * 1000)
513 LiveTimer {
514 frequency: LiveTimer.Relative
515 relativeTime: alarmItem.serverTime
516 onTrigger: alarmItem.time = i18n.relativeDateTime(alarmItem.serverTime)
517 }
518
519 text: menuData && menuData.label || ""
520 iconSource: menuData && menuData.icon || "image://theme/alarm-clock"
521 time: i18n.relativeDateTime(serverTime)
522 enabled: menuData && menuData.sensitive || false
523 highlightWhenPressed: false
524
525 onMenuModelChanged: {
526 loadAttributes();
527 }
528 onMenuIndexChanged: {
529 loadAttributes();
530 }
531 onTriggered: {
532 menuModel.activate(menuIndex);
533 }
534
535 function loadAttributes() {
536 if (!menuModel || menuIndex == -1) return;
537 menuModel.loadExtendedAttributes(menuIndex, {'x-ayatana-time': 'int64'});
538 }
539 }
540 }
541
542 Component {
543 id: appointmentMenu;
544
545 Menus.EventMenu {
546 id: appointmentItem
547 objectName: "appointmentMenu"
548 property QtObject menuData: null
549 property var menuModel: menuFactory.menuModel
550 property int menuIndex: -1
551 property var extendedData: menuData && menuData.ext || undefined
552
553 readonly property date serverTime: new Date(getExtendedProperty(extendedData, "xAyatanaTime", 0) * 1000)
554
555 LiveTimer {
556 frequency: LiveTimer.Relative
557 relativeTime: appointmentItem.serverTime
558 onTrigger: appointmentItem.time = i18n.relativeDateTime(appointmentItem.serverTime)
559 }
560
561 text: menuData && menuData.label || ""
562 iconSource: menuData && menuData.icon || "image://theme/calendar"
563 time: i18n.relativeDateTime(serverTime)
564 eventColor: getExtendedProperty(extendedData, "xAyatanaColor", Qt.rgba(0.0, 0.0, 0.0, 0.0))
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-color': 'string',
581 'x-ayatana-time': 'int64'});
582 }
583 }
584 }
585
586 Component {
587 id: userMenuItem
588
589 Menus.UserSessionMenu {
590 objectName: "userSessionMenu"
591 highlightWhenPressed: false
592
593 property QtObject menuData: null
594 property var menuModel: menuFactory.menuModel
595 property int menuIndex: -1
596
597 name: menuData && menuData.label || "" // label is the user's real name
598 iconSource: menuData && menuData.icon || ""
599
600 // would be better to compare with the logname but sadly the indicator doesn't expose that
601 active: DBusLomiriSessionService.RealName() !== "" ? DBusLomiriSessionService.RealName() == name
602 : DBusLomiriSessionService.UserName() == name
603
604 onTriggered: {
605 menuModel.activate(menuIndex);
606 }
607 }
608 }
609
610 Component {
611 id: calendarMenu
612
613 Menus.CalendarMenu {
614 id: calendarItem
615 objectName: "calendarMenu"
616 focus: true
617
618 property QtObject menuData: null
619 property var menuModel: menuFactory.menuModel
620 property var actionState: menuData && menuData.actionState || null
621 property real calendarDay: getExtendedProperty(actionState, "calendar-day", 0)
622 property int menuIndex: -1
623
624 showWeekNumbers: getExtendedProperty(actionState, "show-week-numbers", false)
625 eventDays: getExtendedProperty(actionState, "appointment-days", [])
626
627 onCalendarDayChanged: {
628 if (calendarDay > 0) {
629 // This would trigger a selectionDateChanged signal, thus
630 // we've to avoid that the subsequent model activation
631 // would cause an infinite loop
632 modelUpdateConnections.enabled = false
633 currentDate = new Date(calendarDay * 1000)
634 modelUpdateConnections.enabled = true
635 }
636 }
637
638 Connections {
639 id: modelUpdateConnections
640 property bool enabled: true
641 target: (enabled && calendarItem.visible) ? calendarItem : null
642
643 onSelectedDateChanged: {
644 menuModel.activate(menuIndex, selectedDate.getTime() / 1000 | 0)
645 }
646 }
647 }
648 }
649
650 Component {
651 id: timezoneMenu
652
653 Menus.TimeZoneMenu {
654 id: tzMenuItem
655 objectName: "timezoneMenu"
656
657 property QtObject menuData: null
658 property var menuModel: menuFactory.menuModel
659 property int menuIndex: -1
660 property var extendedData: menuData && menuData.ext || undefined
661 readonly property string tz: getExtendedProperty(extendedData, "xAyatanaTimezone", "UTC")
662 property var updateTimer: Timer {
663 repeat: true
664 running: tzMenuItem.visible // only run when we're open
665 onTriggered: tzMenuItem.time = Utils.TimezoneFormatter.currentTimeInTimezone(tzMenuItem.tz)
666 }
667
668 city: menuData && menuData.label || ""
669 time: Utils.TimezoneFormatter.currentTimeInTimezone(tz)
670 enabled: menuData && menuData.sensitive || false
671
672 onMenuModelChanged: {
673 loadAttributes();
674 }
675 onMenuIndexChanged: {
676 loadAttributes();
677 }
678 onTriggered: {
679 tzActionGroup.setLocation.activate(tz);
680 }
681
682 QDBusActionGroup {
683 id: tzActionGroup
684 busType: DBus.SessionBus
685 busName: "org.ayatana.indicator.datetime"
686 objectPath: "/org/ayatana/indicator/datetime"
687
688 property variant setLocation: action("set-location")
689
690 Component.onCompleted: tzActionGroup.start()
691 }
692
693 function loadAttributes() {
694 if (!menuModel || menuIndex == -1) return;
695 menuModel.loadExtendedAttributes(menuIndex, {'x-ayatana-timezone': 'string'});
696 }
697 }
698 }
699
700 Component {
701 id: wifiSection;
702
703 Menus.SectionMenu {
704 objectName: "wifiSection"
705 property QtObject menuData: null
706 property var menuModel: menuFactory.menuModel
707 property int menuIndex: -1
708 property var extendedData: menuData && menuData.ext || undefined
709
710 text: menuData && menuData.label || ""
711 busy: getExtendedProperty(extendedData, "xCanonicalBusyAction", false)
712
713 onMenuModelChanged: {
714 loadAttributes();
715 }
716 onMenuIndexChanged: {
717 loadAttributes();
718 }
719
720 function loadAttributes() {
721 if (!menuModel || menuIndex == -1) return;
722 menuModel.loadExtendedAttributes(menuIndex, {'x-canonical-busy-action': 'bool'})
723 }
724 }
725 }
726
727 Component {
728 id: accessPoint;
729
730 Menus.AccessPointMenu {
731 id: apItem
732 objectName: "accessPoint"
733 property QtObject menuData: null
734 property var menuModel: menuFactory.menuModel
735 property int menuIndex: -1
736 property var extendedData: menuData && menuData.ext || undefined
737 property bool serverChecked: menuData && menuData.isToggled || false
738
739 property var strengthAction: AyatanaMenuAction {
740 model: menuModel
741 index: menuIndex
742 name: getExtendedProperty(extendedData, "xAyatanaWifiApStrengthAction", "")
743 }
744
745 text: menuData && menuData.label || ""
746 enabled: menuData && menuData.sensitive || false
747 active: serverChecked
748 secure: getExtendedProperty(extendedData, "xAyatanaWifiApIsSecure", false)
749 adHoc: getExtendedProperty(extendedData, "xAyatanaWifiApIsAdhoc", false)
750 signalStrength: {
751 if (strengthAction.valid) {
752 var state = strengthAction.state; // handle both int and uchar
753 // FIXME remove the special casing when we switch to indicator-network completely
754 if (typeof state == "string") {
755 return state.charCodeAt();
756 }
757 return state;
758 }
759 return 0;
760 }
761 highlightWhenPressed: false
762
763 onMenuModelChanged: {
764 loadAttributes();
765 }
766 onMenuIndexChanged: {
767 loadAttributes();
768 }
769
770 function loadAttributes() {
771 if (!menuModel || menuIndex == -1) return;
772 menuModel.loadExtendedAttributes(menuIndex, {'x-ayatana-wifi-ap-is-adhoc': 'bool',
773 'x-ayatana-wifi-ap-is-secure': 'bool',
774 'x-ayatana-wifi-ap-strength-action': 'string'});
775 }
776
777 ServerPropertySynchroniser {
778 objectName: "sync"
779 syncTimeout: Utils.Constants.indicatorValueTimeout
780
781 serverTarget: apItem
782 serverProperty: "serverChecked"
783 userTarget: apItem
784 userProperty: "active"
785 userTrigger: "onTriggered"
786
787 onSyncTriggered: menuModel.activate(apItem.menuIndex)
788 }
789 }
790 }
791
792 Component {
793 id: modeminfoitem;
794 Menus.ModemInfoItem {
795 objectName: "modemInfoItem"
796 property QtObject menuData: null
797 property var menuModel: menuFactory.menuModel
798 property int menuIndex: -1
799 property var extendedData: menuData && menuData.ext || undefined
800 highlightWhenPressed: false
801
802 property var statusLabelAction: AyatanaMenuAction {
803 model: menuModel
804 index: menuIndex
805 name: getExtendedProperty(extendedData, "xLomiriModemStatusLabelAction", "")
806 }
807 statusText: statusLabelAction.valid ? statusLabelAction.state : ""
808
809 property var statusIconAction: AyatanaMenuAction {
810 model: menuModel
811 index: menuIndex
812 name: getExtendedProperty(extendedData, "xLomiriModemStatusIconAction", "")
813 }
814 statusIcon: statusIconAction.valid ? statusIconAction.state : ""
815
816 property var connectivityIconAction: AyatanaMenuAction {
817 model: menuModel
818 index: menuIndex
819 name: getExtendedProperty(extendedData, "xLomiriModemConnectivityIconAction", "")
820 }
821 connectivityIcon: connectivityIconAction.valid ? connectivityIconAction.state : ""
822
823 property var simIdentifierLabelAction: AyatanaMenuAction {
824 model: menuModel
825 index: menuIndex
826 name: getExtendedProperty(extendedData, "xLomiriModemSimIdentifierLabelAction", "")
827 }
828 simIdentifierText: simIdentifierLabelAction.valid ? simIdentifierLabelAction.state : ""
829
830 property var roamingAction: AyatanaMenuAction {
831 model: menuModel
832 index: menuIndex
833 name: getExtendedProperty(extendedData, "xLomiriModemRoamingAction", "")
834 }
835 roaming: roamingAction.valid ? roamingAction.state : false
836
837 property var unlockAction: AyatanaMenuAction {
838 model: menuModel
839 index: menuIndex
840 name: getExtendedProperty(extendedData, "xLomiriModemLockedAction", "")
841 }
842 onUnlock: {
843 unlockAction.activate();
844 }
845 locked: unlockAction.valid ? unlockAction.state : false
846
847 onMenuModelChanged: {
848 loadAttributes();
849 }
850 onMenuIndexChanged: {
851 loadAttributes();
852 }
853
854 function loadAttributes() {
855 if (!menuModel || menuIndex == -1) return;
856 menuModel.loadExtendedAttributes(menuIndex, {'x-lomiri-modem-status-label-action': 'string',
857 'x-lomiri-modem-status-icon-action': 'string',
858 'x-lomiri-modem-connectivity-icon-action': 'string',
859 'x-lomiri-modem-sim-identifier-label-action': 'string',
860 'x-lomiri-modem-roaming-action': 'string',
861 'x-lomiri-modem-locked-action': 'string'});
862 }
863 }
864 }
865
866 Component {
867 id: messageItem
868
869 MessageMenuItemFactory {
870 objectName: "messageItem"
871 menuModel: menuFactory.menuModel
872 }
873 }
874
875 Component {
876 id: groupedMessage
877
878 Menus.GroupedMessageMenu {
879 objectName: "groupedMessage"
880 property QtObject menuData: null
881 property var menuModel: menuFactory.menuModel
882 property int menuIndex: -1
883 property var extendedData: menuData && menuData.ext || undefined
884
885 text: menuData && menuData.label || ""
886 iconSource: getExtendedProperty(extendedData, "icon", "image://theme/message")
887 count: menuData && menuData.actionState.length > 0 ? menuData.actionState[0] : "0"
888 enabled: menuData && menuData.sensitive || false
889 highlightWhenPressed: false
890 removable: true
891
892 onMenuModelChanged: {
893 loadAttributes();
894 }
895 onMenuIndexChanged: {
896 loadAttributes();
897 }
898 onClicked: {
899 menuModel.activate(menuIndex, true);
900 }
901 onDismissed: {
902 menuModel.activate(menuIndex, false);
903 }
904
905 function loadAttributes() {
906 if (!menuModel || menuIndex == -1) return;
907 menuModel.loadExtendedAttributes(modelIndex, {'icon': 'icon'});
908 }
909 }
910 }
911
912 Component {
913 id: mediaPayerMenu;
914
915 Menus.MediaPlayerMenu {
916 objectName: "mediaPayerMenu"
917 property QtObject menuData: null
918 property var menuModel: menuFactory.menuModel
919 property int menuIndex: -1
920 property var actionState: menuData && menuData.actionState || undefined
921 property bool running: getExtendedProperty(actionState, "running", false)
922
923 playerIcon: menuData && menuData.icon || "image://theme/stock_music"
924 playerName: menuData && menuData.label || i18n.tr("Nothing is playing")
925
926 albumArt: getExtendedProperty(actionState, "art-url", "image://theme/stock_music")
927 song: getExtendedProperty(actionState, "title", "")
928 artist: getExtendedProperty(actionState, "artist", "")
929 album: getExtendedProperty(actionState, "album", "")
930 showTrack: running && (state == "Playing" || state == "Paused")
931 state: getExtendedProperty(actionState, "state", "")
932 enabled: menuData && menuData.sensitive || false
933 highlightWhenPressed: false
934
935 onTriggered: {
936 model.activate(modelIndex);
937 }
938 }
939 }
940
941 Component {
942 id: playbackItemMenu;
943
944 Menus.PlaybackItemMenu {
945 objectName: "playbackItemMenu"
946 property QtObject menuData: null
947 property var menuModel: menuFactory.menuModel
948 property int menuIndex: -1
949 property var extendedData: menuData && menuData.ext || undefined
950
951 property var playAction: AyatanaMenuAction {
952 model: menuModel
953 index: menuIndex
954 name: getExtendedProperty(extendedData, "xAyatanaPlayAction", "")
955 }
956 property var nextAction: AyatanaMenuAction {
957 model: menuModel
958 index: menuIndex
959 name: getExtendedProperty(extendedData, "xAyatanaNextAction", "")
960 }
961 property var previousAction: AyatanaMenuAction {
962 model: menuModel
963 index: menuIndex
964 name: getExtendedProperty(extendedData, "xAyatanaPreviousAction", "")
965 }
966
967 playing: playAction.state === "Playing"
968 canPlay: playAction.valid
969 canGoNext: nextAction.valid
970 canGoPrevious: previousAction.valid
971 enabled: menuData && menuData.sensitive || false
972 highlightWhenPressed: false
973
974 onPlay: {
975 playAction.activate();
976 }
977 onNext: {
978 nextAction.activate();
979 }
980 onPrevious: {
981 previousAction.activate();
982 }
983 onMenuModelChanged: {
984 loadAttributes();
985 }
986 onMenuIndexChanged: {
987 loadAttributes();
988 }
989
990 function loadAttributes() {
991 if (!menuModel || menuIndex == -1) return;
992 menuModel.loadExtendedAttributes(modelIndex, {'x-ayatana-play-action': 'string',
993 'x-ayatana-next-action': 'string',
994 'x-ayatana-previous-action': 'string'});
995 }
996 }
997 }
998
999 Component {
1000 id: transferMenu
1001
1002 Menus.TransferMenu {
1003 objectName: "transferMenu"
1004 id: transfer
1005 property QtObject menuData: null
1006 property var menuModel: menuFactory.menuModel
1007 property int menuIndex: -1
1008 property var extendedData: menuData && menuData.ext || undefined
1009 property var uid: getExtendedProperty(extendedData, "xAyatanaUid", undefined)
1010
1011 text: menuData && menuData.label || ""
1012 iconSource: menuData && menuData.icon || "image://theme/transfer-none"
1013 maximum: 1.0
1014 enabled: menuData && menuData.sensitive || false
1015 highlightWhenPressed: false
1016 removable: true
1017 confirmRemoval: true
1018
1019 QDBusActionGroup {
1020 id: actionGroup
1021 busType: 1
1022 busName: menuFactory.rootModel.busName
1023 objectPath: menuFactory.rootModel.actions["indicator"]
1024
1025 property var activateAction: action("activate-transfer")
1026 property var cancelAction: action("cancel-transfer")
1027 property var transferStateAction: uid !== undefined ? action("transfer-state."+uid) : null
1028
1029 Component.onCompleted: actionGroup.start()
1030 }
1031
1032 property var transferState: {
1033 if (actionGroup.transferStateAction === null) return undefined;
1034 return actionGroup.transferStateAction.valid ? actionGroup.transferStateAction.state : undefined
1035 }
1036
1037 property var runningState : transferState !== undefined ? transferState["state"] : undefined
1038 property var secondsLeft : transferState !== undefined ? transferState["seconds-left"] : undefined
1039
1040 active: runningState !== undefined && runningState !== Menus.TransferState.Finished
1041 progress: transferState !== undefined ? transferState["percent"] : 0.0
1042
1043 // TODO - Should be in the SDK
1044 property var timeRemaining: {
1045 if (secondsLeft === undefined) return undefined;
1046
1047 var remaining = "";
1048 var hours = Math.floor(secondsLeft / (60 * 60));
1049 var minutes = Math.floor(secondsLeft / 60) % 60;
1050 var seconds = secondsLeft % 60;
1051 if (hours > 0) {
1052 remaining += i18n.tr("%1 hour", "%1 hours", hours).arg(hours)
1053 }
1054 if (minutes > 0) {
1055 if (remaining != "") remaining += ", ";
1056 remaining += i18n.tr("%1 minute", "%1 minutes", minutes).arg(minutes)
1057 }
1058 // don't include seconds if hours > 0
1059 if (hours == 0 && minutes < 5 && seconds > 0) {
1060 if (remaining != "") remaining += ", ";
1061 remaining += i18n.tr("%1 second", "%1 seconds", seconds).arg(seconds)
1062 }
1063 if (remaining == "")
1064 remaining = i18n.tr("0 seconds");
1065 // Translators: String like "1 hour, 2 minutes, 3 seconds remaining"
1066 return i18n.tr("%1 remaining").arg(remaining);
1067 }
1068
1069 stateText: {
1070 switch (runningState) {
1071 case Menus.TransferState.Queued:
1072 return i18n.tr("In queue…");
1073 case Menus.TransferState.Hashing:
1074 case Menus.TransferState.Processing:
1075 case Menus.TransferState.Running:
1076 return timeRemaining === undefined ? i18n.tr("Downloading") : timeRemaining;
1077 case Menus.TransferState.Paused:
1078 return i18n.tr("Paused, tap to resume");
1079 case Menus.TransferState.Canceled:
1080 return i18n.tr("Canceled");
1081 case Menus.TransferState.Finished:
1082 return i18n.tr("Finished");
1083 case Menus.TransferState.Error:
1084 return i18n.tr("Failed, tap to retry");
1085 }
1086 return "";
1087 }
1088
1089 onMenuModelChanged: {
1090 loadAttributes();
1091 }
1092 onMenuIndexChanged: {
1093 loadAttributes();
1094 }
1095 onTriggered: {
1096 actionGroup.activateAction.activate(uid);
1097 }
1098 onItemRemoved: {
1099 actionGroup.cancelAction.activate(uid);
1100 }
1101
1102 function loadAttributes() {
1103 if (!menuModel || menuIndex == -1) return;
1104 menuModel.loadExtendedAttributes(menuIndex, {'x-ayatana-uid': 'string'});
1105 }
1106 }
1107 }
1108
1109 Component {
1110 id: buttonSectionMenu;
1111
1112 Menus.ButtonMenu {
1113 objectName: "buttonSectionMenu"
1114 property QtObject menuData: null
1115 property var menuModel: menuFactory.menuModel
1116 property int menuIndex: -1
1117 property var extendedData: menuData && menuData.ext || undefined
1118
1119 iconSource: menuData && menuData.icon || ""
1120 enabled: menuData && menuData.sensitive || false
1121 highlightWhenPressed: false
1122 text: menuData && menuData.label || ""
1123 foregroundColor: theme.palette.normal.backgroundText
1124 buttonText: getExtendedProperty(extendedData, "xAyatanaExtraLabel", "")
1125
1126 onMenuModelChanged: {
1127 loadAttributes();
1128 }
1129 onMenuIndexChanged: {
1130 loadAttributes();
1131 }
1132 function loadAttributes() {
1133 if (!menuModel || menuIndex == -1) return;
1134 menuModel.loadExtendedAttributes(menuIndex, {'x-ayatana-extra-label': 'string'});
1135 }
1136
1137 onButtonClicked: menuModel.activate(menuIndex);
1138 }
1139 }
1140
1141 function load(modelData) {
1142 var component = getComponentForIndicatorEntryAction(modelData.action)
1143 if (component !== undefined) {
1144 return component
1145 }
1146
1147 component = getComponentForIndicatorEntryType(modelData.type)
1148 if (component !== undefined) {
1149 return component;
1150 }
1151
1152 if (modelData.isCheck) {
1153 return checkableMenu;
1154 }
1155 if (modelData.isRadio) {
1156 return radioMenu;
1157 }
1158 if (modelData.isSeparator) {
1159 return separatorMenu;
1160 }
1161 if (modelData.action !== undefined && modelData.action.indexOf("settings") > -1) {
1162 // FIXME : At the moment, the indicators aren't using
1163 // org.ayatana.indicators.link for settings menu. Need to fudge it.
1164 return linkMenu;
1165 }
1166 return standardMenu;
1167 }
1168}