You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

947 lines
33 KiB

/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program 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 3 of the License, or
(at your option) any later version.
This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
//////////////////////////////////////////////////////////////////////////////
// //
// plInputInterfaceMgr.cpp - The manager of all input interface layers //
// //
//// History /////////////////////////////////////////////////////////////////
// //
// 2.20.02 mcn - Created. //
// //
//////////////////////////////////////////////////////////////////////////////
#include "hsConfig.h"
#include "hsWindows.h"
#include "hsTypes.h"
#include "plInputInterfaceMgr.h"
#include "plInputInterface.h"
#include "plInputDevice.h" // For mouse device stuff
#include "pnInputCore/plKeyMap.h"
#include "plMessage/plInputEventMsg.h"
#include "plMessage/plInputIfaceMgrMsg.h"
#include "pnMessage/plClientMsg.h"
#include "pnMessage/plTimeMsg.h"
#include "pnMessage/plCmdIfaceModMsg.h"
#include "pnMessage/plPlayerPageMsg.h"
#include "pnKeyedObject/plKey.h"
#include "pnKeyedObject/plFixedKey.h"
#include "pnNetCommon/plNetApp.h"
#include "plNetClient/plNetClientMgr.h"
#include "hsResMgr.h"
#include "plgDispatch.h"
#include "plProfile.h"
#include "plResMgr/plLocalization.h"
plProfile_CreateTimer("Input", "Update", Input);
//// plCtrlCmd ///////////////////////////////////////////////////////////////
void plCtrlCmd::Write(hsStream* stream, hsResMgr* mgr)
{
stream->WriteSwap32( fControlCode );
stream->WriteBool( fControlActivated );
fPt.Write(stream);
// write cmd/string
plMsgCStringHelper::Poke(fCmd, stream);
}
void plCtrlCmd::Read(hsStream* stream, hsResMgr* mgr)
{
fControlCode = (ControlEventCode)stream->ReadSwap32();
fControlActivated = stream->ReadBool();
fPt.Read(stream);
// read cmd/string
plMsgCStringHelper::Peek(fCmd, stream);
}
//// plDefaultKeyCatcher /////////////////////////////////////////////////////
plDefaultKeyCatcher::~plDefaultKeyCatcher()
{
if( plInputInterfaceMgr::GetInstance() != nil )
plInputInterfaceMgr::GetInstance()->SetDefaultKeyCatcher( nil );
}
//// Statics /////////////////////////////////////////////////////////////////
plInputInterfaceMgr *plInputInterfaceMgr::fInstance = nil;
//// Constructor/Destructor //////////////////////////////////////////////////
plInputInterfaceMgr::plInputInterfaceMgr()
{
fClickEnabled = false;
fCurrentCursor = -1;
fCursorOpacity = 1.f;
fForceCursorHidden = false;
fForceCursorHiddenCount = 0;
InitDefaultKeyMap();
#if 0
// make sure we don't miss control keys on remote players
SetSynchFlagsBit(plSynchedObject::kSendReliably);
#endif
hsAssert( fInstance == nil, "Attempting to create two input interface managers!" );
fInstance = this;
fCurrentFocus = nil;
fDefaultCatcher = nil;
}
plInputInterfaceMgr::~plInputInterfaceMgr()
{
Shutdown();
fInstance = nil;
}
//// Init ////////////////////////////////////////////////////////////////////
#include "plAvatarInputInterface.h"
#include "plSceneInputInterface.h"
#include "plDebugInputInterface.h"
#include "plTelescopeInputInterface.h"
void plInputInterfaceMgr::Init( void )
{
RegisterAs( kInputInterfaceMgr_KEY );
plgDispatch::Dispatch()->RegisterForType( plInputIfaceMgrMsg::Index(), GetKey() );
plgDispatch::Dispatch()->RegisterForType( plInputEventMsg::Index(), GetKey() );
plgDispatch::Dispatch()->RegisterForType( plEvalMsg::Index(), GetKey() );
plgDispatch::Dispatch()->RegisterForExactType( plPlayerPageMsg::Index(), GetKey() );
plgDispatch::Dispatch()->RegisterForExactType( plCmdIfaceModMsg::Index(), GetKey() );
plgDispatch::Dispatch()->RegisterForExactType( plClientMsg::Index(), GetKey() );
/// Hacks (?) for now
plAvatarInputInterface *avatar = TRACKED_NEW plAvatarInputInterface();
IAddInterface( avatar );
hsRefCnt_SafeUnRef( avatar );
plSceneInputInterface *scene = TRACKED_NEW plSceneInputInterface();
IAddInterface( scene );
hsRefCnt_SafeUnRef( scene );
plDebugInputInterface *camDrive = TRACKED_NEW plDebugInputInterface();
IAddInterface( camDrive );
hsRefCnt_SafeUnRef( camDrive );
}
//// Shutdown ////////////////////////////////////////////////////////////////
void plInputInterfaceMgr::Shutdown( void )
{
int i;
// WriteKeyMap();
for( i = 0; i < fInterfaces.GetCount(); i++ )
{
fInterfaces[ i ]->Shutdown();
hsRefCnt_SafeUnRef( fInterfaces[ i ] );
}
fInterfaces.Reset();
for( i = 0; i < fMessageQueue.GetCount(); i++ )
delete fMessageQueue[ i ];
fMessageQueue.Reset();
plgDispatch::Dispatch()->UnRegisterForType( plInputIfaceMgrMsg::Index(), GetKey() );
plgDispatch::Dispatch()->UnRegisterForType( plInputEventMsg::Index(), GetKey() );
plgDispatch::Dispatch()->UnRegisterForType( plEvalMsg::Index(), GetKey() );
plgDispatch::Dispatch()->UnRegisterForExactType( plPlayerPageMsg::Index(), GetKey() );
plgDispatch::Dispatch()->UnRegisterForExactType( plCmdIfaceModMsg::Index(), GetKey() );
plgDispatch::Dispatch()->UnRegisterForExactType( plClientMsg::Index(), GetKey() );
UnRegisterAs( kInputInterfaceMgr_KEY );
}
//// IAdd/RemoveInterface ////////////////////////////////////////////////////
// Each interface has a "priority level", i.e. where in the list it should be.
// Doing it this way allows us to keep the manager unaware of the number or
// types of interfaces.
void plInputInterfaceMgr::IAddInterface( plInputInterface *iface )
{
int i;
for( i = 0; i < fInterfaces.GetCount(); i++ )
{
if( fInterfaces[ i ]->GetPriorityLevel() < iface->GetPriorityLevel() )
break;
}
fInterfaces.Insert( i, iface );
hsRefCnt_SafeRef( iface );
iface->Init( this );
iface->ISetMessageQueue( &fMessageQueue );
}
void plInputInterfaceMgr::IRemoveInterface( plInputInterface *iface )
{
int idx = fInterfaces.Find( iface );
if( idx != fInterfaces.kMissingIndex )
{
fInterfaces[ idx ]->Shutdown();
hsRefCnt_SafeUnRef( fInterfaces[ idx ] );
fInterfaces.Remove( idx );
}
}
/// reset clickable state //////////////////////////////////////////////////////////////////////////
void plInputInterfaceMgr::ResetClickableState()
{
// look for the scene input interface
for(int i = 0; i < fInterfaces.GetCount(); i++ )
fInterfaces[i]->ResetClickableState();
}
//// IUpdateCursor ///////////////////////////////////////////////////////////
void plInputInterfaceMgr::IUpdateCursor( Int32 newCursor )
{
char* mouseCursorResID;
fCurrentCursor = newCursor;
if( fCurrentCursor == plInputInterface::kCursorHidden )
plMouseDevice::HideCursor();
else
{
plMouseDevice::ShowCursor();
switch( fCurrentCursor )
{
case plInputInterface::kCursorUp: mouseCursorResID = CURSOR_UP; break;
case plInputInterface::kCursorLeft: mouseCursorResID = CURSOR_LEFT; break;
case plInputInterface::kCursorRight: mouseCursorResID = CURSOR_RIGHT; break;
case plInputInterface::kCursorDown: mouseCursorResID = CURSOR_DOWN; break;
case plInputInterface::kCursorPoised: mouseCursorResID = CURSOR_POISED; break;
case plInputInterface::kCursorClicked: mouseCursorResID = CURSOR_CLICKED; break;
case plInputInterface::kCursorUnClicked: mouseCursorResID = CURSOR_POISED; break;
case plInputInterface::kCursorOpen: mouseCursorResID = CURSOR_OPEN; break;
case plInputInterface::kCursorGrab: mouseCursorResID = CURSOR_GRAB; break;
case plInputInterface::kCursorArrow: mouseCursorResID = CURSOR_ARROW; break;
case plInputInterface::kCursor4WayDraggable: mouseCursorResID = CURSOR_4WAY_OPEN; break;
case plInputInterface::kCursor4WayDragging: mouseCursorResID = CURSOR_4WAY_CLOSED; break;
case plInputInterface::kCursorUpDownDraggable: mouseCursorResID = CURSOR_UPDOWN_OPEN; break;
case plInputInterface::kCursorUpDownDragging: mouseCursorResID = CURSOR_UPDOWN_CLOSED; break;
case plInputInterface::kCursorLeftRightDraggable: mouseCursorResID = CURSOR_LEFTRIGHT_OPEN; break;
case plInputInterface::kCursorLeftRightDragging: mouseCursorResID = CURSOR_LEFTRIGHT_CLOSED; break;
case plInputInterface::kCursorOfferBook: mouseCursorResID = CURSOR_OFFER_BOOK; break;
case plInputInterface::kCursorOfferBookHilite: mouseCursorResID = CURSOR_OFFER_BOOK_HI; break;
case plInputInterface::kCursorOfferBookClicked: mouseCursorResID = CURSOR_OFFER_BOOK_CLICKED; break;
case plInputInterface::kCursorClickDisabled: mouseCursorResID = CURSOR_CLICK_DISABLED; break;
case plInputInterface::kCursorHand: mouseCursorResID = CURSOR_HAND; break;
case plInputInterface::kCursorUpward: mouseCursorResID = CURSOR_UPWARD; break;
default: mouseCursorResID = CURSOR_OPEN; break;
}
plMouseDevice::NewCursor( mouseCursorResID );
}
}
//// IEval ///////////////////////////////////////////////////////////////////
// Inherited from plSingleModifier, gets called once per IUpdate() loop.
hsBool plInputInterfaceMgr::IEval( double secs, hsScalar del, UInt32 dirty )
{
const char *inputEval = "Eval";
plProfile_BeginLap(Input, inputEval);
int i;
// Let all our layers eval
for( i = 0; i < fInterfaces.GetCount(); i++ )
fInterfaces[ i ]->IEval( secs, del, dirty );
// Handle our message queue now
for( i = 0; i < fMessageQueue.Count(); i++ )
{
// Can its layer handle it?
if( !fMessageQueue[ i ]->GetSource()->IHandleCtrlCmd( fMessageQueue[ i ] ) )
{
// Nope, just dispatch it like normal
plControlEventMsg* pMsg = TRACKED_NEW plControlEventMsg;
for (int j = 0; j < fReceivers.Count(); j++)
pMsg->AddReceiver( fReceivers[ j ] );
pMsg->SetControlActivated( fMessageQueue[i]->fControlActivated );
pMsg->SetControlCode( fMessageQueue[i]->fControlCode );
pMsg->SetControlPct(fMessageQueue[i]->fPct);
pMsg->SetTurnToPt( fMessageQueue[i]->fPt );
pMsg->SetCmdString(fMessageQueue[i]->GetCmdString());
pMsg->SetSender( GetKey() );
plgDispatch::MsgSend( pMsg );
///////////////////////////////////////////////////////
// send same msg over network to players
///////////////////////////////////////////////////////
if (fMessageQueue[i]->fNetPropagateToPlayers)
{
pMsg = TRACKED_NEW plControlEventMsg;
for (int j = 0; j < fReceivers.Count(); j++)
if (fReceivers[j] == plNetClientApp::GetInstance()->GetLocalPlayerKey())
pMsg->AddReceiver( fReceivers[j] );
if (pMsg->GetNumReceivers())
{
pMsg->SetControlActivated( fMessageQueue[i]->fControlActivated );
pMsg->SetControlCode( fMessageQueue[i]->fControlCode );
pMsg->SetControlPct(fMessageQueue[i]->fPct);
pMsg->SetTurnToPt( fMessageQueue[i]->fPt );
pMsg->SetCmdString(fMessageQueue[i]->GetCmdString());
pMsg->SetSender( GetKey() );
pMsg->SetBCastFlag(plMessage::kNetPropagate | plMessage::kPropagateToModifiers |
plMessage::kNetUseRelevanceRegions); // bcast only to other players who care about the region I'm in
pMsg->SetBCastFlag(plMessage::kLocalPropagate, false);
plgDispatch::MsgSend( pMsg );
}
else
delete pMsg;
}
}
}
// Clear the message queue
for( i = 0; i < fMessageQueue.Count(); i++ )
delete fMessageQueue[ i ];
fMessageQueue.SetCount( 0 );
plProfile_EndLap(Input, inputEval);
return true;
}
void plInputInterfaceMgr::ForceCursorHidden( hsBool requestedState )
{
if ( requestedState )
{
fForceCursorHiddenCount++;
fForceCursorHidden = requestedState;
}
else
{
fForceCursorHiddenCount--;
// this happens way too often to leave in
// hsAssert(fForceCursorHiddenCount>=0,"ForceCursorHidded: unhiding more times than hidden" );
#define OnlyHideCursorOnLast
#ifdef OnlyHideCursorOnLast
// is this is the last person... then really unforce hidding the mouse cursor
if ( fForceCursorHiddenCount <= 0 )
{
#endif //OnlyHideCursorOnLast
fForceCursorHidden = requestedState;
fForceCursorHiddenCount = 0;
#ifdef OnlyHideCursorOnLast
}
#endif //OnlyHideCursorOnLast
}
}
hsBool plInputInterfaceMgr::ICheckCursor(plInputInterface *iFace)
{
if( iFace->IsEnabled() && iFace->HasInterestingCursorID() )
{
if( iFace->GetCurrentCursorID() != fCurrentCursor )
IUpdateCursor( iFace->GetCurrentCursorID() );
if( iFace->GetCurrentCursorOpacity() != fCursorOpacity )
{
fCursorOpacity = iFace->GetCurrentCursorOpacity();
plMouseDevice::SetCursorOpacity( fCursorOpacity );
}
return true;
}
return false;
}
//// MsgReceive //////////////////////////////////////////////////////////////
hsBool plInputInterfaceMgr::MsgReceive( plMessage *msg )
{
int i;
plEvalMsg *pEvalMsg = plEvalMsg::ConvertNoRef( msg );
if( pEvalMsg )
{
IEval( pEvalMsg->GetTimeStamp(), pEvalMsg->DelSeconds(), false );
return true;
}
plInputEventMsg *ieMsg = plInputEventMsg::ConvertNoRef( msg );
if( ieMsg != nil )
{
const char *inputIEM = "InputEventMsg";
plProfile_BeginLap(Input, inputIEM);
hsBool handled = false;
UInt32 missedInputStartIdx = 0;
plInputInterface *oldCurrentFocus = fCurrentFocus;
// Current focus (if there is one) gets first crack
if( fCurrentFocus )
{
if( fCurrentFocus->IsEnabled() )
{
handled = (fCurrentFocus->ProcessKeyBindings(ieMsg) || fCurrentFocus->InterpretInputEvent(ieMsg));
}
}
if (!handled)
{
// Walk our stack
for( i = 0; i < fInterfaces.GetCount(); i++ )
{
if( fInterfaces[ i ]->IsEnabled() && fInterfaces[ i ] != oldCurrentFocus)
{
// Try the key bindings first (common for all layers)
if( fInterfaces[ i ]->ProcessKeyBindings( ieMsg ) || fInterfaces[ i ]->InterpretInputEvent( ieMsg ))
{
handled = true;
break;
}
}
}
if( !handled )
{
// Fell all the way through the stack...must've been a very uninteresting message...
if( plKeyEventMsg::ConvertNoRef( ieMsg ) && fDefaultCatcher != nil )
{
// But somebody loves those keys :)
fDefaultCatcher->HandleKeyEvent( plKeyEventMsg::ConvertNoRef( ieMsg ) );
}
}
missedInputStartIdx = i + 1;
}
// Notify the rest of the interfaces in the stack that they missed the event ("lost focus", as it were)
for (i = missedInputStartIdx; i < fInterfaces.GetCount(); i++)
if (fInterfaces[i] != oldCurrentFocus)
fInterfaces[i]->MissedInputEvent(ieMsg);
// Now we re-walk to see who's the new interested party. Note that we have to re-walk
// because a key down may have changed some layer's interest in the cursor
if( !fForceCursorHidden )
{
hsBool cursorHandled = false;
if (fCurrentFocus)
cursorHandled = ICheckCursor(fCurrentFocus);
if (!cursorHandled)
{
for( i = 0; i < fInterfaces.GetCount(); i++ )
{
if (ICheckCursor(fInterfaces[i]))
{
cursorHandled = true;
break;
}
}
if (!cursorHandled)
{
// NOBODY is interested in the mouse, so set to our default cursor
IUpdateCursor( plInputInterface::kCursorUp );
fCursorOpacity = 1.f;
plMouseDevice::SetCursorOpacity( fCursorOpacity );
}
}
}
else
{
// Special debug flag to force the cursor to be hidden
if( fCursorOpacity != 0.f )
{
fCursorOpacity = 0.f;
plMouseDevice::SetCursorOpacity( fCursorOpacity );
}
}
plProfile_EndLap(Input, inputIEM);
return true;
}
plInputIfaceMgrMsg *mgrMsg = plInputIfaceMgrMsg::ConvertNoRef( msg );
if( mgrMsg != nil )
{
if( mgrMsg->GetCommand() == plInputIfaceMgrMsg::kAddInterface )
{
IAddInterface( mgrMsg->GetIFace() );
return true;
}
else if( mgrMsg->GetCommand() == plInputIfaceMgrMsg::kRemoveInterface )
{
IRemoveInterface( mgrMsg->GetIFace() );
return true;
}
else if( mgrMsg->GetCommand() == plInputIfaceMgrMsg::kEnableClickables )
{
fClickEnabled = true;
}
else if( mgrMsg->GetCommand() == plInputIfaceMgrMsg::kDisableClickables )
{
fClickEnabled = false;
}
}
plPlayerPageMsg *pPMsg = plPlayerPageMsg::ConvertNoRef( msg );
if( pPMsg != nil && !pPMsg->fUnload)
{
if( pPMsg->fPlayer == plNetClientMgr::GetInstance()->GetLocalPlayerKey() )
fReceivers.Append( pPMsg->fPlayer );
else
{
int idx = fReceivers.Find( pPMsg->fPlayer );
if( idx != fReceivers.kMissingIndex )
fReceivers.Remove( idx );
}
}
plCmdIfaceModMsg *pCMsg = plCmdIfaceModMsg::ConvertNoRef( msg );
if( pCMsg )
{
if( pCMsg->Cmd( plCmdIfaceModMsg::kAdd ) )
{
for( int i = 0; i < fReceivers.Count(); i++ )
{
if( fReceivers[i] == pCMsg->GetSender() )
return true;
}
fReceivers.Append( pCMsg->GetSender() );
return true;
}
else
if( pCMsg->Cmd( plCmdIfaceModMsg::kRemove ) )
{
for( int i = 0; i < fReceivers.Count(); i++ )
{
if( fReceivers[ i ] == pCMsg->GetSender() )
{
fReceivers.Remove( i );
break;
}
}
return true;
}
}
plClientMsg *cMsg = plClientMsg::ConvertNoRef(msg);
if (cMsg && cMsg->GetClientMsgFlag() == plClientMsg::kInitComplete)
{
// Backwards compatability hack:
// We've loaded in the user prefs for input. If they bind movement
// to an arrow, or numpad, and the other binding is free, automatically
// bind the other one.
plKeyMap *map = plAvatarInputInterface::GetInstance()->fControlMap;
map->HandleAutoDualBinding(KEY_UP, KEY_NUMPAD8);
map->HandleAutoDualBinding(KEY_DOWN, KEY_NUMPAD2);
map->HandleAutoDualBinding(KEY_LEFT, KEY_NUMPAD4);
map->HandleAutoDualBinding(KEY_RIGHT, KEY_NUMPAD6);
plgDispatch::Dispatch()->UnRegisterForExactType( plClientMsg::Index(), GetKey() );
return true;
}
// Wasn't one we want. Was it one that one of our interfaces wanted?
for( i = 0; i < fInterfaces.GetCount(); i++ )
{
if( fInterfaces[ i ]->MsgReceive( msg ) )
return true;
}
// Nothing, pass on...
return plSingleModifier::MsgReceive( msg );
}
//// Read/Write //////////////////////////////////////////////////////////////
void plInputInterfaceMgr::Read( hsStream* s, hsResMgr* mgr )
{
plSingleModifier::Read( s, mgr );
}
void plInputInterfaceMgr::Write( hsStream* s, hsResMgr* mgr )
{
plSingleModifier::Write( s, mgr );
}
//////////////////////////////////////////////////////////////////////////////
//// Key Maps ////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//// IGetRoutedKeyMap ////////////////////////////////////////////////////////
plKeyMap *plInputInterfaceMgr::IGetRoutedKeyMap( ControlEventCode code )
{
int i;
for( i = 0; i < fInterfaces.GetCount(); i++ )
{
if( fInterfaces[ i ]->IOwnsControlCode( code ) )
return fInterfaces[ i ]->fControlMap;
}
return nil;
}
//// IUnbind /////////////////////////////////////////////////////////////////
// Unmaps any mappings with the given key. This prevents you from mapping
// a single key to multiple commands. Currently inactive because some people
// think it's a good idea to be able to do that.
#define ALLOW_MULTIPLE_CMDS_PER_KEY 1
void plInputInterfaceMgr::IUnbind( const plKeyCombo &key )
{
#if !(ALLOW_MULTIPLE_CMDS_PER_KEY)
int i;
for( i = 0; i < fInterfaces.GetCount(); i++ )
fInterfaces[ i ]->fControlMap->UnmapKey( key );
#endif
}
void plInputInterfaceMgr::ClearAllKeyMaps()
{
for (int i = 0; i < fInterfaces.Count(); i++)
fInterfaces[i]->ClearKeyMap();
}
//// Binding Routers /////////////////////////////////////////////////////////
void plInputInterfaceMgr::BindAction( const plKeyCombo &key, ControlEventCode code )
{
plKeyMap *map = IGetRoutedKeyMap( code );
if( map != nil )
{
// Use default prefs
map->EnsureKeysClear( key, plKeyCombo::kUnmapped );
map->BindKey( key, code );
}
RefreshInterfaceKeyMaps();
}
void plInputInterfaceMgr::BindAction( const plKeyCombo &key1, const plKeyCombo &key2,
ControlEventCode code )
{
plKeyMap *map = IGetRoutedKeyMap( code );
if( map != nil )
{
// Force the bindings to each key, since the user specified both
map->EnsureKeysClear( key1, key2 );
map->BindKey( key1, code, plKeyMap::kFirstAlways );
map->BindKey( key2, code, plKeyMap::kSecondAlways );
}
RefreshInterfaceKeyMaps();
}
const plKeyBinding* plInputInterfaceMgr::FindBinding( ControlEventCode code )\
{
plKeyMap *map = IGetRoutedKeyMap( code );
if( map != nil )
{
// Use default prefs
return map->FindBinding(code);
}
return nil;
}
void plInputInterfaceMgr::BindConsoleCmd( const plKeyCombo &key, const char *cmd, plKeyMap::BindPref pref /*= kNoPreference*/ )
{
// not sure why this is not for external...since its done thru the different interfaces?
//#ifdef PLASMA_EXTERNAL_RELEASE
// return;
//#endif
plKeyMap *map = IGetRoutedKeyMap( B_CONTROL_CONSOLE_COMMAND );
if( map != nil )
{
// Default prefs again
map->EnsureKeysClear( key, plKeyCombo::kUnmapped );
// BindKeyToConsoleCmd only works if the console command in question has already been assigned
// to this map. Oftentimes, this isn't true when the user is binding console commands, so go ahead
// and add the command to this map. If it's already added, this call will just quietly fail and
// we're ok to continue
map->AddConsoleCommand( cmd );
map->BindKeyToConsoleCmd( key, cmd, pref );
}
RefreshInterfaceKeyMaps();
}
const plKeyBinding* plInputInterfaceMgr::FindBindingByConsoleCmd( const char *cmd )
{
plKeyMap *map = IGetRoutedKeyMap( B_CONTROL_CONSOLE_COMMAND );
if( map != nil )
{
return map->FindConsoleBinding(cmd);
}
return nil;
}
//// InitDefaultKeyMap ///////////////////////////////////////////////////////
void plInputInterfaceMgr::InitDefaultKeyMap( void )
{
int i;
for( i = 0; i < fInterfaces.GetCount(); i++ )
fInterfaces[ i ]->RestoreDefaultKeyMappings();
RefreshInterfaceKeyMaps();
}
//// RefreshInterfaceKeyMaps /////////////////////////////////////////////////
void plInputInterfaceMgr::RefreshInterfaceKeyMaps( void )
{
int i;
for( i = 0; i < fInterfaces.GetCount(); i++ )
fInterfaces[ i ]->RefreshKeyMap();
}
//// WriteKeyMap /////////////////////////////////////////////////////////////
void plInputInterfaceMgr::WriteKeyMap( void )
{
#ifdef PLASMA_EXTERNAL_RELEASE
return;
#endif
FILE* gKeyFile = 0;
gKeyFile = hsFopen( "init\\keyboard.fni", "wt" );
if (gKeyFile)
{
fprintf(gKeyFile, "# To remap a control to a new key,\n");
fprintf(gKeyFile, "# just change the key listed. \n");
fprintf(gKeyFile, "# The list of available commands is at the bottom.\n");
fprintf(gKeyFile, "# For keys with multi-character names \n");
fprintf(gKeyFile, "# like F1 or Backspace, be sure to enter \n");
fprintf(gKeyFile, "# the key name as it appears in the list \n");
fprintf(gKeyFile, "# at the end of the file. \n");
fprintf(gKeyFile, "#\n");
fprintf(gKeyFile, "# Be sure to put quotes around the actual command\n");
fprintf(gKeyFile, "#\n");
fprintf(gKeyFile, "# To add modifiers to a key mapping (like ctrl or shift)\n");
fprintf(gKeyFile, "# append _C for ctrl or _S for Shift (or both)\n");
fprintf(gKeyFile, "# to the actual name of the key.\n");
fprintf(gKeyFile, "#\n");
fprintf(gKeyFile, "# For example, to map Control-Shift-W to Walk Forward\n");
fprintf(gKeyFile, "# your key mapping entry would look like this:\n");
fprintf(gKeyFile, "# Keyboard.BindAction \tW_C_S \t\"Walk Forward\"\n");
fprintf(gKeyFile, "# This also works for console command bindings\n");
fprintf(gKeyFile, "# Keyboard.BindAction \t\tKey1\tKey2\t\t\t\tControl\n");
fprintf(gKeyFile, "#\n");
// fprintf(gKeyFile, "Keyboard.ClearBindings\n");
int i;
for( i = 0; i < fInterfaces.GetCount(); i++ )
IWriteNonConsoleCmdKeys( fInterfaces[ i ]->fControlMap, gKeyFile );
fprintf(gKeyFile, "#\n");
fprintf(gKeyFile, "# Console command bindings:\n");
fprintf(gKeyFile, "#\n");
fprintf(gKeyFile, "# Keyboard.BindConsoleCmd \tKey\t\t\tCommand\n");
fprintf(gKeyFile, "#\n");
for( i = 0; i < fInterfaces.GetCount(); i++ )
IWriteConsoleCmdKeys( fInterfaces[ i ]->fControlMap, gKeyFile );
fprintf(gKeyFile, "#\n");
fprintf(gKeyFile, "# Available game commands:\n");
fprintf(gKeyFile, "#\n");
for( int j = 0; plKeyMap::fCmdConvert[ j ].fCode != END_CONTROLS; j++ )
{
if( stricmp( plKeyMap::fCmdConvert[ j ].fDesc, "Run Modifier" ) == 0)
continue;
fprintf( gKeyFile, "# %s\n", plKeyMap::fCmdConvert[ j ].fDesc );
}
fprintf(gKeyFile, "#\n");
fprintf(gKeyFile, "# Key name list (for a-z or 0-9 just use the character)\n");
fprintf(gKeyFile, "#\n");
Win32keyConvert* keyConvert = &plKeyMap::fKeyConversionEnglish[0];
switch (plLocalization::GetLanguage())
{
case plLocalization::kFrench:
keyConvert = &plKeyMap::fKeyConversionFrench[0];
break;
case plLocalization::kGerman:
keyConvert = &plKeyMap::fKeyConversionGerman[0];
break;
//case plLocalization::kSpanish:
// keyConvert = &plKeyMap::fKeyConversionSpanish[0];
// break;
//case plLocalization::kItalian:
// keyConvert = &plKeyMap::fKeyConversionItalian[0];
// break;
// default is English
}
for (i = 0; keyConvert[i].fVKey != 0xffffffff; i++)
{
// if (stricmp(fKeyMap->fKeyConversion[i].fKeyName, "Shift") == 0)
// continue;
fprintf(gKeyFile, "# %s\n", keyConvert[i].fKeyName);
}
fclose(gKeyFile);
}
}
void plInputInterfaceMgr::SetCurrentFocus(plInputInterface *focus)
{
fCurrentFocus = focus;
}
void plInputInterfaceMgr::ReleaseCurrentFocus(plInputInterface *focus)
{
if (fCurrentFocus == focus)
fCurrentFocus = nil;
}
//// IKeyComboToString ///////////////////////////////////////////////////////
// Uses static string, so don't call twice and expect the first result to
// be still valid!
const char *plInputInterfaceMgr::IKeyComboToString( const plKeyCombo &combo )
{
static char str[ 64 ];
bool unmapped = false;
if( combo == plKeyCombo::kUnmapped )
sprintf( str, "(unmapped)" );
else
{
const char *c = plKeyMap::ConvertVKeyToChar( combo.fKey );
if( c != nil )
strncpy( str, c, sizeof( str ) );
else
{
if( isalnum( combo.fKey ) )
{
str[ 0 ] = (char)combo.fKey;
str[ 1 ] = 0;
}
else
{
strcpy( str, "(unmapped)" );
unmapped = true;
}
}
if( !unmapped )
{
if( combo.fFlags & plKeyCombo::kCtrl )
strcat( str, "_C" );
if( combo.fFlags & plKeyCombo::kShift )
strcat( str, "_S" );
}
}
return str;
}
//// IWriteNonConsoleCmdKeys /////////////////////////////////////////////////
void plInputInterfaceMgr::IWriteNonConsoleCmdKeys( plKeyMap *keyMap, FILE *keyFile )
{
int i;
for( i = 0; i < keyMap->GetNumBindings(); i++ )
{
const plKeyBinding &binding = keyMap->GetBinding( i );
if( binding.GetCode() == B_CONTROL_CONSOLE_COMMAND )
continue;
char key1[ 64 ];
strcpy( key1, IKeyComboToString( binding.GetKey1() ) );
const char *key2 = IKeyComboToString( binding.GetKey2() );
const char *desc = plInputMap::ConvertControlCodeToString( binding.GetCode() );
fprintf( keyFile, "Keyboard.BindAction \t\t%s\t%s\t\t\t\t\"%s\"\n", key1, key2, desc );
}
}
//// IWriteConsoleCmdKeys ////////////////////////////////////////////////////
void plInputInterfaceMgr::IWriteConsoleCmdKeys( plKeyMap *keyMap, FILE *keyFile )
{
int i;
for( i = 0; i < keyMap->GetNumBindings(); i++ )
{
const plKeyBinding &binding = keyMap->GetBinding( i );
if( binding.GetCode() != B_CONTROL_CONSOLE_COMMAND )
continue;
// Our bindConsoleCmd console command (echo echo) only takes 1 key combo, not 2,
// so as not to confuse people. Or something. So if we got two bindings, we print
// 2 commands, which is perfectly valid
// if( binding.GetKey1() != plKeyCombo::kUnmapped )
// {
const char *key = IKeyComboToString( binding.GetKey1() );
fprintf( keyFile, "Keyboard.BindConsoleCmd\t%s\t\t\t\"%s\"\n", key, binding.GetExtendedString() );
// }
if( binding.GetKey2() != plKeyCombo::kUnmapped )
{
const char *key = IKeyComboToString( binding.GetKey2() );
fprintf( keyFile, "Keyboard.BindConsoleCmd\t%s\t\t\t\"%s\"\n", key, binding.GetExtendedString() );
}
}
}