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(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(c);
105 for (int i = 0; i < c; ++i) {
106 d->currentPoints[i].reset(candidatePoints[i]);
107 d->currentPoints[i].localize(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(p.id());
124 if (ep)
125 p.reset(ep);
126 }
127 QPointF sceneGrabPos = d->centroid.sceneGrabPosition();
128 d->centroid.reset(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 *, QQuickEventPoint::GrabTransition transition, QQuickEventPoint *)
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}
159
160QVector<QQuickEventPoint *> QQuickMultiPointHandler::eligiblePoints(QQuickPointerEvent *event)
161{
162 QVector<QQuickEventPoint *> ret;
163 int c = event->pointCount();
164 // If one or more points are newly pressed or released, all non-released points are candidates for this handler.
165 // In other cases however, check whether it would be OK to steal the grab if the handler chooses to do that.
166 bool stealingAllowed = event->isPressEvent() || event->isReleaseEvent();
167 for (int i = 0; i < c; ++i) {
168 QQuickEventPoint *p = event->point(i);
169 if (QQuickPointerMouseEvent *me = event->asPointerMouseEvent()) {
170 if (me->buttons() == Qt::NoButton)
171 continue;
172 }
173 if (!stealingAllowed) {
174 QObject *exclusiveGrabber = p->exclusiveGrabber();
175 if (exclusiveGrabber && exclusiveGrabber != this && !canGrab(p))
176 continue;
177 }
178 if (p->state() != QQuickEventPoint::Released && wantsEventPoint(p))
179 ret << p;
180 }
181 return ret;
182}
183
184/*!
185 \qmlproperty int MultiPointHandler::minimumPointCount
186
187 The minimum number of touchpoints required to activate this handler.
188
189 If a smaller number of touchpoints are in contact with the
190 \l {PointerHandler::parent}{parent}, they will be ignored.
191
192 Any ignored points are eligible to activate other Input Handlers that
193 have different constraints, on the same Item or on other Items.
194
195 The default value is 2.
196*/
197int QQuickMultiPointHandler::minimumPointCount() const
198{
199 Q_D(const QQuickMultiPointHandler);
200 return d->minimumPointCount;
201}
202
203void QQuickMultiPointHandler::setMinimumPointCount(int c)
204{
205 Q_D(QQuickMultiPointHandler);
206 if (d->minimumPointCount == c)
207 return;
208
209 d->minimumPointCount = c;
210 emit minimumPointCountChanged();
211 if (d->maximumPointCount < 0)
212 emit maximumPointCountChanged();
213}
214
215/*!
216 \qmlproperty int MultiPointHandler::maximumPointCount
217
218 The maximum number of touchpoints this handler can utilize.
219
220 If a larger number of touchpoints are in contact with the
221 \l {PointerHandler::parent}{parent}, the required number of points will be
222 chosen in the order that they are pressed, and the remaining points will
223 be ignored.
224
225 Any ignored points are eligible to activate other Input Handlers that
226 have different constraints, on the same Item or on other Items.
227
228 The default value is the same as \l minimumPointCount.
229*/
230int QQuickMultiPointHandler::maximumPointCount() const
231{
232 Q_D(const QQuickMultiPointHandler);
233 return d->maximumPointCount >= 0 ? d->maximumPointCount : d->minimumPointCount;
234}
235
236void QQuickMultiPointHandler::setMaximumPointCount(int maximumPointCount)
237{
238 Q_D(QQuickMultiPointHandler);
239 if (d->maximumPointCount == maximumPointCount)
240 return;
241
242 d->maximumPointCount = maximumPointCount;
243 emit maximumPointCountChanged();
244}
245
246/*!
247 \readonly
248 \qmlproperty QtQuick::HandlerPoint QtQuick::MultiPointHandler::centroid
249
250 A point exactly in the middle of the currently-pressed touch points.
251 If only one point is pressed, it's the same as that point.
252 A handler that has a \l target will normally transform it relative to this point.
253*/
254const QQuickHandlerPoint &QQuickMultiPointHandler::centroid() const
255{
256 Q_D(const QQuickMultiPointHandler);
257 return d->centroid;
258}
259
260/*!
261 Returns a modifiable reference to the point that will be returned by the
262 \l centroid property. If you modify it, you are responsible to emit
263 \l centroidChanged.
264*/
265QQuickHandlerPoint &QQuickMultiPointHandler::mutableCentroid()
266{
267 Q_D(QQuickMultiPointHandler);
268 return d->centroid;
269}
270
271QVector<QQuickHandlerPoint> &QQuickMultiPointHandler::currentPoints()
272{
273 Q_D(QQuickMultiPointHandler);
274 return d->currentPoints;
275}
276
277bool QQuickMultiPointHandler::hasCurrentPoints(QQuickPointerEvent *event)
278{
279 Q_D(const QQuickMultiPointHandler);
280 if (event->pointCount() < d->currentPoints.size() || d->currentPoints.size() == 0)
281 return false;
282 // TODO optimize: either ensure the points are sorted,
283 // or use std::equal with a predicate
284 for (const QQuickHandlerPoint &p : qAsConst(d->currentPoints)) {
285 const QQuickEventPoint *ep = event->pointById(p.id());
286 if (!ep)
287 return false;
288 if (ep->state() == QQuickEventPoint::Released)
289 return false;
290 }
291 return true;
292}
293
294qreal QQuickMultiPointHandler::averageTouchPointDistance(const QPointF &ref)
295{
296 Q_D(const QQuickMultiPointHandler);
297 qreal ret = 0;
298 if (Q_UNLIKELY(d->currentPoints.size() == 0))
299 return ret;
300 for (const QQuickHandlerPoint &p : d->currentPoints)
301 ret += QVector2D(p.scenePosition() - ref).length();
302 return ret / d->currentPoints.size();
303}
304
305qreal QQuickMultiPointHandler::averageStartingDistance(const QPointF &ref)
306{
307 Q_D(const QQuickMultiPointHandler);
308 // TODO cache it in setActive()?
309 qreal ret = 0;
310 if (Q_UNLIKELY(d->currentPoints.size() == 0))
311 return ret;
312 for (const QQuickHandlerPoint &p : d->currentPoints)
313 ret += QVector2D(p.sceneGrabPosition() - ref).length();
314 return ret / d->currentPoints.size();
315}
316
317QVector<QQuickMultiPointHandler::PointData> QQuickMultiPointHandler::angles(const QPointF &ref) const
318{
319 Q_D(const QQuickMultiPointHandler);
320 QVector<PointData> angles;
321 angles.reserve(d->currentPoints.count());
322 for (const QQuickHandlerPoint &p : d->currentPoints) {
323 qreal angle = QLineF(ref, p.scenePosition()).angle();
324 angles.append(PointData(p.id(), -angle)); // convert to clockwise, to be consistent with QQuickItem::rotation
325 }
326 return angles;
327}
328
329qreal QQuickMultiPointHandler::averageAngleDelta(const QVector<PointData> &old, const QVector<PointData> &newAngles)
330{
331 qreal avgAngleDelta = 0;
332 int numSamples = 0;
333
334 auto oldBegin = old.constBegin();
335
336 for (PointData newData : newAngles) {
337 quint64 id = newData.id;
338 auto it = std::find_if(oldBegin, old.constEnd(), [id] (PointData pd) { return pd.id == id; });
339 qreal angleD = 0;
340 if (it != old.constEnd()) {
341 PointData oldData = *it;
342 // We might rotate from 359 degrees to 1 degree. However, this
343 // should be interpreted as a rotation of +2 degrees instead of
344 // -358 degrees. Therefore, we call remainder() to translate the angle
345 // to be in the range [-180, 180] (-350 to +10 etc)
346 angleD = remainder(newData.angle - oldData.angle, qreal(360));
347 // optimization: narrow down the O(n^2) search to optimally O(n)
348 // if both vectors have the same points and they are in the same order
349 if (it == oldBegin)
350 ++oldBegin;
351 numSamples++;
352 }
353 avgAngleDelta += angleD;
354 }
355 if (numSamples > 1)
356 avgAngleDelta /= numSamples;
357
358 return avgAngleDelta;
359}
360
361void QQuickMultiPointHandler::acceptPoints(const QVector<QQuickEventPoint *> &points)
362{
363 for (QQuickEventPoint* point : points)
364 point->setAccepted();
365}
366
367bool QQuickMultiPointHandler::grabPoints(QVector<QQuickEventPoint *> points)
368{
369 if (points.isEmpty())
370 return false;
371 bool allowed = true;
372 for (QQuickEventPoint* point : points) {
373 if (point->exclusiveGrabber() != this && !canGrab(point)) {
374 allowed = false;
375 break;
376 }
377 }
378 if (allowed) {
379 for (QQuickEventPoint* point : points)
380 setExclusiveGrab(point);
381 }
382 return allowed;
383}
384
385void QQuickMultiPointHandler::moveTarget(QPointF pos)
386{
387 Q_D(QQuickMultiPointHandler);
388 if (QQuickItem *t = target()) {
389 d->xMetaProperty().write(t, pos.x());
390 d->yMetaProperty().write(t, pos.y());
391 d->centroid.m_position = t->mapFromScene(d->centroid.m_scenePosition);
392 } else {
393 qWarning() << "moveTarget: target is null";
394 }
395}
396
397QQuickMultiPointHandlerPrivate::QQuickMultiPointHandlerPrivate(int minPointCount, int maxPointCount)
398 : QQuickPointerDeviceHandlerPrivate()
399 , minimumPointCount(minPointCount)
400 , maximumPointCount(maxPointCount)
401{
402}
403
404QMetaProperty &QQuickMultiPointHandlerPrivate::xMetaProperty() const
405{
406 Q_Q(const QQuickMultiPointHandler);
407 if (!xProperty.isValid() && q->target()) {
408 const QMetaObject *targetMeta = q->target()->metaObject();
409 xProperty = targetMeta->property(targetMeta->indexOfProperty("x"));
410 }
411 return xProperty;
412}
413
414QMetaProperty &QQuickMultiPointHandlerPrivate::yMetaProperty() const
415{
416 Q_Q(const QQuickMultiPointHandler);
417 if (!yProperty.isValid() && q->target()) {
418 const QMetaObject *targetMeta = q->target()->metaObject();
419 yProperty = targetMeta->property(targetMeta->indexOfProperty("y"));
420 }
421 return yProperty;
422}
423
424QT_END_NAMESPACE
425