Merge lp:~dandrader/unity8/fixSurfaceActiveFocus into lp:unity8

Proposed by Daniel d'Andrada on 2015-01-28
Status: Merged
Approved by: Albert Astals Cid on 2015-02-05
Approved revision: 1583
Merged at revision: 1602
Proposed branch: lp:~dandrader/unity8/fixSurfaceActiveFocus
Merge into: lp:unity8
Diff against target: 944 lines (+439/-52)
23 files modified
plugins/Ubuntu/Gestures/TouchGate.cpp (+2/-0)
plugins/Ubuntu/Gestures/TouchGate.h (+2/-0)
qml/Shell.qml (+10/-1)
qml/Stages/ApplicationWindow.qml (+3/-1)
qml/Stages/PhoneStage.qml (+2/-0)
qml/Stages/SessionContainer.qml (+24/-9)
qml/Stages/SpreadDelegate.qml (+2/-1)
qml/Stages/SurfaceContainer.qml (+11/-25)
qml/Stages/TabletStage.qml (+14/-0)
qml/Stages/TransformedTabletSpreadDelegate.qml (+1/-1)
tests/mocks/Unity/Application/MirSurfaceItem.qml (+16/-1)
tests/qmltests/CMakeLists.txt (+1/-0)
tests/qmltests/Stages/ApplicationCheckBox.qml (+45/-0)
tests/qmltests/Stages/RecursingChildSessionControl.qml (+3/-0)
tests/qmltests/Stages/tst_ApplicationWindow.qml (+6/-1)
tests/qmltests/Stages/tst_PhoneStage.qml (+4/-0)
tests/qmltests/Stages/tst_SessionContainer.qml (+14/-7)
tests/qmltests/Stages/tst_TabletStage.qml (+154/-0)
tests/qmltests/tst_Shell.qml (+26/-5)
tests/uqmlscene/ActiveFocusLogger.cpp (+48/-0)
tests/uqmlscene/ActiveFocusLogger.h (+37/-0)
tests/uqmlscene/CMakeLists.txt (+1/-0)
tests/uqmlscene/main.cpp (+13/-0)
To merge this branch: bzr merge lp:~dandrader/unity8/fixSurfaceActiveFocus
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Approve on 2015-02-11
Michael Zanetti (community) 2015-01-28 Approve on 2015-02-03
Review via email: mp+247836@code.launchpad.net

Commit Message

Tapping on a surface gives it active focus

Which needed a big refactoring of the active
focus handling of surfaces.

Useful in tablet mode, where you may have
two running applications: one on the main stage and
the other on the side stage.

Also needed for the desktop mode.

Description of the Change

* Are there any related MPs required for this MP to build/function as expected? Please list.
No.

* Did you perform an exploratory manual test run of your code change and any related functionality?
Yes.

* Did you make sure that your branch does not contain spurious tags?
Yes.

* If you changed the packaging (debian), did you subscribe the ubuntu-unity team to this MP?
Not applicable.

* If you changed the UI, has there been a design review?
Not applicable.

To post a comment you must log in.
Michael Zanetti (mzanetti) wrote :

one inline comment on the code. still have to test it.

review: Needs Information
Michael Zanetti (mzanetti) wrote :

* See two inline comments on the tests...

* There's something fishy with the tryTabletStage and the isFullscreen property. It prints warnings about it being undefined and the scaling of the fullscreen app in spread isn't working.

* When you close an app from the spread, the checkbox isn't updated. Probably not really a big problem though

* I think it'd be better to have it like in the phonestage, with an "add app" button to add many more apps. We'll need that in the long run pretty sure, so better not start building on wrong assumptions.

review: Needs Fixing
Daniel d'Andrada (dandrader) wrote :

Nice catch.

Daniel d'Andrada (dandrader) wrote :

> * See two inline comments on the tests...

Fixed. Thanks.

>
> * There's something fishy with the tryTabletStage and the isFullscreen
> property. It prints warnings about it being undefined and the scaling of the
> fullscreen app in spread isn't working.

That's a pre-existing issue. isFullscreen is already absent and cause warnings in trunk.

>
> * When you close an app from the spread, the checkbox isn't updated. Probably
> not really a big problem though

Right. I didn't have the motivation to make that work as I can already manually test all use cases I was interested in without it.

>
> * I think it'd be better to have it like in the phonestage, with an "add app"
> button to add many more apps. We'll need that in the long run pretty sure, so
> better not start building on wrong assumptions.

I chose the checkbox way as needed to launch a *specific* app (eg. a side stage or a main stage one), not just any app as in the PhoneStage test.

Michael Zanetti (mzanetti) wrote :

> > * There's something fishy with the tryTabletStage and the isFullscreen
> > property. It prints warnings about it being undefined and the scaling of the
> > fullscreen app in spread isn't working.
>
> That's a pre-existing issue. isFullscreen is already absent and cause warnings
> in trunk.

yeah... seems it broke in revision 1201. isFullscreen was renamed to fullscreen and this one didn't get updated... Now would be a good opportunity to fix it again ;)

>
> >
> > * I think it'd be better to have it like in the phonestage, with an "add
> app"
> > button to add many more apps. We'll need that in the long run pretty sure,
> so
> > better not start building on wrong assumptions.
>
> I chose the checkbox way as needed to launch a *specific* app (eg. a side
> stage or a main stage one), not just any app as in the PhoneStage test.

could we have two buttons? "Add main stage" and "Add side stage"?

Daniel d'Andrada (dandrader) wrote :

On 02/02/2015 17:12, Michael Zanetti wrote:
>>> * There's something fishy with the tryTabletStage and the isFullscreen
>>> > >property. It prints warnings about it being undefined and the scaling of the
>>> > >fullscreen app in spread isn't working.
>> >
>> >That's a pre-existing issue. isFullscreen is already absent and cause warnings
>> >in trunk.
> yeah... seems it broke in revision 1201. isFullscreen was renamed to fullscreen and this one didn't get updated... Now would be a good opportunity to fix it again ;)
>
>
Ok, done.

Michael Zanetti (mzanetti) wrote :

 * Did you perform an exploratory manual test run of the code change and any related functionality?

yes

 * Did CI run pass? If not, please explain why.

no, but doesn't look related

 * Did you make sure that the branch does not contain spurious tags?

yes

review: Approve
Albert Astals Cid (aacid) wrote :

Text conflict in tests/qmltests/CMakeLists.txt
Text conflict in tests/qmltests/tst_Shell.qml
2 conflicts encountered.

1583. By Daniel d'Andrada on 2015-02-05

Merge trunk

[ Andrea Cimitan ]
* Fix continue button in wifi wizard page, adds qml-module-
  qtsysteminfo as unity8 dep (LP: #1363400)
* Background needs to be specified to be visible in horizontal cards
  (LP: #1411748)
[ CI Train Bot ]
* Resync trunk
[ MichaƂ Sawicz ]
* Add workaround for gcc ICE.
[ Albert Astals ]
* Implement proper updateRanges for horizontal items (i.e. Carousel,
  Horizontal List)
[ Michael Terry ]
* Don't accept multiple "Finish" clicks during the last step of the
  wizard
[ Daniel d'Andrada ]
* Unify all liblightdm mocks
* Ensure the greeter password field is not covered by the keyboard
* Don't show() the lockscreen if it's already being shown

Daniel d'Andrada (dandrader) wrote :

>
> Text conflict in tests/qmltests/CMakeLists.txt
> Text conflict in tests/qmltests/tst_Shell.qml
> 2 conflicts encountered.

Fixed.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'plugins/Ubuntu/Gestures/TouchGate.cpp'
2--- plugins/Ubuntu/Gestures/TouchGate.cpp 2014-10-17 11:01:53 +0000
3+++ plugins/Ubuntu/Gestures/TouchGate.cpp 2015-02-05 14:50:51 +0000
4@@ -53,6 +53,8 @@
5 m_touchInfoMap[touchPoint.id()].ownership = OwnershipRequested;
6 m_touchInfoMap[touchPoint.id()].ended = false;
7 TouchRegistry::instance()->requestTouchOwnership(touchPoint.id(), this);
8+
9+ Q_EMIT pressed();
10 }
11
12 goodToGo &= m_touchInfoMap.contains(touchPoint.id())
13
14=== modified file 'plugins/Ubuntu/Gestures/TouchGate.h'
15--- plugins/Ubuntu/Gestures/TouchGate.h 2014-10-17 11:01:53 +0000
16+++ plugins/Ubuntu/Gestures/TouchGate.h 2015-02-05 14:50:51 +0000
17@@ -53,6 +53,8 @@
18 Q_SIGNALS:
19 void targetItemChanged(QQuickItem *item);
20
21+ void pressed();
22+
23 protected:
24 void touchEvent(QTouchEvent *event) override;
25 private:
26
27=== modified file 'qml/Shell.qml'
28--- qml/Shell.qml 2015-02-05 10:29:34 +0000
29+++ qml/Shell.qml 2015-02-05 14:50:51 +0000
30@@ -287,6 +287,15 @@
31 source: usageModeSettings.usageMode === "Windowed" ? "Stages/DesktopStage.qml"
32 : tabletMode ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
33
34+ property bool interactive: edgeDemo.stagesEnabled
35+ && !greeter.shown
36+ && !lockscreen.shown
37+ && panel.indicators.fullyClosed
38+ && launcher.progress == 0
39+ && !notifications.useModal
40+
41+ onInteractiveChanged: { if (interactive) { focus = true; } }
42+
43 Binding {
44 target: applicationsDisplayLoader.item
45 property: "objectName"
46@@ -306,7 +315,7 @@
47 Binding {
48 target: applicationsDisplayLoader.item
49 property: "interactive"
50- value: edgeDemo.stagesEnabled && !greeter.shown && !lockscreen.shown && panel.indicators.fullyClosed && launcher.progress == 0 && !notifications.useModal
51+ value: applicationsDisplayLoader.interactive
52 }
53 Binding {
54 target: applicationsDisplayLoader.item
55
56=== modified file 'qml/Stages/ApplicationWindow.qml'
57--- qml/Stages/ApplicationWindow.qml 2014-11-04 14:51:13 +0000
58+++ qml/Stages/ApplicationWindow.qml 2015-02-05 14:50:51 +0000
59@@ -18,7 +18,7 @@
60 import Ubuntu.Components 1.1
61 import Unity.Application 0.1
62
63-Item {
64+FocusScope {
65 id: root
66
67 // to be read from outside
68@@ -120,6 +120,8 @@
69 d.surfaceInitialized = false;
70 }
71 }
72+
73+ focus: true
74 }
75
76 StateGroup {
77
78=== modified file 'qml/Stages/PhoneStage.qml'
79--- qml/Stages/PhoneStage.qml 2015-01-12 11:21:17 +0000
80+++ qml/Stages/PhoneStage.qml 2015-02-05 14:50:51 +0000
81@@ -109,6 +109,8 @@
82
83 onFocusedAppIdChanged: focusedAppDelegate = spreadRepeater.itemAt(0);
84
85+ onFocusedAppDelegateChanged: focusedAppDelegate.focus = true;
86+
87 function indexOf(appId) {
88 for (var i = 0; i < ApplicationManager.count; i++) {
89 if (ApplicationManager.get(i).appId == appId) {
90
91=== modified file 'qml/Stages/SessionContainer.qml'
92--- qml/Stages/SessionContainer.qml 2014-11-12 01:09:03 +0000
93+++ qml/Stages/SessionContainer.qml 2015-02-05 14:50:51 +0000
94@@ -17,7 +17,7 @@
95 import QtQuick 2.0
96 import "Animations"
97
98-Item {
99+FocusScope {
100 id: root
101 objectName: "sessionContainer"
102 property QtObject session
103@@ -34,8 +34,8 @@
104 orientation: root.orientation
105 }
106
107-
108 Repeater {
109+ id: childSessionsRepeater
110 model: root.childSessions
111
112 delegate: Loader {
113@@ -45,9 +45,17 @@
114 // Only way to do recursive qml items.
115 source: Qt.resolvedUrl("SessionContainer.qml")
116
117- Binding {
118- target: item; when: item
119- property: "interactive"; value: root.interactive
120+ z: index
121+
122+ // Since a Loader is a FocusScope, propagate its focus to the loaded Item
123+ Binding {
124+ target: item; when: item
125+ property: "focus"; value: focus
126+ }
127+
128+ Binding {
129+ target: item; when: item
130+ property: "interactive"; value: index == (childSessionsRepeater.count - 1) && root.interactive
131 }
132
133 Binding {
134@@ -69,10 +77,6 @@
135 target: item; when: item
136 property: "orientation"; value: root.orientation
137 }
138-
139- Component.onDestruction: {
140- root.session.surface.forceActiveFocus();
141- }
142 }
143 }
144
145@@ -138,5 +142,16 @@
146 QtObject {
147 id: d
148 property var animations: []
149+
150+ property var focusedChild: {
151+ if (childSessionsRepeater.count == 0) {
152+ return _surfaceContainer;
153+ } else {
154+ return childSessionsRepeater.itemAt(childSessionsRepeater.count - 1);
155+ }
156+ }
157+ onFocusedChildChanged: {
158+ focusedChild.focus = true;
159+ }
160 }
161 }
162
163=== modified file 'qml/Stages/SpreadDelegate.qml'
164--- qml/Stages/SpreadDelegate.qml 2014-11-27 11:13:08 +0000
165+++ qml/Stages/SpreadDelegate.qml 2015-02-05 14:50:51 +0000
166@@ -21,7 +21,7 @@
167 import Ubuntu.Components 1.1
168 import "../Components"
169
170-Item {
171+FocusScope {
172 id: root
173
174 // to be read from outside
175@@ -65,6 +65,7 @@
176 ApplicationWindow {
177 id: appWindow
178 objectName: application ? "appWindow_" + application.appId : "appWindow_null"
179+ focus: true
180 anchors {
181 fill: parent
182 topMargin: appWindow.fullscreen ? 0 : maximizedAppTopMargin
183
184=== modified file 'qml/Stages/SurfaceContainer.qml'
185--- qml/Stages/SurfaceContainer.qml 2014-11-04 14:49:47 +0000
186+++ qml/Stages/SurfaceContainer.qml 2015-02-05 14:50:51 +0000
187@@ -18,7 +18,7 @@
188 import Ubuntu.Components 1.1
189 import Ubuntu.Gestures 0.1 // For TouchGate
190
191-Item {
192+FocusScope {
193 id: root
194 objectName: "surfaceContainer"
195 property Item surface: null
196@@ -26,10 +26,15 @@
197 property int orientation
198 property bool interactive
199
200+ signal surfacePressed()
201+
202 onSurfaceChanged: {
203 if (surface) {
204+ // Set the surface focus *after* it is added to the scene to
205+ // ensure an update to the scene's active focus.
206+ surface.focus = false;
207 surface.parent = root;
208- d.forceSurfaceActiveFocusIfReady();
209+ surface.focus = true;
210 } else {
211 hadSurface = true;
212 }
213@@ -38,7 +43,6 @@
214 Binding { target: surface; property: "orientation"; value: root.orientation }
215 Binding { target: surface; property: "z"; value: 1 }
216 Binding { target: surface; property: "enabled"; value: root.interactive; when: surface }
217- Binding { target: surface; property: "focus"; value: root.interactive; when: surface }
218 Binding { target: surface; property: "antialiasing"; value: !root.interactive; when: surface }
219
220 TouchGate {
221@@ -46,28 +50,10 @@
222 anchors.fill: root
223 enabled: root.surface ? root.surface.enabled : false
224 z: 2
225- }
226-
227- Connections {
228- target: root.surface
229- // FIXME: I would rather not need to do this, but currently it doesn't get
230- // active focus without it and I don't know why.
231- // Possibly because if an item get focus=true before it has a parent, once
232- // it gets a parent QQuickWindow won't check its focus and update its activeFocus
233- // accordingly. Unlike when you focus=true after the item already has a parent.
234- onFocusChanged: d.forceSurfaceActiveFocusIfReady();
235- onParentChanged: d.forceSurfaceActiveFocusIfReady();
236- onEnabledChanged: d.forceSurfaceActiveFocusIfReady();
237- }
238-
239- QtObject {
240- id: d
241- function forceSurfaceActiveFocusIfReady() {
242- if (root.surface !== null &&
243- root.surface.focus &&
244- root.surface.parent === root &&
245- root.surface.enabled) {
246- root.surface.forceActiveFocus();
247+ onPressed: {
248+ root.focus = true;
249+ if (root.interactive) {
250+ root.forceActiveFocus();
251 }
252 }
253 }
254
255=== modified file 'qml/Stages/TabletStage.qml'
256--- qml/Stages/TabletStage.qml 2015-01-12 11:21:17 +0000
257+++ qml/Stages/TabletStage.qml 2015-02-05 14:50:51 +0000
258@@ -76,6 +76,20 @@
259
260 appId0 = ApplicationManager.count >= 1 ? ApplicationManager.get(0).appId : "";
261 appId1 = ApplicationManager.count > 1 ? ApplicationManager.get(1).appId : "";
262+
263+ // Update the QML focus accordingly
264+ updateSpreadDelegateFocus();
265+ }
266+
267+ function updateSpreadDelegateFocus() {
268+ if (priv.focusedAppId) {
269+ var focusedAppIndex = priv.indexOf(priv.focusedAppId);
270+ if (focusedAppIndex !== -1) {
271+ spreadRepeater.itemAt(focusedAppIndex).focus = true;
272+ } else {
273+ console.warn("TabletStage: Failed to find the SpreadDelegate for appID=" + priv.focusedAppId);
274+ }
275+ }
276 }
277
278 function indexOf(appId) {
279
280=== modified file 'qml/Stages/TransformedTabletSpreadDelegate.qml'
281--- qml/Stages/TransformedTabletSpreadDelegate.qml 2014-09-02 12:57:15 +0000
282+++ qml/Stages/TransformedTabletSpreadDelegate.qml 2015-02-05 14:50:51 +0000
283@@ -366,7 +366,7 @@
284 Scale {
285 origin { x: 0; y: (spreadView.height * priv.scale) + maximizedAppTopMargin * 3 }
286 xScale: 1
287- yScale: isFullscreen ? 1 - priv.topMarginProgress * maximizedAppTopMargin / spreadView.height : 1
288+ yScale: fullscreen ? 1 - priv.topMarginProgress * maximizedAppTopMargin / spreadView.height : 1
289 },
290 Translate {
291 x: priv.xTranslate
292
293=== modified file 'tests/mocks/Unity/Application/MirSurfaceItem.qml'
294--- tests/mocks/Unity/Application/MirSurfaceItem.qml 2014-10-01 13:20:32 +0000
295+++ tests/mocks/Unity/Application/MirSurfaceItem.qml 2015-02-05 14:50:51 +0000
296@@ -44,9 +44,24 @@
297 }
298
299 Text {
300+ text: surfaceText.text
301+ color: "black"
302+ font: surfaceText.font
303+ fontSizeMode: Text.Fit
304+ minimumPixelSize: 10
305+ verticalAlignment: Text.AlignVCenter
306+ x: surfaceText.x
307+ y: surfaceText.y
308+ width: surfaceText.width
309+ height: surfaceText.height
310+
311+ transform: Translate { x: -2; y: -2 }
312+ }
313+ Text {
314+ id: surfaceText
315 anchors.fill: parent
316 text: "SURFACE"
317- color: "yellow"
318+ color: root.parent && root.parent.activeFocus ? "yellow" : "blue"
319 font.bold: true
320 fontSizeMode: Text.Fit
321 minimumPixelSize: 10; font.pixelSize: 200
322
323=== modified file 'tests/qmltests/CMakeLists.txt'
324--- tests/qmltests/CMakeLists.txt 2015-01-22 11:56:24 +0000
325+++ tests/qmltests/CMakeLists.txt 2015-02-05 14:50:51 +0000
326@@ -88,6 +88,7 @@
327 add_qml_test(Stages SpreadDelegate ENVIRONMENT)
328 add_qml_test(Stages SurfaceContainer ENVIRONMENT)
329 add_qml_test(Stages SessionContainer ENVIRONMENT)
330+add_qml_test(Stages TabletStage)
331 add_qml_test(Stages WindowMoveResizeArea)
332 add_qml_test(Stages Splash)
333 add_qml_test(Wizard Wizard)
334
335=== added file 'tests/qmltests/Stages/ApplicationCheckBox.qml'
336--- tests/qmltests/Stages/ApplicationCheckBox.qml 1970-01-01 00:00:00 +0000
337+++ tests/qmltests/Stages/ApplicationCheckBox.qml 2015-02-05 14:50:51 +0000
338@@ -0,0 +1,45 @@
339+/*
340+ * Copyright (C) 2015 Canonical, Ltd.
341+ *
342+ * This program is free software; you can redistribute it and/or modify
343+ * it under the terms of the GNU General Public License as published by
344+ * the Free Software Foundation; version 3.
345+ *
346+ * This program is distributed in the hope that it will be useful,
347+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
348+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
349+ * GNU General Public License for more details.
350+ *
351+ * You should have received a copy of the GNU General Public License
352+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
353+ */
354+
355+import QtQuick 2.0
356+import QtQuick.Layouts 1.1
357+import Ubuntu.Components 1.1
358+import Unity.Application 0.1
359+
360+RowLayout {
361+ id: root
362+ property string appId
363+ property alias checked: checkbox.checked
364+
365+ Layout.fillWidth: true
366+ CheckBox {
367+ id: checkbox
368+ checked: false
369+ activeFocusOnPress: false
370+ onCheckedChanged: {
371+ if (checked) {
372+ ApplicationManager.startApplication(root.appId);
373+ } else {
374+ ApplicationManager.stopApplication(root.appId);
375+ }
376+ }
377+ }
378+ Label {
379+ text: root.appId
380+ color: "white"
381+ anchors.verticalCenter: parent.verticalCenter
382+ }
383+}
384
385=== modified file 'tests/qmltests/Stages/RecursingChildSessionControl.qml'
386--- tests/qmltests/Stages/RecursingChildSessionControl.qml 2014-09-10 16:06:05 +0000
387+++ tests/qmltests/Stages/RecursingChildSessionControl.qml 2015-02-05 14:50:51 +0000
388@@ -89,6 +89,7 @@
389 CheckBox {
390 id: _surfaceCheckbox;
391 checked: false;
392+ activeFocusOnPress: false
393 enabled: root.session
394 onCheckedChanged: {
395 if (checked) {
396@@ -117,6 +118,7 @@
397
398 Button {
399 enabled: root.session
400+ activeFocusOnPress: false
401 text: removable ? "Remove" : "Release"
402 onClicked: {
403 if (removable) {
404@@ -131,6 +133,7 @@
405
406 Button {
407 enabled: root.session !== null
408+ activeFocusOnPress: false
409 text: "Add Child"
410 onClicked: {
411 var screenshot = Math.round(Math.random()*100 % (screenshotIds.length-1));
412
413=== modified file 'tests/qmltests/Stages/tst_ApplicationWindow.qml'
414--- tests/qmltests/Stages/tst_ApplicationWindow.qml 2014-10-01 18:16:04 +0000
415+++ tests/qmltests/Stages/tst_ApplicationWindow.qml 2015-02-05 14:50:51 +0000
416@@ -52,10 +52,12 @@
417 application: fakeApplication
418 orientation: Qt.PortraitOrientation
419 interactive: true
420+ focus: true
421 }
422 }
423 Loader {
424 id: applicationWindowLoader
425+ focus: true
426 anchors {
427 top: parent.top
428 bottom: parent.bottom
429@@ -83,6 +85,7 @@
430
431 CheckBox {
432 id: sessionCheckbox; checked: false
433+ activeFocusOnPress: false
434 onCheckedChanged: {
435 if (applicationWindowLoader.status !== Loader.Ready)
436 return;
437@@ -121,6 +124,7 @@
438
439 ListItem.ItemSelector {
440 id: appStateSelector
441+ activeFocusOnPress: false
442 anchors { left: parent.left; right: parent.right }
443 text: "Application state"
444 model: ["Starting",
445@@ -146,6 +150,7 @@
446
447 Button {
448 anchors { left: parent.left; right: parent.right }
449+ activeFocusOnPress: false
450 text: "Rotate device \u27F3"
451 onClicked: {
452 var orientation = applicationWindowLoader.item.orientation
453@@ -398,7 +403,7 @@
454 verify(stateGroup.state === "void");
455 }
456
457- function test_forceActiveFocusFollowsInterative() {
458+ function test_surfaceActiveFocusFollowsAppWindowInterative() {
459 fakeApplication.createSession();
460 applicationWindowLoader.item.interactive = false;
461 applicationWindowLoader.item.interactive = true;
462
463=== modified file 'tests/qmltests/Stages/tst_PhoneStage.qml'
464--- tests/qmltests/Stages/tst_PhoneStage.qml 2015-01-12 11:21:17 +0000
465+++ tests/qmltests/Stages/tst_PhoneStage.qml 2015-02-05 14:50:51 +0000
466@@ -29,6 +29,7 @@
467 PhoneStage {
468 id: phoneStage
469 anchors { fill: parent; rightMargin: units.gu(30) }
470+ focus: true
471 dragAreaWidth: units.gu(2)
472 maximizedAppTopMargin: units.gu(3) + units.dp(2)
473 interactive: true
474@@ -51,6 +52,7 @@
475 Button {
476 anchors { left: parent.left; right: parent.right }
477 text: "Add App"
478+ activeFocusOnPress: false
479 onClicked: {
480 testCase.addApps();
481 }
482@@ -58,6 +60,7 @@
483 Button {
484 anchors { left: parent.left; right: parent.right }
485 text: "Remove Selected"
486+ activeFocusOnPress: false
487 onClicked: {
488 ApplicationManager.stopApplication(ApplicationManager.get(appList.selectedAppIndex).appId);
489 }
490@@ -65,6 +68,7 @@
491 Button {
492 anchors { left: parent.left; right: parent.right }
493 text: "Stop Selected"
494+ activeFocusOnPress: false
495 onClicked: {
496 ApplicationManager.get(appList.selectedAppIndex).setState(ApplicationInfoInterface.Stopped);
497 }
498
499=== modified file 'tests/qmltests/Stages/tst_SessionContainer.qml'
500--- tests/qmltests/Stages/tst_SessionContainer.qml 2014-10-24 14:31:29 +0000
501+++ tests/qmltests/Stages/tst_SessionContainer.qml 2015-02-05 14:50:51 +0000
502@@ -42,11 +42,14 @@
503 id: sessionContainer
504 anchors.fill: parent
505 orientation: Qt.PortraitOrientation
506+ focus: true
507+ interactive: true
508 }
509 }
510
511 Loader {
512 id: sessionContainerLoader
513+ focus: true
514 anchors {
515 top: parent.top
516 bottom: parent.bottom
517@@ -75,6 +78,7 @@
518 CheckBox {
519 id: sessionCheckbox;
520 checked: false;
521+ activeFocusOnPress: false
522 onCheckedChanged: {
523 if (sessionContainerLoader.status !== Loader.Ready)
524 return;
525@@ -190,8 +194,6 @@
526 var sessionContainer = sessionContainerLoader.item;
527 compare(sessionContainer.childSessions.count(), 0);
528
529- sessionContainer.interactive = true;
530-
531 var i;
532 var sessions = [];
533 // 3 sessions should cover all edge cases
534@@ -209,14 +211,19 @@
535
536 var a_session;
537 while(a_session = sessions.pop()) {
538- a_session.surface.forceActiveFocus();
539 compare(a_session.surface.activeFocus, true);
540
541- var parentSession = a_session.parentSession;
542- sessionContainer.session.removeChildSession(a_session);
543- compare(a_session.surface.activeFocus, false);
544+ ApplicationTest.removeSurface(a_session.surface);
545+ ApplicationTest.removeSession(a_session);
546
547- compare(parentSession.surface.activeFocus, true);
548+ if (sessions.length > 0) {
549+ // active focus should have gone to the yongest remaining sibling
550+ var previousSiblingSurface = sessions[sessions.length - 1].surface
551+ tryCompare(previousSiblingSurface, "activeFocus", true);
552+ } else {
553+ // active focus should have gone to the parent surface
554+ tryCompare(sessionContainer.session.surface, "activeFocus", true);
555+ }
556 }
557 }
558
559
560=== added file 'tests/qmltests/Stages/tst_TabletStage.qml'
561--- tests/qmltests/Stages/tst_TabletStage.qml 1970-01-01 00:00:00 +0000
562+++ tests/qmltests/Stages/tst_TabletStage.qml 2015-02-05 14:50:51 +0000
563@@ -0,0 +1,154 @@
564+/*
565+ * Copyright (C) 2015 Canonical, Ltd.
566+ *
567+ * This program is free software; you can redistribute it and/or modify
568+ * it under the terms of the GNU General Public License as published by
569+ * the Free Software Foundation; version 3.
570+ *
571+ * This program is distributed in the hope that it will be useful,
572+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
573+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
574+ * GNU General Public License for more details.
575+ *
576+ * You should have received a copy of the GNU General Public License
577+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
578+ */
579+
580+import QtQuick 2.0
581+import QtTest 1.0
582+import Ubuntu.Components 1.1
583+import Ubuntu.Components.ListItems 1.0 as ListItem
584+import Unity.Application 0.1
585+import Unity.Test 0.1
586+
587+import "../../../qml/Stages"
588+
589+Rectangle {
590+ id: root
591+ color: "grey"
592+ width: tabletStageLoader.width + controls.width
593+ height: tabletStageLoader.height
594+
595+ Loader {
596+ id: tabletStageLoader
597+
598+ x: ((root.width - controls.width) - width) / 2
599+ y: (root.height - height) / 2
600+ width: units.gu(160*0.7)
601+ height: units.gu(100*0.7)
602+
603+ focus: true
604+
605+ property bool itemDestroyed: false
606+ sourceComponent: Component {
607+ TabletStage {
608+ anchors.fill: parent
609+ Component.onDestruction: {
610+ tabletStageLoader.itemDestroyed = true;
611+ }
612+ dragAreaWidth: units.gu(2)
613+ maximizedAppTopMargin: units.gu(3) + units.dp(2)
614+ interactive: true
615+ focus: true
616+ }
617+ }
618+ }
619+
620+ Rectangle {
621+ id: controls
622+ color: "darkgrey"
623+ width: units.gu(30)
624+ anchors {
625+ top: parent.top
626+ bottom: parent.bottom
627+ right: parent.right
628+ }
629+
630+ Column {
631+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
632+ spacing: units.gu(1)
633+ ApplicationCheckBox {
634+ id: webbrowserCheckBox
635+ appId: "webbrowser-app"
636+ }
637+ ApplicationCheckBox {
638+ id: dialerCheckBox
639+ appId: "dialer-app"
640+ }
641+ }
642+ }
643+
644+ UnityTestCase {
645+ id: testCase
646+ name: "TabletStage"
647+ when: windowShown
648+
649+ property Item tabletStage: tabletStageLoader.status === Loader.Ready ? tabletStageLoader.item : null
650+
651+ function init() {
652+ tabletStageLoader.active = true;
653+ tryCompare(tabletStageLoader, "status", Loader.Ready);
654+ }
655+
656+ function cleanup() {
657+ tabletStageLoader.itemDestroyed = false;
658+ tabletStageLoader.active = false;
659+
660+ tryCompare(tabletStageLoader, "status", Loader.Null);
661+ tryCompare(tabletStageLoader, "item", null);
662+ // Loader.status might be Loader.Null and Loader.item might be null but the Loader
663+ // actually took place. Likely because Loader waits until the next event loop
664+ // iteration to do its work. So to ensure the reload, we will wait until the
665+ // Shell instance gets destroyed.
666+ tryCompare(tabletStageLoader, "itemDestroyed", true);
667+
668+ // kill all (fake) running apps
669+ webbrowserCheckBox.checked = false;
670+ dialerCheckBox.checked = false;
671+ }
672+
673+ function waitUntilAppSurfaceShowsUp(appId) {
674+ var appWindow = findChild(tabletStage, "appWindow_" + appId);
675+ verify(appWindow);
676+ var appWindowStates = findInvisibleChild(appWindow, "applicationWindowStateGroup");
677+ verify(appWindowStates);
678+ tryCompare(appWindowStates, "state", "surface");
679+ }
680+
681+ function test_tappingSwitchesFocusBetweenStages() {
682+ webbrowserCheckBox.checked = true;
683+ waitUntilAppSurfaceShowsUp(webbrowserCheckBox.appId);
684+ var webbrowserApp = ApplicationManager.findApplication(webbrowserCheckBox.appId);
685+ compare(webbrowserApp.stage, ApplicationInfoInterface.MainStage);
686+ tryCompare(webbrowserApp.session.surface, "activeFocus", true);
687+
688+ dialerCheckBox.checked = true;
689+ waitUntilAppSurfaceShowsUp(dialerCheckBox.appId);
690+ var dialerApp = ApplicationManager.findApplication(dialerCheckBox.appId);
691+ compare(dialerApp.stage, ApplicationInfoInterface.SideStage);
692+ tryCompare(dialerApp.session.surface, "activeFocus", true);
693+ tryCompare(webbrowserApp.session.surface, "activeFocus", false);
694+
695+ // Tap on the main stage application and check if the focus
696+ // has been passed to it.
697+
698+ var webbrowserWindow = findChild(tabletStage, "appWindow_" + webbrowserApp.appId);
699+ verify(webbrowserWindow);
700+ tap(webbrowserWindow);
701+
702+ tryCompare(dialerApp.session.surface, "activeFocus", false);
703+ tryCompare(webbrowserApp.session.surface, "activeFocus", true);
704+
705+ // Now tap on the side stage application and check if the focus
706+ // has been passed back to it.
707+
708+ var dialerWindow = findChild(tabletStage, "appWindow_" + dialerApp.appId);
709+ verify(dialerWindow);
710+ tap(dialerWindow);
711+
712+ tryCompare(dialerApp.session.surface, "activeFocus", true);
713+ tryCompare(webbrowserApp.session.surface, "activeFocus", false);
714+ }
715+ }
716+
717+}
718
719=== modified file 'tests/qmltests/tst_Shell.qml'
720--- tests/qmltests/tst_Shell.qml 2015-01-20 11:50:19 +0000
721+++ tests/qmltests/tst_Shell.qml 2015-02-05 14:50:51 +0000
722@@ -63,6 +63,8 @@
723 anchors.fill: parent
724 Loader {
725 id: shellLoader
726+ focus: true
727+
728 active: false
729 property bool itemDestroyed: false
730 sourceComponent: Component {
731@@ -88,6 +90,7 @@
732 anchors { left: parent.left; right: parent.right }
733 Button {
734 text: "Show Greeter"
735+ activeFocusOnPress: false
736 onClicked: {
737 if (shellLoader.status !== Loader.Ready)
738 return;
739@@ -434,11 +437,11 @@
740 waitUntilTransitionsEnd(appWindowStateGroup);
741 }
742
743- function test_surfaceLosesFocusWhilePanelIsOpen() {
744+ function test_surfaceLosesActiveFocusWhilePanelIsOpen() {
745 var app = ApplicationManager.startApplication("dialer-app");
746 waitUntilAppWindowIsFullyLoaded(app);
747
748- tryCompare(app.session.surface, "focus", true);
749+ tryCompare(app.session.surface, "activeFocus", true);
750
751 // Drag the indicators panel half-open
752 var touchX = shell.width / 2;
753@@ -449,7 +452,7 @@
754 true /* beginTouch */, false /* endTouch */);
755 verify(indicators.partiallyOpened);
756
757- tryCompare(app.session.surface, "focus", false);
758+ tryCompare(app.session.surface, "activeFocus", false);
759
760 // And finish getting it open
761 touchFlick(indicators,
762@@ -458,11 +461,21 @@
763 false /* beginTouch */, true /* endTouch */);
764 tryCompare(indicators, "fullyOpened", true);
765
766- tryCompare(app.session.surface, "focus", false);
767+ tryCompare(app.session.surface, "activeFocus", false);
768
769 dragToCloseIndicatorsPanel();
770
771- tryCompare(app.session.surface, "focus", true);
772+ tryCompare(app.session.surface, "activeFocus", true);
773+ }
774+
775+ function test_launchedAppHasActiveFocus() {
776+ var dialerApp = ApplicationManager.startApplication("dialer-app");
777+ verify(dialerApp);
778+ waitUntilAppSurfaceShowsUp("dialer-app")
779+
780+ verify(dialerApp.session.surface);
781+
782+ tryCompare(dialerApp.session.surface, "activeFocus", true);
783 }
784
785 // Wait for the whole UI to settle down
786@@ -477,6 +490,14 @@
787 waitForRendering(shell)
788 }
789
790+ function waitUntilAppSurfaceShowsUp(appId) {
791+ var appWindow = findChild(shell, "appWindow_" + appId);
792+ verify(appWindow);
793+ var appWindowStates = findInvisibleChild(appWindow, "applicationWindowStateGroup");
794+ verify(appWindowStates);
795+ tryCompare(appWindowStates, "state", "surface");
796+ }
797+
798 function dragToCloseIndicatorsPanel() {
799 var indicators = findChild(shell, "indicators");
800
801
802=== added file 'tests/uqmlscene/ActiveFocusLogger.cpp'
803--- tests/uqmlscene/ActiveFocusLogger.cpp 1970-01-01 00:00:00 +0000
804+++ tests/uqmlscene/ActiveFocusLogger.cpp 2015-02-05 14:50:51 +0000
805@@ -0,0 +1,48 @@
806+/*
807+ * Copyright (C) 2015 Canonical, Ltd.
808+ *
809+ * This program is free software; you can redistribute it and/or modify
810+ * it under the terms of the GNU General Public License as published by
811+ * the Free Software Foundation; version 3.
812+ *
813+ * This program is distributed in the hope that it will be useful,
814+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
815+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
816+ * GNU General Public License for more details.
817+ *
818+ * You should have received a copy of the GNU General Public License
819+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
820+ */
821+
822+#include "ActiveFocusLogger.h"
823+
824+#include <QDebug>
825+#include <QQuickItem>
826+
827+void ActiveFocusLogger::setWindow(QQuickWindow *window)
828+{
829+ m_window = window;
830+ QObject::connect(window, &QQuickWindow::activeFocusItemChanged,
831+ this, &ActiveFocusLogger::printActiveFocusInfo);
832+}
833+
834+void ActiveFocusLogger::printActiveFocusInfo()
835+{
836+ if (!m_window) {
837+ return;
838+ }
839+
840+ qDebug() << "============== Active focus info START ================";
841+ if (m_window->activeFocusItem()) {
842+ qDebug() << m_window->activeFocusItem();
843+ qDebug() << "Ancestry:";
844+ QQuickItem *item = m_window->activeFocusItem()->parentItem();
845+ while (item != nullptr) {
846+ qDebug() << item << ", isFocusScope =" << item->isFocusScope();
847+ item = item->parentItem();
848+ }
849+ } else {
850+ qDebug() << "NULL";
851+ }
852+ qDebug() << "============== Active focus info END ================";
853+}
854
855=== added file 'tests/uqmlscene/ActiveFocusLogger.h'
856--- tests/uqmlscene/ActiveFocusLogger.h 1970-01-01 00:00:00 +0000
857+++ tests/uqmlscene/ActiveFocusLogger.h 2015-02-05 14:50:51 +0000
858@@ -0,0 +1,37 @@
859+/*
860+ * Copyright (C) 2015 Canonical, Ltd.
861+ *
862+ * This program is free software; you can redistribute it and/or modify
863+ * it under the terms of the GNU General Public License as published by
864+ * the Free Software Foundation; version 3.
865+ *
866+ * This program is distributed in the hope that it will be useful,
867+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
868+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
869+ * GNU General Public License for more details.
870+ *
871+ * You should have received a copy of the GNU General Public License
872+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
873+ */
874+
875+#ifndef ACTIVE_FOCUS_LOGGER_H
876+#define ACTIVE_FOCUS_LOGGER_H
877+
878+#include <QObject>
879+#include <QQuickWindow>
880+#include <QPointer>
881+
882+class ActiveFocusLogger : public QObject {
883+ Q_OBJECT
884+
885+public:
886+ void setWindow(QQuickWindow *window);
887+
888+private Q_SLOTS:
889+ void printActiveFocusInfo();
890+
891+private:
892+ QPointer<QQuickWindow> m_window;
893+};
894+
895+#endif // ACTIVE_FOCUS_LOGGER_H
896
897=== modified file 'tests/uqmlscene/CMakeLists.txt'
898--- tests/uqmlscene/CMakeLists.txt 2014-10-01 13:20:32 +0000
899+++ tests/uqmlscene/CMakeLists.txt 2015-02-05 14:50:51 +0000
900@@ -1,5 +1,6 @@
901 add_executable(uqmlscene
902 ${shellapplication_MOC_SRCS}
903+ ActiveFocusLogger.cpp
904 main.cpp
905 ${CMAKE_SOURCE_DIR}/src/MouseTouchAdaptor.cpp
906 )
907
908=== modified file 'tests/uqmlscene/main.cpp'
909--- tests/uqmlscene/main.cpp 2014-10-17 14:22:40 +0000
910+++ tests/uqmlscene/main.cpp 2015-02-05 14:50:51 +0000
911@@ -68,6 +68,12 @@
912 // UbuntuGestures lib
913 #include <TouchRegistry.h>
914
915+#define UQMLSCENE_DEBUG_ACTIVE_FOCUS 0
916+
917+#if UQMLSCENE_DEBUG_ACTIVE_FOCUS
918+ #include "ActiveFocusLogger.h"
919+#endif
920+
921 #ifdef QML_RUNTIME_TESTING
922 class RenderStatistics
923 {
924@@ -378,6 +384,10 @@
925 {
926 Options options;
927
928+ #if UQMLSCENE_DEBUG_ACTIVE_FOCUS
929+ ActiveFocusLogger activeFocusLogger;
930+ #endif
931+
932 QStringList imports;
933 QList<QPair<QString, QString> > bundles;
934 for (int i = 1; i < argc; ++i) {
935@@ -503,6 +513,9 @@
936 QQuickItem *contentItem = qobject_cast<QQuickItem *>(topLevel);
937 if (contentItem) {
938 qxView = new QQuickView(&engine, nullptr);
939+ #if UQMLSCENE_DEBUG_ACTIVE_FOCUS
940+ activeFocusLogger.setWindow(qxView);
941+ #endif
942 TouchRegistry::instance()->setParent(qxView);
943 qxView->installEventFilter(TouchRegistry::instance());
944 window = qxView;

Subscribers

People subscribed via source and target branches