/* This file is part of Warzone 2100. Copyright (C) 1999-2004 Eidos Interactive Copyright (C) 2005-2007 Warzone Resurrection Project Warzone 2100 is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Warzone 2100 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Warzone 2100; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "lib/framework/frame.h" #include "lib/gamelib/gtime.h" #include "text.h" #include "keymap.h" #include "console.h" #include "keybind.h" #include "display3d.h" #include "keyedit.h" /* KeyMap.c Alex McLean Pumpkin Studios, EIDOS Interactive. Internal Use Only ----------------- Handles the assignment of functions to keys. */ // ---------------------------------------------------------------------------------- /* Function Prototypes */ KEY_MAPPING *keyAddMapping ( KEY_STATUS status, KEY_CODE metaCode, KEY_CODE subcode, KEY_ACTION action, void (*pKeyMapFunc)(void), STRING *name ); BOOL keyRemoveMapping ( KEY_CODE metaCode, KEY_CODE subCode ); BOOL keyRemoveMappingPt ( KEY_MAPPING *psToRemove ); KEY_MAPPING *keyFindMapping ( KEY_CODE metaCode, KEY_CODE subCode ); void keyClearMappings ( void ); void keyProcessMappings ( BOOL bExclude ); UDWORD getNumMappings ( void ); void keyInitMappings ( BOOL bForceDefaults ); void keyShowMapping ( KEY_MAPPING *psMapping ); void keyAllMappingsInactive ( void ); void keyAllMappingsActive ( void ); void keySetMappingStatus ( KEY_MAPPING *psMapping, BOOL state ); void processDebugMappings ( BOOL val ); BOOL getDebugMappingStatus ( void ); BOOL keyReAssignMappingName(STRING *pName, KEY_CODE newMetaCode, KEY_CODE newSubCode); BOOL keyReAssignMapping( KEY_CODE origMetaCode, KEY_CODE origSubCode, KEY_CODE newMetaCode, KEY_CODE newSubCode ); KEY_MAPPING *getKeyMapFromName(STRING *pName); extern BOOL bAllowDebugMode; // ---------------------------------------------------------------------------------- /* WIN 32 specific */ BOOL checkQwertyKeys ( void ); UDWORD asciiKeyCodeToTable ( KEY_CODE code ); KEY_CODE getQwertyKey ( void ); UDWORD getMarkerX ( KEY_CODE code ); UDWORD getMarkerY ( KEY_CODE code ); SDWORD getMarkerSpin ( KEY_CODE code ); // ---------------------------------------------------------------------------------- KEY_MAPPING *keyGetMappingFromFunction(void *function) { KEY_MAPPING *psMapping,*psReturn; for(psMapping = keyMappings,psReturn = NULL; psMapping AND !psReturn; psMapping = psMapping->psNext) { if(psMapping->function == function) { psReturn = psMapping; } } return(psReturn); } // ---------------------------------------------------------------------------------- /* Some win32 specific stuff allowing the user to add key mappings themselves */ #define NUM_QWERTY_KEYS 26 typedef struct _keymap_Marker { KEY_MAPPING *psMapping; UDWORD xPos,yPos; SDWORD spin; } KEYMAP_MARKER; static KEYMAP_MARKER qwertyKeyMappings[NUM_QWERTY_KEYS]; static BOOL bDoingDebugMappings = FALSE; // PSX needs this too... // ---------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------- /* The linked list of present key mappings */ KEY_MAPPING *keyMappings; /* Holds number of active mappings */ UDWORD numActiveMappings; /* Last meta and sub key that were recorded */ static KEY_CODE lastMetaKey,lastSubKey; static BOOL bKeyProcessing = TRUE; // ---------------------------------------------------------------------------------- // Adding a mapped function ? add a save pointer! Thank AlexL. // don't bugger around with the order either. new ones go at the end! DEBUG in debug section.. //typedef void (*_keymapsave)(void); _keymapsave keyMapSaveTable[] = { kf_ChooseManufacture, kf_ChooseResearch, kf_ChooseBuild, kf_ChooseDesign, kf_ChooseIntelligence, kf_ChooseCommand, kf_ToggleRadar, kf_ToggleConsole, kf_ToggleEnergyBars, kf_ToggleReloadBars, kf_ScreenDump , kf_MoveToLastMessagePos, kf_AssignGrouping_1, kf_AssignGrouping_2, kf_AssignGrouping_3, kf_AssignGrouping_4, kf_AssignGrouping_5, kf_AssignGrouping_6, kf_AssignGrouping_7, kf_AssignGrouping_8, kf_AssignGrouping_9, kf_SelectGrouping_1, kf_SelectGrouping_2, kf_SelectGrouping_3, kf_SelectGrouping_4, kf_SelectGrouping_5, kf_SelectGrouping_6, kf_SelectGrouping_7, kf_SelectGrouping_8, kf_SelectGrouping_9, kf_addMultiMenu, kf_multiAudioStart, kf_multiAudioStop, kf_SeekNorth, kf_ToggleCamera, kf_addInGameOptions, kf_RadarZoomOut, kf_RadarZoomIn, kf_ZoomOut, kf_ZoomIn, kf_PitchForward, kf_RotateLeft, kf_ResetPitch, kf_RotateRight, kf_PitchBack, kf_RightOrderMenu, kf_JumpToResourceExtractor, kf_JumpToRepairUnits, kf_JumpToConstructorUnits, kf_JumpToSensorUnits, kf_JumpToCommandUnits, kf_ToggleOverlays, kf_CentreOnBase, kf_SetDroidAttackCease , kf_JumpToUnassignedUnits , kf_SetDroidAttackReturn , kf_SetDroidAttackAtWill , kf_SetDroidReturnToBase , kf_SetDroidRangeDefault, kf_ToggleFormationSpeedLimiting, kf_SetDroidRangeShort, kf_SetDroidMovePursue , kf_SetDroidMovePatrol , kf_SetDroidGoForRepair , kf_SetDroidMoveHold , kf_SendTextMessage, kf_SetDroidRangeLong, kf_ScatterDroids, kf_SetDroidRetreatMedium, kf_SetDroidRetreatHeavy, kf_SetDroidRetreatNever, kf_SelectAllCombatUnits, kf_SelectAllDamaged, kf_SelectAllHalfTracked, kf_SelectAllHovers, kf_SetDroidRecycle, kf_SelectAllOnScreenUnits, kf_SelectAllTracked, kf_SelectAllUnits, kf_SelectAllVTOLs, kf_SelectAllWheeled, kf_FinishResearch, kf_FrameRate, kf_SelectAllSameType, kf_SelectNextFactory, kf_SelectNextResearch, kf_SelectNextPowerStation, kf_SelectNextCyborgFactory, kf_ToggleConsoleDrop, kf_SelectCommander_1, kf_SelectCommander_2, kf_SelectCommander_3, kf_SelectCommander_4, kf_SelectCommander_5, kf_SelectCommander_6, kf_SelectCommander_7, kf_SelectCommander_8, kf_SelectCommander_9, kf_FaceNorth, kf_FaceSouth, kf_FaceWest, kf_FaceEast, kf_SpeedUp, kf_SlowDown, kf_NormalSpeed, kf_ToggleRadarJump, kf_MovePause, kf_ToggleReopenBuildMenu, kf_SensorDisplayOn, kf_SensorDisplayOff, kf_ToggleRadarTerrain, //radar terrain on/off kf_ToggleRadarAllyEnemy, //enemy/ally radar color toggle kf_ToggleSensorDisplay, // Was commented out below. moved also!. Re-enabled --Q 5/10/05 kf_AddHelpBlip, //Add a beacon kf_AllAvailable, kf_ToggleDebugMappings, kf_NewPlayerPower, kf_TogglePauseMode, kf_MaxScrollLimits, kf_DebugDroidInfo, kf_RecalcLighting, kf_ToggleFog, kf_ChooseOptions, kf_TogglePower, kf_ToggleWeather, kf_SelectPlayer, kf_ToggleMistFog, kf_ToggleFogColour, kf_AddMissionOffWorld, kf_KillSelected, kf_ShowMappings, kf_GiveTemplateSet, kf_ToggleVisibility, kf_FinishResearch, kf_LowerTile, kf_ToggleDemoMode, kf_ToggleGodMode, kf_EndMissionOffWorld, kf_SystemClose, kf_ToggleShadows, kf_RaiseTile, kf_ToggleOutline, kf_TriFlip, kf_UpDroidScale, kf_DownDroidScale, kf_RaiseGamma, kf_LowerGamma, kf_AssignGrouping_0, kf_SelectGrouping_0, kf_SelectCommander_0, NULL // last function! }; // ---------------------------------------------------------------------------------- /* Here is where we assign functions to keys and to combinations of keys. these will be read in from a .cfg file customisable by the player from an in-game menu */ void keyInitMappings( BOOL bForceDefaults ) { UDWORD i; keyMappings = NULL; numActiveMappings = 0; bKeyProcessing = TRUE; processDebugMappings(FALSE); for(i=0; ipName = (STRING*)MALLOC(strlen(name)+1); ASSERT( (int)newMapping->pName,"Couldn't allocate the memory for the string in a mapping" ); memSetBlockHeap(psHeap); /* Copy over the name */ strcpy(newMapping->pName,name); /* Fill up our entries, first the ones that activate it */ newMapping->metaKeyCode = metaCode; newMapping->subKeyCode = subCode; newMapping->status = status; /* When it was last called - needed? */ newMapping->lastCalled = gameTime; /* And what gets called when it's activated */ //newMapping->function = function; newMapping->function = pKeyMapFunc; /* Is it functional on the key being down or just pressed */ newMapping->action = action; newMapping->altMetaKeyCode = KEY_IGNORE; /* We always request only the left hand one */ if(metaCode == KEY_LCTRL) {newMapping->altMetaKeyCode = KEY_RCTRL;} else if(metaCode == KEY_LALT) {newMapping->altMetaKeyCode = KEY_RALT;} else if(metaCode == KEY_LSHIFT) {newMapping->altMetaKeyCode = KEY_RSHIFT;} /* Set it to be active */ newMapping->active = TRUE; /* Add it to the start of the list */ newMapping->psNext = keyMappings; keyMappings = newMapping; numActiveMappings++; return(newMapping); } // ---------------------------------------------------------------------------------- /* Removes a mapping from the list specified by the key codes */ BOOL keyRemoveMapping( KEY_CODE metaCode, KEY_CODE subCode ) { KEY_MAPPING *mapping; mapping = keyFindMapping(metaCode, subCode); return(keyRemoveMappingPt(mapping)); } // ---------------------------------------------------------------------------------- /* Returns a pointer to a mapping if it exists - NULL otherwise */ KEY_MAPPING *keyFindMapping( KEY_CODE metaCode, KEY_CODE subCode ) { KEY_MAPPING *psCurr; /* See if we can find it */ for(psCurr = keyMappings; psCurr != NULL; psCurr = psCurr->psNext) { if(psCurr->metaKeyCode == metaCode AND psCurr->subKeyCode == subCode) { return(psCurr); } } return(NULL); } // ---------------------------------------------------------------------------------- /* clears the mappings list and frees the memory */ void keyClearMappings( void ) { while(keyMappings) { keyRemoveMappingPt(keyMappings); } } // ---------------------------------------------------------------------------------- /* Removes a mapping specified by a pointer */ BOOL keyRemoveMappingPt(KEY_MAPPING *psToRemove) { KEY_MAPPING *psPrev,*psCurr; if(psToRemove == NULL) { return(FALSE); } if(psToRemove == keyMappings AND keyMappings->psNext == NULL) { if (keyMappings->pName) FREE(keyMappings->pName); // ffs FREE(keyMappings); keyMappings = NULL; numActiveMappings = 0; return(TRUE); } /* See if we can find it */ for(psPrev = NULL, psCurr = keyMappings; psCurr != NULL AND psCurr!=psToRemove; psPrev = psCurr, psCurr = psCurr->psNext) { /*NOP*/ } /* If it was found... */ if(psCurr==psToRemove) { /* See if it was the first element */ if(psPrev) { /* It wasn't */ psPrev->psNext = psCurr->psNext; } else { /* It was */ keyMappings = psCurr->psNext; } /* Free up the memory, first for the string */ if (psCurr->pName) FREE(psCurr->pName); // only free it if it was allocated in the first place (ffs) /* and then for the mapping itself */ FREE(psCurr); numActiveMappings--; return(TRUE); } return(FALSE); } // ---------------------------------------------------------------------------------- /* Just returns how many are active */ UDWORD getNumMappings( void ) { return(numActiveMappings); } // ---------------------------------------------------------------------------------- /* Manages update of all the active function mappings */ void keyProcessMappings( BOOL bExclude ) { KEY_MAPPING *keyToProcess; BOOL bMetaKeyDown; BOOL bKeyProcessed; /* Bomb out if there are none */ if(!keyMappings OR !numActiveMappings OR !bKeyProcessing) { return; } /* Jump out if we've got a new mapping */ (void) checkQwertyKeys(); /* Check for the meta keys */ if(keyDown(KEY_LCTRL) OR keyDown(KEY_RCTRL) OR keyDown(KEY_LALT) OR keyDown(KEY_RALT) OR keyDown(KEY_LSHIFT) OR keyDown(KEY_RSHIFT)) { bMetaKeyDown = TRUE; } else { bMetaKeyDown = FALSE; } /* Run through all our mappings */ for(keyToProcess = keyMappings; keyToProcess!=NULL; keyToProcess = keyToProcess->psNext) { /* We haven't acted upon it */ bKeyProcessed = FALSE; if(!keyToProcess->active) { /* Get out if it's inactive */ break; } /* Skip innappropriate ones when necessary */ if(bExclude AND keyToProcess->status!=KEYMAP_ALWAYS_PROCESS) { break; } if(keyToProcess->subKeyCode == KEY_MAXSCAN) { continue; } if(keyToProcess->metaKeyCode==KEY_IGNORE AND !bMetaKeyDown AND !(keyToProcess->status==KEYMAP__DEBUG AND bDoingDebugMappings == FALSE) ) { switch(keyToProcess->action) { case KEYMAP_PRESSED: /* Were the right keys pressed? */ if(keyPressed(keyToProcess->subKeyCode)) { lastSubKey = keyToProcess->subKeyCode; /* Jump to the associated function call */ keyToProcess->function(); bKeyProcessed = TRUE; } break; case KEYMAP_DOWN: /* Is the key Down? */ if(keyDown(keyToProcess->subKeyCode)) { lastSubKey = keyToProcess->subKeyCode; /* Jump to the associated function call */ keyToProcess->function(); bKeyProcessed = TRUE; } break; case KEYMAP_RELEASED: /* Has the key been released? */ if(keyReleased(keyToProcess->subKeyCode)) { lastSubKey = keyToProcess->subKeyCode; /* Jump to the associated function call */ keyToProcess->function(); bKeyProcessed = TRUE; } break; default: debug( LOG_ERROR, "Weirdy action on keymap processing" ); abort(); break; } } /* Process the combi ones */ if( (keyToProcess->metaKeyCode!=KEY_IGNORE AND bMetaKeyDown) AND !(keyToProcess->status==KEYMAP__DEBUG AND bDoingDebugMappings == FALSE)) { /* It's a combo keypress - one held down and the other pressed */ if(keyDown(keyToProcess->metaKeyCode) AND keyPressed(keyToProcess->subKeyCode) ) { lastMetaKey = keyToProcess->metaKeyCode; lastSubKey = keyToProcess->subKeyCode; keyToProcess->function(); bKeyProcessed = TRUE; } else if(keyToProcess->altMetaKeyCode!=KEY_IGNORE) { if(keyDown(keyToProcess->altMetaKeyCode) AND keyPressed(keyToProcess->subKeyCode)) { lastMetaKey = keyToProcess->metaKeyCode; lastSubKey = keyToProcess->subKeyCode; keyToProcess->function(); bKeyProcessed = TRUE; } } } if(bKeyProcessed) { if(keyToProcess->status==KEYMAP__DEBUG AND bDoingDebugMappings) { // this got really annoying. what purpose? - Per // CONPRINTF(ConsoleString,(ConsoleString,"DEBUG MAPPING : %s",keyToProcess->pName)); } } } } // ---------------------------------------------------------------------------------- /* Allows _new_ mappings to be made at runtime */ BOOL checkQwertyKeys( void ) { KEY_CODE qKey; UDWORD tableEntry; BOOL aquired; aquired = FALSE; /* Are we trying to make a new map marker? */ if( keyDown(KEY_LALT)) { /* Did we press a key */ qKey = getQwertyKey(); if(qKey) { tableEntry = asciiKeyCodeToTable(qKey); /* We're assigning something to the key */ if(qwertyKeyMappings[tableEntry].psMapping) { /* Get rid of the old mapping on this key if there was one */ keyRemoveMappingPt(qwertyKeyMappings[tableEntry].psMapping); } /* Now add the new one for this location */ qwertyKeyMappings[tableEntry].psMapping = keyAddMapping(KEYMAP_ALWAYS,KEY_LSHIFT,qKey,KEYMAP_PRESSED,kf_JumpToMapMarker,"Jump to new map marker"); aquired = TRUE; /* Store away the position and view angle */ qwertyKeyMappings[tableEntry].xPos = player.p.x; qwertyKeyMappings[tableEntry].yPos = player.p.z; qwertyKeyMappings[tableEntry].spin = player.r.y; } } return(aquired); } // ---------------------------------------------------------------------------------- // this function isn't really module static - should be removed - debug only void keyShowMappings( void ) { KEY_MAPPING *psMapping; for(psMapping = keyMappings; psMapping; psMapping = psMapping->psNext) { keyShowMapping(psMapping); } } // ---------------------------------------------------------------------------------- /* Sends a particular key mapping to the console */ void keyShowMapping(KEY_MAPPING *psMapping) { STRING asciiSub[20],asciiMeta[20]; BOOL onlySub; onlySub = TRUE; if(psMapping->metaKeyCode!=KEY_IGNORE) { keyScanToString(psMapping->metaKeyCode,(STRING *)&asciiMeta,20); onlySub = FALSE; } keyScanToString(psMapping->subKeyCode,(STRING *)&asciiSub,20); if(onlySub) { CONPRINTF(ConsoleString,(ConsoleString,"%s - %s",asciiSub,psMapping->pName)); } else { CONPRINTF(ConsoleString,(ConsoleString,"%s and %s - %s",asciiMeta,asciiSub,psMapping->pName)); } } // ---------------------------------------------------------------------------------- /* Returns the key code of the last sub key pressed - allows called functions to have a simple stack */ KEY_CODE getLastSubKey( void ) { return(lastSubKey); } // ---------------------------------------------------------------------------------- /* Returns the key code of the last meta key pressed - allows called functions to have a simple stack */ KEY_CODE getLastMetaKey( void ) { return(lastMetaKey); } // ---------------------------------------------------------------------------------- /* Allows us to enable/disable the whole mapping system */ void keyEnableProcessing( BOOL val ) { bKeyProcessing = val; } // ---------------------------------------------------------------------------------- /* Sets all mappings to be inactive */ void keyAllMappingsInactive( void ) { KEY_MAPPING *psMapping; for(psMapping = keyMappings; psMapping; psMapping = psMapping->psNext) { psMapping->active = FALSE; } } // ---------------------------------------------------------------------------------- void keyAllMappingsActive( void ) { KEY_MAPPING *psMapping; for(psMapping = keyMappings; psMapping; psMapping = psMapping->psNext) { psMapping->active = TRUE; } } // ---------------------------------------------------------------------------------- /* Allows us to make active/inactive specific mappings */ void keySetMappingStatus(KEY_MAPPING *psMapping, BOOL state) { psMapping->active = state; } /* Returns the key code of the first ascii key that its finds has been PRESSED */ KEY_CODE getQwertyKey( void ) { UDWORD i; for(i = KEY_Q; i <= KEY_P; i++) { if(keyPressed(i)) { return(i); // top row key pressed } } for(i = KEY_A; i <= KEY_L; i++) { if(keyPressed(i)) { return(i); // middle row key pressed } } for(i = KEY_Z; i <= KEY_M; i++) { if(keyPressed(i)) { return(i); // bottomw row key pressed } } return(0); // no ascii key pressed } // ---------------------------------------------------------------------------------- /* Returns the number (0 to 26) of a key on the keyboard from it's keycode. Q is zero, through to M being 25 */ UDWORD asciiKeyCodeToTable(KEY_CODE code) { if(code<=KEY_P) { code = code - KEY_Q; // q is the first of the ascii scan codes } else if(code <=KEY_L) { code = (code - KEY_A) + 10; // ten keys from q to p } else if(code<=KEY_M) { code = (code - KEY_Z) + 19; // 19 keys before, the 10 from q..p and the 9 from a..l } return((UDWORD) code); } // ---------------------------------------------------------------------------------- /* Returns the map X position associated with the passed in keycode */ UDWORD getMarkerX( KEY_CODE code ) { UDWORD entry; entry = asciiKeyCodeToTable(code); return(qwertyKeyMappings[entry].xPos); } // ---------------------------------------------------------------------------------- /* Returns the map Y position associated with the passed in keycode */ UDWORD getMarkerY( KEY_CODE code ) { UDWORD entry; entry = asciiKeyCodeToTable(code); return(qwertyKeyMappings[entry].yPos); } // ---------------------------------------------------------------------------------- /* Returns the map Y rotation associated with the passed in keycode */ SDWORD getMarkerSpin( KEY_CODE code ) { UDWORD entry; entry = asciiKeyCodeToTable(code); return(qwertyKeyMappings[entry].spin); } // ---------------------------------------------------------------------------------- /* Defines whether we process debug key mapping stuff */ void processDebugMappings( BOOL val ) { bDoingDebugMappings = val; } // ---------------------------------------------------------------------------------- /* Returns present status of debug mapping processing */ BOOL getDebugMappingStatus( void ) { return(bDoingDebugMappings); } // ---------------------------------------------------------------------------------- BOOL keyReAssignMapping( KEY_CODE origMetaCode, KEY_CODE origSubCode, KEY_CODE newMetaCode, KEY_CODE newSubCode ) { KEY_MAPPING *psMapping; BOOL bFound; for(psMapping = keyMappings,bFound = FALSE; psMapping AND !bFound; psMapping = psMapping->psNext) { /* Find the original */ if(psMapping->metaKeyCode == origMetaCode AND psMapping->subKeyCode == origSubCode) { /* Not all can be remapped */ if(psMapping->status != KEYMAP_ALWAYS OR psMapping->status == KEYMAP_ALWAYS_PROCESS) { psMapping->metaKeyCode = newMetaCode; psMapping->subKeyCode = newSubCode; bFound = TRUE; } } } return(bFound); } /* BOOL keyReAssignMappingName(STRING *pName, KEY_CODE newMetaCode, KEY_CODE newSubCode) ) { KEY_MAPPING *psMapping; KEY_CODE origMetaCode,origSubCode; BOOL bReplaced; for(psMapping = keyMappings,bReplaced = FALSE; psMapping AND !bReplaced; psMapping = psMapping->psNext) { if(strcmp(psMapping->pName,pName) == FALSE) //negative { if(psMapping->status==KEYMAP_ASSIGNABLE) { (void)keyAddMapping(psMapping->status,newMetaCode, newSubCode, psMapping->action,psMapping->function,psMapping->pName); bReplaced = TRUE; origMetaCode = psMapping->metaKeyCode; origSubCode = psMapping->subKeyCode; } } } if(bReplaced) { keyRemoveMapping(origMetaCode, origSubCode); } return(bReplaced); } */ // ---------------------------------------------------------------------------------- KEY_MAPPING *getKeyMapFromName(STRING *pName) { KEY_MAPPING *psMapping; for(psMapping = keyMappings; psMapping; psMapping = psMapping->psNext) { if(strcmp(pName,psMapping->pName) == FALSE) { return(psMapping); } } return(NULL); } // ---------------------------------------------------------------------------------- BOOL keyReAssignMappingName(STRING *pName,KEY_CODE newMetaCode, KEY_CODE newSubCode) { KEY_MAPPING *psMapping; psMapping = getKeyMapFromName(pName); if(psMapping) { if(psMapping->status == KEYMAP_ASSIGNABLE) { (void)keyAddMapping(psMapping->status,newMetaCode, newSubCode, psMapping->action,psMapping->function,psMapping->pName); keyRemoveMappingPt(psMapping); return(TRUE); } } return(FALSE); } // ----------------------------------------------------------------------------------