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
=== modified file 'debian/control'
--- debian/control 2016-04-20 17:09:15 +0000
+++ debian/control 2016-04-20 17:09:16 +0000
@@ -30,7 +30,7 @@
30 libqt5xmlpatterns5-dev,30 libqt5xmlpatterns5-dev,
31 libsystemsettings-dev,31 libsystemsettings-dev,
32 libudev-dev,32 libudev-dev,
33 libunity-api-dev (>= 7.108),33 libunity-api-dev (>= 7.110),
34 libusermetricsoutput1-dev,34 libusermetricsoutput1-dev,
35# Need those X11 libs touch emulation from mouse events in manual QML tests on a X11 desktop35# Need those X11 libs touch emulation from mouse events in manual QML tests on a X11 desktop
36 libx11-dev[!armhf],36 libx11-dev[!armhf],
3737
=== modified file 'debian/unity8-private.install'
--- debian/unity8-private.install 2015-11-18 09:15:06 +0000
+++ debian/unity8-private.install 2016-04-20 17:09:16 +0000
@@ -14,6 +14,7 @@
14usr/lib/*/unity8/qml/Unity14usr/lib/*/unity8/qml/Unity
15usr/lib/*/unity8/qml/UInput15usr/lib/*/unity8/qml/UInput
16usr/lib/*/unity8/qml/Utils16usr/lib/*/unity8/qml/Utils
17usr/lib/*/unity8/qml/WindowManager
17usr/lib/*/unity8/qml/Wizard18usr/lib/*/unity8/qml/Wizard
18usr/share/accountsservice/interfaces19usr/share/accountsservice/interfaces
19usr/share/dbus-1/interfaces20usr/share/dbus-1/interfaces
2021
=== modified file 'plugins/CMakeLists.txt'
--- plugins/CMakeLists.txt 2015-11-11 16:03:24 +0000
+++ plugins/CMakeLists.txt 2016-04-20 17:09:16 +0000
@@ -25,4 +25,5 @@
25add_subdirectory(UInput)25add_subdirectory(UInput)
26add_subdirectory(Unity)26add_subdirectory(Unity)
27add_subdirectory(Utils)27add_subdirectory(Utils)
28add_subdirectory(WindowManager)
28add_subdirectory(Wizard)29add_subdirectory(Wizard)
2930
=== added directory 'plugins/WindowManager'
=== added file 'plugins/WindowManager/CMakeLists.txt'
--- plugins/WindowManager/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ plugins/WindowManager/CMakeLists.txt 2016-04-20 17:09:16 +0000
@@ -0,0 +1,13 @@
1set(WINDOWMANAGER_SRC
2 TopLevelSurfaceList.cpp
3 WindowManagerPlugin.cpp
4 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
5 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/Mir.h
6 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceInterface.h
7 )
8
9add_library(windowmanager-qml SHARED ${WINDOWMANAGER_SRC})
10
11qt5_use_modules(windowmanager-qml Qml Quick Gui)
12
13add_unity8_plugin(WindowManager 0.1 WindowManager TARGETS windowmanager-qml)
014
=== added file 'plugins/WindowManager/TopLevelSurfaceList.cpp'
--- plugins/WindowManager/TopLevelSurfaceList.cpp 1970-01-01 00:00:00 +0000
+++ plugins/WindowManager/TopLevelSurfaceList.cpp 2016-04-20 17:09:16 +0000
@@ -0,0 +1,486 @@
1/*
2 * Copyright (C) 2016 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#include "TopLevelSurfaceList.h"
18
19// unity-api
20#include <unity/shell/application/ApplicationInfoInterface.h>
21#include <unity/shell/application/MirSurfaceInterface.h>
22#include <unity/shell/application/MirSurfaceListInterface.h>
23
24#include <QMetaObject>
25
26Q_LOGGING_CATEGORY(UNITY_TOPSURFACELIST, "unity.topsurfacelist", QtDebugMsg)
27
28#define DEBUG_MSG qCDebug(UNITY_TOPSURFACELIST).nospace().noquote() << __func__
29
30using namespace unity::shell::application;
31
32TopLevelSurfaceList::TopLevelSurfaceList(QObject *parent) :
33 QAbstractListModel(parent)
34{
35 DEBUG_MSG << "()";
36}
37
38TopLevelSurfaceList::~TopLevelSurfaceList()
39{
40 DEBUG_MSG << "()";
41}
42
43int TopLevelSurfaceList::rowCount(const QModelIndex &parent) const
44{
45 return !parent.isValid() ? m_surfaceList.size() : 0;
46}
47
48QVariant TopLevelSurfaceList::data(const QModelIndex& index, int role) const
49{
50 if (index.row() < 0 || index.row() >= m_surfaceList.size())
51 return QVariant();
52
53 if (role == SurfaceRole) {
54 MirSurfaceInterface *surface = m_surfaceList.at(index.row()).surface;
55 return QVariant::fromValue(surface);
56 } else if (role == ApplicationRole) {
57 return QVariant::fromValue(m_surfaceList.at(index.row()).application);
58 } else if (role == IdRole) {
59 return QVariant::fromValue(m_surfaceList.at(index.row()).id);
60 } else {
61 return QVariant();
62 }
63}
64
65void TopLevelSurfaceList::raise(MirSurfaceInterface *surface)
66{
67 if (!surface)
68 return;
69
70 DEBUG_MSG << "(MirSurface[" << (void*)surface << "])";
71
72 int i = indexOf(surface);
73 if (i != -1) {
74 raiseId(m_surfaceList.at(i).id);
75 }
76}
77
78void TopLevelSurfaceList::appendPlaceholder(ApplicationInfoInterface *application)
79{
80 DEBUG_MSG << "(" << application->appId() << ")";
81
82 appendSurfaceHelper(nullptr, application);
83}
84
85void TopLevelSurfaceList::appendSurface(MirSurfaceInterface *surface, ApplicationInfoInterface *application)
86{
87 Q_ASSERT(surface != nullptr);
88
89 bool filledPlaceholder = false;
90 for (int i = 0; i < m_surfaceList.count() && !filledPlaceholder; ++i) {
91 ModelEntry &entry = m_surfaceList[i];
92 if (entry.application == application && entry.surface == nullptr) {
93 entry.surface = surface;
94 connectSurface(surface);
95 DEBUG_MSG << " appId=" << application->appId() << " surface=" << surface
96 << ", filling out placeholder. after: " << toString();
97 Q_EMIT dataChanged(index(i) /* topLeft */, index(i) /* bottomRight */, QVector<int>() << SurfaceRole);
98 filledPlaceholder = true;
99 }
100 }
101
102 if (!filledPlaceholder) {
103 DEBUG_MSG << " appId=" << application->appId() << " surface=" << surface << ", adding new row";
104 appendSurfaceHelper(surface, application);
105 }
106}
107
108void TopLevelSurfaceList::appendSurfaceHelper(MirSurfaceInterface *surface, ApplicationInfoInterface *application)
109{
110 if (m_modelState == IdleState) {
111 m_modelState = InsertingState;
112 beginInsertRows(QModelIndex(), m_surfaceList.size() /*first*/, m_surfaceList.size() /*last*/);
113 } else {
114 Q_ASSERT(m_modelState == ResettingState);
115 // No point in signaling anything if we're resetting the whole model
116 }
117
118 int id = generateId();
119 m_surfaceList.append(ModelEntry(surface, application, id));
120 if (surface) {
121 connectSurface(surface);
122 }
123
124 if (m_modelState == InsertingState) {
125 endInsertRows();
126 Q_EMIT countChanged();
127 Q_EMIT listChanged();
128 m_modelState = IdleState;
129 }
130
131 DEBUG_MSG << " after " << toString();
132}
133
134void TopLevelSurfaceList::connectSurface(MirSurfaceInterface *surface)
135{
136 connect(surface, &MirSurfaceInterface::focusedChanged, this, [this, surface](bool focused){
137 if (focused) {
138 this->raise(surface);
139 }
140 });
141 connect(surface, &MirSurfaceInterface::liveChanged, this, [this, surface](bool live){
142 if (!live) {
143 onSurfaceDied(surface);
144 }
145 });
146 connect(surface, &QObject::destroyed, this, [this, surface](){ this->onSurfaceDestroyed(surface); });
147}
148
149void TopLevelSurfaceList::onSurfaceDied(MirSurfaceInterface *surface)
150{
151 int i = indexOf(surface);
152 if (i == -1) {
153 return;
154 }
155
156 auto application = m_surfaceList[i].application;
157
158 // can't be starting if it already has a surface
159 Q_ASSERT(application->state() != ApplicationInfoInterface::Starting);
160
161 if (application->state() == ApplicationInfoInterface::Running) {
162 m_surfaceList[i].removeOnceSurfaceDestroyed = true;
163 } else {
164 // assume it got killed by the out-of-memory daemon.
165 //
166 // So leave entry in the model and only remove its surface, so shell can display a screenshot
167 // in its place.
168 m_surfaceList[i].removeOnceSurfaceDestroyed = false;
169 }
170}
171
172void TopLevelSurfaceList::onSurfaceDestroyed(MirSurfaceInterface *surface)
173{
174 int i = indexOf(surface);
175 if (i == -1) {
176 return;
177 }
178
179 if (m_surfaceList[i].removeOnceSurfaceDestroyed) {
180 removeAt(i);
181 } else {
182 m_surfaceList[i].surface = nullptr;
183 Q_EMIT dataChanged(index(i) /* topLeft */, index(i) /* bottomRight */, QVector<int>() << SurfaceRole);
184 DEBUG_MSG << " Removed surface from entry. After: " << toString();
185 }
186}
187
188void TopLevelSurfaceList::removeAt(int index)
189{
190 if (m_modelState == IdleState) {
191 beginRemoveRows(QModelIndex(), index, index);
192 m_modelState = RemovingState;
193 } else {
194 Q_ASSERT(m_modelState == ResettingState);
195 // No point in signaling anything if we're resetting the whole model
196 }
197
198 m_surfaceList.removeAt(index);
199
200 if (m_modelState == RemovingState) {
201 endRemoveRows();
202 Q_EMIT countChanged();
203 Q_EMIT listChanged();
204 m_modelState = IdleState;
205 }
206
207 DEBUG_MSG << " after " << toString();
208}
209
210int TopLevelSurfaceList::indexOf(MirSurfaceInterface *surface)
211{
212 for (int i = 0; i < m_surfaceList.count(); ++i) {
213 if (m_surfaceList.at(i).surface == surface) {
214 return i;
215 }
216 }
217 return -1;
218}
219
220void TopLevelSurfaceList::move(int from, int to)
221{
222 if (from == to) return;
223 DEBUG_MSG << " from=" << from << " to=" << to;
224
225 if (from >= 0 && from < m_surfaceList.size() && to >= 0 && to < m_surfaceList.size()) {
226 QModelIndex parent;
227 /* When moving an item down, the destination index needs to be incremented
228 by one, as explained in the documentation:
229 http://qt-project.org/doc/qt-5.0/qtcore/qabstractitemmodel.html#beginMoveRows */
230
231 Q_ASSERT(m_modelState == IdleState);
232 m_modelState = MovingState;
233
234 beginMoveRows(parent, from, from, parent, to + (to > from ? 1 : 0));
235 m_surfaceList.move(from, to);
236 endMoveRows();
237 Q_EMIT listChanged();
238
239 m_modelState = IdleState;
240
241 DEBUG_MSG << " after " << toString();
242 }
243}
244
245MirSurfaceInterface *TopLevelSurfaceList::surfaceAt(int index) const
246{
247 if (index >=0 && index < m_surfaceList.count()) {
248 return m_surfaceList[index].surface;
249 } else {
250 return nullptr;
251 }
252}
253
254ApplicationInfoInterface *TopLevelSurfaceList::applicationAt(int index) const
255{
256 if (index >=0 && index < m_surfaceList.count()) {
257 return m_surfaceList[index].application;
258 } else {
259 return nullptr;
260 }
261}
262
263int TopLevelSurfaceList::idAt(int index) const
264{
265 if (index >=0 && index < m_surfaceList.count()) {
266 return m_surfaceList[index].id;
267 } else {
268 return 0;
269 }
270}
271
272int TopLevelSurfaceList::indexForId(int id) const
273{
274 for (int i = 0; i < m_surfaceList.count(); ++i) {
275 if (m_surfaceList[i].id == id) {
276 return i;
277 }
278 }
279 return -1;
280}
281
282void TopLevelSurfaceList::doRaiseId(int id)
283{
284 int fromIndex = indexForId(id);
285 if (fromIndex != -1) {
286 move(fromIndex, 0 /* toIndex */);
287 }
288}
289
290void TopLevelSurfaceList::raiseId(int id)
291{
292 if (m_modelState == IdleState) {
293 DEBUG_MSG << "(id=" << id << ") - do it now.";
294 doRaiseId(id);
295 } else {
296 DEBUG_MSG << "(id=" << id << ") - Model busy (modelState=" << m_modelState << "). Try again in the next event loop.";
297 // The model has just signalled some change. If we have a Repeater responding to this update, it will get nuts
298 // if we perform yet another model change straight away.
299 //
300 // A bad sympton of this problem is a Repeater.itemAt(index) call returning null event though Repeater.count says
301 // the index is definitely within bounds.
302 QMetaObject::invokeMethod(this, "raiseId", Qt::QueuedConnection, Q_ARG(int, id));
303 }
304}
305
306int TopLevelSurfaceList::generateId()
307{
308 int id = m_nextId;
309 m_nextId = nextFreeId(m_nextId + 1);
310 Q_EMIT nextIdChanged();
311 return id;
312}
313
314int TopLevelSurfaceList::nextFreeId(int candidateId)
315{
316 if (candidateId > m_maxId) {
317 return nextFreeId(1);
318 } else {
319 if (indexForId(candidateId) == -1) {
320 // it's indeed free
321 return candidateId;
322 } else {
323 return nextFreeId(candidateId + 1);
324 }
325 }
326}
327
328QString TopLevelSurfaceList::toString()
329{
330 QString str;
331 for (int i = 0; i < m_surfaceList.count(); ++i) {
332 auto item = m_surfaceList.at(i);
333
334 QString itemStr = QString("(index=%1,appId=%2,surface=0x%3,id=%4)")
335 .arg(i)
336 .arg(item.application->appId())
337 .arg((qintptr)item.surface, 0, 16)
338 .arg(item.id);
339
340 if (i > 0) {
341 str.append(",");
342 }
343 str.append(itemStr);
344 }
345 return str;
346}
347
348void TopLevelSurfaceList::addApplication(ApplicationInfoInterface *application)
349{
350 DEBUG_MSG << "(" << application->appId() << ")";
351 Q_ASSERT(!m_applications.contains(application));
352 m_applications.append(application);
353
354 MirSurfaceListInterface *surfaceList = application->surfaceList();
355
356 if (application->state() != ApplicationInfoInterface::Stopped) {
357 if (surfaceList->count() == 0) {
358 appendPlaceholder(application);
359 } else {
360 for (int i = 0; i < surfaceList->count(); ++i) {
361 appendSurface(surfaceList->get(i), application);
362 }
363 }
364 }
365
366 connect(surfaceList, &QAbstractItemModel::rowsInserted, this,
367 [this, application, surfaceList](const QModelIndex & /*parent*/, int first, int last)
368 {
369 for (int i = last; i >= first; --i) {
370 this->appendSurface(surfaceList->get(i), application);
371 }
372 });
373}
374
375void TopLevelSurfaceList::removeApplication(ApplicationInfoInterface *application)
376{
377 DEBUG_MSG << "(" << application->appId() << ")";
378 Q_ASSERT(m_applications.contains(application));
379
380 MirSurfaceListInterface *surfaceList = application->surfaceList();
381
382 disconnect(surfaceList, 0, this, 0);
383
384 Q_ASSERT(m_modelState == IdleState);
385 m_modelState = RemovingState;
386
387 int i = 0;
388 while (i < m_surfaceList.count()) {
389 if (m_surfaceList.at(i).application == application) {
390 beginRemoveRows(QModelIndex(), i, i);
391 m_surfaceList.removeAt(i);
392 endRemoveRows();
393 Q_EMIT countChanged();
394 Q_EMIT listChanged();
395 } else {
396 ++i;
397 }
398 }
399
400 m_modelState = IdleState;
401
402 DEBUG_MSG << " after " << toString();
403
404 m_applications.removeAll(application);
405}
406
407QAbstractListModel *TopLevelSurfaceList::applicationsModel() const
408{
409 return m_applicationsModel;
410}
411
412void TopLevelSurfaceList::setApplicationsModel(QAbstractListModel* value)
413{
414 if (m_applicationsModel == value) {
415 return;
416 }
417
418 DEBUG_MSG << "(" << value << ")";
419
420 Q_ASSERT(m_modelState == IdleState);
421 m_modelState = ResettingState;
422
423 beginResetModel();
424
425 if (m_applicationsModel) {
426 m_surfaceList.clear();
427 m_applications.clear();
428 disconnect(m_applicationsModel, 0, this, 0);
429 }
430
431 m_applicationsModel = value;
432
433 if (m_applicationsModel) {
434 findApplicationRole();
435
436 connect(m_applicationsModel, &QAbstractItemModel::rowsInserted,
437 this, [this](const QModelIndex &/*parent*/, int first, int last) {
438 for (int i = first; i <= last; ++i) {
439 auto application = getApplicationFromModelAt(i);
440 addApplication(application);
441 }
442 });
443
444 connect(m_applicationsModel, &QAbstractItemModel::rowsAboutToBeRemoved,
445 this, [this](const QModelIndex &/*parent*/, int first, int last) {
446 for (int i = first; i <= last; ++i) {
447 auto application = getApplicationFromModelAt(i);
448 removeApplication(application);
449 }
450 });
451
452 for (int i = 0; i < m_applicationsModel->rowCount(); ++i) {
453 auto application = getApplicationFromModelAt(i);
454 addApplication(application);
455 }
456 }
457
458 endResetModel();
459 m_modelState = IdleState;
460}
461
462ApplicationInfoInterface *TopLevelSurfaceList::getApplicationFromModelAt(int index)
463{
464 QModelIndex modelIndex = m_applicationsModel->index(index);
465
466 QVariant variant = m_applicationsModel->data(modelIndex, m_applicationRole);
467
468 // variant.value<ApplicationInfoInterface*>() returns null for some reason.
469 return static_cast<ApplicationInfoInterface*>(variant.value<QObject*>());
470}
471
472void TopLevelSurfaceList::findApplicationRole()
473{
474 QHash<int, QByteArray> namesHash = m_applicationsModel->roleNames();
475
476 m_applicationRole = -1;
477 for (auto i = namesHash.begin(); i != namesHash.end() && m_applicationRole == -1; ++i) {
478 if (i.value() == "application") {
479 m_applicationRole = i.key();
480 }
481 }
482
483 if (m_applicationRole == -1) {
484 qFatal("TopLevelSurfaceList: applicationsModel must have a \"application\" role.");
485 }
486}
0487
=== added file 'plugins/WindowManager/TopLevelSurfaceList.h'
--- plugins/WindowManager/TopLevelSurfaceList.h 1970-01-01 00:00:00 +0000
+++ plugins/WindowManager/TopLevelSurfaceList.h 2016-04-20 17:09:16 +0000
@@ -0,0 +1,223 @@
1/*
2 * Copyright (C) 2016 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#ifndef TOPLEVELSURFACELIST_H
18#define TOPLEVELSURFACELIST_H
19
20#include <QAbstractListModel>
21#include <QList>
22#include <QLoggingCategory>
23
24Q_DECLARE_LOGGING_CATEGORY(UNITY_TOPSURFACELIST)
25
26namespace unity {
27 namespace shell {
28 namespace application {
29 class ApplicationInfoInterface;
30 class MirSurfaceInterface;
31 }
32 }
33}
34
35/**
36 * @brief A model of top-level surfaces
37 *
38 * It's an abstraction of top-level application windows.
39 *
40 * When an entry first appears, it normaly doesn't have a surface yet, meaning that the application is
41 * still starting up. A shell should then display a splash screen or saved screenshot of the application
42 * until its surface comes up.
43 *
44 * As applications can have multiple surfaces and you can also have entries without surfaces at all,
45 * the only way to unambiguously refer to an entry in this model is through its id.
46 */
47class TopLevelSurfaceList : public QAbstractListModel
48{
49
50 Q_OBJECT
51
52 /**
53 * @brief A list model of applications.
54 *
55 * It's expected to have a role called "application" which returns a ApplicationInfoInterface
56 */
57 Q_PROPERTY(QAbstractListModel* applicationsModel READ applicationsModel
58 WRITE setApplicationsModel
59 NOTIFY applicationsModelChanged)
60
61 /**
62 * @brief Number of top-level surfaces in this model
63 *
64 * This is the same as rowCount, added in order to keep compatibility with QML ListModels.
65 */
66 Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
67
68 /**
69 The id to be used on the next entry created
70 Useful for tests
71 */
72 Q_PROPERTY(int nextId READ nextId NOTIFY nextIdChanged)
73public:
74
75 /**
76 * @brief The Roles supported by the model
77 *
78 * SurfaceRole - A MirSurfaceInterface. It will be null if the application is still starting up
79 * ApplicationRole - An ApplicationInfoInterface
80 * IdRole - A unique identifier for this entry. Useful to unambiguosly track elements as they move around in the list
81 */
82 enum Roles {
83 SurfaceRole = Qt::UserRole,
84 ApplicationRole = Qt::UserRole + 1,
85 IdRole = Qt::UserRole + 2,
86 };
87
88 explicit TopLevelSurfaceList(QObject *parent = nullptr);
89 virtual ~TopLevelSurfaceList();
90
91 // QAbstractItemModel methods
92 int rowCount(const QModelIndex &parent = QModelIndex()) const override;
93 QVariant data(const QModelIndex& index, int role) const override;
94 QHash<int, QByteArray> roleNames() const override {
95 QHash<int, QByteArray> roleNames { {SurfaceRole, "surface"},
96 {ApplicationRole, "application"},
97 {IdRole, "id"} };
98 return roleNames;
99 }
100
101 int nextId() const { return m_nextId; }
102
103 QAbstractListModel *applicationsModel() const;
104 void setApplicationsModel(QAbstractListModel*);
105
106public Q_SLOTS:
107 /**
108 * @brief Returns the surface at the given index
109 *
110 * It will be a nullptr if the application is still starting up and thus hasn't yet created
111 * and drawn into a surface.
112 */
113 unity::shell::application::MirSurfaceInterface *surfaceAt(int index) const;
114
115 /**
116 * @brief Returns the application at the given index
117 */
118 unity::shell::application::ApplicationInfoInterface *applicationAt(int index) const;
119
120 /**
121 * @brief Returns the unique id of the element at the given index
122 */
123 int idAt(int index) const;
124
125 /**
126 * @brief Returns the index where the row with the given id is located
127 *
128 * Returns -1 if there's no row with the given id.
129 */
130 int indexForId(int id) const;
131
132 /**
133 * @brief Raises the row with the given id to index 0
134 */
135 void raiseId(int id);
136
137 void doRaiseId(int id);
138
139Q_SIGNALS:
140 void countChanged();
141
142 /**
143 * @brief Emitted when the list changes
144 *
145 * Emitted when model gains an element, loses an element or when elements exchange positions.
146 */
147 void listChanged();
148
149 void nextIdChanged();
150
151 void applicationsModelChanged();
152
153private:
154 void addApplication(unity::shell::application::ApplicationInfoInterface *application);
155 void removeApplication(unity::shell::application::ApplicationInfoInterface *application);
156
157 int indexOf(unity::shell::application::MirSurfaceInterface *surface);
158 void raise(unity::shell::application::MirSurfaceInterface *surface);
159 void move(int from, int to);
160 void appendSurfaceHelper(unity::shell::application::MirSurfaceInterface *surface,
161 unity::shell::application::ApplicationInfoInterface *application);
162 void connectSurface(unity::shell::application::MirSurfaceInterface *surface);
163 int generateId();
164 int nextFreeId(int candidateId);
165 QString toString();
166 void onSurfaceDestroyed(unity::shell::application::MirSurfaceInterface *surface);
167 void onSurfaceDied(unity::shell::application::MirSurfaceInterface *surface);
168 void removeAt(int index);
169 void findApplicationRole();
170
171 unity::shell::application::ApplicationInfoInterface *getApplicationFromModelAt(int index);
172
173 /*
174 Placeholder for a future surface from a starting or running application.
175 Enables shell to give immediate feedback to the user by showing, eg,
176 a splash screen.
177
178 It's a model row containing a null surface and the given application.
179 */
180 void appendPlaceholder(unity::shell::application::ApplicationInfoInterface *application);
181
182 /*
183 Adds a model row with the given surface and application
184
185 Alternatively, if a placeholder exists for the given application it's
186 filled with the given surface instead.
187 */
188 void appendSurface(unity::shell::application::MirSurfaceInterface *surface,
189 unity::shell::application::ApplicationInfoInterface *application);
190
191 struct ModelEntry {
192 ModelEntry(unity::shell::application::MirSurfaceInterface *surface, unity::shell::application::ApplicationInfoInterface *application, int id)
193 : surface(surface), application(application), id(id) {}
194 unity::shell::application::MirSurfaceInterface *surface;
195 unity::shell::application::ApplicationInfoInterface *application;
196 int id;
197 bool removeOnceSurfaceDestroyed{false};
198 };
199
200 QList<ModelEntry> m_surfaceList;
201 int m_nextId{1};
202 static const int m_maxId{1000000};
203
204 // applications that are being monitored
205 QList<unity::shell::application::ApplicationInfoInterface *> m_applications;
206
207 QAbstractListModel* m_applicationsModel{nullptr};
208 int m_applicationRole{-1};
209
210 enum ModelState {
211 IdleState,
212 InsertingState,
213 RemovingState,
214 MovingState,
215 ResettingState
216 };
217 ModelState m_modelState{IdleState};
218};
219
220Q_DECLARE_METATYPE(TopLevelSurfaceList*)
221Q_DECLARE_METATYPE(QAbstractListModel*)
222
223#endif // TOPLEVELSURFACELIST_H
0224
=== added file 'plugins/WindowManager/WindowManagerPlugin.cpp'
--- plugins/WindowManager/WindowManagerPlugin.cpp 1970-01-01 00:00:00 +0000
+++ plugins/WindowManager/WindowManagerPlugin.cpp 2016-04-20 17:09:16 +0000
@@ -0,0 +1,28 @@
1/*
2 * Copyright (C) 2016 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#include "WindowManagerPlugin.h"
18
19#include "TopLevelSurfaceList.h"
20
21#include <QtQml>
22
23void WindowManagerPlugin::registerTypes(const char *uri)
24{
25 qmlRegisterType<TopLevelSurfaceList>(uri, 0, 1, "TopLevelSurfaceList");
26
27 qRegisterMetaType<QAbstractListModel*>("QAbstractListModel*");
28}
029
=== added file 'plugins/WindowManager/WindowManagerPlugin.h'
--- plugins/WindowManager/WindowManagerPlugin.h 1970-01-01 00:00:00 +0000
+++ plugins/WindowManager/WindowManagerPlugin.h 2016-04-20 17:09:16 +0000
@@ -0,0 +1,32 @@
1/*
2 * Copyright (C) 2016 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#ifndef WINDOWMANAGER_PLUGIN_H
18#define WINDOWMANAGER_PLUGIN_H
19
20#include <QtQml/QQmlEngine>
21#include <QtQml/QQmlExtensionPlugin>
22
23class WindowManagerPlugin : public QQmlExtensionPlugin
24{
25 Q_OBJECT
26 Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
27
28public:
29 void registerTypes(const char *uri) override;
30};
31
32#endif // WINDOWMANAGER_PLUGIN_H
033
=== added file 'plugins/WindowManager/qmldir'
--- plugins/WindowManager/qmldir 1970-01-01 00:00:00 +0000
+++ plugins/WindowManager/qmldir 2016-04-20 17:09:16 +0000
@@ -0,0 +1,2 @@
1module WindowManager
2plugin windowmanager-qml
03
=== modified file 'qml/Components/KeymapSwitcher.qml'
--- qml/Components/KeymapSwitcher.qml 2016-04-20 17:09:15 +0000
+++ qml/Components/KeymapSwitcher.qml 2016-04-20 17:09:16 +0000
@@ -57,16 +57,8 @@
57 currentKeymapIndex = prevIndex;57 currentKeymapIndex = prevIndex;
58 }58 }
5959
60 // Code below will get much simpler with surface-based window management (the upcoming MirFocusController)
61 property var application: ApplicationManager.focusedApplicationId
62 ? ApplicationManager.findApplication(ApplicationManager.focusedApplicationId)
63 : null
64 property var session: application && application.session ? application.session : null
65 property var surface: session ? session.lastSurface : null
66
67 property Binding surfaceKeymapBinding: Binding {60 property Binding surfaceKeymapBinding: Binding {
68 target: root.surface61 target: MirFocusController.focusedSurface
69 when: root.surface != null && root.surface.live
70 property: "keymap"62 property: "keymap"
71 value: root.currentKeymap63 value: root.currentKeymap
72 }64 }
7365
=== modified file 'qml/Greeter/Greeter.qml'
--- qml/Greeter/Greeter.qml 2016-03-30 07:46:17 +0000
+++ qml/Greeter/Greeter.qml 2016-04-20 17:09:16 +0000
@@ -64,7 +64,7 @@
64 d.selectUser(d.currentIndex, true);64 d.selectUser(d.currentIndex, true);
65 }65 }
6666
67 function notifyAppFocused(appId) {67 function notifyAppFocusRequested(appId) {
68 if (!active) {68 if (!active) {
69 return;69 return;
70 }70 }
@@ -81,19 +81,20 @@
81 }81 }
82 }82 }
8383
84 function notifyAboutToFocusApp(appId) {84 // Notify that the user has explicitly requested the given app through unity8 GUI.
85 function notifyUserRequestedApp(appId) {
85 if (!active) {86 if (!active) {
86 return;87 return;
87 }88 }
8889
89 // A hint that we're about to focus an app. This way we can look90 // A hint that we're about to focus an app. This way we can look
90 // a little more responsive, rather than waiting for the above91 // a little more responsive, rather than waiting for the above
91 // notifyAppFocused call. We also need this in case we have a locked92 // notifyAppFocusRequested call. We also need this in case we have a locked
92 // app, in order to show lockscreen instead of new app.93 // app, in order to show lockscreen instead of new app.
93 d.startUnlock(false /* toTheRight */);94 d.startUnlock(false /* toTheRight */);
94 }95 }
9596
96 // This is a just a glorified notifyAboutToFocusApp(), but it does one97 // This is a just a glorified notifyUserRequestedApp(), but it does one
97 // other thing: it hides any cover pages to the RIGHT, because the user98 // other thing: it hides any cover pages to the RIGHT, because the user
98 // just came from a launcher drag starting on the left.99 // just came from a launcher drag starting on the left.
99 // It also returns a boolean value, indicating whether there was a visual100 // It also returns a boolean value, indicating whether there was a visual
100101
=== modified file 'qml/Launcher/LauncherPanel.qml'
--- qml/Launcher/LauncherPanel.qml 2016-03-16 11:20:24 +0000
+++ qml/Launcher/LauncherPanel.qml 2016-04-20 17:09:16 +0000
@@ -98,6 +98,7 @@
98 AbstractButton {98 AbstractButton {
99 id: dashItem99 id: dashItem
100 anchors.fill: parent100 anchors.fill: parent
101 activeFocusOnPress: false
101 onClicked: root.showDashHome()102 onClicked: root.showDashHome()
102 }103 }
103 Rectangle {104 Rectangle {
104105
=== modified file 'qml/Shell.qml'
--- qml/Shell.qml 2016-04-20 17:09:15 +0000
+++ qml/Shell.qml 2016-04-20 17:09:16 +0000
@@ -42,6 +42,7 @@
42import Unity.DashCommunicator 0.142import Unity.DashCommunicator 0.1
43import Unity.Indicators 0.1 as Indicators43import Unity.Indicators 0.1 as Indicators
44import Cursor 1.044import Cursor 1.0
45import WindowManager 0.1
4546
4647
47Item {48Item {
@@ -96,13 +97,45 @@
96 }97 }
97 }98 }
9899
100 readonly property var mainApp:
101 applicationsDisplayLoader.item ? applicationsDisplayLoader.item.mainApp : null
102 onMainAppChanged: {
103 if (mainApp) {
104 _onMainAppChanged(mainApp.appId);
105 }
106 }
107 Connections {
108 target: ApplicationManager
109 onFocusRequested: {
110 if (shell.mainApp && shell.mainApp.appId === appId) {
111 _onMainAppChanged(appId);
112 }
113 }
114 }
115 function _onMainAppChanged(appId) {
116 if (wizard.active && appId != "" && appId != "unity8-dash") {
117 // If this happens on first boot, we may be in edge
118 // tutorial or wizard while receiving a call. But a call
119 // is more important than wizard so just bail out of those.
120 tutorial.finish();
121 wizard.hide();
122 }
123
124 if (appId === "dialer-app" && callManager.hasCalls && greeter.locked) {
125 // If we are in the middle of a call, make dialer lockedApp and show it.
126 // This can happen if user backs out of dialer back to greeter, then
127 // launches dialer again.
128 greeter.lockedApp = appId;
129 }
130 greeter.notifyAppFocusRequested(appId);
131
132 panel.indicators.hide();
133 launcher.hide();
134 }
135
99 // For autopilot consumption136 // For autopilot consumption
100 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId137 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
101138
102 // internal props from here onwards
103 readonly property var mainApp:
104 applicationsDisplayLoader.item ? applicationsDisplayLoader.item.mainApp : null
105
106 // Disable everything while greeter is waiting, so that the user can't swipe139 // Disable everything while greeter is waiting, so that the user can't swipe
107 // the greeter or launcher until we know whether the session is locked.140 // the greeter or launcher until we know whether the session is locked.
108 enabled: greeter && !greeter.waiting141 enabled: greeter && !greeter.waiting
@@ -139,9 +172,6 @@
139172
140 Component.onCompleted: {173 Component.onCompleted: {
141 theme.name = "Ubuntu.Components.Themes.SuruDark"174 theme.name = "Ubuntu.Components.Themes.SuruDark"
142 if (ApplicationManager.count > 0) {
143 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
144 }
145 finishStartUpTimer.start();175 finishStartUpTimer.start();
146 }176 }
147177
@@ -198,36 +228,10 @@
198 width: parent.width228 width: parent.width
199 height: parent.height229 height: parent.height
200230
201 Connections {231 TopLevelSurfaceList {
202 target: ApplicationManager232 id: topLevelSurfaceList
203233 objectName: "topLevelSurfaceList"
204 // This signal is also fired when we try to focus the current app234 applicationsModel: ApplicationManager
205 // again. We rely on this!
206 onFocusedApplicationIdChanged: {
207 var appId = ApplicationManager.focusedApplicationId;
208
209 if (wizard.active && appId != "" && appId != "unity8-dash") {
210 // If this happens on first boot, we may be in edge
211 // tutorial or wizard while receiving a call. But a call
212 // is more important than wizard so just bail out of those.
213 tutorial.finish();
214 wizard.hide();
215 }
216
217 if (appId === "dialer-app" && callManager.hasCalls && greeter.locked) {
218 // If we are in the middle of a call, make dialer lockedApp and show it.
219 // This can happen if user backs out of dialer back to greeter, then
220 // launches dialer again.
221 greeter.lockedApp = appId;
222 }
223 greeter.notifyAppFocused(appId);
224
225 panel.indicators.hide();
226 }
227
228 onApplicationAdded: {
229 launcher.hide();
230 }
231 }235 }
232236
233 Loader {237 Loader {
@@ -258,6 +262,10 @@
258 return "Stages/DesktopStage.qml";262 return "Stages/DesktopStage.qml";
259 }263 }
260 }264 }
265 // TODO: Ensure the current stage is destroyed before the new one gets loaded.
266 // Currently the new one will get loaded while the old is still hanging
267 // around for a bit, which might lead to conflicts where both stages
268 // change the model simultaneously.
261 onQmlComponentChanged: {269 onQmlComponentChanged: {
262 if (item) item.stageAboutToBeUnloaded();270 if (item) item.stageAboutToBeUnloaded();
263 source = qmlComponent;271 source = qmlComponent;
@@ -272,6 +280,11 @@
272280
273 Binding {281 Binding {
274 target: applicationsDisplayLoader.item282 target: applicationsDisplayLoader.item
283 property: "focus"
284 value: true
285 }
286 Binding {
287 target: applicationsDisplayLoader.item
275 property: "objectName"288 property: "objectName"
276 value: "stage"289 value: "stage"
277 }290 }
@@ -356,6 +369,16 @@
356 property: "leftMargin"369 property: "leftMargin"
357 value: shell.usageScenario == "desktop" && !settings.autohideLauncher ? launcher.panelWidth: 0370 value: shell.usageScenario == "desktop" && !settings.autohideLauncher ? launcher.panelWidth: 0
358 }371 }
372 Binding {
373 target: applicationsDisplayLoader.item
374 property: "applicationManager"
375 value: ApplicationManager
376 }
377 Binding {
378 target: applicationsDisplayLoader.item
379 property: "topLevelSurfaceList"
380 value: topLevelSurfaceList
381 }
359 }382 }
360 }383 }
361384
@@ -370,16 +393,6 @@
370 z: notifications.useModal || panel.indicators.shown || wizard.active || tutorial.running ? overlay.z + 1 : overlay.z - 1393 z: notifications.useModal || panel.indicators.shown || wizard.active || tutorial.running ? overlay.z + 1 : overlay.z - 1
371 }394 }
372395
373 Connections {
374 target: SessionManager
375 onSessionStopping: {
376 if (!session.parentSession && !session.application) {
377 // nothing is using it. delete it right away
378 session.release();
379 }
380 }
381 }
382
383 Loader {396 Loader {
384 id: greeterLoader397 id: greeterLoader
385 anchors.fill: parent398 anchors.fill: parent
@@ -471,7 +484,7 @@
471 }484 }
472485
473 function showHome() {486 function showHome() {
474 greeter.notifyAboutToFocusApp("unity8-dash");487 greeter.notifyUserRequestedApp("unity8-dash");
475488
476 var animate = !lightDM.greeter.active && !stages.shown489 var animate = !lightDM.greeter.active && !stages.shown
477 dash.setCurrentScope(0, animate, false)490 dash.setCurrentScope(0, animate, false)
@@ -520,9 +533,10 @@
520 greeterShown: greeter.shown533 greeterShown: greeter.shown
521 }534 }
522535
523 readonly property bool topmostApplicationIsFullscreen: mainApp && mainApp.fullscreen536 readonly property bool focusedSurfaceIsFullscreen: MirFocusController.focusedSurface
524537 ? MirFocusController.focusedSurface.state === Mir.FullscreenState
525 fullscreenMode: (topmostApplicationIsFullscreen && !lightDM.greeter.active && launcher.progress == 0)538 : false
539 fullscreenMode: (focusedSurfaceIsFullscreen && !lightDM.greeter.active && launcher.progress == 0)
526 || greeter.hasLockedApp540 || greeter.hasLockedApp
527 locked: greeter && greeter.active541 locked: greeter && greeter.active
528 }542 }
@@ -555,7 +569,7 @@
555 }569 }
556 }570 }
557 onLauncherApplicationSelected: {571 onLauncherApplicationSelected: {
558 greeter.notifyAboutToFocusApp(appId);572 greeter.notifyUserRequestedApp(appId);
559 shell.activateApplication(appId);573 shell.activateApplication(appId);
560 }574 }
561 onShownChanged: {575 onShownChanged: {
@@ -719,7 +733,7 @@
719 onMouseMoved: { cursor.opacity = 1; }733 onMouseMoved: { cursor.opacity = 1; }
720 }734 }
721735
722 // non-visual item736 // non-visual object
723 KeymapSwitcher {}737 KeymapSwitcher {}
724738
725 Rectangle {739 Rectangle {
726740
=== modified file 'qml/Stages/AbstractStage.qml'
--- qml/Stages/AbstractStage.qml 2016-04-20 17:09:15 +0000
+++ qml/Stages/AbstractStage.qml 2016-04-20 17:09:16 +0000
@@ -18,12 +18,19 @@
18import Ubuntu.Components 1.318import Ubuntu.Components 1.3
19import GSettings 1.019import GSettings 1.0
2020
21Rectangle {21FocusScope {
22 id: root22 id: root
2323
24 color: "#060606"24 property alias color: backRect.color
25 Rectangle {
26 id: backRect
27 color: "#060606"
28 anchors.fill: parent
29 }
2530
26 // Controls to be set from outside31 // Controls to be set from outside
32 property QtObject applicationManager
33 property QtObject topLevelSurfaceList
27 property bool altTabPressed34 property bool altTabPressed
28 property url background35 property url background
29 property bool beingResized36 property bool beingResized
3037
=== removed directory 'qml/Stages/Animations'
=== removed file 'qml/Stages/Animations/BaseSessionAnimation.qml'
--- qml/Stages/Animations/BaseSessionAnimation.qml 2015-07-15 15:07:19 +0000
+++ qml/Stages/Animations/BaseSessionAnimation.qml 1970-01-01 00:00:00 +0000
@@ -1,96 +0,0 @@
1/*
2 * Copyright (C) 2014 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.4
18
19/* This is the base case for surface animations, used when adding/removing * child surfaces.
20 * The class is meant to be overridden and changes/animations provided for state changes.
21 * NB. It is important to release the surface at the end of the "out" animation.
22 *
23 * Example - Simple fade in/out
24 *
25 * BaseSurfaceAnimation {
26 * outChanges: [ PropertyChanges { target: animation.surface; opacity: 0.0 } ]
27 * outAnimations: [
28 SequentialAnimation {
29 * NumberAnimation { target: animation.surface; property: "opacity"; duration: 300 }
30 * ScriptAction { script: { if (animation.parent.removing) animation.surface.release(); } }
31 * }
32 * ]
33 *
34 * inChanges: [ PropertyChanges { target: animation.surface; opacity: 1.0 } ]
35 * inAnimations: [ NumberAnimation { target: animation.surface; property: "opacity"; duration: 300 } ]
36 * }
37 */
38Item {
39 id: base
40 property var container
41 objectName: "sessionAnimation"
42
43 // changes applied when state changes to "from"
44 property list<QtObject> fromChanges
45 // transition animations when changing state to "from"
46 property list<QtObject> fromAnimations
47
48 // changes applied when state changes to "to"
49 property list<QtObject> toChanges
50 // transition animations when changing state to "to"
51 property list<QtObject> toAnimations
52
53 function start() {
54 // "prep" state forces toChanges without transition animations.
55 state = "prep"
56 state = "to";
57 }
58 function end() {
59 state = "from";
60 }
61
62 signal completed()
63
64 states: [
65 State {
66 name: "baseAnimation"
67 PropertyChanges { target: container; anchors.fill: undefined }
68 },
69
70 State {
71 name: "prep"
72 extend: "baseAnimation"
73 changes: fromChanges
74 },
75 State {
76 name: "from"
77 extend: "prep"
78 },
79 State {
80 name: "in"
81 extend: "baseAnimation"
82 changes: toChanges
83 }
84 ]
85
86 transitions: [
87 Transition {
88 to: "from"
89 animations: fromAnimations
90 },
91 Transition {
92 to: "to"
93 animations: toAnimations
94 }
95 ]
96}
970
=== removed file 'qml/Stages/Animations/SwipeFromBottomAnimation.qml'
--- qml/Stages/Animations/SwipeFromBottomAnimation.qml 2015-07-15 15:07:19 +0000
+++ qml/Stages/Animations/SwipeFromBottomAnimation.qml 1970-01-01 00:00:00 +0000
@@ -1,53 +0,0 @@
1/*
2 * Copyright (C) 2014 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.4
18import Ubuntu.Components 1.3
19
20BaseSessionAnimation {
21 id: animation
22
23 fromChanges: [
24 AnchorChanges {
25 target: container;
26 anchors.top: container.parent.bottom
27 }
28 ]
29 fromAnimations: [
30 SequentialAnimation {
31 // clip so we don't go out of parent's bounds during spread
32 PropertyAction { target: container.parent; property: "clip"; value: true }
33 AnchorAnimation { easing: UbuntuAnimation.StandardEasing; duration: UbuntuAnimation.BriskDuration }
34 PropertyAction { target: container.parent; property: "clip"; value: false }
35 ScriptAction { script: { animation.completed(); } }
36 }
37 ]
38
39 toChanges: [
40 AnchorChanges {
41 target: container;
42 anchors.top: container.parent.top
43 }
44 ]
45 toAnimations: [
46 SequentialAnimation {
47 // clip so we don't go out of parent's bounds during spread
48 PropertyAction { target: container.parent; property: "clip"; value: true }
49 AnchorAnimation { easing: UbuntuAnimation.StandardEasing; duration: UbuntuAnimation.BriskDuration }
50 PropertyAction { target: container.parent; property: "clip"; value: false }
51 }
52 ]
53}
540
=== modified file 'qml/Stages/ApplicationWindow.qml'
--- qml/Stages/ApplicationWindow.qml 2016-04-20 17:09:15 +0000
+++ qml/Stages/ApplicationWindow.qml 2016-04-20 17:09:16 +0000
@@ -20,35 +20,77 @@
2020
21FocusScope {21FocusScope {
22 id: root22 id: root
23 implicitWidth: sessionContainer.implicitWidth23 implicitWidth: surfaceContainer.implicitWidth
24 implicitHeight: sessionContainer.implicitHeight24 implicitHeight: surfaceContainer.implicitHeight
2525
26 // to be read from outside26 // to be read from outside
27 property alias interactive: sessionContainer.interactive27 property alias interactive: surfaceContainer.interactive
28 property bool orientationChangesEnabled: d.supportsSurfaceResize ? d.surfaceOldEnoughToBeResized : true28 property bool orientationChangesEnabled: d.supportsSurfaceResize ? d.surfaceOldEnoughToBeResized : true
29 readonly property string title: sessionContainer.surface && sessionContainer.surface.name !== "" ?29 readonly property string title: surface && surface.name !== "" ? surface.name : d.name
30 sessionContainer.surface.name : d.name
3130
32 // overridable from outside31 // overridable from outside
33 property bool fullscreen: application ? application.fullscreen : false32 property bool fullscreen: {
33 if (surface) {
34 return surface.state === Mir.FullscreenState;
35 } else if (application) {
36 return application.fullscreen;
37 } else {
38 return false;
39 }
40 }
3441
35 // to be set from outside42 // to be set from outside
43 property QtObject surface
36 property QtObject application44 property QtObject application
37 property int surfaceOrientationAngle45 property int surfaceOrientationAngle
38 property alias resizeSurface: sessionContainer.resizeSurface46 property alias resizeSurface: surfaceContainer.resizeSurface
39 property int requestedWidth: -147 property int requestedWidth: -1
40 property int requestedHeight: -148 property int requestedHeight: -1
4149
42 readonly property int minimumWidth: sessionContainer.surface ? sessionContainer.surface.minimumWidth : 050 readonly property int minimumWidth: surface ? surface.minimumWidth : 0
43 readonly property int minimumHeight: sessionContainer.surface ? sessionContainer.surface.minimumHeight : 051 readonly property int minimumHeight: surface ? surface.minimumHeight : 0
44 readonly property int maximumWidth: sessionContainer.surface ? sessionContainer.surface.maximumWidth : 052 readonly property int maximumWidth: surface ? surface.maximumWidth : 0
45 readonly property int maximumHeight: sessionContainer.surface ? sessionContainer.surface.maximumHeight : 053 readonly property int maximumHeight: surface ? surface.maximumHeight : 0
46 readonly property int widthIncrement: sessionContainer.surface ? sessionContainer.surface.widthIncrement : 054 readonly property int widthIncrement: surface ? surface.widthIncrement : 0
47 readonly property int heightIncrement: sessionContainer.surface ? sessionContainer.surface.heightIncrement : 055 readonly property int heightIncrement: surface ? surface.heightIncrement : 0
56
57 onSurfaceChanged: {
58 // The order in which the instructions are executed here matters, to that the correct state
59 // transitions in stateGroup take place.
60 // More specifically, the moment surfaceContainer.surface gets updated relative to the
61 // other instructions.
62 if (surface) {
63 surfaceContainer.surface = surface;
64 d.liveSurface = surface.live;
65 d.hadSurface = false;
66 surfaceInitTimer.start();
67 } else {
68 if (d.surfaceInitialized) {
69 d.hadSurface = true;
70 }
71 d.surfaceInitialized = false;
72 surfaceContainer.surface = null;
73 }
74 }
4875
49 QtObject {76 QtObject {
50 id: d77 id: d
5178
79 property bool liveSurface: false;
80 property var con: Connections {
81 target: root.surface
82 onLiveChanged: d.liveSurface = root.surface.live
83 }
84 // using liveSurface instead of root.surface.live because with the latter
85 // this expression is not reevaluated when root.surface changes
86 readonly property bool needToTakeScreenshot: root.surface && d.surfaceInitialized && !d.liveSurface
87 && applicationState !== ApplicationInfoInterface.Running
88 onNeedToTakeScreenshotChanged: {
89 if (needToTakeScreenshot && screenshotImage.status === Image.Null) {
90 screenshotImage.take();
91 }
92 }
93
52 // helpers so that we don't have to check for the existence of an application everywhere94 // helpers so that we don't have to check for the existence of an application everywhere
53 // (in order to avoid breaking qml binding due to a javascript exception)95 // (in order to avoid breaking qml binding due to a javascript exception)
54 readonly property string name: root.application ? root.application.name : ""96 readonly property string name: root.application ? root.application.name : ""
@@ -62,17 +104,7 @@
62 readonly property color splashColorFooter: root.application ? root.application.splashColorFooter : "#00000000"104 readonly property color splashColorFooter: root.application ? root.application.splashColorFooter : "#00000000"
63105
64 // Whether the Application had a surface before but lost it.106 // Whether the Application had a surface before but lost it.
65 property bool hadSurface: sessionContainer.surfaceContainer.hadSurface107 property bool hadSurface: false
66
67 readonly property bool needToTakeScreenshot:
68 ((sessionContainer.surface && d.surfaceInitialized) || d.hadSurface)
69 && screenshotImage.status === Image.Null
70 && d.applicationState === ApplicationInfoInterface.Stopped
71 onNeedToTakeScreenshotChanged: {
72 if (needToTakeScreenshot) {
73 screenshotImage.take();
74 }
75 }
76108
77 //FIXME - this is a hack to avoid the first few rendered frames as they109 //FIXME - this is a hack to avoid the first few rendered frames as they
78 // might show the UI accommodating due to surface resizes on startup.110 // might show the UI accommodating due to surface resizes on startup.
@@ -99,7 +131,9 @@
99 Timer {131 Timer {
100 id: surfaceInitTimer132 id: surfaceInitTimer
101 interval: 100133 interval: 100
102 onTriggered: { if (sessionContainer.surface) {d.surfaceInitialized = true;} }134 onTriggered: {
135 if (root.surface && root.surface.live) {d.surfaceInitialized = true;}
136 }
103 }137 }
104138
105 Timer {139 Timer {
@@ -117,7 +151,7 @@
117 function take() {151 function take() {
118 // Save memory by using a half-resolution (thus quarter size) screenshot.152 // Save memory by using a half-resolution (thus quarter size) screenshot.
119 // Do not make this a binding, we can only take the screenshot once!153 // Do not make this a binding, we can only take the screenshot once!
120 sessionContainer.grabToImage(154 surfaceContainer.grabToImage(
121 function(result) {155 function(result) {
122 screenshotImage.source = result.url;156 screenshotImage.source = result.url;
123 },157 },
@@ -144,46 +178,33 @@
144 }178 }
145 }179 }
146180
147 SessionContainer {181 SurfaceContainer {
148 id: sessionContainer182 id: surfaceContainer
149 // A fake application might not even have a session property.
150 session: application && application.session ? application.session : null
151
152 requestedWidth: root.requestedWidth183 requestedWidth: root.requestedWidth
153 requestedHeight: root.requestedHeight184 requestedHeight: root.requestedHeight
154
155 surfaceOrientationAngle: application && application.rotatesWindowContents ? root.surfaceOrientationAngle : 0185 surfaceOrientationAngle: application && application.rotatesWindowContents ? root.surfaceOrientationAngle : 0
156
157 onSurfaceChanged: {
158 if (sessionContainer.surface) {
159 surfaceInitTimer.start();
160 } else {
161 d.surfaceInitialized = false;
162 }
163 }
164
165 focus: true186 focus: true
166 }187 }
167188
168 // SessionContainer size drives ApplicationWindow size189 // SurfaceContainer size drives ApplicationWindow size
169 Binding {190 Binding {
170 target: root; property: "width"191 target: root; property: "width"
171 value: stateGroup.state === "surface" ? sessionContainer.width : root.requestedWidth192 value: stateGroup.state === "surface" ? surfaceContainer.width : root.requestedWidth
172 when: root.requestedWidth >= 0193 when: root.requestedWidth >= 0
173 }194 }
174 Binding {195 Binding {
175 target: root; property: "height"196 target: root; property: "height"
176 value: stateGroup.state === "surface" ? sessionContainer.height : root.requestedHeight197 value: stateGroup.state === "surface" ? surfaceContainer.height : root.requestedHeight
177 when: root.requestedHeight >= 0198 when: root.requestedHeight >= 0
178 }199 }
179200
180 // ApplicationWindow size drives SessionContainer size201 // ApplicationWindow size drives SurfaceContainer size
181 Binding {202 Binding {
182 target: sessionContainer; property: "width"; value: root.width203 target: surfaceContainer; property: "width"; value: root.width
183 when: root.requestedWidth < 0204 when: root.requestedWidth < 0
184 }205 }
185 Binding {206 Binding {
186 target: sessionContainer; property: "height"; value: root.height207 target: surfaceContainer; property: "height"; value: root.height
187 when: root.requestedHeight < 0208 when: root.requestedHeight < 0
188 }209 }
189210
@@ -194,32 +215,43 @@
194 State {215 State {
195 name: "void"216 name: "void"
196 when:217 when:
197 d.hadSurface && (!sessionContainer.surface || !d.surfaceInitialized)218 d.hadSurface && (!root.surface || !d.surfaceInitialized)
198 &&219 &&
199 screenshotImage.status !== Image.Ready220 screenshotImage.status !== Image.Ready
200 },221 },
201 State {222 State {
202 name: "splashScreen"223 name: "splashScreen"
203 when:224 when:
204 !d.hadSurface && (!sessionContainer.surface || !d.surfaceInitialized)225 !d.hadSurface && (!root.surface || !d.surfaceInitialized)
205 &&226 &&
206 screenshotImage.status !== Image.Ready227 screenshotImage.status !== Image.Ready
207 },228 },
208 State {229 State {
209 name: "surface"230 name: "surface"
210 when:231 when:
211 (sessionContainer.surface && d.surfaceInitialized)232 (root.surface && d.surfaceInitialized)
212 &&233 &&
213 (d.applicationState !== ApplicationInfoInterface.Stopped234 (d.liveSurface ||
214 || screenshotImage.status !== Image.Ready)235 (d.applicationState !== ApplicationInfoInterface.Running
236 && screenshotImage.status !== Image.Ready))
215 },237 },
216 State {238 State {
217 name: "screenshot"239 name: "screenshot"
218 when:240 when:
219 screenshotImage.status === Image.Ready241 screenshotImage.status === Image.Ready
220 &&242 &&
221 (d.applicationState === ApplicationInfoInterface.Stopped243 (d.applicationState !== ApplicationInfoInterface.Running
222 || !sessionContainer.surface || !d.surfaceInitialized)244 || !root.surface || !d.surfaceInitialized)
245 },
246 State {
247 // This is a dead end. From here we expect the surface to be removed from the model
248 // shortly after we stop referencing to it in our SurfaceContainer.
249 name: "closed"
250 when:
251 // The surface died while the application is running. It must have been closed
252 // by the shell or the application decided to destroy it by itself
253 root.surface && d.surfaceInitialized && !d.liveSurface
254 && d.applicationState === ApplicationInfoInterface.Running
223 }255 }
224 ]256 ]
225257
@@ -227,17 +259,17 @@
227 Transition {259 Transition {
228 from: ""; to: "splashScreen"260 from: ""; to: "splashScreen"
229 PropertyAction { target: splashLoader; property: "active"; value: true }261 PropertyAction { target: splashLoader; property: "active"; value: true }
230 PropertyAction { target: sessionContainer.surfaceContainer262 PropertyAction { target: surfaceContainer
231 property: "visible"; value: false }263 property: "visible"; value: false }
232 },264 },
233 Transition {265 Transition {
234 from: "splashScreen"; to: "surface"266 from: "splashScreen"; to: "surface"
235 SequentialAnimation {267 SequentialAnimation {
236 PropertyAction { target: sessionContainer.surfaceContainer268 PropertyAction { target: surfaceContainer
237 property: "opacity"; value: 0.0 }269 property: "opacity"; value: 0.0 }
238 PropertyAction { target: sessionContainer.surfaceContainer270 PropertyAction { target: surfaceContainer
239 property: "visible"; value: true }271 property: "visible"; value: true }
240 UbuntuNumberAnimation { target: sessionContainer.surfaceContainer; property: "opacity";272 UbuntuNumberAnimation { target: surfaceContainer; property: "opacity";
241 from: 0.0; to: 1.0273 from: 0.0; to: 1.0
242 duration: UbuntuAnimation.BriskDuration }274 duration: UbuntuAnimation.BriskDuration }
243 ScriptAction { script: {275 ScriptAction { script: {
@@ -253,12 +285,12 @@
253 surfaceIsOldTimer.stop();285 surfaceIsOldTimer.stop();
254 d.surfaceOldEnoughToBeResized = false;286 d.surfaceOldEnoughToBeResized = false;
255 splashLoader.active = true;287 splashLoader.active = true;
256 sessionContainer.surfaceContainer.visible = true;288 surfaceContainer.visible = true;
257 } }289 } }
258 UbuntuNumberAnimation { target: splashLoader; property: "opacity";290 UbuntuNumberAnimation { target: splashLoader; property: "opacity";
259 from: 0.0; to: 1.0291 from: 0.0; to: 1.0
260 duration: UbuntuAnimation.BriskDuration }292 duration: UbuntuAnimation.BriskDuration }
261 PropertyAction { target: sessionContainer.surfaceContainer293 PropertyAction { target: surfaceContainer
262 property: "visible"; value: false }294 property: "visible"; value: false }
263 }295 }
264 },296 },
@@ -274,15 +306,16 @@
274 from: 0.0; to: 1.0306 from: 0.0; to: 1.0
275 duration: UbuntuAnimation.BriskDuration }307 duration: UbuntuAnimation.BriskDuration }
276 ScriptAction { script: {308 ScriptAction { script: {
277 sessionContainer.surfaceContainer.visible = false;309 surfaceContainer.visible = false;
278 if (sessionContainer.session) { sessionContainer.session.release(); }310 surfaceContainer.surface = null;
311 d.hadSurface = true;
279 } }312 } }
280 }313 }
281 },314 },
282 Transition {315 Transition {
283 from: "screenshot"; to: "surface"316 from: "screenshot"; to: "surface"
284 SequentialAnimation {317 SequentialAnimation {
285 PropertyAction { target: sessionContainer.surfaceContainer318 PropertyAction { target: surfaceContainer
286 property: "visible"; value: true }319 property: "visible"; value: true }
287 UbuntuNumberAnimation { target: screenshotImage; property: "opacity";320 UbuntuNumberAnimation { target: screenshotImage; property: "opacity";
288 from: 1.0; to: 0.0321 from: 1.0; to: 0.0
@@ -310,22 +343,31 @@
310 ScriptAction { script: {343 ScriptAction { script: {
311 surfaceIsOldTimer.stop();344 surfaceIsOldTimer.stop();
312 d.surfaceOldEnoughToBeResized = false;345 d.surfaceOldEnoughToBeResized = false;
313 sessionContainer.surfaceContainer.visible = false;346 surfaceContainer.visible = false;
314 if (sessionContainer.session) { sessionContainer.session.release(); }
315 } }347 } }
316 },348 },
317 Transition {349 Transition {
318 from: "void"; to: "surface"350 from: "void"; to: "surface"
319 SequentialAnimation {351 SequentialAnimation {
320 PropertyAction { target: sessionContainer.surfaceContainer; property: "opacity"; value: 0.0 }352 PropertyAction { target: surfaceContainer; property: "opacity"; value: 0.0 }
321 PropertyAction { target: sessionContainer.surfaceContainer; property: "visible"; value: true }353 PropertyAction { target: surfaceContainer; property: "visible"; value: true }
322 UbuntuNumberAnimation { target: sessionContainer.surfaceContainer; property: "opacity";354 UbuntuNumberAnimation { target: surfaceContainer; property: "opacity";
323 from: 0.0; to: 1.0355 from: 0.0; to: 1.0
324 duration: UbuntuAnimation.BriskDuration }356 duration: UbuntuAnimation.BriskDuration }
325 ScriptAction { script: {357 ScriptAction { script: {
326 surfaceIsOldTimer.start();358 surfaceIsOldTimer.start();
327 } }359 } }
328 }360 }
361 },
362 Transition {
363 to: "closed"
364 SequentialAnimation {
365 ScriptAction { script: {
366 surfaceContainer.visible = false;
367 surfaceContainer.surface = null;
368 d.hadSurface = true;
369 } }
370 }
329 }371 }
330 ]372 ]
331 }373 }
332374
=== modified file 'qml/Stages/DecoratedWindow.qml'
--- qml/Stages/DecoratedWindow.qml 2016-02-17 13:17:12 +0000
+++ qml/Stages/DecoratedWindow.qml 2016-04-20 17:09:16 +0000
@@ -26,10 +26,10 @@
26 width: applicationWindow.width26 width: applicationWindow.width
27 height: (decorationShown ? decoration.height : 0) + applicationWindow.height27 height: (decorationShown ? decoration.height : 0) + applicationWindow.height
2828
29 property alias window: applicationWindow
30 property alias application: applicationWindow.application29 property alias application: applicationWindow.application
30 property alias surface: applicationWindow.surface
31 property alias active: decoration.active31 property alias active: decoration.active
32 property alias title: decoration.title32 readonly property alias title: applicationWindow.title
33 property alias fullscreen: applicationWindow.fullscreen33 property alias fullscreen: applicationWindow.fullscreen
3434
35 readonly property bool decorationShown: !fullscreen35 readonly property bool decorationShown: !fullscreen
@@ -79,11 +79,11 @@
79 WindowDecoration {79 WindowDecoration {
80 id: decoration80 id: decoration
81 target: root.parent81 target: root.parent
82 objectName: application ? "appWindowDecoration_" + application.appId : "appWindowDecoration_null"82 objectName: "appWindowDecoration"
83 anchors { left: parent.left; top: parent.top; right: parent.right }83 anchors { left: parent.left; top: parent.top; right: parent.right }
84 height: units.gu(3)84 height: units.gu(3)
85 width: root.width85 width: root.width
86 title: window.title86 title: applicationWindow.title
87 visible: root.decorationShown87 visible: root.decorationShown
8888
89 onClose: root.close();89 onClose: root.close();
@@ -94,7 +94,7 @@
9494
95 ApplicationWindow {95 ApplicationWindow {
96 id: applicationWindow96 id: applicationWindow
97 objectName: application ? "appWindow_" + application.appId : "appWindow_null"97 objectName: "appWindow"
98 anchors.top: parent.top98 anchors.top: parent.top
99 anchors.topMargin: decoration.height99 anchors.topMargin: decoration.height
100 anchors.left: parent.left100 anchors.left: parent.left
101101
=== modified file 'qml/Stages/DesktopSpread.qml'
--- qml/Stages/DesktopSpread.qml 2016-03-10 22:39:57 +0000
+++ qml/Stages/DesktopSpread.qml 2016-04-20 17:09:16 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright (C) 2015 Canonical, Ltd.2 * Copyright (C) 2015-2016 Canonical, Ltd.
3 *3 *
4 * This program is free software; you can redistribute it and/or modify4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by5 * it under the terms of the GNU General Public License as published by
@@ -20,6 +20,7 @@
20import Ubuntu.Gestures 0.120import Ubuntu.Gestures 0.1
21import Unity.Application 0.121import Unity.Application 0.1
22import "../Components"22import "../Components"
23import Utils 0.1
2324
24FocusScope {25FocusScope {
25 id: root26 id: root
@@ -69,12 +70,12 @@
69 }70 }
7071
71 function selectNext(isAutoRepeat) {72 function selectNext(isAutoRepeat) {
72 if (isAutoRepeat && spreadRepeater.highlightedIndex >= ApplicationManager.count -1) {73 if (isAutoRepeat && spreadRepeater.highlightedIndex >= topLevelSurfaceList.count -1) {
73 return; // AutoRepeat is not allowed to wrap around74 return; // AutoRepeat is not allowed to wrap around
74 }75 }
7576
76 spreadRepeater.highlightedIndex = (spreadRepeater.highlightedIndex + 1) % ApplicationManager.count;77 spreadRepeater.highlightedIndex = (spreadRepeater.highlightedIndex + 1) % topLevelSurfaceList.count;
77 var newContentX = ((spreadFlickable.contentWidth) / (ApplicationManager.count + 1)) * Math.max(0, Math.min(ApplicationManager.count - 5, spreadRepeater.highlightedIndex - 3));78 var newContentX = ((spreadFlickable.contentWidth) / (topLevelSurfaceList.count + 1)) * Math.max(0, Math.min(topLevelSurfaceList.count - 5, spreadRepeater.highlightedIndex - 3));
78 if (spreadFlickable.contentX < newContentX || spreadRepeater.highlightedIndex == 0) {79 if (spreadFlickable.contentX < newContentX || spreadRepeater.highlightedIndex == 0) {
79 spreadFlickable.snapTo(newContentX)80 spreadFlickable.snapTo(newContentX)
80 }81 }
@@ -85,10 +86,10 @@
85 return; // AutoRepeat is not allowed to wrap around86 return; // AutoRepeat is not allowed to wrap around
86 }87 }
8788
88 var newIndex = spreadRepeater.highlightedIndex - 1 >= 0 ? spreadRepeater.highlightedIndex - 1 : ApplicationManager.count - 1;89 var newIndex = spreadRepeater.highlightedIndex - 1 >= 0 ? spreadRepeater.highlightedIndex - 1 : topLevelSurfaceList.count - 1;
89 spreadRepeater.highlightedIndex = newIndex;90 spreadRepeater.highlightedIndex = newIndex;
90 var newContentX = ((spreadFlickable.contentWidth) / (ApplicationManager.count + 1)) * Math.max(0, Math.min(ApplicationManager.count - 5, spreadRepeater.highlightedIndex - 1));91 var newContentX = ((spreadFlickable.contentWidth) / (topLevelSurfaceList.count + 1)) * Math.max(0, Math.min(topLevelSurfaceList.count - 5, spreadRepeater.highlightedIndex - 1));
91 if (spreadFlickable.contentX > newContentX || newIndex == ApplicationManager.count -1) {92 if (spreadFlickable.contentX > newContentX || newIndex == topLevelSurfaceList.count -1) {
92 spreadFlickable.snapTo(newContentX)93 spreadFlickable.snapTo(newContentX)
93 }94 }
94 }95 }
@@ -98,8 +99,8 @@
98 if (spreadContainer.visible) {99 if (spreadContainer.visible) {
99 root.playFocusAnimation(spreadRepeater.highlightedIndex)100 root.playFocusAnimation(spreadRepeater.highlightedIndex)
100 }101 }
101 var application = ApplicationManager.get(spreadRepeater.highlightedIndex);102 var surface = topLevelSurfaceList.surfaceAt(spreadRepeater.highlightedIndex);
102 ApplicationManager.requestFocusApplication(application.appId);103 surface.requestFocus();
103 }104 }
104 }105 }
105106
@@ -144,7 +145,7 @@
144 Repeater {145 Repeater {
145 id: spreadRepeater146 id: spreadRepeater
146 objectName: "spreadRepeater"147 objectName: "spreadRepeater"
147 model: ApplicationManager148 model: topLevelSurfaceList
148149
149 property int highlightedIndex: -1150 property int highlightedIndex: -1
150 property int closingIndex: -1151 property int closingIndex: -1
@@ -182,7 +183,8 @@
182 objectName: "clippedSpreadDelegate"183 objectName: "clippedSpreadDelegate"
183 anchors.left: parent.left184 anchors.left: parent.left
184 anchors.top: parent.top185 anchors.top: parent.top
185 application: ApplicationManager.get(index)186 application: model.application
187 surface: model.surface
186 width: spreadMaths.spreadHeight188 width: spreadMaths.spreadHeight
187 height: spreadMaths.spreadHeight189 height: spreadMaths.spreadHeight
188190
@@ -216,7 +218,7 @@
216 id: spreadMaths218 id: spreadMaths
217 flickable: spreadFlickable219 flickable: spreadFlickable
218 itemIndex: index220 itemIndex: index
219 totalItems: Math.max(6, ApplicationManager.count)221 totalItems: Math.max(6, topLevelSurfaceList.count)
220 sceneHeight: root.height222 sceneHeight: root.height
221 itemHeight: spreadDelegate.height223 itemHeight: spreadDelegate.height
222 }224 }
@@ -310,7 +312,7 @@
310 Layout.preferredWidth: height * 8 / 7.6312 Layout.preferredWidth: height * 8 / 7.6
311 image: Image {313 image: Image {
312 anchors.fill: parent314 anchors.fill: parent
313 source: model.icon315 source: model.application.icon
314 Rectangle {316 Rectangle {
315 anchors.fill: parent317 anchors.fill: parent
316 color: "black"318 color: "black"
@@ -324,7 +326,9 @@
324 Label {326 Label {
325 Layout.fillWidth: true327 Layout.fillWidth: true
326 Layout.preferredHeight: units.gu(6)328 Layout.preferredHeight: units.gu(6)
327 text: model.name329 property string surfaceName: model.surface ? model.surface.name : ""
330 property string applicationName: model.application ? model.application.name : ""
331 text: surfaceName ? surfaceName : applicationName
328 wrapMode: Text.WordWrap332 wrapMode: Text.WordWrap
329 elide: Text.ElideRight333 elide: Text.ElideRight
330 maximumLineCount: 2334 maximumLineCount: 2
@@ -354,7 +358,7 @@
354 anchors.margins: -units.gu(2)358 anchors.margins: -units.gu(2)
355 onClicked: {359 onClicked: {
356 spreadRepeater.closingIndex = index;360 spreadRepeater.closingIndex = index;
357 ApplicationManager.stopApplication(model.appId)361 model.surface.close();
358 }362 }
359 }363 }
360 }364 }
@@ -421,7 +425,7 @@
421 objectName: "spreadFlickable"425 objectName: "spreadFlickable"
422 anchors.fill: parent426 anchors.fill: parent
423 property int minContentWidth: 6 * Math.min(height / 4, width / 5)427 property int minContentWidth: 6 * Math.min(height / 4, width / 5)
424 contentWidth: Math.max(6, ApplicationManager.count) * Math.min(height / 4, width / 5)428 contentWidth: Math.max(6, topLevelSurfaceList.count) * Math.min(height / 4, width / 5)
425 enabled: false429 enabled: false
426430
427 function snapTo(contentX) {431 function snapTo(contentX) {
@@ -523,7 +527,7 @@
523 Label {527 Label {
524 id: currentSelectedLabel528 id: currentSelectedLabel
525 anchors { bottom: parent.bottom; bottomMargin: root.height * 0.625; horizontalCenter: parent.horizontalCenter }529 anchors { bottom: parent.bottom; bottomMargin: root.height * 0.625; horizontalCenter: parent.horizontalCenter }
526 text: spreadRepeater.highlightedIndex >= 0 ? ApplicationManager.get(spreadRepeater.highlightedIndex).name : ""530 text: spreadRepeater.highlightedIndex >= 0 ? topLevelSurfaceList.surfaceAt(spreadRepeater.highlightedIndex).name : ""
527 visible: false531 visible: false
528 fontSize: "large"532 fontSize: "large"
529 }533 }
@@ -545,7 +549,7 @@
545 from: "*"549 from: "*"
546 to: "altTab"550 to: "altTab"
547 SequentialAnimation {551 SequentialAnimation {
548 PropertyAction { target: spreadRepeater; property: "highlightedIndex"; value: Math.min(ApplicationManager.count - 1, 1) }552 PropertyAction { target: spreadRepeater; property: "highlightedIndex"; value: Math.min(topLevelSurfaceList.count - 1, 1) }
549 PauseAnimation { duration: spreadContainer.animateIn ? 0 : 140 }553 PauseAnimation { duration: spreadContainer.animateIn ? 0 : 140 }
550 PropertyAction { target: workspaceSelector; property: "visible" }554 PropertyAction { target: workspaceSelector; property: "visible" }
551 PropertyAction { target: spreadContainer; property: "visible" }555 PropertyAction { target: spreadContainer; property: "visible" }
552556
=== modified file 'qml/Stages/DesktopSpreadDelegate.qml'
--- qml/Stages/DesktopSpreadDelegate.qml 2016-01-11 12:29:30 +0000
+++ qml/Stages/DesktopSpreadDelegate.qml 2016-04-20 17:09:16 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright (C) 2014-2015 Canonical, Ltd.2 * Copyright (C) 2014-2016 Canonical, Ltd.
3 *3 *
4 * This program is free software; you can redistribute it and/or modify4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by5 * it under the terms of the GNU General Public License as published by
@@ -25,12 +25,13 @@
2525
26 property alias window: applicationWindow26 property alias window: applicationWindow
27 property alias application: applicationWindow.application27 property alias application: applicationWindow.application
28 property alias surface: applicationWindow.surface
2829
29 property bool highlightShown: false30 property bool highlightShown: false
30 property real shadowOpacity: 131 property real shadowOpacity: 1
3132
32 property int windowWidth: application && application.session && application.session.lastSurface ? application.session.lastSurface.size.width : 033 property int windowWidth: surface ? surface.size.width : 0
33 property int windowHeight: application && application.session && application.session.lastSurface ? application.session.lastSurface.size.height : 034 property int windowHeight: surface ? surface.size.height : 0
3435
35 state: "normal"36 state: "normal"
36 states: [37 states: [
3738
=== modified file 'qml/Stages/DesktopStage.qml'
--- qml/Stages/DesktopStage.qml 2016-04-20 17:09:15 +0000
+++ qml/Stages/DesktopStage.qml 2016-04-20 17:09:16 +0000
@@ -39,45 +39,18 @@
39 // Used by TutorialRight39 // Used by TutorialRight
40 property bool spreadShown: spread.state == "altTab"40 property bool spreadShown: spread.state == "altTab"
4141
42 mainApp: ApplicationManager.focusedApplicationId42 mainApp: priv.focusedAppDelegate ? priv.focusedAppDelegate.application : null
43 ? ApplicationManager.findApplication(ApplicationManager.focusedApplicationId)
44 : null
4543
46 // application windows never rotate independently44 // application windows never rotate independently
47 mainAppWindowOrientationAngle: shellOrientationAngle45 mainAppWindowOrientationAngle: shellOrientationAngle
4846
49 orientationChangesEnabled: true47 orientationChangesEnabled: true
5048
51 Connections {
52 target: ApplicationManager
53 onApplicationAdded: {
54 if (spread.state == "altTab") {
55 spread.state = "";
56 }
57
58 ApplicationManager.focusApplication(appId);
59 }
60
61 onApplicationRemoved: {
62 priv.focusNext();
63 }
64
65 onFocusRequested: {
66 var appIndex = priv.indexOf(appId);
67 var appDelegate = appRepeater.itemAt(appIndex);
68 appDelegate.restore();
69
70 if (spread.state == "altTab") {
71 spread.cancel();
72 }
73 }
74 }
75
76 GlobalShortcut {49 GlobalShortcut {
77 id: closeWindowShortcut50 id: closeWindowShortcut
78 shortcut: Qt.AltModifier|Qt.Key_F451 shortcut: Qt.AltModifier|Qt.Key_F4
79 onTriggered: ApplicationManager.stopApplication(priv.focusedAppId)52 onTriggered: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.close(); } }
80 active: priv.focusedAppId !== ""53 active: priv.focusedAppDelegate !== null
81 }54 }
8255
83 GlobalShortcut {56 GlobalShortcut {
@@ -121,40 +94,40 @@
121 active: priv.focusedAppDelegate !== null94 active: priv.focusedAppDelegate !== null
122 }95 }
12396
97 Connections {
98 target: root.topLevelSurfaceList
99 onCountChanged: {
100 if (spread.state == "altTab") {
101 spread.cancel();
102 }
103 }
104 }
105
124 QtObject {106 QtObject {
125 id: priv107 id: priv
108 objectName: "DesktopStagePrivate"
126109
127 readonly property string focusedAppId: ApplicationManager.focusedApplicationId110 property var focusedAppDelegate: null
128 readonly property var focusedAppDelegate: {111 onFocusedAppDelegateChanged: {
129 var index = indexOf(focusedAppId);112 if (spread.state == "altTab") {
130 return index >= 0 && index < appRepeater.count ? appRepeater.itemAt(index) : null113 spread.state = "";
114 }
131 }115 }
132 onFocusedAppDelegateChanged: updateForegroundMaximizedApp();
133116
134 property int foregroundMaximizedAppZ: -1117 property var foregroundMaximizedAppDelegate: null // for stuff like drop shadow and focusing maximized app by clicking panel
135 property int foregroundMaximizedAppIndex: -1 // for stuff like drop shadow and focusing maximized app by clicking panel
136118
137 function updateForegroundMaximizedApp() {119 function updateForegroundMaximizedApp() {
138 var tmp = -1;120 var found = false;
139 var tmpAppId = -1;121 for (var i = 0; i < appRepeater.count && !found; i++) {
140 for (var i = appRepeater.count - 1; i >= 0; i--) {
141 var item = appRepeater.itemAt(i);122 var item = appRepeater.itemAt(i);
142 if (item && item.visuallyMaximized) {123 if (item && item.visuallyMaximized) {
143 tmpAppId = i;124 foregroundMaximizedAppDelegate = item;
144 tmp = Math.max(tmp, item.normalZ);125 found = true;
145 }126 }
146 }127 }
147 foregroundMaximizedAppZ = tmp;128 if (!found) {
148 foregroundMaximizedAppIndex = tmpAppId;129 foregroundMaximizedAppDelegate = null;
149 }130 }
150
151 function indexOf(appId) {
152 for (var i = 0; i < ApplicationManager.count; i++) {
153 if (ApplicationManager.get(i).appId == appId) {
154 return i;
155 }
156 }
157 return -1;
158 }131 }
159132
160 function minimizeAllWindows() {133 function minimizeAllWindows() {
@@ -164,16 +137,13 @@
164 appDelegate.minimize();137 appDelegate.minimize();
165 }138 }
166 }139 }
167
168 ApplicationManager.unfocusCurrentApplication(); // no app should have focus at this point
169 }140 }
170141
171 function focusNext() {142 function focusNext() {
172 ApplicationManager.unfocusCurrentApplication();
173 for (var i = 0; i < appRepeater.count; i++) {143 for (var i = 0; i < appRepeater.count; i++) {
174 var appDelegate = appRepeater.itemAt(i);144 var appDelegate = appRepeater.itemAt(i);
175 if (appDelegate && !appDelegate.minimized) {145 if (appDelegate && !appDelegate.minimized) {
176 ApplicationManager.focusApplication(appDelegate.appId);146 appDelegate.focus = true;
177 return;147 return;
178 }148 }
179 }149 }
@@ -182,15 +152,14 @@
182152
183 Connections {153 Connections {
184 target: PanelState154 target: PanelState
185 onClose: {155 onClose: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.close(); } }
186 ApplicationManager.stopApplication(ApplicationManager.focusedApplicationId)156 onMinimize: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.minimize(); } }
157 onMaximize: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.restoreFromMaximized(); } }
158 onFocusMaximizedApp: {
159 if (priv.foregroundMaximizedAppDelegate) {
160 priv.foregroundMaximizedAppDelegate.focus = true;
161 }
187 }162 }
188 onMinimize: priv.focusedAppDelegate && priv.focusedAppDelegate.minimize();
189 onMaximize: priv.focusedAppDelegate // don't restore minimized apps when double clicking the panel
190 && priv.focusedAppDelegate.restoreFromMaximized();
191 onFocusMaximizedApp: if (priv.foregroundMaximizedAppIndex != -1) {
192 ApplicationManager.focusApplication(appRepeater.itemAt(priv.foregroundMaximizedAppIndex).appId);
193 }
194 }163 }
195164
196 Binding {165 Binding {
@@ -218,7 +187,7 @@
218 Binding {187 Binding {
219 target: PanelState188 target: PanelState
220 property: "dropShadow"189 property: "dropShadow"
221 value: priv.focusedAppDelegate && !priv.focusedAppDelegate.maximized && priv.foregroundMaximizedAppIndex !== -1190 value: priv.focusedAppDelegate && !priv.focusedAppDelegate.maximized && priv.foregroundMaximizedAppDelegate !== null
222 }191 }
223192
224 Component.onDestruction: {193 Component.onDestruction: {
@@ -227,6 +196,27 @@
227 PanelState.dropShadow = false;196 PanelState.dropShadow = false;
228 }197 }
229198
199 Instantiator {
200 model: root.applicationManager
201 delegate: Binding {
202 target: model.application
203 property: "requestedState"
204
205 // TODO: figure out some lifecycle policy, like suspending minimized apps
206 // if running on a tablet or something.
207 // TODO: If the device has a dozen suspended apps because it was running
208 // in staged mode, when it switches to Windowed mode it will suddenly
209 // resume all those apps at once. We might want to avoid that.
210 value: ApplicationInfoInterface.RequestedRunning // Always running for now
211 }
212 }
213
214 Binding {
215 target: MirFocusController
216 property: "focusedSurface"
217 value: priv.focusedAppDelegate ? priv.focusedAppDelegate.surface : null
218 when: !appRepeater.startingUp && root.parent
219 }
230220
231 FocusScope {221 FocusScope {
232 id: appContainer222 id: appContainer
@@ -242,20 +232,25 @@
242 fillMode: Image.PreserveAspectCrop232 fillMode: Image.PreserveAspectCrop
243 }233 }
244234
245 Repeater {235 TopLevelSurfaceRepeater {
246 id: appRepeater236 id: appRepeater
247 model: ApplicationManager237 model: topLevelSurfaceList
248 objectName: "appRepeater"238 objectName: "appRepeater"
249239
250 delegate: FocusScope {240 delegate: FocusScope {
251 id: appDelegate241 id: appDelegate
252 objectName: "appDelegate_" + appId242 objectName: "appDelegate_" + model.id
253 // z might be overriden in some cases by effects, but we need z ordering243 // z might be overriden in some cases by effects, but we need z ordering
254 // to calculate occlusion detection244 // to calculate occlusion detection
255 property int normalZ: ApplicationManager.count - index245 property int normalZ: topLevelSurfaceList.count - index
246 onNormalZChanged: {
247 if (visuallyMaximized) {
248 priv.updateForegroundMaximizedApp();
249 }
250 }
256 z: normalZ251 z: normalZ
257 y: PanelState.panelHeight252 y: PanelState.panelHeight
258 focus: appId === priv.focusedAppId253
259 width: decoratedWindow.width254 width: decoratedWindow.width
260 height: decoratedWindow.height255 height: decoratedWindow.height
261 property int requestedWidth: -1256 property int requestedWidth: -1
@@ -280,36 +275,99 @@
280 readonly property alias minimized: appDelegatePrivate.minimized275 readonly property alias minimized: appDelegatePrivate.minimized
281 readonly property alias fullscreen: decoratedWindow.fullscreen276 readonly property alias fullscreen: decoratedWindow.fullscreen
282277
283 readonly property string appId: model.appId278 readonly property var application: model.application
284 property bool animationsEnabled: true279 property bool animationsEnabled: true
285 property alias title: decoratedWindow.title280 property alias title: decoratedWindow.title
286 readonly property string appName: model.name281 readonly property string appName: model.application ? model.application.name : ""
287 property bool visuallyMaximized: false282 property bool visuallyMaximized: false
288 property bool visuallyMinimized: false283 property bool visuallyMinimized: false
289284
285 readonly property var surface: model.surface
286
287 function claimFocus() {
288 if (spread.state == "altTab") {
289 spread.cancel();
290 }
291 appDelegate.restore();
292 }
293 Connections {
294 target: model.surface
295 onFocusRequested: claimFocus();
296 }
297 Connections {
298 target: model.application
299 onFocusRequested: {
300 if (!model.surface) {
301 // when an app has no surfaces, we assume there's only one entry representing it:
302 // this delegate.
303 claimFocus();
304 } else {
305 // if the application has surfaces, focus request should be at surface-level.
306 }
307 }
308 }
309
290 onFocusChanged: {310 onFocusChanged: {
291 if (focus && ApplicationManager.focusedApplicationId !== appId) {311 if (appRepeater.startingUp)
292 ApplicationManager.focusApplication(appId);312 return;
313
314 if (focus) {
315 priv.focusedAppDelegate = appDelegate;
316
317 // If we're orphan (!parent) it means this stage is no longer the current one
318 // and will be deleted shortly. So we should no longer have a say over the model
319 if (root.parent) {
320 topLevelSurfaceList.raiseId(model.id);
321 }
322 } else if (!focus && priv.focusedAppDelegate === appDelegate) {
323 priv.focusedAppDelegate = null;
324 // FIXME: No idea why the Binding{} doens't update when focusedAppDelegate turns null
325 MirFocusController.focusedSurface = null;
326 }
327 }
328 Component.onCompleted: {
329 // NB: We're differentiating if this delegate was created in response to a new entry in the model
330 // or if the Repeater is just populating itself with delegates to match the model it received.
331 if (!appRepeater.startingUp) {
332 // a top level window is always the focused one when it first appears, unfocusing
333 // any preexisting one
334 focus = true;
335 }
336 }
337 Component.onDestruction: {
338 if (!root.parent) {
339 // This stage is about to be destroyed. Don't mess up with the model at this point
340 return;
341 }
342
343 if (visuallyMaximized) {
344 priv.updateForegroundMaximizedApp();
345 }
346
347 if (focus) {
348 // focus some other window
349 for (var i = 0; i < appRepeater.count; i++) {
350 var appDelegate = appRepeater.itemAt(i);
351 if (appDelegate && !appDelegate.minimized && i != index) {
352 appDelegate.focus = true;
353 return;
354 }
355 }
293 }356 }
294 }357 }
295358
296 onVisuallyMaximizedChanged: priv.updateForegroundMaximizedApp()359 onVisuallyMaximizedChanged: priv.updateForegroundMaximizedApp()
297360
298 visible: !visuallyMinimized &&361 visible: (
299 !greeter.fullyShown &&362 !visuallyMinimized
300 (priv.foregroundMaximizedAppZ === -1 || priv.foregroundMaximizedAppZ <= z) ||363 && !greeter.fullyShown
301 decoratedWindow.fullscreen ||364 && (priv.foregroundMaximizedAppDelegate === null || priv.foregroundMaximizedAppDelegate.normalZ <= z)
302 (spread.state == "altTab" && index === spread.highlightedIndex)365 )
366 || decoratedWindow.fullscreen
367 || (spread.state == "altTab" && index === spread.highlightedIndex)
303368
304 Binding {369 function close() {
305 target: ApplicationManager.get(index)370 model.surface.close();
306 property: "requestedState"
307 // TODO: figure out some lifecycle policy, like suspending minimized apps
308 // if running on a tablet or something.
309 // TODO: If the device has a dozen suspended apps because it was running
310 // in staged mode, when it switches to Windowed mode it will suddenly
311 // resume all those apps at once. We might want to avoid that.
312 value: ApplicationInfoInterface.RequestedRunning // Always running for now
313 }371 }
314372
315 function maximize(animated) {373 function maximize(animated) {
@@ -351,7 +409,8 @@
351 maximizeLeft();409 maximizeLeft();
352 else if (maximizedRight)410 else if (maximizedRight)
353 maximizeRight();411 maximizeRight();
354 ApplicationManager.focusApplication(appId);412
413 focus = true;
355 }414 }
356415
357 function playFocusAnimation() {416 function playFocusAnimation() {
@@ -369,7 +428,7 @@
369428
370 states: [429 states: [
371 State {430 State {
372 name: "fullscreen"; when: decoratedWindow.fullscreen431 name: "fullscreen"; when: decoratedWindow.fullscreen && !appDelegate.minimized
373 PropertyChanges {432 PropertyChanges {
374 target: appDelegate;433 target: appDelegate;
375 x: 0;434 x: 0;
@@ -462,6 +521,7 @@
462 ScriptAction {521 ScriptAction {
463 script: {522 script: {
464 if (appDelegate.minimized) {523 if (appDelegate.minimized) {
524 appDelegate.focus = false;
465 priv.focusNext();525 priv.focusNext();
466 }526 }
467 }527 }
@@ -486,7 +546,7 @@
486 id: previewBinding546 id: previewBinding
487 target: appDelegate547 target: appDelegate
488 property: "z"548 property: "z"
489 value: ApplicationManager.count + 1549 value: topLevelSurfaceList.count + 1
490 when: index == spread.highlightedIndex && spread.ready550 when: index == spread.highlightedIndex && spread.ready
491 }551 }
492552
@@ -497,12 +557,12 @@
497 minWidth: units.gu(10)557 minWidth: units.gu(10)
498 minHeight: units.gu(10)558 minHeight: units.gu(10)
499 borderThickness: units.gu(2)559 borderThickness: units.gu(2)
500 windowId: model.appId // FIXME: Change this to point to windowId once we have such a thing560 windowId: model.application.appId // FIXME: Change this to point to windowId once we have such a thing
501 screenWidth: appContainer.width561 screenWidth: appContainer.width
502 screenHeight: appContainer.height562 screenHeight: appContainer.height
503 leftMargin: root.leftMargin563 leftMargin: root.leftMargin
504564
505 onPressed: { ApplicationManager.focusApplication(model.appId) }565 onPressed: { appDelegate.focus = true; }
506566
507 Component.onCompleted: {567 Component.onCompleted: {
508 loadWindowState();568 loadWindowState();
@@ -529,24 +589,25 @@
529 objectName: "decoratedWindow"589 objectName: "decoratedWindow"
530 anchors.left: appDelegate.left590 anchors.left: appDelegate.left
531 anchors.top: appDelegate.top591 anchors.top: appDelegate.top
532 application: ApplicationManager.get(index)592 application: model.application
533 active: ApplicationManager.focusedApplicationId === model.appId593 surface: model.surface
594 active: appDelegate.focus
534 focus: true595 focus: true
535596
536 requestedWidth: appDelegate.requestedWidth597 requestedWidth: appDelegate.requestedWidth
537 requestedHeight: appDelegate.requestedHeight598 requestedHeight: appDelegate.requestedHeight
538599
539 onClose: ApplicationManager.stopApplication(model.appId)600 onClose: { appDelegate.close(); }
540 onMaximize: appDelegate.maximized || appDelegate.maximizedLeft || appDelegate.maximizedRight601 onMaximize: appDelegate.maximized || appDelegate.maximizedLeft || appDelegate.maximizedRight
541 ? appDelegate.restoreFromMaximized() : appDelegate.maximize()602 ? appDelegate.restoreFromMaximized() : appDelegate.maximize()
542 onMinimize: appDelegate.minimize()603 onMinimize: appDelegate.minimize()
543 onDecorationPressed: { ApplicationManager.focusApplication(model.appId) }604 onDecorationPressed: { appDelegate.focus = true; }
544 }605 }
545606
546 WindowedFullscreenPolicy {607 WindowedFullscreenPolicy {
547 id: fullscreenPolicy608 id: fullscreenPolicy
548 active: true609 active: true
549 application: decoratedWindow.application610 surface: model.surface
550 }611 }
551 }612 }
552 }613 }
553614
=== modified file 'qml/Stages/PhoneStage.qml'
--- qml/Stages/PhoneStage.qml 2016-04-20 17:09:15 +0000
+++ qml/Stages/PhoneStage.qml 2016-04-20 17:09:16 +0000
@@ -26,7 +26,6 @@
26AbstractStage {26AbstractStage {
27 id: root27 id: root
2828
29 property QtObject applicationManager: ApplicationManager
30 property bool focusFirstApp: true // If false, focused app will appear on right edge like other apps29 property bool focusFirstApp: true // If false, focused app will appear on right edge like other apps
31 property bool altTabEnabled: true30 property bool altTabEnabled: true
32 property real startScale: 1.131 property real startScale: 1.1
@@ -107,9 +106,7 @@
107 }106 }
108 }107 }
109108
110 mainApp: applicationManager.focusedApplicationId109 mainApp: priv.focusedAppDelegate ? priv.focusedAppDelegate.application : null
111 ? applicationManager.findApplication(applicationManager.focusedApplicationId)
112 : null
113110
114 orientationChangesEnabled: priv.focusedAppOrientationChangesEnabled111 orientationChangesEnabled: priv.focusedAppOrientationChangesEnabled
115 && !priv.focusedAppDelegateIsDislocated112 && !priv.focusedAppDelegateIsDislocated
@@ -158,72 +155,27 @@
158 onTriggered: { root.beingResized = false; }155 onTriggered: { root.beingResized = false; }
159 }156 }
160157
161 Connections {
162 target: applicationManager
163
164 onFocusRequested: {
165 if (spreadView.phase > 0) {
166 spreadView.snapTo(priv.indexOf(appId));
167 } else {
168 applicationManager.focusApplication(appId);
169 }
170 }
171
172 onApplicationAdded: {
173 if (spreadView.phase == 2) {
174 spreadView.snapTo(applicationManager.count - 1);
175 } else {
176 spreadView.phase = 0;
177 spreadView.contentX = -spreadView.shift;
178 applicationManager.focusApplication(appId);
179 }
180 }
181
182 onApplicationRemoved: {
183 // Unless we're closing the app ourselves in the spread,
184 // lets make sure the spread doesn't mess up by the changing app list.
185 if (spreadView.closingIndex == -1) {
186 spreadView.phase = 0;
187 spreadView.contentX = -spreadView.shift;
188 focusTopMostApp();
189 }
190 }
191
192 function focusTopMostApp() {
193 if (applicationManager.count > 0) {
194 var topmostApp = applicationManager.get(0);
195 applicationManager.focusApplication(topmostApp.appId);
196 }
197 }
198 }
199
200 QtObject {158 QtObject {
201 id: priv159 id: priv
202160
203 property string focusedAppId: root.applicationManager.focusedApplicationId
204 property bool focusedAppOrientationChangesEnabled: false161 property bool focusedAppOrientationChangesEnabled: false
205 readonly property int firstSpreadIndex: root.focusFirstApp ? 1 : 0162 readonly property int firstSpreadIndex: root.focusFirstApp ? 1 : 0
206 readonly property var focusedAppDelegate: {163 property var focusedAppDelegate
207 var index = indexOf(focusedAppId);164 // NB! This may differ from applicationManager.focusedApplicationId if focusedAppDelegate
208 return index >= 0 && index < spreadRepeater.count ? spreadRepeater.itemAt(index) : null165 // contains a screenshot instead of a surface.
209 }166 property string focusedAppId: focusedAppDelegate ? focusedAppDelegate.application.appId : ""
210167
211 property real oldInverseProgress: 0168 property real oldInverseProgress: 0
212 property bool animateX: false169 property bool animateX: false
213 property int highlightIndex: 0170 property int highlightIndex: 0
214171
215 onFocusedAppDelegateChanged: {172 property bool focusedAppDelegateIsDislocated: focusedAppDelegate ?
216 if (focusedAppDelegate) {
217 focusedAppDelegate.focus = true;
218 }
219 }
220
221 property bool focusedAppDelegateIsDislocated: focusedAppDelegate &&
222 (focusedAppDelegate.x !== 0 || focusedAppDelegate.xBehavior.running)173 (focusedAppDelegate.x !== 0 || focusedAppDelegate.xBehavior.running)
174 : false
223175
224 function indexOf(appId) {176 function indexOf(appId) {
225 for (var i = 0; i < root.applicationManager.count; i++) {177 for (var i = 0; i < spreadRepeater.count; i++) {
226 if (root.applicationManager.get(i).appId == appId) {178 if (spreadRepeater.itemAt(i).application.appId == appId) {
227 return i;179 return i;
228 }180 }
229 }181 }
@@ -237,8 +189,8 @@
237 function reset() {189 function reset() {
238 // The app that's about to go to foreground has to be focused, otherwise190 // The app that's about to go to foreground has to be focused, otherwise
239 // it would leave us in an inconsistent state.191 // it would leave us in an inconsistent state.
240 if (!root.applicationManager.focusedApplicationId && root.applicationManager.count > 0) {192 if (!MirFocusController.focusedSurface && spreadRepeater.count > 0) {
241 root.applicationManager.focusApplication(root.applicationManager.get(0).appId);193 spreadRepeater.itemAt(0).focus = true;
242 }194 }
243195
244 spreadView.selectedIndex = -1;196 spreadView.selectedIndex = -1;
@@ -258,6 +210,36 @@
258 }210 }
259 }211 }
260212
213 Instantiator {
214 model: root.applicationManager
215 delegate: QtObject {
216 property var stateBinding: Binding {
217 readonly property bool isDash: model.application ? model.application.appId == "unity8-dash" : false
218 target: model.application
219 property: "requestedState"
220 value: (isDash && root.keepDashRunning)
221 || (!root.suspended && model.application && priv.focusedAppId === model.application.appId)
222 ? ApplicationInfoInterface.RequestedRunning
223 : ApplicationInfoInterface.RequestedSuspended
224 }
225
226 property var lifecycleBinding: Binding {
227 target: model.application
228 property: "exemptFromLifecycle"
229 value: model.application
230 ? (!model.application.isTouchApp || isExemptFromLifecycle(model.application.appId))
231 : false
232 }
233 }
234 }
235
236 Binding {
237 target: MirFocusController
238 property: "focusedSurface"
239 value: priv.focusedAppDelegate ? priv.focusedAppDelegate.surface : null
240 when: root.parent && !spreadRepeater.startingUp
241 }
242
261 Flickable {243 Flickable {
262 id: spreadView244 id: spreadView
263 objectName: "spreadView"245 objectName: "spreadView"
@@ -308,13 +290,38 @@
308 // rely on having Flickable.contentX keeping an out-of-bounds value when it's set programatically290 // rely on having Flickable.contentX keeping an out-of-bounds value when it's set programatically
309 // (as opposed to having contentX reaching an out-of-bounds value through dragging, which will trigger291 // (as opposed to having contentX reaching an out-of-bounds value through dragging, which will trigger
310 // the Flickable.boundsBehavior upon release).292 // the Flickable.boundsBehavior upon release).
311 onContentXChanged: { forceItToRemainStillIfBeingResized(); }293 onContentXChanged: {
294 if (!undoContentXReset()) {
295 forceItToRemainStillIfBeingResized();
296 }
297 }
312 onShiftChanged: { forceItToRemainStillIfBeingResized(); }298 onShiftChanged: { forceItToRemainStillIfBeingResized(); }
313 function forceItToRemainStillIfBeingResized() {299 function forceItToRemainStillIfBeingResized() {
314 if (root.beingResized && contentX != -spreadView.shift) {300 if (root.beingResized && contentX != -spreadView.shift) {
315 contentX = -spreadView.shift;301 contentX = -spreadView.shift;
316 }302 }
317 }303 }
304 function undoContentXReset() {
305 if (contentWidth <= 0) {
306 contentWidthOnLastContentXChange = contentWidth;
307 lastContentX = contentX;
308 return false;
309 }
310
311 if (contentWidth !== contentWidthOnLastContentXChange
312 && lastContentX === -shift && contentX === 0) {
313 // Flickable is resetting contentX because contentWidth has changed. Undo it.
314 contentX = -shift;
315 return true;
316 }
317
318 contentWidthOnLastContentXChange = contentWidth;
319 lastContentX = contentX;
320 return false;
321 }
322 property real contentWidthOnLastContentXChange: -1
323 property real lastContentX: 0
324 // </FIXME-contentX>
318325
319 Behavior on contentX {326 Behavior on contentX {
320 enabled: root.altTabPressed327 enabled: root.altTabPressed
@@ -377,7 +384,7 @@
377 snapAnimation.start();384 snapAnimation.start();
378 return;385 return;
379 }386 }
380 if (root.applicationManager.count <= index) {387 if (topLevelSurfaceList.count <= index) {
381 // In case we're trying to snap to some non existing app, lets snap back to the first one388 // In case we're trying to snap to some non existing app, lets snap back to the first one
382 index = 0;389 index = 0;
383 }390 }
@@ -414,7 +421,8 @@
414 ScriptAction {421 ScriptAction {
415 script: {422 script: {
416 if (spreadView.selectedIndex >= 0) {423 if (spreadView.selectedIndex >= 0) {
417 root.applicationManager.focusApplication(root.applicationManager.get(spreadView.selectedIndex).appId);424 var delegate = spreadRepeater.itemAt(spreadView.selectedIndex)
425 delegate.focus = true;
418426
419 spreadView.selectedIndex = -1;427 spreadView.selectedIndex = -1;
420 spreadView.phase = 0;428 spreadView.phase = 0;
@@ -429,7 +437,7 @@
429 // This width controls how much the spread can be flicked left/right. It's composed of:437 // This width controls how much the spread can be flicked left/right. It's composed of:
430 // tileDistance * app count (with a minimum of 3 apps, in order to also allow moving 1 and 2 apps a bit)438 // tileDistance * app count (with a minimum of 3 apps, in order to also allow moving 1 and 2 apps a bit)
431 // + some constant value (still scales with the screen width) which looks good and somewhat fills the screen439 // + some constant value (still scales with the screen width) which looks good and somewhat fills the screen
432 width: Math.max(3, root.applicationManager.count) * spreadView.tileDistance + (spreadView.width - spreadView.tileDistance) * 1.5440 width: Math.max(3, topLevelSurfaceList.count) * spreadView.tileDistance + (spreadView.width - spreadView.tileDistance) * 1.5
433 height: parent.height441 height: parent.height
434 Behavior on width {442 Behavior on width {
435 enabled: spreadView.closingIndex >= 0443 enabled: spreadView.closingIndex >= 0
@@ -449,13 +457,30 @@
449 }457 }
450 }458 }
451459
452 Repeater {460 TopLevelSurfaceRepeater {
453 id: spreadRepeater461 id: spreadRepeater
454 objectName: "spreadRepeater"462 objectName: "spreadRepeater"
455 model: root.applicationManager463 model: topLevelSurfaceList
464
465 onItemRemoved: {
466 // Unless we're closing the app ourselves in the spread,
467 // lets make sure the spread doesn't mess up by the changing app list.
468 if (spreadView.closingIndex == -1) {
469 spreadView.phase = 0;
470 spreadView.contentX = -spreadView.shift;
471 focusTopMostApp();
472 }
473 }
474 function focusTopMostApp() {
475 if (spreadRepeater.count > 0) {
476 var topmostDelegate = spreadRepeater.itemAt(0);
477 topmostDelegate.focus = true;
478 }
479 }
480
456 delegate: TransformedSpreadDelegate {481 delegate: TransformedSpreadDelegate {
457 id: appDelegate482 id: appDelegate
458 objectName: "appDelegate" + index483 objectName: "spreadDelegate_" + model.id
459 startAngle: 45484 startAngle: 45
460 endAngle: 5485 endAngle: 5
461 startScale: root.startScale486 startScale: root.startScale
@@ -467,28 +492,61 @@
467 selected: spreadView.selectedIndex == index492 selected: spreadView.selectedIndex == index
468 otherSelected: spreadView.selectedIndex >= 0 && !selected493 otherSelected: spreadView.selectedIndex >= 0 && !selected
469 interactive: !spreadView.interactive && spreadView.phase === 0494 interactive: !spreadView.interactive && spreadView.phase === 0
470 && priv.fullyShowingFocusedApp && root.interactive && isFocused495 && priv.fullyShowingFocusedApp && root.interactive && focus
471 swipeToCloseEnabled: spreadView.interactive && root.interactive && !snapAnimation.running496 swipeToCloseEnabled: spreadView.interactive && root.interactive && !snapAnimation.running
472 maximizedAppTopMargin: root.maximizedAppTopMargin497 maximizedAppTopMargin: root.maximizedAppTopMargin
473 dropShadow: spreadView.active || priv.focusedAppDelegateIsDislocated498 dropShadow: spreadView.active || priv.focusedAppDelegateIsDislocated
474 focusFirstApp: root.focusFirstApp499 focusFirstApp: root.focusFirstApp
475 highlightShown: root.altTabPressed && index === priv.highlightIndex500 highlightShown: root.altTabPressed && index === priv.highlightIndex
476501
477 readonly property bool isDash: model.appId == "unity8-dash"502 readonly property bool isDash: model.application.appId == "unity8-dash"
478503
479 Binding {504 Component.onCompleted: {
480 target: appDelegate.application505 // NB: We're differentiating if this delegate was created in response to a new entry in the model
481 property: "exemptFromLifecycle"506 // or if the Repeater is just populating itself with delegates to match the model it received.
482 value: !model.isTouchApp || isExemptFromLifecycle(model.appId)507 if (!spreadRepeater.startingUp) {
483 }508 // a top level window is always the focused one when it first appears, unfocusing
484509 // any preexisting one
485 Binding {510 //
486 target: appDelegate.application511 // new items are appended and must be manually brought to front.
487 property: "requestedState"512 // that's how it *must* be in order to get the animation for new
488 value: (isDash && root.keepDashRunning)513 // surfaces working
489 || (!root.suspended && appDelegate.focus)514 claimFocus();
490 ? ApplicationInfoInterface.RequestedRunning515 }
491 : ApplicationInfoInterface.RequestedSuspended516 }
517
518 onFocusChanged: {
519 if (focus && !spreadRepeater.startingUp) {
520 priv.focusedAppDelegate = appDelegate;
521 // If we're orphan (!parent) it means this stage is no longer the current one
522 // and will be deleted shortly. So we should no longer have a say over the model
523 if (root.parent) {
524 topLevelSurfaceList.raiseId(model.id);
525 }
526 }
527 }
528 function claimFocus() {
529 if (spreadView.phase > 0) {
530 spreadView.snapTo(model.index);
531 } else {
532 appDelegate.focus = true;
533 }
534 }
535 Connections {
536 target: model.surface
537 onFocusRequested: claimFocus()
538 }
539 Connections {
540 target: model.application
541 onFocusRequested: {
542 if (!model.surface) {
543 // when an app has no surfaces, we assume there's only one entry representing it:
544 // this delegate.
545 claimFocus();
546 } else {
547 // if the application has surfaces, focus request should be at surface-level.
548 }
549 }
492 }550 }
493551
494 z: isDash && !spreadView.active ? -1 : behavioredIndex552 z: isDash && !spreadView.active ? -1 : behavioredIndex
@@ -509,7 +567,8 @@
509 return spreadView.width + spreadIndex * spreadView.tileDistance;567 return spreadView.width + spreadIndex * spreadView.tileDistance;
510 }568 }
511569
512 application: root.applicationManager.get(index)570 application: model.application
571 surface: model.surface
513 closeable: !isDash572 closeable: !isDash
514573
515 property real behavioredIndex: index574 property real behavioredIndex: index
@@ -593,11 +652,7 @@
593652
594 onClicked: {653 onClicked: {
595 if (root.altTabEnabled && spreadView.phase == 2) {654 if (root.altTabEnabled && spreadView.phase == 2) {
596 if (root.applicationManager.focusedApplicationId == root.applicationManager.get(index).appId) {655 spreadView.snapTo(index);
597 spreadView.snapTo(index);
598 } else {
599 root.applicationManager.requestFocusApplication(root.applicationManager.get(index).appId);
600 }
601 }656 }
602 }657 }
603658
@@ -611,7 +666,15 @@
611666
612 onClosed: {667 onClosed: {
613 spreadView.closingIndex = index;668 spreadView.closingIndex = index;
614 root.applicationManager.stopApplication(root.applicationManager.get(index).appId);669 if (appDelegate.surface) {
670 appDelegate.surface.close();
671 } else if (appDelegate.application) {
672 root.applicationManager.stopApplication(appDelegate.application.appId);
673 } else {
674 // should never happen
675 console.warn("Can't close topLevelSurfaceList entry as it has neither"
676 + " a surface nor an application");
677 }
615 }678 }
616679
617 Binding {680 Binding {
@@ -629,7 +692,7 @@
629692
630 StagedFullscreenPolicy {693 StagedFullscreenPolicy {
631 id: fullscreenPolicy694 id: fullscreenPolicy
632 application: appDelegate.application695 surface: model.surface
633 }696 }
634697
635 Connections {698 Connections {
@@ -637,7 +700,7 @@
637 onStageAboutToBeUnloaded: fullscreenPolicy.active = false700 onStageAboutToBeUnloaded: fullscreenPolicy.active = false
638 }701 }
639 }702 }
640 }703 } // Repeater {
641 }704 }
642 }705 }
643706
644707
=== added file 'qml/Stages/PromptSurfaceAnimations.qml'
--- qml/Stages/PromptSurfaceAnimations.qml 1970-01-01 00:00:00 +0000
+++ qml/Stages/PromptSurfaceAnimations.qml 2016-04-20 17:09:16 +0000
@@ -0,0 +1,81 @@
1/*
2 * Copyright 2016 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15*/
16
17import QtQuick 2.4
18import Ubuntu.Components 1.3
19
20StateGroup {
21 id: root
22 property var container
23 property var surfaceItem
24
25 states: [
26 State {
27 name: "blank"
28 when: !root.surfaceItem.surface
29 },
30 State {
31 name: "ready"
32 when: root.surfaceItem.surface && root.surfaceItem.live
33 },
34 State {
35 name: "zombie"
36 when: root.surfaceItem.surface && !root.surfaceItem.live
37 }
38 ]
39 transitions: [
40 Transition {
41 from: "*"; to: "zombie"
42 // Slide downwards until it's out of view, through the bottom of the window
43 SequentialAnimation {
44 // clip so we don't go out of parent's bounds during spread
45 PropertyAction { target: root.container.parent; property: "clip"; value: true }
46 UbuntuNumberAnimation { target: root.surfaceItem; property: "y"; to: root.container.height
47 duration: UbuntuAnimation.BriskDuration }
48 PropertyAction { target: root.surfaceItem; property: "visible"; value: false }
49 PropertyAction { target: container.parent; property: "clip"; value: false }
50 ScriptAction { script: {
51 // Unity.Application can't destroy a zombie MirSurface if it's still being
52 // referenced by a MirSurfaceItem.
53 root.surfaceItem.surface = null;
54 } }
55 }
56 },
57 Transition {
58 from: "*"; to: "ready"
59 // Slide upwards into view, from the bottom of the window
60 SequentialAnimation {
61 // clip so we don't go out of parent's bounds during spread
62 PropertyAction { target: root.container.parent; property: "clip"; value: true }
63 ScriptAction { script: {
64 root.surfaceItem.y = root.container.height;
65 root.surfaceItem.visible = true;
66 } }
67 UbuntuNumberAnimation {
68 target: root.surfaceItem; property: "y"; to: 0
69 duration: UbuntuAnimation.BriskDuration
70 }
71 PropertyAction { target: container.parent; property: "clip"; value: false }
72 }
73 },
74 Transition {
75 from: "*"; to: "blank"
76 ScriptAction { script: {
77 root.surfaceItem.visible = false;
78 } }
79 }
80 ]
81}
082
=== modified file 'qml/Stages/SpreadDelegate.qml'
--- qml/Stages/SpreadDelegate.qml 2016-04-20 17:09:15 +0000
+++ qml/Stages/SpreadDelegate.qml 2016-04-20 17:09:16 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright 2014-2015 Canonical Ltd.2 * Copyright 2014-2016 Canonical Ltd.
3 *3 *
4 * This program is free software; you can redistribute it and/or modify4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by5 * it under the terms of the GNU Lesser General Public License as published by
@@ -45,6 +45,7 @@
45 property alias swipeToCloseEnabled: dragArea.enabled45 property alias swipeToCloseEnabled: dragArea.enabled
46 property bool closeable46 property bool closeable
47 property alias application: appWindow.application47 property alias application: appWindow.application
48 property alias surface: appWindow.surface
48 property int shellOrientationAngle49 property int shellOrientationAngle
49 property int shellOrientation50 property int shellOrientation
50 property QtObject orientations51 property QtObject orientations
@@ -250,7 +251,7 @@
250 }251 }
251 PropertyChanges {252 PropertyChanges {
252 target: appWindow253 target: appWindow
253 surfaceOrientationAngle: orientationAngle254 surfaceOrientationAngle: appWindowWithShadow.orientationAngle
254 }255 }
255 },256 },
256 State {257 State {
@@ -292,7 +293,7 @@
292293
293 ApplicationWindow {294 ApplicationWindow {
294 id: appWindow295 id: appWindow
295 objectName: application ? "appWindow_" + application.appId : "appWindow_null"296 objectName: "appWindow"
296 focus: true297 focus: true
297 anchors {298 anchors {
298 fill: parent299 fill: parent
299300
=== modified file 'qml/Stages/StagedFullscreenPolicy.qml'
--- qml/Stages/StagedFullscreenPolicy.qml 2016-03-15 19:38:03 +0000
+++ qml/Stages/StagedFullscreenPolicy.qml 2016-04-20 17:09:16 +0000
@@ -27,31 +27,29 @@
27// Chrome not set and state change to fulscreen -> client window stays "fullscreen"27// Chrome not set and state change to fulscreen -> client window stays "fullscreen"
28QtObject {28QtObject {
29 property bool active: true29 property bool active: true
30 property QtObject application: null
3130
32 readonly property var lastSurface: application && application.session ?31 property var surface: null
33 application.session.lastSurface : null32 onSurfaceChanged: {
34 onLastSurfaceChanged: {33 if (!active || !surface) return;
35 if (!active || !lastSurface) return;34 if (surface.shellChrome === Mir.LowChrome) {
36 if (lastSurface.shellChrome === Mir.LowChrome) {35 surface.state = Mir.FullscreenState;
37 lastSurface.state = Mir.FullscreenState;
38 }36 }
39 }37 }
4038
41 property var _connections: Connections {39 property var _connections: Connections {
42 target: lastSurface40 target: surface
43 onShellChromeChanged: {41 onShellChromeChanged: {
44 if (!active || !lastSurface) return;42 if (!active || !surface) return;
45 if (lastSurface.shellChrome === Mir.LowChrome) {43 if (surface.shellChrome === Mir.LowChrome) {
46 lastSurface.state = Mir.FullscreenState;44 surface.state = Mir.FullscreenState;
47 } else {45 } else {
48 lastSurface.state = Mir.RestoredState;46 surface.state = Mir.RestoredState;
49 }47 }
50 }48 }
51 onStateChanged: {49 onStateChanged: {
52 if (!active) return;50 if (!active) return;
53 if (lastSurface.state === Mir.RestoredState && lastSurface.shellChrome === Mir.LowChrome) {51 if (surface.state === Mir.RestoredState && surface.shellChrome === Mir.LowChrome) {
54 lastSurface.state = Mir.FullscreenState;52 surface.state = Mir.FullscreenState;
55 }53 }
56 }54 }
57 }55 }
5856
=== renamed file 'qml/Stages/SessionContainer.qml' => 'qml/Stages/SurfaceContainer.qml'
--- qml/Stages/SessionContainer.qml 2016-02-03 13:46:18 +0000
+++ qml/Stages/SurfaceContainer.qml 2016-04-20 17:09:16 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright 2014-2015 Canonical Ltd.2 * Copyright 2014-2016 Canonical Ltd.
3 *3 *
4 * This program is free software; you can redistribute it and/or modify4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by5 * it under the terms of the GNU Lesser General Public License as published by
@@ -15,61 +15,124 @@
15*/15*/
1616
17import QtQuick 2.417import QtQuick 2.4
18import "Animations"18import Ubuntu.Components 1.3
19import Ubuntu.Gestures 0.1 // For TouchGate
20import Utils 0.1 // for InputWatcher
21import Unity.Application 0.1 // for MirSurfaceItem
1922
20FocusScope {23FocusScope {
21 id: root24 id: root
22 objectName: "sessionContainer"25 objectName: "surfaceContainer"
23 implicitWidth: _surfaceContainer.implicitWidth26
24 implicitHeight: _surfaceContainer.implicitHeight27 // Must be set from outside
25 property QtObject session28 property var surface: null
26 readonly property var childSessions: session ? session.childSessions : null29
27 readonly property alias surface: _surfaceContainer.surface30 // Might be changed from outside
28 property alias interactive: _surfaceContainer.interactive
29 property alias surfaceOrientationAngle: _surfaceContainer.surfaceOrientationAngle
30 property alias resizeSurface: _surfaceContainer.resizeSurface
31
32 property int requestedWidth: -131 property int requestedWidth: -1
33 property int requestedHeight: -132 property int requestedHeight: -1
3433 property bool interactive
35 readonly property alias surfaceContainer: _surfaceContainer34 property int surfaceOrientationAngle: 0
36 SurfaceContainer {35 property bool resizeSurface: true
37 id: _surfaceContainer36 property bool inPromptSession: false
38 requestedWidth: root.requestedWidth37
39 requestedHeight: root.requestedHeight38 onSurfaceChanged: {
40 surface: session ? session.lastSurface : null39 // Not a binding because animations might remove the surface from the surfaceItem
41 }40 // programatically (in order to signal that a zombie surface is free for deletion),
4241 // even though root.surface is still !null.
43 // SurfaceContainer size drives SessionContainer size42 surfaceItem.surface = surface;
44 Binding {43 }
45 target: root; property: "width"; value: _surfaceContainer.width44
45 InputWatcher {
46 target: surfaceItem
47 onTargetPressedChanged: {
48 if (targetPressed && root.interactive) {
49 root.focus = true;
50 root.forceActiveFocus();
51 }
52 }
53 }
54
55 MirSurfaceItem {
56 id: surfaceItem
57 objectName: "surfaceItem"
58
59 fillMode: MirSurfaceItem.PadOrCrop
60 consumesInput: true
61
62 surfaceWidth: {
63 if (root.resizeSurface) {
64 if (root.requestedWidth >= 0) {
65 return root.requestedWidth;
66 } else {
67 return width;
68 }
69 } else {
70 return -1;
71 }
72 }
73
74 surfaceHeight: {
75 if (root.resizeSurface) {
76 if (root.requestedHeight >= 0) {
77 return root.requestedHeight;
78 } else {
79 return height;
80 }
81 } else {
82 return -1;
83 }
84 }
85
86 enabled: root.interactive
87 antialiasing: !root.interactive
88 orientationAngle: root.surfaceOrientationAngle
89 }
90
91 TouchGate {
92 targetItem: surfaceItem
93 anchors.fill: root
94 enabled: surfaceItem.enabled
95 }
96
97 // MirSurface size drives SurfaceContainer size
98 Binding {
99 target: surfaceItem; property: "width"; value: root.surface ? root.surface.size.width : 0
100 when: root.requestedWidth >= 0 && root.surface
101 }
102 Binding {
103 target: surfaceItem; property: "height"; value: root.surface ? root.surface.size.height : 0
104 when: root.requestedHeight >= 0 && root.surface
105 }
106 Binding {
107 target: root; property: "width"; value: surfaceItem.width
46 when: root.requestedWidth >= 0108 when: root.requestedWidth >= 0
47 }109 }
48 Binding {110 Binding {
49 target: root; property: "height"; value: _surfaceContainer.height111 target: root; property: "height"; value: surfaceItem.height
50 when: root.requestedHeight >= 0112 when: root.requestedHeight >= 0
51 }113 }
52114
53 // SessionContainer size drives SurfaceContainer size115 // SurfaceContainer size drives MirSurface size
54 Binding {116 Binding {
55 target: _surfaceContainer; property: "width"; value: root.width117 target: surfaceItem; property: "width"; value: root.width
56 when: root.requestedWidth < 0118 when: root.requestedWidth < 0
57 }119 }
58 Binding {120 Binding {
59 target: _surfaceContainer; property: "height"; value: root.height121 target: surfaceItem; property: "height"; value: root.height
60 when: root.requestedHeight < 0122 when: root.requestedHeight < 0
61 }123 }
62124
63 Repeater {125 Repeater {
64 id: childSessionsRepeater126 id: childSurfacesRepeater
65 model: root.childSessions127 objectName: "childSurfacesRepeater"
128 model: root.surface ? root.surface.promptSurfaceList : null
66129
67 delegate: Loader {130 delegate: Loader {
68 objectName: "childDelegate" + index131 objectName: "childDelegate" + index
69 anchors.fill: surfaceContainer132 anchors.fill: root
70133
71 // Only way to do recursive qml items.134 // Only way to do recursive qml items.
72 source: Qt.resolvedUrl("SessionContainer.qml")135 source: Qt.resolvedUrl("SurfaceContainer.qml")
73136
74 z: index137 z: index
75138
@@ -81,12 +144,12 @@
81144
82 Binding {145 Binding {
83 target: item; when: item146 target: item; when: item
84 property: "interactive"; value: index == (childSessionsRepeater.count - 1) && root.interactive147 property: "interactive"; value: index == (childSurfacesRepeater.count - 1) && root.interactive
85 }148 }
86149
87 Binding {150 Binding {
88 target: item; when: item151 target: item; when: item
89 property: "session"; value: modelData152 property: "surface"; value: model.surface
90 }153 }
91154
92 Binding {155 Binding {
@@ -98,79 +161,47 @@
98 target: item; when: item161 target: item; when: item
99 property: "height"; value: root.height162 property: "height"; value: root.height
100 }163 }
101 }164
102 }165 Binding {
103166 target: item; when: item
104 states: [167 property: "inPromptSession"; value: true
105 State {168 }
106 name: "rootSession"169 }
107 when: root.session && !root.session.parentSession170 }
108 },171
109172 Loader {
110 State {173 id: animationsLoader
111 name: "childSession"174 objectName: "animationsLoader"
112 when: root.session && root.session.parentSession !== null && root.session.live175 active: root.surface
113 && !root.session.lastSurface176 source: {
114 },177 if (root.inPromptSession) {
115178 return "PromptSurfaceAnimations.qml";
116 State {179 } else {
117 name: "childSessionReady"180 // Let ApplicationWindow do the animations
118 when: root.session && root.session.parentSession !== null && root.session.live181 return "";
119 && root.session.lastSurface !== null182 }
120 },183 }
121184 Binding {
122 State {185 target: animationsLoader.item
123 name: "childSessionZombie"186 when: animationsLoader.item
124 when: root.session && root.session.parentSession !== null && !root.session.live187 property: "surfaceItem"
125 }188 value: surfaceItem
126 ]189 }
127190 Binding {
128 transitions: [191 target: animationsLoader.item
129 Transition {192 when: animationsLoader.item
130 to: "childSessionReady"193 property: "container"
131 ScriptAction { script: { if (!surfaceContainer.hadSurface) { animateIn(swipeFromBottom); } } }194 value: root
132 },195 }
133 Transition {
134 to: "childSessionZombie"
135 ScriptAction { script: { animateOut(); } }
136 }
137 ]
138
139 function animateIn(component) {
140 var animation = component.createObject(root, { "container": root, });
141 animation.start();
142
143 var tmp = d.animations;
144 tmp.push(animation);
145 d.animations = tmp;
146 }
147
148 function animateOut() {
149 if (d.animations.length > 0) {
150 var tmp = d.animations;
151 var popped = tmp.pop();
152 popped.completed.connect(function() { root.session.release(); } );
153 popped.end();
154 d.animations = tmp;
155 } else {
156 root.session.release();
157 }
158 }
159
160 Component {
161 id: swipeFromBottom
162 SwipeFromBottomAnimation {}
163 }196 }
164197
165 QtObject {198 QtObject {
166 id: d199 id: d
167 property var animations: []
168
169 property var focusedChild: {200 property var focusedChild: {
170 if (childSessionsRepeater.count == 0) {201 if (childSurfacesRepeater.count == 0) {
171 return _surfaceContainer;202 return surfaceItem;
172 } else {203 } else {
173 return childSessionsRepeater.itemAt(childSessionsRepeater.count - 1);204 return childSurfacesRepeater.itemAt(childSurfacesRepeater.count - 1);
174 }205 }
175 }206 }
176 onFocusedChildChanged: {207 onFocusedChildChanged: {
177208
=== removed file 'qml/Stages/SurfaceContainer.qml'
--- qml/Stages/SurfaceContainer.qml 2016-04-20 17:09:15 +0000
+++ qml/Stages/SurfaceContainer.qml 1970-01-01 00:00:00 +0000
@@ -1,154 +0,0 @@
1/*
2 * Copyright 2014-2015 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15*/
16
17import QtQuick 2.4
18import Ubuntu.Components 1.3
19import Ubuntu.Gestures 0.1 // For TouchGate
20import Utils 0.1 // for InputWatcher
21import Unity.Application 0.1 // for MirSurfaceItem
22
23FocusScope {
24 id: root
25 objectName: "surfaceContainer"
26
27 property var surface: null
28 property bool hadSurface: false
29 property bool interactive
30 property int surfaceOrientationAngle: 0
31 property string name: surface ? surface.name : ""
32 property bool resizeSurface: true
33
34 property int requestedWidth: -1
35 property int requestedHeight: -1
36
37 onSurfaceChanged: {
38 if (surface) {
39 surfaceItem.surface = surface;
40 root.hadSurface = false;
41 }
42 }
43
44 InputWatcher {
45 target: surfaceItem
46 onTargetPressedChanged: {
47 if (targetPressed && root.interactive) {
48 root.focus = true;
49 root.forceActiveFocus();
50 }
51 }
52 }
53
54 MirSurfaceItem {
55 id: surfaceItem
56 objectName: "surfaceItem"
57
58 fillMode: MirSurfaceItem.PadOrCrop
59 consumesInput: true
60
61 surfaceWidth: {
62 if (root.resizeSurface) {
63 if (root.requestedWidth >= 0) {
64 return root.requestedWidth;
65 } else {
66 return width;
67 }
68 } else {
69 return -1;
70 }
71 }
72
73 surfaceHeight: {
74 if (root.resizeSurface) {
75 if (root.requestedHeight >= 0) {
76 return root.requestedHeight;
77 } else {
78 return height;
79 }
80 } else {
81 return -1;
82 }
83 }
84
85 enabled: root.interactive
86 focus: true
87 antialiasing: !root.interactive
88 orientationAngle: root.surfaceOrientationAngle
89 }
90
91 // MirSurface size drives SurfaceContainer size
92 Binding {
93 target: surfaceItem; property: "width"; value: root.surface ? root.surface.size.width : 0
94 when: root.requestedWidth >= 0 && root.surface
95 }
96 Binding {
97 target: surfaceItem; property: "height"; value: root.surface ? root.surface.size.height : 0
98 when: root.requestedHeight >= 0 && root.surface
99 }
100 Binding {
101 target: root; property: "width"; value: surfaceItem.width
102 when: root.requestedWidth >= 0
103 }
104 Binding {
105 target: root; property: "height"; value: surfaceItem.height
106 when: root.requestedHeight >= 0
107 }
108
109 // SurfaceContainer size drives MirSurface size
110 Binding {
111 target: surfaceItem; property: "width"; value: root.width
112 when: root.requestedWidth < 0
113 }
114 Binding {
115 target: surfaceItem; property: "height"; value: root.height
116 when: root.requestedHeight < 0
117 }
118
119
120 TouchGate {
121 objectName: "touchGate-"+name
122 targetItem: surfaceItem
123 anchors.fill: root
124 enabled: surfaceItem.enabled
125 }
126
127 states: [
128 State {
129 name: "zombie"
130 when: surfaceItem.surface && !surfaceItem.live
131 }
132 ]
133 transitions: [
134 Transition {
135 from: ""; to: "zombie"
136 SequentialAnimation {
137 UbuntuNumberAnimation { target: surfaceItem; property: "opacity"; to: 0.0
138 duration: UbuntuAnimation.BriskDuration }
139 PropertyAction { target: surfaceItem; property: "visible"; value: false }
140 ScriptAction { script: {
141 surfaceItem.surface = null;
142 root.hadSurface = true;
143 } }
144 }
145 },
146 Transition {
147 from: "zombie"; to: ""
148 ScriptAction { script: {
149 surfaceItem.opacity = 1.0;
150 surfaceItem.visible = true;
151 } }
152 }
153 ]
154}
1550
=== modified file 'qml/Stages/TabletStage.qml'
--- qml/Stages/TabletStage.qml 2016-04-20 17:09:15 +0000
+++ qml/Stages/TabletStage.qml 2016-04-20 17:09:16 +0000
@@ -27,19 +27,24 @@
27 objectName: "stages"27 objectName: "stages"
28 anchors.fill: parent28 anchors.fill: parent
2929
30 // <tutorial-hacks> The Tutorial looks into our implementation details
30 property alias sideStageVisible: spreadView.sideStageVisible31 property alias sideStageVisible: spreadView.sideStageVisible
31 property alias sideStageWidth: spreadView.sideStageWidth32 property alias sideStageWidth: spreadView.sideStageWidth
33 // The stage the currently focused surface is in
34 property int stageFocusedSurface: priv.focusedAppDelegate ? priv.focusedAppDelegate.stage : ApplicationInfoInterface.MainStage
35 // </tutorial-hacks>
3236
33 // Functions to be called from outside37 // Functions to be called from outside
34 function updateFocusedAppOrientation() {38 function updateFocusedAppOrientation() {
35 var mainStageAppIndex = priv.indexOf(priv.mainStageAppId);39 var mainStageIndex = root.topLevelSurfaceList.indexForId(priv.mainStageItemId);
36 if (mainStageAppIndex >= 0 && mainStageAppIndex < spreadRepeater.count) {40
37 spreadRepeater.itemAt(mainStageAppIndex).matchShellOrientation();41 if (priv.mainStageItemId && mainStageIndex >= 0 && mainStageIndex < spreadRepeater.count) {
42 spreadRepeater.itemAt(mainStageIndex).matchShellOrientation();
38 }43 }
3944
40 for (var i = 0; i < spreadRepeater.count; ++i) {45 for (var i = 0; i < spreadRepeater.count; ++i) {
4146
42 if (i === mainStageAppIndex) {47 if (i === mainStageIndex) {
43 continue;48 continue;
44 }49 }
4550
@@ -60,16 +65,14 @@
60 }65 }
61 }66 }
62 function updateFocusedAppOrientationAnimated() {67 function updateFocusedAppOrientationAnimated() {
63 var mainStageAppIndex = priv.indexOf(priv.mainStageAppId);68 var mainStageIndex = root.topLevelSurfaceList.indexForId(priv.mainStageItemId);
64 if (mainStageAppIndex >= 0 && mainStageAppIndex < spreadRepeater.count) {69 if (priv.mainStageItemId && mainStageIndex >= 0 && mainStageIndex < spreadRepeater.count) {
65 spreadRepeater.itemAt(mainStageAppIndex).animateToShellOrientation();70 spreadRepeater.itemAt(mainStageIndex).animateToShellOrientation();
66 }71 }
6772
68 if (priv.sideStageAppId) {73 var sideStageIndex = root.topLevelSurfaceList.indexForId(priv.sideStageItemId);
69 var sideStageAppIndex = priv.indexOf(priv.sideStageAppId);74 if (sideStageIndex >= 0 && sideStageIndex < spreadRepeater.count) {
70 if (sideStageAppIndex >= 0 && sideStageAppIndex < spreadRepeater.count) {75 spreadRepeater.itemAt(sideStageIndex).matchShellOrientation();
71 spreadRepeater.itemAt(sideStageAppIndex).matchShellOrientation();
72 }
73 }76 }
74 }77 }
7578
@@ -81,11 +84,20 @@
8184
82 orientationChangesEnabled: priv.mainAppOrientationChangesEnabled85 orientationChangesEnabled: priv.mainAppOrientationChangesEnabled
8386
87 mainApp: {
88 if (priv.mainStageItemId > 0) {
89 var index = root.topLevelSurfaceList.indexForId(priv.mainStageItemId);
90 return root.topLevelSurfaceList.applicationAt(index);
91 } else {
92 return null;
93 }
94 }
95
84 supportedOrientations: {96 supportedOrientations: {
85 if (mainApp) {97 if (mainApp) {
86 var orientations = mainApp.supportedOrientations;98 var orientations = mainApp.supportedOrientations;
87 orientations |= Qt.LandscapeOrientation | Qt.InvertedLandscapeOrientation;99 orientations |= Qt.LandscapeOrientation | Qt.InvertedLandscapeOrientation;
88 if (priv.sideStageAppId && !spreadView.surfaceDragging) {100 if (priv.sideStageItemId && !spreadView.surfaceDragging) {
89 // If we have a sidestage app, support Portrait orientation101 // If we have a sidestage app, support Portrait orientation
90 // so that it will switch the sidestage app to mainstage on rotate102 // so that it will switch the sidestage app to mainstage on rotate
91 orientations |= Qt.PortraitOrientation|Qt.InvertedPortraitOrientation;103 orientations |= Qt.PortraitOrientation|Qt.InvertedPortraitOrientation;
@@ -116,7 +128,7 @@
116 if (inverseProgress == 0 && priv.oldInverseProgress > 0) {128 if (inverseProgress == 0 && priv.oldInverseProgress > 0) {
117 // left edge drag released. Minimum distance is given by design.129 // left edge drag released. Minimum distance is given by design.
118 if (priv.oldInverseProgress > units.gu(22)) {130 if (priv.oldInverseProgress > units.gu(22)) {
119 ApplicationManager.requestFocusApplication("unity8-dash");131 root.applicationManager.requestFocusApplication("unity8-dash");
120 }132 }
121 }133 }
122 priv.oldInverseProgress = inverseProgress;134 priv.oldInverseProgress = inverseProgress;
@@ -154,17 +166,51 @@
154 }166 }
155 }167 }
156168
169 Connections {
170 target: root.topLevelSurfaceList
171 onListChanged: priv.updateMainAndSideStageIndexes()
172 }
173
157 QtObject {174 QtObject {
158 id: priv175 id: priv
159 objectName: "stagesPriv"176 objectName: "stagesPriv"
160177
161 property string focusedAppId: ApplicationManager.focusedApplicationId178 function updateMainAndSideStageIndexes() {
162 readonly property var focusedAppDelegate: {179 var choseMainStage = false;
163 var index = indexOf(focusedAppId);180 var choseSideStage = false;
164 return index >= 0 && index < spreadRepeater.count ? spreadRepeater.itemAt(index) : null181
182 if (!root.topLevelSurfaceList)
183 return;
184
185 for (var i = 0; i < spreadRepeater.count && (!choseMainStage || !choseSideStage); ++i) {
186 var spreadDelegate = spreadRepeater.itemAt(i);
187 if (sideStage.shown && spreadDelegate.stage == ApplicationInfoInterface.SideStage
188 && !choseSideStage) {
189 priv.sideStageDelegate = spreadDelegate
190 priv.sideStageItemId = root.topLevelSurfaceList.idAt(i);
191 priv.sideStageAppId = root.topLevelSurfaceList.applicationAt(i).appId;
192 choseSideStage = true;
193 } else if (!choseMainStage && spreadDelegate.stage == ApplicationInfoInterface.MainStage) {
194 priv.mainStageDelegate = spreadDelegate;
195 priv.mainStageItemId = root.topLevelSurfaceList.idAt(i);
196 priv.mainStageAppId = root.topLevelSurfaceList.applicationAt(i).appId;
197 choseMainStage = true;
198 }
199 }
200 if (!choseMainStage) {
201 priv.mainStageDelegate = null;
202 priv.mainStageItemId = 0;
203 priv.mainStageAppId = "";
204 }
205 if (!choseSideStage) {
206 priv.sideStageDelegate = null;
207 priv.sideStageItemId = 0;
208 priv.sideStageAppId = "";
209 }
165 }210 }
166211
167 property string oldFocusedAppId: ""212 property var focusedAppDelegate: null
213
168 property bool mainAppOrientationChangesEnabled: false214 property bool mainAppOrientationChangesEnabled: false
169215
170 property real landscapeHeight: root.orientations.native_ == Qt.LandscapeOrientation ?216 property real landscapeHeight: root.orientations.native_ == Qt.LandscapeOrientation ?
@@ -173,36 +219,21 @@
173 property bool shellIsLandscape: root.shellOrientation === Qt.LandscapeOrientation219 property bool shellIsLandscape: root.shellOrientation === Qt.LandscapeOrientation
174 || root.shellOrientation === Qt.InvertedLandscapeOrientation220 || root.shellOrientation === Qt.InvertedLandscapeOrientation
175221
176 property string mainStageAppId222 property var mainStageDelegate: null
177 property string sideStageAppId223 property var sideStageDelegate: null
178224
179 // For convenience, keep properties of the first two apps in the model225 property int mainStageItemId: 0
180 property string appId0226 property int sideStageItemId: 0
181 property string appId1227
228 property string mainStageAppId: ""
229 property string sideStageAppId: ""
182230
183 property int oldInverseProgress: 0231 property int oldInverseProgress: 0
184232
185 property int highlightIndex: 0233 property int highlightIndex: 0
186234
187 onFocusedAppIdChanged: updateStageApps()
188
189 onFocusedAppDelegateChanged: {
190 if (focusedAppDelegate) {
191 focusedAppDelegate.focus = true;
192 }
193 }
194
195 property bool focusedAppDelegateIsDislocated: focusedAppDelegate &&235 property bool focusedAppDelegateIsDislocated: focusedAppDelegate &&
196 (focusedAppDelegate.dragOffset !== 0 || focusedAppDelegate.xTranslateAnimating)236 (focusedAppDelegate.dragOffset !== 0 || focusedAppDelegate.xTranslateAnimating)
197 function indexOf(appId) {
198 for (var i = 0; i < ApplicationManager.count; i++) {
199 if (ApplicationManager.get(i).appId == appId) {
200 return i;
201 }
202 }
203 return -1;
204 }
205
206 function evaluateOneWayFlick(gesturePoints) {237 function evaluateOneWayFlick(gesturePoints) {
207 // Need to have at least 3 points to recognize it as a flick238 // Need to have at least 3 points to recognize it as a flick
208 if (gesturePoints.length < 3) {239 if (gesturePoints.length < 3) {
@@ -231,88 +262,45 @@
231 spreadView.contentX = highlightIndex * spreadView.contentWidth / (spreadRepeater.count + 2)262 spreadView.contentX = highlightIndex * spreadView.contentWidth / (spreadRepeater.count + 2)
232 }263 }
233264
234 function getTopApp(stage) {
235 for (var i = 0; i < ApplicationManager.count; i++) {
236 var app = ApplicationManager.get(i)
237 if (app.stage === stage) {
238 return app;
239 }
240 }
241 return null;
242 }
243
244 function setAppStage(appId, stage, save) {
245 var app = ApplicationManager.findApplication(appId);
246 if (app) {
247 app.stage = stage;
248 if (save) {
249 WindowStateStorage.saveStage(appId, stage);
250 }
251 }
252 }
253
254 function updateStageApps() {
255 var app = priv.getTopApp(ApplicationInfoInterface.MainStage);
256 priv.mainStageAppId = app ? app.appId : ""
257 root.mainApp = app;
258
259 if (sideStage.shown) {
260 app = priv.getTopApp(ApplicationInfoInterface.SideStage);
261 priv.sideStageAppId = app ? app.appId : ""
262 } else {
263 priv.sideStageAppId = "";
264 }
265
266 appId0 = ApplicationManager.count >= 1 ? ApplicationManager.get(0).appId : "";
267 appId1 = ApplicationManager.count > 1 ? ApplicationManager.get(1).appId : "";
268 }
269
270 readonly property bool sideStageEnabled: root.shellOrientation == Qt.LandscapeOrientation ||265 readonly property bool sideStageEnabled: root.shellOrientation == Qt.LandscapeOrientation ||
271 root.shellOrientation == Qt.InvertedLandscapeOrientation266 root.shellOrientation == Qt.InvertedLandscapeOrientation
272 Component.onCompleted: updateStageApps();267 }
273 }268
274269 Instantiator {
275 Connections {270 model: root.applicationManager
276 target: ApplicationManager271 delegate: QtObject {
277 onFocusRequested: {272 property var stateBinding: Binding {
278 if (spreadView.interactive) {273 readonly property bool isDash: model.application ? model.application.appId == "unity8-dash" : false
279 spreadView.snapTo(priv.indexOf(appId));274 target: model.application
280 } else {275 property: "requestedState"
281 ApplicationManager.focusApplication(appId);276
282 }277 // NB: the first application clause is just to ensure we never get warnings for trying to access
283 }278 // members of a null variable.
284279 value: model.application &&
285 onApplicationAdded: {280 (
286 if (spreadView.phase == 2) {281 (isDash && root.keepDashRunning)
287 spreadView.snapTo(ApplicationManager.count - 1);282 || (!root.suspended && (model.application.appId === priv.mainStageAppId
288 } else {283 || model.application.appId === priv.sideStageAppId))
289 spreadView.phase = 0;284 )
290 spreadView.contentX = -spreadView.shift;285 ? ApplicationInfoInterface.RequestedRunning
291 ApplicationManager.focusApplication(appId);286 : ApplicationInfoInterface.RequestedSuspended
292 }287 }
293 }288
294289 property var lifecycleBinding: Binding {
295 onApplicationRemoved: {290 target: model.application
296 if (priv.mainStageAppId == appId) {291 property: "exemptFromLifecycle"
297 ApplicationManager.focusApplication("unity8-dash")292 value: model.application
298 }293 ? (!model.application.isTouchApp || isExemptFromLifecycle(model.application.appId))
299 if (priv.sideStageAppId == appId) {294 : false
300 var app = priv.getTopApp(ApplicationInfoInterface.SideStage);295 }
301 priv.sideStageAppId = app === null ? "" : app.appId;296 }
302 }297 }
303298
304 if (ApplicationManager.count == 0) {299 Binding {
305 spreadView.phase = 0;300 target: MirFocusController
306 spreadView.contentX = -spreadView.shift;301 property: "focusedSurface"
307 } else if (spreadView.closingIndex == -1) {302 value: priv.focusedAppDelegate ? priv.focusedAppDelegate.surface : null
308 // Unless we're closing the app ourselves in the spread,303 when: root.parent && !spreadRepeater.startingUp
309 // lets make sure the spread doesn't mess up by the changing app list.
310 spreadView.phase = 0;
311 spreadView.contentX = -spreadView.shift;
312
313 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
314 }
315 }
316 }304 }
317305
318 Flickable {306 Flickable {
@@ -363,16 +351,45 @@
363 property int selectedIndex: -1351 property int selectedIndex: -1
364 property int draggedDelegateCount: 0352 property int draggedDelegateCount: 0
365 property int closingIndex: -1353 property int closingIndex: -1
366 property var selectedApplication: selectedIndex !== -1 ? ApplicationManager.get(selectedIndex) : null354 property var selectedDelegate: selectedIndex !== -1 ? spreadRepeater.itemAt(selectedIndex) : null
367355
368 // FIXME: Workaround Flickable's not keepping its contentX still when resized356 // <FIXME-contentX> Workaround Flickable's behavior of bringing contentX back between valid boundaries
369 onContentXChanged: { forceItToRemainStillIfBeingResized(); }357 // when resized. The proper way to fix this is refactoring PhoneStage so that it doesn't
358 // rely on having Flickable.contentX keeping an out-of-bounds value when it's set programatically
359 // (as opposed to having contentX reaching an out-of-bounds value through dragging, which will trigger
360 // the Flickable.boundsBehavior upon release).
361 onContentXChanged: {
362 if (!undoContentXReset()) {
363 forceItToRemainStillIfBeingResized();
364 }
365 }
370 onShiftChanged: { forceItToRemainStillIfBeingResized(); }366 onShiftChanged: { forceItToRemainStillIfBeingResized(); }
371 function forceItToRemainStillIfBeingResized() {367 function forceItToRemainStillIfBeingResized() {
372 if (root.beingResized && contentX != -shift) {368 if (root.beingResized && contentX != -spreadView.shift) {
369 contentX = -spreadView.shift;
370 }
371 }
372 function undoContentXReset() {
373 if (contentWidth <= 0) {
374 contentWidthOnLastContentXChange = contentWidth;
375 lastContentX = contentX;
376 return false;
377 }
378
379 if (contentWidth != contentWidthOnLastContentXChange
380 && lastContentX == -shift && contentX == 0) {
381 // Flickable is resetting contentX because contentWidth has changed. Undo it.
373 contentX = -shift;382 contentX = -shift;
383 return true;
374 }384 }
385
386 contentWidthOnLastContentXChange = contentWidth;
387 lastContentX = contentX;
388 return false;
375 }389 }
390 property real contentWidthOnLastContentXChange: -1
391 property real lastContentX: 0
392 // </FIXME-contentX>
376393
377 property bool animateX: true394 property bool animateX: true
378 property bool beingResized: root.beingResized395 property bool beingResized: root.beingResized
@@ -385,31 +402,33 @@
385 }402 }
386 }403 }
387404
388 property real sideStageDragProgress: sideStage.progress
389 property bool sideStageVisible: priv.sideStageAppId
390 property real sideStageWidth: units.gu(40)405 property real sideStageWidth: units.gu(40)
391406
392 property bool surfaceDragging: triGestureArea.recognisedDrag407 property bool surfaceDragging: triGestureArea.recognisedDrag
393408
394 // In case the ApplicationManager already holds an app when starting up we're missing animations409 readonly property bool sideStageVisible: priv.sideStageItemId != 0
410
411 // In case applicationManager already holds an app when starting up we're missing animations
395 // Make sure we end up in the same state412 // Make sure we end up in the same state
396 Component.onCompleted: {413 Component.onCompleted: {
397 spreadView.contentX = -spreadView.shift414 spreadView.contentX = -spreadView.shift
398 }415 }
399416
400 property int nextInStack: {417 property int nextInStack: {
418 var mainStageIndex = priv.mainStageDelegate ? priv.mainStageDelegate.index : -1;
419 var sideStageIndex = priv.sideStageDelegate ? priv.sideStageDelegate.index : -1;
401 switch (state) {420 switch (state) {
402 case "main":421 case "main":
403 if (ApplicationManager.count > 1) {422 if (root.topLevelSurfaceList.count > 1) {
404 return 1;423 return 1;
405 }424 }
406 return -1;425 return -1;
407 case "mainAndOverlay":426 case "mainAndOverlay":
408 if (ApplicationManager.count <= 2) {427 if (root.topLevelSurfaceList.count <= 2) {
409 return -1;428 return -1;
410 }429 }
411 if (priv.appId0 == priv.mainStageAppId || priv.appId0 == priv.sideStageAppId) {430 if (mainStageIndex == 0 || sideStageIndex == 0) {
412 if (priv.appId1 == priv.mainStageAppId || priv.appId1 == priv.sideStageAppId) {431 if (mainStageIndex == 1 || sideStageIndex == 1) {
413 return 2;432 return 2;
414 }433 }
415 return 1;434 return 1;
@@ -420,7 +439,7 @@
420 }439 }
421 return -1;440 return -1;
422 }441 }
423 property int nextZInStack: indexToZIndex(nextInStack)442 property int nextZInStack
424443
425 states: [444 states: [
426 State {445 State {
@@ -440,13 +459,13 @@
440 }459 }
441 ]460 ]
442 state: {461 state: {
443 if ((priv.mainStageAppId && !priv.sideStageAppId) || !priv.sideStageEnabled) {462 if ((priv.mainStageItemId && !priv.sideStageItemId) || !priv.sideStageEnabled) {
444 return "main";463 return "main";
445 }464 }
446 if (!priv.mainStageAppId && priv.sideStageAppId) {465 if (!priv.mainStageItemId && priv.sideStageItemId) {
447 return "overlay";466 return "overlay";
448 }467 }
449 if (priv.mainStageAppId && priv.sideStageAppId) {468 if (priv.mainStageItemId && priv.sideStageItemId) {
450 return "mainAndOverlay";469 return "mainAndOverlay";
451 }470 }
452 return "empty";471 return "empty";
@@ -511,38 +530,41 @@
511 // only shuffle when we've got a main and overlay530 // only shuffle when we've got a main and overlay
512 if (state !== "mainAndOverlay") return index;531 if (state !== "mainAndOverlay") return index;
513532
514 var app = ApplicationManager.get(index);533 var app = root.topLevelSurfaceList.applicationAt(index);
515 if (!app) {534 if (!app) {
516 return index;535 return index;
517 }536 }
537 var stage = spreadRepeater.itemAt(index) ? spreadRepeater.itemAt(index).stage : app.stage;
518538
519 // don't shuffle indexes greater than "actives or next"539 // don't shuffle indexes greater than "actives or next"
520 if (index > 2) return index;540 if (index > 2) return index;
521541
522 if (app.appId === priv.mainStageAppId) {542 var mainStageIndex = root.topLevelSurfaceList.indexForId(priv.mainStageItemId);
543
544 if (index == mainStageIndex) {
523 // Active main stage always at 0545 // Active main stage always at 0
524 return 0;546 return 0;
525 }547 }
526548
527 if (spreadView.nextInStack > 0) {549 if (spreadView.nextInStack > 0) {
528 var nextAppInStack = ApplicationManager.get(spreadView.nextInStack);550 var stageOfNextInStack = spreadRepeater.itemAt(spreadView.nextInStack).stage;
529551
530 if (index === spreadView.nextInStack) {552 if (index === spreadView.nextInStack) {
531 // this is the next app in stack.553 // this is the next app in stack.
532554
533 if (app.stage === ApplicationInfoInterface.SideStage) {555 if (stage === ApplicationInfoInterface.SideStage) {
534 // if the next app in stack is a sidestage app, it must order on top of other side stage app556 // if the next app in stack is a sidestage app, it must order on top of other side stage app
535 return Math.min(2, ApplicationManager.count-1);557 return Math.min(2, root.topLevelSurfaceList.count-1);
536 }558 }
537 return 1;559 return 1;
538 }560 }
539 if (nextAppInStack.stage === ApplicationInfoInterface.SideStage) {561 if (stageOfNextInStack === ApplicationInfoInterface.SideStage) {
540 // if the next app in stack is a sidestage app, it must order on top of other side stage app562 // if the next app in stack is a sidestage app, it must order on top of other side stage app
541 return 1;563 return 1;
542 }564 }
543 return Math.min(2, ApplicationManager.count-1);565 return Math.min(2, root.topLevelSurfaceList.count-1);
544 }566 }
545 return Math.min(index+1, ApplicationManager.count-1);567 return Math.min(index+1, root.topLevelSurfaceList.count-1);
546 }568 }
547569
548 SequentialAnimation {570 SequentialAnimation {
@@ -560,12 +582,13 @@
560 script: {582 script: {
561 if (spreadView.selectedIndex >= 0) {583 if (spreadView.selectedIndex >= 0) {
562 var newIndex = spreadView.selectedIndex;584 var newIndex = spreadView.selectedIndex;
563 var application = ApplicationManager.get(newIndex);585 var application = root.topLevelSurfaceList.applicationAt(newIndex);
564 if (application.stage === ApplicationInfoInterface.SideStage) {586 var spreadDelegate = spreadRepeater.itemAt(newIndex);
587 if (spreadDelegate.stage === ApplicationInfoInterface.SideStage) {
565 sideStage.showNow();588 sideStage.showNow();
566 }589 }
567 spreadView.selectedIndex = -1;590 spreadView.selectedIndex = -1;
568 ApplicationManager.focusApplication(application.appId);591 spreadDelegate.focus = true;
569 spreadView.phase = 0;592 spreadView.phase = 0;
570 spreadView.contentX = -spreadView.shift;593 spreadView.contentX = -spreadView.shift;
571 }594 }
@@ -581,7 +604,7 @@
581 MouseArea {604 MouseArea {
582 id: spreadRow605 id: spreadRow
583 x: spreadView.contentX606 x: spreadView.contentX
584 width: spreadView.width + Math.max(spreadView.width, ApplicationManager.count * spreadView.tileDistance)607 width: spreadView.width + Math.max(spreadView.width, root.topLevelSurfaceList.count * spreadView.tileDistance)
585 height: root.height608 height: root.height
586609
587 onClicked: {610 onClicked: {
@@ -599,8 +622,8 @@
599 enabled: priv.sideStageEnabled622 enabled: priv.sideStageEnabled
600623
601 onDropped: {624 onDropped: {
602 priv.setAppStage(drag.source.appId, ApplicationInfoInterface.MainStage, true);625 drop.source.spreadDelegate.stage = ApplicationInfoInterface.MainStage;
603 ApplicationManager.focusApplication(drag.source.appId);626 drop.source.spreadDelegate.focus = true;
604 }627 }
605 keys: "SideStage"628 keys: "SideStage"
606 }629 }
@@ -611,12 +634,12 @@
611 height: priv.landscapeHeight634 height: priv.landscapeHeight
612 x: spreadView.width - width635 x: spreadView.width - width
613 z: {636 z: {
614 if (!priv.mainStageAppId) return 0;637 if (!priv.mainStageItemId) return 0;
615638
616 if (priv.sideStageAppId && spreadView.nextInStack > 0) {639 if (priv.sideStageItemId && spreadView.nextInStack > 0) {
617 var nextAppInStack = ApplicationManager.get(spreadView.nextInStack);640 var nextDelegateInStack = spreadRepeater.itemAt(spreadView.nextInStack);
618641
619 if (nextAppInStack.stage === ApplicationInfoInterface.MainStage) {642 if (nextDelegateInStack.stage === ApplicationInfoInterface.MainStage) {
620 // if the next app in stack is a main stage app, put the sidestage on top of it.643 // if the next app in stack is a main stage app, put the sidestage on top of it.
621 return 2;644 return 2;
622 }645 }
@@ -631,12 +654,11 @@
631 Behavior on opacity { UbuntuNumberAnimation {} }654 Behavior on opacity { UbuntuNumberAnimation {} }
632655
633 onShownChanged: {656 onShownChanged: {
634 if (!shown && ApplicationManager.focusedApplicationId == priv.sideStageAppId) {657 if (!shown && priv.sideStageDelegate && priv.focusedAppDelegate === priv.sideStageDelegate
635 ApplicationManager.requestFocusApplication(priv.mainStageAppId);658 && priv.mainStageDelegate) {
636 }659 priv.mainStageDelegate.focus = true;
637 priv.updateStageApps();660 } else if (shown && priv.sideStageDelegate) {
638 if (shown && priv.sideStageAppId) {661 priv.sideStageDelegate.focus = true;
639 ApplicationManager.requestFocusApplication(priv.sideStageAppId);
640 }662 }
641 }663 }
642664
@@ -655,8 +677,8 @@
655 }677 }
656 onDropped: {678 onDropped: {
657 if (drop.keys == "MainStage") {679 if (drop.keys == "MainStage") {
658 priv.setAppStage(drop.source.appId, ApplicationInfoInterface.SideStage, true);680 drop.source.spreadDelegate.stage = ApplicationInfoInterface.SideStage;
659 ApplicationManager.requestFocusApplication(drop.source.appId);681 drop.source.spreadDelegate.focus = true;
660 }682 }
661 }683 }
662 drag {684 drag {
@@ -669,41 +691,102 @@
669 }691 }
670 }692 }
671693
672 Repeater {694 TopLevelSurfaceRepeater {
673 id: spreadRepeater695 id: spreadRepeater
674 objectName: "spreadRepeater"696 objectName: "spreadRepeater"
675 model: ApplicationManager697 model: root.topLevelSurfaceList
698
699 onItemAdded: {
700 priv.updateMainAndSideStageIndexes();
701 if (spreadView.phase == 2) {
702 spreadView.snapTo(index);
703 }
704 }
705
706 onItemRemoved: {
707 priv.updateMainAndSideStageIndexes();
708 // Unless we're closing the app ourselves in the spread,
709 // lets make sure the spread doesn't mess up by the changing app list.
710 if (spreadView.closingIndex == -1) {
711 spreadView.phase = 0;
712 spreadView.contentX = -spreadView.shift;
713 focusTopMostApp();
714 }
715 }
716 function focusTopMostApp() {
717 if (spreadRepeater.count > 0) {
718 var topmostDelegate = spreadRepeater.itemAt(0);
719 topmostDelegate.focus = true;
720 }
721 }
676722
677 delegate: TransformedTabletSpreadDelegate {723 delegate: TransformedTabletSpreadDelegate {
678 id: spreadTile724 id: spreadTile
679 objectName: model.appId ? "tabletSpreadDelegate_" + model.appId725 objectName: "spreadDelegate_" + model.id
680 : "tabletSpreadDelegate_null";726
727 readonly property int index: model.index
681 width: spreadView.width728 width: spreadView.width
682 height: spreadView.height729 height: spreadView.height
683 active: appId == priv.mainStageAppId || appId == priv.sideStageAppId730 active: model.id == priv.mainStageItemId || model.id == priv.sideStageItemId
684 zIndex: selected && stage == ApplicationInfoInterface.MainStage ? 0 : spreadView.indexToZIndex(index)731 zIndex: selected && stage == ApplicationInfoInterface.MainStage ? 0 : spreadView.indexToZIndex(index)
732 onZIndexChanged: {
733 if (spreadView.nextInStack == model.index) {
734 spreadView.nextZInStack = zIndex;
735 }
736 }
685 selected: spreadView.selectedIndex == index737 selected: spreadView.selectedIndex == index
686 otherSelected: spreadView.selectedIndex >= 0 && !selected738 otherSelected: spreadView.selectedIndex >= 0 && !selected
687 isInSideStage: priv.sideStageAppId === appId739 isInSideStage: priv.sideStageItemId == model.id
688 interactive: !spreadView.interactive && spreadView.phase === 0 && root.interactive740 interactive: !spreadView.interactive && spreadView.phase === 0 && root.interactive
689 swipeToCloseEnabled: spreadView.interactive && !snapAnimation.running741 swipeToCloseEnabled: spreadView.interactive && !snapAnimation.running
690 maximizedAppTopMargin: root.maximizedAppTopMargin742 maximizedAppTopMargin: root.maximizedAppTopMargin
691 dragOffset: !isDash && appId == priv.mainStageAppId && root.inverseProgress > 0 && spreadView.phase === 0 ? root.inverseProgress : 0743 dragOffset: !isDash && model.id == priv.mainStageItemId && root.inverseProgress > 0
692 application: ApplicationManager.get(index)744 && spreadView.phase === 0 ? root.inverseProgress : 0
745 application: model.application
746 surface: model.surface
693 closeable: !isDash747 closeable: !isDash
694 highlightShown: root.altTabPressed && priv.highlightIndex == zIndex748 highlightShown: root.altTabPressed && priv.highlightIndex == zIndex
695749
696 readonly property bool wantsMainStage: model.stage == ApplicationInfoInterface.MainStage750 readonly property bool wantsMainStage: stage == ApplicationInfoInterface.MainStage
697751
698 readonly property string appId: model.appId752 readonly property bool isDash: model.application.appId == "unity8-dash"
699 readonly property bool isDash: model.appId == "unity8-dash"753
700754 onFocusChanged: {
701 stage: model.stage755 if (focus && !spreadRepeater.startingUp) {
756 priv.focusedAppDelegate = spreadTile;
757 root.topLevelSurfaceList.raiseId(model.id);
758 }
759 if (focus && priv.sideStageEnabled && stage === ApplicationInfoInterface.SideStage) {
760 sideStage.show();
761 }
762 }
763 Connections {
764 target: model.surface
765 onFocusRequested: spreadTile.focus = true;
766 }
767 Connections {
768 target: model.application
769 onFocusRequested: {
770 if (!model.surface) {
771 // when an app has no surfaces, we assume there's only one entry representing it:
772 // this delegate.
773 spreadTile.focus = true;
774 } else {
775 // if the application has surfaces, focus request should be at surface-level.
776 }
777 }
778 }
779
702 fullscreen: {780 fullscreen: {
703 if (mainApp && stage === ApplicationInfoInterface.SideStage) {781 if (priv.mainStageDelegate && stage === ApplicationInfoInterface.SideStage) {
704 return mainApp.fullscreen;782 return priv.mainStageDelegate.fullscreen;
783 } else if (surface) {
784 return surface.state === Mir.FullscreenState;
785 } else if (application) {
786 return application.fullscreen;
787 } else {
788 return false;
705 }789 }
706 return application ? application.fullscreen : false;
707 }790 }
708791
709 supportedOrientations: {792 supportedOrientations: {
@@ -724,23 +807,6 @@
724 }807 }
725 }808 }
726809
727
728 Binding {
729 target: spreadTile.application
730 property: "exemptFromLifecycle"
731 value: !model.isTouchApp || isExemptFromLifecycle(model.appId)
732 }
733
734 Binding {
735 target: spreadTile.application
736 property: "requestedState"
737 value: (isDash && root.keepDashRunning)
738 || (!root.suspended && (model.appId == priv.mainStageAppId
739 || model.appId == priv.sideStageAppId))
740 ? ApplicationInfoInterface.RequestedRunning
741 : ApplicationInfoInterface.RequestedSuspended
742 }
743
744 // FIXME: A regular binding doesn't update any more after closing an app.810 // FIXME: A regular binding doesn't update any more after closing an app.
745 // Using a Binding for now.811 // Using a Binding for now.
746 Binding {812 Binding {
@@ -760,34 +826,33 @@
760 onSideStageEnabledChanged: refreshStage()826 onSideStageEnabledChanged: refreshStage()
761 }827 }
762828
829 property bool _constructing: true;
830 onStageChanged: {
831 if (!_constructing) {
832 priv.updateMainAndSideStageIndexes();
833 }
834 }
835
763 Component.onCompleted: {836 Component.onCompleted: {
764 refreshStage()837 // a top level window is always the focused one when it first appears, unfocusing
765 stageChanged.connect(priv.updateStageApps);838 // any preexisting one
839 focus = true;
840 refreshStage();
841 _constructing = false;
842 }
843 Component.onDestruction: {
844 WindowStateStorage.saveStage(model.application.appId, stage);
766 }845 }
767846
768 function refreshStage() {847 function refreshStage() {
769 var stage = ApplicationInfoInterface.MainStage;848 var newStage = ApplicationInfoInterface.MainStage;
770 if (priv.sideStageEnabled) {849 if (priv.sideStageEnabled) {
771 if (application && application.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) {850 if (application && application.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) {
772 stage = WindowStateStorage.getStage(appId);851 newStage = WindowStateStorage.getStage(application.appId);
773 }852 }
774 }853 }
775854
776 if (model.stage !== stage) {855 stage = newStage;
777 priv.setAppStage(appId, stage, false);
778 }
779 }
780
781 // This is required because none of the bindings are triggered in some cases:
782 // When an app is closed, it might happen that ApplicationManager.get(nextInStack)
783 // returns a different app even though the nextInStackIndex and all the related
784 // bindings (index, mainStageApp, sideStageApp, etc) don't change. Let's force a
785 // binding update in that case.
786 Connections {
787 target: ApplicationManager
788 onApplicationRemoved: spreadTile.z = Qt.binding(function() {
789 return spreadView.indexToZIndex(index);
790 })
791 }856 }
792857
793 progress: {858 progress: {
@@ -873,13 +938,10 @@
873 script: {938 script: {
874 // rotate immediately.939 // rotate immediately.
875 spreadTile.matchShellOrientation();940 spreadTile.matchShellOrientation();
876 if (ApplicationManager.focusedApplicationId === spreadTile.appId &&941 if (priv.focusedAppDelegate === spreadTile &&
877 priv.sideStageEnabled && !sideStage.shown) {942 priv.sideStageEnabled && !sideStage.shown) {
878 // Sidestage was focused, so show the side stage.943 // Sidestage was focused, so show the side stage.
879 sideStage.show();944 sideStage.show();
880 // if we've switched to a main app which doesnt support portrait, hide the side stage.
881 } else if (mainApp && (mainApp.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) == 0) {
882 sideStage.hideNow();
883 }945 }
884 }946 }
885 }947 }
@@ -890,10 +952,10 @@
890 SequentialAnimation {952 SequentialAnimation {
891 ScriptAction {953 ScriptAction {
892 script: {954 script: {
893 if (priv.sideStageAppId === spreadTile.appId &&955 if (priv.sideStageDelegate === spreadTile &&
894 mainApp && (mainApp.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) == 0) {956 mainApp && (mainApp.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) == 0) {
895 // The mainstage app did not natively support portrait orientation, so focus the sidestage.957 // The mainstage app did not natively support portrait orientation, so focus the sidestage.
896 ApplicationManager.requestFocusApplication(spreadTile.appId);958 spreadTile.focus = true;
897 }959 }
898 }960 }
899 }961 }
@@ -922,28 +984,26 @@
922984
923 onClosed: {985 onClosed: {
924 spreadView.closingIndex = index;986 spreadView.closingIndex = index;
925 ApplicationManager.stopApplication(ApplicationManager.get(index).appId);987 if (spreadTile.surface) {
926 }988 spreadTile.surface.close();
927989 } else if (spreadTile.application) {
928 onFocusChanged: {990 root.applicationManager.stopApplication(spreadTile.application.appId);
929 if (focus && ApplicationManager.focusedApplicationId !== appId) {991 } else {
930 ApplicationManager.focusApplication(appId);992 // should never happen
931 }993 console.warn("Can't close topLevelSurfaceList entry as it has neither"
932994 + " a surface nor an application");
933 if (focus && priv.sideStageEnabled && stage === ApplicationInfoInterface.SideStage) {
934 sideStage.show();
935 }995 }
936 }996 }
937997
938 Binding {998 Binding {
939 target: root999 target: root
940 when: model.appId == priv.mainStageAppId1000 when: model.id == priv.mainStageItemId
941 property: "mainAppWindowOrientationAngle"1001 property: "mainAppWindowOrientationAngle"
942 value: appWindowOrientationAngle1002 value: appWindowOrientationAngle
943 }1003 }
944 Binding {1004 Binding {
945 target: priv1005 target: priv
946 when: model.appId == priv.mainStageAppId1006 when: model.id == priv.mainStageItemId
947 property: "mainAppOrientationChangesEnabled"1007 property: "mainAppOrientationChangesEnabled"
948 value: orientationChangesEnabled1008 value: orientationChangesEnabled
949 }1009 }
@@ -957,7 +1017,7 @@
9571017
958 StagedFullscreenPolicy {1018 StagedFullscreenPolicy {
959 id: fullscreenPolicy1019 id: fullscreenPolicy
960 application: spreadTile.application1020 surface: model.surface
961 }1021 }
962 Connections {1022 Connections {
963 target: root1023 target: root
@@ -973,17 +1033,19 @@
973 anchors.fill: parent1033 anchors.fill: parent
974 enabled: priv.sideStageEnabled && !spreadView.active1034 enabled: priv.sideStageEnabled && !spreadView.active
975 property var dragObject: null1035 property var dragObject: null
976 property string appId: ""1036
1037 property Item spreadDelegate
1038
977 dragComponent: dragComponent1039 dragComponent: dragComponent
978 dragComponentProperties: { "appId": appId }1040 dragComponentProperties: { "spreadDelegate": spreadDelegate }
9791041
980 onPressed: {1042 onPressed: {
981 function matchDelegate(obj) { return String(obj.objectName).indexOf("tabletSpreadDelegate") >= 0; }1043 function matchDelegate(obj) { return String(obj.objectName).indexOf("spreadDelegate") >= 0; }
9821044
983 var delegateAtCenter = Functions.itemAt(spreadRow, x, y, matchDelegate);1045 var delegateAtCenter = Functions.itemAt(spreadRow, x, y, matchDelegate);
984 if (!delegateAtCenter) return;1046 if (!delegateAtCenter) return;
9851047
986 appId = delegateAtCenter.appId;1048 spreadDelegate = delegateAtCenter;
987 }1049 }
9881050
989 onClicked: {1051 onClicked: {
@@ -1003,11 +1065,11 @@
10031065
1004 Component {1066 Component {
1005 id: dragComponent1067 id: dragComponent
1006 SessionContainer {1068 SurfaceContainer {
1007 property string appId: ""1069 property Item spreadDelegate
1008 property var application: ApplicationManager.findApplication(appId)1070
10091071 surface: spreadDelegate ? spreadDelegate.surface : null
1010 session: application ? application.session : null1072
1011 interactive: false1073 interactive: false
1012 resizeSurface: false1074 resizeSurface: false
1013 focus: false1075 focus: false
@@ -1019,10 +1081,11 @@
1019 Drag.hotSpot.y: height/21081 Drag.hotSpot.y: height/2
1020 // only accept opposite stage.1082 // only accept opposite stage.
1021 Drag.keys: {1083 Drag.keys: {
1022 if (!application) return "Disabled";1084 if (!surface) return "Disabled";
10231085
1024 if (application.stage === ApplicationInfo.MainStage) {1086 if (spreadDelegate.stage === ApplicationInfo.MainStage) {
1025 if (application.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) {1087 if (spreadDelegate.application.supportedOrientations
1088 & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) {
1026 return "MainStage";1089 return "MainStage";
1027 }1090 }
1028 return "Disabled";1091 return "Disabled";
10291092
=== added file 'qml/Stages/TopLevelSurfaceRepeater.qml'
--- qml/Stages/TopLevelSurfaceRepeater.qml 1970-01-01 00:00:00 +0000
+++ qml/Stages/TopLevelSurfaceRepeater.qml 2016-04-20 17:09:16 +0000
@@ -0,0 +1,52 @@
1/*
2 * Copyright (C) 2016 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.4
18
19Repeater {
20 id: root
21 // FIXME: This is a hack around us not knowing whether the Repeater has finished creating its
22 // delegates on start up.
23 // This is a problem when the stage gets a TopLevelSurfaceList already populated with several
24 // rows.
25 property bool startingUp: true
26 onStartingUpChanged: {
27 if (!startingUp) {
28 // the top-most surface must be the focused one.
29 var topmostDelegate = itemAt(0);
30 topmostDelegate.focus = true;
31 }
32 }
33
34 onItemAdded: {
35 if (startingUp) {
36 checkIfStillStartingUp();
37 }
38 }
39
40 function checkIfStillStartingUp() {
41 var i = 0;
42 var missingDelegate = false;
43 for (i = 0; i < model.count && !missingDelegate; ++i) {
44 if (!itemAt(i)) {
45 missingDelegate = true;
46 }
47 }
48 if (!missingDelegate) {
49 startingUp = false;
50 }
51 }
52}
053
=== modified file 'qml/Stages/TransformedSpreadDelegate.qml'
--- qml/Stages/TransformedSpreadDelegate.qml 2015-07-15 15:07:19 +0000
+++ qml/Stages/TransformedSpreadDelegate.qml 2016-04-20 17:09:16 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright 2014 Canonical Ltd.2 * Copyright 2014,2016 Canonical Ltd.
3 *3 *
4 * This program is free software; you can redistribute it and/or modify4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by5 * it under the terms of the GNU Lesser General Public License as published by
@@ -312,7 +312,7 @@
312 // non-fullscreen window when they're stacked on top of each other on the312 // non-fullscreen window when they're stacked on top of each other on the
313 // far left of the spread.313 // far left of the spread.
314 Translate {314 Translate {
315 y: !fullscreen && appWindowRotation === 180315 y: !root.fullscreen && appWindowRotation === 180
316 ? priv.topMarginProgress * maximizedAppTopMargin316 ? priv.topMarginProgress * maximizedAppTopMargin
317 : 0317 : 0
318 },318 },
@@ -326,7 +326,7 @@
326 switch (appWindowRotation) {326 switch (appWindowRotation) {
327 case 90:327 case 90:
328 case 270:328 case 270:
329 if (fullscreen) {329 if (root.fullscreen) {
330 return 1;330 return 1;
331 } else {331 } else {
332 return 1 + priv.topMarginProgress * maximizedAppTopMargin / spreadView.width;332 return 1 + priv.topMarginProgress * maximizedAppTopMargin / spreadView.width;
@@ -340,7 +340,7 @@
340 yScale: {340 yScale: {
341 switch (appWindowRotation) {341 switch (appWindowRotation) {
342 case 0:342 case 0:
343 if (fullscreen) {343 if (root.fullscreen) {
344 return 1 - priv.topMarginProgress * maximizedAppTopMargin / spreadView.height;344 return 1 - priv.topMarginProgress * maximizedAppTopMargin / spreadView.height;
345 } else {345 } else {
346 return 1;346 return 1;
347347
=== modified file 'qml/Stages/TransformedTabletSpreadDelegate.qml'
--- qml/Stages/TransformedTabletSpreadDelegate.qml 2016-03-11 20:18:12 +0000
+++ qml/Stages/TransformedTabletSpreadDelegate.qml 2016-04-20 17:09:16 +0000
@@ -31,7 +31,6 @@
31 // Set this to true when this tile a currently active on either the MS or the SS.31 // Set this to true when this tile a currently active on either the MS or the SS.
32 property bool active: false32 property bool active: false
3333
34 property int stage
35 property int zIndex34 property int zIndex
36 property real progress: 035 property real progress: 0
37 property real animatedProgress: 036 property real animatedProgress: 0
@@ -48,13 +47,15 @@
4847
49 property bool isInSideStage: false48 property bool isInSideStage: false
5049
50 property int stage: ApplicationInfoInterface.MainStage
51
51 property int dragOffset: 052 property int dragOffset: 0
52 readonly property alias xTranslateAnimating: xTranslateAnimation.running53 readonly property alias xTranslateAnimating: xTranslateAnimation.running
53 readonly property bool offScreen: priv.xTranslate >= 054 readonly property bool offScreen: priv.xTranslate >= 0
5455
55 dropShadow: spreadView.active ||56 dropShadow: spreadView.active ||
56 (active57 (active
57 && (stage == ApplicationInfoInterface.MainStage || !priv.shellIsLandscape)58 && (root.stage == ApplicationInfoInterface.MainStage || !priv.shellIsLandscape)
58 && priv.xTranslate != 0)59 && priv.xTranslate != 0)
5960
60 onSelectedChanged: {61 onSelectedChanged: {
@@ -90,7 +91,7 @@
90 id: priv91 id: priv
9192
92 // true if this is the next tile on the stack that comes in when dragging from the right93 // true if this is the next tile on the stack that comes in when dragging from the right
93 property bool nextInStack: spreadView.nextZInStack == zIndex94 property bool nextInStack: spreadView.nextInStack == model.index
94 // true if the next tile in the stack is the nextInStack one. This one will be moved a bit to the left95 // true if the next tile in the stack is the nextInStack one. This one will be moved a bit to the left
95 property bool movedActive: spreadView.nextZInStack == zIndex + 196 property bool movedActive: spreadView.nextZInStack == zIndex + 1
96 property real animatedEndDistance: linearAnimation(0, 2, root.endDistance, 0, root.progress)97 property real animatedEndDistance: linearAnimation(0, 2, root.endDistance, 0, root.progress)
@@ -142,7 +143,7 @@
142 Behavior on xTranslate {143 Behavior on xTranslate {
143 enabled: !spreadView.active &&144 enabled: !spreadView.active &&
144 !snapAnimation.running &&145 !snapAnimation.running &&
145 model.appId !== "unity8-dash" &&146 root.application.appId !== "unity8-dash" &&
146 spreadView.animateX &&147 spreadView.animateX &&
147 !spreadView.beingResized &&148 !spreadView.beingResized &&
148 priv.state !== "sideStage"149 priv.state !== "sideStage"
@@ -156,8 +157,9 @@
156 var newTranslate = 0;157 var newTranslate = 0;
157158
158 // selected app or opposite stage active app.159 // selected app or opposite stage active app.
159 if (isSelected || (otherSelected && root.active && spreadView.selectedApplication && spreadView.selectedApplication.stage !== stage)) {160 if (isSelected || (otherSelected && root.active && spreadView.selectedDelegate
160 if (stage == ApplicationInfoInterface.MainStage) {161 && spreadView.selectedDelegate.stage !== root.stage)) {
162 if (root.stage == ApplicationInfoInterface.MainStage) {
161 return linearAnimation(selectedProgress, negativeProgress, selectedXTranslate, -spreadView.width, root.progress);163 return linearAnimation(selectedProgress, negativeProgress, selectedXTranslate, -spreadView.width, root.progress);
162 } else {164 } else {
163 return linearAnimation(selectedProgress, negativeProgress, selectedXTranslate, -spreadView.sideStageWidth, root.progress);165 return linearAnimation(selectedProgress, negativeProgress, selectedXTranslate, -spreadView.sideStageWidth, root.progress);
@@ -168,8 +170,8 @@
168170
169 // 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 not171 // 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
170 // when we're only dragging the side stage in on top of a main stage app172 // when we're only dragging the side stage in on top of a main stage app
171 var shouldMoveAway = spreadView.nextInStack >= 0 && priv.movedActive && stage === ApplicationInfoInterface.MainStage &&173 var shouldMoveAway = spreadView.nextInStack >= 0 && priv.movedActive && root.stage === ApplicationInfoInterface.MainStage &&
172 ApplicationManager.get(spreadView.nextInStack).stage === ApplicationInfoInterface.MainStage;174 topLevelSurfaceList.applicationAt(spreadView.nextInStack).stage === ApplicationInfoInterface.MainStage;
173175
174 if (active) {176 if (active) {
175 newTranslate -= root.width177 newTranslate -= root.width
@@ -178,7 +180,7 @@
178 newTranslate += linearAnimation(0, spreadView.positionMarker2, 0, -units.gu(4), root.animatedProgress);180 newTranslate += linearAnimation(0, spreadView.positionMarker2, 0, -units.gu(4), root.animatedProgress);
179 }181 }
180 newTranslate += root.dragOffset;182 newTranslate += root.dragOffset;
181 } else if (!spreadView.active && model.appId == "unity8-dash") {183 } else if (!spreadView.active && root.application.appId == "unity8-dash") {
182 newTranslate -= root.width;184 newTranslate -= root.width;
183 }185 }
184186
@@ -190,14 +192,14 @@
190192
191 if (spreadView.phase == 0) {193 if (spreadView.phase == 0) {
192 if (nextInStack) {194 if (nextInStack) {
193 if (stage == ApplicationInfoInterface.MainStage) {195 if (root.stage == ApplicationInfoInterface.MainStage) {
194 if (spreadView.sideStageVisible && root.progress > 0) {196 if (spreadView.sideStageVisible && root.progress > 0) {
195 // Move it so it appears from behind the side stage immediately197 // Move it so it appears from behind the side stage immediately
196 newTranslate += -spreadView.sideStageWidth;198 newTranslate += -spreadView.sideStageWidth;
197 }199 }
198 }200 }
199201
200 if (stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {202 if (root.stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
201 // This is when we only drag the side stage in, without rotation or snapping203 // This is when we only drag the side stage in, without rotation or snapping
202 newTranslate = linearAnimation(0, spreadView.positionMarker2, 0, -spreadView.sideStageWidth, root.animatedProgress);204 newTranslate = linearAnimation(0, spreadView.positionMarker2, 0, -spreadView.sideStageWidth, root.animatedProgress);
203 } else {205 } else {
@@ -210,7 +212,7 @@
210212
211 if (spreadView.phase == 1) {213 if (spreadView.phase == 1) {
212 if (nextInStack) {214 if (nextInStack) {
213 if (stage == ApplicationInfoInterface.MainStage) {215 if (root.stage == ApplicationInfoInterface.MainStage) {
214 var startValue = -spreadView.sideStageWidth * spreadView.snapPosition + (spreadView.sideStageVisible ? -spreadView.sideStageWidth : 0);216 var startValue = -spreadView.sideStageWidth * spreadView.snapPosition + (spreadView.sideStageVisible ? -spreadView.sideStageWidth : 0);
215 newTranslate += linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, startValue, priv.phase2StartTranslate, root.animatedProgress);217 newTranslate += linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, startValue, priv.phase2StartTranslate, root.animatedProgress);
216 } else {218 } else {
@@ -250,7 +252,8 @@
250 }252 }
251253
252 // selected app or opposite stage active app.254 // selected app or opposite stage active app.
253 if (isSelected || (otherSelected && root.active && spreadView.selectedApplication && spreadView.selectedApplication.stage !== stage)) {255 if (isSelected || (otherSelected && root.active && spreadView.selectedDelegate
256 && spreadView.selectedDelegate.stage !== root.stage)) {
254 return linearAnimation(selectedProgress, negativeProgress, selectedScale, 1, root.progress);257 return linearAnimation(selectedProgress, negativeProgress, selectedScale, 1, root.progress);
255 } else if (otherSelected) {258 } else if (otherSelected) {
256 return selectedScale;259 return selectedScale;
@@ -258,7 +261,7 @@
258261
259 if (spreadView.phase == 0) {262 if (spreadView.phase == 0) {
260 if (nextInStack) {263 if (nextInStack) {
261 if (stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {264 if (root.stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
262 return 1;265 return 1;
263 } else {266 } else {
264 var targetScale = root.dragStartScale - ((root.dragStartScale - 1) * spreadView.snapPosition);267 var targetScale = root.dragStartScale - ((root.dragStartScale - 1) * spreadView.snapPosition);
@@ -272,7 +275,7 @@
272 if (spreadView.phase == 1) {275 if (spreadView.phase == 1) {
273 if (nextInStack) {276 if (nextInStack) {
274 var startScale = 1;277 var startScale = 1;
275 if (stage !== ApplicationInfoInterface.SideStage || spreadView.sideStageVisible) {278 if (root.stage !== ApplicationInfoInterface.SideStage || spreadView.sideStageVisible) {
276 startScale = root.dragStartScale - ((root.dragStartScale - 1) * spreadView.snapPosition);279 startScale = root.dragStartScale - ((root.dragStartScale - 1) * spreadView.snapPosition);
277 }280 }
278 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, startScale, priv.phase2StartScale, root.animatedProgress);281 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, startScale, priv.phase2StartScale, root.animatedProgress);
@@ -295,7 +298,8 @@
295 }298 }
296299
297 // selected app or opposite stage active app.300 // selected app or opposite stage active app.
298 if (isSelected || (otherSelected && root.active && spreadView.selectedApplication && spreadView.selectedApplication.stage !== stage)) {301 if (isSelected || (otherSelected && root.active && spreadView.selectedDelegate
302 && spreadView.selectedDelegate.stage !== root.stage)) {
299 return linearAnimation(selectedProgress, negativeProgress, selectedAngle, 0, root.progress);303 return linearAnimation(selectedProgress, negativeProgress, selectedAngle, 0, root.progress);
300 } else if (otherSelected) {304 } else if (otherSelected) {
301 return selectedAngle;305 return selectedAngle;
@@ -304,12 +308,12 @@
304 // The tile should rotate a bit when another one comes on top, but not when only dragging the side stage in308 // The tile should rotate a bit when another one comes on top, but not when only dragging the side stage in
305 var shouldMoveAway = spreadView.nextInStack == -1 ||309 var shouldMoveAway = spreadView.nextInStack == -1 ||
306 spreadView.nextInStack >= 0 && priv.movedActive &&310 spreadView.nextInStack >= 0 && priv.movedActive &&
307 (ApplicationManager.get(spreadView.nextInStack).stage === ApplicationInfoInterface.MainStage ||311 (topLevelSurfaceList.applicationAt(spreadView.nextInStack).stage === ApplicationInfoInterface.MainStage ||
308 stage == ApplicationInfoInterface.SideStage);312 root.stage == ApplicationInfoInterface.SideStage);
309313
310 if (spreadView.phase == 0) {314 if (spreadView.phase == 0) {
311 if (nextInStack) {315 if (nextInStack) {
312 if (stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {316 if (root.stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
313 return 0;317 return 0;
314 } else {318 } else {
315 return linearAnimation(0, spreadView.positionMarker2, root.startAngle, root.startAngle * (1-spreadView.snapPosition), root.animatedProgress);319 return linearAnimation(0, spreadView.positionMarker2, root.startAngle, root.startAngle * (1-spreadView.snapPosition), root.animatedProgress);
@@ -321,7 +325,7 @@
321 }325 }
322 if (spreadView.phase == 1) {326 if (spreadView.phase == 1) {
323 if (nextInStack) {327 if (nextInStack) {
324 if (stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {328 if (root.stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
325 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, 0, priv.phase2StartAngle, root.animatedProgress);329 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, 0, priv.phase2StartAngle, root.animatedProgress);
326 } else {330 } else {
327 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, root.startAngle * (1-spreadView.snapPosition), priv.phase2StartAngle, root.animatedProgress);331 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, root.startAngle * (1-spreadView.snapPosition), priv.phase2StartAngle, root.animatedProgress);
@@ -345,7 +349,8 @@
345 if (root.isSelected) return 1;349 if (root.isSelected) return 1;
346350
347 if (otherSelected) {351 if (otherSelected) {
348 if (root.active && spreadView.selectedApplication && spreadView.selectedApplication.stage !== stage) {352 if (root.active && spreadView.selectedDelegate
353 && spreadView.selectedDelegate.stage !== root.stage) {
349 return 1;354 return 1;
350 }355 }
351 return linearAnimation(selectedProgress, negativeProgress, selectedOpacity, 0, root.progress);356 return linearAnimation(selectedProgress, negativeProgress, selectedOpacity, 0, root.progress);
@@ -355,7 +360,8 @@
355360
356 property real topMarginProgress: {361 property real topMarginProgress: {
357 // selected app or opposite stage active app.362 // selected app or opposite stage active app.
358 if (isSelected || (otherSelected && root.active && spreadView.selectedApplication && spreadView.selectedApplication.stage !== stage)) {363 if (isSelected || (otherSelected && root.active && spreadView.selectedDelegate
364 && spreadView.selectedDelegate.stage !== root.stage)) {
359 return linearAnimation(selectedProgress, negativeProgress, selectedTopMarginProgress, 0, root.progress);365 return linearAnimation(selectedProgress, negativeProgress, selectedTopMarginProgress, 0, root.progress);
360 }366 }
361367
@@ -379,7 +385,7 @@
379 when: root.isInSideStage && spreadView.shiftedContentX == 0 && spreadView.phase == 0385 when: root.isInSideStage && spreadView.shiftedContentX == 0 && spreadView.phase == 0
380 PropertyChanges {386 PropertyChanges {
381 target: priv;387 target: priv;
382 xTranslate: -spreadView.sideStageWidth + spreadView.sideStageWidth * (1-spreadView.sideStageDragProgress)388 xTranslate: -spreadView.sideStageWidth + spreadView.sideStageWidth * (1-sideStage.progress)
383 }389 }
384 }390 }
385 ]391 ]
386392
=== modified file 'qml/Stages/WindowedFullscreenPolicy.qml'
--- qml/Stages/WindowedFullscreenPolicy.qml 2016-03-15 19:38:03 +0000
+++ qml/Stages/WindowedFullscreenPolicy.qml 2016-04-20 17:09:16 +0000
@@ -23,19 +23,17 @@
23// state of the window is returned to restored.23// state of the window is returned to restored.
24QtObject {24QtObject {
25 property bool active: true25 property bool active: true
26 property QtObject application: null26 property QtObject surface: null
2727
28 readonly property var lastSurface: application && application.session ?
29 application.session.lastSurface : null
30 property bool _firstTimeSurface: true28 property bool _firstTimeSurface: true
3129
32 onLastSurfaceChanged: {30 onSurfaceChanged: {
33 if (!active || !lastSurface) return;31 if (!active || !surface) return;
34 if (!_firstTimeSurface) return;32 if (!_firstTimeSurface) return;
35 _firstTimeSurface = false;33 _firstTimeSurface = false;
3634
37 if (lastSurface.state === Mir.FullscreenState && lastSurface.shellChrome === Mir.LowChrome) {35 if (surface.state === Mir.FullscreenState && surface.shellChrome === Mir.LowChrome) {
38 lastSurface.state = Mir.RestoredState;36 surface.state = Mir.RestoredState;
39 }37 }
40 }38 }
41}39}
4240
=== modified file 'qml/Tutorial/TutorialBottom.qml'
--- qml/Tutorial/TutorialBottom.qml 2016-03-16 15:08:49 +0000
+++ qml/Tutorial/TutorialBottom.qml 2016-04-20 17:09:16 +0000
@@ -47,7 +47,7 @@
47 readonly property real sideStageWidth: root.usageScenario === "tablet" && stage.sideStageVisible ?47 readonly property real sideStageWidth: root.usageScenario === "tablet" && stage.sideStageVisible ?
48 stage.sideStageWidth : 048 stage.sideStageWidth : 0
49 readonly property bool isMainStageApp: usageScenario !== "tablet" ||49 readonly property bool isMainStageApp: usageScenario !== "tablet" ||
50 application.stage === ApplicationInfoInterface.MainStage50 stage.stageFocusedSurface === ApplicationInfoInterface.MainStage
51 readonly property real dragAreaHeight: units.gu(3) // based on PageWithBottomEdge.qml51 readonly property real dragAreaHeight: units.gu(3) // based on PageWithBottomEdge.qml
52 readonly property real targetDistance: height * 0.2 + dragAreaHeight // based on PageWithBottomEdge.qml52 readonly property real targetDistance: height * 0.2 + dragAreaHeight // based on PageWithBottomEdge.qml
5353
5454
=== modified file 'tests/mocks/Unity/Application/ApplicationInfo.cpp'
--- tests/mocks/Unity/Application/ApplicationInfo.cpp 2016-03-16 12:29:44 +0000
+++ tests/mocks/Unity/Application/ApplicationInfo.cpp 2016-04-20 17:09:16 +0000
@@ -16,8 +16,7 @@
1616
17#include "ApplicationInfo.h"17#include "ApplicationInfo.h"
18#include "MirSurface.h"18#include "MirSurface.h"
19#include "Session.h"19#include "SurfaceManager.h"
20#include "SessionManager.h"
2120
22#include <paths.h>21#include <paths.h>
2322
@@ -25,97 +24,114 @@
25#include <QQuickItem>24#include <QQuickItem>
26#include <QQuickView>25#include <QQuickView>
27#include <QQmlComponent>26#include <QQmlComponent>
28#include <QTimer>27
28#define APPLICATION_DEBUG 0
29
30#if APPLICATION_DEBUG
31#define DEBUG_MSG(params) qDebug().nospace() << "Application["<<appId()<<"]::" << __func__ << " " << params
32
33QString stateToStr(ApplicationInfo::State state)
34{
35 switch (state) {
36 case ApplicationInfo::Starting:
37 return "starting";
38 case ApplicationInfo::Running:
39 return "running";
40 case ApplicationInfo::Suspended:
41 return "suspended";
42 case ApplicationInfo::Stopped:
43 return "stopped";
44 default:
45 return "???";
46 };
47}
48
49#else
50#define DEBUG_MSG(params) ((void)0)
51#endif
52
53#define WARNING_MSG(params) qWarning().nospace() << "Application["<<appId()<<"]::" << __func__ << " " << params
2954
30ApplicationInfo::ApplicationInfo(const QString &appId, QObject *parent)55ApplicationInfo::ApplicationInfo(const QString &appId, QObject *parent)
31 : ApplicationInfoInterface(appId, parent)56 : ApplicationInfoInterface(appId, parent)
32 , m_appId(appId)57 , m_appId(appId)
33 , m_stage(MainStage)
34 , m_state(Stopped)
35 , m_focused(false)
36 , m_fullscreen(false)
37 , m_session(0)
38 , m_supportedOrientations(Qt::PortraitOrientation |
39 Qt::LandscapeOrientation |
40 Qt::InvertedPortraitOrientation |
41 Qt::InvertedLandscapeOrientation)
42 , m_rotatesWindowContents(false)
43 , m_requestedState(RequestedRunning)
44 , m_isTouchApp(true)
45 , m_exemptFromLifecycle(false)
46 , m_manualSurfaceCreation(false)
47 , m_shellChrome(Mir::NormalChrome)
48{58{
59 connect(&m_surfaceList, &MirSurfaceListModel::countChanged,
60 this, &ApplicationInfo::onSurfaceCountChanged, Qt::QueuedConnection);
61
62 m_surfaceCreationTimer.setSingleShot(true);
63 m_surfaceCreationTimer.setInterval(500);
64 connect(&m_surfaceCreationTimer, &QTimer::timeout, this, &ApplicationInfo::createSurface);
49}65}
5066
51ApplicationInfo::ApplicationInfo(QObject *parent)67ApplicationInfo::ApplicationInfo(QObject *parent)
52 : ApplicationInfoInterface(QString(), parent)68 : ApplicationInfo(QString(), parent)
53 , m_stage(MainStage)
54 , m_state(Stopped)
55 , m_focused(false)
56 , m_fullscreen(false)
57 , m_session(0)
58 , m_supportedOrientations(Qt::PortraitOrientation |
59 Qt::LandscapeOrientation |
60 Qt::InvertedPortraitOrientation |
61 Qt::InvertedLandscapeOrientation)
62 , m_rotatesWindowContents(false)
63 , m_requestedState(RequestedRunning)
64 , m_isTouchApp(true)
65 , m_exemptFromLifecycle(false)
66 , m_manualSurfaceCreation(false)
67 , m_shellChrome(Mir::NormalChrome)
68{69{
69}70}
7071
71ApplicationInfo::~ApplicationInfo()72ApplicationInfo::~ApplicationInfo()
72{73{
73 delete m_session;74}
74}75
7576void ApplicationInfo::createSurface()
76void ApplicationInfo::createSession()77{
77{78 if (state() == ApplicationInfo::Stopped) { return; }
78 if (m_session || state() == ApplicationInfo::Stopped) { return; }79
7980 QString surfaceName = name();
80 setSession(SessionManager::singleton()->createSession(appId(), m_screenshotFileName));81 if (m_surfaceList.count() > 0) {
81}82 surfaceName.append(QString(" %1").arg(m_surfaceList.count()+1));
8283 }
83void ApplicationInfo::destroySession()84
84{85 auto surfaceManager = SurfaceManager::instance();
85 Session *session = this->session();86 if (!surfaceManager) {
86 setSession(nullptr);87 WARNING_MSG("No SurfaceManager");
87 delete session;
88}
89
90void ApplicationInfo::setSession(Session* session)
91{
92 if (m_session == session)
93 return;88 return;
9489 }
95 if (m_session) {90
96 disconnect(this, 0, m_session, 0);91 auto surface = surfaceManager->createSurface(surfaceName,
97 m_session->setApplication(nullptr);92 Mir::NormalType,
98 m_session->setParent(nullptr);93 fullscreen() ? Mir::FullscreenState : Mir::MaximizedState,
99 Q_EMIT m_session->deregister();94 m_screenshotFileName);
100 }95
10196 surface->setShellChrome(m_shellChrome);
102 m_session = session;97
10398 m_surfaceList.appendSurface(surface);
104 if (m_session) {99
105 m_session->setApplication(this);100 ++m_liveSurfaceCount;
106 m_session->setParent(this);101 connect(surface, &MirSurface::liveChanged, this, [this, surface](){
107 m_session->setFullscreen(m_fullscreen);102 if (!surface->live()) {
108 SessionManager::singleton()->registerSession(m_session);103 --m_liveSurfaceCount;
109 connect(m_session, &Session::surfaceAdded,104 if (m_liveSurfaceCount == 0) {
110 this, &ApplicationInfo::onSessionSurfaceAdded);105 if (m_closingSurfaces.contains(surface)
111 connect(m_session, &Session::fullscreenChanged, this, &ApplicationInfo::fullscreenChanged);106 || (m_state == Running && m_requestedState == RequestedRunning)) {
112107 Q_EMIT closed();
113 if (!m_manualSurfaceCreation) {108 }
114 QTimer::singleShot(500, m_session, &Session::createSurface);109 setState(Stopped);
115 }110 } else {
116 }111 if (m_closingSurfaces.contains(surface) && m_requestedState == RequestedSuspended
117112 && m_closingSurfaces.count() == 1) {
118 Q_EMIT sessionChanged(m_session);113 setState(Suspended);
114 }
115 }
116 m_closingSurfaces.removeAll(surface);
117 }
118 });
119 connect(surface, &MirSurface::closeRequested, this, [this, surface](){
120 m_closingSurfaces.append(surface);
121 if (m_state == Suspended) {
122 // resume to allow application to close its surface
123 setState(Running);
124 }
125 });
126 connect(surface, &MirSurface::focusRequested, this, &ApplicationInfo::focusRequested);
127
128 if (m_state == Starting) {
129 if (m_requestedState == RequestedRunning) {
130 setState(Running);
131 } else {
132 setState(Suspended);
133 }
134 }
119}135}
120136
121void ApplicationInfo::setIconId(const QString &iconId)137void ApplicationInfo::setIconId(const QString &iconId)
@@ -138,10 +154,6 @@
138154
139 if (screenshotFileName != m_screenshotFileName) {155 if (screenshotFileName != m_screenshotFileName) {
140 m_screenshotFileName = screenshotFileName;156 m_screenshotFileName = screenshotFileName;
141
142 if (m_session) {
143 m_session->setScreenshot(screenshotFileName);
144 }
145 }157 }
146}158}
147159
@@ -172,38 +184,53 @@
172void ApplicationInfo::setState(State value)184void ApplicationInfo::setState(State value)
173{185{
174 if (value != m_state) {186 if (value != m_state) {
187 DEBUG_MSG(qPrintable(stateToStr(value)));
188 if (!m_manualSurfaceCreation && value == ApplicationInfo::Starting) {
189 Q_ASSERT(m_surfaceList.count() == 0);
190 m_surfaceCreationTimer.start();
191 } else if (value == ApplicationInfo::Stopped) {
192 m_surfaceCreationTimer.stop();
193 for (int i = 0; i < m_surfaceList.count(); ++i) {
194 MirSurface *surface = static_cast<MirSurface*>(m_surfaceList.get(i));
195 surface->setLive(false);
196 }
197 }
198
175 m_state = value;199 m_state = value;
176 Q_EMIT stateChanged(value);200 Q_EMIT stateChanged(value);
177201 }
178 if (!m_manualSurfaceCreation && !m_session && m_state == ApplicationInfo::Starting) {202}
179 QTimer::singleShot(500, this, &ApplicationInfo::createSession);203
180 } else if (m_state == ApplicationInfo::Stopped) {204void ApplicationInfo::close()
181 Session *session = m_session;205{
182 setSession(nullptr);206 DEBUG_MSG("");
183 delete session;207
208 if (m_surfaceList.count() > 0) {
209 for (int i = 0; i < m_surfaceList.count(); ++i) {
210 MirSurface *surface = static_cast<MirSurface*>(m_surfaceList.get(i));
211 surface->close();
184 }212 }
185 }213 } else {
186}214 setState(Stopped);
187215 Q_EMIT closed();
188void ApplicationInfo::setFocused(bool value)
189{
190 if (value != m_focused) {
191 m_focused = value;
192 Q_EMIT focusedChanged(value);
193 }216 }
194}217}
195218
196void ApplicationInfo::setFullscreen(bool value)219void ApplicationInfo::setFullscreen(bool value)
197{220{
198 m_fullscreen = value;221 m_fullscreen = value;
199 if (m_session) {222 if (m_surfaceList.rowCount() > 0) {
200 m_session->setFullscreen(value);223 m_surfaceList.get(0)->setState(Mir::FullscreenState);
201 }224 }
202}225}
203226
204bool ApplicationInfo::fullscreen() const227bool ApplicationInfo::fullscreen() const
205{228{
206 return m_session ? m_session->fullscreen() : m_fullscreen;229 if (m_surfaceList.rowCount() > 0) {
230 return m_surfaceList.get(0)->state() == Mir::FullscreenState;
231 } else {
232 return m_fullscreen;
233 }
207}234}
208235
209void ApplicationInfo::setManualSurfaceCreation(bool value)236void ApplicationInfo::setManualSurfaceCreation(bool value)
@@ -241,15 +268,28 @@
241268
242void ApplicationInfo::setRequestedState(RequestedState value)269void ApplicationInfo::setRequestedState(RequestedState value)
243{270{
244 if (m_requestedState != value) {271 if (m_requestedState == value) {
245 m_requestedState = value;272 return;
246 Q_EMIT requestedStateChanged(m_requestedState);273 }
247274 DEBUG_MSG((value == RequestedRunning ? "RequestedRunning" : "RequestedSuspended") );
248 if (m_requestedState == RequestedRunning && m_state == Suspended) {275
276 m_requestedState = value;
277 Q_EMIT requestedStateChanged(m_requestedState);
278
279 if (m_requestedState == RequestedRunning) {
280
281 if (m_state == Suspended) {
282 Q_ASSERT(m_liveSurfaceCount > 0);
249 setState(Running);283 setState(Running);
250 } else if (m_requestedState == RequestedSuspended && m_state == Running) {284 } else if (m_state == Stopped) {
251 setState(Suspended);285 Q_ASSERT(m_liveSurfaceCount == 0);
286 // it's restarting
287 setState(Starting);
252 }288 }
289
290 } else if (m_requestedState == RequestedSuspended && m_state == Running
291 && m_closingSurfaces.isEmpty()) {
292 setState(Suspended);
253 }293 }
254}294}
255295
@@ -263,18 +303,6 @@
263 m_isTouchApp = isTouchApp;303 m_isTouchApp = isTouchApp;
264}304}
265305
266void ApplicationInfo::onSessionSurfaceAdded(MirSurface* surface)
267{
268 if (surface != nullptr && m_state == Starting) {
269 if (m_requestedState == RequestedRunning) {
270 setState(Running);
271 } else {
272 setState(Suspended);
273 }
274 surface->setShellChrome(m_shellChrome);
275 }
276}
277
278bool ApplicationInfo::exemptFromLifecycle() const306bool ApplicationInfo::exemptFromLifecycle() const
279{307{
280 return m_exemptFromLifecycle;308 return m_exemptFromLifecycle;
@@ -305,7 +333,52 @@
305void ApplicationInfo::setShellChrome(Mir::ShellChrome shellChrome)333void ApplicationInfo::setShellChrome(Mir::ShellChrome shellChrome)
306{334{
307 m_shellChrome = shellChrome;335 m_shellChrome = shellChrome;
308 if (m_session && m_session->lastSurface()) {336 if (m_surfaceList.rowCount() > 0) {
309 m_session->lastSurface()->setShellChrome(shellChrome);337 static_cast<MirSurface*>(m_surfaceList.get(0))->setShellChrome(shellChrome);
338 }
339}
340
341bool ApplicationInfo::focused() const
342{
343 bool someSurfaceHasFocus = false; // to be proven wrong
344 for (int i = 0; i < m_surfaceList.count() && !someSurfaceHasFocus; ++i) {
345 someSurfaceHasFocus = m_surfaceList.get(i)->focused();
346 }
347 return someSurfaceHasFocus;
348}
349
350void ApplicationInfo::setFocused(bool value)
351{
352 if (focused() == value) {
353 return;
354 }
355
356 if (value) {
357 if (m_surfaceList.count() > 0) {
358 m_surfaceList.get(0)->requestFocus();
359 }
360 } else {
361 for (int i = 0; i < m_surfaceList.count(); ++i) {
362 MirSurface *surface = static_cast<MirSurface*>(m_surfaceList.get(i));
363 if (surface->focused()) {
364 surface->setFocused(false);
365 }
366 }
367 }
368}
369
370void ApplicationInfo::onSurfaceCountChanged()
371{
372 if (m_surfaceList.count() == 0 && m_state == Running) {
373 setState(Stopped);
374 }
375}
376
377void ApplicationInfo::requestFocus()
378{
379 if (m_surfaceList.count() == 0) {
380 Q_EMIT focusRequested();
381 } else {
382 m_surfaceList.get(0)->requestFocus();
310 }383 }
311}384}
312385
=== modified file 'tests/mocks/Unity/Application/ApplicationInfo.h'
--- tests/mocks/Unity/Application/ApplicationInfo.h 2016-03-15 20:59:07 +0000
+++ tests/mocks/Unity/Application/ApplicationInfo.h 2016-04-20 17:09:16 +0000
@@ -20,25 +20,32 @@
20#include <QObject>20#include <QObject>
2121
22class MirSurface;22class MirSurface;
23class Session;
2423
25// unity-api24// unity-api
26#include <unity/shell/application/ApplicationInfoInterface.h>25#include <unity/shell/application/ApplicationInfoInterface.h>
27#include <unity/shell/application/Mir.h>26#include <unity/shell/application/Mir.h>
2827
28#include "MirSurfaceListModel.h"
29
30#include <QList>
31#include <QTimer>
32
29using namespace unity::shell::application;33using namespace unity::shell::application;
3034
31class ApplicationInfo : public ApplicationInfoInterface {35class ApplicationInfo : public ApplicationInfoInterface {
32 Q_OBJECT36 Q_OBJECT
3337
38 ////
39 // FIXME: Remove those
34 Q_PROPERTY(bool fullscreen READ fullscreen WRITE setFullscreen NOTIFY fullscreenChanged)40 Q_PROPERTY(bool fullscreen READ fullscreen WRITE setFullscreen NOTIFY fullscreenChanged)
35 Q_PROPERTY(Session* session READ session NOTIFY sessionChanged)
3641
37 // Only exists in this fake implementation42 // Only exists in this fake implementation
3843
39 // whether the test code will explicitly control the creation of the application surface44 // whether the test code will explicitly control the creation of the application surface
40 Q_PROPERTY(bool manualSurfaceCreation READ manualSurfaceCreation WRITE setManualSurfaceCreation NOTIFY manualSurfaceCreationChanged)45 Q_PROPERTY(bool manualSurfaceCreation READ manualSurfaceCreation WRITE setManualSurfaceCreation NOTIFY manualSurfaceCreationChanged)
4146
47 Q_PROPERTY(QString screenshot READ screenshot CONSTANT)
48
42public:49public:
43 ApplicationInfo(QObject *parent = nullptr);50 ApplicationInfo(QObject *parent = nullptr);
44 ApplicationInfo(const QString &appId, QObject *parent = nullptr);51 ApplicationInfo(const QString &appId, QObject *parent = nullptr);
@@ -66,8 +73,7 @@
66 Q_INVOKABLE void setState(State value);73 Q_INVOKABLE void setState(State value);
67 State state() const override { return m_state; }74 State state() const override { return m_state; }
6875
69 void setFocused(bool value);76 bool focused() const override;
70 bool focused() const override { return m_focused; }
7177
72 QString splashTitle() const override { return QString(); }78 QString splashTitle() const override { return QString(); }
73 QUrl splashImage() const override { return QUrl(); }79 QUrl splashImage() const override { return QUrl(); }
@@ -100,21 +106,26 @@
100 void setInitialSurfaceSize(const QSize &size) override;106 void setInitialSurfaceSize(const QSize &size) override;
101107
102 Q_INVOKABLE void setShellChrome(Mir::ShellChrome shellChrome);108 Q_INVOKABLE void setShellChrome(Mir::ShellChrome shellChrome);
103public:109
104 void setSession(Session* session);110 MirSurfaceListInterface* surfaceList() override { return &m_surfaceList; }
105 Session* session() const { return m_session; }111
112 void setFocused(bool value);
113
114 //////
115 // internal mock stuff
116 void close();
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches