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