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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of qtdeclarative/src/quick/handlers/qquickdraghandler.cpp