Initial import from the monolithic Qt.
[profile/ivi/qtdeclarative.git] / src / declarative / graphicsitems / qdeclarativepincharea.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the QtDeclarative module of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** No Commercial Usage
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file.  Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23 **
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27 **
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
30 **
31 **
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qdeclarativepincharea_p.h"
43 #include "qdeclarativepincharea_p_p.h"
44
45 #include <QApplication>
46 #include <QGraphicsScene>
47
48 #include <float.h>
49 #include <math.h>
50
51 QT_BEGIN_NAMESPACE
52
53
54 /*!
55     \qmlclass PinchEvent QDeclarativePinchEvent
56     \ingroup qml-event-elements
57     \brief The PinchEvent object provides information about a pinch event.
58
59     \bold {The PinchEvent element was added in QtQuick 1.1}
60
61     The \c center, \c startCenter, \c previousCenter properties provide the center position between the two touch points.
62
63     The \c scale and \c previousScale properties provide the scale factor.
64
65     The \c angle, \c previousAngle and \c rotation properties provide the angle between the two points and the amount of rotation.
66
67     The \c point1, \c point2, \c startPoint1, \c startPoint2 properties provide the positions of the touch points.
68
69     The \c accepted property may be set to false in the \c onPinchStarted handler if the gesture should not
70     be handled.
71
72     \sa PinchArea
73 */
74
75 /*!
76     \qmlproperty QPointF PinchEvent::center
77     \qmlproperty QPointF PinchEvent::startCenter
78     \qmlproperty QPointF PinchEvent::previousCenter
79
80     These properties hold the position of the center point between the two touch points.
81
82     \list
83     \o \c center is the current center point
84     \o \c previousCenter is the center point of the previous event.
85     \o \c startCenter is the center point when the gesture began
86     \endlist
87 */
88
89 /*!
90     \qmlproperty real PinchEvent::scale
91     \qmlproperty real PinchEvent::previousScale
92
93     These properties hold the scale factor determined by the change in distance between the two touch points.
94
95     \list
96     \o \c scale is the current scale factor.
97     \o \c previousScale is the scale factor of the previous event.
98     \endlist
99
100     When a pinch gesture is started, the scale is 1.0.
101 */
102
103 /*!
104     \qmlproperty real PinchEvent::angle
105     \qmlproperty real PinchEvent::previousAngle
106     \qmlproperty real PinchEvent::rotation
107
108     These properties hold the angle between the two touch points.
109
110     \list
111     \o \c angle is the current angle between the two points in the range -180 to 180.
112     \o \c previousAngle is the angle of the previous event.
113     \o \c rotation is the total rotation since the pinch gesture started.
114     \endlist
115
116     When a pinch gesture is started, the rotation is 0.0.
117 */
118
119 /*!
120     \qmlproperty QPointF PinchEvent::point1
121     \qmlproperty QPointF PinchEvent::startPoint1
122     \qmlproperty QPointF PinchEvent::point2
123     \qmlproperty QPointF PinchEvent::startPoint2
124
125     These properties provide the actual touch points generating the pinch.
126
127     \list
128     \o \c point1 and \c point2 hold the current positions of the points.
129     \o \c startPoint1 and \c startPoint2 hold the positions of the points when the second point was touched.
130     \endlist
131 */
132
133 /*!
134     \qmlproperty bool PinchEvent::accepted
135
136     Setting this property to false in the \c PinchArea::onPinchStarted handler
137     will result in no further pinch events being generated, and the gesture
138     ignored.
139 */
140
141 /*!
142     \qmlproperty int PinchEvent::pointCount
143
144     Holds the number of points currently touched.  The PinchArea will not react
145     until two touch points have initited a gesture, but will remain active until
146     all touch points have been released.
147 */
148
149 QDeclarativePinch::QDeclarativePinch()
150     : m_target(0), m_minScale(1.0), m_maxScale(1.0)
151     , m_minRotation(0.0), m_maxRotation(0.0)
152     , m_axis(NoDrag), m_xmin(-FLT_MAX), m_xmax(FLT_MAX)
153     , m_ymin(-FLT_MAX), m_ymax(FLT_MAX), m_active(false)
154 {
155 }
156
157 QDeclarativePinchAreaPrivate::~QDeclarativePinchAreaPrivate()
158 {
159     delete pinch;
160 }
161
162 /*!
163     \qmlclass PinchArea QDeclarativePinchArea
164     \brief The PinchArea item enables simple pinch gesture handling.
165     \inherits Item
166
167     \bold {The PinchArea element was added in QtQuick 1.1}
168
169     A PinchArea is an invisible item that is typically used in conjunction with
170     a visible item in order to provide pinch gesture handling for that item.
171
172     The \l enabled property is used to enable and disable pinch handling for
173     the proxied item. When disabled, the pinch area becomes transparent to
174     mouse/touch events.
175
176     PinchArea can be used in two ways:
177
178     \list
179     \o setting a \c pinch.target to provide automatic interaction with an element
180     \o using the onPinchStarted, onPinchUpdated and onPinchFinished handlers
181     \endlist
182
183     \sa PinchEvent
184 */
185
186 /*!
187     \qmlsignal PinchArea::onPinchStarted()
188
189     This handler is called when the pinch area detects that a pinch gesture has started.
190
191     The \l {PinchEvent}{pinch} parameter provides information about the pinch gesture,
192     including the scale, center and angle of the pinch.
193
194     To ignore this gesture set the \c pinch.accepted property to false.  The gesture
195     will be cancelled and no further events will be sent.
196 */
197
198 /*!
199     \qmlsignal PinchArea::onPinchUpdated()
200
201     This handler is called when the pinch area detects that a pinch gesture has changed.
202
203     The \l {PinchEvent}{pinch} parameter provides information about the pinch gesture,
204     including the scale, center and angle of the pinch.
205 */
206
207 /*!
208     \qmlsignal PinchArea::onPinchFinished()
209
210     This handler is called when the pinch area detects that a pinch gesture has finished.
211
212     The \l {PinchEvent}{pinch} parameter provides information about the pinch gesture,
213     including the scale, center and angle of the pinch.
214 */
215
216
217 /*!
218     \qmlproperty Item PinchArea::pinch.target
219     \qmlproperty bool PinchArea::pinch.active
220     \qmlproperty real PinchArea::pinch.minimumScale
221     \qmlproperty real PinchArea::pinch.maximumScale
222     \qmlproperty real PinchArea::pinch.minimumRotation
223     \qmlproperty real PinchArea::pinch.maximumRotation
224     \qmlproperty enumeration PinchArea::pinch.dragAxis
225     \qmlproperty real PinchArea::pinch.minimumX
226     \qmlproperty real PinchArea::pinch.maximumX
227     \qmlproperty real PinchArea::pinch.minimumY
228     \qmlproperty real PinchArea::pinch.maximumY
229
230     \c pinch provides a convenient way to make an item react to pinch gestures.
231
232     \list
233     \i \c pinch.target specifies the id of the item to drag.
234     \i \c pinch.active specifies if the target item is currently being dragged.
235     \i \c pinch.minimumScale and \c pinch.maximumScale limit the range of the Item::scale property.
236     \i \c pinch.minimumRotation and \c pinch.maximumRotation limit the range of the Item::rotation property.
237     \i \c pinch.dragAxis specifies whether dragging in not allowed (\c Pinch.NoDrag), can be done horizontally (\c Pinch.XAxis), vertically (\c Pinch.YAxis), or both (\c Pinch.XandYAxis)
238     \i \c pinch.minimum and \c pinch.maximum limit how far the target can be dragged along the corresponding axes.
239     \endlist
240 */
241
242 QDeclarativePinchArea::QDeclarativePinchArea(QDeclarativeItem *parent)
243   : QDeclarativeItem(*(new QDeclarativePinchAreaPrivate), parent)
244 {
245     Q_D(QDeclarativePinchArea);
246     d->init();
247 }
248
249 QDeclarativePinchArea::~QDeclarativePinchArea()
250 {
251 }
252
253 /*!
254     \qmlproperty bool PinchArea::enabled
255     This property holds whether the item accepts pinch gestures.
256
257     This property defaults to true.
258 */
259 bool QDeclarativePinchArea::isEnabled() const
260 {
261     Q_D(const QDeclarativePinchArea);
262     return d->absorb;
263 }
264
265 void QDeclarativePinchArea::setEnabled(bool a)
266 {
267     Q_D(QDeclarativePinchArea);
268     if (a != d->absorb) {
269         d->absorb = a;
270         emit enabledChanged();
271     }
272 }
273
274 bool QDeclarativePinchArea::event(QEvent *event)
275 {
276     Q_D(QDeclarativePinchArea);
277     if (!d->absorb || !isVisible())
278         return QDeclarativeItem::event(event);
279     switch (event->type()) {
280     case QEvent::TouchBegin:
281     case QEvent::TouchUpdate: {
282             QTouchEvent *touch = static_cast<QTouchEvent*>(event);
283             d->touchPoints.clear();
284             for (int i = 0; i < touch->touchPoints().count(); ++i) {
285                 if (!(touch->touchPoints().at(i).state() & Qt::TouchPointReleased)) {
286                     d->touchPoints << touch->touchPoints().at(i);
287                 }
288             }
289             updatePinch();
290         }
291         return true;
292     case QEvent::TouchEnd:
293         d->touchPoints.clear();
294         updatePinch();
295         break;
296     default:
297         return QDeclarativeItem::event(event);
298     }
299
300     return QDeclarativeItem::event(event);
301 }
302
303 void QDeclarativePinchArea::updatePinch()
304 {
305     Q_D(QDeclarativePinchArea);
306     if (d->touchPoints.count() == 0) {
307         if (d->inPinch) {
308             d->stealMouse = false;
309             setKeepMouseGrab(false);
310             d->inPinch = false;
311             QPointF pinchCenter = mapFromScene(d->sceneLastCenter);
312             QDeclarativePinchEvent pe(pinchCenter, d->pinchLastScale, d->pinchLastAngle, d->pinchRotation);
313             pe.setStartCenter(d->pinchStartCenter);
314             pe.setPreviousCenter(pinchCenter);
315             pe.setPreviousAngle(d->pinchLastAngle);
316             pe.setPreviousScale(d->pinchLastScale);
317             pe.setStartPoint1(mapFromScene(d->sceneStartPoint1));
318             pe.setStartPoint2(mapFromScene(d->sceneStartPoint2));
319             pe.setPoint1(mapFromScene(d->lastPoint1));
320             pe.setPoint2(mapFromScene(d->lastPoint2));
321             emit pinchFinished(&pe);
322             d->pinchStartDist = 0;
323             d->pinchActivated = false;
324             if (d->pinch && d->pinch->target())
325                 d->pinch->setActive(false);
326         }
327         return;
328     }
329     QTouchEvent::TouchPoint touchPoint1 = d->touchPoints.at(0);
330     QTouchEvent::TouchPoint touchPoint2 = d->touchPoints.at(d->touchPoints. count() >= 2 ? 1 : 0);
331     if (d->touchPoints.count() == 2
332         && (touchPoint1.state() & Qt::TouchPointPressed || touchPoint2.state() & Qt::TouchPointPressed)) {
333         d->id1 = touchPoint1.id();
334         d->sceneStartPoint1 = touchPoint1.scenePos();
335         d->sceneStartPoint2 = touchPoint2.scenePos();
336         d->inPinch = false;
337         d->pinchRejected = false;
338         d->pinchActivated = true;
339     } else if (d->pinchActivated && !d->pinchRejected) {
340         const int dragThreshold = QApplication::startDragDistance();
341         QPointF p1 = touchPoint1.scenePos();
342         QPointF p2 = touchPoint2.scenePos();
343         qreal dx = p1.x() - p2.x();
344         qreal dy = p1.y() - p2.y();
345         qreal dist = sqrt(dx*dx + dy*dy);
346         QPointF sceneCenter = (p1 + p2)/2;
347         qreal angle = QLineF(p1, p2).angle();
348         if (d->touchPoints.count() == 1) {
349             // If we only have one point then just move the center
350             if (d->id1 == touchPoint1.id())
351                 sceneCenter = d->sceneLastCenter + touchPoint1.scenePos() - d->lastPoint1;
352             else
353                 sceneCenter = d->sceneLastCenter + touchPoint2.scenePos() - d->lastPoint2;
354             angle = d->pinchLastAngle;
355         }
356         d->id1 = touchPoint1.id();
357         if (angle > 180)
358             angle -= 360;
359         if (!d->inPinch) {
360             if (d->touchPoints.count() >= 2
361                     && (qAbs(p1.x()-d->sceneStartPoint1.x()) > dragThreshold
362                     || qAbs(p1.y()-d->sceneStartPoint1.y()) > dragThreshold
363                     || qAbs(p2.x()-d->sceneStartPoint2.x()) > dragThreshold
364                     || qAbs(p2.y()-d->sceneStartPoint2.y()) > dragThreshold)) {
365                 d->sceneStartCenter = sceneCenter;
366                 d->sceneLastCenter = sceneCenter;
367                 d->pinchStartCenter = mapFromScene(sceneCenter);
368                 d->pinchStartDist = dist;
369                 d->pinchStartAngle = angle;
370                 d->pinchLastScale = 1.0;
371                 d->pinchLastAngle = angle;
372                 d->pinchRotation = 0.0;
373                 d->lastPoint1 = p1;
374                 d->lastPoint2 = p2;
375                 QDeclarativePinchEvent pe(d->pinchStartCenter, 1.0, angle, 0.0);
376                 pe.setStartCenter(d->pinchStartCenter);
377                 pe.setPreviousCenter(d->pinchStartCenter);
378                 pe.setPreviousAngle(d->pinchLastAngle);
379                 pe.setPreviousScale(d->pinchLastScale);
380                 pe.setStartPoint1(mapFromScene(d->sceneStartPoint1));
381                 pe.setStartPoint2(mapFromScene(d->sceneStartPoint2));
382                 pe.setPoint1(mapFromScene(d->lastPoint1));
383                 pe.setPoint2(mapFromScene(d->lastPoint2));
384                 pe.setPointCount(d->touchPoints.count());
385                 emit pinchStarted(&pe);
386                 if (pe.accepted()) {
387                     d->inPinch = true;
388                     d->stealMouse = true;
389                     QGraphicsScene *s = scene();
390                     if (s && s->mouseGrabberItem() != this)
391                         grabMouse();
392                     setKeepMouseGrab(true);
393                     if (d->pinch && d->pinch->target()) {
394                         d->pinchStartPos = pinch()->target()->pos();
395                         d->pinchStartScale = d->pinch->target()->scale();
396                         d->pinchStartRotation = d->pinch->target()->rotation();
397                         d->pinch->setActive(true);
398                     }
399                 } else {
400                     d->pinchRejected = true;
401                 }
402             }
403         } else if (d->pinchStartDist > 0) {
404             qreal scale = dist ? dist / d->pinchStartDist : d->pinchLastScale;
405             qreal da = d->pinchLastAngle - angle;
406             if (da > 180)
407                 da -= 360;
408             else if (da < -180)
409                 da += 360;
410             d->pinchRotation += da;
411             QPointF pinchCenter = mapFromScene(sceneCenter);
412             QDeclarativePinchEvent pe(pinchCenter, scale, angle, d->pinchRotation);
413             pe.setStartCenter(d->pinchStartCenter);
414             pe.setPreviousCenter(mapFromScene(d->sceneLastCenter));
415             pe.setPreviousAngle(d->pinchLastAngle);
416             pe.setPreviousScale(d->pinchLastScale);
417             pe.setStartPoint1(mapFromScene(d->sceneStartPoint1));
418             pe.setStartPoint2(mapFromScene(d->sceneStartPoint2));
419             pe.setPoint1(touchPoint1.pos());
420             pe.setPoint2(touchPoint2.pos());
421             pe.setPointCount(d->touchPoints.count());
422             d->pinchLastScale = scale;
423             d->sceneLastCenter = sceneCenter;
424             d->pinchLastAngle = angle;
425             d->lastPoint1 = touchPoint1.scenePos();
426             d->lastPoint2 = touchPoint2.scenePos();
427             emit pinchUpdated(&pe);
428             if (d->pinch && d->pinch->target()) {
429                 qreal s = d->pinchStartScale * scale;
430                 s = qMin(qMax(pinch()->minimumScale(),s), pinch()->maximumScale());
431                 pinch()->target()->setScale(s);
432                 QPointF pos = sceneCenter - d->sceneStartCenter + d->pinchStartPos;
433                 if (pinch()->axis() & QDeclarativePinch::XAxis) {
434                     qreal x = pos.x();
435                     if (x < pinch()->xmin())
436                         x = pinch()->xmin();
437                     else if (x > pinch()->xmax())
438                         x = pinch()->xmax();
439                     pinch()->target()->setX(x);
440                 }
441                 if (pinch()->axis() & QDeclarativePinch::YAxis) {
442                     qreal y = pos.y();
443                     if (y < pinch()->ymin())
444                         y = pinch()->ymin();
445                     else if (y > pinch()->ymax())
446                         y = pinch()->ymax();
447                     pinch()->target()->setY(y);
448                 }
449                 if (d->pinchStartRotation >= pinch()->minimumRotation()
450                         && d->pinchStartRotation <= pinch()->maximumRotation()) {
451                     qreal r = d->pinchRotation + d->pinchStartRotation;
452                     r = qMin(qMax(pinch()->minimumRotation(),r), pinch()->maximumRotation());
453                     pinch()->target()->setRotation(r);
454                 }
455             }
456         }
457     }
458 }
459
460 void QDeclarativePinchArea::mousePressEvent(QGraphicsSceneMouseEvent *event)
461 {
462     Q_D(QDeclarativePinchArea);
463     d->stealMouse = false;
464     if (!d->absorb)
465         QDeclarativeItem::mousePressEvent(event);
466     else {
467         setKeepMouseGrab(false);
468         event->setAccepted(true);
469     }
470 }
471
472 void QDeclarativePinchArea::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
473 {
474     Q_D(QDeclarativePinchArea);
475     if (!d->absorb) {
476         QDeclarativeItem::mouseMoveEvent(event);
477         return;
478     }
479 }
480
481 void QDeclarativePinchArea::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
482 {
483     Q_D(QDeclarativePinchArea);
484     d->stealMouse = false;
485     if (!d->absorb) {
486         QDeclarativeItem::mouseReleaseEvent(event);
487     } else {
488         QGraphicsScene *s = scene();
489         if (s && s->mouseGrabberItem() == this)
490             ungrabMouse();
491         setKeepMouseGrab(false);
492     }
493 }
494
495 bool QDeclarativePinchArea::sceneEvent(QEvent *event)
496 {
497     bool rv = QDeclarativeItem::sceneEvent(event);
498     if (event->type() == QEvent::UngrabMouse) {
499         setKeepMouseGrab(false);
500     }
501     return rv;
502 }
503
504 bool QDeclarativePinchArea::sendMouseEvent(QGraphicsSceneMouseEvent *event)
505 {
506     Q_D(QDeclarativePinchArea);
507     QGraphicsSceneMouseEvent mouseEvent(event->type());
508     QRectF myRect = mapToScene(QRectF(0, 0, width(), height())).boundingRect();
509
510     QGraphicsScene *s = scene();
511     QDeclarativeItem *grabber = s ? qobject_cast<QDeclarativeItem*>(s->mouseGrabberItem()) : 0;
512     bool stealThisEvent = d->stealMouse;
513     if ((stealThisEvent || myRect.contains(event->scenePos().toPoint())) && (!grabber || !grabber->keepMouseGrab())) {
514         mouseEvent.setAccepted(false);
515         for (int i = 0x1; i <= 0x10; i <<= 1) {
516             if (event->buttons() & i) {
517                 Qt::MouseButton button = Qt::MouseButton(i);
518                 mouseEvent.setButtonDownPos(button, mapFromScene(event->buttonDownPos(button)));
519             }
520         }
521         mouseEvent.setScenePos(event->scenePos());
522         mouseEvent.setLastScenePos(event->lastScenePos());
523         mouseEvent.setPos(mapFromScene(event->scenePos()));
524         mouseEvent.setLastPos(mapFromScene(event->lastScenePos()));
525
526         switch(mouseEvent.type()) {
527         case QEvent::GraphicsSceneMouseMove:
528             mouseMoveEvent(&mouseEvent);
529             break;
530         case QEvent::GraphicsSceneMousePress:
531             mousePressEvent(&mouseEvent);
532             break;
533         case QEvent::GraphicsSceneMouseRelease:
534             mouseReleaseEvent(&mouseEvent);
535             break;
536         default:
537             break;
538         }
539         grabber = qobject_cast<QDeclarativeItem*>(s->mouseGrabberItem());
540         if (grabber && stealThisEvent && !grabber->keepMouseGrab() && grabber != this)
541             grabMouse();
542
543         return stealThisEvent;
544     }
545     if (mouseEvent.type() == QEvent::GraphicsSceneMouseRelease) {
546         d->stealMouse = false;
547         if (s && s->mouseGrabberItem() == this)
548             ungrabMouse();
549         setKeepMouseGrab(false);
550     }
551     return false;
552 }
553
554 bool QDeclarativePinchArea::sceneEventFilter(QGraphicsItem *i, QEvent *e)
555 {
556     Q_D(QDeclarativePinchArea);
557     if (!d->absorb || !isVisible())
558         return QDeclarativeItem::sceneEventFilter(i, e);
559     switch (e->type()) {
560     case QEvent::GraphicsSceneMousePress:
561     case QEvent::GraphicsSceneMouseMove:
562     case QEvent::GraphicsSceneMouseRelease:
563         return sendMouseEvent(static_cast<QGraphicsSceneMouseEvent *>(e));
564         break;
565     case QEvent::TouchBegin:
566     case QEvent::TouchUpdate: {
567             QTouchEvent *touch = static_cast<QTouchEvent*>(e);
568             d->touchPoints.clear();
569             for (int i = 0; i < touch->touchPoints().count(); ++i)
570                 if (!(touch->touchPoints().at(i).state() & Qt::TouchPointReleased))
571                     d->touchPoints << touch->touchPoints().at(i);
572             updatePinch();
573         }
574         return d->inPinch;
575     case QEvent::TouchEnd:
576         d->touchPoints.clear();
577         updatePinch();
578         break;
579     default:
580         break;
581     }
582
583     return QDeclarativeItem::sceneEventFilter(i, e);
584 }
585
586 void QDeclarativePinchArea::geometryChanged(const QRectF &newGeometry,
587                                             const QRectF &oldGeometry)
588 {
589     QDeclarativeItem::geometryChanged(newGeometry, oldGeometry);
590 }
591
592 QVariant QDeclarativePinchArea::itemChange(GraphicsItemChange change,
593                                        const QVariant &value)
594 {
595     return QDeclarativeItem::itemChange(change, value);
596 }
597
598 QDeclarativePinch *QDeclarativePinchArea::pinch()
599 {
600     Q_D(QDeclarativePinchArea);
601     if (!d->pinch)
602         d->pinch = new QDeclarativePinch;
603     return d->pinch;
604 }
605
606
607 QT_END_NAMESPACE