/////////////////////////////////////////////////////////////////////////////
// Name:        No names yet.
// Purpose:     Contrib. demo
// Author:      Aleksandras Gluchovas
// Modified by:
// Created:     2000/02/10
// RCS-ID:      $Id: keybinder.cpp,v 1.17 2001/04/20 10:17:11 georgetasker Exp $
// Copyright:   (c) Aleksandras Gluchovas
// Licence:     wxWindows licence
/////////////////////////////////////////////////////////////////////////////

#ifdef __GNUG__
    #pragma implementation "minimal.cpp"
    #pragma interface "minimal.cpp"
#endif

// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
    #pragma hdrstop
#endif

#ifndef WX_PRECOMP
    #include "wx/wx.h"
#endif

#include "wx/pmf/keybinder.h"
#include "wx/debug.h"

// sorry, 3 .cpp's and .h's of AWT-layouts contrib. needed to compile this .cpp!

#include "wx/awtlay/awt_emul.h"
#include "wx/awtlay/gridbaglayout.h"
#include "wx/awtlay/flowlayout.h"

#define LOG(x) wxLogMessage( x );

/***** Implementation for class wxCommandInfo *****/

wxCommandInfo::wxCommandInfo()

	: mId(-1),
	  mIsCheckable( FALSE ),
	  mpUserData( NULL ),
	  mpParent( NULL )
{
    size_t i;
	for( i = 0; i != wxMAX_KEYS_PER_CMD; ++i ) 
        mKeyCodes[i] = -1;
}

wxCommandInfo::wxCommandInfo( int id, const wxString& title, 
							  const wxString& helpString,
							  const wxString& keyStroke,
							  bool isCheckable,
							  bool isChecked )
	: mId( id ), 
	  mTitle( title ), 
	  mHelpString( helpString ),
	  mIsCheckable( isCheckable ),
	  mIsChecked( isChecked ),
	  mTmpStroke( keyStroke ),
	  mpUserData( NULL ),
	  mpParent( NULL )
{
    size_t i;
	for( i = 0; i != wxMAX_KEYS_PER_CMD; ++i )
        mKeyCodes[i] = -1;
}

wxCommandInfo::~wxCommandInfo()
{
	if ( mpUserData )

		delete mpUserData;
}

bool wxCommandInfo::AddKey( int keyCode, int modifiers )
{
	size_t i = GetKeyCount();

	if ( i >= wxMAX_KEYS_PER_CMD ) return FALSE; // no more space for more keys (in fixed-size array)

	mKeyCodes[i]  = keyCode;
	mModifiers[i] = modifiers;

	return TRUE;
}

bool wxCommandInfo::KeyPresent( int keyCode, int modifiers )
{
	size_t cnt = GetKeyCount();

    size_t i;
	for( i = 0; i != GetKeyCount(); ++i )
    {
		if ( mKeyCodes[i] == keyCode &&
			 mModifiers[i] == modifiers )

			 return TRUE;
    }
	return FALSE;
}

void wxCommandInfo::Prepare()
{
	if ( mTmpStroke.Length() > 0 ) // if we "own" static initializtion first keystroke
	{
		int code = 0, mod = 0;

		size_t i = 0, start = 0, len = mTmpStroke.Length();

		for(;;)
		{
			// add all '|'-separated keystrokes

			start = i;

			while( i < len && mTmpStroke[i] != '|' )
                ++i;

			wxString stroke( mTmpStroke, start, i - start );

			ParseKeyStrokeString( stroke, &code, &mod );

			AddKey( code, mod );
			
			++i; // skip '|';

			if ( i >= len ) 
                break;
		}

		mTmpStroke = "";
	}

	wxCommandInfoListT* pLst = GetSubcommands();

	if ( pLst )
    {
        size_t i;
		for( i = 0; i != pLst->Count(); ++i )

			(*pLst)[i]->Prepare();
    }
}

bool wxCommandInfo::RemoveKey( size_t keyNo )
{
	size_t cnt = GetKeyCount();

	if ( keyNo >= cnt )
        return FALSE;

	mKeyCodes[keyNo] = -1;

    size_t i;
	for( i = keyNo+1; i < cnt; ++i )

		mKeyCodes[i-1] = mKeyCodes[i];

	--cnt;

    size_t n;
	for( n = cnt; n != wxMAX_KEYS_PER_CMD; ++n )
        mKeyCodes[n] = -1;

	return TRUE;
}


size_t wxCommandInfo::GetKeyCount()
{
	size_t i;
	for( i = 0 ; i != wxMAX_KEYS_PER_CMD; ++i )
    {
		if ( mKeyCodes[i] == -1 )
            return i;
    }

	return i;
}

int   wxCommandInfo::GetId()                   { return mId; }
const wxString& wxCommandInfo::GetTitle()      { return mTitle; }
const wxString& wxCommandInfo::GetHelpString() { return mHelpString; }

void wxCommandInfo::SetTitle( const wxString& title ) { mTitle = title; }
void wxCommandInfo::SetHelpString( const wxString& helpString ) { mHelpString = helpString; }
void wxCommandInfo::SetId( int id ) { mId = id; }


int* wxCommandInfo::GetKeyCodes()             { return mKeyCodes; }
int* wxCommandInfo::GetKeyModifiers()         { return mModifiers; }

wxString wxCommandInfo::GetStrippedTitle()
{
	return wxCommandInfo::StripControlChars( mTitle );
}

void wxCommandInfo::SetCheckable( bool checkable )
{
	mIsCheckable = checkable;
}


void wxCommandInfo::SetChecked( bool checked )
{
	mIsChecked = checked;
}

bool wxCommandInfo::IsChecked() { return mIsChecked; }

void wxCommandInfo::SetBitmap( const wxBitmap& bmp )
{
	mBitmap = bmp;
}

const wxBitmap& wxCommandInfo::GetBitmap()
{
	return mBitmap;
}
										 
bool wxCommandInfo::IsCheckable()
{
	return mIsCheckable;
}

void wxCommandInfo::SetUserData( wxObject* pData )
{
	mpUserData = pData;
}

wxObject* wxCommandInfo::GetUserData()
{
	return mpUserData;
}

void wxCommandInfo::ResetUserData()
{
	mpUserData = NULL;

	if ( HasSubcommands() )
	{
		wxCommandInfoListT& lst = *GetSubcommands();

        size_t i;
		for( i = 0; i != lst.Count(); ++i )
        {
			lst[i]->ResetUserData();
        }
	}
}

wxCommandInfoListT* wxCommandInfo::GetSubcommands()
{
	return NULL;
}

bool wxCommandInfo::HasSubcommands()
{
	wxCommandInfoListT* pLst = GetSubcommands();

	return pLst && pLst->Count();
}

void wxCommandInfo::AddSubcommand( wxCommandInfo* pInfo )
{
	wxFAIL_MSG( "AddSubcommand() should be only invoked on instances of wxCommandGroup!" );
}

wxCommandInfo* wxCommandInfo::FindCommandById( int id )
{
	if ( id == mId )
        return this;

	wxCommandInfoListT* pLst = GetSubcommands();

	if ( pLst )
    {
        size_t i;
		for( i = 0; i != pLst->Count(); ++i )
		{
			wxCommandInfo* pFound = (*pLst)[i]->FindCommandById( id );

			if ( pFound ) 
                return pFound;
		}
    }
	return NULL;			
}

wxCommandInfo* wxCommandInfo::Clone()
{
	return new wxCommandInfo( *this );
}

void wxCommandInfo::AssignInfoTo( wxCommandInfo* pOther )
{
	if ( pOther->mpUserData ) 
		
		delete pOther->mpUserData;

	*pOther = *this;
	this->mpUserData = NULL; // ownership is transfered!
}

void wxCommandInfo::SetCmdParent( wxCommandInfo* pParent )
{
	mpParent = pParent;
}

wxCommandInfo* wxCommandInfo::GetParent()
{
	return mpParent;
}

/*** static methods ***/

static char* _gVirtualKeys[] = 
{
    "START", "LBUTTON", "RBUTTON","CANCEL","MBUTTON","CLEAR","SHIFT","CONTROL",

	"", // <- really it's "WXK_MENU" TBD:: comment....
	"", // FIXME:: what is this for?? (maintains the order somehow..)

	"PAUSE","CAPITAL","PRIOR","NEXT","END","HOME","LEFT","UP","RIGHT",
    "DOWN","SELECT","PRINT","EXECUTE","SNAPSHOT","INSERT","HELP","NUMPAD0",
    "NUMPAD1","NUMPAD2","NUMPAD3","NUMPAD4","NUMPAD5","NUMPAD6","NUMPAD7",
    "NUMPAD8","NUMPAD9","MULTIPLY","ADD","SEPARATOR","SUBTRACT","DECIMAL",
    "DIVIDE","F1","F2","F3","F4","F5","F6","F7","F8","F9","F10","F11","F12",
    "F13","F14","F15","F16","F17","F18","F19","F20","F21","F22","F23","F24",
    "NUMLOCK","WXK_SCROLL"
};

static wxHashTable _gVKHash( wxKEY_STRING );

wxString wxCommandInfo::KeyCodeToString( int keyCode )
{
	switch(keyCode) 
	{
		case  WXK_BACK : return "BACK";
		case  WXK_TAB  : return "TAB";
		case  WXK_RETURN : return "RETURN";
		case  WXK_ESCAPE : return "ESCAPE";
		case  WXK_SPACE  : return "SPACE";
		case  WXK_DELETE : return "DELETE";
		default : 
		{
			if ( IsPritableChar( keyCode ) )

				return wxString( (char)keyCode );

			// FIXME:: is this sizeof() trick data-alignment-safe? 

			if ( keyCode - 300 >= (int)(sizeof(_gVirtualKeys)/sizeof(char*)) )
			{
				if ( keyCode < 300 )

					return wxString( (char)(keyCode - 130) );
			
				return "<UNKNOWN!!!>";
			}

			return _gVirtualKeys[keyCode - 300];
		}
	}

//	return ""; // never reached
}

wxString wxCommandInfo::KeyModifierToString( int keyModifier )
{
	wxString result;

	if ( keyModifier & wxCONTROL_DOWN ) result += "Ctrl+";
	if ( keyModifier & wxALT_DONW )     result += "Alt+";
	if ( keyModifier & wxSHIFT_DOWN )   result += "Shift+";

	return result;
}

int wxCommandInfo::StringToKeyCode( const wxString& keyName )
{
	if ( keyName.Length() == 0 ) return 0;

	// FIXME:: is this sizeof() trick data-alignment-safe?

	if ( _gVKHash.Get( "F1" ) == NULL )
	{
		// init vk-hash if wasn't yet:

		_gVKHash.Put( "BACK", (wxObject*)WXK_BACK );
		_gVKHash.Put( "TAB", (wxObject*)WXK_TAB );
		_gVKHash.Put( "RETURN", (wxObject*)WXK_RETURN );
		_gVKHash.Put( "ESCAPE", (wxObject*)WXK_ESCAPE );
		_gVKHash.Put( "DELETE", (wxObject*)WXK_DELETE );

		for( size_t i = 0; i != sizeof(_gVirtualKeys)/sizeof(char*); ++i )
		
		   _gVKHash.Put( _gVirtualKeys[i], (wxObject*)(300 + i) );
	}


	int code = (int)_gVKHash.Get( keyName );

	if ( code == 0 && IsPritableChar( keyName[0] ) )

		return int(keyName[0]);

	return code;
}

int wxCommandInfo::StringToKeyModifier( const wxString& keyModifier )
{
	int mod = 0;

	if ( keyModifier.Find( "Alt+"     ) != -1 ) 
		mod |= wxALT_DONW;

	if ( keyModifier.Find( "Ctrl+" ) != -1 ) 
        mod |= wxCONTROL_DOWN;

	if ( keyModifier.Find( "Shift+"   ) != -1 ) 
        mod |= wxSHIFT_DOWN;

	return mod;
}

int wxCommandInfo::GetKeyModifier( wxKeyEvent& event )
{
	int mod = 0;
	if ( event.AltDown() )
        mod |= wxALT_DONW;

	if ( event.ControlDown() && event.KeyCode() != WXK_CONTROL )
        mod |= wxCONTROL_DOWN;

	if ( event.ShiftDown()   && event.KeyCode() != WXK_SHIFT   )
        mod |= wxSHIFT_DOWN;

	return mod;
}

wxString wxCommandInfo::GetKeyStrokeString( wxKeyEvent& event )
{
	return KeyModifierToString( GetKeyModifier(event) ) +
		   KeyCodeToString( event.KeyCode() );
}

wxString wxCommandInfo::GetKeyStrokeString( wxCommandInfo& cmd, int keyIndex )
{
	return KeyModifierToString( cmd.GetKeyModifiers()[keyIndex] ) +
		   KeyCodeToString( cmd.GetKeyCodes()[keyIndex] );
}

bool wxCommandInfo::ParseKeyStrokeString( const wxString& stroke, int* key, int* modifier )
{
	size_t i = stroke.Length() - 1;

	while( i > 0 && stroke[i] != '+' )
        --i;

	if ( stroke[i] == '+' ) ++i;

	*modifier = StringToKeyModifier( stroke );

	*key = StringToKeyCode( wxString( stroke, i, stroke.Length() - i ) );

	return TRUE; // TBD:: error-checks!
}

wxString wxCommandInfo::StripControlChars( const wxString& cmdTitle )
{
	wxString result;

    size_t i;
	for( i = 0; i != cmdTitle.Length(); ++i )

		if ( cmdTitle[i] != '&' && cmdTitle[i] != '.' )
            result += cmdTitle[i];

	return result;
}

/***** Implementation for class wxCommandGroup *****/

wxCommandGroup::wxCommandGroup()
{}

wxCommandGroup::wxCommandGroup( const wxString& title )

	: wxCommandInfo( -1, title )
{}

wxCommandGroup::wxCommandGroup( const wxString& title, wxCommandInfo* pCommands )

	: wxCommandInfo( -1, title )
{
	while( (*pCommands).GetTitle() != "" )
	{
		AddSubcommand( (*pCommands).Clone() );

		++pCommands;
	}
}

wxCommandInfo* wxCommandGroup::Clone()
{
	wxCommandGroup* pCopy = new wxCommandGroup();

	*((wxCommandInfo*)pCopy) = *((wxCommandInfo*)this);

	pCopy->SetTitle( this->GetTitle() );

    size_t i;
	for( i = 0; i != mCommands.Count(); ++i )

		pCopy->AddSubcommand( mCommands[i]->Clone() );

	return pCopy;
}

// bk is this necessary?? It is defined 7 lines down.
// wxCommandInfoListT* wxCommandGroup::GetSubcommands();

wxCommandGroup::~wxCommandGroup()
{
	 WX_CLEAR_ARRAY( mCommands );
}

wxCommandInfoListT* wxCommandGroup::GetSubcommands()
{
	return &mCommands;
}

void wxCommandGroup::AddSubcommand( wxCommandInfo* pInfo )
{
	pInfo->SetCmdParent( this );
	mCommands.Add( pInfo );
}

/***** Implementation for class wxKeyBinder *****/

BEGIN_EVENT_TABLE( wxKeyBinder, wxEvtHandler )

	EVT_KEY_DOWN( wxKeyBinder::OnChar )
	//EVT_CHAR_HOOK( wxKeyBinder::OnChar )
	EVT_CLOSE( wxKeyBinder::OnClose )

END_EVENT_TABLE()

wxKeyBinder::wxKeyBinder()
{}

wxKeyBinder::~wxKeyBinder()
{
	WX_CLEAR_ARRAY( mGroups );
}

void wxKeyBinder::AddGroup( wxCommandGroup* pGroup )
{
	mGroups.Add( pGroup );

	pGroup->Prepare();

	UpdateHashForCommand( *pGroup );
}

void wxKeyBinder::AddGroups( wxCommandGroup* pGroups )
{
	while( (*pGroups).GetTitle() != "" )
	{
		wxCommandInfo* pCmd = (*pGroups).Clone();

		pCmd->Prepare();

		UpdateHashForCommand( *pCmd );

		mGroups.Add( pCmd );

		++pGroups;
	}
}

void wxKeyBinder::RemoveGroup( wxCommandGroup* pGroup )
{
	mGroups.Remove( pGroup );

	UpdateKeyAssignments(); // recreates entire hash
}

wxCommandInfo* wxKeyBinder::FindCommandById( int id )
{
    size_t i;
	for( i = 0; i != mGroups.Count(); ++i )
	{
		wxCommandInfo* pFound = mGroups[i]->FindCommandById( id );

		if ( pFound )
            return pFound;
	}

	return NULL;
}

wxCommandInfoListT& wxKeyBinder::GetGroups()
{
	return mGroups;
}

wxKeyBinder* wxKeyBinder::Clone()
{
	wxKeyBinder* pCopy = new wxKeyBinder();

    size_t i;
	for( i = 0; i != mGroups.Count(); ++i )

		pCopy->mGroups.Add( mGroups[i]->Clone() );

	return pCopy;
}

void wxKeyBinder::AssignFrom( wxKeyBinder& other )
{
	WX_CLEAR_ARRAY( mGroups ); mKeysHash.Clear();

    size_t i;
	for( i = 0; i != other.mGroups.Count(); ++i )
	
		mGroups.Add( other.mGroups[i]->Clone() );

	UpdateKeyAssignments();
}

void wxKeyBinder::ResetUserData()
{
    size_t i;
	for( i = 0; i != mGroups.Count(); ++i )
	
		mGroups[i]->ResetUserData();
}

void wxKeyBinder::UpdateKeyAssignments()
{
	mKeysHash.Clear();

	UpdateHashForCommands( mGroups );
}

void wxKeyBinder::OnChar( wxKeyEvent& event )
{
	int key = event.KeyCode(); 
	int mod = wxCommandInfo::GetKeyModifier( event );

	long c = (long)( key + mod * 65536 );

	if ( key == WXK_BACK )
	{
		int o = 0;  // glt
		++o;
	}

	wxCommandInfo* pCmd = 
		(wxCommandInfo*)mKeysHash.Get( c );

	if ( pCmd != NULL )
	{
		wxCommandEvent menuEvent( wxEVT_COMMAND_MENU_SELECTED, pCmd->GetId() ); 
		menuEvent.SetEventObject( event.GetEventObject() );

		this->GetNextHandler()->ProcessEvent( menuEvent );
	}
	else
		event.Skip();
}

void wxKeyBinder::OnClose( wxCloseEvent& event )
{
	/*
	// FOR NOW:: disabled
	// FIXME:: totally unsafe....???

	wxWindow* pWnd = (wxWindow*)event.GetEventObject();

	wxASSERT( pWnd->IsKindOf( CLASSINFO(wxWindow) ) );
	wxASSERT( pWnd->GetEventHandler() == this );

	wxEvtHandler* pNext = GetNextHandler();

	pNext->ProcessEvent( event );

	pWnd->PopEventHandler();
	*/
	event.Skip();
}

void wxKeyBinder::CreateMenuBar( wxFrame* pForWindow )
{
	 wxMenuBar* pBar = new wxMenuBar();

	 CreateMenusRecursively( mGroups, pBar, NULL, TRUE );

	 pForWindow->SetMenuBar( pBar );
}

void wxKeyBinder::CreateMenusRecursively( wxCommandInfoListT& lst, wxMenuBar* pBar, 
										  wxMenu* pMenu, bool topLevel )
{
    size_t i;
	for( i = 0; i != lst.Count(); ++i )
	{
		if ( topLevel )
		{
			lst[i]->Prepare();

			wxMenu* pMenu = new wxMenu();

			if ( lst[i]->GetSubcommands() )
				
				CreateMenusRecursively( *lst[i]->GetSubcommands(), pBar, pMenu, FALSE );

			pBar->Append( pMenu, lst[i]->GetTitle() );
		}
		else
		{
			wxCommandInfo& cmd = *lst[i];
			wxMenu* pSubMenu = NULL;

			if ( cmd.GetSubcommands() && cmd.GetSubcommands()->Count() )
			{
				pSubMenu = new wxMenu();

//				wxMenuItem* pItem = new wxMenuItem( pMenu, 99, 
//													cmd.GetTitle(), cmd.GetHelpString(), 
//													cmd.IsCheckable(), pSubMenu );

				CreateMenusRecursively( *lst[i]->GetSubcommands(), pBar, pSubMenu, FALSE );

				pMenu->Append( cmd.GetId(), cmd.GetTitle(), pSubMenu );
			}
			else
			{
				wxString title = cmd.GetTitle();

				if ( cmd.GetKeyCount() > 0 )

					title += "\t" + wxCommandInfo::GetKeyStrokeString( cmd, 0 );

				if ( cmd.GetTitle() == "SEPARATOR" )

					pMenu->AppendSeparator();
				else
				{
					// FOR NOW:: hack

					if ( cmd.GetBitmap().Ok() )

						title = " " + title;

					wxMenuItem* pItem = new wxMenuItem( pMenu, cmd.GetId(), title,
														cmd.GetHelpString(), 
														cmd.IsCheckable() );
#ifdef __WINDOWS__
	#if wxCHECK_VERSION(2,3,0)

					// FIMXE:: now this causes crash on OwnerDraw(..) with cvs-wxWin

					if ( cmd.GetBitmap().Ok() ) 
					    
						pItem->SetBitmaps( cmd.GetBitmap() );
	#endif
#endif
					pMenu->Append( pItem );

					if ( cmd.IsCheckable() )

						pItem->Check( cmd.IsChecked() );
				}
			}
		}
	}
}

/*** protected methods ***/

void wxKeyBinder::UpdateHashForCommands( wxCommandInfoListT& lst )
{
    size_t i;
	for( i = 0; i != lst.Count(); ++i )
	
		UpdateHashForCommand( *lst[i] );
}

void wxKeyBinder::UpdateHashForCommand( wxCommandInfo& cmd )
{
	size_t cnt = cmd.GetKeyCount();
	int* codes = cmd.GetKeyCodes();
	int* mods  = cmd.GetKeyModifiers();

    size_t n;
	for( n = 0; n != cnt; ++n )
	{
		long c = (long)( codes[n] + mods[n] * 65536 );

		if ( codes[n] == 65 )
		{
			//glt - compiler warnings fixed
			int o=0;
			++o;
		}

		mKeysHash.Put( c, &cmd );
	}

	wxCommandInfoListT* pSubLst = cmd.GetSubcommands();

	if ( !pSubLst ) return;

    size_t i;
	for( i = 0; i != pSubLst->Count(); ++i )
	
		UpdateHashForCommand( *(*pSubLst)[i] );
}

bool wxCommandInfo::IsPritableChar( int c )
{
	return c >= '!' && c <= '}';
}

/***** Implementation for class wxKeyConfigPanel *****/

enum
{
	GROUPS_BOX_ID = 300,
	COMMANDS_BOX_ID,
	BINDINGS_BOX_ID,
	KEY_FIELD_ID,
	ASSIGN_KEY_ID,
	REMOVE_KEY_ID
};


/*** helper class ***/

class MyMonitoringTextCtrl : public wxTextCtrl
{
public:
	MyMonitoringTextCtrl( wxKeyConfigPanel* pPanel, int id );

	void OnChar( wxKeyEvent& event );
	void OnFocusGained( wxFocusEvent& event );

	DECLARE_EVENT_TABLE()

protected:
	wxKeyConfigPanel* mpPanel;
};

BEGIN_EVENT_TABLE( MyMonitoringTextCtrl, wxTextCtrl )

	EVT_KEY_DOWN ( MyMonitoringTextCtrl::OnChar )
	EVT_SET_FOCUS( MyMonitoringTextCtrl::OnFocusGained )

END_EVENT_TABLE()

MyMonitoringTextCtrl::MyMonitoringTextCtrl( wxKeyConfigPanel* pPanel, int id )

	: wxTextCtrl( pPanel, id, "",
				  wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER ),
	  mpPanel( pPanel )
{}

void MyMonitoringTextCtrl::OnChar( wxKeyEvent& event )
{
	mpPanel->OnKeyPressed( event );

	event.Skip();
}

void MyMonitoringTextCtrl::OnFocusGained( wxFocusEvent& event )
{
	mpPanel->OnKeyFieldFocused();

	event.Skip();
}

BEGIN_EVENT_TABLE( wxKeyConfigPanel, wxPanel )

	EVT_LISTBOX( GROUPS_BOX_ID,   wxKeyConfigPanel::OnGroupSelected )
	EVT_LISTBOX( COMMANDS_BOX_ID, wxKeyConfigPanel::OnCommandSelected )
	EVT_LISTBOX( BINDINGS_BOX_ID, wxKeyConfigPanel::OnBindingSelected )

	EVT_BUTTON( ASSIGN_KEY_ID, wxKeyConfigPanel::OnAssignKey )
	EVT_BUTTON( REMOVE_KEY_ID, wxKeyConfigPanel::OnRemoveKey )

	EVT_IDLE( wxKeyConfigPanel::OnIdle )

END_EVENT_TABLE()

wxKeyConfigPanel::wxKeyConfigPanel( wxWindow* pParent, wxKeyBinder* pBinder,
									bool showApplyBtn )

	: wxPanel( pParent, -1 ),
	  mpBinder( pBinder ),
	  mpCurGroup( NULL ),
	  mStrokeUpdatePending( FALSE )
{
	// creating wxwin controls

	mpGroups   = new wxListBox(  this, GROUPS_BOX_ID   );
	mpCommands = new wxListBox(  this, COMMANDS_BOX_ID );
	mpKeyField = new MyMonitoringTextCtrl( this, KEY_FIELD_ID );
	mpBindings = new wxListBox(  this, BINDINGS_BOX_ID );

	mpAssignBtn  = new wxButton( this, ASSIGN_KEY_ID, "A&ssign" );
	mpRemoveBtn = new wxButton( this, REMOVE_KEY_ID, "&Remove" );

	wxStaticBox* pBox = new wxStaticBox( this, -1, "Description" );			   
	mpDescLabel = new wxTextCtrl( pBox, -1, "" );

	// awt-layouts stuff:

	Container& cont = *new Container( this );
	cont.setLayout( new GridBagLayout() );

	GridBagConstraints c;
	int rmd = GridBagConstraints::REMAINDER;
	c.fill  = GridBagConstraints::BOTH;
	c.weightx = 1.0; c.weighty = 1.0; c.insets = Insets( 5,5,0,0 );
	c.gridy = 0; 

	Container* btnPanel = new Container();
	btnPanel->setInsets( Insets(5,5,5,5) );

	c.gridx = 0; c.weighty = 0.0;
	c.weightx = 0.5; cont.add( new wxStaticText( this,-1,"Groups:" ), &c );
	c.weightx = 1.0; cont.add( new wxStaticText( this,-1,"Commands:" ), &c );
	++c.gridy; 

	c.weighty = 1.0;
	cont.add( mpGroups, &c );   
	cont.add( mpCommands, &c );  
	++c.gridy; 

	c.weightx = 1.0; c.weighty = 0.0;
	cont.add( new wxStaticText( this,-1,"Press new short key:" ), &c );
	cont.add( new wxStaticText( this,-1,"Current keys:" ), &c );
	++c.gridy; 

	c.weightx = 0.5; c.weighty = 0.0;
	cont.add( mpKeyField, &c ); c.weighty = 0.5; c.gridheight = 2; c.weightx = 1.0;
	cont.add( mpBindings, &c ); 
	++c.gridy; 	

	c.gridheight = 1; c.weighty = 0.0; c.weightx = 0.0; 
	cont.add( btnPanel, &c ); 
	++c.gridy;

	c.gridwidth = 2; c.weighty = 0.0; //glt - weighty was 0.3
	Container* boxPanel = new Container( pBox );
	cont.add( boxPanel, &c );
	++c.gridy;

	btnPanel->setLayout( new FlowLayout() ); 

	btnPanel->add( mpAssignBtn ); // ...flowlayout rocks :)
	btnPanel->add( mpRemoveBtn );

	boxPanel->setLayout( new GridBagLayout() );
	boxPanel->setInsets( Insets( boxPanel->getMinimumSize().height, 10, 10, 10 ) );

	GridBagConstraints c1; c1.weightx = 1.0;
	c1.anchor = GridBagConstraints::WEST; 
	c1.fill   = GridBagConstraints::BOTH;
	boxPanel->add( mpDescLabel, &c1 );

	if ( showApplyBtn )
	{
		wxButton* applyBtn  = new wxButton( this, wxID_APPLY,  "A&pply" );
		wxButton* cancelBtn = new wxButton( this, wxID_CANCEL, "C&ancel" );
		
		Container* btnPanel1 = new Container();
		btnPanel1->setLayout( new FlowLayout() ); 
		btnPanel1->setInsets( Insets( 10,0,10,0 ) );

		btnPanel1->setLayout( new FlowLayout() ); 

		btnPanel1->add( applyBtn );
		btnPanel1->add( cancelBtn );

		c.gridwidth = 2; c.weighty = 0.0; 
		cont.add( btnPanel1, &c );
	}
	
	// copy original bindings to temporary binder, which is customized here

	mTmpBinder.AssignFrom( *pBinder );

	FillInGroups();

	mpGroups->SetFocus();

	UpdateButtons();
}

wxKeyConfigPanel::~wxKeyConfigPanel()
{
	mTmpBinder.ResetUserData();
}

void wxKeyConfigPanel::ApplyChanges()
{
	// apply changed bindings to the original binder

	mpBinder->ResetUserData();

	mpBinder->AssignFrom( mTmpBinder );

	mTmpBinder.ResetUserData();
}

void wxKeyConfigPanel::OnGroupSelected( wxCommandEvent& event )
{
	FillInCommands();
	UpdateButtons();
}

void wxKeyConfigPanel::OnCommandSelected( wxCommandEvent& event )
{
	FillInBindings();
	UpdateButtons();
}

void wxKeyConfigPanel::OnBindingSelected( wxCommandEvent& event )
{
	mCurKeyStroke = "";
	mStrokeUpdatePending = TRUE;
}

void wxKeyConfigPanel::OnAssignKey( wxCommandEvent& event )
{
	wxCommandInfo* pCmd = 
		(*mpCurGroup->GetSubcommands())[ GetSelectedCmdIndex() ];

	if ( pCmd->GetKeyCount() >= wxMAX_KEYS_PER_CMD ) 
	{
		wxMessageBox( "Cannot assign any more keystrokes to this command" );

		mCurKeyStroke = "";
		mStrokeUpdatePending = TRUE;
		return;
	}

	int code = 0, mod = 0;
	wxCommandInfo::ParseKeyStrokeString( mCurKeyStroke, &code, &mod );

	if ( pCmd->KeyPresent( code, mod ) )
	{
		wxMessageBox( "The keystroke is already assigned to this command" );
		mpKeyField->SetFocus();
		return;
	}


	pCmd->AddKey( code, mod );
	FillInBindings();

	mCurKeyStroke = "";
	mStrokeUpdatePending = TRUE;

	mpKeyField->SetFocus();
}

void wxKeyConfigPanel::OnRemoveKey( wxCommandEvent& event )
{
	wxCommandInfo* pCmd = 
		(*mpCurGroup->GetSubcommands())[ GetSelectedCmdIndex() ];

	pCmd->RemoveKey( (size_t)mpBindings->GetSelection() );

	FillInBindings();

	if ( pCmd->GetKeyCount() )

		mpBindings->SetSelection( pCmd->GetKeyCount() - 1 );
	else
		mpKeyField->SetFocus();

	mCurKeyStroke = "";
	mStrokeUpdatePending = TRUE;
}

void wxKeyConfigPanel::OnKeyPressed( wxKeyEvent& event )
{
	mCurKeyStroke = wxCommandInfo::GetKeyStrokeString( event );

	mStrokeUpdatePending = TRUE;
}

void wxKeyConfigPanel::OnKeyFieldFocused()
{
	//LOG("OnFocused")
}

void wxKeyConfigPanel::OnIdle( wxIdleEvent& event )
{
	if ( mStrokeUpdatePending )
	{
		mStrokeUpdatePending = FALSE;
		mpKeyField->SetValue( mCurKeyStroke );
		mpKeyField->SetInsertionPoint( mCurKeyStroke.Length() );

		UpdateButtons();
	}

	event.Skip();
}

/*** protected methods ***/

void wxKeyConfigPanel::UpdateButtons()
{
	mpRemoveBtn->Enable( mpBindings->GetSelection() >= 0 );
	mpAssignBtn->Enable( GetSelectedCmdIndex() >= 0 &&
						mpKeyField->GetValue().Length() != 0 );
}

void wxKeyConfigPanel::FillInGroups()
{
	wxCommandInfoListT& lst = mTmpBinder.GetGroups();

    size_t i;
	for( i = 0; i != lst.Count(); ++i )
    {
		mpGroups->Append( wxCommandInfo::StripControlChars( lst[i]->GetTitle() ) );
    }

	if ( lst.Count() )
	{
		mpGroups->SetSelection( 0 );

		FillInCommands();
	}
}

void wxKeyConfigPanel::FillInCommands()
{
	if ( mpGroups->GetSelection() >= 0 )
	{
		mpCurGroup = mTmpBinder.GetGroups()[ mpGroups->GetSelection() ];

		wxCommandInfoListT& lst = *mpCurGroup->GetSubcommands();

		if ( mpCommands->Number() > 0 ) mpCommands->Clear();

        size_t i;
		for( i = 0; i != lst.Count(); ++i )
        {
			if ( !lst[i]->IsSeparator() )
				mpCommands->Append( wxCommandInfo::StripControlChars( lst[i]->GetTitle() ) );
        }
	}
}

void wxKeyConfigPanel::FillInBindings()
{
	if ( GetSelectedCmdIndex() >= 0 )
	{
		wxCommandInfo* pCmd = 
			(*mpCurGroup->GetSubcommands())[ GetSelectedCmdIndex() ];

		size_t cnt = pCmd->GetKeyCount();

		if ( mpBindings->Number() > 0 )
            mpBindings->Clear();

		int* codes = pCmd->GetKeyCodes();
		int* mods  = pCmd->GetKeyModifiers();

        size_t i;
		for( i = 0; i != cnt; ++i )
        {
			mpBindings->Append( wxCommandInfo::KeyModifierToString( mods[i] ) + 
								wxCommandInfo::KeyCodeToString( codes[i] ) );
        }

		mpDescLabel->SetLabel( pCmd->GetHelpString() );

		// somehow Refresh() doesn't do it's jobe here...
		int w = 0, h = 0;
		mpDescLabel->GetParent()->GetSize( &w, &h );
		mpDescLabel->GetParent()->SetSize( w,h+1 );
		mpDescLabel->GetParent()->SetSize( w,h );
	}
}

int wxKeyConfigPanel::GetSelectedCmdIndex()
{
	// this is used instead of mpCommands->GetSelection(), because
	// mpGroups listbox does not contain separator-entries, thus
	// to avoid index mismatch we recalculate it

	int sel = mpCommands->GetSelection();

	if ( sel < 0 )
        return -1;

	wxCommandInfoListT& lst = *mpCurGroup->GetSubcommands();
	int lstIndex = 0;

    size_t i;
	for( i = 0; i != lst.Count(); ++i )
	{
		wxString title = lst[i]->GetTitle();

		if ( lst[i]->IsSeparator() == FALSE ) 
		{
			if ( lstIndex == sel )
                return i;

			++lstIndex;
		}
	}
	
	return lstIndex;
}

/***** Implementation for class wxKeyConfigDialog *****/

BEGIN_EVENT_TABLE( wxKeyConfigDialog, wxDialog )

	EVT_BUTTON( wxID_APPLY,  wxKeyConfigDialog::OnApply  )
	EVT_BUTTON( wxID_CANCEL, wxKeyConfigDialog::OnCancel )

	EVT_SIZE( wxKeyConfigDialog::OnSize )

END_EVENT_TABLE()

static wxSize _gConfigDlgLRUSize( 300,400 );

wxKeyConfigDialog::wxKeyConfigDialog( wxWindow* pParent, wxKeyBinder* pBinder )

	: wxDialog( pParent, -1, "Customize keyboard shortcuts", 
				wxDefaultPosition, _gConfigDlgLRUSize,
				wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER )
{
	mpConfigPanel = new wxKeyConfigPanel( this, pBinder, TRUE );

    // glt - Had to add this, as the panel was not being initially sized
    // correctly to match the wxDialog it is on top of.
    int w,h;
	GetClientSize(&w, &h);
	mpConfigPanel->SetSize(w,h);

	Center( wxBOTH );
}

wxKeyConfigDialog::~wxKeyConfigDialog()
{
}

void wxKeyConfigDialog::OnApply( wxCommandEvent& event )
{
	mpConfigPanel->ApplyChanges();

	EndModal( wxID_APPLY );
}

void wxKeyConfigDialog::OnCancel( wxCommandEvent& event )
{
	EndModal( wxID_CANCEL );
}

void wxKeyConfigDialog::OnSize( wxSizeEvent& event )
{
	int w = 0, h = 0;

	GetClientSize( &w, &h );
	mpConfigPanel->SetSize( w,h );

	GetSize( &w, &h );

	_gConfigDlgLRUSize.x = w; 
	_gConfigDlgLRUSize.y = h;
}
