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

Proposed by Daniel d'Andrada
Status: Merged
Approved by: Michał Sawicz
Approved revision: 2360
Merged at revision: 2336
Proposed branch: lp:~dandrader/unity8/surfaceListModel
Merge into: lp:unity8
Prerequisite: lp:~dandrader/unity8/declarativeKeymap
Diff against target: 11952 lines (+4061/-2915)
78 files modified
debian/control (+1/-1)
debian/unity8-private.install (+1/-0)
plugins/CMakeLists.txt (+1/-0)
plugins/WindowManager/CMakeLists.txt (+13/-0)
plugins/WindowManager/TopLevelSurfaceList.cpp (+486/-0)
plugins/WindowManager/TopLevelSurfaceList.h (+223/-0)
plugins/WindowManager/WindowManagerPlugin.cpp (+28/-0)
plugins/WindowManager/WindowManagerPlugin.h (+32/-0)
plugins/WindowManager/qmldir (+2/-0)
qml/Components/KeymapSwitcher.qml (+1/-9)
qml/Greeter/Greeter.qml (+5/-4)
qml/Launcher/LauncherPanel.qml (+1/-0)
qml/Shell.qml (+67/-53)
qml/Stages/AbstractStage.qml (+9/-2)
qml/Stages/Animations/BaseSessionAnimation.qml (+0/-96)
qml/Stages/Animations/SwipeFromBottomAnimation.qml (+0/-53)
qml/Stages/ApplicationWindow.qml (+110/-68)
qml/Stages/DecoratedWindow.qml (+5/-5)
qml/Stages/DesktopSpread.qml (+22/-18)
qml/Stages/DesktopSpreadDelegate.qml (+4/-3)
qml/Stages/DesktopStage.qml (+162/-101)
qml/Stages/PhoneStage.qml (+154/-91)
qml/Stages/PromptSurfaceAnimations.qml (+81/-0)
qml/Stages/SpreadDelegate.qml (+4/-3)
qml/Stages/StagedFullscreenPolicy.qml (+12/-14)
qml/Stages/SurfaceContainer.qml (+0/-154)
qml/Stages/TabletStage.qml (+312/-249)
qml/Stages/TopLevelSurfaceRepeater.qml (+52/-0)
qml/Stages/TransformedSpreadDelegate.qml (+4/-4)
qml/Stages/TransformedTabletSpreadDelegate.qml (+29/-23)
qml/Stages/WindowedFullscreenPolicy.qml (+5/-7)
qml/Tutorial/TutorialBottom.qml (+1/-1)
tests/mocks/Unity/Application/ApplicationInfo.cpp (+195/-122)
tests/mocks/Unity/Application/ApplicationInfo.h (+39/-24)
tests/mocks/Unity/Application/ApplicationManager.cpp (+115/-80)
tests/mocks/Unity/Application/ApplicationManager.h (+12/-14)
tests/mocks/Unity/Application/ApplicationTestInterface.cpp (+0/-117)
tests/mocks/Unity/Application/ApplicationTestInterface.h (+0/-45)
tests/mocks/Unity/Application/CMakeLists.txt (+3/-4)
tests/mocks/Unity/Application/MirSurface.cpp (+175/-10)
tests/mocks/Unity/Application/MirSurface.h (+51/-7)
tests/mocks/Unity/Application/MirSurfaceItem.cpp (+13/-7)
tests/mocks/Unity/Application/MirSurfaceListModel.cpp (+121/-0)
tests/mocks/Unity/Application/MirSurfaceListModel.h (+56/-0)
tests/mocks/Unity/Application/ObjectListModel.h (+2/-0)
tests/mocks/Unity/Application/Session.cpp (+0/-209)
tests/mocks/Unity/Application/Session.h (+0/-104)
tests/mocks/Unity/Application/SessionManager.cpp (+0/-55)
tests/mocks/Unity/Application/SessionManager.h (+0/-44)
tests/mocks/Unity/Application/SessionModel.h (+0/-35)
tests/mocks/Unity/Application/SurfaceManager.cpp (+16/-9)
tests/mocks/Unity/Application/SurfaceManager.h (+5/-2)
tests/mocks/Unity/Application/UbuntuKeyboardInfo.cpp (+0/-2)
tests/mocks/Unity/Application/UbuntuKeyboardInfo.h (+0/-9)
tests/mocks/Unity/Application/VirtualKeyboard.cpp (+2/-3)
tests/mocks/Unity/Application/VirtualKeyboard.h (+1/-1)
tests/mocks/Unity/Application/plugin.cpp (+41/-44)
tests/mocks/Utils/CMakeLists.txt (+4/-0)
tests/mocks/Utils/plugin.cpp (+1/-1)
tests/plugins/Unity/Launcher/launchermodeltest.cpp (+2/-3)
tests/qmltests/CMakeLists.txt (+0/-1)
tests/qmltests/Dash/tst_DashShell.qml (+6/-2)
tests/qmltests/Greeter/tst_Greeter.qml (+2/-2)
tests/qmltests/Panel/tst_ActiveCallHint.qml (+3/-2)
tests/qmltests/Panel/tst_Panel.qml (+2/-2)
tests/qmltests/Stages/ApplicationCheckBox.qml (+117/-71)
tests/qmltests/Stages/RecursingChildSessionControl.qml (+10/-61)
tests/qmltests/Stages/tst_ApplicationWindow.qml (+84/-70)
tests/qmltests/Stages/tst_DesktopStage.qml (+253/-171)
tests/qmltests/Stages/tst_PhoneStage.qml (+272/-82)
tests/qmltests/Stages/tst_SpreadDelegate.qml (+3/-2)
tests/qmltests/Stages/tst_SurfaceContainer.qml (+0/-184)
tests/qmltests/Stages/tst_TabletStage.qml (+255/-92)
tests/qmltests/Tutorial/tst_Tutorial.qml (+4/-8)
tests/qmltests/tst_OrientedShell.qml (+96/-69)
tests/qmltests/tst_Shell.qml (+244/-171)
tests/qmltests/tst_ShellWithPin.qml (+9/-13)
tests/utils/modules/Unity/Test/UnityTestCase.qml (+20/-2)
To merge this branch: bzr merge lp:~dandrader/unity8/surfaceListModel
Reviewer Review Type Date Requested Status
Michał Sawicz Approve
Unity8 CI Bot continuous-integration Needs Fixing
Lukáš Tinkl (community) Approve
Gerry Boland (community) Approve
Nick Dedekind (community) Needs Fixing
Review via email: mp+290314@code.launchpad.net

Commit message

Surface-based window management

- We no longer deal with the Session concept.
- Each application has a list of top-level surfaces and each surface has a list of prompt surfaces (general support for child surfaces not yet implemented)
- Stages (desktop, phone and tablet) work on a TopLevelSurfaceList model instead of ApplicationManager.
- TopLevelSurfaceList contains all the top-level surfaces from all running (or suspended) applications

Description of the change

* Are there any related MPs required for this MP to build/function as expected? Please list.
https://code.launchpad.net/~dandrader/unity-api/surfaceListModel/+merge/290315
https://code.launchpad.net/~dandrader/qtmir/surfaceListModel/+merge/290316

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

For testing the "multiple surfaces per application" feature you can use multiwindow.qml from lp:~dandrader/+junk/animatedDemos

You can also create as many surfaces for an application as you want in qml tests UI controls, like in "make tryShell"

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

* If you changed the packaging (debian), did you subscribe the ubuntu-unity team to this MP?
N/A

* If you changed the UI, has there been a design review?
N/A

To post a comment you must log in.
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Gerry Boland (gerboland) wrote :

UI bug:
1. open dialer app
2. hit power key to display off, then again to display on
3. enter PIN
4. watch the dialer-app surface

I see dialer-app surface resize after you unlock. AS if it switches from fullscreen back to windowed.

review: Needs Fixing
Revision history for this message
Gerry Boland (gerboland) wrote :

Camera app not becoming fullscreen in 2 ways for me (Nexus 4):
1. panel still visible
2. camera view not filling the whole of the app surface.
See http://imgur.com/GCj7O4R

Revision history for this message
Gerry Boland (gerboland) wrote :

On phone, I'm not seeing background apps being suspended at all!

Wakelocks are being managed ok though, afaics.

review: Needs Fixing
Revision history for this message
Gerry Boland (gerboland) wrote :

Gallery unable to make images fullscreen when viewing them.

Also confirmed that apps not being suspended (they are getting the expose events though) - start Dropping letters and then switch away - game will not stop and you will loose

Revision history for this message
Gerry Boland (gerboland) wrote :

OOM killing not behaving as I'd expect.
1. I managed to crash unity8 with 4 apps open, and killing a non-visible one via command line. Not easily reproduced though
2. Open dialer & clock. Let both load. Focus dialer. Then kill the clock process via adb. Opening spread, the Clock app has been removed from the list. It should remain, and relaunch the app if focused.
3. Open clock. Switch back to Dash. Kill the clock process. Then tap the Clock app in the dash to re-launch it. No splash appears, but clock does appear a moment later.

review: Needs Fixing
Revision history for this message
Nick Dedekind (nick-dedekind) wrote :

A few code comments attached.

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

On 30/03/2016 13:16, Nick Dedekind wrote:
> Why slots rather than invokables?

Because that's the new way for expressing functions callable from QML it
seems. At least mzanetti told me to do like that.

I do see the benefit of cleaner looking header files at least...

Revision history for this message
Nick Dedekind (nick-dedekind) wrote :

Getting an animation artifact in tablet stage. Not sure if it's in trunk, but I think you reported something similar and I fixed it.

TestCase:
Open a bunch of apps, swipe in switcher.
Select an app ( not bottom ).

Expected:
App expands to full screen with apps behind not visible.

Actual:
Briefly see the app behind it until selected one becomes opaque.

review: Needs Fixing
Revision history for this message
Nick Dedekind (nick-dedekind) wrote :

Tablet (N7) just rebooted... power button + greeter swipe a few times.

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

On 30/03/2016 13:16, Nick Dedekind wrote:
> why not just use INT_MAX to decrease your duplicate hit?

Gerry made the same question. Short answer is:
Code does m_maxId+1. If m_maxId is indeed the absolut maximum the
variable type can handle it will overflow and be -INT_MAX (negative).

Revision history for this message
Nick Dedekind (nick-dedekind) wrote :

Tablet Stage:
Side stage becomes immediately visible when swiping for spread.

Test:
Open app.
Send to side stage.
Close side stage.
Slowly swipe in spread

Expected:
Side stage swipes in with finger movement.
Can reverse direction

Actual:
Side stage becomes immediately visible.

review: Needs Fixing
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Daniel d'Andrada (dandrader) wrote :

"""
Played with this over the weekend on my Bq. Issues found:
1. app start animation broken, splash doesn't slide in, it just appears
2. left edge swipe broken, it always bounces back. Can never get to Dash using that method.
3. launcher not launching apps
4. launcher not switching apps
5. app focus has changed slightly, there are times with OSK stays visible, when it should have gone away. Examples:
  i. have app with OSK up, open indicators. OSK remains up.
  ii. have app with OSK up, right-edge swipe to bring up spread. OSK remains up
6. not moving focus to trust prompt sessions. Example, in Camera app, try share photo:
   i. share selector surface not animated in from bottom
   ii. selected share app not brought to front.
"""

Fixed all items.

6.i is wrong though: that share selector seems to be inside camera's surface since I don't see any notice of new surface coming up, trusted session or anything of the like in qtmir's log.

Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Daniel d'Andrada (dandrader) wrote :

On 30/03/2016 11:07, Gerry Boland wrote:
> Review: Needs Fixing
>
> UI bug:
> 1. open dialer app
> 2. hit power key to display off, then again to display on
> 3. enter PIN
> 4. watch the dialer-app surface
>
> I see dialer-app surface resize after you unlock. AS if it switches from fullscreen back to windowed.

Can't reproduce it. Might have been fixed as well along with the other
issues.

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

On 30/03/2016 11:13, Gerry Boland wrote:
> Camera app not becoming fullscreen in 2 ways for me (Nexus 4):
> 1. panel still visible
> 2. camera view not filling the whole of the app surface.
> See http://imgur.com/GCj7O4R

Fixed.

Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Daniel d'Andrada (dandrader) wrote :

On 30/03/2016 11:21, Gerry Boland wrote:
> Review: Needs Fixing
>
> On phone, I'm not seeing background apps being suspended at all!
>
> Wakelocks are being managed ok though, afaics.

Fixed.

Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Daniel d'Andrada (dandrader) wrote :

On 30/03/2016 11:30, Gerry Boland wrote:
> Gallery unable to make images fullscreen when viewing them.

Already fixed

> Also confirmed that apps not being suspended (they are getting the expose events though) - start Dropping letters and then switch away - game will not stop and you will loose

Already fixed

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

On 30/03/2016 12:10, Gerry Boland wrote:
> Review: Needs Fixing
>
> OOM killing not behaving as I'd expect.
> 1. I managed to crash unity8 with 4 apps open, and killing a non-visible one via command line. Not easily reproduced though

Stacktrace please. And try again since a lot has changed since then

> 2. Open dialer & clock. Let both load. Focus dialer. Then kill the clock process via adb. Opening spread, the Clock app has been removed from the list. It should remain, and relaunch the app if focused.

Already fixed.

> 3. Open clock. Switch back to Dash. Kill the clock process. Then tap the Clock app in the dash to re-launch it. No splash appears, but clock does appear a moment later.
>

Fixed.

Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Daniel d'Andrada (dandrader) wrote :

On 30/03/2016 13:39, Nick Dedekind wrote:
> Review: Needs Fixing
>
> Tablet Stage:
> Side stage becomes immediately visible when swiping for spread.
>
> Test:
> Open app.
> Send to side stage.
> Close side stage.
> Slowly swipe in spread
>
> Expected:
> Side stage swipes in with finger movement.
> Can reverse direction
>
> Actual:
> Side stage becomes immediately visible.

Fixed.

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

On 30/03/2016 13:27, Nick Dedekind wrote:
> Review: Needs Fixing
>
> Getting an animation artifact in tablet stage. Not sure if it's in trunk, but I think you reported something similar and I fixed it.
>
> TestCase:
> Open a bunch of apps, swipe in switcher.
> Select an app ( not bottom ).
>
> Expected:
> App expands to full screen with apps behind not visible.
>
> Actual:
> Briefly see the app behind it until selected one becomes opaque.
>

Didn't get the "not bottom" comment.

Anyway, tried with "make tryTabletStage" by clicking on all app
checkboxes, going to spread, and selecting the left most or middle
window. Didn't spot a difference in behaviour between trunk and this branch.

Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Daniel d'Andrada (dandrader) wrote :

On 30/03/2016 13:29, Nick Dedekind wrote:
> Tablet (N7) just rebooted... power button + greeter swipe a few times.

Can't reproduce.

Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Gerry Boland (gerboland) wrote :

You've done some good work, phone is behaving much better now!

Some issues I've found while testing. Nothing in any specific order:
1. open 2 apps. Open the spread. Then reveal launcher and start a *new* app. I see spread closing, with focus going to the last focused app. The new app does launch, but in the background.
2. I've noticed that when an application starts & is shown on screen, it's Qt.application.active is not set to true. If I open spread & reselect it, then Qt.app.active becomes true. This will break camera app.

review: Needs Fixing
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Daniel d'Andrada (dandrader) wrote :

On 05/04/2016 08:13, Gerry Boland wrote:
> Review: Needs Fixing
>
> You've done some good work, phone is behaving much better now!
>
> Some issues I've found while testing. Nothing in any specific order:
> 1. open 2 apps. Open the spread. Then reveal launcher and start a *new* app. I see spread closing, with focus going to the last focused app. The new app does launch, but in the background.

Fixed

> 2. I've noticed that when an application starts & is shown on screen, it's Qt.application.active is not set to true. If I open spread & reselect it, then Qt.app.active becomes true. This will break camera app.

Fixed.

Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Gerry Boland (gerboland) wrote :

Was this an existing bug on the Tablet?:
Open these apps & orient them in the Spread in this order:
1. Dialer app - side stage
2. Dash
3. Messaging app - main stage
with Dialer on top, Messaging on bottom of the stack.

Open spread, try to tap the Dash surface - but only tap to the right of the Messaging app (i.e. the smaller bit of the Dash surface exposed), to select it. See https://imgur.com/vVnthDu

Revision history for this message
Gerry Boland (gerboland) wrote :

Just testing N7 with mouse connected (desktop mode) - the mouse cursor is not visible

review: Needs Fixing
Revision history for this message
Gerry Boland (gerboland) wrote :

Aha, not accurate. It appears mouse does not draw itself until it moves at least once!

Desktop mode testing:
1. Open System Settings. With indicators are open on Time & date page, tap the "Time & Date settings" entry. For me, the indicator stays open, but System Settings does change to the correct page in the background. The indicator should close.

2. In the desktop spread, System Settings has a preview, an icon, but is missing its text. This might not be new though.

I've tested some basic multi-window apps. You have things working nicely.

review: Needs Fixing
Revision history for this message
Daniel d'Andrada (dandrader) wrote :

On 06/04/2016 08:08, Gerry Boland wrote:
> Was this an existing bug on the Tablet?:
> Open these apps & orient them in the Spread in this order:
> 1. Dialer app - side stage
> 2. Dash
> 3. Messaging app - main stage
> with Dialer on top, Messaging on bottom of the stack.
>
> Open spread, try to tap the Dash surface - but only tap to the right of the Messaging app (i.e. the smaller bit of the Dash surface exposed), to select it. See https://imgur.com/vVnthDu

Nothing happens when I tap on the red "X" location on trunk. So,
pre-existing behavior

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

On 06/04/2016 08:28, Gerry Boland wrote:
> Desktop mode testing:
> 1. Open System Settings. With indicators are open on Time & date page, tap the "Time & Date settings" entry. For me, the indicator stays open, but System Settings does change to the correct page in the background. The indicator should close.

Fixed.

>
> 2. In the desktop spread, System Settings has a preview, an icon, but is missing its text. This might not be new though.
>

Fixed.

Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Daniel d'Andrada (dandrader) wrote :

Run unity8 autopilot tests and got only one failure, which is the same result I got with trunk.

Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Daniel d'Andrada (dandrader) wrote :

All tests should be passing now.

Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Gerry Boland (gerboland) wrote :

Did last round of AP tests, unity8 tests ok, UITK tests have similar number of fails before & after. Bunch more manual testing not revealed anything. Overall, I think this is good to go! Nice work

review: Approve
Revision history for this message
Gerry Boland (gerboland) wrote :

OFC need to sort out dependencies

Revision history for this message
Lukáš Tinkl (lukas-kde) wrote :

readonly property string title: surface && surface.name !== "" ? surface.name + " - " + d.name : d.name

This leads to funny window titles like "Scopes - Scopes"

review: Needs Fixing
Revision history for this message
Daniel d'Andrada (dandrader) wrote :

On 12/04/2016 12:11, Lukáš Tinkl wrote:
> Review: Needs Fixing
>
> readonly property string title: surface && surface.name !== "" ? surface.name + " - " + d.name : d.name
>
> This leads to funny window titles like "Scopes - Scopes"

Fixed.

Revision history for this message
Lukáš Tinkl (lukas-kde) wrote :

OK, wfm

review: Abstain
Revision history for this message
Lukáš Tinkl (lukas-kde) wrote :

- onFocusMaximizedApp: if (priv.foregroundMaximizedAppIndex != -1) {
- ApplicationManager.focusApplication(appRepeater.itemAt(priv.foregroundMaximizedAppIndex).appId);
- }

^^ this piece of code disappeared without replacement from DesktopStage.qml; now it's no longer possible to focus a maximized app in the background by clicking the panel.

review: Needs Fixing
Revision history for this message
Daniel d'Andrada (dandrader) wrote :

On 13/04/2016 05:59, Lukáš Tinkl wrote:
> Review: Needs Fixing
>
> - onFocusMaximizedApp: if (priv.foregroundMaximizedAppIndex != -1) {
> - ApplicationManager.focusApplication(appRepeater.itemAt(priv.foregroundMaximizedAppIndex).appId);
> - }
>
>
> ^^ this piece of code disappeared without replacement from DesktopStage.qml; now it's no longer possible to focus a maximized app in the background by clicking the panel.

Fixed.

Revision history for this message
Lukáš Tinkl (lukas-kde) wrote :

OK, no more issues spotted

review: Approve
Revision history for this message
Michał Sawicz (saviq) wrote :

- // Emit signal to notify Upstart that Mir is ready to receive client connections
- // see http://upstart.ubuntu.com/cookbook/#expect-stop
- if (qgetenv("UNITY_MIR_EMITS_SIGSTOP") == "1") {
- raise(SIGSTOP);
- }

That's no good - autopilot tests hang because of this.

review: Needs Fixing
2359. By Daniel d'Andrada

Bring back raise(SIGSTOP) to our ApplicationManager mock

2360. By Daniel d'Andrada

Fix typo in comment

Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :

FAILED: Continuous integration, rev:2358
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/1032/
Executed test runs:
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/1384
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/1354
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial/1354
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial+overlay/1354
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=vivid+overlay/1354/console
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial/1354/console
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1354/console
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=vivid+overlay/1354/console
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial/1354/console
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1354/console
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=vivid+overlay/1354/console
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial/1354/console
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/1354/console

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/1032/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :

FAILED: Continuous integration, rev:2360
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/1033/
Executed test runs:
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/1385
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/1355
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial/1355
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial+overlay/1355
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=vivid+overlay/1355/console
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial/1355/console
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1355/console
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=vivid+overlay/1355/console
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial/1355/console
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1355/console
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=vivid+overlay/1355/console
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial/1355/console
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/1355/console

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/1033/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Michał Sawicz (saviq) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/control'
2--- debian/control 2016-04-20 17:09:15 +0000
3+++ debian/control 2016-04-20 17:09:16 +0000
4@@ -30,7 +30,7 @@
5 libqt5xmlpatterns5-dev,
6 libsystemsettings-dev,
7 libudev-dev,
8- libunity-api-dev (>= 7.108),
9+ libunity-api-dev (>= 7.110),
10 libusermetricsoutput1-dev,
11 # Need those X11 libs touch emulation from mouse events in manual QML tests on a X11 desktop
12 libx11-dev[!armhf],
13
14=== modified file 'debian/unity8-private.install'
15--- debian/unity8-private.install 2015-11-18 09:15:06 +0000
16+++ debian/unity8-private.install 2016-04-20 17:09:16 +0000
17@@ -14,6 +14,7 @@
18 usr/lib/*/unity8/qml/Unity
19 usr/lib/*/unity8/qml/UInput
20 usr/lib/*/unity8/qml/Utils
21+usr/lib/*/unity8/qml/WindowManager
22 usr/lib/*/unity8/qml/Wizard
23 usr/share/accountsservice/interfaces
24 usr/share/dbus-1/interfaces
25
26=== modified file 'plugins/CMakeLists.txt'
27--- plugins/CMakeLists.txt 2015-11-11 16:03:24 +0000
28+++ plugins/CMakeLists.txt 2016-04-20 17:09:16 +0000
29@@ -25,4 +25,5 @@
30 add_subdirectory(UInput)
31 add_subdirectory(Unity)
32 add_subdirectory(Utils)
33+add_subdirectory(WindowManager)
34 add_subdirectory(Wizard)
35
36=== added directory 'plugins/WindowManager'
37=== added file 'plugins/WindowManager/CMakeLists.txt'
38--- plugins/WindowManager/CMakeLists.txt 1970-01-01 00:00:00 +0000
39+++ plugins/WindowManager/CMakeLists.txt 2016-04-20 17:09:16 +0000
40@@ -0,0 +1,13 @@
41+set(WINDOWMANAGER_SRC
42+ TopLevelSurfaceList.cpp
43+ WindowManagerPlugin.cpp
44+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
45+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/Mir.h
46+ ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceInterface.h
47+ )
48+
49+add_library(windowmanager-qml SHARED ${WINDOWMANAGER_SRC})
50+
51+qt5_use_modules(windowmanager-qml Qml Quick Gui)
52+
53+add_unity8_plugin(WindowManager 0.1 WindowManager TARGETS windowmanager-qml)
54
55=== added file 'plugins/WindowManager/TopLevelSurfaceList.cpp'
56--- plugins/WindowManager/TopLevelSurfaceList.cpp 1970-01-01 00:00:00 +0000
57+++ plugins/WindowManager/TopLevelSurfaceList.cpp 2016-04-20 17:09:16 +0000
58@@ -0,0 +1,486 @@
59+/*
60+ * Copyright (C) 2016 Canonical, Ltd.
61+ *
62+ * This program is free software; you can redistribute it and/or modify
63+ * it under the terms of the GNU General Public License as published by
64+ * the Free Software Foundation; version 3.
65+ *
66+ * This program is distributed in the hope that it will be useful,
67+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
68+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
69+ * GNU General Public License for more details.
70+ *
71+ * You should have received a copy of the GNU General Public License
72+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
73+ */
74+
75+#include "TopLevelSurfaceList.h"
76+
77+// unity-api
78+#include <unity/shell/application/ApplicationInfoInterface.h>
79+#include <unity/shell/application/MirSurfaceInterface.h>
80+#include <unity/shell/application/MirSurfaceListInterface.h>
81+
82+#include <QMetaObject>
83+
84+Q_LOGGING_CATEGORY(UNITY_TOPSURFACELIST, "unity.topsurfacelist", QtDebugMsg)
85+
86+#define DEBUG_MSG qCDebug(UNITY_TOPSURFACELIST).nospace().noquote() << __func__
87+
88+using namespace unity::shell::application;
89+
90+TopLevelSurfaceList::TopLevelSurfaceList(QObject *parent) :
91+ QAbstractListModel(parent)
92+{
93+ DEBUG_MSG << "()";
94+}
95+
96+TopLevelSurfaceList::~TopLevelSurfaceList()
97+{
98+ DEBUG_MSG << "()";
99+}
100+
101+int TopLevelSurfaceList::rowCount(const QModelIndex &parent) const
102+{
103+ return !parent.isValid() ? m_surfaceList.size() : 0;
104+}
105+
106+QVariant TopLevelSurfaceList::data(const QModelIndex& index, int role) const
107+{
108+ if (index.row() < 0 || index.row() >= m_surfaceList.size())
109+ return QVariant();
110+
111+ if (role == SurfaceRole) {
112+ MirSurfaceInterface *surface = m_surfaceList.at(index.row()).surface;
113+ return QVariant::fromValue(surface);
114+ } else if (role == ApplicationRole) {
115+ return QVariant::fromValue(m_surfaceList.at(index.row()).application);
116+ } else if (role == IdRole) {
117+ return QVariant::fromValue(m_surfaceList.at(index.row()).id);
118+ } else {
119+ return QVariant();
120+ }
121+}
122+
123+void TopLevelSurfaceList::raise(MirSurfaceInterface *surface)
124+{
125+ if (!surface)
126+ return;
127+
128+ DEBUG_MSG << "(MirSurface[" << (void*)surface << "])";
129+
130+ int i = indexOf(surface);
131+ if (i != -1) {
132+ raiseId(m_surfaceList.at(i).id);
133+ }
134+}
135+
136+void TopLevelSurfaceList::appendPlaceholder(ApplicationInfoInterface *application)
137+{
138+ DEBUG_MSG << "(" << application->appId() << ")";
139+
140+ appendSurfaceHelper(nullptr, application);
141+}
142+
143+void TopLevelSurfaceList::appendSurface(MirSurfaceInterface *surface, ApplicationInfoInterface *application)
144+{
145+ Q_ASSERT(surface != nullptr);
146+
147+ bool filledPlaceholder = false;
148+ for (int i = 0; i < m_surfaceList.count() && !filledPlaceholder; ++i) {
149+ ModelEntry &entry = m_surfaceList[i];
150+ if (entry.application == application && entry.surface == nullptr) {
151+ entry.surface = surface;
152+ connectSurface(surface);
153+ DEBUG_MSG << " appId=" << application->appId() << " surface=" << surface
154+ << ", filling out placeholder. after: " << toString();
155+ Q_EMIT dataChanged(index(i) /* topLeft */, index(i) /* bottomRight */, QVector<int>() << SurfaceRole);
156+ filledPlaceholder = true;
157+ }
158+ }
159+
160+ if (!filledPlaceholder) {
161+ DEBUG_MSG << " appId=" << application->appId() << " surface=" << surface << ", adding new row";
162+ appendSurfaceHelper(surface, application);
163+ }
164+}
165+
166+void TopLevelSurfaceList::appendSurfaceHelper(MirSurfaceInterface *surface, ApplicationInfoInterface *application)
167+{
168+ if (m_modelState == IdleState) {
169+ m_modelState = InsertingState;
170+ beginInsertRows(QModelIndex(), m_surfaceList.size() /*first*/, m_surfaceList.size() /*last*/);
171+ } else {
172+ Q_ASSERT(m_modelState == ResettingState);
173+ // No point in signaling anything if we're resetting the whole model
174+ }
175+
176+ int id = generateId();
177+ m_surfaceList.append(ModelEntry(surface, application, id));
178+ if (surface) {
179+ connectSurface(surface);
180+ }
181+
182+ if (m_modelState == InsertingState) {
183+ endInsertRows();
184+ Q_EMIT countChanged();
185+ Q_EMIT listChanged();
186+ m_modelState = IdleState;
187+ }
188+
189+ DEBUG_MSG << " after " << toString();
190+}
191+
192+void TopLevelSurfaceList::connectSurface(MirSurfaceInterface *surface)
193+{
194+ connect(surface, &MirSurfaceInterface::focusedChanged, this, [this, surface](bool focused){
195+ if (focused) {
196+ this->raise(surface);
197+ }
198+ });
199+ connect(surface, &MirSurfaceInterface::liveChanged, this, [this, surface](bool live){
200+ if (!live) {
201+ onSurfaceDied(surface);
202+ }
203+ });
204+ connect(surface, &QObject::destroyed, this, [this, surface](){ this->onSurfaceDestroyed(surface); });
205+}
206+
207+void TopLevelSurfaceList::onSurfaceDied(MirSurfaceInterface *surface)
208+{
209+ int i = indexOf(surface);
210+ if (i == -1) {
211+ return;
212+ }
213+
214+ auto application = m_surfaceList[i].application;
215+
216+ // can't be starting if it already has a surface
217+ Q_ASSERT(application->state() != ApplicationInfoInterface::Starting);
218+
219+ if (application->state() == ApplicationInfoInterface::Running) {
220+ m_surfaceList[i].removeOnceSurfaceDestroyed = true;
221+ } else {
222+ // assume it got killed by the out-of-memory daemon.
223+ //
224+ // So leave entry in the model and only remove its surface, so shell can display a screenshot
225+ // in its place.
226+ m_surfaceList[i].removeOnceSurfaceDestroyed = false;
227+ }
228+}
229+
230+void TopLevelSurfaceList::onSurfaceDestroyed(MirSurfaceInterface *surface)
231+{
232+ int i = indexOf(surface);
233+ if (i == -1) {
234+ return;
235+ }
236+
237+ if (m_surfaceList[i].removeOnceSurfaceDestroyed) {
238+ removeAt(i);
239+ } else {
240+ m_surfaceList[i].surface = nullptr;
241+ Q_EMIT dataChanged(index(i) /* topLeft */, index(i) /* bottomRight */, QVector<int>() << SurfaceRole);
242+ DEBUG_MSG << " Removed surface from entry. After: " << toString();
243+ }
244+}
245+
246+void TopLevelSurfaceList::removeAt(int index)
247+{
248+ if (m_modelState == IdleState) {
249+ beginRemoveRows(QModelIndex(), index, index);
250+ m_modelState = RemovingState;
251+ } else {
252+ Q_ASSERT(m_modelState == ResettingState);
253+ // No point in signaling anything if we're resetting the whole model
254+ }
255+
256+ m_surfaceList.removeAt(index);
257+
258+ if (m_modelState == RemovingState) {
259+ endRemoveRows();
260+ Q_EMIT countChanged();
261+ Q_EMIT listChanged();
262+ m_modelState = IdleState;
263+ }
264+
265+ DEBUG_MSG << " after " << toString();
266+}
267+
268+int TopLevelSurfaceList::indexOf(MirSurfaceInterface *surface)
269+{
270+ for (int i = 0; i < m_surfaceList.count(); ++i) {
271+ if (m_surfaceList.at(i).surface == surface) {
272+ return i;
273+ }
274+ }
275+ return -1;
276+}
277+
278+void TopLevelSurfaceList::move(int from, int to)
279+{
280+ if (from == to) return;
281+ DEBUG_MSG << " from=" << from << " to=" << to;
282+
283+ if (from >= 0 && from < m_surfaceList.size() && to >= 0 && to < m_surfaceList.size()) {
284+ QModelIndex parent;
285+ /* When moving an item down, the destination index needs to be incremented
286+ by one, as explained in the documentation:
287+ http://qt-project.org/doc/qt-5.0/qtcore/qabstractitemmodel.html#beginMoveRows */
288+
289+ Q_ASSERT(m_modelState == IdleState);
290+ m_modelState = MovingState;
291+
292+ beginMoveRows(parent, from, from, parent, to + (to > from ? 1 : 0));
293+ m_surfaceList.move(from, to);
294+ endMoveRows();
295+ Q_EMIT listChanged();
296+
297+ m_modelState = IdleState;
298+
299+ DEBUG_MSG << " after " << toString();
300+ }
301+}
302+
303+MirSurfaceInterface *TopLevelSurfaceList::surfaceAt(int index) const
304+{
305+ if (index >=0 && index < m_surfaceList.count()) {
306+ return m_surfaceList[index].surface;
307+ } else {
308+ return nullptr;
309+ }
310+}
311+
312+ApplicationInfoInterface *TopLevelSurfaceList::applicationAt(int index) const
313+{
314+ if (index >=0 && index < m_surfaceList.count()) {
315+ return m_surfaceList[index].application;
316+ } else {
317+ return nullptr;
318+ }
319+}
320+
321+int TopLevelSurfaceList::idAt(int index) const
322+{
323+ if (index >=0 && index < m_surfaceList.count()) {
324+ return m_surfaceList[index].id;
325+ } else {
326+ return 0;
327+ }
328+}
329+
330+int TopLevelSurfaceList::indexForId(int id) const
331+{
332+ for (int i = 0; i < m_surfaceList.count(); ++i) {
333+ if (m_surfaceList[i].id == id) {
334+ return i;
335+ }
336+ }
337+ return -1;
338+}
339+
340+void TopLevelSurfaceList::doRaiseId(int id)
341+{
342+ int fromIndex = indexForId(id);
343+ if (fromIndex != -1) {
344+ move(fromIndex, 0 /* toIndex */);
345+ }
346+}
347+
348+void TopLevelSurfaceList::raiseId(int id)
349+{
350+ if (m_modelState == IdleState) {
351+ DEBUG_MSG << "(id=" << id << ") - do it now.";
352+ doRaiseId(id);
353+ } else {
354+ DEBUG_MSG << "(id=" << id << ") - Model busy (modelState=" << m_modelState << "). Try again in the next event loop.";
355+ // The model has just signalled some change. If we have a Repeater responding to this update, it will get nuts
356+ // if we perform yet another model change straight away.
357+ //
358+ // A bad sympton of this problem is a Repeater.itemAt(index) call returning null event though Repeater.count says
359+ // the index is definitely within bounds.
360+ QMetaObject::invokeMethod(this, "raiseId", Qt::QueuedConnection, Q_ARG(int, id));
361+ }
362+}
363+
364+int TopLevelSurfaceList::generateId()
365+{
366+ int id = m_nextId;
367+ m_nextId = nextFreeId(m_nextId + 1);
368+ Q_EMIT nextIdChanged();
369+ return id;
370+}
371+
372+int TopLevelSurfaceList::nextFreeId(int candidateId)
373+{
374+ if (candidateId > m_maxId) {
375+ return nextFreeId(1);
376+ } else {
377+ if (indexForId(candidateId) == -1) {
378+ // it's indeed free
379+ return candidateId;
380+ } else {
381+ return nextFreeId(candidateId + 1);
382+ }
383+ }
384+}
385+
386+QString TopLevelSurfaceList::toString()
387+{
388+ QString str;
389+ for (int i = 0; i < m_surfaceList.count(); ++i) {
390+ auto item = m_surfaceList.at(i);
391+
392+ QString itemStr = QString("(index=%1,appId=%2,surface=0x%3,id=%4)")
393+ .arg(i)
394+ .arg(item.application->appId())
395+ .arg((qintptr)item.surface, 0, 16)
396+ .arg(item.id);
397+
398+ if (i > 0) {
399+ str.append(",");
400+ }
401+ str.append(itemStr);
402+ }
403+ return str;
404+}
405+
406+void TopLevelSurfaceList::addApplication(ApplicationInfoInterface *application)
407+{
408+ DEBUG_MSG << "(" << application->appId() << ")";
409+ Q_ASSERT(!m_applications.contains(application));
410+ m_applications.append(application);
411+
412+ MirSurfaceListInterface *surfaceList = application->surfaceList();
413+
414+ if (application->state() != ApplicationInfoInterface::Stopped) {
415+ if (surfaceList->count() == 0) {
416+ appendPlaceholder(application);
417+ } else {
418+ for (int i = 0; i < surfaceList->count(); ++i) {
419+ appendSurface(surfaceList->get(i), application);
420+ }
421+ }
422+ }
423+
424+ connect(surfaceList, &QAbstractItemModel::rowsInserted, this,
425+ [this, application, surfaceList](const QModelIndex & /*parent*/, int first, int last)
426+ {
427+ for (int i = last; i >= first; --i) {
428+ this->appendSurface(surfaceList->get(i), application);
429+ }
430+ });
431+}
432+
433+void TopLevelSurfaceList::removeApplication(ApplicationInfoInterface *application)
434+{
435+ DEBUG_MSG << "(" << application->appId() << ")";
436+ Q_ASSERT(m_applications.contains(application));
437+
438+ MirSurfaceListInterface *surfaceList = application->surfaceList();
439+
440+ disconnect(surfaceList, 0, this, 0);
441+
442+ Q_ASSERT(m_modelState == IdleState);
443+ m_modelState = RemovingState;
444+
445+ int i = 0;
446+ while (i < m_surfaceList.count()) {
447+ if (m_surfaceList.at(i).application == application) {
448+ beginRemoveRows(QModelIndex(), i, i);
449+ m_surfaceList.removeAt(i);
450+ endRemoveRows();
451+ Q_EMIT countChanged();
452+ Q_EMIT listChanged();
453+ } else {
454+ ++i;
455+ }
456+ }
457+
458+ m_modelState = IdleState;
459+
460+ DEBUG_MSG << " after " << toString();
461+
462+ m_applications.removeAll(application);
463+}
464+
465+QAbstractListModel *TopLevelSurfaceList::applicationsModel() const
466+{
467+ return m_applicationsModel;
468+}
469+
470+void TopLevelSurfaceList::setApplicationsModel(QAbstractListModel* value)
471+{
472+ if (m_applicationsModel == value) {
473+ return;
474+ }
475+
476+ DEBUG_MSG << "(" << value << ")";
477+
478+ Q_ASSERT(m_modelState == IdleState);
479+ m_modelState = ResettingState;
480+
481+ beginResetModel();
482+
483+ if (m_applicationsModel) {
484+ m_surfaceList.clear();
485+ m_applications.clear();
486+ disconnect(m_applicationsModel, 0, this, 0);
487+ }
488+
489+ m_applicationsModel = value;
490+
491+ if (m_applicationsModel) {
492+ findApplicationRole();
493+
494+ connect(m_applicationsModel, &QAbstractItemModel::rowsInserted,
495+ this, [this](const QModelIndex &/*parent*/, int first, int last) {
496+ for (int i = first; i <= last; ++i) {
497+ auto application = getApplicationFromModelAt(i);
498+ addApplication(application);
499+ }
500+ });
501+
502+ connect(m_applicationsModel, &QAbstractItemModel::rowsAboutToBeRemoved,
503+ this, [this](const QModelIndex &/*parent*/, int first, int last) {
504+ for (int i = first; i <= last; ++i) {
505+ auto application = getApplicationFromModelAt(i);
506+ removeApplication(application);
507+ }
508+ });
509+
510+ for (int i = 0; i < m_applicationsModel->rowCount(); ++i) {
511+ auto application = getApplicationFromModelAt(i);
512+ addApplication(application);
513+ }
514+ }
515+
516+ endResetModel();
517+ m_modelState = IdleState;
518+}
519+
520+ApplicationInfoInterface *TopLevelSurfaceList::getApplicationFromModelAt(int index)
521+{
522+ QModelIndex modelIndex = m_applicationsModel->index(index);
523+
524+ QVariant variant = m_applicationsModel->data(modelIndex, m_applicationRole);
525+
526+ // variant.value<ApplicationInfoInterface*>() returns null for some reason.
527+ return static_cast<ApplicationInfoInterface*>(variant.value<QObject*>());
528+}
529+
530+void TopLevelSurfaceList::findApplicationRole()
531+{
532+ QHash<int, QByteArray> namesHash = m_applicationsModel->roleNames();
533+
534+ m_applicationRole = -1;
535+ for (auto i = namesHash.begin(); i != namesHash.end() && m_applicationRole == -1; ++i) {
536+ if (i.value() == "application") {
537+ m_applicationRole = i.key();
538+ }
539+ }
540+
541+ if (m_applicationRole == -1) {
542+ qFatal("TopLevelSurfaceList: applicationsModel must have a \"application\" role.");
543+ }
544+}
545
546=== added file 'plugins/WindowManager/TopLevelSurfaceList.h'
547--- plugins/WindowManager/TopLevelSurfaceList.h 1970-01-01 00:00:00 +0000
548+++ plugins/WindowManager/TopLevelSurfaceList.h 2016-04-20 17:09:16 +0000
549@@ -0,0 +1,223 @@
550+/*
551+ * Copyright (C) 2016 Canonical, Ltd.
552+ *
553+ * This program is free software; you can redistribute it and/or modify
554+ * it under the terms of the GNU General Public License as published by
555+ * the Free Software Foundation; version 3.
556+ *
557+ * This program is distributed in the hope that it will be useful,
558+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
559+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
560+ * GNU General Public License for more details.
561+ *
562+ * You should have received a copy of the GNU General Public License
563+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
564+ */
565+
566+#ifndef TOPLEVELSURFACELIST_H
567+#define TOPLEVELSURFACELIST_H
568+
569+#include <QAbstractListModel>
570+#include <QList>
571+#include <QLoggingCategory>
572+
573+Q_DECLARE_LOGGING_CATEGORY(UNITY_TOPSURFACELIST)
574+
575+namespace unity {
576+ namespace shell {
577+ namespace application {
578+ class ApplicationInfoInterface;
579+ class MirSurfaceInterface;
580+ }
581+ }
582+}
583+
584+/**
585+ * @brief A model of top-level surfaces
586+ *
587+ * It's an abstraction of top-level application windows.
588+ *
589+ * When an entry first appears, it normaly doesn't have a surface yet, meaning that the application is
590+ * still starting up. A shell should then display a splash screen or saved screenshot of the application
591+ * until its surface comes up.
592+ *
593+ * As applications can have multiple surfaces and you can also have entries without surfaces at all,
594+ * the only way to unambiguously refer to an entry in this model is through its id.
595+ */
596+class TopLevelSurfaceList : public QAbstractListModel
597+{
598+
599+ Q_OBJECT
600+
601+ /**
602+ * @brief A list model of applications.
603+ *
604+ * It's expected to have a role called "application" which returns a ApplicationInfoInterface
605+ */
606+ Q_PROPERTY(QAbstractListModel* applicationsModel READ applicationsModel
607+ WRITE setApplicationsModel
608+ NOTIFY applicationsModelChanged)
609+
610+ /**
611+ * @brief Number of top-level surfaces in this model
612+ *
613+ * This is the same as rowCount, added in order to keep compatibility with QML ListModels.
614+ */
615+ Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
616+
617+ /**
618+ The id to be used on the next entry created
619+ Useful for tests
620+ */
621+ Q_PROPERTY(int nextId READ nextId NOTIFY nextIdChanged)
622+public:
623+
624+ /**
625+ * @brief The Roles supported by the model
626+ *
627+ * SurfaceRole - A MirSurfaceInterface. It will be null if the application is still starting up
628+ * ApplicationRole - An ApplicationInfoInterface
629+ * IdRole - A unique identifier for this entry. Useful to unambiguosly track elements as they move around in the list
630+ */
631+ enum Roles {
632+ SurfaceRole = Qt::UserRole,
633+ ApplicationRole = Qt::UserRole + 1,
634+ IdRole = Qt::UserRole + 2,
635+ };
636+
637+ explicit TopLevelSurfaceList(QObject *parent = nullptr);
638+ virtual ~TopLevelSurfaceList();
639+
640+ // QAbstractItemModel methods
641+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
642+ QVariant data(const QModelIndex& index, int role) const override;
643+ QHash<int, QByteArray> roleNames() const override {
644+ QHash<int, QByteArray> roleNames { {SurfaceRole, "surface"},
645+ {ApplicationRole, "application"},
646+ {IdRole, "id"} };
647+ return roleNames;
648+ }
649+
650+ int nextId() const { return m_nextId; }
651+
652+ QAbstractListModel *applicationsModel() const;
653+ void setApplicationsModel(QAbstractListModel*);
654+
655+public Q_SLOTS:
656+ /**
657+ * @brief Returns the surface at the given index
658+ *
659+ * It will be a nullptr if the application is still starting up and thus hasn't yet created
660+ * and drawn into a surface.
661+ */
662+ unity::shell::application::MirSurfaceInterface *surfaceAt(int index) const;
663+
664+ /**
665+ * @brief Returns the application at the given index
666+ */
667+ unity::shell::application::ApplicationInfoInterface *applicationAt(int index) const;
668+
669+ /**
670+ * @brief Returns the unique id of the element at the given index
671+ */
672+ int idAt(int index) const;
673+
674+ /**
675+ * @brief Returns the index where the row with the given id is located
676+ *
677+ * Returns -1 if there's no row with the given id.
678+ */
679+ int indexForId(int id) const;
680+
681+ /**
682+ * @brief Raises the row with the given id to index 0
683+ */
684+ void raiseId(int id);
685+
686+ void doRaiseId(int id);
687+
688+Q_SIGNALS:
689+ void countChanged();
690+
691+ /**
692+ * @brief Emitted when the list changes
693+ *
694+ * Emitted when model gains an element, loses an element or when elements exchange positions.
695+ */
696+ void listChanged();
697+
698+ void nextIdChanged();
699+
700+ void applicationsModelChanged();
701+
702+private:
703+ void addApplication(unity::shell::application::ApplicationInfoInterface *application);
704+ void removeApplication(unity::shell::application::ApplicationInfoInterface *application);
705+
706+ int indexOf(unity::shell::application::MirSurfaceInterface *surface);
707+ void raise(unity::shell::application::MirSurfaceInterface *surface);
708+ void move(int from, int to);
709+ void appendSurfaceHelper(unity::shell::application::MirSurfaceInterface *surface,
710+ unity::shell::application::ApplicationInfoInterface *application);
711+ void connectSurface(unity::shell::application::MirSurfaceInterface *surface);
712+ int generateId();
713+ int nextFreeId(int candidateId);
714+ QString toString();
715+ void onSurfaceDestroyed(unity::shell::application::MirSurfaceInterface *surface);
716+ void onSurfaceDied(unity::shell::application::MirSurfaceInterface *surface);
717+ void removeAt(int index);
718+ void findApplicationRole();
719+
720+ unity::shell::application::ApplicationInfoInterface *getApplicationFromModelAt(int index);
721+
722+ /*
723+ Placeholder for a future surface from a starting or running application.
724+ Enables shell to give immediate feedback to the user by showing, eg,
725+ a splash screen.
726+
727+ It's a model row containing a null surface and the given application.
728+ */
729+ void appendPlaceholder(unity::shell::application::ApplicationInfoInterface *application);
730+
731+ /*
732+ Adds a model row with the given surface and application
733+
734+ Alternatively, if a placeholder exists for the given application it's
735+ filled with the given surface instead.
736+ */
737+ void appendSurface(unity::shell::application::MirSurfaceInterface *surface,
738+ unity::shell::application::ApplicationInfoInterface *application);
739+
740+ struct ModelEntry {
741+ ModelEntry(unity::shell::application::MirSurfaceInterface *surface, unity::shell::application::ApplicationInfoInterface *application, int id)
742+ : surface(surface), application(application), id(id) {}
743+ unity::shell::application::MirSurfaceInterface *surface;
744+ unity::shell::application::ApplicationInfoInterface *application;
745+ int id;
746+ bool removeOnceSurfaceDestroyed{false};
747+ };
748+
749+ QList<ModelEntry> m_surfaceList;
750+ int m_nextId{1};
751+ static const int m_maxId{1000000};
752+
753+ // applications that are being monitored
754+ QList<unity::shell::application::ApplicationInfoInterface *> m_applications;
755+
756+ QAbstractListModel* m_applicationsModel{nullptr};
757+ int m_applicationRole{-1};
758+
759+ enum ModelState {
760+ IdleState,
761+ InsertingState,
762+ RemovingState,
763+ MovingState,
764+ ResettingState
765+ };
766+ ModelState m_modelState{IdleState};
767+};
768+
769+Q_DECLARE_METATYPE(TopLevelSurfaceList*)
770+Q_DECLARE_METATYPE(QAbstractListModel*)
771+
772+#endif // TOPLEVELSURFACELIST_H
773
774=== added file 'plugins/WindowManager/WindowManagerPlugin.cpp'
775--- plugins/WindowManager/WindowManagerPlugin.cpp 1970-01-01 00:00:00 +0000
776+++ plugins/WindowManager/WindowManagerPlugin.cpp 2016-04-20 17:09:16 +0000
777@@ -0,0 +1,28 @@
778+/*
779+ * Copyright (C) 2016 Canonical, Ltd.
780+ *
781+ * This program is free software; you can redistribute it and/or modify
782+ * it under the terms of the GNU General Public License as published by
783+ * the Free Software Foundation; version 3.
784+ *
785+ * This program is distributed in the hope that it will be useful,
786+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
787+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
788+ * GNU General Public License for more details.
789+ *
790+ * You should have received a copy of the GNU General Public License
791+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
792+ */
793+
794+#include "WindowManagerPlugin.h"
795+
796+#include "TopLevelSurfaceList.h"
797+
798+#include <QtQml>
799+
800+void WindowManagerPlugin::registerTypes(const char *uri)
801+{
802+ qmlRegisterType<TopLevelSurfaceList>(uri, 0, 1, "TopLevelSurfaceList");
803+
804+ qRegisterMetaType<QAbstractListModel*>("QAbstractListModel*");
805+}
806
807=== added file 'plugins/WindowManager/WindowManagerPlugin.h'
808--- plugins/WindowManager/WindowManagerPlugin.h 1970-01-01 00:00:00 +0000
809+++ plugins/WindowManager/WindowManagerPlugin.h 2016-04-20 17:09:16 +0000
810@@ -0,0 +1,32 @@
811+/*
812+ * Copyright (C) 2016 Canonical, Ltd.
813+ *
814+ * This program is free software; you can redistribute it and/or modify
815+ * it under the terms of the GNU General Public License as published by
816+ * the Free Software Foundation; version 3.
817+ *
818+ * This program is distributed in the hope that it will be useful,
819+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
820+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
821+ * GNU General Public License for more details.
822+ *
823+ * You should have received a copy of the GNU General Public License
824+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
825+ */
826+
827+#ifndef WINDOWMANAGER_PLUGIN_H
828+#define WINDOWMANAGER_PLUGIN_H
829+
830+#include <QtQml/QQmlEngine>
831+#include <QtQml/QQmlExtensionPlugin>
832+
833+class WindowManagerPlugin : public QQmlExtensionPlugin
834+{
835+ Q_OBJECT
836+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
837+
838+public:
839+ void registerTypes(const char *uri) override;
840+};
841+
842+#endif // WINDOWMANAGER_PLUGIN_H
843
844=== added file 'plugins/WindowManager/qmldir'
845--- plugins/WindowManager/qmldir 1970-01-01 00:00:00 +0000
846+++ plugins/WindowManager/qmldir 2016-04-20 17:09:16 +0000
847@@ -0,0 +1,2 @@
848+module WindowManager
849+plugin windowmanager-qml
850
851=== modified file 'qml/Components/KeymapSwitcher.qml'
852--- qml/Components/KeymapSwitcher.qml 2016-04-20 17:09:15 +0000
853+++ qml/Components/KeymapSwitcher.qml 2016-04-20 17:09:16 +0000
854@@ -57,16 +57,8 @@
855 currentKeymapIndex = prevIndex;
856 }
857
858- // Code below will get much simpler with surface-based window management (the upcoming MirFocusController)
859- property var application: ApplicationManager.focusedApplicationId
860- ? ApplicationManager.findApplication(ApplicationManager.focusedApplicationId)
861- : null
862- property var session: application && application.session ? application.session : null
863- property var surface: session ? session.lastSurface : null
864-
865 property Binding surfaceKeymapBinding: Binding {
866- target: root.surface
867- when: root.surface != null && root.surface.live
868+ target: MirFocusController.focusedSurface
869 property: "keymap"
870 value: root.currentKeymap
871 }
872
873=== modified file 'qml/Greeter/Greeter.qml'
874--- qml/Greeter/Greeter.qml 2016-03-30 07:46:17 +0000
875+++ qml/Greeter/Greeter.qml 2016-04-20 17:09:16 +0000
876@@ -64,7 +64,7 @@
877 d.selectUser(d.currentIndex, true);
878 }
879
880- function notifyAppFocused(appId) {
881+ function notifyAppFocusRequested(appId) {
882 if (!active) {
883 return;
884 }
885@@ -81,19 +81,20 @@
886 }
887 }
888
889- function notifyAboutToFocusApp(appId) {
890+ // Notify that the user has explicitly requested the given app through unity8 GUI.
891+ function notifyUserRequestedApp(appId) {
892 if (!active) {
893 return;
894 }
895
896 // A hint that we're about to focus an app. This way we can look
897 // a little more responsive, rather than waiting for the above
898- // notifyAppFocused call. We also need this in case we have a locked
899+ // notifyAppFocusRequested call. We also need this in case we have a locked
900 // app, in order to show lockscreen instead of new app.
901 d.startUnlock(false /* toTheRight */);
902 }
903
904- // This is a just a glorified notifyAboutToFocusApp(), but it does one
905+ // This is a just a glorified notifyUserRequestedApp(), but it does one
906 // other thing: it hides any cover pages to the RIGHT, because the user
907 // just came from a launcher drag starting on the left.
908 // It also returns a boolean value, indicating whether there was a visual
909
910=== modified file 'qml/Launcher/LauncherPanel.qml'
911--- qml/Launcher/LauncherPanel.qml 2016-03-16 11:20:24 +0000
912+++ qml/Launcher/LauncherPanel.qml 2016-04-20 17:09:16 +0000
913@@ -98,6 +98,7 @@
914 AbstractButton {
915 id: dashItem
916 anchors.fill: parent
917+ activeFocusOnPress: false
918 onClicked: root.showDashHome()
919 }
920 Rectangle {
921
922=== modified file 'qml/Shell.qml'
923--- qml/Shell.qml 2016-04-20 17:09:15 +0000
924+++ qml/Shell.qml 2016-04-20 17:09:16 +0000
925@@ -42,6 +42,7 @@
926 import Unity.DashCommunicator 0.1
927 import Unity.Indicators 0.1 as Indicators
928 import Cursor 1.0
929+import WindowManager 0.1
930
931
932 Item {
933@@ -96,13 +97,45 @@
934 }
935 }
936
937+ readonly property var mainApp:
938+ applicationsDisplayLoader.item ? applicationsDisplayLoader.item.mainApp : null
939+ onMainAppChanged: {
940+ if (mainApp) {
941+ _onMainAppChanged(mainApp.appId);
942+ }
943+ }
944+ Connections {
945+ target: ApplicationManager
946+ onFocusRequested: {
947+ if (shell.mainApp && shell.mainApp.appId === appId) {
948+ _onMainAppChanged(appId);
949+ }
950+ }
951+ }
952+ function _onMainAppChanged(appId) {
953+ if (wizard.active && appId != "" && appId != "unity8-dash") {
954+ // If this happens on first boot, we may be in edge
955+ // tutorial or wizard while receiving a call. But a call
956+ // is more important than wizard so just bail out of those.
957+ tutorial.finish();
958+ wizard.hide();
959+ }
960+
961+ if (appId === "dialer-app" && callManager.hasCalls && greeter.locked) {
962+ // If we are in the middle of a call, make dialer lockedApp and show it.
963+ // This can happen if user backs out of dialer back to greeter, then
964+ // launches dialer again.
965+ greeter.lockedApp = appId;
966+ }
967+ greeter.notifyAppFocusRequested(appId);
968+
969+ panel.indicators.hide();
970+ launcher.hide();
971+ }
972+
973 // For autopilot consumption
974 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
975
976- // internal props from here onwards
977- readonly property var mainApp:
978- applicationsDisplayLoader.item ? applicationsDisplayLoader.item.mainApp : null
979-
980 // Disable everything while greeter is waiting, so that the user can't swipe
981 // the greeter or launcher until we know whether the session is locked.
982 enabled: greeter && !greeter.waiting
983@@ -139,9 +172,6 @@
984
985 Component.onCompleted: {
986 theme.name = "Ubuntu.Components.Themes.SuruDark"
987- if (ApplicationManager.count > 0) {
988- ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
989- }
990 finishStartUpTimer.start();
991 }
992
993@@ -198,36 +228,10 @@
994 width: parent.width
995 height: parent.height
996
997- Connections {
998- target: ApplicationManager
999-
1000- // This signal is also fired when we try to focus the current app
1001- // again. We rely on this!
1002- onFocusedApplicationIdChanged: {
1003- var appId = ApplicationManager.focusedApplicationId;
1004-
1005- if (wizard.active && appId != "" && appId != "unity8-dash") {
1006- // If this happens on first boot, we may be in edge
1007- // tutorial or wizard while receiving a call. But a call
1008- // is more important than wizard so just bail out of those.
1009- tutorial.finish();
1010- wizard.hide();
1011- }
1012-
1013- if (appId === "dialer-app" && callManager.hasCalls && greeter.locked) {
1014- // If we are in the middle of a call, make dialer lockedApp and show it.
1015- // This can happen if user backs out of dialer back to greeter, then
1016- // launches dialer again.
1017- greeter.lockedApp = appId;
1018- }
1019- greeter.notifyAppFocused(appId);
1020-
1021- panel.indicators.hide();
1022- }
1023-
1024- onApplicationAdded: {
1025- launcher.hide();
1026- }
1027+ TopLevelSurfaceList {
1028+ id: topLevelSurfaceList
1029+ objectName: "topLevelSurfaceList"
1030+ applicationsModel: ApplicationManager
1031 }
1032
1033 Loader {
1034@@ -258,6 +262,10 @@
1035 return "Stages/DesktopStage.qml";
1036 }
1037 }
1038+ // TODO: Ensure the current stage is destroyed before the new one gets loaded.
1039+ // Currently the new one will get loaded while the old is still hanging
1040+ // around for a bit, which might lead to conflicts where both stages
1041+ // change the model simultaneously.
1042 onQmlComponentChanged: {
1043 if (item) item.stageAboutToBeUnloaded();
1044 source = qmlComponent;
1045@@ -272,6 +280,11 @@
1046
1047 Binding {
1048 target: applicationsDisplayLoader.item
1049+ property: "focus"
1050+ value: true
1051+ }
1052+ Binding {
1053+ target: applicationsDisplayLoader.item
1054 property: "objectName"
1055 value: "stage"
1056 }
1057@@ -356,6 +369,16 @@
1058 property: "leftMargin"
1059 value: shell.usageScenario == "desktop" && !settings.autohideLauncher ? launcher.panelWidth: 0
1060 }
1061+ Binding {
1062+ target: applicationsDisplayLoader.item
1063+ property: "applicationManager"
1064+ value: ApplicationManager
1065+ }
1066+ Binding {
1067+ target: applicationsDisplayLoader.item
1068+ property: "topLevelSurfaceList"
1069+ value: topLevelSurfaceList
1070+ }
1071 }
1072 }
1073
1074@@ -370,16 +393,6 @@
1075 z: notifications.useModal || panel.indicators.shown || wizard.active || tutorial.running ? overlay.z + 1 : overlay.z - 1
1076 }
1077
1078- Connections {
1079- target: SessionManager
1080- onSessionStopping: {
1081- if (!session.parentSession && !session.application) {
1082- // nothing is using it. delete it right away
1083- session.release();
1084- }
1085- }
1086- }
1087-
1088 Loader {
1089 id: greeterLoader
1090 anchors.fill: parent
1091@@ -471,7 +484,7 @@
1092 }
1093
1094 function showHome() {
1095- greeter.notifyAboutToFocusApp("unity8-dash");
1096+ greeter.notifyUserRequestedApp("unity8-dash");
1097
1098 var animate = !lightDM.greeter.active && !stages.shown
1099 dash.setCurrentScope(0, animate, false)
1100@@ -520,9 +533,10 @@
1101 greeterShown: greeter.shown
1102 }
1103
1104- readonly property bool topmostApplicationIsFullscreen: mainApp && mainApp.fullscreen
1105-
1106- fullscreenMode: (topmostApplicationIsFullscreen && !lightDM.greeter.active && launcher.progress == 0)
1107+ readonly property bool focusedSurfaceIsFullscreen: MirFocusController.focusedSurface
1108+ ? MirFocusController.focusedSurface.state === Mir.FullscreenState
1109+ : false
1110+ fullscreenMode: (focusedSurfaceIsFullscreen && !lightDM.greeter.active && launcher.progress == 0)
1111 || greeter.hasLockedApp
1112 locked: greeter && greeter.active
1113 }
1114@@ -555,7 +569,7 @@
1115 }
1116 }
1117 onLauncherApplicationSelected: {
1118- greeter.notifyAboutToFocusApp(appId);
1119+ greeter.notifyUserRequestedApp(appId);
1120 shell.activateApplication(appId);
1121 }
1122 onShownChanged: {
1123@@ -719,7 +733,7 @@
1124 onMouseMoved: { cursor.opacity = 1; }
1125 }
1126
1127- // non-visual item
1128+ // non-visual object
1129 KeymapSwitcher {}
1130
1131 Rectangle {
1132
1133=== modified file 'qml/Stages/AbstractStage.qml'
1134--- qml/Stages/AbstractStage.qml 2016-04-20 17:09:15 +0000
1135+++ qml/Stages/AbstractStage.qml 2016-04-20 17:09:16 +0000
1136@@ -18,12 +18,19 @@
1137 import Ubuntu.Components 1.3
1138 import GSettings 1.0
1139
1140-Rectangle {
1141+FocusScope {
1142 id: root
1143
1144- color: "#060606"
1145+ property alias color: backRect.color
1146+ Rectangle {
1147+ id: backRect
1148+ color: "#060606"
1149+ anchors.fill: parent
1150+ }
1151
1152 // Controls to be set from outside
1153+ property QtObject applicationManager
1154+ property QtObject topLevelSurfaceList
1155 property bool altTabPressed
1156 property url background
1157 property bool beingResized
1158
1159=== removed directory 'qml/Stages/Animations'
1160=== removed file 'qml/Stages/Animations/BaseSessionAnimation.qml'
1161--- qml/Stages/Animations/BaseSessionAnimation.qml 2015-07-15 15:07:19 +0000
1162+++ qml/Stages/Animations/BaseSessionAnimation.qml 1970-01-01 00:00:00 +0000
1163@@ -1,96 +0,0 @@
1164-/*
1165- * Copyright (C) 2014 Canonical, Ltd.
1166- *
1167- * This program is free software; you can redistribute it and/or modify
1168- * it under the terms of the GNU General Public License as published by
1169- * the Free Software Foundation; version 3.
1170- *
1171- * This program is distributed in the hope that it will be useful,
1172- * but WITHOUT ANY WARRANTY; without even the implied warranty of
1173- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1174- * GNU General Public License for more details.
1175- *
1176- * You should have received a copy of the GNU General Public License
1177- * along with this program. If not, see <http://www.gnu.org/licenses/>.
1178- */
1179-
1180-import QtQuick 2.4
1181-
1182-/* This is the base case for surface animations, used when adding/removing * child surfaces.
1183- * The class is meant to be overridden and changes/animations provided for state changes.
1184- * NB. It is important to release the surface at the end of the "out" animation.
1185- *
1186- * Example - Simple fade in/out
1187- *
1188- * BaseSurfaceAnimation {
1189- * outChanges: [ PropertyChanges { target: animation.surface; opacity: 0.0 } ]
1190- * outAnimations: [
1191- SequentialAnimation {
1192- * NumberAnimation { target: animation.surface; property: "opacity"; duration: 300 }
1193- * ScriptAction { script: { if (animation.parent.removing) animation.surface.release(); } }
1194- * }
1195- * ]
1196- *
1197- * inChanges: [ PropertyChanges { target: animation.surface; opacity: 1.0 } ]
1198- * inAnimations: [ NumberAnimation { target: animation.surface; property: "opacity"; duration: 300 } ]
1199- * }
1200- */
1201-Item {
1202- id: base
1203- property var container
1204- objectName: "sessionAnimation"
1205-
1206- // changes applied when state changes to "from"
1207- property list<QtObject> fromChanges
1208- // transition animations when changing state to "from"
1209- property list<QtObject> fromAnimations
1210-
1211- // changes applied when state changes to "to"
1212- property list<QtObject> toChanges
1213- // transition animations when changing state to "to"
1214- property list<QtObject> toAnimations
1215-
1216- function start() {
1217- // "prep" state forces toChanges without transition animations.
1218- state = "prep"
1219- state = "to";
1220- }
1221- function end() {
1222- state = "from";
1223- }
1224-
1225- signal completed()
1226-
1227- states: [
1228- State {
1229- name: "baseAnimation"
1230- PropertyChanges { target: container; anchors.fill: undefined }
1231- },
1232-
1233- State {
1234- name: "prep"
1235- extend: "baseAnimation"
1236- changes: fromChanges
1237- },
1238- State {
1239- name: "from"
1240- extend: "prep"
1241- },
1242- State {
1243- name: "in"
1244- extend: "baseAnimation"
1245- changes: toChanges
1246- }
1247- ]
1248-
1249- transitions: [
1250- Transition {
1251- to: "from"
1252- animations: fromAnimations
1253- },
1254- Transition {
1255- to: "to"
1256- animations: toAnimations
1257- }
1258- ]
1259-}
1260
1261=== removed file 'qml/Stages/Animations/SwipeFromBottomAnimation.qml'
1262--- qml/Stages/Animations/SwipeFromBottomAnimation.qml 2015-07-15 15:07:19 +0000
1263+++ qml/Stages/Animations/SwipeFromBottomAnimation.qml 1970-01-01 00:00:00 +0000
1264@@ -1,53 +0,0 @@
1265-/*
1266- * Copyright (C) 2014 Canonical, Ltd.
1267- *
1268- * This program is free software; you can redistribute it and/or modify
1269- * it under the terms of the GNU General Public License as published by
1270- * the Free Software Foundation; version 3.
1271- *
1272- * This program is distributed in the hope that it will be useful,
1273- * but WITHOUT ANY WARRANTY; without even the implied warranty of
1274- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1275- * GNU General Public License for more details.
1276- *
1277- * You should have received a copy of the GNU General Public License
1278- * along with this program. If not, see <http://www.gnu.org/licenses/>.
1279- */
1280-
1281-import QtQuick 2.4
1282-import Ubuntu.Components 1.3
1283-
1284-BaseSessionAnimation {
1285- id: animation
1286-
1287- fromChanges: [
1288- AnchorChanges {
1289- target: container;
1290- anchors.top: container.parent.bottom
1291- }
1292- ]
1293- fromAnimations: [
1294- SequentialAnimation {
1295- // clip so we don't go out of parent's bounds during spread
1296- PropertyAction { target: container.parent; property: "clip"; value: true }
1297- AnchorAnimation { easing: UbuntuAnimation.StandardEasing; duration: UbuntuAnimation.BriskDuration }
1298- PropertyAction { target: container.parent; property: "clip"; value: false }
1299- ScriptAction { script: { animation.completed(); } }
1300- }
1301- ]
1302-
1303- toChanges: [
1304- AnchorChanges {
1305- target: container;
1306- anchors.top: container.parent.top
1307- }
1308- ]
1309- toAnimations: [
1310- SequentialAnimation {
1311- // clip so we don't go out of parent's bounds during spread
1312- PropertyAction { target: container.parent; property: "clip"; value: true }
1313- AnchorAnimation { easing: UbuntuAnimation.StandardEasing; duration: UbuntuAnimation.BriskDuration }
1314- PropertyAction { target: container.parent; property: "clip"; value: false }
1315- }
1316- ]
1317-}
1318
1319=== modified file 'qml/Stages/ApplicationWindow.qml'
1320--- qml/Stages/ApplicationWindow.qml 2016-04-20 17:09:15 +0000
1321+++ qml/Stages/ApplicationWindow.qml 2016-04-20 17:09:16 +0000
1322@@ -20,35 +20,77 @@
1323
1324 FocusScope {
1325 id: root
1326- implicitWidth: sessionContainer.implicitWidth
1327- implicitHeight: sessionContainer.implicitHeight
1328+ implicitWidth: surfaceContainer.implicitWidth
1329+ implicitHeight: surfaceContainer.implicitHeight
1330
1331 // to be read from outside
1332- property alias interactive: sessionContainer.interactive
1333+ property alias interactive: surfaceContainer.interactive
1334 property bool orientationChangesEnabled: d.supportsSurfaceResize ? d.surfaceOldEnoughToBeResized : true
1335- readonly property string title: sessionContainer.surface && sessionContainer.surface.name !== "" ?
1336- sessionContainer.surface.name : d.name
1337+ readonly property string title: surface && surface.name !== "" ? surface.name : d.name
1338
1339 // overridable from outside
1340- property bool fullscreen: application ? application.fullscreen : false
1341+ property bool fullscreen: {
1342+ if (surface) {
1343+ return surface.state === Mir.FullscreenState;
1344+ } else if (application) {
1345+ return application.fullscreen;
1346+ } else {
1347+ return false;
1348+ }
1349+ }
1350
1351 // to be set from outside
1352+ property QtObject surface
1353 property QtObject application
1354 property int surfaceOrientationAngle
1355- property alias resizeSurface: sessionContainer.resizeSurface
1356+ property alias resizeSurface: surfaceContainer.resizeSurface
1357 property int requestedWidth: -1
1358 property int requestedHeight: -1
1359
1360- readonly property int minimumWidth: sessionContainer.surface ? sessionContainer.surface.minimumWidth : 0
1361- readonly property int minimumHeight: sessionContainer.surface ? sessionContainer.surface.minimumHeight : 0
1362- readonly property int maximumWidth: sessionContainer.surface ? sessionContainer.surface.maximumWidth : 0
1363- readonly property int maximumHeight: sessionContainer.surface ? sessionContainer.surface.maximumHeight : 0
1364- readonly property int widthIncrement: sessionContainer.surface ? sessionContainer.surface.widthIncrement : 0
1365- readonly property int heightIncrement: sessionContainer.surface ? sessionContainer.surface.heightIncrement : 0
1366+ readonly property int minimumWidth: surface ? surface.minimumWidth : 0
1367+ readonly property int minimumHeight: surface ? surface.minimumHeight : 0
1368+ readonly property int maximumWidth: surface ? surface.maximumWidth : 0
1369+ readonly property int maximumHeight: surface ? surface.maximumHeight : 0
1370+ readonly property int widthIncrement: surface ? surface.widthIncrement : 0
1371+ readonly property int heightIncrement: surface ? surface.heightIncrement : 0
1372+
1373+ onSurfaceChanged: {
1374+ // The order in which the instructions are executed here matters, to that the correct state
1375+ // transitions in stateGroup take place.
1376+ // More specifically, the moment surfaceContainer.surface gets updated relative to the
1377+ // other instructions.
1378+ if (surface) {
1379+ surfaceContainer.surface = surface;
1380+ d.liveSurface = surface.live;
1381+ d.hadSurface = false;
1382+ surfaceInitTimer.start();
1383+ } else {
1384+ if (d.surfaceInitialized) {
1385+ d.hadSurface = true;
1386+ }
1387+ d.surfaceInitialized = false;
1388+ surfaceContainer.surface = null;
1389+ }
1390+ }
1391
1392 QtObject {
1393 id: d
1394
1395+ property bool liveSurface: false;
1396+ property var con: Connections {
1397+ target: root.surface
1398+ onLiveChanged: d.liveSurface = root.surface.live
1399+ }
1400+ // using liveSurface instead of root.surface.live because with the latter
1401+ // this expression is not reevaluated when root.surface changes
1402+ readonly property bool needToTakeScreenshot: root.surface && d.surfaceInitialized && !d.liveSurface
1403+ && applicationState !== ApplicationInfoInterface.Running
1404+ onNeedToTakeScreenshotChanged: {
1405+ if (needToTakeScreenshot && screenshotImage.status === Image.Null) {
1406+ screenshotImage.take();
1407+ }
1408+ }
1409+
1410 // helpers so that we don't have to check for the existence of an application everywhere
1411 // (in order to avoid breaking qml binding due to a javascript exception)
1412 readonly property string name: root.application ? root.application.name : ""
1413@@ -62,17 +104,7 @@
1414 readonly property color splashColorFooter: root.application ? root.application.splashColorFooter : "#00000000"
1415
1416 // Whether the Application had a surface before but lost it.
1417- property bool hadSurface: sessionContainer.surfaceContainer.hadSurface
1418-
1419- readonly property bool needToTakeScreenshot:
1420- ((sessionContainer.surface && d.surfaceInitialized) || d.hadSurface)
1421- && screenshotImage.status === Image.Null
1422- && d.applicationState === ApplicationInfoInterface.Stopped
1423- onNeedToTakeScreenshotChanged: {
1424- if (needToTakeScreenshot) {
1425- screenshotImage.take();
1426- }
1427- }
1428+ property bool hadSurface: false
1429
1430 //FIXME - this is a hack to avoid the first few rendered frames as they
1431 // might show the UI accommodating due to surface resizes on startup.
1432@@ -99,7 +131,9 @@
1433 Timer {
1434 id: surfaceInitTimer
1435 interval: 100
1436- onTriggered: { if (sessionContainer.surface) {d.surfaceInitialized = true;} }
1437+ onTriggered: {
1438+ if (root.surface && root.surface.live) {d.surfaceInitialized = true;}
1439+ }
1440 }
1441
1442 Timer {
1443@@ -117,7 +151,7 @@
1444 function take() {
1445 // Save memory by using a half-resolution (thus quarter size) screenshot.
1446 // Do not make this a binding, we can only take the screenshot once!
1447- sessionContainer.grabToImage(
1448+ surfaceContainer.grabToImage(
1449 function(result) {
1450 screenshotImage.source = result.url;
1451 },
1452@@ -144,46 +178,33 @@
1453 }
1454 }
1455
1456- SessionContainer {
1457- id: sessionContainer
1458- // A fake application might not even have a session property.
1459- session: application && application.session ? application.session : null
1460-
1461+ SurfaceContainer {
1462+ id: surfaceContainer
1463 requestedWidth: root.requestedWidth
1464 requestedHeight: root.requestedHeight
1465-
1466 surfaceOrientationAngle: application && application.rotatesWindowContents ? root.surfaceOrientationAngle : 0
1467-
1468- onSurfaceChanged: {
1469- if (sessionContainer.surface) {
1470- surfaceInitTimer.start();
1471- } else {
1472- d.surfaceInitialized = false;
1473- }
1474- }
1475-
1476 focus: true
1477 }
1478
1479- // SessionContainer size drives ApplicationWindow size
1480+ // SurfaceContainer size drives ApplicationWindow size
1481 Binding {
1482 target: root; property: "width"
1483- value: stateGroup.state === "surface" ? sessionContainer.width : root.requestedWidth
1484+ value: stateGroup.state === "surface" ? surfaceContainer.width : root.requestedWidth
1485 when: root.requestedWidth >= 0
1486 }
1487 Binding {
1488 target: root; property: "height"
1489- value: stateGroup.state === "surface" ? sessionContainer.height : root.requestedHeight
1490+ value: stateGroup.state === "surface" ? surfaceContainer.height : root.requestedHeight
1491 when: root.requestedHeight >= 0
1492 }
1493
1494- // ApplicationWindow size drives SessionContainer size
1495+ // ApplicationWindow size drives SurfaceContainer size
1496 Binding {
1497- target: sessionContainer; property: "width"; value: root.width
1498+ target: surfaceContainer; property: "width"; value: root.width
1499 when: root.requestedWidth < 0
1500 }
1501 Binding {
1502- target: sessionContainer; property: "height"; value: root.height
1503+ target: surfaceContainer; property: "height"; value: root.height
1504 when: root.requestedHeight < 0
1505 }
1506
1507@@ -194,32 +215,43 @@
1508 State {
1509 name: "void"
1510 when:
1511- d.hadSurface && (!sessionContainer.surface || !d.surfaceInitialized)
1512+ d.hadSurface && (!root.surface || !d.surfaceInitialized)
1513 &&
1514 screenshotImage.status !== Image.Ready
1515 },
1516 State {
1517 name: "splashScreen"
1518 when:
1519- !d.hadSurface && (!sessionContainer.surface || !d.surfaceInitialized)
1520+ !d.hadSurface && (!root.surface || !d.surfaceInitialized)
1521 &&
1522 screenshotImage.status !== Image.Ready
1523 },
1524 State {
1525 name: "surface"
1526 when:
1527- (sessionContainer.surface && d.surfaceInitialized)
1528+ (root.surface && d.surfaceInitialized)
1529 &&
1530- (d.applicationState !== ApplicationInfoInterface.Stopped
1531- || screenshotImage.status !== Image.Ready)
1532+ (d.liveSurface ||
1533+ (d.applicationState !== ApplicationInfoInterface.Running
1534+ && screenshotImage.status !== Image.Ready))
1535 },
1536 State {
1537 name: "screenshot"
1538 when:
1539 screenshotImage.status === Image.Ready
1540 &&
1541- (d.applicationState === ApplicationInfoInterface.Stopped
1542- || !sessionContainer.surface || !d.surfaceInitialized)
1543+ (d.applicationState !== ApplicationInfoInterface.Running
1544+ || !root.surface || !d.surfaceInitialized)
1545+ },
1546+ State {
1547+ // This is a dead end. From here we expect the surface to be removed from the model
1548+ // shortly after we stop referencing to it in our SurfaceContainer.
1549+ name: "closed"
1550+ when:
1551+ // The surface died while the application is running. It must have been closed
1552+ // by the shell or the application decided to destroy it by itself
1553+ root.surface && d.surfaceInitialized && !d.liveSurface
1554+ && d.applicationState === ApplicationInfoInterface.Running
1555 }
1556 ]
1557
1558@@ -227,17 +259,17 @@
1559 Transition {
1560 from: ""; to: "splashScreen"
1561 PropertyAction { target: splashLoader; property: "active"; value: true }
1562- PropertyAction { target: sessionContainer.surfaceContainer
1563+ PropertyAction { target: surfaceContainer
1564 property: "visible"; value: false }
1565 },
1566 Transition {
1567 from: "splashScreen"; to: "surface"
1568 SequentialAnimation {
1569- PropertyAction { target: sessionContainer.surfaceContainer
1570+ PropertyAction { target: surfaceContainer
1571 property: "opacity"; value: 0.0 }
1572- PropertyAction { target: sessionContainer.surfaceContainer
1573+ PropertyAction { target: surfaceContainer
1574 property: "visible"; value: true }
1575- UbuntuNumberAnimation { target: sessionContainer.surfaceContainer; property: "opacity";
1576+ UbuntuNumberAnimation { target: surfaceContainer; property: "opacity";
1577 from: 0.0; to: 1.0
1578 duration: UbuntuAnimation.BriskDuration }
1579 ScriptAction { script: {
1580@@ -253,12 +285,12 @@
1581 surfaceIsOldTimer.stop();
1582 d.surfaceOldEnoughToBeResized = false;
1583 splashLoader.active = true;
1584- sessionContainer.surfaceContainer.visible = true;
1585+ surfaceContainer.visible = true;
1586 } }
1587 UbuntuNumberAnimation { target: splashLoader; property: "opacity";
1588 from: 0.0; to: 1.0
1589 duration: UbuntuAnimation.BriskDuration }
1590- PropertyAction { target: sessionContainer.surfaceContainer
1591+ PropertyAction { target: surfaceContainer
1592 property: "visible"; value: false }
1593 }
1594 },
1595@@ -274,15 +306,16 @@
1596 from: 0.0; to: 1.0
1597 duration: UbuntuAnimation.BriskDuration }
1598 ScriptAction { script: {
1599- sessionContainer.surfaceContainer.visible = false;
1600- if (sessionContainer.session) { sessionContainer.session.release(); }
1601+ surfaceContainer.visible = false;
1602+ surfaceContainer.surface = null;
1603+ d.hadSurface = true;
1604 } }
1605 }
1606 },
1607 Transition {
1608 from: "screenshot"; to: "surface"
1609 SequentialAnimation {
1610- PropertyAction { target: sessionContainer.surfaceContainer
1611+ PropertyAction { target: surfaceContainer
1612 property: "visible"; value: true }
1613 UbuntuNumberAnimation { target: screenshotImage; property: "opacity";
1614 from: 1.0; to: 0.0
1615@@ -310,22 +343,31 @@
1616 ScriptAction { script: {
1617 surfaceIsOldTimer.stop();
1618 d.surfaceOldEnoughToBeResized = false;
1619- sessionContainer.surfaceContainer.visible = false;
1620- if (sessionContainer.session) { sessionContainer.session.release(); }
1621+ surfaceContainer.visible = false;
1622 } }
1623 },
1624 Transition {
1625 from: "void"; to: "surface"
1626 SequentialAnimation {
1627- PropertyAction { target: sessionContainer.surfaceContainer; property: "opacity"; value: 0.0 }
1628- PropertyAction { target: sessionContainer.surfaceContainer; property: "visible"; value: true }
1629- UbuntuNumberAnimation { target: sessionContainer.surfaceContainer; property: "opacity";
1630+ PropertyAction { target: surfaceContainer; property: "opacity"; value: 0.0 }
1631+ PropertyAction { target: surfaceContainer; property: "visible"; value: true }
1632+ UbuntuNumberAnimation { target: surfaceContainer; property: "opacity";
1633 from: 0.0; to: 1.0
1634 duration: UbuntuAnimation.BriskDuration }
1635 ScriptAction { script: {
1636 surfaceIsOldTimer.start();
1637 } }
1638 }
1639+ },
1640+ Transition {
1641+ to: "closed"
1642+ SequentialAnimation {
1643+ ScriptAction { script: {
1644+ surfaceContainer.visible = false;
1645+ surfaceContainer.surface = null;
1646+ d.hadSurface = true;
1647+ } }
1648+ }
1649 }
1650 ]
1651 }
1652
1653=== modified file 'qml/Stages/DecoratedWindow.qml'
1654--- qml/Stages/DecoratedWindow.qml 2016-02-17 13:17:12 +0000
1655+++ qml/Stages/DecoratedWindow.qml 2016-04-20 17:09:16 +0000
1656@@ -26,10 +26,10 @@
1657 width: applicationWindow.width
1658 height: (decorationShown ? decoration.height : 0) + applicationWindow.height
1659
1660- property alias window: applicationWindow
1661 property alias application: applicationWindow.application
1662+ property alias surface: applicationWindow.surface
1663 property alias active: decoration.active
1664- property alias title: decoration.title
1665+ readonly property alias title: applicationWindow.title
1666 property alias fullscreen: applicationWindow.fullscreen
1667
1668 readonly property bool decorationShown: !fullscreen
1669@@ -79,11 +79,11 @@
1670 WindowDecoration {
1671 id: decoration
1672 target: root.parent
1673- objectName: application ? "appWindowDecoration_" + application.appId : "appWindowDecoration_null"
1674+ objectName: "appWindowDecoration"
1675 anchors { left: parent.left; top: parent.top; right: parent.right }
1676 height: units.gu(3)
1677 width: root.width
1678- title: window.title
1679+ title: applicationWindow.title
1680 visible: root.decorationShown
1681
1682 onClose: root.close();
1683@@ -94,7 +94,7 @@
1684
1685 ApplicationWindow {
1686 id: applicationWindow
1687- objectName: application ? "appWindow_" + application.appId : "appWindow_null"
1688+ objectName: "appWindow"
1689 anchors.top: parent.top
1690 anchors.topMargin: decoration.height
1691 anchors.left: parent.left
1692
1693=== modified file 'qml/Stages/DesktopSpread.qml'
1694--- qml/Stages/DesktopSpread.qml 2016-03-10 22:39:57 +0000
1695+++ qml/Stages/DesktopSpread.qml 2016-04-20 17:09:16 +0000
1696@@ -1,5 +1,5 @@
1697 /*
1698- * Copyright (C) 2015 Canonical, Ltd.
1699+ * Copyright (C) 2015-2016 Canonical, Ltd.
1700 *
1701 * This program is free software; you can redistribute it and/or modify
1702 * it under the terms of the GNU General Public License as published by
1703@@ -20,6 +20,7 @@
1704 import Ubuntu.Gestures 0.1
1705 import Unity.Application 0.1
1706 import "../Components"
1707+import Utils 0.1
1708
1709 FocusScope {
1710 id: root
1711@@ -69,12 +70,12 @@
1712 }
1713
1714 function selectNext(isAutoRepeat) {
1715- if (isAutoRepeat && spreadRepeater.highlightedIndex >= ApplicationManager.count -1) {
1716+ if (isAutoRepeat && spreadRepeater.highlightedIndex >= topLevelSurfaceList.count -1) {
1717 return; // AutoRepeat is not allowed to wrap around
1718 }
1719
1720- spreadRepeater.highlightedIndex = (spreadRepeater.highlightedIndex + 1) % ApplicationManager.count;
1721- var newContentX = ((spreadFlickable.contentWidth) / (ApplicationManager.count + 1)) * Math.max(0, Math.min(ApplicationManager.count - 5, spreadRepeater.highlightedIndex - 3));
1722+ spreadRepeater.highlightedIndex = (spreadRepeater.highlightedIndex + 1) % topLevelSurfaceList.count;
1723+ var newContentX = ((spreadFlickable.contentWidth) / (topLevelSurfaceList.count + 1)) * Math.max(0, Math.min(topLevelSurfaceList.count - 5, spreadRepeater.highlightedIndex - 3));
1724 if (spreadFlickable.contentX < newContentX || spreadRepeater.highlightedIndex == 0) {
1725 spreadFlickable.snapTo(newContentX)
1726 }
1727@@ -85,10 +86,10 @@
1728 return; // AutoRepeat is not allowed to wrap around
1729 }
1730
1731- var newIndex = spreadRepeater.highlightedIndex - 1 >= 0 ? spreadRepeater.highlightedIndex - 1 : ApplicationManager.count - 1;
1732+ var newIndex = spreadRepeater.highlightedIndex - 1 >= 0 ? spreadRepeater.highlightedIndex - 1 : topLevelSurfaceList.count - 1;
1733 spreadRepeater.highlightedIndex = newIndex;
1734- var newContentX = ((spreadFlickable.contentWidth) / (ApplicationManager.count + 1)) * Math.max(0, Math.min(ApplicationManager.count - 5, spreadRepeater.highlightedIndex - 1));
1735- if (spreadFlickable.contentX > newContentX || newIndex == ApplicationManager.count -1) {
1736+ var newContentX = ((spreadFlickable.contentWidth) / (topLevelSurfaceList.count + 1)) * Math.max(0, Math.min(topLevelSurfaceList.count - 5, spreadRepeater.highlightedIndex - 1));
1737+ if (spreadFlickable.contentX > newContentX || newIndex == topLevelSurfaceList.count -1) {
1738 spreadFlickable.snapTo(newContentX)
1739 }
1740 }
1741@@ -98,8 +99,8 @@
1742 if (spreadContainer.visible) {
1743 root.playFocusAnimation(spreadRepeater.highlightedIndex)
1744 }
1745- var application = ApplicationManager.get(spreadRepeater.highlightedIndex);
1746- ApplicationManager.requestFocusApplication(application.appId);
1747+ var surface = topLevelSurfaceList.surfaceAt(spreadRepeater.highlightedIndex);
1748+ surface.requestFocus();
1749 }
1750 }
1751
1752@@ -144,7 +145,7 @@
1753 Repeater {
1754 id: spreadRepeater
1755 objectName: "spreadRepeater"
1756- model: ApplicationManager
1757+ model: topLevelSurfaceList
1758
1759 property int highlightedIndex: -1
1760 property int closingIndex: -1
1761@@ -182,7 +183,8 @@
1762 objectName: "clippedSpreadDelegate"
1763 anchors.left: parent.left
1764 anchors.top: parent.top
1765- application: ApplicationManager.get(index)
1766+ application: model.application
1767+ surface: model.surface
1768 width: spreadMaths.spreadHeight
1769 height: spreadMaths.spreadHeight
1770
1771@@ -216,7 +218,7 @@
1772 id: spreadMaths
1773 flickable: spreadFlickable
1774 itemIndex: index
1775- totalItems: Math.max(6, ApplicationManager.count)
1776+ totalItems: Math.max(6, topLevelSurfaceList.count)
1777 sceneHeight: root.height
1778 itemHeight: spreadDelegate.height
1779 }
1780@@ -310,7 +312,7 @@
1781 Layout.preferredWidth: height * 8 / 7.6
1782 image: Image {
1783 anchors.fill: parent
1784- source: model.icon
1785+ source: model.application.icon
1786 Rectangle {
1787 anchors.fill: parent
1788 color: "black"
1789@@ -324,7 +326,9 @@
1790 Label {
1791 Layout.fillWidth: true
1792 Layout.preferredHeight: units.gu(6)
1793- text: model.name
1794+ property string surfaceName: model.surface ? model.surface.name : ""
1795+ property string applicationName: model.application ? model.application.name : ""
1796+ text: surfaceName ? surfaceName : applicationName
1797 wrapMode: Text.WordWrap
1798 elide: Text.ElideRight
1799 maximumLineCount: 2
1800@@ -354,7 +358,7 @@
1801 anchors.margins: -units.gu(2)
1802 onClicked: {
1803 spreadRepeater.closingIndex = index;
1804- ApplicationManager.stopApplication(model.appId)
1805+ model.surface.close();
1806 }
1807 }
1808 }
1809@@ -421,7 +425,7 @@
1810 objectName: "spreadFlickable"
1811 anchors.fill: parent
1812 property int minContentWidth: 6 * Math.min(height / 4, width / 5)
1813- contentWidth: Math.max(6, ApplicationManager.count) * Math.min(height / 4, width / 5)
1814+ contentWidth: Math.max(6, topLevelSurfaceList.count) * Math.min(height / 4, width / 5)
1815 enabled: false
1816
1817 function snapTo(contentX) {
1818@@ -523,7 +527,7 @@
1819 Label {
1820 id: currentSelectedLabel
1821 anchors { bottom: parent.bottom; bottomMargin: root.height * 0.625; horizontalCenter: parent.horizontalCenter }
1822- text: spreadRepeater.highlightedIndex >= 0 ? ApplicationManager.get(spreadRepeater.highlightedIndex).name : ""
1823+ text: spreadRepeater.highlightedIndex >= 0 ? topLevelSurfaceList.surfaceAt(spreadRepeater.highlightedIndex).name : ""
1824 visible: false
1825 fontSize: "large"
1826 }
1827@@ -545,7 +549,7 @@
1828 from: "*"
1829 to: "altTab"
1830 SequentialAnimation {
1831- PropertyAction { target: spreadRepeater; property: "highlightedIndex"; value: Math.min(ApplicationManager.count - 1, 1) }
1832+ PropertyAction { target: spreadRepeater; property: "highlightedIndex"; value: Math.min(topLevelSurfaceList.count - 1, 1) }
1833 PauseAnimation { duration: spreadContainer.animateIn ? 0 : 140 }
1834 PropertyAction { target: workspaceSelector; property: "visible" }
1835 PropertyAction { target: spreadContainer; property: "visible" }
1836
1837=== modified file 'qml/Stages/DesktopSpreadDelegate.qml'
1838--- qml/Stages/DesktopSpreadDelegate.qml 2016-01-11 12:29:30 +0000
1839+++ qml/Stages/DesktopSpreadDelegate.qml 2016-04-20 17:09:16 +0000
1840@@ -1,5 +1,5 @@
1841 /*
1842- * Copyright (C) 2014-2015 Canonical, Ltd.
1843+ * Copyright (C) 2014-2016 Canonical, Ltd.
1844 *
1845 * This program is free software; you can redistribute it and/or modify
1846 * it under the terms of the GNU General Public License as published by
1847@@ -25,12 +25,13 @@
1848
1849 property alias window: applicationWindow
1850 property alias application: applicationWindow.application
1851+ property alias surface: applicationWindow.surface
1852
1853 property bool highlightShown: false
1854 property real shadowOpacity: 1
1855
1856- property int windowWidth: application && application.session && application.session.lastSurface ? application.session.lastSurface.size.width : 0
1857- property int windowHeight: application && application.session && application.session.lastSurface ? application.session.lastSurface.size.height : 0
1858+ property int windowWidth: surface ? surface.size.width : 0
1859+ property int windowHeight: surface ? surface.size.height : 0
1860
1861 state: "normal"
1862 states: [
1863
1864=== modified file 'qml/Stages/DesktopStage.qml'
1865--- qml/Stages/DesktopStage.qml 2016-04-20 17:09:15 +0000
1866+++ qml/Stages/DesktopStage.qml 2016-04-20 17:09:16 +0000
1867@@ -39,45 +39,18 @@
1868 // Used by TutorialRight
1869 property bool spreadShown: spread.state == "altTab"
1870
1871- mainApp: ApplicationManager.focusedApplicationId
1872- ? ApplicationManager.findApplication(ApplicationManager.focusedApplicationId)
1873- : null
1874+ mainApp: priv.focusedAppDelegate ? priv.focusedAppDelegate.application : null
1875
1876 // application windows never rotate independently
1877 mainAppWindowOrientationAngle: shellOrientationAngle
1878
1879 orientationChangesEnabled: true
1880
1881- Connections {
1882- target: ApplicationManager
1883- onApplicationAdded: {
1884- if (spread.state == "altTab") {
1885- spread.state = "";
1886- }
1887-
1888- ApplicationManager.focusApplication(appId);
1889- }
1890-
1891- onApplicationRemoved: {
1892- priv.focusNext();
1893- }
1894-
1895- onFocusRequested: {
1896- var appIndex = priv.indexOf(appId);
1897- var appDelegate = appRepeater.itemAt(appIndex);
1898- appDelegate.restore();
1899-
1900- if (spread.state == "altTab") {
1901- spread.cancel();
1902- }
1903- }
1904- }
1905-
1906 GlobalShortcut {
1907 id: closeWindowShortcut
1908 shortcut: Qt.AltModifier|Qt.Key_F4
1909- onTriggered: ApplicationManager.stopApplication(priv.focusedAppId)
1910- active: priv.focusedAppId !== ""
1911+ onTriggered: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.close(); } }
1912+ active: priv.focusedAppDelegate !== null
1913 }
1914
1915 GlobalShortcut {
1916@@ -121,40 +94,40 @@
1917 active: priv.focusedAppDelegate !== null
1918 }
1919
1920+ Connections {
1921+ target: root.topLevelSurfaceList
1922+ onCountChanged: {
1923+ if (spread.state == "altTab") {
1924+ spread.cancel();
1925+ }
1926+ }
1927+ }
1928+
1929 QtObject {
1930 id: priv
1931+ objectName: "DesktopStagePrivate"
1932
1933- readonly property string focusedAppId: ApplicationManager.focusedApplicationId
1934- readonly property var focusedAppDelegate: {
1935- var index = indexOf(focusedAppId);
1936- return index >= 0 && index < appRepeater.count ? appRepeater.itemAt(index) : null
1937+ property var focusedAppDelegate: null
1938+ onFocusedAppDelegateChanged: {
1939+ if (spread.state == "altTab") {
1940+ spread.state = "";
1941+ }
1942 }
1943- onFocusedAppDelegateChanged: updateForegroundMaximizedApp();
1944
1945- property int foregroundMaximizedAppZ: -1
1946- property int foregroundMaximizedAppIndex: -1 // for stuff like drop shadow and focusing maximized app by clicking panel
1947+ property var foregroundMaximizedAppDelegate: null // for stuff like drop shadow and focusing maximized app by clicking panel
1948
1949 function updateForegroundMaximizedApp() {
1950- var tmp = -1;
1951- var tmpAppId = -1;
1952- for (var i = appRepeater.count - 1; i >= 0; i--) {
1953+ var found = false;
1954+ for (var i = 0; i < appRepeater.count && !found; i++) {
1955 var item = appRepeater.itemAt(i);
1956 if (item && item.visuallyMaximized) {
1957- tmpAppId = i;
1958- tmp = Math.max(tmp, item.normalZ);
1959- }
1960- }
1961- foregroundMaximizedAppZ = tmp;
1962- foregroundMaximizedAppIndex = tmpAppId;
1963- }
1964-
1965- function indexOf(appId) {
1966- for (var i = 0; i < ApplicationManager.count; i++) {
1967- if (ApplicationManager.get(i).appId == appId) {
1968- return i;
1969- }
1970- }
1971- return -1;
1972+ foregroundMaximizedAppDelegate = item;
1973+ found = true;
1974+ }
1975+ }
1976+ if (!found) {
1977+ foregroundMaximizedAppDelegate = null;
1978+ }
1979 }
1980
1981 function minimizeAllWindows() {
1982@@ -164,16 +137,13 @@
1983 appDelegate.minimize();
1984 }
1985 }
1986-
1987- ApplicationManager.unfocusCurrentApplication(); // no app should have focus at this point
1988 }
1989
1990 function focusNext() {
1991- ApplicationManager.unfocusCurrentApplication();
1992 for (var i = 0; i < appRepeater.count; i++) {
1993 var appDelegate = appRepeater.itemAt(i);
1994 if (appDelegate && !appDelegate.minimized) {
1995- ApplicationManager.focusApplication(appDelegate.appId);
1996+ appDelegate.focus = true;
1997 return;
1998 }
1999 }
2000@@ -182,15 +152,14 @@
2001
2002 Connections {
2003 target: PanelState
2004- onClose: {
2005- ApplicationManager.stopApplication(ApplicationManager.focusedApplicationId)
2006+ onClose: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.close(); } }
2007+ onMinimize: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.minimize(); } }
2008+ onMaximize: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.restoreFromMaximized(); } }
2009+ onFocusMaximizedApp: {
2010+ if (priv.foregroundMaximizedAppDelegate) {
2011+ priv.foregroundMaximizedAppDelegate.focus = true;
2012+ }
2013 }
2014- onMinimize: priv.focusedAppDelegate && priv.focusedAppDelegate.minimize();
2015- onMaximize: priv.focusedAppDelegate // don't restore minimized apps when double clicking the panel
2016- && priv.focusedAppDelegate.restoreFromMaximized();
2017- onFocusMaximizedApp: if (priv.foregroundMaximizedAppIndex != -1) {
2018- ApplicationManager.focusApplication(appRepeater.itemAt(priv.foregroundMaximizedAppIndex).appId);
2019- }
2020 }
2021
2022 Binding {
2023@@ -218,7 +187,7 @@
2024 Binding {
2025 target: PanelState
2026 property: "dropShadow"
2027- value: priv.focusedAppDelegate && !priv.focusedAppDelegate.maximized && priv.foregroundMaximizedAppIndex !== -1
2028+ value: priv.focusedAppDelegate && !priv.focusedAppDelegate.maximized && priv.foregroundMaximizedAppDelegate !== null
2029 }
2030
2031 Component.onDestruction: {
2032@@ -227,6 +196,27 @@
2033 PanelState.dropShadow = false;
2034 }
2035
2036+ Instantiator {
2037+ model: root.applicationManager
2038+ delegate: Binding {
2039+ target: model.application
2040+ property: "requestedState"
2041+
2042+ // TODO: figure out some lifecycle policy, like suspending minimized apps
2043+ // if running on a tablet or something.
2044+ // TODO: If the device has a dozen suspended apps because it was running
2045+ // in staged mode, when it switches to Windowed mode it will suddenly
2046+ // resume all those apps at once. We might want to avoid that.
2047+ value: ApplicationInfoInterface.RequestedRunning // Always running for now
2048+ }
2049+ }
2050+
2051+ Binding {
2052+ target: MirFocusController
2053+ property: "focusedSurface"
2054+ value: priv.focusedAppDelegate ? priv.focusedAppDelegate.surface : null
2055+ when: !appRepeater.startingUp && root.parent
2056+ }
2057
2058 FocusScope {
2059 id: appContainer
2060@@ -242,20 +232,25 @@
2061 fillMode: Image.PreserveAspectCrop
2062 }
2063
2064- Repeater {
2065+ TopLevelSurfaceRepeater {
2066 id: appRepeater
2067- model: ApplicationManager
2068+ model: topLevelSurfaceList
2069 objectName: "appRepeater"
2070
2071 delegate: FocusScope {
2072 id: appDelegate
2073- objectName: "appDelegate_" + appId
2074+ objectName: "appDelegate_" + model.id
2075 // z might be overriden in some cases by effects, but we need z ordering
2076 // to calculate occlusion detection
2077- property int normalZ: ApplicationManager.count - index
2078+ property int normalZ: topLevelSurfaceList.count - index
2079+ onNormalZChanged: {
2080+ if (visuallyMaximized) {
2081+ priv.updateForegroundMaximizedApp();
2082+ }
2083+ }
2084 z: normalZ
2085 y: PanelState.panelHeight
2086- focus: appId === priv.focusedAppId
2087+
2088 width: decoratedWindow.width
2089 height: decoratedWindow.height
2090 property int requestedWidth: -1
2091@@ -280,36 +275,99 @@
2092 readonly property alias minimized: appDelegatePrivate.minimized
2093 readonly property alias fullscreen: decoratedWindow.fullscreen
2094
2095- readonly property string appId: model.appId
2096+ readonly property var application: model.application
2097 property bool animationsEnabled: true
2098 property alias title: decoratedWindow.title
2099- readonly property string appName: model.name
2100+ readonly property string appName: model.application ? model.application.name : ""
2101 property bool visuallyMaximized: false
2102 property bool visuallyMinimized: false
2103
2104+ readonly property var surface: model.surface
2105+
2106+ function claimFocus() {
2107+ if (spread.state == "altTab") {
2108+ spread.cancel();
2109+ }
2110+ appDelegate.restore();
2111+ }
2112+ Connections {
2113+ target: model.surface
2114+ onFocusRequested: claimFocus();
2115+ }
2116+ Connections {
2117+ target: model.application
2118+ onFocusRequested: {
2119+ if (!model.surface) {
2120+ // when an app has no surfaces, we assume there's only one entry representing it:
2121+ // this delegate.
2122+ claimFocus();
2123+ } else {
2124+ // if the application has surfaces, focus request should be at surface-level.
2125+ }
2126+ }
2127+ }
2128+
2129 onFocusChanged: {
2130- if (focus && ApplicationManager.focusedApplicationId !== appId) {
2131- ApplicationManager.focusApplication(appId);
2132+ if (appRepeater.startingUp)
2133+ return;
2134+
2135+ if (focus) {
2136+ priv.focusedAppDelegate = appDelegate;
2137+
2138+ // If we're orphan (!parent) it means this stage is no longer the current one
2139+ // and will be deleted shortly. So we should no longer have a say over the model
2140+ if (root.parent) {
2141+ topLevelSurfaceList.raiseId(model.id);
2142+ }
2143+ } else if (!focus && priv.focusedAppDelegate === appDelegate) {
2144+ priv.focusedAppDelegate = null;
2145+ // FIXME: No idea why the Binding{} doens't update when focusedAppDelegate turns null
2146+ MirFocusController.focusedSurface = null;
2147+ }
2148+ }
2149+ Component.onCompleted: {
2150+ // NB: We're differentiating if this delegate was created in response to a new entry in the model
2151+ // or if the Repeater is just populating itself with delegates to match the model it received.
2152+ if (!appRepeater.startingUp) {
2153+ // a top level window is always the focused one when it first appears, unfocusing
2154+ // any preexisting one
2155+ focus = true;
2156+ }
2157+ }
2158+ Component.onDestruction: {
2159+ if (!root.parent) {
2160+ // This stage is about to be destroyed. Don't mess up with the model at this point
2161+ return;
2162+ }
2163+
2164+ if (visuallyMaximized) {
2165+ priv.updateForegroundMaximizedApp();
2166+ }
2167+
2168+ if (focus) {
2169+ // focus some other window
2170+ for (var i = 0; i < appRepeater.count; i++) {
2171+ var appDelegate = appRepeater.itemAt(i);
2172+ if (appDelegate && !appDelegate.minimized && i != index) {
2173+ appDelegate.focus = true;
2174+ return;
2175+ }
2176+ }
2177 }
2178 }
2179
2180 onVisuallyMaximizedChanged: priv.updateForegroundMaximizedApp()
2181
2182- visible: !visuallyMinimized &&
2183- !greeter.fullyShown &&
2184- (priv.foregroundMaximizedAppZ === -1 || priv.foregroundMaximizedAppZ <= z) ||
2185- decoratedWindow.fullscreen ||
2186- (spread.state == "altTab" && index === spread.highlightedIndex)
2187+ visible: (
2188+ !visuallyMinimized
2189+ && !greeter.fullyShown
2190+ && (priv.foregroundMaximizedAppDelegate === null || priv.foregroundMaximizedAppDelegate.normalZ <= z)
2191+ )
2192+ || decoratedWindow.fullscreen
2193+ || (spread.state == "altTab" && index === spread.highlightedIndex)
2194
2195- Binding {
2196- target: ApplicationManager.get(index)
2197- property: "requestedState"
2198- // TODO: figure out some lifecycle policy, like suspending minimized apps
2199- // if running on a tablet or something.
2200- // TODO: If the device has a dozen suspended apps because it was running
2201- // in staged mode, when it switches to Windowed mode it will suddenly
2202- // resume all those apps at once. We might want to avoid that.
2203- value: ApplicationInfoInterface.RequestedRunning // Always running for now
2204+ function close() {
2205+ model.surface.close();
2206 }
2207
2208 function maximize(animated) {
2209@@ -351,7 +409,8 @@
2210 maximizeLeft();
2211 else if (maximizedRight)
2212 maximizeRight();
2213- ApplicationManager.focusApplication(appId);
2214+
2215+ focus = true;
2216 }
2217
2218 function playFocusAnimation() {
2219@@ -369,7 +428,7 @@
2220
2221 states: [
2222 State {
2223- name: "fullscreen"; when: decoratedWindow.fullscreen
2224+ name: "fullscreen"; when: decoratedWindow.fullscreen && !appDelegate.minimized
2225 PropertyChanges {
2226 target: appDelegate;
2227 x: 0;
2228@@ -462,6 +521,7 @@
2229 ScriptAction {
2230 script: {
2231 if (appDelegate.minimized) {
2232+ appDelegate.focus = false;
2233 priv.focusNext();
2234 }
2235 }
2236@@ -486,7 +546,7 @@
2237 id: previewBinding
2238 target: appDelegate
2239 property: "z"
2240- value: ApplicationManager.count + 1
2241+ value: topLevelSurfaceList.count + 1
2242 when: index == spread.highlightedIndex && spread.ready
2243 }
2244
2245@@ -497,12 +557,12 @@
2246 minWidth: units.gu(10)
2247 minHeight: units.gu(10)
2248 borderThickness: units.gu(2)
2249- windowId: model.appId // FIXME: Change this to point to windowId once we have such a thing
2250+ windowId: model.application.appId // FIXME: Change this to point to windowId once we have such a thing
2251 screenWidth: appContainer.width
2252 screenHeight: appContainer.height
2253 leftMargin: root.leftMargin
2254
2255- onPressed: { ApplicationManager.focusApplication(model.appId) }
2256+ onPressed: { appDelegate.focus = true; }
2257
2258 Component.onCompleted: {
2259 loadWindowState();
2260@@ -529,24 +589,25 @@
2261 objectName: "decoratedWindow"
2262 anchors.left: appDelegate.left
2263 anchors.top: appDelegate.top
2264- application: ApplicationManager.get(index)
2265- active: ApplicationManager.focusedApplicationId === model.appId
2266+ application: model.application
2267+ surface: model.surface
2268+ active: appDelegate.focus
2269 focus: true
2270
2271 requestedWidth: appDelegate.requestedWidth
2272 requestedHeight: appDelegate.requestedHeight
2273
2274- onClose: ApplicationManager.stopApplication(model.appId)
2275+ onClose: { appDelegate.close(); }
2276 onMaximize: appDelegate.maximized || appDelegate.maximizedLeft || appDelegate.maximizedRight
2277 ? appDelegate.restoreFromMaximized() : appDelegate.maximize()
2278 onMinimize: appDelegate.minimize()
2279- onDecorationPressed: { ApplicationManager.focusApplication(model.appId) }
2280+ onDecorationPressed: { appDelegate.focus = true; }
2281 }
2282
2283 WindowedFullscreenPolicy {
2284 id: fullscreenPolicy
2285 active: true
2286- application: decoratedWindow.application
2287+ surface: model.surface
2288 }
2289 }
2290 }
2291
2292=== modified file 'qml/Stages/PhoneStage.qml'
2293--- qml/Stages/PhoneStage.qml 2016-04-20 17:09:15 +0000
2294+++ qml/Stages/PhoneStage.qml 2016-04-20 17:09:16 +0000
2295@@ -26,7 +26,6 @@
2296 AbstractStage {
2297 id: root
2298
2299- property QtObject applicationManager: ApplicationManager
2300 property bool focusFirstApp: true // If false, focused app will appear on right edge like other apps
2301 property bool altTabEnabled: true
2302 property real startScale: 1.1
2303@@ -107,9 +106,7 @@
2304 }
2305 }
2306
2307- mainApp: applicationManager.focusedApplicationId
2308- ? applicationManager.findApplication(applicationManager.focusedApplicationId)
2309- : null
2310+ mainApp: priv.focusedAppDelegate ? priv.focusedAppDelegate.application : null
2311
2312 orientationChangesEnabled: priv.focusedAppOrientationChangesEnabled
2313 && !priv.focusedAppDelegateIsDislocated
2314@@ -158,72 +155,27 @@
2315 onTriggered: { root.beingResized = false; }
2316 }
2317
2318- Connections {
2319- target: applicationManager
2320-
2321- onFocusRequested: {
2322- if (spreadView.phase > 0) {
2323- spreadView.snapTo(priv.indexOf(appId));
2324- } else {
2325- applicationManager.focusApplication(appId);
2326- }
2327- }
2328-
2329- onApplicationAdded: {
2330- if (spreadView.phase == 2) {
2331- spreadView.snapTo(applicationManager.count - 1);
2332- } else {
2333- spreadView.phase = 0;
2334- spreadView.contentX = -spreadView.shift;
2335- applicationManager.focusApplication(appId);
2336- }
2337- }
2338-
2339- onApplicationRemoved: {
2340- // Unless we're closing the app ourselves in the spread,
2341- // lets make sure the spread doesn't mess up by the changing app list.
2342- if (spreadView.closingIndex == -1) {
2343- spreadView.phase = 0;
2344- spreadView.contentX = -spreadView.shift;
2345- focusTopMostApp();
2346- }
2347- }
2348-
2349- function focusTopMostApp() {
2350- if (applicationManager.count > 0) {
2351- var topmostApp = applicationManager.get(0);
2352- applicationManager.focusApplication(topmostApp.appId);
2353- }
2354- }
2355- }
2356-
2357 QtObject {
2358 id: priv
2359
2360- property string focusedAppId: root.applicationManager.focusedApplicationId
2361 property bool focusedAppOrientationChangesEnabled: false
2362 readonly property int firstSpreadIndex: root.focusFirstApp ? 1 : 0
2363- readonly property var focusedAppDelegate: {
2364- var index = indexOf(focusedAppId);
2365- return index >= 0 && index < spreadRepeater.count ? spreadRepeater.itemAt(index) : null
2366- }
2367+ property var focusedAppDelegate
2368+ // NB! This may differ from applicationManager.focusedApplicationId if focusedAppDelegate
2369+ // contains a screenshot instead of a surface.
2370+ property string focusedAppId: focusedAppDelegate ? focusedAppDelegate.application.appId : ""
2371
2372 property real oldInverseProgress: 0
2373 property bool animateX: false
2374 property int highlightIndex: 0
2375
2376- onFocusedAppDelegateChanged: {
2377- if (focusedAppDelegate) {
2378- focusedAppDelegate.focus = true;
2379- }
2380- }
2381-
2382- property bool focusedAppDelegateIsDislocated: focusedAppDelegate &&
2383+ property bool focusedAppDelegateIsDislocated: focusedAppDelegate ?
2384 (focusedAppDelegate.x !== 0 || focusedAppDelegate.xBehavior.running)
2385+ : false
2386
2387 function indexOf(appId) {
2388- for (var i = 0; i < root.applicationManager.count; i++) {
2389- if (root.applicationManager.get(i).appId == appId) {
2390+ for (var i = 0; i < spreadRepeater.count; i++) {
2391+ if (spreadRepeater.itemAt(i).application.appId == appId) {
2392 return i;
2393 }
2394 }
2395@@ -237,8 +189,8 @@
2396 function reset() {
2397 // The app that's about to go to foreground has to be focused, otherwise
2398 // it would leave us in an inconsistent state.
2399- if (!root.applicationManager.focusedApplicationId && root.applicationManager.count > 0) {
2400- root.applicationManager.focusApplication(root.applicationManager.get(0).appId);
2401+ if (!MirFocusController.focusedSurface && spreadRepeater.count > 0) {
2402+ spreadRepeater.itemAt(0).focus = true;
2403 }
2404
2405 spreadView.selectedIndex = -1;
2406@@ -258,6 +210,36 @@
2407 }
2408 }
2409
2410+ Instantiator {
2411+ model: root.applicationManager
2412+ delegate: QtObject {
2413+ property var stateBinding: Binding {
2414+ readonly property bool isDash: model.application ? model.application.appId == "unity8-dash" : false
2415+ target: model.application
2416+ property: "requestedState"
2417+ value: (isDash && root.keepDashRunning)
2418+ || (!root.suspended && model.application && priv.focusedAppId === model.application.appId)
2419+ ? ApplicationInfoInterface.RequestedRunning
2420+ : ApplicationInfoInterface.RequestedSuspended
2421+ }
2422+
2423+ property var lifecycleBinding: Binding {
2424+ target: model.application
2425+ property: "exemptFromLifecycle"
2426+ value: model.application
2427+ ? (!model.application.isTouchApp || isExemptFromLifecycle(model.application.appId))
2428+ : false
2429+ }
2430+ }
2431+ }
2432+
2433+ Binding {
2434+ target: MirFocusController
2435+ property: "focusedSurface"
2436+ value: priv.focusedAppDelegate ? priv.focusedAppDelegate.surface : null
2437+ when: root.parent && !spreadRepeater.startingUp
2438+ }
2439+
2440 Flickable {
2441 id: spreadView
2442 objectName: "spreadView"
2443@@ -308,13 +290,38 @@
2444 // rely on having Flickable.contentX keeping an out-of-bounds value when it's set programatically
2445 // (as opposed to having contentX reaching an out-of-bounds value through dragging, which will trigger
2446 // the Flickable.boundsBehavior upon release).
2447- onContentXChanged: { forceItToRemainStillIfBeingResized(); }
2448+ onContentXChanged: {
2449+ if (!undoContentXReset()) {
2450+ forceItToRemainStillIfBeingResized();
2451+ }
2452+ }
2453 onShiftChanged: { forceItToRemainStillIfBeingResized(); }
2454 function forceItToRemainStillIfBeingResized() {
2455 if (root.beingResized && contentX != -spreadView.shift) {
2456 contentX = -spreadView.shift;
2457 }
2458 }
2459+ function undoContentXReset() {
2460+ if (contentWidth <= 0) {
2461+ contentWidthOnLastContentXChange = contentWidth;
2462+ lastContentX = contentX;
2463+ return false;
2464+ }
2465+
2466+ if (contentWidth !== contentWidthOnLastContentXChange
2467+ && lastContentX === -shift && contentX === 0) {
2468+ // Flickable is resetting contentX because contentWidth has changed. Undo it.
2469+ contentX = -shift;
2470+ return true;
2471+ }
2472+
2473+ contentWidthOnLastContentXChange = contentWidth;
2474+ lastContentX = contentX;
2475+ return false;
2476+ }
2477+ property real contentWidthOnLastContentXChange: -1
2478+ property real lastContentX: 0
2479+ // </FIXME-contentX>
2480
2481 Behavior on contentX {
2482 enabled: root.altTabPressed
2483@@ -377,7 +384,7 @@
2484 snapAnimation.start();
2485 return;
2486 }
2487- if (root.applicationManager.count <= index) {
2488+ if (topLevelSurfaceList.count <= index) {
2489 // In case we're trying to snap to some non existing app, lets snap back to the first one
2490 index = 0;
2491 }
2492@@ -414,7 +421,8 @@
2493 ScriptAction {
2494 script: {
2495 if (spreadView.selectedIndex >= 0) {
2496- root.applicationManager.focusApplication(root.applicationManager.get(spreadView.selectedIndex).appId);
2497+ var delegate = spreadRepeater.itemAt(spreadView.selectedIndex)
2498+ delegate.focus = true;
2499
2500 spreadView.selectedIndex = -1;
2501 spreadView.phase = 0;
2502@@ -429,7 +437,7 @@
2503 // This width controls how much the spread can be flicked left/right. It's composed of:
2504 // tileDistance * app count (with a minimum of 3 apps, in order to also allow moving 1 and 2 apps a bit)
2505 // + some constant value (still scales with the screen width) which looks good and somewhat fills the screen
2506- width: Math.max(3, root.applicationManager.count) * spreadView.tileDistance + (spreadView.width - spreadView.tileDistance) * 1.5
2507+ width: Math.max(3, topLevelSurfaceList.count) * spreadView.tileDistance + (spreadView.width - spreadView.tileDistance) * 1.5
2508 height: parent.height
2509 Behavior on width {
2510 enabled: spreadView.closingIndex >= 0
2511@@ -449,13 +457,30 @@
2512 }
2513 }
2514
2515- Repeater {
2516+ TopLevelSurfaceRepeater {
2517 id: spreadRepeater
2518 objectName: "spreadRepeater"
2519- model: root.applicationManager
2520+ model: topLevelSurfaceList
2521+
2522+ onItemRemoved: {
2523+ // Unless we're closing the app ourselves in the spread,
2524+ // lets make sure the spread doesn't mess up by the changing app list.
2525+ if (spreadView.closingIndex == -1) {
2526+ spreadView.phase = 0;
2527+ spreadView.contentX = -spreadView.shift;
2528+ focusTopMostApp();
2529+ }
2530+ }
2531+ function focusTopMostApp() {
2532+ if (spreadRepeater.count > 0) {
2533+ var topmostDelegate = spreadRepeater.itemAt(0);
2534+ topmostDelegate.focus = true;
2535+ }
2536+ }
2537+
2538 delegate: TransformedSpreadDelegate {
2539 id: appDelegate
2540- objectName: "appDelegate" + index
2541+ objectName: "spreadDelegate_" + model.id
2542 startAngle: 45
2543 endAngle: 5
2544 startScale: root.startScale
2545@@ -467,28 +492,61 @@
2546 selected: spreadView.selectedIndex == index
2547 otherSelected: spreadView.selectedIndex >= 0 && !selected
2548 interactive: !spreadView.interactive && spreadView.phase === 0
2549- && priv.fullyShowingFocusedApp && root.interactive && isFocused
2550+ && priv.fullyShowingFocusedApp && root.interactive && focus
2551 swipeToCloseEnabled: spreadView.interactive && root.interactive && !snapAnimation.running
2552 maximizedAppTopMargin: root.maximizedAppTopMargin
2553 dropShadow: spreadView.active || priv.focusedAppDelegateIsDislocated
2554 focusFirstApp: root.focusFirstApp
2555 highlightShown: root.altTabPressed && index === priv.highlightIndex
2556
2557- readonly property bool isDash: model.appId == "unity8-dash"
2558-
2559- Binding {
2560- target: appDelegate.application
2561- property: "exemptFromLifecycle"
2562- value: !model.isTouchApp || isExemptFromLifecycle(model.appId)
2563- }
2564-
2565- Binding {
2566- target: appDelegate.application
2567- property: "requestedState"
2568- value: (isDash && root.keepDashRunning)
2569- || (!root.suspended && appDelegate.focus)
2570- ? ApplicationInfoInterface.RequestedRunning
2571- : ApplicationInfoInterface.RequestedSuspended
2572+ readonly property bool isDash: model.application.appId == "unity8-dash"
2573+
2574+ Component.onCompleted: {
2575+ // NB: We're differentiating if this delegate was created in response to a new entry in the model
2576+ // or if the Repeater is just populating itself with delegates to match the model it received.
2577+ if (!spreadRepeater.startingUp) {
2578+ // a top level window is always the focused one when it first appears, unfocusing
2579+ // any preexisting one
2580+ //
2581+ // new items are appended and must be manually brought to front.
2582+ // that's how it *must* be in order to get the animation for new
2583+ // surfaces working
2584+ claimFocus();
2585+ }
2586+ }
2587+
2588+ onFocusChanged: {
2589+ if (focus && !spreadRepeater.startingUp) {
2590+ priv.focusedAppDelegate = appDelegate;
2591+ // If we're orphan (!parent) it means this stage is no longer the current one
2592+ // and will be deleted shortly. So we should no longer have a say over the model
2593+ if (root.parent) {
2594+ topLevelSurfaceList.raiseId(model.id);
2595+ }
2596+ }
2597+ }
2598+ function claimFocus() {
2599+ if (spreadView.phase > 0) {
2600+ spreadView.snapTo(model.index);
2601+ } else {
2602+ appDelegate.focus = true;
2603+ }
2604+ }
2605+ Connections {
2606+ target: model.surface
2607+ onFocusRequested: claimFocus()
2608+ }
2609+ Connections {
2610+ target: model.application
2611+ onFocusRequested: {
2612+ if (!model.surface) {
2613+ // when an app has no surfaces, we assume there's only one entry representing it:
2614+ // this delegate.
2615+ claimFocus();
2616+ } else {
2617+ // if the application has surfaces, focus request should be at surface-level.
2618+ }
2619+ }
2620 }
2621
2622 z: isDash && !spreadView.active ? -1 : behavioredIndex
2623@@ -509,7 +567,8 @@
2624 return spreadView.width + spreadIndex * spreadView.tileDistance;
2625 }
2626
2627- application: root.applicationManager.get(index)
2628+ application: model.application
2629+ surface: model.surface
2630 closeable: !isDash
2631
2632 property real behavioredIndex: index
2633@@ -593,11 +652,7 @@
2634
2635 onClicked: {
2636 if (root.altTabEnabled && spreadView.phase == 2) {
2637- if (root.applicationManager.focusedApplicationId == root.applicationManager.get(index).appId) {
2638- spreadView.snapTo(index);
2639- } else {
2640- root.applicationManager.requestFocusApplication(root.applicationManager.get(index).appId);
2641- }
2642+ spreadView.snapTo(index);
2643 }
2644 }
2645
2646@@ -611,7 +666,15 @@
2647
2648 onClosed: {
2649 spreadView.closingIndex = index;
2650- root.applicationManager.stopApplication(root.applicationManager.get(index).appId);
2651+ if (appDelegate.surface) {
2652+ appDelegate.surface.close();
2653+ } else if (appDelegate.application) {
2654+ root.applicationManager.stopApplication(appDelegate.application.appId);
2655+ } else {
2656+ // should never happen
2657+ console.warn("Can't close topLevelSurfaceList entry as it has neither"
2658+ + " a surface nor an application");
2659+ }
2660 }
2661
2662 Binding {
2663@@ -629,7 +692,7 @@
2664
2665 StagedFullscreenPolicy {
2666 id: fullscreenPolicy
2667- application: appDelegate.application
2668+ surface: model.surface
2669 }
2670
2671 Connections {
2672@@ -637,7 +700,7 @@
2673 onStageAboutToBeUnloaded: fullscreenPolicy.active = false
2674 }
2675 }
2676- }
2677+ } // Repeater {
2678 }
2679 }
2680
2681
2682=== added file 'qml/Stages/PromptSurfaceAnimations.qml'
2683--- qml/Stages/PromptSurfaceAnimations.qml 1970-01-01 00:00:00 +0000
2684+++ qml/Stages/PromptSurfaceAnimations.qml 2016-04-20 17:09:16 +0000
2685@@ -0,0 +1,81 @@
2686+/*
2687+ * Copyright 2016 Canonical Ltd.
2688+ *
2689+ * This program is free software; you can redistribute it and/or modify
2690+ * it under the terms of the GNU Lesser General Public License as published by
2691+ * the Free Software Foundation; version 3.
2692+ *
2693+ * This program is distributed in the hope that it will be useful,
2694+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2695+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2696+ * GNU Lesser General Public License for more details.
2697+ *
2698+ * You should have received a copy of the GNU Lesser General Public License
2699+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2700+*/
2701+
2702+import QtQuick 2.4
2703+import Ubuntu.Components 1.3
2704+
2705+StateGroup {
2706+ id: root
2707+ property var container
2708+ property var surfaceItem
2709+
2710+ states: [
2711+ State {
2712+ name: "blank"
2713+ when: !root.surfaceItem.surface
2714+ },
2715+ State {
2716+ name: "ready"
2717+ when: root.surfaceItem.surface && root.surfaceItem.live
2718+ },
2719+ State {
2720+ name: "zombie"
2721+ when: root.surfaceItem.surface && !root.surfaceItem.live
2722+ }
2723+ ]
2724+ transitions: [
2725+ Transition {
2726+ from: "*"; to: "zombie"
2727+ // Slide downwards until it's out of view, through the bottom of the window
2728+ SequentialAnimation {
2729+ // clip so we don't go out of parent's bounds during spread
2730+ PropertyAction { target: root.container.parent; property: "clip"; value: true }
2731+ UbuntuNumberAnimation { target: root.surfaceItem; property: "y"; to: root.container.height
2732+ duration: UbuntuAnimation.BriskDuration }
2733+ PropertyAction { target: root.surfaceItem; property: "visible"; value: false }
2734+ PropertyAction { target: container.parent; property: "clip"; value: false }
2735+ ScriptAction { script: {
2736+ // Unity.Application can't destroy a zombie MirSurface if it's still being
2737+ // referenced by a MirSurfaceItem.
2738+ root.surfaceItem.surface = null;
2739+ } }
2740+ }
2741+ },
2742+ Transition {
2743+ from: "*"; to: "ready"
2744+ // Slide upwards into view, from the bottom of the window
2745+ SequentialAnimation {
2746+ // clip so we don't go out of parent's bounds during spread
2747+ PropertyAction { target: root.container.parent; property: "clip"; value: true }
2748+ ScriptAction { script: {
2749+ root.surfaceItem.y = root.container.height;
2750+ root.surfaceItem.visible = true;
2751+ } }
2752+ UbuntuNumberAnimation {
2753+ target: root.surfaceItem; property: "y"; to: 0
2754+ duration: UbuntuAnimation.BriskDuration
2755+ }
2756+ PropertyAction { target: container.parent; property: "clip"; value: false }
2757+ }
2758+ },
2759+ Transition {
2760+ from: "*"; to: "blank"
2761+ ScriptAction { script: {
2762+ root.surfaceItem.visible = false;
2763+ } }
2764+ }
2765+ ]
2766+}
2767
2768=== modified file 'qml/Stages/SpreadDelegate.qml'
2769--- qml/Stages/SpreadDelegate.qml 2016-04-20 17:09:15 +0000
2770+++ qml/Stages/SpreadDelegate.qml 2016-04-20 17:09:16 +0000
2771@@ -1,5 +1,5 @@
2772 /*
2773- * Copyright 2014-2015 Canonical Ltd.
2774+ * Copyright 2014-2016 Canonical Ltd.
2775 *
2776 * This program is free software; you can redistribute it and/or modify
2777 * it under the terms of the GNU Lesser General Public License as published by
2778@@ -45,6 +45,7 @@
2779 property alias swipeToCloseEnabled: dragArea.enabled
2780 property bool closeable
2781 property alias application: appWindow.application
2782+ property alias surface: appWindow.surface
2783 property int shellOrientationAngle
2784 property int shellOrientation
2785 property QtObject orientations
2786@@ -250,7 +251,7 @@
2787 }
2788 PropertyChanges {
2789 target: appWindow
2790- surfaceOrientationAngle: orientationAngle
2791+ surfaceOrientationAngle: appWindowWithShadow.orientationAngle
2792 }
2793 },
2794 State {
2795@@ -292,7 +293,7 @@
2796
2797 ApplicationWindow {
2798 id: appWindow
2799- objectName: application ? "appWindow_" + application.appId : "appWindow_null"
2800+ objectName: "appWindow"
2801 focus: true
2802 anchors {
2803 fill: parent
2804
2805=== modified file 'qml/Stages/StagedFullscreenPolicy.qml'
2806--- qml/Stages/StagedFullscreenPolicy.qml 2016-03-15 19:38:03 +0000
2807+++ qml/Stages/StagedFullscreenPolicy.qml 2016-04-20 17:09:16 +0000
2808@@ -27,31 +27,29 @@
2809 // Chrome not set and state change to fulscreen -> client window stays "fullscreen"
2810 QtObject {
2811 property bool active: true
2812- property QtObject application: null
2813
2814- readonly property var lastSurface: application && application.session ?
2815- application.session.lastSurface : null
2816- onLastSurfaceChanged: {
2817- if (!active || !lastSurface) return;
2818- if (lastSurface.shellChrome === Mir.LowChrome) {
2819- lastSurface.state = Mir.FullscreenState;
2820+ property var surface: null
2821+ onSurfaceChanged: {
2822+ if (!active || !surface) return;
2823+ if (surface.shellChrome === Mir.LowChrome) {
2824+ surface.state = Mir.FullscreenState;
2825 }
2826 }
2827
2828 property var _connections: Connections {
2829- target: lastSurface
2830+ target: surface
2831 onShellChromeChanged: {
2832- if (!active || !lastSurface) return;
2833- if (lastSurface.shellChrome === Mir.LowChrome) {
2834- lastSurface.state = Mir.FullscreenState;
2835+ if (!active || !surface) return;
2836+ if (surface.shellChrome === Mir.LowChrome) {
2837+ surface.state = Mir.FullscreenState;
2838 } else {
2839- lastSurface.state = Mir.RestoredState;
2840+ surface.state = Mir.RestoredState;
2841 }
2842 }
2843 onStateChanged: {
2844 if (!active) return;
2845- if (lastSurface.state === Mir.RestoredState && lastSurface.shellChrome === Mir.LowChrome) {
2846- lastSurface.state = Mir.FullscreenState;
2847+ if (surface.state === Mir.RestoredState && surface.shellChrome === Mir.LowChrome) {
2848+ surface.state = Mir.FullscreenState;
2849 }
2850 }
2851 }
2852
2853=== renamed file 'qml/Stages/SessionContainer.qml' => 'qml/Stages/SurfaceContainer.qml'
2854--- qml/Stages/SessionContainer.qml 2016-02-03 13:46:18 +0000
2855+++ qml/Stages/SurfaceContainer.qml 2016-04-20 17:09:16 +0000
2856@@ -1,5 +1,5 @@
2857 /*
2858- * Copyright 2014-2015 Canonical Ltd.
2859+ * Copyright 2014-2016 Canonical Ltd.
2860 *
2861 * This program is free software; you can redistribute it and/or modify
2862 * it under the terms of the GNU Lesser General Public License as published by
2863@@ -15,61 +15,124 @@
2864 */
2865
2866 import QtQuick 2.4
2867-import "Animations"
2868+import Ubuntu.Components 1.3
2869+import Ubuntu.Gestures 0.1 // For TouchGate
2870+import Utils 0.1 // for InputWatcher
2871+import Unity.Application 0.1 // for MirSurfaceItem
2872
2873 FocusScope {
2874 id: root
2875- objectName: "sessionContainer"
2876- implicitWidth: _surfaceContainer.implicitWidth
2877- implicitHeight: _surfaceContainer.implicitHeight
2878- property QtObject session
2879- readonly property var childSessions: session ? session.childSessions : null
2880- readonly property alias surface: _surfaceContainer.surface
2881- property alias interactive: _surfaceContainer.interactive
2882- property alias surfaceOrientationAngle: _surfaceContainer.surfaceOrientationAngle
2883- property alias resizeSurface: _surfaceContainer.resizeSurface
2884-
2885+ objectName: "surfaceContainer"
2886+
2887+ // Must be set from outside
2888+ property var surface: null
2889+
2890+ // Might be changed from outside
2891 property int requestedWidth: -1
2892 property int requestedHeight: -1
2893-
2894- readonly property alias surfaceContainer: _surfaceContainer
2895- SurfaceContainer {
2896- id: _surfaceContainer
2897- requestedWidth: root.requestedWidth
2898- requestedHeight: root.requestedHeight
2899- surface: session ? session.lastSurface : null
2900- }
2901-
2902- // SurfaceContainer size drives SessionContainer size
2903- Binding {
2904- target: root; property: "width"; value: _surfaceContainer.width
2905+ property bool interactive
2906+ property int surfaceOrientationAngle: 0
2907+ property bool resizeSurface: true
2908+ property bool inPromptSession: false
2909+
2910+ onSurfaceChanged: {
2911+ // Not a binding because animations might remove the surface from the surfaceItem
2912+ // programatically (in order to signal that a zombie surface is free for deletion),
2913+ // even though root.surface is still !null.
2914+ surfaceItem.surface = surface;
2915+ }
2916+
2917+ InputWatcher {
2918+ target: surfaceItem
2919+ onTargetPressedChanged: {
2920+ if (targetPressed && root.interactive) {
2921+ root.focus = true;
2922+ root.forceActiveFocus();
2923+ }
2924+ }
2925+ }
2926+
2927+ MirSurfaceItem {
2928+ id: surfaceItem
2929+ objectName: "surfaceItem"
2930+
2931+ fillMode: MirSurfaceItem.PadOrCrop
2932+ consumesInput: true
2933+
2934+ surfaceWidth: {
2935+ if (root.resizeSurface) {
2936+ if (root.requestedWidth >= 0) {
2937+ return root.requestedWidth;
2938+ } else {
2939+ return width;
2940+ }
2941+ } else {
2942+ return -1;
2943+ }
2944+ }
2945+
2946+ surfaceHeight: {
2947+ if (root.resizeSurface) {
2948+ if (root.requestedHeight >= 0) {
2949+ return root.requestedHeight;
2950+ } else {
2951+ return height;
2952+ }
2953+ } else {
2954+ return -1;
2955+ }
2956+ }
2957+
2958+ enabled: root.interactive
2959+ antialiasing: !root.interactive
2960+ orientationAngle: root.surfaceOrientationAngle
2961+ }
2962+
2963+ TouchGate {
2964+ targetItem: surfaceItem
2965+ anchors.fill: root
2966+ enabled: surfaceItem.enabled
2967+ }
2968+
2969+ // MirSurface size drives SurfaceContainer size
2970+ Binding {
2971+ target: surfaceItem; property: "width"; value: root.surface ? root.surface.size.width : 0
2972+ when: root.requestedWidth >= 0 && root.surface
2973+ }
2974+ Binding {
2975+ target: surfaceItem; property: "height"; value: root.surface ? root.surface.size.height : 0
2976+ when: root.requestedHeight >= 0 && root.surface
2977+ }
2978+ Binding {
2979+ target: root; property: "width"; value: surfaceItem.width
2980 when: root.requestedWidth >= 0
2981 }
2982 Binding {
2983- target: root; property: "height"; value: _surfaceContainer.height
2984+ target: root; property: "height"; value: surfaceItem.height
2985 when: root.requestedHeight >= 0
2986 }
2987
2988- // SessionContainer size drives SurfaceContainer size
2989+ // SurfaceContainer size drives MirSurface size
2990 Binding {
2991- target: _surfaceContainer; property: "width"; value: root.width
2992+ target: surfaceItem; property: "width"; value: root.width
2993 when: root.requestedWidth < 0
2994 }
2995 Binding {
2996- target: _surfaceContainer; property: "height"; value: root.height
2997+ target: surfaceItem; property: "height"; value: root.height
2998 when: root.requestedHeight < 0
2999 }
3000
3001 Repeater {
3002- id: childSessionsRepeater
3003- model: root.childSessions
3004+ id: childSurfacesRepeater
3005+ objectName: "childSurfacesRepeater"
3006+ model: root.surface ? root.surface.promptSurfaceList : null
3007
3008 delegate: Loader {
3009 objectName: "childDelegate" + index
3010- anchors.fill: surfaceContainer
3011+ anchors.fill: root
3012
3013 // Only way to do recursive qml items.
3014- source: Qt.resolvedUrl("SessionContainer.qml")
3015+ source: Qt.resolvedUrl("SurfaceContainer.qml")
3016
3017 z: index
3018
3019@@ -81,12 +144,12 @@
3020
3021 Binding {
3022 target: item; when: item
3023- property: "interactive"; value: index == (childSessionsRepeater.count - 1) && root.interactive
3024+ property: "interactive"; value: index == (childSurfacesRepeater.count - 1) && root.interactive
3025 }
3026
3027 Binding {
3028 target: item; when: item
3029- property: "session"; value: modelData
3030+ property: "surface"; value: model.surface
3031 }
3032
3033 Binding {
3034@@ -98,79 +161,47 @@
3035 target: item; when: item
3036 property: "height"; value: root.height
3037 }
3038- }
3039- }
3040-
3041- states: [
3042- State {
3043- name: "rootSession"
3044- when: root.session && !root.session.parentSession
3045- },
3046-
3047- State {
3048- name: "childSession"
3049- when: root.session && root.session.parentSession !== null && root.session.live
3050- && !root.session.lastSurface
3051- },
3052-
3053- State {
3054- name: "childSessionReady"
3055- when: root.session && root.session.parentSession !== null && root.session.live
3056- && root.session.lastSurface !== null
3057- },
3058-
3059- State {
3060- name: "childSessionZombie"
3061- when: root.session && root.session.parentSession !== null && !root.session.live
3062- }
3063- ]
3064-
3065- transitions: [
3066- Transition {
3067- to: "childSessionReady"
3068- ScriptAction { script: { if (!surfaceContainer.hadSurface) { animateIn(swipeFromBottom); } } }
3069- },
3070- Transition {
3071- to: "childSessionZombie"
3072- ScriptAction { script: { animateOut(); } }
3073- }
3074- ]
3075-
3076- function animateIn(component) {
3077- var animation = component.createObject(root, { "container": root, });
3078- animation.start();
3079-
3080- var tmp = d.animations;
3081- tmp.push(animation);
3082- d.animations = tmp;
3083- }
3084-
3085- function animateOut() {
3086- if (d.animations.length > 0) {
3087- var tmp = d.animations;
3088- var popped = tmp.pop();
3089- popped.completed.connect(function() { root.session.release(); } );
3090- popped.end();
3091- d.animations = tmp;
3092- } else {
3093- root.session.release();
3094- }
3095- }
3096-
3097- Component {
3098- id: swipeFromBottom
3099- SwipeFromBottomAnimation {}
3100+
3101+ Binding {
3102+ target: item; when: item
3103+ property: "inPromptSession"; value: true
3104+ }
3105+ }
3106+ }
3107+
3108+ Loader {
3109+ id: animationsLoader
3110+ objectName: "animationsLoader"
3111+ active: root.surface
3112+ source: {
3113+ if (root.inPromptSession) {
3114+ return "PromptSurfaceAnimations.qml";
3115+ } else {
3116+ // Let ApplicationWindow do the animations
3117+ return "";
3118+ }
3119+ }
3120+ Binding {
3121+ target: animationsLoader.item
3122+ when: animationsLoader.item
3123+ property: "surfaceItem"
3124+ value: surfaceItem
3125+ }
3126+ Binding {
3127+ target: animationsLoader.item
3128+ when: animationsLoader.item
3129+ property: "container"
3130+ value: root
3131+ }
3132 }
3133
3134 QtObject {
3135 id: d
3136- property var animations: []
3137-
3138 property var focusedChild: {
3139- if (childSessionsRepeater.count == 0) {
3140- return _surfaceContainer;
3141+ if (childSurfacesRepeater.count == 0) {
3142+ return surfaceItem;
3143 } else {
3144- return childSessionsRepeater.itemAt(childSessionsRepeater.count - 1);
3145+ return childSurfacesRepeater.itemAt(childSurfacesRepeater.count - 1);
3146 }
3147 }
3148 onFocusedChildChanged: {
3149
3150=== removed file 'qml/Stages/SurfaceContainer.qml'
3151--- qml/Stages/SurfaceContainer.qml 2016-04-20 17:09:15 +0000
3152+++ qml/Stages/SurfaceContainer.qml 1970-01-01 00:00:00 +0000
3153@@ -1,154 +0,0 @@
3154-/*
3155- * Copyright 2014-2015 Canonical Ltd.
3156- *
3157- * This program is free software; you can redistribute it and/or modify
3158- * it under the terms of the GNU Lesser General Public License as published by
3159- * the Free Software Foundation; version 3.
3160- *
3161- * This program is distributed in the hope that it will be useful,
3162- * but WITHOUT ANY WARRANTY; without even the implied warranty of
3163- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3164- * GNU Lesser General Public License for more details.
3165- *
3166- * You should have received a copy of the GNU Lesser General Public License
3167- * along with this program. If not, see <http://www.gnu.org/licenses/>.
3168-*/
3169-
3170-import QtQuick 2.4
3171-import Ubuntu.Components 1.3
3172-import Ubuntu.Gestures 0.1 // For TouchGate
3173-import Utils 0.1 // for InputWatcher
3174-import Unity.Application 0.1 // for MirSurfaceItem
3175-
3176-FocusScope {
3177- id: root
3178- objectName: "surfaceContainer"
3179-
3180- property var surface: null
3181- property bool hadSurface: false
3182- property bool interactive
3183- property int surfaceOrientationAngle: 0
3184- property string name: surface ? surface.name : ""
3185- property bool resizeSurface: true
3186-
3187- property int requestedWidth: -1
3188- property int requestedHeight: -1
3189-
3190- onSurfaceChanged: {
3191- if (surface) {
3192- surfaceItem.surface = surface;
3193- root.hadSurface = false;
3194- }
3195- }
3196-
3197- InputWatcher {
3198- target: surfaceItem
3199- onTargetPressedChanged: {
3200- if (targetPressed && root.interactive) {
3201- root.focus = true;
3202- root.forceActiveFocus();
3203- }
3204- }
3205- }
3206-
3207- MirSurfaceItem {
3208- id: surfaceItem
3209- objectName: "surfaceItem"
3210-
3211- fillMode: MirSurfaceItem.PadOrCrop
3212- consumesInput: true
3213-
3214- surfaceWidth: {
3215- if (root.resizeSurface) {
3216- if (root.requestedWidth >= 0) {
3217- return root.requestedWidth;
3218- } else {
3219- return width;
3220- }
3221- } else {
3222- return -1;
3223- }
3224- }
3225-
3226- surfaceHeight: {
3227- if (root.resizeSurface) {
3228- if (root.requestedHeight >= 0) {
3229- return root.requestedHeight;
3230- } else {
3231- return height;
3232- }
3233- } else {
3234- return -1;
3235- }
3236- }
3237-
3238- enabled: root.interactive
3239- focus: true
3240- antialiasing: !root.interactive
3241- orientationAngle: root.surfaceOrientationAngle
3242- }
3243-
3244- // MirSurface size drives SurfaceContainer size
3245- Binding {
3246- target: surfaceItem; property: "width"; value: root.surface ? root.surface.size.width : 0
3247- when: root.requestedWidth >= 0 && root.surface
3248- }
3249- Binding {
3250- target: surfaceItem; property: "height"; value: root.surface ? root.surface.size.height : 0
3251- when: root.requestedHeight >= 0 && root.surface
3252- }
3253- Binding {
3254- target: root; property: "width"; value: surfaceItem.width
3255- when: root.requestedWidth >= 0
3256- }
3257- Binding {
3258- target: root; property: "height"; value: surfaceItem.height
3259- when: root.requestedHeight >= 0
3260- }
3261-
3262- // SurfaceContainer size drives MirSurface size
3263- Binding {
3264- target: surfaceItem; property: "width"; value: root.width
3265- when: root.requestedWidth < 0
3266- }
3267- Binding {
3268- target: surfaceItem; property: "height"; value: root.height
3269- when: root.requestedHeight < 0
3270- }
3271-
3272-
3273- TouchGate {
3274- objectName: "touchGate-"+name
3275- targetItem: surfaceItem
3276- anchors.fill: root
3277- enabled: surfaceItem.enabled
3278- }
3279-
3280- states: [
3281- State {
3282- name: "zombie"
3283- when: surfaceItem.surface && !surfaceItem.live
3284- }
3285- ]
3286- transitions: [
3287- Transition {
3288- from: ""; to: "zombie"
3289- SequentialAnimation {
3290- UbuntuNumberAnimation { target: surfaceItem; property: "opacity"; to: 0.0
3291- duration: UbuntuAnimation.BriskDuration }
3292- PropertyAction { target: surfaceItem; property: "visible"; value: false }
3293- ScriptAction { script: {
3294- surfaceItem.surface = null;
3295- root.hadSurface = true;
3296- } }
3297- }
3298- },
3299- Transition {
3300- from: "zombie"; to: ""
3301- ScriptAction { script: {
3302- surfaceItem.opacity = 1.0;
3303- surfaceItem.visible = true;
3304- } }
3305- }
3306- ]
3307-}
3308
3309=== modified file 'qml/Stages/TabletStage.qml'
3310--- qml/Stages/TabletStage.qml 2016-04-20 17:09:15 +0000
3311+++ qml/Stages/TabletStage.qml 2016-04-20 17:09:16 +0000
3312@@ -27,19 +27,24 @@
3313 objectName: "stages"
3314 anchors.fill: parent
3315
3316+ // <tutorial-hacks> The Tutorial looks into our implementation details
3317 property alias sideStageVisible: spreadView.sideStageVisible
3318 property alias sideStageWidth: spreadView.sideStageWidth
3319+ // The stage the currently focused surface is in
3320+ property int stageFocusedSurface: priv.focusedAppDelegate ? priv.focusedAppDelegate.stage : ApplicationInfoInterface.MainStage
3321+ // </tutorial-hacks>
3322
3323 // Functions to be called from outside
3324 function updateFocusedAppOrientation() {
3325- var mainStageAppIndex = priv.indexOf(priv.mainStageAppId);
3326- if (mainStageAppIndex >= 0 && mainStageAppIndex < spreadRepeater.count) {
3327- spreadRepeater.itemAt(mainStageAppIndex).matchShellOrientation();
3328+ var mainStageIndex = root.topLevelSurfaceList.indexForId(priv.mainStageItemId);
3329+
3330+ if (priv.mainStageItemId && mainStageIndex >= 0 && mainStageIndex < spreadRepeater.count) {
3331+ spreadRepeater.itemAt(mainStageIndex).matchShellOrientation();
3332 }
3333
3334 for (var i = 0; i < spreadRepeater.count; ++i) {
3335
3336- if (i === mainStageAppIndex) {
3337+ if (i === mainStageIndex) {
3338 continue;
3339 }
3340
3341@@ -60,16 +65,14 @@
3342 }
3343 }
3344 function updateFocusedAppOrientationAnimated() {
3345- var mainStageAppIndex = priv.indexOf(priv.mainStageAppId);
3346- if (mainStageAppIndex >= 0 && mainStageAppIndex < spreadRepeater.count) {
3347- spreadRepeater.itemAt(mainStageAppIndex).animateToShellOrientation();
3348+ var mainStageIndex = root.topLevelSurfaceList.indexForId(priv.mainStageItemId);
3349+ if (priv.mainStageItemId && mainStageIndex >= 0 && mainStageIndex < spreadRepeater.count) {
3350+ spreadRepeater.itemAt(mainStageIndex).animateToShellOrientation();
3351 }
3352
3353- if (priv.sideStageAppId) {
3354- var sideStageAppIndex = priv.indexOf(priv.sideStageAppId);
3355- if (sideStageAppIndex >= 0 && sideStageAppIndex < spreadRepeater.count) {
3356- spreadRepeater.itemAt(sideStageAppIndex).matchShellOrientation();
3357- }
3358+ var sideStageIndex = root.topLevelSurfaceList.indexForId(priv.sideStageItemId);
3359+ if (sideStageIndex >= 0 && sideStageIndex < spreadRepeater.count) {
3360+ spreadRepeater.itemAt(sideStageIndex).matchShellOrientation();
3361 }
3362 }
3363
3364@@ -81,11 +84,20 @@
3365
3366 orientationChangesEnabled: priv.mainAppOrientationChangesEnabled
3367
3368+ mainApp: {
3369+ if (priv.mainStageItemId > 0) {
3370+ var index = root.topLevelSurfaceList.indexForId(priv.mainStageItemId);
3371+ return root.topLevelSurfaceList.applicationAt(index);
3372+ } else {
3373+ return null;
3374+ }
3375+ }
3376+
3377 supportedOrientations: {
3378 if (mainApp) {
3379 var orientations = mainApp.supportedOrientations;
3380 orientations |= Qt.LandscapeOrientation | Qt.InvertedLandscapeOrientation;
3381- if (priv.sideStageAppId && !spreadView.surfaceDragging) {
3382+ if (priv.sideStageItemId && !spreadView.surfaceDragging) {
3383 // If we have a sidestage app, support Portrait orientation
3384 // so that it will switch the sidestage app to mainstage on rotate
3385 orientations |= Qt.PortraitOrientation|Qt.InvertedPortraitOrientation;
3386@@ -116,7 +128,7 @@
3387 if (inverseProgress == 0 && priv.oldInverseProgress > 0) {
3388 // left edge drag released. Minimum distance is given by design.
3389 if (priv.oldInverseProgress > units.gu(22)) {
3390- ApplicationManager.requestFocusApplication("unity8-dash");
3391+ root.applicationManager.requestFocusApplication("unity8-dash");
3392 }
3393 }
3394 priv.oldInverseProgress = inverseProgress;
3395@@ -154,17 +166,51 @@
3396 }
3397 }
3398
3399+ Connections {
3400+ target: root.topLevelSurfaceList
3401+ onListChanged: priv.updateMainAndSideStageIndexes()
3402+ }
3403+
3404 QtObject {
3405 id: priv
3406 objectName: "stagesPriv"
3407
3408- property string focusedAppId: ApplicationManager.focusedApplicationId
3409- readonly property var focusedAppDelegate: {
3410- var index = indexOf(focusedAppId);
3411- return index >= 0 && index < spreadRepeater.count ? spreadRepeater.itemAt(index) : null
3412+ function updateMainAndSideStageIndexes() {
3413+ var choseMainStage = false;
3414+ var choseSideStage = false;
3415+
3416+ if (!root.topLevelSurfaceList)
3417+ return;
3418+
3419+ for (var i = 0; i < spreadRepeater.count && (!choseMainStage || !choseSideStage); ++i) {
3420+ var spreadDelegate = spreadRepeater.itemAt(i);
3421+ if (sideStage.shown && spreadDelegate.stage == ApplicationInfoInterface.SideStage
3422+ && !choseSideStage) {
3423+ priv.sideStageDelegate = spreadDelegate
3424+ priv.sideStageItemId = root.topLevelSurfaceList.idAt(i);
3425+ priv.sideStageAppId = root.topLevelSurfaceList.applicationAt(i).appId;
3426+ choseSideStage = true;
3427+ } else if (!choseMainStage && spreadDelegate.stage == ApplicationInfoInterface.MainStage) {
3428+ priv.mainStageDelegate = spreadDelegate;
3429+ priv.mainStageItemId = root.topLevelSurfaceList.idAt(i);
3430+ priv.mainStageAppId = root.topLevelSurfaceList.applicationAt(i).appId;
3431+ choseMainStage = true;
3432+ }
3433+ }
3434+ if (!choseMainStage) {
3435+ priv.mainStageDelegate = null;
3436+ priv.mainStageItemId = 0;
3437+ priv.mainStageAppId = "";
3438+ }
3439+ if (!choseSideStage) {
3440+ priv.sideStageDelegate = null;
3441+ priv.sideStageItemId = 0;
3442+ priv.sideStageAppId = "";
3443+ }
3444 }
3445
3446- property string oldFocusedAppId: ""
3447+ property var focusedAppDelegate: null
3448+
3449 property bool mainAppOrientationChangesEnabled: false
3450
3451 property real landscapeHeight: root.orientations.native_ == Qt.LandscapeOrientation ?
3452@@ -173,36 +219,21 @@
3453 property bool shellIsLandscape: root.shellOrientation === Qt.LandscapeOrientation
3454 || root.shellOrientation === Qt.InvertedLandscapeOrientation
3455
3456- property string mainStageAppId
3457- property string sideStageAppId
3458-
3459- // For convenience, keep properties of the first two apps in the model
3460- property string appId0
3461- property string appId1
3462+ property var mainStageDelegate: null
3463+ property var sideStageDelegate: null
3464+
3465+ property int mainStageItemId: 0
3466+ property int sideStageItemId: 0
3467+
3468+ property string mainStageAppId: ""
3469+ property string sideStageAppId: ""
3470
3471 property int oldInverseProgress: 0
3472
3473 property int highlightIndex: 0
3474
3475- onFocusedAppIdChanged: updateStageApps()
3476-
3477- onFocusedAppDelegateChanged: {
3478- if (focusedAppDelegate) {
3479- focusedAppDelegate.focus = true;
3480- }
3481- }
3482-
3483 property bool focusedAppDelegateIsDislocated: focusedAppDelegate &&
3484 (focusedAppDelegate.dragOffset !== 0 || focusedAppDelegate.xTranslateAnimating)
3485- function indexOf(appId) {
3486- for (var i = 0; i < ApplicationManager.count; i++) {
3487- if (ApplicationManager.get(i).appId == appId) {
3488- return i;
3489- }
3490- }
3491- return -1;
3492- }
3493-
3494 function evaluateOneWayFlick(gesturePoints) {
3495 // Need to have at least 3 points to recognize it as a flick
3496 if (gesturePoints.length < 3) {
3497@@ -231,88 +262,45 @@
3498 spreadView.contentX = highlightIndex * spreadView.contentWidth / (spreadRepeater.count + 2)
3499 }
3500
3501- function getTopApp(stage) {
3502- for (var i = 0; i < ApplicationManager.count; i++) {
3503- var app = ApplicationManager.get(i)
3504- if (app.stage === stage) {
3505- return app;
3506- }
3507- }
3508- return null;
3509- }
3510-
3511- function setAppStage(appId, stage, save) {
3512- var app = ApplicationManager.findApplication(appId);
3513- if (app) {
3514- app.stage = stage;
3515- if (save) {
3516- WindowStateStorage.saveStage(appId, stage);
3517- }
3518- }
3519- }
3520-
3521- function updateStageApps() {
3522- var app = priv.getTopApp(ApplicationInfoInterface.MainStage);
3523- priv.mainStageAppId = app ? app.appId : ""
3524- root.mainApp = app;
3525-
3526- if (sideStage.shown) {
3527- app = priv.getTopApp(ApplicationInfoInterface.SideStage);
3528- priv.sideStageAppId = app ? app.appId : ""
3529- } else {
3530- priv.sideStageAppId = "";
3531- }
3532-
3533- appId0 = ApplicationManager.count >= 1 ? ApplicationManager.get(0).appId : "";
3534- appId1 = ApplicationManager.count > 1 ? ApplicationManager.get(1).appId : "";
3535- }
3536-
3537 readonly property bool sideStageEnabled: root.shellOrientation == Qt.LandscapeOrientation ||
3538 root.shellOrientation == Qt.InvertedLandscapeOrientation
3539- Component.onCompleted: updateStageApps();
3540- }
3541-
3542- Connections {
3543- target: ApplicationManager
3544- onFocusRequested: {
3545- if (spreadView.interactive) {
3546- spreadView.snapTo(priv.indexOf(appId));
3547- } else {
3548- ApplicationManager.focusApplication(appId);
3549- }
3550- }
3551-
3552- onApplicationAdded: {
3553- if (spreadView.phase == 2) {
3554- spreadView.snapTo(ApplicationManager.count - 1);
3555- } else {
3556- spreadView.phase = 0;
3557- spreadView.contentX = -spreadView.shift;
3558- ApplicationManager.focusApplication(appId);
3559- }
3560- }
3561-
3562- onApplicationRemoved: {
3563- if (priv.mainStageAppId == appId) {
3564- ApplicationManager.focusApplication("unity8-dash")
3565- }
3566- if (priv.sideStageAppId == appId) {
3567- var app = priv.getTopApp(ApplicationInfoInterface.SideStage);
3568- priv.sideStageAppId = app === null ? "" : app.appId;
3569- }
3570-
3571- if (ApplicationManager.count == 0) {
3572- spreadView.phase = 0;
3573- spreadView.contentX = -spreadView.shift;
3574- } else if (spreadView.closingIndex == -1) {
3575- // Unless we're closing the app ourselves in the spread,
3576- // lets make sure the spread doesn't mess up by the changing app list.
3577- spreadView.phase = 0;
3578- spreadView.contentX = -spreadView.shift;
3579-
3580- ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
3581- }
3582- }
3583+ }
3584+
3585+ Instantiator {
3586+ model: root.applicationManager
3587+ delegate: QtObject {
3588+ property var stateBinding: Binding {
3589+ readonly property bool isDash: model.application ? model.application.appId == "unity8-dash" : false
3590+ target: model.application
3591+ property: "requestedState"
3592+
3593+ // NB: the first application clause is just to ensure we never get warnings for trying to access
3594+ // members of a null variable.
3595+ value: model.application &&
3596+ (
3597+ (isDash && root.keepDashRunning)
3598+ || (!root.suspended && (model.application.appId === priv.mainStageAppId
3599+ || model.application.appId === priv.sideStageAppId))
3600+ )
3601+ ? ApplicationInfoInterface.RequestedRunning
3602+ : ApplicationInfoInterface.RequestedSuspended
3603+ }
3604+
3605+ property var lifecycleBinding: Binding {
3606+ target: model.application
3607+ property: "exemptFromLifecycle"
3608+ value: model.application
3609+ ? (!model.application.isTouchApp || isExemptFromLifecycle(model.application.appId))
3610+ : false
3611+ }
3612+ }
3613+ }
3614+
3615+ Binding {
3616+ target: MirFocusController
3617+ property: "focusedSurface"
3618+ value: priv.focusedAppDelegate ? priv.focusedAppDelegate.surface : null
3619+ when: root.parent && !spreadRepeater.startingUp
3620 }
3621
3622 Flickable {
3623@@ -363,16 +351,45 @@
3624 property int selectedIndex: -1
3625 property int draggedDelegateCount: 0
3626 property int closingIndex: -1
3627- property var selectedApplication: selectedIndex !== -1 ? ApplicationManager.get(selectedIndex) : null
3628+ property var selectedDelegate: selectedIndex !== -1 ? spreadRepeater.itemAt(selectedIndex) : null
3629
3630- // FIXME: Workaround Flickable's not keepping its contentX still when resized
3631- onContentXChanged: { forceItToRemainStillIfBeingResized(); }
3632+ // <FIXME-contentX> Workaround Flickable's behavior of bringing contentX back between valid boundaries
3633+ // when resized. The proper way to fix this is refactoring PhoneStage so that it doesn't
3634+ // rely on having Flickable.contentX keeping an out-of-bounds value when it's set programatically
3635+ // (as opposed to having contentX reaching an out-of-bounds value through dragging, which will trigger
3636+ // the Flickable.boundsBehavior upon release).
3637+ onContentXChanged: {
3638+ if (!undoContentXReset()) {
3639+ forceItToRemainStillIfBeingResized();
3640+ }
3641+ }
3642 onShiftChanged: { forceItToRemainStillIfBeingResized(); }
3643 function forceItToRemainStillIfBeingResized() {
3644- if (root.beingResized && contentX != -shift) {
3645+ if (root.beingResized && contentX != -spreadView.shift) {
3646+ contentX = -spreadView.shift;
3647+ }
3648+ }
3649+ function undoContentXReset() {
3650+ if (contentWidth <= 0) {
3651+ contentWidthOnLastContentXChange = contentWidth;
3652+ lastContentX = contentX;
3653+ return false;
3654+ }
3655+
3656+ if (contentWidth != contentWidthOnLastContentXChange
3657+ && lastContentX == -shift && contentX == 0) {
3658+ // Flickable is resetting contentX because contentWidth has changed. Undo it.
3659 contentX = -shift;
3660+ return true;
3661 }
3662+
3663+ contentWidthOnLastContentXChange = contentWidth;
3664+ lastContentX = contentX;
3665+ return false;
3666 }
3667+ property real contentWidthOnLastContentXChange: -1
3668+ property real lastContentX: 0
3669+ // </FIXME-contentX>
3670
3671 property bool animateX: true
3672 property bool beingResized: root.beingResized
3673@@ -385,31 +402,33 @@
3674 }
3675 }
3676
3677- property real sideStageDragProgress: sideStage.progress
3678- property bool sideStageVisible: priv.sideStageAppId
3679 property real sideStageWidth: units.gu(40)
3680
3681 property bool surfaceDragging: triGestureArea.recognisedDrag
3682
3683- // In case the ApplicationManager already holds an app when starting up we're missing animations
3684+ readonly property bool sideStageVisible: priv.sideStageItemId != 0
3685+
3686+ // In case applicationManager already holds an app when starting up we're missing animations
3687 // Make sure we end up in the same state
3688 Component.onCompleted: {
3689 spreadView.contentX = -spreadView.shift
3690 }
3691
3692 property int nextInStack: {
3693+ var mainStageIndex = priv.mainStageDelegate ? priv.mainStageDelegate.index : -1;
3694+ var sideStageIndex = priv.sideStageDelegate ? priv.sideStageDelegate.index : -1;
3695 switch (state) {
3696 case "main":
3697- if (ApplicationManager.count > 1) {
3698+ if (root.topLevelSurfaceList.count > 1) {
3699 return 1;
3700 }
3701 return -1;
3702 case "mainAndOverlay":
3703- if (ApplicationManager.count <= 2) {
3704+ if (root.topLevelSurfaceList.count <= 2) {
3705 return -1;
3706 }
3707- if (priv.appId0 == priv.mainStageAppId || priv.appId0 == priv.sideStageAppId) {
3708- if (priv.appId1 == priv.mainStageAppId || priv.appId1 == priv.sideStageAppId) {
3709+ if (mainStageIndex == 0 || sideStageIndex == 0) {
3710+ if (mainStageIndex == 1 || sideStageIndex == 1) {
3711 return 2;
3712 }
3713 return 1;
3714@@ -420,7 +439,7 @@
3715 }
3716 return -1;
3717 }
3718- property int nextZInStack: indexToZIndex(nextInStack)
3719+ property int nextZInStack
3720
3721 states: [
3722 State {
3723@@ -440,13 +459,13 @@
3724 }
3725 ]
3726 state: {
3727- if ((priv.mainStageAppId && !priv.sideStageAppId) || !priv.sideStageEnabled) {
3728+ if ((priv.mainStageItemId && !priv.sideStageItemId) || !priv.sideStageEnabled) {
3729 return "main";
3730 }
3731- if (!priv.mainStageAppId && priv.sideStageAppId) {
3732+ if (!priv.mainStageItemId && priv.sideStageItemId) {
3733 return "overlay";
3734 }
3735- if (priv.mainStageAppId && priv.sideStageAppId) {
3736+ if (priv.mainStageItemId && priv.sideStageItemId) {
3737 return "mainAndOverlay";
3738 }
3739 return "empty";
3740@@ -511,38 +530,41 @@
3741 // only shuffle when we've got a main and overlay
3742 if (state !== "mainAndOverlay") return index;
3743
3744- var app = ApplicationManager.get(index);
3745+ var app = root.topLevelSurfaceList.applicationAt(index);
3746 if (!app) {
3747 return index;
3748 }
3749+ var stage = spreadRepeater.itemAt(index) ? spreadRepeater.itemAt(index).stage : app.stage;
3750
3751 // don't shuffle indexes greater than "actives or next"
3752 if (index > 2) return index;
3753
3754- if (app.appId === priv.mainStageAppId) {
3755+ var mainStageIndex = root.topLevelSurfaceList.indexForId(priv.mainStageItemId);
3756+
3757+ if (index == mainStageIndex) {
3758 // Active main stage always at 0
3759 return 0;
3760 }
3761
3762 if (spreadView.nextInStack > 0) {
3763- var nextAppInStack = ApplicationManager.get(spreadView.nextInStack);
3764+ var stageOfNextInStack = spreadRepeater.itemAt(spreadView.nextInStack).stage;
3765
3766 if (index === spreadView.nextInStack) {
3767 // this is the next app in stack.
3768
3769- if (app.stage === ApplicationInfoInterface.SideStage) {
3770+ if (stage === ApplicationInfoInterface.SideStage) {
3771 // if the next app in stack is a sidestage app, it must order on top of other side stage app
3772- return Math.min(2, ApplicationManager.count-1);
3773+ return Math.min(2, root.topLevelSurfaceList.count-1);
3774 }
3775 return 1;
3776 }
3777- if (nextAppInStack.stage === ApplicationInfoInterface.SideStage) {
3778+ if (stageOfNextInStack === ApplicationInfoInterface.SideStage) {
3779 // if the next app in stack is a sidestage app, it must order on top of other side stage app
3780 return 1;
3781 }
3782- return Math.min(2, ApplicationManager.count-1);
3783+ return Math.min(2, root.topLevelSurfaceList.count-1);
3784 }
3785- return Math.min(index+1, ApplicationManager.count-1);
3786+ return Math.min(index+1, root.topLevelSurfaceList.count-1);
3787 }
3788
3789 SequentialAnimation {
3790@@ -560,12 +582,13 @@
3791 script: {
3792 if (spreadView.selectedIndex >= 0) {
3793 var newIndex = spreadView.selectedIndex;
3794- var application = ApplicationManager.get(newIndex);
3795- if (application.stage === ApplicationInfoInterface.SideStage) {
3796+ var application = root.topLevelSurfaceList.applicationAt(newIndex);
3797+ var spreadDelegate = spreadRepeater.itemAt(newIndex);
3798+ if (spreadDelegate.stage === ApplicationInfoInterface.SideStage) {
3799 sideStage.showNow();
3800 }
3801 spreadView.selectedIndex = -1;
3802- ApplicationManager.focusApplication(application.appId);
3803+ spreadDelegate.focus = true;
3804 spreadView.phase = 0;
3805 spreadView.contentX = -spreadView.shift;
3806 }
3807@@ -581,7 +604,7 @@
3808 MouseArea {
3809 id: spreadRow
3810 x: spreadView.contentX
3811- width: spreadView.width + Math.max(spreadView.width, ApplicationManager.count * spreadView.tileDistance)
3812+ width: spreadView.width + Math.max(spreadView.width, root.topLevelSurfaceList.count * spreadView.tileDistance)
3813 height: root.height
3814
3815 onClicked: {
3816@@ -599,8 +622,8 @@
3817 enabled: priv.sideStageEnabled
3818
3819 onDropped: {
3820- priv.setAppStage(drag.source.appId, ApplicationInfoInterface.MainStage, true);
3821- ApplicationManager.focusApplication(drag.source.appId);
3822+ drop.source.spreadDelegate.stage = ApplicationInfoInterface.MainStage;
3823+ drop.source.spreadDelegate.focus = true;
3824 }
3825 keys: "SideStage"
3826 }
3827@@ -611,12 +634,12 @@
3828 height: priv.landscapeHeight
3829 x: spreadView.width - width
3830 z: {
3831- if (!priv.mainStageAppId) return 0;
3832-
3833- if (priv.sideStageAppId && spreadView.nextInStack > 0) {
3834- var nextAppInStack = ApplicationManager.get(spreadView.nextInStack);
3835-
3836- if (nextAppInStack.stage === ApplicationInfoInterface.MainStage) {
3837+ if (!priv.mainStageItemId) return 0;
3838+
3839+ if (priv.sideStageItemId && spreadView.nextInStack > 0) {
3840+ var nextDelegateInStack = spreadRepeater.itemAt(spreadView.nextInStack);
3841+
3842+ if (nextDelegateInStack.stage === ApplicationInfoInterface.MainStage) {
3843 // if the next app in stack is a main stage app, put the sidestage on top of it.
3844 return 2;
3845 }
3846@@ -631,12 +654,11 @@
3847 Behavior on opacity { UbuntuNumberAnimation {} }
3848
3849 onShownChanged: {
3850- if (!shown && ApplicationManager.focusedApplicationId == priv.sideStageAppId) {
3851- ApplicationManager.requestFocusApplication(priv.mainStageAppId);
3852- }
3853- priv.updateStageApps();
3854- if (shown && priv.sideStageAppId) {
3855- ApplicationManager.requestFocusApplication(priv.sideStageAppId);
3856+ if (!shown && priv.sideStageDelegate && priv.focusedAppDelegate === priv.sideStageDelegate
3857+ && priv.mainStageDelegate) {
3858+ priv.mainStageDelegate.focus = true;
3859+ } else if (shown && priv.sideStageDelegate) {
3860+ priv.sideStageDelegate.focus = true;
3861 }
3862 }
3863
3864@@ -655,8 +677,8 @@
3865 }
3866 onDropped: {
3867 if (drop.keys == "MainStage") {
3868- priv.setAppStage(drop.source.appId, ApplicationInfoInterface.SideStage, true);
3869- ApplicationManager.requestFocusApplication(drop.source.appId);
3870+ drop.source.spreadDelegate.stage = ApplicationInfoInterface.SideStage;
3871+ drop.source.spreadDelegate.focus = true;
3872 }
3873 }
3874 drag {
3875@@ -669,41 +691,102 @@
3876 }
3877 }
3878
3879- Repeater {
3880+ TopLevelSurfaceRepeater {
3881 id: spreadRepeater
3882 objectName: "spreadRepeater"
3883- model: ApplicationManager
3884+ model: root.topLevelSurfaceList
3885+
3886+ onItemAdded: {
3887+ priv.updateMainAndSideStageIndexes();
3888+ if (spreadView.phase == 2) {
3889+ spreadView.snapTo(index);
3890+ }
3891+ }
3892+
3893+ onItemRemoved: {
3894+ priv.updateMainAndSideStageIndexes();
3895+ // Unless we're closing the app ourselves in the spread,
3896+ // lets make sure the spread doesn't mess up by the changing app list.
3897+ if (spreadView.closingIndex == -1) {
3898+ spreadView.phase = 0;
3899+ spreadView.contentX = -spreadView.shift;
3900+ focusTopMostApp();
3901+ }
3902+ }
3903+ function focusTopMostApp() {
3904+ if (spreadRepeater.count > 0) {
3905+ var topmostDelegate = spreadRepeater.itemAt(0);
3906+ topmostDelegate.focus = true;
3907+ }
3908+ }
3909
3910 delegate: TransformedTabletSpreadDelegate {
3911 id: spreadTile
3912- objectName: model.appId ? "tabletSpreadDelegate_" + model.appId
3913- : "tabletSpreadDelegate_null";
3914+ objectName: "spreadDelegate_" + model.id
3915+
3916+ readonly property int index: model.index
3917 width: spreadView.width
3918 height: spreadView.height
3919- active: appId == priv.mainStageAppId || appId == priv.sideStageAppId
3920+ active: model.id == priv.mainStageItemId || model.id == priv.sideStageItemId
3921 zIndex: selected && stage == ApplicationInfoInterface.MainStage ? 0 : spreadView.indexToZIndex(index)
3922+ onZIndexChanged: {
3923+ if (spreadView.nextInStack == model.index) {
3924+ spreadView.nextZInStack = zIndex;
3925+ }
3926+ }
3927 selected: spreadView.selectedIndex == index
3928 otherSelected: spreadView.selectedIndex >= 0 && !selected
3929- isInSideStage: priv.sideStageAppId === appId
3930+ isInSideStage: priv.sideStageItemId == model.id
3931 interactive: !spreadView.interactive && spreadView.phase === 0 && root.interactive
3932 swipeToCloseEnabled: spreadView.interactive && !snapAnimation.running
3933 maximizedAppTopMargin: root.maximizedAppTopMargin
3934- dragOffset: !isDash && appId == priv.mainStageAppId && root.inverseProgress > 0 && spreadView.phase === 0 ? root.inverseProgress : 0
3935- application: ApplicationManager.get(index)
3936+ dragOffset: !isDash && model.id == priv.mainStageItemId && root.inverseProgress > 0
3937+ && spreadView.phase === 0 ? root.inverseProgress : 0
3938+ application: model.application
3939+ surface: model.surface
3940 closeable: !isDash
3941 highlightShown: root.altTabPressed && priv.highlightIndex == zIndex
3942
3943- readonly property bool wantsMainStage: model.stage == ApplicationInfoInterface.MainStage
3944-
3945- readonly property string appId: model.appId
3946- readonly property bool isDash: model.appId == "unity8-dash"
3947-
3948- stage: model.stage
3949+ readonly property bool wantsMainStage: stage == ApplicationInfoInterface.MainStage
3950+
3951+ readonly property bool isDash: model.application.appId == "unity8-dash"
3952+
3953+ onFocusChanged: {
3954+ if (focus && !spreadRepeater.startingUp) {
3955+ priv.focusedAppDelegate = spreadTile;
3956+ root.topLevelSurfaceList.raiseId(model.id);
3957+ }
3958+ if (focus && priv.sideStageEnabled && stage === ApplicationInfoInterface.SideStage) {
3959+ sideStage.show();
3960+ }
3961+ }
3962+ Connections {
3963+ target: model.surface
3964+ onFocusRequested: spreadTile.focus = true;
3965+ }
3966+ Connections {
3967+ target: model.application
3968+ onFocusRequested: {
3969+ if (!model.surface) {
3970+ // when an app has no surfaces, we assume there's only one entry representing it:
3971+ // this delegate.
3972+ spreadTile.focus = true;
3973+ } else {
3974+ // if the application has surfaces, focus request should be at surface-level.
3975+ }
3976+ }
3977+ }
3978+
3979 fullscreen: {
3980- if (mainApp && stage === ApplicationInfoInterface.SideStage) {
3981- return mainApp.fullscreen;
3982+ if (priv.mainStageDelegate && stage === ApplicationInfoInterface.SideStage) {
3983+ return priv.mainStageDelegate.fullscreen;
3984+ } else if (surface) {
3985+ return surface.state === Mir.FullscreenState;
3986+ } else if (application) {
3987+ return application.fullscreen;
3988+ } else {
3989+ return false;
3990 }
3991- return application ? application.fullscreen : false;
3992 }
3993
3994 supportedOrientations: {
3995@@ -724,23 +807,6 @@
3996 }
3997 }
3998
3999-
4000- Binding {
4001- target: spreadTile.application
4002- property: "exemptFromLifecycle"
4003- value: !model.isTouchApp || isExemptFromLifecycle(model.appId)
4004- }
4005-
4006- Binding {
4007- target: spreadTile.application
4008- property: "requestedState"
4009- value: (isDash && root.keepDashRunning)
4010- || (!root.suspended && (model.appId == priv.mainStageAppId
4011- || model.appId == priv.sideStageAppId))
4012- ? ApplicationInfoInterface.RequestedRunning
4013- : ApplicationInfoInterface.RequestedSuspended
4014- }
4015-
4016 // FIXME: A regular binding doesn't update any more after closing an app.
4017 // Using a Binding for now.
4018 Binding {
4019@@ -760,34 +826,33 @@
4020 onSideStageEnabledChanged: refreshStage()
4021 }
4022
4023+ property bool _constructing: true;
4024+ onStageChanged: {
4025+ if (!_constructing) {
4026+ priv.updateMainAndSideStageIndexes();
4027+ }
4028+ }
4029+
4030 Component.onCompleted: {
4031- refreshStage()
4032- stageChanged.connect(priv.updateStageApps);
4033+ // a top level window is always the focused one when it first appears, unfocusing
4034+ // any preexisting one
4035+ focus = true;
4036+ refreshStage();
4037+ _constructing = false;
4038+ }
4039+ Component.onDestruction: {
4040+ WindowStateStorage.saveStage(model.application.appId, stage);
4041 }
4042
4043 function refreshStage() {
4044- var stage = ApplicationInfoInterface.MainStage;
4045+ var newStage = ApplicationInfoInterface.MainStage;
4046 if (priv.sideStageEnabled) {
4047 if (application && application.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) {
4048- stage = WindowStateStorage.getStage(appId);
4049+ newStage = WindowStateStorage.getStage(application.appId);
4050 }
4051 }
4052
4053- if (model.stage !== stage) {
4054- priv.setAppStage(appId, stage, false);
4055- }
4056- }
4057-
4058- // This is required because none of the bindings are triggered in some cases:
4059- // When an app is closed, it might happen that ApplicationManager.get(nextInStack)
4060- // returns a different app even though the nextInStackIndex and all the related
4061- // bindings (index, mainStageApp, sideStageApp, etc) don't change. Let's force a
4062- // binding update in that case.
4063- Connections {
4064- target: ApplicationManager
4065- onApplicationRemoved: spreadTile.z = Qt.binding(function() {
4066- return spreadView.indexToZIndex(index);
4067- })
4068+ stage = newStage;
4069 }
4070
4071 progress: {
4072@@ -873,13 +938,10 @@
4073 script: {
4074 // rotate immediately.
4075 spreadTile.matchShellOrientation();
4076- if (ApplicationManager.focusedApplicationId === spreadTile.appId &&
4077+ if (priv.focusedAppDelegate === spreadTile &&
4078 priv.sideStageEnabled && !sideStage.shown) {
4079 // Sidestage was focused, so show the side stage.
4080 sideStage.show();
4081- // if we've switched to a main app which doesnt support portrait, hide the side stage.
4082- } else if (mainApp && (mainApp.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) == 0) {
4083- sideStage.hideNow();
4084 }
4085 }
4086 }
4087@@ -890,10 +952,10 @@
4088 SequentialAnimation {
4089 ScriptAction {
4090 script: {
4091- if (priv.sideStageAppId === spreadTile.appId &&
4092+ if (priv.sideStageDelegate === spreadTile &&
4093 mainApp && (mainApp.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) == 0) {
4094 // The mainstage app did not natively support portrait orientation, so focus the sidestage.
4095- ApplicationManager.requestFocusApplication(spreadTile.appId);
4096+ spreadTile.focus = true;
4097 }
4098 }
4099 }
4100@@ -922,28 +984,26 @@
4101
4102 onClosed: {
4103 spreadView.closingIndex = index;
4104- ApplicationManager.stopApplication(ApplicationManager.get(index).appId);
4105- }
4106-
4107- onFocusChanged: {
4108- if (focus && ApplicationManager.focusedApplicationId !== appId) {
4109- ApplicationManager.focusApplication(appId);
4110- }
4111-
4112- if (focus && priv.sideStageEnabled && stage === ApplicationInfoInterface.SideStage) {
4113- sideStage.show();
4114+ if (spreadTile.surface) {
4115+ spreadTile.surface.close();
4116+ } else if (spreadTile.application) {
4117+ root.applicationManager.stopApplication(spreadTile.application.appId);
4118+ } else {
4119+ // should never happen
4120+ console.warn("Can't close topLevelSurfaceList entry as it has neither"
4121+ + " a surface nor an application");
4122 }
4123 }
4124
4125 Binding {
4126 target: root
4127- when: model.appId == priv.mainStageAppId
4128+ when: model.id == priv.mainStageItemId
4129 property: "mainAppWindowOrientationAngle"
4130 value: appWindowOrientationAngle
4131 }
4132 Binding {
4133 target: priv
4134- when: model.appId == priv.mainStageAppId
4135+ when: model.id == priv.mainStageItemId
4136 property: "mainAppOrientationChangesEnabled"
4137 value: orientationChangesEnabled
4138 }
4139@@ -957,7 +1017,7 @@
4140
4141 StagedFullscreenPolicy {
4142 id: fullscreenPolicy
4143- application: spreadTile.application
4144+ surface: model.surface
4145 }
4146 Connections {
4147 target: root
4148@@ -973,17 +1033,19 @@
4149 anchors.fill: parent
4150 enabled: priv.sideStageEnabled && !spreadView.active
4151 property var dragObject: null
4152- property string appId: ""
4153+
4154+ property Item spreadDelegate
4155+
4156 dragComponent: dragComponent
4157- dragComponentProperties: { "appId": appId }
4158+ dragComponentProperties: { "spreadDelegate": spreadDelegate }
4159
4160 onPressed: {
4161- function matchDelegate(obj) { return String(obj.objectName).indexOf("tabletSpreadDelegate") >= 0; }
4162+ function matchDelegate(obj) { return String(obj.objectName).indexOf("spreadDelegate") >= 0; }
4163
4164 var delegateAtCenter = Functions.itemAt(spreadRow, x, y, matchDelegate);
4165 if (!delegateAtCenter) return;
4166
4167- appId = delegateAtCenter.appId;
4168+ spreadDelegate = delegateAtCenter;
4169 }
4170
4171 onClicked: {
4172@@ -1003,11 +1065,11 @@
4173
4174 Component {
4175 id: dragComponent
4176- SessionContainer {
4177- property string appId: ""
4178- property var application: ApplicationManager.findApplication(appId)
4179-
4180- session: application ? application.session : null
4181+ SurfaceContainer {
4182+ property Item spreadDelegate
4183+
4184+ surface: spreadDelegate ? spreadDelegate.surface : null
4185+
4186 interactive: false
4187 resizeSurface: false
4188 focus: false
4189@@ -1019,10 +1081,11 @@
4190 Drag.hotSpot.y: height/2
4191 // only accept opposite stage.
4192 Drag.keys: {
4193- if (!application) return "Disabled";
4194+ if (!surface) return "Disabled";
4195
4196- if (application.stage === ApplicationInfo.MainStage) {
4197- if (application.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) {
4198+ if (spreadDelegate.stage === ApplicationInfo.MainStage) {
4199+ if (spreadDelegate.application.supportedOrientations
4200+ & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) {
4201 return "MainStage";
4202 }
4203 return "Disabled";
4204
4205=== added file 'qml/Stages/TopLevelSurfaceRepeater.qml'
4206--- qml/Stages/TopLevelSurfaceRepeater.qml 1970-01-01 00:00:00 +0000
4207+++ qml/Stages/TopLevelSurfaceRepeater.qml 2016-04-20 17:09:16 +0000
4208@@ -0,0 +1,52 @@
4209+/*
4210+ * Copyright (C) 2016 Canonical, Ltd.
4211+ *
4212+ * This program is free software; you can redistribute it and/or modify
4213+ * it under the terms of the GNU General Public License as published by
4214+ * the Free Software Foundation; version 3.
4215+ *
4216+ * This program is distributed in the hope that it will be useful,
4217+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4218+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4219+ * GNU General Public License for more details.
4220+ *
4221+ * You should have received a copy of the GNU General Public License
4222+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4223+ */
4224+
4225+import QtQuick 2.4
4226+
4227+Repeater {
4228+ id: root
4229+ // FIXME: This is a hack around us not knowing whether the Repeater has finished creating its
4230+ // delegates on start up.
4231+ // This is a problem when the stage gets a TopLevelSurfaceList already populated with several
4232+ // rows.
4233+ property bool startingUp: true
4234+ onStartingUpChanged: {
4235+ if (!startingUp) {
4236+ // the top-most surface must be the focused one.
4237+ var topmostDelegate = itemAt(0);
4238+ topmostDelegate.focus = true;
4239+ }
4240+ }
4241+
4242+ onItemAdded: {
4243+ if (startingUp) {
4244+ checkIfStillStartingUp();
4245+ }
4246+ }
4247+
4248+ function checkIfStillStartingUp() {
4249+ var i = 0;
4250+ var missingDelegate = false;
4251+ for (i = 0; i < model.count && !missingDelegate; ++i) {
4252+ if (!itemAt(i)) {
4253+ missingDelegate = true;
4254+ }
4255+ }
4256+ if (!missingDelegate) {
4257+ startingUp = false;
4258+ }
4259+ }
4260+}
4261
4262=== modified file 'qml/Stages/TransformedSpreadDelegate.qml'
4263--- qml/Stages/TransformedSpreadDelegate.qml 2015-07-15 15:07:19 +0000
4264+++ qml/Stages/TransformedSpreadDelegate.qml 2016-04-20 17:09:16 +0000
4265@@ -1,5 +1,5 @@
4266 /*
4267- * Copyright 2014 Canonical Ltd.
4268+ * Copyright 2014,2016 Canonical Ltd.
4269 *
4270 * This program is free software; you can redistribute it and/or modify
4271 * it under the terms of the GNU Lesser General Public License as published by
4272@@ -312,7 +312,7 @@
4273 // non-fullscreen window when they're stacked on top of each other on the
4274 // far left of the spread.
4275 Translate {
4276- y: !fullscreen && appWindowRotation === 180
4277+ y: !root.fullscreen && appWindowRotation === 180
4278 ? priv.topMarginProgress * maximizedAppTopMargin
4279 : 0
4280 },
4281@@ -326,7 +326,7 @@
4282 switch (appWindowRotation) {
4283 case 90:
4284 case 270:
4285- if (fullscreen) {
4286+ if (root.fullscreen) {
4287 return 1;
4288 } else {
4289 return 1 + priv.topMarginProgress * maximizedAppTopMargin / spreadView.width;
4290@@ -340,7 +340,7 @@
4291 yScale: {
4292 switch (appWindowRotation) {
4293 case 0:
4294- if (fullscreen) {
4295+ if (root.fullscreen) {
4296 return 1 - priv.topMarginProgress * maximizedAppTopMargin / spreadView.height;
4297 } else {
4298 return 1;
4299
4300=== modified file 'qml/Stages/TransformedTabletSpreadDelegate.qml'
4301--- qml/Stages/TransformedTabletSpreadDelegate.qml 2016-03-11 20:18:12 +0000
4302+++ qml/Stages/TransformedTabletSpreadDelegate.qml 2016-04-20 17:09:16 +0000
4303@@ -31,7 +31,6 @@
4304 // Set this to true when this tile a currently active on either the MS or the SS.
4305 property bool active: false
4306
4307- property int stage
4308 property int zIndex
4309 property real progress: 0
4310 property real animatedProgress: 0
4311@@ -48,13 +47,15 @@
4312
4313 property bool isInSideStage: false
4314
4315+ property int stage: ApplicationInfoInterface.MainStage
4316+
4317 property int dragOffset: 0
4318 readonly property alias xTranslateAnimating: xTranslateAnimation.running
4319 readonly property bool offScreen: priv.xTranslate >= 0
4320
4321 dropShadow: spreadView.active ||
4322 (active
4323- && (stage == ApplicationInfoInterface.MainStage || !priv.shellIsLandscape)
4324+ && (root.stage == ApplicationInfoInterface.MainStage || !priv.shellIsLandscape)
4325 && priv.xTranslate != 0)
4326
4327 onSelectedChanged: {
4328@@ -90,7 +91,7 @@
4329 id: priv
4330
4331 // true if this is the next tile on the stack that comes in when dragging from the right
4332- property bool nextInStack: spreadView.nextZInStack == zIndex
4333+ property bool nextInStack: spreadView.nextInStack == model.index
4334 // true if the next tile in the stack is the nextInStack one. This one will be moved a bit to the left
4335 property bool movedActive: spreadView.nextZInStack == zIndex + 1
4336 property real animatedEndDistance: linearAnimation(0, 2, root.endDistance, 0, root.progress)
4337@@ -142,7 +143,7 @@
4338 Behavior on xTranslate {
4339 enabled: !spreadView.active &&
4340 !snapAnimation.running &&
4341- model.appId !== "unity8-dash" &&
4342+ root.application.appId !== "unity8-dash" &&
4343 spreadView.animateX &&
4344 !spreadView.beingResized &&
4345 priv.state !== "sideStage"
4346@@ -156,8 +157,9 @@
4347 var newTranslate = 0;
4348
4349 // selected app or opposite stage active app.
4350- if (isSelected || (otherSelected && root.active && spreadView.selectedApplication && spreadView.selectedApplication.stage !== stage)) {
4351- if (stage == ApplicationInfoInterface.MainStage) {
4352+ if (isSelected || (otherSelected && root.active && spreadView.selectedDelegate
4353+ && spreadView.selectedDelegate.stage !== root.stage)) {
4354+ if (root.stage == ApplicationInfoInterface.MainStage) {
4355 return linearAnimation(selectedProgress, negativeProgress, selectedXTranslate, -spreadView.width, root.progress);
4356 } else {
4357 return linearAnimation(selectedProgress, negativeProgress, selectedXTranslate, -spreadView.sideStageWidth, root.progress);
4358@@ -168,8 +170,8 @@
4359
4360 // The tile should move a bit to the left if a new one comes on top of it, but not for the Side Stage and not
4361 // when we're only dragging the side stage in on top of a main stage app
4362- var shouldMoveAway = spreadView.nextInStack >= 0 && priv.movedActive && stage === ApplicationInfoInterface.MainStage &&
4363- ApplicationManager.get(spreadView.nextInStack).stage === ApplicationInfoInterface.MainStage;
4364+ var shouldMoveAway = spreadView.nextInStack >= 0 && priv.movedActive && root.stage === ApplicationInfoInterface.MainStage &&
4365+ topLevelSurfaceList.applicationAt(spreadView.nextInStack).stage === ApplicationInfoInterface.MainStage;
4366
4367 if (active) {
4368 newTranslate -= root.width
4369@@ -178,7 +180,7 @@
4370 newTranslate += linearAnimation(0, spreadView.positionMarker2, 0, -units.gu(4), root.animatedProgress);
4371 }
4372 newTranslate += root.dragOffset;
4373- } else if (!spreadView.active && model.appId == "unity8-dash") {
4374+ } else if (!spreadView.active && root.application.appId == "unity8-dash") {
4375 newTranslate -= root.width;
4376 }
4377
4378@@ -190,14 +192,14 @@
4379
4380 if (spreadView.phase == 0) {
4381 if (nextInStack) {
4382- if (stage == ApplicationInfoInterface.MainStage) {
4383+ if (root.stage == ApplicationInfoInterface.MainStage) {
4384 if (spreadView.sideStageVisible && root.progress > 0) {
4385 // Move it so it appears from behind the side stage immediately
4386 newTranslate += -spreadView.sideStageWidth;
4387 }
4388 }
4389
4390- if (stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
4391+ if (root.stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
4392 // This is when we only drag the side stage in, without rotation or snapping
4393 newTranslate = linearAnimation(0, spreadView.positionMarker2, 0, -spreadView.sideStageWidth, root.animatedProgress);
4394 } else {
4395@@ -210,7 +212,7 @@
4396
4397 if (spreadView.phase == 1) {
4398 if (nextInStack) {
4399- if (stage == ApplicationInfoInterface.MainStage) {
4400+ if (root.stage == ApplicationInfoInterface.MainStage) {
4401 var startValue = -spreadView.sideStageWidth * spreadView.snapPosition + (spreadView.sideStageVisible ? -spreadView.sideStageWidth : 0);
4402 newTranslate += linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, startValue, priv.phase2StartTranslate, root.animatedProgress);
4403 } else {
4404@@ -250,7 +252,8 @@
4405 }
4406
4407 // selected app or opposite stage active app.
4408- if (isSelected || (otherSelected && root.active && spreadView.selectedApplication && spreadView.selectedApplication.stage !== stage)) {
4409+ if (isSelected || (otherSelected && root.active && spreadView.selectedDelegate
4410+ && spreadView.selectedDelegate.stage !== root.stage)) {
4411 return linearAnimation(selectedProgress, negativeProgress, selectedScale, 1, root.progress);
4412 } else if (otherSelected) {
4413 return selectedScale;
4414@@ -258,7 +261,7 @@
4415
4416 if (spreadView.phase == 0) {
4417 if (nextInStack) {
4418- if (stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
4419+ if (root.stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
4420 return 1;
4421 } else {
4422 var targetScale = root.dragStartScale - ((root.dragStartScale - 1) * spreadView.snapPosition);
4423@@ -272,7 +275,7 @@
4424 if (spreadView.phase == 1) {
4425 if (nextInStack) {
4426 var startScale = 1;
4427- if (stage !== ApplicationInfoInterface.SideStage || spreadView.sideStageVisible) {
4428+ if (root.stage !== ApplicationInfoInterface.SideStage || spreadView.sideStageVisible) {
4429 startScale = root.dragStartScale - ((root.dragStartScale - 1) * spreadView.snapPosition);
4430 }
4431 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, startScale, priv.phase2StartScale, root.animatedProgress);
4432@@ -295,7 +298,8 @@
4433 }
4434
4435 // selected app or opposite stage active app.
4436- if (isSelected || (otherSelected && root.active && spreadView.selectedApplication && spreadView.selectedApplication.stage !== stage)) {
4437+ if (isSelected || (otherSelected && root.active && spreadView.selectedDelegate
4438+ && spreadView.selectedDelegate.stage !== root.stage)) {
4439 return linearAnimation(selectedProgress, negativeProgress, selectedAngle, 0, root.progress);
4440 } else if (otherSelected) {
4441 return selectedAngle;
4442@@ -304,12 +308,12 @@
4443 // The tile should rotate a bit when another one comes on top, but not when only dragging the side stage in
4444 var shouldMoveAway = spreadView.nextInStack == -1 ||
4445 spreadView.nextInStack >= 0 && priv.movedActive &&
4446- (ApplicationManager.get(spreadView.nextInStack).stage === ApplicationInfoInterface.MainStage ||
4447- stage == ApplicationInfoInterface.SideStage);
4448+ (topLevelSurfaceList.applicationAt(spreadView.nextInStack).stage === ApplicationInfoInterface.MainStage ||
4449+ root.stage == ApplicationInfoInterface.SideStage);
4450
4451 if (spreadView.phase == 0) {
4452 if (nextInStack) {
4453- if (stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
4454+ if (root.stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
4455 return 0;
4456 } else {
4457 return linearAnimation(0, spreadView.positionMarker2, root.startAngle, root.startAngle * (1-spreadView.snapPosition), root.animatedProgress);
4458@@ -321,7 +325,7 @@
4459 }
4460 if (spreadView.phase == 1) {
4461 if (nextInStack) {
4462- if (stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
4463+ if (root.stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
4464 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, 0, priv.phase2StartAngle, root.animatedProgress);
4465 } else {
4466 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, root.startAngle * (1-spreadView.snapPosition), priv.phase2StartAngle, root.animatedProgress);
4467@@ -345,7 +349,8 @@
4468 if (root.isSelected) return 1;
4469
4470 if (otherSelected) {
4471- if (root.active && spreadView.selectedApplication && spreadView.selectedApplication.stage !== stage) {
4472+ if (root.active && spreadView.selectedDelegate
4473+ && spreadView.selectedDelegate.stage !== root.stage) {
4474 return 1;
4475 }
4476 return linearAnimation(selectedProgress, negativeProgress, selectedOpacity, 0, root.progress);
4477@@ -355,7 +360,8 @@
4478
4479 property real topMarginProgress: {
4480 // selected app or opposite stage active app.
4481- if (isSelected || (otherSelected && root.active && spreadView.selectedApplication && spreadView.selectedApplication.stage !== stage)) {
4482+ if (isSelected || (otherSelected && root.active && spreadView.selectedDelegate
4483+ && spreadView.selectedDelegate.stage !== root.stage)) {
4484 return linearAnimation(selectedProgress, negativeProgress, selectedTopMarginProgress, 0, root.progress);
4485 }
4486
4487@@ -379,7 +385,7 @@
4488 when: root.isInSideStage && spreadView.shiftedContentX == 0 && spreadView.phase == 0
4489 PropertyChanges {
4490 target: priv;
4491- xTranslate: -spreadView.sideStageWidth + spreadView.sideStageWidth * (1-spreadView.sideStageDragProgress)
4492+ xTranslate: -spreadView.sideStageWidth + spreadView.sideStageWidth * (1-sideStage.progress)
4493 }
4494 }
4495 ]
4496
4497=== modified file 'qml/Stages/WindowedFullscreenPolicy.qml'
4498--- qml/Stages/WindowedFullscreenPolicy.qml 2016-03-15 19:38:03 +0000
4499+++ qml/Stages/WindowedFullscreenPolicy.qml 2016-04-20 17:09:16 +0000
4500@@ -23,19 +23,17 @@
4501 // state of the window is returned to restored.
4502 QtObject {
4503 property bool active: true
4504- property QtObject application: null
4505+ property QtObject surface: null
4506
4507- readonly property var lastSurface: application && application.session ?
4508- application.session.lastSurface : null
4509 property bool _firstTimeSurface: true
4510
4511- onLastSurfaceChanged: {
4512- if (!active || !lastSurface) return;
4513+ onSurfaceChanged: {
4514+ if (!active || !surface) return;
4515 if (!_firstTimeSurface) return;
4516 _firstTimeSurface = false;
4517
4518- if (lastSurface.state === Mir.FullscreenState && lastSurface.shellChrome === Mir.LowChrome) {
4519- lastSurface.state = Mir.RestoredState;
4520+ if (surface.state === Mir.FullscreenState && surface.shellChrome === Mir.LowChrome) {
4521+ surface.state = Mir.RestoredState;
4522 }
4523 }
4524 }
4525
4526=== modified file 'qml/Tutorial/TutorialBottom.qml'
4527--- qml/Tutorial/TutorialBottom.qml 2016-03-16 15:08:49 +0000
4528+++ qml/Tutorial/TutorialBottom.qml 2016-04-20 17:09:16 +0000
4529@@ -47,7 +47,7 @@
4530 readonly property real sideStageWidth: root.usageScenario === "tablet" && stage.sideStageVisible ?
4531 stage.sideStageWidth : 0
4532 readonly property bool isMainStageApp: usageScenario !== "tablet" ||
4533- application.stage === ApplicationInfoInterface.MainStage
4534+ stage.stageFocusedSurface === ApplicationInfoInterface.MainStage
4535 readonly property real dragAreaHeight: units.gu(3) // based on PageWithBottomEdge.qml
4536 readonly property real targetDistance: height * 0.2 + dragAreaHeight // based on PageWithBottomEdge.qml
4537
4538
4539=== modified file 'tests/mocks/Unity/Application/ApplicationInfo.cpp'
4540--- tests/mocks/Unity/Application/ApplicationInfo.cpp 2016-03-16 12:29:44 +0000
4541+++ tests/mocks/Unity/Application/ApplicationInfo.cpp 2016-04-20 17:09:16 +0000
4542@@ -16,8 +16,7 @@
4543
4544 #include "ApplicationInfo.h"
4545 #include "MirSurface.h"
4546-#include "Session.h"
4547-#include "SessionManager.h"
4548+#include "SurfaceManager.h"
4549
4550 #include <paths.h>
4551
4552@@ -25,97 +24,114 @@
4553 #include <QQuickItem>
4554 #include <QQuickView>
4555 #include <QQmlComponent>
4556-#include <QTimer>
4557+
4558+#define APPLICATION_DEBUG 0
4559+
4560+#if APPLICATION_DEBUG
4561+#define DEBUG_MSG(params) qDebug().nospace() << "Application["<<appId()<<"]::" << __func__ << " " << params
4562+
4563+QString stateToStr(ApplicationInfo::State state)
4564+{
4565+ switch (state) {
4566+ case ApplicationInfo::Starting:
4567+ return "starting";
4568+ case ApplicationInfo::Running:
4569+ return "running";
4570+ case ApplicationInfo::Suspended:
4571+ return "suspended";
4572+ case ApplicationInfo::Stopped:
4573+ return "stopped";
4574+ default:
4575+ return "???";
4576+ };
4577+}
4578+
4579+#else
4580+#define DEBUG_MSG(params) ((void)0)
4581+#endif
4582+
4583+#define WARNING_MSG(params) qWarning().nospace() << "Application["<<appId()<<"]::" << __func__ << " " << params
4584
4585 ApplicationInfo::ApplicationInfo(const QString &appId, QObject *parent)
4586 : ApplicationInfoInterface(appId, parent)
4587 , m_appId(appId)
4588- , m_stage(MainStage)
4589- , m_state(Stopped)
4590- , m_focused(false)
4591- , m_fullscreen(false)
4592- , m_session(0)
4593- , m_supportedOrientations(Qt::PortraitOrientation |
4594- Qt::LandscapeOrientation |
4595- Qt::InvertedPortraitOrientation |
4596- Qt::InvertedLandscapeOrientation)
4597- , m_rotatesWindowContents(false)
4598- , m_requestedState(RequestedRunning)
4599- , m_isTouchApp(true)
4600- , m_exemptFromLifecycle(false)
4601- , m_manualSurfaceCreation(false)
4602- , m_shellChrome(Mir::NormalChrome)
4603 {
4604+ connect(&m_surfaceList, &MirSurfaceListModel::countChanged,
4605+ this, &ApplicationInfo::onSurfaceCountChanged, Qt::QueuedConnection);
4606+
4607+ m_surfaceCreationTimer.setSingleShot(true);
4608+ m_surfaceCreationTimer.setInterval(500);
4609+ connect(&m_surfaceCreationTimer, &QTimer::timeout, this, &ApplicationInfo::createSurface);
4610 }
4611
4612 ApplicationInfo::ApplicationInfo(QObject *parent)
4613- : ApplicationInfoInterface(QString(), parent)
4614- , m_stage(MainStage)
4615- , m_state(Stopped)
4616- , m_focused(false)
4617- , m_fullscreen(false)
4618- , m_session(0)
4619- , m_supportedOrientations(Qt::PortraitOrientation |
4620- Qt::LandscapeOrientation |
4621- Qt::InvertedPortraitOrientation |
4622- Qt::InvertedLandscapeOrientation)
4623- , m_rotatesWindowContents(false)
4624- , m_requestedState(RequestedRunning)
4625- , m_isTouchApp(true)
4626- , m_exemptFromLifecycle(false)
4627- , m_manualSurfaceCreation(false)
4628- , m_shellChrome(Mir::NormalChrome)
4629+ : ApplicationInfo(QString(), parent)
4630 {
4631 }
4632
4633 ApplicationInfo::~ApplicationInfo()
4634 {
4635- delete m_session;
4636-}
4637-
4638-void ApplicationInfo::createSession()
4639-{
4640- if (m_session || state() == ApplicationInfo::Stopped) { return; }
4641-
4642- setSession(SessionManager::singleton()->createSession(appId(), m_screenshotFileName));
4643-}
4644-
4645-void ApplicationInfo::destroySession()
4646-{
4647- Session *session = this->session();
4648- setSession(nullptr);
4649- delete session;
4650-}
4651-
4652-void ApplicationInfo::setSession(Session* session)
4653-{
4654- if (m_session == session)
4655+}
4656+
4657+void ApplicationInfo::createSurface()
4658+{
4659+ if (state() == ApplicationInfo::Stopped) { return; }
4660+
4661+ QString surfaceName = name();
4662+ if (m_surfaceList.count() > 0) {
4663+ surfaceName.append(QString(" %1").arg(m_surfaceList.count()+1));
4664+ }
4665+
4666+ auto surfaceManager = SurfaceManager::instance();
4667+ if (!surfaceManager) {
4668+ WARNING_MSG("No SurfaceManager");
4669 return;
4670-
4671- if (m_session) {
4672- disconnect(this, 0, m_session, 0);
4673- m_session->setApplication(nullptr);
4674- m_session->setParent(nullptr);
4675- Q_EMIT m_session->deregister();
4676- }
4677-
4678- m_session = session;
4679-
4680- if (m_session) {
4681- m_session->setApplication(this);
4682- m_session->setParent(this);
4683- m_session->setFullscreen(m_fullscreen);
4684- SessionManager::singleton()->registerSession(m_session);
4685- connect(m_session, &Session::surfaceAdded,
4686- this, &ApplicationInfo::onSessionSurfaceAdded);
4687- connect(m_session, &Session::fullscreenChanged, this, &ApplicationInfo::fullscreenChanged);
4688-
4689- if (!m_manualSurfaceCreation) {
4690- QTimer::singleShot(500, m_session, &Session::createSurface);
4691- }
4692- }
4693-
4694- Q_EMIT sessionChanged(m_session);
4695+ }
4696+
4697+ auto surface = surfaceManager->createSurface(surfaceName,
4698+ Mir::NormalType,
4699+ fullscreen() ? Mir::FullscreenState : Mir::MaximizedState,
4700+ m_screenshotFileName);
4701+
4702+ surface->setShellChrome(m_shellChrome);
4703+
4704+ m_surfaceList.appendSurface(surface);
4705+
4706+ ++m_liveSurfaceCount;
4707+ connect(surface, &MirSurface::liveChanged, this, [this, surface](){
4708+ if (!surface->live()) {
4709+ --m_liveSurfaceCount;
4710+ if (m_liveSurfaceCount == 0) {
4711+ if (m_closingSurfaces.contains(surface)
4712+ || (m_state == Running && m_requestedState == RequestedRunning)) {
4713+ Q_EMIT closed();
4714+ }
4715+ setState(Stopped);
4716+ } else {
4717+ if (m_closingSurfaces.contains(surface) && m_requestedState == RequestedSuspended
4718+ && m_closingSurfaces.count() == 1) {
4719+ setState(Suspended);
4720+ }
4721+ }
4722+ m_closingSurfaces.removeAll(surface);
4723+ }
4724+ });
4725+ connect(surface, &MirSurface::closeRequested, this, [this, surface](){
4726+ m_closingSurfaces.append(surface);
4727+ if (m_state == Suspended) {
4728+ // resume to allow application to close its surface
4729+ setState(Running);
4730+ }
4731+ });
4732+ connect(surface, &MirSurface::focusRequested, this, &ApplicationInfo::focusRequested);
4733+
4734+ if (m_state == Starting) {
4735+ if (m_requestedState == RequestedRunning) {
4736+ setState(Running);
4737+ } else {
4738+ setState(Suspended);
4739+ }
4740+ }
4741 }
4742
4743 void ApplicationInfo::setIconId(const QString &iconId)
4744@@ -138,10 +154,6 @@
4745
4746 if (screenshotFileName != m_screenshotFileName) {
4747 m_screenshotFileName = screenshotFileName;
4748-
4749- if (m_session) {
4750- m_session->setScreenshot(screenshotFileName);
4751- }
4752 }
4753 }
4754
4755@@ -172,38 +184,53 @@
4756 void ApplicationInfo::setState(State value)
4757 {
4758 if (value != m_state) {
4759+ DEBUG_MSG(qPrintable(stateToStr(value)));
4760+ if (!m_manualSurfaceCreation && value == ApplicationInfo::Starting) {
4761+ Q_ASSERT(m_surfaceList.count() == 0);
4762+ m_surfaceCreationTimer.start();
4763+ } else if (value == ApplicationInfo::Stopped) {
4764+ m_surfaceCreationTimer.stop();
4765+ for (int i = 0; i < m_surfaceList.count(); ++i) {
4766+ MirSurface *surface = static_cast<MirSurface*>(m_surfaceList.get(i));
4767+ surface->setLive(false);
4768+ }
4769+ }
4770+
4771 m_state = value;
4772 Q_EMIT stateChanged(value);
4773-
4774- if (!m_manualSurfaceCreation && !m_session && m_state == ApplicationInfo::Starting) {
4775- QTimer::singleShot(500, this, &ApplicationInfo::createSession);
4776- } else if (m_state == ApplicationInfo::Stopped) {
4777- Session *session = m_session;
4778- setSession(nullptr);
4779- delete session;
4780+ }
4781+}
4782+
4783+void ApplicationInfo::close()
4784+{
4785+ DEBUG_MSG("");
4786+
4787+ if (m_surfaceList.count() > 0) {
4788+ for (int i = 0; i < m_surfaceList.count(); ++i) {
4789+ MirSurface *surface = static_cast<MirSurface*>(m_surfaceList.get(i));
4790+ surface->close();
4791 }
4792- }
4793-}
4794-
4795-void ApplicationInfo::setFocused(bool value)
4796-{
4797- if (value != m_focused) {
4798- m_focused = value;
4799- Q_EMIT focusedChanged(value);
4800+ } else {
4801+ setState(Stopped);
4802+ Q_EMIT closed();
4803 }
4804 }
4805
4806 void ApplicationInfo::setFullscreen(bool value)
4807 {
4808 m_fullscreen = value;
4809- if (m_session) {
4810- m_session->setFullscreen(value);
4811+ if (m_surfaceList.rowCount() > 0) {
4812+ m_surfaceList.get(0)->setState(Mir::FullscreenState);
4813 }
4814 }
4815
4816 bool ApplicationInfo::fullscreen() const
4817 {
4818- return m_session ? m_session->fullscreen() : m_fullscreen;
4819+ if (m_surfaceList.rowCount() > 0) {
4820+ return m_surfaceList.get(0)->state() == Mir::FullscreenState;
4821+ } else {
4822+ return m_fullscreen;
4823+ }
4824 }
4825
4826 void ApplicationInfo::setManualSurfaceCreation(bool value)
4827@@ -241,15 +268,28 @@
4828
4829 void ApplicationInfo::setRequestedState(RequestedState value)
4830 {
4831- if (m_requestedState != value) {
4832- m_requestedState = value;
4833- Q_EMIT requestedStateChanged(m_requestedState);
4834-
4835- if (m_requestedState == RequestedRunning && m_state == Suspended) {
4836+ if (m_requestedState == value) {
4837+ return;
4838+ }
4839+ DEBUG_MSG((value == RequestedRunning ? "RequestedRunning" : "RequestedSuspended") );
4840+
4841+ m_requestedState = value;
4842+ Q_EMIT requestedStateChanged(m_requestedState);
4843+
4844+ if (m_requestedState == RequestedRunning) {
4845+
4846+ if (m_state == Suspended) {
4847+ Q_ASSERT(m_liveSurfaceCount > 0);
4848 setState(Running);
4849- } else if (m_requestedState == RequestedSuspended && m_state == Running) {
4850- setState(Suspended);
4851+ } else if (m_state == Stopped) {
4852+ Q_ASSERT(m_liveSurfaceCount == 0);
4853+ // it's restarting
4854+ setState(Starting);
4855 }
4856+
4857+ } else if (m_requestedState == RequestedSuspended && m_state == Running
4858+ && m_closingSurfaces.isEmpty()) {
4859+ setState(Suspended);
4860 }
4861 }
4862
4863@@ -263,18 +303,6 @@
4864 m_isTouchApp = isTouchApp;
4865 }
4866
4867-void ApplicationInfo::onSessionSurfaceAdded(MirSurface* surface)
4868-{
4869- if (surface != nullptr && m_state == Starting) {
4870- if (m_requestedState == RequestedRunning) {
4871- setState(Running);
4872- } else {
4873- setState(Suspended);
4874- }
4875- surface->setShellChrome(m_shellChrome);
4876- }
4877-}
4878-
4879 bool ApplicationInfo::exemptFromLifecycle() const
4880 {
4881 return m_exemptFromLifecycle;
4882@@ -305,7 +333,52 @@
4883 void ApplicationInfo::setShellChrome(Mir::ShellChrome shellChrome)
4884 {
4885 m_shellChrome = shellChrome;
4886- if (m_session && m_session->lastSurface()) {
4887- m_session->lastSurface()->setShellChrome(shellChrome);
4888+ if (m_surfaceList.rowCount() > 0) {
4889+ static_cast<MirSurface*>(m_surfaceList.get(0))->setShellChrome(shellChrome);
4890+ }
4891+}
4892+
4893+bool ApplicationInfo::focused() const
4894+{
4895+ bool someSurfaceHasFocus = false; // to be proven wrong
4896+ for (int i = 0; i < m_surfaceList.count() && !someSurfaceHasFocus; ++i) {
4897+ someSurfaceHasFocus = m_surfaceList.get(i)->focused();
4898+ }
4899+ return someSurfaceHasFocus;
4900+}
4901+
4902+void ApplicationInfo::setFocused(bool value)
4903+{
4904+ if (focused() == value) {
4905+ return;
4906+ }
4907+
4908+ if (value) {
4909+ if (m_surfaceList.count() > 0) {
4910+ m_surfaceList.get(0)->requestFocus();
4911+ }
4912+ } else {
4913+ for (int i = 0; i < m_surfaceList.count(); ++i) {
4914+ MirSurface *surface = static_cast<MirSurface*>(m_surfaceList.get(i));
4915+ if (surface->focused()) {
4916+ surface->setFocused(false);
4917+ }
4918+ }
4919+ }
4920+}
4921+
4922+void ApplicationInfo::onSurfaceCountChanged()
4923+{
4924+ if (m_surfaceList.count() == 0 && m_state == Running) {
4925+ setState(Stopped);
4926+ }
4927+}
4928+
4929+void ApplicationInfo::requestFocus()
4930+{
4931+ if (m_surfaceList.count() == 0) {
4932+ Q_EMIT focusRequested();
4933+ } else {
4934+ m_surfaceList.get(0)->requestFocus();
4935 }
4936 }
4937
4938=== modified file 'tests/mocks/Unity/Application/ApplicationInfo.h'
4939--- tests/mocks/Unity/Application/ApplicationInfo.h 2016-03-15 20:59:07 +0000
4940+++ tests/mocks/Unity/Application/ApplicationInfo.h 2016-04-20 17:09:16 +0000
4941@@ -20,25 +20,32 @@
4942 #include <QObject>
4943
4944 class MirSurface;
4945-class Session;
4946
4947 // unity-api
4948 #include <unity/shell/application/ApplicationInfoInterface.h>
4949 #include <unity/shell/application/Mir.h>
4950
4951+#include "MirSurfaceListModel.h"
4952+
4953+#include <QList>
4954+#include <QTimer>
4955+
4956 using namespace unity::shell::application;
4957
4958 class ApplicationInfo : public ApplicationInfoInterface {
4959 Q_OBJECT
4960
4961+ ////
4962+ // FIXME: Remove those
4963 Q_PROPERTY(bool fullscreen READ fullscreen WRITE setFullscreen NOTIFY fullscreenChanged)
4964- Q_PROPERTY(Session* session READ session NOTIFY sessionChanged)
4965
4966 // Only exists in this fake implementation
4967
4968 // whether the test code will explicitly control the creation of the application surface
4969 Q_PROPERTY(bool manualSurfaceCreation READ manualSurfaceCreation WRITE setManualSurfaceCreation NOTIFY manualSurfaceCreationChanged)
4970
4971+ Q_PROPERTY(QString screenshot READ screenshot CONSTANT)
4972+
4973 public:
4974 ApplicationInfo(QObject *parent = nullptr);
4975 ApplicationInfo(const QString &appId, QObject *parent = nullptr);
4976@@ -66,8 +73,7 @@
4977 Q_INVOKABLE void setState(State value);
4978 State state() const override { return m_state; }
4979
4980- void setFocused(bool value);
4981- bool focused() const override { return m_focused; }
4982+ bool focused() const override;
4983
4984 QString splashTitle() const override { return QString(); }
4985 QUrl splashImage() const override { return QUrl(); }
4986@@ -100,21 +106,26 @@
4987 void setInitialSurfaceSize(const QSize &size) override;
4988
4989 Q_INVOKABLE void setShellChrome(Mir::ShellChrome shellChrome);
4990-public:
4991- void setSession(Session* session);
4992- Session* session() const { return m_session; }
4993+
4994+ MirSurfaceListInterface* surfaceList() override { return &m_surfaceList; }
4995+
4996+ void setFocused(bool value);
4997+
4998+ //////
4999+ // internal mock stuff
5000+ void close();
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches