Unity 8
 All Classes Functions
DirectionalDragArea.cpp
1 /*
2  * Copyright (C) 2013 Canonical, Ltd.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; version 3.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  */
16 
17 #include "DirectionalDragArea.h"
18 
19 #include <QtCore/qmath.h>
20 #include <QtCore/QTimer>
21 #include <QDebug>
22 
23 using namespace UbuntuGestures;
24 
25 #define DIRECTIONALDRAGAREA_DEBUG 0
26 
27 #if DIRECTIONALDRAGAREA_DEBUG
28 #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 }
74 
75 const char *statusToString(DirectionalDragArea::Status status)
76 {
77  if (status == DirectionalDragArea::WaitingForTouch) {
78  return "WaitingForTouch";
79  } else if (status == DirectionalDragArea::Undecided) {
80  return "Undecided";
81  } else {
82  return "Recognized";
83  }
84 }
85 
86 } // namespace {
87 #else // DIRECTIONALDRAGAREA_DEBUG
88 #define DDA_DEBUG(msg) do{}while(0)
89 #endif // DIRECTIONALDRAGAREA_DEBUG
90 
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 };
108 
109 DirectionalDragArea::DirectionalDragArea(QQuickItem *parent)
110  : QQuickItem(parent)
111  , m_status(WaitingForTouch)
112  , m_sceneDistance(0)
113  , m_touchId(-1)
114  , m_direction(Direction::Rightwards)
115  , m_wideningAngle(0)
116  , m_wideningFactor(0)
117  , m_distanceThreshold(0)
118  , m_distanceThresholdSquared(0.)
119  , m_minSpeed(0)
120  , m_maxSilenceTime(200)
121  , m_silenceTime(0)
122  , m_compositionTime(60)
123  , m_numSamplesOnLastSpeedCheck(0)
124  , m_recognitionTimer(0)
125  , m_velocityCalculator(0)
126  , m_timeSource(new RealTimeSource)
127  , m_activeTouches(m_timeSource)
128 {
129  setRecognitionTimer(new RecognitionTimer(this));
130  m_recognitionTimer->setInterval(60);
131 
132  m_velocityCalculator = new AxisVelocityCalculator(this);
133 
134  connect(this, &QQuickItem::enabledChanged, this, &DirectionalDragArea::onEnabledChanged);
135 }
136 
137 Direction::Type DirectionalDragArea::direction() const
138 {
139  return m_direction;
140 }
141 
142 void DirectionalDragArea::setDirection(Direction::Type direction)
143 {
144  if (direction != m_direction) {
145  m_direction = direction;
146  Q_EMIT directionChanged(m_direction);
147  }
148 }
149 
150 void DirectionalDragArea::setMaxDeviation(qreal value)
151 {
152  if (m_dampedScenePos.maxDelta() != value) {
153  m_dampedScenePos.setMaxDelta(value);
154  Q_EMIT maxDeviationChanged(value);
155  }
156 }
157 
158 qreal DirectionalDragArea::wideningAngle() const
159 {
160  return m_wideningAngle;
161 }
162 
163 void DirectionalDragArea::setWideningAngle(qreal angle)
164 {
165  if (angle == m_wideningAngle)
166  return;
167 
168  m_wideningAngle = angle;
169 
170  // wideningFactor = pow(cosine(angle), 2)
171  {
172  qreal angleRadians = angle * M_PI / 180.0;
173  m_wideningFactor = qCos(angleRadians);
174  m_wideningFactor = m_wideningFactor * m_wideningFactor;
175  }
176 
177  Q_EMIT wideningAngleChanged(angle);
178 }
179 
180 void DirectionalDragArea::setDistanceThreshold(qreal value)
181 {
182  if (m_distanceThreshold != value) {
183  m_distanceThreshold = value;
184  m_distanceThresholdSquared = m_distanceThreshold * m_distanceThreshold;
185  Q_EMIT distanceThresholdChanged(value);
186  }
187 }
188 
189 void DirectionalDragArea::setMinSpeed(qreal value)
190 {
191  if (m_minSpeed != value) {
192  m_minSpeed = value;
193  Q_EMIT minSpeedChanged(value);
194  }
195 }
196 
197 void DirectionalDragArea::setMaxSilenceTime(int value)
198 {
199  if (m_maxSilenceTime != value) {
200  m_maxSilenceTime = value;
201  Q_EMIT maxSilenceTimeChanged(value);
202  }
203 }
204 
205 void DirectionalDragArea::setCompositionTime(int value)
206 {
207  if (m_compositionTime != value) {
208  m_compositionTime = value;
209  Q_EMIT compositionTimeChanged(value);
210  }
211 }
212 
213 void DirectionalDragArea::setRecognitionTimer(UbuntuGestures::AbstractTimer *timer)
214 {
215  int interval = 0;
216  bool timerWasRunning = false;
217 
218  // can be null when called from the constructor
219  if (m_recognitionTimer) {
220  interval = m_recognitionTimer->interval();
221  timerWasRunning = m_recognitionTimer->isRunning();
222  if (m_recognitionTimer->parent() == this) {
223  delete m_recognitionTimer;
224  }
225  }
226 
227  m_recognitionTimer = timer;
228  timer->setInterval(interval);
229  connect(timer, &UbuntuGestures::AbstractTimer::timeout,
230  this, &DirectionalDragArea::checkSpeed);
231  if (timerWasRunning) {
232  m_recognitionTimer->start();
233  }
234 }
235 
236 void DirectionalDragArea::setTimeSource(const SharedTimeSource &timeSource)
237 {
238  m_timeSource = timeSource;
239  m_velocityCalculator->setTimeSource(timeSource);
240  m_activeTouches.m_timeSource = timeSource;
241 }
242 
243 qreal DirectionalDragArea::distance() const
244 {
245  if (Direction::isHorizontal(m_direction)) {
246  return m_previousPos.x() - m_startPos.x();
247  } else {
248  return m_previousPos.y() - m_startPos.y();
249  }
250 }
251 
252 void DirectionalDragArea::updateSceneDistance()
253 {
254  QPointF totalMovement = m_previousScenePos - m_startScenePos;
255  m_sceneDistance = projectOntoDirectionVector(totalMovement);
256 }
257 
258 qreal DirectionalDragArea::sceneDistance() const
259 {
260  return m_sceneDistance;
261 }
262 
263 qreal DirectionalDragArea::touchX() const
264 {
265  return m_previousPos.x();
266 }
267 
268 qreal DirectionalDragArea::touchY() const
269 {
270  return m_previousPos.y();
271 }
272 
273 qreal DirectionalDragArea::touchSceneX() const
274 {
275  return m_previousScenePos.x();
276 }
277 
278 qreal DirectionalDragArea::touchSceneY() const
279 {
280  return m_previousScenePos.y();
281 }
282 
283 void DirectionalDragArea::touchEvent(QTouchEvent *event)
284 {
285  #if DIRECTIONALDRAGAREA_DEBUG
286  // TODO Consider using qCDebug() when available (Qt 5.2)
287  qDebug() << "[DDA]" << m_timeSource->msecsSinceReference()
288  << qPrintable(touchEventToString(event));
289  #endif
290 
291  if (!isEnabled() || !isVisible()) {
292  QQuickItem::touchEvent(event);
293  return;
294  }
295 
296  switch (m_status) {
297  case WaitingForTouch:
298  touchEvent_absent(event);
299  break;
300  case Undecided:
301  touchEvent_undecided(event);
302  break;
303  default: // Recognized:
304  touchEvent_recognized(event);
305  break;
306  }
307 
308  m_activeTouches.update(event);
309 }
310 
311 void DirectionalDragArea::touchEvent_absent(QTouchEvent *event)
312 {
313  if (!event->touchPointStates().testFlag(Qt::TouchPointPressed)) {
314  // Nothing to see here. No touch starting in this event.
315  return;
316  }
317 
318  if (isWithinTouchCompositionWindow()) {
319  // too close to the last touch start. So we consider them as starting roughly at the same time.
320  // Can't be a single-touch gesture.
321  #if DIRECTIONALDRAGAREA_DEBUG
322  qDebug("[DDA] A new touch point came in but we're still within time composition window. Ignoring it.");
323  #endif
324  return;
325  }
326 
327  const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
328 
329  const QTouchEvent::TouchPoint *newTouchPoint = nullptr;
330  for (int i = 0; i < touchPoints.count(); ++i) {
331  const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
332  if (touchPoint.state() == Qt::TouchPointPressed) {
333  if (newTouchPoint) {
334  // more than one touch starting in this QTouchEvent. Can't be a single-touch gesture
335  return;
336  } else {
337  // that's our candidate
338  m_touchId = touchPoint.id();
339  newTouchPoint = &touchPoint;
340  }
341  }
342  }
343 
344  Q_ASSERT(newTouchPoint);
345 
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);
361 }
362 
363 void DirectionalDragArea::touchEvent_undecided(QTouchEvent *event)
364 {
365  const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(event);
366 
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  }
384 
385  if (event->touchPointStates().testFlag(Qt::TouchPointPressed) && isWithinTouchCompositionWindow()) {
386  // multi-finger drags are not accepted
387  DDA_DEBUG("Multi-finger drags are not accepted");
388  setStatus(WaitingForTouch);
389  return;
390  }
391 
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  }
402 
403  if (!movingInRightDirection()) {
404  DDA_DEBUG("Rejecting gesture becauuse touch point is moving in the wrong direction.");
405  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.");
423  }
424 }
425 
426 void DirectionalDragArea::touchEvent_recognized(QTouchEvent *event)
427 {
428  const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(event);
429 
430  if (!touchPoint) {
431  qCritical() << "DirectionalDragArea[status=Recognized]: touch " << m_touchId
432  << "missing from QTouchEvent without first reaching state Qt::TouchPointReleased. "
433  "Considering it as released.";
434  setStatus(WaitingForTouch);
435  } else {
436  setPreviousPos(touchPoint->pos());
437  setPreviousScenePos(touchPoint->scenePos());
438 
439  if (touchPoint->state() == Qt::TouchPointReleased) {
440  setStatus(WaitingForTouch);
441  }
442  }
443 }
444 
445 const QTouchEvent::TouchPoint *DirectionalDragArea::fetchTargetTouchPoint(QTouchEvent *event)
446 {
447  const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
448  const QTouchEvent::TouchPoint *touchPoint = 0;
449  for (int i = 0; i < touchPoints.size(); ++i) {
450  if (touchPoints.at(i).id() == m_touchId) {
451  touchPoint = &touchPoints.at(i);
452  break;
453  }
454  }
455  return touchPoint;
456 }
457 
458 bool DirectionalDragArea::pointInsideAllowedArea() const
459 {
460  // NB: Using squared values to avoid computing the square root to find
461  // the length totalMovement
462 
463  QPointF totalMovement(m_dampedScenePos.x() - m_startScenePos.x(),
464  m_dampedScenePos.y() - m_startScenePos.y());
465 
466  qreal squaredTotalMovSize = totalMovement.x() * totalMovement.x() +
467  totalMovement.y() * totalMovement.y();
468 
469  if (squaredTotalMovSize == 0.) {
470  // didn't move
471  return true;
472  }
473 
474  qreal projectedMovement = projectOntoDirectionVector(totalMovement);
475 
476 
477  qreal cosineAngleSquared = (projectedMovement * projectedMovement) / squaredTotalMovSize;
478 
479  // Same as:
480  // angle_between_movement_vector_and_gesture_direction_vector <= widening_angle
481  return cosineAngleSquared >= m_wideningFactor;
482 }
483 
484 bool DirectionalDragArea::movingInRightDirection() const
485 {
486  QPointF movementVector(m_dampedScenePos.x() - m_previousDampedScenePos.x(),
487  m_dampedScenePos.y() - m_previousDampedScenePos.y());
488 
489  qreal scalarProjection = projectOntoDirectionVector(movementVector);
490 
491  return scalarProjection >= 0.;
492 }
493 
494 bool DirectionalDragArea::movedFarEnough(const QPointF &point) const
495 {
496  if (m_distanceThreshold <= 0.) {
497  // distance threshold check is disabled
498  return true;
499  } else {
500  QPointF totalMovement(point.x() - m_startScenePos.x(),
501  point.y() - m_startScenePos.y());
502 
503  qreal squaredTotalMovSize = totalMovement.x() * totalMovement.x() +
504  totalMovement.y() * totalMovement.y();
505 
506  return squaredTotalMovSize > m_distanceThresholdSquared;
507  }
508 }
509 
510 void DirectionalDragArea::checkSpeed()
511 {
512  if (m_velocityCalculator->numSamples() >= AxisVelocityCalculator::MIN_SAMPLES_NEEDED) {
513  qreal speed = qFabs(m_velocityCalculator->calculate());
514  qreal minSpeedMsecs = m_minSpeed / 1000.0;
515 
516  if (speed < minSpeedMsecs) {
517  DDA_DEBUG("Rejecting gesture because it's below minimum speed.");
518  setStatus(WaitingForTouch);
519  }
520  }
521 
522  if (m_velocityCalculator->numSamples() == m_numSamplesOnLastSpeedCheck) {
523  m_silenceTime += m_recognitionTimer->interval();
524 
525  if (m_silenceTime > m_maxSilenceTime) {
526  DDA_DEBUG("Rejecting gesture because it's silence time has been exceeded.");
527  setStatus(WaitingForTouch);
528  }
529  } else {
530  m_silenceTime = 0;
531  }
532  m_numSamplesOnLastSpeedCheck = m_velocityCalculator->numSamples();
533 }
534 
535 void DirectionalDragArea::onEnabledChanged()
536 {
537  if (!isEnabled() && m_status != WaitingForTouch) {
538  setStatus(WaitingForTouch);
539  }
540 }
541 
542 void DirectionalDragArea::setStatus(DirectionalDragArea::Status newStatus)
543 {
544  if (newStatus == m_status)
545  return;
546 
547  DirectionalDragArea::Status oldStatus = m_status;
548 
549  if (oldStatus == Undecided) {
550  m_recognitionTimer->stop();
551  }
552 
553  m_status = newStatus;
554  Q_EMIT statusChanged(m_status);
555 
556  #if DIRECTIONALDRAGAREA_DEBUG
557  qDebug() << "[DDA]" << statusToString(oldStatus) << "->" << statusToString(newStatus);
558  #endif
559 
560  switch (newStatus) {
561  case WaitingForTouch:
562  Q_EMIT draggingChanged(false);
563  break;
564  case Undecided:
565  m_recognitionTimer->start();
566  Q_EMIT draggingChanged(true);
567  break;
568  case Recognized:
569  if (oldStatus == WaitingForTouch)
570  Q_EMIT draggingChanged(true);
571  break;
572  default:
573  // no-op
574  break;
575  }
576 }
577 
578 void DirectionalDragArea::setPreviousPos(const QPointF &point)
579 {
580  bool xChanged = m_previousPos.x() != point.x();
581  bool yChanged = m_previousPos.y() != point.y();
582 
583  m_previousPos = point;
584 
585  if (xChanged) {
586  Q_EMIT touchXChanged(point.x());
587  if (Direction::isHorizontal(m_direction))
588  Q_EMIT distanceChanged(distance());
589  }
590 
591  if (yChanged) {
592  Q_EMIT touchYChanged(point.y());
593  if (Direction::isVertical(m_direction))
594  Q_EMIT distanceChanged(distance());
595  }
596 }
597 
598 void DirectionalDragArea::setPreviousScenePos(const QPointF &point)
599 {
600  bool xChanged = m_previousScenePos.x() != point.x();
601  bool yChanged = m_previousScenePos.y() != point.y();
602 
603  if (!xChanged && !yChanged)
604  return;
605 
606  qreal oldSceneDistance = sceneDistance();
607  m_previousScenePos = point;
608  updateSceneDistance();
609 
610  if (oldSceneDistance != sceneDistance()) {
611  Q_EMIT sceneDistanceChanged(sceneDistance());
612  }
613 
614  if (xChanged) {
615  Q_EMIT touchSceneXChanged(point.x());
616  }
617 
618  if (yChanged) {
619  Q_EMIT touchSceneYChanged(point.y());
620  }
621 }
622 
623 void DirectionalDragArea::updateVelocityCalculator(const QPointF &scenePos)
624 {
625  QPointF totalSceneMovement = scenePos - m_startScenePos;
626 
627  qreal scalarProjection = projectOntoDirectionVector(totalSceneMovement);
628 
629  m_velocityCalculator->setTrackedPosition(scalarProjection);
630 }
631 
632 bool DirectionalDragArea::isWithinTouchCompositionWindow()
633 {
634  return !m_activeTouches.isEmpty() &&
635  m_timeSource->msecsSinceReference() <=
636  m_activeTouches.mostRecentStartTime() + (qint64)compositionTime();
637 }
638 
639 //************************** ActiveTouchesInfo **************************
640 
641 DirectionalDragArea::ActiveTouchesInfo::ActiveTouchesInfo(const SharedTimeSource &timeSource)
642  : m_timeSource(timeSource), m_lastUsedIndex(-1)
643 {
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);
648 }
649 
650 void DirectionalDragArea::ActiveTouchesInfo::update(QTouchEvent *event)
651 {
652  if (!(event->touchPointStates() & (Qt::TouchPointPressed | Qt::TouchPointReleased))) {
653  // nothing to update
654  return;
655  }
656 
657  const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
658  for (int i = 0; i < touchPoints.count(); ++i) {
659  const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
660  if (touchPoint.state() == Qt::TouchPointPressed) {
661  addTouchPoint(touchPoint);
662  } else if (touchPoint.state() == Qt::TouchPointReleased) {
663  removeTouchPoint(touchPoint);
664  }
665  }
666 }
667 
668 void DirectionalDragArea::ActiveTouchesInfo::addTouchPoint(const QTouchEvent::TouchPoint &touchPoint)
669 {
670  ActiveTouchInfo &activeTouchInfo = getEmptySlot();
671  activeTouchInfo.id = touchPoint.id();
672  activeTouchInfo.startTime = m_timeSource->msecsSinceReference();
673 }
674 
675 void DirectionalDragArea::ActiveTouchesInfo::removeTouchPoint(const QTouchEvent::TouchPoint &touchPoint)
676 {
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
684 }
685 
686 DirectionalDragArea::ActiveTouchInfo &DirectionalDragArea::ActiveTouchesInfo::getEmptySlot()
687 {
688  Q_ASSERT(m_lastUsedIndex < m_vector.size());
689 
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;
695  }
696  }
697 
698  ++m_lastUsedIndex;
699  if (m_lastUsedIndex >= m_vector.size()) {
700  m_vector.resize(m_lastUsedIndex + 1);
701  }
702 
703  return m_vector[m_lastUsedIndex];
704 }
705 
706 void DirectionalDragArea::ActiveTouchesInfo::freeSlot(int index)
707 {
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  }
714 }
715 
716 qint64 DirectionalDragArea::ActiveTouchesInfo::mostRecentStartTime()
717 {
718  Q_ASSERT(m_lastUsedIndex >= 0);
719 
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;
726  }
727  ++i;
728  } while (i < m_lastUsedIndex);
729 
730  return highestStartTime;
731 }
732 
733 void DirectionalDragArea::updateSceneDirectionVector()
734 {
735  QPointF localOrigin(0., 0.);
736  QPointF localDirection;
737  switch (m_direction) {
738  case Direction::Upwards:
739  localDirection.rx() = 0.;
740  localDirection.ry() = -1.;
741  break;
742  case Direction::Downwards:
743  localDirection.rx() = 0.;
744  localDirection.ry() = 1;
745  break;
746  case Direction::Leftwards:
747  localDirection.rx() = -1.;
748  localDirection.ry() = 0.;
749  break;
750  default: // Direction::Rightwards:
751  localDirection.rx() = 1.;
752  localDirection.ry() = 0.;
753  break;
754  }
755  QPointF sceneOrigin = mapToScene(localOrigin);
756  QPointF sceneDirection = mapToScene(localDirection);
757  m_sceneDirectionVector = sceneDirection - sceneOrigin;
758 }
759 
760 qreal DirectionalDragArea::projectOntoDirectionVector(const QPointF &sceneVector) const
761 {
762  // same as dot product as m_sceneDirectionVector is a unit vector
763  return sceneVector.x() * m_sceneDirectionVector.x() +
764  sceneVector.y() * m_sceneDirectionVector.y();
765 }
766 
767 // Because we are defining a new QObject-based class (RecognitionTimer) here.
768 #include "DirectionalDragArea.moc"