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
=== modified file 'CMakeLists.txt'
--- CMakeLists.txt 2013-06-05 22:03:08 +0000
+++ CMakeLists.txt 2013-06-13 14:42:44 +0000
@@ -144,7 +144,7 @@
144 Hud144 Hud
145 Panel145 Panel
146 Launcher146 Launcher
147 SideStage147 Stages
148 Notifications148 Notifications
149 )149 )
150150
151151
=== modified file 'Shell.qml'
--- Shell.qml 2013-06-12 08:48:37 +0000
+++ Shell.qml 2013-06-13 14:42:44 +0000
@@ -26,7 +26,7 @@
26import "Components"26import "Components"
27import "Components/Math.js" as MathLocal27import "Components/Math.js" as MathLocal
28import "Bottombar"28import "Bottombar"
29import "SideStage"29import "Stages"
3030
31FocusScope {31FocusScope {
32 id: shell32 id: shell
@@ -42,71 +42,14 @@
42 readonly property real panelHeight: panel.panelHeight42 readonly property real panelHeight: panel.panelHeight
4343
44 property bool dashShown: dash.shown44 property bool dashShown: dash.shown
45 property bool stageScreenshotsReady: {45 readonly property bool stageScreenshotsReady: stageManager.stageScreenshotsReady
46 if (sideStage.shown) {
47 if (mainStage.applications.count > 0) {
48 return mainStage.usingScreenshots && sideStage.usingScreenshots;
49 } else {
50 return sideStage.usingScreenshots;
51 }
52 } else {
53 return mainStage.usingScreenshots;
54 }
55 }
5646
57 property ListModel searchHistory: SearchHistoryModel {}47 property ListModel searchHistory: SearchHistoryModel {}
5848
59 property var applicationManager: ApplicationManagerWrapper {}49 property var applicationManager: ApplicationManagerWrapper {}
6050
61 Component.onCompleted: {
62 applicationManager.sideStageEnabled = Qt.binding(function() { return sideStage.enabled })
63
64 // FIXME: if application focused before shell starts, shell draws on top of it only.
65 // We should detect already running applications on shell start and bring them to the front.
66 applicationManager.unfocusCurrentApplication();
67 }
68
69 readonly property bool fullscreenMode: {
70 if (greeter.shown || lockscreen.shown) {
71 return false;
72 } else if (mainStage.usingScreenshots) { // Window Manager animating so want to re-evaluate fullscreen mode
73 return mainStage.switchingFromFullscreenToFullscreen;
74 } else if (applicationManager.mainStageFocusedApplication) {
75 return applicationManager.mainStageFocusedApplication.fullscreen;
76 } else {
77 return false;
78 }
79 }
80
81 Connections {
82 target: applicationManager
83 ignoreUnknownSignals: true
84 onFocusRequested: {
85 // TODO: this should be protected to only unlock for certain applications / certain usecases
86 // potentially only in connection with a notification
87 greeter.hide();
88 shell.activateApplication(desktopFile);
89 }
90 }
91
92 function activateApplication(desktopFile, argument) {51 function activateApplication(desktopFile, argument) {
93 if (applicationManager) {52 stageManager.activateApplication(desktopFile, argument);
94 // For newly started applications, as it takes them time to draw their first frame
95 // we add a delay before we hide the animation screenshots to compensate.
96 var addDelay = !applicationManager.getApplicationFromDesktopFile(desktopFile);
97
98 var application;
99 application = applicationManager.activateApplication(desktopFile, argument);
100 if (application == null) {
101 return;
102 }
103 if (application.stage == ApplicationInfo.MainStage || !sideStage.enabled) {
104 mainStage.activateApplication(desktopFile, addDelay);
105 } else {
106 sideStage.activateApplication(desktopFile, addDelay);
107 }
108 stages.show();
109 }
110 }53 }
11154
112 VolumeControl {55 VolumeControl {
@@ -126,9 +69,7 @@
126 id: underlay69 id: underlay
127 anchors.fill: parent70 anchors.fill: parent
128 visible: !(panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth)71 visible: !(panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth)
129 && (stages.fullyHidden72 && stageManager.needUnderlay
130 || (stages.fullyShown && mainStage.usingScreenshots)
131 || !stages.fullyShown && (mainStage.usingScreenshots || (sideStage.shown && sideStage.usingScreenshots)))
13273
133 Image {74 Image {
134 id: backgroundImage75 id: backgroundImage
@@ -148,7 +89,7 @@
148 id: dash89 id: dash
14990
150 available: !greeter.shown && !lockscreen.shown91 available: !greeter.shown && !lockscreen.shown
151 hides: [stages, launcher, panel.indicators]92 hides: [stageManager, launcher, panel.indicators]
152 shown: disappearingAnimationProgress !== 1.093 shown: disappearingAnimationProgress !== 1.0
153 enabled: disappearingAnimationProgress === 0.094 enabled: disappearingAnimationProgress === 0.0
154 // FIXME: unfocus all applications when going back to the dash95 // FIXME: unfocus all applications when going back to the dash
@@ -165,182 +106,24 @@
165106
166 contentScale: 1.0 - 0.2 * disappearingAnimationProgress107 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
167 opacity: 1.0 - disappearingAnimationProgress108 opacity: 1.0 - disappearingAnimationProgress
168 property real disappearingAnimationProgress: ((greeter.shown) ? greeterRevealer.animatedProgress : stagesRevealer.animatedProgress)109 property real disappearingAnimationProgress: ((greeter.shown) ? greeterRevealer.animatedProgress : stageManager.animatedProgress)
169 // FIXME: only necessary because stagesRevealer.animatedProgress and110 // FIXME: only necessary because stageManager.animatedProgress and
170 // greeterRevealer.animatedProgress are not animated111 // greeterRevealer.animatedProgress are not animated
171 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}112 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
172 }113 }
173 }114 }
174115
175116 StageManager {
176 Item {117 id: stageManager
177118
178 width: parent.width119 anchors.fill: parent
179 height: parent.height120
180 x: launcher.progress121 enabled: !greeter.shown
181 Behavior on x {SmoothedAnimation{velocity: 600}}122 hides: [launcher, panel.indicators]
182123 applicationManager: shell.applicationManager
183124 leftSwipePosition: launcher.progress
184 Showable {125 panelHeight: panel.panelHeight
185 id: stages126 edgeHandleSize: shell.edgeSize
186
187 property bool fullyShown: shown && stages[stagesRevealer.boundProperty] == stagesRevealer.openedValue
188 && parent.x == 0
189 property bool fullyHidden: !shown && stages[stagesRevealer.boundProperty] == stagesRevealer.closedValue
190 available: !greeter.shown
191 hides: [launcher, panel.indicators]
192 shown: false
193 opacity: 1.0
194 showAnimation: StandardAnimation { property: "x"; duration: 350; to: stagesRevealer.openedValue; easing.type: Easing.OutCubic }
195 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: stagesRevealer.closedValue; easing.type: Easing.OutCubic }
196
197 width: parent.width
198 height: parent.height
199
200 // close the stages when no focused application remains
201 Connections {
202 target: shell.applicationManager
203 onMainStageFocusedApplicationChanged: stages.closeIfNoApplications()
204 onSideStageFocusedApplicationChanged: stages.closeIfNoApplications()
205 ignoreUnknownSignals: true
206 }
207
208 function closeIfNoApplications() {
209 if (!shell.applicationManager.mainStageFocusedApplication
210 && !shell.applicationManager.sideStageFocusedApplication
211 && shell.applicationManager.mainStageApplications.count == 0
212 && shell.applicationManager.sideStageApplications.count == 0) {
213 stages.hide();
214 }
215 }
216
217 // show the stages when an application gets the focus
218 Connections {
219 target: shell.applicationManager
220 onMainStageFocusedApplicationChanged: {
221 if (shell.applicationManager.mainStageFocusedApplication) {
222 mainStage.show();
223 stages.show();
224 }
225 }
226 onSideStageFocusedApplicationChanged: {
227 if (shell.applicationManager.sideStageFocusedApplication) {
228 sideStage.show();
229 stages.show();
230 }
231 }
232 ignoreUnknownSignals: true
233 }
234
235
236 Stage {
237 id: mainStage
238
239 anchors.fill: parent
240 fullyShown: stages.fullyShown
241 shouldUseScreenshots: !fullyShown
242 rightEdgeEnabled: !sideStage.enabled
243
244 applicationManager: shell.applicationManager
245 rightEdgeDraggingAreaWidth: shell.edgeSize
246 normalApplicationY: shell.panelHeight
247
248 shown: true
249 function show() {
250 stages.show();
251 }
252 function showWithoutAnimation() {
253 stages.showWithoutAnimation();
254 }
255 function hide() {
256 }
257
258 // FIXME: workaround the fact that focusing a main stage application
259 // raises its surface on top of all other surfaces including the ones
260 // that belong to side stage applications.
261 onFocusedApplicationChanged: {
262 if (focusedApplication && sideStage.focusedApplication && sideStage.fullyShown) {
263 shell.applicationManager.focusApplication(sideStage.focusedApplication);
264 }
265 }
266 }
267
268 SideStage {
269 id: sideStage
270
271 applicationManager: shell.applicationManager
272 rightEdgeDraggingAreaWidth: shell.edgeSize
273 normalApplicationY: shell.panelHeight
274
275 onShownChanged: {
276 if (!shown && mainStage.applications.count == 0) {
277 stages.hide();
278 }
279 }
280 // FIXME: when hiding the side stage, refocus the main stage
281 // application so that it goes in front of the side stage
282 // application and hides it
283 onFullyShownChanged: {
284 if (!fullyShown && stages.fullyShown && sideStage.focusedApplication != null) {
285 shell.applicationManager.focusApplication(mainStage.focusedApplication);
286 }
287 }
288
289 enabled: shell.width >= units.gu(60)
290 visible: enabled
291 fullyShown: stages.fullyShown && shown
292 && sideStage[sideStageRevealer.boundProperty] == sideStageRevealer.openedValue
293 shouldUseScreenshots: !fullyShown || mainStage.usingScreenshots || sideStageRevealer.pressed
294
295 available: !greeter.shown && !lockscreen.shown && enabled
296 hides: [launcher, panel.indicators]
297 shown: false
298 showAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.openedValue; easing.type: Easing.OutQuint }
299 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.closedValue; easing.type: Easing.OutQuint }
300
301 width: units.gu(40)
302 height: stages.height
303 handleExpanded: sideStageRevealer.pressed
304 }
305
306 Revealer {
307 id: sideStageRevealer
308
309 enabled: mainStage.applications.count > 0 && sideStage.applications.count > 0
310 && sideStage.available
311 direction: Qt.RightToLeft
312 openedValue: parent.width - sideStage.width
313 hintDisplacement: units.gu(3)
314 /* The size of the sidestage handle needs to be bigger than the
315 typical size used for edge detection otherwise it is really
316 hard to grab.
317 */
318 handleSize: sideStage.shown ? units.gu(4) : shell.edgeSize
319 closedValue: parent.width + sideStage.handleSizeCollapsed
320 target: sideStage
321 x: parent.width - width
322 width: sideStage.width + handleSize * 0.7
323 height: sideStage.height
324 orientation: Qt.Horizontal
325 }
326 }
327 }
328
329
330 Revealer {
331 id: stagesRevealer
332
333 property real animatedProgress: MathLocal.clamp((-dragPosition - launcher.progress) / closedValue, 0, 1)
334 enabled: mainStage.applications.count > 0 || sideStage.applications.count > 0
335 direction: Qt.RightToLeft
336 openedValue: 0
337 hintDisplacement: units.gu(3)
338 handleSize: shell.edgeSize
339 closedValue: width
340 target: stages
341 width: stages.width
342 height: stages.height
343 orientation: Qt.Horizontal
344 }127 }
345128
346 Lockscreen {129 Lockscreen {
@@ -409,8 +192,8 @@
409 greeter.forceActiveFocus();192 greeter.forceActiveFocus();
410 // FIXME: *FocusedApplication are not updated when unfocused, hence the need to check whether193 // FIXME: *FocusedApplication are not updated when unfocused, hence the need to check whether
411 // the stage was actually shown194 // the stage was actually shown
412 if (mainStage.fullyShown) greeter.previousMainApp = applicationManager.mainStageFocusedApplication;195 if (stageManager.mainStageFullyShown) greeter.previousMainApp = applicationManager.mainStageFocusedApplication;
413 if (sideStage.fullyShown) greeter.previousSideApp = applicationManager.sideStageFocusedApplication;196 if (stageManager.sideStageFullyShown) greeter.previousSideApp = applicationManager.sideStageFocusedApplication;
414 applicationManager.unfocusCurrentApplication();197 applicationManager.unfocusCurrentApplication();
415 } else {198 } else {
416 if (greeter.previousMainApp) {199 if (greeter.previousMainApp) {
@@ -461,7 +244,7 @@
461 indicators {244 indicators {
462 hides: [launcher]245 hides: [launcher]
463 }246 }
464 fullscreenMode: shell.fullscreenMode247 fullscreenMode: stageManager.fullscreenMode
465 searchVisible: !greeter.shown && !lockscreen.shown248 searchVisible: !greeter.shown && !lockscreen.shown
466249
467 InputFilterArea {250 InputFilterArea {
@@ -537,16 +320,16 @@
537 onDashItemSelected: {320 onDashItemSelected: {
538 greeter.hide()321 greeter.hide()
539 // Animate if moving between application and dash322 // Animate if moving between application and dash
540 if (!stages.shown) {323 if (!stageManager.shown) {
541 dash.setCurrentLens("home.lens", true, false)324 dash.setCurrentLens("home.lens", true, false)
542 } else {325 } else {
543 dash.setCurrentLens("home.lens", false, false)326 dash.setCurrentLens("home.lens", false, false)
544 }327 }
545 stages.hide();328 stageManager.hide();
546 }329 }
547 onDash: {330 onDash: {
548 dash.setCurrentLens("applications.lens", true, false)331 dash.setCurrentLens("applications.lens", true, false)
549 stages.hide();332 stageManager.hide();
550 }333 }
551 onLauncherApplicationSelected:{334 onLauncherApplicationSelected:{
552 greeter.hide()335 greeter.hide()
553336
=== removed directory 'SideStage'
=== added directory 'Stages'
=== renamed file 'SideStage/SideStage.qml' => 'Stages/SideStage.qml'
--- SideStage/SideStage.qml 2013-06-05 22:03:08 +0000
+++ Stages/SideStage.qml 2013-06-13 14:42:44 +0000
@@ -38,7 +38,7 @@
38 left: parent.left38 left: parent.left
39 right: parent.right39 right: parent.right
40 }40 }
41 height: shell.panelHeight41 height: normalApplicationY
42 color: background.color42 color: background.color
43 z: -143 z: -1
44 }44 }
4545
=== renamed file 'SideStage/SidestageHandle.qml' => 'Stages/SidestageHandle.qml'
=== renamed file 'Components/Stage.qml' => 'Stages/Stage.qml'
--- Components/Stage.qml 2013-06-07 18:01:50 +0000
+++ Stages/Stage.qml 2013-06-13 14:42:44 +0000
@@ -18,6 +18,7 @@
18import Ubuntu.Application 0.118import Ubuntu.Application 0.1
19import Ubuntu.Components 0.119import Ubuntu.Components 0.1
20import Ubuntu.Gestures 0.120import Ubuntu.Gestures 0.1
21import "../Components"
2122
22/*23/*
23 Responsible for application switching.24 Responsible for application switching.
@@ -179,7 +180,11 @@
179 delayedHideScreenshots.stop();180 delayedHideScreenshots.stop();
180 applicationManager.focusApplication(application);181 applicationManager.focusApplication(application);
181 }182 }
182 stage.focusedApplicationWhenUsingScreenshots = null;183
184 // Don't over-write this if another animation has begun, as another app will get focus then
185 if (!showStartingApplicationAnimation.running && !switchToApplicationAnimation.running) {
186 stage.focusedApplicationWhenUsingScreenshots = null;
187 }
183 }188 }
184189
185 function __focusApplicationUsingScreenshots(application) {190 function __focusApplicationUsingScreenshots(application) {
186191
=== added file 'Stages/StageManager.qml'
--- Stages/StageManager.qml 1970-01-01 00:00:00 +0000
+++ Stages/StageManager.qml 2013-06-13 14:42:44 +0000
@@ -0,0 +1,287 @@
1/*
2 * Copyright (C) 2013 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.0
18import Ubuntu.Application 0.1
19import "../Components"
20import "../Components/Math.js" as MathLocal
21
22Item {
23 id: stageManager
24
25 property var applicationManager
26 property real leftSwipePosition: 0
27 property real panelHeight: 0
28 property real edgeHandleSize
29 property bool enabled
30 property var hides: []
31 readonly property bool shown: stages.shown
32 readonly property real animatedProgress: MathLocal.clamp((-stagesRevealer.dragPosition - leftSwipePosition)
33 / stagesRevealer.closedValue, 0, 1)
34
35 readonly property bool stageScreenshotsReady: {
36 if (sideStage.shown) {
37 if (mainStage.applications.count > 0) {
38 return mainStage.usingScreenshots || sideStage.usingScreenshots;
39 } else {
40 return sideStage.usingScreenshots;
41 }
42 } else {
43 return mainStage.usingScreenshots;
44 }
45 }
46
47 readonly property bool needUnderlay: (stages.fullyHidden
48 || (stages.fullyShown && mainStage.usingScreenshots)
49 || !stages.fullyShown && (mainStage.usingScreenshots || (sideStage.shown && sideStage.usingScreenshots)))
50
51 readonly property bool fullscreenMode: {
52 if (!enabled) {
53 return false;
54 } else if (mainStage.usingScreenshots) { // Window Manager animating so want to re-evaluate fullscreen mode
55 return mainStage.switchingFromFullscreenToFullscreen;
56 } else if (applicationManager.mainStageFocusedApplication) {
57 return applicationManager.mainStageFocusedApplication.fullscreen;
58 } else {
59 return false;
60 }
61 }
62
63 readonly property alias mainStageFullyShown: mainStage.fullyShown
64 readonly property alias sideStageFullyShown: sideStage.fullyShown
65
66 function hide() {
67 stages.hide()
68 }
69
70 Component.onCompleted: {
71 applicationManager.sideStageEnabled = Qt.binding(function() { return sideStage.enabled })
72
73 // FIXME: if application focused before shell starts, shell draws on top of it only.
74 // We should detect already running applications on shell start and bring them to the front.
75 applicationManager.unfocusCurrentApplication();
76 }
77
78 Connections {
79 target: applicationManager
80 ignoreUnknownSignals: true
81 onFocusRequested: {
82 // TODO: this should be protected to only unlock for certain applications / certain usecases
83 // potentially only in connection with a notification
84 shell.greeter.hide();
85 activateApplication(desktopFile);
86 }
87 }
88
89 function activateApplication(desktopFile, argument) {
90 if (applicationManager) {
91 // For newly started applications, as it takes them time to draw their first frame
92 // we add a delay before we hide the animation screenshots to compensate.
93 var addDelay = !applicationManager.getApplicationFromDesktopFile(desktopFile);
94
95 var application;
96 application = applicationManager.activateApplication(desktopFile, argument);
97 if (application == null) {
98 return;
99 }
100 if (application.stage == ApplicationInfo.MainStage || !sideStage.enabled) {
101 mainStage.activateApplication(desktopFile, addDelay);
102 } else {
103 sideStage.activateApplication(desktopFile, addDelay);
104 }
105 stages.show();
106 }
107 }
108 Item {
109 id: windowsContainer
110
111 x: leftSwipePosition
112 Behavior on x {SmoothedAnimation{velocity: 600}}
113
114 width: parent.width
115 anchors {
116 top: parent.top
117 bottom: parent.bottom
118 }
119
120 Showable {
121 id: stages
122
123 property bool fullyShown: shown && stages[stagesRevealer.boundProperty] == stagesRevealer.openedValue
124 && parent.x == 0
125 property bool fullyHidden: !shown && stages[stagesRevealer.boundProperty] == stagesRevealer.closedValue
126 available: stageManager.enabled
127 hides: stageManager.hides
128 shown: false
129 opacity: 1.0
130 showAnimation: StandardAnimation { property: "x"; duration: 350; to: stagesRevealer.openedValue; easing.type: Easing.OutCubic }
131 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: stagesRevealer.closedValue; easing.type: Easing.OutCubic }
132
133 width: parent.width
134 height: parent.height
135
136 // close the stages when no focused application remains
137 Connections {
138 target: applicationManager
139 onMainStageFocusedApplicationChanged: stages.closeIfNoApplications()
140 onSideStageFocusedApplicationChanged: stages.closeIfNoApplications()
141 ignoreUnknownSignals: true
142 }
143 Connections {
144 target: applicationManager.mainStageApplications
145 onCountChanged: stages.closeIfNoApplications();
146 }
147 Connections {
148 target: applicationManager.sideStageApplications
149 onCountChanged: stages.closeIfNoApplications();
150 }
151
152 function closeIfNoApplications() {
153 if (!applicationManager.mainStageFocusedApplication
154 && !applicationManager.sideStageFocusedApplication
155 && applicationManager.mainStageApplications.count == 0
156 && applicationManager.sideStageApplications.count == 0) {
157 stages.hide();
158 }
159 }
160
161 // show the stages when an application gets the focus
162 Connections {
163 target: applicationManager
164 onMainStageFocusedApplicationChanged: {
165 if (applicationManager.mainStageFocusedApplication) {
166 mainStage.show();
167 stages.show();
168 }
169 }
170 onSideStageFocusedApplicationChanged: {
171 if (applicationManager.sideStageFocusedApplication) {
172 sideStage.show();
173 stages.show();
174 } }
175 ignoreUnknownSignals: true
176 }
177
178 Stage {
179 id: mainStage
180 objectName: "mainStage"
181
182 anchors.fill: parent
183 fullyShown: stages.fullyShown
184 shouldUseScreenshots: !fullyShown
185 rightEdgeEnabled: !sideStage.enabled
186
187 applicationManager: stageManager.applicationManager
188 rightEdgeDraggingAreaWidth: edgeHandleSize
189 normalApplicationY: panelHeight
190
191 shown: true
192 function show() {
193 stages.show();
194 }
195 function showWithoutAnimation() {
196 stages.showWithoutAnimation();
197 }
198 function hide() {
199 }
200
201 // FIXME: workaround the fact that focusing a main stage application
202 // raises its surface on top of all other surfaces including the ones
203 // that belong to side stage applications.
204 onFocusedApplicationChanged: {
205 if (focusedApplication && sideStage.focusedApplication && sideStage.fullyShown) {
206 applicationManager.focusApplication(sideStage.focusedApplication);
207 }
208 }
209 }
210
211 SideStage {
212 id: sideStage
213 objectName: "sideStage"
214
215 applicationManager: stageManager.applicationManager
216 rightEdgeDraggingAreaWidth: edgeHandleSize
217 normalApplicationY: panelHeight
218
219 onShownChanged: {
220 if (!shown && mainStage.applications.count == 0) {
221 stages.hide();
222 }
223 }
224 // FIXME: when hiding the side stage, refocus the main stage
225 // application so that it goes in front of the side stage
226 // application and hides it
227 onFullyShownChanged: {
228 if (!fullyShown && stages.fullyShown && sideStage.focusedApplication != null) {
229 applicationManager.focusApplication(mainStage.focusedApplication);
230 }
231 }
232
233 enabled: stageManager.width >= units.gu(60)
234 visible: enabled
235 fullyShown: stages.fullyShown && shown
236 && sideStage[sideStageRevealer.boundProperty] == sideStageRevealer.openedValue
237 shouldUseScreenshots: !fullyShown || mainStage.usingScreenshots || sideStageRevealer.pressed
238
239 available: stageManager.enabled && enabled
240 hides: stageManager.hides
241 shown: false
242 showAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.openedValue; easing.type: Easing.OutQuint }
243 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.closedValue; easing.type: Easing.OutQuint }
244
245 width: units.gu(40)
246 height: stages.height
247 handleExpanded: sideStageRevealer.pressed
248 }
249
250 Revealer {
251 id: sideStageRevealer
252
253 enabled: mainStage.applications.count > 0 && sideStage.applications.count > 0
254 && sideStage.available
255 direction: Qt.RightToLeft
256 openedValue: parent.width - sideStage.width
257 hintDisplacement: units.gu(3)
258 /* The size of the sidestage handle needs to be bigger than the
259 typical size used for edge detection otherwise it is really
260 hard to grab.
261 */
262 handleSize: sideStage.shown ? units.gu(4) : edgeHandleSize
263 closedValue: parent.width + sideStage.handleSizeCollapsed
264 target: sideStage
265 x: parent.width - width
266 width: sideStage.width + handleSize * 0.7
267 height: sideStage.height
268 orientation: Qt.Horizontal
269 }
270 }
271 }
272
273 Revealer {
274 id: stagesRevealer
275
276 enabled: mainStage.applications.count > 0 || sideStage.applications.count > 0
277 direction: Qt.RightToLeft
278 openedValue: 0
279 hintDisplacement: units.gu(3)
280 handleSize: edgeHandleSize
281 closedValue: width
282 target: stages
283 width: stages.width
284 height: stages.height
285 orientation: Qt.Horizontal
286 }
287}
0288
=== renamed directory 'SideStage/graphics' => 'Stages/graphics'
=== modified file 'debian/unity8.install'
--- debian/unity8.install 2013-06-05 22:03:08 +0000
+++ debian/unity8.install 2013-06-13 14:42:44 +0000
@@ -11,7 +11,7 @@
11/usr/share/unity8/Notifications/*11/usr/share/unity8/Notifications/*
12/usr/share/unity8/Panel/*12/usr/share/unity8/Panel/*
13/usr/share/unity8/Shell.qml13/usr/share/unity8/Shell.qml
14/usr/share/unity8/SideStage/*14/usr/share/unity8/Stages/*
15/usr/share/unity8/graphics/*15/usr/share/unity8/graphics/*
16/usr/share/unity8/plugins/HudClient/*16/usr/share/unity8/plugins/HudClient/*
17/usr/share/unity8/plugins/LightDM/*17/usr/share/unity8/plugins/LightDM/*
1818
=== modified file 'tests/mocks/Ubuntu/Application/ApplicationListModel.cpp'
--- tests/mocks/Ubuntu/Application/ApplicationListModel.cpp 2013-06-05 22:03:08 +0000
+++ tests/mocks/Ubuntu/Application/ApplicationListModel.cpp 2013-06-13 14:42:44 +0000
@@ -64,8 +64,8 @@
6464
65void ApplicationListModel::add(ApplicationInfo* application)65void ApplicationListModel::add(ApplicationInfo* application)
66{66{
67 beginInsertRows(QModelIndex(), m_applications.size(), m_applications.size());67 beginInsertRows(QModelIndex(), 0, 0);
68 m_applications.append(application);68 m_applications.prepend(application);
69 endInsertRows();69 endInsertRows();
70 Q_EMIT countChanged();70 Q_EMIT countChanged();
71}71}
7272
=== modified file 'tests/mocks/Ubuntu/Application/ApplicationManager.cpp'
--- tests/mocks/Ubuntu/Application/ApplicationManager.cpp 2013-06-05 22:03:08 +0000
+++ tests/mocks/Ubuntu/Application/ApplicationManager.cpp 2013-06-13 14:42:44 +0000
@@ -24,6 +24,21 @@
24#include <QQuickItem>24#include <QQuickItem>
25#include <QQuickView>25#include <QQuickView>
26#include <QQmlComponent>26#include <QQmlComponent>
27#include <QWaitCondition>
28#include <QMutex>
29
30struct Sleeper {
31 QMutex mutex;
32 QWaitCondition sleeper;
33
34 Sleeper() { mutex.lock(); }
35 ~Sleeper() { mutex.unlock(); }
36
37 void sleep(unsigned long duration)
38 {
39 sleeper.wait(&mutex, duration);
40 }
41};
2742
28ApplicationManager::ApplicationManager(QObject *parent)43ApplicationManager::ApplicationManager(QObject *parent)
29 : QObject(parent)44 : QObject(parent)
@@ -35,10 +50,11 @@
35 , m_mainStage(0)50 , m_mainStage(0)
36 , m_sideStageComponent(0)51 , m_sideStageComponent(0)
37 , m_sideStage(0)52 , m_sideStage(0)
53 , m_quickView(0)
38{54{
39 buildListOfAvailableApplications();55 buildListOfAvailableApplications();
40 createMainStageComponent();56
41 createSideStageComponent();57 QGuiApplication::instance()->installEventFilter(this);
42}58}
4359
44ApplicationManager::~ApplicationManager()60ApplicationManager::~ApplicationManager()
@@ -47,6 +63,19 @@
47 delete m_sideStageApplications;63 delete m_sideStageApplications;
48}64}
4965
66bool ApplicationManager::eventFilter(QObject *object, QEvent *event)
67{
68 // best to wait for this event before locating the view
69 if (!m_quickView && (event->type() == QEvent::ApplicationActivate)) {
70 m_quickView = qobject_cast<QQuickView*>(QGuiApplication::topLevelWindows()[0]);
71
72 createMainStageComponent();
73 createSideStageComponent();
74 }
75
76 return QObject::eventFilter(object, event);
77}
78
50int ApplicationManager::keyboardHeight() const79int ApplicationManager::keyboardHeight() const
51{80{
52 return 0;81 return 0;
@@ -92,6 +121,22 @@
92 return m_sideStageFocusedApplication;121 return m_sideStageFocusedApplication;
93}122}
94123
124void ApplicationManager::setMainStageFocusedApplication(ApplicationInfo *app)
125{
126 if (app != m_mainStageFocusedApplication) {
127 m_mainStageFocusedApplication = app;
128 Q_EMIT mainStageFocusedApplicationChanged();
129 }
130}
131
132void ApplicationManager::setSideStageFocusedApplication(ApplicationInfo *app)
133{
134 if (app != m_sideStageFocusedApplication) {
135 m_sideStageFocusedApplication = app;
136 Q_EMIT sideStageFocusedApplicationChanged();
137 }
138}
139
95ApplicationInfo* ApplicationManager::startProcess(QString desktopFile,140ApplicationInfo* ApplicationManager::startProcess(QString desktopFile,
96 ExecFlags flags,141 ExecFlags flags,
97 QStringList arguments)142 QStringList arguments)
@@ -112,9 +157,13 @@
112157
113 if (flags.testFlag(ApplicationManager::ForceMainStage)158 if (flags.testFlag(ApplicationManager::ForceMainStage)
114 || application->stage() == ApplicationInfo::MainStage) {159 || application->stage() == ApplicationInfo::MainStage) {
115 m_mainStageApplications->add(application);160 if (!m_mainStageApplications->contains(application)) {
161 m_mainStageApplications->add(application);
162 }
116 } else {163 } else {
117 m_sideStageApplications->add(application);164 if (!m_sideStageApplications->contains(application)) {
165 m_sideStageApplications->add(application);
166 }
118 }167 }
119168
120 return application;169 return application;
@@ -123,18 +172,18 @@
123void ApplicationManager::stopProcess(ApplicationInfo* application)172void ApplicationManager::stopProcess(ApplicationInfo* application)
124{173{
125 if (m_mainStageApplications->contains(application)) {174 if (m_mainStageApplications->contains(application)) {
175 application->hideWindow();
126 m_mainStageApplications->remove(application);176 m_mainStageApplications->remove(application);
127177
128 if (m_mainStageFocusedApplication == application) {178 if (m_mainStageFocusedApplication == application) {
129 m_mainStageFocusedApplication = 0;179 setMainStageFocusedApplication(0);
130 Q_EMIT mainStageFocusedApplicationChanged();
131 }180 }
132 } else if (m_sideStageApplications->contains(application)){181 } else if (m_sideStageApplications->contains(application)){
182 application->hideWindow();
133 m_sideStageApplications->remove(application);183 m_sideStageApplications->remove(application);
134184
135 if (m_sideStageFocusedApplication == application) {185 if (m_sideStageFocusedApplication == application) {
136 m_sideStageFocusedApplication = 0;186 setSideStageFocusedApplication(0);
137 Q_EMIT sideStageFocusedApplicationChanged();
138 }187 }
139 }188 }
140}189}
@@ -146,14 +195,13 @@
146 if (application->handle() == handle) {195 if (application->handle() == handle) {
147 if (m_mainStageFocusedApplication)196 if (m_mainStageFocusedApplication)
148 m_mainStageFocusedApplication->hideWindow();197 m_mainStageFocusedApplication->hideWindow();
149 m_mainStageFocusedApplication = application;
150 if (!m_mainStage)198 if (!m_mainStage)
151 createMainStage();199 createMainStage();
152 application->showWindow(m_mainStage);200 application->showWindow(m_mainStage);
153 m_mainStage->setZ(-1000);201 m_mainStage->setZ(-1000);
154 if (m_sideStage)202 if (m_sideStage)
155 m_sideStage->setZ(-2000);203 m_sideStage->setZ(-2000);
156 Q_EMIT mainStageFocusedApplicationChanged();204 setMainStageFocusedApplication(application);
157 return;205 return;
158 }206 }
159 }207 }
@@ -163,14 +211,13 @@
163 if (application->handle() == handle) {211 if (application->handle() == handle) {
164 if (m_sideStageFocusedApplication)212 if (m_sideStageFocusedApplication)
165 m_sideStageFocusedApplication->hideWindow();213 m_sideStageFocusedApplication->hideWindow();
166 m_sideStageFocusedApplication = application;
167 if (!m_sideStage)214 if (!m_sideStage)
168 createSideStage();215 createSideStage();
169 application->showWindow(m_sideStage);216 application->showWindow(m_sideStage);
170 m_sideStage->setZ(-1000);217 m_sideStage->setZ(-1000);
171 if (m_mainStage)218 if (m_mainStage)
172 m_mainStage->setZ(-2000);219 m_mainStage->setZ(-2000);
173 Q_EMIT sideStageFocusedApplicationChanged();220 setSideStageFocusedApplication(application);
174 return;221 return;
175 }222 }
176 }223 }
@@ -181,14 +228,12 @@
181 if (stageHint == SideStage) {228 if (stageHint == SideStage) {
182 if (m_sideStageFocusedApplication) {229 if (m_sideStageFocusedApplication) {
183 m_sideStageFocusedApplication->hideWindow();230 m_sideStageFocusedApplication->hideWindow();
184 m_sideStageFocusedApplication = 0;231 setSideStageFocusedApplication(0);
185 Q_EMIT sideStageFocusedApplicationChanged();
186 }232 }
187 } else {233 } else {
188 if (m_mainStageFocusedApplication) {234 if (m_mainStageFocusedApplication) {
189 m_mainStageFocusedApplication->hideWindow();235 m_mainStageFocusedApplication->hideWindow();
190 m_mainStageFocusedApplication = 0;236 setMainStageFocusedApplication(0);
191 Q_EMIT mainStageFocusedApplicationChanged();
192 }237 }
193 }238 }
194}239}
@@ -384,11 +429,7 @@
384429
385void ApplicationManager::createMainStageComponent()430void ApplicationManager::createMainStageComponent()
386{431{
387 // The assumptions I make here really should hold.432 QQmlEngine *engine = m_quickView->engine();
388 QQuickView *quickView =
389 qobject_cast<QQuickView*>(QGuiApplication::topLevelWindows()[0]);
390
391 QQmlEngine *engine = quickView->engine();
392433
393 m_mainStageComponent = new QQmlComponent(engine, this);434 m_mainStageComponent = new QQmlComponent(engine, this);
394 QString mainStageQml =435 QString mainStageQml =
@@ -403,11 +444,12 @@
403444
404void ApplicationManager::createMainStage()445void ApplicationManager::createMainStage()
405{446{
406 // The assumptions I make here really should hold.447 Sleeper sleeper;
407 QQuickView *quickView =448 while (m_quickView->status() != QQuickView::Ready) {
408 qobject_cast<QQuickView*>(QGuiApplication::topLevelWindows()[0]);449 sleeper.sleep(500);
450 }
409451
410 QQuickItem *shell = quickView->rootObject();452 QQuickItem *shell = m_quickView->rootObject();
411453
412 m_mainStage = qobject_cast<QQuickItem *>(m_mainStageComponent->create());454 m_mainStage = qobject_cast<QQuickItem *>(m_mainStageComponent->create());
413 m_mainStage->setParentItem(shell);455 m_mainStage->setParentItem(shell);
@@ -415,11 +457,7 @@
415457
416void ApplicationManager::createSideStageComponent()458void ApplicationManager::createSideStageComponent()
417{459{
418 // The assumptions I make here really should hold.460 QQmlEngine *engine = m_quickView->engine();
419 QQuickView *quickView =
420 qobject_cast<QQuickView*>(QGuiApplication::topLevelWindows()[0]);
421
422 QQmlEngine *engine = quickView->engine();
423461
424 m_sideStageComponent = new QQmlComponent(engine, this);462 m_sideStageComponent = new QQmlComponent(engine, this);
425 QString sideStageQml =463 QString sideStageQml =
@@ -437,11 +475,12 @@
437475
438void ApplicationManager::createSideStage()476void ApplicationManager::createSideStage()
439{477{
440 // The assumptions I make here really should hold.478 Sleeper sleeper;
441 QQuickView *quickView =479 while (m_quickView->status() != QQuickView::Ready) {
442 qobject_cast<QQuickView*>(QGuiApplication::topLevelWindows()[0]);480 sleeper.sleep(500);
481 }
443482
444 QQuickItem *shell = quickView->rootObject();483 QQuickItem *shell = m_quickView->rootObject();
445484
446 m_sideStage = qobject_cast<QQuickItem *>(m_sideStageComponent->create());485 m_sideStage = qobject_cast<QQuickItem *>(m_sideStageComponent->create());
447 m_sideStage->setParentItem(shell);486 m_sideStage->setParentItem(shell);
448487
=== modified file 'tests/mocks/Ubuntu/Application/ApplicationManager.h'
--- tests/mocks/Ubuntu/Application/ApplicationManager.h 2013-06-05 22:03:08 +0000
+++ tests/mocks/Ubuntu/Application/ApplicationManager.h 2013-06-13 14:42:44 +0000
@@ -24,6 +24,8 @@
24#include "ApplicationInfo.h"24#include "ApplicationInfo.h"
2525
26class QQuickItem;26class QQuickItem;
27class QQuickView;
28class QEvent;
2729
28class ApplicationManager : public QObject {30class ApplicationManager : public QObject {
29 Q_OBJECT31 Q_OBJECT
@@ -95,6 +97,12 @@
95 void sideStageFocusedApplicationChanged();97 void sideStageFocusedApplicationChanged();
96 void focusRequested(FavoriteApplication favoriteApplication);98 void focusRequested(FavoriteApplication favoriteApplication);
9799
100protected:
101 bool eventFilter(QObject *, QEvent *);
102
103 void setMainStageFocusedApplication(ApplicationInfo *);
104 void setSideStageFocusedApplication(ApplicationInfo *);
105
98 private:106 private:
99 void showApplicationWindow(ApplicationInfo *application);107 void showApplicationWindow(ApplicationInfo *application);
100 void buildListOfAvailableApplications();108 void buildListOfAvailableApplications();
@@ -114,6 +122,7 @@
114 QQuickItem *m_mainStage;122 QQuickItem *m_mainStage;
115 QQmlComponent *m_sideStageComponent;123 QQmlComponent *m_sideStageComponent;
116 QQuickItem *m_sideStage;124 QQuickItem *m_sideStage;
125 QQuickView *m_quickView;
117};126};
118127
119Q_DECLARE_OPERATORS_FOR_FLAGS(ApplicationManager::ExecFlags)128Q_DECLARE_OPERATORS_FOR_FLAGS(ApplicationManager::ExecFlags)
120129
=== modified file 'tests/qmltests/CMakeLists.txt'
--- tests/qmltests/CMakeLists.txt 2013-06-12 16:10:47 +0000
+++ tests/qmltests/CMakeLists.txt 2013-06-13 14:42:44 +0000
@@ -32,9 +32,6 @@
32add_qml_test(Components ResponsiveGridView)32add_qml_test(Components ResponsiveGridView)
33add_qml_test(Components Revealer)33add_qml_test(Components Revealer)
34add_qml_test(Components Showable)34add_qml_test(Components Showable)
35add_qml_test(Components Stage IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS}
36 ${CMAKE_BINARY_DIR}/tests/mocks
37 ${CMAKE_BINARY_DIR}/plugins)
38add_qml_test(Components Tile)35add_qml_test(Components Tile)
39add_qml_test(Components PageHeader)36add_qml_test(Components PageHeader)
40add_qml_test(Dash Dash IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})37add_qml_test(Dash Dash IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})
@@ -67,6 +64,7 @@
67add_qml_test(Panel Panel)64add_qml_test(Panel Panel)
68add_qml_test(Panel SearchIndicator)65add_qml_test(Panel SearchIndicator)
69add_qml_test(Panel/Menus IndicatorMenuWindow IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS})66add_qml_test(Panel/Menus IndicatorMenuWindow IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS})
70add_qml_test(SideStage SideStage IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS}67add_qml_test(Stages Stage IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/tests/mocks ${CMAKE_BINARY_DIR}/plugins)
71 ${CMAKE_BINARY_DIR}/tests/mocks68add_qml_test(Stages StageManager-phone IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/tests/mocks ${CMAKE_BINARY_DIR}/plugins)
72 ${CMAKE_BINARY_DIR}/plugins)69add_qml_test(Stages StageManager-tablet IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/tests/mocks ${CMAKE_BINARY_DIR}/plugins)
70add_qml_test(Stages SideStage IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/tests/mocks ${CMAKE_BINARY_DIR}/plugins)
7371
=== modified file 'tests/qmltests/Dash/Apps/tst_RunningApplicationsGrid.qml'
--- tests/qmltests/Dash/Apps/tst_RunningApplicationsGrid.qml 2013-06-05 22:03:08 +0000
+++ tests/qmltests/Dash/Apps/tst_RunningApplicationsGrid.qml 2013-06-13 14:42:44 +0000
@@ -128,6 +128,7 @@
128 compare(runningApplicationsGrid.terminationModeEnabled, false)128 compare(runningApplicationsGrid.terminationModeEnabled, false)
129129
130 isCalendarLongPressed = false130 isCalendarLongPressed = false
131 waitForRendering(runningApplicationsGrid)
131 mousePress(calendarTile, calendarTile.width/2, calendarTile.height/2)132 mousePress(calendarTile, calendarTile.width/2, calendarTile.height/2)
132 tryCompareFunction(checkSwitchToTerminationModeAfterLongPress, true)133 tryCompareFunction(checkSwitchToTerminationModeAfterLongPress, true)
133134
@@ -186,6 +187,7 @@
186 verify(calendarTile != undefined)187 verify(calendarTile != undefined)
187188
188 verify(fakeRunningAppsModel.contains(calendarApp))189 verify(fakeRunningAppsModel.contains(calendarApp))
190 waitForRendering(runningApplicationsGrid) //ensure populating animation has stopped
189191
190 mouseClick(calendarTile, calendarTile.width/2, calendarTile.height/2)192 mouseClick(calendarTile, calendarTile.width/2, calendarTile.height/2)
191193
192194
=== removed directory 'tests/qmltests/SideStage'
=== added directory 'tests/qmltests/Stages'
=== renamed file 'tests/qmltests/SideStage/tst_SideStage.qml' => 'tests/qmltests/Stages/tst_SideStage.qml'
--- tests/qmltests/SideStage/tst_SideStage.qml 2013-06-05 22:03:08 +0000
+++ tests/qmltests/Stages/tst_SideStage.qml 2013-06-13 14:42:44 +0000
@@ -18,7 +18,7 @@
18import QtTest 1.018import QtTest 1.0
19import Unity.Test 0.1 as UT19import Unity.Test 0.1 as UT
20import ".."20import ".."
21import "../../../SideStage"21import "../../../Stages"
22import Ubuntu.Components 0.122import Ubuntu.Components 0.1
2323
24UT.UnityTestCase {24UT.UnityTestCase {
2525
=== renamed directory 'tests/qmltests/Components/tst_Stage' => 'tests/qmltests/Stages/tst_Stage'
=== renamed file 'tests/qmltests/Components/tst_Stage.qml' => 'tests/qmltests/Stages/tst_Stage.qml'
--- tests/qmltests/Components/tst_Stage.qml 2013-06-10 16:50:18 +0000
+++ tests/qmltests/Stages/tst_Stage.qml 2013-06-13 14:42:44 +0000
@@ -17,7 +17,7 @@
17import QtQuick 2.017import QtQuick 2.0
18import QtTest 1.018import QtTest 1.0
19import ".."19import ".."
20import "../../../Components"20import "../../../Stages"
21import Ubuntu.Application 0.121import Ubuntu.Application 0.1
22import Ubuntu.Components 0.122import Ubuntu.Components 0.1
23import Unity.Test 0.1 as UT23import Unity.Test 0.1 as UT
2424
=== added directory 'tests/qmltests/Stages/tst_StageManager'
=== added file 'tests/qmltests/Stages/tst_StageManager-phone.qml'
--- tests/qmltests/Stages/tst_StageManager-phone.qml 1970-01-01 00:00:00 +0000
+++ tests/qmltests/Stages/tst_StageManager-phone.qml 2013-06-13 14:42:44 +0000
@@ -0,0 +1,255 @@
1/*
2 * Copyright 2013 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.0
18import Ubuntu.Application 0.1
19import "../../../Stages"
20import "../../../Components"
21import "tst_StageManager"
22
23Item {
24 id: root
25 width: units.gu(40)
26 height: units.gu(72)
27
28 property var applicationManager: ApplicationManagerWrapper {}
29
30 Rectangle { //fake background
31 anchors.fill: parent
32 color: "black"
33 visible: stageManager.needUnderlay
34 }
35
36 StageManager {
37 id: stageManager
38
39 anchors.fill: parent
40
41 applicationManager: root.applicationManager
42 leftSwipePosition: 0
43 panelHeight: units.gu(3) + units.dp(2)
44 edgeHandleSize: units.gu(2)
45 enabled: true
46 }
47
48 ListSelector {
49 id: applications
50 anchors {
51 left: parent.left
52 right: parent.right
53 bottom: parent.bottom
54 }
55
56 list: ["phone-app", "gallery-app", "camera-app"]
57 onActivated: stageManager.activateApplication(desktopFileOf(entry))
58 onDeactivated: applicationManager.stopProcess(applicationManager.getApplicationFromDesktopFile(desktopFileOf(entry)))
59 }
60
61 function desktopFileOf(entry) {
62 return "/usr/share/applications/" + entry + ".desktop"
63 }
64
65 StageManagerTestCase {
66 name: "StageManager - phone - 0 window case"
67 when: windowShown
68 stageManagerUnderTest: stageManager
69
70 // left swipe does nothing when no applications open
71 function test_leftEdgeSwipeDisabledWithNoApplicationsOpen() {
72 rightEdgeSwipe()
73 checkStageManagerOffScreen()
74 }
75
76 function test_stageManagerOffScreenByDefault() {
77 checkStageManagerOffScreen()
78 }
79 }
80
81 StageManagerTestCase {
82 name: "StageManager - phone - 1 window case"
83 when: windowShown
84 stageManagerUnderTest: stageManager
85
86 function init() {
87 applications.activate("phone-app")
88 waitForAnimationsToFinish()
89 }
90
91 // When application started and StageManager hidden, StageManager shows immediately
92 function test_onLaunchStageManagerShown() {
93 checkStageManagerOnScreen()
94 }
95
96 // Left-edge swipe the StageManager away animates the StageManager away
97 function test_leftSwipeHidesStageManager() {
98 leftEdgeSwipe()
99 checkStageManagerOffScreen()
100 }
101
102 // Left-edge swipe the StageManager away unfocuses the application
103 function test_leftSwipeUnfocusesApplication() {
104 leftEdgeSwipe()
105 checkStageManagerOffScreen()
106
107 skip("FIXME: mainStageFocusedApplication not updated when application unfocused, see lp:1186980")
108 tryCompare(applicationManager, "mainStageFocusedApplication", null)
109 }
110
111 // Left-edge swipe StageManager away. Right-edge swipe should restore it
112 function test_rightSwipeRestoresApplication() {
113 leftEdgeSwipe()
114 checkStageManagerOffScreen()
115
116 rightEdgeSwipe()
117 checkStageManagerOnScreen()
118
119 waitForAnimationsToFinish()
120
121 tryCompare(applicationManager.mainStageFocusedApplication, "desktopFile",
122 desktopFileOf("phone-app"))
123 }
124
125 // Activate application. Left-edge swipe stages away. Use launcher to activate application
126 // again - the StageManager should slide in and focus application
127 function test_activateApplicationWhenStageHiddenRevealsApplication() {
128 leftEdgeSwipe()
129 checkStageManagerOffScreen()
130
131 applications.activate("phone-app")
132 checkStageManagerOnScreen()
133
134 waitForAnimationsToFinish()
135
136 tryCompare(applicationManager.mainStageFocusedApplication, "desktopFile",
137 desktopFileOf("phone-app"))
138 }
139
140 // Kill focused application. StageManager should hide
141 function test_killingApplicationShouldHideStageManager() {
142 applications.deactivate("phone-app")
143
144 checkStageManagerOffScreen()
145 }
146
147 // Starting a second app while StageManager on-screen does nothing to StageManager
148 function test_openingSecondAppWhileStageManagerOnScreen(){
149 applications.activate("gallery-app")
150
151 checkStageManagerOnScreen()
152
153 waitForAnimationsToFinish()
154
155 tryCompare(applicationManager.mainStageFocusedApplication, "desktopFile",
156 desktopFileOf("gallery-app"))
157 }
158
159 // Starting a second app while StageManager off-screen shows StageManager
160 function test_openingSecondAppWhileStageManagerOffScreen(){
161 leftEdgeSwipe()
162
163 applications.activate("gallery-app")
164 checkStageManagerOnScreen()
165
166 waitForAnimationsToFinish()
167
168 tryCompare(applicationManager.mainStageFocusedApplication, "desktopFile",
169 desktopFileOf("gallery-app"))
170 }
171
172 // Application focus change after animations complete
173 function test_onLaunchApplicationGetsFocusedOnlyAfterAnimation() {
174 applications.activate("gallery-app")
175
176 verify(applicationManager.mainStageFocusedApplication.desktopFile !== desktopFileOf("gallery-app"))
177
178 waitForAnimationsToFinish()
179
180 compare(applicationManager.mainStageFocusedApplication.desktopFile, desktopFileOf("gallery-app"),
181 "Focus not on newly activated application after animation")
182 }
183 }
184
185 StageManagerTestCase {
186 name: "StageManager - phone - 2 window case"
187 when: windowShown
188 stageManagerUnderTest: stageManager
189
190 function init() {
191 applications.activate("phone-app")
192 applications.activate("camera-app")
193 waitForAnimationsToFinish()
194 }
195
196 function test_focusOrder() {
197 compare(applicationManager.mainStageFocusedApplication.desktopFile, desktopFileOf("camera-app"),
198 "Focus not on last launched application")
199 }
200
201 // Check hiding and then revealing StageManager doesn't change the focus order
202 function test_focusOrderAfterUnfocus() {
203 leftEdgeSwipe()
204
205 rightEdgeSwipe()
206 waitForAnimationsToFinish()
207
208 compare(applicationManager.mainStageFocusedApplication.desktopFile, desktopFileOf("camera-app"),
209 "Focus order changed somehow by hide/reveal of StageManager")
210 }
211
212 // Check that a hidden StageManager reacts correctly when background application activated
213 function test_focusOrderWhenActivating() {
214 leftEdgeSwipe()
215
216 applications.activate("phone-app")
217
218 waitForAnimationsToFinish()
219 checkStageManagerOnScreen()
220
221 tryCompare(applicationManager.mainStageFocusedApplication, "desktopFile", desktopFileOf("phone-app"))
222
223 compare(applicationManager.mainStageFocusedApplication.desktopFile, desktopFileOf("phone-app"),
224 "Focus not on newly activated application when StageManager hidden")
225 }
226
227 // If the focused application dies, the StageManager should hide
228 function test_foregroundApplicationDeathDismissesStageManager() {
229 tryCompare(applicationManager.mainStageFocusedApplication, "desktopFile", desktopFileOf("camera-app"))
230
231 skip("FIXME: StageManager does not hide when foreground application dies")
232 applications.deactivate("camera-app")
233
234 checkStageManagerOffScreen()
235 }
236
237 // If a background application dies, the StageManager should not react
238 function test_nonForegroundApplicationDeathDoesNothing() {
239 tryCompare(applicationManager.mainStageFocusedApplication, "desktopFile", desktopFileOf("camera-app"))
240 applications.deactivate("phone-app")
241
242 checkStageManagerOnScreen()
243 }
244
245 function test_fullScreenMode() {
246 tryCompare(applicationManager.mainStageFocusedApplication, "desktopFile", desktopFileOf("camera-app"))
247
248 tryCompare(stageManager, "fullscreenMode", true)
249
250 rightEdgeSwipe()
251
252 tryCompare(stageManager, "fullscreenMode", false)
253 }
254 }
255}
0256
=== added file 'tests/qmltests/Stages/tst_StageManager-tablet.qml'
--- tests/qmltests/Stages/tst_StageManager-tablet.qml 1970-01-01 00:00:00 +0000
+++ tests/qmltests/Stages/tst_StageManager-tablet.qml 2013-06-13 14:42:44 +0000
@@ -0,0 +1,394 @@
1/*
2 * Copyright 2013 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.0
18import Ubuntu.Application 0.1
19import "../../../Stages"
20import "../../../Components"
21import "tst_StageManager"
22
23Item {
24 id: root
25 width: units.gu(100)
26 height: units.gu(72)
27
28 property var applicationManager: ApplicationManagerWrapper {}
29
30 Rectangle { //fake background
31 anchors.fill: parent
32 color: "black"
33 visible: stageManager.needUnderlay
34 }
35
36 StageManager {
37 id: stageManager
38
39 anchors.fill: parent
40
41 applicationManager: root.applicationManager
42 leftSwipePosition: 0
43 panelHeight: units.gu(3) + units.dp(2)
44 edgeHandleSize: units.gu(2)
45 enabled: true
46 }
47
48 ListSelector {
49 id: applications
50 anchors {
51 left: parent.left
52 right: parent.right
53 bottom: parent.bottom
54 }
55
56 list: ["phone-app", "facebook-webapp", "gallery-app"]
57 onActivated: stageManager.activateApplication(desktopFileOf(entry))
58 onDeactivated: applicationManager.stopProcess(applicationManager.getApplicationFromDesktopFile(desktopFileOf(entry)))
59 }
60
61 function desktopFileOf(entry) {
62 return "/usr/share/applications/" + entry + ".desktop"
63 }
64
65 StageManagerTestCase {
66 name: "StageManager - tablet - 1 main-stage window case"
67 when: windowShown
68 stageManagerUnderTest: stageManager
69
70 function init() {
71 applications.activate("gallery-app") //main-stage app
72 waitForAnimationsToFinish()
73 }
74
75 // Right-edge press does not change main-stage app
76 function test_rightEdgeSwipeDoesNotChangeMainStageApp() {
77 rightEdgePress()
78 waitForRendering(stageManager)
79 compare(mainStage.oldApplicationScreenshot.visible, false) // i.e. no screenshot animation occurs
80 }
81 }
82
83 StageManagerTestCase {
84 name: "StageManager - tablet - 1 side-stage window case"
85 when: windowShown
86 stageManagerUnderTest: stageManager
87
88 function init() {
89 applications.activate("phone-app") // side-stage app
90 waitForAnimationsToFinish()
91 }
92
93 // Check loaded as side-stage application
94 function test_sideStage() {
95 tryCompare(applicationManager.sideStageFocusedApplication, "desktopFile", desktopFileOf("phone-app"))
96 compare(applicationManager.mainStageFocusedApplication, null)
97 }
98
99 // When application started and StageManager hidden, StageManager shows immediately
100 function test_onLaunchStageManagerShown() {
101 checkStageManagerOnScreen()
102 }
103
104 // Check side-stage positioned correctly
105 function test_sideStagePositionedCorrectly() {
106 compare(sideStage.x, stageManager.width - units.gu(40), "Sidestage not positioned correctly")
107 }
108
109 // Left-edge swipe the StageManager away animates the StageManager away
110 function test_leftSwipeHidesStageManager() {
111 leftEdgeSwipe()
112 checkStageManagerOffScreen()
113 }
114
115 // Left-edge swipe the StageManager away unfocuses the application
116 function test_leftSwipeUnfocusesApplication() {
117 leftEdgeSwipe()
118 checkStageManagerOffScreen()
119
120 skip("FIXME: sideStageFocusedApplication not updated when application unfocused, see lp:1186980")
121 tryCompare(applicationManager, "sideStageFocusedApplication", null)
122 }
123
124 // Left-edge swipe StageManager away. Right-edge swipe should restore it
125 function test_rightSwipeRestoresApplication() {
126 leftEdgeSwipe()
127 checkStageManagerOffScreen()
128
129 rightEdgeSwipe()
130 checkStageManagerOnScreen()
131
132 waitForAnimationsToFinish()
133
134 tryCompare(applicationManager.sideStageFocusedApplication, "desktopFile", desktopFileOf("phone-app"))
135 }
136
137 // Activate application. Left-edge swipe stages away. Use launcher to activate application
138 // again - the StageManager should slide in and focus application
139 function test_activateApplicationWhenStageHiddenRevealsApplication() {
140 leftEdgeSwipe()
141 checkStageManagerOffScreen()
142
143 applications.activate("phone-app")
144 checkStageManagerOnScreen()
145
146 waitForAnimationsToFinish()
147
148 tryCompare(applicationManager.sideStageFocusedApplication, "desktopFile", desktopFileOf("phone-app"))
149 }
150
151 // Kill focused application. StageManager should hide
152 function test_killingApplicationShouldHideStageManager() {
153 applications.deactivate("phone-app")
154
155 checkStageManagerOffScreen()
156 }
157
158 // Starting a second side-stage app while StageManager on-screen does nothing to StageManager
159 function test_openingSecondSideStageAppWhileStageManagerOnScreen(){
160 applications.activate("facebook-webapp") //side-stage app
161 checkStageManagerOnScreen()
162
163 waitForAnimationsToFinish()
164 tryCompare(applicationManager.sideStageFocusedApplication, "desktopFile", desktopFileOf("facebook-webapp"))
165 }
166
167 // Starting a second side-stage app while StageManager off-screen shows StageManager
168 function test_openingSecondSideStageAppWhileStageManagerOffScreen(){
169 leftEdgeSwipe()
170
171 applications.activate("facebook-webapp") //side-stage app
172 checkStageManagerOnScreen()
173
174 waitForAnimationsToFinish()
175 tryCompare(applicationManager.sideStageFocusedApplication, "desktopFile", desktopFileOf("facebook-webapp"))
176 }
177
178
179 // Starting a main-stage app while StageManager off-screen shows StageManager
180 function test_openingMainStageAppWhileStageManagerOffScreen(){
181 leftEdgeSwipe()
182 checkStageManagerOffScreen()
183
184 applications.activate("gallery-app") //main-stage app
185 checkStageManagerOnScreen()
186
187 waitForAnimationsToFinish()
188 tryCompare(applicationManager.mainStageFocusedApplication, "desktopFile", desktopFileOf("gallery-app"))
189 }
190
191 // Application focus change after animations complete
192 function test_onLaunchApplicationGetsFocusedOnlyAfterAnimation() {
193 applications.activate("facebook-webapp") //side-stage app
194
195 verify(applicationManager.sideStageFocusedApplication.desktopFile !== desktopFileOf("facebook-webapp"))
196
197 waitForAnimationsToFinish()
198 compare(applicationManager.sideStageFocusedApplication.desktopFile, desktopFileOf("facebook-webapp"),
199 "Newly activated application does not have focus after animation")
200 }
201
202 // Right-edge press changes side-stage app
203 function test_rightEdgeSwipeChangesSideStageOnly() {
204 rightEdgePress()
205 tryCompareFunction( function() {
206 return sideStage.oldApplicationScreenshot.scale < 1
207 }, true)
208 }
209
210 // Right-swipe of side-stage handle does nothing
211 function test_rightSwipeOfSideStageHandleDoesNothing() {
212 sideStageHandleRightSwipe()
213
214 compare(sideStage.x, stageManager.width - units.gu(40),
215 "Side-stage was incorrectly dismissed when no main-stage application open")
216 }
217 }
218
219 StageManagerTestCase {
220 name: "StageManager - tablet - 1 side-stage & 1 main-stage window case"
221 when: windowShown
222 stageManagerUnderTest: stageManager
223
224 function init() {
225 applications.activate("gallery-app") //main-stage app
226 applications.activate("phone-app") //side-stage app
227 waitForAnimationsToFinish()
228 }
229
230 function test_focusOrder() {
231 compare(applicationManager.mainStageFocusedApplication.desktopFile, desktopFileOf("gallery-app"),
232 "Main-stage application not got focus after activation")
233 compare(applicationManager.sideStageFocusedApplication.desktopFile, desktopFileOf("phone-app"),
234 "Side-stage application not got focus after activation")
235 }
236
237 // Check hiding and then revealing StageManager doesn't change the focus order
238 function test_focusOrderAfterUnfocus() {
239 leftEdgeSwipe()
240
241 rightEdgeSwipe()
242 waitForAnimationsToFinish()
243
244 compare(applicationManager.mainStageFocusedApplication.desktopFile, desktopFileOf("gallery-app"),
245 "Main-stage application lost focus from StageManager hide/show")
246 compare(applicationManager.sideStageFocusedApplication.desktopFile, desktopFileOf("phone-app"),
247 "Side-stage application lost focus from StageManager hide/show")
248 }
249
250 // Check that a hidden StageManager reacts correctly when already-open side-stage application activated
251 function test_focusOrderWhenActivatingSideStage() {
252 leftEdgeSwipe()
253
254 applications.activate("phone-app") //side-stage app
255
256 checkStageManagerOnScreen()
257 }
258
259 // Check that a hidden StageManager reacts correctly when already-open main-stage application activated
260 function test_focusOrderWhenActivatingMainStage() {
261 leftEdgeSwipe()
262
263 applications.activate("gallery-app") //main-stage app
264
265 checkStageManagerOnScreen()
266 }
267
268 // If the main-stage application dies, the StageManager should stay on screen
269 function test_foregroundMainStageApplicationDeathDoesNothing() {
270 applications.deactivate("gallery-app") //main-stage app
271
272 checkStageManagerOnScreen()
273 compare(applicationManager.sideStageFocusedApplication.desktopFile, desktopFileOf("phone-app"),
274 "Side-stage application lost focus after main-stage application died")
275 }
276
277 // If the side-stage application dies, the StageManager should stay on screen
278 function test_foregroundSideStageApplicationDeathDoesNothing() {
279 applications.deactivate("phone-app") //side-stage app
280
281 checkStageManagerOnScreen()
282 compare(applicationManager.mainStageFocusedApplication.desktopFile, desktopFileOf("gallery-app"),
283 "Main-stage application lost focus after side-stage application died")
284 }
285
286 // If the side-stage application dies, the side stage should hide
287 function test_foregroundSideStageApplicationDeathHidesSideStage() {
288 applications.deactivate("phone-app") //side-stage app
289
290 tryCompareFunction( function() {
291 return sideStage.x > stageManager.width
292 }, true)
293 }
294
295 // Right-edge swipe changes side-stage app
296 function test_rightEdgeSwipeChangesSideStage() {
297 tryCompare(sideStage, "x", stageManager.width - sideStage.width) //ensure side-stage open
298
299 waitForAnimationsToFinish()
300 rightEdgePress()
301 waitForRendering(stageManager)
302 tryCompareFunction( function() {
303 return sideStage.oldApplicationScreenshot.scale < 1
304 }, true)
305 }
306
307 // Right-edge swipe does not change main-stage app
308 function test_rightEdgeSwipeDoesNotChangeMainStage() {
309 rightEdgePress()
310 waitForRendering(stageManager)
311 compare(mainStage.oldApplicationScreenshot.scale, 1,
312 "Right-edge swipe animating main-stage while side-stage app open")
313 }
314
315 // Right-swipe of side-stage handle dismisses side-stage
316 function test_rightSwipeOfSideStageHandleHidesSideStage() {
317 sideStageHandleRightSwipe()
318 waitForAnimationsToFinish()
319
320 tryCompare(sideStage, "x", stageManager.width + sideStage.handleSizeCollapsed)
321 }
322
323 // Right-swipe of side-stage unfocuses side-stage app
324 function test_rightSwipeOfSideStageHandleUnfocusesSideStageApp() {
325 tryCompare(sideStage, "x", stageManager.width - sideStage.width) //ensure side-stage open
326
327 sideStageHandleRightSwipe()
328 waitForAnimationsToFinish()
329
330 compare(applicationManager.mainStageFocusedApplication.desktopFile, desktopFileOf("gallery-app"),
331 "Right-edge swipe of side-stage app changed main-stage focus")
332 skip("FIXME: sideStageFocusedApplication not updated when application unfocused, see lp:1186980")
333 //tryCompare(applicationManager, "sideStageFocusedApplication", null)
334 }
335
336 // Hidden side-stage can be right-edge swiped back
337 function test_rightEdgeSwipeRestoresHiddenSideStageApp() {
338 sideStageHandleRightSwipe()
339 waitForAnimationsToFinish()
340
341 rightEdgeSwipe()
342 waitForAnimationsToFinish()
343
344 tryCompare(sideStage, "x", stageManager.width - units.gu(40))
345 }
346
347 // Hidden side-stage app when right-edge swiped has focus restored
348 function test_rightEdgeSwipeRestoresFocusToSideStageApp() {
349 sideStageHandleRightSwipe()
350 waitForAnimationsToFinish()
351
352 rightEdgeSwipe()
353 waitForAnimationsToFinish()
354
355 compare(applicationManager.sideStageFocusedApplication.desktopFile, desktopFileOf("phone-app"),
356 "Side-stage app not returned focus after StageManager un-hidden")
357 compare(applicationManager.mainStageFocusedApplication.desktopFile, desktopFileOf("gallery-app"),
358 "Main-stage app not returned focus after StageManager un-hidden")
359 }
360
361 // Hide side-stage, then activate the side-stage application. It should slide back in
362 function test_activatingHiddenSideStageAppSlidesItIn() {
363 sideStageHandleRightSwipe()
364 waitForAnimationsToFinish()
365
366 applications.activate("phone-app") //side-stage app
367 waitForAnimationsToFinish()
368
369 tryCompare(sideStage, "x", stageManager.width - sideStage.width)
370 }
371
372 // Hide side-stage, then left-swipe to hide whole StageManager. Activate side-stage app
373 // StageManager should slide in, with side-stage app fixed in place (i.e. not sliding independently)
374 function test_activatingHiddenSideStageAppWhenStageManagerHidden() {
375 sideStageHandleRightSwipe()
376 waitForAnimationsToFinish()
377
378 leftEdgeSwipe(units.gu(160))
379 checkStageManagerOffScreen()
380
381 applications.activate("phone-app") //side-stage app
382 tryCompare(sideStage, "x", stageManager.width - units.gu(40))
383 }
384
385 // Starting a main-stage app while StageManager on-screen does nothing to StageManager
386 function test_openingSecondMainStageAppWhileStageManagerOnScreen(){
387 applications.activate("gallery-app") //main-stage app
388 checkStageManagerOnScreen()
389
390 waitForAnimationsToFinish()
391 tryCompare(applicationManager.mainStageFocusedApplication, "desktopFile", desktopFileOf("gallery-app"))
392 }
393 }
394}
0395
=== added file 'tests/qmltests/Stages/tst_StageManager/ListSelector.qml'
--- tests/qmltests/Stages/tst_StageManager/ListSelector.qml 1970-01-01 00:00:00 +0000
+++ tests/qmltests/Stages/tst_StageManager/ListSelector.qml 2013-06-13 14:42:44 +0000
@@ -0,0 +1,101 @@
1/*
2 * Copyright 2013 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.0
18import Ubuntu.Application 0.1
19import Ubuntu.Components 0.1
20
21Rectangle {
22 id: root
23 color: "#ccffffff"
24 height: childrenRect.height
25
26 property alias list: repeater.model
27 signal activated(string entry)
28 signal deactivated(string entry)
29
30 function activate(entry) {
31 lister.changeEntry(entry, true);
32 }
33
34 function deactivate(entry) {
35 lister.changeEntry(entry, false);
36 }
37
38 function deactivateAll() {
39 for (var i=0; i<list.length; i++) {
40 lister.changeEntry(list[i], false);
41 }
42 }
43
44 Flow {
45 id: lister
46 anchors {
47 top: parent.top
48 left: parent.left
49 right: parent.right
50 }
51
52 function changeEntry(entry, setting) {
53 for (var i=0; i<lister.children.length; i++) {
54 var child = lister.children[i];
55 if (child === lister) continue;
56 if (child.label == entry) {
57 if (child.checked == setting) {
58 if (setting) root.activated(child.label); //ensure we fire activated again
59 } else {
60 child.checked = setting;
61 }
62 break;
63 }
64 }
65 }
66 Repeater {
67 id: repeater
68 delegate: entry
69 }
70
71 Component {
72 id: entry
73
74 Row {
75 height: units.gu(5)
76 width: units.gu(20)
77
78 property alias checked: checkbox.checked
79 property string label: modelData
80
81 CheckBox {
82 id: checkbox
83 onCheckedChanged: {
84 if (checked) {
85 activated(modelData)
86 } else {
87 deactivated(modelData)
88 }
89 }
90 }
91 Button {
92 text: parent.label
93 width: parent.width - checkbox.width
94 anchors.verticalCenter: checkbox.verticalCenter
95 enabled: checkbox.checked
96 onClicked: activated(modelData)
97 }
98 }
99 }
100 }
101}
0102
=== added file 'tests/qmltests/Stages/tst_StageManager/StageManagerTestCase.qml'
--- tests/qmltests/Stages/tst_StageManager/StageManagerTestCase.qml 1970-01-01 00:00:00 +0000
+++ tests/qmltests/Stages/tst_StageManager/StageManagerTestCase.qml 2013-06-13 14:42:44 +0000
@@ -0,0 +1,98 @@
1/*
2 * Copyright 2013 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.0
18import QtTest 1.0
19import Unity.Test 0.1 as UT
20import "../../../../Stages"
21
22UT.UnityTestCase {
23 property StageManager stageManagerUnderTest
24 property point __lastMouseEvent: Qt.point(-1, -1)
25 property SideStage sideStage: null
26 property Stage mainStage: null
27
28 onStageManagerUnderTestChanged: {
29 mainStage = findChild(stageManagerUnderTest, "mainStage")
30 sideStage = findChild(stageManagerUnderTest, "sideStage")
31 }
32
33 function cleanup() {
34 resetMouseState()
35 applications.deactivateAll()
36 compare(applicationManager.mainStageApplications.count, 0, "Some main-stage application failed to quit")
37 compare(applicationManager.sideStageApplications.count, 0, "Some side-stage application failed to quit")
38 checkStageManagerOffScreen()
39 }
40
41 function rightEdgePress() {
42 __lastMouseEvent.x = stageManagerUnderTest.width - (stageManagerUnderTest.edgeHandleSize / 2)
43 __lastMouseEvent.y = stageManagerUnderTest.height / 2
44 touchPress(stageManagerUnderTest, __lastMouseEvent.x, __lastMouseEvent.y)
45 }
46
47 function rightEdgeRelease() {
48 touchRelease(stageManagerUnderTest, __lastMouseEvent.x, __lastMouseEvent.y)
49 __lastMouseEvent = Qt.point(-1, -1)
50 }
51
52 function resetMouseState() {
53 if (__lastMouseEvent !== Qt.point(-1, -1)) {
54 rightEdgeRelease()
55 }
56 }
57
58 function waitForAnimationsToFinish() {
59 tryCompare(stageManagerUnderTest, "stageScreenshotsReady", false)
60 }
61
62 function leftEdgeSwipe(distance) {
63 if (distance == undefined) distance = stageManagerUnderTest.width / 3 * 2
64
65 var x = stageManagerUnderTest.edgeHandleSize / 2
66 var y = stageManagerUnderTest.height / 2
67 touchFlick(stageManagerUnderTest, x, y,
68 x + distance, y)
69 }
70
71 function rightEdgeSwipe(distance) {
72 if (distance == undefined) distance = stageManagerUnderTest.width / 3 * 2
73
74 var x = stageManagerUnderTest.width - (stageManagerUnderTest.edgeHandleSize / 2)
75 var y = stageManagerUnderTest.height / 2
76 touchFlick(stageManagerUnderTest, x, y,
77 x - distance, y)
78 }
79
80 function sideStageHandleRightSwipe(distance) {
81 if (distance == undefined) distance = sideStage.width / 3 * 2
82
83 var x = sideStage.x - (sideStage.rightEdgeDraggingAreaWidth / 2)
84 var y = sideStage.height / 2
85 touchFlick(stageManagerUnderTest, x, y,
86 x + distance, y)
87 }
88
89 function checkStageManagerOffScreen() {
90 tryCompare(stageManagerUnderTest, "animatedProgress", 0) //0 means off-screen
91 tryCompare(stageManagerUnderTest, "shown", false)
92 }
93
94 function checkStageManagerOnScreen() {
95 tryCompare(stageManagerUnderTest, "animatedProgress", 1) //1 means on-screen
96 tryCompare(stageManagerUnderTest, "shown", true)
97 }
98}

Subscribers

People subscribed via source and target branches

to all changes: