Unity 8
 All Classes Functions Properties
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  return;
382  }
383 
384  if (event->touchPointStates().testFlag(Qt::TouchPointPressed) && isWithinTouchCompositionWindow()) {
385  // multi-finger drags are not accepted
386  DDA_DEBUG("Multi-finger drags are not accepted");
387  setStatus(WaitingForTouch);
388  return;
389  }
390 
391  m_previousDampedScenePos.setX(m_dampedScenePos.x());
392  m_previousDampedScenePos.setY(m_dampedScenePos.y());
393  m_dampedScenePos.update(touchScenePos);
394  updateVelocityCalculator(touchScenePos);
395 
396  if (!pointInsideAllowedArea()) {
397  DDA_DEBUG("Rejecting gesture because touch point is outside allowed area.");
398  setStatus(WaitingForTouch);
399  return;
400  }
401 
402  if (!movingInRightDirection()) {
403  DDA_DEBUG("Rejecting gesture becauuse touch point is moving in the wrong direction.");
404  setStatus(WaitingForTouch);
405  return;
406  }
407 
408  setPreviousPos(touchPoint->pos());
409  setPreviousScenePos(touchScenePos);
410 
411  if (isWithinTouchCompositionWindow()) {
412  // There's still time for some new touch to appear and ruin our party as it would be combined
413  // with our m_touchId one and therefore deny the possibility of a single-finger gesture.
414  DDA_DEBUG("Sill within composition window. Let's wait more.");
415  return;
416  }
417 
418  if (movedFarEnough(touchScenePos)) {
419  setStatus(Recognized);
420  } else {
421  DDA_DEBUG("Didn't move far enough yet. Let's wait more.");
422  }
423 }
424 
425 void DirectionalDragArea::touchEvent_recognized(QTouchEvent *event)
426 {
427  const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(event);
428 
429  if (!touchPoint) {
430  qCritical() << "DirectionalDragArea[status=Recognized]: touch " << m_touchId
431  << "missing from QTouchEvent without first reaching state Qt::TouchPointReleased. "
432  "Considering it as released.";
433  setStatus(WaitingForTouch);
434  } else {
435  setPreviousPos(touchPoint->pos());
436  setPreviousScenePos(touchPoint->scenePos());
437 
438  if (touchPoint->state() == Qt::TouchPointReleased) {
439  setStatus(WaitingForTouch);
440  }
441  }
442 }
443 
444 const QTouchEvent::TouchPoint *DirectionalDragArea::fetchTargetTouchPoint(QTouchEvent *event)
445 {
446  const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
447  const QTouchEvent::TouchPoint *touchPoint = 0;
448  for (int i = 0; i < touchPoints.size(); ++i) {
449  if (touchPoints.at(i).id() == m_touchId) {
450  touchPoint = &touchPoints.at(i);
451  break;
452  }
453  }
454  return touchPoint;
455 }
456 
457 bool DirectionalDragArea::pointInsideAllowedArea() const
458 {
459  // NB: Using squared values to avoid computing the square root to find
460  // the length totalMovement
461 
462  QPointF totalMovement(m_dampedScenePos.x() - m_startScenePos.x(),
463  m_dampedScenePos.y() - m_startScenePos.y());
464 
465  qreal squaredTotalMovSize = totalMovement.x() * totalMovement.x() +
466  totalMovement.y() * totalMovement.y();
467 
468  if (squaredTotalMovSize == 0.) {
469  // didn't move
470  return true;
471  }
472 
473  qreal projectedMovement = projectOntoDirectionVector(totalMovement);
474 
475 
476  qreal cosineAngleSquared = (projectedMovement * projectedMovement) / squaredTotalMovSize;
477 
478  // Same as:
479  // angle_between_movement_vector_and_gesture_direction_vector <= widening_angle
480  return cosineAngleSquared >= m_wideningFactor;
481 }
482 
483 bool DirectionalDragArea::movingInRightDirection() const
484 {
485  QPointF movementVector(m_dampedScenePos.x() - m_previousDampedScenePos.x(),
486  m_dampedScenePos.y() - m_previousDampedScenePos.y());
487 
488  qreal scalarProjection = projectOntoDirectionVector(movementVector);
489 
490  return scalarProjection >= 0.;
491 }
492 
493 bool DirectionalDragArea::movedFarEnough(const QPointF &point) const
494 {
495  if (m_distanceThreshold <= 0.) {
496  // distance threshold check is disabled
497  return true;
498  } else {
499  QPointF totalMovement(point.x() - m_startScenePos.x(),
500  point.y() - m_startScenePos.y());
501 
502  qreal squaredTotalMovSize = totalMovement.x() * totalMovement.x() +
503  totalMovement.y() * totalMovement.y();
504 
505  return squaredTotalMovSize > m_distanceThresholdSquared;
506  }
507 }
508 
509 void DirectionalDragArea::checkSpeed()
510 {
511  if (m_velocityCalculator->numSamples() >= AxisVelocityCalculator::MIN_SAMPLES_NEEDED) {
512  qreal speed = qFabs(m_velocityCalculator->calculate());
513  qreal minSpeedMsecs = m_minSpeed / 1000.0;
514 
515  if (speed < minSpeedMsecs) {
516  DDA_DEBUG("Rejecting gesture because it's below minimum speed.");
517  setStatus(WaitingForTouch);
518  }
519  }
520 
521  if (m_velocityCalculator->numSamples() == m_numSamplesOnLastSpeedCheck) {
522  m_silenceTime += m_recognitionTimer->interval();
523 
524  if (m_silenceTime > m_maxSilenceTime) {
525  DDA_DEBUG("Rejecting gesture because it's silence time has been exceeded.");
526  setStatus(WaitingForTouch);
527  }
528  } else {
529  m_silenceTime = 0;
530  }
531  m_numSamplesOnLastSpeedCheck = m_velocityCalculator->numSamples();
532 }
533 
534 void DirectionalDragArea::onEnabledChanged()
535 {
536  if (!isEnabled() && m_status != WaitingForTouch) {
537  setStatus(WaitingForTouch);
538  }
539 }
540 
541 void DirectionalDragArea::setStatus(DirectionalDragArea::Status newStatus)
542 {
543  if (newStatus == m_status)
544  return;
545 
546  DirectionalDragArea::Status oldStatus = m_status;
547 
548  if (oldStatus == Undecided) {
549  m_recognitionTimer->stop();
550  }
551 
552  m_status = newStatus;
553  Q_EMIT statusChanged(m_status);
554 
555  #if DIRECTIONALDRAGAREA_DEBUG
556  qDebug() << "[DDA]" << statusToString(oldStatus) << "->" << statusToString(newStatus);
557  #endif
558 
559  switch (newStatus) {
560  case WaitingForTouch:
561  Q_EMIT draggingChanged(false);
562  break;
563  case Undecided:
564  m_recognitionTimer->start();
565  Q_EMIT draggingChanged(true);
566  break;
567  case Recognized:
568  if (oldStatus == WaitingForTouch)
569  Q_EMIT draggingChanged(true);
570  break;
571  default:
572  // no-op
573  break;
574  }
575 }
576 
577 void DirectionalDragArea::setPreviousPos(const QPointF &point)
578 {
579  bool xChanged = m_previousPos.x() != point.x();
580  bool yChanged = m_previousPos.y() != point.y();
581 
582  m_previousPos = point;
583 
584  if (xChanged) {
585  Q_EMIT touchXChanged(point.x());
586  if (Direction::isHorizontal(m_direction))
587  Q_EMIT distanceChanged(distance());
588  }
589 
590  if (yChanged) {
591  Q_EMIT touchYChanged(point.y());
592  if (Direction::isVertical(m_direction))
593  Q_EMIT distanceChanged(distance());
594  }
595 }
596 
597 void DirectionalDragArea::setPreviousScenePos(const QPointF &point)
598 {
599  bool xChanged = m_previousScenePos.x() != point.x();
600  bool yChanged = m_previousScenePos.y() != point.y();
601 
602  if (!xChanged && !yChanged)
603  return;
604 
605  qreal oldSceneDistance = sceneDistance();
606  m_previousScenePos = point;
607  updateSceneDistance();
608 
609  if (oldSceneDistance != sceneDistance()) {
610  Q_EMIT sceneDistanceChanged(sceneDistance());
611  }
612 
613  if (xChanged) {
614  Q_EMIT touchSceneXChanged(point.x());
615  }
616 
617  if (yChanged) {
618  Q_EMIT touchSceneYChanged(point.y());
619  }
620 }
621 
622 void DirectionalDragArea::updateVelocityCalculator(const QPointF &scenePos)
623 {
624  QPointF totalSceneMovement = scenePos - m_startScenePos;
625 
626  qreal scalarProjection = projectOntoDirectionVector(totalSceneMovement);
627 
628  m_velocityCalculator->setTrackedPosition(scalarProjection);
629 }
630 
631 bool DirectionalDragArea::isWithinTouchCompositionWindow()
632 {
633  return !m_activeTouches.isEmpty() &&
634  m_timeSource->msecsSinceReference() <=
635  m_activeTouches.mostRecentStartTime() + (qint64)compositionTime();
636 }
637 
638 //************************** ActiveTouchesInfo **************************
639 
640 DirectionalDragArea::ActiveTouchesInfo::ActiveTouchesInfo(const SharedTimeSource &timeSource)
641  : m_timeSource(timeSource), m_lastUsedIndex(-1)
642 {
643  // Estimate of the maximum number of active touches we might reach.
644  // Not a problem if it ends up being an underestimate as this is just
645  // an optimization.
646  m_vector.resize(3);
647 }
648 
649 void DirectionalDragArea::ActiveTouchesInfo::update(QTouchEvent *event)
650 {
651  if (!(event->touchPointStates() & (Qt::TouchPointPressed | Qt::TouchPointReleased))) {
652  // nothing to update
653  return;
654  }
655 
656  const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
657  for (int i = 0; i < touchPoints.count(); ++i) {
658  const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
659  if (touchPoint.state() == Qt::TouchPointPressed) {
660  addTouchPoint(touchPoint);
661  } else if (touchPoint.state() == Qt::TouchPointReleased) {
662  removeTouchPoint(touchPoint);
663  }
664  }
665 }
666 
667 void DirectionalDragArea::ActiveTouchesInfo::addTouchPoint(const QTouchEvent::TouchPoint &touchPoint)
668 {
669  ActiveTouchInfo &activeTouchInfo = getEmptySlot();
670  activeTouchInfo.id = touchPoint.id();
671  activeTouchInfo.startTime = m_timeSource->msecsSinceReference();
672 }
673 
674 void DirectionalDragArea::ActiveTouchesInfo::removeTouchPoint(const QTouchEvent::TouchPoint &touchPoint)
675 {
676  for (int i = 0; i <= m_lastUsedIndex; ++i) {
677  if (touchPoint.id() == m_vector.at(i).id) {
678  freeSlot(i);
679  return;
680  }
681  }
682  Q_ASSERT(false); // shouldn't reach this point
683 }
684 
685 DirectionalDragArea::ActiveTouchInfo &DirectionalDragArea::ActiveTouchesInfo::getEmptySlot()
686 {
687  Q_ASSERT(m_lastUsedIndex < m_vector.size());
688 
689  // Look for an in-between vacancy first
690  for (int i = 0; i < m_lastUsedIndex; ++i) {
691  ActiveTouchInfo &activeTouchInfo = m_vector[i];
692  if (!activeTouchInfo.isValid()) {
693  return activeTouchInfo;
694  }
695  }
696 
697  ++m_lastUsedIndex;
698  if (m_lastUsedIndex >= m_vector.size()) {
699  m_vector.resize(m_lastUsedIndex + 1);
700  }
701 
702  return m_vector[m_lastUsedIndex];
703 }
704 
705 void DirectionalDragArea::ActiveTouchesInfo::freeSlot(int index)
706 {
707  m_vector[index].reset();
708  if (index == m_lastUsedIndex) {
709  do {
710  --m_lastUsedIndex;
711  } while (m_lastUsedIndex >= 0 && !m_vector.at(m_lastUsedIndex).isValid());
712  }
713 }
714 
715 qint64 DirectionalDragArea::ActiveTouchesInfo::mostRecentStartTime()
716 {
717  Q_ASSERT(m_lastUsedIndex >= 0);
718 
719  qint64 highestStartTime = m_vector.at(0).startTime;
720  int i = 1;
721  do {
722  const ActiveTouchInfo &activeTouchInfo = m_vector.at(i);
723  if (activeTouchInfo.isValid() && activeTouchInfo.startTime > highestStartTime) {
724  highestStartTime = activeTouchInfo.startTime;
725  }
726  ++i;
727  } while (i < m_lastUsedIndex);
728 
729  return highestStartTime;
730 }
731 
732 void DirectionalDragArea::updateSceneDirectionVector()
733 {
734  QPointF localOrigin(0., 0.);
735  QPointF localDirection;
736  switch (m_direction) {
737  case Direction::Upwards:
738  localDirection.rx() = 0.;
739  localDirection.ry() = -1.;
740  break;
741  case Direction::Downwards:
742  localDirection.rx() = 0.;
743  localDirection.ry() = 1;
744  break;
745  case Direction::Leftwards:
746  localDirection.rx() = -1.;
747  localDirection.ry() = 0.;
748  break;
749  default: // Direction::Rightwards:
750  localDirection.rx() = 1.;
751  localDirection.ry() = 0.;
752  break;
753  }
754  QPointF sceneOrigin = mapToScene(localOrigin);
755  QPointF sceneDirection = mapToScene(localDirection);
756  m_sceneDirectionVector = sceneDirection - sceneOrigin;
757 }
758 
759 qreal DirectionalDragArea::projectOntoDirectionVector(const QPointF &sceneVector) const
760 {
761  // same as dot product as m_sceneDirectionVector is a unit vector
762  return sceneVector.x() * m_sceneDirectionVector.x() +
763  sceneVector.y() * m_sceneDirectionVector.y();
764 }
765 
766 // Because we are defining a new QObject-based class (RecognitionTimer) here.
767 #include "DirectionalDragArea.moc"