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
qquickdrag.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:critical reason:interprocess-communication
4
5#include "qquickdrag_p.h"
7
8#include <private/qguiapplication_p.h>
9#include <qpa/qplatformintegration.h>
10#include <private/qquickitem_p.h>
11#include <QtQuick/private/qquickevents_p_p.h>
12#include <private/qquickitemchangelistener_p.h>
13#include <private/qquickpixmap_p.h>
14#include <private/qv4scopedvalue_p.h>
15#include <QtCore/qbuffer.h>
16#include <QtCore/qmimedata.h>
17#include <QtCore/qstringconverter.h>
18#include <QtQml/qqmlinfo.h>
19#include <QtGui/qevent.h>
20#include <QtGui/qstylehints.h>
21#include <QtGui/qguiapplication.h>
22#include <QtGui/qimagewriter.h>
23
24#ifdef Q_OS_ANDROID
25#include <QtQuick/QQuickItemGrabResult>
26#endif
27
28#include <qpa/qplatformdrag.h>
29#include <QtGui/qdrag.h>
30
32
33using namespace Qt::StringLiterals;
34
35
36/*!
37 \qmltype Drag
38 \nativetype QQuickDrag
39 \inqmlmodule QtQuick
40 \ingroup qtquick-input
41 \brief For specifying drag and drop events for moved Items.
42
43 Using the Drag attached property, any Item can be made a source of drag and drop
44 events within a scene.
45
46 When a drag is \l active on an item, any change in that item's position will
47 generate a drag event that will be sent to any DropArea that intersects
48 with the new position of the item. Other items which implement drag and
49 drop event handlers can also receive these events.
50
51 The following snippet shows how an item can be dragged with a MouseArea.
52 However, dragging is not limited to mouse drags; anything that can move an item
53 can generate drag events, including touch events, animations and bindings.
54
55 \snippet qml/drag.qml 0
56
57 A drag can be terminated either by canceling it with Drag.cancel() or setting
58 Drag.active to false, or it can be terminated with a drop event by calling
59 Drag.drop(). If the drop event is accepted, Drag.drop() will return the
60 \l {supportedActions}{drop action} chosen by the recipient of the event,
61 otherwise it will return Qt.IgnoreAction.
62
63 \sa {Qt Quick Examples - Drag and Drop}
64*/
65
66void QQuickDragAttachedPrivate::itemGeometryChanged(QQuickItem *, QQuickGeometryChange change,
67 const QRectF &)
68{
69 if (!change.positionChange() || !active || itemMoved)
70 return;
71 updatePosition();
72}
73
74void QQuickDragAttachedPrivate::itemParentChanged(QQuickItem *, QQuickItem *)
75{
76 if (!active || dragRestarted)
77 return;
78
79 QQuickWindow *newWindow = attachedItem->window();
80
81 if (window != newWindow)
82 restartDrag();
83 else if (window)
84 updatePosition();
85}
86
87/*!
88 \internal
89 Called while the item this is attached to is being destroyed. If that happens during
90 a native drag, everything after QDrag::exec() in startDrag() is unreachable: we are a
91 QObject child of that item, so ~QObject is about to delete us. ~QQuickItem notifies
92 its change listeners before deleting its children, though, so this is the last point
93 at which the drag can still be wound down properly.
94*/
95void QQuickDragAttachedPrivate::itemDestroyed(QQuickItem *item)
96{
97 Q_Q(QQuickDragAttached);
98
99 listenForAttachedItemDestruction(false);
100 if (!executingNativeDrag)
101 return;
102
103 Q_ASSERT(item == attachedItem);
104 Q_UNUSED(item);
105
106 // Stop the platform delivering drag events to a scene that no longer has anything to
107 // drag, and unwind exec()'s nested event loop. This is also what delivers the drag
108 // leave that the drop target is waiting for. ~QDrag would end up cancelling too,
109 // since the QDrag is a child of the same item, but the order in which the item
110 // deletes its children is not ours to rely on. executingNativeDrag stays set for the
111 // duration, so that a handler reacting to the leave cannot re-enter setActive().
112 if (QPlatformDrag *platformDrag = QGuiApplicationPrivate::platformIntegration()->drag())
113 platformDrag->cancelDrag();
114
115 executingNativeDrag = false;
116 deliverLeaveEvent();
117 if (target) {
118 target = nullptr;
119 emit q->targetChanged();
120 }
121 emit q->dragFinished(Qt::IgnoreAction);
122 active = false;
123 emit q->activeChanged();
124}
125
126void QQuickDragAttachedPrivate::listenForAttachedItemDestruction(bool listen)
127{
128 if (listen == listeningForDestruction || !attachedItem)
129 return;
130
131 auto *itemPrivate = QQuickItemPrivate::get(attachedItem);
132 if (listen)
133 itemPrivate->addItemChangeListener(this, QQuickItemPrivate::Destroyed);
134 else
135 itemPrivate->removeItemChangeListener(this, QQuickItemPrivate::Destroyed);
136 listeningForDestruction = listen;
137}
138
139void QQuickDragAttachedPrivate::updatePosition()
140{
141 Q_Q(QQuickDragAttached);
142 itemMoved = true;
143 if (!eventQueued) {
144 eventQueued = true;
145 QCoreApplication::postEvent(q, new QEvent(QEvent::User));
146 }
147}
148
149void QQuickDragAttachedPrivate::restartDrag()
150{
151 Q_Q(QQuickDragAttached);
152 dragRestarted = true;
153 if (!eventQueued) {
154 eventQueued = true;
155 QCoreApplication::postEvent(q, new QEvent(QEvent::User));
156 }
157}
158
159void QQuickDragAttachedPrivate::deliverEnterEvent()
160{
161 dragRestarted = false;
162 itemMoved = false;
163
164 window = attachedItem->window();
165
166 mimeData->m_source = source;
167 if (!overrideActions)
168 mimeData->m_supportedActions = supportedActions;
169 mimeData->m_keys = keys;
170
171 if (window) {
172 QDragEnterEvent event(attachedItem->mapToScene(hotSpot), mimeData->m_supportedActions,
173 mimeData, Qt::NoButton, Qt::NoModifier);
174 QQuickDropEventEx::setProposedAction(&event, proposedAction);
175 deliverEvent(window, &event);
176 }
177}
178
179void QQuickDragAttachedPrivate::deliverMoveEvent()
180{
181 Q_Q(QQuickDragAttached);
182
183 itemMoved = false;
184 if (window) {
185 QDragMoveEvent event(attachedItem->mapToScene(hotSpot), mimeData->m_supportedActions,
186 mimeData, Qt::NoButton, Qt::NoModifier);
187 QQuickDropEventEx::setProposedAction(&event, proposedAction);
188 deliverEvent(window, &event);
189 if (target != dragGrabber.target()) {
190 target = dragGrabber.target();
191 emit q->targetChanged();
192 }
193 }
194}
195
196void QQuickDragAttachedPrivate::deliverLeaveEvent()
197{
198 if (window) {
199 QDragLeaveEvent event;
200 deliverEvent(window, &event);
201 window = nullptr;
202 }
203}
204
205void QQuickDragAttachedPrivate::deliverEvent(QQuickWindow *window, QEvent *event)
206{
207 Q_ASSERT(!inEvent);
208 inEvent = true;
209 QQuickWindowPrivate::get(window)->deliveryAgentPrivate()->deliverDragEvent(&dragGrabber, event);
210 inEvent = false;
211}
212
213bool QQuickDragAttached::event(QEvent *event)
214{
215 Q_D(QQuickDragAttached);
216
217 if (event->type() == QEvent::User) {
218 d->eventQueued = false;
219 if (d->dragRestarted) {
220 d->deliverLeaveEvent();
221 if (!d->mimeData)
222 d->mimeData = new QQuickDragMimeData;
223 d->deliverEnterEvent();
224
225 if (d->target != d->dragGrabber.target()) {
226 d->target = d->dragGrabber.target();
227 emit targetChanged();
228 }
229 } else if (d->itemMoved) {
230 d->deliverMoveEvent();
231 }
232 return true;
233 } else {
234 return QObject::event(event);
235 }
236}
237
238QQuickDragAttached::QQuickDragAttached(QObject *parent)
239 : QObject(*new QQuickDragAttachedPrivate, parent)
240{
241 Q_D(QQuickDragAttached);
242 d->attachedItem = qobject_cast<QQuickItem *>(parent);
243 d->source = d->attachedItem;
244}
245
246QQuickDragAttached::~QQuickDragAttached()
247{
248 Q_D(QQuickDragAttached);
249 delete d->mimeData;
250}
251
252/*!
253 \qmlattachedproperty bool QtQuick::Drag::active
254
255 This property holds whether a drag event sequence is currently active.
256
257 Binding this property to the active property of \l MouseArea::drag will
258 cause \l startDrag to be called when the user starts dragging.
259
260 Setting this property to true will also send a QDragEnter event to the scene
261 with the item's current position. Setting it to false will send a
262 QDragLeave event.
263
264 While a drag is active any change in an item's position will send a QDragMove
265 event with item's new position to the scene.
266*/
267
268bool QQuickDragAttached::isActive() const
269{
270 Q_D(const QQuickDragAttached);
271 return d->active;
272}
273
274void QQuickDragAttached::setActive(bool active)
275{
276 Q_D(QQuickDragAttached);
277 if (d->active != active) {
278 if (d->inEvent)
279 qmlWarning(this) << "active cannot be changed from within a drag event handler";
280 else if (d->executingNativeDrag) {
281 // QDrag::exec() is blocking in a nested event loop. Pointer release events
282 // processed there may deactivate the DragHandler and re-trigger this setter.
283 // Suppress: startDrag() already handles cleanup when exec() returns.
284 } else if (active) {
285 if (d->dragType == QQuickDrag::Internal) {
286 d->start(d->supportedActions);
287 } else {
288 d->active = true;
289 emit activeChanged();
290 if (d->dragType == QQuickDrag::Automatic) {
291 bool grabbingItemPixmap = false;
292#ifdef Q_OS_ANDROID
293 // Android can't capture a SurfaceView for the drag shadow, so grab
294 // the item into a pixmap and start once the async grab is ready.
295 if (auto grab = d->attachedItem->grabToImage()) {
296 grabbingItemPixmap = true;
297 QObject::connect(grab.data(), &QQuickItemGrabResult::ready, this,
298 [d, grab] {
299 if (d->active) {
300 const QPixmap pixmap = QPixmap::fromImage(grab->image());
301 d->startDrag(d->supportedActions, pixmap);
302 }
303 });
304 }
305#endif
306 if (!grabbingItemPixmap) {
307 // QDrag::exec() enters a nested event loop; calling it directly
308 // from a QML binding or JS expression would block the engine mid-
309 // evaluation. Defer to a queued call so the current JS frame
310 // unwinds completely before exec() runs from the event loop.
311 QMetaObject::invokeMethod(this, [this] {
312 Q_D(QQuickDragAttached);
313 if (d->active)
314 d->startDrag(d->supportedActions);
315 }, Qt::QueuedConnection);
316 }
317 }
318 }
319 }
320 else
321 cancel();
322 }
323}
324
325/*!
326 \qmlattachedproperty Object QtQuick::Drag::source
327
328 This property holds an object that is identified to recipients of drag events as
329 the source of the events. By default this is the item that the Drag
330 property is attached to.
331
332 Changing the source while a drag is active will reset the sequence of drag events by
333 sending a drag leave event followed by a drag enter event with the new source.
334*/
335
336QObject *QQuickDragAttached::source() const
337{
338 Q_D(const QQuickDragAttached);
339 return d->source;
340}
341
342void QQuickDragAttached::setSource(QObject *item)
343{
344 Q_D(QQuickDragAttached);
345 if (d->source != item) {
346 d->source = item;
347 if (d->active)
348 d->restartDrag();
349 emit sourceChanged();
350 }
351}
352
353void QQuickDragAttached::resetSource()
354{
355 Q_D(QQuickDragAttached);
356 if (d->source != d->attachedItem) {
357 d->source = d->attachedItem;
358 if (d->active)
359 d->restartDrag();
360 emit sourceChanged();
361 }
362}
363
364/*!
365 \qmlattachedproperty Object QtQuick::Drag::target
366
367 While a drag is active this property holds the last object to accept an
368 enter event from the dragged item, if the current drag position doesn't
369 intersect any accepting targets it is null.
370
371 When a drag is not active this property holds the object that accepted
372 the drop event that ended the drag, if no object accepted the drop or
373 the drag was canceled the target will then be null.
374*/
375
376QObject *QQuickDragAttached::target() const
377{
378 Q_D(const QQuickDragAttached);
379 return d->target;
380}
381
382/*!
383 \qmlattachedproperty point QtQuick::Drag::hotSpot
384
385 This property holds the drag position relative to the top left of the item.
386
387 By default this is (0, 0).
388
389 Changes to hotSpot trigger a new drag move with the updated position.
390*/
391
392QPointF QQuickDragAttached::hotSpot() const
393{
394 Q_D(const QQuickDragAttached);
395 return d->hotSpot;
396}
397
398void QQuickDragAttached::setHotSpot(const QPointF &hotSpot)
399{
400 Q_D(QQuickDragAttached);
401 if (d->hotSpot != hotSpot) {
402 d->hotSpot = hotSpot;
403
404 if (d->active)
405 d->updatePosition();
406
407 emit hotSpotChanged();
408 }
409}
410
411/*!
412 \qmlattachedproperty url QtQuick::Drag::imageSource
413 \since 5.8
414
415 This property holds the URL of the image which will be used to represent
416 the data during the drag and drop operation. Changing this property after
417 the drag operation has started will have no effect.
418
419 The example below uses an item's contents as a drag image:
420
421 \snippet qml/externaldrag.qml 0
422
423 \sa Item::grabToImage()
424*/
425
426QUrl QQuickDragAttached::imageSource() const
427{
428 Q_D(const QQuickDragAttached);
429 return d->imageSource;
430}
431
432void QQuickDragAttached::setImageSource(const QUrl &url)
433{
434 Q_D(QQuickDragAttached);
435 if (d->imageSource != url) {
436 d->imageSource = url;
437
438 if (url.isEmpty()) {
439 d->pixmapLoader.clear();
440 } else {
441 d->loadPixmap();
442 }
443
444 Q_EMIT imageSourceChanged();
445 }
446}
447
448/*!
449 \qmlattachedproperty size QtQuick::Drag::imageSourceSize
450 \since 6.8
451
452 This property holds the size of the image that will be used to represent
453 the data during the drag and drop operation. Changing this property after
454 the drag operation has started will have no effect.
455
456 This property sets the maximum number of pixels stored for the loaded
457 image so that large images do not use more memory than necessary.
458 See \l {QtQuick::Image::sourceSize}{Image.sourceSize} for more details.
459
460 The example below shows an SVG image rendered at one size, and re-renders
461 it at a different size for the drag image:
462
463 \snippet qml/externalDragScaledImage.qml 0
464
465 \sa imageSource, Item::grabToImage()
466*/
467
468QSize QQuickDragAttached::imageSourceSize() const
469{
470 Q_D(const QQuickDragAttached);
471 int width = d->imageSourceSize.width();
472 int height = d->imageSourceSize.height();
473 // If width or height is invalid, check whether the size is valid from the loaded image.
474 // If it ends up 0x0 though, leave it as an invalid QSize instead (-1 x -1).
475 if (width == -1) {
476 width = d->pixmapLoader.width();
477 if (!width)
478 width = -1;
479 }
480 if (height == -1) {
481 height = d->pixmapLoader.height();
482 if (!height)
483 height = -1;
484 }
485 return QSize(width, height);
486}
487
488void QQuickDragAttached::setImageSourceSize(const QSize &size)
489{
490 Q_D(QQuickDragAttached);
491 if (d->imageSourceSize != size) {
492 d->imageSourceSize = size;
493
494 if (!d->imageSource.isEmpty())
495 d->loadPixmap();
496
497 Q_EMIT imageSourceSizeChanged();
498 }
499}
500
501/*!
502 \qmlattachedproperty stringlist QtQuick::Drag::keys
503
504 This property holds a list of keys that can be used by a DropArea to filter drag events.
505
506 Changing the keys while a drag is active will reset the sequence of drag events by
507 sending a drag leave event followed by a drag enter event with the new source.
508*/
509
510QStringList QQuickDragAttached::keys() const
511{
512 Q_D(const QQuickDragAttached);
513 return d->keys;
514}
515
516void QQuickDragAttached::setKeys(const QStringList &keys)
517{
518 Q_D(QQuickDragAttached);
519 if (d->keys != keys) {
520 d->keys = keys;
521 if (d->active)
522 d->restartDrag();
523 emit keysChanged();
524 }
525}
526
527/*!
528 \qmlattachedproperty var QtQuick::Drag::mimeData
529 \since 5.2
530
531 This property holds a map from mime type to data that is used during startDrag.
532 The mime data needs to be of a type that matches the mime type (e.g. a string if
533 the mime type is "text/plain", or an image if the mime type is "image/png"), or
534 an \c ArrayBuffer with the data encoded according to the mime type.
535*/
536
537QVariantMap QQuickDragAttached::mimeData() const
538{
539 Q_D(const QQuickDragAttached);
540 return d->externalMimeData;
541}
542
543void QQuickDragAttached::setMimeData(const QVariantMap &mimeData)
544{
545 Q_D(QQuickDragAttached);
546 if (d->externalMimeData != mimeData) {
547 d->externalMimeData = mimeData;
548 emit mimeDataChanged();
549 }
550}
551
552/*!
553 \qmlattachedproperty flags QtQuick::Drag::supportedActions
554
555 This property holds return values of Drag.drop() supported by the drag source.
556
557 Changing the supportedActions while a drag is active will reset the sequence of drag
558 events by sending a drag leave event followed by a drag enter event with the new source.
559*/
560
561Qt::DropActions QQuickDragAttached::supportedActions() const
562{
563 Q_D(const QQuickDragAttached);
564 return d->supportedActions;
565}
566
567void QQuickDragAttached::setSupportedActions(Qt::DropActions actions)
568{
569 Q_D(QQuickDragAttached);
570 if (d->supportedActions != actions) {
571 d->supportedActions = actions;
572 if (d->active)
573 d->restartDrag();
574 emit supportedActionsChanged();
575 }
576}
577
578/*!
579 \qmlattachedproperty enumeration QtQuick::Drag::proposedAction
580
581 This property holds an action that is recommended by the drag source as a
582 return value from Drag.drop().
583
584 Changes to proposedAction will trigger a move event with the updated proposal.
585*/
586
587Qt::DropAction QQuickDragAttached::proposedAction() const
588{
589 Q_D(const QQuickDragAttached);
590 return d->proposedAction;
591}
592
593void QQuickDragAttached::setProposedAction(Qt::DropAction action)
594{
595 Q_D(QQuickDragAttached);
596 if (d->proposedAction != action) {
597 d->proposedAction = action;
598 // The proposed action shouldn't affect whether a drag is accepted
599 // so leave/enter events are excessive, but the target should still
600 // updated.
601 if (d->active)
602 d->updatePosition();
603 emit proposedActionChanged();
604 }
605}
606
607/*!
608 \qmlattachedproperty enumeration QtQuick::Drag::dragType
609 \since 5.2
610
611 This property indicates whether to automatically start drags, do nothing, or
612 to use backwards compatible internal drags. The default is to use backwards
613 compatible internal drags.
614
615 A drag can also be started manually using \l startDrag.
616
617 \value Drag.None do not start drags automatically
618 \value Drag.Automatic start drags automatically
619 \value Drag.Internal (default) start backwards compatible drags automatically
620
621 When using \c Drag.Automatic you should also define \l mimeData and bind the
622 \l active property to the active property of MouseArea : \l {MouseArea::drag.active}
623*/
624
625QQuickDrag::DragType QQuickDragAttached::dragType() const
626{
627 Q_D(const QQuickDragAttached);
628 return d->dragType;
629}
630
631void QQuickDragAttached::setDragType(QQuickDrag::DragType dragType)
632{
633 Q_D(QQuickDragAttached);
634 if (d->dragType != dragType) {
635 d->dragType = dragType;
636 emit dragTypeChanged();
637 }
638}
639
640void QQuickDragAttachedPrivate::start(Qt::DropActions supportedActions)
641{
642 Q_Q(QQuickDragAttached);
643 Q_ASSERT(!active);
644
645 if (!mimeData)
646 mimeData = new QQuickDragMimeData;
647 if (!listening) {
648 QQuickItemPrivate::get(attachedItem)->addItemChangeListener(
649 this, QQuickItemPrivate::Geometry | QQuickItemPrivate::Parent);
650 listening = true;
651 }
652
653 mimeData->m_supportedActions = supportedActions;
654 active = true;
655 itemMoved = false;
656 dragRestarted = false;
657
658 deliverEnterEvent();
659
660 if (target != dragGrabber.target()) {
661 target = dragGrabber.target();
662 emit q->targetChanged();
663 }
664
665 emit q->activeChanged();
666}
667
668/*!
669 \qmlattachedmethod void QtQuick::Drag::start(flags supportedActions)
670
671 Starts sending drag events. Used for starting old-style internal drags. \l startDrag is the
672 new-style, preferred method of starting drags.
673
674 The optional \a supportedActions argument can be used to override the \l supportedActions
675 property for the started sequence.
676*/
677
678void QQuickDragAttached::start(QQmlV4FunctionPtr args)
679{
680 Q_D(QQuickDragAttached);
681 if (d->inEvent) {
682 qmlWarning(this) << "start() cannot be called from within a drag event handler";
683 return;
684 }
685
686 if (d->active)
687 cancel();
688
689 d->overrideActions = false;
690 Qt::DropActions supportedActions = d->supportedActions;
691 // check arguments for supportedActions, maybe data?
692 if (args->length() >= 1) {
693 QV4::Scope scope(args->v4engine());
694 QV4::ScopedValue v(scope, (*args)[0]);
695 if (v->isInt32()) {
696 supportedActions = Qt::DropActions(v->integerValue());
697 d->overrideActions = true;
698 }
699 }
700
701 d->start(supportedActions);
702}
703
704/*!
705 \qmlattachedmethod enumeration QtQuick::Drag::drop()
706
707 Ends a drag sequence by sending a drop event to the target item.
708
709 Returns the action accepted by the target item. If the target item or a parent doesn't accept
710 the drop event then Qt.IgnoreAction will be returned.
711
712 The returned drop action may be one of:
713
714 \value Qt.CopyAction Copy the data to the target
715 \value Qt.MoveAction Move the data from the source to the target
716 \value Qt.LinkAction Create a link from the source to the target.
717 \value Qt.IgnoreAction Ignore the action (do nothing with the data).
718*/
719
720int QQuickDragAttached::drop()
721{
722 Q_D(QQuickDragAttached);
723 Qt::DropAction acceptedAction = Qt::IgnoreAction;
724
725 if (d->inEvent) {
726 qmlWarning(this) << "drop() cannot be called from within a drag event handler";
727 return acceptedAction;
728 }
729
730 if (d->itemMoved)
731 d->deliverMoveEvent();
732
733 if (!d->active)
734 return acceptedAction;
735 d->active = false;
736
737 QObject *target = nullptr;
738
739 if (d->window) {
740 QPoint scenePos = d->attachedItem->mapToScene(d->hotSpot).toPoint();
741
742 QDropEvent event(
743 scenePos, d->mimeData->m_supportedActions, d->mimeData, Qt::NoButton, Qt::NoModifier);
744 QQuickDropEventEx::setProposedAction(&event, d->proposedAction);
745 d->deliverEvent(d->window, &event);
746
747 if (event.isAccepted()) {
748 acceptedAction = event.dropAction();
749 target = d->dragGrabber.target();
750 }
751 }
752
753 if (d->target != target) {
754 d->target = target;
755 emit targetChanged();
756 }
757
758 emit activeChanged();
759 return acceptedAction;
760}
761
762/*!
763 \qmlattachedmethod void QtQuick::Drag::cancel()
764
765 Ends a drag sequence.
766*/
767
768void QQuickDragAttached::cancel()
769{
770 Q_D(QQuickDragAttached);
771
772 if (d->inEvent) {
773 qmlWarning(this) << "cancel() cannot be called from within a drag event handler";
774 return;
775 }
776
777 if (!d->active)
778 return;
779 d->active = false;
780 d->deliverLeaveEvent();
781
782 if (d->target) {
783 d->target = nullptr;
784 emit targetChanged();
785 }
786
787 emit activeChanged();
788}
789
790/*!
791 \qmlattachedsignal QtQuick::Drag::dragStarted()
792
793 This signal is emitted when a drag is started with the \l startDrag() method
794 or when it is started automatically using the \l dragType property.
795 */
796
797/*!
798 \qmlattachedsignal QtQuick::Drag::dragFinished(DropAction dropAction)
799
800 This signal is emitted when a drag finishes and the drag was started with the
801 \l startDrag() method or started automatically using the \l dragType property.
802
803 \a dropAction holds the action accepted by the target item.
804
805 \sa drop()
806 */
807
808QMimeData *QQuickDragAttachedPrivate::createMimeData() const
809{
810 Q_Q(const QQuickDragAttached);
811 QMimeData *mimeData = new QMimeData();
812
813 for (const auto [mimeType, value] : externalMimeData.asKeyValueRange()) {
814 switch (value.typeId()) {
815 case QMetaType::QByteArray:
816 // byte array assumed to already be correctly encoded
817 mimeData->setData(mimeType, value.toByteArray());
818 break;
819 case QMetaType::QString: {
820 const QString text = value.toString();
821 if (mimeType == u"text/plain"_s) {
822 mimeData->setText(text);
823 } else if (mimeType == u"text/html"_s) {
824 mimeData->setHtml(text);
825 } else if (mimeType == u"text/uri-list"_s) {
826 QList<QUrl> urls;
827 // parse and split according to RFC2483
828 const auto lines = text.split(u"\r\n"_s, Qt::SkipEmptyParts);
829 for (const auto &line : lines) {
830 const QUrl url(line);
831 if (url.isValid())
832 urls.push_back(url);
833 else
834 qmlWarning(q) << line << " is not a valid URI";
835
836 }
837 mimeData->setUrls(urls);
838 } else if (mimeType.startsWith(u"text/"_s)) {
839 if (qsizetype charsetIdx = mimeType.lastIndexOf(u";charset="_s); charsetIdx != -1) {
840 charsetIdx += sizeof(";charset=") - 1;
841 const QByteArray encoding = mimeType.mid(charsetIdx).toUtf8();
842 QStringEncoder encoder(encoding);
843 if (encoder.isValid())
844 mimeData->setData(mimeType, encoder.encode(text));
845 else
846 qmlWarning(q) << "Don't know how to encode text as " << mimeType;
847 } else {
848 mimeData->setData(mimeType, text.toUtf8());
849 }
850 } else {
851 mimeData->setData(mimeType, text.toUtf8());
852 }
853 break;
854 }
855 case QMetaType::QVariantList:
856 case QMetaType::QStringList:
857 if (mimeType == u"text/uri-list"_s) {
858 const QVariantList values = value.toList();
859 QList<QUrl> urls;
860 urls.reserve(values.size());
861 bool error = false;
862 for (qsizetype index = 0; index < values.size(); ++index) {
863 const QUrl url = values.at(index).value<QUrl>();
864 if (url.isValid()) {
865 urls += url;
866 } else {
867 error = true;
868 qmlWarning(q) << "Value '" << values.at(index) << "' at index " << index
869 << " is not a valid URI";
870 }
871 }
872 if (!error)
873 mimeData->setUrls(urls);
874 }
875 break;
876 case QMetaType::QColor:
877 if (mimeType == u"application/x-color"_s)
878 mimeData->setColorData(value);
879 break;
880 case QMetaType::QImage:
881 if (const QByteArray mimeTypeUtf8 = mimeType.toUtf8();
882 QImageWriter::supportedMimeTypes().contains(mimeTypeUtf8)) {
883 const auto imageFormats = QImageWriter::imageFormatsForMimeType(mimeTypeUtf8);
884 if (imageFormats.isEmpty()) { // shouldn't happen, but we can fall back
885 mimeData->setImageData(value);
886 break;
887 }
888 const QImage image = value.value<QImage>();
889 QByteArray bytes;
890 {
891 QBuffer buffer(&bytes);
892 QImageWriter encoder(&buffer, imageFormats.first());
893 encoder.write(image);
894 }
895 mimeData->setData(mimeType, bytes);
896 break;
897 }
898 Q_FALLTHROUGH();
899 default:
900 qmlWarning(q) << "Don't know how to encode variant of type " << value.metaType()
901 << " as mime type " << mimeType;
902 // compatibility with pre-6.5 - probably a bad idea
903 mimeData->setData(mimeType, value.toString().toUtf8());
904 break;
905 }
906 }
907
908 return mimeData;
909}
910
911void QQuickDragAttachedPrivate::loadPixmap()
912{
913 Q_Q(QQuickDragAttached);
914
915 QUrl loadUrl = imageSource;
916 const QQmlContext *context = qmlContext(q->parent());
917 if (context)
918 loadUrl = context->resolvedUrl(imageSource);
919 pixmapLoader.load(context ? context->engine() : nullptr, loadUrl, QRect(), q->imageSourceSize());
920}
921
922Qt::DropAction QQuickDragAttachedPrivate::startDrag(Qt::DropActions supportedActions,
923 QPixmap pixmap)
924{
925 Q_Q(QQuickDragAttached);
926
927 QDrag *drag = new QDrag(source ? source : q);
928
929 drag->setMimeData(createMimeData());
930 if (!pixmap.isNull())
931 drag->setPixmap(pixmap);
932 else if (pixmapLoader.isReady())
933 drag->setPixmap(QPixmap::fromImage(pixmapLoader.image()));
934
935 drag->setHotSpot(hotSpot.toPoint());
936
937 // exec() below blocks in a nested event loop, during which the item we are attached
938 // to can be destroyed -- a view recycling the delegate that is being dragged is
939 // enough (QTBUG-124663). Both this attached object and the QDrag are QObject
940 // children of that item, so they would go with it, leaving nothing to run the code
941 // after exec(). Listen for it, so that itemDestroyed() can wind the drag down while
942 // we are still alive, and guard what we touch afterwards.
943 listenForAttachedItemDestruction(true);
944 QPointer<QQuickDragAttached> self(q);
945 QPointer<QDrag> dragGuard(drag);
946
947 emit q->dragStarted();
948
949 executingNativeDrag = true;
950 Qt::DropAction dropAction = drag->exec(supportedActions);
951 if (!self) {
952 // itemDestroyed() has already delivered the leave event and reset the state.
953 return Qt::IgnoreAction;
954 }
955 executingNativeDrag = false;
956 listenForAttachedItemDestruction(false);
957
958 if (dragGuard
959 && !QGuiApplicationPrivate::platformIntegration()->drag()->ownsDragObject()) {
960 dragGuard->deleteLater();
961 }
962
963 deliverLeaveEvent();
964
965 if (target) {
966 target = nullptr;
967 emit q->targetChanged();
968 }
969
970 emit q->dragFinished(dropAction);
971
972 active = false;
973 emit q->activeChanged();
974
975 return dropAction;
976}
977
978
979/*!
980 \qmlattachedmethod void QtQuick::Drag::startDrag(flags supportedActions)
981
982 Starts sending drag events.
983
984 The optional \a supportedActions argument can be used to override the \l supportedActions
985 property for the started sequence.
986*/
987
988void QQuickDragAttached::startDrag(QQmlV4FunctionPtr args)
989{
990 Q_D(QQuickDragAttached);
991
992 if (d->inEvent) {
993 qmlWarning(this) << "startDrag() cannot be called from within a drag event handler";
994 return;
995 }
996
997 if (!d->active) {
998 qmlWarning(this) << "startDrag() drag must be active";
999 return;
1000 }
1001
1002 Qt::DropActions supportedActions = d->supportedActions;
1003
1004 // check arguments for supportedActions
1005 if (args->length() >= 1) {
1006 QV4::Scope scope(args->v4engine());
1007 QV4::ScopedValue v(scope, (*args)[0]);
1008 if (v->isInt32()) {
1009 supportedActions = Qt::DropActions(v->integerValue());
1010 }
1011 }
1012
1013 Qt::DropAction dropAction = d->startDrag(supportedActions);
1014
1015 args->setReturnValue(QV4::Encode((int)dropAction));
1016}
1017
1018QQuickDrag::QQuickDrag(QObject *parent)
1019: QObject(parent), _target(nullptr), _axis(XAndYAxis), _xmin(-FLT_MAX),
1020_xmax(FLT_MAX), _ymin(-FLT_MAX), _ymax(FLT_MAX), _active(false), _filterChildren(false),
1021 _smoothed(true), _threshold(QGuiApplication::styleHints()->startDragDistance())
1022{
1023}
1024
1025QQuickDrag::~QQuickDrag()
1026{
1027}
1028
1029QQuickItem *QQuickDrag::target() const
1030{
1031 return _target;
1032}
1033
1034void QQuickDrag::setTarget(QQuickItem *t)
1035{
1036 if (_target == t)
1037 return;
1038 _target = t;
1039 emit targetChanged();
1040}
1041
1042void QQuickDrag::resetTarget()
1043{
1044 if (_target == nullptr)
1045 return;
1046 _target = nullptr;
1047 emit targetChanged();
1048}
1049
1050QQuickDrag::Axis QQuickDrag::axis() const
1051{
1052 return _axis;
1053}
1054
1055void QQuickDrag::setAxis(QQuickDrag::Axis a)
1056{
1057 if (_axis == a)
1058 return;
1059 _axis = a;
1060 emit axisChanged();
1061}
1062
1063qreal QQuickDrag::xmin() const
1064{
1065 return _xmin;
1066}
1067
1068void QQuickDrag::setXmin(qreal m)
1069{
1070 if (_xmin == m)
1071 return;
1072 _xmin = m;
1073 emit minimumXChanged();
1074}
1075
1076qreal QQuickDrag::xmax() const
1077{
1078 return _xmax;
1079}
1080
1081void QQuickDrag::setXmax(qreal m)
1082{
1083 if (_xmax == m)
1084 return;
1085 _xmax = m;
1086 emit maximumXChanged();
1087}
1088
1089qreal QQuickDrag::ymin() const
1090{
1091 return _ymin;
1092}
1093
1094void QQuickDrag::setYmin(qreal m)
1095{
1096 if (_ymin == m)
1097 return;
1098 _ymin = m;
1099 emit minimumYChanged();
1100}
1101
1102qreal QQuickDrag::ymax() const
1103{
1104 return _ymax;
1105}
1106
1107void QQuickDrag::setYmax(qreal m)
1108{
1109 if (_ymax == m)
1110 return;
1111 _ymax = m;
1112 emit maximumYChanged();
1113}
1114
1115bool QQuickDrag::smoothed() const
1116{
1117 return _smoothed;
1118}
1119
1120void QQuickDrag::setSmoothed(bool smooth)
1121{
1122 if (_smoothed != smooth) {
1123 _smoothed = smooth;
1124 emit smoothedChanged();
1125 }
1126}
1127
1128qreal QQuickDrag::threshold() const
1129{
1130 return _threshold;
1131}
1132
1133void QQuickDrag::setThreshold(qreal value)
1134{
1135 if (_threshold != value) {
1136 _threshold = value;
1137 emit thresholdChanged();
1138 }
1139}
1140
1141void QQuickDrag::resetThreshold()
1142{
1143 setThreshold(QGuiApplication::styleHints()->startDragDistance());
1144}
1145
1146bool QQuickDrag::active() const
1147{
1148 return _active;
1149}
1150
1151void QQuickDrag::setActive(bool drag)
1152{
1153 if (_active == drag)
1154 return;
1155 _active = drag;
1156 emit activeChanged();
1157}
1158
1159bool QQuickDrag::filterChildren() const
1160{
1161 return _filterChildren;
1162}
1163
1164void QQuickDrag::setFilterChildren(bool filter)
1165{
1166 if (_filterChildren == filter)
1167 return;
1168 _filterChildren = filter;
1169 emit filterChildrenChanged();
1170}
1171
1172QQuickDragAttached *QQuickDrag::qmlAttachedProperties(QObject *obj)
1173{
1174 return new QQuickDragAttached(obj);
1175}
1176
1177QT_END_NAMESPACE
1178
1179#include "moc_qquickdrag_p.cpp"
Combined button and popup list for selecting options.