diff options
Diffstat (limited to 'src/ui/ui_shared.c')
-rw-r--r-- | src/ui/ui_shared.c | 8081 |
1 files changed, 8081 insertions, 0 deletions
diff --git a/src/ui/ui_shared.c b/src/ui/ui_shared.c new file mode 100644 index 0000000..7a47413 --- /dev/null +++ b/src/ui/ui_shared.c @@ -0,0 +1,8081 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 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 +================= +*/ +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 +================= +*/ +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 + 0.15 ); + emoticonW = UI_EmoticonWidth( font, scale + 0.15 ); + + 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 = sizeof( commandList ) / sizeof( commandDef_t ); + +// 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; + int count; + + count = DC->feederCount( item->feederID ); + + 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 ▭ +} + +void Menu_HandleKey( menuDef_t *menu, int key, qboolean down ) +{ + int i; + itemDef_t *item = NULL; + qboolean inHandler = qfalse; + + inHandler = qtrue; + + if( g_waitingForKey && down ) + { + Item_Bind_HandleKey( g_bindItem, key, down ); + inHandler = qfalse; + 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; + inHandler = qfalse; + return; + } + else + { + Item_RunScript( g_editItem, g_editItem->onCharEntry ); + } + } + + if( menu == NULL ) + { + inHandler = qfalse; + 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; + inHandler = 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 ); + inHandler = qfalse; + return; + } + } + + if( !down ) + { + inHandler = qfalse; + 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; + } + + inHandler = qfalse; +} + +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 int g_bindCount = sizeof( g_bindings ) / sizeof( bind_t ); + +/* +================= +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, value; + menuDef_t *parent = ( menuDef_t* )item->parent; + float vScale = Item_Slider_VScale( item ); + + 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 ) ); + + 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; + 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 ); + 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; +} |