on xenial help sign in
Fix being unable to use window control buttons or menus with touch (LP: #1667604, #1668387)
6948e5fLukáš Tinkl
qml/ApplicationMenus/MenuBar.qml+35−11
@@ -28,11 +28,19 @@ Item {
28 property alias unityMenuModel: rowRepeater.model
28 property alias unityMenuModel: rowRepeater.model
29 property bool enableKeyFilter: false
29 property bool enableKeyFilter: false
30 property real overflowWidth: width
30 property real overflowWidth: width
31+ property bool windowMoving: false
31
32
32 // read from outside
33 // read from outside
33 readonly property bool valid: rowRepeater.count > 0
34 readonly property bool valid: rowRepeater.count > 0
34 readonly property bool showRequested: d.longAltPressed || d.currentItem != null
35 readonly property bool showRequested: d.longAltPressed || d.currentItem != null
35
36
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+
36 implicitWidth: row.width
44 implicitWidth: row.width
37 height: parent.height
45 height: parent.height
38
46
@@ -77,7 +85,7 @@ Item {
77 anchors.fill: parent
85 anchors.fill: parent
78 enabled: d.currentItem != null
86 enabled: d.currentItem != null
79 hoverEnabled: enabled && d.currentItem && d.currentItem.__popup != null
87 hoverEnabled: enabled && d.currentItem && d.currentItem.__popup != null
80− onPressed: d.dismissAll()
88+ onPressed: { mouse.accepted = false; d.dismissAll(); }
81 }
89 }
82
90
83 Row {
91 Row {
@@ -218,24 +226,40 @@ Item {
218 } // Row
226 } // Row
219
227
220 MouseArea {
228 MouseArea {
221− anchors.fill: row
229+ anchors.fill: parent
222 hoverEnabled: d.currentItem
230 hoverEnabled: d.currentItem
223
231
232+ property bool moved: false
233+
224 onEntered: {
234 onEntered: {
225 if (d.currentItem) {
235 if (d.currentItem) {
226 updateCurrentItemFromPosition(Qt.point(mouseX, mouseY))
236 updateCurrentItemFromPosition(Qt.point(mouseX, mouseY))
227 }
237 }
228 }
238 }
229− onPositionChanged: {
239+
230− if (d.currentItem) {
240+ onClicked: {
241+ if (!moved) {
242+ var prevItem = d.currentItem;
231− updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y))
243+ updateCurrentItemFromPosition(Qt.point(mouseX, mouseY));
244+ if (prevItem && d.currentItem == prevItem) {
245+ prevItem.hide();
246+ }
232 }
247 }
248+ moved = false;
233 }
249 }
234− onClicked: {
250+
235− var prevItem = d.currentItem;
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) {
236− updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y))
262+ updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y))
237− if (prevItem && d.currentItem == prevItem) {
238− prevItem.hide();
239 }
263 }
240 }
264 }
241
265
@@ -260,7 +284,7 @@ Item {
260 hoverEnabled: d.currentItem
284 hoverEnabled: d.currentItem
261 onEntered: d.currentItem = this
285 onEntered: d.currentItem = this
262 onPositionChanged: d.currentItem = this
286 onPositionChanged: d.currentItem = this
263− onClicked: d.currentItem = this
287+ onPressed: d.currentItem = this
264
288
265 property Item __popup: null;
289 property Item __popup: null;
266 readonly property bool popupVisible: __popup && __popup.visible
290 readonly property bool popupVisible: __popup && __popup.visible
qml/Components/WindowControlButtons.qml−21
@@ -47,13 +47,6 @@ Row {
47 onClicked: root.closeClicked()
47 onClicked: root.closeClicked()
48 visible: root.closeButtonShown
48 visible: root.closeButtonShown
49
49
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−
57 Rectangle {
50 Rectangle {
58 anchors.fill: parent
51 anchors.fill: parent
59 anchors.margins: windowIsMaximized ? units.dp(3) : 0
52 anchors.margins: windowIsMaximized ? units.dp(3) : 0
@@ -78,13 +71,6 @@ Row {
78 onClicked: root.minimizeClicked()
71 onClicked: root.minimizeClicked()
79 visible: root.minimizeButtonVisible
72 visible: root.minimizeButtonVisible
80
73
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−
88 Rectangle {
74 Rectangle {
89 anchors.fill: parent
75 anchors.fill: parent
90 anchors.margins: windowIsMaximized ? units.dp(3) : 0
76 anchors.margins: windowIsMaximized ? units.dp(3) : 0
@@ -118,13 +104,6 @@ Row {
118 }
104 }
119 }
105 }
120
106
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−
128 Rectangle {
107 Rectangle {
129 anchors.fill: parent
108 anchors.fill: parent
130 anchors.margins: windowIsMaximized ? units.dp(3) : 0
109 anchors.margins: windowIsMaximized ? units.dp(3) : 0
qml/Panel/Panel.qml+2
@@ -229,6 +229,8 @@ Item {
229 target: __indicators
229 target: __indicators
230 onShownChanged: bar.dismiss();
230 onShownChanged: bar.dismiss();
231 }
231 }
232+
233+ onDoubleClicked: PanelState.restoreClicked()
232 }
234 }
233 }
235 }
234 }
236 }
qml/Stage/DecoratedWindow.qml+3
@@ -208,12 +208,14 @@ FocusScope {
208 height: units.gu(3)
208 height: units.gu(3)
209
209
210 title: applicationWindow.title
210 title: applicationWindow.title
211+ windowMoving: moveHandler.moving && !altDragHandler.dragging
211
212
212 opacity: root.hasDecoration ? Math.min(1, root.showDecoration) : 0
213 opacity: root.hasDecoration ? Math.min(1, root.showDecoration) : 0
213 Behavior on opacity { UbuntuNumberAnimation { } }
214 Behavior on opacity { UbuntuNumberAnimation { } }
214
215
215 onPressed: root.decorationPressed();
216 onPressed: root.decorationPressed();
216 onPressedChanged: moveHandler.handlePressedChanged(pressed, pressedButtons, mouseX, mouseY)
217 onPressedChanged: moveHandler.handlePressedChanged(pressed, pressedButtons, mouseX, mouseY)
218+ onPressedChangedEx: moveHandler.handlePressedChanged(pressed, pressedButtons, mouseX, mouseY)
217 onPositionChanged: moveHandler.handlePositionChanged(mouse)
219 onPositionChanged: moveHandler.handlePositionChanged(mouse)
218 onReleased: {
220 onReleased: {
219 root.decorationReleased();
221 root.decorationReleased();
@@ -259,6 +261,7 @@ FocusScope {
259 }
261 }
260
262
261 MouseArea {
263 MouseArea {
264+ id: altDragHandler
262 anchors.fill: applicationWindow
265 anchors.fill: applicationWindow
263 acceptedButtons: Qt.LeftButton
266 acceptedButtons: Qt.LeftButton
264 property bool dragging: false
267 property bool dragging: false
qml/Stage/MoveHandler.qml+4
@@ -29,6 +29,7 @@ QtObject {
29 property real boundsTopMargin: 0
29 property real boundsTopMargin: 0
30
30
31 readonly property bool dragging: priv.dragging
31 readonly property bool dragging: priv.dragging
32+ readonly property bool moving: priv.moving
32
33
33 signal fakeMaximizeAnimationRequested(real amount)
34 signal fakeMaximizeAnimationRequested(real amount)
34 signal fakeMaximizeLeftAnimationRequested(real amount)
35 signal fakeMaximizeLeftAnimationRequested(real amount)
@@ -43,6 +44,7 @@ QtObject {
43 property real distanceX
44 property real distanceX
44 property real distanceY
45 property real distanceY
45 property bool dragging
46 property bool dragging
47+ property bool moving
46
48
47 readonly property int triggerArea: units.gu(8)
49 readonly property int triggerArea: units.gu(8)
48 property bool nearLeftEdge: target.maximizedLeft
50 property bool nearLeftEdge: target.maximizedLeft
@@ -110,6 +112,7 @@ QtObject {
110
112
111 function handlePositionChanged(mouse, sensingPoints) {
113 function handlePositionChanged(mouse, sensingPoints) {
112 if (priv.dragging) {
114 if (priv.dragging) {
115+ priv.moving = true;
113 priv.mouseDownTimer.stop();
116 priv.mouseDownTimer.stop();
114 Mir.cursorName = "grabbing";
117 Mir.cursorName = "grabbing";
115
118
@@ -180,6 +183,7 @@ QtObject {
180 }
183 }
181
184
182 function handleReleased(touchMode) {
185 function handleReleased(touchMode) {
186+ priv.moving = false;
183 if (touchMode) {
187 if (touchMode) {
184 priv.progress = 0;
188 priv.progress = 0;
185 priv.resetEdges();
189 priv.resetEdges();
qml/Stage/WindowDecoration.qml+12−14
@@ -33,23 +33,21 @@ MouseArea {
33 property alias overlayShown: buttons.overlayShown
33 property alias overlayShown: buttons.overlayShown
34 property var menu: undefined
34 property var menu: undefined
35 property bool enableMenus: true
35 property bool enableMenus: true
36+ property bool windowMoving: false
36
37
37 readonly property real buttonsWidth: buttons.width + row.spacing
38 readonly property real buttonsWidth: buttons.width + row.spacing
38
39
39 acceptedButtons: Qt.AllButtons // prevent leaking unhandled mouse events
40 acceptedButtons: Qt.AllButtons // prevent leaking unhandled mouse events
40 hoverEnabled: true
41 hoverEnabled: true
41
42
42− drag.target: Item {}
43− drag.filterChildren: true
44− drag.threshold: 0
45−
46 signal closeClicked()
43 signal closeClicked()
47 signal minimizeClicked()
44 signal minimizeClicked()
48 signal maximizeClicked()
45 signal maximizeClicked()
49 signal maximizeHorizontallyClicked()
46 signal maximizeHorizontallyClicked()
50 signal maximizeVerticallyClicked()
47 signal maximizeVerticallyClicked()
51
48
52− onClicked: mouse.accepted = true // propogated event
49+ signal pressedChangedEx(bool pressed, var pressedButtons, real mouseX, real mouseY)
50+
53 onDoubleClicked: {
51 onDoubleClicked: {
54 if (mouse.button == Qt.LeftButton) {
52 if (mouse.button == Qt.LeftButton) {
55 root.maximizeClicked();
53 root.maximizeClicked();
@@ -69,13 +67,6 @@ MouseArea {
69 (menuBar.showRequested || root.containsMouse)
67 (menuBar.showRequested || root.containsMouse)
70 }
68 }
71
69
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−
79 Rectangle {
70 Rectangle {
80 id: background
71 id: background
81 anchors.fill: parent
72 anchors.fill: parent
@@ -136,7 +127,7 @@ MouseArea {
136 fontSize: "medium"
127 fontSize: "medium"
137 font.weight: root.active ? Font.Light : Font.Medium
128 font.weight: root.active ? Font.Light : Font.Medium
138 elide: Text.ElideRight
129 elide: Text.ElideRight
139− opacity: overlayShown || priv.shouldShowMenus ? 0 : 1
130+ opacity: overlayShown || menuBarLoader.visible ? 0 : 1
140 visible: opacity != 0
131 visible: opacity != 0
141 Behavior on opacity { UbuntuNumberAnimation {} }
132 Behavior on opacity { UbuntuNumberAnimation {} }
142 }
133 }
@@ -154,9 +145,16 @@ MouseArea {
154 height: menuBarLoader.height
145 height: menuBarLoader.height
155 enableKeyFilter: valid && root.active && root.enableMenus
146 enableKeyFilter: valid && root.active && root.enableMenus
156 unityMenuModel: root.menu
147 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)
157 }
155 }
158
156
159− opacity: !overlayShown && priv.shouldShowMenus ? 1 : 0
157+ opacity: (!overlayShown && priv.shouldShowMenus) || (active && priv.menuBar.valid && root.windowMoving) ? 1 : 0
160 visible: opacity == 1
158 visible: opacity == 1
161 Behavior on opacity { UbuntuNumberAnimation {} }
159 Behavior on opacity { UbuntuNumberAnimation {} }
162 }
160 }
tests/qmltests/ApplicationMenus/tst_MenuBar.qml+2
@@ -98,6 +98,7 @@ Item {
98 var menuItem2 = findChild(menuBar, "menuBar-item2"); verify(menuItem2);
98 var menuItem2 = findChild(menuBar, "menuBar-item2"); verify(menuItem2);
99
99
100 menuItem0.show();
100 menuItem0.show();
101+ mouseMove(menuItem0, menuItem0.width/2, menuItem0.height/2);
101 compare(priv.currentItem, menuItem0, "CurrentItem should be set to item 0");
102 compare(priv.currentItem, menuItem0, "CurrentItem should be set to item 0");
102 compare(priv.currentItem.popupVisible, true, "Popup should be visible");
103 compare(priv.currentItem.popupVisible, true, "Popup should be visible");
103
104
@@ -179,6 +180,7 @@ Item {
179
180
180 keyPress(data.tag, Qt.AltModifier, 100);
181 keyPress(data.tag, Qt.AltModifier, 100);
181 tryCompare(priv, "currentItem", menuItem);
182 tryCompare(priv, "currentItem", menuItem);
183+ keyRelease(data.tag, Qt.AltModifier, 100);
182 }
184 }
183
185
184 function test_menuActivateClosesMenu() {
186 function test_menuActivateClosesMenu() {
tests/qmltests/Stage/tst_DesktopStage.qml+18
@@ -1039,5 +1039,23 @@ Item {
1039 expectFail("", "Hovering the window controls should be ignored when the menu is open");
1039 expectFail("", "Hovering the window controls should be ignored when the menu is open");
1040 tryCompare(closeButton, "containsMouse", true);
1040 tryCompare(closeButton, "containsMouse", true);
1041 }
1041 }
1042+
1043+ function test_windowControlsTouchInteractionWithMenu() {
1044+ var appDelegate = startApplication("gmail-webapp");
1045+
1046+ var wd = findChild(appDelegate, "appWindowDecoration");
1047+ var maxButton = findChild(wd, "maximizeWindowButton");
1048+ var menuBarLoader = findChild(wd, "menuBarLoader");
1049+ var menuNav = findInvisibleChild(menuBarLoader, "d");
1050+
1051+ // make the menubar active and visible, select first item
1052+ menuBarLoader.active = true;
1053+ menuNav.select(0);
1054+ tryCompare(menuBarLoader.item, "visible", true);
1055+
1056+ // verify the maximized button can still be tapped
1057+ tap(maxButton);
1058+ tryCompare(appDelegate, "state", "maximized");
1059+ }
1042 }
1060 }
1043}
1061}
tests/qmltests/tst_Shell.qml+24
@@ -2907,5 +2907,29 @@ Rectangle {
2907 tryCompare(topLevelSurfaceList.focusedWindow, "surface", appDelegate.surface);
2907 tryCompare(topLevelSurfaceList.focusedWindow, "surface", appDelegate.surface);
2908 tryCompare(topLevelSurfaceList.applicationAt(0), "appId", "dialer-app");
2908 tryCompare(topLevelSurfaceList.applicationAt(0), "appId", "dialer-app");
2909 }
2909 }
2910+
2911+ function test_doubleClickPanelRestoresWindow() {
2912+ loadShell("desktop");
2913+ shell.usageScenario = "desktop";
2914+ waitForRendering(shell);
2915+ swipeAwayGreeter();
2916+
2917+ // start dialer
2918+ var appDelegate = startApplication("dialer-app")
2919+ verify(appDelegate);
2920+ tryCompare(appDelegate, "state", "normal");
2921+
2922+ // maximize dialer
2923+ var decoration = findChild(appDelegate, "appWindowDecoration");
2924+ verify(decoration);
2925+ mouseDoubleClickSequence(decoration);
2926+ tryCompare(appDelegate, "state", "maximized");
2927+
2928+ // double click the panel
2929+ var panel = findChild(shell, "panel");
2930+ verify(panel);
2931+ mouseDoubleClickSequence(panel, panel.width/2, PanelState.panelHeight/2, Qt.LeftButton, Qt.NoModifier, 300);
2932+ tryCompare(appDelegate, "state", "restored");
2933+ }
2910 }
2934 }
2911}
2935}