Update to 5.0.0-beta1
[profile/ivi/qtdeclarative.git] / src / quick / items / qquickdrag.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
5 **
6 ** This file is part of the QtQml module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
16 **
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
20 **
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
28 **
29 ** Other Usage
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qquickdrag_p.h"
43
44 #include <private/qquickitem_p.h>
45 #include <QtQuick/private/qquickevents_p_p.h>
46 #include <private/qquickitemchangelistener_p.h>
47 #include <private/qv8engine_p.h>
48 #include <QtCore/qcoreapplication.h>
49 #include <QtQml/qqmlinfo.h>
50 #include <QtGui/qevent.h>
51
52 #ifndef QT_NO_DRAGANDDROP
53
54 QT_BEGIN_NAMESPACE
55
56 class QQuickDragAttachedPrivate : public QObjectPrivate, public QQuickItemChangeListener
57 {
58     Q_DECLARE_PUBLIC(QQuickDragAttached)
59 public:
60     static QQuickDragAttachedPrivate *get(QQuickDragAttached *attached) {
61         return static_cast<QQuickDragAttachedPrivate *>(QObjectPrivate::get(attached)); }
62
63     QQuickDragAttachedPrivate()
64         : attachedItem(0)
65         , mimeData(0)
66         , proposedAction(Qt::MoveAction)
67         , supportedActions(Qt::MoveAction | Qt::CopyAction | Qt::LinkAction)
68         , active(false)
69         , listening(false)
70         , inEvent(false)
71         , dragRestarted(false)
72         , itemMoved(false)
73         , eventQueued(false)
74         , overrideActions(false)
75     {
76     }
77
78     void itemGeometryChanged(QQuickItem *, const QRectF &, const QRectF &);
79     void itemParentChanged(QQuickItem *, QQuickItem *parent);
80     void updatePosition();
81     void restartDrag();
82     void deliverEnterEvent();
83     void deliverMoveEvent();
84     void deliverLeaveEvent();
85     void deliverEvent(QQuickWindow *window, QEvent *event);
86     void start() { start(supportedActions); }
87     void start(Qt::DropActions supportedActions);
88     void setTarget(QQuickItem *item);
89
90     QQuickDragGrabber dragGrabber;
91
92     QQmlGuard<QObject> source;
93     QQmlGuard<QObject> target;
94     QQmlGuard<QQuickWindow> window;
95     QQuickItem *attachedItem;
96     QQuickDragMimeData *mimeData;
97     Qt::DropAction proposedAction;
98     Qt::DropActions supportedActions;
99     bool active : 1;
100     bool listening : 1;
101     bool inEvent : 1;
102     bool dragRestarted : 1;
103     bool itemMoved : 1;
104     bool eventQueued : 1;
105     bool overrideActions : 1;
106     QPointF hotSpot;
107     QStringList keys;
108 };
109
110 /*!
111     \qmltype Drag
112     \instantiates QQuickDrag
113     \inqmlmodule QtQuick 2
114     \ingroup qtquick-input
115     \brief For specifying drag and drop events for moved Items
116
117     Using the Drag attached property any Item can made a source of drag and drop
118     events within a scene.
119
120     When a drag is \l active on an item any change in that items position will
121     generate a drag events that will be sent to any DropArea that intersects
122     the with new  position of the item.  Other items which implement drag and
123      drop event handlers can also receive these events.
124
125     The following snippet shows how an item can be dragged with a MouseArea.
126     However, dragging is not limited to mouse drags, anything that can move an item
127     can generate drag events, this can include touch events, animations and bindings.
128
129     \snippet qml/drag.qml 0
130
131     A drag can be terminated either by canceling it with Drag.cancel() or setting
132     Drag.active to false, or it can be terminated with a drop event by calling
133     Drag.drop().  If the drop event is accepted Drag.drop() will return the
134     \l {supportedActions}{drop action} chosen by the recipient of the event,
135     otherwise it will return Qt.IgnoreAction.
136
137 */
138
139 void QQuickDragAttachedPrivate::itemGeometryChanged(QQuickItem *, const QRectF &newGeometry, const QRectF &oldGeometry)
140 {
141     if (newGeometry.topLeft() == oldGeometry.topLeft() || !active || itemMoved)
142         return;
143     updatePosition();
144 }
145
146 void QQuickDragAttachedPrivate::itemParentChanged(QQuickItem *, QQuickItem *)
147 {
148     if (!active || dragRestarted)
149         return;
150
151     QQuickWindow *newWindow = attachedItem->window();
152
153     if (window != newWindow)
154         restartDrag();
155     else if (window)
156         updatePosition();
157 }
158
159 void QQuickDragAttachedPrivate::updatePosition()
160 {
161     Q_Q(QQuickDragAttached);
162     itemMoved = true;
163     if (!eventQueued) {
164         eventQueued = true;
165         QCoreApplication::postEvent(q, new QEvent(QEvent::User));
166     }
167 }
168
169 void QQuickDragAttachedPrivate::restartDrag()
170 {
171     Q_Q(QQuickDragAttached);
172     dragRestarted = true;
173     if (!eventQueued) {
174         eventQueued = true;
175         QCoreApplication::postEvent(q, new QEvent(QEvent::User));
176     }
177 }
178
179 void QQuickDragAttachedPrivate::deliverEnterEvent()
180 {
181     dragRestarted = false;
182     itemMoved = false;
183
184     window = attachedItem->window();
185
186     mimeData->m_source = source;
187     if (!overrideActions)
188         mimeData->m_supportedActions = supportedActions;
189     mimeData->m_keys = keys;
190
191     if (window) {
192         QPoint scenePos = attachedItem->mapToScene(hotSpot).toPoint();
193         QDragEnterEvent event(scenePos, mimeData->m_supportedActions, mimeData, Qt::NoButton, Qt::NoModifier);
194         QQuickDropEventEx::setProposedAction(&event, proposedAction);
195         deliverEvent(window, &event);
196     }
197 }
198
199 void QQuickDragAttachedPrivate::deliverMoveEvent()
200 {
201     Q_Q(QQuickDragAttached);
202
203     itemMoved = false;
204     if (window) {
205         QPoint scenePos = attachedItem->mapToScene(hotSpot).toPoint();
206         QDragMoveEvent event(scenePos, mimeData->m_supportedActions, mimeData, Qt::NoButton, Qt::NoModifier);
207         QQuickDropEventEx::setProposedAction(&event, proposedAction);
208         deliverEvent(window, &event);
209         if (target != dragGrabber.target()) {
210             target = dragGrabber.target();
211             emit q->targetChanged();
212         }
213     }
214 }
215
216 void QQuickDragAttachedPrivate::deliverLeaveEvent()
217 {
218     if (window) {
219         QDragLeaveEvent event;
220         deliverEvent(window, &event);
221         window = 0;
222     }
223 }
224
225 void QQuickDragAttachedPrivate::deliverEvent(QQuickWindow *window, QEvent *event)
226 {
227     Q_ASSERT(!inEvent);
228     inEvent = true;
229     QQuickWindowPrivate::get(window)->deliverDragEvent(&dragGrabber, event);
230     inEvent = false;
231 }
232
233 bool QQuickDragAttached::event(QEvent *event)
234 {
235     Q_D(QQuickDragAttached);
236
237     if (event->type() == QEvent::User) {
238         d->eventQueued = false;
239         if (d->dragRestarted) {
240             d->deliverLeaveEvent();
241             d->deliverEnterEvent();
242
243             if (d->target != d->dragGrabber.target()) {
244                 d->target = d->dragGrabber.target();
245                 emit targetChanged();
246             }
247         } else if (d->itemMoved) {
248             d->deliverMoveEvent();
249         }
250         return true;
251     } else {
252         return QObject::event(event);
253     }
254 }
255
256 QQuickDragAttached::QQuickDragAttached(QObject *parent)
257     : QObject(*new QQuickDragAttachedPrivate, parent)
258 {
259     Q_D(QQuickDragAttached);
260     d->attachedItem = qobject_cast<QQuickItem *>(parent);
261     d->source = d->attachedItem;
262 }
263
264 QQuickDragAttached::~QQuickDragAttached()
265 {
266     Q_D(QQuickDragAttached);
267     delete d->mimeData;
268 }
269
270 /*!
271     \qmlattachedproperty bool QtQuick2::Drag::active
272
273     This property holds whether a drag event sequence is currently active.
274
275     Setting this property to true will send a QDragEnter event to the scene
276     with the item's current position.  Setting it to false will send a
277     QDragLeave event.
278
279     While a drag is active any change in an item's position will send a QDragMove
280     event with item's new position to the scene.
281 */
282
283 bool QQuickDragAttached::isActive() const
284 {
285     Q_D(const QQuickDragAttached);
286     return d->active;
287 }
288
289 void QQuickDragAttached::setActive(bool active)
290 {
291     Q_D(QQuickDragAttached);
292     if (d->active != active) {
293         if (d->inEvent)
294             qmlInfo(this) << "active cannot be changed from within a drag event handler";
295         else if (active)
296             d->start(d->supportedActions);
297         else
298             cancel();
299     }
300 }
301
302 /*!
303     \qmlattachedproperty Object QtQuick2::Drag::source
304
305     This property holds an object that is identified to recipients of drag events as
306     the source of the events.  By default this is the item Drag property is attached to.
307
308     Changing the source while a drag is active will reset the sequence of drag events by
309     sending a drag leave event followed by a drag enter event with the new source.
310 */
311
312 QObject *QQuickDragAttached::source() const
313 {
314     Q_D(const QQuickDragAttached);
315     return d->source;
316 }
317
318 void QQuickDragAttached::setSource(QObject *item)
319 {
320     Q_D(QQuickDragAttached);
321     if (d->source != item) {
322         d->source = item;
323         if (d->active)
324             d->restartDrag();
325         emit sourceChanged();
326     }
327 }
328
329 void QQuickDragAttached::resetSource()
330 {
331     Q_D(QQuickDragAttached);
332     if (d->source != d->attachedItem) {
333         d->source = d->attachedItem;
334         if (d->active)
335             d->restartDrag();
336         emit sourceChanged();
337     }
338 }
339
340 /*!
341     \qmlattachedproperty Object QtQuick2::Drag::target
342
343     While a drag is active this property holds the last object to accept an
344     enter event from the dragged item, if the current drag position doesn't
345     intersect any accepting targets it is null.
346
347     When a drag is not active this property holds the object that accepted
348     the drop event that ended the drag, if no object accepted the drop or
349     the drag was canceled the target will then be null.
350 */
351
352 QObject *QQuickDragAttached::target() const
353 {
354     Q_D(const QQuickDragAttached);
355     return d->target;
356 }
357
358 /*!
359     \qmlattachedproperty QPointF QtQuick2::Drag::hotSpot
360
361     This property holds the drag position relative to the top left of the item.
362
363     By default this is (0, 0).
364
365     Changes to hotSpot trigger a new drag move with the updated position.
366 */
367
368 QPointF QQuickDragAttached::hotSpot() const
369 {
370     Q_D(const QQuickDragAttached);
371     return d->hotSpot;
372 }
373
374 void QQuickDragAttached::setHotSpot(const QPointF &hotSpot)
375 {
376     Q_D(QQuickDragAttached);
377     if (d->hotSpot != hotSpot) {
378         d->hotSpot = hotSpot;
379
380         if (d->active)
381             d->updatePosition();
382
383         emit hotSpotChanged();
384     }
385 }
386
387 /*!
388     \qmlattachedproperty stringlist QtQuick2::Drag::keys
389
390     This property holds a list of keys that can be used by a DropArea to filter drag events.
391
392     Changing the keys while a drag is active will reset the sequence of drag events by
393     sending a drag leave event followed by a drag enter event with the new source.
394 */
395
396 QStringList QQuickDragAttached::keys() const
397 {
398     Q_D(const QQuickDragAttached);
399     return d->keys;
400 }
401
402 void QQuickDragAttached::setKeys(const QStringList &keys)
403 {
404     Q_D(QQuickDragAttached);
405     if (d->keys != keys) {
406         d->keys = keys;
407         if (d->active)
408             d->restartDrag();
409         emit keysChanged();
410     }
411 }
412
413 /*!
414     \qmlattachedproperty flags QtQuick2::Drag::supportedActions
415
416     This property holds return values of Drag.drop() supported by the drag source.
417
418     Changing the supportedActions while a drag is active will reset the sequence of drag
419     events by sending a drag leave event followed by a drag enter event with the new source.
420 */
421
422 Qt::DropActions QQuickDragAttached::supportedActions() const
423 {
424     Q_D(const QQuickDragAttached);
425     return d->supportedActions;
426 }
427
428 void QQuickDragAttached::setSupportedActions(Qt::DropActions actions)
429 {
430     Q_D(QQuickDragAttached);
431     if (d->supportedActions != actions) {
432         d->supportedActions = actions;
433         if (d->active)
434             d->restartDrag();
435         emit supportedActionsChanged();
436     }
437 }
438
439 /*!
440     \qmlattachedproperty enumeration QtQuick2::Drag::proposedAction
441
442     This property holds an action that is recommended by the drag source as a
443     return value from Drag.drop().
444
445     Changes to proposedAction will trigger a move event with the updated proposal.
446 */
447
448 Qt::DropAction QQuickDragAttached::proposedAction() const
449 {
450     Q_D(const QQuickDragAttached);
451     return d->proposedAction;
452 }
453
454 void QQuickDragAttached::setProposedAction(Qt::DropAction action)
455 {
456     Q_D(QQuickDragAttached);
457     if (d->proposedAction != action) {
458         d->proposedAction = action;
459         // The proposed action shouldn't affect whether a drag is accepted
460         // so leave/enter events are excessive, but the target should still
461         // updated.
462         if (d->active)
463             d->updatePosition();
464         emit proposedActionChanged();
465     }
466 }
467
468 void QQuickDragAttachedPrivate::start(Qt::DropActions supportedActions)
469 {
470     Q_Q(QQuickDragAttached);
471     Q_ASSERT(!active);
472
473     if (!mimeData)
474         mimeData = new QQuickDragMimeData;
475     if (!listening) {
476         QQuickItemPrivate::get(attachedItem)->addItemChangeListener(
477                 this, QQuickItemPrivate::Geometry | QQuickItemPrivate::Parent);
478         listening = true;
479     }
480
481     mimeData->m_supportedActions = supportedActions;
482     active = true;
483     itemMoved = false;
484     dragRestarted = false;
485
486     deliverEnterEvent();
487
488     if (target != dragGrabber.target()) {
489         target = dragGrabber.target();
490         emit q->targetChanged();
491     }
492
493     emit q->activeChanged();
494 }
495
496 /*!
497     \qmlattachedmethod void QtQuick2::Drag::start(flags supportedActions)
498
499     Starts sending drag events.
500
501     The optional \a supportedActions argument can be used to override the \l supportedActions
502     property for the started sequence.
503 */
504
505 void QQuickDragAttached::start(QQmlV8Function *args)
506 {
507     Q_D(QQuickDragAttached);
508     if (d->inEvent) {
509         qmlInfo(this) << "start() cannot be called from within a drag event handler";
510         return;
511     }
512
513     if (d->active)
514         cancel();
515
516     d->overrideActions = false;
517     Qt::DropActions supportedActions = d->supportedActions;
518     // check arguments for supportedActions, maybe data?
519     if (args->Length() >= 1) {
520         v8::Local<v8::Value> v = (*args)[0];
521         if (v->IsInt32()) {
522             supportedActions = Qt::DropActions(v->Int32Value());
523             d->overrideActions = true;
524         }
525     }
526
527     d->start(supportedActions);
528 }
529
530 /*!
531     \qmlattachedmethod enumeration QtQuick2::Drag::drop()
532
533     Ends a drag sequence by sending a drop event to the target item.
534
535     Returns the action accepted by the target item.  If the target item or a parent doesn't accept
536     the drop event then Qt.IgnoreAction will be returned.
537
538     The returned drop action may be one of:
539
540     \list
541     \li Qt.CopyAction Copy the data to the target
542     \li Qt.MoveAction Move the data from the source to the target
543     \li Qt.LinkAction Create a link from the source to the target.
544     \li Qt.IgnoreAction Ignore the action (do nothing with the data).
545     \endlist
546
547 */
548
549 int QQuickDragAttached::drop()
550 {
551     Q_D(QQuickDragAttached);
552     Qt::DropAction acceptedAction = Qt::IgnoreAction;
553
554     if (d->inEvent) {
555         qmlInfo(this) << "drop() cannot be called from within a drag event handler";
556         return acceptedAction;
557     }
558
559     if (d->itemMoved)
560         d->deliverMoveEvent();
561
562     if (!d->active)
563         return acceptedAction;
564     d->active = false;
565
566     QObject *target = 0;
567
568     if (d->window) {
569         QPoint scenePos = d->attachedItem->mapToScene(d->hotSpot).toPoint();
570
571         QDropEvent event(
572                 scenePos, d->mimeData->m_supportedActions, d->mimeData, Qt::NoButton, Qt::NoModifier);
573         QQuickDropEventEx::setProposedAction(&event, d->proposedAction);
574         d->deliverEvent(d->window, &event);
575
576         if (event.isAccepted()) {
577             acceptedAction = event.dropAction();
578             target = d->dragGrabber.target();
579         }
580     }
581
582     if (d->target != target) {
583         d->target = target;
584         emit targetChanged();
585     }
586
587     emit activeChanged();
588     return acceptedAction;
589 }
590
591 /*!
592     \qmlattachedmethod void QtQuick2::Drag::cancel()
593
594     Ends a drag sequence.
595 */
596
597 void QQuickDragAttached::cancel()
598 {
599     Q_D(QQuickDragAttached);
600
601     if (d->inEvent) {
602         qmlInfo(this) << "cancel() cannot be called from within a drag event handler";
603         return;
604     }
605
606     if (!d->active)
607         return;
608     d->active = false;
609     d->deliverLeaveEvent();
610
611     if (d->target) {
612         d->target = 0;
613         emit targetChanged();
614     }
615
616     emit activeChanged();
617 }
618
619 QT_END_NAMESPACE
620
621 #endif // QT_NO_DRAGANDDROP