/*
* Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 2.1 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not,
* see "http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html/".
*
* Description:
*
*/
/*
W A R N I N G
-------------
THIS IS A TEMPORARY GESTURE CODE. WOULD BE REPLACED WHEN BROWSER HAS ITS OWN GESTURE FRAMEWORK
*/
#include "GestureRecognizer.h"
#include "GestureListener.h"
#include "GestureRecognizer_p.h"
#include <QtGui>
static const int DoubleClickFilterDuration = 300;
static const int LongClickFilterDuration = 1000;
static const int MinTimeHoldForClick = 50;
static const int ThresholdForMove = 30;
namespace GVA {
GestureRecognizer::GestureRecognizer(GestureListener* gestureListener)
: d_ptr(new GestureRecognizerPrivate(gestureListener))
{
Q_D(GestureRecognizer);
d->q_ptr = this;
}
GestureRecognizer::~GestureRecognizer()
{ }
bool GestureRecognizer::mouseEventFilter(QGraphicsSceneMouseEvent* event)
{
Q_D(GestureRecognizer);
bool handled = false;
switch(event->type()) {
case QEvent::GraphicsSceneMouseDoubleClick:
handled = d->mouseDoubleClickEvent(event);
break;
case QEvent::GraphicsSceneMouseMove:
handled = d->mouseMoveEvent(event);
break;
case QEvent::GraphicsSceneMousePress:
handled = d->mousePressEvent(event);
break;
case QEvent::GraphicsSceneMouseRelease:
handled = d->mouseReleaseEvent(event);
break;
case QEvent::GraphicsSceneContextMenu:
//Swallow context menu event.
//Since we have own way of handling it
handled = true;
default:
break;
}
return handled;
}
qreal GestureRecognizer::dragInertia() const
{
Q_D(const GestureRecognizer);
return d->m_dragInertia;
}
void GestureRecognizer::setDragInertia(qreal inertia)
{
Q_D(GestureRecognizer);
d->m_dragInertia = inertia;
}
int GestureRecognizer::directionErrorMargin() const
{
Q_D(const GestureRecognizer);
return d->m_directionErrorMargin;
}
void GestureRecognizer::setDirectionErrorMargin(int errorMargin)
{
Q_D(GestureRecognizer);
d->m_directionErrorMargin = errorMargin;
}
qreal GestureRecognizer::axisLockThreshold() const
{
Q_D(const GestureRecognizer);
return d->m_axisLockThreshold;
}
void GestureRecognizer::setAxisLockThreshold(qreal threshold)
{
Q_D(GestureRecognizer);
d->m_axisLockThreshold = threshold;
}
qreal GestureRecognizer::maximumVelocity() const
{
Q_D(const GestureRecognizer);
return d->m_maxVelocity;
}
void GestureRecognizer::setMaximumVelocity(qreal v)
{
Q_D(GestureRecognizer);
d->m_maxVelocity = v;
}
qreal GestureRecognizer::minimumVelocity() const
{
Q_D(const GestureRecognizer);
return d->m_minVelocity;
}
void GestureRecognizer::setMinimumVelocity(qreal v)
{
Q_D(GestureRecognizer);
d->m_minVelocity = v;
}
int GestureRecognizer::panningThreshold() const
{
Q_D(const GestureRecognizer);
return d->m_panningThreshold;
}
void GestureRecognizer::setPanningThreshold(int threshold)
{
Q_D(GestureRecognizer);
d->m_panningThreshold = threshold;
}
qreal GestureRecognizer::fastVelocityFactor() const
{
Q_D(const GestureRecognizer);
return d->m_fastVelocityFactor;
}
void GestureRecognizer::setFastVelocityFactor(qreal v)
{
Q_D(GestureRecognizer);
d->m_fastVelocityFactor = v;
}
int GestureRecognizer::scrollsPerSecond() const
{
Q_D(const GestureRecognizer);
return d->m_fastVelocityFactor;
}
void GestureRecognizer::setScrollsPerSecond(int sps)
{
Q_D(GestureRecognizer);
d->m_scrollsPerSecond = sps;
}
//
//GestureRecognizerPrivate DIFINITION
//
GestureRecognizerPrivate::GestureRecognizerPrivate(GestureListener* gestureListener)
: m_gestureListener(gestureListener)
, m_state(GestureRecognizerPrivate::Inactive)
, m_dragInertia(0.85)
, m_directionErrorMargin(10)
, m_axisLockThreshold(0)
, m_maxVelocity(3500)
, m_minVelocity(10)
, m_panningThreshold(25)
, m_fastVelocityFactor(0.01)
, m_scrollsPerSecond(20)
, m_velocity(QPointF(0, 0))
, m_position(QPointF(-1, -1))
, m_initialPos(QPointF(-1, -1))
{ }
GestureRecognizerPrivate::~GestureRecognizerPrivate()
{ }
bool GestureRecognizerPrivate::mousePressEvent(QGraphicsSceneMouseEvent* event)
{
if (event->button() != Qt::LeftButton)
return false;
if (m_state == GestureRecognizerPrivate::Inactive) {
//First mouse press.
m_position = event->scenePos();
m_initialPos = m_position;
changeState(GestureRecognizerPrivate::Press);
GestureEvent gesture = gestureEvent(m_initialPos, GestureEvent::Touch);
m_gestureListener->handleGesture(&gesture);
m_lastTime.start();
m_delayedPressMoment.start();
m_timer.start(LongClickFilterDuration, this);
} else if(m_state == GestureRecognizerPrivate::Release) {
//This press is for double tap.
changeState(GestureRecognizerPrivate::DoublePress);
}
event->setAccepted(true);
return true;
}
bool GestureRecognizerPrivate::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
{
if (event->button() != Qt::LeftButton)
return true;
if (m_state == GestureRecognizerPrivate::Inactive) {
//Release event cannot be generated in gesture recognizer is in active!
event->setAccepted(true);
resetTouchPositions();
return true;
}
if (m_state == GestureRecognizerPrivate::Press) {
if (m_delayedPressMoment.elapsed() > MinTimeHoldForClick) {
//Waiting for MinTimeHoldForClick to make sure use has actually pressed.
//Removes accidentall press
changeState(GestureRecognizerPrivate::Release);
m_timer.start(DoubleClickFilterDuration, this);
} else
resetTouchPositions();
} else if (m_state == GestureRecognizerPrivate::Move) {
if (qAbs(m_velocity.x()) > m_minVelocity
|| qAbs(m_velocity.y()) > m_minVelocity) {
GestureEvent gesture = gestureEvent(m_position, GestureEvent::Flick);
gesture.setVelocity(m_velocity);
m_gestureListener->handleGesture(&gesture);
}
m_velocity = QPointF(0,0);
resetTouchPositions();
} else if (m_state == GestureRecognizerPrivate::DoublePress) {
//Stop double tap timer
m_timer.stop();
GestureEvent gesture = gestureEvent(m_initialPos, GestureEvent::DoubleTap);
m_gestureListener->handleGesture(&gesture);
resetTouchPositions();
}
event->setAccepted(true);
return true;
}
bool GestureRecognizerPrivate::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
{
QPoint delta;
if (m_state == GestureRecognizerPrivate::Press) {
delta = (event->scenePos() - m_initialPos).toPoint();
QPoint absDelta;
absDelta.setX(qAbs(delta.x()));
absDelta.setY(qAbs(delta.y()));
if ((absDelta.x() > ThresholdForMove) || (absDelta.y() > ThresholdForMove)) {
//Stop long tap timer
m_timer.stop();
changeState(GestureRecognizerPrivate::Move);
m_position = event->scenePos();
} else {
//Ignore until user has actually moved
return true;
}
} else if (m_state == GestureRecognizerPrivate::Move) {
delta = (event->scenePos() - m_position).toPoint();
m_position = event->scenePos();
} else {
resetTouchPositions();
return true;
}
//Aplly if axis stickiness is specified.
if (m_axisLockThreshold) {
int dx = qAbs(delta.x());
int dy = qAbs(delta.y());
if (dx || dy) {
bool vertical = (dy > dx);
qreal alpha = qreal(vertical ? dx : dy) / qreal(vertical ? dy : dx);
if (alpha <= m_axisLockThreshold) {
if (vertical)
delta.setX(0);
else
delta.setY(0);
}
}
}
m_velocity = calculateVelocity(delta, m_lastTime.elapsed());
//Send pan gesture
GestureEvent gesture = gestureEvent(m_position, GestureEvent::Pan);
gesture.setDelta(delta);
m_gestureListener->handleGesture(&gesture);
m_lastTime.restart();
event->setAccepted(true);
return true;
}
bool GestureRecognizerPrivate::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event)
{
if (m_state == GestureRecognizerPrivate::Release) {
m_timer.stop();
GestureEvent gesture = gestureEvent(m_initialPos, GestureEvent::DoubleTap);
m_gestureListener->handleGesture(&gesture);
}
resetTouchPositions();
event->setAccepted(true);
return true;
}
void GestureRecognizerPrivate::changeState(GestureRecognizerPrivate::State state)
{
m_state = state;
}
GestureEvent GestureRecognizerPrivate::gestureEvent(const QPointF& pos, GestureEvent::Type type)
{
GestureEvent gestureEvent;
gestureEvent.setType(type);
gestureEvent.setPosition(pos);
return gestureEvent;
}
void GestureRecognizerPrivate::timerEvent(QTimerEvent* event)
{
if (event->timerId() != m_timer.timerId())
return;
m_timer.stop();
QPointF position;
GestureEvent::Type gestureType;
if (m_state == GestureRecognizerPrivate::Press) {
//Long press
position = m_position;
gestureType = GestureEvent::LongTap;
} else if (m_state == GestureRecognizerPrivate::Release) {
//Actual release event
position = m_initialPos;
gestureType = GestureEvent::Release;
} else {
resetTouchPositions();
changeState(GestureRecognizerPrivate::Inactive);
return;
}
resetTouchPositions();
GestureEvent gesture = gestureEvent(position, gestureType);
m_gestureListener->handleGesture(&gesture);
return;
}
void GestureRecognizerPrivate::resetTouchPositions()
{
m_position = QPointF(-1, -1);
m_initialPos = m_position;
changeState(GestureRecognizerPrivate::Inactive);
}
QPointF GestureRecognizerPrivate::calculateVelocity(const QPointF& delta, int time)
{
QPointF newVelocity = m_velocity;
if ((delta / qreal(time)).manhattanLength() < 25) {
QPointF rawVelocity = delta / qreal(time) * qreal(1000) / qreal(m_scrollsPerSecond);
newVelocity = newVelocity * (qreal(1) - m_dragInertia) + rawVelocity * m_dragInertia;
}
newVelocity.setX(delta.x() ? qBound(-m_maxVelocity, newVelocity.x(), m_maxVelocity) : m_velocity.x());
newVelocity.setY(delta.y() ? qBound(-m_maxVelocity, newVelocity.y(), m_maxVelocity) : m_velocity.y());
return newVelocity;
}
} // namespace GVA