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