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 
17 import QtQuick 2.4
18 import QtQuick.Window 2.2
19 import Lomiri.Settings.Menus 0.1 as Menus
20 import Lomiri.Settings.Components 0.1
21 import QMenuModel 1.0
22 import Utils 0.1 as Utils
23 import Lomiri.Components.ListItems 1.3 as ListItems
24 import Lomiri.Components 1.3
25 import Lomiri.Session 0.1
26 import Lomiri.Platform 1.0
27 
28 Item {
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 }