Unity 8
TouchGestureArea.cpp
1 /*
2  * Copyright (C) 2016 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 "TouchGestureArea.h"
18 
19 #include <UbuntuGestures/private/touchownershipevent_p.h>
20 #include <UbuntuGestures/private/touchregistry_p.h>
21 #include <UbuntuGestures/private/unownedtouchevent_p.h>
22 // #include "TouchRegistry.h"
23 // #include "UnownedTouchEvent.h"
24 
25 #include <QGuiApplication>
26 #include <QStyleHints>
27 #include <private/qquickwindow_p.h>
28 
29 UG_USE_NAMESPACE
30 
31 #define TOUCHGESTUREAREA_DEBUG 0
32 
33 // TODO - understand more about why we lose touch event releases.
34 // Workaround for now is to monitor all the touch points from first touch till
35 // all have been released; no matter if we've rejected the gesture.
36 
37 namespace {
38 
39 struct InternalStatus {
40  enum Status {
41  WaitingForTouch,
42  WaitingForMoreTouches,
43  WaitingForOwnership, //Recognizing,
44  Recognized,
45  WaitingForRejection,
46  Rejected
47  };
48 };
49 
50 TouchGestureArea::Status internalStatusToGestureStatus(int internalStatus) {
51  switch (internalStatus) {
52  case InternalStatus::WaitingForTouch: return TouchGestureArea::WaitingForTouch;
53  case InternalStatus::WaitingForMoreTouches: return TouchGestureArea::Undecided;
54  case InternalStatus::WaitingForOwnership: return TouchGestureArea::Undecided;
55  case InternalStatus::Recognized: return TouchGestureArea::Recognized;
56  case InternalStatus::WaitingForRejection: return TouchGestureArea::Recognized;
57  case InternalStatus::Rejected: return TouchGestureArea::Rejected;
58  }
59  return TouchGestureArea::WaitingForTouch;
60 }
61 
62 }
63 
64 #if TOUCHGESTUREAREA_DEBUG
65 #define tgaDebug(params) qDebug().nospace() << "[TGA(" << qPrintable(objectName()) << ")] " << params
66 #include "DebugHelpers.h"
67 
68 namespace {
69 
70 const char *statusToString(int status)
71 {
72  if (status == InternalStatus::WaitingForTouch) {
73  return "WaitingForTouch";
74  } else if (status == InternalStatus::WaitingForMoreTouches) {
75  return "WaitingForMoreTouches";
76  } else if (status == InternalStatus::WaitingForOwnership) {
77  return "WaitingForOwnership";
78  } else if (status == InternalStatus::Rejected) {
79  return "Rejected";
80  } else if (status == InternalStatus::WaitingForRejection) {
81  return "WaitingForRejection";
82  } else {
83  return "Recognized";
84  }
85  return "Unknown";
86 }
87 
88 QString touchState(Qt::TouchPointState state) {
89  switch (state) {
90  case Qt::TouchPointPressed: return "pressed";
91  case Qt::TouchPointMoved: return "moved";
92  case Qt::TouchPointStationary: return "stationary";
93  case Qt::TouchPointReleased: return "released";
94  break;
95  }
96  return "unknown";
97 }
98 
99 QString touchesString(const QList<QObject*> touches) {
100  QString str;
101  Q_FOREACH(QObject* object, touches) {
102  GestureTouchPoint* touchPoint = qobject_cast<GestureTouchPoint*>(object);
103  if (touchPoint) {
104  str += QStringLiteral("[%1 @ (%2, %3)], ").arg(touchPoint->id())
105  .arg(touchPoint->x())
106  .arg(touchPoint->y());
107  }
108  }
109  return str;
110 }
111 
112 QString touchEventString(QTouchEvent* event) {
113  if (!event) return QString();
114  QString str;
115  Q_FOREACH(const auto& touchPoint, event->touchPoints()) {
116  str += QStringLiteral("[%1:%2 @ (%3, %4)], ").arg(touchPoint.id())
117  .arg(touchState(touchPoint.state()))
118  .arg(touchPoint.pos().x())
119  .arg(touchPoint.pos().y());
120  }
121  return str;
122 }
123 
124 
125 } // namespace {
126 #else // TOUCHGESTUREAREA_DEBUG
127 #define tgaDebug(params) ((void)0)
128 #endif // TOUCHGESTUREAREA_DEBUG
129 
130 TouchGestureArea::TouchGestureArea(QQuickItem* parent)
131  : QQuickItem(parent)
132  , m_status(WaitingForTouch)
133  , m_recognitionTimer(nullptr)
134  , m_dragging(false)
135  , m_minimumTouchPoints(1)
136  , m_maximumTouchPoints(INT_MAX)
137  , m_recognitionPeriod(50)
138  , m_releaseRejectPeriod(100)
139 {
140  setRecognitionTimer(new Timer(this));
141  m_recognitionTimer->setInterval(m_recognitionPeriod);
142  m_recognitionTimer->setSingleShot(true);
143 }
144 
145 TouchGestureArea::~TouchGestureArea()
146 {
147  clearTouchLists();
148  qDeleteAll(m_liveTouchPoints);
149  m_liveTouchPoints.clear();
150  qDeleteAll(m_cachedTouchPoints);
151  m_cachedTouchPoints.clear();
152 }
153 
154 bool TouchGestureArea::event(QEvent *event)
155 {
156  // Process unowned touch events (handles update/release for incomplete gestures)
157  if (event->type() == TouchOwnershipEvent::touchOwnershipEventType()) {
158  touchOwnershipEvent(static_cast<TouchOwnershipEvent*>(event));
159  return true;
160  } else if (event->type() == UnownedTouchEvent::unownedTouchEventType()) {
161  unownedTouchEvent(static_cast<UnownedTouchEvent*>(event)->touchEvent());
162  return true;
163  }
164 
165  return QQuickItem::event(event);
166 }
167 
168 void TouchGestureArea::touchOwnershipEvent(TouchOwnershipEvent *event)
169 {
170  int touchId = event->touchId();
171  tgaDebug("touchOwnershipEvent - id:" << touchId << ", gained:" << event->gained());
172 
173  if (event->gained()) {
174  grabTouchPoints(QVector<int>() << touchId);
175  m_candidateTouches.remove(touchId);
176  TouchRegistry::instance()->addTouchWatcher(touchId, this);
177  m_watchedTouches.insert(touchId);
178 
179  if (m_watchedTouches.count() >= m_minimumTouchPoints) {
180  setInternalStatus(InternalStatus::Recognized);
181  }
182  } else {
183  rejectGesture();
184  }
185 }
186 
187 void TouchGestureArea::touchEvent(QTouchEvent *event)
188 {
189  if (!isEnabled() || !isVisible()) {
190  tgaDebug(QString("NOT ENABLED touchEvent(%1) %2").arg(statusToString(m_status)).arg(touchEventString(event)));
191  QQuickItem::touchEvent(event);
192  return;
193  }
194 
195  tgaDebug(QString("touchEvent(%1) %2").arg(statusToString(m_status)).arg(touchEventString(event)));
196 
197  switch (m_status) {
198  case InternalStatus::WaitingForTouch:
199  touchEvent_waitingForTouch(event);
200  break;
201  case InternalStatus::WaitingForMoreTouches:
202  touchEvent_waitingForMoreTouches(event);
203  break;
204  case InternalStatus::WaitingForOwnership:
205  touchEvent_waitingForOwnership(event);
206  break;
207  case InternalStatus::Recognized:
208  case InternalStatus::WaitingForRejection:
209  touchEvent_recognized(event);
210  break;
211  case InternalStatus::Rejected:
212  touchEvent_rejected(event);
213  break;
214  default: // Recognized:
215  break;
216  }
217 
218  updateTouchPoints(event);
219 }
220 
221 void TouchGestureArea::touchEvent_waitingForTouch(QTouchEvent *event)
222 {
223  Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
224  Qt::TouchPointState touchPointState = touchPoint.state();
225  int touchId = touchPoint.id();
226 
227  if (touchPointState == Qt::TouchPointPressed) {
228  if (!m_candidateTouches.contains(touchId)) {
229  TouchRegistry::instance()->addCandidateOwnerForTouch(touchId, this);
230  m_candidateTouches.insert(touchId);
231  }
232  }
233  }
234  event->ignore();
235 
236  if (m_candidateTouches.count() > m_maximumTouchPoints) {
237  rejectGesture();
238  } else if (m_candidateTouches.count() >= m_minimumTouchPoints) {
239  setInternalStatus(InternalStatus::WaitingForOwnership);
240 
241  QSet<int> tmpCandidates(m_candidateTouches);
242  Q_FOREACH(int candidateTouchId, tmpCandidates) {
243  TouchRegistry::instance()->requestTouchOwnership(candidateTouchId, this);
244  }
245  // We accept the gesture; so don't pass to lower items
246  event->accept();
247  } else if (m_candidateTouches.count() > 0) {
248  setInternalStatus(InternalStatus::WaitingForMoreTouches);
249  }
250 }
251 
252 void TouchGestureArea::touchEvent_waitingForMoreTouches(QTouchEvent *event)
253 {
254  Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
255  Qt::TouchPointState touchPointState = touchPoint.state();
256  int touchId = touchPoint.id();
257 
258  if (touchPointState == Qt::TouchPointPressed) {
259  if (!m_candidateTouches.contains(touchId)) {
260  TouchRegistry::instance()->addCandidateOwnerForTouch(touchId, this);
261  m_candidateTouches.insert(touchId);
262  }
263  }
264  }
265  event->ignore();
266 
267  if (m_candidateTouches.count() > m_maximumTouchPoints) {
268  rejectGesture();
269  } else if (m_candidateTouches.count() >= m_minimumTouchPoints) {
270  setInternalStatus(InternalStatus::WaitingForOwnership);
271 
272  QSet<int> tmpCandidates(m_candidateTouches);
273  Q_FOREACH(int candidateTouchId, tmpCandidates) {
274  TouchRegistry::instance()->requestTouchOwnership(candidateTouchId, this);
275  }
276  // We accept the gesture; so don't pass to lower items
277  event->accept();
278  }
279 }
280 
281 void TouchGestureArea::touchEvent_waitingForOwnership(QTouchEvent *event)
282 {
283  Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
284  Qt::TouchPointState touchPointState = touchPoint.state();
285  int touchId = touchPoint.id();
286 
287  if (touchPointState == Qt::TouchPointPressed) {
288  if (!m_watchedTouches.contains(touchId)) {
289  TouchRegistry::instance()->addTouchWatcher(touchId, this);
290  m_watchedTouches.insert(touchId);
291  }
292  }
293  }
294 }
295 
296 void TouchGestureArea::touchEvent_recognized(QTouchEvent *event)
297 {
298  Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
299  Qt::TouchPointState touchPointState = touchPoint.state();
300  int touchId = touchPoint.id();
301 
302  if (touchPointState == Qt::TouchPointPressed) {
303  if (!m_watchedTouches.contains(touchId)) {
304  TouchRegistry::instance()->addTouchWatcher(touchId, this);
305  m_watchedTouches.insert(touchId);
306  }
307  }
308  }
309 
310  if (m_watchedTouches.count() > m_maximumTouchPoints) {
311  rejectGesture();
312  } else if (m_watchedTouches.count() >= m_minimumTouchPoints &&
313  m_status==InternalStatus::WaitingForRejection) {
314  setInternalStatus(InternalStatus::Recognized);
315  }
316 }
317 
318 void TouchGestureArea::touchEvent_rejected(QTouchEvent *event)
319 {
320  Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
321  Qt::TouchPointState touchPointState = touchPoint.state();
322  int touchId = touchPoint.id();
323 
324  if (touchPointState == Qt::TouchPointPressed) {
325  if (!m_watchedTouches.contains(touchId)) {
326  TouchRegistry::instance()->addTouchWatcher(touchId, this);
327  m_watchedTouches.insert(touchId);
328  }
329  }
330  }
331 
332  // If this event is a new touch, ignore it and let the item below have it.
333  // We won't recognize any new touch until the whole current gesture is done
334  // i.e. we're in WaitingForTouch state.
335  // And even if it isn't, just ignore it anyway; we don't care about it.
336  event->ignore();
337 }
338 
339 void TouchGestureArea::unownedTouchEvent(QTouchEvent *unownedTouchEvent)
340 {
341  tgaDebug(QString("unownedTouchEvent(%1) %2").arg(statusToString(m_status)).arg(touchEventString(unownedTouchEvent)));
342 
343  // Only monitor unowned touch events for presses/releases
344  if ((unownedTouchEvent->touchPointStates() & (Qt::TouchPointPressed|Qt::TouchPointReleased)) == 0) {
345  return;
346  }
347 
348  switch (m_status) {
349  case InternalStatus::WaitingForTouch:
350  break;
351  case InternalStatus::WaitingForMoreTouches:
352  unownedTouchEvent_waitingForMoreTouches(unownedTouchEvent);
353  // do nothing
354  break;
355  case InternalStatus::WaitingForOwnership:
356  unownedTouchEvent_waitingForOwnership(unownedTouchEvent);
357  break;
358  case InternalStatus::Recognized:
359  case InternalStatus::WaitingForRejection:
360  unownedTouchEvent_recognised(unownedTouchEvent);
361  break;
362  case InternalStatus::Rejected:
363  unownedTouchEvent_rejected(unownedTouchEvent);
364  break;
365  default:
366  break;
367  }
368 
369  updateTouchPoints(unownedTouchEvent);
370 }
371 
372 void TouchGestureArea::unownedTouchEvent_waitingForMoreTouches(QTouchEvent *event)
373 {
374  Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
375  Qt::TouchPointState touchPointState = touchPoint.state();
376  int touchId = touchPoint.id();
377 
378  if (touchPointState == Qt::TouchPointReleased) {
379  if (m_candidateTouches.contains(touchId)) {
380  TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, this);
381  m_candidateTouches.remove(touchId);
382  }
383  }
384  }
385 
386  if (m_candidateTouches.isEmpty()) {
387  setInternalStatus(InternalStatus::WaitingForTouch);
388  }
389 }
390 
391 void TouchGestureArea::unownedTouchEvent_waitingForOwnership(QTouchEvent *event)
392 {
393  Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
394  Qt::TouchPointState touchPointState = touchPoint.state();
395  int touchId = touchPoint.id();
396 
397  if (touchPointState == Qt::TouchPointReleased) {
398  if (m_candidateTouches.contains(touchId)) {
399  TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, this);
400  m_candidateTouches.remove(touchId);
401  }
402  if (m_watchedTouches.contains(touchId)) {
403  m_watchedTouches.remove(touchId);
404  }
405  }
406  }
407 
408  if (m_candidateTouches.count() + m_watchedTouches.count() == 0) {
409  setInternalStatus(InternalStatus::WaitingForTouch);
410  }
411 }
412 
413 void TouchGestureArea::unownedTouchEvent_recognised(QTouchEvent *event)
414 {
415  Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
416  Qt::TouchPointState touchPointState = touchPoint.state();
417  int touchId = touchPoint.id();
418 
419  if (touchPointState == Qt::TouchPointReleased) {
420  if (m_watchedTouches.contains(touchId)) {
421  m_watchedTouches.remove(touchId);
422  }
423  }
424  }
425 
426  if (m_watchedTouches.count() < m_minimumTouchPoints && m_status==InternalStatus::Recognized) {
427  setInternalStatus(InternalStatus::WaitingForRejection);
428  }
429 }
430 
431 void TouchGestureArea::unownedTouchEvent_rejected(QTouchEvent *event)
432 {
433  Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
434  Qt::TouchPointState touchPointState = touchPoint.state();
435  int touchId = touchPoint.id();
436 
437  if (touchPointState == Qt::TouchPointPressed) {
438  if (!m_watchedTouches.contains(touchId)) {
439  TouchRegistry::instance()->addTouchWatcher(touchId, this);
440  m_watchedTouches.insert(touchId);
441  }
442  }
443  if (touchPointState == Qt::TouchPointReleased) {
444  if (m_watchedTouches.contains(touchId)) {
445  m_watchedTouches.remove(touchId);
446  }
447  }
448  }
449 
450  if (m_watchedTouches.isEmpty()) {
451  setInternalStatus(InternalStatus::WaitingForTouch);
452  }
453 }
454 
455 void TouchGestureArea::updateTouchPoints(QTouchEvent *touchEvent)
456 {
457  bool added = false;
458  bool ended = false;
459  bool moved = false;
460 
461  const int dragThreshold = qApp->styleHints()->startDragDistance();
462  const int dragVelocity = qApp->styleHints()->startDragVelocity();
463 
464  clearTouchLists();
465  bool updateable = m_status != InternalStatus::WaitingForRejection;
466 
467  Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, touchEvent->touchPoints()) {
468  Qt::TouchPointState touchPointState = touchPoint.state();
469  int touchId = touchPoint.id();
470 
471  if (touchPointState & Qt::TouchPointReleased) {
472  GestureTouchPoint* gtp = m_liveTouchPoints.value(touchId);
473  if (!gtp) continue;
474 
475  gtp->setPos(touchPoint.pos());
476  gtp->setPressed(false);
477  m_releasedTouchPoints.append(gtp);
478  m_liveTouchPoints.remove(touchId);
479 
480  if (updateable) {
481  if (m_cachedTouchPoints.contains(touchId)) {
482  GestureTouchPoint* cachedPoint = m_cachedTouchPoints.take(touchId);
483  cachedPoint->deleteLater();
484  }
485  }
486  ended = true;
487  } else {
488  GestureTouchPoint* gtp = m_liveTouchPoints.value(touchPoint.id(), nullptr);
489  if (!gtp) {
490  gtp = addTouchPoint(&touchPoint);
491  m_pressedTouchPoints.append(gtp);
492 
493  if (updateable) {
494  if (m_cachedTouchPoints.contains(touchId)) {
495  m_cachedTouchPoints[touchId]->setPos(touchPoint.pos());
496  } else {
497  m_cachedTouchPoints[touchId] = new GestureTouchPoint(*gtp);
498  }
499  }
500  added = true;
501  } else if (touchPointState & Qt::TouchPointMoved) {
502  gtp->setPos(touchPoint.pos());
503  m_movedTouchPoints.append(gtp);
504  moved = true;
505 
506  const QPointF &currentPos = touchPoint.scenePos();
507  const QPointF &startPos = touchPoint.startScenePos();
508 
509  bool overDragThreshold = false;
510  bool supportsVelocity = (touchEvent->device()->capabilities() & QTouchDevice::Velocity) && dragVelocity;
511  overDragThreshold |= qAbs(currentPos.x() - startPos.x()) > dragThreshold ||
512  qAbs(currentPos.y() - startPos.y()) > dragThreshold;
513  if (supportsVelocity) {
514  QVector2D velocityVec = touchPoint.velocity();
515  overDragThreshold |= qAbs(velocityVec.x()) > dragVelocity;
516  overDragThreshold |= qAbs(velocityVec.y()) > dragVelocity;
517  }
518 
519  if (overDragThreshold) {
520  gtp->setDragging(true);
521  }
522 
523  if (updateable) {
524  if (m_cachedTouchPoints.contains(touchId)) {
525  m_cachedTouchPoints[touchId]->setPos(touchPoint.pos());
526  if (overDragThreshold) {
527  m_cachedTouchPoints[touchId]->setDragging(true);
528  }
529  }
530  }
531  }
532  }
533  }
534 
535  if (updateable) {
536  if (!dragging() && m_status == InternalStatus::Recognized) {
537  bool allWantDrag = !m_liveTouchPoints.isEmpty();
538  Q_FOREACH(const auto &point, m_liveTouchPoints) {
539  allWantDrag &= point->dragging();
540  }
541  // only dragging if all points are dragging.
542  if (allWantDrag) {
543  setDragging(true);
544  }
545  }
546 
547  if (ended) {
548  if (m_liveTouchPoints.isEmpty()) {
549  if (!dragging()) Q_EMIT clicked();
550  setDragging(false);
551  }
552  tgaDebug("Released " << touchesString(m_releasedTouchPoints));
553  Q_EMIT released(m_releasedTouchPoints);
554  }
555  if (added) {
556  tgaDebug("Pressed " << touchesString(m_pressedTouchPoints));
557  Q_EMIT pressed(m_pressedTouchPoints);
558  }
559  if (moved) {
560  tgaDebug("Updated " << touchesString(m_movedTouchPoints));
561  Q_EMIT updated(m_movedTouchPoints);
562  }
563  if (added || ended || moved) {
564  Q_EMIT touchPointsUpdated();
565  }
566  }
567 }
568 
569 void TouchGestureArea::clearTouchLists()
570 {
571  Q_FOREACH (QObject *gtp, m_releasedTouchPoints) {
572  delete gtp;
573  }
574  m_releasedTouchPoints.clear();
575  m_pressedTouchPoints.clear();
576  m_movedTouchPoints.clear();
577 }
578 
579 void TouchGestureArea::setInternalStatus(uint newStatus)
580 {
581  if (newStatus == m_status)
582  return;
583 
584  uint oldStatus = m_status;
585 
586  m_status = newStatus;
587  Q_EMIT statusChanged(status());
588 
589  if (oldStatus == InternalStatus::WaitingForMoreTouches || oldStatus == InternalStatus::WaitingForRejection) {
590  m_recognitionTimer->stop();
591  }
592 
593  tgaDebug(statusToString(oldStatus) << " -> " << statusToString(newStatus));
594 
595  switch (newStatus) {
596  case InternalStatus::WaitingForTouch:
597  resyncCachedTouchPoints();
598  break;
599  case InternalStatus::WaitingForMoreTouches:
600  m_recognitionTimer->setInterval(m_recognitionPeriod);
601  m_recognitionTimer->start();
602  break;
603  case InternalStatus::Recognized:
604  resyncCachedTouchPoints();
605  break;
606  case InternalStatus::WaitingForRejection:
607  m_recognitionTimer->setInterval(m_releaseRejectPeriod);
608  m_recognitionTimer->start();
609  break;
610  case InternalStatus::Rejected:
611  resyncCachedTouchPoints();
612  break;
613  default:
614  // no-op
615  break;
616  }
617 }
618 
619 void TouchGestureArea::setRecognitionTimer(AbstractTimer *timer)
620 {
621  int interval = 0;
622  bool timerWasRunning = false;
623  bool wasSingleShot = false;
624 
625  // can be null when called from the constructor
626  if (m_recognitionTimer) {
627  interval = m_recognitionTimer->interval();
628  timerWasRunning = m_recognitionTimer->isRunning();
629  if (m_recognitionTimer->parent() == this) {
630  delete m_recognitionTimer;
631  }
632  }
633 
634  m_recognitionTimer = timer;
635  timer->setInterval(interval);
636  timer->setSingleShot(wasSingleShot);
637  connect(timer, SIGNAL(timeout()),
638  this, SLOT(rejectGesture()));
639  if (timerWasRunning) {
640  m_recognitionTimer->start();
641  }
642 }
643 
644 int TouchGestureArea::status() const
645 {
646  return internalStatusToGestureStatus(m_status);
647 }
648 
649 bool TouchGestureArea::dragging() const
650 {
651  return m_dragging;
652 }
653 
654 QQmlListProperty<GestureTouchPoint> TouchGestureArea::touchPoints()
655 {
656  return QQmlListProperty<GestureTouchPoint>(this,
657  nullptr,
658  TouchGestureArea::touchPoint_count,
659  TouchGestureArea::touchPoint_at);
660 }
661 
662 int TouchGestureArea::minimumTouchPoints() const
663 {
664  return m_minimumTouchPoints;
665 }
666 
667 void TouchGestureArea::setMinimumTouchPoints(int value)
668 {
669  if (m_minimumTouchPoints != value) {
670  m_minimumTouchPoints = value;
671  Q_EMIT minimumTouchPointsChanged(value);
672  }
673 }
674 
675 int TouchGestureArea::maximumTouchPoints() const
676 {
677  return m_maximumTouchPoints;
678 }
679 
680 void TouchGestureArea::setMaximumTouchPoints(int value)
681 {
682  if (m_maximumTouchPoints != value) {
683  m_maximumTouchPoints = value;
684  Q_EMIT maximumTouchPointsChanged(value);
685  }
686 }
687 
688 int TouchGestureArea::recognitionPeriod() const
689 {
690  return m_recognitionPeriod;
691 }
692 
693 void TouchGestureArea::setRecognitionPeriod(int value)
694 {
695  if (value != m_recognitionPeriod) {
696  m_recognitionPeriod = value;
697  Q_EMIT recognitionPeriodChanged(value);
698  }
699 }
700 
701 int TouchGestureArea::releaseRejectPeriod() const
702 {
703  return m_releaseRejectPeriod;
704 }
705 
706 void TouchGestureArea::setReleaseRejectPeriod(int value)
707 {
708  if (value != m_releaseRejectPeriod) {
709  m_releaseRejectPeriod = value;
710  Q_EMIT releaseRejectPeriodChanged(value);
711  }
712 }
713 
714 void TouchGestureArea::rejectGesture()
715 {
716  tgaDebug("rejectGesture");
717  ungrabTouchPoints();
718 
719  Q_FOREACH(int touchId, m_candidateTouches) {
720  TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, this);
721  }
722 
723  // Monitor the candidates
724  Q_FOREACH(int touchId, m_candidateTouches) {
725  TouchRegistry::instance()->addTouchWatcher(touchId, this);
726  m_watchedTouches.insert(touchId);
727  }
728  m_candidateTouches.clear();
729 
730  if (m_watchedTouches.isEmpty()) {
731  setInternalStatus(InternalStatus::WaitingForTouch);
732  } else {
733  setInternalStatus(InternalStatus::Rejected);
734  }
735 }
736 
737 void TouchGestureArea::resyncCachedTouchPoints()
738 {
739  clearTouchLists();
740 
741  bool added = false;
742  bool ended = false;
743  bool moved = false;
744 
745  // list of deletes
746  QMutableHashIterator<int, GestureTouchPoint*> removeIter(m_cachedTouchPoints);
747  while(removeIter.hasNext()) {
748  removeIter.next();
749  if (!m_liveTouchPoints.contains(removeIter.key())) {
750  m_releasedTouchPoints.append(removeIter.value());
751  removeIter.remove();
752  ended = true;
753  }
754  }
755 
756  // list of adds/moves
757  Q_FOREACH(GestureTouchPoint* touchPoint, m_liveTouchPoints) {
758  if (m_cachedTouchPoints.contains(touchPoint->id())) {
759  GestureTouchPoint* cachedPoint = m_cachedTouchPoints[touchPoint->id()];
760 
761  if (*cachedPoint != *touchPoint) {
762  *cachedPoint = *touchPoint;
763  m_movedTouchPoints.append(touchPoint);
764  moved = true;
765  }
766  } else {
767  m_cachedTouchPoints.insert(touchPoint->id(), new GestureTouchPoint(*touchPoint));
768  m_pressedTouchPoints.append(touchPoint);
769  added = true;
770  }
771  }
772 
773  if (ended) {
774  if (m_cachedTouchPoints.isEmpty()) {
775  if (!dragging()) Q_EMIT clicked();
776  setDragging(false);
777  }
778  tgaDebug("Cached Release " << touchesString(m_releasedTouchPoints));
779  Q_EMIT released(m_releasedTouchPoints);
780  }
781  if (added) {
782  tgaDebug("Cached Press " << touchesString(m_pressedTouchPoints));
783  Q_EMIT pressed(m_pressedTouchPoints);
784  }
785  if (moved) {
786  tgaDebug("Cached Update " << touchesString(m_movedTouchPoints));
787  Q_EMIT updated(m_movedTouchPoints);
788  }
789  if (added || ended || moved) Q_EMIT touchPointsUpdated();
790 }
791 
792 int TouchGestureArea::touchPoint_count(QQmlListProperty<GestureTouchPoint> *list)
793 {
794  TouchGestureArea *q = static_cast<TouchGestureArea*>(list->object);
795  return q->m_cachedTouchPoints.count();
796 }
797 
798 GestureTouchPoint *TouchGestureArea::touchPoint_at(QQmlListProperty<GestureTouchPoint> *list, int index)
799 {
800  TouchGestureArea *q = static_cast<TouchGestureArea*>(list->object);
801  return (q->m_cachedTouchPoints.begin()+index).value();
802 }
803 
804 GestureTouchPoint* TouchGestureArea::addTouchPoint(QTouchEvent::TouchPoint const* tp)
805 {
806  GestureTouchPoint* gtp = new GestureTouchPoint();
807  gtp->setId(tp->id());
808  gtp->setPressed(true);
809  gtp->setPos(tp->pos());
810  m_liveTouchPoints.insert(tp->id(), gtp);
811  return gtp;
812 }
813 
814 void TouchGestureArea::itemChange(ItemChange change, const ItemChangeData &value)
815 {
816  if (change == QQuickItem::ItemSceneChange) {
817  if (value.window != nullptr) {
818  value.window->installEventFilter(TouchRegistry::instance());
819  }
820  }
821 }
822 
823 void TouchGestureArea::setDragging(bool dragging)
824 {
825  if (m_dragging == dragging)
826  return;
827 
828  tgaDebug("setDragging " << dragging);
829 
830  m_dragging = dragging;
831  Q_EMIT draggingChanged(m_dragging);
832 }
833 
834 void GestureTouchPoint::setId(int id)
835 {
836  if (m_id == id)
837  return;
838  m_id = id;
839  Q_EMIT idChanged();
840 }
841 
842 void GestureTouchPoint::setPressed(bool pressed)
843 {
844  if (m_pressed == pressed)
845  return;
846  m_pressed = pressed;
847  Q_EMIT pressedChanged();
848 }
849 
850 void GestureTouchPoint::setX(qreal x)
851 {
852  if (m_x == x)
853  return;
854  m_x = x;
855  Q_EMIT xChanged();
856 }
857 
858 void GestureTouchPoint::setY(qreal y)
859 {
860  if (m_y == y)
861  return;
862  m_y = y;
863  Q_EMIT yChanged();
864 }
865 
866 void GestureTouchPoint::setDragging(bool dragging)
867 {
868  if (m_dragging == dragging)
869  return;
870 
871  m_dragging = dragging;
872  Q_EMIT draggingChanged();
873 }
874 
875 void GestureTouchPoint::setPos(const QPointF &pos)
876 {
877  setX(pos.x());
878  setY(pos.y());
879 }