qstmgesturelib/recognisers/qstmpinchgesturerecogniser.cpp
changeset 0 1450b09d0cfd
child 3 0954f5dd2cd0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qstmgesturelib/recognisers/qstmpinchgesturerecogniser.cpp	Tue May 04 12:39:35 2010 +0300
@@ -0,0 +1,401 @@
+/*
+* Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+* All rights reserved.
+* This component and the accompanying materials are made available
+* under the terms of "Eclipse Public License v1.0"
+* which accompanies this distribution, and is available
+* at the URL "http://www.eclipse.org/legal/epl-v10.html".
+*
+* Initial Contributors:
+* Nokia Corporation - initial contribution.
+*
+* Contributors:
+*
+* Description: 
+*
+*/
+
+
+#include "qstmpinchgesturerecogniser.h"
+#include "qstmgenericsimplegesture.h"
+#include <qstmuievent_if.h>
+#include <qstmfilelogger.h>
+#include <qstmutils.h>
+
+
+using namespace qstmGesture ;
+using namespace qstmUiEventEngine;
+
+
+QStm_PinchGestureRecogniser::QStm_PinchGestureRecogniser(QStm_GestureListenerIf* listener) : 
+		                      QStm_GestureRecogniser(listener)
+{
+    m_pinching = false ;
+    m_pinchingspeed = 3.5 ; // by default something suitable for capacitive
+    m_holdseen = false ;
+}
+
+QStm_PinchGestureRecogniser::~QStm_PinchGestureRecogniser()
+{
+}
+
+QStm_GestureRecognitionState QStm_PinchGestureRecogniser::recognise(int numOfActiveStreams,
+                                QStm_GestureEngineIf* pge)
+{
+    QStm_GestureRecognitionState state = m_state = ENotMyGesture;
+    // Check if we are enabled or not
+    if (!m_gestureEnabled) return state ;
+
+    if (m_loggingenabled) {
+        LOGARG("QStm_PinchGestureRecogniser: %d %d %d ", m_pinching, m_holdseen, numOfActiveStreams) ;
+    }
+#if !defined(ADVANCED_POINTER_EVENTS)
+    // Look at the events to see if it looks like pinch in single touch
+    // WARNING: this code is a hack : in single touch capacitive touch device (like Alvin with 52.50) it works so-and-so, 
+    // because the pointer events were reported from the corners of the rectangle formed by two fingers pressing.
+    // In resistive touch device like Tube or Ivalo the reported points are somewhere in the middle between the fingers
+    // and jumping a lot, so it is very difficult to get it right.
+    if (numOfActiveStreams == 1) {
+        // Then look at the event stream, first we need to see a hold and then a fast jump
+        const qstmUiEventEngine::QStm_UiEventIf* puie = pge->getUiEvents(0);
+        int countOfEvents = puie->countOfEvents() ;
+        qstmUiEventEngine::QStm_UiEventCode eventCode = puie->code() ;
+
+        if (countOfEvents > 0 ) {// how many events
+            if (m_loggingenabled) {
+                LOGARG("QStm_PinchGestureRecogniser: %d %d %d %d %d, m: %d b: %d", 
+                		m_pinching, m_holdseen, numOfActiveStreams, countOfEvents, eventCode, int(m_m), int(m_b)) ;
+            }
+
+            if (m_pinching) {
+                // We have entered pinching state, lets move one of the points unless it is a release
+                if (eventCode == qstmUiEventEngine::ERelease) {
+                    m_pinching = false ;
+                    m_holdseen = false ;
+                }
+                else {
+                    bool pointIgnored = true ;  // for logging purposes
+                    int currentLength = m_loggingenabled ? QStm_Utils::distance(m_pinchstart, m_pinchend) : 0;
+
+                    QPoint oStart(m_pinchstart) ;
+                    QPoint oEnd(m_pinchend) ;
+                    int difference = 0 ;
+                    state = ELockToThisGesture ;
+                    const QPoint& tp = puie->currentXY();
+                    // calculate the distance of the new point from the stored vector
+                    int d1 =  ((m_pinchstart.x() - tp.x()) * (m_pinchstart.x() - tp.x())) + 
+                    		  ((m_pinchstart.y() - tp.y()) * (m_pinchstart.y() - tp.y())) ;
+                    int d2 =  ((m_pinchend.x() - tp.x()) * (m_pinchend.x() - tp.x())) + 
+                    		  ((m_pinchend.y() - tp.y()) * (m_pinchend.y() - tp.y())) ;
+                    // check also if the Y coordinate happens to be near the hold point,
+                    // this seems to be the case at least with alvin, we keep getting two points,
+                    // where one is near the Y coordinate of the hold point
+                    int diffY = ABS(tp.y() - m_pinchstart.y()) ;
+
+                    if (d1 < d2 || diffY < 12) {
+                        // the detected point is near the first point,
+                        // or the detected point is about on the same horizontal line with the hold point
+                        // do not do anything, but keep the gesture
+                    }
+                    else {
+                        pointIgnored = false ;
+                        // the detected point is close to the other end, then adjust the stored vector
+                        int xd = m_pinchend.x() - tp.x() ;
+                        int yd = m_pinchend.y() - tp.y() ;
+                        if (xd < 0 ) xd = - xd ;
+                        if (yd < 0 ) yd = - yd ;
+                        // look which coordinate is closer to the original and use that
+                        if (xd < yd) {
+                            // calculate new point based on the X value
+                            m_pinchend.setX(tp.x()) ;
+                            m_pinchend.setY(m_m * m_pinchend.x() + m_b) ;
+                            if (m_pinchend.y() < 0) m_pinchend.setY(0) ;
+                        }
+                        else {
+                            if (m_m != 0) {
+                                m_pinchend.setY(tp.y()) ;
+                                m_pinchend.setX((m_pinchend.y() - m_b) / m_m) ;
+                                if (m_pinchend.x() < 0 ) m_pinchend.setX(0) ;
+                            }
+                            else {
+                                m_pinchend.setX(tp.x()) ;
+                                m_pinchend.setY(m_m * m_pinchend.x() + m_b) ;
+                                if (m_pinchend.y() < 0) m_pinchend.setY(0) ;
+                            }
+                        }
+                        float newd = calculateDistance() ;
+                        // check if the difference is too big and adjust accordingly
+                        // the method also updates the m_ddistance
+                        difference = adjustPinchMove(m_ddistance, newd) ;
+                        // Now we have a pinch gesture with size as details
+                        qstmGesture::QStm_TwoPointGesture pgest(KUid, m_pinchstart, m_pinchend);
+                        pgest.setLogging(m_loggingenabled);
+                        pgest.setDetails(&difference) ;
+                        pgest.setTarget(puie->target());
+                        // inform the listener
+                        m_listener->gestureEnter(pgest);
+                    }
+                    if (m_loggingenabled) {
+                        int newLength = QStm_Utils::distance(m_pinchstart, m_pinchend);
+                        float speed = puie->speed() ;
+
+                        LOGARG("QStm_PinchGestureRecogniser: %d: o: %d, n: %d, d: %d (%d,%d) speed %f (%d,%d : %d,%d) (from: (%d,%d : %d,%d) (m: %f b: %f)",
+                                pointIgnored,
+                                currentLength, newLength, difference,
+                                tp.x(), tp.y(), double(speed),
+                                m_pinchstart.x(), m_pinchstart.y(), m_pinchend.x(), m_pinchend.y(),
+                                oStart.x(), oStart.y(), oEnd.x(), oEnd.y(),
+                                double(m_m), double(m_b)) ;
+                    }
+
+                }
+            }
+            else if (eventCode == qstmUiEventEngine::EMove) { // The last one is move and we were not pinching
+
+                if (m_loggingenabled) {
+                    LOGARG("QStm_PinchGestureRecogniser: %d: num %d code %d", m_pinching, countOfEvents, eventCode);
+                }
+                qstmUiEventEngine::QStm_UiEventIf* puieFirst = puie->previousEvent();
+
+                // check if we have seen hold
+                if (m_holdseen) {
+                    const QPoint& tp1 = puie->currentXY() ;
+                    float speed = puie->speed() ;
+                    if (m_loggingenabled) {
+                        LOGARG("QStm_PinchGestureRecogniser: tp1: %d %d hold %d %d, speed %f", 
+                        		tp1.x(), tp1.x(), m_holdseenAtPos.x(), m_holdseenAtPos.y(), double(speed) );
+                    }
+                    // is the speed extremely high so that it looks like other finger pressing in different location?
+                    if (speed > m_pinchingspeed) {
+                        QTime tstamp = puie->timestamp() ;
+                        
+                        int tim = m_holdseenAtTime.msecsTo(tstamp);
+          
+                        m_pinching = true;
+                        m_pinchstart = m_holdseenAtPos;
+                        m_pinchend = tp1;
+                        calculateZoomingLine();
+                        m_ddistance = calculateDistance();
+                        state = ELockToThisGesture ;    // NOTE: once pinch is started, it will stay until release
+                        // create the first pich gesture which does not yet resize anything
+                        qstmGesture::QStm_TwoPointGesture pgest(KUid, m_pinchstart, m_pinchend);
+                        pgest.setTarget(puie->target());
+                        pgest.setLogging(m_loggingenabled);
+                        pgest.setDetails(0) ;
+                        // inform the listener
+                        m_listener->gestureEnter(pgest);
+                    }
+                }
+            }
+        }
+        if (!m_pinching) {
+            if (m_loggingenabled) {
+                LOGARG("QStm_PinchGestureRecogniser: not pinching %d", puie);
+            }
+            if (puie != NULL && puie->code() == qstmUiEventEngine::EHold) { // The last one is hold and we were not pinching
+
+                m_holdseen = true;
+                m_holdseenAtPos = puie->currentXY();
+                m_holdseenAtTime = puie->timestamp() ;
+                if (m_loggingenabled) {
+                    LOGARG("QStm_PinchGestureRecogniser: hold seen at(%d, %d) at %s", 
+                    		m_holdseenAtPos.x(), m_holdseenAtPos.y(), m_holdseenAtTime.toString("hh:mm:ss.zzz"));
+                }
+            }
+        }
+        if (puie && puie->code() == qstmUiEventEngine::ETouch) {// The last one is touch
+            m_holdseen = false;
+        }
+        else if (puie && puie->code() == qstmUiEventEngine::ERelease) {// The last one is release
+            m_holdseen = false;
+        }
+    }
+#else
+    // This is the multi touch case: two event streams needs to be there; this is the real pinch zoom
+    if (numOfActiveStreams == 2) {
+        const qstmUiEventEngine::QStm_UiEventIf* puie1 = pge->getUiEvents(0);
+        const qstmUiEventEngine::QStm_UiEventIf* puie2 = pge->getUiEvents(1);
+        qstmUiEventEngine::QStm_UiEventCode eventCode1 = puie1->code() ;
+        qstmUiEventEngine::QStm_UiEventCode eventCode2 = puie2->code() ;
+
+        if (m_loggingenabled) {
+            QPoint p1 = puie1->currentXY() ;
+            QPoint p2 = puie2->currentXY() ;
+            LOGARG("QStm_PinchGestureRecogniser: two streams: %s at [%d,%d], %s at [%d,%d]",
+                    qstmUiEventEngine::event_name(eventCode1), p1.x(), p1.y(),
+                    qstmUiEventEngine::event_name(eventCode1), p2.x(), p2.y()
+                    ) ;
+
+        }
+
+
+        if (!m_pinching) {
+            // This means we start pinching, the events can be any combination of ETouch, EMove, EHold
+            if ((eventCode1 == qstmUiEventEngine::ETouch || 
+                 eventCode1 == qstmUiEventEngine::EMove || 
+                 eventCode1 == qstmUiEventEngine::EHold) &&
+                (eventCode2 == qstmUiEventEngine::ETouch || 
+                 eventCode2 == qstmUiEventEngine::EMove  || 
+                 eventCode2 == qstmUiEventEngine::EHold))   {
+            	
+                // This is valid pinching start
+                m_pinching = true ;
+                // get the start and end position for the picnhing vector
+                m_pinchstart = puie1->currentXY() ;
+                m_pinchend = puie2->currentXY() ;
+                calculateZoomingLine();
+                m_ddistance = calculateDistance();
+                state = ELockToThisGesture ;    // NOTE: once pich is started, it will stay until release
+                if (m_loggingenabled) {
+                    LOGARG("QStm_PinchGestureRecogniser: pinch start: [%d,%d][%d,%d]",
+                            m_pinchstart.x(), m_pinchstart.y(), m_pinchend.x(), m_pinchend.y()) ;
+
+                }
+                // create the first pich gesture which does not yet resize anything
+                qstmGesture::QStm_TwoPointGesture pgest(KUid, m_pinchstart, m_pinchend);
+                pgest.setLogging(m_loggingenabled);
+                pgest.setDetails(0) ;
+                pgest.setTarget(puie->target());
+                // inform the listener
+                m_listener->gestureEnter(pgest);
+            }
+            else  {
+            	// Not a valid pinching start, do nothing (maybe it were easier to just check if one of the events is ERelease)
+            }
+        }
+        else  {
+            // We have entered pinching state, lets move one of the points unless it is a release
+            if (eventCode1 == qstmUiEventEngine::ERelease || eventCode2 == qstmUiEventEngine::ERelease) {
+                m_pinching = false ;
+            }
+            else  {
+                state = ELockToThisGesture ;
+
+                // get the start and end position for the picnhing vector
+                m_pinchstart = puie1->currentXY() ;
+                m_pinchend = puie2->currentXY() ;
+                float newd = calculateDistance() ;
+                // check if the difference is too big and adjust accordingly
+                // the method also updates the m_ddistance
+                int difference = adjustPinchMove(m_ddistance, newd) ;
+                // Now we have a pinch gesture with size
+                if (m_loggingenabled) {
+                    LOGARG("QStm_PinchGestureRecogniser: pinch: [%d,%d][%d,%d], diff %d",
+                            m_pinchstart.x(), m_pinchstart.y(), m_pinchend.x(), m_pinchend.y(), difference) ;
+
+                }
+
+                qstmGesture::QStm_TwoPointGesture pgest(KUid, m_pinchstart, m_pinchend);
+                pgest.setLogging(m_loggingenabled);
+                pgest.setDetails(difference) ;
+                pgest.setTarget(puie->target());
+                // inform the listener
+                m_listener->gestureEnter(pgest);
+            }
+        }
+
+    }
+#endif
+
+    if (state == ENotMyGesture) {
+        if (m_loggingenabled) {
+            LOGARG("QStm_PinchGestureRecogniser: NotMyGesture %d %d %d ", m_pinching, m_holdseen, numOfActiveStreams) ;
+        }
+        // if it was not our gesture, then the state can not be pinching...
+        m_pinching = false ;
+    }
+    m_state = state;
+    
+    return state;
+}
+
+void QStm_PinchGestureRecogniser::release(QStm_GestureEngineIf* pge)
+{
+    m_pinching = false ;
+    const qstmUiEventEngine::QStm_UiEventIf* puie = pge->getUiEvents(0);
+    qstmGesture::QStm_TwoPointGesture pgest(KUid, m_pinchstart, m_pinchend);
+    pgest.setDetails(0) ;
+    pgest.setTarget(puie->target());
+    m_listener->gestureExit(pgest) ;
+    m_state = ENotMyGesture;
+}
+
+
+/*!
+ * Now that we know the two points where the zooming started, we move those points only along
+ * the same line y = mx + b, so lets calculate m and b.
+ */
+void QStm_PinchGestureRecogniser::calculateZoomingLine()
+{
+    int sX = m_pinchstart.x() ;
+    int sY = m_pinchstart.y() ;
+    int eX = m_pinchend.x() ;
+    int eY = m_pinchend.y() ;
+
+    if (eX == sX) {
+        m_m = 0.f ;
+    }
+    else {
+        m_m = float(eY-sY)/(eX-sX) ;
+    }
+    m_b = sY-(m_m*sX) ;
+}
+
+/*!
+ * calculate the distance, return as float
+ */
+float QStm_PinchGestureRecogniser::calculateDistance()
+{
+	/*
+    double x = ((m_pinchstart.x() - m_pinchend.x()) * (m_pinchstart.x() - m_pinchend.x())) + 
+    		   ((m_pinchstart.y() - m_pinchend.y()) * (m_pinchstart.y() - m_pinchend.y())) ;
+    double ddist; 
+    SQRT(ddist, x) ;
+    return float(ddist) ;
+    */
+	return QStm_Utils::distanceF(m_pinchstart, m_pinchend);
+}
+
+/*!
+ * Set the pinching speed as pixels / ms (meaning that in case of singletouch device
+ * the other finger looks like the EMove UI event suddenly jumps to new location;
+ * in resistive the new location is somewhere in the middle of the touches, in capacitive
+ * the driver seems to report three or four points:
+ * original (x,y), new (a,b) and also (a,y), sometimes (x,b)
+ */
+void QStm_PinchGestureRecogniser::setPinchingSpeed(float speed) /* __SOFTFP */
+{
+    m_pinchingspeed = speed ;
+}
+
+/*!
+ * Adjust the pinch move so that it will not be too jumpy
+ */
+int QStm_PinchGestureRecogniser::adjustPinchMove(float& previousDistance, float newDistance)
+{
+    float diff = newDistance - previousDistance ;
+    float logdiff = diff ;
+    if (diff < 0) diff = -diff ;	// Note that the next calculations need the positive diff value, but keep the original in logdiff 
+    float changePercentage = (diff/previousDistance)*100.f ;
+    if (changePercentage > 10.f) {
+        // change more than 10%, make at most 10%
+        float newdiff = previousDistance*0.1f ;
+        if (previousDistance > newDistance) newdiff = -newdiff ;
+        if (m_loggingenabled) {
+            LOGARG("QStm_PinchGestureRecogniser: adjustPinchMove from %f to %f : was, now %f %f",
+                double(logdiff), double(newdiff), double(previousDistance), double(newDistance));
+        }
+
+        previousDistance = previousDistance + newdiff ;
+        diff = newdiff ;
+    }
+    else {
+        if (m_loggingenabled) {
+            LOGARG("QStm_PinchGestureRecogniser: adjustPinchMove from %f to %f : was, now %f %f",
+                double(logdiff), double(diff), double(previousDistance), double(newDistance));
+        }
+        previousDistance = newDistance ;  // accept the new value and update the new length
+        diff = logdiff ;    // put the original back (this is why the logdiff can not be ABS(diff)!
+    }
+    return (int)diff ;
+}