AppSrc/Gesture.cpp
changeset 3 93fff7023be8
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/AppSrc/Gesture.cpp	Fri Oct 15 10:18:29 2010 +0900
@@ -0,0 +1,384 @@
+/*
+* Copyright (c) 2009 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: Juha Kauppinen, Mika Hokkanen
+* 
+* Description: Photo Browser
+*
+*/
+
+#include "debug.h"
+#include "Gesture.h"
+#include <aknnotewrappers.h>
+
+// TODO: Add flexibility to support more gesture. e.g. circle
+// TODO: Add tap range (size of allowed moving area for tap)
+// TODO: Add mode-type working. Change config by one call
+
+CGesture::CGesture(MGestureCallBack* aOwner)
+    //: CTimer(CActive::EPriorityUserInput)
+: CTimer(CActive::EPriorityHigh)
+    , iState(EWaiting)
+    , iOwner(aOwner)
+    , iFirstGesture(EGestureNone)
+    , iThresholdOfTap(KDefaultThresholdOfTapPixels)
+    , iThresholdOfCursor(KDefaultThresholdOfCursorPixels)
+    , iStationaryTime(KDefaultStationaryTime)
+    , iLongTapTime(KDefaultLongTapTime)
+    , iMonitoringTime(KDefaultMonitoringTime)
+    , iSafetyTime(KDefaultSafetyTime)
+    {
+    // No implementation required
+    }
+
+CGesture::~CGesture()
+    {
+    Cancel(); // CTimer
+    iDragPoints.Close();
+    iDragTicks.Close();
+    }
+
+CGesture* CGesture::NewLC(MGestureCallBack* aOwner)
+    {
+    CGesture* self = new (ELeave) CGesture(aOwner);
+    CleanupStack::PushL(self);
+    self->ConstructL();
+    return self;
+    }
+
+CGesture* CGesture::NewL(MGestureCallBack* aOwner)
+    {
+    CGesture* self = CGesture::NewLC(aOwner);
+    CleanupStack::Pop(); // self;
+    return self;
+    }
+
+void CGesture::ConstructL()
+    {
+    CTimer::ConstructL();
+    CActiveScheduler::Add(this);
+    }
+
+EXPORT_C void CGesture::PointerEventL( const TPointerEvent& aEvent )
+    {
+#define RESETPOINTS()        {iDragPoints.Reset(); iDragTicks.Reset();}
+#define RECORDPOINTS(p, t)   {iDragPoints.Append(p); iDragTicks.Append(t);}
+#define SETMOVEMENT(d, c, p) {d.iX=c.iX-p.iX; d.iY=c.iY-p.iY;}
+
+    TInt tick = User::NTickCount();
+    TGestureType type = EGestureNone;
+    TPoint delta;
+    TPoint vector;
+    TInt threshold;
+
+    switch( aEvent.iType )
+        {
+        case TPointerEvent::EButton1Down:
+            DP2_IMAGIC(_L("CGesture::PointerEventL: TPointerEvent::EButton1Down (%d, %d)"), aEvent.iPosition.iX, aEvent.iPosition.iY);
+            
+            if (iState == EEnded) break; // do nothing if it's in safety time
+            
+            // Start timer to monitor long tap
+            Cancel(); // CTimer. Stop all timers.
+            DP1_IMAGIC(_L("iStationaryTime=%d"), iStationaryTime);
+            After( TTimeIntervalMicroSeconds32(iStationaryTime));
+
+            // Initialise pointer records and start new record
+            iPointBegan = iPointLastCursor = iPointPreviousEvent = aEvent.iPosition;
+            RESETPOINTS();
+            RECORDPOINTS(aEvent.iPosition, tick);
+
+            if (iState == EMonitoring)
+                {
+                DP0_IMAGIC(_L("CGesture::PointerEventL (2nd gesture starting)"));
+                // store 1st gesture type in high 16 bits if this is 2nd gesture
+                iFirstGesture = iFirstGesture << 16;
+                }
+            else // iState should be Waiting. Resets anyway even if it's not. 
+                {
+                DP0_IMAGIC(_L("CGesture::PointerEventL (1st gesture starting)"));
+                // Set point only for 1st gesture and notify owner
+                iFirstGesture    = EGestureNone;
+                iPointFirstBegan = aEvent.iPosition;
+                iOwner->HandleGestureBeganL(aEvent.iPosition);
+                }
+
+            iState = EBegan;
+
+            DP1_IMAGIC(_L("####### Down:iFirstGesture=%x"), iFirstGesture);
+            break;
+
+        case TPointerEvent::EDrag:
+            DP2_IMAGIC(_L("CGesture::PointerEventL: TPointerEvent::EDrag (%d, %d)"), aEvent.iPosition.iX, aEvent.iPosition.iY);
+
+            if ((iState != EBegan) && (iState != EStationary) && (iState != ETravelling))
+                break; // do nothing if it's not in the state above
+            
+            // record drag points and thier tick counts
+            RECORDPOINTS(aEvent.iPosition, tick);
+
+            SETMOVEMENT(delta, aEvent.iPosition, iPointPreviousEvent);
+
+            threshold = (iState == EBegan)? iThresholdOfTap * 3: iThresholdOfTap;
+            
+            if (IsMovementWithinThreshold(delta, threshold))
+                {
+                DP0_IMAGIC(_L("CGesture::PointerEventL (staying within threshold)"));
+                }
+            else
+                {
+                DP0_IMAGIC(_L("CGesture::PointerEventL (going beyond threshold)"));
+                Cancel(); // CTimer. Stop all timers.
+                iState = ETravelling;
+                }
+
+            if (iState == ETravelling)
+                {
+                DP0_IMAGIC(_L("CGesture::PointerEventL (notify drag event)"));
+                iOwner->HandleGestureMovedL(delta, EGestureDrag);
+                iPointPreviousEvent = aEvent.iPosition;
+                }
+
+#ifdef CURSOR_SIMULATION
+            type = CheckMovement(iPointLastCursor, aEvent.iPosition);
+
+            if (type != EGestureNone)
+                {
+                iOwner->HandleGestureMovedL(aEvent.iPosition, EGestureCursor|type);
+                iPointLastCursor = aEvent.iPosition;
+                }
+            DP5_IMAGIC(_L("iPointLastCursor(%d)"), type);
+#endif
+            break;
+
+        case TPointerEvent::EButton1Up:
+            DP2_IMAGIC(_L("CGesture::PointerEventL: TPointerEvent::EButton1Up (%d, %d)"), aEvent.iPosition.iX, aEvent.iPosition.iY);
+
+            if ((iState != EBegan) && (iState != EStationary) && (iState != ETravelling))
+                break; // do nothing if it's not in the state above
+            
+            Cancel(); // Stop timers
+            
+            iPointPreviousEvent = aEvent.iPosition;
+
+            // record drag points and thier tick counts
+            RECORDPOINTS(aEvent.iPosition, tick);
+
+            if ((iState == EBegan) ||
+                ((iState == EStationary) && !IS_GESTURE_LONGTAPPING(iFirstGesture)))
+                {
+                DP0_IMAGIC(_L("CGesture::PointerEventL (Tap!)"));
+                type = EGestureTap;
+                
+                // TODO: check distance of each tap if it's double tap
+                iFirstGesture |= type;
+
+                TInt t = (IS_GESTURE_TAPPED(iFirstGesture))? 0: iMonitoringTime;
+                After(TTimeIntervalMicroSeconds32(t)); // call immediately if double tap
+                iState = EMonitoring;
+                }
+            else if ((iState == EStationary) && IS_GESTURE_LONGTAPPING(iFirstGesture))
+                {
+                DP0_IMAGIC(_L("CGesture::PointerEventL (Long tap!)"));
+                type = EGestureLongTap;
+
+                iFirstGesture |= type;
+                After(TTimeIntervalMicroSeconds32(iMonitoringTime));
+                iState = EMonitoring;
+                }
+            else if (iState == ETravelling)
+                {
+                DP0_IMAGIC(_L("CGesture::PointerEventL (Was drag. Flick!)"));
+                type = CheckFlick(KDefaultValidityTimeOfFlick, vector);
+                iPointPreviousEvent = vector;
+
+                iFirstGesture |= type;
+                After(TTimeIntervalMicroSeconds32(0)); // call immediately 
+                iState = EMonitoring;
+                }
+
+            DP1_IMAGIC(_L("####### Up:iFirstGesture=%x"), iFirstGesture);
+            break;
+
+        default:
+            break;
+        }
+    }
+
+EXPORT_C void CGesture::SetThresholdOfTap( const TInt aPixels)
+    {
+    iThresholdOfTap = aPixels;
+    }
+
+EXPORT_C void CGesture::SetThresholdOfCursor( const TInt aPixels)
+    {
+    iThresholdOfCursor = aPixels;
+    }
+
+EXPORT_C void CGesture::SetStationaryTime(const TInt aMicroseconds)
+    {
+    iStationaryTime = aMicroseconds;
+    }
+
+EXPORT_C void CGesture::SetLongTapTime(const TInt aMicroSeconds)
+    {
+    iLongTapTime = aMicroSeconds;
+    }
+
+EXPORT_C void CGesture::SetMonitoringTime(const TInt aMicroseconds)
+    {
+    iMonitoringTime = aMicroseconds;
+    }
+
+EXPORT_C void CGesture::SetSafetyTime(const TInt aMicroseconds)
+    {
+    iSafetyTime = aMicroseconds;
+    }
+
+TBool CGesture::IsMovementWithinThreshold(const TPoint aDelta, const TInt aThreshold)
+    {
+    TBool ret = ETrue;
+
+    TInt diff_x      = aDelta.iX;
+    TInt diff_y      = aDelta.iY;
+    TInt abs_diff_2  = diff_x * diff_x + diff_y * diff_y;
+    TInt threshold_2 = aThreshold * aThreshold;
+
+    if (abs_diff_2 > threshold_2) ret = EFalse;
+    
+    return ret;
+    }
+
+TGestureType CGesture::CheckMovement(const TPoint aPointPrevious, const TPoint aPointCurrent, const TBool aSkipThresholdCheck)
+    {
+    TGestureType ret = EGestureNone;
+
+    TInt diff_x  = aPointCurrent.iX - aPointPrevious.iX;
+    TInt diff_y  = aPointCurrent.iY - aPointPrevious.iY;
+
+    TInt abs_diff_x = Abs(diff_x);
+    TInt abs_diff_y = Abs(diff_y);
+    TInt abs_diff_2 = abs_diff_x*abs_diff_x + abs_diff_y*abs_diff_y;  
+
+    if (abs_diff_2 > iThresholdOfCursor*iThresholdOfCursor)
+        {
+        // Movement is mapped to one of 8 directions
+        TBool valid_x = (abs_diff_x && (abs_diff_x * 5 > abs_diff_y * 4))? ETrue: EFalse;
+        TBool valid_y = (abs_diff_y && (abs_diff_y * 5 > abs_diff_x * 4))? ETrue: EFalse;
+
+        if (valid_y)
+            {
+            if (diff_y < 0) ret |= EGestureUp;
+            else            ret |= EGestureDown;
+            }
+        if (valid_x)
+            {
+            if (diff_x < 0) ret |= EGestureLeft;
+            else            ret |= EGestureRight;
+            }
+        }
+    
+    return ret;
+    }
+
+TGestureType CGesture::CheckFlick(const TInt aValidityTime, TPoint& aVector)
+    {
+    DP0_IMAGIC(_L("CGesture::CheckFlick++"));
+
+    // TODO: need to check if counts are same in iDragPoints and iDragTicks
+    TInt first, last;
+    TInt validtick = aValidityTime / 1000; // convert micro sec to milli sec.
+    TGestureType ret = EGestureNone;
+
+    first = last = iDragPoints.Count() - 1;
+    aVector.iX = aVector.iY = 0;
+    
+    for (TInt i=last-1;i>=0;--i)
+        {
+        TInt tickdiff = iDragTicks[last] - iDragTicks[i];
+
+        DP5_IMAGIC(_L("i=%d, tick=%d, tickdiff=%d (x,y)=(%d,%d)"),
+                i, iDragTicks[i], tickdiff, iDragPoints[i].iX, iDragPoints[i].iY);
+
+        if (tickdiff < validtick)
+            {
+            first = i;
+            }
+        else
+            {
+            break;
+            }
+        }
+
+    if (first != last)
+        {
+        TInt tickdiff = iDragTicks[last] - iDragTicks[first];
+        ret = CheckMovement(iDragPoints[first], iDragPoints[last], ETrue);
+
+        // Tick diff is 100 at minimum to avoid returning extreamly big vector.
+        if (tickdiff < 100) tickdiff = 100;
+
+        // Calculate the movement speed = pixels/sec
+        aVector.iX = (iDragPoints[last].iX - iDragPoints[first].iX)*1000/tickdiff;
+        aVector.iY = (iDragPoints[last].iY - iDragPoints[first].iY)*1000/tickdiff;
+        }
+
+    DP5_IMAGIC(_L("%d~%d, %d=>%d =%d"),
+            first, last, iDragPoints[first].iX, iDragPoints[last].iX,
+            CheckMovement(iDragPoints[first], iDragPoints[last]));
+    DP2_IMAGIC(_L("vector (%d,%d)"), aVector.iX, aVector.iY);
+    DP0_IMAGIC(_L("CGesture::CheckFlick--"));
+
+    return ret;
+    }
+
+void CGesture::RunL()
+    {
+    DP0_IMAGIC(_L("CGesture::RunL++"));
+
+    switch (iState)
+        {
+        case EBegan:
+            DP1_IMAGIC(_L("CGesture::RunL (EBegan -> EStationary) iLongTapTime=%d"), iLongTapTime);
+            iOwner->HandleGestureMovedL(iPointPreviousEvent, EGestureStationary);
+            After(TTimeIntervalMicroSeconds32(iLongTapTime));
+            iState = EStationary;
+
+            // Update the position so that movement check occurs against 
+            // last drag event, not against initial touch position
+            if (iDragPoints.Count() > 1)
+                iPointPreviousEvent = iDragPoints[iDragPoints.Count()-1];
+            break;
+        case EStationary:
+            DP0_IMAGIC(_L("CGesture::RunL (EStationary -> EStationary)"));
+            iOwner->HandleGestureMovedL(iPointPreviousEvent, EGestureLongTapping);
+            // record it's possible long tap if movement stays within threshold
+            iFirstGesture |= EGestureLongTapping;
+            // no state change
+            break;
+        case EMonitoring:
+            DP1_IMAGIC(_L("CGesture::RunL (EMonitoring -> EEnded) iSafetyTime=%d"), iSafetyTime);
+            iOwner->HandleGestureEndedL(iPointPreviousEvent, iFirstGesture);
+            After(TTimeIntervalMicroSeconds32(iSafetyTime));
+            iState = EEnded;
+            break;
+        case EEnded:
+            DP0_IMAGIC(_L("CGesture::RunL (EEnded -> EWaiting)"));
+            iState = EWaiting;
+            break;
+        default:
+            DP0_IMAGIC(_L("CGesture::RunL (default)"));
+            // do nothing
+            break;
+        }
+
+    DP0_IMAGIC(_L("CGesture::RunL--"));
+    }