AppSrc/Gesture.cpp
author jkauppin
Fri, 15 Oct 2010 10:18:29 +0900
changeset 3 93fff7023be8
permissions -rw-r--r--
Initial version

/*
* 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--"));
    }