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