@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2014 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -14,64 +14,32 @@
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1515 */
1616
17+#define ACTIVETOUCHESINFO_DEBUG 0
18+#define DIRECTIONALDRAGAREA_DEBUG 0
19+
1720#include "DirectionalDragArea.h"
1821
22+#include <QQuickWindow>
1923#include <QtCore/qmath.h>
20−#include <QtCore/QTimer>
2124#include <QDebug>
2225
23−using namespace UbuntuGestures;
26+#pragma GCC diagnostic push
27+#pragma GCC diagnostic ignored "-pedantic"
28+#include <private/qquickwindow_p.h>
29+#pragma GCC diagnostic pop
2430
25−#define DIRECTIONALDRAGAREA_DEBUG 0
31+// local
32+#include "TouchOwnershipEvent.h"
33+#include "TouchRegistry.h"
34+#include "UnownedTouchEvent.h"
35+
36+using namespace UbuntuGestures;
2637
2738#if DIRECTIONALDRAGAREA_DEBUG
2839#define DDA_DEBUG(msg) qDebug("[DDA] " msg)
29−namespace {
30−QString touchPointStateToString(Qt::TouchPointState state) {
31− switch (state) {
32− case Qt::TouchPointPressed:
33− return QString("pressed");
34− case Qt::TouchPointMoved:
35− return QString("moved");
36− case Qt::TouchPointStationary:
37− return QString("stationary");
38− default: // Qt::TouchPointReleased:
39− return QString("released");
40− }
41−}
42−QString touchEventToString(QTouchEvent *ev)
43−{
44− QString message;
45−
46− switch (ev->type()) {
47− case QEvent::TouchBegin:
48− message.append("TouchBegin ");
49− break;
50− case QEvent::TouchUpdate:
51− message.append("TouchUpdate ");
52− break;
53− case QEvent::TouchEnd:
54− message.append("TouchEnd ");
55− break;
56− default: //QEvent::TouchCancel
57− message.append("TouchCancel ");
58− }
59−
60− for (int i=0; i < ev->touchPoints().size(); ++i) {
61−
62− const QTouchEvent::TouchPoint& touchPoint = ev->touchPoints().at(i);
63− message.append(
64− QString("(id:%1, state:%2, scenePos:(%3,%4)) ")
65− .arg(touchPoint.id())
66− .arg(touchPointStateToString(touchPoint.state()))
67− .arg(touchPoint.scenePos().x())
68− .arg(touchPoint.scenePos().y())
69− );
70− }
71−
72− return message;
73−}
40+#include "DebugHelpers.h"
7441
42+namespace {
7543const char *statusToString(DirectionalDragArea::Status status)
7644{
7745 if (status == DirectionalDragArea::WaitingForTouch) {
@@ -88,23 +56,6 @@ const char *statusToString(DirectionalDragArea::Status status)
8856#define DDA_DEBUG(msg) do{}while(0)
8957#endif // DIRECTIONALDRAGAREA_DEBUG
9058
91−// Essentially a QTimer wrapper
92−class RecognitionTimer : public UbuntuGestures::AbstractTimer
93−{
94− Q_OBJECT
95−public:
96− RecognitionTimer(QObject *parent) : UbuntuGestures::AbstractTimer(parent) {
97− m_timer.setSingleShot(false);
98− connect(&m_timer, &QTimer::timeout,
99− this, &UbuntuGestures::AbstractTimer::timeout);
100− }
101− virtual int interval() const { return m_timer.interval(); }
102− virtual void setInterval(int msecs) { m_timer.setInterval(msecs); }
103− virtual void start() { m_timer.start(); UbuntuGestures::AbstractTimer::start(); }
104− virtual void stop() { m_timer.stop(); UbuntuGestures::AbstractTimer::stop(); }
105−private:
106− QTimer m_timer;
107−};
10859
10960DirectionalDragArea::DirectionalDragArea(QQuickItem *parent)
11061 : QQuickItem(parent)
@@ -126,12 +77,14 @@ DirectionalDragArea::DirectionalDragArea(QQuickItem *parent)
12677 , m_timeSource(new RealTimeSource)
12778 , m_activeTouches(m_timeSource)
12879{
129− setRecognitionTimer(new RecognitionTimer(this));
80+ setRecognitionTimer(new Timer(this));
13081 m_recognitionTimer->setInterval(60);
82+ m_recognitionTimer->setSingleShot(false);
13183
13284 m_velocityCalculator = new AxisVelocityCalculator(this);
13385
134− connect(this, &QQuickItem::enabledChanged, this, &DirectionalDragArea::onEnabledChanged);
86+ connect(this, &QQuickItem::enabledChanged, this, &DirectionalDragArea::giveUpIfDisabledOrInvisible);
87+ connect(this, &QQuickItem::visibleChanged, this, &DirectionalDragArea::giveUpIfDisabledOrInvisible);
13588}
13689
13790Direction::Type DirectionalDragArea::direction() const
@@ -214,6 +167,7 @@ void DirectionalDragArea::setRecognitionTimer(UbuntuGestures::AbstractTimer *tim
214167{
215168 int interval = 0;
216169 bool timerWasRunning = false;
170+ bool wasSingleShot = false;
217171
218172 // can be null when called from the constructor
219173 if (m_recognitionTimer) {
@@ -226,6 +180,7 @@ void DirectionalDragArea::setRecognitionTimer(UbuntuGestures::AbstractTimer *tim
226180
227181 m_recognitionTimer = timer;
228182 timer->setInterval(interval);
183+ timer->setSingleShot(wasSingleShot);
229184 connect(timer, &UbuntuGestures::AbstractTimer::timeout,
230185 this, &DirectionalDragArea::checkSpeed);
231186 if (timerWasRunning) {
@@ -280,8 +235,144 @@ qreal DirectionalDragArea::touchSceneY() const
280235 return m_previousScenePos.y();
281236}
282237
238+bool DirectionalDragArea::event(QEvent *event)
239+{
240+ if (event->type() == TouchOwnershipEvent::touchOwnershipEventType()) {
241+ touchOwnershipEvent(static_cast<TouchOwnershipEvent *>(event));
242+ return true;
243+ } else if (event->type() == UnownedTouchEvent::unownedTouchEventType()) {
244+ unownedTouchEvent(static_cast<UnownedTouchEvent *>(event));
245+ return true;
246+ } else {
247+ return QQuickItem::event(event);
248+ }
249+}
250+
251+void DirectionalDragArea::touchOwnershipEvent(TouchOwnershipEvent *event)
252+{
253+ if (event->gained()) {
254+ QVector<int> ids;
255+ ids.append(event->touchId());
256+ DDA_DEBUG("grabbing touch");
257+ grabTouchPoints(ids);
258+
259+ // Work around for Qt bug. If we grab a touch that is being used for mouse pointer
260+ // emulation it will cause the emulation logic to go nuts.
261+ // Thus we have to also grab the mouse in this case.
262+ // TODO: Report bug to Qt
263+ if (window()) {
264+ QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(window());
265+ if (windowPrivate->touchMouseId == event->touchId() && window()->mouseGrabberItem()) {
266+ DDA_DEBUG("removing mouse grabber");
267+ window()->mouseGrabberItem()->ungrabMouse();
268+ }
269+ }
270+ } else {
271+ // We still wanna know when it ends for keeping the composition time window up-to-date
272+ TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
273+
274+ setStatus(WaitingForTouch);
275+ }
276+}
277+
278+void DirectionalDragArea::unownedTouchEvent(UnownedTouchEvent *unownedTouchEvent)
279+{
280+ QTouchEvent *event = unownedTouchEvent->touchEvent();
281+
282+ Q_ASSERT(!event->touchPointStates().testFlag(Qt::TouchPointPressed));
283+
284+ #if DIRECTIONALDRAGAREA_DEBUG
285+ // TODO Consider using qCDebug() when available (Qt 5.2)
286+ qDebug() << "[DDA] Unowned" << m_timeSource->msecsSinceReference()
287+ << qPrintable(touchEventToString(event));
288+ #endif
289+
290+ switch (m_status) {
291+ case WaitingForTouch:
292+ // do nothing
293+ break;
294+ case Undecided:
295+ Q_ASSERT(isEnabled() && isVisible());
296+ unownedTouchEvent_undecided(unownedTouchEvent);
297+ break;
298+ default: // Recognized:
299+ // do nothing
300+ break;
301+ }
302+
303+ m_activeTouches.update(event);
304+}
305+
306+void DirectionalDragArea::unownedTouchEvent_undecided(UnownedTouchEvent *unownedTouchEvent)
307+{
308+ const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(unownedTouchEvent->touchEvent());
309+ if (!touchPoint) {
310+ qCritical() << "DirectionalDragArea[status=Undecided]: touch " << m_touchId
311+ << "missing from UnownedTouchEvent without first reaching state Qt::TouchPointReleased. "
312+ "Considering it as released.";
313+
314+ TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
315+ setStatus(WaitingForTouch);
316+ return;
317+ }
318+
319+ const QPointF &touchScenePos = touchPoint->scenePos();
320+
321+ if (touchPoint->state() == Qt::TouchPointReleased) {
322+ // touch has ended before recognition concluded
323+ DDA_DEBUG("Touch has ended before recognition concluded");
324+ TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
325+ emitSignalIfTapped();
326+ setStatus(WaitingForTouch);
327+ return;
328+ }
329+
330+ m_previousDampedScenePos.setX(m_dampedScenePos.x());
331+ m_previousDampedScenePos.setY(m_dampedScenePos.y());
332+ m_dampedScenePos.update(touchScenePos);
333+ updateVelocityCalculator(touchScenePos);
334+
335+ if (!pointInsideAllowedArea()) {
336+ DDA_DEBUG("Rejecting gesture because touch point is outside allowed area.");
337+ TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
338+ // We still wanna know when it ends for keeping the composition time window up-to-date
339+ TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
340+ setStatus(WaitingForTouch);
341+ return;
342+ }
343+
344+ if (!movingInRightDirection()) {
345+ DDA_DEBUG("Rejecting gesture because touch point is moving in the wrong direction.");
346+ TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
347+ // We still wanna know when it ends for keeping the composition time window up-to-date
348+ TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
349+ setStatus(WaitingForTouch);
350+ return;
351+ }
352+
353+ setPreviousPos(touchPoint->pos());
354+ setPreviousScenePos(touchScenePos);
355+
356+ if (isWithinTouchCompositionWindow()) {
357+ // There's still time for some new touch to appear and ruin our party as it would be combined
358+ // with our m_touchId one and therefore deny the possibility of a single-finger gesture.
359+ DDA_DEBUG("Sill within composition window. Let's wait more.");
360+ return;
361+ }
362+
363+ if (movedFarEnough(touchScenePos)) {
364+ TouchRegistry::instance()->requestTouchOwnership(m_touchId, this);
365+ setStatus(Recognized);
366+ } else {
367+ DDA_DEBUG("Didn't move far enough yet. Let's wait more.");
368+ }
369+}
370+
283371void DirectionalDragArea::touchEvent(QTouchEvent *event)
284372{
373+ // TODO: Consider when more than one touch starts in the same event (although it's not possible
374+ // with Mir's android-input). Have to track them all. Consider it a plus/bonus.
375+
285376 #if DIRECTIONALDRAGAREA_DEBUG
286377 // TODO Consider using qCDebug() when available (Qt 5.2)
287378 qDebug() << "[DDA]" << m_timeSource->msecsSinceReference()
@@ -310,29 +401,34 @@ void DirectionalDragArea::touchEvent(QTouchEvent *event)
310401
311402void DirectionalDragArea::touchEvent_absent(QTouchEvent *event)
312403{
404+ // TODO: accept/reject is for the whole event, not per touch id. See how that affects us.
405+
313406 if (!event->touchPointStates().testFlag(Qt::TouchPointPressed)) {
314407 // Nothing to see here. No touch starting in this event.
315408 return;
316409 }
317410
411+ // to be proven wrong, if that's the case
412+ bool allGood = true;
413+
318414 if (isWithinTouchCompositionWindow()) {
319415 // too close to the last touch start. So we consider them as starting roughly at the same time.
320416 // Can't be a single-touch gesture.
321417 #if DIRECTIONALDRAGAREA_DEBUG
322418 qDebug("[DDA] A new touch point came in but we're still within time composition window. Ignoring it.");
323419 #endif
324− return;
420+ allGood = false;
325421 }
326422
327423 const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
328424
329425 const QTouchEvent::TouchPoint *newTouchPoint = nullptr;
330− for (int i = 0; i < touchPoints.count(); ++i) {
426+ for (int i = 0; i < touchPoints.count() && allGood; ++i) {
331427 const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
332428 if (touchPoint.state() == Qt::TouchPointPressed) {
333429 if (newTouchPoint) {
334430 // more than one touch starting in this QTouchEvent. Can't be a single-touch gesture
335− return;
431+ allGood = false;
336432 } else {
337433 // that's our candidate
338434 m_touchId = touchPoint.id();
@@ -341,85 +437,61 @@ void DirectionalDragArea::touchEvent_absent(QTouchEvent *event)
341437 }
342438 }
343439
344− Q_ASSERT(newTouchPoint);
440+ if (allGood) {
441+ Q_ASSERT(newTouchPoint);
345442
346− // If we have made this far, we are good to go to the next status.
347−
348− m_startPos = newTouchPoint->pos();
349− m_startScenePos = newTouchPoint->scenePos();
350− m_touchId = newTouchPoint->id();
351− m_dampedScenePos.reset(m_startScenePos);
352− m_velocityCalculator->setTrackedPosition(0.);
353− m_velocityCalculator->reset();
354− m_numSamplesOnLastSpeedCheck = 0;
355− m_silenceTime = 0;
356− setPreviousPos(m_startPos);
357− setPreviousScenePos(m_startScenePos);
358− updateSceneDirectionVector();
359−
360− setStatus(Undecided);
443+ m_startPos = newTouchPoint->pos();
444+ m_startScenePos = newTouchPoint->scenePos();
445+ m_touchId = newTouchPoint->id();
446+ m_dampedScenePos.reset(m_startScenePos);
447+ m_velocityCalculator->setTrackedPosition(0.);
448+ m_velocityCalculator->reset();
449+ m_numSamplesOnLastSpeedCheck = 0;
450+ m_silenceTime = 0;
451+ setPreviousPos(m_startPos);
452+ setPreviousScenePos(m_startScenePos);
453+ updateSceneDirectionVector();
454+
455+ if (recognitionIsDisabled()) {
456+ // Behave like a dumb TouchArea
457+ TouchRegistry::instance()->requestTouchOwnership(m_touchId, this);
458+ setStatus(Recognized);
459+ event->accept();
460+ } else {
461+ // just monitor the touch points for now.
462+ TouchRegistry::instance()->addCandidateOwnerForTouch(m_touchId, this);
463+
464+ setStatus(Undecided);
465+ // Let the item below have it. We will monitor it and grab it later if a gesture
466+ // gets recognized.
467+ event->ignore();
468+ }
469+ } else {
470+ watchPressedTouchPoints(touchPoints);
471+ event->ignore();
472+ }
361473}
362474
363475void DirectionalDragArea::touchEvent_undecided(QTouchEvent *event)
364476{
365− const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(event);
477+ Q_ASSERT(event->type() == QEvent::TouchBegin);
478+ Q_ASSERT(fetchTargetTouchPoint(event) == nullptr);
366479
367− if (!touchPoint) {
368− qCritical() << "DirectionalDragArea[status=Undecided]: touch " << m_touchId
369− << "missing from QTouchEvent without first reaching state Qt::TouchPointReleased. "
370− "Considering it as released.";
371− setStatus(WaitingForTouch);
372− return;
373− }
374−
375− const QPointF &touchScenePos = touchPoint->scenePos();
376−
377− if (touchPoint->state() == Qt::TouchPointReleased) {
378− // touch has ended before recognition concluded
379− DDA_DEBUG("Touch has ended before recognition concluded");
380− setStatus(WaitingForTouch);
381− Q_EMIT tapped();
382− return;
383− }
480+ // We're not interested in new touch points. We already have our candidate (m_touchId).
481+ // But we do want to know when those new touches end for keeping the composition time
482+ // window up-to-date
483+ event->ignore();
484+ watchPressedTouchPoints(event->touchPoints());
384485
385486 if (event->touchPointStates().testFlag(Qt::TouchPointPressed) && isWithinTouchCompositionWindow()) {
386487 // multi-finger drags are not accepted
387488 DDA_DEBUG("Multi-finger drags are not accepted");
388− setStatus(WaitingForTouch);
389− return;
390− }
391489
392− m_previousDampedScenePos.setX(m_dampedScenePos.x());
393− m_previousDampedScenePos.setY(m_dampedScenePos.y());
394− m_dampedScenePos.update(touchScenePos);
395− updateVelocityCalculator(touchScenePos);
396−
397− if (!pointInsideAllowedArea()) {
398− DDA_DEBUG("Rejecting gesture because touch point is outside allowed area.");
399− setStatus(WaitingForTouch);
400− return;
401− }
490+ TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
491+ // We still wanna know when it ends for keeping the composition time window up-to-date
492+ TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
402493
403− if (!movingInRightDirection()) {
404− DDA_DEBUG("Rejecting gesture becauuse touch point is moving in the wrong direction.");
405494 setStatus(WaitingForTouch);
406− return;
407− }
408−
409− setPreviousPos(touchPoint->pos());
410− setPreviousScenePos(touchScenePos);
411−
412− if (isWithinTouchCompositionWindow()) {
413− // There's still time for some new touch to appear and ruin our party as it would be combined
414− // with our m_touchId one and therefore deny the possibility of a single-finger gesture.
415− DDA_DEBUG("Sill within composition window. Let's wait more.");
416− return;
417− }
418−
419− if (movedFarEnough(touchScenePos)) {
420− setStatus(Recognized);
421− } else {
422− DDA_DEBUG("Didn't move far enough yet. Let's wait more.");
423495 }
424496}
425497
@@ -437,11 +509,35 @@ void DirectionalDragArea::touchEvent_recognized(QTouchEvent *event)
437509 setPreviousScenePos(touchPoint->scenePos());
438510
439511 if (touchPoint->state() == Qt::TouchPointReleased) {
512+ emitSignalIfTapped();
440513 setStatus(WaitingForTouch);
441514 }
442515 }
443516}
444517
518+void DirectionalDragArea::watchPressedTouchPoints(const QList<QTouchEvent::TouchPoint> &touchPoints)
519+{
520+ for (int i = 0; i < touchPoints.count(); ++i) {
521+ const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
522+ if (touchPoint.state() == Qt::TouchPointPressed) {
523+ TouchRegistry::instance()->addTouchWatcher(touchPoint.id(), this);
524+ }
525+ }
526+}
527+
528+bool DirectionalDragArea::recognitionIsDisabled() const
529+{
530+ return distanceThreshold() <= 0 && compositionTime() <= 0;
531+}
532+
533+void DirectionalDragArea::emitSignalIfTapped()
534+{
535+ qint64 touchDuration = m_timeSource->msecsSinceReference() - m_activeTouches.touchStartTime(m_touchId);
536+ if (touchDuration <= maxTapDuration()) {
537+ Q_EMIT tapped();
538+ }
539+}
540+
445541const QTouchEvent::TouchPoint *DirectionalDragArea::fetchTargetTouchPoint(QTouchEvent *event)
446542{
447543 const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
@@ -509,12 +605,16 @@ bool DirectionalDragArea::movedFarEnough(const QPointF &point) const
509605
510606void DirectionalDragArea::checkSpeed()
511607{
608+ Q_ASSERT(m_status == Undecided);
609+
512610 if (m_velocityCalculator->numSamples() >= AxisVelocityCalculator::MIN_SAMPLES_NEEDED) {
513611 qreal speed = qFabs(m_velocityCalculator->calculate());
514612 qreal minSpeedMsecs = m_minSpeed / 1000.0;
515613
516614 if (speed < minSpeedMsecs) {
517615 DDA_DEBUG("Rejecting gesture because it's below minimum speed.");
616+ TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
617+ TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
518618 setStatus(WaitingForTouch);
519619 }
520620 }
@@ -523,7 +623,9 @@ void DirectionalDragArea::checkSpeed()
523623 m_silenceTime += m_recognitionTimer->interval();
524624
525625 if (m_silenceTime > m_maxSilenceTime) {
526− DDA_DEBUG("Rejecting gesture because it's silence time has been exceeded.");
626+ DDA_DEBUG("Rejecting gesture because its silence time has been exceeded.");
627+ TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
628+ TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
527629 setStatus(WaitingForTouch);
528630 }
529631 } else {
@@ -532,10 +634,19 @@ void DirectionalDragArea::checkSpeed()
532634 m_numSamplesOnLastSpeedCheck = m_velocityCalculator->numSamples();
533635}
534636
535−void DirectionalDragArea::onEnabledChanged()
637+void DirectionalDragArea::giveUpIfDisabledOrInvisible()
536638{
537− if (!isEnabled() && m_status != WaitingForTouch) {
538− setStatus(WaitingForTouch);
639+ if (!isEnabled() || !isVisible()) {
640+ if (m_status == Undecided) {
641+ TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
642+ // We still wanna know when it ends for keeping the composition time window up-to-date
643+ TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
644+ }
645+
646+ if (m_status != WaitingForTouch) {
647+ DDA_DEBUG("Resetting status because got disabled or made invisible");
648+ setStatus(WaitingForTouch);
649+ }
539650 }
540651}
541652
@@ -631,7 +742,9 @@ void DirectionalDragArea::updateVelocityCalculator(const QPointF &scenePos)
631742
632743bool DirectionalDragArea::isWithinTouchCompositionWindow()
633744{
634− return !m_activeTouches.isEmpty() &&
745+ return
746+ compositionTime() > 0 &&
747+ !m_activeTouches.isEmpty() &&
635748 m_timeSource->msecsSinceReference() <=
636749 m_activeTouches.mostRecentStartTime() + (qint64)compositionTime();
637750}
@@ -639,18 +752,17 @@ bool DirectionalDragArea::isWithinTouchCompositionWindow()
639752//************************** ActiveTouchesInfo **************************
640753
641754DirectionalDragArea::ActiveTouchesInfo::ActiveTouchesInfo(const SharedTimeSource &timeSource)
642− : m_timeSource(timeSource), m_lastUsedIndex(-1)
755+ : m_timeSource(timeSource)
643756{
644− // Estimate of the maximum number of active touches we might reach.
645− // Not a problem if it ends up being an underestimate as this is just
646− // an optimization.
647− m_vector.resize(3);
648757}
649758
650759void DirectionalDragArea::ActiveTouchesInfo::update(QTouchEvent *event)
651760{
652761 if (!(event->touchPointStates() & (Qt::TouchPointPressed | Qt::TouchPointReleased))) {
653762 // nothing to update
763+ #if ACTIVETOUCHESINFO_DEBUG
764+ qDebug("[DDA::ActiveTouchesInfo] Nothing to Update");
765+ #endif
654766 return;
655767 }
656768
@@ -658,74 +770,88 @@ void DirectionalDragArea::ActiveTouchesInfo::update(QTouchEvent *event)
658770 for (int i = 0; i < touchPoints.count(); ++i) {
659771 const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
660772 if (touchPoint.state() == Qt::TouchPointPressed) {
661− addTouchPoint(touchPoint);
773+ addTouchPoint(touchPoint.id());
662774 } else if (touchPoint.state() == Qt::TouchPointReleased) {
663− removeTouchPoint(touchPoint);
775+ removeTouchPoint(touchPoint.id());
664776 }
665777 }
666778}
667779
668−void DirectionalDragArea::ActiveTouchesInfo::addTouchPoint(const QTouchEvent::TouchPoint &touchPoint)
780+#if ACTIVETOUCHESINFO_DEBUG
781+QString DirectionalDragArea::ActiveTouchesInfo::toString()
669782{
670− ActiveTouchInfo &activeTouchInfo = getEmptySlot();
671− activeTouchInfo.id = touchPoint.id();
672− activeTouchInfo.startTime = m_timeSource->msecsSinceReference();
783+ QString string = "(";
784+
785+ {
786+ QTextStream stream(&string);
787+ m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) {
788+ stream << "(id=" << touchInfo->id << ",startTime=" << touchInfo->startTime << ")";
789+ return true;
790+ });
791+ }
792+
793+ string.append(")");
794+
795+ return string;
673796}
797+#endif // ACTIVETOUCHESINFO_DEBUG
674798
675−void DirectionalDragArea::ActiveTouchesInfo::removeTouchPoint(const QTouchEvent::TouchPoint &touchPoint)
799+void DirectionalDragArea::ActiveTouchesInfo::addTouchPoint(int touchId)
676800{
677− for (int i = 0; i <= m_lastUsedIndex; ++i) {
678− if (touchPoint.id() == m_vector.at(i).id) {
679− freeSlot(i);
680− return;
681− }
682− }
683− Q_ASSERT(false); // shouldn't reach this point
801+ ActiveTouchInfo &activeTouchInfo = m_touchInfoPool.getEmptySlot();
802+ activeTouchInfo.id = touchId;
803+ activeTouchInfo.startTime = m_timeSource->msecsSinceReference();
804+
805+ #if ACTIVETOUCHESINFO_DEBUG
806+ qDebug() << "[DDA::ActiveTouchesInfo]" << qPrintable(toString());
807+ #endif
684808}
685809
686−DirectionalDragArea::ActiveTouchInfo &DirectionalDragArea::ActiveTouchesInfo::getEmptySlot()
810+qint64 DirectionalDragArea::ActiveTouchesInfo::touchStartTime(int touchId)
687811{
688− Q_ASSERT(m_lastUsedIndex < m_vector.size());
812+ qint64 result = -1;
689813
690− // Look for an in-between vacancy first
691− for (int i = 0; i < m_lastUsedIndex; ++i) {
692− ActiveTouchInfo &activeTouchInfo = m_vector[i];
693− if (!activeTouchInfo.isValid()) {
694− return activeTouchInfo;
814+ m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) {
815+ if (touchId == touchInfo->id) {
816+ result = touchInfo->startTime;
817+ return false;
818+ } else {
819+ return true;
695820 }
696− }
697−
698− ++m_lastUsedIndex;
699− if (m_lastUsedIndex >= m_vector.size()) {
700− m_vector.resize(m_lastUsedIndex + 1);
701− }
821+ });
702822
703− return m_vector[m_lastUsedIndex];
823+ Q_ASSERT(result != -1);
824+ return result;
704825}
705826
706−void DirectionalDragArea::ActiveTouchesInfo::freeSlot(int index)
827+void DirectionalDragArea::ActiveTouchesInfo::removeTouchPoint(int touchId)
707828{
708− m_vector[index].reset();
709− if (index == m_lastUsedIndex) {
710− do {
711− --m_lastUsedIndex;
712− } while (m_lastUsedIndex >= 0 && !m_vector.at(m_lastUsedIndex).isValid());
713− }
829+ m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) {
830+ if (touchId == touchInfo->id) {
831+ m_touchInfoPool.freeSlot(touchInfo);
832+ return false;
833+ } else {
834+ return true;
835+ }
836+ });
837+
838+ #if ACTIVETOUCHESINFO_DEBUG
839+ qDebug() << "[DDA::ActiveTouchesInfo]" << qPrintable(toString());
840+ #endif
714841}
715842
716843qint64 DirectionalDragArea::ActiveTouchesInfo::mostRecentStartTime()
717844{
718− Q_ASSERT(m_lastUsedIndex >= 0);
845+ Q_ASSERT(!m_touchInfoPool.isEmpty());
846+
847+ qint64 highestStartTime = -1;
719848
720− qint64 highestStartTime = m_vector.at(0).startTime;
721− int i = 1;
722− do {
723− const ActiveTouchInfo &activeTouchInfo = m_vector.at(i);
724− if (activeTouchInfo.isValid() && activeTouchInfo.startTime > highestStartTime) {
725− highestStartTime = activeTouchInfo.startTime;
849+ m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &activeTouchInfo) {
850+ if (activeTouchInfo->startTime > highestStartTime) {
851+ highestStartTime = activeTouchInfo->startTime;
726852 }
727− ++i;
728− } while (i < m_lastUsedIndex);
853+ return true;
854+ });
729855
730856 return highestStartTime;
731857}