diff -r e1e28b0273b0 -r 93fff7023be8 AppSrc/Gesture.cpp --- /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 + +// 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--")); + }