on xenial help sign in
Tapping on a surface gives it active focus
dac85dcDaniel d'Andrada
plugins/Ubuntu/Gestures/TouchGate.cpp+2
@@ -53,6 +53,8 @@ void TouchGate::touchEvent(QTouchEvent *event)
53 m_touchInfoMap[touchPoint.id()].ownership = OwnershipRequested;
53 m_touchInfoMap[touchPoint.id()].ownership = OwnershipRequested;
54 m_touchInfoMap[touchPoint.id()].ended = false;
54 m_touchInfoMap[touchPoint.id()].ended = false;
55 TouchRegistry::instance()->requestTouchOwnership(touchPoint.id(), this);
55 TouchRegistry::instance()->requestTouchOwnership(touchPoint.id(), this);
56+
57+ Q_EMIT pressed();
56 }
58 }
57
59
58 goodToGo &= m_touchInfoMap.contains(touchPoint.id())
60 goodToGo &= m_touchInfoMap.contains(touchPoint.id())
plugins/Ubuntu/Gestures/TouchGate.h+2
@@ -53,6 +53,8 @@ public:
53Q_SIGNALS:
53Q_SIGNALS:
54 void targetItemChanged(QQuickItem *item);
54 void targetItemChanged(QQuickItem *item);
55
55
56+ void pressed();
57+
56protected:
58protected:
57 void touchEvent(QTouchEvent *event) override;
59 void touchEvent(QTouchEvent *event) override;
58private:
60private:
qml/Shell.qml+10−1
@@ -287,6 +287,15 @@ Item {
287 source: usageModeSettings.usageMode === "Windowed" ? "Stages/DesktopStage.qml"
287 source: usageModeSettings.usageMode === "Windowed" ? "Stages/DesktopStage.qml"
288 : tabletMode ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
288 : tabletMode ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
289
289
290+ property bool interactive: edgeDemo.stagesEnabled
291+ && !greeter.shown
292+ && !lockscreen.shown
293+ && panel.indicators.fullyClosed
294+ && launcher.progress == 0
295+ && !notifications.useModal
296+
297+ onInteractiveChanged: { if (interactive) { focus = true; } }
298+
290 Binding {
299 Binding {
291 target: applicationsDisplayLoader.item
300 target: applicationsDisplayLoader.item
292 property: "objectName"
301 property: "objectName"
@@ -306,7 +315,7 @@ Item {
306 Binding {
315 Binding {
307 target: applicationsDisplayLoader.item
316 target: applicationsDisplayLoader.item
308 property: "interactive"
317 property: "interactive"
309− value: edgeDemo.stagesEnabled && !greeter.shown && !lockscreen.shown && panel.indicators.fullyClosed && launcher.progress == 0 && !notifications.useModal
318+ value: applicationsDisplayLoader.interactive
310 }
319 }
311 Binding {
320 Binding {
312 target: applicationsDisplayLoader.item
321 target: applicationsDisplayLoader.item
qml/Stages/ApplicationWindow.qml+3−1
@@ -18,7 +18,7 @@ import QtQuick 2.0
18import Ubuntu.Components 1.1
18import Ubuntu.Components 1.1
19import Unity.Application 0.1
19import Unity.Application 0.1
20
20
21−Item {
21+FocusScope {
22 id: root
22 id: root
23
23
24 // to be read from outside
24 // to be read from outside
@@ -120,6 +120,8 @@ Item {
120 d.surfaceInitialized = false;
120 d.surfaceInitialized = false;
121 }
121 }
122 }
122 }
123+
124+ focus: true
123 }
125 }
124
126
125 StateGroup {
127 StateGroup {
qml/Stages/PhoneStage.qml+2
@@ -109,6 +109,8 @@ Rectangle {
109
109
110 onFocusedAppIdChanged: focusedAppDelegate = spreadRepeater.itemAt(0);
110 onFocusedAppIdChanged: focusedAppDelegate = spreadRepeater.itemAt(0);
111
111
112+ onFocusedAppDelegateChanged: focusedAppDelegate.focus = true;
113+
112 function indexOf(appId) {
114 function indexOf(appId) {
113 for (var i = 0; i < ApplicationManager.count; i++) {
115 for (var i = 0; i < ApplicationManager.count; i++) {
114 if (ApplicationManager.get(i).appId == appId) {
116 if (ApplicationManager.get(i).appId == appId) {
qml/Stages/SessionContainer.qml+22−7
@@ -17,7 +17,7 @@
17import QtQuick 2.0
17import QtQuick 2.0
18import "Animations"
18import "Animations"
19
19
20−Item {
20+FocusScope {
21 id: root
21 id: root
22 objectName: "sessionContainer"
22 objectName: "sessionContainer"
23 property QtObject session
23 property QtObject session
@@ -34,8 +34,8 @@ Item {
34 orientation: root.orientation
34 orientation: root.orientation
35 }
35 }
36
36
37−
38 Repeater {
37 Repeater {
38+ id: childSessionsRepeater
39 model: root.childSessions
39 model: root.childSessions
40
40
41 delegate: Loader {
41 delegate: Loader {
@@ -45,9 +45,17 @@ Item {
45 // Only way to do recursive qml items.
45 // Only way to do recursive qml items.
46 source: Qt.resolvedUrl("SessionContainer.qml")
46 source: Qt.resolvedUrl("SessionContainer.qml")
47
47
48+ z: index
49+
50+ // Since a Loader is a FocusScope, propagate its focus to the loaded Item
51+ Binding {
52+ target: item; when: item
53+ property: "focus"; value: focus
54+ }
55+
48 Binding {
56 Binding {
49 target: item; when: item
57 target: item; when: item
50− property: "interactive"; value: root.interactive
58+ property: "interactive"; value: index == (childSessionsRepeater.count - 1) && root.interactive
51 }
59 }
52
60
53 Binding {
61 Binding {
@@ -69,10 +77,6 @@ Item {
69 target: item; when: item
77 target: item; when: item
70 property: "orientation"; value: root.orientation
78 property: "orientation"; value: root.orientation
71 }
79 }
72−
73− Component.onDestruction: {
74− root.session.surface.forceActiveFocus();
75− }
76 }
80 }
77 }
81 }
78
82
@@ -138,5 +142,16 @@ Item {
138 QtObject {
142 QtObject {
139 id: d
143 id: d
140 property var animations: []
144 property var animations: []
145+
146+ property var focusedChild: {
147+ if (childSessionsRepeater.count == 0) {
148+ return _surfaceContainer;
149+ } else {
150+ return childSessionsRepeater.itemAt(childSessionsRepeater.count - 1);
151+ }
152+ }
153+ onFocusedChildChanged: {
154+ focusedChild.focus = true;
155+ }
141 }
156 }
142}
157}
qml/Stages/SpreadDelegate.qml+2−1
@@ -21,7 +21,7 @@ import QtQuick 2.0
21import Ubuntu.Components 1.1
21import Ubuntu.Components 1.1
22import "../Components"
22import "../Components"
23
23
24−Item {
24+FocusScope {
25 id: root
25 id: root
26
26
27 // to be read from outside
27 // to be read from outside
@@ -65,6 +65,7 @@ Item {
65 ApplicationWindow {
65 ApplicationWindow {
66 id: appWindow
66 id: appWindow
67 objectName: application ? "appWindow_" + application.appId : "appWindow_null"
67 objectName: application ? "appWindow_" + application.appId : "appWindow_null"
68+ focus: true
68 anchors {
69 anchors {
69 fill: parent
70 fill: parent
70 topMargin: appWindow.fullscreen ? 0 : maximizedAppTopMargin
71 topMargin: appWindow.fullscreen ? 0 : maximizedAppTopMargin
qml/Stages/SurfaceContainer.qml+11−25
@@ -18,7 +18,7 @@ import QtQuick 2.0
18import Ubuntu.Components 1.1
18import Ubuntu.Components 1.1
19import Ubuntu.Gestures 0.1 // For TouchGate
19import Ubuntu.Gestures 0.1 // For TouchGate
20
20
21−Item {
21+FocusScope {
22 id: root
22 id: root
23 objectName: "surfaceContainer"
23 objectName: "surfaceContainer"
24 property Item surface: null
24 property Item surface: null
@@ -26,10 +26,15 @@ Item {
26 property int orientation
26 property int orientation
27 property bool interactive
27 property bool interactive
28
28
29+ signal surfacePressed()
30+
29 onSurfaceChanged: {
31 onSurfaceChanged: {
30 if (surface) {
32 if (surface) {
33+ // Set the surface focus *after* it is added to the scene to
34+ // ensure an update to the scene's active focus.
35+ surface.focus = false;
31 surface.parent = root;
36 surface.parent = root;
32− d.forceSurfaceActiveFocusIfReady();
37+ surface.focus = true;
33 } else {
38 } else {
34 hadSurface = true;
39 hadSurface = true;
35 }
40 }
@@ -38,7 +43,6 @@ Item {
38 Binding { target: surface; property: "orientation"; value: root.orientation }
43 Binding { target: surface; property: "orientation"; value: root.orientation }
39 Binding { target: surface; property: "z"; value: 1 }
44 Binding { target: surface; property: "z"; value: 1 }
40 Binding { target: surface; property: "enabled"; value: root.interactive; when: surface }
45 Binding { target: surface; property: "enabled"; value: root.interactive; when: surface }
41− Binding { target: surface; property: "focus"; value: root.interactive; when: surface }
42 Binding { target: surface; property: "antialiasing"; value: !root.interactive; when: surface }
46 Binding { target: surface; property: "antialiasing"; value: !root.interactive; when: surface }
43
47
44 TouchGate {
48 TouchGate {
@@ -46,28 +50,10 @@ Item {
46 anchors.fill: root
50 anchors.fill: root
47 enabled: root.surface ? root.surface.enabled : false
51 enabled: root.surface ? root.surface.enabled : false
48 z: 2
52 z: 2
49− }
53+ onPressed: {
50−
54+ root.focus = true;
51− Connections {
55+ if (root.interactive) {
52− target: root.surface
53− // FIXME: I would rather not need to do this, but currently it doesn't get
54− // active focus without it and I don't know why.
55− // Possibly because if an item get focus=true before it has a parent, once
56− // it gets a parent QQuickWindow won't check its focus and update its activeFocus
57− // accordingly. Unlike when you focus=true after the item already has a parent.
58− onFocusChanged: d.forceSurfaceActiveFocusIfReady();
59− onParentChanged: d.forceSurfaceActiveFocusIfReady();
60− onEnabledChanged: d.forceSurfaceActiveFocusIfReady();
61− }
62−
63− QtObject {
64− id: d
65− function forceSurfaceActiveFocusIfReady() {
66− if (root.surface !== null &&
67− root.surface.focus &&
68− root.surface.parent === root &&
69− root.surface.enabled) {
70− root.surface.forceActiveFocus();
56+ root.forceActiveFocus();
71 }
57 }
72 }
58 }
73 }
59 }
qml/Stages/TabletStage.qml+14
@@ -76,6 +76,20 @@ Rectangle {
76
76
77 appId0 = ApplicationManager.count >= 1 ? ApplicationManager.get(0).appId : "";
77 appId0 = ApplicationManager.count >= 1 ? ApplicationManager.get(0).appId : "";
78 appId1 = ApplicationManager.count > 1 ? ApplicationManager.get(1).appId : "";
78 appId1 = ApplicationManager.count > 1 ? ApplicationManager.get(1).appId : "";
79+
80+ // Update the QML focus accordingly
81+ updateSpreadDelegateFocus();
82+ }
83+
84+ function updateSpreadDelegateFocus() {
85+ if (priv.focusedAppId) {
86+ var focusedAppIndex = priv.indexOf(priv.focusedAppId);
87+ if (focusedAppIndex !== -1) {
88+ spreadRepeater.itemAt(focusedAppIndex).focus = true;
89+ } else {
90+ console.warn("TabletStage: Failed to find the SpreadDelegate for appID=" + priv.focusedAppId);
91+ }
92+ }
79 }
93 }
80
94
81 function indexOf(appId) {
95 function indexOf(appId) {
qml/Stages/TransformedTabletSpreadDelegate.qml+1−1
@@ -366,7 +366,7 @@ SpreadDelegate {
366 Scale {
366 Scale {
367 origin { x: 0; y: (spreadView.height * priv.scale) + maximizedAppTopMargin * 3 }
367 origin { x: 0; y: (spreadView.height * priv.scale) + maximizedAppTopMargin * 3 }
368 xScale: 1
368 xScale: 1
369− yScale: isFullscreen ? 1 - priv.topMarginProgress * maximizedAppTopMargin / spreadView.height : 1
369+ yScale: fullscreen ? 1 - priv.topMarginProgress * maximizedAppTopMargin / spreadView.height : 1
370 },
370 },
371 Translate {
371 Translate {
372 x: priv.xTranslate
372 x: priv.xTranslate
tests/mocks/Unity/Application/MirSurfaceItem.qml+16−1
@@ -44,9 +44,24 @@ Rectangle {
44 }
44 }
45
45
46 Text {
46 Text {
47+ text: surfaceText.text
48+ color: "black"
49+ font: surfaceText.font
50+ fontSizeMode: Text.Fit
51+ minimumPixelSize: 10
52+ verticalAlignment: Text.AlignVCenter
53+ x: surfaceText.x
54+ y: surfaceText.y
55+ width: surfaceText.width
56+ height: surfaceText.height
57+
58+ transform: Translate { x: -2; y: -2 }
59+ }
60+ Text {
61+ id: surfaceText
47 anchors.fill: parent
62 anchors.fill: parent
48 text: "SURFACE"
63 text: "SURFACE"
49− color: "yellow"
64+ color: root.parent && root.parent.activeFocus ? "yellow" : "blue"
50 font.bold: true
65 font.bold: true
51 fontSizeMode: Text.Fit
66 fontSizeMode: Text.Fit
52 minimumPixelSize: 10; font.pixelSize: 200
67 minimumPixelSize: 10; font.pixelSize: 200
tests/qmltests/CMakeLists.txt+1
@@ -88,6 +88,7 @@ add_qml_test(Stages PhoneStage ENVIRONMENT)
88add_qml_test(Stages SpreadDelegate ENVIRONMENT)
88add_qml_test(Stages SpreadDelegate ENVIRONMENT)
89add_qml_test(Stages SurfaceContainer ENVIRONMENT)
89add_qml_test(Stages SurfaceContainer ENVIRONMENT)
90add_qml_test(Stages SessionContainer ENVIRONMENT)
90add_qml_test(Stages SessionContainer ENVIRONMENT)
91+add_qml_test(Stages TabletStage)
91add_qml_test(Stages WindowMoveResizeArea)
92add_qml_test(Stages WindowMoveResizeArea)
92add_qml_test(Stages Splash)
93add_qml_test(Stages Splash)
93add_qml_test(Wizard Wizard)
94add_qml_test(Wizard Wizard)
tests/qmltests/Stages/ApplicationCheckBox.qmlnew file+45
@@ -0,0 +1,45 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU 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 General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import QtQuick.Layouts 1.1
19+import Ubuntu.Components 1.1
20+import Unity.Application 0.1
21+
22+RowLayout {
23+ id: root
24+ property string appId
25+ property alias checked: checkbox.checked
26+
27+ Layout.fillWidth: true
28+ CheckBox {
29+ id: checkbox
30+ checked: false
31+ activeFocusOnPress: false
32+ onCheckedChanged: {
33+ if (checked) {
34+ ApplicationManager.startApplication(root.appId);
35+ } else {
36+ ApplicationManager.stopApplication(root.appId);
37+ }
38+ }
39+ }
40+ Label {
41+ text: root.appId
42+ color: "white"
43+ anchors.verticalCenter: parent.verticalCenter
44+ }
45+}
tests/qmltests/Stages/RecursingChildSessionControl.qml+3
@@ -89,6 +89,7 @@ ColumnLayout {
89 CheckBox {
89 CheckBox {
90 id: _surfaceCheckbox;
90 id: _surfaceCheckbox;
91 checked: false;
91 checked: false;
92+ activeFocusOnPress: false
92 enabled: root.session
93 enabled: root.session
93 onCheckedChanged: {
94 onCheckedChanged: {
94 if (checked) {
95 if (checked) {
@@ -117,6 +118,7 @@ ColumnLayout {
117
118
118 Button {
119 Button {
119 enabled: root.session
120 enabled: root.session
121+ activeFocusOnPress: false
120 text: removable ? "Remove" : "Release"
122 text: removable ? "Remove" : "Release"
121 onClicked: {
123 onClicked: {
122 if (removable) {
124 if (removable) {
@@ -131,6 +133,7 @@ ColumnLayout {
131
133
132 Button {
134 Button {
133 enabled: root.session !== null
135 enabled: root.session !== null
136+ activeFocusOnPress: false
134 text: "Add Child"
137 text: "Add Child"
135 onClicked: {
138 onClicked: {
136 var screenshot = Math.round(Math.random()*100 % (screenshotIds.length-1));
139 var screenshot = Math.round(Math.random()*100 % (screenshotIds.length-1));
tests/qmltests/Stages/tst_ApplicationWindow.qml+6−1
@@ -52,10 +52,12 @@ Rectangle {
52 application: fakeApplication
52 application: fakeApplication
53 orientation: Qt.PortraitOrientation
53 orientation: Qt.PortraitOrientation
54 interactive: true
54 interactive: true
55+ focus: true
55 }
56 }
56 }
57 }
57 Loader {
58 Loader {
58 id: applicationWindowLoader
59 id: applicationWindowLoader
60+ focus: true
59 anchors {
61 anchors {
60 top: parent.top
62 top: parent.top
61 bottom: parent.bottom
63 bottom: parent.bottom
@@ -83,6 +85,7 @@ Rectangle {
83
85
84 CheckBox {
86 CheckBox {
85 id: sessionCheckbox; checked: false
87 id: sessionCheckbox; checked: false
88+ activeFocusOnPress: false
86 onCheckedChanged: {
89 onCheckedChanged: {
87 if (applicationWindowLoader.status !== Loader.Ready)
90 if (applicationWindowLoader.status !== Loader.Ready)
88 return;
91 return;
@@ -121,6 +124,7 @@ Rectangle {
121
124
122 ListItem.ItemSelector {
125 ListItem.ItemSelector {
123 id: appStateSelector
126 id: appStateSelector
127+ activeFocusOnPress: false
124 anchors { left: parent.left; right: parent.right }
128 anchors { left: parent.left; right: parent.right }
125 text: "Application state"
129 text: "Application state"
126 model: ["Starting",
130 model: ["Starting",
@@ -146,6 +150,7 @@ Rectangle {
146
150
147 Button {
151 Button {
148 anchors { left: parent.left; right: parent.right }
152 anchors { left: parent.left; right: parent.right }
153+ activeFocusOnPress: false
149 text: "Rotate device \u27F3"
154 text: "Rotate device \u27F3"
150 onClicked: {
155 onClicked: {
151 var orientation = applicationWindowLoader.item.orientation
156 var orientation = applicationWindowLoader.item.orientation
@@ -398,7 +403,7 @@ Rectangle {
398 verify(stateGroup.state === "void");
403 verify(stateGroup.state === "void");
399 }
404 }
400
405
401− function test_forceActiveFocusFollowsInterative() {
406+ function test_surfaceActiveFocusFollowsAppWindowInterative() {
402 fakeApplication.createSession();
407 fakeApplication.createSession();
403 applicationWindowLoader.item.interactive = false;
408 applicationWindowLoader.item.interactive = false;
404 applicationWindowLoader.item.interactive = true;
409 applicationWindowLoader.item.interactive = true;
tests/qmltests/Stages/tst_PhoneStage.qml+4
@@ -29,6 +29,7 @@ Item {
29 PhoneStage {
29 PhoneStage {
30 id: phoneStage
30 id: phoneStage
31 anchors { fill: parent; rightMargin: units.gu(30) }
31 anchors { fill: parent; rightMargin: units.gu(30) }
32+ focus: true
32 dragAreaWidth: units.gu(2)
33 dragAreaWidth: units.gu(2)
33 maximizedAppTopMargin: units.gu(3) + units.dp(2)
34 maximizedAppTopMargin: units.gu(3) + units.dp(2)
34 interactive: true
35 interactive: true
@@ -51,6 +52,7 @@ Item {
51 Button {
52 Button {
52 anchors { left: parent.left; right: parent.right }
53 anchors { left: parent.left; right: parent.right }
53 text: "Add App"
54 text: "Add App"
55+ activeFocusOnPress: false
54 onClicked: {
56 onClicked: {
55 testCase.addApps();
57 testCase.addApps();
56 }
58 }
@@ -58,6 +60,7 @@ Item {
58 Button {
60 Button {
59 anchors { left: parent.left; right: parent.right }
61 anchors { left: parent.left; right: parent.right }
60 text: "Remove Selected"
62 text: "Remove Selected"
63+ activeFocusOnPress: false
61 onClicked: {
64 onClicked: {
62 ApplicationManager.stopApplication(ApplicationManager.get(appList.selectedAppIndex).appId);
65 ApplicationManager.stopApplication(ApplicationManager.get(appList.selectedAppIndex).appId);
63 }
66 }
@@ -65,6 +68,7 @@ Item {
65 Button {
68 Button {
66 anchors { left: parent.left; right: parent.right }
69 anchors { left: parent.left; right: parent.right }
67 text: "Stop Selected"
70 text: "Stop Selected"
71+ activeFocusOnPress: false
68 onClicked: {
72 onClicked: {
69 ApplicationManager.get(appList.selectedAppIndex).setState(ApplicationInfoInterface.Stopped);
73 ApplicationManager.get(appList.selectedAppIndex).setState(ApplicationInfoInterface.Stopped);
70 }
74 }
tests/qmltests/Stages/tst_SessionContainer.qml+14−7
@@ -42,11 +42,14 @@ Rectangle {
42 id: sessionContainer
42 id: sessionContainer
43 anchors.fill: parent
43 anchors.fill: parent
44 orientation: Qt.PortraitOrientation
44 orientation: Qt.PortraitOrientation
45+ focus: true
46+ interactive: true
45 }
47 }
46 }
48 }
47
49
48 Loader {
50 Loader {
49 id: sessionContainerLoader
51 id: sessionContainerLoader
52+ focus: true
50 anchors {
53 anchors {
51 top: parent.top
54 top: parent.top
52 bottom: parent.bottom
55 bottom: parent.bottom
@@ -75,6 +78,7 @@ Rectangle {
75 CheckBox {
78 CheckBox {
76 id: sessionCheckbox;
79 id: sessionCheckbox;
77 checked: false;
80 checked: false;
81+ activeFocusOnPress: false
78 onCheckedChanged: {
82 onCheckedChanged: {
79 if (sessionContainerLoader.status !== Loader.Ready)
83 if (sessionContainerLoader.status !== Loader.Ready)
80 return;
84 return;
@@ -190,8 +194,6 @@ Rectangle {
190 var sessionContainer = sessionContainerLoader.item;
194 var sessionContainer = sessionContainerLoader.item;
191 compare(sessionContainer.childSessions.count(), 0);
195 compare(sessionContainer.childSessions.count(), 0);
192
196
193− sessionContainer.interactive = true;
194−
195 var i;
197 var i;
196 var sessions = [];
198 var sessions = [];
197 // 3 sessions should cover all edge cases
199 // 3 sessions should cover all edge cases
@@ -209,14 +211,19 @@ Rectangle {
209
211
210 var a_session;
212 var a_session;
211 while(a_session = sessions.pop()) {
213 while(a_session = sessions.pop()) {
212− a_session.surface.forceActiveFocus();
213 compare(a_session.surface.activeFocus, true);
214 compare(a_session.surface.activeFocus, true);
214
215
215− var parentSession = a_session.parentSession;
216+ ApplicationTest.removeSurface(a_session.surface);
216− sessionContainer.session.removeChildSession(a_session);
217+ ApplicationTest.removeSession(a_session);
217− compare(a_session.surface.activeFocus, false);
218
218
219− compare(parentSession.surface.activeFocus, true);
219+ if (sessions.length > 0) {
220+ // active focus should have gone to the yongest remaining sibling
221+ var previousSiblingSurface = sessions[sessions.length - 1].surface
222+ tryCompare(previousSiblingSurface, "activeFocus", true);
223+ } else {
224+ // active focus should have gone to the parent surface
225+ tryCompare(sessionContainer.session.surface, "activeFocus", true);
226+ }
220 }
227 }
221 }
228 }
222
229
tests/qmltests/Stages/tst_TabletStage.qmlnew file+154
@@ -0,0 +1,154 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU 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 General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import QtTest 1.0
19+import Ubuntu.Components 1.1
20+import Ubuntu.Components.ListItems 1.0 as ListItem
21+import Unity.Application 0.1
22+import Unity.Test 0.1
23+
24+import "../../../qml/Stages"
25+
26+Rectangle {
27+ id: root
28+ color: "grey"
29+ width: tabletStageLoader.width + controls.width
30+ height: tabletStageLoader.height
31+
32+ Loader {
33+ id: tabletStageLoader
34+
35+ x: ((root.width - controls.width) - width) / 2
36+ y: (root.height - height) / 2
37+ width: units.gu(160*0.7)
38+ height: units.gu(100*0.7)
39+
40+ focus: true
41+
42+ property bool itemDestroyed: false
43+ sourceComponent: Component {
44+ TabletStage {
45+ anchors.fill: parent
46+ Component.onDestruction: {
47+ tabletStageLoader.itemDestroyed = true;
48+ }
49+ dragAreaWidth: units.gu(2)
50+ maximizedAppTopMargin: units.gu(3) + units.dp(2)
51+ interactive: true
52+ focus: true
53+ }
54+ }
55+ }
56+
57+ Rectangle {
58+ id: controls
59+ color: "darkgrey"
60+ width: units.gu(30)
61+ anchors {
62+ top: parent.top
63+ bottom: parent.bottom
64+ right: parent.right
65+ }
66+
67+ Column {
68+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
69+ spacing: units.gu(1)
70+ ApplicationCheckBox {
71+ id: webbrowserCheckBox
72+ appId: "webbrowser-app"
73+ }
74+ ApplicationCheckBox {
75+ id: dialerCheckBox
76+ appId: "dialer-app"
77+ }
78+ }
79+ }
80+
81+ UnityTestCase {
82+ id: testCase
83+ name: "TabletStage"
84+ when: windowShown
85+
86+ property Item tabletStage: tabletStageLoader.status === Loader.Ready ? tabletStageLoader.item : null
87+
88+ function init() {
89+ tabletStageLoader.active = true;
90+ tryCompare(tabletStageLoader, "status", Loader.Ready);
91+ }
92+
93+ function cleanup() {
94+ tabletStageLoader.itemDestroyed = false;
95+ tabletStageLoader.active = false;
96+
97+ tryCompare(tabletStageLoader, "status", Loader.Null);
98+ tryCompare(tabletStageLoader, "item", null);
99+ // Loader.status might be Loader.Null and Loader.item might be null but the Loader
100+ // actually took place. Likely because Loader waits until the next event loop
101+ // iteration to do its work. So to ensure the reload, we will wait until the
102+ // Shell instance gets destroyed.
103+ tryCompare(tabletStageLoader, "itemDestroyed", true);
104+
105+ // kill all (fake) running apps
106+ webbrowserCheckBox.checked = false;
107+ dialerCheckBox.checked = false;
108+ }
109+
110+ function waitUntilAppSurfaceShowsUp(appId) {
111+ var appWindow = findChild(tabletStage, "appWindow_" + appId);
112+ verify(appWindow);
113+ var appWindowStates = findInvisibleChild(appWindow, "applicationWindowStateGroup");
114+ verify(appWindowStates);
115+ tryCompare(appWindowStates, "state", "surface");
116+ }
117+
118+ function test_tappingSwitchesFocusBetweenStages() {
119+ webbrowserCheckBox.checked = true;
120+ waitUntilAppSurfaceShowsUp(webbrowserCheckBox.appId);
121+ var webbrowserApp = ApplicationManager.findApplication(webbrowserCheckBox.appId);
122+ compare(webbrowserApp.stage, ApplicationInfoInterface.MainStage);
123+ tryCompare(webbrowserApp.session.surface, "activeFocus", true);
124+
125+ dialerCheckBox.checked = true;
126+ waitUntilAppSurfaceShowsUp(dialerCheckBox.appId);
127+ var dialerApp = ApplicationManager.findApplication(dialerCheckBox.appId);
128+ compare(dialerApp.stage, ApplicationInfoInterface.SideStage);
129+ tryCompare(dialerApp.session.surface, "activeFocus", true);
130+ tryCompare(webbrowserApp.session.surface, "activeFocus", false);
131+
132+ // Tap on the main stage application and check if the focus
133+ // has been passed to it.
134+
135+ var webbrowserWindow = findChild(tabletStage, "appWindow_" + webbrowserApp.appId);
136+ verify(webbrowserWindow);
137+ tap(webbrowserWindow);
138+
139+ tryCompare(dialerApp.session.surface, "activeFocus", false);
140+ tryCompare(webbrowserApp.session.surface, "activeFocus", true);
141+
142+ // Now tap on the side stage application and check if the focus
143+ // has been passed back to it.
144+
145+ var dialerWindow = findChild(tabletStage, "appWindow_" + dialerApp.appId);
146+ verify(dialerWindow);
147+ tap(dialerWindow);
148+
149+ tryCompare(dialerApp.session.surface, "activeFocus", true);
150+ tryCompare(webbrowserApp.session.surface, "activeFocus", false);
151+ }
152+ }
153+
154+}
tests/qmltests/tst_Shell.qml+26−5
@@ -63,6 +63,8 @@ Item {
63 anchors.fill: parent
63 anchors.fill: parent
64 Loader {
64 Loader {
65 id: shellLoader
65 id: shellLoader
66+ focus: true
67+
66 active: false
68 active: false
67 property bool itemDestroyed: false
69 property bool itemDestroyed: false
68 sourceComponent: Component {
70 sourceComponent: Component {
@@ -88,6 +90,7 @@ Item {
88 anchors { left: parent.left; right: parent.right }
90 anchors { left: parent.left; right: parent.right }
89 Button {
91 Button {
90 text: "Show Greeter"
92 text: "Show Greeter"
93+ activeFocusOnPress: false
91 onClicked: {
94 onClicked: {
92 if (shellLoader.status !== Loader.Ready)
95 if (shellLoader.status !== Loader.Ready)
93 return;
96 return;
@@ -434,11 +437,11 @@ Item {
434 waitUntilTransitionsEnd(appWindowStateGroup);
437 waitUntilTransitionsEnd(appWindowStateGroup);
435 }
438 }
436
439
437− function test_surfaceLosesFocusWhilePanelIsOpen() {
440+ function test_surfaceLosesActiveFocusWhilePanelIsOpen() {
438 var app = ApplicationManager.startApplication("dialer-app");
441 var app = ApplicationManager.startApplication("dialer-app");
439 waitUntilAppWindowIsFullyLoaded(app);
442 waitUntilAppWindowIsFullyLoaded(app);
440
443
441− tryCompare(app.session.surface, "focus", true);
444+ tryCompare(app.session.surface, "activeFocus", true);
442
445
443 // Drag the indicators panel half-open
446 // Drag the indicators panel half-open
444 var touchX = shell.width / 2;
447 var touchX = shell.width / 2;
@@ -449,7 +452,7 @@ Item {
449 true /* beginTouch */, false /* endTouch */);
452 true /* beginTouch */, false /* endTouch */);
450 verify(indicators.partiallyOpened);
453 verify(indicators.partiallyOpened);
451
454
452− tryCompare(app.session.surface, "focus", false);
455+ tryCompare(app.session.surface, "activeFocus", false);
453
456
454 // And finish getting it open
457 // And finish getting it open
455 touchFlick(indicators,
458 touchFlick(indicators,
@@ -458,11 +461,21 @@ Item {
458 false /* beginTouch */, true /* endTouch */);
461 false /* beginTouch */, true /* endTouch */);
459 tryCompare(indicators, "fullyOpened", true);
462 tryCompare(indicators, "fullyOpened", true);
460
463
461− tryCompare(app.session.surface, "focus", false);
464+ tryCompare(app.session.surface, "activeFocus", false);
462
465
463 dragToCloseIndicatorsPanel();
466 dragToCloseIndicatorsPanel();
464
467
465− tryCompare(app.session.surface, "focus", true);
468+ tryCompare(app.session.surface, "activeFocus", true);
469+ }
470+
471+ function test_launchedAppHasActiveFocus() {
472+ var dialerApp = ApplicationManager.startApplication("dialer-app");
473+ verify(dialerApp);
474+ waitUntilAppSurfaceShowsUp("dialer-app")
475+
476+ verify(dialerApp.session.surface);
477+
478+ tryCompare(dialerApp.session.surface, "activeFocus", true);
466 }
479 }
467
480
468 // Wait for the whole UI to settle down
481 // Wait for the whole UI to settle down
@@ -477,6 +490,14 @@ Item {
477 waitForRendering(shell)
490 waitForRendering(shell)
478 }
491 }
479
492
493+ function waitUntilAppSurfaceShowsUp(appId) {
494+ var appWindow = findChild(shell, "appWindow_" + appId);
495+ verify(appWindow);
496+ var appWindowStates = findInvisibleChild(appWindow, "applicationWindowStateGroup");
497+ verify(appWindowStates);
498+ tryCompare(appWindowStates, "state", "surface");
499+ }
500+
480 function dragToCloseIndicatorsPanel() {
501 function dragToCloseIndicatorsPanel() {
481 var indicators = findChild(shell, "indicators");
502 var indicators = findChild(shell, "indicators");
482
503
tests/uqmlscene/ActiveFocusLogger.cppnew file+48
@@ -0,0 +1,48 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU 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 General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "ActiveFocusLogger.h"
18+
19+#include <QDebug>
20+#include <QQuickItem>
21+
22+void ActiveFocusLogger::setWindow(QQuickWindow *window)
23+{
24+ m_window = window;
25+ QObject::connect(window, &QQuickWindow::activeFocusItemChanged,
26+ this, &ActiveFocusLogger::printActiveFocusInfo);
27+}
28+
29+void ActiveFocusLogger::printActiveFocusInfo()
30+{
31+ if (!m_window) {
32+ return;
33+ }
34+
35+ qDebug() << "============== Active focus info START ================";
36+ if (m_window->activeFocusItem()) {
37+ qDebug() << m_window->activeFocusItem();
38+ qDebug() << "Ancestry:";
39+ QQuickItem *item = m_window->activeFocusItem()->parentItem();
40+ while (item != nullptr) {
41+ qDebug() << item << ", isFocusScope =" << item->isFocusScope();
42+ item = item->parentItem();
43+ }
44+ } else {
45+ qDebug() << "NULL";
46+ }
47+ qDebug() << "============== Active focus info END ================";
48+}
tests/uqmlscene/ActiveFocusLogger.hnew file+37
@@ -0,0 +1,37 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU 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 General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef ACTIVE_FOCUS_LOGGER_H
18+#define ACTIVE_FOCUS_LOGGER_H
19+
20+#include <QObject>
21+#include <QQuickWindow>
22+#include <QPointer>
23+
24+class ActiveFocusLogger : public QObject {
25+ Q_OBJECT
26+
27+public:
28+ void setWindow(QQuickWindow *window);
29+
30+private Q_SLOTS:
31+ void printActiveFocusInfo();
32+
33+private:
34+ QPointer<QQuickWindow> m_window;
35+};
36+
37+#endif // ACTIVE_FOCUS_LOGGER_H
tests/uqmlscene/CMakeLists.txt+1
@@ -1,5 +1,6 @@
1add_executable(uqmlscene
1add_executable(uqmlscene
2 ${shellapplication_MOC_SRCS}
2 ${shellapplication_MOC_SRCS}
3+ ActiveFocusLogger.cpp
3 main.cpp
4 main.cpp
4 ${CMAKE_SOURCE_DIR}/src/MouseTouchAdaptor.cpp
5 ${CMAKE_SOURCE_DIR}/src/MouseTouchAdaptor.cpp
5 )
6 )
tests/uqmlscene/main.cpp+13
@@ -68,6 +68,12 @@
68// UbuntuGestures lib
68// UbuntuGestures lib
69#include <TouchRegistry.h>
69#include <TouchRegistry.h>
70
70
71+#define UQMLSCENE_DEBUG_ACTIVE_FOCUS 0
72+
73+#if UQMLSCENE_DEBUG_ACTIVE_FOCUS
74+ #include "ActiveFocusLogger.h"
75+#endif
76+
71#ifdef QML_RUNTIME_TESTING
77#ifdef QML_RUNTIME_TESTING
72class RenderStatistics
78class RenderStatistics
73{
79{
@@ -378,6 +384,10 @@ int main(int argc, char ** argv)
378{
384{
379 Options options;
385 Options options;
380
386
387+ #if UQMLSCENE_DEBUG_ACTIVE_FOCUS
388+ ActiveFocusLogger activeFocusLogger;
389+ #endif
390+
381 QStringList imports;
391 QStringList imports;
382 QList<QPair<QString, QString> > bundles;
392 QList<QPair<QString, QString> > bundles;
383 for (int i = 1; i < argc; ++i) {
393 for (int i = 1; i < argc; ++i) {
@@ -503,6 +513,9 @@ int main(int argc, char ** argv)
503 QQuickItem *contentItem = qobject_cast<QQuickItem *>(topLevel);
513 QQuickItem *contentItem = qobject_cast<QQuickItem *>(topLevel);
504 if (contentItem) {
514 if (contentItem) {
505 qxView = new QQuickView(&engine, nullptr);
515 qxView = new QQuickView(&engine, nullptr);
516+ #if UQMLSCENE_DEBUG_ACTIVE_FOCUS
517+ activeFocusLogger.setWindow(qxView);
518+ #endif
506 TouchRegistry::instance()->setParent(qxView);
519 TouchRegistry::instance()->setParent(qxView);
507 qxView->installEventFilter(TouchRegistry::instance());
520 qxView->installEventFilter(TouchRegistry::instance());
508 window = qxView;
521 window = qxView;