@@ -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,146 @@ 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+ //
263+ // The fix for this bug has landed in Qt 5.4 (https://codereview.qt-project.org/96887)
264+ // TODO: Remove this workaround once we start using Qt 5.4
265+ if (window()) {
266+ QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(window());
267+ if (windowPrivate->touchMouseId == event->touchId() && window()->mouseGrabberItem()) {
268+ DDA_DEBUG("removing mouse grabber");
269+ window()->mouseGrabberItem()->ungrabMouse();
270+ }
271+ }
272+ } else {
273+ // We still wanna know when it ends for keeping the composition time window up-to-date
274+ TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
275+
276+ setStatus(WaitingForTouch);
277+ }
278+}
279+
280+void DirectionalDragArea::unownedTouchEvent(UnownedTouchEvent *unownedTouchEvent)
281+{
282+ QTouchEvent *event = unownedTouchEvent->touchEvent();
283+
284+ Q_ASSERT(!event->touchPointStates().testFlag(Qt::TouchPointPressed));
285+
286+ #if DIRECTIONALDRAGAREA_DEBUG
287+ // TODO Consider using qCDebug() when available (Qt 5.2)
288+ qDebug() << "[DDA] Unowned" << m_timeSource->msecsSinceReference()
289+ << qPrintable(touchEventToString(event));
290+ #endif
291+
292+ switch (m_status) {
293+ case WaitingForTouch:
294+ // do nothing
295+ break;
296+ case Undecided:
297+ Q_ASSERT(isEnabled() && isVisible());
298+ unownedTouchEvent_undecided(unownedTouchEvent);
299+ break;
300+ default: // Recognized:
301+ // do nothing
302+ break;
303+ }
304+
305+ m_activeTouches.update(event);
306+}
307+
308+void DirectionalDragArea::unownedTouchEvent_undecided(UnownedTouchEvent *unownedTouchEvent)
309+{
310+ const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(unownedTouchEvent->touchEvent());
311+ if (!touchPoint) {
312+ qCritical() << "DirectionalDragArea[status=Undecided]: touch " << m_touchId
313+ << "missing from UnownedTouchEvent without first reaching state Qt::TouchPointReleased. "
314+ "Considering it as released.";
315+
316+ TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
317+ setStatus(WaitingForTouch);
318+ return;
319+ }
320+
321+ const QPointF &touchScenePos = touchPoint->scenePos();
322+
323+ if (touchPoint->state() == Qt::TouchPointReleased) {
324+ // touch has ended before recognition concluded
325+ DDA_DEBUG("Touch has ended before recognition concluded");
326+ TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
327+ emitSignalIfTapped();
328+ setStatus(WaitingForTouch);
329+ return;
330+ }
331+
332+ m_previousDampedScenePos.setX(m_dampedScenePos.x());
333+ m_previousDampedScenePos.setY(m_dampedScenePos.y());
334+ m_dampedScenePos.update(touchScenePos);
335+ updateVelocityCalculator(touchScenePos);
336+
337+ if (!pointInsideAllowedArea()) {
338+ DDA_DEBUG("Rejecting gesture because touch point is outside allowed area.");
339+ TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
340+ // We still wanna know when it ends for keeping the composition time window up-to-date
341+ TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
342+ setStatus(WaitingForTouch);
343+ return;
344+ }
345+
346+ if (!movingInRightDirection()) {
347+ DDA_DEBUG("Rejecting gesture because touch point is moving in the wrong direction.");
348+ TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
349+ // We still wanna know when it ends for keeping the composition time window up-to-date
350+ TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
351+ setStatus(WaitingForTouch);
352+ return;
353+ }
354+
355+ setPreviousPos(touchPoint->pos());
356+ setPreviousScenePos(touchScenePos);
357+
358+ if (isWithinTouchCompositionWindow()) {
359+ // There's still time for some new touch to appear and ruin our party as it would be combined
360+ // with our m_touchId one and therefore deny the possibility of a single-finger gesture.
361+ DDA_DEBUG("Sill within composition window. Let's wait more.");
362+ return;
363+ }
364+
365+ if (movedFarEnough(touchScenePos)) {
366+ TouchRegistry::instance()->requestTouchOwnership(m_touchId, this);
367+ setStatus(Recognized);
368+ } else {
369+ DDA_DEBUG("Didn't move far enough yet. Let's wait more.");
370+ }
371+}
372+
283373void DirectionalDragArea::touchEvent(QTouchEvent *event)
284374{
375+ // TODO: Consider when more than one touch starts in the same event (although it's not possible
376+ // with Mir's android-input). Have to track them all. Consider it a plus/bonus.
377+
285378 #if DIRECTIONALDRAGAREA_DEBUG
286379 // TODO Consider using qCDebug() when available (Qt 5.2)
287380 qDebug() << "[DDA]" << m_timeSource->msecsSinceReference()
@@ -310,29 +403,34 @@ void DirectionalDragArea::touchEvent(QTouchEvent *event)
310403
311404void DirectionalDragArea::touchEvent_absent(QTouchEvent *event)
312405{
406+ // TODO: accept/reject is for the whole event, not per touch id. See how that affects us.
407+
313408 if (!event->touchPointStates().testFlag(Qt::TouchPointPressed)) {
314409 // Nothing to see here. No touch starting in this event.
315410 return;
316411 }
317412
413+ // to be proven wrong, if that's the case
414+ bool allGood = true;
415+
318416 if (isWithinTouchCompositionWindow()) {
319417 // too close to the last touch start. So we consider them as starting roughly at the same time.
320418 // Can't be a single-touch gesture.
321419 #if DIRECTIONALDRAGAREA_DEBUG
322420 qDebug("[DDA] A new touch point came in but we're still within time composition window. Ignoring it.");
323421 #endif
324− return;
422+ allGood = false;
325423 }
326424
327425 const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
328426
329427 const QTouchEvent::TouchPoint *newTouchPoint = nullptr;
330− for (int i = 0; i < touchPoints.count(); ++i) {
428+ for (int i = 0; i < touchPoints.count() && allGood; ++i) {
331429 const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
332430 if (touchPoint.state() == Qt::TouchPointPressed) {
333431 if (newTouchPoint) {
334432 // more than one touch starting in this QTouchEvent. Can't be a single-touch gesture
335− return;
433+ allGood = false;
336434 } else {
337435 // that's our candidate
338436 m_touchId = touchPoint.id();
@@ -341,85 +439,61 @@ void DirectionalDragArea::touchEvent_absent(QTouchEvent *event)
341439 }
342440 }
343441
344− Q_ASSERT(newTouchPoint);
442+ if (allGood) {
443+ Q_ASSERT(newTouchPoint);
345444
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);
445+ m_startPos = newTouchPoint->pos();
446+ m_startScenePos = newTouchPoint->scenePos();
447+ m_touchId = newTouchPoint->id();
448+ m_dampedScenePos.reset(m_startScenePos);
449+ m_velocityCalculator->setTrackedPosition(0.);
450+ m_velocityCalculator->reset();
451+ m_numSamplesOnLastSpeedCheck = 0;
452+ m_silenceTime = 0;
453+ setPreviousPos(m_startPos);
454+ setPreviousScenePos(m_startScenePos);
455+ updateSceneDirectionVector();
456+
457+ if (recognitionIsDisabled()) {
458+ // Behave like a dumb TouchArea
459+ TouchRegistry::instance()->requestTouchOwnership(m_touchId, this);
460+ setStatus(Recognized);
461+ event->accept();
462+ } else {
463+ // just monitor the touch points for now.
464+ TouchRegistry::instance()->addCandidateOwnerForTouch(m_touchId, this);
465+
466+ setStatus(Undecided);
467+ // Let the item below have it. We will monitor it and grab it later if a gesture
468+ // gets recognized.
469+ event->ignore();
470+ }
471+ } else {
472+ watchPressedTouchPoints(touchPoints);
473+ event->ignore();
474+ }
361475}
362476
363477void DirectionalDragArea::touchEvent_undecided(QTouchEvent *event)
364478{
365− const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(event);
479+ Q_ASSERT(event->type() == QEvent::TouchBegin);
480+ Q_ASSERT(fetchTargetTouchPoint(event) == nullptr);
366481
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− }
482+ // We're not interested in new touch points. We already have our candidate (m_touchId).
483+ // But we do want to know when those new touches end for keeping the composition time
484+ // window up-to-date
485+ event->ignore();
486+ watchPressedTouchPoints(event->touchPoints());
384487
385488 if (event->touchPointStates().testFlag(Qt::TouchPointPressed) && isWithinTouchCompositionWindow()) {
386489 // multi-finger drags are not accepted
387490 DDA_DEBUG("Multi-finger drags are not accepted");
388− setStatus(WaitingForTouch);
389− return;
390− }
391491
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− }
492+ TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
493+ // We still wanna know when it ends for keeping the composition time window up-to-date
494+ TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
402495
403− if (!movingInRightDirection()) {
404− DDA_DEBUG("Rejecting gesture becauuse touch point is moving in the wrong direction.");
405496 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.");
423497 }
424498}
425499
@@ -437,11 +511,35 @@ void DirectionalDragArea::touchEvent_recognized(QTouchEvent *event)
437511 setPreviousScenePos(touchPoint->scenePos());
438512
439513 if (touchPoint->state() == Qt::TouchPointReleased) {
514+ emitSignalIfTapped();
440515 setStatus(WaitingForTouch);
441516 }
442517 }
443518}
444519
520+void DirectionalDragArea::watchPressedTouchPoints(const QList<QTouchEvent::TouchPoint> &touchPoints)
521+{
522+ for (int i = 0; i < touchPoints.count(); ++i) {
523+ const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
524+ if (touchPoint.state() == Qt::TouchPointPressed) {
525+ TouchRegistry::instance()->addTouchWatcher(touchPoint.id(), this);
526+ }
527+ }
528+}
529+
530+bool DirectionalDragArea::recognitionIsDisabled() const
531+{
532+ return distanceThreshold() <= 0 && compositionTime() <= 0;
533+}
534+
535+void DirectionalDragArea::emitSignalIfTapped()
536+{
537+ qint64 touchDuration = m_timeSource->msecsSinceReference() - m_activeTouches.touchStartTime(m_touchId);
538+ if (touchDuration <= maxTapDuration()) {
539+ Q_EMIT tapped();
540+ }
541+}
542+
445543const QTouchEvent::TouchPoint *DirectionalDragArea::fetchTargetTouchPoint(QTouchEvent *event)
446544{
447545 const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
@@ -509,12 +607,16 @@ bool DirectionalDragArea::movedFarEnough(const QPointF &point) const
509607
510608void DirectionalDragArea::checkSpeed()
511609{
610+ Q_ASSERT(m_status == Undecided);
611+
512612 if (m_velocityCalculator->numSamples() >= AxisVelocityCalculator::MIN_SAMPLES_NEEDED) {
513613 qreal speed = qFabs(m_velocityCalculator->calculate());
514614 qreal minSpeedMsecs = m_minSpeed / 1000.0;
515615
516616 if (speed < minSpeedMsecs) {
517617 DDA_DEBUG("Rejecting gesture because it's below minimum speed.");
618+ TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
619+ TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
518620 setStatus(WaitingForTouch);
519621 }
520622 }
@@ -523,7 +625,9 @@ void DirectionalDragArea::checkSpeed()
523625 m_silenceTime += m_recognitionTimer->interval();
524626
525627 if (m_silenceTime > m_maxSilenceTime) {
526− DDA_DEBUG("Rejecting gesture because it's silence time has been exceeded.");
628+ DDA_DEBUG("Rejecting gesture because its silence time has been exceeded.");
629+ TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
630+ TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
527631 setStatus(WaitingForTouch);
528632 }
529633 } else {
@@ -532,10 +636,19 @@ void DirectionalDragArea::checkSpeed()
532636 m_numSamplesOnLastSpeedCheck = m_velocityCalculator->numSamples();
533637}
534638
535−void DirectionalDragArea::onEnabledChanged()
639+void DirectionalDragArea::giveUpIfDisabledOrInvisible()
536640{
537− if (!isEnabled() && m_status != WaitingForTouch) {
538− setStatus(WaitingForTouch);
641+ if (!isEnabled() || !isVisible()) {
642+ if (m_status == Undecided) {
643+ TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
644+ // We still wanna know when it ends for keeping the composition time window up-to-date
645+ TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
646+ }
647+
648+ if (m_status != WaitingForTouch) {
649+ DDA_DEBUG("Resetting status because got disabled or made invisible");
650+ setStatus(WaitingForTouch);
651+ }
539652 }
540653}
541654
@@ -631,7 +744,9 @@ void DirectionalDragArea::updateVelocityCalculator(const QPointF &scenePos)
631744
632745bool DirectionalDragArea::isWithinTouchCompositionWindow()
633746{
634− return !m_activeTouches.isEmpty() &&
747+ return
748+ compositionTime() > 0 &&
749+ !m_activeTouches.isEmpty() &&
635750 m_timeSource->msecsSinceReference() <=
636751 m_activeTouches.mostRecentStartTime() + (qint64)compositionTime();
637752}
@@ -639,18 +754,17 @@ bool DirectionalDragArea::isWithinTouchCompositionWindow()
639754//************************** ActiveTouchesInfo **************************
640755
641756DirectionalDragArea::ActiveTouchesInfo::ActiveTouchesInfo(const SharedTimeSource &timeSource)
642− : m_timeSource(timeSource), m_lastUsedIndex(-1)
757+ : m_timeSource(timeSource)
643758{
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);
648759}
649760
650761void DirectionalDragArea::ActiveTouchesInfo::update(QTouchEvent *event)
651762{
652763 if (!(event->touchPointStates() & (Qt::TouchPointPressed | Qt::TouchPointReleased))) {
653764 // nothing to update
765+ #if ACTIVETOUCHESINFO_DEBUG
766+ qDebug("[DDA::ActiveTouchesInfo] Nothing to Update");
767+ #endif
654768 return;
655769 }
656770
@@ -658,74 +772,88 @@ void DirectionalDragArea::ActiveTouchesInfo::update(QTouchEvent *event)
658772 for (int i = 0; i < touchPoints.count(); ++i) {
659773 const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
660774 if (touchPoint.state() == Qt::TouchPointPressed) {
661− addTouchPoint(touchPoint);
775+ addTouchPoint(touchPoint.id());
662776 } else if (touchPoint.state() == Qt::TouchPointReleased) {
663− removeTouchPoint(touchPoint);
777+ removeTouchPoint(touchPoint.id());
664778 }
665779 }
666780}
667781
668−void DirectionalDragArea::ActiveTouchesInfo::addTouchPoint(const QTouchEvent::TouchPoint &touchPoint)
782+#if ACTIVETOUCHESINFO_DEBUG
783+QString DirectionalDragArea::ActiveTouchesInfo::toString()
669784{
670− ActiveTouchInfo &activeTouchInfo = getEmptySlot();
671− activeTouchInfo.id = touchPoint.id();
672− activeTouchInfo.startTime = m_timeSource->msecsSinceReference();
785+ QString string = "(";
786+
787+ {
788+ QTextStream stream(&string);
789+ m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) {
790+ stream << "(id=" << touchInfo->id << ",startTime=" << touchInfo->startTime << ")";
791+ return true;
792+ });
793+ }
794+
795+ string.append(")");
796+
797+ return string;
673798}
799+#endif // ACTIVETOUCHESINFO_DEBUG
674800
675−void DirectionalDragArea::ActiveTouchesInfo::removeTouchPoint(const QTouchEvent::TouchPoint &touchPoint)
801+void DirectionalDragArea::ActiveTouchesInfo::addTouchPoint(int touchId)
676802{
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
803+ ActiveTouchInfo &activeTouchInfo = m_touchInfoPool.getEmptySlot();
804+ activeTouchInfo.id = touchId;
805+ activeTouchInfo.startTime = m_timeSource->msecsSinceReference();
806+
807+ #if ACTIVETOUCHESINFO_DEBUG
808+ qDebug() << "[DDA::ActiveTouchesInfo]" << qPrintable(toString());
809+ #endif
684810}
685811
686−DirectionalDragArea::ActiveTouchInfo &DirectionalDragArea::ActiveTouchesInfo::getEmptySlot()
812+qint64 DirectionalDragArea::ActiveTouchesInfo::touchStartTime(int touchId)
687813{
688− Q_ASSERT(m_lastUsedIndex < m_vector.size());
814+ qint64 result = -1;
689815
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;
816+ m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) {
817+ if (touchId == touchInfo->id) {
818+ result = touchInfo->startTime;
819+ return false;
820+ } else {
821+ return true;
695822 }
696− }
697−
698− ++m_lastUsedIndex;
699− if (m_lastUsedIndex >= m_vector.size()) {
700− m_vector.resize(m_lastUsedIndex + 1);
701− }
823+ });
702824
703− return m_vector[m_lastUsedIndex];
825+ Q_ASSERT(result != -1);
826+ return result;
704827}
705828
706−void DirectionalDragArea::ActiveTouchesInfo::freeSlot(int index)
829+void DirectionalDragArea::ActiveTouchesInfo::removeTouchPoint(int touchId)
707830{
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− }
831+ m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) {
832+ if (touchId == touchInfo->id) {
833+ m_touchInfoPool.freeSlot(touchInfo);
834+ return false;
835+ } else {
836+ return true;
837+ }
838+ });
839+
840+ #if ACTIVETOUCHESINFO_DEBUG
841+ qDebug() << "[DDA::ActiveTouchesInfo]" << qPrintable(toString());
842+ #endif
714843}
715844
716845qint64 DirectionalDragArea::ActiveTouchesInfo::mostRecentStartTime()
717846{
718− Q_ASSERT(m_lastUsedIndex >= 0);
847+ Q_ASSERT(!m_touchInfoPool.isEmpty());
848+
849+ qint64 highestStartTime = -1;
719850
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;
851+ m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &activeTouchInfo) {
852+ if (activeTouchInfo->startTime > highestStartTime) {
853+ highestStartTime = activeTouchInfo->startTime;
726854 }
727− ++i;
728− } while (i < m_lastUsedIndex);
855+ return true;
856+ });
729857
730858 return highestStartTime;
731859}