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 const auto mapFromScene = [this](const auto &scenePos) {
192 return target() ? target()->mapFromScene(scenePos) : scenePos;
193 };
194
195 if (active()) {
196 // Calculate drag delta, taking into account the axis enabled constraint
197 // i.e. if xAxis is not enabled, then ignore the horizontal component of the actual movement
198 QVector2D accumulatedDragDelta(mapFromScene(centroid().scenePosition())
199 - mapFromScene(centroid().scenePressPosition()));
200 if (!m_xAxis.enabled())
201 accumulatedDragDelta.setX(0);
202 if (!m_yAxis.enabled())
203 accumulatedDragDelta.setY(0);
204 setActiveTranslation(accumulatedDragDelta);
205 } else {
206 // Check that all points have been dragged past the drag threshold,
207 // to the extent that the constraints allow,
208 // and in approximately the same direction
209 qreal minAngle = 361;
210 qreal maxAngle = -361;
211 bool allOverThreshold = QQuickDeliveryAgentPrivate::isTouchEvent(event) ?
212 static_cast<QTouchEvent *>(event)->touchPointStates() != QEventPoint::Released :
213 !event->isEndEvent();
214 QList<QEventPoint> chosenPoints;
215
216 if (event->isBeginEvent())
217 m_pressedInsideTarget = target() && currentPoints().size() > 0;
218
219 for (const QQuickHandlerPoint &p : std::as_const(currentPoints())) {
220 if (!allOverThreshold)
221 break;
222 auto point = event->pointById(p.id());
223 Q_ASSERT(point);
224 chosenPoints << *point;
225 setPassiveGrab(event, *point);
226 // Calculate drag delta, taking into account the axis enabled constraint
227 // i.e. if xAxis is not enabled, then ignore the horizontal component of the actual movement
228 QVector2D accumulatedDragDelta = QVector2D(mapFromScene(point->scenePosition())
229 - mapFromScene(point->scenePressPosition()));
230 if (!m_xAxis.enabled()) {
231 // If horizontal dragging is disallowed, but the user is dragging
232 // mostly horizontally, then don't activate.
233 if (qAbs(accumulatedDragDelta.x()) > qAbs(accumulatedDragDelta.y()))
234 accumulatedDragDelta.setY(0);
235 accumulatedDragDelta.setX(0);
236 }
237 if (!m_yAxis.enabled()) {
238 // If vertical dragging is disallowed, but the user is dragging
239 // mostly vertically, then don't activate.
240 if (qAbs(accumulatedDragDelta.y()) > qAbs(accumulatedDragDelta.x()))
241 accumulatedDragDelta.setX(0);
242 accumulatedDragDelta.setY(0);
243 }
244 qreal angle = std::atan2(accumulatedDragDelta.y(), accumulatedDragDelta.x()) * 180 / M_PI;
245 bool overThreshold = d_func()->dragOverThreshold(accumulatedDragDelta);
246 qCDebug(lcDragHandler) << "movement" << accumulatedDragDelta << "angle" << angle << "of point" << point
247 << "pressed @" << point->scenePressPosition() << "over threshold?" << overThreshold;
248 minAngle = qMin(angle, minAngle);
249 maxAngle = qMax(angle, maxAngle);
250 if (allOverThreshold && !overThreshold)
251 allOverThreshold = false;
252
253 if (event->isBeginEvent()) {
254 // m_pressedInsideTarget should stay true iff ALL points in which DragHandler is interested
255 // have been pressed inside the target() Item. (E.g. in a Slider the parent might be the
256 // whole control while the target is just the knob.)
257 if (target()) {
258 const QPointF localPressPos = target()->mapFromScene(point->scenePressPosition());
259 m_pressedInsideTarget &= target()->contains(localPressPos);
260 m_pressTargetPos = targetCentroidPosition();
261 }
262 // QQuickDeliveryAgentPrivate::deliverToPassiveGrabbers() skips subsequent delivery if the event is filtered.
263 // That affects behavior for mouse but not for touch, because Flickable behaves differently in the mouse case.
264 // So we have to compensate by accepting the event here to avoid any parent Flickable from
265 // getting the event via direct delivery and grabbing too soon.
266 if (QQuickDeliveryAgentPrivate::isMouseEvent(event))
267 point->setAccepted(true); // stop propagation iff it's a mouse event
268 }
269 }
270 if (allOverThreshold) {
271 qreal angleDiff = maxAngle - minAngle;
272 if (angleDiff > 180)
273 angleDiff = 360 - angleDiff;
274 qCDebug(lcDragHandler) << "angle min" << minAngle << "max" << maxAngle << "range" << angleDiff;
275 if (angleDiff < DragAngleToleranceDegrees && grabPoints(event, chosenPoints))
276 setActive(true);
277 }
278 }
279 if (active() && target() && target()->parentItem()) {
280 const QPointF newTargetTopLeft = targetCentroidPosition() - m_pressTargetPos;
281 const QPointF xformOrigin = target()->transformOriginPoint();
282 const QPointF targetXformOrigin = newTargetTopLeft + xformOrigin;
283 QPointF pos = target()->parentItem()->mapFromItem(target(), targetXformOrigin);
284 pos -= xformOrigin;
285 QPointF targetItemPos = target()->position();
286 if (!m_xAxis.enabled())
287 pos.setX(targetItemPos.x());
288 if (!m_yAxis.enabled())
289 pos.setY(targetItemPos.y());
290 enforceAxisConstraints(&pos);
291 moveTarget(pos);
292 }
293}
294
295void QQuickDragHandler::enforceAxisConstraints(QPointF *localPos)
296{
297 if (m_xAxis.enabled())
298 localPos->setX(qBound(m_xAxis.minimum(), localPos->x(), m_xAxis.maximum()));
299 if (m_yAxis.enabled())
300 localPos->setY(qBound(m_yAxis.minimum(), localPos->y(), m_yAxis.maximum()));
301}
302
303void QQuickDragHandler::setPersistentTranslation(const QVector2D &trans)
304{
305 if (trans == persistentTranslation())
306 return;
307
308 m_xAxis.updateValue(m_xAxis.activeValue(), trans.x());
309 m_yAxis.updateValue(m_yAxis.activeValue(), trans.y());
310 emit translationChanged({});
311}
312
313void QQuickDragHandler::setActiveTranslation(const QVector2D &trans)
314{
315 if (trans == activeTranslation())
316 return;
317
318 const QVector2D delta = trans - activeTranslation();
319 m_xAxis.updateValue(trans.x(), m_xAxis.persistentValue() + delta.x(), delta.x());
320 m_yAxis.updateValue(trans.y(), m_yAxis.persistentValue() + delta.y(), delta.y());
321
322 qCDebug(lcDragHandler) << "translation: delta" << delta
323 << "active" << trans << "accumulated" << persistentTranslation();
324 emit translationChanged(delta);
325}
326
327/*!
328 \qmlpropertygroup QtQuick::DragHandler::xAxis
329 \qmlproperty real QtQuick::DragHandler::xAxis.minimum
330 \qmlproperty real QtQuick::DragHandler::xAxis.maximum
331 \qmlproperty bool QtQuick::DragHandler::xAxis.enabled
332 \qmlproperty real QtQuick::DragHandler::xAxis.activeValue
333
334 \c xAxis controls the constraints for horizontal dragging.
335
336 \c minimum is the minimum acceptable value of \l {Item::x}{x} to be
337 applied to the \l {PointerHandler::target} {target}.
338 \c maximum is the maximum acceptable value of \l {Item::x}{x} to be
339 applied to the \l {PointerHandler::target} {target}.
340 If \c enabled is true, horizontal dragging is allowed.
341 \c activeValue is the same as \l {QtQuick::DragHandler::activeTranslation}{activeTranslation.x}.
342
343 The \c activeValueChanged signal is emitted when \c activeValue changes, to
344 provide the increment by which it changed.
345 This is intended for incrementally adjusting one property via multiple handlers.
346*/
347
348/*!
349 \qmlpropertygroup QtQuick::DragHandler::yAxis
350 \qmlproperty real QtQuick::DragHandler::yAxis.minimum
351 \qmlproperty real QtQuick::DragHandler::yAxis.maximum
352 \qmlproperty bool QtQuick::DragHandler::yAxis.enabled
353 \qmlproperty real QtQuick::DragHandler::yAxis.activeValue
354
355 \c yAxis controls the constraints for vertical dragging.
356
357 \c minimum is the minimum acceptable value of \l {Item::y}{y} to be
358 applied to the \l {PointerHandler::target} {target}.
359 \c maximum is the maximum acceptable value of \l {Item::y}{y} to be
360 applied to the \l {PointerHandler::target} {target}.
361 If \c enabled is true, vertical dragging is allowed.
362 \c activeValue is the same as \l {QtQuick::DragHandler::activeTranslation}{activeTranslation.y}.
363
364 The \c activeValueChanged signal is emitted when \c activeValue changes, to
365 provide the increment by which it changed.
366 This is intended for incrementally adjusting one property via multiple handlers:
367
368 \snippet pointerHandlers/rotateViaWheelOrDrag.qml 0
369*/
370
371/*!
372 \readonly
373 \qmlproperty vector2d QtQuick::DragHandler::translation
374 \deprecated [6.2] Use activeTranslation
375*/
376
377/*!
378 \qmlproperty vector2d QtQuick::DragHandler::persistentTranslation
379
380 The translation to be applied to the \l target if it is not \c null.
381 Otherwise, bindings can be used to do arbitrary things with this value.
382 While the drag gesture is being performed, \l activeTranslation is
383 continuously added to it; after the gesture ends, it stays the same.
384*/
385
386/*!
387 \readonly
388 \qmlproperty vector2d QtQuick::DragHandler::activeTranslation
389
390 The translation while the drag gesture is being performed.
391 It is \c {0, 0} when the gesture begins, and increases as the event
392 point(s) are dragged downward and to the right. After the gesture ends, it
393 stays the same; and when the next drag gesture begins, it is reset to
394 \c {0, 0} again.
395*/
396
397/*!
398 \qmlproperty flags QtQuick::DragHandler::acceptedButtons
399
400 The mouse buttons that can activate this DragHandler.
401
402 By default, this property is set to
403 \l {QtQuick::MouseEvent::button} {Qt.LeftButton}.
404 It can be set to an OR combination of mouse buttons, and will ignore events
405 from other buttons.
406
407 For example, if a component (such as TextEdit) already handles
408 left-button drags in its own way, it can be augmented with a
409 DragHandler that does something different when dragged via the
410 right button:
411
412 \snippet pointerHandlers/dragHandlerAcceptedButtons.qml 0
413*/
414
415/*!
416 \qmlproperty flags DragHandler::acceptedDevices
417
418 The types of pointing devices that can activate this DragHandler.
419
420 By default, this property is set to
421 \l{QInputDevice::DeviceType}{PointerDevice.AllDevices}.
422 If you set it to an OR combination of device types, it will ignore events
423 from non-matching devices.
424
425 \note Not all platforms are yet able to distinguish mouse and touchpad; and
426 on those that do, you often want to make mouse and touchpad behavior the same.
427*/
428
429/*!
430 \qmlproperty flags DragHandler::acceptedModifiers
431
432 If this property is set, it will require the given keyboard modifiers to
433 be pressed in order to react to pointer events, and otherwise ignore them.
434
435 For example, two DragHandlers can perform two different drag-and-drop
436 operations, depending on whether the \c Control modifier is pressed:
437
438 \snippet pointerHandlers/draggableGridView.qml entire
439
440 If this property is set to \c Qt.KeyboardModifierMask (the default value),
441 then the DragHandler ignores the modifier keys.
442
443 If you set \c acceptedModifiers to an OR combination of modifier keys,
444 it means \e all of those modifiers must be pressed to activate the handler.
445
446 The available modifiers are as follows:
447
448 \value NoModifier No modifier key is allowed.
449 \value ShiftModifier A Shift key on the keyboard must be pressed.
450 \value ControlModifier A Ctrl key on the keyboard must be pressed.
451 \value AltModifier An Alt key on the keyboard must be pressed.
452 \value MetaModifier A Meta key on the keyboard must be pressed.
453 \value KeypadModifier A keypad button must be pressed.
454 \value GroupSwitchModifier X11 only (unless activated on Windows by a command line argument).
455 A Mode_switch key on the keyboard must be pressed.
456 \value KeyboardModifierMask The handler does not care which modifiers are pressed.
457
458 \sa Qt::KeyboardModifier
459*/
460
461/*!
462 \qmlproperty flags DragHandler::acceptedPointerTypes
463
464 The types of pointing instruments (finger, stylus, eraser, etc.)
465 that can activate this DragHandler.
466
467 By default, this property is set to
468 \l {QPointingDevice::PointerType} {PointerDevice.AllPointerTypes}.
469 If you set it to an OR combination of device types, it will ignore events
470 from non-matching \l {PointerDevice}{devices}.
471*/
472
473/*!
474 \qmlproperty real DragHandler::margin
475
476 The margin beyond the bounds of the \l {PointerHandler::parent}{parent}
477 item within which an \l eventPoint can activate this handler. For example,
478 you can make it easier to drag small items by allowing the user to drag
479 from a position nearby:
480
481 \snippet pointerHandlers/dragHandlerMargin.qml draggable
482*/
483
484QT_END_NAMESPACE
485
486#include "moc_qquickdraghandler_p.cpp"
Combined button and popup list for selecting options.
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)
static QT_BEGIN_NAMESPACE const qreal DragAngleToleranceDegrees