ginebra2/Gestures/GestureRecognizer.cpp
changeset 10 232fbd5a2dcb
child 16 3c88a81ff781
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ginebra2/Gestures/GestureRecognizer.cpp	Wed Aug 18 09:37:05 2010 +0300
@@ -0,0 +1,398 @@
+/*
+* 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
+