/*
===========================================================================

Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.

This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").

Doom 3 BFG Edition Source Code 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.

Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code.  If not, see <http://www.gnu.org/licenses/>.

In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code.  If not, please request a copy in writing from id Software at the address below.

If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.

===========================================================================
*/
#ifndef __MENU_WIDGET_H__
#define __MENU_WIDGET_H__

class idMenuHandler;
class idMenuWidget;

enum menuOption_t
{
	OPTION_INVALID = -1,
	OPTION_BUTTON_TEXT,
	OPTION_SLIDER_BAR,
	OPTION_SLIDER_TEXT,
	OPTION_SLIDER_TOGGLE,
	OPTION_BUTTON_INFO,
	OPTION_BUTTON_FULL_TEXT_SLIDER,
	MAX_MENU_OPTION_TYPES
};

enum widgetEvent_t
{
	WIDGET_EVENT_PRESS,
	WIDGET_EVENT_RELEASE,
	WIDGET_EVENT_ROLL_OVER,
	WIDGET_EVENT_ROLL_OUT,
	WIDGET_EVENT_FOCUS_ON,
	WIDGET_EVENT_FOCUS_OFF,

	WIDGET_EVENT_SCROLL_UP_LSTICK,
	WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE,
	WIDGET_EVENT_SCROLL_DOWN_LSTICK,
	WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE,
	WIDGET_EVENT_SCROLL_LEFT_LSTICK,
	WIDGET_EVENT_SCROLL_LEFT_LSTICK_RELEASE,
	WIDGET_EVENT_SCROLL_RIGHT_LSTICK,
	WIDGET_EVENT_SCROLL_RIGHT_LSTICK_RELEASE,

	WIDGET_EVENT_SCROLL_UP_RSTICK,
	WIDGET_EVENT_SCROLL_UP_RSTICK_RELEASE,
	WIDGET_EVENT_SCROLL_DOWN_RSTICK,
	WIDGET_EVENT_SCROLL_DOWN_RSTICK_RELEASE,
	WIDGET_EVENT_SCROLL_LEFT_RSTICK,
	WIDGET_EVENT_SCROLL_LEFT_RSTICK_RELEASE,
	WIDGET_EVENT_SCROLL_RIGHT_RSTICK,
	WIDGET_EVENT_SCROLL_RIGHT_RSTICK_RELEASE,

	WIDGET_EVENT_SCROLL_UP,
	WIDGET_EVENT_SCROLL_UP_RELEASE,
	WIDGET_EVENT_SCROLL_DOWN,
	WIDGET_EVENT_SCROLL_DOWN_RELEASE,
	WIDGET_EVENT_SCROLL_LEFT,
	WIDGET_EVENT_SCROLL_LEFT_RELEASE,
	WIDGET_EVENT_SCROLL_RIGHT,
	WIDGET_EVENT_SCROLL_RIGHT_RELEASE,

	WIDGET_EVENT_DRAG_START,
	WIDGET_EVENT_DRAG_STOP,

	WIDGET_EVENT_SCROLL_PAGEDWN,
	WIDGET_EVENT_SCROLL_PAGEDWN_RELEASE,
	WIDGET_EVENT_SCROLL_PAGEUP,
	WIDGET_EVENT_SCROLL_PAGEUP_RELEASE,

	WIDGET_EVENT_SCROLL,
	WIDGET_EVENT_SCROLL_RELEASE,
	WIDGET_EVENT_BACK,
	WIDGET_EVENT_COMMAND,
	WIDGET_EVENT_TAB_NEXT,
	WIDGET_EVENT_TAB_PREV,
	MAX_WIDGET_EVENT
};

enum scrollType_t
{
	SCROLL_SINGLE,		// scroll a single unit
	SCROLL_PAGE,		// scroll a page
	SCROLL_FULL,		// scroll all the way to the end
	SCROLL_TOP,			// scroll to the first selection
	SCROLL_END,			// scroll to the last selection
};

enum widgetAction_t
{
	WIDGET_ACTION_NONE,
	WIDGET_ACTION_COMMAND,
	WIDGET_ACTION_FUNCTION,					// call the SWF function
	WIDGET_ACTION_SCROLL_VERTICAL,			// scroll something. takes one param = amount to scroll (can be negative)
	WIDGET_ACTION_SCROLL_VERTICAL_VARIABLE,
	WIDGET_ACTION_SCROLL_PAGE,
	WIDGET_ACTION_SCROLL_HORIZONTAL,		// scroll something. takes one param = amount to scroll (can be negative)
	WIDGET_ACTION_SCROLL_TAB,
	WIDGET_ACTION_START_REPEATER,
	WIDGET_ACTION_STOP_REPEATER,
	WIDGET_ACTION_ADJUST_FIELD,
	WIDGET_ACTION_PRESS_FOCUSED,
	WIDGET_ACTION_JOY3_ON_PRESS,
	WIDGET_ACTION_JOY4_ON_PRESS,
	//
	WIDGET_ACTION_GOTO_MENU,
	WIDGET_ACTION_GO_BACK,
	WIDGET_ACTION_EXIT_GAME,
	WIDGET_ACTION_LAUNCH_MULTIPLAYER,
	WIDGET_ACTION_MENU_BAR_SELECT,
	WIDGET_ACTION_EMAIL_HOVER,
	// PDA USER DATA ACTIONS
	WIDGET_ACTION_PDA_SELECT_USER,
	WIDGET_ACTION_SELECT_GAMERTAG,
	WIDGET_ACTION_PDA_SELECT_NAV,
	WIDGET_ACTION_SELECT_PDA_AUDIO,
	WIDGET_ACTION_SELECT_PDA_VIDEO,
	WIDGET_ACTION_SELECT_PDA_ITEM,
	WIDGET_ACTION_SCROLL_DRAG,
	// PDA EMAIL ACTIONS
	WIDGET_ACTION_PDA_SELECT_EMAIL,
	WIDGET_ACTION_PDA_CLOSE,
	WIDGET_ACTION_REFRESH,
	WIDGET_ACTION_MUTE_PLAYER,
	MAX_WIDGET_ACTION
};

enum actionHandler_t
{
	WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER,
	WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER_VARIABLE,
	WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER,
	WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER_VARIABLE,
	WIDGET_ACTION_EVENT_SCROLL_LEFT_START_REPEATER,
	WIDGET_ACTION_EVENT_SCROLL_RIGHT_START_REPEATER,
	WIDGET_ACTION_EVENT_SCROLL_PAGE_DOWN_START_REPEATER,
	WIDGET_ACTION_EVENT_SCROLL_PAGE_UP_START_REPEATER,
	WIDGET_ACTION_EVENT_STOP_REPEATER,
	WIDGET_ACTION_EVENT_TAB_NEXT,
	WIDGET_ACTION_EVENT_TAB_PREV,
	WIDGET_ACTION_EVENT_DRAG_START,
	WIDGET_ACTION_EVENT_DRAG_STOP,
	WIDGET_ACTION_EVENT_JOY3_ON_PRESS,
};

struct widgetTransition_t
{
	widgetTransition_t() :
		animationName( NULL )
	{

	}

	const char* 						animationName;			// name of the animation to run
	idStaticList< const char*, 4 >		prefixes;				// prefixes to try to use for animation
};


/*
================================================
scoreboardInfo_t
================================================
*/
struct scoreboardInfo_t
{
	scoreboardInfo_t() :
		index( -1 ),
		voiceState( VOICECHAT_DISPLAY_NONE )
	{
	}

	idList< idStr, TAG_IDLIB_LIST_MENU> values;
	int						index;
	voiceStateDisplay_t		voiceState;
};

/*
================================================
idSort_SavesByDate
================================================
*/
class idSort_SavesByDate : public idSort_Quick< idSaveGameDetails, idSort_SavesByDate >
{
public:
	int Compare( const idSaveGameDetails& a, const idSaveGameDetails& b ) const
	{
		return b.date - a.date;
	}
};

/*
================================================
idMenuDataSource
================================================
*/
class idMenuDataSource
{
public:
	virtual ~idMenuDataSource() { }

	virtual void				LoadData()							= 0;
	virtual void				CommitData()						= 0;
	virtual bool				IsDataChanged() const				= 0;
	virtual idSWFScriptVar		GetField( const int fieldIndex ) const	= 0;
	virtual void				AdjustField( const int fieldIndex, const int adjustAmount ) = 0;
};

/*
================================================
idWidgetEvent
================================================
*/
class idWidgetEvent
{
public:
	idWidgetEvent() :
		type( WIDGET_EVENT_PRESS ),
		arg( 0 ),
		thisObject( NULL )
	{

	}

	idWidgetEvent( const widgetEvent_t type_, const int arg_, idSWFScriptObject* thisObject_, const idSWFParmList& parms_ ) :
		type( type_ ),
		arg( arg_ ),
		thisObject( thisObject_ ),
		parms( parms_ )
	{
	}

	widgetEvent_t			type;
	int						arg;
	idSWFScriptObject* 		thisObject;
	idSWFParmList 			parms;
};

/*
================================================
idWidgetAction
================================================
*/
class idWidgetAction
{
public:
	idWidgetAction() :
		action( WIDGET_ACTION_NONE ),
		scriptFunction( NULL )
	{
	}

	idWidgetAction( const idWidgetAction& src )
	{
		action = src.action;
		parms = src.parms;
		scriptFunction = src.scriptFunction;
		if( scriptFunction != NULL )
		{
			scriptFunction->AddRef();
		}
	}

	~idWidgetAction()
	{
		if( scriptFunction != NULL )
		{
			scriptFunction->Release();
		}
	}

	void operator=( const idWidgetAction& src )
	{
		action = src.action;
		parms = src.parms;
		scriptFunction = src.scriptFunction;
		if( scriptFunction != NULL )
		{
			scriptFunction->AddRef();
		}
	}

	bool operator==( const idWidgetAction& otherAction ) const
	{
		if( GetType() != otherAction.GetType()
				|| GetParms().Num() != otherAction.GetParms().Num() )
		{
			return false;
		}

		// everything else is equal, so check all parms. NOTE: this assumes we are only sending
		// integral types.
		for( int i = 0; i < GetParms().Num(); ++i )
		{
			if( GetParms()[ i ].GetType() != otherAction.GetParms()[ i ].GetType()
					|| GetParms()[ i ].ToInteger() != otherAction.GetParms()[ i ].ToInteger() )
			{
				return false;
			}
		}

		return true;
	}

	void Set( idSWFScriptFunction* function )
	{
		action = WIDGET_ACTION_FUNCTION;
		if( scriptFunction != NULL )
		{
			scriptFunction->Release();
		}
		scriptFunction = function;
		scriptFunction->AddRef();
	}

	void Set( widgetAction_t action_ )
	{
		action = action_;
		parms.Clear();
	}

	void Set( widgetAction_t action_, const idSWFScriptVar& var1 )
	{
		action = action_;
		parms.Clear();
		parms.Append( var1 );
	}

	void Set( widgetAction_t action_, const idSWFScriptVar& var1, const idSWFScriptVar& var2 )
	{
		action = action_;
		parms.Clear();
		parms.Append( var1 );
		parms.Append( var2 );
	}

	void Set( widgetAction_t action_, const idSWFScriptVar& var1, const idSWFScriptVar& var2, const idSWFScriptVar& var3 )
	{
		action = action_;
		parms.Clear();
		parms.Append( var1 );
		parms.Append( var2 );
		parms.Append( var3 );
	}

	void Set( widgetAction_t action_, const idSWFScriptVar& var1, const idSWFScriptVar& var2, const idSWFScriptVar& var3, const idSWFScriptVar& var4 )
	{
		action = action_;
		parms.Clear();
		parms.Append( var1 );
		parms.Append( var2 );
		parms.Append( var3 );
		parms.Append( var4 );
	}

	idSWFScriptFunction* 	GetScriptFunction()
	{
		return scriptFunction;
	}
	const widgetAction_t	GetType() const
	{
		return action;
	}
	const idSWFParmList& 	GetParms() const
	{
		return parms;
	}

private:
	widgetAction_t			action;
	idSWFParmList			parms;
	idSWFScriptFunction* 	scriptFunction;
};

typedef idList< idMenuWidget*, TAG_IDLIB_LIST_MENU > idMenuWidgetList;

/*
================================================
idMenuWidget

We're using a model/view architecture, so this is the combination of both model and view.  The
other part of the view is the SWF itself.
================================================
*/
class idMenuWidget
{
public:

	/*
	================================================
	WrapWidgetSWFEvent
	================================================
	*/
	class WrapWidgetSWFEvent : public idSWFScriptFunction_RefCounted
	{
	public:
		WrapWidgetSWFEvent( idMenuWidget* widget, const widgetEvent_t event, const int eventArg ) :
			targetWidget( widget ),
			targetEvent( event ),
			targetEventArg( eventArg )
		{
		}

		idSWFScriptVar Call( idSWFScriptObject* thisObject, const idSWFParmList& parms )
		{
			targetWidget->ReceiveEvent( idWidgetEvent( targetEvent, targetEventArg, thisObject, parms ) );
			return idSWFScriptVar();
		}

	private:
		idMenuWidget* 	targetWidget;
		widgetEvent_t	targetEvent;
		int				targetEventArg;
	};


	enum widgetState_t
	{
		WIDGET_STATE_HIDDEN,	// hidden
		WIDGET_STATE_NORMAL,	// normal
		WIDGET_STATE_SELECTING,	// going into the selected state
		WIDGET_STATE_SELECTED,	// fully selected
		WIDGET_STATE_DISABLED,	// disabled
		WIDGET_STATE_MAX
	};

	idMenuWidget();

	virtual								~idMenuWidget();

	void								Cleanup();
	// typically this is where the allocations for a widget will occur: sub widgets, etc.
	// Note that SWF sprite objects may not be accessible at this point.
	virtual void						Initialize( idMenuHandler* data )
	{
		menuData = data;
	}

	// takes the information described in this widget and applies it to a given script object.
	// the script object should point to the root that you want to run from. Running this will
	// also create the sprite binding, if any.
	virtual void						Update() {}
	virtual void						Show();
	virtual void						Hide();

	widgetState_t						GetState() const
	{
		return widgetState;
	}
	void								SetState( const widgetState_t state );

	// actually binds the sprite. this must be called after setting sprite path!
	idSWFSpriteInstance* 				GetSprite()
	{
		return boundSprite;
	}
	idSWF* 								GetSWFObject();
	idMenuHandler* 						GetMenuData();
	bool								BindSprite( idSWFScriptObject& root );
	void								ClearSprite();

	void								SetSpritePath( const char* arg1, const char* arg2 = NULL, const char* arg3 = NULL, const char* arg4 = NULL, const char* arg5 = NULL );
	void								SetSpritePath( const idList< idStr >& spritePath_, const char* arg1 = NULL, const char* arg2 = NULL, const char* arg3 = NULL, const char* arg4 = NULL, const char* arg5 = NULL );
	idList< idStr, TAG_IDLIB_LIST_MENU >& 					GetSpritePath()
	{
		return spritePath;
	}
	int									GetRefCount() const
	{
		return refCount;
	}
	void						AddRef()
	{
		refCount++;
	}
	void						Release()
	{
		assert( refCount > 0 );
		if( --refCount == 0 && !noAutoFree )
		{
			delete this;
		}
	}

	//------------------------
	// Event Handling
	//------------------------
	virtual bool						HandleAction( idWidgetAction& action, const idWidgetEvent& event, idMenuWidget* widget, bool forceHandled = false );
	virtual void						ObserveEvent( const idMenuWidget& widget, const idWidgetEvent& event ) { }
	void								SendEventToObservers( const idWidgetEvent& event );
	void								RegisterEventObserver( idMenuWidget* observer );
	void								ReceiveEvent( const idWidgetEvent& event );

	// Executes an event in the context of this widget.  Only rarely should this be called
	// directly.  Instead calls should go through ReceiveEvent which will propagate the event
	// through the standard focus order.
	virtual bool						ExecuteEvent( const idWidgetEvent& event );

	// returns the list of actions for a given event, or NULL if no actions are registered for
	// that event.  Events should not be directly added to the returned list.  Instead use
	// AddEventAction for adding new events.
	idList< idWidgetAction, TAG_IDLIB_LIST_MENU >* 			GetEventActions( const widgetEvent_t eventType );

	// allocates an action for the given event
	idWidgetAction& 					AddEventAction( const widgetEvent_t eventType );
	void								ClearEventActions();

	//------------------------
	// Data modeling
	//------------------------
	void								SetDataSource( idMenuDataSource* dataSource, const int fieldIndex );
	idMenuDataSource* 					GetDataSource()
	{
		return dataSource;
	}
	void								SetDataSourceFieldIndex( const int dataSourceFieldIndex_ )
	{
		dataSourceFieldIndex = dataSourceFieldIndex_;
	}
	int									GetDataSourceFieldIndex() const
	{
		return dataSourceFieldIndex;
	}

	idMenuWidget* 						GetFocus()
	{
		return ( focusIndex >= 0 && focusIndex < children.Num() ) ? children[ focusIndex ] : NULL;
	}
	int									GetFocusIndex() const
	{
		return focusIndex;
	}
	void								SetFocusIndex( const int index, bool skipSound = false );

	//------------------------
	// Hierarchy
	//------------------------
	idMenuWidgetList& 					GetChildren()
	{
		return children;
	}
	const idMenuWidgetList& 			GetChildren() const
	{
		return children;
	}
	idMenuWidget& 						GetChildByIndex( const int index ) const
	{
		return *children[ index ];
	}

	void								AddChild( idMenuWidget* widget );
	void								RemoveChild( idMenuWidget* widget );
	bool								HasChild( idMenuWidget* widget );
	void								RemoveAllChildren();

	idMenuWidget* 						GetParent()
	{
		return parent;
	}
	const idMenuWidget* 				GetParent() const
	{
		return parent;
	}
	void								SetParent( idMenuWidget* parent_ )
	{
		parent = parent_;
	}
	void								SetSWFObj( idSWF* obj )
	{
		swfObj = obj;
	}
	bool								GetHandlerIsParent()
	{
		return handlerIsParent;
	}
	void								SetHandlerIsParent( bool val )
	{
		handlerIsParent = val;
	}
	void								SetNoAutoFree( bool b )
	{
		noAutoFree = b;
	}

protected:
	void								ForceFocusIndex( const int index )
	{
		focusIndex = index;
	}

protected:
	bool								handlerIsParent;
	idMenuHandler* 						menuData;
	idSWF* 								swfObj;
	idSWFSpriteInstance* 				boundSprite;
	idMenuWidget* 						parent;
	idList< idStr, TAG_IDLIB_LIST_MENU >						spritePath;
	idMenuWidgetList											children;
	idMenuWidgetList											observers;

	static const int INVALID_ACTION_INDEX = -1;
	idList< idList< idWidgetAction, TAG_IDLIB_LIST_MENU >, TAG_IDLIB_LIST_MENU >		eventActions;
	idStaticList< int, MAX_WIDGET_EVENT >	eventActionLookup;

	idMenuDataSource* 					dataSource;
	int									dataSourceFieldIndex;

	int									focusIndex;

	widgetState_t						widgetState;
	int									refCount;
	bool								noAutoFree;
};

/*
================================================
idMenuWidget_Button

This might be better named "ComboButton", as it contains behavior for several controls along
with standard button behavior.
================================================
*/
class idMenuWidget_Button : public idMenuWidget
{
public:

	enum animState_t
	{
		ANIM_STATE_UP,			// standard
		ANIM_STATE_DOWN,		// pressed down
		ANIM_STATE_OVER,		// hovered over this
		ANIM_STATE_MAX
	};

	idMenuWidget_Button() :
		animState( ANIM_STATE_UP ),
		img( NULL ),
		ignoreColor( false )
	{
	}

	virtual ~idMenuWidget_Button() {}

	virtual bool			ExecuteEvent( const idWidgetEvent& event );
	virtual void			Update();

	//---------------
	// Model
	//---------------
	void					SetLabel( const idStr& label )
	{
		btnLabel = label;
	}
	const idStr& 			GetLabel() const
	{
		return btnLabel;
	}
	void					SetValues( idList< idStr >& list );
	const idStr& 			GetValue( int index ) const;
	void					SetImg( const idMaterial* val )
	{
		img = val;
	}
	const idMaterial* 		GetImg()
	{
		return img;
	}
	void					SetDescription( const char* desc_ )
	{
		description = desc_;
	}
	const idStr& 			GetDescription() const
	{
		return description;
	}

	void					SetIgnoreColor( const bool b )
	{
		ignoreColor = b;
	}

	animState_t				GetAnimState() const
	{
		return animState;
	}
	void					SetAnimState( const animState_t state )
	{
		animState = state;
	}
	void					SetOnPressFunction( idSWFScriptFunction* func )
	{
		scriptFunction = func;
	}

protected:
	void					SetupTransitionInfo( widgetTransition_t& trans, const widgetState_t buttonState, const animState_t sourceAnimState, const animState_t destAnimState ) const;
	void					AnimateToState( const animState_t targetState, const bool force = false );

	idList< idStr, TAG_IDLIB_LIST_MENU >			values;
	idStr					btnLabel;
	idStr					description;
	animState_t				animState;
	const idMaterial* 		img;
	idSWFScriptFunction* 	scriptFunction;
	bool					ignoreColor;
};

/*
================================================
idMenuWidget_LobbyButton
================================================
*/
class idMenuWidget_LobbyButton : public idMenuWidget_Button
{
public:
	idMenuWidget_LobbyButton() :
		voiceState( VOICECHAT_DISPLAY_NONE )
	{
	}

	virtual void			Update();
	void					SetButtonInfo( idStr name_, voiceStateDisplay_t voiceState_ );
	bool					IsValid()
	{
		return !name.IsEmpty();
	}

protected:
	idStr					name;
	voiceStateDisplay_t		voiceState;
};

/*
================================================
idMenuWidget_ScoreboardButton
================================================
*/
class idMenuWidget_ScoreboardButton : public idMenuWidget_Button
{
public:
	idMenuWidget_ScoreboardButton() :
		voiceState( VOICECHAT_DISPLAY_NONE ),
		index( -1 )
	{
	}

	virtual void			Update();
	void					SetButtonInfo( int index_, idList< idStr >& list, voiceStateDisplay_t voiceState_ );

protected:
	voiceStateDisplay_t		voiceState;
	int						index;
};

/*
================================================
idMenuWidget_ControlButton
================================================
*/
class idMenuWidget_ControlButton : public idMenuWidget_Button
{
public:
	idMenuWidget_ControlButton() :
		optionType( OPTION_BUTTON_TEXT ),
		disabled( false )
	{
	}

	virtual void			Update();
	void					SetOptionType( const menuOption_t type )
	{
		optionType = type;
	}
	menuOption_t			GetOptionType() const
	{
		return optionType;
	}
	void					SetupEvents( int delay, int index );
	void					SetDisabled( bool disable )
	{
		disabled = disable;
	}

protected:
	menuOption_t			optionType;
	bool					disabled;
};

/*
================================================
idMenuWidget_ServerButton
================================================
*/
class idMenuWidget_ServerButton : public idMenuWidget_Button
{
public:

	idMenuWidget_ServerButton() :
		index( 0 ),
		players( 0 ),
		maxPlayers( 0 ),
		joinable( false ),
		validMap( false )
	{
	}

	virtual void			Update();
	void					SetButtonInfo( idStr name_, idStrId mapName_, idStr modeName_, int index_ = 0, int players_ = 0, int maxPlayers_ = 0, bool joinable_ = false, bool validMap_ = false );
	bool					IsValid()
	{
		return !serverName.IsEmpty();
	}
	bool					CanJoin()
	{
		return ( joinable && validMap );
	}

protected:
	idStr					serverName;
	int						index;
	int						players;
	int						maxPlayers;
	bool					joinable;
	bool					validMap;
	idStrId					mapName;
	idStr					modeName;
};

/*
================================================
idMenuWidget_NavButton
================================================
*/
class idMenuWidget_NavButton : public idMenuWidget_Button
{
public:

	enum navWidgetState_t
	{
		NAV_WIDGET_LEFT,		// option on left side
		NAV_WIDGET_RIGHT,		// option on right side
		NAV_WIDGET_SELECTED		// option is selected
	};

	idMenuWidget_NavButton() :
		navIndex( 0 ),
		xPos( 0 )
	{
	}

	virtual bool			ExecuteEvent( const idWidgetEvent& event );
	virtual void			Update();

	void					SetNavIndex( int i, const navWidgetState_t type )
	{
		navIndex = i;
		navState = type;
	}
	void					SetPosition( float pos )
	{
		xPos = pos;
	}

private:

	int						navIndex;
	float					xPos;
	navWidgetState_t		navState;

};

/*
================================================
idMenuWidget_NavButton
================================================
*/
class idMenuWidget_MenuButton : public idMenuWidget_Button
{
public:

	idMenuWidget_MenuButton() :
		xPos( 0 )
	{
	}

	virtual void			Update();
	void					SetPosition( float pos )
	{
		xPos = pos;
	}

private:

	float					xPos;

};

/*
================================================
idMenuWidget_List

Stores a list of widgets but displays only a segment of them at a time.
================================================
*/
class idMenuWidget_List : public idMenuWidget
{
public:
	idMenuWidget_List() :
		numVisibleOptions( 0 ),
		viewOffset( 0 ),
		viewIndex( 0 ),
		allowWrapping( false )
	{

	}

	virtual void				Update();
	virtual bool				HandleAction( idWidgetAction& action, const idWidgetEvent& event, idMenuWidget* widget, bool forceHandled = false );
	virtual void				ObserveEvent( const idMenuWidget& widget, const idWidgetEvent& event );
	virtual void				Scroll( const int scrollIndexAmount, const bool wrapAround = false );
	virtual void				ScrollOffset( const int scrollIndexAmount );
	virtual int					GetTotalNumberOfOptions() const
	{
		return GetChildren().Num();
	}
	virtual bool				PrepareListElement( idMenuWidget& widget, const int childIndex )
	{
		return true;
	}

	bool						IsWrappingAllowed() const
	{
		return allowWrapping;
	}
	void						SetWrappingAllowed( const bool allow )
	{
		allowWrapping = allow;
	}

	void						SetNumVisibleOptions( const int numVisibleOptions_ )
	{
		numVisibleOptions = numVisibleOptions_;
	}
	int							GetNumVisibleOptions() const
	{
		return numVisibleOptions;
	}

	int							GetViewOffset() const
	{
		return viewOffset;
	}
	void						SetViewOffset( const int offset )
	{
		viewOffset = offset;
	}

	int							GetViewIndex() const
	{
		return viewIndex;
	}
	void						SetViewIndex( const int index )
	{
		viewIndex = index;
	}

	void						CalculatePositionFromIndexDelta( int& outIndex, int& outOffset, const int currentIndex, const int currentOffset, const int windowSize, const int maxSize, const int indexDelta, const bool allowWrapping, const bool wrapAround = false ) const;
	void						CalculatePositionFromOffsetDelta( int& outIndex, int& outOffset, const int currentIndex, const int currentOffset, const int windowSize, const int maxSize, const int offsetDelta ) const;

private:
	int							numVisibleOptions;
	int							viewOffset;
	int							viewIndex;
	bool						allowWrapping;
};

class idBrowserEntry_t
{
public:
	idStr serverName;
	int	index;
	int players;
	int maxPlayers;
	bool joinable;
	bool validMap;
	idStrId mapName;
	idStr modeName;
};

/*
================================================
idMenuWidget_GameBrowserList
================================================
*/
class idMenuWidget_GameBrowserList : public idMenuWidget_List
{
public:
	virtual void				Update();
	virtual bool				PrepareListElement( idMenuWidget& widget, const int childIndex );
	virtual int					GetTotalNumberOfOptions() const;
	void						ClearGames();
	void						AddGame( idStr name_, idStrId mapName_, idStr modeName_, int index_ = 0, int players_ = 0, int maxPlayers_ = 0, bool joinable_ = false, bool validMap_ = false );
	int							GetServerIndex();
private:
	idList< idBrowserEntry_t >	games;
};

/*
================================================
idMenuWidget_Carousel
Displays a list of items in a looping carousel pattern
================================================
*/
class idMenuWidget_Carousel : public idMenuWidget
{
public:

	idMenuWidget_Carousel() :
		numVisibleOptions( 0 ),
		viewIndex( 0 ),
		moveToIndex( 0 ),
		moveDiff( 0 ),
		fastScroll( false ),
		scrollLeft( false )
	{
	}

	virtual void				Initialize( idMenuHandler* data );
	virtual void				Update();
	virtual bool				HandleAction( idWidgetAction& action, const idWidgetEvent& event, idMenuWidget* widget, bool forceHandled = false );
	virtual int					GetTotalNumberOfOptions() const
	{
		return imgList.Num();
	}
	virtual bool				PrepareListElement( idMenuWidget& widget, const int childIndex )
	{
		return true;
	}

	void						SetNumVisibleOptions( const int numVisibleOptions_ )
	{
		numVisibleOptions = numVisibleOptions_;
	}
	int							GetNumVisibleOptions() const
	{
		return numVisibleOptions;
	}

	void						MoveToIndex( int index, bool instant = false );
	void						MoveToFirstItem( bool instant = true );
	void						MoveToLastItem( bool instant = true );
	int							GetMoveToIndex()
	{
		return moveToIndex;
	}
	void						SetMoveToIndex( int index )
	{
		moveToIndex = index;
	}
	void						SetViewIndex( int index )
	{
		viewIndex = index;
	}
	int							GetViewIndex() const
	{
		return viewIndex;
	}
	void						SetListImages( idList<const idMaterial*>& list );
	void						SetMoveDiff( int val )
	{
		moveDiff = val;
	}
	int							GetMoveDiff()
	{
		return moveDiff;
	}
	bool						GetScrollLeft()
	{
		return scrollLeft;
	}

private:

	int							numVisibleOptions;
	int							viewIndex;
	int							moveToIndex;
	int							moveDiff;
	bool						fastScroll;
	bool						scrollLeft;
	idList<const idMaterial*>	imgList;

};

/*
================================================
idMenuWidget_Help

Knows how to display help tooltips by observing events from other widgets
================================================
*/
class idMenuWidget_Help : public idMenuWidget
{
public:
	virtual void Update();
	virtual void ObserveEvent( const idMenuWidget& widget, const idWidgetEvent& event );

private:
	idStr		lastFocusedMessage;		// message from last widget that had focus
	idStr		lastHoveredMessage;		// message from last widget that was hovered over
	bool		hideMessage;
};

/*
================================================
idMenuWidget_CommandBar
================================================
*/
class idMenuWidget_CommandBar : public idMenuWidget
{
public:
	enum button_t
	{
		BUTTON_JOY1,
		BUTTON_JOY2,
		BUTTON_JOY3,
		BUTTON_JOY4,
		BUTTON_JOY10,
		BUTTON_TAB,
		MAX_BUTTONS
	};

	enum alignment_t
	{
		LEFT,
		RIGHT
	};

	struct buttonInfo_t
	{
		idStr			label;			// empty labels are treated as hidden buttons
		idWidgetAction	action;
	};

	idMenuWidget_CommandBar() :
		alignment( LEFT )
	{

		buttons.SetNum( MAX_BUTTONS );
	}

	virtual void		Update();
	virtual bool		ExecuteEvent( const idWidgetEvent& event );

	buttonInfo_t* 		GetButton( const button_t button )
	{
		return &buttons[ button ];
	}
	void				ClearAllButtons();

	alignment_t			GetAlignment() const
	{
		return alignment;
	}
	void				SetAlignment( const alignment_t alignment_ )
	{
		alignment = alignment_;
	}

private:
	idStaticList< buttonInfo_t, MAX_BUTTONS >	buttons;
	alignment_t									alignment;
};

/*
================================================
	idMenuWidget_DynamicList
================================================
*/
class idMenuWidget_LobbyList : public idMenuWidget_List
{
public:
	idMenuWidget_LobbyList() :
		numEntries( 0 )
	{
	}

	virtual void				Update();
	virtual bool				PrepareListElement( idMenuWidget& widget, const int childIndex );
	virtual int					GetTotalNumberOfOptions() const
	{
		return numEntries;
	}
	void						SetEntryData( int index, idStr name, voiceStateDisplay_t voiceState );
	void						SetHeadingInfo( idList< idStr >& list );
	void						SetNumEntries( int num )
	{
		numEntries = num;
	}
	int							GetNumEntries()
	{
		return numEntries;
	}
	void						SetRefreshFunction( const char* func );
private:
	idList< idStr, TAG_IDLIB_LIST_MENU >	headings;
	int							numEntries;
};

/*
================================================
idMenuWidget_DynamicList
================================================
*/
class idMenuWidget_DynamicList : public idMenuWidget_List
{
public:

	idMenuWidget_DynamicList() :
		controlList( false ),
		ignoreColor( false )
	{
	}

	virtual void				Update();
	virtual void				Initialize( idMenuHandler* data );
	virtual int					GetTotalNumberOfOptions() const;
	virtual bool				PrepareListElement( idMenuWidget& widget, const int childIndex );

	virtual void				Recalculate();
	virtual void				SetListData( idList< idList< idStr, TAG_IDLIB_LIST_MENU >, TAG_IDLIB_LIST_MENU >& list );

	void						SetControlList( bool val )
	{
		controlList = val;
	}
	void						SetIgnoreColor( bool val )
	{
		ignoreColor = val;
	}

protected:
	idList< idList< idStr, TAG_IDLIB_LIST_MENU >, TAG_IDLIB_LIST_MENU >	listItemInfo;
	bool						controlList;
	bool						ignoreColor;
};

/*
================================================
idMenuWidget_ScoreboardList
================================================
*/
class idMenuWidget_ScoreboardList : public idMenuWidget_DynamicList
{
public:
	virtual void				Update();
	virtual int				GetTotalNumberOfOptions() const;
};

/*
================================================
idMenuWidget_NavBar

The nav bar is set up with the main option being at the safe frame line.
================================================
*/
class idMenuWidget_NavBar : public idMenuWidget_DynamicList
{
public:

	idMenuWidget_NavBar() :
		initialPos( 0.0f ),
		buttonPos( 0.0f ),
		leftSpacer( 0.0f ),
		rightSpacer( 0.0f ),
		selectedSpacer( 0.0f )
	{
	}

	virtual void				Update();
	virtual void				Initialize( idMenuHandler* data );
	virtual void				SetInitialXPos( float pos )
	{
		initialPos = pos;
	}
	virtual void				SetButtonSpacing( float lSpace, float rSpace, float sSpace )
	{
		leftSpacer = lSpace;
		rightSpacer = rSpace;
		selectedSpacer = sSpace;
	}
	virtual bool				PrepareListElement( idMenuWidget& widget, const int navIndex );
	virtual void				SetListHeadings( idList< idStr >& list );
	virtual int					GetTotalNumberOfOptions() const;

private:

	idList< idStr, TAG_IDLIB_LIST_MENU >		headings;
	float				initialPos;
	float				buttonPos;
	float				leftSpacer;
	float				rightSpacer;
	float				selectedSpacer;

};

/*
================================================
idMenuWidget_NavBar

The nav bar is set up with the main option being at the safe frame line.
================================================
*/
class idMenuWidget_MenuBar : public idMenuWidget_DynamicList
{
public:

	idMenuWidget_MenuBar() :
		totalWidth( 0.0f ),
		buttonPos( 0.0f ),
		rightSpacer( 0.0f )
	{
	}

	virtual void				Update();
	virtual void				Initialize( idMenuHandler* data );
	virtual void				SetButtonSpacing( float rSpace )
	{
		rightSpacer = rSpace;
	}
	virtual bool				PrepareListElement( idMenuWidget& widget, const int navIndex );
	virtual void				SetListHeadings( idList< idStr >& list );
	virtual int					GetTotalNumberOfOptions() const;

private:

	idList< idStr, TAG_IDLIB_LIST_MENU >				headings;
	float						totalWidth;
	float						buttonPos;
	float						rightSpacer;

};

/*
================================================
idMenuWidget_PDA_UserData
================================================
*/
class idMenuWidget_PDA_UserData : public idMenuWidget
{
public:
	idMenuWidget_PDA_UserData() :
		pdaIndex( 0 )
	{
	}
	virtual ~idMenuWidget_PDA_UserData() {}
	virtual void	Update();
	virtual void	ObserveEvent( const idMenuWidget& widget, const idWidgetEvent& event );

private:
	int		pdaIndex;
};

/*
================================================
idMenuWidget_DynamicList
================================================
*/
class idMenuWidget_ScrollBar : public idMenuWidget
{
public:
	idMenuWidget_ScrollBar() :
		yTop( 0.0f ),
		yBot( 0.0f ),
		dragging( false )
	{
	}

	virtual void	Initialize( idMenuHandler* data );
	virtual void	Update();
	virtual bool	HandleAction( idWidgetAction& action, const idWidgetEvent& event, idMenuWidget* widget, bool forceHandled = false );
	virtual void	ObserveEvent( const idMenuWidget& widget, const idWidgetEvent& event );

	void			CalcTopAndBottom();
	void			CalculatePosition( float x, float y );

	float			yTop;
	float			yBot;
	bool			dragging;
};

/*
================================================
idMenuWidget_InfoBox
================================================
*/
class idMenuWidget_InfoBox: public idMenuWidget
{
public:
	idMenuWidget_InfoBox() :
		scrollbar( NULL )
	{
	}

	virtual void	Initialize( idMenuHandler* data );
	virtual void	Update();
	virtual bool	HandleAction( idWidgetAction& action, const idWidgetEvent& event, idMenuWidget* widget, bool forceHandled = false );
	virtual void	ObserveEvent( const idMenuWidget& widget, const idWidgetEvent& event );
	void			SetHeading( idStr val )
	{
		heading = val;
	}
	void			SetBody( idStr val )
	{
		info = val;
	}
	void			ResetInfoScroll();
	void			Scroll( int d );
	int				GetScroll();
	int				GetMaxScroll();
	void			SetScroll( int scroll );
	void			SetScrollbar( idMenuWidget_ScrollBar* bar );
private:
	idMenuWidget_ScrollBar* 	scrollbar;
	idStr						heading;
	idStr						info;
};

/*
================================================
idMenuWidget_PDA_Objective
================================================
*/
class idMenuWidget_PDA_Objective: public idMenuWidget
{
public:
	idMenuWidget_PDA_Objective() :
		pdaIndex( 0 )
	{
	}
	virtual void	Update();
	virtual void	ObserveEvent( const idMenuWidget& widget, const idWidgetEvent& event );
private:
	int		pdaIndex;
};

/*
================================================
idMenuWidget_Shell_SaveInfo
================================================
*/
class idMenuWidget_Shell_SaveInfo: public idMenuWidget
{
public:
	idMenuWidget_Shell_SaveInfo() :
		loadIndex( 0 ),
		forSaveScreen( false )
	{
	}
	virtual void	Update();
	virtual void	ObserveEvent( const idMenuWidget& widget, const idWidgetEvent& event );

	void			SetForSaveScreen( bool val )
	{
		forSaveScreen = val;
	}
private:
	int		loadIndex;
	bool	forSaveScreen;
};

/*
================================================
idMenuWidget_PDA_AudioFiles
================================================
*/
class idMenuWidget_PDA_AudioFiles: public idMenuWidget
{
public:
	idMenuWidget_PDA_AudioFiles() :
		pdaIndex( 0 )
	{
	}
	virtual ~idMenuWidget_PDA_AudioFiles();
	virtual void	Update();
	virtual void	Initialize( idMenuHandler* data );
	virtual void	ObserveEvent( const idMenuWidget& widget, const idWidgetEvent& event );
private:
	int								pdaIndex;
	idList< idList< idStr, TAG_IDLIB_LIST_MENU >, TAG_IDLIB_LIST_MENU >		audioFileNames;
};

/*
================================================
idMenuWidget_PDA_AudioFiles
================================================
*/
class idMenuWidget_PDA_EmailInbox: public idMenuWidget
{
public:
	idMenuWidget_PDA_EmailInbox() :
		emailList( NULL ),
		scrollbar( NULL ),
		pdaIndex( 0 )
	{
	}
	virtual void	Update();
	virtual void	Initialize( idMenuHandler* data );
	virtual void	ObserveEvent( const idMenuWidget& widget, const idWidgetEvent& event );

	idMenuWidget_DynamicList* 	GetEmailList()
	{
		return emailList;
	}
	idMenuWidget_ScrollBar* 	GetScrollbar()
	{
		return scrollbar;
	}
private:
	idMenuWidget_DynamicList* 		emailList;
	idMenuWidget_ScrollBar* 		scrollbar;
	int								pdaIndex;
	idList< idList< idStr, TAG_IDLIB_LIST_MENU >, TAG_IDLIB_LIST_MENU >		emailInfo;
};

/*
================================================
	idMenuWidget_PDA_AudioFiles
================================================
*/
class idMenuWidget_ItemAssignment: public idMenuWidget
{
public:
	idMenuWidget_ItemAssignment() :
		slotIndex( 0 )
	{
	}

	virtual void	Update();
	void			SetIcon( int index, const idMaterial* icon );
	void			FindFreeSpot();
	int				GetSlotIndex()
	{
		return slotIndex;
	}
	void			SetSlotIndex( int num )
	{
		slotIndex = num;
	}
private:
	const idMaterial* images[ NUM_QUICK_SLOTS ];
	int				slotIndex;

};


/*
================================================
idMenuWidget_PDA_AudioFiles
================================================
*/
class idMenuWidget_PDA_VideoInfo: public idMenuWidget
{
public:
	virtual void	Update();
	virtual void	ObserveEvent( const idMenuWidget& widget, const idWidgetEvent& event );
private:
	int				videoIndex;
};


/*
================================================
idWidgetActionHandler
================================================
*/
class idWidgetActionHandler : public idSWFScriptFunction_RefCounted
{
public:
	idWidgetActionHandler( idMenuWidget* widget, actionHandler_t actionEventType, widgetEvent_t _event ) :
		targetWidget( widget ),
		type( actionEventType ),
		targetEvent( _event )
	{

	}

	idSWFScriptVar Call( idSWFScriptObject* thisObject, const idSWFParmList& parms )
	{

		idWidgetAction action;
		bool handled = false;
		switch( type )
		{
			case WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER:
			{
				action.Set( ( widgetAction_t )WIDGET_ACTION_START_REPEATER, WIDGET_ACTION_SCROLL_VERTICAL, 1 );
				handled = true;
				break;
			}
			case WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER:
			{
				action.Set( ( widgetAction_t )WIDGET_ACTION_START_REPEATER, WIDGET_ACTION_SCROLL_VERTICAL, -1 );
				handled = true;
				break;
			}
			case WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER_VARIABLE:
			{
				action.Set( ( widgetAction_t )WIDGET_ACTION_START_REPEATER, WIDGET_ACTION_SCROLL_VERTICAL_VARIABLE, 1 );
				handled = true;
				break;
			}
			case WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER_VARIABLE:
			{
				action.Set( ( widgetAction_t )WIDGET_ACTION_START_REPEATER, WIDGET_ACTION_SCROLL_VERTICAL_VARIABLE, -1 );
				handled = true;
				break;
			}
			case WIDGET_ACTION_EVENT_SCROLL_PAGE_DOWN_START_REPEATER:
			{
				action.Set( ( widgetAction_t )WIDGET_ACTION_START_REPEATER, WIDGET_ACTION_SCROLL_PAGE, 1 );
				handled = true;
				break;
			}
			case WIDGET_ACTION_EVENT_SCROLL_PAGE_UP_START_REPEATER:
			{
				action.Set( ( widgetAction_t )WIDGET_ACTION_START_REPEATER, WIDGET_ACTION_SCROLL_PAGE, -1 );
				handled = true;
				break;
			}
			case WIDGET_ACTION_EVENT_STOP_REPEATER:
			{
				action.Set( ( widgetAction_t )WIDGET_ACTION_STOP_REPEATER );
				handled = true;
				break;
			}
			case WIDGET_ACTION_EVENT_TAB_NEXT:
			{
				action.Set( ( widgetAction_t )WIDGET_ACTION_SCROLL_TAB, 1 );
				handled = true;
				break;
			}
			case WIDGET_ACTION_EVENT_TAB_PREV:
			{
				action.Set( ( widgetAction_t )WIDGET_ACTION_SCROLL_TAB, -1 );
				handled = true;
				break;
			}
			case WIDGET_ACTION_EVENT_JOY3_ON_PRESS:
			{
				action.Set( ( widgetAction_t )WIDGET_ACTION_JOY3_ON_PRESS );
				handled = true;
				break;
			}
			case WIDGET_ACTION_EVENT_SCROLL_LEFT_START_REPEATER:
			{
				action.Set( ( widgetAction_t )WIDGET_ACTION_START_REPEATER, WIDGET_ACTION_SCROLL_HORIZONTAL, -1 );
				handled = true;
				break;
			}
			case WIDGET_ACTION_EVENT_SCROLL_RIGHT_START_REPEATER:
			{
				action.Set( ( widgetAction_t )WIDGET_ACTION_START_REPEATER, WIDGET_ACTION_SCROLL_HORIZONTAL, 1 );
				handled = true;
				break;
			}
			case WIDGET_ACTION_EVENT_DRAG_START:
			{
				action.Set( ( widgetAction_t )WIDGET_ACTION_SCROLL_DRAG );
				handled = true;
				break;
			}
			case WIDGET_ACTION_EVENT_DRAG_STOP:
			{
				action.Set( ( widgetAction_t )WIDGET_ACTION_EVENT_DRAG_STOP );
				handled = true;
				break;
			}
		}

		if( handled )
		{
			targetWidget->HandleAction( action, idWidgetEvent( targetEvent, 0, thisObject, parms ), targetWidget );
		}

		return idSWFScriptVar();
	}

private:
	idMenuWidget* 	targetWidget;
	actionHandler_t type;
	widgetEvent_t targetEvent;
};

#endif
