on xenial help sign in
fixed menu navigation
f7a888fNick Dedekind
qml/ApplicationMenus/MenuBarLoader.qml+21−16
@@ -33,6 +33,10 @@ Item {
33 implicitWidth: row.width + units.gu(1)
33 implicitWidth: row.width + units.gu(1)
34 height: parent.height
34 height: parent.height
35
35
36+ function dismiss() {
37+ d.dismissAll();
38+ }
39+
36 WindowInputFilter {
40 WindowInputFilter {
37 id: altFilter
41 id: altFilter
38 property bool altPressed: false
42 property bool altPressed: false
@@ -70,17 +74,7 @@ Item {
70 acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
74 acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
71 anchors.fill: parent
75 anchors.fill: parent
72 enabled: d.currentItem != null
76 enabled: d.currentItem != null
73− onPressed: {
77+ onPressed: d.dismissAll()
74− if (d.currentItem) {
75− d.currentItem.dismiss();
76− }
77− }
78− }
79−
80− ActionContext {
81− id: menuBarContext
82− objectName: "menuBarContext"
83− active: true
84 }
78 }
85
79
86 Row {
80 Row {
@@ -89,6 +83,12 @@ Item {
89 height: parent.height
83 height: parent.height
90 spacing: units.gu(2)
84 spacing: units.gu(2)
91
85
86+ ActionContext {
87+ id: menuBarContext
88+ objectName: "barContext"
89+ active: !d.currentItem
90+ }
91+
92 Repeater {
92 Repeater {
93 id: rowRepeater
93 id: rowRepeater
94
94
@@ -108,7 +108,7 @@ Item {
108
108
109 function show() {
109 function show() {
110 if (!__popup) {
110 if (!__popup) {
111− __popup = menuComponent.createObject(visualItem);
111+ __popup = menuComponent.createObject(root);
112 } else {
112 } else {
113 __popup.visible = true;
113 __popup.visible = true;
114 }
114 }
@@ -126,10 +126,15 @@ Item {
126 }
126 }
127 }
127 }
128
128
129+ Connections {
130+ target: d
131+ onDismissAll: visualItem.dismiss()
132+ }
133+
129 Component {
134 Component {
130 id: menuComponent
135 id: menuComponent
131 MenuLoader {
136 MenuLoader {
132− x: -units.gu(1)
137+ x: visualItem.x - units.gu(1)
133 anchors.top: parent.bottom
138 anchors.top: parent.bottom
134 unityMenuModel: root.unityMenuModel.submenu(visualItem.__index)
139 unityMenuModel: root.unityMenuModel.submenu(visualItem.__index)
135 }
140 }
@@ -203,6 +208,8 @@ Item {
203 property Item hoveredItem: null
208 property Item hoveredItem: null
204 property Item prevCurrentItem: null
209 property Item prevCurrentItem: null
205
210
211+ signal dismissAll()
212+
206 onCurrentItemChanged: {
213 onCurrentItemChanged: {
207 if (prevCurrentItem && prevCurrentItem != currentItem) {
214 if (prevCurrentItem && prevCurrentItem != currentItem) {
208 if (currentItem) {
215 if (currentItem) {
@@ -238,8 +245,6 @@ Item {
238 return false;
245 return false;
239 if (d.currentItem != d.hoveredItem) {
246 if (d.currentItem != d.hoveredItem) {
240 d.currentItem = d.hoveredItem;
247 d.currentItem = d.hoveredItem;
241−// if (d.currentItem) d.currentItem.hide();
242−// if (d.hoveredItem) d.hoveredItem.show();
243 }
248 }
244 }
249 }
245 return true;
250 return true;
@@ -247,7 +252,7 @@ Item {
247 }
252 }
248
253
249 Keys.onEscapePressed: {
254 Keys.onEscapePressed: {
250− if (d.currentItem) d.currentItem.hide()
255+ d.dismissAll();
251 event.accepted = true;
256 event.accepted = true;
252 }
257 }
253
258
qml/ApplicationMenus/MenuItemLoader.qml+2−1
@@ -58,7 +58,8 @@ ActionItem {
58 Keys.onRightPressed: {
58 Keys.onRightPressed: {
59 if (hasSubmenu) {
59 if (hasSubmenu) {
60 root.trigger(action && action.checkable ? !action.checked : undefined);
60 root.trigger(action && action.checkable ? !action.checked : undefined);
61+ } else {
61− event.accepted = true;
62+ event.accepted = false;
62 }
63 }
63 }
64 }
64 Keys.onEnterPressed: {
65 Keys.onEnterPressed: {
qml/ApplicationMenus/MenuLoader.qml+31−15
@@ -47,8 +47,15 @@ UbuntuShape {
47 implicitHeight: MathUtils.clamp(listView.contentHeight, __ajustedMinimumHeight, __maximumHeight)
47 implicitHeight: MathUtils.clamp(listView.contentHeight, __ajustedMinimumHeight, __maximumHeight)
48
48
49 focus: true
49 focus: true
50+
50 Keys.onUpPressed: d.selectPrevious(d.currentIndex)
51 Keys.onUpPressed: d.selectPrevious(d.currentIndex)
51 Keys.onDownPressed: d.selectNext(d.currentIndex)
52 Keys.onDownPressed: d.selectNext(d.currentIndex)
53+ Keys.onRightPressed: {
54+ // Don't let right keypresses fall through if the current item has a visible popup.
55+ if (!d.currentItem || !d.currentItem.popup || !d.currentItem.popup.visible) {
56+ event.accepted = false;
57+ }
58+ }
52
59
53 onVisibleChanged: {
60 onVisibleChanged: {
54 if (!visible) {
61 if (!visible) {
@@ -133,12 +140,6 @@ UbuntuShape {
133 }
140 }
134 }
141 }
135
142
136− ActionContext {
137− id: menuContext
138− objectName: "menuContext"
139− active: true
140− }
141−
142 ColumnLayout {
143 ColumnLayout {
143 id: container
144 id: container
144 objectName: "container"
145 objectName: "container"
@@ -238,11 +239,26 @@ UbuntuShape {
238 }
239 }
239 }
240 }
240
241
242+ ActionContext {
243+ id: menuBarContext
244+ objectName: "menuContext"
245+ active: {
246+ if (!root.visible) return false;
247+ if (d.currentItem && d.currentItem.popup && d.currentItem.popup.visible) {
248+ return false;
249+ }
250+ return true;
251+ }
252+ }
253+
241 delegate: Loader {
254 delegate: Loader {
242 id: loader
255 id: loader
243− width: root.width
244
256
257+ property int __ownIndex: index
258+
259+ width: root.width
245 enabled: model.isSeparator ? false : model.sensitive
260 enabled: model.isSeparator ? false : model.sensitive
261+ Keys.forwardTo: [ item ]
246
262
247 sourceComponent: {
263 sourceComponent: {
248 if (model.isSeparator) {
264 if (model.isSeparator) {
@@ -250,9 +266,8 @@ UbuntuShape {
250 }
266 }
251 return menuItemComponent;
267 return menuItemComponent;
252 }
268 }
253− property int __ownIndex: index
254
269
255− Keys.forwardTo: [ item ]
270+ property Item popup: null
256
271
257 Component {
272 Component {
258 id: menuItemComponent
273 id: menuItemComponent
@@ -260,9 +275,9 @@ UbuntuShape {
260 id: menuItem
275 id: menuItem
261 menuData: model
276 menuData: model
262
277
263− property Item popup: null
264−
265 action.onTriggered: {
278 action.onTriggered: {
279+ d.currentItem = loader;
280+
266 if (hasSubmenu) {
281 if (hasSubmenu) {
267 if (!popup) {
282 if (!popup) {
268 var model = root.unityMenuModel.submenu(__ownIndex);
283 var model = root.unityMenuModel.submenu(__ownIndex);
@@ -288,8 +303,8 @@ UbuntuShape {
288 Connections {
303 Connections {
289 target: d
304 target: d
290 onCurrentIndexChanged: {
305 onCurrentIndexChanged: {
291− if (menuItem.popup && d.currentIndex != __ownIndex) {
306+ if (popup && d.currentIndex != __ownIndex) {
292− menuItem.popup.visible = false;
307+ popup.visible = false;
293 }
308 }
294 }
309 }
295 }
310 }
@@ -302,7 +317,6 @@ UbuntuShape {
302 }
317 }
303 }
318 }
304 }
319 }
305−
306 } // ListView
320 } // ListView
307
321
308 // FIXME use ListView.footer - tried but was flaky with positionViewAtIndex.
322 // FIXME use ListView.footer - tried but was flaky with positionViewAtIndex.
@@ -358,8 +372,10 @@ UbuntuShape {
358 value: submenuLoader.unityMenuModel
372 value: submenuLoader.unityMenuModel
359 }
373 }
360
374
361− onLoaded: item.select(0)
362 Keys.onLeftPressed: dismiss()
375 Keys.onLeftPressed: dismiss()
376+
377+ Component.onCompleted: item.select(0);
378+ onVisibleChanged: if (visible) { item.select(0); }
363 }
379 }
364 }
380 }
365}
381}
qml/Panel/Panel.qml+6
@@ -337,9 +337,15 @@ Item {
337 property bool menusRequested: menuBarLoader.item ? menuBarLoader.item.showRequested : false
337 property bool menusRequested: menuBarLoader.item ? menuBarLoader.item.showRequested : false
338
338
339 sourceComponent: MenuBarLoader {
339 sourceComponent: MenuBarLoader {
340+ id: bar
340 height: menuBarLoader.height
341 height: menuBarLoader.height
341 enableKeyFilter: valid && PanelState.decorationsVisible
342 enableKeyFilter: valid && PanelState.decorationsVisible
342 unityMenuModel: fakeApplicationMenus.model
343 unityMenuModel: fakeApplicationMenus.model
344+
345+ Connections {
346+ target: fakeApplicationMenus
347+ onHide: bar.dismiss();
348+ }
343 }
349 }
344 }
350 }
345
351
tests/qmltests/ApplicationMenuDataLoader.qml+7−6
@@ -11,15 +11,16 @@ Object {
11 var fakeMenuPath = "/" + surface.persistentId.replace(/\W+/g, "");
11 var fakeMenuPath = "/" + surface.persistentId.replace(/\W+/g, "");
12
12
13 ApplicationMenuRegistry.RegisterSurfaceMenu(surface.persistentId, fakeMenuPath, fakeMenuPath, ":1");
13 ApplicationMenuRegistry.RegisterSurfaceMenu(surface.persistentId, fakeMenuPath, fakeMenuPath, ":1");
14− Indicators.UnityMenuModelCache.setCachedModelData(fakeMenuPath, generateTestData(5, 3, 3, "menu"));
14+ Indicators.UnityMenuModelCache.setCachedModelData(fakeMenuPath, generateTestData(5, 3, 2, 3, "menu"));
15 }
15 }
16 onSurfaceDestroyed: {
16 onSurfaceDestroyed: {
17 ApplicationMenuRegistry.UnregisterSurfaceMenu(persistentSurfaceId, "/app");
17 ApplicationMenuRegistry.UnregisterSurfaceMenu(persistentSurfaceId, "/app");
18 }
18 }
19 }
19 }
20
20
21− function generateTestData(length, depth, separatorInterval, prefix) {
21+ function generateTestData(length, depth, submenuInterval, separatorInterval, prefix, root) {
22 var data = [];
22 var data = [];
23+ if (root === undefined) root = true;
23
24
24 if (prefix === undefined) prefix = "menu"
25 if (prefix === undefined) prefix = "menu"
25
26
@@ -27,7 +28,7 @@ Object {
27
28
28 var menuCode = String.fromCharCode(i+65);
29 var menuCode = String.fromCharCode(i+65);
29
30
30− var isSeparator = separatorInterval > 0 && ((i+1) % separatorInterval == 0);
31+ var isSeparator = !root && separatorInterval > 0 && ((i+1) % separatorInterval == 0);
31 var row = {
32 var row = {
32 "rowData": { // 1
33 "rowData": { // 1
33 "label": prefix + "&" + menuCode,
34 "label": prefix + "&" + menuCode,
@@ -43,9 +44,9 @@ Object {
43 "shortcut": ""
44 "shortcut": ""
44 }
45 }
45 }
46 }
47+ var isSubmenu = root === undefined || root === true || (submenuInterval > 0 && ((i+1) % submenuInterval == 0));
46− if (!isSeparator && depth > 1) {
48+ if (isSubmenu && !isSeparator && depth > 1) {
47− var submenu = generateTestData(length, depth-1, separatorInterval, prefix + menuCode + ".");
49+ row["submenu"] = generateTestData(length, depth-1, submenuInterval, separatorInterval, prefix + menuCode + ".", false);
48− row["submenu"] = submenu;
49 }
50 }
50 data[i] = row;
51 data[i] = row;
51 }
52 }
tests/qmltests/Panel/tst_Panel.qml+5−18
@@ -21,8 +21,7 @@ import Unity.Test 0.1
21import Ubuntu.Components 1.3
21import Ubuntu.Components 1.3
22import Ubuntu.Components.ListItems 1.3 as ListItem
22import Ubuntu.Components.ListItems 1.3 as ListItem
23import Unity.Application 0.1
23import Unity.Application 0.1
24−import Unity.ApplicationMenu 0.1
24+import QMenuModel 0.1
25−import Unity.Indicators 0.1 as Indicators
26import Ubuntu.Telephony 0.1 as Telephony
25import Ubuntu.Telephony 0.1 as Telephony
27import AccountsService 0.1
26import AccountsService 0.1
28import Unity.InputInfo 0.1
27import Unity.InputInfo 0.1
@@ -80,7 +79,10 @@ PanelTest {
80 applicationMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
79 applicationMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
81
80
82 applicationMenus {
81 applicationMenus {
83− model: sharedAppModel.model
82+ model: UnityMenuModel {
83+ modelData: appMenuData.generateTestData(5, 4, 2, 3, "menu")
84+ }
85+
84 hides: [ panel.indicators ]
86 hides: [ panel.indicators ]
85 }
87 }
86
88
@@ -88,14 +90,6 @@ PanelTest {
88 model: root.indicatorsModel
90 model: root.indicatorsModel
89 hides: [ panel.applicationMenus ]
91 hides: [ panel.applicationMenus ]
90 }
92 }
91−
92− Indicators.SharedUnityMenuModel {
93− id: sharedAppModel
94−
95− busName: ":1"
96− menuObjectPath: "/DialerswindowtitleId"
97− actions: { "unity": "/DialerswindowtitleId" }
98− }
99 }
93 }
100 }
94 }
101 }
95 }
@@ -175,13 +169,6 @@ PanelTest {
175 color: "black"
169 color: "black"
176 }
170 }
177
171
178− ApplicationCheckBox {
179− id: applicationCheckBox
180− Layout.fillWidth: true
181− appId: "dialer-app"
182− onCheckedChanged: PanelState.focusedPersistentSurfaceId = checked ? "Dialers window titleId" : "";
183− }
184−
185 Rectangle {
172 Rectangle {
186 Layout.preferredHeight: units.dp(1);
173 Layout.preferredHeight: units.dp(1);
187 Layout.fillWidth: true;
174 Layout.fillWidth: true;