on xenial help sign in
app & touch menu tweaks
3281137Nick Dedekind
qml/ApplicationMenus/ApplicationMenuItemFactory.qmlnew file+135
@@ -0,0 +1,135 @@
1+/*
2+ * Copyright 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 Ubuntu.Settings.Menus 0.1 as Menus
19+import Ubuntu.Settings.Components 0.1
20+import QMenuModel 0.1
21+import Utils 0.1 as Utils
22+import Ubuntu.Components.ListItems 1.3 as ListItems
23+import Ubuntu.Components 1.3
24+
25+Object {
26+ id: menuFactory
27+
28+ property string context
29+ property var rootModel: null
30+ property var menuModel: null
31+
32+ Component {
33+ id: applicationMenu;
34+
35+ ListItem {
36+ property QtObject menuData: null
37+ property int menuIndex: -1
38+
39+ height: layout.height
40+ enabled: menuData && menuData.sensitive || false
41+ divider.visible: false
42+
43+ onClicked: {
44+ menuModel.activate(menuIndex);
45+ }
46+
47+ Action {
48+ id: action
49+ text: menuData.label.replace("_", "&")
50+ }
51+
52+ ListItemLayout {
53+ id: layout
54+ title.text: action.text
55+
56+ Icon {
57+ source: menuData && menuData.icon || ""
58+ SlotsLayout.position: SlotsLayout.Leading
59+ height: units.gu(3)
60+ }
61+
62+ Label {
63+ text: menuData.shortcut
64+ visible: menuData.shortcut && QuickUtils.keyboardAttached
65+ SlotsLayout.position: SlotsLayout.Trailing
66+ color: enabled ? theme.palette.normal.backgroundSecondaryText :
67+ theme.palette.disabled.backgroundSecondaryText
68+ }
69+ }
70+ }
71+ }
72+
73+ Component {
74+ id: submenu
75+
76+ ListItem {
77+ property QtObject menuData: null
78+ property int menuIndex: -1
79+
80+ height: layout.height
81+ enabled: menuData && menuData.sensitive || false
82+ divider.visible: false
83+
84+ onClicked: {
85+ menuModel.activate(menuIndex);
86+ }
87+
88+ Action {
89+ id: action
90+ text: menuData.label.replace("_", "&")
91+ }
92+
93+ ListItemLayout {
94+ id: layout
95+ title.text: action.text
96+
97+ Icon {
98+ source: menuData && menuData.icon || ""
99+ SlotsLayout.position: SlotsLayout.Leading
100+ height: units.gu(3)
101+ }
102+
103+ Icon {
104+ name: "toolkit_chevron-ltr_1gu"
105+ SlotsLayout.position: SlotsLayout.Trailing
106+ width: units.gu(2)
107+ color: enabled ? theme.palette.normal.backgroundSecondaryText :
108+ theme.palette.disabled.backgroundSecondaryText
109+ }
110+ }
111+ }
112+ }
113+
114+ Component {
115+ id: applicationMenuSeparator;
116+
117+ ListItems.Divider {
118+ }
119+ }
120+
121+ function load(modelData) {
122+ if (modelData.isSeparator) {
123+ return applicationMenuSeparator;
124+ }
125+ if (modelData.isRadio) {
126+ }
127+ if (modelData.isCheck) {
128+
129+ }
130+ if (modelData.hasSubmenu) {
131+ return submenu;
132+ }
133+ return applicationMenu;
134+ }
135+}
qml/ApplicationMenus/MenuBarLoader.qml+4−4
@@ -30,6 +30,9 @@ Item {
30
30
31 readonly property bool showRequested: altFilter.longAltPressed || d.currentItem != null
31 readonly property bool showRequested: altFilter.longAltPressed || d.currentItem != null
32
32
33+ implicitWidth: row.width + units.gu(1)
34+ height: parent.height
35+
33 WindowInputFilter {
36 WindowInputFilter {
34 id: altFilter
37 id: altFilter
35 property bool altPressed: false
38 property bool altPressed: false
@@ -63,9 +66,6 @@ Item {
63 }
66 }
64 }
67 }
65
68
66− implicitWidth: row.width + units.gu(1)
67− height: parent.height
68−
69 InverseMouseArea {
69 InverseMouseArea {
70 acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
70 acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
71 anchors.fill: parent
71 anchors.fill: parent
@@ -80,7 +80,7 @@ Item {
80 ActionContext {
80 ActionContext {
81 id: menuBarContext
81 id: menuBarContext
82 objectName: "menuBarContext"
82 objectName: "menuBarContext"
83− active: d.currentItem == null && altFilter.altPressed
83+ active: true
84 }
84 }
85
85
86 Row {
86 Row {
qml/ApplicationMenus/MenuItemLoader.qml+8−5
@@ -32,11 +32,10 @@ ActionItem {
32 val += units.gu(1) + icon.width
32 val += units.gu(1) + icon.width
33 }
33 }
34 val += units.gu(1) + title.contentWidth;
34 val += units.gu(1) + title.contentWidth;
35− if (shortcut != undefined) {
36− val += units.gu(3) + shortcutLabel.contentWidth;
37− }
38 if (hasSubmenu) {
35 if (hasSubmenu) {
39 val += units.gu(1) + chevronIcon.width;
36 val += units.gu(1) + chevronIcon.width;
37+ } else if (shortcut != undefined) {
38+ val += units.gu(3) + shortcutLabel.contentWidth;
40 }
39 }
41 return val + units.gu(1);
40 return val + units.gu(1);
42 }
41 }
@@ -111,9 +110,11 @@ ActionItem {
111 elide: Text.ElideNone
110 elide: Text.ElideNone
112 wrapMode: Text.NoWrap
111 wrapMode: Text.NoWrap
113 clip: true
112 clip: true
113+ color: enabled ? theme.palette.normal.backgroundSecondaryText :
114+ theme.palette.disabled.backgroundSecondaryText
114
115
115− visible: root.shortcut != undefined
116+ visible: menuData.shortcut != undefined && !root.hasSubmenu && QuickUtils.keyboardAttached
116− text: root.shortcut ? root.shortcut : ""
117+ text: menuData.shortcut ? menuData.shortcut : ""
117 }
118 }
118 }
119 }
119
120
@@ -121,6 +122,8 @@ ActionItem {
121 id: chevronIcon
122 id: chevronIcon
122 width: units.gu(2)
123 width: units.gu(2)
123 height: units.gu(2)
124 height: units.gu(2)
125+ color: enabled ? theme.palette.normal.backgroundSecondaryText :
126+ theme.palette.disabled.backgroundSecondaryText
124
127
125 visible: root.hasSubmenu
128 visible: root.hasSubmenu
126 name: "chevron"
129 name: "chevron"
qml/ApplicationMenus/MenuLoader.qml+7−1
@@ -133,6 +133,12 @@ UbuntuShape {
133 }
133 }
134 }
134 }
135
135
136+ ActionContext {
137+ id: menuContext
138+ objectName: "menuContext"
139+ active: true
140+ }
141+
136 ColumnLayout {
142 ColumnLayout {
137 id: container
143 id: container
138 objectName: "container"
144 objectName: "container"
@@ -256,7 +262,7 @@ UbuntuShape {
256
262
257 property Item popup: null
263 property Item popup: null
258
264
259− onTriggered: {
265+ action.onTriggered: {
260 if (hasSubmenu) {
266 if (hasSubmenu) {
261 if (!popup) {
267 if (!popup) {
262 var model = root.unityMenuModel.submenu(__ownIndex);
268 var model = root.unityMenuModel.submenu(__ownIndex);
qml/Panel/Indicators/MenuItemFactory.qml → qml/Panel/Indicators/IndicatorMenuItemFactory.qmlrenamed+1−86
@@ -25,7 +25,7 @@ import Ubuntu.Components 1.3
25import Unity.Session 0.1
25import Unity.Session 0.1
26import Unity.Platform 1.0
26import Unity.Platform 1.0
27
27
28−Item {
28+Object {
29 id: menuFactory
29 id: menuFactory
30
30
31 property string context
31 property string context
@@ -1042,92 +1042,7 @@ Item {
1042 }
1042 }
1043 }
1043 }
1044
1044
1045− Component {
1046− id: applicationMenu;
1047−
1048− ListItem {
1049− property QtObject menuData: null
1050− property int menuIndex: -1
1051−
1052− height: layout.height
1053− enabled: menuData && menuData.sensitive || false
1054− divider.visible: false
1055−
1056− onClicked: {
1057− menuModel.activate(menuIndex);
1058− }
1059−
1060− ListItemLayout {
1061− id: layout
1062− title.text: menuData && menuData.label || ""
1063−
1064− Icon {
1065− source: menuData && menuData.icon || ""
1066− SlotsLayout.position: SlotsLayout.Leading
1067− height: units.gu(3)
1068− }
1069−
1070− }
1071− }
1072− }
1073−
1074− Component {
1075− id: submenu
1076−
1077− ListItem {
1078− property QtObject menuData: null
1079− property int menuIndex: -1
1080−
1081− height: layout.height
1082− enabled: menuData && menuData.sensitive || false
1083− divider.visible: false
1084−
1085− onClicked: {
1086− menuModel.activate(menuIndex);
1087− }
1088−
1089− ListItemLayout {
1090− id: layout
1091− title.text: menuData && menuData.label || ""
1092−
1093− Icon {
1094− source: menuData && menuData.icon || ""
1095− SlotsLayout.position: SlotsLayout.Leading
1096− height: units.gu(3)
1097− }
1098−
1099− Icon {
1100− name: "toolkit_chevron-ltr_1gu"
1101− SlotsLayout.position: SlotsLayout.Trailing
1102− width: units.gu(2)
1103− }
1104− }
1105− }
1106− }
1107−
1108− Component {
1109− id: applicationMenuSeparator;
1110−
1111− ListItems.Divider {
1112− }
1113− }
1114−
1115 function load(modelData) {
1045 function load(modelData) {
1116− if (context == "application-menu") {
1117− if (modelData.isSeparator) {
1118− return applicationMenuSeparator;
1119− }
1120− if (modelData.isRadio) {
1121− }
1122− if (modelData.isCheck) {
1123−
1124− }
1125− if (modelData.hasSubmenu) {
1126− return submenu;
1127− }
1128− return applicationMenu;
1129− }
1130−
1131 // tweak indicator-session items
1046 // tweak indicator-session items
1132 if (context === "indicator-session") {
1047 if (context === "indicator-session") {
1133 if ((modelData.action === "indicator.logout" || modelData.action === "indicator.suspend" || modelData.action === "indicator.hibernate" ||
1048 if ((modelData.action === "indicator.logout" || modelData.action === "indicator.suspend" || modelData.action === "indicator.hibernate" ||
qml/Panel/MenuContent.qml−1
@@ -61,7 +61,6 @@ Rectangle {
61
61
62 width: ListView.view.width
62 width: ListView.view.width
63 height: ListView.view.height
63 height: ListView.view.height
64− objectName: item ? item.context : ""
65 asynchronous: true
64 asynchronous: true
66 visible: ListView.isCurrentItem
65 visible: ListView.isCurrentItem
67
66
qml/Panel/Panel.qml+11−3
@@ -199,9 +199,12 @@ Item {
199
199
200 pageDelegate: PanelMenuPage {
200 pageDelegate: PanelMenuPage {
201 id: page
201 id: page
202− context: "application-menu"
203 menuModel: __applicationMenus.model
202 menuModel: __applicationMenus.model
204 submenuIndex: modelIndex
203 submenuIndex: modelIndex
204+
205+ factory: ApplicationMenuItemFactory {
206+ rootModel: __applicationMenus.model
207+ }
205 }
208 }
206
209
207 enabled: !root.locked && model
210 enabled: !root.locked && model
@@ -440,11 +443,16 @@ Item {
440
443
441 pageDelegate: PanelMenuPage {
444 pageDelegate: PanelMenuPage {
442 id: page
445 id: page
443− objectName: context + "-page"
444− context: modelData.identifier
446+ objectName: modelData.identifier + "-page"
445 submenuIndex: 0
447 submenuIndex: 0
446
448
447 menuModel: delegate.menuModel
449 menuModel: delegate.menuModel
450+
451+ factory: IndicatorMenuItemFactory {
452+ context: modelData.identifier
453+ rootModel: delegate.menuModel
454+ }
455+
448 IndicatorDelegate {
456 IndicatorDelegate {
449 id: delegate
457 id: delegate
450 busName: modelData.indicatorProperties.busName
458 busName: modelData.indicatorProperties.busName
qml/Panel/PanelMenuPage.qml+37−23
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright 2013-2014 Canonical Ltd.
2+ * Copyright 2013-2016 Canonical Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU Lesser General Public License as published by
@@ -25,26 +25,47 @@ PageStack {
25 id: root
25 id: root
26
26
27 property var submenuIndex: undefined
27 property var submenuIndex: undefined
28− property string context
29 property QtObject menuModel: null
28 property QtObject menuModel: null
29+ property Component factory
30
30
31 Connections {
31 Connections {
32+ id: dynamicChanges
32 target: root.menuModel
33 target: root.menuModel
34+ property bool ready: false
33
35
34 // fix async creation with signal from model before it's finished.
36 // fix async creation with signal from model before it's finished.
35− onRowsInserted: root.reset()
37+ onRowsInserted: {
38+ if (submenuIndex !== undefined && first <= submenuIndex) {
39+ reset(true);
40+ }
41+ }
36− onRowsRemoved: root.reset()
42+ onRowsRemoved: {
43+ if (submenuIndex !== undefined && first <= submenuIndex) {
44+ reset(true);
45+ }
46+ }
37− onModelReset: root.reset()
47+ onModelReset: {
48+ if (root.submenuIndex !== undefined) {
49+ reset(true);
50+ }
51+ }
38 }
52 }
39
53
40 Component.onCompleted: {
54 Component.onCompleted: {
41− reset();
55+ reset(true);
56+ dynamicChanges.ready = true;
42 }
57 }
43
58
44− function reset() {
59+ function reset(clearModel) {
60+ if (clearModel) {
45− clear();
61+ clear();
46− var model = submenuIndex == undefined ? menuModel : menuModel.submenu(submenuIndex)
62+ var model = submenuIndex == undefined ? menuModel : menuModel.submenu(submenuIndex)
63+ if (model) {
47− push(pageComponent, { "menuModel": model });
64+ push(pageComponent, { "menuModel": model });
65+ }
66+ } else {
67+ root.currentPage.reset();
68+ }
48 }
69 }
49
70
50 Component {
71 Component {
@@ -56,13 +77,12 @@ PageStack {
56 property alias title: backLabel.title
77 property alias title: backLabel.title
57 property bool isSubmenu: false
78 property bool isSubmenu: false
58
79
59− MenuItemFactory {
80+ function reset() {
60− id: factory
81+ mainMenu.positionViewAtBeginning();
61− context: root.context
62− rootModel: root.menuModel ? root.menuModel : null
63− menuModel: mainMenu.model ? mainMenu.model : null
64 }
82 }
65
83
84+ property QtObject factory: root.factory.createObject(page, { menuModel: page.menuModel } )
85+
66 header: PageHeader {
86 header: PageHeader {
67 id: backLabel
87 id: backLabel
68 visible: page.isSubmenu
88 visible: page.isSubmenu
@@ -151,7 +171,7 @@ PageStack {
151 visible: status == Loader.Ready
171 visible: status == Loader.Ready
152
172
153 property int modelIndex: index
173 property int modelIndex: index
154− sourceComponent: factory.load(model)
174+ sourceComponent: page.factory.load(model)
155
175
156 onLoaded: {
176 onLoaded: {
157 if (item.hasOwnProperty("selected")) {
177 if (item.hasOwnProperty("selected")) {
@@ -174,7 +194,7 @@ PageStack {
174 if (model.hasSubmenu) {
194 if (model.hasSubmenu) {
175 root.push(pageComponent, {
195 root.push(pageComponent, {
176 "isSubmenu": true,
196 "isSubmenu": true,
177− "title": model.label,
197+ "title": model.label.replace(/_|&/, ""),
178 "menuModel": page.menuModel.submenu(modelIndex)
198 "menuModel": page.menuModel.submenu(modelIndex)
179 });
199 });
180 }
200 }
@@ -202,10 +222,4 @@ PageStack {
202 }
222 }
203 }
223 }
204 }
224 }
205−
206−// function reset()
207−// {
208−// menuStack.reset();
209−// mainMenu.positionViewAtBeginning();
210−// }
211}
225}
tests/qmltests/Panel/Indicators/tst_MenuItemFactory.qml → tests/qmltests/Panel/Indicators/tst_IndicatorMenuItemFactory.qmlrenamed+2−2
@@ -28,7 +28,7 @@ Item {
28 width: units.gu(40)
28 width: units.gu(40)
29 height: units.gu(70)
29 height: units.gu(70)
30
30
31− MenuItemFactory {
31+ IndicatorMenuItemFactory {
32 id: factory
32 id: factory
33 menuModel: UnityMenuModel {}
33 menuModel: UnityMenuModel {}
34 }
34 }
@@ -51,7 +51,7 @@ Item {
51 }
51 }
52
52
53 UT.UnityTestCase {
53 UT.UnityTestCase {
54− name: "MenuItemFactory"
54+ name: "IndicatorMenuItemFactory"
55 when: windowShown
55 when: windowShown
56
56
57 property QtObject menuData: QtObject {
57 property QtObject menuData: QtObject {
tests/qmltests/Panel/tst_Panel.qml+18−6
@@ -37,20 +37,18 @@ PanelTest {
37 color: "black"
37 color: "black"
38
38
39 Binding {
39 Binding {
40− target: mouseEmulation
40+ target: QuickUtils
41− property: "checked"
41+ property: "keyboardAttached"
42− value: !windowControlsCB.checked
42+ value: keyboardAttached.checked
43 }
43 }
44
44
45 DesktopMenuData { id: appMenuData }
45 DesktopMenuData { id: appMenuData }
46
46
47 Component.onCompleted: {
47 Component.onCompleted: {
48− QuickUtils.keyboardAttached = true;
49 theme.name = "Ubuntu.Components.Themes.SuruDark"
48 theme.name = "Ubuntu.Components.Themes.SuruDark"
50
49
51 ApplicationMenuRegistry.RegisterSurfaceMenu("dialerAppSurfaceId", "/dialerapp", "/dialerapp", ":2");
50 ApplicationMenuRegistry.RegisterSurfaceMenu("dialerAppSurfaceId", "/dialerapp", "/dialerapp", ":2");
52 Indicators.UnityMenuModelCache.setCachedModelData("/dialerapp", appMenuData.dialerData);
51 Indicators.UnityMenuModelCache.setCachedModelData("/dialerapp", appMenuData.dialerData);
53− PanelState.title = "AppMenu";
54 }
52 }
55
53
56 Rectangle {
54 Rectangle {
@@ -78,7 +76,7 @@ PanelTest {
78 Panel {
76 Panel {
79 id: panel
77 id: panel
80 anchors.fill: parent
78 anchors.fill: parent
81− mode: "staged"
79+ mode: modeSelector.model[modeSelector.selectedIndex]
82
80
83 indicatorMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
81 indicatorMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
84 applicationMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
82 applicationMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
@@ -109,12 +107,14 @@ PanelTest {
109 Layout.fillWidth: false
107 Layout.fillWidth: false
110
108
111 ListItem.ItemSelector {
109 ListItem.ItemSelector {
110+ id: modeSelector
112 anchors { left: parent.left; right: parent.right }
111 anchors { left: parent.left; right: parent.right }
113 activeFocusOnPress: false
112 activeFocusOnPress: false
114 text: "Mode"
113 text: "Mode"
115 model: ["staged", "windowed" ]
114 model: ["staged", "windowed" ]
116 onSelectedIndexChanged: {
115 onSelectedIndexChanged: {
117 panel.mode = model[selectedIndex];
116 panel.mode = model[selectedIndex];
117+ keyboardAttached.checked = panel.mode == "windowed"
118 }
118 }
119 }
119 }
120
120
@@ -223,6 +223,18 @@ PanelTest {
223 MouseTouchEmulationCheckbox {
223 MouseTouchEmulationCheckbox {
224 id: mouseEmulation
224 id: mouseEmulation
225 color: "white"
225 color: "white"
226+ checked: panel.mode == "staged"
227+ }
228+
229+ RowLayout {
230+ Layout.fillWidth: true
231+ CheckBox {
232+ id: keyboardAttached
233+ }
234+ Label {
235+ text: "Keyboard Attached"
236+ color: "white"
237+ }
226 }
238 }
227 }
239 }
228 }
240 }
tests/qmltests/Stages/DesktopMenuData.qml+13−13
@@ -5,38 +5,39 @@ QtObject {
5 property var dialerData: [{
5 property var dialerData: [{
6 "rowData": { // 1.1
6 "rowData": { // 1.1
7 "label": "&dialer1",
7 "label": "&dialer1",
8− "action": "dialer1",
8+ "action": "dialer1"
9− "shortcut": "Alt+F"
10 },
9 },
11 "submenu": [{
10 "submenu": [{
12 "rowData": { // 1.1
11 "rowData": { // 1.1
13− "label": "menu1.1",
12+ "label": "&menu1.1",
14 "action": "menu1.1",
13 "action": "menu1.1",
15− "shortcut": "Alt+0"
14+ "shortcut": "Ctrl+m"
16 }}, {
15 }}, {
17 "rowData": { // 1.2
16 "rowData": { // 1.2
18− "label": "menu1.2",
17+ "label": "m&enu1.2",
19 "action": "menu1.2",
18 "action": "menu1.2",
20− "shortcut": "Alt+1"
19+ "shortcut": "Ctrl+e"
21 },
20 },
22 "submenu": [{
21 "submenu": [{
23 "rowData": { // 1.2.1
22 "rowData": { // 1.2.1
24− "label": "menu1.2.1",
23+ "label": "me&nu1.2.1",
25− "action": "menu1.2.1",
24+ "action": "menu1.2.1"
26− "shortcut": ""
27 }}, {
25 }}, {
28 "rowData": { // 1.2.2
26 "rowData": { // 1.2.2
29 "label": "menu1.2.2",
27 "label": "menu1.2.2",
30 "action": "menu1.2.2"
28 "action": "menu1.2.2"
31 }}, {
29 }}, {
32 "rowData": { // 1.2.3
30 "rowData": { // 1.2.3
33− "sensitive": false,
34 "isSeparator": true
31 "isSeparator": true
35 }}, {
32 }}, {
36 "rowData": { // row 1.2.4
33 "rowData": { // row 1.2.4
37 "label": "menu1.2.4",
34 "label": "menu1.2.4",
38 "action": "menu1.2.4",
35 "action": "menu1.2.4",
39 "isToggled": true
36 "isToggled": true
37+ }}, {
38+ "rowData": { // row 1.2.5
39+ "label": "menu1.2.5",
40+ "sensitive": false,
40 }}
41 }}
41 ]}, {
42 ]}, {
42 "rowData": { // 1.3
43 "rowData": { // 1.3
@@ -46,13 +47,12 @@ QtObject {
46 "rowData": { // row 1.4
47 "rowData": { // row 1.4
47 "label": "menu1.4",
48 "label": "menu1.4",
48 "action": "menu1.4",
49 "action": "menu1.4",
49− "shortcut": "Alt+2"
50+ "shortcut": "Ctrl+n"
50 }}
51 }}
51 ]}, {
52 ]}, {
52 "rowData": { // 2
53 "rowData": { // 2
53 "label": "d&ialer2",
54 "label": "d&ialer2",
54− "action": "dialer2",
55+ "action": "dialer2"
55− "shortcut": "Alt+E"
56 },
56 },
57 "submenu": [{
57 "submenu": [{
58 "rowData": { // 2.1
58 "rowData": { // 2.1