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
qquickdraghandler.cpp
Go to the documentation of this file.
1// Copyright (C) 2018 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
6#include <private/qquickwindow_p.h>
7#include <private/qquickmultipointhandler_p_p.h>
8#include <QDebug>
9
11
13
14Q_STATIC_LOGGING_CATEGORY(lcDragHandler, "qt.quick.handler.drag")
15
16/*!
17 \qmltype DragHandler
18 \nativetype QQuickDragHandler
19 \inherits MultiPointHandler
20 \inqmlmodule QtQuick
21 \ingroup qtquick-input-handlers
22 \brief Handler for dragging.
23
24 DragHandler is a handler that is used to interactively move an Item.
25 Like other Input Handlers, by default it is fully functional, and
26 manipulates its \l {PointerHandler::target} {target}.
27
28 \snippet pointerHandlers/dragHandler.qml 0
29
30 It has properties to restrict the range of dragging.
31
32 If it is declared within one Item but is assigned a different
33 \l {PointerHandler::target} {target}, then it handles events within the
34 bounds of the \l {PointerHandler::parent} {parent} Item but
35 manipulates the \c target Item instead:
36
37 \snippet pointerHandlers/dragHandlerDifferentTarget.qml 0
38
39 A third way to use it is to set \l {PointerHandler::target} {target} to
40 \c null and react to property changes in some other way:
41
42 \snippet pointerHandlers/dragHandlerNullTarget.qml 0
43
44 If minimumPointCount and maximumPointCount are set to values larger than 1,
45 the user will need to drag that many fingers in the same direction to start
46 dragging. A multi-finger drag gesture can be detected independently of both
47 a (default) single-finger DragHandler and a PinchHandler on the same Item,
48 and thus can be used to adjust some other feature independently of the
49 usual pinch behavior: for example adjust a tilt transformation, or adjust
50 some other numeric value, if the \c target is set to null. But if the
51 \c target is an Item, \c centroid is the point at which the drag begins and
52 to which the \c target will be moved (subject to constraints).
53
54 DragHandler can be used together with the \l Drag attached property to
55 implement drag-and-drop.
56
57 \sa Drag, MouseArea, {Qt Quick Examples - Pointer Handlers}
58*/
59
60QQuickDragHandler::QQuickDragHandler(QQuickItem *parent)
61 : QQuickMultiPointHandler(parent, 1, 1)
62{
63}
64
65QPointF QQuickDragHandler::targetCentroidPosition()
66{
67 QPointF pos = centroid().position();
68 if (auto par = parentItem()) {
69 if (target() != par)
70 pos = par->mapToItem(target(), pos);
71 }
72 return pos;
73}
74
75void QQuickDragHandler::onGrabChanged(QQuickPointerHandler *grabber, QPointingDevice::GrabTransition transition, QPointerEvent *event, QEventPoint &point)
76{
77 QQuickMultiPointHandler::onGrabChanged(grabber, transition, event, point);
78 if (grabber == this && transition == QPointingDevice::GrabExclusive && target()) {
79 // In case the grab got handed over from another grabber, we might not get the Press.
80
81 auto isDescendant = [](QQuickItem *parent, QQuickItem *target) {
82 return parent && (target != parent) && !target->isAncestorOf(parent);
83 };
84 if (m_snapMode == SnapAlways
85 || (m_snapMode == SnapIfPressedOutsideTarget && !m_pressedInsideTarget)
86 || (m_snapMode == SnapAuto && !m_pressedInsideTarget && isDescendant(parentItem(), target()))
87 ) {
88 m_pressTargetPos = QPointF(target()->width(), target()->height()) / 2;
89 } else if (m_pressTargetPos.isNull()) {
90 m_pressTargetPos = targetCentroidPosition();
91 }
92 }
93}
94
95/*!
96 \qmlproperty enumeration QtQuick::DragHandler::snapMode
97
98 This property holds the snap mode.
99
100 The snap mode configures snapping of the \l target item's center to the \l eventPoint.
101
102 Possible values:
103 \value DragHandler.NoSnap Never snap
104 \value DragHandler.SnapAuto The \l target snaps if the \l eventPoint was pressed outside of the \l target
105 item \e and the \l target is a descendant of \l {PointerHandler::}{parent} item (default)
106 \value DragHandler.SnapWhenPressedOutsideTarget The \l target snaps if the \l eventPoint was pressed outside of the \l target
107 \value DragHandler.SnapAlways Always snap
108*/
109QQuickDragHandler::SnapMode QQuickDragHandler::snapMode() const
110{
111 return m_snapMode;
112}
113
114void QQuickDragHandler::setSnapMode(QQuickDragHandler::SnapMode mode)
115{
116 if (mode == m_snapMode)
117 return;
118 m_snapMode = mode;
119 emit snapModeChanged();
120}
121
122void QQuickDragHandler::onActiveChanged()
123{
124 QQuickMultiPointHandler::onActiveChanged();
125 const bool curActive = active();
126 m_xAxis.onActiveChanged(curActive, 0);
127 m_yAxis.onActiveChanged(curActive, 0);
128 if (curActive) {
129 if (auto parent = parentItem()) {
130 if (QQuickDeliveryAgentPrivate::isTouchEvent(currentEvent()))
131 parent->setKeepTouchGrab(true);
132 // tablet and mouse are treated the same by Item's legacy event handling, and
133 // touch becomes synth-mouse for Flickable, so we need to prevent stealing
134 // mouse grab too, whenever dragging occurs in an enabled direction
135 parent->setKeepMouseGrab(true);
136 }
137 } else {
138 m_pressTargetPos = QPointF();
139 m_pressedInsideTarget = false;
140 m_pressedInsideParent = false;
141 if (auto parent = parentItem()) {
142 parent->setKeepTouchGrab(false);
143 parent->setKeepMouseGrab(false);
144 }
145 }
146}
147
148bool QQuickDragHandler::wantsPointerEvent(QPointerEvent *event)
149{
150 if (!QQuickMultiPointHandler::wantsPointerEvent(event))
151 /* Do handle other events than we would normally care about
152 while we are still doing a drag; otherwise we would suddenly
153 become inactive when a wheel event arrives during dragging.
154 This extra condition needs to be kept in sync with
155 handlePointerEventImpl */
156 if (!active())
157 return false;
158
159#if QT_CONFIG(gestures)
160 if (event->type() == QEvent::NativeGesture)
161 return false;
162#endif
163
164 if (event->isBeginEvent()) {
165 // At least one point must be pressed in the parent item
166 if (event->isSinglePointEvent()) {
167 m_pressedInsideParent = parentContains(event->points().first());
168 } else {
169 for (int i = 0; !m_pressedInsideParent && i < event->pointCount(); ++i) {
170 auto &p = event->point(i);
171 if (p.state() == QEventPoint::Pressed && parentContains(p))
172 m_pressedInsideParent = true;
173 }
174 }
175 }
176
177 if (!m_pressedInsideParent)
178 return false;
179
180 return true;
181}
182
183void QQuickDragHandler::handlePointerEventImpl(QPointerEvent *event)
184{
185 if (active() && !QQuickMultiPointHandler::wantsPointerEvent(event))
186 return; // see QQuickDragHandler::wantsPointerEvent; we don't want to handle those events
187
188 QQuickMultiPointHandler::handlePointerEventImpl(event);
189 event->accept(); // just the event, not the points
190
191 if (active()) {
192 // Calculate drag delta, taking into account the axis enabled constraint
193 // i.e. if xAxis is not enabled, then ignore the horizontal component of the actual movement
194 QVector2D accumulatedDragDelta = QVector2D(centroid().scenePosition() - centroid().scenePressPosition());
195 if (!m_xAxis.enabled())
196 accumulatedDragDelta.setX(0);
197 if (!m_yAxis.enabled())
198 accumulatedDragDelta.setY(0);
199 setActiveTranslation(accumulatedDragDelta);
200 } else {
201 // Check that all points have been dragged past the drag threshold,
202 // to the extent that the constraints allow,
203 // and in approximately the same direction
204 qreal minAngle = 361;
205 qreal maxAngle = -361;
206 bool allOverThreshold = QQuickDeliveryAgentPrivate::isTouchEvent(event) ?
207 static_cast<QTouchEvent *>(event)->touchPointStates() != QEventPoint::Released :
208 !event->isEndEvent();
209 QVector<QEventPoint> chosenPoints;
210
211 if (event->isBeginEvent())
212 m_pressedInsideTarget = target() && currentPoints().size() > 0;
213
214 for (const QQuickHandlerPoint &p : std::as_const(currentPoints())) {
215 if (!allOverThreshold)
216 break;
217 auto point = event->pointById(p.id());
218 Q_ASSERT(point);
219 chosenPoints << *point;
220 setPassiveGrab(event, *point);
221 // Calculate drag delta, taking into account the axis enabled constraint
222 // i.e. if xAxis is not enabled, then ignore the horizontal component of the actual movement
223 auto const mapFromScene = [this](auto const &scenePos) {
224 return target() ? target()->mapFromScene(scenePos) : scenePos;
225 };
226 QVector2D accumulatedDragDelta = QVector2D(mapFromScene(point->scenePosition())
227 - mapFromScene(point->scenePressPosition()));
228 if (!m_xAxis.enabled()) {
229 // If horizontal dragging is disallowed, but the user is dragging
230 // mostly horizontally, then don't activate.
231 if (qAbs(accumulatedDragDelta.x()) > qAbs(accumulatedDragDelta.y()))
232 accumulatedDragDelta.setY(0);
233 accumulatedDragDelta.setX(0);
234 }
235 if (!m_yAxis.enabled()) {
236 // If vertical dragging is disallowed, but the user is dragging
237 // mostly vertically, then don't activate.
238 if (qAbs(accumulatedDragDelta.y()) > qAbs(accumulatedDragDelta.x()))
239 accumulatedDragDelta.setX(0);
240 accumulatedDragDelta.setY(0);
241 }
242 qreal angle = std::atan2(accumulatedDragDelta.y(), accumulatedDragDelta.x()) * 180 / M_PI;
243 bool overThreshold = d_func()->dragOverThreshold(accumulatedDragDelta);
244 qCDebug(lcDragHandler) << "movement" << accumulatedDragDelta << "angle" << angle << "of point" << point
245 << "pressed @" << point->scenePressPosition() << "over threshold?" << overThreshold;
246 minAngle = qMin(angle, minAngle);
247 maxAngle = qMax(angle, maxAngle);
248 if (allOverThreshold && !overThreshold)
249 allOverThreshold = false;
250
251 if (event->isBeginEvent()) {
252 // m_pressedInsideTarget should stay true iff ALL points in which DragHandler is interested
253 // have been pressed inside the target() Item. (E.g. in a Slider the parent might be the
254 // whole control while the target is just the knob.)
255 if (target()) {
256 const QPointF localPressPos = target()->mapFromScene(point->scenePressPosition());
257 m_pressedInsideTarget &= target()->contains(localPressPos);
258 m_pressTargetPos = targetCentroidPosition();
259 }
260 // QQuickDeliveryAgentPrivate::deliverToPassiveGrabbers() skips subsequent delivery if the event is filtered.
261 // That affects behavior for mouse but not for touch, because Flickable behaves differently in the mouse case.
262 // So we have to compensate by accepting the event here to avoid any parent Flickable from
263 // getting the event via direct delivery and grabbing too soon.
264 if (QQuickDeliveryAgentPrivate::isMouseEvent(event))
265 point->setAccepted(true); // stop propagation iff it's a mouse event
266 }
267 }
268 if (allOverThreshold) {
269 qreal angleDiff = maxAngle - minAngle;
270 if (angleDiff > 180)
271 angleDiff = 360 - angleDiff;
272 qCDebug(lcDragHandler) << "angle min" << minAngle << "max" << maxAngle << "range" << angleDiff;
273 if (angleDiff < DragAngleToleranceDegrees && grabPoints(event, chosenPoints))
274 setActive(true);
275 }
276 }
277 if (active() && target() && target()->parentItem()) {
278 const QPointF newTargetTopLeft = targetCentroidPosition() - m_pressTargetPos;
279 const QPointF xformOrigin = target()->transformOriginPoint();
280 const QPointF targetXformOrigin = newTargetTopLeft + xformOrigin;
281 QPointF pos = target()->parentItem()->mapFromItem(target(), targetXformOrigin);
282 pos -= xformOrigin;
283 QPointF targetItemPos = target()->position();
284 if (!m_xAxis.enabled())
285 pos.setX(targetItemPos.x());
286 if (!m_yAxis.enabled())
287 pos.setY(targetItemPos.y());
288 enforceAxisConstraints(&pos);
289 moveTarget(pos);
290 }
291}
292
293void QQuickDragHandler::enforceAxisConstraints(QPointF *localPos)
294{
295 if (m_xAxis.enabled())
296 localPos->setX(qBound(m_xAxis.minimum(), localPos->x(), m_xAxis.maximum()));
297 if (m_yAxis.enabled())
298 localPos->setY(qBound(m_yAxis.minimum(), localPos->y(), m_yAxis.maximum()));
299}
300
301void QQuickDragHandler::setPersistentTranslation(const QVector2D &trans)
302{
303 if (trans == persistentTranslation())
304 return;
305
306 m_xAxis.updateValue(m_xAxis.activeValue(), trans.x());
307 m_yAxis.updateValue(m_yAxis.activeValue(), trans.y());
308 emit translationChanged({});
309}
310
311void QQuickDragHandler::setActiveTranslation(const QVector2D &trans)
312{
313 if (trans == activeTranslation())
314 return;
315
316 const QVector2D delta = trans - activeTranslation();
317 m_xAxis.updateValue(trans.x(), m_xAxis.persistentValue() + delta.x(), delta.x());
318 m_yAxis.updateValue(trans.y(), m_yAxis.persistentValue() + delta.y(), delta.y());
319
320 qCDebug(lcDragHandler) << "translation: delta" << delta
321 << "active" << trans << "accumulated" << persistentTranslation();
322 emit translationChanged(delta);
323}
324
325/*!
326 \qmlpropertygroup QtQuick::DragHandler::xAxis
327 \qmlproperty real QtQuick::DragHandler::xAxis.minimum
328 \qmlproperty real QtQuick::DragHandler::xAxis.maximum
329 \qmlproperty bool QtQuick::DragHandler::xAxis.enabled
330 \qmlproperty real QtQuick::DragHandler::xAxis.activeValue
331
332 \c xAxis controls the constraints for horizontal dragging.
333
334 \c minimum is the minimum acceptable value of \l {Item::x}{x} to be
335 applied to the \l {PointerHandler::target} {target}.
336 \c maximum is the maximum acceptable value of \l {Item::x}{x} to be
337 applied to the \l {PointerHandler::target} {target}.
338 If \c enabled is true, horizontal dragging is allowed.
339 \c activeValue is the same as \l {QtQuick::DragHandler::activeTranslation}{activeTranslation.x}.
340
341 The \c activeValueChanged signal is emitted when \c activeValue changes, to
342 provide the increment by which it changed.
343 This is intended for incrementally adjusting one property via multiple handlers.
344*/
345
346/*!
347 \qmlpropertygroup QtQuick::DragHandler::yAxis
348 \qmlproperty real QtQuick::DragHandler::yAxis.minimum
349 \qmlproperty real QtQuick::DragHandler::yAxis.maximum
350 \qmlproperty bool QtQuick::DragHandler::yAxis.enabled
351 \qmlproperty real QtQuick::DragHandler::yAxis.activeValue
352
353 \c yAxis controls the constraints for vertical dragging.
354
355 \c minimum is the minimum acceptable value of \l {Item::y}{y} to be
356 applied to the \l {PointerHandler::target} {target}.
357 \c maximum is the maximum acceptable value of \l {Item::y}{y} to be
358 applied to the \l {PointerHandler::target} {target}.
359 If \c enabled is true, vertical dragging is allowed.
360 \c activeValue is the same as \l {QtQuick::DragHandler::activeTranslation}{activeTranslation.y}.
361
362 The \c activeValueChanged signal is emitted when \c activeValue changes, to
363 provide the increment by which it changed.
364 This is intended for incrementally adjusting one property via multiple handlers:
365
366 \snippet pointerHandlers/rotateViaWheelOrDrag.qml 0
367*/
368
369/*!
370 \readonly
371 \qmlproperty vector2d QtQuick::DragHandler::translation
372 \deprecated [6.2] Use activeTranslation
373*/
374
375/*!
376 \qmlproperty vector2d QtQuick::DragHandler::persistentTranslation
377
378 The translation to be applied to the \l target if it is not \c null.
379 Otherwise, bindings can be used to do arbitrary things with this value.
380 While the drag gesture is being performed, \l activeTranslation is
381 continuously added to it; after the gesture ends, it stays the same.
382*/
383
384/*!
385 \readonly
386 \qmlproperty vector2d QtQuick::DragHandler::activeTranslation
387
388 The translation while the drag gesture is being performed.
389 It is \c {0, 0} when the gesture begins, and increases as the event
390 point(s) are dragged downward and to the right. After the gesture ends, it
391 stays the same; and when the next drag gesture begins, it is reset to
392 \c {0, 0} again.
393*/
394
395/*!
396 \qmlproperty flags QtQuick::DragHandler::acceptedButtons
397
398 The mouse buttons that can activate this DragHandler.
399
400 By default, this property is set to
401 \l {QtQuick::MouseEvent::button} {Qt.LeftButton}.
402 It can be set to an OR combination of mouse buttons, and will ignore events
403 from other buttons.
404
405 For example, if a component (such as TextEdit) already handles
406 left-button drags in its own way, it can be augmented with a
407 DragHandler that does something different when dragged via the
408 right button:
409
410 \snippet pointerHandlers/dragHandlerAcceptedButtons.qml 0
411*/
412
413/*!
414 \qmlproperty flags DragHandler::acceptedDevices
415
416 The types of pointing devices that can activate this DragHandler.
417
418 By default, this property is set to
419 \l{QInputDevice::DeviceType}{PointerDevice.AllDevices}.
420 If you set it to an OR combination of device types, it will ignore events
421 from non-matching devices.
422
423 \note Not all platforms are yet able to distinguish mouse and touchpad; and
424 on those that do, you often want to make mouse and touchpad behavior the same.
425*/
426
427/*!
428 \qmlproperty flags DragHandler::acceptedModifiers
429
430 If this property is set, it will require the given keyboard modifiers to
431 be pressed in order to react to pointer events, and otherwise ignore them.
432
433 For example, two DragHandlers can perform two different drag-and-drop
434 operations, depending on whether the \c Control modifier is pressed:
435
436 \snippet pointerHandlers/draggableGridView.qml entire
437
438 If this property is set to \c Qt.KeyboardModifierMask (the default value),
439 then the DragHandler ignores the modifier keys.
440
441 If you set \c acceptedModifiers to an OR combination of modifier keys,
442 it means \e all of those modifiers must be pressed to activate the handler.
443
444 The available modifiers are as follows:
445
446 \value NoModifier No modifier key is allowed.
447 \value ShiftModifier A Shift key on the keyboard must be pressed.
448 \value ControlModifier A Ctrl key on the keyboard must be pressed.
449 \value AltModifier An Alt key on the keyboard must be pressed.
450 \value MetaModifier A Meta key on the keyboard must be pressed.
451 \value KeypadModifier A keypad button must be pressed.
452 \value GroupSwitchModifier X11 only (unless activated on Windows by a command line argument).
453 A Mode_switch key on the keyboard must be pressed.
454 \value KeyboardModifierMask The handler does not care which modifiers are pressed.
455
456 \sa Qt::KeyboardModifier
457*/
458
459/*!
460 \qmlproperty flags DragHandler::acceptedPointerTypes
461
462 The types of pointing instruments (finger, stylus, eraser, etc.)
463 that can activate this DragHandler.
464
465 By default, this property is set to
466 \l {QPointingDevice::PointerType} {PointerDevice.AllPointerTypes}.
467 If you set it to an OR combination of device types, it will ignore events
468 from non-matching \l {PointerDevice}{devices}.
469*/
470
471/*!
472 \qmlproperty real DragHandler::margin
473
474 The margin beyond the bounds of the \l {PointerHandler::parent}{parent}
475 item within which an \l eventPoint can activate this handler. For example,
476 you can make it easier to drag small items by allowing the user to drag
477 from a position nearby:
478
479 \snippet pointerHandlers/dragHandlerMargin.qml draggable
480*/
481
482QT_END_NAMESPACE
483
484#include "moc_qquickdraghandler_p.cpp"
Q_STATIC_LOGGING_CATEGORY(lcAccessibilityCore, "qt.accessibility.core")
static QT_BEGIN_NAMESPACE const qreal DragAngleToleranceDegrees