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