r122 - in trunk: . common menu menu/item menu/nexuiz menu/oo

DONOTREPLY at icculus.org DONOTREPLY at icculus.org
Sat Aug 30 02:46:38 EDT 2008


Author: vermeulen
Date: 2008-08-30 02:46:38 -0400 (Sat, 30 Aug 2008)
New Revision: 122

Added:
   trunk/common/
   trunk/common/campaign_common.qh
   trunk/common/campaign_file.qc
   trunk/common/campaign_setup.qc
   trunk/common/gamecommand.qc
   trunk/common/mapinfo.qc
   trunk/common/mapinfo.qh
   trunk/common/util.qc
   trunk/common/util.qh
   trunk/menu/
   trunk/menu/classes.c
   trunk/menu/config.qh
   trunk/menu/draw.qc
   trunk/menu/draw.qh
   trunk/menu/gamecommand.qc
   trunk/menu/gamecommand.qh
   trunk/menu/item.c
   trunk/menu/item/
   trunk/menu/item/borderimage.c
   trunk/menu/item/button.c
   trunk/menu/item/checkbox.c
   trunk/menu/item/container.c
   trunk/menu/item/dialog.c
   trunk/menu/item/gecko.c
   trunk/menu/item/image.c
   trunk/menu/item/inputbox.c
   trunk/menu/item/inputcontainer.c
   trunk/menu/item/label.c
   trunk/menu/item/listbox.c
   trunk/menu/item/modalcontroller.c
   trunk/menu/item/nexposee.c
   trunk/menu/item/radiobutton.c
   trunk/menu/item/slider.c
   trunk/menu/item/tab.c
   trunk/menu/item/textslider.c
   trunk/menu/mbuiltin.qh
   trunk/menu/menu.qc
   trunk/menu/menu.qh
   trunk/menu/msys.qh
   trunk/menu/nexuiz/
   trunk/menu/nexuiz/button.c
   trunk/menu/nexuiz/campaign.c
   trunk/menu/nexuiz/charmap.c
   trunk/menu/nexuiz/checkbox.c
   trunk/menu/nexuiz/checkbox_slider_invalid.c
   trunk/menu/nexuiz/colorbutton.c
   trunk/menu/nexuiz/commandbutton.c
   trunk/menu/nexuiz/credits.c
   trunk/menu/nexuiz/crosshairbutton.c
   trunk/menu/nexuiz/dialog.c
   trunk/menu/nexuiz/dialog_classselect.c
   trunk/menu/nexuiz/dialog_credits.c
   trunk/menu/nexuiz/dialog_infoscreen.c
   trunk/menu/nexuiz/dialog_multiplayer.c
   trunk/menu/nexuiz/dialog_multiplayer_create.c
   trunk/menu/nexuiz/dialog_multiplayer_create_mapinfo.c
   trunk/menu/nexuiz/dialog_multiplayer_create_mutators.c
   trunk/menu/nexuiz/dialog_multiplayer_join.c
   trunk/menu/nexuiz/dialog_multiplayer_playersetup.c
   trunk/menu/nexuiz/dialog_news.c
   trunk/menu/nexuiz/dialog_quit.c
   trunk/menu/nexuiz/dialog_settings.c
   trunk/menu/nexuiz/dialog_settings_effects.c
   trunk/menu/nexuiz/dialog_settings_input.c
   trunk/menu/nexuiz/dialog_settings_input_userbind.c
   trunk/menu/nexuiz/dialog_settings_misc.c
   trunk/menu/nexuiz/dialog_settings_video.c
   trunk/menu/nexuiz/dialog_singleplayer.c
   trunk/menu/nexuiz/dialog_singleplayer_winner.c
   trunk/menu/nexuiz/dialog_teamselect.c
   trunk/menu/nexuiz/gametypebutton.c
   trunk/menu/nexuiz/image.c
   trunk/menu/nexuiz/inputbox.c
   trunk/menu/nexuiz/keybinder.c
   trunk/menu/nexuiz/listbox.c
   trunk/menu/nexuiz/mainwindow.c
   trunk/menu/nexuiz/maplist.c
   trunk/menu/nexuiz/nexposee.c
   trunk/menu/nexuiz/playermodel.c
   trunk/menu/nexuiz/radiobutton.c
   trunk/menu/nexuiz/rootdialog.c
   trunk/menu/nexuiz/serverlist.c
   trunk/menu/nexuiz/slider.c
   trunk/menu/nexuiz/slider_decibels.c
   trunk/menu/nexuiz/slider_resolution.c
   trunk/menu/nexuiz/tab.c
   trunk/menu/nexuiz/tabcontroller.c
   trunk/menu/nexuiz/textlabel.c
   trunk/menu/nexuiz/textslider.c
   trunk/menu/nexuiz/util.qc
   trunk/menu/nexuiz/util.qh
   trunk/menu/oo/
   trunk/menu/oo/base.h
   trunk/menu/oo/classdefs.h
   trunk/menu/oo/constructors.h
   trunk/menu/oo/implementation.h
   trunk/menu/progs.src
   trunk/menu/skin-customizables.inc
   trunk/menu/skin.qh
   trunk/menu/todo
Log:
Forked from Nexuiz

Added: trunk/common/campaign_common.qh
===================================================================
--- trunk/common/campaign_common.qh	                        (rev 0)
+++ trunk/common/campaign_common.qh	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,28 @@
+#ifndef CAMPAIGN_MAX_ENTRIES
+#define CAMPAIGN_MAX_ENTRIES 64
+#endif
+
+// each i-th array element corresponds to the list entry campaign_offset+i
+float campaign_entries;
+float campaign_offset;
+string campaign_gametype[CAMPAIGN_MAX_ENTRIES];
+string campaign_mapname[CAMPAIGN_MAX_ENTRIES];
+float campaign_bots[CAMPAIGN_MAX_ENTRIES];
+float campaign_botskill[CAMPAIGN_MAX_ENTRIES];
+float campaign_fraglimit[CAMPAIGN_MAX_ENTRIES];
+string campaign_mutators[CAMPAIGN_MAX_ENTRIES];
+string campaign_shortdesc[CAMPAIGN_MAX_ENTRIES];
+string campaign_longdesc[CAMPAIGN_MAX_ENTRIES];
+
+// load the campaign file, but use the given offset and limit the number of
+// entries being read. Returns the number of entries successfully read (this
+// number is also stored in campaign_entries).
+// NOTE: there MUST be a corresponding CampaignFile_Unload() to unzone the
+// strings.
+string campaign_name; // set that to the campaign you want to load before calling CampaignFile_Load
+float CampaignFile_Load(float offset, float entries);
+void CampaignFile_Unload();
+
+// Sets up the campaign for the n-th array item (meaning: campaign_offset+nth
+// level) using localcmd()
+void CampaignSetup(float n);

Added: trunk/common/campaign_file.qc
===================================================================
--- trunk/common/campaign_file.qc	                        (rev 0)
+++ trunk/common/campaign_file.qc	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,80 @@
+// CampaignFileLoad(offset, n)
+// - Loads campaign level data (up to n entries starting at offset)
+//   into the globals
+// - Returns the number of entries successfully read
+float CampaignFile_Load(float offset, float n)
+{
+	float fh;
+	float lineno;
+	float entlen;
+	float i;
+	string l, a;
+	string fn;
+
+	if(n > CAMPAIGN_MAX_ENTRIES)
+		n = CAMPAIGN_MAX_ENTRIES;
+
+	campaign_offset = offset;
+	campaign_entries = 0;
+
+	fn = strcat("maps/campaign", campaign_name, ".txt");
+	fh = fopen(fn, FILE_READ);
+	if(fh >= 0)
+	{
+		for(lineno = 0; (l = fgets(fh)); )
+		{
+			if(strlen(l) == 0)
+				continue; // empty line
+			if(substring(l, 0, 2) == "//")
+				continue; // comment
+			if(substring(l, 0, 3) == "\"//")
+				continue; // comment
+			if(lineno >= offset)
+			{
+				entlen = tokenize(l);
+
+#define CAMPAIGN_GETARG0                  if(i >= entlen)
+#define CAMPAIGN_GETARG1 CAMPAIGN_GETARG0     error("syntax error in campaign file: line has not enough fields");
+#define CAMPAIGN_GETARG2 CAMPAIGN_GETARG1 a = argv(i++);
+#define CAMPAIGN_GETARG3 CAMPAIGN_GETARG2 if(a == ",")
+#define CAMPAIGN_GETARG4 CAMPAIGN_GETARG3     a = "";
+#define CAMPAIGN_GETARG5 CAMPAIGN_GETARG4 else
+#define CAMPAIGN_GETARG  CAMPAIGN_GETARG5     ++i
+// What you're seeing here is what people will do when your compiler supports
+// C-style macros but no line continuations.
+
+				i = 0;
+				CAMPAIGN_GETARG; campaign_gametype[campaign_entries] = strzone(a);
+				CAMPAIGN_GETARG; campaign_mapname[campaign_entries] = strzone(a);
+				CAMPAIGN_GETARG; campaign_bots[campaign_entries] = stof(a);
+				CAMPAIGN_GETARG; campaign_botskill[campaign_entries] = stof(a);
+				CAMPAIGN_GETARG; campaign_fraglimit[campaign_entries] = stof(a);
+				CAMPAIGN_GETARG; campaign_mutators[campaign_entries] = strzone(a);
+				CAMPAIGN_GETARG; campaign_shortdesc[campaign_entries] = strzone(a);
+				CAMPAIGN_GETARG; campaign_longdesc[campaign_entries] = strzone(a);
+				campaign_entries = campaign_entries + 1;
+
+				if(campaign_entries >= n)
+					break;
+			}
+			lineno = lineno + 1;
+		}
+		fclose(fh);
+	}
+
+	return campaign_entries;
+}
+
+void CampaignFile_Unload()
+{
+	float i;
+	for(i = 0; i < campaign_entries; ++i)
+	{
+		strunzone(campaign_gametype[i]);
+		strunzone(campaign_mapname[i]);
+		strunzone(campaign_mutators[i]);
+		strunzone(campaign_shortdesc[i]);
+		strunzone(campaign_longdesc[i]);
+	}
+	campaign_entries = 0;
+}

Added: trunk/common/campaign_setup.qc
===================================================================
--- trunk/common/campaign_setup.qc	                        (rev 0)
+++ trunk/common/campaign_setup.qc	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,27 @@
+void CampaignSetup(float n)
+{
+#ifndef MAPINFO
+	localcmd("exec game_reset.cfg\n");
+#endif
+	localcmd("exec mutator_reset.cfg\n");
+	localcmd("set g_campaign 1\n");
+	localcmd("set _campaign_name \"");
+		localcmd(campaign_name);
+		localcmd("\"\n");
+	localcmd("set _campaign_index ");
+		localcmd(ftos(campaign_offset + n));
+		localcmd("\n");
+	localcmd(campaign_mutators[n]);
+		localcmd("\n");
+#ifdef MAPINFO
+	MapInfo_SwitchGameType(MapInfo_Type_FromString(campaign_gametype[n]));
+	//print(">>", cvar_string("g_tdm"), "<<\n");
+	MapInfo_LoadMap(campaign_mapname[n]);
+#else
+	localcmd("exec maps/"); // can't use strcat here in current fteqcc
+		localcmd(campaign_gametype[n]);
+		localcmd("_");
+		localcmd(campaign_mapname[n]);
+		localcmd(".mapcfg\n");
+#endif
+}

Added: trunk/common/gamecommand.qc
===================================================================
--- trunk/common/gamecommand.qc	                        (rev 0)
+++ trunk/common/gamecommand.qc	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,386 @@
+#define MAX_RPN_STACK 8
+float rpn_error;
+float rpn_sp;
+string rpn_stack[MAX_RPN_STACK];
+string rpn_pop() {
+	if(rpn_sp > 0) {
+		--rpn_sp;
+		return rpn_stack[rpn_sp];
+	} else {
+		print("rpn: stack underflow\n");
+		rpn_error = TRUE;
+		return "";
+	}
+}
+void rpn_push(string s) {
+	if(rpn_sp < MAX_RPN_STACK) {
+		rpn_stack[rpn_sp] = s;
+		++rpn_sp;
+	} else {
+		print("rpn: stack overflow\n");
+		rpn_error = TRUE;
+	}
+}
+string rpn_get() {
+	if(rpn_sp > 0) {
+		return rpn_stack[rpn_sp - 1];
+	} else {
+		print("rpn: empty stack\n");
+		rpn_error = TRUE;
+		return "";
+	}
+}
+void rpn_set(string s) {
+	if(rpn_sp > 0) {
+		rpn_stack[rpn_sp - 1] = s;
+	} else {
+		print("rpn: empty stack\n");
+		rpn_error = TRUE;
+	}
+}
+float rpn_getf() { return stof(rpn_get()); }
+float rpn_popf() { return stof(rpn_pop()); }
+void rpn_pushf(float f) { return rpn_push(ftos(f)); }
+void rpn_setf(float f) { return rpn_set(ftos(f)); }
+
+float GameCommand_Generic(string command)
+{
+	float argc;
+	float i, j, f, n;
+	string s, s2;
+	argc = tokenize(command);
+	if(argv(0) == "help")
+	{
+		print("  rpn EXPRESSION... - a RPN calculator.\n");
+		print("    Operator description (x: string, s: set, f: float):\n");
+		print("    x pop ----------------------------->     : removes the top\n");
+		print("    x dup -----------------------------> x x : duplicates the top\n");
+		print("    x x exch --------------------------> x x : swap the top two\n");
+		print("    /cvarname load --------------------> x   : loads a cvar\n");
+		print("    /cvarname x def ------------------->     : writes to a cvar\n");
+		print("    f f add|sub|mul|div|mod|max|min ---> f   : adds/... two numbers\n");
+		print("    f f eq|ne|gt|ge|lt|le -------------> f   : compares two numbers\n");
+		print("    f neg|abs|sgn|rand ----------------> f   : negates/... a number\n");
+		print("    f f f bound -----------------------> f   : bounds the middle number\n");
+		print("    f1 f2 b when ----------------------> f   : f1 if b, f2 otherwise\n");
+		print("    s s union|intersection|difference -> s   : set operations\n");
+		print("    s shuffle -------------------------> s   : randomly arrange elements\n");
+		print("    Set operations operate on 'such''strings' like g_maplist.\n");
+		print("    Unknown tokens insert their cvar value.\n");
+		print("  maplist add map\n");
+		print("  maplist remove map\n");
+		print("  maplist shuffle\n");
+		return TRUE;
+	}
+	
+	if(argv(0) == "maplist")
+	{
+		if(argv(1) == "add" && argc == 3)
+		{
+#ifdef MAPINFO
+			f = fopen(strcat("maps/", argv(2), ".bsp"), FILE_READ);
+			if(f != -1)
+				fclose(f);
+			else {
+				print("maplist: ERROR: ", argv(2), " does not exist!\n");
+				return TRUE;
+			}
+			if(cvar_string("g_maplist") == "")
+				cvar_set("g_maplist", argv(2));
+			else
+				cvar_set("g_maplist", strcat(argv(2), " ", cvar_string("g_maplist")));
+#else
+			f = fopen(strcat("maps/", argv(2), ".mapcfg"), FILE_READ);
+			if(f != -1)
+				fclose(f);
+			else {
+				print("maplist: ERROR: ", argv(2), " does not exist!\n");
+				return TRUE;
+			}
+			cvar_set("g_maplist", strcat("'", argv(2), "'", cvar_string("g_maplist")));
+#endif
+			return TRUE;
+		}
+		else if(argv(1) == "remove" && argc == 3)
+		{
+			s = argv(2);
+#ifdef MAPINFO
+			n = tokenizebyseparator(cvar_string("g_maplist"), " ");
+#else
+			n = tokenize(cvar_string("g_maplist"));
+#endif
+			s2 = "";
+			for(i = 0; i < n; ++i)
+				if(argv(i) != s)
+				{
+#ifdef MAPINFO
+					s2 = strcat(s2, " ", argv(i));
+#else
+					s2 = strcat(s2, "'", argv(i), "'");
+#endif
+				}
+#ifdef MAPINFO
+			s2 = substring(s2, 1, strlen(s2) - 1);
+#endif
+			cvar_set("g_maplist", s2);
+			return TRUE;
+		}
+		else if(argv(1) == "shuffle" && argc == 2)
+		{
+			s = cvar_string("g_maplist");
+#ifdef MAPINFO
+			for(i = 1; i < (n = tokenizebyseparator(s, " ")); ++i)
+#else
+			for(i = 1; i < (n = tokenize(s)); ++i)
+#endif
+			{
+				// swap i-th item at a random position from 0 to i
+				// proof for even distribution:
+				//   n = 1: obvious
+				//   n -> n+1:
+				//     item n+1 gets at any position with chance 1/(n+1)
+				//     all others will get their 1/n chance reduced by factor n/(n+1)
+				//     to be on place n+1, their chance will be 1/(n+1)
+				//     1/n * n/(n+1) = 1/(n+1)
+				//     q.e.d.
+				f = ceil(random() * (i + 1)) - 1; // 0 to i
+				if(f == i)
+					continue; // no change
+
+				s2 = "";
+				for(j = 0; j < n; ++j)
+#ifdef MAPINFO
+					s2 = strcat(s2, " ", argv((j == i) ? f : (j == f) ? i : j));
+				s = substring(s2, 1, strlen(s2) - 1);
+#else
+					s2 = strcat(s2, "'", argv((j == i) ? f : (j == f) ? i : j), "'");
+				s = s2;
+#endif
+			}
+			cvar_set("g_maplist", s);
+			return TRUE;
+		}
+	}
+	else if(argv(0) == "rpn")
+	{
+		if(argc >= 2)
+		{
+			float rpnpos;
+			string rpncmd;
+			float f2, f3;
+			rpn_sp = 0;
+			rpn_error = FALSE;
+			for(rpnpos = 1; rpnpos < argc; ++rpnpos)
+			{
+				rpncmd = argv(rpnpos);
+				f = strlen(rpncmd);
+				if(rpncmd == "") {
+				} else if(stof(substring(rpncmd, 0, 1)) > 0) {
+					rpn_push(rpncmd);
+				} else if(substring(rpncmd, 0, 1) == "0") {
+					rpn_push(rpncmd);
+				} else if(f >= 2 && substring(rpncmd, 0, 1) == "+") {
+					rpn_push(rpncmd);
+				} else if(f >= 2 && substring(rpncmd, 0, 1) == "-") {
+					rpn_push(rpncmd);
+				} else if(f >= 2 && substring(rpncmd, 0, 1) == "/") {
+					rpn_push(substring(rpncmd, 1, strlen(rpncmd) - 1));
+				} else if(rpncmd == "def" || rpncmd == "=") {
+					s = rpn_pop();
+					s2 = rpn_pop();
+#ifdef MENUQC
+					registercvar(s2, "", 0);
+#else
+					registercvar(s2, "");
+#endif
+					if(!rpn_error) // don't change cvars if a stack error had happened!
+						cvar_set(s2, s);
+				} else if(rpncmd == "load") {
+					rpn_set(cvar_string(rpn_get()));
+				} else if(rpncmd == "exch") {
+					s = rpn_pop();
+					s2 = rpn_get();
+					rpn_set(s);
+					rpn_push(s2);
+				} else if(rpncmd == "dup") {
+					rpn_push(rpn_get());
+				} else if(rpncmd == "pop") {
+					rpn_pop();
+				} else if(rpncmd == "add" || rpncmd == "+") {
+					f = rpn_popf();
+					rpn_setf(rpn_getf() + f);
+				} else if(rpncmd == "sub" || rpncmd == "-") {
+					f = rpn_popf();
+					rpn_setf(rpn_getf() - f);
+				} else if(rpncmd == "mul" || rpncmd == "*") {
+					f = rpn_popf();
+					rpn_setf(rpn_getf() * f);
+				} else if(rpncmd == "div" || rpncmd == "/") {
+					f = rpn_popf();
+					rpn_setf(rpn_getf() / f);
+				} else if(rpncmd == "mod" || rpncmd == "%") {
+					f = rpn_popf();
+					f2 = rpn_getf();
+					rpn_setf(f2 - f * floor(f2 / f));
+				} else if(rpncmd == "abs") {
+					rpn_setf(fabs(rpn_getf()));
+				} else if(rpncmd == "sgn") {
+					f = rpn_getf();
+					if(f < 0)
+						rpn_set("-1");
+					else if(f > 0)
+						rpn_set("1");
+					else
+						rpn_set("0");
+				} else if(rpncmd == "neg" || rpncmd == "~") {
+					rpn_setf(-rpn_getf());
+				} else if(rpncmd == "max") {
+					f = rpn_popf();
+					f2 = rpn_getf();
+					rpn_setf(max(f2, f));
+				} else if(rpncmd == "min") {
+					f = rpn_popf();
+					f2 = rpn_getf();
+					rpn_setf(min(f2, f));
+				} else if(rpncmd == "bound") {
+					f = rpn_popf();
+					f2 = rpn_popf();
+					f3 = rpn_getf();
+					rpn_setf(bound(f3, f2, f));
+				} else if(rpncmd == "when") {
+					f = rpn_popf();
+					f2 = rpn_popf();
+					f3 = rpn_getf();
+					if(f)
+						rpn_setf(f3);
+					else
+						rpn_setf(f2);
+				} else if(rpncmd == ">" || rpncmd == "gt") {
+					f = rpn_popf();
+					rpn_setf(rpn_getf() > f);
+				} else if(rpncmd == "<" || rpncmd == "lt") {
+					f = rpn_popf();
+					rpn_setf(rpn_getf() < f);
+				} else if(rpncmd == "==" || rpncmd == "eq") {
+					f = rpn_popf();
+					rpn_setf(rpn_getf() == f);
+				} else if(rpncmd == ">=" || rpncmd == "ge") {
+					f = rpn_popf();
+					rpn_setf(rpn_getf() >= f);
+				} else if(rpncmd == "<=" || rpncmd == "le") {
+					f = rpn_popf();
+					rpn_setf(rpn_getf() <= f);
+				} else if(rpncmd == "!=" || rpncmd == "ne") {
+					f = rpn_popf();
+					rpn_setf(rpn_getf() != f);
+				} else if(rpncmd == "rand") {
+					rpn_setf(ceil(random() * rpn_getf()) - 1);
+				} else if(rpncmd == "union") {
+					// s s2 union
+					s2 = rpn_pop();
+					s = rpn_get();
+					f = tokenize(s);
+					f2 = tokenize(strcat(s, s2));
+					// tokens 0..(f-1) represent s
+					// tokens f..f2 represent s2
+					// UNION: add all tokens to s that are in s2 but not in s
+					s = "";
+					for(i = 0; i < f; ++i)
+						s = strcat(s, "'", argv(i), "'");
+					for(i = f; i < f2; ++i) {
+						for(j = 0; j < f; ++j)
+							if(argv(i) == argv(j))
+								goto skip_union;
+						s = strcat(s, "'", argv(i), "'");
+:skip_union
+					}
+					rpn_set(s);
+					tokenize(command);
+				} else if(rpncmd == "intersection") {
+					// s s2 intersection
+					s2 = rpn_pop();
+					s = rpn_get();
+					f = tokenize(s);
+					f2 = tokenize(strcat(s, s2));
+					// tokens 0..(f-1) represent s
+					// tokens f..f2 represent s2
+					// INTERSECTION: keep only the tokens from s that are also in s2
+					s = "";
+					for(i = 0; i < f; ++i) {
+						for(j = f; j < f2; ++j)
+							if(argv(i) == argv(j))
+							{
+								s = strcat(s, "'", argv(i), "'");
+								break;
+							}
+					}
+					rpn_set(s);
+					tokenize(command);
+				} else if(rpncmd == "difference") {
+					// s s2 difference
+					s2 = rpn_pop();
+					s = rpn_get();
+					f = tokenize(s);
+					f2 = tokenize(strcat(s, s2));
+					// tokens 0..(f-1) represent s
+					// tokens f..f2 represent s2
+					// DIFFERENCE: keep only the tokens from s that are not in s2
+					s = "";
+					for(i = 0; i < f; ++i) {
+						for(j = f; j < f2; ++j)
+							if(argv(i) == argv(j))
+								goto skip_difference;
+						s = strcat(s, "'", argv(i), "'");
+:skip_difference
+					}
+					rpn_set(s);
+					tokenize(command);
+				} else if(rpncmd == "shuffle") {
+					// s shuffle
+					s = rpn_get();
+					f = tokenize(s);
+
+					for(i = 0; i < f - 1; ++i) {
+						// move a random item from i..f-1 to position i
+						s = "";
+						f2 = ceil(random() * (f - i) + i) - 1;
+						for(j = 0; j < i; ++j)
+							s = strcat(s, "'", argv(j), "'");
+						s = strcat(s, "'", argv(f2), "'");
+						for(j = i; j < f; ++j)
+							if(j != f2)
+								s = strcat(s, "'", argv(j), "'");
+						f = tokenize(s);
+					}
+
+					rpn_set(s);
+					tokenize(command);
+				} else if(rpncmd == "fexists_assert") {
+					s = rpn_pop();
+					if(!rpn_error)
+					{
+						f = fopen(s, FILE_READ);
+						if(f != -1)
+							fclose(f);
+						else {
+							print("rpn: ERROR: ", s, " does not exist!\n");
+							rpn_error = TRUE;
+						}
+					}
+				} else {
+					rpn_push(cvar_string(rpncmd));
+				}
+				if(rpn_error)
+					break;
+			}
+			while(rpn_sp > 0)
+			{
+				s = rpn_pop();
+				print("rpn: still on stack: ", s, "\n");
+			}
+			return TRUE;
+		}
+	}
+
+	return FALSE;
+}

Added: trunk/common/mapinfo.qc
===================================================================
--- trunk/common/mapinfo.qc	                        (rev 0)
+++ trunk/common/mapinfo.qc	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,817 @@
+	// internal toy
+void cvar_settemp(string pKey, string pValue)
+{
+	//localcmd(strcat("\nsettemp ", t, " \"", s, "\"\n"));
+	
+	// duplicate what this alias does:
+	// alias settemp "settemp_list \"1 $1 $settemp_var $settemp_list\"; set $settemp_var \"${$1}\"; settemp_var ${settemp_var}x; $1 \"$2\""
+	
+	cvar_set("settemp_list", strcat("1 ", pKey, " ", cvar_string("settemp_var"), " ", cvar_string("settemp_list")));
+#ifdef MENUQC
+	registercvar(cvar_string("settemp_var"), "", 0);
+#else
+	registercvar(cvar_string("settemp_var"), "");
+#endif
+	cvar_set(cvar_string("settemp_var"), cvar_string(pKey));
+	cvar_set("settemp_var", strcat(cvar_string("settemp_var"), "x"));
+	cvar_set(pKey, pValue);
+}
+
+void cvar_settemp_restore()
+{
+	// undo what cvar_settemp did
+	float n, i;
+	n = tokenize(cvar_string("settemp_list"));
+	for(i = 0; i < n - 3; i += 3)
+		cvar_set(argv(i + 1), cvar_string(argv(i + 2)));
+	cvar_set("settemp_list", "0");
+}
+
+#ifdef HSOI
+// HUGE SET - stored in a string
+string HugeSetOfIntegers_empty()
+{
+	return "";
+}
+float HugeSetOfIntegers_get(string pArr, float i)
+{
+	return stof(substring(pArr, i * 4, 4));
+}
+float HugeSetOfIntegers_length(string pArr)
+{
+	return strlen(pArr) / 4;
+}
+string HugeSetOfIntegers_concat(string a1, string a2)
+{
+	return strcat(a1, a2);
+}
+string HugeSetOfIntegers_insert(string a1, float n, string a2)
+	// special concat function to build up large lists in less time by binary concatenation
+{
+	string s;
+	s = strcat("    ", ftos(n));
+	return strcat(a1, substring(s, strlen(s) - 4, 4), a2);
+}
+#endif
+
+// generic string stuff
+float startsWith(string haystack, string needle)
+{
+	return substring(haystack, 0, strlen(needle)) == needle;
+}
+float startsWithNocase(string haystack, string needle)
+{
+	return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
+}
+string extractRestOfLine(string haystack, string needle)
+{
+	if(startsWith(haystack, needle))
+		return substring(haystack, strlen(needle), strlen(haystack) - strlen(needle));
+	return string_null;
+}
+string car(string s)
+{
+	float o;
+	o = strstrofs(s, " ", 0);
+	if(o < 0)
+		return s;
+	return substring(s, 0, o);
+}
+string cdr(string s)
+{
+	float o;
+	o = strstrofs(s, " ", 0);
+	if(o < 0)
+		return string_null;
+	return substring(s, o + 1, strlen(s) - (o + 1));
+}
+
+float _MapInfo_Cache_Active;
+float _MapInfo_Cache_DB_NameToIndex;
+float _MapInfo_Cache_Buf_IndexToMapData;
+
+void MapInfo_Cache_Destroy()
+{
+	if(!_MapInfo_Cache_Active)
+		return;
+
+	db_close(_MapInfo_Cache_DB_NameToIndex);
+	buf_del(_MapInfo_Cache_Buf_IndexToMapData);
+	_MapInfo_Cache_Active = 0;
+}
+
+void MapInfo_Cache_Create()
+{
+	MapInfo_Cache_Destroy();
+	_MapInfo_Cache_DB_NameToIndex = db_create();
+	_MapInfo_Cache_Buf_IndexToMapData = buf_create();
+	_MapInfo_Cache_Active = 1;
+}
+
+void MapInfo_Cache_Invalidate()
+{
+	if(!_MapInfo_Cache_Active)
+		return;
+
+	MapInfo_Cache_Create();
+}
+
+void MapInfo_Cache_Store()
+{
+	float i;
+	string s;
+	if(!_MapInfo_Cache_Active)
+		return;
+
+	s = db_get(_MapInfo_Cache_DB_NameToIndex, MapInfo_Map_bspname);
+	if(!s) // empty string is NOT valid here!
+	{
+		i = buf_getsize(_MapInfo_Cache_Buf_IndexToMapData);
+		db_put(_MapInfo_Cache_DB_NameToIndex, MapInfo_Map_bspname, ftos(i));
+	}
+	else
+		i = stof(s);
+
+	// now store all the stuff
+	bufstr_set(_MapInfo_Cache_Buf_IndexToMapData, i++, MapInfo_Map_bspname);
+	bufstr_set(_MapInfo_Cache_Buf_IndexToMapData, i++, MapInfo_Map_title);
+	bufstr_set(_MapInfo_Cache_Buf_IndexToMapData, i++, MapInfo_Map_description);
+	bufstr_set(_MapInfo_Cache_Buf_IndexToMapData, i++, MapInfo_Map_author);
+	bufstr_set(_MapInfo_Cache_Buf_IndexToMapData, i++, ftos(MapInfo_Map_supportedGametypes));
+	bufstr_set(_MapInfo_Cache_Buf_IndexToMapData, i++, ftos(MapInfo_Map_supportedFeatures));
+	bufstr_set(_MapInfo_Cache_Buf_IndexToMapData, i++, ftos(MapInfo_Map_diameter));
+	bufstr_set(_MapInfo_Cache_Buf_IndexToMapData, i++, ftos(MapInfo_Map_spawnpoints));
+}
+
+float MapInfo_Cache_Retrieve(string map)
+{
+	float i;
+	string s;
+	if(!_MapInfo_Cache_Active)
+		return 0;
+
+	s = db_get(_MapInfo_Cache_DB_NameToIndex, map);
+	if(!s)
+		return 0;
+	i = stof(s);
+
+	// now retrieve all the stuff
+	MapInfo_Map_bspname = bufstr_get(_MapInfo_Cache_Buf_IndexToMapData, i++);
+	MapInfo_Map_title = bufstr_get(_MapInfo_Cache_Buf_IndexToMapData, i++);
+	MapInfo_Map_description = bufstr_get(_MapInfo_Cache_Buf_IndexToMapData, i++);
+	MapInfo_Map_author = bufstr_get(_MapInfo_Cache_Buf_IndexToMapData, i++);
+	MapInfo_Map_supportedGametypes = stof(bufstr_get(_MapInfo_Cache_Buf_IndexToMapData, i++));
+	MapInfo_Map_supportedFeatures = stof(bufstr_get(_MapInfo_Cache_Buf_IndexToMapData, i++));
+	MapInfo_Map_diameter = stof(bufstr_get(_MapInfo_Cache_Buf_IndexToMapData, i++));
+	MapInfo_Map_spawnpoints = stof(bufstr_get(_MapInfo_Cache_Buf_IndexToMapData, i++));
+	return 1;
+}
+
+// GLOB HANDLING (for all BSP files)
+float _MapInfo_globopen;
+float _MapInfo_globcount; 
+float _MapInfo_globhandle;
+string _MapInfo_GlobItem(float i)
+{
+	string s;
+	s = search_getfilename(_MapInfo_globhandle, i);
+	return substring(s, 5, strlen(s) - 9); // without maps/ and .bsp
+}
+
+void MapInfo_Enumerate()
+{
+	if(_MapInfo_globopen)
+		search_end(_MapInfo_globhandle);
+	MapInfo_Cache_Invalidate();
+	_MapInfo_globhandle = search_begin("maps/*.bsp", TRUE, TRUE);
+	_MapInfo_globcount = search_getsize(_MapInfo_globhandle);
+	_MapInfo_globopen = 1;
+}
+
+// filter the info by game type mask (updates MapInfo_count)
+//
+#ifdef HSOI
+string _MapInfo_filtered;
+float MapInfo_FilterList_Lookup(float i)
+{
+	return MapInfo_FilterList_Lookup(i);
+}
+
+string MapInfo_FilterGametype_Recursive(float pGametype, float pFeatures, float pBegin, float pEnd, float pAbortOnGenerate)
+{
+	float m, valid;
+	string l, r;
+
+	if(pBegin == pEnd)
+		return HugeSetOfIntegers_empty();
+
+	m = floor((pBegin + pEnd) / 2);
+
+	l = MapInfo_FilterGametype_Recursive(pGametype, pFeatures, pBegin, m, pAbortOnGenerate);
+	if not(l)
+		return string_null; // BAIL OUT
+	if(MapInfo_Get_ByName(_MapInfo_GlobItem(m), 1, 0) == 2) // if we generated one... BAIL OUT and let the caller continue in the next frame.
+		if(pAbortOnGenerate)
+		{
+			MapInfo_progress = m / _MapInfo_globcount;
+			return string_null; // BAIL OUT
+		}
+	valid = (((MapInfo_Map_supportedGametypes & pGametype) != 0) && ((MapInfo_Map_supportedFeatures & pFeatures) == pFeatures));
+	r = MapInfo_FilterGametype_Recursive(pGametype, pFeatures, m + 1, pEnd, pAbortOnGenerate);
+	if not(r)
+		return string_null; // BAIL OUT
+
+	if(valid)
+		return HugeSetOfIntegers_insert(l, m, r);
+	else
+		return HugeSetOfIntegers_concat(l, r);
+}
+
+float MapInfo_FilterGametype(float pGametype, float pFeatures, float pAbortOnGenerate)
+{
+	if(_MapInfo_filtered)
+		strunzone(_MapInfo_filtered);
+	_MapInfo_filtered = MapInfo_FilterGametype_Recursive(pGametype, pFeatures, 0, _MapInfo_globcount, pAbortOnGenerate);
+	if not(_MapInfo_filtered)
+	{
+		dprint("Autogenerated a .mapinfo, doing the rest later.\n");
+		return 0;
+	}
+	_MapInfo_filtered = strzone(_MapInfo_filtered);
+	MapInfo_count = HugeSetOfIntegers_length(_MapInfo_filtered);
+	MapInfo_ClearTemps();
+	return 1;
+}
+#else
+float _MapInfo_filtered;
+float _MapInfo_filtered_allocated;
+float MapInfo_FilterList_Lookup(float i)
+{
+	return stof(bufstr_get(_MapInfo_filtered, i));
+}
+
+float MapInfo_FilterGametype(float pGametype, float pFeatures, float pAbortOnGenerate)
+{
+	float i, j;
+	if not(_MapInfo_filtered_allocated)
+	{
+		_MapInfo_filtered_allocated = 1;
+		_MapInfo_filtered = buf_create();
+	}
+	MapInfo_count = 0;
+	for(i = 0, j = 0; i < _MapInfo_globcount; ++i)
+	{
+		if(MapInfo_Get_ByName(_MapInfo_GlobItem(i), 1, 0) == 2) // if we generated one... BAIL OUT and let the caller continue in the next frame.
+			if(pAbortOnGenerate)
+			{
+				dprint("Autogenerated a .mapinfo, doing the rest later.\n");
+				MapInfo_progress = i / _MapInfo_globcount;
+				return 0;
+			}
+		if(((MapInfo_Map_supportedGametypes & pGametype) != 0) && ((MapInfo_Map_supportedFeatures & pFeatures) == pFeatures))
+			bufstr_set(_MapInfo_filtered, j++, ftos(i));
+	}
+	MapInfo_count = j;
+	return 1;
+}
+#endif
+
+// load info about the i-th map into the MapInfo_Map_* globals
+string MapInfo_BSPName_ByID(float i)
+{
+	return _MapInfo_GlobItem(MapInfo_FilterList_Lookup(i));
+}
+
+string unquote(string s)
+{
+	float i, j, l;
+	l = strlen(s);
+	j = -1;
+	for(i = 0; i < l; ++i)
+	{
+		string ch;
+		ch = substring(s, i, 1);
+		if(ch != " ") if(ch != "\"")
+		{
+			for(j = strlen(s) - i - 1; j > 0; --j)
+			{
+				ch = substring(s, i+j, 1);
+				if(ch != " ") if(ch != "\"")
+					return substring(s, i, j+1);
+			}
+			return substring(s, i, 1);
+		}
+	}
+	return "";
+}
+
+float MapInfo_Get_ByID(float i)
+{
+	if(MapInfo_Get_ByName(MapInfo_BSPName_ByID(i), 0, 0))
+		return 1;
+	return 0;
+}
+
+float _MapInfo_Generate(string pFilename) // 0: failure, 1: ok ent, 2: ok bsp
+{
+	string fn;
+	float fh;
+	string s, k, v;
+	vector o;
+	float i;
+	float inWorldspawn;
+	float r;
+	float twoBaseModes;
+
+	vector mapMins, mapMaxs;
+
+	r = 1;
+	fn = strcat("maps/", pFilename, ".ent");
+	fh = fopen(fn, FILE_READ);
+	if(fh < 0)
+	{
+		r = 2;
+		fn = strcat("maps/", pFilename, ".bsp");
+		fh = fopen(fn, FILE_READ);
+	}
+	if(fh < 0)
+		return 0;
+	print("Analyzing ", fn, " to generate initial mapinfo; please edit that file later\n");
+
+	inWorldspawn = 2;
+	MapInfo_Map_supportedGametypes = 0;
+
+	for(;;)
+	{
+		if not((s = fgets(fh)))
+			break;
+		if(inWorldspawn == 1)
+			if(startsWith(s, "}"))
+				inWorldspawn = 0;
+		k = unquote(car(s));
+		v = unquote(cdr(s));
+		if(inWorldspawn)
+		{
+			if(k == "classname" && v == "worldspawn")
+				inWorldspawn = 1;
+			else if(k == "author")
+				MapInfo_Map_author = v;
+			else if(k == "_description")
+				MapInfo_Map_description = v;
+			else if(k == "message")
+			{
+				i = strstrofs(v, " by ", 0);
+				if(MapInfo_Map_author == "<AUTHOR>" && i >= 0)
+				{
+					MapInfo_Map_title = substring(v, 0, i);
+					MapInfo_Map_author = substring(v, i + 4, strlen(v) - (i + 4));
+				}
+				else
+					MapInfo_Map_title = v;
+			}
+		}
+		else
+		{
+			if(k == "origin")
+			{
+				o = stov(strcat("'", v, "'"));
+				mapMins_x = min(mapMins_x, o_x);
+				mapMins_y = min(mapMins_y, o_y);
+				mapMins_z = min(mapMins_z, o_z);
+				mapMaxs_x = max(mapMaxs_x, o_x);
+				mapMaxs_y = max(mapMaxs_y, o_y);
+				mapMaxs_z = max(mapMaxs_z, o_z);
+			}
+			else if(k == "classname")
+			{
+				if(v == "dom_controlpoint")
+					MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_DOMINATION;
+				else if(v == "item_flag_team2")
+					MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_CTF;
+				else if(v == "team_CTF_blueflag")
+					MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_CTF;
+				else if(v == "runematch_spawn_point")
+					MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_RUNEMATCH;
+				else if(v == "target_assault_roundend")
+					MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_ASSAULT;
+				else if(v == "onslaught_generator")
+					MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_ONSLAUGHT;
+				else if(v == "info_player_team1")
+					++MapInfo_Map_spawnpoints;
+				else if(v == "info_player_team2")
+					++MapInfo_Map_spawnpoints;
+				else if(v == "info_player_start")
+					++MapInfo_Map_spawnpoints;
+				else if(v == "info_player_deathmatch")
+					++MapInfo_Map_spawnpoints;
+				else if(v == "weapon_nex")
+					{ }
+				else if(v == "weapon_railgun")
+					{ }
+				else if(startsWith(v, "weapon_"))
+					MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_WEAPONS;
+			}
+		}
+	}
+	if(inWorldspawn)
+	{
+		print(fn, " ended still in worldspawn, BUG\n");
+		return 0;
+	}
+	MapInfo_Map_diameter = vlen(mapMaxs - mapMins);
+
+	twoBaseModes = MapInfo_Map_supportedGametypes & (MAPINFO_TYPE_CTF | MAPINFO_TYPE_ASSAULT);
+	if(twoBaseModes && (MapInfo_Map_supportedGametypes == twoBaseModes))
+	{
+		// we have a CTF-only or Assault-only map. Don't add other modes then,
+		// as the map is too symmetric for them.
+	}
+	else
+	{
+		MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_DEATHMATCH;      // DM always works
+		MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_LMS;             // LMS always works
+
+		if(MapInfo_Map_spawnpoints >= 8  && MapInfo_Map_diameter > 4096)
+			MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_TEAM_DEATHMATCH;
+		if(                MapInfo_Map_diameter < 4096)
+			MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_ARENA;
+		if(MapInfo_Map_spawnpoints >= 12 && MapInfo_Map_diameter > 5120)
+			MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_KEYHUNT;
+	}
+
+	fclose(fh);
+
+	return r;
+}
+
+void _MapInfo_Map_Reset()
+{
+	MapInfo_Map_title = "<TITLE>";
+	MapInfo_Map_description = "<DESCRIPTION>";
+	MapInfo_Map_author = "<AUTHOR>";
+	MapInfo_Map_supportedGametypes = 0;
+	MapInfo_Map_supportedFeatures = 0;
+	MapInfo_Map_diameter = 0;
+	MapInfo_Map_spawnpoints = 0;
+}
+
+void _MapInfo_Map_ApplyGametype(string s, float pWantedType, float pThisType)
+{
+	MapInfo_Map_supportedGametypes |= pThisType;
+	if(!(pThisType & pWantedType))
+		return;
+	
+	cvar_set("fraglimit", car(s));
+	s = cdr(s);
+
+	cvar_set("timelimit", car(s));
+	s = cdr(s);
+
+	if(pWantedType == MAPINFO_TYPE_TEAM_DEATHMATCH)
+	{
+		cvar_set("g_tdm_teams", car(s));
+		s = cdr(s);
+	}
+
+	if(pWantedType == MAPINFO_TYPE_KEYHUNT)
+	{
+		cvar_set("g_keyhunt_teams", car(s));
+		s = cdr(s);
+	}
+}
+
+float MapInfo_Type_FromString(string t)
+{
+	if     (t == "dm")    return MAPINFO_TYPE_DEATHMATCH;
+	else if(t == "tdm")   return MAPINFO_TYPE_TEAM_DEATHMATCH;
+	else if(t == "dom")   return MAPINFO_TYPE_DOMINATION;
+	else if(t == "ctf")   return MAPINFO_TYPE_CTF;
+	else if(t == "rune")  return MAPINFO_TYPE_RUNEMATCH;
+	else if(t == "lms")   return MAPINFO_TYPE_LMS;
+	else if(t == "arena") return MAPINFO_TYPE_ARENA;
+	else if(t == "kh")    return MAPINFO_TYPE_KEYHUNT;
+	else if(t == "as")    return MAPINFO_TYPE_ASSAULT;
+	else if(t == "ons")   return MAPINFO_TYPE_ONSLAUGHT;
+	else if(t == "all")   return MAPINFO_TYPE_ALL;
+	else                  return 0;
+}
+
+// load info about a map by name into the MapInfo_Map_* globals
+float MapInfo_Get_ByName(string pFilename, float pAllowGenerate, float pGametypeToSet)
+{
+	string fn;
+	string s, t;
+	float fh, fh2;
+	float r, f;
+
+	if(pGametypeToSet == 0)
+		if(MapInfo_Cache_Retrieve(pFilename))
+			return 1;
+
+	r = 1;
+
+	MapInfo_Map_bspname = pFilename;
+
+	// default all generic fields so they have "good" values in case something fails
+	fn = strcat("maps/", pFilename, ".mapinfo");
+	fh = fopen(fn, FILE_READ);
+	if(fh < 0)
+	{
+		if(!pAllowGenerate)
+			return 0;
+		_MapInfo_Map_Reset();
+		r = _MapInfo_Generate(pFilename);
+		if(!r)
+			return 0;
+		fh = fopen(fn, FILE_WRITE);
+		fputs(fh, strcat("title ", MapInfo_Map_title, "\n"));
+		fputs(fh, strcat("description ", MapInfo_Map_description, "\n"));
+		fputs(fh, strcat("author ", MapInfo_Map_author, "\n"));
+		fputs(fh, strcat("_diameter ", ftos(MapInfo_Map_diameter), "\n"));
+		fputs(fh, strcat("_spawnpoints ", ftos(MapInfo_Map_spawnpoints), "\n"));
+		if(MapInfo_Map_supportedFeatures & MAPINFO_FEATURE_WEAPONS)       fputs(fh, "has weapons\n");
+		if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_DEATHMATCH)      fputs(fh, "type dm 30 20\n");
+		if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_TEAM_DEATHMATCH) fputs(fh, "type tdm 50 20 2\n");
+		if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_DOMINATION)      fputs(fh, "type dom 200 20\n");
+		if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_CTF)             fputs(fh, "type ctf 300 20\n");
+		if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_RUNEMATCH)       fputs(fh, "type rune 200 20\n");
+		if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_LMS)             fputs(fh, "type lms 9 20\n");
+		if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_ARENA)           fputs(fh, "type arena 10 20\n");
+		if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_KEYHUNT)         fputs(fh, "type kh 1000 20 3\n");
+		if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_ASSAULT)         fputs(fh, "type as 20\n");
+		if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_ONSLAUGHT)       fputs(fh, "type ons 20\n");
+
+		fh2 = fopen(strcat("scripts/", pFilename, ".arena"), FILE_READ);
+		if(fh2 >= 0)
+		{
+			fclose(fh2);
+			fputs(fh, "settemp_for_type all sv_q3acompat_machineshotgunswap 1\n");
+		}
+
+		fclose(fh);
+		r = 2;
+		// return r;
+		fh = fopen(fn, FILE_READ);
+		if(fh < 0)
+			error("... but I just wrote it!");
+	}
+
+	_MapInfo_Map_Reset();
+	for(;;)
+	{
+		if not((s = fgets(fh)))
+			break;
+		t = car(s); s = cdr(s);
+		if     (t == "title")
+			MapInfo_Map_title = s;
+		else if(t == "description")
+			MapInfo_Map_description = s;
+		else if(t == "author")
+			MapInfo_Map_author = s;
+		else if(t == "_diameter")
+			MapInfo_Map_diameter = stof(s);
+		else if(t == "_spawnpoints")
+			MapInfo_Map_spawnpoints = stof(s);
+		else if(t == "has")
+		{
+			t = car(s); s = cdr(s);
+			if     (t == "weapons") MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_WEAPONS;
+			else
+				dprint("Map ", pFilename, " supports unknown feature ", t, ", ignored\n");
+		}
+		else if(t == "type")
+		{
+			t = car(s); s = cdr(s);
+			f = MapInfo_Type_FromString(t);
+			if(f)
+				_MapInfo_Map_ApplyGametype (s, pGametypeToSet, f);
+			else
+				dprint("Map ", pFilename, " supports unknown game type ", t, ", ignored\n");
+		}
+		else if(t == "settemp_for_type")
+		{
+			t = car(s); s = cdr(s);
+			if((f = MapInfo_Type_FromString(t)))
+			{
+				if(f & pGametypeToSet)
+				{
+					t = car(s); s = cdr(s);
+					if(strstrofs(t, "\"", 0) >= 0)
+						print("Map ", pFilename, " contains a potentially harmful setting, ignored\n");
+					else if(strstrofs(t, "\\", 0) >= 0)
+						print("Map ", pFilename, " contains a potentially harmful setting, ignored\n");
+					else if(strstrofs(t, ";", 0) >= 0)
+						print("Map ", pFilename, " contains a potentially harmful setting, ignored\n");
+					else if(strstrofs(s, "\"", 0) >= 0)
+						print("Map ", pFilename, " contains a potentially harmful setting, ignored\n");
+					else if(strstrofs(s, "\\", 0) >= 0)
+						print("Map ", pFilename, " contains a potentially harmful setting, ignored\n");
+					else if(strstrofs(s, ";", 0) >= 0)
+						print("Map ", pFilename, " contains a potentially harmful setting, ignored\n");
+					else
+					{
+						dprint("Applying temporary setting ", t, " := ", s, "\n");
+						cvar_settemp(t, s);
+					}
+				}
+			}
+			else
+			{
+				dprint("Map ", pFilename, " has a setting for unknown game type ", t, ", ignored\n");
+			}
+		}
+		else
+			dprint("Map ", pFilename, " provides unknown info item ", t, ", ignored\n");
+	}
+	fclose(fh);
+	if(pGametypeToSet)
+		if(!(MapInfo_Map_supportedGametypes & pGametypeToSet))
+			error("Can't select the requested game type. Bailing out.");
+	MapInfo_Cache_Store();
+	if(MapInfo_Map_supportedGametypes != 0)
+		return r;
+	dprint("Map ", pFilename, " supports no game types, ignored\n");
+	return 0;
+}
+
+float MapInfo_FindName(string s)
+{
+	// if there is exactly one map of prefix s, return it
+	// if not, return the null string
+	// note that DP sorts glob results... so I can use a binary search
+	float l, r, m, cmp;
+	l = 0;
+	r = MapInfo_count;
+	// invariants: r is behind s, l-1 is equal or before
+	while(l != r)
+	{
+		m = floor((l + r) / 2);
+		MapInfo_FindName_match = _MapInfo_GlobItem(MapInfo_FilterList_Lookup(m));
+		cmp = strcasecmp(MapInfo_FindName_match, s);
+		if(cmp == 0)
+			return m; // found and good
+		if(cmp < 0)
+			l = m + 1; // l-1 is before s
+		else
+			r = m; // behind s
+	}
+	MapInfo_FindName_match = _MapInfo_GlobItem(MapInfo_FilterList_Lookup(l));
+	MapInfo_FindName_firstResult = l;
+	// r == l, so: l is behind s, l-1 is before
+	// SO: if there is any, l is the one with the right prefix
+	//     and l+1 may be one too
+	if(l == MapInfo_count)
+	{
+		MapInfo_FindName_match = string_null;
+		MapInfo_FindName_firstResult = -1;
+		return -1; // no MapInfo_FindName_match, behind last item
+	}
+	if(!startsWithNocase(MapInfo_FindName_match, s))
+	{
+		MapInfo_FindName_match = string_null;
+		MapInfo_FindName_firstResult = -1;
+		return -1; // wrong prefix
+	}
+	if(l == MapInfo_count - 1)
+		return l; // last one, nothing can follow => unique
+	if(startsWithNocase(_MapInfo_GlobItem(MapInfo_FilterList_Lookup(l + 1)), s))
+	{
+		MapInfo_FindName_match = string_null;
+		return -1; // ambigous MapInfo_FindName_match
+	}
+	return l;
+}
+
+string MapInfo_FixName(string s)
+{
+	MapInfo_FindName(s);
+	return MapInfo_FindName_match;
+}
+
+float MapInfo_CurrentFeatures()
+{
+	float req;
+	req = 0;
+	if(!(cvar("g_lms") || cvar("g_instagib") || cvar("g_minstagib") || cvar("g_nixnex") || cvar("g_rocketarena")))
+		req |= MAPINFO_FEATURE_WEAPONS;
+	return req;
+}
+
+float MapInfo_CurrentGametype()
+{
+	if(cvar("g_domination"))
+		return MAPINFO_TYPE_DOMINATION;
+	else if(cvar("g_ctf"))
+		return MAPINFO_TYPE_CTF;
+	else if(cvar("g_runematch"))
+		return MAPINFO_TYPE_RUNEMATCH;
+	else if(cvar("g_tdm"))
+		return MAPINFO_TYPE_TEAM_DEATHMATCH;
+	else if(cvar("g_assault"))
+		return MAPINFO_TYPE_ASSAULT;
+	else if(cvar("g_lms"))
+		return MAPINFO_TYPE_LMS;
+	else if(cvar("g_arena"))
+		return MAPINFO_TYPE_ARENA;
+	else if(cvar("g_keyhunt"))
+		return MAPINFO_TYPE_KEYHUNT;
+	else if(cvar("g_onslaught"))
+		return MAPINFO_TYPE_ONSLAUGHT;
+	else
+		return MAPINFO_TYPE_DEATHMATCH;
+}
+
+float _MapInfo_CheckMap(string s) // returns 0 if the map can't be played with the current settings, 1 otherwise
+{
+	if(!MapInfo_Get_ByName(s, 1, 0))
+		return 0;
+	if((MapInfo_Map_supportedGametypes & MapInfo_CurrentGametype()) == 0)
+		return 0;
+	if((MapInfo_Map_supportedFeatures & MapInfo_CurrentFeatures()) != MapInfo_CurrentFeatures())
+		return 0;
+	return 1;
+}
+
+float MapInfo_CheckMap(string s) // returns 0 if the map can't be played with the current settings, 1 otherwise
+{
+	float r;
+	r = _MapInfo_CheckMap(s);
+	MapInfo_ClearTemps();
+	return r;
+}
+
+void MapInfo_SwitchGameType(float t)
+{
+	cvar_set("gamecfg",      "0");
+	cvar_set("g_dm",         (t == MAPINFO_TYPE_DEATHMATCH)      ? "1" : "0");
+	cvar_set("g_tdm",        (t == MAPINFO_TYPE_TEAM_DEATHMATCH) ? "1" : "0");
+	cvar_set("g_domination", (t == MAPINFO_TYPE_DOMINATION)      ? "1" : "0");
+	cvar_set("g_ctf",        (t == MAPINFO_TYPE_CTF)             ? "1" : "0");
+	cvar_set("g_runematch",  (t == MAPINFO_TYPE_RUNEMATCH)       ? "1" : "0");
+	cvar_set("g_lms",        (t == MAPINFO_TYPE_LMS)             ? "1" : "0");
+	cvar_set("g_arena",      (t == MAPINFO_TYPE_ARENA)           ? "1" : "0");
+	cvar_set("g_keyhunt",    (t == MAPINFO_TYPE_KEYHUNT)         ? "1" : "0");
+	cvar_set("g_assault",    (t == MAPINFO_TYPE_ASSAULT)         ? "1" : "0");
+	cvar_set("g_onslaught",  (t == MAPINFO_TYPE_ONSLAUGHT)       ? "1" : "0");
+}
+
+void MapInfo_LoadMap(string s)
+{
+	MapInfo_Map_supportedGametypes = 0;
+	if(!MapInfo_CheckMap(s))
+	{
+		print("EMERGENCY: can't play the selected map in the given game mode. Falling back to DM.\n");
+		MapInfo_SwitchGameType(MAPINFO_TYPE_DEATHMATCH);
+	}
+	localcmd(strcat("\nsettemp_restore\nchangelevel ", s, "\n"));
+}
+
+string MapInfo_ListAllowedMaps()
+{
+	string out;
+	float i;
+
+	// to make absolutely sure:
+	MapInfo_Enumerate();
+	MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), 0);
+
+	out = "";
+	for(i = 0; i < MapInfo_count; ++i)
+		out = strcat(out, " ", _MapInfo_GlobItem(MapInfo_FilterList_Lookup(i)));
+	return substring(out, 1, strlen(out) - 1);
+}
+
+void MapInfo_LoadMapSettings(string s) // to be called from worldspawn
+{
+	float t;
+	if(!_MapInfo_CheckMap(s)) // with underscore, it keeps temps
+	{
+		if(MapInfo_Map_supportedGametypes <= 0)
+			error("Mapinfo system is not functional at all. BAILED OUT.\n");
+
+		t = 1;
+		while(!(MapInfo_Map_supportedGametypes & 1))
+		{
+			t *= 2;
+			MapInfo_Map_supportedGametypes = floor(MapInfo_Map_supportedGametypes / 2);
+		}
+		// t is now a supported mode!
+		print("EMERGENCY: can't play the selected map in the given game mode. Falling back to a supported mode.\n");
+		MapInfo_SwitchGameType(t);
+	}
+	cvar_settemp_restore();
+	MapInfo_Get_ByName(s, 1, MapInfo_CurrentGametype());
+	MapInfo_ClearTemps();
+}
+
+void MapInfo_ClearTemps()
+{
+	MapInfo_Map_bspname = string_null;
+	MapInfo_Map_title = string_null;
+	MapInfo_Map_description = string_null;
+	MapInfo_Map_author = string_null;
+	MapInfo_Map_supportedGametypes = 0;
+	MapInfo_Map_supportedFeatures = 0;
+	MapInfo_Map_diameter = 0;
+	MapInfo_Map_spawnpoints = 0;
+}

Added: trunk/common/mapinfo.qh
===================================================================
--- trunk/common/mapinfo.qh	                        (rev 0)
+++ trunk/common/mapinfo.qh	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,68 @@
+float MAPINFO_TYPE_DEATHMATCH		= 1;
+float MAPINFO_TYPE_TEAM_DEATHMATCH	= 2;
+float MAPINFO_TYPE_DOMINATION		= 4;
+float MAPINFO_TYPE_CTF				= 8;
+float MAPINFO_TYPE_RUNEMATCH		= 16;
+float MAPINFO_TYPE_LMS				= 32;
+float MAPINFO_TYPE_ARENA			= 64;
+float MAPINFO_TYPE_KEYHUNT			= 128;
+float MAPINFO_TYPE_ASSAULT			= 256;
+float MAPINFO_TYPE_ONSLAUGHT		= 512;
+float MAPINFO_TYPE_ALL              = 65535; // this has to include all above bits
+
+float MAPINFO_FEATURE_WEAPONS       = 1; // not defined for minstagib-only maps
+
+float MapInfo_count;
+
+// info about a map that MapInfo loads
+string MapInfo_Map_bspname;
+string MapInfo_Map_title;
+string MapInfo_Map_description;
+string MapInfo_Map_author;
+float MapInfo_Map_supportedGametypes;
+float MapInfo_Map_supportedFeatures;
+float MapInfo_Map_diameter;
+float MapInfo_Map_spawnpoints;
+
+// load MapInfo_count; generate mapinfo for maps that miss them, and clear the
+// cache; you need to call MapInfo_FilterGametype afterwards!
+void MapInfo_Enumerate();
+
+// filter the info by game type mask (updates MapInfo_count)
+float MapInfo_progress;
+float MapInfo_FilterGametype(float gametype, float features, float pAbortOnGenerate); // 1 on success, 0 on temporary failure (call it again next frame then; use MapInfo_progress as progress indicator)
+float MapInfo_CurrentFeatures(); // retrieves currently required features from cvars
+float MapInfo_CurrentGametype(); // retrieves current gametype from cvars
+
+// load info about the i-th map into the MapInfo_Map_* globals
+float MapInfo_Get_ByID(float i); // 1 on success, 0 on failure
+string MapInfo_BSPName_ByID(float i);
+
+// load info about a map by name into the MapInfo_Map_* globals
+float MapInfo_Get_ByName(string s, float allowGenerate, float gametypeToSet); // 1 on success, 0 on failure, 2 if it autogenerated a mapinfo file
+
+// look for a map by a prefix, returns the actual map name on success, string_null on failure or ambigous match
+string MapInfo_FindName_match; // the name of the map that was found
+float MapInfo_FindName_firstResult; // -1 if none were found, index of first one if not unique but found (FindName then returns -1)
+float MapInfo_FindName(string s);
+string MapInfo_FixName(string s);
+
+// play a map
+float MapInfo_CheckMap(string s); // returns 0 if the map can't be played with the current settings
+void MapInfo_LoadMap(string s);
+
+// list all maps for the current game type
+string MapInfo_ListAllowedMaps();
+
+// gets a gametype from a string
+float MapInfo_Type_FromString(string t);
+void MapInfo_SwitchGameType(float t);
+
+// to be called from worldspawn to set up cvars
+void MapInfo_LoadMapSettings(string s);
+
+void MapInfo_Cache_Destroy(); // disable caching
+void MapInfo_Cache_Create(); // enable caching
+void MapInfo_Cache_Invalidate(); // delete cache if any, but keep enabled
+
+void MapInfo_ClearTemps(); // call this when done with mapinfo for this frame

Added: trunk/common/util.qc
===================================================================
--- trunk/common/util.qc	                        (rev 0)
+++ trunk/common/util.qc	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,378 @@
+string wordwrap_buffer;
+
+void wordwrap_buffer_put(string s)
+{
+	wordwrap_buffer = strcat(wordwrap_buffer, s);
+}
+
+string wordwrap(string s, float l)
+{
+	string r;
+	wordwrap_buffer = "";
+	wordwrap_cb(s, l, wordwrap_buffer_put);
+	r = wordwrap_buffer;
+	wordwrap_buffer = "";
+	return r;
+}
+
+#ifndef MENUQC
+void wordwrap_buffer_sprint(string s)
+{
+	wordwrap_buffer = strcat(wordwrap_buffer, s);
+	if(s == "\n")
+	{
+		sprint(self, wordwrap_buffer);
+		wordwrap_buffer = "";
+	}
+}
+
+void wordwrap_sprint(string s, float l)
+{
+	wordwrap_buffer = "";
+	wordwrap_cb(s, l, wordwrap_buffer_sprint);
+	if(wordwrap_buffer != "")
+		sprint(self, strcat(wordwrap_buffer, "\n"));
+	wordwrap_buffer = "";
+	return;
+}
+#endif
+
+void wordwrap_cb(string s, float l, void(string) callback)
+{
+	local string c;
+	local float lleft, i, j, wlen;
+
+	s = strzone(s);
+	lleft = l;
+	for (i = 0;i < strlen(s);i++)
+	{
+		if (substring(s, i, 2) == "\\n")
+		{
+			callback("\n");
+			lleft = l;
+			i++;
+		}
+		else if (substring(s, i, 1) == "\n")
+		{
+			callback("\n");
+			lleft = l;
+		}
+		else if (substring(s, i, 1) == " ")
+		{
+			if (lleft > 0)
+			{
+				callback(" ");
+				lleft = lleft - 1;
+			}
+		}
+		else
+		{
+			for (j = i+1;j < strlen(s);j++)
+				//    ^^ this skips over the first character of a word, which
+				//       is ALWAYS part of the word
+				//       this is safe since if i+1 == strlen(s), i will become
+				//       strlen(s)-1 at the end of this block and the function
+				//       will terminate. A space can't be the first character we
+				//       read here, and neither can a \n be the start, since these
+				//       two cases have been handled above.
+			{
+				c = substring(s, j, 1);
+				if (c == " ")
+					break;
+				if (c == "\\")
+					break;
+				if (c == "\n")
+					break;
+				// we need to keep this tempstring alive even if substring is
+				// called repeatedly, so call strcat even though we're not
+				// doing anything
+				callback("");
+			}
+			wlen = j - i;
+			if (lleft < wlen)
+			{
+				callback("\n");
+				lleft = l;
+			}
+			callback(substring(s, i, wlen));
+			lleft = lleft - wlen;
+			i = j - 1;
+		}
+	}
+	strunzone(s);
+}
+
+float dist_point_line(vector p, vector l0, vector ldir)
+{
+	ldir = normalize(ldir);
+	
+	// remove the component in line direction
+	p = p - (p * ldir) * ldir;
+
+	// vlen of the remaining vector
+	return vlen(p);
+}
+
+void depthfirst(entity start, .entity up, .entity downleft, .entity right, void(entity, entity) funcPre, void(entity, entity) funcPost, entity pass)
+{
+	entity e;
+	e = start;
+	funcPre(pass, e);
+	while(e.downleft)
+	{
+		e = e.downleft;
+		funcPre(pass, e);
+	}
+	funcPost(pass, e);
+	while(e != start)
+	{
+		if(e.right)
+		{
+			e = e.right;
+			funcPre(pass, e);
+			while(e.downleft)
+			{
+				e = e.downleft;
+				funcPre(pass, e);
+			}
+		}
+		else
+			e = e.up;
+		funcPost(pass, e);
+	}
+}
+
+float median(float a, float b, float c)
+{
+	if(a < c)
+		return bound(a, b, c);
+	return bound(c, b, a);
+}
+
+// converts a number to a string with the indicated number of decimals
+// works for up to 10 decimals!
+string ftos_decimals(float number, float decimals)
+{
+	string result;
+	string tmp;
+	float len;
+
+	// if negative, cut off the sign first
+	if(number < 0)
+		return strcat("-", ftos_decimals(-number, decimals));
+	// it now is always positive!
+
+	// 3.516 -> 352
+	number = floor(number * pow(10, decimals) + 0.5);
+
+	// 352 -> "352"
+	result = ftos(number);
+	len = strlen(result);
+	// does it have a decimal point (should not happen)? If there is one, it is always at len-7)
+		// if ftos had fucked it up, which should never happen: "34278.000000"
+	if(len >= 7)
+		if(substring(result, len - 7, 1) == ".")
+		{
+			dprint("ftos(integer) has comma? Can't be. Affected result: ", result, "\n");
+			result = substring(result, 0, len - 7);
+			len -= 7;
+		}
+		// "34278"
+	if(decimals == 0)
+		return result; // don't insert a point for zero decimals
+	// is it too short? If yes, insert leading zeroes
+	if(len <= decimals)
+	{
+		result = strcat(substring("0000000000", 0, decimals - len + 1), result);
+		len = decimals + 1;
+	}
+	// and now... INSERT THE POINT!
+	tmp = substring(result, len - decimals, decimals);
+	result = strcat(substring(result, 0, len - decimals), ".", tmp);
+	return result;
+}
+
+float time;
+vector colormapPaletteColor(float c, float isPants)
+{
+	switch(c)
+	{
+		case  0: return '0.733 0.733 0.733';
+		case  1: return '0.451 0.341 0.122';
+		case  2: return '0.000 0.733 0.733';
+		case  3: return '0.000 1.000 0.000';
+		case  4: return '1.000 0.000 0.000';
+		case  5: return '0.000 0.502 1.000';
+		case  6: return '0.812 0.561 0.169';
+		case  7: return '0.718 0.529 0.420';
+		case  8: return '0.765 0.545 0.667';
+		case  9: return '1.000 0.000 1.000';
+		case 10: return '0.639 0.529 0.482';
+		case 11: return '0.310 0.388 0.341';
+		case 12: return '1.000 1.000 0.000';
+		case 13: return '0.000 0.000 1.000';
+		case 14: return '1.000 0.502 0.000';
+		case 15:
+			if(isPants)
+				return
+					  '1 0 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 0.0000000000))
+					+ '0 1 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 2.0943951024))
+					+ '0 0 1' * (0.502 + 0.498 * sin(time / 2.7182818285 + 4.1887902048));
+			else
+				return
+					  '1 0 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 5.2359877560))
+					+ '0 1 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 3.1415926536))
+					+ '0 0 1' * (0.502 + 0.498 * sin(time / 3.1415926536 + 1.0471975512));
+		default: return '0.000 0.000 0.000';
+	}
+}
+
+// unzone the string, and return it as tempstring. Safe to be called on string_null
+string fstrunzone(string s)
+{
+	string sc;
+	if not(s)
+		return s;
+	sc = strcat(s, "");
+	strunzone(s);
+	return sc;
+}
+
+// Databases (hash tables)
+#define DB_BUCKETS 8192
+void db_save(float db, string pFilename)
+{
+	float fh, i, n;
+	fh = fopen(pFilename, FILE_WRITE);
+	if(fh < 0)
+		error(strcat("Can't write DB to ", pFilename));
+	n = buf_getsize(db);
+	fputs(fh, strcat(ftos(DB_BUCKETS), "\n"));
+	for(i = 0; i < n; ++i)
+		fputs(fh, strcat(bufstr_get(db, i), "\n"));
+	fclose(fh);
+}
+
+float db_create()
+{
+	return buf_create();
+}
+
+float db_load(string pFilename)
+{
+	float db, fh, i, j, n;
+	string l;
+	db = buf_create();
+	if(db < 0)
+		return -1;
+	fh = fopen(pFilename, FILE_READ);
+	if(fh < 0)
+		return db;
+	if(stof(fgets(fh)) == DB_BUCKETS)
+	{
+		i = 0;
+		while((l = fgets(fh)))
+		{
+			if(l != "")
+				bufstr_set(db, i, l);
+			++i;
+		}
+	}
+	else
+	{
+		// different count of buckets?
+		// need to reorganize the database then (SLOW)
+		while((l = fgets(fh)))
+		{
+			n = tokenizebyseparator(l, "\\");
+			for(j = 2; j < n; j += 2)
+				db_put(db, argv(j-1), argv(j));
+		}
+	}
+	fclose(fh);
+	return db;
+}
+
+void db_dump(float db, string pFilename)
+{
+	float fh, i, j, n, m;
+	fh = fopen(pFilename, FILE_WRITE);
+	if(fh < 0)
+		error(strcat("Can't dump DB to ", pFilename));
+	n = buf_getsize(db);
+	fputs(fh, "0\n");
+	for(i = 0; i < n; ++i)
+	{
+		m = tokenizebyseparator(bufstr_get(db, i), "\\");
+		for(j = 2; j < m; j += 2)
+			fputs(fh, strcat("\\", argv(j-1), "\\", argv(j), "\n"));
+	}
+	fclose(fh);
+}
+
+void db_close(float db)
+{
+	buf_del(db);
+}
+
+string db_get(float db, string pKey)
+{
+	float h;
+	h = mod(crc16(FALSE, pKey), DB_BUCKETS);
+	return infoget(bufstr_get(db, h), pKey);
+}
+
+void db_put(float db, string pKey, string pValue)
+{
+	float h;
+	h = mod(crc16(FALSE, pKey), DB_BUCKETS);
+	bufstr_set(db, h, infoadd(bufstr_get(db, h), pKey, pValue));
+}
+
+void db_test()
+{
+	float db, i;
+	print("LOAD...\n");
+	db = db_load("foo.db");
+	print("LOADED. FILL...\n");
+	for(i = 0; i < DB_BUCKETS; ++i)
+		db_put(db, ftos(random()), "X");
+	print("FILLED. SAVE...\n");
+	db_save(db, "foo.db");
+	print("SAVED. CLOSE...\n");
+	db_close(db);
+	print("CLOSED.\n");
+}
+
+// Multiline text file buffers
+float buf_load(string pFilename)
+{
+	float buf, fh, i;
+	string l;
+	buf = buf_create();
+	if(buf < 0)
+		return -1;
+	fh = fopen(pFilename, FILE_READ);
+	if(fh < 0)
+		return buf;
+	i = 0;
+	while((l = fgets(fh)))
+	{
+		bufstr_set(buf, i, l);
+		++i;
+	}
+	fclose(fh);
+	return buf;
+}
+
+void buf_save(float buf, string pFilename)
+{
+	float fh, i, n;
+	fh = fopen(pFilename, FILE_WRITE);
+	if(fh < 0)
+		error(strcat("Can't write buf to ", pFilename));
+	n = buf_getsize(buf);
+	for(i = 0; i < n; ++i)
+		fputs(fh, strcat(bufstr_get(buf, i), "\n"));
+	fclose(fh);
+}

Added: trunk/common/util.qh
===================================================================
--- trunk/common/util.qh	                        (rev 0)
+++ trunk/common/util.qh	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,55 @@
+// note: this is in util.qh so it is included as early as possible.
+var void(string s, ...) dprint;
+void dprint_null() { }
+void dprint_load()
+{
+	if(cvar("developer") > 0)
+		dprint = print;
+	else
+		dprint = dprint_null;
+}
+
+// this returns a tempstring containing a copy of s with additional \n newlines added, it also replaces \n in the text with a real newline
+// NOTE: s IS allowed to be a tempstring
+string wordwrap(string s, float l);
+#ifndef MENUQC
+void wordwrap_sprint(string s, float l);
+#endif
+void wordwrap_cb(string s, float l, void(string) callback)
+
+float GameCommand_Generic(string cmd);
+// returns TRUE if handled, FALSE otherwise
+// uses tokenize on its argument!
+
+// iterative depth-first search, with fields that go "up", "down left" and "right" in a tree
+// for each element, funcPre is called first, then funcPre and funcPost for all its children, and funcPost last
+void depthfirst(entity start, .entity up, .entity downleft, .entity right, void(entity, entity) funcPre, void(entity, entity) funcPost, entity pass);
+
+float median(float a, float b, float c);
+
+// converts a number to a string with the indicated number of decimals
+// works for up to 10 decimals!
+string ftos_decimals(float number, float decimals);
+
+vector colormapPaletteColor(float c, float isPants);
+
+// unzone the string, and return it as tempstring. Safe to be called on string_null
+string fstrunzone(string s);
+
+// database (NOTE: keys are case sensitive)
+void db_save(float db, string filename);
+void db_dump(float db, string pFilename);
+float db_create();
+float db_load(string filename);
+void db_close(float db);
+string db_get(float db, string key);
+void db_put(float db, string key, string value);
+
+// stringbuffer loading/saving
+float buf_load(string filename);
+void buf_save(float buf, string filename);
+
+// modulo function
+#ifndef MENUQC
+float mod(float a, float b) { return a - (floor(a / b) * b); }   
+#endif

Added: trunk/menu/classes.c
===================================================================
--- trunk/menu/classes.c	                        (rev 0)
+++ trunk/menu/classes.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,67 @@
+#include "item.c"
+#include "item/container.c"
+#include "item/inputcontainer.c"
+#include "item/nexposee.c"
+#include "item/modalcontroller.c"
+#include "item/image.c"
+#include "item/label.c"
+#include "item/button.c"
+#include "item/checkbox.c"
+#include "item/radiobutton.c"
+#include "item/borderimage.c"
+#include "item/slider.c"
+#include "item/dialog.c"
+#include "item/tab.c"
+#include "item/textslider.c"
+#include "item/listbox.c"
+#include "item/inputbox.c"
+#include "item/gecko.c"
+#include "nexuiz/dialog.c"
+#include "nexuiz/tab.c"
+#include "nexuiz/mainwindow.c"
+#include "nexuiz/button.c"
+#include "nexuiz/commandbutton.c"
+#include "nexuiz/dialog_teamselect.c"
+#include "nexuiz/dialog_classselect.c"
+#include "nexuiz/dialog_infoscreen.c"
+#include "nexuiz/dialog_settings.c"
+#include "nexuiz/dialog_settings_video.c"
+#include "nexuiz/dialog_settings_effects.c"
+#include "nexuiz/dialog_settings_misc.c"
+#include "nexuiz/dialog_multiplayer.c"
+#include "nexuiz/dialog_multiplayer_playersetup.c"
+#include "nexuiz/tabcontroller.c"
+#include "nexuiz/textlabel.c"
+#include "nexuiz/slider.c"
+#include "nexuiz/slider_resolution.c"
+#include "nexuiz/checkbox.c"
+#include "nexuiz/radiobutton.c"
+#include "nexuiz/nexposee.c"
+#include "nexuiz/rootdialog.c"
+#include "nexuiz/textslider.c"
+#include "nexuiz/colorbutton.c"
+#include "nexuiz/dialog_multiplayer_join.c"
+#include "nexuiz/listbox.c"
+#include "nexuiz/serverlist.c"
+#include "nexuiz/inputbox.c"
+#include "nexuiz/dialog_quit.c"
+#include "nexuiz/dialog_multiplayer_create.c"
+#include "nexuiz/dialog_multiplayer_create_mutators.c"
+#include "nexuiz/dialog_multiplayer_create_mapinfo.c"
+#include "nexuiz/gametypebutton.c"
+#include "nexuiz/maplist.c"
+#include "nexuiz/image.c"
+#include "nexuiz/crosshairbutton.c"
+#include "nexuiz/playermodel.c"
+#include "nexuiz/dialog_news.c"
+#include "nexuiz/checkbox_slider_invalid.c"
+#include "nexuiz/charmap.c"
+#include "nexuiz/keybinder.c"
+#include "nexuiz/dialog_settings_input.c"
+#include "nexuiz/dialog_settings_input_userbind.c"
+#include "nexuiz/slider_decibels.c"
+#include "nexuiz/dialog_singleplayer.c"
+#include "nexuiz/campaign.c"
+#include "nexuiz/dialog_singleplayer_winner.c"
+#include "nexuiz/dialog_credits.c"
+#include "nexuiz/credits.c"

Added: trunk/menu/config.qh
===================================================================
--- trunk/menu/config.qh	                        (rev 0)
+++ trunk/menu/config.qh	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,4 @@
+// build config file
+#define MENUQC // so common/*.qc can check for menu QC or game QC
+// #define MAPINFO // maybe later, when it is the default in server QC
+

Added: trunk/menu/draw.qc
===================================================================
--- trunk/menu/draw.qc	                        (rev 0)
+++ trunk/menu/draw.qc	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,318 @@
+string draw_mousepointer;
+vector draw_mousepointer_offset;
+vector draw_mousepointer_size;
+
+string draw_UseSkinFor(string pic)
+{
+	if(substring(pic, 0, 1) == "/")
+		return substring(pic, 1, strlen(pic)-1);
+	else
+		return strcat(draw_currentSkin, "/", pic);
+}
+
+void draw_setMousePointer(string pic, vector theSize, vector theOffset)
+{
+	draw_mousepointer = strzone(draw_UseSkinFor(pic));
+	draw_mousepointer_size = theSize;
+	draw_mousepointer_offset = eX * (theOffset_x * theSize_x) + eY * (theOffset_y * theSize_y);
+}
+
+void draw_drawMousePointer(vector where)
+{
+	drawpic(boxToGlobal(where, draw_shift, draw_scale) - draw_mousepointer_offset, draw_mousepointer, draw_mousepointer_size, '1 1 1', draw_alpha, 0);
+}
+
+void draw_reset()
+{
+	drawfont = FONT_USER+0;
+	draw_shift = '0 0 0';
+	draw_scale = '1 0 0' * cvar("vid_conwidth") + '0 1 0' * cvar("vid_conheight");
+	draw_alpha = 1;
+}
+
+vector globalToBox(vector v, vector theOrigin, vector theScale)
+{
+	v -= theOrigin;
+	v_x /= theScale_x;
+	v_y /= theScale_y;
+	return v;
+}
+
+vector globalToBoxSize(vector v, vector theScale)
+{
+	v_x /= theScale_x;
+	v_y /= theScale_y;
+	return v;
+}
+
+vector boxToGlobal(vector v, vector theOrigin, vector theScale)
+{
+	v_x *= theScale_x;
+	v_y *= theScale_y;
+	v += theOrigin;
+	return v;
+}
+
+vector boxToGlobalSize(vector v, vector theScale)
+{
+	v_x *= theScale_x;
+	v_y *= theScale_y;
+	return v;
+}
+
+void draw_PreloadPicture(string pic)
+{
+	pic = draw_UseSkinFor(pic);
+	precache_pic(pic);
+}
+
+void draw_Picture(vector theOrigin, string pic, vector theSize, vector theColor, float theAlpha)
+{
+	pic = draw_UseSkinFor(pic);
+	drawpic(boxToGlobal(theOrigin, draw_shift, draw_scale), pic, boxToGlobalSize(theSize, draw_scale), theColor, theAlpha * draw_alpha, 0);
+}
+
+vector draw_PictureSize(string pic)
+{
+	pic = draw_UseSkinFor(pic);
+	return drawgetimagesize(pic);
+}
+
+void draw_Fill(vector theOrigin, vector theSize, vector theColor, float theAlpha)
+{
+	drawfill(boxToGlobal(theOrigin, draw_shift, draw_scale), boxToGlobalSize(theSize, draw_scale), theColor, theAlpha * draw_alpha, 0);
+}
+
+// a button picture is a texture containing three parts:
+//   1/4 width: left part
+//   1/2 width: middle part (stretched)
+//   1/4 width: right part
+// it is assumed to be 4x as wide as high for aspect ratio purposes, which
+// means, the parts are a square, two squares and a square.
+void draw_ButtonPicture(vector theOrigin, string pic, vector theSize, vector theColor, float theAlpha)
+{
+	vector square;
+	vector width, height;
+	vector bW;
+	pic = draw_UseSkinFor(pic);
+	theOrigin = boxToGlobal(theOrigin, draw_shift, draw_scale);
+	theSize = boxToGlobalSize(theSize, draw_scale);
+	theAlpha *= draw_alpha;
+	width = eX * theSize_x;
+	height = eY * theSize_y;
+	if(theSize_x <= theSize_y * 2)
+	{
+		// button not wide enough
+		// draw just left and right part then
+		square = eX * theSize_x * 0.5;
+		bW = eX * (0.25 * theSize_x / (theSize_y * 2));
+		drawsubpic(theOrigin,          square + height, pic, '0 0 0', eY + bW, theColor, theAlpha, 0);
+		drawsubpic(theOrigin + square, square + height, pic, eX - bW, eY + bW, theColor, theAlpha, 0);
+	}
+	else
+	{
+		square = eX * theSize_y;
+		drawsubpic(theOrigin,                  height  +     square, pic, '0    0 0', '0.25 1 0', theColor, theAlpha, 0);
+		drawsubpic(theOrigin +         square, theSize - 2 * square, pic, '0.25 0 0', '0.5  1 0', theColor, theAlpha, 0);
+		drawsubpic(theOrigin + width - square, height  +     square, pic, '0.75 0 0', '0.25 1 0', theColor, theAlpha, 0);
+	}
+}
+
+// a vertical button picture is a texture containing three parts:
+//   1/4 height: left part
+//   1/2 height: middle part (stretched)
+//   1/4 height: right part
+// it is assumed to be 4x as high as wide for aspect ratio purposes, which
+// means, the parts are a square, two squares and a square.
+void draw_VertButtonPicture(vector theOrigin, string pic, vector theSize, vector theColor, float theAlpha)
+{
+	vector square;
+	vector width, height;
+	vector bH;
+	pic = draw_UseSkinFor(pic);
+	theOrigin = boxToGlobal(theOrigin, draw_shift, draw_scale);
+	theSize = boxToGlobalSize(theSize, draw_scale);
+	theAlpha *= draw_alpha;
+	width = eX * theSize_x;
+	height = eY * theSize_y;
+	if(theSize_y <= theSize_x * 2)
+	{
+		// button not high enough
+		// draw just upper and lower part then
+		square = eY * theSize_y * 0.5;
+		bH = eY * (0.25 * theSize_y / (theSize_x * 2));
+		drawsubpic(theOrigin,          square + width, pic, '0 0 0', eX + bH, theColor, theAlpha, 0);
+		drawsubpic(theOrigin + square, square + width, pic, eY - bH, eX + bH, theColor, theAlpha, 0);
+	}
+	else
+	{
+		square = eY * theSize_x;
+		drawsubpic(theOrigin,                   width   +     square, pic, '0 0    0', '1 0.25 0', theColor, theAlpha, 0);
+		drawsubpic(theOrigin +          square, theSize - 2 * square, pic, '0 0.25 0', '1 0.5  0', theColor, theAlpha, 0);
+		drawsubpic(theOrigin + height - square, width   +     square, pic, '0 0.75 0', '1 0.25 0', theColor, theAlpha, 0);
+	}
+}
+
+// a border picture is a texture containing nine parts:
+//   1/4 width: left part
+//   1/2 width: middle part (stretched)
+//   1/4 width: right part
+// divided into
+//   1/4 height: top part
+//   1/2 height: middle part (stretched)
+//   1/4 height: bottom part
+void draw_BorderPicture(vector theOrigin, string pic, vector theSize, vector theColor, float theAlpha, vector theBorderSize)
+{
+	vector dX, dY;
+	vector width, height;
+	vector bW, bH;
+	pic = draw_UseSkinFor(pic);
+	theOrigin = boxToGlobal(theOrigin, draw_shift, draw_scale);
+	theSize = boxToGlobalSize(theSize, draw_scale);
+	theBorderSize = boxToGlobalSize(theBorderSize, draw_scale);
+	theAlpha *= draw_alpha;
+	width = eX * theSize_x;
+	height = eY * theSize_y;
+	if(theSize_x <= theBorderSize_x * 2)
+	{
+		// not wide enough... draw just left and right then
+		bW = eX * (0.25 * theSize_x / (theBorderSize_x * 2));
+		if(theSize_y <= theBorderSize_y * 2)
+		{
+			// not high enough... draw just corners
+			bH = eY * (0.25 * theSize_y / (theBorderSize_y * 2));
+			drawsubpic(theOrigin,                 width * 0.5 + height * 0.5, pic, '0 0 0',           bW + bH, theColor, theAlpha, 0);
+			drawsubpic(theOrigin + width   * 0.5, width * 0.5 + height * 0.5, pic, eX - bW,           bW + bH, theColor, theAlpha, 0);
+			drawsubpic(theOrigin + height  * 0.5, width * 0.5 + height * 0.5, pic, eY - bH,           bW + bH, theColor, theAlpha, 0);
+			drawsubpic(theOrigin + theSize * 0.5, width * 0.5 + height * 0.5, pic, eX + eY - bW - bH, bW + bH, theColor, theAlpha, 0);
+		}
+		else
+		{
+			dY = theBorderSize_x * eY;
+			drawsubpic(theOrigin,                             width * 0.5          +     dY, pic, '0 0    0',           '0 0.25 0' + bW, theColor, theAlpha, 0);
+			drawsubpic(theOrigin + width * 0.5,               width * 0.5          +     dY, pic, '0 0    0' + eX - bW, '0 0.25 0' + bW, theColor, theAlpha, 0);
+			drawsubpic(theOrigin                        + dY, width * 0.5 + height - 2 * dY, pic, '0 0.25 0',           '0 0.5  0' + bW, theColor, theAlpha, 0);
+			drawsubpic(theOrigin + width * 0.5          + dY, width * 0.5 + height - 2 * dY, pic, '0 0.25 0' + eX - bW, '0 0.5  0' + bW, theColor, theAlpha, 0);
+			drawsubpic(theOrigin               + height - dY, width * 0.5          +     dY, pic, '0 0.75 0',           '0 0.25 0' + bW, theColor, theAlpha, 0);
+			drawsubpic(theOrigin + width * 0.5 + height - dY, width * 0.5          +     dY, pic, '0 0.75 0' + eX - bW, '0 0.25 0' + bW, theColor, theAlpha, 0);
+		}
+	}
+	else
+	{
+		if(theSize_y <= theBorderSize_y * 2)
+		{
+			// not high enough... draw just top and bottom then
+			bH = eY * (0.25 * theSize_y / (theBorderSize_y * 2));
+			dX = theBorderSize_x * eX;
+			drawsubpic(theOrigin,                                         dX + height * 0.5, pic, '0    0 0',           '0.25 0 0' + bH, theColor, theAlpha, 0);
+			drawsubpic(theOrigin + dX,                        width - 2 * dX + height * 0.5, pic, '0.25 0 0',           '0.5  0 0' + bH, theColor, theAlpha, 0);
+			drawsubpic(theOrigin + width - dX,                            dX + height * 0.5, pic, '0.75 0 0',           '0.25 0 0' + bH, theColor, theAlpha, 0);
+			drawsubpic(theOrigin              + height * 0.5,             dX + height * 0.5, pic, '0    0 0' + eY - bH, '0.25 0 0' + bH, theColor, theAlpha, 0);
+			drawsubpic(theOrigin + dX         + height * 0.5, width - 2 * dX + height * 0.5, pic, '0.25 0 0' + eY - bH, '0.5  0 0' + bH, theColor, theAlpha, 0);
+			drawsubpic(theOrigin + width - dX + height * 0.5,             dX + height * 0.5, pic, '0.75 0 0' + eY - bH, '0.25 0 0' + bH, theColor, theAlpha, 0);
+		}
+		else
+		{
+			dX = theBorderSize_x * eX;
+			dY = theBorderSize_x * eY;
+			drawsubpic(theOrigin,                                        dX          +     dY, pic, '0    0    0', '0.25 0.25 0', theColor, theAlpha, 0);
+			drawsubpic(theOrigin                  + dX,      width - 2 * dX          +     dY, pic, '0.25 0    0', '0.5  0.25 0', theColor, theAlpha, 0);
+			drawsubpic(theOrigin          + width - dX,                  dX          +     dY, pic, '0.75 0    0', '0.25 0.25 0', theColor, theAlpha, 0);
+			drawsubpic(theOrigin          + dY,                          dX + height - 2 * dY, pic, '0    0.25 0', '0.25 0.5  0', theColor, theAlpha, 0);
+			drawsubpic(theOrigin          + dY         + dX, width - 2 * dX + height - 2 * dY, pic, '0.25 0.25 0', '0.5  0.5  0', theColor, theAlpha, 0);
+			drawsubpic(theOrigin          + dY + width - dX,             dX + height - 2 * dY, pic, '0.75 0.25 0', '0.25 0.5  0', theColor, theAlpha, 0);
+			drawsubpic(theOrigin + height - dY,                          dX          +     dY, pic, '0    0.75 0', '0.25 0.25 0', theColor, theAlpha, 0);
+			drawsubpic(theOrigin + height - dY         + dX, width - 2 * dX          +     dY, pic, '0.25 0.75 0', '0.5  0.25 0', theColor, theAlpha, 0);
+			drawsubpic(theOrigin + height - dY + width - dX,             dX          +     dY, pic, '0.75 0.75 0', '0.25 0.25 0', theColor, theAlpha, 0);
+		}
+	}
+}
+void draw_Text(vector theOrigin, string theText, vector theSize, vector theColor, float theAlpha, float ICanHasKallerz)
+{
+	if(theSize_x <= 0 || theSize_y <= 0)
+		error("Drawing zero size text?\n");
+	if(ICanHasKallerz)
+		drawcolorcodedstring(boxToGlobal(theOrigin, draw_shift, draw_scale), theText, boxToGlobalSize(theSize, draw_scale), theAlpha * draw_alpha, 0);
+	else
+		drawstring(boxToGlobal(theOrigin, draw_shift, draw_scale), theText, boxToGlobalSize(theSize, draw_scale), theColor, theAlpha * draw_alpha, 0);
+}
+void draw_CenterText(vector theOrigin, string theText, vector theSize, vector theColor, float theAlpha, float ICanHasKallerz)
+{
+	draw_Text(theOrigin - eX * theSize_x * 0.5 * draw_TextWidth(theText, ICanHasKallerz), theText, theSize, theColor, theAlpha, ICanHasKallerz);
+}
+
+float draw_TextWidth(string theText, float ICanHasKallerz)
+{
+	//return strlen(theText);
+	//print("draw_TextWidth \"", theText, "\"\n");
+	return stringwidth(theText, ICanHasKallerz);
+}
+
+float draw_clipSet;
+void draw_SetClip()
+{
+	if(draw_clipSet)
+		error("Already clipping, no stack implemented here, sorry");
+	drawsetcliparea(draw_shift_x, draw_shift_y, draw_scale_x, draw_scale_y);
+	draw_clipSet = 1;
+}
+
+void draw_SetClipRect(vector theOrigin, vector theScale)
+{
+	vector o, s;
+	if(draw_clipSet)
+		error("Already clipping, no stack implemented here, sorry");
+	o = boxToGlobal(theOrigin, draw_shift, draw_scale);
+	s = boxToGlobalSize(theScale, draw_scale);
+	drawsetcliparea(o_x, o_y, s_x, s_y);
+	draw_clipSet = 1;
+}
+
+void draw_ClearClip()
+{
+	if(!draw_clipSet)
+		error("Not clipping, can't clear it then");
+	drawresetcliparea();
+	draw_clipSet = 0;
+}
+
+string draw_TextShortenToWidth(string theText, float maxWidth, float ICanHasKallerz)
+{
+	if(draw_TextWidth(theText, ICanHasKallerz) <= maxWidth)
+		return theText;
+	else
+		return strcat(substring(theText, 0, draw_TextLengthUpToWidth(theText, maxWidth - draw_TextWidth("...", ICanHasKallerz), ICanHasKallerz)), "...");
+}
+
+float draw_TextLengthUpToWidth(string theText, float maxWidth, float ICanHasKallerz)
+{
+	// STOP.
+	// The following function is SLOW.
+	// For your safety and for the protection of those around you...
+	// DO NOT CALL THIS AT HOME.
+	// No really, don't.
+	if(draw_TextWidth(theText, ICanHasKallerz) <= maxWidth)
+		return strlen(theText); // yeah!
+
+	// binary search for right place to cut string
+	float left, right, middle; // this always works
+	left = 0;
+	right = strlen(theText); // this always fails
+	do
+	{
+		middle = floor((left + right) / 2);
+		if(draw_TextWidth(substring(theText, 0, middle), ICanHasKallerz) <= maxWidth)
+			left = middle;
+		else
+			right = middle;
+	}
+	while(left < right - 1);
+
+	// NOTE: when color codes are involved, this binary search is,
+	// mathematically, BROKEN. However, it is obviously guaranteed to
+	// terminate, as the range still halves each time - but nevertheless, it is
+	// guaranteed that it finds ONE valid cutoff place (where "left" is in
+	// range, and "right" is outside).
+
+	return left;
+}

Added: trunk/menu/draw.qh
===================================================================
--- trunk/menu/draw.qh	                        (rev 0)
+++ trunk/menu/draw.qh	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,33 @@
+vector draw_shift;
+vector draw_scale;
+float draw_alpha;
+
+void draw_reset();
+void draw_setMousePointer(string pic, vector theSize, vector theOffset);
+void draw_drawMousePointer(vector where);
+
+void draw_PreloadPicture(string pic);
+void draw_ButtonPicture(vector theOrigin, string pic, vector theSize, vector theColor, float theAlpha);
+void draw_VertButtonPicture(vector theOrigin, string pic, vector theSize, vector theColor, float theAlpha);
+void draw_BorderPicture(vector theOrigin, string pic, vector theSize, vector theColor, float theAlpha, vector theBorderSize);
+void draw_Picture(vector origin, string pic, vector size, vector color, float alpha);
+vector draw_PictureSize(string pic);
+void draw_Fill(vector theOrigin, vector theSize, vector theColor, float theAlpha);
+void draw_Text(vector origin, string text, vector size, vector color, float alpha, float allowColorCodes);
+void draw_CenterText(vector origin, string text, vector size, vector color, float alpha, float allowColorCodes);
+float draw_TextWidth(string text, float allowColorCodes);
+string draw_TextShortenToWidth(string text, float maxWidth, float allowColorCodes);
+float draw_TextLengthUpToWidth(string text, float maxWidth, float allowColorCodes);
+
+void draw_SetClip();
+void draw_SetClipRect(vector theOrigin, vector theScale);
+void draw_ClearClip();
+
+vector boxToGlobal(vector v, vector shift, vector scale);
+vector boxToGlobalSize(vector v, vector scale);
+vector globalToBox(vector v, vector shift, vector scale);
+vector globalToBoxSize(vector v, vector scale);
+
+float draw_NeedResizeNotify;
+
+string draw_currentSkin;

Added: trunk/menu/gamecommand.qc
===================================================================
--- trunk/menu/gamecommand.qc	                        (rev 0)
+++ trunk/menu/gamecommand.qc	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,72 @@
+void GameCommand_Init()
+{
+	// make gg call menu QC theCommands
+	localcmd("alias qc_cmd \"menu_cmd $*\"\n");
+}
+
+string _dumptree_space;
+void _dumptree_open(entity pass, entity me)
+{
+	string s;
+	s = me.toString(me);
+	if(s == "")
+		s = me.classname;
+	else
+		s = strcat(me.classname, ": ", s);
+	print(_dumptree_space, etos(me), " (", s, ")");
+	if(me.firstChild)
+	{
+		print(" {\n");
+		_dumptree_space = strcat(_dumptree_space, "  ");
+	}
+	else
+		print("\n");
+}
+void _dumptree_close(entity pass, entity me)
+{
+	if(me.firstChild)
+	{
+		_dumptree_space = substring(_dumptree_space, 0, strlen(_dumptree_space) - 2);
+		print(_dumptree_space, "}\n");
+	}
+}
+
+void GameCommand(string theCommand)
+{
+	float argc;
+	argc = tokenize(theCommand);
+
+	if(argv(0) == "help" || argc == 0)
+	{
+		print("Usage: menu_cmd theCommand..., where possible theCommands are:\n");
+		print("  sync - reloads all cvars on the current menu page\n");
+		print("  directmenu ITEM - select a menu item as main item\n");
+		GameCommand_Generic("help");
+		return;
+	}
+
+	if(GameCommand_Generic(theCommand))
+		return;
+
+	if(argv(0) == "sync")
+	{
+		loadAllCvars(main);
+		return;
+	}
+
+	if(argv(0) == "directmenu") if(argc == 2)
+	{
+		// switch to a menu item
+		m_goto(argv(1));
+		return;
+	}
+
+	if(argv(0) == "dumptree")
+	{
+		_dumptree_space = "";
+		depthfirst(main, parent, firstChild, nextSibling, _dumptree_open, _dumptree_close, NULL);
+		return;
+	}
+
+	print("Invalid theCommand. For a list of supported theCommands, try menu_cmd help.\n");
+}

Added: trunk/menu/gamecommand.qh
===================================================================
--- trunk/menu/gamecommand.qh	                        (rev 0)
+++ trunk/menu/gamecommand.qh	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,2 @@
+void GameCommand_Init();
+void GameCommand(string command);

Added: trunk/menu/item/borderimage.c
===================================================================
--- trunk/menu/item/borderimage.c	                        (rev 0)
+++ trunk/menu/item/borderimage.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,87 @@
+#ifdef INTERFACE
+CLASS(BorderImage) EXTENDS(Label)
+	METHOD(BorderImage, configureBorderImage, void(entity, string, float, vector, string, float))
+	METHOD(BorderImage, resizeNotify, void(entity, vector, vector, vector, vector))
+	METHOD(BorderImage, draw, void(entity))
+	ATTRIB(BorderImage, src, string, string_null)
+	ATTRIB(BorderImage, borderHeight, float, 0)
+	ATTRIB(BorderImage, borderVec, vector, '0 0 0')
+	ATTRIB(BorderImage, color, vector, '1 1 1')
+	ATTRIB(BorderImage, closeButton, entity, NULL)
+	ATTRIB(BorderImage, realFontSize_Nexposeed, vector, '0 0 0')
+	ATTRIB(BorderImage, realOrigin_Nexposeed, vector, '0 0 0')
+	ATTRIB(BorderImage, isNexposeeTitleBar, float, 0)
+	ATTRIB(BorderImage, zoomedOutTitleBarPosition, float, 0)
+	ATTRIB(BorderImage, zoomedOutTitleBar, float, 0)
+ENDCLASS(BorderImage)
+#endif
+
+#ifdef IMPLEMENTATION
+void resizeNotifyBorderImage(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
+{
+	me.isNexposeeTitleBar = 0;
+	if(me.zoomedOutTitleBar)
+		if(me.parent.parent.instanceOfNexposee)
+			if(me.parent.instanceOfDialog)
+				if(me == me.parent.frame)
+					me.isNexposeeTitleBar = 1;
+	if(me.isNexposeeTitleBar)
+	{
+		vector scrs;
+		scrs = eX * conwidth + eY * conheight;
+		resizeNotifyLabel(me, relOrigin, relSize, boxToGlobal(me.parent.Nexposee_smallOrigin, '0 0 0', scrs), boxToGlobalSize(me.parent.Nexposee_smallSize, scrs));
+		me.realOrigin_y = me.realFontSize_y * me.zoomedOutTitleBarPosition;
+		me.realOrigin_Nexposeed = me.realOrigin;
+		me.realFontSize_Nexposeed = me.realFontSize;
+	}
+	resizeNotifyLabel(me, relOrigin, relSize, absOrigin, absSize);
+	me.borderVec = me.borderHeight / absSize_y * (eY + eX * (absSize_y / absSize_x));
+	me.realOrigin_y = 0.5 * (me.borderVec_y - me.realFontSize_y);
+	if(me.closeButton)
+	{
+		// move the close button to the right place
+		me.closeButton.Container_origin = '1 0 0' * (1 - me.borderVec_x);
+		me.closeButton.Container_size = me.borderVec;
+		me.closeButton.color = me.color;
+		me.closeButton.colorC = me.color;
+		me.closeButton.colorF = me.color;
+	}
+}
+void configureBorderImageBorderImage(entity me, string theTitle, float sz, vector theColor, string path, float theBorderHeight)
+{
+	me.configureLabel(me, theTitle, sz, 0.5);
+	me.src = path;
+	me.color = theColor;
+	me.borderHeight = theBorderHeight;
+}
+void drawBorderImage(entity me)
+{
+	//print(vtos(me.borderVec), "\n");
+
+	if(me.src)
+		draw_BorderPicture('0 0 0', me.src, '1 1 0', me.color, 1, me.borderVec);
+	if(me.fontSize > 0)
+	{
+		vector ro, rf;
+		if(me.isNexposeeTitleBar)
+		{
+			// me.parent.Nexposee_animationFactor 0 (small) or 1 (full)
+			// default values are for 1
+			ro = me.realOrigin;
+			rf = me.realFontSize;
+			me.realOrigin = ro * me.parent.Nexposee_animationFactor + me.realOrigin_Nexposeed * (1 - me.parent.Nexposee_animationFactor);
+			me.realFontSize = rf * me.parent.Nexposee_animationFactor + me.realFontSize_Nexposeed * (1 - me.parent.Nexposee_animationFactor);
+		}
+
+		drawLabel(me);
+
+		if(me.isNexposeeTitleBar)
+		{
+			// me.Nexposee_animationState 0 (small) or 1 (full)
+			// default values are for 1
+			me.realOrigin = ro;
+			me.realFontSize = rf;
+		}
+	}
+};
+#endif

Added: trunk/menu/item/button.c
===================================================================
--- trunk/menu/item/button.c	                        (rev 0)
+++ trunk/menu/item/button.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,162 @@
+#ifdef INTERFACE
+CLASS(Button) EXTENDS(Label)
+	METHOD(Button, configureButton, void(entity, string, float, string))
+	METHOD(Button, draw, void(entity))
+	METHOD(Button, showNotify, void(entity))
+	METHOD(Button, resizeNotify, void(entity, vector, vector, vector, vector))
+	METHOD(Button, keyDown, float(entity, float, float, float))
+	METHOD(Button, mousePress, float(entity, vector))
+	METHOD(Button, mouseDrag, float(entity, vector))
+	METHOD(Button, mouseRelease, float(entity, vector))
+	ATTRIB(Button, onClick, void(entity, entity), SUB_Null)
+	ATTRIB(Button, onClickEntity, entity, NULL)
+	ATTRIB(Button, src, string, string_null)
+	ATTRIB(Button, srcSuffix, string, string_null)
+	ATTRIB(Button, src2, string, string_null) // is centered, same aspect, and stretched to label size
+	ATTRIB(Button, src2scale, float, 1)
+	ATTRIB(Button, srcMulti, float, 1) // 0: button square left, text right; 1: button stretched, text over it
+	ATTRIB(Button, buttonLeftOfText, float, 0)
+	ATTRIB(Button, focusable, float, 1)
+	ATTRIB(Button, pressed, float, 0)
+	ATTRIB(Button, clickTime, float, 0)
+	ATTRIB(Button, disabled, float, 0)
+	ATTRIB(Button, disabledAlpha, float, 0.3)
+	ATTRIB(Button, forcePressed, float, 0)
+	ATTRIB(Button, color, vector, '1 1 1')
+	ATTRIB(Button, colorC, vector, '1 1 1')
+	ATTRIB(Button, colorF, vector, '1 1 1')
+	ATTRIB(Button, colorD, vector, '1 1 1')
+	ATTRIB(Button, color2, vector, '1 1 1')
+	ATTRIB(Button, alpha2, float, 1)
+
+	ATTRIB(Button, origin, vector, '0 0 0')
+	ATTRIB(Button, size, vector, '0 0 0')
+ENDCLASS(Button)
+#endif
+
+#ifdef IMPLEMENTATION
+void resizeNotifyButton(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
+{
+	if(me.srcMulti)
+		me.keepspaceLeft = 0;
+	else
+		me.keepspaceLeft = min(0.8, absSize_y / absSize_x);
+	resizeNotifyLabel(me, relOrigin, relSize, absOrigin, absSize);
+	me.origin = absOrigin;
+	me.size = absSize;
+}
+void configureButtonButton(entity me, string txt, float sz, string gfx)
+{
+	configureLabelLabel(me, txt, sz, me.srcMulti ? 0.5 : 0);
+	me.src = gfx;
+}
+float keyDownButton(entity me, float key, float ascii, float shift)
+{
+	if(key == K_ENTER || key == K_SPACE)
+	{
+		me.clickTime = 0.1; // delayed for effect
+		return 1;
+	}
+	return 0;
+}
+float mouseDragButton(entity me, vector pos)
+{
+	me.pressed = 1;
+	if(pos_x < 0) me.pressed = 0;
+	if(pos_y < 0) me.pressed = 0;
+	if(pos_x >= 1) me.pressed = 0;
+	if(pos_y >= 1) me.pressed = 0;
+	return 1;
+}
+float mousePressButton(entity me, vector pos)
+{
+	me.mouseDrag(me, pos); // verify coordinates
+	return 1;
+}
+float mouseReleaseButton(entity me, vector pos)
+{
+	me.mouseDrag(me, pos); // verify coordinates
+	if(me.pressed)
+	{
+		if not(me.disabled)
+			me.onClick(me, me.onClickEntity);
+		me.pressed = 0;
+	}
+	return 1;
+}
+void showNotifyButton(entity me)
+{
+	me.focusable = !me.disabled;
+}
+void drawButton(entity me)
+{
+	vector bOrigin, bSize;
+	float save;
+
+	me.focusable = !me.disabled;
+
+	save = draw_alpha;
+	if(me.disabled)
+		draw_alpha *= me.disabledAlpha;
+
+	if(me.src)
+	{
+		if(me.srcMulti)
+		{
+			bOrigin = '0 0 0';
+			bSize = '1 1 0';
+			if(me.disabled)
+				draw_ButtonPicture(bOrigin, strcat(me.src, "_d", me.srcSuffix), bSize, me.colorD, 1);
+			else if(me.forcePressed || me.pressed || me.clickTime > 0)
+				draw_ButtonPicture(bOrigin, strcat(me.src, "_c", me.srcSuffix), bSize, me.colorC, 1);
+			else if(me.focused)
+				draw_ButtonPicture(bOrigin, strcat(me.src, "_f", me.srcSuffix), bSize, me.colorF, 1);
+			else
+				draw_ButtonPicture(bOrigin, strcat(me.src, "_n", me.srcSuffix), bSize, me.color, 1);
+		}
+		else
+		{
+			if(me.realFontSize_y == 0)
+			{
+				bOrigin = '0 0 0';
+				bSize = '1 1 0';
+			}
+			else
+			{
+				bOrigin = eY * (0.5 * (1 - me.realFontSize_y)) + eX * (0.5 * (me.keepspaceLeft - me.realFontSize_x));
+				bSize = me.realFontSize;
+			}
+			if(me.disabled)
+				draw_Picture(bOrigin, strcat(me.src, "_d", me.srcSuffix), bSize, me.colorD, 1);
+			else if(me.forcePressed || me.pressed || me.clickTime > 0)
+				draw_Picture(bOrigin, strcat(me.src, "_c", me.srcSuffix), bSize, me.colorC, 1);
+			else if(me.focused)
+				draw_Picture(bOrigin, strcat(me.src, "_f", me.srcSuffix), bSize, me.colorF, 1);
+			else
+				draw_Picture(bOrigin, strcat(me.src, "_n", me.srcSuffix), bSize, me.color, 1);
+		}
+	}
+	if(me.src2)
+	{
+		bOrigin = me.keepspaceLeft * eX;
+		bSize = eY + eX * (1 - me.keepspaceLeft);
+
+		bOrigin += bSize * (0.5 - 0.5 * me.src2scale);
+		bSize = bSize * me.src2scale;
+
+		draw_Picture(bOrigin, me.src2, bSize, me.color2, me.alpha2);
+	}
+
+	draw_alpha = save;
+
+	drawLabel(me);
+
+	if(me.clickTime > 0 && me.clickTime <= frametime)
+	{
+		// keyboard click timer expired? Fire the event then.
+		if not(me.disabled)
+			me.onClick(me, me.onClickEntity);
+	}
+	me.clickTime -= frametime;
+}
+#endif

Added: trunk/menu/item/checkbox.c
===================================================================
--- trunk/menu/item/checkbox.c	                        (rev 0)
+++ trunk/menu/item/checkbox.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,48 @@
+#ifdef INTERFACE
+void CheckBox_Click(entity me, entity other);
+CLASS(CheckBox) EXTENDS(Button)
+	METHOD(CheckBox, configureCheckBox, void(entity, string, float, string))
+	METHOD(CheckBox, draw, void(entity))
+	METHOD(CheckBox, toString, string(entity))
+	METHOD(CheckBox, setChecked, void(entity, float))
+	ATTRIB(CheckBox, useDownAsChecked, float, 0)
+	ATTRIB(CheckBox, checked, float, 0)
+	ATTRIB(CheckBox, onClick, void(entity, entity), CheckBox_Click)
+	ATTRIB(CheckBox, srcMulti, float, 0)
+	ATTRIB(CheckBox, disabled, float, 0)
+ENDCLASS(CheckBox)
+#endif
+
+#ifdef IMPLEMENTATION
+void setCheckedCheckBox(entity me, float val)
+{
+	me.checked = val;
+}
+void CheckBox_Click(entity me, entity other)
+{
+	me.setChecked(me, !me.checked);
+}
+string toStringCheckBox(entity me)
+{
+	return strcat(toStringLabel(me), ", ", me.checked ? "checked" : "unchecked");
+}
+void configureCheckBoxCheckBox(entity me, string txt, float sz, string gfx)
+{
+	me.configureButton(me, txt, sz, gfx);
+	me.align = 0;
+}
+void drawCheckBox(entity me)
+{
+	float s;
+	s = me.pressed;
+	if(me.useDownAsChecked)
+	{
+		me.srcSuffix = string_null;
+		me.forcePressed = me.checked;
+	}
+	else
+		me.srcSuffix = (me.checked ? "1" : "0");
+	drawButton(me);
+	me.pressed = s;
+}
+#endif

Added: trunk/menu/item/container.c
===================================================================
--- trunk/menu/item/container.c	                        (rev 0)
+++ trunk/menu/item/container.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,342 @@
+#ifdef INTERFACE
+CLASS(Container) EXTENDS(Item)
+	METHOD(Container, draw, void(entity))
+	METHOD(Container, keyUp, float(entity, float, float, float))
+	METHOD(Container, keyDown, float(entity, float, float, float))
+	METHOD(Container, mouseMove, float(entity, vector))
+	METHOD(Container, mousePress, float(entity, vector))
+	METHOD(Container, mouseDrag, float(entity, vector))
+	METHOD(Container, mouseRelease, float(entity, vector))
+	METHOD(Container, focusLeave, void(entity))
+	METHOD(Container, resizeNotify, void(entity, vector, vector, vector, vector))
+	METHOD(Container, resizeNotifyLie, void(entity, vector, vector, vector, vector, .vector, .vector))
+	METHOD(Container, addItem, void(entity, entity, vector, vector, float))
+	METHOD(Container, addItemCentered, void(entity, entity, vector, float))
+	METHOD(Container, moveItemAfter, void(entity, entity, entity))
+	METHOD(Container, removeItem, void(entity, entity))
+	METHOD(Container, setFocus, void(entity, entity))
+	METHOD(Container, setAlphaOf, void(entity, entity, float))
+	METHOD(Container, itemFromPoint, entity(entity, vector))
+	METHOD(Container, showNotify, void(entity))
+	METHOD(Container, hideNotify, void(entity))
+	ATTRIB(Container, focusable, float, 0)
+	ATTRIB(Container, firstChild, entity, NULL)
+	ATTRIB(Container, lastChild, entity, NULL)
+	ATTRIB(Container, focusedChild, entity, NULL)
+	ATTRIB(Container, shown, float, 0)
+ENDCLASS(Container)
+.entity nextSibling;
+.entity prevSibling;
+.float resized;
+.vector Container_origin;
+.vector Container_size;
+.float Container_alpha;
+#endif
+
+#ifdef IMPLEMENTATION
+void showNotifyContainer(entity me)
+{
+	entity e;
+	if(me.shown)
+		return;
+	me.shown = 1;
+	for(e = me.firstChild; e; e = e.nextSibling)
+		if(e.Container_alpha > 0)
+			e.showNotify(e);
+}
+
+void hideNotifyContainer(entity me)
+{
+	entity e;
+	if not(me.shown)
+		return;
+	me.shown = 0;
+	for(e = me.firstChild; e; e = e.nextSibling)
+		if(e.Container_alpha > 0)
+			e.hideNotify(e);
+}
+
+void setAlphaOfContainer(entity me, entity other, float theAlpha)
+{
+	if(theAlpha <= 0)
+	{
+		if(other.Container_alpha > 0)
+			other.hideNotify(other);
+	}
+	else // value > 0
+	{
+		if(other.Container_alpha <= 0)
+			other.showNotify(other);
+	}
+	other.Container_alpha = theAlpha;
+}
+
+void resizeNotifyLieContainer(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize, .vector originField, .vector sizeField)
+{
+	entity e;
+	vector o, s;
+	float d;
+	for(e = me.firstChild; e; e = e.nextSibling)
+	{
+		o = e.originField;
+		s = e.sizeField;
+		e.resizeNotify(e, o, s, boxToGlobal(o, absOrigin, absSize), boxToGlobalSize(s, absSize));
+	}
+	do
+	{
+		d = 0;
+		for(e = me.firstChild; e; e = e.nextSibling)
+			if(e.resized)
+			{
+				e.resized = 0;
+				d = 1;
+				o = e.originField;
+				s = e.sizeField;
+				e.resizeNotify(e, o, s, boxToGlobal(o, absOrigin, absSize), boxToGlobalSize(s, absSize));
+			}
+	}
+	while(d);
+	resizeNotifyItem(me, relOrigin, relSize, absOrigin, absSize);
+}
+
+void resizeNotifyContainer(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
+{
+	me.resizeNotifyLie(me, relOrigin, relSize, absOrigin, absSize, Container_origin, Container_size);
+}
+
+entity itemFromPointContainer(entity me, vector pos)
+{
+	entity e;
+	vector o, s;
+	for(e = me.lastChild; e; e = e.prevSibling)
+	{
+		o = e.Container_origin;
+		s = e.Container_size;
+		if(pos_x < o_x) continue;
+		if(pos_y < o_y) continue;
+		if(pos_x >= o_x + s_x) continue;
+		if(pos_y >= o_y + s_y) continue;
+		return e;
+	}
+	return NULL;
+}
+
+void drawContainer(entity me)
+{
+	vector oldshift;
+	vector oldscale;
+	float oldalpha;
+	entity e;
+
+	oldshift = draw_shift;
+	oldscale = draw_scale;
+	oldalpha = draw_alpha;
+	me.focusable = 0;
+	for(e = me.firstChild; e; e = e.nextSibling)
+	{
+		if(e.focusable)
+			me.focusable += 1;
+		if(e.Container_alpha < 0.003) // can't change color values anyway
+			continue;
+		draw_shift = boxToGlobal(e.Container_origin, oldshift, oldscale);
+		draw_scale = boxToGlobalSize(e.Container_size, oldscale);
+		draw_alpha *= e.Container_alpha;
+		e.draw(e);
+		draw_shift = oldshift;
+		draw_scale = oldscale;
+		draw_alpha = oldalpha;
+	}
+};
+
+void focusLeaveContainer(entity me)
+{
+	me.setFocus(me, NULL);
+}
+
+float keyUpContainer(entity me, float scan, float ascii, float shift)
+{
+	entity f;
+	f = me.focusedChild;
+	if(f)
+		return f.keyUp(f, scan, ascii, shift);
+	return 0;
+}
+
+float keyDownContainer(entity me, float scan, float ascii, float shift)
+{
+	entity f;
+	f = me.focusedChild;
+	if(f)
+		return f.keyDown(f, scan, ascii, shift);
+	return 0;
+}
+
+float mouseMoveContainer(entity me, vector pos)
+{
+	entity f;
+	f = me.focusedChild;
+	if(f)
+		return f.mouseMove(f, globalToBox(pos, f.Container_origin, f.Container_size));
+	return 0;
+}
+float mousePressContainer(entity me, vector pos)
+{
+	entity f;
+	f = me.focusedChild;
+	if(f)
+		return f.mousePress(f, globalToBox(pos, f.Container_origin, f.Container_size));
+	return 0;
+}
+float mouseDragContainer(entity me, vector pos)
+{
+	entity f;
+	f = me.focusedChild;
+	if(f)
+		return f.mouseDrag(f, globalToBox(pos, f.Container_origin, f.Container_size));
+	return 0;
+}
+float mouseReleaseContainer(entity me, vector pos)
+{
+	entity f;
+	f = me.focusedChild;
+	if(f)
+		return f.mouseRelease(f, globalToBox(pos, f.Container_origin, f.Container_size));
+	return 0;
+}
+
+void addItemCenteredContainer(entity me, entity other, vector theSize, float theAlpha)
+{
+	me.addItem(me, other, '0.5 0.5 0' - 0.5 * theSize, theSize, theAlpha);
+}
+
+void addItemContainer(entity me, entity other, vector theOrigin, vector theSize, float theAlpha)
+{
+	if(other.parent)
+		error("Can't add already added item!");
+
+	if(other.focusable)
+		me.focusable += 1;
+
+	if(theSize_x > 1)
+	{
+		theOrigin_x -= 0.5 * (theSize_x - 1);
+		theSize_x = 1;
+	}
+	if(theSize_y > 1)
+	{
+		theOrigin_y -= 0.5 * (theSize_y - 1);
+		theSize_y = 1;
+	}
+	theOrigin_x = bound(0, theOrigin_x, 1 - theSize_x);
+	theOrigin_y = bound(0, theOrigin_y, 1 - theSize_y);
+
+	other.parent = me;
+	other.Container_origin = theOrigin;
+	other.Container_size = theSize;
+	me.setAlphaOf(me, other, theAlpha);
+
+	entity f, l;
+	f = me.firstChild;
+	l = me.lastChild;
+
+	if(l)
+		l.nextSibling = other;
+	else
+		me.firstChild = other;
+
+	other.prevSibling = l;
+	other.nextSibling = NULL;
+	me.lastChild = other;
+
+	draw_NeedResizeNotify = 1;
+}
+
+void removeItemContainer(entity me, entity other)
+{
+	if(other.parent != me)
+		error("Can't remove from wrong container!");
+
+	if(other.focusable)
+		me.focusable -= 1;
+
+	other.parent = NULL;
+
+	entity n, p, f, l;
+	f = me.firstChild;
+	l = me.lastChild;
+	n = other.nextSibling;
+	p = other.prevSibling;
+
+	if(p)
+		p.nextSibling = n;
+	else
+		me.firstChild = n;
+
+	if(n)
+		n.prevSibling = p;
+	else
+		me.lastChild = p;
+}
+
+void setFocusContainer(entity me, entity other)
+{
+	if(other)
+		if not(me.focused)
+			error("Trying to set focus in a non-focused control!");
+	if(me.focusedChild == other)
+		return;
+	//print(etos(me), ": focus changes from ", etos(me.focusedChild), " to ", etos(other), "\n");
+	if(me.focusedChild)
+	{
+		me.focusedChild.focused = 0;
+		me.focusedChild.focusLeave(me.focusedChild);
+	}
+	if(other)
+	{
+		other.focused = 1;
+		other.focusEnter(other);
+	}
+	me.focusedChild = other;
+}
+
+void moveItemAfterContainer(entity me, entity other, entity dest)
+{
+	// first: remove other from the chain
+	entity n, p, f, l;
+
+	if(other.parent != me)
+		error("Can't move in wrong container!");
+
+	f = me.firstChild;
+	l = me.lastChild;
+	n = other.nextSibling;
+	p = other.prevSibling;
+
+	if(p)
+		p.nextSibling = n;
+	else
+		me.firstChild = n;
+
+	if(n)
+		n.prevSibling = p;
+	else
+		me.lastChild = p;
+	
+	// now other got removed. Insert it behind dest now.
+	other.prevSibling = dest;
+	if(dest)
+		other.nextSibling = dest.nextSibling;
+	else
+		other.nextSibling = me.firstChild;
+
+	if(dest)
+		dest.nextSibling = other;
+	else
+		me.firstChild = other;
+
+	if(other.nextSibling)
+		other.nextSibling.prevSibling = other;
+	else
+		me.lastChild = other;
+}
+#endif

Added: trunk/menu/item/dialog.c
===================================================================
--- trunk/menu/item/dialog.c	                        (rev 0)
+++ trunk/menu/item/dialog.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,191 @@
+// Note: this class is called Dialog, but it can also handle a tab under the following conditions:
+// - isTabRoot is 0
+// - backgroundImage is the tab's background
+// - closable is 0
+// - rootDialog is 0
+// - title is ""
+// - marginTop is 
+// - intendedHeight ends up to be the tab's actual height, or at least close
+// - titleFontSize is 0
+// - marginTop cancels out as much of titleHeight as needed (that is, it should be actualMarginTop - titleHeight)
+// To ensure the latter, you best create all tabs FIRST and insert the tabbed
+// control to your dialog THEN - with the right height
+//
+// a subclass may help with using this as a tab
+
+#ifdef INTERFACE
+CLASS(Dialog) EXTENDS(InputContainer)
+	METHOD(Dialog, configureDialog, void(entity)) // no runtime configuration, all parameters are given in the code!
+	METHOD(Dialog, fill, void(entity)) // to be overridden by user to fill the dialog with controls
+	METHOD(Dialog, keyDown, float(entity, float, float, float))
+	METHOD(Dialog, close, void(entity))
+	METHOD(Dialog, addItemSimple, void(entity, float, float, float, float, entity, vector))
+
+	METHOD(Dialog, TD, void(entity, float, float, entity))
+	METHOD(Dialog, TDNoMargin, void(entity, float, float, entity, vector))
+	METHOD(Dialog, TDempty, void(entity, float))
+	METHOD(Dialog, setFirstColumn, void(entity, float))
+	METHOD(Dialog, TR, void(entity))
+	METHOD(Dialog, gotoRC, void(entity, float, float))
+
+	ATTRIB(Dialog, isTabRoot, float, 1)
+	ATTRIB(Dialog, closeButton, entity, NULL)
+	ATTRIB(Dialog, intendedHeight, float, 0)
+	ATTRIB(Dialog, itemOrigin, vector, '0 0 0')
+	ATTRIB(Dialog, itemSize, vector, '0 0 0')
+	ATTRIB(Dialog, itemSpacing, vector, '0 0 0')
+	ATTRIB(Dialog, currentRow, float, 0)
+	ATTRIB(Dialog, currentColumn, float, 0)
+	ATTRIB(Dialog, firstColumn, float, 0)
+
+	// to be customized
+	ATTRIB(Dialog, closable, float, 1)
+	ATTRIB(Dialog, title, string, "Form1") // ;)
+	ATTRIB(Dialog, color, vector, '1 0.5 1')
+	ATTRIB(Dialog, intendedWidth, float, 0)
+	ATTRIB(Dialog, rows, float, 3)
+	ATTRIB(Dialog, columns, float, 2)
+
+	ATTRIB(Dialog, marginTop, float, 0) // pixels
+	ATTRIB(Dialog, marginBottom, float, 0) // pixels
+	ATTRIB(Dialog, marginLeft, float, 0) // pixels
+	ATTRIB(Dialog, marginRight, float, 0) // pixels
+	ATTRIB(Dialog, columnSpacing, float, 0) // pixels
+	ATTRIB(Dialog, rowSpacing, float, 0) // pixels
+	ATTRIB(Dialog, rowHeight, float, 0) // pixels
+	ATTRIB(Dialog, titleHeight, float, 0) // pixels
+	ATTRIB(Dialog, titleFontSize, float, 0) // pixels; if 0, title causes no margin
+	ATTRIB(Dialog, zoomedOutTitleBarPosition, float, 0)
+	ATTRIB(Dialog, zoomedOutTitleBar, float, 0)
+
+	ATTRIB(Dialog, backgroundImage, string, string_null)
+	ATTRIB(Dialog, closeButtonImage, string, string_null)
+
+	ATTRIB(Dialog, frame, entity, NULL)
+ENDCLASS(Dialog)
+#endif
+
+#ifdef IMPLEMENTATION
+void Dialog_Close(entity button, entity me)
+{
+	me.close(me);
+}
+
+void fillDialog(entity me)
+{
+}
+
+void addItemSimpleDialog(entity me, float row, float col, float rowspan, float colspan, entity e, vector v)
+{
+	//print(vtos(me.itemSpacing), " ", vtos(me.itemSize), "\n");
+	vector o, s;
+	o = me.itemOrigin + eX * ( col          * me.itemSpacing_x) + eY * ( row          * me.itemSpacing_y);
+	s = me.itemSize   + eX * ((colspan - 1) * me.itemSpacing_x) + eY * ((rowspan - 1) * me.itemSpacing_y);
+	o_x -= 0.5 * (me.itemSpacing_x - me.itemSize_x) * v_x;
+	s_x +=       (me.itemSpacing_x - me.itemSize_x) * v_x;
+	o_y -= 0.5 * (me.itemSpacing_y - me.itemSize_y) * v_y;
+	s_y +=       (me.itemSpacing_y - me.itemSize_y) * v_y;
+	me.addItem(me, e, o, s, 1);
+}
+
+void gotoRCDialog(entity me, float row, float col)
+{
+	me.currentRow = row;
+	me.currentColumn = col;
+}
+
+void TRDialog(entity me)
+{
+	me.currentRow += 1;
+	me.currentColumn = me.firstColumn;
+}
+
+void TDDialog(entity me, float rowspan, float colspan, entity e)
+{
+	me.addItemSimple(me, me.currentRow, me.currentColumn, rowspan, colspan, e, '0 0 0');
+	me.currentColumn += colspan;
+}
+
+void TDNoMarginDialog(entity me, float rowspan, float colspan, entity e, vector v)
+{
+	me.addItemSimple(me, me.currentRow, me.currentColumn, rowspan, colspan, e, v);
+	me.currentColumn += colspan;
+}
+
+void setFirstColumnDialog(entity me, float col)
+{
+	me.firstColumn = col;
+}
+
+void TDemptyDialog(entity me, float colspan)
+{
+	me.currentColumn += colspan;
+}
+
+void configureDialogDialog(entity me)
+{
+	entity closebutton;
+	float absWidth, absHeight;
+
+	me.frame = spawnBorderImage();
+	me.frame.configureBorderImage(me.frame, me.title, me.titleFontSize, me.color, me.backgroundImage, me.titleHeight);
+	me.frame.zoomedOutTitleBarPosition = me.zoomedOutTitleBarPosition;
+	me.frame.zoomedOutTitleBar = me.zoomedOutTitleBar;
+	me.frame.alpha = me.alpha;
+	me.addItem(me, me.frame, '0 0 0', '1 1 0', 1);
+
+	if not(me.titleFontSize)
+		me.titleHeight = 0; // no title bar
+
+	absWidth = me.intendedWidth * conwidth;
+	absHeight = me.titleHeight + me.marginTop + me.rows * me.rowHeight + (me.rows - 1) * me.rowSpacing + me.marginBottom;
+	me.itemOrigin  = eX * (me.marginLeft / absWidth)
+	               + eY * ((me.titleHeight + me.marginTop) / absHeight);
+	me.itemSize    = eX * ((1 - (me.marginLeft + me.marginRight + me.columnSpacing * (me.columns - 1)) / absWidth) / me.columns)
+	               + eY * (me.rowHeight / absHeight);
+	me.itemSpacing = me.itemSize
+	               + eX * (me.columnSpacing / absWidth)
+	               + eY * (me.rowSpacing / absHeight);
+	me.intendedHeight = absHeight / conheight;
+	me.currentRow = -1;
+	me.currentColumn = -1;
+
+	me.fill(me);
+
+	if(me.closable)
+	{
+		closebutton = me.closeButton = spawnButton();
+		closebutton.configureButton(closebutton, "Close", 0, me.closeButtonImage);
+		closebutton.onClick = Dialog_Close; closebutton.onClickEntity = me;
+		closebutton.srcMulti = 0;
+		me.addItem(me, closebutton, '0 0 0', '1 1 0', 1); // put it as LAST
+	}
+
+	me.frame.closeButton = closebutton;
+}
+
+void closeDialog(entity me)
+{
+	if(me.parent.instanceOfNexposee)
+	{
+		ExposeeCloseButton_Click(me, me.parent);
+	}
+	else if(me.parent.instanceOfModalController)
+	{
+		DialogCloseButton_Click(me, me);
+	}
+}
+
+float keyDownDialog(entity me, float key, float ascii, float shift)
+{
+	if(me.closable)
+	{
+		if(key == K_ESCAPE)
+		{
+			me.close(me);
+			return 1;
+		}
+	}
+	return keyDownInputContainer(me, key, ascii, shift);
+}
+#endif

Added: trunk/menu/item/gecko.c
===================================================================
--- trunk/menu/item/gecko.c	                        (rev 0)
+++ trunk/menu/item/gecko.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,97 @@
+// Andreas Kirsch Gecko item (to test it)
+#ifdef INTERFACE
+CLASS(Gecko) EXTENDS(Item)
+	METHOD( Gecko, configureBrowser, void( entity, string ) )
+	METHOD( Gecko, draw, void(entity))
+	METHOD( Gecko, keyDown, float(entity, float, float, float))
+	METHOD( Gecko, keyUp, float(entity, float, float, float))
+	METHOD( Gecko, mouseMove, float(entity, vector))
+	METHOD( Gecko, mouseDrag, float(entity, vector))
+	METHOD( Gecko, resizeNotify, void(entity, vector, vector, vector, vector))
+	ATTRIB( Gecko, texturePath, string, string_null )
+	ATTRIB( Gecko, textureExtent, vector, '0 0 0')
+ENDCLASS(Item)
+#endif
+
+#ifdef IMPLEMENTATION
+// define static members
+float _gecko_instanceNumber;
+
+void configureBrowserGecko( entity me, string URI ) {
+	me.focusable = 1;
+
+	//create a new gecko object if needed
+	if( !me.texturePath ) {
+		me.texturePath = strzone( strcat( "_dynamic/gecko/menu/",  ftos( _gecko_instanceNumber ) ) );
+		_gecko_instanceNumber+=1;
+		// TODO: add error checks
+		gecko_create( me.texturePath );
+	}
+	gecko_navigate( me.texturePath, URI );
+}
+
+void drawGecko(entity me)
+{
+	vector drawSize;
+  
+	if( me.texturePath ) {
+		/* The gecko browser is actually only drawn to a part of the
+		   texture. Correct scaling so that part fills up the whole
+		   item area. */
+		drawSize_x = 1.0 / me.textureExtent_x;
+		drawSize_y = 1.0 / me.textureExtent_y;
+		draw_Picture( '0 0 0', strcat( "/", me.texturePath ), 
+			drawSize, '1 1 1', 1.0 );
+	} else {
+		local vector fontsize;
+		fontsize_x = fontsize_y = 1.0 / 30.0;
+		fontsize_z = 0.0;
+		draw_Text( '0 0 0', "Browser not initialized!", fontsize, '1 1 1', 1.0, 0 );
+	}
+}
+
+float keyDownGecko(entity me, float scan, float ascii, float shift)
+{
+	if( scan == K_ESCAPE ) {
+		return 0;
+	}
+	if (ascii >= 32)
+		return gecko_keyevent( me.texturePath, ascii, GECKO_BUTTON_DOWN );
+	else
+		return gecko_keyevent( me.texturePath, scan, GECKO_BUTTON_DOWN );
+}
+
+float keyUpGecko(entity me, float scan, float ascii, float shift)
+{
+	if (ascii >= 32)
+		return gecko_keyevent( me.texturePath, ascii, GECKO_BUTTON_UP );
+	else
+		return gecko_keyevent( me.texturePath, scan, GECKO_BUTTON_UP );
+}
+
+float mouseMoveGecko(entity me, vector pos)
+{
+	gecko_mousemove( me.texturePath, pos_x, pos_y );
+	return 1;
+}
+
+float mouseDragGecko(entity me, vector pos)
+{
+	gecko_mousemove( me.texturePath, pos_x, pos_y );
+	return 1;
+}
+
+void resizeNotifyGecko(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
+{
+	me.origin = absOrigin;
+	me.size = absSize;
+	gecko_resize( me.texturePath, absSize_x, absSize_y );
+	me.textureExtent = gecko_get_texture_extent( me.texturePath );
+}
+
+string toStringGecko(entity me)
+{
+	return me.texturePath;
+}
+
+#endif

Added: trunk/menu/item/image.c
===================================================================
--- trunk/menu/item/image.c	                        (rev 0)
+++ trunk/menu/item/image.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,50 @@
+#ifdef INTERFACE
+CLASS(Image) EXTENDS(Item)
+	METHOD(Image, configureImage, void(entity, string))
+	METHOD(Image, draw, void(entity))
+	METHOD(Image, toString, string(entity))
+	METHOD(Image, resizeNotify, void(entity, vector, vector, vector, vector))
+	ATTRIB(Image, src, string, string_null)
+	ATTRIB(Image, color, vector, '1 1 1')
+	ATTRIB(Image, forcedAspect, float, 0)
+	ATTRIB(Image, imgOrigin, vector, '0 0 0')
+	ATTRIB(Image, imgSize, vector, '0 0 0')
+ENDCLASS(Image)
+#endif
+
+#ifdef IMPLEMENTATION
+string toStringImage(entity me)
+{
+	return me.src;
+}
+void configureImageImage(entity me, string path)
+{
+	me.src = path;
+}
+void drawImage(entity me)
+{
+	draw_Picture(me.imgOrigin, me.src, me.imgSize, me.color, 1);
+}
+void resizeNotifyImage(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
+{
+	if(me.forcedAspect == 0)
+	{
+		me.imgOrigin = '0 0 0';
+		me.imgSize = '1 1 0';
+	}
+	else
+	{
+		if(absSize_x > me.forcedAspect * absSize_y)
+		{
+			// x too large, so center x-wise
+			me.imgSize = eY + eX * (absSize_y * me.forcedAspect / absSize_x);
+		}
+		else
+		{
+			// y too large, so center y-wise
+			me.imgSize = eX + eY * (absSize_x / (me.forcedAspect * absSize_y));
+		}
+		me.imgOrigin = '0.5 0.5 0' - 0.5 * me.imgSize;
+	}
+}
+#endif

Added: trunk/menu/item/inputbox.c
===================================================================
--- trunk/menu/item/inputbox.c	                        (rev 0)
+++ trunk/menu/item/inputbox.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,232 @@
+#ifdef INTERFACE
+CLASS(InputBox) EXTENDS(Label)
+	METHOD(InputBox, configureInputBox, void(entity, string, float, float, string))
+	METHOD(InputBox, draw, void(entity))
+	METHOD(InputBox, setText, void(entity, string))
+	METHOD(InputBox, enterText, void(entity, string))
+	METHOD(InputBox, keyDown, float(entity, float, float, float))
+	METHOD(InputBox, mouseRelease, float(entity, vector))
+	METHOD(InputBox, mousePress, float(entity, vector))
+	METHOD(InputBox, mouseDrag, float(entity, vector))
+	METHOD(InputBox, showNotify, void(entity))
+
+	ATTRIB(InputBox, src, string, string_null)
+
+	ATTRIB(InputBox, cursorPos, float, 0) // characters
+	ATTRIB(InputBox, scrollPos, float, 0) // widths
+
+	ATTRIB(InputBox, focusable, float, 1)
+	ATTRIB(InputBox, disabled, float, 0)
+	ATTRIB(InputBox, lastChangeTime, float, 0)
+	ATTRIB(InputBox, dragScrollTimer, float, 0)
+	ATTRIB(InputBox, dragScrollPos, vector, '0 0 0')
+	ATTRIB(InputBox, pressed, float, 0)
+	ATTRIB(InputBox, editColorCodes, float, 1)
+	ATTRIB(InputBox, forbiddenCharacters, string, "")
+	ATTRIB(InputBox, color, vector, '1 1 1')
+	ATTRIB(InputBox, colorF, vector, '1 1 1')
+ENDCLASS(InputBox)
+void InputBox_Clear_Click(entity btn, entity me);
+#endif
+
+#ifdef IMPLEMENTATION
+void configureInputBoxInputBox(entity me, string theText, float theCursorPos, float theFontSize, string gfx)
+{
+	configureLabelLabel(me, theText, theFontSize, 0.0);
+	me.src = gfx;
+	me.cursorPos = theCursorPos;
+}
+
+void setTextInputBox(entity me, string txt)
+{
+	if(me.text)
+		strunzone(me.text);
+	setTextLabel(me, strzone(txt));
+}
+
+void InputBox_Clear_Click(entity btn, entity me)
+{
+	me.setText(me, "");
+}
+
+float mouseDragInputBox(entity me, vector pos)
+{
+	float p;
+	me.dragScrollPos = pos;
+	p = me.scrollPos + pos_x - me.keepspaceLeft;
+	me.cursorPos = draw_TextLengthUpToWidth(me.text, p / me.realFontSize_x, 0);
+	me.lastChangeTime = time;
+	return 1;
+}
+
+float mousePressInputBox(entity me, vector pos)
+{
+	me.dragScrollTimer = time;
+	me.pressed = 1;
+	return mouseDragInputBox(me, pos);
+}
+
+float mouseReleaseInputBox(entity me, vector pos)
+{
+	me.pressed = 0;
+	return mouseDragInputBox(me, pos);
+}
+
+void enterTextInputBox(entity me, string ch)
+{
+	float i;
+	for(i = 0; i < strlen(ch); ++i)
+		if(strstrofs(me.forbiddenCharacters, substring(ch, i, 1), 0) > -1)
+			return;
+	me.setText(me, strcat(substring(me.text, 0, me.cursorPos), ch, substring(me.text, me.cursorPos, strlen(me.text) - me.cursorPos)));
+	me.cursorPos += strlen(ch);
+}
+
+float keyDownInputBox(entity me, float key, float ascii, float shift)
+{
+	me.lastChangeTime = time;
+	me.dragScrollTimer = time;
+	if(ascii >= 32 && ascii != 127)
+	{
+		me.enterText(me, chr(ascii));
+		return 1;
+	}
+	switch(key)
+	{
+		case K_LEFTARROW:
+			me.cursorPos -= 1;
+			return 1;
+		case K_RIGHTARROW:
+			me.cursorPos += 1;
+			return 1;
+		case K_HOME:
+			me.cursorPos = 0;
+			return 1;
+		case K_END:
+			me.cursorPos = strlen(me.text);
+			return 1;
+		case K_BACKSPACE:
+			if(me.cursorPos > 0)
+			{
+				me.cursorPos -= 1;
+				me.setText(me, strcat(substring(me.text, 0, me.cursorPos), substring(me.text, me.cursorPos + 1, strlen(me.text) - me.cursorPos - 1)));
+			}
+			return 1;
+		case K_DEL:
+			if(shift & S_CTRL)
+				me.setText(me, "");
+			else
+				me.setText(me, strcat(substring(me.text, 0, me.cursorPos), substring(me.text, me.cursorPos + 1, strlen(me.text) - me.cursorPos - 1)));
+			return 1;
+	}
+	return 0;
+}
+
+void drawInputBox(entity me)
+{
+#define CURSOR "_"
+	float cursorPosInWidths, totalSizeInWidths;
+
+	if(me.pressed)
+		me.mouseDrag(me, me.dragScrollPos); // simulate mouseDrag event
+
+	me.focusable = !me.disabled;
+	if(me.disabled)
+		draw_alpha *= me.disabledAlpha;
+
+	if(me.src)
+	{
+		if(me.focused && !me.disabled)
+			draw_ButtonPicture('0 0 0', strcat(me.src, "_f"), '1 1 0', me.colorF, 1);
+		else
+			draw_ButtonPicture('0 0 0', strcat(me.src, "_n"), '1 1 0', me.color, 1);
+	}
+
+	me.cursorPos = bound(0, me.cursorPos, strlen(me.text));
+	cursorPosInWidths = draw_TextWidth(substring(me.text, 0, me.cursorPos), 0) * me.realFontSize_x;
+	totalSizeInWidths = draw_TextWidth(strcat(me.text, CURSOR), 0) * me.realFontSize_x;
+
+	if(me.dragScrollTimer < time)
+	{
+		float save;
+		save = me.scrollPos;
+		me.scrollPos = bound(cursorPosInWidths - (0.875 - me.keepspaceLeft - me.keepspaceRight), me.scrollPos, cursorPosInWidths - 0.125);
+		if(me.scrollPos != save)
+			me.dragScrollTimer = time + 0.2;
+	}
+	me.scrollPos = min(me.scrollPos, totalSizeInWidths - (1 - me.keepspaceRight - me.keepspaceLeft));
+	me.scrollPos = max(0, me.scrollPos);
+
+	draw_SetClipRect(eX * me.keepspaceLeft, eX * (1 - me.keepspaceLeft - me.keepspaceRight) + eY);
+	if(me.editColorCodes)
+	{
+		string ch, ch2;
+		float i;
+		vector theColor;
+		float theAlpha;
+		vector p;
+		float brightness;
+		brightness = cvar("r_textbrightness");
+		p = me.realOrigin - eX * me.scrollPos;
+		theColor = '1 1 1';
+		theAlpha = 1;
+		for(i = 0; i < strlen(me.text); ++i)
+		{
+			ch = substring(me.text, i, 1);
+			if(ch == "^")
+			{
+				float w;
+				ch2 = substring(me.text, i+1, 1);
+				w = draw_TextWidth(strcat(ch, ch2), 0) * me.realFontSize_x;
+				if(ch2 == "^")
+				{
+					draw_Fill(p, eX * w + eY * me.realFontSize_y, '0 0 1', 0.5);
+					draw_Text(p + eX * 0.25 * w, "^", me.realFontSize, theColor, theAlpha, 0);
+				}
+				else if(ch2 == "0" || stof(ch2)) // digit?
+				{
+					switch(stof(ch2))
+					{
+						case 0: theColor = '0 0 0'; theAlpha = 1; break;
+						case 1: theColor = '1 0 0'; theAlpha = 1; break;
+						case 2: theColor = '0 1 0'; theAlpha = 1; break;
+						case 3: theColor = '1 1 0'; theAlpha = 1; break;
+						case 4: theColor = '0 0 1'; theAlpha = 1; break;
+						case 5: theColor = '0 1 1'; theAlpha = 1; break;
+						case 6: theColor = '1 0 1'; theAlpha = 1; break;
+						case 7: theColor = '1 1 1'; theAlpha = 1; break;
+						case 8: theColor = '1 1 1'; theAlpha = 0.5; break;
+						case 9: theColor = '0.5 0.5 0.5'; theAlpha = 1; break;
+					}
+					theColor = theColor * (1 - brightness) + brightness * '1 1 1';
+					draw_Fill(p, eX * w + eY * me.realFontSize_y, '1 1 1', 0.5);
+					draw_Text(p, strcat(ch, ch2), me.realFontSize, theColor, theAlpha, 0);
+					draw_Text(p, strcat(ch, ch2), me.realFontSize, theColor, theAlpha, 0);
+				}
+				else
+				{
+					draw_Fill(p, eX * w + eY * me.realFontSize_y, '1 0 0', 0.5);
+					draw_Text(p, strcat(ch, ch2), me.realFontSize, theColor, theAlpha, 0);
+					draw_Text(p, strcat(ch, ch2), me.realFontSize, theColor, theAlpha, 0);
+				}
+				p += w * eX;
+				++i;
+				continue;
+			}
+			draw_Text(p, ch, me.realFontSize, theColor, theAlpha, 0); p += eX * draw_TextWidth(ch, 0) * me.realFontSize_x;
+		}
+	}
+	else
+		draw_Text(me.realOrigin - eX * me.scrollPos, me.text, me.realFontSize, '1 1 1', 1, 0);
+		// skipping drawLabel(me);
+	if(!me.focused || (time - me.lastChangeTime) < floor(time - me.lastChangeTime) + 0.5)
+		draw_Text(me.realOrigin + eX * (cursorPosInWidths - me.scrollPos), CURSOR, me.realFontSize, '1 1 1', 1, 0);
+
+	draw_ClearClip();
+}
+
+void showNotifyInputBox(entity me)
+{
+	me.focusable = !me.disabled;
+}
+#endif

Added: trunk/menu/item/inputcontainer.c
===================================================================
--- trunk/menu/item/inputcontainer.c	                        (rev 0)
+++ trunk/menu/item/inputcontainer.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,165 @@
+#ifdef INTERFACE
+CLASS(InputContainer) EXTENDS(Container)
+	METHOD(InputContainer, keyDown, float(entity, float, float, float))
+	METHOD(InputContainer, mouseMove, float(entity, vector))
+	METHOD(InputContainer, mousePress, float(entity, vector))
+	METHOD(InputContainer, mouseRelease, float(entity, vector))
+	METHOD(InputContainer, mouseDrag, float(entity, vector))
+	METHOD(InputContainer, focusLeave, void(entity))
+	METHOD(InputContainer, resizeNotify, void(entity, vector, vector, vector, vector))
+
+	METHOD(InputContainer, _changeFocusXY, float(entity, vector))
+	ATTRIB(InputContainer, mouseFocusedChild, entity, NULL)
+	ATTRIB(InputContainer, isTabRoot, float, 0)
+ENDCLASS(InputContainer)
+#endif
+
+#ifdef IMPLEMENTATION
+void resizeNotifyInputContainer(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
+{
+	resizeNotifyContainer(me, relOrigin, relSize, absOrigin, absSize);
+	/*
+	if(me.parent.instanceOfInputContainer)
+		me.isTabRoot = 0;
+	else
+		me.isTabRoot = 1;
+	*/
+}
+
+void focusLeaveInputContainer(entity me)
+{
+	focusLeaveContainer(me);
+	me.mouseFocusedChild = NULL;
+}
+
+float keyDownInputContainer(entity me, float scan, float ascii, float shift)
+{
+	entity f, ff;
+	if(keyDownContainer(me, scan, ascii, shift))
+		return 1;
+	if(scan == K_ESCAPE)
+	{
+		f = me.focusedChild;
+		if(f)
+		{
+			me.setFocus(me, NULL);
+			return 1;
+		}
+		return 0;
+	}
+	if(scan == K_TAB)
+	{
+		f = me.focusedChild;
+		if(shift & S_SHIFT)
+		{
+			if(f)
+			{
+				for(ff = f.prevSibling; ff; ff = ff.prevSibling)
+				{
+					if not(ff.focusable)
+						continue;
+					me.setFocus(me, ff);
+					return 1;
+				}
+			}
+			if(!f || me.isTabRoot)
+			{
+				for(ff = me.lastChild; ff; ff = ff.prevSibling)
+				{
+					if not(ff.focusable)
+						continue;
+					me.setFocus(me, ff);
+					return 1;
+				}
+				return 0; // AIIIIEEEEE!
+			}
+		}
+		else
+		{
+			if(f)
+			{
+				for(ff = f.nextSibling; ff; ff = ff.nextSibling)
+				{
+					if not(ff.focusable)
+						continue;
+					me.setFocus(me, ff);
+					return 1;
+				}
+			}
+			if(!f || me.isTabRoot)
+			{
+				for(ff = me.firstChild; ff; ff = ff.nextSibling)
+				{
+					if not(ff.focusable)
+						continue;
+					me.setFocus(me, ff);
+					return 1;
+				}
+				return 0; // AIIIIEEEEE!
+			}
+		}
+	}
+	return 0;
+}
+
+float _changeFocusXYInputContainer(entity me, vector pos)
+{
+	entity e, ne;
+	e = me.mouseFocusedChild;
+	ne = me.itemFromPoint(me, pos);
+	if(ne)
+		if not(ne.focusable)
+			ne = NULL;
+	me.mouseFocusedChild = ne;
+	if(ne)
+		if(ne != e)
+		{
+			me.setFocus(me, ne);
+			if(ne.instanceOfInputContainer)
+			{
+				ne.focusedChild = NULL;
+				ne._changeFocusXY(e, globalToBox(pos, ne.Container_origin, ne.Container_size));
+			}
+		}
+	return (ne != NULL);
+}
+
+float mouseDragInputContainer(entity me, vector pos)
+{
+	if(mouseDragContainer(me, pos))
+		return 1;
+	if(pos_x >= 0 && pos_y >= 0 && pos_x < 1 && pos_y < 1)
+		return 1;
+	return 0;
+}
+float mouseMoveInputContainer(entity me, vector pos)
+{
+	if(me._changeFocusXY(me, pos))
+		if(mouseMoveContainer(me, pos))
+			return 1;
+	if(pos_x >= 0 && pos_y >= 0 && pos_x < 1 && pos_y < 1)
+		return 1;
+	return 0;
+}
+float mousePressInputContainer(entity me, vector pos)
+{
+	me.mouseFocusedChild = NULL; // force focusing
+	if(me._changeFocusXY(me, pos))
+		if(mousePressContainer(me, pos))
+			return 1;
+	if(pos_x >= 0 && pos_y >= 0 && pos_x < 1 && pos_y < 1)
+		return 1;
+	return 0;
+}
+float mouseReleaseInputContainer(entity me, vector pos)
+{
+	float r;
+	r = mouseReleaseContainer(me, pos);
+	if(me.focused) // am I still eligible for this? (UGLY HACK, but a mouse event could have changed focus away)
+		if(me._changeFocusXY(me, pos))
+			return 1;
+	if(pos_x >= 0 && pos_y >= 0 && pos_x < 1 && pos_y < 1)
+		return 1;
+	return 0;
+}
+#endif

Added: trunk/menu/item/label.c
===================================================================
--- trunk/menu/item/label.c	                        (rev 0)
+++ trunk/menu/item/label.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,71 @@
+#ifdef INTERFACE
+CLASS(Label) EXTENDS(Item)
+	METHOD(Label, configureLabel, void(entity, string, float, float))
+	METHOD(Label, draw, void(entity))
+	METHOD(Label, resizeNotify, void(entity, vector, vector, vector, vector))
+	METHOD(Label, setText, void(entity, string))
+	METHOD(Label, toString, string(entity))
+	ATTRIB(Label, text, string, string_null)
+	ATTRIB(Label, fontSize, float, 8)
+	ATTRIB(Label, align, float, 0.5)
+	ATTRIB(Label, allowCut, float, 0)
+	ATTRIB(Label, keepspaceLeft, float, 0) // for use by subclasses (radiobuttons for example)
+	ATTRIB(Label, keepspaceRight, float, 0)
+	ATTRIB(Label, realFontSize, vector, '0 0 0')
+	ATTRIB(Label, realOrigin, vector, '0 0 0')
+	ATTRIB(Label, alpha, float, 0.7)
+	ATTRIB(Label, colorL, vector, '1 1 1')
+	ATTRIB(Label, disabled, float, 0)
+	ATTRIB(Label, disabledAlpha, float, 0.3)
+	ATTRIB(Label, textEntity, entity, NULL)
+ENDCLASS(Label)
+#endif
+
+#ifdef IMPLEMENTATION
+string toStringLabel(entity me)
+{
+	return me.text;
+}
+void setTextLabel(entity me, string txt)
+{
+	me.text = txt;
+	me.realOrigin_x = me.align * (1 - me.keepspaceLeft - me.keepspaceRight - min(me.realFontSize_x * draw_TextWidth(me.text, 0), (1 - me.keepspaceLeft - me.keepspaceRight))) + me.keepspaceLeft;
+}
+void resizeNotifyLabel(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
+{
+	// absSize_y is height of label
+	me.realFontSize_y = me.fontSize / absSize_y;
+	me.realFontSize_x = me.fontSize / absSize_x;
+	me.realOrigin_x = me.align * (1 - me.keepspaceLeft - me.keepspaceRight - min(me.realFontSize_x * draw_TextWidth(me.text, 0), (1 - me.keepspaceLeft - me.keepspaceRight))) + me.keepspaceLeft;
+	me.realOrigin_y = 0.5 * (1 - me.realFontSize_y);
+}
+void configureLabelLabel(entity me, string txt, float sz, float algn)
+{
+	me.fontSize = sz;
+	me.align = algn;
+	me.setText(me, txt);
+}
+void drawLabel(entity me)
+{
+	string t;
+	if(me.disabled)
+		draw_alpha *= me.disabledAlpha;
+
+	if(me.textEntity)
+	{
+		t = me.textEntity.toString(me.textEntity);
+		me.realOrigin_x = me.align * (1 - me.keepspaceLeft - me.keepspaceRight - min(me.realFontSize_x * draw_TextWidth(t, 0), (1 - me.keepspaceLeft - me.keepspaceRight))) + me.keepspaceLeft;
+	}
+	else
+		t = me.text;
+	
+	if(me.fontSize)
+		if(t)
+		{
+			if(me.allowCut)
+				draw_Text(me.realOrigin, draw_TextShortenToWidth(t, (1 - me.keepspaceLeft - me.keepspaceRight) / me.realFontSize_x, 0), me.realFontSize, me.colorL, me.alpha, 0);
+			else
+				draw_Text(me.realOrigin, t, me.realFontSize, me.colorL, me.alpha, 0);
+		}
+}
+#endif

Added: trunk/menu/item/listbox.c
===================================================================
--- trunk/menu/item/listbox.c	                        (rev 0)
+++ trunk/menu/item/listbox.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,285 @@
+#ifdef INTERFACE
+CLASS(ListBox) EXTENDS(Item)
+	METHOD(ListBox, resizeNotify, void(entity, vector, vector, vector, vector))
+	METHOD(ListBox, configureListBox, void(entity, float, float))
+	METHOD(ListBox, draw, void(entity))
+	METHOD(ListBox, keyDown, float(entity, float, float, float))
+	METHOD(ListBox, mousePress, float(entity, vector))
+	METHOD(ListBox, mouseDrag, float(entity, vector))
+	METHOD(ListBox, mouseRelease, float(entity, vector))
+	ATTRIB(ListBox, focusable, float, 1)
+	ATTRIB(ListBox, selectedItem, float, 0)
+	ATTRIB(ListBox, size, vector, '0 0 0')
+	ATTRIB(ListBox, scrollPos, float, 0) // measured in window heights, fixed when needed
+	ATTRIB(ListBox, previousValue, float, 0)
+	ATTRIB(ListBox, pressed, float, 0) // 0 = normal, 1 = scrollbar dragging, 2 = item dragging, 3 = released
+	ATTRIB(ListBox, pressOffset, float, 0)
+
+	METHOD(ListBox, updateControlTopBottom, void(entity))
+	ATTRIB(ListBox, controlTop, float, 0)
+	ATTRIB(ListBox, controlBottom, float, 0)
+	ATTRIB(ListBox, controlWidth, float, 0)
+	ATTRIB(ListBox, dragScrollTimer, float, 0)
+	ATTRIB(ListBox, dragScrollPos, vector, '0 0 0')
+
+	ATTRIB(ListBox, src, string, string_null) // scrollbar
+	ATTRIB(ListBox, color, vector, '1 1 1')
+	ATTRIB(ListBox, color2, vector, '1 1 1')
+	ATTRIB(ListBox, colorC, vector, '1 1 1')
+	ATTRIB(ListBox, colorF, vector, '1 1 1')
+	ATTRIB(ListBox, tolerance, vector, '0 0 0') // drag tolerance
+	ATTRIB(ListBox, scrollbarWidth, float, 0) // pixels
+	ATTRIB(ListBox, nItems, float, 42)
+	ATTRIB(ListBox, itemHeight, float, 0)
+	METHOD(ListBox, drawListBoxItem, void(entity, float, vector, float)) // item number, width/height, selected
+	METHOD(ListBox, clickListBoxItem, void(entity, float, vector)) // item number, relative clickpos
+	METHOD(ListBox, setSelected, void(entity, float))
+ENDCLASS(ListBox)
+#endif
+
+#ifdef IMPLEMENTATION
+void setSelectedListBox(entity me, float i)
+{
+	me.selectedItem = floor(0.5 + bound(0, i, me.nItems - 1));
+}
+void resizeNotifyListBox(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
+{
+	me.size = absSize;
+	me.controlWidth = me.scrollbarWidth / absSize_x;
+}
+void configureListBoxListBox(entity me, float theScrollbarWidth, float theItemHeight)
+{
+	me.scrollbarWidth = theScrollbarWidth;
+	me.itemHeight = theItemHeight;
+}
+float keyDownListBox(entity me, float key, float ascii, float shift)
+{
+	me.dragScrollTimer = time;
+	if(key == K_MWHEELUP)
+	{
+		me.scrollPos = max(me.scrollPos - 0.5, 0);
+		me.setSelected(me, min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1)));
+	}
+	else if(key == K_MWHEELDOWN)
+	{
+		me.scrollPos = min(me.scrollPos + 0.5, me.nItems * me.itemHeight - 1);
+		me.setSelected(me, max(me.selectedItem, ceil(me.scrollPos / me.itemHeight)));
+	}
+	else if(key == K_PGUP)
+		me.setSelected(me, me.selectedItem - 1 / me.itemHeight);
+	else if(key == K_PGDN)
+		me.setSelected(me, me.selectedItem + 1 / me.itemHeight);
+	else if(key == K_UPARROW)
+		me.setSelected(me, me.selectedItem - 1);
+	else if(key == K_DOWNARROW)
+		me.setSelected(me, me.selectedItem + 1);
+	else if(key == K_HOME)
+	{
+		me.scrollPos = 0;
+		me.setSelected(me, 0);
+	}
+	else if(key == K_END)
+	{
+		me.scrollPos = max(0, me.nItems * me.itemHeight - 1);
+		me.setSelected(me, me.nItems - 1);
+	}
+	else
+		return 0;
+	return 1;
+}
+float mouseDragListBox(entity me, vector pos)
+{
+	float hit;
+	float i;
+	me.updateControlTopBottom(me);
+	me.dragScrollPos = pos;
+	if(me.pressed == 1)
+	{
+		hit = 1;
+		if(pos_x < 1 - me.controlWidth - me.tolerance_y * me.controlWidth) hit = 0;
+		if(pos_y < 0 - me.tolerance_x) hit = 0;
+		if(pos_x >= 1 + me.tolerance_y * me.controlWidth) hit = 0;
+		if(pos_y >= 1 + me.tolerance_x) hit = 0;
+		if(hit)
+		{
+			// calculate new pos to v
+			float delta;
+			delta = (pos_y - me.pressOffset) / (1 - (me.controlBottom - me.controlTop)) * (me.nItems * me.itemHeight - 1);
+			me.scrollPos = me.previousValue + delta;
+		}
+		else
+			me.scrollPos = me.previousValue;
+		me.scrollPos = min(me.scrollPos, me.nItems * me.itemHeight - 1);
+		me.scrollPos = max(me.scrollPos, 0);
+		i = min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1));
+		i = max(i, ceil(me.scrollPos / me.itemHeight));
+		me.setSelected(me, i);
+	}
+	else if(me.pressed == 2)
+	{
+		me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
+	}
+	return 1;
+}
+float mousePressListBox(entity me, vector pos)
+{
+	if(pos_x < 0) return 0;
+	if(pos_y < 0) return 0;
+	if(pos_x >= 1) return 0;
+	if(pos_y >= 1) return 0;
+	me.dragScrollPos = pos;
+	me.updateControlTopBottom(me);
+	me.dragScrollTimer = time;
+	if(pos_x >= 1 - me.controlWidth)
+	{
+		// if hit, set me.pressed, otherwise scroll by one page
+		if(pos_y < me.controlTop)
+		{
+			// page up
+			me.scrollPos = max(me.scrollPos - 1, 0);
+			me.setSelected(me, min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1)));
+		}
+		else if(pos_y > me.controlBottom)
+		{
+			// page down
+			me.scrollPos = min(me.scrollPos + 1, me.nItems * me.itemHeight - 1);
+			me.setSelected(me, max(me.selectedItem, ceil(me.scrollPos / me.itemHeight)));
+		}
+		else
+		{
+			me.pressed = 1;
+			me.pressOffset = pos_y;
+			me.previousValue = me.scrollPos;
+		}
+	}
+	else
+	{
+		// continue doing that while dragging (even when dragging outside). When releasing, forward the click to the then selected item.
+		me.pressed = 2;
+		// an item has been clicked. Select it, ...
+		me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
+	}
+	return 1;
+}
+float mouseReleaseListBox(entity me, vector pos)
+{
+	vector absSize;
+	if(me.pressed == 1)
+	{
+		// slider dragging mode
+		// in that case, nothing happens on releasing
+	}
+	else if(me.pressed == 2)
+	{
+		me.pressed = 3; // do that here, so setSelected can know the mouse has been released
+		// item dragging mode
+		// select current one one last time...
+		me.setSelected(me, floor((me.scrollPos + pos_y) / me.itemHeight));
+		// and give it a nice click event
+		if(me.nItems > 0)
+		{
+			absSize = boxToGlobalSize(me.size, eX * (1 - me.controlWidth) + eY * me.itemHeight);
+			me.clickListBoxItem(me, me.selectedItem, globalToBox(pos, eY * (me.selectedItem * me.itemHeight - me.scrollPos), eX * (1 - me.controlWidth) + eY * me.itemHeight));
+		}
+	}
+	me.pressed = 0;
+	return 1;
+}
+void updateControlTopBottomListBox(entity me)
+{
+	float f;
+	// scrollPos is in 0..1 and indicates where the "page" currently shown starts.
+	if(me.nItems * me.itemHeight <= 1)
+	{
+		// we don't need no stinkin' scrollbar, we don't need no view control...
+		me.controlTop = 0;
+		me.controlBottom = 1;
+		me.scrollPos = 0;
+	}
+	else
+	{
+		if(frametime) // only do this in draw frames
+		{
+			if(me.dragScrollTimer < time)
+			{
+				float save;
+				save = me.scrollPos;
+				// if selected item is below listbox, increase scrollpos so it is in
+				me.scrollPos = max(me.scrollPos, me.selectedItem * me.itemHeight - 1 + me.itemHeight);
+				// if selected item is above listbox, decrease scrollpos so it is in
+				me.scrollPos = min(me.scrollPos, me.selectedItem * me.itemHeight);
+				if(me.scrollPos != save)
+					me.dragScrollTimer = time + 0.2;
+			}
+		}
+		// if scroll pos is below end of list, fix it
+		me.scrollPos = min(me.scrollPos, me.nItems * me.itemHeight - 1);
+		// if scroll pos is above beginning of list, fix it
+		me.scrollPos = max(me.scrollPos, 0);
+		// now that we know where the list is scrolled to, find out where to draw the control
+		me.controlTop = max(0, me.scrollPos / (me.nItems * me.itemHeight));
+		me.controlBottom = min((me.scrollPos + 1) / (me.nItems * me.itemHeight), 1);
+
+		float fmin;
+		fmin = 1 * me.controlWidth / me.size_y * me.size_x;
+		f = me.controlBottom - me.controlTop;
+		if(f < fmin) // FIXME good default?
+		{
+			// f * X + 1 * (1-X) = fmin
+			// (f - 1) * X + 1 = fmin
+			// (f - 1) * X = fmin - 1
+			// X = (fmin - 1) / (f - 1)
+			f = (fmin - 1) / (f - 1);
+			me.controlTop = me.controlTop * f + 0 * (1 - f);
+			me.controlBottom = me.controlBottom * f + 1 * (1 - f);
+		}
+	}
+}
+void drawListBox(entity me)
+{
+	float i;
+	vector absSize;
+	vector oldshift, oldscale;
+	if(me.pressed == 2)
+		me.mouseDrag(me, me.dragScrollPos); // simulate mouseDrag event
+	me.updateControlTopBottom(me);
+	draw_VertButtonPicture(eX * (1 - me.controlWidth), strcat(me.src, "_s"), eX * me.controlWidth + eY, me.color2, 1);
+	if(me.nItems * me.itemHeight > 1)
+	{
+		vector o, s;
+		o = eX * (1 - me.controlWidth) + eY * me.controlTop;
+		s = eX * me.controlWidth + eY * (me.controlBottom - me.controlTop);
+		if(me.pressed == 1)
+			draw_VertButtonPicture(o, strcat(me.src, "_c"), s, me.colorC, 1);
+		else if(me.focused)
+			draw_VertButtonPicture(o, strcat(me.src, "_f"), s, me.colorF, 1);
+		else
+			draw_VertButtonPicture(o, strcat(me.src, "_n"), s, me.color, 1);
+	}
+	draw_SetClip();
+	oldshift = draw_shift;
+	oldscale = draw_scale;
+	absSize = boxToGlobalSize(me.size, eX * (1 - me.controlWidth) + eY * me.itemHeight);
+	for(i = floor(me.scrollPos / me.itemHeight); i < me.nItems; ++i)
+	{
+		float y;
+		y = i * me.itemHeight - me.scrollPos;
+		if(y >= 1)
+			break;
+		draw_shift = boxToGlobal(eY * y, oldshift, oldscale);
+		draw_scale = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), oldscale);
+		me.drawListBoxItem(me, i, absSize, (me.selectedItem == i));
+	}
+	draw_ClearClip();
+}
+
+void clickListBoxItemListBox(entity me, float i, vector where)
+{
+	// itemclick, itemclick, does whatever itemclick does
+}
+
+void drawListBoxItemListBox(entity me, float i, vector absSize, float selected)
+{
+	draw_Text('0 0 0', strcat("Item ", ftos(i)), eX * (8 / absSize_x) + eY * (8 / absSize_y), (selected ? '0 1 0' : '1 1 1'), 1, 0);
+}
+#endif

Added: trunk/menu/item/modalcontroller.c
===================================================================
--- trunk/menu/item/modalcontroller.c	                        (rev 0)
+++ trunk/menu/item/modalcontroller.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,282 @@
+#ifdef INTERFACE
+CLASS(ModalController) EXTENDS(Container)
+	METHOD(ModalController, resizeNotify, void(entity, vector, vector, vector, vector))
+	METHOD(ModalController, draw, void(entity))
+	METHOD(ModalController, addItem, void(entity, entity, vector, vector, float))
+	METHOD(ModalController, showChild, void(entity, entity, vector, vector, float))
+	METHOD(ModalController, hideChild, void(entity, entity, float))
+	METHOD(ModalController, hideAll, void(entity, float))
+	METHOD(ModalController, addItem, void(entity, entity, vector, vector, float))
+	METHOD(ModalController, addTab, void(entity, entity, entity))
+
+	METHOD(ModalController, initializeDialog, void(entity, entity))
+
+	METHOD(ModalController, switchState, void(entity, entity, float, float))
+	ATTRIB(ModalController, origin, vector, '0 0 0')
+	ATTRIB(ModalController, size, vector, '0 0 0')
+	ATTRIB(ModalController, previousButton, entity, NULL)
+	ATTRIB(ModalController, fadedAlpha, float, 0.3)
+ENDCLASS(ModalController)
+
+.vector origin;
+.vector size;
+void TabButton_Click(entity button, entity tab); // assumes a button has set the above fields to its own absolute origin, its size, and the tab to activate
+void DialogOpenButton_Click(entity button, entity tab); // assumes a button has set the above fields to its own absolute origin, its size, and the tab to activate
+void DialogOpenButton_Click_withCoords(entity button, entity tab, vector theOrigin, vector theSize);
+void DialogCloseButton_Click(entity button, entity tab); // assumes a button has set the above fields to the tab to close
+#endif
+
+#ifdef IMPLEMENTATION
+
+// modal dialog controller
+// handles a stack of dialog elements
+// each element can have one of the following states:
+//   0: hidden (fading out)
+//   1: visible (zooming in)
+//   2: greyed out (inactive)
+// While an animation is running, no item has focus. When an animation is done,
+// the topmost item gets focus.
+// The items are assumed to be added in overlapping order, that is, the lowest
+// window must get added first.
+//
+// Possible uses:
+// - to control a modal dialog:
+//   - show modal dialog: me.showChild(me, childItem, buttonAbsOrigin, buttonAbsSize, 0) // childItem also gets focus
+//   - dismiss modal dialog: me.hideChild(me, childItem, 0) // childItem fades out and relinquishes focus
+//   - show first screen in m_show: me.hideAll(me, 1); me.showChild(me, me.firstChild, '0 0 0', '0 0 0', 1);
+// - to show a temporary dialog instead of the menu (teamselect): me.hideAll(me, 1); me.showChild(me, teamSelectDialog, '0 0 0', '0 0 0', 1);
+// - as a tabbed dialog control:
+//   - to initialize: me.hideAll(me, 1); me.showChild(me, me.firstChild, '0 0 0', '0 0 0', 1);
+//   - to show a tab: me.hideChild(me, currentTab, 0); me.showChild(me, newTab, buttonAbsOrigin, buttonAbsSize, 0);
+
+.vector ModalController_initialSize;
+.vector ModalController_initialOrigin;
+.float ModalController_initialAlpha;
+.vector ModalController_buttonSize;
+.vector ModalController_buttonOrigin;
+.float ModalController_state;
+.float ModalController_factor;
+.entity ModalController_controllingButton;
+
+void initializeDialogModalController(entity me, entity root)
+{
+	me.hideAll(me, 1);
+	me.showChild(me, root, '0 0 0', '0 0 0', 1); // someone else animates for us
+}
+
+void TabButton_Click(entity button, entity tab)
+{
+	if(tab.ModalController_state == 1)
+		return;
+	tab.parent.hideAll(tab.parent, 0);
+	button.forcePressed = 1;
+	tab.ModalController_controllingButton = button;
+	tab.parent.showChild(tab.parent, tab, button.origin, button.size, 0);
+}
+
+void DialogOpenButton_Click(entity button, entity tab)
+{
+	DialogOpenButton_Click_withCoords(button, tab, button.origin, button.size);
+}
+
+void DialogOpenButton_Click_withCoords(entity button, entity tab, vector theOrigin, vector theSize)
+{
+	if(tab.ModalController_state)
+		return;
+	if(button)
+		button.forcePressed = 1;
+	tab.ModalController_controllingButton = button;
+	tab.parent.showChild(tab.parent, tab, theOrigin, theSize, 0);
+}
+
+void DialogCloseButton_Click(entity button, entity tab)
+{
+	tab.parent.hideChild(tab.parent, tab, 0);
+}
+
+void resizeNotifyModalController(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
+{
+	me.origin = absOrigin;
+	me.size = absSize;
+	me.resizeNotifyLie(me, relOrigin, relSize, absOrigin, absSize, ModalController_initialOrigin, ModalController_initialSize);
+}
+
+void switchStateModalController(entity me, entity other, float state, float skipAnimation)
+{
+	float previousState;
+	previousState = other.ModalController_state;
+	if(state == previousState && !skipAnimation)
+		return;
+	other.ModalController_state = state;
+	switch(state)
+	{
+		case 0:
+			other.ModalController_factor = 1 - other.Container_alpha / other.ModalController_initialAlpha;
+			// fading out
+			break;
+		case 1:
+			other.ModalController_factor = other.Container_alpha / other.ModalController_initialAlpha;
+			if(previousState == 0 && !skipAnimation)
+			{
+				other.Container_origin = other.ModalController_buttonOrigin;
+				other.Container_size = other.ModalController_buttonSize;
+			}
+			// zooming in
+			break;
+		case 2:
+			other.ModalController_factor = bound(0, (1 - other.Container_alpha / other.ModalController_initialAlpha) / me.fadedAlpha, 1);
+			// fading out halfway
+			break;
+	}
+	if(skipAnimation)
+		other.ModalController_factor = 1;
+}
+
+void drawModalController(entity me)
+{
+	entity e;
+	entity front;
+	float animating;
+	float f; // animation factor
+	float df; // animation step size
+	float prevFactor, targetFactor;
+	vector targetOrigin, targetSize; float targetAlpha;
+	animating = 0;
+
+	for(e = me.firstChild; e; e = e.nextSibling)
+		if(e.ModalController_state)
+		{
+			if(front)
+				me.switchState(me, front, 2, 0);
+			front = e;
+		}
+	if(front)
+		me.switchState(me, front, 1, 0);
+
+	df = frametime * 3; // animation speed
+
+	for(e = me.firstChild; e; e = e.nextSibling)
+	{
+		f = (e.ModalController_factor = min(1, e.ModalController_factor + df));
+		if(e.ModalController_state)
+			if(f < 1)
+				animating = 1;
+
+		if(f < 1)
+		{
+			prevFactor   = (1 - f) / (1 - f + df);
+			targetFactor =     df  / (1 - f + df);
+		}
+		else
+		{
+			prevFactor = 0;
+			targetFactor = 1;
+		}
+
+		if(e.ModalController_state == 2)
+		{
+			// fading out partially
+			targetOrigin = e.Container_origin; // stay as is
+			targetSize = e.Container_size; // stay as is
+			targetAlpha = me.fadedAlpha * e.ModalController_initialAlpha;
+		}
+		else if(e.ModalController_state == 1)
+		{
+			// zooming in
+			targetOrigin = e.ModalController_initialOrigin;
+			targetSize = e.ModalController_initialSize;
+			targetAlpha = e.ModalController_initialAlpha;
+		}
+		else
+		{
+			// fading out
+			if(f < 1)
+				animating = 1;
+			targetOrigin = e.Container_origin; // stay as is
+			targetSize = e.Container_size; // stay as is
+			targetAlpha = 0;
+		}
+
+		if(f == 1)
+		{
+			e.Container_origin = targetOrigin;
+			e.Container_size = targetSize;
+			me.setAlphaOf(me, e, targetAlpha);
+		}
+		else
+		{
+			e.Container_origin = e.Container_origin * prevFactor + targetOrigin * targetFactor;
+			e.Container_size   = e.Container_size   * prevFactor + targetSize   * targetFactor;
+			me.setAlphaOf(me, e, e.Container_alpha  * prevFactor + targetAlpha  * targetFactor);
+		}
+		// assume: o == to * f_prev + X * (1 - f_prev)
+		// make:   o' = to * f  + X * (1 - f)
+		// -->
+		// X == (o - to * f_prev) / (1 - f_prev)
+		// o' = to * f + (o - to * f_prev) / (1 - f_prev) * (1 - f)
+		// --> (maxima)
+		// o' = (to * (f - f_prev) + o * (1 - f)) / (1 - f_prev)
+	}
+	if(animating || !me.focused)
+		me.setFocus(me, NULL);
+	else
+		me.setFocus(me, front);
+	drawContainer(me);
+};
+
+void addTabModalController(entity me, entity other, entity tabButton)
+{
+	me.addItem(me, other, '0 0 0', '1 1 1', 1);
+	tabButton.onClick = TabButton_Click;
+	tabButton.onClickEntity = other;
+	if(other == me.firstChild)
+	{
+		tabButton.forcePressed = 1;
+		other.ModalController_controllingButton = tabButton;
+		me.showChild(me, other, '0 0 0', '0 0 0', 1);
+	}
+}
+
+void addItemModalController(entity me, entity other, vector theOrigin, vector theSize, float theAlpha)
+{
+	addItemContainer(me, other, theOrigin, theSize, (other == me.firstChild) ? theAlpha : 0);
+	other.ModalController_initialSize = other.Container_size;
+	other.ModalController_initialOrigin = other.Container_origin;
+	other.ModalController_initialAlpha = theAlpha; // hope Container never modifies this
+}
+
+void showChildModalController(entity me, entity other, vector theOrigin, vector theSize, float skipAnimation)
+{
+	if(other.ModalController_state == 0 || skipAnimation)
+	{
+		me.setFocus(me, NULL);
+		if(!skipAnimation)
+		{
+			other.ModalController_buttonOrigin = globalToBox(theOrigin, me.origin, me.size);
+			other.ModalController_buttonSize = globalToBoxSize(theSize, me.size);
+		}
+		me.switchState(me, other, 1, skipAnimation);
+	} // zoom in from button (factor increases)
+}
+
+void hideAllModalController(entity me, float skipAnimation)
+{
+	entity e;
+	for(e = me.firstChild; e; e = e.nextSibling)
+		me.hideChild(me, e, skipAnimation);
+}
+
+void hideChildModalController(entity me, entity other, float skipAnimation)
+{
+	if(other.ModalController_state || skipAnimation)
+	{
+		me.setFocus(me, NULL);
+		me.switchState(me, other, 0, skipAnimation);
+		if(other.ModalController_controllingButton)
+		{
+			other.ModalController_controllingButton.forcePressed = 0;
+			other.ModalController_controllingButton = NULL;
+		}
+	} // just alpha fade out (factor increases and decreases alpha)
+}
+#endif

Added: trunk/menu/item/nexposee.c
===================================================================
--- trunk/menu/item/nexposee.c	                        (rev 0)
+++ trunk/menu/item/nexposee.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,371 @@
+#ifdef INTERFACE
+CLASS(Nexposee) EXTENDS(Container)
+	METHOD(Nexposee, draw, void(entity))
+	METHOD(Nexposee, keyDown, float(entity, float, float, float))
+	METHOD(Nexposee, keyUp, float(entity, float, float, float))
+	METHOD(Nexposee, mousePress, float(entity, vector))
+	METHOD(Nexposee, mouseMove, float(entity, vector))
+	METHOD(Nexposee, mouseRelease, float(entity, vector))
+	METHOD(Nexposee, mouseDrag, float(entity, vector))
+	METHOD(Nexposee, resizeNotify, void(entity, vector, vector, vector, vector))
+	METHOD(Nexposee, focusEnter, void(entity))
+	METHOD(Nexposee, close, void(entity))
+
+	ATTRIB(Nexposee, animationState, float, -1)
+	ATTRIB(Nexposee, animationFactor, float, 0)
+	ATTRIB(Nexposee, selectedChild, entity, NULL)
+	ATTRIB(Nexposee, mouseFocusedChild, entity, NULL)
+	METHOD(Nexposee, addItem, void(entity, entity, vector, vector, float))
+	METHOD(Nexposee, calc, void(entity))
+	METHOD(Nexposee, setNexposee, void(entity, entity, vector, float, float))
+	ATTRIB(Nexposee, mousePosition, vector, '0 0 0')
+	METHOD(Nexposee, pullNexposee, void(entity, entity, vector))
+ENDCLASS(Nexposee)
+
+void ExposeeCloseButton_Click(entity button, entity other); // un-exposees the current state
+#endif
+
+// animation states:
+//   0 = thumbnails seen
+//   1 = zooming in
+//   2 = zoomed in
+//   3 = zooming out
+// animation factor: 0 = minimum theSize, 1 = maximum theSize
+
+#ifdef IMPLEMENTATION
+
+.vector Nexposee_initialSize;
+.vector Nexposee_initialOrigin;
+.float Nexposee_initialAlpha;
+
+.vector Nexposee_smallSize;
+.vector Nexposee_smallOrigin;
+.float Nexposee_smallAlpha;
+.float Nexposee_mediumAlpha;
+.vector Nexposee_scaleCenter;
+.vector Nexposee_align;
+.float Nexposee_animationFactor;
+
+void closeNexposee(entity me)
+{
+	// user must override this
+}
+
+void ExposeeCloseButton_Click(entity button, entity other)
+{
+	other.selectedChild = other.focusedChild;
+	other.setFocus(other, NULL);
+	other.animationState = 3;
+}
+
+void resizeNotifyNexposee(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
+{
+	me.calc(me);
+	me.resizeNotifyLie(me, relOrigin, relSize, absOrigin, absSize, Nexposee_initialOrigin, Nexposee_initialSize);
+}
+
+void Nexposee_Calc_Scale(entity me, float scale)
+{
+	entity e;
+	for(e = me.firstChild; e; e = e.nextSibling)
+	{
+		e.Nexposee_smallOrigin = (e.Nexposee_initialOrigin - e.Nexposee_scaleCenter) * scale + e.Nexposee_scaleCenter;
+		e.Nexposee_smallSize = e.Nexposee_initialSize * scale;
+		if(e.Nexposee_align_x > 0)
+			e.Nexposee_smallOrigin_x = 1 - e.Nexposee_align_x * scale;
+		if(e.Nexposee_align_x < 0)
+			e.Nexposee_smallOrigin_x = -e.Nexposee_smallSize_x + e.Nexposee_align_x * scale;
+		if(e.Nexposee_align_y > 0)
+			e.Nexposee_smallOrigin_y = 1 - e.Nexposee_align_y * scale;
+		if(e.Nexposee_align_y < 0)
+			e.Nexposee_smallOrigin_y = -e.Nexposee_smallSize_y + e.Nexposee_align_y * scale;
+	}
+}
+
+void calcNexposee(entity me)
+{
+	/*
+	 * patented by Apple
+	 * can't put that here ;)
+	 */
+	float scale;
+	entity e, e2;
+	vector emins, emaxs, e2mins, e2maxs;
+	
+	for(scale = 0.7;; scale *= 0.99)
+	{
+		Nexposee_Calc_Scale(me, scale);
+
+		for(e = me.firstChild; e; e = e.nextSibling)
+		{
+			emins = e.Nexposee_smallOrigin;
+			emaxs = emins + e.Nexposee_smallSize;
+			for(e2 = e.nextSibling; e2; e2 = e2.nextSibling)
+			{
+				e2mins = e2.Nexposee_smallOrigin;
+				e2maxs = e2mins + e2.Nexposee_smallSize;
+
+				// two intervals [amins, amaxs] and [bmins, bmaxs] overlap if:
+				//   amins < bmins < amaxs < bmaxs
+				// for which suffices
+				//   bmins < amaxs
+				//   amins < bmaxs
+				if((e2mins_x - emaxs_x) * (emins_x - e2maxs_x) > 0) // x overlap
+					if((e2mins_y - emaxs_y) * (emins_y - e2maxs_y) > 0) // y overlap
+					{
+						goto have_overlap;
+					}
+			}
+		}
+
+		break;
+:have_overlap
+	}
+
+	scale *= 0.95;
+
+	Nexposee_Calc_Scale(me, scale);
+}
+
+void setNexposeeNexposee(entity me, entity other, vector scalecenter, float a0, float a1)
+{
+	other.Nexposee_scaleCenter = scalecenter;
+	other.Nexposee_smallAlpha = a0;
+	me.setAlphaOf(me, other, a0);
+	other.Nexposee_mediumAlpha = a1;
+}
+
+void drawNexposee(entity me)
+{
+	float a;
+	float a0;
+	entity e;
+	float f;
+
+	if(me.animationState == -1)
+	{
+		me.animationState = 0;
+	}
+
+	//print(ftos(me.animationState), "\n");
+
+	f = min(1, frametime * 5);
+	switch(me.animationState)
+	{
+		case 0:
+			me.animationFactor = 0;
+			break;
+		case 1:
+			me.animationFactor += f;
+			if(me.animationFactor >= 1)
+			{
+				me.animationFactor = 1;
+				me.animationState = 2;
+				setFocusContainer(me, me.selectedChild);
+			}
+			break;
+		case 2:
+			me.animationFactor = 1;
+			break;
+		case 3:
+			me.animationFactor -= f;
+			me.mouseFocusedChild = me.itemFromPoint(me, me.mousePosition);
+			if(me.animationFactor <= 0)
+			{
+				me.animationFactor = 0;
+				me.animationState = 0;
+				me.selectedChild = me.mouseFocusedChild;
+			}
+			break;
+	}
+
+	f = min(1, frametime * 10);
+	for(e = me.firstChild; e; e = e.nextSibling)
+	{
+		if(e == me.selectedChild)
+		{
+			e.Container_origin = e.Nexposee_smallOrigin * (1 - me.animationFactor) + e.Nexposee_initialOrigin * me.animationFactor;
+			e.Container_size = e.Nexposee_smallSize * (1 - me.animationFactor) + e.Nexposee_initialSize * me.animationFactor;
+			e.Nexposee_animationFactor = me.animationFactor;
+			a0 = e.Nexposee_mediumAlpha;
+			if(me.animationState == 3)
+				if(e != me.mouseFocusedChild)
+					a0 = e.Nexposee_smallAlpha;
+			a = a0 * (1 - me.animationFactor) + me.animationFactor;
+		}
+		else
+		{
+			// minimum theSize counts
+			e.Container_origin = e.Nexposee_smallOrigin;
+			e.Container_size = e.Nexposee_smallSize;
+			e.Nexposee_animationFactor = 0;
+			a = e.Nexposee_smallAlpha * (1 - me.animationFactor);
+		}
+		me.setAlphaOf(me, e, e.Container_alpha * (1 - f) + a * f);
+	}
+
+	drawContainer(me);
+
+	/*
+	for(e = me.firstChild; e; e = e.nextSibling)
+	{
+		vector t, fs;
+		a0 = e.Container_alpha;
+		if(a0 < e.Nexposee_smallAlpha)
+			a = 0.3 * (a0 - 0) / (e.Nexposee_smallAlpha - 0);
+		else if(a0 < e.Nexposee_mediumAlpha)
+			a = 0.3 + 0.5 * (a0 - e.Nexposee_smallAlpha)  / (e.Nexposee_mediumAlpha - e.Nexposee_smallAlpha);
+		else
+			a = 0.8 - 0.8 * (a0 - e.Nexposee_mediumAlpha) / (1 - e.Nexposee_mediumAlpha);
+		fs = (eX * (1 / draw_scale_x) + eY * (1 / draw_scale_y)) * 36;
+		t = draw_TextWidth(e.title, FALSE) * eX * fs_x + eY * fs_y;
+		draw_Text(e.Container_origin + (e.Container_size_x * eX - t) * 0.5 - 0.5 * eY * t_y, e.title, fs, e.color, a, FALSE);
+	}
+	*/
+};
+
+float mousePressNexposee(entity me, vector pos)
+{
+	if(me.animationState == 0)
+	{
+		me.mouseFocusedChild = NULL;
+		mouseMoveNexposee(me, pos);
+		if(me.mouseFocusedChild)
+		{
+			me.animationState = 1;
+			setFocusContainer(me, NULL);
+		}
+		else
+			me.close(me);
+		return 1;
+	}
+	else if(me.animationState == 2)
+	{
+		if not(mousePressContainer(me, pos))
+		{
+			me.animationState = 3;
+			setFocusContainer(me, NULL);
+		}
+		return 1;
+	}
+	return 0;
+}
+
+float mouseReleaseNexposee(entity me, vector pos)
+{
+	if(me.animationState == 2)
+		return mouseReleaseContainer(me, pos);
+	return 0;
+}
+
+float mouseDragNexposee(entity me, vector pos)
+{
+	if(me.animationState == 2)
+		return mouseDragContainer(me, pos);
+	return 0;
+}
+
+float mouseMoveNexposee(entity me, vector pos)
+{
+	entity e;
+	me.mousePosition = pos;
+	e = me.mouseFocusedChild;
+	me.mouseFocusedChild = me.itemFromPoint(me, pos);
+	if(me.animationState == 2)
+		return mouseMoveContainer(me, pos);
+	if(me.animationState == 0)
+	{
+		if(me.mouseFocusedChild)
+			if(me.mouseFocusedChild != e)
+				me.selectedChild = me.mouseFocusedChild;
+		return 1;
+	}
+	return 0;
+}
+
+float keyUpNexposee(entity me, float scan, float ascii, float shift)
+{
+	if(me.animationState == 2)
+		return keyUpContainer(me, scan, ascii, shift);
+	return 0;
+}
+
+float keyDownNexposee(entity me, float scan, float ascii, float shift)
+{
+	float nexposeeKey;
+	if(me.animationState == 2)
+		if(keyDownContainer(me, scan, ascii, shift))
+			return 1;
+	if(scan == K_TAB)
+	{
+		if(me.animationState == 0)
+		{
+			if(shift & S_SHIFT)
+			{
+				if(me.selectedChild)
+					me.selectedChild = me.selectedChild.prevSibling;
+				if not(me.selectedChild)
+					me.selectedChild = me.lastChild;
+			}
+			else
+			{
+				if(me.selectedChild)
+					me.selectedChild = me.selectedChild.nextSibling;
+				if not(me.selectedChild)
+					me.selectedChild = me.firstChild;
+			}
+		}
+	}
+	switch(me.animationState)
+	{
+		case 0:
+		case 3:
+			nexposeeKey = ((scan == K_SPACE) || (scan == K_ENTER));
+			break;
+		case 1:
+		case 2:
+			nexposeeKey = (scan == K_ESCAPE);
+			break;
+	}
+	if(nexposeeKey)
+	{
+		switch(me.animationState)
+		{
+			case 0:
+			case 3:
+				me.animationState = 1;
+				break;
+			case 1:
+			case 2:
+				me.animationState = 3;
+				break;
+		}
+		if(me.focusedChild)
+			me.selectedChild = me.focusedChild;
+		if not(me.selectedChild)
+			me.animationState = 0;
+		setFocusContainer(me, NULL);
+		return 1;
+	}
+	return 0;
+}
+
+void addItemNexposee(entity me, entity other, vector theOrigin, vector theSize, float theAlpha)
+{
+	addItemContainer(me, other, theOrigin, theSize, theAlpha);
+	other.Nexposee_initialSize = other.Container_size;
+	other.Nexposee_initialOrigin = other.Container_origin;
+	other.Nexposee_initialAlpha = other.Container_alpha;
+}
+
+void focusEnterNexposee(entity me)
+{
+	if(me.animationState == 2)
+		setFocusContainer(me, me.selectedChild);
+}
+
+void pullNexposeeNexposee(entity me, entity other, vector theAlign)
+{
+	other.Nexposee_align = theAlign;
+}
+#endif

Added: trunk/menu/item/radiobutton.c
===================================================================
--- trunk/menu/item/radiobutton.c	                        (rev 0)
+++ trunk/menu/item/radiobutton.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,37 @@
+#ifdef INTERFACE
+void RadioButton_Click(entity me, entity other);
+CLASS(RadioButton) EXTENDS(CheckBox)
+	METHOD(RadioButton, configureRadioButton, void(entity, string, float, string, float, float))
+	ATTRIB(RadioButton, checked, float, 0)
+	ATTRIB(RadioButton, group, float, 0)
+	ATTRIB(RadioButton, allowDeselect, float, 0)
+	ATTRIB(RadioButton, onClick, void(entity, entity), RadioButton_Click)
+ENDCLASS(RadioButton)
+#endif
+
+#ifdef IMPLEMENTATION
+void configureRadioButtonRadioButton(entity me, string txt, float sz, string gfx, float theGroup, float doAllowDeselect)
+{
+	me.configureCheckBox(me, txt, sz, gfx);
+	me.align = 0;
+	me.group = theGroup;
+	me.allowDeselect = doAllowDeselect;
+}
+void RadioButton_Click(entity me, entity other)
+{
+	if(me.checked)
+	{
+		if(me.allowDeselect)
+			me.setChecked(me, 0);
+	}
+	else
+	{
+		entity e;
+		for(e = me.parent.firstChild; e; e = e.nextSibling)
+			if(e != me)
+				if(e.group == me.group)
+					e.setChecked(e, 0);
+		me.setChecked(me, 1);
+	}
+}
+#endif

Added: trunk/menu/item/slider.c
===================================================================
--- trunk/menu/item/slider.c	                        (rev 0)
+++ trunk/menu/item/slider.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,225 @@
+// Note:
+//   to use this, you FIRST call configureSliderVisuals, then configureSliderValues
+#ifdef INTERFACE
+CLASS(Slider) EXTENDS(Label)
+	METHOD(Slider, resizeNotify, void(entity, vector, vector, vector, vector))
+	METHOD(Slider, configureSliderVisuals, void(entity, float, float, float, string))
+	METHOD(Slider, configureSliderValues, void(entity, float, float, float, float, float, float))
+	METHOD(Slider, draw, void(entity))
+	METHOD(Slider, keyDown, float(entity, float, float, float))
+	METHOD(Slider, mousePress, float(entity, vector))
+	METHOD(Slider, mouseDrag, float(entity, vector))
+	METHOD(Slider, mouseRelease, float(entity, vector))
+	METHOD(Slider, valueToText, string(entity, float))
+	METHOD(Slider, toString, string(entity))
+	METHOD(Slider, setValue, void(entity, float))
+	METHOD(Slider, showNotify, void(entity))
+	ATTRIB(Slider, src, string, string_null)
+	ATTRIB(Slider, focusable, float, 1)
+	ATTRIB(Slider, value, float, 0)
+	ATTRIB(Slider, valueMin, float, 0)
+	ATTRIB(Slider, valueMax, float, 0)
+	ATTRIB(Slider, valueStep, float, 0)
+	ATTRIB(Slider, valueDigits, float, 0)
+	ATTRIB(Slider, valueKeyStep, float, 0)
+	ATTRIB(Slider, valuePageStep, float, 0)
+	ATTRIB(Slider, valueDisplayMultiplier, float, 1.0)
+	ATTRIB(Slider, textSpace, float, 0)
+	ATTRIB(Slider, controlWidth, float, 0)
+	ATTRIB(Slider, pressed, float, 0)
+	ATTRIB(Slider, pressOffset, float, 0)
+	ATTRIB(Slider, previousValue, float, 0)
+	ATTRIB(Slider, tolerance, vector, '0 0 0')
+	ATTRIB(Slider, disabled, float, 0)
+	ATTRIB(Slider, color, vector, '1 1 1')
+	ATTRIB(Slider, color2, vector, '1 1 1')
+	ATTRIB(Slider, colorD, vector, '1 1 1')
+	ATTRIB(Slider, colorC, vector, '1 1 1')
+	ATTRIB(Slider, colorF, vector, '1 1 1')
+	ATTRIB(Slider, disabledAlpha, float, 0.3)
+ENDCLASS(Slider)
+#endif
+
+#ifdef IMPLEMENTATION
+void setValueSlider(entity me, float val)
+{
+	me.value = val;
+}
+string toStringSlider(entity me)
+{
+	return strcat(ftos(me.value), " (", me.valueToText(me, me.value), ")");
+}
+void resizeNotifySlider(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
+{
+	resizeNotifyLabel(me, relOrigin, relSize, absOrigin, absSize);
+	me.controlWidth = absSize_y / absSize_x;
+}
+string valueToTextSlider(entity me, float val)
+{
+	if(val < me.valueMin) return "";
+	if(val > me.valueMax) return "";
+	return ftos_decimals(val * me.valueDisplayMultiplier, me.valueDigits);
+}
+void configureSliderVisualsSlider(entity me, float sz, float theAlign, float theTextSpace, string gfx)
+{
+	configureLabelLabel(me, string_null, sz, theAlign);
+	me.textSpace = theTextSpace;
+	me.keepspaceLeft = (theTextSpace == 0) ? 0 : (1 - theTextSpace);
+	me.src = gfx;
+}
+void configureSliderValuesSlider(entity me, float theValueMin, float theValue, float theValueMax, float theValueStep, float theValueKeyStep, float theValuePageStep)
+{
+	me.value = theValue;
+	me.valueStep = theValueStep;
+	me.valueMin = theValueMin;
+	me.valueMax = theValueMax;
+	me.valueKeyStep = theValueKeyStep;
+	me.valuePageStep = theValuePageStep;
+	me.valueDigits = 3;
+	if(fabs(floor(me.valueStep * 100 + 0.5) - (me.valueStep * 100)) < 0.01) // about a whole number of 100ths
+		me.valueDigits = 2;
+	if(fabs(floor(me.valueStep * 10 + 0.5) - (me.valueStep * 10)) < 0.01) // about a whole number of 10ths
+		me.valueDigits = 1;
+	if(fabs(floor(me.valueStep * 1 + 0.5) - (me.valueStep * 1)) < 0.01) // about a whole number
+		me.valueDigits = 0;
+}
+float keyDownSlider(entity me, float key, float ascii, float shift)
+{
+	float inRange;
+	if(me.disabled)
+		return 0;
+	inRange = (me.value == median(me.valueMin, me.value, me.valueMax));
+	if(key == K_LEFTARROW)
+	{
+		if(inRange)
+			me.setValue(me, median(me.valueMin, me.value - me.valueKeyStep, me.valueMax));
+		else
+			me.setValue(me, me.valueMax);
+		return 1;
+	}
+	if(key == K_RIGHTARROW)
+	{
+		if(inRange)
+			me.setValue(me, median(me.valueMin, me.value + me.valueKeyStep, me.valueMax));
+		else
+			me.setValue(me, me.valueMin);
+		return 1;
+	}
+	if(key == K_PGUP)
+	{
+		if(inRange)
+			me.setValue(me, median(me.valueMin, me.value - me.valuePageStep, me.valueMax));
+		else
+			me.setValue(me, me.valueMax);
+		return 1;
+	}
+	if(key == K_PGDN)
+	{
+		if(inRange)
+			me.setValue(me, median(me.valueMin, me.value + me.valuePageStep, me.valueMax));
+		else
+			me.setValue(me, me.valueMin);
+		return 1;
+	}
+	if(key == K_HOME)
+	{
+		me.setValue(me, me.valueMin);
+		return 1;
+	}
+	if(key == K_END)
+	{
+		me.setValue(me, me.valueMax);
+		return 1;
+	}
+	// TODO more keys
+	return 0;
+}
+float mouseDragSlider(entity me, vector pos)
+{
+	float hit;
+	float v;
+	if(me.disabled)
+		return 0;
+	if(me.pressed)
+	{
+		hit = 1;
+		if(pos_x < 0 - me.tolerance_x) hit = 0;
+		if(pos_y < 0 - me.tolerance_y) hit = 0;
+		if(pos_x >= 1 - me.textSpace + me.tolerance_x) hit = 0;
+		if(pos_y >= 1 + me.tolerance_y) hit = 0;
+		if(hit)
+		{
+			v = median(0, (pos_x - me.pressOffset - 0.5 * me.controlWidth) / (1 - me.textSpace - me.controlWidth), 1) * (me.valueMax - me.valueMin) + me.valueMin;
+			if(me.valueStep)
+				v = floor(0.5 + v / me.valueStep) * me.valueStep;
+			me.setValue(me, v);
+		}
+		else
+			me.setValue(me, me.previousValue);
+	}
+	return 1;
+}
+float mousePressSlider(entity me, vector pos)
+{
+	float controlCenter;
+	if(me.disabled)
+		return 0;
+	if(pos_x < 0) return 0;
+	if(pos_y < 0) return 0;
+	if(pos_x >= 1 - me.textSpace) return 0;
+	if(pos_y >= 1) return 0;
+	controlCenter = (me.value - me.valueMin) / (me.valueMax - me.valueMin) * (1 - me.textSpace - me.controlWidth) + 0.5 * me.controlWidth;
+	if(fabs(pos_x - controlCenter) <= 0.5 * me.controlWidth)
+	{
+		me.pressed = 1;
+		me.pressOffset = pos_x - controlCenter;
+		me.previousValue = me.value;
+		//me.mouseDrag(me, pos);
+	}
+	else
+	{
+		if(pos_x < controlCenter)
+			me.keyDown(me, K_PGUP, 0, 0);
+		else
+			me.keyDown(me, K_PGDN, 0, 0);
+	}
+	return 1;
+}
+float mouseReleaseSlider(entity me, vector pos)
+{
+	me.pressed = 0;
+	if(me.disabled)
+		return 0;
+	return 1;
+}
+void showNotifySlider(entity me)
+{
+	me.focusable = !me.disabled;
+}
+void drawSlider(entity me)
+{
+	float controlLeft;
+	float save;
+	me.focusable = !me.disabled;
+	save = draw_alpha;
+	if(me.disabled)
+		draw_alpha *= me.disabledAlpha;
+	draw_ButtonPicture('0 0 0', strcat(me.src, "_s"), eX * (1 - me.textSpace) + eY, me.color2, 1);
+	if(me.value == median(me.valueMin, me.value, me.valueMax))
+	{
+		controlLeft = (me.value - me.valueMin) / (me.valueMax - me.valueMin) * (1 - me.textSpace - me.controlWidth);
+		if(me.disabled)
+			draw_Picture(eX * controlLeft, strcat(me.src, "_d"), eX * me.controlWidth + eY, me.colorD, 1);
+		else if(me.pressed)
+			draw_Picture(eX * controlLeft, strcat(me.src, "_c"), eX * me.controlWidth + eY, me.colorC, 1);
+		else if(me.focused)
+			draw_Picture(eX * controlLeft, strcat(me.src, "_f"), eX * me.controlWidth + eY, me.colorF, 1);
+		else
+			draw_Picture(eX * controlLeft, strcat(me.src, "_n"), eX * me.controlWidth + eY, me.color, 1);
+	}
+	me.setText(me, me.valueToText(me, me.value));
+	draw_alpha = save;
+	drawLabel(me);
+	me.text = string_null; // TEMPSTRING!
+}
+#endif

Added: trunk/menu/item/tab.c
===================================================================
--- trunk/menu/item/tab.c	                        (rev 0)
+++ trunk/menu/item/tab.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,28 @@
+#ifdef INTERFACE
+CLASS(Tab) EXTENDS(Dialog)
+	ATTRIB(Tab, isTabRoot, float, 0)
+	ATTRIB(Tab, closable, float, 0)
+	ATTRIB(Tab, rootDialog, float, 0)
+	ATTRIB(Tab, title, string, string_null)
+	ATTRIB(Tab, titleFontSize, float, 0) // pixels
+	
+	// still to be customized
+	ATTRIB(Tab, intendedWidth, float, 0)
+	ATTRIB(Tab, rows, float, 3)
+	ATTRIB(Tab, columns, float, 2)
+
+	ATTRIB(Tab, marginTop, float, 0) // pixels
+	ATTRIB(Tab, marginBottom, float, 0) // pixels
+	ATTRIB(Tab, marginLeft, float, 0) // pixels
+	ATTRIB(Tab, marginRight, float, 0) // pixels
+	ATTRIB(Tab, columnSpacing, float, 0) // pixels
+	ATTRIB(Tab, rowSpacing, float, 0) // pixels
+	ATTRIB(Tab, rowHeight, float, 0) // pixels
+	ATTRIB(Tab, titleHeight, float, 0) // pixels
+
+	ATTRIB(Tab, backgroundImage, string, string_null)
+ENDCLASS(Tab)
+#endif
+
+#ifdef IMPLEMENTATION
+#endif

Added: trunk/menu/item/textslider.c
===================================================================
--- trunk/menu/item/textslider.c	                        (rev 0)
+++ trunk/menu/item/textslider.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,60 @@
+// Note:
+//   to use this, you FIRST call configureSliderVisuals, then multiple times addValue, then configureTextSlider
+#ifdef INTERFACE
+CLASS(TextSlider) EXTENDS(Slider)
+	METHOD(TextSlider, valueToText, string(entity, float))
+	METHOD(TextSlider, valueToIdentifier, string(entity, float))
+	METHOD(TextSlider, setValueFromIdentifier, void(entity, string))
+	METHOD(TextSlider, getIdentifier, string(entity))
+	METHOD(TextSlider, addValue, void(entity, string, string))
+	METHOD(TextSlider, configureTextSliderValues, void(entity, string))
+	ATTRIBARRAY(TextSlider, valueStrings, string, 16)
+	ATTRIBARRAY(TextSlider, valueIdentifiers, string, 16)
+	ATTRIB(TextSlider, nValues, float, 0)
+ENDCLASS(TextSlider)
+#endif
+
+#ifdef IMPLEMENTATION
+string valueToIdentifierTextSlider(entity me, float val)
+{
+	if(val >= me.nValues)
+		return "custom";
+	if(val < 0)
+		return "custom";
+	return me.(valueIdentifiers[val]);
+}
+string valueToTextTextSlider(entity me, float val)
+{
+	if(val >= me.nValues)
+		return "custom";
+	if(val < 0)
+		return "custom";
+	return me.(valueStrings[val]);
+}
+void setValueFromIdentifierTextSlider(entity me, string id)
+{
+	float i;
+	for(i = 0; i < me.nValues; ++i)
+		if(me.valueToIdentifier(me, i) == id)
+		{
+			me.value = i;
+			return;
+		}
+	me.value = -1;
+}
+string getIdentifierTextSlider(entity me)
+{
+	return me.valueToIdentifier(me, me.value);
+}
+void addValueTextSlider(entity me, string theString, string theIdentifier)
+{
+	me.(valueStrings[me.nValues]) = theString;
+	me.(valueIdentifiers[me.nValues]) = theIdentifier;
+	me.nValues += 1;
+}
+void configureTextSliderValuesTextSlider(entity me, string theDefault)
+{
+	me.configureSliderValues(me, 0, 0, me.nValues - 1, 1, 1, 1);
+	me.setValueFromIdentifier(me, theDefault);
+}
+#endif

Added: trunk/menu/item.c
===================================================================
--- trunk/menu/item.c	                        (rev 0)
+++ trunk/menu/item.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,89 @@
+#ifdef INTERFACE
+CLASS(Item) EXTENDS(Object)
+	METHOD(Item, draw, void(entity))
+	METHOD(Item, keyDown, float(entity, float, float, float))
+	METHOD(Item, keyUp, float(entity, float, float, float))
+	METHOD(Item, mouseMove, float(entity, vector))
+	METHOD(Item, mousePress, float(entity, vector))
+	METHOD(Item, mouseDrag, float(entity, vector))
+	METHOD(Item, mouseRelease, float(entity, vector))
+	METHOD(Item, focusEnter, void(entity))
+	METHOD(Item, focusLeave, void(entity))
+	METHOD(Item, resizeNotify, void(entity, vector, vector, vector, vector))
+	METHOD(Item, relinquishFocus, void(entity))
+	METHOD(Item, showNotify, void(entity))
+	METHOD(Item, hideNotify, void(entity))
+	METHOD(Item, toString, string(entity))
+	ATTRIB(Item, focused, float, 0)
+	ATTRIB(Item, focusable, float, 0)
+	ATTRIB(Item, parent, entity, NULL)
+ENDCLASS(Item)
+#endif
+
+#ifdef IMPLEMENTATION
+void relinquishFocusItem(entity me)
+{
+	if(me.parent)
+		if(me.parent.instanceOfContainer)
+			me.parent.setFocus(me.parent, NULL);
+}
+
+void resizeNotifyItem(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
+{
+}
+
+void drawItem(entity me)
+{
+}
+
+void showNotifyItem(entity me)
+{
+}
+
+void hideNotifyItem(entity me)
+{
+}
+
+float keyDownItem(entity me, float scan, float ascii, float shift)
+{
+	return 0; // unhandled
+}
+
+float keyUpItem(entity me, float scan, float ascii, float shift)
+{
+	return 0; // unhandled
+}
+
+float mouseMoveItem(entity me, vector pos)
+{
+	return 0; // unhandled
+}
+
+float mousePressItem(entity me, vector pos)
+{
+	return 0; // unhandled
+}
+
+float mouseDragItem(entity me, vector pos)
+{
+	return 0; // unhandled
+}
+
+float mouseReleaseItem(entity me, vector pos)
+{
+	return 0; // unhandled
+}
+
+void focusEnterItem(entity me)
+{
+}
+
+void focusLeaveItem(entity me)
+{
+}
+
+string toStringItem(entity me)
+{
+	return string_null;
+}
+#endif

Added: trunk/menu/mbuiltin.qh
===================================================================
--- trunk/menu/mbuiltin.qh	                        (rev 0)
+++ trunk/menu/mbuiltin.qh	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,302 @@
+//////////////////////////////////////////////////
+// common cmd
+//////////////////////////////////////////////////
+// AK FIXME: Create perhaps a special builtin file for the common cmds
+
+//#define PROFILESTRZONE
+//#define FIXEDFOPEN
+
+void 	checkextension(string ext) = #1;
+
+// error cmds
+void 	error(string err,...) 	= #2;
+void 	objerror(string err,...) 	= #3;
+
+// print
+
+void 	print(string text,...) 	= #4;
+void 	bprint(string text,...)	= #5;
+void	sprint(float clientnum, string text,...) = #6;
+void 	centerprint(string text,...) = #7;
+
+// vector stuff
+
+vector	normalize(vector v) 	= #8;
+float 	vlen(vector v)			= #9;
+float  	vectoyaw(vector v)		= #10;
+vector 	vectoangles(vector v)	= #11;
+
+float	random(void)  = #12;
+
+void	cmd(string command, ...) = #13;
+
+// cvar cmds
+
+float 	cvar(string name) = #14;
+const string cvar_string(string name) = #71;
+const string cvar_defstring(string name) = #89;
+void	cvar_set(string name, string value) = #15;
+
+//void	dprint(string text,...) = #16;
+
+// conversion functions
+
+string	ftos(float f)  = #17;
+float	fabs(float f)	= #18;
+string	vtos(vector v)  = #19;
+string	etos(entity e) = #20;
+
+float	stof(string val,...)  = #21;
+
+entity	spawn(void) = #22;
+void	remove(entity e) = #23;
+
+entity	findstring(entity start, .string _field, string match)  = #24;
+entity	findfloat(entity start, .float _field, float match) = #25;
+entity	findentity(entity start, .entity _field, entity match) = #25;
+
+entity	findchainstring(.string _field, string match) = #26;
+entity	findchainfloat(.float _field, float match) = #27;
+entity	findchainentity(.entity _field, entity match) = #27;
+
+entity	findflags(entity start, .float field, float match) = #87;
+entity	findchainflags(.float field, float match) = #88;
+
+string	precache_file(string file) = #28;
+string	precache_sound(string sample) = #29;
+
+void	crash(void)	= #72;
+void	coredump(void) = #30;
+void	stackdump(void) = #73;
+void	traceon(void) = #31;
+void	traceoff(void) = #32;
+
+void	eprint(entity e)  = #33;
+float	rint(float f) = #34;
+float	floor(float f)  = #35;
+float	ceil(float f)  = #36;
+entity	nextent(entity e)  = #37;
+float	sin(float f)  = #38;
+float	cos(float f)  = #39;
+float	sqrt(float f)  = #40;
+vector	randomvec(void)  = #41;
+
+float	registercvar(string name, string value, float flags)  = #42; // returns 1 if success
+float	min(float f,...)  = #43;
+float	max(float f,...)  = #44;
+float	bound(float min,float value, float max)  = #45;
+float	pow(float a, float b)  = #46;
+void	copyentity(entity src, entity dst)  = #47;
+
+#ifdef FIXEDFOPEN
+float 	_fopen( string filename, float mode ) = #48;
+#else
+float	fopen(string filename, float mode)  = #48;
+#endif
+void	fclose(float fhandle)  = #49;
+string	fgets(float fhandle)  = #50;
+void	fputs(float fhandle, string s)  = #51;
+
+float	strlen(string s)  = #52;
+//string	strcat(string s1,string s2,...)  = #53;
+string	strcat(string s1, ...)  = #53;
+string	substring(string s, float start, float length)  = #54;
+
+vector	stov(string s)  = #55;
+
+#ifdef PROFILESTRZONE
+string	_strzone(string s)  = #56;
+void	_strunzone(string s) = #57;
+
+string( string s ) strzone =
+{
+	return _strzone( s );
+};
+
+void( string s ) strunzone =
+{
+	return _strunzone( s );
+};
+#else
+string	strzone(string s)  = #56;
+void	strunzone(string s) = #57;
+#endif
+
+float	tokenize(string s) = #58;
+float(string s, string separator1, ...) tokenizebyseparator = #479;
+string	argv(float n)  = #59;
+
+float	isserver(void)  = #60;
+float	clientcount(void)  = #61;
+float	clientstate(void)  = #62;
+void	clientcommand(float client, string s)  = #63;
+void	changelevel(string map)  = #64;
+void	localsound(string sample)  = #65;
+vector	getmousepos(void)  	= #66;
+float	gettime(void)		= #67;
+void 	loadfromdata(string data) = #68;
+void	loadfromfile(string file) = #69;
+
+float	mod(float val, float m) = #70;
+
+float	search_begin(string pattern, float caseinsensitive, float quiet) = #74;
+void	search_end(float handle) = #75;
+float	search_getsize(float handle) = #76;
+string	search_getfilename(float handle, float num) = #77;
+
+string	chr(float ascii) = #78;
+
+float 	etof(entity ent) = #79;
+entity 	ftoe(float num)	 = #80;
+
+float   validstring(string str) = #81;
+
+float 	altstr_count(string str) = #82;
+string  altstr_prepare(string str) = #83;
+string  altstr_get(string str, float num) = #84;
+string  altstr_set(string str, float num, string set) = #85;
+string 	altstr_ins(string str, float num, string set) = #86;
+
+/////////////////////////////////////////////////
+// Write* Functions
+/////////////////////////////////////////////////
+void	WriteByte(float data, float dest, float desto)	= #401;
+void	WriteChar(float data, float dest, float desto)	= #402;
+void	WriteShort(float data, float dest, float desto)	= #403;
+void	WriteLong(float data, float dest, float desto)	= #404;
+void	WriteAngle(float data, float dest, float desto)	= #405;
+void	WriteCoord(float data, float dest, float desto)	= #406;
+void	WriteString(string data, float dest, float desto)= #407;
+void	WriteEntity(entity data, float dest, float desto) = #408;
+
+//////////////////////////////////////////////////
+// Draw funtions
+//////////////////////////////////////////////////
+
+float	iscachedpic(string name)	= #451;
+string	precache_pic(string name)	= #452;
+void	freepic(string name)		= #453;
+
+float	drawcharacter(vector position, float character, vector scale, vector rgb, float alpha, float flag) = #454;
+
+float	drawstring(vector position, string text, vector scale, vector rgb, float alpha, float flag) = #455;
+float   drawcolorcodedstring(vector position, string text, vector scale, float alpha, float flag) = #467;
+float	stringwidth(string text, float handleColors) = #468;
+
+float	drawpic(vector position, string pic, vector size, vector rgb, float alpha, float flag) = #456;
+float	drawsubpic(vector position, vector size, string pic, vector srcPosition, vector srcSize, vector rgb, float alpha, float flag) = #469;
+
+float	drawfill(vector position, vector size, vector rgb, float alpha, float flag) = #457;
+
+void	drawsetcliparea(float x, float y, float width, float height) = #458;
+
+void	drawresetcliparea(void) = #459;
+
+vector  drawgetimagesize(string pic) = #460;
+
+float	cin_open(string file, string name) 	= #461;
+void	cin_close(string name) 			= #462;
+void	cin_setstate(string name, float type) 	= #463;
+float	cin_getstate(string name)		= #464;
+
+////////////////////////////////////////////////
+// Menu functions
+////////////////////////////////////////////////
+
+void	setkeydest(float dest) 	= #601;
+float	getkeydest(void)	= #602;
+
+void	setmousetarget(float trg) = #603;
+float	getmousetarget(void)	  = #604;
+
+float	isfunction(string function_name) = #607;
+void	callfunction(...) = #605;
+void	writetofile(float fhandle, entity ent) = #606;
+vector	getresolution(float number) = #608;
+string	keynumtostring(float keynum) = #609;
+string	findkeysforcommand(string command) = #610;
+
+float	gethostcachevalue(float type) = #611;
+string	gethostcachestring(float type, float hostnr) = #612;
+
+void 	parseentitydata(entity ent, string data) = #613;
+
+float	stringtokeynum(string key) = #614;
+
+void 	resethostcachemasks(void) = #615;
+void 	sethostcachemaskstring(float mask, float fld, string str, float op) = #616;
+void	sethostcachemasknumber(float mask, float fld, float num, float op) = #617;
+void 	resorthostcache(void) = #618;
+void	sethostcachesort(float fld, float descending) = #619;
+void	refreshhostcache(void) = #620;
+float	gethostcachenumber(float fld, float hostnr) = #621;
+float	gethostcacheindexforkey(string key) = #622;
+void	addwantedhostcachekey(string key) = #623;
+string	getextresponse(void) = #624;
+
+// AK the builtin numbers may change - the code might be removed again
+float gecko_create( string name ) = #487;
+void gecko_destroy( string name ) = #488;
+void gecko_navigate( string name, string URI ) = #489;
+float gecko_keyevent( string name, float key, float eventtype ) = #490;
+void gecko_mousemove( string name, float x, float y ) = #491;
+void gecko_resize( string name, float w, float h ) = #492;
+vector gecko_get_texture_extent( string name ) = #493;
+
+//FTE_STRINGS
+//idea: many
+//darkplaces implementation: KrimZon
+//description:
+//various string manipulation functions
+float(string str, string sub, float startpos) strstrofs = #221;
+float(string str, float ofs) str2chr = #222;
+string(float c, ...) chr2str = #223;
+string(float ccase, float calpha, float cnum, string s, ...) strconv = #224;
+string(float chars, string s, ...) strpad = #225;
+string(string info, string key, string value, ...) infoadd = #226;
+string(string info, string key) infoget = #227;
+float(string s1, string s2, float len) strncmp = #228;
+float(string s1, string s2) strcasecmp = #229;
+float(string s1, string s2, float len) strncasecmp = #230;
+
+//DP_QC_STRINGBUFFERS
+//idea: ??
+//darkplaces implementation: LordHavoc
+//functions to manage string buffer objects - that is, arbitrary length string arrays that are handled by the engine
+float() buf_create = #440;
+void(float bufhandle) buf_del = #441;
+float(float bufhandle) buf_getsize = #442;
+void(float bufhandle_from, float bufhandle_to) buf_copy = #443;
+void(float bufhandle, float sortpower, float backward) buf_sort = #444;
+string(float bufhandle, string glue) buf_implode = #445;
+string(float bufhandle, float string_index) bufstr_get = #446;
+void(float bufhandle, float string_index, string str) bufstr_set = #447;
+float(float bufhandle, string str, float order) bufstr_add = #448;
+void(float bufhandle, float string_index) bufstr_free = #449;
+
+//DP_QC_CRC16
+//idea: div0
+//darkplaces implementation: div0
+//Some hash function to build hash tables with. This has to be be the CRC-16-CCITT that is also required for the QuakeWorld download protocol.
+//When caseinsensitive is set, the CRC is calculated of the lower cased string.
+float(float caseinsensitive, string s, ...) crc16 = #494;
+
+#ifdef FIXEDFOPEN
+float 	fopen( string filename, float mode ) =
+{
+	local float handle;
+	if( mode == FILE_READ ) {
+		return _fopen( filename, mode );
+	}
+
+	// check for data/
+	filename = strzone( filename );
+	if( substring( filename, 0, 5 ) != "data/" ) {
+		print( "menu: fopen: all output must go into data/!\n" );
+		return -1;
+	}
+	handle = _fopen( substring( filename, 5, 10000 ), mode );
+	strunzone( filename );
+	return handle;
+};
+#endif

Added: trunk/menu/menu.qc
===================================================================
--- trunk/menu/menu.qc	                        (rev 0)
+++ trunk/menu/menu.qc	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,398 @@
+///////////////////////////////////////////////
+// Menu Source File
+///////////////////////
+// This file belongs to dpmod/darkplaces
+// AK contains all menu functions (especially the required ones)
+///////////////////////////////////////////////
+
+float mouseButtonsPressed;
+vector menuMousePos;
+float menuShiftState;
+float menuPrevTime;
+float menuAlpha;
+float menuLogoAlpha;
+float prevMenuAlpha;
+float menuInitialized;
+float menuNotTheFirstFrame;
+
+void SUB_Null() { };
+
+void() m_init =
+{
+	dprint_load();
+}
+
+void UpdateConWidthHeight()
+{
+	float conwidth_s, conheight_s;
+	conwidth_s = conwidth;
+	conheight_s = conheight;
+	conwidth = cvar("vid_conwidth");
+	conheight = cvar("vid_conheight");
+	if(conwidth < 800)
+	{
+		conheight *= 800 / conwidth;
+		conwidth = 800;
+	}
+	if(conheight < 600)
+	{
+		conwidth *= 600 / conheight;
+		conheight = 600;
+	}
+	if(main)
+	{
+		if(conwidth_s != conwidth || conheight_s != conheight)
+		{
+			draw_reset();
+			main.resizeNotify(main, '0 0 0', eX * conwidth + eY * conheight, '0 0 0', eX * conwidth + eY * conheight);
+		}
+	}
+}
+
+void() m_init_delayed =
+{
+	float fh, glob, n, i;
+	string s;
+
+	dprint_load();
+
+	menuInitialized = 0;
+	if(!preMenuInit())
+		return;
+	menuInitialized = 1;
+	GameCommand_Init();
+
+	fh = -1;
+	if(cvar_string("menu_skin") != "")
+	{
+		draw_currentSkin = strcat("gfx/menu/", cvar_string("menu_skin"));
+		fh = fopen(strcat(draw_currentSkin, "/skinvalues.txt"), FILE_READ);
+	}
+	if(fh < 0)
+	{
+		draw_currentSkin = "gfx/menu/default";
+		fh = fopen(strcat(draw_currentSkin, "/skinvalues.txt"), FILE_READ);
+	}
+	draw_currentSkin = strzone(draw_currentSkin);
+	while((s = fgets(fh)))
+		if(tokenize(s) == 2)
+			Skin_ApplySetting(argv(0), argv(1));
+	fclose(fh);
+
+	glob = search_begin(strcat(draw_currentSkin, "/*.tga"), TRUE, TRUE);
+	if(glob >= 0)
+	{
+		n = search_getsize(glob);
+		for(i = 0; i < n; ++i)
+			precache_pic(search_getfilename(glob, i));
+		search_end(glob);
+	}
+
+	draw_setMousePointer(SKINGFX_CURSOR, SKINSIZE_CURSOR, SKINOFFSET_CURSOR);
+
+	conwidth = conheight = -1;
+	draw_reset();
+	UpdateConWidthHeight();
+	main = spawnMainWindow(); main.configureMainWindow(main);
+	main.resizeNotify(main, '0 0 0', eX * conwidth + eY * conheight, '0 0 0', eX * conwidth + eY * conheight);
+	main.focused = 1;
+	menuShiftState = 0;
+	menuMousePos = '0.5 0.5 0';
+
+	if(Menu_Active)
+		m_display(); // delayed menu display
+};
+
+void(float key, float ascii) m_keyup =
+{
+	if(!menuInitialized)
+		return;
+	if(!Menu_Active)
+		return;
+	draw_reset();
+	main.keyUp(main, key, ascii, menuShiftState);
+	if(key >= K_MOUSE1 && key <= K_MOUSE3)
+	{
+		--mouseButtonsPressed;
+		if(!mouseButtonsPressed)
+			main.mouseRelease(main, menuMousePos);
+		if(mouseButtonsPressed < 0)
+		{
+			mouseButtonsPressed = 0;
+			print("Warning: released an already released button\n");
+		}
+	}
+	if(key == K_ALT) menuShiftState -= (menuShiftState & S_ALT);
+	if(key == K_CTRL) menuShiftState -= (menuShiftState & S_CTRL);
+	if(key == K_SHIFT) menuShiftState -= (menuShiftState & S_SHIFT);
+};
+
+void(float key, float ascii) m_keydown =
+{
+	if(!menuInitialized)
+		return;
+	if(!Menu_Active)
+		return;
+	if(keyGrabber)
+	{
+		entity e;
+		e = keyGrabber;
+		keyGrabber = NULL;
+		e.keyGrabbed(e, key, ascii);
+	}
+	else
+	{
+		draw_reset();
+		if(!main.keyDown(main, key, ascii, menuShiftState))
+			if(key == K_ESCAPE)
+				if(gamestatus & (GAME_ISSERVER | GAME_CONNECTED)) // don't back out to console only
+					m_hide(); // disable menu on unhandled ESC
+		if(key >= K_MOUSE1 && key <= K_MOUSE3)
+			if(!mouseButtonsPressed)
+				main.mousePress(main, menuMousePos);
+	}
+	if(key >= K_MOUSE1 && key <= K_MOUSE3)
+	{
+		++mouseButtonsPressed;
+		if(mouseButtonsPressed > 10)
+		{
+			mouseButtonsPressed = 10;
+			print("Warning: pressed an already pressed button\n");
+		}
+	}
+	if(key == K_ALT) menuShiftState |= S_ALT;
+	if(key == K_CTRL) menuShiftState |= S_CTRL;
+	if(key == K_SHIFT) menuShiftState |= S_SHIFT;
+};
+
+void(string img, float a) drawBackground =
+{
+	vector sz;
+	vector isz;
+	sz = draw_PictureSize(img);
+	// keep aspect of image
+	if(sz_x * draw_scale_y >= sz_y * draw_scale_x)
+	{
+		// that is, sz_x/sz_y >= draw_scale_x/draw_scale_y
+		// match up the height
+		isz_y = 1;
+		isz_x = isz_y * (sz_x / sz_y) * (draw_scale_y / draw_scale_x);
+	}
+	else
+	{
+		// that is, sz_x/sz_y <= draw_scale_x/draw_scale_y
+		// match up the width
+		isz_x = 1;
+		isz_y = isz_x * (sz_y / sz_x) * (draw_scale_x / draw_scale_y);
+	}
+	draw_Picture('0.5 0.5 0' - 0.5 * isz, img, isz, '1 1 1', a);
+}
+
+void() m_draw =
+{
+	float t;
+	float realFrametime;
+
+	if(main)
+		UpdateConWidthHeight();
+
+	if(!menuInitialized)
+	{
+		// TODO draw an info image about this situation
+		m_init_delayed();
+		return;
+	}
+	if(!menuNotTheFirstFrame)
+	{
+		menuNotTheFirstFrame = 1;
+		if(Menu_Active)
+		if(!cvar("menu_video_played"))
+		{
+			localcmd("set menu_video_played 1; cd loop 1; play sound/announcer/male/welcome.ogg\n");
+			menuLogoAlpha = -0.8; // no idea why, but when I start this at zero, it jumps instead of fading
+		}
+	}
+
+	t = gettime();
+	realFrametime = frametime = min(0.2, t - menuPrevTime);
+	menuPrevTime = t;
+	time += frametime;
+
+	t = cvar("menu_slowmo");
+	if(t)
+	{
+		frametime *= t;
+		realFrametime *= t;
+	}
+
+	if(Menu_Active)
+	{
+		if(getmousetarget() == MT_MENU && (getkeydest() == KEY_MENU || getkeydest() == KEY_MENU_GRABBED))
+			setkeydest(keyGrabber ? KEY_MENU_GRABBED : KEY_MENU);
+		else
+			m_hide();
+	}
+
+	if(cvar("cl_capturevideo"))
+		frametime = cvar("menu_slowmo") / cvar("cl_capturevideo_fps"); // make capturevideo work smoothly
+
+	dprint_load();
+	gamestatus = 0;
+	if(isserver())
+		gamestatus = gamestatus | GAME_ISSERVER;
+	if(clientstate() == CS_CONNECTED)
+		gamestatus = gamestatus | GAME_CONNECTED;
+	if(cvar("developer"))
+		gamestatus = gamestatus | GAME_DEVELOPER;
+
+	prevMenuAlpha = menuAlpha;
+	if(Menu_Active)
+	{
+		if(menuAlpha == 0 && menuLogoAlpha < 2)
+		{
+			menuLogoAlpha = menuLogoAlpha + frametime * 2;
+		}
+		else
+		{
+			menuAlpha = min(1, menuAlpha + frametime * 5);
+			menuLogoAlpha = 2;
+		}
+	}
+	else
+	{
+		menuAlpha = max(0, menuAlpha - frametime * 5);
+		menuLogoAlpha = 2;
+	}
+
+	draw_reset();
+
+	if(!(gamestatus & (GAME_CONNECTED | GAME_ISSERVER)))
+	{
+		if(menuLogoAlpha > 0)
+		{
+			drawBackground(SKINGFX_BACKGROUND, bound(0, menuLogoAlpha, 1));
+			if(menuAlpha <= 0 && SKINALPHA_CURSOR_INTRO > 0)
+			{
+				draw_alpha = SKINALPHA_CURSOR_INTRO * bound(0, menuLogoAlpha, 1);
+				draw_drawMousePointer(menuMousePos);
+				draw_alpha = 1;
+			}
+		}
+	}
+	else if(SKINALPHA_BACKGROUND_INGAME)
+	{
+		if(menuAlpha > 0)
+			drawBackground(SKINGFX_BACKGROUND_INGAME, menuAlpha * SKINALPHA_BACKGROUND_INGAME);
+	}
+
+	draw_reset();
+	preMenuDraw();
+	draw_reset();
+
+	if(menuAlpha <= 0)
+	{
+		if(prevMenuAlpha > 0)
+			main.initializeDialog(main, main.firstChild);
+		draw_reset();
+		postMenuDraw();
+		return;
+	}
+
+	draw_alpha *= menuAlpha;
+
+	if(frametime > 0)
+	{
+		vector dMouse;
+		dMouse = getmousepos() * (frametime / realFrametime); // for capturevideo
+		if(dMouse != '0 0 0')
+		{
+			dMouse = globalToBoxSize(dMouse, draw_scale);
+			menuMousePos += dMouse * 1; // TODO use a cvar here
+			menuMousePos_x = bound(0, menuMousePos_x, 1);
+			menuMousePos_y = bound(0, menuMousePos_y, 1);
+			if(mouseButtonsPressed)
+				main.mouseDrag(main, menuMousePos);
+			else
+				main.mouseMove(main, menuMousePos);
+		}
+	}
+	main.draw(main);
+	draw_alpha = max(draw_alpha, SKINALPHA_CURSOR_INTRO * bound(0, menuLogoAlpha, 1));
+
+	draw_drawMousePointer(menuMousePos);
+
+	draw_reset();
+	postMenuDraw();
+
+	frametime = 0;
+};
+
+void() m_display =
+{
+	Menu_Active = true;
+	setkeydest(KEY_MENU);
+	setmousetarget(MT_MENU);
+
+	if(!menuInitialized)
+		return;
+
+	if(mouseButtonsPressed)
+		main.mouseRelease(main, menuMousePos);
+	mouseButtonsPressed = 0;
+
+	main.focusEnter(main);
+	main.showNotify(main);
+};
+
+void() m_hide =
+{
+	Menu_Active = false;
+	setkeydest(KEY_GAME);
+	setmousetarget(MT_CLIENT);
+
+	if(!menuInitialized)
+		return;
+
+	main.focusLeave(main);
+	main.hideNotify(main);
+};
+
+void() m_toggle =
+{
+	if(Menu_Active)
+		m_hide();
+	else
+		m_display();
+};
+
+void() m_shutdown =
+{
+	m_hide();
+};
+
+void(string itemname) m_goto =
+{
+	entity e;
+	if(!menuInitialized)
+		return;
+	if(itemname == "") // this can be called by GameCommand
+	{
+		if(gamestatus & (GAME_ISSERVER | GAME_CONNECTED))
+			m_hide();
+		else
+		{
+			main.initializeDialog(main, main.firstChild);
+			m_display();
+		}
+	}
+	else
+	{
+		e = findstring(NULL, name, itemname);
+		if(e && e.parent == main)
+		{
+			m_hide();
+			main.initializeDialog(main, e);
+			m_display();
+		}
+	}
+}

Added: trunk/menu/menu.qh
===================================================================
--- trunk/menu/menu.qh	                        (rev 0)
+++ trunk/menu/menu.qh	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,45 @@
+#define localcmd cmd
+
+#define NULL (null_entity)
+
+#define MAPINFO
+
+// constants
+
+const string string_null;
+const vector eX = '1 0 0';
+const vector eY = '0 1 0';
+const vector eZ = '0 0 1';
+
+const float GAME_ISSERVER 	= 1;
+const float GAME_CONNECTED	= 2;
+const float GAME_DEVELOPER	= 4;
+
+// prototypes
+
+float Menu_Active;
+float gamestatus;
+
+const float S_SHIFT = 1;
+const float S_CTRL = 2;
+const float S_ALT = 4;
+
+float frametime;
+float time;
+
+entity main;
+void m_hide();
+void m_display();
+void m_goto(string name);
+.string name;
+
+entity keyGrabber;
+.void(entity me, float key, float ascii) keyGrabbed;
+
+float conwidth, conheight; // "virtual" conwidth/height values for other stuff to assume for scaling
+
+void SUB_Null();
+
+float preMenuInit(); // you have to define this for pre-menu initialization. Return 0 if initialization needs to be retried a frame later, 1 if it succeeded.
+void preMenuDraw(); // this is run before the menu is drawn. You may put some stuff there that has to be done every frame.
+void postMenuDraw(); // this is run just after the menu is drawn (or not). Useful to draw something over everything else.

Added: trunk/menu/msys.qh
===================================================================
--- trunk/menu/msys.qh	                        (rev 0)
+++ trunk/menu/msys.qh	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,303 @@
+#pragma flag off fastarrays // make dp behave with new fteqcc versions. remove when dp bug with fteqcc fastarrays is fixed
+
+//////////////////////////////////////////////////////////
+// sys globals
+
+entity self;
+
+/////////////////////////////////////////////////////////
+void		end_sys_globals;
+/////////////////////////////////////////////////////////
+// sys fields
+
+/////////////////////////////////////////////////////////
+void		end_sys_fields;
+/////////////////////////////////////////////////////////
+// sys functions
+
+void() m_init;
+void(float keynr, float ascii) m_keydown;
+void() m_draw;
+void() m_display; 	// old NG Menu
+void() m_toggle;
+void() m_hide;		// old NG Menu
+void() m_shutdown;
+
+/////////////////////////////////////////////////////////
+// sys constants
+///////////////////////////
+// key constants
+
+//
+// these are the key numbers that should be passed to Key_Event
+//
+float K_TAB			=	9;
+float K_ENTER		=	13;
+float K_ESCAPE		=	27;
+float K_SPACE		=	32;
+
+// normal keys should be passed as lowercased ascii
+
+float K_BACKSPACE	=	127;
+float K_UPARROW		=	128;
+float K_DOWNARROW	=	129;
+float K_LEFTARROW	=	130;
+float K_RIGHTARROW	=	131;
+
+float K_ALT		=	132;
+float K_CTRL	=	133;
+float K_SHIFT	=	134;
+float K_F1		=	135;
+float K_F2		=	136;
+float K_F3		=	137;
+float K_F4		=	138;
+float K_F5		=	139;
+float K_F6		=	140;
+float K_F7		=	141;
+float K_F8		=	142;
+float K_F9		=	143;
+float K_F10		=	144;
+float K_F11		=	145;
+float K_F12		=	146;
+float K_INS		=	147;
+float K_DEL		=	148;
+float K_PGDN	=	149;
+float K_PGUP	=	150;
+float K_HOME	=	151;
+float K_END		=	152;
+
+float K_KP_HOME			=	160;
+float K_KP_UPARROW		=	161;
+float K_KP_PGUP			=	162;
+float K_KP_LEFTARROW	=	163;
+float K_KP_5			=	164;
+float K_KP_RIGHTARROW	=	165;
+float K_KP_END			=	166;
+float K_KP_DOWNARROW	=	167;
+float K_KP_PGDN			=	168;
+float K_KP_ENTER		=	169;
+float K_KP_INS   		=	170;
+float K_KP_DEL			=	171;
+float K_KP_SLASH		=	172;
+float K_KP_MINUS		=	173;
+float K_KP_PLUS			=	174;
+
+float K_PAUSE		=	255;
+
+//
+// joystick buttons
+//
+float K_JOY1		=	768;
+float K_JOY2		=	769;
+float K_JOY3		=	770;
+float K_JOY4		=	771;
+
+//
+// aux keys are for multi-buttoned joysticks to generate so they can use
+// the normal binding process
+//
+float K_AUX1		=	772;
+float K_AUX2		=	773;
+float K_AUX3		=	774;
+float K_AUX4		=	775;
+float K_AUX5		=	776;
+float K_AUX6		=	777;
+float K_AUX7		=	778;
+float K_AUX8		=	779;
+float K_AUX9		=	780;
+float K_AUX10		=	781;
+float K_AUX11		=	782;
+float K_AUX12		=	783;
+float K_AUX13		=	784;
+float K_AUX14		=	785;
+float K_AUX15		=	786;
+float K_AUX16		=	787;
+float K_AUX17		=	788;
+float K_AUX18		=	789;
+float K_AUX19		=	790;
+float K_AUX20		=	791;
+float K_AUX21		=	792;
+float K_AUX22		=	793;
+float K_AUX23		=	794;
+float K_AUX24		=	795;
+float K_AUX25		=	796;
+float K_AUX26		=	797;
+float K_AUX27		=	798;
+float K_AUX28		=	799;
+float K_AUX29		=	800;
+float K_AUX30		=	801;
+float K_AUX31		=	802;
+float K_AUX32		=	803;
+
+//
+// mouse buttons generate virtual keys
+//
+float K_MOUSE1		=	512;
+float K_MOUSE2		=	513;
+float K_MOUSE3		=	514;
+float K_MWHEELUP	=	515;
+float K_MWHEELDOWN	=	516;
+float K_MOUSE4		=	517;
+float K_MOUSE5		=	518;
+float K_MOUSE6		=	519;
+float K_MOUSE7		=	520;
+float K_MOUSE8		=	521;
+float K_MOUSE9		=	522;
+float K_MOUSE10		=	523;
+float K_MOUSE11		=	524;
+float K_MOUSE12		=	525;
+float K_MOUSE13		=	526;
+float K_MOUSE14		=	527;
+float K_MOUSE15		=	528;
+float K_MOUSE16		=	529;
+
+///////////////////////////
+// key dest constants
+
+float KEY_UNKNOWN	= 	-1;
+float KEY_GAME 		=	0;
+float KEY_MENU		=	2;
+float KEY_MENU_GRABBED = 3;
+
+///////////////////////////
+// file constants
+
+float FILE_READ = 0;
+float FILE_APPEND = 1;
+float FILE_WRITE = 2;
+
+///////////////////////////
+// logical constants (just for completeness)
+
+float TRUE 	= 1;
+float FALSE = 0;
+
+///////////////////////////
+// boolean constants
+
+float true  = 1;
+float false = 0;
+
+///////////////////////////
+// msg constants
+
+float MSG_BROADCAST		= 0;		// unreliable to all
+float MSG_ONE			= 1;		// reliable to one (msg_entity)
+float MSG_ALL			= 2;		// reliable to all
+float MSG_INIT			= 3;		// write to the init string
+
+/////////////////////////////
+// mouse target constants
+
+float MT_MENU = 1;
+float MT_CLIENT = 2;
+
+/////////////////////////
+// client state constants
+
+float CS_DEDICATED 		= 0;
+float CS_DISCONNECTED 	= 1;
+float CS_CONNECTED		= 2;
+
+///////////////////////////
+// blend flags
+
+float DRAWFLAG_NORMAL		= 0;
+float DRAWFLAG_ADDITIVE 	= 1;
+float DRAWFLAG_MODULATE 	= 2;
+float DRAWFLAG_2XMODULATE   = 3;
+
+
+///////////////////////////
+// cvar constants
+
+float CVAR_SAVE 	= 1;
+float CVAR_NOTIFY 	= 2;
+float CVAR_READONLY	= 4;
+
+///////////////////////////
+// server list constants
+
+float SLIST_HOSTCACHEVIEWCOUNT  = 0;
+float SLIST_HOSTCACHETOTALCOUNT = 1;
+float SLIST_MASTERQUERYCOUNT 	= 2;
+float SLIST_MASTERREPLYCOUNT 	= 3;
+float SLIST_SERVERQUERYCOUNT 	= 4;
+float SLIST_SERVERREPLYCOUNT 	= 5;
+float SLIST_SORTFIELD		= 6;
+float SLIST_SORTDESCENDING	= 7;
+
+float SLIST_LEGACY_LINE1 	= 1024;
+float SLIST_LEGACY_LINE2 	= 1025;
+
+float SLIST_TEST_CONTAINS	= 0;
+float SLIST_TEST_NOTCONTAIN	= 1;
+float SLIST_TEST_LESSEQUAL	= 2;
+float SLIST_TEST_LESS		= 3;
+float SLIST_TEST_EQUAL		= 4;
+float SLIST_TEST_GREATER	= 5;
+float SLIST_TEST_GREATEREQUAL	= 6;
+float SLIST_TEST_NOTEQUAL	= 7;
+
+float SLIST_MASK_AND = 0;
+float SLIST_MASK_OR  = 512;
+
+float NET_CURRENTPROTOCOL = 3;
+
+////////////////////////////////
+// cinematic action constants
+
+float CINE_PLAY 	= 1;
+float CINE_LOOP 	= 2;
+float CINE_PAUSE 	= 3;
+float CINE_FIRSTFRAME 	= 4;
+float CINE_RESETONWAKEUP= 5;
+
+///////////////////////////
+// null entity (actually it is the same like the world entity)
+
+entity null_entity;
+
+///////////////////////////
+// error constants
+
+// file handling
+float ERR_CANNOTOPEN			= -1; // fopen
+float ERR_NOTENOUGHFILEHANDLES 	= -2; // fopen
+float ERR_INVALIDMODE 			= -3; // fopen
+float ERR_BADFILENAME 			= -4; // fopen
+
+// drawing functions
+
+float ERR_NULLSTRING			= -1;
+float ERR_BADDRAWFLAG			= -2;
+float ERR_BADSCALE			= -3;
+//float ERR_BADSIZE			= ERR_BADSCALE;
+float ERR_NOTCACHED			= -4;
+
+float GECKO_BUTTON_DOWN 		= 0;
+float GECKO_BUTTON_UP 			= 1;
+// either use down and up or just press but not all of them!
+float GECKO_BUTTON_PRESS 		= 2;
+// use this for mouse events if needed?
+float GECKO_BUTTON_DOUBLECLICK 	= 3;
+
+/* not supported at the moment
+///////////////////////////
+// os constants
+
+float OS_WINDOWS	= 0;
+float OS_LINUX		= 1;
+float OS_MAC		= 2;
+*/
+
+float drawfont; // set this to one of the following for draw text routines to work with another font
+float FONT_DEFAULT     = 0;
+float FONT_CONSOLE     = 1;
+float FONT_SBAR        = 2;
+float FONT_NOTIFY      = 3;
+float FONT_CHAT        = 4;
+float FONT_CENTERPRINT = 5;
+float FONT_INFOBAR     = 6;
+float FONT_MENU        = 7;
+float FONT_USER        = 8; // add to this the index, like FONT_USER+3 = user3. At least 8 of them are supported.

Added: trunk/menu/nexuiz/button.c
===================================================================
--- trunk/menu/nexuiz/button.c	                        (rev 0)
+++ trunk/menu/nexuiz/button.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,40 @@
+#ifdef INTERFACE
+CLASS(NexuizButton) EXTENDS(Button)
+	METHOD(NexuizButton, configureNexuizButton, void(entity, string, vector))
+	ATTRIB(NexuizButton, fontSize, float, SKINFONTSIZE_NORMAL)
+	ATTRIB(NexuizButton, image, string, SKINGFX_BUTTON)
+	ATTRIB(NexuizButton, grayImage, string, SKINGFX_BUTTON_GRAY)
+	ATTRIB(NexuizButton, color, vector, SKINCOLOR_BUTTON_N)
+	ATTRIB(NexuizButton, colorC, vector, SKINCOLOR_BUTTON_C)
+	ATTRIB(NexuizButton, colorF, vector, SKINCOLOR_BUTTON_F)
+	ATTRIB(NexuizButton, colorD, vector, SKINCOLOR_BUTTON_D)
+	ATTRIB(NexuizButton, alpha, float, SKINALPHA_TEXT)
+	ATTRIB(NexuizButton, disabledAlpha, float, SKINALPHA_DISABLED)
+ENDCLASS(NexuizButton)
+entity makeNexuizButton(string theText, vector theColor);
+#endif
+
+#ifdef IMPLEMENTATION
+entity makeNexuizButton(string theText, vector theColor)
+{
+	entity me;
+	me = spawnNexuizButton();
+	me.configureNexuizButton(me, theText, theColor);
+	return me;
+}
+
+void configureNexuizButtonNexuizButton(entity me, string theText, vector theColor)
+{
+	if(theColor == '0 0 0')
+	{
+		me.configureButton(me, theText, me.fontSize, me.image);
+	}
+	else
+	{
+		me.configureButton(me, theText, me.fontSize, me.grayImage);
+		me.color = theColor;
+		me.colorC = theColor;
+		me.colorF = theColor;
+	}
+}
+#endif

Added: trunk/menu/nexuiz/campaign.c
===================================================================
--- trunk/menu/nexuiz/campaign.c	                        (rev 0)
+++ trunk/menu/nexuiz/campaign.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,261 @@
+#ifdef INTERFACE
+CLASS(NexuizCampaignList) EXTENDS(NexuizListBox)
+	METHOD(NexuizCampaignList, configureNexuizCampaignList, void(entity))
+	ATTRIB(NexuizCampaignList, rowsPerItem, float, 10)
+	METHOD(NexuizCampaignList, draw, void(entity))
+	METHOD(NexuizCampaignList, drawListBoxItem, void(entity, float, vector, float))
+	METHOD(NexuizCampaignList, clickListBoxItem, void(entity, float, vector))
+	METHOD(NexuizCampaignList, resizeNotify, void(entity, vector, vector, vector, vector))
+	METHOD(NexuizCampaignList, setSelected, void(entity, float))
+	METHOD(NexuizCampaignList, keyDown, float(entity, float, float, float))
+
+	ATTRIB(NexuizCampaignList, realFontSize, vector, '0 0 0')
+	ATTRIB(NexuizCampaignList, columnPreviewOrigin, float, 0)
+	ATTRIB(NexuizCampaignList, columnPreviewSize, float, 0)
+	ATTRIB(NexuizCampaignList, columnNameOrigin, float, 0)
+	ATTRIB(NexuizCampaignList, columnNameSize, float, 0)
+	ATTRIB(NexuizCampaignList, columnCheckMarkOrigin, float, 0)
+	ATTRIB(NexuizCampaignList, columnCheckMarkSize, float, 0)
+	ATTRIB(NexuizCampaignList, checkMarkOrigin, vector, '0 0 0')
+	ATTRIB(NexuizCampaignList, checkMarkSize, vector, '0 0 0')
+	ATTRIB(NexuizCampaignList, realUpperMargin1, float, 0)
+	ATTRIB(NexuizCampaignList, realUpperMargin2, float, 0)
+
+	ATTRIB(NexuizCampaignList, lastClickedMap, float, -1)
+	ATTRIB(NexuizCampaignList, lastClickedTime, float, 0)
+
+	ATTRIB(NexuizCampaignList, origin, vector, '0 0 0')
+	ATTRIB(NexuizCampaignList, itemAbsSize, vector, '0 0 0')
+	ATTRIB(NexuizCampaignList, emptyLineHeight, float, 0.5)
+
+	ATTRIB(NexuizCampaignList, campaignIndex, float, 0)
+	ATTRIB(NexuizCampaignList, cvarName, string, string_null)
+	METHOD(NexuizCampaignList, loadCvars, void(entity))
+	METHOD(NexuizCampaignList, saveCvars, void(entity))
+ENDCLASS(NexuizCampaignList)
+entity makeNexuizCampaignList();
+void CampaignList_LoadMap(entity btn, entity me);
+#endif
+
+#ifdef IMPLEMENTATION
+string campaign_longdesc_wrapped[CAMPAIGN_MAX_ENTRIES];
+
+void rewrapCampaign(float w, float l0, float emptyheight)
+{
+	float i, j;
+	float n, take, cantake, l;
+	string r, s;
+	for(i = 0; i < campaign_entries; ++i)
+	{
+		l = l0;
+		if(campaign_longdesc_wrapped[i])
+			strunzone(campaign_longdesc_wrapped[i]);
+		n = tokenizebyseparator(campaign_longdesc[i], "\n");
+		r = "";
+		for(j = 0; j < n; ++j)
+		{
+			s = argv(j);
+			if(s == "")
+			{
+				l -= emptyheight;
+				r = strcat(r, "\n");
+				continue;
+			}
+			for(;;)
+			{
+				cantake = draw_TextLengthUpToWidth(s, w, 0);
+				if(cantake > 0 && cantake < strlen(s))
+				{
+					take = cantake - 1;
+					while(take > 0 && substring(s, take, 1) != " ")
+						--take;
+					if(take == 0)
+					{
+						if(--l < 0) goto toolong;
+						r = strcat(r, substring(s, 0, cantake), "\n");
+						s = substring(s, cantake, strlen(s) - cantake);
+					}
+					else
+					{
+						if(--l < 0) goto toolong;
+						r = strcat(r, substring(s, 0, take), "\n");
+						s = substring(s, take + 1, strlen(s) - take);
+					}
+				}
+				else
+				{
+					if(--l < 0) goto toolong;
+					r = strcat(r, s, "\n");
+					break;
+				}
+			}
+		}
+		goto nottoolong;
+:toolong
+		while(substring(r, strlen(r) - 1, 1) == "\n")
+			r = substring(r, 0, strlen(r) - 1);
+		r = strcat(r, "...\n");
+:nottoolong
+		campaign_longdesc_wrapped[i] = strzone(substring(r, 0, strlen(r) - 1));
+	}
+}
+
+entity makeNexuizCampaignList()
+{
+	entity me;
+	me = spawnNexuizCampaignList();
+	me.configureNexuizCampaignList(me);
+	return me;
+}
+void configureNexuizCampaignListNexuizCampaignList(entity me)
+{
+	me.configureNexuizListBox(me);
+	me.loadCvars(me);
+}
+
+void loadCvarsNexuizCampaignList(entity me)
+{
+	// read campaign cvars
+	if(campaign_name)
+		strunzone(campaign_name);
+	if(me.cvarName)
+		strunzone(me.cvarName);
+	campaign_name = strzone(cvar_string("g_campaign_name"));
+	me.cvarName = strzone(strcat("g_campaign", campaign_name, "_index"));
+	CampaignFile_Load(0, CAMPAIGN_MAX_ENTRIES);
+	me.campaignIndex = bound(0, cvar(me.cvarName), campaign_entries);
+	cvar_set(me.cvarName, ftos(me.campaignIndex));
+	if(me.columnNameSize)
+		rewrapCampaign(me.columnNameSize / me.realFontSize_x, me.rowsPerItem - 3, me.emptyLineHeight);
+	me.nItems = min(me.campaignIndex + 2, campaign_entries);
+	me.selectedItem = min(me.campaignIndex, me.nItems - 1);
+	me.scrollPos = me.nItems * me.itemHeight - 1;
+}
+
+void saveCvarsNexuizCampaignList(entity me)
+{
+	// write campaign cvars
+	cvar_set("g_campaign_name", campaign_name);
+	// cvar_set(me.cvarName, ftos(me.campaignIndex)); // NOTE: only server QC does that!
+}
+
+void drawNexuizCampaignList(entity me)
+{
+	if(cvar(me.cvarName) != me.campaignIndex || cvar_string("g_campaign_name") != campaign_name)
+		me.loadCvars(me);
+	drawListBox(me);
+}
+
+void resizeNotifyNexuizCampaignList(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
+{
+	me.origin = absOrigin;
+	me.itemAbsSize = '0 0 0';
+	resizeNotifyNexuizListBox(me, relOrigin, relSize, absOrigin, absSize);
+
+	me.realFontSize_y = me.fontSize / (me.itemAbsSize_y = (absSize_y * me.itemHeight));
+	me.realFontSize_x = me.fontSize / (me.itemAbsSize_x = (absSize_x * (1 - me.controlWidth)));
+	me.realUpperMargin1 = 0.5 * me.realFontSize_y;
+	me.realUpperMargin2 = me.realUpperMargin1 + 2 * me.realFontSize_y;
+
+	me.checkMarkSize = (eX * (me.itemAbsSize_y / me.itemAbsSize_x) + eY) * 0.5;
+
+	me.columnPreviewOrigin = 0;
+	me.columnPreviewSize = me.itemAbsSize_y / me.itemAbsSize_x * 4 / 3;
+	me.columnCheckMarkSize = me.checkMarkSize_x;
+	me.columnNameSize = 1 - me.columnPreviewSize - me.columnCheckMarkSize - 4 * me.realFontSize_x;
+	me.columnNameOrigin = me.columnPreviewOrigin + me.columnPreviewSize + me.realFontSize_x;
+	me.columnCheckMarkOrigin = me.columnNameOrigin + me.columnNameSize + me.realFontSize_x * 2;
+
+	me.checkMarkOrigin = eY + eX * (me.columnCheckMarkOrigin + me.columnCheckMarkSize) - me.checkMarkSize;
+
+	rewrapCampaign(me.columnNameSize / me.realFontSize_x, me.rowsPerItem - 3, me.emptyLineHeight);
+}
+void clickListBoxItemNexuizCampaignList(entity me, float i, vector where)
+{
+	if(i == me.lastClickedMap)
+		if(time < me.lastClickedTime + 0.3)
+		{
+			// DOUBLE CLICK!
+			// start game
+			CampaignList_LoadMap(me, me);
+			return;
+		}
+	me.lastClickedMap = i;
+	me.lastClickedTime = time;
+}
+void drawListBoxItemNexuizCampaignList(entity me, float i, vector absSize, float isSelected)
+{
+	string s;
+	float p;
+	vector theColor;
+	float theAlpha;
+	float j, n;
+	vector o;
+
+	if(i < me.campaignIndex)
+	{
+		theAlpha = SKINALPHA_CAMPAIGN_SELECTABLE;
+		theColor = SKINCOLOR_CAMPAIGN_SELECTABLE;
+	}
+	else if(i == me.campaignIndex)
+	{
+		theAlpha = SKINALPHA_CAMPAIGN_CURRENT;
+		theColor = SKINCOLOR_CAMPAIGN_CURRENT;
+	}
+	else
+	{
+		theAlpha = SKINALPHA_CAMPAIGN_FUTURE;
+		theColor = SKINCOLOR_CAMPAIGN_FUTURE;
+	}
+
+	if(isSelected)
+		draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
+
+	s = ftos(p);
+	draw_Picture(me.columnPreviewOrigin * eX, strcat("/maps/", campaign_mapname[i]), me.columnPreviewSize * eX + eY, '1 1 1', theAlpha);
+	if(i < me.campaignIndex)
+		draw_Picture(me.checkMarkOrigin, "checkmark", me.checkMarkSize, '1 1 1', 1);
+	if(i <= me.campaignIndex)
+		s = campaign_shortdesc[i]; // fteqcc sucks
+	else
+		s = "???";
+	s = draw_TextShortenToWidth(strcat("Level ", ftos(i + 1), ": ", s), me.columnNameSize / me.realFontSize_x, 0);
+	draw_Text(me.realUpperMargin1 * eY + (me.columnNameOrigin + 0.00 * (me.columnNameSize - draw_TextWidth(s, 0) * me.realFontSize_x)) * eX, s, me.realFontSize, theColor, theAlpha, 0);
+
+	if(i <= me.campaignIndex)
+	{
+		s = campaign_longdesc_wrapped[i];
+		n = tokenizebyseparator(s, "\n");
+		o = me.realUpperMargin2 * eY + me.columnNameOrigin * eX;
+		for(j = 0; j < n; ++j)
+			if(argv(j) != "")
+			{
+				draw_Text(o, argv(j), me.realFontSize, theColor, theAlpha * SKINALPHA_CAMPAIGN_DESCRIPTION, 0);
+				o_y += me.realFontSize_y;
+			}
+			else
+				o_y += me.realFontSize_y * me.emptyLineHeight;
+	}
+}
+void CampaignList_LoadMap(entity btn, entity me)
+{
+	if(me.selectedItem >= me.nItems || me.selectedItem < 0)
+		return;
+	CampaignSetup(me.selectedItem);
+}
+
+void setSelectedNexuizCampaignList(entity me, float i)
+{
+	// prevent too late items from being played
+	setSelectedListBox(me, min(i, me.campaignIndex));
+}
+
+float keyDownNexuizCampaignList(entity me, float scan, float ascii, float shift)
+{
+	if(scan == K_ENTER || scan == K_SPACE)
+		CampaignList_LoadMap(me, me);
+	else
+		return keyDownListBox(me, scan, ascii, shift);
+	return 1;
+}
+#endif

Added: trunk/menu/nexuiz/charmap.c
===================================================================
--- trunk/menu/nexuiz/charmap.c	                        (rev 0)
+++ trunk/menu/nexuiz/charmap.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,130 @@
+#ifdef INTERFACE
+CLASS(NexuizCharmap) EXTENDS(Image)
+	METHOD(NexuizCharmap, configureNexuizCharmap, void(entity, entity))
+	METHOD(NexuizCharmap, mousePress, float(entity, vector))
+	METHOD(NexuizCharmap, mouseRelease, float(entity, vector))
+	METHOD(NexuizCharmap, mouseMove, float(entity, vector))
+	METHOD(NexuizCharmap, mouseDrag, float(entity, vector))
+	METHOD(NexuizCharmap, keyDown, float(entity, float, float, float))
+	METHOD(NexuizCharmap, draw, void(entity))
+	ATTRIB(NexuizCharmap, controlledTextbox, entity, NULL)
+	ATTRIB(NexuizCharmap, image, string, SKINGFX_CHARMAP)
+	ATTRIB(NexuizCharmap, image2, string, SKINGFX_CHARMAP_SELECTED)
+	ATTRIB(NexuizCharmap, focusable, float, 1)
+	ATTRIB(NexuizCharmap, previouslySelectedCharacterCell, float, -1)
+	ATTRIB(NexuizCharmap, selectedCharacterCell, float, 0)
+	ATTRIB(NexuizCharmap, mouseSelectedCharacterCell, float, -1)
+ENDCLASS(NexuizCharmap)
+entity makeNexuizCharmap(entity theTextbox);
+#endif
+
+#ifdef IMPLEMENTATION
+entity makeNexuizCharmap(entity theTextbox)
+{
+	entity me;
+	me = spawnNexuizCharmap();
+	me.configureNexuizCharmap(me, theTextbox);
+	return me;
+}
+
+string CharMap_CellToChar(float c)
+{
+	if(c == 13)
+		return chr(127);
+	else if(c < 32)
+		return chr(c);
+	else
+		return chr(c + 96);
+}
+
+void configureNexuizCharmapNexuizCharmap(entity me, entity theTextbox)
+{
+	me.controlledTextbox = theTextbox;
+	me.configureImage(me, me.image);
+}
+
+float mouseMoveNexuizCharmap(entity me, vector coords)
+{
+	float x, y, c;
+	x = floor(coords_x * 16);
+	y = floor(coords_y * 10);
+	if(x < 0 || y < 0 || x >= 16 || y >= 10)
+	{
+		me.mouseSelectedCharacterCell = -1;
+		return 0;
+	}
+	c = y * 16 + x;
+	if(c != me.mouseSelectedCharacterCell)
+		me.mouseSelectedCharacterCell = me.selectedCharacterCell = c;
+	return 1;
+}
+float mouseDragNexuizCharmap(entity me, vector coords)
+{
+	return me.mouseMove(me, coords);
+}
+float mousePressNexuizCharmap(entity me, vector coords)
+{
+	me.mouseMove(me, coords);
+	if(me.mouseSelectedCharacterCell >= 0)
+	{
+		me.pressed = 1;
+		me.previouslySelectedCharacterCell = me.selectedCharacterCell;
+	}
+	return 1;
+}
+float mouseReleaseNexuizCharmap(entity me, vector coords)
+{
+	if(!me.pressed)
+		return 0;
+	me.mouseMove(me, coords);
+	if(me.selectedCharacterCell == me.previouslySelectedCharacterCell)
+		me.controlledTextbox.enterText(me.controlledTextbox, CharMap_CellToChar(me.selectedCharacterCell));
+	me.pressed = 0;
+	return 1;
+}
+float keyDownNexuizCharmap(entity me, float key, float ascii, float shift)
+{
+	switch(key)
+	{
+		case K_LEFTARROW:
+			me.selectedCharacterCell = mod(me.selectedCharacterCell + 159, 160);
+			return 1;
+		case K_RIGHTARROW:
+			me.selectedCharacterCell = mod(me.selectedCharacterCell + 1, 160);
+			return 1;
+		case K_UPARROW:
+			me.selectedCharacterCell = mod(me.selectedCharacterCell + 144, 160);
+			return 1;
+		case K_DOWNARROW:
+			me.selectedCharacterCell = mod(me.selectedCharacterCell + 16, 160);
+			return 1;
+		case K_HOME:
+			me.selectedCharacterCell = 0;
+			return 1;
+		case K_END:
+			me.selectedCharacterCell = 159;
+			return 1;
+		case K_SPACE:
+		case K_ENTER:
+		case K_INS:
+			me.controlledTextbox.enterText(me.controlledTextbox, CharMap_CellToChar(me.selectedCharacterCell));
+			return 1;
+		default:
+			return me.controlledTextbox.keyDown(me.controlledTextbox, key, ascii, shift);
+	}
+}
+void drawNexuizCharmap(entity me)
+{
+	if(me.focused)
+	{
+		if(!me.pressed || (me.selectedCharacterCell == me.previouslySelectedCharacterCell))
+		{
+			vector c;
+			c = eX * (mod(me.selectedCharacterCell, 16) / 16.0);
+			c += eY * (floor(me.selectedCharacterCell / 16.0) / 10.0);
+			draw_Picture(c, me.image2, '0.0625 0.1 0', '1 1 1', 1);
+		}
+	}
+	drawImage(me);
+}
+#endif

Added: trunk/menu/nexuiz/checkbox.c
===================================================================
--- trunk/menu/nexuiz/checkbox.c	                        (rev 0)
+++ trunk/menu/nexuiz/checkbox.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,87 @@
+#ifdef INTERFACE
+CLASS(NexuizCheckBox) EXTENDS(CheckBox)
+	METHOD(NexuizCheckBox, configureNexuizCheckBox, void(entity, float, string, string))
+	METHOD(NexuizCheckBox, setChecked, void(entity, float))
+	ATTRIB(NexuizCheckBox, fontSize, float, SKINFONTSIZE_NORMAL)
+	ATTRIB(NexuizCheckBox, image, string, SKINGFX_CHECKBOX)
+	ATTRIB(NexuizCheckBox, inverted, float, 0)
+	// can be: 0   (off =  0, on =  1)
+	//         1   (off =  1, on =  0)
+	//         1+a (off =  a, on = -a)
+	//        -1-a (off = -a, on =  a)
+
+	ATTRIB(NexuizCheckBox, color, vector, SKINCOLOR_CHECKBOX_N)
+	ATTRIB(NexuizCheckBox, colorC, vector, SKINCOLOR_CHECKBOX_C)
+	ATTRIB(NexuizCheckBox, colorF, vector, SKINCOLOR_CHECKBOX_F)
+	ATTRIB(NexuizCheckBox, colorD, vector, SKINCOLOR_CHECKBOX_D)
+
+	ATTRIB(NexuizCheckBox, cvarName, string, string_null)
+	METHOD(NexuizCheckBox, loadCvars, void(entity))
+	METHOD(NexuizCheckBox, saveCvars, void(entity))
+
+	ATTRIB(NexuizCheckBox, alpha, float, SKINALPHA_TEXT)
+	ATTRIB(NexuizCheckBox, disabledAlpha, float, SKINALPHA_DISABLED)
+ENDCLASS(NexuizCheckBox)
+entity makeNexuizCheckBox(float, string, string);
+#endif
+
+#ifdef IMPLEMENTATION
+entity makeNexuizCheckBox(float isInverted, string theCvar, string theText)
+{
+	entity me;
+	me = spawnNexuizCheckBox();
+	me.configureNexuizCheckBox(me, isInverted, theCvar, theText);
+	return me;
+}
+void configureNexuizCheckBoxNexuizCheckBox(entity me, float isInverted, string theCvar, string theText)
+{
+	me.inverted = isInverted;
+	me.checked = 0;
+	if(theCvar)
+	{
+		me.cvarName = theCvar;
+		me.loadCvars(me);
+	}
+	me.configureCheckBox(me, theText, me.fontSize, me.image);
+}
+void setCheckedNexuizCheckBox(entity me, float val)
+{
+	if(val != me.checked)
+	{
+		me.checked = val;
+		me.saveCvars(me);
+	}
+}
+void loadCvarsNexuizCheckBox(entity me)
+{
+	if(me.inverted == 0)
+		me.checked = cvar(me.cvarName);
+	else if(me.inverted == 1)
+		me.checked = !cvar(me.cvarName);
+	else if(me.inverted > 1)
+		me.checked = (cvar(me.cvarName) < 0);
+	else if(me.inverted < -1)
+		me.checked = (cvar(me.cvarName) > 0);
+}
+void saveCvarsNexuizCheckBox(entity me)
+{
+	if(me.inverted == 0)
+		cvar_set(me.cvarName, me.checked ? "1" : "0");
+	else if(me.inverted == 1)
+		cvar_set(me.cvarName, me.checked ? "0" : "1");
+	else if(me.inverted > 1)
+	{
+		if(me.checked)
+			cvar_set(me.cvarName, ftos(-(me.inverted - 1)));
+		else
+			cvar_set(me.cvarName, ftos(+(me.inverted - 1)));
+	}
+	else if(me.inverted < -1)
+	{
+		if(me.checked)
+			cvar_set(me.cvarName, ftos(-(me.inverted + 1)));
+		else
+			cvar_set(me.cvarName, ftos(+(me.inverted + 1)));
+	}
+}
+#endif

Added: trunk/menu/nexuiz/checkbox_slider_invalid.c
===================================================================
--- trunk/menu/nexuiz/checkbox_slider_invalid.c	                        (rev 0)
+++ trunk/menu/nexuiz/checkbox_slider_invalid.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,63 @@
+#ifdef INTERFACE
+CLASS(NexuizSliderCheckBox) EXTENDS(CheckBox)
+	METHOD(NexuizSliderCheckBox, configureNexuizSliderCheckBox, void(entity, float, float, entity, string))
+	METHOD(NexuizSliderCheckBox, setChecked, void(entity, float))
+	METHOD(NexuizSliderCheckBox, draw, void(entity))
+	ATTRIB(NexuizSliderCheckBox, fontSize, float, SKINFONTSIZE_NORMAL)
+	ATTRIB(NexuizSliderCheckBox, image, string, SKINGFX_CHECKBOX)
+
+	ATTRIB(NexuizSliderCheckBox, color, vector, SKINCOLOR_CHECKBOX_N)
+	ATTRIB(NexuizSliderCheckBox, colorC, vector, SKINCOLOR_CHECKBOX_C)
+	ATTRIB(NexuizSliderCheckBox, colorF, vector, SKINCOLOR_CHECKBOX_F)
+	ATTRIB(NexuizSliderCheckBox, colorD, vector, SKINCOLOR_CHECKBOX_D)
+
+	ATTRIB(NexuizSliderCheckBox, alpha, float, SKINALPHA_TEXT)
+	ATTRIB(NexuizSliderCheckBox, disabledAlpha, float, SKINALPHA_DISABLED)
+
+	ATTRIB(NexuizSliderCheckBox, controlledSlider, entity, NULL)
+	ATTRIB(NexuizSliderCheckBox, offValue, float, -1)
+	ATTRIB(NexuizSliderCheckBox, inverted, float, 0)
+	ATTRIB(NexuizSliderCheckBox, savedValue, float, -1)
+ENDCLASS(NexuizSliderCheckBox)
+entity makeNexuizSliderCheckBox(float, float, entity, string);
+#endif
+
+#ifdef IMPLEMENTATION
+entity makeNexuizSliderCheckBox(float theOffValue, float isInverted, entity theControlledSlider, string theText)
+{
+	entity me;
+	me = spawnNexuizSliderCheckBox();
+	me.configureNexuizSliderCheckBox(me, theOffValue, isInverted, theControlledSlider, theText);
+	return me;
+}
+void configureNexuizSliderCheckBoxNexuizSliderCheckBox(entity me, float theOffValue, float isInverted, entity theControlledSlider, string theText)
+{
+	me.offValue = theOffValue;
+	me.inverted = isInverted;
+	me.checked = (theControlledSlider.value == theOffValue);
+	if(theControlledSlider.value == median(theControlledSlider.valueMin, theControlledSlider.value, theControlledSlider.valueMax))
+		me.savedValue = theControlledSlider.value;
+	else
+		me.savedValue = theControlledSlider.valueMin; 
+	me.controlledSlider = theControlledSlider;
+	me.configureCheckBox(me, theText, me.fontSize, me.image);
+}
+void drawNexuizSliderCheckBox(entity me)
+{
+	me.checked = ((me.controlledSlider.value == me.offValue) != me.inverted);
+	if(me.controlledSlider.value == median(me.controlledSlider.valueMin, me.controlledSlider.value, me.controlledSlider.valueMax))
+		me.savedValue = me.controlledSlider.value;
+	drawCheckBox(me);
+}
+void setCheckedNexuizSliderCheckBox(entity me, float val)
+{
+	if(me.checked == val)
+		return;
+	me.checked = val;
+	if(val == me.inverted)
+		me.controlledSlider.setValue(me.controlledSlider, median(me.controlledSlider.valueMin, me.savedValue, me.controlledSlider.valueMax));
+	else
+		me.controlledSlider.setValue(me.controlledSlider, me.offValue);
+}
+
+#endif

Added: trunk/menu/nexuiz/colorbutton.c
===================================================================
--- trunk/menu/nexuiz/colorbutton.c	                        (rev 0)
+++ trunk/menu/nexuiz/colorbutton.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,70 @@
+#ifdef INTERFACE
+CLASS(NexuizColorButton) EXTENDS(RadioButton)
+	METHOD(NexuizColorButton, configureNexuizColorButton, void(entity, float, float, float))
+	METHOD(NexuizColorButton, setChecked, void(entity, float))
+	METHOD(NexuizColorButton, draw, void(entity))
+	ATTRIB(NexuizColorButton, fontSize, float, SKINFONTSIZE_NORMAL)
+	ATTRIB(NexuizColorButton, image, string, SKINGFX_COLORBUTTON)
+	ATTRIB(NexuizColorButton, image2, string, SKINGFX_COLORBUTTON_COLOR)
+
+	ATTRIB(NexuizColorButton, useDownAsChecked, float, 1)
+
+	ATTRIB(NexuizColorButton, cvarPart, float, 0)
+	ATTRIB(NexuizColorButton, cvarName, string, string_null)
+	ATTRIB(NexuizColorButton, cvarValueFloat, float, 0)
+	METHOD(NexuizColorButton, loadCvars, void(entity))
+	METHOD(NexuizColorButton, saveCvars, void(entity))
+ENDCLASS(NexuizColorButton)
+entity makeNexuizColorButton(float, float, float);
+#endif
+
+#ifdef IMPLEMENTATION
+entity makeNexuizColorButton(float theGroup, float theColor, float theValue)
+{
+	entity me;
+	me = spawnNexuizColorButton();
+	me.configureNexuizColorButton(me, theGroup, theColor, theValue);
+	return me;
+}
+void configureNexuizColorButtonNexuizColorButton(entity me, float theGroup, float theColor, float theValue)
+{
+	me.cvarName = "_cl_color";
+	me.cvarValueFloat = theValue;
+	me.cvarPart = theColor;
+	me.loadCvars(me);
+	me.configureRadioButton(me, string_null, me.fontSize, me.image, theGroup, 0);
+	me.srcMulti = 1;
+	me.src2 = me.image2;
+}
+void setCheckedNexuizColorButton(entity me, float val)
+{
+	if(val != me.checked)
+	{
+		me.checked = val;
+		me.saveCvars(me);
+	}
+}
+void loadCvarsNexuizColorButton(entity me)
+{
+	if(me.cvarPart == 1)
+		me.checked = (cvar(me.cvarName) & 240) == me.cvarValueFloat * 16;
+	else
+		me.checked = (cvar(me.cvarName) & 15) == me.cvarValueFloat;
+}
+void saveCvarsNexuizColorButton(entity me)
+{
+	if(me.checked)
+	{
+		if(me.cvarPart == 1)
+			cvar_set(me.cvarName, ftos(cvar(me.cvarName) & 15 + me.cvarValueFloat * 16));
+		else
+			cvar_set(me.cvarName, ftos(cvar(me.cvarName) & 240 + me.cvarValueFloat));
+	}
+	// TODO on an apply button, read _cl_color and execute the color command for it
+}
+void drawNexuizColorButton(entity me)
+{
+	me.color2 = colormapPaletteColor(me.cvarValueFloat, me.cvarPart);
+	drawCheckBox(me);
+}
+#endif

Added: trunk/menu/nexuiz/commandbutton.c
===================================================================
--- trunk/menu/nexuiz/commandbutton.c	                        (rev 0)
+++ trunk/menu/nexuiz/commandbutton.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,44 @@
+#ifndef COMMANDBUTTON_CLOSE
+# define COMMANDBUTTON_CLOSE 1
+# define COMMANDBUTTON_APPLY 2
+//# define COMMANDBUTTON_REVERT 4
+#endif
+
+#ifdef INTERFACE
+CLASS(NexuizCommandButton) EXTENDS(NexuizButton)
+	METHOD(NexuizCommandButton, configureNexuizCommandButton, void(entity, string, vector, string, float))
+	ATTRIB(NexuizCommandButton, onClickCommand, string, string_null)
+	ATTRIB(NexuizCommandButton, flags, float, 0)
+ENDCLASS(NexuizCommandButton)
+entity makeNexuizCommandButton(string theText, vector theColor, string theCommand, float closesMenu);
+#endif
+
+#ifdef IMPLEMENTATION
+entity makeNexuizCommandButton(string theText, vector theColor, string theCommand, float theFlags)
+{
+	entity me;
+	me = spawnNexuizCommandButton();
+	me.configureNexuizCommandButton(me, theText, theColor, theCommand, theFlags);
+	return me;
+}
+
+void NexuizCommandButton_Click(entity me, entity other)
+{
+	//if(me.flags & COMMANDBUTTON_APPLY)
+	//	saveAllCvars(me.parent);
+	cmd("\n", me.onClickCommand, "\n");
+	//if(me.flags & COMMANDBUTTON_REVERT)
+	//	loadAllCvars(me.parent);
+	if(me.flags & COMMANDBUTTON_CLOSE)
+		m_goto(string_null);
+}
+
+void configureNexuizCommandButtonNexuizCommandButton(entity me, string theText, vector theColor, string theCommand, float theFlags)
+{
+	me.configureNexuizButton(me, theText, theColor);
+	me.onClickCommand = theCommand;
+	me.flags = theFlags;
+	me.onClick = NexuizCommandButton_Click;
+	me.onClickEntity = me;
+}
+#endif

Added: trunk/menu/nexuiz/credits.c
===================================================================
--- trunk/menu/nexuiz/credits.c	                        (rev 0)
+++ trunk/menu/nexuiz/credits.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,106 @@
+#ifdef INTERFACE
+CLASS(NexuizCreditsList) EXTENDS(NexuizListBox)
+	METHOD(NexuizCreditsList, configureNexuizCreditsList, void(entity))
+	ATTRIB(NexuizCreditsList, rowsPerItem, float, 1)
+	METHOD(NexuizCreditsList, draw, void(entity))
+	METHOD(NexuizCreditsList, drawListBoxItem, void(entity, float, vector, float))
+	METHOD(NexuizCreditsList, resizeNotify, void(entity, vector, vector, vector, vector))
+	METHOD(NexuizCreditsList, keyDown, float(entity, float, float, float))
+
+	ATTRIB(NexuizCreditsList, realFontSize, vector, '0 0 0')
+	ATTRIB(NexuizCreditsList, realUpperMargin, float, 0)
+	ATTRIB(NexuizCreditsList, bufferIndex, float, 0)
+	ATTRIB(NexuizCreditsList, scrolling, float, 0)
+ENDCLASS(NexuizCreditsList)
+entity makeNexuizCreditsList();
+#endif
+
+#ifdef IMPLEMENTATION
+entity makeNexuizCreditsList()
+{
+	entity me;
+	me = spawnNexuizCreditsList();
+	me.configureNexuizCreditsList(me);
+	return me;
+}
+void configureNexuizCreditsListNexuizCreditsList(entity me)
+{
+	me.configureNexuizListBox(me);
+	// load the file
+	me.bufferIndex = buf_load("nexuiz-credits.txt");
+	me.nItems = buf_getsize(me.bufferIndex);
+}
+void drawNexuizCreditsList(entity me)
+{
+	float i;
+	if(me.scrolling)
+	{
+		me.scrollPos = bound(0, (time - me.scrolling) * me.itemHeight, me.nItems * me.itemHeight - 1);
+		i = min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1));
+		i = max(i, ceil(me.scrollPos / me.itemHeight));
+		me.setSelected(me, i);
+	}
+	drawListBox(me);
+}
+void resizeNotifyNexuizCreditsList(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
+{
+	resizeNotifyNexuizListBox(me, relOrigin, relSize, absOrigin, absSize);
+
+	me.realFontSize_y = me.fontSize / (absSize_y * me.itemHeight);
+	me.realFontSize_x = me.fontSize / (absSize_x * (1 - me.controlWidth));
+	me.realUpperMargin = 0.5 * (1 - me.realFontSize_y);
+}
+void drawListBoxItemNexuizCreditsList(entity me, float i, vector absSize, float isSelected)
+{
+	// layout: Ping, Credits name, Map name, NP, TP, MP
+	string s;
+	float theAlpha;
+	vector theColor;
+
+	s = bufstr_get(me.bufferIndex, i);
+
+	if(substring(s, 0, 2) == "**")
+	{
+		s = substring(s, 2, strlen(s) - 2);
+		theColor = SKINCOLOR_CREDITS_TITLE;
+		theAlpha = SKINALPHA_CREDITS_TITLE;
+	}
+	else if(substring(s, 0, 1) == "*")
+	{
+		s = substring(s, 1, strlen(s) - 1);
+		theColor = SKINCOLOR_CREDITS_FUNCTION;
+		theAlpha = SKINALPHA_CREDITS_FUNCTION;
+	}
+	else
+	{
+		theColor = SKINCOLOR_CREDITS_PERSON;
+		theAlpha = SKINALPHA_CREDITS_PERSON;
+	}
+
+	draw_CenterText(me.realUpperMargin * eY + 0.5 * eX, s, me.realFontSize, theColor, theAlpha, 0);
+}
+
+float keyDownNexuizCreditsList(entity me, float scan, float ascii, float shift)
+{
+	float i;
+	me.dragScrollTimer = time;
+	me.scrolling = 0;
+
+	if(scan == K_PGUP)
+		me.scrollPos = max(me.scrollPos - 0.5, 0);
+	else if(scan == K_PGDN)
+		me.scrollPos = min(me.scrollPos + 0.5, me.nItems * me.itemHeight - 1);
+	else if(scan == K_UPARROW)
+		me.scrollPos = max(me.scrollPos - me.itemHeight, 0);
+	else if(scan == K_DOWNARROW)
+		me.scrollPos = min(me.scrollPos + me.itemHeight, me.nItems * me.itemHeight - 1);
+	else
+		return keyDownListBox(me, scan, ascii, shift);
+
+	i = min(me.selectedItem, floor((me.scrollPos + 1) / me.itemHeight - 1));
+	i = max(i, ceil(me.scrollPos / me.itemHeight));
+	me.setSelected(me, i);
+
+	return 1;
+}
+#endif

Added: trunk/menu/nexuiz/crosshairbutton.c
===================================================================
--- trunk/menu/nexuiz/crosshairbutton.c	                        (rev 0)
+++ trunk/menu/nexuiz/crosshairbutton.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,66 @@
+#ifdef INTERFACE
+CLASS(NexuizCrosshairButton) EXTENDS(RadioButton)
+	METHOD(NexuizCrosshairButton, configureNexuizCrosshairButton, void(entity, float, float))
+	METHOD(NexuizCrosshairButton, setChecked, void(entity, float))
+	METHOD(NexuizCrosshairButton, draw, void(entity))
+	ATTRIB(NexuizCrosshairButton, fontSize, float, SKINFONTSIZE_NORMAL)
+	ATTRIB(NexuizCrosshairButton, image, string, SKINGFX_CROSSHAIRBUTTON)
+
+	ATTRIB(NexuizCrosshairButton, useDownAsChecked, float, 1)
+
+	ATTRIB(NexuizCrosshairButton, cvarName, string, string_null)
+	ATTRIB(NexuizCrosshairButton, cvarValueFloat, float, 0)
+	METHOD(NexuizCrosshairButton, loadCvars, void(entity))
+	METHOD(NexuizCrosshairButton, saveCvars, void(entity))
+ENDCLASS(NexuizCrosshairButton)
+entity makeNexuizCrosshairButton(float, float);
+#endif
+
+#ifdef IMPLEMENTATION
+entity makeNexuizCrosshairButton(float theGroup, float theCrosshair)
+{
+	entity me;
+	me = spawnNexuizCrosshairButton();
+	me.configureNexuizCrosshairButton(me, theGroup, theCrosshair);
+	return me;
+}
+void configureNexuizCrosshairButtonNexuizCrosshairButton(entity me, float theGroup, float theCrosshair)
+{
+	me.cvarName = "crosshair";
+	me.cvarValueFloat = theCrosshair;
+	me.loadCvars(me);
+	me.configureRadioButton(me, string_null, me.fontSize, me.image, theGroup, 0);
+	me.srcMulti = 1;
+	me.src2 = strzone(strcat("/gfx/crosshair", ftos(me.cvarValueFloat)));
+}
+void setCheckedNexuizCrosshairButton(entity me, float val)
+{
+	if(val != me.checked)
+	{
+		me.checked = val;
+		me.saveCvars(me);
+	}
+}
+void loadCvarsNexuizCrosshairButton(entity me)
+{
+	me.checked = (cvar(me.cvarName) == me.cvarValueFloat);
+}
+void saveCvarsNexuizCrosshairButton(entity me)
+{
+	if(me.checked)
+		cvar_set(me.cvarName, ftos(me.cvarValueFloat));
+	// TODO on an apply button, read _cl_color and execute the color command for it
+}
+void drawNexuizCrosshairButton(entity me)
+{
+	me.color2 = eX * cvar("crosshair_color_red") + eY * cvar("crosshair_color_green") + eZ * cvar("crosshair_color_blue");
+	me.alpha2 = cvar("crosshair_color_alpha");
+	me.src2scale = min((draw_PictureSize(me.src2) * eY * cvar("crosshair_size")) / me.size_y, (draw_PictureSize(me.src2) * eX * cvar("crosshair_size")) / me.size_x, 0.8);
+	if(!me.checked && !me.focused)
+	{
+		me.alpha2 *= me.disabledAlpha;
+		me.color2 = '1 1 1';
+	}
+	drawCheckBox(me);
+}
+#endif

Added: trunk/menu/nexuiz/dialog.c
===================================================================
--- trunk/menu/nexuiz/dialog.c	                        (rev 0)
+++ trunk/menu/nexuiz/dialog.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,32 @@
+#ifdef INTERFACE
+CLASS(NexuizDialog) EXTENDS(Dialog)
+	// still to be customized by user
+	/*
+	ATTRIB(NexuizDialog, closable, float, 1)
+	ATTRIB(NexuizDialog, title, string, "Form1") // ;)
+	ATTRIB(NexuizDialog, color, vector, '1 0.5 1')
+	ATTRIB(NexuizDialog, intendedWidth, float, 0)
+	ATTRIB(NexuizDialog, rows, float, 3)
+	ATTRIB(NexuizDialog, columns, float, 2)
+	*/
+	ATTRIB(NexuizDialog, marginTop, float, SKINMARGIN_TOP) // pixels
+	ATTRIB(NexuizDialog, marginBottom, float, SKINMARGIN_BOTTOM) // pixels
+	ATTRIB(NexuizDialog, marginLeft, float, SKINMARGIN_LEFT) // pixels
+	ATTRIB(NexuizDialog, marginRight, float, SKINMARGIN_RIGHT) // pixels
+	ATTRIB(NexuizDialog, columnSpacing, float, SKINMARGIN_COLUMNS) // pixels
+	ATTRIB(NexuizDialog, rowSpacing, float, SKINMARGIN_ROWS) // pixels
+	ATTRIB(NexuizDialog, rowHeight, float, SKINFONTSIZE_NORMAL * SKINHEIGHT_NORMAL) // pixels
+	ATTRIB(NexuizDialog, titleHeight, float, SKINFONTSIZE_TITLE * SKINHEIGHT_TITLE) // pixels
+	ATTRIB(NexuizDialog, titleFontSize, float, SKINFONTSIZE_TITLE) // pixels
+
+	ATTRIB(NexuizDialog, backgroundImage, string, SKINGFX_DIALOGBORDER)
+	ATTRIB(NexuizDialog, closeButtonImage, string, SKINGFX_CLOSEBUTTON)
+	ATTRIB(NexuizDialog, zoomedOutTitleBarPosition, float, SKINHEIGHT_ZOOMEDTITLE * 0.5 - 0.5)
+	ATTRIB(NexuizDialog, zoomedOutTitleBar, float, SKINHEIGHT_ZOOMEDTITLE != 0)
+
+	ATTRIB(NexuizDialog, alpha, float, SKINALPHA_TEXT)
+ENDCLASS(NexuizDialog)
+#endif
+
+#ifdef IMPLEMENTATION
+#endif

Added: trunk/menu/nexuiz/dialog_classselect.c
===================================================================
--- trunk/menu/nexuiz/dialog_classselect.c	                        (rev 0)
+++ trunk/menu/nexuiz/dialog_classselect.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,53 @@
+#ifdef INTERFACE
+CLASS(NexuizClassSelectDialog) EXTENDS(NexuizRootDialog)
+	METHOD(NexuizClassSelectDialog, fill, void(entity)) // to be overridden by user to fill the dialog with controls
+	METHOD(NexuizClassSelectDialog, showNotify, void(entity))
+	ATTRIB(NexuizClassSelectDialog, title, string, "Class Selection") // ;)
+	ATTRIB(NexuizClassSelectDialog, color, vector, SKINCOLOR_DIALOG_TEAMSELECT)
+	ATTRIB(NexuizClassSelectDialog, intendedWidth, float, 0.4)
+	ATTRIB(NexuizClassSelectDialog, rows, float, 5)
+	ATTRIB(NexuizClassSelectDialog, columns, float, 4)
+	ATTRIB(NexuizClassSelectDialog, name, string, "ClassSelect")
+	ATTRIB(NexuizClassSelectDialog, text1, entity, NULL)
+	ATTRIB(NexuizClassSelectDialog, text2, entity, NULL)
+	ATTRIB(NexuizClassSelectDialog, class1, entity, NULL)
+	ATTRIB(NexuizClassSelectDialog, class2, entity, NULL)
+	ATTRIB(NexuizClassSelectDialog, class3, entity, NULL)
+	ATTRIB(NexuizClassSelectDialog, class4, entity, NULL)
+ENDCLASS(NexuizClassSelectDialog)
+#endif
+
+#ifdef IMPLEMENTATION
+entity makeClassButton(string theName, vector theColor, string commandtheName)
+{
+	entity b;
+	b = makeNexuizCommandButton(theName, theColor, commandtheName, 1);
+	return b;
+}
+
+void showNotifyNexuizClassSelectDialog(entity me)
+{
+	float classes, nClasses;
+	classes = cvar("_classes_available");
+	nClasses = 0;
+	me.class1.disabled = !(classes & 1); nClasses += !!(classes & 1);
+	me.class2.disabled = !(classes & 2); nClasses += !!(classes & 2);
+	me.class3.disabled = !(classes & 4); nClasses += !!(classes & 4);
+	me.class4.disabled = !(classes & 8); nClasses += !!(classes & 8);
+}
+
+void fillNexuizClassSelectDialog(entity me)
+{
+	me.TR(me);
+	me.TD(me, 1, 1, me.text1 = makeNexuizTextLabel(0, "Offensive:"));
+	me.TD(me, 2, 1, me.class1 = makeTeamButton("Commando", '1 0.5 0.5', "cmd selectclass 1; cmd join;"));
+	me.TD(me, 2, 1, me.class2 = makeTeamButton("Enforcer", '0.5 0.5 1', "cmd selectclass 2; cmd join;"));
+	me.TR(me);
+	me.TR(me);
+	me.TD(me, 1, 1, me.text2 = makeNexuizTextLabel(0, "Defensive:"));
+	me.TD(me, 2, 1, me.class3 = makeTeamButton("Techie", '1 1 0.5', "cmd selectclass 3; cmd join;"));
+	me.TD(me, 2, 1, me.class4 = makeTeamButton("Heavy", '1 0.5 1', "cmd selectclass 4; cmd join;"));
+}
+#endif
+
+// click. The C-word so you can grep for it.

Added: trunk/menu/nexuiz/dialog_credits.c
===================================================================
--- trunk/menu/nexuiz/dialog_credits.c	                        (rev 0)
+++ trunk/menu/nexuiz/dialog_credits.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,29 @@
+#ifdef INTERFACE
+CLASS(NexuizCreditsDialog) EXTENDS(NexuizDialog)
+	METHOD(NexuizCreditsDialog, fill, void(entity))
+	METHOD(NexuizCreditsDialog, focusEnter, void(entity))
+	ATTRIB(NexuizCreditsDialog, title, string, "Credits")
+	ATTRIB(NexuizCreditsDialog, color, vector, SKINCOLOR_DIALOG_CREDITS)
+	ATTRIB(NexuizCreditsDialog, intendedWidth, float, SKINWIDTH_CREDITS)
+	ATTRIB(NexuizCreditsDialog, rows, float, SKINROWS_CREDITS)
+	ATTRIB(NexuizCreditsDialog, columns, float, 2)
+	ATTRIB(NexuizCreditsDialog, creditsList, entity, NULL)
+ENDCLASS(NexuizCreditsDialog)
+#endif
+
+#ifdef IMPLEMENTATION
+void fillNexuizCreditsDialog(entity me)
+{
+	entity e;
+	me.TR(me);
+		me.TD(me, me.rows - 1, me.columns, me.creditsList = makeNexuizCreditsList());
+	me.gotoRC(me, me.rows - 1, 0);
+		me.TD(me, 1, me.columns, e = makeNexuizButton("OK", '0 0 0'));
+			e.onClick = Dialog_Close;
+			e.onClickEntity = me;
+}
+void focusEnterNexuizCreditsDialog(entity me)
+{
+	me.creditsList.scrolling = time + 1;
+}
+#endif

Added: trunk/menu/nexuiz/dialog_infoscreen.c
===================================================================
--- trunk/menu/nexuiz/dialog_infoscreen.c	                        (rev 0)
+++ trunk/menu/nexuiz/dialog_infoscreen.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,34 @@
+#ifdef INTERFACE
+CLASS(NexuizInfoScreenDialog) EXTENDS(NexuizRootDialog)
+	METHOD(NexuizInfoScreenDialog, fill, void(entity)) // to be overridden by user to fill the dialog with controls
+	METHOD(NexuizInfoScreenDialog, showNotify, void(entity))
+	ATTRIB(NexuizInfoScreenDialog, title, string, "MISSION OBJECTIVE:") // ;)
+	ATTRIB(NexuizInfoScreenDialog, color, vector, SKINCOLOR_DIALOG_TEAMSELECT)
+	ATTRIB(NexuizInfoScreenDialog, intendedWidth, float, 0.4)
+	ATTRIB(NexuizInfoScreenDialog, rows, float, 5)
+	ATTRIB(NexuizInfoScreenDialog, columns, float, 4)
+	ATTRIB(NexuizInfoScreenDialog, name, string, "InfoScreen")
+	ATTRIB(NexuizTeamSelectDialog, okay1, entity, NULL)
+ENDCLASS(NexuizInfoScreenDialog)
+#endif
+
+#ifdef IMPLEMENTATION
+
+void showNotifyNexuizInfoScreenDialog(entity me)
+{
+
+}
+
+void fillNexuizInfoScreenDialog(entity me)
+{
+	me.TR(me);
+	me.TD(me, 1, 1, me.text1 = makeNexuizTextLabel(0, "Destroy the 5 power stations throughout the level"));
+	me.TR(me);
+	me.TR(me);
+	me.TR(me);
+	me.TDempty(me, (me.columns - 1) / 2);
+	me.TD(me, 2, 1, me.okay1 = makeNexuizCommandButton("Start", '1 1 1', "",1));
+}
+#endif
+
+// click. The C-word so you can grep for it.

Added: trunk/menu/nexuiz/dialog_multiplayer.c
===================================================================
--- trunk/menu/nexuiz/dialog_multiplayer.c	                        (rev 0)
+++ trunk/menu/nexuiz/dialog_multiplayer.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,29 @@
+#ifdef INTERFACE
+CLASS(NexuizMultiplayerDialog) EXTENDS(NexuizDialog)
+	METHOD(NexuizMultiplayerDialog, fill, void(entity))
+	ATTRIB(NexuizMultiplayerDialog, title, string, "Multiplayer")
+	ATTRIB(NexuizMultiplayerDialog, color, vector, SKINCOLOR_DIALOG_MULTIPLAYER)
+	ATTRIB(NexuizMultiplayerDialog, intendedWidth, float, 0.96)
+	ATTRIB(NexuizMultiplayerDialog, rows, float, 24)
+	ATTRIB(NexuizMultiplayerDialog, columns, float, 6)
+ENDCLASS(NexuizMultiplayerDialog)
+#endif
+
+#ifdef IMPLEMENTATION
+void fillNexuizMultiplayerDialog(entity me)
+{
+	entity mc, e;
+	mc = makeNexuizTabController(me.rows - 2);
+	me.TR(me);
+		me.TD(me, 1, 1, e = mc.makeTabButton(mc, "Join",  makeNexuizServerListTab()));
+			setDependentStringNotEqual(e, "_cl_name", "Player");
+		me.TD(me, 1, 1, e = mc.makeTabButton(mc, "Player Setup",  makeNexuizPlayerSettingsTab()));
+			if(cvar_string("_cl_name") == "Player")
+				e.onClick(e, e.onClickEntity); // lol animation
+		me.TD(me, 1, 1, e = mc.makeTabButton(mc, "Create",  makeNexuizServerCreateTab()));
+			setDependentStringNotEqual(e, "_cl_name", "Player");
+	me.TR(me);
+	me.TR(me);
+		me.TD(me, me.rows - 2, me.columns, mc);
+}
+#endif

Added: trunk/menu/nexuiz/dialog_multiplayer_create.c
===================================================================
--- trunk/menu/nexuiz/dialog_multiplayer_create.c	                        (rev 0)
+++ trunk/menu/nexuiz/dialog_multiplayer_create.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,192 @@
+#ifdef INTERFACE
+CLASS(NexuizServerCreateTab) EXTENDS(NexuizTab)
+	METHOD(NexuizServerCreateTab, fill, void(entity))
+	METHOD(NexuizServerCreateTab, gameTypeChangeNotify, void(entity))
+	ATTRIB(NexuizServerCreateTab, title, string, "Create")
+	ATTRIB(NexuizServerCreateTab, intendedWidth, float, 0.9)
+	ATTRIB(NexuizServerCreateTab, rows, float, 22)
+	ATTRIB(NexuizServerCreateTab, columns, float, 6.5)
+
+	ATTRIB(NexuizServerCreateTab, mapListBox, entity, NULL)
+	ATTRIB(NexuizServerCreateTab, sliderFraglimit, entity, NULL)
+	ATTRIB(NexuizServerCreateTab, sliderTimelimit, entity, NULL)
+	ATTRIB(NexuizServerCreateTab, checkboxFraglimit, entity, NULL)
+ENDCLASS(NexuizServerCreateTab)
+entity makeNexuizServerCreateTab();
+#endif
+
+#ifdef IMPLEMENTATION
+
+entity makeNexuizServerCreateTab()
+{
+	entity me;
+	me = spawnNexuizServerCreateTab();
+	me.configureDialog(me);
+	return me;
+}
+
+void fillNexuizServerCreateTab(entity me)
+{
+	entity e, e0;
+	float n;
+
+	me.TR(me);
+		n = 8 + 2 * !!cvar("developer");
+		me.TD(me, 2, me.columns / n, e = makeNexuizGametypeButton(1, "g_dm", "DM"));
+			e0 = e;
+		me.TD(me, 2, me.columns / n, e = makeNexuizGametypeButton(1, "g_tdm", "TDM"));
+			if(e.checked) e0 = NULL;
+		me.TD(me, 2, me.columns / n, e = makeNexuizGametypeButton(1, "g_lms", "LMS"));
+			if(e.checked) e0 = NULL;
+		me.TD(me, 2, me.columns / n, e = makeNexuizGametypeButton(1, "g_arena", "Arena"));
+			if(e.checked) e0 = NULL;
+		me.TD(me, 2, me.columns / n, e = makeNexuizGametypeButton(1, "g_runematch", "Rune"));
+			if(e.checked) e0 = NULL;
+		me.TD(me, 2, me.columns / n, e = makeNexuizGametypeButton(1, "g_domination", "Dom"));
+			if(e.checked) e0 = NULL;
+		me.TD(me, 2, me.columns / n, e = makeNexuizGametypeButton(1, "g_keyhunt", "Key Hunt"));
+			if(e.checked) e0 = NULL;
+		me.TD(me, 2, me.columns / n, e = makeNexuizGametypeButton(1, "g_ctf", "CTF"));
+			if(e.checked) e0 = NULL;
+		if(cvar("developer"))
+		{
+			me.TD(me, 2, me.columns / n, e = makeNexuizGametypeButton(1, "g_assault", "Assault"));
+				if(e.checked) e0 = NULL;
+			me.TD(me, 2, me.columns / n, e = makeNexuizGametypeButton(1, "g_onslaught", "Onslaught"));
+				if(e.checked) e0 = NULL;
+		}
+		if(e0)
+		{
+			//print("NO CHECK\n");
+			e0.setChecked(e0, 1);
+		}
+	me.TR(me);
+	me.TR(me);
+	me.TR(me);
+		me.TD(me, 1, 3, e = makeNexuizTextLabel(0, "Map list:"));
+	me.TR(me);
+		me.TD(me, me.rows - 7, 3, e = makeNexuizMapList());
+		me.mapListBox = e;
+	me.gotoRC(me, me.rows - 3, 0);
+		me.TDempty(me, 0.5);
+		me.TD(me, 1, 1, e = makeNexuizButton("All", '0 0 0'));
+			e.onClick = MapList_All;
+			e.onClickEntity = me.mapListBox;
+		me.TD(me, 1, 1, e = makeNexuizButton("None", '0 0 0'));
+			e.onClick = MapList_None;
+			e.onClickEntity = me.mapListBox;
+		me.TDempty(me, 0.5);
+
+	me.gotoRC(me, 3, 3.5); me.setFirstColumn(me, me.currentColumn);
+		me.TD(me, 1, 3, e = makeNexuizTextLabel(0, "Settings:"));
+	me.TR(me);
+		me.sliderTimelimit = makeNexuizSlider(1.0, 60.0, 0.5, "timelimit_override");
+		me.TD(me, 1, 1, e = makeNexuizSliderCheckBox(0, 1, me.sliderTimelimit, "Time limit:"));
+		me.TD(me, 1, 2, me.sliderTimelimit);
+	me.TR(me);
+		me.TDempty(me, 0.2);
+		me.TD(me, 1, 2.8, e = makeNexuizSliderCheckBox(-1, 0, me.sliderTimelimit, "Use map specified default"));
+	me.TR(me);
+		me.sliderFraglimit = makeNexuizSlider(1.0, 2000.0, 5, "fraglimit_override");
+		me.TD(me, 1, 1, e = makeNexuizSliderCheckBox(0, 1, me.sliderFraglimit, "Point limit:"));
+			me.checkboxFraglimit = e;
+		me.TD(me, 1, 2, me.sliderFraglimit);
+	me.TR(me);
+		me.TDempty(me, 0.2);
+		me.TD(me, 1, 2.8, e = makeNexuizSliderCheckBox(-1, 0, me.sliderFraglimit, "Use map specified default"));
+	me.TR(me);
+	me.TR(me);
+		me.TD(me, 1, 3, e = makeNexuizCheckBox(0, "g_antilag", "AntiLag"));
+	me.TR(me);
+	me.TR(me);
+		me.TD(me, 1, 1, e = makeNexuizTextLabel(0, "Map voting:"));
+		me.TD(me, 1, 2, e = makeNexuizTextSlider("g_maplist_votable"));
+			e.addValue(e, "No voting", "0");
+			e.addValue(e, "2 choices", "2");
+			e.addValue(e, "3 choices", "3");
+			e.addValue(e, "4 choices", "4");
+			e.addValue(e, "5 choices", "5");
+			e.addValue(e, "6 choices", "6");
+			e.addValue(e, "7 choices", "7");
+			e.configureNexuizTextSliderValues(e);
+	me.TR(me);
+		me.TD(me, 1, 3, e = makeNexuizCheckBox(0, "sv_vote_simple_majority", "Simple majority wins vcall"));
+	me.TR(me);
+	me.TR(me);
+		me.TD(me, 1, 1, e = makeNexuizTextLabel(0, "Number of players:"));
+		me.TD(me, 1, 2, makeNexuizSlider(1, 32, 1, "menu_maxplayers"));
+	me.TR(me);
+		me.TD(me, 1, 1, e = makeNexuizTextLabel(0, "Number of bots:"));
+		me.TD(me, 1, 2, makeNexuizSlider(0, 7, 1, "bot_number"));
+	me.TR(me);
+		me.TDempty(me, 0.2);
+		me.TD(me, 1, 0.8, e = makeNexuizTextLabel(0, "Bot skill:"));
+		me.TD(me, 1, 2, e = makeNexuizTextSlider("skill"));
+			e.addValue(e, "Botlike", "0");
+			e.addValue(e, "Beginner", "1");
+			e.addValue(e, "You will win", "2");
+			e.addValue(e, "You can win", "3");
+			e.addValue(e, "You might win", "4");
+			e.addValue(e, "Advanced", "5");
+			e.addValue(e, "Expert", "6");
+			e.addValue(e, "Pro", "7");
+			e.addValue(e, "Assassin", "8");
+			e.addValue(e, "Unhuman", "9");
+			e.addValue(e, "Godlike", "10");
+			e.configureNexuizTextSliderValues(e);
+			setDependent(e, "bot_number", 0, -1);
+	me.TR(me);
+		me.TDempty(me, 0.2);
+		me.TD(me, 1, 0.8, e = makeNexuizTextLabel(0, "Bot names:"));
+		me.TD(me, 1, 0.7, e = makeNexuizInputBox(1, "bot_prefix"));
+			setDependent(e, "bot_number", 0, -1);
+		me.TD(me, 1, 0.6, e = makeNexuizTextLabel(0.5, "Spellbinder"));
+			setDependent(e, "bot_number", 0, -1);
+		me.TD(me, 1, 0.7, e = makeNexuizInputBox(1, "bot_suffix"));
+			setDependent(e, "bot_number", 0, -1);
+	me.TR(me);
+	me.TR(me);
+		me.TD(me, 1, 1, e = makeNexuizButton("Mutators...", '0 0 0'));
+			e.onClick = DialogOpenButton_Click;
+			e.onClickEntity = main.mutatorsDialog;
+			main.mutatorsDialog.refilterEntity = me.mapListBox;
+		me.TD(me, 1, 2, e0 = makeNexuizTextLabel(0, string_null));
+			e0.textEntity = main.mutatorsDialog;
+			e0.allowCut = 1;
+
+	me.gotoRC(me, me.rows - 1, 0);
+		me.TD(me, 1, me.columns, e = makeNexuizButton("Start!", '0 0 0'));
+			e.onClick = MapList_LoadMap;
+			e.onClickEntity = me.mapListBox;
+			me.mapListBox.startButton = e;
+
+	me.gameTypeChangeNotify(me);
+}
+
+void GameType_ConfigureSliders(entity e, entity l, string pLabel, float pMin, float pMax, float pStep, string pCvar)
+{
+	e.configureNexuizSlider(e, pMin, pMax, pStep, pCvar);
+	l.setText(l, pLabel);
+}
+
+void gameTypeChangeNotifyNexuizServerCreateTab(entity me)
+{
+	// tell the map list to update
+	float gt;
+	entity e, l;
+	gt = MapInfo_CurrentGametype();
+	e = me.sliderFraglimit;
+	l = me.checkboxFraglimit;
+	switch(gt)
+	{
+		case MAPINFO_TYPE_CTF:        GameType_ConfigureSliders(e, l, "Point limit:",  50,  500, 10, "g_ctf_capture_limit");      break;
+		case MAPINFO_TYPE_DOMINATION: GameType_ConfigureSliders(e, l, "Point limit:",  50,  500, 10, "g_domination_point_limit"); break;
+		case MAPINFO_TYPE_KEYHUNT:    GameType_ConfigureSliders(e, l, "Point limit:", 200, 1500, 50, "g_keyhunt_point_limit");    break;
+		case MAPINFO_TYPE_RUNEMATCH:  GameType_ConfigureSliders(e, l, "Point limit:",  50,  500, 10, "g_runematch_point_limit");  break;
+		case MAPINFO_TYPE_LMS:        GameType_ConfigureSliders(e, l, "Lives:",         3,   50,  1, "g_lms_lives_override");     break;
+		default:                      GameType_ConfigureSliders(e, l, "Frag limit:",    5,  100,  5, "fraglimit_override");       break;
+	}
+	me.mapListBox.refilter(me.mapListBox);
+}
+
+#endif

Added: trunk/menu/nexuiz/dialog_multiplayer_create_mapinfo.c
===================================================================
--- trunk/menu/nexuiz/dialog_multiplayer_create_mapinfo.c	                        (rev 0)
+++ trunk/menu/nexuiz/dialog_multiplayer_create_mapinfo.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,147 @@
+#ifdef INTERFACE
+CLASS(NexuizMapInfoDialog) EXTENDS(NexuizDialog)
+	METHOD(NexuizMapInfoDialog, fill, void(entity))
+	METHOD(NexuizMapInfoDialog, loadMapInfo, void(entity, float))
+	ATTRIB(NexuizMapInfoDialog, title, string, "Map Information")
+	ATTRIB(NexuizMapInfoDialog, color, vector, SKINCOLOR_DIALOG_MAPINFO)
+	ATTRIB(NexuizMapInfoDialog, intendedWidth, float, 0.85)
+	ATTRIB(NexuizMapInfoDialog, rows, float, 9)
+	ATTRIB(NexuizMapInfoDialog, columns, float, 10)
+
+	ATTRIB(NexuizMapInfoDialog, previewImage, entity, NULL)
+	ATTRIB(NexuizMapInfoDialog, titleLabel, entity, NULL)
+	ATTRIB(NexuizMapInfoDialog, authorLabel, entity, NULL)
+	ATTRIB(NexuizMapInfoDialog, descriptionLabel, entity, NULL)
+	ATTRIB(NexuizMapInfoDialog, featuresLabel, entity, NULL)
+
+	ATTRIB(NexuizMapInfoDialog, typeDeathmatchLabel, entity, NULL)
+	ATTRIB(NexuizMapInfoDialog, typeTDMLabel, entity, NULL)
+	ATTRIB(NexuizMapInfoDialog, typeLMSLabel, entity, NULL)
+	ATTRIB(NexuizMapInfoDialog, typeArenaLabel, entity, NULL)
+	ATTRIB(NexuizMapInfoDialog, typeRuneLabel, entity, NULL)
+	ATTRIB(NexuizMapInfoDialog, typeDominationLabel, entity, NULL)
+	ATTRIB(NexuizMapInfoDialog, typeKeyHuntLabel, entity, NULL)
+	ATTRIB(NexuizMapInfoDialog, typeCTFLabel, entity, NULL)
+	ATTRIB(NexuizMapInfoDialog, typeAssaultLabel, entity, NULL)
+	ATTRIB(NexuizMapInfoDialog, typeOnslaughtLabel, entity, NULL)
+
+	ATTRIB(NexuizMapInfoDialog, currentMapIndex, float, 0)
+	ATTRIB(NexuizMapInfoDialog, currentMapBSPName, string, string_null)
+	ATTRIB(NexuizMapInfoDialog, currentMapTitle, string, string_null)
+	ATTRIB(NexuizMapInfoDialog, currentMapAuthor, string, string_null)
+	ATTRIB(NexuizMapInfoDialog, currentMapDescription, string, string_null)
+	ATTRIB(NexuizMapInfoDialog, currentMapPreviewImage, string, string_null)
+	ATTRIB(NexuizMapInfoDialog, currentMapFeaturesText, string, string_null)
+ENDCLASS(NexuizMapInfoDialog)
+#endif
+
+#ifdef IMPLEMENTATION
+void loadMapInfoNexuizMapInfoDialog(entity me, float i)
+{
+	me.currentMapIndex = i;
+	MapInfo_Get_ByID(i);
+
+	if(me.currentMapBSPName)
+	{
+		strunzone(me.currentMapBSPName);
+		strunzone(me.currentMapTitle);
+		strunzone(me.currentMapAuthor);
+		strunzone(me.currentMapDescription);
+		strunzone(me.currentMapPreviewImage);
+		strunzone(me.currentMapFeaturesText);
+	}
+	me.currentMapBSPName = strzone(MapInfo_Map_bspname);
+	me.currentMapTitle = strzone(MapInfo_Map_title);
+	me.currentMapAuthor = strzone(MapInfo_Map_author);
+	me.currentMapDescription = strzone(MapInfo_Map_description);
+	me.currentMapFeaturesText = strzone((MapInfo_Map_supportedFeatures & MAPINFO_FEATURE_WEAPONS) ? "Full item placement" : "MinstaGib only");
+	me.currentMapPreviewImage = strzone(strcat("/maps/", MapInfo_Map_bspname));
+
+	me.frame.setText(me.frame, me.currentMapBSPName);
+	me.titleLabel.setText(me.titleLabel, me.currentMapTitle);
+	me.authorLabel.setText(me.authorLabel, me.currentMapAuthor);
+	me.descriptionLabel.setText(me.descriptionLabel, me.currentMapDescription);
+	me.featuresLabel.setText(me.featuresLabel, me.currentMapFeaturesText);
+	me.previewImage.src = me.currentMapPreviewImage;
+
+	me.typeDeathmatchLabel.disabled = !(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_DEATHMATCH);
+	me.typeTDMLabel.disabled = !(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_TEAM_DEATHMATCH);
+	me.typeLMSLabel.disabled = !(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_LMS);
+	me.typeArenaLabel.disabled = !(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_ARENA);
+	me.typeDominationLabel.disabled = !(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_DOMINATION);
+	me.typeRuneLabel.disabled = !(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_RUNEMATCH);
+	me.typeKeyHuntLabel.disabled = !(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_KEYHUNT);
+	me.typeCTFLabel.disabled = !(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_CTF);
+	if(me.typeAssaultLabel)
+		me.typeAssaultLabel.disabled = !(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_ASSAULT);
+	if(me.typeOnslaughtLabel)
+		me.typeOnslaughtLabel.disabled = !(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_ONSLAUGHT);
+
+	MapInfo_ClearTemps();
+}
+void fillNexuizMapInfoDialog(entity me)
+{
+	entity e;
+	float w;
+	me.TR(me);
+		me.TD(me, me.rows - 2, 3, e = makeNexuizImage(string_null, 4.0/3.0));
+		me.previewImage = e;
+	me.gotoRC(me, 0, 3.5); me.setFirstColumn(me, me.currentColumn);
+	w = me.columns - me.currentColumn;
+		me.TD(me, 1, 1, e = makeNexuizTextLabel(0, "Title:"));
+		me.TD(me, 1, w-1, e = makeNexuizTextLabel(0, ""));
+			e.alpha = 1;
+			e.colorL = SKINCOLOR_MAPLIST_TITLE;
+			e.allowCut = 1;
+			me.titleLabel = e;
+	me.TR(me);
+		me.TD(me, 1, 1, e = makeNexuizTextLabel(0, "Author:"));
+		me.TD(me, 1, w-1, e = makeNexuizTextLabel(0, ""));
+			e.alpha = 1;
+			e.colorL = SKINCOLOR_MAPLIST_AUTHOR;
+			e.allowCut = 1;
+			me.authorLabel = e;
+	me.TR(me);
+		me.TD(me, 1, w, e = makeNexuizTextLabel(0, ""));
+			e.allowCut = 1;
+			me.featuresLabel = e;
+	me.TR(me);
+		me.TD(me, 1, w, e = makeNexuizTextLabel(0, "Game types:"));
+	me.TR(me);
+		me.TD(me, 1, w/4, e = makeNexuizTextLabel(0, "Deathmatch"));
+			me.typeDeathmatchLabel = e;
+		me.TD(me, 1, w/4, e = makeNexuizTextLabel(0, "TDM"));
+			me.typeTDMLabel = e;
+		me.TD(me, 1, w/4, e = makeNexuizTextLabel(0, "LMS"));
+			me.typeLMSLabel = e;
+		me.TD(me, 1, w/4, e = makeNexuizTextLabel(0, "Arena"));
+			me.typeArenaLabel = e;
+	me.TR(me);
+		me.TD(me, 1, w/4, e = makeNexuizTextLabel(0, "Rune"));
+			me.typeRuneLabel = e;
+		me.TD(me, 1, w/4, e = makeNexuizTextLabel(0, "Domination"));
+			me.typeDominationLabel = e;
+		me.TD(me, 1, w/4, e = makeNexuizTextLabel(0, "Key Hunt"));
+			me.typeKeyHuntLabel = e;
+		me.TD(me, 1, w/4, e = makeNexuizTextLabel(0, "CTF"));
+			me.typeCTFLabel = e;
+	me.TR(me);
+		if(cvar("developer"))
+		{
+			me.TD(me, 1, w/4, e = makeNexuizTextLabel(0, "Assault"));
+				me.typeAssaultLabel = e;
+			me.TD(me, 1, w/4, e = makeNexuizTextLabel(0, "Onslaught"));
+				me.typeOnslaughtLabel = e;
+		}
+
+	me.gotoRC(me, me.rows - 2, 0);
+		me.TD(me, 1, me.columns, e = makeNexuizTextLabel(0.5, ""));
+			e.allowCut = 1;
+			me.descriptionLabel = e;
+
+	me.gotoRC(me, me.rows - 1, 0);
+		me.TD(me, 1, me.columns, e = makeNexuizButton("OK", '0 0 0'));
+			e.onClick = Dialog_Close;
+			e.onClickEntity = me;
+}
+#endif

Added: trunk/menu/nexuiz/dialog_multiplayer_create_mutators.c
===================================================================
--- trunk/menu/nexuiz/dialog_multiplayer_create_mutators.c	                        (rev 0)
+++ trunk/menu/nexuiz/dialog_multiplayer_create_mutators.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,107 @@
+#ifdef INTERFACE
+CLASS(NexuizMutatorsDialog) EXTENDS(NexuizDialog)
+	METHOD(NexuizMutatorsDialog, toString, string(entity))
+	METHOD(NexuizMutatorsDialog, fill, void(entity))
+	METHOD(NexuizMutatorsDialog, showNotify, void(entity))
+	METHOD(NexuizMutatorsDialog, close, void(entity))
+	ATTRIB(NexuizMutatorsDialog, title, string, "Mutators")
+	ATTRIB(NexuizMutatorsDialog, color, vector, SKINCOLOR_DIALOG_MUTATORS)
+	ATTRIB(NexuizMutatorsDialog, intendedWidth, float, 0.6)
+	ATTRIB(NexuizMutatorsDialog, rows, float, 9)
+	ATTRIB(NexuizMutatorsDialog, columns, float, 4)
+	ATTRIB(NexuizMutatorsDialog, refilterEntity, entity, NULL)
+ENDCLASS(NexuizMutatorsDialog)
+#endif
+
+#ifdef IMPLEMENTATION
+void showNotifyNexuizMutatorsDialog(entity me)
+{
+        loadAllCvars(me);
+}
+string toStringNexuizMutatorsDialog(entity me)
+{
+	string s;
+	s = "";
+	if(cvar("g_instagib"))
+		s = strcat(s, ", InstaGib");
+	if(cvar("g_minstagib"))
+		s = strcat(s, ", MinstaGib");
+	if(cvar("g_nixnex"))
+		s = strcat(s, ", NixNex");
+	if(cvar("g_rocketarena"))
+		s = strcat(s, ", RL arena");
+	if(cvar("sv_gravity") < 800)
+		s = strcat(s, ", Low gravity");
+	if(cvar("g_cloaked"))
+		s = strcat(s, ", Cloaked");
+	if(cvar("g_footsteps"))
+		s = strcat(s, ", Steps");
+	if(cvar("g_grappling_hook"))
+		s = strcat(s, ", Hook");
+	if(cvar("g_laserguided_missile"))
+		s = strcat(s, ", LG missiles");
+	if(cvar("g_midair"))
+		s = strcat(s, ", Mid-air");
+	if(cvar("g_vampire"))
+		s = strcat(s, ", Vampire");
+	if(s == "")
+		return "None";
+	else
+		return substring(s, 2, strlen(s) - 2);
+}
+void fillNexuizMutatorsDialog(entity me)
+{
+	entity e, s;
+	me.TR(me);
+		me.TD(me, 1, 2, makeNexuizTextLabel(0, "Game mutators:"));
+	me.TR(me);
+		me.TD(me, 1, 2, e = makeNexuizCheckBox(0, "g_cloaked", "Cloaked"));
+	me.TR(me);
+		me.TD(me, 1, 2, e = makeNexuizCheckBox(0, "g_footsteps", "Foot steps"));
+	me.TR(me);
+		me.TD(me, 1, 2, e = makeNexuizCheckBox(0, "g_grappling_hook", "Grappling hook"));
+	me.TR(me);
+		me.TD(me, 1, 2, e = makeNexuizCheckBox(0, "g_laserguided_missile", "Laser guided missiles"));
+	me.TR(me);
+		me.TD(me, 1, 2, e = makeNexuizCheckBox(0, "g_midair", "Mid-air"));
+	me.TR(me);
+		me.TD(me, 1, 2, e = makeNexuizCheckBox(0, "g_vampire", "Vampire"));
+
+	me.gotoRC(me, 0, 2); me.setFirstColumn(me, me.currentColumn);
+		me.TD(me, 1, 2, makeNexuizTextLabel(0, "Arena mutators:"));
+	me.TR(me);
+		me.TD(me, 1, 2, e = makeNexuizRadioButton(1, string_null, string_null, "Regular"));
+	me.TR(me);
+		me.TD(me, 1, 2, e = makeNexuizRadioButton(1, "g_instagib", string_null, "InstaGib"));
+	me.TR(me);
+		me.TD(me, 1, 2, e = makeNexuizRadioButton(1, "g_minstagib", string_null, "MinstaGib"));
+	me.TR(me);
+		me.TD(me, 1, 2, e = makeNexuizRadioButton(1, "g_nixnex", string_null, "NixNex"));
+	me.TR(me);
+		me.TDempty(me, 0.2);
+		me.TD(me, 1, 1.8, e = makeNexuizCheckBox(1, "g_nixnex_with_laser", "with laser"));
+			setDependent(e, "g_nixnex", 1, 1);
+	me.TR(me);
+		me.TD(me, 1, 2, e = makeNexuizRadioButton(1, "g_rocketarena", string_null, "Rocket launcher arena"));
+
+	me.gotoRC(me, me.rows - 2, 0);
+		s = makeNexuizSlider(80, 400, 8, "sv_gravity");
+			s.valueDigits = 0;
+			s.valueDisplayMultiplier = 0.125; // show gravity in percent
+		me.TD(me, 1, 1, e = makeNexuizSliderCheckBox(800, 1, s, "Low gravity"));
+			e.savedValue = 200; // good on silvercity
+		me.TD(me, 1, 3, s);
+
+	me.gotoRC(me, me.rows - 1, 0);
+		me.TD(me, 1, me.columns, e = makeNexuizButton("OK", '0 0 0'));
+			e.onClick = Dialog_Close;
+			e.onClickEntity = me;
+}
+
+void closeNexuizMutatorsDialog(entity me)
+{
+	if(me.refilterEntity)
+		me.refilterEntity.refilter(me.refilterEntity);
+	closeDialog(me);
+}
+#endif

Added: trunk/menu/nexuiz/dialog_multiplayer_join.c
===================================================================
--- trunk/menu/nexuiz/dialog_multiplayer_join.c	                        (rev 0)
+++ trunk/menu/nexuiz/dialog_multiplayer_join.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,60 @@
+#ifdef INTERFACE
+CLASS(NexuizServerListTab) EXTENDS(NexuizTab)
+	METHOD(NexuizServerListTab, fill, void(entity))
+	ATTRIB(NexuizServerListTab, title, string, "Join")
+	ATTRIB(NexuizServerListTab, intendedWidth, float, 0.9)
+	ATTRIB(NexuizServerListTab, rows, float, 22)
+	ATTRIB(NexuizServerListTab, columns, float, 6.5)
+ENDCLASS(NexuizServerListTab)
+entity makeNexuizServerListTab();
+#endif
+
+#ifdef IMPLEMENTATION
+
+entity makeNexuizServerListTab()
+{
+	entity me;
+	me = spawnNexuizServerListTab();
+	me.configureDialog(me);
+	return me;
+}
+void fillNexuizServerListTab(entity me)
+{
+	entity e, slist, clearbtn;
+
+	slist  = makeNexuizServerList();
+
+	me.TR(me);
+		me.TD(me, 1, 0.5, e = makeNexuizTextLabel(0, "Filter:"));
+		me.TD(me, 1, 0.5, clearbtn = makeNexuizButton("Clear", '0 0 0'));
+			clearbtn.onClick = InputBox_Clear_Click;
+		me.TD(me, 1, me.columns - 2.5, e = makeNexuizInputBox(0, string_null));
+			e.onChange = ServerList_Filter_Change;
+			e.onChangeEntity = slist;
+			clearbtn.onClickEntity = e;
+			slist.controlledTextbox = e;
+		me.TD(me, 1, 0.5, e = makeNexuizCheckBox(0, "menu_slist_showempty", "Empty"));
+			slist.filterShowEmpty = e.checked;
+			e.onClickEntity = slist;
+			e.onClick = ServerList_ShowEmpty_Click;
+		me.TD(me, 1, 0.5, e = makeNexuizCheckBox(0, "menu_slist_showfull", "Full"));
+			slist.filterShowFull = e.checked;
+			e.onClickEntity = slist;
+			e.onClick = ServerList_ShowFull_Click;
+		me.TD(me, 1, 0.5, e = makeNexuizCheckBox(0, "net_slist_pause", "Pause"));
+
+	me.TR(me);
+		me.TD(me, 1, 1, slist.sortButton1 = makeNexuizButton(string_null, '0 0 0'));
+		me.TD(me, 1, 1, slist.sortButton2 = makeNexuizButton(string_null, '0 0 0'));
+		me.TD(me, 1, 1, slist.sortButton3 = makeNexuizButton(string_null, '0 0 0'));
+		me.TD(me, 1, 1, slist.sortButton4 = makeNexuizButton(string_null, '0 0 0'));
+	me.TR(me);
+		me.TD(me, me.rows - 3, me.columns, slist);
+
+	me.gotoRC(me, me.rows - 1, 0);
+		me.TD(me, 1, me.columns, e = makeNexuizButton("Join!", '0 0 0'));
+			e.onClick = ServerList_Connect_Click;
+			e.onClickEntity = slist;
+			slist.connectButton = e;
+}
+#endif

Added: trunk/menu/nexuiz/dialog_multiplayer_playersetup.c
===================================================================
--- trunk/menu/nexuiz/dialog_multiplayer_playersetup.c	                        (rev 0)
+++ trunk/menu/nexuiz/dialog_multiplayer_playersetup.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,144 @@
+#ifdef INTERFACE
+CLASS(NexuizPlayerSettingsTab) EXTENDS(NexuizTab)
+	METHOD(NexuizPlayerSettingsTab, fill, void(entity))
+	METHOD(NexuizPlayerSettingsTab, draw, void(entity))
+	ATTRIB(NexuizPlayerSettingsTab, title, string, "Player Setup")
+	ATTRIB(NexuizPlayerSettingsTab, intendedWidth, float, 0.9)
+	ATTRIB(NexuizPlayerSettingsTab, rows, float, 22)
+	ATTRIB(NexuizPlayerSettingsTab, columns, float, 6.5)
+	ATTRIB(NexuizPlayerSettingsTab, playerNameLabel, entity, NULL)
+	ATTRIB(NexuizPlayerSettingsTab, playerNameLabelAlpha, float, 0)
+ENDCLASS(NexuizPlayerSettingsTab)
+entity makeNexuizPlayerSettingsTab();
+#endif
+
+#ifdef IMPLEMENTATION
+entity makeNexuizPlayerSettingsTab()
+{
+	entity me;
+	me = spawnNexuizPlayerSettingsTab();
+	me.configureDialog(me);
+	return me;
+}
+void drawNexuizPlayerSettingsTab(entity me)
+{
+	if(cvar_string("_cl_name") == "Player")
+		me.playerNameLabel.alpha = ((mod(time * 2, 2) < 1) ? 1 : 0);
+	else
+		me.playerNameLabel.alpha = me.playerNameLabelAlpha;
+	drawContainer(me);
+}
+void fillNexuizPlayerSettingsTab(entity me)
+{
+	entity e, pms, sl;
+	float i, n;
+
+	me.TR(me);
+		me.TD(me, 1, 1, me.playerNameLabel = makeNexuizTextLabel(0, "Player Name:"));
+			me.playerNameLabelAlpha = me.playerNameLabel.alpha;
+		me.TD(me, 1, 2, e = makeNexuizInputBox(1, "_cl_name"));
+			e.forbiddenCharacters = "\r\n\\\""; // don't care, isn't getting saved
+	me.TR(me);
+		me.TDempty(me, 1);
+		me.TD(me, 5, 2, e = makeNexuizCharmap(e));
+	me.TR(me);
+	me.TR(me);
+	me.TR(me);
+	me.TR(me);
+	me.TR(me);
+		me.TD(me, 1, 1, e = makeNexuizTextLabel(0, "Shirt Color:"));
+		n = 16 - !cvar("developer");
+		for(i = 0; i < n; ++i)
+			me.TDNoMargin(me, 1, 2 / n, e = makeNexuizColorButton(1, 0, i), '1 0 0');
+	me.TR(me);
+		me.TD(me, 1, 1, e = makeNexuizTextLabel(0, "Pants Color:"));
+		for(i = 0; i < n; ++i)
+			me.TDNoMargin(me, 1, 2 / n, e = makeNexuizColorButton(2, 1, i), '1 0 0');
+	me.TR(me);
+		pms = makeNexuizPlayerModelSelector();
+		me.TD(me, 1, 0.3, e = makeNexuizButton("<<", '0 0 0'));
+			e.onClick = PlayerModelSelector_Prev_Click;
+			e.onClickEntity = pms;
+		me.TD(me, me.rows - me.currentRow - 1, 2.4, pms);
+		me.TD(me, 1, 0.3, e = makeNexuizButton(">>", '0 0 0'));
+			e.onClick = PlayerModelSelector_Next_Click;
+			e.onClickEntity = pms;
+
+	me.gotoRC(me, 0, 3.5); me.setFirstColumn(me, me.currentColumn);
+		me.TD(me, 1, 1, e = makeNexuizTextLabel(0, "Field of View:"));
+		me.TD(me, 1, 2, e = makeNexuizSlider(60, 130, 1, "fov"));
+	me.TR(me);
+
+		me.TD(me, 1, 1, e = makeNexuizTextLabel(0, "Zoom Factor:"));
+		me.TD(me, 1, 2, e = makeNexuizSlider(2, 16, 0.5, "cl_zoomfactor"));
+	me.TR(me);
+		sl = makeNexuizSlider(1, 8, 0.5, "cl_zoomspeed");
+		me.TD(me, 1, 2.8, e = makeNexuizSliderCheckBox(-1, 1, sl, "Zoom speed:"));
+		me.TD(me, 1, 2, sl);
+	me.TR(me);
+		me.TD(me, 1, 3, e = makeNexuizCheckBox(0, "cl_autoswitch", "Auto switch weapons on pickup"));
+	me.TR(me);
+	me.TR(me);
+		me.TD(me, 1, 1, e = makeNexuizTextLabel(0, "Crosshair:"));
+		n = 10;
+		for(i = 1; i <= n; ++i)
+			me.TDNoMargin(me, 1, 2 / n, e = makeNexuizCrosshairButton(3, i), '0 0 0');
+	me.TR(me);
+		me.TD(me, 1, 1, e = makeNexuizTextLabel(0, "Crosshair Size:"));
+		me.TD(me, 1, 2, e = makeNexuizSlider(0.40, 2, 0.05, "crosshair_size"));
+	me.TR(me);
+		me.TD(me, 1, 1, e = makeNexuizTextLabel(0, "Crosshair Alpha:"));
+		me.TD(me, 1, 2, e = makeNexuizSlider(0, 1, 0.01, "crosshair_color_alpha"));
+	me.TR(me);
+		me.TD(me, 1, 1, e = makeNexuizTextLabel(0, "Crosshair Red:"));
+		me.TD(me, 1, 2, e = makeNexuizSlider(0, 1, 0.01, "crosshair_color_red"));
+	me.TR(me);
+		me.TD(me, 1, 1, e = makeNexuizTextLabel(0, "Crosshair Green:"));
+		me.TD(me, 1, 2, e = makeNexuizSlider(0, 1, 0.01, "crosshair_color_green"));
+	me.TR(me);
+		me.TD(me, 1, 1, e = makeNexuizTextLabel(0, "Crosshair Blue:"));
+		me.TD(me, 1, 2, e = makeNexuizSlider(0, 1, 0.01, "crosshair_color_blue"));
+	me.TR(me);
+	me.TR(me);
+		me.TD(me, 1, 1, e = makeNexuizTextLabel(0, "HUD size:"));
+		me.TD(me, 1, 2, e = makeNexuizTextSlider("viewsize"));
+			e.addValue(e, "None", "120");
+			e.addValue(e, "Reduced", "110");
+			e.addValue(e, "Full", "100");
+			e.configureNexuizTextSliderValues(e);
+	me.TR(me);
+		me.TDempty(me, 0.2);
+		me.TD(me, 1, 2.8, e = makeNexuizCheckBox(1, "sbar_hudselector", "Use alternate HUD layout"));
+			setDependent(e, "viewsize", 0, 110);
+	me.TR(me);
+	me.TR(me);
+		me.TD(me, 1, 1, e = makeNexuizTextLabel(0, "Show names:"));
+		me.TD(me, 1, 2, e = makeNexuizTextSlider("cl_shownames"));
+			e.addValue(e, "Never", "0");
+			e.addValue(e, "Team games", "1");
+			e.addValue(e, "Always", "2");
+			e.configureNexuizTextSliderValues(e);
+	me.TR(me);
+		me.TD(me, 1, 1, e = makeNexuizTextLabel(0, "Show waypoints:"));
+		me.TD(me, 1, 2, e = makeNexuizTextSlider("cl_hidewaypoints"));
+			e.addValue(e, "Players", "1");
+			e.addValue(e, "All", "0");
+			e.configureNexuizTextSliderValues(e);
+	me.TR(me);
+	me.TR(me);
+		me.TD(me, 1, 1, e = makeNexuizTextLabel(0, "Network speed:"));
+		me.TD(me, 1, 2, e = makeNexuizTextSlider("_cl_rate"));
+			e.addValue(e, "56k", "4000");
+			e.addValue(e, "ISDN", "7000");
+			e.addValue(e, "Slow ADSL", "15000");
+			e.addValue(e, "Fast ADSL", "20000");
+			e.addValue(e, "Broadband", "25000");
+			e.configureNexuizTextSliderValues(e);
+	me.TR(me);
+		me.TD(me, 1, 1, e = makeNexuizTextLabel(0, "Client UDP port:"));
+		me.TD(me, 1, 0.5, e = makeNexuizInputBox(0, "cl_port"));
+
+	me.gotoRC(me, me.rows - 1, 0);
+		me.TD(me, 1, me.columns, makeNexuizCommandButton("Apply immediately", '0 0 0', "color -1 -1;name $_cl_name;sendcvar cl_zoomfactor;sendcvar cl_zoomspeed;sendcvar cl_autoswitch;rate $_cl_rate", COMMANDBUTTON_APPLY));
+}
+#endif

Added: trunk/menu/nexuiz/dialog_news.c
===================================================================
--- trunk/menu/nexuiz/dialog_news.c	                        (rev 0)
+++ trunk/menu/nexuiz/dialog_news.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,20 @@
+#ifdef INTERFACE
+CLASS(NexuizNewsDialog) EXTENDS(NexuizDialog)
+	METHOD(NexuizNewsDialog, fill, void(entity))
+	ATTRIB(NexuizNewsDialog, title, string, "News")
+	ATTRIB(NexuizNewsDialog, color, vector, SKINCOLOR_DIALOG_SETTINGS)
+	ATTRIB(NexuizNewsDialog, intendedWidth, float, 0.96)
+	ATTRIB(NexuizNewsDialog, rows, float, 24)
+	ATTRIB(NexuizNewsDialog, columns, float, 1)
+ENDCLASS(NexuizNewsDialog)
+#endif
+
+#ifdef IMPLEMENTATION
+void fillNexuizNewsDialog(entity me)
+{
+	entity e;
+	me.TR(me);
+		me.TD(me, 24, 1, e = spawnGecko());
+		e.configureBrowser( e, "http://alientrap.org/nexuiz/index.php?module=news" );
+}
+#endif

Added: trunk/menu/nexuiz/dialog_quit.c
===================================================================
--- trunk/menu/nexuiz/dialog_quit.c	                        (rev 0)
+++ trunk/menu/nexuiz/dialog_quit.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,25 @@
+#ifdef INTERFACE
+CLASS(NexuizQuitDialog) EXTENDS(NexuizDialog)
+	METHOD(NexuizQuitDialog, fill, void(entity))
+	ATTRIB(NexuizQuitDialog, title, string, "Quit")
+	ATTRIB(NexuizQuitDialog, color, vector, SKINCOLOR_DIALOG_QUIT)
+	ATTRIB(NexuizQuitDialog, intendedWidth, float, 0.5)
+	ATTRIB(NexuizQuitDialog, rows, float, 3)
+	ATTRIB(NexuizQuitDialog, columns, float, 2)
+ENDCLASS(NexuizQuitDialog)
+#endif
+
+#ifdef IMPLEMENTATION
+void fillNexuizQuitDialog(entity me)
+{
+	entity e;
+	me.TR(me);
+		me.TD(me, 1, 2, makeNexuizTextLabel(0.5, "Are you sure you want to quit?"));
+	me.TR(me);
+	me.TR(me);
+		me.TD(me, 1, 1, e = makeNexuizCommandButton("Yes", '1 0 0', "quit", 0));
+		me.TD(me, 1, 1, e = makeNexuizButton("No", '0 1 0'));
+			e.onClick = Dialog_Close;
+			e.onClickEntity = me;
+}
+#endif

Added: trunk/menu/nexuiz/dialog_settings.c
===================================================================
--- trunk/menu/nexuiz/dialog_settings.c	                        (rev 0)
+++ trunk/menu/nexuiz/dialog_settings.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,26 @@
+#ifdef INTERFACE
+CLASS(NexuizSettingsDialog) EXTENDS(NexuizDialog)
+	METHOD(NexuizSettingsDialog, fill, void(entity))
+	ATTRIB(NexuizSettingsDialog, title, string, "Settings")
+	ATTRIB(NexuizSettingsDialog, color, vector, SKINCOLOR_DIALOG_SETTINGS)
+	ATTRIB(NexuizSettingsDialog, intendedWidth, float, 0.96)
+	ATTRIB(NexuizSettingsDialog, rows, float, 17)
+	ATTRIB(NexuizSettingsDialog, columns, float, 6)
+ENDCLASS(NexuizSettingsDialog)
+#endif
+
+#ifdef IMPLEMENTATION
+void fillNexuizSettingsDialog(entity me)
+{
+	entity mc;
+	mc = makeNexuizTabController(me.rows - 2);
+	me.TR(me);
+		me.TD(me, 1, 1, mc.makeTabButton(mc, "Input",   makeNexuizInputSettingsTab()));
+		me.TD(me, 1, 1, mc.makeTabButton(mc, "Video",   makeNexuizVideoSettingsTab()));
+		me.TD(me, 1, 1, mc.makeTabButton(mc, "Effects", makeNexuizEffectsSettingsTab()));
+		me.TD(me, 1, 1, mc.makeTabButton(mc, "Misc",    makeNexuizMiscSettingsTab()));
+	me.TR(me);
+	me.TR(me);
+		me.TD(me, me.rows - 2, me.columns, mc);
+}
+#endif

Added: trunk/menu/nexuiz/dialog_settings_effects.c
===================================================================
--- trunk/menu/nexuiz/dialog_settings_effects.c	                        (rev 0)
+++ trunk/menu/nexuiz/dialog_settings_effects.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,193 @@
+#ifdef INTERFACE
+CLASS(NexuizEffectsSettingsTab) EXTENDS(NexuizTab)
+	METHOD(NexuizEffectsSettingsTab, fill, void(entity))
+	ATTRIB(NexuizEffectsSettingsTab, title, string, "Effects")
+	ATTRIB(NexuizEffectsSettingsTab, intendedWidth, float, 0.9)
+	ATTRIB(NexuizEffectsSettingsTab, rows, float, 15)
+	ATTRIB(NexuizEffectsSettingsTab, columns, float, 6.5)
+ENDCLASS(NexuizEffectsSettingsTab)
+entity makeNexuizEffectsSettingsTab();
+#endif
+
+#ifdef IMPLEMENTATION
+entity makeNexuizEffectsSettingsTab()
+{
+	entity me;
+	me = spawnNexuizEffectsSettingsTab();
+	me.configureDialog(me);
+	return me;
+}
+/*
+ * cl_decals 1
+ * cl_nogibs 0
+ * cl_particles_quality 1
+ * cl_particles_snow 1
+ * r_bloom 1
+ * r_coronas 1
+ * r_glsl_deluxemapping 1
+ * r_glsl_offsetmapping 1
+ * r_glsl_offsetmapping_reliefmapping 0
+ * r_hdr 0
+ * r_shadow_usenormalmap 1
+ * r_shadow_gloss 1
+ * r_shadow_realtime_dlight 1
+ * r_shadow_realtime_dlight_shadows 1
+ * r_shadow_realtime_world 1
+ * r_shadow_realtime_world_shadows 1
+ * r_depthfirst 2
+ * r_showsurfaces 0
+ * r_water 1
+ * r_water_resolutionmultiplier 0.5
+ *
+ *
+ * [X] cl_decals
+ * [X] !cl_nogibs
+ * Particles: |--v--|
+ * [X] Bloom [X] HDR
+ * [X] Coronas
+ * [X] Deluxemapping [X] Gloss
+ * [X] Offsetmapping [X] Reliefmapping
+ * [X] dlights [X] normalmaps [X] shadows
+ * [X] rtworld [X] shadows
+ * [X] depth first world [X] depth first models (?????)
+ * [X] OMGLOLWTFBBQ (showsurfaces)
+ * [X] water |----------v----|
+ *
+ *
+ */
+void fillNexuizEffectsSettingsTab(entity me)
+{
+	entity e;
+	float n;
+	me.TR(me);
+		me.TD(me, 1, 1, e = makeNexuizTextLabel(0, "Quality preset:"));
+		n = 6 + !!cvar("developer");
+		if(cvar("developer"))
+			me.TD(me, 1, 5.5 / n, e = makeNexuizCommandButton("OMG!", '1 0 1', "exec omg.cfg", 0));
+		me.TD(me, 1, 5.5 / n, e = makeNexuizCommandButton("Low", '0 0 0', "exec low.cfg", 0));
+		me.TD(me, 1, 5.5 / n, e = makeNexuizCommandButton("Medium", '0 0 0', "exec med.cfg", 0));
+		me.TD(me, 1, 5.5 / n, e = makeNexuizCommandButton("Normal", '0 0 0', "exec normal.cfg", 0));
+		me.TD(me, 1, 5.5 / n, e = makeNexuizCommandButton("High", '0 0 0', "exec high.cfg", 0));
+		me.TD(me, 1, 5.5 / n, e = makeNexuizCommandButton("Ultra", '0 0 0', "exec ultra.cfg", 0));
+		me.TD(me, 1, 5.5 / n, e = makeNexuizCommandButton("Ultimate", '0 0 0', "exec ultimate.cfg", 0));
+
+	me.TR(me);
+	me.TR(me);
+		me.TD(me, 1, 1, e = makeNexuizTextLabel(0, "Texture quality:"));
+		me.TD(me, 1, 2, e = makeNexuizTextSlider("gl_picmip"));
+			if(cvar("developer"))
+				e.addValue(e, "Leet", "1337");
+			e.addValue(e, "Lowest", "4");
+			e.addValue(e, "Low", "3");
+			e.addValue(e, "Normal", "2");
+			e.addValue(e, "Good", "1");
+			e.addValue(e, "Best", "0");
+			e.configureNexuizTextSliderValues(e);
+	me.TR(me);
+		me.TDempty(me, 0.2);
+		me.TD(me, 1, 2.8, e = makeNexuizCheckBox(1, "r_picmipworld", "Reduce model texture quality only"));
+			setDependent(e, "gl_picmip", 0.5, -0.5);
+
+	me.TR(me);
+
+	me.TR(me);
+		me.TD(me, 1, 1, e = makeNexuizTextLabel(0, "Particle quality:"));
+		me.TD(me, 1, 2, e = makeNexuizSlider(0.1, 1.0, 0.05, "cl_particles_quality"));
+	me.TR(me);
+		me.TD(me, 1, 3, e = makeNexuizCheckBox(0, "cl_decals", "Decals"));
+	me.TR(me);
+		me.TD(me, 1, 1, e = makeNexuizTextLabel(0, "Gibs:"));
+		me.TD(me, 1, 2, e = makeNexuizTextSlider("cl_nogibs"));
+			if(cvar("developer"))
+				e.addValue(e, "German", "1");
+			e.addValue(e, "Few", "0.75");
+			e.addValue(e, "Many", "0.5");
+			e.addValue(e, "Lots", "1");
+			e.configureNexuizTextSliderValues(e);
+
+	me.TR(me);
+
+	me.TR(me);
+		me.TD(me, 1, 3, e = makeNexuizCheckBox(0, "r_coronas", "Coronas"));
+	me.TR(me);
+		me.TD(me, 1, 1, e = makeNexuizCheckBox(0, "r_bloom", "Bloom"));
+			setDependent(e, "r_hdr", 0, 0);
+		me.TD(me, 1, 2, e = makeNexuizCheckBox(0, "r_hdr", "High Dynamic Range (HDR)"));
+
+	me.TR(me);
+
+	me.TR(me);
+		me.TD(me, 1, 3, e = makeNexuizCheckBox(0, "showfps", "Show frames per second"));
+
+	me.gotoRC(me, 2, 3.5); me.setFirstColumn(me, me.currentColumn);
+		me.TD(me, 1, 1, e = makeNexuizTextLabel(0, "Anisotropy:"));
+		me.TD(me, 1, 2, e = makeNexuizTextSlider("gl_texture_anisotropy"));
+			e.addValue(e, "1x", "1");
+			e.addValue(e, "2x", "2");
+			e.addValue(e, "4x", "4");
+			e.addValue(e, "8x", "8");
+			e.addValue(e, "16x", "16");
+			e.configureNexuizTextSliderValues(e);
+	me.TR(me);
+		me.TD(me, 1, 1.5, e = makeNexuizCheckBox(0, "r_glsl_deluxemapping", "Deluxe mapping"));
+			setDependent(e, "r_glsl", 1, 1);
+		me.TD(me, 1, 1.5, e = makeNexuizCheckBox(0, "r_shadow_gloss", "Gloss"));
+			setDependentAND(e, "r_glsl", 1, 1, "r_glsl_deluxemapping", 1, 1);
+
+	me.TR(me);
+
+	me.TR(me);
+		me.TD(me, 1, 2, e = makeNexuizCheckBox(0, "r_shadow_realtime_dlight", "Realtime dynamic lighting"));
+		me.TD(me, 1, 1, e = makeNexuizCheckBox(0, "r_shadow_realtime_dlight_shadows", "Shadows"));
+			setDependent(e, "r_shadow_realtime_dlight", 1, 1);
+
+	me.TR(me);
+		me.TD(me, 1, 2, e = makeNexuizCheckBox(0, "r_shadow_realtime_world", "Realtime world lighting"));
+		me.TD(me, 1, 1, e = makeNexuizCheckBox(0, "r_shadow_realtime_world_shadows", "Shadows"));
+			setDependent(e, "r_shadow_realtime_world", 1, 1);
+
+	me.TR(me);
+		me.TDempty(me, 0.2);
+		me.TD(me, 1, 2.8, e = makeNexuizCheckBox(0, "r_shadow_usenormalmap", "Use normal maps"));
+			setDependentOR(e, "r_shadow_realtime_dlight", 1, 1, "r_shadow_realtime_world", 1, 1);
+	
+	me.TR(me);
+
+	me.TR(me);
+		me.TD(me, 1, 1.5, e = makeNexuizCheckBox(0, "r_glsl_offsetmapping", "Offset mapping"));
+			setDependent(e, "r_glsl", 1, 1);
+		me.TD(me, 1, 1.5, e = makeNexuizCheckBox(0, "r_glsl_offsetmapping_reliefmapping", "Relief mapping"));
+			setDependentAND(e, "r_glsl", 1, 1, "r_glsl_offsetmapping", 1, 1);
+	me.TR(me);
+		me.TD(me, 1, 1, e = makeNexuizCheckBox(0, "r_water", "Reflections"));
+			setDependent(e, "r_glsl", 1, 1);
+		me.TD(me, 1, 2, e = makeNexuizTextSlider("r_water_resolutionmultiplier"));
+			e.addValue(e, "Blurred", "0.25");
+			e.addValue(e, "Good", "0.5");
+			e.addValue(e, "Sharp", "1");
+			e.addValue(e, "Insane", "2");
+			e.configureNexuizTextSliderValues(e);
+			setDependentAND(e, "r_glsl", 1, 1, "r_water", 1, 1);
+	
+	me.TR(me);
+
+	me.TR(me);
+		me.TD(me, 1, 1, e = makeNexuizTextLabel(0, "Damage view kick:"));
+		me.TD(me, 1, 2, e = makeNexuizSlider(0, 0.5, 0.05, "v_kicktime"));
+
+	me.TR(me);
+
+	me.TR(me);
+		if(cvar("developer"))
+			me.TD(me, 1, 3, e = makeNexuizCheckBox(0, "r_showsurfaces", "Show surfaces"));
+
+	me.gotoRC(me, me.rows - 1, 0);
+		me.TD(me, 1, me.columns, makeNexuizCommandButton("Apply immediately", '0 0 0', "sendcvar cl_nogibs; r_restart", COMMANDBUTTON_APPLY));
+}
+/*
+ * [X] depth first world [X] depth first models (?????)
+ * [X] OMGLOLWTFBBQ (showsurfaces)
+ *
+ *
+ */
+#endif

Added: trunk/menu/nexuiz/dialog_settings_input.c
===================================================================
--- trunk/menu/nexuiz/dialog_settings_input.c	                        (rev 0)
+++ trunk/menu/nexuiz/dialog_settings_input.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,67 @@
+#ifdef INTERFACE
+CLASS(NexuizInputSettingsTab) EXTENDS(NexuizTab)
+	METHOD(NexuizInputSettingsTab, fill, void(entity))
+	ATTRIB(NexuizInputSettingsTab, title, string, "Input")
+	ATTRIB(NexuizInputSettingsTab, intendedWidth, float, 0.9)
+	ATTRIB(NexuizInputSettingsTab, rows, float, 15)
+	ATTRIB(NexuizInputSettingsTab, columns, float, 6.5)
+ENDCLASS(NexuizInputSettingsTab)
+entity makeNexuizInputSettingsTab();
+#endif
+
+#ifdef IMPLEMENTATION
+entity makeNexuizInputSettingsTab()
+{
+	entity me;
+	me = spawnNexuizInputSettingsTab();
+	me.configureDialog(me);
+	return me;
+}
+void fillNexuizInputSettingsTab(entity me)
+{
+	entity e;
+	entity kb;
+
+	me.TR(me);
+		me.TD(me, 1, 3, e = makeNexuizTextLabel(0, "Key bindings:"));
+	me.TR(me);
+		me.TD(me, me.rows - 2, 3, kb = makeNexuizKeyBinder());
+	me.gotoRC(me, me.rows - 1, 0);
+	me.TR(me);
+		me.TD(me, 1, 1, e = makeNexuizButton("Change key...", '0 0 0'));
+			e.onClick = KeyBinder_Bind_Change;
+			e.onClickEntity = kb;
+			kb.keyGrabButton = e;
+		me.TD(me, 1, 1, e = makeNexuizButton("Edit...", '0 0 0'));
+			e.onClick = KeyBinder_Bind_Edit;
+			e.onClickEntity = kb;
+			kb.userbindEditButton = e;
+			kb.userbindEditDialog = main.userbindEditDialog;
+			main.userbindEditDialog.keybindBox = kb;
+		me.TD(me, 1, 1, e = makeNexuizButton("Clear", '0 0 0'));
+			e.onClick = KeyBinder_Bind_Clear;
+			e.onClickEntity = kb;
+
+	me.gotoRC(me, 0, 3.5); me.setFirstColumn(me, me.currentColumn);
+		me.TD(me, 1, 1, e = makeNexuizTextLabel(0, "Sensitivity:"));
+		me.TD(me, 1, 2, e = makeNexuizSlider(1, 32, 0.2, "sensitivity"));
+	me.TR(me);
+		me.TD(me, 1, 3, e = makeNexuizCheckBox(0, "m_filter", "Mouse filter"));
+	me.TR(me);
+		me.TD(me, 1, 3, e = makeNexuizCheckBox(1.022, "m_pitch", "Invert mouse"));
+	me.TR(me);
+		if(cvar_defstring("joy_enable") != "")
+			me.TD(me, 1, 3, e = makeNexuizCheckBox(0, "joy_enable", "Use joystick input"));
+		else if(cvar_defstring("joystick") != "")
+			me.TD(me, 1, 3, e = makeNexuizCheckBox(0, "joystick", "Use joystick input"));
+	me.TR(me);
+		me.TD(me, 1, 3, e = makeNexuizCheckBox(0, "cl_movement", "Client-side movement prediction"));
+	me.TR(me);
+		me.TD(me, 1, 3, e = makeNexuizCheckBox(0, "con_closeontoggleconsole", "\"enter console\" also closes"));
+	me.TR(me);
+		if(cvar_defstring("vid_dgamouse") != "")
+			me.TD(me, 1, 3, e = makeNexuizCheckBox(0, "vid_dgamouse", "Turn off OS mouse acceleration"));
+		else if(cvar_defstring("apple_mouse_noaccel") != "")
+			me.TD(me, 1, 3, e = makeNexuizCheckBox(0, "vid_dgamouse", "Turn off OS mouse acceleration"));
+}
+#endif

Added: trunk/menu/nexuiz/dialog_settings_input_userbind.c
===================================================================
--- trunk/menu/nexuiz/dialog_settings_input_userbind.c	                        (rev 0)
+++ trunk/menu/nexuiz/dialog_settings_input_userbind.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,55 @@
+#ifdef INTERFACE
+CLASS(NexuizUserbindEditDialog) EXTENDS(NexuizDialog)
+	METHOD(NexuizUserbindEditDialog, loadUserBind, void(entity, string, string, string))
+	METHOD(NexuizUserbindEditDialog, fill, void(entity))
+	ATTRIB(NexuizUserbindEditDialog, title, string, "User defined key bind")
+	ATTRIB(NexuizUserbindEditDialog, color, vector, SKINCOLOR_DIALOG_USERBIND)
+	ATTRIB(NexuizUserbindEditDialog, intendedWidth, float, 0.7)
+	ATTRIB(NexuizUserbindEditDialog, rows, float, 4)
+	ATTRIB(NexuizUserbindEditDialog, columns, float, 3)
+	ATTRIB(NexuizUserbindEditDialog, keybindBox, entity, NULL)
+
+	ATTRIB(NexuizUserbindEditDialog, nameBox, entity, NULL)
+	ATTRIB(NexuizUserbindEditDialog, commandPressBox, entity, NULL)
+	ATTRIB(NexuizUserbindEditDialog, commandReleaseBox, entity, NULL)
+ENDCLASS(NexuizUserbindEditDialog)
+#endif
+
+#ifdef IMPLEMENTATION
+void NexuizUserbindEditDialog_Save(entity btn, entity me)
+{
+	me.keybindBox.editUserbind(me.keybindBox, me.nameBox.text, me.commandPressBox.text, me.commandReleaseBox.text);
+	Dialog_Close(btn, me);
+}
+
+void loadUserBindNexuizUserbindEditDialog(entity me, string theName, string theCommandPress, string theCommandRelease)
+{
+	me.nameBox.setText(me.nameBox, theName);
+		me.nameBox.keyDown(me.nameBox, K_END, 0, 0);
+	me.commandPressBox.setText(me.commandPressBox, theCommandPress);
+		me.nameBox.keyDown(me.commandPressBox, K_END, 0, 0);
+	me.commandReleaseBox.setText(me.commandReleaseBox, theCommandRelease);
+		me.nameBox.keyDown(me.commandReleaseBox, K_END, 0, 0);
+}
+
+void fillNexuizUserbindEditDialog(entity me)
+{
+	entity e;
+	me.TR(me);
+		me.TD(me, 1, 1, e = makeNexuizTextLabel(0, "Name:"));
+		me.TD(me, 1, me.columns - 1, me.nameBox = makeNexuizInputBox(0, string_null));
+	me.TR(me);
+		me.TD(me, 1, 1, e = makeNexuizTextLabel(0, "Command when pressed:"));
+		me.TD(me, 1, me.columns - 1, me.commandPressBox = makeNexuizInputBox(0, string_null));
+	me.TR(me);
+		me.TD(me, 1, 1, e = makeNexuizTextLabel(0, "Command when released:"));
+		me.TD(me, 1, me.columns - 1, me.commandReleaseBox = makeNexuizInputBox(0, string_null));
+	me.TR(me);
+		me.TD(me, 1, me.columns / 2, e = makeNexuizButton("Save", '0 0 0'));
+			e.onClick = NexuizUserbindEditDialog_Save;
+			e.onClickEntity = me;
+		me.TD(me, 1, me.columns / 2, e = makeNexuizButton("Cancel", '0 0 0'));
+			e.onClick = Dialog_Close;
+			e.onClickEntity = me;
+}
+#endif

Added: trunk/menu/nexuiz/dialog_settings_misc.c
===================================================================
--- trunk/menu/nexuiz/dialog_settings_misc.c	                        (rev 0)
+++ trunk/menu/nexuiz/dialog_settings_misc.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,88 @@
+#ifdef INTERFACE
+CLASS(NexuizMiscSettingsTab) EXTENDS(NexuizTab)
+	METHOD(NexuizMiscSettingsTab, fill, void(entity))
+	ATTRIB(NexuizMiscSettingsTab, title, string, "Misc")
+	ATTRIB(NexuizMiscSettingsTab, intendedWidth, float, 0.9)
+	ATTRIB(NexuizMiscSettingsTab, rows, float, 15)
+	ATTRIB(NexuizMiscSettingsTab, columns, float, 6.5)
+ENDCLASS(NexuizMiscSettingsTab)
+entity makeNexuizMiscSettingsTab();
+#endif
+
+#ifdef IMPLEMENTATION
+entity makeNexuizMiscSettingsTab()
+{
+	entity me;
+	me = spawnNexuizMiscSettingsTab();
+	me.configureDialog(me);
+	return me;
+}
+entity makeNexuizDemoListbox()
+{
+	entity me;
+	me = spawnListBox();
+	me.configureListBox(me, 16, 0.1);
+	return me;
+}
+void fillNexuizMiscSettingsTab(entity me)
+{
+	entity e, s;
+
+	me.TR(me);
+		s = makeNexuizDecibelsSlider(-20, 0, 0.5, "bgmvolume");
+		me.TD(me, 1, 1, e = makeNexuizSliderCheckBox(-1000000, 1, s, "Music:"));
+		me.TD(me, 1, 2, s);
+	me.TR(me);
+		s = makeNexuizDecibelsSlider(-20, 0, 0.5, "volume");
+		me.TD(me, 1, 1, e = makeNexuizSliderCheckBox(-1000000, 1, s, "Game:"));
+		me.TD(me, 1, 2, s);
+	me.TR(me);
+		s = makeNexuizDecibelsSlider(-20, 0, 0.5, "snd_staticvolume");
+		me.TD(me, 1, 1, e = makeNexuizSliderCheckBox(-1000000, 1, s, "Ambient:"));
+		me.TD(me, 1, 2, s);
+	me.TR(me);
+	me.TR(me);
+		me.TD(me, 1, 1, e = makeNexuizTextLabel(0, "Frequency:"));
+		me.TD(me, 1, 2, e = makeNexuizTextSlider("snd_speed"));
+			e.addValue(e, "8 kHz", "8000");
+			e.addValue(e, "11.025 kHz", "11025");
+			e.addValue(e, "16 kHz", "16000");
+			e.addValue(e, "22.05 kHz", "22050");
+			e.addValue(e, "24 kHz", "24000");
+			e.addValue(e, "32 kHz", "32000");
+			e.addValue(e, "44.1 kHz", "44100");
+			e.addValue(e, "48 kHz", "48000");
+			e.configureNexuizTextSliderValues(e);
+	me.TR(me);
+		me.TD(me, 1, 1, e = makeNexuizTextLabel(0, "Channels:"));
+		me.TD(me, 1, 2, e = makeNexuizSlider(1, 8, 1, "snd_channels"));
+	me.TR(me);
+		me.TDempty(me, 0.2);
+		me.TD(me, 1, 2.8, e = makeNexuizCheckBox(0, "snd_swapstereo", "Swap Stereo"));
+
+	me.gotoRC(me, 0, 3.5); me.setFirstColumn(me, me.currentColumn);
+		me.TD(me, 1, 3, e = makeNexuizCheckBox(0, "cl_autodemo", "Demo recording"));
+	me.TR(me);
+	me.TR(me);
+		me.TD(me, 1, 3, e = makeNexuizCheckBox(0, "showtime", "Show current time"));
+	me.TR(me);
+		me.TD(me, 1, 3, e = makeNexuizCheckBox(0, "showdate", "Show current date"));
+	me.TR(me);
+		me.TD(me, 1, 3, e = makeNexuizCheckBox(0, "showfps", "Show frames per second"));
+	me.TR(me);
+		me.TD(me, 1, 3, e = makeNexuizTextLabel(0, "Speedmeter:"));
+	me.TR(me);
+		me.TDempty(me, 0.2);
+		me.TD(me, 1, 2.8/3, e = makeNexuizRadioButton(1, "showspeed", "0", "Off"));
+		me.TD(me, 1, 2.8/3, e = makeNexuizRadioButton(1, "showspeed", "1", "in/s"));
+		me.TD(me, 1, 2.8/3, e = makeNexuizRadioButton(1, "showspeed", "2", "m/s"));
+	me.TR(me);
+		me.TDempty(me, 0.2);
+		me.TD(me, 1, 2.8/3, e = makeNexuizRadioButton(1, "showspeed", "3", "km/h"));
+		me.TD(me, 1, 2.8/3, e = makeNexuizRadioButton(1, "showspeed", "4", "mph"));
+		me.TD(me, 1, 2.8/3, e = makeNexuizRadioButton(1, "showspeed", "5", "knots"));
+
+	me.gotoRC(me, me.rows - 1, 0);
+		me.TD(me, 1, me.columns, makeNexuizCommandButton("Apply immediately", '0 0 0', "snd_restart", COMMANDBUTTON_APPLY));
+}
+#endif

Added: trunk/menu/nexuiz/dialog_settings_video.c
===================================================================
--- trunk/menu/nexuiz/dialog_settings_video.c	                        (rev 0)
+++ trunk/menu/nexuiz/dialog_settings_video.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,94 @@
+#ifdef INTERFACE
+CLASS(NexuizVideoSettingsTab) EXTENDS(NexuizTab)
+	METHOD(NexuizVideoSettingsTab, fill, void(entity))
+	ATTRIB(NexuizVideoSettingsTab, title, string, "Video")
+	ATTRIB(NexuizVideoSettingsTab, intendedWidth, float, 0.9)
+	ATTRIB(NexuizVideoSettingsTab, rows, float, 15)
+	ATTRIB(NexuizVideoSettingsTab, columns, float, 6.5)
+ENDCLASS(NexuizVideoSettingsTab)
+entity makeNexuizVideoSettingsTab();
+#endif
+
+#ifdef IMPLEMENTATION
+entity makeNexuizVideoSettingsTab()
+{
+	entity me;
+	me = spawnNexuizVideoSettingsTab();
+	me.configureDialog(me);
+	return me;
+}
+void fillNexuizVideoSettingsTab(entity me)
+{
+	entity e;
+
+	me.TR(me);
+		me.TD(me, 1, 1, e = makeNexuizTextLabel(0, "Resolution:"));
+		me.TD(me, 1, 2, e = makeNexuizResolutionSlider());
+	me.TR(me);
+		me.TD(me, 1, 1, e = makeNexuizTextLabel(0, "Color depth:"));
+		me.TD(me, 1, 2, e = makeNexuizSlider(16, 32, 16, "vid_bitsperpixel"));
+	me.TR(me);
+		me.TD(me, 1, 1, e = makeNexuizCheckBox(0, "vid_fullscreen", "Full screen"));
+		me.TD(me, 1, 2, e = makeNexuizCheckBox(0, "vid_vsync", "Vertical synchronization"));
+
+	me.TR(me);
+
+	me.TR(me);
+		me.TD(me, 1, 3, e = makeNexuizCheckBox(0, "r_glsl", "Use OpenGL 2.0 shaders (GLSL)"));
+	me.TR(me);
+		me.TD(me, 1, 3, e = makeNexuizCheckBox(0, "gl_vbo", "Use Vertex Buffer Objects (VBO)"));
+	me.TR(me);
+		me.TD(me, 1, 1.5, e = makeNexuizTextLabel(0, "Texture compression"));
+		me.TD(me, 1, 0.5, e = makeNexuizRadioButton(1, "gl_texturecompression", "0", "None"));
+		me.TD(me, 1, 0.5, e = makeNexuizRadioButton(1, "gl_texturecompression", "1", "Fast"));
+		me.TD(me, 1, 0.5, e = makeNexuizRadioButton(1, "gl_texturecompression", "2", "Good"));
+
+	me.TR(me);
+
+	me.TR(me);
+		me.TD(me, 1, 1, e = makeNexuizTextLabel(0, "Depth first:"));
+		me.TD(me, 1, 2, e = makeNexuizTextSlider("r_depthfirst"));
+			e.addValue(e, "Disabled", "0");
+			e.addValue(e, "World", "1");
+			e.addValue(e, "All", "2");
+			e.configureNexuizTextSliderValues(e);
+
+	me.TR(me);
+		if(cvar_defstring("apple_multithreadedgl") != "") // FIXME can this check against string_null too?
+			me.TD(me, 1, 3, e = makeNexuizCheckBox(1, "apple_multithreadedgl", "Disable multithreaded OpenGL"));
+
+	me.TR(me);
+		me.TD(me, 1, 3, e = makeNexuizCheckBox(0, "gl_finish", "Wait for GPU to finish each frame"));
+
+	me.TR(me);
+
+	me.TR(me);
+		me.TD(me, 1, 3, e = makeNexuizCheckBox(0, "showfps", "Show frames per second"));
+
+	me.gotoRC(me, 0, 3.5); me.setFirstColumn(me, me.currentColumn);
+		me.TD(me, 1, 1, e = makeNexuizTextLabel(0, "Brightness:"));
+		me.TD(me, 1, 2, e = makeNexuizSlider(0.0, 0.5, 0.02, "v_brightness"));
+	me.TR(me);
+		me.TD(me, 1, 1, e = makeNexuizTextLabel(0, "Contrast:"));
+		me.TD(me, 1, 2, e = makeNexuizSlider(1.0, 3.0, 0.05, "v_contrast"));
+	me.TR(me);
+		me.TD(me, 1, 1, e = makeNexuizTextLabel(0, "Gamma:"));
+		me.TD(me, 1, 2, e = makeNexuizSlider(0.5, 2.0, 0.05, "v_gamma"));
+	me.TR(me);
+		me.TD(me, 1, 1, e = makeNexuizTextLabel(0, "Contrast boost:"));
+		me.TD(me, 1, 2, e = makeNexuizSlider(1.0, 5.0, 0.1, "v_contrastboost"));
+	me.TR(me);
+		me.TD(me, 1, 3, e = makeNexuizCheckBox(0, "v_glslgamma", "Use GLSL to handle color control"));
+			setDependent(e, "r_glsl", 1, 1);
+	me.TR(me);
+	me.TR(me);
+		me.TD(me, 1, 1, e = makeNexuizTextLabel(0, "Ambient lighting:"));
+		me.TD(me, 1, 2, e = makeNexuizSlider(0, 20.0, 1.0, "r_ambient"));
+	me.TR(me);
+		me.TD(me, 1, 1, e = makeNexuizTextLabel(0, "Scene brightness:"));
+		me.TD(me, 1, 2, e = makeNexuizSlider(0.5, 2.0, 0.05, "r_hdr_scenebrightness"));
+
+	me.gotoRC(me, me.rows - 1, 0);
+		me.TD(me, 1, me.columns, makeNexuizCommandButton("Apply immediately", '0 0 0', "vid_restart", COMMANDBUTTON_APPLY));
+}
+#endif

Added: trunk/menu/nexuiz/dialog_singleplayer.c
===================================================================
--- trunk/menu/nexuiz/dialog_singleplayer.c	                        (rev 0)
+++ trunk/menu/nexuiz/dialog_singleplayer.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,71 @@
+#ifdef INTERFACE
+CLASS(NexuizSingleplayerDialog) EXTENDS(NexuizDialog)
+	METHOD(NexuizSingleplayerDialog, fill, void(entity))
+	ATTRIB(NexuizSingleplayerDialog, title, string, "Singleplayer")
+	ATTRIB(NexuizSingleplayerDialog, color, vector, SKINCOLOR_DIALOG_SINGLEPLAYER)
+	ATTRIB(NexuizSingleplayerDialog, intendedWidth, float, 0.80)
+	ATTRIB(NexuizSingleplayerDialog, rows, float, 24)
+	ATTRIB(NexuizSingleplayerDialog, columns, float, 5)
+	ATTRIB(NexuizSingleplayerDialog, campaignBox, entity, NULL)
+ENDCLASS(NexuizSingleplayerDialog)
+#endif
+
+#ifdef IMPLEMENTATION
+
+void InstantAction_LoadMap(entity btn, entity dummy)
+{
+	float glob, i, n, fh;
+	string s;
+	glob = search_begin("maps/*.instantaction", TRUE, TRUE);
+	if(glob < 0)
+		return;
+	i = ceil(random() * search_getsize(glob)) - 1;
+	fh = fopen(search_getfilename(glob, i), FILE_READ);
+	search_end(glob);
+	if(fh < 0)
+		return;
+	while((s = fgets(fh)))
+	{
+		if(substring(s, 0, 4) == "set ")
+			s = substring(s, 4, strlen(s) - 4);
+		n = tokenize(s);
+		if(argv(0) == "bot_number")
+			cvar_set("bot_number", argv(1));
+		else if(argv(0) == "skill")
+			cvar_set("skill", argv(1));
+		else if(argv(0) == "timelimit")
+			cvar_set("timelimit_override", argv(1));
+		else if(argv(0) == "fraglimit")
+			cvar_set("fraglimit_override", argv(1));
+		else if(argv(0) == "changelevel")
+		{
+			fclose(fh);
+			MapInfo_SwitchGameType(MAPINFO_TYPE_DEATHMATCH);
+			MapInfo_LoadMap(argv(1));
+			cvar_set("lastlevel", "1");
+			return;
+		}
+	}
+	fclose(fh);
+}
+
+void fillNexuizSingleplayerDialog(entity me)
+{
+	entity e;
+
+	me.TR(me);
+		me.TDempty(me, (me.columns - 2) / 2);
+		me.TD(me, 2, 2, e = makeNexuizButton("Instant action!", '0 0 0'));
+			e.onClick = InstantAction_LoadMap;
+			e.onClickEntity = NULL;
+	me.TR(me);
+	me.TR(me);
+	me.TR(me);
+		me.TD(me, me.rows - 5, me.columns, me.campaignBox = makeNexuizCampaignList());
+
+	me.gotoRC(me, me.rows - 1, 0);
+		me.TD(me, 1, me.columns, e = makeNexuizButton("Start!", '0 0 0'));
+			e.onClick = CampaignList_LoadMap;
+			e.onClickEntity = me.campaignBox;
+}
+#endif

Added: trunk/menu/nexuiz/dialog_singleplayer_winner.c
===================================================================
--- trunk/menu/nexuiz/dialog_singleplayer_winner.c	                        (rev 0)
+++ trunk/menu/nexuiz/dialog_singleplayer_winner.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,25 @@
+#ifdef INTERFACE
+CLASS(NexuizWinnerDialog) EXTENDS(NexuizDialog)
+	METHOD(NexuizWinnerDialog, fill, void(entity))
+	ATTRIB(NexuizWinnerDialog, title, string, "Winner")
+	ATTRIB(NexuizWinnerDialog, color, vector, SKINCOLOR_DIALOG_MULTIPLAYER)
+	ATTRIB(NexuizWinnerDialog, intendedWidth, float, 0.64)
+	ATTRIB(NexuizWinnerDialog, rows, float, 12)
+	ATTRIB(NexuizWinnerDialog, columns, float, 3)
+ENDCLASS(NexuizWinnerDialog)
+#endif
+
+#ifdef IMPLEMENTATION
+void fillNexuizWinnerDialog(entity me)
+{
+	entity e;
+
+	me.TR(me);
+		me.TD(me, me.rows - 2, me.columns, e = makeNexuizImage("/gfx/winner", -1));
+
+	me.gotoRC(me, me.rows - 1, 0);
+		me.TD(me, 1, me.columns, e = makeNexuizButton("OK", '0 0 0'));
+			e.onClick = Dialog_Close;
+			e.onClickEntity = me;
+}
+#endif

Added: trunk/menu/nexuiz/dialog_teamselect.c
===================================================================
--- trunk/menu/nexuiz/dialog_teamselect.c	                        (rev 0)
+++ trunk/menu/nexuiz/dialog_teamselect.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,50 @@
+#ifdef INTERFACE
+CLASS(NexuizTeamSelectDialog) EXTENDS(NexuizRootDialog)
+	METHOD(NexuizTeamSelectDialog, fill, void(entity)) // to be overridden by user to fill the dialog with controls
+	METHOD(NexuizTeamSelectDialog, showNotify, void(entity))
+	ATTRIB(NexuizTeamSelectDialog, title, string, "Team Selection") // ;)
+	ATTRIB(NexuizTeamSelectDialog, color, vector, SKINCOLOR_DIALOG_TEAMSELECT)
+	ATTRIB(NexuizTeamSelectDialog, intendedWidth, float, 0.4)
+	ATTRIB(NexuizTeamSelectDialog, rows, float, 5)
+	ATTRIB(NexuizTeamSelectDialog, columns, float, 4)
+	ATTRIB(NexuizTeamSelectDialog, name, string, "TeamSelect")
+	ATTRIB(NexuizTeamSelectDialog, team1, entity, NULL)
+	ATTRIB(NexuizTeamSelectDialog, team2, entity, NULL)
+	ATTRIB(NexuizTeamSelectDialog, team3, entity, NULL)
+	ATTRIB(NexuizTeamSelectDialog, team4, entity, NULL)
+ENDCLASS(NexuizTeamSelectDialog)
+#endif
+
+#ifdef IMPLEMENTATION
+entity makeTeamButton(string theName, vector theColor, string commandtheName)
+{
+	entity b;
+	b = makeNexuizCommandButton(theName, theColor, commandtheName, 1);
+	return b;
+}
+
+void showNotifyNexuizTeamSelectDialog(entity me)
+{
+	float teams, nTeams;
+	teams = cvar("_teams_available");
+	nTeams = 0;
+	me.team1.disabled = !(teams & 1); nTeams += !!(teams & 1);
+	me.team2.disabled = !(teams & 2); nTeams += !!(teams & 2);
+	me.team3.disabled = !(teams & 4); nTeams += !!(teams & 4);
+	me.team4.disabled = !(teams & 8); nTeams += !!(teams & 8);
+}
+
+void fillNexuizTeamSelectDialog(entity me)
+{
+	me.TR(me);
+	me.TR(me);
+	me.TR(me);
+		me.TD(me, 2, 1, me.team1 = makeTeamButton("red", '1 0.5 0.5', "cmd selectteam red; menu_showclassselect;"));
+		me.TD(me, 2, 1, me.team2 = makeTeamButton("blue", '0.5 0.5 1', "cmd selectteam blue; menu_showclassselect;"));
+		me.TD(me, 2, 1, me.team3 = makeTeamButton("yellow", '1 1 0.5', "cmd selectteam yellow; menu_showclassselect;"));
+		me.TD(me, 2, 1, me.team4 = makeTeamButton("pink", '1 0.5 1', "cmd selectteam pink; menu_showclassselect;"));
+	me.TR(me);
+}
+#endif
+
+// click. The C-word so you can grep for it.

Added: trunk/menu/nexuiz/gametypebutton.c
===================================================================
--- trunk/menu/nexuiz/gametypebutton.c	                        (rev 0)
+++ trunk/menu/nexuiz/gametypebutton.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,66 @@
+#ifdef INTERFACE
+CLASS(NexuizGametypeButton) EXTENDS(RadioButton)
+	METHOD(NexuizGametypeButton, configureNexuizGametypeButton, void(entity, float, string, string))
+	METHOD(NexuizGametypeButton, setChecked, void(entity, float))
+	ATTRIB(NexuizGametypeButton, fontSize, float, SKINFONTSIZE_NORMAL)
+	ATTRIB(NexuizGametypeButton, image, string, SKINGFX_BUTTON)
+	ATTRIB(NexuizGametypeButton, color, vector, SKINCOLOR_BUTTON_N)
+	ATTRIB(NexuizGametypeButton, colorC, vector, SKINCOLOR_BUTTON_C)
+	ATTRIB(NexuizGametypeButton, colorF, vector, SKINCOLOR_BUTTON_F)
+	ATTRIB(NexuizGametypeButton, colorD, vector, SKINCOLOR_BUTTON_D)
+	ATTRIB(NexuizGametypeButton, srcMulti, float, 1)
+	ATTRIB(NexuizGametypeButton, useDownAsChecked, float, 1)
+
+	ATTRIB(NexuizGametypeButton, cvarName, string, string_null)
+	METHOD(NexuizGametypeButton, loadCvars, void(entity))
+	METHOD(NexuizGametypeButton, saveCvars, void(entity))
+
+	ATTRIB(NexuizGametypeButton, alpha, float, SKINALPHA_TEXT)
+	ATTRIB(NexuizGametypeButton, disabledAlpha, float, SKINALPHA_DISABLED)
+ENDCLASS(NexuizGametypeButton)
+entity makeNexuizGametypeButton(float, string, string);
+#endif
+
+#ifdef IMPLEMENTATION
+void GameTypeButton_Click(entity me, entity other);
+entity makeNexuizGametypeButton(float theGroup, string theCvar, string theText)
+{
+	entity me;
+	me = spawnNexuizGametypeButton();
+	me.configureNexuizGametypeButton(me, theGroup, theCvar, theText);
+	return me;
+}
+void configureNexuizGametypeButtonNexuizGametypeButton(entity me, float theGroup, string theCvar, string theText)
+{
+	if(theCvar)
+	{
+		me.cvarName = theCvar;
+		me.loadCvars(me);
+	}
+	me.configureRadioButton(me, theText, me.fontSize, me.image, theGroup, 0);
+	me.align = 0.5;
+	me.onClick = GameTypeButton_Click;
+	me.onClickEntity = NULL;
+}
+void setCheckedNexuizGametypeButton(entity me, float val)
+{
+	if(val != me.checked)
+	{
+		me.checked = val;
+		me.saveCvars(me);
+	}
+}
+void loadCvarsNexuizGametypeButton(entity me)
+{
+	me.checked = cvar(me.cvarName);
+}
+void saveCvarsNexuizGametypeButton(entity me)
+{
+	cvar_set(me.cvarName, ftos(me.checked));
+}
+void GameTypeButton_Click(entity me, entity other)
+{
+	RadioButton_Click(me, other);
+	me.parent.gameTypeChangeNotify(me.parent);
+}
+#endif

Added: trunk/menu/nexuiz/image.c
===================================================================
--- trunk/menu/nexuiz/image.c	                        (rev 0)
+++ trunk/menu/nexuiz/image.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,28 @@
+#ifdef INTERFACE
+CLASS(NexuizImage) EXTENDS(Image)
+	METHOD(NexuizImage, configureNexuizImage, void(entity, string, float))
+ENDCLASS(NexuizImage)
+entity makeNexuizImage(string theImage, float theAspect);
+#endif
+
+#ifdef IMPLEMENTATION
+entity makeNexuizImage(string theImage, float theAspect)
+{
+	entity me;
+	me = spawnNexuizImage();
+	me.configureNexuizImage(me, theImage, theAspect);
+	return me;
+}
+void configureNexuizImageNexuizImage(entity me, string theImage, float theAspect)
+{
+	me.configureImage(me, theImage);
+	if(theAspect < 0) // use image aspect
+	{
+		vector sz;
+		sz = draw_PictureSize(theImage);
+		me.forcedAspect = sz_x / sz_y;
+	}
+	else
+		me.forcedAspect = theAspect;
+}
+#endif

Added: trunk/menu/nexuiz/inputbox.c
===================================================================
--- trunk/menu/nexuiz/inputbox.c	                        (rev 0)
+++ trunk/menu/nexuiz/inputbox.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,66 @@
+#ifdef INTERFACE
+CLASS(NexuizInputBox) EXTENDS(InputBox)
+	METHOD(NexuizInputBox, configureNexuizInputBox, void(entity, float, string))
+	METHOD(NexuizInputBox, focusLeave, void(entity))
+	METHOD(NexuizInputBox, setText, void(entity, string))
+	ATTRIB(NexuizInputBox, fontSize, float, SKINFONTSIZE_NORMAL)
+	ATTRIB(NexuizInputBox, image, string, SKINGFX_INPUTBOX)
+	ATTRIB(NexuizInputBox, onChange, void(entity, entity), SUB_Null)
+	ATTRIB(NexuizInputBox, onChangeEntity, entity, NULL)
+	ATTRIB(NexuizInputBox, keepspaceLeft, float, SKINMARGIN_INPUTBOX)
+	ATTRIB(NexuizInputBox, keepspaceRight, float, SKINMARGIN_INPUTBOX)
+	ATTRIB(NexuizInputBox, color, vector, SKINCOLOR_INPUTBOX_N)
+	ATTRIB(NexuizInputBox, colorF, vector, SKINCOLOR_INPUTBOX_F)
+
+	ATTRIB(NexuizInputBox, alpha, float, SKINALPHA_TEXT)
+
+	ATTRIB(NexuizInputBox, cvarName, string, string_null)
+	METHOD(NexuizInputBox, loadCvars, void(entity))
+	METHOD(NexuizInputBox, saveCvars, void(entity))
+ENDCLASS(NexuizInputBox)
+entity makeNexuizInputBox(float, string);
+#endif
+
+#ifdef IMPLEMENTATION
+entity makeNexuizInputBox(float doEditColorCodes, string theCvar)
+{
+	entity me;
+	me = spawnNexuizInputBox();
+	me.configureNexuizInputBox(me, doEditColorCodes, theCvar);
+	return me;
+}
+void configureNexuizInputBoxNexuizInputBox(entity me, float doEditColorCodes, string theCvar)
+{
+	me.configureInputBox(me, "", 0, me.fontSize, me.image);
+	me.editColorCodes = doEditColorCodes;
+	if(theCvar)
+	{
+		me.cvarName = theCvar;
+		me.loadCvars(me);
+	}
+	me.cursorPos = strlen(me.text);
+}
+void focusLeaveNexuizInputBox(entity me)
+{
+	if(me.cvarName)
+		me.saveCvars(me);
+}
+void setTextNexuizInputBox(entity me, string new)
+{
+	if(me.text != new)
+	{
+		setTextInputBox(me, new);
+		me.onChange(me, me.onChangeEntity);
+	}
+	else
+		setTextInputBox(me, new);
+}
+void loadCvarsNexuizInputBox(entity me)
+{
+	setTextInputBox(me, cvar_string(me.cvarName));
+}
+void saveCvarsNexuizInputBox(entity me)
+{
+	cvar_set(me.cvarName, me.text);
+}
+#endif

Added: trunk/menu/nexuiz/keybinder.c
===================================================================
--- trunk/menu/nexuiz/keybinder.c	                        (rev 0)
+++ trunk/menu/nexuiz/keybinder.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,319 @@
+#ifdef INTERFACE
+CLASS(NexuizKeyBinder) EXTENDS(NexuizListBox)
+	METHOD(NexuizKeyBinder, configureNexuizKeyBinder, void(entity))
+	ATTRIB(NexuizKeyBinder, rowsPerItem, float, 1)
+	METHOD(NexuizKeyBinder, drawListBoxItem, void(entity, float, vector, float))
+	METHOD(NexuizKeyBinder, clickListBoxItem, void(entity, float, vector))
+	METHOD(NexuizKeyBinder, resizeNotify, void(entity, vector, vector, vector, vector))
+	METHOD(NexuizKeyBinder, setSelected, void(entity, float))
+	METHOD(NexuizKeyBinder, keyDown, float(entity, float, float, float))
+	METHOD(NexuizKeyBinder, keyGrabbed, void(entity, float, float))
+
+	ATTRIB(NexuizKeyBinder, realFontSize, vector, '0 0 0')
+	ATTRIB(NexuizKeyBinder, realUpperMargin, float, 0)
+	ATTRIB(NexuizKeyBinder, columnFunctionOrigin, float, 0)
+	ATTRIB(NexuizKeyBinder, columnFunctionSize, float, 0)
+	ATTRIB(NexuizKeyBinder, columnKeysOrigin, float, 0)
+	ATTRIB(NexuizKeyBinder, columnKeysSize, float, 0)
+
+	ATTRIB(NexuizKeyBinder, lastClickedKey, float, -1)
+	ATTRIB(NexuizKeyBinder, lastClickedTime, float, 0)
+	ATTRIB(NexuizKeyBinder, previouslySelected, float, -1)
+	ATTRIB(NexuizKeyBinder, inMouseHandler, float, 0)
+	ATTRIB(NexuizKeyBinder, userbindEditButton, entity, NULL)
+	ATTRIB(NexuizKeyBinder, keyGrabButton, entity, NULL)
+	ATTRIB(NexuizKeyBinder, userbindEditDialog, entity, NULL)
+	METHOD(NexuizKeyBinder, editUserbind, void(entity, string, string, string))
+ENDCLASS(NexuizKeyBinder)
+entity makeNexuizKeyBinder();
+void KeyBinder_Bind_Change(entity btn, entity me);
+void KeyBinder_Bind_Clear(entity btn, entity me);
+void KeyBinder_Bind_Edit(entity btn, entity me);
+#endif
+
+#ifdef IMPLEMENTATION
+
+#define MAX_KEYS_PER_FUNCTION 2
+#define MAX_KEYBINDS 256
+string Nexuiz_KeyBinds_Functions[MAX_KEYBINDS];
+string Nexuiz_KeyBinds_Descriptions[MAX_KEYBINDS];
+var float Nexuiz_KeyBinds_Count = -1;
+
+void Nexuiz_KeyBinds_Read()
+{
+	float fh;
+	string s;
+
+	Nexuiz_KeyBinds_Count = 0;
+	fh = fopen("keybinds.txt", FILE_READ);
+	if(fh < 0)
+		return;
+	while((s = fgets(fh)))
+	{
+		if(tokenize(s) != 2)
+			continue;
+		Nexuiz_KeyBinds_Functions[Nexuiz_KeyBinds_Count] = strzone(argv(0));
+		Nexuiz_KeyBinds_Descriptions[Nexuiz_KeyBinds_Count] = strzone(argv(1));
+		++Nexuiz_KeyBinds_Count;
+		if(Nexuiz_KeyBinds_Count >= MAX_KEYBINDS)
+			break;
+	}
+	fclose(fh);
+}
+
+entity makeNexuizKeyBinder()
+{
+	entity me;
+	me = spawnNexuizKeyBinder();
+	me.configureNexuizKeyBinder(me);
+	return me;
+}
+void configureNexuizKeyBinderNexuizKeyBinder(entity me)
+{
+	me.configureNexuizListBox(me);
+	if(Nexuiz_KeyBinds_Count < 0)
+		Nexuiz_KeyBinds_Read();
+	me.nItems = Nexuiz_KeyBinds_Count;
+	me.setSelected(me, 0);
+}
+void resizeNotifyNexuizKeyBinder(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
+{
+	resizeNotifyNexuizListBox(me, relOrigin, relSize, absOrigin, absSize);
+
+	me.realFontSize_y = me.fontSize / (absSize_y * me.itemHeight);
+	me.realFontSize_x = me.fontSize / (absSize_x * (1 - me.controlWidth));
+	me.realUpperMargin = 0.5 * (1 - me.realFontSize_y);
+
+	me.columnFunctionOrigin = 0;
+	me.columnKeysSize = me.realFontSize_x * 12;
+	me.columnFunctionSize = 1 - me.columnKeysSize - 2 * me.realFontSize_x;
+	me.columnKeysOrigin = me.columnFunctionOrigin + me.columnFunctionSize + me.realFontSize_x;
+
+	if(me.userbindEditButton)
+		me.userbindEditButton.disabled = (substring(Nexuiz_KeyBinds_Descriptions[me.selectedItem], 0, 1) != "$");
+}
+void KeyBinder_Bind_Change(entity btn, entity me)
+{
+	string func;
+
+	func = Nexuiz_KeyBinds_Functions[me.selectedItem];
+	if(func == "")
+		return;
+
+	me.keyGrabButton.forcePressed = 1;
+	keyGrabber = me;
+}
+void keyGrabbedNexuizKeyBinder(entity me, float key, float ascii)
+{
+	float n, j, k, nvalid;
+	string func;
+
+	me.keyGrabButton.forcePressed = 0;
+	if(key == K_ESCAPE)
+		return;
+
+	func = Nexuiz_KeyBinds_Functions[me.selectedItem];
+	if(func == "")
+		return;
+
+	n = tokenize(findkeysforcommand(func));
+	nvalid = 0;
+	for(j = 0; j < n; ++j)
+	{
+		k = stof(argv(j));
+		if(k != -1)
+			++nvalid;
+	}
+	if(nvalid >= MAX_KEYS_PER_FUNCTION)
+	{
+		for(j = 0; j < n; ++j)
+		{
+			k = stof(argv(j));
+			if(k != -1)
+				localcmd("\nunbind \"", keynumtostring(k), "\"\n");
+		}
+	}
+	localcmd("\nbind \"", keynumtostring(key), "\" \"", func, "\"\n");
+}
+void editUserbindNexuizKeyBinder(entity me, string theName, string theCommandPress, string theCommandRelease)
+{
+	string func, descr;
+
+	if(!me.userbindEditDialog)
+		return;
+	
+	func = Nexuiz_KeyBinds_Functions[me.selectedItem];
+	if(func == "")
+		return;
+	
+	descr = Nexuiz_KeyBinds_Descriptions[me.selectedItem];
+	if(substring(descr, 0, 1) != "$")
+		return;
+	descr = substring(descr, 1, strlen(descr) - 1);
+
+	// Hooray! It IS a user bind!
+	cvar_set(strcat(descr, "_description"), theName);
+	cvar_set(strcat(descr, "_press"), theCommandPress);
+	cvar_set(strcat(descr, "_release"), theCommandRelease);
+}
+void KeyBinder_Bind_Edit(entity btn, entity me)
+{
+	string func, descr;
+
+	if(!me.userbindEditDialog)
+		return;
+	
+	func = Nexuiz_KeyBinds_Functions[me.selectedItem];
+	if(func == "")
+		return;
+	
+	descr = Nexuiz_KeyBinds_Descriptions[me.selectedItem];
+	if(substring(descr, 0, 1) != "$")
+		return;
+	descr = substring(descr, 1, strlen(descr) - 1);
+
+	// Hooray! It IS a user bind!
+	me.userbindEditDialog.loadUserBind(me.userbindEditDialog, cvar_string(strcat(descr, "_description")), cvar_string(strcat(descr, "_press")), cvar_string(strcat(descr, "_release")));
+
+	DialogOpenButton_Click(btn, me.userbindEditDialog);
+}
+void KeyBinder_Bind_Clear(entity btn, entity me)
+{
+	float n, j, k;
+	string func;
+
+	func = Nexuiz_KeyBinds_Functions[me.selectedItem];
+	if(func == "")
+		return;
+
+	n = tokenize(findkeysforcommand(func));
+	for(j = 0; j < n; ++j)
+	{
+		k = stof(argv(j));
+		if(k != -1)
+			localcmd("\nunbind \"", keynumtostring(k), "\"\n");
+	}
+
+}
+void clickListBoxItemNexuizKeyBinder(entity me, float i, vector where)
+{
+	if(i == me.lastClickedServer)
+		if(time < me.lastClickedTime + 0.3)
+		{
+			// DOUBLE CLICK!
+			KeyBinder_Bind_Change(NULL, me);
+		}
+	me.lastClickedServer = i;
+	me.lastClickedTime = time;
+}
+void setSelectedNexuizKeyBinder(entity me, float i)
+{
+	// handling of "unselectable" items
+	i = floor(0.5 + bound(0, i, me.nItems - 1));
+	if(me.pressed == 0 || me.pressed == 1) // keyboard or scrolling - skip unselectable items
+	{
+		if(i > me.previouslySelected)
+		{
+			while((i < me.nItems - 1) && (Nexuiz_KeyBinds_Functions[i] == ""))
+				++i;
+		}
+		while((i > 0) && (Nexuiz_KeyBinds_Functions[i] == ""))
+			--i;
+		while((i < me.nItems - 1) && (Nexuiz_KeyBinds_Functions[i] == ""))
+			++i;
+	}
+	if(me.pressed == 3) // released the mouse - fall back to last valid item
+	{
+		if(Nexuiz_KeyBinds_Functions[i] == "")
+			i = me.previouslySelected;
+	}
+	if(Nexuiz_KeyBinds_Functions[i] != "")
+		me.previouslySelected = i;
+	if(me.userbindEditButton)
+		me.userbindEditButton.disabled = (substring(Nexuiz_KeyBinds_Descriptions[i], 0, 1) != "$");
+	setSelectedListBox(me, i);
+}
+float keyDownNexuizKeyBinder(entity me, float key, float ascii, float shift)
+{
+	float r;
+	r = 1;
+	switch(key)
+	{
+		case K_ENTER:
+		case K_SPACE:
+			KeyBinder_Bind_Change(me, me);
+			break;
+		case K_DEL:
+		case K_BACKSPACE:
+			KeyBinder_Bind_Clear(me, me);
+			break;
+		default:
+			r = keyDownListBox(me, key, ascii, shift);
+			break;
+	}
+	return r;
+}
+void drawListBoxItemNexuizKeyBinder(entity me, float i, vector absSize, float isSelected)
+{
+	string s;
+	float j, k, n;
+	vector theColor;
+	float theAlpha;
+	string func, descr;
+	float extraMargin;
+
+	descr = Nexuiz_KeyBinds_Descriptions[i];
+	func = Nexuiz_KeyBinds_Functions[i];
+
+	if(func == "")
+	{
+		theAlpha = 1;
+		theColor = SKINCOLOR_KEYGRABBER_TITLES;
+		theAlpha = SKINALPHA_KEYGRABBER_TITLES;
+		extraMargin = 0;
+	}
+	else
+	{
+		if(isSelected)
+		{
+			if(keyGrabber == me)
+				draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_WAITING, SKINALPHA_LISTBOX_WAITING);
+			else
+				draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
+		}
+		theAlpha = SKINALPHA_KEYGRABBER_KEYS;
+		theColor = SKINCOLOR_KEYGRABBER_KEYS;
+		extraMargin = me.realFontSize_x * 0.5;
+	}
+
+	if(substring(descr, 0, 1) == "$")
+	{
+		s = substring(descr, 1, strlen(descr) - 1);
+		descr = cvar_string(strcat(s, "_description"));
+		if(descr == "")
+			descr = s;
+		if(cvar_string(strcat(s, "_press")) == "")
+			if(cvar_string(strcat(s, "_release")) == "")
+				theAlpha *= SKINALPHA_DISABLED;
+	}
+
+	draw_Text(me.realUpperMargin * eY + extraMargin * eX, descr, me.realFontSize, theColor, theAlpha, 0);
+	if(func != "")
+	{
+		n = tokenize(findkeysforcommand(func));
+		s = "";
+		for(j = 0; j < n; ++j)
+		{
+			k = stof(argv(j));
+			if(k != -1)
+			{
+				if(s != "")
+					s = strcat(s, ", ");
+				s = strcat(s, keynumtostring(k));
+			}
+		}
+		s = draw_TextShortenToWidth(s, me.columnKeysSize / me.realFontSize_x, 0);
+		draw_CenterText(me.realUpperMargin * eY + (me.columnKeysOrigin + 0.5 * me.columnKeysSize) * eX, s, me.realFontSize, theColor, theAlpha, 0);
+	}
+}
+#endif

Added: trunk/menu/nexuiz/listbox.c
===================================================================
--- trunk/menu/nexuiz/listbox.c	                        (rev 0)
+++ trunk/menu/nexuiz/listbox.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,34 @@
+#ifdef INTERFACE
+CLASS(NexuizListBox) EXTENDS(ListBox)
+	METHOD(NexuizListBox, configureNexuizListBox, void(entity))
+	ATTRIB(NexuizListBox, fontSize, float, SKINFONTSIZE_NORMAL)
+	ATTRIB(NexuizListBox, scrollbarWidth, float, SKINWIDTH_SCROLLBAR)
+	ATTRIB(NexuizListBox, src, string, SKINGFX_SCROLLBAR)
+	ATTRIB(NexuizListBox, tolerance, vector, SKINTOLERANCE_SLIDER)
+	ATTRIB(NexuizListBox, rowsPerItem, float, 1)
+	METHOD(NexuizListBox, resizeNotify, void(entity, vector, vector, vector, vector))
+	ATTRIB(NexuizListBox, color, vector, SKINCOLOR_SCROLLBAR_N)
+	ATTRIB(NexuizListBox, colorF, vector, SKINCOLOR_SCROLLBAR_F)
+	ATTRIB(NexuizListBox, color2, vector, SKINCOLOR_SCROLLBAR_S)
+ENDCLASS(NexuizListBox)
+entity makeNexuizListBox();
+#endif
+
+#ifdef IMPLEMENTATION
+entity makeNexuizListBox()
+{
+	entity me;
+	me = spawnNexuizListBox();
+	me.configureNexuizListBox(me);
+	return me;
+}
+void configureNexuizListBoxNexuizListBox(entity me)
+{
+	me.configureListBox(me, me.scrollbarWidth, 1); // item height gets set up later
+}
+void resizeNotifyNexuizListBox(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
+{
+	me.itemHeight = me.rowsPerItem * me.fontSize / absSize_y;
+	resizeNotifyListBox(me, relOrigin, relSize, absOrigin, absSize);
+}
+#endif

Added: trunk/menu/nexuiz/mainwindow.c
===================================================================
--- trunk/menu/nexuiz/mainwindow.c	                        (rev 0)
+++ trunk/menu/nexuiz/mainwindow.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,97 @@
+#ifdef INTERFACE
+CLASS(MainWindow) EXTENDS(ModalController)
+	METHOD(MainWindow, configureMainWindow, void(entity))
+	ATTRIB(MainWindow, mutatorsDialog, entity, NULL)
+	ATTRIB(MainWindow, mapInfoDialog, entity, NULL)
+	ATTRIB(MainWindow, userbindEditDialog, entity, NULL)
+	ATTRIB(MainWindow, winnerDialog, entity, NULL)
+	ATTRIB(MainWindow, fadedAlpha, float, SKINALPHA_BEHIND)
+ENDCLASS(MainWindow)
+#endif
+
+#ifdef IMPLEMENTATION
+
+void DemoButton_Click(entity me, entity other)
+{
+	if(me.text == "Do not press this button again!")
+		DialogOpenButton_Click(me, other);
+	else
+		me.setText(me, "Do not press this button again!");
+}
+
+void configureMainWindowMainWindow(entity me)
+{
+	entity n, i;
+
+	i = spawnNexuizTeamSelectDialog();
+	i.configureDialog(i);
+	me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
+
+	i = spawnNexuizClassSelectDialog();
+	i.configureDialog(i);
+	me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
+
+	i = spawnNexuizInfoScreenDialog();
+	i.configureDialog(i);
+	me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
+
+	me.mutatorsDialog = i = spawnNexuizMutatorsDialog();
+	i.configureDialog(i);
+	me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
+
+	me.mapInfoDialog = i = spawnNexuizMapInfoDialog();
+	i.configureDialog(i);
+	me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
+
+	me.userbindEditDialog = i = spawnNexuizUserbindEditDialog();
+	i.configureDialog(i);
+	me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
+
+	me.winnerDialog = i = spawnNexuizWinnerDialog();
+	i.configureDialog(i);
+	me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
+
+	n = spawnNexuizNexposee();
+	/*
+		if(checkextension("DP_GECKO_SUPPORT"))
+		{
+			i = spawnNexuizNewsDialog();
+			i.configureDialog(i);
+			n.addItemCentered(n, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
+			n.setNexposee(n, i, '0.1 0.1 0', SKINALPHAS_MAINMENU_x, SKINALPHAS_MAINMENU_y);
+		}
+	*/
+		i = spawnNexuizSingleplayerDialog();
+		i.configureDialog(i);
+		n.addItemCentered(n, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
+		n.setNexposee(n, i, SKINPOSITION_DIALOG_SINGLEPLAYER, SKINALPHAS_MAINMENU_x, SKINALPHAS_MAINMENU_y);
+		
+		i = spawnNexuizMultiplayerDialog();
+		i.configureDialog(i);
+		n.addItemCentered(n, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
+		n.setNexposee(n, i, SKINPOSITION_DIALOG_MULTIPLAYER, SKINALPHAS_MAINMENU_x, SKINALPHAS_MAINMENU_y);
+
+		i = spawnNexuizSettingsDialog();
+		i.configureDialog(i);
+		n.addItemCentered(n, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
+		n.setNexposee(n, i, SKINPOSITION_DIALOG_SETTINGS, SKINALPHAS_MAINMENU_x, SKINALPHAS_MAINMENU_y);
+
+		i = spawnNexuizCreditsDialog();
+		i.configureDialog(i);
+		n.addItemCentered(n, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
+		n.setNexposee(n, i, SKINPOSITION_DIALOG_CREDITS, SKINALPHAS_MAINMENU_x, SKINALPHAS_MAINMENU_y);
+		n.pullNexposee(n, i, eY * (SKINHEIGHT_TITLE * SKINFONTSIZE_TITLE / conheight));
+
+		i = spawnNexuizQuitDialog();
+		i.configureDialog(i);
+		n.addItemCentered(n, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
+		n.setNexposee(n, i, SKINPOSITION_DIALOG_QUIT, SKINALPHAS_MAINMENU_x, SKINALPHAS_MAINMENU_y);
+		n.pullNexposee(n, i, eY * (SKINHEIGHT_TITLE * SKINFONTSIZE_TITLE / conheight));
+	me.addItem(me, n, '0 0 0', '1 1 0', SKINALPHAS_MAINMENU_z);
+	me.moveItemAfter(me, n, NULL);
+
+	me.initializeDialog(me, n);
+}
+#endif
+
+// click. The C-word so you can grep for it.

Added: trunk/menu/nexuiz/maplist.c
===================================================================
--- trunk/menu/nexuiz/maplist.c	                        (rev 0)
+++ trunk/menu/nexuiz/maplist.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,319 @@
+#ifdef INTERFACE
+CLASS(NexuizMapList) EXTENDS(NexuizListBox)
+	METHOD(NexuizMapList, configureNexuizMapList, void(entity))
+	ATTRIB(NexuizMapList, rowsPerItem, float, 4)
+	METHOD(NexuizMapList, draw, void(entity))
+	METHOD(NexuizMapList, drawListBoxItem, void(entity, float, vector, float))
+	METHOD(NexuizMapList, clickListBoxItem, void(entity, float, vector))
+	METHOD(NexuizMapList, resizeNotify, void(entity, vector, vector, vector, vector))
+	METHOD(NexuizMapList, refilter, void(entity))
+	METHOD(NexuizMapList, keyDown, float(entity, float, float, float))
+
+	ATTRIB(NexuizMapList, realFontSize, vector, '0 0 0')
+	ATTRIB(NexuizMapList, columnPreviewOrigin, float, 0)
+	ATTRIB(NexuizMapList, columnPreviewSize, float, 0)
+	ATTRIB(NexuizMapList, columnNameOrigin, float, 0)
+	ATTRIB(NexuizMapList, columnNameSize, float, 0)
+	ATTRIB(NexuizMapList, checkMarkOrigin, vector, '0 0 0')
+	ATTRIB(NexuizMapList, checkMarkSize, vector, '0 0 0')
+	ATTRIB(NexuizMapList, realUpperMargin1, float, 0)
+	ATTRIB(NexuizMapList, realUpperMargin2, float, 0)
+
+	ATTRIB(NexuizMapList, lastClickedMap, float, -1)
+	ATTRIB(NexuizMapList, lastClickedTime, float, 0)
+
+	ATTRIB(NexuizMapList, lastGametype, float, 0)
+	ATTRIB(NexuizMapList, lastFeatures, float, 0)
+
+	ATTRIB(NexuizMapList, origin, vector, '0 0 0')
+	ATTRIB(NexuizMapList, itemAbsSize, vector, '0 0 0')
+
+	ATTRIB(NexuizMapList, g_maplistCache, string, string_null)
+	METHOD(NexuizMapList, g_maplistCacheToggle, void(entity, float))
+	METHOD(NexuizMapList, g_maplistCacheQuery, float(entity, float))
+
+	ATTRIB(NexuizMapList, startButton, entity, NULL)
+
+	ATTRIB(NexuizMapList, cvarName, string, "dummy")
+	METHOD(NexuizMapList, loadCvars, void(entity))
+
+	ATTRIB(NexuizMapList, typeToSearchString, string, string_null)
+	ATTRIB(NexuizMapList, typeToSearchTime, float, 0)
+ENDCLASS(NexuizMapList)
+entity makeNexuizMapList();
+void MapList_All(entity btn, entity me);
+void MapList_None(entity btn, entity me);
+void MapList_LoadMap(entity btn, entity me);
+#endif
+
+#ifdef IMPLEMENTATION
+entity makeNexuizMapList()
+{
+	entity me;
+	me = spawnNexuizMapList();
+	me.configureNexuizMapList(me);
+	return me;
+}
+void configureNexuizMapListNexuizMapList(entity me)
+{
+	me.configureNexuizListBox(me);
+	me.refilter(me);
+}
+
+void loadCvarsNexuizMapList(entity me)
+{
+	me.refilter(me);
+}
+
+float g_maplistCacheQueryNexuizMapList(entity me, float i)
+{
+	return stof(substring(me.g_maplistCache, i, 1));
+}
+void g_maplistCacheToggleNexuizMapList(entity me, float i)
+{
+	string a, b, c, s, bspname;
+	float n;
+	s = me.g_maplistCache;
+	if not(s)
+		return;
+	b = substring(s, i, 1);
+	if(b == "0")
+		b = "1";
+	else if(b == "1")
+		b = "0";
+	else
+		return; // nothing happens
+	a = substring(s, 0, i);
+	c = substring(s, i+1, strlen(s) - (i+1));
+	strunzone(s);
+	me.g_maplistCache = strzone(strcat(a, b, c));
+	// TODO also update the actual cvar
+	if not((bspname = MapInfo_BSPName_ByID(i)))
+		return;
+	if(b == "1")
+		cvar_set("g_maplist", strcat(bspname, " ", cvar_string("g_maplist")));
+	else
+	{
+		s = "";
+		n = tokenize(cvar_string("g_maplist"));
+		for(i = 0; i < n; ++i)
+			if(argv(i) != bspname)
+				s = strcat(s, " ", argv(i));
+		cvar_set("g_maplist", substring(s, 1, strlen(s) - 1));
+	}
+}
+
+void drawNexuizMapList(entity me)
+{
+	if(me.startButton)
+		me.startButton.disabled = ((me.selectedItem < 0) || (me.selectedItem >= me.nItems));
+	drawListBox(me);
+}
+
+void resizeNotifyNexuizMapList(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
+{
+	me.origin = absOrigin;
+	me.itemAbsSize = '0 0 0';
+	resizeNotifyNexuizListBox(me, relOrigin, relSize, absOrigin, absSize);
+
+	me.realFontSize_y = me.fontSize / (me.itemAbsSize_y = (absSize_y * me.itemHeight));
+	me.realFontSize_x = me.fontSize / (me.itemAbsSize_x = (absSize_x * (1 - me.controlWidth)));
+	me.realUpperMargin1 = 0.5 * (1 - 2.5 * me.realFontSize_y);
+	me.realUpperMargin2 = me.realUpperMargin1 + 1.5 * me.realFontSize_y;
+
+	me.columnPreviewOrigin = 0;
+	me.columnPreviewSize = me.itemAbsSize_y / me.itemAbsSize_x * 4 / 3;
+	me.columnNameOrigin = me.columnPreviewOrigin + me.columnPreviewSize + me.realFontSize_x;
+	me.columnNameSize = 1 - me.columnPreviewSize - 2 * me.realFontSize_x;
+
+	me.checkMarkSize = (eX * (me.itemAbsSize_y / me.itemAbsSize_x) + eY) * 0.5;
+	me.checkMarkOrigin = eY + eX * (me.columnPreviewOrigin + me.columnPreviewSize) - me.checkMarkSize;
+}
+void clickListBoxItemNexuizMapList(entity me, float i, vector where)
+{
+	if(where_x <= me.columnPreviewOrigin + me.columnPreviewSize)
+	{
+		if(where_x >= 0)
+			me.g_maplistCacheToggle(me, i);
+	}
+	if(where_x >= me.columnNameOrigin)
+		if(where_x <= 1)
+			{
+				if(i == me.lastClickedMap)
+					if(time < me.lastClickedTime + 0.3)
+					{
+						// DOUBLE CLICK!
+						// pop up map info screen
+						main.mapInfoDialog.loadMapInfo(main.mapInfoDialog, i);
+						DialogOpenButton_Click_withCoords(NULL, main.mapInfoDialog, me.origin + eX * (me.columnNameOrigin * me.size_x) + eY * ((me.itemHeight * i - me.scrollPos) * me.size_y), eY * me.itemAbsSize_y + eX * (me.itemAbsSize_x * me.columnNameSize));
+						return;
+					}
+				me.lastClickedMap = i;
+				me.lastClickedTime = time;
+			}
+}
+void drawListBoxItemNexuizMapList(entity me, float i, vector absSize, float isSelected)
+{
+	// layout: Ping, Map name, Map name, NP, TP, MP
+	string s;
+	float p;
+	float theAlpha;
+	float included;
+
+	if(!MapInfo_Get_ByID(i))
+		return;
+
+	included = me.g_maplistCacheQuery(me, i);
+	if(included)
+		theAlpha = SKINALPHA_MAPLIST_INCLUDEDFG;
+	else
+		theAlpha = SKINALPHA_MAPLIST_NOTINCLUDEDFG;
+
+	if(isSelected)
+		draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
+	else if(included)
+		draw_Fill('0 0 0', '1 1 0', SKINCOLOR_MAPLIST_INCLUDEDBG, SKINALPHA_MAPLIST_INCLUDEDBG);
+
+	s = ftos(p);
+	draw_Picture(me.columnPreviewOrigin * eX, strcat("/maps/", MapInfo_Map_bspname), me.columnPreviewSize * eX + eY, '1 1 1', theAlpha);
+	if(included)
+		draw_Picture(me.checkMarkOrigin, "checkmark", me.checkMarkSize, '1 1 1', 1);
+	s = draw_TextShortenToWidth(strcat(MapInfo_Map_bspname, ": ", MapInfo_Map_title), me.columnNameSize / me.realFontSize_x, 0);
+	draw_Text(me.realUpperMargin1 * eY + (me.columnNameOrigin + 0.00 * (me.columnNameSize - draw_TextWidth(s, 0) * me.realFontSize_x)) * eX, s, me.realFontSize, SKINCOLOR_MAPLIST_TITLE, theAlpha, 0);
+	s = draw_TextShortenToWidth(MapInfo_Map_author, me.columnNameSize / me.realFontSize_x, 0);
+	draw_Text(me.realUpperMargin2 * eY + (me.columnNameOrigin + 1.00 * (me.columnNameSize - draw_TextWidth(s, 0) * me.realFontSize_x)) * eX, s, me.realFontSize, SKINCOLOR_MAPLIST_AUTHOR, theAlpha, 0);
+
+	MapInfo_ClearTemps();
+}
+
+void refilterNexuizMapList(entity me)
+{
+	float i, j, n;
+	string s;
+	float gt, f;
+	gt = MapInfo_CurrentGametype();
+	f = MapInfo_CurrentFeatures();
+	MapInfo_FilterGametype(gt, f, 0);
+	me.nItems = MapInfo_count;
+	for(i = 0; i < MapInfo_count; ++i)
+		draw_PreloadPicture(strcat("/maps/", MapInfo_BSPName_ByID(i)));
+	if(me.g_maplistCache)
+		strunzone(me.g_maplistCache);
+	s = "0";
+	for(i = 1; i < MapInfo_count; i *= 2)
+		s = strcat(s, s);
+	n = tokenize(cvar_string("g_maplist"));
+	for(i = 0; i < n; ++i)
+	{
+		j = MapInfo_FindName(argv(i));
+		if(j >= 0)
+			s = strcat(
+				substring(s, 0, j),
+				"1",
+				substring(s, j+1, MapInfo_count - (j+1))
+			);
+	}
+	me.g_maplistCache = strzone(s);
+	if(gt != me.lastGametype || f != me.lastFeatures)
+	{
+		me.lastGametype = gt;
+		me.lastFeatures = f;
+		me.setSelected(me, 0);
+	}
+}
+void MapList_All(entity btn, entity me)
+{
+	float i;
+	string s;
+	MapInfo_FilterGametype(MAPINFO_TYPE_ALL, 0, 0); // all
+	s = "";
+	for(i = 0; i < MapInfo_count; ++i)
+		s = strcat(s, " ", MapInfo_BSPName_ByID(i));
+	cvar_set("g_maplist", substring(s, 1, strlen(s) - 1));
+	me.refilter(me);
+}
+void MapList_None(entity btn, entity me)
+{
+	cvar_set("g_maplist", "");
+	me.refilter(me);
+}
+void MapList_LoadMap(entity btn, entity me)
+{
+	string m;
+	if(me.selectedItem >= me.nItems || me.selectedItem < 0)
+		return;
+	m = MapInfo_BSPName_ByID(me.selectedItem);
+	if not(m)
+		return;
+	if(MapInfo_CheckMap(m))
+	{
+		localcmd("\ndisconnect\nwait\nmaxplayers $menu_maxplayers\ng_maplist_shufflenow\n");
+		MapInfo_LoadMap(m);
+	}
+	else
+	{
+		print("Huh? Can't play this. Refiltering so this won't happen again.\n");
+		me.refilter(me);
+	}
+}
+
+float keyDownNexuizMapList(entity me, float scan, float ascii, float shift)
+{
+	string ch, save;
+	if(scan == K_ENTER)
+	{
+		// pop up map info screen
+		main.mapInfoDialog.loadMapInfo(main.mapInfoDialog, me.selectedItem);
+		DialogOpenButton_Click_withCoords(NULL, main.mapInfoDialog, me.origin + eX * (me.columnNameOrigin * me.size_x) + eY * ((me.itemHeight * me.selectedItem - me.scrollPos) * me.size_y), eY * me.itemAbsSize_y + eX * (me.itemAbsSize_x * me.columnNameSize));
+	}
+	else if(scan == K_SPACE)
+	{
+		me.g_maplistCacheToggle(me, me.selectedItem);
+	}
+	else if(ascii == 43) // +
+	{
+		if not(me.g_maplistCacheQuery(me, me.selectedItem))
+			me.g_maplistCacheToggle(me, me.selectedItem);
+	}
+	else if(ascii == 45) // -
+	{
+		if(me.g_maplistCacheQuery(me, me.selectedItem))
+			me.g_maplistCacheToggle(me, me.selectedItem);
+	}
+	else if(scan == K_BACKSPACE)
+	{
+		if(time < me.typeToSearchTime)
+		{
+			save = substring(me.typeToSearchString, 0, strlen(me.typeToSearchString) - 1);
+			if(me.typeToSearchString)
+				strunzone(me.typeToSearchString);
+			me.typeToSearchString = strzone(save);
+			me.typeToSearchTime = time + 0.5;
+			if(strlen(me.typeToSearchString))
+			{
+				MapInfo_FindName(me.typeToSearchString);
+				if(MapInfo_FindName_firstResult >= 0)
+					me.setSelected(me, MapInfo_FindName_firstResult);
+			}
+		}
+	}
+	else if(ascii >= 32 && ascii != 127)
+	{
+		ch = chr(ascii);
+		if(time > me.typeToSearchTime)
+			save = ch;
+		else
+			save = strcat(me.typeToSearchString, ch);
+		if(me.typeToSearchString)
+			strunzone(me.typeToSearchString);
+		me.typeToSearchString = strzone(save);
+		me.typeToSearchTime = time + 0.5;
+		MapInfo_FindName(me.typeToSearchString);
+		if(MapInfo_FindName_firstResult >= 0)
+			me.setSelected(me, MapInfo_FindName_firstResult);
+	}
+	else
+		return keyDownListBox(me, scan, ascii, shift);
+	return 1;
+}
+#endif

Added: trunk/menu/nexuiz/nexposee.c
===================================================================
--- trunk/menu/nexuiz/nexposee.c	                        (rev 0)
+++ trunk/menu/nexuiz/nexposee.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,26 @@
+#ifdef INTERFACE
+CLASS(NexuizNexposee) EXTENDS(Nexposee)
+	METHOD(NexuizNexposee, configureNexuizNexposee, void(entity))
+	METHOD(NexuizNexposee, close, void(entity))
+ENDCLASS(NexuizNexposee)
+entity makeNexuizNexposee();
+#endif
+
+#ifdef IMPLEMENTATION
+entity makeNexuizNexposee()
+{
+	entity me;
+	me = spawnNexuizNexposee();
+	me.configureNexuizNexposee(me);
+	return me;
+}
+
+void configureNexuizNexposeeNexuizNexposee(entity me)
+{
+}
+
+void closeNexuizNexposee(entity me)
+{
+	m_goto(string_null); // hide
+}
+#endif

Added: trunk/menu/nexuiz/playermodel.c
===================================================================
--- trunk/menu/nexuiz/playermodel.c	                        (rev 0)
+++ trunk/menu/nexuiz/playermodel.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,201 @@
+#ifdef INTERFACE
+CLASS(NexuizPlayerModelSelector) EXTENDS(NexuizImage)
+	METHOD(NexuizPlayerModelSelector, configureNexuizPlayerModelSelector, void(entity))
+	METHOD(NexuizPlayerModelSelector, loadCvars, void(entity))
+	METHOD(NexuizPlayerModelSelector, saveCvars, void(entity))
+	METHOD(NexuizPlayerModelSelector, draw, void(entity))
+	METHOD(NexuizPlayerModelSelector, resizeNotify, void(entity, vector, vector, vector, vector))
+	ATTRIB(NexuizPlayerModelSelector, currentModel, string, string_null)
+	ATTRIB(NexuizPlayerModelSelector, currentSkin, float, 0)
+	ATTRIB(NexuizPlayerModelSelector, currentModelName, string, string_null)
+	ATTRIB(NexuizPlayerModelSelector, currentModelTitle, string, string_null)
+	ATTRIB(NexuizPlayerModelSelector, currentModelTxtName, string, string_null)
+	ATTRIB(NexuizPlayerModelSelector, currentModelDescription, string, string_null)
+	ATTRIB(NexuizPlayerModelSelector, cvarName, string, "_cl_playermodel")
+	METHOD(NexuizPlayerModelSelector, go, void(entity, float))
+	ATTRIB(NexuizPlayerModelSelector, origin, vector, '0 0 0')
+	ATTRIB(NexuizPlayerModelSelector, size, vector, '0 0 0')
+	ATTRIB(NexuizPlayerModelSelector, realFontSize, vector, '0 0 0')
+	ATTRIB(NexuizPlayerModelSelector, fontSize, float, SKINFONTSIZE_NORMAL)
+	ATTRIB(NexuizPlayerModelSelector, titleFontSize, float, SKINFONTSIZE_TITLE)
+ENDCLASS(NexuizPlayerModelSelector)
+entity makeNexuizPlayerModelSelector();
+void PlayerModelSelector_Next_Click(entity btn, entity me);
+void PlayerModelSelector_Prev_Click(entity btn, entity me);
+#endif
+
+#ifdef IMPLEMENTATION
+entity makeNexuizPlayerModelSelector()
+{
+	entity me;
+	me = spawnNexuizPlayerModelSelector();
+	me.configureNexuizPlayerModelSelector(me);
+	return me;
+}
+
+void configureNexuizPlayerModelSelectorNexuizPlayerModelSelector(entity me)
+{
+	me.configureNexuizImage(me, string_null, 263.0/360.0);
+	me.loadCvars(me);
+}
+
+void loadCvarsNexuizPlayerModelSelector(entity me)
+{
+	float glob, i, fh;
+	string fn;
+	string nm, t, l;
+
+	if(me.currentModel)
+		strunzone(me.currentModel);
+	if(me.currentModelTitle)
+		strunzone(me.currentModelTitle);
+	if(me.currentModelName)
+		strunzone(me.currentModelName);
+	if(me.currentModelTxtName)
+		strunzone(me.currentModelTxtName);
+	if(me.currentModelDescription)
+		strunzone(me.currentModelDescription);
+	me.currentSkin = cvar("_cl_playerskin");
+	me.currentModel = strzone(cvar_string("_cl_playermodel"));
+	me.currentModelName = string_null;
+	me.currentModelDescription = string_null;
+	me.currentModelTitle = string_null;
+	me.currentModelTxtName = string_null;
+
+	// lookup model name
+	glob = search_begin("models/player/*.txt", TRUE, TRUE);
+	if(glob < 0)
+		return;
+	for(i = 0; i < search_getsize(glob); ++i)
+	{
+		fn = search_getfilename(glob, i);
+		fh = fopen(fn, FILE_READ);
+		if(fh < 0)
+			continue;
+		t = fgets(fh);
+		nm = fgets(fh);
+		if(stof(fgets(fh)) == me.currentSkin)
+		if(fgets(fh) == me.currentModel)
+		{
+			me.currentModelName = strzone(strcat("/", nm));
+			me.currentModelTxtName = strzone(fn);
+			me.currentModelTitle = strzone(t);
+			me.currentModelDescription = "";
+			while((l = fgets(fh)))
+			{
+				if(me.currentModelDescription != "")
+					me.currentModelDescription = strcat(me.currentModelDescription, "\n");
+				me.currentModelDescription = strcat(me.currentModelDescription, l);
+			}
+			me.currentModelDescription = strzone(me.currentModelDescription);
+			fclose(fh);
+			break;
+		}
+		fclose(fh);
+	}
+	search_end(glob);
+}
+
+void goNexuizPlayerModelSelector(entity me, float d)
+{
+	float glob, i, fh;
+	string l;
+
+	glob = search_begin("models/player/*.txt", TRUE, TRUE);
+	if(glob < 0)
+		return;
+	for(i = 0; i < search_getsize(glob); ++i)
+		if(search_getfilename(glob, i) == me.currentModelTxtName)
+			break;
+	// now i is search_getsize(glob) if not found, and the right index if found.
+	if(i == search_getsize(glob))
+	{
+		if(d < 0)
+			i = search_getsize(glob) - 1;
+		else
+			i = 0;
+	}
+	else
+	{
+		i = mod(i + d + search_getsize(glob), search_getsize(glob));
+	}
+
+	if(me.currentModel)
+		strunzone(me.currentModel);
+	if(me.currentModelTitle)
+		strunzone(me.currentModelTitle);
+	if(me.currentModelName)
+		strunzone(me.currentModelName);
+	if(me.currentModelTxtName)
+		strunzone(me.currentModelTxtName);
+	if(me.currentModelDescription)
+		strunzone(me.currentModelDescription);
+
+	// select model #i!
+	me.currentModelTxtName = strzone(search_getfilename(glob, i));
+	fh = fopen(me.currentModelTxtName, FILE_READ);
+	search_end(glob);
+	if(fh < 0)
+		return;
+	me.currentModelTitle = strzone(fgets(fh));
+	me.currentModelName = strzone(strcat("/", fgets(fh)));
+	me.currentSkin = stof(fgets(fh));
+	me.currentModel = strzone(fgets(fh));
+	me.currentModelDescription = "";
+	while((l = fgets(fh)))
+	{
+		if(me.currentModelDescription != "")
+			me.currentModelDescription = strcat(me.currentModelDescription, "\n");
+		me.currentModelDescription = strcat(me.currentModelDescription, l);
+	}
+	me.currentModelDescription = strzone(me.currentModelDescription);
+	fclose(fh);
+}
+
+void PlayerModelSelector_Next_Click(entity btn, entity me)
+{
+	me.go(me, +1);
+	me.saveCvars(me);
+}
+
+void PlayerModelSelector_Prev_Click(entity btn, entity me)
+{
+	me.go(me, -1);
+	me.saveCvars(me);
+}
+
+void saveCvarsNexuizPlayerModelSelector(entity me)
+{
+	// TODO rather set the _cl ones and apply later?
+	localcmd(strcat("playermodel ", me.currentModel, "\nplayerskin ", ftos(me.currentSkin), "\n"));
+}
+
+void drawNexuizPlayerModelSelector(entity me)
+{
+	float i, n;
+	vector o;
+
+	me.src = me.currentModelName;
+	drawImage(me);
+	me.src = string_null;
+
+	// draw text on the image, handle \n in the description
+	draw_CenterText('0.5 0 0', me.currentModelTitle, me.realFontSize * (me.titleFontSize / me.fontSize), SKINCOLOR_MODELTITLE, SKINALPHA_MODELTITLE, FALSE);
+
+	o = '0.5 1 0' - eY * me.realFontSize_y * ((n = tokenizebyseparator(me.currentModelDescription, "\n")) + 0.5);
+	for(i = 0; i < n; ++i)
+	{
+		draw_CenterText(o, argv(i), me.realFontSize, '1 1 1', 1, FALSE);
+		o += eY * me.realFontSize_y;
+	}
+}
+
+void resizeNotifyNexuizPlayerModelSelector(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
+{
+	resizeNotifyImage(me, relOrigin, relSize, absOrigin, absSize);
+	me.origin = absOrigin;
+	me.size = absSize;
+	me.realFontSize_y = me.fontSize / absSize_y;
+	me.realFontSize_x = me.fontSize / absSize_x;
+}
+#endif

Added: trunk/menu/nexuiz/radiobutton.c
===================================================================
--- trunk/menu/nexuiz/radiobutton.c	                        (rev 0)
+++ trunk/menu/nexuiz/radiobutton.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,104 @@
+#ifdef INTERFACE
+CLASS(NexuizRadioButton) EXTENDS(RadioButton)
+	METHOD(NexuizRadioButton, configureNexuizRadioButton, void(entity, float, string, string, string))
+	METHOD(NexuizRadioButton, draw, void(entity))
+	METHOD(NexuizRadioButton, setChecked, void(entity, float))
+	ATTRIB(NexuizRadioButton, fontSize, float, SKINFONTSIZE_NORMAL)
+	ATTRIB(NexuizRadioButton, image, string, SKINGFX_RADIOBUTTON)
+	ATTRIB(NexuizRadioButton, color, vector, SKINCOLOR_RADIOBUTTON_N)
+	ATTRIB(NexuizRadioButton, colorC, vector, SKINCOLOR_RADIOBUTTON_C)
+	ATTRIB(NexuizRadioButton, colorF, vector, SKINCOLOR_RADIOBUTTON_F)
+	ATTRIB(NexuizRadioButton, colorD, vector, SKINCOLOR_RADIOBUTTON_D)
+
+	ATTRIB(NexuizRadioButton, cvarName, string, string_null)
+	ATTRIB(NexuizRadioButton, cvarValue, string, string_null)
+	METHOD(NexuizRadioButton, loadCvars, void(entity))
+	METHOD(NexuizRadioButton, saveCvars, void(entity))
+
+	ATTRIB(NexuizRadioButton, alpha, float, SKINALPHA_TEXT)
+	ATTRIB(NexuizRadioButton, disabledAlpha, float, SKINALPHA_DISABLED)
+ENDCLASS(NexuizRadioButton)
+entity makeNexuizRadioButton(float, string, string, string);
+#endif
+
+#ifdef IMPLEMENTATION
+entity makeNexuizRadioButton(float theGroup, string theCvar, string theValue, string theText)
+{
+	entity me;
+	me = spawnNexuizRadioButton();
+	me.configureNexuizRadioButton(me, theGroup, theCvar, theValue, theText);
+	return me;
+}
+void configureNexuizRadioButtonNexuizRadioButton(entity me, float theGroup, string theCvar, string theValue, string theText)
+{
+	if(theCvar)
+	{
+		me.cvarName = theCvar;
+		me.cvarValue = theValue;
+		me.loadCvars(me);
+	}
+	me.configureRadioButton(me, theText, me.fontSize, me.image, theGroup, 0);
+}
+void setCheckedNexuizRadioButton(entity me, float val)
+{
+	if(val != me.checked)
+	{
+		me.checked = val;
+		me.saveCvars(me);
+	}
+}
+void loadCvarsNexuizRadioButton(entity me)
+{
+	if(me.cvarValue)
+		me.checked = (cvar_string(me.cvarName) == me.cvarValue);
+	else
+	{
+		if(me.cvarName)
+		{
+			me.checked = !!cvar(me.cvarName);
+		}
+		else
+		{
+			// this is difficult
+			// this is the "generic" selection... but at this time, not
+			// everything is constructed yet.
+			// we need to set this later in draw()
+			me.checked = 0;
+		}
+	}
+}
+void drawNexuizRadioButton(entity me)
+{
+	if not(me.cvarValue)
+		if not(me.cvarName)
+		{
+			// this is the "other" option
+			// always select this if none other is
+			entity e;
+			float found;
+			found = 0;
+			for(e = me.parent.firstChild; e; e = e.nextSibling)
+				if(e.group == me.group)
+					if(e.checked)
+						found = 1;
+			if(!found)
+				me.setChecked(me, 1);
+		}
+	drawCheckBox(me);
+}
+void saveCvarsNexuizRadioButton(entity me)
+{
+	if(me.cvarValue)
+	{
+		if(me.checked)
+			cvar_set(me.cvarName, me.cvarValue);
+	}
+	else
+	{
+		if(me.cvarName)
+		{
+			cvar_set(me.cvarName, ftos(me.checked));
+		}
+	}
+}
+#endif

Added: trunk/menu/nexuiz/rootdialog.c
===================================================================
--- trunk/menu/nexuiz/rootdialog.c	                        (rev 0)
+++ trunk/menu/nexuiz/rootdialog.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,21 @@
+#ifdef INTERFACE
+CLASS(NexuizRootDialog) EXTENDS(NexuizDialog)
+	// still to be customized by user
+	/*
+	ATTRIB(NexuizDialog, closable, float, 1)
+	ATTRIB(NexuizDialog, title, string, "Form1") // ;)
+	ATTRIB(NexuizDialog, color, vector, '1 0.5 1')
+	ATTRIB(NexuizDialog, intendedWidth, float, 0)
+	ATTRIB(NexuizDialog, rows, float, 3)
+	ATTRIB(NexuizDialog, columns, float, 2)
+	*/
+	METHOD(NexuizRootDialog, close, void(entity))
+ENDCLASS(NexuizRootDialog)
+#endif
+
+#ifdef IMPLEMENTATION
+void closeNexuizRootDialog(entity me)
+{
+	m_goto(string_null);
+}
+#endif

Added: trunk/menu/nexuiz/serverlist.c
===================================================================
--- trunk/menu/nexuiz/serverlist.c	                        (rev 0)
+++ trunk/menu/nexuiz/serverlist.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,377 @@
+#ifdef INTERFACE
+CLASS(NexuizServerList) EXTENDS(NexuizListBox)
+	METHOD(NexuizServerList, configureNexuizServerList, void(entity))
+	ATTRIB(NexuizServerList, rowsPerItem, float, 1)
+	METHOD(NexuizServerList, draw, void(entity))
+	METHOD(NexuizServerList, drawListBoxItem, void(entity, float, vector, float))
+	METHOD(NexuizServerList, clickListBoxItem, void(entity, float, vector))
+	METHOD(NexuizServerList, resizeNotify, void(entity, vector, vector, vector, vector))
+	METHOD(NexuizServerList, keyDown, float(entity, float, float, float))
+
+	ATTRIB(NexuizServerList, realFontSize, vector, '0 0 0')
+	ATTRIB(NexuizServerList, realUpperMargin, float, 0)
+	ATTRIB(NexuizServerList, columnPingOrigin, float, 0)
+	ATTRIB(NexuizServerList, columnPingSize, float, 0)
+	ATTRIB(NexuizServerList, columnNameOrigin, float, 0)
+	ATTRIB(NexuizServerList, columnNameSize, float, 0)
+	ATTRIB(NexuizServerList, columnMapOrigin, float, 0)
+	ATTRIB(NexuizServerList, columnMapSize, float, 0)
+	ATTRIB(NexuizServerList, columnPlayersOrigin, float, 0)
+	ATTRIB(NexuizServerList, columnPlayersSize, float, 0)
+
+	ATTRIB(NexuizServerList, selectedServer, string, string_null) // to restore selected server when needed
+	METHOD(NexuizServerList, setSelected, void(entity, float))
+	METHOD(NexuizServerList, setSortOrder, void(entity, float, float))
+	ATTRIB(NexuizServerList, filterShowEmpty, float, 1)
+	ATTRIB(NexuizServerList, filterShowFull, float, 1)
+	ATTRIB(NexuizServerList, filterString, string, string_null)
+	ATTRIB(NexuizServerList, controlledTextbox, entity, NULL)
+	ATTRIB(NexuizServerList, nextRefreshTime, float, 0)
+	METHOD(NexuizServerList, refreshServerList, void(entity, float)) // refresh mode: 0 = just reparametrize, 1 = send new requests, 2 = clear
+	ATTRIB(NexuizServerList, needsRefresh, float, 1)
+	METHOD(NexuizServerList, focusEnter, void(entity))
+	METHOD(NexuizServerList, positionSortButton, void(entity, entity, float, float, string, void(entity, entity)))
+	ATTRIB(NexuizServerList, sortButton1, entity, NULL)
+	ATTRIB(NexuizServerList, sortButton2, entity, NULL)
+	ATTRIB(NexuizServerList, sortButton3, entity, NULL)
+	ATTRIB(NexuizServerList, sortButton4, entity, NULL)
+	ATTRIB(NexuizServerList, connectButton, entity, NULL)
+	ATTRIB(NexuizServerList, currentSortOrder, float, 0)
+	ATTRIB(NexuizServerList, currentSortField, float, -1)
+	ATTRIB(NexuizServerList, lastClickedServer, float, -1)
+	ATTRIB(NexuizServerList, lastClickedTime, float, 0)
+ENDCLASS(NexuizServerList)
+entity makeNexuizServerList();
+void ServerList_Connect_Click(entity btn, entity me);
+void ServerList_ShowEmpty_Click(entity box, entity me);
+void ServerList_ShowFull_Click(entity box, entity me);
+void ServerList_Filter_Change(entity box, entity me);
+#endif
+
+#ifdef IMPLEMENTATION
+float SLIST_FIELD_CNAME;
+float SLIST_FIELD_PING;
+float SLIST_FIELD_GAME;
+float SLIST_FIELD_MOD;
+float SLIST_FIELD_MAP;
+float SLIST_FIELD_NAME;
+float SLIST_FIELD_MAXPLAYERS;
+float SLIST_FIELD_NUMPLAYERS;
+float SLIST_FIELD_NUMHUMANS;
+float SLIST_FIELD_NUMBOTS;
+float SLIST_FIELD_PROTOCOL;
+float SLIST_FIELD_FREESLOTS;
+void ServerList_UpdateFieldIDs()
+{
+	SLIST_FIELD_CNAME = gethostcacheindexforkey( "cname" );
+	SLIST_FIELD_PING = gethostcacheindexforkey( "ping" );
+	SLIST_FIELD_GAME = gethostcacheindexforkey( "game" );
+	SLIST_FIELD_MOD = gethostcacheindexforkey( "mod" );
+	SLIST_FIELD_MAP = gethostcacheindexforkey( "map" );
+	SLIST_FIELD_NAME = gethostcacheindexforkey( "name" );
+	SLIST_FIELD_MAXPLAYERS = gethostcacheindexforkey( "maxplayers" );
+	SLIST_FIELD_NUMPLAYERS = gethostcacheindexforkey( "numplayers" );
+	SLIST_FIELD_NUMHUMANS = gethostcacheindexforkey( "numhumans" );
+	SLIST_FIELD_NUMBOTS = gethostcacheindexforkey( "numbots" );
+	SLIST_FIELD_PROTOCOL = gethostcacheindexforkey( "protocol" );
+	SLIST_FIELD_FREESLOTS = gethostcacheindexforkey( "freeslots" );
+}
+
+entity makeNexuizServerList()
+{
+	entity me;
+	me = spawnNexuizServerList();
+	me.configureNexuizServerList(me);
+	return me;
+}
+void configureNexuizServerListNexuizServerList(entity me)
+{
+	me.configureNexuizListBox(me);
+
+	ServerList_UpdateFieldIDs();
+
+	me.nItems = 0;
+}
+void setSelectedNexuizServerList(entity me, float i)
+{
+	float save;
+	save = me.selectedItem;
+	setSelectedListBox(me, i);
+	/*
+	if(me.selectedItem == save)
+		return;
+	*/
+	if(me.nItems == 0)
+		return;
+	if(gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT) != me.nItems)
+		return; // sorry, it would be wrong
+	if(me.selectedServer)
+		strunzone(me.selectedServer);
+	me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, me.selectedItem));
+}
+void refreshServerListNexuizServerList(entity me, float mode)
+{
+	// 0: just reparametrize
+	// 1: also ask for new servers
+	// 2: clear
+	//print("refresh of type ", ftos(mode), "\n");
+	/* if(mode == 2) // borken
+	{
+		// clear list
+		localcmd("net_slist\n");
+		me.needsRefresh = 1; // net_slist kills sort order, so we need to restore it later
+	}
+	else */
+	{
+		float m;
+		m = SLIST_MASK_AND;
+		resethostcachemasks();
+		if(!me.filterShowFull)
+			sethostcachemasknumber(m++, SLIST_FIELD_FREESLOTS, 1, SLIST_TEST_GREATEREQUAL);
+		if(!me.filterShowEmpty)
+			sethostcachemasknumber(m++, SLIST_FIELD_NUMHUMANS, 1, SLIST_TEST_GREATEREQUAL);
+		m = SLIST_MASK_OR;
+		if(me.filterString)
+		{
+			sethostcachemaskstring(m++, SLIST_FIELD_NAME, me.filterString, SLIST_TEST_CONTAINS);
+			sethostcachemaskstring(m++, SLIST_FIELD_MAP, me.filterString, SLIST_TEST_CONTAINS);
+		}
+		sethostcachesort(me.currentSortField, me.currentSortOrder < 0);
+		resorthostcache();
+		if(mode >= 1)
+			refreshhostcache();
+	}
+}
+void focusEnterNexuizServerList(entity me)
+{
+	if(time < me.nextRefreshTime)
+	{
+		//print("sorry, no refresh yet\n");
+		return;
+	}
+	me.nextRefreshTime = time + 10;
+	me.refreshServerList(me, 1);
+}
+void drawNexuizServerList(entity me)
+{
+	float i, found;
+
+	if(me.currentSortField == -1)
+	{
+		me.setSortOrder(me, SLIST_FIELD_PING, +1);
+		me.refreshServerList(me, 2);
+	}
+	else if(me.needsRefresh == 1)
+	{
+		me.needsRefresh = 2; // delay by one frame to make sure "slist" has been executed
+	}
+	else if(me.needsRefresh == 2)
+	{
+		me.needsRefresh = 0;
+		me.refreshServerList(me, 0);
+	}
+
+	me.nItems = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT);
+	me.connectButton.disabled = (me.nItems == 0);
+
+	found = 0;
+	if(me.selectedServer)
+	{
+		for(i = 0; i < me.nItems; ++i)
+			if(gethostcachestring(SLIST_FIELD_CNAME, i) == me.selectedServer)
+			{
+				if(i != me.selectedItem)
+				{
+					me.lastClickedServer = -1;
+					me.selectedItem = i;
+				}
+				found = 1;
+				break;
+			}
+	}
+	if(!found)
+		if(me.nItems > 0)
+		{
+			if(me.selectedItem >= me.nItems)
+				me.selectedItem = me.nItems - 1;
+			if(me.selectedServer)
+				strunzone(me.selectedServer);
+			me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, me.selectedItem));
+		}
+
+	drawListBox(me);
+}
+void ServerList_PingSort_Click(entity btn, entity me)
+{
+	me.setSortOrder(me, SLIST_FIELD_PING, +1);
+}
+void ServerList_NameSort_Click(entity btn, entity me)
+{
+	me.setSortOrder(me, SLIST_FIELD_NAME, -1); // why?
+}
+void ServerList_MapSort_Click(entity btn, entity me)
+{
+	me.setSortOrder(me, SLIST_FIELD_MAP, -1); // why?
+}
+void ServerList_PlayerSort_Click(entity btn, entity me)
+{
+	me.setSortOrder(me, SLIST_FIELD_NUMHUMANS, -1);
+}
+void ServerList_Filter_Change(entity box, entity me)
+{
+	if(me.filterString)
+		strunzone(me.filterString);
+	if(box.text != "")
+		me.filterString = strzone(box.text);
+	else
+		me.filterString = string_null;
+	me.refreshServerList(me, 0);
+}
+void ServerList_ShowEmpty_Click(entity box, entity me)
+{
+	box.checked = me.filterShowEmpty = !me.filterShowEmpty;
+	me.refreshServerList(me, 0);
+}
+void ServerList_ShowFull_Click(entity box, entity me)
+{
+	box.checked = me.filterShowFull = !me.filterShowFull;
+	me.refreshServerList(me, 0);
+}
+void setSortOrderNexuizServerList(entity me, float field, float direction)
+{
+	if(me.currentSortField == field)
+		direction = -me.currentSortOrder;
+	me.currentSortOrder = direction;
+	me.currentSortField = field;
+	me.sortButton1.forcePressed = (field == SLIST_FIELD_PING);
+	me.sortButton2.forcePressed = (field == SLIST_FIELD_NAME);
+	me.sortButton3.forcePressed = (field == SLIST_FIELD_MAP);
+	me.sortButton4.forcePressed = (field == SLIST_FIELD_NUMHUMANS);
+	me.selectedItem = 0;
+	if(me.selectedServer)
+		strunzone(me.selectedServer);
+	me.selectedServer = string_null;
+	me.refreshServerList(me, 0);
+}
+void positionSortButtonNexuizServerList(entity me, entity btn, float theOrigin, float theSize, string theTitle, void(entity, entity) theFunc)
+{
+	vector originInLBSpace, sizeInLBSpace;
+	originInLBSpace = eY * (-me.itemHeight);
+	sizeInLBSpace = eY * me.itemHeight + eX * (1 - me.controlWidth);
+
+	vector originInDialogSpace, sizeInDialogSpace;
+	originInDialogSpace = boxToGlobal(originInLBSpace, me.Container_origin, me.Container_size);
+	sizeInDialogSpace = boxToGlobalSize(sizeInLBSpace, me.Container_size);
+
+	btn.Container_origin_x = originInDialogSpace_x + sizeInDialogSpace_x * theOrigin;
+	btn.Container_size_x   =                         sizeInDialogSpace_x * theSize;
+	btn.setText(btn, theTitle);
+	btn.onClick = theFunc;
+	btn.onClickEntity = me;
+	btn.resized = 1;
+}
+void resizeNotifyNexuizServerList(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
+{
+	resizeNotifyNexuizListBox(me, relOrigin, relSize, absOrigin, absSize);
+
+	me.realFontSize_y = me.fontSize / (absSize_y * me.itemHeight);
+	me.realFontSize_x = me.fontSize / (absSize_x * (1 - me.controlWidth));
+	me.realUpperMargin = 0.5 * (1 - me.realFontSize_y);
+
+	me.columnPingOrigin = 0;
+	me.columnPingSize = me.realFontSize_x * 4;
+	me.columnMapSize = me.realFontSize_x * 12;
+	me.columnPlayersSize = me.realFontSize_x * 6;
+	me.columnNameSize = 1 - me.columnPlayersSize - me.columnMapSize - me.columnPingSize - 3 * me.realFontSize_x;
+	me.columnNameOrigin = me.columnPingOrigin + me.columnPingSize + me.realFontSize_x;
+	me.columnMapOrigin = me.columnNameOrigin + me.columnNameSize + me.realFontSize_x;
+	me.columnPlayersOrigin = me.columnMapOrigin + me.columnMapSize + me.realFontSize_x;
+
+	me.positionSortButton(me, me.sortButton1, me.columnPingOrigin, me.columnPingSize, "Ping", ServerList_PingSort_Click);
+	me.positionSortButton(me, me.sortButton2, me.columnNameOrigin, me.columnNameSize, "Host name", ServerList_NameSort_Click);
+	me.positionSortButton(me, me.sortButton3, me.columnMapOrigin, me.columnMapSize, "Map", ServerList_MapSort_Click);
+	me.positionSortButton(me, me.sortButton4, me.columnPlayersOrigin, me.columnPlayersSize, "Players", ServerList_PlayerSort_Click);
+
+	float f;
+	f = me.currentSortField;
+	if(f >= 0)
+	{
+		me.currentSortField = -1;
+		me.setSortOrder(me, f, me.currentSortOrder); // force resetting the sort order
+	}
+}
+void ServerList_Connect_Click(entity btn, entity me)
+{
+	if(me.nItems > 0)
+		localcmd("connect ", me.selectedServer, "\n");
+}
+void clickListBoxItemNexuizServerList(entity me, float i, vector where)
+{
+	if(i == me.lastClickedServer)
+		if(time < me.lastClickedTime + 0.3)
+		{
+			// DOUBLE CLICK!
+			ServerList_Connect_Click(NULL, me);
+		}
+	me.lastClickedServer = i;
+	me.lastClickedTime = time;
+}
+void drawListBoxItemNexuizServerList(entity me, float i, vector absSize, float isSelected)
+{
+	// layout: Ping, Server name, Map name, NP, TP, MP
+	string s;
+	float p;
+	vector theColor;
+	float theAlpha;
+
+	if(isSelected)
+		draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
+
+	if(gethostcachenumber(SLIST_FIELD_NUMPLAYERS, i) >= gethostcachenumber(SLIST_FIELD_MAXPLAYERS, i))
+		theAlpha = SKINALPHA_SERVERLIST_FULL;
+	else if not(gethostcachenumber(SLIST_FIELD_NUMHUMANS, i))
+		theAlpha = SKINALPHA_SERVERLIST_EMPTY;
+	else
+		theAlpha = 1;
+	
+	p = gethostcachenumber(SLIST_FIELD_PING, i);
+	if(p < 50)
+		theColor = SKINCOLOR_SERVERLIST_LOWPING + (SKINCOLOR_SERVERLIST_MEDPING - SKINCOLOR_SERVERLIST_LOWPING) * (p / 50);
+	else if(p < 150)
+		theColor = SKINCOLOR_SERVERLIST_MEDPING + (SKINCOLOR_SERVERLIST_HIGHPING - SKINCOLOR_SERVERLIST_MEDPING) * ((p - 50) / 100);
+	else if(p < 650)
+	{
+		theColor = SKINCOLOR_SERVERLIST_HIGHPING;
+		theAlpha *= 1 + (SKINALPHA_SERVERLIST_HIGHPING - 1) * ((p - 150) / 500);
+	}
+	else
+	{
+		theColor = eX;
+		theAlpha *= SKINALPHA_SERVERLIST_HIGHPING;
+	}
+	
+	s = ftos(p);
+	draw_Text(me.realUpperMargin * eY + (me.columnPingSize - draw_TextWidth(s, 0) * me.realFontSize_x) * eX, s, me.realFontSize, theColor, theAlpha, 0);
+	s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_NAME, i), me.columnNameSize / me.realFontSize_x, 0);
+	draw_Text(me.realUpperMargin * eY + me.columnNameOrigin * eX, s, me.realFontSize, theColor, theAlpha, 0);
+	s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_MAP, i), me.columnMapSize / me.realFontSize_x, 0);
+	draw_Text(me.realUpperMargin * eY + (me.columnMapOrigin + (me.columnMapSize - draw_TextWidth(s, 0) * me.realFontSize_x) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
+	s = strcat(ftos(gethostcachenumber(SLIST_FIELD_NUMHUMANS, i)), "/", ftos(gethostcachenumber(SLIST_FIELD_MAXPLAYERS, i)));
+	draw_Text(me.realUpperMargin * eY + (me.columnPlayersOrigin + (me.columnPlayersSize - draw_TextWidth(s, 0) * me.realFontSize_x) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
+}
+
+float keyDownNexuizServerList(entity me, float scan, float ascii, float shift)
+{
+	if(scan == K_ENTER)
+	{
+		ServerList_Connect_Click(NULL, me);
+		return 1;
+	}
+	else if(keyDownListBox(me, scan, ascii, shift))
+		return 1;
+	else if(!me.controlledTextbox)
+		return 0;
+	else
+		return me.controlledTextbox.keyDown(me.controlledTextbox, scan, ascii, shift);
+}
+#endif

Added: trunk/menu/nexuiz/slider.c
===================================================================
--- trunk/menu/nexuiz/slider.c	                        (rev 0)
+++ trunk/menu/nexuiz/slider.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,66 @@
+#ifdef INTERFACE
+CLASS(NexuizSlider) EXTENDS(Slider)
+	METHOD(NexuizSlider, configureNexuizSlider, void(entity, float, float, float, string))
+	METHOD(NexuizSlider, setValue, void(entity, float))
+	ATTRIB(NexuizSlider, fontSize, float, SKINFONTSIZE_NORMAL)
+	ATTRIB(NexuizSlider, valueSpace, float, SKINWIDTH_SLIDERTEXT)
+	ATTRIB(NexuizSlider, image, string, SKINGFX_SLIDER)
+	ATTRIB(NexuizSlider, tolerance, vector, SKINTOLERANCE_SLIDER)
+	ATTRIB(NexuizSlider, align, float, 0.5)
+	ATTRIB(NexuizSlider, color, vector, SKINCOLOR_SLIDER_N)
+	ATTRIB(NexuizSlider, colorC, vector, SKINCOLOR_SLIDER_C)
+	ATTRIB(NexuizSlider, colorF, vector, SKINCOLOR_SLIDER_F)
+	ATTRIB(NexuizSlider, colorD, vector, SKINCOLOR_SLIDER_D)
+	ATTRIB(NexuizSlider, color2, vector, SKINCOLOR_SLIDER_S)
+
+	ATTRIB(NexuizSlider, cvarName, string, string_null)
+	METHOD(NexuizSlider, loadCvars, void(entity))
+	METHOD(NexuizSlider, saveCvars, void(entity))
+
+	ATTRIB(NexuizSlider, alpha, float, SKINALPHA_TEXT)
+	ATTRIB(NexuizSlider, disabledAlpha, float, SKINALPHA_DISABLED)
+ENDCLASS(NexuizSlider)
+entity makeNexuizSlider(float, float, float, string);
+#endif
+
+#ifdef IMPLEMENTATION
+entity makeNexuizSlider(float theValueMin, float theValueMax, float theValueStep, string theCvar)
+{
+	entity me;
+	me = spawnNexuizSlider();
+	me.configureNexuizSlider(me, theValueMin, theValueMax, theValueStep, theCvar);
+	return me;
+}
+void configureNexuizSliderNexuizSlider(entity me, float theValueMin, float theValueMax, float theValueStep, string theCvar)
+{
+	float v, vk, vp;
+	v = theValueMin;
+	vk = theValueStep;
+	vp = theValueStep * 10;
+	while(fabs(vp) < fabs(theValueMax - theValueMin) / 40)
+		vp *= 10;
+	me.configureSliderVisuals(me, me.fontSize, me.align, me.valueSpace, me.image);
+	me.configureSliderValues(me, theValueMin, v, theValueMax, theValueStep, vk, vp);
+	if(theCvar)
+	{
+		me.cvarName = theCvar;
+		me.loadCvars(me);
+	}
+}
+void setValueNexuizSlider(entity me, float val)
+{
+	if(val != me.value)
+	{
+		me.value = val;
+		me.saveCvars(me);
+	}
+}
+void loadCvarsNexuizSlider(entity me)
+{
+	me.value = cvar(me.cvarName);
+}
+void saveCvarsNexuizSlider(entity me)
+{
+	cvar_set(me.cvarName, ftos(me.value));
+}
+#endif

Added: trunk/menu/nexuiz/slider_decibels.c
===================================================================
--- trunk/menu/nexuiz/slider_decibels.c	                        (rev 0)
+++ trunk/menu/nexuiz/slider_decibels.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,88 @@
+#ifdef INTERFACE
+CLASS(NexuizDecibelsSlider) EXTENDS(NexuizSlider)
+	METHOD(NexuizDecibelsSlider, loadCvars, void(entity))
+	METHOD(NexuizDecibelsSlider, saveCvars, void(entity))
+	METHOD(NexuizDecibelsSlider, valueToText, string(entity, float))
+ENDCLASS(NexuizDecibelsSlider)
+entity makeNexuizDecibelsSlider(float, float, float, string);
+#endif
+
+#ifdef IMPLEMENTATION
+
+float exp(float x)
+{
+	float i;
+	float t, s;
+
+	s = 1;
+	t = 1;
+	for(i = 1; i < 100; ++i)
+	{
+		t *= x;
+		t /= i;
+		s += t;
+	}
+
+	return s;
+}
+
+float ln(float x)
+{
+	float i;
+	float r, r0;
+
+	r = 1;
+	r0 = 0;
+	for(i = 1; fabs(r - r0) >= 0.05; ++i)
+	{
+		// Newton iteration on exp(r) = x:
+		//   r <- r - (exp(r) - x) / (exp(r))
+		//   r <- r - 1 + x / exp(r)
+		r0 = r;
+		r = r0 - 1 + x / exp(r0);
+	}
+	dprint("ln: ", ftos(i), " iterations\n");
+
+	return r;
+}
+
+#define LOG10 2.302585093
+
+entity makeNexuizDecibelsSlider(float theValueMin, float theValueMax, float theValueStep, string theCvar)
+{
+	entity me;
+	me = spawnNexuizDecibelsSlider();
+	me.configureNexuizSlider(me, theValueMin, theValueMax, theValueStep, theCvar);
+	return me;
+}
+void loadCvarsNexuizDecibelsSlider(entity me)
+{
+	float v;
+	v = cvar(me.cvarName);
+	if(v >= 0.98)
+		me.value = 0;
+	else if(v < 0.0005)
+		me.value = -1000000;
+	else
+		me.value = 0.1 * floor(0.5 + 10.0 * ln(cvar(me.cvarName)) * 10 / LOG10);
+}
+void saveCvarsNexuizDecibelsSlider(entity me)
+{
+	if(me.value >= -0.1)
+		cvar_set(me.cvarName, "1");
+	if(me.value < -33)
+		cvar_set(me.cvarName, "0");
+	else
+		cvar_set(me.cvarName, ftos(exp(me.value / 10 * LOG10)));
+}
+
+string valueToTextNexuizDecibelsSlider(entity me, float v)
+{
+	if(v < -33)
+		return "OFF";
+	else if(v >= -0.1)
+		return "MAX";
+	return strcat(valueToTextSlider(me, v), " dB");
+}
+
+#endif

Added: trunk/menu/nexuiz/slider_resolution.c
===================================================================
--- trunk/menu/nexuiz/slider_resolution.c	                        (rev 0)
+++ trunk/menu/nexuiz/slider_resolution.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,57 @@
+#ifdef INTERFACE
+CLASS(NexuizResolutionSlider) EXTENDS(NexuizTextSlider)
+	METHOD(NexuizResolutionSlider, configureNexuizResolutionSlider, void(entity))
+	METHOD(NexuizResolutionSlider, addResolution, void(entity, float, float, float, float))
+	METHOD(NexuizResolutionSlider, loadCvars, void(entity))
+	METHOD(NexuizResolutionSlider, saveCvars, void(entity))
+ENDCLASS(NexuizResolutionSlider)
+entity makeNexuizResolutionSlider();
+#endif
+
+#ifdef IMPLEMENTATION
+entity makeNexuizResolutionSlider()
+{
+	entity me;
+	me = spawnNexuizResolutionSlider();
+	me.configureNexuizResolutionSlider(me);
+	return me;
+}
+void addResolutionNexuizResolutionSlider(entity me, float w, float h, float cw, float ch)
+{
+	me.addValue(me, strzone(strcat(ftos(w), "x", ftos(h))), strzone(strcat(ftos(w), " ", ftos(h), " ", ftos(cw), " ", ftos(ch))));
+	// FIXME (in case you ever want to dynamically instantiate this): THIS IS NEVER FREED
+}
+void configureNexuizResolutionSliderNexuizResolutionSlider(entity me)
+{
+	me.configureNexuizTextSlider(me, "vid_width");
+	me.addResolution(me, 640, 480, 640, 480);
+	me.addResolution(me, 800, 600, 800, 600);
+	me.addResolution(me, 1024, 768, 800, 600);
+	me.addResolution(me, 1152, 864, 800, 600);
+	me.addResolution(me, 1280, 800, 800, 600);
+	me.addResolution(me, 1280, 960, 800, 600);
+	me.addResolution(me, 1280, 1024, 800, 600);
+	me.addResolution(me, 1440, 900, 800, 600);
+	me.addResolution(me, 1600, 900, 800, 600);
+	me.addResolution(me, 1600, 1200, 800, 600);
+	me.addResolution(me, 1680, 1050, 800, 600);
+	me.addResolution(me, 1920, 1200, 800, 600);
+	me.addResolution(me, 2048, 1536, 800, 600);
+	me.configureNexuizTextSliderValues(me);
+}
+void loadCvarsNexuizResolutionSlider(entity me)
+{
+	me.setValueFromIdentifier(me, strcat(cvar_string("vid_width"), " ", cvar_string("vid_height"), " ", cvar_string("vid_conwidth"), " ", cvar_string("vid_conheight")));
+}
+void saveCvarsNexuizResolutionSlider(entity me)
+{
+	if(me.value >= 0 || me.value < me.nValues)
+	{
+		tokenize(me.getIdentifier(me));
+		cvar_set("vid_width", argv(0));
+		cvar_set("vid_height", argv(1));
+		cvar_set("vid_conwidth", argv(2));
+		cvar_set("vid_conheight", argv(3));
+	}
+}
+#endif

Added: trunk/menu/nexuiz/tab.c
===================================================================
--- trunk/menu/nexuiz/tab.c	                        (rev 0)
+++ trunk/menu/nexuiz/tab.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,29 @@
+#ifdef INTERFACE
+CLASS(NexuizTab) EXTENDS(Tab)
+	// still to be customized by user
+	/*
+	ATTRIB(NexuizTab, intendedWidth, float, 0)
+	ATTRIB(NexuizTab, rows, float, 3)
+	ATTRIB(NexuizTab, columns, float, 2)
+	*/
+	METHOD(NexuizTab, showNotify, void(entity))
+
+	ATTRIB(NexuizTab, marginTop, float, 0) // pixels
+	ATTRIB(NexuizTab, marginBottom, float, 0) // pixels
+	ATTRIB(NexuizTab, marginLeft, float, 0) // pixels
+	ATTRIB(NexuizTab, marginRight, float, 0) // pixels
+	ATTRIB(NexuizTab, columnSpacing, float, SKINMARGIN_COLUMNS) // pixels
+	ATTRIB(NexuizTab, rowSpacing, float, SKINMARGIN_ROWS) // pixels
+	ATTRIB(NexuizTab, rowHeight, float, SKINFONTSIZE_NORMAL * SKINHEIGHT_NORMAL) // pixels
+	ATTRIB(NexuizTab, titleHeight, float, SKINFONTSIZE_TITLE * SKINHEIGHT_TITLE) // pixels
+
+	ATTRIB(NexuizTab, backgroundImage, string, string_null)
+ENDCLASS(NexuizTab)
+#endif
+
+#ifdef IMPLEMENTATION
+void showNotifyNexuizTab(entity me)
+{
+	loadAllCvars(me);
+}
+#endif

Added: trunk/menu/nexuiz/tabcontroller.c
===================================================================
--- trunk/menu/nexuiz/tabcontroller.c	                        (rev 0)
+++ trunk/menu/nexuiz/tabcontroller.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,34 @@
+#ifdef INTERFACE
+CLASS(NexuizTabController) EXTENDS(ModalController)
+	METHOD(NexuizTabController, configureNexuizTabController, void(entity, float))
+	METHOD(NexuizTabController, makeTabButton, entity(entity, string, entity))
+	ATTRIB(NexuizTabController, rows, float, 0)
+	ATTRIB(NexuizTabController, fontSize, float, SKINFONTSIZE_NORMAL)
+	ATTRIB(NexuizTabController, image, string, SKINGFX_BUTTON)
+ENDCLASS(NexuizTabController)
+entity makeNexuizTabController(float theRows);
+#endif
+
+#ifdef IMPLEMENTATION
+entity makeNexuizTabController(float theRows)
+{
+	entity me;
+	me = spawnNexuizTabController();
+	me.configureNexuizTabController(me, theRows);
+	return me;
+}
+void configureNexuizTabControllerNexuizTabController(entity me, float theRows)
+{
+	me.rows = theRows;
+}
+entity makeTabButtonNexuizTabController(entity me, string theTitle, entity tab)
+{
+	entity b;
+	if(me.rows != tab.rows)
+		error("Tab dialog height mismatch!");
+	b = makeNexuizButton(theTitle, '0 0 0');
+		me.addTab(me, tab, b);
+	// TODO make this real tab buttons (with color parameters, and different gfx)
+	return b;
+}
+#endif

Added: trunk/menu/nexuiz/textlabel.c
===================================================================
--- trunk/menu/nexuiz/textlabel.c	                        (rev 0)
+++ trunk/menu/nexuiz/textlabel.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,28 @@
+#ifdef INTERFACE
+CLASS(NexuizTextLabel) EXTENDS(Label)
+	METHOD(NexuizTextLabel, configureNexuizTextLabel, void(entity, float, string))
+	METHOD(NexuizTextLabel, draw, void(entity))
+	ATTRIB(NexuizTextLabel, fontSize, float, SKINFONTSIZE_NORMAL)
+	ATTRIB(NexuizTextLabel, alpha, float, SKINALPHA_TEXT)
+	ATTRIB(NexuizTextLabel, disabledAlpha, float, SKINALPHA_DISABLED)
+ENDCLASS(NexuizTextLabel)
+entity makeNexuizTextLabel(float theAlign, string theText);
+#endif
+
+#ifdef IMPLEMENTATION
+entity makeNexuizTextLabel(float theAlign, string theText)
+{
+	entity me;
+	me = spawnNexuizTextLabel();
+	me.configureNexuizTextLabel(me, theAlign, theText);
+	return me;
+}
+void configureNexuizTextLabelNexuizTextLabel(entity me, float theAlign, string theText)
+{
+	me.configureLabel(me, theText, me.fontSize, theAlign);
+}
+void drawNexuizTextLabel(entity me)
+{
+	drawLabel(me);
+}
+#endif

Added: trunk/menu/nexuiz/textslider.c
===================================================================
--- trunk/menu/nexuiz/textslider.c	                        (rev 0)
+++ trunk/menu/nexuiz/textslider.c	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,64 @@
+#ifdef INTERFACE
+CLASS(NexuizTextSlider) EXTENDS(TextSlider)
+	METHOD(NexuizTextSlider, configureNexuizTextSlider, void(entity, string))
+	METHOD(NexuizTextSlider, setValue, void(entity, float))
+	METHOD(NexuizTextSlider, configureNexuizTextSliderValues, void(entity))
+	ATTRIB(NexuizTextSlider, fontSize, float, SKINFONTSIZE_NORMAL)
+	ATTRIB(NexuizTextSlider, valueSpace, float, SKINWIDTH_SLIDERTEXT)
+	ATTRIB(NexuizTextSlider, image, string, SKINGFX_SLIDER)
+	ATTRIB(NexuizSlider, tolerance, vector, SKINTOLERANCE_SLIDER)
+	ATTRIB(NexuizTextSlider, align, float, 0.5)
+	ATTRIB(NexuizSlider, color, vector, SKINCOLOR_SLIDER_N)
+	ATTRIB(NexuizSlider, colorC, vector, SKINCOLOR_SLIDER_C)
+	ATTRIB(NexuizSlider, colorF, vector, SKINCOLOR_SLIDER_F)
+	ATTRIB(NexuizSlider, colorD, vector, SKINCOLOR_SLIDER_D)
+	ATTRIB(NexuizSlider, color2, vector, SKINCOLOR_SLIDER_S)
+
+	ATTRIB(NexuizTextSlider, cvarName, string, string_null)
+	METHOD(NexuizTextSlider, loadCvars, void(entity))
+	METHOD(NexuizTextSlider, saveCvars, void(entity))
+
+	ATTRIB(NexuizTextSlider, alpha, float, SKINALPHA_TEXT)
+	ATTRIB(NexuizTextSlider, disabledAlpha, float, SKINALPHA_DISABLED)
+ENDCLASS(NexuizTextSlider)
+entity makeNexuizTextSlider(string); // note: you still need to call addValue and configureNexuizTextSliderValues!
+#endif
+
+#ifdef IMPLEMENTATION
+entity makeNexuizTextSlider(string theCvar)
+{
+	entity me;
+	me = spawnNexuizTextSlider();
+	me.configureNexuizTextSlider(me, theCvar);
+	return me;
+}
+void configureNexuizTextSliderNexuizTextSlider(entity me, string theCvar)
+{
+	me.configureSliderVisuals(me, me.fontSize, me.align, me.valueSpace, me.image);
+	if(theCvar)
+		me.cvarName = theCvar;
+		// don't load it yet
+}
+void setValueNexuizTextSlider(entity me, float val)
+{
+	if(val != me.value)
+	{
+		me.value = val;
+		me.saveCvars(me);
+	}
+}
+void loadCvarsNexuizTextSlider(entity me)
+{
+	me.setValueFromIdentifier(me, cvar_string(me.cvarName));
+}
+void saveCvarsNexuizTextSlider(entity me)
+{
+	if(me.value >= 0 && me.value < me.nValues)
+		cvar_set(me.cvarName, me.getIdentifier(me));
+}
+void configureNexuizTextSliderValuesNexuizTextSlider(entity me)
+{
+	me.configureTextSliderValues(me, string_null);
+	me.loadCvars(me);
+}
+#endif

Added: trunk/menu/nexuiz/util.qc
===================================================================
--- trunk/menu/nexuiz/util.qc	                        (rev 0)
+++ trunk/menu/nexuiz/util.qc	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,261 @@
+void forAllDescendants(entity root, void(entity, entity) funcPre, void(entity, entity) funcPost, entity pass)
+{
+	depthfirst(root, parent, firstChild, nextSibling, funcPre, funcPost, pass);
+}
+
+.string cvarName;
+void SUB_Null_ee(entity e1, entity e2)
+{
+}
+void saveCvarsOf(entity ignore, entity e)
+{
+	if(e.cvarName)
+		e.saveCvars(e);
+}
+void loadCvarsOf(entity ignore, entity e)
+{
+	if(e.cvarName)
+		e.loadCvars(e);
+}
+void saveAllCvars(entity root)
+{
+	forAllDescendants(root, saveCvarsOf, SUB_Null_ee, NULL);
+}
+void loadAllCvars(entity root)
+{
+	forAllDescendants(root, loadCvarsOf, SUB_Null_ee, NULL);
+}
+
+.void(entity) draw_setDependent;
+.string cvar_setDependent;
+.float cvarMin_setDependent;
+.float cvarMax_setDependent;
+.string cvar2_setDependent;
+.float cvar2Min_setDependent;
+.float cvar2Max_setDependent;
+.float op_setDependent;
+.string cvarString_setDependent;
+.string cvarValue_setDependent;
+void setDependent_Check(entity e)
+{
+	float f;
+	string s;
+	if(e.cvarString_setDependent)
+	{
+		s = cvar_string(e.cvarString_setDependent);
+		e.disabled = (cvar_string(e.cvarString_setDependent) == e.cvarValue_setDependent);
+	}
+	else
+	{
+		if(e.cvar_setDependent)
+		{
+			f = cvar(e.cvar_setDependent);
+			if(e.cvarMin_setDependent <= e.cvarMax_setDependent)
+				e.disabled = ((f < e.cvarMin_setDependent) || (f > e.cvarMax_setDependent));
+			else
+				e.disabled = ((f >= e.cvarMax_setDependent) && (f <= e.cvarMin_setDependent));
+		}
+		if(e.cvar2_setDependent)
+		{
+			f = cvar(e.cvar2_setDependent);
+			if(e.cvar2Min_setDependent <= e.cvar2Max_setDependent)
+				e.disabled = (e.disabled + ((f < e.cvar2Min_setDependent) || (f > e.cvar2Max_setDependent)) > e.op_setDependent);
+			else
+				e.disabled = (e.disabled + ((f >= e.cvar2Max_setDependent) && (f <= e.cvar2Min_setDependent)) > e.op_setDependent);
+		}
+	}
+}
+void setDependent_Draw(entity e)
+{
+	setDependent_Check(e);
+	e.draw_setDependent(e);
+}
+void setDependent(entity e, string theCvarName, float theCvarMin, float theCvarMax)
+{
+	e.draw_setDependent = e.draw;
+	e.cvar_setDependent = theCvarName;
+	e.cvarMin_setDependent = theCvarMin;
+	e.cvarMax_setDependent = theCvarMax;
+	e.cvar2_setDependent = string_null;
+	e.draw = setDependent_Draw;
+	setDependent_Check(e);
+}
+void setDependentStringNotEqual(entity e, string theCvarName, string theCvarValue)
+{
+	e.draw_setDependent = e.draw;
+	e.cvarString_setDependent = theCvarName;
+	e.cvarValue_setDependent = theCvarValue;
+	e.cvar_setDependent = string_null;
+	e.cvar2_setDependent = string_null;
+	e.draw = setDependent_Draw;
+	setDependent_Check(e);
+}
+void setDependentAND(entity e, string theCvarName, float theCvarMin, float theCvarMax, string theCvar2Name, float theCvar2Min, float theCvar2Max)
+{
+	e.draw_setDependent = e.draw;
+	e.cvar_setDependent = theCvarName;
+	e.cvarMin_setDependent = theCvarMin;
+	e.cvarMax_setDependent = theCvarMax;
+	e.cvar2_setDependent = theCvar2Name;
+	e.cvar2Min_setDependent = theCvar2Min;
+	e.cvar2Max_setDependent = theCvar2Max;
+	e.op_setDependent = 0;
+	e.draw = setDependent_Draw;
+	setDependent_Check(e);
+}
+void setDependentOR(entity e, string theCvarName, float theCvarMin, float theCvarMax, string theCvar2Name, float theCvar2Min, float theCvar2Max)
+{
+	e.draw_setDependent = e.draw;
+	e.cvar_setDependent = theCvarName;
+	e.cvarMin_setDependent = theCvarMin;
+	e.cvarMax_setDependent = theCvarMax;
+	e.cvar2_setDependent = theCvar2Name;
+	e.cvar2Min_setDependent = theCvar2Min;
+	e.cvar2Max_setDependent = theCvar2Max;
+	e.op_setDependent = 1;
+	e.draw = setDependent_Draw;
+	setDependent_Check(e);
+}
+
+// EXTRESPONSE SYSTEM ////////////////////////////////////////////////////////
+
+float _Nex_ExtResponseSystem_RequestsSent;
+float _Nex_ExtResponseSystem_VersionHandled;
+string _Nex_ExtResponseSystem_UpdateTo;
+float _Nex_ExtResponseSystem_RetryTime;
+float _Nex_ExtResponseSystem_RetryTime_LastDelay;
+
+void() Item_Nex_ExtResponseSystem_SendQuery =
+{
+	dprint("Sending extended response requests...\n");
+	localcmd(strcat("packet update.alientrap.org:27950 \"getExtResponse checkUpdates nexuiz ", cvar_string("g_nexuizversion"), "\"\n"));
+	_Nex_ExtResponseSystem_RequestsSent = TRUE;
+	_Nex_ExtResponseSystem_RetryTime_LastDelay = _Nex_ExtResponseSystem_RetryTime_LastDelay * 2 + 1;
+	_Nex_ExtResponseSystem_RetryTime = time + _Nex_ExtResponseSystem_RetryTime_LastDelay;
+}
+
+void(float argc) Item_Nex_ExtResponseSystem_Parse =
+{
+	dprint("Received extended response packet from ", argv(0), "\n");
+	if(!_Nex_ExtResponseSystem_RequestsSent)
+	{
+		dprint("  But I haven't sent a request yet! Ignoring.\n");
+		return;
+	}
+	if(argv(1) == "noUpdateAvailable")
+	{
+		if(_Nex_ExtResponseSystem_VersionHandled)
+		{
+			dprint("  duplicated update notice, ignored\n");
+			return;
+		}
+		_Nex_ExtResponseSystem_VersionHandled = 1;
+	}
+	else if(argv(1) == "updateAvailable")
+	{
+		if(_Nex_ExtResponseSystem_VersionHandled)
+		{
+			dprint("  duplicated update notice, ignored\n");
+			return;
+		}
+		_Nex_ExtResponseSystem_VersionHandled = 1;
+		_Nex_ExtResponseSystem_UpdateTo = strzone(argv(2)); // note: only one packet can be handled, so this can't be a leak
+	}
+	else
+		dprint("  UNKNOWN RESPONSE TYPE: ", argv(1), "\n");
+}
+
+void() Item_Nex_ExtResponseSystem_CheckForResponse =
+{
+	local string s;
+	local float argc;
+	while(strlen((s = getextresponse())))
+	{
+		argc = tokenize(s);
+		Item_Nex_ExtResponseSystem_Parse(argc);
+	}
+}
+
+// END OF EXTRESPONSE SYSTEM /////////////////////////////////////////////////
+
+float preMenuInit()
+{
+	vector sz;
+	vector boxA, boxB;
+
+	MapInfo_Cache_Create();
+	MapInfo_Enumerate();
+	if(!MapInfo_FilterGametype(MAPINFO_TYPE_ALL, 0, 1))
+	{
+		draw_reset();
+
+		sz = eX * 0.025 + eY * 0.025 * (draw_scale_x / draw_scale_y);
+		draw_CenterText('0.5 0.5 0' - 1.25 * sz_y * eY, "Autogenerating mapinfo for newly added maps...", sz, '1 1 1', 1, 0);
+
+		boxA = '0.05 0.5 0' + 0.25 * sz_y * eY;
+		boxB = '0.95 0.5 0' + 1.25 * sz_y * eY;
+		draw_Fill(boxA, boxB - boxA, '1 1 1', 1);
+		
+		boxA += sz * 0.1;
+		boxB -= sz * 0.1;
+		draw_Fill(boxA, boxB - boxA, '0.1 0.1 0.1', 1);
+
+		boxB_x = boxA_x * (1 - MapInfo_progress) + boxB_x * MapInfo_progress;
+		draw_Fill(boxA, boxB - boxA, '0 0 1', 1);
+
+		return FALSE;
+	}
+	return TRUE;
+}
+
+string campaign_name_previous;
+float campaign_won_previous;
+void postMenuDraw()
+{
+}
+void preMenuDraw()
+{
+	vector fs, sz, line, mid;
+	Item_Nex_ExtResponseSystem_CheckForResponse();
+	if(!_Nex_ExtResponseSystem_VersionHandled)
+		if(time > _Nex_ExtResponseSystem_RetryTime)
+			Item_Nex_ExtResponseSystem_SendQuery();
+
+	if(_Nex_ExtResponseSystem_UpdateTo != "")
+	{
+		fs = ((1/draw_scale_x) * eX + (1/draw_scale_y) * eY) * 12;
+		line = eY * fs_y;
+		sz_x = draw_TextWidth("  http://www.nexuiz.com/  ", 0) * fs_x;
+		sz_y = 3 * fs_y;
+
+		draw_alpha = sin(time * 0.112 - 0.3) * 0.7;
+		mid = eX * (0.5 + 0.5 * (1 - sz_x) * cos(time * 0.071))
+		    + eY * (0.5 + 0.5 * (1 - sz_y) * sin(time * 0.071));
+
+		draw_Fill(mid - 0.5 * sz, sz, '1 1 0', 1);
+		draw_CenterText(mid - 1 * line, strcat("Update to ", _Nex_ExtResponseSystem_UpdateTo, " now!"), fs, '1 0 0', 1, 0);
+		draw_CenterText(mid - 0 * line, "http://www.nexuiz.com/", fs, '0 0 1', 1, 0);
+	}
+	if not(campaign_name_previous)
+		campaign_name_previous = strzone(strcat(campaign_name, "x")); // force unequal
+	if(campaign_name == campaign_name_previous)
+	{
+		if(cvar(strcat("g_campaign", campaign_name, "_won")))
+		{
+			if(!campaign_won_previous)
+			{
+				m_display();
+				DialogOpenButton_Click_withCoords(NULL, main.winnerDialog, '0 0 0', eX * conwidth + eY * conheight);
+			}
+			campaign_won_previous = 1;
+		}
+		else
+			campaign_won_previous = 0;
+	}
+	else
+	{
+		strunzone(campaign_name_previous);
+		campaign_name_previous = strzone(campaign_name);
+		campaign_won_previous = cvar(strcat("g_campaign", campaign_name, "_won"));
+	}
+}

Added: trunk/menu/nexuiz/util.qh
===================================================================
--- trunk/menu/nexuiz/util.qh	                        (rev 0)
+++ trunk/menu/nexuiz/util.qh	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,8 @@
+void forAllDescendants(entity root, void(entity, entity) funcPre, void(entity, entity) funcPost, entity pass);
+void saveAllCvars(entity root);
+void loadAllCvars(entity root);
+
+void setDependent(entity e, string theCvarName, float theCvarMin, float theCvarMax);
+void setDependentAND(entity e, string theCvarName, float theCvarMin, float theCvarMax, string theCvar2Name, float theCvar2Min, float theCvar2Max);
+void setDependentOR(entity e, string theCvarName, float theCvarMin, float theCvarMax, string theCvar2Name, float theCvar2Min, float theCvar2Max);
+void setDependentStringNotEqual(entity e, string theCvarName, string theCvarValue);

Added: trunk/menu/oo/base.h
===================================================================
--- trunk/menu/oo/base.h	                        (rev 0)
+++ trunk/menu/oo/base.h	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,8 @@
+.string classname;
+entity spawnObject()
+{
+	entity e;
+	e = spawn();
+	e.classname = "Object";
+	return e;
+}

Added: trunk/menu/oo/classdefs.h
===================================================================
--- trunk/menu/oo/classdefs.h	                        (rev 0)
+++ trunk/menu/oo/classdefs.h	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,23 @@
+#ifndef INTERFACE
+#define INTERFACE
+#endif
+
+#ifdef IMPLEMENTATION
+#undef IMPLEMENTATION
+#endif
+
+#ifdef CLASS
+#undef CLASS
+#undef EXTENDS
+#undef METHOD
+#undef ATTRIB
+#undef ATTRIBARRAY
+#undef ENDCLASS
+#endif
+
+#define CLASS(cname)                       entity spawn##cname();
+#define EXTENDS(base)                
+#define METHOD(cname,name,prototype)       prototype name##cname; .prototype name;
+#define ATTRIB(cname,name,type,val)        .type name;
+#define ATTRIBARRAY(cname,name,type,cnt)   .type name[cnt];
+#define ENDCLASS(cname)                    .float instanceOf##cname;

Added: trunk/menu/oo/constructors.h
===================================================================
--- trunk/menu/oo/constructors.h	                        (rev 0)
+++ trunk/menu/oo/constructors.h	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,23 @@
+#ifndef INTERFACE
+#define INTERFACE
+#endif
+
+#ifdef IMPLEMENTATION
+#undef IMPLEMENTATION
+#endif
+
+#ifdef CLASS
+#undef CLASS
+#undef EXTENDS
+#undef METHOD
+#undef ATTRIB
+#undef ATTRIBARRAY
+#undef ENDCLASS
+#endif
+
+#define CLASS(cname)                       entity spawn##cname() { entity me;
+#define EXTENDS(base)                      me = spawn##base ();
+#define METHOD(cname,name,prototype)       me.name = name##cname;
+#define ATTRIB(cname,name,type,val)        me.name = val;
+#define ATTRIBARRAY(cname,name,type,cnt)   me.name = me.name;
+#define ENDCLASS(cname)                    me.instanceOf##cname = 1; me.classname = #cname; return me; }

Added: trunk/menu/oo/implementation.h
===================================================================
--- trunk/menu/oo/implementation.h	                        (rev 0)
+++ trunk/menu/oo/implementation.h	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,16 @@
+#ifdef INTERFACE
+#undef INTERFACE
+#endif
+
+#ifndef IMPLEMENTATION
+#define IMPLEMENTATION
+#endif
+
+#ifdef CLASS
+#undef CLASS
+#undef EXTENDS
+#undef METHOD
+#undef ATTRIB
+#undef ATTRIBARRAY
+#undef ENDCLASS
+#endif

Added: trunk/menu/progs.src
===================================================================
--- trunk/menu/progs.src	                        (rev 0)
+++ trunk/menu/progs.src	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,36 @@
+../../menu.dat
+
+config.qh
+msys.qh
+mbuiltin.qh
+
+oo/base.h
+
+../common/util.qh
+../common/mapinfo.qh
+../common/campaign_common.qh
+
+gamecommand.qh
+menu.qh
+draw.qh
+skin.qh
+nexuiz/util.qh
+
+oo/classdefs.h
+	classes.c
+
+oo/constructors.h
+	classes.c
+oo/implementation.h
+	classes.c
+
+../common/util.qc
+../common/gamecommand.qc
+gamecommand.qc
+menu.qc
+draw.qc
+nexuiz/util.qc
+
+../common/campaign_file.qc
+../common/campaign_setup.qc
+../common/mapinfo.qc

Added: trunk/menu/skin-customizables.inc
===================================================================
--- trunk/menu/skin-customizables.inc	                        (rev 0)
+++ trunk/menu/skin-customizables.inc	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,192 @@
+#if 0
+"Perl code to convert this to a skinvalues.txt file.";
+while(<DATA>)
+{
+	chomp;
+	if(/^\s*(?:SKINFLOAT|SKINVECTOR)\(([A-Z_]+), ([-'0-9. ]+)\);$/)
+	{
+		printf "%-31s %s\n", $1, $2;
+	}
+	elsif(/^\s*SKINSTRING\(([A-Z_]+), "(.*)"\);$/)
+	{
+		printf "//   uses \"$2\" images\n";
+	}
+	elsif(/^$/)
+	{
+		print "\n";
+	}
+	elsif(/^\s+\/\/ (.*)$/)
+	{
+		print "// $1\n";
+	}
+	elsif(/^SKINBEGIN$|^SKINEND$|^#endif$/)
+	{
+	}
+	else
+	{
+		print "!!! $_\n";
+	}
+}
+__DATA__
+#endif
+SKINBEGIN
+	// font sizes (used for everything)
+	SKINFLOAT(FONTSIZE_NORMAL, 12);
+	SKINFLOAT(HEIGHT_NORMAL, 1.5);
+	SKINFLOAT(FONTSIZE_TITLE, 16);
+	SKINFLOAT(HEIGHT_TITLE, 1.5);
+	SKINFLOAT(HEIGHT_ZOOMEDTITLE, -1);
+
+	// the individual dialog background colors
+	SKINVECTOR(COLOR_DIALOG_MULTIPLAYER, '0.7 0.7 1');
+	SKINVECTOR(COLOR_DIALOG_SETTINGS, '0.7 0.7 1');
+	SKINVECTOR(COLOR_DIALOG_TEAMSELECT, '1 1 1');
+	SKINVECTOR(COLOR_DIALOG_QUIT, '1 0 0');
+	SKINVECTOR(COLOR_DIALOG_MUTATORS, '0.7 0.7 1');
+	SKINVECTOR(COLOR_DIALOG_MAPINFO, '0.7 0.7 1');
+	SKINVECTOR(COLOR_DIALOG_USERBIND, '0.7 0.7 1');
+	SKINVECTOR(COLOR_DIALOG_SINGLEPLAYER, '1 1 0.7');
+	SKINVECTOR(COLOR_DIALOG_CREDITS, '0.7 0.7 1');
+
+	// nexposee positions of windows (they are the scale transformation
+	// centers, NOT the actual positions of the windows!)
+	SKINVECTOR(POSITION_DIALOG_MULTIPLAYER, '0.9 0.5 0');
+	SKINVECTOR(POSITION_DIALOG_SINGLEPLAYER, '0.1 0.1 0');
+	SKINVECTOR(POSITION_DIALOG_SETTINGS, '0.1 0.9 0');
+	SKINVECTOR(POSITION_DIALOG_CREDITS, '0.3 1.2 0');
+	SKINVECTOR(POSITION_DIALOG_QUIT, '0.9 1.2 0');
+
+	// mouse
+	SKINSTRING(GFX_CURSOR, "cursor");
+	SKINVECTOR(SIZE_CURSOR, '32 32 0');
+	SKINVECTOR(OFFSET_CURSOR, '0 0 0');
+	SKINFLOAT(ALPHA_CURSOR_INTRO, 0);
+
+	// general
+	SKINSTRING(GFX_BACKGROUND, "background");
+	SKINSTRING(GFX_BACKGROUND_INGAME, "background_ingame");
+	SKINFLOAT(ALPHA_BACKGROUND_INGAME, 0.7);
+	SKINFLOAT(ALPHA_DISABLED, 0.2);
+	SKINFLOAT(ALPHA_BEHIND, 0.5);
+	SKINFLOAT(ALPHA_TEXT, 0.7);
+
+	// item: button
+	SKINSTRING(GFX_BUTTON, "button");
+	SKINSTRING(GFX_BUTTON_GRAY, "buttongray");
+	SKINVECTOR(COLOR_BUTTON_N, '1 1 1');
+	SKINVECTOR(COLOR_BUTTON_C, '1 1 1');
+	SKINVECTOR(COLOR_BUTTON_F, '1 1 1');
+	SKINVECTOR(COLOR_BUTTON_D, '1 1 1');
+
+	// item: campaign
+	SKINFLOAT(ALPHA_CAMPAIGN_SELECTABLE, 0.8);
+	SKINVECTOR(COLOR_CAMPAIGN_SELECTABLE, '1 1 1');
+	SKINFLOAT(ALPHA_CAMPAIGN_CURRENT, 1);
+	SKINVECTOR(COLOR_CAMPAIGN_CURRENT, '1 1 0');
+	SKINFLOAT(ALPHA_CAMPAIGN_FUTURE, 0.2);
+	SKINVECTOR(COLOR_CAMPAIGN_FUTURE, '1 1 1');
+	SKINFLOAT(ALPHA_CAMPAIGN_DESCRIPTION, 0.7);
+
+	// item: checkbox
+	SKINSTRING(GFX_CHECKBOX, "checkbox");
+	SKINVECTOR(COLOR_CHECKBOX_N, '1 1 1');
+	SKINVECTOR(COLOR_CHECKBOX_C, '1 1 1');
+	SKINVECTOR(COLOR_CHECKBOX_F, '1 1 1');
+	SKINVECTOR(COLOR_CHECKBOX_D, '1 1 1');
+
+	// item: credits list
+	SKINVECTOR(COLOR_CREDITS_TITLE, '1 1 1');
+	SKINFLOAT(ALPHA_CREDITS_TITLE, 1);
+	SKINVECTOR(COLOR_CREDITS_FUNCTION, '1 1 1');
+	SKINFLOAT(ALPHA_CREDITS_FUNCTION, 0.7);
+	SKINVECTOR(COLOR_CREDITS_PERSON, '0.7 0.7 1');
+	SKINFLOAT(ALPHA_CREDITS_PERSON, 0.7);
+	SKINFLOAT(ROWS_CREDITS, 20);
+	SKINFLOAT(WIDTH_CREDITS, 0.5);
+
+	// item: crosshair button
+	SKINSTRING(GFX_CROSSHAIRBUTTON, "crosshairbutton");
+
+	// item: dialog
+	SKINSTRING(GFX_DIALOGBORDER, "border");
+	SKINSTRING(GFX_CLOSEBUTTON, "closebutton");
+	SKINFLOAT(MARGIN_TOP, 8);
+	SKINFLOAT(MARGIN_BOTTOM, 8);
+	SKINFLOAT(MARGIN_LEFT, 8);
+	SKINFLOAT(MARGIN_RIGHT, 8);
+	SKINFLOAT(MARGIN_COLUMNS, 4);
+	SKINFLOAT(MARGIN_ROWS, 4);
+
+	// item: input box
+	SKINSTRING(GFX_INPUTBOX, "inputbox");
+	SKINVECTOR(COLOR_INPUTBOX_N, '1 1 1');
+	SKINVECTOR(COLOR_INPUTBOX_F, '1 1 1');
+	SKINFLOAT(MARGIN_INPUTBOX, 0.02);
+
+	// item: key grabber
+	SKINVECTOR(COLOR_KEYGRABBER_TITLES, '1 1 1');
+	SKINFLOAT(ALPHA_KEYGRABBER_TITLES, 1);
+	SKINVECTOR(COLOR_KEYGRABBER_KEYS, '1 1 1');
+	SKINFLOAT(ALPHA_KEYGRABBER_KEYS, 0.7);
+
+	// item: list box
+	SKINVECTOR(COLOR_LISTBOX_SELECTED, '0 0 1');
+	SKINFLOAT(ALPHA_LISTBOX_SELECTED, 0.5);
+	SKINVECTOR(COLOR_LISTBOX_WAITING, '1 0 0');
+	SKINFLOAT(ALPHA_LISTBOX_WAITING, 0.5);
+
+	// item: map list
+	SKINVECTOR(COLOR_MAPLIST_TITLE, '1 1 1');
+	SKINVECTOR(COLOR_MAPLIST_AUTHOR, '0.4 0.4 0.7');
+	SKINVECTOR(COLOR_MAPLIST_INCLUDEDBG, '0 0 0');
+	SKINFLOAT(ALPHA_MAPLIST_INCLUDEDFG, 1);
+	SKINFLOAT(ALPHA_MAPLIST_INCLUDEDBG, 0.5);
+	SKINFLOAT(ALPHA_MAPLIST_NOTINCLUDEDFG, 0.4);
+
+	// item: nexposee
+	SKINVECTOR(ALPHAS_MAINMENU, '0.6 0.8 0.9');
+
+	// item: player color button
+	SKINSTRING(GFX_COLORBUTTON, "colorbutton");
+	SKINSTRING(GFX_COLORBUTTON_COLOR, "color");
+
+	// item: player model
+	SKINVECTOR(COLOR_MODELTITLE, '1 1 1');
+	SKINFLOAT(ALPHA_MODELTITLE, 1);
+
+	// item: player name editor
+	SKINSTRING(GFX_CHARMAP, "charmap");
+	SKINSTRING(GFX_CHARMAP_SELECTED, "charmapbutton");
+
+	// item: radio button
+	SKINSTRING(GFX_RADIOBUTTON, "radiobutton");
+	SKINVECTOR(COLOR_RADIOBUTTON_N, '1 1 1');
+	SKINVECTOR(COLOR_RADIOBUTTON_C, '1 1 1');
+	SKINVECTOR(COLOR_RADIOBUTTON_F, '1 1 1');
+	SKINVECTOR(COLOR_RADIOBUTTON_D, '1 1 1');
+
+	// item: scrollbar
+	SKINSTRING(GFX_SCROLLBAR, "scrollbar");
+	SKINVECTOR(COLOR_SCROLLBAR_N, '1 1 1');
+	SKINVECTOR(COLOR_SCROLLBAR_F, '1 1 1');
+	SKINVECTOR(COLOR_SCROLLBAR_S, '1 1 1');
+	SKINFLOAT(WIDTH_SCROLLBAR, 16);
+
+	// item: server list
+	SKINFLOAT(ALPHA_SERVERLIST_FULL, 0.2);
+	SKINFLOAT(ALPHA_SERVERLIST_EMPTY, 0.5);
+	SKINVECTOR(COLOR_SERVERLIST_LOWPING, '0 1 0');
+	SKINVECTOR(COLOR_SERVERLIST_MEDPING, '1 1 0');
+	SKINVECTOR(COLOR_SERVERLIST_HIGHPING, '1 0 0');
+	SKINFLOAT(ALPHA_SERVERLIST_HIGHPING, 0.2);
+
+	// item: slider
+	SKINSTRING(GFX_SLIDER, "slider");
+	SKINVECTOR(COLOR_SLIDER_N, '1 1 1');
+	SKINVECTOR(COLOR_SLIDER_C, '1 1 1');
+	SKINVECTOR(COLOR_SLIDER_F, '1 1 1');
+	SKINVECTOR(COLOR_SLIDER_D, '1 1 1');
+	SKINVECTOR(COLOR_SLIDER_S, '1 1 1');
+	SKINFLOAT(WIDTH_SLIDERTEXT, 0.333333333333);
+	SKINVECTOR(TOLERANCE_SLIDER, '0.2 2 0');
+SKINEND

Added: trunk/menu/skin.qh
===================================================================
--- trunk/menu/skin.qh	                        (rev 0)
+++ trunk/menu/skin.qh	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1,23 @@
+#define SKINBEGIN
+#define SKINVECTOR(name,def) var vector SKIN##name = def
+#define SKINFLOAT(name,def) var float SKIN##name = def
+#define SKINSTRING(name,def) const string SKIN##name = def
+#define SKINEND
+#include "skin-customizables.inc"
+#undef SKINEND
+#undef SKINBEGIN
+#undef SKINSTRING
+#undef SKINFLOAT
+#undef SKINVECTOR
+
+#define SKINBEGIN void Skin_ApplySetting(string key, string value) { switch(key) {
+#define SKINVECTOR(name,def) case #name: SKIN##name = stov(value); break
+#define SKINFLOAT(name,def) case #name: SKIN##name = stof(value); break
+#define SKINSTRING(name,def) break
+#define SKINEND case "": break; case "//": break; default: print("Invalid key in skin file: ", key, "\n"); } }
+#include "skin-customizables.inc"
+#undef SKINEND
+#undef SKINSTRING
+#undef SKINFLOAT
+#undef SKINVECTOR
+#undef SKINBEGIN

Added: trunk/menu/todo
===================================================================
--- trunk/menu/todo	                        (rev 0)
+++ trunk/menu/todo	2008-08-30 06:46:38 UTC (rev 122)
@@ -0,0 +1 @@
+TODO credits dialog




More information about the zymotic-commits mailing list