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