1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtQuick module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qquickmultipointhandler_p.h"
41#include "qquickmultipointhandler_p_p.h"
42#include <private/qquickitem_p.h>
43#include <QLineF>
44#include <QMouseEvent>
45#include <QDebug>
46
47QT_BEGIN_NAMESPACE
48
49/*!
50 \qmltype MultiPointHandler
51 \since 5.10
52 \preliminary
53 \instantiates QQuickMultiPointHandler
54 \inherits PointerDeviceHandler
55 \inqmlmodule QtQuick
56 \brief Abstract handler for multi-point Pointer Events.
57
58 An intermediate class (not registered as a QML type)
59 for any type of handler which requires and acts upon a specific number
60 of multiple touchpoints.
61*/
62QQuickMultiPointHandler::QQuickMultiPointHandler(QQuickItem *parent, int minimumPointCount, int maximumPointCount)
63 : QQuickPointerDeviceHandler(*(new QQuickMultiPointHandlerPrivate(minimumPointCount, maximumPointCount)), parent)
64{
65}
66
67bool QQuickMultiPointHandler::wantsPointerEvent(QQuickPointerEvent *event)
68{
69 Q_D(QQuickMultiPointHandler);
70 if (!QQuickPointerDeviceHandler::wantsPointerEvent(event))
71 return false;
72
73 if (event->asPointerScrollEvent())
74 return false;
75
76 bool ret = false;
77#if QT_CONFIG(gestures)
78 if (event->asPointerNativeGestureEvent() && event->point(i: 0)->state() != QQuickEventPoint::Released)
79 ret = true;
80#endif
81
82 // If points were pressed or released within parentItem, reset stored state
83 // and check eligible points again. This class of handlers is intended to
84 // handle a specific number of points, so a differing number of points will
85 // usually result in different behavior. But otherwise if the currentPoints
86 // are all still there in the event, we're good to go (do not reset
87 // currentPoints, because we don't want to lose the pressPosition, and do
88 // not want to reshuffle the order either).
89 const QVector<QQuickEventPoint *> candidatePoints = eligiblePoints(event);
90 if (candidatePoints.count() != d->currentPoints.count()) {
91 d->currentPoints.clear();
92 if (active()) {
93 setActive(false);
94 d->centroid.reset();
95 emit centroidChanged();
96 }
97 } else if (hasCurrentPoints(event)) {
98 return true;
99 }
100
101 ret = ret || (candidatePoints.size() >= minimumPointCount() && candidatePoints.size() <= maximumPointCount());
102 if (ret) {
103 const int c = candidatePoints.count();
104 d->currentPoints.resize(asize: c);
105 for (int i = 0; i < c; ++i) {
106 d->currentPoints[i].reset(point: candidatePoints[i]);
107 d->currentPoints[i].localize(item: parentItem());
108 }
109 } else {
110 d->currentPoints.clear();
111 }
112 return ret;
113}
114
115void QQuickMultiPointHandler::handlePointerEventImpl(QQuickPointerEvent *event)
116{
117 Q_D(QQuickMultiPointHandler);
118 QQuickPointerHandler::handlePointerEventImpl(event);
119 // event's points can be reordered since the previous event, which is why currentPoints
120 // is _not_ a shallow copy of the QQuickPointerTouchEvent::m_touchPoints vector.
121 // So we have to update our currentPoints instances based on the given event.
122 for (QQuickHandlerPoint &p : d->currentPoints) {
123 const QQuickEventPoint *ep = event->pointById(pointId: p.id());
124 if (ep)
125 p.reset(point: ep);
126 }
127 QPointF sceneGrabPos = d->centroid.sceneGrabPosition();
128 d->centroid.reset(points: d->currentPoints);
129 d->centroid.m_sceneGrabPosition = sceneGrabPos; // preserve as it was
130 emit centroidChanged();
131}
132
133void QQuickMultiPointHandler::onActiveChanged()
134{
135 Q_D(QQuickMultiPointHandler);
136 if (active()) {
137 d->centroid.m_sceneGrabPosition = d->centroid.m_scenePosition;
138 } else {
139 // Don't call centroid.reset() here, because in a QML onActiveChanged
140 // callback, we'd like to see what the position _was_, what the velocity _was_, etc.
141 // (having them undefined is not useful)
142 // But pressedButtons and pressedModifiers are meant to be more real-time than those
143 // (which seems a bit inconsistent, from one side).
144 d->centroid.m_pressedButtons = Qt::NoButton;
145 d->centroid.m_pressedModifiers = Qt::NoModifier;
146 }
147}
148
149void QQuickMultiPointHandler::onGrabChanged(QQuickPointerHandler *grabber, QQuickEventPoint::GrabTransition transition, QQuickEventPoint *point)
150{
151 Q_D(QQuickMultiPointHandler);
152 // If another handler or item takes over this set of points, assume it has
153 // decided that it's the better fit for them. Don't immediately re-grab
154 // at the next opportunity. This should help to avoid grab cycles
155 // (e.g. between DragHandler and PinchHandler).
156 if (transition == QQuickEventPoint::UngrabExclusive || transition == QQuickEventPoint::CancelGrabExclusive)
157 d->currentPoints.clear();
158 if (grabber != this)
159 return;
160 switch (transition) {
161 case QQuickEventPoint::GrabExclusive:
162 case QQuickEventPoint::GrabPassive:
163 case QQuickEventPoint::UngrabPassive:
164 case QQuickEventPoint::UngrabExclusive:
165 case QQuickEventPoint::CancelGrabPassive:
166 case QQuickEventPoint::CancelGrabExclusive:
167 QQuickPointerHandler::onGrabChanged(grabber, transition, point);
168 break;
169 case QQuickEventPoint::OverrideGrabPassive:
170 return; // don't emit
171 }
172}
173
174QVector<QQuickEventPoint *> QQuickMultiPointHandler::eligiblePoints(QQuickPointerEvent *event)
175{
176 QVector<QQuickEventPoint *> ret;
177 int c = event->pointCount();
178 // If one or more points are newly pressed or released, all non-released points are candidates for this handler.
179 // In other cases however, check whether it would be OK to steal the grab if the handler chooses to do that.
180 bool stealingAllowed = event->isPressEvent() || event->isReleaseEvent();
181 for (int i = 0; i < c; ++i) {
182 QQuickEventPoint *p = event->point(i);
183 if (QQuickPointerMouseEvent *me = event->asPointerMouseEvent()) {
184 if (me->buttons() == Qt::NoButton)
185 continue;
186 }
187 if (!stealingAllowed) {
188 QObject *exclusiveGrabber = p->exclusiveGrabber();
189 if (exclusiveGrabber && exclusiveGrabber != this && !canGrab(point: p))
190 continue;
191 }
192 if (p->state() != QQuickEventPoint::Released && wantsEventPoint(point: p))
193 ret << p;
194 }
195 return ret;
196}
197
198/*!
199 \qmlproperty int MultiPointHandler::minimumPointCount
200
201 The minimum number of touchpoints required to activate this handler.
202
203 If a smaller number of touchpoints are in contact with the
204 \l {PointerHandler::parent}{parent}, they will be ignored.
205
206 Any ignored points are eligible to activate other Input Handlers that
207 have different constraints, on the same Item or on other Items.
208
209 The default value is 2.
210*/
211int QQuickMultiPointHandler::minimumPointCount() const
212{
213 Q_D(const QQuickMultiPointHandler);
214 return d->minimumPointCount;
215}
216
217void QQuickMultiPointHandler::setMinimumPointCount(int c)
218{
219 Q_D(QQuickMultiPointHandler);
220 if (d->minimumPointCount == c)
221 return;
222
223 d->minimumPointCount = c;
224 emit minimumPointCountChanged();
225 if (d->maximumPointCount < 0)
226 emit maximumPointCountChanged();
227}
228
229/*!
230 \qmlproperty int MultiPointHandler::maximumPointCount
231
232 The maximum number of touchpoints this handler can utilize.
233
234 If a larger number of touchpoints are in contact with the
235 \l {PointerHandler::parent}{parent}, the required number of points will be
236 chosen in the order that they are pressed, and the remaining points will
237 be ignored.
238
239 Any ignored points are eligible to activate other Input Handlers that
240 have different constraints, on the same Item or on other Items.
241
242 The default value is the same as \l minimumPointCount.
243*/
244int QQuickMultiPointHandler::maximumPointCount() const
245{
246 Q_D(const QQuickMultiPointHandler);
247 return d->maximumPointCount >= 0 ? d->maximumPointCount : d->minimumPointCount;
248}
249
250void QQuickMultiPointHandler::setMaximumPointCount(int maximumPointCount)
251{
252 Q_D(QQuickMultiPointHandler);
253 if (d->maximumPointCount == maximumPointCount)
254 return;
255
256 d->maximumPointCount = maximumPointCount;
257 emit maximumPointCountChanged();
258}
259
260/*!
261 \readonly
262 \qmlproperty QtQuick::HandlerPoint QtQuick::MultiPointHandler::centroid
263
264 A point exactly in the middle of the currently-pressed touch points.
265 If only one point is pressed, it's the same as that point.
266 A handler that has a \l target will normally transform it relative to this point.
267*/
268const QQuickHandlerPoint &QQuickMultiPointHandler::centroid() const
269{
270 Q_D(const QQuickMultiPointHandler);
271 return d->centroid;
272}
273
274/*!
275 Returns a modifiable reference to the point that will be returned by the
276 \l centroid property. If you modify it, you are responsible to emit
277 \l centroidChanged.
278*/
279QQuickHandlerPoint &QQuickMultiPointHandler::mutableCentroid()
280{
281 Q_D(QQuickMultiPointHandler);
282 return d->centroid;
283}
284
285QVector<QQuickHandlerPoint> &QQuickMultiPointHandler::currentPoints()
286{
287 Q_D(QQuickMultiPointHandler);
288 return d->currentPoints;
289}
290
291bool QQuickMultiPointHandler::hasCurrentPoints(QQuickPointerEvent *event)
292{
293 Q_D(const QQuickMultiPointHandler);
294 if (event->pointCount() < d->currentPoints.size() || d->currentPoints.size() == 0)
295 return false;
296 // TODO optimize: either ensure the points are sorted,
297 // or use std::equal with a predicate
298 for (const QQuickHandlerPoint &p : qAsConst(t: d->currentPoints)) {
299 const QQuickEventPoint *ep = event->pointById(pointId: p.id());
300 if (!ep)
301 return false;
302 if (ep->state() == QQuickEventPoint::Released)
303 return false;
304 }
305 return true;
306}
307
308qreal QQuickMultiPointHandler::averageTouchPointDistance(const QPointF &ref)
309{
310 Q_D(const QQuickMultiPointHandler);
311 qreal ret = 0;
312 if (Q_UNLIKELY(d->currentPoints.size() == 0))
313 return ret;
314 for (const QQuickHandlerPoint &p : d->currentPoints)
315 ret += QVector2D(p.scenePosition() - ref).length();
316 return ret / d->currentPoints.size();
317}
318
319qreal QQuickMultiPointHandler::averageStartingDistance(const QPointF &ref)
320{
321 Q_D(const QQuickMultiPointHandler);
322 // TODO cache it in setActive()?
323 qreal ret = 0;
324 if (Q_UNLIKELY(d->currentPoints.size() == 0))
325 return ret;
326 for (const QQuickHandlerPoint &p : d->currentPoints)
327 ret += QVector2D(p.sceneGrabPosition() - ref).length();
328 return ret / d->currentPoints.size();
329}
330
331QVector<QQuickMultiPointHandler::PointData> QQuickMultiPointHandler::angles(const QPointF &ref) const
332{
333 Q_D(const QQuickMultiPointHandler);
334 QVector<PointData> angles;
335 angles.reserve(asize: d->currentPoints.count());
336 for (const QQuickHandlerPoint &p : d->currentPoints) {
337 qreal angle = QLineF(ref, p.scenePosition()).angle();
338 angles.append(t: PointData(p.id(), -angle)); // convert to clockwise, to be consistent with QQuickItem::rotation
339 }
340 return angles;
341}
342
343qreal QQuickMultiPointHandler::averageAngleDelta(const QVector<PointData> &old, const QVector<PointData> &newAngles)
344{
345 qreal avgAngleDelta = 0;
346 int numSamples = 0;
347
348 auto oldBegin = old.constBegin();
349
350 for (PointData newData : newAngles) {
351 quint64 id = newData.id;
352 auto it = std::find_if(first: oldBegin, last: old.constEnd(), pred: [id] (PointData pd) { return pd.id == id; });
353 qreal angleD = 0;
354 if (it != old.constEnd()) {
355 PointData oldData = *it;
356 // We might rotate from 359 degrees to 1 degree. However, this
357 // should be interpreted as a rotation of +2 degrees instead of
358 // -358 degrees. Therefore, we call remainder() to translate the angle
359 // to be in the range [-180, 180] (-350 to +10 etc)
360 angleD = remainder(x: newData.angle - oldData.angle, y: qreal(360));
361 // optimization: narrow down the O(n^2) search to optimally O(n)
362 // if both vectors have the same points and they are in the same order
363 if (it == oldBegin)
364 ++oldBegin;
365 numSamples++;
366 }
367 avgAngleDelta += angleD;
368 }
369 if (numSamples > 1)
370 avgAngleDelta /= numSamples;
371
372 return avgAngleDelta;
373}
374
375void QQuickMultiPointHandler::acceptPoints(const QVector<QQuickEventPoint *> &points)
376{
377 for (QQuickEventPoint* point : points)
378 point->setAccepted();
379}
380
381bool QQuickMultiPointHandler::grabPoints(const QVector<QQuickEventPoint *> &points)
382{
383 if (points.isEmpty())
384 return false;
385 bool allowed = true;
386 for (QQuickEventPoint* point : points) {
387 if (point->exclusiveGrabber() != this && !canGrab(point)) {
388 allowed = false;
389 break;
390 }
391 }
392 if (allowed) {
393 for (QQuickEventPoint* point : points)
394 setExclusiveGrab(point);
395 }
396 return allowed;
397}
398
399void QQuickMultiPointHandler::moveTarget(QPointF pos)
400{
401 Q_D(QQuickMultiPointHandler);
402 if (QQuickItem *t = target()) {
403 d->xMetaProperty().write(obj: t, value: pos.x());
404 d->yMetaProperty().write(obj: t, value: pos.y());
405 d->centroid.m_position = t->mapFromScene(point: d->centroid.m_scenePosition);
406 } else {
407 qWarning() << "moveTarget: target is null";
408 }
409}
410
411QQuickMultiPointHandlerPrivate::QQuickMultiPointHandlerPrivate(int minPointCount, int maxPointCount)
412 : QQuickPointerDeviceHandlerPrivate()
413 , minimumPointCount(minPointCount)
414 , maximumPointCount(maxPointCount)
415{
416}
417
418QMetaProperty &QQuickMultiPointHandlerPrivate::xMetaProperty() const
419{
420 Q_Q(const QQuickMultiPointHandler);
421 if (!xProperty.isValid() && q->target()) {
422 const QMetaObject *targetMeta = q->target()->metaObject();
423 xProperty = targetMeta->property(index: targetMeta->indexOfProperty(name: "x"));
424 }
425 return xProperty;
426}
427
428QMetaProperty &QQuickMultiPointHandlerPrivate::yMetaProperty() const
429{
430 Q_Q(const QQuickMultiPointHandler);
431 if (!yProperty.isValid() && q->target()) {
432 const QMetaObject *targetMeta = q->target()->metaObject();
433 yProperty = targetMeta->property(index: targetMeta->indexOfProperty(name: "y"));
434 }
435 return yProperty;
436}
437
438QT_END_NAMESPACE
439

source code of qtdeclarative/src/quick/handlers/qquickmultipointhandler.cpp