diff -r 6297cdf66332 -r d39add9822e2 webengine/webkitutils/stmgesturefw/src/stateengine.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/webengine/webkitutils/stmgesturefw/src/stateengine.cpp Tue Feb 02 00:56:45 2010 +0200 @@ -0,0 +1,1159 @@ +/* +* 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: +* +*/ + + +#include "stateengine.h" +#include "utils.h" +#include "uievent.h" +#include "uieventsender.h" +#include "filelogger.h" +//#include "flogger.h" + +using namespace stmUiEventEngine ; + +/*! + State definitions glue together the methods of the stateengine.cpp + so that it will behave as defined in the state machine specification. + First define the separate elements for each possible event and then tie them together + to create one state entry. The state entries then are put to array + where the index is at the same time also the state ID. + + STATE_ELEMENT arrays define the methods called when handling a message. + \sa STATE_ELEMENT. + Note that the last row of STATE_ELEMENT array must NOT have a ConditionFunction entry + and it must have a NextState entry != Eignore. Otherwise the state machine will + not behave correctly. + + */ +/*! Add macro with token pasting to make creation of the state machine tables easier + and removing the necessity to write the classname twice. + Maybe there would be some better way to do this using templates and typedefs? + */ +#define CND(x) isF +#define ACT(x) aF + +/********************************************************************************* + * empty statedef as a dummy entry + * */ +const STATE_ELEMENT __ErrorEvent[1] = { + 0, + ACT(ErrorEvent), + EInit +}; + +const STATE Ignore__[1] = { + EDown, __ErrorEvent +} ; + +/*! + :INIT state and its event specific elements + See the spec in http://wikis.in.nokia.com/Runtimes/NewGestureLibrary + Down is only valid event in :INIT state + The event is consumed immediately, so that the state machine will process only these + methods when processing the message. + If touch timer has been set, the next state is InTouchTime. + If no touch timer, but if hold timer has been defined, the next state is InHoldTime_U + If no touch or hold timer have been defined, but touch area has been defined, next state is InTouchArea. + 11-May-2009: addition: add another touch area: one for touch time and one for touch area after touch time + has elapsed. This allows "sloppy" touch to be handled properly without extra move if touchtimearea is larger, + but after touch has been detected a smaller movement is allowed. + */ +const STATE_ELEMENT Init__Down[12] = { + 0, ACT(ConsumeEvent), Eignore, + 0, ACT(SetGestureStart), Eignore, + 0, ACT(SetCurrentPos), Eignore, + CND(IsTouchTimer), ACT(InitTouchTimer), Eignore, + CND(IsHoldTimer), ACT(InitHoldTimer), Eignore, + CND(IsTouchTimeArea), ACT(PrepareTouchTimeArea),Eignore, + CND(IsHoldArea), ACT(PrepareHoldArea) ,Eignore, + CND(IsTouchTimer), 0, EInTouchTime, + 0, ACT(ProduceTouch), Eignore, + CND(IsHoldTimer), 0, EInHoldTime_U, + CND(IsTouchArea), ACT(PrepareTouchArea),EInTouchArea, + 0, 0, EDispatch // If nothing else happens, goto to Dispatch state +}; +/** + * All the rest of the events are errors so produce error entry to log and + * stay in the Init state + */ +const STATE_ELEMENT Init__ErrorEvent[2] = { + 0, ACT(ConsumeEvent), Eignore, // remember to consume event, otherwise state machine will loop... + 0, ACT(ErrorEvent), EInit +}; + +/*! + * :INIT + * note that only valid event is DOWN, all the rest can be handled as errors + */ +const STATE Init__[7] = { + EDown, Init__Down, + EDrag, Init__ErrorEvent, + ECapacitiveUP, Init__ErrorEvent, + EResistiveUP, Init__ErrorEvent, + ETouchTimer, Init__ErrorEvent, + EHoldTimer, Init__ErrorEvent, + ESuppressTimer, Init__ErrorEvent +}; + +/*! + * :Dispatch state end its elements + * Here the valid events are DRAG and the UP events. + */ +const STATE_ELEMENT Dispatch__Drag[7] = { + 0, ACT(StoreMovePos), Eignore, + 0, ACT(ConsumeEvent), Eignore, + 0, ACT(SetCurrentPos), Eignore, + 0, ACT(AddDraggingPos), Eignore, + 0, ACT(ProduceMove), Eignore, + CND(LooksLikeHold), ACT(InitHoldTimer),EInHoldTime_U, + 0, 0, EDispatch +} ; + +const STATE_ELEMENT Dispatch__CapacitiveUp[3] = { + 0, ACT(ConsumeEvent), Eignore, + 0, ACT(SetCurrentPos), Eignore, + 0, ACT(ProduceRelease), EInit +} ; + +const STATE_ELEMENT Dispatch__ResistiveUp[4] = { + 0, ACT(ConsumeEvent), Eignore, + 0, ACT(SetCurrentPos), Eignore, + CND(IsSuppressTimer),ACT(InitMoveSuppressTimer), ESuppress_D, + 0, ACT(ProduceRelease), EInit +} ; + +/*! + * All the rest of the events are errors so produce error entry to log and + * stay in the Dispatch state + * (TODO: note that in the future we may further + * define the error cases so that they may change state; ) + */ +const STATE_ELEMENT DispatchErrorEvent[2] = { + 0, ACT(ConsumeEvent), Eignore, // remember to consume event, otherwise state machine will loop... + 0, ACT(ErrorEvent), EDispatch +}; + +const STATE Dispatch__[7] = { + EDown, DispatchErrorEvent, + EDrag, Dispatch__Drag, + ECapacitiveUP, Dispatch__CapacitiveUp, + EResistiveUP, Dispatch__ResistiveUp, + ETouchTimer, DispatchErrorEvent, + EHoldTimer, DispatchErrorEvent, + ESuppressTimer, DispatchErrorEvent +}; + +/*! + * :InTouchTime state end its elements + * Here the valid events are DRAG and the UP events and the TouchTimer + */ +const STATE_ELEMENT InTouchTime__Drag[6] = { + 0, ACT(StoreMovePos), Eignore, + CND(InsideTouchTimeArea), ACT(ConsumeEvent), Eignore, + CND(InsideTouchTimeArea), ACT(AddToTouch), EInTouchTime, // Calculate touch XY as average of the touches + 0, ACT(ClearTouchTimer), Eignore, // These lines are done only if InsideTouchArea returns false + 0, ACT(ClearHoldTimer), Eignore, + 0, ACT(ProduceTouch), EDispatch + +} ; +/** + * Note that ConsumeEvent is missing so after doing this the state engine will do EDispatch + */ +const STATE_ELEMENT InTouchTime__CapacitiveUp[4] = { + 0, ACT(SetCurrentPos), Eignore, + 0, ACT(ClearTouchTimer), Eignore, + 0, ACT(ClearHoldTimer), Eignore, + 0, ACT(ProduceTouch), EDispatch +} ; +/** + * Note that ConsumeEvent is not called if IsHoldTimer returns false, so the Dispatch will be done + * by the state machine. + */ +const STATE_ELEMENT InTouchTime__ResistiveUp[5] = { + 0, ACT(SetCurrentPos), Eignore, + 0, ACT(ClearTouchTimer), Eignore, + 0, ACT(ProduceTouch), Eignore, + CND(IsHoldTimer), 0, /*ACT(ConsumeEvent),*/EInHoldTime_U, // Note that otherwise immediate UP is handled improperly + 0, 0, EDispatch +} ; + +const STATE_ELEMENT InTouchTime__TouchTimer[6] = { + 0, ACT(ConsumeEvent), Eignore, + 0, ACT(ClearTouchTimer), Eignore, + 0, ACT(ProduceTouch), Eignore, + CND(IsTouchArea), ACT(PrepareTouchArea),Eignore, // prepare the other touch area + CND(IsHoldTimer), 0, EInHoldTime_U, + 0, 0, EInTouchArea +} ; + + +/** + * All the rest of the events are errors so produce error entry to log and + * stay in the InTouchTime state + */ +const STATE_ELEMENT InTouchTimeErrorEvent[2] = { + 0, ACT(ConsumeEvent), Eignore, // remember to consume event, otherwise state machine will loop... + 0, ACT(ErrorEvent), EInTouchTime +}; + +const STATE InTouchTime__[7] = { + EDown, InTouchTimeErrorEvent, + EDrag, InTouchTime__Drag, + ECapacitiveUP, InTouchTime__CapacitiveUp, + EResistiveUP, InTouchTime__ResistiveUp, + ETouchTimer, InTouchTime__TouchTimer, + EHoldTimer, InTouchTimeErrorEvent, + ESuppressTimer, InTouchTimeErrorEvent +}; + +/*! + * :InHoldTime_U state end its elements + * Here only touch timer event is invalid + */ + +const STATE_ELEMENT InHoldTime_U__Down[1] = { + 0, 0, EInHoldTime_D // Note that ConsumeEvent is not called +} ; + +const STATE_ELEMENT InHoldTime_U__Drag[3] = { + 0, ACT(StoreMovePos), Eignore, + CND(InsideHoldArea), ACT(ConsumeEvent), EInHoldTime_U, + 0, ACT(ClearHoldTimer), EDispatch // Note that in this case ConsumeEvent is not called +} ; +/** + * Note that ConsumeEvent is missing so after doing this the state engine will do EDispatch + */ +const STATE_ELEMENT InHoldTime_U__CapacitiveUp[1] = { + 0, ACT(ClearHoldTimer), EDispatch // Note that ConsumeEvent not called +} ; +/** + * + */ +const STATE_ELEMENT InHoldTime_U__ResistiveUp[5] = { + 0, ACT(ConsumeEvent), Eignore, + 0, ACT(SetCurrentPos), Eignore, + CND(IsSuppressTimer),ACT(InitTouchSuppressTimer), EInHoldTime_D, // If suppression, start timer and wait for down or timer + 0, ACT(ClearHoldTimer), Eignore, // remember to do this + 0, ACT(ProduceRelease), EInit // No suppression, then this is immediate release +} ; + +const STATE_ELEMENT InHoldTime_U__HoldTimer[3] = { + 0, ACT(ConsumeEvent), Eignore, + 0, ACT(ProduceHold), Eignore, + 0, ACT(RestartHoldTimer), EInHoldTime_U, +} ; + +const STATE_ELEMENT InHoldTime_U__SuppressTimer[2] = { + 0, ACT(ConsumeEvent), Eignore, // remember to consume event, otherwise state machine will loop... + 0, ACT(ErrorEvent), EInHoldTime_U +} ; + + +/** + * All the rest of the events are errors so produce error entry to log and + * stay in the InHoldTime_U state + */ +const STATE_ELEMENT InHoldTime_UErrorEvent[2] = { + 0, ACT(ConsumeEvent), Eignore, // remember to consume event, otherwise state machine will loop... + 0, ACT(ErrorEvent), EInHoldTime_U +}; + +const STATE InHoldTime_U__[7] = { + EDown, InHoldTime_U__Down, + EDrag, InHoldTime_U__Drag, + ECapacitiveUP, InHoldTime_U__CapacitiveUp, + EResistiveUP, InHoldTime_U__ResistiveUp, + ETouchTimer, InHoldTime_UErrorEvent, + EHoldTimer, InHoldTime_U__HoldTimer, + ESuppressTimer, InHoldTime_U__SuppressTimer +}; + + +/*! + * :InHoldTime_D state end its elements + * Here drag, touch timer and suppress timer events are invalid + */ + +const STATE_ELEMENT InHoldTime_D__Down[5] = { + 0, ACT(ClearSuppressTimer), Eignore, + 0, ACT(ConsumeEvent), Eignore, + CND(InsideHoldArea), 0, EInHoldTime_U, + 0, ACT(ClearHoldTimer), Eignore, + 0, ACT(ProduceMove), EDispatch +} ; + +/** + * Note that ConsumeEvent is missing so after doing this the state engine will do InHoldTime_U + */ +const STATE_ELEMENT InHoldTime_D__CapacitiveUp[1] = { + 0, 0, EInHoldTime_U +} ; +/** + * Note that ConsumeEvent is missing so after doing this the state engine will do InHoldTime_U + */ +const STATE_ELEMENT InHoldTime_D__ResistiveUp[1] = { + 0, 0, EInHoldTime_U // InHoldTime_U initialises timers etc. if needed +} ; +/*! + * In case of hold timer has been elapsed stop the timers, generate Release UI event. + */ +const STATE_ELEMENT InHoldTime_D__HoldTimer[4] = { + 0, ACT(ConsumeEvent), Eignore, + 0, ACT(ClearSuppressTimer), Eignore, + 0, ACT(ClearHoldTimer), Eignore, + 0, ACT(ProduceRelease), EInit, +} ; +/*! + * If suppress timer hits, stop the timers and generate Release UI event. + */ +const STATE_ELEMENT InHoldTime_D__SuppressTimer[4] = { + 0, ACT(ConsumeEvent), Eignore, + 0, ACT(ClearSuppressTimer), Eignore, + 0, ACT(ClearHoldTimer), Eignore, + 0, ACT(ProduceRelease), EInit, +} ; + +/** + * All the rest of the events are errors so produce error entry to log and + * stay in the InHoldTime_D state + */ +const STATE_ELEMENT InHoldTime_DErrorEvent[2] = { + 0, ACT(ConsumeEvent), Eignore, // remember to consume event, otherwise state machine will loop... + 0, ACT(ErrorEvent), EInHoldTime_D +}; + +const STATE InHoldTime_D__[7] = { + EDown, InHoldTime_D__Down, + EDrag, InHoldTime_DErrorEvent, + ECapacitiveUP, InHoldTime_D__CapacitiveUp, + EResistiveUP, InHoldTime_D__ResistiveUp, + ETouchTimer, InHoldTime_DErrorEvent, + EHoldTimer, InHoldTime_D__HoldTimer, + ESuppressTimer, InHoldTime_D__SuppressTimer +}; + + +/*! + * :InTouchArea state end its elements + * Here Drag and Up events are valid. + * If drag is inside touch are it is ignored, otherwise + * the Dispatch state will handle the event. + */ + +const STATE_ELEMENT InTouchArea__Drag[3] = { + 0, ACT(StoreMovePos), Eignore, + CND(InsideTouchArea), ACT(ConsumeEvent), EInTouchArea, + 0, 0, EDispatch // Note that in this case ConsumeEvent has not been called so Dispatch state processes the message +} ; + +/** + * Note that ConsumeEvent is missing so after doing this the state engine will do Dispatch + */ +const STATE_ELEMENT InTouchArea__CapacitiveUp[1] = { + 0, 0, EDispatch +} ; +/** + * Note that ConsumeEvent is missing so after doing this the state engine will do Dispatch + */ +const STATE_ELEMENT InTouchArea__ResistiveUp[1] = { + 0, 0, EDispatch +} ; + +/** + * All the rest of the events are errors so produce error entry to log and + * stay in the InTouchArea state + */ +const STATE_ELEMENT InTouchAreaErrorEvent[2] = { + 0, ACT(ConsumeEvent), Eignore, // remember to consume event, otherwise state machine will loop... + 0, ACT(ErrorEvent), EInTouchArea +}; + +const STATE InTouchArea__[7] = { + EDown, InTouchAreaErrorEvent, + EDrag, InTouchArea__Drag, + ECapacitiveUP, InTouchArea__CapacitiveUp, + EResistiveUP, InTouchArea__ResistiveUp, + ETouchTimer, InTouchAreaErrorEvent, + EHoldTimer, InTouchAreaErrorEvent, + ESuppressTimer, InTouchAreaErrorEvent +}; + + +/*! + * :Suppress_D state end its elements + * Here Down and suppress timers are OK. + */ + +/*! + * Down will be handled as a Drag event in the Dispatch state. + */ +const STATE_ELEMENT Suppress_D__Down[4] = { + 0, ACT(ClearSuppressTimer), Eignore, + 0, ACT(RenameToDrag), EDispatch +} ; +/*! + * Suppress timer will generate Release UI event. + */ +const STATE_ELEMENT Suppress_D__SuppressTimer[3] = { + 0, ACT(ConsumeEvent), Eignore, + 0, ACT(ClearSuppressTimer), Eignore, + 0, ACT(ProduceRelease), EInit, +} ; + +/** + * All the rest of the events are errors so produce error entry to log and + * stay in the Suppress_D state + */ +const STATE_ELEMENT Suppress_DErrorEvent[2] = { + 0, ACT(ConsumeEvent), Eignore, // remember to consume event, otherwise state machine will loop... + 0, ACT(ErrorEvent), ESuppress_D +}; + +const STATE Suppress_D__[7] = { + EDown, Suppress_D__Down, + EDrag, Suppress_DErrorEvent, + ECapacitiveUP, Suppress_DErrorEvent, + EResistiveUP, Suppress_DErrorEvent, + ETouchTimer, Suppress_DErrorEvent, + EHoldTimer, Suppress_DErrorEvent, + ESuppressTimer, Suppress_D__SuppressTimer +}; +/*! + * The allStates array contains all the possible states of the state machine. + */ +const STATE* const allStates[8] = +{ + Ignore__, + Init__, + Dispatch__, + InTouchTime__, + InHoldTime_U__, + InHoldTime_D__, + InTouchArea__, + Suppress_D__ +}; +/*! + * stateNames are used in the logging + */ +const char* const stateNames[8] = +{ + "Ignore", + "Init", + "Dispatch", + "InTouchTime", + "InHoldTime_U", + "InHoldTime_D", + "InTouchArea", + "Suppress" +}; + +// event names are also used in logging +const char* const hweventNames[] = { + "EDown", + "EDrag", + "ECapacitiveUP", + "EResistiveUP", + "ETouchTimer", + "EHoldTimer", + "ESuppressTimer" +} ; + +/*! CStateEngine contains the methods used in the state machine implementation. + * + * The methods in CStateEngine used in the state machine definition are + * either condition methods or action methods. + * + * Constructor + * \param[in]: MTimerInterface atimerif. An attempt to make this more OS agnostic the actual + * timers are accessed using a separate interface. + */ +CStateEngine::CStateEngine(CStateEngineConfiguration* aConfig, MTimerInterface* atimerif, int aIndex) +{ + m_config = aConfig ; + m_timerif = atimerif ; + m_currentState = EInit ; + m_index = aIndex ; +} + +CStateEngine::~CStateEngine() +{ + // Just to be sure... + iTouchPoints.Reset() ; + iDragPoints.ResetAndDestroy() ; +} +/*! + * ConsumeEvent: the method defines that the turnStateMachine will stop the processing + * of the state methods after it has reached the next state. + * + */ +void CStateEngine::ConsumeEvent() +{ + m_eventConsumed = true ; +} +/*! + * Condition method + * \return true, if the touch timer limit > 0 + */ +bool CStateEngine::IsTouchTimer() +{ + bool isit = (m_config->m_touchTimerLimit > 0) ; + + return isit ; +} +/*! + * Condition method + * \return true, if the hold timer limit > 0 + */ +bool CStateEngine::IsHoldTimer() +{ + bool isit = (m_config->m_holdTimerLimit > 0) ; + + return isit ; +} +/*! + * Condition method + * \return true, if the suppress timer limit > 0 + */ +bool CStateEngine::IsSuppressTimer() +{ + bool isit = (m_config->m_suppressTimerLimit > 0) ; + + return isit ; +} +/*! + * Condition method + * \return true, if the touch area has been defined (the touch tolerancelength > 0) + */ +bool CStateEngine::IsTouchTimeArea() +{ + bool isit = (m_config->m_touchTimeTolerance.iX > 0) ; + return isit ; +} +/*! + * Condition method + * \return true, if the touch area has been defined (the touch tolerancelength > 0) + */ +bool CStateEngine::IsTouchArea() +{ + bool isit = (m_config->m_touchTolerance.iX > 0) ; + return isit ; +} +/*! + * Condition method + * \return true, if the hold area has been defined (the hold tolerancelength > 0) + */ +bool CStateEngine::IsHoldArea() +{ + bool isit = (m_config->m_holdTolerance.iX > 0) ; + return isit ; +} + +bool CStateEngine::InsideArea(const TPoint& point, + const TRect& rect, + TAreaShape shape, + const TPoint& tolerance) +{ + bool isit; + switch(shape) + { + default: // pass trough + case ERectangle: + { + isit = rect.Contains(m_hwe.iPosition) ; + break ; + } + case ECircle: + { + TPoint delta = m_hwe.iPosition - point; + long circlepoint = delta.iX * delta.iX + delta.iY * delta.iY; + isit = (circlepoint < tolerance.iX * tolerance.iX); + break ; + } + case EEllipse: + { + int asquare = tolerance.iX * tolerance.iX ; + int bsquare = tolerance.iY * tolerance.iY ; + TPoint delta = m_hwe.iPosition - point; + int result = (delta.iX * delta.iX) * bsquare + (delta.iY * delta.iY) * asquare; + + isit = (result < asquare * bsquare); + break ; + } + } + return isit ; +} + +/*! + * Condition method + * Check if the current event is positioned inside the touch area. + * Touch area can be a rectangle, a circle or an ellipse, so different + * calculation needs to be done based on the shape of the area. + */ +bool CStateEngine::InsideTouchTimeArea() +{ + return InsideArea(m_touchCentre, m_touchRect, + m_config->m_touchAreaShape, m_config->m_touchTimeTolerance); +} +/*! + * Condition method + * Check if the current event is positioned inside the touch area. + * Touch area can be a rectangle, a circle or an ellipse, so different + * calculation needs to be done based on the shape of the area. + */ +bool CStateEngine::InsideTouchArea() +{ + return InsideArea(m_touchCentre, m_touchRect, + m_config->m_touchAreaShape, m_config->m_touchTolerance); +} +/*! + * Condition method + * Check if the current event is positioned inside the hold area. + * Hold area can be a rectangle, a circle or an ellipse, so different + * calculation needs to be done based on the shape of the area. + */ +bool CStateEngine::InsideHoldArea() +{ + return InsideArea(m_holdCentre, m_holdRect, + m_config->m_holdAreaShape, m_config->m_holdTolerance); +} +/*! + * Condition method + * Check if the gesture looks like a hold, i.e. the movement has stopped. + * \sa isNewHoldingPoint + */ +bool CStateEngine::LooksLikeHold() +{ + bool isit = isNewHoldingPoint() ; + return isit ; +} +/*! + * Action method + * Error logging. + */ +void CStateEngine::ErrorEvent() +{ + // Log the error + if (m_config->m_enableLogging) + { + LOGARG("ErrorEvent: %s %s", stateNames[m_currentState], hweventNames[m_hwe.iType]) ; + } +} +/*! + * Action method + * Initialize touch timer. At the same time calculate also the touch rectangle. + */ +void CStateEngine::InitTouchTimer() +{ + m_touchRect = ToleranceRect(m_hwe.iPosition, m_config->m_touchTolerance) ; + m_touchCentre = m_hwe.iPosition ; + m_timerif->startTouchTimer(m_config->m_touchTimerLimit, m_index) ; +} +/*! + * Action method. + * Initialize hold timer. At the same time calculate also the hold rectangle. + */ +void CStateEngine::InitHoldTimer() +{ + m_holdRect = ToleranceRect(m_hwe.iPosition, m_config->m_holdTolerance) ; + m_holdCentre = m_hwe.iPosition ; + m_timerif->startHoldTimer(m_config->m_holdTimerLimit, m_index) ; +} +/*! + * Action method + * Restart the hold timer using the hold timer limit. + */ +void CStateEngine::RestartHoldTimer() +{ + m_timerif->startHoldTimer(m_config->m_holdTimerLimit, m_index) ; +} +/*! + * Action method + * Initialize suppression timer. This timer is used during touch detection when + * resistive UP has been detected. If new DOWN comes while timer is running, it is ignored. + */ +void CStateEngine::InitTouchSuppressTimer() +{ + m_timerif->startSuppressTimer(m_config->m_suppressTimerLimit, m_index) ; +} +/*! + * Action method. + * Initialize suppression timer after move. Tests show that when user is using light touch and + * moving finger to opposite directions there may be accidental ups and downs where the time between + * up and down may be well over 120 ms. + */ +void CStateEngine::InitMoveSuppressTimer() +{ + m_timerif->startSuppressTimer(m_config->m_moveSuppressTimerLimit, m_index) ; +} +/*! + * Action method + * Stop the touch timer. + */ +void CStateEngine::ClearTouchTimer() +{ + m_timerif->cancelTouchTimer(m_index) ; +} +/*! + * Action method + * Stop the hold timer. + */ +void CStateEngine::ClearHoldTimer() +{ + m_timerif->cancelHoldTimer(m_index) ; +} +/*! + * Action method + * Stop the suppress timer. + */ +void CStateEngine::ClearSuppressTimer() +{ + m_timerif->cancelSuppressTimer(m_index) ; +} +/*!Helper method. + * Create UI event + * \param code The new UI event type (Touch, Release, Move, Hold) + */ +CUiEvent* CStateEngine::createUIEventL(TUiEventCode code, const TPoint& aPos) +{ + + m_previousUiGenerated = code ; + return CUiEvent::NewL(code, m_gestureStartXY, aPos, getPreviousXY(aPos), + isTimerMessage(), m_hwe.iTarget, getInterval(), m_index, m_hwe.iTime.Int64()) ; +} +/*! + * Return the previous XY position and store the current for next round + */ +TPoint CStateEngine::getPreviousXY(const TPoint& aCurrentXY) +{ + TPoint p = m_previousXY ; + m_previousXY = aCurrentXY ; + return p ; +} +/*! + * \return true, if the current event was timer triggered + */ +bool CStateEngine::isTimerMessage() +{ + return (m_hwe.iType >= ETouchTimer); // NOTE: if new events are added at the end of the list this needs to be changed +} +/*! + * Action method. + * Generate the Touch UI event. + * If there are a set of touch points collected, calculate the position to the + * Touch UI event to be the average of the collected points. + */ +void CStateEngine::ProduceTouch() +{ + m_wasFiltered = false ; + CUiEvent* cue = NULL; + getInterval() ; // dummy call to initialize the variable.... + TInt err(KErrNone); + if (iTouchPoints.Count()>0) + { + // calculate average of the touch points + m_currentTouchXY = calculateTouchAverageFromPoints() ; + TRAP(err, cue = createUIEventL(stmUiEventEngine::ETouch, m_currentTouchXY)) ; + } + else + { + TRAP(err, cue = createUIEventL(stmUiEventEngine::ETouch, m_uiEventXY)) ; + } + if(!err) + m_config->m_uiEventSender->AddEvent(cue) ; +} +/*! + * Action method + * Generate the Move UI event. The position of the event has been set in the SetCurrentPos + * The previous position needs some special handling, if filtering has been used. + * \sa SetCurrentPos + */ +void CStateEngine::ProduceMove() +{ + m_wasFiltered = false ; + if (m_uiEventXY == m_previousXY) { + return; + } + CUiEvent* cue = NULL; + TRAPD(err, cue = createUIEventL(stmUiEventEngine::EMove, m_uiEventXY)) ; + + if(!err) + m_config->m_uiEventSender->AddEvent(cue) ; +} +/*! + * Action method + * Generate the Release UI event. + */ +void CStateEngine::ProduceRelease() +{ + m_wasFiltered = false ; + CUiEvent* cue = NULL; + TRAPD(err, cue = createUIEventL(stmUiEventEngine::ERelease, m_uiEventXY)) ; + if(!err) + m_config->m_uiEventSender->AddEvent(cue) ; + + if (m_config->m_enableLogging) + { + LOGFLUSH ; + } +} +/*! + * Action method + * Generate the Hold UI event. + */ +void CStateEngine::ProduceHold() +{ + m_wasFiltered = false ; + CUiEvent* cue = NULL; + TRAPD(err, cue = createUIEventL(stmUiEventEngine::EHold, m_holdCentre)) ; + if(!err) + m_config->m_uiEventSender->AddEvent(cue) ; + +} +/*! + * Action method + * Rename the current event to drag. This is used when the accidental up/down message pair + * has been detected, the DOWN event is handled as it were a move event. + */ +void CStateEngine::RenameToDrag() +{ + m_hwe.iType = stmUiEventEngine::EDrag ; +} +/*! + * Action method + * Initialize the touch time area. Clear the array for collected touch points and + * calculate the touch rectangle. + */ +void CStateEngine::PrepareTouchTimeArea() +{ + if (iTouchPoints.Count()>0) iTouchPoints.Reset() ; + m_touchRect = ToleranceRect(m_hwe.iPosition, m_config->m_touchTimeTolerance) ; +} + +/*! + * Action method + * Initialize the touch area. Clear the array for collected touch points and + * calculate the touch rectangle. + */ +void CStateEngine::PrepareTouchArea() +{ + if (iTouchPoints.Count()>0) iTouchPoints.Reset() ; + m_touchRect = ToleranceRect(m_hwe.iPosition, m_config->m_touchTolerance) ; +} +/*! + * Action method + * Initialize the hold area rectangle. + */ +void CStateEngine::PrepareHoldArea() +{ + m_holdRect = ToleranceRect(m_hwe.iPosition, m_config->m_holdTolerance) ; +} +/*! + * Action method + * Store the current position and time always when we see EDrag. The stored value is used + * to calculate correct speed after filtered messages. + */ +void CStateEngine::StoreMovePos() +{ + if (m_config->m_enableLogging) + { + LOGARG("store move pos from (%d, %d) to (%d, %d)", + m_lastFilteredPosition.iX, m_lastFilteredPosition.iY,m_hwe.iPosition.iX, + m_hwe.iPosition.iY ) ; + } + m_lastFilteredPosition = m_hwe.iPosition ; + m_lastFilteredMessageTime = m_hwe.iTime ; + +} +/*! + * Action method + * Store the current position and time. + */ +void CStateEngine::SetCurrentPos() +{ + m_uiEventXY = m_hwe.iPosition ; +} +/*! + * Action method + * Initialize the gesture starting. + */ +void CStateEngine::SetGestureStart() +{ + m_gestureStartXY = m_hwe.iPosition ; + m_previousXY = m_hwe.iPosition ; + m_gestureTarget = m_hwe.iTarget ; + iDragPoints.ResetAndDestroy() ; + iTouchPoints.Reset() ; +} +/*! + * Action method + * Add current point to the set of touch points. + */ +void CStateEngine::AddToTouch() +{ + iTouchPoints.Append(THwEvent(m_hwe.iType, + m_hwe.iPosition, + m_hwe.iTime, + m_hwe.iTarget, + m_index) + ) ; + // calculate the average of touch points and move the touch area accordingly + // this allows slight movement of the figertip while inside touch time + if (iTouchPoints.Count()>2) + { + TPoint newtp = calculateTouchAverageFromPoints() ; + m_touchRect = ToleranceRect(newtp, m_config->m_touchTolerance) ; + m_holdRect = ToleranceRect(newtp, m_config->m_holdTolerance) ; + } +} +/*! + * Action method + * Add the current point to the set of dragging points. + * The set of dragging points is examined to determine if a enw hold has been started. + */ +void CStateEngine::AddDraggingPos() +{ + iDragPoints.Append(new THwEvent(m_hwe.iType, + m_hwe.iPosition, + m_hwe.iTime, + m_hwe.iTarget, + m_index) + ) ; +} +/*! + * HandleStateEvent processes one event, which can be either pointer event or timer event. + * The event is handled by calling the turnStateMachine method. + */ +bool CStateEngine::handleStateEvent() +{ + // We get an event into m_hwe by this moment, lets kick the state machine + m_wasFiltered = ETrue ; + + CalculateDelta() ; + turnStateMachine() ; + + m_previousPointerEventPosition = m_hwe.iPosition ; + return m_wasFiltered ; +} + +/*! + * Get the current touch rectangle. If touch state not currently on, returns TRect(TPoint(0,0),TPoint(0,0)) + * (touch state meaning that the touch timer is still running and the points have been kept inside the area) + */ +TRect CStateEngine::getTouchArea() +{ + return m_touchRect ; +} +/*! + * get the hold area rectangle + */ +TRect CStateEngine::getHoldArea() +{ + return m_holdRect ; +} +/*! + * MStateMachine method. + */ +bool CStateEngine::wasLastMessageFiltered() +{ + return m_wasFiltered ; +} + +/*! + * Check if the last X points in the stored points look like the movement has stopped + */ +bool CStateEngine::isNewHoldingPoint() +{ + int x = iDragPoints.Count(); + if (x > 2) // are there any points to be checked? + { + THwEvent* phwe = iDragPoints[x-1] ; + THwEvent* phweinsidehold = phwe ; + TRect recth = ToleranceRect(phwe->iPosition, m_config->m_holdTolerance) ; + // Look backwards from the last point to see if there are enought points (enough in time) to look like a hold + x -= 2 ; + while (x > 0 && recth.Contains(iDragPoints[x]->iPosition)) + { + phweinsidehold = iDragPoints[x]; + --x; + } + TTimeIntervalMicroSeconds tival = phwe->iTime.MicroSecondsFrom(phweinsidehold->iTime) ; + + /** + * remove the extra points from the list if they are outside of holding area + */ + while (x > 0) + { + THwEvent* p = iDragPoints[x] ; + delete p ; + iDragPoints.Remove(x) ; + --x ; + } + + // See the time difference of the two points which still are inside the hold area + TTimeIntervalMicroSeconds limit = m_config->m_holdTimerLimit/2 ; + if (tival > limit) + { + if (m_config->m_enableLogging) + { + LOGARG("isNewHoldingPoint: %s, dragpoints count %d", + stateNames[m_currentState], iDragPoints.Count()) ; + } + return true ; + } + } + else + { + // one or 0 points does not look like hold + + } + return false ; +} +/*! + * calculate simple average of the touch points, i.e. calculate the average of the previous and current + * position. Note that the touch point remains the same, this just calculates new value for the UI position + */ +void CStateEngine::CalculateTouchAverage() +{ + m_uiEventXY.iX = (m_uiEventXY.iX+m_hwe.iPosition.iX)/2 ; + m_uiEventXY.iY = (m_uiEventXY.iY+m_hwe.iPosition.iY)/2 ; +} +/*! + * Calculate the movement vector. + */ +void CStateEngine::CalculateDelta() +{ + m_deltaVector.iX = m_hwe.iPosition.iX-m_previousPointerEventPosition.iX ; + m_deltaVector.iY = m_hwe.iPosition.iY-m_previousPointerEventPosition.iY ; +} +/*!internal + * Debug logging method + */ +void CStateEngine::DebugPrintState(TStateMachineState anextstate) +{ + if (m_config->m_enableLogging) + { + LOGARG("%s: cuiev(%d,%d) cTxy ((%d,%d)(%d,%d)) cHxy ((%d,%d)(%d,%d)) gsXY(%d,%d) dV(%d,%d) EVNT(%d,%d (%s)) going to %s", + stateNames[m_currentState], + m_uiEventXY.iX, m_uiEventXY.iY, + m_touchRect.iTl.iX, m_touchRect.iTl.iY,m_touchRect.iBr.iX, m_touchRect.iBr.iY, + m_holdRect.iTl.iX, m_holdRect.iTl.iY,m_holdRect.iBr.iX, m_holdRect.iBr.iY, + m_gestureStartXY.iX, m_gestureStartXY.iY, + m_deltaVector.iX, m_deltaVector.iY, + m_hwe.iPosition.iX, m_hwe.iPosition.iY, hweventNames[m_hwe.iType], + stateNames[anextstate] + ); + } +} + +/*! + * calculate the rectangle for touch or hold + */ +TRect CStateEngine::ToleranceRect(const TPoint& aCenterPoint, const TPoint& tolerance) +{ + // grow by the tolerance length, while keeping the center point + TRect toleranceRect( + aCenterPoint - tolerance, + aCenterPoint + tolerance); + return toleranceRect; +} +/*! + * turnStateMachine. Go trough the state elements found for the current event + * until the event has been consumed. + * + * \pre m_currentState defines the current state and the index to the allStates array. + * \pre m_hwe is the message being handled. The corresponding STATE_ELEMENT array must be found and processed. + * + */ +void CStateEngine::turnStateMachine() +{ + + const STATE_ELEMENT* pelement ; + m_eventConsumed = false ; // run the loop until the event has been consumed + // Now run trough the motions of the state elements, and prepare to change to next state while doing so. + // If the state elements set the m_eventConsumed then all is done + while (!m_eventConsumed) + { + int i = 0 ; + const STATE* const pcurrentstate = allStates[m_currentState] ; + // Since each state definition must contain entries for all possible events the following loop cannot fail ;-) + while (pcurrentstate[i].theEvent != m_hwe.iType ) ++i ; + pelement = pcurrentstate[i].stateElements ; + TStateMachineState nextState = Eignore ; + /* + * Handle the individual state elements. If there is a condition function, + * call the function and if it returns true, handle the action function and possible next state + * if the condition returns false, continue to next element + * if there is no condition, run the action function if it exists. + * if the next state is defined (i.e it is != Eignore), go to that state + */ + while (nextState == Eignore) + { + condition_t cndfunc = pelement->conditionFunction ; + action_t actfunc = pelement->actionFunction ; + if (cndfunc != 0) + { + /* + * There was a condition function, call it to see whether the action needs to performed and/or the next satte defined + */ + if (cndfunc(this)) + { + // Condition was true, handle it + // call the action if it exists + if (actfunc != 0) actfunc(this) ; + // and now get to the next state + nextState = pelement->nextState ; // Note that while this remains Eignore there are elements to be run + } + } + else + { + /** + * No condition function, call the possible action function and get the next state + */ + if (actfunc != 0) actfunc(this) ; + nextState = pelement->nextState ; // Note that while this remains Eignore there are elements to be run + } + ++pelement ; // next entry in the elements + } + if (m_config->m_enableLogging) DebugPrintState(nextState) ; + m_currentState = nextState ; // Change to the next state + } +} +TTimeIntervalMicroSeconds CStateEngine::getInterval() +{ + TTime now ; + now.HomeTime() ; + TTimeIntervalMicroSeconds interval = now.MicroSecondsFrom(m_lastMessageTime) ; + m_lastMessageTime = now ; + return interval ; +} + +TPoint CStateEngine::calculateTouchAverageFromPoints() +{ + TPoint tp ; + int count = iTouchPoints.Count() ; + for (int i = 0; i < count; i++) + { + tp += iTouchPoints[i].iPosition; + } + if(count) + { + tp.iX /= count ; + tp.iY /= count ; + } + return tp ; +} +