Merge lp:~nick-dedekind/unity8/side-stage-redesign into lp:unity8

Proposed by Nick Dedekind
Status: Superseded
Proposed branch: lp:~nick-dedekind/unity8/side-stage-redesign
Merge into: lp:unity8
Prerequisite: lp:~mzanetti/unity8/external-deviceconfig
Diff against target: 5646 lines (+3671/-447) (has conflicts)
55 files modified
CMakeLists.txt (+1/-1)
CODING (+2/-0)
debian/control (+7/-3)
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/-191)
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/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)
Text conflict in debian/control
To merge this branch: bzr merge lp:~nick-dedekind/unity8/side-stage-redesign
Reviewer Review Type Date Requested Status
Daniel d'Andrada (community) Approve
Unity8 CI Bot continuous-integration Needs Fixing
PS Jenkins bot (community) continuous-integration Needs Fixing
Michael Zanetti (community) Needs Fixing
Lukáš Tinkl code-review Pending
Review via email: mp+286194@code.launchpad.net

This proposal supersedes a proposal from 2016-01-04.

This proposal has been superseded by a proposal from 2016-03-11.

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 :
review: Needs Fixing (continuous-integration)
Revision history for this message
Nick Dedekind (nick-dedekind) wrote :

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 :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Daniel d'Andrada (dandrader) 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.

review: Needs Fixing
Revision history for this message
Daniel d'Andrada (dandrader) 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.

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

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

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

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 :

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 :
review: Needs Fixing (continuous-integration)
Revision history for this message
Nick Dedekind (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.

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

> 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 :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Nick Dedekind (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.

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

On 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 :
review: Needs Fixing (continuous-integration)
Revision history for this message
Daniel d'Andrada (dandrader) 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"
"""

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

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 :

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 :

> 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 :

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 :
review: Needs Fixing (continuous-integration)
Revision history for this message
Nick Dedekind (nick-dedekind) wrote :

> 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 :

> 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 :

> 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 :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Daniel d'Andrada (dandrader) wrote :

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 :

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 :
review: Needs Fixing (continuous-integration)
Revision history for this message
Nick Dedekind (nick-dedekind) wrote :

> 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 :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Zanetti (mzanetti) wrote :

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 :
review: Needs Fixing (continuous-integration)
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Nick Dedekind (nick-dedekind) wrote :

> 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 :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Daniel d'Andrada (dandrader) wrote :

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 :

> 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 :
review: Needs Fixing (continuous-integration)
Revision history for this message
Daniel d'Andrada (dandrader) wrote :

Thanks

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

* 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 :

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

review: Needs Fixing
2077. By Nick Dedekind

fixed tests

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

version bump

Unmerged revisions

2078. By Nick Dedekind

version bump

2077. By Nick Dedekind

fixed tests

2076. By Nick Dedekind

fixed opacicty

2075. By Daniel d'Andrada

review comments

2074. By Nick Dedekind

better visible values

2073. By Nick Dedekind

translate so shadows dont overlap

2072. By Nick Dedekind

added compiler suggestion

2071. By Nick Dedekind

Updated fullscreen app margin progress

2070. By Nick Dedekind

only set drag if all points are dragging

2069. By Nick Dedekind

dont update touch on unowned event if not pressed/released

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

Subscribers

People subscribed via source and target branches