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