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