 
/******************************************************************************
*                                                                             *
* Tut_Mngr.cpp : definition of the low level functions of the                 *
*                TUT_CTutorialManager class                                   *
*                                                                             *
* This class can take the control of all windows of the application           *
* to create a tutorial or an automatic test                                   *
*                                                                             *
* Author : Alexis Vaisse                                                      *
*                                                                             *
******************************************************************************/


//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_csGetTreeCtrlItemText
// Get the text of one item of a tree ctrl
//-----------------------------------------------------------------------------
CString TUT_CTutorialManager::m_fn_csGetTreeCtrlItemText (HWND _hTreeCtrl , HTREEITEM _hItem)
{
    char szBuffer [256];
    * szBuffer = 0;
    TV_ITEM Item;
    Item . mask       = TVIF_TEXT;
    Item . hItem      = _hItem;
    Item . pszText    = szBuffer;
    Item . cchTextMax = 255;
    TreeView_GetItem (_hTreeCtrl , & Item);
    return szBuffer;
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_uiGetTreeCtrlState
// Get the state of one item of a tree ctrl
//-----------------------------------------------------------------------------
UINT TUT_CTutorialManager::m_fn_uiGetTreeCtrlState (HWND _hTreeCtrl , HTREEITEM _hItem , UINT _uiFlag)
{
    TV_ITEM Item;
    Item . mask      = TVIF_STATE;
    Item . hItem     = _hItem;
    Item . stateMask = _uiFlag;
    TreeView_GetItem (_hTreeCtrl , & Item);
    return Item . state;
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_hFindStringInTree
// Recursiv function to find a string into a tree
//-----------------------------------------------------------------------------
HTREEITEM TUT_CTutorialManager::m_fn_hFindStringInTree (HWND _hTreeCtrl , HTREEITEM _hItem , CString _csText)
{
    while (_hItem)
    {
        // Is it the good item ?
        if (m_fn_csGetTreeCtrlItemText (_hTreeCtrl , _hItem) == _csText)  return _hItem;

        // Get the first child and recursiv call
        HTREEITEM _hChildItem = TreeView_GetChild (_hTreeCtrl , _hItem);
        if (_hChildItem)
        {
            HTREEITEM hResultItem = m_fn_hFindStringInTree (_hTreeCtrl , _hChildItem , _csText);
            if (hResultItem)  return hResultItem;
        }

        // Get the next brother
        _hItem = TreeView_GetNextItem (_hTreeCtrl , _hItem , TVGN_NEXT);
    }

    return NULL;    // Not found
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_bIsItemVIsibleInTree
// Return TRUE if an item is visible in a tree
//-----------------------------------------------------------------------------
BOOL TUT_CTutorialManager::m_fn_bIsItemVisibleInTree (HWND _hTreeCtrl , HTREEITEM _hItemToFind)
{
    HTREEITEM hItem = TreeView_GetFirstVisible (_hTreeCtrl);

    while (hItem)
    {
        if (hItem == _hItemToFind)  return TRUE;
        hItem = TreeView_GetNextVisible (_hTreeCtrl , hItem);
    }

    return FALSE;    // Not found
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_bSearchWndDefinition
// Search a window name into the list
//-----------------------------------------------------------------------------
BOOL TUT_CTutorialManager::m_fn_bSearchWndDefinition (const CString _csName , int *_p_iIndex /* = NULL */)
{
    int                     iMin    = -1;
    int                     iMax    = m_oArrayOfWnd . GetSize ();
    int                     iMiddle = (iMin + iMax) >> 1;
    int                     iComp;
    BOOL                    bFound  = FALSE;
    TUT_tdstWndDefinition * p_stWndDefinition;

    while (iMax != iMin + 1)
    {
        p_stWndDefinition = m_oArrayOfWnd [iMiddle];
        iComp = p_stWndDefinition -> csName . CompareNoCase (_csName);

        if (iComp == 0)    // Found
        {
            if (_p_iIndex)  * _p_iIndex = iMiddle;
            return TRUE;
        }
        else
        if (iComp == -1)
        {
            iMin    = iMiddle;
            iMiddle = (iMin + iMax) >> 1;
        }
        else
        {
            iMax    = iMiddle;
            iMiddle = (iMin + iMax) >> 1;
        }
    }

    // Not found : we set the position where to insert the new window
    if (_p_iIndex)  * _p_iIndex = iMin;

    return FALSE;
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_bSearchWndDefinition
// Search a window name into the list
//-----------------------------------------------------------------------------
BOOL TUT_CTutorialManager::m_fn_bSearchWndDefinition (HWND _hWnd , int * _p_iIndex /*=NULL*/ )
{
    int                     iMin    = 0;
    int                     iMax    = m_oArrayOfWnd . GetSize ();
    int                     iIndex;
    TUT_tdstWndDefinition * p_stWndDefinition;

    for (iIndex = iMin ; iIndex < iMax ; iIndex ++)
    {
        p_stWndDefinition = m_oArrayOfWnd [iIndex];
        if (p_stWndDefinition -> hWnd == _hWnd)    // Found
        {
            if (_p_iIndex)  * _p_iIndex = iIndex;
            return TRUE;
        }
    }

    // Not found : we set the position where to insert the new window
    if (_p_iIndex)  * _p_iIndex = -1;

    return FALSE;
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fnp_stFindWndDefinition
// Search a window name into the list
//-----------------------------------------------------------------------------
TUT_tdstWndDefinition * TUT_CTutorialManager::m_fnp_stFindWndDefinition (const CString _csName)
{
    int iIndex;
    return m_fn_bSearchWndDefinition (_csName , & iIndex) ? m_oArrayOfWnd [iIndex] : NULL;
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fnp_stFindWndDefinition
// Search a window handle into the list
//-----------------------------------------------------------------------------
TUT_tdstWndDefinition * TUT_CTutorialManager::m_fnp_stFindWndDefinition (HWND _hWnd)
{
    int iIndex;
    return m_fn_bSearchWndDefinition (_hWnd , & iIndex) ? m_oArrayOfWnd [iIndex] : NULL;
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fnp_stFindLabel
// Search a label name into the list
//-----------------------------------------------------------------------------
TUT_tdstLabelDefinition * TUT_CTutorialManager::m_fnp_stFindLabel (char * _szName)
{
    int      iCurrentContextNumber = m_oListOfContext . GetCount ();
    POSITION Pos                   = m_oListOfLabel   . GetHeadPosition ();
    while (Pos)
    {
        TUT_tdstLabelDefinition * p_stLabelDef = m_oListOfLabel . GetNext (Pos);
        if (p_stLabelDef -> csName . CompareNoCase (_szName) == 0 && p_stLabelDef -> iContextNumber == iCurrentContextNumber)
            return p_stLabelDef;
    }

    return NULL;    // Not found
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fnp_stFindVariable
// Search a variable name into the list
//-----------------------------------------------------------------------------
TUT_tdstVariableDefinition * TUT_CTutorialManager::m_fnp_stFindVariable (char *  _szName)
{
    BOOL     bDeclareVariable;
    if (* _szName == TUT_C_cDeclarationOfVariable)
    {
        bDeclareVariable = TRUE;
        _szName ++;
    }
    else bDeclareVariable = FALSE;

    int      iCurrentContextNumber = m_oListOfContext  . GetCount ();
    POSITION Pos                   = m_oListOfVariable . GetHeadPosition ();

    TUT_tdstVariableDefinition * p_stGlobalVariableDef = NULL;

    while (Pos)
    {
        TUT_tdstVariableDefinition * p_stVariableDef = m_oListOfVariable . GetNext (Pos);
        if (p_stVariableDef -> csName . CompareNoCase (_szName) == 0)          // We have found a variable
        {
            if (p_stVariableDef -> iContextNumber == iCurrentContextNumber)    // Is this variable a local variable ?
            {
                if (bDeclareVariable)  return NULL;                            // Declaration of a already existing variable -> error
                                 else  return p_stVariableDef;                 // Ok
            }
            else
            if (p_stVariableDef -> iContextNumber == 0)                        // Is this variable a global variable ?
            {
                p_stGlobalVariableDef = p_stVariableDef;
            }
        }
    }

    // Here, we have not found a local variable

    if (bDeclareVariable)
    {
        TUT_tdstVariableDefinition * p_stVariableDef = new TUT_tdstVariableDefinition;
        m_oListOfVariable . AddTail (p_stVariableDef);
        p_stVariableDef -> csName         = _szName;
		p_stVariableDef -> csValue        = "";
        p_stVariableDef -> iContextNumber = iCurrentContextNumber;
        return p_stVariableDef;
    }
    else
    {
        if (p_stGlobalVariableDef)  return p_stGlobalVariableDef;
                              else  return NULL;    // Not found
    }
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fnp_stFindOrCreateVariable
// Search a variable name into the list. If it does not exist, create it
//-----------------------------------------------------------------------------
TUT_tdstVariableDefinition * TUT_CTutorialManager::m_fnp_stFindOrCreateVariable (char * _szName)
{
    TUT_tdstVariableDefinition * p_stVariableDef = m_fnp_stFindVariable (_szName);
    if (p_stVariableDef == NULL)
    {
        p_stVariableDef = new TUT_tdstVariableDefinition;
        m_oListOfVariable . AddTail (p_stVariableDef);
        p_stVariableDef -> csName         = _szName;
		p_stVariableDef -> csValue        = "";
        p_stVariableDef -> iContextNumber = m_oListOfContext . GetCount ();
    }

    return p_stVariableDef;
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_vSetValueToVariable
// Set a value to a variable
//-----------------------------------------------------------------------------
void TUT_CTutorialManager::m_fn_vSetValueToVariable (TUT_tdstVariableDefinition * _p_stVariableDef , long _lValue)
{
    _p_stVariableDef -> csValue . Format ("%d" , _lValue);
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_vSetGetLastError
// Put an error number into the GetLastError' global variable
//-----------------------------------------------------------------------------
void TUT_CTutorialManager::m_fn_vSetGetLastError (long _lErrorNumber)
{
    // Fill the GetLastError variable
    TUT_tdstVariableDefinition * p_stVariableDef = m_fnp_stFindOrCreateVariable (TUT_C_szGetLastError);
    m_fn_vSetValueToVariable (p_stVariableDef , _lErrorNumber);

    // Fill the GetLastErrorMessage variable
    char * szMessage;
    switch (_lErrorNumber)
    {
        case TUT_C_iNoError                    : szMessage = "No error";                            break;
        case TUT_C_iError_ListBoxItemNotFound  : szMessage = "Item not found in list box";          break;
        case TUT_C_iError_ComboBoxItemNotFound : szMessage = "Item not found in combo box";         break;
        case TUT_C_iError_ListCtrlItemNotFound : szMessage = "Item not found in list control";      break;
        case TUT_C_iError_ListBoxInvalidLine   : szMessage = "Invalid line number in list box";     break;
        case TUT_C_iError_ComboBoxInvalidLine  : szMessage = "Invalid line number in combo box";    break;
        case TUT_C_iError_ListCtrlInvalidLine  : szMessage = "Invalid line number in list control"; break;
        case TUT_C_iError_TreeCtrlItemNotFound : szMessage = "Item not found in tree control";      break;
        case TUT_C_iError_SoundError           : szMessage = "Wav file not found or sound error";   break;
		case TUT_C_iError_UnregisteredWindow   : szMessage = "Unregistered window";					break;
        default                                : szMessage = NULL;                                  break;
    }

    p_stVariableDef = m_fnp_stFindOrCreateVariable (TUT_C_szGetLastErrorMessage);
    if (szMessage)  p_stVariableDef -> csValue = szMessage;
              else  p_stVariableDef -> csValue . Format ("Unknown error code (%d)" , _lErrorNumber);
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_iConvertToInt
// Convert a string into an integer. Return TUT_C_iErrorNotANumber if error
//-----------------------------------------------------------------------------
int TUT_CTutorialManager::m_fn_iConvertToInt (char * _szString)
{
    char * szEndOfNumber;
    long   lValue = strtol (_szString , & szEndOfNumber , 10);
    if (* szEndOfNumber != 0)  return TUT_C_iErrorNotANumber;

    return (int) lValue;
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_iConvertToDouble
// Convert a string into a double. Return TUT_C_dErrorNotANumber if error
//-----------------------------------------------------------------------------
double TUT_CTutorialManager::m_fn_dConvertToDouble (char * _szString)
{
    char * szEndOfNumber;
    double dValue = strtod (_szString , & szEndOfNumber);
    if (* szEndOfNumber != 0)  return TUT_C_dErrorNotANumber;

    return dValue;
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_bAreParametersEqual
// Return TRUE if the two parameters are equal (can be integer, float or string)
//-----------------------------------------------------------------------------
BOOL TUT_CTutorialManager::m_fn_bAreParametersEqual (char * _szParameter1 , char * _szParameter2)
{
    double dParameter1 = m_fn_dConvertToDouble (_szParameter1);
    BOOL   bIsDouble1  = dParameter1 != TUT_C_dErrorNotANumber;

    double dParameter2 = m_fn_dConvertToDouble (_szParameter2);
    BOOL   bIsDouble2  = dParameter2 != TUT_C_dErrorNotANumber;

    if (bIsDouble1 && bIsDouble2)  return dParameter1 == dParameter2;
                              else return stricmp (_szParameter1 , _szParameter2) == 0;
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_bIsParameter1LessThanParameter2
// Return TRUE if the first parameter is less than the seoncd parameter (can be integer, float or string)
//-----------------------------------------------------------------------------
BOOL TUT_CTutorialManager::m_fn_bIsParameter1LessThanParameter2 (char * _szParameter1 , char * _szParameter2)
{
    double dParameter1 = m_fn_dConvertToDouble (_szParameter1);
    BOOL   bIsDouble1  = dParameter1 != TUT_C_dErrorNotANumber;

    double dParameter2 = m_fn_dConvertToDouble (_szParameter2);
    BOOL   bIsDouble2  = dParameter2 != TUT_C_dErrorNotANumber;

    if (bIsDouble1 && bIsDouble2)  return dParameter1 < dParameter2;
                              else return stricmp (_szParameter1 , _szParameter2) < 0;
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_bIsParameterTrue
// Return if a parameter is TRUE or FALSE
//-----------------------------------------------------------------------------
BOOL TUT_CTutorialManager::m_fn_bIsParameterTrue (char * _szParameter , int  * _p_iReturnValue)
{
    * _p_iReturnValue = TUT_C_iNoError;

    if (stricmp (_szParameter , TUT_C_szTrue ) == 0)  return TRUE;
    if (stricmp (_szParameter , TUT_C_szFalse) == 0)  return FALSE;

    * _p_iReturnValue = TUT_C_iError_BooleanValueRequired;
    return FALSE;
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_bIsTestVerified
// Execute a test and return the result of this test
//-----------------------------------------------------------------------------
BOOL TUT_CTutorialManager::m_fn_bIsTestVerified (char * _szTestOperator , char * _szParameter1 ,
                                                 char * _szParameter2   , int  * _p_iReturnValue)
{
    BOOL bTestVerified;
    * _p_iReturnValue = TUT_C_iNoError;

    if (stricmp (_szTestOperator , "=")  == 0 || stricmp (_szTestOperator , "==") == 0)
         bTestVerified = m_fn_bAreParametersEqual (_szParameter1 , _szParameter2);
    else
    if (stricmp (_szTestOperator , "<>") == 0 || stricmp (_szTestOperator , "!=") == 0)
         bTestVerified = ! m_fn_bAreParametersEqual (_szParameter1 , _szParameter2);
    else
    if (stricmp (_szTestOperator , "<") == 0)
         bTestVerified =   m_fn_bIsParameter1LessThanParameter2 (_szParameter1 , _szParameter2);
    else
    if (stricmp (_szTestOperator , "<=") == 0)
         bTestVerified = ! m_fn_bIsParameter1LessThanParameter2 (_szParameter2 , _szParameter1);
    else
    if (stricmp (_szTestOperator , ">") == 0)
         bTestVerified =   m_fn_bIsParameter1LessThanParameter2 (_szParameter2 , _szParameter1);
    else
    if (stricmp (_szTestOperator , ">=") == 0)
         bTestVerified = ! m_fn_bIsParameter1LessThanParameter2 (_szParameter1 , _szParameter2);
    else
    {
        bTestVerified     = FALSE;
        * _p_iReturnValue = TUT_C_iError_TestOperatorRequired;
    }

    return bTestVerified;
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_bMustActionBeExecuted
// Return if the current action must be executed
//-----------------------------------------------------------------------------
BOOL TUT_CTutorialManager::m_fn_bMustActionBeExecuted ()
{
    // Not inside a test -> no problem
    if (m_oListOfTest . GetCount () == 0)  return TRUE;

    TUT_tdstTestDefinition * p_stTestDef = m_oListOfTest . GetHead ();

    // Return TRUE if test verified XOR we are in the else block
    return p_stTestDef -> bTestVerified ^ p_stTestDef -> ePositionInTest == TUT_eAfterElse;
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_vAddAnActionForTheTest
// Called each time an action is executed
//-----------------------------------------------------------------------------
void TUT_CTutorialManager::m_fn_vAddAnActionForTheTest ()
{
    // Not inside a test -> nothing to do
    if (m_oListOfTest . GetCount () == 0)  return;

    TUT_tdstTestDefinition * p_stTestDef = m_oListOfTest . GetHead ();
    if (p_stTestDef -> ePositionInTest == TUT_eAfterIf)  p_stTestDef -> ePositionInTest = TUT_eBeforeElse;
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_vATestRightButtonDoubleClicAndPosition
// These five flags must be set to FALSE after one action
//-----------------------------------------------------------------------------
void TUT_CTutorialManager::m_fn_vTestRightButtonDoubleClicAndPosition ()
{
    static BOOL sbPreviousValueUseRightButton = FALSE;
    static BOOL sbPreviousValueUseDoubleClic  = FALSE;
    static BOOL sbPreviousValueUsePosition    = FALSE;
    static BOOL sbPreviousValueUseDrag        = FALSE;
    static BOOL sbPreviousValueUseDrop        = FALSE;

    if (sbPreviousValueUseRightButton)  m_bUseRightButton = FALSE;
    if (sbPreviousValueUseDoubleClic )  m_bUseDoubleClic  = FALSE;
    if (sbPreviousValueUsePosition   )  m_bUsePosition    = FALSE;
    if (sbPreviousValueUseDrag       )  m_bUseDrag        = FALSE;
    if (sbPreviousValueUseDrop       )  m_bUseDrop        = FALSE;

    sbPreviousValueUseRightButton = m_bUseRightButton;
    sbPreviousValueUseDoubleClic  = m_bUseDoubleClic;
    sbPreviousValueUsePosition    = m_bUsePosition;
    sbPreviousValueUseDrag        = m_bUseDrag;
    sbPreviousValueUseDrop        = m_bUseDrop;
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_vClearAllList
// Clear the list used by the class
//-----------------------------------------------------------------------------
void TUT_CTutorialManager::m_fn_vClearAllList (BOOL _bUnregisterControl)
{
    if (_bUnregisterControl)
    {
        for (int iIndex = m_oArrayOfWnd . GetSize() - 1 ; iIndex >= 0 ; iIndex --)
            delete m_oArrayOfWnd [iIndex];
        m_oArrayOfWnd . RemoveAll ();
    }

	int iNumber;

	iNumber = 0;
    while (m_oListOfVariable . GetCount ())
	{
		TUT_tdstVariableDefinition *p_stVarDef = m_oListOfVariable . RemoveHead ();
        delete p_stVarDef;
		iNumber ++;
	}

	iNumber = 0;
    while (m_oListOfLoop . GetCount ())
	{
		TUT_tdstLoopDefinition *p_stLoopDef = m_oListOfLoop . RemoveHead ();
        delete p_stLoopDef;
		iNumber ++;
	}

	iNumber = 0;
    while (m_oListOfTest . GetCount ())
	{
		TUT_tdstTestDefinition *p_stTestDef = m_oListOfTest . RemoveHead ();
		iNumber ++;
        delete p_stTestDef;
	}

	iNumber = 0;
    while (m_oListOfLabel . GetCount ())
	{
		TUT_tdstLabelDefinition *p_stLabelDef = m_oListOfLabel . RemoveHead ();
        delete p_stLabelDef;
		iNumber ++;
	}

    // Reset the variables
    m_iNumberOfParameter =     0;
    m_iNumberOfResult    =     0;
    m_bInsideAGoto       = FALSE;
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_vPreprocessString
// Remove spaces, tab and quotes at the beginning and the end of the string
// Remplace variable name by its value. Return FALSE if error
//-----------------------------------------------------------------------------
BOOL TUT_CTutorialManager::m_fn_vPreprocessString (char * & _szString)
{
    if (_szString == NULL || * _szString == 0)  return TRUE;;

    // Remove spaces and tab at the beginning of the string
    while (* _szString == ' ' || * _szString == '\t')  _szString ++;

    // Remove spaces and tab at the end of the string
    char * p = _szString + strlen (_szString) - 1;    // Points to the last character of the string
    while (* _szString != 0 && (* p == ' ' || * p == '\t'))  * (p--) = 0;

    // Check if it is a variable name
    int iNbVariable = 0;
    while (* _szString == TUT_C_cBeginningOfVariable)
    {
        _szString   ++;
        iNbVariable ++;
    }

    if (iNbVariable)
    {
        while (iNbVariable --)
        {
            TUT_tdstVariableDefinition * p_stVariableDef = m_fnp_stFindVariable (_szString);
            if (p_stVariableDef)  _szString = (char *) (LPCTSTR) p_stVariableDef -> csValue;
                            else  return FALSE;    // Use of an unknown variable -> error
        }

        return TRUE;
    }

    // Remove quote at the beginning of the string
    if (* _szString == '"')  _szString ++;

    // Remove quote at the end of the string
    if (* _szString != 0 && * p == '"')  * p = 0;

    return TRUE;
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_szStrtok
// same as strtok but don't stop at delimiter inside quote
//-----------------------------------------------------------------------------
char * TUT_CTutorialManager::m_fn_szStrtok (char * _szString , char _cDelimiter)
{
    static char * p = NULL;

    if (_szString != NULL)  p = _szString;      // in both cases,
    else
    {
        if (p == NULL)  return NULL;
                   else p ++;                   // p points to the first character
    }

    char * szReturnValue = p;
    if (* szReturnValue == 0)  return NULL;     // end of the string -> return NULL

    BOOL bInsideQuote = FALSE;
    while (* p != 0 && (* p != _cDelimiter || bInsideQuote))      // Find delimiter or end of string
    {
        if (* p == '\"')  bInsideQuote = ! bInsideQuote;
        p ++;
    }

    if (* p != 0)  * p = 0;                     // Put 0 instead of a delimiter
               else  p = NULL;                  // Eof of the string


    return szReturnValue;
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_iGetParameters
// Split a string into a variable number of parameters. Return 0 if no error
//-----------------------------------------------------------------------------
int TUT_CTutorialManager::m_fn_iGetParameters (char * _szString , int _iMinNumberOfParameter , int _iMaxNumberOfParameter ,
                                               int * _p_iNumberOfParameter , ...)
{
    va_list Marker;
    va_start (Marker, _p_iNumberOfParameter);     // Initialize variable arguments
    * _p_iNumberOfParameter = 0;

    char * szParameter = m_fn_szStrtok (_szString , ',');
    while (szParameter != NULL)
    {
        (* _p_iNumberOfParameter) ++;
        if (* _p_iNumberOfParameter > _iMaxNumberOfParameter)  return TUT_C_iError_TooManyParameters;

        char * * p_szParameter = va_arg (Marker , char * *);    // Get the next argument
        if (! m_fn_vPreprocessString (szParameter))  return TUT_C_iError_UnknownVariable;
        * p_szParameter = m_fn_szHandleQuote (szParameter);

        szParameter = m_fn_szStrtok (NULL , ',');
    }

    va_end (Marker);

    if (* _p_iNumberOfParameter < _iMinNumberOfParameter)  return TUT_C_iError_ParameterRequired;

    return TUT_C_iNoError;
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_vPumpMessage
// Dispatch messages
//-----------------------------------------------------------------------------
void TUT_CTutorialManager::m_fn_vPumpMessage ()
{
    MSG Msg;
    while (PeekMessage (& Msg , NULL , 0 , 0 , PM_REMOVE))
        DispatchMessage (& Msg);
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_vSleep
// Wait for the given duration (in millisecond)
//-----------------------------------------------------------------------------
void TUT_CTutorialManager::m_fn_vSleep (int _iDuration)
{
    BOOL bLeftKeyPressed = (GetAsyncKeyState (VK_LSHIFT) & 0x8000) && (GetAsyncKeyState (VK_LCONTROL) & 0x8000);

    m_fn_vPumpMessage ();

    while (_iDuration > 0)
    {
        int iDelta = min (50 , _iDuration);
        _iDuration -= iDelta;

        // Are the control and shift keys pressed ?
        if ((GetAsyncKeyState (VK_RCONTROL) & 0x8000) && (GetAsyncKeyState (VK_RSHIFT) & 0x8000))  return;

        // Is the 'Escape' key pressed ?
        if ((GetAsyncKeyState (VK_ESCAPE) & 0x8000))  return;

        if (bLeftKeyPressed)
        {
            if ((GetAsyncKeyState (VK_LSHIFT) & 0x8000) == 0 && (GetAsyncKeyState (VK_LCONTROL) & 0x8000) == 0)
                bLeftKeyPressed = FALSE;
        }
        else
        {
            if ((GetAsyncKeyState (VK_LSHIFT) & 0x8000) && (GetAsyncKeyState (VK_LCONTROL) & 0x8000))  return;
        }

        Sleep (iDelta);

        m_fn_vPumpMessage ();
    }
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_vActivateWindow
// Activate a window and its parent (recursiv call)
//-----------------------------------------------------------------------------
void TUT_CTutorialManager::m_fn_vActivateWindow (HWND _hWnd)
{
    SetForegroundWindow (_hWnd);
    UpdateWindow (_hWnd);
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_hOpenUserWindow
// Open one user window (a rich edit control inside a frame) and return its handle
//-----------------------------------------------------------------------------
HWND TUT_CTutorialManager::m_fn_hOpenUserWindow (char * _szWindowName , int _iLeft  , int _iTop ,
                                                                        int _iRight , int _iBottom)
{
    // Compute the size of the frame to create
    CRect ScreenPos;
    GetWindowRect (GetDesktopWindow () , & ScreenPos);
    CRect WindowPos;
    WindowPos . left   = ScreenPos . right  * _iLeft   / 100;
    WindowPos . right  = ScreenPos . right  * _iRight  / 100;
    WindowPos . top    = ScreenPos . bottom * _iTop    / 100;
    WindowPos . bottom = ScreenPos . bottom * _iBottom / 100;

    // Create a new class for the frame
    LPCTSTR lpszSmartClass = AfxRegisterWndClass (CS_HREDRAW | CS_VREDRAW , LoadCursor (NULL, IDC_ARROW) , (HBRUSH) (COLOR_WINDOW + 1));

    // Create the frame window
    HWND hWnd = CreateWindowEx (WS_EX_TOPMOST | WS_EX_TOOLWINDOW , lpszSmartClass , _szWindowName ,
                                WS_POPUP | WS_BORDER | WS_CAPTION | WS_VISIBLE ,
                                WindowPos . left , WindowPos . top , WindowPos . Width () , WindowPos . Height () ,
                                NULL /* No parent to avoid problem */ , NULL , AfxGetInstanceHandle () , NULL);
    if (hWnd == NULL)
    {
        DWORD dwError = GetLastError ();
        ASSERT (0);
    }

    // Create a text view
    CRect ClientRect;
    GetClientRect (hWnd , & ClientRect);

    HWND hEditView = CreateWindowEx (WS_EX_LEFT , "RichEdit" , "" , WS_CHILD | WS_VISIBLE | WS_VSCROLL | ES_MULTILINE ,
                                     ClientRect . left , ClientRect . top , ClientRect . Width () , ClientRect . Height () ,
                                     hWnd , NULL , AfxGetInstanceHandle () , NULL);
    if (hEditView == NULL)
    {
        DWORD dwError = GetLastError ();
        ASSERT (0);
    }
    SendMessage (hEditView , EM_SETREADONLY , TRUE , 0);

    return hEditView;
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_vCloseUserWindow
// Close one user window
//-----------------------------------------------------------------------------
void TUT_CTutorialManager::m_fn_vCloseUserWindow (int _iIndex)
{
    TUT_tdstWndDefinition * p_stWndDefinition = m_oArrayOfWnd [_iIndex];
    HWND hWindow = p_stWndDefinition -> hWnd;
    HWND hParent = GetParent (hWindow);
    ASSERT (hParent);

    DestroyWindow (hWindow);
    DestroyWindow (hParent);

    m_oArrayOfWnd . RemoveAt (_iIndex);

    delete p_stWndDefinition;

    if (m_p_stCurrentUserWindow == p_stWndDefinition)  m_p_stCurrentUserWindow = NULL;
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_vCloseAllUserWindow
// Close all open user windows
//-----------------------------------------------------------------------------
void TUT_CTutorialManager::m_fn_vCloseAllUserWindow ()
{
    for (int iIndex = m_oArrayOfWnd . GetSize () - 1 ; iIndex >= 0 ; iIndex --)
    {
        TUT_tdstWndDefinition * p_stWndDefinition = m_oArrayOfWnd [iIndex];
        if (p_stWndDefinition -> eType == TUT_eUserWindow)  m_fn_vCloseUserWindow (iIndex);
    }

    if (m_hDebugWnd)
    {
        DestroyWindow (m_hDebugWnd);
        m_hDebugWnd = NULL;
    }
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_vDeleteLocalLabelAndVariable
// Delete all local labels and variables
//-----------------------------------------------------------------------------
void TUT_CTutorialManager::m_fn_vDeleteLocalLabelAndVariable ()
{
    int      iCurrentContextNumber = m_oListOfContext  . GetCount ();

    POSITION Pos                   = m_oListOfVariable . GetHeadPosition ();
    while (Pos)
    {
        POSITION SavePos = Pos;
        TUT_tdstVariableDefinition * p_stVariableDef = m_oListOfVariable . GetNext (Pos);

        if (p_stVariableDef -> iContextNumber == iCurrentContextNumber)
        {
            m_oListOfVariable . RemoveAt (SavePos);
            delete p_stVariableDef;
        }
    }

    Pos = m_oListOfLabel . GetHeadPosition ();
    while (Pos)
    {
        POSITION SavePos = Pos;
        TUT_tdstLabelDefinition * p_stLabelDef = m_oListOfLabel . GetNext (Pos);

        if (p_stLabelDef -> iContextNumber == iCurrentContextNumber)
        {
            m_oListOfLabel . RemoveAt (SavePos);
            delete p_stLabelDef;
        }
    }
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_vCloseLogFile
// Close the given log file if it is open
//-----------------------------------------------------------------------------
void TUT_CTutorialManager::m_fn_vCloseLogFile (TUT_tdstLogFileDefinition * _p_stLogFile)
{
    if (_p_stLogFile -> bOpen)
    {
        fclose (_p_stLogFile -> pFile);
        _p_stLogFile -> bOpen  = FALSE;
        _p_stLogFile -> csName = "";
        _p_stLogFile -> pFile  = NULL;
    }
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_vCloseAllLogFiles
// Close the given log file if it is open
//-----------------------------------------------------------------------------
void TUT_CTutorialManager::m_fn_vCloseAllLogFiles ()
{
    m_fn_vCloseLogFile (& m_stLogFile);
    m_fn_vCloseLogFile (& m_stDebugLogFile);
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_bFindColor
// Find RGB code of the given color name. Return FALSE if not found
//-----------------------------------------------------------------------------
BOOL TUT_CTutorialManager::m_fn_bFindColor (char * _szColorName , COLORREF * _p_xColor)
{
    const TUT_tdstColorDefinition * p = g_a_stColorDefinition;

    while (p -> szColorName)
    {
        if (stricmp (p -> szColorName , _szColorName) == 0)
        {
            * _p_xColor = p -> xColor;
            return TRUE;
        }

        p ++;
    }

    return FALSE;    // Not found
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_iFindVirtualKeyCode
// Find a virtual key code with the given name. Return 0 if not found
//-----------------------------------------------------------------------------
int TUT_CTutorialManager::m_fn_iFindVirtualKeyCode  (char * _szVirtualKeyCode)
{
    if (strlen (_szVirtualKeyCode) == 1)
    {
        char cChar = (char) toupper (* _szVirtualKeyCode);
        if (cChar >= 'A' && cChar <= 'Z')  return cChar;
    }

    int    i = -1;
    char * szKeyName;
    do
    {
        szKeyName = TUT_cgs_a_stKeyConversionTable [++ i] . szKeyName;
        if (szKeyName == NULL)  return 0;    // End of the list : not found -> return 0;
    } while (stricmp (szKeyName , _szVirtualKeyCode) != 0);

    return TUT_cgs_a_stKeyConversionTable [i] . iVirtualKeyCode;
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_iFindIdForMessageMox
// Find RGB code of the given color name. Return FALSE if not found
//-----------------------------------------------------------------------------
int TUT_CTutorialManager::m_fn_iFindIdForMessageBox (char * _szControlName)
{
    if (stricmp (_szControlName , TUT_C_szOk    ) == 0)  return IDOK;
    if (stricmp (_szControlName , TUT_C_szCancel) == 0)  return IDCANCEL;
    if (stricmp (_szControlName , TUT_C_szAbort ) == 0)  return IDABORT;
    if (stricmp (_szControlName , TUT_C_szRetry ) == 0)  return IDRETRY;
    if (stricmp (_szControlName , TUT_C_szIgnore) == 0)  return IDIGNORE;
    if (stricmp (_szControlName , TUT_C_szYes   ) == 0)  return IDYES;
    if (stricmp (_szControlName , TUT_C_szNo    ) == 0)  return IDNO;

    return 0;    // Not found
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_szGetWndNameType
// Return the name of the given window type
//-----------------------------------------------------------------------------
char * TUT_CTutorialManager::m_fn_szGetWndNameType (TUT_tdeWndType _eType)
{
    switch (_eType)
    {
        case TUT_eWindow     : return "Window";
        case TUT_eMdiWindow  : return "Mdi window";
        case TUT_eButton     : return "Button";
        case TUT_eTextEdit   : return "Text edit";
        case TUT_eListBox    : return "List box";
        case TUT_eComboBox   : return "Combo box";
        case TUT_eTreeCtrl   : return "Tree control";
        case TUT_eListCtrl   : return "List control";
        case TUT_eSpin       : return "Spin";
        case TUT_eSlider     : return "Slider";
        case TUT_eScrollBar  : return "Scrollbar";
        case TUT_eUserWindow : return "User window";
		case TUT_e3DView	 : return "3DView";
        default              : ASSERT (0);
                               return NULL;
    }
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_vShowError
// Open a message box with a message associated to the given error code
//-----------------------------------------------------------------------------
void TUT_CTutorialManager::m_fn_vShowError (int _iErrorCode , const CString _csFileName , int _iLineNumber , char * _szLine)
{
    CString csMessage;

    if (_iErrorCode == TUT_C_iError_FileNotFound)  csMessage . Format ("File not found : %s" , _csFileName);
    else
    {
        CString csText;

        switch (_iErrorCode)
        {
            case TUT_C_iError_UnknownCommand            : csText = "Unknown command";                                   break;
            case TUT_C_iError_TooManyParameters         : csText = "Too many parameters";                               break;
            case TUT_C_iError_ParameterRequired         : csText = "More parameters are required";                      break;
            case TUT_C_iError_UnknownButton             : csText = "Unknown button name";                               break;
            case TUT_C_iError_ButtonRequired            : csText = "The control is not a button";                       break;
            case TUT_C_iError_InvalidCoordinates        : csText = "Coordinates are not valid";                         break;
            case TUT_C_iError_NoWindowToWriteText       : csText = "No open window to write text";                      break;
            case TUT_C_iError_InvalidNumber             : csText = "Invalid number";                                    break;
            case TUT_C_iError_UnknownTextEdit           : csText = "Unknown text edit name";                            break;
            case TUT_C_iError_TextEditRequired          : csText = "The control is not a text edit";                    break;
            case TUT_C_iError_UnknownWindow             : csText = "Unknown window name";                               break;
            case TUT_C_iError_UnknownComboBox           : csText = "Unknown combo box name";                            break;
            case TUT_C_iError_ListOrComboRequired       : csText = "The control must be a combo box, a list box or a list control"; break;
            case TUT_C_iError_UnknownMdiWindow          : csText = "Unknown mdi window name";                           break;
            case TUT_C_iError_MdiWindowRequired         : csText = "The control is not a mdi window";                   break;
            case TUT_C_iError_UnknownColor              : csText = "Unknown color name";                                break;
            case TUT_C_iError_UnknownControl            : csText = "Unknown control name";                              break;
            case TUT_C_iError_SelectCtrlRequired        : csText = "The control is not a control with selection";       break;
            case TUT_C_iError_InvalidTextAlignment      : csText = "Invalid text alignment";                            break;
            case TUT_C_iError_UnknownTreeBox            : csText = "Unknown tree box name";                             break;
            case TUT_C_iError_TreeBoxRequired           : csText = "The control is not a tree box";                     break;
            case TUT_C_iError_FunctionNotCompiled       : csText = "Not compiled function";                             break;
            case TUT_C_iError_Unknown3dObject           : csText = "Unknown 3d object";                                 break;
            case TUT_C_iError_NoMenuAvailable           : csText = "No menu available";                                 break;
            case TUT_C_iError_UnknownMenuName           : csText = "Unknown menu name";                                 break;
            case TUT_C_iError_NotASubMenu               : csText = "Not a sub menu";                                    break;
            case TUT_C_iError_EndLoopWithoutLoop        : csText = "'EndLoop' without a 'Loop'";                        break;
            case TUT_C_iError_TestOperatorRequired      : csText = "A test operator is required";                       break;
            case TUT_C_iError_OrIfWithoutAnIf           : csText = "'OrIf' without an 'If'";                            break;
            case TUT_C_iError_ElseWithoutAnIf           : csText = "'Else' without an 'If'";                            break;
            case TUT_C_iError_ElseInTheWrongPlace       : csText = "Incorrect 'Else'";                                  break;
            case TUT_C_iError_EndIfWithoutAnIf          : csText = "'EndIf' without an 'If'";                           break;
            case TUT_C_iError_DebugWndAlreadyOpen       : csText = "The debug window is already open";                  break;
            case TUT_C_iError_DebugWndNotOpen           : csText = "The debug window is not open";                      break;
            case TUT_C_iError_InvalidMdiHandle          : csText = "Invalid handle to a MDI window";                    break;
            case TUT_C_iError_InvalidParentMdiHandle    : csText = "Invalid handle to a parent of a MDI window";        break;
            case TUT_C_iError_InvalidListBoxHandle      : csText = "Invalid handle to a list box";                      break;
            case TUT_C_iError_InvalidComboBoxHandle     : csText = "Invalid handle to a combo box";                     break;
            case TUT_C_iError_InvalidTreeBoxHandle      : csText = "Invalid handle to a tree box";                      break;
            case TUT_C_iError_InvalidMenuHandle         : csText = "Invalid menu handle";                               break;
            case TUT_C_iError_InvalidMenuWndHandle      : csText = "Invalid handle to a menu owner";                    break;
            case TUT_C_iError_InvalidButtonHandle       : csText = "Invalid handle to a button";                        break;
            case TUT_C_iError_InvalidTextEditHandle     : csText = "Invalid handle to a text edit";                     break;
            case TUT_C_iError_WindowOrMdiRequired       : csText = "The control is not a window or a MDI window";       break;
            case TUT_C_iError_InvalidWndOrMdiHandle     : csText = "Invalid handle to a window or a MDI window";        break;
            case TUT_C_iError_CtrlWithTextRequired      : csText = "This type of control has no text";                  break;
            case TUT_C_iError_InvalidHandle             : csText = "Invalid handle";                                    break;
            case TUT_C_iError_BooleanValueRequired      : csText = "A boolean value is required";                       break;
            case TUT_C_iError_ItemNotFoundInTree        : csText = "The item was not found in the tree";                break;
            case TUT_C_iError_ItemHasNoChild            : csText = "The item has no child";                             break;
            case TUT_C_iError_InvalidChildNumber        : csText = "Invalid child number";                              break;
            case TUT_C_iError_NotANumericVariable       : csText = "A numeric variable is required";                    break;
            case TUT_C_iError_DivisionByZero            : csText = "Division by zero";                                  break;
            case TUT_C_iError_EmptyVariable             : csText = "A not-empty variable is required";                  break;
            case TUT_C_iError_LogFileAlreadyOpen        : csText = "A log file is already open";                        break;
            case TUT_C_iError_UnableToOpenLogFile       : csText = "Unable to open the log file";                       break;
            case TUT_C_iError_LogFileNotOpen            : csText = "The log file is not open";                          break;
            case TUT_C_iError_InvalidXBorder            : csText = "'Left' or 'Right' is required";                     break;
            case TUT_C_iError_InvalidYBorder            : csText = "'Top' or 'Bottom' is required";                     break;
            case TUT_C_iError_InvalidNumberOrPercent    : csText = "Invalid number or percentage";                      break;
            case TUT_C_iError_InvalidListCtrlHandle     : csText = "Invalid handle to a list control";                  break;
            case TUT_C_iError_LabelsWithSameName        : csText = "This name is already used by another label";        break;
            case TUT_C_iError_LabelNotFound             : csText = "Label not found";                                   break;
            case TUT_C_iError_ModelNotFound             : csText = "Model not found";                                   break;
            case TUT_C_iError_DragAtTheWrongPlace       : csText = "'Drag' at the wrong place";                         break;
            case TUT_C_iError_DropAtTheWrongPlace       : csText = "'Drop' at the wrong place";                         break;
            case TUT_C_iError_InvisibleControl          : csText = "Use of an invisible control";                       break;
            case TUT_C_iError_DisabledControl           : csText = "Use of a disabled control";                         break;
            case TUT_C_iError_NoMultipleSelection       : csText = "The list is not a multiple selection list";         break;
            case TUT_C_iError_UnknownListBox            : csText = "Unknown list box name";                             break;
            case TUT_C_iError_ListBoxRequired           : csText = "The control is not a list box";                     break;
            case TUT_C_iError_UnknownObject             : csText = "Unknown object name";                               break;
            case TUT_C_iError_UnknownVariable           : csText = "Unknown variable name or variable already declared";break;
            case TUT_C_iError_CountCtrlRequired         : csText = "A list box, list control or combo box is required"; break;
            case TUT_C_iError_UnknownMessageBoxCtrl     : csText = "Unknown message box control name";                  break;
            case TUT_C_iError_MessageBoxCtrlNotFound    : csText = "Message box control not found";                     break;
            case TUT_C_iError_NoReturnValue             : csText = "There is no return value";                          break;
            case TUT_C_iError_UnknownKey                : csText = "Unknown key name";                                  break;
            case TUT_C_iError_MultiLineRequired         : csText = "The text edit is not multi-line";                   break;
            case TUT_C_iError_InvalidLineNumber         : csText = "Invalid line number";                               break;
			case TUT_C_iError_LocalVariableExist        : csText = "A local variable with the same name already exists";break;
			case TUT_C_iError_GlobalVariableExist       : csText = "A global variable with the same name already exists";break;
			case TUT_C_iError_ListViewRequired			: csText = "A List View control is required";					break;
			case TUT_C_iError_UncoherentSelectionLimits : csText = "Limits of selection are incoherent";				break;

            default                                     : csText . Format ("Unknown error : code = %d" , _iErrorCode);  break;
        }

        if (_iErrorCode == TUT_C_iError_LabelNotFound)
             csMessage . Format ("Error n° %d in file '%s' : %s (%s)" , _iErrorCode , _csFileName , csText , m_csLabelToFind);
        else csMessage . Format ("Error n° %d in file '%s' : %s (line %d : '%s')" , _iErrorCode  , _csFileName , csText ,
                                                                                    _iLineNumber , _szLine);
    }

    // Give the focus to the tutorial thread
    if (m_p_stCurrentUserWindow)
        SetForegroundWindow (m_p_stCurrentUserWindow -> hWnd);

    MessageBox (NULL , csMessage , "Tutorial manager" , MB_OK + MB_ICONSTOP + MB_TASKMODAL);
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_vMoveMouseCursor
// Move the mouse cursor to the destination point
// Duration is in millisecond, speed in pixel/second and acceleration in pixel/secondē
//-----------------------------------------------------------------------------
void TUT_CTutorialManager::m_fn_vMoveMouseCursor (CPoint _stDestination, HWND _hWnd /*=NULL*/)
{
    long lMaxDuration  = m_lCurrentMaxDuration;
    long lMinSpeed     = m_lCurrentMinSpeed;
    long lAcceleration = m_lCurrentAcceleration;

    CPoint stCurrentMousePos;
    GetCursorPos (& stCurrentMousePos);
    CPoint stDelta = _stDestination - stCurrentMousePos;
                    
    long  lCurrentDistance = (long) sqrt ((double) (stDelta . x * stDelta . x + stDelta . y * stDelta . y));
    long  lOptimumSpeed   = lCurrentDistance * 1000 / lMaxDuration;
    if (lOptimumSpeed < lMinSpeed)  lOptimumSpeed = lMinSpeed;

    long lCurrentSpeed = 0;

    const long lDeltaT = m_bUseDrop ? 30 : 10;    // one move every 10 milliseconds

    long lTime = 0;

    while (stCurrentMousePos != _stDestination && lTime < lMaxDuration)
    {
        long lDistanceToStop = lCurrentSpeed * lCurrentSpeed / 2 / lAcceleration;
        long lCurrentAcceleration;
        // breake ?
        if (lCurrentDistance <= lDistanceToStop * 1100 / 1000)  lCurrentAcceleration = - lAcceleration;
        else
        // accelerate ?
        if (lCurrentSpeed < lOptimumSpeed * 1300 / 1000)        lCurrentAcceleration =   lAcceleration;
        else                                                    lCurrentAcceleration =               0;

        lCurrentSpeed         += lCurrentAcceleration * lDeltaT / 1000;
        POINT stTranslation;
        stTranslation . x = lDeltaT * lCurrentSpeed * stDelta . x / lCurrentDistance / 1000;
        stTranslation . y = lDeltaT * lCurrentSpeed * stDelta . y / lCurrentDistance / 1000;
        if (stTranslation . x == 0)
        {
            if (stCurrentMousePos . x < _stDestination . x)  stTranslation . x =  1;    else
            if (stCurrentMousePos . x > _stDestination . x)  stTranslation . x = -1;
        }
        if (stTranslation . y == 0)
        {
            if (stCurrentMousePos . y < _stDestination . y)  stTranslation . y =  1;    else
            if (stCurrentMousePos . y > _stDestination . y)  stTranslation . y = -1;
        }
        stCurrentMousePos . x += stTranslation . x;
        stCurrentMousePos . y += stTranslation . y;

        SetCursorPos (stCurrentMousePos .x , stCurrentMousePos . y);
		if (_hWnd)
		{
			CPoint stLocalMousePos = stCurrentMousePos;
		
			ScreenToClient (_hWnd , & stLocalMousePos);
			PostMessage (_hWnd , WM_MOUSEMOVE , 0 , MAKELPARAM (stLocalMousePos . x , stLocalMousePos . y) );
		}

        m_fn_vSleep (lDeltaT);
        lTime += lDeltaT;

        GetCursorPos (& stCurrentMousePos);
        stDelta = _stDestination - stCurrentMousePos;

        lCurrentDistance = (long) sqrt ((double) (stDelta . x * stDelta . x + stDelta . y * stDelta . y));
    }

    SetCursorPos (_stDestination . x , _stDestination . y);
	if (_hWnd)
	{
		CPoint stLocalMousePos = _stDestination;
	
		ScreenToClient (_hWnd , & stLocalMousePos);
		PostMessage (_hWnd , WM_MOUSEMOVE , 0 , MAKELPARAM (stLocalMousePos . x , stLocalMousePos . y) );
	}
}
                                                  
//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_vMoveMouseCursor
// Move the mouse cursor to the center of the given window
//-----------------------------------------------------------------------------
void TUT_CTutorialManager::m_fn_vMoveMouseCursor (HWND _hWnd)
{
    CRect  WindowPos;
    GetWindowRect (_hWnd , & WindowPos);

    m_fn_vMoveMouseCursor (WindowPos . CenterPoint ());
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_vClickOnControl
// Simulate a click on a control
//-----------------------------------------------------------------------------
void TUT_CTutorialManager::m_fn_vClickOnControl (HWND _hControl)
{
    // Get the center of the control
    CRect   Rect;
    GetClientRect (_hControl , & Rect);
    LPARAM  lParam      = MAKELPARAM (Rect . CenterPoint () . x , Rect . CenterPoint () . y);
    UINT    uiMsgDown;
    UINT    uiMsgUp;
    UINT    uiMsgDblClk;
    WPARAM  wParam,wButton;

    if (m_bUseRightButton)
    {
        uiMsgDown   = WM_RBUTTONDOWN;
        uiMsgUp     = WM_RBUTTONUP;
        uiMsgDblClk = WM_RBUTTONDBLCLK;
        wButton     = MK_RBUTTON;
    }
    else
    {
        uiMsgDown   = WM_LBUTTONDOWN;
        uiMsgUp     = WM_LBUTTONUP;
        uiMsgDblClk = WM_LBUTTONDBLCLK;
        wButton     = MK_LBUTTON;
    }
    wParam = (m_bUseShift ? MK_SHIFT : 0) + (m_bUseControl ? MK_CONTROL : 0);

    m_fn_vSleep (60);
    PostMessage (_hControl , uiMsgDown , wParam | wButton, lParam);    // Clic on the button
    m_fn_vSleep (60);
    if (m_bUseDoubleClic)
    {
        PostMessage (_hControl , uiMsgUp , wParam , lParam);
        m_fn_vSleep (60);
        PostMessage (_hControl , uiMsgDblClk , wParam | wButton, lParam);
        m_fn_vSleep (60);
    }
    PostMessage (_hControl , uiMsgUp , wParam , lParam);    // Release the button
    m_fn_vSleep (60);
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_vSetTextInEdit
// Simulate an edition of text,
//-----------------------------------------------------------------------------
void TUT_CTutorialManager::m_fn_vSetTextInEdit (HWND _hTextEdit , const CString _csText)
{
    HWND hParent = GetParent (_hTextEdit);

    for (int i = 1 ; i <= _csText . GetLength () ; i ++)
    {
        SetWindowText  (_hTextEdit , _csText . Left (i));

        SendMessage    (_hTextEdit , EM_SETSEL , i , i);    // Put the caret at the end of the line
        InvalidateRect (_hTextEdit , NULL      , FALSE);
        UpdateWindow   (_hTextEdit);

        SendMessage    (_hTextEdit , EM_SETMODIFY , TRUE , 0);

        if (_csText [i - 1] != ' ')  m_fn_vSleep (m_iDurationBetweenKeys);
    }
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_vSelectLineInComboBox
// Simulate a selection into a combo box
//-----------------------------------------------------------------------------
void TUT_CTutorialManager::m_fn_vSelectLineInComboBox (HWND _hComboBox , int _iLineNumber)
{
    m_fn_vSetGetLastError (TUT_C_iNoError);

    int iComboBoxGetCount = SendMessage (_hComboBox , CB_GETCOUNT , 0 , 0);
    if (_iLineNumber < 1 || _iLineNumber > iComboBoxGetCount)
    {
        m_fn_vSetGetLastError (TUT_C_iError_ComboBoxInvalidLine);
        return;
    }

    SendMessage (_hComboBox , CB_SHOWDROPDOWN , TRUE , 0);

    // To be sure the line will be visible
    CRect stListRect;
    SendMessage (_hComboBox , CB_GETDROPPEDCONTROLRECT , 0 , (LPARAM) & stListRect);

    int iTopIndex            = SendMessage (_hComboBox , CB_GETTOPINDEX , 0 , 0);
    int iItemRect            = SendMessage (_hComboBox , CB_GETITEMHEIGHT , 0 , 0);
    int iNumberOfVisibleItem = stListRect . Height () / iItemRect - 2;

    if (_iLineNumber - 1 < iTopIndex)
        SendMessage (_hComboBox , CB_SETTOPINDEX , _iLineNumber - 1 , 0);
    else
    if (_iLineNumber - 1 > iTopIndex + iNumberOfVisibleItem - 1)
        SendMessage (_hComboBox , CB_SETTOPINDEX , _iLineNumber - iNumberOfVisibleItem , 0);

    iTopIndex = SendMessage (_hComboBox , CB_GETTOPINDEX , 0 , 0);

    // Compute the position of the selected line to move the mouse to
    CRect ComboPos;
    GetWindowRect (_hComboBox , & ComboPos);
    CPoint Pos = ComboPos . CenterPoint ();
    Pos . y += ComboPos . Height () / 2 + iItemRect / 2 + iItemRect * (_iLineNumber - 1 - iTopIndex);
    m_fn_vMoveMouseCursor (Pos);

    SendMessage (_hComboBox , CB_SHOWDROPDOWN , FALSE , 0);

    if (m_bUseRightButton)
    {
		LPARAM lParam = MAKELPARAM (Pos . x , Pos . y);
		WPARAM wParam = (m_bUseShift ? MK_SHIFT : 0) | (m_bUseControl ? MK_CONTROL : 0);;

        ScreenToClient (_hComboBox , & Pos);
        PostMessage (_hComboBox , WM_RBUTTONDOWN , wParam + MK_RBUTTON , lParam);
        PostMessage (_hComboBox , WM_RBUTTONUP   , wParam              , lParam);
    }
    else
    {
        HWND hParentWnd = GetParent (_hComboBox);
        SendMessage (_hComboBox , CB_SETCURSEL , _iLineNumber - 1 , 0);
        PostMessage (hParentWnd , WM_COMMAND , (CBN_SELCHANGE << 16) + GetDlgCtrlID (_hComboBox) , (LPARAM) _hComboBox);
    }
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_vSelectLineInListBox
// Simulate a selection into a list box
//-----------------------------------------------------------------------------
void TUT_CTutorialManager::m_fn_vSelectLineInListBox (HWND _hListBox , int _iLineNumber , BOOL _bAddToSelection /*=FALSE*/)
{
    m_fn_vSetGetLastError (TUT_C_iNoError);

    int iListBoxGetCount = SendMessage (_hListBox , LB_GETCOUNT , 0 , 0);
    if (_iLineNumber < 1 || _iLineNumber > iListBoxGetCount)
    {
        m_fn_vSetGetLastError (TUT_C_iError_ListBoxInvalidLine);
        return;
    }

    // To be sure the line will be visible
    SendMessage(_hListBox , LVM_ENSUREVISIBLE , _iLineNumber - 1 , FALSE);

    // Compute the position of the selected line to move the mouse to
    CRect ListPos;
    GetWindowRect (_hListBox , & ListPos);
    CPoint Pos;
    int iTopIndex   = SendMessage (_hListBox , LB_GETTOPINDEX   , 0 , 0);
    int iItemHeight = SendMessage (_hListBox , LB_GETITEMHEIGHT , 0 , 0);
    if (m_bUsePosition)
    {
        Pos . y = ListPos . top + iItemHeight * (_iLineNumber - 1 - iTopIndex);

        if (m_bPercentForX)
            if (m_bRightBorder )  Pos . x  = ListPos . right - ListPos . Width () * m_iXPosition / 100;
                            else  Pos . x  = ListPos . left  + ListPos . Width () * m_iXPosition / 100;
        else
            if (m_bRightBorder )  Pos . x  = ListPos . right - m_iXPosition;
                            else  Pos . x  = ListPos . left  + m_iXPosition;

        if (m_bPercentForY)
            if (m_bBottomBorder)  Pos . y += iItemHeight - iItemHeight * m_iYPosition / 100;
                            else  Pos . y +=               iItemHeight * m_iYPosition / 100;
        else
            if (m_bBottomBorder)  Pos . y += iItemHeight - m_iYPosition;
                            else  Pos . y +=               m_iYPosition;
    }
    else
    {
        Pos . x = min (ListPos . CenterPoint () . x , ListPos . left + 4 * iItemHeight);
        Pos . y = ListPos . top + iItemHeight / 2 + iItemHeight * (_iLineNumber - 1 - iTopIndex);
    }
    m_fn_vMoveMouseCursor (Pos);

    if (m_bUseRightButton)
    {
        ScreenToClient (_hListBox , & Pos);
        LPARAM lParam = MAKELPARAM (Pos . x , Pos . y);
		WPARAM wParam = (m_bUseShift ? MK_SHIFT : 0) | (m_bUseControl ? MK_CONTROL : 0);

        PostMessage (_hListBox , WM_RBUTTONDOWN , wParam | MK_RBUTTON , lParam);
        PostMessage (_hListBox , WM_RBUTTONUP   , wParam              , lParam);
    }
    else
    if (m_bUseDoubleClic)
    {
        ScreenToClient (_hListBox , & Pos);
        LPARAM lParam = MAKELPARAM (Pos . x , Pos . y);
		WPARAM wParam = (m_bUseShift ? MK_SHIFT : 0) | (m_bUseControl ? MK_CONTROL : 0);

        PostMessage (_hListBox , WM_LBUTTONDOWN   , wParam | MK_LBUTTON , lParam);
        PostMessage (_hListBox , WM_LBUTTONUP     , wParam			    , lParam);
        PostMessage (_hListBox , WM_LBUTTONDBLCLK , wParam | MK_LBUTTON , lParam);
        PostMessage (_hListBox , WM_LBUTTONUP     , wParam				, lParam);
    }
    else
    if (m_bUsePosition)
    {
        ScreenToClient (_hListBox , & Pos);
        LPARAM lParam = MAKELPARAM (Pos . x , Pos . y);
		WPARAM wParam = (m_bUseShift ? MK_SHIFT : 0) | (m_bUseControl ? MK_CONTROL : 0);

        PostMessage (_hListBox , WM_LBUTTONDOWN , wParam | MK_LBUTTON , lParam);
        PostMessage (_hListBox , WM_LBUTTONUP   , wParam			  , lParam);
    }
    else
    {
        HWND hParentWnd = GetParent (_hListBox);
		if (GetWindowLong (_hListBox , GWL_STYLE) & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL))
		{
			// if not add, unselect all
			if (! _bAddToSelection)
				SendMessage (_hListBox , LB_SETSEL , FALSE , - 1);
			SendMessage (_hListBox , LB_SETSEL , TRUE , _iLineNumber - 1);
		}
        else
			SendMessage (_hListBox , LB_SETCURSEL , _iLineNumber - 1 , 0);

        PostMessage (hParentWnd , WM_COMMAND , (LBN_SELCHANGE << 16) + GetDlgCtrlID (_hListBox) , (LPARAM) _hListBox);
    }
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_vSelectLineInListCtrl
// Simulate a selection into a list control
//-----------------------------------------------------------------------------
void TUT_CTutorialManager::m_fn_vSelectLineInListCtrl (HWND _hListCtrl , int _iLineNumber)
{
    m_fn_vSetGetLastError (TUT_C_iNoError);

    int iListCtrlGetCount = ListView_GetItemCount (_hListCtrl);
    if (_iLineNumber < 1 || _iLineNumber > iListCtrlGetCount)
    {
        m_fn_vSetGetLastError (TUT_C_iError_ListCtrlInvalidLine);
        return;
    }

    // To be sure the line will be visible
    ListView_EnsureVisible (_hListCtrl , _iLineNumber - 1 , FALSE);

    // Compute the position of the selected line to move the mouse to
    CRect stItemRect;
    ListView_GetItemRect (_hListCtrl , _iLineNumber - 1 , & stItemRect , LVIR_BOUNDS);
    POINT stPos;
    stPos . x = min (stItemRect . CenterPoint () . x , stItemRect . left + 2 * stItemRect . Height ());
    stPos . y = stItemRect . CenterPoint () . y;
    LPARAM lParam = MAKELPARAM (stPos . x , stPos . y);
	WPARAM wParam = (m_bUseShift ? MK_SHIFT : 0) | (m_bUseControl ? MK_CONTROL : 0);

    if (m_bUseRightButton)
    {
        ClientToScreen (_hListCtrl , & stPos);
        m_fn_vMoveMouseCursor (stPos);
        PostMessage (_hListCtrl , WM_RBUTTONDOWN , wParam + MK_RBUTTON , lParam);
        PostMessage (_hListCtrl , WM_RBUTTONUP   , wParam			   , lParam);
    }
    else
    if (m_bUseDoubleClic)
    {
        ClientToScreen (_hListCtrl , & stPos);
        m_fn_vMoveMouseCursor (stPos);

        PostMessage (_hListCtrl , WM_LBUTTONDOWN   , wParam + MK_LBUTTON , lParam);
        PostMessage (_hListCtrl , WM_LBUTTONUP     , wParam				 , lParam);
        PostMessage (_hListCtrl , WM_LBUTTONDBLCLK , wParam + MK_LBUTTON , lParam);
        PostMessage (_hListCtrl , WM_LBUTTONUP     , wParam				 , lParam);
    }
    else
    {
        if (m_bUsePosition)
        {
            if (m_bPercentForX)
                if (m_bRightBorder )  stPos . x = stItemRect . right  - stItemRect . Width  () * m_iXPosition / 100;
                                else  stPos . x = stItemRect . left   + stItemRect . Width  () * m_iXPosition / 100;
            else
                if (m_bRightBorder )  stPos . x = stItemRect . right  - m_iXPosition;
                                else  stPos . x = stItemRect . left   + m_iXPosition;

            if (m_bPercentForY)
                if (m_bBottomBorder)  stPos . y = stItemRect . bottom - stItemRect . Height () * m_iYPosition / 100;
                                else  stPos . y = stItemRect . top    + stItemRect . Height () * m_iYPosition / 100;
            else
                if (m_bBottomBorder)  stPos . y = stItemRect . bottom - m_iYPosition;
                                else  stPos . y = stItemRect . top    + m_iYPosition;

            lParam = MAKELPARAM (stPos . x , stPos . y);
        }

        ClientToScreen (_hListCtrl , & stPos);
        m_fn_vMoveMouseCursor (stPos);

        PostMessage (_hListCtrl , WM_LBUTTONDOWN , wParam + MK_LBUTTON , lParam);
        PostMessage (_hListCtrl , WM_LBUTTONUP   , wParam              , lParam);
    }
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_vEnsureVisibleInTree
// Make sure the given item of a tree is visible or invisible
//-----------------------------------------------------------------------------
void TUT_CTutorialManager::m_fn_vEnsureVisibleInTree (HWND _hTreeCtrl , HTREEITEM _hItem ,
                                                      BOOL _bEnsureVisible      /* = TRUE */)

{
    // Get the parent item. If there is no parent, we show the item and we return
    HTREEITEM hParentItem = TreeView_GetParent (_hTreeCtrl , _hItem);
    if (! hParentItem)
    {
        TreeView_EnsureVisible (_hTreeCtrl , _hItem);
        return;
    }

    // If the parent is not visible, we make it visible (recursiv call)
    if (! m_fn_bIsItemVisibleInTree (_hTreeCtrl , hParentItem))
          m_fn_vEnsureVisibleInTree (_hTreeCtrl , hParentItem);

    // Now that the parent is visible, we check if its children are visible
    if (m_fn_uiGetTreeCtrlState (_hTreeCtrl , hParentItem , TVIS_EXPANDED) != TVIS_EXPANDED)
    {
        CRect Rect;
        TreeView_GetItemRect (_hTreeCtrl , hParentItem , & Rect , FALSE);
        POINT Pos;
        Pos . x = Rect . left + Rect . Height () / 2;
        Pos . y = Rect . CenterPoint () . y;
        ClientToScreen (_hTreeCtrl , & Pos);

        m_fn_vMoveMouseCursor (Pos);
        TreeView_Expand (_hTreeCtrl , hParentItem , _bEnsureVisible ? TVE_EXPAND : TVE_COLLAPSE);
    }

    if (_bEnsureVisible)  TreeView_EnsureVisible (_hTreeCtrl , _hItem);
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_bSelectItemInTreeBox
// Simulate a selection in a tree box
// Return FALSE if there is no item with the given text
//-----------------------------------------------------------------------------
BOOL TUT_CTutorialManager::m_fn_bSelectItemInTreeBox (HWND _hTreeCtrl , CString _csTextToSelect)
{
    HTREEITEM hRootItem     = TreeView_GetRoot (_hTreeCtrl);
    HTREEITEM hItemToSelect = m_fn_hFindStringInTree (_hTreeCtrl , hRootItem , _csTextToSelect);
    if (! hItemToSelect)  return FALSE;    // Not found -> return FALSE

    m_fn_vEnsureVisibleInTree (_hTreeCtrl , hItemToSelect , TRUE);

    CRect Rect;
    TreeView_GetItemRect (_hTreeCtrl , hItemToSelect , & Rect , FALSE);
    POINT Pos;
    Pos . x = Rect . left + Rect . Height () * 4;
    Pos . y = Rect . CenterPoint () . y;
    ClientToScreen (_hTreeCtrl , & Pos);

    // Select the item
    m_fn_vMoveMouseCursor (Pos);
    TreeView_SelectItem (_hTreeCtrl , hItemToSelect);

    // Send a second notification to the parent, because the first one sent by TreeView_SelectItem did not have action = TVC_BYMOUSE
    NM_TREEVIEW stTreeView;
    stTreeView . hdr . hwndFrom = _hTreeCtrl;
    stTreeView . hdr . idFrom   = GetDlgCtrlID (_hTreeCtrl);
    stTreeView . hdr . code     = TVN_SELCHANGED;
    stTreeView . action         = TVC_BYMOUSE;
    SendMessage (GetParent (_hTreeCtrl) , WM_NOTIFY , GetDlgCtrlID (_hTreeCtrl) , (LPARAM) & stTreeView);

    return TRUE;
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_vWriteText
// Write a line of text into a tutorial window
//-----------------------------------------------------------------------------
void TUT_CTutorialManager::m_fn_vWriteText (HWND _hEditView , CString _csText)
{
    ASSERT (IsWindow (_hEditView));     // The edit view must be correct

    HWND hPreviousWnd = GetForegroundWindow ();

    // Put the caret at the end of the text
    int iNumberOfChar = GetWindowTextLength (_hEditView);
    SendMessage (_hEditView , EM_SETSEL , iNumberOfChar , iNumberOfChar);

    // Change the format
    CHARFORMAT stCharFormat;
    stCharFormat . cbSize      = sizeof (CHARFORMAT);
    stCharFormat . dwMask      = CFM_COLOR | CFM_SIZE | CFM_FACE;
    stCharFormat . dwEffects  &= ~ CFE_AUTOCOLOR;    // Remove the flag "auto color"
    stCharFormat . crTextColor = m_xCurrentColor;
    stCharFormat . yHeight     = m_lCurrentHeight * GetDeviceCaps (GetDC (_hEditView) , LOGPIXELSY) * 180 / 1000;
    strcpy (stCharFormat . szFaceName , "Arial");
    SendMessage (_hEditView , EM_SETCHARFORMAT , SCF_SELECTION , (LPARAM) & stCharFormat);

    PARAFORMAT stParaFormat;
    stParaFormat . cbSize     = sizeof (PARAFORMAT);
    stParaFormat . dwMask     = PFM_ALIGNMENT;
    stParaFormat . wAlignment = (unsigned short) m_lCurrentAlignment;
    SendMessage (_hEditView ,EM_SETPARAFORMAT , 0 , (LPARAM) & stParaFormat);

    // Insert the new text
    if (_csText . Right (1) == "\\")  _csText = _csText . Left (_csText . GetLength () - 1);    // If the string ends with '\', no line feed
    else
    {
        _csText += (char)13;
        _csText += (char)10;
    }
    SendMessage    (_hEditView , EM_REPLACESEL , FALSE /* No Undo */ , (LPARAM) (LPCTSTR) _csText);

    // Check if scroll is necessary
    RECT  stEditViewRect;
    GetClientRect (_hEditView , & stEditViewRect);
    POINT stEndOfTextPos;
    BOOL  bOk;
    int   iCounter = 0;
    do
    {
        SendMessage    (_hEditView , EM_POSFROMCHAR , (WPARAM) & stEndOfTextPos , GetWindowTextLength (_hEditView));
        bOk = stEndOfTextPos . y <= stEditViewRect . bottom;
        if (! bOk)  SendMessage (_hEditView , EM_LINESCROLL , 0 , 1);
    } while (bOk == FALSE && ++iCounter < 10);    // To prevent from infinite loop

    InvalidateRect (_hEditView , NULL , FALSE);
    UpdateWindow   (_hEditView);

    SetForegroundWindow (hPreviousWnd);
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_vSendKey
// Send a key
//-----------------------------------------------------------------------------
void TUT_CTutorialManager::m_fn_vSendKey (int _iVirtualKeyCode , BOOL _bKeyPressed)
{
    keybd_event (_iVirtualKeyCode , _iVirtualKeyCode , _bKeyPressed ? 0 : KEYEVENTF_KEYUP , 0);
	Sleep (10);
}

//-----------------------------------------------------------------------------
// class TUT_CTutorialManager - m_fn_szHandleQuote
// replace two consecutive quotes by a double-quote
//-----------------------------------------------------------------------------
char *TUT_CTutorialManager::m_fn_szHandleQuote(char * _szString)
{
	char *cBegin = _szString;
	char *cTarget = _szString;
	char *cSource = _szString;
	BOOL bPreviousQuote = FALSE;

	while (*cSource)
	{
		if (*cSource == '\'')
		{
			if (bPreviousQuote)
			{
				*cTarget++ = '\"';
				bPreviousQuote = FALSE;
			}
			else
			{
				bPreviousQuote = TRUE;
			}
		}
		else
		{
			if (bPreviousQuote)
			{
				*cTarget++ = '\'';
			}
			*cTarget++ = *cSource;
			bPreviousQuote = FALSE;
		}
		cSource++;
	}
	if (bPreviousQuote)
	{
		*cTarget++ = '\'';
	}

	*cTarget = 0;

	return _szString;
}