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
\b {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
\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
\brief The PinchArea item enables simple pinch gesture handling.
169
\b {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
\li setting a \c pinch.target to provide automatic interaction with an element
182
\li 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
\li \c pinch.target specifies the id of the item to drag.
236
\li \c pinch.active specifies if the target item is currently being dragged.
237
\li \c pinch.minimumScale and \c pinch.maximumScale limit the range of the Item::scale property.
238
\li \c pinch.minimumRotation and \c pinch.maximumRotation limit the range of the Item::rotation property.
239
\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)
240
\li \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);
331
QTouchEvent::TouchPoint touchPoint1 = d->touchPoints.at(0);
332
QTouchEvent::TouchPoint touchPoint2 = d->touchPoints.at(d->touchPoints. count() >= 2 ? 1 : 0);
333
if (d->touchPoints.count() == 2
334
&& (touchPoint1.state() & Qt::TouchPointPressed || touchPoint2.state() & Qt::TouchPointPressed)) {
335
d->id1 = touchPoint1.id();
336
d->sceneStartPoint1 = touchPoint1.scenePos();
337
d->sceneStartPoint2 = touchPoint2.scenePos();
338
d->pinchActivated = true;
341
if (d->pinchActivated && !d->pinchRejected){
342
const int dragThreshold = qApp->styleHints()->startDragDistance();
343
QPointF p1 = touchPoint1.scenePos();
344
QPointF p2 = touchPoint2.scenePos();
345
qreal dx = p1.x() - p2.x();
346
qreal dy = p1.y() - p2.y();
347
qreal dist = sqrt(dx*dx + dy*dy);
348
QPointF sceneCenter = (p1 + p2)/2;
349
qreal angle = QLineF(p1, p2).angle();
350
if (d->touchPoints.count() == 1) {
351
// If we only have one point then just move the center
352
if (d->id1 == touchPoint1.id())
353
sceneCenter = d->sceneLastCenter + touchPoint1.scenePos() - d->lastPoint1;
355
sceneCenter = d->sceneLastCenter + touchPoint2.scenePos() - d->lastPoint2;
356
angle = d->pinchLastAngle;
358
d->id1 = touchPoint1.id();
361
if (!d->inPinch || d->initPinch) {
362
if (d->touchPoints.count() >= 2
363
&& (qAbs(p1.x()-d->sceneStartPoint1.x()) > dragThreshold
364
|| qAbs(p1.y()-d->sceneStartPoint1.y()) > dragThreshold
365
|| qAbs(p2.x()-d->sceneStartPoint2.x()) > dragThreshold
366
|| qAbs(p2.y()-d->sceneStartPoint2.y()) > dragThreshold)) {
367
d->initPinch = false;
368
d->sceneStartCenter = sceneCenter;
369
d->sceneLastCenter = sceneCenter;
370
d->pinchStartCenter = mapFromScene(sceneCenter);
371
d->pinchStartDist = dist;
372
d->pinchStartAngle = angle;
373
d->pinchLastScale = 1.0;
374
d->pinchLastAngle = angle;
375
d->pinchRotation = 0.0;
378
QQuickPinchEvent pe(d->pinchStartCenter, 1.0, angle, 0.0);
379
pe.setStartCenter(d->pinchStartCenter);
380
pe.setPreviousCenter(d->pinchStartCenter);
381
pe.setPreviousAngle(d->pinchLastAngle);
382
pe.setPreviousScale(d->pinchLastScale);
383
pe.setStartPoint1(mapFromScene(d->sceneStartPoint1));
384
pe.setStartPoint2(mapFromScene(d->sceneStartPoint2));
385
pe.setPoint1(mapFromScene(d->lastPoint1));
386
pe.setPoint2(mapFromScene(d->lastPoint2));
387
pe.setPointCount(d->touchPoints.count());
388
emit pinchStarted(&pe);
391
d->stealMouse = true;
392
QQuickCanvas *c = canvas();
393
if (c && c->mouseGrabberItem() != this)
395
setKeepMouseGrab(true);
396
if (d->pinch && d->pinch->target()) {
397
d->pinchStartPos = pinch()->target()->pos();
398
d->pinchStartScale = d->pinch->target()->scale();
399
d->pinchStartRotation = d->pinch->target()->rotation();
400
d->pinch->setActive(true);
403
d->pinchRejected = true;
406
} else if (d->pinchStartDist > 0) {
407
qreal scale = dist ? dist / d->pinchStartDist : d->pinchLastScale;
408
qreal da = d->pinchLastAngle - angle;
413
d->pinchRotation += da;
414
QPointF pinchCenter = mapFromScene(sceneCenter);
415
QQuickPinchEvent pe(pinchCenter, scale, angle, d->pinchRotation);
416
pe.setStartCenter(d->pinchStartCenter);
417
pe.setPreviousCenter(mapFromScene(d->sceneLastCenter));
418
pe.setPreviousAngle(d->pinchLastAngle);
419
pe.setPreviousScale(d->pinchLastScale);
420
pe.setStartPoint1(mapFromScene(d->sceneStartPoint1));
421
pe.setStartPoint2(mapFromScene(d->sceneStartPoint2));
422
pe.setPoint1(touchPoint1.pos());
423
pe.setPoint2(touchPoint2.pos());
424
pe.setPointCount(d->touchPoints.count());
425
d->pinchLastScale = scale;
426
d->sceneLastCenter = sceneCenter;
427
d->pinchLastAngle = angle;
428
d->lastPoint1 = touchPoint1.scenePos();
429
d->lastPoint2 = touchPoint2.scenePos();
430
emit pinchUpdated(&pe);
431
if (d->pinch && d->pinch->target()) {
432
qreal s = d->pinchStartScale * scale;
433
s = qMin(qMax(pinch()->minimumScale(),s), pinch()->maximumScale());
434
pinch()->target()->setScale(s);
435
QPointF pos = sceneCenter - d->sceneStartCenter + d->pinchStartPos;
436
if (pinch()->axis() & QQuickPinch::XAxis) {
438
if (x < pinch()->xmin())
440
else if (x > pinch()->xmax())
442
pinch()->target()->setX(x);
444
if (pinch()->axis() & QQuickPinch::YAxis) {
446
if (y < pinch()->ymin())
448
else if (y > pinch()->ymax())
450
pinch()->target()->setY(y);
452
if (d->pinchStartRotation >= pinch()->minimumRotation()
453
&& d->pinchStartRotation <= pinch()->maximumRotation()) {
454
qreal r = d->pinchRotation + d->pinchStartRotation;
455
r = qMin(qMax(pinch()->minimumRotation(),r), pinch()->maximumRotation());
456
pinch()->target()->setRotation(r);
463
void QQuickPinchArea::mousePressEvent(QMouseEvent *event)
465
Q_D(QQuickPinchArea);
466
d->stealMouse = false;
468
QQuickItem::mousePressEvent(event);
470
setKeepMouseGrab(false);
471
event->setAccepted(true);
475
void QQuickPinchArea::mouseMoveEvent(QMouseEvent *event)
477
Q_D(QQuickPinchArea);
479
QQuickItem::mouseMoveEvent(event);
484
void QQuickPinchArea::mouseReleaseEvent(QMouseEvent *event)
486
Q_D(QQuickPinchArea);
487
d->stealMouse = false;
489
QQuickItem::mouseReleaseEvent(event);
491
QQuickCanvas *c = canvas();
492
if (c && c->mouseGrabberItem() == this)
494
setKeepMouseGrab(false);
498
void QQuickPinchArea::mouseUngrabEvent()
500
setKeepMouseGrab(false);
503
bool QQuickPinchArea::sendMouseEvent(QMouseEvent *event)
505
Q_D(QQuickPinchArea);
506
QRectF myRect = mapRectToScene(QRectF(0, 0, width(), height()));
508
QQuickCanvas *c = canvas();
509
QQuickItem *grabber = c ? c->mouseGrabberItem() : 0;
510
bool stealThisEvent = d->stealMouse;
511
if ((stealThisEvent || myRect.contains(event->windowPos())) && (!grabber || !grabber->keepMouseGrab())) {
512
QMouseEvent mouseEvent(event->type(), mapFromScene(event->windowPos()), event->windowPos(), event->screenPos(),
513
event->button(), event->buttons(), event->modifiers());
514
mouseEvent.setAccepted(false);
516
switch (mouseEvent.type()) {
517
case QEvent::MouseMove:
518
mouseMoveEvent(&mouseEvent);
520
case QEvent::MouseButtonPress:
521
mousePressEvent(&mouseEvent);
523
case QEvent::MouseButtonRelease:
524
mouseReleaseEvent(&mouseEvent);
529
grabber = c->mouseGrabberItem();
530
if (grabber && stealThisEvent && !grabber->keepMouseGrab() && grabber != this)
533
return stealThisEvent;
535
if (event->type() == QEvent::MouseButtonRelease) {
536
d->stealMouse = false;
537
if (c && c->mouseGrabberItem() == this)
539
setKeepMouseGrab(false);
544
bool QQuickPinchArea::childMouseEventFilter(QQuickItem *i, QEvent *e)
546
Q_D(QQuickPinchArea);
547
if (!d->absorb || !isVisible())
548
return QQuickItem::childMouseEventFilter(i, e);
550
case QEvent::MouseButtonPress:
551
case QEvent::MouseMove:
552
case QEvent::MouseButtonRelease:
553
return sendMouseEvent(static_cast<QMouseEvent *>(e));
555
case QEvent::TouchBegin:
556
case QEvent::TouchUpdate: {
557
QTouchEvent *touch = static_cast<QTouchEvent*>(e);
558
d->touchPoints.clear();
559
for (int i = 0; i < touch->touchPoints().count(); ++i)
560
if (!(touch->touchPoints().at(i).state() & Qt::TouchPointReleased))
561
d->touchPoints << touch->touchPoints().at(i);
565
case QEvent::TouchEnd:
566
d->touchPoints.clear();
573
return QQuickItem::childMouseEventFilter(i, e);
576
void QQuickPinchArea::geometryChanged(const QRectF &newGeometry,
577
const QRectF &oldGeometry)
579
QQuickItem::geometryChanged(newGeometry, oldGeometry);
582
void QQuickPinchArea::itemChange(ItemChange change, const ItemChangeData &value)
584
QQuickItem::itemChange(change, value);
587
QQuickPinch *QQuickPinchArea::pinch()
589
Q_D(QQuickPinchArea);
591
d->pinch = new QQuickPinch;