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