Merge lp:~zsombi/ubuntu-ui-toolkit/migrate_unity8_gestures into lp:ubuntu-ui-toolkit/staging

Proposed by Zsombor Egri on 2015-10-21
Status: Merged
Approved by: Christian Dywan on 2015-11-17
Approved revision: 1736
Merged at revision: 1718
Proposed branch: lp:~zsombi/ubuntu-ui-toolkit/migrate_unity8_gestures
Merge into: lp:ubuntu-ui-toolkit/staging
Diff against target: 7195 lines (+6597/-24)
72 files modified
components.api (+23/-0)
debian/control (+23/-0)
debian/libubuntugestures-dev.install (+28/-0)
debian/libubuntugestures.install (+1/-0)
documentation/overview.qdoc (+7/-0)
examples/ubuntu-ui-toolkit-gallery/SwipeAreaPage.qml (+144/-0)
examples/ubuntu-ui-toolkit-gallery/Template.qml (+1/-0)
examples/ubuntu-ui-toolkit-gallery/WidgetsModel.qml (+5/-0)
examples/ubuntu-ui-toolkit-gallery/gallery (+1/-1)
examples/ubuntu-ui-toolkit-gallery/gallery-logging.config (+4/-0)
examples/ubuntu-ui-toolkit-gallery/ubuntu-ui-toolkit-gallery.pro (+2/-1)
export_modules_dir.sh (+2/-0)
features/ubuntu_qml_plugin.prf (+11/-0)
features/ubuntu_qt_module.prf (+19/-0)
src/Ubuntu/Components/plugin/gestures/CandidateInactivityTimer.cpp (+46/-0)
src/Ubuntu/Components/plugin/gestures/CandidateInactivityTimer.h (+52/-0)
src/Ubuntu/Components/plugin/gestures/damper.cpp (+24/-0)
src/Ubuntu/Components/plugin/gestures/damper.h (+89/-0)
src/Ubuntu/Components/plugin/gestures/ubuntugesturesqmlglobal.h (+24/-0)
src/Ubuntu/Components/plugin/gestures/ucswipearea.cpp (+958/-0)
src/Ubuntu/Components/plugin/gestures/ucswipearea.h (+98/-0)
src/Ubuntu/Components/plugin/gestures/ucswipearea_p.h (+157/-0)
src/Ubuntu/Components/plugin/plugin.cpp (+2/-0)
src/Ubuntu/Components/plugin/plugin.pri (+9/-4)
src/Ubuntu/Test/plugin/plugin.pri (+4/-2)
src/Ubuntu/Test/plugin/testplugin.cpp (+10/-0)
src/Ubuntu/Test/plugin/ucmousetouchadaptor.cpp (+198/-0)
src/Ubuntu/Test/plugin/ucmousetouchadaptor.h (+60/-0)
src/Ubuntu/Test/plugin/uctestcase.cpp (+2/-1)
src/Ubuntu/Test/plugin/uctestcase.h (+1/-1)
src/Ubuntu/Test/plugin/uctestextras.h (+2/-0)
src/Ubuntu/UbuntuGestures/UbuntuGestures.pro (+31/-0)
src/Ubuntu/UbuntuGestures/candidateinactivitytimer.cpp (+46/-0)
src/Ubuntu/UbuntuGestures/candidateinactivitytimer.h (+51/-0)
src/Ubuntu/UbuntuGestures/debughelpers.cpp (+95/-0)
src/Ubuntu/UbuntuGestures/debughelpers.h (+31/-0)
src/Ubuntu/UbuntuGestures/pool.h (+132/-0)
src/Ubuntu/UbuntuGestures/timer.cpp (+152/-0)
src/Ubuntu/UbuntuGestures/timer.h (+117/-0)
src/Ubuntu/UbuntuGestures/timesource.cpp (+47/-0)
src/Ubuntu/UbuntuGestures/timesource.h (+62/-0)
src/Ubuntu/UbuntuGestures/touchownershipevent.cpp (+35/-0)
src/Ubuntu/UbuntuGestures/touchownershipevent.h (+50/-0)
src/Ubuntu/UbuntuGestures/touchregistry.cpp (+520/-0)
src/Ubuntu/UbuntuGestures/touchregistry.h (+206/-0)
src/Ubuntu/UbuntuGestures/ubuntugesturesglobal.h (+23/-0)
src/Ubuntu/UbuntuGestures/unownedtouchevent.cpp (+39/-0)
src/Ubuntu/UbuntuGestures/unownedtouchevent.h (+45/-0)
src/src.pro (+18/-4)
sync.profile (+29/-0)
tests/license/checklicense.sh (+1/-1)
tests/qmlapicheck.sh (+1/-1)
tests/unit/add_makecheck.pri (+1/-1)
tests/unit/add_qmlmakecheck.pri (+1/-1)
tests/unit_x11/add_makecheck.pri (+1/-1)
tests/unit_x11/add_qmlmakecheck.pri (+1/-1)
tests/unit_x11/test-include.pri (+1/-1)
tests/unit_x11/tst_deprecated_theme_engine/tst_deprecated_theme_engine.cpp (+1/-1)
tests/unit_x11/tst_subtheming/tst_subtheming.cpp (+1/-1)
tests/unit_x11/tst_swipearea/DownwardsLauncher.qml (+72/-0)
tests/unit_x11/tst_swipearea/GestureTest.cpp (+140/-0)
tests/unit_x11/tst_swipearea/GestureTest.h (+92/-0)
tests/unit_x11/tst_swipearea/LeftwardsLauncher.qml (+76/-0)
tests/unit_x11/tst_swipearea/RightwardsLauncher.qml (+76/-0)
tests/unit_x11/tst_swipearea/UpwardsLauncher.qml (+76/-0)
tests/unit_x11/tst_swipearea/tst_swipearea.cpp (+1305/-0)
tests/unit_x11/tst_swipearea/tst_swipearea.pro (+13/-0)
tests/unit_x11/tst_swipearea/tst_swipearea.qml (+77/-0)
tests/unit_x11/tst_touchregistry/tst_TouchRegistry.cpp (+915/-0)
tests/unit_x11/tst_touchregistry/tst_touchregistry.pro (+6/-0)
tests/unit_x11/unit_x11.pro (+3/-1)
ubuntu-sdk.pro (+1/-0)
To merge this branch: bzr merge lp:~zsombi/ubuntu-ui-toolkit/migrate_unity8_gestures
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Approve on 2015-11-17
Christian Dywan Approve on 2015-11-17
Benjamin Zeller (community) Approve on 2015-11-16
Zsombor Egri (community) Approve on 2015-11-11
Daniel d'Andrada (community) 2015-10-21 Approve on 2015-11-10
Michał Sawicz 2015-10-21 Needs Information on 2015-10-23
Review via email: mp+275146@code.launchpad.net

Commit Message

Migrate DirectionalDragArea from Unity8, named as SwipeArea. Original code (from lp:unity8) by: Daniel d'Andrada <email address hidden>

Description of the Change

SwipeArea is the new name of the Unity8 DirectionalDragArea.

To post a comment you must log in.
1696. By Zsombor Egri on 2015-10-21

add trace functions

1697. By Zsombor Egri on 2015-10-21

documentation added, point properties compacted

1698. By Zsombor Egri on 2015-10-21

missing file added

1699. By Zsombor Egri on 2015-10-21

gallery page added, doc sample as well

1700. By Zsombor Egri on 2015-10-21

commented code removed

1701. By Zsombor Egri on 2015-10-21

specifying the Flickable relationship in the documentation

Daniel d'Andrada (dandrader) wrote :

I think you need the "Area" suffix in the name, otherwise it will be misleading. Because it *really* is an area that detects gestures.

review: Needs Fixing
Daniel d'Andrada (dandrader) wrote :

And I had the "Directional" in the name because it's not for all kinds of drag gestures. It's specifically for axis-aligned drags. It does not support dragging freely, in both X and Y axes. But I understand it makes for a mouthful if you keep it. On the other and it might clash if you provide some generic drag component in the future.

Daniel d'Andrada (dandrader) wrote :

DragGesture.svg is outdated. You're better off without it.

Michał Sawicz (saviq) wrote :

Shouldn't it be DragGestureArea? It is an area after all...

The gallery page for this is quite tricky to use, planning to update it somehow?

We discussed the potential need for a "monitoring-only" version of DragGesture, that would never grab the touch points, but work exactly the same otherwise. Now might be a good time to add?

Inline comments didn't work, so...

+ * The drag recognision is performed in a distanceThreshold, which is the size

→ recognition

+ * DragGesture {
+ * anchors {
+ * left: parent.left
+ * top: parent.top
+ * bottom: parent.bottom
+ * }
+ * height: units.gu(5)
+ * Label {
+ * text: "Drag upwards"
+ * anchors {
+ * centerIn: parent
+ * verticalOffset: parent.dragging ? parent.distance : 0
+ * }
+ * }
+ * }

Something wrong here with anchors - it's anchored left/top/bottom but has height, should probably be anchored left/right/bottom.

+ * \qmlproperty bool DragGesture::immediateRecognition
+ * \readonly
+ * Drives whether the gesture should be recognized as soon as the touch lands on
+ * the area. With this property set it will work the same way as a MultiPointTouchArea,
+ *
+ * Defaults to false. In most cases this should not be set.

Not sure of the use case, Daniel?

+ * \qmlproperty real DragGesture::sceneDistance
+ * \readonly
+ * The distance travelled by the finger along the axis specified by \l direction
+ * in scene coordinates

Distance in coordinates? Shouldn't distance be oblivious of coordinates?

TouchRegistry documentation isn't built, not sure if we want to commit to it, though.

[TBC]

review: Needs Fixing
Daniel d'Andrada (dandrader) wrote :

On 21/10/2015 13:14, Michał Sawicz wrote:
> + * \qmlproperty bool DragGesture::immediateRecognition
> + * \readonly
> + * Drives whether the gesture should be recognized as soon as the touch lands on
> + * the area. With this property set it will work the same way as a MultiPointTouchArea,
> + *
> + * Defaults to false. In most cases this should not be set.
>
> Not sure of the use case, Daniel?

Indicator panel drag handles.

Daniel d'Andrada (dandrader) wrote :

On 21/10/2015 13:14, Michał Sawicz wrote:
> + * \qmlproperty real DragGesture::sceneDistance
> + * \readonly
> + * The distance travelled by the finger along the axis specified by \l direction
> + * in scene coordinates
>
> Distance in coordinates? Shouldn't distance be oblivious of coordinates?

In scene coordinates. No, "distance" is the length of a line segment in
some coordinate system.

This property is in scene coordinates. The "distance" one (without the
"scene" prefix) is in local item coordinates.

Daniel d'Andrada (dandrader) wrote :

It's missing tests/libs/UbuntuGestures/tst_TouchRegistry.cpp from unity8.

review: Needs Fixing
Zsombor Egri (zsombi) wrote :

> Shouldn't it be DragGestureArea? It is an area after all...

After Daniel's arguments I decided to move back to your naming. After all that is also a suitable name for it as well...

>
> The gallery page for this is quite tricky to use, planning to update it
> somehow?

Yes, we discussed it with Daniel that the DDA (DirectionalDragArea) should not be a child of a Flickable. so I am locking the child flickable as long as there's one drag area active.

>
> We discussed the potential need for a "monitoring-only" version of
> DragGesture, that would never grab the touch points, but work exactly the same
> otherwise. Now might be a good time to add?

Perhaps, but let's get that in a separate MR, this is already a monster one :)

>
> Inline comments didn't work, so...

That's weird, it used to work for us!

>
> + * The drag recognision is performed in a distanceThreshold, which is the
> size
>
> → recognition

Fixed.

>
>
> + * DragGesture {
> + * anchors {
> + * left: parent.left
> + * top: parent.top
> + * bottom: parent.bottom
> + * }
> + * height: units.gu(5)
> + * Label {
> + * text: "Drag upwards"
> + * anchors {
> + * centerIn: parent
> + * verticalOffset: parent.dragging ? parent.distance : 0
> + * }
> + * }
> + * }
>
> Something wrong here with anchors - it's anchored left/top/bottom but has
> height, should probably be anchored left/right/bottom.

Yes. Fixed. In addition the direction is also specified.

>
>
> + * \qmlproperty bool DragGesture::immediateRecognition
> + * \readonly
> + * Drives whether the gesture should be recognized as soon as the touch lands
> on
> + * the area. With this property set it will work the same way as a
> MultiPointTouchArea,
> + *
> + * Defaults to false. In most cases this should not be set.
>
> Not sure of the use case, Daniel?
>
>
> + * \qmlproperty real DragGesture::sceneDistance
> + * \readonly
> + * The distance travelled by the finger along the axis specified by \l
> direction
> + * in scene coordinates
>
> Distance in coordinates? Shouldn't distance be oblivious of coordinates?
>
> TouchRegistry documentation isn't built, not sure if we want to commit to it,
> though.

This is only the QML part. I am not exporting any libraries here. And seems it would make sense to move the whole UbuntuGestures library as well.

>
> [TBC]

1702. By Zsombor Egri on 2015-10-22

review comments applied; component renamed into DirectionalDragArea

Michał Sawicz (saviq) wrote :

> > + * \qmlproperty real DragGesture::sceneDistance
> > + * \readonly
> > + * The distance travelled by the finger along the axis specified by \l
> direction
> > + * in scene coordinates
> >
> > Distance in coordinates? Shouldn't distance be oblivious of coordinates?
>
> In scene coordinates. No, "distance" is the length of a line segment in
> some coordinate system.
>
> This property is in scene coordinates. The "distance" one (without the
> "scene" prefix) is in local item coordinates.

So it's not "distance travelled", because if the finger went back and forth, travel increases all the time. Not sure if there's a better word for "distance", but the doc needs improving IMO.

Also:
+ * The distance travelled by the finger along the axis specified by \l direction.

Not "distance travelled", definitely not "by the finger". Not even sure how to describe that, it's the distance between the current position and TouchBegin point, in local coordinates, or something.

+ // TODO: Remove this workaround once we start using Qt 5.4
Time to do so?

+ // TODO: Consider when more than one touch starts in the same event (although it's not possible
+ // with Mir's android-input). Have to track them all. Consider it a plus/bonus.

Now, I believe, we've moved away from android-input, can this become a problem?

At least some of the documentation in ucdirectionaldragarea.h and ucdirectionaldragarea_p.h seems duplicate?

review: Needs Information
1703. By Zsombor Egri on 2015-11-05

addin Gestures library to the project

1704. By Zsombor Egri on 2015-11-05

fixups

1705. By Zsombor Egri on 2015-11-06

fix library build

Daniel d'Andrada (dandrader) wrote :

Would be great if you could turn all the logging from the current have-to-rebuild model (#ifdef) to QLoggingCategory (I do hope QLoggingCategories have negligible impact when turned off). I didn't bother doing that in unity8 because since the component lived in our source tree, rebuilding was trivial and painless. But this won't be the case anymore once it moves to ubuntu-ui-toolkit.

NB: Please keep the message format, like printing object names, as this helps immensely to tell instances apart.

1706. By Zsombor Egri on 2015-11-06

staging sync

1707. By Zsombor Egri on 2015-11-06

fixup, DirectionalDragArea renamed into SwipeArea

Zsombor Egri (zsombi) wrote :

> Would be great if you could turn all the logging from the current have-to-
> rebuild model (#ifdef) to QLoggingCategory (I do hope QLoggingCategories have
> negligible impact when turned off). I didn't bother doing that in unity8
> because since the component lived in our source tree, rebuilding was trivial
> and painless. But this won't be the case anymore once it moves to ubuntu-ui-
> toolkit.
>
> NB: Please keep the message format, like printing object names, as this helps
> immensely to tell instances apart.

Ok, I will try to move to that. It has some impact on the execution, but it suppose dot be minimal - otherwise none would use it.

But first we need to spend more time with the API, especially that the AxisVelocityCalculator will be needed as exported QML type. touchScenePos is redundant as mapToItem(null, touchPos.x, touchPos.y) would be enough to map to scene.

AxisVelocityCalculator API needs some thinking too... And we'd like to get this out ASAP as we need it in the BottomEdge release.

1708. By Zsombor Egri on 2015-11-09

fixing gallery

1709. By Zsombor Egri on 2015-11-09

API and test launcher fix

1710. By Zsombor Egri on 2015-11-09

licence changed; add LD_LIBRARY_PATH to unit and unit_x11 makecheck

1711. By Zsombor Egri on 2015-11-09

leftover :/

1712. By Zsombor Egri on 2015-11-09

proper proeprty names for touch positions

1713. By Zsombor Egri on 2015-11-09

fixing SwipeArea tests and test launcher

1714. By Zsombor Egri on 2015-11-09

fix documentation and remove orphan code

1715. By Zsombor Egri on 2015-11-09

remove workaround for touch events required prior Qt 5.4

1716. By Zsombor Egri on 2015-11-09

use UbuntuTestCase for the test file

1717. By Zsombor Egri on 2015-11-09

fix API; remove unneeded axisvelocitycalculator

1718. By Zsombor Egri on 2015-11-09

remove touchScenePosition()

1719. By Zsombor Egri on 2015-11-09

remove sceneDirection property

1720. By Zsombor Egri on 2015-11-09

simplify position updates

1721. By Zsombor Egri on 2015-11-09

reorder members to optimize allocation

1722. By Zsombor Egri on 2015-11-09

add include folder to the licence exceptions

1723. By Zsombor Egri on 2015-11-09

touch registry tests added

1724. By Zsombor Egri on 2015-11-09

staging sync

1725. By Zsombor Egri on 2015-11-10

packaging added

1726. By Zsombor Egri on 2015-11-10

add logging support for SwipeArea and ActiveTouchPoints; logging can be activated through ubuntu.components.SwipeArea.log.debug=true and ubuntu.components.SwipeArea.ActiveTouchInfo.log.debug=true either set individually to QT_LOGGING_RULES or in a file set to QT_LOGGING_CONF env variable; gallery-logging.config added and gallery runs with this rule file when launched from terminal

1727. By Zsombor Egri on 2015-11-10

turn logging default off

Daniel d'Andrada (dandrader) wrote :

Please remove that from ucswipearea.h: " See doc/DirectionalDragArea.svg"

Daniel d'Andrada (dandrader) wrote :

Looking good otherwise

review: Approve
Daniel d'Andrada (dandrader) wrote :

You have two copies of MouseTouchAdaptor:
ubuntu-ui-toolkit-launcher/MouseTouchAdaptor.h
src/Ubuntu/Test/plugin/ucmousetouchadaptor.h

Would be great if you could have only one in your source tree.

Daniel d'Andrada (dandrader) wrote :

Could you please add this line to the commit message:
"Original code (from lp:unity8) by: Daniel d'Andrada <email address hidden>"

Once it gets removed from lp:unity8 it will be pretty hard to track down who originally wrote it in case of questions.

Zsombor Egri (zsombi) wrote :

> You have two copies of MouseTouchAdaptor:
> ubuntu-ui-toolkit-launcher/MouseTouchAdaptor.h
> src/Ubuntu/Test/plugin/ucmousetouchadaptor.h
>
> Would be great if you could have only one in your source tree.

Ah, good catch!

Zsombor Egri (zsombi) wrote :

> Could you please add this line to the commit message:
> "Original code (from lp:unity8) by: Daniel d'Andrada
> <email address hidden>"
>
> Once it gets removed from lp:unity8 it will be pretty hard to track down who
> originally wrote it in case of questions.

Sure. Done.

Zsombor Egri (zsombi) wrote :

> You have two copies of MouseTouchAdaptor:
> ubuntu-ui-toolkit-launcher/MouseTouchAdaptor.h
> src/Ubuntu/Test/plugin/ucmousetouchadaptor.h
>
> Would be great if you could have only one in your source tree.

Ahm... actually those are two separate modules, the launcher has its own stuff, toolkit test code has its own as well. I also noticed that we have a collision between the MouseTouchAdaptror I took from unity8 and our touch functions from TestExtras. I have to fix that as well.

Daniel d'Andrada (dandrader) wrote :

On 10/11/2015 12:26, Zsombor Egri wrote:
>> You have two copies of MouseTouchAdaptor:
>> ubuntu-ui-toolkit-launcher/MouseTouchAdaptor.h
>> src/Ubuntu/Test/plugin/ucmousetouchadaptor.h
>>
>> Would be great if you could have only one in your source tree.
> Ahm... actually those are two separate modules, the launcher has its own stuff, toolkit test code has its own as well. I also noticed that we have a collision between the MouseTouchAdaptror I took from unity8 and our touch functions from TestExtras. I have to fix that as well.

You could still compile it twice but have only one copy of it in your
source tree

1728. By Zsombor Egri on 2015-11-10

make sure we have only one touch device registered no matter if MouseTouchAdaptor or TestExtras is used; add QT_LOGGING_RULES to test runner to stop logging on tests

Zsombor Egri (zsombi) wrote :

qmlapicheck.sh prints loads of warnings. Must be fixed!

review: Needs Fixing
Christian Dywan (kalikiana) wrote :

+
+ // disable logging filters
+ QLoggingCategory::setFilterRules(QStringLiteral("ubuntu.components.SwipeArea.log.debug=false"));
+ QLoggingCategory::setFilterRules(QStringLiteral("ubuntu.components.SwipeArea.ActiveTouchInfo.log.debug=false"));

This overrides the filter rule set by apicheck which is "*=false".

Also the second filter replaces the first one.

It's definitely wrong for a QML component to override the global filter. If any other component or library does that you'll get undefined behavior. And as seen here, if the app (here apicheck) sets it, you're also screwed, depending on who sets it first.

review: Needs Fixing
Daniel d'Andrada (dandrader) wrote :

On 10/11/2015 16:31, Christian Dywan wrote:
> Review: Needs Fixing
>
> +
> + // disable logging filters
> + QLoggingCategory::setFilterRules(QStringLiteral("ubuntu.components.SwipeArea.log.debug=false"));
> + QLoggingCategory::setFilterRules(QStringLiteral("ubuntu.components.SwipeArea.ActiveTouchInfo.log.debug=false"));
>
> This overrides the filter rule set by apicheck which is "*=false".
>
> Also the second filter replaces the first one.
>
> It's definitely wrong for a QML component to override the global filter. If any other component or library does that you'll get undefined behavior. And as seen here, if the app (here apicheck) sets it, you're also screwed, depending on who sets it first.

By the way, when you define a logging category you can specify its
default logging level. This would be a more appropriate approach:
Q_LOGGING_CATEGORY(ucSwipeArea, "ubuntu.components.SwipeArea", QtWarningMsg)

And, by the way, the ".log" suffix is redundant. We already know it's
about logging. It's a logging category after all! :)

Zsombor Egri (zsombi) wrote :

> +
> + // disable logging filters
> + QLoggingCategory::setFilterRules(QStringLiteral("ubuntu.components.SwipeA
> rea.log.debug=false"));
> + QLoggingCategory::setFilterRules(QStringLiteral("ubuntu.components.SwipeArea
> .ActiveTouchInfo.log.debug=false"));
>
> This overrides the filter rule set by apicheck which is "*=false".

If you read the docs, you will see it won't.

>
> Also the second filter replaces the first one.

Nope, the second filter is a different filter. Please read the docs if you are in dpoubths. Also, you can test it by enabling the two same time or separately from gallery-logs.config

>
> It's definitely wrong for a QML component to override the global filter. If
> any other component or library does that you'll get undefined behavior. And as
> seen here, if the app (here apicheck) sets it, you're also screwed, depending
> on who sets it first.

Please, again, read the docs. The order is (quote from Qt Docs):
QtProject/qtlogging.ini
setFilterRules()
QT_LOGGING_CONF
QT_LOGGING_RULES

Zsombor Egri (zsombi) wrote :

> By the way, when you define a logging category you can specify its
> default logging level. This would be a more appropriate approach:
> Q_LOGGING_CATEGORY(ucSwipeArea, "ubuntu.components.SwipeArea", QtWarningMsg)

Eventually QtDebugMsg, as we use qCDebug() here and not qCWarning(). Or we switch to use qCWarning() instead.

> And, by the way, the ".log" suffix is redundant. We already know it's
> about logging. It's a logging category after all! :)

Ok, makes sense.

Daniel d'Andrada (dandrader) wrote :

On 11/11/2015 06:31, Zsombor Egri wrote:
>> By the way, when you define a logging category you can specify its
>> >default logging level. This would be a more appropriate approach:
>> >Q_LOGGING_CATEGORY(ucSwipeArea, "ubuntu.components.SwipeArea", QtWarningMsg)
> Eventually QtDebugMsg, as we use qCDebug() here and not qCWarning(). Or we switch to use qCWarning() instead.
>

Ok, maybe you misunderstood me. The construction above says that by
default only warnings and above (like criticals) are logged. debug
messages being ignored. Then if you want debug messages to get logged as
well you have to explicitly enable then in the logging configuration ini
file or env var.

This is what should be done for SwipeArea debug messages otherwise they
will flood logs with events that are not interesting at all unless
you're debugging some swipe related issue.

Zsombor Egri (zsombi) wrote :

OK, I think I misunderstood you partly. Let me try again :)

> +
> + // disable logging filters
> + QLoggingCategory::setFilterRules(QStringLiteral("ubuntu.components.SwipeA
> rea.log.debug=false"));
> + QLoggingCategory::setFilterRules(QStringLiteral("ubuntu.components.SwipeArea
> .ActiveTouchInfo.log.debug=false"));
>
> This overrides the filter rule set by apicheck which is "*=false".

Nope, as the QT_LOGGING_RULES env var overrides the setFilterRules() settings.

>
> Also the second filter replaces the first one.

THIS is true.

>
> It's definitely wrong for a QML component to override the global filter. If
> any other component or library does that you'll get undefined behavior. And as
> seen here, if the app (here apicheck) sets it, you're also screwed, depending
> on who sets it first.

I do not see from where have you got this idea that we do override the global filter. We don't. We define two special categories here, one for SwipeArea and one for the internal ActiveTouchInfo. The default category is intact, so we don't override the global filter in any component. specifying *.debug=false means we turn of all the debug logs so the api check (and also the other loggers) are turned off. And again QT_LOGGING_CONF and/or QT_LOGGING_RULES overrides the setFilterRules(). We have to turn them off, otherwise the Q_LOGGING_CATEGORY() will left them turned on. Unfortunately.

1729. By Zsombor Egri on 2015-11-11

remove .log from categories; rise the message type to warnings to rule out qCDebug() logs s default, therefore no more need to override the filter rules

Christian Dywan (kalikiana) wrote :

> > +
> > + // disable logging filters
> > +
> QLoggingCategory::setFilterRules(QStringLiteral("ubuntu.components.SwipeA
> > rea.log.debug=false"));
> > +
> QLoggingCategory::setFilterRules(QStringLiteral("ubuntu.components.SwipeArea
> > .ActiveTouchInfo.log.debug=false"));
> >
> > This overrides the filter rule set by apicheck which is "*=false".
>
> Nope, as the QT_LOGGING_RULES env var overrides the setFilterRules() settings.
>
> >
> > Also the second filter replaces the first one.
>
> THIS is true.
>
> >
> > It's definitely wrong for a QML component to override the global filter. If
> > any other component or library does that you'll get undefined behavior. And
> as
> > seen here, if the app (here apicheck) sets it, you're also screwed,
> depending
> > on who sets it first.
>
> I do not see from where have you got this idea that we do override the global
> filter. We don't. We define two special categories here, one for SwipeArea and
> one for the internal ActiveTouchInfo. The default category is intact, so we
> don't override the global filter in any component. specifying *.debug=false
> means we turn of all the debug logs so the api check (and also the other
> loggers) are turned off. And again QT_LOGGING_CONF and/or QT_LOGGING_RULES
> overrides the setFilterRules(). We have to turn them off, otherwise the
> Q_LOGGING_CATEGORY() will left them turned on. Unfortunately.

You agreed above that QLoggingCategory::setFilterRules replaces whatever rules were set first - then you must realize that setFilterRules is global. You set it, apicheck sets it, an app using Ubuntu.Components sets it, only one of these can win.
For apicheck it's fine if QT_LOGGING_* is set by a developer or script btw, if intended, since that allows you to debug the component causing the spam (which some day should be done for Ubuntu.Components).

Daniel's suggestion seems sensible - if you set the default level for the category to warnings only you won't effectively see any debug logging.

Zsombor Egri (zsombi) wrote :

All changes done.

review: Approve
Christian Dywan (kalikiana) wrote :

./examples/ubuntu-ui-toolkit-gallery/ubuntu-ui-toolkit-gallery.qml:18:1 plugin cannot be loaded for module "Ubuntu.Components": Cannot load library ./qml/Ubuntu/Components/libUbuntuComponents.so: (libUbuntuGestures.so.5: open shared object file: No such file or directory)
 import Ubuntu.Components 1.3
 ^
Usage: ./ubuntu-ui-toolkit-launcher/ubuntu-ui-toolkit-launcher [options] filename

Should I do something special to run the launcher now? Running it from the build folder like I used to doesn't work anymore with the branch.

Zsombor Egri (zsombi) wrote :

> ./examples/ubuntu-ui-toolkit-gallery/ubuntu-ui-toolkit-gallery.qml:18:1 plugin
> cannot be loaded for module "Ubuntu.Components": Cannot load library
> ./qml/Ubuntu/Components/libUbuntuComponents.so: (libUbuntuGestures.so.5: open
> shared object file: No such file or directory)
> import Ubuntu.Components 1.3
> ^
> Usage: ./ubuntu-ui-toolkit-launcher/ubuntu-ui-toolkit-launcher [options]
> filename
>
> Should I do something special to run the launcher now? Running it from the
> build folder like I used to doesn't work anymore with the branch.

If you launch gallery from the terminal, it should work, the scripts have the LD_LIBRARY_PATH set. If you run it from QtCreator, you must set LD_LIBRARY_PATH to
a) ./libn if in-source build is used
b) relative path to your out-of-source lib path if that one is chosen

if you launch launcher just like that you need to set the LD_LIBRARY_PATH

Benjamin Zeller (zeller-benjamin) wrote :

See inline comments

review: Needs Fixing
1730. By Zsombor Egri on 2015-11-13

fixing wasSingleShot setting, remove GCC pragmas

Benjamin Zeller (zeller-benjamin) wrote :

LGTM

review: Approve
Christian Dywan (kalikiana) wrote :

+#define TOUCHREGISTRY_DEBUG 0

Why not use proper logging here as well?

+/* Defines an interface for a Timer. Useful for tests. */
2931 +class UBUNTUGESTURES_EXPORT AbstractTimer : public QObject

I'm inclined to agree with the comment - why isn't this in UbuntuTest?

+Description: Ubuntu gestures library - DragArea
65 + Ubuntu gestures library with DragArea

Should be "SwipeArea".

There's also many uses of "dragArea" in the tests, which might be fixed with a search and replace, but I could live with those.

More importantly, what's the plan for DraggingArea? I see no FIXME and no bug number anywhere. Even if that's done in a follow-up we should have a plan so it's not forgotten.

review: Needs Fixing
Zsombor Egri (zsombi) wrote :

> +#define TOUCHREGISTRY_DEBUG 0
>
> Why not use proper logging here as well?

Good point. Will do that! in RevNo 1731.
>
> +/* Defines an interface for a Timer. Useful for tests. */
> 2931 +class UBUNTUGESTURES_EXPORT AbstractTimer : public QObject
>
> I'm inclined to agree with the comment - why isn't this in UbuntuTest?

That is a leftover from the old times... I'll remove that line, as the class is the base class for the timers used in the gesture recognition. Revno 1732.

>
> +Description: Ubuntu gestures library - DragArea
> 65 + Ubuntu gestures library with DragArea
>
> Should be "SwipeArea".

You seemed to have an older merge. It has been fixed since that.

>
> There's also many uses of "dragArea" in the tests, which might be fixed with a
> search and replace, but I could live with those.

done, revno 1733.

>
> More importantly, what's the plan for DraggingArea? I see no FIXME and no bug
> number anywhere. Even if that's done in a follow-up we should have a plan so
> it's not forgotten.

I think we will ditch that, I checked it but I thought to have a separate MR for that. Here's a bug for it: https://bugs.launchpad.net/ubuntu/+source/ubuntu-ui-toolkit/+bug/1517008

1731. By Zsombor Egri on 2015-11-17

use logging for TouchRegistry; fix parameters for the SwipeArea signals; do not export debughelper functions

1732. By Zsombor Egri on 2015-11-17

remove misleading comment

1733. By Zsombor Egri on 2015-11-17

renaming dragArea into swipeArea

1734. By Zsombor Egri on 2015-11-17

fix API file

Daniel d'Andrada (dandrader) wrote :

On 17/11/2015 10:23, Zsombor Egri wrote:
>> +/* Defines an interface for a Timer. Useful for tests. */
>> >2931 +class UBUNTUGESTURES_EXPORT AbstractTimer : public QObject
>> >
>> >I'm inclined to agree with the comment - why isn't this in UbuntuTest?
> That is a leftover from the old times... I'll remove that line, as the class is the base class for the timers used in the gesture recognition. Revno 1732.
>

No, the whole point of hiding the real timers behind this interface is
to have the ability to use fake/mock timers in tests. So it does exactly
what the commment says.

Zsombor Egri (zsombi) wrote :

> On 17/11/2015 10:23, Zsombor Egri wrote:
> >> +/* Defines an interface for a Timer. Useful for tests. */
> >> >2931 +class UBUNTUGESTURES_EXPORT AbstractTimer : public QObject
> >> >
> >> >I'm inclined to agree with the comment - why isn't this in UbuntuTest?
> > That is a leftover from the old times... I'll remove that line, as the class
> is the base class for the timers used in the gesture recognition. Revno 1732.
> >
>
> No, the whole point of hiding the real timers behind this interface is
> to have the ability to use fake/mock timers in tests. So it does exactly
> what the commment says.

Christian was wondering whether the stuff should be moved in UbuntuTest module, as it wasn't clear. But as SwipeArea uses it as well, we must keep it here... not sure about the fake timers then... Or what exactly should we export at all from this lib?

Christian Dywan (kalikiana) wrote :

+# debughelpers.h \
2346 + timer.h \

Should this be removed?

review: Needs Fixing
1735. By Zsombor Egri on 2015-11-17

revert removing debughelper.h

1736. By Zsombor Egri on 2015-11-17

fix package description

Zsombor Egri (zsombi) wrote :

> +# debughelpers.h \
> 2346 + timer.h \
>
> Should this be removed?

Nope, should not, only the declspec was changed, so not being exported by the lib.

Christian Dywan (kalikiana) wrote :

Thanks!

review: Approve

Preview Diff

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

Subscribers

People subscribed via source and target branches