/*
* 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 "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
*
*/
// System includes
#include <e32base.h>
#include <w32std.h>
// User includes
#include "xngesturehelper.h"
#include "xngesture.h"
#include "xngesturedefs.h"
#include "xnnode.h"
using namespace XnGestureHelper;
namespace XnGestureHelper
{
NONSHARABLE_CLASS( CHoldingTimer ) : public CTimer
{
public:
/** Two-phase constructor */
static CHoldingTimer* NewL( CXnGestureHelper& aHelper )
{
CHoldingTimer* self = new ( ELeave ) CHoldingTimer( aHelper );
CleanupStack::PushL( self );
self->ConstructL();
// "hold event" sending is enabled by default
self->iIsEnabled = ETrue;
CActiveScheduler::Add( self );
CleanupStack::Pop( self );
return self;
}
/** Destructor */
~CHoldingTimer()
{
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 CXnGestureHelper::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( KHoldDuration );
}
}
private:
/** Constructor */
CHoldingTimer( CXnGestureHelper& aHelper )
: // give higher priority to new pointer events with - 1
CTimer( EPriorityUserInput - 1 ),
iHelper( aHelper )
{
}
void RunL() // From CActive
{
iHelper.StartHoldingL();
}
private:
/// helper object that will be called back when timer is triggered
CXnGestureHelper& iHelper;
/// 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.iParentPosition;
}
// ----------------------------------------------------------------------------
// Two-phase constructor
// ----------------------------------------------------------------------------
//
CXnGestureHelper* CXnGestureHelper::NewL( CXnNode& aNode )
{
CXnGestureHelper* self = new ( ELeave ) CXnGestureHelper( aNode );
CleanupStack::PushL( self );
self->iHoldingTimer = CHoldingTimer::NewL( *self );
self->iGesture = new ( ELeave ) CXnGesture();
CleanupStack::Pop( self );
return self;
}
// ----------------------------------------------------------------------------
// Constructor
// ----------------------------------------------------------------------------
//
CXnGestureHelper::CXnGestureHelper( CXnNode& aNode )
: iOwner( aNode )
{
}
// ----------------------------------------------------------------------------
// Destructor
// ----------------------------------------------------------------------------
//
CXnGestureHelper::~CXnGestureHelper()
{
delete iHoldingTimer;
delete iGesture;
}
// ----------------------------------------------------------------------------
// SetHoldingEnabled
// ----------------------------------------------------------------------------
//
void CXnGestureHelper::SetHoldingEnabled( TBool aEnabled )
{
iHoldingTimer->SetEnabled( aEnabled );
}
// ----------------------------------------------------------------------------
// IsHoldingEnabled
// ----------------------------------------------------------------------------
//
TBool CXnGestureHelper::IsHoldingEnabled() const
{
return iHoldingTimer->IsEnabled();
}
// ----------------------------------------------------------------------------
// Reset state
// ----------------------------------------------------------------------------
//
void CXnGestureHelper::Reset()
{
iHoldingTimer->Cancel();
iGesture->Reset();
}
/**
* Helper function that calls Reset on the pointer to CXnGestureHelper
*/
static void ResetHelper( TAny* aHelper )
{
static_cast< CXnGestureHelper* >( aHelper )->Reset();
}
// ----------------------------------------------------------------------------
// Sets gesture destination
// ----------------------------------------------------------------------------
//
void CXnGestureHelper::SetDestination( CXnNode* aDestination )
{
iDestination = aDestination;
}
// ----------------------------------------------------------------------------
// Gets gesture destination
// ----------------------------------------------------------------------------
//
CXnNode* CXnGestureHelper::Destination() const
{
return iDestination;
}
// ----------------------------------------------------------------------------
// Gets gesture owner
// ----------------------------------------------------------------------------
//
CXnNode* CXnGestureHelper::Owner() const
{
return &iOwner;
}
// ----------------------------------------------------------------------------
// Handle a pointer event
// ----------------------------------------------------------------------------
//
TSwipeResult CXnGestureHelper::HandlePointerEventL( const TPointerEvent& aEvent )
{
TSwipeResult ret = ESwipeNone;
switch ( aEvent.iType )
{
case TPointerEvent::EButton1Down:
// If no up event was received during previous gesture, cancel
// previous event and reset state
if ( !IsIdle() )
{
iGesture->SetCancelled();
// ambiguous what is the right thing when "cancel" event leaves
// and "start" does not. Leaving for cancel *after* "start" could
// be unexpected to client, as client would have handled start
// event successfully. Assume that leaving upon cancellation
// can be ignored.
Reset();
}
// adding the first point implicitly makes the state "not idle"
AddPointL( aEvent );
// If AddPointL leaves, IsIdle will return EFalse for other events
// types, hence further pointer events will be ignored.
// Therefore, holding will NOT be started if AddPointL leaves,
// since the callback would trigger a gesture callback, and that
// would access an empty points array.
iHoldingTimer->Start();
break;
case TPointerEvent::EDrag:
// ignore the event in case not in "recording" state. this may
// happen if holding was triggered, or client sends up event after
// down event was received in a different *client* state, and
// client did not forward the down event to here.
// Also, while stylus down, the same event is received repeatedly
// even if stylus does not move. Filter out by checking if point
// is the same as the latest point
if ( !IsIdle() && !iGesture->IsLatestPoint( Position( aEvent ) ) )
{
AddPointL( aEvent );
if ( !( iGesture->IsHolding() ||
iGesture->IsNearHoldingPoint( Position( aEvent ) ) ) )
{
// restart hold timer, since pointer has moved
iHoldingTimer->Start();
// Remember the point in which holding was started
iGesture->SetHoldingPoint();
}
}
break;
case TPointerEvent::EButton1Up:
// ignore up event if no down event received
if ( !IsIdle() )
{
// reset in case the down event is not received for a reason
// in client, and instead drag or up events are received.
// reset via cleanup stack to ensure Reset is run even if
// observer leaves
CleanupStack::PushL( TCleanupItem( &ResetHelper, this ) );
iGesture->SetComplete();
// if adding of the point fails, notify client with a
// cancelled event. It would be wrong to send another
// gesture code when the up point is not known
if ( AddPoint( aEvent ) != KErrNone )
{
iGesture->SetCancelled();
}
else
{
// send gesture code if holding has not been started
if ( !iGesture->IsHolding() )
{
// if client leaves, the state is automatically reset.
// In this case the client will not get the released event
ret = ValidSwipe();
}
// send an event that stylus was lifted
iGesture->SetReleased();
}
// reset state
CleanupStack::PopAndDestroy( this );
}
break;
default:
break;
}
return ret;
}
// ----------------------------------------------------------------------------
// Is the helper idle?
// inline ok in cpp file for a private member function
// ----------------------------------------------------------------------------
//
inline TBool CXnGestureHelper::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 CXnGestureHelper::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 CXnGestureHelper::AddPoint( const TPointerEvent& aEvent )
{
return iGesture->AddPoint( Position ( aEvent ) );
}
/**
* Helper function that calls ContinueHolding on the pointer to TGesture
*/
static void ContinueHolding( TAny* aGesture )
{
static_cast< CXnGesture* >( aGesture )->ContinueHolding();
}
// ----------------------------------------------------------------------------
// Add a point to the sequence of points that together make up the gesture
// ----------------------------------------------------------------------------
//
void CXnGestureHelper::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.
if ( !iGesture->IsTap() )
{
// 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 ) );
// set holding state to "post holding"
CleanupStack::PopAndDestroy( iGesture );
}
}
// ----------------------------------------------------------------------------
// Check if swipe is valid
// ----------------------------------------------------------------------------
//
TSwipeResult CXnGestureHelper::ValidSwipe()
{
TSwipeResult ret = ESwipeNone;
TBool validSwipe(ETrue);
// check if swipe is between defined values
TInt distanceX = Abs( iGesture->Distance().iX );
TInt speedX = Abs( static_cast< TInt >( iGesture->Speed().iX ) );
TInt minLength( iOwner.MarginRect().Width() / 2 );
TInt dy( Abs( iGesture->StartPos().iY - iGesture->CurrentPos().iY ) );
if ( distanceX < minLength )
{
validSwipe = EFalse;
}
if ( speedX < KGestureMinSpeedX )
{
validSwipe = EFalse;
}
if ( dy > KGestureMaxDeltaY )
{
validSwipe = EFalse;
}
// check the direction of swipe
if ( validSwipe )
{
switch ( iGesture->Code( CXnGesture::EAxisHorizontal ) )
{
case EGestureSwipeLeft:
ret = ESwipeLeft;
break;
case EGestureSwipeRight:
ret = ESwipeRight;
break;
default: // fall through
break;
}
}
return ret;
}