diff -r d39add9822e2 -r 5bfc169077b2 webengine/webkitutils/rt_gesturehelper/src/gesturehelperimpl.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/webengine/webkitutils/rt_gesturehelper/src/gesturehelperimpl.cpp Fri Mar 19 09:52:28 2010 +0200 @@ -0,0 +1,631 @@ +/* +* Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +* All rights reserved. +* This component and the accompanying materials are made available +* under the terms of the License "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: Gesture helper implementation +* +*/ + + +#include "gesturehelperimpl.h" + +#include +#include + +#include "gesture.h" +#include "gesturedefs.h" +#include "utils.h" +#include "gestureeventfilter.h" +#include "gesturehelpereventsender.h" +#include "flogger.h" + +using namespace RT_GestureHelper; + +namespace RT_GestureHelper +{ + +/// type of function in gesture helper to be called by the timer +/// when timer triggers +typedef void (CGestureHelperImpl::*CallbackFunctionL)(); + +NONSHARABLE_CLASS( CCallbackTimer ) : public CTimer + { +public: + /** Two-phase constructor */ + static CCallbackTimer* NewL( CGestureHelperImpl& aHelper, + CallbackFunctionL aCallbackFunctionL, TInt aDelay, TBool aIsEnabled ) + { + CCallbackTimer* self = new ( ELeave ) CCallbackTimer( aHelper, + aCallbackFunctionL, aDelay, aIsEnabled ); + CleanupStack::PushL( self ); + self->ConstructL(); // construct base class + CActiveScheduler::Add( self ); + CleanupStack::Pop( self ); + return self; + } + + /** Destructor */ + ~CCallbackTimer() + { + Cancel(); + } + + /** Set whether sending holding events is currently enabled */ + void SetEnabled( TBool aEnabled ) + { + iIsEnabled = aEnabled; + // cancel in case hold timer is already running + Cancel(); + } + + /** @return whether sending holding events is currently enabled */ + TBool IsEnabled() const + { + return iIsEnabled; + } + + /** Start the timer. Calls CGestureHelperImpl::StartHoldingL upon completion */ + void Start() + { + // if sending hold events is disabled, do not ever start the hold timer, and + // hence hold events will never be triggered + if ( iIsEnabled ) + { + Cancel(); + After( iDelay ); + } + } + void SetDelay(TInt aDelay) { iDelay = aDelay; } + TInt GetDelay() { return iDelay; } + +private: + /** Constructor */ + CCallbackTimer( CGestureHelperImpl& aHelper, + CallbackFunctionL aCallbackFunctionL, TInt aDelay, TBool aIsEnabled ) + : CTimer( EPriorityUserInput - 1 ), // give higher priority to new pointer events with - 1 + iHelper( aHelper ), iCallbackFunctionL( aCallbackFunctionL ), + iDelay( aDelay ), iIsEnabled( aIsEnabled ) + { + } + + void RunL() // From CActive + { + (iHelper.*iCallbackFunctionL)(); + } + +private: + /// helper object that will be called back when timer is triggered + CGestureHelperImpl& iHelper; + /// Function in the iHelper object call + CallbackFunctionL iCallbackFunctionL; + /// How long a time to wait befor calling back after Start() + TInt iDelay; + /// whether sending holding events is currently enabled + TBool iIsEnabled; + }; + +} // namespace GestureHelper + +/** + * @return position from event. Use this instead of using aEvent direction to + * avoid accidentally using TPointerEvent::iPosition + */ +inline TPoint Position( const TPointerEvent& aEvent ) + { + // use parent position, since the capturer is using full screen area, + // and because the (Alfred) drag events are not local to visual even when + // coming from the client + + return aEvent.iPosition; + } + +// ---------------------------------------------------------------------------- +// Two-phase constructor +// ---------------------------------------------------------------------------- +// +CGestureHelperImpl* CGestureHelperImpl::NewL( MGestureObserver& aObserver ) + { + CGestureHelperImpl* self = new ( ELeave ) CGestureHelperImpl( aObserver ); + CleanupStack::PushL( self ); + self->iEventSender = CGestureEventSender::NewL( aObserver ); + self->iDoubleTapTimer = CCallbackTimer::NewL( *self, EmitFirstTapEvent, + KMaxTapDuration, EFalse ); // double tap is disabled by default + self->iHoldingTimer = CCallbackTimer::NewL( *self, StartHoldingL, + KHoldDuration, EFalse ); // holding is enabled by default + + self->iLongTouchTimer = CCallbackTimer::NewL( *self, HandleLongTouch, + KLongTapDuration, ETrue ); // holding is enabled by default + + self->iGesture = new ( ELeave ) CGesture(); + self->iUnusedGesture = new ( ELeave ) CGesture(); + TInt tapLimit = Mm2Pixels(KFingerSize_mm) / 2; + self->iEventFilter = new (ELeave) CGestureEventFilter(tapLimit); + CleanupStack::Pop( self ); + return self; + } + +// ---------------------------------------------------------------------------- +// Constructor +// ---------------------------------------------------------------------------- +// +CGestureHelperImpl::CGestureHelperImpl( MGestureObserver& aObserver ) + : iObserver( aObserver ) + { + } + +// ---------------------------------------------------------------------------- +// Destructor +// ---------------------------------------------------------------------------- +// +CGestureHelperImpl::~CGestureHelperImpl() + { + delete iDoubleTapTimer; + delete iHoldingTimer; + delete iGesture; + delete iPreviousTapGesture; + delete iUnusedGesture; + delete iLongTouchTimer; + delete iEventFilter; + delete iEventSender; + } + + +// ---------------------------------------------------------------------------- +// SetHoldingEnabled +// ---------------------------------------------------------------------------- +// +void CGestureHelperImpl::SetHoldingEnabled( TBool aEnabled ) + { + iHoldingTimer->SetEnabled( aEnabled ); + } + +// ---------------------------------------------------------------------------- +// IsHoldingEnabled +// ---------------------------------------------------------------------------- +// +TBool CGestureHelperImpl::IsHoldingEnabled() const + { + return iHoldingTimer->IsEnabled(); + } + +// ---------------------------------------------------------------------------- +// SetHoldingEnabled +// ---------------------------------------------------------------------------- +// +void CGestureHelperImpl::SetDoubleTapEnabled( TBool aEnabled ) + { + iDoubleTapTimer->SetEnabled( aEnabled ); + } + +// ---------------------------------------------------------------------------- +// IsHoldingEnabled +// ---------------------------------------------------------------------------- +// +TBool CGestureHelperImpl::IsDoubleTapEnabled() const + { + return iDoubleTapTimer->IsEnabled(); + } + + + +// ---------------------------------------------------------------------------- +// Reset state +// ---------------------------------------------------------------------------- +// +void CGestureHelperImpl::Reset() + { + iHoldingTimer->Cancel(); + iLongTouchTimer->Cancel(); + iGesture->Reset(); + } + +/** + * Helper function that calls Reset on the pointer to CGestureHelperImpl + */ +static void ResetHelper( TAny* aHelper ) + { + static_cast< CGestureHelperImpl* >( aHelper )->Reset(); + } + +// ---------------------------------------------------------------------------- +// Handle a pointer event +// ---------------------------------------------------------------------------- +// +TBool CGestureHelperImpl::HandlePointerEventL( const TPointerEvent& aEvent ) + { + TInt filterReason; + SetLastEventTime(); + if (!iEventFilter->FilterDrag(aEvent, iLastEventTime, filterReason)) + { + return noneAlf_HandlePointerEventL( aEvent ); + } + else + { + /* + TBuf<10> num; + num.Num( filterReason ); + TBuf<128> str; + str.AppendFormat(_L("Filter reason: %d"), filterReason); + RFileLogger::Write( _L("gh"), _L("gh.txt"), EFileLoggingModeAppend, str); + */ + return EFalse; + } + } + + +TBool CGestureHelperImpl::noneAlf_HandlePointerEventL( const TPointerEvent& aEvent) + { + switch ( aEvent.iType ) + { + case TPointerEvent::EButton1Down: + { + HandleTouchDownL(aEvent); + break; + } + case TPointerEvent::EDrag: + { + HandleMoveL(aEvent); + break; + } + case TPointerEvent::EButton1Up: + { + if (KErrNone == AddPoint( aEvent )) + { + HandleTouchUp(aEvent); + } + else + { + EmitCancelEvent(); + } + Reset(); + break; + } + default: + break; + } + return ETrue; + } + +TBool CGestureHelperImpl::IsMovementGesture(TGestureCode aCode) + { + return (aCode == EGestureDrag || aCode == EGestureFlick || aCode == EGestureSwipeUp || + aCode == EGestureSwipeDown || aCode == EGestureSwipeRight || aCode == EGestureSwipeLeft); + } + +void CGestureHelperImpl::HandleLongTouch() + { + iDoubleTapTimer->Cancel(); + iGesture->SetLongTap(ETrue); + iGesture->SetComplete(); + TPoint startPos = iGesture->StartPos(); + EmitEvent(*iGesture); + iGesture->Reset(); + iGesture->AddPoint( startPos, GetLastEventTime() ); + } + +void CGestureHelperImpl::HandleTouchDownL(const TPointerEvent& aEvent) + { + TGestureCode prevCode = iGesture->PreviousGestureCode(); + if (prevCode == EGestureStart) return; + if (prevCode == EGestureDrag) + { + iGesture->Reset(); + } + AddPointL( aEvent ); + + if (!iLongTouchTimer->IsActive()) + { + iLongTouchTimer->Start(); + } + if (!iDoubleTapTimer->IsActive()) + { + EmitEvent( *iGesture ); + } + } + +void CGestureHelperImpl::HandleMoveL(const TPointerEvent& aEvent) + { + if (iGesture->IsLatestPoint( Position(aEvent))) return; // I'm not sure we need this + //Cancel double tap time - it's neither tap nor double tap + iDoubleTapTimer->Cancel(); + iLongTouchTimer->Cancel(); + + TBool isFirstPoint = IsIdle(); + + AddPointL( aEvent ); + + if (iPreviousTapGesture) + { + RecycleGesture(iPreviousTapGesture); + } + + if (!isFirstPoint) + { + EmitEvent( *iGesture ); + } + } + +void CGestureHelperImpl::HandleTouchUp(const TPointerEvent& /*aEvent*/) + { + TGestureCode prevCode = iGesture->PreviousGestureCode(); + iLongTouchTimer->Cancel(); + iDoubleTapTimer->Cancel(); + TInt64 fromLastTouchUp = iLastEventTime.MicroSecondsFrom(iLastTouchUpTime).Int64(); + TInt64 fromLastDoubleTap = iLastEventTime.MicroSecondsFrom(iLastDoubleTapTime).Int64(); + /* + TBuf<1024> str; + str.AppendFormat(_L("fromLastTouchUp: %d, "), fromLastTouchUp); + str.AppendFormat(_L("fromLastDoubleTap: %d, "), fromLastTouchUp); + str.AppendFormat(_L("iPreviousTapGesture: %d, "), iPreviousTapGesture); + RFileLogger::Write( _L("gh"), _L("gh.txt"), EFileLoggingModeAppend, str); + */ + if ( prevCode == EGestureLongTap ) + { + EmitReleasedEvent(); + } + else if (IsMovementGesture(prevCode) || + !iDoubleTapTimer->IsEnabled() /* || !iGesture->IsTap()*/ ) + { + iGesture->SetComplete(); + EmitEvent(*iGesture); + } + + else + { + if ( iPreviousTapGesture && + (fromLastTouchUp > KDoubleTapMinActivationInterval) && + (fromLastTouchUp < KDoubleTapMaxActivationInterval) && + (fromLastDoubleTap > KDoubleTapIdleInterval)) + { + // it's a double tap + iLastTouchUpTime = iLastEventTime; + iLastDoubleTapTime = iLastEventTime; + EmitDoubleTapEvent(); + } + else + { + // it's a first tap + iLastTouchUpTime = iLastEventTime; + if (iPreviousTapGesture) + { + RecycleGesture(iPreviousTapGesture); + } + + iPreviousTapGesture = iGesture; + iGesture = NewGesture(); + iDoubleTapTimer->Start(); + } + } + } + + + +void CGestureHelperImpl::EmitDoubleTapEvent() + { + iPreviousTapGesture->SetDoubleTap(); + EmitFirstTapEvent(); + } + + +void CGestureHelperImpl::EmitReleasedEvent() + { + iGesture->SetComplete(); + iGesture->SetReleased(); + EmitEvent(*iGesture); + } + + +// ---------------------------------------------------------------------------- +// Is the helper idle? +// inline ok in cpp file for a private member function +// ---------------------------------------------------------------------------- +// +inline TBool CGestureHelperImpl::IsIdle() const + { + return iGesture->IsEmpty(); + } + +// ---------------------------------------------------------------------------- +// Add a point to the sequence of points that together make up the gesture +// inline ok in cpp file for a private member function +// ---------------------------------------------------------------------------- +// +inline void CGestureHelperImpl::AddPointL( const TPointerEvent& aEvent ) + { + User::LeaveIfError( AddPoint( aEvent ) ); + } + +// ---------------------------------------------------------------------------- +// Add a point to the sequence of points that together make up the gesture +// inline ok in cpp file for a private member function +// ---------------------------------------------------------------------------- +// +inline TInt CGestureHelperImpl::AddPoint( const TPointerEvent& aEvent ) + { + TPoint pos = Position ( aEvent ); + return iGesture->AddPoint( pos, GetLastEventTime() ); + } + +// ---------------------------------------------------------------------------- +// StartHoldingTimer +// ---------------------------------------------------------------------------- +// +void CGestureHelperImpl::StartHoldingTimer( const TPointerEvent& aNewEvent ) + { + if ( !( iGesture->IsHolding() || + iGesture->IsNearHoldingPoint( Position( aNewEvent ) ) ) ) + { + // restart hold timer, since pointer has moved + iHoldingTimer->Start(); + // Remember the point in which holding was started + iGesture->SetHoldingPoint(); + } + } + +/** + * Helper function that calls ContinueHolding on the pointer to TGesture + */ +static void ContinueHolding( TAny* aGesture ) + { + static_cast< CGesture* >( aGesture )->ContinueHolding(); + } + +// ---------------------------------------------------------------------------- +// Add a point to the sequence of points that together make up the gesture +// ---------------------------------------------------------------------------- +// +void CGestureHelperImpl::StartHoldingL() + { + // hold & tap event is specifically filtered out. Use case: in list fast + // scrolling activation (e.g. enhanced coverflow), tap & hold should not + // start fast scroll. In addition, after long tap on start position, + // drag and drag & hold swiping should emit normal swipe and swipe&hold + // events. Therefore, tap & hold is not supported. + __ASSERT_DEBUG( !iGesture->IsTap() && !iPreviousTapGesture, Panic( EGesturePanicIllegalLogic ) ); + + // holding has just started, and gesture code should be provided to client. + // set gesture state so that it produces a gesture code (other than drag) + iGesture->StartHolding(); + + // create an item in the cleanup stack that will set the gesture state + // to holding-was-started-earlier state. NotifyL may leave, but the + // holding-was-started-earlier state must still be successfully set, + // otherwise, the holding gesture code will be sent twice + CleanupStack::PushL( TCleanupItem( &ContinueHolding, iGesture ) ); + + EmitEvent( *iGesture ); + + // set holding state to "post holding" + CleanupStack::PopAndDestroy( iGesture ); + } + +// ---------------------------------------------------------------------------- +// RecyclePreviousTapGesture +// ---------------------------------------------------------------------------- +// +void CGestureHelperImpl::RecyclePreviousTapGesture( TAny* aSelf ) + { + CGestureHelperImpl& self = *reinterpret_cast( aSelf ); + self.RecycleGesture( self.iPreviousTapGesture ); + } + +// ---------------------------------------------------------------------------- +// Emit the remainder of the previous tap event (tap + released) +// ---------------------------------------------------------------------------- +// +void CGestureHelperImpl::EmitFirstTapEvent() + { + // when this function is called, a tap has turned out to _not_ be a double tap + __ASSERT_DEBUG( IsDoubleTapEnabled(), Panic( EGesturePanicIllegalLogic ) ); + __ASSERT_DEBUG( iPreviousTapGesture, Panic( EGesturePanicIllegalLogic ) ); + + iDoubleTapTimer->Cancel(); + CompleteAndEmit( *iPreviousTapGesture ); + RecycleGesture(iPreviousTapGesture); + + } + +// ---------------------------------------------------------------------------- +// EmitStartEventL +// ---------------------------------------------------------------------------- +// +void CGestureHelperImpl::EmitStartEventL( const CGesture& aGesture ) + { + CGesture* startGesture = aGesture.AsStartEventLC(); + EmitEvent( *startGesture ); + CleanupStack::PopAndDestroy( startGesture ); + } + +// ---------------------------------------------------------------------------- +// EmitCompletionEventsL +// ---------------------------------------------------------------------------- +// +void CGestureHelperImpl::CompleteAndEmit( CGesture& aGesture ) + { + aGesture.SetComplete(); + // send gesture code if holding has not been started. If holding has + // been started, client has already received a "hold swipe left" e.g. event, in which + // case don't another "swipe left" event + if ( !aGesture.IsHolding() ) + { + // if client leaves, the state is automatically reset. + // In this case the client will not get the released event + EmitEvent( aGesture ); + } + + // send an event that stylus was lifted + aGesture.SetReleased(); + EmitEvent( aGesture ); + } + +// ---------------------------------------------------------------------------- +// EmitCancelEventL +// ---------------------------------------------------------------------------- +// +void CGestureHelperImpl::EmitCancelEvent() + { + iDoubleTapTimer->Cancel(); + + + CGesture& gestureToCancel = iPreviousTapGesture ? *iPreviousTapGesture : *iGesture; + gestureToCancel.SetCancelled(); + EmitEvent( gestureToCancel ); + RecycleGesture(iPreviousTapGesture); + + } + +// ---------------------------------------------------------------------------- +// Notify observer +// ---------------------------------------------------------------------------- +// +void CGestureHelperImpl::EmitEvent( const CGesture& aGesture ) + { + // deallocation of the event is happening in CGestureEventSender::RunL() + TGestureEvent event; + event.SetCode(const_cast(aGesture).Code(EAxisBoth)); + event.SetCurrentPos(aGesture.CurrentPos()); + event.SetDistance(aGesture.Distance()); + event.SetStartPos(aGesture.StartPos()); + event.SetIsHolding(aGesture.IsHolding()); + event.SetSpeed(aGesture.Speed()); + iEventSender->AddEvent(event); + } + +// ---------------------------------------------------------------------------- +// Return a fresh gesture from the gesture pool (pool of one gesture) +// ---------------------------------------------------------------------------- +// +CGesture* CGestureHelperImpl::NewGesture() + { + __ASSERT_DEBUG( iUnusedGesture, Panic( EGesturePanicIllegalLogic ) ); // pool should no be empty + + iUnusedGesture->Reset(); + CGesture* freshGesture = iUnusedGesture; + iUnusedGesture = NULL; + return freshGesture; + } + +// ---------------------------------------------------------------------------- +// Return a fresh gesture from the gesture pool (pool of one gesture) +// ---------------------------------------------------------------------------- +// +void CGestureHelperImpl::RecycleGesture( CGesture*& aGesturePointer ) + { + // only one object fits into the pool, and that should currently be enough + // one pointer must be null, one non-null + __ASSERT_DEBUG( !iUnusedGesture != !aGesturePointer, Panic( EGesturePanicIllegalLogic ) ); + if ( aGesturePointer ) + { + iUnusedGesture = aGesturePointer; + aGesturePointer = NULL; + } + }