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