/*
   Copyright (C) 1997-2001 Id Software, 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 2
   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, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

 */
#include <string.h>
#include <ctype.h>

#include "ui_local.h"

static void  Field_Init( menucommon_t *f );
static void  Action_DoEnter( menucommon_t *a );
static void  Action_Draw( menucommon_t *a );
static void  Menu_DrawStatusBar( char *string );
static void Menu_AdjustRectangle( int *mins, int *maxs );
static void  Separator_Draw( menucommon_t *s );
static void  Slider_DoSlide( menucommon_t *s, int dir );
static void  Slider_Draw( menucommon_t *s );
//static void	 SpinControl_DoEnter( menulist_s *s );
static void  SpinControl_Draw( menucommon_t *s );
static void  SpinControl_DoSlide( menucommon_t *s, int dir );
static void  Scrollbar_DoSlide( menucommon_t *s, int dir );
static void  Scrollbar_Draw( menucommon_t *s );

#define RCOLUMN_OFFSET  16
#define LCOLUMN_OFFSET -16

vec4_t colorWarsowOrange = { 0.95f, 0.33f, 0.1f, 1.0f };
vec4_t colorWarsowOrangeBright = { 1.0f, 0.6f, 0.1f, 1.0f };
vec4_t colorWarsowPurpleBright = { 0.66f, 0.66f, 1.0f, 1.0f };
vec4_t colorWarsowPurple = { 0.3843f, 0.2902f, 0.5843f, 1.0f };

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

   STRINGS DRAWING

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

//=============
//UI_FillRect
//=============
void UI_FillRect( int x, int y, int w, int h, vec4_t color )
{
	trap_R_DrawStretchPic( x, y, w, h, 0, 0, 1, 1, color, uis.whiteShader );
}

//=============
//UI_StringWidth
//=============
int UI_StringWidth( char *s, struct mufont_s *font )
{
	if( !font )
		font = uis.fontSystemSmall;
	return trap_SCR_strWidth( s, font, 0 );
}

//=============
//UI_StringHeight
//=============
int UI_StringHeight( struct mufont_s *font )
{
	if( !font )
		font = uis.fontSystemSmall;
	return trap_SCR_strHeight( font );
}

//=============
//UISCR_HorizontalAlignOffset
//=============
static int UISCR_HorizontalAlignOffset( int align, int width )
{
	int nx = 0;

	if( align % 3 == 0 )  // left
		nx = 0;
	if( align % 3 == 1 )  // center
		nx = -( width / 2 );
	if( align % 3 == 2 )  // right
		nx = -width;

	return nx;
}

//=============
//UISCR_VerticalAlignOffset
//=============
static int UISCR_VerticalAlignOffset( int align, int height )
{
	int ny = 0;

	if( align / 3 == 0 )  // top
		ny = 0;
	else if( align / 3 == 1 )  // middle
		ny = -( height / 2 );
	else if( align / 3 == 2 )  // bottom
		ny = -height;

	return ny;
}

//=============
//UI_DrawStringHigh
//This string is highlighted by the mouse
//=============
void UI_DrawStringHigh( int x, int y, int align, const char *str, int maxwidth, struct mufont_s *font, vec4_t color )
{
	int shadowoffset = 1;
	if( !font )
		font = uis.fontSystemSmall;

	shadowoffset += ( trap_SCR_strHeight( font ) >= trap_SCR_strHeight( uis.fontSystemBig ) );

	if( maxwidth > 0 )
	{
		trap_SCR_DrawStringWidth( x+shadowoffset, y+shadowoffset, align, COM_RemoveColorTokens( str ), maxwidth, font, colorBlack );
		trap_SCR_DrawStringWidth( x, y, align, COM_RemoveColorTokens( str ), maxwidth, font, UI_COLOR_HIGHLIGHT );
	}
	else
	{
		trap_SCR_DrawString( x+shadowoffset, y+shadowoffset, align, str, font, colorBlack );
		trap_SCR_DrawString( x, y, align, str, font, UI_COLOR_HIGHLIGHT );
	}
}

//=============
//UI_DrawString
//=============
void UI_DrawString( int x, int y, int align, const char *str, int maxwidth, struct mufont_s *font, vec4_t color )
{
	if( !font )
		font = uis.fontSystemSmall;

	if( maxwidth > 0 )
		trap_SCR_DrawStringWidth( x, y, align, str, maxwidth, font, color );
	else
		trap_SCR_DrawString( x, y, align, str, font, color );
}



//=================================================================
//
//		MENUITEMS
//
//=================================================================

//=================================================================
// ACTION
//=================================================================

static void Action_UpdateBox( menucommon_t *a )
{
	a->mins[0] = a->x + a->parent->x + UISCR_HorizontalAlignOffset( a->align, UI_StringWidth( a->title, a->font ) );
	if( a->width > 0 )
	{
		a->maxs[0] = a->mins[0] + a->width;
	}
	else
	{
		a->maxs[0] = a->mins[0] + UI_StringWidth( a->title, a->font );
	}

	a->mins[1] = a->y + a->parent->y + UISCR_VerticalAlignOffset( a->align, UI_StringHeight( a->font ) );
	if( a->height > 0 )
	{
		a->maxs[1] = a->mins[1] + a->height;
	}
	else
	{
		a->maxs[1] = a->mins[1] + UI_StringHeight( a->font );
	}
}

static void Action_Init( menucommon_t *a )
{
	Action_UpdateBox( a );
}

void Action_DoEnter( menucommon_t *menuitem )
{
	if( menuitem->disabled )
		return;

	if( menuitem->callback )
		menuitem->callback( menuitem );
}

void Action_Draw( menucommon_t *menuitem )
{
	int x, y;

	x = menuitem->x + menuitem->parent->x;
	y = menuitem->y + menuitem->parent->y;

	// update box for string size
	Action_UpdateBox( menuitem );

	if( menuitem->ownerdraw )
		menuitem->ownerdraw( menuitem );

	if( menuitem->disabled )
	{
		UI_DrawString( x, y, menuitem->align, menuitem->title, menuitem->width, menuitem->font, colorMdGrey );
	}
	else if( Menu_ItemAtCursor( menuitem->parent ) == menuitem )
	{
		UI_DrawStringHigh( x, y, menuitem->align, menuitem->title, menuitem->width, menuitem->font, colorWhite );
	}
	else
	{
		UI_DrawString( x, y, menuitem->align, menuitem->title, menuitem->width, menuitem->font, UI_COLOR_LIVETEXT );
	}
}

//=================================================================
// FIELD
//=================================================================
static void Field_ResetCursor( menucommon_t *f )
{
	menufield_t *itemlocal;
	itemlocal = (menufield_t *)f->itemlocal;

	itemlocal->cursor = strlen( itemlocal->buffer );
	if( itemlocal->cursor > itemlocal->length )
		itemlocal->cursor = itemlocal->length;
}

static void Field_SetupBox( menucommon_t *f )
{
	menufield_t *itemlocal;
	itemlocal = (menufield_t *)f->itemlocal;

	f->mins[0] = f->x + f->parent->x + 16;
	f->maxs[0] = f->mins[0] + itemlocal->width;

	f->mins[1] = f->y + f->parent->y;
	f->maxs[1] = f->mins[1] + UI_StringHeight( f->font );
}

static void Field_Init( menucommon_t *f )
{
	Field_SetupBox( f );
}

static qboolean Field_DoEnter( menucommon_t *f )
{
	if( f->disabled )
		return qtrue;

	if( f->callback )
	{
		f->callback( f );
		return qtrue;
	}
	return qfalse;
}

static void Field_Draw( menucommon_t *f )
{
	int x, y;
	char tempbuffer[128] = "";
	menufield_t *itemlocal;

	int offset, xcursor;
	char *str;

	itemlocal = (menufield_t *)f->itemlocal;
	if( !itemlocal )
		return;



	Field_SetupBox( f ); // update box

	x = f->x + f->parent->x + LCOLUMN_OFFSET;
	y = f->y + f->parent->y;

	if( f->title[0] )
	{
		UI_DrawString( x, y, f->align, f->title, 0, f->font, UI_COLOR_DEADTEXT );
	}

	x = f->x + f->parent->x + 16;
	y = f->y + f->parent->y;

	{
		float color[4] = { 0.5, 0.5, 0.5, 0.5 };
		UI_FillRect( x, y, itemlocal->width, trap_SCR_strHeight( f->font ), color );
	}

	if( f->disabled )
		return;

	if( Menu_ItemAtCursor( f->parent ) == f )
	{

		Q_strncpyz( tempbuffer, itemlocal->buffer, sizeof( tempbuffer ) );

		// password protect fields, replace with '*'
		if( f->flags & F_PASSWORD )
		{
			str = tempbuffer;
			while( *str )
				*str++ = '*';
		}

		str = tempbuffer;
		while( *str && ( (int)trap_SCR_strWidth( str, f->font, 0 ) > itemlocal->width - 16 ) )
		{
			str++;
		}


		offset = str - tempbuffer;
		if( itemlocal->cursor < offset )
			Field_ResetCursor( f ); // force the cursor to be always at the end of the string

		UI_DrawString( x, y, ALIGN_LEFT_TOP, str, 0, f->font, UI_COLOR_LIVETEXT );
		//draw cursor
		if( ( int ) ( uis.time / 250 ) & 1 )
		{
			xcursor = trap_SCR_strWidth( str, f->font, itemlocal->cursor );
			UI_DrawString( x+xcursor, y, ALIGN_LEFT_TOP, "_", 0, f->font, UI_COLOR_LIVETEXT );
		}
	}
	else
	{
		x = f->x + f->parent->x + 16;
		y = f->y + f->parent->y;

		Q_strncpyz( tempbuffer, itemlocal->buffer, sizeof( tempbuffer ) );
		// password protect fields, replace with '*'
		if( f->flags & F_PASSWORD )
		{
			str = tempbuffer;
			while( *str )
				*str++ = '*';
		}
		UI_DrawString( x, y, ALIGN_LEFT_TOP, tempbuffer, 0, f->font, colorLtGrey );
	}
}

qboolean Field_Key( menucommon_t *f, int key )
{
	menufield_t *itemlocal;
	itemlocal = (menufield_t *)f->itemlocal;
	if( !itemlocal )
		return qfalse;

	if( f->disabled )
		return qfalse;

	/*
	** support pasting from the clipboard
	*/
	if( ( toupper( key ) == 'V' && trap_Key_IsDown( K_CTRL ) ) ||
	   ( ( ( key == K_INS ) || ( key == KP_INS ) ) && trap_Key_IsDown( K_SHIFT ) ) )
	{
		char *cbd, *p;

		cbd = trap_CL_GetClipboardData( (qboolean)( key == K_INS || key == KP_INS ) );

		if( cbd )
		{
			p = strpbrk( cbd, "\n\r\b" );
			if( p )
				*p = 0;

			Q_strncpyz( itemlocal->buffer, cbd, sizeof( itemlocal->buffer ) );
			Field_ResetCursor( f );
			//Field_DoEnter( f ); // exec callback

			trap_CL_FreeClipboardData( cbd );
		}
		return qtrue;
	}

	switch( key )
	{
	case K_LEFTARROW:
	case K_BACKSPACE:
		if( itemlocal->cursor > 0 )
		{
			memmove( &itemlocal->buffer[itemlocal->cursor-1], &itemlocal->buffer[itemlocal->cursor], strlen( &itemlocal->buffer[itemlocal->cursor] ) + 1 );
			Field_ResetCursor( f );
			//Field_DoEnter( f ); // exec callback
		}
		return qtrue;

	case KP_DEL:
	case K_DEL:
	{
		memmove( &itemlocal->buffer[itemlocal->cursor], &itemlocal->buffer[itemlocal->cursor+1], strlen( &itemlocal->buffer[itemlocal->cursor+1] ) + 1 );
		Field_ResetCursor( f );
		//Field_DoEnter( f ); // exec callback
	}
		return qtrue;

	case KP_HOME:
	case KP_UPARROW:
	case KP_PGUP:
	case KP_LEFTARROW:
	case KP_5:
	case KP_RIGHTARROW:
	case KP_END:
	case KP_DOWNARROW:
	case KP_PGDN:
		return qtrue; // ignore keypad stuff, so it can be used for writing numbers (little ugly)
	}

	return qfalse;
}

qboolean Field_CharEvent( menucommon_t *f, qwchar key )
{
	menufield_t *itemlocal;
	itemlocal = (menufield_t *)f->itemlocal;
	if( !itemlocal )
		return qfalse;

	if( f->disabled )
		return qfalse;

	if( key < 32 || key > 126 )
		return qfalse; // non printable

	// jalfixme: reimplement numbers only option?
	//if ( !isdigit( key ) && ( f->flags & QMF_NUMBERSONLY ) )
	//	return qfalse;
	if( !isdigit( key ) && f->flags & F_NUMBERSONLY )
		return qfalse;

	if( itemlocal->cursor < itemlocal->length )
	{
		itemlocal->buffer[itemlocal->cursor++] = key;
		itemlocal->buffer[itemlocal->cursor] = 0;
		Field_SetupBox( f );
		//Field_DoEnter( f ); // exec callback
	}

	return qtrue;
}

//=================================================================
// SEPARATOR
//=================================================================
static void Separator_Init( menucommon_t *s )
{
}

void Separator_Draw( menucommon_t *s )
{
	int x, y;

	if( s->ownerdraw )
		s->ownerdraw( s );

	if( !s->title[0] )
	{
		return;
	}

	x = s->x + s->parent->x;
	y = s->y + s->parent->y;

	UI_DrawString( x, y, s->align, s->title, s->width, s->font, UI_COLOR_DEADTEXT );
}

//=================================================================
// SLIDER
//=================================================================
#define SCROLLBAR_PIC_SIZE 16

static void Slider_Init( menucommon_t *s )
{
	s->align = ALIGN_LEFT_TOP;

	s->mins[0] = s->x + s->parent->x + RCOLUMN_OFFSET;
	s->maxs[0] = s->mins[0] + s->width * SCROLLBAR_PIC_SIZE + SCROLLBAR_PIC_SIZE;

	s->mins[1] = s->y + s->parent->y;
	s->maxs[1] = s->mins[1] + SCROLLBAR_PIC_SIZE;
}

void Slider_Draw( menucommon_t *s )
{
	int i, x, y;
	vec4_t buttoncolor, color;
	int cursorxpos;

	Slider_Init( s ); //update box size

	x = s->x + s->parent->x;
	y = s->y + s->parent->y;

	//draw slider title, if any
	if( s->title[0] )
		UI_DrawString( x + LCOLUMN_OFFSET, y, ALIGN_RIGHT_TOP, s->title, 0, s->font, UI_COLOR_DEADTEXT );

	//The title is offset LCOLUMN_OFFSET, then everything else is offset RCOLUMN_OFFSET
	x += RCOLUMN_OFFSET; //move the whole thing to the right 16 units to right-align

	if( s->maxvalue > s->minvalue )
	{
		s->range = (float)( s->curvalue - s->minvalue ); // reserve space for the buttons
		s->range /= (float)( s->maxvalue - s->minvalue );
		clamp( s->range, 0, 1 );
	}
	else
		s->range = 0;

	if( s->disabled )
		Vector4Copy( colorMdGrey, color );
	else
		Vector4Copy( colorWhite, color );

	trap_R_DrawStretchPic( x, y, SCROLLBAR_PIC_SIZE, SCROLLBAR_PIC_SIZE, 0, 0, 1, 1,
	                      color, trap_R_RegisterPic( "gfx/ui/slidebar_1" ) );

	for( i = 1; i < s->width-1; i++ )
		trap_R_DrawStretchPic( x + i*SCROLLBAR_PIC_SIZE, y, SCROLLBAR_PIC_SIZE, SCROLLBAR_PIC_SIZE, 0, 0, 1, 1,
		                      color, trap_R_RegisterPic( "gfx/ui/slidebar_2" ) );

	trap_R_DrawStretchPic( x + s->width*SCROLLBAR_PIC_SIZE - SCROLLBAR_PIC_SIZE, y, SCROLLBAR_PIC_SIZE, SCROLLBAR_PIC_SIZE, 0, 0, 1, 1,
	                      color, trap_R_RegisterPic( "gfx/ui/slidebar_3" ) );

	if( s->disabled )
		Vector4Copy( colorMdGrey, buttoncolor );
	else if( Menu_ItemAtCursor( s->parent ) == s )
		Vector4Copy( UI_COLOR_HIGHLIGHT, buttoncolor );
	else
		Vector4Copy( colorWhite, buttoncolor );

	cursorxpos = x + SCROLLBAR_PIC_SIZE // start with offset for the button
	             + ( ( s->width-3 ) * SCROLLBAR_PIC_SIZE * s->range );

	trap_R_DrawStretchPic( cursorxpos, y, SCROLLBAR_PIC_SIZE, SCROLLBAR_PIC_SIZE, 0, 0, 1, 1,
	                      buttoncolor, trap_R_RegisterPic( "gfx/ui/slidebar_4" ) );
}

void Slider_DoSlide( menucommon_t *s, int dir )
{
	int min, max;
	float newrange;
	float value;

	//empty or erroneous
	if( s->width <= 0 )
		return;

	if( s->disabled )
		return;

	//offset 16 to match spincontrol alignment
	min = s->x + s->parent->x + SCROLLBAR_PIC_SIZE + RCOLUMN_OFFSET;
	max = min + ( ( s->width-1 ) * SCROLLBAR_PIC_SIZE ) - SCROLLBAR_PIC_SIZE;
	//empty or erroneous
	if( max < min )
		UI_Error( "Invalid slidebar range: 'min < max'" );

	// see if the mouse is touching the step buttons
	if( uis.cursorX < min || dir < 0 )
	{
		s->curvalue--;
	}
	else if( uis.cursorX > max || dir > 0 )
	{
		s->curvalue++;
	}
	else
	{
		newrange = uis.cursorX - min;
		newrange /= (float)( max - min );

		clamp( newrange, 0.0, 1.0 );

		value = newrange * (float)( s->maxvalue - s->minvalue ) + s->minvalue;
		if( value - (int)value > 0.5 )
			value = (int)value + 1;
		else
			value = (int)value;

		s->curvalue = (int)value;
	}

	if( s->curvalue > s->maxvalue )
		s->curvalue = s->maxvalue;
	else if( s->curvalue < s->minvalue )
		s->curvalue = s->minvalue;

	if( s->callback )
		s->callback( s );

}

//=================================================================
// SCROLLBAR
//=================================================================

static void Scrollbar_Init( menucommon_t *s )
{
	int y_size;

	y_size = trap_SCR_strHeight( s->font ); //y_size is used to stretch the graphic to the current font size's height

	s->align = ALIGN_LEFT_TOP;

	s->mins[0] = s->x + s->parent->x;
	s->maxs[0] = s->mins[0] + SCROLLBAR_PIC_SIZE;

	s->mins[1] = s->y + s->parent->y;
	s->maxs[1] = s->mins[1] + s->height * y_size;
}

void Scrollbar_DoSlide( menucommon_t *s, int dir )
{
	int min, max, y_size, active_scroll, cv_pos;

	//This is pretty ugly, but it is all correct, modify with caution
	y_size = trap_SCR_strHeight( s->font ); //y_size is used to stretch the graphic to the current font size's height
	active_scroll = s->maxvalue < 1 ? ( s->height - 2 ) * y_size : ( ( s->height - 2 ) * y_size ) * ( (float)( s->height - 2 ) / (float)( s->height - 2 + s->maxvalue ) );
	cv_pos = s->y + s->parent->y + y_size // start with offset for the button
	         + s->range * ( ( s->height - 2 ) * y_size - active_scroll ); //limit the range to the space that "active area" isn't using

	//empty or erroneous
	if( s->height <= 0 )
		return;

	min = s->y + s->parent->y + y_size; // extra pics reserved for the buttons
	max = min + ( ( s->height-1 ) * y_size ) - y_size;

	//empty or erroneous
	if( max < min )
		UI_Error( "Invalid scrollbar range: 'min < max'" );

	if( dir < 3 && dir > -3 && Menu_ItemAtCursor( s->parent ) == s && ( uis.cursorY > min && uis.cursorY < max ) )
	{
		if( uis.cursorY < cv_pos )
			s->curvalue = s->curvalue - s->height + 1; //first line gets moved to the last
		else if( uis.cursorY > cv_pos + active_scroll )
			s->curvalue = s->curvalue + s->height - 1; //last line gets moved to the first
	}
	else
	{
		if( uis.cursorY < min && Menu_ItemAtCursor( s->parent ) == s && dir == 1 )
			dir = -1;
		s->curvalue += dir;
	}

	if( s->curvalue > s->maxvalue )
		s->curvalue = s->maxvalue;
	else if( s->curvalue < s->minvalue )
		s->curvalue = s->minvalue;

	if( s->callback )
		s->callback( s );
}

void Scrollbar_Draw( menucommon_t *s )
{
	int i, x, y, y_size, active_scroll, cv_pos;
	vec4_t buttoncolor;

	Scrollbar_Init( s ); //update box size

	x = s->x + s->parent->x;
	y = s->y + s->parent->y;
	y_size = trap_SCR_strHeight( s->font ); //y_size is used to stretch the graphic to the current font size's height

	//scrollbar title is for identification only, never draw it
	s->curvalue = trap_Cvar_Value( s->title );

	if( s->maxvalue > s->minvalue )
	{
		s->range = (float)( s->curvalue - s->minvalue ); // reserve space for the buttons
		s->range /= (float)( s->maxvalue - s->minvalue );
		clamp( s->range, 0, 1 );
	}
	else
		s->range = 0;

	trap_R_DrawStretchPic( x, y, SCROLLBAR_PIC_SIZE, y_size, 0, 0, 1, 1,
	                      colorWhite, trap_R_RegisterPic( "gfx/ui/scrollbar_1" ) );

	for( i = 1; i < s->height-1; i += 1 )
		trap_R_DrawStretchPic( x, y + i*y_size, SCROLLBAR_PIC_SIZE, y_size, 0, 0, 1, 1,
		                      colorWhite, trap_R_RegisterPic( "gfx/ui/scrollbar_2" ) );

	trap_R_DrawStretchPic( x, y + s->height*y_size - y_size, SCROLLBAR_PIC_SIZE, y_size, 0, 0, 1, 1,
	                      colorWhite, trap_R_RegisterPic( "gfx/ui/scrollbar_3" ) );

	if( Menu_ItemAtCursor( s->parent ) == s )
	{
		Vector4Copy( UI_COLOR_HIGHLIGHT, buttoncolor );
	}
	else
	{
		Vector4Copy( colorWhite, buttoncolor );
	}

	//This is pretty ugly, but it is all correct, modify with caution
	active_scroll = s->maxvalue < 1 ? ( s->height - 2 ) * y_size : ( ( s->height - 2 ) * y_size ) * ( (float)( s->height - 2 ) / (float)( s->height - 2 + s->maxvalue ) );
	cv_pos = s->y + s->parent->y + y_size // start with offset for the button
	         + s->range * ( ( s->height - 2 ) * y_size - active_scroll ); //limit the range to the space that "active area" isn't using

	trap_R_DrawStretchPic( x, cv_pos, SCROLLBAR_PIC_SIZE, active_scroll, 0, 0, 1, 1,
	                      buttoncolor, trap_R_RegisterPic( "gfx/ui/scrollbar_4" ) );

	if( s->callback )
		s->callback( s );
}

//=================================================================
// SPINCONTROL
//=================================================================
static void SpinControl_Init( menucommon_t *s )
{
	char buffer[100] = { 0 };
	int ysize, xsize, spacing, len;
	char **n;

	//set the curvalue of all non-selected sorts to 1 so the first click sorts down
	if( s->sort_active && s->sort_type )
	{
		int i;
		if( s->sort_active == s->sort_type )
		{
			for( i = 0; i < s->parent->nitems; i++ )
			{
				if( s->parent->items[i]->sort_active )  // only do this against spincontrols that are using sorting
					s->parent->items[i]->sort_active = s->sort_type;
				if( s->parent->items[i]->sort_active && s->parent->items[i]->sort_type != s->sort_type )
					s->parent->items[i]->curvalue = 1; // always make the first sort click switch to sort down
			}
		}
	}

	n = s->itemnames;

	if( !n )
		return;

	if( s->title[0] != 0 )
		s->mins[0] = s->x + s->parent->x + RCOLUMN_OFFSET;
	else
		s->mins[0] = s->x + s->parent->x;

	s->mins[1] = s->y + s->parent->y;

	ysize = UI_StringHeight( s->font );
	spacing = UI_StringHeight( s->font );

	xsize = 0;

	while( *n )
	{
		if( !strchr( *n, '\n' ) )
		{
			len = UI_StringWidth( *n, s->font );
			xsize = max( xsize, len );
		}
		else
		{

			Q_strncpyz( buffer, *n, sizeof( buffer ) );
			*strchr( buffer, '\n' ) = 0;
			len = UI_StringWidth( buffer, s->font );
			xsize = max( xsize, len );

			ysize = ysize + spacing;
			Q_strncpyz( buffer, strchr( *n, '\n' ) + 1, sizeof( buffer ) );
			len = UI_StringWidth( buffer, s->font );
			xsize = max( xsize, len );
		}

		n++;
	}

	if( s->align == ALIGN_CENTER_BOTTOM || s->align == ALIGN_CENTER_TOP || s->align == ALIGN_CENTER_MIDDLE )
		s->mins[0] -= xsize / 2;

	s->maxs[0] = s->mins[0] + xsize;
	s->maxs[1] = s->mins[1] + ysize;

	if( s->title != 0 )
	{                  //sort spincontrols have graphics which can be clicked on
		s->maxs[0] += s->pict.width * 2; //*2 to account for the space between the pict and the text
	}
}

/*
   void SpinControl_DoEnter( menulist_s *s )
   {
   s->curvalue++;
   if ( s->itemnames[s->curvalue] == 0 )
   s->curvalue = 0;

   if ( s->generic.callback )
   s->generic.callback( s );
   }
 */

void SpinControl_DoSlide( menucommon_t *s, int dir )
{
	if( s->disabled )
		return;

	s->curvalue += dir;

	if( s->maxvalue == 0 )
		s->curvalue = 0;
	else if( s->curvalue < 0 )
		s->curvalue = s->maxvalue;
	else if( s->itemnames[s->curvalue] == 0 )
		s->curvalue = 0;

	//set the curvalue of all non-selected sorts to 1 so the first click sorts down
	if( s->sort_active && s->sort_type )
	{
		int i;
		s->sort_active = s->sort_type;
		for( i = 0; i < s->parent->nitems; i++ )
		{
			if( s->parent->items[i]->sort_active )  // only do this against spincontrols that are using sorting
				s->parent->items[i]->sort_active = s->sort_type;
			if( s->parent->items[i]->sort_active && s->parent->items[i]->sort_type != s->sort_type )
				s->parent->items[i]->curvalue = 1; // always make the first sort click switch to sort down
		}
	}

	if( s->callback )
		s->callback( s );
}

void SpinControl_Draw( menucommon_t *s )
{
	char buffer[100];
	int x, y;
	qboolean font_highlighted = qfalse;

	SpinControl_Init( s ); //update box size

	x = s->x + s->parent->x;
	y = s->y + s->parent->y;

	if( s->title[0] )
	{               //only offset it if there is a title, allows for sorting
		x = s->x + s->parent->x + LCOLUMN_OFFSET;
		UI_DrawString( x, y, s->align, s->title, 0, s->font, UI_COLOR_DEADTEXT );
		x = s->x + s->parent->x + RCOLUMN_OFFSET;
	}

	if( Menu_ItemAtCursor( s->parent ) == s )
		font_highlighted = qtrue;

	if( !strchr( s->itemnames[s->curvalue], '\n' ) )
	{
		//start with sort spincontrols
		if( s->sort_active && s->sort_active == s->sort_type )
		{
			Vector4Copy( UI_COLOR_DEADTEXT, s->pict.color );
			Vector4Copy( UI_COLOR_HIGHLIGHT, s->pict.colorHigh );
			s->pict.yoffset = 2;
			s->pict.width = s->pict.height = 10;
			if( s->curvalue == 0 )
				s->pict.shader = s->pict.shaderHigh = trap_R_RegisterPic( "gfx/ui/arrow_down" );
			else
				s->pict.shader = s->pict.shaderHigh = trap_R_RegisterPic( "gfx/ui/arrow_up" );
		}
		else if( s->sort_active )
			s->pict.shader = s->pict.shaderHigh = NULL;

		if( s->disabled )
			UI_DrawString( x, y, ALIGN_LEFT_TOP, ( char *)s->itemnames[s->curvalue], 0, s->font, colorMdGrey );
		else if( font_highlighted )
			UI_DrawStringHigh( x, y, ALIGN_LEFT_TOP, ( char *)s->itemnames[s->curvalue], 0, s->font, colorWhite );
		else if( s->sort_active && s->sort_active == s->sort_type )  //sort spingcontrols get special color if active
			UI_DrawString( x, y, ALIGN_LEFT_TOP, ( char *)s->itemnames[s->curvalue], 0, s->font, UI_COLOR_DEADTEXT );
		else
			UI_DrawString( x, y, ALIGN_LEFT_TOP, ( char *)s->itemnames[s->curvalue], 0, s->font, UI_COLOR_LIVETEXT );
	}
	else
	{
		Q_strncpyz( buffer, s->itemnames[s->curvalue], sizeof( buffer ) );
		*strchr( buffer, '\n' ) = 0;
		if( font_highlighted )
			UI_DrawStringHigh( x, y, s->align, buffer, 0, s->font, colorWhite );
		else
			UI_DrawString( x, y, s->align, buffer, 0, s->font, UI_COLOR_LIVETEXT );

		Q_strncpyz( buffer, strchr( s->itemnames[s->curvalue], '\n' ) + 1, sizeof( buffer ) );
		if( font_highlighted )
			UI_DrawStringHigh( x, y + UI_StringHeight( s->font ), ALIGN_LEFT_TOP, buffer, 0, s->font, colorWhite );
		else
			UI_DrawString( x, y + UI_StringHeight( s->font ), ALIGN_LEFT_TOP, buffer, 0, s->font, UI_COLOR_LIVETEXT );
	}
}

//=================================================================
//
// MENUITEM ASSOCIATED ELEMENTS DRAWING (Generic to all types)
//
//=================================================================
static void MenuItem_DrawPict( menucommon_t *menuitem )
{
	int x, y;
	x = menuitem->x + menuitem->parent->x;
	y = menuitem->y + menuitem->parent->y;

	x += menuitem->pict.xoffset;
	y += menuitem->pict.yoffset;

	if( Menu_ItemAtCursor( menuitem->parent ) == menuitem )
	{
		if( menuitem->pict.shaderHigh )
		{
			trap_R_DrawStretchPic( x, y, menuitem->pict.width, menuitem->pict.height, 0, 0, 1, 1,
			                       menuitem->pict.colorHigh, menuitem->pict.shaderHigh );

			return;
		}
	}

	if( menuitem->pict.shader )
	{
		trap_R_DrawStretchPic( x, y, menuitem->pict.width, menuitem->pict.height, 0, 0, 1, 1,
		                       menuitem->pict.color, menuitem->pict.shader );
	}
}

static void Menu_DrawWindowedBackground( menuframework_s *menu )
{
	// it works, but needs to be improved
	int i;
	vec2_t mins, maxs;
	static vec4_t colorback = { .0f, .0f, .0f, 0.6f };

	mins[0] = uis.vidWidth;
	mins[1] = uis.vidHeight;
	maxs[0] = maxs[1] = 0;
	for( i = 0; i < menu->nitems; i++ )
	{
		if( menu->items[i]->mins[0] && menu->items[i]->mins[0] < mins[0] )
			mins[0] = menu->items[i]->mins[0];
		if( menu->items[i]->mins[1] && menu->items[i]->mins[1] < mins[1] )
			mins[1] = menu->items[i]->mins[1];
		if( menu->items[i]->maxs[0] && menu->items[i]->maxs[0] > maxs[0] )
			maxs[0] = menu->items[i]->maxs[0];
		if( menu->items[i]->maxs[1] && menu->items[i]->maxs[1] > maxs[1] )
			maxs[1] = menu->items[i]->maxs[1];
	}
	//extend it
	mins[0] -= 16;
	mins[1] -= 16;
	maxs[0] += 16;
	maxs[1] += 16;

	// add a background to the menu
	//trap_R_DrawStretchPic( mins[0], mins[1], maxs[0]-mins[0], maxs[1]-mins[1],
	//	0, 0, 1, 1, colorWhite, trap_R_RegisterPic( "gfx/ui/novideoback") );

	trap_R_DrawStretchPic( 0, mins[1], uis.vidWidth, maxs[1]-mins[1],
	                       0, 0, 1, 1, colorback, uis.whiteShader );
}

//=================================================================
//
//		MENU
//
//=================================================================

void Menu_AddItem( menuframework_s *menu, void *item )
{
	int i;
	qboolean found = qfalse;

	if( menu->nitems == 0 )
		menu->nslots = 0;

	for( i = 0; i < menu->nitems; i++ )
	{
		if( menu->items[i] == item )
		{
			found = qtrue;
			break;
		}
	}

	if( !found && menu->nitems < MAXMENUITEMS )
	{
		menu->items[menu->nitems] = (menucommon_t *)item;
		menu->items[menu->nitems]->parent = menu;
		menu->nitems++;
	}

	menu->nslots = Menu_TallySlots( menu );
}

/*
** Menu_AdjustCursor
**
** This function takes the given menu, the direction, and attempts
** to adjust the menu's cursor so that it's at the next available
** slot.
*/
void Menu_AdjustCursor( menuframework_s *m, int dir )
{
	menucommon_t *citem;
	int i;

	// see if it's in a valid spot
	if( m->cursor >= 0 && m->cursor < m->nitems )
	{
		if( ( citem = Menu_ItemAtCursor( m ) ) != 0 )
		{
			if( citem->type != MTYPE_SEPARATOR )
				return;
		}
	}

	// it's not in a valid spot, so crawl in the direction indicated until we
	// find a valid spot
	if( dir == 1 )
	{
		for( i = 0; i < m->nitems; i++ )
		{
			citem = Menu_ItemAtCursor( m );
			if( citem )
				if( citem->type != MTYPE_SEPARATOR )
					break;
			m->cursor += dir;
			if( m->cursor >= m->nitems )
				m->cursor = 0;
		}
	}
	else
	{
		for( i = 0; i < m->nitems; i++ )
		{
			citem = Menu_ItemAtCursor( m );
			if( citem )
				if( citem->type != MTYPE_SEPARATOR )
					break;
			m->cursor += dir;
			if( m->cursor < 0 )
				m->cursor = m->nitems - 1;
		}
	}
}

/*
** Menu_Center
**
** This function takes the given menu, the direction, and attempts
** to adjust its position to the center of the screen.
** Note: will now be ignored when a menu is already dynamically
**	set to scale the screen or if it is too big for the default
**	menu sizes.
*/
void Menu_Center( menuframework_s *menu )
{
	int i, height = 0;

	menu->x = uis.vidWidth / 2;

	// y center
	for( i = 0; i < menu->nitems; i++ )
	{
		if( menu->items[i]->y > height )
			height = menu->items[i]->y;
	}
	height += 10;
	menu->y = ( uis.vidHeight - height ) / 2;
}

void Menu_AdjustRectangle( int *mins, int *maxs )
{
	mins[0] *= UI_WIDTHSCALE;
	maxs[0] *= UI_HEIGHTSCALE;

	mins[1] *= UI_WIDTHSCALE;
	maxs[1] *= UI_HEIGHTSCALE;
}

void Menu_Init( menuframework_s *menu )
{
	int i;

	// init items
	for( i = 0; i < menu->nitems; i++ )
	{
		switch( menu->items[i]->type )
		{
		case MTYPE_FIELD:
			Field_Init( menu->items[i] );
			break;
		case MTYPE_SLIDER:
			Slider_Init( menu->items[i] );
			break;
		case MTYPE_SPINCONTROL:
			SpinControl_Init( menu->items[i] );
			break;
		case MTYPE_ACTION:
			Action_Init( menu->items[i] );
			break;
		case MTYPE_SEPARATOR:
			Separator_Init( menu->items[i] );
			break;
		case MTYPE_SCROLLBAR:
			Scrollbar_Init( menu->items[i] );
			break;
		}

		Menu_AdjustRectangle( menu->items[i]->mins, menu->items[i]->maxs );
	}
}

void Menu_Draw( menuframework_s *menu )
{
	int i;
	menucommon_t *item;

	if( !uis.backGround )
		Menu_DrawWindowedBackground( menu );

	// draw contents
	for( i = 0; i < menu->nitems; i++ )
	{
		//draw associated picture
		MenuItem_DrawPict( menu->items[i] );

		switch( menu->items[i]->type )
		{
		case MTYPE_FIELD:
			Field_Draw( menu->items[i] );
			break;
		case MTYPE_SLIDER:
			Slider_Draw( menu->items[i] );
			break;
		case MTYPE_SPINCONTROL:
			SpinControl_Draw( menu->items[i] );
			break;
		case MTYPE_ACTION:
			Action_Draw( menu->items[i] );
			break;
		case MTYPE_SEPARATOR:
			Separator_Draw( menu->items[i] );
			break;
		case MTYPE_SCROLLBAR:
			Scrollbar_Draw( menu->items[i] );
			break;
		}
	}

	item = Menu_ItemAtCursor( menu );

	if( item && item->cursordraw )
	{
		item->cursordraw( item );
	}
	else if( menu->cursordraw )
	{
		menu->cursordraw( menu );
	}

	if( item )
	{
		if( item->statusbarfunc )
			item->statusbarfunc( item );
		else if( item->statusbar )
			Menu_DrawStatusBar( item->statusbar );
		else if( menu->statusbar )
			Menu_DrawStatusBar( menu->statusbar );
	}
	else if( menu->statusbar )
	{
		Menu_DrawStatusBar( menu->statusbar );
	}
}

void Menu_DrawStatusBar( char *string )
{
	struct mufont_s *font = uis.fontSystemSmall;

	UI_FillRect( 0, uis.vidHeight-trap_SCR_strHeight( font ), uis.vidWidth, trap_SCR_strHeight( font ), colorDkGrey );
	trap_SCR_DrawStringWidth( uis.vidWidth/2, uis.vidHeight, ALIGN_CENTER_BOTTOM, string, uis.vidWidth, font, colorWhite );
}

menucommon_t *Menu_ItemAtCursor( menuframework_s *m )
{
	if( m->cursor < 0 || m->cursor >= m->nitems )
		return NULL;

	return m->items[m->cursor];
}

qboolean Menu_SelectItem( menuframework_s *s )
{
	menucommon_t *item = Menu_ItemAtCursor( s );

	if( item )
	{
		switch( item->type )
		{
		case MTYPE_FIELD:
			return Field_DoEnter( item );
		case MTYPE_ACTION:
			Action_DoEnter( item );
			return qtrue;
		case MTYPE_SPINCONTROL:
			//			SpinControl_DoEnter( item );
			return qfalse;
		}
	}
	return qfalse;
}

void Menu_SetStatusBar( menuframework_s *m, char *string )
{
	m->statusbar = string;
}

qboolean Menu_SlideItem( menuframework_s *s, int dir, int key )
{
	menucommon_t *item = Menu_ItemAtCursor( s );

	if( !item )
		return qfalse;
	if( item->scrollbar_id )  //if we're hoving on a list, we want to move its scrollbar, not the list
		item = s->items[item->scrollbar_id];

	if( item )
	{
		switch( item->type )
		{
		case MTYPE_SLIDER:
			if( key == K_MOUSE2 )
				return qfalse;
			if( key == K_MOUSE1 )
				dir = 0; //we move to where the mouse is, not just one step
			Slider_DoSlide( item, dir );
			return qtrue;
		case MTYPE_SPINCONTROL:
			SpinControl_DoSlide( item, dir );
			return qtrue;
		case MTYPE_SCROLLBAR:
			if( key != K_MOUSE2 ) //K_MOUSE2 just goes back a menu, does not move the scrollbar
			{
				if( ( Menu_ItemAtCursor( s ) != item && key != K_MOUSE1 ) || ( Menu_ItemAtCursor( s ) == item ) )
				{
					if( dir > 3 || dir < -3 )
						dir = 0;
					Scrollbar_DoSlide( item, dir ); //only use K_MOUSE1 for scrolling if we're hovering the scrollbar
				}
			}
			if( Menu_ItemAtCursor( s )->scrollbar_id )  //if we're moving the scrollbar, and not the list, we have to return qfalse
				return qfalse;
			else
				return qtrue;
		}
	}

	return qfalse;
}

int Menu_TallySlots( menuframework_s *menu )
{
	int i;
	int total = 0;

	for( i = 0; i < menu->nitems; i++ )
	{
		total++;
	}

	return total;
}

//=======================================================
// scroll lists management
//=======================================================

//==================
//UI_FreeScrollItemList
//==================
void UI_FreeScrollItemList( m_itemslisthead_t *itemlist )
{
	m_listitem_t *ptr;

	while( itemlist->headNode )
	{
		ptr = itemlist->headNode;
		itemlist->headNode = ptr->pnext;
		UI_Free( ptr );
	}

	itemlist->headNode = NULL;
	itemlist->numItems = 0;
}

//==================
//UI_FindItemInScrollListWithId
//==================
m_listitem_t *UI_FindItemInScrollListWithId( m_itemslisthead_t *itemlist, int itemid )
{
	m_listitem_t *item;

	if( !itemlist->headNode )
		return NULL;

	item = itemlist->headNode;
	while( item )
	{
		if( item->id == itemid )
			return item;
		item = item->pnext;
	}

	return NULL;
}

//==================
//UI_AddItemToScrollList
//==================
void UI_AddItemToScrollList( m_itemslisthead_t *itemlist, const char *name, void *data )
{
	m_listitem_t *newitem, *checkitem;

	//check for the address being already in the list
	checkitem = itemlist->headNode;
	while( checkitem )
	{
		if( !Q_stricmp( name, checkitem->name ) )
			return;
		checkitem = checkitem->pnext;
	}

	newitem = (m_listitem_t *)UI_Malloc( sizeof( m_listitem_t ) );
	Q_strncpyz( newitem->name, name, sizeof( newitem->name ) );
	newitem->pnext = itemlist->headNode;
	itemlist->headNode = newitem;
	newitem->id = itemlist->numItems;
	newitem->data = data;

	// update item names array
	itemlist->item_names[itemlist->numItems] = UI_CopyString( newitem->name );
	itemlist->item_names[itemlist->numItems+1] = NULL;

	itemlist->numItems++;
}

//=======================================================
// menu actions registration
//=======================================================
menucommon_t *ui_menuitems_headnode;

menucommon_t *UI_MenuItemByName( char *name )
{
	menucommon_t *menuitem;

	if( !name )
		return NULL;

	for( menuitem = ui_menuitems_headnode; menuitem; menuitem = (menucommon_t *)menuitem->next )
	{
		if( !Q_stricmp( menuitem->name, name ) )
			return menuitem;
	}

	return NULL;
}

char *UI_GetMenuitemFieldBuffer( menucommon_t *menuitem )
{
	menufield_t *field;

	if( !menuitem || menuitem->type != MTYPE_FIELD )
		return NULL;
	if( !menuitem->itemlocal )
		return NULL;

	field = (menufield_t *)menuitem->itemlocal;
	return field->buffer;
}

menucommon_t *UI_RegisterMenuItem( const char *name, int type )
{
	menucommon_t *menuitem;
	size_t size;

	if( !name )
		return NULL;

	for( menuitem = ui_menuitems_headnode; menuitem; menuitem = (menucommon_t *)menuitem->next )
	{
		if( !Q_stricmp( menuitem->name, name ) )
			return menuitem;
	}

	switch( type )
	{
	case MTYPE_SLIDER:
		size = 0;
		break;
	case MTYPE_ACTION:
		size = 0;
		break;
	case MTYPE_SPINCONTROL:
		size = 0;
		break;
	case MTYPE_SEPARATOR:
		size = 0;
		break;
	case MTYPE_FIELD:
		size = sizeof( menufield_t );
		break;
	case MTYPE_SCROLLBAR:
		size = 0;
		break;
	default:
		size = 0;
		break;
	}

	// allocate one huge array to hold our data
	menuitem = (menucommon_t *)UI_Malloc( sizeof( menucommon_t ) );
	if( size )
		menuitem->itemlocal = UI_Malloc( size );
	menuitem->name = UI_CopyString( name );
	menuitem->next = ui_menuitems_headnode;
	ui_menuitems_headnode = menuitem;

	return menuitem;
}

menucommon_t *UI_InitMenuItem( const char *name, char *title, int x, int y, int type, int align, struct mufont_s *font, void ( *callback )(struct menucommon_s *) )
{
	menucommon_t *menuitem;

	if( !name )
		return NULL;

	menuitem = UI_RegisterMenuItem( name, type );
	if( !menuitem )
		return NULL;

	menuitem->type = type;
	menuitem->align = align;
	menuitem->x = x;
	menuitem->y = y;
	menuitem->font = font;
	menuitem->callback = callback;
	if( title )
		Q_strncpyz( menuitem->title, title, MAX_STRING_CHARS );
	else
		menuitem->title[0] = 0;

	// clear up associated picture trash
	menuitem->pict.shader = NULL;
	menuitem->pict.shaderHigh = NULL;
	Vector4Copy( colorWhite, menuitem->pict.color );
	Vector4Copy( colorWhite, menuitem->pict.colorHigh );

	return menuitem;
}

menucommon_t *UI_SetupSlider( menucommon_t *menuitem, int width, int curvalue, int minvalue, int maxvalue )
{
	if( !menuitem )
		return NULL;

	menuitem->curvalue = curvalue;
	menuitem->minvalue = minvalue;
	menuitem->maxvalue = maxvalue;
	clamp( menuitem->curvalue, minvalue, maxvalue );
	menuitem->width = width;
	if( menuitem->width < 3 )
		menuitem->width = 3;

	return menuitem;
}

menucommon_t *UI_SetupScrollbar( menucommon_t *menuitem, int height, int curvalue, int minvalue, int maxvalue )
{
	if( !menuitem )
		return NULL;

	menuitem->minvalue = minvalue;
	if( !menuitem->maxvalue )
		menuitem->maxvalue = maxvalue;
	if( !menuitem->curvalue )
		menuitem->curvalue = curvalue;
	clamp( menuitem->curvalue, minvalue, maxvalue );
	menuitem->height = height;
	if( menuitem->height < 3 )
		menuitem->height = 3;
	return menuitem;
}

menucommon_t *UI_SetupSpinControl( menucommon_t *menuitem, char **item_names, int curvalue )
{
	int numitems;

	if( !menuitem || !item_names )
		return NULL;

	//count item names
	numitems = 0;
	while( item_names[numitems] )
		numitems++;

	menuitem->itemnames = item_names;
	menuitem->curvalue = curvalue;
	menuitem->minvalue = 0;
	menuitem->maxvalue = numitems-1;
	clamp( menuitem->curvalue, menuitem->minvalue, menuitem->maxvalue );

	return menuitem;
}

menufield_t *UI_SetupField( menucommon_t *menuitem, const char *string, size_t length, int width )
{
	menufield_t *field;

	if( !menuitem )
		return NULL;

	field = (menufield_t *)menuitem->itemlocal;

	field->length = length;
	if( length < 1 )
		length = 1;

	if( width >= ( 2*(int)trap_SCR_strWidth( "_", menuitem->font, 0 ) ) )
		field->width = width;
	else
		field->width = ( length+1 ) *trap_SCR_strWidth( "_", menuitem->font, 0 );

	if( string )
	{
		Q_strncpyz( field->buffer, string, sizeof( field->buffer ) );
		field->cursor = strlen( field->buffer );
	}
	else
	{
		memset( field->buffer, 0, sizeof( field->buffer ) );
		field->cursor = 0;
	}

	return field;
}

void UI_SetupFlags( menucommon_t *menuitem, int flags )
{
	menuitem->flags = flags;
}
