on xenial help sign in
merge lp:~lukas-kde/unity8/fix-window-buttons-touch
commit983cd3795133bbf8f607764cfb1163e30e0d22bb authorLukáš Tinkl <lukas.tinkl@canonical.com> dateFri 10 Mar 2017, 13:11 (10 years ago) parent16aeee9f4ef2e3
11 files changed+195−137
qml/ApplicationMenus/MenuBar.qml+48−23 qml/ApplicationMenus/MenuItem.qml+6−6 qml/ApplicationMenus/MenuPopup.qml+75−73 qml/Components/WindowControlButtons.qml−21 qml/Panel/Panel.qml+2 qml/Stage/DecoratedWindow.qml+3 qml/Stage/MoveHandler.qml+4 qml/Stage/WindowDecoration.qml+12−14 tests/qmltests/ApplicationMenus/tst_MenuBar.qml+3 tests/qmltests/Stage/tst_DesktopStage.qml+18 tests/qmltests/tst_Shell.qml+24
qml/ApplicationMenus/MenuBar.qml+48−23
@@ -28,11 +28,19 @@ Item {
2828 property alias unityMenuModel: rowRepeater.model
2929 property bool enableKeyFilter: false
3030 property real overflowWidth: width
31+ property bool windowMoving: false
3132
3233 // read from outside
3334 readonly property bool valid: rowRepeater.count > 0
3435 readonly property bool showRequested: d.longAltPressed || d.currentItem != null
3536
37+ // MoveHandler API for DecoratedWindow
38+ signal pressed(var mouse)
39+ signal pressedChangedEx(bool pressed, var pressedButtons, real mouseX, real mouseY)
40+ signal positionChanged(var mouse)
41+ signal released(var mouse)
42+ signal doubleClicked(var mouse)
43+
3644 implicitWidth: row.width
3745 height: parent.height
3846
@@ -77,7 +85,7 @@ Item {
7785 anchors.fill: parent
7886 enabled: d.currentItem != null
7987 hoverEnabled: enabled && d.currentItem && d.currentItem.__popup != null
80− onPressed: d.dismissAll()
88+ onPressed: { mouse.accepted = false; d.dismissAll(); }
8189 }
8290
8391 Row {
@@ -96,6 +104,11 @@ Item {
96104 onModelReset: d.firstInvisibleIndex = undefined
97105 }
98106
107+ Component {
108+ id: menuComponent
109+ MenuPopup { }
110+ }
111+
99112 Repeater {
100113 id: rowRepeater
101114
@@ -119,7 +132,14 @@ Item {
119132
120133 function show() {
121134 if (!__popup) {
122− __popup = menuComponent.createObject(root, { objectName: visualItem.objectName + "-menu" });
135+ __popup = menuComponent.createObject(root,
136+ {
137+ objectName: visualItem.objectName + "-menu",
138+ desiredX: Qt.binding(function() { return visualItem.x - units.gu(1); }),
139+ desiredY: Qt.binding(function() { return root.height; }),
140+ unityMenuModel: Qt.binding(function() { return root.unityMenuModel.submenu(visualItem.__ownIndex); })
141+ });
142+ __popup.reset();
123143 __popup.childActivated.connect(dismiss);
124144 // force the current item to be the newly popped up menu
125145 } else {
@@ -162,17 +182,6 @@ Item {
162182 onDismissAll: visualItem.dismiss()
163183 }
164184
165− Component {
166− id: menuComponent
167− MenuPopup {
168− desiredX: visualItem.x - units.gu(1)
169− desiredY: parent.height
170− unityMenuModel: root.unityMenuModel.submenu(visualItem.__ownIndex)
171−
172− Component.onCompleted: reset();
173− }
174− }
175−
176185 RowLayout {
177186 id: column
178187 spacing: units.gu(1)
@@ -217,24 +226,40 @@ Item {
217226 } // Row
218227
219228 MouseArea {
220− anchors.fill: row
229+ anchors.fill: parent
221230 hoverEnabled: d.currentItem
222231
232+ property bool moved: false
233+
223234 onEntered: {
224235 if (d.currentItem) {
225236 updateCurrentItemFromPosition(Qt.point(mouseX, mouseY))
226237 }
227238 }
228− onPositionChanged: {
229− if (d.currentItem) {
230− updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y))
239+
240+ onClicked: {
241+ if (!moved) {
242+ var prevItem = d.currentItem;
243+ updateCurrentItemFromPosition(Qt.point(mouseX, mouseY));
244+ if (prevItem && d.currentItem == prevItem) {
245+ prevItem.hide();
246+ }
231247 }
248+ moved = false;
232249 }
233− onClicked: {
234− var prevItem = d.currentItem;
235− updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y))
236− if (prevItem && d.currentItem == prevItem) {
237− prevItem.hide();
250+
251+ // for the MoveHandler
252+ onPressed: root.pressed(mouse)
253+ onPressedChanged: root.pressedChangedEx(pressed, pressedButtons, mouseX, mouseY)
254+ onReleased: root.released(mouse)
255+ onDoubleClicked: root.doubleClicked(mouse)
256+
257+ Mouse.ignoreSynthesizedEvents: true
258+ Mouse.onPositionChanged: {
259+ root.positionChanged(mouse);
260+ moved = root.windowMoving;
261+ if (d.currentItem) {
262+ updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y))
238263 }
239264 }
240265
@@ -259,7 +284,7 @@ Item {
259284 hoverEnabled: d.currentItem
260285 onEntered: d.currentItem = this
261286 onPositionChanged: d.currentItem = this
262− onClicked: d.currentItem = this
287+ onPressed: d.currentItem = this
263288
264289 property Item __popup: null;
265290 readonly property bool popupVisible: __popup && __popup.visible
qml/ApplicationMenus/MenuItem.qml+6−6
@@ -34,7 +34,7 @@ ActionItem {
3434 val += units.gu(1) + title.contentWidth;
3535 if (hasSubmenu) {
3636 val += units.gu(1) + chevronIcon.width;
37− } else if (shortcut != undefined) {
37+ } else if (menuData && menuData.shortcut != undefined) {
3838 val += units.gu(3) + shortcutLabel.contentWidth;
3939 }
4040 return val + units.gu(1);
@@ -49,9 +49,9 @@ ActionItem {
4949 enabled: root.enabled
5050
5151 // FIXME - SDK Action:text modifies menu text with html underline for mnemonic
52− text: menuData.label.replace("_", "&").replace("<u>", "&").replace("</u>", "")
53− checkable: menuData.isCheck || menuData.isRadio
54− checked: menuData.isToggled
52+ text: menuData ? menuData.label.replace("_", "&").replace("<u>", "&").replace("</u>", "") : ""
53+ checkable: menuData && (menuData.isCheck || menuData.isRadio)
54+ checked: menuData && menuData.isToggled
5555 }
5656
5757 width: {
@@ -127,8 +127,8 @@ ActionItem {
127127 color: enabled ? theme.palette.normal.overlaySecondaryText :
128128 theme.palette.disabled.overlaySecondaryText
129129
130− visible: menuData.shortcut != undefined && !root.hasSubmenu && QuickUtils.keyboardAttached
131− text: menuData.shortcut ? menuData.shortcut : ""
130+ visible: menuData && menuData.shortcut != undefined && !root.hasSubmenu && QuickUtils.keyboardAttached
131+ text: menuData && menuData.shortcut ? menuData.shortcut : ""
132132 }
133133 }
134134
qml/ApplicationMenus/MenuPopup.qml+75−73
@@ -276,6 +276,79 @@ UbuntuShape {
276276 }
277277 }
278278
279+ Component {
280+ id: separatorComponent
281+ ListItems.ThinDivider {
282+ // Parent will be loader
283+ objectName: parent.objectName + "-separator"
284+ implicitHeight: units.dp(2)
285+ }
286+ }
287+
288+ Component {
289+ id: menuItemComponent
290+ MenuItem {
291+ // Parent will be loader
292+ id: menuItem
293+ menuData: parent.__menuData
294+ objectName: parent.objectName + "-actionItem"
295+
296+ width: MathUtils.clamp(implicitWidth, d.__minimumWidth, d.__maximumWidth)
297+
298+ action.onTriggered: {
299+ submenuHoverTimer.stop();
300+
301+ d.currentItem = parent;
302+
303+ if (hasSubmenu) {
304+ if (!popup) {
305+ var model = root.unityMenuModel.submenu(__ownIndex);
306+ popup = submenuComponent.createObject(focusScope, {
307+ objectName: parent.objectName + "-",
308+ unityMenuModel: model,
309+ substractWidth: true,
310+ desiredX: Qt.binding(function() { return root.width }),
311+ desiredY: Qt.binding(function() {
312+ var dummy = listView.contentY; // force a recalc on contentY change.
313+ return mapToItem(container, 0, y).y;
314+ })
315+ });
316+ popup.retreat.connect(function() {
317+ popup.destroy();
318+ popup = null;
319+ menuItem.forceActiveFocus();
320+ });
321+ popup.childActivated.connect(function() {
322+ popup.destroy();
323+ popup = null;
324+ root.childActivated();
325+ });
326+ } else if (popup) {
327+ popup.visible = true;
328+ }
329+ } else {
330+ root.unityMenuModel.activate(__ownIndex);
331+ root.childActivated();
332+ }
333+ }
334+
335+ Connections {
336+ target: d
337+ onCurrentIndexChanged: {
338+ if (popup && d.currentIndex != __ownIndex) {
339+ popup.visible = false;
340+ }
341+ }
342+ onDismissAll: {
343+ if (popup) {
344+ popup.destroy();
345+ popup = null;
346+ }
347+ }
348+ }
349+ }
350+ }
351+
279352 ColumnLayout {
280353 id: menuColumn
281354 spacing: 0
@@ -289,6 +362,8 @@ UbuntuShape {
289362 id: loader
290363 objectName: root.objectName + "-item" + __ownIndex
291364
365+ property Item popup: null
366+ property var __menuData: model
292367 property int __ownIndex: index
293368 property bool __isSeparator: model.isSeparator
294369
@@ -301,80 +376,7 @@ UbuntuShape {
301376 return menuItemComponent;
302377 }
303378
304− property Item popup: null
305−
306379 Layout.fillWidth: true
307−
308− Component {
309− id: menuItemComponent
310− MenuItem {
311− id: menuItem
312− menuData: model
313− objectName: loader.objectName + "-actionItem"
314−
315− width: MathUtils.clamp(implicitWidth, d.__minimumWidth, d.__maximumWidth)
316−
317− action.onTriggered: {
318− submenuHoverTimer.stop();
319−
320− d.currentItem = loader;
321−
322− if (hasSubmenu) {
323− if (!popup) {
324− var model = root.unityMenuModel.submenu(__ownIndex);
325− popup = submenuComponent.createObject(focusScope, {
326− objectName: loader.objectName + "-",
327− unityMenuModel: model,
328− substractWidth: true,
329− desiredX: Qt.binding(function() { return root.width }),
330− desiredY: Qt.binding(function() {
331− var dummy = listView.contentY; // force a recalc on contentY change.
332− return mapToItem(container, 0, y).y;
333− })
334− });
335− popup.retreat.connect(function() {
336− popup.destroy();
337− popup = null;
338− menuItem.forceActiveFocus();
339− });
340− popup.childActivated.connect(function() {
341− popup.destroy();
342− popup = null;
343− root.childActivated();
344− });
345− } else if (popup) {
346− popup.visible = true;
347− }
348− } else {
349− root.unityMenuModel.activate(__ownIndex);
350− root.childActivated();
351− }
352− }
353−
354− Connections {
355− target: d
356− onCurrentIndexChanged: {
357− if (popup && d.currentIndex != __ownIndex) {
358− popup.visible = false;
359− }
360− }
361− onDismissAll: {
362− if (popup) {
363− popup.destroy();
364− popup = null;
365− }
366− }
367− }
368− }
369− }
370−
371− Component {
372− id: separatorComponent
373− ListItems.ThinDivider {
374− objectName: loader.objectName + "-separator"
375− implicitHeight: units.dp(2)
376− }
377− }
378380 }
379381
380382 }
qml/Components/WindowControlButtons.qml−21
@@ -47,13 +47,6 @@ Row {
4747 onClicked: root.closeClicked()
4848 visible: root.closeButtonShown
4949
50− // We dont want touch events to fall through to parent,
51− // otherwise the containsMouse will not work.
52− MouseArea {
53− anchors.fill: parent
54− propagateComposedEvents: true
55− }
56−
5750 Rectangle {
5851 anchors.fill: parent
5952 anchors.margins: windowIsMaximized ? units.dp(3) : 0
@@ -78,13 +71,6 @@ Row {
7871 onClicked: root.minimizeClicked()
7972 visible: root.minimizeButtonVisible
8073
81− // We dont want touch events to fall through to parent,
82− // otherwise the containsMouse will not work.
83− MouseArea {
84− anchors.fill: parent
85− propagateComposedEvents: true
86− }
87−
8874 Rectangle {
8975 anchors.fill: parent
9076 anchors.margins: windowIsMaximized ? units.dp(3) : 0
@@ -118,13 +104,6 @@ Row {
118104 }
119105 }
120106
121− // We dont want touch events to fall through to parent,
122− // otherwise the containsMouse will not work.
123− MouseArea {
124− anchors.fill: parent
125− propagateComposedEvents: true
126− }
127−
128107 Rectangle {
129108 anchors.fill: parent
130109 anchors.margins: windowIsMaximized ? units.dp(3) : 0
qml/Panel/Panel.qml+2
@@ -231,6 +231,8 @@ Item {
231231 target: __indicators
232232 onShownChanged: bar.dismiss();
233233 }
234+
235+ onDoubleClicked: PanelState.restoreClicked()
234236 }
235237 }
236238 }
qml/Stage/DecoratedWindow.qml+3
@@ -204,12 +204,14 @@ FocusScope {
204204 height: units.gu(3)
205205
206206 title: applicationWindow.title
207+ windowMoving: moveHandler.moving && !altDragHandler.dragging
207208
208209 opacity: root.hasDecoration ? Math.min(1, root.showDecoration) : 0
209210 Behavior on opacity { UbuntuNumberAnimation { } }
210211
211212 onPressed: root.decorationPressed();
212213 onPressedChanged: moveHandler.handlePressedChanged(pressed, pressedButtons, mouseX, mouseY)
214+ onPressedChangedEx: moveHandler.handlePressedChanged(pressed, pressedButtons, mouseX, mouseY)
213215 onPositionChanged: moveHandler.handlePositionChanged(mouse)
214216 onReleased: {
215217 root.decorationReleased();
@@ -255,6 +257,7 @@ FocusScope {
255257 }
256258
257259 MouseArea {
260+ id: altDragHandler
258261 anchors.fill: applicationWindow
259262 acceptedButtons: Qt.LeftButton
260263 property bool dragging: false
qml/Stage/MoveHandler.qml+4
@@ -29,6 +29,7 @@ QtObject {
2929 property real boundsTopMargin: 0
3030
3131 readonly property bool dragging: priv.dragging
32+ readonly property bool moving: priv.moving
3233
3334 signal fakeMaximizeAnimationRequested(real amount)
3435 signal fakeMaximizeLeftAnimationRequested(real amount)
@@ -43,6 +44,7 @@ QtObject {
4344 property real distanceX
4445 property real distanceY
4546 property bool dragging
47+ property bool moving
4648
4749 readonly property int triggerArea: units.gu(8)
4850 property bool nearLeftEdge: target.maximizedLeft
@@ -110,6 +112,7 @@ QtObject {
110112
111113 function handlePositionChanged(mouse, sensingPoints) {
112114 if (priv.dragging) {
115+ priv.moving = true;
113116 priv.mouseDownTimer.stop();
114117 Mir.cursorName = "grabbing";
115118
@@ -180,6 +183,7 @@ QtObject {
180183 }
181184
182185 function handleReleased(touchMode) {
186+ priv.moving = false;
183187 if (touchMode) {
184188 priv.progress = 0;
185189 priv.resetEdges();
qml/Stage/WindowDecoration.qml+12−14
@@ -33,23 +33,21 @@ MouseArea {
3333 property alias overlayShown: buttons.overlayShown
3434 property var menu: undefined
3535 property bool enableMenus: true
36+ property bool windowMoving: false
3637
3738 readonly property real buttonsWidth: buttons.width + row.spacing
3839
3940 acceptedButtons: Qt.AllButtons // prevent leaking unhandled mouse events
4041 hoverEnabled: true
4142
42− drag.target: Item {}
43− drag.filterChildren: true
44− drag.threshold: 0
45−
4643 signal closeClicked()
4744 signal minimizeClicked()
4845 signal maximizeClicked()
4946 signal maximizeHorizontallyClicked()
5047 signal maximizeVerticallyClicked()
5148
52− onClicked: mouse.accepted = true // propogated event
49+ signal pressedChangedEx(bool pressed, var pressedButtons, real mouseX, real mouseY)
50+
5351 onDoubleClicked: {
5452 if (mouse.button == Qt.LeftButton) {
5553 root.maximizeClicked();
@@ -69,13 +67,6 @@ MouseArea {
6967 (menuBar.showRequested || root.containsMouse)
7068 }
7169
72− // We dont want touch events to fall through to parent as it expect some child MouseArea to have them
73− // If not some MouseArea in the menu bar, it will be this one.
74− MouseArea {
75− anchors.fill: parent
76− propagateComposedEvents: true
77− }
78−
7970 Rectangle {
8071 id: background
8172 anchors.fill: parent
@@ -136,7 +127,7 @@ MouseArea {
136127 fontSize: "medium"
137128 font.weight: root.active ? Font.Light : Font.Medium
138129 elide: Text.ElideRight
139− opacity: overlayShown || priv.shouldShowMenus ? 0 : 1
130+ opacity: overlayShown || menuBarLoader.visible ? 0 : 1
140131 visible: opacity != 0
141132 Behavior on opacity { UbuntuNumberAnimation {} }
142133 }
@@ -154,9 +145,16 @@ MouseArea {
154145 height: menuBarLoader.height
155146 enableKeyFilter: valid && root.active && root.enableMenus
156147 unityMenuModel: root.menu
148+ windowMoving: root.windowMoving
149+
150+ onPressed: root.onPressed(mouse)
151+ onPressedChangedEx: root.pressedChangedEx(pressed, pressedButtons, mouseX, mouseY)
152+ onPositionChanged: root.onPositionChanged(mouse)
153+ onReleased: root.onReleased(mouse)
154+ onDoubleClicked: root.onDoubleClicked(mouse)
157155 }
158156
159− opacity: !overlayShown && priv.shouldShowMenus ? 1 : 0
157+ opacity: (!overlayShown && priv.shouldShowMenus) || (active && priv.menuBar.valid && root.windowMoving) ? 1 : 0
160158 visible: opacity == 1
161159 Behavior on opacity { UbuntuNumberAnimation {} }
162160 }
tests/qmltests/ApplicationMenus/tst_MenuBar.qml+3
@@ -85,6 +85,7 @@ Item {
8585 menuBar.dismiss();
8686 menuBackend.modelData = appMenuData.generateTestData(5,5,2,3, "menu")
8787 activatedSpy.clear();
88+ waitForRendering(menuBar);
8889 }
8990
9091 function test_mouseNavigation() {
@@ -97,6 +98,7 @@ Item {
9798 var menuItem2 = findChild(menuBar, "menuBar-item2"); verify(menuItem2);
9899
99100 menuItem0.show();
101+ mouseMove(menuItem0, menuItem0.width/2, menuItem0.height/2);
100102 compare(priv.currentItem, menuItem0, "CurrentItem should be set to item 0");
101103 compare(priv.currentItem.popupVisible, true, "Popup should be visible");
102104
@@ -178,6 +180,7 @@ Item {
178180
179181 keyPress(data.tag, Qt.AltModifier, 100);
180182 tryCompare(priv, "currentItem", menuItem);
183+ keyRelease(data.tag, Qt.AltModifier, 100);
181184 }
182185
183186 function test_menuActivateClosesMenu() {
tests/qmltests/Stage/tst_DesktopStage.qml+18
@@ -1013,5 +1013,23 @@ Item {
10131013 expectFail("", "Hovering the window controls should be ignored when the menu is open");
10141014 tryCompare(closeButton, "containsMouse", true);
10151015 }
1016+
1017+ function test_windowControlsTouchInteractionWithMenu() {
1018+ var appDelegate = startApplication("gmail-webapp");
1019+
1020+ var wd = findChild(appDelegate, "appWindowDecoration");
1021+ var maxButton = findChild(wd, "maximizeWindowButton");
1022+ var menuBarLoader = findChild(wd, "menuBarLoader");
1023+ var menuNav = findInvisibleChild(menuBarLoader, "d");
1024+
1025+ // make the menubar active and visible, select first item
1026+ menuBarLoader.active = true;
1027+ menuNav.select(0);
1028+ tryCompare(menuBarLoader.item, "visible", true);
1029+
1030+ // verify the maximized button can still be tapped
1031+ tap(maxButton);
1032+ tryCompare(appDelegate, "state", "maximized");
1033+ }
10161034 }
10171035}
tests/qmltests/tst_Shell.qml+24
@@ -2910,5 +2910,29 @@ Rectangle {
29102910 tryCompare(topLevelSurfaceList.focusedWindow, "surface", appDelegate.surface);
29112911 tryCompare(topLevelSurfaceList.applicationAt(0), "appId", "dialer-app");
29122912 }
2913+
2914+ function test_doubleClickPanelRestoresWindow() {
2915+ loadShell("desktop");
2916+ shell.usageScenario = "desktop";
2917+ waitForRendering(shell);
2918+ swipeAwayGreeter();
2919+
2920+ // start dialer
2921+ var appDelegate = startApplication("dialer-app")
2922+ verify(appDelegate);
2923+ tryCompare(appDelegate, "state", "normal");
2924+
2925+ // maximize dialer
2926+ var decoration = findChild(appDelegate, "appWindowDecoration");
2927+ verify(decoration);
2928+ mouseDoubleClickSequence(decoration);
2929+ tryCompare(appDelegate, "state", "maximized");
2930+
2931+ // double click the panel
2932+ var panel = findChild(shell, "panel");
2933+ verify(panel);
2934+ mouseDoubleClickSequence(panel, panel.width/2, PanelState.panelHeight/2, Qt.LeftButton, Qt.NoModifier, 300);
2935+ tryCompare(appDelegate, "state", "restored");
2936+ }
29132937 }
29142938}