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 qml-event-elements
58 \brief The PinchEvent object provides information about a pinch event.
60 \bold {The PinchEvent element 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 \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
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 \o \c scale is the current scale factor.
98 \o \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 \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.
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 \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.
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 \brief The PinchArea item enables simple pinch gesture handling.
169 \bold {The PinchArea element was added in QtQuick 1.1}
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.
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
178 PinchArea can be used in two ways:
181 \o setting a \c pinch.target to provide automatic interaction with an element
182 \o using the onPinchStarted, onPinchUpdated and onPinchFinished handlers
189 \qmlsignal QtQuick2::PinchArea::onPinchStarted()
191 This handler is called when the pinch area detects that a pinch gesture has started.
193 The \l {PinchEvent}{pinch} parameter provides information about the pinch gesture,
194 including the scale, center and angle of the pinch.
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.
201 \qmlsignal QtQuick2::PinchArea::onPinchUpdated()
203 This handler is called when the pinch area detects that a pinch gesture has changed.
205 The \l {PinchEvent}{pinch} parameter provides information about the pinch gesture,
206 including the scale, center and angle of the pinch.
210 \qmlsignal QtQuick2::PinchArea::onPinchFinished()
212 This handler is called when the pinch area detects that a pinch gesture has finished.
214 The \l {PinchEvent}{pinch} parameter provides information about the pinch gesture,
215 including the scale, center and angle of the pinch.
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
232 \c pinch provides a convenient way to make an item react to pinch gestures.
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.
244 QQuickPinchArea::QQuickPinchArea(QQuickItem *parent)
245 : QQuickItem(*(new QQuickPinchAreaPrivate), parent)
247 Q_D(QQuickPinchArea);
251 QQuickPinchArea::~QQuickPinchArea()
255 \qmlproperty bool QtQuick2::PinchArea::enabled
256 This property holds whether the item accepts pinch gestures.
258 This property defaults to true.
260 bool QQuickPinchArea::isEnabled() const
262 Q_D(const QQuickPinchArea);
266 void QQuickPinchArea::setEnabled(bool a)
268 Q_D(QQuickPinchArea);
269 if (a != d->absorb) {
271 emit enabledChanged();
275 void QQuickPinchArea::touchEvent(QTouchEvent *event)
277 Q_D(QQuickPinchArea);
278 if (!d->absorb || !isVisible()) {
279 QQuickItem::event(event);
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);
294 case QEvent::TouchEnd:
295 d->touchPoints.clear();
299 QQuickItem::event(event);
303 void QQuickPinchArea::updatePinch()
305 Q_D(QQuickPinchArea);
306 if (d->touchPoints.count() == 0) {
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);
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)
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;
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;
358 sceneCenter = d->sceneLastCenter + touchPoint2.scenePos() - d->lastPoint2;
359 angle = d->pinchLastAngle;
361 d->id1 = touchPoint1.id();
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;
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);
394 d->stealMouse = true;
395 QQuickCanvas *c = canvas();
396 if (c && c->mouseGrabberItem() != this)
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);
406 d->pinchRejected = true;
409 } else if (d->pinchStartDist > 0) {
410 qreal scale = dist ? dist / d->pinchStartDist : d->pinchLastScale;
411 qreal da = d->pinchLastAngle - angle;
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) {
441 if (x < pinch()->xmin())
443 else if (x > pinch()->xmax())
445 pinch()->target()->setX(x);
447 if (pinch()->axis() & QQuickPinch::YAxis) {
449 if (y < pinch()->ymin())
451 else if (y > pinch()->ymax())
453 pinch()->target()->setY(y);
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);
466 void QQuickPinchArea::mousePressEvent(QMouseEvent *event)
468 Q_D(QQuickPinchArea);
469 d->stealMouse = false;
471 QQuickItem::mousePressEvent(event);
473 setKeepMouseGrab(false);
474 event->setAccepted(true);
478 void QQuickPinchArea::mouseMoveEvent(QMouseEvent *event)
480 Q_D(QQuickPinchArea);
482 QQuickItem::mouseMoveEvent(event);
487 void QQuickPinchArea::mouseReleaseEvent(QMouseEvent *event)
489 Q_D(QQuickPinchArea);
490 d->stealMouse = false;
492 QQuickItem::mouseReleaseEvent(event);
494 QQuickCanvas *c = canvas();
495 if (c && c->mouseGrabberItem() == this)
497 setKeepMouseGrab(false);
501 void QQuickPinchArea::mouseUngrabEvent()
503 setKeepMouseGrab(false);
506 bool QQuickPinchArea::sendMouseEvent(QMouseEvent *event)
508 Q_D(QQuickPinchArea);
509 QRectF myRect = mapRectToScene(QRectF(0, 0, width(), height()));
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);
519 switch (mouseEvent.type()) {
520 case QEvent::MouseMove:
521 mouseMoveEvent(&mouseEvent);
523 case QEvent::MouseButtonPress:
524 mousePressEvent(&mouseEvent);
526 case QEvent::MouseButtonRelease:
527 mouseReleaseEvent(&mouseEvent);
532 grabber = c->mouseGrabberItem();
533 if (grabber && stealThisEvent && !grabber->keepMouseGrab() && grabber != this)
536 return stealThisEvent;
538 if (event->type() == QEvent::MouseButtonRelease) {
539 d->stealMouse = false;
540 if (c && c->mouseGrabberItem() == this)
542 setKeepMouseGrab(false);
547 bool QQuickPinchArea::childMouseEventFilter(QQuickItem *i, QEvent *e)
549 Q_D(QQuickPinchArea);
550 if (!d->absorb || !isVisible())
551 return QQuickItem::childMouseEventFilter(i, e);
553 case QEvent::MouseButtonPress:
554 case QEvent::MouseMove:
555 case QEvent::MouseButtonRelease:
556 return sendMouseEvent(static_cast<QMouseEvent *>(e));
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);
568 case QEvent::TouchEnd:
569 d->touchPoints.clear();
576 return QQuickItem::childMouseEventFilter(i, e);
579 void QQuickPinchArea::geometryChanged(const QRectF &newGeometry,
580 const QRectF &oldGeometry)
582 QQuickItem::geometryChanged(newGeometry, oldGeometry);
585 void QQuickPinchArea::itemChange(ItemChange change, const ItemChangeData &value)
587 QQuickItem::itemChange(change, value);
590 QQuickPinch *QQuickPinchArea::pinch()
592 Q_D(QQuickPinchArea);
594 d->pinch = new QQuickPinch;