2926 lines
62 KiB
C
2926 lines
62 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
|
|
#define MAX_MACCESSKEY 26
|
|
|
|
// -------------------------------------------------------------------------------------
|
|
|
|
cvar_t m_debug = {"m_debug","0"};
|
|
int m_mousex, m_mousey;
|
|
|
|
qmtable_t box_mtable =
|
|
{
|
|
M_LoadXmlBox, // Load
|
|
NULL, // Draw
|
|
NULL, // Focus
|
|
M_XmlBoxKey // HandleKey
|
|
};
|
|
qmtable_t label_mtable =
|
|
{
|
|
M_LoadXmlLabel, // Load
|
|
M_DrawXmlLabel, // Draw
|
|
NULL, // Focus
|
|
NULL // HandleKey
|
|
};
|
|
qmtable_t image_mtable =
|
|
{
|
|
M_LoadXmlImage, // Load
|
|
M_DrawXmlImage, // Draw
|
|
NULL, // Focus
|
|
NULL // HandleKey
|
|
};
|
|
qmtable_t button_mtable =
|
|
{
|
|
M_LoadXmlButton, // Load
|
|
M_DrawXmlButton, // Draw
|
|
NULL, // Focus
|
|
M_XmlButtonKey // HandleKey
|
|
};
|
|
qmtable_t checkbox_mtable =
|
|
{
|
|
M_LoadXmlCheckBox, // Load
|
|
M_DrawXmlCheckBox, // Draw
|
|
NULL, // Focus
|
|
M_XmlCheckBoxKey // HandleKey
|
|
};
|
|
qmtable_t radio_mtable =
|
|
{
|
|
M_LoadXmlRadio, // Load
|
|
M_DrawXmlRadio, // Draw
|
|
NULL, // Focus
|
|
M_XmlRadioKey // HandleKey
|
|
};
|
|
qmtable_t rgroup_mtable =
|
|
{
|
|
M_LoadXmlRadioGroup, // Load
|
|
M_DrawXmlRadioGroup, // Draw
|
|
NULL, // Focus
|
|
M_XmlRadioGroupKey // HandleKey
|
|
};
|
|
qmtable_t slider_mtable =
|
|
{
|
|
M_LoadXmlSlider, // Load
|
|
M_DrawXmlSlider, // Draw
|
|
NULL, // Focus
|
|
M_XmlSliderKey // HandleKey
|
|
};
|
|
qmtable_t edit_mtable =
|
|
{
|
|
M_LoadXmlEdit, // Load
|
|
M_DrawXmlEdit, // Draw
|
|
NULL, // Focus
|
|
M_XmlEditKey // HandleKey
|
|
};
|
|
qmtable_t window_mtable =
|
|
{
|
|
M_LoadXmlWindow, // Load
|
|
NULL, // Draw
|
|
NULL, // Focus
|
|
M_XmlWindowKey // HandleKey
|
|
};
|
|
|
|
/*
|
|
qmtable_t key_mtable =
|
|
{
|
|
M_LoadXmlKey,
|
|
NULL,
|
|
NULL,
|
|
NULL
|
|
};
|
|
|
|
qmtable_t broadcaster_mtable =
|
|
{
|
|
M_LoadXmlBroadcaster,
|
|
NULL,
|
|
NULL,
|
|
NULL
|
|
};
|
|
*/
|
|
|
|
// -------------------------------------------------------------------------------------
|
|
|
|
typedef struct xmlimagedata_s
|
|
{
|
|
char src[MAX_OSPATH];
|
|
shader_t *pic;
|
|
int width;
|
|
int height;
|
|
} xmlimagedata_t;
|
|
|
|
|
|
typedef struct xmlbuttondata_s
|
|
{
|
|
int isdown:1;
|
|
char label[MAX_MLABEL];
|
|
char command[255];
|
|
shader_t *pic;
|
|
} xmlbuttondata_t;
|
|
|
|
typedef struct xmlcheckboxdata_s
|
|
{
|
|
int checked:1;
|
|
char label[MAX_MLABEL];
|
|
} xmlcheckboxdata_t;
|
|
|
|
|
|
typedef struct xmlradiodata_s
|
|
{
|
|
int selected:1;
|
|
char label[MAX_MLABEL];
|
|
} xmlradiodata_t;
|
|
|
|
typedef struct xmllabeldata_s
|
|
{
|
|
qwidget_t *target;
|
|
char text[MAX_MLABEL];
|
|
} xmllabeldata_t;
|
|
|
|
typedef struct xmlsliderdata_s
|
|
{
|
|
int range;
|
|
int cursor;
|
|
char label[MAX_MLABEL];
|
|
} xmlsliderdata_t;
|
|
|
|
typedef struct xmleditdata_s
|
|
{
|
|
int cursor;
|
|
int length;
|
|
char text[MAX_MLABEL];
|
|
} xmleditdata_t;
|
|
|
|
typedef struct xmlradiogroupdata_s
|
|
{
|
|
char label[MAX_MLABEL];
|
|
} xmlradiogroupdata_t;
|
|
|
|
|
|
typedef struct xmlmenudata_s
|
|
{
|
|
int dummy;
|
|
} xmlmenudata_t;
|
|
|
|
/*
|
|
xml window data
|
|
*/
|
|
|
|
typedef struct refstring_s
|
|
{
|
|
int refcount;
|
|
char *str;
|
|
struct refstring_s *next;
|
|
} refstring_t;
|
|
|
|
typedef struct qaccesskey_s
|
|
{
|
|
int key;
|
|
qwidget_t *widget;
|
|
} qaccesskey_t;
|
|
|
|
typedef struct qwindow_s {
|
|
struct qwindow_s *next; // available window list
|
|
//char file[MAX_OSPATH];
|
|
qwidget_t *widget;
|
|
qwidget_t *focused;
|
|
refstring_t string_table;
|
|
int keycount;
|
|
qaccesskey_t keytable[MAX_MACCESSKEY];
|
|
struct qwindow_s *stack; // visible window stack
|
|
} qwindow_t;
|
|
|
|
|
|
char box_tag[] = "box";
|
|
char hbox_tag[] = "hbox";
|
|
char vbox_tag[] = "vbox";
|
|
char label_tag[] = "label";
|
|
char image_tag[] = "image";
|
|
char button_tag[] = "button";
|
|
char menuitem_tag[] = "menuitem";
|
|
char checkbox_tag[] = "checkbox";
|
|
char radio_tag[] = "radio";
|
|
char rgroup_tag[] = "radiogroup";
|
|
char slider_tag[] = "slider";
|
|
char edit_tag[] = "edit";
|
|
char window_tag[] = "window";
|
|
char package_tag[] = "package";
|
|
|
|
/*
|
|
xml primitives template
|
|
*/
|
|
|
|
#define DEF_STR_VALUE NULL
|
|
|
|
qwidget_t box_template =
|
|
{
|
|
&box_mtable, // mtable
|
|
DEF_STR_VALUE, // name
|
|
DEF_STR_VALUE, // id
|
|
box_tag, // tag
|
|
0, // num_children
|
|
NULL, // parent
|
|
NULL, // previous
|
|
NULL, // next
|
|
NULL, // children
|
|
NULL, // rchildren
|
|
0, // debug
|
|
1, // enabled
|
|
0, // focusable
|
|
0, // orient = horizontal
|
|
a_stretch, // align
|
|
p_start, // pack
|
|
0, // xpos
|
|
0, // ypos
|
|
{0,0}, // width
|
|
{0,0}, // height
|
|
0, // accesskey
|
|
NULL, // onCommand
|
|
NULL, // onMouseOver
|
|
NULL, // onMouseDown
|
|
NULL, // onMouseUp
|
|
NULL // data
|
|
};
|
|
|
|
qwidget_t vbox_template =
|
|
{
|
|
&box_mtable, // mtable
|
|
DEF_STR_VALUE, // name
|
|
DEF_STR_VALUE, // id
|
|
vbox_tag, // tag
|
|
0, // num_children
|
|
NULL, // parent
|
|
NULL, // previous
|
|
NULL, // next
|
|
NULL, // children
|
|
NULL, // rchildren
|
|
0, // debug
|
|
1, // enabled
|
|
0, // focusable
|
|
1, // orient = vertical
|
|
a_stretch, // align
|
|
p_start, // pack
|
|
0, // xpos
|
|
0, // ypos
|
|
{0,0}, // width
|
|
{0,0}, // height
|
|
0, // accesskey
|
|
NULL, // onCommand
|
|
NULL, // onMouseOver
|
|
NULL, // onMouseDown
|
|
NULL, // onMouseUp
|
|
NULL // data
|
|
};
|
|
|
|
qwidget_t label_template =
|
|
{
|
|
&label_mtable, // mtable
|
|
DEF_STR_VALUE, // name
|
|
DEF_STR_VALUE, // id
|
|
label_tag, // tag
|
|
0, // num_children
|
|
NULL, // parent
|
|
NULL, // previous
|
|
NULL, // next
|
|
NULL, // children
|
|
NULL, // rchildren
|
|
0, // debug
|
|
1, // enabled
|
|
0, // focusable
|
|
0, // orient
|
|
a_stretch, // align
|
|
p_start, // pack
|
|
0, // xpos
|
|
0, // ypos
|
|
{0,0}, // width
|
|
{0,0}, // height
|
|
0, // accesskey
|
|
NULL, // onCommand
|
|
NULL, // onMouseOver
|
|
NULL, // onMouseDown
|
|
NULL, // onMouseUp
|
|
NULL // data
|
|
};
|
|
|
|
qwidget_t image_template =
|
|
{
|
|
&image_mtable, // mtable
|
|
DEF_STR_VALUE, // name
|
|
DEF_STR_VALUE, // id
|
|
image_tag, // tag
|
|
0, // num_children
|
|
NULL, // parent
|
|
NULL, // previous
|
|
NULL, // next
|
|
NULL, // children
|
|
NULL, // rchildren
|
|
0, // debug
|
|
1, // enabled
|
|
0, // focusable
|
|
0, // orient
|
|
a_stretch, // align
|
|
p_start, // pack
|
|
0, // xpos
|
|
0, // ypos
|
|
{0,0}, // width
|
|
{0,0}, // height
|
|
0, // accesskey
|
|
NULL, // onCommand
|
|
NULL, // onMouseOver
|
|
NULL, // onMouseDown
|
|
NULL, // onMouseUp
|
|
NULL // data
|
|
};
|
|
|
|
qwidget_t button_template =
|
|
{
|
|
&button_mtable,// mtable
|
|
DEF_STR_VALUE, // name
|
|
DEF_STR_VALUE, // id
|
|
button_tag, // tag
|
|
0, // num_children
|
|
NULL, // parent
|
|
NULL, // previous
|
|
NULL, // next
|
|
NULL, // children
|
|
NULL, // rchildren
|
|
0, // debug
|
|
1, // enabled
|
|
1, // focusable
|
|
0, // orient
|
|
a_stretch, // align
|
|
p_start, // pack
|
|
0, // xpos
|
|
0, // ypos
|
|
{0,0}, // width
|
|
{0,0}, // height
|
|
0, // accesskey
|
|
NULL, // onCommand
|
|
NULL, // onMouseOver
|
|
NULL, // onMouseDown
|
|
NULL, // onMouseUp
|
|
NULL // data
|
|
};
|
|
|
|
qwidget_t checkbox_template =
|
|
{
|
|
&checkbox_mtable, // mtable
|
|
DEF_STR_VALUE, // name
|
|
DEF_STR_VALUE, // id
|
|
checkbox_tag, // tag
|
|
0, // num_children
|
|
NULL, // parent
|
|
NULL, // previous
|
|
NULL, // next
|
|
NULL, // children
|
|
NULL, // rchildren
|
|
0, // debug
|
|
1, // enabled
|
|
1, // focusable
|
|
0, // orient
|
|
a_stretch, // align
|
|
p_start, // pack
|
|
0, // xpos
|
|
0, // ypos
|
|
{0,0}, // width
|
|
{0,0}, // height
|
|
0, // accesskey
|
|
NULL, // onCommand
|
|
NULL, // onMouseOver
|
|
NULL, // onMouseDown
|
|
NULL, // onMouseUp
|
|
NULL // data
|
|
};
|
|
|
|
qwidget_t radio_template =
|
|
{
|
|
&radio_mtable, // mtable
|
|
DEF_STR_VALUE, // name
|
|
DEF_STR_VALUE, // id
|
|
radio_tag, // tag
|
|
0, // num_children
|
|
NULL, // parent
|
|
NULL, // previous
|
|
NULL, // next
|
|
NULL, // children
|
|
NULL, // rchildren
|
|
0, // debug
|
|
1, // enabled
|
|
1, // focusable
|
|
0, // orient
|
|
a_stretch, // align
|
|
p_start, // pack
|
|
0, // xpos
|
|
0, // ypos
|
|
{0,0}, // width
|
|
{0,0}, // height
|
|
0, // accesskey
|
|
NULL, // onCommand
|
|
NULL, // onMouseOver
|
|
NULL, // onMouseDown
|
|
NULL, // onMouseUp
|
|
NULL // data
|
|
};
|
|
|
|
qwidget_t rgroup_template =
|
|
{
|
|
&rgroup_mtable, // mtable
|
|
DEF_STR_VALUE, // name
|
|
DEF_STR_VALUE, // id
|
|
rgroup_tag, // tag
|
|
0, // num_children
|
|
NULL, // parent
|
|
NULL, // previous
|
|
NULL, // next
|
|
NULL, // children
|
|
NULL, // rchildren
|
|
0, // debug
|
|
1, // enabled
|
|
1, // focusable
|
|
1, // orient = vertical
|
|
a_stretch, // align
|
|
p_start, // pack
|
|
0, // xpos
|
|
0, // ypos
|
|
{0,0}, // width
|
|
{0,0}, // height
|
|
0, // accesskey
|
|
NULL, // onCommand
|
|
NULL, // onMouseOver
|
|
NULL, // onMouseDown
|
|
NULL, // onMouseUp
|
|
NULL // data
|
|
};
|
|
|
|
qwidget_t slider_template =
|
|
{
|
|
&slider_mtable, // mtable
|
|
DEF_STR_VALUE, // name
|
|
DEF_STR_VALUE, // id
|
|
slider_tag, // tag
|
|
0, // num_children
|
|
NULL, // parent
|
|
NULL, // previous
|
|
NULL, // next
|
|
NULL, // children
|
|
NULL, // rchildren
|
|
0, // debug
|
|
1, // enabled
|
|
1, // focusable
|
|
0, // orient
|
|
a_stretch, // align
|
|
p_start, // pack
|
|
0, // xpos
|
|
0, // ypos
|
|
{0,0}, // width
|
|
{0,0}, // height
|
|
0, // accesskey
|
|
NULL, // onCommand
|
|
NULL, // onMouseOver
|
|
NULL, // onMouseDown
|
|
NULL, // onMouseUp
|
|
NULL // data
|
|
};
|
|
|
|
qwidget_t edit_template =
|
|
{
|
|
&edit_mtable, // mtable
|
|
DEF_STR_VALUE, // name
|
|
DEF_STR_VALUE, // id
|
|
edit_tag, // tag
|
|
0, // num_children
|
|
NULL, // parent
|
|
NULL, // previous
|
|
NULL, // next
|
|
NULL, // children
|
|
NULL, // rchildren
|
|
0, // debug
|
|
1, // enabled
|
|
1, // focusable
|
|
0, // orient
|
|
a_stretch, // align
|
|
p_start, // pack
|
|
0, // xpos
|
|
0, // ypos
|
|
{0,0}, // width
|
|
{0,0}, // height
|
|
0, // accesskey
|
|
NULL, // onCommand
|
|
NULL, // onMouseOver
|
|
NULL, // onMouseDown
|
|
NULL, // onMouseUp
|
|
NULL // data
|
|
};
|
|
|
|
qwidget_t window_template =
|
|
{
|
|
&window_mtable, // mtable
|
|
DEF_STR_VALUE, // name
|
|
DEF_STR_VALUE, // id
|
|
window_tag, // tag
|
|
0, // num_children
|
|
NULL, // parent
|
|
NULL, // previous
|
|
NULL, // next
|
|
NULL, // children
|
|
NULL, // rchildren
|
|
0, // debug
|
|
1, // enabled
|
|
0, // focusable
|
|
0, // orient
|
|
a_stretch, // align
|
|
p_start, // pack
|
|
0, // xpos
|
|
0, // ypos
|
|
{0,0}, // width
|
|
{0,0}, // height
|
|
0, // accesskey
|
|
NULL, // onCommand
|
|
NULL, // onMouseOver
|
|
NULL, // onMouseDown
|
|
NULL, // onMouseUp
|
|
NULL // data
|
|
};
|
|
|
|
typedef struct xmlhandlers_s {
|
|
char *tag;
|
|
void *template;
|
|
int templatesize;
|
|
int datasize;
|
|
} xmlhandler_t;
|
|
|
|
xmlhandler_t widgethandlers[] =
|
|
{
|
|
{
|
|
box_tag,
|
|
&box_template,
|
|
sizeof(qwidget_t),
|
|
0
|
|
},
|
|
{
|
|
hbox_tag,
|
|
&box_template,
|
|
sizeof(qwidget_t),
|
|
0
|
|
},
|
|
{
|
|
vbox_tag,
|
|
&vbox_template,
|
|
sizeof(qwidget_t),
|
|
0
|
|
},
|
|
{
|
|
label_tag,
|
|
&label_template,
|
|
sizeof(qwidget_t),
|
|
sizeof(xmllabeldata_t)
|
|
},
|
|
{
|
|
image_tag,
|
|
&image_template,
|
|
sizeof(qwidget_t),
|
|
sizeof(xmlimagedata_t)
|
|
},
|
|
{
|
|
button_tag,
|
|
&button_template,
|
|
sizeof(qwidget_t),
|
|
sizeof(xmlbuttondata_t)
|
|
},
|
|
{
|
|
menuitem_tag,
|
|
&button_template,
|
|
sizeof(qwidget_t),
|
|
sizeof(xmlbuttondata_t)
|
|
},
|
|
{
|
|
checkbox_tag,
|
|
&checkbox_template,
|
|
sizeof(qwidget_t),
|
|
sizeof(xmlcheckboxdata_t)
|
|
},
|
|
{
|
|
radio_tag,
|
|
&radio_template,
|
|
sizeof(qwidget_t),
|
|
sizeof(xmlradiodata_t)
|
|
},
|
|
{
|
|
rgroup_tag,
|
|
&rgroup_template,
|
|
sizeof(qwidget_t),
|
|
sizeof(xmlradiogroupdata_t)
|
|
},
|
|
{
|
|
slider_tag,
|
|
&slider_template,
|
|
sizeof(qwidget_t),
|
|
sizeof(xmlsliderdata_t)
|
|
},
|
|
{
|
|
edit_tag,
|
|
&edit_template,
|
|
sizeof(qwidget_t),
|
|
sizeof(xmleditdata_t)
|
|
},
|
|
{
|
|
window_tag,
|
|
&window_template,
|
|
sizeof(qwidget_t),
|
|
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 *self, int xpos, int ypos);
|
|
|
|
|
|
// -------------------------------------------------------------------------------------
|
|
/*
|
|
window_stack : holds all instances of qwindow_t
|
|
*/
|
|
|
|
/*
|
|
static struct qwindow_dummy_t {
|
|
qwindow_t *next;
|
|
} w_dummy = { NULL };
|
|
*/
|
|
|
|
qwindow_t *windows_stack = NULL;
|
|
|
|
|
|
|
|
void M_AddWindowInList (qwindow_t *window)
|
|
{
|
|
window->next = windows_stack;
|
|
windows_stack = window;
|
|
}
|
|
|
|
qwindow_t * M_RemoveWindowInList (char *name)
|
|
{
|
|
qwindow_t **w = &windows_stack;
|
|
qwindow_t *ret;
|
|
|
|
while (*w!=NULL && !strcmp (name,(*w)->widget->name))
|
|
w = &(*w)->next;
|
|
if (!*w)
|
|
return NULL;
|
|
ret = *w;
|
|
*w = (*w)->next;
|
|
return ret;
|
|
}
|
|
|
|
qwindow_t *loading;
|
|
|
|
qwindow_t *visible_window;
|
|
|
|
shader_t *focus_shader;
|
|
|
|
// -------------------------------------------------------------------------------------
|
|
|
|
/* object name table */
|
|
|
|
char *M_CreateRefString (char *str, int len)
|
|
{
|
|
refstring_t *ptr = &(loading->string_table);
|
|
|
|
while (ptr->next) {
|
|
if (!Q_strcmp (ptr->str,str)) {
|
|
ptr->refcount++;
|
|
return ptr->str;
|
|
}
|
|
if (ptr->next == NULL)
|
|
break;
|
|
ptr = ptr->next;
|
|
}
|
|
|
|
ptr->next = Z_Malloc (sizeof (refstring_t));
|
|
ptr = ptr->next;
|
|
ptr->next = NULL;
|
|
ptr->refcount = 1;
|
|
ptr->str = Z_Malloc (len+1);
|
|
strncpy (ptr->str, str, len);
|
|
ptr->str[len] = '\0';
|
|
return ptr->str;
|
|
}
|
|
|
|
void M_DeleteRefString (char *str, int len)
|
|
{
|
|
refstring_t *ptr = &(loading->string_table);
|
|
while (ptr->next != NULL) {
|
|
if (!Q_strcmp (ptr->next->str,str)) {
|
|
refstring_t *next = ptr->next;
|
|
next->refcount--;
|
|
if (next->refcount == 0) {
|
|
ptr->next = next->next;
|
|
Z_Free (ptr->str);
|
|
Z_Free (ptr);
|
|
}
|
|
return;
|
|
}
|
|
else
|
|
ptr = ptr->next;
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------
|
|
|
|
/* shortcuts */
|
|
|
|
void M_AddAccessKey (qwidget_t *self, qwindow_t *window)
|
|
{
|
|
//qwidget_t *self=ptr;
|
|
if (!self->accesskey)
|
|
return;
|
|
if (window->keycount >= MAX_MACCESSKEY) {
|
|
Con_Printf("warning : accesskey table maximum reached for %s",window->widget->id);
|
|
return;
|
|
}
|
|
window->keytable[window->keycount].key = self->accesskey;
|
|
window->keytable[window->keycount++].widget = self;
|
|
}
|
|
|
|
void M_ReadAccessKeys (qwidget_t *self, qwindow_t *root)
|
|
{
|
|
qwidget_t *w;
|
|
|
|
M_AddAccessKey (self, root);
|
|
for (w = self->children; w != NULL; w = w->next)
|
|
M_ReadAccessKeys (w, root);
|
|
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------
|
|
|
|
/*
|
|
struct qmattrptr_s
|
|
{
|
|
unsigned int attr_offset;
|
|
unsigned int attr_size;
|
|
unsigned int mask;
|
|
};
|
|
|
|
struct qmobserver_s
|
|
{
|
|
qwidget *owner;
|
|
char *broadcaster;
|
|
char *attribute;
|
|
};
|
|
|
|
struct qmbroadcaster_s
|
|
{
|
|
qwidget_t widget;
|
|
|
|
}
|
|
*/
|
|
|
|
// -------------------------------------------------------------------------------------
|
|
|
|
|
|
/*
|
|
typedef struct m_iterator_s
|
|
{
|
|
qmelement_t *w;
|
|
void (*next_f)(struct eliterator_s *self);
|
|
} m_iterator_t;
|
|
|
|
void iterator_next (m_iterator_t *self)
|
|
{
|
|
qmelement_t *w = self->w;
|
|
if (w->next)
|
|
self->w = w->next;
|
|
else
|
|
self->w = NULL;
|
|
}
|
|
|
|
void iterator_prev (m_iterator_t *self)
|
|
{
|
|
qmelement_t *w = self->w;
|
|
if (w->previous)
|
|
self->w = w->previous;
|
|
else
|
|
self->w = NULL;
|
|
}
|
|
|
|
void treeiterator_next (m_iterator_t *self)
|
|
{
|
|
qmelement_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 (m_iterator_t *self)
|
|
{
|
|
qmelement_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
|
|
#define SLIDER_START_CHAR 128
|
|
#define SLIDER_CHAR 129
|
|
#define SLIDER_END_CHAR 131
|
|
|
|
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];
|
|
|
|
enum {m_none, m_menu } m_state;
|
|
|
|
void M_SetFocus(qwindow_t *w, qwidget_t *newFocus) {
|
|
|
|
//notify widget of lost focus
|
|
if (w->focused->mtable->Focus) {
|
|
w->focused->mtable->Focus(w->focused);
|
|
}
|
|
|
|
//make new widged focused
|
|
w->focused = newFocus;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------
|
|
//
|
|
// Menu action script commands, these are all normal console commands but are specially
|
|
// aimed to be used in menu actions.
|
|
//
|
|
// -------------------------------------------------------------------------------------
|
|
|
|
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;
|
|
|
|
//release focus for this window
|
|
M_SetFocus(visible_window, NULL);
|
|
|
|
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_stack;
|
|
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, strlen(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_stack;
|
|
while (w){
|
|
Con_Printf ("%s\n", w->widget->id);
|
|
w = w->next;
|
|
}
|
|
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------
|
|
//
|
|
// Interface of the menus towards the rest of the game engine
|
|
//
|
|
// -------------------------------------------------------------------------------------
|
|
|
|
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
|
|
Con_Printf("=================================\n");
|
|
Con_Printf("M_Init: Initializing xml menu system\n");
|
|
COM_FindAllExt ("menu", "xul", M_LoadXmlWindowFile);
|
|
Con_Printf("=================================\n");
|
|
|
|
focus_shader = GL_ShaderForName ("menu/focus");
|
|
}
|
|
|
|
/*
|
|
========
|
|
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->mtable->HandleKey)
|
|
M_XmlElementKey (focused,key);
|
|
else // current window get the key
|
|
M_XmlElementKey (visible_window->widget, key);
|
|
}
|
|
|
|
void M_XmlMouseMove ( int x, int y );
|
|
void M_MouseMove ( int x, int y ) {
|
|
m_mousex = x;
|
|
m_mousey = y;
|
|
M_XmlMouseMove (x, y);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------
|
|
// STATUS BAR CODE STUB
|
|
// -------------------------------------------------------------------------------------
|
|
|
|
int sb_lines;
|
|
qwindow_t *statusWindow = NULL;
|
|
qwindow_t *bigStatusWindow = NULL;
|
|
|
|
/*
|
|
========
|
|
Sbar_Changed
|
|
|
|
marks status bar to be updated next frame
|
|
========
|
|
*/
|
|
void Sbar_Changed (void)
|
|
{
|
|
|
|
}
|
|
/*
|
|
========
|
|
Sbar_Draw
|
|
|
|
render status bar
|
|
========
|
|
*/
|
|
void Sbar_Draw (void)
|
|
{
|
|
qwindow_t *oldviz = visible_window;
|
|
qwindow_t *wnd;
|
|
int w, h;
|
|
|
|
if (sb_lines <= 0) return;
|
|
|
|
if (sb_lines <= 24) {
|
|
if (!statusWindow) return;
|
|
wnd = statusWindow;
|
|
} else {
|
|
if (!bigStatusWindow) return;
|
|
wnd = bigStatusWindow;
|
|
}
|
|
|
|
visible_window = wnd;
|
|
|
|
w = vid.width - wnd->widget->width.absolute;
|
|
h = sb_lines - wnd->widget->height.absolute;
|
|
M_DrawXmlNestedWidget (wnd->widget, w/2, (vid.height - sb_lines) + h/2);
|
|
|
|
visible_window = oldviz;
|
|
}
|
|
/*
|
|
========
|
|
Sbar_FinaleOverlay
|
|
|
|
end of game panel
|
|
========
|
|
*/
|
|
void Sbar_FinaleOverlay (void)
|
|
{
|
|
}
|
|
/*
|
|
========
|
|
Sbar_Init
|
|
|
|
setup data, ccommands and cvars related to status bar
|
|
========
|
|
*/
|
|
void Sbar_Init (void)
|
|
{
|
|
qwindow_t *w = windows_stack;
|
|
while (w){
|
|
if (!strncmp (w->widget->id, "statusbar", strlen(w->widget->id)))
|
|
break;
|
|
w = w->next;
|
|
}
|
|
if (!w) {
|
|
Con_Printf ("Sbar_Init :no statusbar defined\n");
|
|
} else {
|
|
statusWindow = w;
|
|
}
|
|
|
|
w = windows_stack;
|
|
while (w){
|
|
if (!strncmp (w->widget->id, "bigstatusbar", strlen(w->widget->id)))
|
|
break;
|
|
w = w->next;
|
|
}
|
|
if (!w) {
|
|
Con_Printf ("Sbar_Init :no bigstatusbar defined\n");
|
|
} else {
|
|
bigStatusWindow = w;
|
|
}
|
|
}
|
|
/*
|
|
========
|
|
Sbar_IntermissionOverlay
|
|
|
|
end of level statistics box
|
|
========
|
|
*/
|
|
void Sbar_IntermissionOverlay (void)
|
|
{
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------
|
|
|
|
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_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 ();
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------
|
|
//
|
|
// Menu event handling, code that handles key presses and mouse moves for the different
|
|
// widgets.
|
|
//
|
|
// -------------------------------------------------------------------------------------
|
|
|
|
void M_GetSaveDescription(const char *filename, char *buff, int len) {
|
|
|
|
char name[MAX_OSPATH];
|
|
int version;
|
|
FILE *f;
|
|
|
|
strncpy (buff, "--- UNUSED SLOT ---", len-1);
|
|
|
|
sprintf (name, "%s%s.sav", com_gamedir, filename);
|
|
f = fopen (name, "r");
|
|
if (!f)
|
|
return;
|
|
fscanf (f, "%i\n", &version);
|
|
fscanf (f, "%79s\n", name);
|
|
strncpy (buff, name, len-1);
|
|
|
|
fclose(f);
|
|
}
|
|
|
|
qboolean M_IsSaveLoadable(const char *filename) {
|
|
|
|
char name[MAX_OSPATH];
|
|
FILE *f;
|
|
|
|
sprintf (name, "%s%s.sav", com_gamedir, filename);
|
|
f = fopen (name, "r");
|
|
if (!f)
|
|
return false;
|
|
fclose(f);
|
|
return true;
|
|
}
|
|
|
|
void M_XmlButtonCommand (qwidget_t *self)
|
|
{
|
|
// qwidget_t *self = ptr;
|
|
xmlbuttondata_t *data = self->data;
|
|
Cbuf_AddText (data->command);
|
|
Cbuf_AddText ("\n");
|
|
}
|
|
|
|
qmelement_t *M_NextQMElement (qmelement_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 NULL;
|
|
return w->next;
|
|
}
|
|
|
|
qmelement_t *M_PreviousQMElement (qmelement_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 NULL;
|
|
return w->previous;
|
|
}
|
|
|
|
qboolean M_IsFocusable (qmelement_t *e)
|
|
{
|
|
return e->focusable && e->enabled;
|
|
}
|
|
|
|
void M_CycleFocus (qmelement_t *(*next_f)(qmelement_t *))
|
|
{
|
|
qmelement_t *w = (qmelement_t *)visible_window->focused;
|
|
qmelement_t *start = w;
|
|
do {
|
|
w = next_f (w);
|
|
if (w == NULL)
|
|
w = (qmelement_t *)visible_window->widget;
|
|
} while ((w != start) && !M_IsFocusable (w));
|
|
M_SetFocus(visible_window, (qwidget_t *)w);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------
|
|
|
|
qboolean M_XmlBoxKey (qwidget_t *self, int k)
|
|
{
|
|
// qwidget_t *self = ptr;
|
|
|
|
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)
|
|
{
|
|
// qwidget_t *self = ptr;
|
|
xmlcheckboxdata_t *data = self->data;
|
|
switch (k)
|
|
{
|
|
case K_ENTER:
|
|
case K_MOUSE1:
|
|
// (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)
|
|
{
|
|
// qwidget_t *self = ptr;
|
|
switch (k)
|
|
{
|
|
case K_ENTER:
|
|
case K_MOUSE1:
|
|
//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:
|
|
case K_MOUSE1:
|
|
// 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)
|
|
{
|
|
// qwidget_t *self = ptr;
|
|
xmlsliderdata_t *data = self->data;
|
|
switch (k)
|
|
{
|
|
case K_LEFTARROW:
|
|
// reduce cvar value
|
|
//S_LocalSound ("misc/menu3.wav");
|
|
data->cursor -= 1/SLIDER_RANGE;
|
|
break;
|
|
case K_RIGHTARROW:
|
|
// augment cvar value
|
|
//S_LocalSound ("misc/menu3.wav");
|
|
data->cursor += 1/SLIDER_RANGE;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
qboolean M_XmlEditKey (qwidget_t *self, int k)
|
|
{
|
|
// qwidget_t *self = ptr;
|
|
xmleditdata_t *data = self->data;
|
|
int textlen = strlen(data->text);
|
|
switch (k)
|
|
{
|
|
case K_LEFTARROW:
|
|
// reduce cvar value
|
|
//S_LocalSound ("misc/menu3.wav");
|
|
if (data->cursor > 0)
|
|
data->cursor -= 1;
|
|
break;
|
|
case K_RIGHTARROW:
|
|
// augment cvar value
|
|
//S_LocalSound ("misc/menu3.wav");
|
|
if (data->cursor < textlen)
|
|
data->cursor += 1;
|
|
break;
|
|
case K_BACKSPACE:
|
|
if (data->cursor > 0) {
|
|
data->text[data->cursor-1] = 0;
|
|
data->cursor -= 1;
|
|
}
|
|
Cvar_Set (self->id, data->text);
|
|
break;
|
|
default:
|
|
if ((k >= 32) && (k <= 126)) {
|
|
data->text[data->cursor] = k;
|
|
if (data->cursor >= textlen) {
|
|
data->text[data->cursor+1] = 0; //null terminator was overwritten
|
|
}
|
|
data->cursor += 1;
|
|
Cvar_Set (self->id, data->text);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
qboolean M_XmlWindowKey (qwidget_t *self, int k)
|
|
{
|
|
// qwidget_t *self = ptr;
|
|
qwindow_t *data = self->data;
|
|
int i;
|
|
if (M_XmlBoxKey (self,k))
|
|
return true;
|
|
for (i=0; i<data->keycount; ++i)
|
|
if (data->keytable[i].key==k) {
|
|
M_SetFocus(data, data->keytable[i].widget);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
qboolean M_XmlMenuKey (qwidget_t *self, int k)
|
|
{
|
|
return M_XmlBoxKey (self,k);
|
|
}
|
|
|
|
qboolean M_XmlElementKey (qwidget_t *self, int k)
|
|
{
|
|
// start from widget
|
|
// and propagate the key up the parents tree
|
|
// until it has been consumed or it reaches the
|
|
// tree root
|
|
//qmelement_t *widget = ptr;
|
|
while (self){
|
|
if (self->mtable->HandleKey)
|
|
if (self->mtable->HandleKey (self, k))
|
|
return true;
|
|
self = self->parent;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
qboolean M_XmlMouseMove_R (qwidget_t *self, int xpos, int ypos, int mouse_x, int mouse_y )
|
|
{
|
|
qwidget_t *w;
|
|
|
|
//mouse is not in this screen
|
|
if ((mouse_x < xpos) || (mouse_x > xpos+self->width.absolute) ||
|
|
(mouse_y < ypos) || (mouse_y > ypos+self->height.absolute)) {
|
|
return false;
|
|
}
|
|
|
|
//No cildren and cursor is in bounds make this the focused element
|
|
if (self->children == NULL) {
|
|
if (M_IsFocusable ((qmelement_t *)self)) {
|
|
M_SetFocus(visible_window, self);
|
|
return true;
|
|
} else
|
|
return false;
|
|
}
|
|
|
|
// vertical orientation
|
|
if (self->orient) {
|
|
int wwidth;
|
|
//ypos += widget->yoffset;
|
|
switch (self->pack){
|
|
case p_start:
|
|
for (w = self->children; w != NULL; w = w->next){
|
|
if (M_XmlMouseMove_R (w, xpos, ypos, mouse_x, mouse_y)) return true;
|
|
ypos += w->height.absolute;
|
|
}
|
|
break;
|
|
case p_center:
|
|
for (w = self->children; w != NULL; w = w->next){
|
|
wwidth = self->width.absolute - w->width.absolute;
|
|
if (M_XmlMouseMove_R (w, xpos+wwidth/2, ypos, mouse_x, mouse_y)) return true;
|
|
ypos += w->height.absolute;
|
|
}
|
|
break;
|
|
case p_end:
|
|
for (w = self->children; w != NULL; w = w->next){
|
|
wwidth = self->width.absolute - w->width.absolute;
|
|
if (M_XmlMouseMove_R (w, xpos+wwidth, ypos, mouse_x, mouse_y)) return true;
|
|
ypos += w->height.absolute;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
int wheight;
|
|
//xpos += self->xoffset;
|
|
switch (self->pack){
|
|
case p_start:
|
|
for (w = self->children; w != NULL; w = w->next){
|
|
if (M_XmlMouseMove_R (w, xpos, ypos, mouse_x, mouse_y)) return true;
|
|
xpos += w->width.absolute;
|
|
}
|
|
break;
|
|
case p_center:
|
|
for (w = self->children; w != NULL; w = w->next){
|
|
wheight = - w->height.absolute + self->height.absolute;
|
|
if (M_XmlMouseMove_R (w, xpos, ypos+wheight/2, mouse_x, mouse_y)) return true;
|
|
xpos += w->width.absolute;
|
|
}
|
|
break;
|
|
case p_end:
|
|
for (w = self->children; w != NULL; w = w->next){
|
|
wheight = - w->height.absolute + self->height.absolute;
|
|
if (M_XmlMouseMove_R (w, xpos, ypos+wheight, mouse_x, mouse_y)) return true;
|
|
xpos += w->width.absolute;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
//None of the children could be selected so try ourselve
|
|
if (M_IsFocusable ((qmelement_t *)self)) {
|
|
M_SetFocus(visible_window, self);
|
|
return true;
|
|
} else
|
|
return false;
|
|
}
|
|
|
|
void M_XmlMouseMove ( int x, int y ) {
|
|
int w = vid.width - visible_window->widget->width.absolute;
|
|
int h = vid.height - visible_window->widget->height.absolute;
|
|
M_XmlMouseMove_R (visible_window->widget, w/2, h/2, x, y);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------
|
|
//
|
|
// Menu drawing code
|
|
//
|
|
// -------------------------------------------------------------------------------------
|
|
void M_DrawPic (int x1, int y1, int x2, int y2, shader_t *sh)
|
|
{
|
|
Draw_Pic (x1, y1, x2, y2, sh);
|
|
}
|
|
|
|
void M_DrawCharacter (int cx, int cy, char c)
|
|
{
|
|
Draw_Character(cx, cy, c);
|
|
}
|
|
|
|
void M_Print (int cx, int cy, char *str)
|
|
{
|
|
while (*str)
|
|
{
|
|
M_DrawCharacter (cx, cy, (*str));
|
|
str++;
|
|
cx += 8;
|
|
}
|
|
}
|
|
|
|
void M_PrintWhite (int cx, int cy, char *str)
|
|
{
|
|
while (*str)
|
|
{
|
|
M_DrawCharacter (cx, cy, *str);
|
|
str++;
|
|
cx += 8;
|
|
}
|
|
}
|
|
|
|
|
|
void M_DrawTransPic (int x, int y, qpic_t *pic)
|
|
{
|
|
//Draw_TransPic (x + ((vid.width - 320)>>1), y, pic);
|
|
}
|
|
|
|
/*
|
|
================
|
|
M_DrawString
|
|
|
|
draws the str string of length len in the (x1,y1,x2,y2) frame
|
|
================
|
|
*/
|
|
int M_DrawString (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)
|
|
{
|
|
M_DrawCharacter (x, y, (*str)+128);
|
|
str++;
|
|
x += 8;
|
|
len--;
|
|
}
|
|
return x;
|
|
}
|
|
|
|
/*
|
|
================
|
|
M_Draw
|
|
|
|
called each frame if there's a menu to render
|
|
================
|
|
*/
|
|
|
|
void M_Draw (void)
|
|
{
|
|
usercmd_t dummy;
|
|
|
|
if (m_state == m_none || key_dest != key_menu)
|
|
return;
|
|
|
|
if (!m_recursiveDraw)
|
|
{
|
|
scr_copyeverything = 1;
|
|
|
|
if (scr_con_current)
|
|
{
|
|
Draw_ConsoleBackground (vid.height);
|
|
S_ExtraUpdate ();
|
|
}
|
|
else
|
|
Draw_FadeScreen ();
|
|
|
|
scr_fullupdate = 0;
|
|
}
|
|
else
|
|
{
|
|
m_recursiveDraw = false;
|
|
}
|
|
|
|
// draw current window
|
|
M_DrawVisibleWindow ();
|
|
|
|
//Draw a mouse pointer
|
|
M_DrawCharacter(m_mousex-4, m_mousey-8, '+');
|
|
|
|
if (m_entersound)
|
|
{
|
|
//S_LocalSound ("misc/menu2.wav");
|
|
m_entersound = false;
|
|
}
|
|
|
|
S_ExtraUpdate ();
|
|
}
|
|
|
|
/*
|
|
================
|
|
M_DrawXml*
|
|
|
|
handlers taking care of the rendering of each widget available
|
|
These functions are called for _each_ frame (as some widget may
|
|
change from frame to frame, depending of what they contain : shaders,
|
|
models etc...)
|
|
So the idea here is to avoid recomputing things if possible.
|
|
for ex. : width/height are calculated at loading time
|
|
(FIXME : or only when a widget size changes)
|
|
================
|
|
*/
|
|
|
|
|
|
void M_DrawXmlButton (qwidget_t *self, int x, int y)
|
|
{
|
|
// qwidget_t *self = ptr;
|
|
xmlbuttondata_t *data = self->data;
|
|
if (data->pic) {
|
|
M_DrawPic (x, y, x+self->width.absolute, y+self->height.absolute, data->pic);
|
|
}
|
|
M_DrawString (x, y, x+self->width.absolute, y+self->height.absolute, data->label, /*sizeof(data->label)*/ strlen(data->label) );
|
|
}
|
|
|
|
void M_DrawXmlImage (qwidget_t *self, int x, int y)
|
|
{
|
|
// qwidget_t *self = ptr;
|
|
xmlimagedata_t *data = self->data;
|
|
if (!data->pic)
|
|
data->pic = GL_ShaderForName (data->src);
|
|
if (data->pic)
|
|
M_DrawPic (x, y, x+self->width.absolute, y+self->height.absolute, data->pic);
|
|
}
|
|
|
|
void M_DrawXmlLabel (qwidget_t *self, int x, int y)
|
|
{
|
|
// qwidget_t *self = ptr;
|
|
xmllabeldata_t *data = self->data;
|
|
M_DrawString (x, y, x+self->width.absolute, y+self->height.absolute, data->text, strlen(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)
|
|
{
|
|
// qwidget_t *self = ptr;
|
|
xmlradiodata_t *data = self->data;
|
|
qboolean on = data->selected;
|
|
M_DrawString (x, y, x+self->width.absolute, y+self->height.absolute, data->label, strlen(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;
|
|
// qwidget_t *self = ptr;
|
|
xmlsliderdata_t *data = self->data;
|
|
int cursor = data->cursor;
|
|
|
|
if (cursor < 0)
|
|
cursor = 0;
|
|
if (cursor > 1)
|
|
cursor = 1;
|
|
|
|
M_DrawCharacter (x, y, '<');
|
|
x += 8;
|
|
for (i=0 ; i<SLIDER_RANGE ; i++)
|
|
M_DrawCharacter (x + i*8, y, '=');
|
|
M_DrawCharacter (x+i*8, y, '>');
|
|
M_DrawCharacter (x + (SLIDER_RANGE-1)*8 * cursor, y, '#');
|
|
}
|
|
|
|
void M_DrawXmlEdit (qwidget_t *self, int x, int y)
|
|
{
|
|
int i;
|
|
// qwidget_t *self = ptr;
|
|
xmleditdata_t *data = self->data;
|
|
int cursor = data->cursor;
|
|
char label[MAX_MLABEL];
|
|
|
|
if ((int)(realtime*4)&1 && self == visible_window->focused) {
|
|
strcpy(label, data->text);
|
|
if (cursor >= strlen(label)) {
|
|
label[cursor+1] = 0;
|
|
}
|
|
label[cursor] = '#';
|
|
M_DrawString (x, y, x+self->width.absolute, y+self->height.absolute, label, data->length );
|
|
} else {
|
|
M_DrawString (x, y, x+self->width.absolute, y+self->height.absolute, data->text, data->length );
|
|
}
|
|
}
|
|
|
|
// almost pasted from menu.c
|
|
|
|
void M_DrawXmlCheckBox (qwidget_t *self,int x, int y)
|
|
{
|
|
// qwidget_t *self = ptr;
|
|
xmlcheckboxdata_t *data = self->data;
|
|
qboolean on = data->checked;
|
|
|
|
x = M_DrawString (x, y, x+self->width.absolute, y+self->height.absolute, data->label, strlen(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)
|
|
{
|
|
// qwidget_t *self = ptr;
|
|
int xs = x+self->width.absolute;
|
|
int ys = y+self->height.absolute;
|
|
|
|
if (focus_shader == NULL)
|
|
focus_shader = GL_ShaderForName ("menu/focus");
|
|
|
|
/*
|
|
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_Border (x, y, xs, ys, 5, focus_shader);
|
|
/*
|
|
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)
|
|
{
|
|
// qwidget_t *self = ptr;
|
|
int xs = x+self->width.absolute;
|
|
int ys = x+self->height.absolute;
|
|
GLfloat color[4];
|
|
|
|
glGetFloatv(GL_CURRENT_COLOR, color);
|
|
glColor4f(1,1,1,1);
|
|
glDisable(GL_TEXTURE_2D);
|
|
glBegin (GL_LINE_STRIP);
|
|
glVertex2f (x, y);
|
|
glVertex2f (xs, y);
|
|
glVertex2f (xs, ys);
|
|
glVertex2f (x, ys);
|
|
glVertex2f (x, y);
|
|
glEnd ();
|
|
glColor4fv(color);
|
|
glEnable(GL_TEXTURE_2D);
|
|
}
|
|
|
|
/* -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 *self, int xpos, int ypos)
|
|
{
|
|
// all objects within the window tree have to be a widget
|
|
//qwidget_t *widget=ptr;
|
|
qwidget_t *w;
|
|
|
|
if (self->mtable->Draw){
|
|
self->mtable->Draw (self, xpos, ypos);
|
|
}
|
|
|
|
if (self == visible_window->focused){
|
|
M_DrawFocus (self, xpos, ypos);
|
|
}
|
|
|
|
if (m_debug.value) {
|
|
// draw outlines
|
|
M_DrawOutlines (self, xpos, ypos);
|
|
}
|
|
if (self->children == NULL)
|
|
return;
|
|
// vertical orientation
|
|
if (self->orient) {
|
|
int wwidth;
|
|
//ypos += widget->yoffset;
|
|
switch (self->pack){
|
|
case p_start:
|
|
for (w = self->children; w != NULL; w = w->next){
|
|
M_DrawXmlNestedWidget (w, xpos, ypos);
|
|
ypos += w->height.absolute;
|
|
}
|
|
break;
|
|
case p_center:
|
|
for (w = self->children; w != NULL; w = w->next){
|
|
wwidth = self->width.absolute - w->width.absolute;
|
|
M_DrawXmlNestedWidget (w, xpos+wwidth/2, ypos);
|
|
ypos += w->height.absolute;
|
|
}
|
|
break;
|
|
case p_end:
|
|
for (w = self->children; w != NULL; w = w->next){
|
|
wwidth = self->width.absolute - w->width.absolute;
|
|
M_DrawXmlNestedWidget (w, xpos+wwidth, ypos);
|
|
ypos += w->height.absolute;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
int wheight;
|
|
//xpos += self->xoffset;
|
|
switch (self->pack){
|
|
case p_start:
|
|
for (w = self->children; w != NULL; w = w->next){
|
|
M_DrawXmlNestedWidget (w, xpos, ypos);
|
|
xpos += w->width.absolute;
|
|
}
|
|
break;
|
|
case p_center:
|
|
for (w = self->children; w != NULL; w = w->next){
|
|
wheight = - w->height.absolute + self->height.absolute;
|
|
M_DrawXmlNestedWidget (w, xpos, ypos+wheight/2);
|
|
xpos += w->width.absolute;
|
|
}
|
|
break;
|
|
case p_end:
|
|
for (w = self->children; w != NULL; w = w->next){
|
|
wheight = - w->height.absolute + self->height.absolute;
|
|
M_DrawXmlNestedWidget (w, xpos, ypos+wheight);
|
|
xpos += w->width.absolute;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void M_DrawWindow (qwindow_t *wnd)
|
|
{
|
|
int w = vid.width - wnd->widget->width.absolute;
|
|
int h = vid.height - wnd->widget->height.absolute;
|
|
M_DrawXmlNestedWidget (wnd->widget, w/2, h/2);
|
|
}
|
|
|
|
void M_DrawVisibleWindow ()
|
|
{
|
|
M_DrawWindow(visible_window);
|
|
}
|
|
|
|
void M_DrawNamedWindow (const char *name)
|
|
{
|
|
qwindow_t *w = windows_stack;
|
|
qwindow_t *oldviz = visible_window;
|
|
while (w){
|
|
if (!strncmp (w->widget->id, name, strlen(w->widget->id)))
|
|
break;
|
|
w = w->next;
|
|
}
|
|
if (!w) {
|
|
Con_Printf ("M_DrawNamedWindow : no such window '%s'\n", name);
|
|
} else {
|
|
visible_window = w;
|
|
M_DrawWindow(w);
|
|
}
|
|
visible_window = oldviz;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------
|
|
//
|
|
// Menu script file parsing and initialization
|
|
//
|
|
// -------------------------------------------------------------------------------------
|
|
|
|
|
|
char empty_str[] = "";
|
|
#define EMPTY_STR empty_str
|
|
|
|
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_ReadXmlPropAsChar (xmlNodePtr node, char *name, int defvalue)
|
|
{
|
|
int ret;
|
|
int p;
|
|
xmlChar *value;
|
|
value = xmlGetProp (node, name);
|
|
if (value){
|
|
ret = (int)value[0];
|
|
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=EMPTY_STR;
|
|
|
|
return ret;
|
|
}
|
|
|
|
char *M_ReadXmlPropAsRefString (xmlNodePtr node, char *name)
|
|
{
|
|
xmlChar *value;
|
|
char *ret;
|
|
value = xmlGetProp (node, name);
|
|
if (value){
|
|
ret = M_CreateRefString (value, strlen(value));
|
|
xmlFree (value);
|
|
} else
|
|
ret=EMPTY_STR;
|
|
|
|
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_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);
|
|
}
|
|
|
|
void M_PrepareQWindow (qwidget_t *widget)
|
|
{
|
|
char command[255];
|
|
// 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 :
|
|
// let's initialize children dependent data
|
|
M_CalculateWidgetDim (widget);
|
|
|
|
/*
|
|
// set root on all sub-widget
|
|
w = widget;
|
|
while (w){
|
|
w->root = widget;
|
|
w = M_NextQMElement (w);
|
|
}
|
|
*/
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------
|
|
|
|
|
|
/*
|
|
================
|
|
M_LoadXml*
|
|
|
|
Load a Xml Element definition from the provided libxml tree pointer
|
|
================
|
|
*/
|
|
|
|
|
|
void M_LoadXmlBox (qwidget_t *self, xmlNodePtr node)
|
|
{
|
|
|
|
/*
|
|
* --- supported attributes :
|
|
* --- unsupported :
|
|
*/
|
|
|
|
|
|
}
|
|
|
|
void M_LoadXmlLabel (qwidget_t *self, xmlNodePtr node)
|
|
{
|
|
/*
|
|
* --- supported attributes :
|
|
|
|
disabled = bool
|
|
value = string : label text
|
|
|
|
* --- unsupported :
|
|
|
|
control = string : xul element id
|
|
accesskey = string
|
|
|
|
*/
|
|
// qwidget_t *self = ptr;
|
|
xmllabeldata_t *data = self->data;
|
|
M_ReadXmlPropAsString (node, "value", data->text, sizeof (data->text));
|
|
}
|
|
|
|
void M_LoadXmlImage (qwidget_t *self, xmlNodePtr node)
|
|
{
|
|
|
|
/*
|
|
* --- supported attributes :
|
|
|
|
src = URL
|
|
|
|
* --- unsupported :
|
|
|
|
onerror = string
|
|
onload = string
|
|
validate = {always|never} : load image from cache
|
|
|
|
*/
|
|
// qwidget_t *self = ptr;
|
|
xmlimagedata_t *data = self->data;
|
|
M_ReadXmlPropAsString (node, "src", data->src, sizeof (data->src));
|
|
//GL_LoadPic (data->src,&(data->pic));
|
|
}
|
|
|
|
|
|
void M_LoadXmlButton (qwidget_t *self, xmlNodePtr node)
|
|
{
|
|
/*
|
|
* --- supported attributes :
|
|
|
|
command = string
|
|
disabled = bool
|
|
label = string
|
|
accesskey = string : shortcut key
|
|
image = URL
|
|
|
|
* --- unsupported :
|
|
|
|
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
|
|
|
|
*/
|
|
// qwidget_t *self = ptr;
|
|
static char buffer[MAX_QPATH];
|
|
xmlbuttondata_t *data = self->data;
|
|
int temp;
|
|
|
|
M_ReadXmlPropAsString (node, "command", data->command, sizeof(data->command));
|
|
M_ReadXmlPropAsString (node, "label", data->label, sizeof(data->label));
|
|
|
|
buffer[0] = 0;
|
|
M_ReadXmlPropAsString (node, "image", buffer, sizeof(buffer));
|
|
|
|
if (buffer[0])
|
|
data->pic = GL_ShaderForName(buffer);
|
|
else
|
|
data->pic = NULL;
|
|
|
|
self->accesskey = M_ReadXmlPropAsChar (node, "accesskey", 0);
|
|
temp = M_CompareXmlProp (node, "disabled", xmlbool, 2);
|
|
if (temp != -1)
|
|
self->enabled = !temp;
|
|
|
|
}
|
|
|
|
void M_LoadXmlCheckBox (qwidget_t *self, xmlNodePtr node)
|
|
{
|
|
/*
|
|
* --- supported attributes :
|
|
|
|
disabled = bool
|
|
label = string
|
|
accesskey = string : shortcut key
|
|
|
|
* --- unsupported :
|
|
|
|
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
|
|
*/
|
|
// qwidget_t *self = ptr;
|
|
xmlcheckboxdata_t *data = self->data;
|
|
int temp;
|
|
|
|
M_ReadXmlPropAsString (node, "label", data->label, sizeof (data->label));
|
|
self->accesskey = M_ReadXmlPropAsChar (node, "accesskey", 0);
|
|
temp = M_CompareXmlProp (node, "disabled", xmlbool, 2);
|
|
if (temp != -1)
|
|
self->enabled = !temp;
|
|
data->checked = Cvar_VariableValue (self->id);
|
|
}
|
|
|
|
void M_LoadXmlRadio (qwidget_t *self, xmlNodePtr node)
|
|
{
|
|
/*
|
|
* --- supported attributes :
|
|
|
|
disabled = bool
|
|
label = string
|
|
selected = bool
|
|
accesskey = string : shortcut key
|
|
|
|
* --- unsupported :
|
|
|
|
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
|
|
*/
|
|
// qwidget_t *self = ptr;
|
|
xmlradiodata_t *data = self->data;
|
|
int temp;
|
|
|
|
M_ReadXmlPropAsString (node, "label", data->label, sizeof (data->label));
|
|
self->accesskey = M_ReadXmlPropAsChar (node, "accesskey", 0);
|
|
|
|
temp = M_CompareXmlProp (node, "disabled", xmlbool, 2);
|
|
if (temp != -1)
|
|
self->enabled = !temp;
|
|
|
|
temp = M_CompareXmlProp (node, "selected", xmlbool, 2);
|
|
if (temp != -1)
|
|
data->selected = temp;
|
|
|
|
}
|
|
|
|
void M_LoadXmlRadioGroup (qwidget_t *self, xmlNodePtr node)
|
|
{
|
|
/*
|
|
* --- supported attributes :
|
|
|
|
disabled = bool
|
|
|
|
* --- unsupported :
|
|
|
|
value = string ? : user attribute
|
|
*/
|
|
// qwidget_t *self = ptr;
|
|
xmlradiogroupdata_t *data = self->data;
|
|
int temp;
|
|
temp = M_CompareXmlProp (node, "disabled", xmlbool, 2);
|
|
if (temp != -1)
|
|
self->enabled = !temp;
|
|
}
|
|
|
|
void M_LoadXmlSlider (qwidget_t *self, xmlNodePtr node)
|
|
{
|
|
/*
|
|
* --- supported attributes :
|
|
|
|
curpos = [0,maxpos] : cursor position
|
|
maxpos = int : maximum cursor value
|
|
pageincrement = int
|
|
|
|
* --- unsupported :
|
|
|
|
increment = int :
|
|
|
|
*/
|
|
// qwidget_t *self = ptr;
|
|
xmlsliderdata_t *data = self->data;
|
|
|
|
data->range = M_ReadXmlPropAsInt (node, "maxpos", 0);
|
|
// FIXME : shoud property override cvar value ?
|
|
data->cursor = M_ReadXmlPropAsInt (node, "curpos", Cvar_VariableValue (self->name));
|
|
}
|
|
|
|
void M_LoadXmlEdit (qwidget_t *self, xmlNodePtr node)
|
|
{
|
|
/*
|
|
* --- supported attributes :
|
|
|
|
text = inital text
|
|
length = length of the textfield
|
|
|
|
*/
|
|
// qwidget_t *self = ptr;
|
|
xmleditdata_t *data = self->data;
|
|
cvar_t *var;
|
|
|
|
data->length = M_ReadXmlPropAsInt (node, "length", 20);
|
|
|
|
var = Cvar_FindVar(self->id);
|
|
if (!var) {
|
|
M_ReadXmlPropAsString (node, "text", data->text, sizeof (data->text));
|
|
// Con_Printf("var not found %s\n",self->id);
|
|
} else {
|
|
strcpy(data->text, var->string);
|
|
}
|
|
data->cursor = strlen(data->text);
|
|
}
|
|
|
|
void M_LoadXmlMenu (qwidget_t *self, 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 *self, 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 *self = ptr;
|
|
qwidget_t *w;
|
|
qwindow_t *data = self->data;
|
|
data->widget = self;
|
|
|
|
// check window size
|
|
if (self->width.ratio != -1)
|
|
self->width.absolute = self->width.ratio * vid.width;
|
|
if (self->height.ratio != -1)
|
|
self->height.absolute = self->height.ratio * vid.height;
|
|
|
|
if (self->width.absolute == -1)
|
|
self->width.absolute = vid.width;
|
|
if (self->height.absolute == -1)
|
|
self->height.absolute = vid.height;
|
|
|
|
// init some data
|
|
data->string_table.str = "";
|
|
data->keycount = 0;
|
|
data->focused = 0;
|
|
data->stack = NULL;
|
|
|
|
// add newly created window to the list
|
|
M_AddWindowInList (data);
|
|
}
|
|
|
|
void M_LoadXmlElement (qwidget_t *self, xmlNodePtr root)
|
|
{
|
|
/*
|
|
These are attributes shared by all widgets.
|
|
|
|
* --- 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
|
|
*/
|
|
|
|
int temp;
|
|
|
|
// read general attributes from the file
|
|
|
|
M_ReadXmlDim (root, "width", &self->width);
|
|
M_ReadXmlDim (root, "height", &self->height);
|
|
self->debug = (M_ReadXmlPropAsInt (root, "debug", 0) != 0);
|
|
|
|
self->id = M_ReadXmlPropAsRefString (root, "id");
|
|
self->name = M_ReadXmlPropAsRefString (root, "name");
|
|
|
|
temp = M_CompareXmlProp (root, "orient", xmlorient, 2);
|
|
if (temp != -1)
|
|
self->orient = temp;
|
|
|
|
temp = M_CompareXmlProp (root, "align", xmlalign, 5);
|
|
if (temp != -1)
|
|
self->align = temp;
|
|
temp = M_CompareXmlProp (root, "pack", xmlpack, 3);
|
|
if (temp != -1)
|
|
self->pack = temp;
|
|
|
|
// then init the private data
|
|
|
|
self->mtable->Load (self, root);
|
|
}
|
|
|
|
/*
|
|
This actually traverses the xml parse tree and creates windows, widgets, ... from it
|
|
*/
|
|
qwidget_t *M_LoadXmlElementTree (xmlNodePtr root)
|
|
{
|
|
xmlNodePtr node;
|
|
qwidget_t *ret,*sub;
|
|
xmlhandler_t *handler;
|
|
|
|
// 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 (handler->templatesize, handler->template);
|
|
|
|
if (handler->datasize)
|
|
ret->data = M_AllocateMem (handler->datasize, NULL);
|
|
ret->tag = handler->tag;
|
|
|
|
// new window ?
|
|
if (ret->tag == window_tag)
|
|
loading = ret->data;
|
|
|
|
M_LoadXmlElement (ret, root);
|
|
|
|
/* 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_LoadXmlElementTree (node);
|
|
if (sub)
|
|
M_InsertXmlWidget (ret, sub);
|
|
}
|
|
node = node->next;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
|
|
void M_XMLError (void *ctx, const char *fmt, ...) {
|
|
va_list argptr;
|
|
char msg[512];
|
|
|
|
//Creepy!
|
|
va_start (argptr,fmt);
|
|
#ifdef _MSC_VER
|
|
|
|
vsprintf(msg, fmt, argptr);
|
|
|
|
#else
|
|
|
|
vsnprintf (msg,512,fmt,argptr);
|
|
#endif
|
|
|
|
va_end (argptr);
|
|
|
|
Con_Printf(msg);
|
|
}
|
|
|
|
void M_XMLFatalError (void *ctx, const char *fmt, ...) {
|
|
va_list argptr;
|
|
char msg[512];
|
|
|
|
//Creepy!
|
|
va_start (argptr,fmt);
|
|
#ifdef _MSC_VER
|
|
|
|
vsprintf(msg, fmt, argptr);
|
|
|
|
#else
|
|
|
|
vsnprintf (msg,512,fmt,argptr);
|
|
#endif
|
|
|
|
va_end (argptr);
|
|
|
|
Con_Printf("fatal error: %s",msg);
|
|
}
|
|
|
|
/*
|
|
================
|
|
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;
|
|
|
|
//Setup error funciton pointers
|
|
xmlInitParser();
|
|
xmlSetGenericErrorFunc(NULL, M_XMLError);
|
|
|
|
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 xmlParseMemory returned NULL\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 *)"package")) {
|
|
//parse all the menus in this package
|
|
node = node->xmlChildrenNode;
|
|
while ( node != NULL ){
|
|
// comment nodes have node->name == NULL
|
|
if (!xmlIsBlankNode (node) && (node->type != XML_COMMENT_NODE)){
|
|
qwidget_t *qwidget = M_LoadXmlElementTree (node);
|
|
if (qwidget)
|
|
M_PrepareQWindow (qwidget);
|
|
}
|
|
node = node->next;
|
|
}
|
|
}
|
|
else {
|
|
Con_Printf ("Warning : %s document root isn't a package\n",filename);
|
|
}
|
|
}
|
|
|
|
xmlFreeDoc (doc);
|
|
|
|
#endif
|
|
Con_DPrintf ("XML : loading %s document ended \n",filename);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------
|