/**************************************************************************** | |
** | |
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). | |
** All rights reserved. | |
** Contact: Nokia Corporation (qt-info@nokia.com) | |
** | |
** This file is part of the QtDeclarative module of the Qt Toolkit. | |
** | |
** $QT_BEGIN_LICENSE:LGPL$ | |
** GNU Lesser General Public License Usage | |
** This file may be used under the terms of the GNU Lesser General Public | |
** License version 2.1 as published by the Free Software Foundation and | |
** appearing in the file LICENSE.LGPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU Lesser | |
** General Public License version 2.1 requirements will be met: | |
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. | |
** | |
** In addition, as a special exception, Nokia gives you certain additional | |
** rights. These rights are described in the Nokia Qt LGPL Exception | |
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. | |
** | |
** GNU General Public License Usage | |
** Alternatively, this file may be used under the terms of the GNU General | |
** Public License version 3.0 as published by the Free Software Foundation | |
** and appearing in the file LICENSE.GPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU General | |
** Public License version 3.0 requirements will be met: | |
** http://www.gnu.org/copyleft/gpl.html. | |
** | |
** Other Usage | |
** Alternatively, this file may be used in accordance with the terms and | |
** conditions contained in a signed written agreement between you and Nokia. | |
** | |
** | |
** | |
** | |
** | |
** $QT_END_LICENSE$ | |
** | |
****************************************************************************/ | |
#include "qdeclarativepincharea_p.h" | |
#include "qdeclarativepincharea_p_p.h" | |
#include <QApplication> | |
#include <QGraphicsScene> | |
#include <float.h> | |
#include <math.h> | |
QT_BEGIN_NAMESPACE | |
/*! | |
\qmlclass PinchEvent QDeclarativePinchEvent | |
\ingroup qml-event-elements | |
\brief The PinchEvent object provides information about a pinch event. | |
\bold {The PinchEvent element was added in QtQuick 1.1} | |
The \c center, \c startCenter, \c previousCenter properties provide the center position between the two touch points. | |
The \c scale and \c previousScale properties provide the scale factor. | |
The \c angle, \c previousAngle and \c rotation properties provide the angle between the two points and the amount of rotation. | |
The \c point1, \c point2, \c startPoint1, \c startPoint2 properties provide the positions of the touch points. | |
The \c accepted property may be set to false in the \c onPinchStarted handler if the gesture should not | |
be handled. | |
\sa PinchArea | |
*/ | |
/*! | |
\qmlproperty QPointF PinchEvent::center | |
\qmlproperty QPointF PinchEvent::startCenter | |
\qmlproperty QPointF PinchEvent::previousCenter | |
These properties hold the position of the center point between the two touch points. | |
\list | |
\o \c center is the current center point | |
\o \c previousCenter is the center point of the previous event. | |
\o \c startCenter is the center point when the gesture began | |
\endlist | |
*/ | |
/*! | |
\qmlproperty real PinchEvent::scale | |
\qmlproperty real PinchEvent::previousScale | |
These properties hold the scale factor determined by the change in distance between the two touch points. | |
\list | |
\o \c scale is the current scale factor. | |
\o \c previousScale is the scale factor of the previous event. | |
\endlist | |
When a pinch gesture is started, the scale is 1.0. | |
*/ | |
/*! | |
\qmlproperty real PinchEvent::angle | |
\qmlproperty real PinchEvent::previousAngle | |
\qmlproperty real PinchEvent::rotation | |
These properties hold the angle between the two touch points. | |
\list | |
\o \c angle is the current angle between the two points in the range -180 to 180. | |
\o \c previousAngle is the angle of the previous event. | |
\o \c rotation is the total rotation since the pinch gesture started. | |
\endlist | |
When a pinch gesture is started, the rotation is 0.0. | |
*/ | |
/*! | |
\qmlproperty QPointF PinchEvent::point1 | |
\qmlproperty QPointF PinchEvent::startPoint1 | |
\qmlproperty QPointF PinchEvent::point2 | |
\qmlproperty QPointF PinchEvent::startPoint2 | |
These properties provide the actual touch points generating the pinch. | |
\list | |
\o \c point1 and \c point2 hold the current positions of the points. | |
\o \c startPoint1 and \c startPoint2 hold the positions of the points when the second point was touched. | |
\endlist | |
*/ | |
/*! | |
\qmlproperty bool PinchEvent::accepted | |
Setting this property to false in the \c PinchArea::onPinchStarted handler | |
will result in no further pinch events being generated, and the gesture | |
ignored. | |
*/ | |
/*! | |
\qmlproperty int PinchEvent::pointCount | |
Holds the number of points currently touched. The PinchArea will not react | |
until two touch points have initited a gesture, but will remain active until | |
all touch points have been released. | |
*/ | |
QDeclarativePinch::QDeclarativePinch() | |
: m_target(0), m_minScale(1.0), m_maxScale(1.0) | |
, m_minRotation(0.0), m_maxRotation(0.0) | |
, m_axis(NoDrag), m_xmin(-FLT_MAX), m_xmax(FLT_MAX) | |
, m_ymin(-FLT_MAX), m_ymax(FLT_MAX), m_active(false) | |
{ | |
} | |
QDeclarativePinchAreaPrivate::~QDeclarativePinchAreaPrivate() | |
{ | |
delete pinch; | |
} | |
/*! | |
\qmlclass PinchArea QDeclarativePinchArea | |
\brief The PinchArea item enables simple pinch gesture handling. | |
\inherits Item | |
\bold {The PinchArea element was added in QtQuick 1.1} | |
A PinchArea is an invisible item that is typically used in conjunction with | |
a visible item in order to provide pinch gesture handling for that item. | |
The \l enabled property is used to enable and disable pinch handling for | |
the proxied item. When disabled, the pinch area becomes transparent to | |
mouse/touch events. | |
PinchArea can be used in two ways: | |
\list | |
\o setting a \c pinch.target to provide automatic interaction with an element | |
\o using the onPinchStarted, onPinchUpdated and onPinchFinished handlers | |
\endlist | |
\sa PinchEvent | |
*/ | |
/*! | |
\qmlsignal PinchArea::onPinchStarted() | |
This handler is called when the pinch area detects that a pinch gesture has started. | |
The \l {PinchEvent}{pinch} parameter provides information about the pinch gesture, | |
including the scale, center and angle of the pinch. | |
To ignore this gesture set the \c pinch.accepted property to false. The gesture | |
will be cancelled and no further events will be sent. | |
*/ | |
/*! | |
\qmlsignal PinchArea::onPinchUpdated() | |
This handler is called when the pinch area detects that a pinch gesture has changed. | |
The \l {PinchEvent}{pinch} parameter provides information about the pinch gesture, | |
including the scale, center and angle of the pinch. | |
*/ | |
/*! | |
\qmlsignal PinchArea::onPinchFinished() | |
This handler is called when the pinch area detects that a pinch gesture has finished. | |
The \l {PinchEvent}{pinch} parameter provides information about the pinch gesture, | |
including the scale, center and angle of the pinch. | |
*/ | |
/*! | |
\qmlproperty Item PinchArea::pinch.target | |
\qmlproperty bool PinchArea::pinch.active | |
\qmlproperty real PinchArea::pinch.minimumScale | |
\qmlproperty real PinchArea::pinch.maximumScale | |
\qmlproperty real PinchArea::pinch.minimumRotation | |
\qmlproperty real PinchArea::pinch.maximumRotation | |
\qmlproperty enumeration PinchArea::pinch.dragAxis | |
\qmlproperty real PinchArea::pinch.minimumX | |
\qmlproperty real PinchArea::pinch.maximumX | |
\qmlproperty real PinchArea::pinch.minimumY | |
\qmlproperty real PinchArea::pinch.maximumY | |
\c pinch provides a convenient way to make an item react to pinch gestures. | |
\list | |
\i \c pinch.target specifies the id of the item to drag. | |
\i \c pinch.active specifies if the target item is currently being dragged. | |
\i \c pinch.minimumScale and \c pinch.maximumScale limit the range of the Item::scale property. | |
\i \c pinch.minimumRotation and \c pinch.maximumRotation limit the range of the Item::rotation property. | |
\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) | |
\i \c pinch.minimum and \c pinch.maximum limit how far the target can be dragged along the corresponding axes. | |
\endlist | |
*/ | |
QDeclarativePinchArea::QDeclarativePinchArea(QDeclarativeItem *parent) | |
: QDeclarativeItem(*(new QDeclarativePinchAreaPrivate), parent) | |
{ | |
Q_D(QDeclarativePinchArea); | |
d->init(); | |
} | |
QDeclarativePinchArea::~QDeclarativePinchArea() | |
{ | |
} | |
/*! | |
\qmlproperty bool PinchArea::enabled | |
This property holds whether the item accepts pinch gestures. | |
This property defaults to true. | |
*/ | |
bool QDeclarativePinchArea::isEnabled() const | |
{ | |
Q_D(const QDeclarativePinchArea); | |
return d->absorb; | |
} | |
void QDeclarativePinchArea::setEnabled(bool a) | |
{ | |
Q_D(QDeclarativePinchArea); | |
if (a != d->absorb) { | |
d->absorb = a; | |
emit enabledChanged(); | |
} | |
} | |
bool QDeclarativePinchArea::event(QEvent *event) | |
{ | |
Q_D(QDeclarativePinchArea); | |
if (!d->absorb || !isVisible()) | |
return QDeclarativeItem::event(event); | |
switch (event->type()) { | |
case QEvent::TouchBegin: | |
case QEvent::TouchUpdate: { | |
QTouchEvent *touch = static_cast<QTouchEvent*>(event); | |
d->touchPoints.clear(); | |
for (int i = 0; i < touch->touchPoints().count(); ++i) { | |
if (!(touch->touchPoints().at(i).state() & Qt::TouchPointReleased)) { | |
d->touchPoints << touch->touchPoints().at(i); | |
} | |
} | |
updatePinch(); | |
} | |
return true; | |
case QEvent::TouchEnd: | |
d->touchPoints.clear(); | |
updatePinch(); | |
break; | |
default: | |
return QDeclarativeItem::event(event); | |
} | |
return QDeclarativeItem::event(event); | |
} | |
void QDeclarativePinchArea::updatePinch() | |
{ | |
Q_D(QDeclarativePinchArea); | |
if (d->touchPoints.count() == 0) { | |
if (d->inPinch) { | |
d->stealMouse = false; | |
setKeepMouseGrab(false); | |
d->inPinch = false; | |
QPointF pinchCenter = mapFromScene(d->sceneLastCenter); | |
QDeclarativePinchEvent pe(pinchCenter, d->pinchLastScale, d->pinchLastAngle, d->pinchRotation); | |
pe.setStartCenter(d->pinchStartCenter); | |
pe.setPreviousCenter(pinchCenter); | |
pe.setPreviousAngle(d->pinchLastAngle); | |
pe.setPreviousScale(d->pinchLastScale); | |
pe.setStartPoint1(mapFromScene(d->sceneStartPoint1)); | |
pe.setStartPoint2(mapFromScene(d->sceneStartPoint2)); | |
pe.setPoint1(mapFromScene(d->lastPoint1)); | |
pe.setPoint2(mapFromScene(d->lastPoint2)); | |
emit pinchFinished(&pe); | |
d->pinchStartDist = 0; | |
d->pinchActivated = false; | |
if (d->pinch && d->pinch->target()) | |
d->pinch->setActive(false); | |
} | |
return; | |
} | |
QTouchEvent::TouchPoint touchPoint1 = d->touchPoints.at(0); | |
QTouchEvent::TouchPoint touchPoint2 = d->touchPoints.at(d->touchPoints. count() >= 2 ? 1 : 0); | |
if (d->touchPoints.count() == 2 | |
&& (touchPoint1.state() & Qt::TouchPointPressed || touchPoint2.state() & Qt::TouchPointPressed)) { | |
d->id1 = touchPoint1.id(); | |
d->sceneStartPoint1 = touchPoint1.scenePos(); | |
d->sceneStartPoint2 = touchPoint2.scenePos(); | |
d->inPinch = false; | |
d->pinchRejected = false; | |
d->pinchActivated = true; | |
} else if (d->pinchActivated && !d->pinchRejected) { | |
const int dragThreshold = QApplication::startDragDistance(); | |
QPointF p1 = touchPoint1.scenePos(); | |
QPointF p2 = touchPoint2.scenePos(); | |
qreal dx = p1.x() - p2.x(); | |
qreal dy = p1.y() - p2.y(); | |
qreal dist = sqrt(dx*dx + dy*dy); | |
QPointF sceneCenter = (p1 + p2)/2; | |
qreal angle = QLineF(p1, p2).angle(); | |
if (d->touchPoints.count() == 1) { | |
// If we only have one point then just move the center | |
if (d->id1 == touchPoint1.id()) | |
sceneCenter = d->sceneLastCenter + touchPoint1.scenePos() - d->lastPoint1; | |
else | |
sceneCenter = d->sceneLastCenter + touchPoint2.scenePos() - d->lastPoint2; | |
angle = d->pinchLastAngle; | |
} | |
d->id1 = touchPoint1.id(); | |
if (angle > 180) | |
angle -= 360; | |
if (!d->inPinch) { | |
if (d->touchPoints.count() >= 2 | |
&& (qAbs(p1.x()-d->sceneStartPoint1.x()) > dragThreshold | |
|| qAbs(p1.y()-d->sceneStartPoint1.y()) > dragThreshold | |
|| qAbs(p2.x()-d->sceneStartPoint2.x()) > dragThreshold | |
|| qAbs(p2.y()-d->sceneStartPoint2.y()) > dragThreshold)) { | |
d->sceneStartCenter = sceneCenter; | |
d->sceneLastCenter = sceneCenter; | |
d->pinchStartCenter = mapFromScene(sceneCenter); | |
d->pinchStartDist = dist; | |
d->pinchStartAngle = angle; | |
d->pinchLastScale = 1.0; | |
d->pinchLastAngle = angle; | |
d->pinchRotation = 0.0; | |
d->lastPoint1 = p1; | |
d->lastPoint2 = p2; | |
QDeclarativePinchEvent pe(d->pinchStartCenter, 1.0, angle, 0.0); | |
pe.setStartCenter(d->pinchStartCenter); | |
pe.setPreviousCenter(d->pinchStartCenter); | |
pe.setPreviousAngle(d->pinchLastAngle); | |
pe.setPreviousScale(d->pinchLastScale); | |
pe.setStartPoint1(mapFromScene(d->sceneStartPoint1)); | |
pe.setStartPoint2(mapFromScene(d->sceneStartPoint2)); | |
pe.setPoint1(mapFromScene(d->lastPoint1)); | |
pe.setPoint2(mapFromScene(d->lastPoint2)); | |
pe.setPointCount(d->touchPoints.count()); | |
emit pinchStarted(&pe); | |
if (pe.accepted()) { | |
d->inPinch = true; | |
d->stealMouse = true; | |
QGraphicsScene *s = scene(); | |
if (s && s->mouseGrabberItem() != this) | |
grabMouse(); | |
setKeepMouseGrab(true); | |
if (d->pinch && d->pinch->target()) { | |
d->pinchStartPos = pinch()->target()->pos(); | |
d->pinchStartScale = d->pinch->target()->scale(); | |
d->pinchStartRotation = d->pinch->target()->rotation(); | |
d->pinch->setActive(true); | |
} | |
} else { | |
d->pinchRejected = true; | |
} | |
} | |
} else if (d->pinchStartDist > 0) { | |
qreal scale = dist ? dist / d->pinchStartDist : d->pinchLastScale; | |
qreal da = d->pinchLastAngle - angle; | |
if (da > 180) | |
da -= 360; | |
else if (da < -180) | |
da += 360; | |
d->pinchRotation += da; | |
QPointF pinchCenter = mapFromScene(sceneCenter); | |
QDeclarativePinchEvent pe(pinchCenter, scale, angle, d->pinchRotation); | |
pe.setStartCenter(d->pinchStartCenter); | |
pe.setPreviousCenter(mapFromScene(d->sceneLastCenter)); | |
pe.setPreviousAngle(d->pinchLastAngle); | |
pe.setPreviousScale(d->pinchLastScale); | |
pe.setStartPoint1(mapFromScene(d->sceneStartPoint1)); | |
pe.setStartPoint2(mapFromScene(d->sceneStartPoint2)); | |
pe.setPoint1(touchPoint1.pos()); | |
pe.setPoint2(touchPoint2.pos()); | |
pe.setPointCount(d->touchPoints.count()); | |
d->pinchLastScale = scale; | |
d->sceneLastCenter = sceneCenter; | |
d->pinchLastAngle = angle; | |
d->lastPoint1 = touchPoint1.scenePos(); | |
d->lastPoint2 = touchPoint2.scenePos(); | |
emit pinchUpdated(&pe); | |
if (d->pinch && d->pinch->target()) { | |
qreal s = d->pinchStartScale * scale; | |
s = qMin(qMax(pinch()->minimumScale(),s), pinch()->maximumScale()); | |
pinch()->target()->setScale(s); | |
QPointF pos = sceneCenter - d->sceneStartCenter + d->pinchStartPos; | |
if (pinch()->axis() & QDeclarativePinch::XAxis) { | |
qreal x = pos.x(); | |
if (x < pinch()->xmin()) | |
x = pinch()->xmin(); | |
else if (x > pinch()->xmax()) | |
x = pinch()->xmax(); | |
pinch()->target()->setX(x); | |
} | |
if (pinch()->axis() & QDeclarativePinch::YAxis) { | |
qreal y = pos.y(); | |
if (y < pinch()->ymin()) | |
y = pinch()->ymin(); | |
else if (y > pinch()->ymax()) | |
y = pinch()->ymax(); | |
pinch()->target()->setY(y); | |
} | |
if (d->pinchStartRotation >= pinch()->minimumRotation() | |
&& d->pinchStartRotation <= pinch()->maximumRotation()) { | |
qreal r = d->pinchRotation + d->pinchStartRotation; | |
r = qMin(qMax(pinch()->minimumRotation(),r), pinch()->maximumRotation()); | |
pinch()->target()->setRotation(r); | |
} | |
} | |
} | |
} | |
} | |
void QDeclarativePinchArea::mousePressEvent(QGraphicsSceneMouseEvent *event) | |
{ | |
Q_D(QDeclarativePinchArea); | |
d->stealMouse = false; | |
if (!d->absorb) | |
QDeclarativeItem::mousePressEvent(event); | |
else { | |
setKeepMouseGrab(false); | |
event->setAccepted(true); | |
} | |
} | |
void QDeclarativePinchArea::mouseMoveEvent(QGraphicsSceneMouseEvent *event) | |
{ | |
Q_D(QDeclarativePinchArea); | |
if (!d->absorb) { | |
QDeclarativeItem::mouseMoveEvent(event); | |
return; | |
} | |
} | |
void QDeclarativePinchArea::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) | |
{ | |
Q_D(QDeclarativePinchArea); | |
d->stealMouse = false; | |
if (!d->absorb) { | |
QDeclarativeItem::mouseReleaseEvent(event); | |
} else { | |
QGraphicsScene *s = scene(); | |
if (s && s->mouseGrabberItem() == this) | |
ungrabMouse(); | |
setKeepMouseGrab(false); | |
} | |
} | |
bool QDeclarativePinchArea::sceneEvent(QEvent *event) | |
{ | |
bool rv = QDeclarativeItem::sceneEvent(event); | |
if (event->type() == QEvent::UngrabMouse) { | |
setKeepMouseGrab(false); | |
} | |
return rv; | |
} | |
bool QDeclarativePinchArea::sendMouseEvent(QGraphicsSceneMouseEvent *event) | |
{ | |
Q_D(QDeclarativePinchArea); | |
QGraphicsSceneMouseEvent mouseEvent(event->type()); | |
QRectF myRect = mapToScene(QRectF(0, 0, width(), height())).boundingRect(); | |
QGraphicsScene *s = scene(); | |
QDeclarativeItem *grabber = s ? qobject_cast<QDeclarativeItem*>(s->mouseGrabberItem()) : 0; | |
bool stealThisEvent = d->stealMouse; | |
if ((stealThisEvent || myRect.contains(event->scenePos().toPoint())) && (!grabber || !grabber->keepMouseGrab())) { | |
mouseEvent.setAccepted(false); | |
for (int i = 0x1; i <= 0x10; i <<= 1) { | |
if (event->buttons() & i) { | |
Qt::MouseButton button = Qt::MouseButton(i); | |
mouseEvent.setButtonDownPos(button, mapFromScene(event->buttonDownPos(button))); | |
} | |
} | |
mouseEvent.setScenePos(event->scenePos()); | |
mouseEvent.setLastScenePos(event->lastScenePos()); | |
mouseEvent.setPos(mapFromScene(event->scenePos())); | |
mouseEvent.setLastPos(mapFromScene(event->lastScenePos())); | |
switch(mouseEvent.type()) { | |
case QEvent::GraphicsSceneMouseMove: | |
mouseMoveEvent(&mouseEvent); | |
break; | |
case QEvent::GraphicsSceneMousePress: | |
mousePressEvent(&mouseEvent); | |
break; | |
case QEvent::GraphicsSceneMouseRelease: | |
mouseReleaseEvent(&mouseEvent); | |
break; | |
default: | |
break; | |
} | |
grabber = qobject_cast<QDeclarativeItem*>(s->mouseGrabberItem()); | |
if (grabber && stealThisEvent && !grabber->keepMouseGrab() && grabber != this) | |
grabMouse(); | |
return stealThisEvent; | |
} | |
if (mouseEvent.type() == QEvent::GraphicsSceneMouseRelease) { | |
d->stealMouse = false; | |
if (s && s->mouseGrabberItem() == this) | |
ungrabMouse(); | |
setKeepMouseGrab(false); | |
} | |
return false; | |
} | |
bool QDeclarativePinchArea::sceneEventFilter(QGraphicsItem *i, QEvent *e) | |
{ | |
Q_D(QDeclarativePinchArea); | |
if (!d->absorb || !isVisible()) | |
return QDeclarativeItem::sceneEventFilter(i, e); | |
switch (e->type()) { | |
case QEvent::GraphicsSceneMousePress: | |
case QEvent::GraphicsSceneMouseMove: | |
case QEvent::GraphicsSceneMouseRelease: | |
return sendMouseEvent(static_cast<QGraphicsSceneMouseEvent *>(e)); | |
break; | |
case QEvent::TouchBegin: | |
case QEvent::TouchUpdate: { | |
QTouchEvent *touch = static_cast<QTouchEvent*>(e); | |
d->touchPoints.clear(); | |
for (int i = 0; i < touch->touchPoints().count(); ++i) | |
if (!(touch->touchPoints().at(i).state() & Qt::TouchPointReleased)) | |
d->touchPoints << touch->touchPoints().at(i); | |
updatePinch(); | |
} | |
return d->inPinch; | |
case QEvent::TouchEnd: | |
d->touchPoints.clear(); | |
updatePinch(); | |
break; | |
default: | |
break; | |
} | |
return QDeclarativeItem::sceneEventFilter(i, e); | |
} | |
void QDeclarativePinchArea::geometryChanged(const QRectF &newGeometry, | |
const QRectF &oldGeometry) | |
{ | |
QDeclarativeItem::geometryChanged(newGeometry, oldGeometry); | |
} | |
QVariant QDeclarativePinchArea::itemChange(GraphicsItemChange change, | |
const QVariant &value) | |
{ | |
return QDeclarativeItem::itemChange(change, value); | |
} | |
QDeclarativePinch *QDeclarativePinchArea::pinch() | |
{ | |
Q_D(QDeclarativePinchArea); | |
if (!d->pinch) | |
d->pinch = new QDeclarativePinch; | |
return d->pinch; | |
} | |
QT_END_NAMESPACE |