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

Proposed by Daniel d'Andrada
Status: Merged
Approved by: Albert Astals Cid
Approved revision: 1341
Merged at revision: 1364
Proposed branch: lp:~dandrader/unity8/touchOwnership
Merge into: lp:unity8
Diff against target: 6741 lines (+4656/-503)
79 files modified
CMakeLists.txt (+2/-0)
debian/control (+1/-1)
debian/unity8-private.install (+1/-0)
libs/CMakeLists.txt (+1/-0)
libs/UbuntuGestures/CMakeLists.txt (+40/-0)
libs/UbuntuGestures/CandidateInactivityTimer.cpp (+41/-0)
libs/UbuntuGestures/CandidateInactivityTimer.h (+49/-0)
libs/UbuntuGestures/DebugHelpers.cpp (+68/-0)
libs/UbuntuGestures/DebugHelpers.h (+29/-0)
libs/UbuntuGestures/Pool.h (+134/-0)
libs/UbuntuGestures/Timer.cpp (+109/-0)
libs/UbuntuGestures/Timer.h (+105/-0)
libs/UbuntuGestures/TouchOwnershipEvent.cpp (+35/-0)
libs/UbuntuGestures/TouchOwnershipEvent.h (+50/-0)
libs/UbuntuGestures/TouchRegistry.cpp (+500/-0)
libs/UbuntuGestures/TouchRegistry.h (+182/-0)
libs/UbuntuGestures/UbuntuGesturesGlobal.h (+23/-0)
libs/UbuntuGestures/UnownedTouchEvent.cpp (+39/-0)
libs/UbuntuGestures/UnownedTouchEvent.h (+45/-0)
plugins/Ubuntu/Gestures/AxisVelocityCalculator.h (+2/-2)
plugins/Ubuntu/Gestures/CMakeLists.txt (+26/-7)
plugins/Ubuntu/Gestures/Direction.h (+2/-2)
plugins/Ubuntu/Gestures/DirectionalDragArea.cpp (+335/-209)
plugins/Ubuntu/Gestures/DirectionalDragArea.h (+37/-29)
plugins/Ubuntu/Gestures/TimeSource.h (+2/-2)
plugins/Ubuntu/Gestures/TouchGate.cpp (+347/-0)
plugins/Ubuntu/Gestures/TouchGate.h (+126/-0)
plugins/Ubuntu/Gestures/UbuntuGesturesQmlGlobal.h (+3/-3)
plugins/Ubuntu/Gestures/plugin.cpp (+3/-1)
plugins/Ubuntu/Gestures/plugin.h (+1/-1)
plugins/Ubuntu/Gestures/qmldir (+1/-1)
qml/Components/DragHandle.qml (+2/-2)
qml/Components/EdgeDragArea.qml (+2/-0)
qml/Components/InputMethod.qml (+13/-0)
qml/Dash/Dash.qml (+29/-7)
qml/Dash/DashContent.qml (+4/-1)
qml/Dash/GenericScopeView.qml (+2/-0)
qml/Launcher/Launcher.qml (+3/-1)
qml/Launcher/LauncherPanel.qml (+1/-0)
qml/Stages/ApplicationWindow.qml (+0/-18)
qml/Stages/PhoneStage.qml (+14/-5)
qml/Stages/SessionContainer.qml (+1/-13)
qml/Stages/SurfaceContainer.qml (+38/-7)
src/CMakeLists.txt (+6/-0)
src/Dash/CMakeLists.txt (+5/-0)
src/Dash/main.cpp (+6/-0)
src/main.cpp (+5/-0)
tests/CMakeLists.txt (+1/-0)
tests/libs/CMakeLists.txt (+1/-0)
tests/libs/UbuntuGestures/CMakeLists.txt (+20/-0)
tests/libs/UbuntuGestures/tst_TouchRegistry.cpp (+803/-0)
tests/mocks/Unity/Application/CMakeLists.txt (+1/-0)
tests/mocks/Unity/Application/MirSurfaceItem.cpp (+18/-22)
tests/mocks/Unity/Application/MirSurfaceItem.h (+18/-1)
tests/mocks/Unity/Application/MirSurfaceItem.qml (+0/-12)
tests/mocks/Unity/Application/UbuntuKeyboardInfo.cpp (+28/-0)
tests/mocks/Unity/Application/UbuntuKeyboardInfo.h (+60/-0)
tests/mocks/Unity/Application/plugin.cpp (+7/-0)
tests/plugins/Ubuntu/Gestures/CMakeLists.txt (+9/-2)
tests/plugins/Ubuntu/Gestures/RightwardsLauncher.qml (+3/-0)
tests/plugins/Ubuntu/Gestures/touchGateExample.qml (+27/-0)
tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp (+494/-17)
tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.qml (+1/-0)
tests/plugins/Ubuntu/Gestures/tst_TouchGate.cpp (+345/-0)
tests/qmltests/Components/CMakeLists.txt (+2/-1)
tests/qmltests/Components/tst_DragHandle.cpp (+14/-17)
tests/qmltests/Greeter/tst_SingleGreeter.qml (+1/-1)
tests/qmltests/Launcher/tst_Launcher.qml (+123/-107)
tests/qmltests/Stages/tst_ApplicationWindow.qml (+2/-2)
tests/qmltests/Stages/tst_PhoneStage.qml (+2/-1)
tests/qmltests/Stages/tst_SurfaceContainer.qml (+26/-1)
tests/qmltests/tst_Shell.qml (+118/-3)
tests/uqmlscene/CMakeLists.txt (+2/-0)
tests/uqmlscene/README (+3/-1)
tests/uqmlscene/main.cpp (+9/-0)
tests/utils/modules/Unity/Test/CMakeLists.txt (+2/-0)
tests/utils/modules/Unity/Test/UnityTestCase.qml (+2/-0)
tests/utils/modules/Unity/Test/testutil.cpp (+41/-2)
tests/utils/modules/Unity/Test/testutil.h (+3/-1)
To merge this branch: bzr merge lp:~dandrader/unity8/touchOwnership
Reviewer Review Type Date Requested Status
Albert Astals Cid (community) Approve
PS Jenkins bot (community) continuous-integration Needs Fixing
kevin gunn (community) Needs Fixing
Review via email: mp+236152@code.launchpad.net

Commit message

Add touch ownership logic on top of qt input handling

It does mainly 3 things:
- Adds TouchRegistry.
- Adds TouchGate
- Modifies DirectionalDragArea so that it uses TouchRegistry and only grabs a touch once it has recognized a gesture done by it.

TouchRegistry was put into a library as both unity8 and Ubuntu.Gestures plugin need to use it.

The next step is to have surfaces participating in the touch ownership negotiation. But for now they're put behind TouchGates so that they only receive thouches whose ownership has already been settled.

The most noticeable improvement of this patch is that taps near screen edges will now reach applications.

It also makes right-edge and bottom-edge drags only animate once a directional drag gesture has been fully recognized.

Description of the change

* Are there any related MPs required for this MP to build/function as expected? Please list.
https://code.launchpad.net/~dandrader/qtmir/UbuntuKeyboardInfoQMLSingleton/+merge/236151

* 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?
Not applicable.

* If you changed the UI, has there been a design review?
Not applicable.

To post a comment you must log in.
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
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~dandrader/unity8/touchOwnership updated
1296. By Nick Dedekind

Visual changes for indicator RTM polishing Fixes: 1329289, 1349921, 1350308, 1350952, 1354506, 1373319
Approved by: Andrea Cimitan

1297. By PS Jenkins bot

Releasing 8.00+14.10.20140926-0ubuntu1

1298. By PS Jenkins bot

Resync trunk

1299. By Launchpad Translations on behalf of unity-team

Launchpad automatic translations update.

1300. By Launchpad Translations on behalf of unity-team

Launchpad automatic translations update.

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)
lp:~dandrader/unity8/touchOwnership updated
1301. By Launchpad Translations on behalf of unity-team

Launchpad automatic translations update.

Revision history for this message
Albert Astals Cid (aacid) wrote :

Found 2 license problems:
libs/UbuntuGestures/UnownedTouchEvent.cpp UNKNOWN *No copyright*
tests/plugins/Ubuntu/Gestures/touchGateExample.qml UNKNOWN *No copyright*

Revision history for this message
kevin gunn (kgunn72) wrote :

currently failling to build in rtm-silo

https://launchpadlibrarian.net/186133082/buildlog_ubuntu-rtm-14.09-i386.unity8_8.00%2B14.10.20140930.2-0ubuntu1_FAILEDTOBUILD.txt.gz

looks like some code unhappiness & unit test failure of touchRegistryInput

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

> Found 2 license problems:
> libs/UbuntuGestures/UnownedTouchEvent.cpp UNKNOWN *No copyright*
> tests/plugins/Ubuntu/Gestures/touchGateExample.qml UNKNOWN *No copyright*

Fixed.

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

> currently failling to build in rtm-silo
>
> https://launchpadlibrarian.net/186133082/buildlog_ubuntu-
> rtm-14.09-i386.unity8_8.00%2B14.10.20140930.2-0ubuntu1_FAILEDTOBUILD.txt.gz
>
> looks like some code unhappiness & unit test failure of touchRegistryInput

Fixed.

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

> currently failling to build in rtm-silo
>
> https://launchpadlibrarian.net/186133082/buildlog_ubuntu-
> rtm-14.09-i386.unity8_8.00%2B14.10.20140930.2-0ubuntu1_FAILEDTOBUILD.txt.gz
>
> looks like some code unhappiness & unit test failure of touchRegistryInput

Fixed.

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)
lp:~dandrader/unity8/touchOwnership updated
1302. By Josh Arenson

Fix lp:1367894 by correcting how the minute value is calculated in the panel. Fixes: 1367894
Approved by: Nick Dedekind

1303. By Ying-Chun Liu

Remove maxLineCount in preview. (LP: 1328513) Fixes: 1328513
Approved by: Michael Terry

1304. By Gerry Boland

Cleanup: Remove unused member and fix small syntax error in OrientationLock
Approved by: Daniel d'Andrada

1305. By Andrea Cimitan

Move activity indicator on top of keyboard Fixes: 1354519
Approved by: Michael Terry

1306. By Alexandros Frantzis

Remove stale trusted socket before starting unity8 from upstart (LP: #1371597) Fixes: 1371597
Approved by: Ying-Chun Liu

1307. By Michael Terry

Fix some code that accidentally landed in trunk before it got cleaned up. The current code just has some duplication to it that should be unified.
Approved by: Michael Zanetti

1308. By Michael Terry

Implement latest visual designs for passphrase lockscreen.

The visual specs are here:
https://drive.google.com/a/canonical.com/folderview?id=0B8I8ZVKH-8Ssb2Zhcm9kUE9TLVE

This branch does a few things:

- Total redesign for passphrase lockscreen. It now has no visible text box nor says "Hello".
- Total redesign of how we inform the user that they have attempted to log in too many times. Now instead of saying something like "please wait" in the same lockscreen, we show a custom lockscreen with a longer message and a lock icon.
- Tweak the spacing on the passcode lockscreen.
- Adds a phone icon to the "Emergency Call" label on the bottom -- and makes that label move up if the OSK covers it.
- Changes the "Swipe to Unlock" text to just "Unlock" with arrow brackets on either side.
Approved by: Michael Zanetti

1309. By Michael Terry

Make it easier to use the Lockscreen component from the welcome wizard.
Approved by: Albert Astals Cid

1310. By Daniel d'Andrada

Add gdbTestComponentName build targets
Approved by: Michael Zanetti

1311. By Michael Terry

Limit how much memory we reserve for the greeter background image, allowing giant images to appear correctly.

The downside is that even if the image is smaller, we'll still reserve the whole screen size in memory. But we avoid the actual bug anyway. Fixes: 1373462
Approved by: Michael Zanetti

1312. By Nick Dedekind

Fixed DefaultIndicatorPage test. Fixed warnings from test.
Approved by: Albert Astals Cid

1313. By PS Jenkins bot

Releasing 8.00+14.10.20140930.2-0ubuntu1

Revision history for this message
Albert Astals Cid (aacid) wrote :

Do you think you can add to the commit a two or three lines description of the big picture of this change so that if someone in the future comes back looking at it has a starting point?

review: Needs Information
Revision history for this message
Albert Astals Cid (aacid) wrote :

Copyright (C) 2013 Canonical -> 2014

review: Needs Fixing
Revision history for this message
Albert Astals Cid (aacid) wrote :

 m_touchId = touchId;
 m_candidate = candidate;

Move those up to the constructor initializer list?

review: Needs Fixing
Revision history for this message
Albert Astals Cid (aacid) wrote :

default: // Qt::TouchPointReleased:
default: //QEvent::TouchCancel

I think it's better if you actually add a case so if/when they add new values to the enum we get a warning.

review: Needs Fixing
Revision history for this message
Albert Astals Cid (aacid) wrote :

for (int i=0; i < ev->touchPoints().size(); ++i) {
   const QTouchEvent::TouchPoint& touchPoint = ev->touchPoints().at(i);

foreach?

review: Needs Fixing
Revision history for this message
Albert Astals Cid (aacid) wrote :

template <class ItemType> class Pool

Can you please document the functions that ItemType has to have to be able to use Pool?

review: Needs Fixing
Revision history for this message
Albert Astals Cid (aacid) wrote :

virtual void start() { m_isRunning = true; };

Remove the extra ; at the end

review: Needs Fixing
Revision history for this message
Albert Astals Cid (aacid) wrote :

 FakeTimer(QObject *parent = nullptr) needs to initialize m_interval to something

review: Needs Fixing
Revision history for this message
Albert Astals Cid (aacid) wrote :

auto &watchers = touchInfo->watchers;

is this smart enough to make it const? if not make it const :)

review: Needs Fixing
lp:~dandrader/unity8/touchOwnership updated
1314. By PS Jenkins bot

Resync trunk

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

> Do you think you can add to the commit a two or three lines description of the
> big picture of this change so that if someone in the future comes back looking
> at it has a starting point?

Done.

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

> Copyright (C) 2013 Canonical -> 2014

Done.

Revision history for this message
Albert Astals Cid (aacid) wrote :

what about this?

=== modified file 'libs/UbuntuGestures/TouchRegistry.cpp'
--- libs/UbuntuGestures/TouchRegistry.cpp 2014-09-26 16:08:18 +0000
+++ libs/UbuntuGestures/TouchRegistry.cpp 2014-10-01 11:00:28 +0000
@@ -139,10 +139,10 @@
     // TODO: Consider what happens if an item calls any of TouchRegistry's public methods
     // from the event handler callback.
     m_inDispatchLoop = true;
- auto it = touchIdsForItems.begin();
- while (it != touchIdsForItems.end()) {
+ auto it = touchIdsForItems.constBegin();
+ while (it != touchIdsForItems.constEnd()) {
         QQuickItem *item = it.key();
- QList<int> &touchIds = it.value();
+ const QList<int> &touchIds = it.value();
         dispatchPointsToItem(event, touchIds, item);
         ++it;
     };
@@ -163,7 +163,7 @@
    Extracts the touches with the given touchIds from event and send them in a
    UnownedTouchEvent to the given item
  */
-void TouchRegistry::dispatchPointsToItem(const QTouchEvent *event, QList<int> &touchIds,
+void TouchRegistry::dispatchPointsToItem(const QTouchEvent *event, const QList<int> &touchIds,
         QQuickItem *item)
 {
     Qt::TouchPointStates touchPointStates = 0;

=== modified file 'libs/UbuntuGestures/TouchRegistry.h'
--- libs/UbuntuGestures/TouchRegistry.h 2014-09-26 15:52:09 +0000
+++ libs/UbuntuGestures/TouchRegistry.h 2014-10-01 11:01:03 +0000
@@ -156,7 +156,7 @@

     static void translateTouchPointFromScreenToWindowCoords(QTouchEvent::TouchPoint &touchPoint);

- static void dispatchPointsToItem(const QTouchEvent *event, QList<int> &touchIds,
+ static void dispatchPointsToItem(const QTouchEvent *event, const QList<int> &touchIds,
                                      QQuickItem *item);
     void freeEndedTouchInfos();

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

> m_touchId = touchId;
> m_candidate = candidate;
>
> Move those up to the constructor initializer list?

Done.

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

> default: // Qt::TouchPointReleased:
> default: //QEvent::TouchCancel
>
> I think it's better if you actually add a case so if/when they add new values
> to the enum we get a warning.

Done.

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

> for (int i=0; i < ev->touchPoints().size(); ++i) {
> const QTouchEvent::TouchPoint& touchPoint = ev->touchPoints().at(i);
>
> foreach?

Done.

Revision history for this message
Albert Astals Cid (aacid) wrote :

In TouchRegistry::dispatchPointsToItem who is deleting?

QTouchEvent *eventForItem = new QTouchEvent

?

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

> template <class ItemType> class Pool
>
> Can you please document the functions that ItemType has to have to be able to
> use Pool?

Done.

Revision history for this message
Albert Astals Cid (aacid) wrote :

"2- That item will receive UnownedTouchEvents about for items"

I could not understand, Daniel agrees needs to be fixed

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

> virtual void start() { m_isRunning = true; };
>
> Remove the extra ; at the end

Done.

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

> FakeTimer(QObject *parent = nullptr) needs to initialize m_interval to
> something

Done.

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

> auto &watchers = touchInfo->watchers;
>
> is this smart enough to make it const? if not make it const :)

Done.

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

> what about this?
>
> === modified file 'libs/UbuntuGestures/TouchRegistry.cpp'
> [...]

Done.

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

> In TouchRegistry::dispatchPointsToItem who is deleting?
>
> QTouchEvent *eventForItem = new QTouchEvent
>
> ?

Oh, you scared me for a minute.

UnownedTouchEvent::m_touchEvent is a QScopedPointer. Thus eventForItem will get deleted once the UnownedTouchEvent that holds it gets deleted.

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 :

> "2- That item will receive UnownedTouchEvents about for items"
>
> I could not understand, Daniel agrees needs to be fixed

Done.

lp:~dandrader/unity8/touchOwnership updated
1315. By Daniel d'Andrada

Add touch ownership logic on top of qt input handling

It does mainly 3 things:
- Adds TouchRegistry.
- Adds TouchGate
- Modifies DirectionalDragArea so that it uses TouchRegistry and only grabs a touch once it has recognized a gesture done by it.

TouchRegistry was put into a library as both unity8 and Ubuntu.Gestures plugin need to use it.

The next step is to have surfaces participating in the touch ownership negotiation. But for now they're put behind TouchGates so that they only receive thouches whose ownership has already been settled.

The most noticeable improvement of this patch is that taps near screen edges will now reach applications.

Revision history for this message
Albert Astals Cid (aacid) wrote :

DirectionalDragArea::touchEvent_undecided has a return at the end that is not necessary

review: Needs Fixing
Revision history for this message
Albert Astals Cid (aacid) wrote :

 if (oldStatus == Undecided) {
 }

in WaitingForTouch case of DirectionalDragArea::setStatus feels like it either needs a body or should be removed.

review: Needs Fixing
lp:~dandrader/unity8/touchOwnership updated
1316. By Daniel d'Andrada

exit loop once there's no point in continuing

1317. By Daniel d'Andrada

Remove superfluous return statement.

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

On 01/10/14 10:38, Albert Astals Cid wrote:
> Review: Needs Fixing
>
> DirectionalDragArea::touchEvent_undecided has a return at the end that is not necessary
Fixed.

lp:~dandrader/unity8/touchOwnership updated
1318. By Daniel d'Andrada

Remove leftover code

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

On 01/10/14 10:40, Albert Astals Cid wrote:
> Review: Needs Fixing
>
> if (oldStatus == Undecided) {
> }
>
> in WaitingForTouch case of DirectionalDragArea::setStatus feels like it either needs a body or should be removed.

Curious leftover. :)
Removed.

Revision history for this message
Albert Astals Cid (aacid) wrote :

What about ?

 void TouchGate::dispatchFullyOwnedEvents()
 {
     while (!m_storedEvents.isEmpty() && eventIsFullyOwned(m_storedEvents.first())) {
- QTouchEvent *event = m_storedEvents.first();
- m_storedEvents.removeFirst();
+ QTouchEvent *event = m_storedEvents.takeFirst();

Revision history for this message
Albert Astals Cid (aacid) wrote :

I think we're leaking event in TouchGate::removeTouchFromStoredEvents when doing m_storedEvents.removeAt(i); ?

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

On 01/10/14 11:44, Albert Astals Cid wrote:
> What about ?
>
> void TouchGate::dispatchFullyOwnedEvents()
> {
> while (!m_storedEvents.isEmpty() && eventIsFullyOwned(m_storedEvents.first())) {
> - QTouchEvent *event = m_storedEvents.first();
> - m_storedEvents.removeFirst();
> + QTouchEvent *event = m_storedEvents.takeFirst();
>

Done.

lp:~dandrader/unity8/touchOwnership updated
1319. By Daniel d'Andrada

Small refactoring

Revision history for this message
Albert Astals Cid (aacid) wrote :

Should we have a qDeleteAll(m_storedEvents); on TouchGate destructor?

review: Needs Fixing
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~dandrader/unity8/touchOwnership updated
1320. By Daniel d'Andrada

TouchGate can no longer leak stored QTouchEvents

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

On 01/10/14 11:46, Albert Astals Cid wrote:
> Review: Needs Fixing
>
> I think we're leaking event in TouchGate::removeTouchFromStoredEvents when doing m_storedEvents.removeAt(i); ?
Yes. Fixed. Thanks for spotting this.

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

On 01/10/14 11:52, Albert Astals Cid wrote:
> Review: Needs Fixing
>
> Should we have a qDeleteAll(m_storedEvents); on TouchGate destructor?
No longer applicable after I refactored m_storedEvents.

lp:~dandrader/unity8/touchOwnership updated
1321. By Daniel d'Andrada

Missing bit

1322. By Daniel d'Andrada

Clean up things

1323. By Daniel d'Andrada

Fix debug code

1324. By Daniel d'Andrada

fix tst_ApplicationWindow.qml

Revision history for this message
Albert Astals Cid (aacid) wrote :

Failing tests:

FAIL! : qmltestrunner::Drag and Drop::test_dragndrop(startDrag) property x
   Actual (): -64
   Expected (): 0
   Loc: [/home/tsdgeos_work/phablet/unity8/touchOwnership/tests/qmltests/Launcher/tst_Launcher.qml(82)]

FAIL! : qmltestrunner::Drag and Drop::test_dragndrop(startDrag) property x
   Actual (): -64
   Expected (): 0
   Loc: [/home/tsdgeos_work/phablet/unity8/touchOwnership/tests/qmltests/Launcher/tst_Launcher.qml(82)]

FAIL! : qmltestrunner::Drag and Drop::test_quicklist_dismiss() property visible
   Actual (): false
   Expected (): true
   Loc: [/home/tsdgeos_work/phablet/unity8/touchOwnership/tests/qmltests/Launcher/tst_Launcher.qml(384)]

FAIL! : qmltestrunner::Drag and Drop::test_quicklist_positioning(bottom) property opacity
   Actual (): 0
   Expected (): 0.8
   Loc: [/home/tsdgeos_work/phablet/unity8/touchOwnership/tests/qmltests/Launcher/tst_Launcher.qml(419)]

FAIL! : qmltestrunner::Launcher::test_clickingOnAppIconCausesSignalEmission() property lastSelectedApplication
   Actual ():
   Expected (): dialer-app
   Loc: [/home/tsdgeos_work/phablet/unity8/touchOwnership/tests/qmltests/Launcher/tst_Launcher.qml(139)]

FAIL! : qmltestrunner::test_clickFlick(unfolded top) 'verify()' returned FALSE. ()
   Loc: [/home/tsdgeos_work/phablet/unity8/touchOwnership/tests/qmltests/Launcher/tst_Launcher.qml(253)]

FAIL! : qmltestrunner::test_clickFlick(unfolded bottom) 'verify()' returned FALSE. ()
   Loc: [/home/tsdgeos_work/phablet/unity8/touchOwnership/tests/qmltests/Launcher/tst_Launcher.qml(253)]

FAIL! : qmltestrunner::Panel::test_drag_show(fullscreen) function returned unexpected result
   Actual (): false
   Expected (): true
   Loc: [/home/tsdgeos_work/phablet/unity8/touchOwnership/tests/qmltests/Panel/tst_Panel.qml(281)]

QFATAL : qmltestrunner::Panel::test_drag_show(pinned-alreadyOpen) ASSERT: "!event->touchPointStates().testFlag(Qt::TouchPointPressed)" in file /home/tsdgeos_work/phablet/unity8/touchOwnership/plugins/Ubuntu/Gestures/DirectionalDragArea.cpp, line 263
FAIL! : qmltestrunner::Panel::test_drag_show(pinned-alreadyOpen) Received a fatal error.
   Loc: [Unknown file(0)]

FAIL! : qmltestrunner::PhoneStage::test_enterSpread(<position1 (linear movement)) property focusedApplicationId
   Actual (): webbrowser-app
   Expected (): facebook-webapp

review: Needs Fixing
lp:~dandrader/unity8/touchOwnership updated
1325. By Daniel d'Andrada

Add a mock UbuntuKeyboardInfo

1326. By Daniel d'Andrada

Made tst_Launcher tests pass again took the opportunity to recfactor it a bit

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

> Failing tests:
>
> [...]

Fixed.

Revision history for this message
Albert Astals Cid (aacid) wrote :

New one

FAIL! : qmltestrunner::Launcher::test_clickFlick(unfolded bottom) 'verify()' returned FALSE. ()
   Loc: [/home/tsdgeos_work/phablet/unity8/touchOwnership/tests/qmltests/Launcher/tst_Launcher.qml(282)]

review: Needs Fixing
Revision history for this message
Albert Astals Cid (aacid) wrote :

Interestingly now test_clickFlick failed but got

FAIL! : qmltestrunner::Launcher::test_dragndrop(fullDrag vertical) property itemOpacity
   Actual (): 1
   Expected (): 0
   Loc: [/home/tsdgeos_work/phablet/unity8/touchOwnership/tests/qmltests/Launcher/tst_Launcher.qml(318)]

Try looping it with

while [ true ]; do make xvfbtestLauncher; if [ $? -ne 0 ]; then break; fi; done

review: Needs Fixing
Revision history for this message
Albert Astals Cid (aacid) wrote :
Download full text (5.8 KiB)

Crashes when trying to show the dash overview.

$ GRID_UNIT_PX=12 gdb -arg ./builddir/src/Dash/unity8-dash -mousetouch

#0 0x00007fffcf9c0f11 in Pool<TouchRegistry::TouchInfo>::forEach<TouchRegistry::findTouchInfo(int)::<lambda(Pool<TouchRegistry::TouchInfo>::Iterator&)> >(TouchRegistry::<lambda(Pool<TouchRegistry::TouchInfo>::Iterator&)>) (this=0x10, func=...)
    at /home/tsdgeos_work/phablet/unity8/touchOwnership/libs/UbuntuGestures/Pool.h:116
#1 0x00007fffcf9c0675 in TouchRegistry::findTouchInfo (this=0x0, id=0) at /home/tsdgeos_work/phablet/unity8/touchOwnership/libs/UbuntuGestures/TouchRegistry.cpp:380
#2 0x00007fffcf9bfdd7 in TouchRegistry::addCandidateOwnerForTouch (this=0x0, id=0, candidate=0xb67fb0) at /home/tsdgeos_work/phablet/unity8/touchOwnership/libs/UbuntuGestures/TouchRegistry.cpp:252
#3 0x00007fffcfbe4ed2 in DirectionalDragArea::touchEvent_absent (this=0xb67fb0, event=0x45f53e0) at /home/tsdgeos_work/phablet/unity8/touchOwnership/plugins/Ubuntu/Gestures/DirectionalDragArea.cpp:443
#4 0x00007fffcfbe4bf8 in DirectionalDragArea::touchEvent (this=0xb67fb0, event=0x45f53e0) at /home/tsdgeos_work/phablet/unity8/touchOwnership/plugins/Ubuntu/Gestures/DirectionalDragArea.cpp:370
#5 0x00007ffff6c6677d in QQuickItem::event(QEvent*) () from /usr/lib/x86_64-linux-gnu/libQt5Quick.so.5
#6 0x00007fffcfbe4606 in DirectionalDragArea::event (this=0xb67fb0, event=0x45f53e0) at /home/tsdgeos_work/phablet/unity8/touchOwnership/plugins/Ubuntu/Gestures/DirectionalDragArea.cpp:241
#7 0x00007ffff610ad05 in QCoreApplication::notify (this=<optimized out>, receiver=<optimized out>, event=<optimized out>) at kernel/qcoreapplication.cpp:997
#8 0x00007ffff610ae3b in QCoreApplication::notifyInternal (this=0x613000, receiver=0xb67fb0, event=0x45f53e0) at kernel/qcoreapplication.cpp:935
#9 0x00007ffff6c77f57 in QQuickWindowPrivate::deliverMatchingPointsToItem(QQuickItem*, QTouchEvent*, QSet<int>*, QSet<int> const&, QList<QTouchEvent::TouchPoint> const&) () from /usr/lib/x86_64-linux-gnu/libQt5Quick.so.5
#10 0x00007ffff6c788cb in QQuickWindowPrivate::deliverTouchPoints(QQuickItem*, QTouchEvent*, QList<QTouchEvent::TouchPoint> const&, QSet<int>*, QHash<QQuickItem*, QList<QTouchEvent::TouchPoint> >*) () from /usr/lib/x86_64-linux-gnu/libQt5Quick.so.5
#11 0x00007ffff6c7873e in QQuickWindowPrivate::deliverTouchPoints(QQuickItem*, QTouchEvent*, QList<QTouchEvent::TouchPoint> const&, QSet<int>*, QHash<QQuickItem*, QList<QTouchEvent::TouchPoint> >*) () from /usr/lib/x86_64-linux-gnu/libQt5Quick.so.5
#12 0x00007ffff6c7873e in QQuickWindowPrivate::deliverTouchPoints(QQuickItem*, QTouchEvent*, QList<QTouchEvent::TouchPoint> const&, QSet<int>*, QHash<QQuickItem*, QList<QTouchEvent::TouchPoint> >*) () from /usr/lib/x86_64-linux-gnu/libQt5Quick.so.5
#13 0x00007ffff6c7873e in QQuickWindowPrivate::deliverTouchPoints(QQuickItem*, QTouchEvent*, QList<QTouchEvent::TouchPoint> const&, QSet<int>*, QHash<QQuickItem*, QList<QTouchEvent::TouchPoint> >*) () from /usr/lib/x86_64-linux-gnu/libQt5Quick.so.5
#14 0x00007ffff6c7873e in QQuickWindowPrivate::deliverTouchPoints(QQuickItem*, QTouchEvent*, QList<QTouchEvent::TouchPoint> const&, QSet<int>*, QHash<QQuickItem*, QList<Q...

Read more...

review: Needs Fixing
lp:~dandrader/unity8/touchOwnership updated
1327. By Daniel d'Andrada

unity8-dash also needs a TouchRegistry

as it uses DirectionDragArea as well

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

On 02/10/14 12:10, Albert Astals Cid wrote:
> Review: Needs Fixing
>
> Crashes when trying to show the dash overview.

Fixed.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Albert Astals Cid (aacid) wrote :

I've been playing with it and the right hand site is much better but still is a bit flacky, i'm using this ppa https://launchpad.net/~unity-team/+archive/ubuntu/phone-right-edge/ that contains both lp:~aacid/unity8/list_on_bottom_swipe and merge edgeowner lp:~dandrader/unity8/touchOwnership and half of the times i do a click tap very close to the right edge while being the the "scopes list" (the thing that shows when you pull up from the bottom in the scopes) i get the favorite/unfavorite toggled and the other half i get the right edge animation.

Can you have a look? I did put a
  Rectangle { anchors.fill: parent; color: "red"; opacity: 0.5 }
inside starArea of /usr/share/unity8/Dash/ScopesListCategoryItem.qml to make sure it's really attach to the border.

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

> I've been playing with it and the right hand site is much better but still is
> a bit flacky, i'm using this ppa https://launchpad.net/~unity-
> team/+archive/ubuntu/phone-right-edge/ that contains both
> lp:~aacid/unity8/list_on_bottom_swipe and merge edgeowner
> lp:~dandrader/unity8/touchOwnership and half of the times i do a click tap
> very close to the right edge while being the the "scopes list" (the thing that
> shows when you pull up from the bottom in the scopes) i get the
> favorite/unfavorite toggled and the other half i get the right edge animation.
>
> Can you have a look? I did put a
> Rectangle { anchors.fill: parent; color: "red"; opacity: 0.5 }
> inside starArea of /usr/share/unity8/Dash/ScopesListCategoryItem.qml to make
> sure it's really attach to the border.

The remaining problem with the right edge is that its edge-drag recognizer parameters makes it recognize pretty much anything as a edge drag. So a tap there with a fat finger will cause a small movement enough for it to consider as an edge drag. So it's parameters must be tweaked. Strictly speaking it's a separate issue and thus I was planning to tackle it on a separate MP as this one is already huge. But maybe I should do it already...

lp:~dandrader/unity8/touchOwnership updated
1328. By Daniel d'Andrada

Make tst_Launcher stable

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

> New one
>
> FAIL! : qmltestrunner::Launcher::test_clickFlick(unfolded bottom) 'verify()'
> returned FALSE. ()
> Loc: [/home/tsdgeos_work/phablet/unity8/touchOwnership/tests/qmltests/Launc
> her/tst_Launcher.qml(282)]

Fixed. tst_Launcher works reliably now.

lp:~dandrader/unity8/touchOwnership updated
1329. By Daniel d'Andrada

Only move the stage once an edge-drag gesture has been recognized

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

> > I've been playing with it and the right hand site is much better but still
> is
> > a bit flacky, i'm using this ppa https://launchpad.net/~unity-
> > team/+archive/ubuntu/phone-right-edge/ that contains both
> > lp:~aacid/unity8/list_on_bottom_swipe and merge edgeowner
> > lp:~dandrader/unity8/touchOwnership and half of the times i do a click tap
> > very close to the right edge while being the the "scopes list" (the thing
> that
> > shows when you pull up from the bottom in the scopes) i get the
> > favorite/unfavorite toggled and the other half i get the right edge
> animation.
> >
> > Can you have a look? I did put a
> > Rectangle { anchors.fill: parent; color: "red"; opacity: 0.5 }
> > inside starArea of /usr/share/unity8/Dash/ScopesListCategoryItem.qml to make
> > sure it's really attach to the border.
>
> The remaining problem with the right edge is that its edge-drag recognizer
> parameters makes it recognize pretty much anything as a edge drag. So a tap
> there with a fat finger will cause a small movement enough for it to consider
> as an edge drag. So it's parameters must be tweaked. Strictly speaking it's a
> separate issue and thus I was planning to tackle it on a separate MP as this
> one is already huge. But maybe I should do it already...

Fixed.

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)
lp:~dandrader/unity8/touchOwnership updated
1330. By Daniel d'Andrada

Only show app shadows when we are indeed perfoming and edge drag

1331. By Daniel d'Andrada

Only animate the dash overview drag after the gesture has been recognized

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

Found an issue that seems to have been caused by this branch:
1 - Launch a couple of apps
2 - go to the app spread
3 - Drag in the launcher
4 - tap on the ubuntu icon

expected outcome:
dash is brought to foreground

actual outcome:
the app spread behind the launcher just shudders a bit and that's it. Only on the third tap or so does the dash finally go to foreground.

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)
lp:~dandrader/unity8/touchOwnership updated
1332. By Daniel d'Andrada

Add regression test Shell.test_tapUbuntuIconInLauncherOverAppSpread

1333. By Daniel d'Andrada

Merge lp:unity8

[ Andrea Cimitan ]
* Tweak card header to match the spec
* Add preview image slideshow (LP: #1351537)
[ Michał Sawicz ]
* Cache more things in memory, so flicking scopes should be faster
  (LP: #1336724)
* Tweak card header to match the spec
* Save texture memory by limiting sourceSize (LP: #1338430)
[ Ying-Chun Liu ]
* Add attributes to Preview. (LP: #1282460)
[ Albert Astals ]
* Update pot
* Cache more things in memory, so flicking scopes should be faster
  (LP: #1336724)
* Save texture memory by limiting sourceSize (LP: #1338430)
* Clip the settings list
* Fix unlocking from the left again
* Add wait_ makes tests more reliable
[ Michael Zanetti ]
* fix fading out the launcher instead of sliding it out on left-edge
  minimizing an app.
* Make the DashCommunicator async and more flexible to handle a
  lifecycle-suspended dash (LP: #1339883)
[ Michael Terry ]
* Retry unlock-device script if it fails, as there is always a risk of
  a small race with boot-up. (LP: #1370644)
* Add pull-to-refresh functionality to scopes. (LP: #1368336)
[ CI bot ]
* Resync trunk

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~dandrader/unity8/touchOwnership updated
1334. By Daniel d'Andrada

Work around bug in Qt

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

> Found an issue that seems to have been caused by this branch:
> 1 - Launch a couple of apps
> 2 - go to the app spread
> 3 - Drag in the launcher
> 4 - tap on the ubuntu icon
>
> expected outcome:
> dash is brought to foreground
>
> actual outcome:
> the app spread behind the launcher just shudders a bit and that's it. Only on
> the third tap or so does the dash finally go to foreground.

Fixed it. It's actually caused by a problem in Qt code.

Revision history for this message
Albert Astals Cid (aacid) wrote :

> > Found an issue that seems to have been caused by this branch:
> > 1 - Launch a couple of apps
> > 2 - go to the app spread
> > 3 - Drag in the launcher
> > 4 - tap on the ubuntu icon
> >
> > expected outcome:
> > dash is brought to foreground
> >
> > actual outcome:
> > the app spread behind the launcher just shudders a bit and that's it. Only
> on
> > the third tap or so does the dash finally go to foreground.
>
> Fixed it. It's actually caused by a problem in Qt code.

Should we file a bug for it with testcase?

review: Needs Information
lp:~dandrader/unity8/touchOwnership updated
1335. By Daniel d'Andrada

Remove traling whitespace

1336. By Daniel d'Andrada

Add TODO comment on bug report

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

> > > Found an issue that seems to have been caused by this branch:
> > > 1 - Launch a couple of apps
> > > 2 - go to the app spread
> > > 3 - Drag in the launcher
> > > 4 - tap on the ubuntu icon
> > >
> > > expected outcome:
> > > dash is brought to foreground
> > >
> > > actual outcome:
> > > the app spread behind the launcher just shudders a bit and that's it. Only
> > on
> > > the third tap or so does the dash finally go to foreground.
> >
> > Fixed it. It's actually caused by a problem in Qt code.
>
> Should we file a bug for it with testcase?

Yes, but this kind of thing takes time and a critical bug just landed on my lap.
Added it to my TODO list and add a TODO comment in the code.

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)
lp:~dandrader/unity8/touchOwnership updated
1337. By Daniel d'Andrada

Update tst_PhoneStage.qml

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
Albert Astals Cid (aacid) wrote :

So i've been playing with the bottom edge and still didn't like that sometimes you can get it to scroll the list while doing the overview and i came up with http://paste.ubuntu.com/8520086/ that seems to solve the thing for me and you can still click on the bottom edges.

What do you think? Maybe the name should forceNoFlickInteractive since ClickInteractions are still enabled?

lp:~dandrader/unity8/touchOwnership updated
1338. By Daniel d'Andrada

Avoid conflict between dash vertical flickable and its bottom-edge DDA

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

> So i've been playing with the bottom edge and still didn't like that sometimes
> you can get it to scroll the list while doing the overview and i came up with
> http://paste.ubuntu.com/8520086/ that seems to solve the thing for me and you
> can still click on the bottom edges.
>
> What do you think? Maybe the name should forceNoFlickInteractive since
> ClickInteractions are still enabled?

I think it's a sound approach. Pushed with minor modifications (added a big fat comment and s/forceNoInteractive/forceNonInteractive)

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Albert Astals Cid (aacid) wrote :

$ make xvfbtestPhoneStage
...
QFATAL : qmltestrunner::PhoneStage::test_enterSpread(>position1) ASSERT: "event->type() == QEvent::TouchBegin" in file /home/tsdgeos_work/phablet/unity8/touchOwnership/plugins/Ubuntu/Gestures/DirectionalDragArea.cpp, line 475
FAIL! : qmltestrunner::PhoneStage::test_enterSpread(>position1) Received a fatal error.

review: Needs Fixing
Revision history for this message
Albert Astals Cid (aacid) wrote :

$ make xvfbtestShell
....
FAIL! : qmltestrunner::Shell::test_leftEdgeDrag(with launcher) property focusedApplicationId
   Actual (): dialer-app
   Expected (): unity8-dash
   Loc: [/home/tsdgeos_work/phablet/unity8/touchOwnership/tests/qmltests/tst_Shell.qml(440)]

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

On 08/10/14 10:40, Albert Astals Cid wrote:
> Review: Needs Fixing
>
> $ make xvfbtestPhoneStage
> ...
> QFATAL : qmltestrunner::PhoneStage::test_enterSpread(>position1) ASSERT: "event->type() == QEvent::TouchBegin" in file /home/tsdgeos_work/phablet/unity8/touchOwnership/plugins/Ubuntu/Gestures/DirectionalDragArea.cpp, line 475
> FAIL! : qmltestrunner::PhoneStage::test_enterSpread(>position1) Received a fatal error.
>
I don't get this error. Are you sure you have the latest version of this
branch?

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

On 08/10/14 10:42, Albert Astals Cid wrote:
> Review: Needs Fixing
>
> $ make xvfbtestShell
> ....
> FAIL! : qmltestrunner::Shell::test_leftEdgeDrag(with launcher) property focusedApplicationId
> Actual (): dialer-app
> Expected (): unity8-dash
> Loc: [/home/tsdgeos_work/phablet/unity8/touchOwnership/tests/qmltests/tst_Shell.qml(440)]
>
That's failing in lp:unity8 as well. I'm not fixing it in this branch.

Revision history for this message
Albert Astals Cid (aacid) wrote :

> On 08/10/14 10:40, Albert Astals Cid wrote:
> > Review: Needs Fixing
> >
> > $ make xvfbtestPhoneStage
> > ...
> > QFATAL : qmltestrunner::PhoneStage::test_enterSpread(>position1) ASSERT:
> "event->type() == QEvent::TouchBegin" in file /home/tsdgeos_work/phablet/unity
> 8/touchOwnership/plugins/Ubuntu/Gestures/DirectionalDragArea.cpp, line 475
> > FAIL! : qmltestrunner::PhoneStage::test_enterSpread(>position1) Received a
> fatal error.
> >
> I don't get this error. Are you sure you have the latest version of this
> branch?

Error went away when i dist-upgraded and this came in via the ppa so i guess there is a need to prepend the local libUbuntuGestures to the library path so the one compiled in the code is the one that takes precedence and not this one.

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

On 08/10/14 11:01, Albert Astals Cid wrote:
>> On 08/10/14 10:40, Albert Astals Cid wrote:
>>> Review: Needs Fixing
>>>
>>> $ make xvfbtestPhoneStage
>>> ...
>>> QFATAL : qmltestrunner::PhoneStage::test_enterSpread(>position1) ASSERT:
>> "event->type() == QEvent::TouchBegin" in file /home/tsdgeos_work/phablet/unity
>> 8/touchOwnership/plugins/Ubuntu/Gestures/DirectionalDragArea.cpp, line 475
>>> FAIL! : qmltestrunner::PhoneStage::test_enterSpread(>position1) Received a
>> fatal error.
>> I don't get this error. Are you sure you have the latest version of this
>> branch?
> Error went away when i dist-upgraded and this came in via the ppa so i guess there is a need to prepend the local libUbuntuGestures to the library path so the one compiled in the code is the one that takes precedence and not this one.
Code in libUbuntuGestures didn't change in a while.

Revision history for this message
Albert Astals Cid (aacid) wrote :

> On 08/10/14 11:01, Albert Astals Cid wrote:
> >> On 08/10/14 10:40, Albert Astals Cid wrote:
> >>> Review: Needs Fixing
> >>>
> >>> $ make xvfbtestPhoneStage
> >>> ...
> >>> QFATAL : qmltestrunner::PhoneStage::test_enterSpread(>position1) ASSERT:
> >> "event->type() == QEvent::TouchBegin" in file
> /home/tsdgeos_work/phablet/unity
> >> 8/touchOwnership/plugins/Ubuntu/Gestures/DirectionalDragArea.cpp, line 475
> >>> FAIL! : qmltestrunner::PhoneStage::test_enterSpread(>position1) Received
> a
> >> fatal error.
> >> I don't get this error. Are you sure you have the latest version of this
> >> branch?
> > Error went away when i dist-upgraded and this came in via the ppa so i guess
> there is a need to prepend the local libUbuntuGestures to the library path so
> the one compiled in the code is the one that takes precedence and not this
> one.
> Code in libUbuntuGestures didn't change in a while.

Ok, the plugin then r1334?

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)
lp:~dandrader/unity8/touchOwnership updated
1339. By Daniel d'Andrada

left-edge swipe should always work, even when the launcher is being show

1340. By Daniel d'Andrada

Work around qt bug to make tests pass

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

> On 08/10/14 10:42, Albert Astals Cid wrote:
> > Review: Needs Fixing
> >
> > $ make xvfbtestShell
> > ....
> > FAIL! : qmltestrunner::Shell::test_leftEdgeDrag(with launcher) property
> focusedApplicationId
> > Actual (): dialer-app
> > Expected (): unity8-dash
> > Loc: [/home/tsdgeos_work/phablet/unity8/touchOwnership/tests/qmltests/tst
> _Shell.qml(440)]
> >
> That's failing in lp:unity8 as well. I'm not fixing it in this branch.

I messed up. Indeed passes in lp:unity8.
Fixed it, finally.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~dandrader/unity8/touchOwnership updated
1341. By Daniel d'Andrada

DDA - ungrab current mouse grabber instead of going all the way grabbing for himself

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
Albert Astals Cid (aacid) 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.
qmluitests: ShellWithPin::test_factoryReset is broken in trunk
autopilot: could not reproduce locally

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

review: Approve
lp:~dandrader/unity8/touchOwnership updated
1342. By Daniel d'Andrada

Bump qtmir version dependency

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 2014-10-06 15:56:08 +0000
3+++ CMakeLists.txt 2014-10-10 15:05:15 +0000
4@@ -58,6 +58,7 @@
5 include(GNUInstallDirs)
6
7 set(SHELL_APP_DIR ${CMAKE_INSTALL_DATADIR}/unity8)
8+set(SHELL_PRIVATE_LIBDIR ${CMAKE_INSTALL_LIBDIR}/unity8)
9
10 execute_process(COMMAND ${PKG_CONFIG_EXECUTABLE} --variable=plugindir_suffix unity-shell-api OUTPUT_VARIABLE SHELL_INSTALL_QML OUTPUT_STRIP_TRAILING_WHITESPACE)
11 if(SHELL_INSTALL_QML STREQUAL "")
12@@ -109,6 +110,7 @@
13
14 # add subdirectories to build
15 add_subdirectory(include)
16+add_subdirectory(libs)
17 add_subdirectory(src)
18 add_subdirectory(tools)
19 add_subdirectory(qml)
20
21=== modified file 'debian/control'
22--- debian/control 2014-10-06 08:00:45 +0000
23+++ debian/control 2014-10-10 15:05:15 +0000
24@@ -86,7 +86,7 @@
25 qmenumodel-qml (>= 0.2.8),
26 qml-module-qtquick-xmllistmodel,
27 qtdeclarative5-gsettings1.0,
28- qtdeclarative5-qtmir-plugin (>= 0.4.3),
29+ qtdeclarative5-qtmir-plugin (>= 0.4.4),
30 qtdeclarative5-ubuntu-telephony0.1,
31 unity-launcher-impl-4,
32 unity8-common (= ${source:Version}),
33
34=== modified file 'debian/unity8-private.install'
35--- debian/unity8-private.install 2014-09-05 12:25:51 +0000
36+++ debian/unity8-private.install 2014-10-10 15:05:15 +0000
37@@ -9,5 +9,6 @@
38 usr/lib/*/unity8/qml/Ubuntu
39 usr/lib/*/unity8/qml/Unity
40 usr/lib/*/unity8/qml/Utils
41+usr/lib/*/unity8/libUbuntuGestures*
42 usr/share/accountsservice/interfaces
43 usr/share/dbus-1/interfaces
44
45=== added directory 'libs'
46=== added file 'libs/CMakeLists.txt'
47--- libs/CMakeLists.txt 1970-01-01 00:00:00 +0000
48+++ libs/CMakeLists.txt 2014-10-10 15:05:15 +0000
49@@ -0,0 +1,1 @@
50+add_subdirectory(UbuntuGestures)
51
52=== added directory 'libs/UbuntuGestures'
53=== added file 'libs/UbuntuGestures/CMakeLists.txt'
54--- libs/UbuntuGestures/CMakeLists.txt 1970-01-01 00:00:00 +0000
55+++ libs/UbuntuGestures/CMakeLists.txt 2014-10-10 15:05:15 +0000
56@@ -0,0 +1,40 @@
57+# in order to include Qt's private headers
58+remove_definitions(-DQT_NO_KEYWORDS)
59+
60+set(UbuntuGestures_SOURCES
61+ CandidateInactivityTimer.cpp
62+ DebugHelpers.cpp
63+ Timer.cpp
64+ TouchOwnershipEvent.cpp
65+ TouchRegistry.cpp
66+ UnownedTouchEvent.cpp
67+)
68+
69+add_definitions(-DUBUNTUGESTURES_LIBRARY)
70+
71+add_library(UbuntuGestures SHARED ${UbuntuGestures_SOURCES})
72+
73+qt5_use_modules(UbuntuGestures Core Quick)
74+
75+# So that Foo.cpp can #include "Foo.moc"
76+include_directories(${CMAKE_CURRENT_BINARY_DIR})
77+
78+install(TARGETS UbuntuGestures
79+ DESTINATION ${SHELL_PRIVATE_LIBDIR})
80+
81+
82+# There's no cmake var for v8 include path :-/ so create one
83+LIST(GET Qt5Core_INCLUDE_DIRS 0 QtCoreDir0)
84+if(${Qt5Core_VERSION_STRING} VERSION_LESS "5.1.0")
85+ SET(Qt5V8_PRIVATE_INCLUDE_DIR ${QtCoreDir0}/../QtV8/${Qt5Core_VERSION_STRING}/QtV8)
86+else()
87+ SET(Qt5V8_PRIVATE_INCLUDE_DIR ${QtCoreDir0}/QtV8/${Qt5Core_VERSION_STRING}/QtV8)
88+endif()
89+
90+# DANGER! DANGER! Using Qt's private API!
91+include_directories(
92+ ${Qt5Qml_PRIVATE_INCLUDE_DIRS}
93+ ${Qt5Quick_INCLUDE_DIRS}
94+ ${Qt5Quick_PRIVATE_INCLUDE_DIRS}
95+ ${Qt5V8_PRIVATE_INCLUDE_DIR}
96+)
97
98=== added file 'libs/UbuntuGestures/CandidateInactivityTimer.cpp'
99--- libs/UbuntuGestures/CandidateInactivityTimer.cpp 1970-01-01 00:00:00 +0000
100+++ libs/UbuntuGestures/CandidateInactivityTimer.cpp 2014-10-10 15:05:15 +0000
101@@ -0,0 +1,41 @@
102+/*
103+ * Copyright (C) 2014 Canonical, Ltd.
104+ *
105+ * This program is free software; you can redistribute it and/or modify
106+ * it under the terms of the GNU General Public License as published by
107+ * the Free Software Foundation; version 3.
108+ *
109+ * This program is distributed in the hope that it will be useful,
110+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
111+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
112+ * GNU General Public License for more details.
113+ *
114+ * You should have received a copy of the GNU General Public License
115+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
116+ */
117+
118+#include "CandidateInactivityTimer.h"
119+
120+namespace UbuntuGestures {
121+
122+CandidateInactivityTimer::CandidateInactivityTimer(int touchId, QQuickItem *candidate,
123+ AbstractTimerFactory &timerFactory, QObject *parent)
124+ : QObject(parent)
125+ , m_touchId(touchId)
126+ , m_candidate(candidate)
127+{
128+ m_timer = timerFactory.createTimer(this);
129+ connect(m_timer, &AbstractTimer::timeout,
130+ this, &CandidateInactivityTimer::onTimeout);
131+ m_timer->setInterval(durationMs);
132+ m_timer->setSingleShot(true);
133+ m_timer->start();
134+}
135+
136+void CandidateInactivityTimer::onTimeout()
137+{
138+ qWarning("[TouchRegistry] Candidate for touch %d defaulted!", m_touchId);
139+ Q_EMIT candidateDefaulted(m_touchId, m_candidate);
140+}
141+
142+} // namespace UbuntuGestures
143
144=== added file 'libs/UbuntuGestures/CandidateInactivityTimer.h'
145--- libs/UbuntuGestures/CandidateInactivityTimer.h 1970-01-01 00:00:00 +0000
146+++ libs/UbuntuGestures/CandidateInactivityTimer.h 2014-10-10 15:05:15 +0000
147@@ -0,0 +1,49 @@
148+/*
149+ * Copyright (C) 2014 Canonical, Ltd.
150+ *
151+ * This program is free software; you can redistribute it and/or modify
152+ * it under the terms of the GNU General Public License as published by
153+ * the Free Software Foundation; version 3.
154+ *
155+ * This program is distributed in the hope that it will be useful,
156+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
157+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
158+ * GNU General Public License for more details.
159+ *
160+ * You should have received a copy of the GNU General Public License
161+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
162+ */
163+
164+#ifndef UBUNTUGESTURES_CANDIDATE_INACTIVITY_TIMER_H
165+#define UBUNTUGESTURES_CANDIDATE_INACTIVITY_TIMER_H
166+
167+#include <QObject>
168+
169+class QQuickItem;
170+
171+#include "Timer.h"
172+
173+namespace UbuntuGestures {
174+
175+class UBUNTUGESTURES_EXPORT CandidateInactivityTimer : public QObject {
176+ Q_OBJECT
177+public:
178+ CandidateInactivityTimer(int touchId, QQuickItem *candidate,
179+ AbstractTimerFactory &timerFactory,
180+ QObject *parent = nullptr);
181+
182+ const int durationMs = 350;
183+
184+Q_SIGNALS:
185+ void candidateDefaulted(int touchId, QQuickItem *candidate);
186+private Q_SLOTS:
187+ void onTimeout();
188+private:
189+ AbstractTimer *m_timer;
190+ int m_touchId;
191+ QQuickItem *m_candidate;
192+};
193+
194+} // namespace UbuntuGestures
195+
196+#endif // UBUNTUGESTURES_CANDIDATE_INACTIVITY_TIMER_H
197
198=== added file 'libs/UbuntuGestures/DebugHelpers.cpp'
199--- libs/UbuntuGestures/DebugHelpers.cpp 1970-01-01 00:00:00 +0000
200+++ libs/UbuntuGestures/DebugHelpers.cpp 2014-10-10 15:05:15 +0000
201@@ -0,0 +1,68 @@
202+/*
203+ * Copyright (C) 2014 Canonical, Ltd.
204+ *
205+ * This program is free software; you can redistribute it and/or modify
206+ * it under the terms of the GNU General Public License as published by
207+ * the Free Software Foundation; version 3.
208+ *
209+ * This program is distributed in the hope that it will be useful,
210+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
211+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
212+ * GNU General Public License for more details.
213+ *
214+ * You should have received a copy of the GNU General Public License
215+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
216+ */
217+
218+#include "DebugHelpers.h"
219+#include <QTouchEvent>
220+
221+QString touchPointStateToString(Qt::TouchPointState state)
222+{
223+ switch (state) {
224+ case Qt::TouchPointPressed:
225+ return QString("pressed");
226+ case Qt::TouchPointMoved:
227+ return QString("moved");
228+ case Qt::TouchPointStationary:
229+ return QString("stationary");
230+ case Qt::TouchPointReleased:
231+ return QString("released");
232+ default:
233+ return QString("INVALID_STATE");
234+ }
235+}
236+
237+QString touchEventToString(const QTouchEvent *ev)
238+{
239+ QString message;
240+
241+ switch (ev->type()) {
242+ case QEvent::TouchBegin:
243+ message.append("TouchBegin ");
244+ break;
245+ case QEvent::TouchUpdate:
246+ message.append("TouchUpdate ");
247+ break;
248+ case QEvent::TouchEnd:
249+ message.append("TouchEnd ");
250+ break;
251+ case QEvent::TouchCancel:
252+ message.append("TouchCancel ");
253+ break;
254+ default:
255+ message.append("INVALID_TOUCH_EVENT_TYPE ");
256+ }
257+
258+ foreach(const QTouchEvent::TouchPoint& touchPoint, ev->touchPoints()) {
259+ message.append(
260+ QString("(id:%1, state:%2, scenePos:(%3,%4)) ")
261+ .arg(touchPoint.id())
262+ .arg(touchPointStateToString(touchPoint.state()))
263+ .arg(touchPoint.scenePos().x())
264+ .arg(touchPoint.scenePos().y())
265+ );
266+ }
267+
268+ return message;
269+}
270
271=== added file 'libs/UbuntuGestures/DebugHelpers.h'
272--- libs/UbuntuGestures/DebugHelpers.h 1970-01-01 00:00:00 +0000
273+++ libs/UbuntuGestures/DebugHelpers.h 2014-10-10 15:05:15 +0000
274@@ -0,0 +1,29 @@
275+/*
276+ * Copyright (C) 2014 Canonical, Ltd.
277+ *
278+ * This program is free software; you can redistribute it and/or modify
279+ * it under the terms of the GNU General Public License as published by
280+ * the Free Software Foundation; version 3.
281+ *
282+ * This program is distributed in the hope that it will be useful,
283+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
284+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
285+ * GNU General Public License for more details.
286+ *
287+ * You should have received a copy of the GNU General Public License
288+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
289+ */
290+
291+#ifndef UBUNTUGESTURES_DEBUG_HELPER_H
292+#define UBUNTUGESTURES_DEBUG_HELPER_H
293+
294+#include <QString>
295+
296+#include "UbuntuGesturesGlobal.h"
297+
298+class QTouchEvent;
299+
300+UBUNTUGESTURES_EXPORT QString touchPointStateToString(Qt::TouchPointState state);
301+UBUNTUGESTURES_EXPORT QString touchEventToString(const QTouchEvent *ev);
302+
303+#endif // UBUNTUGESTURES_DEBUG_HELPER_H
304
305=== added file 'libs/UbuntuGestures/Pool.h'
306--- libs/UbuntuGestures/Pool.h 1970-01-01 00:00:00 +0000
307+++ libs/UbuntuGestures/Pool.h 2014-10-10 15:05:15 +0000
308@@ -0,0 +1,134 @@
309+/*
310+ * Copyright (C) 2014 Canonical, Ltd.
311+ *
312+ * This program is free software; you can redistribute it and/or modify
313+ * it under the terms of the GNU General Public License as published by
314+ * the Free Software Foundation; version 3.
315+ *
316+ * This program is distributed in the hope that it will be useful,
317+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
318+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
319+ * GNU General Public License for more details.
320+ *
321+ * You should have received a copy of the GNU General Public License
322+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
323+ */
324+
325+#ifndef UBUNTUGESTURES_POOL_H
326+#define UBUNTUGESTURES_POOL_H
327+
328+#include <QVector>
329+
330+#include "UbuntuGesturesGlobal.h"
331+
332+/*
333+ An object pool.
334+ Avoids unnecessary creations/initializations and deletions/destructions of items. Useful
335+ in a scenario where items are created and destroyed very frequently but the total number
336+ of items at any given time remains small. They're stored in a unordered fashion.
337+
338+ To be used in Pool, ItemType needs to have the following methods:
339+
340+ - ItemType();
341+
342+ A constructor that takes no parameters. An object contructed with it must return false if
343+ isValid() is called.
344+
345+ - bool isValid() const;
346+
347+ Returns wheter the object holds a valid , "filled" state or is empty.
348+ Used by Pool to check if the slot occupied by this object is actually available.
349+
350+ - void reset();
351+
352+ Resets the object to its initial, empty, state. After calling this method, isValid() must
353+ return false.
354+ */
355+template <class ItemType> class Pool
356+{
357+public:
358+ Pool() : m_lastUsedIndex(-1) {
359+ }
360+
361+ class Iterator {
362+ public:
363+ Iterator() : index(-1), item(nullptr) {}
364+ Iterator(int index, ItemType *item)
365+ : index(index), item(item) {}
366+
367+ ItemType *operator->() const { return item; }
368+ ItemType &operator*() const { return *item; }
369+ ItemType &value() const { return *item; }
370+
371+ Iterator &operator= (const Iterator& other) {
372+ index = other.index;
373+ item = other.item;
374+
375+ // by convention, always return *this
376+ return *this;
377+ }
378+
379+ operator bool() const { return item != nullptr; }
380+
381+ int index;
382+ ItemType *item;
383+ };
384+
385+ Iterator &end() const { return Iterator(); }
386+
387+ ItemType &getEmptySlot() {
388+ Q_ASSERT(m_lastUsedIndex < m_slots.size());
389+
390+ // Look for an in-between vacancy first
391+ for (int i = 0; i < m_lastUsedIndex; ++i) {
392+ ItemType &item = m_slots[i];
393+ if (!item.isValid()) {
394+ return item;
395+ }
396+ }
397+
398+ ++m_lastUsedIndex;
399+ if (m_lastUsedIndex >= m_slots.size()) {
400+ m_slots.resize(m_lastUsedIndex + 1);
401+ }
402+
403+ return m_slots[m_lastUsedIndex];
404+ }
405+
406+ void freeSlot(Iterator &iterator) {
407+ m_slots[iterator.index].reset();
408+ if (iterator.index == m_lastUsedIndex) {
409+ do {
410+ --m_lastUsedIndex;
411+ } while (m_lastUsedIndex >= 0 && !m_slots.at(m_lastUsedIndex).isValid());
412+ }
413+ }
414+
415+ // Iterates through all valid items (i.e. the occupied slots)
416+ // calling the given function, with the option of ending the loop early.
417+ //
418+ // bool Func(Iterator& item)
419+ //
420+ // Returning true means it wants to continue the "for" loop, false
421+ // terminates the loop.
422+ template<typename Func> void forEach(Func func) {
423+ Iterator it;
424+ for (it.index = 0; it.index <= m_lastUsedIndex; ++it.index) {
425+ it.item = &m_slots[it.index];
426+ if (!it.item->isValid())
427+ continue;
428+
429+ if (!func(it))
430+ break;
431+ }
432+ }
433+
434+ bool isEmpty() const { return m_lastUsedIndex == -1; }
435+
436+
437+private:
438+ QVector<ItemType> m_slots;
439+ int m_lastUsedIndex;
440+};
441+
442+#endif // UBUNTUGESTURES_POOL_H
443
444=== added file 'libs/UbuntuGestures/Timer.cpp'
445--- libs/UbuntuGestures/Timer.cpp 1970-01-01 00:00:00 +0000
446+++ libs/UbuntuGestures/Timer.cpp 2014-10-10 15:05:15 +0000
447@@ -0,0 +1,109 @@
448+/*
449+ * Copyright (C) 2014 Canonical, Ltd.
450+ *
451+ * This program is free software; you can redistribute it and/or modify
452+ * it under the terms of the GNU General Public License as published by
453+ * the Free Software Foundation; version 3.
454+ *
455+ * This program is distributed in the hope that it will be useful,
456+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
457+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
458+ * GNU General Public License for more details.
459+ *
460+ * You should have received a copy of the GNU General Public License
461+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
462+ */
463+
464+#include "Timer.h"
465+
466+namespace UbuntuGestures {
467+
468+Timer::Timer(QObject *parent) : AbstractTimer(parent)
469+{
470+ m_timer.setSingleShot(false);
471+ connect(&m_timer, &QTimer::timeout, this, &AbstractTimer::timeout);
472+}
473+
474+int Timer::interval() const
475+{
476+ return m_timer.interval();
477+}
478+
479+void Timer::setInterval(int msecs)
480+{
481+ m_timer.setInterval(msecs);
482+}
483+
484+void Timer::start()
485+{
486+ m_timer.start();
487+ AbstractTimer::start();
488+}
489+
490+void Timer::stop()
491+{
492+ m_timer.stop();
493+ AbstractTimer::stop();
494+}
495+
496+bool Timer::isSingleShot() const
497+{
498+ return m_timer.isSingleShot();
499+}
500+
501+void Timer::setSingleShot(bool value)
502+{
503+ m_timer.setSingleShot(value);
504+}
505+
506+/////////////////////////////////// FakeTimer //////////////////////////////////
507+
508+FakeTimer::FakeTimer(QObject *parent)
509+ : UbuntuGestures::AbstractTimer(parent)
510+ , m_interval(0)
511+ , m_singleShot(false)
512+{
513+}
514+
515+int FakeTimer::interval() const
516+{
517+ return m_interval;
518+}
519+
520+void FakeTimer::setInterval(int msecs)
521+{
522+ m_interval = msecs;
523+}
524+
525+bool FakeTimer::isSingleShot() const
526+{
527+ return m_singleShot;
528+}
529+
530+void FakeTimer::setSingleShot(bool value)
531+{
532+ m_singleShot = value;
533+}
534+
535+/////////////////////////////////// FakeTimerFactory //////////////////////////////////
536+
537+AbstractTimer *FakeTimerFactory::createTimer(QObject *parent)
538+{
539+ FakeTimer *fakeTimer = new FakeTimer(parent);
540+
541+ timers.append(fakeTimer);
542+
543+ return fakeTimer;
544+}
545+
546+void FakeTimerFactory::makeRunningTimersTimeout()
547+{
548+ for (int i = 0; i < timers.count(); ++i) {
549+ FakeTimer *timer = timers[i].data();
550+ if (timer && timer->isRunning()) {
551+ timer->emitTimeout();
552+ }
553+ }
554+}
555+
556+} // namespace UbuntuGestures
557
558=== added file 'libs/UbuntuGestures/Timer.h'
559--- libs/UbuntuGestures/Timer.h 1970-01-01 00:00:00 +0000
560+++ libs/UbuntuGestures/Timer.h 2014-10-10 15:05:15 +0000
561@@ -0,0 +1,105 @@
562+/*
563+ * Copyright (C) 2014 Canonical, Ltd.
564+ *
565+ * This program is free software; you can redistribute it and/or modify
566+ * it under the terms of the GNU General Public License as published by
567+ * the Free Software Foundation; version 3.
568+ *
569+ * This program is distributed in the hope that it will be useful,
570+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
571+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
572+ * GNU General Public License for more details.
573+ *
574+ * You should have received a copy of the GNU General Public License
575+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
576+ */
577+
578+#ifndef UBUNTUGESTURES_TIMER_H
579+#define UBUNTUGESTURES_TIMER_H
580+
581+#include "UbuntuGesturesGlobal.h"
582+
583+#include <QObject>
584+#include <QPointer>
585+#include <QTimer>
586+
587+namespace UbuntuGestures {
588+
589+/* Defines an interface for a Timer. Useful for tests. */
590+class UBUNTUGESTURES_EXPORT AbstractTimer : public QObject
591+{
592+ Q_OBJECT
593+public:
594+ AbstractTimer(QObject *parent) : QObject(parent), m_isRunning(false) {}
595+ virtual int interval() const = 0;
596+ virtual void setInterval(int msecs) = 0;
597+ virtual void start() { m_isRunning = true; }
598+ virtual void stop() { m_isRunning = false; }
599+ bool isRunning() const { return m_isRunning; }
600+ virtual bool isSingleShot() const = 0;
601+ virtual void setSingleShot(bool value) = 0;
602+Q_SIGNALS:
603+ void timeout();
604+private:
605+ bool m_isRunning;
606+};
607+
608+/* Essentially a QTimer wrapper */
609+class UBUNTUGESTURES_EXPORT Timer : public AbstractTimer
610+{
611+ Q_OBJECT
612+public:
613+ Timer(QObject *parent = nullptr);
614+
615+ int interval() const override;
616+ void setInterval(int msecs) override;
617+ void start() override;
618+ void stop() override;
619+ bool isSingleShot() const override;
620+ void setSingleShot(bool value) override;
621+private:
622+ QTimer m_timer;
623+};
624+
625+/* For tests */
626+class UBUNTUGESTURES_EXPORT FakeTimer : public AbstractTimer
627+{
628+ Q_OBJECT
629+public:
630+ FakeTimer(QObject *parent = nullptr);
631+
632+ virtual void emitTimeout() { Q_EMIT timeout(); }
633+
634+ int interval() const override;
635+ void setInterval(int msecs) override;
636+ bool isSingleShot() const override;
637+ void setSingleShot(bool value) override;
638+private:
639+ int m_interval;
640+ bool m_singleShot;
641+};
642+
643+class UBUNTUGESTURES_EXPORT AbstractTimerFactory
644+{
645+public:
646+ virtual ~AbstractTimerFactory() {}
647+ virtual AbstractTimer *createTimer(QObject *parent = nullptr) = 0;
648+};
649+
650+class UBUNTUGESTURES_EXPORT TimerFactory : public AbstractTimerFactory
651+{
652+public:
653+ AbstractTimer *createTimer(QObject *parent = nullptr) override { return new Timer(parent); }
654+};
655+
656+class UBUNTUGESTURES_EXPORT FakeTimerFactory : public AbstractTimerFactory
657+{
658+public:
659+ AbstractTimer *createTimer(QObject *parent = nullptr) override;
660+ void makeRunningTimersTimeout();
661+ QList<QPointer<FakeTimer>> timers;
662+};
663+
664+} // namespace UbuntuGestures
665+
666+#endif // UBUNTUGESTURES_TIMER_H
667
668=== added file 'libs/UbuntuGestures/TouchOwnershipEvent.cpp'
669--- libs/UbuntuGestures/TouchOwnershipEvent.cpp 1970-01-01 00:00:00 +0000
670+++ libs/UbuntuGestures/TouchOwnershipEvent.cpp 2014-10-10 15:05:15 +0000
671@@ -0,0 +1,35 @@
672+/*
673+ * Copyright (C) 2014 Canonical, Ltd.
674+ *
675+ * This program is free software; you can redistribute it and/or modify
676+ * it under the terms of the GNU General Public License as published by
677+ * the Free Software Foundation; version 3.
678+ *
679+ * This program is distributed in the hope that it will be useful,
680+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
681+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
682+ * GNU General Public License for more details.
683+ *
684+ * You should have received a copy of the GNU General Public License
685+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
686+ */
687+
688+#include "TouchOwnershipEvent.h"
689+
690+QEvent::Type TouchOwnershipEvent::m_touchOwnershipType = (QEvent::Type)-1;
691+
692+TouchOwnershipEvent::TouchOwnershipEvent(int touchId, bool gained)
693+ : QEvent(touchOwnershipEventType())
694+ , m_touchId(touchId)
695+ , m_gained(gained)
696+{
697+}
698+
699+QEvent::Type TouchOwnershipEvent::touchOwnershipEventType()
700+{
701+ if (m_touchOwnershipType == (QEvent::Type)-1) {
702+ m_touchOwnershipType = (QEvent::Type)registerEventType();
703+ }
704+
705+ return m_touchOwnershipType;
706+}
707
708=== added file 'libs/UbuntuGestures/TouchOwnershipEvent.h'
709--- libs/UbuntuGestures/TouchOwnershipEvent.h 1970-01-01 00:00:00 +0000
710+++ libs/UbuntuGestures/TouchOwnershipEvent.h 2014-10-10 15:05:15 +0000
711@@ -0,0 +1,50 @@
712+/*
713+ * Copyright (C) 2014 Canonical, Ltd.
714+ *
715+ * This program is free software; you can redistribute it and/or modify
716+ * it under the terms of the GNU General Public License as published by
717+ * the Free Software Foundation; version 3.
718+ *
719+ * This program is distributed in the hope that it will be useful,
720+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
721+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
722+ * GNU General Public License for more details.
723+ *
724+ * You should have received a copy of the GNU General Public License
725+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
726+ */
727+
728+#ifndef UBUNTU_TOUCHOWNERSHIPEVENT_H
729+#define UBUNTU_TOUCHOWNERSHIPEVENT_H
730+
731+#include <QEvent>
732+#include "UbuntuGesturesGlobal.h"
733+
734+/*
735+ When an item get an ownership event for a touch it can grab/steal that touch
736+ with a clean conscience.
737+ */
738+class UBUNTUGESTURES_EXPORT TouchOwnershipEvent : public QEvent
739+{
740+public:
741+ TouchOwnershipEvent(int touchId, bool gained);
742+
743+ static Type touchOwnershipEventType();
744+
745+ /*
746+ Whether ownership was gained (true) or lost (false)
747+ */
748+ bool gained() const { return m_gained; }
749+
750+ /*
751+ Id of the touch whose ownership was granted.
752+ */
753+ int touchId() const { return m_touchId; }
754+
755+private:
756+ static Type m_touchOwnershipType;
757+ int m_touchId;
758+ bool m_gained;
759+};
760+
761+#endif // UBUNTU_TOUCHOWNERSHIPEVENT_H
762
763=== added file 'libs/UbuntuGestures/TouchRegistry.cpp'
764--- libs/UbuntuGestures/TouchRegistry.cpp 1970-01-01 00:00:00 +0000
765+++ libs/UbuntuGestures/TouchRegistry.cpp 2014-10-10 15:05:15 +0000
766@@ -0,0 +1,500 @@
767+/*
768+ * Copyright (C) 2014 Canonical, Ltd.
769+ *
770+ * This program is free software; you can redistribute it and/or modify
771+ * it under the terms of the GNU General Public License as published by
772+ * the Free Software Foundation; version 3.
773+ *
774+ * This program is distributed in the hope that it will be useful,
775+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
776+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
777+ * GNU General Public License for more details.
778+ *
779+ * You should have received a copy of the GNU General Public License
780+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
781+ */
782+
783+#include "TouchRegistry.h"
784+
785+#include <QCoreApplication>
786+#include <QDebug>
787+
788+#pragma GCC diagnostic push
789+#pragma GCC diagnostic ignored "-pedantic"
790+#include <private/qquickitem_p.h>
791+#pragma GCC diagnostic pop
792+
793+#include "CandidateInactivityTimer.h"
794+#include "Timer.h"
795+#include "TouchOwnershipEvent.h"
796+#include "UnownedTouchEvent.h"
797+
798+#define TOUCHREGISTRY_DEBUG 0
799+
800+#if TOUCHREGISTRY_DEBUG
801+ #include "DebugHelpers.h"
802+ #define UG_DEBUG qDebug() << "[TouchRegistry]"
803+#endif // TOUCHREGISTRY_DEBUG
804+
805+using namespace UbuntuGestures;
806+
807+TouchRegistry *TouchRegistry::m_instance = nullptr;
808+
809+TouchRegistry::TouchRegistry(QObject *parent)
810+ : TouchRegistry(parent, new TimerFactory)
811+{
812+}
813+
814+TouchRegistry::TouchRegistry(QObject *parent, AbstractTimerFactory *timerFactory)
815+ : QObject(parent)
816+ , m_inDispatchLoop(false)
817+ , m_timerFactory(timerFactory)
818+{
819+ if (m_instance == nullptr) {
820+ m_instance = this;
821+ } else {
822+ qFatal("Cannot have more than one instance of TouchRegistry. It must be a singleton.");
823+ }
824+}
825+
826+TouchRegistry::~TouchRegistry()
827+{
828+ Q_ASSERT(m_instance != nullptr);
829+ m_instance = nullptr;
830+ delete m_timerFactory;
831+}
832+
833+void TouchRegistry::update(const QTouchEvent *event)
834+{
835+ #if TOUCHREGISTRY_DEBUG
836+ UG_DEBUG << "got" << qPrintable(touchEventToString(event));
837+ #endif
838+
839+ const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
840+ for (int i = 0; i < touchPoints.count(); ++i) {
841+ const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
842+ if (touchPoint.state() == Qt::TouchPointPressed) {
843+ TouchInfo &touchInfo = m_touchInfoPool.getEmptySlot();
844+ touchInfo.init(touchPoint.id());
845+ } else if (touchPoint.state() == Qt::TouchPointReleased) {
846+ Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(touchPoint.id());
847+
848+ touchInfo->physicallyEnded = true;
849+ }
850+ }
851+
852+ deliverTouchUpdatesToUndecidedCandidatesAndWatchers(event);
853+
854+ freeEndedTouchInfos();
855+}
856+
857+void TouchRegistry::deliverTouchUpdatesToUndecidedCandidatesAndWatchers(const QTouchEvent *event)
858+{
859+ // TODO: Look into how we could optimize this whole thing.
860+ // Although it's not really a problem as we should have at most two candidates
861+ // for each point and there should not be many active points at any given moment.
862+ // But having three nested for-loops does scare.
863+
864+ // TODO: Don't send it to the object that is already receiving the regular event
865+ // because QQuickWindow is sending it to him (i.e., he's the touch owner from Qt's point of view)
866+ // Problem is, we cannnot easily get this information.
867+
868+ const QList<QTouchEvent::TouchPoint> &updatedTouchPoints = event->touchPoints();
869+
870+ // Maps an item to the touches in this event he should be informed about.
871+ // E.g.: a QTouchEvent might have three touches but a given item might be interested in only
872+ // one of them. So he will get a UnownedTouchEvent from this QTouchEvent containing only that
873+ // touch point.
874+ QMap<QQuickItem*, QList<int>> touchIdsForItems;
875+
876+ // Build touchIdsForItems
877+ m_touchInfoPool.forEach([&](Pool<TouchInfo>::Iterator &touchInfo) {
878+ if (touchInfo->isOwned() && touchInfo->watchers.isEmpty())
879+ return true;
880+
881+ for (int j = 0; j < updatedTouchPoints.count(); ++j) {
882+ if (updatedTouchPoints[j].id() == touchInfo->id) {
883+ if (!touchInfo->isOwned()) {
884+ for (int i = 0; i < touchInfo->candidates.count(); ++i) {
885+ CandidateInfo &candidate = touchInfo->candidates[i];
886+ Q_ASSERT(!candidate.item.isNull());
887+ touchIdsForItems[candidate.item.data()].append(touchInfo->id);
888+ }
889+ }
890+
891+ const QList<QPointer<QQuickItem>> &watchers = touchInfo->watchers;
892+ for (int i = 0; i < watchers.count(); ++i) {
893+ if (!watchers[i].isNull()) {
894+ touchIdsForItems[watchers[i].data()].append(touchInfo->id);
895+ }
896+ }
897+
898+ return true;
899+ }
900+ }
901+
902+ return true;
903+ });
904+
905+ // TODO: Consider what happens if an item calls any of TouchRegistry's public methods
906+ // from the event handler callback.
907+ m_inDispatchLoop = true;
908+ auto it = touchIdsForItems.constBegin();
909+ while (it != touchIdsForItems.constEnd()) {
910+ QQuickItem *item = it.key();
911+ const QList<int> &touchIds = it.value();
912+ dispatchPointsToItem(event, touchIds, item);
913+ ++it;
914+ };
915+ m_inDispatchLoop = false;
916+}
917+
918+void TouchRegistry::freeEndedTouchInfos()
919+{
920+ m_touchInfoPool.forEach([&](Pool<TouchInfo>::Iterator &touchInfo) {
921+ if (touchInfo->ended()) {
922+ m_touchInfoPool.freeSlot(touchInfo);
923+ }
924+ return true;
925+ });
926+}
927+
928+/*
929+ Extracts the touches with the given touchIds from event and send them in a
930+ UnownedTouchEvent to the given item
931+ */
932+void TouchRegistry::dispatchPointsToItem(const QTouchEvent *event, const QList<int> &touchIds,
933+ QQuickItem *item)
934+{
935+ Qt::TouchPointStates touchPointStates = 0;
936+ QList<QTouchEvent::TouchPoint> touchPoints;
937+
938+ const QList<QTouchEvent::TouchPoint> &allTouchPoints = event->touchPoints();
939+
940+ QTransform windowToCandidateTransform = QQuickItemPrivate::get(item)->windowToItemTransform();
941+ QMatrix4x4 windowToCandidateMatrix(windowToCandidateTransform);
942+
943+ for (int i = 0; i < allTouchPoints.count(); ++i) {
944+ const QTouchEvent::TouchPoint &originalTouchPoint = allTouchPoints[i];
945+ if (touchIds.contains(originalTouchPoint.id())) {
946+ QTouchEvent::TouchPoint touchPoint = originalTouchPoint;
947+
948+ translateTouchPointFromScreenToWindowCoords(touchPoint);
949+
950+ // Set the point's local coordinates to that of the item
951+ touchPoint.setRect(windowToCandidateTransform.mapRect(touchPoint.sceneRect()));
952+ touchPoint.setStartPos(windowToCandidateTransform.map(touchPoint.startScenePos()));
953+ touchPoint.setLastPos(windowToCandidateTransform.map(touchPoint.lastScenePos()));
954+ touchPoint.setVelocity(windowToCandidateMatrix.mapVector(touchPoint.velocity()).toVector2D());
955+
956+ touchPoints.append(touchPoint);
957+ touchPointStates |= touchPoint.state();
958+ }
959+ }
960+
961+ QTouchEvent *eventForItem = new QTouchEvent(event->type(),
962+ event->device(),
963+ event->modifiers(),
964+ touchPointStates,
965+ touchPoints);
966+ eventForItem->setWindow(event->window());
967+ eventForItem->setTimestamp(event->timestamp());
968+ eventForItem->setTarget(event->target());
969+
970+ UnownedTouchEvent unownedTouchEvent(eventForItem);
971+
972+ #if TOUCHREGISTRY_DEBUG
973+ UG_DEBUG << "Sending unowned" << qPrintable(touchEventToString(eventForItem))
974+ << "to" << item;
975+ #endif
976+
977+ QCoreApplication::sendEvent(item, &unownedTouchEvent);
978+}
979+
980+void TouchRegistry::translateTouchPointFromScreenToWindowCoords(QTouchEvent::TouchPoint &touchPoint)
981+{
982+ touchPoint.setScreenRect(touchPoint.sceneRect());
983+ touchPoint.setStartScreenPos(touchPoint.startScenePos());
984+ touchPoint.setLastScreenPos(touchPoint.lastScenePos());
985+
986+ touchPoint.setSceneRect(touchPoint.rect());
987+ touchPoint.setStartScenePos(touchPoint.startPos());
988+ touchPoint.setLastScenePos(touchPoint.lastPos());
989+}
990+
991+bool TouchRegistry::eventFilter(QObject *watched, QEvent *event)
992+{
993+ Q_UNUSED(watched);
994+
995+ switch (event->type()) {
996+ case QEvent::TouchBegin:
997+ case QEvent::TouchUpdate:
998+ case QEvent::TouchEnd:
999+ case QEvent::TouchCancel:
1000+ update(static_cast<QTouchEvent*>(event));
1001+ break;
1002+ default:
1003+ // do nothing
1004+ break;
1005+ }
1006+
1007+ // Do not filter out the event. i.e., let it be handled further as
1008+ // we're just monitoring events
1009+ return false;
1010+}
1011+
1012+void TouchRegistry::addCandidateOwnerForTouch(int id, QQuickItem *candidate)
1013+{
1014+ #if TOUCHREGISTRY_DEBUG
1015+ UG_DEBUG << "addCandidateOwnerForTouch id" << id << "candidate" << candidate;
1016+ #endif
1017+
1018+ Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(id);
1019+ if (!touchInfo) { qFatal("TouchRegistry: Failed to find TouchInfo"); }
1020+
1021+ if (touchInfo->isOwned()) {
1022+ qWarning("TouchRegistry: trying to add candidate owner for a touch that's already owned");
1023+ return;
1024+ }
1025+
1026+ // TODO: Check if candidate already exists
1027+
1028+ CandidateInfo candidateInfo;
1029+ candidateInfo.undecided = true;
1030+ candidateInfo.item = candidate;
1031+ candidateInfo.inactivityTimer = new CandidateInactivityTimer(id, candidate,
1032+ *m_timerFactory,
1033+ this);
1034+ connect(candidateInfo.inactivityTimer, &CandidateInactivityTimer::candidateDefaulted,
1035+ this, &TouchRegistry::rejectCandidateOwnerForTouch);
1036+
1037+ touchInfo->candidates.append(candidateInfo);
1038+}
1039+
1040+void TouchRegistry::addTouchWatcher(int touchId, QQuickItem *watcher)
1041+{
1042+ #if TOUCHREGISTRY_DEBUG
1043+ UG_DEBUG << "addTouchWatcher id" << touchId << "watcher" << watcher;
1044+ #endif
1045+
1046+ Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(touchId);
1047+ if (!touchInfo) { qFatal("TouchRegistry: Failed to find TouchInfo"); }
1048+
1049+ // TODO: Check if watcher already exists
1050+
1051+ touchInfo->watchers.append(watcher);
1052+}
1053+
1054+void TouchRegistry::removeCandidateOwnerForTouch(int id, QQuickItem *candidate)
1055+{
1056+ #if TOUCHREGISTRY_DEBUG
1057+ UG_DEBUG << "removeCandidateOwnerForTouch id" << id << "candidate" << candidate;
1058+ #endif
1059+
1060+ Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(id);
1061+ if (!touchInfo) { qFatal("TouchRegistry: Failed to find TouchInfo"); }
1062+
1063+ int indexRemoved = -1;
1064+
1065+ // TODO: check if the candidate is in fact the owner of the touch
1066+
1067+ for (int i = 0; i < touchInfo->candidates.count() && indexRemoved == -1; ++i) {
1068+ CandidateInfo &candidateInfo = touchInfo->candidates[i];
1069+ if (candidateInfo.item == candidate) {
1070+ Q_ASSERT(i > 0 || candidateInfo.undecided);
1071+ if (i == 0 && !candidateInfo.undecided) {
1072+ qCritical("TouchRegistry: touch owner is being removed.");
1073+ }
1074+ delete candidateInfo.inactivityTimer;
1075+ candidateInfo.inactivityTimer = nullptr;
1076+ touchInfo->candidates.removeAt(i);
1077+ indexRemoved = i;
1078+ }
1079+ }
1080+
1081+ if (indexRemoved == 0) {
1082+ // the top candidate has been removed. if the new top candidate
1083+ // wants the touch let him know he's now the owner.
1084+ if (touchInfo->isOwned()) {
1085+ touchInfo->notifyCandidatesOfOwnershipResolution();
1086+ }
1087+ }
1088+
1089+ if (!m_inDispatchLoop && touchInfo->ended()) {
1090+ m_touchInfoPool.freeSlot(touchInfo);
1091+ }
1092+}
1093+
1094+void TouchRegistry::requestTouchOwnership(int id, QQuickItem *candidate)
1095+{
1096+ #if TOUCHREGISTRY_DEBUG
1097+ UG_DEBUG << "requestTouchOwnership id " << id << "candidate" << candidate;
1098+ #endif
1099+
1100+ Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(id);
1101+ if (!touchInfo) { qFatal("TouchRegistry: Failed to find TouchInfo"); }
1102+
1103+ Q_ASSERT(!touchInfo->isOwned());
1104+
1105+ int candidateIndex = -1;
1106+ for (int i = 0; i < touchInfo->candidates.count(); ++i) {
1107+ CandidateInfo &candidateInfo = touchInfo->candidates[i];
1108+ if (candidateInfo.item == candidate) {
1109+ candidateInfo.undecided = false;
1110+ delete candidateInfo.inactivityTimer;
1111+ candidateInfo.inactivityTimer = nullptr;
1112+ candidateIndex = i;
1113+ break;
1114+ }
1115+ }
1116+
1117+ // add it as a candidate if not present yet
1118+ if (candidateIndex < 0) {
1119+ CandidateInfo candidateInfo;
1120+ candidateInfo.undecided = false;
1121+ candidateInfo.item = candidate;
1122+ candidateInfo.inactivityTimer = nullptr;
1123+ touchInfo->candidates.append(candidateInfo);
1124+ // it's the last one
1125+ candidateIndex = touchInfo->candidates.count() - 1;
1126+ }
1127+
1128+ // If it's the top candidate it means it's now the owner. Let
1129+ // it know about it.
1130+ if (candidateIndex == 0) {
1131+ touchInfo->notifyCandidatesOfOwnershipResolution();
1132+ }
1133+}
1134+
1135+Pool<TouchRegistry::TouchInfo>::Iterator TouchRegistry::findTouchInfo(int id)
1136+{
1137+ Pool<TouchInfo>::Iterator touchInfo;
1138+
1139+ m_touchInfoPool.forEach([&](Pool<TouchInfo>::Iterator &someTouchInfo) -> bool {
1140+ if (someTouchInfo->id == id) {
1141+ touchInfo = someTouchInfo;
1142+ return false;
1143+ } else {
1144+ return true;
1145+ }
1146+ });
1147+
1148+ return touchInfo;
1149+}
1150+
1151+
1152+void TouchRegistry::rejectCandidateOwnerForTouch(int id, QQuickItem *candidate)
1153+{
1154+ // NB: It's technically possible that candidate is a dangling pointer at this point.
1155+ // Although that would most likely be due to a bug in our code.
1156+ // In any case, only dereference it after it's confirmed that it indeed exists.
1157+
1158+ #if TOUCHREGISTRY_DEBUG
1159+ UG_DEBUG << "rejectCandidateOwnerForTouch id" << id << "candidate" << (void*)candidate;
1160+ #endif
1161+
1162+ Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(id);
1163+ if (!touchInfo) {
1164+ #if TOUCHREGISTRY_DEBUG
1165+ UG_DEBUG << "Failed to find TouchInfo for id" << id;
1166+ #endif
1167+ return;
1168+ }
1169+
1170+ int rejectedCandidateIndex = -1;
1171+
1172+ // Check if the given candidate is valid and still undecided
1173+ for (int i = 0; i < touchInfo->candidates.count() && rejectedCandidateIndex == -1; ++i) {
1174+ CandidateInfo &candidateInfo = touchInfo->candidates[i];
1175+ if (candidateInfo.item == candidate) {
1176+ Q_ASSERT(i > 0 || candidateInfo.undecided);
1177+ if (i == 0 && !candidateInfo.undecided) {
1178+ qCritical() << "TouchRegistry: Can't reject item (" << (void*)candidate
1179+ << ") as it already owns touch" << id;
1180+ return;
1181+ } else {
1182+ // we found the guy and it's all fine.
1183+ rejectedCandidateIndex = i;
1184+ }
1185+ }
1186+ }
1187+
1188+ // If we reached this point it's because the given candidate exists and is indeed undecided.
1189+
1190+ Q_ASSERT(rejectedCandidateIndex >= 0 && rejectedCandidateIndex < touchInfo->candidates.size());
1191+
1192+ {
1193+ TouchOwnershipEvent lostOwnershipEvent(id, false /*gained*/);
1194+ QCoreApplication::sendEvent(candidate, &lostOwnershipEvent);
1195+ }
1196+
1197+ touchInfo->candidates.removeAt(rejectedCandidateIndex);
1198+
1199+ if (rejectedCandidateIndex == 0) {
1200+ // the top candidate has been removed. if the new top candidate
1201+ // wants the touch let him know he's now the owner.
1202+ if (touchInfo->isOwned()) {
1203+ touchInfo->notifyCandidatesOfOwnershipResolution();
1204+ }
1205+ }
1206+}
1207+
1208+////////////////////////////////////// TouchRegistry::TouchInfo ////////////////////////////////////
1209+
1210+TouchRegistry::TouchInfo::TouchInfo(int id)
1211+{
1212+ init(id);
1213+}
1214+
1215+void TouchRegistry::TouchInfo::reset()
1216+{
1217+ id = -1;
1218+
1219+ for (int i = 0; i < candidates.count(); ++i) {
1220+ CandidateInfo &candidate = candidates[i];
1221+ delete candidate.inactivityTimer;
1222+ candidate.inactivityTimer.clear(); // shoundn't be needed but anyway...
1223+ }
1224+}
1225+
1226+void TouchRegistry::TouchInfo::init(int id)
1227+{
1228+ this->id = id;
1229+ physicallyEnded = false;
1230+ candidates.clear();
1231+ watchers.clear();
1232+}
1233+
1234+bool TouchRegistry::TouchInfo::isOwned() const
1235+{
1236+ return !candidates.isEmpty() && !candidates.first().undecided;
1237+}
1238+
1239+bool TouchRegistry::TouchInfo::ended() const
1240+{
1241+ Q_ASSERT(isValid());
1242+ return physicallyEnded && (isOwned() || candidates.isEmpty());
1243+}
1244+
1245+void TouchRegistry::TouchInfo::notifyCandidatesOfOwnershipResolution()
1246+{
1247+ Q_ASSERT(isOwned());
1248+
1249+ #if TOUCHREGISTRY_DEBUG
1250+ UG_DEBUG << "sending TouchOwnershipEvent(id =" << id
1251+ << " gained) to candidate" << candidates[0].item;
1252+ #endif
1253+
1254+ TouchOwnershipEvent gainedOwnershipEvent(id, true /*gained*/);
1255+ QCoreApplication::sendEvent(candidates[0].item, &gainedOwnershipEvent);
1256+
1257+
1258+ TouchOwnershipEvent lostOwnershipEvent(id, false /*gained*/);
1259+ for (int i = 1; i < candidates.count(); ++i) {
1260+ #if TOUCHREGISTRY_DEBUG
1261+ UG_DEBUG << "sending TouchWonershipEvent(id =" << id << " lost) to candidate"
1262+ << candidates[i].item;
1263+ #endif
1264+ QCoreApplication::sendEvent(candidates[i].item, &lostOwnershipEvent);
1265+ }
1266+}
1267
1268=== added file 'libs/UbuntuGestures/TouchRegistry.h'
1269--- libs/UbuntuGestures/TouchRegistry.h 1970-01-01 00:00:00 +0000
1270+++ libs/UbuntuGestures/TouchRegistry.h 2014-10-10 15:05:15 +0000
1271@@ -0,0 +1,182 @@
1272+/*
1273+ * Copyright (C) 2014 Canonical, Ltd.
1274+ *
1275+ * This program is free software; you can redistribute it and/or modify
1276+ * it under the terms of the GNU General Public License as published by
1277+ * the Free Software Foundation; version 3.
1278+ *
1279+ * This program is distributed in the hope that it will be useful,
1280+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1281+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1282+ * GNU General Public License for more details.
1283+ *
1284+ * You should have received a copy of the GNU General Public License
1285+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1286+ */
1287+
1288+#ifndef UNITY_TOUCHREGISTRY_H
1289+#define UNITY_TOUCHREGISTRY_H
1290+
1291+#include <QQuickItem>
1292+#include <QObject>
1293+#include <QPointer>
1294+#include <QTouchEvent>
1295+#include <QVector>
1296+
1297+#include "UbuntuGesturesGlobal.h"
1298+#include "CandidateInactivityTimer.h"
1299+#include "Pool.h"
1300+
1301+namespace UbuntuGestures {
1302+ class AbstractTimerFactory;
1303+}
1304+
1305+/*
1306+ Where the ownership of touches is registered.
1307+
1308+ Singleton used for adding a touch point ownership model analogous to the one
1309+ described in the XInput 2.2 protocol[1] on top of the existing input dispatch logic in QQuickWindow.
1310+
1311+ It provides a much more flexible and powerful way of dealing with pointer ownership than the existing
1312+ mechanisms in Qt. Namely QQuickItem::grabTouchPoints, QuickItem::keepTouchGrab,
1313+ QQuickItem::setFiltersChildMouseEvents, QQuickItem::ungrabTouchPoints and QQuickItem::touchUngrabEvent.
1314+
1315+ Usage:
1316+
1317+ 1- An item receives a a new touch point. If he's not sure whether he wants it yet, he calls:
1318+ TouchRegistry::instance()->addCandidateOwnerForTouch(touchId, this);
1319+ touchEvent->ignore();
1320+ Ignoring the event is crucial so that it can be seen by other interested parties, which will
1321+ behave similarly.
1322+
1323+ 2- That item will then start receiving UnownedTouchEvents for that touch from step 1. Once he's
1324+ made a decision he calls either:
1325+ TouchRegistry::instance()->requestTouchOwnership(touchId, this);
1326+ If he wants the touch point or:
1327+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, this);
1328+  if he does not want it.
1329+
1330+ Candidates are put in a priority queue. The first one to call addCandidateOwnerForTouch() will
1331+ take precedence over the others for receiving ownership over the touch point (from now on called
1332+ simply top-candidate).
1333+
1334+ If the top-candidate calls requestTouchOwnership() he will immediately receive a
1335+ TouchOwnershipEvent(gained=true) for that touch point. He can then safely call
1336+ QQuickItem::grabTouchPoints to actually get the owned touch points. The other candidates
1337+ will receive TouchOwnershipEvent(gained=false) and will no longer receive UnownedTouchEvents
1338+ for that touch point. They will have to undo whatever action they were performing with that
1339+ touch point.
1340+
1341+ But if the top-candidate calls removeCandidateOwnerForTouch() instead, he's popped from the
1342+ candidacy queue and ownership is given to the new top-most candidate if he has already
1343+ made his decision, that is.
1344+
1345+ The TouchRegistry cannot enforce the results of this pointer ownership negotiation (i.e.,
1346+ who gets to grab the touch points) as that would clash with QQuickWindow's input event
1347+ dispatching logic. The candidates have to respect the decision and grab the touch points
1348+ themselves.
1349+
1350+ If an item wants ownership over touches as soon as he receives the TouchBegin for them, his step 1
1351+ would be instead:
1352+ TouchRegistry::instance()->requestTouchOwnership(touchId, this);
1353+ return true;
1354+ He would then be notified once ownership has been granted to him, from which point onwards he could
1355+ safely assume other TouchRegistry users wouldn't snatch this touch away from him.
1356+
1357+ Items oblivious to TouchRegistry will lose their touch points without warning, just like in plain Qt.
1358+
1359+ [1] - http://www.x.org/releases/X11R7.7/doc/inputproto/XI2proto.txt (see multitouch-ownership)
1360+ */
1361+class UBUNTUGESTURES_EXPORT TouchRegistry : public QObject
1362+{
1363+ Q_OBJECT
1364+public:
1365+ TouchRegistry(QObject *parent = nullptr);
1366+ // Useful for tests, where you should feed a fake timer
1367+ TouchRegistry(QObject *parent, UbuntuGestures::AbstractTimerFactory *timerFactory);
1368+
1369+ virtual ~TouchRegistry();
1370+
1371+ // Returns a pointer to the application's TouchRegistry instance.
1372+ // If no instance has been allocated, null is returned.
1373+ static TouchRegistry *instance() { return m_instance; }
1374+
1375+ void update(const QTouchEvent *event);
1376+
1377+ // Calls update() if the given event is a QTouchEvent
1378+ bool eventFilter(QObject *watched, QEvent *event) override;
1379+
1380+ // An item that might later request ownership over the given touch point.
1381+ // He will be kept informed about that touch point through UnownedTouchEvents
1382+ // All candidates must eventually decide whether they want to own the touch point
1383+ // or not. That decision is informed through requestTouchOwnership() or
1384+ // removeCandidateOwnerForTouch()
1385+ void addCandidateOwnerForTouch(int id, QQuickItem *candidate);
1386+
1387+ // The same as rejecting ownership of a touch
1388+ void removeCandidateOwnerForTouch(int id, QQuickItem *candidate);
1389+
1390+ // The candidate object wants to be the owner of the touch with the given id.
1391+ // If he's currently the oldest/top-most candidate, he will get an ownership
1392+ // event immediately. If not, he will get ownership if (or once) he becomes the
1393+ // top-most candidate.
1394+ void requestTouchOwnership(int id, QQuickItem *candidate);
1395+
1396+ // An item that has no interest (effective or potential) in owning a touch point
1397+ // but would nonetheless like to be kept up-to-date on its state.
1398+ void addTouchWatcher(int touchId, QQuickItem *watcherItem);
1399+
1400+private Q_SLOTS:
1401+ void rejectCandidateOwnerForTouch(int id, QQuickItem *candidate);
1402+
1403+private:
1404+ class CandidateInfo {
1405+ public:
1406+ bool undecided;
1407+ // TODO: Prune candidates that become null and resolve ownership accordingly.
1408+ QPointer<QQuickItem> item;
1409+ QPointer<UbuntuGestures::CandidateInactivityTimer> inactivityTimer;
1410+ };
1411+
1412+ class TouchInfo {
1413+ public:
1414+ TouchInfo() : id(-1) {}
1415+ TouchInfo(int id);
1416+ bool isValid() const { return id >= 0; }
1417+ void reset();
1418+ void init(int id);
1419+ int id;
1420+ bool physicallyEnded;
1421+ bool isOwned() const;
1422+ bool ended() const;
1423+ void notifyCandidatesOfOwnershipResolution();
1424+
1425+ // TODO optimize storage (s/QList/Pool)
1426+ QList<CandidateInfo> candidates;
1427+ QList<QPointer<QQuickItem>> watchers;
1428+ };
1429+
1430+ Pool<TouchInfo>::Iterator findTouchInfo(int id);
1431+
1432+ void deliverTouchUpdatesToUndecidedCandidatesAndWatchers(const QTouchEvent *event);
1433+
1434+ static void translateTouchPointFromScreenToWindowCoords(QTouchEvent::TouchPoint &touchPoint);
1435+
1436+ static void dispatchPointsToItem(const QTouchEvent *event, const QList<int> &touchIds,
1437+ QQuickItem *item);
1438+ void freeEndedTouchInfos();
1439+
1440+ Pool<TouchInfo> m_touchInfoPool;
1441+
1442+ // the singleton instance
1443+ static TouchRegistry *m_instance;
1444+
1445+ bool m_inDispatchLoop;
1446+
1447+ UbuntuGestures::AbstractTimerFactory *m_timerFactory;
1448+
1449+ friend class tst_TouchRegistry;
1450+ friend class tst_DirectionalDragArea;
1451+};
1452+
1453+#endif // UNITY_TOUCHREGISTRY_H
1454
1455=== added file 'libs/UbuntuGestures/UbuntuGesturesGlobal.h'
1456--- libs/UbuntuGestures/UbuntuGesturesGlobal.h 1970-01-01 00:00:00 +0000
1457+++ libs/UbuntuGestures/UbuntuGesturesGlobal.h 2014-10-10 15:05:15 +0000
1458@@ -0,0 +1,23 @@
1459+/*
1460+ * Copyright (C) 2014 Canonical, Ltd.
1461+ *
1462+ * This program is free software; you can redistribute it and/or modify
1463+ * it under the terms of the GNU General Public License as published by
1464+ * the Free Software Foundation; version 3.
1465+ *
1466+ * This program is distributed in the hope that it will be useful,
1467+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1468+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1469+ * GNU General Public License for more details.
1470+ *
1471+ * You should have received a copy of the GNU General Public License
1472+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1473+ */
1474+
1475+#include <QtCore/QtGlobal>
1476+
1477+#if defined(UBUNTUGESTURES_LIBRARY)
1478+# define UBUNTUGESTURES_EXPORT Q_DECL_EXPORT
1479+#else
1480+# define UBUNTUGESTURES_EXPORT Q_DECL_IMPORT
1481+#endif
1482
1483=== added file 'libs/UbuntuGestures/UnownedTouchEvent.cpp'
1484--- libs/UbuntuGestures/UnownedTouchEvent.cpp 1970-01-01 00:00:00 +0000
1485+++ libs/UbuntuGestures/UnownedTouchEvent.cpp 2014-10-10 15:05:15 +0000
1486@@ -0,0 +1,39 @@
1487+/*
1488+ * Copyright (C) 2014 Canonical, Ltd.
1489+ *
1490+ * This program is free software; you can redistribute it and/or modify
1491+ * it under the terms of the GNU General Public License as published by
1492+ * the Free Software Foundation; version 3.
1493+ *
1494+ * This program is distributed in the hope that it will be useful,
1495+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1496+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1497+ * GNU General Public License for more details.
1498+ *
1499+ * You should have received a copy of the GNU General Public License
1500+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1501+ */
1502+
1503+#include "UnownedTouchEvent.h"
1504+
1505+QEvent::Type UnownedTouchEvent::m_unownedTouchEventType = (QEvent::Type)-1;
1506+
1507+UnownedTouchEvent::UnownedTouchEvent(QTouchEvent *touchEvent)
1508+ : QEvent(unownedTouchEventType())
1509+ , m_touchEvent(touchEvent)
1510+{
1511+}
1512+
1513+QEvent::Type UnownedTouchEvent::unownedTouchEventType()
1514+{
1515+ if (m_unownedTouchEventType == (QEvent::Type)-1) {
1516+ m_unownedTouchEventType = (QEvent::Type)registerEventType();
1517+ }
1518+
1519+ return m_unownedTouchEventType;
1520+}
1521+
1522+QTouchEvent *UnownedTouchEvent::touchEvent()
1523+{
1524+ return m_touchEvent.data();
1525+}
1526
1527=== added file 'libs/UbuntuGestures/UnownedTouchEvent.h'
1528--- libs/UbuntuGestures/UnownedTouchEvent.h 1970-01-01 00:00:00 +0000
1529+++ libs/UbuntuGestures/UnownedTouchEvent.h 2014-10-10 15:05:15 +0000
1530@@ -0,0 +1,45 @@
1531+/*
1532+ * Copyright (C) 2014 Canonical, Ltd.
1533+ *
1534+ * This program is free software; you can redistribute it and/or modify
1535+ * it under the terms of the GNU General Public License as published by
1536+ * the Free Software Foundation; version 3.
1537+ *
1538+ * This program is distributed in the hope that it will be useful,
1539+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1540+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1541+ * GNU General Public License for more details.
1542+ *
1543+ * You should have received a copy of the GNU General Public License
1544+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1545+ */
1546+
1547+#ifndef UBUNTU_UNOWNEDTOUCHEVENT_H
1548+#define UBUNTU_UNOWNEDTOUCHEVENT_H
1549+
1550+#include <QTouchEvent>
1551+#include <QScopedPointer>
1552+#include "UbuntuGesturesGlobal.h"
1553+
1554+/*
1555+ A touch event with touch points that do not belong the item receiving it.
1556+
1557+ See TouchRegistry::addCandidateOwnerForTouch and TouchRegistry::addTouchWatcher
1558+ */
1559+class UBUNTUGESTURES_EXPORT UnownedTouchEvent : public QEvent
1560+{
1561+public:
1562+ UnownedTouchEvent(QTouchEvent *touchEvent);
1563+ static Type unownedTouchEventType();
1564+
1565+ // TODO: It might be cleaner to store the information directly in UnownedTouchEvent
1566+ // instead of carrying around a synthesized QTouchEvent. But the latter option
1567+ // is very convenient.
1568+ QTouchEvent *touchEvent();
1569+
1570+private:
1571+ static Type m_unownedTouchEventType;
1572+ QScopedPointer<QTouchEvent> m_touchEvent;
1573+};
1574+
1575+#endif // UBUNTU_UNOWNEDTOUCHEVENT_H
1576
1577=== modified file 'plugins/Ubuntu/Gestures/AxisVelocityCalculator.h'
1578--- plugins/Ubuntu/Gestures/AxisVelocityCalculator.h 2013-10-23 12:25:40 +0000
1579+++ plugins/Ubuntu/Gestures/AxisVelocityCalculator.h 2014-10-10 15:05:15 +0000
1580@@ -21,7 +21,7 @@
1581 #ifndef VELOCITY_CALCULATOR_H
1582 #define VELOCITY_CALCULATOR_H
1583
1584-#include "UbuntuGesturesGlobal.h"
1585+#include "UbuntuGesturesQmlGlobal.h"
1586 #include <stdint.h>
1587 #include <QtCore/QObject>
1588 #include "TimeSource.h"
1589@@ -49,7 +49,7 @@
1590 }
1591 }
1592 */
1593-class UBUNTUGESTURES_EXPORT AxisVelocityCalculator : public QObject
1594+class UBUNTUGESTURESQML_EXPORT AxisVelocityCalculator : public QObject
1595 {
1596 Q_OBJECT
1597
1598
1599=== modified file 'plugins/Ubuntu/Gestures/CMakeLists.txt'
1600--- plugins/Ubuntu/Gestures/CMakeLists.txt 2014-05-02 22:57:00 +0000
1601+++ plugins/Ubuntu/Gestures/CMakeLists.txt 2014-10-10 15:05:15 +0000
1602@@ -1,19 +1,38 @@
1603-set(UbuntuGestureQml_SOURCES
1604+# in order to include Qt's private headers
1605+remove_definitions(-DQT_NO_KEYWORDS)
1606+
1607+set(UbuntuGesturesQml_SOURCES
1608 plugin.cpp
1609 AxisVelocityCalculator.cpp
1610 Direction.cpp
1611 DirectionalDragArea.cpp
1612 PressedOutsideNotifier.cpp
1613 TimeSource.cpp
1614+ TouchGate.cpp
1615 )
1616
1617-add_definitions(-DUBUNTUGESTURES_LIBRARY)
1618-
1619-add_library(UbuntuGestureQml MODULE ${UbuntuGestureQml_SOURCES})
1620-
1621-qt5_use_modules(UbuntuGestureQml Core Quick)
1622+add_definitions(-DUBUNTUGESTURESQML_LIBRARY)
1623+
1624+add_library(UbuntuGesturesQml MODULE ${UbuntuGesturesQml_SOURCES})
1625+target_link_libraries(UbuntuGesturesQml UbuntuGestures)
1626+
1627+qt5_use_modules(UbuntuGesturesQml Core Quick)
1628
1629 # So that Foo.cpp can #include "Foo.moc"
1630 include_directories(${CMAKE_CURRENT_BINARY_DIR})
1631
1632-add_unity8_plugin(Ubuntu.Gestures 0.1 Ubuntu/Gestures TARGETS UbuntuGestureQml)
1633+include_directories(${CMAKE_SOURCE_DIR}/libs/UbuntuGestures)
1634+
1635+# There's no cmake var for v8 include path :-/ so create one
1636+LIST(GET Qt5Core_INCLUDE_DIRS 0 QtCoreDir0)
1637+SET(Qt5V8_PRIVATE_INCLUDE_DIR ${QtCoreDir0}/QtV8/${Qt5Core_VERSION_STRING}/QtV8)
1638+
1639+# DANGER! DANGER! Using Qt's private API!
1640+include_directories(
1641+ ${Qt5Qml_PRIVATE_INCLUDE_DIRS}
1642+ ${Qt5Quick_INCLUDE_DIRS}
1643+ ${Qt5Quick_PRIVATE_INCLUDE_DIRS}
1644+ ${Qt5V8_PRIVATE_INCLUDE_DIR}
1645+)
1646+
1647+add_unity8_plugin(Ubuntu.Gestures 0.1 Ubuntu/Gestures TARGETS UbuntuGesturesQml)
1648
1649=== modified file 'plugins/Ubuntu/Gestures/Direction.h'
1650--- plugins/Ubuntu/Gestures/Direction.h 2013-06-19 08:29:34 +0000
1651+++ plugins/Ubuntu/Gestures/Direction.h 2014-10-10 15:05:15 +0000
1652@@ -17,14 +17,14 @@
1653 #ifndef DIRECTION_H
1654 #define DIRECTION_H
1655
1656-#include "UbuntuGesturesGlobal.h"
1657+#include "UbuntuGesturesQmlGlobal.h"
1658 #include <QObject>
1659
1660 /*
1661 A Direction enum wrapper so that we can do things like "direction: Direction.Righwards"
1662 from QML.
1663 */
1664-class UBUNTUGESTURES_EXPORT Direction : public QObject {
1665+class UBUNTUGESTURESQML_EXPORT Direction : public QObject {
1666 Q_OBJECT
1667 Q_ENUMS(Type)
1668
1669
1670=== modified file 'plugins/Ubuntu/Gestures/DirectionalDragArea.cpp'
1671--- plugins/Ubuntu/Gestures/DirectionalDragArea.cpp 2014-05-02 08:29:26 +0000
1672+++ plugins/Ubuntu/Gestures/DirectionalDragArea.cpp 2014-10-10 15:05:15 +0000
1673@@ -1,5 +1,5 @@
1674 /*
1675- * Copyright (C) 2013 Canonical, Ltd.
1676+ * Copyright (C) 2013-2014 Canonical, Ltd.
1677 *
1678 * This program is free software; you can redistribute it and/or modify
1679 * it under the terms of the GNU General Public License as published by
1680@@ -14,64 +14,32 @@
1681 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1682 */
1683
1684+#define ACTIVETOUCHESINFO_DEBUG 0
1685+#define DIRECTIONALDRAGAREA_DEBUG 0
1686+
1687 #include "DirectionalDragArea.h"
1688
1689+#include <QQuickWindow>
1690 #include <QtCore/qmath.h>
1691-#include <QtCore/QTimer>
1692 #include <QDebug>
1693
1694+#pragma GCC diagnostic push
1695+#pragma GCC diagnostic ignored "-pedantic"
1696+#include <private/qquickwindow_p.h>
1697+#pragma GCC diagnostic pop
1698+
1699+// local
1700+#include "TouchOwnershipEvent.h"
1701+#include "TouchRegistry.h"
1702+#include "UnownedTouchEvent.h"
1703+
1704 using namespace UbuntuGestures;
1705
1706-#define DIRECTIONALDRAGAREA_DEBUG 0
1707-
1708 #if DIRECTIONALDRAGAREA_DEBUG
1709 #define DDA_DEBUG(msg) qDebug("[DDA] " msg)
1710+#include "DebugHelpers.h"
1711+
1712 namespace {
1713-QString touchPointStateToString(Qt::TouchPointState state) {
1714- switch (state) {
1715- case Qt::TouchPointPressed:
1716- return QString("pressed");
1717- case Qt::TouchPointMoved:
1718- return QString("moved");
1719- case Qt::TouchPointStationary:
1720- return QString("stationary");
1721- default: // Qt::TouchPointReleased:
1722- return QString("released");
1723- }
1724-}
1725-QString touchEventToString(QTouchEvent *ev)
1726-{
1727- QString message;
1728-
1729- switch (ev->type()) {
1730- case QEvent::TouchBegin:
1731- message.append("TouchBegin ");
1732- break;
1733- case QEvent::TouchUpdate:
1734- message.append("TouchUpdate ");
1735- break;
1736- case QEvent::TouchEnd:
1737- message.append("TouchEnd ");
1738- break;
1739- default: //QEvent::TouchCancel
1740- message.append("TouchCancel ");
1741- }
1742-
1743- for (int i=0; i < ev->touchPoints().size(); ++i) {
1744-
1745- const QTouchEvent::TouchPoint& touchPoint = ev->touchPoints().at(i);
1746- message.append(
1747- QString("(id:%1, state:%2, scenePos:(%3,%4)) ")
1748- .arg(touchPoint.id())
1749- .arg(touchPointStateToString(touchPoint.state()))
1750- .arg(touchPoint.scenePos().x())
1751- .arg(touchPoint.scenePos().y())
1752- );
1753- }
1754-
1755- return message;
1756-}
1757-
1758 const char *statusToString(DirectionalDragArea::Status status)
1759 {
1760 if (status == DirectionalDragArea::WaitingForTouch) {
1761@@ -88,23 +56,6 @@
1762 #define DDA_DEBUG(msg) do{}while(0)
1763 #endif // DIRECTIONALDRAGAREA_DEBUG
1764
1765-// Essentially a QTimer wrapper
1766-class RecognitionTimer : public UbuntuGestures::AbstractTimer
1767-{
1768- Q_OBJECT
1769-public:
1770- RecognitionTimer(QObject *parent) : UbuntuGestures::AbstractTimer(parent) {
1771- m_timer.setSingleShot(false);
1772- connect(&m_timer, &QTimer::timeout,
1773- this, &UbuntuGestures::AbstractTimer::timeout);
1774- }
1775- virtual int interval() const { return m_timer.interval(); }
1776- virtual void setInterval(int msecs) { m_timer.setInterval(msecs); }
1777- virtual void start() { m_timer.start(); UbuntuGestures::AbstractTimer::start(); }
1778- virtual void stop() { m_timer.stop(); UbuntuGestures::AbstractTimer::stop(); }
1779-private:
1780- QTimer m_timer;
1781-};
1782
1783 DirectionalDragArea::DirectionalDragArea(QQuickItem *parent)
1784 : QQuickItem(parent)
1785@@ -126,12 +77,14 @@
1786 , m_timeSource(new RealTimeSource)
1787 , m_activeTouches(m_timeSource)
1788 {
1789- setRecognitionTimer(new RecognitionTimer(this));
1790+ setRecognitionTimer(new Timer(this));
1791 m_recognitionTimer->setInterval(60);
1792+ m_recognitionTimer->setSingleShot(false);
1793
1794 m_velocityCalculator = new AxisVelocityCalculator(this);
1795
1796- connect(this, &QQuickItem::enabledChanged, this, &DirectionalDragArea::onEnabledChanged);
1797+ connect(this, &QQuickItem::enabledChanged, this, &DirectionalDragArea::giveUpIfDisabledOrInvisible);
1798+ connect(this, &QQuickItem::visibleChanged, this, &DirectionalDragArea::giveUpIfDisabledOrInvisible);
1799 }
1800
1801 Direction::Type DirectionalDragArea::direction() const
1802@@ -214,6 +167,7 @@
1803 {
1804 int interval = 0;
1805 bool timerWasRunning = false;
1806+ bool wasSingleShot = false;
1807
1808 // can be null when called from the constructor
1809 if (m_recognitionTimer) {
1810@@ -226,6 +180,7 @@
1811
1812 m_recognitionTimer = timer;
1813 timer->setInterval(interval);
1814+ timer->setSingleShot(wasSingleShot);
1815 connect(timer, &UbuntuGestures::AbstractTimer::timeout,
1816 this, &DirectionalDragArea::checkSpeed);
1817 if (timerWasRunning) {
1818@@ -280,8 +235,144 @@
1819 return m_previousScenePos.y();
1820 }
1821
1822+bool DirectionalDragArea::event(QEvent *event)
1823+{
1824+ if (event->type() == TouchOwnershipEvent::touchOwnershipEventType()) {
1825+ touchOwnershipEvent(static_cast<TouchOwnershipEvent *>(event));
1826+ return true;
1827+ } else if (event->type() == UnownedTouchEvent::unownedTouchEventType()) {
1828+ unownedTouchEvent(static_cast<UnownedTouchEvent *>(event));
1829+ return true;
1830+ } else {
1831+ return QQuickItem::event(event);
1832+ }
1833+}
1834+
1835+void DirectionalDragArea::touchOwnershipEvent(TouchOwnershipEvent *event)
1836+{
1837+ if (event->gained()) {
1838+ QVector<int> ids;
1839+ ids.append(event->touchId());
1840+ DDA_DEBUG("grabbing touch");
1841+ grabTouchPoints(ids);
1842+
1843+ // Work around for Qt bug. If we grab a touch that is being used for mouse pointer
1844+ // emulation it will cause the emulation logic to go nuts.
1845+ // Thus we have to also grab the mouse in this case.
1846+ // TODO: Report bug to Qt
1847+ if (window()) {
1848+ QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(window());
1849+ if (windowPrivate->touchMouseId == event->touchId() && window()->mouseGrabberItem()) {
1850+ DDA_DEBUG("removing mouse grabber");
1851+ window()->mouseGrabberItem()->ungrabMouse();
1852+ }
1853+ }
1854+ } else {
1855+ // We still wanna know when it ends for keeping the composition time window up-to-date
1856+ TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
1857+
1858+ setStatus(WaitingForTouch);
1859+ }
1860+}
1861+
1862+void DirectionalDragArea::unownedTouchEvent(UnownedTouchEvent *unownedTouchEvent)
1863+{
1864+ QTouchEvent *event = unownedTouchEvent->touchEvent();
1865+
1866+ Q_ASSERT(!event->touchPointStates().testFlag(Qt::TouchPointPressed));
1867+
1868+ #if DIRECTIONALDRAGAREA_DEBUG
1869+ // TODO Consider using qCDebug() when available (Qt 5.2)
1870+ qDebug() << "[DDA] Unowned" << m_timeSource->msecsSinceReference()
1871+ << qPrintable(touchEventToString(event));
1872+ #endif
1873+
1874+ switch (m_status) {
1875+ case WaitingForTouch:
1876+ // do nothing
1877+ break;
1878+ case Undecided:
1879+ Q_ASSERT(isEnabled() && isVisible());
1880+ unownedTouchEvent_undecided(unownedTouchEvent);
1881+ break;
1882+ default: // Recognized:
1883+ // do nothing
1884+ break;
1885+ }
1886+
1887+ m_activeTouches.update(event);
1888+}
1889+
1890+void DirectionalDragArea::unownedTouchEvent_undecided(UnownedTouchEvent *unownedTouchEvent)
1891+{
1892+ const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(unownedTouchEvent->touchEvent());
1893+ if (!touchPoint) {
1894+ qCritical() << "DirectionalDragArea[status=Undecided]: touch " << m_touchId
1895+ << "missing from UnownedTouchEvent without first reaching state Qt::TouchPointReleased. "
1896+ "Considering it as released.";
1897+
1898+ TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
1899+ setStatus(WaitingForTouch);
1900+ return;
1901+ }
1902+
1903+ const QPointF &touchScenePos = touchPoint->scenePos();
1904+
1905+ if (touchPoint->state() == Qt::TouchPointReleased) {
1906+ // touch has ended before recognition concluded
1907+ DDA_DEBUG("Touch has ended before recognition concluded");
1908+ TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
1909+ emitSignalIfTapped();
1910+ setStatus(WaitingForTouch);
1911+ return;
1912+ }
1913+
1914+ m_previousDampedScenePos.setX(m_dampedScenePos.x());
1915+ m_previousDampedScenePos.setY(m_dampedScenePos.y());
1916+ m_dampedScenePos.update(touchScenePos);
1917+ updateVelocityCalculator(touchScenePos);
1918+
1919+ if (!pointInsideAllowedArea()) {
1920+ DDA_DEBUG("Rejecting gesture because touch point is outside allowed area.");
1921+ TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
1922+ // We still wanna know when it ends for keeping the composition time window up-to-date
1923+ TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
1924+ setStatus(WaitingForTouch);
1925+ return;
1926+ }
1927+
1928+ if (!movingInRightDirection()) {
1929+ DDA_DEBUG("Rejecting gesture because touch point is moving in the wrong direction.");
1930+ TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
1931+ // We still wanna know when it ends for keeping the composition time window up-to-date
1932+ TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
1933+ setStatus(WaitingForTouch);
1934+ return;
1935+ }
1936+
1937+ setPreviousPos(touchPoint->pos());
1938+ setPreviousScenePos(touchScenePos);
1939+
1940+ if (isWithinTouchCompositionWindow()) {
1941+ // There's still time for some new touch to appear and ruin our party as it would be combined
1942+ // with our m_touchId one and therefore deny the possibility of a single-finger gesture.
1943+ DDA_DEBUG("Sill within composition window. Let's wait more.");
1944+ return;
1945+ }
1946+
1947+ if (movedFarEnough(touchScenePos)) {
1948+ TouchRegistry::instance()->requestTouchOwnership(m_touchId, this);
1949+ setStatus(Recognized);
1950+ } else {
1951+ DDA_DEBUG("Didn't move far enough yet. Let's wait more.");
1952+ }
1953+}
1954+
1955 void DirectionalDragArea::touchEvent(QTouchEvent *event)
1956 {
1957+ // TODO: Consider when more than one touch starts in the same event (although it's not possible
1958+ // with Mir's android-input). Have to track them all. Consider it a plus/bonus.
1959+
1960 #if DIRECTIONALDRAGAREA_DEBUG
1961 // TODO Consider using qCDebug() when available (Qt 5.2)
1962 qDebug() << "[DDA]" << m_timeSource->msecsSinceReference()
1963@@ -310,29 +401,34 @@
1964
1965 void DirectionalDragArea::touchEvent_absent(QTouchEvent *event)
1966 {
1967+ // TODO: accept/reject is for the whole event, not per touch id. See how that affects us.
1968+
1969 if (!event->touchPointStates().testFlag(Qt::TouchPointPressed)) {
1970 // Nothing to see here. No touch starting in this event.
1971 return;
1972 }
1973
1974+ // to be proven wrong, if that's the case
1975+ bool allGood = true;
1976+
1977 if (isWithinTouchCompositionWindow()) {
1978 // too close to the last touch start. So we consider them as starting roughly at the same time.
1979 // Can't be a single-touch gesture.
1980 #if DIRECTIONALDRAGAREA_DEBUG
1981 qDebug("[DDA] A new touch point came in but we're still within time composition window. Ignoring it.");
1982 #endif
1983- return;
1984+ allGood = false;
1985 }
1986
1987 const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
1988
1989 const QTouchEvent::TouchPoint *newTouchPoint = nullptr;
1990- for (int i = 0; i < touchPoints.count(); ++i) {
1991+ for (int i = 0; i < touchPoints.count() && allGood; ++i) {
1992 const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
1993 if (touchPoint.state() == Qt::TouchPointPressed) {
1994 if (newTouchPoint) {
1995 // more than one touch starting in this QTouchEvent. Can't be a single-touch gesture
1996- return;
1997+ allGood = false;
1998 } else {
1999 // that's our candidate
2000 m_touchId = touchPoint.id();
2001@@ -341,85 +437,61 @@
2002 }
2003 }
2004
2005- Q_ASSERT(newTouchPoint);
2006-
2007- // If we have made this far, we are good to go to the next status.
2008-
2009- m_startPos = newTouchPoint->pos();
2010- m_startScenePos = newTouchPoint->scenePos();
2011- m_touchId = newTouchPoint->id();
2012- m_dampedScenePos.reset(m_startScenePos);
2013- m_velocityCalculator->setTrackedPosition(0.);
2014- m_velocityCalculator->reset();
2015- m_numSamplesOnLastSpeedCheck = 0;
2016- m_silenceTime = 0;
2017- setPreviousPos(m_startPos);
2018- setPreviousScenePos(m_startScenePos);
2019- updateSceneDirectionVector();
2020-
2021- setStatus(Undecided);
2022+ if (allGood) {
2023+ Q_ASSERT(newTouchPoint);
2024+
2025+ m_startPos = newTouchPoint->pos();
2026+ m_startScenePos = newTouchPoint->scenePos();
2027+ m_touchId = newTouchPoint->id();
2028+ m_dampedScenePos.reset(m_startScenePos);
2029+ m_velocityCalculator->setTrackedPosition(0.);
2030+ m_velocityCalculator->reset();
2031+ m_numSamplesOnLastSpeedCheck = 0;
2032+ m_silenceTime = 0;
2033+ setPreviousPos(m_startPos);
2034+ setPreviousScenePos(m_startScenePos);
2035+ updateSceneDirectionVector();
2036+
2037+ if (recognitionIsDisabled()) {
2038+ // Behave like a dumb TouchArea
2039+ TouchRegistry::instance()->requestTouchOwnership(m_touchId, this);
2040+ setStatus(Recognized);
2041+ event->accept();
2042+ } else {
2043+ // just monitor the touch points for now.
2044+ TouchRegistry::instance()->addCandidateOwnerForTouch(m_touchId, this);
2045+
2046+ setStatus(Undecided);
2047+ // Let the item below have it. We will monitor it and grab it later if a gesture
2048+ // gets recognized.
2049+ event->ignore();
2050+ }
2051+ } else {
2052+ watchPressedTouchPoints(touchPoints);
2053+ event->ignore();
2054+ }
2055 }
2056
2057 void DirectionalDragArea::touchEvent_undecided(QTouchEvent *event)
2058 {
2059- const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(event);
2060-
2061- if (!touchPoint) {
2062- qCritical() << "DirectionalDragArea[status=Undecided]: touch " << m_touchId
2063- << "missing from QTouchEvent without first reaching state Qt::TouchPointReleased. "
2064- "Considering it as released.";
2065- setStatus(WaitingForTouch);
2066- return;
2067- }
2068-
2069- const QPointF &touchScenePos = touchPoint->scenePos();
2070-
2071- if (touchPoint->state() == Qt::TouchPointReleased) {
2072- // touch has ended before recognition concluded
2073- DDA_DEBUG("Touch has ended before recognition concluded");
2074- setStatus(WaitingForTouch);
2075- Q_EMIT tapped();
2076- return;
2077- }
2078+ Q_ASSERT(event->type() == QEvent::TouchBegin);
2079+ Q_ASSERT(fetchTargetTouchPoint(event) == nullptr);
2080+
2081+ // We're not interested in new touch points. We already have our candidate (m_touchId).
2082+ // But we do want to know when those new touches end for keeping the composition time
2083+ // window up-to-date
2084+ event->ignore();
2085+ watchPressedTouchPoints(event->touchPoints());
2086
2087 if (event->touchPointStates().testFlag(Qt::TouchPointPressed) && isWithinTouchCompositionWindow()) {
2088 // multi-finger drags are not accepted
2089 DDA_DEBUG("Multi-finger drags are not accepted");
2090- setStatus(WaitingForTouch);
2091- return;
2092- }
2093-
2094- m_previousDampedScenePos.setX(m_dampedScenePos.x());
2095- m_previousDampedScenePos.setY(m_dampedScenePos.y());
2096- m_dampedScenePos.update(touchScenePos);
2097- updateVelocityCalculator(touchScenePos);
2098-
2099- if (!pointInsideAllowedArea()) {
2100- DDA_DEBUG("Rejecting gesture because touch point is outside allowed area.");
2101- setStatus(WaitingForTouch);
2102- return;
2103- }
2104-
2105- if (!movingInRightDirection()) {
2106- DDA_DEBUG("Rejecting gesture becauuse touch point is moving in the wrong direction.");
2107- setStatus(WaitingForTouch);
2108- return;
2109- }
2110-
2111- setPreviousPos(touchPoint->pos());
2112- setPreviousScenePos(touchScenePos);
2113-
2114- if (isWithinTouchCompositionWindow()) {
2115- // There's still time for some new touch to appear and ruin our party as it would be combined
2116- // with our m_touchId one and therefore deny the possibility of a single-finger gesture.
2117- DDA_DEBUG("Sill within composition window. Let's wait more.");
2118- return;
2119- }
2120-
2121- if (movedFarEnough(touchScenePos)) {
2122- setStatus(Recognized);
2123- } else {
2124- DDA_DEBUG("Didn't move far enough yet. Let's wait more.");
2125+
2126+ TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
2127+ // We still wanna know when it ends for keeping the composition time window up-to-date
2128+ TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
2129+
2130+ setStatus(WaitingForTouch);
2131 }
2132 }
2133
2134@@ -437,11 +509,35 @@
2135 setPreviousScenePos(touchPoint->scenePos());
2136
2137 if (touchPoint->state() == Qt::TouchPointReleased) {
2138+ emitSignalIfTapped();
2139 setStatus(WaitingForTouch);
2140 }
2141 }
2142 }
2143
2144+void DirectionalDragArea::watchPressedTouchPoints(const QList<QTouchEvent::TouchPoint> &touchPoints)
2145+{
2146+ for (int i = 0; i < touchPoints.count(); ++i) {
2147+ const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
2148+ if (touchPoint.state() == Qt::TouchPointPressed) {
2149+ TouchRegistry::instance()->addTouchWatcher(touchPoint.id(), this);
2150+ }
2151+ }
2152+}
2153+
2154+bool DirectionalDragArea::recognitionIsDisabled() const
2155+{
2156+ return distanceThreshold() <= 0 && compositionTime() <= 0;
2157+}
2158+
2159+void DirectionalDragArea::emitSignalIfTapped()
2160+{
2161+ qint64 touchDuration = m_timeSource->msecsSinceReference() - m_activeTouches.touchStartTime(m_touchId);
2162+ if (touchDuration <= maxTapDuration()) {
2163+ Q_EMIT tapped();
2164+ }
2165+}
2166+
2167 const QTouchEvent::TouchPoint *DirectionalDragArea::fetchTargetTouchPoint(QTouchEvent *event)
2168 {
2169 const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
2170@@ -509,12 +605,16 @@
2171
2172 void DirectionalDragArea::checkSpeed()
2173 {
2174+ Q_ASSERT(m_status == Undecided);
2175+
2176 if (m_velocityCalculator->numSamples() >= AxisVelocityCalculator::MIN_SAMPLES_NEEDED) {
2177 qreal speed = qFabs(m_velocityCalculator->calculate());
2178 qreal minSpeedMsecs = m_minSpeed / 1000.0;
2179
2180 if (speed < minSpeedMsecs) {
2181 DDA_DEBUG("Rejecting gesture because it's below minimum speed.");
2182+ TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
2183+ TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
2184 setStatus(WaitingForTouch);
2185 }
2186 }
2187@@ -523,7 +623,9 @@
2188 m_silenceTime += m_recognitionTimer->interval();
2189
2190 if (m_silenceTime > m_maxSilenceTime) {
2191- DDA_DEBUG("Rejecting gesture because it's silence time has been exceeded.");
2192+ DDA_DEBUG("Rejecting gesture because its silence time has been exceeded.");
2193+ TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
2194+ TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
2195 setStatus(WaitingForTouch);
2196 }
2197 } else {
2198@@ -532,10 +634,19 @@
2199 m_numSamplesOnLastSpeedCheck = m_velocityCalculator->numSamples();
2200 }
2201
2202-void DirectionalDragArea::onEnabledChanged()
2203+void DirectionalDragArea::giveUpIfDisabledOrInvisible()
2204 {
2205- if (!isEnabled() && m_status != WaitingForTouch) {
2206- setStatus(WaitingForTouch);
2207+ if (!isEnabled() || !isVisible()) {
2208+ if (m_status == Undecided) {
2209+ TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
2210+ // We still wanna know when it ends for keeping the composition time window up-to-date
2211+ TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
2212+ }
2213+
2214+ if (m_status != WaitingForTouch) {
2215+ DDA_DEBUG("Resetting status because got disabled or made invisible");
2216+ setStatus(WaitingForTouch);
2217+ }
2218 }
2219 }
2220
2221@@ -631,7 +742,9 @@
2222
2223 bool DirectionalDragArea::isWithinTouchCompositionWindow()
2224 {
2225- return !m_activeTouches.isEmpty() &&
2226+ return
2227+ compositionTime() > 0 &&
2228+ !m_activeTouches.isEmpty() &&
2229 m_timeSource->msecsSinceReference() <=
2230 m_activeTouches.mostRecentStartTime() + (qint64)compositionTime();
2231 }
2232@@ -639,18 +752,17 @@
2233 //************************** ActiveTouchesInfo **************************
2234
2235 DirectionalDragArea::ActiveTouchesInfo::ActiveTouchesInfo(const SharedTimeSource &timeSource)
2236- : m_timeSource(timeSource), m_lastUsedIndex(-1)
2237+ : m_timeSource(timeSource)
2238 {
2239- // Estimate of the maximum number of active touches we might reach.
2240- // Not a problem if it ends up being an underestimate as this is just
2241- // an optimization.
2242- m_vector.resize(3);
2243 }
2244
2245 void DirectionalDragArea::ActiveTouchesInfo::update(QTouchEvent *event)
2246 {
2247 if (!(event->touchPointStates() & (Qt::TouchPointPressed | Qt::TouchPointReleased))) {
2248 // nothing to update
2249+ #if ACTIVETOUCHESINFO_DEBUG
2250+ qDebug("[DDA::ActiveTouchesInfo] Nothing to Update");
2251+ #endif
2252 return;
2253 }
2254
2255@@ -658,74 +770,88 @@
2256 for (int i = 0; i < touchPoints.count(); ++i) {
2257 const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
2258 if (touchPoint.state() == Qt::TouchPointPressed) {
2259- addTouchPoint(touchPoint);
2260+ addTouchPoint(touchPoint.id());
2261 } else if (touchPoint.state() == Qt::TouchPointReleased) {
2262- removeTouchPoint(touchPoint);
2263+ removeTouchPoint(touchPoint.id());
2264 }
2265 }
2266 }
2267
2268-void DirectionalDragArea::ActiveTouchesInfo::addTouchPoint(const QTouchEvent::TouchPoint &touchPoint)
2269-{
2270- ActiveTouchInfo &activeTouchInfo = getEmptySlot();
2271- activeTouchInfo.id = touchPoint.id();
2272+#if ACTIVETOUCHESINFO_DEBUG
2273+QString DirectionalDragArea::ActiveTouchesInfo::toString()
2274+{
2275+ QString string = "(";
2276+
2277+ {
2278+ QTextStream stream(&string);
2279+ m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) {
2280+ stream << "(id=" << touchInfo->id << ",startTime=" << touchInfo->startTime << ")";
2281+ return true;
2282+ });
2283+ }
2284+
2285+ string.append(")");
2286+
2287+ return string;
2288+}
2289+#endif // ACTIVETOUCHESINFO_DEBUG
2290+
2291+void DirectionalDragArea::ActiveTouchesInfo::addTouchPoint(int touchId)
2292+{
2293+ ActiveTouchInfo &activeTouchInfo = m_touchInfoPool.getEmptySlot();
2294+ activeTouchInfo.id = touchId;
2295 activeTouchInfo.startTime = m_timeSource->msecsSinceReference();
2296-}
2297-
2298-void DirectionalDragArea::ActiveTouchesInfo::removeTouchPoint(const QTouchEvent::TouchPoint &touchPoint)
2299-{
2300- for (int i = 0; i <= m_lastUsedIndex; ++i) {
2301- if (touchPoint.id() == m_vector.at(i).id) {
2302- freeSlot(i);
2303- return;
2304- }
2305- }
2306- Q_ASSERT(false); // shouldn't reach this point
2307-}
2308-
2309-DirectionalDragArea::ActiveTouchInfo &DirectionalDragArea::ActiveTouchesInfo::getEmptySlot()
2310-{
2311- Q_ASSERT(m_lastUsedIndex < m_vector.size());
2312-
2313- // Look for an in-between vacancy first
2314- for (int i = 0; i < m_lastUsedIndex; ++i) {
2315- ActiveTouchInfo &activeTouchInfo = m_vector[i];
2316- if (!activeTouchInfo.isValid()) {
2317- return activeTouchInfo;
2318- }
2319- }
2320-
2321- ++m_lastUsedIndex;
2322- if (m_lastUsedIndex >= m_vector.size()) {
2323- m_vector.resize(m_lastUsedIndex + 1);
2324- }
2325-
2326- return m_vector[m_lastUsedIndex];
2327-}
2328-
2329-void DirectionalDragArea::ActiveTouchesInfo::freeSlot(int index)
2330-{
2331- m_vector[index].reset();
2332- if (index == m_lastUsedIndex) {
2333- do {
2334- --m_lastUsedIndex;
2335- } while (m_lastUsedIndex >= 0 && !m_vector.at(m_lastUsedIndex).isValid());
2336- }
2337+
2338+ #if ACTIVETOUCHESINFO_DEBUG
2339+ qDebug() << "[DDA::ActiveTouchesInfo]" << qPrintable(toString());
2340+ #endif
2341+}
2342+
2343+qint64 DirectionalDragArea::ActiveTouchesInfo::touchStartTime(int touchId)
2344+{
2345+ qint64 result = -1;
2346+
2347+ m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) {
2348+ if (touchId == touchInfo->id) {
2349+ result = touchInfo->startTime;
2350+ return false;
2351+ } else {
2352+ return true;
2353+ }
2354+ });
2355+
2356+ Q_ASSERT(result != -1);
2357+ return result;
2358+}
2359+
2360+void DirectionalDragArea::ActiveTouchesInfo::removeTouchPoint(int touchId)
2361+{
2362+ m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) {
2363+ if (touchId == touchInfo->id) {
2364+ m_touchInfoPool.freeSlot(touchInfo);
2365+ return false;
2366+ } else {
2367+ return true;
2368+ }
2369+ });
2370+
2371+ #if ACTIVETOUCHESINFO_DEBUG
2372+ qDebug() << "[DDA::ActiveTouchesInfo]" << qPrintable(toString());
2373+ #endif
2374 }
2375
2376 qint64 DirectionalDragArea::ActiveTouchesInfo::mostRecentStartTime()
2377 {
2378- Q_ASSERT(m_lastUsedIndex >= 0);
2379-
2380- qint64 highestStartTime = m_vector.at(0).startTime;
2381- int i = 1;
2382- do {
2383- const ActiveTouchInfo &activeTouchInfo = m_vector.at(i);
2384- if (activeTouchInfo.isValid() && activeTouchInfo.startTime > highestStartTime) {
2385- highestStartTime = activeTouchInfo.startTime;
2386+ Q_ASSERT(!m_touchInfoPool.isEmpty());
2387+
2388+ qint64 highestStartTime = -1;
2389+
2390+ m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &activeTouchInfo) {
2391+ if (activeTouchInfo->startTime > highestStartTime) {
2392+ highestStartTime = activeTouchInfo->startTime;
2393 }
2394- ++i;
2395- } while (i < m_lastUsedIndex);
2396+ return true;
2397+ });
2398
2399 return highestStartTime;
2400 }
2401
2402=== modified file 'plugins/Ubuntu/Gestures/DirectionalDragArea.h'
2403--- plugins/Ubuntu/Gestures/DirectionalDragArea.h 2014-05-02 08:29:26 +0000
2404+++ plugins/Ubuntu/Gestures/DirectionalDragArea.h 2014-10-10 15:05:15 +0000
2405@@ -1,5 +1,5 @@
2406 /*
2407- * Copyright (C) 2013 Canonical, Ltd.
2408+ * Copyright (C) 2013,2014 Canonical, Ltd.
2409 *
2410 * This program is free software; you can redistribute it and/or modify
2411 * it under the terms of the GNU General Public License as published by
2412@@ -19,27 +19,16 @@
2413
2414 #include <QtQuick/QQuickItem>
2415 #include "AxisVelocityCalculator.h"
2416-#include "UbuntuGesturesGlobal.h"
2417+#include "UbuntuGesturesQmlGlobal.h"
2418 #include "Damper.h"
2419 #include "Direction.h"
2420
2421-namespace UbuntuGestures {
2422-/* Defines an interface for a Timer. */
2423-class UBUNTUGESTURES_EXPORT AbstractTimer : public QObject {
2424- Q_OBJECT
2425-public:
2426- AbstractTimer(QObject *parent) : QObject(parent), m_isRunning(false) {}
2427- virtual int interval() const = 0;
2428- virtual void setInterval(int msecs) = 0;
2429- virtual void start() { m_isRunning = true; };
2430- virtual void stop() { m_isRunning = false; }
2431- bool isRunning() const { return m_isRunning; }
2432-Q_SIGNALS:
2433- void timeout();
2434-private:
2435- bool m_isRunning;
2436-};
2437-}
2438+// lib UbuntuGestures
2439+#include <Pool.h>
2440+#include <Timer.h>
2441+
2442+class TouchOwnershipEvent;
2443+class UnownedTouchEvent;
2444
2445 /*
2446 An area that detects axis-aligned single-finger drag gestures
2447@@ -50,7 +39,7 @@
2448
2449 See doc/DirectionalDragArea.svg
2450 */
2451-class UBUNTUGESTURES_EXPORT DirectionalDragArea : public QQuickItem {
2452+class UBUNTUGESTURESQML_EXPORT DirectionalDragArea : public QQuickItem {
2453 Q_OBJECT
2454
2455 // The direction in which the gesture should move in order to be recognized.
2456@@ -196,6 +185,12 @@
2457 // Useful for testing, where a fake time source can be supplied
2458 void setTimeSource(const UbuntuGestures::SharedTimeSource &timeSource);
2459
2460+ bool event(QEvent *e) override;
2461+
2462+ // Maximum time, in milliseconds, between a press and a release, for a touch
2463+ // sequence to be considered a tap.
2464+ int maxTapDuration() const { return 300; }
2465+
2466 Q_SIGNALS:
2467 void directionChanged(Direction::Type direction);
2468 void statusChanged(Status value);
2469@@ -212,6 +207,10 @@
2470 void touchYChanged(qreal value);
2471 void touchSceneXChanged(qreal value);
2472 void touchSceneYChanged(qreal value);
2473+
2474+ // TODO: I would rather not have such signal as it has nothing to do with drag gestures.
2475+ // Remove when no longer used or move its implementation to the QML code that uses it
2476+ // See maxTapDuration()
2477 void tapped();
2478
2479 protected:
2480@@ -219,7 +218,7 @@
2481
2482 private Q_SLOTS:
2483 void checkSpeed();
2484- void onEnabledChanged();
2485+ void giveUpIfDisabledOrInvisible();
2486
2487 private:
2488 void touchEvent_absent(QTouchEvent *event);
2489@@ -238,6 +237,12 @@
2490 // returns the scalar projection between the given vector (in scene coordinates)
2491 // and m_sceneDirectionVector
2492 qreal projectOntoDirectionVector(const QPointF &sceneVector) const;
2493+ void touchOwnershipEvent(TouchOwnershipEvent *event);
2494+ void unownedTouchEvent(UnownedTouchEvent *event);
2495+ void unownedTouchEvent_undecided(UnownedTouchEvent *unownedTouchEvent);
2496+ void watchPressedTouchPoints(const QList<QTouchEvent::TouchPoint> &touchPoints);
2497+ bool recognitionIsDisabled() const;
2498+ void emitSignalIfTapped();
2499
2500 Status m_status;
2501
2502@@ -283,18 +288,21 @@
2503 public:
2504 ActiveTouchesInfo(const UbuntuGestures::SharedTimeSource &timeSource);
2505 void update(QTouchEvent *event);
2506- ActiveTouchInfo &touchInfo(int id);
2507+ qint64 touchStartTime(int id);
2508+ bool isEmpty() const { return m_touchInfoPool.isEmpty(); }
2509 qint64 mostRecentStartTime();
2510 UbuntuGestures::SharedTimeSource m_timeSource;
2511- bool isEmpty() const { return m_lastUsedIndex == -1; }
2512 private:
2513- void addTouchPoint(const QTouchEvent::TouchPoint &touchPoint);
2514- ActiveTouchInfo &getEmptySlot();
2515- void freeSlot(int index);
2516- void removeTouchPoint(const QTouchEvent::TouchPoint &touchPoint);
2517- QVector<struct ActiveTouchInfo> m_vector;
2518- int m_lastUsedIndex;
2519+ void addTouchPoint(int touchId);
2520+ void removeTouchPoint(int touchId);
2521+ #if ACTIVETOUCHESINFO_DEBUG
2522+ QString toString();
2523+ #endif
2524+
2525+ Pool<ActiveTouchInfo> m_touchInfoPool;
2526 } m_activeTouches;
2527+
2528+ friend class tst_DirectionalDragArea;
2529 };
2530
2531 #endif // DIRECTIONAL_DRAG_AREA_H
2532
2533=== modified file 'plugins/Ubuntu/Gestures/TimeSource.h'
2534--- plugins/Ubuntu/Gestures/TimeSource.h 2013-10-22 15:56:37 +0000
2535+++ plugins/Ubuntu/Gestures/TimeSource.h 2014-10-10 15:05:15 +0000
2536@@ -21,14 +21,14 @@
2537 #ifndef UBUNTUGESTURES_TIMESOURCE_H
2538 #define UBUNTUGESTURES_TIMESOURCE_H
2539
2540-#include "UbuntuGesturesGlobal.h"
2541+#include "UbuntuGesturesQmlGlobal.h"
2542 #include <QSharedPointer>
2543
2544 namespace UbuntuGestures {
2545 /*
2546 Interface for a time source.
2547 */
2548-class UBUNTUGESTURES_EXPORT TimeSource {
2549+class UBUNTUGESTURESQML_EXPORT TimeSource {
2550 public:
2551 virtual ~TimeSource() {}
2552 /* Returns the current time in milliseconds since some reference time in the past. */
2553
2554=== added file 'plugins/Ubuntu/Gestures/TouchGate.cpp'
2555--- plugins/Ubuntu/Gestures/TouchGate.cpp 1970-01-01 00:00:00 +0000
2556+++ plugins/Ubuntu/Gestures/TouchGate.cpp 2014-10-10 15:05:15 +0000
2557@@ -0,0 +1,347 @@
2558+/*
2559+ * Copyright (C) 2014 Canonical, Ltd.
2560+ *
2561+ * This program is free software; you can redistribute it and/or modify
2562+ * it under the terms of the GNU General Public License as published by
2563+ * the Free Software Foundation; version 3.
2564+ *
2565+ * This program is distributed in the hope that it will be useful,
2566+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2567+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2568+ * GNU General Public License for more details.
2569+ *
2570+ * You should have received a copy of the GNU General Public License
2571+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2572+ */
2573+
2574+#include "TouchGate.h"
2575+
2576+#include <QCoreApplication>
2577+#include <QDebug>
2578+
2579+#include <TouchOwnershipEvent.h>
2580+#include <TouchRegistry.h>
2581+
2582+#pragma GCC diagnostic push
2583+#pragma GCC diagnostic ignored "-pedantic"
2584+#include <private/qquickitem_p.h>
2585+#pragma GCC diagnostic pop
2586+
2587+#if TOUCHGATE_DEBUG
2588+#include <DebugHelpers.h>
2589+#endif
2590+
2591+
2592+bool TouchGate::event(QEvent *e)
2593+{
2594+ if (e->type() == TouchOwnershipEvent::touchOwnershipEventType()) {
2595+ touchOwnershipEvent(static_cast<TouchOwnershipEvent *>(e));
2596+ return true;
2597+ } else {
2598+ return QQuickItem::event(e);
2599+ }
2600+}
2601+
2602+void TouchGate::touchEvent(QTouchEvent *event)
2603+{
2604+ #if TOUCHGATE_DEBUG
2605+ qDebug() << "[TouchGate] got touch event" << qPrintable(touchEventToString(event));
2606+ #endif
2607+ event->accept();
2608+
2609+ const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
2610+ bool goodToGo = true;
2611+ for (int i = 0; i < touchPoints.count(); ++i) {
2612+ const QTouchEvent::TouchPoint &touchPoint = touchPoints[i];
2613+
2614+ if (touchPoint.state() == Qt::TouchPointPressed) {
2615+ Q_ASSERT(!m_touchInfoMap.contains(touchPoint.id()));
2616+ m_touchInfoMap[touchPoint.id()].ownership = OwnershipRequested;
2617+ m_touchInfoMap[touchPoint.id()].ended = false;
2618+ TouchRegistry::instance()->requestTouchOwnership(touchPoint.id(), this);
2619+ }
2620+
2621+ goodToGo &= m_touchInfoMap.contains(touchPoint.id())
2622+ && m_touchInfoMap[touchPoint.id()].ownership == OwnershipGranted;
2623+
2624+ if (touchPoint.state() == Qt::TouchPointReleased && m_touchInfoMap.contains(touchPoint.id())) {
2625+ m_touchInfoMap[touchPoint.id()].ended = true;
2626+ }
2627+
2628+ }
2629+
2630+ if (goodToGo) {
2631+ if (m_storedEvents.isEmpty()) {
2632+ // let it pass through
2633+ dispatchTouchEventToTarget(event);
2634+ } else {
2635+ // Retain the event to ensure TouchGate dispatches them in order.
2636+ // Otherwise the current event would come before the stored ones, which are older.
2637+ #if TOUCHGATE_DEBUG
2638+ qDebug("[TouchGate] Storing event because thouches %s are still pending ownership.",
2639+ qPrintable(oldestPendingTouchIdsString()));
2640+ #endif
2641+ storeTouchEvent(event);
2642+ }
2643+ } else {
2644+ // Retain events that have unowned touches
2645+ storeTouchEvent(event);
2646+ }
2647+}
2648+
2649+void TouchGate::touchOwnershipEvent(TouchOwnershipEvent *event)
2650+{
2651+ // TODO: Optimization: batch those actions as TouchOwnershipEvents
2652+ // might come one right after the other.
2653+
2654+ Q_ASSERT(m_touchInfoMap.contains(event->touchId()));
2655+
2656+ TouchInfo &touchInfo = m_touchInfoMap[event->touchId()];
2657+
2658+ if (event->gained()) {
2659+ #if TOUCHGATE_DEBUG
2660+ qDebug() << "[TouchGate] Got ownership of touch " << event->touchId();
2661+ #endif
2662+ touchInfo.ownership = OwnershipGranted;
2663+ } else {
2664+ #if TOUCHGATE_DEBUG
2665+ qDebug() << "[TouchGate] Lost ownership of touch " << event->touchId();
2666+ #endif
2667+ m_touchInfoMap.remove(event->touchId());
2668+ removeTouchFromStoredEvents(event->touchId());
2669+ }
2670+
2671+ dispatchFullyOwnedEvents();
2672+}
2673+
2674+bool TouchGate::isTouchPointOwned(int touchId) const
2675+{
2676+ return m_touchInfoMap[touchId].ownership == OwnershipGranted;
2677+}
2678+
2679+void TouchGate::storeTouchEvent(const QTouchEvent *event)
2680+{
2681+ #if TOUCHGATE_DEBUG
2682+ qDebug() << "[TouchGate] Storing" << qPrintable(touchEventToString(event));
2683+ #endif
2684+
2685+ TouchEvent clonedEvent(event);
2686+ m_storedEvents.append(std::move(clonedEvent));
2687+}
2688+
2689+void TouchGate::removeTouchFromStoredEvents(int touchId)
2690+{
2691+ int i = 0;
2692+ while (i < m_storedEvents.count()) {
2693+ TouchEvent &event = m_storedEvents[i];
2694+ bool removed = event.removeTouch(touchId);
2695+
2696+ if (removed && event.touchPoints.isEmpty()) {
2697+ m_storedEvents.removeAt(i);
2698+ } else {
2699+ ++i;
2700+ }
2701+ }
2702+}
2703+
2704+void TouchGate::dispatchFullyOwnedEvents()
2705+{
2706+ while (!m_storedEvents.isEmpty() && eventIsFullyOwned(m_storedEvents.first())) {
2707+ TouchEvent event = m_storedEvents.takeFirst();
2708+ dispatchTouchEventToTarget(event);
2709+ }
2710+}
2711+
2712+#if TOUCHGATE_DEBUG
2713+QString TouchGate::oldestPendingTouchIdsString()
2714+{
2715+ Q_ASSERT(!m_storedEvents.isEmpty());
2716+
2717+ QString str;
2718+
2719+ const auto &touchPoints = m_storedEvents.first().touchPoints;
2720+ for (int i = 0; i < touchPoints.count(); ++i) {
2721+ if (!isTouchPointOwned(touchPoints[i].id())) {
2722+ if (!str.isEmpty()) {
2723+ str.append(", ");
2724+ }
2725+ str.append(QString::number(touchPoints[i].id()));
2726+ }
2727+ }
2728+
2729+ return str;
2730+}
2731+#endif
2732+
2733+bool TouchGate::eventIsFullyOwned(const TouchGate::TouchEvent &event) const
2734+{
2735+ for (int i = 0; i < event.touchPoints.count(); ++i) {
2736+ if (!isTouchPointOwned(event.touchPoints[i].id())) {
2737+ return false;
2738+ }
2739+ }
2740+
2741+ return true;
2742+}
2743+
2744+void TouchGate::setTargetItem(QQuickItem *item)
2745+{
2746+ // TODO: changing the target item while dispatch of touch events is taking place will
2747+ // create a mess
2748+
2749+ if (item == m_targetItem.data())
2750+ return;
2751+
2752+ m_targetItem = item;
2753+ Q_EMIT targetItemChanged(item);
2754+}
2755+
2756+void TouchGate::dispatchTouchEventToTarget(const TouchEvent &event)
2757+{
2758+ dispatchTouchEventToTarget(event.eventType,
2759+ event.device,
2760+ event.modifiers,
2761+ event.touchPoints,
2762+ event.target,
2763+ event.window,
2764+ event.timestamp);
2765+}
2766+
2767+void TouchGate::dispatchTouchEventToTarget(QTouchEvent* event)
2768+{
2769+ dispatchTouchEventToTarget(event->type(),
2770+ event->device(),
2771+ event->modifiers(),
2772+ event->touchPoints(),
2773+ event->target(),
2774+ event->window(),
2775+ event->timestamp());
2776+}
2777+
2778+void TouchGate::dispatchTouchEventToTarget(QEvent::Type eventType,
2779+ QTouchDevice *device,
2780+ Qt::KeyboardModifiers modifiers,
2781+ const QList<QTouchEvent::TouchPoint> &touchPoints,
2782+ QObject *target,
2783+ QWindow *window,
2784+ ulong timestamp)
2785+{
2786+ removeTouchInfoForEndedTouches(touchPoints);
2787+
2788+ if (m_targetItem.isNull()) {
2789+ qWarning("[TouchGate] Cannot dispatch touch event because target item is null");
2790+ return;
2791+ }
2792+
2793+ QQuickItem *targetItem = m_targetItem.data();
2794+
2795+ if (!targetItem->isEnabled() || !targetItem->isVisible()) {
2796+ #if TOUCHGATE_DEBUG
2797+ qDebug() << "[TouchGate] Cannot dispatch touch event to" << targetItem
2798+ << "because it's disabled or invisible.";
2799+ #endif
2800+ return;
2801+ }
2802+
2803+ // Map touch points to targetItem coordinates
2804+ QList<QTouchEvent::TouchPoint> targetTouchPoints = touchPoints;
2805+ transformTouchPoints(targetTouchPoints, QQuickItemPrivate::get(targetItem)->windowToItemTransform());
2806+ QTouchEvent *eventForTargetItem = createQTouchEvent(eventType, device, modifiers, targetTouchPoints,
2807+ target, window, timestamp);
2808+
2809+ #if TOUCHGATE_DEBUG
2810+ qDebug() << "[TouchGate] dispatching" << qPrintable(touchEventToString(eventForTargetItem))
2811+ << "to" << targetItem;
2812+ #endif
2813+
2814+ QCoreApplication::sendEvent(targetItem, eventForTargetItem);
2815+
2816+ delete eventForTargetItem;
2817+}
2818+
2819+// NB: From QQuickWindow
2820+void TouchGate::transformTouchPoints(QList<QTouchEvent::TouchPoint> &touchPoints, const QTransform &transform)
2821+{
2822+ QMatrix4x4 transformMatrix(transform);
2823+ for (int i=0; i<touchPoints.count(); i++) {
2824+ QTouchEvent::TouchPoint &touchPoint = touchPoints[i];
2825+ touchPoint.setRect(transform.mapRect(touchPoint.sceneRect()));
2826+ touchPoint.setStartPos(transform.map(touchPoint.startScenePos()));
2827+ touchPoint.setLastPos(transform.map(touchPoint.lastScenePos()));
2828+ touchPoint.setVelocity(transformMatrix.mapVector(touchPoint.velocity()).toVector2D());
2829+ }
2830+}
2831+
2832+QTouchEvent *TouchGate::createQTouchEvent(QEvent::Type eventType,
2833+ QTouchDevice *device,
2834+ Qt::KeyboardModifiers modifiers,
2835+ const QList<QTouchEvent::TouchPoint> &touchPoints,
2836+ QObject *target,
2837+ QWindow *window,
2838+ ulong timestamp)
2839+{
2840+ Qt::TouchPointStates eventStates = 0;
2841+ for (int i = 0; i < touchPoints.count(); i++)
2842+ eventStates |= touchPoints[i].state();
2843+ // if all points have the same state, set the event type accordingly
2844+ switch (eventStates) {
2845+ case Qt::TouchPointPressed:
2846+ eventType = QEvent::TouchBegin;
2847+ break;
2848+ case Qt::TouchPointReleased:
2849+ eventType = QEvent::TouchEnd;
2850+ break;
2851+ default:
2852+ eventType = QEvent::TouchUpdate;
2853+ break;
2854+ }
2855+
2856+ QTouchEvent *touchEvent = new QTouchEvent(eventType);
2857+ touchEvent->setWindow(window);
2858+ touchEvent->setTarget(target);
2859+ touchEvent->setDevice(device);
2860+ touchEvent->setModifiers(modifiers);
2861+ touchEvent->setTouchPoints(touchPoints);
2862+ touchEvent->setTouchPointStates(eventStates);
2863+ touchEvent->setTimestamp(timestamp);
2864+ touchEvent->accept();
2865+ return touchEvent;
2866+}
2867+
2868+void TouchGate::removeTouchInfoForEndedTouches(const QList<QTouchEvent::TouchPoint> &touchPoints)
2869+{
2870+ for (int i = 0; i < touchPoints.size(); ++i) {\
2871+ const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
2872+
2873+ if (touchPoint.state() == Qt::TouchPointReleased) {
2874+ Q_ASSERT(m_touchInfoMap.contains(touchPoint.id()));
2875+ Q_ASSERT(m_touchInfoMap[touchPoint.id()].ended);
2876+ Q_ASSERT(m_touchInfoMap[touchPoint.id()].ownership == OwnershipGranted);
2877+ m_touchInfoMap.remove(touchPoint.id());
2878+ }
2879+ }
2880+}
2881+
2882+TouchGate::TouchEvent::TouchEvent(const QTouchEvent *event)
2883+ : eventType(event->type())
2884+ , device(event->device())
2885+ , modifiers(event->modifiers())
2886+ , touchPoints(event->touchPoints())
2887+ , target(event->target())
2888+ , window(event->window())
2889+ , timestamp(event->timestamp())
2890+{
2891+}
2892+
2893+bool TouchGate::TouchEvent::removeTouch(int touchId)
2894+{
2895+ bool removed = false;
2896+ for (int i = 0; i < touchPoints.count() && !removed; ++i) {
2897+ if (touchPoints[i].id() == touchId) {
2898+ touchPoints.removeAt(i);
2899+ removed = true;
2900+ }
2901+ }
2902+
2903+ return removed;
2904+}
2905
2906=== added file 'plugins/Ubuntu/Gestures/TouchGate.h'
2907--- plugins/Ubuntu/Gestures/TouchGate.h 1970-01-01 00:00:00 +0000
2908+++ plugins/Ubuntu/Gestures/TouchGate.h 2014-10-10 15:05:15 +0000
2909@@ -0,0 +1,126 @@
2910+/*
2911+ * Copyright (C) 2014 Canonical, Ltd.
2912+ *
2913+ * This program is free software; you can redistribute it and/or modify
2914+ * it under the terms of the GNU General Public License as published by
2915+ * the Free Software Foundation; version 3.
2916+ *
2917+ * This program is distributed in the hope that it will be useful,
2918+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2919+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2920+ * GNU General Public License for more details.
2921+ *
2922+ * You should have received a copy of the GNU General Public License
2923+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2924+ */
2925+
2926+#ifndef UBUNTU_TOUCH_GATE_H
2927+#define UBUNTU_TOUCH_GATE_H
2928+
2929+#include "UbuntuGesturesQmlGlobal.h"
2930+
2931+#include <QQuickItem>
2932+#include <QList>
2933+#include <QPointer>
2934+#include <QMap>
2935+
2936+#define TOUCHGATE_DEBUG 0
2937+
2938+class TouchOwnershipEvent;
2939+
2940+/*
2941+ Blocks the passage of events until ownership over the related touch points is granted.
2942+
2943+ Blocked touch events won't be discarded. Instead they will be buffered until ownership
2944+ is granted. If ownership is given to another item, the event buffer is cleared.
2945+
2946+ A TouchGate is useful as a mediator for items that do not understand, or gracefully handle,
2947+ touch canceling. By having a TouchGate in front of them you guarantee that only owned touches (i.e.,
2948+ touches that won't be canceled later) reaches them.
2949+ */
2950+class UBUNTUGESTURESQML_EXPORT TouchGate : public QQuickItem {
2951+ Q_OBJECT
2952+
2953+ // Item that's going to receive the touch events that make it through the gate.
2954+ Q_PROPERTY(QQuickItem* targetItem READ targetItem WRITE setTargetItem NOTIFY targetItemChanged)
2955+
2956+public:
2957+ bool event(QEvent *e) override;
2958+
2959+ QQuickItem *targetItem() { return m_targetItem; }
2960+ void setTargetItem(QQuickItem *item);
2961+
2962+Q_SIGNALS:
2963+ void targetItemChanged(QQuickItem *item);
2964+
2965+protected:
2966+ void touchEvent(QTouchEvent *event) override;
2967+private:
2968+ class TouchEvent {
2969+ public:
2970+ TouchEvent(const QTouchEvent *event);
2971+
2972+ bool removeTouch(int touchId);
2973+
2974+ QEvent::Type eventType;
2975+ QTouchDevice *device;
2976+ Qt::KeyboardModifiers modifiers;
2977+ QList<QTouchEvent::TouchPoint> touchPoints;
2978+ QObject *target;
2979+ QWindow *window;
2980+ ulong timestamp;
2981+ };
2982+
2983+ void touchOwnershipEvent(TouchOwnershipEvent *event);
2984+ bool isTouchPointOwned(int touchId) const;
2985+ void storeTouchEvent(const QTouchEvent *event);
2986+ void removeTouchFromStoredEvents(int touchId);
2987+ void dispatchFullyOwnedEvents();
2988+ bool eventIsFullyOwned(const TouchEvent &event) const;
2989+
2990+ void dispatchTouchEventToTarget(const TouchEvent &event);
2991+ void dispatchTouchEventToTarget(QTouchEvent* event);
2992+ void dispatchTouchEventToTarget(
2993+ QEvent::Type eventType,
2994+ QTouchDevice *device,
2995+ Qt::KeyboardModifiers modifiers,
2996+ const QList<QTouchEvent::TouchPoint> &touchPoints,
2997+ QObject *target,
2998+ QWindow *window,
2999+ ulong timestamp);
3000+
3001+ void transformTouchPoints(QList<QTouchEvent::TouchPoint> &touchPoints, const QTransform &transform);
3002+ static QTouchEvent *createQTouchEvent(QEvent::Type eventType,
3003+ QTouchDevice *device,
3004+ Qt::KeyboardModifiers modifiers,
3005+ const QList<QTouchEvent::TouchPoint> &touchPoints,
3006+ QObject *target,
3007+ QWindow *window,
3008+ ulong timestamp);
3009+ void removeTouchInfoForEndedTouches(const QList<QTouchEvent::TouchPoint> &touchPoints);
3010+
3011+ #if TOUCHGATE_DEBUG
3012+ QString oldestPendingTouchIdsString();
3013+ #endif
3014+
3015+ QList<TouchEvent> m_storedEvents;
3016+
3017+ enum {
3018+ OwnershipUndefined,
3019+ OwnershipRequested,
3020+ OwnershipGranted,
3021+ };
3022+ class TouchInfo {
3023+ public:
3024+ TouchInfo() {ownership = OwnershipUndefined; ended = false;}
3025+ int ownership;
3026+ bool ended;
3027+ };
3028+ QMap<int, TouchInfo> m_touchInfoMap;
3029+
3030+ QPointer<QQuickItem> m_targetItem;
3031+
3032+ friend class tst_TouchGate;
3033+};
3034+
3035+#endif // UBUNTU_TOUCH_GATE_H
3036
3037=== renamed file 'plugins/Ubuntu/Gestures/UbuntuGesturesGlobal.h' => 'plugins/Ubuntu/Gestures/UbuntuGesturesQmlGlobal.h'
3038--- plugins/Ubuntu/Gestures/UbuntuGesturesGlobal.h 2013-06-05 22:03:08 +0000
3039+++ plugins/Ubuntu/Gestures/UbuntuGesturesQmlGlobal.h 2014-10-10 15:05:15 +0000
3040@@ -16,8 +16,8 @@
3041
3042 #include <QtCore/QtGlobal>
3043
3044-#if defined(UBUNTUGESTURES_LIBRARY)
3045-# define UBUNTUGESTURES_EXPORT Q_DECL_EXPORT
3046+#if defined(UBUNTUGESTURESQML_LIBRARY)
3047+# define UBUNTUGESTURESQML_EXPORT Q_DECL_EXPORT
3048 #else
3049-# define UBUNTUGESTURES_EXPORT Q_DECL_IMPORT
3050+# define UBUNTUGESTURESQML_EXPORT Q_DECL_IMPORT
3051 #endif
3052
3053=== modified file 'plugins/Ubuntu/Gestures/plugin.cpp'
3054--- plugins/Ubuntu/Gestures/plugin.cpp 2013-11-22 13:43:40 +0000
3055+++ plugins/Ubuntu/Gestures/plugin.cpp 2014-10-10 15:05:15 +0000
3056@@ -19,6 +19,7 @@
3057 #include "Direction.h"
3058 #include "DirectionalDragArea.h"
3059 #include "PressedOutsideNotifier.h"
3060+#include "TouchGate.h"
3061
3062 #include <qqml.h>
3063
3064@@ -28,10 +29,11 @@
3065 return new Direction;
3066 }
3067
3068-void UbuntuGestureQmlPlugin::registerTypes(const char *uri)
3069+void UbuntuGesturesQmlPlugin::registerTypes(const char *uri)
3070 {
3071 qmlRegisterSingletonType<Direction>(uri, 0, 1, "Direction", directionSingleton);
3072 qmlRegisterType<DirectionalDragArea>(uri, 0, 1, "DirectionalDragArea");
3073 qmlRegisterType<AxisVelocityCalculator>(uri, 0, 1, "AxisVelocityCalculator");
3074 qmlRegisterType<PressedOutsideNotifier>(uri, 0, 1, "PressedOutsideNotifier");
3075+ qmlRegisterType<TouchGate>(uri, 0, 1, "TouchGate");
3076 }
3077
3078=== modified file 'plugins/Ubuntu/Gestures/plugin.h'
3079--- plugins/Ubuntu/Gestures/plugin.h 2013-06-05 22:03:08 +0000
3080+++ plugins/Ubuntu/Gestures/plugin.h 2014-10-10 15:05:15 +0000
3081@@ -19,7 +19,7 @@
3082
3083 #include <QtQml/QQmlExtensionPlugin>
3084
3085-class UbuntuGestureQmlPlugin : public QQmlExtensionPlugin
3086+class UbuntuGesturesQmlPlugin : public QQmlExtensionPlugin
3087 {
3088 Q_OBJECT
3089 Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
3090
3091=== modified file 'plugins/Ubuntu/Gestures/qmldir'
3092--- plugins/Ubuntu/Gestures/qmldir 2014-05-02 22:57:00 +0000
3093+++ plugins/Ubuntu/Gestures/qmldir 2014-10-10 15:05:15 +0000
3094@@ -1,3 +1,3 @@
3095 module Ubuntu.Gestures
3096-plugin UbuntuGestureQml
3097+plugin UbuntuGesturesQml
3098 typeinfo Gestures.qmltypes
3099
3100=== modified file 'qml/Components/DragHandle.qml'
3101--- qml/Components/DragHandle.qml 2014-02-12 18:51:36 +0000
3102+++ qml/Components/DragHandle.qml 2014-10-10 15:05:15 +0000
3103@@ -47,12 +47,12 @@
3104 id: dragArea
3105 objectName: "dragHandle"
3106
3107- // Disable most of the gesture recognition parameters by default when hinting is used as
3108+ // Disable gesture recognition by default when hinting is used as
3109 // it conflicts with the hinting idea.
3110- // The only part we keep in this situation is that it must be a single-finger gesture.
3111 distanceThreshold: hintDisplacement > 0 ? 0 : defaultDistanceThreshold
3112 maxSilenceTime: hintDisplacement > 0 ? 60*60*1000 : defaultMaxSilenceTime
3113 maxDeviation: hintDisplacement > 0 ? 999999 : defaultMaxDeviation
3114+ compositionTime: hintDisplacement > 0 ? 0 : defaultCompositionTime
3115
3116 property bool stretch: false
3117
3118
3119=== modified file 'qml/Components/EdgeDragArea.qml'
3120--- qml/Components/EdgeDragArea.qml 2013-10-22 15:56:37 +0000
3121+++ qml/Components/EdgeDragArea.qml 2014-10-10 15:05:15 +0000
3122@@ -34,6 +34,7 @@
3123 distanceThreshold: defaultDistanceThreshold
3124 minSpeed: defaultMinSpeed
3125 maxSilenceTime: defaultMaxSilenceTime
3126+ compositionTime: defaultCompositionTime
3127
3128 readonly property real defaultMaxDeviation: units.gu(3)
3129 readonly property real defaultWideningAngle: 50
3130@@ -41,4 +42,5 @@
3131 // some people were getting false negatives with it enabled.
3132 readonly property real defaultMinSpeed: units.gu(0)
3133 readonly property int defaultMaxSilenceTime: 200
3134+ readonly property int defaultCompositionTime: 60
3135 }
3136
3137=== modified file 'qml/Components/InputMethod.qml'
3138--- qml/Components/InputMethod.qml 2014-07-23 18:25:53 +0000
3139+++ qml/Components/InputMethod.qml 2014-10-10 15:05:15 +0000
3140@@ -17,6 +17,7 @@
3141 import QtQuick 2.0
3142 import Unity.Application 0.1
3143 import Ubuntu.Components 0.1
3144+import Ubuntu.Gestures 0.1
3145
3146 Item {
3147 id: root
3148@@ -25,6 +26,18 @@
3149
3150 property int transitionDuration: UbuntuAnimation.FastDuration
3151
3152+ Binding { target: surface; property: "z"; value: 0 }
3153+
3154+ TouchGate {
3155+ x: UbuntuKeyboardInfo.x
3156+ y: UbuntuKeyboardInfo.y
3157+ z: 1
3158+ width: UbuntuKeyboardInfo.width
3159+ height: UbuntuKeyboardInfo.height
3160+
3161+ targetItem: root.surface
3162+ }
3163+
3164 state: {
3165 if (surface && surface.state != MirSurfaceItem.Minimized) {
3166 return "shown";
3167
3168=== modified file 'qml/Dash/Dash.qml'
3169--- qml/Dash/Dash.qml 2014-10-09 14:04:53 +0000
3170+++ qml/Dash/Dash.qml 2014-10-10 15:05:15 +0000
3171@@ -207,6 +207,12 @@
3172 }
3173 }
3174
3175+ // This is to avoid the situation where a bottom-edge swipe would bring up the dash overview
3176+ // (as expected) but would also cause the dash content flickable to move a bit, because
3177+ // that flickable was getting the touch events while overviewDragHandle was still undecided
3178+ // about whether that touch was indeed performing a directional drag gesture.
3179+ forceNonInteractive: overviewDragHandle.status != DirectionalDragArea.WaitingForTouch
3180+
3181 enabled: overviewController.progress == 0
3182 opacity: enabled ? 1 : 0
3183 }
3184@@ -363,16 +369,32 @@
3185 height: units.gu(2)
3186
3187 onSceneDistanceChanged: {
3188- if (overviewController.enableAnimation) {
3189- dashContentCache.scheduleUpdate();
3190+ if (status == DirectionalDragArea.Recognized && initialSceneDistance != -1) {
3191+ if (overviewController.enableAnimation) {
3192+ dashContentCache.scheduleUpdate();
3193+ }
3194+ overviewController.enableAnimation = false;
3195+ var deltaDistance = sceneDistance - initialSceneDistance;
3196+ overviewController.progress = Math.max(0, Math.min(1, deltaDistance / fullMovement));
3197 }
3198- overviewController.enableAnimation = false;
3199- overviewController.progress = Math.max(0, Math.min(1, sceneDistance / fullMovement));
3200 }
3201
3202- onDraggingChanged: {
3203- overviewController.enableAnimation = true;
3204- overviewController.progress = (overviewController.progress > 0.7) ? 1 : 0;
3205+ property int previousStatus: -1
3206+ property int currentStatus: DirectionalDragArea.WaitingForTouch
3207+ property real initialSceneDistance: -1
3208+
3209+ onStatusChanged: {
3210+ previousStatus = currentStatus;
3211+ currentStatus = status;
3212+
3213+ if (status == DirectionalDragArea.Recognized) {
3214+ initialSceneDistance = sceneDistance;
3215+ } else if (status == DirectionalDragArea.WaitingForTouch &&
3216+ previousStatus == DirectionalDragArea.Recognized) {
3217+ overviewController.enableAnimation = true;
3218+ overviewController.progress = (overviewController.progress > 0.7) ? 1 : 0;
3219+ initialSceneDistance = -1;
3220+ }
3221 }
3222 }
3223
3224
3225=== modified file 'qml/Dash/DashContent.qml'
3226--- qml/Dash/DashContent.qml 2014-09-22 14:24:34 +0000
3227+++ qml/Dash/DashContent.qml 2014-10-10 15:05:15 +0000
3228@@ -23,6 +23,7 @@
3229 Item {
3230 id: dashContent
3231
3232+ property bool forceNonInteractive: false
3233 property alias scopes: dashContentList.model
3234 readonly property alias currentIndex: dashContentList.currentIndex
3235 readonly property string currentScopeId: dashContentList.currentItem ? dashContentList.currentItem.scopeId : ""
3236@@ -101,7 +102,8 @@
3237 id: dashContentList
3238 objectName: "dashContentList"
3239
3240- interactive: dashContent.scopes.loaded && currentItem && !currentItem.moving && !currentItem.navigationShown && !currentItem.subPageShown
3241+ interactive: !dashContent.forceNonInteractive && dashContent.scopes.loaded && currentItem
3242+ && !currentItem.moving && !currentItem.navigationShown && !currentItem.subPageShown
3243 anchors.fill: parent
3244 orientation: ListView.Horizontal
3245 boundsBehavior: Flickable.DragAndOvershootBounds
3246@@ -180,6 +182,7 @@
3247 item.paginationCount = Qt.binding(function() { return dashContentList.count } )
3248 item.paginationIndex = Qt.binding(function() { return dashContentList.currentIndex } )
3249 item.holdingList = dashContentList;
3250+ item.forceNonInteractive = Qt.binding(function() { return dashContent.forceNonInteractive } )
3251 }
3252 Connections {
3253 target: isCurrent ? scope : null
3254
3255=== modified file 'qml/Dash/GenericScopeView.qml'
3256--- qml/Dash/GenericScopeView.qml 2014-10-06 15:54:18 +0000
3257+++ qml/Dash/GenericScopeView.qml 2014-10-10 15:05:15 +0000
3258@@ -26,6 +26,7 @@
3259 id: scopeView
3260
3261 readonly property bool navigationShown: pageHeaderLoader.item ? pageHeaderLoader.item.bottomItem[0].openList : false
3262+ property bool forceNonInteractive: false
3263 property var scope: null
3264 property SortFilterProxyModel categories: categoryFilter
3265 property bool isCurrent: false
3266@@ -149,6 +150,7 @@
3267 ScopeListView {
3268 id: categoryView
3269 objectName: "categoryListView"
3270+ interactive: !forceNonInteractive
3271
3272 x: subPageLoader.open ? -width : 0
3273 visible: x != -width
3274
3275=== modified file 'qml/Launcher/Launcher.qml'
3276--- qml/Launcher/Launcher.qml 2014-10-06 16:39:10 +0000
3277+++ qml/Launcher/Launcher.qml 2014-10-10 15:05:15 +0000
3278@@ -1,5 +1,5 @@
3279 /*
3280- * Copyright (C) 2013 Canonical, Ltd.
3281+ * Copyright (C) 2013-2014 Canonical, Ltd.
3282 *
3283 * This program is free software; you can redistribute it and/or modify
3284 * it under the terms of the GNU General Public License as published by
3285@@ -76,6 +76,7 @@
3286
3287 Timer {
3288 id: dismissTimer
3289+ objectName: "dismissTimer"
3290 interval: 5000
3291 onTriggered: {
3292 if (!panel.preventHiding) {
3293@@ -218,6 +219,7 @@
3294
3295 EdgeDragArea {
3296 id: dragArea
3297+ objectName: "launcherDragArea"
3298
3299 direction: Direction.Rightwards
3300
3301
3302=== modified file 'qml/Launcher/LauncherPanel.qml'
3303--- qml/Launcher/LauncherPanel.qml 2014-10-07 08:25:59 +0000
3304+++ qml/Launcher/LauncherPanel.qml 2014-10-10 15:05:15 +0000
3305@@ -45,6 +45,7 @@
3306 }
3307
3308 Rectangle {
3309+ objectName: "buttonShowDashHome"
3310 width: parent.width
3311 height: units.gu(7)
3312 color: UbuntuColors.orange
3313
3314=== modified file 'qml/Stages/ApplicationWindow.qml'
3315--- qml/Stages/ApplicationWindow.qml 2014-09-19 11:20:07 +0000
3316+++ qml/Stages/ApplicationWindow.qml 2014-10-10 15:05:15 +0000
3317@@ -61,14 +61,6 @@
3318 // Remove this when possible
3319 property bool surfaceInitialized: false
3320
3321- function forceSurfaceActiveFocusIfReady() {
3322- if (sessionContainer.surface !== null &&
3323- sessionContainer.surface.focus &&
3324- sessionContainer.surface.parent === sessionContainer.surfaceContainer &&
3325- sessionContainer.surface.enabled) {
3326- sessionContainer.surface.forceActiveFocus();
3327- }
3328- }
3329 }
3330
3331 Timer {
3332@@ -77,15 +69,6 @@
3333 onTriggered: { if (sessionContainer.surface) {d.surfaceInitialized = true;} }
3334 }
3335
3336- Connections {
3337- target: sessionContainer.surface
3338- // FIXME: I would rather not need to do this, but currently it doesn't get
3339- // active focus without it and I don't know why.
3340- onFocusChanged: d.forceSurfaceActiveFocusIfReady();
3341- onParentChanged: d.forceSurfaceActiveFocusIfReady();
3342- onEnabledChanged: d.forceSurfaceActiveFocusIfReady();
3343- }
3344-
3345 Image {
3346 id: screenshotImage
3347 objectName: "screenshotImage"
3348@@ -132,7 +115,6 @@
3349 onSurfaceChanged: {
3350 if (sessionContainer.surface) {
3351 surfaceInitTimer.start();
3352- d.forceSurfaceActiveFocusIfReady();
3353 } else {
3354 d.surfaceInitialized = false;
3355 }
3356
3357=== modified file 'qml/Stages/PhoneStage.qml'
3358--- qml/Stages/PhoneStage.qml 2014-10-07 18:18:44 +0000
3359+++ qml/Stages/PhoneStage.qml 2014-10-10 15:05:15 +0000
3360@@ -131,7 +131,7 @@
3361
3362 // This indicates when the spreadView is active. That means, all the animations
3363 // are activated and tiles need to line up for the spread.
3364- readonly property bool active: shiftedContentX > 0 || spreadDragArea.dragging
3365+ readonly property bool active: shiftedContentX > 0 || spreadDragArea.status === DirectionalDragArea.Recognized
3366
3367 // The flickable needs to fill the screen in order to get touch events all over.
3368 // However, we don't want to the user to be able to scroll back all the way. For
3369@@ -418,6 +418,7 @@
3370
3371 EdgeDragArea {
3372 id: spreadDragArea
3373+ objectName: "spreadDragArea"
3374 direction: Direction.Leftwards
3375 enabled: spreadView.phase != 2 && root.spreadEnabled
3376
3377@@ -437,9 +438,10 @@
3378 spreadView.phase = 0;
3379 spreadView.contentX = -spreadView.shift;
3380 }
3381- if (dragging && attachedToView) {
3382+ if (dragging && status == DirectionalDragArea.Recognized && attachedToView) {
3383 // Gesture recognized. Let's move the spreadView with the finger
3384- spreadView.contentX = -touchX + spreadDragArea.width - spreadView.shift;
3385+ var finalX = Math.min(touchX + width, width);
3386+ spreadView.contentX = -finalX + spreadDragArea.width - spreadView.shift;
3387 }
3388 if (attachedToView && spreadView.shiftedContentX >= spreadView.width * spreadView.positionMarker3) {
3389 // We passed positionMarker3. Detach from spreadView and snap it.
3390@@ -449,7 +451,13 @@
3391 gesturePoints.push(touchX);
3392 }
3393
3394+ property int previousStatus: -1
3395+ property int currentStatus: DirectionalDragArea.WaitingForTouch
3396+
3397 onStatusChanged: {
3398+ previousStatus = currentStatus;
3399+ currentStatus = status;
3400+
3401 if (status == DirectionalDragArea.Recognized) {
3402 attachedToView = true;
3403 }
3404@@ -457,7 +465,7 @@
3405
3406 onDraggingChanged: {
3407 if (dragging) {
3408- // Gesture recognized. Start recording this gesture
3409+ // A potential edge-drag gesture has started. Start recording it
3410 gesturePoints = [];
3411 return;
3412 }
3413@@ -474,7 +482,8 @@
3414 }
3415 gesturePoints = [];
3416
3417- if (oneWayFlick && spreadView.shiftedContentX > units.gu(2) &&
3418+ if (previousStatus == DirectionalDragArea.Recognized &&
3419+ oneWayFlick && spreadView.shiftedContentX > units.gu(2) &&
3420 spreadView.shiftedContentX < spreadView.positionMarker1 * spreadView.width) {
3421 // If it was a short one-way movement, do the Alt+Tab switch
3422 // no matter if we didn't cross positionMarker1 yet.
3423
3424=== modified file 'qml/Stages/SessionContainer.qml'
3425--- qml/Stages/SessionContainer.qml 2014-09-11 10:41:11 +0000
3426+++ qml/Stages/SessionContainer.qml 2014-10-10 15:05:15 +0000
3427@@ -23,7 +23,7 @@
3428 property QtObject session
3429 readonly property var childSessions: session ? session.childSessions : null
3430 readonly property alias surface: _surfaceContainer.surface
3431- property bool interactive: true
3432+ property alias interactive: _surfaceContainer.interactive
3433 property int orientation
3434
3435 readonly property alias surfaceContainer: _surfaceContainer
3436@@ -34,18 +34,6 @@
3437 orientation: root.orientation
3438 }
3439
3440- Binding {
3441- target: surfaceContainer.surface
3442- when: surfaceContainer.surface
3443- property: "enabled"
3444- value: root.interactive
3445- }
3446- Binding {
3447- target: surfaceContainer.surface
3448- when: surfaceContainer.surface
3449- property: "focus"
3450- value: root.interactive
3451- }
3452
3453 Repeater {
3454 model: root.childSessions
3455
3456=== modified file 'qml/Stages/SurfaceContainer.qml'
3457--- qml/Stages/SurfaceContainer.qml 2014-09-11 10:41:11 +0000
3458+++ qml/Stages/SurfaceContainer.qml 2014-10-10 15:05:15 +0000
3459@@ -16,6 +16,7 @@
3460
3461 import QtQuick 2.0
3462 import Ubuntu.Components 1.1
3463+import Ubuntu.Gestures 0.1 // For TouchGate
3464
3465 Item {
3466 id: root
3467@@ -23,21 +24,51 @@
3468 property Item surface: null
3469 property bool hadSurface: false
3470 property int orientation
3471+ property bool interactive
3472
3473 onSurfaceChanged: {
3474 if (surface) {
3475 surface.parent = root;
3476+ d.forceSurfaceActiveFocusIfReady();
3477 } else {
3478 hadSurface = true;
3479 }
3480 }
3481- Binding {
3482- target: surface
3483- property: "anchors.fill"; value: root
3484- }
3485- Binding {
3486- target: surface
3487- property: "orientation"; value: root.orientation
3488+ Binding { target: surface; property: "anchors.fill"; value: root }
3489+ Binding { target: surface; property: "orientation"; value: root.orientation }
3490+ Binding { target: surface; property: "z"; value: 1 }
3491+ Binding { target: surface; property: "enabled"; value: root.interactive; when: surface }
3492+ Binding { target: surface; property: "focus"; value: root.interactive; when: surface }
3493+
3494+ TouchGate {
3495+ targetItem: surface
3496+ anchors.fill: root
3497+ enabled: root.surface ? root.surface.enabled : false
3498+ z: 2
3499+ }
3500+
3501+ Connections {
3502+ target: root.surface
3503+ // FIXME: I would rather not need to do this, but currently it doesn't get
3504+ // active focus without it and I don't know why.
3505+ // Possibly because if an item get focus=true before it has a parent, once
3506+ // it gets a parent QQuickWindow won't check its focus and update its activeFocus
3507+ // accordingly. Unlike when you focus=true after the item already has a parent.
3508+ onFocusChanged: d.forceSurfaceActiveFocusIfReady();
3509+ onParentChanged: d.forceSurfaceActiveFocusIfReady();
3510+ onEnabledChanged: d.forceSurfaceActiveFocusIfReady();
3511+ }
3512+
3513+ QtObject {
3514+ id: d
3515+ function forceSurfaceActiveFocusIfReady() {
3516+ if (root.surface !== null &&
3517+ root.surface.focus &&
3518+ root.surface.parent === root &&
3519+ root.surface.enabled) {
3520+ root.surface.forceActiveFocus();
3521+ }
3522+ }
3523 }
3524
3525 states: [
3526
3527=== modified file 'src/CMakeLists.txt'
3528--- src/CMakeLists.txt 2014-09-04 23:11:17 +0000
3529+++ src/CMakeLists.txt 2014-10-10 15:05:15 +0000
3530@@ -1,5 +1,6 @@
3531 include_directories(
3532 ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
3533+ ${CMAKE_SOURCE_DIR}/libs/UbuntuGestures
3534 )
3535
3536 file(GLOB_RECURSE QML_FILES
3537@@ -29,6 +30,11 @@
3538 target_link_libraries(${SHELL_APP} ${XCB_LDFLAGS})
3539 endif()
3540
3541+target_link_libraries(${SHELL_APP} UbuntuGestures)
3542+
3543+# For it to find libUbuntuGestures.so
3544+set_target_properties(${SHELL_APP} PROPERTIES INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${SHELL_PRIVATE_LIBDIR}")
3545+
3546 # install binaries
3547 install(TARGETS ${SHELL_APP}
3548 RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
3549
3550=== modified file 'src/Dash/CMakeLists.txt'
3551--- src/Dash/CMakeLists.txt 2014-09-22 15:37:31 +0000
3552+++ src/Dash/CMakeLists.txt 2014-10-10 15:05:15 +0000
3553@@ -9,6 +9,11 @@
3554
3555 qt5_use_modules(unity8-dash Gui Qml Quick Test)
3556
3557+# For it to find libUbuntuGestures.so, needed by Ubuntu.Gestures QML module.
3558+set_target_properties(unity8-dash PROPERTIES INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${SHELL_PRIVATE_LIBDIR}")
3559+
3560+target_link_libraries(unity8-dash UbuntuGestures)
3561+
3562 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden")
3563
3564 # install binaries
3565
3566=== modified file 'src/Dash/main.cpp'
3567--- src/Dash/main.cpp 2014-09-18 21:22:37 +0000
3568+++ src/Dash/main.cpp 2014-10-10 15:05:15 +0000
3569@@ -31,6 +31,9 @@
3570 #include "../ApplicationArguments.h"
3571 #include "../CachingNetworkManagerFactory.h"
3572
3573+// Ubuntu Gestures
3574+#include <TouchRegistry.h>
3575+
3576 int main(int argc, const char *argv[])
3577 {
3578 QGuiApplication *application = new QGuiApplication(argc, (char**)argv);
3579@@ -88,6 +91,9 @@
3580 view->setTitle("Scopes");
3581 view->rootContext()->setContextProperty("applicationArguments", &qmlArgs);
3582
3583+ TouchRegistry touchRegistry;
3584+ view->installEventFilter(&touchRegistry);
3585+
3586 // You will need this if you want to interact with touch-only components using a mouse
3587 // Needed only when manually testing on a desktop.
3588 MouseTouchAdaptor *mouseTouchAdaptor = 0;
3589
3590=== modified file 'src/main.cpp'
3591--- src/main.cpp 2014-09-01 14:44:30 +0000
3592+++ src/main.cpp 2014-10-10 15:05:15 +0000
3593@@ -34,6 +34,9 @@
3594 #include "ApplicationArguments.h"
3595 #include "CachingNetworkManagerFactory.h"
3596
3597+// Ubuntu Gestures
3598+#include <TouchRegistry.h>
3599+
3600 int main(int argc, const char *argv[])
3601 {
3602 bool isMirServer = false;
3603@@ -120,6 +123,8 @@
3604 if (parser.isSet(framelessOption)) {
3605 view->setFlags(Qt::FramelessWindowHint);
3606 }
3607+ TouchRegistry touchRegistry;
3608+ view->installEventFilter(&touchRegistry);
3609
3610 // You will need this if you want to interact with touch-only components using a mouse
3611 // Needed only when manually testing on a desktop.
3612
3613=== modified file 'tests/CMakeLists.txt'
3614--- tests/CMakeLists.txt 2014-05-30 09:21:39 +0000
3615+++ tests/CMakeLists.txt 2014-10-10 15:05:15 +0000
3616@@ -7,6 +7,7 @@
3617 add_custom_target(qmltests)
3618 add_dependencies(qmltests qmlunittests qmluitests)
3619
3620+add_subdirectory(libs)
3621 add_subdirectory(qmltests)
3622 add_subdirectory(mocks)
3623 add_subdirectory(whitespace)
3624
3625=== added directory 'tests/libs'
3626=== added file 'tests/libs/CMakeLists.txt'
3627--- tests/libs/CMakeLists.txt 1970-01-01 00:00:00 +0000
3628+++ tests/libs/CMakeLists.txt 2014-10-10 15:05:15 +0000
3629@@ -0,0 +1,1 @@
3630+add_subdirectory(UbuntuGestures)
3631
3632=== added directory 'tests/libs/UbuntuGestures'
3633=== added file 'tests/libs/UbuntuGestures/CMakeLists.txt'
3634--- tests/libs/UbuntuGestures/CMakeLists.txt 1970-01-01 00:00:00 +0000
3635+++ tests/libs/UbuntuGestures/CMakeLists.txt 2014-10-10 15:05:15 +0000
3636@@ -0,0 +1,20 @@
3637+include_directories(
3638+ ${CMAKE_SOURCE_DIR}/libs/UbuntuGestures
3639+ ${CMAKE_CURRENT_BINARY_DIR}
3640+ )
3641+
3642+macro(add_gesture_test CLASSNAME)
3643+ set(testCommand
3644+ ${CLASSNAME}TestExec -o ${CMAKE_BINARY_DIR}/test${CLASSNAME}.xml,xunitxml -o -,txt)
3645+
3646+ add_test(NAME test${CLASSNAME} COMMAND ${testCommand})
3647+
3648+ set_tests_properties(test${CLASSNAME} PROPERTIES
3649+ ENVIRONMENT LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/libs/UbuntuGestures)
3650+
3651+ add_executable(${CLASSNAME}TestExec tst_${CLASSNAME}.cpp)
3652+ qt5_use_modules(${CLASSNAME}TestExec Test Core Gui Quick)
3653+ target_link_libraries(${CLASSNAME}TestExec UbuntuGestures)
3654+endmacro(add_gesture_test)
3655+
3656+add_gesture_test(TouchRegistry)
3657
3658=== added file 'tests/libs/UbuntuGestures/tst_TouchRegistry.cpp'
3659--- tests/libs/UbuntuGestures/tst_TouchRegistry.cpp 1970-01-01 00:00:00 +0000
3660+++ tests/libs/UbuntuGestures/tst_TouchRegistry.cpp 2014-10-10 15:05:15 +0000
3661@@ -0,0 +1,803 @@
3662+/*
3663+ * Copyright (C) 2014 Canonical, Ltd.
3664+ *
3665+ * This program is free software; you can redistribute it and/or modify
3666+ * it under the terms of the GNU General Public License as published by
3667+ * the Free Software Foundation; version 3.
3668+ *
3669+ * This program is distributed in the hope that it will be useful,
3670+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3671+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3672+ * GNU General Public License for more details.
3673+ *
3674+ * You should have received a copy of the GNU General Public License
3675+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3676+ */
3677+
3678+#include <QtTest>
3679+#include <QSet>
3680+#include <QTouchEvent>
3681+
3682+#include <Timer.h>
3683+#include <TouchOwnershipEvent.h>
3684+#include <TouchRegistry.h>
3685+#include <UnownedTouchEvent.h>
3686+
3687+using namespace UbuntuGestures;
3688+
3689+class TouchMemento {
3690+public:
3691+ TouchMemento(const QTouchEvent *touchEvent);
3692+ Qt::TouchPointStates touchPointStates;
3693+ QList<QTouchEvent::TouchPoint> touchPoints;
3694+
3695+ bool containsTouchWithId(int touchId) const;
3696+};
3697+
3698+class DummyCandidate : public QQuickItem
3699+{
3700+ Q_OBJECT
3701+public:
3702+ bool event(QEvent *e) override;
3703+ QSet<int> ownedTouches;
3704+ QSet<int> lostTouches;
3705+ QList<TouchMemento> unownedTouchEvents;
3706+};
3707+
3708+class tst_TouchRegistry : public QObject
3709+{
3710+ Q_OBJECT
3711+public:
3712+ tst_TouchRegistry() : QObject(0) { }
3713+private Q_SLOTS:
3714+ void initTestCase() {} // will be called before the first test function is executed
3715+ void cleanupTestCase() {} // will be called after the last test function was executed.
3716+
3717+ void init() {} // called right before each and every test function is executed
3718+ void cleanup() {} // called right after each and every test function is executed
3719+
3720+ void requestWithNoCandidates();
3721+ void lateCandidateRequestGetsNothing();
3722+ void lateCandidateGestOwnershipOnceEarlyCandidateQuits();
3723+ void dispatchesTouchEventsToCandidates();
3724+ void dispatchesTouchEventsToWatchers();
3725+ void keepDispatchingToWatchersAfterLastCandidateGivesUp();
3726+ void candidatesAndWatchers_1();
3727+ void candidatesAndWatchers_2();
3728+ void rejectingTouchfterItsEnd();
3729+ void removeOldUndecidedCandidates();
3730+};
3731+
3732+void tst_TouchRegistry::requestWithNoCandidates()
3733+{
3734+ TouchRegistry touchRegistry;
3735+ DummyCandidate candidate;
3736+
3737+ {
3738+ QList<QTouchEvent::TouchPoint> touchPoints;
3739+ touchPoints.append(QTouchEvent::TouchPoint(0));
3740+ touchPoints[0].setState(Qt::TouchPointPressed);
3741+ QTouchEvent touchEvent(QEvent::TouchBegin,
3742+ 0 /* device */,
3743+ Qt::NoModifier,
3744+ Qt::TouchPointPressed,
3745+ touchPoints);
3746+ touchRegistry.update(&touchEvent);
3747+ }
3748+
3749+ touchRegistry.requestTouchOwnership(0, &candidate);
3750+
3751+ QVERIFY(candidate.ownedTouches.contains(0));
3752+}
3753+
3754+void tst_TouchRegistry::lateCandidateRequestGetsNothing()
3755+{
3756+ TouchRegistry touchRegistry;
3757+ DummyCandidate earlyCandidate;
3758+ earlyCandidate.setObjectName("early");
3759+ DummyCandidate lateCandidate;
3760+ lateCandidate.setObjectName("late");
3761+
3762+ {
3763+ QList<QTouchEvent::TouchPoint> touchPoints;
3764+ touchPoints.append(QTouchEvent::TouchPoint(0));
3765+ touchPoints[0].setState(Qt::TouchPointPressed);
3766+ QTouchEvent touchEvent(QEvent::TouchBegin,
3767+ 0 /* device */,
3768+ Qt::NoModifier,
3769+ Qt::TouchPointPressed,
3770+ touchPoints);
3771+ touchRegistry.update(&touchEvent);
3772+ }
3773+
3774+ touchRegistry.addCandidateOwnerForTouch(0, &earlyCandidate);
3775+ touchRegistry.addCandidateOwnerForTouch(0, &lateCandidate);
3776+
3777+ QVERIFY(earlyCandidate.ownedTouches.isEmpty());
3778+ QVERIFY(lateCandidate.ownedTouches.isEmpty());
3779+
3780+ touchRegistry.requestTouchOwnership(0, &lateCandidate);
3781+
3782+ QVERIFY(earlyCandidate.ownedTouches.isEmpty());
3783+ QVERIFY(lateCandidate.ownedTouches.isEmpty());
3784+
3785+ touchRegistry.requestTouchOwnership(0, &earlyCandidate);
3786+
3787+ QVERIFY(earlyCandidate.ownedTouches.contains(0));
3788+ QVERIFY(lateCandidate.ownedTouches.isEmpty());
3789+}
3790+
3791+void tst_TouchRegistry::lateCandidateGestOwnershipOnceEarlyCandidateQuits()
3792+{
3793+ TouchRegistry touchRegistry;
3794+ DummyCandidate earlyCandidate;
3795+ DummyCandidate lateCandidate;
3796+
3797+ {
3798+ QList<QTouchEvent::TouchPoint> touchPoints;
3799+ touchPoints.append(QTouchEvent::TouchPoint(0));
3800+ touchPoints[0].setState(Qt::TouchPointPressed);
3801+ QTouchEvent touchEvent(QEvent::TouchBegin,
3802+ 0 /* device */,
3803+ Qt::NoModifier,
3804+ Qt::TouchPointPressed,
3805+ touchPoints);
3806+ touchRegistry.update(&touchEvent);
3807+ }
3808+
3809+ touchRegistry.addCandidateOwnerForTouch(0, &earlyCandidate);
3810+ touchRegistry.addCandidateOwnerForTouch(0, &lateCandidate);
3811+
3812+ QVERIFY(earlyCandidate.ownedTouches.isEmpty());
3813+ QVERIFY(lateCandidate.ownedTouches.isEmpty());
3814+
3815+ touchRegistry.requestTouchOwnership(0, &lateCandidate);
3816+
3817+ QVERIFY(earlyCandidate.ownedTouches.isEmpty());
3818+ QVERIFY(lateCandidate.ownedTouches.isEmpty());
3819+
3820+ touchRegistry.removeCandidateOwnerForTouch(0, &earlyCandidate);
3821+
3822+ QVERIFY(earlyCandidate.ownedTouches.isEmpty());
3823+ QVERIFY(lateCandidate.ownedTouches.contains(0));
3824+}
3825+
3826+void tst_TouchRegistry::dispatchesTouchEventsToCandidates()
3827+{
3828+ TouchRegistry touchRegistry;
3829+ QQuickItem rootItem;
3830+
3831+ DummyCandidate candidate0;
3832+ candidate0.setObjectName("0");
3833+ candidate0.setParentItem(&rootItem);
3834+ candidate0.setX(1);
3835+ candidate0.setY(2);
3836+
3837+ DummyCandidate candidate1;
3838+ candidate1.setObjectName("1");
3839+ candidate0.setParentItem(&rootItem);
3840+ candidate1.setX(3);
3841+ candidate1.setY(4);
3842+
3843+ {
3844+ QList<QTouchEvent::TouchPoint> touchPoints;
3845+ touchPoints.append(QTouchEvent::TouchPoint(0));
3846+ touchPoints[0].setState(Qt::TouchPointPressed);
3847+ touchPoints[0].setRect(QRect(10, 10, 0, 0));
3848+ QTouchEvent touchEvent(QEvent::TouchBegin,
3849+ 0 /* device */,
3850+ Qt::NoModifier,
3851+ Qt::TouchPointPressed,
3852+ touchPoints);
3853+ touchRegistry.update(&touchEvent);
3854+ }
3855+
3856+ touchRegistry.addCandidateOwnerForTouch(0, &candidate0);
3857+
3858+ {
3859+ QList<QTouchEvent::TouchPoint> touchPoints;
3860+ touchPoints.append(QTouchEvent::TouchPoint(0));
3861+ touchPoints[0].setState(Qt::TouchPointMoved);
3862+ touchPoints[0].setRect(QRect(11, 11, 0, 0));
3863+ touchPoints.append(QTouchEvent::TouchPoint(1));
3864+ touchPoints[1].setState(Qt::TouchPointPressed);
3865+ touchPoints[1].setRect(QRect(20, 20, 0, 0));
3866+ QTouchEvent touchEvent(QEvent::TouchUpdate,
3867+ 0 /* device */,
3868+ Qt::NoModifier,
3869+ Qt::TouchPointPressed | Qt::TouchPointMoved,
3870+ touchPoints);
3871+ touchRegistry.update(&touchEvent);
3872+ }
3873+
3874+ // candidate0 should have received an update on the touch he's interested on.
3875+ QCOMPARE(candidate0.unownedTouchEvents.count(), 1);
3876+ // make sure only touch 0 is there (i.e. no mention of touch 1)
3877+ QCOMPARE(candidate0.unownedTouchEvents[0].touchPoints.count(), 1);
3878+ QCOMPARE(candidate0.unownedTouchEvents[0].touchPoints[0].id(), 0);
3879+ // Check that the points local coordinates have been mapped to candidate0's coordinate system.
3880+ QCOMPARE(candidate0.unownedTouchEvents[0].touchPoints[0].rect().x(), 11 - candidate0.x());
3881+ QCOMPARE(candidate0.unownedTouchEvents[0].touchPoints[0].rect().y(), 11 - candidate0.y());
3882+
3883+ touchRegistry.addCandidateOwnerForTouch(1, &candidate1);
3884+
3885+ {
3886+ QList<QTouchEvent::TouchPoint> touchPoints;
3887+ touchPoints.append(QTouchEvent::TouchPoint(0));
3888+ touchPoints[0].setState(Qt::TouchPointMoved);
3889+ touchPoints[0].setRect(QRect(12, 12, 0, 0));
3890+ touchPoints.append(QTouchEvent::TouchPoint(1));
3891+ touchPoints[1].setState(Qt::TouchPointMoved);
3892+ touchPoints[1].setRect(QRect(21, 21, 0, 0));
3893+ QTouchEvent touchEvent(QEvent::TouchUpdate,
3894+ 0 /* device */,
3895+ Qt::NoModifier,
3896+ Qt::TouchPointPressed | Qt::TouchPointMoved,
3897+ touchPoints);
3898+ touchRegistry.update(&touchEvent);
3899+ }
3900+
3901+ // candidate0 gets updates only for touch 0 and
3902+ // candidate1 gets updates only for touch 1
3903+
3904+ QCOMPARE(candidate0.unownedTouchEvents.count(), 2);
3905+ QCOMPARE(candidate0.unownedTouchEvents[1].touchPoints.count(), 1);
3906+ QCOMPARE(candidate0.unownedTouchEvents[1].touchPoints[0].id(), 0);
3907+
3908+ QCOMPARE(candidate1.unownedTouchEvents.count(), 1);
3909+ QCOMPARE(candidate1.unownedTouchEvents[0].touchPoints.count(), 1);
3910+ QCOMPARE(candidate1.unownedTouchEvents[0].touchPoints[0].id(), 1);
3911+}
3912+
3913+void tst_TouchRegistry::dispatchesTouchEventsToWatchers()
3914+{
3915+ TouchRegistry touchRegistry;
3916+ QQuickItem rootItem;
3917+
3918+ DummyCandidate watcher;
3919+ watcher.setObjectName("watcher");
3920+ watcher.setParentItem(&rootItem);
3921+ watcher.setX(1);
3922+ watcher.setY(2);
3923+
3924+ {
3925+ QList<QTouchEvent::TouchPoint> touchPoints;
3926+ touchPoints.append(QTouchEvent::TouchPoint(0));
3927+ touchPoints[0].setState(Qt::TouchPointPressed);
3928+ touchPoints[0].setRect(QRect(10, 10, 0, 0));
3929+ QTouchEvent touchEvent(QEvent::TouchBegin,
3930+ 0 /* device */,
3931+ Qt::NoModifier,
3932+ Qt::TouchPointPressed,
3933+ touchPoints);
3934+ touchRegistry.update(&touchEvent);
3935+ }
3936+
3937+ touchRegistry.addTouchWatcher(0, &watcher);
3938+
3939+ {
3940+ QList<QTouchEvent::TouchPoint> touchPoints;
3941+ touchPoints.append(QTouchEvent::TouchPoint(0));
3942+ touchPoints[0].setState(Qt::TouchPointMoved);
3943+ touchPoints[0].setRect(QRect(11, 11, 0, 0));
3944+ touchPoints.append(QTouchEvent::TouchPoint(1));
3945+ touchPoints[1].setState(Qt::TouchPointPressed);
3946+ touchPoints[1].setRect(QRect(20, 20, 0, 0));
3947+ QTouchEvent touchEvent(QEvent::TouchUpdate,
3948+ 0 /* device */,
3949+ Qt::NoModifier,
3950+ Qt::TouchPointPressed | Qt::TouchPointMoved,
3951+ touchPoints);
3952+ touchRegistry.update(&touchEvent);
3953+ }
3954+
3955+ // watcher should have received an update on the touch he's interested on.
3956+ QCOMPARE(watcher.unownedTouchEvents.count(), 1);
3957+ // make sure only touch 0 is there (i.e. no mention of touch 1)
3958+ QCOMPARE(watcher.unownedTouchEvents[0].touchPoints.count(), 1);
3959+ QCOMPARE(watcher.unownedTouchEvents[0].touchPoints[0].id(), 0);
3960+ // Check that the points local coordinates have been mapped to watcher's coordinate system.
3961+ QCOMPARE(watcher.unownedTouchEvents[0].touchPoints[0].rect().x(), 11 - watcher.x());
3962+ QCOMPARE(watcher.unownedTouchEvents[0].touchPoints[0].rect().y(), 11 - watcher.y());
3963+
3964+ {
3965+ QList<QTouchEvent::TouchPoint> touchPoints;
3966+ touchPoints.append(QTouchEvent::TouchPoint(0));
3967+ touchPoints[0].setState(Qt::TouchPointMoved);
3968+ touchPoints[0].setRect(QRect(12, 12, 0, 0));
3969+ touchPoints.append(QTouchEvent::TouchPoint(1));
3970+ touchPoints[1].setState(Qt::TouchPointMoved);
3971+ touchPoints[1].setRect(QRect(21, 21, 0, 0));
3972+ QTouchEvent touchEvent(QEvent::TouchUpdate,
3973+ 0 /* device */,
3974+ Qt::NoModifier,
3975+ Qt::TouchPointPressed | Qt::TouchPointMoved,
3976+ touchPoints);
3977+ touchRegistry.update(&touchEvent);
3978+ }
3979+
3980+ // watcher gets updates only for touch 0
3981+
3982+ QCOMPARE(watcher.unownedTouchEvents.count(), 2);
3983+ QCOMPARE(watcher.unownedTouchEvents[1].touchPoints.count(), 1);
3984+ QCOMPARE(watcher.unownedTouchEvents[1].touchPoints[0].id(), 0);
3985+}
3986+
3987+void tst_TouchRegistry::keepDispatchingToWatchersAfterLastCandidateGivesUp()
3988+{
3989+ TouchRegistry touchRegistry;
3990+ DummyCandidate item;
3991+
3992+ {
3993+ QList<QTouchEvent::TouchPoint> touchPoints;
3994+ touchPoints.append(QTouchEvent::TouchPoint(0));
3995+ touchPoints[0].setState(Qt::TouchPointPressed);
3996+ QTouchEvent touchEvent(QEvent::TouchBegin,
3997+ 0 /* device */,
3998+ Qt::NoModifier,
3999+ Qt::TouchPointPressed,
4000+ touchPoints);
4001+ touchRegistry.update(&touchEvent);
4002+ }
4003+
4004+ touchRegistry.addCandidateOwnerForTouch(0, &item);
4005+
4006+ {
4007+ QList<QTouchEvent::TouchPoint> touchPoints;
4008+ touchPoints.append(QTouchEvent::TouchPoint(0));
4009+ touchPoints[0].setState(Qt::TouchPointMoved);
4010+ touchPoints.append(QTouchEvent::TouchPoint(1));
4011+ touchPoints[1].setState(Qt::TouchPointPressed);
4012+ QTouchEvent touchEvent(QEvent::TouchUpdate,
4013+ 0 /* device */,
4014+ Qt::NoModifier,
4015+ Qt::TouchPointPressed | Qt::TouchPointMoved,
4016+ touchPoints);
4017+ touchRegistry.update(&touchEvent);
4018+ }
4019+
4020+ QCOMPARE(item.unownedTouchEvents.count(), 1);
4021+ QCOMPARE(item.unownedTouchEvents[0].touchPoints.count(), 1);
4022+ QCOMPARE(item.unownedTouchEvents[0].touchPoints[0].id(), 0);
4023+ item.unownedTouchEvents.clear();
4024+
4025+ touchRegistry.addTouchWatcher(1, &item);
4026+
4027+ {
4028+ QList<QTouchEvent::TouchPoint> touchPoints;
4029+ touchPoints.append(QTouchEvent::TouchPoint(0));
4030+ touchPoints[0].setState(Qt::TouchPointMoved);
4031+ touchPoints.append(QTouchEvent::TouchPoint(1));
4032+ touchPoints[1].setState(Qt::TouchPointMoved);
4033+ QTouchEvent touchEvent(QEvent::TouchUpdate,
4034+ 0 /* device */,
4035+ Qt::NoModifier,
4036+ Qt::TouchPointMoved,
4037+ touchPoints);
4038+ touchRegistry.update(&touchEvent);
4039+ }
4040+
4041+ QCOMPARE(item.unownedTouchEvents.count(), 1);
4042+ QCOMPARE(item.unownedTouchEvents[0].touchPoints.count(), 2);
4043+ QVERIFY(item.unownedTouchEvents[0].containsTouchWithId(0));
4044+ QVERIFY(item.unownedTouchEvents[0].containsTouchWithId(1));
4045+ item.unownedTouchEvents.clear();
4046+
4047+ {
4048+ QList<QTouchEvent::TouchPoint> touchPoints;
4049+ touchPoints.append(QTouchEvent::TouchPoint(0));
4050+ touchPoints[0].setState(Qt::TouchPointReleased);
4051+ touchPoints.append(QTouchEvent::TouchPoint(1));
4052+ touchPoints[1].setState(Qt::TouchPointMoved);
4053+ QTouchEvent touchEvent(QEvent::TouchUpdate,
4054+ 0 /* device */,
4055+ Qt::NoModifier,
4056+ Qt::TouchPointReleased | Qt::TouchPointMoved,
4057+ touchPoints);
4058+ touchRegistry.update(&touchEvent);
4059+ }
4060+
4061+ QCOMPARE(item.unownedTouchEvents.count(), 1);
4062+ QCOMPARE(item.unownedTouchEvents[0].touchPoints.count(), 2);
4063+ QVERIFY(item.unownedTouchEvents[0].containsTouchWithId(0));
4064+ QVERIFY(item.unownedTouchEvents[0].containsTouchWithId(1));
4065+ item.unownedTouchEvents.clear();
4066+
4067+ touchRegistry.removeCandidateOwnerForTouch(0, &item);
4068+
4069+ {
4070+ QList<QTouchEvent::TouchPoint> touchPoints;
4071+ touchPoints.append(QTouchEvent::TouchPoint(1));
4072+ touchPoints[0].setState(Qt::TouchPointReleased);
4073+ QTouchEvent touchEvent(QEvent::TouchEnd,
4074+ 0 /* device */,
4075+ Qt::NoModifier,
4076+ Qt::TouchPointReleased,
4077+ touchPoints);
4078+ touchRegistry.update(&touchEvent);
4079+ }
4080+
4081+ QCOMPARE(item.unownedTouchEvents.count(), 1);
4082+ QCOMPARE(item.unownedTouchEvents[0].touchPoints.count(), 1);
4083+ QVERIFY(item.unownedTouchEvents[0].containsTouchWithId(1));
4084+ item.unownedTouchEvents.clear();
4085+
4086+ QVERIFY(touchRegistry.m_touchInfoPool.isEmpty());
4087+}
4088+
4089+/*
4090+ Regression test that reproduces a problematic scenario that came up during manual testing.
4091+ It reproduces the interaction between TouchRegistry and a DirectionalDragArea
4092+ */
4093+void tst_TouchRegistry::candidatesAndWatchers_1()
4094+{
4095+ TouchRegistry touchRegistry;
4096+ DummyCandidate item;
4097+
4098+ {
4099+ QList<QTouchEvent::TouchPoint> touchPoints;
4100+ touchPoints.append(QTouchEvent::TouchPoint(0));
4101+ touchPoints[0].setState(Qt::TouchPointPressed);
4102+ QTouchEvent touchEvent(QEvent::TouchBegin,
4103+ 0 /* device */,
4104+ Qt::NoModifier,
4105+ Qt::TouchPointPressed,
4106+ touchPoints);
4107+ touchRegistry.update(&touchEvent);
4108+ }
4109+
4110+ touchRegistry.addCandidateOwnerForTouch(0, &item);
4111+
4112+ {
4113+ QList<QTouchEvent::TouchPoint> touchPoints;
4114+ touchPoints.append(QTouchEvent::TouchPoint(0));
4115+ touchPoints[0].setState(Qt::TouchPointMoved);
4116+ touchPoints.append(QTouchEvent::TouchPoint(1));
4117+ touchPoints[1].setState(Qt::TouchPointPressed);
4118+ QTouchEvent touchEvent(QEvent::TouchUpdate,
4119+ 0 /* device */,
4120+ Qt::NoModifier,
4121+ Qt::TouchPointPressed | Qt::TouchPointMoved,
4122+ touchPoints);
4123+ touchRegistry.update(&touchEvent);
4124+ }
4125+
4126+ touchRegistry.addTouchWatcher(1, &item);
4127+
4128+ touchRegistry.removeCandidateOwnerForTouch(0, &item);
4129+
4130+ touchRegistry.addTouchWatcher(0, &item);
4131+
4132+ {
4133+ QList<QTouchEvent::TouchPoint> touchPoints;
4134+ touchPoints.append(QTouchEvent::TouchPoint(0));
4135+ touchPoints[0].setState(Qt::TouchPointReleased);
4136+ touchPoints.append(QTouchEvent::TouchPoint(1));
4137+ touchPoints[1].setState(Qt::TouchPointMoved);
4138+ QTouchEvent touchEvent(QEvent::TouchUpdate,
4139+ 0 /* device */,
4140+ Qt::NoModifier,
4141+ Qt::TouchPointReleased | Qt::TouchPointMoved,
4142+ touchPoints);
4143+ touchRegistry.update(&touchEvent);
4144+ }
4145+
4146+ {
4147+ QList<QTouchEvent::TouchPoint> touchPoints;
4148+ touchPoints.append(QTouchEvent::TouchPoint(1));
4149+ touchPoints[0].setState(Qt::TouchPointReleased);
4150+ QTouchEvent touchEvent(QEvent::TouchEnd,
4151+ 0 /* device */,
4152+ Qt::NoModifier,
4153+ Qt::TouchPointReleased,
4154+ touchPoints);
4155+ touchRegistry.update(&touchEvent);
4156+ }
4157+
4158+ QVERIFY(touchRegistry.m_touchInfoPool.isEmpty());
4159+
4160+ item.unownedTouchEvents.clear();
4161+
4162+ {
4163+ QList<QTouchEvent::TouchPoint> touchPoints;
4164+ touchPoints.append(QTouchEvent::TouchPoint(0));
4165+ touchPoints[0].setState(Qt::TouchPointPressed);
4166+ QTouchEvent touchEvent(QEvent::TouchBegin,
4167+ 0 /* device */,
4168+ Qt::NoModifier,
4169+ Qt::TouchPointPressed,
4170+ touchPoints);
4171+ touchRegistry.update(&touchEvent);
4172+ }
4173+
4174+ // Haven't made any subscription for that new touch 0.
4175+ QCOMPARE(item.unownedTouchEvents.count(), 0);
4176+}
4177+
4178+/*
4179+ Regression test that reproduces a problematic scenario that came up during manual testing.
4180+ It reproduces the interaction between TouchRegistry, DirectionalDragArea and a TouchGate.
4181+ */
4182+void tst_TouchRegistry::candidatesAndWatchers_2()
4183+{
4184+ TouchRegistry touchRegistry;
4185+
4186+ DummyCandidate directionalDragArea;
4187+ directionalDragArea.setObjectName("DirectionalDragArea");
4188+
4189+ DummyCandidate touchGate;
4190+ touchGate.setObjectName("TouchGate");
4191+
4192+ // [DDA] 1298 TouchBegin (id:0, state:pressed, scenePos:(147,1023))
4193+ {
4194+ QList<QTouchEvent::TouchPoint> touchPoints;
4195+ touchPoints.append(QTouchEvent::TouchPoint(0));
4196+ touchPoints[0].setState(Qt::TouchPointPressed);
4197+ QTouchEvent touchEvent(QEvent::TouchBegin,
4198+ 0 /* device */,
4199+ Qt::NoModifier,
4200+ Qt::TouchPointPressed,
4201+ touchPoints);
4202+ touchRegistry.update(&touchEvent);
4203+ }
4204+
4205+ // [TouchRegistry] addCandidateOwnerForTouch id 0 candidate DirectionalDragArea
4206+ touchRegistry.addCandidateOwnerForTouch(0, &directionalDragArea);
4207+
4208+ // [TouchRegistry] requestTouchOwnership id 0 candidate TouchGate
4209+ touchRegistry.requestTouchOwnership(0, &touchGate);
4210+
4211+ //[TouchRegistry] got TouchUpdate (id:0, state:moved, scenePos:(147,1023)) (id:1, state:pressed, scenePos:(327,792))
4212+ {
4213+ QList<QTouchEvent::TouchPoint> touchPoints;
4214+ touchPoints.append(QTouchEvent::TouchPoint(0));
4215+ touchPoints[0].setState(Qt::TouchPointMoved);
4216+ touchPoints.append(QTouchEvent::TouchPoint(1));
4217+ touchPoints[1].setState(Qt::TouchPointPressed);
4218+ QTouchEvent touchEvent(QEvent::TouchUpdate,
4219+ 0 /* device */,
4220+ Qt::NoModifier,
4221+ Qt::TouchPointPressed | Qt::TouchPointMoved,
4222+ touchPoints);
4223+ touchRegistry.update(&touchEvent);
4224+ }
4225+
4226+ // [TouchRegistry] addTouchWatcher id 1 watcher DirectionalDragArea
4227+ touchRegistry.addTouchWatcher(1, &directionalDragArea);
4228+
4229+ // [TouchRegistry] removeCandidateOwnerForTouch id 0 candidate DirectionalDragArea
4230+ touchRegistry.removeCandidateOwnerForTouch(0, &directionalDragArea);
4231+
4232+ //[TouchRegistry] sending TouchWonershipEvent(id = 0 gained) to candidate TouchGate
4233+ QCOMPARE(touchGate.ownedTouches.count(), 1);
4234+ QVERIFY(touchGate.ownedTouches.contains(0));
4235+
4236+ // [TouchRegistry] addTouchWatcher id 0 watcher DirectionalDragArea
4237+ touchRegistry.addTouchWatcher(0, &directionalDragArea);
4238+
4239+ // [TouchRegistry] requestTouchOwnership id 1 candidate TouchGate
4240+ touchRegistry.requestTouchOwnership(1, &touchGate);
4241+
4242+ //[TouchRegistry] sending TouchWonershipEvent(id = 1 gained) to candidate TouchGate
4243+ QCOMPARE(touchGate.ownedTouches.count(), 2);
4244+ QVERIFY(touchGate.ownedTouches.contains(1));
4245+
4246+ directionalDragArea.unownedTouchEvents.clear();
4247+ touchGate.unownedTouchEvents.clear();
4248+
4249+ //[TouchRegistry] got TouchUpdate (id:0, state:moved, scenePos:(148,1025)) (id:1, state:moved, scenePos:(329,795))
4250+ {
4251+ QList<QTouchEvent::TouchPoint> touchPoints;
4252+ touchPoints.append(QTouchEvent::TouchPoint(0));
4253+ touchPoints[0].setState(Qt::TouchPointMoved);
4254+ touchPoints.append(QTouchEvent::TouchPoint(1));
4255+ touchPoints[1].setState(Qt::TouchPointMoved);
4256+ QTouchEvent touchEvent(QEvent::TouchUpdate,
4257+ 0 /* device */,
4258+ Qt::NoModifier,
4259+ Qt::TouchPointMoved,
4260+ touchPoints);
4261+ touchRegistry.update(&touchEvent);
4262+ }
4263+
4264+ //vvvvv DDA Watchers are being ignored from now on vvvvvvv
4265+
4266+ QCOMPARE(directionalDragArea.unownedTouchEvents.count(), 1);
4267+ QCOMPARE(directionalDragArea.unownedTouchEvents.first().touchPoints.count(), 2);
4268+ QVERIFY(directionalDragArea.unownedTouchEvents.first().containsTouchWithId(0));
4269+ QVERIFY(directionalDragArea.unownedTouchEvents.first().containsTouchWithId(1));
4270+
4271+ QVERIFY(touchGate.unownedTouchEvents.isEmpty());
4272+
4273+ directionalDragArea.unownedTouchEvents.clear();
4274+
4275+ //[TouchRegistry] got TouchUpdate (id:0, state:moved, scenePos:(151,1029)) (id:1, state:released, scenePos:(329,795))
4276+ {
4277+ QList<QTouchEvent::TouchPoint> touchPoints;
4278+ touchPoints.append(QTouchEvent::TouchPoint(0));
4279+ touchPoints[0].setState(Qt::TouchPointMoved);
4280+ touchPoints.append(QTouchEvent::TouchPoint(1));
4281+ touchPoints[1].setState(Qt::TouchPointReleased);
4282+ QTouchEvent touchEvent(QEvent::TouchUpdate,
4283+ 0 /* device */,
4284+ Qt::NoModifier,
4285+ Qt::TouchPointMoved | Qt::TouchPointReleased,
4286+ touchPoints);
4287+ touchRegistry.update(&touchEvent);
4288+ }
4289+
4290+ QCOMPARE(directionalDragArea.unownedTouchEvents.count(), 1);
4291+ QCOMPARE(directionalDragArea.unownedTouchEvents.first().touchPoints.count(), 2);
4292+ QVERIFY(directionalDragArea.unownedTouchEvents.first().containsTouchWithId(0));
4293+ QVERIFY(directionalDragArea.unownedTouchEvents.first().containsTouchWithId(1));
4294+
4295+ QVERIFY(touchGate.unownedTouchEvents.isEmpty());
4296+
4297+ directionalDragArea.unownedTouchEvents.clear();
4298+
4299+ //[TouchRegistry] got TouchEnd (id:0, state:released, scenePos:(157,1034))
4300+ {
4301+ QList<QTouchEvent::TouchPoint> touchPoints;
4302+ touchPoints.append(QTouchEvent::TouchPoint(0));
4303+ touchPoints[0].setState(Qt::TouchPointReleased);
4304+ QTouchEvent touchEvent(QEvent::TouchEnd,
4305+ 0 /* device */,
4306+ Qt::NoModifier,
4307+ Qt::TouchPointReleased,
4308+ touchPoints);
4309+ touchRegistry.update(&touchEvent);
4310+ }
4311+
4312+ QCOMPARE(directionalDragArea.unownedTouchEvents.count(), 1);
4313+ QCOMPARE(directionalDragArea.unownedTouchEvents.first().touchPoints.count(), 1);
4314+ QVERIFY(directionalDragArea.unownedTouchEvents.first().containsTouchWithId(0));
4315+
4316+ QVERIFY(touchGate.unownedTouchEvents.isEmpty());
4317+}
4318+
4319+void tst_TouchRegistry::rejectingTouchfterItsEnd()
4320+{
4321+ TouchRegistry touchRegistry;
4322+ DummyCandidate earlyCandidate;
4323+ DummyCandidate lateCandidate;
4324+
4325+ {
4326+ QList<QTouchEvent::TouchPoint> touchPoints;
4327+ touchPoints.append(QTouchEvent::TouchPoint(0));
4328+ touchPoints[0].setState(Qt::TouchPointPressed);
4329+ QTouchEvent touchEvent(QEvent::TouchBegin,
4330+ 0 /* device */,
4331+ Qt::NoModifier,
4332+ Qt::TouchPointPressed,
4333+ touchPoints);
4334+ touchRegistry.update(&touchEvent);
4335+ }
4336+
4337+ touchRegistry.addCandidateOwnerForTouch(0, &earlyCandidate);
4338+ touchRegistry.addCandidateOwnerForTouch(0, &lateCandidate);
4339+
4340+ QVERIFY(earlyCandidate.ownedTouches.isEmpty());
4341+ QVERIFY(lateCandidate.ownedTouches.isEmpty());
4342+
4343+ touchRegistry.requestTouchOwnership(0, &lateCandidate);
4344+
4345+ QVERIFY(earlyCandidate.ownedTouches.isEmpty());
4346+ QVERIFY(lateCandidate.ownedTouches.isEmpty());
4347+
4348+ {
4349+ QList<QTouchEvent::TouchPoint> touchPoints;
4350+ touchPoints.append(QTouchEvent::TouchPoint(0));
4351+ touchPoints[0].setState(Qt::TouchPointReleased);
4352+ QTouchEvent touchEvent(QEvent::TouchEnd,
4353+ 0 /* device */,
4354+ Qt::NoModifier,
4355+ Qt::TouchPointPressed,
4356+ touchPoints);
4357+ touchRegistry.update(&touchEvent);
4358+ }
4359+
4360+ QVERIFY(earlyCandidate.ownedTouches.isEmpty());
4361+ QVERIFY(lateCandidate.ownedTouches.isEmpty());
4362+
4363+ touchRegistry.removeCandidateOwnerForTouch(0, &earlyCandidate);
4364+
4365+ QCOMPARE(lateCandidate.ownedTouches.count(), 1);
4366+ QCOMPARE(lateCandidate.ownedTouches.contains(0), 1);
4367+
4368+ // Check that there's no trace left of touch 0 as we no longer need to keep tabs on it.
4369+ QVERIFY(!touchRegistry.findTouchInfo(0));
4370+}
4371+
4372+void tst_TouchRegistry::removeOldUndecidedCandidates()
4373+{
4374+ FakeTimerFactory *fakeTimerFactory = new FakeTimerFactory;
4375+ TouchRegistry *touchRegistry = new TouchRegistry(nullptr, fakeTimerFactory);
4376+
4377+ DummyCandidate undecidedCandidate;
4378+ undecidedCandidate.setObjectName("undecided");
4379+
4380+ DummyCandidate candidateThatWantsTouch;
4381+ candidateThatWantsTouch.setObjectName("wantsTouch");
4382+
4383+ {
4384+ QList<QTouchEvent::TouchPoint> touchPoints;
4385+ touchPoints.append(QTouchEvent::TouchPoint(0));
4386+ touchPoints[0].setState(Qt::TouchPointPressed);
4387+ QTouchEvent touchEvent(QEvent::TouchBegin,
4388+ 0 /* device */,
4389+ Qt::NoModifier,
4390+ Qt::TouchPointPressed,
4391+ touchPoints);
4392+ touchRegistry->update(&touchEvent);
4393+ }
4394+
4395+ touchRegistry->addCandidateOwnerForTouch(0, &undecidedCandidate);
4396+
4397+ touchRegistry->requestTouchOwnership(0, &candidateThatWantsTouch);
4398+
4399+ QVERIFY(undecidedCandidate.ownedTouches.isEmpty());
4400+ QVERIFY(undecidedCandidate.lostTouches.isEmpty());
4401+ QVERIFY(candidateThatWantsTouch.ownedTouches.isEmpty());
4402+ QVERIFY(candidateThatWantsTouch.lostTouches.isEmpty());
4403+
4404+ // Simulate that enough time has passed to cause the CandidateInactivityTimer to timeout,
4405+ // making TouchRegistry consider that undecidedCantidate defaulted.
4406+ fakeTimerFactory->makeRunningTimersTimeout();
4407+
4408+ QVERIFY(undecidedCandidate.ownedTouches.isEmpty());
4409+ QVERIFY(undecidedCandidate.lostTouches.contains(0));
4410+ QVERIFY(candidateThatWantsTouch.ownedTouches.contains(0));
4411+ QVERIFY(candidateThatWantsTouch.lostTouches.isEmpty());
4412+
4413+ delete touchRegistry;
4414+}
4415+
4416+////////////// TouchMemento //////////
4417+
4418+TouchMemento::TouchMemento(const QTouchEvent *touchEvent)
4419+ : touchPointStates(touchEvent->touchPointStates()), touchPoints(touchEvent->touchPoints())
4420+{
4421+}
4422+
4423+bool TouchMemento::containsTouchWithId(int touchId) const
4424+{
4425+ for (int i = 0; i < touchPoints.count(); ++i) {
4426+ if (touchPoints.at(i).id() == touchId) {
4427+ return true;
4428+ }
4429+ }
4430+ return false;
4431+}
4432+
4433+////////////// DummyCandidate //////////
4434+
4435+bool DummyCandidate::event(QEvent *e)
4436+{
4437+ if (e->type() == TouchOwnershipEvent::touchOwnershipEventType()) {
4438+ TouchOwnershipEvent *touchOwnershipEvent = static_cast<TouchOwnershipEvent *>(e);
4439+
4440+ // NB: Cannot use QVERIFY here because the macro doesn't return a boolean and is
4441+ // meant for use only directly in the body of a test function
4442+ if (ownedTouches.contains(touchOwnershipEvent->touchId()))
4443+ qFatal("Sent ownership event for a touch that is already owned.");
4444+ if (lostTouches.contains(touchOwnershipEvent->touchId()))
4445+ qFatal("Sent ownership event for a touch that has already been lost.");
4446+
4447+ if (touchOwnershipEvent->gained()) {
4448+ ownedTouches.insert(touchOwnershipEvent->touchId());
4449+ } else {
4450+ lostTouches.insert(touchOwnershipEvent->touchId());
4451+ }
4452+ return true;
4453+ } else if (e->type() == UnownedTouchEvent::unownedTouchEventType()) {
4454+ UnownedTouchEvent *unownedTouchEvent = static_cast<UnownedTouchEvent *>(e);
4455+ unownedTouchEvents.append(TouchMemento(unownedTouchEvent->touchEvent()));
4456+ return true;
4457+ } else {
4458+ return QObject::event(e);
4459+ }
4460+}
4461+
4462+QTEST_GUILESS_MAIN(tst_TouchRegistry)
4463+
4464+#include "tst_TouchRegistry.moc"
4465
4466=== modified file 'tests/mocks/Unity/Application/CMakeLists.txt'
4467--- tests/mocks/Unity/Application/CMakeLists.txt 2014-09-29 09:43:18 +0000
4468+++ tests/mocks/Unity/Application/CMakeLists.txt 2014-10-10 15:05:15 +0000
4469@@ -13,6 +13,7 @@
4470 SessionManager.cpp
4471 SurfaceManager.cpp
4472 SessionModel.h
4473+ UbuntuKeyboardInfo.cpp
4474 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
4475 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
4476 )
4477
4478=== modified file 'tests/mocks/Unity/Application/MirSurfaceItem.cpp'
4479--- tests/mocks/Unity/Application/MirSurfaceItem.cpp 2014-09-04 10:03:10 +0000
4480+++ tests/mocks/Unity/Application/MirSurfaceItem.cpp 2014-10-10 15:05:15 +0000
4481@@ -40,6 +40,8 @@
4482 , m_state(state)
4483 , m_live(true)
4484 , m_orientation(Qt::PortraitOrientation)
4485+ , m_touchPressCount(0)
4486+ , m_touchReleaseCount(0)
4487 , m_qmlItem(nullptr)
4488 , m_screenshotUrl(screenshot)
4489 {
4490@@ -144,24 +146,12 @@
4491 }
4492 }
4493
4494-void MirSurfaceItem::onQmlWantInputMethodChanged()
4495-{
4496- QQmlProperty prop(m_qmlItem, "wantInputMethod");
4497- bool wantInputMethod = prop.read().toBool();
4498-
4499- if (hasFocus() && wantInputMethod) {
4500- Q_EMIT inputMethodRequested();
4501- }
4502-}
4503-
4504 void MirSurfaceItem::onFocusChanged()
4505 {
4506- QQmlProperty prop(m_qmlItem, "wantInputMethod");
4507- bool wantInputMethod = prop.read().toBool();
4508-
4509- if (!hasFocus() && wantInputMethod) {
4510- Q_EMIT inputMethodDismissed();
4511- prop.write(QVariant::fromValue(false));
4512+ if (!hasFocus()) {
4513+ // Causes a crash in tst_Shell.qml, inside the mock Unity.Application itself.
4514+ // Didn't have time to debug yet.
4515+ //Q_EMIT inputMethodDismissed();
4516 }
4517 }
4518
4519@@ -194,12 +184,18 @@
4520 QQmlProperty screenshotSource(m_qmlItem, "screenshotSource");
4521 screenshotSource.write(QVariant::fromValue(m_screenshotUrl));
4522 }
4523+}
4524
4525- {
4526- QQmlProperty prop(m_qmlItem, "wantInputMethod");
4527- if (prop.type() == QQmlProperty::Property) {
4528- bool ok = prop.connectNotifySignal(this, SLOT(onQmlWantInputMethodChanged()));
4529- if (!ok) qCritical("MirSurfaceItem: failed to connect to wantInputMethod notify signal");
4530- }
4531+void MirSurfaceItem::touchEvent(QTouchEvent * event)
4532+{
4533+ if (event->touchPointStates() & Qt::TouchPointPressed) {
4534+ ++m_touchPressCount;
4535+ Q_EMIT touchPressCountChanged(m_touchPressCount);
4536+ // Causes a crash in tst_Shell.qml, inside the mock Unity.Application itself.
4537+ // Didn't have time to debug yet.
4538+ // Q_EMIT inputMethodRequested();
4539+ } else if (event->touchPointStates() & Qt::TouchPointReleased) {
4540+ ++m_touchReleaseCount;
4541+ Q_EMIT touchReleaseCountChanged(m_touchReleaseCount);
4542 }
4543 }
4544
4545=== modified file 'tests/mocks/Unity/Application/MirSurfaceItem.h'
4546--- tests/mocks/Unity/Application/MirSurfaceItem.h 2014-09-04 10:03:10 +0000
4547+++ tests/mocks/Unity/Application/MirSurfaceItem.h 2014-10-10 15:05:15 +0000
4548@@ -36,6 +36,11 @@
4549 Q_PROPERTY(bool live READ live NOTIFY liveChanged)
4550 Q_PROPERTY(Qt::ScreenOrientation orientation READ orientation WRITE setOrientation NOTIFY orientationChanged DESIGNABLE false)
4551
4552+ Q_PROPERTY(int touchPressCount READ touchPressCount WRITE setTouchPressCount NOTIFY touchPressCountChanged
4553+ DESIGNABLE false)
4554+ Q_PROPERTY(int touchReleaseCount READ touchReleaseCount WRITE setTouchReleaseCount NOTIFY touchReleaseCountChanged
4555+ DESIGNABLE false)
4556+
4557 public:
4558 enum Type {
4559 Normal,
4560@@ -73,6 +78,12 @@
4561 void setScreenshot(const QUrl& screenshot);
4562 void setLive(bool live);
4563
4564+ int touchPressCount() const { return m_touchPressCount; }
4565+ void setTouchPressCount(int count) { m_touchPressCount = count; Q_EMIT touchPressCountChanged(count); }
4566+
4567+ int touchReleaseCount() const { return m_touchReleaseCount; }
4568+ void setTouchReleaseCount(int count) { m_touchReleaseCount = count; Q_EMIT touchReleaseCountChanged(count); }
4569+
4570 Q_INVOKABLE void setState(State newState);
4571 Q_INVOKABLE void release();
4572
4573@@ -81,6 +92,8 @@
4574 void stateChanged(State);
4575 void liveChanged(bool live);
4576 void orientationChanged();
4577+ void touchPressCountChanged(int count);
4578+ void touchReleaseCountChanged(int count);
4579
4580 void inputMethodRequested();
4581 void inputMethodDismissed();
4582@@ -88,10 +101,12 @@
4583 // internal mock use
4584 void deregister();
4585
4586+protected:
4587+ void touchEvent(QTouchEvent * event) override;
4588+
4589 private Q_SLOTS:
4590 void onFocusChanged();
4591 void onComponentStatusChanged(QQmlComponent::Status status);
4592- void onQmlWantInputMethodChanged();
4593
4594 private:
4595 explicit MirSurfaceItem(const QString& name,
4596@@ -110,6 +125,8 @@
4597 State m_state;
4598 bool m_live;
4599 Qt::ScreenOrientation m_orientation;
4600+ int m_touchPressCount;
4601+ int m_touchReleaseCount;
4602
4603 QQmlComponent *m_qmlContentComponent;
4604 QQuickItem *m_qmlItem;
4605
4606=== modified file 'tests/mocks/Unity/Application/MirSurfaceItem.qml'
4607--- tests/mocks/Unity/Application/MirSurfaceItem.qml 2014-09-10 17:10:48 +0000
4608+++ tests/mocks/Unity/Application/MirSurfaceItem.qml 2014-10-10 15:05:15 +0000
4609@@ -17,7 +17,6 @@
4610 import QtQuick 2.0
4611
4612 Rectangle {
4613- objectName: "fakeSurfaceQML"
4614 id: root
4615 color: "pink"
4616
4617@@ -38,11 +37,6 @@
4618 property alias screenshotSource: screenshotImage.source
4619 property int orientation: Qt.PortraitOrientation
4620
4621- property bool wantInputMethod: false
4622-
4623- property int touchPressCount: 0
4624- property int touchReleaseCount: 0
4625-
4626 Image {
4627 id: screenshotImage
4628 anchors.fill: parent
4629@@ -58,10 +52,4 @@
4630 minimumPixelSize: 10; font.pixelSize: 200
4631 verticalAlignment: Text.AlignVCenter
4632 }
4633-
4634- MultiPointTouchArea {
4635- anchors.fill: parent
4636- onPressed: { root.wantInputMethod = true; root.touchPressCount++; }
4637- onReleased: { root.touchReleaseCount++; }
4638- }
4639 }
4640
4641=== added file 'tests/mocks/Unity/Application/UbuntuKeyboardInfo.cpp'
4642--- tests/mocks/Unity/Application/UbuntuKeyboardInfo.cpp 1970-01-01 00:00:00 +0000
4643+++ tests/mocks/Unity/Application/UbuntuKeyboardInfo.cpp 2014-10-10 15:05:15 +0000
4644@@ -0,0 +1,28 @@
4645+/*
4646+ * Copyright (C) 2014 Canonical, Ltd.
4647+ *
4648+ * This program is free software: you can redistribute it and/or modify it under
4649+ * the terms of the GNU Lesser General Public License version 3, as published by
4650+ * the Free Software Foundation.
4651+ *
4652+ * This program is distributed in the hope that it will be useful, but WITHOUT
4653+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
4654+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
4655+ * Lesser General Public License for more details.
4656+ *
4657+ * You should have received a copy of the GNU Lesser General Public License
4658+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4659+ */
4660+
4661+#include "UbuntuKeyboardInfo.h"
4662+
4663+UbuntuKeyboardInfo *UbuntuKeyboardInfo::m_instance = nullptr;
4664+
4665+UbuntuKeyboardInfo::UbuntuKeyboardInfo(QObject *parent)
4666+ : QObject(parent),
4667+ m_x(0),
4668+ m_y(0),
4669+ m_width(200),
4670+ m_height(200)
4671+{
4672+}
4673
4674=== added file 'tests/mocks/Unity/Application/UbuntuKeyboardInfo.h'
4675--- tests/mocks/Unity/Application/UbuntuKeyboardInfo.h 1970-01-01 00:00:00 +0000
4676+++ tests/mocks/Unity/Application/UbuntuKeyboardInfo.h 2014-10-10 15:05:15 +0000
4677@@ -0,0 +1,60 @@
4678+/*
4679+ * Copyright (C) 2014 Canonical, Ltd.
4680+ *
4681+ * This program is free software: you can redistribute it and/or modify it under
4682+ * the terms of the GNU Lesser General Public License version 3, as published by
4683+ * the Free Software Foundation.
4684+ *
4685+ * This program is distributed in the hope that it will be useful, but WITHOUT
4686+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
4687+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
4688+ * Lesser General Public License for more details.
4689+ *
4690+ * You should have received a copy of the GNU Lesser General Public License
4691+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4692+ */
4693+
4694+#ifndef UBUNTU_KEYBOARD_INFO_H
4695+#define UBUNTU_KEYBOARD_INFO_H
4696+
4697+#include <QLocalSocket>
4698+#include <QTimer>
4699+
4700+class UbuntuKeyboardInfo : public QObject {
4701+ Q_OBJECT
4702+ Q_PROPERTY(qreal x READ x NOTIFY xChanged)
4703+ Q_PROPERTY(qreal y READ y NOTIFY yChanged)
4704+ Q_PROPERTY(qreal width READ width NOTIFY widthChanged)
4705+ Q_PROPERTY(qreal height READ height NOTIFY heightChanged)
4706+public:
4707+ UbuntuKeyboardInfo(QObject *parent = 0);
4708+ virtual ~UbuntuKeyboardInfo() {}
4709+ qreal x() const { return m_x; }
4710+ qreal y() const { return m_y; }
4711+ qreal width() const { return m_width; }
4712+ qreal height() const { return m_height; }
4713+
4714+ static UbuntuKeyboardInfo *singleton() {
4715+ if (!m_instance) {
4716+ m_instance = new UbuntuKeyboardInfo;
4717+ }
4718+ return m_instance;
4719+ }
4720+
4721+Q_SIGNALS:
4722+ void xChanged(qreal x);
4723+ void yChanged(qreal y);
4724+ void widthChanged(qreal width);
4725+ void heightChanged(qreal height);
4726+
4727+private:
4728+ QLocalSocket m_socket;
4729+ qint32 m_x;
4730+ qint32 m_y;
4731+ qint32 m_width;
4732+ qint32 m_height;
4733+
4734+ static UbuntuKeyboardInfo *m_instance;
4735+};
4736+
4737+#endif // UBUNTU_KEYBOARD_INFO_H
4738
4739=== modified file 'tests/mocks/Unity/Application/plugin.cpp'
4740--- tests/mocks/Unity/Application/plugin.cpp 2014-09-01 16:07:55 +0000
4741+++ tests/mocks/Unity/Application/plugin.cpp 2014-10-10 15:05:15 +0000
4742@@ -24,6 +24,7 @@
4743 #include "SurfaceManager.h"
4744 #include "SessionManager.h"
4745 #include "ApplicationTestInterface.h"
4746+#include "UbuntuKeyboardInfo.h"
4747
4748 #include <qqml.h>
4749 #include <QQmlEngine>
4750@@ -46,6 +47,11 @@
4751 return SessionManager::singleton();
4752 }
4753
4754+static QObject* ubuntuKeyboardInfoSingleton(QQmlEngine*, QJSEngine*) {
4755+ return UbuntuKeyboardInfo::singleton();
4756+}
4757+
4758+
4759 ApplicationTestInterface* s_appTestInterface = nullptr;
4760
4761 static QObject* applicationTestInterface(QQmlEngine* engine, QJSEngine* scriptEngine) {
4762@@ -76,6 +82,7 @@
4763 qmlRegisterSingletonType<SurfaceManager>(uri, 0, 1, "SurfaceManager", surfaceManagerSingleton);
4764 qmlRegisterSingletonType<SessionManager>(uri, 0, 1, "SessionManager", sessionManagerSingleton);
4765 qmlRegisterSingletonType<ApplicationTestInterface>(uri, 0, 1, "ApplicationTest", applicationTestInterface);
4766+ qmlRegisterSingletonType<UbuntuKeyboardInfo>(uri, 0, 1, "UbuntuKeyboardInfo", ubuntuKeyboardInfoSingleton);
4767
4768 qRegisterMetaType<MirSurfaceItem::Type>("MirSurfaceItem::Type");
4769 qRegisterMetaType<MirSurfaceItem::State>("MirSurfaceItem::State");
4770
4771=== modified file 'tests/plugins/Ubuntu/Gestures/CMakeLists.txt'
4772--- tests/plugins/Ubuntu/Gestures/CMakeLists.txt 2014-09-01 09:13:08 +0000
4773+++ tests/plugins/Ubuntu/Gestures/CMakeLists.txt 2014-10-10 15:05:15 +0000
4774@@ -1,7 +1,13 @@
4775 include(QmlTest)
4776
4777+# DANGER! DANGER! Using Qt's private API!
4778+include_directories(
4779+ ${Qt5Quick_PRIVATE_INCLUDE_DIRS}
4780+)
4781+
4782 include_directories(
4783 ${CMAKE_SOURCE_DIR}/plugins/Ubuntu/Gestures
4784+ ${CMAKE_SOURCE_DIR}/libs/UbuntuGestures
4785 ${CMAKE_CURRENT_BINARY_DIR}
4786 ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
4787 )
4788@@ -17,7 +23,7 @@
4789 macro(add_gesture_ui_test CLASSNAME)
4790 add_executable(${CLASSNAME}TestExec tst_${CLASSNAME}.cpp GestureTest.cpp)
4791 qt5_use_modules(${CLASSNAME}TestExec Test Core Qml Gui Quick)
4792- target_link_libraries(${CLASSNAME}TestExec UbuntuGestureQml)
4793+ target_link_libraries(${CLASSNAME}TestExec UbuntuGesturesQml UbuntuGestures)
4794
4795 add_binary_qml_test(${CLASSNAME} ${CMAKE_BINARY_DIR}/plugins/Ubuntu/Gestures UbuntuGesturesTestQmlFiles "")
4796 add_manual_qml_test(. ${CLASSNAME} IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
4797@@ -34,10 +40,11 @@
4798
4799 add_executable(${CLASSNAME}TestExec tst_${CLASSNAME}.cpp)
4800 qt5_use_modules(${CLASSNAME}TestExec Test Core)
4801- target_link_libraries(${CLASSNAME}TestExec UbuntuGestureQml)
4802+ target_link_libraries(${CLASSNAME}TestExec UbuntuGesturesQml)
4803 endmacro(add_gesture_test)
4804
4805 add_gesture_ui_test(DirectionalDragArea)
4806 add_gesture_ui_test(PressedOutsideNotifier)
4807+add_gesture_ui_test(TouchGate)
4808 add_gesture_test(Damper)
4809 add_gesture_test(AxisVelocityCalculator)
4810
4811=== modified file 'tests/plugins/Ubuntu/Gestures/RightwardsLauncher.qml'
4812--- tests/plugins/Ubuntu/Gestures/RightwardsLauncher.qml 2014-02-11 20:21:24 +0000
4813+++ tests/plugins/Ubuntu/Gestures/RightwardsLauncher.qml 2014-10-10 15:05:15 +0000
4814@@ -46,6 +46,9 @@
4815 id: dragArea
4816 objectName: "hpDragArea"
4817
4818+ // give some room for items to be dynamically stacked right behind him
4819+ z: 10.0
4820+
4821 width: units.gu(5)
4822
4823 direction: Direction.Rightwards
4824
4825=== added file 'tests/plugins/Ubuntu/Gestures/touchGateExample.qml'
4826--- tests/plugins/Ubuntu/Gestures/touchGateExample.qml 1970-01-01 00:00:00 +0000
4827+++ tests/plugins/Ubuntu/Gestures/touchGateExample.qml 2014-10-10 15:05:15 +0000
4828@@ -0,0 +1,27 @@
4829+/*
4830+ * Copyright (C) 2014 Canonical, Ltd.
4831+ *
4832+ * This program is free software; you can redistribute it and/or modify
4833+ * it under the terms of the GNU General Public License as published by
4834+ * the Free Software Foundation; version 3.
4835+ *
4836+ * This program is distributed in the hope that it will be useful,
4837+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4838+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4839+ * GNU General Public License for more details.
4840+ *
4841+ * You should have received a copy of the GNU General Public License
4842+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4843+ */
4844+
4845+import QtQuick 2.0
4846+import Ubuntu.Gestures 0.1
4847+
4848+Rectangle {
4849+ width: 720; height: 720
4850+
4851+ TouchGate {
4852+ anchors.fill: parent
4853+ objectName: "touchGate"
4854+ }
4855+}
4856
4857=== modified file 'tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp'
4858--- tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp 2014-03-07 10:49:30 +0000
4859+++ tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp 2014-10-10 15:05:15 +0000
4860@@ -1,5 +1,5 @@
4861 /*
4862- * Copyright (C) 2013 Canonical, Ltd.
4863+ * Copyright (C) 2013-2014 Canonical, Ltd.
4864 *
4865 * This program is free software; you can redistribute it and/or modify
4866 * it under the terms of the GNU General Public License as published by
4867@@ -19,37 +19,63 @@
4868 #include <QtQuick/QQuickView>
4869 #include <QtQml/QQmlEngine>
4870 #include <QPointer>
4871+#include <private/qquickmousearea_p.h>
4872+
4873+// C++ std lib
4874+#include <functional>
4875
4876 #include <DirectionalDragArea.h>
4877+#include <TouchRegistry.h>
4878
4879 #include "GestureTest.h"
4880
4881 using namespace UbuntuGestures;
4882
4883-class FakeTimer : public AbstractTimer
4884-{
4885- Q_OBJECT
4886-public:
4887- FakeTimer(const SharedTimeSource &timeSource, QObject *parent = 0)
4888- : UbuntuGestures::AbstractTimer(parent),
4889+class TouchMemento {
4890+public:
4891+ TouchMemento(const QTouchEvent *touchEvent);
4892+ Qt::TouchPointStates touchPointStates;
4893+ QList<QTouchEvent::TouchPoint> touchPoints;
4894+
4895+ bool containsTouchWithId(int touchId) const;
4896+};
4897+
4898+class DummyItem : public QQuickItem
4899+{
4900+ Q_OBJECT
4901+public:
4902+ DummyItem(QQuickItem *parent = 0);
4903+
4904+ QList<TouchMemento> touchEvents;
4905+ std::function<void(QTouchEvent*)> touchEventHandler;
4906+protected:
4907+ void touchEvent(QTouchEvent *event) override;
4908+private:
4909+ static void defaultTouchEventHandler(QTouchEvent *event);
4910+};
4911+
4912+class ComplexFakeTimer : public FakeTimer
4913+{
4914+ Q_OBJECT
4915+public:
4916+ ComplexFakeTimer(const SharedTimeSource &timeSource, QObject *parent = 0)
4917+ : FakeTimer(parent),
4918 m_timeSource(timeSource)
4919 {}
4920
4921- int interval() const override { return m_interval; }
4922- void setInterval(int msecs) override { m_interval = msecs; }
4923 void start() override {
4924 AbstractTimer::start();
4925- m_nextTimeoutTime = m_timeSource->msecsSinceReference() + (qint64)m_interval;
4926+ m_nextTimeoutTime = m_timeSource->msecsSinceReference() + (qint64)interval();
4927 }
4928
4929 void emitTimeout() {
4930- m_nextTimeoutTime += m_interval;
4931+ m_nextTimeoutTime += interval();
4932 Q_EMIT timeout();
4933 }
4934
4935 qint64 nextTimeoutTime() const { return m_nextTimeoutTime; }
4936+
4937 private:
4938- int m_interval;
4939 SharedTimeSource m_timeSource;
4940 qint64 m_nextTimeoutTime;
4941 };
4942@@ -62,6 +88,30 @@
4943 qint64 m_msecsSinceReference;
4944 };
4945
4946+/*
4947+ QQuickMouseArea::canceled() signal is not registered in the meta object system.
4948+ So using a QSignalSpy to track it won't work. Thus the only way to connect to it
4949+ is using its method address directly.
4950+ */
4951+class MouseAreaSpy : public QObject
4952+{
4953+ Q_OBJECT
4954+public:
4955+ MouseAreaSpy(QQuickMouseArea *mouseArea)
4956+ : canceledCount(0)
4957+ {
4958+ connect(mouseArea, &QQuickMouseArea::canceled,
4959+ this, &MouseAreaSpy::onMouseAreaCanceled);
4960+ }
4961+
4962+ int canceledCount;
4963+
4964+private Q_SLOTS:
4965+ void onMouseAreaCanceled() {
4966+ ++canceledCount;
4967+ }
4968+};
4969+
4970 class tst_DirectionalDragArea: public GestureTest
4971 {
4972 Q_OBJECT
4973@@ -87,10 +137,20 @@
4974 void sceneDistance();
4975 void sceneDistance_data();
4976 void disabledWhileDragging();
4977+ void oneFingerDownFollowedByLateSecondFingerDown();
4978+ void givesUpWhenLosesTouch();
4979+ void threeFingerDrag();
4980+ void immediateRecognitionWhenConstraintsDisabled();
4981+ void withdrawTouchOwnershipCandidacyIfDisabledDuringRecognition();
4982+ void withdrawTouchOwnershipCandidacyIfDisabledDuringRecognition_data();
4983+ void tappedSignal();
4984+ void tappedSignal_data();
4985+ void gettingTouchOwnershipMakesMouseAreaBehindGetCanceled();
4986
4987 private:
4988- void passTime(qint64 timeSpan);
4989- FakeTimer *fakeTimer;
4990+ void passTime(qint64 timeSpanMs);
4991+ TouchRegistry *touchRegistry;
4992+ ComplexFakeTimer *fakeTimer;
4993 QSharedPointer<FakeTimeSource> fakeTimeSource;
4994 };
4995
4996@@ -111,12 +171,19 @@
4997 QTRY_COMPARE(m_view->width(), (int)m_view->rootObject()->width());
4998 QTRY_COMPARE(m_view->height(), (int)m_view->rootObject()->height());
4999
5000+ touchRegistry = new TouchRegistry;
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches