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 "qquickwindow.h"
45 #include <QtGui/qevent.h>
46 #include <QtGui/qguiapplication.h>
47 #include <QtGui/qstylehints.h>
56 \instantiates QQuickPinchEvent
57 \inqmlmodule QtQuick 2
58 \ingroup qtquick-input-events
59 \brief For specifying information about a pinch event
61 \b {The PinchEvent type was added in QtQuick 1.1}
63 The \c center, \c startCenter, \c previousCenter properties provide the center position between the two touch points.
65 The \c scale and \c previousScale properties provide the scale factor.
67 The \c angle, \c previousAngle and \c rotation properties provide the angle between the two points and the amount of rotation.
69 The \c point1, \c point2, \c startPoint1, \c startPoint2 properties provide the positions of the touch points.
71 The \c accepted property may be set to false in the \c onPinchStarted handler if the gesture should not
78 \qmlproperty QPointF QtQuick2::PinchEvent::center
79 \qmlproperty QPointF QtQuick2::PinchEvent::startCenter
80 \qmlproperty QPointF QtQuick2::PinchEvent::previousCenter
82 These properties hold the position of the center point between the two touch points.
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
92 \qmlproperty real QtQuick2::PinchEvent::scale
93 \qmlproperty real QtQuick2::PinchEvent::previousScale
95 These properties hold the scale factor determined by the change in distance between the two touch points.
98 \li \c scale is the current scale factor.
99 \li \c previousScale is the scale factor of the previous event.
102 When a pinch gesture is started, the scale is 1.0.
106 \qmlproperty real QtQuick2::PinchEvent::angle
107 \qmlproperty real QtQuick2::PinchEvent::previousAngle
108 \qmlproperty real QtQuick2::PinchEvent::rotation
110 These properties hold the angle between the two touch points.
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.
118 When a pinch gesture is started, the rotation is 0.0.
122 \qmlproperty QPointF QtQuick2::PinchEvent::point1
123 \qmlproperty QPointF QtQuick2::PinchEvent::startPoint1
124 \qmlproperty QPointF QtQuick2::PinchEvent::point2
125 \qmlproperty QPointF QtQuick2::PinchEvent::startPoint2
127 These properties provide the actual touch points generating the pinch.
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.
136 \qmlproperty bool QtQuick2::PinchEvent::accepted
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
144 \qmlproperty int QtQuick2::PinchEvent::pointCount
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.
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)
159 QQuickPinchAreaPrivate::~QQuickPinchAreaPrivate()
166 \instantiates QQuickPinchArea
167 \inqmlmodule QtQuick 2
168 \ingroup qtquick-input
170 \brief Enables simple pinch gesture handling
172 \b {The PinchArea type was added in QtQuick 1.1}
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.
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
181 PinchArea can be used in two ways:
184 \li setting a \c pinch.target to provide automatic interaction with an item
185 \li using the onPinchStarted, onPinchUpdated and onPinchFinished handlers
192 \qmlsignal QtQuick2::PinchArea::onPinchStarted()
194 This handler is called when the pinch area detects that a pinch gesture has started.
196 The \l {PinchEvent}{pinch} parameter provides information about the pinch gesture,
197 including the scale, center and angle of the pinch.
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.
204 \qmlsignal QtQuick2::PinchArea::onPinchUpdated()
206 This handler is called when the pinch area detects that a pinch gesture has changed.
208 The \l {PinchEvent}{pinch} parameter provides information about the pinch gesture,
209 including the scale, center and angle of the pinch.
213 \qmlsignal QtQuick2::PinchArea::onPinchFinished()
215 This handler is called when the pinch area detects that a pinch gesture has finished.
217 The \l {PinchEvent}{pinch} parameter provides information about the pinch gesture,
218 including the scale, center and angle of the pinch.
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
235 \c pinch provides a convenient way to make an item react to pinch gestures.
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.
247 QQuickPinchArea::QQuickPinchArea(QQuickItem *parent)
248 : QQuickItem(*(new QQuickPinchAreaPrivate), parent)
250 Q_D(QQuickPinchArea);
252 setAcceptedMouseButtons(Qt::LeftButton);
253 setFiltersChildMouseEvents(true);
256 QQuickPinchArea::~QQuickPinchArea()
260 \qmlproperty bool QtQuick2::PinchArea::enabled
261 This property holds whether the item accepts pinch gestures.
263 This property defaults to true.
265 bool QQuickPinchArea::isEnabled() const
267 Q_D(const QQuickPinchArea);
271 void QQuickPinchArea::setEnabled(bool a)
273 Q_D(QQuickPinchArea);
274 if (a != d->enabled) {
276 emit enabledChanged();
280 void QQuickPinchArea::touchEvent(QTouchEvent *event)
282 Q_D(QQuickPinchArea);
283 if (!d->enabled || !isVisible()) {
284 QQuickItem::event(event);
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).
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);
313 case QEvent::TouchEnd:
314 d->touchPoints.clear();
318 QQuickItem::event(event);
322 void QQuickPinchArea::updatePinch()
324 Q_D(QQuickPinchArea);
325 if (d->touchPoints.count() == 0) {
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);
344 d->initPinch = false;
345 d->pinchRejected = false;
346 d->stealMouse = false;
347 setKeepMouseGrab(false);
351 if (d->touchPoints.count() == 1) {
352 setKeepMouseGrab(false);
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;
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;
384 sceneCenter = d->sceneLastCenter + touchPoint2.scenePos() - d->lastPoint2;
385 angle = d->pinchLastAngle;
387 d->id1 = touchPoint1.id();
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;
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);
420 d->stealMouse = true;
421 QQuickWindow *c = window();
422 if (c && c->mouseGrabberItem() != this)
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);
432 d->pinchRejected = true;
435 } else if (d->pinchStartDist > 0) {
436 qreal scale = dist ? dist / d->pinchStartDist : d->pinchLastScale;
437 qreal da = d->pinchLastAngle - angle;
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) {
467 if (x < pinch()->xmin())
469 else if (x > pinch()->xmax())
471 pinch()->target()->setX(x);
473 if (pinch()->axis() & QQuickPinch::YAxis) {
475 if (y < pinch()->ymin())
477 else if (y > pinch()->ymax())
479 pinch()->target()->setY(y);
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);
492 bool QQuickPinchArea::childMouseEventFilter(QQuickItem *i, QEvent *e)
494 Q_D(QQuickPinchArea);
495 if (!d->enabled || !isVisible())
496 return QQuickItem::childMouseEventFilter(i, e);
498 case QEvent::TouchBegin:
499 case QEvent::TouchUpdate: {
500 QTouchEvent *touch = static_cast<QTouchEvent*>(e);
501 if (touch->touchPoints().count() > 1) {
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);
512 case QEvent::TouchEnd:
513 d->touchPoints.clear();
520 return QQuickItem::childMouseEventFilter(i, e);
523 void QQuickPinchArea::geometryChanged(const QRectF &newGeometry,
524 const QRectF &oldGeometry)
526 QQuickItem::geometryChanged(newGeometry, oldGeometry);
529 void QQuickPinchArea::itemChange(ItemChange change, const ItemChangeData &value)
531 QQuickItem::itemChange(change, value);
534 QQuickPinch *QQuickPinchArea::pinch()
536 Q_D(QQuickPinchArea);
538 d->pinch = new QQuickPinch;