Merge lp:~unity-team/unity8/side-stage-redesign into lp:unity8

Proposed by Michał Sawicz
Status: Merged
Approved by: Michael Terry
Approved revision: 2276
Merged at revision: 2287
Proposed branch: lp:~unity-team/unity8/side-stage-redesign
Merge into: lp:unity8
Prerequisite: lp:~unity-team/unity8/oobe
Diff against target: 5700 lines (+3695/-449)
56 files modified
CMakeLists.txt (+1/-1)
CODING (+2/-0)
debian/control (+4/-4)
libs/UbuntuGestures/Timer.h (+5/-0)
libs/UbuntuGestures/TouchRegistry.cpp (+15/-10)
plugins/Cursor/Cursor.qmltypes (+78/-0)
plugins/GlobalShortcut/GlobalShortcut.qmltypes (+31/-0)
plugins/Ubuntu/Gestures/CMakeLists.txt (+1/-0)
plugins/Ubuntu/Gestures/DirectionalDragArea.cpp (+3/-0)
plugins/Ubuntu/Gestures/Gestures.qmltypes (+87/-46)
plugins/Ubuntu/Gestures/TouchGestureArea.cpp (+875/-0)
plugins/Ubuntu/Gestures/TouchGestureArea.h (+229/-0)
plugins/Ubuntu/Gestures/plugin.cpp (+3/-0)
plugins/Unity/InputInfo/InputInfo.qmltypes (+71/-0)
plugins/Unity/Platform/Platform.qmltypes (+20/-0)
plugins/Utils/CMakeLists.txt (+1/-0)
plugins/Utils/globalfunctions.cpp (+56/-0)
plugins/Utils/globalfunctions.h (+42/-0)
plugins/Utils/plugin.cpp (+9/-0)
plugins/Utils/windowstatestorage.cpp (+36/-4)
plugins/Utils/windowstatestorage.h (+3/-0)
qml/Components/Showable.qml (+10/-0)
qml/Shell.qml (+2/-6)
qml/Stages/AbstractStage.qml (+1/-1)
qml/Stages/ApplicationWindow.qml (+3/-1)
qml/Stages/SideStage.qml (+118/-0)
qml/Stages/SpreadDelegate.qml (+10/-1)
qml/Stages/SurfaceContainer.qml (+1/-0)
qml/Stages/TabletSideStageTouchGesture.qml (+154/-0)
qml/Stages/TabletStage.qml (+402/-187)
qml/Stages/TransformedTabletSpreadDelegate.qml (+86/-58)
qml/Stages/graphics/sidestage_drag.svg (+207/-0)
qml/Stages/graphics/sidestage_open.svg (+181/-0)
src/MouseTouchAdaptor.cpp (+53/-9)
src/MouseTouchAdaptor.h (+5/-3)
tests/libs/UbuntuGestures/tst_TouchRegistry.cpp (+59/-0)
tests/mocks/Unity/Application/ApplicationManager.cpp (+10/-25)
tests/mocks/Unity/Application/ApplicationManager.h (+0/-8)
tests/mocks/Unity/Application/MirSurfaceItem.cpp (+17/-0)
tests/mocks/Unity/Application/MirSurfaceItem.h (+1/-0)
tests/mocks/Unity/Application/resources/MirSurfaceItem.qml (+59/-0)
tests/mocks/Unity/Indicators/ModelActionRootState.qml (+27/-5)
tests/mocks/Utils/CMakeLists.txt (+1/-0)
tests/mocks/Utils/plugin.cpp (+9/-0)
tests/mocks/Utils/windowstatestorage.cpp (+13/-0)
tests/mocks/Utils/windowstatestorage.h (+4/-0)
tests/plugins/Ubuntu/Gestures/CMakeLists.txt (+7/-1)
tests/plugins/Ubuntu/Gestures/GestureTest.cpp (+44/-0)
tests/plugins/Ubuntu/Gestures/GestureTest.h (+13/-1)
tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp (+0/-56)
tests/plugins/Ubuntu/Gestures/tst_TouchGestureArea.cpp (+270/-0)
tests/plugins/Ubuntu/Gestures/tst_TouchGestureArea.qml (+90/-0)
tests/plugins/Unity/Launcher/launchermodeltest.cpp (+1/-0)
tests/qmltests/Stages/tst_TabletStage.qml (+143/-0)
tests/qmltests/tst_OrientedShell.qml (+55/-11)
tests/utils/modules/Unity/Test/UnityTestCase.qml (+67/-11)
To merge this branch: bzr merge lp:~unity-team/unity8/side-stage-redesign
Reviewer Review Type Date Requested Status
Michael Zanetti (community) Abstain
Michael Terry Approve
Unity8 CI Bot continuous-integration Needs Fixing
PS Jenkins bot continuous-integration Pending
Lukáš Tinkl code-review Pending
Daniel d'Andrada Pending
Review via email: mp+288827@code.launchpad.net

This proposal supersedes a proposal from 2016-02-16.

Commit message

Sidestage load/unload redesign

Description of the change

Side stage redesign phase 1

 * Are there any related MPs required for this MP to build/function as expected? Please list.
https://code.launchpad.net/~nick-dedekind/unity-api/side-stage-redesign/+merge/279308
https://code.launchpad.net/~nick-dedekind/qtmir/side-stage-redesign/+merge/286191
 * Did you perform an exploratory manual test run of your code change and any related functionality?
Yes

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

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

 * If you changed the UI, has there been a design review?
yes

To post a comment you must log in.
Revision history for this message
Lukáš Tinkl (lukas-kde) wrote : Posted in a previous version of this proposal

Just a couple of inline code comments

review: Needs Fixing (code-review)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Nick Dedekind (nick-dedekind) wrote : Posted in a previous version of this proposal

Fixed review comments.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Nick Dedekind (nick-dedekind) wrote : Posted in a previous version of this proposal

Added tests

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

A testing silo would be convenient (since qtmir is involved and jenkins can't figure it out). But I can build myself in the meantime.

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

Ah, silo 1 already exists.

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

I started looking at this, but realized that my only tablet suitable for testing this is a manta, and it's busted these days. :( So maybe someone else should actually do the review. But just one quick comment:

+ const QString queryString = QStringLiteral("INSERT OR REPLACE INTO stage (appId, stage) values ('%1', '%2');")
+ .arg(appId)
+ .arg((int)stage);

I know that appIds are not the wild west of string input, but it makes me very nervous to see an unescaped sprintf fed into SQL. Is there an easy QString call or similar that we can make to give us 100% future proof peace of mind?

I mean, I assume the appId rules are sane -- only alphanumeric and hyphen maybe? But is u8 actually enforcing that, or are we just trusting that other components have? Wouldn't hurt to either add a comment here explaining exactly why we trust this input or simply escape it ourselves.

Same for getStage right below this.

Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal

FAILED: Continuous integration, rev:2040
https://unity8-jenkins.ubuntu.com/job/lp-unity8-1-ci/21/
Executed test runs:

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

review: Needs Fixing (continuous-integration)
Revision history for this message
Nick Dedekind (nick-dedekind) wrote : Posted in a previous version of this proposal

> I started looking at this, but realized that my only tablet suitable for
> testing this is a manta, and it's busted these days. :( So maybe someone
> else should actually do the review. But just one quick comment:
>
> + const QString queryString = QStringLiteral("INSERT OR REPLACE INTO stage
> (appId, stage) values ('%1', '%2');")
> + .arg(appId)
> + .arg((int)stage);
>
> I know that appIds are not the wild west of string input, but it makes me very
> nervous to see an unescaped sprintf fed into SQL. Is there an easy QString
> call or similar that we can make to give us 100% future proof peace of mind?
>
> I mean, I assume the appId rules are sane -- only alphanumeric and hyphen
> maybe? But is u8 actually enforcing that, or are we just trusting that other
> components have? Wouldn't hurt to either add a comment here explaining
> exactly why we trust this input or simply escape it ourselves.
>
> Same for getStage right below this.

I've added some string sanitising to the sql input. Removed the offensive chars rather escaping them.

Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal

FAILED: Continuous integration, rev:2041
https://unity8-jenkins.ubuntu.com/job/lp-unity8-1-ci/104/
Executed test runs:

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

review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Albert Astals Cid (aacid) wrote : Posted in a previous version of this proposal

plugins/Ubuntu/Gestures/TouchGestureArea.cpp UNKNOWN *No copyright*
plugins/Ubuntu/Gestures/TouchGestureArea.h UNKNOWN *No copyright*
qml/Stages/SideStage.qml UNKNOWN *No copyright*

Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal

FAILED: Continuous integration, rev:2041
https://unity8-jenkins.ubuntu.com/job/lp-unity8-1-ci/104/
Executed test runs:

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

review: Needs Fixing (continuous-integration)
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal

FAILED: Continuous integration, rev:2041
https://unity8-jenkins.ubuntu.com/job/lp-unity8-1-ci/104/
Executed test runs:

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

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

About the TouchRegistry change: Please write a regression test to cover this fix and please also propose it to the TouchRegistry copy in ubuntu-ui-toolkit. We will soon move to use it (s/DirectionalDragArea/SwipeArea) so we will need the fix there as well.

I feared such reentrancy issues and was considering s/sendEvent/postEvent throughout TouchRegistry because of that, but ended up never doing so...

review: Needs Fixing
Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

In TouchGestureArea.h:

"""
Q_PROPERTY(int minimumTouchPoints READ minimumTouchPoints WRITE setMinimumTouchPoints NOTIFY minimumTouchPointsChanged)
Q_PROPERTY(int maximumTouchPoints READ maximumTouchPoints WRITE setMaximumTouchPoints NOTIFY maximumTouchPointsChanged)
"""

Doing that way would save you from the boredom of writing down standard getters and setters yourself:

Q_PROPERTY(int minimumTouchPoints MEMBER m_minimumTouchPoints NOTIFY minimumTouchPointsChanged)
Q_PROPERTY(int maximumTouchPoints MEMBER m_maximumTouchPoints NOTIFY maximumTouchPointsChanged)

Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

Please write some documentation on TouchGestureArea class (eg, why use it instead of MultiPointTouchArea).

copy-and-paste leftover in TouchGestureArea.h:

"""
    // Describes the state of the directional drag gesture.
"""

review: Needs Fixing
Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

TouchGestureArea.cpp:

"""
TouchGestureArea::Status intenralStatusToGestureStatus(int internalStatus) {
"""

Typo: "intenral"

review: Needs Fixing
Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

Also some tests for TouchGestureArea would be nice. Exercising minimum/maximum touch points and touch ownership. Logic in this class is not trivial...

Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

TouchGestureArea is an interesting class. We might eventually move it to uitk just like we did for DDA...

Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

TouchGestureArea:

I think a better name for InternalStatus::Undecided would be InternalStatus::WaitingForMoreTouches.

You effectively split DDA's original Undecided into WaitingForMoreTouches and WaitingForOwnership, as in both states you're effectively undecided on whether those touches are performing the gesture you're intersted in or not (aka doing gesture recognition).

Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

Actually TouchGestureArea::touchEvent_undecided might never get called as from the moment you ignore the TouchBegin, from that point onwards you will only get touch updates via UnowedTouchEvents.

-----

In TouchGestureArea::unownedTouchEvent_undecided:

"""
    event->ignore();
"""

This call doesn't make sense and has no effect there.

review: Needs Fixing
Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

In TouchGestureArea::touchOwnershipEvent:

"""
TouchRegistry::instance()->addTouchWatcher(touchId, this);
m_watchedTouches.insert(touchId);
"""

There's no point in watching touches that you own. You will already get its updates from regular QTouchEvents.

review: Needs Fixing
Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

TouchGestureArea.cpp:

"""
GestureTouchPoint* gtp = static_cast<GestureTouchPoint*>(m_liveTouchPoints.value(touchId));
"""

I see no reason for this cast.

review: Needs Fixing
Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

In TouchGestureArea::touchPoint_at:

"""
    return static_cast<GestureTouchPoint*>((q->m_cachedTouchPoints.begin()+index).value());

"""

Another unnecessary cast.

review: Needs Fixing
Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

"make tryTabletStage" is broken.

review: Needs Fixing
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal

FAILED: Continuous integration, rev:2043
https://unity8-jenkins.ubuntu.com/job/lp-unity8-1-ci/163/
Executed test runs:

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

review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Albert Astals Cid (aacid) wrote : Posted in a previous version of this proposal

Text conflict in plugins/Utils/CMakeLists.txt
Text conflict in plugins/Utils/plugin.cpp
Text conflict in qml/Shell.qml
Text conflict in qml/Stages/AbstractStage.qml
Text conflict in qml/Stages/TabletStage.qml
Text conflict in tests/mocks/Utils/CMakeLists.txt
Text conflict in tests/mocks/Utils/plugin.cpp
7 conflicts encountered.

Revision history for this message
Nick Dedekind (nick-dedekind) wrote : Posted in a previous version of this proposal

> In TouchGestureArea::touchOwnershipEvent:
>
> """
> TouchRegistry::instance()->addTouchWatcher(touchId, this);
> m_watchedTouches.insert(touchId);
> """
>
> There's no point in watching touches that you own. You will already get its
> updates from regular QTouchEvents.

I've found that sometimes we will get an event stolen and we don't get the "touch leave" event through. I can't remember the circumstances.
The component caches the last touch points all the time, in case we get a momentary release of one or more of the points. If I don't monitor all the points from start to finish, we can end up with "zombie points" in the cache map. So I'm monitoring all touches from start to finish of the gesture.

Revision history for this message
Nick Dedekind (nick-dedekind) wrote : Posted in a previous version of this proposal

review comments fixed.

Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Nick Dedekind (nick-dedekind) wrote : Posted in a previous version of this proposal

> "make tryTabletStage" is broken.

Broken how? Seems to be working on my side. Was getting a seg fault but clean compiled and it's fine now :/

Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Nick Dedekind (nick-dedekind) wrote : Posted in a previous version of this proposal

Tests added.

Revision history for this message
Nick Dedekind (nick-dedekind) wrote : Posted in a previous version of this proposal

> In TouchGestureArea.h:
>
> """
> Q_PROPERTY(int minimumTouchPoints READ minimumTouchPoints WRITE
> setMinimumTouchPoints NOTIFY minimumTouchPointsChanged)
> Q_PROPERTY(int maximumTouchPoints READ maximumTouchPoints WRITE
> setMaximumTouchPoints NOTIFY maximumTouchPointsChanged)
> """
>
> Doing that way would save you from the boredom of writing down standard
> getters and setters yourself:
>
> Q_PROPERTY(int minimumTouchPoints MEMBER m_minimumTouchPoints NOTIFY
> minimumTouchPointsChanged)
> Q_PROPERTY(int maximumTouchPoints MEMBER m_maximumTouchPoints NOTIFY
> maximumTouchPointsChanged)

I did this, but reverted because I needed the set for tests.

Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

> > "make tryTabletStage" is broken.
>
> Broken how? Seems to be working on my side. Was getting a seg fault but clean
> compiled and it's fine now :/

It's working now.

Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

Can't see any application in "make tryShell".

review: Needs Fixing
Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

In qml/Stages/TabletSideStageTouchGesture.qml:

" * Copyright (C) 2014-2015 Canonical, Ltd."

Should be just 2016.

review: Needs Fixing
Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

In qml/Stages/TabletSideStageTouchGesture.qml:

"""
property bool enableDrag: true
"""

Doesn't seem to ever change to false (at least couldn't find code doing it). So maybe this is redundant now (and thus could be removed)?

"""
    property bool wasRecognisedPress: false
    property bool wasRecognisedDrag: false
    readonly property bool recognisedPress: status == TouchGestureArea.Recognized &&
                                            touchPoints.length >= minimumTouchPoints &&
                                            touchPoints.length <= maximumTouchPoints
    readonly property bool recognisedDrag: wasRecognisedPress && dragging
"""

They all seem to be private properties. If so, please move them to the priv object.

review: Needs Fixing
Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

In qml/Stages/TabletSideStageTouchGesture.qml:

"""
    signal drag
"""

It's named like a function. Suggestions: "signal dragStarted" or "property bool dragging" (like in DirectionalDragArea/SwipeArea)

Also couldn't find any code using it. Maybe we could just remove it?

"""
    signal drop
"""

It's named like a function. Suggestion: "dropped" (would a "property bool dragging" cover both signals?). Also the only code I found using it was TabletSideStageTouchGesture. Should it be privatized somehow?

"""
    signal cancel
"""

It's named like a function. Also the only code I found using it was TabletSideStageTouchGesture, like with the "drop" signal.

review: Needs Fixing
Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

"""
property bool surfaceDragging: triGestureArea.recognisedDrag
"""

Ok so recognisedDrag is being used externally. Sorry for that.

Revision history for this message
Nick Dedekind (nick-dedekind) wrote : Posted in a previous version of this proposal

> In qml/Stages/TabletSideStageTouchGesture.qml:
>
> """
> property bool enableDrag: true
> """
>
> Doesn't seem to ever change to false (at least couldn't find code doing it).
> So maybe this is redundant now (and thus could be removed)?

It's being used by the tutorial branch associated with this one.
https://code.launchpad.net/~nick-dedekind/unity8/side-stage-redesign-tutorial/+merge/281506

>
> """
> property bool wasRecognisedPress: false
> property bool wasRecognisedDrag: false
> readonly property bool recognisedPress: status ==
> TouchGestureArea.Recognized &&
> touchPoints.length >=
> minimumTouchPoints &&
> touchPoints.length <=
> maximumTouchPoints
> readonly property bool recognisedDrag: wasRecognisedPress && dragging
> """
>
> They all seem to be private properties. If so, please move them to the priv
> object.

Privatized the wasRecognised[Drag/Press]

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Nick Dedekind (nick-dedekind) wrote : Posted in a previous version of this proposal

> In qml/Stages/TabletSideStageTouchGesture.qml:
>
> """
> signal drag
> """
>
> It's named like a function. Suggestions: "signal dragStarted" or "property
> bool dragging" (like in DirectionalDragArea/SwipeArea)
>
> Also couldn't find any code using it. Maybe we could just remove it?
>
> """
> signal drop
> """
>
> It's named like a function. Suggestion: "dropped" (would a "property bool
> dragging" cover both signals?). Also the only code I found using it was
> TabletSideStageTouchGesture. Should it be privatized somehow?
>
>
> """
> signal cancel
> """
>
> It's named like a function. Also the only code I found using it was
> TabletSideStageTouchGesture, like with the "drop" signal.

Changed the names.
I'm using clicked, dropped & cancelled signals in the tutorial branch. Drag signal is for API completeness (drop without a drag isn't great API).

Revision history for this message
Nick Dedekind (nick-dedekind) wrote : Posted in a previous version of this proposal

> Can't see any application in "make tryShell".

Fixed

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

"""
    void topAreaRecevesOwnershipFirstWithEqualPoints();
    void topAreaRecevesOwnershipFirstWithMorePoints();
"""

typo:
s/Receves/Receives

Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

In Shell.qml, you're reformatting finishStartUpTimer for no good reason. That unnecessarily bloats the diff and increases the chances for conflicts with other MPs when merging trunk etc.

Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

In Shell.qml:

In the supportedOrientations binding, you're merging two "if" clauses for no good reason. I think the end result is actually worse as the code is less readable.

review: Needs Fixing
Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

Again on Shell.qml diff bloat: you added a newline in applicationsDisplayLoader all by itself. If you at least were changing the code around it....

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

Unnecessary reformatting also in qml/Stages/PhoneStage.qml

Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

qml/Stages/SideStage.qml is misssing a copyright header

review: Needs Fixing
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

Getting lots of false negatives with the side stage drag handle. eg: lay a finger over it and hold it for some 2 seconds. then drag rightwards. You're no longer dragging the handle.

You have to configure this drag handle for immediate recognition as this is a visible target. There's no point in doing gesture recognition for a target the user can see.

review: Needs Fixing
Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

1 - launch a main-stage app
2 - go back to dash
3 - launch another main-stage app
4 - drag that app to the side-stage
5 - hide the side-stage (drag it to the right)
6 - perform a long drag from the right edge (to get to the spread). Do it slowly so you can better see the animation details

untiy8-dash (the top-most app when you perform the right-edge drag) will move far too much to the right initially, not aligning nicely with the other 2 application in the dash.

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

"""
- void tst_TouchGestureArea::topAreaRecevesOwnershipFirstWithEqualPoints()
+ void tst_TouchGestureArea::topAreaReicevesOwnershipFirstWithEqualPoints()
"""

:-D

Tried to fix the typo but ended up making another typo!

review: Needs Fixing
Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

"""
Text conflict in CMakeLists.txt
Text conflict in debian/control
Text conflict in qml/Stages/ApplicationWindow.qml
Text conflict in tests/mocks/Unity/Application/MirSurfaceItem.h
"""

A bunch of conflicts with latest trunk now.

review: Needs Fixing
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Nick Dedekind (nick-dedekind) wrote : Posted in a previous version of this proposal

Fixed the stage dragging bug.

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

Went to test on my Nexus 7. It seems to interpret a single-finger drag as a 3-finger drag. Maybe you forgot to change back the minimum touch count again?

By the way, there *must* be a way to play with dragging apps around in "make tryFoo" tests which doesn't involve tweaking the code (like setting minimumTouchCount to 1). Something like when you have some modifier (like Ctrl or Shift or Right-Alt) while the mouse is pressed MouseTouchAdaptor will generate 3 separate touch points instead of a single one out of the mouse event.

review: Needs Fixing
Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

Went to test on my Nexus 7. It seems to interpret a single-finger drag as a 3-finger drag. Maybe you forgot to change back the minimum touch count again?

By the way, there *must* be a way to play with dragging apps around in "make tryFoo" tests which doesn't involve tweaking the code (like setting minimumTouchCount to 1). Something like when you have some modifier (like Ctrl or Shift or Right-Alt) while the mouse is pressed MouseTouchAdaptor will generate 3 separate touch points instead of a single one out of the mouse event.

review: Needs Fixing
Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

Copy-and-paste leftover: Copyright year in tst_TouchGestureArea.qml says 2013

Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

And this comment in tst_TouchGestureArea.qml as well:

"""
/*
  NB: If you change positions or sizes here make sure
      tst_PressedOutsideNotifier.cpp is updated accordingly
 */
"""

Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

tst_TouchGestureArea.cpp is not using QTest::touchEvent correctly.

Look at this sequence:

"""
QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());

QTest::touchEvent(m_view, m_device).press(1, touchPoint.toPoint());

QTest::touchEvent(m_view, m_device).press(2, touchPoint.toPoint());
"""

On each subsequent QTouchEvent you're forgetting about the previouly pressed touch points. So this sequence is invalid. It should be something like this:

"""
QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());

QTest::touchEvent(m_view, m_device)
    .move(0, touchPoint0)
    .press(1, touchPoint.toPoint());

QTest::touchEvent(m_view, m_device)
    .move(0, touchPoint0)
    .move(1, touchPoint1);
    .press(2, touchPoint.toPoint());
"""

Ie, a QTouchEvent must mention every single active touch point. See tst_DirectionalDragArea.cpp for some usage examples.

review: Needs Fixing
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Nick Dedekind (nick-dedekind) wrote : Posted in a previous version of this proposal

> Went to test on my Nexus 7. It seems to interpret a single-finger drag as a
> 3-finger drag. Maybe you forgot to change back the minimum touch count again?
>
> By the way, there *must* be a way to play with dragging apps around in "make
> tryFoo" tests which doesn't involve tweaking the code (like setting
> minimumTouchCount to 1). Something like when you have some modifier (like Ctrl
> or Shift or Right-Alt) while the mouse is pressed MouseTouchAdaptor will
> generate 3 separate touch points instead of a single one out of the mouse
> event.

Fixed.
I'll look into adding a modifier.

Revision history for this message
Nick Dedekind (nick-dedekind) wrote : Posted in a previous version of this proposal

> tst_TouchGestureArea.cpp is not using QTest::touchEvent correctly.
>
> Look at this sequence:
>
> """
> QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
>
> QTest::touchEvent(m_view, m_device).press(1, touchPoint.toPoint());
>
> QTest::touchEvent(m_view, m_device).press(2, touchPoint.toPoint());
> """
>
> On each subsequent QTouchEvent you're forgetting about the previouly pressed
> touch points. So this sequence is invalid. It should be something like this:
>
> """
> QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
>
> QTest::touchEvent(m_view, m_device)
> .move(0, touchPoint0)
> .press(1, touchPoint.toPoint());
>
> QTest::touchEvent(m_view, m_device)
> .move(0, touchPoint0)
> .move(1, touchPoint1);
> .press(2, touchPoint.toPoint());
> """
>
> Ie, a QTouchEvent must mention every single active touch point. See
> tst_DirectionalDragArea.cpp for some usage examples.

Yeah, forgot about this; i've done it before. used TouchEvent::stationary.

Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Nick Dedekind (nick-dedekind) wrote : Posted in a previous version of this proposal

> > Went to test on my Nexus 7. It seems to interpret a single-finger drag as a
> > 3-finger drag. Maybe you forgot to change back the minimum touch count
> again?
> >
> > By the way, there *must* be a way to play with dragging apps around in "make
> > tryFoo" tests which doesn't involve tweaking the code (like setting
> > minimumTouchCount to 1). Something like when you have some modifier (like
> Ctrl
> > or Shift or Right-Alt) while the mouse is pressed MouseTouchAdaptor will
> > generate 3 separate touch points instead of a single one out of the mouse
> > event.
>
> Fixed.
> I'll look into adding a modifier.

I've added a MouseTouchAdaptor tripress modifier. Control & Shift & Alt for 3 touch events. All three at same time.

Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

On 17/02/2016 08:32, Nick Dedekind wrote:
>>> Went to test on my Nexus 7. It seems to interpret a single-finger drag as a
>>> 3-finger drag. Maybe you forgot to change back the minimum touch count
>> again?
>>> By the way, there *must* be a way to play with dragging apps around in "make
>>> tryFoo" tests which doesn't involve tweaking the code (like setting
>>> minimumTouchCount to 1). Something like when you have some modifier (like
>> Ctrl
>>> or Shift or Right-Alt) while the mouse is pressed MouseTouchAdaptor will
>>> generate 3 separate touch points instead of a single one out of the mouse
>>> event.
>> Fixed.
>> I'll look into adding a modifier.
> I've added a MouseTouchAdaptor tripress modifier. Control & Shift & Alt for 3 touch events. All three at same time.

Wow, that was fast! Awesome! Will try it out. Thanks!

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

Ctrl+Shift+Alt for 3-finger touch emulation is working nicely.

There seems to be something wrong in the Unity.Application mock though.

1 - run "make tryOrientedShell"
2 - Select "manta" in the "Device Name" field
3 - Unlock screen
4 - launch dialer-app
5 - 3-finger drag it onto the side stage

Expected outcome:
Dialer goes to side stage

Actual outcome:

Nothing happens and instead I get this in the terminal:
"""
qml/Stages/TabletStage.qml:197: TypeError: Cannot assign to read-only property "stage"
"""

review: Needs Fixing
Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

Again on the 3-finger touch emulation, I think it would be good to advertise it in our CODING file, under the "Running tests" section. As it's one of those things you cannot discover by yourself.

Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

The side stage help graphics appears rapidly (shows up and quickly fades out) when you do a right-edge swipe to go to the spread mode.

https://www.dropbox.com/s/x9a9hso795dem7p/sidestage_help_showing_up.MP4?dl=0

review: Needs Fixing
Revision history for this message
Nick Dedekind (nick-dedekind) wrote : Posted in a previous version of this proposal

> Ctrl+Shift+Alt for 3-finger touch emulation is working nicely.
>
> There seems to be something wrong in the Unity.Application mock though.
>
> 1 - run "make tryOrientedShell"
> 2 - Select "manta" in the "Device Name" field
> 3 - Unlock screen
> 4 - launch dialer-app
> 5 - 3-finger drag it onto the side stage
>
> Expected outcome:
> Dialer goes to side stage
>
> Actual outcome:
>
> Nothing happens and instead I get this in the terminal:
> """
> qml/Stages/TabletStage.qml:197: TypeError: Cannot assign to read-only property
> "stage"
> """

Think you may need to remoc or install the unity-api that this branch comes with?

Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

On 17/02/2016 14:43, Nick Dedekind wrote:
>> Ctrl+Shift+Alt for 3-finger touch emulation is working nicely.
>>
>> There seems to be something wrong in the Unity.Application mock though.
>>
>> 1 - run "make tryOrientedShell"
>> 2 - Select "manta" in the "Device Name" field
>> 3 - Unlock screen
>> 4 - launch dialer-app
>> 5 - 3-finger drag it onto the side stage
>>
>> Expected outcome:
>> Dialer goes to side stage
>>
>> Actual outcome:
>>
>> Nothing happens and instead I get this in the terminal:
>> """
>> qml/Stages/TabletStage.qml:197: TypeError: Cannot assign to read-only property
>> "stage"
>> """
> Think you may need to remoc or install the unity-api that this branch comes with?

Rebuilding the untiy8 branch from scratch did the trick. Thanks.

Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Nick Dedekind (nick-dedekind) wrote : Posted in a previous version of this proposal

> The side stage help graphics appears rapidly (shows up and quickly fades out)
> when you do a right-edge swipe to go to the spread mode.
>
> https://www.dropbox.com/s/x9a9hso795dem7p/sidestage_help_showing_up.MP4?dl=0

The side stage fades out when not available.
I've changed it to use the spreadView.active property rather than the phase; so it should disappear sooner now.

Revision history for this message
Nick Dedekind (nick-dedekind) wrote : Posted in a previous version of this proposal

> The side stage help graphics appears rapidly (shows up and quickly fades out)
> when you do a right-edge swipe to go to the spread mode.
>
> https://www.dropbox.com/s/x9a9hso795dem7p/sidestage_help_showing_up.MP4?dl=0

The side stage fades out when not available.
I've changed it to use the spreadView.active property rather than the phase; so it should disappear sooner now.

Revision history for this message
Nick Dedekind (nick-dedekind) wrote : Posted in a previous version of this proposal

> Again on the 3-finger touch emulation, I think it would be good to advertise
> it in our CODING file, under the "Running tests" section. As it's one of those
> things you cannot discover by yourself.

I've added it under "Running Unity 8 on your desktop". There's already a note on mouse emu there.

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

It's getting there...

In TouchGestureArea.cpp:

If you insist in watching touches you already own (as in QQuickWindow already sends updates to you), would it be possible to at least updateTouchPoints() only once per QTouchEvent? As opposed to in both touchEvent() and unownedTouchEvent()?

Also I think it's important to have a comment in the code (probably a TODO or FIXME) explaining why the heck do we watch touch points we already own. It doesn't make sense unless you're working around some issue. Eventually this issue should be properly understood.

review: Needs Information
Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

Missing copyright header:
plugins/Ubuntu/Gestures/TouchGestureArea.*

s/2014/2016 in tests/plugins/Ubuntu/Gestures/tst_TouchGestureArea.qml

review: Needs Fixing
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Nick Dedekind (nick-dedekind) wrote : Posted in a previous version of this proposal

> It's getting there...
>
> In TouchGestureArea.cpp:
>
> If you insist in watching touches you already own (as in QQuickWindow already
> sends updates to you), would it be possible to at least updateTouchPoints()
> only once per QTouchEvent? As opposed to in both touchEvent() and
> unownedTouchEvent()?

I think we can ignore unowned touch updates unless they're presses or releases.
I'll have to test on device.

>
> Also I think it's important to have a comment in the code (probably a TODO or
> FIXME) explaining why the heck do we watch touch points we already own. It
> doesn't make sense unless you're working around some issue. Eventually this
> issue should be properly understood.

Done.

Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

Small visual issue:

* Open something that can go fullscreen on the main stage (e.g. gallery with picture)
* Open a side stage app

Now do a slow long right edge swipe. When the side stage app starts moving, it will jump upwards.

review: Needs Fixing
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Nick Dedekind (nick-dedekind) wrote : Posted in a previous version of this proposal

> Small visual issue:
>
> * Open something that can go fullscreen on the main stage (e.g. gallery with
> picture)
> * Open a side stage app
>
> Now do a slow long right edge swipe. When the side stage app starts moving, it
> will jump upwards.

Arg. little bit complicated in there but I think i got it sorted. :)

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

Looks like some bitrotting happened overtime.

- run "make tryTabletStage"
- launch a bunch of apps
- go to spread
- tap on an application

during the animation to bring the selected application to foreground, the other apps in the spread are kept in front of that selected one (Z ordering issue).

------------------------

Some miscellaneous clean up (revisions 2217 to 2221):
lp:~dandrader/unity8/side-stage-redesign

review: Needs Fixing
Revision history for this message
Nick Dedekind (nick-dedekind) wrote : Posted in a previous version of this proposal

> Looks like some bitrotting happened overtime.
>
> - run "make tryTabletStage"
> - launch a bunch of apps
> - go to spread
> - tap on an application
>
> during the animation to bring the selected application to foreground, the
> other apps in the spread are kept in front of that selected one (Z ordering
> issue).

Fixed.

>
> ------------------------
>
> Some miscellaneous clean up (revisions 2217 to 2221):
> lp:~dandrader/unity8/side-stage-redesign

Merged

Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

Thanks

review: Approve
Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

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

* Did CI run pass? If not, please explain why.
No, due to unity-api dependency.

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

Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

OrientedShell::test_portraitOnlyAppInSideStage needs to be updated (it's failing):
http://pastebin.ubuntu.com/15340982/

review: Needs Fixing
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Daniel d'Andrada (dandrader) : Posted in a previous version of this proposal
review: Approve
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
2275. By Nick Dedekind

Sidestage load/unload redesign

2276. By Michał Sawicz

Merge lp:~unity-team/unity8/side-stage-redesign

Revision history for this message
Michael Terry (mterry) wrote :

Carrying approval over from previous MP

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

there went something bad with the last merge it seems

review: Needs Fixing
2277. By Michael Zanetti

fix bad merge

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

> there went something bad with the last merge it seems

ok. fixed it myself

review: Abstain
2278. By Nick Dedekind

merged with trunk

2279. By Nick Dedekind

updated indicator model mock to fix warnings

2280. By Nick Dedekind

fixed touchPress

2281. By Nick Dedekind

resolved conflict

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2016-03-16 11:49:35 +0000
3+++ CMakeLists.txt 2016-03-16 11:49:36 +0000
4@@ -57,7 +57,7 @@
5 find_package(Qt5Concurrent 5.4 REQUIRED)
6 find_package(Qt5Sql 5.4 REQUIRED)
7
8-pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=13)
9+pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=14)
10 pkg_check_modules(GEONAMES REQUIRED geonames>=0.2)
11 pkg_check_modules(GIO REQUIRED gio-2.0>=2.32)
12 pkg_check_modules(GLIB REQUIRED glib-2.0>=2.32)
13
14=== modified file 'CODING'
15--- CODING 2015-06-23 07:51:27 +0000
16+++ CODING 2016-03-16 11:49:36 +0000
17@@ -54,6 +54,8 @@
18 Notes
19 -----
20 - to navigate, utilize the mouse left button as you would your finger
21+- a three point drag (for side stage operations) can be performed by using
22+ the Shift+Control+Alt (all together) keyboard modifiers while using the mouse.
23 - to get the translations work, currently you have to do make install to
24 put the *.mo files into correct structure. We'll look for a better solution
25 later.
26
27=== modified file 'debian/control'
28--- debian/control 2016-03-16 11:49:35 +0000
29+++ debian/control 2016-03-16 11:49:36 +0000
30@@ -30,7 +30,7 @@
31 libqt5xmlpatterns5-dev,
32 libsystemsettings-dev,
33 libudev-dev,
34- libunity-api-dev (>= 7.107),
35+ libunity-api-dev (>= 7.108),
36 libusermetricsoutput1-dev,
37 # Need those X11 libs touch emulation from mouse events in manual QML tests on a X11 desktop
38 libx11-dev[!armhf],
39@@ -102,7 +102,7 @@
40 qml-module-qt-labs-folderlistmodel,
41 qml-module-qtquick-xmllistmodel,
42 qml-module-qtsysteminfo,
43- qtdeclarative5-qtmir-plugin (>= 0.4.5),
44+ qtdeclarative5-qtmir-plugin (>= 0.4.8),
45 qtdeclarative5-ubuntu-telephony0.1,
46 qtdeclarative5-ubuntu-web-plugin,
47 ubuntu-system-settings,
48@@ -131,7 +131,7 @@
49 qtdeclarative5-ubuntu-ui-toolkit-plugin (>= 1.3.1845) | qtdeclarative5-ubuntu-ui-toolkit-plugin-gles (>= 1.3.1845),
50 qtdeclarative5-unity-notifications-plugin (>= 0.1.2) | unity-notifications-impl,
51 ubuntu-thumbnailer-impl-0,
52- unity-application-impl-13,
53+ unity-application-impl-14,
54 unity-notifications-impl-3,
55 unity-plugin-scopes | unity-scopes-impl,
56 unity-scopes-impl-10,
57@@ -177,7 +177,7 @@
58 Depends: ${misc:Depends},
59 ${shlibs:Depends},
60 Provides: unity-application-impl,
61- unity-application-impl-13,
62+ unity-application-impl-14,
63 Replaces: unity8-autopilot (<< 8.02+15.04.20150422-0ubuntu1)
64 Description: Fake environment for running Unity 8 shell
65 Provides fake implementations of some QML modules used by Unity 8 shell
66
67=== modified file 'libs/UbuntuGestures/Timer.h'
68--- libs/UbuntuGestures/Timer.h 2015-04-24 13:19:24 +0000
69+++ libs/UbuntuGestures/Timer.h 2016-03-16 11:49:36 +0000
70@@ -35,6 +35,11 @@
71 virtual int interval() const = 0;
72 virtual void setInterval(int msecs) = 0;
73 virtual void start() { m_isRunning = true; }
74+ virtual void start(int msecs)
75+ {
76+ setInterval(msecs);
77+ start();
78+ }
79 virtual void stop() { m_isRunning = false; }
80 bool isRunning() const { return m_isRunning; }
81 virtual bool isSingleShot() const = 0;
82
83=== modified file 'libs/UbuntuGestures/TouchRegistry.cpp'
84--- libs/UbuntuGestures/TouchRegistry.cpp 2015-09-14 09:11:08 +0000
85+++ libs/UbuntuGestures/TouchRegistry.cpp 2016-03-16 11:49:36 +0000
86@@ -359,10 +359,6 @@
87
88 void TouchRegistry::requestTouchOwnership(int id, QQuickItem *candidate)
89 {
90- #if TOUCHREGISTRY_DEBUG
91- UG_DEBUG << "requestTouchOwnership id " << id << "candidate" << candidate;
92- #endif
93-
94 Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(id);
95 if (!touchInfo) { qFatal("TouchRegistry: Failed to find TouchInfo"); }
96
97@@ -379,6 +375,9 @@
98 break;
99 }
100 }
101+ #if TOUCHREGISTRY_DEBUG
102+ UG_DEBUG << "requestTouchOwnership id " << id << "candidate" << candidate << "index: " << candidateIndex;
103+ #endif
104
105 // add it as a candidate if not present yet
106 if (candidateIndex < 0) {
107@@ -533,16 +532,22 @@
108 << " gained) to candidate" << candidates[0].item;
109 #endif
110
111+ // need to take a copy of the item list in case
112+ // we call back in to remove candidate during the lost ownership event.
113+ QList<QPointer<QQuickItem>> items;
114+ Q_FOREACH(const CandidateInfo& info, candidates) {
115+ items << info.item;
116+ }
117+
118 TouchOwnershipEvent gainedOwnershipEvent(id, true /*gained*/);
119- QCoreApplication::sendEvent(candidates[0].item, &gainedOwnershipEvent);
120-
121+ QCoreApplication::sendEvent(items[0], &gainedOwnershipEvent);
122
123 TouchOwnershipEvent lostOwnershipEvent(id, false /*gained*/);
124- for (int i = 1; i < candidates.count(); ++i) {
125+ for (int i = 1; i < items.count(); ++i) {
126 #if TOUCHREGISTRY_DEBUG
127- UG_DEBUG << "sending TouchWonershipEvent(id =" << id << " lost) to candidate"
128- << candidates[i].item;
129+ UG_DEBUG << "sending TouchOwnershipEvent(id =" << id << " lost) to candidate"
130+ << items[i];
131 #endif
132- QCoreApplication::sendEvent(candidates[i].item, &lostOwnershipEvent);
133+ QCoreApplication::sendEvent(items[i], &lostOwnershipEvent);
134 }
135 }
136
137=== added file 'plugins/Cursor/Cursor.qmltypes'
138--- plugins/Cursor/Cursor.qmltypes 1970-01-01 00:00:00 +0000
139+++ plugins/Cursor/Cursor.qmltypes 2016-03-16 11:49:36 +0000
140@@ -0,0 +1,78 @@
141+import QtQuick.tooling 1.1
142+
143+// This file describes the plugin-supplied types contained in the library.
144+// It is used for QML tooling purposes only.
145+//
146+// This file was auto-generated by:
147+// 'qmlplugindump -notrelocatable Cursor 1.0 plugins'
148+
149+Module {
150+ Component {
151+ name: "MirMousePointerInterface"
152+ defaultProperty: "data"
153+ prototype: "QQuickItem"
154+ Property { name: "cursorName"; type: "string"; isReadonly: true }
155+ Property { name: "themeName"; type: "string"; isReadonly: true }
156+ Property { name: "hotspotX"; type: "double"; isReadonly: true }
157+ Property { name: "hotspotY"; type: "double"; isReadonly: true }
158+ Signal {
159+ name: "cursorNameChanged"
160+ Parameter { name: "name"; type: "string" }
161+ }
162+ Signal {
163+ name: "themeNameChanged"
164+ Parameter { name: "name"; type: "string" }
165+ }
166+ Signal {
167+ name: "hotspotXChanged"
168+ Parameter { name: "value"; type: "double" }
169+ }
170+ Signal {
171+ name: "hotspotYChanged"
172+ Parameter { name: "value"; type: "double" }
173+ }
174+ Method {
175+ name: "handleMouseEvent"
176+ Parameter { name: "timestamp"; type: "ulong" }
177+ Parameter { name: "movement"; type: "QPointF" }
178+ Parameter { name: "buttons"; type: "Qt::MouseButtons" }
179+ Parameter { name: "modifiers"; type: "Qt::KeyboardModifiers" }
180+ }
181+ Method {
182+ name: "handleWheelEvent"
183+ Parameter { name: "timestamp"; type: "ulong" }
184+ Parameter { name: "angleDelta"; type: "QPoint" }
185+ Parameter { name: "modifiers"; type: "Qt::KeyboardModifiers" }
186+ }
187+ }
188+ Component {
189+ name: "MousePointer"
190+ defaultProperty: "data"
191+ prototype: "MirMousePointerInterface"
192+ exports: ["Cursor/MousePointer 1.0"]
193+ exportMetaObjectRevisions: [0]
194+ Signal {
195+ name: "pushedLeftBoundary"
196+ Parameter { name: "amount"; type: "double" }
197+ Parameter { name: "buttons"; type: "Qt::MouseButtons" }
198+ }
199+ Signal {
200+ name: "pushedRightBoundary"
201+ Parameter { name: "amount"; type: "double" }
202+ Parameter { name: "buttons"; type: "Qt::MouseButtons" }
203+ }
204+ Method {
205+ name: "handleMouseEvent"
206+ Parameter { name: "timestamp"; type: "ulong" }
207+ Parameter { name: "movement"; type: "QPointF" }
208+ Parameter { name: "buttons"; type: "Qt::MouseButtons" }
209+ Parameter { name: "modifiers"; type: "Qt::KeyboardModifiers" }
210+ }
211+ Method {
212+ name: "handleWheelEvent"
213+ Parameter { name: "timestamp"; type: "ulong" }
214+ Parameter { name: "angleDelta"; type: "QPoint" }
215+ Parameter { name: "modifiers"; type: "Qt::KeyboardModifiers" }
216+ }
217+ }
218+}
219
220=== added file 'plugins/GlobalShortcut/GlobalShortcut.qmltypes'
221--- plugins/GlobalShortcut/GlobalShortcut.qmltypes 1970-01-01 00:00:00 +0000
222+++ plugins/GlobalShortcut/GlobalShortcut.qmltypes 2016-03-16 11:49:36 +0000
223@@ -0,0 +1,31 @@
224+import QtQuick.tooling 1.1
225+
226+// This file describes the plugin-supplied types contained in the library.
227+// It is used for QML tooling purposes only.
228+//
229+// This file was auto-generated by:
230+// 'qmlplugindump -notrelocatable GlobalShortcut 1.0 plugins'
231+
232+Module {
233+ Component {
234+ name: "GlobalShortcut"
235+ defaultProperty: "data"
236+ prototype: "QQuickItem"
237+ exports: ["GlobalShortcut/GlobalShortcut 1.0"]
238+ exportMetaObjectRevisions: [0]
239+ Property { name: "shortcut"; type: "QVariant" }
240+ Property { name: "active"; type: "bool" }
241+ Signal {
242+ name: "shortcutChanged"
243+ Parameter { name: "shortcut"; type: "QVariant" }
244+ }
245+ Signal {
246+ name: "triggered"
247+ Parameter { name: "shortcut"; type: "string" }
248+ }
249+ Signal {
250+ name: "activeChanged"
251+ Parameter { name: "active"; type: "bool" }
252+ }
253+ }
254+}
255
256=== modified file 'plugins/Ubuntu/Gestures/CMakeLists.txt'
257--- plugins/Ubuntu/Gestures/CMakeLists.txt 2015-05-27 09:37:34 +0000
258+++ plugins/Ubuntu/Gestures/CMakeLists.txt 2016-03-16 11:49:36 +0000
259@@ -11,6 +11,7 @@
260 PressedOutsideNotifier.cpp
261 TouchDispatcher.cpp
262 TouchGate.cpp
263+ TouchGestureArea.cpp
264 )
265
266 add_definitions(-DUBUNTUGESTURESQML_LIBRARY)
267
268=== modified file 'plugins/Ubuntu/Gestures/DirectionalDragArea.cpp'
269--- plugins/Ubuntu/Gestures/DirectionalDragArea.cpp 2015-12-16 18:28:21 +0000
270+++ plugins/Ubuntu/Gestures/DirectionalDragArea.cpp 2016-03-16 11:49:36 +0000
271@@ -606,6 +606,9 @@
272 Q_EMIT q->pressedChanged(true);
273 break;
274 case Recognized:
275+ if (oldStatus == WaitingForTouch) { // for immediate recognition
276+ Q_EMIT q->pressedChanged(true);
277+ }
278 Q_EMIT q->draggingChanged(true);
279 break;
280 default:
281
282=== modified file 'plugins/Ubuntu/Gestures/Gestures.qmltypes'
283--- plugins/Ubuntu/Gestures/Gestures.qmltypes 2015-02-13 09:01:16 +0000
284+++ plugins/Ubuntu/Gestures/Gestures.qmltypes 2016-03-16 11:49:36 +0000
285@@ -34,7 +34,8 @@
286 "Leftwards": 1,
287 "Downwards": 2,
288 "Upwards": 3,
289- "Horizontal": 4
290+ "Horizontal": 4,
291+ "Vertical": 5
292 }
293 }
294 Method {
295@@ -59,14 +60,6 @@
296 prototype: "QQuickItem"
297 exports: ["Ubuntu.Gestures/DirectionalDragArea 0.1"]
298 exportMetaObjectRevisions: [0]
299- Enum {
300- name: "Status"
301- values: {
302- "WaitingForTouch": 0,
303- "Undecided": 1,
304- "Recognized": 2
305- }
306- }
307 Property { name: "direction"; type: "Direction::Type" }
308 Property { name: "distance"; type: "double"; isReadonly: true }
309 Property { name: "sceneDistance"; type: "double"; isReadonly: true }
310@@ -74,27 +67,22 @@
311 Property { name: "touchY"; type: "double"; isReadonly: true }
312 Property { name: "touchSceneX"; type: "double"; isReadonly: true }
313 Property { name: "touchSceneY"; type: "double"; isReadonly: true }
314- Property { name: "status"; type: "Status"; isReadonly: true }
315 Property { name: "dragging"; type: "bool"; isReadonly: true }
316- Property { name: "maxDeviation"; type: "double" }
317- Property { name: "wideningAngle"; type: "double" }
318- Property { name: "distanceThreshold"; type: "double" }
319- Property { name: "minSpeed"; type: "double" }
320- Property { name: "maxSilenceTime"; type: "int" }
321- Property { name: "compositionTime"; type: "int" }
322+ Property { name: "pressed"; type: "bool"; isReadonly: true }
323+ Property { name: "immediateRecognition"; type: "bool" }
324 Signal {
325 name: "directionChanged"
326 Parameter { name: "direction"; type: "Direction::Type" }
327 }
328 Signal {
329- name: "statusChanged"
330- Parameter { name: "value"; type: "Status" }
331- }
332- Signal {
333 name: "draggingChanged"
334 Parameter { name: "value"; type: "bool" }
335 }
336 Signal {
337+ name: "pressedChanged"
338+ Parameter { name: "value"; type: "bool" }
339+ }
340+ Signal {
341 name: "distanceChanged"
342 Parameter { name: "value"; type: "double" }
343 }
344@@ -103,30 +91,6 @@
345 Parameter { name: "value"; type: "double" }
346 }
347 Signal {
348- name: "maxDeviationChanged"
349- Parameter { name: "value"; type: "double" }
350- }
351- Signal {
352- name: "wideningAngleChanged"
353- Parameter { name: "value"; type: "double" }
354- }
355- Signal {
356- name: "distanceThresholdChanged"
357- Parameter { name: "value"; type: "double" }
358- }
359- Signal {
360- name: "minSpeedChanged"
361- Parameter { name: "value"; type: "double" }
362- }
363- Signal {
364- name: "maxSilenceTimeChanged"
365- Parameter { name: "value"; type: "int" }
366- }
367- Signal {
368- name: "compositionTimeChanged"
369- Parameter { name: "value"; type: "int" }
370- }
371- Signal {
372 name: "touchXChanged"
373 Parameter { name: "value"; type: "double" }
374 }
375@@ -142,7 +106,35 @@
376 name: "touchSceneYChanged"
377 Parameter { name: "value"; type: "double" }
378 }
379- Signal { name: "tapped" }
380+ Signal {
381+ name: "immediateRecognitionChanged"
382+ Parameter { name: "value"; type: "bool" }
383+ }
384+ Method { name: "removeTimeConstraints" }
385+ }
386+ Component {
387+ name: "FloatingFlickable"
388+ defaultProperty: "data"
389+ prototype: "QQuickItem"
390+ exports: ["Ubuntu.Gestures/FloatingFlickable 0.1"]
391+ exportMetaObjectRevisions: [0]
392+ Property { name: "contentWidth"; type: "double" }
393+ Property { name: "contentHeight"; type: "double" }
394+ Property { name: "contentX"; type: "double" }
395+ Property { name: "contentY"; type: "double" }
396+ Property { name: "direction"; type: "Direction::Type" }
397+ }
398+ Component {
399+ name: "GestureTouchPoint"
400+ prototype: "QObject"
401+ exports: ["Ubuntu.Gestures/GestureTouchPoint 0.1"]
402+ isCreatable: false
403+ exportMetaObjectRevisions: [0]
404+ Property { name: "pointId"; type: "int"; isReadonly: true }
405+ Property { name: "pressed"; type: "bool"; isReadonly: true }
406+ Property { name: "x"; type: "double"; isReadonly: true }
407+ Property { name: "y"; type: "double"; isReadonly: true }
408+ Property { name: "dragging"; type: "bool"; isReadonly: true }
409 }
410 Component {
411 name: "PressedOutsideNotifier"
412@@ -163,6 +155,55 @@
413 name: "targetItemChanged"
414 Parameter { name: "item"; type: "QQuickItem"; isPointer: true }
415 }
416- Signal { name: "pressed" }
417+ }
418+ Component {
419+ name: "TouchGestureArea"
420+ defaultProperty: "data"
421+ prototype: "QQuickItem"
422+ exports: ["Ubuntu.Gestures/TouchGestureArea 0.1"]
423+ exportMetaObjectRevisions: [0]
424+ Enum {
425+ name: "Status"
426+ values: {
427+ "WaitingForTouch": 0,
428+ "Undecided": 1,
429+ "Recognized": 2,
430+ "Rejected": 3
431+ }
432+ }
433+ Property { name: "touchPoints"; type: "GestureTouchPoint"; isList: true; isReadonly: true }
434+ Property { name: "dragging"; type: "bool"; isReadonly: true }
435+ Property { name: "minimumTouchPoints"; type: "int" }
436+ Property { name: "maximumTouchPoints"; type: "int" }
437+ Signal {
438+ name: "statusChanged"
439+ Parameter { name: "status"; type: "Status" }
440+ }
441+ Signal { name: "touchPointsUpdated" }
442+ Signal {
443+ name: "draggingChanged"
444+ Parameter { name: "dragging"; type: "bool" }
445+ }
446+ Signal {
447+ name: "minimumTouchPointsChanged"
448+ Parameter { name: "value"; type: "bool" }
449+ }
450+ Signal {
451+ name: "maximumTouchPointsChanged"
452+ Parameter { name: "value"; type: "bool" }
453+ }
454+ Signal {
455+ name: "pressed"
456+ Parameter { name: "points"; type: "QList<QObject*>" }
457+ }
458+ Signal {
459+ name: "released"
460+ Parameter { name: "points"; type: "QList<QObject*>" }
461+ }
462+ Signal {
463+ name: "updated"
464+ Parameter { name: "points"; type: "QList<QObject*>" }
465+ }
466+ Signal { name: "clicked" }
467 }
468 }
469
470=== added file 'plugins/Ubuntu/Gestures/TouchGestureArea.cpp'
471--- plugins/Ubuntu/Gestures/TouchGestureArea.cpp 1970-01-01 00:00:00 +0000
472+++ plugins/Ubuntu/Gestures/TouchGestureArea.cpp 2016-03-16 11:49:36 +0000
473@@ -0,0 +1,875 @@
474+/*
475+ * Copyright (C) 2016 Canonical, Ltd.
476+ *
477+ * This program is free software; you can redistribute it and/or modify
478+ * it under the terms of the GNU General Public License as published by
479+ * the Free Software Foundation; version 3.
480+ *
481+ * This program is distributed in the hope that it will be useful,
482+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
483+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
484+ * GNU General Public License for more details.
485+ *
486+ * You should have received a copy of the GNU General Public License
487+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
488+ */
489+
490+#include "TouchGestureArea.h"
491+
492+// local
493+#include "TouchOwnershipEvent.h"
494+#include "TouchRegistry.h"
495+#include "UnownedTouchEvent.h"
496+
497+#include <QGuiApplication>
498+#include <QStyleHints>
499+#include <private/qquickwindow_p.h>
500+
501+#define TOUCHGESTUREAREA_DEBUG 0
502+
503+// TODO - understand more about why we lose touch event releases.
504+// Workaround for now is to monitor all the touch points from first touch till
505+// all have been released; no matter if we've rejected the gesture.
506+
507+namespace {
508+
509+struct InternalStatus {
510+ enum Status {
511+ WaitingForTouch,
512+ WaitingForMoreTouches,
513+ WaitingForOwnership, //Recognizing,
514+ Recognized,
515+ WaitingForRejection,
516+ Rejected
517+ };
518+};
519+
520+TouchGestureArea::Status internalStatusToGestureStatus(int internalStatus) {
521+ switch (internalStatus) {
522+ case InternalStatus::WaitingForTouch: return TouchGestureArea::WaitingForTouch;
523+ case InternalStatus::WaitingForMoreTouches: return TouchGestureArea::Undecided;
524+ case InternalStatus::WaitingForOwnership: return TouchGestureArea::Undecided;
525+ case InternalStatus::Recognized: return TouchGestureArea::Recognized;
526+ case InternalStatus::WaitingForRejection: return TouchGestureArea::Recognized;
527+ case InternalStatus::Rejected: return TouchGestureArea::Rejected;
528+ }
529+ return TouchGestureArea::WaitingForTouch;
530+}
531+
532+}
533+
534+#if TOUCHGESTUREAREA_DEBUG
535+#define tgaDebug(params) qDebug().nospace() << "[TGA(" << qPrintable(objectName()) << ")] " << params
536+#include "DebugHelpers.h"
537+
538+namespace {
539+
540+const char *statusToString(int status)
541+{
542+ if (status == InternalStatus::WaitingForTouch) {
543+ return "WaitingForTouch";
544+ } else if (status == InternalStatus::WaitingForMoreTouches) {
545+ return "WaitingForMoreTouches";
546+ } else if (status == InternalStatus::WaitingForOwnership) {
547+ return "WaitingForOwnership";
548+ } else if (status == InternalStatus::Rejected) {
549+ return "Rejected";
550+ } else if (status == InternalStatus::WaitingForRejection) {
551+ return "WaitingForRejection";
552+ } else {
553+ return "Recognized";
554+ }
555+ return "Unknown";
556+}
557+
558+QString touchState(Qt::TouchPointState state) {
559+ switch (state) {
560+ case Qt::TouchPointPressed: return "pressed";
561+ case Qt::TouchPointMoved: return "moved";
562+ case Qt::TouchPointStationary: return "stationary";
563+ case Qt::TouchPointReleased: return "released";
564+ break;
565+ }
566+ return "unknown";
567+}
568+
569+QString touchesString(const QList<QObject*> touches) {
570+ QString str;
571+ Q_FOREACH(QObject* object, touches) {
572+ GestureTouchPoint* touchPoint = qobject_cast<GestureTouchPoint*>(object);
573+ if (touchPoint) {
574+ str += QStringLiteral("[%1 @ (%2, %3)], ").arg(touchPoint->id())
575+ .arg(touchPoint->x())
576+ .arg(touchPoint->y());
577+ }
578+ }
579+ return str;
580+}
581+
582+QString touchEventString(QTouchEvent* event) {
583+ if (!event) return QString();
584+ QString str;
585+ Q_FOREACH(const auto& touchPoint, event->touchPoints()) {
586+ str += QStringLiteral("[%1:%2 @ (%3, %4)], ").arg(touchPoint.id())
587+ .arg(touchState(touchPoint.state()))
588+ .arg(touchPoint.pos().x())
589+ .arg(touchPoint.pos().y());
590+ }
591+ return str;
592+}
593+
594+
595+} // namespace {
596+#else // TOUCHGESTUREAREA_DEBUG
597+#define tgaDebug(params) ((void)0)
598+#endif // TOUCHGESTUREAREA_DEBUG
599+
600+TouchGestureArea::TouchGestureArea(QQuickItem* parent)
601+ : QQuickItem(parent)
602+ , m_status(WaitingForTouch)
603+ , m_recognitionTimer(nullptr)
604+ , m_dragging(false)
605+ , m_minimumTouchPoints(1)
606+ , m_maximumTouchPoints(INT_MAX)
607+ , m_recognitionPeriod(50)
608+ , m_releaseRejectPeriod(100)
609+{
610+ setRecognitionTimer(new UbuntuGestures::Timer(this));
611+ m_recognitionTimer->setInterval(m_recognitionPeriod);
612+ m_recognitionTimer->setSingleShot(true);
613+}
614+
615+TouchGestureArea::~TouchGestureArea()
616+{
617+ clearTouchLists();
618+ qDeleteAll(m_liveTouchPoints);
619+ m_liveTouchPoints.clear();
620+ qDeleteAll(m_cachedTouchPoints);
621+ m_cachedTouchPoints.clear();
622+}
623+
624+bool TouchGestureArea::event(QEvent *event)
625+{
626+ // Process unowned touch events (handles update/release for incomplete gestures)
627+ if (event->type() == TouchOwnershipEvent::touchOwnershipEventType()) {
628+ touchOwnershipEvent(static_cast<TouchOwnershipEvent *>(event));
629+ return true;
630+ } else if (event->type() == UnownedTouchEvent::unownedTouchEventType()) {
631+ unownedTouchEvent(static_cast<UnownedTouchEvent *>(event)->touchEvent());
632+ return true;
633+ }
634+
635+ return QQuickItem::event(event);
636+}
637+
638+void TouchGestureArea::touchOwnershipEvent(TouchOwnershipEvent *event)
639+{
640+ int touchId = event->touchId();
641+ tgaDebug("touchOwnershipEvent - id:" << touchId << ", gained:" << event->gained());
642+
643+ if (event->gained()) {
644+ grabTouchPoints(QVector<int>() << touchId);
645+ m_candidateTouches.remove(touchId);
646+ TouchRegistry::instance()->addTouchWatcher(touchId, this);
647+ m_watchedTouches.insert(touchId);
648+
649+ if (m_watchedTouches.count() >= m_minimumTouchPoints) {
650+ setInternalStatus(InternalStatus::Recognized);
651+ }
652+ } else {
653+ rejectGesture();
654+ }
655+}
656+
657+void TouchGestureArea::touchEvent(QTouchEvent *event)
658+{
659+ if (!isEnabled() || !isVisible()) {
660+ tgaDebug(QString("NOT ENABLED touchEvent(%1) %2").arg(statusToString(m_status)).arg(touchEventString(event)));
661+ QQuickItem::touchEvent(event);
662+ return;
663+ }
664+
665+ tgaDebug(QString("touchEvent(%1) %2").arg(statusToString(m_status)).arg(touchEventString(event)));
666+
667+ switch (m_status) {
668+ case InternalStatus::WaitingForTouch:
669+ touchEvent_waitingForTouch(event);
670+ break;
671+ case InternalStatus::WaitingForMoreTouches:
672+ touchEvent_waitingForMoreTouches(event);
673+ break;
674+ case InternalStatus::WaitingForOwnership:
675+ touchEvent_waitingForOwnership(event);
676+ break;
677+ case InternalStatus::Recognized:
678+ case InternalStatus::WaitingForRejection:
679+ touchEvent_recognized(event);
680+ break;
681+ case InternalStatus::Rejected:
682+ touchEvent_rejected(event);
683+ break;
684+ default: // Recognized:
685+ break;
686+ }
687+
688+ updateTouchPoints(event);
689+}
690+
691+void TouchGestureArea::touchEvent_waitingForTouch(QTouchEvent *event)
692+{
693+ Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
694+ Qt::TouchPointState touchPointState = touchPoint.state();
695+ int touchId = touchPoint.id();
696+
697+ if (touchPointState == Qt::TouchPointPressed) {
698+ if (!m_candidateTouches.contains(touchId)) {
699+ TouchRegistry::instance()->addCandidateOwnerForTouch(touchId, this);
700+ m_candidateTouches.insert(touchId);
701+ }
702+ }
703+ }
704+ event->ignore();
705+
706+ if (m_candidateTouches.count() > m_maximumTouchPoints) {
707+ rejectGesture();
708+ } else if (m_candidateTouches.count() >= m_minimumTouchPoints) {
709+ setInternalStatus(InternalStatus::WaitingForOwnership);
710+
711+ QSet<int> tmpCandidates(m_candidateTouches);
712+ Q_FOREACH(int candidateTouchId, tmpCandidates) {
713+ TouchRegistry::instance()->requestTouchOwnership(candidateTouchId, this);
714+ }
715+ // We accept the gesture; so don't pass to lower items
716+ event->accept();
717+ } else if (m_candidateTouches.count() > 0) {
718+ setInternalStatus(InternalStatus::WaitingForMoreTouches);
719+ }
720+}
721+
722+void TouchGestureArea::touchEvent_waitingForMoreTouches(QTouchEvent *event)
723+{
724+ Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
725+ Qt::TouchPointState touchPointState = touchPoint.state();
726+ int touchId = touchPoint.id();
727+
728+ if (touchPointState == Qt::TouchPointPressed) {
729+ if (!m_candidateTouches.contains(touchId)) {
730+ TouchRegistry::instance()->addCandidateOwnerForTouch(touchId, this);
731+ m_candidateTouches.insert(touchId);
732+ }
733+ }
734+ }
735+ event->ignore();
736+
737+ if (m_candidateTouches.count() > m_maximumTouchPoints) {
738+ rejectGesture();
739+ } else if (m_candidateTouches.count() >= m_minimumTouchPoints) {
740+ setInternalStatus(InternalStatus::WaitingForOwnership);
741+
742+ QSet<int> tmpCandidates(m_candidateTouches);
743+ Q_FOREACH(int candidateTouchId, tmpCandidates) {
744+ TouchRegistry::instance()->requestTouchOwnership(candidateTouchId, this);
745+ }
746+ // We accept the gesture; so don't pass to lower items
747+ event->accept();
748+ }
749+}
750+
751+void TouchGestureArea::touchEvent_waitingForOwnership(QTouchEvent *event)
752+{
753+ Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
754+ Qt::TouchPointState touchPointState = touchPoint.state();
755+ int touchId = touchPoint.id();
756+
757+ if (touchPointState == Qt::TouchPointPressed) {
758+ if (!m_watchedTouches.contains(touchId)) {
759+ TouchRegistry::instance()->addTouchWatcher(touchId, this);
760+ m_watchedTouches.insert(touchId);
761+ }
762+ }
763+ }
764+}
765+
766+void TouchGestureArea::touchEvent_recognized(QTouchEvent *event)
767+{
768+ Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
769+ Qt::TouchPointState touchPointState = touchPoint.state();
770+ int touchId = touchPoint.id();
771+
772+ if (touchPointState == Qt::TouchPointPressed) {
773+ if (!m_watchedTouches.contains(touchId)) {
774+ TouchRegistry::instance()->addTouchWatcher(touchId, this);
775+ m_watchedTouches.insert(touchId);
776+ }
777+ }
778+ }
779+
780+ if (m_watchedTouches.count() > m_maximumTouchPoints) {
781+ rejectGesture();
782+ } else if (m_watchedTouches.count() >= m_minimumTouchPoints &&
783+ m_status==InternalStatus::WaitingForRejection) {
784+ setInternalStatus(InternalStatus::Recognized);
785+ }
786+}
787+
788+void TouchGestureArea::touchEvent_rejected(QTouchEvent *event)
789+{
790+ Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
791+ Qt::TouchPointState touchPointState = touchPoint.state();
792+ int touchId = touchPoint.id();
793+
794+ if (touchPointState == Qt::TouchPointPressed) {
795+ if (!m_watchedTouches.contains(touchId)) {
796+ TouchRegistry::instance()->addTouchWatcher(touchId, this);
797+ m_watchedTouches.insert(touchId);
798+ }
799+ }
800+ }
801+}
802+
803+void TouchGestureArea::unownedTouchEvent(QTouchEvent *unownedTouchEvent)
804+{
805+ tgaDebug(QString("unownedTouchEvent(%1) %2").arg(statusToString(m_status)).arg(touchEventString(unownedTouchEvent)));
806+
807+ // Only monitor unowned touch events for presses/releases
808+ if ((unownedTouchEvent->touchPointStates() & (Qt::TouchPointPressed|Qt::TouchPointReleased)) == 0) {
809+ return;
810+ }
811+
812+ switch (m_status) {
813+ case InternalStatus::WaitingForTouch:
814+ break;
815+ case InternalStatus::WaitingForMoreTouches:
816+ unownedTouchEvent_waitingForMoreTouches(unownedTouchEvent);
817+ // do nothing
818+ break;
819+ case InternalStatus::WaitingForOwnership:
820+ unownedTouchEvent_waitingForOwnership(unownedTouchEvent);
821+ break;
822+ case InternalStatus::Recognized:
823+ case InternalStatus::WaitingForRejection:
824+ unownedTouchEvent_recognised(unownedTouchEvent);
825+ break;
826+ case InternalStatus::Rejected:
827+ unownedTouchEvent_rejected(unownedTouchEvent);
828+ break;
829+ default:
830+ break;
831+ }
832+
833+ updateTouchPoints(unownedTouchEvent);
834+}
835+
836+void TouchGestureArea::unownedTouchEvent_waitingForMoreTouches(QTouchEvent *event)
837+{
838+ Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
839+ Qt::TouchPointState touchPointState = touchPoint.state();
840+ int touchId = touchPoint.id();
841+
842+ if (touchPointState == Qt::TouchPointReleased) {
843+ if (m_candidateTouches.contains(touchId)) {
844+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, this);
845+ m_candidateTouches.remove(touchId);
846+ }
847+ }
848+ }
849+
850+ if (m_candidateTouches.count() == 0) {
851+ setInternalStatus(InternalStatus::WaitingForTouch);
852+ }
853+}
854+
855+void TouchGestureArea::unownedTouchEvent_waitingForOwnership(QTouchEvent *event)
856+{
857+ Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
858+ Qt::TouchPointState touchPointState = touchPoint.state();
859+ int touchId = touchPoint.id();
860+
861+ if (touchPointState == Qt::TouchPointReleased) {
862+ if (m_candidateTouches.contains(touchId)) {
863+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, this);
864+ m_candidateTouches.remove(touchId);
865+ }
866+ if (m_watchedTouches.contains(touchId)) {
867+ m_watchedTouches.remove(touchId);
868+ }
869+ }
870+ }
871+
872+ if (m_candidateTouches.count() + m_watchedTouches.count() == 0) {
873+ setInternalStatus(InternalStatus::WaitingForTouch);
874+ }
875+}
876+
877+void TouchGestureArea::unownedTouchEvent_recognised(QTouchEvent *event)
878+{
879+ Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
880+ Qt::TouchPointState touchPointState = touchPoint.state();
881+ int touchId = touchPoint.id();
882+
883+ if (touchPointState == Qt::TouchPointReleased) {
884+ if (m_watchedTouches.contains(touchId)) {
885+ m_watchedTouches.remove(touchId);
886+ }
887+ }
888+ }
889+
890+ if (m_watchedTouches.count() < m_minimumTouchPoints && m_status==InternalStatus::Recognized) {
891+ setInternalStatus(InternalStatus::WaitingForRejection);
892+ }
893+}
894+
895+void TouchGestureArea::unownedTouchEvent_rejected(QTouchEvent *event)
896+{
897+ Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
898+ Qt::TouchPointState touchPointState = touchPoint.state();
899+ int touchId = touchPoint.id();
900+
901+ if (touchPointState == Qt::TouchPointPressed) {
902+ if (!m_watchedTouches.contains(touchId)) {
903+ TouchRegistry::instance()->addTouchWatcher(touchId, this);
904+ m_watchedTouches.insert(touchId);
905+ }
906+ }
907+ if (touchPointState == Qt::TouchPointReleased) {
908+ if (m_watchedTouches.contains(touchId)) {
909+ m_watchedTouches.remove(touchId);
910+ }
911+ }
912+ }
913+
914+ if (m_watchedTouches.count() == 0) {
915+ setInternalStatus(InternalStatus::WaitingForTouch);
916+ }
917+}
918+
919+void TouchGestureArea::updateTouchPoints(QTouchEvent *touchEvent)
920+{
921+ bool added = false;
922+ bool ended = false;
923+ bool moved = false;
924+
925+ const int dragThreshold = qApp->styleHints()->startDragDistance();
926+ const int dragVelocity = qApp->styleHints()->startDragVelocity();
927+
928+ clearTouchLists();
929+ bool updateable = m_status != InternalStatus::WaitingForRejection;
930+
931+ Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, touchEvent->touchPoints()) {
932+ Qt::TouchPointState touchPointState = touchPoint.state();
933+ int touchId = touchPoint.id();
934+
935+ if (touchPointState & Qt::TouchPointReleased) {
936+ GestureTouchPoint* gtp = m_liveTouchPoints.value(touchId);
937+ if (!gtp) continue;
938+
939+ gtp->setPos(touchPoint.pos());
940+ gtp->setPressed(false);
941+ m_releasedTouchPoints.append(gtp);
942+ m_liveTouchPoints.remove(touchId);
943+
944+ if (updateable) {
945+ if (m_cachedTouchPoints.contains(touchId)) {
946+ GestureTouchPoint* cachedPoint = m_cachedTouchPoints.take(touchId);
947+ cachedPoint->deleteLater();
948+ }
949+ }
950+ ended = true;
951+ } else {
952+ GestureTouchPoint* gtp = m_liveTouchPoints.value(touchPoint.id(), nullptr);
953+ if (!gtp) {
954+ gtp = addTouchPoint(&touchPoint);
955+ m_pressedTouchPoints.append(gtp);
956+
957+ if (updateable) {
958+ if (m_cachedTouchPoints.contains(touchId)) {
959+ m_cachedTouchPoints[touchId]->setPos(touchPoint.pos());
960+ } else {
961+ m_cachedTouchPoints[touchId] = new GestureTouchPoint(*gtp);
962+ }
963+ }
964+ added = true;
965+ } else if (touchPointState & Qt::TouchPointMoved) {
966+ gtp->setPos(touchPoint.pos());
967+ m_movedTouchPoints.append(gtp);
968+ moved = true;
969+
970+ const QPointF &currentPos = touchPoint.scenePos();
971+ const QPointF &startPos = touchPoint.startScenePos();
972+
973+ bool overDragThreshold = false;
974+ bool supportsVelocity = (touchEvent->device()->capabilities() & QTouchDevice::Velocity) && dragVelocity;
975+ overDragThreshold |= qAbs(currentPos.x() - startPos.x()) > dragThreshold ||
976+ qAbs(currentPos.y() - startPos.y()) > dragThreshold;
977+ if (supportsVelocity) {
978+ QVector2D velocityVec = touchPoint.velocity();
979+ overDragThreshold |= qAbs(velocityVec.x()) > dragVelocity;
980+ overDragThreshold |= qAbs(velocityVec.y()) > dragVelocity;
981+ }
982+
983+ if (overDragThreshold) {
984+ gtp->setDragging(true);
985+ }
986+
987+ if (updateable) {
988+ if (m_cachedTouchPoints.contains(touchId)) {
989+ m_cachedTouchPoints[touchId]->setPos(touchPoint.pos());
990+ if (overDragThreshold) {
991+ m_cachedTouchPoints[touchId]->setDragging(true);
992+ }
993+ }
994+ }
995+ }
996+ }
997+ }
998+
999+ if (updateable) {
1000+ if (!dragging() && m_status == InternalStatus::Recognized) {
1001+ bool allWantDrag = !m_liveTouchPoints.isEmpty();
1002+ Q_FOREACH(auto point, m_liveTouchPoints) {
1003+ allWantDrag &= point->dragging();
1004+ }
1005+ // only dragging if all points are dragging.
1006+ if (allWantDrag) {
1007+ setDragging(true);
1008+ }
1009+ }
1010+
1011+ if (ended) {
1012+ if (m_liveTouchPoints.isEmpty()) {
1013+ if (!dragging()) Q_EMIT clicked();
1014+ setDragging(false);
1015+ }
1016+ tgaDebug("Released " << touchesString(m_releasedTouchPoints));
1017+ Q_EMIT released(m_releasedTouchPoints);
1018+ }
1019+ if (added) {
1020+ tgaDebug("Pressed " << touchesString(m_pressedTouchPoints));
1021+ Q_EMIT pressed(m_pressedTouchPoints);
1022+ }
1023+ if (moved) {
1024+ tgaDebug("Updated " << touchesString(m_movedTouchPoints));
1025+ Q_EMIT updated(m_movedTouchPoints);
1026+ }
1027+ if (added || ended || moved) {
1028+ Q_EMIT touchPointsUpdated();
1029+ }
1030+ }
1031+}
1032+
1033+void TouchGestureArea::clearTouchLists()
1034+{
1035+ Q_FOREACH (QObject *gtp, m_releasedTouchPoints) {
1036+ delete gtp;
1037+ }
1038+ m_releasedTouchPoints.clear();
1039+ m_pressedTouchPoints.clear();
1040+ m_movedTouchPoints.clear();
1041+}
1042+
1043+void TouchGestureArea::setInternalStatus(uint newStatus)
1044+{
1045+ if (newStatus == m_status)
1046+ return;
1047+
1048+ uint oldStatus = m_status;
1049+
1050+ m_status = newStatus;
1051+ Q_EMIT statusChanged(status());
1052+
1053+ if (oldStatus == InternalStatus::WaitingForMoreTouches || oldStatus == InternalStatus::WaitingForRejection) {
1054+ m_recognitionTimer->stop();
1055+ }
1056+
1057+ tgaDebug(statusToString(oldStatus) << " -> " << statusToString(newStatus));
1058+
1059+ switch (newStatus) {
1060+ case InternalStatus::WaitingForTouch:
1061+ resyncCachedTouchPoints();
1062+ break;
1063+ case InternalStatus::WaitingForMoreTouches:
1064+ m_recognitionTimer->start(m_recognitionPeriod);
1065+ break;
1066+ case InternalStatus::Recognized:
1067+ resyncCachedTouchPoints();
1068+ break;
1069+ case InternalStatus::WaitingForRejection:
1070+ m_recognitionTimer->start(m_releaseRejectPeriod);
1071+ break;
1072+ case InternalStatus::Rejected:
1073+ resyncCachedTouchPoints();
1074+ break;
1075+ default:
1076+ // no-op
1077+ break;
1078+ }
1079+}
1080+
1081+void TouchGestureArea::setRecognitionTimer(UbuntuGestures::AbstractTimer *timer)
1082+{
1083+ int interval = 0;
1084+ bool timerWasRunning = false;
1085+ bool wasSingleShot = false;
1086+
1087+ // can be null when called from the constructor
1088+ if (m_recognitionTimer) {
1089+ interval = m_recognitionTimer->interval();
1090+ timerWasRunning = m_recognitionTimer->isRunning();
1091+ if (m_recognitionTimer->parent() == this) {
1092+ delete m_recognitionTimer;
1093+ }
1094+ }
1095+
1096+ m_recognitionTimer = timer;
1097+ timer->setInterval(interval);
1098+ timer->setSingleShot(wasSingleShot);
1099+ connect(timer, SIGNAL(timeout()),
1100+ this, SLOT(rejectGesture()));
1101+ if (timerWasRunning) {
1102+ m_recognitionTimer->start();
1103+ }
1104+}
1105+
1106+int TouchGestureArea::status() const
1107+{
1108+ return internalStatusToGestureStatus(m_status);
1109+}
1110+
1111+bool TouchGestureArea::dragging() const
1112+{
1113+ return m_dragging;
1114+}
1115+
1116+QQmlListProperty<GestureTouchPoint> TouchGestureArea::touchPoints()
1117+{
1118+ return QQmlListProperty<GestureTouchPoint>(this,
1119+ 0,
1120+ nullptr,
1121+ TouchGestureArea::touchPoint_count,
1122+ TouchGestureArea::touchPoint_at,
1123+ 0);
1124+}
1125+
1126+int TouchGestureArea::minimumTouchPoints() const
1127+{
1128+ return m_minimumTouchPoints;
1129+}
1130+
1131+void TouchGestureArea::setMinimumTouchPoints(int value)
1132+{
1133+ if (m_minimumTouchPoints != value) {
1134+ m_minimumTouchPoints = value;
1135+ Q_EMIT minimumTouchPointsChanged(value);
1136+ }
1137+}
1138+
1139+int TouchGestureArea::maximumTouchPoints() const
1140+{
1141+ return m_maximumTouchPoints;
1142+}
1143+
1144+void TouchGestureArea::setMaximumTouchPoints(int value)
1145+{
1146+ if (m_maximumTouchPoints != value) {
1147+ m_maximumTouchPoints = value;
1148+ Q_EMIT maximumTouchPointsChanged(value);
1149+ }
1150+}
1151+
1152+int TouchGestureArea::recognitionPeriod() const
1153+{
1154+ return m_recognitionPeriod;
1155+}
1156+
1157+void TouchGestureArea::setRecognitionPeriod(int value)
1158+{
1159+ if (value != m_recognitionPeriod) {
1160+ m_recognitionPeriod = value;
1161+ Q_EMIT recognitionPeriodChanged(value);
1162+ }
1163+}
1164+
1165+int TouchGestureArea::releaseRejectPeriod() const
1166+{
1167+ return m_releaseRejectPeriod;
1168+}
1169+
1170+void TouchGestureArea::setReleaseRejectPeriod(int value)
1171+{
1172+ if (value != m_releaseRejectPeriod) {
1173+ m_releaseRejectPeriod = value;
1174+ Q_EMIT releaseRejectPeriodChanged(value);
1175+ }
1176+}
1177+
1178+void TouchGestureArea::rejectGesture()
1179+{
1180+ tgaDebug("rejectGesture");
1181+ ungrabTouchPoints();
1182+
1183+ Q_FOREACH(int touchId, m_candidateTouches) {
1184+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, this);
1185+ }
1186+
1187+ // Monitor the candidates
1188+ Q_FOREACH(int touchId, m_candidateTouches) {
1189+ TouchRegistry::instance()->addTouchWatcher(touchId, this);
1190+ m_watchedTouches.insert(touchId);
1191+ }
1192+ m_candidateTouches.clear();
1193+
1194+ if (m_watchedTouches.count() == 0) {
1195+ setInternalStatus(InternalStatus::WaitingForTouch);
1196+ } else {
1197+ setInternalStatus(InternalStatus::Rejected);
1198+ }
1199+}
1200+
1201+void TouchGestureArea::resyncCachedTouchPoints()
1202+{
1203+ clearTouchLists();
1204+
1205+ bool added = false;
1206+ bool ended = false;
1207+ bool moved = false;
1208+ bool wantsDrag = false;
1209+
1210+ // list of deletes
1211+ QMutableHashIterator<int, GestureTouchPoint*> removeIter(m_cachedTouchPoints);
1212+ while(removeIter.hasNext()) {
1213+ removeIter.next();
1214+ if (!m_liveTouchPoints.contains(removeIter.key())) {
1215+ m_releasedTouchPoints.append(removeIter.value());
1216+ removeIter.remove();
1217+ ended = true;
1218+ }
1219+ }
1220+
1221+ // list of adds/moves
1222+ Q_FOREACH(GestureTouchPoint* touchPoint, m_liveTouchPoints) {
1223+ if (m_cachedTouchPoints.contains(touchPoint->id())) {
1224+ GestureTouchPoint* cachedPoint = m_cachedTouchPoints[touchPoint->id()];
1225+
1226+ if (*cachedPoint != *touchPoint) {
1227+ *cachedPoint = *touchPoint;
1228+ m_movedTouchPoints.append(touchPoint);
1229+ moved = true;
1230+ }
1231+ } else {
1232+ m_cachedTouchPoints.insert(touchPoint->id(), new GestureTouchPoint(*touchPoint));
1233+ m_pressedTouchPoints.append(touchPoint);
1234+ added = true;
1235+ }
1236+ }
1237+
1238+ if (wantsDrag && !dragging()) {
1239+ setDragging(true);
1240+ }
1241+
1242+ if (ended) {
1243+ if (m_cachedTouchPoints.isEmpty()) {
1244+ if (!dragging()) Q_EMIT clicked();
1245+ setDragging(false);
1246+ }
1247+ tgaDebug("Cached Release " << touchesString(m_releasedTouchPoints));
1248+ Q_EMIT released(m_releasedTouchPoints);
1249+ }
1250+ if (added) {
1251+ tgaDebug("Cached Press " << touchesString(m_pressedTouchPoints));
1252+ Q_EMIT pressed(m_pressedTouchPoints);
1253+ }
1254+ if (moved) {
1255+ tgaDebug("Cached Update " << touchesString(m_movedTouchPoints));
1256+ Q_EMIT updated(m_movedTouchPoints);
1257+ }
1258+ if (added || ended || moved) Q_EMIT touchPointsUpdated();
1259+}
1260+
1261+int TouchGestureArea::touchPoint_count(QQmlListProperty<GestureTouchPoint> *list)
1262+{
1263+ TouchGestureArea *q = static_cast<TouchGestureArea*>(list->object);
1264+ return q->m_cachedTouchPoints.count();
1265+}
1266+
1267+GestureTouchPoint *TouchGestureArea::touchPoint_at(QQmlListProperty<GestureTouchPoint> *list, int index)
1268+{
1269+ TouchGestureArea *q = static_cast<TouchGestureArea*>(list->object);
1270+ return (q->m_cachedTouchPoints.begin()+index).value();
1271+}
1272+
1273+GestureTouchPoint* TouchGestureArea::addTouchPoint(QTouchEvent::TouchPoint const* tp)
1274+{
1275+ GestureTouchPoint* gtp = new GestureTouchPoint();
1276+ gtp->setId(tp->id());
1277+ gtp->setPressed(true);
1278+ gtp->setPos(tp->pos());
1279+ m_liveTouchPoints.insert(tp->id(), gtp);
1280+ return gtp;
1281+}
1282+
1283+void TouchGestureArea::itemChange(ItemChange change, const ItemChangeData &value)
1284+{
1285+ if (change == QQuickItem::ItemSceneChange) {
1286+ if (value.window != nullptr) {
1287+ value.window->installEventFilter(TouchRegistry::instance());
1288+ }
1289+ }
1290+}
1291+
1292+void TouchGestureArea::setDragging(bool dragging)
1293+{
1294+ if (m_dragging == dragging)
1295+ return;
1296+
1297+ tgaDebug("setDragging " << dragging);
1298+
1299+ m_dragging = dragging;
1300+ Q_EMIT draggingChanged(m_dragging);
1301+}
1302+
1303+void GestureTouchPoint::setId(int id)
1304+{
1305+ if (m_id == id)
1306+ return;
1307+ m_id = id;
1308+ Q_EMIT idChanged();
1309+}
1310+
1311+void GestureTouchPoint::setPressed(bool pressed)
1312+{
1313+ if (m_pressed == pressed)
1314+ return;
1315+ m_pressed = pressed;
1316+ Q_EMIT pressedChanged();
1317+}
1318+
1319+void GestureTouchPoint::setX(qreal x)
1320+{
1321+ if (m_x == x)
1322+ return;
1323+ m_x = x;
1324+ Q_EMIT xChanged();
1325+}
1326+
1327+void GestureTouchPoint::setY(qreal y)
1328+{
1329+ if (m_y == y)
1330+ return;
1331+ m_y = y;
1332+ Q_EMIT yChanged();
1333+}
1334+
1335+void GestureTouchPoint::setDragging(bool dragging)
1336+{
1337+ if (m_dragging == dragging)
1338+ return;
1339+
1340+ m_dragging = dragging;
1341+ Q_EMIT draggingChanged();
1342+}
1343+
1344+void GestureTouchPoint::setPos(const QPointF &pos)
1345+{
1346+ setX(pos.x());
1347+ setY(pos.y());
1348+}
1349
1350=== added file 'plugins/Ubuntu/Gestures/TouchGestureArea.h'
1351--- plugins/Ubuntu/Gestures/TouchGestureArea.h 1970-01-01 00:00:00 +0000
1352+++ plugins/Ubuntu/Gestures/TouchGestureArea.h 2016-03-16 11:49:36 +0000
1353@@ -0,0 +1,229 @@
1354+/*
1355+ * Copyright (C) 2016 Canonical, Ltd.
1356+ *
1357+ * This program is free software; you can redistribute it and/or modify
1358+ * it under the terms of the GNU General Public License as published by
1359+ * the Free Software Foundation; version 3.
1360+ *
1361+ * This program is distributed in the hope that it will be useful,
1362+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1363+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1364+ * GNU General Public License for more details.
1365+ *
1366+ * You should have received a copy of the GNU General Public License
1367+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1368+ */
1369+
1370+#ifndef TOUCHGESTUREAREA_H
1371+#define TOUCHGESTUREAREA_H
1372+
1373+#include "UbuntuGesturesQmlGlobal.h"
1374+
1375+#include <QQuickItem>
1376+
1377+// lib UbuntuGestures
1378+#include <Timer.h>
1379+
1380+class TouchOwnershipEvent;
1381+class UnownedTouchEvent;
1382+
1383+class GestureTouchPoint : public QObject
1384+{
1385+ Q_OBJECT
1386+ Q_PROPERTY(int id READ id NOTIFY idChanged)
1387+ Q_PROPERTY(bool pressed READ pressed NOTIFY pressedChanged)
1388+ Q_PROPERTY(qreal x READ x NOTIFY xChanged)
1389+ Q_PROPERTY(qreal y READ y NOTIFY yChanged)
1390+ Q_PROPERTY(bool dragging READ dragging NOTIFY draggingChanged)
1391+public:
1392+ GestureTouchPoint()
1393+ : m_id(-1)
1394+ , m_pressed(false)
1395+ , m_x(0)
1396+ , m_y(0)
1397+ , m_dragging(false)
1398+ {
1399+ }
1400+
1401+ GestureTouchPoint(const GestureTouchPoint& other)
1402+ : QObject(nullptr)
1403+ {
1404+ operator=(other);
1405+ }
1406+
1407+ int id() const { return m_id; }
1408+ void setId(int id);
1409+
1410+ bool pressed() const { return m_pressed; }
1411+ void setPressed(bool pressed);
1412+
1413+ qreal x() const { return m_x; }
1414+ void setX(qreal x);
1415+
1416+ qreal y() const { return m_y; }
1417+ void setY(qreal y);
1418+
1419+ bool dragging() const { return m_dragging; }
1420+ void setDragging(bool dragging);
1421+
1422+ GestureTouchPoint& operator=(const GestureTouchPoint& rhs) {
1423+ if (&rhs == this) return *this;
1424+ m_id = rhs.m_id;
1425+ m_pressed = rhs.m_pressed;
1426+ m_x = rhs.m_x;
1427+ m_y = rhs.m_y;
1428+ m_dragging = rhs.m_dragging;
1429+ return *this;
1430+ }
1431+
1432+ bool operator=(const GestureTouchPoint& rhs) const {
1433+ if (&rhs == this) return true;
1434+ return m_id == rhs.m_id &&
1435+ m_pressed == rhs.m_pressed &&
1436+ m_x == rhs.m_x &&
1437+ m_y == rhs.m_y &&
1438+ m_dragging == rhs.m_dragging;
1439+ }
1440+ bool operator!=(const GestureTouchPoint& rhs) const { return !operator=(rhs); }
1441+
1442+ void setPos(const QPointF &pos);
1443+
1444+Q_SIGNALS:
1445+ void idChanged();
1446+ void pressedChanged();
1447+ void xChanged();
1448+ void yChanged();
1449+ void draggingChanged();
1450+
1451+private:
1452+ int m_id;
1453+ bool m_pressed;
1454+ qreal m_x;
1455+ qreal m_y;
1456+ bool m_dragging;
1457+};
1458+
1459+/*
1460+ An area that detects multi-finger gestures.
1461+
1462+ We can use this to detect gestures contstrained by a minimim and/or maximum number of touch points.
1463+ This components uses the touch registry to apply for ownership of touch points.
1464+ This way we can use the component in conjuntion with the directional drag area to compete for ownwership
1465+ or gestures; unlike the MultiPointTouchArea.
1466+ */
1467+class UBUNTUGESTURESQML_EXPORT TouchGestureArea : public QQuickItem
1468+{
1469+ Q_OBJECT
1470+ Q_ENUMS(Status)
1471+
1472+ Q_PROPERTY(int status READ status NOTIFY statusChanged)
1473+ Q_PROPERTY(bool dragging READ dragging NOTIFY draggingChanged)
1474+ Q_PROPERTY(QQmlListProperty<GestureTouchPoint> touchPoints READ touchPoints NOTIFY touchPointsUpdated)
1475+
1476+ Q_PROPERTY(int minimumTouchPoints READ minimumTouchPoints WRITE setMinimumTouchPoints NOTIFY minimumTouchPointsChanged)
1477+ Q_PROPERTY(int maximumTouchPoints READ maximumTouchPoints WRITE setMaximumTouchPoints NOTIFY maximumTouchPointsChanged)
1478+
1479+ // Time(ms) the component will wait for after receiving an initial touch to recognise a gesutre before rejecting it.
1480+ Q_PROPERTY(int recognitionPeriod READ recognitionPeriod WRITE setRecognitionPeriod NOTIFY recognitionPeriodChanged)
1481+ // Time(ms) the component will allow a recognised gesture to intermitently release a touch point before rejecting the gesture.
1482+ // This is so we will not immediately reject a gesture if there are fleeting touch point releases while dragging.
1483+ Q_PROPERTY(int releaseRejectPeriod READ releaseRejectPeriod WRITE setReleaseRejectPeriod NOTIFY releaseRejectPeriodChanged)
1484+
1485+public:
1486+ // Describes the state of the touch gesture area.
1487+ enum Status {
1488+ WaitingForTouch,
1489+ Undecided,
1490+ Recognized,
1491+ Rejected
1492+ };
1493+ TouchGestureArea(QQuickItem* parent = NULL);
1494+ ~TouchGestureArea();
1495+
1496+ bool event(QEvent *e) override;
1497+
1498+ void setRecognitionTimer(UbuntuGestures::AbstractTimer *timer);
1499+
1500+ int status() const;
1501+ bool dragging() const;
1502+ QQmlListProperty<GestureTouchPoint> touchPoints();
1503+
1504+ int minimumTouchPoints() const;
1505+ void setMinimumTouchPoints(int value);
1506+
1507+ int maximumTouchPoints() const;
1508+ void setMaximumTouchPoints(int value);
1509+
1510+ int recognitionPeriod() const;
1511+ void setRecognitionPeriod(int value);
1512+
1513+ int releaseRejectPeriod() const;
1514+ void setReleaseRejectPeriod(int value);
1515+
1516+Q_SIGNALS:
1517+ void statusChanged(int status);
1518+
1519+ void touchPointsUpdated();
1520+ void draggingChanged(bool dragging);
1521+ void minimumTouchPointsChanged(bool value);
1522+ void maximumTouchPointsChanged(bool value);
1523+ void recognitionPeriodChanged(bool value);
1524+ void releaseRejectPeriodChanged(bool value);
1525+
1526+ void pressed(const QList<QObject*>& points);
1527+ void released(const QList<QObject*>& points);
1528+ void updated(const QList<QObject*>& points);
1529+ void clicked();
1530+
1531+protected:
1532+ void itemChange(ItemChange change, const ItemChangeData &value);
1533+
1534+private Q_SLOTS:
1535+ void rejectGesture();
1536+
1537+private:
1538+ void touchEvent(QTouchEvent *event) override;
1539+ void touchEvent_waitingForTouch(QTouchEvent *event);
1540+ void touchEvent_waitingForMoreTouches(QTouchEvent *event);
1541+ void touchEvent_waitingForOwnership(QTouchEvent *event);
1542+ void touchEvent_recognized(QTouchEvent *event);
1543+ void touchEvent_rejected(QTouchEvent *event);
1544+
1545+ void unownedTouchEvent(QTouchEvent *unownedTouchEvent);
1546+ void unownedTouchEvent_waitingForMoreTouches(QTouchEvent *unownedTouchEvent);
1547+ void unownedTouchEvent_waitingForOwnership(QTouchEvent *unownedTouchEvent);
1548+ void unownedTouchEvent_recognised(QTouchEvent *unownedTouchEvent);
1549+ void unownedTouchEvent_rejected(QTouchEvent *unownedTouchEvent);
1550+
1551+ void touchOwnershipEvent(TouchOwnershipEvent *event);
1552+ void updateTouchPoints(QTouchEvent *event);
1553+
1554+ GestureTouchPoint* addTouchPoint(const QTouchEvent::TouchPoint *tp);
1555+ void clearTouchLists();
1556+ void setDragging(bool dragging);
1557+ void setInternalStatus(uint status);
1558+ void resyncCachedTouchPoints();
1559+
1560+ static int touchPoint_count(QQmlListProperty<GestureTouchPoint> *list);
1561+ static GestureTouchPoint* touchPoint_at(QQmlListProperty<GestureTouchPoint> *list, int index);
1562+
1563+ uint m_status;
1564+ QSet<int> m_candidateTouches;
1565+ QSet<int> m_watchedTouches;
1566+ UbuntuGestures::AbstractTimer *m_recognitionTimer;
1567+
1568+ bool m_dragging;
1569+ QHash<int, GestureTouchPoint*> m_liveTouchPoints;
1570+ QHash<int, GestureTouchPoint*> m_cachedTouchPoints;
1571+ QList<QObject*> m_releasedTouchPoints;
1572+ QList<QObject*> m_pressedTouchPoints;
1573+ QList<QObject*> m_movedTouchPoints;
1574+ int m_minimumTouchPoints;
1575+ int m_maximumTouchPoints;
1576+ int m_recognitionPeriod;
1577+ int m_releaseRejectPeriod;
1578+};
1579+
1580+QML_DECLARE_TYPE(GestureTouchPoint)
1581+
1582+#endif // TOUCHGESTUREAREA_H
1583
1584=== modified file 'plugins/Ubuntu/Gestures/plugin.cpp'
1585--- plugins/Ubuntu/Gestures/plugin.cpp 2015-05-27 09:37:34 +0000
1586+++ plugins/Ubuntu/Gestures/plugin.cpp 2016-03-16 11:49:36 +0000
1587@@ -21,6 +21,7 @@
1588 #include "FloatingFlickable.h"
1589 #include "PressedOutsideNotifier.h"
1590 #include "TouchGate.h"
1591+#include "TouchGestureArea.h"
1592
1593 #include <qqml.h>
1594
1595@@ -38,4 +39,6 @@
1596 qmlRegisterType<FloatingFlickable>(uri, 0, 1, "FloatingFlickable");
1597 qmlRegisterType<PressedOutsideNotifier>(uri, 0, 1, "PressedOutsideNotifier");
1598 qmlRegisterType<TouchGate>(uri, 0, 1, "TouchGate");
1599+ qmlRegisterType<TouchGestureArea>(uri, 0, 1, "TouchGestureArea");
1600+ qmlRegisterUncreatableType<GestureTouchPoint>(uri, 0, 1, "GestureTouchPoint", "Cannot create GestureTouchPoints");
1601 }
1602
1603=== added file 'plugins/Unity/InputInfo/InputInfo.qmltypes'
1604--- plugins/Unity/InputInfo/InputInfo.qmltypes 1970-01-01 00:00:00 +0000
1605+++ plugins/Unity/InputInfo/InputInfo.qmltypes 2016-03-16 11:49:36 +0000
1606@@ -0,0 +1,71 @@
1607+import QtQuick.tooling 1.1
1608+
1609+// This file describes the plugin-supplied types contained in the library.
1610+// It is used for QML tooling purposes only.
1611+//
1612+// This file was auto-generated by:
1613+// 'qmlplugindump -notrelocatable Unity.InputInfo 0.1 plugins'
1614+
1615+Module {
1616+ Component {
1617+ name: "QDeclarativeInputDeviceModel"
1618+ prototype: "QAbstractListModel"
1619+ exports: ["Unity.InputInfo/InputDeviceModel 0.1"]
1620+ exportMetaObjectRevisions: [0]
1621+ Property { name: "deviceFilter"; type: "QInputDevice::InputType" }
1622+ Property { name: "count"; type: "int"; isReadonly: true }
1623+ Signal {
1624+ name: "deviceAdded"
1625+ Parameter { name: "devicePath"; type: "string" }
1626+ }
1627+ Signal {
1628+ name: "deviceRemoved"
1629+ Parameter { name: "devicePath"; type: "string" }
1630+ }
1631+ Signal {
1632+ name: "deviceFilterChanged"
1633+ Parameter { name: "filter"; type: "QInputDevice::InputType" }
1634+ }
1635+ Method { name: "updateDeviceList" }
1636+ Method {
1637+ name: "indexOf"
1638+ type: "int"
1639+ Parameter { name: "devicePath"; type: "string" }
1640+ }
1641+ Method {
1642+ name: "get"
1643+ type: "QInputDevice*"
1644+ Parameter { name: "index"; type: "int" }
1645+ }
1646+ }
1647+ Component {
1648+ name: "QInputDevice"
1649+ prototype: "QObject"
1650+ exports: ["Unity.InputInfo/InputInfo 0.1"]
1651+ exportMetaObjectRevisions: [0]
1652+ Enum {
1653+ name: "InputType"
1654+ values: {
1655+ "Unknown": 0,
1656+ "Button": 1,
1657+ "Mouse": 2,
1658+ "TouchPad": 4,
1659+ "TouchScreen": 8,
1660+ "Keyboard": 16,
1661+ "Switch": 32
1662+ }
1663+ }
1664+ Enum {
1665+ name: "InputTypeFlags"
1666+ values: {
1667+ "Unknown": 0,
1668+ "Button": 1,
1669+ "Mouse": 2,
1670+ "TouchPad": 4,
1671+ "TouchScreen": 8,
1672+ "Keyboard": 16,
1673+ "Switch": 32
1674+ }
1675+ }
1676+ }
1677+}
1678
1679=== added file 'plugins/Unity/Platform/Platform.qmltypes'
1680--- plugins/Unity/Platform/Platform.qmltypes 1970-01-01 00:00:00 +0000
1681+++ plugins/Unity/Platform/Platform.qmltypes 2016-03-16 11:49:36 +0000
1682@@ -0,0 +1,20 @@
1683+import QtQuick.tooling 1.1
1684+
1685+// This file describes the plugin-supplied types contained in the library.
1686+// It is used for QML tooling purposes only.
1687+//
1688+// This file was auto-generated by:
1689+// 'qmlplugindump -notrelocatable Unity.Platform 1.0 plugins'
1690+
1691+Module {
1692+ Component {
1693+ name: "Platform"
1694+ prototype: "QObject"
1695+ exports: ["Unity.Platform/Platform 1.0"]
1696+ isCreatable: false
1697+ isSingleton: true
1698+ exportMetaObjectRevisions: [0]
1699+ Property { name: "chassis"; type: "string"; isReadonly: true }
1700+ Property { name: "isPC"; type: "bool"; isReadonly: true }
1701+ }
1702+}
1703
1704=== modified file 'plugins/Utils/CMakeLists.txt'
1705--- plugins/Utils/CMakeLists.txt 2016-02-11 16:59:40 +0000
1706+++ plugins/Utils/CMakeLists.txt 2016-03-16 11:49:36 +0000
1707@@ -24,6 +24,7 @@
1708 timezoneFormatter.cpp
1709 inputeventgenerator.cpp
1710 deviceconfigparser.cpp
1711+ globalfunctions.cpp
1712 plugin.cpp
1713 )
1714
1715
1716=== added file 'plugins/Utils/globalfunctions.cpp'
1717--- plugins/Utils/globalfunctions.cpp 1970-01-01 00:00:00 +0000
1718+++ plugins/Utils/globalfunctions.cpp 2016-03-16 11:49:36 +0000
1719@@ -0,0 +1,56 @@
1720+/*
1721+ * Copyright 2015 Canonical Ltd.
1722+ *
1723+ * This program is free software; you can redistribute it and/or modify
1724+ * it under the terms of the GNU Lesser General Public License as published by
1725+ * the Free Software Foundation; version 3.
1726+ *
1727+ * This program is distributed in the hope that it will be useful,
1728+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1729+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1730+ * GNU Lesser General Public License for more details.
1731+ *
1732+ * You should have received a copy of the GNU Lesser General Public License
1733+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1734+*/
1735+
1736+#include "globalfunctions.h"
1737+
1738+#pragma GCC diagnostic push
1739+#pragma GCC diagnostic ignored "-pedantic"
1740+#include <private/qquickitem_p.h>
1741+#pragma GCC diagnostic pop
1742+#include <QQmlEngine>
1743+
1744+GlobalFunctions::GlobalFunctions(QObject *parent)
1745+ : QObject(parent)
1746+{
1747+}
1748+
1749+QQuickItem *GlobalFunctions::itemAt(QQuickItem* parent, int x, int y, QJSValue matcher)
1750+{
1751+ if (!parent) return nullptr;
1752+ QList<QQuickItem *> children = QQuickItemPrivate::get(parent)->paintOrderChildItems();
1753+
1754+ for (int i = children.count() - 1; i >= 0; --i) {
1755+ QQuickItem *child = children.at(i);
1756+
1757+ // Map coordinates to the child element's coordinate space
1758+ QPointF point = parent->mapToItem(child, QPointF(x, y));
1759+ if (child->isVisible() && point.x() >= 0
1760+ && child->width() >= point.x()
1761+ && point.y() >= 0
1762+ && child->height() >= point.y()) {
1763+ if (!matcher.isCallable()) return child;
1764+
1765+ QQmlEngine* engine = qmlEngine(child);
1766+ if (!engine) return child;
1767+
1768+ QJSValue newObj = engine->newQObject(child);
1769+ if (matcher.call(QJSValueList() << newObj).toBool()) {
1770+ return child;
1771+ }
1772+ }
1773+ }
1774+ return nullptr;
1775+}
1776
1777=== added file 'plugins/Utils/globalfunctions.h'
1778--- plugins/Utils/globalfunctions.h 1970-01-01 00:00:00 +0000
1779+++ plugins/Utils/globalfunctions.h 2016-03-16 11:49:36 +0000
1780@@ -0,0 +1,42 @@
1781+/*
1782+ * Copyright 2015 Canonical Ltd.
1783+ *
1784+ * This program is free software; you can redistribute it and/or modify
1785+ * it under the terms of the GNU Lesser General Public License as published by
1786+ * the Free Software Foundation; version 3.
1787+ *
1788+ * This program is distributed in the hope that it will be useful,
1789+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1790+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1791+ * GNU Lesser General Public License for more details.
1792+ *
1793+ * You should have received a copy of the GNU Lesser General Public License
1794+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1795+*/
1796+
1797+#ifndef GLOBALFUNCTIONS_H
1798+#define GLOBALFUNCTIONS_H
1799+
1800+#include <QObject>
1801+#include <QJSValue>
1802+class QQuickItem;
1803+
1804+/**
1805+ * @brief The GlobalFunctions class
1806+ *
1807+ * This singleton class exposes utility functions to QML
1808+ *
1809+ */
1810+class GlobalFunctions : public QObject
1811+{
1812+ Q_OBJECT
1813+public:
1814+ explicit GlobalFunctions(QObject *parent = 0);
1815+
1816+ static Q_INVOKABLE QQuickItem* itemAt(QQuickItem* parent,
1817+ int x,
1818+ int y,
1819+ QJSValue matcher);
1820+};
1821+
1822+#endif // GLOBALFUNCTIONS_H
1823
1824=== modified file 'plugins/Utils/plugin.cpp'
1825--- plugins/Utils/plugin.cpp 2016-02-11 16:59:40 +0000
1826+++ plugins/Utils/plugin.cpp 2016-03-16 11:49:36 +0000
1827@@ -38,6 +38,7 @@
1828 #include "applicationsfiltermodel.h"
1829 #include "inputeventgenerator.h"
1830 #include "deviceconfigparser.h"
1831+#include "globalfunctions.h"
1832
1833 static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
1834 {
1835@@ -53,6 +54,13 @@
1836 return new Constants();
1837 }
1838
1839+static QObject *createGlobalFunctions(QQmlEngine *engine, QJSEngine *scriptEngine)
1840+{
1841+ Q_UNUSED(engine)
1842+ Q_UNUSED(scriptEngine)
1843+ return new GlobalFunctions();
1844+}
1845+
1846 void UtilsPlugin::registerTypes(const char *uri)
1847 {
1848 Q_ASSERT(uri == QLatin1String("Utils"));
1849@@ -72,6 +80,7 @@
1850 qmlRegisterType<ApplicationsFilterModel>(uri, 0, 1, "ApplicationsFilterModel");
1851 qmlRegisterType<InputEventGenerator>(uri, 0, 1, "InputEventGenerator");
1852 qmlRegisterType<DeviceConfigParser>(uri, 0, 1, "DeviceConfigParser");
1853+ qmlRegisterSingletonType<GlobalFunctions>(uri, 0, 1, "Functions", createGlobalFunctions);
1854 }
1855
1856 void UtilsPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
1857
1858=== modified file 'plugins/Utils/windowstatestorage.cpp'
1859--- plugins/Utils/windowstatestorage.cpp 2015-11-20 15:01:39 +0000
1860+++ plugins/Utils/windowstatestorage.cpp 2016-03-16 11:49:36 +0000
1861@@ -23,9 +23,14 @@
1862 #include <QSqlError>
1863 #include <QSqlResult>
1864 #include <QRect>
1865+#include <unity/shell/application/ApplicationInfoInterface.h>
1866
1867 QMutex WindowStateStorage::s_mutex;
1868
1869+inline QString sanitiseString(QString string) {
1870+ return string.remove("\"").remove("'").remove("\\");
1871+}
1872+
1873 WindowStateStorage::WindowStateStorage(QObject *parent):
1874 QObject(parent)
1875 {
1876@@ -50,7 +55,7 @@
1877 void WindowStateStorage::saveState(const QString &windowId, WindowStateStorage::WindowState state)
1878 {
1879 const QString queryString = QStringLiteral("INSERT OR REPLACE INTO state (windowId, state) values ('%1', '%2');")
1880- .arg(windowId)
1881+ .arg(sanitiseString(windowId))
1882 .arg((int)state);
1883
1884 saveValue(queryString);
1885@@ -59,7 +64,7 @@
1886 WindowStateStorage::WindowState WindowStateStorage::getState(const QString &windowId, WindowStateStorage::WindowState defaultValue) const
1887 {
1888 const QString queryString = QStringLiteral("SELECT * FROM state WHERE windowId = '%1';")
1889- .arg(windowId);
1890+ .arg(sanitiseString(windowId));
1891
1892 QSqlQuery query = getValue(queryString);
1893
1894@@ -72,7 +77,7 @@
1895 void WindowStateStorage::saveGeometry(const QString &windowId, const QRect rect)
1896 {
1897 const QString queryString = QStringLiteral("INSERT OR REPLACE INTO geometry (windowId, x, y, width, height) values ('%1', '%2', '%3', '%4', '%5');")
1898- .arg(windowId)
1899+ .arg(sanitiseString(windowId))
1900 .arg(rect.x())
1901 .arg(rect.y())
1902 .arg(rect.width())
1903@@ -81,6 +86,28 @@
1904 saveValue(queryString);
1905 }
1906
1907+void WindowStateStorage::saveStage(const QString &appId, int stage)
1908+{
1909+ const QString queryString = QStringLiteral("INSERT OR REPLACE INTO stage (appId, stage) values ('%1', '%2');")
1910+ .arg(sanitiseString(appId))
1911+ .arg((int)stage);
1912+
1913+ saveValue(queryString);
1914+}
1915+
1916+int WindowStateStorage::getStage(const QString &appId) const
1917+{
1918+ const QString queryString = QStringLiteral("SELECT * FROM stage WHERE appId = '%1';")
1919+ .arg(sanitiseString(appId));
1920+
1921+ QSqlQuery query = getValue(queryString);
1922+
1923+ if (!query.first()) {
1924+ return unity::shell::application::ApplicationInfoInterface::MainStage;
1925+ }
1926+ return query.value("stage").toInt();
1927+}
1928+
1929 void WindowStateStorage::executeAsyncQuery(const QString &queryString)
1930 {
1931 QMutexLocker l(&s_mutex);
1932@@ -97,7 +124,7 @@
1933 QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect defaultValue) const
1934 {
1935 QString queryString = QStringLiteral("SELECT * FROM geometry WHERE windowId = '%1';")
1936- .arg(windowId);
1937+ .arg(sanitiseString(windowId));
1938
1939 QSqlQuery query = getValue(queryString);
1940
1941@@ -124,6 +151,11 @@
1942 QSqlQuery query;
1943 query.exec(QStringLiteral("CREATE TABLE state(windowId TEXT UNIQUE, state INTEGER);"));
1944 }
1945+
1946+ if (!m_db.tables().contains(QStringLiteral("stage"))) {
1947+ QSqlQuery query;
1948+ query.exec(QStringLiteral("CREATE TABLE stage(appId TEXT UNIQUE, stage INTEGER);"));
1949+ }
1950 }
1951
1952 void WindowStateStorage::saveValue(const QString &queryString)
1953
1954=== modified file 'plugins/Utils/windowstatestorage.h'
1955--- plugins/Utils/windowstatestorage.h 2015-11-20 15:01:39 +0000
1956+++ plugins/Utils/windowstatestorage.h 2016-03-16 11:49:36 +0000
1957@@ -38,6 +38,9 @@
1958 Q_INVOKABLE void saveGeometry(const QString &windowId, const QRect rect);
1959 Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect defaultValue) const;
1960
1961+ Q_INVOKABLE void saveStage(const QString &appId, int stage);
1962+ Q_INVOKABLE int getStage(const QString &appId) const;
1963+
1964 private:
1965 void initdb();
1966
1967
1968=== modified file 'qml/Components/Showable.qml'
1969--- qml/Components/Showable.qml 2015-07-15 15:07:19 +0000
1970+++ qml/Components/Showable.qml 2016-03-16 11:49:36 +0000
1971@@ -31,6 +31,7 @@
1972 property bool required
1973 property bool __shouldShow: false
1974 property bool __skipShowAnimation: false
1975+ property bool __skipHideAnimation: false
1976
1977 property list<QtObject> hides
1978 property var showAnimation
1979@@ -120,12 +121,21 @@
1980 if (!hideAnimation.running) {
1981 hideAnimation.restart()
1982 }
1983+ if (__skipHideAnimation) {
1984+ hideAnimation.complete();
1985+ }
1986 } else {
1987 visible = false
1988 required = false
1989 }
1990
1991 shown = false
1992+ __skipHideAnimation = false;
1993+ }
1994+
1995+ function hideNow() {
1996+ __skipHideAnimation = true;
1997+ hide();
1998 }
1999
2000 Connections {
2001
2002=== modified file 'qml/Shell.qml'
2003--- qml/Shell.qml 2016-03-16 11:49:35 +0000
2004+++ qml/Shell.qml 2016-03-16 11:49:36 +0000
2005@@ -120,9 +120,7 @@
2006 if (ApplicationManager.findApplication(appId)) {
2007 ApplicationManager.requestFocusApplication(appId);
2008 } else {
2009- var execFlags = shell.usageScenario === "phone" ? ApplicationManager.ForceMainStage
2010- : ApplicationManager.NoFlag;
2011- ApplicationManager.startApplication(appId, execFlags);
2012+ ApplicationManager.startApplication(appId);
2013 }
2014 }
2015
2016@@ -547,9 +545,7 @@
2017 greeterShown: greeter.shown
2018 }
2019
2020- readonly property bool topmostApplicationIsFullscreen:
2021- ApplicationManager.focusedApplicationId &&
2022- ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
2023+ readonly property bool topmostApplicationIsFullscreen: mainApp && mainApp.fullscreen
2024
2025 fullscreenMode: (topmostApplicationIsFullscreen && !lightDM.greeter.active && launcher.progress == 0)
2026 || greeter.hasLockedApp
2027
2028=== modified file 'qml/Stages/AbstractStage.qml'
2029--- qml/Stages/AbstractStage.qml 2016-03-10 22:41:22 +0000
2030+++ qml/Stages/AbstractStage.qml 2016-03-16 11:49:36 +0000
2031@@ -44,7 +44,7 @@
2032
2033 // To be read from outside
2034 property var mainApp: null
2035- property int mainAppWindowOrientationAngle
2036+ property int mainAppWindowOrientationAngle: 0
2037 property bool orientationChangesEnabled
2038 property int supportedOrientations: Qt.PortraitOrientation
2039 | Qt.LandscapeOrientation
2040
2041=== modified file 'qml/Stages/ApplicationWindow.qml'
2042--- qml/Stages/ApplicationWindow.qml 2016-02-12 00:10:54 +0000
2043+++ qml/Stages/ApplicationWindow.qml 2016-03-16 11:49:36 +0000
2044@@ -24,12 +24,14 @@
2045 implicitHeight: sessionContainer.implicitHeight
2046
2047 // to be read from outside
2048- readonly property bool fullscreen: application ? application.fullscreen : false
2049 property alias interactive: sessionContainer.interactive
2050 property bool orientationChangesEnabled: d.supportsSurfaceResize ? d.surfaceOldEnoughToBeResized : true
2051 readonly property string title: sessionContainer.surface && sessionContainer.surface.name !== "" ?
2052 sessionContainer.surface.name : d.name
2053
2054+ // overridable from outside
2055+ property bool fullscreen: application ? application.fullscreen : false
2056+
2057 // to be set from outside
2058 property QtObject application
2059 property int surfaceOrientationAngle
2060
2061=== added file 'qml/Stages/SideStage.qml'
2062--- qml/Stages/SideStage.qml 1970-01-01 00:00:00 +0000
2063+++ qml/Stages/SideStage.qml 2016-03-16 11:49:36 +0000
2064@@ -0,0 +1,118 @@
2065+/*
2066+ * Copyright (C) 2016 Canonical, Ltd.
2067+ *
2068+ * This program is free software; you can redistribute it and/or modify
2069+ * it under the terms of the GNU General Public License as published by
2070+ * the Free Software Foundation; version 3.
2071+ *
2072+ * This program is distributed in the hope that it will be useful,
2073+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2074+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2075+ * GNU General Public License for more details.
2076+ *
2077+ * You should have received a copy of the GNU General Public License
2078+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2079+ */
2080+
2081+import QtQuick 2.4
2082+import QtQuick.Layouts 1.1
2083+import Ubuntu.Components 1.3
2084+import Ubuntu.Gestures 0.1
2085+import "../Components"
2086+
2087+Showable {
2088+ id: root
2089+ property bool showHint: true
2090+ property int panelWidth: units.gu(40)
2091+ readonly property alias dragging: hideSideStageDragArea.dragging
2092+ readonly property real progress: width / panelWidth
2093+
2094+ width: 0
2095+ shown: false
2096+
2097+ Item {
2098+ id: sideStageDragHandle
2099+ anchors {
2100+ right: root.left
2101+ top: root.top
2102+ bottom: root.bottom
2103+ }
2104+ width: units.gu(2)
2105+
2106+ opacity: root.shown ? 1 : 0
2107+ Behavior on opacity { UbuntuNumberAnimation {} }
2108+
2109+ Image {
2110+ anchors.centerIn: parent
2111+ width: hideSideStageDragArea.pressed ? parent.width * 2 : parent.width
2112+ height: parent.height
2113+ source: "graphics/sidestage_handle@20.png"
2114+ Behavior on width { UbuntuNumberAnimation {} }
2115+ }
2116+ }
2117+
2118+ Rectangle {
2119+ anchors.fill: parent
2120+ color: Qt.rgba(0,0,0,0.95)
2121+ }
2122+
2123+ Column {
2124+ anchors.verticalCenter: parent.verticalCenter
2125+ width: panelWidth - units.gu(6)
2126+ x: panelWidth/2 - width/2
2127+ spacing: units.gu(3)
2128+ opacity: 0.8
2129+ visible: showHint
2130+
2131+ Icon {
2132+ width: units.gu(30)
2133+ anchors.horizontalCenter: parent.horizontalCenter
2134+ source: "graphics/sidestage_drag.svg"
2135+ color: enabled ? Qt.rgba(1,1,1,1) : Qt.rgba(1,0,0,1)
2136+ keyColor: Qt.rgba(1,1,1,1)
2137+ }
2138+
2139+ Label {
2140+ text: i18n.tr("Drag using 3 fingers any application from one window to the other")
2141+ width: parent.width
2142+ wrapMode: Text.WordWrap
2143+ color: enabled ? Qt.rgba(1,1,1,1) : Qt.rgba(1,0,0,1)
2144+ }
2145+ }
2146+
2147+ showAnimation: NumberAnimation {
2148+ property: "width"
2149+ to: panelWidth
2150+ duration: UbuntuAnimation.BriskDuration
2151+ easing.type: Easing.OutCubic
2152+ }
2153+
2154+ hideAnimation: NumberAnimation {
2155+ property: "width"
2156+ to: 0
2157+ duration: UbuntuAnimation.BriskDuration
2158+ easing.type: Easing.OutCubic
2159+ }
2160+
2161+ DragHandle {
2162+ id: hideSideStageDragArea
2163+ objectName: "hideSideStageDragArea"
2164+
2165+ direction: Direction.Leftwards
2166+ rotation: 180
2167+ enabled: root.shown
2168+ anchors.right: root.left
2169+ width: sideStageDragHandle.width
2170+ height: root.height
2171+ stretch: true
2172+
2173+ immediateRecognition: true
2174+ maxTotalDragDistance: panelWidth
2175+ autoCompleteDragThreshold: panelWidth / 2
2176+ }
2177+
2178+ // SideStage mouse event eater
2179+ MouseArea {
2180+ anchors.fill: parent
2181+ }
2182+}
2183
2184=== modified file 'qml/Stages/SpreadDelegate.qml'
2185--- qml/Stages/SpreadDelegate.qml 2016-03-10 09:19:38 +0000
2186+++ qml/Stages/SpreadDelegate.qml 2016-03-16 11:49:36 +0000
2187@@ -32,6 +32,11 @@
2188 readonly property alias appWindowOrientationAngle: appWindowWithShadow.orientationAngle
2189 readonly property alias appWindowRotation: appWindowWithShadow.rotation
2190 readonly property alias orientationChangesEnabled: appWindow.orientationChangesEnabled
2191+ property int supportedOrientations: application ? application.supportedOrientations :
2192+ Qt.PortraitOrientation
2193+ | Qt.LandscapeOrientation
2194+ | Qt.InvertedPortraitOrientation
2195+ | Qt.InvertedLandscapeOrientation
2196
2197 // to be set from outside
2198 property bool interactive: true
2199@@ -45,6 +50,9 @@
2200 property QtObject orientations
2201 property bool highlightShown: false
2202
2203+ // overrideable from outside
2204+ property alias fullscreen: appWindow.fullscreen
2205+
2206 function matchShellOrientation() {
2207 if (!root.application)
2208 return;
2209@@ -154,7 +162,8 @@
2210 if (!root.application || root.application.rotatesWindowContents) {
2211 return 0;
2212 }
2213- var supportedOrientations = root.application.supportedOrientations;
2214+
2215+ var supportedOrientations = root.supportedOrientations;
2216
2217 if (supportedOrientations === Qt.PrimaryOrientation) {
2218 supportedOrientations = root.orientations.primary;
2219
2220=== modified file 'qml/Stages/SurfaceContainer.qml'
2221--- qml/Stages/SurfaceContainer.qml 2015-11-30 12:18:40 +0000
2222+++ qml/Stages/SurfaceContainer.qml 2016-03-16 11:49:36 +0000
2223@@ -118,6 +118,7 @@
2224
2225
2226 TouchGate {
2227+ objectName: "touchGate-"+name
2228 targetItem: surfaceItem
2229 anchors.fill: root
2230 enabled: surfaceItem.enabled
2231
2232=== added file 'qml/Stages/TabletSideStageTouchGesture.qml'
2233--- qml/Stages/TabletSideStageTouchGesture.qml 1970-01-01 00:00:00 +0000
2234+++ qml/Stages/TabletSideStageTouchGesture.qml 2016-03-16 11:49:36 +0000
2235@@ -0,0 +1,154 @@
2236+/*
2237+ * Copyright (C) 2016 Canonical, Ltd.
2238+ *
2239+ * This program is free software; you can redistribute it and/or modify
2240+ * it under the terms of the GNU General Public License as published by
2241+ * the Free Software Foundation; version 3.
2242+ *
2243+ * This program is distributed in the hope that it will be useful,
2244+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2245+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2246+ * GNU General Public License for more details.
2247+ *
2248+ * You should have received a copy of the GNU General Public License
2249+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2250+ */
2251+
2252+import QtQuick 2.4
2253+import Ubuntu.Gestures 0.1
2254+
2255+TouchGestureArea {
2256+ id: root
2257+ minimumTouchPoints: 3
2258+ maximumTouchPoints: 3
2259+
2260+ property bool enableDrag: true
2261+ property Component dragComponent
2262+ property var dragComponentProperties: undefined
2263+
2264+ readonly property bool recognisedPress: status == TouchGestureArea.Recognized &&
2265+ touchPoints.length >= minimumTouchPoints &&
2266+ touchPoints.length <= maximumTouchPoints
2267+ readonly property bool recognisedDrag: priv.wasRecognisedPress && dragging
2268+
2269+ signal pressed(int x, int y)
2270+ signal clicked
2271+ signal dragStarted
2272+ signal dropped
2273+ signal cancelled
2274+
2275+ onEnabledChanged: {
2276+ if (!enabled) {
2277+ if (priv.dragObject) root.cancelled();
2278+ priv.wasRecognisedDrag = false;
2279+ priv.wasRecognisedPress = false;
2280+ }
2281+ }
2282+
2283+ onRecognisedPressChanged: {
2284+ if (recognisedPress) {
2285+ // get the app at the center of the gesture
2286+ var centerX = 0;
2287+ var centerY = 0;
2288+ for (var i = 0; i < touchPoints.length; i++) {
2289+ centerX += touchPoints[i].x;
2290+ centerY += touchPoints[i].y;
2291+ }
2292+ centerX = centerX/touchPoints.length;
2293+ centerY = centerY/touchPoints.length;
2294+
2295+ pressed(centerX, centerY);
2296+ priv.wasRecognisedPress = true;
2297+ }
2298+ }
2299+
2300+ onStatusChanged: {
2301+ if (status != TouchGestureArea.Recognized) {
2302+ if (status == TouchGestureArea.Rejected) {
2303+ root.cancelled();
2304+ } else if (status == TouchGestureArea.WaitingForTouch) {
2305+ if (priv.wasRecognisedPress) {
2306+ if (!priv.wasRecognisedDrag) {
2307+ root.clicked();
2308+ } else {
2309+ root.dropped();
2310+ }
2311+ }
2312+ }
2313+ priv.wasRecognisedDrag = false;
2314+ priv.wasRecognisedPress = false;
2315+ }
2316+ }
2317+
2318+ onRecognisedDragChanged: {
2319+ if (enableDrag && recognisedDrag) {
2320+ priv.wasRecognisedDrag = true;
2321+ root.dragStarted()
2322+ }
2323+ }
2324+
2325+ QtObject {
2326+ id: priv
2327+ property var dragObject: null
2328+
2329+ property bool wasRecognisedPress: false
2330+ property bool wasRecognisedDrag: false
2331+ }
2332+
2333+ onCancelled: {
2334+ if (priv.dragObject) {
2335+ var obj = priv.dragObject;
2336+ priv.dragObject = null;
2337+
2338+ obj.Drag.cancel();
2339+ obj.destroy();
2340+ }
2341+ }
2342+
2343+ onDragStarted: {
2344+ if (dragComponentProperties) {
2345+ priv.dragObject = dragComponent.createObject(root, dragComponentProperties);
2346+ } else {
2347+ priv.dragObject = dragComponent.createObject(root);
2348+ }
2349+ priv.dragObject.Drag.start();
2350+ }
2351+
2352+ onDropped: {
2353+ if (priv.dragObject) {
2354+ var obj = priv.dragObject;
2355+ priv.dragObject = null;
2356+
2357+ obj.Drag.drop();
2358+ obj.destroy();
2359+ }
2360+ }
2361+
2362+ Binding {
2363+ target: priv.dragObject
2364+ when: priv.dragObject && priv.wasRecognisedDrag
2365+ property: "x"
2366+ value: {
2367+ if (!priv.dragObject) return 0;
2368+ var sum = 0;
2369+ for (var i = 0; i < root.touchPoints.length; i++) {
2370+ sum += root.touchPoints[i].x;
2371+ }
2372+ return sum/root.touchPoints.length - priv.dragObject.width/2;
2373+ }
2374+ }
2375+
2376+ Binding {
2377+ target: priv.dragObject
2378+ when: priv.dragObject && priv.wasRecognisedDrag
2379+ property: "y"
2380+ value: {
2381+ if (!priv.dragObject) return 0;
2382+ var sum = 0;
2383+ for (var i = 0; i < root.touchPoints.length; i++) {
2384+ sum += root.touchPoints[i].y;
2385+ }
2386+ return sum/root.touchPoints.length - priv.dragObject.height/2;
2387+ }
2388+ }
2389+}
2390
2391=== modified file 'qml/Stages/TabletStage.qml'
2392--- qml/Stages/TabletStage.qml 2016-03-10 09:19:38 +0000
2393+++ qml/Stages/TabletStage.qml 2016-03-16 11:49:36 +0000
2394@@ -46,7 +46,7 @@
2395 if (delta < 0) { delta += 360; }
2396 delta = delta % 360;
2397
2398- var supportedOrientations = spreadDelegate.application.supportedOrientations;
2399+ var supportedOrientations = spreadDelegate.supportedOrientations;
2400 if (supportedOrientations === Qt.PrimaryOrientation) {
2401 supportedOrientations = spreadDelegate.orientations.primary;
2402 }
2403@@ -78,9 +78,24 @@
2404
2405 orientationChangesEnabled: priv.mainAppOrientationChangesEnabled
2406
2407- supportedOrientations: mainApp ? mainApp.supportedOrientations
2408- : (Qt.PortraitOrientation | Qt.LandscapeOrientation
2409- | Qt.InvertedPortraitOrientation | Qt.InvertedLandscapeOrientation)
2410+ supportedOrientations: {
2411+ if (mainApp) {
2412+ var orientations = mainApp.supportedOrientations;
2413+ orientations |= Qt.LandscapeOrientation | Qt.InvertedLandscapeOrientation;
2414+ if (priv.sideStageAppId && !spreadView.surfaceDragging) {
2415+ // If we have a sidestage app, support Portrait orientation
2416+ // so that it will switch the sidestage app to mainstage on rotate
2417+ orientations |= Qt.PortraitOrientation|Qt.InvertedPortraitOrientation;
2418+ }
2419+ return orientations;
2420+ } else {
2421+ // we just don't care
2422+ return Qt.PortraitOrientation |
2423+ Qt.LandscapeOrientation |
2424+ Qt.InvertedPortraitOrientation |
2425+ Qt.InvertedLandscapeOrientation;
2426+ }
2427+ }
2428
2429 onWidthChanged: {
2430 spreadView.selectedIndex = -1;
2431@@ -88,13 +103,6 @@
2432 spreadView.contentX = -spreadView.shift;
2433 }
2434
2435- onShellOrientationChanged: {
2436- if (shellOrientation == Qt.PortraitOrientation || shellOrientation == Qt.InvertedPortraitOrientation) {
2437- ApplicationManager.focusApplication(priv.mainStageAppId);
2438- priv.sideStageAppId = "";
2439- }
2440- }
2441-
2442 onInverseProgressChanged: {
2443 // This can't be a simple binding because that would be triggered after this handler
2444 // while we need it active before doing the anition left/right
2445@@ -170,20 +178,7 @@
2446
2447 property int highlightIndex: 0
2448
2449- onFocusedAppIdChanged: {
2450- if (priv.focusedAppId.length > 0) {
2451- var focusedApp = ApplicationManager.findApplication(focusedAppId);
2452- if (focusedApp.stage == ApplicationInfoInterface.SideStage) {
2453- priv.sideStageAppId = focusedAppId;
2454- } else {
2455- priv.mainStageAppId = focusedAppId;
2456- root.mainApp = focusedApp;
2457- }
2458- }
2459-
2460- appId0 = ApplicationManager.count >= 1 ? ApplicationManager.get(0).appId : "";
2461- appId1 = ApplicationManager.count > 1 ? ApplicationManager.get(1).appId : "";
2462- }
2463+ onFocusedAppIdChanged: updateStageApps()
2464
2465 onFocusedAppDelegateChanged: {
2466 if (focusedAppDelegate) {
2467@@ -229,6 +224,46 @@
2468 onHighlightIndexChanged: {
2469 spreadView.contentX = highlightIndex * spreadView.contentWidth / (spreadRepeater.count + 2)
2470 }
2471+
2472+ function getTopApp(stage) {
2473+ for (var i = 0; i < ApplicationManager.count; i++) {
2474+ var app = ApplicationManager.get(i)
2475+ if (app.stage === stage) {
2476+ return app;
2477+ }
2478+ }
2479+ return null;
2480+ }
2481+
2482+ function setAppStage(appId, stage, save) {
2483+ var app = ApplicationManager.findApplication(appId);
2484+ if (app) {
2485+ app.stage = stage;
2486+ if (save) {
2487+ WindowStateStorage.saveStage(appId, stage);
2488+ }
2489+ }
2490+ }
2491+
2492+ function updateStageApps() {
2493+ var app = priv.getTopApp(ApplicationInfoInterface.MainStage);
2494+ priv.mainStageAppId = app ? app.appId : ""
2495+ root.mainApp = app;
2496+
2497+ if (sideStage.shown) {
2498+ app = priv.getTopApp(ApplicationInfoInterface.SideStage);
2499+ priv.sideStageAppId = app ? app.appId : ""
2500+ } else {
2501+ priv.sideStageAppId = "";
2502+ }
2503+
2504+ appId0 = ApplicationManager.count >= 1 ? ApplicationManager.get(0).appId : "";
2505+ appId1 = ApplicationManager.count > 1 ? ApplicationManager.get(1).appId : "";
2506+ }
2507+
2508+ readonly property bool sideStageEnabled: root.shellOrientation == Qt.LandscapeOrientation ||
2509+ root.shellOrientation == Qt.InvertedLandscapeOrientation
2510+ Component.onCompleted: updateStageApps();
2511 }
2512
2513 Connections {
2514@@ -256,7 +291,8 @@
2515 ApplicationManager.focusApplication("unity8-dash")
2516 }
2517 if (priv.sideStageAppId == appId) {
2518- priv.sideStageAppId = "";
2519+ var app = priv.getTopApp(ApplicationInfoInterface.SideStage);
2520+ priv.sideStageAppId = app === null ? "" : app.appId;
2521 }
2522
2523 if (ApplicationManager.count == 0) {
2524@@ -267,6 +303,7 @@
2525 // lets make sure the spread doesn't mess up by the changing app list.
2526 spreadView.phase = 0;
2527 spreadView.contentX = -spreadView.shift;
2528+
2529 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
2530 }
2531 }
2532@@ -322,6 +359,7 @@
2533 property int selectedIndex: -1
2534 property int draggedDelegateCount: 0
2535 property int closingIndex: -1
2536+ property var selectedApplication: selectedIndex !== -1 ? ApplicationManager.get(selectedIndex) : null
2537
2538 // FIXME: Workaround Flickable's not keepping its contentX still when resized
2539 onContentXChanged: { forceItToRemainStillIfBeingResized(); }
2540@@ -343,15 +381,8 @@
2541 }
2542 }
2543
2544- property bool sideStageDragging: sideStageDragHandle.dragging
2545- property real sideStageDragProgress: sideStageDragHandle.progress
2546-
2547- onSideStageDragProgressChanged: {
2548- if (sideStageDragProgress == 1) {
2549- ApplicationManager.focusApplication(priv.mainStageAppId);
2550- priv.sideStageAppId = "";
2551- }
2552- }
2553+ property real sideStageDragProgress: sideStage.progress
2554+ property bool surfaceDragging: triGestureArea.recognisedDrag
2555
2556 // In case the ApplicationManager already holds an app when starting up we're missing animations
2557 // Make sure we end up in the same state
2558@@ -402,7 +433,7 @@
2559 }
2560 ]
2561 state: {
2562- if (priv.mainStageAppId && !priv.sideStageAppId) {
2563+ if ((priv.mainStageAppId && !priv.sideStageAppId) || !priv.sideStageEnabled) {
2564 return "main";
2565 }
2566 if (!priv.mainStageAppId && priv.sideStageAppId) {
2567@@ -419,12 +450,26 @@
2568 // Flickabe.contentX wiggles during resizes. Don't react to it.
2569 return;
2570 }
2571- if (spreadView.phase == 0 && spreadView.shiftedContentX > spreadView.width * spreadView.positionMarker2) {
2572- spreadView.phase = 1;
2573- } else if (spreadView.phase == 1 && spreadView.shiftedContentX > spreadView.width * spreadView.positionMarker4) {
2574- spreadView.phase = 2;
2575- } else if (spreadView.phase == 1 && spreadView.shiftedContentX < spreadView.width * spreadView.positionMarker2) {
2576- spreadView.phase = 0;
2577+
2578+ switch (phase) {
2579+ case 0:
2580+ // the "spreadEnabled" part is because when code does "phase = 0; contentX = -shift" to
2581+ // dismiss the spread because spreadEnabled went to false, for some reason, during tests,
2582+ // Flickable might jump in and change contentX value back, causing the code below to do
2583+ // "phase = 1" which will make the spread stay.
2584+ // It sucks that we have no control whatsoever over whether or when Flickable animates its
2585+ // contentX.
2586+ if (root.spreadEnabled && shiftedContentX > width * positionMarker2) {
2587+ phase = 1;
2588+ }
2589+ break;
2590+ case 1:
2591+ if (shiftedContentX < width * positionMarker2) {
2592+ phase = 0;
2593+ } else if (shiftedContentX >= width * positionMarker4 && !spreadDragArea.dragging) {
2594+ phase = 2;
2595+ }
2596+ break;
2597 }
2598 }
2599
2600@@ -446,6 +491,7 @@
2601 }
2602
2603 function snapTo(index) {
2604+ snapAnimation.stop();
2605 spreadView.selectedIndex = index;
2606 snapAnimation.targetContentX = -shift;
2607 snapAnimation.start();
2608@@ -455,55 +501,41 @@
2609 // We don't want to really reorder them in the model because that allows us to keep track
2610 // of the last focused order.
2611 function indexToZIndex(index) {
2612+ // only shuffle when we've got a main and overlay
2613+ if (state !== "mainAndOverlay") return index;
2614+
2615 var app = ApplicationManager.get(index);
2616 if (!app) {
2617 return index;
2618 }
2619
2620- var active = app.appId == priv.mainStageAppId || app.appId == priv.sideStageAppId;
2621- if (active && app.stage == ApplicationInfoInterface.MainStage) {
2622- // if this app is active, and its the MainStage, always put it to index 0
2623+ // don't shuffle indexes greater than "actives or next"
2624+ if (index > 2) return index;
2625+
2626+ if (app.appId === priv.mainStageAppId) {
2627+ // Active main stage always at 0
2628 return 0;
2629 }
2630- if (active && app.stage == ApplicationInfoInterface.SideStage) {
2631- if (!priv.mainStageAppId) {
2632- // Only have SS apps running. Put the active one at 0
2633- return 0;
2634- }
2635-
2636- // Precondition now: There's an active MS app and this is SS app:
2637- if (spreadView.nextInStack >= 0 && ApplicationManager.get(spreadView.nextInStack).stage == ApplicationInfoInterface.MainStage) {
2638- // If the next app coming from the right is a MS app, we need to elevate this SS ap above it.
2639- // Put it to at least level 2, or higher if there's more apps coming in before this one.
2640- return Math.max(index, 2);
2641- } else {
2642- // if this is no next app to come in from the right, place this one at index 1, just on top the active MS app.
2643- return 1;
2644- }
2645- }
2646- if (index <= 2 && app.stage == ApplicationInfoInterface.MainStage && priv.sideStageAppId) {
2647- // Ok, this is an inactive MS app. If there's an active SS app around, we need to place this one
2648- // in between the active MS app and the active SS app, so that it comes in from there when dragging from the right.
2649- // If there's now active SS app, just leave it where it is.
2650- return priv.indexOf(priv.sideStageAppId) < index ? index - 1 : index;
2651- }
2652- if (index == spreadView.nextInStack && app.stage == ApplicationInfoInterface.SideStage) {
2653- // This is a SS app and the next one to come in from the right:
2654- if (priv.sideStageAppId && priv.mainStageAppId) {
2655- // If there's both, an active MS and an active SS app, put this one right on top of that
2656- return 2;
2657- }
2658- // Or if there's only one other active app, put it on top of that.
2659- // The case that there isn't any other active app is already handled above.
2660- return 1;
2661- }
2662- if (index == 2 && spreadView.nextInStack == 1 && priv.sideStageAppId) {
2663- // If its index 2 but not the next one to come in, it means
2664- // we've pulled another one down to index 2. Move this one up to 2 instead.
2665- return 3;
2666- }
2667- // don't touch all others... (mostly index > 3 + simple cases where the above doesn't shuffle much)
2668- return index;
2669+
2670+ if (spreadView.nextInStack > 0) {
2671+ var nextAppInStack = ApplicationManager.get(spreadView.nextInStack);
2672+
2673+ if (index === spreadView.nextInStack) {
2674+ // this is the next app in stack.
2675+
2676+ if (app.stage === ApplicationInfoInterface.SideStage) {
2677+ // if the next app in stack is a sidestage app, it must order on top of other side stage app
2678+ return Math.min(2, ApplicationManager.count-1);
2679+ }
2680+ return 1;
2681+ }
2682+ if (nextAppInStack.stage === ApplicationInfoInterface.SideStage) {
2683+ // if the next app in stack is a sidestage app, it must order on top of other side stage app
2684+ return 1;
2685+ }
2686+ return Math.min(2, ApplicationManager.count-1);
2687+ }
2688+ return Math.min(index+1, ApplicationManager.count-1);
2689 }
2690
2691 SequentialAnimation {
2692@@ -521,8 +553,12 @@
2693 script: {
2694 if (spreadView.selectedIndex >= 0) {
2695 var newIndex = spreadView.selectedIndex;
2696+ var application = ApplicationManager.get(newIndex);
2697+ if (application.stage === ApplicationInfoInterface.SideStage) {
2698+ sideStage.showNow();
2699+ }
2700 spreadView.selectedIndex = -1;
2701- ApplicationManager.focusApplication(ApplicationManager.get(newIndex).appId);
2702+ ApplicationManager.focusApplication(application.appId);
2703 spreadView.phase = 0;
2704 spreadView.contentX = -spreadView.shift;
2705 }
2706@@ -545,87 +581,82 @@
2707 spreadView.snapTo(0);
2708 }
2709
2710- Rectangle {
2711- id: sideStageBackground
2712- color: "black"
2713- width: spreadView.sideStageWidth * (1 - sideStageDragHandle.progress)
2714+ DropArea {
2715+ objectName: "MainStageDropArea"
2716+ anchors {
2717+ left: parent.left
2718+ top: parent.top
2719+ bottom: parent.bottom
2720+ }
2721+ width: spreadView.width - sideStage.width
2722+ enabled: priv.sideStageEnabled
2723+
2724+ onDropped: {
2725+ priv.setAppStage(drag.source.appId, ApplicationInfoInterface.MainStage, true);
2726+ ApplicationManager.focusApplication(drag.source.appId);
2727+ }
2728+ keys: "SideStage"
2729+ }
2730+
2731+ SideStage {
2732+ id: sideStage
2733+ objectName: "sideStage"
2734 height: priv.landscapeHeight
2735 x: spreadView.width - width
2736- z: spreadView.indexToZIndex(priv.indexOf(priv.sideStageAppId))
2737- opacity: spreadView.phase == 0 ? 1 : 0
2738- Behavior on opacity { UbuntuNumberAnimation {} }
2739- }
2740-
2741- Item {
2742- id: sideStageDragHandle
2743- anchors.right: sideStageBackground.left
2744- anchors.top: sideStageBackground.top
2745- width: units.gu(2)
2746- height: priv.landscapeHeight
2747- z: sideStageBackground.z
2748- opacity: spreadView.phase <= 0 && spreadView.sideStageVisible ? 1 : 0
2749- property real progress: 0
2750- property bool dragging: false
2751-
2752- Behavior on opacity { UbuntuNumberAnimation {} }
2753-
2754- Connections {
2755- target: spreadView
2756- onSideStageVisibleChanged: {
2757- if (spreadView.sideStageVisible) {
2758- sideStageDragHandle.progress = 0;
2759+ z: {
2760+ if (!priv.mainStageAppId) return 0;
2761+
2762+ if (priv.sideStageAppId && spreadView.nextInStack > 0) {
2763+ var nextAppInStack = ApplicationManager.get(spreadView.nextInStack);
2764+
2765+ if (nextAppInStack.stage === ApplicationInfoInterface.MainStage) {
2766+ // if the next app in stack is a main stage app, put the sidestage on top of it.
2767+ return 2;
2768 }
2769- }
2770- }
2771-
2772- Image {
2773- anchors.centerIn: parent
2774- width: sideStageDragHandleMouseArea.pressed ? parent.width * 2 : parent.width
2775- height: parent.height
2776- source: "graphics/sidestage_handle@20.png"
2777- Behavior on width { UbuntuNumberAnimation {} }
2778- }
2779-
2780- MouseArea {
2781- id: sideStageDragHandleMouseArea
2782+ return 1;
2783+ }
2784+
2785+ return 1;
2786+ }
2787+ visible: progress != 0
2788+ enabled: priv.sideStageEnabled && sideStageDropArea.dropAllowed
2789+ opacity: priv.sideStageEnabled && !spreadView.active ? 1 : 0
2790+ Behavior on opacity { UbuntuNumberAnimation {} }
2791+
2792+ onShownChanged: {
2793+ if (!shown && ApplicationManager.focusedApplicationId == priv.sideStageAppId) {
2794+ ApplicationManager.requestFocusApplication(priv.mainStageAppId);
2795+ }
2796+ priv.updateStageApps();
2797+ if (shown && priv.sideStageAppId) {
2798+ ApplicationManager.requestFocusApplication(priv.sideStageAppId);
2799+ }
2800+ }
2801+
2802+ DropArea {
2803+ id: sideStageDropArea
2804+ objectName: "SideStageDropArea"
2805 anchors.fill: parent
2806- enabled: spreadView.shiftedContentX == 0
2807- property int startX
2808- property var gesturePoints: new Array()
2809- property real totalDiff
2810-
2811- onPressed: {
2812- gesturePoints = [];
2813- startX = mouseX;
2814- totalDiff = 0.0;
2815- sideStageDragHandle.progress = 0;
2816- sideStageDragHandle.dragging = true;
2817- }
2818- onMouseXChanged: {
2819- totalDiff += mouseX - startX;
2820- if (priv.mainStageAppId) {
2821- sideStageDragHandle.progress = Math.max(0, totalDiff / spreadView.sideStageWidth);
2822- }
2823- gesturePoints.push(mouseX);
2824- }
2825- onReleased: {
2826- if (priv.mainStageAppId) {
2827- var oneWayFlick = priv.evaluateOneWayFlick(gesturePoints);
2828- sideStageDragSnapAnimation.to = sideStageDragHandle.progress > 0.5 || oneWayFlick ? 1 : 0;
2829- sideStageDragSnapAnimation.start();
2830- } else {
2831- sideStageDragHandle.dragging = false;
2832- }
2833- }
2834- }
2835- UbuntuNumberAnimation {
2836- id: sideStageDragSnapAnimation
2837- target: sideStageDragHandle
2838- property: "progress"
2839-
2840- onRunningChanged: {
2841- if (!running) {
2842- sideStageDragHandle.dragging = false;
2843+
2844+ property bool dropAllowed: true
2845+
2846+ onEntered: {
2847+ dropAllowed = drag.keys != "Disabled";
2848+ }
2849+ onExited: {
2850+ dropAllowed = true;
2851+ }
2852+ onDropped: {
2853+ if (drop.keys == "MainStage") {
2854+ priv.setAppStage(drop.source.appId, ApplicationInfoInterface.SideStage, true);
2855+ ApplicationManager.requestFocusApplication(drop.source.appId);
2856+ }
2857+ }
2858+ drag {
2859+ onSourceChanged: {
2860+ if (!sideStageDropArea.drag.source) {
2861+ dropAllowed = true;
2862+ }
2863 }
2864 }
2865 }
2866@@ -640,37 +671,53 @@
2867 id: spreadTile
2868 objectName: model.appId ? "tabletSpreadDelegate_" + model.appId
2869 : "tabletSpreadDelegate_null";
2870- width: {
2871- if (wantsMainStage) {
2872- return spreadView.width;
2873- } else {
2874- return spreadView.sideStageWidth;
2875- }
2876- }
2877- height: {
2878- if (wantsMainStage) {
2879- return spreadView.height;
2880- } else {
2881- return priv.landscapeHeight;
2882- }
2883- }
2884- active: model.appId == priv.mainStageAppId || model.appId == priv.sideStageAppId
2885- zIndex: spreadView.indexToZIndex(index)
2886+ width: spreadView.width
2887+ height: spreadView.height
2888+ active: appId == priv.mainStageAppId || appId == priv.sideStageAppId
2889+ zIndex: selected && stage == ApplicationInfoInterface.MainStage ? 0 : spreadView.indexToZIndex(index)
2890 selected: spreadView.selectedIndex == index
2891 otherSelected: spreadView.selectedIndex >= 0 && !selected
2892- isInSideStage: priv.sideStageAppId == model.appId
2893+ isInSideStage: priv.sideStageAppId === appId
2894 interactive: !spreadView.interactive && spreadView.phase === 0 && root.interactive
2895 swipeToCloseEnabled: spreadView.interactive && !snapAnimation.running
2896 maximizedAppTopMargin: root.maximizedAppTopMargin
2897- dragOffset: !isDash && model.appId == priv.mainStageAppId && root.inverseProgress > 0 && spreadView.phase === 0 ? root.inverseProgress : 0
2898+ dragOffset: !isDash && appId == priv.mainStageAppId && root.inverseProgress > 0 && spreadView.phase === 0 ? root.inverseProgress : 0
2899 application: ApplicationManager.get(index)
2900 closeable: !isDash
2901 highlightShown: root.altTabPressed && priv.highlightIndex == zIndex
2902
2903 readonly property bool wantsMainStage: model.stage == ApplicationInfoInterface.MainStage
2904
2905+ readonly property string appId: model.appId
2906 readonly property bool isDash: model.appId == "unity8-dash"
2907
2908+ stage: model.stage
2909+ fullscreen: {
2910+ if (mainApp && stage === ApplicationInfoInterface.SideStage) {
2911+ return mainApp.fullscreen;
2912+ }
2913+ return application ? application.fullscreen : false;
2914+ }
2915+
2916+ supportedOrientations: {
2917+ if (application) {
2918+ var orientations = application.supportedOrientations;
2919+ if (stage == ApplicationInfoInterface.MainStage) {
2920+ // When an app is in the mainstage, it always supports Landscape|InvertedLandscape
2921+ // so that we can drag it from the main stage to the side stage
2922+ orientations |= Qt.LandscapeOrientation | Qt.InvertedLandscapeOrientation;
2923+ }
2924+ return orientations;
2925+ } else {
2926+ // we just don't care
2927+ return Qt.PortraitOrientation |
2928+ Qt.LandscapeOrientation |
2929+ Qt.InvertedPortraitOrientation |
2930+ Qt.InvertedLandscapeOrientation;
2931+ }
2932+ }
2933+
2934+
2935 Binding {
2936 target: spreadTile.application
2937 property: "exemptFromLifecycle"
2938@@ -701,6 +748,28 @@
2939 enabled: spreadView.closingIndex >= 0
2940 UbuntuNumberAnimation {}
2941 }
2942+ Connections {
2943+ target: priv
2944+ onSideStageEnabledChanged: refreshStage()
2945+ }
2946+
2947+ Component.onCompleted: {
2948+ refreshStage()
2949+ stageChanged.connect(priv.updateStageApps);
2950+ }
2951+
2952+ function refreshStage() {
2953+ var stage = ApplicationInfoInterface.MainStage;
2954+ if (priv.sideStageEnabled) {
2955+ if (application && application.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) {
2956+ stage = WindowStateStorage.getStage(appId);
2957+ }
2958+ }
2959+
2960+ if (model.stage !== stage) {
2961+ priv.setAppStage(appId, stage, false);
2962+ }
2963+ }
2964
2965 // This is required because none of the bindings are triggered in some cases:
2966 // When an app is closed, it might happen that ApplicationManager.get(nextInStack)
2967@@ -725,7 +794,7 @@
2968
2969 // TODO: Hiding tile when progress is such that it will be off screen.
2970 property bool occluded: {
2971- if (spreadView.active) return false;
2972+ if (spreadView.active && !offScreen) return false;
2973 else if (spreadTile.active) return false;
2974 else if (xTranslateAnimating) return false;
2975 else if (z <= 1 && priv.focusedAppDelegateIsDislocated) return false;
2976@@ -749,17 +818,87 @@
2977 return progress;
2978 }
2979
2980- shellOrientationAngle: wantsMainStage ? root.shellOrientationAngle : 0
2981- shellOrientation: wantsMainStage ? root.shellOrientation : Qt.PortraitOrientation
2982- orientations: Orientations {
2983- primary: spreadTile.wantsMainStage ? root.orientations.primary : Qt.PortraitOrientation
2984- native_: spreadTile.wantsMainStage ? root.orientations.native_ : Qt.PortraitOrientation
2985+ shellOrientationAngle: root.shellOrientationAngle
2986+ shellOrientation: root.shellOrientation
2987+ orientations: root.orientations
2988+
2989+ states: [
2990+ State {
2991+ name: "MainStage"
2992+ when: spreadTile.stage == ApplicationInfoInterface.MainStage
2993+ },
2994+ State {
2995+ name: "SideStage"
2996+ when: spreadTile.stage == ApplicationInfoInterface.SideStage
2997+
2998+ PropertyChanges {
2999+ target: spreadTile
3000+ width: spreadView.sideStageWidth
3001+ height: priv.landscapeHeight
3002+
3003+ supportedOrientations: Qt.PortraitOrientation
3004+ shellOrientationAngle: 0
3005+ shellOrientation: Qt.PortraitOrientation
3006+ orientations: sideStageOrientations
3007+ }
3008+ }
3009+ ]
3010+
3011+ Orientations {
3012+ id: sideStageOrientations
3013+ primary: Qt.PortraitOrientation
3014+ native_: Qt.PortraitOrientation
3015 portrait: root.orientations.portrait
3016 invertedPortrait: root.orientations.invertedPortrait
3017 landscape: root.orientations.landscape
3018 invertedLandscape: root.orientations.invertedLandscape
3019 }
3020
3021+ transitions: [
3022+ Transition {
3023+ to: "SideStage"
3024+ SequentialAnimation {
3025+ PropertyAction {
3026+ target: spreadTile
3027+ properties: "width,height,supportedOrientations,shellOrientationAngle,shellOrientation,orientations"
3028+ }
3029+ ScriptAction {
3030+ script: {
3031+ // rotate immediately.
3032+ spreadTile.matchShellOrientation();
3033+ if (ApplicationManager.focusedApplicationId === spreadTile.appId &&
3034+ priv.sideStageEnabled && !sideStage.shown) {
3035+ // Sidestage was focused, so show the side stage.
3036+ sideStage.show();
3037+ // if we've switched to a main app which doesnt support portrait, hide the side stage.
3038+ } else if (mainApp && (mainApp.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) == 0) {
3039+ sideStage.hideNow();
3040+ }
3041+ }
3042+ }
3043+ }
3044+ },
3045+ Transition {
3046+ from: "SideStage"
3047+ SequentialAnimation {
3048+ ScriptAction {
3049+ script: {
3050+ if (priv.sideStageAppId === spreadTile.appId &&
3051+ mainApp && (mainApp.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) == 0) {
3052+ // The mainstage app did not natively support portrait orientation, so focus the sidestage.
3053+ ApplicationManager.requestFocusApplication(spreadTile.appId);
3054+ }
3055+ }
3056+ }
3057+ PropertyAction {
3058+ target: spreadTile
3059+ properties: "width,height,supportedOrientations,shellOrientationAngle,shellOrientation,orientations"
3060+ }
3061+ ScriptAction { script: { spreadTile.matchShellOrientation(); } }
3062+ }
3063+ }
3064+ ]
3065+
3066 onClicked: {
3067 if (spreadView.phase == 2) {
3068 spreadView.snapTo(index);
3069@@ -779,6 +918,16 @@
3070 ApplicationManager.stopApplication(ApplicationManager.get(index).appId);
3071 }
3072
3073+ onFocusChanged: {
3074+ if (focus && ApplicationManager.focusedApplicationId !== appId) {
3075+ ApplicationManager.focusApplication(appId);
3076+ }
3077+
3078+ if (focus && priv.sideStageEnabled && stage === ApplicationInfoInterface.SideStage) {
3079+ sideStage.show();
3080+ }
3081+ }
3082+
3083 Binding {
3084 target: root
3085 when: model.appId == priv.mainStageAppId
3086@@ -803,6 +952,71 @@
3087 }
3088 }
3089
3090+ TabletSideStageTouchGesture {
3091+ id: triGestureArea
3092+ anchors.fill: parent
3093+ enabled: priv.sideStageEnabled && !spreadView.active
3094+ property var dragObject: null
3095+ property string appId: ""
3096+ dragComponent: dragComponent
3097+ dragComponentProperties: { "appId": appId }
3098+
3099+ onPressed: {
3100+ function matchDelegate(obj) { return String(obj.objectName).indexOf("tabletSpreadDelegate") >= 0; }
3101+
3102+ var delegateAtCenter = Functions.itemAt(spreadRow, x, y, matchDelegate);
3103+ if (!delegateAtCenter) return;
3104+
3105+ appId = delegateAtCenter.appId;
3106+ }
3107+
3108+ onClicked: {
3109+ if (sideStage.shown) {
3110+ sideStage.hide();
3111+ } else {
3112+ sideStage.show();
3113+ }
3114+ }
3115+
3116+ onDragStarted: {
3117+ // If we're dragging to the sidestage.
3118+ if (!sideStage.shown) {
3119+ sideStage.show();
3120+ }
3121+ }
3122+
3123+ Component {
3124+ id: dragComponent
3125+ SessionContainer {
3126+ property string appId: ""
3127+ property var application: ApplicationManager.findApplication(appId)
3128+
3129+ session: application ? application.session : null
3130+ interactive: false
3131+ resizeSurface: false
3132+ focus: false
3133+
3134+ width: units.gu(40)
3135+ height: units.gu(40)
3136+
3137+ Drag.hotSpot.x: width/2
3138+ Drag.hotSpot.y: height/2
3139+ // only accept opposite stage.
3140+ Drag.keys: {
3141+ if (!application) return "Disabled";
3142+
3143+ if (application.stage === ApplicationInfo.MainStage) {
3144+ if (application.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) {
3145+ return "MainStage";
3146+ }
3147+ return "Disabled";
3148+ }
3149+ return "SideStage";
3150+ }
3151+ }
3152+ }
3153+ }
3154+
3155 //eat touch events during the right edge gesture
3156 MouseArea {
3157 anchors.fill: parent
3158@@ -812,7 +1026,8 @@
3159 DirectionalDragArea {
3160 id: spreadDragArea
3161 objectName: "spreadDragArea"
3162- anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
3163+ x: parent.width - root.dragAreaWidth
3164+ anchors { top: parent.top; bottom: parent.bottom }
3165 width: root.dragAreaWidth
3166 direction: Direction.Leftwards
3167 enabled: (spreadView.phase != 2 && root.spreadEnabled) || dragging
3168
3169=== modified file 'qml/Stages/TransformedTabletSpreadDelegate.qml'
3170--- qml/Stages/TransformedTabletSpreadDelegate.qml 2015-11-04 14:58:05 +0000
3171+++ qml/Stages/TransformedTabletSpreadDelegate.qml 2016-03-16 11:49:36 +0000
3172@@ -31,6 +31,7 @@
3173 // Set this to true when this tile a currently active on either the MS or the SS.
3174 property bool active: false
3175
3176+ property int stage
3177 property int zIndex
3178 property real progress: 0
3179 property real animatedProgress: 0
3180@@ -49,10 +50,11 @@
3181
3182 property int dragOffset: 0
3183 readonly property alias xTranslateAnimating: xTranslateAnimation.running
3184+ readonly property bool offScreen: priv.xTranslate >= 0
3185
3186 dropShadow: spreadView.active ||
3187 (active
3188- && (model.stage == ApplicationInfoInterface.MainStage || !priv.shellIsLandscape)
3189+ && (stage == ApplicationInfoInterface.MainStage || !priv.shellIsLandscape)
3190 && priv.xTranslate != 0)
3191
3192 onSelectedChanged: {
3193@@ -141,9 +143,9 @@
3194 enabled: !spreadView.active &&
3195 !snapAnimation.running &&
3196 model.appId !== "unity8-dash" &&
3197- !spreadView.sideStageDragging &&
3198 spreadView.animateX &&
3199- !spreadView.beingResized
3200+ !spreadView.beingResized &&
3201+ priv.state !== "sideStage"
3202 UbuntuNumberAnimation {
3203 id: xTranslateAnimation
3204 duration: UbuntuAnimation.FastDuration
3205@@ -153,21 +155,20 @@
3206 property real xTranslate: {
3207 var newTranslate = 0;
3208
3209- if (otherSelected) {
3210- return priv.selectedXTranslate;
3211- }
3212-
3213- if (isSelected) {
3214- if (model.stage == ApplicationInfoInterface.MainStage) {
3215+ // selected app or opposite stage active app.
3216+ if (isSelected || (otherSelected && root.active && spreadView.selectedApplication && spreadView.selectedApplication.stage !== stage)) {
3217+ if (stage == ApplicationInfoInterface.MainStage) {
3218 return linearAnimation(selectedProgress, negativeProgress, selectedXTranslate, -spreadView.width, root.progress);
3219 } else {
3220 return linearAnimation(selectedProgress, negativeProgress, selectedXTranslate, -spreadView.sideStageWidth, root.progress);
3221 }
3222+ } else if (otherSelected) {
3223+ return selectedXTranslate;
3224 }
3225
3226 // 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
3227 // when we're only dragging the side stage in on top of a main stage app
3228- var shouldMoveAway = spreadView.nextInStack >= 0 && priv.movedActive && model.stage === ApplicationInfoInterface.MainStage &&
3229+ var shouldMoveAway = spreadView.nextInStack >= 0 && priv.movedActive && stage === ApplicationInfoInterface.MainStage &&
3230 ApplicationManager.get(spreadView.nextInStack).stage === ApplicationInfoInterface.MainStage;
3231
3232 if (active) {
3233@@ -177,36 +178,45 @@
3234 newTranslate += linearAnimation(0, spreadView.positionMarker2, 0, -units.gu(4), root.animatedProgress);
3235 }
3236 newTranslate += root.dragOffset;
3237- }
3238- if (!spreadView.active && model.appId == "unity8-dash" && !root.active) {
3239+ } else if (!spreadView.active && model.appId == "unity8-dash") {
3240 newTranslate -= root.width;
3241 }
3242
3243- if (nextInStack && spreadView.phase == 0) {
3244- if (model.stage == ApplicationInfoInterface.MainStage) {
3245- if (spreadView.sideStageVisible && root.progress > 0) {
3246- // Move it so it appears from behind the side stage immediately
3247- newTranslate += -spreadView.sideStageWidth;
3248- }
3249- }
3250-
3251- if (model.stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
3252- // This is when we only drag the side stage in, without rotation or snapping
3253- newTranslate = linearAnimation(0, spreadView.positionMarker2, 0, -spreadView.sideStageWidth, root.progress);
3254- } else {
3255- newTranslate += linearAnimation(0, spreadView.positionMarker2, 0, -spreadView.sideStageWidth * spreadView.snapPosition, root.animatedProgress);
3256+ if (!spreadView.active) {
3257+ return newTranslate;
3258+ }
3259+
3260+ var shadowOffset = units.gu(2);
3261+
3262+ if (spreadView.phase == 0) {
3263+ if (nextInStack) {
3264+ if (stage == ApplicationInfoInterface.MainStage) {
3265+ if (spreadView.sideStageVisible && root.progress > 0) {
3266+ // Move it so it appears from behind the side stage immediately
3267+ newTranslate += -spreadView.sideStageWidth;
3268+ }
3269+ }
3270+
3271+ if (stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
3272+ // This is when we only drag the side stage in, without rotation or snapping
3273+ newTranslate = linearAnimation(0, spreadView.positionMarker2, 0, -spreadView.sideStageWidth, root.animatedProgress);
3274+ } else {
3275+ newTranslate += linearAnimation(0, spreadView.positionMarker2, 0, -spreadView.sideStageWidth * spreadView.snapPosition, root.animatedProgress);
3276+ }
3277+ } else if (!root.active) {
3278+ newTranslate += shadowOffset;
3279 }
3280 }
3281
3282 if (spreadView.phase == 1) {
3283 if (nextInStack) {
3284- if (model.stage == ApplicationInfoInterface.MainStage) {
3285+ if (stage == ApplicationInfoInterface.MainStage) {
3286 var startValue = -spreadView.sideStageWidth * spreadView.snapPosition + (spreadView.sideStageVisible ? -spreadView.sideStageWidth : 0);
3287 newTranslate += linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, startValue, priv.phase2StartTranslate, root.animatedProgress);
3288 } else {
3289 var endValue = -spreadView.width + spreadView.width * root.zIndex / 6;
3290 if (!spreadView.sideStageVisible) {
3291- newTranslate += linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, -spreadView.sideStageWidth, priv.phase2StartTranslate, root.progress);
3292+ newTranslate += linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, -spreadView.sideStageWidth, priv.phase2StartTranslate, root.animatedProgress);
3293 } else {
3294 newTranslate += linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, -spreadView.sideStageWidth * spreadView.snapPosition, priv.phase2StartTranslate, root.animatedProgress);
3295 }
3296@@ -215,16 +225,20 @@
3297 var startProgress = spreadView.positionMarker2 - (zIndex * spreadView.positionMarker2 / 2);
3298 var endProgress = spreadView.positionMarker4 - (zIndex * spreadView.tileDistance / spreadView.width);
3299 var startTranslate = -root.width + (shouldMoveAway ? -units.gu(4) : 0);
3300- newTranslate = linearAnimation(startProgress, endProgress, startTranslate, priv.phase2StartTranslate, root.progress);
3301+ newTranslate = linearAnimation(startProgress, endProgress, startTranslate, priv.phase2StartTranslate, root.animatedProgress);
3302 } else {
3303 var startProgress = spreadView.positionMarker2 - (zIndex * spreadView.positionMarker2 / 2);
3304 var endProgress = spreadView.positionMarker4 - (zIndex * spreadView.tileDistance / spreadView.width);
3305- newTranslate = linearAnimation(startProgress, endProgress, 0, priv.phase2StartTranslate, root.progress);
3306+ newTranslate = linearAnimation(startProgress, endProgress, shadowOffset * Math.max(0, zIndex-2), priv.phase2StartTranslate, root.animatedProgress);
3307 }
3308 }
3309
3310 if (spreadView.phase == 2) {
3311- newTranslate = -easingCurve.value * (spreadView.width - root.zIndex * animatedEndDistance);
3312+ if (easingCurve.value == 0 && !nextInStack) {
3313+ newTranslate = shadowOffset;
3314+ } else {
3315+ newTranslate = -easingCurve.value * (spreadView.width - root.zIndex * animatedEndDistance);
3316+ }
3317 }
3318
3319 return newTranslate;
3320@@ -235,33 +249,30 @@
3321 return 1;
3322 }
3323
3324- if (otherSelected) {
3325+ // selected app or opposite stage active app.
3326+ if (isSelected || (otherSelected && root.active && spreadView.selectedApplication && spreadView.selectedApplication.stage !== stage)) {
3327+ return linearAnimation(selectedProgress, negativeProgress, selectedScale, 1, root.progress);
3328+ } else if (otherSelected) {
3329 return selectedScale;
3330 }
3331
3332- if (isSelected) {
3333- return linearAnimation(selectedProgress, negativeProgress, selectedScale, 1, root.progress);
3334- }
3335-
3336 if (spreadView.phase == 0) {
3337 if (nextInStack) {
3338- if (model.stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
3339+ if (stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
3340 return 1;
3341 } else {
3342 var targetScale = root.dragStartScale - ((root.dragStartScale - 1) * spreadView.snapPosition);
3343 return linearAnimation(0, spreadView.positionMarker2, root.dragStartScale, targetScale, root.animatedProgress);
3344 }
3345- } else if (active) {
3346+ } else {
3347 return 1;
3348- } else {
3349- return linearAnimation(0, spreadView.positionMarker2, root.startScale, root.endScale, root.progress);
3350 }
3351 }
3352
3353 if (spreadView.phase == 1) {
3354 if (nextInStack) {
3355 var startScale = 1;
3356- if (model.stage !== ApplicationInfoInterface.SideStage || spreadView.sideStageVisible) {
3357+ if (stage !== ApplicationInfoInterface.SideStage || spreadView.sideStageVisible) {
3358 startScale = root.dragStartScale - ((root.dragStartScale - 1) * spreadView.snapPosition);
3359 }
3360 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, startScale, priv.phase2StartScale, root.animatedProgress);
3361@@ -283,21 +294,22 @@
3362 return 0;
3363 }
3364
3365- if (otherSelected) {
3366+ // selected app or opposite stage active app.
3367+ if (isSelected || (otherSelected && root.active && spreadView.selectedApplication && spreadView.selectedApplication.stage !== stage)) {
3368+ return linearAnimation(selectedProgress, negativeProgress, selectedAngle, 0, root.progress);
3369+ } else if (otherSelected) {
3370 return selectedAngle;
3371 }
3372- if (isSelected) {
3373- return linearAnimation(selectedProgress, negativeProgress, selectedAngle, 0, root.progress);
3374- }
3375
3376 // The tile should rotate a bit when another one comes on top, but not when only dragging the side stage in
3377- var shouldMoveAway = spreadView.nextInStack >= 0 && movedActive &&
3378+ var shouldMoveAway = spreadView.nextInStack == -1 ||
3379+ spreadView.nextInStack >= 0 && priv.movedActive &&
3380 (ApplicationManager.get(spreadView.nextInStack).stage === ApplicationInfoInterface.MainStage ||
3381- model.stage == ApplicationInfoInterface.SideStage);
3382+ stage == ApplicationInfoInterface.SideStage);
3383
3384 if (spreadView.phase == 0) {
3385 if (nextInStack) {
3386- if (model.stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
3387+ if (stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
3388 return 0;
3389 } else {
3390 return linearAnimation(0, spreadView.positionMarker2, root.startAngle, root.startAngle * (1-spreadView.snapPosition), root.animatedProgress);
3391@@ -309,7 +321,7 @@
3392 }
3393 if (spreadView.phase == 1) {
3394 if (nextInStack) {
3395- if (model.stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
3396+ if (stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
3397 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, 0, priv.phase2StartAngle, root.animatedProgress);
3398 } else {
3399 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, root.startAngle * (1-spreadView.snapPosition), priv.phase2StartAngle, root.animatedProgress);
3400@@ -328,31 +340,47 @@
3401 }
3402
3403 property real opacityTransform: {
3404- if (otherSelected && spreadView.phase == 2) {
3405+ // animate opacity for items not snapping into view.
3406+ if (spreadView.phase !== 2) return 1;
3407+ if (root.isSelected) return 1;
3408+
3409+ if (otherSelected) {
3410+ if (root.active && spreadView.selectedApplication && spreadView.selectedApplication.stage !== stage) {
3411+ return 1;
3412+ }
3413 return linearAnimation(selectedProgress, negativeProgress, selectedOpacity, 0, root.progress);
3414 }
3415-
3416 return 1;
3417 }
3418
3419 property real topMarginProgress: {
3420- if (priv.isSelected) {
3421+ // selected app or opposite stage active app.
3422+ if (isSelected || (otherSelected && root.active && spreadView.selectedApplication && spreadView.selectedApplication.stage !== stage)) {
3423 return linearAnimation(selectedProgress, negativeProgress, selectedTopMarginProgress, 0, root.progress);
3424 }
3425- switch (spreadView.phase) {
3426- case 0:
3427+
3428+ if (spreadView.phase == 0) {
3429 return 0;
3430- case 1:
3431- return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
3432- 0, priv.phase2StartTopMarginProgress, root.progress);
3433+ } else if (spreadView.phase == 1) {
3434+ if (nextInStack) {
3435+ return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
3436+ 0, 1, root.progress);
3437+ }
3438+ var startProgress = spreadView.positionMarker2 - (zIndex * spreadView.positionMarker2 / 2);
3439+ var endProgress = spreadView.positionMarker4 - (zIndex * spreadView.tileDistance / spreadView.width);
3440+ return linearAnimation(startProgress, endProgress, 0, 1, root.progress);
3441 }
3442 return 1;
3443 }
3444
3445 states: [
3446 State {
3447- name: "sideStageDragging"; when: spreadView.sideStageDragging && root.isInSideStage
3448- PropertyChanges { target: priv; xTranslate: -spreadView.sideStageWidth + spreadView.sideStageWidth * spreadView.sideStageDragProgress }
3449+ name: "sideStage";
3450+ when: root.isInSideStage && spreadView.shiftedContentX == 0 && spreadView.phase == 0
3451+ PropertyChanges {
3452+ target: priv;
3453+ xTranslate: -spreadView.sideStageWidth + spreadView.sideStageWidth * (1-spreadView.sideStageDragProgress)
3454+ }
3455 }
3456 ]
3457 }
3458
3459=== added file 'qml/Stages/graphics/sidestage_drag.svg'
3460--- qml/Stages/graphics/sidestage_drag.svg 1970-01-01 00:00:00 +0000
3461+++ qml/Stages/graphics/sidestage_drag.svg 2016-03-16 11:49:36 +0000
3462@@ -0,0 +1,207 @@
3463+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
3464+<!-- Created with Inkscape (http://www.inkscape.org/) -->
3465+
3466+<svg
3467+ xmlns:dc="http://purl.org/dc/elements/1.1/"
3468+ xmlns:cc="http://creativecommons.org/ns#"
3469+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
3470+ xmlns:svg="http://www.w3.org/2000/svg"
3471+ xmlns="http://www.w3.org/2000/svg"
3472+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
3473+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
3474+ width="540"
3475+ height="324"
3476+ id="svg4874"
3477+ version="1.1"
3478+ inkscape:version="0.91+devel r"
3479+ viewBox="0 0 540 324"
3480+ sodipodi:docname="gesture_3f_touch.svg">
3481+ <defs
3482+ id="defs4876" />
3483+ <sodipodi:namedview
3484+ id="base"
3485+ pagecolor="#292929"
3486+ bordercolor="#666666"
3487+ borderopacity="1.0"
3488+ inkscape:pageopacity="1"
3489+ inkscape:pageshadow="2"
3490+ inkscape:zoom="1.1785993"
3491+ inkscape:cx="-19.084613"
3492+ inkscape:cy="11.707485"
3493+ inkscape:document-units="px"
3494+ inkscape:current-layer="g4780"
3495+ showgrid="false"
3496+ showborder="true"
3497+ fit-margin-top="0"
3498+ fit-margin-left="0"
3499+ fit-margin-right="0"
3500+ fit-margin-bottom="0"
3501+ inkscape:snap-bbox="true"
3502+ inkscape:bbox-paths="true"
3503+ inkscape:bbox-nodes="true"
3504+ inkscape:snap-bbox-edge-midpoints="true"
3505+ inkscape:snap-bbox-midpoints="true"
3506+ inkscape:object-paths="true"
3507+ inkscape:snap-intersection-paths="true"
3508+ inkscape:object-nodes="true"
3509+ inkscape:snap-smooth-nodes="true"
3510+ inkscape:snap-midpoints="true"
3511+ inkscape:snap-object-midpoints="true"
3512+ inkscape:snap-center="true"
3513+ showguides="false"
3514+ inkscape:guide-bbox="true"
3515+ inkscape:snap-global="true"
3516+ inkscape:snap-others="false"
3517+ inkscape:snap-page="true">
3518+ <inkscape:grid
3519+ type="xygrid"
3520+ id="grid5451"
3521+ empspacing="8"
3522+ originx="-2.7715014e-06"
3523+ originy="-2.5124622e-06" />
3524+ <sodipodi:guide
3525+ orientation="1,0"
3526+ position="7.9999972,-8.0000025"
3527+ id="guide4063"
3528+ inkscape:locked="false" />
3529+ <sodipodi:guide
3530+ orientation="1,0"
3531+ position="3.9999972,-8.0000025"
3532+ id="guide4065"
3533+ inkscape:locked="false" />
3534+ <sodipodi:guide
3535+ orientation="0,1"
3536+ position="-8.0000027,87.999998"
3537+ id="guide4067"
3538+ inkscape:locked="false" />
3539+ <sodipodi:guide
3540+ orientation="0,1"
3541+ position="-8.0000027,91.999998"
3542+ id="guide4069"
3543+ inkscape:locked="false" />
3544+ <sodipodi:guide
3545+ orientation="0,1"
3546+ position="104,3.9999975"
3547+ id="guide4071"
3548+ inkscape:locked="false" />
3549+ <sodipodi:guide
3550+ orientation="0,1"
3551+ position="-5.0000027,7.9999975"
3552+ id="guide4073"
3553+ inkscape:locked="false" />
3554+ <sodipodi:guide
3555+ orientation="1,0"
3556+ position="91.999996,-8.0000025"
3557+ id="guide4075"
3558+ inkscape:locked="false" />
3559+ <sodipodi:guide
3560+ orientation="1,0"
3561+ position="87.999997,-8.0000025"
3562+ id="guide4077"
3563+ inkscape:locked="false" />
3564+ <sodipodi:guide
3565+ orientation="0,1"
3566+ position="-8.0000027,83.999998"
3567+ id="guide4074"
3568+ inkscape:locked="false" />
3569+ <sodipodi:guide
3570+ orientation="1,0"
3571+ position="11.999997,-8.0000025"
3572+ id="guide4076"
3573+ inkscape:locked="false" />
3574+ <sodipodi:guide
3575+ orientation="0,1"
3576+ position="-5.0000027,11.999997"
3577+ id="guide4078"
3578+ inkscape:locked="false" />
3579+ <sodipodi:guide
3580+ orientation="1,0"
3581+ position="83.999997,-9.0000025"
3582+ id="guide4080"
3583+ inkscape:locked="false" />
3584+ <sodipodi:guide
3585+ position="47.999997,-8.0000025"
3586+ orientation="1,0"
3587+ id="guide4170"
3588+ inkscape:locked="false" />
3589+ <sodipodi:guide
3590+ position="-8.0000027,47.999997"
3591+ orientation="0,1"
3592+ id="guide4172"
3593+ inkscape:locked="false" />
3594+ </sodipodi:namedview>
3595+ <metadata
3596+ id="metadata4879">
3597+ <rdf:RDF>
3598+ <cc:Work
3599+ rdf:about="">
3600+ <dc:format>image/svg+xml</dc:format>
3601+ <dc:type
3602+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
3603+ <dc:title></dc:title>
3604+ </cc:Work>
3605+ </rdf:RDF>
3606+ </metadata>
3607+ <g
3608+ inkscape:label="Layer 1"
3609+ inkscape:groupmode="layer"
3610+ id="layer1"
3611+ transform="translate(67.857143,149.49498)">
3612+ <g
3613+ transform="matrix(0,-1,-1,0,373.50506,516.50504)"
3614+ id="g4845"
3615+ style="display:inline">
3616+ <g
3617+ inkscape:export-ydpi="90"
3618+ inkscape:export-xdpi="90"
3619+ inkscape:export-filename="next01.png"
3620+ transform="matrix(-0.9996045,0,0,1,575.94296,-611.00001)"
3621+ id="g4778"
3622+ inkscape:label="Layer 1">
3623+ <g
3624+ transform="matrix(-1,0,0,1,575.99999,611)"
3625+ id="g4780"
3626+ style="display:inline">
3627+ <path
3628+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:53.6204567px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:end;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:end;display:inline;fill:none;fill-opacity:1;stroke:#f7f7f7;stroke-width:4.00079107;stroke-miterlimit:4;stroke-dasharray:none"
3629+ d="m 526.90578,343.72628 c 14.3803,3.85163 8.37809,26.10701 -5.96811,22.26448 l -10e-6,10e-6 -50.91437,-13.63709 67.92084,18.21108 c 15.93089,4.26693 9.28148,28.92202 -6.61162,24.66516 l -10e-6,1e-5 -67.93497,-18.15834 58.38757,15.63874 c 15.85637,4.24702 9.23761,28.78845 -6.58115,24.5515 v -2e-5 l 1e-5,-2e-5 -71.03537,-19.00646 -33.09607,-8.85524 -0.18781,-0.0503 v 0 l -6.91408,-1.85189 18.07353,23.47068 c 8.45364,10.48525 11.9521,17.66446 -3.54852,27.89697 l -43.91145,-37.31132 c -28.38683,-22.97811 -35.17793,-41.86133 -27.95127,-70.42985 10.25002,-40.5206 40.5632,-49.89857 76.24862,-40.34047 4.5224,1.21129 36.24839,9.63672 40.29164,10.71968 14.0412,3.76084 12.54552,13.74563 6.76936,26.96549 z"
3630+ id="path4845-6"
3631+ inkscape:connector-curvature="0"
3632+ sodipodi:nodetypes="ccccccccccccccccccccssscc" />
3633+ <path
3634+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#f7f7f7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.51758671;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
3635+ d="m 571.27587,383.27233 a 35.064884,35.05101 0 0 1 -16.02978,29.39649 35.064884,35.05101 0 0 1 -34.38665,28.3418 35.064884,35.05101 0 0 1 -34.85362,-31.56055 l 29.19904,7.8125 c 15.81876,4.23695 22.4371,-20.30571 6.58073,-24.55274 l -28.91769,-7.74609 a 35.064884,35.05101 0 0 1 0.01,-0.0137 l 38.45466,10.2793 c 15.8931,4.25686 22.54288,-20.39714 6.61199,-24.66407 l -48.41758,-12.98242 a 35.064884,35.05101 0 0 1 0,-0.006 l 31.41086,8.41407 c 14.3462,3.84253 20.34945,-18.414 5.96915,-22.26563 l -31.70199,-8.49023 a 35.064884,35.05101 0 0 1 29.21468,-15.70704 35.064884,35.05101 0 0 1 35.06465,35.05274 35.064884,35.05101 0 0 1 -0.0879,2.45312 35.064884,35.05101 0 0 1 11.8797,26.23828 z"
3636+ id="ellipse4239-1"
3637+ inkscape:connector-curvature="0" />
3638+ <g
3639+ style="display:inline"
3640+ id="g5379"
3641+ transform="matrix(-0.99999722,0,0,-0.99999722,645.25394,919.46958)">
3642+ <path
3643+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#f7f7f7;stroke-width:4.00079107;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
3644+ id="path4116"
3645+ sodipodi:type="arc"
3646+ sodipodi:cx="808.72699"
3647+ sodipodi:cy="244.79436"
3648+ sodipodi:rx="250.36082"
3649+ sodipodi:ry="263.63275"
3650+ sodipodi:start="3.8048178"
3651+ sodipodi:end="5.6286868"
3652+ sodipodi:open="true"
3653+ d="M 611.43997,82.485822 A 250.36082,263.63275 0 0 1 809.81939,-18.835885 250.36082,263.63275 0 0 1 1007.3516,84.304899"
3654+ inkscape:transform-center-x="-17.044876"
3655+ inkscape:transform-center-y="-0.0015034578"
3656+ transform="matrix(0,1,1,0,0,0)" />
3657+ <path
3658+ inkscape:transform-center-y="4.9738279"
3659+ inkscape:transform-center-x="2.0443183"
3660+ inkscape:connector-curvature="0"
3661+ id="path4198"
3662+ d="m 73.559638,1014.0226 14.78221,-18.91287 c 0,0 6.80763,12.50667 9.95238,23.00047 -10.9469,-0.5114 -24.73459,-4.0876 -24.73459,-4.0876 z"
3663+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#f7f7f7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.00059342;marker:none;enable-background:accumulate" />
3664+ </g>
3665+ </g>
3666+ </g>
3667+ </g>
3668+ </g>
3669+</svg>
3670
3671=== added file 'qml/Stages/graphics/sidestage_open.svg'
3672--- qml/Stages/graphics/sidestage_open.svg 1970-01-01 00:00:00 +0000
3673+++ qml/Stages/graphics/sidestage_open.svg 2016-03-16 11:49:36 +0000
3674@@ -0,0 +1,181 @@
3675+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
3676+<!-- Created with Inkscape (http://www.inkscape.org/) -->
3677+
3678+<svg
3679+ xmlns:dc="http://purl.org/dc/elements/1.1/"
3680+ xmlns:cc="http://creativecommons.org/ns#"
3681+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
3682+ xmlns:svg="http://www.w3.org/2000/svg"
3683+ xmlns="http://www.w3.org/2000/svg"
3684+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
3685+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
3686+ width="162"
3687+ height="230"
3688+ id="svg4874"
3689+ version="1.1"
3690+ inkscape:version="0.91+devel r"
3691+ viewBox="0 0 162 230"
3692+ sodipodi:docname="gesture_3f_tap.svg"
3693+ style="shape-rendering:crispEdges">
3694+ <defs
3695+ id="defs4876" />
3696+ <sodipodi:namedview
3697+ id="base"
3698+ pagecolor="#292929"
3699+ bordercolor="#666666"
3700+ borderopacity="1.0"
3701+ inkscape:pageopacity="1"
3702+ inkscape:pageshadow="2"
3703+ inkscape:zoom="1.8415614"
3704+ inkscape:cx="162.13161"
3705+ inkscape:cy="116.01485"
3706+ inkscape:document-units="px"
3707+ inkscape:current-layer="g4780"
3708+ showgrid="false"
3709+ showborder="true"
3710+ fit-margin-top="0"
3711+ fit-margin-left="0"
3712+ fit-margin-right="0"
3713+ fit-margin-bottom="0"
3714+ inkscape:snap-bbox="true"
3715+ inkscape:bbox-paths="true"
3716+ inkscape:bbox-nodes="true"
3717+ inkscape:snap-bbox-edge-midpoints="true"
3718+ inkscape:snap-bbox-midpoints="true"
3719+ inkscape:object-paths="true"
3720+ inkscape:snap-intersection-paths="true"
3721+ inkscape:object-nodes="true"
3722+ inkscape:snap-smooth-nodes="true"
3723+ inkscape:snap-midpoints="true"
3724+ inkscape:snap-object-midpoints="true"
3725+ inkscape:snap-center="true"
3726+ showguides="false"
3727+ inkscape:guide-bbox="true"
3728+ inkscape:snap-global="false"
3729+ inkscape:snap-others="false"
3730+ inkscape:snap-page="true">
3731+ <inkscape:grid
3732+ type="xygrid"
3733+ id="grid5451"
3734+ empspacing="8"
3735+ originx="-2.7715014e-06"
3736+ originy="-2.5124622e-06" />
3737+ <sodipodi:guide
3738+ orientation="1,0"
3739+ position="7.9999972,-8.0000026"
3740+ id="guide4063"
3741+ inkscape:locked="false" />
3742+ <sodipodi:guide
3743+ orientation="1,0"
3744+ position="3.9999972,-8.0000026"
3745+ id="guide4065"
3746+ inkscape:locked="false" />
3747+ <sodipodi:guide
3748+ orientation="0,1"
3749+ position="-8.0000027,87.999998"
3750+ id="guide4067"
3751+ inkscape:locked="false" />
3752+ <sodipodi:guide
3753+ orientation="0,1"
3754+ position="-8.0000027,91.999998"
3755+ id="guide4069"
3756+ inkscape:locked="false" />
3757+ <sodipodi:guide
3758+ orientation="0,1"
3759+ position="104,3.9999975"
3760+ id="guide4071"
3761+ inkscape:locked="false" />
3762+ <sodipodi:guide
3763+ orientation="0,1"
3764+ position="-5.0000027,7.9999975"
3765+ id="guide4073"
3766+ inkscape:locked="false" />
3767+ <sodipodi:guide
3768+ orientation="1,0"
3769+ position="91.999997,-8.0000026"
3770+ id="guide4075"
3771+ inkscape:locked="false" />
3772+ <sodipodi:guide
3773+ orientation="1,0"
3774+ position="87.999997,-8.0000026"
3775+ id="guide4077"
3776+ inkscape:locked="false" />
3777+ <sodipodi:guide
3778+ orientation="0,1"
3779+ position="-8.0000027,83.999998"
3780+ id="guide4074"
3781+ inkscape:locked="false" />
3782+ <sodipodi:guide
3783+ orientation="1,0"
3784+ position="11.999997,-8.0000026"
3785+ id="guide4076"
3786+ inkscape:locked="false" />
3787+ <sodipodi:guide
3788+ orientation="0,1"
3789+ position="-5.0000027,11.999997"
3790+ id="guide4078"
3791+ inkscape:locked="false" />
3792+ <sodipodi:guide
3793+ orientation="1,0"
3794+ position="83.999997,-9.0000026"
3795+ id="guide4080"
3796+ inkscape:locked="false" />
3797+ <sodipodi:guide
3798+ position="47.999997,-8.0000026"
3799+ orientation="1,0"
3800+ id="guide4170"
3801+ inkscape:locked="false" />
3802+ <sodipodi:guide
3803+ position="-8.0000027,47.999997"
3804+ orientation="0,1"
3805+ id="guide4172"
3806+ inkscape:locked="false" />
3807+ </sodipodi:namedview>
3808+ <metadata
3809+ id="metadata4879">
3810+ <rdf:RDF>
3811+ <cc:Work
3812+ rdf:about="">
3813+ <dc:format>image/svg+xml</dc:format>
3814+ <dc:type
3815+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
3816+ <dc:title></dc:title>
3817+ </cc:Work>
3818+ </rdf:RDF>
3819+ </metadata>
3820+ <g
3821+ inkscape:label="Layer 1"
3822+ inkscape:groupmode="layer"
3823+ id="layer1"
3824+ transform="translate(67.857143,55.494966)">
3825+ <g
3826+ transform="matrix(0,-1,-1,0,373.50506,516.50504)"
3827+ id="g4845"
3828+ style="display:inline">
3829+ <g
3830+ inkscape:export-ydpi="90"
3831+ inkscape:export-xdpi="90"
3832+ inkscape:export-filename="next01.png"
3833+ transform="matrix(-0.9996045,0,0,1,575.94296,-611.00001)"
3834+ id="g4778"
3835+ inkscape:label="Layer 1">
3836+ <g
3837+ transform="matrix(-1,0,0,1,575.99999,611)"
3838+ id="g4780"
3839+ style="display:inline">
3840+ <path
3841+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:53.6204567px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:end;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:end;display:inline;fill:none;fill-opacity:1;stroke:#f7f7f7;stroke-width:4.00079107;stroke-miterlimit:4;stroke-dasharray:none"
3842+ d="m 526.90578,343.72628 c 14.3803,3.85163 8.37809,26.10701 -5.96811,22.26448 l -10e-6,10e-6 -50.91437,-13.63709 67.92084,18.21108 c 15.93089,4.26693 9.28148,28.92202 -6.61162,24.66516 l -10e-6,1e-5 -67.93497,-18.15834 58.38757,15.63874 c 15.85637,4.24702 9.23761,28.78845 -6.58115,24.5515 v -2e-5 l 1e-5,-2e-5 -71.03537,-19.00646 -33.09607,-8.85524 -0.18781,-0.0503 v 0 l -6.91408,-1.85189 18.07353,23.47068 c 8.45364,10.48525 11.9521,17.66446 -3.54852,27.89697 l -43.91145,-37.31132 c -28.38683,-22.97811 -35.17793,-41.86133 -27.95127,-70.42985 10.25002,-40.5206 40.5632,-49.89857 76.24862,-40.34047 4.5224,1.21129 36.24839,9.63672 40.29164,10.71968 14.0412,3.76084 12.54552,13.74563 6.76936,26.96549 z"
3843+ id="path4845-6"
3844+ inkscape:connector-curvature="0"
3845+ sodipodi:nodetypes="ccccccccccccccccccccssscc" />
3846+ <path
3847+ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.5;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#f7f7f7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.99999976;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
3848+ d="m 571.45563,383.2743 c -0.0175,12.04505 -6.22338,23.19765 -16.35804,29.64062 -3.3052,16.24493 -17.57029,28.04101 -34.23815,28.04493 -18.13447,4.3e-4 -33.08059,-13.83538 -34.83995,-31.50586 l 4.16766,1.11523 c 2.22233,14.95164 15.08799,26.39099 30.67229,26.39063 15.06147,-0.004 27.92054,-10.79228 30.53552,-25.61915 l 0.15631,-0.89648 0.78156,-0.46875 c 9.37156,-5.64255 15.10396,-15.76543 15.12121,-26.70117 v -0.004 c -0.0108,-9.30873 -4.17362,-18.12357 -11.35605,-24.04883 l -0.8265,-0.68359 0.10942,-1.06641 c 0.10013,-0.96319 0.1562,-1.9301 0.16608,-2.89843 -0.004,-17.20453 -13.91519,-31.10752 -31.12755,-31.10743 -10.37812,-5e-5 -19.55027,5.05891 -25.20333,12.84375 l -4.0856,-1.09375 c 6.29481,-9.48451 17.06827,-15.75006 29.28893,-15.75 19.37764,-1e-4 35.12924,15.74527 35.12913,35.11524 v 0.01 0.01 c -0.008,0.77822 -0.0716,1.55426 -0.13091,2.33008 7.57765,6.66645 12.02745,16.21382 12.03797,26.33984 v 0.002 z m -74.3634,2.80273 c -0.004,0.005 -0.008,0.0106 -0.0117,0.0156 l -4.15595,-1.11328 c 0.003,-0.004 0.006,-0.009 0.01,-0.0137 z m -3.51507,-27.4082 -4.14421,-1.10938 c -1.8e-4,-0.002 1.7e-4,-0.004 0,-0.006 l 4.14226,1.10937 c 2.7e-4,0.002 0.002,0.004 0.002,0.006 z"
3849+ id="ellipse4488"
3850+ inkscape:connector-curvature="0" />
3851+ </g>
3852+ </g>
3853+ </g>
3854+ </g>
3855+</svg>
3856
3857=== modified file 'src/MouseTouchAdaptor.cpp'
3858--- src/MouseTouchAdaptor.cpp 2015-12-15 19:55:26 +0000
3859+++ src/MouseTouchAdaptor.cpp 2016-03-16 11:49:36 +0000
3860@@ -71,6 +71,8 @@
3861 namespace {
3862 MouseTouchAdaptor *g_instance = nullptr;
3863
3864+const Qt::KeyboardModifiers TRI_PRESS_MODIFIER = Qt::ShiftModifier|Qt::ControlModifier|Qt::AltModifier;
3865+
3866 Qt::MouseButton translateMouseButton(xcb_button_t detail)
3867 {
3868 switch (detail) {
3869@@ -81,10 +83,25 @@
3870 default: return Qt::NoButton;
3871 }
3872 }
3873+
3874+Qt::KeyboardModifiers translateMofidier(uint32_t mod)
3875+{
3876+ Qt::KeyboardModifiers qtMod = Qt::NoModifier;
3877+
3878+ if (mod & 0x01) qtMod |= Qt::ShiftModifier;
3879+ if (mod & 0x04) qtMod |= Qt::ControlModifier;
3880+ if (mod & 0x08) qtMod |= Qt::AltModifier;
3881+ if (mod & 0x80) qtMod |= Qt::MetaModifier;
3882+
3883+ return qtMod;
3884+}
3885 } // end of anonymous namespace
3886
3887 MouseTouchAdaptor::MouseTouchAdaptor()
3888- : QObject(nullptr), m_leftButtonIsPressed(false), m_enabled(true)
3889+ : QObject(nullptr)
3890+ , m_leftButtonIsPressed(false)
3891+ , m_triPressModifier(false)
3892+ , m_enabled(true)
3893 {
3894 QCoreApplication::instance()->installNativeEventFilter(this);
3895
3896@@ -192,17 +209,20 @@
3897 return handleButtonPress(
3898 static_cast<WId>(xiDeviceEvent->event),
3899 xiDeviceEvent->detail,
3900+ xiDeviceEvent->mods.base_mods,
3901 fixed1616ToReal(xiDeviceEvent->event_x),
3902 fixed1616ToReal(xiDeviceEvent->event_y));
3903 case XI_ButtonRelease:
3904 return handleButtonRelease(
3905 static_cast<WId>(xiDeviceEvent->event),
3906 xiDeviceEvent->detail,
3907+ xiDeviceEvent->mods.base_mods,
3908 fixed1616ToReal(xiDeviceEvent->event_x),
3909 fixed1616ToReal(xiDeviceEvent->event_y));
3910 case XI_Motion:
3911 return handleMotionNotify(
3912 static_cast<WId>(xiDeviceEvent->event),
3913+ xiDeviceEvent->mods.base_mods,
3914 fixed1616ToReal(xiDeviceEvent->event_x),
3915 fixed1616ToReal(xiDeviceEvent->event_y));
3916 return true;
3917@@ -232,17 +252,18 @@
3918 switch (xcbEvent->response_type & ~0x80) {
3919 case XCB_BUTTON_PRESS: {
3920 auto pressEvent = reinterpret_cast<xcb_button_press_event_t *>(xcbEvent);
3921- return handleButtonPress(static_cast<WId>(pressEvent->event), pressEvent->detail,
3922+ return handleButtonPress(static_cast<WId>(pressEvent->event), pressEvent->detail, 0,
3923 pressEvent->event_x, pressEvent->event_y);
3924 }
3925 case XCB_BUTTON_RELEASE: {
3926 auto releaseEvent = reinterpret_cast<xcb_button_release_event_t *>(xcbEvent);
3927- return handleButtonRelease(static_cast<WId>(releaseEvent->event), releaseEvent->detail,
3928+ return handleButtonRelease(static_cast<WId>(releaseEvent->event), releaseEvent->detail, 0,
3929 releaseEvent->event_x, releaseEvent->event_y);
3930 }
3931 case XCB_MOTION_NOTIFY: {
3932 auto motionEvent = reinterpret_cast<xcb_motion_notify_event_t *>(xcbEvent);
3933- return handleMotionNotify(static_cast<WId>(motionEvent->event), motionEvent->event_x, motionEvent->event_y);
3934+ return handleMotionNotify(static_cast<WId>(motionEvent->event), 0,
3935+ motionEvent->event_x, motionEvent->event_y);
3936 }
3937 case XCB_GE_GENERIC:
3938 if (m_xi2Enabled) {
3939@@ -255,9 +276,10 @@
3940 };
3941 }
3942
3943-bool MouseTouchAdaptor::handleButtonPress(WId windowId, uint32_t detail, int x, int y)
3944+bool MouseTouchAdaptor::handleButtonPress(WId windowId, uint32_t detail, uint32_t modifiers, int x, int y)
3945 {
3946 Qt::MouseButton button = translateMouseButton(detail);
3947+ Qt::KeyboardModifiers qtMod = translateMofidier(modifiers);
3948
3949 // Just eat the event if it wasn't a left mouse press
3950 if (button != Qt::LeftButton)
3951@@ -270,19 +292,24 @@
3952 QTouchEventSequence touchEvent = QTest::touchEvent(targetWindow, m_touchDevice,
3953 false /* autoCommit */);
3954 touchEvent.press(0 /* touchId */, windowPos);
3955+ if (qtMod == TRI_PRESS_MODIFIER) {
3956+ touchEvent.press(1, windowPos);
3957+ touchEvent.press(2, windowPos);
3958+ m_triPressModifier = true;
3959+ }
3960 touchEvent.commit(false /* processEvents */);
3961
3962 m_leftButtonIsPressed = true;
3963 return true;
3964 }
3965
3966-bool MouseTouchAdaptor::handleButtonRelease(WId windowId, uint32_t detail, int x, int y)
3967+bool MouseTouchAdaptor::handleButtonRelease(WId windowId, uint32_t detail, uint32_t, int x, int y)
3968 {
3969 Qt::MouseButton button = translateMouseButton(detail);
3970
3971- // Just eat the event if it wasn't a left mouse release
3972+ // Don't eat the event if it wasn't a left mouse press
3973 if (button != Qt::LeftButton)
3974- return true;
3975+ return false;
3976
3977 QWindow *targetWindow = findQWindowWithXWindowID(windowId);
3978
3979@@ -291,17 +318,23 @@
3980 QTouchEventSequence touchEvent = QTest::touchEvent(targetWindow, m_touchDevice,
3981 false /* autoCommit */);
3982 touchEvent.release(0 /* touchId */, windowPos);
3983+ if (m_triPressModifier) {
3984+ touchEvent.release(1, windowPos);
3985+ touchEvent.release(2, windowPos);
3986+ }
3987 touchEvent.commit(false /* processEvents */);
3988
3989 m_leftButtonIsPressed = false;
3990+ m_triPressModifier = false;
3991 return true;
3992 }
3993
3994-bool MouseTouchAdaptor::handleMotionNotify(WId windowId, int x, int y)
3995+bool MouseTouchAdaptor::handleMotionNotify(WId windowId, uint32_t modifiers, int x, int y)
3996 {
3997 if (!m_leftButtonIsPressed) {
3998 return true;
3999 }
4000+ Qt::KeyboardModifiers qtMod = translateMofidier(modifiers);
4001
4002 QWindow *targetWindow = findQWindowWithXWindowID(windowId);
4003
4004@@ -310,6 +343,17 @@
4005 QTouchEventSequence touchEvent = QTest::touchEvent(targetWindow, m_touchDevice,
4006 false /* autoCommit */);
4007 touchEvent.move(0 /* touchId */, windowPos);
4008+ if (m_triPressModifier) {
4009+ if (qtMod == TRI_PRESS_MODIFIER) {
4010+ touchEvent.move(1, windowPos);
4011+ touchEvent.move(2, windowPos);
4012+ } else {
4013+ // released modifiers
4014+ touchEvent.release(1, windowPos);
4015+ touchEvent.release(2, windowPos);
4016+ m_triPressModifier = false;
4017+ }
4018+ }
4019 touchEvent.commit(false /* processEvents */);
4020
4021 return true;
4022
4023=== modified file 'src/MouseTouchAdaptor.h'
4024--- src/MouseTouchAdaptor.h 2015-12-15 19:55:26 +0000
4025+++ src/MouseTouchAdaptor.h 2016-03-16 11:49:36 +0000
4026@@ -51,13 +51,15 @@
4027 void fetchXInput2Info();
4028 bool xi2HandleEvent(xcb_ge_event_t *event);
4029
4030- bool handleButtonPress(WId windowId, uint32_t detail, int x, int y);
4031- bool handleButtonRelease(WId windowId, uint32_t detail, int x, int y);
4032- bool handleMotionNotify(WId windowId, int x, int y);
4033+ bool handleButtonPress(WId windowId, uint32_t detail, uint32_t modifiers, int x, int y);
4034+ bool handleButtonRelease(WId windowId, uint32_t detail, uint32_t modifiers, int x, int y);
4035+ bool handleMotionNotify(WId windowId, uint32_t modifiers, int x, int y);
4036 QWindow *findQWindowWithXWindowID(WId windowId);
4037
4038 QTouchDevice *m_touchDevice;
4039 bool m_leftButtonIsPressed;
4040+ bool m_triPressModifier;
4041+
4042
4043 bool m_enabled;
4044
4045
4046=== modified file 'tests/libs/UbuntuGestures/tst_TouchRegistry.cpp'
4047--- tests/libs/UbuntuGestures/tst_TouchRegistry.cpp 2015-07-20 16:30:40 +0000
4048+++ tests/libs/UbuntuGestures/tst_TouchRegistry.cpp 2016-03-16 11:49:36 +0000
4049@@ -42,6 +42,10 @@
4050 QSet<int> ownedTouches;
4051 QSet<int> lostTouches;
4052 QList<TouchMemento> unownedTouchEvents;
4053+
4054+Q_SIGNALS:
4055+ void gainedOwnership();
4056+ void lostOwnership();
4057 };
4058
4059 class tst_TouchRegistry : public QObject
4060@@ -68,6 +72,7 @@
4061 void removeOldUndecidedCandidates();
4062 void interimOwnerWontGetUnownedTouchEvents();
4063 void candidateVanishes();
4064+ void candicateOwnershipReentrace();
4065
4066 private:
4067 TouchRegistry *touchRegistry;
4068@@ -864,6 +869,58 @@
4069 QVERIFY(mainCandidate.ownedTouches.contains(0));
4070 }
4071
4072+/*
4073+ Regression test for canidate reentrance
4074+
4075+ Bug caused by candidates getting removed during ownership resolution
4076+ */
4077+void tst_TouchRegistry::candicateOwnershipReentrace()
4078+{
4079+ DummyCandidate mainCandidate;
4080+ DummyCandidate candicate2;
4081+ DummyCandidate candicate3;
4082+
4083+ {
4084+ QList<QTouchEvent::TouchPoint> touchPoints;
4085+ touchPoints.append(QTouchEvent::TouchPoint(0));
4086+ touchPoints[0].setState(Qt::TouchPointPressed);
4087+ QTouchEvent touchEvent(QEvent::TouchBegin,
4088+ 0 /* device */,
4089+ Qt::NoModifier,
4090+ Qt::TouchPointPressed,
4091+ touchPoints);
4092+ touchRegistry->update(&touchEvent);
4093+ }
4094+
4095+ // Re-entrance!
4096+ connect(&candicate2, &DummyCandidate::lostOwnership, this, [&]() {
4097+ touchRegistry->removeCandidateOwnerForTouch(0, &candicate2);
4098+ });
4099+
4100+ touchRegistry->addCandidateOwnerForTouch(0, &mainCandidate);
4101+ touchRegistry->addCandidateOwnerForTouch(0, &candicate2);
4102+ touchRegistry->addCandidateOwnerForTouch(0, &candicate3);
4103+
4104+ {
4105+ QList<QTouchEvent::TouchPoint> touchPoints;
4106+ touchPoints.append(QTouchEvent::TouchPoint(0));
4107+ touchPoints[0].setState(Qt::TouchPointMoved);
4108+ QTouchEvent touchEvent(QEvent::TouchUpdate,
4109+ 0 /* device */,
4110+ Qt::NoModifier,
4111+ Qt::TouchPointMoved,
4112+ touchPoints);
4113+ touchRegistry->update(&touchEvent);
4114+ }
4115+
4116+ touchRegistry->requestTouchOwnership(0, &mainCandidate);
4117+
4118+ QCOMPARE(mainCandidate.ownedTouches.count(), 1);
4119+ QCOMPARE(candicate2.lostTouches.count(), 1);
4120+ QCOMPARE(candicate3.lostTouches.count(), 1);
4121+}
4122+
4123+
4124 ////////////// TouchMemento //////////
4125
4126 TouchMemento::TouchMemento(const QTouchEvent *touchEvent)
4127@@ -897,8 +954,10 @@
4128
4129 if (touchOwnershipEvent->gained()) {
4130 ownedTouches.insert(touchOwnershipEvent->touchId());
4131+ Q_EMIT gainedOwnership();
4132 } else {
4133 lostTouches.insert(touchOwnershipEvent->touchId());
4134+ Q_EMIT lostOwnership();
4135 }
4136 return true;
4137 } else if (e->type() == UnownedTouchEvent::unownedTouchEventType()) {
4138
4139=== modified file 'tests/mocks/Unity/Application/ApplicationManager.cpp'
4140--- tests/mocks/Unity/Application/ApplicationManager.cpp 2015-12-03 18:10:39 +0000
4141+++ tests/mocks/Unity/Application/ApplicationManager.cpp 2016-03-16 11:49:36 +0000
4142@@ -151,10 +151,6 @@
4143
4144 beginInsertRows(QModelIndex(), m_runningApplications.size(), m_runningApplications.size());
4145 m_runningApplications.append(application);
4146- endInsertRows();
4147- Q_EMIT applicationAdded(application->appId());
4148- Q_EMIT countChanged();
4149- if (count() == 1) Q_EMIT emptyChanged(isEmpty()); // was empty but not anymore
4150
4151 connect(application, &ApplicationInfo::sessionChanged, this, [application, this]() {
4152 QModelIndex appIndex = findIndex(application);
4153@@ -171,6 +167,16 @@
4154 if (!appIndex.isValid()) return;
4155 Q_EMIT dataChanged(appIndex, appIndex, QVector<int>() << ApplicationManager::RoleState);
4156 });
4157+ connect(application, &ApplicationInfo::stageChanged, this, [application, this]() {
4158+ QModelIndex appIndex = findIndex(application);
4159+ if (!appIndex.isValid()) return;
4160+ Q_EMIT dataChanged(appIndex, appIndex, QVector<int>() << ApplicationManager::RoleStage);
4161+ });
4162+
4163+ endInsertRows();
4164+ Q_EMIT applicationAdded(application->appId());
4165+ Q_EMIT countChanged();
4166+ if (count() == 1) Q_EMIT emptyChanged(isEmpty()); // was empty but not anymore
4167 }
4168
4169 void ApplicationManager::remove(ApplicationInfo *application) {
4170@@ -203,22 +209,10 @@
4171 ApplicationInfo* ApplicationManager::startApplication(const QString &appId,
4172 const QStringList &arguments)
4173 {
4174- return startApplication(appId, NoFlag, arguments);
4175-}
4176-
4177-ApplicationInfo* ApplicationManager::startApplication(const QString &appId,
4178- ExecFlags flags,
4179- const QStringList &arguments)
4180-{
4181 Q_UNUSED(arguments)
4182 ApplicationInfo *application = add(appId);
4183 if (!application)
4184 return 0;
4185-
4186- if (flags.testFlag(ApplicationManager::ForceMainStage)
4187- && application->stage() == ApplicationInfo::SideStage) {
4188- application->setStage(ApplicationInfo::MainStage);
4189- }
4190 application->setState(ApplicationInfo::Starting);
4191
4192 return application;
4193@@ -317,7 +311,6 @@
4194 application->setAppId("unity8-dash");
4195 application->setName("Unity 8 Mock Dash");
4196 application->setScreenshotId("unity8-dash");
4197- application->setStage(ApplicationInfo::MainStage);
4198 application->setSupportedOrientations(Qt::PrimaryOrientation);
4199 m_availableApplications.append(application);
4200
4201@@ -326,7 +319,6 @@
4202 application->setName("Dialer");
4203 application->setScreenshotId("dialer");
4204 application->setIconId("dialer-app");
4205- application->setStage(ApplicationInfo::SideStage);
4206 application->setSupportedOrientations(Qt::PortraitOrientation
4207 | Qt::InvertedPortraitOrientation);
4208 m_availableApplications.append(application);
4209@@ -350,7 +342,6 @@
4210 application->setScreenshotId("gallery");
4211 application->setIconId("gallery");
4212 application->setFullscreen(true);
4213- application->setStage(ApplicationInfo::MainStage);
4214 m_availableApplications.append(application);
4215
4216 application = new ApplicationInfo(this);
4217@@ -358,7 +349,6 @@
4218 application->setName("Facebook");
4219 application->setScreenshotId("facebook");
4220 application->setIconId("facebook");
4221- application->setStage(ApplicationInfo::SideStage);
4222 m_availableApplications.append(application);
4223
4224 application = new ApplicationInfo(this);
4225@@ -374,7 +364,6 @@
4226 application->setName("Twitter");
4227 application->setScreenshotId("twitter");
4228 application->setIconId("twitter");
4229- application->setStage(ApplicationInfo::SideStage);
4230 m_availableApplications.append(application);
4231
4232 application = new ApplicationInfo(this);
4233@@ -390,7 +379,6 @@
4234 application->setIconId("gmail");
4235 application->setScreenshotId("gmail-webapp.svg");
4236 application->setFullscreen(false);
4237- application->setStage(ApplicationInfo::MainStage);
4238 application->setSupportedOrientations(Qt::PortraitOrientation
4239 | Qt::LandscapeOrientation
4240 | Qt::InvertedPortraitOrientation
4241@@ -403,7 +391,6 @@
4242 application->setIconId("soundcloud");
4243 application->setScreenshotId("music");
4244 application->setFullscreen(false);
4245- application->setStage(ApplicationInfo::MainStage);
4246 application->setSupportedOrientations(Qt::PortraitOrientation
4247 | Qt::LandscapeOrientation
4248 | Qt::InvertedPortraitOrientation
4249@@ -423,14 +410,12 @@
4250 application->setAppId("notes-app");
4251 application->setName("Notepad");
4252 application->setIconId("notepad");
4253- application->setStage(ApplicationInfo::SideStage);
4254 m_availableApplications.append(application);
4255
4256 application = new ApplicationInfo(this);
4257 application->setAppId("calendar-app");
4258 application->setName("Calendar");
4259 application->setIconId("calendar");
4260- application->setStage(ApplicationInfo::SideStage);
4261 m_availableApplications.append(application);
4262
4263 application = new ApplicationInfo(this);
4264
4265=== modified file 'tests/mocks/Unity/Application/ApplicationManager.h'
4266--- tests/mocks/Unity/Application/ApplicationManager.h 2015-12-03 18:10:39 +0000
4267+++ tests/mocks/Unity/Application/ApplicationManager.h 2016-03-16 11:49:36 +0000
4268@@ -46,11 +46,6 @@
4269 RoleSession = RoleExemptFromLifecycle+1,
4270 RoleFullscreen,
4271 };
4272- enum Flag {
4273- NoFlag = 0x0,
4274- ForceMainStage = 0x1,
4275- };
4276- Q_DECLARE_FLAGS(ExecFlags, Flag)
4277
4278 // QAbstractItemModel methods.
4279 int rowCount(const QModelIndex& parent = QModelIndex()) const override;
4280@@ -65,7 +60,6 @@
4281 Q_INVOKABLE bool focusApplication(const QString &appId) override;
4282 Q_INVOKABLE void unfocusCurrentApplication() override;
4283 Q_INVOKABLE ApplicationInfo *startApplication(const QString &appId, const QStringList &arguments = QStringList()) override;
4284- Q_INVOKABLE ApplicationInfo *startApplication(const QString &appId, ExecFlags flags, const QStringList &arguments = QStringList());
4285 Q_INVOKABLE bool stopApplication(const QString &appId) override;
4286
4287 QString focusedApplicationId() const override;
4288@@ -98,8 +92,6 @@
4289 static ApplicationManager *the_application_manager;
4290 };
4291
4292-Q_DECLARE_OPERATORS_FOR_FLAGS(ApplicationManager::ExecFlags)
4293-
4294 Q_DECLARE_METATYPE(ApplicationManager*)
4295
4296 #endif // APPLICATION_MANAGER_H
4297
4298=== modified file 'tests/mocks/Unity/Application/MirSurfaceItem.cpp'
4299--- tests/mocks/Unity/Application/MirSurfaceItem.cpp 2016-01-25 15:00:31 +0000
4300+++ tests/mocks/Unity/Application/MirSurfaceItem.cpp 2016-03-16 11:49:36 +0000
4301@@ -156,6 +156,10 @@
4302
4303 void MirSurfaceItem::touchEvent(QTouchEvent * event)
4304 {
4305+ if (event->type() == QEvent::TouchBegin) {
4306+ m_touchTrail.clear();
4307+ }
4308+
4309 if (event->touchPointStates() & Qt::TouchPointPressed) {
4310 ++m_touchPressCount;
4311 Q_EMIT touchPressCountChanged(m_touchPressCount);
4312@@ -163,6 +167,19 @@
4313 ++m_touchReleaseCount;
4314 Q_EMIT touchReleaseCountChanged(m_touchReleaseCount);
4315 }
4316+
4317+ Q_FOREACH(QTouchEvent::TouchPoint touchPoint, event->touchPoints()) {
4318+ QString id(touchPoint.id());
4319+ QVariantList list = m_touchTrail[id].toList();
4320+ list.append(QVariant::fromValue(touchPoint.pos()));
4321+ if (list.count() > 100) list.pop_front();
4322+ m_touchTrail[id] = list;
4323+ }
4324+
4325+ if (m_qmlItem) {
4326+ QQmlProperty touchTrail(m_qmlItem, "touchTrail");
4327+ touchTrail.write(m_touchTrail);
4328+ }
4329 }
4330
4331 void MirSurfaceItem::mousePressEvent(QMouseEvent * event)
4332
4333=== modified file 'tests/mocks/Unity/Application/MirSurfaceItem.h'
4334--- tests/mocks/Unity/Application/MirSurfaceItem.h 2016-01-25 15:00:31 +0000
4335+++ tests/mocks/Unity/Application/MirSurfaceItem.h 2016-03-16 11:49:36 +0000
4336@@ -123,6 +123,7 @@
4337 int m_touchReleaseCount;
4338 int m_mousePressCount;
4339 int m_mouseReleaseCount;
4340+ QVariantMap m_touchTrail;
4341
4342 FillMode m_fillMode{Stretch};
4343
4344
4345=== modified file 'tests/mocks/Unity/Application/resources/MirSurfaceItem.qml'
4346--- tests/mocks/Unity/Application/resources/MirSurfaceItem.qml 2015-09-29 12:28:10 +0000
4347+++ tests/mocks/Unity/Application/resources/MirSurfaceItem.qml 2016-03-16 11:49:36 +0000
4348@@ -29,6 +29,9 @@
4349 property alias screenshotSource: screenshotImage.source
4350 property int orientationAngle
4351
4352+ property var touchTrail
4353+ onTouchTrailChanged: canvas.requestPaint()
4354+
4355 Image {
4356 id: screenshotImage
4357 anchors.fill: parent
4358@@ -67,4 +70,60 @@
4359 width: (rotation == 0 || rotation == 180 ? parent.width : parent.height)
4360 height:(rotation == 0 || rotation == 180 ? parent.height : parent.width)
4361 }
4362+
4363+ Canvas {
4364+ id: canvas
4365+ anchors.fill: parent
4366+ onPaint: {
4367+ // get context to draw with
4368+ var ctx = getContext("2d")
4369+ ctx.reset(0, 0, canvas.width, canvas.height);
4370+ ctx.lineWidth = 20;
4371+
4372+ if (touchTrail === undefined) return;
4373+
4374+ var i = 0;
4375+ for (var prop in touchTrail) {
4376+ var trail = touchTrail[prop];
4377+
4378+ if (trail.length > 0) {
4379+ ctx.fillStyle = Qt.rgba(0, 0, 1, 0.3);
4380+ ctx.strokeStyle = Qt.rgba(0, 0, 1, 0.3);
4381+ ctx.beginPath();
4382+ ctx.moveTo(trail[0].x, trail[0].y);
4383+ ctx.ellipse(trail[0].x-1, trail[0].y-1, 2, 2);
4384+ ctx.stroke();
4385+
4386+ ctx.beginPath();
4387+ if (i == 0) {
4388+ ctx.strokeStyle = Qt.rgba(1, 1, 0, 0.3);
4389+ } else if (i == 1) {
4390+ ctx.strokeStyle = Qt.rgba(0, 1, 1, 0.3);
4391+
4392+ } else if (i == 2) {
4393+ ctx.strokeStyle = Qt.rgba(1, 0, 1, 0.3);
4394+
4395+ } else if (i == 3) {
4396+ ctx.strokeStyle = Qt.rgba(0, 0.5, 0.5, 0.3);
4397+
4398+ } else {
4399+ ctx.strokeStyle = Qt.rgba(0.7, 0.5, 0, 0.3);
4400+ }
4401+
4402+ for (var i = 1; i < trail.length; i++) {
4403+ ctx.lineTo(trail[i].x, trail[i].y)
4404+ }
4405+ ctx.stroke();
4406+
4407+ ctx.beginPath();
4408+ ctx.fillStyle = Qt.rgba(1, 0, 0, 0.3);
4409+ ctx.strokeStyle = Qt.rgba(1, 0, 0, 0.3);
4410+ ctx.ellipse(trail[i-1].x-1, trail[i-1].y-1, 2, 2);
4411+ ctx.stroke();
4412+ }
4413+ i++;
4414+ }
4415+
4416+ }
4417+ }
4418 }
4419
4420=== modified file 'tests/mocks/Unity/Indicators/ModelActionRootState.qml'
4421--- tests/mocks/Unity/Indicators/ModelActionRootState.qml 2016-01-05 06:09:56 +0000
4422+++ tests/mocks/Unity/Indicators/ModelActionRootState.qml 2016-03-16 11:49:36 +0000
4423@@ -33,17 +33,39 @@
4424 property string submenuAction: {
4425 if (!menu) return "";
4426 var ext = menu.get(0, "ext");
4427- var submenuVar = ext ? ext["submenu-action"] : undefined;
4428- return submenuVar ? submenuVar : ""
4429- }
4430+ var action = ext ? ext["submenu-action"] : undefined;
4431+ return action ? action : ""
4432+ }
4433+
4434+ property string secondaryAction: {
4435+ if (!menu) return "";
4436+ var ext = menu.get(0, "ext");
4437+ var action = ext ? ext["x-canonical-secondary-action"] : undefined;
4438+ return action ? action : ""
4439+ }
4440+
4441+ property string scrollAction: {
4442+ if (!menu) return "";
4443+ var ext = menu.get(0, "ext");
4444+ var action = ext ? ext["x-canonical-scroll-action"] : undefined;
4445+ return action ? action : ""
4446+ }
4447+
4448 Connections {
4449 target: menu
4450 onModelDataChanged: {
4451 cachedState = menu.get(0, "actionState");
4452
4453 var ext = menu.get(0, "ext");
4454- var submenuVar = ext ? ext["submenu-action"] : undefined;
4455- submenuAction = submenuVar ? submenuVar : ""
4456+
4457+ var action = ext ? ext["submenu-action"] : undefined;
4458+ submenuAction = action ? action : ""
4459+
4460+ action = ext ? ext["x-canonical-secondary-action"] : undefined;
4461+ secondaryAction = action ? action : ""
4462+
4463+ action = ext ? ext["x-canonical-scroll-action"] : undefined;
4464+ scrollAction = action ? action : ""
4465 }
4466 }
4467
4468
4469=== modified file 'tests/mocks/Utils/CMakeLists.txt'
4470--- tests/mocks/Utils/CMakeLists.txt 2016-03-16 11:49:35 +0000
4471+++ tests/mocks/Utils/CMakeLists.txt 2016-03-16 11:49:36 +0000
4472@@ -20,6 +20,7 @@
4473 ${CMAKE_SOURCE_DIR}/plugins/Utils/applicationsfiltermodel.cpp
4474 ${CMAKE_SOURCE_DIR}/plugins/Utils/inputeventgenerator.cpp
4475 ${CMAKE_SOURCE_DIR}/plugins/Utils/deviceconfigparser.cpp
4476+ ${CMAKE_SOURCE_DIR}/plugins/Utils/globalfunctions.cpp
4477 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
4478 constants.cpp
4479 plugin.cpp
4480
4481=== modified file 'tests/mocks/Utils/plugin.cpp'
4482--- tests/mocks/Utils/plugin.cpp 2016-03-16 11:49:35 +0000
4483+++ tests/mocks/Utils/plugin.cpp 2016-03-16 11:49:36 +0000
4484@@ -39,6 +39,7 @@
4485 #include <applicationsfiltermodel.h>
4486 #include <inputeventgenerator.h>
4487 #include <deviceconfigparser.h>
4488+#include <globalfunctions.h>
4489
4490 static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
4491 {
4492@@ -54,6 +55,13 @@
4493 return new Constants();
4494 }
4495
4496+static QObject *createGlobalFunctions(QQmlEngine *engine, QJSEngine *scriptEngine)
4497+{
4498+ Q_UNUSED(engine)
4499+ Q_UNUSED(scriptEngine)
4500+ return new GlobalFunctions();
4501+}
4502+
4503 void FakeUtilsPlugin::registerTypes(const char *uri)
4504 {
4505 Q_ASSERT(uri == QLatin1String("Utils"));
4506@@ -72,6 +80,7 @@
4507 qmlRegisterType<ApplicationsFilterModel>(uri, 0, 1, "ApplicationsFilterModel");
4508 qmlRegisterType<InputEventGenerator>(uri, 0, 1, "InputEventGenerator");
4509 qmlRegisterType<DeviceConfigParser>(uri, 0, 1, "DeviceConfigParser");
4510+ qmlRegisterSingletonType<GlobalFunctions>(uri, 0, 1, "Functions", createGlobalFunctions);
4511 }
4512
4513 void FakeUtilsPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
4514
4515=== modified file 'tests/mocks/Utils/windowstatestorage.cpp'
4516--- tests/mocks/Utils/windowstatestorage.cpp 2015-10-27 11:16:31 +0000
4517+++ tests/mocks/Utils/windowstatestorage.cpp 2016-03-16 11:49:36 +0000
4518@@ -16,6 +16,8 @@
4519
4520 #include "windowstatestorage.h"
4521
4522+#include <unity/shell/application/ApplicationInfoInterface.h>
4523+
4524 WindowStateStorage::WindowStateStorage(QObject *parent):
4525 QObject(parent)
4526 {
4527@@ -45,10 +47,21 @@
4528 return m_geometry.value(windowId).toRect();
4529 }
4530
4531+void WindowStateStorage::saveStage(const QString &appId, int stage)
4532+{
4533+ m_stage[appId] = stage;
4534+}
4535+
4536+int WindowStateStorage::getStage(const QString &appId) const
4537+{
4538+ return m_stage.value(appId, unity::shell::application::ApplicationInfoInterface::MainStage);
4539+}
4540+
4541 void WindowStateStorage::clear()
4542 {
4543 m_state.clear();
4544 m_geometry.clear();
4545+ m_stage.clear();
4546 }
4547
4548 void WindowStateStorage::saveState(const QString &windowId, WindowState state)
4549
4550=== modified file 'tests/mocks/Utils/windowstatestorage.h'
4551--- tests/mocks/Utils/windowstatestorage.h 2015-10-27 11:16:31 +0000
4552+++ tests/mocks/Utils/windowstatestorage.h 2016-03-16 11:49:36 +0000
4553@@ -36,6 +36,9 @@
4554 Q_INVOKABLE void saveGeometry(const QString &windowId, const QRect &rect);
4555 Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect &defaultValue);
4556
4557+ Q_INVOKABLE void saveStage(const QString &appId, int stage);
4558+ Q_INVOKABLE int getStage(const QString &appId) const;
4559+
4560 // Only in the mock, to easily restore a fresh state
4561 Q_INVOKABLE void clear();
4562
4563@@ -47,5 +50,6 @@
4564 QVariantMap geometry() const;
4565
4566 QHash<QString, WindowState> m_state;
4567+ QHash<QString, int> m_stage;
4568 QVariantMap m_geometry;
4569 };
4570
4571=== modified file 'tests/plugins/Ubuntu/Gestures/CMakeLists.txt'
4572--- tests/plugins/Ubuntu/Gestures/CMakeLists.txt 2015-08-03 13:49:42 +0000
4573+++ tests/plugins/Ubuntu/Gestures/CMakeLists.txt 2016-03-16 11:49:36 +0000
4574@@ -20,6 +20,11 @@
4575 target_link_libraries(${CLASSNAME}TestExec UbuntuGesturesQml UbuntuGestures)
4576 endmacro()
4577
4578+set(UNITY_IMPORT_PATHS
4579+ ${CMAKE_BINARY_DIR}/tests/utils/modules
4580+ ${UNITY_PLUGINPATH}
4581+)
4582+
4583 macro(add_gesture_unit_test CLASSNAME)
4584 build_gesture_test(${CLASSNAME})
4585 add_unity8_unittest(${CLASSNAME} ${CLASSNAME}TestExec
4586@@ -36,7 +41,7 @@
4587 DEPENDS UbuntuGesturesTest-qmlfiles
4588 ${ARGN}
4589 )
4590- add_manual_qml_test(. ${CLASSNAME} IMPORT_PATHS ${UNITY_PLUGINPATH})
4591+ add_manual_qml_test(. ${CLASSNAME} IMPORT_PATHS ${UNITY_IMPORT_PATHS})
4592 endmacro()
4593
4594 add_gesture_ui_test(DirectionalDragArea)
4595@@ -46,3 +51,4 @@
4596 add_gesture_ui_test(TouchGate)
4597 add_gesture_unit_test(Damper)
4598 add_gesture_unit_test(AxisVelocityCalculator)
4599+add_gesture_ui_test(TouchGestureArea)
4600
4601=== modified file 'tests/plugins/Ubuntu/Gestures/GestureTest.cpp'
4602--- tests/plugins/Ubuntu/Gestures/GestureTest.cpp 2015-05-21 15:52:48 +0000
4603+++ tests/plugins/Ubuntu/Gestures/GestureTest.cpp 2016-03-16 11:49:36 +0000
4604@@ -17,6 +17,7 @@
4605 #include "GestureTest.h"
4606
4607 #include <qpa/qwindowsysteminterface.h>
4608+#include <private/qquickwindow_p.h>
4609 #include <QQmlEngine>
4610 #include <QQuickView>
4611 #include <QtTest>
4612@@ -72,6 +73,49 @@
4613 m_view = nullptr;
4614 }
4615
4616+void GestureTest::sendTouchPress(qint64 timestamp, int id, QPointF pos)
4617+{
4618+ sendTouch(timestamp, id, pos, Qt::TouchPointPressed, QEvent::TouchBegin);
4619+}
4620+
4621+void GestureTest::sendTouchUpdate(qint64 timestamp, int id, QPointF pos)
4622+{
4623+ sendTouch(timestamp, id, pos, Qt::TouchPointMoved, QEvent::TouchUpdate);
4624+}
4625+
4626+void GestureTest::sendTouchRelease(qint64 timestamp, int id, QPointF pos)
4627+{
4628+ sendTouch(timestamp, id, pos, Qt::TouchPointReleased, QEvent::TouchEnd);
4629+}
4630+
4631+void GestureTest::sendTouch(qint64 timestamp, int id, QPointF pos,
4632+ Qt::TouchPointState pointState, QEvent::Type eventType)
4633+{
4634+ m_fakeTimerFactory->updateTime(timestamp);
4635+
4636+ QTouchEvent::TouchPoint point;
4637+
4638+ point.setState(pointState);
4639+ point.setId(id);
4640+ point.setScenePos(pos);
4641+ point.setPos(pos);
4642+
4643+ QList<QTouchEvent::TouchPoint> points;
4644+ points << point;
4645+
4646+ QTouchEvent touchEvent(eventType, m_device, Qt::NoModifier, Qt::TouchPointPressed, points);
4647+ QCoreApplication::sendEvent(m_view, &touchEvent);
4648+
4649+ QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(m_view);
4650+ windowPrivate->flushDelayedTouchEvent();
4651+}
4652+
4653+void GestureTest::passTime(qint64 timeSpanMs)
4654+{
4655+ qint64 finalTime = m_fakeTimerFactory->timeSource()->msecsSinceReference() + timeSpanMs;
4656+ m_fakeTimerFactory->updateTime(finalTime);
4657+}
4658+
4659 ////////////////////////// TouchMemento /////////////////////////////
4660
4661 TouchMemento::TouchMemento(const QTouchEvent *touchEvent)
4662
4663=== modified file 'tests/plugins/Ubuntu/Gestures/GestureTest.h'
4664--- tests/plugins/Ubuntu/Gestures/GestureTest.h 2015-04-10 21:16:37 +0000
4665+++ tests/plugins/Ubuntu/Gestures/GestureTest.h 2016-03-16 11:49:36 +0000
4666@@ -72,7 +72,7 @@
4667 {
4668 Q_OBJECT
4669 public:
4670- // \param qmlFilename name of the qml file to be loaded by the QQuickView
4671+ // \param qmlFilename name of the qml file to be loaded by the QQuickView
4672 GestureTest(const QString &qmlFilename);
4673
4674 protected Q_SLOTS:
4675@@ -81,6 +81,18 @@
4676 virtual void cleanup(); // called right after each and every test function is executed
4677
4678 protected:
4679+ // QTest::touchEvent takes QPoint instead of QPointF and I don't want to
4680+ // lose precision due to rounding.
4681+ // Besides, those helper functions lead to more compact code.
4682+ void sendTouchPress(qint64 timestamp, int id, QPointF pos);
4683+ void sendTouchUpdate(qint64 timestamp, int id, QPointF pos);
4684+ void sendTouchRelease(qint64 timestamp, int id, QPointF pos);
4685+ void sendTouch(qint64 timestamp, int id, QPointF pos,
4686+ Qt::TouchPointState pointState, QEvent::Type eventType);
4687+
4688+ void passTime(qint64 timeSpanMs);
4689+
4690+protected:
4691 QTouchDevice *m_device;
4692 QQuickView *m_view;
4693 TouchRegistry *m_touchRegistry;
4694
4695=== modified file 'tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp'
4696--- tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp 2015-08-19 14:24:07 +0000
4697+++ tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp 2016-03-16 11:49:36 +0000
4698@@ -20,7 +20,6 @@
4699 #include <QtQml/QQmlEngine>
4700 #include <QPointer>
4701 #include <private/qquickmousearea_p.h>
4702-#include <private/qquickwindow_p.h>
4703
4704
4705 #include <DirectionalDragArea.h>
4706@@ -109,18 +108,6 @@
4707 void makoRightEdgeDrag_verticalDownwards();
4708 void makoLeftEdgeDrag_slowStart();
4709 void makoLeftEdgeDrag_movesSlightlyBackwardsOnStart();
4710-
4711-private:
4712- // QTest::touchEvent takes QPoint instead of QPointF and I don't want to
4713- // lose precision due to rounding.
4714- // Besides, those helper functions lead to more compact code.
4715- void sendTouchPress(qint64 timestamp, int id, QPointF pos);
4716- void sendTouchUpdate(qint64 timestamp, int id, QPointF pos);
4717- void sendTouchRelease(qint64 timestamp, int id, QPointF pos);
4718- void sendTouch(qint64 timestamp, int id, QPointF pos,
4719- Qt::TouchPointState pointState, QEvent::Type eventType);
4720-
4721- void passTime(qint64 timeSpanMs);
4722 };
4723
4724 tst_DirectionalDragArea::tst_DirectionalDragArea()
4725@@ -141,49 +128,6 @@
4726 QTRY_COMPARE(m_view->height(), (int)m_view->rootObject()->height());
4727 }
4728
4729-void tst_DirectionalDragArea::sendTouchPress(qint64 timestamp, int id, QPointF pos)
4730-{
4731- sendTouch(timestamp, id, pos, Qt::TouchPointPressed, QEvent::TouchBegin);
4732-}
4733-
4734-void tst_DirectionalDragArea::sendTouchUpdate(qint64 timestamp, int id, QPointF pos)
4735-{
4736- sendTouch(timestamp, id, pos, Qt::TouchPointMoved, QEvent::TouchUpdate);
4737-}
4738-
4739-void tst_DirectionalDragArea::sendTouchRelease(qint64 timestamp, int id, QPointF pos)
4740-{
4741- sendTouch(timestamp, id, pos, Qt::TouchPointReleased, QEvent::TouchEnd);
4742-}
4743-
4744-void tst_DirectionalDragArea::sendTouch(qint64 timestamp, int id, QPointF pos,
4745- Qt::TouchPointState pointState, QEvent::Type eventType)
4746-{
4747- m_fakeTimerFactory->updateTime(timestamp);
4748-
4749- QTouchEvent::TouchPoint point;
4750-
4751- point.setState(pointState);
4752- point.setId(id);
4753- point.setScenePos(pos);
4754- point.setPos(pos);
4755-
4756- QList<QTouchEvent::TouchPoint> points;
4757- points << point;
4758-
4759- QTouchEvent touchEvent(eventType, m_device, Qt::NoModifier, Qt::TouchPointPressed, points);
4760- QCoreApplication::sendEvent(m_view, &touchEvent);
4761-
4762- QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(m_view);
4763- windowPrivate->flushDelayedTouchEvent();
4764-}
4765-
4766-void tst_DirectionalDragArea::passTime(qint64 timeSpanMs)
4767-{
4768- qint64 finalTime = m_fakeTimerFactory->timeSource()->msecsSinceReference() + timeSpanMs;
4769- m_fakeTimerFactory->updateTime(finalTime);
4770-}
4771-
4772 namespace {
4773 QPointF calculateInitialTouchPos(DirectionalDragArea *edgeDragArea)
4774 {
4775
4776=== added file 'tests/plugins/Ubuntu/Gestures/tst_TouchGestureArea.cpp'
4777--- tests/plugins/Ubuntu/Gestures/tst_TouchGestureArea.cpp 1970-01-01 00:00:00 +0000
4778+++ tests/plugins/Ubuntu/Gestures/tst_TouchGestureArea.cpp 2016-03-16 11:49:36 +0000
4779@@ -0,0 +1,270 @@
4780+/*
4781+ * Copyright (C) 2016 Canonical, Ltd.
4782+ *
4783+ * This program is free software; you can redistribute it and/or modify
4784+ * it under the terms of the GNU General Public License as published by
4785+ * the Free Software Foundation; version 3.
4786+ *
4787+ * This program is distributed in the hope that it will be useful,
4788+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4789+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4790+ * GNU General Public License for more details.
4791+ *
4792+ * You should have received a copy of the GNU General Public License
4793+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4794+ */
4795+
4796+#include "GestureTest.h"
4797+
4798+#include <TouchGestureArea.h>
4799+
4800+#include <QQuickItem>
4801+#include <QQuickView>
4802+#include <QtTest>
4803+
4804+namespace {
4805+QPointF calculateInitialTouchPos(TouchGestureArea *edgeDragArea)
4806+{
4807+ QPointF localCenter(edgeDragArea->width() / 2., edgeDragArea->height() / 2.);
4808+ return edgeDragArea->mapToScene(localCenter);
4809+}
4810+}
4811+
4812+class tst_TouchGestureArea: public GestureTest
4813+{
4814+ Q_OBJECT
4815+public:
4816+ tst_TouchGestureArea();
4817+private Q_SLOTS:
4818+ void init() override; // called right before each and every test function is executed
4819+
4820+ void minimumTouchPoints();
4821+ void maximumTouchPoints();
4822+ void minimumAndMaximumTouchPoints();
4823+ void rejectGestureAfterRecognitionPeriod();
4824+ void releaseAndPressRecognisedGestureDoesNotRejectForPeriod();
4825+ void topAreaReceivesOwnershipFirstWithEqualPoints();
4826+ void topAreaReceivesOwnershipFirstWithMorePoints();
4827+
4828+private:
4829+ void initGestureComponent(TouchGestureArea *area);
4830+
4831+ QQuickItem *m_blueRect;
4832+ TouchGestureArea *m_gestureBottom;
4833+ TouchGestureArea *m_gestureMiddle;
4834+ TouchGestureArea *m_gestureTop;
4835+};
4836+
4837+tst_TouchGestureArea::tst_TouchGestureArea()
4838+ : GestureTest(QStringLiteral("tst_TouchGestureArea.qml"))
4839+{
4840+}
4841+
4842+inline void tst_TouchGestureArea::initGestureComponent(TouchGestureArea* area)
4843+{
4844+ area->setRecognitionTimer(m_fakeTimerFactory->createTimer(area));
4845+ area->setMinimumTouchPoints(1);
4846+ area->setMaximumTouchPoints(INT_MAX);
4847+ area->setRecognitionPeriod(50);
4848+ area->setReleaseRejectPeriod(100);
4849+ // start tests with area disabled (enable as desired)
4850+ area->setEnabled(false);
4851+}
4852+
4853+void tst_TouchGestureArea::init()
4854+{
4855+ GestureTest::init();
4856+
4857+ m_blueRect = m_view->rootObject()->findChild<QQuickItem*>("blueRect");
4858+ Q_ASSERT(m_blueRect != nullptr);
4859+
4860+ m_gestureBottom =
4861+ m_view->rootObject()->findChild<TouchGestureArea*>("touchGestureAreaBottom");
4862+ Q_ASSERT(m_gestureBottom != nullptr);
4863+
4864+ m_gestureMiddle =
4865+ m_view->rootObject()->findChild<TouchGestureArea*>("touchGestureAreaMiddle");
4866+ Q_ASSERT(m_gestureMiddle != nullptr);
4867+
4868+ m_gestureTop =
4869+ m_view->rootObject()->findChild<TouchGestureArea*>("touchGestureAreaTop");
4870+ Q_ASSERT(m_gestureTop != nullptr);
4871+
4872+ initGestureComponent(m_gestureBottom);
4873+ initGestureComponent(m_gestureMiddle);
4874+ initGestureComponent(m_gestureTop);
4875+}
4876+
4877+void tst_TouchGestureArea::minimumTouchPoints()
4878+{
4879+ m_gestureBottom->setEnabled(true);
4880+ m_gestureBottom->setMinimumTouchPoints(4);
4881+
4882+ QPointF touchPoint = calculateInitialTouchPos(m_gestureBottom);
4883+
4884+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::WaitingForTouch);
4885+ QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
4886+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Undecided);
4887+ QTest::touchEvent(m_view, m_device).stationary(0)
4888+ .press(1, touchPoint.toPoint());
4889+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Undecided);
4890+ QTest::touchEvent(m_view, m_device).stationary(0)
4891+ .stationary(1)
4892+ .press(2, touchPoint.toPoint());
4893+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Undecided);
4894+ QTest::touchEvent(m_view, m_device).stationary(0)
4895+ .stationary(1)
4896+ .stationary(2)
4897+ .press(3, touchPoint.toPoint());
4898+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Recognized);
4899+ // test minimum overflow
4900+ QTest::touchEvent(m_view, m_device).stationary(0)
4901+ .stationary(1)
4902+ .stationary(2)
4903+ .stationary(3)
4904+ .press(4, touchPoint.toPoint());
4905+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Recognized);
4906+}
4907+
4908+void tst_TouchGestureArea::maximumTouchPoints()
4909+{
4910+ m_gestureBottom->setEnabled(true);
4911+ m_gestureBottom->setMaximumTouchPoints(2);
4912+
4913+ QPointF touchPoint = calculateInitialTouchPos(m_gestureBottom);
4914+
4915+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::WaitingForTouch);
4916+ QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
4917+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Recognized);
4918+ QTest::touchEvent(m_view, m_device).stationary(0)
4919+ .press(1, touchPoint.toPoint());
4920+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Recognized);
4921+
4922+ // test maximum overflow
4923+ QTest::touchEvent(m_view, m_device).stationary(0)
4924+ .stationary(1)
4925+ .press(2, touchPoint.toPoint());
4926+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Rejected);
4927+ // test still rejected
4928+ QTest::touchEvent(m_view, m_device).stationary(0)
4929+ .stationary(1)
4930+ .press(3, touchPoint.toPoint());
4931+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Rejected);
4932+}
4933+
4934+void tst_TouchGestureArea::minimumAndMaximumTouchPoints()
4935+{
4936+ m_gestureBottom->setEnabled(true);
4937+ m_gestureBottom->setMinimumTouchPoints(2);
4938+ m_gestureBottom->setMaximumTouchPoints(2);
4939+
4940+ QPointF touchPoint = calculateInitialTouchPos(m_gestureBottom);
4941+
4942+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::WaitingForTouch);
4943+ QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
4944+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Undecided);
4945+ QTest::touchEvent(m_view, m_device).stationary(0)
4946+ .press(1, touchPoint.toPoint());
4947+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Recognized);
4948+
4949+ // test maximum overflow
4950+ QTest::touchEvent(m_view, m_device).stationary(0)
4951+ .stationary(1)
4952+ .press(2, touchPoint.toPoint());
4953+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Rejected);
4954+}
4955+
4956+void tst_TouchGestureArea::rejectGestureAfterRecognitionPeriod()
4957+{
4958+ m_gestureBottom->setEnabled(true);
4959+ m_gestureBottom->setMinimumTouchPoints(2);
4960+ m_gestureBottom->setMaximumTouchPoints(2);
4961+
4962+ QPointF touchPoint = calculateInitialTouchPos(m_gestureBottom);
4963+
4964+ QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
4965+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Undecided); // Recognition period is 50.
4966+ passTime(40);
4967+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Undecided);
4968+ passTime(10);
4969+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Rejected);
4970+}
4971+
4972+void tst_TouchGestureArea::releaseAndPressRecognisedGestureDoesNotRejectForPeriod()
4973+{
4974+ m_gestureBottom->setEnabled(true);
4975+ m_gestureBottom->setMinimumTouchPoints(2);
4976+ m_gestureBottom->setMaximumTouchPoints(2);
4977+
4978+ bool wasRejected = false;
4979+ connect(m_gestureBottom, &TouchGestureArea::statusChanged,
4980+ this, [&wasRejected](int status) {
4981+ if (status == TouchGestureArea::Rejected) wasRejected = true;
4982+ });
4983+
4984+ QPointF touchPoint = calculateInitialTouchPos(m_gestureBottom);
4985+
4986+ QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint())
4987+ .press(1, touchPoint.toPoint());
4988+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Recognized);
4989+ passTime(5);
4990+ QTest::touchEvent(m_view, m_device).stationary(0)
4991+ .release(1, touchPoint.toPoint()); // Release period is 100.
4992+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Recognized);
4993+ passTime(70);
4994+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Recognized);
4995+ QCOMPARE(wasRejected, false);
4996+ passTime(29);
4997+ QCOMPARE(wasRejected, false);
4998+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Recognized);
4999+ passTime(1);
5000+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Rejected);
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches