1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the QtSG module of the Qt Toolkit.
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.
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.
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.
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.
40 ****************************************************************************/
42 #include "qquickpincharea_p_p.h"
43 #include "qquickcanvas.h"
45 #include <QtGui/qevent.h>
46 #include <QtGui/qguiapplication.h>
47 #include <QtGui/qstylehints.h>
55 \qmlclass PinchEvent QQuickPinchEvent
56 \inqmlmodule QtQuick 2
57 \ingroup qtquick-input-events
58 \brief For specifying information about a pinch event
60 \b {The PinchEvent type was added in QtQuick 1.1}
62 The \c center, \c startCenter, \c previousCenter properties provide the center position between the two touch points.
64 The \c scale and \c previousScale properties provide the scale factor.
66 The \c angle, \c previousAngle and \c rotation properties provide the angle between the two points and the amount of rotation.
68 The \c point1, \c point2, \c startPoint1, \c startPoint2 properties provide the positions of the touch points.
70 The \c accepted property may be set to false in the \c onPinchStarted handler if the gesture should not
77 \qmlproperty QPointF QtQuick2::PinchEvent::center
78 \qmlproperty QPointF QtQuick2::PinchEvent::startCenter
79 \qmlproperty QPointF QtQuick2::PinchEvent::previousCenter
81 These properties hold the position of the center point between the two touch points.
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
91 \qmlproperty real QtQuick2::PinchEvent::scale
92 \qmlproperty real QtQuick2::PinchEvent::previousScale
94 These properties hold the scale factor determined by the change in distance between the two touch points.
97 \li \c scale is the current scale factor.
98 \li \c previousScale is the scale factor of the previous event.
101 When a pinch gesture is started, the scale is 1.0.
105 \qmlproperty real QtQuick2::PinchEvent::angle
106 \qmlproperty real QtQuick2::PinchEvent::previousAngle
107 \qmlproperty real QtQuick2::PinchEvent::rotation
109 These properties hold the angle between the two touch points.
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.
117 When a pinch gesture is started, the rotation is 0.0.
121 \qmlproperty QPointF QtQuick2::PinchEvent::point1
122 \qmlproperty QPointF QtQuick2::PinchEvent::startPoint1
123 \qmlproperty QPointF QtQuick2::PinchEvent::point2
124 \qmlproperty QPointF QtQuick2::PinchEvent::startPoint2
126 These properties provide the actual touch points generating the pinch.
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.
135 \qmlproperty bool QtQuick2::PinchEvent::accepted
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
143 \qmlproperty int QtQuick2::PinchEvent::pointCount
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.
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)
158 QQuickPinchAreaPrivate::~QQuickPinchAreaPrivate()
164 \qmlclass PinchArea QQuickPinchArea
165 \inqmlmodule QtQuick 2
166 \ingroup qtquick-input
168 \brief Enables simple pinch gesture handling
170 \b {The PinchArea type was added in QtQuick 1.1}
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.
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
179 PinchArea can be used in two ways:
182 \li setting a \c pinch.target to provide automatic interaction with an item
183 \li using the onPinchStarted, onPinchUpdated and onPinchFinished handlers
190 \qmlsignal QtQuick2::PinchArea::onPinchStarted()
192 This handler is called when the pinch area detects that a pinch gesture has started.
194 The \l {PinchEvent}{pinch} parameter provides information about the pinch gesture,
195 including the scale, center and angle of the pinch.
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.
202 \qmlsignal QtQuick2::PinchArea::onPinchUpdated()
204 This handler is called when the pinch area detects that a pinch gesture has changed.
206 The \l {PinchEvent}{pinch} parameter provides information about the pinch gesture,
207 including the scale, center and angle of the pinch.
211 \qmlsignal QtQuick2::PinchArea::onPinchFinished()
213 This handler is called when the pinch area detects that a pinch gesture has finished.
215 The \l {PinchEvent}{pinch} parameter provides information about the pinch gesture,
216 including the scale, center and angle of the pinch.
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
233 \c pinch provides a convenient way to make an item react to pinch gestures.
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.
245 QQuickPinchArea::QQuickPinchArea(QQuickItem *parent)
246 : QQuickItem(*(new QQuickPinchAreaPrivate), parent)
248 Q_D(QQuickPinchArea);
250 setAcceptedMouseButtons(Qt::LeftButton);
251 setFiltersChildMouseEvents(true);
254 QQuickPinchArea::~QQuickPinchArea()
258 \qmlproperty bool QtQuick2::PinchArea::enabled
259 This property holds whether the item accepts pinch gestures.
261 This property defaults to true.
263 bool QQuickPinchArea::isEnabled() const
265 Q_D(const QQuickPinchArea);
269 void QQuickPinchArea::setEnabled(bool a)
271 Q_D(QQuickPinchArea);
272 if (a != d->enabled) {
274 emit enabledChanged();
278 void QQuickPinchArea::touchEvent(QTouchEvent *event)
280 Q_D(QQuickPinchArea);
281 if (!d->enabled || !isVisible()) {
282 QQuickItem::event(event);
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 QQuickCanvas 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).
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);
311 case QEvent::TouchEnd:
312 d->touchPoints.clear();
316 QQuickItem::event(event);
320 void QQuickPinchArea::updatePinch()
322 Q_D(QQuickPinchArea);
323 if (d->touchPoints.count() == 0) {
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);
342 d->initPinch = false;
343 d->pinchRejected = false;
344 d->stealMouse = false;
345 setKeepMouseGrab(false);
349 if (d->touchPoints.count() == 1) {
350 setKeepMouseGrab(false);
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;
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;
382 sceneCenter = d->sceneLastCenter + touchPoint2.scenePos() - d->lastPoint2;
383 angle = d->pinchLastAngle;
385 d->id1 = touchPoint1.id();
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;
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);
418 d->stealMouse = true;
419 QQuickCanvas *c = canvas();
420 if (c && c->mouseGrabberItem() != this)
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);
430 d->pinchRejected = true;
433 } else if (d->pinchStartDist > 0) {
434 qreal scale = dist ? dist / d->pinchStartDist : d->pinchLastScale;
435 qreal da = d->pinchLastAngle - angle;
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) {
465 if (x < pinch()->xmin())
467 else if (x > pinch()->xmax())
469 pinch()->target()->setX(x);
471 if (pinch()->axis() & QQuickPinch::YAxis) {
473 if (y < pinch()->ymin())
475 else if (y > pinch()->ymax())
477 pinch()->target()->setY(y);
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);
490 bool QQuickPinchArea::childMouseEventFilter(QQuickItem *i, QEvent *e)
492 Q_D(QQuickPinchArea);
493 if (!d->enabled || !isVisible())
494 return QQuickItem::childMouseEventFilter(i, e);
496 case QEvent::TouchBegin:
497 case QEvent::TouchUpdate: {
498 QTouchEvent *touch = static_cast<QTouchEvent*>(e);
499 if (touch->touchPoints().count() > 1) {
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);
510 case QEvent::TouchEnd:
511 d->touchPoints.clear();
518 return QQuickItem::childMouseEventFilter(i, e);
521 void QQuickPinchArea::geometryChanged(const QRectF &newGeometry,
522 const QRectF &oldGeometry)
524 QQuickItem::geometryChanged(newGeometry, oldGeometry);
527 void QQuickPinchArea::itemChange(ItemChange change, const ItemChangeData &value)
529 QQuickItem::itemChange(change, value);
532 QQuickPinch *QQuickPinchArea::pinch()
534 Q_D(QQuickPinchArea);
536 d->pinch = new QQuickPinch;