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