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 
333 void TouchGestureArea::unownedTouchEvent(QTouchEvent *unownedTouchEvent)
334 {
335  tgaDebug(QString("unownedTouchEvent(%1) %2").arg(statusToString(m_status)).arg(touchEventString(unownedTouchEvent)));
336 
337  // Only monitor unowned touch events for presses/releases
338  if ((unownedTouchEvent->touchPointStates() & (Qt::TouchPointPressed|Qt::TouchPointReleased)) == 0) {
339  return;
340  }
341 
342  switch (m_status) {
343  case InternalStatus::WaitingForTouch:
344  break;
345  case InternalStatus::WaitingForMoreTouches:
346  unownedTouchEvent_waitingForMoreTouches(unownedTouchEvent);
347  // do nothing
348  break;
349  case InternalStatus::WaitingForOwnership:
350  unownedTouchEvent_waitingForOwnership(unownedTouchEvent);
351  break;
352  case InternalStatus::Recognized:
353  case InternalStatus::WaitingForRejection:
354  unownedTouchEvent_recognised(unownedTouchEvent);
355  break;
356  case InternalStatus::Rejected:
357  unownedTouchEvent_rejected(unownedTouchEvent);
358  break;
359  default:
360  break;
361  }
362 
363  updateTouchPoints(unownedTouchEvent);
364 }
365 
366 void TouchGestureArea::unownedTouchEvent_waitingForMoreTouches(QTouchEvent *event)
367 {
368  Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
369  Qt::TouchPointState touchPointState = touchPoint.state();
370  int touchId = touchPoint.id();
371 
372  if (touchPointState == Qt::TouchPointReleased) {
373  if (m_candidateTouches.contains(touchId)) {
374  TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, this);
375  m_candidateTouches.remove(touchId);
376  }
377  }
378  }
379 
380  if (m_candidateTouches.isEmpty()) {
381  setInternalStatus(InternalStatus::WaitingForTouch);
382  }
383 }
384 
385 void TouchGestureArea::unownedTouchEvent_waitingForOwnership(QTouchEvent *event)
386 {
387  Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
388  Qt::TouchPointState touchPointState = touchPoint.state();
389  int touchId = touchPoint.id();
390 
391  if (touchPointState == Qt::TouchPointReleased) {
392  if (m_candidateTouches.contains(touchId)) {
393  TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, this);
394  m_candidateTouches.remove(touchId);
395  }
396  if (m_watchedTouches.contains(touchId)) {
397  m_watchedTouches.remove(touchId);
398  }
399  }
400  }
401 
402  if (m_candidateTouches.count() + m_watchedTouches.count() == 0) {
403  setInternalStatus(InternalStatus::WaitingForTouch);
404  }
405 }
406 
407 void TouchGestureArea::unownedTouchEvent_recognised(QTouchEvent *event)
408 {
409  Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
410  Qt::TouchPointState touchPointState = touchPoint.state();
411  int touchId = touchPoint.id();
412 
413  if (touchPointState == Qt::TouchPointReleased) {
414  if (m_watchedTouches.contains(touchId)) {
415  m_watchedTouches.remove(touchId);
416  }
417  }
418  }
419 
420  if (m_watchedTouches.count() < m_minimumTouchPoints && m_status==InternalStatus::Recognized) {
421  setInternalStatus(InternalStatus::WaitingForRejection);
422  }
423 }
424 
425 void TouchGestureArea::unownedTouchEvent_rejected(QTouchEvent *event)
426 {
427  Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
428  Qt::TouchPointState touchPointState = touchPoint.state();
429  int touchId = touchPoint.id();
430 
431  if (touchPointState == Qt::TouchPointPressed) {
432  if (!m_watchedTouches.contains(touchId)) {
433  TouchRegistry::instance()->addTouchWatcher(touchId, this);
434  m_watchedTouches.insert(touchId);
435  }
436  }
437  if (touchPointState == Qt::TouchPointReleased) {
438  if (m_watchedTouches.contains(touchId)) {
439  m_watchedTouches.remove(touchId);
440  }
441  }
442  }
443 
444  if (m_watchedTouches.isEmpty()) {
445  setInternalStatus(InternalStatus::WaitingForTouch);
446  }
447 }
448 
449 void TouchGestureArea::updateTouchPoints(QTouchEvent *touchEvent)
450 {
451  bool added = false;
452  bool ended = false;
453  bool moved = false;
454 
455  const int dragThreshold = qApp->styleHints()->startDragDistance();
456  const int dragVelocity = qApp->styleHints()->startDragVelocity();
457 
458  clearTouchLists();
459  bool updateable = m_status != InternalStatus::WaitingForRejection;
460 
461  Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, touchEvent->touchPoints()) {
462  Qt::TouchPointState touchPointState = touchPoint.state();
463  int touchId = touchPoint.id();
464 
465  if (touchPointState & Qt::TouchPointReleased) {
466  GestureTouchPoint* gtp = m_liveTouchPoints.value(touchId);
467  if (!gtp) continue;
468 
469  gtp->setPos(touchPoint.pos());
470  gtp->setPressed(false);
471  m_releasedTouchPoints.append(gtp);
472  m_liveTouchPoints.remove(touchId);
473 
474  if (updateable) {
475  if (m_cachedTouchPoints.contains(touchId)) {
476  GestureTouchPoint* cachedPoint = m_cachedTouchPoints.take(touchId);
477  cachedPoint->deleteLater();
478  }
479  }
480  ended = true;
481  } else {
482  GestureTouchPoint* gtp = m_liveTouchPoints.value(touchPoint.id(), nullptr);
483  if (!gtp) {
484  gtp = addTouchPoint(&touchPoint);
485  m_pressedTouchPoints.append(gtp);
486 
487  if (updateable) {
488  if (m_cachedTouchPoints.contains(touchId)) {
489  m_cachedTouchPoints[touchId]->setPos(touchPoint.pos());
490  } else {
491  m_cachedTouchPoints[touchId] = new GestureTouchPoint(*gtp);
492  }
493  }
494  added = true;
495  } else if (touchPointState & Qt::TouchPointMoved) {
496  gtp->setPos(touchPoint.pos());
497  m_movedTouchPoints.append(gtp);
498  moved = true;
499 
500  const QPointF &currentPos = touchPoint.scenePos();
501  const QPointF &startPos = touchPoint.startScenePos();
502 
503  bool overDragThreshold = false;
504  bool supportsVelocity = (touchEvent->device()->capabilities() & QTouchDevice::Velocity) && dragVelocity;
505  overDragThreshold |= qAbs(currentPos.x() - startPos.x()) > dragThreshold ||
506  qAbs(currentPos.y() - startPos.y()) > dragThreshold;
507  if (supportsVelocity) {
508  QVector2D velocityVec = touchPoint.velocity();
509  overDragThreshold |= qAbs(velocityVec.x()) > dragVelocity;
510  overDragThreshold |= qAbs(velocityVec.y()) > dragVelocity;
511  }
512 
513  if (overDragThreshold) {
514  gtp->setDragging(true);
515  }
516 
517  if (updateable) {
518  if (m_cachedTouchPoints.contains(touchId)) {
519  m_cachedTouchPoints[touchId]->setPos(touchPoint.pos());
520  if (overDragThreshold) {
521  m_cachedTouchPoints[touchId]->setDragging(true);
522  }
523  }
524  }
525  }
526  }
527  }
528 
529  if (updateable) {
530  if (!dragging() && m_status == InternalStatus::Recognized) {
531  bool allWantDrag = !m_liveTouchPoints.isEmpty();
532  Q_FOREACH(const auto &point, m_liveTouchPoints) {
533  allWantDrag &= point->dragging();
534  }
535  // only dragging if all points are dragging.
536  if (allWantDrag) {
537  setDragging(true);
538  }
539  }
540 
541  if (ended) {
542  if (m_liveTouchPoints.isEmpty()) {
543  if (!dragging()) Q_EMIT clicked();
544  setDragging(false);
545  }
546  tgaDebug("Released " << touchesString(m_releasedTouchPoints));
547  Q_EMIT released(m_releasedTouchPoints);
548  }
549  if (added) {
550  tgaDebug("Pressed " << touchesString(m_pressedTouchPoints));
551  Q_EMIT pressed(m_pressedTouchPoints);
552  }
553  if (moved) {
554  tgaDebug("Updated " << touchesString(m_movedTouchPoints));
555  Q_EMIT updated(m_movedTouchPoints);
556  }
557  if (added || ended || moved) {
558  Q_EMIT touchPointsUpdated();
559  }
560  }
561 }
562 
563 void TouchGestureArea::clearTouchLists()
564 {
565  Q_FOREACH (QObject *gtp, m_releasedTouchPoints) {
566  delete gtp;
567  }
568  m_releasedTouchPoints.clear();
569  m_pressedTouchPoints.clear();
570  m_movedTouchPoints.clear();
571 }
572 
573 void TouchGestureArea::setInternalStatus(uint newStatus)
574 {
575  if (newStatus == m_status)
576  return;
577 
578  uint oldStatus = m_status;
579 
580  m_status = newStatus;
581  Q_EMIT statusChanged(status());
582 
583  if (oldStatus == InternalStatus::WaitingForMoreTouches || oldStatus == InternalStatus::WaitingForRejection) {
584  m_recognitionTimer->stop();
585  }
586 
587  tgaDebug(statusToString(oldStatus) << " -> " << statusToString(newStatus));
588 
589  switch (newStatus) {
590  case InternalStatus::WaitingForTouch:
591  resyncCachedTouchPoints();
592  break;
593  case InternalStatus::WaitingForMoreTouches:
594  m_recognitionTimer->setInterval(m_recognitionPeriod);
595  m_recognitionTimer->start();
596  break;
597  case InternalStatus::Recognized:
598  resyncCachedTouchPoints();
599  break;
600  case InternalStatus::WaitingForRejection:
601  m_recognitionTimer->setInterval(m_releaseRejectPeriod);
602  m_recognitionTimer->start();
603  break;
604  case InternalStatus::Rejected:
605  resyncCachedTouchPoints();
606  break;
607  default:
608  // no-op
609  break;
610  }
611 }
612 
613 void TouchGestureArea::setRecognitionTimer(AbstractTimer *timer)
614 {
615  int interval = 0;
616  bool timerWasRunning = false;
617  bool wasSingleShot = false;
618 
619  // can be null when called from the constructor
620  if (m_recognitionTimer) {
621  interval = m_recognitionTimer->interval();
622  timerWasRunning = m_recognitionTimer->isRunning();
623  if (m_recognitionTimer->parent() == this) {
624  delete m_recognitionTimer;
625  }
626  }
627 
628  m_recognitionTimer = timer;
629  timer->setInterval(interval);
630  timer->setSingleShot(wasSingleShot);
631  connect(timer, SIGNAL(timeout()),
632  this, SLOT(rejectGesture()));
633  if (timerWasRunning) {
634  m_recognitionTimer->start();
635  }
636 }
637 
638 int TouchGestureArea::status() const
639 {
640  return internalStatusToGestureStatus(m_status);
641 }
642 
643 bool TouchGestureArea::dragging() const
644 {
645  return m_dragging;
646 }
647 
648 QQmlListProperty<GestureTouchPoint> TouchGestureArea::touchPoints()
649 {
650  return QQmlListProperty<GestureTouchPoint>(this,
651  nullptr,
652  TouchGestureArea::touchPoint_count,
653  TouchGestureArea::touchPoint_at);
654 }
655 
656 int TouchGestureArea::minimumTouchPoints() const
657 {
658  return m_minimumTouchPoints;
659 }
660 
661 void TouchGestureArea::setMinimumTouchPoints(int value)
662 {
663  if (m_minimumTouchPoints != value) {
664  m_minimumTouchPoints = value;
665  Q_EMIT minimumTouchPointsChanged(value);
666  }
667 }
668 
669 int TouchGestureArea::maximumTouchPoints() const
670 {
671  return m_maximumTouchPoints;
672 }
673 
674 void TouchGestureArea::setMaximumTouchPoints(int value)
675 {
676  if (m_maximumTouchPoints != value) {
677  m_maximumTouchPoints = value;
678  Q_EMIT maximumTouchPointsChanged(value);
679  }
680 }
681 
682 int TouchGestureArea::recognitionPeriod() const
683 {
684  return m_recognitionPeriod;
685 }
686 
687 void TouchGestureArea::setRecognitionPeriod(int value)
688 {
689  if (value != m_recognitionPeriod) {
690  m_recognitionPeriod = value;
691  Q_EMIT recognitionPeriodChanged(value);
692  }
693 }
694 
695 int TouchGestureArea::releaseRejectPeriod() const
696 {
697  return m_releaseRejectPeriod;
698 }
699 
700 void TouchGestureArea::setReleaseRejectPeriod(int value)
701 {
702  if (value != m_releaseRejectPeriod) {
703  m_releaseRejectPeriod = value;
704  Q_EMIT releaseRejectPeriodChanged(value);
705  }
706 }
707 
708 void TouchGestureArea::rejectGesture()
709 {
710  tgaDebug("rejectGesture");
711  ungrabTouchPoints();
712 
713  Q_FOREACH(int touchId, m_candidateTouches) {
714  TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, this);
715  }
716 
717  // Monitor the candidates
718  Q_FOREACH(int touchId, m_candidateTouches) {
719  TouchRegistry::instance()->addTouchWatcher(touchId, this);
720  m_watchedTouches.insert(touchId);
721  }
722  m_candidateTouches.clear();
723 
724  if (m_watchedTouches.isEmpty()) {
725  setInternalStatus(InternalStatus::WaitingForTouch);
726  } else {
727  setInternalStatus(InternalStatus::Rejected);
728  }
729 }
730 
731 void TouchGestureArea::resyncCachedTouchPoints()
732 {
733  clearTouchLists();
734 
735  bool added = false;
736  bool ended = false;
737  bool moved = false;
738 
739  // list of deletes
740  QMutableHashIterator<int, GestureTouchPoint*> removeIter(m_cachedTouchPoints);
741  while(removeIter.hasNext()) {
742  removeIter.next();
743  if (!m_liveTouchPoints.contains(removeIter.key())) {
744  m_releasedTouchPoints.append(removeIter.value());
745  removeIter.remove();
746  ended = true;
747  }
748  }
749 
750  // list of adds/moves
751  Q_FOREACH(GestureTouchPoint* touchPoint, m_liveTouchPoints) {
752  if (m_cachedTouchPoints.contains(touchPoint->id())) {
753  GestureTouchPoint* cachedPoint = m_cachedTouchPoints[touchPoint->id()];
754 
755  if (*cachedPoint != *touchPoint) {
756  *cachedPoint = *touchPoint;
757  m_movedTouchPoints.append(touchPoint);
758  moved = true;
759  }
760  } else {
761  m_cachedTouchPoints.insert(touchPoint->id(), new GestureTouchPoint(*touchPoint));
762  m_pressedTouchPoints.append(touchPoint);
763  added = true;
764  }
765  }
766 
767  if (ended) {
768  if (m_cachedTouchPoints.isEmpty()) {
769  if (!dragging()) Q_EMIT clicked();
770  setDragging(false);
771  }
772  tgaDebug("Cached Release " << touchesString(m_releasedTouchPoints));
773  Q_EMIT released(m_releasedTouchPoints);
774  }
775  if (added) {
776  tgaDebug("Cached Press " << touchesString(m_pressedTouchPoints));
777  Q_EMIT pressed(m_pressedTouchPoints);
778  }
779  if (moved) {
780  tgaDebug("Cached Update " << touchesString(m_movedTouchPoints));
781  Q_EMIT updated(m_movedTouchPoints);
782  }
783  if (added || ended || moved) Q_EMIT touchPointsUpdated();
784 }
785 
786 int TouchGestureArea::touchPoint_count(QQmlListProperty<GestureTouchPoint> *list)
787 {
788  TouchGestureArea *q = static_cast<TouchGestureArea*>(list->object);
789  return q->m_cachedTouchPoints.count();
790 }
791 
792 GestureTouchPoint *TouchGestureArea::touchPoint_at(QQmlListProperty<GestureTouchPoint> *list, int index)
793 {
794  TouchGestureArea *q = static_cast<TouchGestureArea*>(list->object);
795  return (q->m_cachedTouchPoints.begin()+index).value();
796 }
797 
798 GestureTouchPoint* TouchGestureArea::addTouchPoint(QTouchEvent::TouchPoint const* tp)
799 {
800  GestureTouchPoint* gtp = new GestureTouchPoint();
801  gtp->setId(tp->id());
802  gtp->setPressed(true);
803  gtp->setPos(tp->pos());
804  m_liveTouchPoints.insert(tp->id(), gtp);
805  return gtp;
806 }
807 
808 void TouchGestureArea::itemChange(ItemChange change, const ItemChangeData &value)
809 {
810  if (change == QQuickItem::ItemSceneChange) {
811  if (value.window != nullptr) {
812  value.window->installEventFilter(TouchRegistry::instance());
813  }
814  }
815 }
816 
817 void TouchGestureArea::setDragging(bool dragging)
818 {
819  if (m_dragging == dragging)
820  return;
821 
822  tgaDebug("setDragging " << dragging);
823 
824  m_dragging = dragging;
825  Q_EMIT draggingChanged(m_dragging);
826 }
827 
828 void GestureTouchPoint::setId(int id)
829 {
830  if (m_id == id)
831  return;
832  m_id = id;
833  Q_EMIT idChanged();
834 }
835 
836 void GestureTouchPoint::setPressed(bool pressed)
837 {
838  if (m_pressed == pressed)
839  return;
840  m_pressed = pressed;
841  Q_EMIT pressedChanged();
842 }
843 
844 void GestureTouchPoint::setX(qreal x)
845 {
846  if (m_x == x)
847  return;
848  m_x = x;
849  Q_EMIT xChanged();
850 }
851 
852 void GestureTouchPoint::setY(qreal y)
853 {
854  if (m_y == y)
855  return;
856  m_y = y;
857  Q_EMIT yChanged();
858 }
859 
860 void GestureTouchPoint::setDragging(bool dragging)
861 {
862  if (m_dragging == dragging)
863  return;
864 
865  m_dragging = dragging;
866  Q_EMIT draggingChanged();
867 }
868 
869 void GestureTouchPoint::setPos(const QPointF &pos)
870 {
871  setX(pos.x());
872  setY(pos.y());
873 }