Update to 5.0.0-beta1
[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 "qquickwindow.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     \qmltype PinchEvent
56     \instantiates QQuickPinchEvent
57     \inqmlmodule QtQuick 2
58     \ingroup qtquick-input-events
59     \brief For specifying information about a pinch event
60
61     \b {The PinchEvent type was added in QtQuick 1.1}
62
63     The \c center, \c startCenter, \c previousCenter properties provide the center position between the two touch points.
64
65     The \c scale and \c previousScale properties provide the scale factor.
66
67     The \c angle, \c previousAngle and \c rotation properties provide the angle between the two points and the amount of rotation.
68
69     The \c point1, \c point2, \c startPoint1, \c startPoint2 properties provide the positions of the touch points.
70
71     The \c accepted property may be set to false in the \c onPinchStarted handler if the gesture should not
72     be handled.
73
74     \sa PinchArea
75 */
76
77 /*!
78     \qmlproperty QPointF QtQuick2::PinchEvent::center
79     \qmlproperty QPointF QtQuick2::PinchEvent::startCenter
80     \qmlproperty QPointF QtQuick2::PinchEvent::previousCenter
81
82     These properties hold the position of the center point between the two touch points.
83
84     \list
85     \li \c center is the current center point
86     \li \c previousCenter is the center point of the previous event.
87     \li \c startCenter is the center point when the gesture began
88     \endlist
89 */
90
91 /*!
92     \qmlproperty real QtQuick2::PinchEvent::scale
93     \qmlproperty real QtQuick2::PinchEvent::previousScale
94
95     These properties hold the scale factor determined by the change in distance between the two touch points.
96
97     \list
98     \li \c scale is the current scale factor.
99     \li \c previousScale is the scale factor of the previous event.
100     \endlist
101
102     When a pinch gesture is started, the scale is 1.0.
103 */
104
105 /*!
106     \qmlproperty real QtQuick2::PinchEvent::angle
107     \qmlproperty real QtQuick2::PinchEvent::previousAngle
108     \qmlproperty real QtQuick2::PinchEvent::rotation
109
110     These properties hold the angle between the two touch points.
111
112     \list
113     \li \c angle is the current angle between the two points in the range -180 to 180.
114     \li \c previousAngle is the angle of the previous event.
115     \li \c rotation is the total rotation since the pinch gesture started.
116     \endlist
117
118     When a pinch gesture is started, the rotation is 0.0.
119 */
120
121 /*!
122     \qmlproperty QPointF QtQuick2::PinchEvent::point1
123     \qmlproperty QPointF QtQuick2::PinchEvent::startPoint1
124     \qmlproperty QPointF QtQuick2::PinchEvent::point2
125     \qmlproperty QPointF QtQuick2::PinchEvent::startPoint2
126
127     These properties provide the actual touch points generating the pinch.
128
129     \list
130     \li \c point1 and \c point2 hold the current positions of the points.
131     \li \c startPoint1 and \c startPoint2 hold the positions of the points when the second point was touched.
132     \endlist
133 */
134
135 /*!
136     \qmlproperty bool QtQuick2::PinchEvent::accepted
137
138     Setting this property to false in the \c PinchArea::onPinchStarted handler
139     will result in no further pinch events being generated, and the gesture
140     ignored.
141 */
142
143 /*!
144     \qmlproperty int QtQuick2::PinchEvent::pointCount
145
146     Holds the number of points currently touched.  The PinchArea will not react
147     until two touch points have initited a gesture, but will remain active until
148     all touch points have been released.
149 */
150
151 QQuickPinch::QQuickPinch()
152     : m_target(0), m_minScale(1.0), m_maxScale(1.0)
153     , m_minRotation(0.0), m_maxRotation(0.0)
154     , m_axis(NoDrag), m_xmin(-FLT_MAX), m_xmax(FLT_MAX)
155     , m_ymin(-FLT_MAX), m_ymax(FLT_MAX), m_active(false)
156 {
157 }
158
159 QQuickPinchAreaPrivate::~QQuickPinchAreaPrivate()
160 {
161     delete pinch;
162 }
163
164 /*!
165     \qmltype PinchArea
166     \instantiates QQuickPinchArea
167     \inqmlmodule QtQuick 2
168     \ingroup qtquick-input
169     \inherits Item
170     \brief Enables simple pinch gesture handling
171
172     \b {The PinchArea type was added in QtQuick 1.1}
173
174     A PinchArea is an invisible item that is typically used in conjunction with
175     a visible item in order to provide pinch gesture handling for that item.
176
177     The \l enabled property is used to enable and disable pinch handling for
178     the proxied item. When disabled, the pinch area becomes transparent to
179     mouse/touch events.
180
181     PinchArea can be used in two ways:
182
183     \list
184     \li setting a \c pinch.target to provide automatic interaction with an item
185     \li using the onPinchStarted, onPinchUpdated and onPinchFinished handlers
186     \endlist
187
188     \sa PinchEvent
189 */
190
191 /*!
192     \qmlsignal QtQuick2::PinchArea::onPinchStarted()
193
194     This handler is called when the pinch area detects that a pinch gesture has started.
195
196     The \l {PinchEvent}{pinch} parameter provides information about the pinch gesture,
197     including the scale, center and angle of the pinch.
198
199     To ignore this gesture set the \c pinch.accepted property to false.  The gesture
200     will be canceled and no further events will be sent.
201 */
202
203 /*!
204     \qmlsignal QtQuick2::PinchArea::onPinchUpdated()
205
206     This handler is called when the pinch area detects that a pinch gesture has changed.
207
208     The \l {PinchEvent}{pinch} parameter provides information about the pinch gesture,
209     including the scale, center and angle of the pinch.
210 */
211
212 /*!
213     \qmlsignal QtQuick2::PinchArea::onPinchFinished()
214
215     This handler is called when the pinch area detects that a pinch gesture has finished.
216
217     The \l {PinchEvent}{pinch} parameter provides information about the pinch gesture,
218     including the scale, center and angle of the pinch.
219 */
220
221
222 /*!
223     \qmlproperty Item QtQuick2::PinchArea::pinch.target
224     \qmlproperty bool QtQuick2::PinchArea::pinch.active
225     \qmlproperty real QtQuick2::PinchArea::pinch.minimumScale
226     \qmlproperty real QtQuick2::PinchArea::pinch.maximumScale
227     \qmlproperty real QtQuick2::PinchArea::pinch.minimumRotation
228     \qmlproperty real QtQuick2::PinchArea::pinch.maximumRotation
229     \qmlproperty enumeration QtQuick2::PinchArea::pinch.dragAxis
230     \qmlproperty real QtQuick2::PinchArea::pinch.minimumX
231     \qmlproperty real QtQuick2::PinchArea::pinch.maximumX
232     \qmlproperty real QtQuick2::PinchArea::pinch.minimumY
233     \qmlproperty real QtQuick2::PinchArea::pinch.maximumY
234
235     \c pinch provides a convenient way to make an item react to pinch gestures.
236
237     \list
238     \li \c pinch.target specifies the id of the item to drag.
239     \li \c pinch.active specifies if the target item is currently being dragged.
240     \li \c pinch.minimumScale and \c pinch.maximumScale limit the range of the Item::scale property.
241     \li \c pinch.minimumRotation and \c pinch.maximumRotation limit the range of the Item::rotation property.
242     \li \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)
243     \li \c pinch.minimum and \c pinch.maximum limit how far the target can be dragged along the corresponding axes.
244     \endlist
245 */
246
247 QQuickPinchArea::QQuickPinchArea(QQuickItem *parent)
248   : QQuickItem(*(new QQuickPinchAreaPrivate), parent)
249 {
250     Q_D(QQuickPinchArea);
251     d->init();
252     setAcceptedMouseButtons(Qt::LeftButton);
253     setFiltersChildMouseEvents(true);
254 }
255
256 QQuickPinchArea::~QQuickPinchArea()
257 {
258 }
259 /*!
260     \qmlproperty bool QtQuick2::PinchArea::enabled
261     This property holds whether the item accepts pinch gestures.
262
263     This property defaults to true.
264 */
265 bool QQuickPinchArea::isEnabled() const
266 {
267     Q_D(const QQuickPinchArea);
268     return d->enabled;
269 }
270
271 void QQuickPinchArea::setEnabled(bool a)
272 {
273     Q_D(QQuickPinchArea);
274     if (a != d->enabled) {
275         d->enabled = a;
276         emit enabledChanged();
277     }
278 }
279
280 void QQuickPinchArea::touchEvent(QTouchEvent *event)
281 {
282     Q_D(QQuickPinchArea);
283     if (!d->enabled || !isVisible()) {
284         QQuickItem::event(event);
285         return;
286     }
287
288     // A common non-trivial starting scenario is the user puts down one finger,
289     // then that finger remains stationary while putting down a second one.
290     // However QQuickWindow will not send TouchUpdates for TouchPoints which
291     // were not initially accepted; that would be inefficient and noisy.
292     // So even if there is only one touchpoint so far, it's important to accept it
293     // in order to get updates later on (and it's accepted by default anyway).
294     // If the user puts down one finger, we're waiting for the other finger to drop.
295     // Therefore updatePinch() must do the right thing for any combination of
296     // points and states that may occur, and there is no reason to ignore any event.
297     // One consequence though is that if PinchArea is on top of something else,
298     // it's always going to accept the touches, and that means the item underneath
299     // will not get them (unless the PA's parent is doing parent filtering,
300     // as the Flickable does, for example).
301
302     switch (event->type()) {
303     case QEvent::TouchBegin:
304     case QEvent::TouchUpdate:
305         d->touchPoints.clear();
306         for (int i = 0; i < event->touchPoints().count(); ++i) {
307             if (!(event->touchPoints().at(i).state() & Qt::TouchPointReleased)) {
308                 d->touchPoints << event->touchPoints().at(i);
309             }
310         }
311         updatePinch();
312         break;
313     case QEvent::TouchEnd:
314         d->touchPoints.clear();
315         updatePinch();
316         break;
317     default:
318         QQuickItem::event(event);
319     }
320 }
321
322 void QQuickPinchArea::updatePinch()
323 {
324     Q_D(QQuickPinchArea);
325     if (d->touchPoints.count() == 0) {
326         if (d->inPinch) {
327             d->inPinch = false;
328             QPointF pinchCenter = mapFromScene(d->sceneLastCenter);
329             QQuickPinchEvent pe(pinchCenter, d->pinchLastScale, d->pinchLastAngle, d->pinchRotation);
330             pe.setStartCenter(d->pinchStartCenter);
331             pe.setPreviousCenter(pinchCenter);
332             pe.setPreviousAngle(d->pinchLastAngle);
333             pe.setPreviousScale(d->pinchLastScale);
334             pe.setStartPoint1(mapFromScene(d->sceneStartPoint1));
335             pe.setStartPoint2(mapFromScene(d->sceneStartPoint2));
336             pe.setPoint1(mapFromScene(d->lastPoint1));
337             pe.setPoint2(mapFromScene(d->lastPoint2));
338             emit pinchFinished(&pe);
339             d->pinchStartDist = 0;
340             d->pinchActivated = false;
341             if (d->pinch && d->pinch->target())
342                 d->pinch->setActive(false);
343         }
344         d->initPinch = false;
345         d->pinchRejected = false;
346         d->stealMouse = false;
347         setKeepMouseGrab(false);
348         return;
349     }
350
351     if (d->touchPoints.count() == 1) {
352         setKeepMouseGrab(false);
353     }
354
355     QTouchEvent::TouchPoint touchPoint1 = d->touchPoints.at(0);
356     QTouchEvent::TouchPoint touchPoint2 = d->touchPoints.at(d->touchPoints. count() >= 2 ? 1 : 0);
357     QRectF bounds = clipRect();
358     // Pinch is not started unless there are exactly two touch points
359     // AND one or more of the points has just now been pressed (wasn't pressed already)
360     // AND both points are inside the bounds.
361     if (d->touchPoints.count() == 2
362             && (touchPoint1.state() & Qt::TouchPointPressed || touchPoint2.state() & Qt::TouchPointPressed) &&
363             bounds.contains(touchPoint1.pos()) && bounds.contains(touchPoint2.pos())) {
364         d->id1 = touchPoint1.id();
365         d->sceneStartPoint1 = touchPoint1.scenePos();
366         d->sceneStartPoint2 = touchPoint2.scenePos();
367         d->pinchActivated = true;
368         d->initPinch = true;
369     }
370     if (d->pinchActivated && !d->pinchRejected) {
371         const int dragThreshold = qApp->styleHints()->startDragDistance();
372         QPointF p1 = touchPoint1.scenePos();
373         QPointF p2 = touchPoint2.scenePos();
374         qreal dx = p1.x() - p2.x();
375         qreal dy = p1.y() - p2.y();
376         qreal dist = sqrt(dx*dx + dy*dy);
377         QPointF sceneCenter = (p1 + p2)/2;
378         qreal angle = QLineF(p1, p2).angle();
379         if (d->touchPoints.count() == 1) {
380             // If we only have one point then just move the center
381             if (d->id1 == touchPoint1.id())
382                 sceneCenter = d->sceneLastCenter + touchPoint1.scenePos() - d->lastPoint1;
383             else
384                 sceneCenter = d->sceneLastCenter + touchPoint2.scenePos() - d->lastPoint2;
385             angle = d->pinchLastAngle;
386         }
387         d->id1 = touchPoint1.id();
388         if (angle > 180)
389             angle -= 360;
390         if (!d->inPinch || d->initPinch) {
391             if (d->touchPoints.count() >= 2
392                     && (qAbs(p1.x()-d->sceneStartPoint1.x()) >= dragThreshold
393                     || qAbs(p1.y()-d->sceneStartPoint1.y()) >= dragThreshold
394                     || qAbs(p2.x()-d->sceneStartPoint2.x()) >= dragThreshold
395                     || qAbs(p2.y()-d->sceneStartPoint2.y()) >= dragThreshold)) {
396                 d->initPinch = false;
397                 d->sceneStartCenter = sceneCenter;
398                 d->sceneLastCenter = sceneCenter;
399                 d->pinchStartCenter = mapFromScene(sceneCenter);
400                 d->pinchStartDist = dist;
401                 d->pinchStartAngle = angle;
402                 d->pinchLastScale = 1.0;
403                 d->pinchLastAngle = angle;
404                 d->pinchRotation = 0.0;
405                 d->lastPoint1 = p1;
406                 d->lastPoint2 = p2;
407                 QQuickPinchEvent pe(d->pinchStartCenter, 1.0, angle, 0.0);
408                 pe.setStartCenter(d->pinchStartCenter);
409                 pe.setPreviousCenter(d->pinchStartCenter);
410                 pe.setPreviousAngle(d->pinchLastAngle);
411                 pe.setPreviousScale(d->pinchLastScale);
412                 pe.setStartPoint1(mapFromScene(d->sceneStartPoint1));
413                 pe.setStartPoint2(mapFromScene(d->sceneStartPoint2));
414                 pe.setPoint1(mapFromScene(d->lastPoint1));
415                 pe.setPoint2(mapFromScene(d->lastPoint2));
416                 pe.setPointCount(d->touchPoints.count());
417                 emit pinchStarted(&pe);
418                 if (pe.accepted()) {
419                     d->inPinch = true;
420                     d->stealMouse = true;
421                     QQuickWindow *c = window();
422                     if (c && c->mouseGrabberItem() != this)
423                         grabMouse();
424                     setKeepMouseGrab(true);
425                     if (d->pinch && d->pinch->target()) {
426                         d->pinchStartPos = pinch()->target()->pos();
427                         d->pinchStartScale = d->pinch->target()->scale();
428                         d->pinchStartRotation = d->pinch->target()->rotation();
429                         d->pinch->setActive(true);
430                     }
431                 } else {
432                     d->pinchRejected = true;
433                 }
434             }
435         } else if (d->pinchStartDist > 0) {
436             qreal scale = dist ? dist / d->pinchStartDist : d->pinchLastScale;
437             qreal da = d->pinchLastAngle - angle;
438             if (da > 180)
439                 da -= 360;
440             else if (da < -180)
441                 da += 360;
442             d->pinchRotation += da;
443             QPointF pinchCenter = mapFromScene(sceneCenter);
444             QQuickPinchEvent pe(pinchCenter, scale, angle, d->pinchRotation);
445             pe.setStartCenter(d->pinchStartCenter);
446             pe.setPreviousCenter(mapFromScene(d->sceneLastCenter));
447             pe.setPreviousAngle(d->pinchLastAngle);
448             pe.setPreviousScale(d->pinchLastScale);
449             pe.setStartPoint1(mapFromScene(d->sceneStartPoint1));
450             pe.setStartPoint2(mapFromScene(d->sceneStartPoint2));
451             pe.setPoint1(touchPoint1.pos());
452             pe.setPoint2(touchPoint2.pos());
453             pe.setPointCount(d->touchPoints.count());
454             d->pinchLastScale = scale;
455             d->sceneLastCenter = sceneCenter;
456             d->pinchLastAngle = angle;
457             d->lastPoint1 = touchPoint1.scenePos();
458             d->lastPoint2 = touchPoint2.scenePos();
459             emit pinchUpdated(&pe);
460             if (d->pinch && d->pinch->target()) {
461                 qreal s = d->pinchStartScale * scale;
462                 s = qMin(qMax(pinch()->minimumScale(),s), pinch()->maximumScale());
463                 pinch()->target()->setScale(s);
464                 QPointF pos = sceneCenter - d->sceneStartCenter + d->pinchStartPos;
465                 if (pinch()->axis() & QQuickPinch::XAxis) {
466                     qreal x = pos.x();
467                     if (x < pinch()->xmin())
468                         x = pinch()->xmin();
469                     else if (x > pinch()->xmax())
470                         x = pinch()->xmax();
471                     pinch()->target()->setX(x);
472                 }
473                 if (pinch()->axis() & QQuickPinch::YAxis) {
474                     qreal y = pos.y();
475                     if (y < pinch()->ymin())
476                         y = pinch()->ymin();
477                     else if (y > pinch()->ymax())
478                         y = pinch()->ymax();
479                     pinch()->target()->setY(y);
480                 }
481                 if (d->pinchStartRotation >= pinch()->minimumRotation()
482                         && d->pinchStartRotation <= pinch()->maximumRotation()) {
483                     qreal r = d->pinchRotation + d->pinchStartRotation;
484                     r = qMin(qMax(pinch()->minimumRotation(),r), pinch()->maximumRotation());
485                     pinch()->target()->setRotation(r);
486                 }
487             }
488         }
489     }
490 }
491
492 bool QQuickPinchArea::childMouseEventFilter(QQuickItem *i, QEvent *e)
493 {
494     Q_D(QQuickPinchArea);
495     if (!d->enabled || !isVisible())
496         return QQuickItem::childMouseEventFilter(i, e);
497     switch (e->type()) {
498     case QEvent::TouchBegin:
499     case QEvent::TouchUpdate: {
500             QTouchEvent *touch = static_cast<QTouchEvent*>(e);
501             if (touch->touchPoints().count() > 1) {
502                 touchEvent(touch);
503             } else {
504                 d->touchPoints.clear();
505                 for (int i = 0; i < touch->touchPoints().count(); ++i)
506                     if (!(touch->touchPoints().at(i).state() & Qt::TouchPointReleased))
507                         d->touchPoints << touch->touchPoints().at(i);
508                 updatePinch();
509             }
510         }
511         return d->inPinch;
512     case QEvent::TouchEnd:
513         d->touchPoints.clear();
514         updatePinch();
515         break;
516     default:
517         break;
518     }
519
520     return QQuickItem::childMouseEventFilter(i, e);
521 }
522
523 void QQuickPinchArea::geometryChanged(const QRectF &newGeometry,
524                                             const QRectF &oldGeometry)
525 {
526     QQuickItem::geometryChanged(newGeometry, oldGeometry);
527 }
528
529 void QQuickPinchArea::itemChange(ItemChange change, const ItemChangeData &value)
530 {
531     QQuickItem::itemChange(change, value);
532 }
533
534 QQuickPinch *QQuickPinchArea::pinch()
535 {
536     Q_D(QQuickPinchArea);
537     if (!d->pinch)
538         d->pinch = new QQuickPinch;
539     return d->pinch;
540 }
541
542
543 QT_END_NAMESPACE
544