Merge lp:~gerboland/unity/8-refactor-wm-and-test into lp:unity/8.0

Proposed by Gerry Boland
Status: Superseded
Proposed branch: lp:~gerboland/unity/8-refactor-wm-and-test
Merge into: lp:unity/8.0
Diff against target: 1925 lines (+1260/-289)
17 files modified
CMakeLists.txt (+1/-1)
Shell.qml (+24/-241)
Stages/SideStage.qml (+1/-1)
Stages/Stage.qml (+6/-1)
Stages/StageManager.qml (+287/-0)
debian/unity8.install (+1/-1)
tests/mocks/Ubuntu/Application/ApplicationListModel.cpp (+2/-2)
tests/mocks/Ubuntu/Application/ApplicationManager.cpp (+73/-34)
tests/mocks/Ubuntu/Application/ApplicationManager.h (+9/-0)
tests/qmltests/CMakeLists.txt (+4/-6)
tests/qmltests/Dash/Apps/tst_RunningApplicationsGrid.qml (+2/-0)
tests/qmltests/Stages/tst_SideStage.qml (+1/-1)
tests/qmltests/Stages/tst_Stage.qml (+1/-1)
tests/qmltests/Stages/tst_StageManager-phone.qml (+255/-0)
tests/qmltests/Stages/tst_StageManager-tablet.qml (+394/-0)
tests/qmltests/Stages/tst_StageManager/ListSelector.qml (+101/-0)
tests/qmltests/Stages/tst_StageManager/StageManagerTestCase.qml (+98/-0)
To merge this branch: bzr merge lp:~gerboland/unity/8-refactor-wm-and-test
Reviewer Review Type Date Requested Status
Michał Sawicz Needs Fixing
PS Jenkins bot (community) continuous-integration Needs Fixing
Michael Zanetti (community) Needs Fixing
Review via email: mp+168051@code.launchpad.net

This proposal supersedes a proposal from 2013-05-30.

This proposal has been superseded by a proposal from 2013-07-02.

Commit message

Refactor window management into a separate component, add tests and fix bug

- move the window management code out of Shell into new component called StageManager
- place StageManager and related components (Stage,SideStage) into a Stages directory, replacing the old SideStage directory
- Add tests for StageManager.
- Modify the mock Ubuntu.Application plugin to:
    - fix crash where view not initialized when plugin initialized
    - prepend new applications to the application list
    - always emit {side,main}StageFocusedApplicationChanged signal when it changes
- Fix bug where if one stage animation started before the cleanup of the previous animation had completed, the new application would not be focused

Description of the change

Refactor the window management into separate component (StageManager), add tests and fix a hard-to-reproduce bug that the tests revealed

- move the window management code out of Shell into new component called StageManager
- place StageManager and related components (Stage,SideStage) into a Stages directory, replacing the old SideStage directory
- Add tests for StageManager.
- Modify the mock Ubuntu.Application plugin to:
    - fix crash where view not initialized when plugin initialized
    - prepend new applications to the application list
    - always emit {side,main}StageFocusedApplicationChanged signal when it changes
- Fix bug where if one stage animation started before the cleanup of the previous animation had completed, the new application would not be focused

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

765 + //hides: [launcher, panel.indicators]

This will never be used in here... can go away

1454 + function rightEdgeSwipe(distance) {
1455 + if (distance == undefined) distance = units.gu(stageManagerUnderTest.width)
1456 +
1457 + var x = stageManagerUnderTest.width - (stageManagerUnderTest.edgeHandleSize / 2)
1458 + var y = stageManagerUnderTest.height / 2
1459 + mouseFlick(stageManagerUnderTest, x, y,
1460 + x - distance, y)
1461 + }
1462 +
1463 + function sideStageHandleRightSwipe(distance) {
1464 + if (distance == undefined) distance = units.gu(sideStage.width)
1465 +
1466 + var x = sideStage.x - (sideStage.rightEdgeDraggingAreaWidth / 2)
1467 + var y = sideStage.height / 2
1468 + mouseFlick(stageManagerUnderTest, x, y,
1469 + x + distance, y)
1470 + }

whats the difference between those two? wouldn't the first one be enough?

1474 + compare(stageManagerUnderTest.shown, false)

would it make sense to add a message to compare()s for easier debugging in case the test fails? I think for some of your compare()s it does make sense, not necessarily for all tho.

836 + skip("FIXME: application not unfocused when dismissed with left edge swipe")
837 + tryCompare(applicationManager, "mainStageFocusedApplication", null)

This merge seems to be right place to fix this, unless it causes lots of other changes too. Also, when this is fixed, the tryCompare() seems like a good addition to checkStageManagerOffScreen().

1026 + width: units.gu(160)

This seems a bit much and not really needed. Can we make this a bit smaller so the tests can run and seen on smaller displays too?

When running the test files in qmlscene (for manual testing/debuggin) they segfault:
# qmlscene ../tests/qmltests/Stages/tst_StageManager-tablet.qml -I ./tests/mocks/ -I tests/utils/modules/
ASSERT failure in QList<T>::operator[]: "index out of range", file /usr/include/qt5/QtCore/qlist.h, line 462
Aborted (core dumped)

We try to create testcases that are able to run standalone for manual testing/debugging which I think is quite useful once a test fails.

Overall really nice test set. Well done!

review: Needs Fixing
Revision history for this message
Gerry Boland (gerboland) wrote : Posted in a previous version of this proposal

All comments acted upon except where I've replied below:

> 1454 + function rightEdgeSwipe(distance) {
> 1457 + var x = stageManagerUnderTest.width -
> (stageManagerUnderTest.edgeHandleSize / 2)

> 1463 + function sideStageHandleRightSwipe(distance) {
> 1466 + var x = sideStage.x - (sideStage.rightEdgeDraggingAreaWidth / 2)
> whats the difference between those two? wouldn't the first one be enough?

One performs a right-edge swipe on the StageManager. The other grabs the side-stage handle and drags it to the right. They really only differ on their calculation of the starting x of the gesture, but I used different functions as there are only a small number of gestures relevant and I like verbose functions names.

> 836 + skip("FIXME: application not unfocused when dismissed with left edge
> swipe")
> 837 + tryCompare(applicationManager, "mainStageFocusedApplication", null)
>
> This merge seems to be right place to fix this, unless it causes lots of other
> changes too.
It's a change that needs to be made in qtubuntu. StageManager does call unfocus here, but qtubuntu doesn't update the value of mainStageFocusedApplication.

> When running the test files in qmlscene (for manual testing/debuggin) they
> segfault:
> # qmlscene ../tests/qmltests/Stages/tst_StageManager-tablet.qml -I
> ./tests/mocks/ -I tests/utils/modules/
> ASSERT failure in QList<T>::operator[]: "index out of range", file
> /usr/include/qt5/QtCore/qlist.h, line 462
> Aborted (core dumped)

I need to dig into this, not sure what's wrong.

> We try to create testcases that are able to run standalone for manual
> testing/debugging which I think is quite useful once a test fails.

To do this, I'd need to emulate tst_Stage.qml and add checkboxes to launch/quit applications. Shall I?

Revision history for this message
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

> One performs a right-edge swipe on the StageManager. The other grabs the side-
> stage handle and drags it to the right. They really only differ on their
> calculation of the starting x of the gesture, but I used different functions
> as there are only a small number of gestures relevant and I like verbose
> functions names.

Ok, fine with me.

> > 836 + skip("FIXME: application not unfocused when dismissed with left
> edge
> > swipe")
> > 837 + tryCompare(applicationManager, "mainStageFocusedApplication",
> null)
> >
> > This merge seems to be right place to fix this, unless it causes lots of
> other
> > changes too.
> It's a change that needs to be made in qtubuntu. StageManager does call
> unfocus here, but qtubuntu doesn't update the value of
> mainStageFocusedApplication.

Ack. Lets leave it. I'm wondering how we would keep track of this to not forget to enable it once the bug is fixed.

> > When running the test files in qmlscene (for manual testing/debuggin) they
> > segfault:
> > # qmlscene ../tests/qmltests/Stages/tst_StageManager-tablet.qml -I
> > ./tests/mocks/ -I tests/utils/modules/
> > ASSERT failure in QList<T>::operator[]: "index out of range", file
> > /usr/include/qt5/QtCore/qlist.h, line 462
> > Aborted (core dumped)
>
> I need to dig into this, not sure what's wrong.
>
> > We try to create testcases that are able to run standalone for manual
> > testing/debugging which I think is quite useful once a test fails.
>
> To do this, I'd need to emulate tst_Stage.qml and add checkboxes to
> launch/quit applications. Shall I?

Unless it would take you ages to do so I think its quite useful. For instance only today we used the tst_Revealer in a qmlscene to find out if its the revealer itself that causes the freeze we had today.

Don't overdo it. Checkboxes for basic features are enough so that most use cases can be reproduced manually. A checkbox for every single test function is not needed.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

Could you rephrase your commit message to the following format:

"""
Short, single line, summary

Detailed, multi-line, description.
"""

Many history/log views show only the first line of each commit message.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Gerry Boland (gerboland) wrote : Posted in a previous version of this proposal

All comments acted upon, unfortunately changing the diff more than I'd like. Please have a look again.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
8. By Gerry Boland

Fix RunningApplicationsGrid test which seems to fail because of this branch

Revision history for this message
Michael Zanetti (mzanetti) wrote :

- Close all apps
- run the phone-app
- open the notes-app
- edit some note, finish editing, so the OSK hides again
- now swipe from the right

-> expected result: phone-app shows up
-> actual result: only the notes-app shows up in the right-edge swipe

I cannot reproduce this with a freshly flashed phone without modifications.

Also, sometimes I could get it into a weird state without the OSK being involved, but I haven't found a way to reliably reproduce that.

review: Needs Fixing
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
9. By Gerry Boland

Restore broken property needed by greeter

10. By Gerry Boland

Merge trunk

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michał Sawicz (saviq) wrote :

Hey, https://bugs.launchpad.net/touch-preview-images/+bug/1178288/comments/4 and https://bugs.launchpad.net/touch-preview-images/+bug/1178288/comments/5

Does this branch contain a test that this works correctly on the shell side?

Newly born sessions should move to the front.

review: Needs Information
11. By Gerry Boland

Merge trunk

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
12. By Gerry Boland

No need to refer to shell here

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
13. By Gerry Boland

Gah, conflict I missed fixed

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michał Sawicz (saviq) wrote :

Failed tests:
    testStageManager-phone.xml.<init>
    testStageManager-tablet.xml.<init>

Output:
process 27977: D-Bus library appears to be incorrectly set up; failed to read machine uuid: Failed to open "/etc/machine-id": No such file or directory
See the manual page for dbus-uuidgen to correct this issue.
process 27977: D-Bus library appears to be incorrectly set up; failed to read machine uuid: Failed to open "/etc/machine-id": No such file or directory
See the manual page for dbus-uuidgen to correct this issue.

???

review: Needs Fixing
14. By Gerry Boland

Mock ApplicationManager possible fix for crash

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
15. By Gerry Boland

Revert last fix, does not work

16. By Gerry Boland

Experiment 2 to fix crash

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
17. By Gerry Boland

Whitespace

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michał Sawicz (saviq) wrote :

Hey, there's a conflict in Shell.qml

review: Needs Fixing

Unmerged revisions

17. By Gerry Boland

Whitespace

16. By Gerry Boland

Experiment 2 to fix crash

15. By Gerry Boland

Revert last fix, does not work

14. By Gerry Boland

Mock ApplicationManager possible fix for crash

13. By Gerry Boland

Gah, conflict I missed fixed

12. By Gerry Boland

No need to refer to shell here

11. By Gerry Boland

Merge trunk

10. By Gerry Boland

Merge trunk

9. By Gerry Boland

Restore broken property needed by greeter

8. By Gerry Boland

Fix RunningApplicationsGrid test which seems to fail because of this branch

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2013-06-05 22:03:08 +0000
3+++ CMakeLists.txt 2013-06-13 14:42:44 +0000
4@@ -144,7 +144,7 @@
5 Hud
6 Panel
7 Launcher
8- SideStage
9+ Stages
10 Notifications
11 )
12
13
14=== modified file 'Shell.qml'
15--- Shell.qml 2013-06-12 08:48:37 +0000
16+++ Shell.qml 2013-06-13 14:42:44 +0000
17@@ -26,7 +26,7 @@
18 import "Components"
19 import "Components/Math.js" as MathLocal
20 import "Bottombar"
21-import "SideStage"
22+import "Stages"
23
24 FocusScope {
25 id: shell
26@@ -42,71 +42,14 @@
27 readonly property real panelHeight: panel.panelHeight
28
29 property bool dashShown: dash.shown
30- property bool stageScreenshotsReady: {
31- if (sideStage.shown) {
32- if (mainStage.applications.count > 0) {
33- return mainStage.usingScreenshots && sideStage.usingScreenshots;
34- } else {
35- return sideStage.usingScreenshots;
36- }
37- } else {
38- return mainStage.usingScreenshots;
39- }
40- }
41+ readonly property bool stageScreenshotsReady: stageManager.stageScreenshotsReady
42
43 property ListModel searchHistory: SearchHistoryModel {}
44
45 property var applicationManager: ApplicationManagerWrapper {}
46
47- Component.onCompleted: {
48- applicationManager.sideStageEnabled = Qt.binding(function() { return sideStage.enabled })
49-
50- // FIXME: if application focused before shell starts, shell draws on top of it only.
51- // We should detect already running applications on shell start and bring them to the front.
52- applicationManager.unfocusCurrentApplication();
53- }
54-
55- readonly property bool fullscreenMode: {
56- if (greeter.shown || lockscreen.shown) {
57- return false;
58- } else if (mainStage.usingScreenshots) { // Window Manager animating so want to re-evaluate fullscreen mode
59- return mainStage.switchingFromFullscreenToFullscreen;
60- } else if (applicationManager.mainStageFocusedApplication) {
61- return applicationManager.mainStageFocusedApplication.fullscreen;
62- } else {
63- return false;
64- }
65- }
66-
67- Connections {
68- target: applicationManager
69- ignoreUnknownSignals: true
70- onFocusRequested: {
71- // TODO: this should be protected to only unlock for certain applications / certain usecases
72- // potentially only in connection with a notification
73- greeter.hide();
74- shell.activateApplication(desktopFile);
75- }
76- }
77-
78 function activateApplication(desktopFile, argument) {
79- if (applicationManager) {
80- // For newly started applications, as it takes them time to draw their first frame
81- // we add a delay before we hide the animation screenshots to compensate.
82- var addDelay = !applicationManager.getApplicationFromDesktopFile(desktopFile);
83-
84- var application;
85- application = applicationManager.activateApplication(desktopFile, argument);
86- if (application == null) {
87- return;
88- }
89- if (application.stage == ApplicationInfo.MainStage || !sideStage.enabled) {
90- mainStage.activateApplication(desktopFile, addDelay);
91- } else {
92- sideStage.activateApplication(desktopFile, addDelay);
93- }
94- stages.show();
95- }
96+ stageManager.activateApplication(desktopFile, argument);
97 }
98
99 VolumeControl {
100@@ -126,9 +69,7 @@
101 id: underlay
102 anchors.fill: parent
103 visible: !(panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth)
104- && (stages.fullyHidden
105- || (stages.fullyShown && mainStage.usingScreenshots)
106- || !stages.fullyShown && (mainStage.usingScreenshots || (sideStage.shown && sideStage.usingScreenshots)))
107+ && stageManager.needUnderlay
108
109 Image {
110 id: backgroundImage
111@@ -148,7 +89,7 @@
112 id: dash
113
114 available: !greeter.shown && !lockscreen.shown
115- hides: [stages, launcher, panel.indicators]
116+ hides: [stageManager, launcher, panel.indicators]
117 shown: disappearingAnimationProgress !== 1.0
118 enabled: disappearingAnimationProgress === 0.0
119 // FIXME: unfocus all applications when going back to the dash
120@@ -165,182 +106,24 @@
121
122 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
123 opacity: 1.0 - disappearingAnimationProgress
124- property real disappearingAnimationProgress: ((greeter.shown) ? greeterRevealer.animatedProgress : stagesRevealer.animatedProgress)
125- // FIXME: only necessary because stagesRevealer.animatedProgress and
126+ property real disappearingAnimationProgress: ((greeter.shown) ? greeterRevealer.animatedProgress : stageManager.animatedProgress)
127+ // FIXME: only necessary because stageManager.animatedProgress and
128 // greeterRevealer.animatedProgress are not animated
129 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
130 }
131 }
132
133-
134- Item {
135-
136- width: parent.width
137- height: parent.height
138- x: launcher.progress
139- Behavior on x {SmoothedAnimation{velocity: 600}}
140-
141-
142- Showable {
143- id: stages
144-
145- property bool fullyShown: shown && stages[stagesRevealer.boundProperty] == stagesRevealer.openedValue
146- && parent.x == 0
147- property bool fullyHidden: !shown && stages[stagesRevealer.boundProperty] == stagesRevealer.closedValue
148- available: !greeter.shown
149- hides: [launcher, panel.indicators]
150- shown: false
151- opacity: 1.0
152- showAnimation: StandardAnimation { property: "x"; duration: 350; to: stagesRevealer.openedValue; easing.type: Easing.OutCubic }
153- hideAnimation: StandardAnimation { property: "x"; duration: 350; to: stagesRevealer.closedValue; easing.type: Easing.OutCubic }
154-
155- width: parent.width
156- height: parent.height
157-
158- // close the stages when no focused application remains
159- Connections {
160- target: shell.applicationManager
161- onMainStageFocusedApplicationChanged: stages.closeIfNoApplications()
162- onSideStageFocusedApplicationChanged: stages.closeIfNoApplications()
163- ignoreUnknownSignals: true
164- }
165-
166- function closeIfNoApplications() {
167- if (!shell.applicationManager.mainStageFocusedApplication
168- && !shell.applicationManager.sideStageFocusedApplication
169- && shell.applicationManager.mainStageApplications.count == 0
170- && shell.applicationManager.sideStageApplications.count == 0) {
171- stages.hide();
172- }
173- }
174-
175- // show the stages when an application gets the focus
176- Connections {
177- target: shell.applicationManager
178- onMainStageFocusedApplicationChanged: {
179- if (shell.applicationManager.mainStageFocusedApplication) {
180- mainStage.show();
181- stages.show();
182- }
183- }
184- onSideStageFocusedApplicationChanged: {
185- if (shell.applicationManager.sideStageFocusedApplication) {
186- sideStage.show();
187- stages.show();
188- }
189- }
190- ignoreUnknownSignals: true
191- }
192-
193-
194- Stage {
195- id: mainStage
196-
197- anchors.fill: parent
198- fullyShown: stages.fullyShown
199- shouldUseScreenshots: !fullyShown
200- rightEdgeEnabled: !sideStage.enabled
201-
202- applicationManager: shell.applicationManager
203- rightEdgeDraggingAreaWidth: shell.edgeSize
204- normalApplicationY: shell.panelHeight
205-
206- shown: true
207- function show() {
208- stages.show();
209- }
210- function showWithoutAnimation() {
211- stages.showWithoutAnimation();
212- }
213- function hide() {
214- }
215-
216- // FIXME: workaround the fact that focusing a main stage application
217- // raises its surface on top of all other surfaces including the ones
218- // that belong to side stage applications.
219- onFocusedApplicationChanged: {
220- if (focusedApplication && sideStage.focusedApplication && sideStage.fullyShown) {
221- shell.applicationManager.focusApplication(sideStage.focusedApplication);
222- }
223- }
224- }
225-
226- SideStage {
227- id: sideStage
228-
229- applicationManager: shell.applicationManager
230- rightEdgeDraggingAreaWidth: shell.edgeSize
231- normalApplicationY: shell.panelHeight
232-
233- onShownChanged: {
234- if (!shown && mainStage.applications.count == 0) {
235- stages.hide();
236- }
237- }
238- // FIXME: when hiding the side stage, refocus the main stage
239- // application so that it goes in front of the side stage
240- // application and hides it
241- onFullyShownChanged: {
242- if (!fullyShown && stages.fullyShown && sideStage.focusedApplication != null) {
243- shell.applicationManager.focusApplication(mainStage.focusedApplication);
244- }
245- }
246-
247- enabled: shell.width >= units.gu(60)
248- visible: enabled
249- fullyShown: stages.fullyShown && shown
250- && sideStage[sideStageRevealer.boundProperty] == sideStageRevealer.openedValue
251- shouldUseScreenshots: !fullyShown || mainStage.usingScreenshots || sideStageRevealer.pressed
252-
253- available: !greeter.shown && !lockscreen.shown && enabled
254- hides: [launcher, panel.indicators]
255- shown: false
256- showAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.openedValue; easing.type: Easing.OutQuint }
257- hideAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.closedValue; easing.type: Easing.OutQuint }
258-
259- width: units.gu(40)
260- height: stages.height
261- handleExpanded: sideStageRevealer.pressed
262- }
263-
264- Revealer {
265- id: sideStageRevealer
266-
267- enabled: mainStage.applications.count > 0 && sideStage.applications.count > 0
268- && sideStage.available
269- direction: Qt.RightToLeft
270- openedValue: parent.width - sideStage.width
271- hintDisplacement: units.gu(3)
272- /* The size of the sidestage handle needs to be bigger than the
273- typical size used for edge detection otherwise it is really
274- hard to grab.
275- */
276- handleSize: sideStage.shown ? units.gu(4) : shell.edgeSize
277- closedValue: parent.width + sideStage.handleSizeCollapsed
278- target: sideStage
279- x: parent.width - width
280- width: sideStage.width + handleSize * 0.7
281- height: sideStage.height
282- orientation: Qt.Horizontal
283- }
284- }
285- }
286-
287-
288- Revealer {
289- id: stagesRevealer
290-
291- property real animatedProgress: MathLocal.clamp((-dragPosition - launcher.progress) / closedValue, 0, 1)
292- enabled: mainStage.applications.count > 0 || sideStage.applications.count > 0
293- direction: Qt.RightToLeft
294- openedValue: 0
295- hintDisplacement: units.gu(3)
296- handleSize: shell.edgeSize
297- closedValue: width
298- target: stages
299- width: stages.width
300- height: stages.height
301- orientation: Qt.Horizontal
302+ StageManager {
303+ id: stageManager
304+
305+ anchors.fill: parent
306+
307+ enabled: !greeter.shown
308+ hides: [launcher, panel.indicators]
309+ applicationManager: shell.applicationManager
310+ leftSwipePosition: launcher.progress
311+ panelHeight: panel.panelHeight
312+ edgeHandleSize: shell.edgeSize
313 }
314
315 Lockscreen {
316@@ -409,8 +192,8 @@
317 greeter.forceActiveFocus();
318 // FIXME: *FocusedApplication are not updated when unfocused, hence the need to check whether
319 // the stage was actually shown
320- if (mainStage.fullyShown) greeter.previousMainApp = applicationManager.mainStageFocusedApplication;
321- if (sideStage.fullyShown) greeter.previousSideApp = applicationManager.sideStageFocusedApplication;
322+ if (stageManager.mainStageFullyShown) greeter.previousMainApp = applicationManager.mainStageFocusedApplication;
323+ if (stageManager.sideStageFullyShown) greeter.previousSideApp = applicationManager.sideStageFocusedApplication;
324 applicationManager.unfocusCurrentApplication();
325 } else {
326 if (greeter.previousMainApp) {
327@@ -461,7 +244,7 @@
328 indicators {
329 hides: [launcher]
330 }
331- fullscreenMode: shell.fullscreenMode
332+ fullscreenMode: stageManager.fullscreenMode
333 searchVisible: !greeter.shown && !lockscreen.shown
334
335 InputFilterArea {
336@@ -537,16 +320,16 @@
337 onDashItemSelected: {
338 greeter.hide()
339 // Animate if moving between application and dash
340- if (!stages.shown) {
341+ if (!stageManager.shown) {
342 dash.setCurrentLens("home.lens", true, false)
343 } else {
344 dash.setCurrentLens("home.lens", false, false)
345 }
346- stages.hide();
347+ stageManager.hide();
348 }
349 onDash: {
350 dash.setCurrentLens("applications.lens", true, false)
351- stages.hide();
352+ stageManager.hide();
353 }
354 onLauncherApplicationSelected:{
355 greeter.hide()
356
357=== removed directory 'SideStage'
358=== added directory 'Stages'
359=== renamed file 'SideStage/SideStage.qml' => 'Stages/SideStage.qml'
360--- SideStage/SideStage.qml 2013-06-05 22:03:08 +0000
361+++ Stages/SideStage.qml 2013-06-13 14:42:44 +0000
362@@ -38,7 +38,7 @@
363 left: parent.left
364 right: parent.right
365 }
366- height: shell.panelHeight
367+ height: normalApplicationY
368 color: background.color
369 z: -1
370 }
371
372=== renamed file 'SideStage/SidestageHandle.qml' => 'Stages/SidestageHandle.qml'
373=== renamed file 'Components/Stage.qml' => 'Stages/Stage.qml'
374--- Components/Stage.qml 2013-06-07 18:01:50 +0000
375+++ Stages/Stage.qml 2013-06-13 14:42:44 +0000
376@@ -18,6 +18,7 @@
377 import Ubuntu.Application 0.1
378 import Ubuntu.Components 0.1
379 import Ubuntu.Gestures 0.1
380+import "../Components"
381
382 /*
383 Responsible for application switching.
384@@ -179,7 +180,11 @@
385 delayedHideScreenshots.stop();
386 applicationManager.focusApplication(application);
387 }
388- stage.focusedApplicationWhenUsingScreenshots = null;
389+
390+ // Don't over-write this if another animation has begun, as another app will get focus then
391+ if (!showStartingApplicationAnimation.running && !switchToApplicationAnimation.running) {
392+ stage.focusedApplicationWhenUsingScreenshots = null;
393+ }
394 }
395
396 function __focusApplicationUsingScreenshots(application) {
397
398=== added file 'Stages/StageManager.qml'
399--- Stages/StageManager.qml 1970-01-01 00:00:00 +0000
400+++ Stages/StageManager.qml 2013-06-13 14:42:44 +0000
401@@ -0,0 +1,287 @@
402+/*
403+ * Copyright (C) 2013 Canonical, Ltd.
404+ *
405+ * This program is free software; you can redistribute it and/or modify
406+ * it under the terms of the GNU General Public License as published by
407+ * the Free Software Foundation; version 3.
408+ *
409+ * This program is distributed in the hope that it will be useful,
410+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
411+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
412+ * GNU General Public License for more details.
413+ *
414+ * You should have received a copy of the GNU General Public License
415+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
416+ */
417+
418+import QtQuick 2.0
419+import Ubuntu.Application 0.1
420+import "../Components"
421+import "../Components/Math.js" as MathLocal
422+
423+Item {
424+ id: stageManager
425+
426+ property var applicationManager
427+ property real leftSwipePosition: 0
428+ property real panelHeight: 0
429+ property real edgeHandleSize
430+ property bool enabled
431+ property var hides: []
432+ readonly property bool shown: stages.shown
433+ readonly property real animatedProgress: MathLocal.clamp((-stagesRevealer.dragPosition - leftSwipePosition)
434+ / stagesRevealer.closedValue, 0, 1)
435+
436+ readonly property bool stageScreenshotsReady: {
437+ if (sideStage.shown) {
438+ if (mainStage.applications.count > 0) {
439+ return mainStage.usingScreenshots || sideStage.usingScreenshots;
440+ } else {
441+ return sideStage.usingScreenshots;
442+ }
443+ } else {
444+ return mainStage.usingScreenshots;
445+ }
446+ }
447+
448+ readonly property bool needUnderlay: (stages.fullyHidden
449+ || (stages.fullyShown && mainStage.usingScreenshots)
450+ || !stages.fullyShown && (mainStage.usingScreenshots || (sideStage.shown && sideStage.usingScreenshots)))
451+
452+ readonly property bool fullscreenMode: {
453+ if (!enabled) {
454+ return false;
455+ } else if (mainStage.usingScreenshots) { // Window Manager animating so want to re-evaluate fullscreen mode
456+ return mainStage.switchingFromFullscreenToFullscreen;
457+ } else if (applicationManager.mainStageFocusedApplication) {
458+ return applicationManager.mainStageFocusedApplication.fullscreen;
459+ } else {
460+ return false;
461+ }
462+ }
463+
464+ readonly property alias mainStageFullyShown: mainStage.fullyShown
465+ readonly property alias sideStageFullyShown: sideStage.fullyShown
466+
467+ function hide() {
468+ stages.hide()
469+ }
470+
471+ Component.onCompleted: {
472+ applicationManager.sideStageEnabled = Qt.binding(function() { return sideStage.enabled })
473+
474+ // FIXME: if application focused before shell starts, shell draws on top of it only.
475+ // We should detect already running applications on shell start and bring them to the front.
476+ applicationManager.unfocusCurrentApplication();
477+ }
478+
479+ Connections {
480+ target: applicationManager
481+ ignoreUnknownSignals: true
482+ onFocusRequested: {
483+ // TODO: this should be protected to only unlock for certain applications / certain usecases
484+ // potentially only in connection with a notification
485+ shell.greeter.hide();
486+ activateApplication(desktopFile);
487+ }
488+ }
489+
490+ function activateApplication(desktopFile, argument) {
491+ if (applicationManager) {
492+ // For newly started applications, as it takes them time to draw their first frame
493+ // we add a delay before we hide the animation screenshots to compensate.
494+ var addDelay = !applicationManager.getApplicationFromDesktopFile(desktopFile);
495+
496+ var application;
497+ application = applicationManager.activateApplication(desktopFile, argument);
498+ if (application == null) {
499+ return;
500+ }
501+ if (application.stage == ApplicationInfo.MainStage || !sideStage.enabled) {
502+ mainStage.activateApplication(desktopFile, addDelay);
503+ } else {
504+ sideStage.activateApplication(desktopFile, addDelay);
505+ }
506+ stages.show();
507+ }
508+ }
509+ Item {
510+ id: windowsContainer
511+
512+ x: leftSwipePosition
513+ Behavior on x {SmoothedAnimation{velocity: 600}}
514+
515+ width: parent.width
516+ anchors {
517+ top: parent.top
518+ bottom: parent.bottom
519+ }
520+
521+ Showable {
522+ id: stages
523+
524+ property bool fullyShown: shown && stages[stagesRevealer.boundProperty] == stagesRevealer.openedValue
525+ && parent.x == 0
526+ property bool fullyHidden: !shown && stages[stagesRevealer.boundProperty] == stagesRevealer.closedValue
527+ available: stageManager.enabled
528+ hides: stageManager.hides
529+ shown: false
530+ opacity: 1.0
531+ showAnimation: StandardAnimation { property: "x"; duration: 350; to: stagesRevealer.openedValue; easing.type: Easing.OutCubic }
532+ hideAnimation: StandardAnimation { property: "x"; duration: 350; to: stagesRevealer.closedValue; easing.type: Easing.OutCubic }
533+
534+ width: parent.width
535+ height: parent.height
536+
537+ // close the stages when no focused application remains
538+ Connections {
539+ target: applicationManager
540+ onMainStageFocusedApplicationChanged: stages.closeIfNoApplications()
541+ onSideStageFocusedApplicationChanged: stages.closeIfNoApplications()
542+ ignoreUnknownSignals: true
543+ }
544+ Connections {
545+ target: applicationManager.mainStageApplications
546+ onCountChanged: stages.closeIfNoApplications();
547+ }
548+ Connections {
549+ target: applicationManager.sideStageApplications
550+ onCountChanged: stages.closeIfNoApplications();
551+ }
552+
553+ function closeIfNoApplications() {
554+ if (!applicationManager.mainStageFocusedApplication
555+ && !applicationManager.sideStageFocusedApplication
556+ && applicationManager.mainStageApplications.count == 0
557+ && applicationManager.sideStageApplications.count == 0) {
558+ stages.hide();
559+ }
560+ }
561+
562+ // show the stages when an application gets the focus
563+ Connections {
564+ target: applicationManager
565+ onMainStageFocusedApplicationChanged: {
566+ if (applicationManager.mainStageFocusedApplication) {
567+ mainStage.show();
568+ stages.show();
569+ }
570+ }
571+ onSideStageFocusedApplicationChanged: {
572+ if (applicationManager.sideStageFocusedApplication) {
573+ sideStage.show();
574+ stages.show();
575+ } }
576+ ignoreUnknownSignals: true
577+ }
578+
579+ Stage {
580+ id: mainStage
581+ objectName: "mainStage"
582+
583+ anchors.fill: parent
584+ fullyShown: stages.fullyShown
585+ shouldUseScreenshots: !fullyShown
586+ rightEdgeEnabled: !sideStage.enabled
587+
588+ applicationManager: stageManager.applicationManager
589+ rightEdgeDraggingAreaWidth: edgeHandleSize
590+ normalApplicationY: panelHeight
591+
592+ shown: true
593+ function show() {
594+ stages.show();
595+ }
596+ function showWithoutAnimation() {
597+ stages.showWithoutAnimation();
598+ }
599+ function hide() {
600+ }
601+
602+ // FIXME: workaround the fact that focusing a main stage application
603+ // raises its surface on top of all other surfaces including the ones
604+ // that belong to side stage applications.
605+ onFocusedApplicationChanged: {
606+ if (focusedApplication && sideStage.focusedApplication && sideStage.fullyShown) {
607+ applicationManager.focusApplication(sideStage.focusedApplication);
608+ }
609+ }
610+ }
611+
612+ SideStage {
613+ id: sideStage
614+ objectName: "sideStage"
615+
616+ applicationManager: stageManager.applicationManager
617+ rightEdgeDraggingAreaWidth: edgeHandleSize
618+ normalApplicationY: panelHeight
619+
620+ onShownChanged: {
621+ if (!shown && mainStage.applications.count == 0) {
622+ stages.hide();
623+ }
624+ }
625+ // FIXME: when hiding the side stage, refocus the main stage
626+ // application so that it goes in front of the side stage
627+ // application and hides it
628+ onFullyShownChanged: {
629+ if (!fullyShown && stages.fullyShown && sideStage.focusedApplication != null) {
630+ applicationManager.focusApplication(mainStage.focusedApplication);
631+ }
632+ }
633+
634+ enabled: stageManager.width >= units.gu(60)
635+ visible: enabled
636+ fullyShown: stages.fullyShown && shown
637+ && sideStage[sideStageRevealer.boundProperty] == sideStageRevealer.openedValue
638+ shouldUseScreenshots: !fullyShown || mainStage.usingScreenshots || sideStageRevealer.pressed
639+
640+ available: stageManager.enabled && enabled
641+ hides: stageManager.hides
642+ shown: false
643+ showAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.openedValue; easing.type: Easing.OutQuint }
644+ hideAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.closedValue; easing.type: Easing.OutQuint }
645+
646+ width: units.gu(40)
647+ height: stages.height
648+ handleExpanded: sideStageRevealer.pressed
649+ }
650+
651+ Revealer {
652+ id: sideStageRevealer
653+
654+ enabled: mainStage.applications.count > 0 && sideStage.applications.count > 0
655+ && sideStage.available
656+ direction: Qt.RightToLeft
657+ openedValue: parent.width - sideStage.width
658+ hintDisplacement: units.gu(3)
659+ /* The size of the sidestage handle needs to be bigger than the
660+ typical size used for edge detection otherwise it is really
661+ hard to grab.
662+ */
663+ handleSize: sideStage.shown ? units.gu(4) : edgeHandleSize
664+ closedValue: parent.width + sideStage.handleSizeCollapsed
665+ target: sideStage
666+ x: parent.width - width
667+ width: sideStage.width + handleSize * 0.7
668+ height: sideStage.height
669+ orientation: Qt.Horizontal
670+ }
671+ }
672+ }
673+
674+ Revealer {
675+ id: stagesRevealer
676+
677+ enabled: mainStage.applications.count > 0 || sideStage.applications.count > 0
678+ direction: Qt.RightToLeft
679+ openedValue: 0
680+ hintDisplacement: units.gu(3)
681+ handleSize: edgeHandleSize
682+ closedValue: width
683+ target: stages
684+ width: stages.width
685+ height: stages.height
686+ orientation: Qt.Horizontal
687+ }
688+}
689
690=== renamed directory 'SideStage/graphics' => 'Stages/graphics'
691=== modified file 'debian/unity8.install'
692--- debian/unity8.install 2013-06-05 22:03:08 +0000
693+++ debian/unity8.install 2013-06-13 14:42:44 +0000
694@@ -11,7 +11,7 @@
695 /usr/share/unity8/Notifications/*
696 /usr/share/unity8/Panel/*
697 /usr/share/unity8/Shell.qml
698-/usr/share/unity8/SideStage/*
699+/usr/share/unity8/Stages/*
700 /usr/share/unity8/graphics/*
701 /usr/share/unity8/plugins/HudClient/*
702 /usr/share/unity8/plugins/LightDM/*
703
704=== modified file 'tests/mocks/Ubuntu/Application/ApplicationListModel.cpp'
705--- tests/mocks/Ubuntu/Application/ApplicationListModel.cpp 2013-06-05 22:03:08 +0000
706+++ tests/mocks/Ubuntu/Application/ApplicationListModel.cpp 2013-06-13 14:42:44 +0000
707@@ -64,8 +64,8 @@
708
709 void ApplicationListModel::add(ApplicationInfo* application)
710 {
711- beginInsertRows(QModelIndex(), m_applications.size(), m_applications.size());
712- m_applications.append(application);
713+ beginInsertRows(QModelIndex(), 0, 0);
714+ m_applications.prepend(application);
715 endInsertRows();
716 Q_EMIT countChanged();
717 }
718
719=== modified file 'tests/mocks/Ubuntu/Application/ApplicationManager.cpp'
720--- tests/mocks/Ubuntu/Application/ApplicationManager.cpp 2013-06-05 22:03:08 +0000
721+++ tests/mocks/Ubuntu/Application/ApplicationManager.cpp 2013-06-13 14:42:44 +0000
722@@ -24,6 +24,21 @@
723 #include <QQuickItem>
724 #include <QQuickView>
725 #include <QQmlComponent>
726+#include <QWaitCondition>
727+#include <QMutex>
728+
729+struct Sleeper {
730+ QMutex mutex;
731+ QWaitCondition sleeper;
732+
733+ Sleeper() { mutex.lock(); }
734+ ~Sleeper() { mutex.unlock(); }
735+
736+ void sleep(unsigned long duration)
737+ {
738+ sleeper.wait(&mutex, duration);
739+ }
740+};
741
742 ApplicationManager::ApplicationManager(QObject *parent)
743 : QObject(parent)
744@@ -35,10 +50,11 @@
745 , m_mainStage(0)
746 , m_sideStageComponent(0)
747 , m_sideStage(0)
748+ , m_quickView(0)
749 {
750 buildListOfAvailableApplications();
751- createMainStageComponent();
752- createSideStageComponent();
753+
754+ QGuiApplication::instance()->installEventFilter(this);
755 }
756
757 ApplicationManager::~ApplicationManager()
758@@ -47,6 +63,19 @@
759 delete m_sideStageApplications;
760 }
761
762+bool ApplicationManager::eventFilter(QObject *object, QEvent *event)
763+{
764+ // best to wait for this event before locating the view
765+ if (!m_quickView && (event->type() == QEvent::ApplicationActivate)) {
766+ m_quickView = qobject_cast<QQuickView*>(QGuiApplication::topLevelWindows()[0]);
767+
768+ createMainStageComponent();
769+ createSideStageComponent();
770+ }
771+
772+ return QObject::eventFilter(object, event);
773+}
774+
775 int ApplicationManager::keyboardHeight() const
776 {
777 return 0;
778@@ -92,6 +121,22 @@
779 return m_sideStageFocusedApplication;
780 }
781
782+void ApplicationManager::setMainStageFocusedApplication(ApplicationInfo *app)
783+{
784+ if (app != m_mainStageFocusedApplication) {
785+ m_mainStageFocusedApplication = app;
786+ Q_EMIT mainStageFocusedApplicationChanged();
787+ }
788+}
789+
790+void ApplicationManager::setSideStageFocusedApplication(ApplicationInfo *app)
791+{
792+ if (app != m_sideStageFocusedApplication) {
793+ m_sideStageFocusedApplication = app;
794+ Q_EMIT sideStageFocusedApplicationChanged();
795+ }
796+}
797+
798 ApplicationInfo* ApplicationManager::startProcess(QString desktopFile,
799 ExecFlags flags,
800 QStringList arguments)
801@@ -112,9 +157,13 @@
802
803 if (flags.testFlag(ApplicationManager::ForceMainStage)
804 || application->stage() == ApplicationInfo::MainStage) {
805- m_mainStageApplications->add(application);
806+ if (!m_mainStageApplications->contains(application)) {
807+ m_mainStageApplications->add(application);
808+ }
809 } else {
810- m_sideStageApplications->add(application);
811+ if (!m_sideStageApplications->contains(application)) {
812+ m_sideStageApplications->add(application);
813+ }
814 }
815
816 return application;
817@@ -123,18 +172,18 @@
818 void ApplicationManager::stopProcess(ApplicationInfo* application)
819 {
820 if (m_mainStageApplications->contains(application)) {
821+ application->hideWindow();
822 m_mainStageApplications->remove(application);
823
824 if (m_mainStageFocusedApplication == application) {
825- m_mainStageFocusedApplication = 0;
826- Q_EMIT mainStageFocusedApplicationChanged();
827+ setMainStageFocusedApplication(0);
828 }
829 } else if (m_sideStageApplications->contains(application)){
830+ application->hideWindow();
831 m_sideStageApplications->remove(application);
832
833 if (m_sideStageFocusedApplication == application) {
834- m_sideStageFocusedApplication = 0;
835- Q_EMIT sideStageFocusedApplicationChanged();
836+ setSideStageFocusedApplication(0);
837 }
838 }
839 }
840@@ -146,14 +195,13 @@
841 if (application->handle() == handle) {
842 if (m_mainStageFocusedApplication)
843 m_mainStageFocusedApplication->hideWindow();
844- m_mainStageFocusedApplication = application;
845 if (!m_mainStage)
846 createMainStage();
847 application->showWindow(m_mainStage);
848 m_mainStage->setZ(-1000);
849 if (m_sideStage)
850 m_sideStage->setZ(-2000);
851- Q_EMIT mainStageFocusedApplicationChanged();
852+ setMainStageFocusedApplication(application);
853 return;
854 }
855 }
856@@ -163,14 +211,13 @@
857 if (application->handle() == handle) {
858 if (m_sideStageFocusedApplication)
859 m_sideStageFocusedApplication->hideWindow();
860- m_sideStageFocusedApplication = application;
861 if (!m_sideStage)
862 createSideStage();
863 application->showWindow(m_sideStage);
864 m_sideStage->setZ(-1000);
865 if (m_mainStage)
866 m_mainStage->setZ(-2000);
867- Q_EMIT sideStageFocusedApplicationChanged();
868+ setSideStageFocusedApplication(application);
869 return;
870 }
871 }
872@@ -181,14 +228,12 @@
873 if (stageHint == SideStage) {
874 if (m_sideStageFocusedApplication) {
875 m_sideStageFocusedApplication->hideWindow();
876- m_sideStageFocusedApplication = 0;
877- Q_EMIT sideStageFocusedApplicationChanged();
878+ setSideStageFocusedApplication(0);
879 }
880 } else {
881 if (m_mainStageFocusedApplication) {
882 m_mainStageFocusedApplication->hideWindow();
883- m_mainStageFocusedApplication = 0;
884- Q_EMIT mainStageFocusedApplicationChanged();
885+ setMainStageFocusedApplication(0);
886 }
887 }
888 }
889@@ -384,11 +429,7 @@
890
891 void ApplicationManager::createMainStageComponent()
892 {
893- // The assumptions I make here really should hold.
894- QQuickView *quickView =
895- qobject_cast<QQuickView*>(QGuiApplication::topLevelWindows()[0]);
896-
897- QQmlEngine *engine = quickView->engine();
898+ QQmlEngine *engine = m_quickView->engine();
899
900 m_mainStageComponent = new QQmlComponent(engine, this);
901 QString mainStageQml =
902@@ -403,11 +444,12 @@
903
904 void ApplicationManager::createMainStage()
905 {
906- // The assumptions I make here really should hold.
907- QQuickView *quickView =
908- qobject_cast<QQuickView*>(QGuiApplication::topLevelWindows()[0]);
909+ Sleeper sleeper;
910+ while (m_quickView->status() != QQuickView::Ready) {
911+ sleeper.sleep(500);
912+ }
913
914- QQuickItem *shell = quickView->rootObject();
915+ QQuickItem *shell = m_quickView->rootObject();
916
917 m_mainStage = qobject_cast<QQuickItem *>(m_mainStageComponent->create());
918 m_mainStage->setParentItem(shell);
919@@ -415,11 +457,7 @@
920
921 void ApplicationManager::createSideStageComponent()
922 {
923- // The assumptions I make here really should hold.
924- QQuickView *quickView =
925- qobject_cast<QQuickView*>(QGuiApplication::topLevelWindows()[0]);
926-
927- QQmlEngine *engine = quickView->engine();
928+ QQmlEngine *engine = m_quickView->engine();
929
930 m_sideStageComponent = new QQmlComponent(engine, this);
931 QString sideStageQml =
932@@ -437,11 +475,12 @@
933
934 void ApplicationManager::createSideStage()
935 {
936- // The assumptions I make here really should hold.
937- QQuickView *quickView =
938- qobject_cast<QQuickView*>(QGuiApplication::topLevelWindows()[0]);
939+ Sleeper sleeper;
940+ while (m_quickView->status() != QQuickView::Ready) {
941+ sleeper.sleep(500);
942+ }
943
944- QQuickItem *shell = quickView->rootObject();
945+ QQuickItem *shell = m_quickView->rootObject();
946
947 m_sideStage = qobject_cast<QQuickItem *>(m_sideStageComponent->create());
948 m_sideStage->setParentItem(shell);
949
950=== modified file 'tests/mocks/Ubuntu/Application/ApplicationManager.h'
951--- tests/mocks/Ubuntu/Application/ApplicationManager.h 2013-06-05 22:03:08 +0000
952+++ tests/mocks/Ubuntu/Application/ApplicationManager.h 2013-06-13 14:42:44 +0000
953@@ -24,6 +24,8 @@
954 #include "ApplicationInfo.h"
955
956 class QQuickItem;
957+class QQuickView;
958+class QEvent;
959
960 class ApplicationManager : public QObject {
961 Q_OBJECT
962@@ -95,6 +97,12 @@
963 void sideStageFocusedApplicationChanged();
964 void focusRequested(FavoriteApplication favoriteApplication);
965
966+protected:
967+ bool eventFilter(QObject *, QEvent *);
968+
969+ void setMainStageFocusedApplication(ApplicationInfo *);
970+ void setSideStageFocusedApplication(ApplicationInfo *);
971+
972 private:
973 void showApplicationWindow(ApplicationInfo *application);
974 void buildListOfAvailableApplications();
975@@ -114,6 +122,7 @@
976 QQuickItem *m_mainStage;
977 QQmlComponent *m_sideStageComponent;
978 QQuickItem *m_sideStage;
979+ QQuickView *m_quickView;
980 };
981
982 Q_DECLARE_OPERATORS_FOR_FLAGS(ApplicationManager::ExecFlags)
983
984=== modified file 'tests/qmltests/CMakeLists.txt'
985--- tests/qmltests/CMakeLists.txt 2013-06-12 16:10:47 +0000
986+++ tests/qmltests/CMakeLists.txt 2013-06-13 14:42:44 +0000
987@@ -32,9 +32,6 @@
988 add_qml_test(Components ResponsiveGridView)
989 add_qml_test(Components Revealer)
990 add_qml_test(Components Showable)
991-add_qml_test(Components Stage IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS}
992- ${CMAKE_BINARY_DIR}/tests/mocks
993- ${CMAKE_BINARY_DIR}/plugins)
994 add_qml_test(Components Tile)
995 add_qml_test(Components PageHeader)
996 add_qml_test(Dash Dash IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})
997@@ -67,6 +64,7 @@
998 add_qml_test(Panel Panel)
999 add_qml_test(Panel SearchIndicator)
1000 add_qml_test(Panel/Menus IndicatorMenuWindow IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS})
1001-add_qml_test(SideStage SideStage IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS}
1002- ${CMAKE_BINARY_DIR}/tests/mocks
1003- ${CMAKE_BINARY_DIR}/plugins)
1004+add_qml_test(Stages Stage IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/tests/mocks ${CMAKE_BINARY_DIR}/plugins)
1005+add_qml_test(Stages StageManager-phone IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/tests/mocks ${CMAKE_BINARY_DIR}/plugins)
1006+add_qml_test(Stages StageManager-tablet IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/tests/mocks ${CMAKE_BINARY_DIR}/plugins)
1007+add_qml_test(Stages SideStage IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/tests/mocks ${CMAKE_BINARY_DIR}/plugins)
1008
1009=== modified file 'tests/qmltests/Dash/Apps/tst_RunningApplicationsGrid.qml'
1010--- tests/qmltests/Dash/Apps/tst_RunningApplicationsGrid.qml 2013-06-05 22:03:08 +0000
1011+++ tests/qmltests/Dash/Apps/tst_RunningApplicationsGrid.qml 2013-06-13 14:42:44 +0000
1012@@ -128,6 +128,7 @@
1013 compare(runningApplicationsGrid.terminationModeEnabled, false)
1014
1015 isCalendarLongPressed = false
1016+ waitForRendering(runningApplicationsGrid)
1017 mousePress(calendarTile, calendarTile.width/2, calendarTile.height/2)
1018 tryCompareFunction(checkSwitchToTerminationModeAfterLongPress, true)
1019
1020@@ -186,6 +187,7 @@
1021 verify(calendarTile != undefined)
1022
1023 verify(fakeRunningAppsModel.contains(calendarApp))
1024+ waitForRendering(runningApplicationsGrid) //ensure populating animation has stopped
1025
1026 mouseClick(calendarTile, calendarTile.width/2, calendarTile.height/2)
1027
1028
1029=== removed directory 'tests/qmltests/SideStage'
1030=== added directory 'tests/qmltests/Stages'
1031=== renamed file 'tests/qmltests/SideStage/tst_SideStage.qml' => 'tests/qmltests/Stages/tst_SideStage.qml'
1032--- tests/qmltests/SideStage/tst_SideStage.qml 2013-06-05 22:03:08 +0000
1033+++ tests/qmltests/Stages/tst_SideStage.qml 2013-06-13 14:42:44 +0000
1034@@ -18,7 +18,7 @@
1035 import QtTest 1.0
1036 import Unity.Test 0.1 as UT
1037 import ".."
1038-import "../../../SideStage"
1039+import "../../../Stages"
1040 import Ubuntu.Components 0.1
1041
1042 UT.UnityTestCase {
1043
1044=== renamed directory 'tests/qmltests/Components/tst_Stage' => 'tests/qmltests/Stages/tst_Stage'
1045=== renamed file 'tests/qmltests/Components/tst_Stage.qml' => 'tests/qmltests/Stages/tst_Stage.qml'
1046--- tests/qmltests/Components/tst_Stage.qml 2013-06-10 16:50:18 +0000
1047+++ tests/qmltests/Stages/tst_Stage.qml 2013-06-13 14:42:44 +0000
1048@@ -17,7 +17,7 @@
1049 import QtQuick 2.0
1050 import QtTest 1.0
1051 import ".."
1052-import "../../../Components"
1053+import "../../../Stages"
1054 import Ubuntu.Application 0.1
1055 import Ubuntu.Components 0.1
1056 import Unity.Test 0.1 as UT
1057
1058=== added directory 'tests/qmltests/Stages/tst_StageManager'
1059=== added file 'tests/qmltests/Stages/tst_StageManager-phone.qml'
1060--- tests/qmltests/Stages/tst_StageManager-phone.qml 1970-01-01 00:00:00 +0000
1061+++ tests/qmltests/Stages/tst_StageManager-phone.qml 2013-06-13 14:42:44 +0000
1062@@ -0,0 +1,255 @@
1063+/*
1064+ * Copyright 2013 Canonical Ltd.
1065+ *
1066+ * This program is free software; you can redistribute it and/or modify
1067+ * it under the terms of the GNU General Public License as published by
1068+ * the Free Software Foundation; version 3.
1069+ *
1070+ * This program is distributed in the hope that it will be useful,
1071+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1072+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1073+ * GNU General Public License for more details.
1074+ *
1075+ * You should have received a copy of the GNU General Public License
1076+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1077+ */
1078+
1079+import QtQuick 2.0
1080+import Ubuntu.Application 0.1
1081+import "../../../Stages"
1082+import "../../../Components"
1083+import "tst_StageManager"
1084+
1085+Item {
1086+ id: root
1087+ width: units.gu(40)
1088+ height: units.gu(72)
1089+
1090+ property var applicationManager: ApplicationManagerWrapper {}
1091+
1092+ Rectangle { //fake background
1093+ anchors.fill: parent
1094+ color: "black"
1095+ visible: stageManager.needUnderlay
1096+ }
1097+
1098+ StageManager {
1099+ id: stageManager
1100+
1101+ anchors.fill: parent
1102+
1103+ applicationManager: root.applicationManager
1104+ leftSwipePosition: 0
1105+ panelHeight: units.gu(3) + units.dp(2)
1106+ edgeHandleSize: units.gu(2)
1107+ enabled: true
1108+ }
1109+
1110+ ListSelector {
1111+ id: applications
1112+ anchors {
1113+ left: parent.left
1114+ right: parent.right
1115+ bottom: parent.bottom
1116+ }
1117+
1118+ list: ["phone-app", "gallery-app", "camera-app"]
1119+ onActivated: stageManager.activateApplication(desktopFileOf(entry))
1120+ onDeactivated: applicationManager.stopProcess(applicationManager.getApplicationFromDesktopFile(desktopFileOf(entry)))
1121+ }
1122+
1123+ function desktopFileOf(entry) {
1124+ return "/usr/share/applications/" + entry + ".desktop"
1125+ }
1126+
1127+ StageManagerTestCase {
1128+ name: "StageManager - phone - 0 window case"
1129+ when: windowShown
1130+ stageManagerUnderTest: stageManager
1131+
1132+ // left swipe does nothing when no applications open
1133+ function test_leftEdgeSwipeDisabledWithNoApplicationsOpen() {
1134+ rightEdgeSwipe()
1135+ checkStageManagerOffScreen()
1136+ }
1137+
1138+ function test_stageManagerOffScreenByDefault() {
1139+ checkStageManagerOffScreen()
1140+ }
1141+ }
1142+
1143+ StageManagerTestCase {
1144+ name: "StageManager - phone - 1 window case"
1145+ when: windowShown
1146+ stageManagerUnderTest: stageManager
1147+
1148+ function init() {
1149+ applications.activate("phone-app")
1150+ waitForAnimationsToFinish()
1151+ }
1152+
1153+ // When application started and StageManager hidden, StageManager shows immediately
1154+ function test_onLaunchStageManagerShown() {
1155+ checkStageManagerOnScreen()
1156+ }
1157+
1158+ // Left-edge swipe the StageManager away animates the StageManager away
1159+ function test_leftSwipeHidesStageManager() {
1160+ leftEdgeSwipe()
1161+ checkStageManagerOffScreen()
1162+ }
1163+
1164+ // Left-edge swipe the StageManager away unfocuses the application
1165+ function test_leftSwipeUnfocusesApplication() {
1166+ leftEdgeSwipe()
1167+ checkStageManagerOffScreen()
1168+
1169+ skip("FIXME: mainStageFocusedApplication not updated when application unfocused, see lp:1186980")
1170+ tryCompare(applicationManager, "mainStageFocusedApplication", null)
1171+ }
1172+
1173+ // Left-edge swipe StageManager away. Right-edge swipe should restore it
1174+ function test_rightSwipeRestoresApplication() {
1175+ leftEdgeSwipe()
1176+ checkStageManagerOffScreen()
1177+
1178+ rightEdgeSwipe()
1179+ checkStageManagerOnScreen()
1180+
1181+ waitForAnimationsToFinish()
1182+
1183+ tryCompare(applicationManager.mainStageFocusedApplication, "desktopFile",
1184+ desktopFileOf("phone-app"))
1185+ }
1186+
1187+ // Activate application. Left-edge swipe stages away. Use launcher to activate application
1188+ // again - the StageManager should slide in and focus application
1189+ function test_activateApplicationWhenStageHiddenRevealsApplication() {
1190+ leftEdgeSwipe()
1191+ checkStageManagerOffScreen()
1192+
1193+ applications.activate("phone-app")
1194+ checkStageManagerOnScreen()
1195+
1196+ waitForAnimationsToFinish()
1197+
1198+ tryCompare(applicationManager.mainStageFocusedApplication, "desktopFile",
1199+ desktopFileOf("phone-app"))
1200+ }
1201+
1202+ // Kill focused application. StageManager should hide
1203+ function test_killingApplicationShouldHideStageManager() {
1204+ applications.deactivate("phone-app")
1205+
1206+ checkStageManagerOffScreen()
1207+ }
1208+
1209+ // Starting a second app while StageManager on-screen does nothing to StageManager
1210+ function test_openingSecondAppWhileStageManagerOnScreen(){
1211+ applications.activate("gallery-app")
1212+
1213+ checkStageManagerOnScreen()
1214+
1215+ waitForAnimationsToFinish()
1216+
1217+ tryCompare(applicationManager.mainStageFocusedApplication, "desktopFile",
1218+ desktopFileOf("gallery-app"))
1219+ }
1220+
1221+ // Starting a second app while StageManager off-screen shows StageManager
1222+ function test_openingSecondAppWhileStageManagerOffScreen(){
1223+ leftEdgeSwipe()
1224+
1225+ applications.activate("gallery-app")
1226+ checkStageManagerOnScreen()
1227+
1228+ waitForAnimationsToFinish()
1229+
1230+ tryCompare(applicationManager.mainStageFocusedApplication, "desktopFile",
1231+ desktopFileOf("gallery-app"))
1232+ }
1233+
1234+ // Application focus change after animations complete
1235+ function test_onLaunchApplicationGetsFocusedOnlyAfterAnimation() {
1236+ applications.activate("gallery-app")
1237+
1238+ verify(applicationManager.mainStageFocusedApplication.desktopFile !== desktopFileOf("gallery-app"))
1239+
1240+ waitForAnimationsToFinish()
1241+
1242+ compare(applicationManager.mainStageFocusedApplication.desktopFile, desktopFileOf("gallery-app"),
1243+ "Focus not on newly activated application after animation")
1244+ }
1245+ }
1246+
1247+ StageManagerTestCase {
1248+ name: "StageManager - phone - 2 window case"
1249+ when: windowShown
1250+ stageManagerUnderTest: stageManager
1251+
1252+ function init() {
1253+ applications.activate("phone-app")
1254+ applications.activate("camera-app")
1255+ waitForAnimationsToFinish()
1256+ }
1257+
1258+ function test_focusOrder() {
1259+ compare(applicationManager.mainStageFocusedApplication.desktopFile, desktopFileOf("camera-app"),
1260+ "Focus not on last launched application")
1261+ }
1262+
1263+ // Check hiding and then revealing StageManager doesn't change the focus order
1264+ function test_focusOrderAfterUnfocus() {
1265+ leftEdgeSwipe()
1266+
1267+ rightEdgeSwipe()
1268+ waitForAnimationsToFinish()
1269+
1270+ compare(applicationManager.mainStageFocusedApplication.desktopFile, desktopFileOf("camera-app"),
1271+ "Focus order changed somehow by hide/reveal of StageManager")
1272+ }
1273+
1274+ // Check that a hidden StageManager reacts correctly when background application activated
1275+ function test_focusOrderWhenActivating() {
1276+ leftEdgeSwipe()
1277+
1278+ applications.activate("phone-app")
1279+
1280+ waitForAnimationsToFinish()
1281+ checkStageManagerOnScreen()
1282+
1283+ tryCompare(applicationManager.mainStageFocusedApplication, "desktopFile", desktopFileOf("phone-app"))
1284+
1285+ compare(applicationManager.mainStageFocusedApplication.desktopFile, desktopFileOf("phone-app"),
1286+ "Focus not on newly activated application when StageManager hidden")
1287+ }
1288+
1289+ // If the focused application dies, the StageManager should hide
1290+ function test_foregroundApplicationDeathDismissesStageManager() {
1291+ tryCompare(applicationManager.mainStageFocusedApplication, "desktopFile", desktopFileOf("camera-app"))
1292+
1293+ skip("FIXME: StageManager does not hide when foreground application dies")
1294+ applications.deactivate("camera-app")
1295+
1296+ checkStageManagerOffScreen()
1297+ }
1298+
1299+ // If a background application dies, the StageManager should not react
1300+ function test_nonForegroundApplicationDeathDoesNothing() {
1301+ tryCompare(applicationManager.mainStageFocusedApplication, "desktopFile", desktopFileOf("camera-app"))
1302+ applications.deactivate("phone-app")
1303+
1304+ checkStageManagerOnScreen()
1305+ }
1306+
1307+ function test_fullScreenMode() {
1308+ tryCompare(applicationManager.mainStageFocusedApplication, "desktopFile", desktopFileOf("camera-app"))
1309+
1310+ tryCompare(stageManager, "fullscreenMode", true)
1311+
1312+ rightEdgeSwipe()
1313+
1314+ tryCompare(stageManager, "fullscreenMode", false)
1315+ }
1316+ }
1317+}
1318
1319=== added file 'tests/qmltests/Stages/tst_StageManager-tablet.qml'
1320--- tests/qmltests/Stages/tst_StageManager-tablet.qml 1970-01-01 00:00:00 +0000
1321+++ tests/qmltests/Stages/tst_StageManager-tablet.qml 2013-06-13 14:42:44 +0000
1322@@ -0,0 +1,394 @@
1323+/*
1324+ * Copyright 2013 Canonical Ltd.
1325+ *
1326+ * This program is free software; you can redistribute it and/or modify
1327+ * it under the terms of the GNU General Public License as published by
1328+ * the Free Software Foundation; version 3.
1329+ *
1330+ * This program is distributed in the hope that it will be useful,
1331+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1332+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1333+ * GNU General Public License for more details.
1334+ *
1335+ * You should have received a copy of the GNU General Public License
1336+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1337+ */
1338+
1339+import QtQuick 2.0
1340+import Ubuntu.Application 0.1
1341+import "../../../Stages"
1342+import "../../../Components"
1343+import "tst_StageManager"
1344+
1345+Item {
1346+ id: root
1347+ width: units.gu(100)
1348+ height: units.gu(72)
1349+
1350+ property var applicationManager: ApplicationManagerWrapper {}
1351+
1352+ Rectangle { //fake background
1353+ anchors.fill: parent
1354+ color: "black"
1355+ visible: stageManager.needUnderlay
1356+ }
1357+
1358+ StageManager {
1359+ id: stageManager
1360+
1361+ anchors.fill: parent
1362+
1363+ applicationManager: root.applicationManager
1364+ leftSwipePosition: 0
1365+ panelHeight: units.gu(3) + units.dp(2)
1366+ edgeHandleSize: units.gu(2)
1367+ enabled: true
1368+ }
1369+
1370+ ListSelector {
1371+ id: applications
1372+ anchors {
1373+ left: parent.left
1374+ right: parent.right
1375+ bottom: parent.bottom
1376+ }
1377+
1378+ list: ["phone-app", "facebook-webapp", "gallery-app"]
1379+ onActivated: stageManager.activateApplication(desktopFileOf(entry))
1380+ onDeactivated: applicationManager.stopProcess(applicationManager.getApplicationFromDesktopFile(desktopFileOf(entry)))
1381+ }
1382+
1383+ function desktopFileOf(entry) {
1384+ return "/usr/share/applications/" + entry + ".desktop"
1385+ }
1386+
1387+ StageManagerTestCase {
1388+ name: "StageManager - tablet - 1 main-stage window case"
1389+ when: windowShown
1390+ stageManagerUnderTest: stageManager
1391+
1392+ function init() {
1393+ applications.activate("gallery-app") //main-stage app
1394+ waitForAnimationsToFinish()
1395+ }
1396+
1397+ // Right-edge press does not change main-stage app
1398+ function test_rightEdgeSwipeDoesNotChangeMainStageApp() {
1399+ rightEdgePress()
1400+ waitForRendering(stageManager)
1401+ compare(mainStage.oldApplicationScreenshot.visible, false) // i.e. no screenshot animation occurs
1402+ }
1403+ }
1404+
1405+ StageManagerTestCase {
1406+ name: "StageManager - tablet - 1 side-stage window case"
1407+ when: windowShown
1408+ stageManagerUnderTest: stageManager
1409+
1410+ function init() {
1411+ applications.activate("phone-app") // side-stage app
1412+ waitForAnimationsToFinish()
1413+ }
1414+
1415+ // Check loaded as side-stage application
1416+ function test_sideStage() {
1417+ tryCompare(applicationManager.sideStageFocusedApplication, "desktopFile", desktopFileOf("phone-app"))
1418+ compare(applicationManager.mainStageFocusedApplication, null)
1419+ }
1420+
1421+ // When application started and StageManager hidden, StageManager shows immediately
1422+ function test_onLaunchStageManagerShown() {
1423+ checkStageManagerOnScreen()
1424+ }
1425+
1426+ // Check side-stage positioned correctly
1427+ function test_sideStagePositionedCorrectly() {
1428+ compare(sideStage.x, stageManager.width - units.gu(40), "Sidestage not positioned correctly")
1429+ }
1430+
1431+ // Left-edge swipe the StageManager away animates the StageManager away
1432+ function test_leftSwipeHidesStageManager() {
1433+ leftEdgeSwipe()
1434+ checkStageManagerOffScreen()
1435+ }
1436+
1437+ // Left-edge swipe the StageManager away unfocuses the application
1438+ function test_leftSwipeUnfocusesApplication() {
1439+ leftEdgeSwipe()
1440+ checkStageManagerOffScreen()
1441+
1442+ skip("FIXME: sideStageFocusedApplication not updated when application unfocused, see lp:1186980")
1443+ tryCompare(applicationManager, "sideStageFocusedApplication", null)
1444+ }
1445+
1446+ // Left-edge swipe StageManager away. Right-edge swipe should restore it
1447+ function test_rightSwipeRestoresApplication() {
1448+ leftEdgeSwipe()
1449+ checkStageManagerOffScreen()
1450+
1451+ rightEdgeSwipe()
1452+ checkStageManagerOnScreen()
1453+
1454+ waitForAnimationsToFinish()
1455+
1456+ tryCompare(applicationManager.sideStageFocusedApplication, "desktopFile", desktopFileOf("phone-app"))
1457+ }
1458+
1459+ // Activate application. Left-edge swipe stages away. Use launcher to activate application
1460+ // again - the StageManager should slide in and focus application
1461+ function test_activateApplicationWhenStageHiddenRevealsApplication() {
1462+ leftEdgeSwipe()
1463+ checkStageManagerOffScreen()
1464+
1465+ applications.activate("phone-app")
1466+ checkStageManagerOnScreen()
1467+
1468+ waitForAnimationsToFinish()
1469+
1470+ tryCompare(applicationManager.sideStageFocusedApplication, "desktopFile", desktopFileOf("phone-app"))
1471+ }
1472+
1473+ // Kill focused application. StageManager should hide
1474+ function test_killingApplicationShouldHideStageManager() {
1475+ applications.deactivate("phone-app")
1476+
1477+ checkStageManagerOffScreen()
1478+ }
1479+
1480+ // Starting a second side-stage app while StageManager on-screen does nothing to StageManager
1481+ function test_openingSecondSideStageAppWhileStageManagerOnScreen(){
1482+ applications.activate("facebook-webapp") //side-stage app
1483+ checkStageManagerOnScreen()
1484+
1485+ waitForAnimationsToFinish()
1486+ tryCompare(applicationManager.sideStageFocusedApplication, "desktopFile", desktopFileOf("facebook-webapp"))
1487+ }
1488+
1489+ // Starting a second side-stage app while StageManager off-screen shows StageManager
1490+ function test_openingSecondSideStageAppWhileStageManagerOffScreen(){
1491+ leftEdgeSwipe()
1492+
1493+ applications.activate("facebook-webapp") //side-stage app
1494+ checkStageManagerOnScreen()
1495+
1496+ waitForAnimationsToFinish()
1497+ tryCompare(applicationManager.sideStageFocusedApplication, "desktopFile", desktopFileOf("facebook-webapp"))
1498+ }
1499+
1500+
1501+ // Starting a main-stage app while StageManager off-screen shows StageManager
1502+ function test_openingMainStageAppWhileStageManagerOffScreen(){
1503+ leftEdgeSwipe()
1504+ checkStageManagerOffScreen()
1505+
1506+ applications.activate("gallery-app") //main-stage app
1507+ checkStageManagerOnScreen()
1508+
1509+ waitForAnimationsToFinish()
1510+ tryCompare(applicationManager.mainStageFocusedApplication, "desktopFile", desktopFileOf("gallery-app"))
1511+ }
1512+
1513+ // Application focus change after animations complete
1514+ function test_onLaunchApplicationGetsFocusedOnlyAfterAnimation() {
1515+ applications.activate("facebook-webapp") //side-stage app
1516+
1517+ verify(applicationManager.sideStageFocusedApplication.desktopFile !== desktopFileOf("facebook-webapp"))
1518+
1519+ waitForAnimationsToFinish()
1520+ compare(applicationManager.sideStageFocusedApplication.desktopFile, desktopFileOf("facebook-webapp"),
1521+ "Newly activated application does not have focus after animation")
1522+ }
1523+
1524+ // Right-edge press changes side-stage app
1525+ function test_rightEdgeSwipeChangesSideStageOnly() {
1526+ rightEdgePress()
1527+ tryCompareFunction( function() {
1528+ return sideStage.oldApplicationScreenshot.scale < 1
1529+ }, true)
1530+ }
1531+
1532+ // Right-swipe of side-stage handle does nothing
1533+ function test_rightSwipeOfSideStageHandleDoesNothing() {
1534+ sideStageHandleRightSwipe()
1535+
1536+ compare(sideStage.x, stageManager.width - units.gu(40),
1537+ "Side-stage was incorrectly dismissed when no main-stage application open")
1538+ }
1539+ }
1540+
1541+ StageManagerTestCase {
1542+ name: "StageManager - tablet - 1 side-stage & 1 main-stage window case"
1543+ when: windowShown
1544+ stageManagerUnderTest: stageManager
1545+
1546+ function init() {
1547+ applications.activate("gallery-app") //main-stage app
1548+ applications.activate("phone-app") //side-stage app
1549+ waitForAnimationsToFinish()
1550+ }
1551+
1552+ function test_focusOrder() {
1553+ compare(applicationManager.mainStageFocusedApplication.desktopFile, desktopFileOf("gallery-app"),
1554+ "Main-stage application not got focus after activation")
1555+ compare(applicationManager.sideStageFocusedApplication.desktopFile, desktopFileOf("phone-app"),
1556+ "Side-stage application not got focus after activation")
1557+ }
1558+
1559+ // Check hiding and then revealing StageManager doesn't change the focus order
1560+ function test_focusOrderAfterUnfocus() {
1561+ leftEdgeSwipe()
1562+
1563+ rightEdgeSwipe()
1564+ waitForAnimationsToFinish()
1565+
1566+ compare(applicationManager.mainStageFocusedApplication.desktopFile, desktopFileOf("gallery-app"),
1567+ "Main-stage application lost focus from StageManager hide/show")
1568+ compare(applicationManager.sideStageFocusedApplication.desktopFile, desktopFileOf("phone-app"),
1569+ "Side-stage application lost focus from StageManager hide/show")
1570+ }
1571+
1572+ // Check that a hidden StageManager reacts correctly when already-open side-stage application activated
1573+ function test_focusOrderWhenActivatingSideStage() {
1574+ leftEdgeSwipe()
1575+
1576+ applications.activate("phone-app") //side-stage app
1577+
1578+ checkStageManagerOnScreen()
1579+ }
1580+
1581+ // Check that a hidden StageManager reacts correctly when already-open main-stage application activated
1582+ function test_focusOrderWhenActivatingMainStage() {
1583+ leftEdgeSwipe()
1584+
1585+ applications.activate("gallery-app") //main-stage app
1586+
1587+ checkStageManagerOnScreen()
1588+ }
1589+
1590+ // If the main-stage application dies, the StageManager should stay on screen
1591+ function test_foregroundMainStageApplicationDeathDoesNothing() {
1592+ applications.deactivate("gallery-app") //main-stage app
1593+
1594+ checkStageManagerOnScreen()
1595+ compare(applicationManager.sideStageFocusedApplication.desktopFile, desktopFileOf("phone-app"),
1596+ "Side-stage application lost focus after main-stage application died")
1597+ }
1598+
1599+ // If the side-stage application dies, the StageManager should stay on screen
1600+ function test_foregroundSideStageApplicationDeathDoesNothing() {
1601+ applications.deactivate("phone-app") //side-stage app
1602+
1603+ checkStageManagerOnScreen()
1604+ compare(applicationManager.mainStageFocusedApplication.desktopFile, desktopFileOf("gallery-app"),
1605+ "Main-stage application lost focus after side-stage application died")
1606+ }
1607+
1608+ // If the side-stage application dies, the side stage should hide
1609+ function test_foregroundSideStageApplicationDeathHidesSideStage() {
1610+ applications.deactivate("phone-app") //side-stage app
1611+
1612+ tryCompareFunction( function() {
1613+ return sideStage.x > stageManager.width
1614+ }, true)
1615+ }
1616+
1617+ // Right-edge swipe changes side-stage app
1618+ function test_rightEdgeSwipeChangesSideStage() {
1619+ tryCompare(sideStage, "x", stageManager.width - sideStage.width) //ensure side-stage open
1620+
1621+ waitForAnimationsToFinish()
1622+ rightEdgePress()
1623+ waitForRendering(stageManager)
1624+ tryCompareFunction( function() {
1625+ return sideStage.oldApplicationScreenshot.scale < 1
1626+ }, true)
1627+ }
1628+
1629+ // Right-edge swipe does not change main-stage app
1630+ function test_rightEdgeSwipeDoesNotChangeMainStage() {
1631+ rightEdgePress()
1632+ waitForRendering(stageManager)
1633+ compare(mainStage.oldApplicationScreenshot.scale, 1,
1634+ "Right-edge swipe animating main-stage while side-stage app open")
1635+ }
1636+
1637+ // Right-swipe of side-stage handle dismisses side-stage
1638+ function test_rightSwipeOfSideStageHandleHidesSideStage() {
1639+ sideStageHandleRightSwipe()
1640+ waitForAnimationsToFinish()
1641+
1642+ tryCompare(sideStage, "x", stageManager.width + sideStage.handleSizeCollapsed)
1643+ }
1644+
1645+ // Right-swipe of side-stage unfocuses side-stage app
1646+ function test_rightSwipeOfSideStageHandleUnfocusesSideStageApp() {
1647+ tryCompare(sideStage, "x", stageManager.width - sideStage.width) //ensure side-stage open
1648+
1649+ sideStageHandleRightSwipe()
1650+ waitForAnimationsToFinish()
1651+
1652+ compare(applicationManager.mainStageFocusedApplication.desktopFile, desktopFileOf("gallery-app"),
1653+ "Right-edge swipe of side-stage app changed main-stage focus")
1654+ skip("FIXME: sideStageFocusedApplication not updated when application unfocused, see lp:1186980")
1655+ //tryCompare(applicationManager, "sideStageFocusedApplication", null)
1656+ }
1657+
1658+ // Hidden side-stage can be right-edge swiped back
1659+ function test_rightEdgeSwipeRestoresHiddenSideStageApp() {
1660+ sideStageHandleRightSwipe()
1661+ waitForAnimationsToFinish()
1662+
1663+ rightEdgeSwipe()
1664+ waitForAnimationsToFinish()
1665+
1666+ tryCompare(sideStage, "x", stageManager.width - units.gu(40))
1667+ }
1668+
1669+ // Hidden side-stage app when right-edge swiped has focus restored
1670+ function test_rightEdgeSwipeRestoresFocusToSideStageApp() {
1671+ sideStageHandleRightSwipe()
1672+ waitForAnimationsToFinish()
1673+
1674+ rightEdgeSwipe()
1675+ waitForAnimationsToFinish()
1676+
1677+ compare(applicationManager.sideStageFocusedApplication.desktopFile, desktopFileOf("phone-app"),
1678+ "Side-stage app not returned focus after StageManager un-hidden")
1679+ compare(applicationManager.mainStageFocusedApplication.desktopFile, desktopFileOf("gallery-app"),
1680+ "Main-stage app not returned focus after StageManager un-hidden")
1681+ }
1682+
1683+ // Hide side-stage, then activate the side-stage application. It should slide back in
1684+ function test_activatingHiddenSideStageAppSlidesItIn() {
1685+ sideStageHandleRightSwipe()
1686+ waitForAnimationsToFinish()
1687+
1688+ applications.activate("phone-app") //side-stage app
1689+ waitForAnimationsToFinish()
1690+
1691+ tryCompare(sideStage, "x", stageManager.width - sideStage.width)
1692+ }
1693+
1694+ // Hide side-stage, then left-swipe to hide whole StageManager. Activate side-stage app
1695+ // StageManager should slide in, with side-stage app fixed in place (i.e. not sliding independently)
1696+ function test_activatingHiddenSideStageAppWhenStageManagerHidden() {
1697+ sideStageHandleRightSwipe()
1698+ waitForAnimationsToFinish()
1699+
1700+ leftEdgeSwipe(units.gu(160))
1701+ checkStageManagerOffScreen()
1702+
1703+ applications.activate("phone-app") //side-stage app
1704+ tryCompare(sideStage, "x", stageManager.width - units.gu(40))
1705+ }
1706+
1707+ // Starting a main-stage app while StageManager on-screen does nothing to StageManager
1708+ function test_openingSecondMainStageAppWhileStageManagerOnScreen(){
1709+ applications.activate("gallery-app") //main-stage app
1710+ checkStageManagerOnScreen()
1711+
1712+ waitForAnimationsToFinish()
1713+ tryCompare(applicationManager.mainStageFocusedApplication, "desktopFile", desktopFileOf("gallery-app"))
1714+ }
1715+ }
1716+}
1717
1718=== added file 'tests/qmltests/Stages/tst_StageManager/ListSelector.qml'
1719--- tests/qmltests/Stages/tst_StageManager/ListSelector.qml 1970-01-01 00:00:00 +0000
1720+++ tests/qmltests/Stages/tst_StageManager/ListSelector.qml 2013-06-13 14:42:44 +0000
1721@@ -0,0 +1,101 @@
1722+/*
1723+ * Copyright 2013 Canonical Ltd.
1724+ *
1725+ * This program is free software; you can redistribute it and/or modify
1726+ * it under the terms of the GNU General Public License as published by
1727+ * the Free Software Foundation; version 3.
1728+ *
1729+ * This program is distributed in the hope that it will be useful,
1730+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1731+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1732+ * GNU General Public License for more details.
1733+ *
1734+ * You should have received a copy of the GNU General Public License
1735+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1736+ */
1737+
1738+import QtQuick 2.0
1739+import Ubuntu.Application 0.1
1740+import Ubuntu.Components 0.1
1741+
1742+Rectangle {
1743+ id: root
1744+ color: "#ccffffff"
1745+ height: childrenRect.height
1746+
1747+ property alias list: repeater.model
1748+ signal activated(string entry)
1749+ signal deactivated(string entry)
1750+
1751+ function activate(entry) {
1752+ lister.changeEntry(entry, true);
1753+ }
1754+
1755+ function deactivate(entry) {
1756+ lister.changeEntry(entry, false);
1757+ }
1758+
1759+ function deactivateAll() {
1760+ for (var i=0; i<list.length; i++) {
1761+ lister.changeEntry(list[i], false);
1762+ }
1763+ }
1764+
1765+ Flow {
1766+ id: lister
1767+ anchors {
1768+ top: parent.top
1769+ left: parent.left
1770+ right: parent.right
1771+ }
1772+
1773+ function changeEntry(entry, setting) {
1774+ for (var i=0; i<lister.children.length; i++) {
1775+ var child = lister.children[i];
1776+ if (child === lister) continue;
1777+ if (child.label == entry) {
1778+ if (child.checked == setting) {
1779+ if (setting) root.activated(child.label); //ensure we fire activated again
1780+ } else {
1781+ child.checked = setting;
1782+ }
1783+ break;
1784+ }
1785+ }
1786+ }
1787+ Repeater {
1788+ id: repeater
1789+ delegate: entry
1790+ }
1791+
1792+ Component {
1793+ id: entry
1794+
1795+ Row {
1796+ height: units.gu(5)
1797+ width: units.gu(20)
1798+
1799+ property alias checked: checkbox.checked
1800+ property string label: modelData
1801+
1802+ CheckBox {
1803+ id: checkbox
1804+ onCheckedChanged: {
1805+ if (checked) {
1806+ activated(modelData)
1807+ } else {
1808+ deactivated(modelData)
1809+ }
1810+ }
1811+ }
1812+ Button {
1813+ text: parent.label
1814+ width: parent.width - checkbox.width
1815+ anchors.verticalCenter: checkbox.verticalCenter
1816+ enabled: checkbox.checked
1817+ onClicked: activated(modelData)
1818+ }
1819+ }
1820+ }
1821+ }
1822+}
1823
1824=== added file 'tests/qmltests/Stages/tst_StageManager/StageManagerTestCase.qml'
1825--- tests/qmltests/Stages/tst_StageManager/StageManagerTestCase.qml 1970-01-01 00:00:00 +0000
1826+++ tests/qmltests/Stages/tst_StageManager/StageManagerTestCase.qml 2013-06-13 14:42:44 +0000
1827@@ -0,0 +1,98 @@
1828+/*
1829+ * Copyright 2013 Canonical Ltd.
1830+ *
1831+ * This program is free software; you can redistribute it and/or modify
1832+ * it under the terms of the GNU General Public License as published by
1833+ * the Free Software Foundation; version 3.
1834+ *
1835+ * This program is distributed in the hope that it will be useful,
1836+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1837+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1838+ * GNU General Public License for more details.
1839+ *
1840+ * You should have received a copy of the GNU General Public License
1841+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1842+ */
1843+
1844+import QtQuick 2.0
1845+import QtTest 1.0
1846+import Unity.Test 0.1 as UT
1847+import "../../../../Stages"
1848+
1849+UT.UnityTestCase {
1850+ property StageManager stageManagerUnderTest
1851+ property point __lastMouseEvent: Qt.point(-1, -1)
1852+ property SideStage sideStage: null
1853+ property Stage mainStage: null
1854+
1855+ onStageManagerUnderTestChanged: {
1856+ mainStage = findChild(stageManagerUnderTest, "mainStage")
1857+ sideStage = findChild(stageManagerUnderTest, "sideStage")
1858+ }
1859+
1860+ function cleanup() {
1861+ resetMouseState()
1862+ applications.deactivateAll()
1863+ compare(applicationManager.mainStageApplications.count, 0, "Some main-stage application failed to quit")
1864+ compare(applicationManager.sideStageApplications.count, 0, "Some side-stage application failed to quit")
1865+ checkStageManagerOffScreen()
1866+ }
1867+
1868+ function rightEdgePress() {
1869+ __lastMouseEvent.x = stageManagerUnderTest.width - (stageManagerUnderTest.edgeHandleSize / 2)
1870+ __lastMouseEvent.y = stageManagerUnderTest.height / 2
1871+ touchPress(stageManagerUnderTest, __lastMouseEvent.x, __lastMouseEvent.y)
1872+ }
1873+
1874+ function rightEdgeRelease() {
1875+ touchRelease(stageManagerUnderTest, __lastMouseEvent.x, __lastMouseEvent.y)
1876+ __lastMouseEvent = Qt.point(-1, -1)
1877+ }
1878+
1879+ function resetMouseState() {
1880+ if (__lastMouseEvent !== Qt.point(-1, -1)) {
1881+ rightEdgeRelease()
1882+ }
1883+ }
1884+
1885+ function waitForAnimationsToFinish() {
1886+ tryCompare(stageManagerUnderTest, "stageScreenshotsReady", false)
1887+ }
1888+
1889+ function leftEdgeSwipe(distance) {
1890+ if (distance == undefined) distance = stageManagerUnderTest.width / 3 * 2
1891+
1892+ var x = stageManagerUnderTest.edgeHandleSize / 2
1893+ var y = stageManagerUnderTest.height / 2
1894+ touchFlick(stageManagerUnderTest, x, y,
1895+ x + distance, y)
1896+ }
1897+
1898+ function rightEdgeSwipe(distance) {
1899+ if (distance == undefined) distance = stageManagerUnderTest.width / 3 * 2
1900+
1901+ var x = stageManagerUnderTest.width - (stageManagerUnderTest.edgeHandleSize / 2)
1902+ var y = stageManagerUnderTest.height / 2
1903+ touchFlick(stageManagerUnderTest, x, y,
1904+ x - distance, y)
1905+ }
1906+
1907+ function sideStageHandleRightSwipe(distance) {
1908+ if (distance == undefined) distance = sideStage.width / 3 * 2
1909+
1910+ var x = sideStage.x - (sideStage.rightEdgeDraggingAreaWidth / 2)
1911+ var y = sideStage.height / 2
1912+ touchFlick(stageManagerUnderTest, x, y,
1913+ x + distance, y)
1914+ }
1915+
1916+ function checkStageManagerOffScreen() {
1917+ tryCompare(stageManagerUnderTest, "animatedProgress", 0) //0 means off-screen
1918+ tryCompare(stageManagerUnderTest, "shown", false)
1919+ }
1920+
1921+ function checkStageManagerOnScreen() {
1922+ tryCompare(stageManagerUnderTest, "animatedProgress", 1) //1 means on-screen
1923+ tryCompare(stageManagerUnderTest, "shown", true)
1924+ }
1925+}

Subscribers

People subscribed via source and target branches

to all changes: