on xenial help sign in
Merge from tutorial-refactor
commit4114c7901bc4d2c8f0454a6da57df17398142261 authorMichael Terry <michael.terry@canonical.com> dateWed 11 Feb 2015, 11:32 (12 years ago) parent9f9d0f1df795f3
23 files changed+437−50
plugins/Ubuntu/Gestures/TouchGate.cpp+2 plugins/Ubuntu/Gestures/TouchGate.h+2 qml/Shell.qml+10−1 qml/Stages/ApplicationWindow.qml+3−1 qml/Stages/PhoneStage.qml+2 qml/Stages/SessionContainer.qml+22−7 qml/Stages/SpreadDelegate.qml+2−1 qml/Stages/SurfaceContainer.qml+11−25 qml/Stages/TabletStage.qml+14 qml/Stages/TransformedTabletSpreadDelegate.qml+1−1 tests/mocks/Unity/Application/MirSurfaceItem.qml+16−1 tests/qmltests/CMakeLists.txt+1 tests/qmltests/Stages/ApplicationCheckBox.qml+45 tests/qmltests/Stages/RecursingChildSessionControl.qml+3 tests/qmltests/Stages/tst_ApplicationWindow.qml+6−1 tests/qmltests/Stages/tst_PhoneStage.qml+4 tests/qmltests/Stages/tst_SessionContainer.qml+14−7 tests/qmltests/Stages/tst_TabletStage.qml+154 tests/qmltests/tst_Shell.qml+26−5 tests/uqmlscene/ActiveFocusLogger.cpp+48 tests/uqmlscene/ActiveFocusLogger.h+37 tests/uqmlscene/CMakeLists.txt+1 tests/uqmlscene/main.cpp+13
plugins/Ubuntu/Gestures/TouchGate.cpp+2
@@ -53,6 +53,8 @@ void TouchGate::touchEvent(QTouchEvent *event)
5353 m_touchInfoMap[touchPoint.id()].ownership = OwnershipRequested;
5454 m_touchInfoMap[touchPoint.id()].ended = false;
5555 TouchRegistry::instance()->requestTouchOwnership(touchPoint.id(), this);
56+
57+ Q_EMIT pressed();
5658 }
5759
5860 goodToGo &= m_touchInfoMap.contains(touchPoint.id())
plugins/Ubuntu/Gestures/TouchGate.h+2
@@ -53,6 +53,8 @@ public:
5353Q_SIGNALS:
5454 void targetItemChanged(QQuickItem *item);
5555
56+ void pressed();
57+
5658protected:
5759 void touchEvent(QTouchEvent *event) override;
5860private:
qml/Shell.qml+10−1
@@ -290,6 +290,15 @@ Item {
290290 source: usageModeSettings.usageMode === "Windowed" ? "Stages/DesktopStage.qml"
291291 : tabletMode ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
292292
293+ property bool interactive: tutorial.spreadEnabled
294+ && !greeter.shown
295+ && !lockscreen.shown
296+ && panel.indicators.fullyClosed
297+ && launcher.progress == 0
298+ && !notifications.useModal
299+
300+ onInteractiveChanged: { if (interactive) { focus = true; } }
301+
293302 Binding {
294303 target: applicationsDisplayLoader.item
295304 property: "objectName"
@@ -309,7 +318,7 @@ Item {
309318 Binding {
310319 target: applicationsDisplayLoader.item
311320 property: "interactive"
312− value: tutorial.spreadEnabled && !greeter.shown && !lockscreen.shown && panel.indicators.fullyClosed && launcher.progress == 0 && !notifications.useModal
321+ value: applicationsDisplayLoader.interactive
313322 }
314323 Binding {
315324 target: applicationsDisplayLoader.item
qml/Stages/ApplicationWindow.qml+3−1
@@ -18,7 +18,7 @@ import QtQuick 2.0
1818import Ubuntu.Components 1.1
1919import Unity.Application 0.1
2020
21−Item {
21+FocusScope {
2222 id: root
2323
2424 // to be read from outside
@@ -121,6 +121,8 @@ Item {
121121 d.surfaceInitialized = false;
122122 }
123123 }
124+
125+ focus: true
124126 }
125127
126128 StateGroup {
qml/Stages/PhoneStage.qml+2
@@ -125,6 +125,8 @@ Rectangle {
125125
126126 onFocusedAppIdChanged: focusedAppDelegate = spreadRepeater.itemAt(0);
127127
128+ onFocusedAppDelegateChanged: focusedAppDelegate.focus = true;
129+
128130 function indexOf(appId) {
129131 for (var i = 0; i < applicationManager.count; i++) {
130132 if (applicationManager.get(i).appId == appId) {
qml/Stages/SessionContainer.qml+22−7
@@ -17,7 +17,7 @@
1717import QtQuick 2.0
1818import "Animations"
1919
20−Item {
20+FocusScope {
2121 id: root
2222 objectName: "sessionContainer"
2323 property QtObject session
@@ -34,8 +34,8 @@ Item {
3434 orientation: root.orientation
3535 }
3636
37−
3837 Repeater {
38+ id: childSessionsRepeater
3939 model: root.childSessions
4040
4141 delegate: Loader {
@@ -45,9 +45,17 @@ Item {
4545 // Only way to do recursive qml items.
4646 source: Qt.resolvedUrl("SessionContainer.qml")
4747
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+
4856 Binding {
4957 target: item; when: item
50− property: "interactive"; value: root.interactive
58+ property: "interactive"; value: index == (childSessionsRepeater.count - 1) && root.interactive
5159 }
5260
5361 Binding {
@@ -69,10 +77,6 @@ Item {
6977 target: item; when: item
7078 property: "orientation"; value: root.orientation
7179 }
72−
73− Component.onDestruction: {
74− root.session.surface.forceActiveFocus();
75− }
7680 }
7781 }
7882
@@ -138,5 +142,16 @@ Item {
138142 QtObject {
139143 id: d
140144 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+ }
141156 }
142157}
qml/Stages/SpreadDelegate.qml+2−1
@@ -21,7 +21,7 @@ import QtQuick 2.0
2121import Ubuntu.Components 1.1
2222import "../Components"
2323
24−Item {
24+FocusScope {
2525 id: root
2626
2727 // to be read from outside
@@ -65,6 +65,7 @@ Item {
6565 ApplicationWindow {
6666 id: appWindow
6767 objectName: application ? "appWindow_" + application.appId : "appWindow_null"
68+ focus: true
6869 anchors {
6970 fill: parent
7071 topMargin: appWindow.fullscreen ? 0 : maximizedAppTopMargin
qml/Stages/SurfaceContainer.qml+11−25
@@ -18,7 +18,7 @@ import QtQuick 2.0
1818import Ubuntu.Components 1.1
1919import Ubuntu.Gestures 0.1 // For TouchGate
2020
21−Item {
21+FocusScope {
2222 id: root
2323 objectName: "surfaceContainer"
2424 property Item surface: null
@@ -26,10 +26,15 @@ Item {
2626 property int orientation
2727 property bool interactive
2828
29+ signal surfacePressed()
30+
2931 onSurfaceChanged: {
3032 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;
3136 surface.parent = root;
32− d.forceSurfaceActiveFocusIfReady();
37+ surface.focus = true;
3338 } else {
3439 hadSurface = true;
3540 }
@@ -38,7 +43,6 @@ Item {
3843 Binding { target: surface; property: "orientation"; value: root.orientation }
3944 Binding { target: surface; property: "z"; value: 1 }
4045 Binding { target: surface; property: "enabled"; value: root.interactive; when: surface }
41− Binding { target: surface; property: "focus"; value: root.interactive; when: surface }
4246 Binding { target: surface; property: "antialiasing"; value: !root.interactive; when: surface }
4347
4448 TouchGate {
@@ -46,28 +50,10 @@ Item {
4650 anchors.fill: root
4751 enabled: root.surface ? root.surface.enabled : false
4852 z: 2
49− }
50−
51− Connections {
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();
53+ onPressed: {
54+ root.focus = true;
55+ if (root.interactive) {
56+ root.forceActiveFocus();
7157 }
7258 }
7359 }
qml/Stages/TabletStage.qml+14
@@ -76,6 +76,20 @@ Rectangle {
7676
7777 appId0 = ApplicationManager.count >= 1 ? ApplicationManager.get(0).appId : "";
7878 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+ }
7993 }
8094
8195 function indexOf(appId) {
qml/Stages/TransformedTabletSpreadDelegate.qml+1−1
@@ -366,7 +366,7 @@ SpreadDelegate {
366366 Scale {
367367 origin { x: 0; y: (spreadView.height * priv.scale) + maximizedAppTopMargin * 3 }
368368 xScale: 1
369− yScale: isFullscreen ? 1 - priv.topMarginProgress * maximizedAppTopMargin / spreadView.height : 1
369+ yScale: fullscreen ? 1 - priv.topMarginProgress * maximizedAppTopMargin / spreadView.height : 1
370370 },
371371 Translate {
372372 x: priv.xTranslate
tests/mocks/Unity/Application/MirSurfaceItem.qml+16−1
@@ -44,9 +44,24 @@ Rectangle {
4444 }
4545
4646 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
4762 anchors.fill: parent
4863 text: "SURFACE"
49− color: "yellow"
64+ color: root.parent && root.parent.activeFocus ? "yellow" : "blue"
5065 font.bold: true
5166 fontSizeMode: Text.Fit
5267 minimumPixelSize: 10; font.pixelSize: 200
tests/qmltests/CMakeLists.txt+1
@@ -87,6 +87,7 @@ add_qml_test(Stages PhoneStage ENVIRONMENT)
8787add_qml_test(Stages SpreadDelegate ENVIRONMENT)
8888add_qml_test(Stages SurfaceContainer ENVIRONMENT)
8989add_qml_test(Stages SessionContainer ENVIRONMENT)
90+add_qml_test(Stages TabletStage)
9091add_qml_test(Stages WindowMoveResizeArea)
9192add_qml_test(Stages Splash)
9293add_qml_test(Tutorial Tutorial)
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 {
8989 CheckBox {
9090 id: _surfaceCheckbox;
9191 checked: false;
92+ activeFocusOnPress: false
9293 enabled: root.session
9394 onCheckedChanged: {
9495 if (checked) {
@@ -117,6 +118,7 @@ ColumnLayout {
117118
118119 Button {
119120 enabled: root.session
121+ activeFocusOnPress: false
120122 text: removable ? "Remove" : "Release"
121123 onClicked: {
122124 if (removable) {
@@ -131,6 +133,7 @@ ColumnLayout {
131133
132134 Button {
133135 enabled: root.session !== null
136+ activeFocusOnPress: false
134137 text: "Add Child"
135138 onClicked: {
136139 var screenshot = Math.round(Math.random()*100 % (screenshotIds.length-1));
tests/qmltests/Stages/tst_ApplicationWindow.qml+6−1
@@ -52,10 +52,12 @@ Rectangle {
5252 application: fakeApplication
5353 orientation: Qt.PortraitOrientation
5454 interactive: true
55+ focus: true
5556 }
5657 }
5758 Loader {
5859 id: applicationWindowLoader
60+ focus: true
5961 anchors {
6062 top: parent.top
6163 bottom: parent.bottom
@@ -83,6 +85,7 @@ Rectangle {
8385
8486 CheckBox {
8587 id: sessionCheckbox; checked: false
88+ activeFocusOnPress: false
8689 onCheckedChanged: {
8790 if (applicationWindowLoader.status !== Loader.Ready)
8891 return;
@@ -121,6 +124,7 @@ Rectangle {
121124
122125 ListItem.ItemSelector {
123126 id: appStateSelector
127+ activeFocusOnPress: false
124128 anchors { left: parent.left; right: parent.right }
125129 text: "Application state"
126130 model: ["Starting",
@@ -146,6 +150,7 @@ Rectangle {
146150
147151 Button {
148152 anchors { left: parent.left; right: parent.right }
153+ activeFocusOnPress: false
149154 text: "Rotate device \u27F3"
150155 onClicked: {
151156 var orientation = applicationWindowLoader.item.orientation
@@ -398,7 +403,7 @@ Rectangle {
398403 verify(stateGroup.state === "void");
399404 }
400405
401− function test_forceActiveFocusFollowsInterative() {
406+ function test_surfaceActiveFocusFollowsAppWindowInterative() {
402407 fakeApplication.createSession();
403408 applicationWindowLoader.item.interactive = false;
404409 applicationWindowLoader.item.interactive = true;
tests/qmltests/Stages/tst_PhoneStage.qml+4
@@ -29,6 +29,7 @@ Item {
2929 PhoneStage {
3030 id: phoneStage
3131 anchors { fill: parent; rightMargin: units.gu(30) }
32+ focus: true
3233 dragAreaWidth: units.gu(2)
3334 maximizedAppTopMargin: units.gu(3) + units.dp(2)
3435 interactive: true
@@ -51,6 +52,7 @@ Item {
5152 Button {
5253 anchors { left: parent.left; right: parent.right }
5354 text: "Add App"
55+ activeFocusOnPress: false
5456 onClicked: {
5557 testCase.addApps();
5658 }
@@ -58,6 +60,7 @@ Item {
5860 Button {
5961 anchors { left: parent.left; right: parent.right }
6062 text: "Remove Selected"
63+ activeFocusOnPress: false
6164 onClicked: {
6265 ApplicationManager.stopApplication(ApplicationManager.get(appList.selectedAppIndex).appId);
6366 }
@@ -65,6 +68,7 @@ Item {
6568 Button {
6669 anchors { left: parent.left; right: parent.right }
6770 text: "Stop Selected"
71+ activeFocusOnPress: false
6872 onClicked: {
6973 ApplicationManager.get(appList.selectedAppIndex).setState(ApplicationInfoInterface.Stopped);
7074 }
tests/qmltests/Stages/tst_SessionContainer.qml+14−7
@@ -42,11 +42,14 @@ Rectangle {
4242 id: sessionContainer
4343 anchors.fill: parent
4444 orientation: Qt.PortraitOrientation
45+ focus: true
46+ interactive: true
4547 }
4648 }
4749
4850 Loader {
4951 id: sessionContainerLoader
52+ focus: true
5053 anchors {
5154 top: parent.top
5255 bottom: parent.bottom
@@ -75,6 +78,7 @@ Rectangle {
7578 CheckBox {
7679 id: sessionCheckbox;
7780 checked: false;
81+ activeFocusOnPress: false
7882 onCheckedChanged: {
7983 if (sessionContainerLoader.status !== Loader.Ready)
8084 return;
@@ -190,8 +194,6 @@ Rectangle {
190194 var sessionContainer = sessionContainerLoader.item;
191195 compare(sessionContainer.childSessions.count(), 0);
192196
193− sessionContainer.interactive = true;
194−
195197 var i;
196198 var sessions = [];
197199 // 3 sessions should cover all edge cases
@@ -209,14 +211,19 @@ Rectangle {
209211
210212 var a_session;
211213 while(a_session = sessions.pop()) {
212− a_session.surface.forceActiveFocus();
213214 compare(a_session.surface.activeFocus, true);
214215
215− var parentSession = a_session.parentSession;
216− sessionContainer.session.removeChildSession(a_session);
217− compare(a_session.surface.activeFocus, false);
216+ ApplicationTest.removeSurface(a_session.surface);
217+ ApplicationTest.removeSession(a_session);
218218
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+ }
220227 }
221228 }
222229
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 {
6363 anchors.fill: parent
6464 Loader {
6565 id: shellLoader
66+ focus: true
67+
6668 active: false
6769 property bool itemDestroyed: false
6870 sourceComponent: Component {
@@ -88,6 +90,7 @@ Item {
8890 anchors { left: parent.left; right: parent.right }
8991 Button {
9092 text: "Show Greeter"
93+ activeFocusOnPress: false
9194 onClicked: {
9295 if (shellLoader.status !== Loader.Ready)
9396 return;
@@ -434,11 +437,11 @@ Item {
434437 waitUntilTransitionsEnd(appWindowStateGroup);
435438 }
436439
437− function test_surfaceLosesFocusWhilePanelIsOpen() {
440+ function test_surfaceLosesActiveFocusWhilePanelIsOpen() {
438441 var app = ApplicationManager.startApplication("dialer-app");
439442 waitUntilAppWindowIsFullyLoaded(app);
440443
441− tryCompare(app.session.surface, "focus", true);
444+ tryCompare(app.session.surface, "activeFocus", true);
442445
443446 // Drag the indicators panel half-open
444447 var touchX = shell.width / 2;
@@ -449,7 +452,7 @@ Item {
449452 true /* beginTouch */, false /* endTouch */);
450453 verify(indicators.partiallyOpened);
451454
452− tryCompare(app.session.surface, "focus", false);
455+ tryCompare(app.session.surface, "activeFocus", false);
453456
454457 // And finish getting it open
455458 touchFlick(indicators,
@@ -458,11 +461,21 @@ Item {
458461 false /* beginTouch */, true /* endTouch */);
459462 tryCompare(indicators, "fullyOpened", true);
460463
461− tryCompare(app.session.surface, "focus", false);
464+ tryCompare(app.session.surface, "activeFocus", false);
462465
463466 dragToCloseIndicatorsPanel();
464467
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);
466479 }
467480
468481 // Wait for the whole UI to settle down
@@ -477,6 +490,14 @@ Item {
477490 waitForRendering(shell)
478491 }
479492
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+
480501 function dragToCloseIndicatorsPanel() {
481502 var indicators = findChild(shell, "indicators");
482503
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 @@
11add_executable(uqmlscene
22 ${shellapplication_MOC_SRCS}
3+ ActiveFocusLogger.cpp
34 main.cpp
45 ${CMAKE_SOURCE_DIR}/src/MouseTouchAdaptor.cpp
56 )
tests/uqmlscene/main.cpp+13
@@ -68,6 +68,12 @@
6868// UbuntuGestures lib
6969#include <TouchRegistry.h>
7070
71+#define UQMLSCENE_DEBUG_ACTIVE_FOCUS 0
72+
73+#if UQMLSCENE_DEBUG_ACTIVE_FOCUS
74+ #include "ActiveFocusLogger.h"
75+#endif
76+
7177#ifdef QML_RUNTIME_TESTING
7278class RenderStatistics
7379{
@@ -378,6 +384,10 @@ int main(int argc, char ** argv)
378384{
379385 Options options;
380386
387+ #if UQMLSCENE_DEBUG_ACTIVE_FOCUS
388+ ActiveFocusLogger activeFocusLogger;
389+ #endif
390+
381391 QStringList imports;
382392 QList<QPair<QString, QString> > bundles;
383393 for (int i = 1; i < argc; ++i) {
@@ -503,6 +513,9 @@ int main(int argc, char ** argv)
503513 QQuickItem *contentItem = qobject_cast<QQuickItem *>(topLevel);
504514 if (contentItem) {
505515 qxView = new QQuickView(&engine, nullptr);
516+ #if UQMLSCENE_DEBUG_ACTIVE_FOCUS
517+ activeFocusLogger.setWindow(qxView);
518+ #endif
506519 TouchRegistry::instance()->setParent(qxView);
507520 qxView->installEventFilter(TouchRegistry::instance());
508521 window = qxView;