/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
Copyright (C) 2000-2013 Darklegion Development
 
This file is part of Tremulous.
 
Tremulous 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.
 
Tremulous 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 Tremulous; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

#include "ui_shared.h"

#define SCROLL_TIME_START         500
#define SCROLL_TIME_ADJUST        150
#define SCROLL_TIME_ADJUSTOFFSET  40
#define SCROLL_TIME_FLOOR         20

typedef struct scrollInfo_s
{
  int nextScrollTime;
  int nextAdjustTime;
  int adjustValue;
  int scrollKey;
  float xStart;
  float yStart;
  itemDef_t *item;
  qboolean scrollDir;
}
scrollInfo_t;

static scrollInfo_t scrollInfo;

// prevent compiler warnings
void voidFunction( void *var )
{
  return;
}

qboolean voidFunction2( itemDef_t *var1, int var2 )
{
  return qfalse;
}

static CaptureFunc *captureFunc = voidFunction;
static int captureFuncExpiry = 0;
static void *captureData = NULL;
static itemDef_t *itemCapture = NULL;   // item that has the mouse captured ( if any )

displayContextDef_t *DC = NULL;

static qboolean g_waitingForKey = qfalse;
static qboolean g_editingField = qfalse;

static itemDef_t *g_bindItem = NULL;
static itemDef_t *g_editItem = NULL;
static itemDef_t *g_comboBoxItem = NULL;

menuDef_t Menus[MAX_MENUS];      // defined menus
int menuCount = 0;               // how many

menuDef_t *menuStack[MAX_OPEN_MENUS];
int openMenuCount = 0;

#define DOUBLE_CLICK_DELAY 300
static int lastListBoxClickTime = 0;

itemDataType_t Item_DataType( itemDef_t *item );
void Item_RunScript( itemDef_t *item, const char *s );
void Item_SetupKeywordHash( void );
static ID_INLINE qboolean Item_IsEditField( itemDef_t *item );
static ID_INLINE qboolean Item_IsListBox( itemDef_t *item );
static void Item_ListBox_SetStartPos( itemDef_t *item, int startPos );
void Menu_SetupKeywordHash( void );
int BindingIDFromName( const char *name );
qboolean Item_Bind_HandleKey( itemDef_t *item, int key, qboolean down );
itemDef_t *Menu_SetPrevCursorItem( menuDef_t *menu );
itemDef_t *Menu_SetNextCursorItem( menuDef_t *menu );
static qboolean Menu_OverActiveItem( menuDef_t *menu, float x, float y );

/*
===============
UI_InstallCaptureFunc
===============
*/
void UI_InstallCaptureFunc( CaptureFunc *f, void *data, int timeout )
{
  captureFunc = f;
  captureData = data;

  if( timeout > 0 )
    captureFuncExpiry = DC->realTime + timeout;
  else
    captureFuncExpiry = 0;
}

/*
===============
UI_RemoveCaptureFunc
===============
*/
void UI_RemoveCaptureFunc( void )
{
  captureFunc = voidFunction;
  captureData = NULL;
  captureFuncExpiry = 0;
}

#ifdef CGAME
#define MEM_POOL_SIZE  128 * 1024
#else
#define MEM_POOL_SIZE  1024 * 1024
#endif

static char   UI_memoryPool[MEM_POOL_SIZE];
static int    allocPoint, outOfMemory;

/*
===============
UI_Alloc
===============
*/
void *UI_Alloc( int size )
{
  char  *p;

  if( allocPoint + size > MEM_POOL_SIZE )
  {
    outOfMemory = qtrue;

    if( DC->Print )
      DC->Print( "UI_Alloc: Failure. Out of memory!\n" );

    //DC->trap_Print(S_COLOR_YELLOW"WARNING: UI Out of Memory!\n");
    return NULL;
  }

  p = &UI_memoryPool[ allocPoint ];

  allocPoint += ( size + 15 ) & ~15;

  return p;
}

/*
===============
UI_InitMemory
===============
*/
void UI_InitMemory( void )
{
  allocPoint = 0;
  outOfMemory = qfalse;
}

qboolean UI_OutOfMemory( )
{
  return outOfMemory;
}





#define HASH_TABLE_SIZE 2048
/*
================
return a hash value for the string
================
*/
static long hashForString( const char *str )
{
  int   i;
  long  hash;
  char  letter;

  hash = 0;
  i = 0;

  while( str[i] != '\0' )
  {
    letter = tolower( str[i] );
    hash += ( long )( letter ) * ( i + 119 );
    i++;
  }

  hash &= ( HASH_TABLE_SIZE - 1 );
  return hash;
}

typedef struct stringDef_s
{
  struct stringDef_s *next;
  const char *str;
}

stringDef_t;

static int strPoolIndex = 0;
static char strPool[STRING_POOL_SIZE];

static int strHandleCount = 0;
static stringDef_t *strHandle[HASH_TABLE_SIZE];

// Make a copy of a string for later use. Can safely be called on the
// same string repeatedly. Redundant on string literals or global
// constants.
const char *String_Alloc( const char *p )
{
  int len;
  long hash;
  stringDef_t *str, *last;

  if( p == NULL )
    return NULL;

  if( *p == 0 )
    return "";

  hash = hashForString( p );

  str = strHandle[hash];

  while( str )
  {
    if( strcmp( p, str->str ) == 0 )
      return str->str;

    str = str->next;
  }

  len = strlen( p );

  if( len + strPoolIndex + 1 < STRING_POOL_SIZE )
  {
    int ph = strPoolIndex;
    strcpy( &strPool[strPoolIndex], p );
    strPoolIndex += len + 1;

    str = strHandle[hash];
    last = str;

    while( str && str->next )
    {
      last = str;
      str = str->next;
    }

    str  = UI_Alloc( sizeof( stringDef_t ) );
    str->next = NULL;
    str->str = &strPool[ph];

    if( last )
      last->next = str;
    else
      strHandle[hash] = str;

    return &strPool[ph];
  }
  else
  {
    Com_Error( ERR_DROP, "String_Alloc( %s ): string pool full!", p );
    return NULL; // not that we return at all
  }
}

void String_Report( void )
{
  float f;
  Com_Printf( "Memory/String Pool Info\n" );
  Com_Printf( "----------------\n" );
  f = strPoolIndex;
  f /= STRING_POOL_SIZE;
  f *= 100;
  Com_Printf( "String Pool is %.1f%% full, %i bytes out of %i used.\n", f, strPoolIndex, STRING_POOL_SIZE );
  f = allocPoint;
  f /= MEM_POOL_SIZE;
  f *= 100;
  Com_Printf( "Memory Pool is %.1f%% full, %i bytes out of %i used.\n", f, allocPoint, MEM_POOL_SIZE );
}

/*
=================
String_Init
=================
*/
void String_Init( void )
{
  int i;

  for( i = 0; i < HASH_TABLE_SIZE; i++ )
    strHandle[ i ] = 0;

  strHandleCount = 0;

  strPoolIndex = 0;

  menuCount = 0;

  openMenuCount = 0;

  UI_InitMemory( );

  Item_SetupKeywordHash( );

  Menu_SetupKeywordHash( );

  if( DC && DC->getBindingBuf )
    Controls_GetConfig( );
}

/*
=================
PC_SourceWarning
=================
*/
__attribute__ ((format (printf, 2, 3))) void PC_SourceWarning( int handle, char *format, ... )
{
  int line;
  char filename[128];
  va_list argptr;
  static char string[4096];

  va_start( argptr, format );
  Q_vsnprintf( string, sizeof( string ), format, argptr );
  va_end( argptr );

  filename[0] = '\0';
  line = 0;
  trap_Parse_SourceFileAndLine( handle, filename, &line );

  Com_Printf( S_COLOR_YELLOW "WARNING: %s, line %d: %s\n", filename, line, string );
}

/*
=================
PC_SourceError
=================
*/
__attribute__ ((format (printf, 2, 3))) void PC_SourceError( int handle, char *format, ... )
{
  int line;
  char filename[128];
  va_list argptr;
  static char string[4096];

  va_start( argptr, format );
  Q_vsnprintf( string, sizeof( string ), format, argptr );
  va_end( argptr );

  filename[0] = '\0';
  line = 0;
  trap_Parse_SourceFileAndLine( handle, filename, &line );

  Com_Printf( S_COLOR_RED "ERROR: %s, line %d: %s\n", filename, line, string );
}

/*
=================
LerpColor
=================
*/
void LerpColor( vec4_t a, vec4_t b, vec4_t c, float t )
{
  int i;

  // lerp and clamp each component

  for( i = 0; i < 4; i++ )
  {
    c[i] = a[i] + t * ( b[i] - a[i] );

    if( c[i] < 0 )
      c[i] = 0;
    else if( c[i] > 1.0 )
      c[i] = 1.0;
  }
}

/*
=================
Float_Parse
=================
*/
qboolean Float_Parse( char **p, float *f )
{
  char  *token;
  token = COM_ParseExt( p, qfalse );

  if( token && token[0] != 0 )
  {
    *f = atof( token );
    return qtrue;
  }
  else
    return qfalse;
}

#define MAX_EXPR_ELEMENTS 32

typedef enum
{
  EXPR_OPERATOR,
  EXPR_VALUE
}
exprType_t;

typedef struct exprToken_s
{
  exprType_t  type;
  union
  {
    char  op;
    float val;
  } u;
}
exprToken_t;

typedef struct exprList_s
{
  exprToken_t l[ MAX_EXPR_ELEMENTS ];
  int f, b;
}
exprList_t;

/*
=================
OpPrec

Return a value reflecting operator precedence
=================
*/
static ID_INLINE int OpPrec( char op )
{
  switch( op )
  {
    case '*':
      return 4;

    case '/':
      return 3;

    case '+':
      return 2;

    case '-':
      return 1;

    case '(':
      return 0;

    default:
      return -1;
  }
}

/*
=================
PC_Expression_Parse
=================
*/
static qboolean PC_Expression_Parse( int handle, float *f )
{
  pc_token_t  token;
  int         unmatchedParentheses = 0;
  exprList_t  stack, fifo;
  exprToken_t value;
  qboolean    expectingNumber = qtrue;

#define FULL( a ) ( a.b >= ( MAX_EXPR_ELEMENTS - 1 ) )
#define EMPTY( a ) ( a.f > a.b )

#define PUSH_VAL( a, v ) \
  { \
    if( FULL( a ) ) \
      return qfalse; \
    a.b++; \
    a.l[ a.b ].type = EXPR_VALUE; \
    a.l[ a.b ].u.val = v; \
  }

#define PUSH_OP( a, o ) \
  { \
    if( FULL( a ) ) \
      return qfalse; \
    a.b++; \
    a.l[ a.b ].type = EXPR_OPERATOR; \
    a.l[ a.b ].u.op = o; \
  }

#define POP_STACK( a ) \
  { \
    if( EMPTY( a ) ) \
      return qfalse; \
    value = a.l[ a.b ]; \
    a.b--; \
  }

#define PEEK_STACK_OP( a )  ( a.l[ a.b ].u.op )
#define PEEK_STACK_VAL( a ) ( a.l[ a.b ].u.val )

#define POP_FIFO( a ) \
  { \
    if( EMPTY( a ) ) \
      return qfalse; \
    value = a.l[ a.f ]; \
    a.f++; \
  }

  stack.f = fifo.f = 0;
  stack.b = fifo.b = -1;

  while( trap_Parse_ReadToken( handle, &token ) )
  {
    if( !unmatchedParentheses && token.string[ 0 ] == ')' )
      break;

    // Special case to catch negative numbers
    if( expectingNumber && token.string[ 0 ] == '-' )
    {
      if( !trap_Parse_ReadToken( handle, &token ) )
        return qfalse;

      token.floatvalue = -token.floatvalue;
    }

    if( token.type == TT_NUMBER )
    {
      if( !expectingNumber )
        return qfalse;

      expectingNumber = !expectingNumber;

      PUSH_VAL( fifo, token.floatvalue );
    }
    else
    {
      switch( token.string[ 0 ] )
      {
        case '(':
          unmatchedParentheses++;
          PUSH_OP( stack, '(' );
          break;

        case ')':
          unmatchedParentheses--;

          if( unmatchedParentheses < 0 )
            return qfalse;

          while( !EMPTY( stack ) && PEEK_STACK_OP( stack ) != '(' )
          {
            POP_STACK( stack );
            PUSH_OP( fifo, value.u.op );
          }

          // Pop the '('
          POP_STACK( stack );

          break;

        case '*':
        case '/':
        case '+':
        case '-':
          if( expectingNumber )
            return qfalse;

          expectingNumber = !expectingNumber;

          if( EMPTY( stack ) )
          {
            PUSH_OP( stack, token.string[ 0 ] );
          }
          else
          {
            while( !EMPTY( stack ) && OpPrec( token.string[ 0 ] ) < OpPrec( PEEK_STACK_OP( stack ) ) )
            {
              POP_STACK( stack );
              PUSH_OP( fifo, value.u.op );
            }

            PUSH_OP( stack, token.string[ 0 ] );
          }

          break;

        default:
          // Unknown token
          return qfalse;
      }
    }
  }

  while( !EMPTY( stack ) )
  {
    POP_STACK( stack );
    PUSH_OP( fifo, value.u.op );
  }

  while( !EMPTY( fifo ) )
  {
    POP_FIFO( fifo );

    if( value.type == EXPR_VALUE )
    {
      PUSH_VAL( stack, value.u.val );
    }
    else if( value.type == EXPR_OPERATOR )
    {
      char op = value.u.op;
      float operand1, operand2, result;

      POP_STACK( stack );
      operand2 = value.u.val;
      POP_STACK( stack );
      operand1 = value.u.val;

      switch( op )
      {
        case '*':
          result = operand1 * operand2;
          break;

        case '/':
          result = operand1 / operand2;
          break;

        case '+':
          result = operand1 + operand2;
          break;

        case '-':
          result = operand1 - operand2;
          break;

        default:
          Com_Error( ERR_FATAL, "Unknown operator '%c' in postfix string", op );
          return qfalse;
      }

      PUSH_VAL( stack, result );
    }
  }

  POP_STACK( stack );

  *f = value.u.val;

  return qtrue;

#undef FULL
#undef EMPTY
#undef PUSH_VAL
#undef PUSH_OP
#undef POP_STACK
#undef PEEK_STACK_OP
#undef PEEK_STACK_VAL
#undef POP_FIFO
}

/*
=================
PC_Float_Parse
=================
*/
qboolean PC_Float_Parse( int handle, float *f )
{
  pc_token_t token;
  int negative = qfalse;

  if( !trap_Parse_ReadToken( handle, &token ) )
    return qfalse;

  if( token.string[ 0 ] == '(' )
    return PC_Expression_Parse( handle, f );

  if( token.string[0] == '-' )
  {
    if( !trap_Parse_ReadToken( handle, &token ) )
      return qfalse;

    negative = qtrue;
  }

  if( token.type != TT_NUMBER )
  {
    PC_SourceError( handle, "expected float but found %s\n", token.string );
    return qfalse;
  }

  if( negative )
    * f = -token.floatvalue;
  else
    *f = token.floatvalue;

  return qtrue;
}

/*
=================
Color_Parse
=================
*/
qboolean Color_Parse( char **p, vec4_t *c )
{
  int i;
  float f;

  for( i = 0; i < 4; i++ )
  {
    if( !Float_Parse( p, &f ) )
      return qfalse;

    ( *c )[i] = f;
  }

  return qtrue;
}

/*
=================
PC_Color_Parse
=================
*/
qboolean PC_Color_Parse( int handle, vec4_t *c )
{
  int i;
  float f;

  for( i = 0; i < 4; i++ )
  {
    if( !PC_Float_Parse( handle, &f ) )
      return qfalse;

    ( *c )[i] = f;
  }

  return qtrue;
}

/*
=================
Int_Parse
=================
*/
qboolean Int_Parse( char **p, int *i )
{
  char  *token;
  token = COM_ParseExt( p, qfalse );

  if( token && token[0] != 0 )
  {
    *i = atoi( token );
    return qtrue;
  }
  else
    return qfalse;
}

/*
=================
PC_Int_Parse
=================
*/
qboolean PC_Int_Parse( int handle, int *i )
{
  pc_token_t token;
  int negative = qfalse;

  if( !trap_Parse_ReadToken( handle, &token ) )
    return qfalse;

  if( token.string[ 0 ] == '(' )
  {
    float f;

    if( PC_Expression_Parse( handle, &f ) )
    {
      *i = ( int )f;
      return qtrue;
    }
    else
      return qfalse;
  }

  if( token.string[0] == '-' )
  {
    if( !trap_Parse_ReadToken( handle, &token ) )
      return qfalse;

    negative = qtrue;
  }

  if( token.type != TT_NUMBER )
  {
    PC_SourceError( handle, "expected integer but found %s\n", token.string );
    return qfalse;
  }

  *i = token.intvalue;

  if( negative )
    * i = - *i;

  return qtrue;
}

/*
=================
Rect_Parse
=================
*/
qboolean Rect_Parse( char **p, rectDef_t *r )
{
  if( Float_Parse( p, &r->x ) )
  {
    if( Float_Parse( p, &r->y ) )
    {
      if( Float_Parse( p, &r->w ) )
      {
        if( Float_Parse( p, &r->h ) )
          return qtrue;
      }
    }
  }

  return qfalse;
}

/*
=================
PC_Rect_Parse
=================
*/
qboolean PC_Rect_Parse( int handle, rectDef_t *r )
{
  if( PC_Float_Parse( handle, &r->x ) )
  {
    if( PC_Float_Parse( handle, &r->y ) )
    {
      if( PC_Float_Parse( handle, &r->w ) )
      {
        if( PC_Float_Parse( handle, &r->h ) )
          return qtrue;
      }
    }
  }

  return qfalse;
}

/*
=================
String_Parse
=================
*/
qboolean String_Parse( char **p, const char **out )
{
  char *token;

  token = COM_ParseExt( p, qfalse );

  if( token && token[0] != 0 )
  {
    *( out ) = String_Alloc( token );
    return qtrue;
  }

  return qfalse;
}

/*
=================
PC_String_Parse
=================
*/
qboolean PC_String_Parse( int handle, const char **out )
{
  pc_token_t token;

  if( !trap_Parse_ReadToken( handle, &token ) )
    return qfalse;

  *( out ) = String_Alloc( token.string );

  return qtrue;
}

/*
=================
PC_Script_Parse
=================
*/
qboolean PC_Script_Parse( int handle, const char **out )
{
  char script[1024];
  pc_token_t token;

  memset( script, 0, sizeof( script ) );
  // scripts start with { and have ; separated command lists.. commands are command, arg..
  // basically we want everything between the { } as it will be interpreted at run time

  if( !trap_Parse_ReadToken( handle, &token ) )
    return qfalse;

  if( Q_stricmp( token.string, "{" ) != 0 )
    return qfalse;

  while( 1 )
  {
    if( !trap_Parse_ReadToken( handle, &token ) )
      return qfalse;

    if( Q_stricmp( token.string, "}" ) == 0 )
    {
      *out = String_Alloc( script );
      return qtrue;
    }

    if( token.string[1] != '\0' )
      Q_strcat( script, 1024, va( "\"%s\"", token.string ) );
    else
      Q_strcat( script, 1024, token.string );

    Q_strcat( script, 1024, " " );
  }

  return qfalse;
}

// display, window, menu, item code
//

/*
==================
Init_Display
 
Initializes the display with a structure to all the drawing routines
==================
*/
void Init_Display( displayContextDef_t *dc )
{
  DC = dc;
}



// type and style painting

void GradientBar_Paint( rectDef_t *rect, vec4_t color )
{
  // gradient bar takes two paints
  DC->setColor( color );
  DC->drawHandlePic( rect->x, rect->y, rect->w, rect->h, DC->Assets.gradientBar );
  DC->setColor( NULL );
}


/*
==================
Window_Init

Initializes a window structure ( windowDef_t ) with defaults
==================
*/
void Window_Init( Window *w )
{
  memset( w, 0, sizeof( windowDef_t ) );
  w->borderSize = 1;
  w->foreColor[0] = w->foreColor[1] = w->foreColor[2] = w->foreColor[3] = 1.0;
  w->cinematic = -1;
}

void Fade( int *flags, float *f, float clamp, int *nextTime, int offsetTime, qboolean bFlags, float fadeAmount )
{
  if( *flags & ( WINDOW_FADINGOUT | WINDOW_FADINGIN ) )
  {
    if( DC->realTime > *nextTime )
    {
      *nextTime = DC->realTime + offsetTime;

      if( *flags & WINDOW_FADINGOUT )
      {
        *f -= fadeAmount;

        if( bFlags && *f <= 0.0 )
          *flags &= ~( WINDOW_FADINGOUT | WINDOW_VISIBLE );
      }
      else
      {
        *f += fadeAmount;

        if( *f >= clamp )
        {
          *f = clamp;

          if( bFlags )
            *flags &= ~WINDOW_FADINGIN;
        }
      }
    }
  }
}



static void Window_Paint( Window *w, float fadeAmount, float fadeClamp, float fadeCycle )
{
  vec4_t color;
  rectDef_t fillRect = w->rect;


  if( DC->getCVarValue( "ui_developer" ) )
  {
    color[0] = color[1] = color[2] = color[3] = 1;
    DC->drawRect( w->rect.x, w->rect.y, w->rect.w, w->rect.h, 1, color );
  }

  if( w == NULL || ( w->style == 0 && w->border == 0 ) )
    return;

  if( w->border != 0 )
  {
    fillRect.x += w->borderSize;
    fillRect.y += w->borderSize;
    fillRect.w -= w->borderSize + 1;
    fillRect.h -= w->borderSize + 1;
  }

  if( w->style == WINDOW_STYLE_FILLED )
  {
    // box, but possible a shader that needs filled

    if( w->background )
    {
      Fade( &w->flags, &w->backColor[3], fadeClamp, &w->nextTime, fadeCycle, qtrue, fadeAmount );
      DC->setColor( w->backColor );
      DC->drawHandlePic( fillRect.x, fillRect.y, fillRect.w, fillRect.h, w->background );
      DC->setColor( NULL );
    }
    else
      DC->fillRect( fillRect.x, fillRect.y, fillRect.w, fillRect.h, w->backColor );
  }
  else if( w->style == WINDOW_STYLE_GRADIENT )
  {
    GradientBar_Paint( &fillRect, w->backColor );
    // gradient bar
  }
  else if( w->style == WINDOW_STYLE_SHADER )
  {
    if( w->flags & WINDOW_FORECOLORSET )
      DC->setColor( w->foreColor );

    DC->drawHandlePic( fillRect.x, fillRect.y, fillRect.w, fillRect.h, w->background );
    DC->setColor( NULL );
  }
  else if( w->style == WINDOW_STYLE_CINEMATIC )
  {
    if( w->cinematic == -1 )
    {
      w->cinematic = DC->playCinematic( w->cinematicName, fillRect.x, fillRect.y, fillRect.w, fillRect.h );

      if( w->cinematic == -1 )
        w->cinematic = -2;
    }

    if( w->cinematic >= 0 )
    {
      DC->runCinematicFrame( w->cinematic );
      DC->drawCinematic( w->cinematic, fillRect.x, fillRect.y, fillRect.w, fillRect.h );
    }
  }
}

static void Border_Paint( Window *w )
{
  if( w == NULL || ( w->style == 0 && w->border == 0 ) )
    return;

  if( w->border == WINDOW_BORDER_FULL )
  {
    // full
    DC->drawRect( w->rect.x, w->rect.y, w->rect.w, w->rect.h, w->borderSize, w->borderColor );
  }
  else if( w->border == WINDOW_BORDER_HORZ )
  {
    // top/bottom
    DC->setColor( w->borderColor );
    DC->drawTopBottom( w->rect.x, w->rect.y, w->rect.w, w->rect.h, w->borderSize );
    DC->setColor( NULL );
  }
  else if( w->border == WINDOW_BORDER_VERT )
  {
    // left right
    DC->setColor( w->borderColor );
    DC->drawSides( w->rect.x, w->rect.y, w->rect.w, w->rect.h, w->borderSize );
    DC->setColor( NULL );
  }
  else if( w->border == WINDOW_BORDER_KCGRADIENT )
  {
    // this is just two gradient bars along each horz edge
    rectDef_t r = w->rect;
    r.h = w->borderSize;
    GradientBar_Paint( &r, w->borderColor );
    r.y = w->rect.y + w->rect.h - 1;
    GradientBar_Paint( &r, w->borderColor );
  }
}


void Item_SetScreenCoords( itemDef_t *item, float x, float y )
{
  if( item == NULL )
    return;

  if( item->window.border != 0 )
  {
    x += item->window.borderSize;
    y += item->window.borderSize;
  }

  item->window.rect.x = x + item->window.rectClient.x;
  item->window.rect.y = y + item->window.rectClient.y;
  item->window.rect.w = item->window.rectClient.w;
  item->window.rect.h = item->window.rectClient.h;

  // force the text rects to recompute
  item->textRect.w = 0;
  item->textRect.h = 0;
}

// FIXME: consolidate this with nearby stuff
void Item_UpdatePosition( itemDef_t *item )
{
  float x, y;
  menuDef_t *menu;

  if( item == NULL || item->parent == NULL )
    return;

  menu = item->parent;

  x = menu->window.rect.x;
  y = menu->window.rect.y;

  if( menu->window.border != 0 )
  {
    x += menu->window.borderSize;
    y += menu->window.borderSize;
  }

  Item_SetScreenCoords( item, x, y );

}

// menus
void Menu_UpdatePosition( menuDef_t *menu )
{
  int i;
  float x, y;

  if( menu == NULL )
    return;

  x = menu->window.rect.x;
  y = menu->window.rect.y;

  if( menu->window.border != 0 )
  {
    x += menu->window.borderSize;
    y += menu->window.borderSize;
  }

  for( i = 0; i < menu->itemCount; i++ )
    Item_SetScreenCoords( menu->items[i], x, y );
}

static void Menu_AspectiseRect( int bias, Rectangle *rect )
{
  switch( bias )
  {
    case ALIGN_LEFT:
      rect->x *= DC->aspectScale;
      rect->w *= DC->aspectScale;
      break;

    case ALIGN_CENTER:
      rect->x = ( rect->x * DC->aspectScale ) +
                ( 320.0f - ( 320.0f * DC->aspectScale ) );
      rect->w *= DC->aspectScale;
      break;

    case ALIGN_RIGHT:
      rect->x = 640.0f - ( ( 640.0f - rect->x ) * DC->aspectScale );
      rect->w *= DC->aspectScale;
      break;

    default:

    case ASPECT_NONE:
      break;
  }
}

void Menu_AspectCompensate( menuDef_t *menu )
{
  int i;

  if( menu->window.aspectBias != ASPECT_NONE )
  {
    Menu_AspectiseRect( menu->window.aspectBias, &menu->window.rect );

    for( i = 0; i < menu->itemCount; i++ )
    {
      menu->items[ i ]->window.rectClient.x *= DC->aspectScale;
      menu->items[ i ]->window.rectClient.w *= DC->aspectScale;
      menu->items[ i ]->textalignx *= DC->aspectScale;
    }
  }
  else
  {
    for( i = 0; i < menu->itemCount; i++ )
    {
      Menu_AspectiseRect( menu->items[ i ]->window.aspectBias,
                          &menu->items[ i ]->window.rectClient );

      if( menu->items[ i ]->window.aspectBias != ASPECT_NONE )
        menu->items[ i ]->textalignx *= DC->aspectScale;
    }
  }
}

void Menu_PostParse( menuDef_t *menu )
{
  int i, j;

  if( menu == NULL )
    return;

  if( menu->fullScreen )
  {
    menu->window.rect.x = 0;
    menu->window.rect.y = 0;
    menu->window.rect.w = 640;
    menu->window.rect.h = 480;
  }

  Menu_AspectCompensate( menu );
  Menu_UpdatePosition( menu );

  // Push lists to the end of the array as they can potentially be drawn on top
  // of other elements
  for( i = 0; i < menu->itemCount; i++ )
  {
    itemDef_t *item = menu->items[ i ];

    if( Item_IsListBox( item ) )
    {
      for( j = i; j < menu->itemCount - 1; j++ )
        menu->items[ j ] = menu->items[ j + 1 ];

      menu->items[ j ] = item;
    }
  }
}

itemDef_t *Menu_ClearFocus( menuDef_t *menu )
{
  int i;
  itemDef_t *ret = NULL;

  if( menu == NULL )
    return NULL;

  for( i = 0; i < menu->itemCount; i++ )
  {
    if( menu->items[i]->window.flags & WINDOW_HASFOCUS )
      ret = menu->items[i];

    menu->items[i]->window.flags &= ~WINDOW_HASFOCUS;

    if( menu->items[i]->leaveFocus )
      Item_RunScript( menu->items[i], menu->items[i]->leaveFocus );
  }

  return ret;
}

qboolean IsVisible( int flags )
{
  return ( flags & WINDOW_VISIBLE && !( flags & WINDOW_FADINGOUT ) );
}

qboolean Rect_ContainsPoint( rectDef_t *rect, float x, float y )
{
  if( rect )
  {
    if( x > rect->x && x < rect->x + rect->w && y > rect->y && y < rect->y + rect->h )
      return qtrue;
  }

  return qfalse;
}

int Menu_ItemsMatchingGroup( menuDef_t *menu, const char *name )
{
  int i;
  int count = 0;

  for( i = 0; i < menu->itemCount; i++ )
  {
    if( Q_stricmp( menu->items[i]->window.name, name ) == 0 ||
        ( menu->items[i]->window.group && Q_stricmp( menu->items[i]->window.group, name ) == 0 ) )
    {
      count++;
    }
  }

  return count;
}

itemDef_t *Menu_GetMatchingItemByNumber( menuDef_t *menu, int index, const char *name )
{
  int i;
  int count = 0;

  for( i = 0; i < menu->itemCount; i++ )
  {
    if( Q_stricmp( menu->items[i]->window.name, name ) == 0 ||
        ( menu->items[i]->window.group && Q_stricmp( menu->items[i]->window.group, name ) == 0 ) )
    {
      if( count == index )
        return menu->items[i];

      count++;
    }
  }

  return NULL;
}



void Script_SetColor( itemDef_t *item, char **args )
{
  const char *name;
  int i;
  float f;
  vec4_t *out;
  // expecting type of color to set and 4 args for the color

  if( String_Parse( args, &name ) )
  {
    out = NULL;

    if( Q_stricmp( name, "backcolor" ) == 0 )
    {
      out = &item->window.backColor;
      item->window.flags |= WINDOW_BACKCOLORSET;
    }
    else if( Q_stricmp( name, "forecolor" ) == 0 )
    {
      out = &item->window.foreColor;
      item->window.flags |= WINDOW_FORECOLORSET;
    }
    else if( Q_stricmp( name, "bordercolor" ) == 0 )
      out = &item->window.borderColor;

    if( out )
    {
      for( i = 0; i < 4; i++ )
      {
        if( !Float_Parse( args, &f ) )
          return;

        ( *out )[i] = f;
      }
    }
  }
}

void Script_SetAsset( itemDef_t *item, char **args )
{
  const char *name;
  // expecting name to set asset to

  if( String_Parse( args, &name ) )
  {
    // check for a model

    if( item->type == ITEM_TYPE_MODEL )
    {}

  }

}

void Script_SetBackground( itemDef_t *item, char **args )
{
  const char *name;
  // expecting name to set asset to

  if( String_Parse( args, &name ) )
    item->window.background = DC->registerShaderNoMip( name );
}




itemDef_t *Menu_FindItemByName( menuDef_t *menu, const char *p )
{
  int i;

  if( menu == NULL || p == NULL )
    return NULL;

  for( i = 0; i < menu->itemCount; i++ )
  {
    if( Q_stricmp( p, menu->items[i]->window.name ) == 0 )
      return menu->items[i];
  }

  return NULL;
}

void Script_SetItemColor( itemDef_t *item, char **args )
{
  const char *itemname;
  const char *name;
  vec4_t color;
  int i;
  vec4_t *out;
  // expecting type of color to set and 4 args for the color

  if( String_Parse( args, &itemname ) && String_Parse( args, &name ) )
  {
    itemDef_t * item2;
    int j;
    int count = Menu_ItemsMatchingGroup( item->parent, itemname );

    if( !Color_Parse( args, &color ) )
      return;

    for( j = 0; j < count; j++ )
    {
      item2 = Menu_GetMatchingItemByNumber( item->parent, j, itemname );

      if( item2 != NULL )
      {
        out = NULL;

        if( Q_stricmp( name, "backcolor" ) == 0 )
          out = &item2->window.backColor;
        else if( Q_stricmp( name, "forecolor" ) == 0 )
        {
          out = &item2->window.foreColor;
          item2->window.flags |= WINDOW_FORECOLORSET;
        }
        else if( Q_stricmp( name, "bordercolor" ) == 0 )
          out = &item2->window.borderColor;

        if( out )
        {
          for( i = 0; i < 4; i++ )
            ( *out )[i] = color[i];
        }
      }
    }
  }
}


void Menu_ShowItemByName( menuDef_t *menu, const char *p, qboolean bShow )
{
  itemDef_t * item;
  int i;
  int count = Menu_ItemsMatchingGroup( menu, p );

  for( i = 0; i < count; i++ )
  {
    item = Menu_GetMatchingItemByNumber( menu, i, p );

    if( item != NULL )
    {
      if( bShow )
        item->window.flags |= WINDOW_VISIBLE;
      else
      {
        item->window.flags &= ~WINDOW_VISIBLE;
        // stop cinematics playing in the window

        if( item->window.cinematic >= 0 )
        {
          DC->stopCinematic( item->window.cinematic );
          item->window.cinematic = -1;
        }
      }
    }
  }
}

void Menu_FadeItemByName( menuDef_t *menu, const char *p, qboolean fadeOut )
{
  itemDef_t * item;
  int i;
  int count = Menu_ItemsMatchingGroup( menu, p );

  for( i = 0; i < count; i++ )
  {
    item = Menu_GetMatchingItemByNumber( menu, i, p );

    if( item != NULL )
    {
      if( fadeOut )
      {
        item->window.flags |= ( WINDOW_FADINGOUT | WINDOW_VISIBLE );
        item->window.flags &= ~WINDOW_FADINGIN;
      }
      else
      {
        item->window.flags |= ( WINDOW_VISIBLE | WINDOW_FADINGIN );
        item->window.flags &= ~WINDOW_FADINGOUT;
      }
    }
  }
}

menuDef_t *Menus_FindByName( const char *p )
{
  int i;

  for( i = 0; i < menuCount; i++ )
  {
    if( Q_stricmp( Menus[i].window.name, p ) == 0 )
      return & Menus[i];
  }

  return NULL;
}

static void Menu_RunCloseScript( menuDef_t *menu )
{
  if( menu && menu->window.flags & WINDOW_VISIBLE && menu->onClose )
  {
    itemDef_t item;
    item.parent = menu;
    Item_RunScript( &item, menu->onClose );
  }
}

static void Menus_Close( menuDef_t *menu )
{
  if( menu != NULL )
  {
    Menu_RunCloseScript( menu );
    menu->window.flags &= ~( WINDOW_VISIBLE | WINDOW_HASFOCUS );

    if( openMenuCount > 0 )
      openMenuCount--;

    if( openMenuCount > 0 )
      Menus_Activate( menuStack[ openMenuCount - 1 ] );
  }
}

void Menus_CloseByName( const char *p )
{
  Menus_Close( Menus_FindByName( p ) );
}

void Menus_CloseAll( void )
{
  int i;

  // Close any menus on the stack first
  if( openMenuCount > 0 )
  {
    for( i = openMenuCount; i > 0; i-- )
      Menus_Close( menuStack[ i ] );

    openMenuCount = 0;
  }

  // Close all other menus
  for( i = 0; i < menuCount; i++ )
    Menus_Close( &Menus[ i ] );

  g_editingField = qfalse;
  g_waitingForKey = qfalse;
  g_comboBoxItem = NULL;
}


void Script_Show( itemDef_t *item, char **args )
{
  const char *name;

  if( String_Parse( args, &name ) )
    Menu_ShowItemByName( item->parent, name, qtrue );
}

void Script_Hide( itemDef_t *item, char **args )
{
  const char *name;

  if( String_Parse( args, &name ) )
    Menu_ShowItemByName( item->parent, name, qfalse );
}

void Script_FadeIn( itemDef_t *item, char **args )
{
  const char *name;

  if( String_Parse( args, &name ) )
    Menu_FadeItemByName( item->parent, name, qfalse );
}

void Script_FadeOut( itemDef_t *item, char **args )
{
  const char *name;

  if( String_Parse( args, &name ) )
    Menu_FadeItemByName( item->parent, name, qtrue );
}



void Script_Open( itemDef_t *item, char **args )
{
  const char *name;

  if( String_Parse( args, &name ) )
    Menus_ActivateByName( name );
}

void Script_ConditionalOpen( itemDef_t *item, char **args )
{
  const char *cvar;
  const char *name1;
  const char *name2;
  float           val;

  if( String_Parse( args, &cvar ) && String_Parse( args, &name1 ) && String_Parse( args, &name2 ) )
  {
    val = DC->getCVarValue( cvar );

    if( val == 0.0f )
      Menus_ActivateByName( name2 );
    else
      Menus_ActivateByName( name1 );
  }
}

void Script_Close( itemDef_t *item, char **args )
{
  const char *name;

  if( String_Parse( args, &name ) )
    Menus_CloseByName( name );
}

void Menu_TransitionItemByName( menuDef_t *menu, const char *p, rectDef_t rectFrom, rectDef_t rectTo,
                                int time, float amt )
{
  itemDef_t *item;
  int i;
  int count = Menu_ItemsMatchingGroup( menu, p );

  for( i = 0; i < count; i++ )
  {
    item = Menu_GetMatchingItemByNumber( menu, i, p );

    if( item != NULL )
    {
      item->window.flags |= ( WINDOW_INTRANSITION | WINDOW_VISIBLE );
      item->window.offsetTime = time;
      memcpy( &item->window.rectClient, &rectFrom, sizeof( rectDef_t ) );
      memcpy( &item->window.rectEffects, &rectTo, sizeof( rectDef_t ) );
      item->window.rectEffects2.x = abs( rectTo.x - rectFrom.x ) / amt;
      item->window.rectEffects2.y = abs( rectTo.y - rectFrom.y ) / amt;
      item->window.rectEffects2.w = abs( rectTo.w - rectFrom.w ) / amt;
      item->window.rectEffects2.h = abs( rectTo.h - rectFrom.h ) / amt;
      Item_UpdatePosition( item );
    }
  }
}


void Script_Transition( itemDef_t *item, char **args )
{
  const char *name;
  rectDef_t rectFrom, rectTo;
  int time;
  float amt;

  if( String_Parse( args, &name ) )
  {
    if( Rect_Parse( args, &rectFrom ) && Rect_Parse( args, &rectTo ) &&
        Int_Parse( args, &time ) && Float_Parse( args, &amt ) )
    {
      Menu_TransitionItemByName( item->parent, name, rectFrom, rectTo, time, amt );
    }
  }
}


void Menu_OrbitItemByName( menuDef_t *menu, const char *p, float x, float y, float cx, float cy, int time )
{
  itemDef_t *item;
  int i;
  int count = Menu_ItemsMatchingGroup( menu, p );

  for( i = 0; i < count; i++ )
  {
    item = Menu_GetMatchingItemByNumber( menu, i, p );

    if( item != NULL )
    {
      item->window.flags |= ( WINDOW_ORBITING | WINDOW_VISIBLE );
      item->window.offsetTime = time;
      item->window.rectEffects.x = cx;
      item->window.rectEffects.y = cy;
      item->window.rectClient.x = x;
      item->window.rectClient.y = y;
      Item_UpdatePosition( item );
    }
  }
}


void Script_Orbit( itemDef_t *item, char **args )
{
  const char *name;
  float cx, cy, x, y;
  int time;

  if( String_Parse( args, &name ) )
  {
    if( Float_Parse( args, &x ) && Float_Parse( args, &y ) &&
        Float_Parse( args, &cx ) && Float_Parse( args, &cy ) && Int_Parse( args, &time ) )
    {
      Menu_OrbitItemByName( item->parent, name, x, y, cx, cy, time );
    }
  }
}



void Script_SetFocus( itemDef_t *item, char **args )
{
  const char *name;
  itemDef_t *focusItem;

  if( String_Parse( args, &name ) )
  {
    focusItem = Menu_FindItemByName( item->parent, name );

    if( focusItem && !( focusItem->window.flags & WINDOW_DECORATION ) )
    {
      Menu_ClearFocus( item->parent );
      focusItem->window.flags |= WINDOW_HASFOCUS;

      if( focusItem->onFocus )
        Item_RunScript( focusItem, focusItem->onFocus );

      // Edit fields get activated too
      if( Item_IsEditField( focusItem ) )
      {
        g_editingField = qtrue;
        g_editItem = focusItem;
      }

      if( DC->Assets.itemFocusSound )
        DC->startLocalSound( DC->Assets.itemFocusSound, CHAN_LOCAL_SOUND );
    }
  }
}

void Script_Reset( itemDef_t *item, char **args )
{
  const char *name;
  itemDef_t *resetItem;

  if( String_Parse( args, &name ) )
  {
    resetItem = Menu_FindItemByName( item->parent, name );

    if( resetItem )
    {
      if( Item_IsListBox( resetItem ) )
      {
        resetItem->cursorPos = DC->feederInitialise( resetItem->feederID );
        Item_ListBox_SetStartPos( resetItem, 0 );
        DC->feederSelection( resetItem->feederID, resetItem->cursorPos );
      }
    }
  }
}

void Script_SetPlayerModel( itemDef_t *item, char **args )
{
  const char *name;

  if( String_Parse( args, &name ) )
    DC->setCVar( "team_model", name );
}

void Script_SetPlayerHead( itemDef_t *item, char **args )
{
  const char *name;

  if( String_Parse( args, &name ) )
    DC->setCVar( "team_headmodel", name );
}

void Script_SetCvar( itemDef_t *item, char **args )
{
  const char *cvar, *val;

  if( String_Parse( args, &cvar ) && String_Parse( args, &val ) )
    DC->setCVar( cvar, val );

}

void Script_Exec( itemDef_t *item, char **args )
{
  const char *val;

  if( String_Parse( args, &val ) )
    DC->executeText( EXEC_APPEND, va( "%s ; ", val ) );
}

void Script_Play( itemDef_t *item, char **args )
{
  const char *val;

  if( String_Parse( args, &val ) )
    DC->startLocalSound( DC->registerSound( val, qfalse ), CHAN_LOCAL_SOUND );
}

void Script_playLooped( itemDef_t *item, char **args )
{
  const char *val;

  if( String_Parse( args, &val ) )
  {
    DC->stopBackgroundTrack();
    DC->startBackgroundTrack( val, val );
  }
}

static ID_INLINE float UI_EmoticonHeight( fontInfo_t *font, float scale )
{
  return font->glyphs[ (int)'[' ].height * scale * font->glyphScale;
}

static ID_INLINE float UI_EmoticonWidth( fontInfo_t *font, float scale )
{
  return UI_EmoticonHeight( font, scale ) * DC->aspectScale;
}

void UI_EscapeEmoticons( char *dest, const char *src, int destsize )
{
  int len;
  qboolean escaped;

  for( ; *src && destsize > 1; src++, destsize-- )
  {
    if( UI_Text_IsEmoticon( src, &escaped, &len, NULL, NULL ) && !escaped )
    {
      *dest++ = '[';
      destsize--;
    }

    *dest++ = *src;
  }

  *dest++ = '\0';
}

qboolean UI_Text_IsEmoticon( const char *s, qboolean *escaped,
                             int *length, qhandle_t *h, int *width )
{
  const char *p = s;
  char emoticon[ MAX_EMOTICON_NAME_LEN ];
  int i;

  if( *p != '[' )
    return qfalse;
  p++;

  if( *p == '[' )
  {
    *escaped = qtrue;
    p++;
  }
  else
    *escaped = qfalse;

  for( *length = 0; p[ *length ] != ']'; ( *length )++ )
  {
    if( !p[ *length ] || *length == MAX_EMOTICON_NAME_LEN - 1 )
      return qfalse;

    emoticon[ *length ] = p[ *length ];
  }
  emoticon[ *length ] = '\0';

  for( i = 0; i < DC->Assets.emoticonCount; i++ )
    if( !Q_stricmp( DC->Assets.emoticons[ i ].name, emoticon ) )
      break;

  if( i == DC->Assets.emoticonCount )
    return qfalse;

  if( h )
    *h = DC->Assets.emoticons[ i ].shader;
  if( width )
    *width = DC->Assets.emoticons[ i ].width;

  ( *length ) += 2;

  if( *escaped )
    ( *length )++;

  return qtrue;
}

static float UI_Parse_Indent( const char **text )
{
  char        indentWidth[ 32 ];
  char        *indentWidthPtr;
  const char  *p = *text;
  int         numDigits;
  float       pixels;

  while( isdigit( *p ) || *p == '.' )
    p++;

  if( *p != INDENT_MARKER )
    return 0.0f;

  numDigits = ( p - *text );

  if( numDigits > sizeof( indentWidth ) - 1 )
    return 0.0f;

  strncpy( indentWidth, *text, numDigits );

  indentWidth[ numDigits ] = '\0';
  indentWidthPtr = indentWidth;

  if( !Float_Parse( &indentWidthPtr, &pixels ) )
    return 0.0f;

  (*text) += ( numDigits + 1 );

  return pixels;
}

static ID_INLINE fontInfo_t *UI_FontForScale( float scale )
{
  if( scale <= DC->smallFontScale )
    return &DC->Assets.smallFont;
  else if( scale >= DC->bigFontScale )
    return &DC->Assets.bigFont;
  else
    return &DC->Assets.textFont;
}

float UI_Char_Width( const char **text, float scale )
{
  glyphInfo_t *glyph;
  fontInfo_t  *font;
  int         emoticonLen;
  qboolean    emoticonEscaped;
  int         emoticonWidth;

  if( text && *text )
  {
    if( Q_IsColorString( *text ) )
    {
      *text += 2;
      return 0.0f;
    }

    if( **text == INDENT_MARKER )
    {
      (*text)++;
      return 0.0f;
    }

    font = UI_FontForScale( scale );

    if( UI_Text_IsEmoticon( *text, &emoticonEscaped, &emoticonLen,
                            NULL, &emoticonWidth ) )
    {
      if( emoticonEscaped )
        (*text)++;
      else
      {
        *text += emoticonLen;
        return emoticonWidth * UI_EmoticonWidth( font, scale );
      }
    }

    (*text)++;

    glyph = &font->glyphs[ (int)**text ];
    return glyph->xSkip * DC->aspectScale * scale * font->glyphScale;
  }

  return 0.0f;
}

float UI_Text_Width( const char *text, float scale )
{
  float       out;
  const char  *s = text;
  float       indentWidth = 0.0f;

  out = 0.0f;

  if( text )
  {
    indentWidth = UI_Parse_Indent( &s );

    while( *s )
      out += UI_Char_Width( &s, scale );
  }

  return out + indentWidth;
}

float UI_Text_Height( const char *text, float scale )
{
  float       max;
  glyphInfo_t *glyph;
  float       useScale;
  const char  *s = text;
  fontInfo_t  *font = UI_FontForScale( scale );

  useScale = scale * font->glyphScale;
  max = 0;

  if( text )
  {
    while( s && *s )
    {
      if( Q_IsColorString( s ) )
      {
        s += 2;
        continue;
      }
      else
      {
        glyph = &font->glyphs[( int )*s];

        if( max < glyph->height )
          max = glyph->height;

        s++;
      }
    }
  }

  return max * useScale;
}

float UI_Text_EmWidth( float scale )
{
  return UI_Text_Width( "M", scale );
}

float UI_Text_EmHeight( float scale )
{
  return UI_Text_Height( "M", scale );
}


/*
================
UI_AdjustFrom640

Adjusted for resolution and screen aspect ratio
================
*/
void UI_AdjustFrom640( float *x, float *y, float *w, float *h )
{
  *x *= DC->xscale;
  *y *= DC->yscale;
  *w *= DC->xscale;
  *h *= DC->yscale;
}

/*
================
UI_SetClipRegion
=================
*/
void UI_SetClipRegion( float x, float y, float w, float h )
{
  vec4_t clip;

  UI_AdjustFrom640( &x, &y, &w, &h );

  clip[ 0 ] = x;
  clip[ 1 ] = y;
  clip[ 2 ] = x + w;
  clip[ 3 ] = y + h;

  trap_R_SetClipRegion( clip );
}

/*
================
UI_ClearClipRegion
=================
*/
void UI_ClearClipRegion( void )
{
  trap_R_SetClipRegion( NULL );
}

static void UI_Text_PaintChar( float x, float y, float scale,
                               glyphInfo_t *glyph, float size )
{
  float w, h;

  w = glyph->imageWidth;
  h = glyph->imageHeight;

  if( size > 0.0f )
  {
    float half = size * 0.5f * scale;
    x -= half;
    y -= half;
    w += size;
    h += size;
  }

  w *= ( DC->aspectScale * scale );
  h *= scale;
  y -= ( glyph->top * scale );
  UI_AdjustFrom640( &x, &y, &w, &h );

  DC->drawStretchPic( x, y, w, h,
                      glyph->s,
                      glyph->t,
                      glyph->s2,
                      glyph->t2,
                      glyph->glyph );
}

static void UI_Text_Paint_Generic( float x, float y, float scale, float gapAdjust,
                                   const char *text, vec4_t color, int style,
                                   int limit, float *maxX,
                                   int cursorPos, char cursor )
{
  const char  *s = text;
  int         len;
  int         count = 0;
  vec4_t      newColor;
  fontInfo_t  *font = UI_FontForScale( scale );
  glyphInfo_t *glyph;
  float       useScale;
  qhandle_t   emoticonHandle = 0;
  float       emoticonH, emoticonW;
  qboolean    emoticonEscaped;
  int         emoticonLen = 0;
  int         emoticonWidth;
  int         cursorX = -1;

  if( !text )
    return;

  useScale = scale * font->glyphScale;

  emoticonH = UI_EmoticonHeight( font, scale );
  emoticonW = UI_EmoticonWidth( font, scale );

  len = strlen( text );
  if( limit > 0 && len > limit )
    len = limit;

  DC->setColor( color );
  memcpy( &newColor[0], &color[0], sizeof( vec4_t ) );

  x += UI_Parse_Indent( &s );

  while( s && *s && count < len )
  {
    const char *t = s;
    float charWidth = UI_Char_Width( &t, scale );
    glyph = &font->glyphs[ (int)*s ];

    if( maxX && charWidth + x > *maxX )
    {
      *maxX = 0;
      break;
    }

    if( cursorPos < 0 )
    {
      if( Q_IsColorString( s ) )
      {
        memcpy( newColor, g_color_table[ColorIndex( *( s+1 ) )],
                sizeof( newColor ) );
        newColor[3] = color[3];
        DC->setColor( newColor );
        s += 2;
        continue;
      }

      if( *s == INDENT_MARKER )
      {
        s++;
        continue;
      }

      if( UI_Text_IsEmoticon( s, &emoticonEscaped, &emoticonLen,
                              &emoticonHandle, &emoticonWidth ) )
      {
        if( emoticonEscaped )
          s++;
        else
        {
          float yadj = useScale * glyph->top;

          DC->setColor( NULL );
          DC->drawHandlePic( x, y - yadj,
                             ( emoticonW * emoticonWidth ),
                             emoticonH,
                             emoticonHandle );
          DC->setColor( newColor );
          x += ( emoticonW * emoticonWidth ) + gapAdjust;
          s += emoticonLen;
          count += emoticonWidth;
          continue;
        }
      }
    }

    if( style == ITEM_TEXTSTYLE_SHADOWED ||
       style == ITEM_TEXTSTYLE_SHADOWEDMORE )
    {
      int ofs;

      if( style == ITEM_TEXTSTYLE_SHADOWED )
        ofs = 1;
      else
        ofs = 2;
      colorBlack[3] = newColor[3];
      DC->setColor( colorBlack );
      UI_Text_PaintChar( x + ofs, y + ofs, useScale, glyph, 0.0f );
      DC->setColor( newColor );
      colorBlack[3] = 1.0f;
    }
    else if( style == ITEM_TEXTSTYLE_NEON )
    {
      vec4_t glow;

      memcpy( &glow[0], &newColor[0], sizeof( vec4_t ) );
      glow[ 3 ] *= 0.2f;

      DC->setColor( glow );
      UI_Text_PaintChar( x, y, useScale, glyph, 6.0f );
      UI_Text_PaintChar( x, y, useScale, glyph, 4.0f );
      DC->setColor( newColor );
      UI_Text_PaintChar( x, y, useScale, glyph, 2.0f );

      DC->setColor( colorWhite );
    }

    UI_Text_PaintChar( x, y, useScale, glyph, 0.0f );

    if( count == cursorPos )
      cursorX = x;

    x += ( glyph->xSkip * DC->aspectScale * useScale ) + gapAdjust;
    s++;
    count++;

  }

  if( maxX )
      *maxX = x;

  // paint cursor
  if( cursorPos >= 0 )
  {
    if( cursorPos == len )
      cursorX = x;

    if( cursorX >= 0 && !( ( DC->realTime / BLINK_DIVISOR ) & 1 ) )
    {
      glyph = &font->glyphs[ (int)cursor ];
      UI_Text_PaintChar( cursorX, y, useScale, glyph, 0.0f );
    }
  }

  DC->setColor( NULL );
}

void UI_Text_Paint_Limit( float *maxX, float x, float y, float scale,
                          vec4_t color, const char *text, float adjust, int limit )
{
  UI_Text_Paint_Generic( x, y, scale, adjust,
                         text, color, ITEM_TEXTSTYLE_NORMAL,
                         limit, maxX, -1, 0 );
}

void UI_Text_Paint( float x, float y, float scale, vec4_t color, const char *text,
                    float adjust, int limit, int style )
{
  UI_Text_Paint_Generic( x, y, scale, adjust,
                         text, color, style,
                         limit, NULL, -1, 0 );
}

void UI_Text_PaintWithCursor( float x, float y, float scale, vec4_t color, const char *text,
                              int cursorPos, char cursor, int limit, int style )
{
  UI_Text_Paint_Generic( x, y, scale, 0.0,
                         text, color, style,
                         limit, NULL, cursorPos, cursor );
}

commandDef_t commandList[] =
  {
    {"close", &Script_Close},                     // menu
    {"conditionalopen", &Script_ConditionalOpen}, // menu
    {"exec", &Script_Exec},                       // group/name
    {"fadein", &Script_FadeIn},                   // group/name
    {"fadeout", &Script_FadeOut},                 // group/name
    {"hide", &Script_Hide},                       // group/name
    {"open", &Script_Open},                       // menu
    {"orbit", &Script_Orbit},                     // group/name
    {"play", &Script_Play},                       // group/name
    {"playlooped", &Script_playLooped},           // group/name
    {"reset", &Script_Reset},                     // resets the state of the item argument
    {"setasset", &Script_SetAsset},               // works on this
    {"setbackground", &Script_SetBackground},     // works on this
    {"setcolor", &Script_SetColor},               // works on this
    {"setcvar", &Script_SetCvar},                 // group/name
    {"setfocus", &Script_SetFocus},               // sets this background color to team color
    {"setitemcolor", &Script_SetItemColor},       // group/name
    {"setplayerhead", &Script_SetPlayerHead},     // sets this background color to team color
    {"setplayermodel", &Script_SetPlayerModel},   // sets this background color to team color
    {"show", &Script_Show},                       // group/name
    {"transition", &Script_Transition},           // group/name
  };

static size_t scriptCommandCount = ARRAY_LEN( commandList );

// despite what lcc thinks, we do not get cmdcmp here
static int commandComp( const void *a, const void *b )
{
  return Q_stricmp( (const char *)a, ((commandDef_t *)b)->name );
}

void Item_RunScript( itemDef_t *item, const char *s )
{
  char script[1024], *p;
  commandDef_t *cmd;
  memset( script, 0, sizeof( script ) );

  if( item && s && s[0] )
  {
    Q_strcat( script, 1024, s );
    p = script;

    while( 1 )
    {
      const char *command;
      // expect command then arguments, ; ends command, NULL ends script

      if( !String_Parse( &p, &command ) )
        return;

      if( command[0] == ';' && command[1] == '\0' )
        continue;

      cmd = bsearch( command, commandList, scriptCommandCount,
        sizeof( commandDef_t ), commandComp );
      if( cmd )
        cmd->handler( item, &p );
      else
      // not in our auto list, pass to handler
        DC->runScript( &p );
    }
  }
}


qboolean Item_EnableShowViaCvar( itemDef_t *item, int flag )
{
  char script[1024], *p;
  memset( script, 0, sizeof( script ) );

  if( item && item->enableCvar && *item->enableCvar && item->cvarTest && *item->cvarTest )
  {
    char buff[1024];
    DC->getCVarString( item->cvarTest, buff, sizeof( buff ) );

    Q_strcat( script, 1024, item->enableCvar );
    p = script;

    while( 1 )
    {
      const char *val;
      // expect value then ; or NULL, NULL ends list

      if( !String_Parse( &p, &val ) )
        return ( item->cvarFlags & flag ) ? qfalse : qtrue;

      if( val[0] == ';' && val[1] == '\0' )
        continue;

      // enable it if any of the values are true
      if( item->cvarFlags & flag )
      {
        if( Q_stricmp( buff, val ) == 0 )
          return qtrue;
      }
      else
      {
        // disable it if any of the values are true

        if( Q_stricmp( buff, val ) == 0 )
          return qfalse;
      }

    }

    return ( item->cvarFlags & flag ) ? qfalse : qtrue;
  }

  return qtrue;
}


// will optionaly set focus to this item
qboolean Item_SetFocus( itemDef_t *item, float x, float y )
{
  int i;
  itemDef_t *oldFocus;
  sfxHandle_t *sfx = &DC->Assets.itemFocusSound;
  qboolean playSound = qfalse;
  menuDef_t *parent;
  // sanity check, non-null, not a decoration and does not already have the focus

  if( item == NULL || item->window.flags & WINDOW_DECORATION ||
      item->window.flags & WINDOW_HASFOCUS || !( item->window.flags & WINDOW_VISIBLE ) )
  {
    return qfalse;
  }

  parent = ( menuDef_t* )item->parent;

  // items can be enabled and disabled based on cvars

  if( item->cvarFlags & ( CVAR_ENABLE | CVAR_DISABLE ) && !Item_EnableShowViaCvar( item, CVAR_ENABLE ) )
    return qfalse;

  if( item->cvarFlags & ( CVAR_SHOW | CVAR_HIDE ) && !Item_EnableShowViaCvar( item, CVAR_SHOW ) )
    return qfalse;

  oldFocus = Menu_ClearFocus( item->parent );

  if( item->type == ITEM_TYPE_TEXT )
  {
    rectDef_t r;
    r = item->textRect;
    r.y -= r.h;

    if( Rect_ContainsPoint( &r, x, y ) )
    {
      item->window.flags |= WINDOW_HASFOCUS;

      if( item->focusSound )
        sfx = &item->focusSound;

      playSound = qtrue;
    }
    else
    {
      if( oldFocus )
      {
        oldFocus->window.flags |= WINDOW_HASFOCUS;

        if( oldFocus->onFocus )
          Item_RunScript( oldFocus, oldFocus->onFocus );
      }
    }
  }
  else
  {
    item->window.flags |= WINDOW_HASFOCUS;

    if( item->onFocus )
      Item_RunScript( item, item->onFocus );

    if( item->focusSound )
      sfx = &item->focusSound;

    playSound = qtrue;
  }

  if( playSound && sfx )
    DC->startLocalSound( *sfx, CHAN_LOCAL_SOUND );

  for( i = 0; i < parent->itemCount; i++ )
  {
    if( parent->items[i] == item )
    {
      parent->cursorItem = i;
      break;
    }
  }

  return qtrue;
}

static float Item_ListBox_HeightForNumItems( itemDef_t *item, int numItems )
{
  listBoxDef_t *listPtr = item->typeData.list;

  return ( listPtr->elementHeight * numItems ) + 2.0f;
}

static int Item_ListBox_NumItemsForItemHeight( itemDef_t *item )
{
  listBoxDef_t *listPtr = item->typeData.list;

  if( item->type == ITEM_TYPE_COMBOBOX )
    return listPtr->dropItems;
  else
    return ( ( item->window.rect.h - 2.0f ) / listPtr->elementHeight );
}

int Item_ListBox_MaxScroll( itemDef_t *item )
{
  int total = DC->feederCount( item->feederID );
  int max = total - Item_ListBox_NumItemsForItemHeight( item );

  if( max < 0 )
    return 0;

  return max;
}

static float oldComboBoxY;
static float oldComboBoxH;

static qboolean Item_ComboBox_MaybeCastToListBox( itemDef_t *item )
{
  listBoxDef_t *listPtr = item->typeData.list;
  qboolean cast = g_comboBoxItem != NULL &&
    ( item->type == ITEM_TYPE_COMBOBOX );

  if( cast )
  {
    oldComboBoxY = item->window.rect.y;
    oldComboBoxH = item->window.rect.h;

    item->window.rect.y += item->window.rect.h;
    item->window.rect.h = Item_ListBox_HeightForNumItems( item, listPtr->dropItems );
    item->type = ITEM_TYPE_LISTBOX;
  }

  return cast;
}

static void Item_ComboBox_MaybeUnCastFromListBox( itemDef_t *item, qboolean unCast )
{
  if( unCast )
  {
    item->window.rect.y = oldComboBoxY;
    item->window.rect.h = oldComboBoxH;
    item->type = ITEM_TYPE_COMBOBOX;
  }
}

static void Item_ListBox_SetStartPos( itemDef_t *item, int startPos )
{
  listBoxDef_t  *listPtr = item->typeData.list;
  int           total = DC->feederCount( item->feederID );
  int           max = Item_ListBox_MaxScroll( item );

  if( startPos < 0 )
    listPtr->startPos = 0;
  else if( startPos > max )
    listPtr->startPos = max;
  else
    listPtr->startPos = startPos;

  listPtr->endPos = listPtr->startPos + MIN( ( total - listPtr->startPos ),
    Item_ListBox_NumItemsForItemHeight( item ) );
}

float Item_ListBox_ThumbPosition( itemDef_t *item )
{
  float max, pos, size;
  float startPos = (float)item->typeData.list->startPos;

  max = Item_ListBox_MaxScroll( item );
  size = SCROLLBAR_SLIDER_HEIGHT( item );

  if( max > 0.0f )
    pos = ( size - SCROLLBAR_ARROW_HEIGHT ) / max;
  else
    pos = 0.0f;

  pos *= startPos;

  return SCROLLBAR_SLIDER_Y( item ) + pos;
}

float Item_ListBox_ThumbDrawPosition( itemDef_t *item )
{
  if( itemCapture == item )
  {
    float min = SCROLLBAR_SLIDER_Y( item );
    float max = min + SCROLLBAR_SLIDER_HEIGHT( item ) - SCROLLBAR_ARROW_HEIGHT;
    float halfThumbSize = SCROLLBAR_ARROW_HEIGHT / 2.0f;

    if( DC->cursory >= min + halfThumbSize && DC->cursory <= max + halfThumbSize )
      return DC->cursory - halfThumbSize;
  }

  return Item_ListBox_ThumbPosition( item );
}

float Item_Slider_ThumbPosition( itemDef_t *item )
{
  float value, range, x;
  editFieldDef_t *editDef = item->typeData.edit;

  if( item->text )
    x = item->textRect.x + item->textRect.w + ITEM_VALUE_OFFSET;
  else
    x = item->window.rect.x;

  if( editDef == NULL && item->cvar )
    return x;

  value = DC->getCVarValue( item->cvar );

  if( value < editDef->minVal )
    value = editDef->minVal;
  else if( value > editDef->maxVal )
    value = editDef->maxVal;

  range = editDef->maxVal - editDef->minVal;
  value -= editDef->minVal;
  value /= range;
  value *= SLIDER_WIDTH;
  x += value;

  return x;
}

static float Item_Slider_VScale( itemDef_t *item )
{
  if( SLIDER_THUMB_HEIGHT > item->window.rect.h )
    return item->window.rect.h / SLIDER_THUMB_HEIGHT;
  else
    return 1.0f;
}

int Item_Slider_OverSlider( itemDef_t *item, float x, float y )
{
  rectDef_t r;
  float vScale = Item_Slider_VScale( item );

  r.x = Item_Slider_ThumbPosition( item ) - ( SLIDER_THUMB_WIDTH / 2 );
  r.y = item->textRect.y - item->textRect.h +
        ( ( item->textRect.h - ( SLIDER_THUMB_HEIGHT * vScale ) ) / 2.0f );
  r.w = SLIDER_THUMB_WIDTH;
  r.h = SLIDER_THUMB_HEIGHT * vScale;

  if( Rect_ContainsPoint( &r, x, y ) )
    return WINDOW_LB_THUMB;

  return 0;
}

int Item_ListBox_OverLB( itemDef_t *item, float x, float y )
{
  rectDef_t r;
  int thumbstart;

  r.x = SCROLLBAR_SLIDER_X( item );
  r.y = SCROLLBAR_Y( item );
  r.w = SCROLLBAR_ARROW_WIDTH;
  r.h = SCROLLBAR_ARROW_HEIGHT;

  if( Rect_ContainsPoint( &r, x, y ) )
    return WINDOW_LB_UPARROW;

  r.y = SCROLLBAR_SLIDER_Y( item ) + SCROLLBAR_SLIDER_HEIGHT( item );

  if( Rect_ContainsPoint( &r, x, y ) )
    return WINDOW_LB_DOWNARROW;

  thumbstart = Item_ListBox_ThumbPosition( item );
  r.y = thumbstart;

  if( Rect_ContainsPoint( &r, x, y ) )
    return WINDOW_LB_THUMB;

  r.y = SCROLLBAR_SLIDER_Y( item );
  r.h = thumbstart - r.y;

  if( Rect_ContainsPoint( &r, x, y ) )
    return WINDOW_LB_PGUP;

  r.y = thumbstart + SCROLLBAR_ARROW_HEIGHT;
  r.h = ( SCROLLBAR_SLIDER_Y( item ) + SCROLLBAR_SLIDER_HEIGHT( item ) ) - r.y;

  if( Rect_ContainsPoint( &r, x, y ) )
    return WINDOW_LB_PGDN;

  return 0;
}


void Item_ListBox_MouseEnter( itemDef_t *item, float x, float y )
{
  rectDef_t     r;
  listBoxDef_t  *listPtr = item->typeData.list;
  int           listBoxFlags = ( WINDOW_LB_UPARROW | WINDOW_LB_DOWNARROW | WINDOW_LB_THUMB |
                                 WINDOW_LB_PGUP | WINDOW_LB_PGDN );
  int           total = DC->feederCount( item->feederID );

  item->window.flags &= ~listBoxFlags;
  item->window.flags |= Item_ListBox_OverLB( item, x, y );

  if( !( item->window.flags & listBoxFlags ) )
  {
    r.x = SCROLLBAR_X( item );
    r.y = SCROLLBAR_Y( item );
    r.w = SCROLLBAR_W( item );
    r.h = listPtr->elementHeight *
      MIN( Item_ListBox_NumItemsForItemHeight( item ), total );

    if( Rect_ContainsPoint( &r, x, y ) )
    {
      listPtr->cursorPos = (int)( ( y - r.y ) / listPtr->elementHeight ) + listPtr->startPos;

      if( listPtr->cursorPos >= listPtr->endPos )
        listPtr->cursorPos = listPtr->endPos - 1;
    }
    else
      listPtr->cursorPos = -1;
  }
}

void Item_MouseEnter( itemDef_t *item, float x, float y )
{
  rectDef_t r;

  if( item )
  {
    r = item->textRect;
    r.y -= r.h;
    // in the text rect?

    // items can be enabled and disabled based on cvars

    if( item->cvarFlags & ( CVAR_ENABLE | CVAR_DISABLE ) && !Item_EnableShowViaCvar( item, CVAR_ENABLE ) )
      return;

    if( item->cvarFlags & ( CVAR_SHOW | CVAR_HIDE ) && !Item_EnableShowViaCvar( item, CVAR_SHOW ) )
      return;

    if( Rect_ContainsPoint( &r, x, y ) )
    {
      if( !( item->window.flags & WINDOW_MOUSEOVERTEXT ) )
      {
        Item_RunScript( item, item->mouseEnterText );
        item->window.flags |= WINDOW_MOUSEOVERTEXT;
      }

      if( !( item->window.flags & WINDOW_MOUSEOVER ) )
      {
        Item_RunScript( item, item->mouseEnter );
        item->window.flags |= WINDOW_MOUSEOVER;
      }

    }
    else
    {
      // not in the text rect

      if( item->window.flags & WINDOW_MOUSEOVERTEXT )
      {
        // if we were
        Item_RunScript( item, item->mouseExitText );
        item->window.flags &= ~WINDOW_MOUSEOVERTEXT;
      }

      if( !( item->window.flags & WINDOW_MOUSEOVER ) )
      {
        Item_RunScript( item, item->mouseEnter );
        item->window.flags |= WINDOW_MOUSEOVER;
      }

      if( item->type == ITEM_TYPE_LISTBOX )
        Item_ListBox_MouseEnter( item, x, y );
    }
  }
}

void Item_MouseLeave( itemDef_t *item )
{
  if( item )
  {
    if( item->window.flags & WINDOW_MOUSEOVERTEXT )
    {
      Item_RunScript( item, item->mouseExitText );
      item->window.flags &= ~WINDOW_MOUSEOVERTEXT;
    }

    Item_RunScript( item, item->mouseExit );
    item->window.flags &= ~( WINDOW_LB_DOWNARROW | WINDOW_LB_UPARROW );
  }
}

itemDef_t *Menu_HitTest( menuDef_t *menu, float x, float y )
{
  int i;

  for( i = 0; i < menu->itemCount; i++ )
  {
    if( Rect_ContainsPoint( &menu->items[i]->window.rect, x, y ) )
      return menu->items[i];
  }

  return NULL;
}

void Item_SetMouseOver( itemDef_t *item, qboolean focus )
{
  if( item )
  {
    if( focus )
      item->window.flags |= WINDOW_MOUSEOVER;
    else
      item->window.flags &= ~WINDOW_MOUSEOVER;
  }
}


qboolean Item_OwnerDraw_HandleKey( itemDef_t *item, int key )
{
  if( item && DC->ownerDrawHandleKey )
    return DC->ownerDrawHandleKey( item->window.ownerDraw, key );

  return qfalse;
}

qboolean Item_ListBox_HandleKey( itemDef_t *item, int key, qboolean down, qboolean force )
{
  listBoxDef_t *listPtr = item->typeData.list;
  int count = DC->feederCount( item->feederID );
  int viewmax;

  if( force || ( Rect_ContainsPoint( &item->window.rect, DC->cursorx, DC->cursory ) &&
      item->window.flags & WINDOW_HASFOCUS ) )
  {
    viewmax = Item_ListBox_NumItemsForItemHeight( item );

    switch( key )
    {
      case K_MOUSE1:
      case K_MOUSE2:
        if( item->window.flags & WINDOW_LB_UPARROW )
          Item_ListBox_SetStartPos( item, listPtr->startPos - 1 );
        else if( item->window.flags & WINDOW_LB_DOWNARROW )
          Item_ListBox_SetStartPos( item, listPtr->startPos + 1 );
        else if( item->window.flags & WINDOW_LB_PGUP )
          Item_ListBox_SetStartPos( item, listPtr->startPos - viewmax );
        else if( item->window.flags & WINDOW_LB_PGDN )
          Item_ListBox_SetStartPos( item, listPtr->startPos + viewmax );
        else if( item->window.flags & WINDOW_LB_THUMB )
          break; // Handled by capture function
        else
        {
          // Select an item
          qboolean runDoubleClick = qfalse;

          // Mouse isn't over an item
          if( listPtr->cursorPos < 0 )
            break;

          if( item->cursorPos != listPtr->cursorPos )
          {
            item->cursorPos = listPtr->cursorPos;
            DC->feederSelection( item->feederID, item->cursorPos );
          }

          runDoubleClick = DC->realTime < lastListBoxClickTime && listPtr->doubleClick;
          lastListBoxClickTime = DC->realTime + DOUBLE_CLICK_DELAY;

          // Made a selection, so close combobox
          if( g_comboBoxItem != NULL )
          {
            if( listPtr->doubleClick )
              runDoubleClick = qtrue;

            g_comboBoxItem = NULL;
          }

          if( runDoubleClick )
            Item_RunScript( item, listPtr->doubleClick );
        }

        break;

      case K_MWHEELUP:
        Item_ListBox_SetStartPos( item, listPtr->startPos - 1 );
        break;

      case K_MWHEELDOWN:
        Item_ListBox_SetStartPos( item, listPtr->startPos + 1 );
        break;

      case K_ENTER:
        // Invoke the doubleClick handler when enter is pressed
        if( listPtr->doubleClick )
          Item_RunScript( item, listPtr->doubleClick );

        break;

      case K_PGUP:
      case K_KP_PGUP:
        if( !listPtr->notselectable )
        {
          listPtr->cursorPos -= viewmax;

          if( listPtr->cursorPos < 0 )
            listPtr->cursorPos = 0;

          if( listPtr->cursorPos < listPtr->startPos )
            Item_ListBox_SetStartPos( item, listPtr->cursorPos );

          if( listPtr->cursorPos >= listPtr->startPos + viewmax )
            Item_ListBox_SetStartPos( item, listPtr->cursorPos - viewmax + 1 );

          item->cursorPos = listPtr->cursorPos;
          DC->feederSelection( item->feederID, item->cursorPos );
        }
        else
          Item_ListBox_SetStartPos( item, listPtr->startPos - viewmax );

        break;

      case K_PGDN:
      case K_KP_PGDN:
        if( !listPtr->notselectable )
        {
          listPtr->cursorPos += viewmax;

          if( listPtr->cursorPos < listPtr->startPos )
            Item_ListBox_SetStartPos( item, listPtr->cursorPos );

          if( listPtr->cursorPos >= count )
            listPtr->cursorPos = count - 1;

          if( listPtr->cursorPos >= listPtr->startPos + viewmax )
            Item_ListBox_SetStartPos( item, listPtr->cursorPos - viewmax + 1 );

          item->cursorPos = listPtr->cursorPos;
          DC->feederSelection( item->feederID, item->cursorPos );
        }
        else
          Item_ListBox_SetStartPos( item, listPtr->startPos + viewmax );

        break;

      default:
        // Not handled
        return qfalse;
    }

    return qtrue;
  }

  return qfalse;
}

qboolean Item_ComboBox_HandleKey( itemDef_t *item, int key, qboolean down, qboolean force )
{
  if( g_comboBoxItem != NULL )
  {
    qboolean result;

    qboolean cast = Item_ComboBox_MaybeCastToListBox( item );
    result = Item_ListBox_HandleKey( item, key, down, force );
    Item_ComboBox_MaybeUnCastFromListBox( item, cast );

    if( !result )
      g_comboBoxItem = NULL;

    return result;
  }
  else
  {
    if( force || ( Rect_ContainsPoint( &item->window.rect, DC->cursorx, DC->cursory ) &&
        item->window.flags & WINDOW_HASFOCUS ) )
    {
      if( key == K_MOUSE1 || key == K_MOUSE2 )
      {
        g_comboBoxItem = item;

        return qtrue;
      }
    }
  }

  return qfalse;
}

qboolean Item_YesNo_HandleKey( itemDef_t *item, int key )
{
  if( Rect_ContainsPoint( &item->window.rect, DC->cursorx, DC->cursory ) &&
      item->window.flags & WINDOW_HASFOCUS && item->cvar )
  {
    if( key == K_MOUSE1 || key == K_ENTER || key == K_MOUSE2 || key == K_MOUSE3 )
    {
      DC->setCVar( item->cvar, va( "%i", !DC->getCVarValue( item->cvar ) ) );
      return qtrue;
    }
  }

  return qfalse;

}

int Item_Multi_CountSettings( itemDef_t *item )
{
  if( item->typeData.multi == NULL )
    return 0;

  return item->typeData.multi->count;
}

int Item_Multi_FindCvarByValue( itemDef_t *item )
{
  char buff[1024];
  float value = 0;
  int i;
  multiDef_t *multiPtr = item->typeData.multi;

  if( multiPtr )
  {
    if( multiPtr->strDef )
      DC->getCVarString( item->cvar, buff, sizeof( buff ) );
    else
      value = DC->getCVarValue( item->cvar );

    for( i = 0; i < multiPtr->count; i++ )
    {
      if( multiPtr->strDef )
      {
        if( Q_stricmp( buff, multiPtr->cvarStr[i] ) == 0 )
          return i;
      }
      else
      {
        if( multiPtr->cvarValue[i] == value )
          return i;
      }
    }
  }

  return 0;
}

const char *Item_Multi_Setting( itemDef_t *item )
{
  char buff[1024];
  float value = 0;
  int i;
  multiDef_t *multiPtr = item->typeData.multi;

  if( multiPtr )
  {
    if( multiPtr->strDef )
      DC->getCVarString( item->cvar, buff, sizeof( buff ) );
    else
      value = DC->getCVarValue( item->cvar );

    for( i = 0; i < multiPtr->count; i++ )
    {
      if( multiPtr->strDef )
      {
        if( Q_stricmp( buff, multiPtr->cvarStr[i] ) == 0 )
          return multiPtr->cvarList[i];
      }
      else
      {
        if( multiPtr->cvarValue[i] == value )
          return multiPtr->cvarList[i];
      }
    }
  }

  return "";
}

qboolean Item_Cycle_HandleKey( itemDef_t *item, int key )
{
  cycleDef_t *cyclePtr = item->typeData.cycle;
  qboolean mouseOver = Rect_ContainsPoint( &item->window.rect, DC->cursorx, DC->cursory );
  int count = DC->feederCount( item->feederID );

  if( cyclePtr )
  {
    if( item->window.flags & WINDOW_HASFOCUS )
    {
      if( ( mouseOver && key == K_MOUSE1 ) ||
          key == K_ENTER || key == K_RIGHTARROW || key == K_DOWNARROW )
      {
        if( count > 0 )
          cyclePtr->cursorPos = ( cyclePtr->cursorPos + 1 ) % count;

        DC->feederSelection( item->feederID, cyclePtr->cursorPos );

        return qtrue;
      }
      else if( ( mouseOver && key == K_MOUSE2 ) ||
               key == K_LEFTARROW || key == K_UPARROW )
      {
        if( count > 0 )
          cyclePtr->cursorPos = ( count + cyclePtr->cursorPos - 1 ) % count;

        DC->feederSelection( item->feederID, cyclePtr->cursorPos );

        return qtrue;
      }
    }
  }

  return qfalse;
}

qboolean Item_Multi_HandleKey( itemDef_t *item, int key )
{
  qboolean mouseOver = Rect_ContainsPoint( &item->window.rect, DC->cursorx, DC->cursory );
  int max = Item_Multi_CountSettings( item );
  qboolean changed = qfalse;

  if( item->typeData.multi )
  {
    if( item->window.flags & WINDOW_HASFOCUS && item->cvar && max > 0 )
    {
      int current;

      if( ( mouseOver && key == K_MOUSE1 ) ||
          key == K_ENTER || key == K_RIGHTARROW || key == K_DOWNARROW )
      {
        current = ( Item_Multi_FindCvarByValue( item ) + 1 ) % max;
        changed = qtrue;
      }
      else if( ( mouseOver && key == K_MOUSE2 ) ||
               key == K_LEFTARROW || key == K_UPARROW )
      {
        current = ( Item_Multi_FindCvarByValue( item ) + max - 1 ) % max;
        changed = qtrue;
      }

      if( changed )
      {
        if( item->typeData.multi->strDef )
          DC->setCVar( item->cvar, item->typeData.multi->cvarStr[current] );
        else
        {
          float value = item->typeData.multi->cvarValue[current];

          if( ( ( float )( ( int ) value ) ) == value )
            DC->setCVar( item->cvar, va( "%i", ( int ) value ) );
          else
            DC->setCVar( item->cvar, va( "%f", value ) );
        }

        return qtrue;
      }
    }
  }

  return qfalse;
}

#define MIN_FIELD_WIDTH   10
#define EDIT_CURSOR_WIDTH 10

static void Item_TextField_CalcPaintOffset( itemDef_t *item, char *buff )
{
  editFieldDef_t  *editPtr = item->typeData.edit;

  if( item->cursorPos < editPtr->paintOffset )
    editPtr->paintOffset = item->cursorPos;
  else
  {
    // If there is a maximum field width

    if( editPtr->maxFieldWidth > 0 )
    {
      // If the cursor is at the end of the string, maximise the amount of the
      // string that's visible

      if( buff[ item->cursorPos + 1 ] == '\0' )
      {
        while( UI_Text_Width( &buff[ editPtr->paintOffset ], item->textscale ) <=
               ( editPtr->maxFieldWidth - EDIT_CURSOR_WIDTH ) && editPtr->paintOffset > 0 )
          editPtr->paintOffset--;
      }

      buff[ item->cursorPos + 1 ] = '\0';

      // Shift paintOffset so that the cursor is visible

      while( UI_Text_Width( &buff[ editPtr->paintOffset ], item->textscale ) >
             ( editPtr->maxFieldWidth - EDIT_CURSOR_WIDTH ) )
        editPtr->paintOffset++;
    }
  }
}

qboolean Item_TextField_HandleKey( itemDef_t *item, int key )
{
  char buff[1024];
  int len;
  itemDef_t *newItem = NULL;
  editFieldDef_t *editPtr = item->typeData.edit;
  qboolean releaseFocus = qtrue;

  if( item->cvar )
  {
    Com_Memset( buff, 0, sizeof( buff ) );
    DC->getCVarString( item->cvar, buff, sizeof( buff ) );
    len = strlen( buff );

    if( len < item->cursorPos )
      item->cursorPos = len;

    if( editPtr->maxChars && len > editPtr->maxChars )
      len = editPtr->maxChars;

    if( key & K_CHAR_FLAG )
    {
      key &= ~K_CHAR_FLAG;

      if( key == 'h' - 'a' + 1 )
      {
        // ctrl-h is backspace

        if( item->cursorPos > 0 )
        {
          memmove( &buff[item->cursorPos - 1], &buff[item->cursorPos], len + 1 - item->cursorPos );
          item->cursorPos--;
        }

        DC->setCVar( item->cvar, buff );
      }
      else if( key < 32 || !item->cvar )
      {
        // Ignore any non printable chars
        releaseFocus = qfalse;
        goto exit;
      }
      else if( item->type == ITEM_TYPE_NUMERICFIELD && ( key < '0' || key > '9' ) )
      {
        // Ignore non-numeric characters
        releaseFocus = qfalse;
        goto exit;
      }
      else
      {
        if( !DC->getOverstrikeMode() )
        {
          if( ( len == MAX_EDITFIELD - 1 ) || ( editPtr->maxChars && len >= editPtr->maxChars ) )
          {
            // Reached maximum field length
            releaseFocus = qfalse;
            goto exit;
          }

          memmove( &buff[item->cursorPos + 1], &buff[item->cursorPos], len + 1 - item->cursorPos );
        }
        else
        {
          // Reached maximum field length
          if( editPtr->maxChars && item->cursorPos >= editPtr->maxChars )
          {
            releaseFocus = qfalse;
            goto exit;
          }
        }

        buff[ item->cursorPos ] = key;

        DC->setCVar( item->cvar, buff );

        if( item->cursorPos < len + 1 )
          item->cursorPos++;
      }
    }
    else
    {
      switch( key )
      {
        case K_DEL:
        case K_KP_DEL:
          if( item->cursorPos < len )
          {
            memmove( buff + item->cursorPos, buff + item->cursorPos + 1, len - item->cursorPos );
            DC->setCVar( item->cvar, buff );
          }

          break;

        case K_RIGHTARROW:
        case K_KP_RIGHTARROW:
          if( item->cursorPos < len )
            item->cursorPos++;

          break;

        case K_LEFTARROW:
        case K_KP_LEFTARROW:
          if( item->cursorPos > 0 )
            item->cursorPos--;

          break;

        case K_HOME:
        case K_KP_HOME:
          item->cursorPos = 0;

          break;

        case K_END:
        case K_KP_END:
          item->cursorPos = len;

          break;

        case K_INS:
        case K_KP_INS:
          DC->setOverstrikeMode( !DC->getOverstrikeMode() );

          break;

        case K_TAB:
        case K_DOWNARROW:
        case K_KP_DOWNARROW:
        case K_UPARROW:
        case K_KP_UPARROW:
          // Ignore these keys from the say field
          if( item->type == ITEM_TYPE_SAYFIELD )
            break;

          newItem = Menu_SetNextCursorItem( item->parent );

          if( newItem && Item_IsEditField( newItem ) )
          {
            g_editItem = newItem;
          }
          else
          {
            releaseFocus = qtrue;
            goto exit;
          }

          break;

        case K_MOUSE1:
        case K_MOUSE2:
        case K_MOUSE3:
        case K_MOUSE4:
          // Ignore these buttons from the say field
          if( item->type == ITEM_TYPE_SAYFIELD )
            break;
          // FALLTHROUGH
        case K_ENTER:
        case K_KP_ENTER:
        case K_ESCAPE:
          releaseFocus = qtrue;
          goto exit;

        default:
          break;
      }
    }

    releaseFocus = qfalse;
  }

exit:
  Item_TextField_CalcPaintOffset( item, buff );

  return !releaseFocus;
}

static void _Scroll_ListBox_AutoFunc( scrollInfo_t *si )
{
  if( DC->realTime > si->nextScrollTime )
  {
    // need to scroll which is done by simulating a click to the item
    // this is done a bit sideways as the autoscroll "knows" that the item is a listbox
    // so it calls it directly
    Item_ListBox_HandleKey( si->item, si->scrollKey, qtrue, qfalse );

    si->nextScrollTime = DC->realTime + si->adjustValue;
  }

  if( DC->realTime > si->nextAdjustTime )
  {
    si->nextAdjustTime = DC->realTime + SCROLL_TIME_ADJUST;

    if( si->adjustValue > SCROLL_TIME_FLOOR )
      si->adjustValue -= SCROLL_TIME_ADJUSTOFFSET;
  }
}

static void Scroll_ListBox_AutoFunc( void *p )
{
  scrollInfo_t *si = ( scrollInfo_t* )p;

  qboolean cast = Item_ComboBox_MaybeCastToListBox( si->item );
  _Scroll_ListBox_AutoFunc( si );
  Item_ComboBox_MaybeUnCastFromListBox( si->item, cast );
}

static void _Scroll_ListBox_ThumbFunc( scrollInfo_t *si )
{
  rectDef_t r;
  int pos, max;

  if( DC->cursory != si->yStart )
  {
    r.x = si->item->window.rect.x + si->item->window.rect.w - SCROLLBAR_ARROW_WIDTH - 1;
    r.y = si->item->window.rect.y + SCROLLBAR_ARROW_HEIGHT + 1;
    r.w = SCROLLBAR_ARROW_WIDTH;
    r.h = si->item->window.rect.h - ( SCROLLBAR_ARROW_HEIGHT * 2 ) - 2;
    max = Item_ListBox_MaxScroll( si->item );
    //
    pos = ( DC->cursory - r.y - SCROLLBAR_ARROW_HEIGHT / 2 ) * max / ( r.h - SCROLLBAR_ARROW_HEIGHT );

    if( pos < 0 )
      pos = 0;
    else if( pos > max )
      pos = max;

    Item_ListBox_SetStartPos( si->item, pos );
    si->yStart = DC->cursory;
  }

  if( DC->realTime > si->nextScrollTime )
  {
    // need to scroll which is done by simulating a click to the item
    // this is done a bit sideways as the autoscroll "knows" that the item is a listbox
    // so it calls it directly
    Item_ListBox_HandleKey( si->item, si->scrollKey, qtrue, qfalse );

    si->nextScrollTime = DC->realTime + si->adjustValue;
  }

  if( DC->realTime > si->nextAdjustTime )
  {
    si->nextAdjustTime = DC->realTime + SCROLL_TIME_ADJUST;

    if( si->adjustValue > SCROLL_TIME_FLOOR )
      si->adjustValue -= SCROLL_TIME_ADJUSTOFFSET;
  }
}

static void Scroll_ListBox_ThumbFunc( void *p )
{
  scrollInfo_t *si = ( scrollInfo_t* )p;

  qboolean cast = Item_ComboBox_MaybeCastToListBox( si->item );
  _Scroll_ListBox_ThumbFunc( si );
  Item_ComboBox_MaybeUnCastFromListBox( si->item, cast );
}

static void Scroll_Slider_ThumbFunc( void *p )
{
  float x, value, cursorx;
  scrollInfo_t *si = ( scrollInfo_t* )p;

  if( si->item->text )
    x = si->item->textRect.x + si->item->textRect.w + ITEM_VALUE_OFFSET;
  else
    x = si->item->window.rect.x;

  cursorx = DC->cursorx;

  if( cursorx < x )
    cursorx = x;
  else if( cursorx > x + SLIDER_WIDTH )
    cursorx = x + SLIDER_WIDTH;

  value = cursorx - x;
  value /= SLIDER_WIDTH;
  value *= si->item->typeData.edit->maxVal - si->item->typeData.edit->minVal;
  value += si->item->typeData.edit->minVal;
  DC->setCVar( si->item->cvar, va( "%f", value ) );
}

void Item_StartCapture( itemDef_t *item, int key )
{
  int flags;

  // Don't allow captureFunc to be overridden

  if( captureFunc != voidFunction )
    return;

  switch( item->type )
  {
    case ITEM_TYPE_LISTBOX:
    case ITEM_TYPE_COMBOBOX:
    {
      qboolean cast = Item_ComboBox_MaybeCastToListBox( item );
      flags = Item_ListBox_OverLB( item, DC->cursorx, DC->cursory );
      Item_ComboBox_MaybeUnCastFromListBox( item, cast );

      if( flags & ( WINDOW_LB_UPARROW | WINDOW_LB_DOWNARROW ) )
      {
        scrollInfo.nextScrollTime = DC->realTime + SCROLL_TIME_START;
        scrollInfo.nextAdjustTime = DC->realTime + SCROLL_TIME_ADJUST;
        scrollInfo.adjustValue = SCROLL_TIME_START;
        scrollInfo.scrollKey = key;
        scrollInfo.scrollDir = ( flags & WINDOW_LB_UPARROW ) ? qtrue : qfalse;
        scrollInfo.item = item;
        UI_InstallCaptureFunc( Scroll_ListBox_AutoFunc, &scrollInfo, 0 );
        itemCapture = item;
      }
      else if( flags & WINDOW_LB_THUMB )
      {
        scrollInfo.scrollKey = key;
        scrollInfo.item = item;
        scrollInfo.xStart = DC->cursorx;
        scrollInfo.yStart = DC->cursory;
        UI_InstallCaptureFunc( Scroll_ListBox_ThumbFunc, &scrollInfo, 0 );
        itemCapture = item;
      }

      break;
    }

    case ITEM_TYPE_SLIDER:
    {
      flags = Item_Slider_OverSlider( item, DC->cursorx, DC->cursory );

      if( flags & WINDOW_LB_THUMB )
      {
        scrollInfo.scrollKey = key;
        scrollInfo.item = item;
        scrollInfo.xStart = DC->cursorx;
        scrollInfo.yStart = DC->cursory;
        UI_InstallCaptureFunc( Scroll_Slider_ThumbFunc, &scrollInfo, 0 );
        itemCapture = item;
      }

      break;
    }
  }
}

void Item_StopCapture( itemDef_t *item )
{
}

qboolean Item_Slider_HandleKey( itemDef_t *item, int key, qboolean down )
{
  float x, value, width;

  if( item->window.flags & WINDOW_HASFOCUS && item->cvar &&
      Rect_ContainsPoint( &item->window.rect, DC->cursorx, DC->cursory ) )
  {
    if( item->typeData.edit && ( key == K_ENTER ||
        key == K_MOUSE1 || key == K_MOUSE2 || key == K_MOUSE3 ) )
    {
      rectDef_t testRect;
      width = SLIDER_WIDTH;

      if( item->text )
        x = item->textRect.x + item->textRect.w + ITEM_VALUE_OFFSET;
      else
        x = item->window.rect.x;

      testRect = item->window.rect;
      value = ( float )SLIDER_THUMB_WIDTH / 2;
      testRect.x = x - value;
      testRect.w = SLIDER_WIDTH + value;

      if( Rect_ContainsPoint( &testRect, DC->cursorx, DC->cursory ) )
      {
        value = ( float )( DC->cursorx - x ) / width;
        value *= ( item->typeData.edit->maxVal - item->typeData.edit->minVal );
        value += item->typeData.edit->minVal;
        DC->setCVar( item->cvar, va( "%f", value ) );
        return qtrue;
      }
    }
  }

  return qfalse;
}


qboolean Item_HandleKey( itemDef_t *item, int key, qboolean down )
{
  if( itemCapture )
  {
    Item_StopCapture( itemCapture );
    itemCapture = NULL;
    UI_RemoveCaptureFunc( );
  }
  else
  {
    if( down && ( key == K_MOUSE1 || key == K_MOUSE2 || key == K_MOUSE3 ) )
      Item_StartCapture( item, key );
  }

  if( !down )
    return qfalse;

  // Edit fields are handled specially
  if( Item_IsEditField( item ) )
    return qfalse;

  switch( item->type )
  {
    case ITEM_TYPE_BUTTON:
      return qfalse;

    case ITEM_TYPE_RADIOBUTTON:
      return qfalse;

    case ITEM_TYPE_CHECKBOX:
      return qfalse;

    case ITEM_TYPE_CYCLE:
      return Item_Cycle_HandleKey( item, key );

    case ITEM_TYPE_LISTBOX:
      return Item_ListBox_HandleKey( item, key, down, qfalse );

    case ITEM_TYPE_COMBOBOX:
      return Item_ComboBox_HandleKey( item, key, down, qfalse );

    case ITEM_TYPE_YESNO:
      return Item_YesNo_HandleKey( item, key );

    case ITEM_TYPE_MULTI:
      return Item_Multi_HandleKey( item, key );

    case ITEM_TYPE_OWNERDRAW:
      return Item_OwnerDraw_HandleKey( item, key );

    case ITEM_TYPE_BIND:
      return Item_Bind_HandleKey( item, key, down );

    case ITEM_TYPE_SLIDER:
      return Item_Slider_HandleKey( item, key, down );

    default:
      return qfalse;
  }
}

void Item_Action( itemDef_t *item )
{
  if( item )
    Item_RunScript( item, item->action );
}

itemDef_t *Menu_SetPrevCursorItem( menuDef_t *menu )
{
  qboolean wrapped = qfalse;
  int oldCursor = menu->cursorItem;

  if( menu->cursorItem < 0 )
  {
    menu->cursorItem = menu->itemCount - 1;
    wrapped = qtrue;
  }

  while( menu->cursorItem > -1 )
  {
    menu->cursorItem--;

    if( menu->cursorItem < 0 && !wrapped )
    {
      wrapped = qtrue;
      menu->cursorItem = menu->itemCount - 1;
    }

    if( Item_SetFocus( menu->items[menu->cursorItem], DC->cursorx, DC->cursory ) )
    {
      Menu_HandleMouseMove( menu, menu->items[menu->cursorItem]->window.rect.x + 1,
                            menu->items[menu->cursorItem]->window.rect.y + 1 );
      return menu->items[menu->cursorItem];
    }
  }

  menu->cursorItem = oldCursor;
  return NULL;

}

itemDef_t *Menu_SetNextCursorItem( menuDef_t *menu )
{
  qboolean wrapped = qfalse;
  int oldCursor = menu->cursorItem;


  if( menu->cursorItem == -1 )
  {
    menu->cursorItem = 0;
    wrapped = qtrue;
  }

  while( menu->cursorItem < menu->itemCount )
  {
    menu->cursorItem++;

    if( menu->cursorItem >= menu->itemCount && !wrapped )
    {
      wrapped = qtrue;
      menu->cursorItem = 0;
    }

    if( Item_SetFocus( menu->items[menu->cursorItem], DC->cursorx, DC->cursory ) )
    {
      Menu_HandleMouseMove( menu, menu->items[menu->cursorItem]->window.rect.x + 1,
                            menu->items[menu->cursorItem]->window.rect.y + 1 );
      return menu->items[menu->cursorItem];
    }

  }

  menu->cursorItem = oldCursor;
  return NULL;
}

static void Window_CloseCinematic( windowDef_t *window )
{
  if( window->style == WINDOW_STYLE_CINEMATIC && window->cinematic >= 0 )
  {
    DC->stopCinematic( window->cinematic );
    window->cinematic = -1;
  }
}

static void Menu_CloseCinematics( menuDef_t *menu )
{
  if( menu )
  {
    int i;
    Window_CloseCinematic( &menu->window );

    for( i = 0; i < menu->itemCount; i++ )
    {
      Window_CloseCinematic( &menu->items[i]->window );

      if( menu->items[i]->type == ITEM_TYPE_OWNERDRAW )
        DC->stopCinematic( 0 - menu->items[i]->window.ownerDraw );
    }
  }
}

static void Display_CloseCinematics( void )
{
  int i;

  for( i = 0; i < menuCount; i++ )
    Menu_CloseCinematics( &Menus[i] );
}

void Menus_Activate( menuDef_t *menu )
{
  int i;
  qboolean onTopOfMenuStack = qfalse;

  if( openMenuCount > 0 && menuStack[ openMenuCount - 1 ] == menu )
    onTopOfMenuStack = qtrue;

  menu->window.flags |= ( WINDOW_HASFOCUS | WINDOW_VISIBLE );

  // If being opened for the first time
  if( !onTopOfMenuStack )
  {
    if( menu->onOpen )
    {
      itemDef_t item;
      item.parent = menu;
      Item_RunScript( &item, menu->onOpen );
    }

    if( menu->soundName && *menu->soundName )
      DC->startBackgroundTrack( menu->soundName, menu->soundName );

    Display_CloseCinematics( );

    Menu_HandleMouseMove( menu, DC->cursorx, DC->cursory ); // force the item under the cursor to focus

    for( i = 0; i < menu->itemCount; i++ ) // reset selection in listboxes when opened
    {
      if( Item_IsListBox( menu->items[ i ] ) )
      {
        menu->items[ i ]->cursorPos = DC->feederInitialise( menu->items[ i ]->feederID );
        Item_ListBox_SetStartPos( menu->items[ i ], 0 );
        DC->feederSelection( menu->items[ i ]->feederID, menu->items[ i ]->cursorPos );
      }
      else if( menu->items[ i ]->type == ITEM_TYPE_CYCLE )
      {
        menu->items[ i ]->typeData.cycle->cursorPos =
          DC->feederInitialise( menu->items[ i ]->feederID );
      }

    }

    if( openMenuCount < MAX_OPEN_MENUS )
      menuStack[ openMenuCount++ ] = menu;
  }
}

qboolean Menus_ReplaceActive( menuDef_t *menu )
{
  int i;
  menuDef_t *active;

  if( openMenuCount < 1 )
    return qfalse;

  active = menuStack[ openMenuCount - 1 ];

  if( !( active->window.flags & WINDOW_HASFOCUS ) ||
      !( active->window.flags & WINDOW_VISIBLE ) )
  {
    return qfalse;
  }

  if( menu == active )
    return qfalse;

  if( menu->itemCount != active->itemCount )
  {
    Com_Printf( S_COLOR_YELLOW
      "WARNING: Menus_ReplaceActive: expecting %i menu items, found %i\n",
      menu->itemCount, active->itemCount);
    return qfalse;
  }

  for( i = 0; i < menu->itemCount; i++ )
  {
    if( menu->items[ i ]->type != active->items[ i ]->type )
    {
      Com_Printf( S_COLOR_YELLOW
        "WARNING: Menus_ReplaceActive: type mismatch on item %i\n", i + 1 );
      return qfalse;
    }
  }

  active->window.flags &= ~( WINDOW_FADINGOUT | WINDOW_VISIBLE );
  menu->window.flags |= ( WINDOW_HASFOCUS | WINDOW_VISIBLE );

  menuStack[ openMenuCount - 1 ] = menu;
  if( menu->onOpen )
  {
    itemDef_t item;
    item.parent = menu;
    Item_RunScript( &item, menu->onOpen );
  }

  // set the cursor position on the new menu to match the active one
  for( i = 0; i < menu->itemCount; i++ ) 	 
  {
    menu->items[ i ]->cursorPos = active->items[ i ]->cursorPos;
    menu->items[ i ]->feederID = active->items[ i ]->feederID;
    switch( Item_DataType( menu->items[ i ] ) )
    {
      case TYPE_LIST:
        menu->items[ i ]->typeData.list->startPos =
          active->items[ i ]->typeData.list->startPos;
        menu->items[ i ]->typeData.list->cursorPos =
          active->items[ i ]->typeData.list->cursorPos;
        break;
      case TYPE_COMBO:
        menu->items[ i ]->typeData.cycle->cursorPos =
          active->items[ i ]->typeData.cycle->cursorPos;
        break;
      default:
        break;
    }
  }
  return qtrue;
}

int Display_VisibleMenuCount( void )
{
  int i, count;
  count = 0;

  for( i = 0; i < menuCount; i++ )
  {
    if( Menus[i].window.flags & ( WINDOW_FORCED | WINDOW_VISIBLE ) )
      count++;
  }

  return count;
}

void Menus_HandleOOBClick( menuDef_t *menu, int key, qboolean down )
{
  if( menu )
  {
    int i;
    // basically the behaviour we are looking for is if there are windows in the stack.. see if
    // the cursor is within any of them.. if not close them otherwise activate them and pass the
    // key on.. force a mouse move to activate focus and script stuff

    if( down && menu->window.flags & WINDOW_OOB_CLICK )
      Menus_Close( menu );

    for( i = 0; i < menuCount; i++ )
    {
      if( Menu_OverActiveItem( &Menus[i], DC->cursorx, DC->cursory ) )
      {
        Menus_Close( menu );
        Menus_Activate( &Menus[i] );
        Menu_HandleMouseMove( &Menus[i], DC->cursorx, DC->cursory );
        Menu_HandleKey( &Menus[i], key, down );
      }
    }

    if( Display_VisibleMenuCount() == 0 )
    {
      if( DC->Pause )
        DC->Pause( qfalse );
    }

    Display_CloseCinematics();
  }
}

static rectDef_t *Item_CorrectedTextRect( itemDef_t *item )
{
  static rectDef_t rect;
  memset( &rect, 0, sizeof( rectDef_t ) );

  if( item )
  {
    rect = item->textRect;

    if( rect.w )
      rect.y -= rect.h;
  }

  return &rect;
}

void Menu_HandleKey( menuDef_t *menu, int key, qboolean down )
{
  int i;
  itemDef_t *item = NULL;

  if( g_waitingForKey && down )
  {
    Item_Bind_HandleKey( g_bindItem, key, down );
    return;
  }

  if( g_editingField && down )
  {
    if( !Item_TextField_HandleKey( g_editItem, key ) )
    {
      g_editingField = qfalse;
      Item_RunScript( g_editItem, g_editItem->onTextEntry );
      g_editItem = NULL;
      return;
    }
    else
    {
      Item_RunScript( g_editItem, g_editItem->onCharEntry );
    }
  }

  if( menu == NULL )
    return;

  // see if the mouse is within the window bounds and if so is this a mouse click
  if( down && !( menu->window.flags & WINDOW_POPUP ) &&
      !Rect_ContainsPoint( &menu->window.rect, DC->cursorx, DC->cursory ) )
  {
    static qboolean inHandleKey = qfalse;

    if( !inHandleKey && ( key == K_MOUSE1 || key == K_MOUSE2 || key == K_MOUSE3 ) )
    {
      inHandleKey = qtrue;
      Menus_HandleOOBClick( menu, key, down );
      inHandleKey = qfalse;
      return;
    }
  }

  if( g_comboBoxItem == NULL )
  {
    // get the item with focus
    for( i = 0; i < menu->itemCount; i++ )
    {
      if( menu->items[i]->window.flags & WINDOW_HASFOCUS )
        item = menu->items[i];
    }
  }
  else
    item = g_comboBoxItem;

  if( item != NULL )
  {
    if( Item_HandleKey( item, key, down ) )
    {
      Item_Action( item );
      return;
    }
  }

  if( !down )
    return;

  // default handling
  switch( key )
  {
    case K_F12:
      if( DC->getCVarValue( "developer" ) )
        DC->executeText( EXEC_APPEND, "screenshot\n" );

      break;

    case K_KP_UPARROW:
    case K_UPARROW:
      Menu_SetPrevCursorItem( menu );
      break;

    case K_ESCAPE:
      if( !g_waitingForKey && menu->onESC )
      {
        itemDef_t it;
        it.parent = menu;
        Item_RunScript( &it, menu->onESC );
      }

      break;

    case K_TAB:
    case K_KP_DOWNARROW:
    case K_DOWNARROW:
      Menu_SetNextCursorItem( menu );
      break;

    case K_MOUSE1:
    case K_MOUSE2:
      if( item )
      {
        if( item->type == ITEM_TYPE_TEXT )
        {
          if( Rect_ContainsPoint( Item_CorrectedTextRect( item ), DC->cursorx, DC->cursory ) )
            Item_Action( item );
        }
        else if( Item_IsEditField( item ) )
        {
          if( Rect_ContainsPoint( &item->window.rect, DC->cursorx, DC->cursory ) )
          {
            char buffer[ MAX_STRING_CHARS ] = { 0 };

            if( item->cvar )
              DC->getCVarString( item->cvar, buffer, sizeof( buffer ) );

            item->cursorPos = strlen( buffer );

            Item_TextField_CalcPaintOffset( item, buffer );

            g_editingField = qtrue;

            g_editItem = item;
          }
        }
        else
        {
          if( Rect_ContainsPoint( &item->window.rect, DC->cursorx, DC->cursory ) )
            Item_Action( item );
        }
      }

      break;

    case K_JOY1:
    case K_JOY2:
    case K_JOY3:
    case K_JOY4:
    case K_AUX1:
    case K_AUX2:
    case K_AUX3:
    case K_AUX4:
    case K_AUX5:
    case K_AUX6:
    case K_AUX7:
    case K_AUX8:
    case K_AUX9:
    case K_AUX10:
    case K_AUX11:
    case K_AUX12:
    case K_AUX13:
    case K_AUX14:
    case K_AUX15:
    case K_AUX16:
      break;

    case K_KP_ENTER:
    case K_ENTER:
      if( item )
      {
        if( Item_IsEditField( item ) )
        {
          char buffer[ MAX_STRING_CHARS ] = { 0 };

          if( item->cvar )
            DC->getCVarString( item->cvar, buffer, sizeof( buffer ) );

          item->cursorPos = strlen( buffer );

          Item_TextField_CalcPaintOffset( item, buffer );

          g_editingField = qtrue;

          g_editItem = item;
        }
        else
          Item_Action( item );
      }

      break;
  }
}

void ToWindowCoords( float *x, float *y, windowDef_t *window )
{
  if( window->border != 0 )
  {
    *x += window->borderSize;
    *y += window->borderSize;
  }

  *x += window->rect.x;
  *y += window->rect.y;
}

void Rect_ToWindowCoords( rectDef_t *rect, windowDef_t *window )
{
  ToWindowCoords( &rect->x, &rect->y, window );
}

void Item_SetTextExtents( itemDef_t *item, const char *text )
{
  const char *textPtr = ( text ) ? text : item->text;
  qboolean cvarContent;

  // It's hard to make a policy on what should be aligned statically and what
  // should be aligned dynamically; there are reasonable cases for both. If
  // it continues to be a problem then there should probably be an item keyword
  // for it; but for the moment only adjusting the alignment of ITEM_TYPE_TEXT
  // seems to suffice.
  cvarContent = ( item->cvar && item->textalignment != ALIGN_LEFT &&
                  item->type == ITEM_TYPE_TEXT );

  if( textPtr == NULL )
    return;

  // as long as the item isn't dynamic content (ownerdraw or cvar), this
  // keeps us from computing the widths and heights more than once
  if( item->textRect.w == 0.0f || cvarContent || ( item->type == ITEM_TYPE_OWNERDRAW &&
      item->textalignment != ALIGN_LEFT ) )
  {
    float originalWidth = 0.0f;

    if( item->textalignment == ALIGN_CENTER || item->textalignment == ALIGN_RIGHT )
    {
      if( cvarContent )
      {
        char buff[ MAX_CVAR_VALUE_STRING ];
        DC->getCVarString( item->cvar, buff, sizeof( buff ) );
        originalWidth = UI_Text_Width( item->text, item->textscale ) +
          UI_Text_Width( buff, item->textscale );
      }
      else
        originalWidth = UI_Text_Width( item->text, item->textscale );
    }

    item->textRect.w = UI_Text_Width( textPtr, item->textscale );
    item->textRect.h = UI_Text_Height( textPtr, item->textscale );

    if( item->textvalignment == VALIGN_BOTTOM )
      item->textRect.y = item->textaligny + item->window.rect.h;
    else if( item->textvalignment == VALIGN_CENTER )
      item->textRect.y = item->textaligny + ( ( item->textRect.h + item->window.rect.h ) / 2.0f );
    else if( item->textvalignment == VALIGN_TOP )
      item->textRect.y = item->textaligny + item->textRect.h;

    if( item->textalignment == ALIGN_LEFT )
      item->textRect.x = item->textalignx;
    else if( item->textalignment == ALIGN_CENTER )
      item->textRect.x = item->textalignx + ( ( item->window.rect.w - originalWidth ) / 2.0f );
    else if( item->textalignment == ALIGN_RIGHT )
      item->textRect.x = item->textalignx + item->window.rect.w - originalWidth;

    ToWindowCoords( &item->textRect.x, &item->textRect.y, &item->window );
  }
}

void Item_TextColor( itemDef_t *item, vec4_t *newColor )
{
  vec4_t lowLight;
  menuDef_t *parent = ( menuDef_t* )item->parent;

  Fade( &item->window.flags, &item->window.foreColor[3], parent->fadeClamp,
        &item->window.nextTime, parent->fadeCycle, qtrue, parent->fadeAmount );

  if( item->window.flags & WINDOW_HASFOCUS )
    memcpy( newColor, &parent->focusColor, sizeof( vec4_t ) );
  else if( item->textStyle == ITEM_TEXTSTYLE_BLINK && !( ( DC->realTime / BLINK_DIVISOR ) & 1 ) )
  {
    lowLight[0] = 0.8 * item->window.foreColor[0];
    lowLight[1] = 0.8 * item->window.foreColor[1];
    lowLight[2] = 0.8 * item->window.foreColor[2];
    lowLight[3] = 0.8 * item->window.foreColor[3];
    LerpColor( item->window.foreColor, lowLight, *newColor, 0.5 + 0.5 * sin( DC->realTime / PULSE_DIVISOR ) );
  }
  else
  {
    memcpy( newColor, &item->window.foreColor, sizeof( vec4_t ) );
    // items can be enabled and disabled based on cvars
  }

  if( item->enableCvar != NULL && *item->enableCvar && item->cvarTest != NULL && *item->cvarTest )
  {
    if( item->cvarFlags & ( CVAR_ENABLE | CVAR_DISABLE ) && !Item_EnableShowViaCvar( item, CVAR_ENABLE ) )
      memcpy( newColor, &parent->disableColor, sizeof( vec4_t ) );
  }
}

static void SkipColorCodes( const char **text, char *lastColor )
{
  while( Q_IsColorString( *text ) )
  {
    lastColor[ 0 ] = (*text)[ 0 ];
    lastColor[ 1 ] = (*text)[ 1 ];
    (*text) += 2;
  }
}

static void SkipWhiteSpace( const char **text, char *lastColor )
{
  while( **text )
  {
    SkipColorCodes( text, lastColor );

    if( **text != '\n' && isspace( **text ) )
      (*text)++;
    else
      break;
  }
}

const char *Item_Text_Wrap( const char *text, float scale, float width )
{
  static char   out[ 8192 ] = "";
  char          *paint = out;
  char          c[ 3 ] = "";
  const char    *p;
  const char    *eos;
  float         indentWidth = 0.0f;

  if( !text )
    return NULL;

  p = text;
  eos = p + strlen( p );

  if( ( eos - p ) >= sizeof( out ) )
    return NULL;

  *paint = '\0';

  while( *p )
  {
    float       textWidth = 0.0f;
    const char  *eol = p;
    const char  *q = p;
    float       testWidth = width - indentWidth;

    SkipColorCodes( &q, c );

    while( q && textWidth < testWidth )
    {
      qboolean previousCharIsSpace = qfalse;

      // Remaining string is too short to wrap
      if( q >= eos )
      {
        eol = eos;
        break;
      }

      if( q > p && *q == INDENT_MARKER )
      {
        indentWidth = textWidth;
        eol = p;
      }

      // Some color escapes might still be present
      SkipColorCodes( &q, c );

      // Manual line break
      if( *q == '\n' )
      {
        eol = q + 1;
        break;
      }

      if( !previousCharIsSpace && isspace( *q ) )
        eol = q;

      textWidth += UI_Char_Width( &q, scale );
    }

    // No split has taken place, so just split mid-word
    if( eol == p )
      eol = q;

    // Note that usage of strcat and strlen is deliberately being
    // avoided here as it becomes surprisingly expensive on larger
    // blocks of text

    // Copy text
    strncpy( paint, p, eol - p );
    paint += ( eol - p );
    *paint = '\0';

    p = eol;

    if( paint - out > 0 && *( paint - 1 ) == '\n' )
    {
      // The line is deliberately broken, clear the color and
      // any current indent
      c[ 0 ] = '\0';
      indentWidth = 0.0f;
    }
    else
    {
      // Add a \n if it's not there already
      *paint++ = '\n';
      *paint = '\0';

      // Insert a pixel indent on the next line
      if( indentWidth > 0.0f )
      {
        char  *indentMarkerText       = va( "%f%c", indentWidth, INDENT_MARKER );
        int   indentMarkerTextLength  = strlen( indentMarkerText );

        strncpy( paint, indentMarkerText, indentMarkerTextLength );
        paint += indentMarkerTextLength;
        *paint = '\0';
      }

      // Skip leading whitespace on next line and save the
      // last color code
      SkipWhiteSpace( &p, c );
    }

    if( c[ 0 ] )
    {
      *paint++ = c[ 0 ];
      *paint++ = c[ 1 ];
      *paint = '\0';
    }
  }

  return out;
}

#define MAX_WRAP_CACHE  16
#define MAX_WRAP_LINES  32
#define MAX_WRAP_TEXT   512

typedef struct
{
  char      text[ MAX_WRAP_TEXT * MAX_WRAP_LINES ]; //FIXME: augment with hash?
  rectDef_t rect;
  float     scale;
  char      lines[ MAX_WRAP_LINES ][ MAX_WRAP_TEXT ];
  float     lineCoords[ MAX_WRAP_LINES ][ 2 ];
  int       numLines;
}

wrapCache_t;

static wrapCache_t  wrapCache[ MAX_WRAP_CACHE ];
static int          cacheWriteIndex   = 0;
static int          cacheReadIndex    = 0;
static int          cacheReadLineNum  = 0;

static void UI_CreateCacheEntry( const char *text, rectDef_t *rect, float scale )
{
  wrapCache_t *cacheEntry = &wrapCache[ cacheWriteIndex ];

  Q_strncpyz( cacheEntry->text, text, sizeof( cacheEntry->text ) );
  cacheEntry->rect.x    = rect->x;
  cacheEntry->rect.y    = rect->y;
  cacheEntry->rect.w    = rect->w;
  cacheEntry->rect.h    = rect->h;
  cacheEntry->scale     = scale;
  cacheEntry->numLines  = 0;
}

static void UI_AddCacheEntryLine( const char *text, float x, float y )
{
  wrapCache_t *cacheEntry = &wrapCache[ cacheWriteIndex ];

  if( cacheEntry->numLines >= MAX_WRAP_LINES )
    return;

  Q_strncpyz( cacheEntry->lines[ cacheEntry->numLines ], text,
              sizeof( cacheEntry->lines[ 0 ] ) );

  cacheEntry->lineCoords[ cacheEntry->numLines ][ 0 ] = x;

  cacheEntry->lineCoords[ cacheEntry->numLines ][ 1 ] = y;

  cacheEntry->numLines++;
}

static void UI_FinishCacheEntry( void )
{
  cacheWriteIndex = ( cacheWriteIndex + 1 ) % MAX_WRAP_CACHE;
}

static qboolean UI_CheckWrapCache( const char *text, rectDef_t *rect, float scale )
{
  int i;

  for( i = 0; i < MAX_WRAP_CACHE; i++ )
  {
    wrapCache_t *cacheEntry = &wrapCache[ i ];

    if( strcmp( text, cacheEntry->text ) )
      continue;

    if( rect->x != cacheEntry->rect.x ||
        rect->y != cacheEntry->rect.y ||
        rect->w != cacheEntry->rect.w ||
        rect->h != cacheEntry->rect.h )
      continue;

    if( cacheEntry->scale != scale )
      continue;

    // This is a match
    cacheReadIndex = i;

    cacheReadLineNum = 0;

    return qtrue;
  }

  // No match - wrap isn't cached
  return qfalse;
}

static qboolean UI_NextWrapLine( const char **text, float *x, float *y )
{
  wrapCache_t *cacheEntry = &wrapCache[ cacheReadIndex ];

  if( cacheReadLineNum >= cacheEntry->numLines )
    return qfalse;

  *text = cacheEntry->lines[ cacheReadLineNum ];

  *x    = cacheEntry->lineCoords[ cacheReadLineNum ][ 0 ];

  *y    = cacheEntry->lineCoords[ cacheReadLineNum ][ 1 ];

  cacheReadLineNum++;

  return qtrue;
}

void Item_Text_Wrapped_Paint( itemDef_t *item )
{
  char        text[ 1024 ];
  const char  *p, *textPtr;
  float       x, y, w, h;
  vec4_t      color;
  qboolean    useWrapCache = (qboolean)DC->getCVarValue( "ui_textWrapCache" );

  if( item->text == NULL )
  {
    if( item->cvar == NULL )
      return;
    else
    {
      DC->getCVarString( item->cvar, text, sizeof( text ) );
      textPtr = text;
    }
  }
  else
    textPtr = item->text;

  if( *textPtr == '\0' )
    return;

  Item_TextColor( item, &color );

  // Check if this block is cached
  if( useWrapCache && UI_CheckWrapCache( textPtr, &item->window.rect, item->textscale ) )
  {
    while( UI_NextWrapLine( &p, &x, &y ) )
    {
      UI_Text_Paint( x, y, item->textscale, color,
                     p, 0, 0, item->textStyle );
    }
  }
  else
  {
    char        buff[ 1024 ];
    float       fontHeight    = UI_Text_EmHeight( item->textscale );
    const float lineSpacing   = fontHeight * 0.4f;
    float       lineHeight    = fontHeight + lineSpacing;
    float       textHeight;
    int         textLength;
    int         paintLines, totalLines, lineNum = 0;
    float       paintY;
    int         i;

    if( useWrapCache )
      UI_CreateCacheEntry( textPtr, &item->window.rect, item->textscale );

    x = item->window.rect.x + item->textalignx;
    y = item->window.rect.y + item->textaligny;
    w = item->window.rect.w - ( 2.0f * item->textalignx );
    h = item->window.rect.h - ( 2.0f * item->textaligny );

    textPtr = Item_Text_Wrap( textPtr, item->textscale, w );
    textLength = strlen( textPtr );

    // Count lines
    totalLines = 0;

    for( i = 0; i < textLength; i++ )
    {
      if( textPtr[ i ] == '\n' )
        totalLines++;
    }

    paintLines = ( int )floor( ( h + lineSpacing ) / lineHeight );

    if( paintLines > totalLines )
      paintLines = totalLines;

    textHeight = ( paintLines * lineHeight ) - lineSpacing;

    switch( item->textvalignment )
    {
      default:

      case VALIGN_BOTTOM:
        paintY = y + ( h - textHeight );
        break;

      case VALIGN_CENTER:
        paintY = y + ( ( h - textHeight ) / 2.0f );
        break;

      case VALIGN_TOP:
        paintY = y;
        break;
    }

    p = textPtr;

    for( i = 0, lineNum = 0; i < textLength && lineNum < paintLines; i++ )
    {
      int lineLength = &textPtr[ i ] - p;

      if( lineLength >= sizeof( buff ) - 1 )
        break;

      if( textPtr[ i ] == '\n' || textPtr[ i ] == '\0' )
      {
        itemDef_t   lineItem;

        memset( &lineItem, 0, sizeof( itemDef_t ) );
        strncpy( buff, p, lineLength );
        buff[ lineLength ] = '\0';
        p = &textPtr[ i + 1 ];

        lineItem.type               = ITEM_TYPE_TEXT;
        lineItem.textscale          = item->textscale;
        lineItem.textStyle          = item->textStyle;
        lineItem.text               = buff;
        lineItem.textalignment      = item->textalignment;
        lineItem.textvalignment     = VALIGN_TOP;
        lineItem.textalignx         = 0.0f;
        lineItem.textaligny         = 0.0f;

        lineItem.textRect.w         = 0.0f;
        lineItem.textRect.h         = 0.0f;
        lineItem.window.rect.x      = x;
        lineItem.window.rect.y      = paintY + ( lineNum * lineHeight );
        lineItem.window.rect.w      = w;
        lineItem.window.rect.h      = lineHeight;
        lineItem.window.border      = item->window.border;
        lineItem.window.borderSize  = item->window.borderSize;

        if( DC->getCVarValue( "ui_developer" ) )
        {
          vec4_t color;
          color[ 0 ] = color[ 2 ] = color[ 3 ] = 1.0f;
          color[ 1 ] = 0.0f;
          DC->drawRect( lineItem.window.rect.x, lineItem.window.rect.y,
                        lineItem.window.rect.w, lineItem.window.rect.h, 1, color );
        }

        Item_SetTextExtents( &lineItem, buff );
        UI_Text_Paint( lineItem.textRect.x, lineItem.textRect.y,
                       lineItem.textscale, color, buff, 0, 0,
                       lineItem.textStyle );

        if( useWrapCache )
          UI_AddCacheEntryLine( buff, lineItem.textRect.x, lineItem.textRect.y );

        lineNum++;
      }
    }

    if( useWrapCache )
      UI_FinishCacheEntry( );
  }
}

/*
==============
UI_DrawTextBlock
==============
*/
void UI_DrawTextBlock( rectDef_t *rect, float text_x, float text_y, vec4_t color,
                       float scale, int textalign, int textvalign,
                       int textStyle, const char *text )
{
  static menuDef_t  dummyParent;
  static itemDef_t  textItem;

  textItem.text = text;

  textItem.parent = &dummyParent;
  memcpy( textItem.window.foreColor, color, sizeof( vec4_t ) );
  textItem.window.flags = 0;

  textItem.window.rect.x = rect->x;
  textItem.window.rect.y = rect->y;
  textItem.window.rect.w = rect->w;
  textItem.window.rect.h = rect->h;
  textItem.window.border = 0;
  textItem.window.borderSize = 0.0f;
  textItem.textRect.x = 0.0f;
  textItem.textRect.y = 0.0f;
  textItem.textRect.w = 0.0f;
  textItem.textRect.h = 0.0f;
  textItem.textalignment = textalign;
  textItem.textvalignment = textvalign;
  textItem.textalignx = text_x;
  textItem.textaligny = text_y;
  textItem.textscale = scale;
  textItem.textStyle = textStyle;

  // Utilise existing wrap code
  Item_Text_Wrapped_Paint( &textItem );
}

void Item_Text_Paint( itemDef_t *item )
{
  char text[1024];
  const char *textPtr;
  vec4_t color;

  if( item->window.flags & WINDOW_WRAPPED )
  {
    Item_Text_Wrapped_Paint( item );
    return;
  }

  if( item->text == NULL )
  {
    if( item->cvar == NULL )
      return;
    else
    {
      DC->getCVarString( item->cvar, text, sizeof( text ) );
      textPtr = text;
    }
  }
  else
    textPtr = item->text;

  // this needs to go here as it sets extents for cvar types as well
  Item_SetTextExtents( item, textPtr );

  if( *textPtr == '\0' )
    return;

  Item_TextColor( item, &color );

  UI_Text_Paint( item->textRect.x, item->textRect.y, item->textscale, color, textPtr, 0, 0, item->textStyle );
}



void Item_TextField_Paint( itemDef_t *item )
{
  char            buff[1024];
  vec4_t          newColor;
  int             offset = ( item->text && *item->text ) ? ITEM_VALUE_OFFSET : 0;
  menuDef_t       *parent = ( menuDef_t* )item->parent;
  editFieldDef_t  *editPtr = item->typeData.edit;
  char            cursor = DC->getOverstrikeMode() ? '|' : '_';
  qboolean        editing = ( item->window.flags & WINDOW_HASFOCUS && g_editingField );
  const int       cursorWidth = editing ? EDIT_CURSOR_WIDTH : 0;

  //FIXME: causes duplicate printing if item->text is not set (NULL)
  Item_Text_Paint( item );

  buff[0] = '\0';

  if( item->cvar )
    DC->getCVarString( item->cvar, buff, sizeof( buff ) );

  // maxFieldWidth hasn't been set, so use the item's rect
  if( editPtr->maxFieldWidth == 0 )
  {
    editPtr->maxFieldWidth = item->window.rect.w -
                             ( item->textRect.w + offset + ( item->textRect.x - item->window.rect.x ) );

    if( editPtr->maxFieldWidth < MIN_FIELD_WIDTH )
      editPtr->maxFieldWidth = MIN_FIELD_WIDTH;
  }

  if( !editing )
    editPtr->paintOffset = 0;

  // Shorten string to max viewable
  while( UI_Text_Width( buff + editPtr->paintOffset, item->textscale ) >
         ( editPtr->maxFieldWidth - cursorWidth ) && strlen( buff ) > 0 )
    buff[ strlen( buff ) - 1 ] = '\0';

  parent = ( menuDef_t* )item->parent;

  if( item->window.flags & WINDOW_HASFOCUS )
    memcpy( newColor, &parent->focusColor, sizeof( vec4_t ) );
  else
    memcpy( &newColor, &item->window.foreColor, sizeof( vec4_t ) );

  if( editing )
  {
    UI_Text_PaintWithCursor( item->textRect.x + item->textRect.w + offset,
                             item->textRect.y, item->textscale, newColor,
                             buff + editPtr->paintOffset,
                             item->cursorPos - editPtr->paintOffset,
                             cursor, editPtr->maxPaintChars, item->textStyle );
  }
  else
  {
    UI_Text_Paint( item->textRect.x + item->textRect.w + offset,
                   item->textRect.y, item->textscale, newColor,
                   buff + editPtr->paintOffset, 0,
                   editPtr->maxPaintChars, item->textStyle );
  }

}

void Item_YesNo_Paint( itemDef_t *item )
{
  vec4_t newColor;
  float value;
  int offset;
  menuDef_t *parent = ( menuDef_t* )item->parent;

  value = ( item->cvar ) ? DC->getCVarValue( item->cvar ) : 0;

  if( item->window.flags & WINDOW_HASFOCUS )
    memcpy( newColor, &parent->focusColor, sizeof( vec4_t ) );
  else
    memcpy( &newColor, &item->window.foreColor, sizeof( vec4_t ) );

  offset = ( item->text && *item->text ) ? ITEM_VALUE_OFFSET : 0;

  if( item->text )
  {
    Item_Text_Paint( item );
    UI_Text_Paint( item->textRect.x + item->textRect.w + offset, item->textRect.y, item->textscale,
                   newColor, ( value != 0 ) ? "Yes" : "No", 0, 0, item->textStyle );
  }
  else
    UI_Text_Paint( item->textRect.x, item->textRect.y, item->textscale, newColor, ( value != 0 ) ? "Yes" : "No", 0, 0, item->textStyle );
}

void Item_Multi_Paint( itemDef_t *item )
{
  vec4_t newColor;
  const char *text = "";
  menuDef_t *parent = ( menuDef_t* )item->parent;

  if( item->window.flags & WINDOW_HASFOCUS )
    memcpy( newColor, &parent->focusColor, sizeof( vec4_t ) );
  else
    memcpy( &newColor, &item->window.foreColor, sizeof( vec4_t ) );

  text = Item_Multi_Setting( item );

  if( item->text )
  {
    Item_Text_Paint( item );
    UI_Text_Paint( item->textRect.x + item->textRect.w + ITEM_VALUE_OFFSET, item->textRect.y,
                   item->textscale, newColor, text, 0, 0, item->textStyle );
  }
  else
    UI_Text_Paint( item->textRect.x, item->textRect.y, item->textscale, newColor, text, 0, 0, item->textStyle );
}

void Item_Cycle_Paint( itemDef_t *item )
{
  vec4_t newColor;
  const char *text = "";
  menuDef_t *parent = (menuDef_t *)item->parent;

  if( item->window.flags & WINDOW_HASFOCUS )
    memcpy( newColor, &parent->focusColor, sizeof( vec4_t ) );
  else
    memcpy( &newColor, &item->window.foreColor, sizeof( vec4_t ) );

  if( item->typeData.cycle )
    text = DC->feederItemText( item->feederID, item->typeData.cycle->cursorPos,
                               0, NULL );

  if( item->text )
  {
    Item_Text_Paint( item );
    UI_Text_Paint( item->textRect.x + item->textRect.w + ITEM_VALUE_OFFSET, item->textRect.y,
                   item->textscale, newColor, text, 0, 0, item->textStyle );
  }
  else
    UI_Text_Paint( item->textRect.x, item->textRect.y, item->textscale, newColor, text, 0, 0, item->textStyle );
}


typedef struct
{
  char  *command;
  int   id;
  int   defaultbind1;
  int   defaultbind2;
  int   bind1;
  int   bind2;
}
bind_t;

typedef struct
{
  char *name;
  float defaultvalue;
  float value;
}
configcvar_t;


static bind_t g_bindings[] =
  {
    { "+scores",      K_TAB,         -1, -1, -1 },
    { "+button2",     K_ENTER,       -1, -1, -1 },
    { "+speed",       K_SHIFT,       -1, -1, -1 },
    { "+button6",     'z',           -1, -1, -1 }, // human dodging
    { "+button8",     'x',           -1, -1, -1 },
    { "+forward",     K_UPARROW,     -1, -1, -1 },
    { "+back",        K_DOWNARROW,   -1, -1, -1 },
    { "+moveleft",    ',',           -1, -1, -1 },
    { "+moveright",   '.',           -1, -1, -1 },
    { "+moveup",      K_SPACE,       -1, -1, -1 },
    { "+movedown",    'c',           -1, -1, -1 },
    { "+left",        K_LEFTARROW,   -1, -1, -1 },
    { "+right",       K_RIGHTARROW,  -1, -1, -1 },
    { "+strafe",      K_ALT,         -1, -1, -1 },
    { "+lookup",      K_PGDN,        -1, -1, -1 },
    { "+lookdown",    K_DEL,         -1, -1, -1 },
    { "+mlook",       '/',           -1, -1, -1 },
    { "centerview",   K_END,         -1, -1, -1 },
    { "+zoom",        -1,            -1, -1, -1 },
    { "weapon 1",     '1',           -1, -1, -1 },
    { "weapon 2",     '2',           -1, -1, -1 },
    { "weapon 3",     '3',           -1, -1, -1 },
    { "weapon 4",     '4',           -1, -1, -1 },
    { "weapon 5",     '5',           -1, -1, -1 },
    { "weapon 6",     '6',           -1, -1, -1 },
    { "weapon 7",     '7',           -1, -1, -1 },
    { "weapon 8",     '8',           -1, -1, -1 },
    { "weapon 9",     '9',           -1, -1, -1 },
    { "weapon 10",    '0',           -1, -1, -1 },
    { "weapon 11",    -1,            -1, -1, -1 },
    { "weapon 12",    -1,            -1, -1, -1 },
    { "weapon 13",    -1,            -1, -1, -1 },
    { "+attack",      K_MOUSE1,      -1, -1, -1 },
    { "+button5",     K_MOUSE2,      -1, -1, -1 }, // secondary attack
    { "reload",       'r',           -1, -1, -1 }, // reload
    { "buy ammo",     'b',           -1, -1, -1 }, // buy ammo
    { "itemact medkit", 'm',         -1, -1, -1 }, // use medkit
    { "+button7",     'q',           -1, -1, -1 }, // buildable use
    { "deconstruct",  'e',           -1, -1, -1 }, // buildable destroy
    { "weapprev",     '[',           -1, -1, -1 },
    { "weapnext",     ']',           -1, -1, -1 },
    { "+button3",     K_MOUSE3,      -1, -1, -1 },
    { "+button4",     K_MOUSE4,      -1, -1, -1 },
    { "vote yes",     K_F1,          -1, -1, -1 },
    { "vote no",      K_F2,          -1, -1, -1 },
    { "teamvote yes", K_F3,          -1, -1, -1 },
    { "teamvote no",  K_F4,          -1, -1, -1 },
    { "scoresUp",      K_KP_PGUP,    -1, -1, -1 },
    { "scoresDown",    K_KP_PGDN,    -1, -1, -1 },
    { "screenshotJPEG",-1,           -1, -1, -1 },
    { "messagemode",  -1,            -1, -1, -1 },
    { "messagemode2", -1,            -1, -1, -1 }
  };


static const size_t g_bindCount = ARRAY_LEN( g_bindings );

/*
=================
Controls_GetKeyAssignment
=================
*/
static void Controls_GetKeyAssignment ( char *command, int *twokeys )
{
  int   count;
  int   j;
  char  b[256];

  twokeys[0] = twokeys[1] = -1;
  count = 0;

  for( j = 0; j < 256; j++ )
  {
    DC->getBindingBuf( j, b, 256 );

    if( *b == 0 )
      continue;

    if( !Q_stricmp( b, command ) )
    {
      twokeys[count] = j;
      count++;

      if( count == 2 )
        break;
    }
  }
}

/*
=================
Controls_GetConfig
=================
*/
void Controls_GetConfig( void )
{
  int   i;
  int   twokeys[ 2 ];

  // iterate each command, get its numeric binding

  for( i = 0; i < g_bindCount; i++ )
  {
    Controls_GetKeyAssignment( g_bindings[ i ].command, twokeys );

    g_bindings[ i ].bind1 = twokeys[ 0 ];
    g_bindings[ i ].bind2 = twokeys[ 1 ];
  }
}

/*
=================
Controls_SetConfig
=================
*/
void Controls_SetConfig( qboolean restart )
{
  int   i;

  // iterate each command, get its numeric binding

  for( i = 0; i < g_bindCount; i++ )
  {
    if( g_bindings[i].bind1 != -1 )
    {
      DC->setBinding( g_bindings[i].bind1, g_bindings[i].command );

      if( g_bindings[i].bind2 != -1 )
        DC->setBinding( g_bindings[i].bind2, g_bindings[i].command );
    }
  }

  DC->executeText( EXEC_APPEND, "in_restart\n" );
}

/*
=================
Controls_SetDefaults
=================
*/
void Controls_SetDefaults( void )
{
  int i;

  // iterate each command, set its default binding

  for( i = 0; i < g_bindCount; i++ )
  {
    g_bindings[i].bind1 = g_bindings[i].defaultbind1;
    g_bindings[i].bind2 = g_bindings[i].defaultbind2;
  }
}

int BindingIDFromName( const char *name )
{
  int i;

  for( i = 0; i < g_bindCount; i++ )
  {
    if( Q_stricmp( name, g_bindings[i].command ) == 0 )
      return i;
  }

  return -1;
}

char g_nameBind1[32];
char g_nameBind2[32];

void BindingFromName( const char *cvar )
{
  int i, b1, b2;

  // iterate each command, set its default binding

  for( i = 0; i < g_bindCount; i++ )
  {
    if( Q_stricmp( cvar, g_bindings[i].command ) == 0 )
    {
      b1 = g_bindings[i].bind1;

      if( b1 == -1 )
        break;

      DC->keynumToStringBuf( b1, g_nameBind1, 32 );
      Q_strupr( g_nameBind1 );

      b2 = g_bindings[i].bind2;

      if( b2 != -1 )
      {
        DC->keynumToStringBuf( b2, g_nameBind2, 32 );
        Q_strupr( g_nameBind2 );
        strcat( g_nameBind1, " or " );
        strcat( g_nameBind1, g_nameBind2 );
      }

      return;
    }
  }

  strcpy( g_nameBind1, "???" );
}

void Item_Slider_Paint( itemDef_t *item )
{
  vec4_t newColor;
  float x, y;
  menuDef_t *parent = ( menuDef_t* )item->parent;
  float vScale = Item_Slider_VScale( item );

  if( item->window.flags & WINDOW_HASFOCUS )
    memcpy( newColor, &parent->focusColor, sizeof( vec4_t ) );
  else
    memcpy( &newColor, &item->window.foreColor, sizeof( vec4_t ) );

  if( item->text )
  {
    Item_Text_Paint( item );
    x = item->textRect.x + item->textRect.w + ITEM_VALUE_OFFSET;
    y = item->textRect.y - item->textRect.h +
        ( ( item->textRect.h - ( SLIDER_HEIGHT * vScale ) ) / 2.0f );
  }
  else
  {
    x = item->window.rect.x;
    y = item->window.rect.y;
  }

  DC->setColor( newColor );
  DC->drawHandlePic( x, y, SLIDER_WIDTH, SLIDER_HEIGHT * vScale, DC->Assets.sliderBar );

  y = item->textRect.y - item->textRect.h +
      ( ( item->textRect.h - ( SLIDER_THUMB_HEIGHT * vScale ) ) / 2.0f );

  x = Item_Slider_ThumbPosition( item );
  DC->drawHandlePic( x - ( SLIDER_THUMB_WIDTH / 2 ), y,
                     SLIDER_THUMB_WIDTH, SLIDER_THUMB_HEIGHT * vScale, DC->Assets.sliderThumb );

}

void Item_Bind_Paint( itemDef_t *item )
{
  vec4_t newColor, lowLight;
  float value;
  int maxChars = 0;
  menuDef_t *parent = ( menuDef_t* )item->parent;

  if( item->typeData.edit )
    maxChars = item->typeData.edit->maxPaintChars;

  value = ( item->cvar ) ? DC->getCVarValue( item->cvar ) : 0;

  if( item->window.flags & WINDOW_HASFOCUS )
  {
    if( g_bindItem == item )
    {
      lowLight[0] = 0.8f * parent->focusColor[0];
      lowLight[1] = 0.8f * parent->focusColor[1];
      lowLight[2] = 0.8f * parent->focusColor[2];
      lowLight[3] = 0.8f * parent->focusColor[3];

      LerpColor( parent->focusColor, lowLight, newColor,
                 0.5 + 0.5 * sin( DC->realTime / PULSE_DIVISOR ) );
    }
    else
      memcpy( &newColor, &parent->focusColor, sizeof( vec4_t ) );
  }
  else
    memcpy( &newColor, &item->window.foreColor, sizeof( vec4_t ) );

  if( item->text )
  {
    Item_Text_Paint( item );

    if( g_bindItem == item && g_waitingForKey )
    {
      UI_Text_Paint( item->textRect.x + item->textRect.w + ITEM_VALUE_OFFSET, item->textRect.y,
                     item->textscale, newColor, "Press key", 0, maxChars, item->textStyle );
    }
    else
    {
      BindingFromName( item->cvar );
      UI_Text_Paint( item->textRect.x + item->textRect.w + ITEM_VALUE_OFFSET, item->textRect.y,
                     item->textscale, newColor, g_nameBind1, 0, maxChars, item->textStyle );
    }
  }
  else
    UI_Text_Paint( item->textRect.x, item->textRect.y, item->textscale, newColor,
                   ( value != 0 ) ? "FIXME" : "FIXME", 0, maxChars, item->textStyle );
}

qboolean Display_KeyBindPending( void )
{
  return g_waitingForKey;
}

qboolean Item_Bind_HandleKey( itemDef_t *item, int key, qboolean down )
{
  int     id;
  int     i;

  if( Rect_ContainsPoint( &item->window.rect, DC->cursorx, DC->cursory ) && !g_waitingForKey )
  {
    if( down && ( key == K_MOUSE1 || key == K_ENTER ) )
    {
      g_waitingForKey = qtrue;
      g_bindItem = item;
    }

    return qtrue;
  }
  else
  {
    if( !g_waitingForKey || g_bindItem == NULL )
      return qtrue;

    if( key & K_CHAR_FLAG )
      return qtrue;

    switch( key )
    {
      case K_ESCAPE:
        g_waitingForKey = qfalse;
        return qtrue;

      case K_BACKSPACE:
        id = BindingIDFromName( item->cvar );

        if( id != -1 )
        {
          g_bindings[id].bind1 = -1;
          g_bindings[id].bind2 = -1;
        }

        Controls_SetConfig( qtrue );
        g_waitingForKey = qfalse;
        g_bindItem = NULL;
        return qtrue;

      case '`':
        return qtrue;
    }
  }

  if( key != -1 )
  {
    for( i = 0; i < g_bindCount; i++ )
    {
      if( g_bindings[i].bind2 == key )
        g_bindings[i].bind2 = -1;

      if( g_bindings[i].bind1 == key )
      {
        g_bindings[i].bind1 = g_bindings[i].bind2;
        g_bindings[i].bind2 = -1;
      }
    }
  }


  id = BindingIDFromName( item->cvar );

  if( id != -1 )
  {
    if( key == -1 )
    {
      if( g_bindings[id].bind1 != -1 )
      {
        DC->setBinding( g_bindings[id].bind1, "" );
        g_bindings[id].bind1 = -1;
      }

      if( g_bindings[id].bind2 != -1 )
      {
        DC->setBinding( g_bindings[id].bind2, "" );
        g_bindings[id].bind2 = -1;
      }
    }
    else if( g_bindings[id].bind1 == -1 )
      g_bindings[id].bind1 = key;
    else if( g_bindings[id].bind1 != key && g_bindings[id].bind2 == -1 )
      g_bindings[id].bind2 = key;
    else
    {
      DC->setBinding( g_bindings[id].bind1, "" );
      DC->setBinding( g_bindings[id].bind2, "" );
      g_bindings[id].bind1 = key;
      g_bindings[id].bind2 = -1;
    }
  }

  Controls_SetConfig( qtrue );
  g_waitingForKey = qfalse;

  return qtrue;
}


void Item_Model_Paint( itemDef_t *item )
{
  float x, y, w, h;
  refdef_t refdef;
  refEntity_t   ent;
  vec3_t      mins, maxs, origin;
  vec3_t      angles;
  modelDef_t *modelPtr = item->typeData.model;

  if( modelPtr == NULL )
    return;

  // setup the refdef
  memset( &refdef, 0, sizeof( refdef ) );

  refdef.rdflags = RDF_NOWORLDMODEL;

  AxisClear( refdef.viewaxis );

  x = item->window.rect.x + 1;
  y = item->window.rect.y + 1;
  w = item->window.rect.w - 2;
  h = item->window.rect.h - 2;

  UI_AdjustFrom640( &x, &y, &w, &h );

  refdef.x = x;
  refdef.y = y;
  refdef.width = w;
  refdef.height = h;

  DC->modelBounds( item->asset, mins, maxs );

  origin[2] = -0.5 * ( mins[2] + maxs[2] );
  origin[1] = 0.5 * ( mins[1] + maxs[1] );

  // calculate distance so the model nearly fills the box
  if( qtrue )
  {
    float len = 0.5 * ( maxs[2] - mins[2] );
    origin[0] = len / 0.268;  // len / tan( fov/2 )
    //origin[0] = len / tan(w/2);
  }
  else
    origin[0] = item->textscale;

  refdef.fov_x = ( modelPtr->fov_x ) ? modelPtr->fov_x : w;
  refdef.fov_y = ( modelPtr->fov_y ) ? modelPtr->fov_y : h;

  //refdef.fov_x = (int)((float)refdef.width / 640.0f * 90.0f);
  //xx = refdef.width / tan( refdef.fov_x / 360 * M_PI );
  //refdef.fov_y = atan2( refdef.height, xx );
  //refdef.fov_y *= ( 360 / M_PI );

  DC->clearScene();

  refdef.time = DC->realTime;

  // add the model

  memset( &ent, 0, sizeof( ent ) );

  //adjust = 5.0 * sin( (float)uis.realtime / 500 );
  //adjust = 360 % (int)((float)uis.realtime / 1000);
  //VectorSet( angles, 0, 0, 1 );

  // use item storage to track

  if( modelPtr->rotationSpeed )
  {
    if( DC->realTime > item->window.nextTime )
    {
      item->window.nextTime = DC->realTime + modelPtr->rotationSpeed;
      modelPtr->angle = ( int )( modelPtr->angle + 1 ) % 360;
    }
  }

  VectorSet( angles, 0, modelPtr->angle, 0 );
  AnglesToAxis( angles, ent.axis );

  ent.hModel = item->asset;
  VectorCopy( origin, ent.origin );
  VectorCopy( origin, ent.lightingOrigin );
  ent.renderfx = RF_LIGHTING_ORIGIN | RF_NOSHADOW;
  VectorCopy( ent.origin, ent.oldorigin );

  DC->addRefEntityToScene( &ent );
  DC->renderScene( &refdef );

}


void Item_ListBoxRow_Paint( itemDef_t *item, int row, int renderPos, qboolean highlight, qboolean scrollbar )
{
  float         x, y, w;
  listBoxDef_t  *listPtr = item->typeData.list;
  menuDef_t     *menu = ( menuDef_t * )item->parent;
  float         one, two;

  one = 1.0f * DC->aspectScale;
  two = 2.0f * DC->aspectScale;

  x = SCROLLBAR_X( item );
  y = SCROLLBAR_Y( item ) + ( listPtr->elementHeight * renderPos );
  w = item->window.rect.w - ( two * item->window.borderSize );

  if( scrollbar )
    w -= SCROLLBAR_ARROW_WIDTH;

  if( listPtr->elementStyle == LISTBOX_IMAGE )
  {
    qhandle_t image = DC->feederItemImage( item->feederID, row );

    UI_SetClipRegion( x, y, listPtr->elementWidth, listPtr->elementHeight );

    if( image )
      DC->drawHandlePic( x + one, y + 1.0f, listPtr->elementWidth - two, listPtr->elementHeight - 2.0f, image );

    if( highlight && row == item->cursorPos )
    {
      DC->drawRect( x, y, listPtr->elementWidth, listPtr->elementHeight,
          item->window.borderSize, item->window.borderColor );
    }

    UI_ClearClipRegion( );
  }
  else
  {
    const float m = UI_Text_EmHeight( item->textscale );
    char        text[ MAX_STRING_CHARS ];
    qhandle_t   optionalImage;

    if( listPtr->numColumns > 0 )
    {
      int   j;

      for( j = 0; j < listPtr->numColumns; j++ )
      {
        float columnPos;
        float width, height, yOffset;

        if( menu->window.aspectBias != ASPECT_NONE || item->window.aspectBias != ASPECT_NONE )
        {
          columnPos = ( listPtr->columnInfo[ j ].pos + 4.0f ) * DC->aspectScale;
          width = listPtr->columnInfo[ j ].width * DC->aspectScale;
        }
        else
        {
          columnPos = ( listPtr->columnInfo[ j ].pos + 4.0f );
          width = listPtr->columnInfo[ j ].width;
        }

        height = listPtr->columnInfo[ j ].width;
        yOffset = y + ( ( listPtr->elementHeight - height ) / 2.0f );

        Q_strncpyz( text, DC->feederItemText( item->feederID, row, j, &optionalImage ), sizeof( text ) );

        UI_SetClipRegion( x + columnPos, yOffset, width, height );

        if( optionalImage >= 0 )
          DC->drawHandlePic( x + columnPos, yOffset, width, height, optionalImage );
        else if( text[ 0 ] )
        {
          float alignOffset = 0.0f, tw;

          tw = UI_Text_Width( text, item->textscale );

          switch( listPtr->columnInfo[ j ].align )
          {
            case ALIGN_LEFT:
              alignOffset = 0.0f;
              break;

            case ALIGN_RIGHT:
              alignOffset = width - tw;
              break;

            case ALIGN_CENTER:
              alignOffset = ( width / 2.0f ) - ( tw / 2.0f );
              break;

            default:
              alignOffset = 0.0f;
          }

          UI_Text_Paint( x + columnPos + alignOffset,
              y + m + ( ( listPtr->elementHeight - m ) / 2.0f ),
              item->textscale, item->window.foreColor, text, 0,
              0, item->textStyle );
        }

        UI_ClearClipRegion( );
      }
    }
    else
    {
      float offset;

      if( menu->window.aspectBias != ASPECT_NONE || item->window.aspectBias != ASPECT_NONE )
        offset = 4.0f * DC->aspectScale;
      else
        offset = 4.0f;

      Q_strncpyz( text, DC->feederItemText( item->feederID, row, 0, &optionalImage ), sizeof( text ) );

      UI_SetClipRegion( x, y, w, listPtr->elementHeight );

      if( optionalImage >= 0 )
        DC->drawHandlePic( x + offset, y, listPtr->elementHeight, listPtr->elementHeight, optionalImage );
      else if( text[ 0 ] )
      {
        UI_Text_Paint( x + offset, y + m + ( ( listPtr->elementHeight - m ) / 2.0f ),
            item->textscale, item->window.foreColor, text, 0,
            0, item->textStyle );
      }

      UI_ClearClipRegion( );
    }

    if( highlight && row == item->cursorPos )
      DC->fillRect( x, y, w, listPtr->elementHeight, item->window.outlineColor );
  }
}

void Item_ListBox_Paint( itemDef_t *item )
{
  float         size;
  int           i;
  listBoxDef_t  *listPtr = item->typeData.list;
  int           count = DC->feederCount( item->feederID );
  qboolean      scrollbar = !listPtr->noscrollbar &&
                            count > Item_ListBox_NumItemsForItemHeight( item );

  if( scrollbar )
  {
    float x = SCROLLBAR_SLIDER_X( item );
    float y = SCROLLBAR_Y( item );
    float thumbY = Item_ListBox_ThumbDrawPosition( item );

    // Up arrow
    DC->drawHandlePic( x, y, SCROLLBAR_ARROW_WIDTH, SCROLLBAR_ARROW_HEIGHT, DC->Assets.scrollBarArrowUp );
    y = SCROLLBAR_SLIDER_Y( item );

    // Scroll bar
    size = SCROLLBAR_SLIDER_HEIGHT( item );
    DC->drawHandlePic( x, y, SCROLLBAR_ARROW_WIDTH, size, DC->Assets.scrollBar );
    y = SCROLLBAR_SLIDER_Y( item ) + size;

    // Down arrow
    DC->drawHandlePic( x, y, SCROLLBAR_ARROW_WIDTH, SCROLLBAR_ARROW_HEIGHT, DC->Assets.scrollBarArrowDown );

    // Thumb
    DC->drawHandlePic( x, thumbY, SCROLLBAR_ARROW_WIDTH, SCROLLBAR_ARROW_HEIGHT, DC->Assets.scrollBarThumb );
  }

  // Paint rows
  for( i = listPtr->startPos; i < listPtr->endPos; i++ )
    Item_ListBoxRow_Paint( item, i, i - listPtr->startPos, qtrue, scrollbar );
}

void Item_Paint( itemDef_t *item );

void Item_ComboBox_Paint( itemDef_t *item )
{
  float x, y, h;

  x = SCROLLBAR_SLIDER_X( item );
  y = SCROLLBAR_Y( item );
  h = item->window.rect.h - 2.0f;

  // Down arrow
  DC->drawHandlePic( x, y, SCROLLBAR_ARROW_WIDTH, h, DC->Assets.scrollBarArrowDown );

  Item_ListBoxRow_Paint( item, item->cursorPos, 0, qfalse, qtrue );

  if( g_comboBoxItem != NULL )
  {
    qboolean cast = Item_ComboBox_MaybeCastToListBox( item );
    Item_Paint( item );
    Item_ComboBox_MaybeUnCastFromListBox( item, cast );
  }
}

void Item_ListBox_Update( itemDef_t *item )
{
  listBoxDef_t  *listPtr = item->typeData.list;
  int           feederCount = DC->feederCount( item->feederID );

  if( listPtr->lastFeederCount != feederCount )
  {
    if( listPtr->resetonfeederchange )
    {
      item->cursorPos = DC->feederInitialise( item->feederID );
      Item_ListBox_SetStartPos( item, 0 );
      DC->feederSelection( item->feederID, item->cursorPos );
    }
    else
    {
      // Make sure endPos is up-to-date
      Item_ListBox_SetStartPos( item, listPtr->startPos );

      // If the selection is off the end now, select the last element
      if( item->cursorPos >= feederCount )
        item->cursorPos = feederCount - 1;
    }
  }

  listPtr->lastFeederCount = feederCount;
}

void Item_OwnerDraw_Paint( itemDef_t *item )
{
  menuDef_t *parent;
  const char *text;

  if( item == NULL )
    return;

  parent = ( menuDef_t* )item->parent;

  if( DC->ownerDrawItem )
  {
    vec4_t color, lowLight;
    Fade( &item->window.flags, &item->window.foreColor[3], parent->fadeClamp, &item->window.nextTime,
          parent->fadeCycle, qtrue, parent->fadeAmount );
    memcpy( &color, &item->window.foreColor, sizeof( color ) );

    if( item->numColors > 0 && DC->getValue )
    {
      // if the value is within one of the ranges then set color to that, otherwise leave at default
      int i;
      float f = DC->getValue( item->window.ownerDraw );

      for( i = 0; i < item->numColors; i++ )
      {
        if( f >= item->colorRanges[i].low && f <= item->colorRanges[i].high )
        {
          memcpy( &color, &item->colorRanges[i].color, sizeof( color ) );
          break;
        }
      }
    }

    if( item->window.flags & WINDOW_HASFOCUS )
      memcpy( color, &parent->focusColor, sizeof( vec4_t ) );
    else if( item->textStyle == ITEM_TEXTSTYLE_BLINK && !( ( DC->realTime / BLINK_DIVISOR ) & 1 ) )
    {
      lowLight[0] = 0.8 * item->window.foreColor[0];
      lowLight[1] = 0.8 * item->window.foreColor[1];
      lowLight[2] = 0.8 * item->window.foreColor[2];
      lowLight[3] = 0.8 * item->window.foreColor[3];
      LerpColor( item->window.foreColor, lowLight, color, 0.5 + 0.5 * sin( DC->realTime / PULSE_DIVISOR ) );
    }

    if( item->cvarFlags & ( CVAR_ENABLE | CVAR_DISABLE ) && !Item_EnableShowViaCvar( item, CVAR_ENABLE ) )
      Com_Memcpy( color, parent->disableColor, sizeof( vec4_t ) );

    if( DC->ownerDrawText && ( text = DC->ownerDrawText( item->window.ownerDraw ) ) )
    {
      if( item->text && *item->text )
      {
        Item_Text_Paint( item );

        UI_Text_Paint( item->textRect.x + item->textRect.w + ITEM_VALUE_OFFSET,
            item->textRect.y, item->textscale,
            color, text, 0, 0, item->textStyle );
      }
      else
      {
        item->text = text;
        Item_Text_Paint( item );
        item->text = NULL;
      }
    }
    else
    {
      DC->ownerDrawItem( item->window.rect.x, item->window.rect.y,
          item->window.rect.w, item->window.rect.h,
          item->textalignx, item->textaligny,
          item->window.ownerDraw, item->window.ownerDrawFlags,
          item->alignment, item->textalignment, item->textvalignment,
          item->window.borderSize, item->textscale, color, item->window.backColor,
          item->window.background, item->textStyle );
    }
  }
}


void Item_Update( itemDef_t *item )
{
  if( item == NULL )
    return;

  if( Item_IsListBox( item ) )
    Item_ListBox_Update( item );
}

void Item_Paint( itemDef_t *item )
{
  vec4_t red;
  menuDef_t *parent = ( menuDef_t* )item->parent;
  red[0] = red[3] = 1;
  red[1] = red[2] = 0;

  if( item == NULL )
    return;

  if( item->window.flags & WINDOW_ORBITING )
  {
    if( DC->realTime > item->window.nextTime )
    {
      float rx, ry, a, c, s, w, h;

      item->window.nextTime = DC->realTime + item->window.offsetTime;
      // translate
      w = item->window.rectClient.w / 2;
      h = item->window.rectClient.h / 2;
      rx = item->window.rectClient.x + w - item->window.rectEffects.x;
      ry = item->window.rectClient.y + h - item->window.rectEffects.y;
      a = 3 * M_PI / 180;
      c = cos( a );
      s = sin( a );
      item->window.rectClient.x = ( rx * c - ry * s ) + item->window.rectEffects.x - w;
      item->window.rectClient.y = ( rx * s + ry * c ) + item->window.rectEffects.y - h;
      Item_UpdatePosition( item );

    }
  }


  if( item->window.flags & WINDOW_INTRANSITION )
  {
    if( DC->realTime > item->window.nextTime )
    {
      int done = 0;
      item->window.nextTime = DC->realTime + item->window.offsetTime;
      // transition the x,y

      if( item->window.rectClient.x == item->window.rectEffects.x )
        done++;
      else
      {
        if( item->window.rectClient.x < item->window.rectEffects.x )
        {
          item->window.rectClient.x += item->window.rectEffects2.x;

          if( item->window.rectClient.x > item->window.rectEffects.x )
          {
            item->window.rectClient.x = item->window.rectEffects.x;
            done++;
          }
        }
        else
        {
          item->window.rectClient.x -= item->window.rectEffects2.x;

          if( item->window.rectClient.x < item->window.rectEffects.x )
          {
            item->window.rectClient.x = item->window.rectEffects.x;
            done++;
          }
        }
      }

      if( item->window.rectClient.y == item->window.rectEffects.y )
        done++;
      else
      {
        if( item->window.rectClient.y < item->window.rectEffects.y )
        {
          item->window.rectClient.y += item->window.rectEffects2.y;

          if( item->window.rectClient.y > item->window.rectEffects.y )
          {
            item->window.rectClient.y = item->window.rectEffects.y;
            done++;
          }
        }
        else
        {
          item->window.rectClient.y -= item->window.rectEffects2.y;

          if( item->window.rectClient.y < item->window.rectEffects.y )
          {
            item->window.rectClient.y = item->window.rectEffects.y;
            done++;
          }
        }
      }

      if( item->window.rectClient.w == item->window.rectEffects.w )
        done++;
      else
      {
        if( item->window.rectClient.w < item->window.rectEffects.w )
        {
          item->window.rectClient.w += item->window.rectEffects2.w;

          if( item->window.rectClient.w > item->window.rectEffects.w )
          {
            item->window.rectClient.w = item->window.rectEffects.w;
            done++;
          }
        }
        else
        {
          item->window.rectClient.w -= item->window.rectEffects2.w;

          if( item->window.rectClient.w < item->window.rectEffects.w )
          {
            item->window.rectClient.w = item->window.rectEffects.w;
            done++;
          }
        }
      }

      if( item->window.rectClient.h == item->window.rectEffects.h )
        done++;
      else
      {
        if( item->window.rectClient.h < item->window.rectEffects.h )
        {
          item->window.rectClient.h += item->window.rectEffects2.h;

          if( item->window.rectClient.h > item->window.rectEffects.h )
          {
            item->window.rectClient.h = item->window.rectEffects.h;
            done++;
          }
        }
        else
        {
          item->window.rectClient.h -= item->window.rectEffects2.h;

          if( item->window.rectClient.h < item->window.rectEffects.h )
          {
            item->window.rectClient.h = item->window.rectEffects.h;
            done++;
          }
        }
      }

      Item_UpdatePosition( item );

      if( done == 4 )
        item->window.flags &= ~WINDOW_INTRANSITION;

    }
  }

  if( item->window.ownerDrawFlags && DC->ownerDrawVisible )
  {
    if( !DC->ownerDrawVisible( item->window.ownerDrawFlags ) )
      item->window.flags &= ~WINDOW_VISIBLE;
    else
      item->window.flags |= WINDOW_VISIBLE;
  }

  if( item->cvarFlags & ( CVAR_SHOW | CVAR_HIDE ) )
  {
    if( !Item_EnableShowViaCvar( item, CVAR_SHOW ) )
      return;
  }

  if( item->window.flags & WINDOW_TIMEDVISIBLE )
  {
  }

  if( !( item->window.flags & WINDOW_VISIBLE ) )
    return;

  Window_Paint( &item->window, parent->fadeAmount, parent->fadeClamp, parent->fadeCycle );

  if( DC->getCVarValue( "ui_developer" ) )
  {
    vec4_t color;
    rectDef_t *r = Item_CorrectedTextRect( item );
    color[1] = color[3] = 1;
    color[0] = color[2] = 0;
    DC->drawRect( r->x, r->y, r->w, r->h, 1, color );
  }

  switch( item->type )
  {
    case ITEM_TYPE_OWNERDRAW:
      Item_OwnerDraw_Paint( item );
      break;

    case ITEM_TYPE_TEXT:
    case ITEM_TYPE_BUTTON:
      Item_Text_Paint( item );
      break;

    case ITEM_TYPE_RADIOBUTTON:
      break;

    case ITEM_TYPE_CHECKBOX:
      break;

    case ITEM_TYPE_CYCLE:
      Item_Cycle_Paint( item );
      break;

    case ITEM_TYPE_LISTBOX:
      Item_ListBox_Paint( item );
      break;

    case ITEM_TYPE_COMBOBOX:
      Item_ComboBox_Paint( item );
      break;

    case ITEM_TYPE_MODEL:
      Item_Model_Paint( item );
      break;

    case ITEM_TYPE_YESNO:
      Item_YesNo_Paint( item );
      break;

    case ITEM_TYPE_MULTI:
      Item_Multi_Paint( item );
      break;

    case ITEM_TYPE_BIND:
      Item_Bind_Paint( item );
      break;

    case ITEM_TYPE_SLIDER:
      Item_Slider_Paint( item );
      break;

    default:
      if( Item_IsEditField( item ) )
        Item_TextField_Paint( item );

      break;
  }

  Border_Paint( &item->window );
}

void Menu_Init( menuDef_t *menu )
{
  memset( menu, 0, sizeof( menuDef_t ) );
  menu->cursorItem = -1;
  menu->fadeAmount = DC->Assets.fadeAmount;
  menu->fadeClamp = DC->Assets.fadeClamp;
  menu->fadeCycle = DC->Assets.fadeCycle;
  Window_Init( &menu->window );
  menu->window.aspectBias = ALIGN_CENTER;
}

itemDef_t *Menu_GetFocusedItem( menuDef_t *menu )
{
  int i;

  if( menu )
  {
    for( i = 0; i < menu->itemCount; i++ )
    {
      if( menu->items[i]->window.flags & WINDOW_HASFOCUS )
        return menu->items[i];
    }
  }

  return NULL;
}

menuDef_t *Menu_GetFocused( void )
{
  int i;

  for( i = 0; i < menuCount; i++ )
  {
    if( Menus[i].window.flags & WINDOW_HASFOCUS && Menus[i].window.flags & WINDOW_VISIBLE )
      return & Menus[i];
  }

  return NULL;
}

void Menu_ScrollFeeder( menuDef_t *menu, int feeder, qboolean down )
{
  if( menu )
  {
    int i;

    for( i = 0; i < menu->itemCount; i++ )
    {
      itemDef_t *item = menu->items[ i ];

      if( item->feederID == feeder )
      {
        qboolean cast = Item_ComboBox_MaybeCastToListBox( item );
        Item_ListBox_HandleKey( item, down ? K_DOWNARROW : K_UPARROW, qtrue, qtrue );
        Item_ComboBox_MaybeUnCastFromListBox( item, cast );

        return;
      }
    }
  }
}



void Menu_SetFeederSelection( menuDef_t *menu, int feeder, int index, const char *name )
{
  if( menu == NULL )
  {
    if( name == NULL )
      menu = Menu_GetFocused();
    else
      menu = Menus_FindByName( name );
  }

  if( menu )
  {
    int i;

    for( i = 0; i < menu->itemCount; i++ )
    {
      if( menu->items[i]->feederID == feeder )
      {
        if( Item_IsListBox( menu->items[i] ) && index == 0 )
        {
          menu->items[ i ]->typeData.list->cursorPos = 0;
          Item_ListBox_SetStartPos( menu->items[ i ], 0 );
        }

        menu->items[i]->cursorPos = index;
        DC->feederSelection( menu->items[i]->feederID, menu->items[i]->cursorPos );
        return;
      }
    }
  }
}

qboolean Menus_AnyFullScreenVisible( void )
{
  int i;

  for( i = 0; i < menuCount; i++ )
  {
    if( Menus[i].window.flags & WINDOW_VISIBLE && Menus[i].fullScreen )
      return qtrue;
  }

  return qfalse;
}

menuDef_t *Menus_ActivateByName( const char *p )
{
  int i;
  menuDef_t *m = NULL;

  // Activate one menu

  for( i = 0; i < menuCount; i++ )
  {
    if( Q_stricmp( Menus[i].window.name, p ) == 0 )
    {
      m = &Menus[i];
      Menus_Activate( m );
      break;
    }
  }

  // Defocus the others
  for( i = 0; i < menuCount; i++ )
  {
    if( Q_stricmp( Menus[i].window.name, p ) != 0 )
      Menus[i].window.flags &= ~WINDOW_HASFOCUS;
  }

  return m;
}

menuDef_t *Menus_ReplaceActiveByName( const char *p )
{
  int i;
  menuDef_t *m = NULL;

  // Activate one menu

  for( i = 0; i < menuCount; i++ )
  {
    if( Q_stricmp( Menus[i].window.name, p ) == 0 )
    {
      m = &Menus[i];
      if(! Menus_ReplaceActive( m ) )
        return NULL;
      break;
    }
  }
  return m;
}


void Item_Init( itemDef_t *item )
{
  memset( item, 0, sizeof( itemDef_t ) );
  item->textscale = 0.55f;
  Window_Init( &item->window );
  item->window.aspectBias = ASPECT_NONE;
}

static qboolean Item_HandleMouseMove( itemDef_t *item, float x, float y, int pass, qboolean focusSet )
{
  if( Rect_ContainsPoint( &item->window.rect, x, y ) )
  {
    if( pass == 1 )
    {
      if( item->type == ITEM_TYPE_TEXT && item->text )
      {
        if( !Rect_ContainsPoint( Item_CorrectedTextRect( item ), x, y ) )
          return qtrue;
      }

      // if we are over an item
      if( IsVisible( item->window.flags ) )
      {
        // different one
        Item_MouseEnter( item, x, y );

        if( !focusSet )
          focusSet = Item_SetFocus( item, x, y );
      }
    }

    return qtrue;
  }

  return qfalse;
}

void Menu_HandleMouseMove( menuDef_t *menu, float x, float y )
{
  int i, pass;
  qboolean focusSet = qfalse;
  qboolean result;
  qboolean cast;

  if( menu == NULL )
    return;

  if( !( menu->window.flags & ( WINDOW_VISIBLE | WINDOW_FORCED ) ) )
    return;

  if( itemCapture )
  {
    //Item_MouseMove(itemCapture, x, y);
    return;
  }

  if( g_waitingForKey || g_editingField )
    return;

  if( g_comboBoxItem != NULL )
  {
    Item_SetFocus( g_comboBoxItem, x, y );
    focusSet = qtrue;
  }

  // FIXME: this is the whole issue of focus vs. mouse over..
  // need a better overall solution as i don't like going through everything twice
  for( pass = 0; pass < 2; pass++ )
  {
    for( i = 0; i < menu->itemCount; i++ )
    {
      itemDef_t *item = menu->items[ i ];

      // turn off focus each item
      // menu->items[i].window.flags &= ~WINDOW_HASFOCUS;

      if( !( item->window.flags & ( WINDOW_VISIBLE | WINDOW_FORCED ) ) )
        continue;

      // items can be enabled and disabled based on cvars
      if( item->cvarFlags & ( CVAR_ENABLE | CVAR_DISABLE ) &&
          !Item_EnableShowViaCvar( item, CVAR_ENABLE ) )
        continue;

      if( item->cvarFlags & ( CVAR_SHOW | CVAR_HIDE ) &&
          !Item_EnableShowViaCvar( item, CVAR_SHOW ) )
        continue;

      cast = Item_ComboBox_MaybeCastToListBox( item );
      result = Item_HandleMouseMove( item, x, y, pass, focusSet );
      Item_ComboBox_MaybeUnCastFromListBox( item, cast );

      if( !result && item->window.flags & WINDOW_MOUSEOVER )
      {
        Item_MouseLeave( item );
        Item_SetMouseOver( item, qfalse );
      }
    }
  }

}

void Menu_Update( menuDef_t *menu )
{
  int i;

  if( menu == NULL )
    return;

  for( i = 0; i < menu->itemCount; i++ )
    Item_Update( menu->items[ i ] );
}

void Menu_Paint( menuDef_t *menu, qboolean forcePaint )
{
  int i;

  if( menu == NULL )
    return;

  if( !( menu->window.flags & WINDOW_VISIBLE ) && !forcePaint )
    return;

  if( menu->window.ownerDrawFlags && DC->ownerDrawVisible && !DC->ownerDrawVisible( menu->window.ownerDrawFlags ) )
    return;

  if( forcePaint )
    menu->window.flags |= WINDOW_FORCED;

  // draw the background if necessary
  if( menu->fullScreen )
  {
    // implies a background shader
    // FIXME: make sure we have a default shader if fullscreen is set with no background
    DC->drawHandlePic( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, menu->window.background );
  }

  // paint the background and or border
  Window_Paint( &menu->window, menu->fadeAmount, menu->fadeClamp, menu->fadeCycle );

  Border_Paint( &menu->window );

  for( i = 0; i < menu->itemCount; i++ )
    Item_Paint( menu->items[i] );

  if( DC->getCVarValue( "ui_developer" ) )
  {
    vec4_t color;
    color[0] = color[2] = color[3] = 1;
    color[1] = 0;
    DC->drawRect( menu->window.rect.x, menu->window.rect.y, menu->window.rect.w, menu->window.rect.h, 1, color );
  }
}

/*
===============
Keyword Hash
===============
*/

#define KEYWORDHASH_SIZE  512

typedef struct keywordHash_s
{
  char *keyword;
  qboolean ( *func )( itemDef_t *item, int handle );

  int param;

  struct keywordHash_s *next;
}

keywordHash_t;

int KeywordHash_Key( char *keyword )
{
  int register hash, i;

  hash = 0;

  for( i = 0; keyword[i] != '\0'; i++ )
  {
    if( keyword[i] >= 'A' && keyword[i] <= 'Z' )
      hash += ( keyword[i] + ( 'a' - 'A' ) ) * ( 119 + i );
    else
      hash += keyword[i] * ( 119 + i );
  }

  hash = ( hash ^ ( hash >> 10 ) ^ ( hash >> 20 ) ) & ( KEYWORDHASH_SIZE - 1 );
  return hash;
}

void KeywordHash_Add( keywordHash_t *table[], keywordHash_t *key )
{
  int hash;

  hash = KeywordHash_Key( key->keyword );
  /*
     if(table[hash])     int collision = qtrue;
     */
  key->next = table[hash];
  table[hash] = key;
}

keywordHash_t *KeywordHash_Find( keywordHash_t *table[], char *keyword )
{
  keywordHash_t *key;
  int hash;

  hash = KeywordHash_Key( keyword );

  for( key = table[hash]; key; key = key->next )
  {
    if( !Q_stricmp( key->keyword, keyword ) )
      return key;
  }

  return NULL;
}

/*
===============
Item_DataType

Give a numeric representation of which typeData union element this item uses
===============
*/
itemDataType_t Item_DataType( itemDef_t *item )
{
  switch( item->type )
  {
    default:
    case ITEM_TYPE_NONE:
      return TYPE_NONE;

    case ITEM_TYPE_LISTBOX:
    case ITEM_TYPE_COMBOBOX:
      return TYPE_LIST;

    case ITEM_TYPE_CYCLE:
      return TYPE_COMBO;

    case ITEM_TYPE_EDITFIELD:
    case ITEM_TYPE_NUMERICFIELD:
    case ITEM_TYPE_SAYFIELD:
    case ITEM_TYPE_YESNO:
    case ITEM_TYPE_BIND:
    case ITEM_TYPE_SLIDER:
    case ITEM_TYPE_TEXT:
      return TYPE_EDIT;

    case ITEM_TYPE_MULTI:
      return TYPE_MULTI;

    case ITEM_TYPE_MODEL:
      return TYPE_MODEL;
  }
}

/*
===============
Item_IsEditField
===============
*/
static ID_INLINE qboolean Item_IsEditField( itemDef_t *item )
{
  switch( item->type )
  {
    case ITEM_TYPE_EDITFIELD:
    case ITEM_TYPE_NUMERICFIELD:
    case ITEM_TYPE_SAYFIELD:
      return qtrue;

    default:
      return qfalse;
  }
}

/*
===============
Item_IsListBox
===============
*/
static ID_INLINE qboolean Item_IsListBox( itemDef_t *item )
{
  switch( item->type )
  {
    case ITEM_TYPE_LISTBOX:
    case ITEM_TYPE_COMBOBOX:
      return qtrue;

    default:
      return qfalse;
  }
}

/*
===============
Item Keyword Parse functions
===============
*/

// name <string>
qboolean ItemParse_name( itemDef_t *item, int handle )
{
  if( !PC_String_Parse( handle, &item->window.name ) )
    return qfalse;

  return qtrue;
}

// name <string>
qboolean ItemParse_focusSound( itemDef_t *item, int handle )
{
  const char *temp;

  if( !PC_String_Parse( handle, &temp ) )
    return qfalse;

  item->focusSound = DC->registerSound( temp, qfalse );
  return qtrue;
}


// text <string>
qboolean ItemParse_text( itemDef_t *item, int handle )
{
  if( !PC_String_Parse( handle, &item->text ) )
    return qfalse;

  return qtrue;
}

// group <string>
qboolean ItemParse_group( itemDef_t *item, int handle )
{
  if( !PC_String_Parse( handle, &item->window.group ) )
    return qfalse;

  return qtrue;
}

// asset_model <string>
qboolean ItemParse_asset_model( itemDef_t *item, int handle )
{
  const char *temp;

  if( !PC_String_Parse( handle, &temp ) )
    return qfalse;

  item->asset = DC->registerModel( temp );
  item->typeData.model->angle = rand() % 360;
  return qtrue;
}

// asset_shader <string>
qboolean ItemParse_asset_shader( itemDef_t *item, int handle )
{
  const char *temp;

  if( !PC_String_Parse( handle, &temp ) )
    return qfalse;

  item->asset = DC->registerShaderNoMip( temp );
  return qtrue;
}

// model_origin <number> <number> <number>
qboolean ItemParse_model_origin( itemDef_t *item, int handle )
{
  return ( PC_Float_Parse( handle, &item->typeData.model->origin[0] ) &&
           PC_Float_Parse( handle, &item->typeData.model->origin[1] ) &&
           PC_Float_Parse( handle, &item->typeData.model->origin[2] ) );
}

// model_fovx <number>
qboolean ItemParse_model_fovx( itemDef_t *item, int handle )
{
  return PC_Float_Parse( handle, &item->typeData.model->fov_x );
}

// model_fovy <number>
qboolean ItemParse_model_fovy( itemDef_t *item, int handle )
{
  return PC_Float_Parse( handle, &item->typeData.model->fov_y );
}

// model_rotation <integer>
qboolean ItemParse_model_rotation( itemDef_t *item, int handle )
{
  return PC_Int_Parse( handle, &item->typeData.model->rotationSpeed );
}

// model_angle <integer>
qboolean ItemParse_model_angle( itemDef_t *item, int handle )
{
  return PC_Int_Parse( handle, &item->typeData.model->angle );
}

// rect <rectangle>
qboolean ItemParse_rect( itemDef_t *item, int handle )
{
  if( !PC_Rect_Parse( handle, &item->window.rectClient ) )
    return qfalse;

  return qtrue;
}

// aspectBias <bias>
qboolean ItemParse_aspectBias( itemDef_t *item, int handle )
{
  if( !PC_Int_Parse( handle, &item->window.aspectBias ) )
    return qfalse;

  return qtrue;
}

// style <integer>
qboolean ItemParse_style( itemDef_t *item, int handle )
{
  if( !PC_Int_Parse( handle, &item->window.style ) )
    return qfalse;

  return qtrue;
}

// decoration
qboolean ItemParse_decoration( itemDef_t *item, int handle )
{
  item->window.flags |= WINDOW_DECORATION;
  return qtrue;
}

// notselectable
qboolean ItemParse_notselectable( itemDef_t *item, int handle )
{
  item->typeData.list->notselectable = qtrue;
  return qtrue;
}

// noscrollbar
qboolean ItemParse_noscrollbar( itemDef_t *item, int handle )
{
  item->typeData.list->noscrollbar = qtrue;
  return qtrue;
}

// resetonfeederchange
qboolean ItemParse_resetonfeederchange( itemDef_t *item, int handle )
{
  item->typeData.list->resetonfeederchange = qtrue;
  return qtrue;
}

// auto wrapped
qboolean ItemParse_wrapped( itemDef_t *item, int handle )
{
  item->window.flags |= WINDOW_WRAPPED;
  return qtrue;
}


// type <integer>
qboolean ItemParse_type( itemDef_t *item, int handle )
{
  if( item->type != ITEM_TYPE_NONE )
  {
    PC_SourceError( handle, "item already has a type" );
    return qfalse;
  }

  if( !PC_Int_Parse( handle, &item->type ) )
    return qfalse;

  if( item->type == ITEM_TYPE_NONE )
  {
    PC_SourceError( handle, "type must not be none" );
    return qfalse;
  }

  // allocate the relevant type data
  switch( item->type )
  {
    case ITEM_TYPE_LISTBOX:
    case ITEM_TYPE_COMBOBOX:
      item->typeData.list = UI_Alloc( sizeof( listBoxDef_t ) );
      memset( item->typeData.list, 0, sizeof( listBoxDef_t ) );
      break;

    case ITEM_TYPE_CYCLE:
      item->typeData.cycle = UI_Alloc( sizeof( cycleDef_t ) );
      memset( item->typeData.cycle, 0, sizeof( cycleDef_t ) );
      break;

    case ITEM_TYPE_EDITFIELD:
    case ITEM_TYPE_SAYFIELD:
    case ITEM_TYPE_NUMERICFIELD:
    case ITEM_TYPE_YESNO:
    case ITEM_TYPE_BIND:
    case ITEM_TYPE_SLIDER:
    case ITEM_TYPE_TEXT:
      item->typeData.edit = UI_Alloc( sizeof( editFieldDef_t ) );
      memset( item->typeData.edit, 0, sizeof( editFieldDef_t ) );

      if( item->type == ITEM_TYPE_EDITFIELD || item->type == ITEM_TYPE_SAYFIELD )
        item->typeData.edit->maxPaintChars = MAX_EDITFIELD;
      break;

    case ITEM_TYPE_MULTI:
      item->typeData.multi = UI_Alloc( sizeof( multiDef_t ) );
      memset( item->typeData.multi, 0, sizeof( multiDef_t ) );
      break;

    case ITEM_TYPE_MODEL:
      item->typeData.model = UI_Alloc( sizeof( modelDef_t ) );
      memset( item->typeData.model, 0, sizeof( modelDef_t ) );
      break;

    default:
      break;
  }

  return qtrue;
}

// elementwidth, used for listbox image elements
qboolean ItemParse_elementwidth( itemDef_t *item, int handle )
{
  return PC_Float_Parse( handle, &item->typeData.list->elementWidth );
}

// elementheight, used for listbox image elements
qboolean ItemParse_elementheight( itemDef_t *item, int handle )
{
  return PC_Float_Parse( handle, &item->typeData.list->elementHeight );
}

// dropitems, number of items to drop from a combobox
qboolean ItemParse_dropitems( itemDef_t *item, int handle )
{
  return PC_Int_Parse( handle, &item->typeData.list->dropItems );
}

// feeder <int>
qboolean ItemParse_feeder( itemDef_t *item, int handle )
{
  if( !PC_Int_Parse( handle, &item->feederID ) )
    return qfalse;

  return qtrue;
}

// elementtype, used to specify what type of elements a listbox contains
// uses textstyle for storage
qboolean ItemParse_elementtype( itemDef_t *item, int handle )
{
  return PC_Int_Parse( handle, &item->typeData.list->elementStyle );
}

// columns sets a number of columns and an x pos and width per..
qboolean ItemParse_columns( itemDef_t *item, int handle )
{
  int i;

  if( !PC_Int_Parse( handle, &item->typeData.list->numColumns ) )
    return qfalse;

  if( item->typeData.list->numColumns > MAX_LB_COLUMNS )
  {
    PC_SourceError( handle, "exceeded maximum allowed columns (%d)",
                    MAX_LB_COLUMNS );
    return qfalse;
  }

  for( i = 0; i < item->typeData.list->numColumns; i++ )
  {
    int pos, width, align;

    if( !PC_Int_Parse( handle, &pos ) ||
        !PC_Int_Parse( handle, &width ) ||
        !PC_Int_Parse( handle, &align ) )
      return qfalse;

    item->typeData.list->columnInfo[i].pos = pos;
    item->typeData.list->columnInfo[i].width = width;
    item->typeData.list->columnInfo[i].align = align;
  }

  return qtrue;
}

qboolean ItemParse_border( itemDef_t *item, int handle )
{
  if( !PC_Int_Parse( handle, &item->window.border ) )
    return qfalse;

  return qtrue;
}

qboolean ItemParse_bordersize( itemDef_t *item, int handle )
{
  if( !PC_Float_Parse( handle, &item->window.borderSize ) )
    return qfalse;

  return qtrue;
}

// FIXME: why does this require a parameter? visible MENU_FALSE does nothing
qboolean ItemParse_visible( itemDef_t *item, int handle )
{
  int i;

  if( !PC_Int_Parse( handle, &i ) )
    return qfalse;

  if( i )
    item->window.flags |= WINDOW_VISIBLE;

  return qtrue;
}

// ownerdraw <number>, implies ITEM_TYPE_OWNERDRAW
qboolean ItemParse_ownerdraw( itemDef_t *item, int handle )
{
  if( !PC_Int_Parse( handle, &item->window.ownerDraw ) )
    return qfalse;

  if( item->type != ITEM_TYPE_NONE && item->type != ITEM_TYPE_OWNERDRAW )
  {
    PC_SourceError( handle, "ownerdraws cannot have an item type" );
    return qfalse;
  }

  item->type = ITEM_TYPE_OWNERDRAW;

  return qtrue;
}

qboolean ItemParse_align( itemDef_t *item, int handle )
{
  if( !PC_Int_Parse( handle, &item->alignment ) )
    return qfalse;

  return qtrue;
}

qboolean ItemParse_textalign( itemDef_t *item, int handle )
{
  if( !PC_Int_Parse( handle, &item->textalignment ) )
    return qfalse;

  return qtrue;
}

qboolean ItemParse_textvalign( itemDef_t *item, int handle )
{
  if( !PC_Int_Parse( handle, &item->textvalignment ) )
    return qfalse;

  return qtrue;
}

qboolean ItemParse_textalignx( itemDef_t *item, int handle )
{
  if( !PC_Float_Parse( handle, &item->textalignx ) )
    return qfalse;

  return qtrue;
}

qboolean ItemParse_textaligny( itemDef_t *item, int handle )
{
  if( !PC_Float_Parse( handle, &item->textaligny ) )
    return qfalse;

  return qtrue;
}

qboolean ItemParse_textscale( itemDef_t *item, int handle )
{
  if( !PC_Float_Parse( handle, &item->textscale ) )
    return qfalse;

  return qtrue;
}

qboolean ItemParse_textstyle( itemDef_t *item, int handle )
{
  if( !PC_Int_Parse( handle, &item->textStyle ) )
    return qfalse;

  return qtrue;
}

qboolean ItemParse_backcolor( itemDef_t *item, int handle )
{
  int i;
  float f;

  for( i = 0; i < 4; i++ )
  {
    if( !PC_Float_Parse( handle, &f ) )
      return qfalse;

    item->window.backColor[i]  = f;
  }

  return qtrue;
}

qboolean ItemParse_forecolor( itemDef_t *item, int handle )
{
  int i;
  float f;

  for( i = 0; i < 4; i++ )
  {
    if( !PC_Float_Parse( handle, &f ) )
      return qfalse;

    item->window.foreColor[i]  = f;
    item->window.flags |= WINDOW_FORECOLORSET;
  }

  return qtrue;
}

qboolean ItemParse_bordercolor( itemDef_t *item, int handle )
{
  int i;
  float f;

  for( i = 0; i < 4; i++ )
  {
    if( !PC_Float_Parse( handle, &f ) )
      return qfalse;

    item->window.borderColor[i]  = f;
  }

  return qtrue;
}

qboolean ItemParse_outlinecolor( itemDef_t *item, int handle )
{
  if( !PC_Color_Parse( handle, &item->window.outlineColor ) )
    return qfalse;

  return qtrue;
}

qboolean ItemParse_background( itemDef_t *item, int handle )
{
  const char *temp;

  if( !PC_String_Parse( handle, &temp ) )
    return qfalse;

  item->window.background = DC->registerShaderNoMip( temp );
  return qtrue;
}

qboolean ItemParse_cinematic( itemDef_t *item, int handle )
{
  if( !PC_String_Parse( handle, &item->window.cinematicName ) )
    return qfalse;

  return qtrue;
}

qboolean ItemParse_doubleClick( itemDef_t *item, int handle )
{
  return ( item->typeData.list &&
           PC_Script_Parse( handle, &item->typeData.list->doubleClick ) );
}

qboolean ItemParse_onFocus( itemDef_t *item, int handle )
{
  if( !PC_Script_Parse( handle, &item->onFocus ) )
    return qfalse;

  return qtrue;
}

qboolean ItemParse_leaveFocus( itemDef_t *item, int handle )
{
  if( !PC_Script_Parse( handle, &item->leaveFocus ) )
    return qfalse;

  return qtrue;
}

qboolean ItemParse_mouseEnter( itemDef_t *item, int handle )
{
  if( !PC_Script_Parse( handle, &item->mouseEnter ) )
    return qfalse;

  return qtrue;
}

qboolean ItemParse_mouseExit( itemDef_t *item, int handle )
{
  if( !PC_Script_Parse( handle, &item->mouseExit ) )
    return qfalse;

  return qtrue;
}

qboolean ItemParse_mouseEnterText( itemDef_t *item, int handle )
{
  if( !PC_Script_Parse( handle, &item->mouseEnterText ) )
    return qfalse;

  return qtrue;
}

qboolean ItemParse_mouseExitText( itemDef_t *item, int handle )
{
  if( !PC_Script_Parse( handle, &item->mouseExitText ) )
    return qfalse;

  return qtrue;
}

qboolean ItemParse_onTextEntry( itemDef_t *item, int handle )
{
  if( !PC_Script_Parse( handle, &item->onTextEntry ) )
    return qfalse;

  return qtrue;
}


qboolean ItemParse_onCharEntry( itemDef_t *item, int handle )
{
  if( !PC_Script_Parse( handle, &item->onCharEntry ) )
    return qfalse;

  return qtrue;
}

qboolean ItemParse_action( itemDef_t *item, int handle )
{
  if( !PC_Script_Parse( handle, &item->action ) )
    return qfalse;

  return qtrue;
}

qboolean ItemParse_cvarTest( itemDef_t *item, int handle )
{
  if( !PC_String_Parse( handle, &item->cvarTest ) )
    return qfalse;

  return qtrue;
}

qboolean ItemParse_cvar( itemDef_t *item, int handle )
{
  if( !PC_String_Parse( handle, &item->cvar ) )
    return qfalse;

  if( Item_DataType( item ) == TYPE_EDIT )
  {
    item->typeData.edit->minVal = -1;
    item->typeData.edit->maxVal = -1;
    item->typeData.edit->defVal = -1;
  }

  return qtrue;
}

qboolean ItemParse_maxChars( itemDef_t *item, int handle )
{
  return PC_Int_Parse( handle, &item->typeData.edit->maxChars );
}

qboolean ItemParse_maxPaintChars( itemDef_t *item, int handle )
{
  return PC_Int_Parse( handle, &item->typeData.edit->maxPaintChars );
}

qboolean ItemParse_maxFieldWidth( itemDef_t *item, int handle )
{
  if( !PC_Int_Parse( handle, &item->typeData.edit->maxFieldWidth ) )
    return qfalse;

  if( item->typeData.edit->maxFieldWidth < MIN_FIELD_WIDTH )
  {
    PC_SourceError( handle, "max field width must be at least %d",
                    MIN_FIELD_WIDTH );
    return qfalse;
  }

  return qtrue;
}



qboolean ItemParse_cvarFloat( itemDef_t *item, int handle )
{
  return ( PC_String_Parse( handle, &item->cvar ) &&
           PC_Float_Parse( handle, &item->typeData.edit->defVal ) &&
           PC_Float_Parse( handle, &item->typeData.edit->minVal ) &&
           PC_Float_Parse( handle, &item->typeData.edit->maxVal ) );
}

qboolean ItemParse_cvarStrList( itemDef_t *item, int handle )
{
  pc_token_t token;
  multiDef_t *multiPtr;
  int pass;

  multiPtr = item->typeData.multi;
  multiPtr->count = 0;
  multiPtr->strDef = qtrue;

  if( !trap_Parse_ReadToken( handle, &token ) )
    return qfalse;

  if( *token.string != '{' )
    return qfalse;

  pass = 0;

  while( 1 )
  {
    if( !trap_Parse_ReadToken( handle, &token ) )
    {
      PC_SourceError( handle, "end of file inside menu item\n" );
      return qfalse;
    }

    if( *token.string == '}' )
      return qtrue;

    if( *token.string == ',' || *token.string == ';' )
      continue;

    if( pass == 0 )
    {
      multiPtr->cvarList[multiPtr->count] = String_Alloc( token.string );
      pass = 1;
    }
    else
    {
      multiPtr->cvarStr[multiPtr->count] = String_Alloc( token.string );
      pass = 0;
      multiPtr->count++;

      if( multiPtr->count >= MAX_MULTI_CVARS )
      {
        PC_SourceError( handle, "cvar string list may not exceed %d cvars",
                        MAX_MULTI_CVARS );
        return qfalse;
      }
    }

  }

  return qfalse;
}

qboolean ItemParse_cvarFloatList( itemDef_t *item, int handle )
{
  pc_token_t token;
  multiDef_t *multiPtr;

  multiPtr = item->typeData.multi;
  multiPtr->count = 0;
  multiPtr->strDef = qfalse;

  if( !trap_Parse_ReadToken( handle, &token ) )
    return qfalse;

  if( *token.string != '{' )
    return qfalse;

  while( 1 )
  {
    if( !trap_Parse_ReadToken( handle, &token ) )
    {
      PC_SourceError( handle, "end of file inside menu item\n" );
      return qfalse;
    }

    if( *token.string == '}' )
      return qtrue;

    if( *token.string == ',' || *token.string == ';' )
      continue;

    multiPtr->cvarList[multiPtr->count] = String_Alloc( token.string );

    if( !PC_Float_Parse( handle, &multiPtr->cvarValue[multiPtr->count] ) )
      return qfalse;

    multiPtr->count++;

    if( multiPtr->count >= MAX_MULTI_CVARS )
    {
      PC_SourceError( handle, "cvar string list may not exceed %d cvars",
                      MAX_MULTI_CVARS );
      return qfalse;
    }
  }

  return qfalse;
}



qboolean ItemParse_addColorRange( itemDef_t *item, int handle )
{
  colorRangeDef_t color;

  if( PC_Float_Parse( handle, &color.low ) &&
      PC_Float_Parse( handle, &color.high ) &&
      PC_Color_Parse( handle, &color.color ) )
  {
    if( item->numColors < MAX_COLOR_RANGES )
    {
      memcpy( &item->colorRanges[item->numColors], &color, sizeof( color ) );
      item->numColors++;
    }
    else
    {
      PC_SourceError( handle, "may not exceed %d color ranges",
                      MAX_COLOR_RANGES );
      return qfalse;
    }

    return qtrue;
  }

  return qfalse;
}

qboolean ItemParse_ownerdrawFlag( itemDef_t *item, int handle )
{
  int i;

  if( !PC_Int_Parse( handle, &i ) )
    return qfalse;

  item->window.ownerDrawFlags |= i;
  return qtrue;
}

qboolean ItemParse_enableCvar( itemDef_t *item, int handle )
{
  if( PC_Script_Parse( handle, &item->enableCvar ) )
  {
    item->cvarFlags = CVAR_ENABLE;
    return qtrue;
  }

  return qfalse;
}

qboolean ItemParse_disableCvar( itemDef_t *item, int handle )
{
  if( PC_Script_Parse( handle, &item->enableCvar ) )
  {
    item->cvarFlags = CVAR_DISABLE;
    return qtrue;
  }

  return qfalse;
}

qboolean ItemParse_showCvar( itemDef_t *item, int handle )
{
  if( PC_Script_Parse( handle, &item->enableCvar ) )
  {
    item->cvarFlags = CVAR_SHOW;
    return qtrue;
  }

  return qfalse;
}

qboolean ItemParse_hideCvar( itemDef_t *item, int handle )
{
  if( PC_Script_Parse( handle, &item->enableCvar ) )
  {
    item->cvarFlags = CVAR_HIDE;
    return qtrue;
  }

  return qfalse;
}


keywordHash_t itemParseKeywords[] = {
  {"name", ItemParse_name, TYPE_ANY},
  {"type", ItemParse_type, TYPE_ANY},
  {"text", ItemParse_text, TYPE_ANY},
  {"group", ItemParse_group, TYPE_ANY},
  {"asset_model", ItemParse_asset_model, TYPE_MODEL},
  {"asset_shader", ItemParse_asset_shader, TYPE_ANY}, // ?
  {"model_origin", ItemParse_model_origin, TYPE_MODEL},
  {"model_fovx", ItemParse_model_fovx, TYPE_MODEL},
  {"model_fovy", ItemParse_model_fovy, TYPE_MODEL},
  {"model_rotation", ItemParse_model_rotation, TYPE_MODEL},
  {"model_angle", ItemParse_model_angle, TYPE_MODEL},
  {"rect", ItemParse_rect, TYPE_ANY},
  {"aspectBias", ItemParse_aspectBias, TYPE_ANY},
  {"style", ItemParse_style, TYPE_ANY},
  {"decoration", ItemParse_decoration, TYPE_ANY},
  {"notselectable", ItemParse_notselectable, TYPE_LIST},
  {"noscrollbar", ItemParse_noscrollbar, TYPE_LIST},
  {"resetonfeederchange", ItemParse_resetonfeederchange, TYPE_LIST},
  {"wrapped", ItemParse_wrapped, TYPE_ANY},
  {"elementwidth", ItemParse_elementwidth, TYPE_LIST},
  {"elementheight", ItemParse_elementheight, TYPE_LIST},
  {"dropitems", ItemParse_dropitems, TYPE_LIST},
  {"feeder", ItemParse_feeder, TYPE_ANY},
  {"elementtype", ItemParse_elementtype, TYPE_LIST},
  {"columns", ItemParse_columns, TYPE_LIST},
  {"border", ItemParse_border, TYPE_ANY},
  {"bordersize", ItemParse_bordersize, TYPE_ANY},
  {"visible", ItemParse_visible, TYPE_ANY},
  {"ownerdraw", ItemParse_ownerdraw, TYPE_ANY},
  {"align", ItemParse_align, TYPE_ANY},
  {"textalign", ItemParse_textalign, TYPE_ANY},
  {"textvalign", ItemParse_textvalign, TYPE_ANY},
  {"textalignx", ItemParse_textalignx, TYPE_ANY},
  {"textaligny", ItemParse_textaligny, TYPE_ANY},
  {"textscale", ItemParse_textscale, TYPE_ANY},
  {"textstyle", ItemParse_textstyle, TYPE_ANY},
  {"backcolor", ItemParse_backcolor, TYPE_ANY},
  {"forecolor", ItemParse_forecolor, TYPE_ANY},
  {"bordercolor", ItemParse_bordercolor, TYPE_ANY},
  {"outlinecolor", ItemParse_outlinecolor, TYPE_ANY},
  {"background", ItemParse_background, TYPE_ANY},
  {"onFocus", ItemParse_onFocus, TYPE_ANY},
  {"leaveFocus", ItemParse_leaveFocus, TYPE_ANY},
  {"mouseEnter", ItemParse_mouseEnter, TYPE_ANY},
  {"mouseExit", ItemParse_mouseExit, TYPE_ANY},
  {"mouseEnterText", ItemParse_mouseEnterText, TYPE_ANY},
  {"mouseExitText", ItemParse_mouseExitText, TYPE_ANY},
  {"onTextEntry", ItemParse_onTextEntry, TYPE_ANY},
  {"onCharEntry", ItemParse_onCharEntry, TYPE_ANY},
  {"action", ItemParse_action, TYPE_ANY},
  {"cvar", ItemParse_cvar, TYPE_ANY},
  {"maxChars", ItemParse_maxChars, TYPE_EDIT},
  {"maxPaintChars", ItemParse_maxPaintChars, TYPE_EDIT},
  {"maxFieldWidth", ItemParse_maxFieldWidth, TYPE_EDIT},
  {"focusSound", ItemParse_focusSound, TYPE_ANY},
  {"cvarFloat", ItemParse_cvarFloat, TYPE_EDIT},
  {"cvarStrList", ItemParse_cvarStrList, TYPE_MULTI},
  {"cvarFloatList", ItemParse_cvarFloatList, TYPE_MULTI},
  {"addColorRange", ItemParse_addColorRange, TYPE_ANY},
  {"ownerdrawFlag", ItemParse_ownerdrawFlag, TYPE_ANY}, // hm.
  {"enableCvar", ItemParse_enableCvar, TYPE_ANY},
  {"cvarTest", ItemParse_cvarTest, TYPE_ANY},
  {"disableCvar", ItemParse_disableCvar, TYPE_ANY},
  {"showCvar", ItemParse_showCvar, TYPE_ANY},
  {"hideCvar", ItemParse_hideCvar, TYPE_ANY},
  {"cinematic", ItemParse_cinematic, TYPE_ANY},
  {"doubleclick", ItemParse_doubleClick, TYPE_LIST},
  {NULL, voidFunction2}
};

keywordHash_t *itemParseKeywordHash[KEYWORDHASH_SIZE];

/*
===============
Item_SetupKeywordHash
===============
*/
void Item_SetupKeywordHash( void )
{
  int i;

  memset( itemParseKeywordHash, 0, sizeof( itemParseKeywordHash ) );

  for( i = 0; itemParseKeywords[ i ].keyword; i++ )
    KeywordHash_Add( itemParseKeywordHash, &itemParseKeywords[ i ] );
}

/*
===============
Item_Parse
===============
*/
qboolean Item_Parse( int handle, itemDef_t *item )
{
  pc_token_t token;
  keywordHash_t *key;


  if( !trap_Parse_ReadToken( handle, &token ) )
    return qfalse;

  if( *token.string != '{' )
    return qfalse;

  while( 1 )
  {
    if( !trap_Parse_ReadToken( handle, &token ) )
    {
      PC_SourceError( handle, "end of file inside menu item\n" );
      return qfalse;
    }

    if( *token.string == '}' )
      return qtrue;

    key = KeywordHash_Find( itemParseKeywordHash, token.string );

    if( !key )
    {
      PC_SourceError( handle, "unknown menu item keyword %s", token.string );
      continue;
    }

    // do type-checks
    if( key->param != TYPE_ANY )
    {
      itemDataType_t test = Item_DataType( item );

      if( test != key->param )
      {
        if( test == TYPE_NONE )
          PC_SourceError( handle, "menu item keyword %s requires "
                          "type specification", token.string );
        else
          PC_SourceError( handle, "menu item keyword %s is incompatible with "
                          "specified item type", token.string );
        continue;
      }
    }

    if( !key->func( item, handle ) )
    {
      PC_SourceError( handle, "couldn't parse menu item keyword %s", token.string );
      return qfalse;
    }
  }

  return qfalse;
}


// Item_InitControls
// init's special control types
void Item_InitControls( itemDef_t *item )
{
  if( item == NULL )
    return;

  if( Item_IsListBox( item ) )
  {
    item->cursorPos = 0;

    if( item->typeData.list )
    {
      item->typeData.list->cursorPos = 0;
      Item_ListBox_SetStartPos( item, 0 );
      item->typeData.list->cursorPos = 0;
    }
  }
}

/*
===============
Menu Keyword Parse functions
===============
*/

qboolean MenuParse_font( itemDef_t *item, int handle )
{
  menuDef_t *menu = ( menuDef_t* )item;

  if( !PC_String_Parse( handle, &menu->font ) )
    return qfalse;

  if( !DC->Assets.fontRegistered )
  {
    DC->registerFont( menu->font, 48, &DC->Assets.textFont );
    DC->Assets.fontRegistered = qtrue;
  }

  return qtrue;
}

qboolean MenuParse_name( itemDef_t *item, int handle )
{
  menuDef_t *menu = ( menuDef_t* )item;

  if( !PC_String_Parse( handle, &menu->window.name ) )
    return qfalse;

  return qtrue;
}

qboolean MenuParse_fullscreen( itemDef_t *item, int handle )
{
  menuDef_t *menu = ( menuDef_t* )item;

  if( !PC_Int_Parse( handle, ( int* ) & menu->fullScreen ) )
    return qfalse;

  return qtrue;
}

qboolean MenuParse_rect( itemDef_t *item, int handle )
{
  menuDef_t *menu = ( menuDef_t* )item;

  if( !PC_Rect_Parse( handle, &menu->window.rect ) )
    return qfalse;

  return qtrue;
}

qboolean MenuParse_aspectBias( itemDef_t *item, int handle )
{
  menuDef_t *menu = ( menuDef_t* )item;

  if( !PC_Int_Parse( handle, &menu->window.aspectBias ) )
    return qfalse;

  return qtrue;
}

qboolean MenuParse_style( itemDef_t *item, int handle )
{
  menuDef_t *menu = ( menuDef_t* )item;

  if( !PC_Int_Parse( handle, &menu->window.style ) )
    return qfalse;

  return qtrue;
}

qboolean MenuParse_visible( itemDef_t *item, int handle )
{
  int i;
  menuDef_t *menu = ( menuDef_t* )item;

  if( !PC_Int_Parse( handle, &i ) )
    return qfalse;

  if( i )
    menu->window.flags |= WINDOW_VISIBLE;

  return qtrue;
}

qboolean MenuParse_onOpen( itemDef_t *item, int handle )
{
  menuDef_t *menu = ( menuDef_t* )item;

  if( !PC_Script_Parse( handle, &menu->onOpen ) )
    return qfalse;

  return qtrue;
}

qboolean MenuParse_onClose( itemDef_t *item, int handle )
{
  menuDef_t *menu = ( menuDef_t* )item;

  if( !PC_Script_Parse( handle, &menu->onClose ) )
    return qfalse;

  return qtrue;
}

qboolean MenuParse_onESC( itemDef_t *item, int handle )
{
  menuDef_t *menu = ( menuDef_t* )item;

  if( !PC_Script_Parse( handle, &menu->onESC ) )
    return qfalse;

  return qtrue;
}



qboolean MenuParse_border( itemDef_t *item, int handle )
{
  menuDef_t *menu = ( menuDef_t* )item;

  if( !PC_Int_Parse( handle, &menu->window.border ) )
    return qfalse;

  return qtrue;
}

qboolean MenuParse_borderSize( itemDef_t *item, int handle )
{
  menuDef_t *menu = ( menuDef_t* )item;

  if( !PC_Float_Parse( handle, &menu->window.borderSize ) )
    return qfalse;

  return qtrue;
}

qboolean MenuParse_backcolor( itemDef_t *item, int handle )
{
  int i;
  float f;
  menuDef_t *menu = ( menuDef_t* )item;

  for( i = 0; i < 4; i++ )
  {
    if( !PC_Float_Parse( handle, &f ) )
      return qfalse;

    menu->window.backColor[i]  = f;
  }

  return qtrue;
}

qboolean MenuParse_forecolor( itemDef_t *item, int handle )
{
  int i;
  float f;
  menuDef_t *menu = ( menuDef_t* )item;

  for( i = 0; i < 4; i++ )
  {
    if( !PC_Float_Parse( handle, &f ) )
      return qfalse;

    menu->window.foreColor[i]  = f;
    menu->window.flags |= WINDOW_FORECOLORSET;
  }

  return qtrue;
}

qboolean MenuParse_bordercolor( itemDef_t *item, int handle )
{
  int i;
  float f;
  menuDef_t *menu = ( menuDef_t* )item;

  for( i = 0; i < 4; i++ )
  {
    if( !PC_Float_Parse( handle, &f ) )
      return qfalse;

    menu->window.borderColor[i]  = f;
  }

  return qtrue;
}

qboolean MenuParse_focuscolor( itemDef_t *item, int handle )
{
  int i;
  float f;
  menuDef_t *menu = ( menuDef_t* )item;

  for( i = 0; i < 4; i++ )
  {
    if( !PC_Float_Parse( handle, &f ) )
      return qfalse;

    menu->focusColor[i]  = f;
  }

  return qtrue;
}

qboolean MenuParse_disablecolor( itemDef_t *item, int handle )
{
  int i;
  float f;
  menuDef_t *menu = ( menuDef_t* )item;

  for( i = 0; i < 4; i++ )
  {
    if( !PC_Float_Parse( handle, &f ) )
      return qfalse;

    menu->disableColor[i]  = f;
  }

  return qtrue;
}


qboolean MenuParse_outlinecolor( itemDef_t *item, int handle )
{
  menuDef_t *menu = ( menuDef_t* )item;

  if( !PC_Color_Parse( handle, &menu->window.outlineColor ) )
    return qfalse;

  return qtrue;
}

qboolean MenuParse_background( itemDef_t *item, int handle )
{
  const char *buff;
  menuDef_t *menu = ( menuDef_t* )item;

  if( !PC_String_Parse( handle, &buff ) )
    return qfalse;

  menu->window.background = DC->registerShaderNoMip( buff );
  return qtrue;
}

qboolean MenuParse_cinematic( itemDef_t *item, int handle )
{
  menuDef_t *menu = ( menuDef_t* )item;

  if( !PC_String_Parse( handle, &menu->window.cinematicName ) )
    return qfalse;

  return qtrue;
}

qboolean MenuParse_ownerdrawFlag( itemDef_t *item, int handle )
{
  int i;
  menuDef_t *menu = ( menuDef_t* )item;

  if( !PC_Int_Parse( handle, &i ) )
    return qfalse;

  menu->window.ownerDrawFlags |= i;
  return qtrue;
}

qboolean MenuParse_ownerdraw( itemDef_t *item, int handle )
{
  menuDef_t *menu = ( menuDef_t* )item;

  if( !PC_Int_Parse( handle, &menu->window.ownerDraw ) )
    return qfalse;

  return qtrue;
}


// decoration
qboolean MenuParse_popup( itemDef_t *item, int handle )
{
  menuDef_t *menu = ( menuDef_t* )item;
  menu->window.flags |= WINDOW_POPUP;
  return qtrue;
}


qboolean MenuParse_outOfBounds( itemDef_t *item, int handle )
{
  menuDef_t *menu = ( menuDef_t* )item;

  menu->window.flags |= WINDOW_OOB_CLICK;
  return qtrue;
}

qboolean MenuParse_soundLoop( itemDef_t *item, int handle )
{
  menuDef_t *menu = ( menuDef_t* )item;

  if( !PC_String_Parse( handle, &menu->soundName ) )
    return qfalse;

  return qtrue;
}

qboolean MenuParse_fadeClamp( itemDef_t *item, int handle )
{
  menuDef_t *menu = ( menuDef_t* )item;

  if( !PC_Float_Parse( handle, &menu->fadeClamp ) )
    return qfalse;

  return qtrue;
}

qboolean MenuParse_fadeAmount( itemDef_t *item, int handle )
{
  menuDef_t *menu = ( menuDef_t* )item;

  if( !PC_Float_Parse( handle, &menu->fadeAmount ) )
    return qfalse;

  return qtrue;
}


qboolean MenuParse_fadeCycle( itemDef_t *item, int handle )
{
  menuDef_t *menu = ( menuDef_t* )item;

  if( !PC_Int_Parse( handle, &menu->fadeCycle ) )
    return qfalse;

  return qtrue;
}


qboolean MenuParse_itemDef( itemDef_t *item, int handle )
{
  menuDef_t *menu = ( menuDef_t* )item;

  if( menu->itemCount < MAX_MENUITEMS )
  {
    menu->items[menu->itemCount] = UI_Alloc( sizeof( itemDef_t ) );
    Item_Init( menu->items[menu->itemCount] );

    if( !Item_Parse( handle, menu->items[menu->itemCount] ) )
      return qfalse;

    Item_InitControls( menu->items[menu->itemCount] );
    menu->items[menu->itemCount++]->parent = menu;
  }
  else
  {
    PC_SourceError( handle, "itemDefs per menu may not exceed %d",
                    MAX_MENUITEMS );
    return qfalse;
  }

  return qtrue;
}

keywordHash_t menuParseKeywords[] = {
  {"font", MenuParse_font},
  {"name", MenuParse_name},
  {"fullscreen", MenuParse_fullscreen},
  {"rect", MenuParse_rect},
  {"aspectBias", MenuParse_aspectBias},
  {"style", MenuParse_style},
  {"visible", MenuParse_visible},
  {"onOpen", MenuParse_onOpen},
  {"onClose", MenuParse_onClose},
  {"onESC", MenuParse_onESC},
  {"border", MenuParse_border},
  {"borderSize", MenuParse_borderSize},
  {"backcolor", MenuParse_backcolor},
  {"forecolor", MenuParse_forecolor},
  {"bordercolor", MenuParse_bordercolor},
  {"focuscolor", MenuParse_focuscolor},
  {"disablecolor", MenuParse_disablecolor},
  {"outlinecolor", MenuParse_outlinecolor},
  {"background", MenuParse_background},
  {"ownerdraw", MenuParse_ownerdraw},
  {"ownerdrawFlag", MenuParse_ownerdrawFlag},
  {"outOfBoundsClick", MenuParse_outOfBounds},
  {"soundLoop", MenuParse_soundLoop},
  {"itemDef", MenuParse_itemDef},
  {"cinematic", MenuParse_cinematic},
  {"popup", MenuParse_popup},
  {"fadeClamp", MenuParse_fadeClamp},
  {"fadeCycle", MenuParse_fadeCycle},
  {"fadeAmount", MenuParse_fadeAmount},
  {NULL, voidFunction2}
};

keywordHash_t *menuParseKeywordHash[KEYWORDHASH_SIZE];

/*
===============
Menu_SetupKeywordHash
===============
*/
void Menu_SetupKeywordHash( void )
{
  int i;

  memset( menuParseKeywordHash, 0, sizeof( menuParseKeywordHash ) );

  for( i = 0; menuParseKeywords[ i ].keyword; i++ )
    KeywordHash_Add( menuParseKeywordHash, &menuParseKeywords[ i ] );
}

/*
===============
Menu_Parse
===============
*/
qboolean Menu_Parse( int handle, menuDef_t *menu )
{
  pc_token_t token;
  keywordHash_t *key;

  if( !trap_Parse_ReadToken( handle, &token ) )
    return qfalse;

  if( *token.string != '{' )
    return qfalse;

  while( 1 )
  {
    memset( &token, 0, sizeof( pc_token_t ) );

    if( !trap_Parse_ReadToken( handle, &token ) )
    {
      PC_SourceError( handle, "end of file inside menu\n" );
      return qfalse;
    }

    if( *token.string == '}' )
      return qtrue;

    key = KeywordHash_Find( menuParseKeywordHash, token.string );

    if( !key )
    {
      PC_SourceError( handle, "unknown menu keyword %s", token.string );
      continue;
    }

    if( !key->func( ( itemDef_t* )menu, handle ) )
    {
      PC_SourceError( handle, "couldn't parse menu keyword %s", token.string );
      return qfalse;
    }
  }

  return qfalse;
}

/*
===============
Menu_New
===============
*/
void Menu_New( int handle )
{
  menuDef_t *menu = &Menus[menuCount];

  if( menuCount < MAX_MENUS )
  {
    Menu_Init( menu );

    if( Menu_Parse( handle, menu ) )
    {
      Menu_PostParse( menu );
      menuCount++;
    }
  }
}

int Menu_Count( void )
{
  return menuCount;
}

void Menu_UpdateAll( void )
{
  int i;

  for( i = 0; i < openMenuCount; i++ )
    Menu_Update( menuStack[ i ] );
}

void Menu_PaintAll( void )
{
  int i;

  if( g_editingField || g_waitingForKey )
    DC->setCVar( "ui_hideCursor", "1" );
  else
    DC->setCVar( "ui_hideCursor", "0" );

  if( captureFunc != voidFunction )
  {
    if( captureFuncExpiry > 0 && DC->realTime > captureFuncExpiry )
      UI_RemoveCaptureFunc( );
    else
      captureFunc( captureData );
  }

  for( i = 0; i < openMenuCount; i++ )
    Menu_Paint( menuStack[ i ], qfalse );

  if( DC->getCVarValue( "ui_developer" ) )
  {
    vec4_t v = {1, 1, 1, 1};
    UI_Text_Paint( 5, 25, .5, v, va( "fps: %f", DC->FPS ), 0, 0, 0 );
  }
}

void Menu_Reset( void )
{
  menuCount = 0;
}

displayContextDef_t *Display_GetContext( void )
{
  return DC;
}

void *Display_CaptureItem( int x, int y )
{
  int i;

  for( i = 0; i < menuCount; i++ )
  {
    if( Rect_ContainsPoint( &Menus[i].window.rect, x, y ) )
      return & Menus[i];
  }

  return NULL;
}


// FIXME:
qboolean Display_MouseMove( void *p, float x, float y )
{
  int i;
  menuDef_t *menu = p;

  if( menu == NULL )
  {
    menu = Menu_GetFocused();

    if( menu )
    {
      if( menu->window.flags & WINDOW_POPUP )
      {
        Menu_HandleMouseMove( menu, x, y );
        return qtrue;
      }
    }

    for( i = 0; i < menuCount; i++ )
      Menu_HandleMouseMove( &Menus[i], x, y );
  }
  else
  {
    menu->window.rect.x += x;
    menu->window.rect.y += y;
    Menu_UpdatePosition( menu );
  }

  return qtrue;

}

int Display_CursorType( int x, int y )
{
  int i;

  for( i = 0; i < menuCount; i++ )
  {
    rectDef_t r2;
    r2.x = Menus[i].window.rect.x - 3;
    r2.y = Menus[i].window.rect.y - 3;
    r2.w = r2.h = 7;

    if( Rect_ContainsPoint( &r2, x, y ) )
      return CURSOR_SIZER;
  }

  return CURSOR_ARROW;
}


void Display_HandleKey( int key, qboolean down, int x, int y )
{
  menuDef_t *menu = Display_CaptureItem( x, y );

  if( menu == NULL )
    menu = Menu_GetFocused();

  if( menu )
    Menu_HandleKey( menu, key, down );
}

static void Window_CacheContents( windowDef_t *window )
{
  if( window )
  {
    if( window->cinematicName )
    {
      int cin = DC->playCinematic( window->cinematicName, 0, 0, 0, 0 );
      DC->stopCinematic( cin );
    }
  }
}


static void Item_CacheContents( itemDef_t *item )
{
  if( item )
    Window_CacheContents( &item->window );

}

static void Menu_CacheContents( menuDef_t *menu )
{
  if( menu )
  {
    int i;
    Window_CacheContents( &menu->window );

    for( i = 0; i < menu->itemCount; i++ )
      Item_CacheContents( menu->items[i] );

    if( menu->soundName && *menu->soundName )
      DC->registerSound( menu->soundName, qfalse );
  }

}

void Display_CacheAll( void )
{
  int i;

  for( i = 0; i < menuCount; i++ )
    Menu_CacheContents( &Menus[i] );
}


static qboolean Menu_OverActiveItem( menuDef_t *menu, float x, float y )
{
  if( menu && menu->window.flags & ( WINDOW_VISIBLE | WINDOW_FORCED ) )
  {
    if( Rect_ContainsPoint( &menu->window.rect, x, y ) )
    {
      int i;

      for( i = 0; i < menu->itemCount; i++ )
      {
        if( !( menu->items[i]->window.flags & ( WINDOW_VISIBLE | WINDOW_FORCED ) ) )
          continue;

        if( menu->items[i]->window.flags & WINDOW_DECORATION )
          continue;

        if( Rect_ContainsPoint( &menu->items[i]->window.rect, x, y ) )
        {
          itemDef_t * overItem = menu->items[i];

          if( overItem->type == ITEM_TYPE_TEXT && overItem->text )
          {
            if( Rect_ContainsPoint( Item_CorrectedTextRect( overItem ), x, y ) )
              return qtrue;
            else
              continue;
          }
          else
            return qtrue;
        }
      }

    }
  }

  return qfalse;
}
