tenebrae2/xmlmenu.c
2003-02-02 17:57:45 +00:00

2220 lines
48 KiB
C

/*
Copyright (C) 2003 Tenebrae Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
/*
-DC-
a pretty inefficient way of implementing XUL elements ;)
most of the attributes aren't supported
*/
#include "quakedef.h"
#ifdef _WIN32
#include "winquake.h"
#endif
#define MAX_MLABEL 128
// -------------------------------------------------------------------------------------
// --X--X--X-- WARNING WARNING WARNING --X--X--X--
// FIXME :
// copy & pasted from gl_draw.c
// update me accordingly or better, move me in glquake.h
// while I'm still supported
typedef struct
{
int texnum;
float sl, tl, sh, th;
} glpic_t;
// --X--X--X-- WARNING WARNING WARNING --X--X--X--
void GL_LoadPic (char *src, glpic_t *target)
{
typedef struct _TargaHeader
{
unsigned char id_length, colormap_type, image_type;
unsigned short colormap_index, colormap_length;
unsigned char colormap_size;
unsigned short x_origin, y_origin, width, height;
unsigned char pixel_size, attributes;
} TargaHeader;
extern TargaHeader targa_header;
extern byte *targa_rgba;
qpic_t *pic;
int len = strlen(src);
if (strncmp(src+len-4,".lmp",4)){
// load file in memory
LoadTexture (src, 4);
// assign it to a texture object
target->texnum = GL_LoadTexture (src, targa_header.width, targa_header.height, targa_rgba, false, true, false);
free (targa_rgba);
}
else {
// lump file
// FIXME : drop lump file support in the future
pic = (qpic_t *) COM_LoadTempFile (src);
if (!pic)
Sys_Error ("GL_LoadPic: failed to load %s", src);
SwapPic (pic);
target->texnum = GL_LoadTexture (src, pic->width, pic->height, pic->data, false, true, false);
}
target->sl = 0;
target->sh = 1;
target->tl = 0;
target->th = 1;
}
// -------------------------------------------------------------------------------------
cvar_t m_debug = {"m_debug","0"};
typedef struct xmlimagedata_s
{
char src[MAX_OSPATH];
glpic_t pic;
int width;
int height;
} xmlimagedata_t;
typedef struct xmlbuttondata_s
{
int disabled:1;
int isdown:1;
char label[MAX_MLABEL];
char command[255];
} xmlbuttondata_t;
typedef struct xmlcheckboxdata_s
{
int disabled:1;
int checked:1;
char label[MAX_MLABEL];
} xmlcheckboxdata_t;
typedef struct xmllabeldata_s
{
char text[MAX_MLABEL];
} xmllabeldata_t;
typedef struct xmlsliderdata_s
{
int range;
int cursor;
char label[MAX_MLABEL];
} xmlsliderdata_t;
typedef struct xmlradiodata_s
{
int disabled:1;
int selected:1;
char label[MAX_MLABEL];
} xmlradiodata_t;
typedef struct xmlradiogroupdata_s
{
int disabled:1;
char label[MAX_MLABEL];
} xmlradiogroupdata_t;
typedef struct xmlmenudata_s
{
int dummy;
} xmlmenudata_t;
/*
xml window data
*/
typedef struct qwindow_s {
char file[MAX_OSPATH];
qwidget_t *widget;
qwidget_t *focused;
struct qwindow_s *stack; // visible window stack
struct qwindow_s *next; // available window list
} qwindow_t;
xmlhandler_t widgethandlers[] =
{
{
(const xmlChar *)"box",
{
0, // debug
0, // focusable
0, // orient = horizontal
a_stretch, // align
p_start, // pack
"", // name
"", // id
0, // xpos
0, // ypos
{0,0}, // width
{0,0}, // height
0, // num_children
NULL, // tag
NULL, // parent
// NULL, // root
NULL, // previous
NULL, // next
NULL, // children
NULL, // rchildren
// functions
M_LoadXmlBox, // Load
NULL, // Draw
NULL, // Focus
M_XmlBoxKey, // HandleKey
NULL, // onMouseOver
NULL, // onMouseDown
NULL, // onMouseUp
NULL // data
},
0
},
{(const xmlChar *)"hbox",
{
0, // debug
0, // focusable
0, // orient = horizontal
a_stretch, // align
p_start, // pack
"", // name
"", // id
0, // xpos
0, // ypos
{0,0}, // width
{0,0}, // height
0, // num_children
NULL, // tag
NULL, // parent
// NULL, // root
NULL, // previous
NULL, // next
NULL, // children
NULL, // rchildren
// functions
M_LoadXmlBox, // Load
NULL, // Draw
NULL, // Focus
M_XmlBoxKey, // HandleKey
NULL, // onMouseOver
NULL, // onMouseDown
NULL, // onMouseUp
NULL // data
},
0
},
{
(const xmlChar *)"vbox",
{
0, // debug
0, // focusable
1, // orient = vertical
a_stretch, // align
p_start, // pack
"", // name
"", // id
0, // xpos
0, // ypos
{0,0}, // width
{0,0}, // height
0, // num_children
NULL, // tag
NULL, // parent
// NULL, // root
NULL, // previous
NULL, // next
NULL, // children
NULL, // rchildren
// functions
M_LoadXmlBox, // Load
NULL, // Draw
NULL, // Focus
M_XmlBoxKey, // HandleKey
NULL, // onMouseOver
NULL, // onMouseDown
NULL, // onMouseUp
NULL // data
},
0
},
{
(const xmlChar *)"label",
{
0, // debug
0, // focusable
0, // orient
a_stretch, // align
p_start, // pack
"", // name
"", // id
0, // xpos
0, // ypos
{0,0}, // width
{0,0}, // height
0, // num_children
NULL, // tag
NULL, // parent
// NULL, // root
NULL, // previous
NULL, // next
NULL, // children
NULL, // rchildren
// functions
M_LoadXmlLabel, // Load
M_DrawXmlLabel, // Draw
NULL, // Focus
NULL, // HandleKey
NULL, // onMouseOver
NULL, // onMouseDown
NULL, // onMouseUp
NULL // data
},
sizeof(xmllabeldata_t)
},
{
(const xmlChar *)"image",
{
0, // debug
0, // focusable
0, // orient
a_stretch, // align
p_start, // pack
"", // name
"", // id
0, // xpos
0, // ypos
{0,0}, // width
{0,0}, // height
0, // num_children
NULL, // tag
NULL, // parent
// NULL, // root
NULL, // previous
NULL, // next
NULL, // children
NULL, // rchildren
// functions
M_LoadXmlImage, // Load
M_DrawXmlImage, // Draw
NULL, // Focus
NULL, // HandleKey
NULL, // onMouseOver
NULL, // onMouseDown
NULL, // onMouseUp
NULL // data
},
sizeof(xmlimagedata_t)
},
{
(const xmlChar *)"button",
{
0, // debug
1, // focusable
0, // orient
a_stretch, // align
p_start, // pack
"", // name
"", // id
0, // xpos
0, // ypos
{0,0}, // width
{0,0}, // height
0, // num_children
NULL, // tag
NULL, // parent
// NULL, // root
NULL, // previous
NULL, // next
NULL, // children
NULL, // rchildren
// functions
M_LoadXmlButton, // Load
M_DrawXmlButton, // Draw
NULL, // Focus
M_XmlButtonKey, // HandleKey
NULL, // onMouseOver
NULL, // onMouseDown
NULL, // onMouseUp
NULL // data
},
sizeof(xmlbuttondata_t)
},
{
(const xmlChar *)"menuitem",
{
0, // debug
1, // focusable
0, // orient
a_stretch, // align
p_start, // pack
"", // name
"", // id
0, // xpos
0, // ypos
{0,0}, // width
{0,0}, // height
0, // num_children
NULL, // tag
NULL, // parent
// NULL, // root
NULL, // previous
NULL, // next
NULL, // children
NULL, // rchildren
// functions
M_LoadXmlButton, // Load
M_DrawXmlButton, // Draw
NULL, // Focus
M_XmlButtonKey, // HandleKey
NULL, // onMouseOver
NULL, // onMouseDown
NULL, // onMouseUp
NULL // data
},
sizeof(xmlbuttondata_t)
},
{
(const xmlChar *)"checkbox",
{
0, // debug
1, // focusable
0, // orient
a_stretch, // align
p_start, // pack
"", // name
"", // id
0, // xpos
0, // ypos
{0,0}, // width
{0,0}, // height
0, // num_children
NULL, // tag
NULL, // parent
// NULL, // root
NULL, // previous
NULL, // next
NULL, // children
NULL, // rchildren
// functions
M_LoadXmlCheckBox, // Load
M_DrawXmlCheckBox, // Draw
NULL, // Focus
M_XmlCheckBoxKey, // HandleKey
NULL, // onMouseOver
NULL, // onMouseDown
NULL, // onMouseUp
NULL // data
},
sizeof(xmlcheckboxdata_t)
},
{
(const xmlChar *)"radio",
{
0, // debug
1, // focusable
0, // orient
a_stretch, // align
p_start, // pack
"", // name
"", // id
0, // xpos
0, // ypos
{0,0}, // width
{0,0}, // height
0, // num_children
NULL, // tag
NULL, // parent
// NULL, // root
NULL, // previous
NULL, // next
NULL, // children
NULL, // rchildren
// functions
M_LoadXmlRadio, // Load
M_DrawXmlRadio, // Draw
NULL, // Focus
M_XmlRadioKey, // HandleKey
NULL, // onMouseOver
NULL, // onMouseDown
NULL, // onMouseUp
NULL // data
},
sizeof(xmlradiodata_t)
},
{
(const xmlChar *)"radiogroup",
{
0, // debug
1, // focusable
1, // orient = vertical
a_stretch, // align
p_start, // pack
"", // name
"", // id
0, // xpos
0, // ypos
{0,0}, // width
{0,0}, // height
0, // num_children
NULL, // tag
NULL, // parent
// NULL, // root
NULL, // previous
NULL, // next
NULL, // children
NULL, // rchildren
// functions
M_LoadXmlRadioGroup, // Load
M_DrawXmlRadioGroup, // Draw
NULL, // Focus
M_XmlRadioGroupKey, // HandleKey
NULL, // onMouseOver
NULL, // onMouseDown
NULL, // onMouseUp
NULL // data
},
sizeof(xmlradiogroupdata_t)
},
{
(const xmlChar *)"slider",
{
0, // debug
1, // focusable
0, // orient
a_stretch, // align
p_start, // pack
"", // name
"", // id
0, // xpos
0, // ypos
{0,0}, // width
{0,0}, // height
0, // num_children
NULL, // tag
NULL, // parent
// NULL, // root
NULL, // previous
NULL, // next
NULL, // children
NULL, // rchildren
// functions
M_LoadXmlSlider, // Load
M_DrawXmlSlider, // Draw
NULL, // Focus
M_XmlSliderKey, // HandleKey
NULL, // onMouseOver
NULL, // onMouseDown
NULL, // onMouseUp
NULL // data
},
sizeof(xmlsliderdata_t)
},
{
(const xmlChar *)"window",
{
0, // debug
0, // focusable
0, // orient
a_stretch, // align
p_start, // pack
"", // name
"", // id
0, // xpos
0, // ypos
{0,0}, // width
{0,0}, // height
0, // num_children
NULL, // tag
NULL, // parent
// NULL, // root
NULL, // previous
NULL, // next
NULL, // children
NULL, // rchildren
// functions
M_LoadXmlWindow, // Load
NULL, // Draw
NULL, // Focus
M_XmlWindowKey, // HandleKey
NULL, // onMouseOver
NULL, // onMouseDown
NULL, // onMouseUp
NULL // data
},
sizeof(qwindow_t)
},
NULL
};
xmlChar *xmlalign[] = {
"start",
"center",
"end",
"baseline",
"stretch"
};
xmlChar *xmlpack[] = {
"start",
"center",
"end"
};
xmlChar *xmlorient[] = {
"horizontal",
"vertical"
};
xmlChar *xmlbool[] = {
"false",
"true"
};
/* -DC-
* perhaps we could use xml menus to define hud ?
* qwidget_t *hud_window;
*/
void M_DrawXmlNestedWidget (qwidget_t *widget, int xpos, int ypos);
qwindow_t *windows = NULL;
qwindow_t *visible_window;
glpic_t focus_pic;
// -------------------------------------------------------------------------------------
/* -DC- useless for now
typedef struct qwiterator_s {
qwidget_t *w;
void *(next_f)(struct qwiterator_s *self);
} qwiterator_t;
void iterator_next (qwiterator_t *self)
{
qwidget_t *w = self->w;
if (w->next) {
self->w = w->next;
else
self->w = NULL;
}
void iterator_prev (qwiterator_t *self)
{
qwidget_t *w = self->w;
if (w->previous)
self->w = w->previous;
else
self->w = NULL;
}
void treeiterator_next (qwiterator_t *self)
{
qwidget_t *w = self->w;
if (w->children)
self->w = w->children;
else if (!w->next)
while (w->parent && !w->next)
w = w->parent;
self->w = w->next;
}
void treeiterator_prev (qwiterator_t *self)
{
qwidget_t *w = self->w;
if (w->rchildren)
self->w = w->rchildren;
else if (!w->previous)
while (w->parent && !w->previous)
w = w->parent;
self->w = w->previous;
}
*/
// -------------------------------------------------------------------------------------
#define SLIDER_RANGE 10
qboolean m_entersound; // play after drawing a frame, so caching
// won't disrupt the sound
qboolean m_recursiveDraw;
int m_return_state;
qboolean m_return_onerror;
char m_return_reason [32];
extern cvar_t con_spiral;
enum {m_none, m_menu } m_state;
// -------------------------------------------------------------------------------------
void M_StartGame_f (void)
{
if (sv.active) {
if (!SCR_ModalMessage("Are you sure you want to\nstart a new game?\n"))
return;
Cbuf_AddText ("disconnect\n");
}
Cbuf_AddText ("maxplayers 1\n");
Cbuf_AddText ("map start\n");
}
/* -DC-
FIXME :
change Host_Quit_f in host_cmd to M_Quit_f code
*/
void M_Quit_f (void)
{
CL_Disconnect ();
Host_ShutdownServer(false);
Sys_Quit ();
}
void M_CloseWindow_f (void)
{
qwindow_t *temp = visible_window;
if (visible_window != NULL){
visible_window = visible_window->stack;
temp->stack = NULL;
}
if (visible_window == NULL)
key_dest = key_game;
}
void M_OpenWindow_f (void)
{
qwindow_t *w = windows;
qwindow_t *temp = visible_window;
char *name;
if (Cmd_Argc() != 2)
{
Con_Printf ("openwindow <name> : bring up the window called 'name'\n");
return;
}
name = Cmd_Argv(1);
while (w){
if (!strncmp (w->widget->id, name, sizeof(w->widget->id)))
break;
w = w->next;
}
if (!w) {
Con_Printf ("openwindow : no such window '%s'\n", name);
return;
}
while (temp && (temp!=w))
temp = temp->stack;
if (temp == w){
// which one ?
// - close all opened windows back to w
// - close all opened windows from w and stack up w
// * close all opened windows then stack up w
// - change the whole damn window code
while (visible_window)
M_CloseWindow_f ();
}
w->stack = visible_window;
visible_window = w;
if (w->focused == NULL) {
w->focused = w->widget;
M_CycleFocusNext ();
}
key_dest = key_menu;
m_state = m_menu;
}
void M_WindowList_f (void)
{
qwindow_t *w = windows;
while (w){
Con_Printf ("%s\n", w->widget->id);
w = w->next;
}
}
void M_Init (void)
{
Cmd_AddCommand ("startgame", M_StartGame_f);
Cmd_AddCommand ("openwindow", M_OpenWindow_f);
Cmd_AddCommand ("closewindow", M_CloseWindow_f);
Cmd_AddCommand ("windowlist", M_WindowList_f);
Cvar_RegisterVariable (&m_debug);
// let's load the whole stuff now
COM_FindAllExt ("menu", "xul", M_LoadXmlWindowFile);
GL_LoadPic ("menu/focus.png",&focus_pic);
}
/*
========
M_Menu_Main_f
M_Menu_Options_f
M_Menu_Quit_f
these functions are used in another part of the code (console.c, vid_glnt.c, vid_glsdl.c) so I implemented them as stub to
the console commands they are bound to originally to concentrate the menus change in one file.
So basically here we're just expecting that some other part of the game will have defined these commands/aliases/whatever
(the menus should create the corresponding windows).
========
*/
void M_Menu_Main_f (void)
{
Cbuf_AddText ("openwindow main\n");
}
void M_Menu_Quit_f (void)
{
M_Quit_f ();
}
void M_Menu_Options_f (void)
{
Cbuf_AddText ("openwindow help\n");
}
void (*vid_menudrawfn)(void);
void (*vid_menukeyfn)(int key);
/*
========
M_Keydown
recursive function set : this one is at top level, and calls the focused widget key handler
========
*/
void M_Keydown (int key)
{
qwidget_t *focused = visible_window->focused;
if (focused && focused->HandleKey)
M_XmlElementKey (focused,key);
else // current window get the key
M_XmlElementKey (visible_window->widget, key);
}
// -------------------------------------------------------------------------------------
void M_Print (int cx, int cy, char *str)
{
while (*str)
{
Draw_Character (cx, cy, (*str)+128);
str++;
cx += 8;
}
}
void M_DrawTransPic (int x, int y, qpic_t *pic)
{
Draw_TransPic (x + ((vid.width - 320)>>1), y, pic);
}
void M_DrawPic (int x, int y, qpic_t *pic)
{
Draw_Pic (x + ((vid.width - 320)>>1), y, pic);
}
byte identityTable[256];
byte translationTable[256];
void M_BuildTranslationTable(int top, int bottom)
{
int j;
byte *dest, *source;
for (j = 0; j < 256; j++)
identityTable[j] = j;
dest = translationTable;
source = identityTable;
memcpy (dest, source, 256);
if (top < 128) // the artists made some backwards ranges. sigh.
memcpy (dest + TOP_RANGE, source + top, 16);
else
for (j=0 ; j<16 ; j++)
dest[TOP_RANGE+j] = source[top+15-j];
if (bottom < 128)
memcpy (dest + BOTTOM_RANGE, source + bottom, 16);
else
for (j=0 ; j<16 ; j++)
dest[BOTTOM_RANGE+j] = source[bottom+15-j];
}
void M_DrawTransPicTranslate (int x, int y, qpic_t *pic)
{
Draw_TransPicTranslate (x + ((vid.width - 320)>>1), y, pic, translationTable);
}
void M_DrawTextBox (int x, int y, int width, int lines)
{
qpic_t *p;
int cx, cy;
int n;
// draw left side
cx = x;
cy = y;
p = Draw_CachePic ("gfx/box_tl.lmp");
M_DrawTransPic (cx, cy, p);
p = Draw_CachePic ("gfx/box_ml.lmp");
for (n = 0; n < lines; n++)
{
cy += 16;
M_DrawTransPic (cx, cy, p);
}
p = Draw_CachePic ("gfx/box_bl.lmp");
M_DrawTransPic (cx, cy+16, p);
// draw middle
cx += 8;
while (width > 0)
{
cy = y;
p = Draw_CachePic ("gfx/box_tm.lmp");
M_DrawTransPic (cx, cy, p);
p = Draw_CachePic ("gfx/box_mm.lmp");
for (n = 0; n < lines; n++)
{
cy += 16;
if (n == 1)
p = Draw_CachePic ("gfx/box_mm2.lmp");
M_DrawTransPic (cx, cy, p);
}
p = Draw_CachePic ("gfx/box_bm.lmp");
M_DrawTransPic (cx, cy+16, p);
width -= 2;
cx += 16;
}
// draw right side
cy = y;
p = Draw_CachePic ("gfx/box_tr.lmp");
M_DrawTransPic (cx, cy, p);
p = Draw_CachePic ("gfx/box_mr.lmp");
for (n = 0; n < lines; n++)
{
cy += 8;
M_DrawTransPic (cx, cy, p);
}
p = Draw_CachePic ("gfx/box_br.lmp");
M_DrawTransPic (cx, cy+8, p);
}
void M_ToggleMenu_f (void)
{
m_entersound = true;
if (key_dest == key_menu)
{
// if not main menu
{
M_Menu_Main_f ();
return;
}
key_dest = key_game;
m_state = m_none;
return;
}
if (key_dest == key_console)
{
Con_ToggleConsole_f ();
}
else
{
M_Menu_Main_f ();
}
}
// -------------------------------------------------------------------------------------
void M_XmlButtonCommand (qwidget_t *self)
{
xmlbuttondata_t *data = self->data;
Cbuf_AddText (data->command);
Cbuf_AddText ("\n");
}
// -------------------------------------------------------------------------------------
qwidget_t *M_NextXmlElement (qwidget_t *w)
{
if (w->children)
return w->children;
if (w->next)
return w->next;
while (w->parent && !w->next)
w = w->parent;
if (!w->next)
return visible_window->widget;
return w->next;
}
qwidget_t *M_PreviousXmlElement (qwidget_t *w)
{
if (w->rchildren)
return w->rchildren;
if (w->previous)
return w->previous;
while (w->parent && !w->previous)
w = w->parent;
if (!w->next)
return visible_window->widget;
return w->previous;
}
void M_CycleFocus (qwidget_t *(*next_f)(qwidget_t *))
{
qwidget_t *w = visible_window->focused;
do {
w = next_f (w);
} while ((w != visible_window->focused) && !(w->focusable));
visible_window->focused = w;
}
// -------------------------------------------------------------------------------------
qboolean M_XmlBoxKey (qwidget_t *self, int k)
{
if (!self->children)
return false;
switch (k)
{
case K_UPARROW:
S_LocalSound ("misc/menu1.wav");
M_CycleFocusPrevious ();
break;
case K_DOWNARROW:
case K_TAB:
S_LocalSound ("misc/menu1.wav");
M_CycleFocusNext ();
break;
case K_ESCAPE:
// pop up previous menu
S_LocalSound ("misc/menu2.wav");
M_CloseWindow_f ();
break;
default:
return false;
}
return true;
}
qboolean M_XmlCheckBoxKey (qwidget_t *self, int k)
{
xmlcheckboxdata_t *data = self->data;
switch (k)
{
case K_ENTER:
// (un)check the checkbox
S_LocalSound ("misc/menu2.wav");
data->checked = !data->checked;
Cvar_SetValue (self->id, data->checked);
break;
default:
return false;
}
return true;
}
qboolean M_XmlButtonKey (qwidget_t *self, int k)
{
switch (k)
{
case K_ENTER:
S_LocalSound ("misc/menu2.wav");
M_XmlButtonCommand (self);
break;
default:
return false;
}
return true;
}
qboolean M_XmlRadioKey (qwidget_t *self, int k)
{
switch (k)
{
case K_ENTER:
// select the radio button
break;
default:
return false;
}
return true;
}
qboolean M_XmlRadioGroupKey (qwidget_t *self, int k)
{
return M_XmlBoxKey (self,k);
}
qboolean M_XmlSliderKey (qwidget_t *self, int k)
{
xmlsliderdata_t *data = self->data;
switch (k)
{
case K_LEFTARROW:
// reduce cvar value
S_LocalSound ("misc/menu3.wav");
data->range -= 1/SLIDER_RANGE;
break;
case K_RIGHTARROW:
// augment cvar value
S_LocalSound ("misc/menu3.wav");
data->range += 1/SLIDER_RANGE;
break;
default:
return false;
}
return true;
}
qboolean M_XmlWindowKey (qwidget_t *self, int k)
{
return M_XmlBoxKey (self,k);
}
qboolean M_XmlMenuKey (qwidget_t *self, int k)
{
return M_XmlBoxKey (self,k);
}
qboolean M_XmlElementKey (qwidget_t *widget, int k)
{
// start from widget
// and propagate the key up the parents tree
// until it has been consumed or it reaches the
// tree root
while (widget){
if (widget->HandleKey)
if (widget->HandleKey (widget, k))
return true;
widget = widget->parent;
}
return false;
}
// -------------------------------------------------------------------------------------
/*
================
Draw_StringN
draws the str string of length len in the (x1,y1,x2,y2) frame
================
*/
int Draw_StringN (int x1, int y1, int x2, int y2, char *str, int len)
{
// calculate string width and height, center it
int w = len * 8;
int h = 16;
int x = x1 + (x2 - x1 - w)/2;
int y = y1 + (y2 - y1 - h)/2;
while (*str && len)
{
Draw_Character (x, y, (*str)+128);
str++;
x += 8;
len--;
}
return x;
}
void M_Draw (void)
{
if (m_state == m_none || key_dest != key_menu)
return;
if (!m_recursiveDraw)
{
scr_copyeverything = 1;
if (scr_con_current)
{
if (con_spiral.value) //Console Spiral - Eradicator
Draw_SpiralConsoleBackground (vid.height);
else
Draw_ConsoleBackground (vid.height);
VID_UnlockBuffer ();
S_ExtraUpdate ();
VID_LockBuffer ();
}
else
Draw_FadeScreen ();
scr_fullupdate = 0;
}
else
{
m_recursiveDraw = false;
}
// draw current window
M_DrawVisibleWindow ();
if (m_entersound)
{
S_LocalSound ("misc/menu2.wav");
m_entersound = false;
}
VID_UnlockBuffer ();
S_ExtraUpdate ();
VID_LockBuffer ();
}
void M_DrawXmlButton (qwidget_t *self, int x, int y)
{
xmlbuttondata_t *data = self->data;
Draw_StringN (x, y, x+self->width.absolute, y+self->height.absolute, data->label, sizeof(data->label));
}
void M_DrawXmlImage (qwidget_t *self, int x, int y)
{
xmlimagedata_t *data = self->data;
if (data->pic.texnum == 0)
GL_LoadPic (data->src, &(data->pic));
glColor4f (1,1,1,1);
Draw_GLPic (x, y, x+self->width.absolute, y+self->height.absolute, &(data->pic));
}
void M_DrawXmlLabel (qwidget_t *self, int x, int y)
{
xmllabeldata_t *data = self->data;
Draw_StringN (x, y, x+self->width.absolute, y+self->height.absolute, data->text, sizeof(data->text));
}
void M_DrawXmlBox (qwidget_t *self, int x, int y)
{
// nothing to do
}
void M_DrawXmlRadio (qwidget_t *self, int x, int y)
{
xmlradiodata_t *data = self->data;
qboolean on = data->selected;
Draw_StringN (x, y, x+self->width.absolute, y+self->height.absolute, data->label, sizeof(data->label));
if (on)
M_Print (x, y, "(o)");
else
M_Print (x, y, "( )");
}
void M_DrawXmlRadioGroup (qwidget_t *self, int x, int y)
{
// nothing to do
}
// almost pasted from menu.c
void M_DrawXmlSlider (qwidget_t *self, int x, int y)
{
int i;
xmlsliderdata_t *data = self->data;
int range = data->range;
if (range < 0)
range = 0;
if (range > 1)
range = 1;
Draw_Character (x, y, 128);
x += 8;
for (i=0 ; i<SLIDER_RANGE ; i++)
Draw_Character (x + i*8, y, 129);
Draw_Character (x+i*8, y, 130);
Draw_Character (x + (SLIDER_RANGE-1)*8 * range, y, 131);
}
// almost pasted from menu.c
void M_DrawXmlCheckBox (qwidget_t *self,int x, int y)
{
xmlcheckboxdata_t *data = self->data;
qboolean on = data->checked;
x = Draw_StringN (x, y, x+self->width.absolute, y+self->height.absolute, data->label, sizeof(data->label));
if (on)
M_Print (x, y, "[x]");
else
M_Print (x, y, "[ ]");
}
void M_DrawXmlWindow (qwidget_t *self,int x, int y)
{
qwindow_t *data = self->data;
// draw a border ?
}
void M_DrawFocus (qwidget_t *self, int x, int y){
int xs = x+self->width.absolute;
int ys = y+self->height.absolute;
if (focus_pic.texnum == 0)
GL_LoadPic ("menu/focus.png",&focus_pic);
glDisable (GL_ALPHA_TEST);
glEnable (GL_BLEND);
glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glColor4f (0.5,0.5,0.5,0.5);
Draw_GLPic (x, y, xs, ys, &focus_pic);
glDisable (GL_BLEND);
glEnable (GL_ALPHA_TEST);
}
/* -DC-
FIXME : doesn't work at all
So this function is a little hack to display widget outline
just for debugging purpose
-> There is certainly a (better) way to do this
cause I don't know much about OpenGL (shame on me ^^;)
*/
void M_DrawOutlines (qwidget_t *self, int x, int y){
int xs = x + self->width.absolute;
int ys = x + self->height.absolute;
GLfloat color[4];
GLint shademodel;
glGetFloatv(GL_CURRENT_COLOR, color);
glColor4f(1,1,1,1);
//glGetIntegerv(GL_SHADE_MODEL, &shademodel);
//glShadeModel (GL_FLAT);
glBegin (GL_LINE_STRIP);
glVertex2f (x, y);
glVertex2f (xs, y);
glVertex2f (xs, ys);
glVertex2f (x, ys);
glEnd ();
//glShadeModel (shademodel);
glColor4fv(color);
GL_EnableMultitexture ();
}
/* -DC-
instead of having a function handling recursive drawing of widget
we could perhaps put this code in widget rendering code directly
-> more flexible if some widget need to do stuff in between children
rendering
-> but code duplication
*/
void M_DrawXmlNestedWidget (qwidget_t *widget, int xpos, int ypos)
{
qwidget_t *w;
if (widget->Draw){
widget->Draw (widget, xpos, ypos);
}
if (widget == visible_window->focused){
M_DrawFocus (widget, xpos, ypos);
}
if (m_debug.value) {
// draw outlines
M_DrawOutlines (widget, xpos, ypos);
}
if (widget->children == NULL)
return;
// vertical orientation
if (widget->orient) {
int wwidth;
//ypos += widget->yoffset;
switch (widget->pack){
case p_start:
for (w = widget->children; w != NULL; w = w->next){
M_DrawXmlNestedWidget (w, xpos, ypos);
ypos += w->height.absolute;
}
break;
case p_center:
for (w = widget->children; w != NULL; w = w->next){
wwidth = widget->width.absolute - w->width.absolute;
M_DrawXmlNestedWidget (w, xpos+wwidth/2, ypos);
ypos += w->height.absolute;
}
break;
case p_end:
for (w = widget->children; w != NULL; w = w->next){
wwidth = widget->width.absolute - w->width.absolute;
M_DrawXmlNestedWidget (w, xpos+wwidth, ypos);
ypos += w->height.absolute;
}
break;
}
}
else {
int wheight;
//xpos += widget->xoffset;
switch (widget->pack){
case p_start:
for (w = widget->children; w != NULL; w = w->next){
M_DrawXmlNestedWidget (w, xpos, ypos);
xpos += w->width.absolute;
}
break;
case p_center:
for (w = widget->children; w != NULL; w = w->next){
wheight = - w->height.absolute + widget->height.absolute;
M_DrawXmlNestedWidget (w, xpos, ypos+wheight/2);
xpos += w->width.absolute;
}
break;
case p_end:
for (w = widget->children; w != NULL; w = w->next){
wheight = - w->height.absolute + widget->height.absolute;
M_DrawXmlNestedWidget (w, xpos, ypos+wheight);
xpos += w->width.absolute;
}
break;
}
}
}
void M_DrawVisibleWindow ()
{
int w = vid.width - visible_window->widget->width.absolute;
int h = vid.height - visible_window->widget->height.absolute;
M_DrawXmlNestedWidget (visible_window->widget, w/2, h/2);
}
// -------------------------------------------------------------------------------------
int M_ReadXmlPropAsInt (xmlNodePtr node, char *name, int defvalue)
{
int ret;
int p;
xmlChar *value;
value = xmlGetProp (node, name);
if (value){
ret = atoi (value);
xmlFree (value);
} else
ret = defvalue;
return ret;
}
int M_ReadXmlPropAsFloat (xmlNodePtr node, char *name, float defvalue)
{
float ret;
xmlChar *value;
value = xmlGetProp (node, name);
if (value) {
ret = atof (value);
xmlFree (value);
} else
ret = defvalue;
return ret;
}
char *M_ReadXmlPropAsString (xmlNodePtr node, char *name, char *ret, int size)
{
xmlChar *value;
value = xmlGetProp (node, name);
if (value){
strncpy (ret, (char *)value, size);
xmlFree (value);
} else
*ret='\0';
return ret;
}
int M_CompareXmlProp (xmlNodePtr node, char *name, xmlChar **str, int count)
{
int ret;
xmlChar *value;
value = xmlGetProp (node, name);
if (!value)
return -1;
for (ret = 0; ret < count; ret++){
if (strcmp ((char *)value, (char *)str[ret]) == 0){
xmlFree (value);
return ret;
}
}
xmlFree (value);
return -1;
}
void M_ReadXmlDim (xmlNodePtr node, char *name, xmldim_t *dim)
{
char *p;
xmlChar *value;
value = xmlGetProp (node, name);
if (value) {
p = strchr (value,'%');
if (p){
*p = '\0';
dim->ratio = atof (value) / 100;
*p = '%';
dim->absolute = -1;
}
else {
dim->absolute = atoi (value);
dim->ratio = -1;
}
}
else {
dim->absolute = -1;
dim->ratio = -1;
}
}
// -------------------------------------------------------------------------------------
void *M_AllocateMem (int size, void *template)
{
// FIXME : allocate it in Cache ?
void *ret = Hunk_AllocName (size,"xmlmenu");
if (template)
memcpy (ret, template, size);
else
memset (ret,0,size);
return ret;
}
void M_RemoveXmlWidget (qwidget_t *owner, qwidget_t *child)
{
if (child->parent == owner) {
if (child->previous)
child->previous->next = child->next;
else
owner->children = child->next;
if (child->next)
child->next->previous = child->previous;
else
owner->rchildren = child->previous;
owner->num_children--;
child->next = child->previous = NULL;
}
}
void M_InsertXmlWidget (qwidget_t *owner, qwidget_t *child)
{
child->parent = owner;
owner->num_children++;
if (owner->rchildren) {
qwidget_t *w = owner->rchildren;
child->next = NULL;
owner->rchildren = child;
w->next = child;
child->previous = w;
}
else {
owner->children = child;
owner->rchildren = child;
}
}
// -------------------------------------------------------------------------------------
/*
================
M_BuildHardcodedMenu
Build a default menu set
FIXME : should make something here and add a call in case no menu have been loaded
(perhaps hardcode the original quake menu)
================
*/
void M_BuildHardcodedMenus ()
{
// check legacy aliases (menu_main etc...)
}
// -------------------------------------------------------------------------------------
/*
================
M_CalculateWidgetDim
evaluate widgets size and offsets recursively
The goal is to avoid recalculating everything for each frame
================
*/
void M_CalculateWidgetDim (qwidget_t *root)
{
// we don't want unecessary data on the stack -> so let's make them static
qwidget_t *w;
static int num; // undimensioned widget counter
static int width; // remaining width
static int height; // and height
if (root->children == NULL)
return;
num = 0;
//Con_DPrintf("%s (%d,%d)\n",root->tag,root->width.absolute,root->height.absolute);
width = root->width.absolute;
height = root->height.absolute;
if (root->orient){ // vertical orientation
for (w = root->children; w != NULL; w = w->next){
if (w->height.ratio != -1)
w->height.absolute = (root->height.absolute * w->height.ratio);
if (w->height.absolute != -1)
height -= w->height.absolute;
else num++;
if (w->width.ratio != -1)
w->width.absolute = (root->width.absolute * w->width.ratio);
if (w->width.absolute == -1)
w->width.absolute = root->width.absolute;
}
if (num) {
// widget without defined dimension take a part of the remaining space
for (w = root->children; w != NULL; w = w->next)
if (w->height.absolute == -1)
w->height.absolute = height / num;
} else {
// check for remaining space and set x offset according to the align properties
if (width)
// if orient == vertical -> align changes x offset
switch (root->align) {
case a_start:
root->xoffset = 0;
break;
case a_center:
root->xoffset = width / 2;
break;
case a_end:
root->xoffset = width;
break;
case a_baseline:
// not supported for vertical boxes
root->xoffset = 0;
break;
case a_stretch:
// search for first flexible child and resize it
// FIXME : support flex attribute
root->xoffset = 0;
break;
}
}
}
else { // horizontal orientation
for (w = root->children; w != NULL; w = w->next){
if (w->width.ratio != -1)
w->width.absolute = (root->width.absolute * w->width.ratio);
if (w->width.absolute != -1)
width -= w->width.absolute;
else num++;
if (w->height.ratio != -1)
w->height.absolute = (height * w->height.ratio);
if (w->height.absolute == -1)
w->height.absolute = height;
}
if (num) {
// widget without defined dimension take a part of the remaining space
for (w = root->children; w != NULL; w = w->next)
if (w->width.absolute == -1)
w->width.absolute = width / num;
} else {
// check for remaining space and set y offset according to the align properties
if (height)
// if orient == horizontal -> align changes y offset
switch (root->align) {
case a_start:
root->yoffset = 0;
break;
case a_center:
root->yoffset = height / 2;
break;
case a_end:
root->yoffset = height;
break;
case a_baseline:
// FIXME: here we should look for label
// in children and align them accordingly
// but it's getting complicated
root->yoffset = 0;
break;
case a_stretch:
// search for first flexible child and resize it
// FIXME : support flex attribute
root->yoffset = 0;
break;
}
}
}
// let's do it for the children now
for (w = root->children; w != NULL; w = w->next)
M_CalculateWidgetDim(w);
}
// -------------------------------------------------------------------------------------
/*
================
M_LoadXml*
Load a Xml Element definition from the provided libxml tree pointer
================
*/
void M_LoadXmlBox (qwidget_t *widget, xmlNodePtr node)
{
/*
* --- supported attributes :
* --- unsupported :
*/
}
void M_LoadXmlLabel (qwidget_t *widget, xmlNodePtr node)
{
/*
* --- supported attributes :
control = string : xul element id
disabled = bool
value = string : label text
* --- unsupported :
accesskey = string
*/
xmllabeldata_t *data = widget->data;
M_ReadXmlPropAsString (node, "value", data->text, sizeof (data->text));
}
void M_LoadXmlImage (qwidget_t *widget, xmlNodePtr node)
{
/*
* --- supported attributes :
src = URL
* --- unsupported :
onerror = string
onload = string
validate = {always|never} : load image from cache
*/
xmlimagedata_t *data = widget->data;
M_ReadXmlPropAsString (node, "src", data->src, sizeof (data->src));
//GL_LoadPic (data->src,&(data->pic));
}
void M_LoadXmlButton (qwidget_t *widget, xmlNodePtr node)
{
/*
* --- supported attributes :
command = string
disabled = bool
label = string
* --- unsupported :
image = URL
accesskey = string : shortcut key
autoCheck = bool ?
checked = bool ?
checkState = bool ?
crop = {start|end|center|none} : how the text is cropped when too large
dlgType = {accept|cancel|help|disclosure}
group = int
open = ?
tabindex = int : tab order
type = {checkbox|menu|menu-button|radio}
value = string ? : user attribute
*/
xmlbuttondata_t *data = widget->data;
int temp;
M_ReadXmlPropAsString (node, "command", data->command, sizeof(data->command));
M_ReadXmlPropAsString (node, "label", data->label, sizeof(data->label));
temp = M_CompareXmlProp (node, "disabled", xmlbool, 2);
if (temp != -1)
data->disabled = temp;
}
void M_LoadXmlCheckBox (qwidget_t *widget, xmlNodePtr node)
{
/*
* --- supported attributes :
disabled = bool
label = string
* --- unsupported :
accesskey = string : shortcut key
autoCheck = bool ?
checked = bool ?
checkState = bool ?
crop = {start|end|center|none} : how the text is cropped when too large
dlgType = {accept|cancel|help|disclosure}
group = int
open = ?
tabindex = int : tab order
type = {checkbox|menu|menu-button|radio}
value = string ? : user attribute
*/
xmlcheckboxdata_t *data = widget->data;
int temp;
M_ReadXmlPropAsString (node, "label", data->label, sizeof (data->label));
temp = M_CompareXmlProp (node, "disabled", xmlbool, 2);
if (temp != -1)
data->disabled = temp;
data->checked = Cvar_VariableValue (widget->id);
}
void M_LoadXmlRadio (qwidget_t *widget, xmlNodePtr node)
{
/*
* --- supported attributes :
disabled = bool
label = string
selected = bool
* --- unsupported :
accesskey = string : shortcut key
autoCheck = bool ?
checked = bool ?
checkState = bool ?
crop = {start|end|center|none} : how the text is cropped when too large
focused = bool
group = int
open = ?
tabindex = int : tab order
type = {checkbox|menu|menu-button|radio}
value = string ? : user attribute
*/
xmlradiodata_t *data = widget->data;
int temp;
M_ReadXmlPropAsString (node, "label", data->label, sizeof (data->label));
temp = M_CompareXmlProp (node, "disabled", xmlbool, 2);
if (temp != -1)
data->disabled = temp;
temp = M_CompareXmlProp (node, "selected", xmlbool, 2);
if (temp != -1)
data->selected = temp;
}
void M_LoadXmlRadioGroup (qwidget_t *widget, xmlNodePtr node)
{
/*
* --- supported attributes :
disabled = bool
* --- unsupported :
value = string ? : user attribute
*/
xmlradiogroupdata_t *data = widget->data;
int temp;
temp = M_CompareXmlProp (node, "disabled", xmlbool, 2);
if (temp != -1)
data->disabled = temp;
}
void M_LoadXmlSlider (qwidget_t *widget, xmlNodePtr node)
{
/*
* --- supported attributes :
curpos = [0,maxpos] : cursor position
maxpos = int : maximum cursor value
pageincrement = int
* --- unsupported :
increment = int :
*/
xmlsliderdata_t *data = widget->data;
data->range = M_ReadXmlPropAsInt (node, "maxpos", 0);
// FIXME : shoud property override cvar value ?
data->cursor = M_ReadXmlPropAsInt (node, "curpos", Cvar_VariableValue (widget->name));
}
void M_LoadXmlMenu (qwidget_t *widget, xmlNodePtr node)
{
/*
* --- supported attributes :
* --- unsupported :
screenX = int : window x position (persistent ?)
screenY = int : window y position (persistent ?)
sizemode = {maximized|minimized|normal} : window size state
title = string
windowtype = string
*/
}
void M_LoadXmlWindow (qwidget_t *widget, xmlNodePtr node)
{
/*
* --- supported attributes :
* --- unsupported :
screenX = int : window x position (persistent ?)
screenY = int : window y position (persistent ?)
sizemode = {maximized|minimized|normal} : window size state
title = string
windowtype = string
*/
qwidget_t *w;
qwindow_t *data = widget->data;
char command[255];
data->widget = widget;
// check window size
if (widget->width.ratio != -1)
widget->width.absolute = widget->width.ratio * vid.width;
if (widget->height.ratio != -1)
widget->height.absolute = widget->height.ratio * vid.height;
if (widget->width.absolute == -1)
widget->width.absolute = vid.width;
if (widget->height.absolute == -1)
widget->height.absolute = vid.height;
// add newly created window to the list
data->next = windows;
windows = data;
// register alias for quake compat
strcpy (command, "alias menu_\0");
strcat (command, widget->id); // no more that 32 char in widget->id
strcat (command, " \"openwindow "); //
strcat (command, widget->id); // no more that 32 char in widget->id
strcat (command, "\"\n"); // so it should fit in command
Cbuf_AddText (command);
// we have a fully loaded window here
M_CalculateWidgetDim (widget);
/*
// set root on all sub-widget
w = widget;
while (w){
w->root = widget;
w = M_NextXmlElement (w);
}
*/
}
qwidget_t *M_LoadXmlElement (xmlNodePtr root)
{
/*
* --- supported attributes :
align = {start|center|end|baseline|stretch}
pack = {start|center|end}
debug = bool : draw a border around it and all children ?
height = int
width = int
id = string : the name/command of the object
orient = {horizontal|vertical}
* --- unsupported :
allowevents
allownegativeassertions
class
coalesceduplicatearcs
collapsed
container
containment
context
contextmenu
datasources
dir
empty
equalsize = {always|never} : This attribute can be used to make the children of the element equal in size.
flags = {dont-test-empty|dont-build-content}
flex
flexgroup
hidden
insertafter
insertbefore
left
maxheight
maxwidth
menu
minheight
minwidth
observes
ordinal
pack = {start|center|end}
persist
popup
position
ref
removeelement
statustext
style
template
tooltip
tooltiptext
top
uri
*/
xmlNodePtr node;
qwidget_t *ret,*sub;
xmlhandler_t *handler;
int temp;
static int sp;
// we don't want anonymous tags
if (!root->name)
return NULL;
// first read/initialize specifics attributes
handler = widgethandlers;
while (handler->tag) {
if (!xmlStrcmp (root->name, handler->tag)){
break;
}
handler++;
}
if (!handler->tag)
return NULL;
ret = M_AllocateMem (sizeof(qwidget_t), &(handler->template));
if (handler->datasize)
ret->data = M_AllocateMem (handler->datasize, NULL);
ret->tag = (char *)handler->tag;
// then read general attributes from the file
M_ReadXmlDim (root, "width", &ret->width);
M_ReadXmlDim (root, "height", &ret->height);
ret->debug = (M_ReadXmlPropAsInt (root, "debug", 0) != 0);
M_ReadXmlPropAsString (root, "id", ret->id, sizeof(ret->id));
M_ReadXmlPropAsString (root, "name", ret->name, sizeof(ret->name));
temp = M_CompareXmlProp (root, "orient", xmlorient, 2);
if (temp != -1)
ret->orient = temp;
temp = M_CompareXmlProp (root, "align", xmlalign, 5);
if (temp != -1)
ret->align = temp;
temp = M_CompareXmlProp (root, "pack", xmlpack, 3);
if (temp != -1)
ret->pack = temp;
#if 0
for (temp=0;temp<sp;temp++)
Con_Printf(" ");
Con_Printf("%s [%d:%d] (%d,%d)\n",ret->tag, ret->align, ret->pack, ret->width.absolute,ret->height.absolute);
sp++;
#endif
/* load all the subnodes */
node = root->xmlChildrenNode;
while ( node != NULL ){
// comment nodes have node->name == NULL
if (!xmlIsBlankNode (node) && (node->type != XML_COMMENT_NODE)){
sub = M_LoadXmlElement (node);
if (sub)
M_InsertXmlWidget (ret, sub);
}
node = node->next;
}
#if 0
sp--;
#endif
// finally init the private data
ret->Load (ret, root);
return ret;
}
/*
================
M_LoadXmlMenu
Load a Menu(Window) definition from a file
================
*/
void M_LoadXmlWindowFile (const char *filename)
{
#if 0
// best method : lower resource need
// unfinished ^^;
int res, size;
xmlParserCtxtPtr ctxt;
FILE *f=NULL;
COM_FOpenFile(filename, &f);
// here set the elements callbacks
if (f != NULL) {
int res, size = 1024;
char chars[1024];
xmlParserCtxtPtr ctxt;
res = fread (chars, 1, 4, f);
if (res > 0) {
ctxt = xmlCreatePushParserCtxt (NULL, NULL,
chars, res, filename);
while ((res = fread (chars, 1, size, f)) > 0) {
xmlParseChunk (ctxt, chars, res, 0);
}
xmlParseChunk (ctxt, chars, 0, 1);
doc = ctxt->myDoc;
xmlFreeParserCtxt (ctxt);
}
}
#else
// easier method : memory heavier
char *buffer;
xmlDocPtr doc;
xmlNodePtr node;
int h, len;
Con_DPrintf ("XML : loading %s document started \n",filename);
// yuck - dirty filesize check
len = COM_OpenFile (filename, &h);
if (h == -1) {
Con_Printf ("Warning : %s document not found\n",filename);
return;
}
COM_CloseFile (h);
buffer = COM_LoadTempFile (filename);
doc = xmlParseMemory (buffer, len);
if (doc == NULL) {
Con_Printf ("Warning : %s document not found\n",filename);
return;
}
node = xmlDocGetRootElement (doc);
if (node == NULL) {
Con_Printf ("Warning : %s empty document\n",filename);
xmlFreeDoc (doc);
return;
}
if (!xmlIsBlankNode (node) && (node->name)){
if (!xmlStrcmp (node->name, (const xmlChar *)"window")) {
M_LoadXmlElement (node);
}
else {
Con_Printf ("Warning : %s document root isn't a window\n",filename);
}
}
xmlFreeDoc (doc);
#endif
Con_DPrintf ("XML : loading %s document ended \n",filename);
}
// -------------------------------------------------------------------------------------