/*
* Copyright 2013 Canonical Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see .
*
* Authors:
* Nick Dedekind
*/
import QtQuick 2.0
import Ubuntu.Settings.Menus 0.1 as Menus
import QMenuModel 0.1
import Utils 0.1 as Utils
import Ubuntu.Components.ListItems 0.1 as ListItems
import Ubuntu.Components 1.1
Item {
id: menuFactory
property var rootModel: null
property var menuModel: null
property var _map: {
"default": {
"unity.widgets.systemsettings.tablet.volumecontrol" : sliderMenu,
"unity.widgets.systemsettings.tablet.switch" : switchMenu,
"com.canonical.indicator.button" : buttonMenu,
"com.canonical.indicator.div" : separatorMenu,
"com.canonical.indicator.section" : sectionMenu,
"com.canonical.indicator.progress" : progressMenu,
"com.canonical.indicator.slider" : sliderMenu,
"com.canonical.indicator.switch" : switchMenu,
"com.canonical.indicator.alarm" : alarmMenu,
"com.canonical.indicator.appointment" : appointmentMenu,
"com.canonical.indicator.transfer" : transferMenu,
"com.canonical.indicator.button-section" : buttonSectionMenu,
"com.canonical.indicator.link" : linkMenu,
"com.canonical.indicator.messages.messageitem" : messageItem,
"com.canonical.indicator.messages.sourceitem" : groupedMessage,
"com.canonical.unity.slider" : sliderMenu,
"com.canonical.unity.switch" : switchMenu,
"com.canonical.unity.media-player" : mediaPayerMenu,
"com.canonical.unity.playback-item" : playbackItemMenu,
"unity.widgets.systemsettings.tablet.wifisection" : wifiSection,
"unity.widgets.systemsettings.tablet.accesspoint" : accessPoint,
"com.canonical.indicator.network.modeminfoitem" : modeminfoitem,
},
"indicator-messages" : {
"com.canonical.indicator.button" : messagesButtonMenu
}
}
function getExtendedProperty(object, propertyName, defaultValue) {
if (object && object.hasOwnProperty(propertyName)) {
return object[propertyName];
}
return defaultValue;
}
Component {
id: separatorMenu;
Menus.SeparatorMenu {
objectName: "separatorMenu"
}
}
Component {
id: sliderMenu;
Menus.SliderMenu {
objectName: "sliderMenu"
property QtObject menuData: null
property var menuModel: menuFactory.menuModel
property int menuIndex: -1
property var extendedData: menuData && menuData.ext || undefined
property var serverValue: getExtendedProperty(menuData, "actionState", undefined)
text: menuData && menuData.label || ""
iconSource: menuData && menuData.icon || ""
minIcon: getExtendedProperty(extendedData, "minIcon", "")
maxIcon: getExtendedProperty(extendedData, "maxIcon", "")
minimumValue: getExtendedProperty(extendedData, "minValue", 0.0)
maximumValue: {
var maximum = getExtendedProperty(extendedData, "maxValue", 1.0);
if (maximum <= minimumValue) {
return minimumValue + 1;
}
return maximum;
}
enabled: menuData && menuData.sensitive || false
highlightWhenPressed: false
onMenuModelChanged: {
loadAttributes();
}
onMenuIndexChanged: {
loadAttributes();
}
onServerValueChanged: {
// value can be changed by slider, so a binding won't work.
if (serverValue !== undefined) {
value = serverValue;
}
}
onUpdated: {
menuModel.changeState(menuIndex, value);
}
function loadAttributes() {
if (!menuModel || menuIndex == -1) return;
menuModel.loadExtendedAttributes(menuIndex, {'min-value': 'double',
'max-value': 'double',
'min-icon': 'icon',
'max-icon': 'icon'});
}
}
}
Component {
id: buttonMenu;
Menus.ButtonMenu {
objectName: "buttonMenu"
property QtObject menuData: null
property var menuModel: menuFactory.menuModel
property int menuIndex: -1
buttonText: menuData && menuData.label || ""
enabled: menuData && menuData.sensitive || false
highlightWhenPressed: false
onTriggered: {
menuModel.activate(menuIndex);
}
}
}
Component {
id: messagesButtonMenu;
Item {
objectName: "messagesButtonMenu"
property QtObject menuData: null
property var menuModel: menuFactory.menuModel
property int menuIndex: -1
implicitHeight: units.gu(5)
enabled: menuData && menuData.sensitive || false
Label {
id: buttonMenuLabel
text: menuData && menuData.label || ""
anchors.centerIn: parent
font.bold: true
}
MouseArea {
anchors {
fill: buttonMenuLabel
margins: units.gu(-1)
}
onClicked: menuModel.activate(menuIndex);
}
}
}
Component {
id: sectionMenu;
Menus.SectionMenu {
objectName: "sectionMenu"
property QtObject menuData: null
property var menuIndex: undefined
text: menuData && menuData.label || ""
busy: false
}
}
Component {
id: progressMenu;
Menus.ProgressValueMenu {
objectName: "progressMenu"
property QtObject menuData: null
property int menuIndex: -1
text: menuData && menuData.label || ""
iconSource: menuData && menuData.icon || ""
value : menuData && menuData.actionState || 0.0
enabled: menuData && menuData.sensitive || false
highlightWhenPressed: false
}
}
Component {
id: standardMenu;
Menus.StandardMenu {
objectName: "standardMenu"
property QtObject menuData: null
property int menuIndex: -1
text: menuData && menuData.label || ""
iconSource: menuData && menuData.icon || ""
enabled: menuData && menuData.sensitive || false
highlightWhenPressed: false
onTriggered: {
menuModel.activate(menuIndex);
}
// FIXME : At the moment, the indicators aren't using
// com.canonical.indicators.link for settings menu. Need to fudge it.
property bool settingsMenu: menuData && menuData.action.indexOf("settings") > -1 || false
backColor: settingsMenu ? Qt.rgba(1,1,1,0.07) : "transparent"
component: settingsMenu ? buttonForSettings : undefined
Component {
id: buttonForSettings
Icon {
name: "settings"
height: units.gu(3)
width: height
color: Theme.palette.selected.backgroundText
}
}
}
}
Component {
id: linkMenu;
Menus.StandardMenu {
objectName: "linkMenu"
property QtObject menuData: null
property int menuIndex: -1
text: menuData && menuData.label || ""
iconSource: menuData && menuData.icon || ""
enabled: menuData && menuData.sensitive || false
highlightWhenPressed: false
onTriggered: {
menuModel.activate(menuIndex);
}
backColor: Qt.rgba(1,1,1,0.07)
component: menuData.icon ? icon : undefined
Component {
id: icon
Icon {
source: menuData.icon
height: units.gu(3)
width: height
color: Theme.palette.selected.backgroundText
}
}
}
}
Component {
id: checkableMenu;
Menus.CheckableMenu {
objectName: "checkableMenu"
property QtObject menuData: null
property int menuIndex: -1
property bool serverChecked: menuData && menuData.isToggled || false
text: menuData && menuData.label || ""
enabled: menuData && menuData.sensitive || false
checked: serverChecked
highlightWhenPressed: false
onServerCheckedChanged: updateFromServer()
onTriggered: {
menuModel.activate(menuIndex);
resyncTimer.restart();
}
// value can be changed by menu, so a binding won't work.
function updateFromServer() {
resyncTimer.stop();
if (checked != serverChecked) {
checked = serverChecked;
}
}
// Server value is not guaranteed to change to what we expect from an activation.
// In this case, we need to re-assert that we are presenting the UI with the set backend value.
Timer {
id: resyncTimer
interval: 1500
onTriggered: updateFromServer()
}
}
}
Component {
id: switchMenu;
Menus.SwitchMenu {
objectName: "switchMenu"
property QtObject menuData: null
property int menuIndex: -1
property bool serverChecked: menuData && menuData.isToggled || false
text: menuData && menuData.label || ""
iconSource: menuData && menuData.icon || ""
enabled: menuData && menuData.sensitive || false
checked: serverChecked
highlightWhenPressed: false
onServerCheckedChanged: updateFromServer()
onTriggered: {
menuModel.activate(menuIndex);
resyncTimer.restart();
}
// value can be changed by menu, so a binding won't work.
function updateFromServer() {
resyncTimer.stop();
if (checked != serverChecked) {
checked = serverChecked;
}
}
// Server value is not guaranteed to change to what we expect from an activation.
// In this case, we need to re-assert that we are presenting the UI with the set backend value.
Timer {
id: resyncTimer
interval: 1500
onTriggered: updateFromServer()
}
}
}
Component {
id: alarmMenu;
Menus.EventMenu {
objectName: "alarmMenu"
property QtObject menuData: null
property var menuModel: menuFactory.menuModel
property int menuIndex: -1
property var extendedData: menuData && menuData.ext || undefined
// TODO - bug #1260728
property var timeFormatter: Utils.GDateTimeFormatter {
time: getExtendedProperty(extendedData, "xCanonicalTime", 0)
format: getExtendedProperty(extendedData, "xCanonicalTimeFormat", "")
}
text: menuData && menuData.label || ""
iconSource: menuData && menuData.icon || "image://theme/alarm-clock"
time: timeFormatter.timeString
enabled: menuData && menuData.sensitive || false
highlightWhenPressed: false
onMenuModelChanged: {
loadAttributes();
}
onMenuIndexChanged: {
loadAttributes();
}
onTriggered: {
menuModel.activate(menuIndex);
}
function loadAttributes() {
if (!menuModel || menuIndex == -1) return;
menuModel.loadExtendedAttributes(menuIndex, {'x-canonical-time': 'int64',
'x-canonical-time-format': 'string'});
}
}
}
Component {
id: appointmentMenu;
Menus.EventMenu {
objectName: "appointmentMenu"
property QtObject menuData: null
property var menuModel: menuFactory.menuModel
property int menuIndex: -1
property var extendedData: menuData && menuData.ext || undefined
// TODO - bug #1260728
property var timeFormatter: Utils.GDateTimeFormatter {
time: getExtendedProperty(extendedData, "xCanonicalTime", 0)
format: getExtendedProperty(extendedData, "xCanonicalTimeFormat", "")
}
text: menuData && menuData.label || ""
iconSource: menuData && menuData.icon || "image://theme/calendar"
time: timeFormatter.timeString
eventColor: getExtendedProperty(extendedData, "xCanonicalColor", Qt.rgba(0.0, 0.0, 0.0, 0.0))
enabled: menuData && menuData.sensitive || false
highlightWhenPressed: false
onMenuModelChanged: {
loadAttributes();
}
onMenuIndexChanged: {
loadAttributes();
}
onTriggered: {
menuModel.activate(menuIndex);
}
function loadAttributes() {
if (!menuModel || menuIndex == -1) return;
menuModel.loadExtendedAttributes(menuIndex, {'x-canonical-color': 'string',
'x-canonical-time': 'int64',
'x-canonical-time-format': 'string'});
}
}
}
Component {
id: wifiSection;
Menus.SectionMenu {
objectName: "wifiSection"
property QtObject menuData: null
property var menuModel: menuFactory.menuModel
property int menuIndex: -1
property var extendedData: menuData && menuData.ext || undefined
text: menuData && menuData.label || ""
busy: getExtendedProperty(extendedData, "xCanonicalBusyAction", false)
onMenuModelChanged: {
loadAttributes();
}
onMenuIndexChanged: {
loadAttributes();
}
function loadAttributes() {
if (!menuModel || menuIndex == -1) return;
menuModel.loadExtendedAttributes(menuIndex, {'x-canonical-busy-action': 'bool'})
}
}
}
Component {
id: accessPoint;
Menus.AccessPointMenu {
objectName: "accessPoint"
property QtObject menuData: null
property var menuModel: menuFactory.menuModel
property int menuIndex: -1
property var extendedData: menuData && menuData.ext || undefined
property bool serverChecked: menuData && menuData.isToggled || false
property var strengthAction: UnityMenuAction {
model: menuModel
index: menuIndex
name: getExtendedProperty(extendedData, "xCanonicalWifiApStrengthAction", "")
}
text: menuData && menuData.label || ""
enabled: menuData && menuData.sensitive || false
active: serverChecked
secure: getExtendedProperty(extendedData, "xCanonicalWifiApIsSecure", false)
adHoc: getExtendedProperty(extendedData, "xCanonicalWifiApIsAdhoc", false)
signalStrength: strengthAction.valid ? strengthAction.state : 0
highlightWhenPressed: false
onServerCheckedChanged: {
// value can be changed by menu, so a binding won't work.
active = serverChecked;
}
onMenuModelChanged: {
loadAttributes();
}
onMenuIndexChanged: {
loadAttributes();
}
onTriggered: {
menuModel.activate(menuIndex);
}
function loadAttributes() {
if (!menuModel || menuIndex == -1) return;
menuModel.loadExtendedAttributes(menuIndex, {'x-canonical-wifi-ap-is-adhoc': 'bool',
'x-canonical-wifi-ap-is-secure': 'bool',
'x-canonical-wifi-ap-strength-action': 'string'});
}
}
}
Component {
id: modeminfoitem;
ModemInfoItem {
objectName: "modemInfoItem"
property QtObject menuData: null
property var menuModel: menuFactory.menuModel
property int menuIndex: -1
property var extendedData: menuData && menuData.ext || undefined
highlightWhenPressed: false
property var statusLabelAction: UnityMenuAction {
model: menuModel
index: menuIndex
name: getExtendedProperty(extendedData, "xCanonicalModemStatusLabelAction", "")
}
statusText: statusLabelAction.valid ? statusLabelAction.state : ""
property var statusIconAction: UnityMenuAction {
model: menuModel
index: menuIndex
name: getExtendedProperty(extendedData, "xCanonicalModemStatusIconAction", "")
}
statusIcon: statusIconAction.valid ? statusIconAction.state : ""
property var connectivityIconAction: UnityMenuAction {
model: menuModel
index: menuIndex
name: getExtendedProperty(extendedData, "xCanonicalModemConnectivityIconAction", "")
}
connectivityIcon: connectivityIconAction.valid ? connectivityIconAction.state : ""
property var simIdentifierLabelAction: UnityMenuAction {
model: menuModel
index: menuIndex
name: getExtendedProperty(extendedData, "xCanonicalModemSimIdentifierLabelAction", "")
}
simIdentifierText: simIdentifierLabelAction.valid ? simIdentifierLabelAction.state : ""
property var roamingAction: UnityMenuAction {
model: menuModel
index: menuIndex
name: getExtendedProperty(extendedData, "xCanonicalModemRoamingAction", "")
}
roaming: roamingAction.valid ? roamingAction.state : false
property var unlockAction: UnityMenuAction {
model: menuModel
index: menuIndex
name: getExtendedProperty(extendedData, "xCanonicalModemLockedAction", "")
}
onUnlock: {
unlockAction.activate();
}
locked: unlockAction.valid ? unlockAction.state : false
onMenuModelChanged: {
loadAttributes();
}
onMenuIndexChanged: {
loadAttributes();
}
function loadAttributes() {
if (!menuModel || menuIndex == -1) return;
menuModel.loadExtendedAttributes(menuIndex, {'x-canonical-modem-status-label-action': 'string',
'x-canonical-modem-status-icon-action': 'string',
'x-canonical-modem-connectivity-icon-action': 'string',
'x-canonical-modem-sim-identifier-label-action': 'string',
'x-canonical-modem-roaming-action': 'string',
'x-canonical-modem-locked-action': 'string'});
}
}
}
Component {
id: messageItem
MessageMenuItemFactory {
objectName: "messageItem"
menuModel: menuFactory.menuModel
}
}
Component {
id: groupedMessage
Menus.GroupedMessageMenu {
objectName: "groupedMessage"
property QtObject menuData: null
property var menuModel: menuFactory.menuModel
property int menuIndex: -1
property var extendedData: menuData && menuData.ext || undefined
text: menuData && menuData.label || ""
iconSource: getExtendedProperty(extendedData, "icon", "image://theme/message")
count: menuData && menuData.actionState.length > 0 ? menuData.actionState[0] : "0"
enabled: menuData && menuData.sensitive || false
highlightWhenPressed: false
removable: true
onMenuModelChanged: {
loadAttributes();
}
onMenuIndexChanged: {
loadAttributes();
}
onClicked: {
menuModel.activate(menuIndex, true);
}
onDismissed: {
menuModel.activate(menuIndex, false);
}
function loadAttributes() {
if (!menuModel || menuIndex == -1) return;
menuModel.loadExtendedAttributes(modelIndex, {'icon': 'icon'});
}
}
}
Component {
id: mediaPayerMenu;
Menus.MediaPlayerMenu {
objectName: "mediaPayerMenu"
property QtObject menuData: null
property var menuModel: menuFactory.menuModel
property int menuIndex: -1
property var actionState: menuData && menuData.actionState || undefined
property bool running: getExtendedProperty(actionState, "running", false)
playerIcon: menuData && menuData.icon || "image://theme/stock_music"
playerName: menuData && menuData.label || i18n.tr("Nothing is playing")
albumArt: getExtendedProperty(actionState, "art-url", "image://theme/stock_music")
song: getExtendedProperty(actionState, "title", "")
artist: getExtendedProperty(actionState, "artist", "")
album: getExtendedProperty(actionState, "album", "")
showTrack: running && (state == "Playing" || state == "Paused")
state: getExtendedProperty(actionState, "state", "")
enabled: menuData && menuData.sensitive || false
highlightWhenPressed: false
showDivider: false
onTriggered: {
model.activate(modelIndex);
}
}
}
Component {
id: playbackItemMenu;
Menus.PlaybackItemMenu {
objectName: "playbackItemMenu"
property QtObject menuData: null
property var menuModel: menuFactory.menuModel
property int menuIndex: -1
property var extendedData: menuData && menuData.ext || undefined
property var playAction: UnityMenuAction {
model: menuModel
index: menuIndex
name: getExtendedProperty(extendedData, "xCanonicalPlayAction", "")
}
property var nextAction: UnityMenuAction {
model: menuModel
index: menuIndex
name: getExtendedProperty(extendedData, "xCanonicalNextAction", "")
}
property var previousAction: UnityMenuAction {
model: menuModel
index: menuIndex
name: getExtendedProperty(extendedData, "xCanonicalPreviousAction", "")
}
playing: playAction.state === "Playing"
canPlay: playAction.valid
canGoNext: nextAction.valid
canGoPrevious: previousAction.valid
enabled: menuData && menuData.sensitive || false
highlightWhenPressed: false
onPlay: {
playAction.activate();
}
onNext: {
nextAction.activate();
}
onPrevious: {
previousAction.activate();
}
onMenuModelChanged: {
loadAttributes();
}
onMenuIndexChanged: {
loadAttributes();
}
function loadAttributes() {
if (!menuModel || menuIndex == -1) return;
menuModel.loadExtendedAttributes(modelIndex, {'x-canonical-play-action': 'string',
'x-canonical-next-action': 'string',
'x-canonical-previous-action': 'string'});
}
}
}
Component {
id: transferMenu
Menus.TransferMenu {
objectName: "transferMenu"
id: transfer
property QtObject menuData: null
property var menuModel: menuFactory.menuModel
property int menuIndex: -1
property var extendedData: menuData && menuData.ext || undefined
property var uid: getExtendedProperty(extendedData, "xCanonicalUid", undefined)
text: menuData && menuData.label || ""
iconSource: menuData && menuData.icon || "image://theme/transfer-none"
maximum: 1.0
enabled: menuData && menuData.sensitive || false
highlightWhenPressed: false
removable: true
confirmRemoval: true
QDBusActionGroup {
id: actionGroup
busType: 1
busName: rootModel.busName
objectPath: rootModel.actions["indicator"]
property var activateAction: action("activate-transfer")
property var cancelAction: action("cancel-transfer")
property var transferStateAction: uid !== undefined ? action("transfer-state."+uid) : null
Component.onCompleted: actionGroup.start()
}
property var transferState: {
if (actionGroup.transferStateAction === null) return undefined;
return actionGroup.transferStateAction.valid ? actionGroup.transferStateAction.state : undefined
}
property var runningState : transferState !== undefined ? transferState["state"] : undefined
property var secondsLeft : transferState !== undefined ? transferState["seconds-left"] : undefined
active: runningState !== undefined && runningState !== Menus.TransferState.Finished
progress: transferState !== undefined ? transferState["percent"] : 0.0
// TODO - Should be in the SDK
property var timeRemaining: {
if (secondsLeft === undefined) return undefined;
var remaining = "";
var hours = Math.floor(secondsLeft / (60 * 60));
var minutes = Math.floor(secondsLeft / 60) % 60;
var seconds = secondsLeft % 60;
if (hours > 0) {
remaining += hours + (hours == 1 ? " hour" : " hours");
}
if (minutes > 0) {
if (remaining != "") remaining += ", ";
remaining += minutes + (minutes == 1 ? " minute" : " minutes");
}
// don't include seconds if hours > 0
if (hours == 0 && minutes < 5 && seconds > 0) {
if (remaining != "") remaining += ", ";
remaining += seconds + (seconds == 1 ? " second" : " seconds");
}
if (remaining == "")
remaining = "0 seconds";
return remaining + " remaining";
}
stateText: {
switch (runningState) {
case Menus.TransferState.Queued:
return i18n.tr("In queue…");
case Menus.TransferState.Hashing:
case Menus.TransferState.Processing:
case Menus.TransferState.Running:
return timeRemaining === undefined ? i18n.tr("Downloading") : timeRemaining;
case Menus.TransferState.Paused:
return i18n.tr("Paused, tap to resume");
case Menus.TransferState.Canceled:
return i18n.tr("Canceled");
case Menus.TransferState.Finished:
return i18n.tr("Finished");
case Menus.TransferState.Error:
return i18n.tr("Failed, tap to retry");
}
return "";
}
onMenuModelChanged: {
loadAttributes();
}
onMenuIndexChanged: {
loadAttributes();
}
onTriggered: {
actionGroup.activateAction.activate(uid);
}
onItemRemoved: {
actionGroup.cancelAction.activate(uid);
}
function loadAttributes() {
if (!menuModel || menuIndex == -1) return;
menuModel.loadExtendedAttributes(menuIndex, {'x-canonical-uid': 'string'});
}
}
}
Component {
id: buttonSectionMenu;
Menus.StandardMenu {
objectName: "buttonSectionMenu"
property QtObject menuData: null
property var menuModel: menuFactory.menuModel
property int menuIndex: -1
property var extendedData: menuData && menuData.ext || undefined
iconSource: menuData && menuData.icon || ""
enabled: menuData && menuData.sensitive || false
highlightWhenPressed: false
text: menuData && menuData.label || ""
foregroundColor: Theme.palette.normal.backgroundText
onMenuModelChanged: {
loadAttributes();
}
onMenuIndexChanged: {
loadAttributes();
}
function loadAttributes() {
if (!menuModel || menuIndex == -1) return;
menuModel.loadExtendedAttributes(menuIndex, {'x-canonical-extra-label': 'string'});
}
component: Component {
Button {
objectName: "buttonSectionMenuControl"
text: getExtendedProperty(extendedData, "xCanonicalExtraLabel", "")
onClicked: {
menuModel.activate(menuIndex);
}
}
}
}
}
function load(modelData, context) {
if (modelData.type !== undefined && modelData.type !== "") {
var component = undefined;
var contextComponents = _map[context];
if (contextComponents !== undefined) {
component = contextComponents[modelData.type];
}
if (component === undefined) {
component = _map["default"][modelData.type];
}
if (component !== undefined) {
return component;
}
console.debug("Don't know how to make " + modelData.type + " for " + context);
}
if (modelData.isCheck || modelData.isRadio) {
return checkableMenu;
}
if (modelData.isSeparator) {
return separatorMenu;
}
return standardMenu;
}
}