1 /**
2  * @file
3  * @todo remove all "token" param from function and use Com_UnParseLastToken
4  * @todo reduce use of uiGlobal (create global functions to add/get/... entities)
5  * @todo remove Com_EParseValue and use Com_ParseValue
6  */
7 
8 /*
9 Copyright (C) 2002-2013 UFO: Alien Invasion.
10 
11 This program is free software; you can redistribute it and/or
12 modify it under the terms of the GNU General Public License
13 as published by the Free Software Foundation; either version 2
14 of the License, or (at your option) any later version.
15 
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
19 
20 See the GNU General Public License for more details.
21 
22 You should have received a copy of the GNU General Public License
23 along with this program; if not, write to the Free Software
24 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
25 
26 */
27 
28 #include "../client.h"
29 #include "ui_parse.h"
30 #include "ui_main.h"
31 #include "ui_node.h"
32 #include "ui_data.h"
33 #include "ui_internal.h"
34 #include "ui_actions.h"
35 #include "ui_sprite.h"
36 #include "ui_components.h"
37 #include "node/ui_node_window.h"
38 #include "node/ui_node_selectbox.h"
39 #include "node/ui_node_abstractnode.h"
40 #include "node/ui_node_abstractoption.h"
41 
42 #include "../../shared/parse.h"
43 #include "../cl_language.h"
44 
45 /** prototypes */
46 static bool UI_ParseProperty(void* object, const value_t* property, const char* objectName, const char** text, const char** token);
47 static uiAction_t* UI_ParseActionList(uiNode_t* node, const char** text, const char** token);
48 static uiNode_t* UI_ParseNode(uiNode_t* parent, const char** text, const char** token, const char* errhead);
49 
50 /** @brief valid properties for a UI model definition */
51 static const value_t uiModelProperties[] = {
52 	{"model", V_HUNK_STRING, offsetof(uiModel_t, model), 0},
53 	{"need", V_NULL, 0, 0},
54 	{"anim", V_HUNK_STRING, offsetof(uiModel_t, anim), 0},
55 	{"skin", V_INT, offsetof(uiModel_t, skin), sizeof(int)},
56 	{"color", V_COLOR, offsetof(uiModel_t, color), sizeof(vec4_t)},
57 	{"tag", V_HUNK_STRING, offsetof(uiModel_t, tag), 0},
58 	{"parent", V_HUNK_STRING, offsetof(uiModel_t, parent), 0},
59 
60 	{nullptr, V_NULL, 0, 0},
61 };
62 
63 /** @brief reserved token preventing calling a node with it
64  * @todo Use dichotomic search
65  */
66 static char const* const reservedTokens[] = {
67 	"this",
68 	"parent",
69 	"root",
70 	"null",
71 	"super",
72 	"node",
73 	"cvar",
74 	"int",
75 	"float",
76 	"string",
77 	"var",
78 	nullptr
79 };
80 
UI_TokenIsReserved(const char * name)81 static bool UI_TokenIsReserved (const char* name)
82 {
83 	char const* const* token = reservedTokens;
84 	while (*token) {
85 		if (Q_streq(*token, name))
86 			return true;
87 		token++;
88 	}
89 	return false;
90 }
91 
UI_TokenIsValue(const char * name,bool isQuoted)92 static bool UI_TokenIsValue (const char* name, bool isQuoted)
93 {
94 	assert(name);
95 	if (isQuoted)
96 		return true;
97 	/* is it a number */
98 	if ((name[0] >= '0' && name[0] <= '9') || name[0] == '-' || name[0] == '.')
99 		return true;
100 	/* is it a var (*cvar:...) */
101 	if (name[0] == '*')
102 		return true;
103 	if (Q_streq(name, "true"))
104 		return true;
105 	if (Q_streq(name, "false"))
106 		return true;
107 
108 	/* uppercase const name */
109 	if ((name[0] >= 'A' && name[0] <= 'Z') || name[0] == '_') {
110 		bool onlyUpperCase = true;
111 		while (*name != '\0') {
112 			if ((name[0] >= 'A' && name[0] <= 'Z') || name[0] == '_' || (name[0] >= '0' && name[0] <= '9')) {
113 				/* available chars */
114 			} else {
115 				return false;
116 			}
117 			name++;
118 		}
119 		return onlyUpperCase;
120 	}
121 
122 	return false;
123 }
124 
UI_TokenIsName(const char * name,bool isQuoted)125 static bool UI_TokenIsName (const char* name, bool isQuoted)
126 {
127 	assert(name);
128 	if (isQuoted)
129 		return false;
130 	if ((name[0] >= 'a' && name[0] <= 'z') || (name[0] >= 'A' && name[0] <= 'Z') || name[0] == '_') {
131 		bool onlyUpperCase = true;
132 		while (*name != '\0') {
133 			if (name[0] >= 'a' && name[0] <= 'z') {
134 				onlyUpperCase = false;
135 			} else if ((name[0] >= '0' && name[0] <= '9') || (name[0] >= 'A' && name[0] <= 'Z') || name[0] == '_') {
136 				/* available chars */
137 			} else {
138 				return false;
139 			}
140 			name++;
141 		}
142 		return !onlyUpperCase;
143 	}
144 	return false;
145 }
146 
147 /**
148  * @brief Find a value_t by name into a array of value_t
149  * @param[in] propertyList Array of value_t, with null termination
150  * @param[in] name Property name we search
151  * @return A value_t with the requested name, else nullptr
152  */
UI_FindPropertyByName(const value_t * propertyList,const char * name)153 const value_t* UI_FindPropertyByName (const value_t* propertyList, const char* name)
154 {
155 	const value_t* current = propertyList;
156 	while (current->string != nullptr) {
157 		if (!Q_strcasecmp(name, current->string))
158 			return current;
159 		current++;
160 	}
161 	return nullptr;
162 }
163 
164 /**
165  * @brief Allocate a float into the UI static memory
166  * @note Its not a dynamic memory allocation. Please only use it at the loading time
167  * @param[in] count number of element need to allocate
168  * @todo Assert out when we are not in parsing/loading stage
169  */
UI_AllocStaticFloat(int count)170 float* UI_AllocStaticFloat (int count)
171 {
172 	float* result;
173 	assert(count > 0);
174 	result = (float*) UI_AllocHunkMemory(sizeof(float) * count, sizeof(float), false);
175 	if (result == nullptr)
176 		Com_Error(ERR_FATAL, "UI_AllocFloat: UI memory hunk exceeded - increase the size");
177 	return result;
178 }
179 
180 /**
181  * @brief Allocate a color into the UI static memory
182  * @note Its not a dynamic memory allocation. Please only use it at the loading time
183  * @param[in] count number of element need to allocate
184  * @todo Assert out when we are not in parsing/loading stage
185  */
UI_AllocStaticColor(int count)186 vec4_t* UI_AllocStaticColor (int count)
187 {
188 	vec4_t* result;
189 	assert(count > 0);
190 	result = (vec4_t*) UI_AllocHunkMemory(sizeof(vec_t) * 4 * count, sizeof(vec_t), false);
191 	if (result == nullptr)
192 		Com_Error(ERR_FATAL, "UI_AllocColor: UI memory hunk exceeded - increase the size");
193 	return result;
194 }
195 
196 /**
197  * @brief Allocate a string into the UI static memory
198  * @note Its not a dynamic memory allocation. Please only use it at the loading time
199  * @param[in] string Use to initialize the string
200  * @param[in] size request a fixed memory size, if 0 the string size is used
201  * @todo Assert out when we are not in parsing/loading stage
202  */
UI_AllocStaticString(const char * string,int size)203 char* UI_AllocStaticString (const char* string, int size)
204 {
205 	char* result;
206 	if (size == 0) {
207 		size = strlen(string) + 1;
208 	}
209 	result = (char*) UI_AllocHunkMemory(size, sizeof(char), false);
210 	if (result == nullptr)
211 		Com_Error(ERR_FATAL, "UI_AllocString: UI memory hunk exceeded - increase the size");
212 	Q_strncpyz(result, string, size);
213 	return result;
214 }
215 
216 /**
217  * @brief Allocate an action
218  * @return An action
219  */
UI_AllocStaticAction(void)220 uiAction_t* UI_AllocStaticAction (void)
221 {
222 	if (ui_global.numActions >= UI_MAX_ACTIONS)
223 		Com_Error(ERR_FATAL, "UI_AllocAction: Too many UI actions");
224 	return &ui_global.actions[ui_global.numActions++];
225 }
226 
227 /**
228  * Parse a string according to a property type, and allocate a raw value to the static memory
229  *
230  * @param action Action to initialize
231  * @param node Current node we are parsing, only used for error message
232  * @param property Type of the value to parse, if nullptr the string is not stored as string
233  * @param string String value to parse
234  * @return True if the action is initialized
235  * @todo remove node param and catch error where we call that function
236  */
UI_InitRawActionValue(uiAction_t * action,uiNode_t * node,const value_t * property,const char * string)237 bool UI_InitRawActionValue (uiAction_t* action, uiNode_t* node, const value_t* property, const char* string)
238 {
239 	if (property == nullptr) {
240 		action->type = EA_VALUE_STRING;
241 		action->d.terminal.d1.data = UI_AllocStaticString(string, 0);
242 		action->d.terminal.d2.integer = 0;
243 		return true;
244 	}
245 
246 	if (property->type == V_UI_SPRITEREF) {
247 		uiSprite_t* sprite = UI_GetSpriteByName(string);
248 		if (sprite == nullptr) {
249 			Com_Printf("UI_ParseSetAction: sprite '%s' not found (%s)\n", string, UI_GetPath(node));
250 			return false;
251 		}
252 		action->type = EA_VALUE_RAW;
253 		action->d.terminal.d1.data = sprite;
254 		action->d.terminal.d2.integer = property->type;
255 		return true;
256 	} else {
257 		const int baseType = property->type & V_UI_MASK;
258 		if (baseType != 0 && baseType != V_UI_CVAR) {
259 			Com_Printf("UI_ParseRawValue: setter for property '%s' (type %d, 0x%X) is not supported (%s)\n", property->string, property->type, property->type, UI_GetPath(node));
260 			return false;
261 		}
262 		ui_global.curadata = (byte*) Com_AlignPtr(ui_global.curadata, (valueTypes_t) (property->type & V_BASETYPEMASK));
263 		action->type = EA_VALUE_RAW;
264 		action->d.terminal.d1.data = ui_global.curadata;
265 		action->d.terminal.d2.integer = property->type;
266 		/** @todo we should hide use of ui_global.curadata */
267 		ui_global.curadata += Com_EParseValue(ui_global.curadata, string, (valueTypes_t) (property->type & V_BASETYPEMASK), 0, property->size);
268 		return true;
269 	}
270 }
271 
272 /**
273  * @brief Parser for setter command
274  */
UI_ParseSetAction(uiNode_t * node,uiAction_t * action,const char ** text,const char ** token,const char * errhead)275 static bool UI_ParseSetAction (uiNode_t* node, uiAction_t* action, const char** text, const char** token, const char* errhead)
276 {
277 	const value_t* property;
278 	int type;
279 	uiAction_t* localAction;
280 
281 	assert((*token)[0] == '*');
282 
283 	Com_UnParseLastToken();
284 	action->d.nonTerminal.left = UI_ParseExpression(text);
285 
286 	type = action->d.nonTerminal.left->type;
287 	if (type != EA_VALUE_CVARNAME && type != EA_VALUE_CVARNAME_WITHINJECTION
288 		&& type != EA_VALUE_PATHPROPERTY && type != EA_VALUE_PATHPROPERTY_WITHINJECTION) {
289 		Com_Printf("UI_ParseSetAction: Cvar or Node property expected. Type '%i' found\n", type);
290 		return false;
291 	}
292 
293 	/* must use "equal" char between name and value */
294 	*token = Com_EParse(text, errhead, nullptr);
295 	if (!*text)
296 		return false;
297 	if (!Q_streq(*token, "=")) {
298 		Com_Printf("UI_ParseSetAction: Assign sign '=' expected between variable and value. '%s' found in node %s.\n", *token, UI_GetPath(node));
299 		return false;
300 	}
301 
302 	/* get the value */
303 	if (type == EA_VALUE_CVARNAME || type == EA_VALUE_CVARNAME_WITHINJECTION) {
304 		action->d.nonTerminal.right = UI_ParseExpression(text);
305 		return true;
306 	}
307 
308 	property = (const value_t*) action->d.nonTerminal.left->d.terminal.d2.data;
309 
310 	*token = Com_EParse(text, errhead, nullptr);
311 	if (!*text)
312 		return false;
313 
314 	if (Q_streq(*token, "{")) {
315 		uiAction_t* actionList;
316 
317 		if (property != nullptr && property->type != V_UI_ACTION) {
318 			Com_Printf("UI_ParseSetAction: Property %s@%s do not expect code block.\n", UI_GetPath(node), property->string);
319 			return false;
320 		}
321 
322 		actionList = UI_ParseActionList(node, text, token);
323 		if (actionList == nullptr)
324 			return false;
325 
326 		localAction = UI_AllocStaticAction();
327 		localAction->type = EA_VALUE_RAW;
328 		localAction->d.terminal.d1.data = actionList;
329 		localAction->d.terminal.d2.integer = V_UI_ACTION;
330 		action->d.nonTerminal.right = localAction;
331 
332 		return true;
333 	}
334 
335 	if (Q_streq(*token, "(")) {
336 		Com_UnParseLastToken();
337 		action->d.nonTerminal.right = UI_ParseExpression(text);
338 		return true;
339 	}
340 
341 	/* @todo everything should come from UI_ParseExpression */
342 
343 	if (UI_IsInjectedString(*token)) {
344 		localAction = UI_AllocStaticAction();
345 		localAction->type = EA_VALUE_STRING_WITHINJECTION;
346 		localAction->d.terminal.d1.data = UI_AllocStaticString(*token, 0);
347 		action->d.nonTerminal.right = localAction;
348 		return true;
349 	}
350 
351 	localAction = UI_AllocStaticAction();
352 	UI_InitRawActionValue(localAction, node, property, *token);
353 	action->d.nonTerminal.right = localAction;
354 	return true;
355 }
356 
357 /**
358  * @brief Parser for c command
359  */
UI_ParseCallAction(uiNode_t * node,uiAction_t * action,const char ** text,const char ** token,const char * errhead)360 static bool UI_ParseCallAction (uiNode_t* node, uiAction_t* action, const char** text, const char** token, const char* errhead)
361 {
362 	uiAction_t* expression;
363 	uiAction_t* lastParam = nullptr;
364 	int paramID = 0;
365 	expression = UI_ParseExpression(text);
366 	if (expression == nullptr)
367 		return false;
368 
369 	if (expression->type != EA_VALUE_PATHNODE_WITHINJECTION && expression->type != EA_VALUE_PATHNODE && expression->type != EA_VALUE_PATHPROPERTY && expression->type != EA_VALUE_PATHPROPERTY_WITHINJECTION) {
370 		Com_Printf("UI_ParseCallAction: \"call\" keyword only support pathnode and pathproperty (node: %s)\n", UI_GetPath(node));
371 		return false;
372 	}
373 
374 	action->d.nonTerminal.left = expression;
375 
376 	/* check parameters */
377 	*token = Com_EParse(text, errhead, nullptr);
378 	if ((*token)[0] == '\0')
379 		return false;
380 
381 	/* there is no parameters */
382 	if (!Q_streq(*token, "(")) {
383 		Com_UnParseLastToken();
384 		return true;
385 	}
386 
387 	/* read parameters */
388 	do {
389 		uiAction_t* param;
390 		paramID++;
391 
392 		/* parameter */
393 		param = UI_ParseExpression(text);
394 		if (param == nullptr) {
395 			Com_Printf("UI_ParseCallAction: problem with the %i parameter\n", paramID);
396 			return false;
397 		}
398 		if (lastParam == nullptr)
399 			action->d.nonTerminal.right = param;
400 		else
401 			lastParam->next = param;
402 		lastParam = param;
403 
404 		/* separator */
405 		*token = Com_EParse(text, errhead, nullptr);
406 		if (!*token)
407 			return false;
408 		if (!Q_streq(*token, ",")) {
409 			if (Q_streq(*token, ")"))
410 				break;
411 			Com_UnParseLastToken();
412 			Com_Printf("UI_ParseCallAction: Invalidate end of 'call' after param %i\n", paramID);
413 			return false;
414 		}
415 	} while(true);
416 
417 	return true;
418 }
419 
420 /**
421  * @brief Parse actions and return action list
422  * @return The first element from a list of action
423  * @sa ea_t
424  * @todo need cleanup, compute action out of the final memory; reduce number of var
425  */
UI_ParseActionList(uiNode_t * node,const char ** text,const char ** token)426 static uiAction_t* UI_ParseActionList (uiNode_t* node, const char** text, const char** token)
427 {
428 	const char* errhead = "UI_ParseActionList: unexpected end of file (in event)";
429 	uiAction_t* firstAction;
430 	uiAction_t* lastAction;
431 	uiAction_t* action;
432 
433 	lastAction = nullptr;
434 	firstAction = nullptr;
435 
436 	/* prevent bad position */
437 	if ((*token)[0] != '{') {
438 		Com_Printf("UI_ParseActionList: token \"{\" expected, but \"%s\" found (in event) (node: %s)\n", *token, UI_GetPath(node));
439 		return nullptr;
440 	}
441 
442 	while (true) {
443 		bool result;
444 		int type = EA_NULL;
445 
446 		/* get new token */
447 		*token = Com_EParse(text, errhead, nullptr);
448 		if (!*token)
449 			return nullptr;
450 
451 		if ((*token)[0] == '}')
452 			break;
453 
454 		type = UI_GetActionTokenType(*token, EA_ACTION);
455 		/* setter form */
456 		if (type == EA_NULL && (*token)[0] == '*')
457 			type = EA_ASSIGN;
458 
459 		/* unknown, we break the parsing */
460 		if (type == EA_NULL) {
461 			Com_Printf("UI_ParseActionList: unknown token \"%s\" ignored (in event) (node: %s)\n", *token, UI_GetPath(node));
462 			return nullptr;
463 		}
464 
465 		/* add the action */
466 		action = UI_AllocStaticAction();
467 		/** @todo better to append the action after initialization */
468 		if (lastAction)
469 			lastAction->next = action;
470 		if (!firstAction)
471 			firstAction = action;
472 		action->type = type;
473 
474 		/* decode action */
475 		switch (action->type) {
476 		case EA_CMD:
477 			/* get parameter values */
478 			*token = Com_EParse(text, errhead, nullptr);
479 			if (!*text)
480 				return nullptr;
481 
482 			/* get the value */
483 			action->d.terminal.d1.constString = UI_AllocStaticString(*token, 0);
484 			break;
485 
486 		case EA_ASSIGN:
487 			result = UI_ParseSetAction(node, action, text, token, errhead);
488 			if (!result)
489 				return nullptr;
490 			break;
491 
492 		case EA_CALL:
493 			result = UI_ParseCallAction(node, action, text, token, errhead);
494 			if (!result)
495 				return nullptr;
496 			break;
497 
498 		case EA_DELETE:
499 			{
500 				uiAction_t* expression;
501 				expression = UI_ParseExpression(text);
502 				if (expression == nullptr)
503 					return nullptr;
504 
505 				if (expression->type != EA_VALUE_CVARNAME) {
506 					Com_Printf("UI_ParseActionList: \"delete\" keyword only support cvarname (node: %s)\n", UI_GetPath(node));
507 					return nullptr;
508 				}
509 
510 				action->d.nonTerminal.left = expression;
511 				break;
512 			}
513 
514 		case EA_ELIF:
515 			/* check previous action */
516 			if (!lastAction || (lastAction->type != EA_IF && lastAction->type != EA_ELIF)) {
517 				Com_Printf("UI_ParseActionList: 'elif' must be set after an 'if' or an 'elif' (node: %s)\n", UI_GetPath(node));
518 				return nullptr;
519 			}
520 			/* then it execute EA_IF, no break */
521 		case EA_WHILE:
522 		case EA_IF:
523 			{
524 				uiAction_t* expression;
525 
526 				/* get the condition */
527 				expression = UI_ParseExpression(text);
528 				if (expression == nullptr)
529 					return nullptr;
530 				action->d.nonTerminal.left = expression;
531 
532 				/* get the action block */
533 				*token = Com_EParse(text, errhead, nullptr);
534 				if (!*text)
535 					return nullptr;
536 				action->d.nonTerminal.right = UI_ParseActionList(node, text, token);
537 				if (action->d.nonTerminal.right == nullptr) {
538 					if (action->type == EA_IF)
539 						Com_Printf("UI_ParseActionList: block expected after \"if\" (node: %s)\n", UI_GetPath(node));
540 					else if (action->type == EA_ELIF)
541 						Com_Printf("UI_ParseActionList: block expected after \"elif\" (node: %s)\n", UI_GetPath(node));
542 					else
543 						Com_Printf("UI_ParseActionList: block expected after \"while\" (node: %s)\n", UI_GetPath(node));
544 					return nullptr;
545 				}
546 				break;
547 			}
548 
549 		case EA_ELSE:
550 			/* check previous action */
551 			if (!lastAction || (lastAction->type != EA_IF && lastAction->type != EA_ELIF)) {
552 				Com_Printf("UI_ParseActionList: 'else' must be set after an 'if' or an 'elif' (node: %s)\n", UI_GetPath(node));
553 				return nullptr;
554 			}
555 
556 			/* get the action block */
557 			*token = Com_EParse(text, errhead, nullptr);
558 			if (!*text)
559 				return nullptr;
560 			action->d.nonTerminal.left = nullptr;
561 			action->d.nonTerminal.right = UI_ParseActionList(node, text, token);
562 			if (action->d.nonTerminal.right == nullptr) {
563 				Com_Printf("UI_ParseActionList: block expected after \"else\" (node: %s)\n", UI_GetPath(node));
564 				return nullptr;
565 			}
566 			break;
567 
568 		default:
569 			assert(false);
570 		}
571 
572 		/* step */
573 		lastAction = action;
574 	}
575 
576 	assert((*token)[0] == '}');
577 
578 	/* return non nullptr value */
579 	if (firstAction == nullptr) {
580 		firstAction = UI_AllocStaticAction();
581 	}
582 
583 	return firstAction;
584 }
585 
UI_ParseExcludeRect(uiNode_t * node,const char ** text,const char ** token,const char * errhead)586 static bool UI_ParseExcludeRect (uiNode_t* node, const char** text, const char** token, const char* errhead)
587 {
588 	uiExcludeRect_t rect;
589 	uiExcludeRect_t* newRect;
590 
591 	/* get parameters */
592 	*token = Com_EParse(text, errhead, node->name);
593 	if (!*text)
594 		return false;
595 	if ((*token)[0] != '{') {
596 		Com_Printf("UI_ParseExcludeRect: node with bad excluderect ignored (node \"%s\")\n", UI_GetPath(node));
597 		return true;
598 	}
599 
600 	do {
601 		*token = Com_EParse(text, errhead, node->name);
602 		if (!*text)
603 			return false;
604 		/** @todo move it into a property array */
605 		if (Q_streq(*token, "pos")) {
606 			*token = Com_EParse(text, errhead, node->name);
607 			if (!*text)
608 				return false;
609 			Com_EParseValue(&rect, *token, V_POS, offsetof(uiExcludeRect_t, pos), sizeof(vec2_t));
610 		} else if (Q_streq(*token, "size")) {
611 			*token = Com_EParse(text, errhead, node->name);
612 			if (!*text)
613 				return false;
614 			Com_EParseValue(&rect, *token, V_POS, offsetof(uiExcludeRect_t, size), sizeof(vec2_t));
615 		}
616 	} while ((*token)[0] != '}');
617 
618 	newRect = (uiExcludeRect_t*) UI_AllocHunkMemory(sizeof(*newRect), STRUCT_MEMORY_ALIGN, false);
619 	if (newRect == nullptr) {
620 		Com_Printf("UI_ParseExcludeRect: ui hunk memory exceeded.");
621 		return false;
622 	}
623 
624 	/* move data to final memory and link to node */
625 	*newRect = rect;
626 	newRect->next = node->firstExcludeRect;
627 	node->firstExcludeRect = newRect;
628 	return true;
629 }
630 
UI_ParseEventProperty(uiNode_t * node,const value_t * event,const char ** text,const char ** token,const char * errhead)631 static bool UI_ParseEventProperty (uiNode_t* node, const value_t* event, const char** text, const char** token, const char* errhead)
632 {
633 	/* add new actions to end of list */
634 	uiAction_t** action = &Com_GetValue<uiAction_t*>(node, event);
635 	for (; *action; action = &(*action)->next) {}
636 
637 	/* get the action body */
638 	*token = Com_EParse(text, errhead, node->name);
639 	if (!*text)
640 		return false;
641 
642 	if ((*token)[0] != '{') {
643 		Com_Printf("UI_ParseEventProperty: Event '%s' without body (%s)\n", event->string, UI_GetPath(node));
644 		return false;
645 	}
646 
647 	*action = UI_ParseActionList(node, text, token);
648 	if (*action == nullptr)
649 		return false;
650 
651 	/* block terminal already read */
652 	assert((*token)[0] == '}');
653 
654 	return true;
655 }
656 
657 /**
658  * @brief Parse a property value
659  * @todo don't read the next token (need to change the script language)
660  */
UI_ParseProperty(void * object,const value_t * property,const char * objectName,const char ** text,const char ** token)661 static bool UI_ParseProperty (void* object, const value_t* property, const char* objectName, const char** text, const char** token)
662 {
663 	const char* errhead = "UI_ParseProperty: unexpected end of file (object";
664 	static const char* notWellFormedValue = "UI_ParseProperty: \"%s\" is not a well formed node name (it must be quoted, uppercase const, a number, or prefixed with '*')\n";
665 	size_t bytes;
666 	int result;
667 	const int specialType = property->type & V_UI_MASK;
668 
669 	if (property->type == V_NULL) {
670 		return false;
671 	}
672 
673 	switch (specialType) {
674 	case V_NOT_UI:	/* common type */
675 
676 		*token = Com_EParse(text, errhead, objectName);
677 		if (!*text)
678 			return false;
679 		if (!UI_TokenIsValue(*token, Com_GetType(text) == TT_QUOTED_WORD)) {
680 			Com_Printf(notWellFormedValue, *token);
681 			return false;
682 		}
683 
684 		if (property->type == V_TRANSLATION_STRING) {
685 			/* selectbox values are static arrays */
686 			char* const target = Com_GetValue<char[]>(object, property);
687 			const char* translatableToken = *token;
688 			assert(property->size);
689 			if (translatableToken[0] == '_')
690 				translatableToken++;
691 			Q_strncpyz(target, translatableToken, property->size);
692 		} else {
693 			result = Com_ParseValue(object, *token, property->type, property->ofs, property->size, &bytes);
694 			if (result != RESULT_OK) {
695 				Com_Printf("UI_ParseProperty: Invalid value for property '%s': %s\n", property->string, Com_GetLastParseError());
696 				return false;
697 			}
698 		}
699 		break;
700 
701 	case V_UI_REF:
702 		*token = Com_EParse(text, errhead, objectName);
703 		if (!*text)
704 			return false;
705 		if (!UI_TokenIsValue(*token, Com_GetType(text) == TT_QUOTED_WORD)) {
706 			Com_Printf(notWellFormedValue, *token);
707 			return false;
708 		}
709 
710 		/* a reference to data is handled like this */
711 		ui_global.curadata = (byte*) Com_AlignPtr(ui_global.curadata, (valueTypes_t) (property->type & V_BASETYPEMASK));
712 		Com_GetValue<byte*>(object, property) = ui_global.curadata;
713 
714 		/** @todo check for the moment its not a cvar */
715 		assert((*token)[0] != '*');
716 
717 		/* sanity check */
718 		if ((property->type & V_BASETYPEMASK) == V_STRING && strlen(*token) > MAX_VAR - 1) {
719 			Com_Printf("UI_ParseProperty: Value '%s' is too long (key %s)\n", *token, property->string);
720 			return false;
721 		}
722 
723 		result = Com_ParseValue(ui_global.curadata, *token, (valueTypes_t) (property->type & V_BASETYPEMASK), 0, property->size, &bytes);
724 		if (result != RESULT_OK) {
725 			Com_Printf("UI_ParseProperty: Invalid value for property '%s': %s\n", property->string, Com_GetLastParseError());
726 			return false;
727 		}
728 		ui_global.curadata += bytes;
729 
730 		break;
731 
732 	case V_UI_CVAR:	/* common type */
733 		*token = Com_EParse(text, errhead, objectName);
734 		if (!*text)
735 			return false;
736 		if (!UI_TokenIsValue(*token, Com_GetType(text) == TT_QUOTED_WORD)) {
737 			Com_Printf(notWellFormedValue, *token);
738 			return false;
739 		}
740 
741 		/* references are parsed as string */
742 		if ((*token)[0] == '*') {
743 			/* a reference to data */
744 			ui_global.curadata = (byte*) Com_AlignPtr(ui_global.curadata, V_STRING);
745 			Com_GetValue<byte*>(object, property) = ui_global.curadata;
746 
747 			/* sanity check */
748 			if (strlen(*token) > MAX_VAR - 1) {
749 				Com_Printf("UI_ParseProperty: Value '%s' is too long (key %s)\n", *token, property->string);
750 				return false;
751 			}
752 
753 			result = Com_ParseValue(ui_global.curadata, *token, V_STRING, 0, 0, &bytes);
754 			if (result != RESULT_OK) {
755 				Com_Printf("UI_ParseProperty: Invalid value for property '%s': %s\n", property->string, Com_GetLastParseError());
756 				return false;
757 			}
758 			ui_global.curadata += bytes;
759 		} else {
760 			/* a reference to data */
761 			ui_global.curadata = (byte*) Com_AlignPtr(ui_global.curadata, (valueTypes_t)(property->type & V_BASETYPEMASK));
762 			Com_GetValue<byte*>(object, property) = ui_global.curadata;
763 
764 			/* sanity check */
765 			if ((property->type & V_BASETYPEMASK) == V_STRING && strlen(*token) > MAX_VAR - 1) {
766 				Com_Printf("UI_ParseProperty: Value '%s' is too long (key %s)\n", *token, property->string);
767 				return false;
768 			}
769 
770 			result = Com_ParseValue(ui_global.curadata, *token, (valueTypes_t)(property->type & V_BASETYPEMASK), 0, property->size, &bytes);
771 			if (result != RESULT_OK) {
772 				Com_Printf("UI_ParseProperty: Invalid value for property '%s': %s\n", property->string, Com_GetLastParseError());
773 				return false;
774 			}
775 			ui_global.curadata += bytes;
776 		}
777 		break;
778 
779 	case V_UI:
780 
781 		switch ((int)property->type) {
782 		case V_UI_ACTION:
783 			result = UI_ParseEventProperty(static_cast<uiNode_t*>(object), property, text, token, errhead);
784 			if (!result)
785 				return false;
786 			break;
787 
788 		case V_UI_EXCLUDERECT:
789 			result = UI_ParseExcludeRect(static_cast<uiNode_t*>(object), text, token, errhead);
790 			if (!result)
791 				return false;
792 			break;
793 
794 		case V_UI_SPRITEREF:
795 			{
796 				*token = Com_EParse(text, errhead, objectName);
797 				if (!*text)
798 					return false;
799 
800 				uiSprite_t const*& sprite = Com_GetValue<uiSprite_t const*>(object, property);
801 				sprite = UI_GetSpriteByName(*token);
802 				if (!sprite) {
803 					Com_Printf("UI_ParseProperty: sprite '%s' not found (object %s)\n", *token, objectName);
804 				}
805 			}
806 			break;
807 
808 		case V_UI_IF:
809 			{
810 				*token = Com_EParse(text, errhead, objectName);
811 				if (!*text)
812 					return false;
813 
814 				uiAction_t*& expression = Com_GetValue<uiAction_t*>(object, property);
815 				expression = UI_AllocStaticStringCondition(*token);
816 				if (!expression)
817 					return false;
818 			}
819 			break;
820 
821 		case V_UI_DATAID:
822 			{
823 				*token = Com_EParse(text, errhead, objectName);
824 				if (!*text)
825 					return false;
826 
827 				int& dataId = Com_GetValue<int>(object, property);
828 				dataId = UI_GetDataIDByName(*token);
829 				if (dataId < 0) {
830 					Com_Printf("UI_ParseProperty: Could not find shared data ID '%s' (%s@%s)\n",
831 							*token, objectName, property->string);
832 					return false;
833 				}
834 			}
835 			break;
836 
837 		default:
838 			Com_Printf("UI_ParseProperty: unknown property type '%d' (0x%X) (%s@%s)\n",
839 					property->type, property->type, objectName, property->string);
840 			return false;
841 		}
842 		break;
843 
844 	default:
845 		Com_Printf("UI_ParseProperties: unknown property type '%d' (0x%X) (%s@%s)\n",
846 				property->type, property->type, objectName, property->string);
847 		return false;
848 	}
849 
850 	return true;
851 }
852 
UI_ParseFunction(uiNode_t * node,const char ** text,const char ** token)853 static bool UI_ParseFunction (uiNode_t* node, const char** text, const char** token)
854 {
855 	uiAction_t** action;
856 	assert(UI_Node_IsFunction(node));
857 
858 	action = &node->onClick;
859 	*action = UI_ParseActionList(node, text, token);
860 	if (*action == nullptr)
861 		return false;
862 
863 	return (*token)[0] == '}';
864 }
865 
866 /**
867  * @sa UI_ParseNodeProperties
868  * @brief parse all sequencial properties into a block
869  * @note allow to use an extra block
870  * @code
871  * foobehaviour foonode {
872  *   { properties }
873  *   // the function stop reading here
874  *   nodes
875  * }
876  * foobehaviour foonode {
877  *   properties
878  *   // the function stop reading here
879  *   nodes
880  * }
881  * @endcode
882  */
UI_ParseNodeProperties(uiNode_t * node,const char ** text,const char ** token)883 static bool UI_ParseNodeProperties (uiNode_t* node, const char** text, const char** token)
884 {
885 	const char* errhead = "UI_ParseNodeProperties: unexpected end of file (node";
886 	bool nextTokenAlreadyRead = false;
887 
888 	if ((*token)[0] != '{')
889 		nextTokenAlreadyRead = true;
890 
891 	do {
892 		const value_t* val;
893 		int result;
894 
895 		/* get new token */
896 		if (!nextTokenAlreadyRead) {
897 			*token = Com_EParse(text, errhead, node->name);
898 			if (!*text)
899 				return false;
900 		} else {
901 			nextTokenAlreadyRead = false;
902 		}
903 
904 		/* is finished */
905 		if ((*token)[0] == '}')
906 			break;
907 
908 		/* find the property */
909 		val = UI_GetPropertyFromBehaviour(node->behaviour, *token);
910 		if (!val) {
911 			/* unknown token, print message and continue */
912 			Com_Printf("UI_ParseNodeProperties: unknown property \"%s\", node ignored (node %s)\n",
913 					*token, UI_GetPath(node));
914 			return false;
915 		}
916 
917 		/* get parameter values */
918 		result = UI_ParseProperty(node, val, node->name, text, token);
919 		if (!result) {
920 			Com_Printf("UI_ParseNodeProperties: Problem with parsing of node property '%s@%s'. See upper\n",
921 					UI_GetPath(node), val->string);
922 			return false;
923 		}
924 	} while (*text);
925 
926 	return true;
927 }
928 
929 /**
930  * @brief Read a node body
931  * @note Node header already read, we are over the node name, or '{'
932  * @code
933  * Allowed syntax
934  * { properties }
935  * OR
936  * { nodes }
937  * OR
938  * { { properties } nodes }
939  * @endcode
940  */
UI_ParseNodeBody(uiNode_t * node,const char ** text,const char ** token,const char * errhead)941 static bool UI_ParseNodeBody (uiNode_t* node, const char** text, const char** token, const char* errhead)
942 {
943 	bool result = true;
944 
945 	if ((*token)[0] != '{') {
946 		/* read the body block start */
947 		*token = Com_EParse(text, errhead, node->name);
948 		if (!*text)
949 			return false;
950 		if ((*token)[0] != '{') {
951 			Com_Printf("UI_ParseNodeBody: node doesn't have body, token '%s' read (node \"%s\")\n", *token, UI_GetPath(node));
952 			ui_global.numNodes--;
953 			return false;
954 		}
955 	}
956 
957 	/* functions are a special case */
958 	if (UI_Node_IsFunction(node)) {
959 		result = UI_ParseFunction(node, text, token);
960 	} else {
961 
962 		/* check the content */
963 		*token = Com_EParse(text, errhead, node->name);
964 		if (!*text)
965 			return false;
966 
967 		if ((*token)[0] == '{') {
968 			/* we have a special block for properties */
969 			result = UI_ParseNodeProperties(node, text, token);
970 			if (!result)
971 				return false;
972 
973 			/* move token over the next node behaviour */
974 			*token = Com_EParse(text, errhead, node->name);
975 			if (!*text)
976 				return false;
977 
978 			/* and then read all nodes */
979 			while ((*token)[0] != '}') {
980 				uiNode_t* newNode = UI_ParseNode(node, text, token, errhead);
981 				if (!newNode)
982 					return false;
983 
984 				*token = Com_EParse(text, errhead, node->name);
985 				if (*text == nullptr)
986 					return false;
987 			}
988 		} else if (UI_GetPropertyFromBehaviour(node->behaviour, *token)) {
989 			/* we should have a block with properties only */
990 			result = UI_ParseNodeProperties(node, text, token);
991 		} else {
992 			/* we should have a block with nodes only */
993 			while ((*token)[0] != '}') {
994 				uiNode_t* newNode = UI_ParseNode(node, text, token, errhead);
995 				if (!newNode)
996 					return false;
997 
998 				*token = Com_EParse(text, errhead, node->name);
999 				if (*text == nullptr)
1000 					return false;
1001 			}
1002 		}
1003 	}
1004 	if (!result) {
1005 		Com_Printf("UI_ParseNodeBody: node with bad body ignored (node \"%s\")\n", UI_GetPath(node));
1006 		ui_global.numNodes--;
1007 		return false;
1008 	}
1009 
1010 	/* already check on UI_ParseNodeProperties */
1011 	assert((*token)[0] == '}');
1012 	return true;
1013 }
1014 
1015 /**
1016  * @brief parse a node
1017  * @sa UI_ParseNodeProperties
1018  * @todo we can think about merging UI_ParseNodeProperties here
1019  * @note first token already read
1020  * @note dont read more than the need token (last right token is '}' of end of node)
1021  */
UI_ParseNode(uiNode_t * parent,const char ** text,const char ** token,const char * errhead)1022 static uiNode_t* UI_ParseNode (uiNode_t* parent, const char** text, const char** token, const char* errhead)
1023 {
1024 	uiNode_t* node = nullptr;
1025 	uiBehaviour_t* behaviour;
1026 	uiNode_t* component = nullptr;
1027 
1028 	/* get the behaviour */
1029 	behaviour = UI_GetNodeBehaviour(*token);
1030 	if (!behaviour) {
1031 		component = UI_GetComponent(*token);
1032 	}
1033 	if (behaviour == nullptr && component == nullptr) {
1034 		Com_Printf("UI_ParseNode: node behaviour/component '%s' doesn't exists (%s)\n", *token, UI_GetPath(parent));
1035 		return nullptr;
1036 	}
1037 
1038 	/* get the name */
1039 	*token = Com_EParse(text, errhead, "");
1040 	if (!*text)
1041 		return nullptr;
1042 	if (!UI_TokenIsName(*token, Com_GetType(text) == TT_QUOTED_WORD)) {
1043 		Com_Printf("UI_ParseNode: \"%s\" is not a well formed node name ([a-zA-Z_][a-zA-Z0-9_]*)\n", *token);
1044 		return nullptr;
1045 	}
1046 	if (UI_TokenIsReserved(*token)) {
1047 		Com_Printf("UI_ParseNode: \"%s\" is a reserved token, we can't call a node with it\n", *token);
1048 		return nullptr;
1049 	}
1050 
1051 	/* test if node already exists */
1052 	/* Already existing node should only come from inherited node,we should not have 2 definitions of the same node into the same window. */
1053 	if (parent)
1054 		node = UI_GetNode(parent, *token);
1055 
1056 	/* reuse a node */
1057 	if (node) {
1058 		if (node->behaviour != behaviour) {
1059 			Com_Printf("UI_ParseNode: we can't change node type (node \"%s\")\n", UI_GetPath(node));
1060 			return nullptr;
1061 		}
1062 		Com_DPrintf(DEBUG_CLIENT, "... over-riding node %s\n", UI_GetPath(node));
1063 
1064 	/* else initialize a component */
1065 	} else if (component) {
1066 		node = UI_CloneNode(component, nullptr, true, *token, false);
1067 		if (parent) {
1068 			if (parent->root)
1069 				UI_UpdateRoot(node, parent->root);
1070 			UI_AppendNode(parent, node);
1071 		}
1072 
1073 	/* else initialize a new node */
1074 	} else {
1075 		node = UI_AllocNode(*token, behaviour->name, false);
1076 		node->parent = parent;
1077 		if (parent)
1078 			node->root = parent->root;
1079 		/** @todo move it into caller */
1080 		if (parent)
1081 			UI_AppendNode(parent, node);
1082 	}
1083 
1084 	/* get body */
1085 	const bool result = UI_ParseNodeBody(node, text, token, errhead);
1086 	if (!result)
1087 		return nullptr;
1088 
1089 	/* validate properties */
1090 	UI_Node_Loaded(node);
1091 
1092 	return node;
1093 }
1094 
1095 /**
1096  * @brief parses the models.ufo and all files where UI models (menu_model) are defined
1097  * @sa CL_ParseClientData
1098  */
UI_ParseUIModel(const char * name,const char ** text)1099 bool UI_ParseUIModel (const char* name, const char** text)
1100 {
1101 	uiModel_t* model;
1102 	const char* token;
1103 	int i;
1104 	const char* errhead = "UI_ParseUIModel: unexpected end of file (names ";
1105 
1106 	/* search for a UI models with same name */
1107 	for (i = 0; i < ui_global.numModels; i++)
1108 		if (Q_streq(ui_global.models[i].id, name)) {
1109 			Com_Printf("UI_ParseUIModel: menu_model \"%s\" with same name found, second ignored\n", name);
1110 			return false;
1111 		}
1112 
1113 	if (ui_global.numModels >= UI_MAX_MODELS) {
1114 		Com_Printf("UI_ParseUIModel: Max UI models reached\n");
1115 		return false;
1116 	}
1117 
1118 	/* initialize the model */
1119 	model = &ui_global.models[ui_global.numModels];
1120 	OBJZERO(*model);
1121 
1122 	Vector4Set(model->color, 1, 1, 1, 1);
1123 
1124 	model->id = Mem_PoolStrDup(name, ui_sysPool, 0);
1125 	Com_DPrintf(DEBUG_CLIENT, "Found UI model %s (%i)\n", model->id, ui_global.numModels);
1126 
1127 	/* get it's body */
1128 	token = Com_Parse(text);
1129 
1130 	if (!*text || token[0] != '{') {
1131 		Com_Printf("UI_ParseUIModel: Model \"%s\" without body ignored\n", model->id);
1132 		return false;
1133 	}
1134 
1135 	ui_global.numModels++;
1136 
1137 	do {
1138 		const value_t* v = nullptr;
1139 		/* get the name type */
1140 		token = Com_EParse(text, errhead, name);
1141 		if (!*text)
1142 			return false;
1143 		if (token[0] == '}')
1144 			break;
1145 
1146 		v = UI_FindPropertyByName(uiModelProperties, token);
1147 		if (!v) {
1148 			Com_Printf("UI_ParseUIModel: unknown token \"%s\" ignored (UI model %s)\n", token, name);
1149 			return false;
1150 		}
1151 
1152 		if (v->type == V_NULL) {
1153 			if (Q_streq(v->string, "need")) {
1154 				token = Com_EParse(text, errhead, name);
1155 				if (!*text)
1156 					return false;
1157 				if (model->next != nullptr)
1158 					Sys_Error("UI_ParseUIModel: second 'need' token found in model %s", name);
1159 				model->next = UI_GetUIModel(token);
1160 				 if (!model->next)
1161 					Com_Printf("Could not find UI model %s", token);
1162 			}
1163 		} else {
1164 			token = Com_EParse(text, errhead, name);
1165 			if (!*text)
1166 				return false;
1167 			switch (v->type) {
1168 			case V_HUNK_STRING:
1169 				Mem_PoolStrDupTo(token, &Com_GetValue<char*>(model, v), ui_sysPool, 0);
1170 				break;
1171 			default:
1172 				Com_EParseValue(model, token, v->type, v->ofs, v->size);
1173 				break;
1174 			}
1175 		}
1176 	} while (*text);
1177 
1178 	return true;
1179 }
1180 
UI_ParseSprite(const char * name,const char ** text)1181 bool UI_ParseSprite (const char* name, const char** text)
1182 {
1183 	uiSprite_t* icon;
1184 	const char* token;
1185 
1186 	/* search for icons with same name */
1187 	icon = UI_AllocStaticSprite(name);
1188 
1189 	/* get it's body */
1190 	token = Com_Parse(text);
1191 	assert(token[0] == '{');
1192 
1193 	/* read properties */
1194 	while (true) {
1195 		const value_t* property;
1196 
1197 		token = Com_Parse(text);
1198 		if (*text == nullptr)
1199 			return false;
1200 
1201 		if (token[0] == '}')
1202 			break;
1203 
1204 		property = UI_FindPropertyByName(ui_spriteProperties, token);
1205 		if (!property) {
1206 			Com_Printf("UI_ParseIcon: unknown options property: '%s' - ignore it\n", token);
1207 			return false;
1208 		}
1209 
1210 		/* get parameter values */
1211 		const bool result = UI_ParseProperty(icon, property, icon->name, text, &token);
1212 		if (!result) {
1213 			Com_Printf("UI_ParseIcon: Parsing for sprite '%s'. See upper\n", icon->name);
1214 			return false;
1215 		}
1216 	}
1217 
1218 	return true;
1219 }
1220 
1221 /**
1222  * @brief Parse a component
1223  * @sa CL_ParseClientData
1224  * @code
1225  * component panel componentName {
1226  * }
1227  * @endcode
1228  */
UI_ParseComponent(const char * type,const char * name,const char ** text)1229 bool UI_ParseComponent (const char* type, const char* name, const char** text)
1230 {
1231 	const char* errhead = "UI_ParseComponent: unexpected end of file (component";
1232 	const char* token;
1233 
1234 	if (!Q_streq(type, "component")) {
1235 		Com_Error(ERR_FATAL, "UI_ParseComponent: \"component\" expected but \"%s\" found.\n", type);
1236 		return false;	/* never reached */
1237 	}
1238 
1239 	/* check the name */
1240 	if (!UI_TokenIsName(name, false)) {
1241 		Com_Printf("UI_ParseNode: \"%s\" is not a well formed node name ([a-zA-Z_][a-zA-Z0-9_]*)\n", name);
1242 		return false;
1243 	}
1244 	if (UI_TokenIsReserved(name)) {
1245 		Com_Printf("UI_ParseNode: \"%s\" is a reserved token, we can't call a node with it\n", name);
1246 		return false;
1247 	}
1248 
1249 	token = Com_EParse(text, errhead, "");
1250 	if (text == nullptr)
1251 		return false;
1252 
1253 	/* get keyword */
1254 	if (!Q_streq(token, "extends")) {
1255 		Com_Printf("UI_ParseComponent: \"extends\" expected but \"%s\" found (component %s)\n", token, name);
1256 		return false;
1257 	}
1258 	token = Com_EParse(text, errhead, "");
1259 	if (text == nullptr)
1260 		return false;
1261 
1262 	/* initialize component */
1263 	uiNode_t* component = nullptr;
1264 	const uiBehaviour_t* behaviour = UI_GetNodeBehaviour(token);
1265 	if (behaviour) {
1266 		/* initialize a new node from behaviour */
1267 		component = UI_AllocNode(name, behaviour->name, false);
1268 	} else {
1269 		const uiNode_t* inheritedComponent = UI_GetComponent(token);
1270 		if (inheritedComponent) {
1271 			/* initialize from a component */
1272 			component = UI_CloneNode(inheritedComponent, nullptr, true, name, false);
1273 		} else {
1274 			Com_Printf("UI_ParseComponent: node behaviour/component '%s' doesn't exists (component %s)\n", token, name);
1275 			return false;
1276 		}
1277 	}
1278 
1279 	/* get body */
1280 	token = Com_EParse(text, errhead, "");
1281 	if (!*text)
1282 		return false;
1283 	bool result = UI_ParseNodeBody(component, text, &token, errhead);
1284 	if (!result)
1285 		return false;
1286 
1287 	/* validate properties */
1288 	UI_Node_Loaded(component);
1289 
1290 	UI_InsertComponent(component);
1291 	return true;
1292 }
1293 
1294 
1295 /**
1296  * @brief Parse a window
1297  * @sa CL_ParseClientData
1298  * @code
1299  * window windowName {
1300  * }
1301  * @endcode
1302  */
UI_ParseWindow(const char * type,const char * name,const char ** text)1303 bool UI_ParseWindow (const char* type, const char* name, const char** text)
1304 {
1305 	const char* errhead = "UI_ParseWindow: unexpected end of file (window";
1306 	uiNode_t* window;
1307 	const char* token;
1308 	int i;
1309 
1310 	if (!Q_streq(type, "window")) {
1311 		Com_Error(ERR_FATAL, "UI_ParseWindow: '%s %s' is not a window node\n", type, name);
1312 		return false;	/* never reached */
1313 	}
1314 
1315 	if (!UI_TokenIsName(name, Com_GetType(text) == TT_QUOTED_WORD)) {
1316 		Com_Printf("UI_ParseWindow: \"%s\" is not a well formed node name ([a-zA-Z_][a-zA-Z0-9_]*)\n", name);
1317 		return false;
1318 	}
1319 	if (UI_TokenIsReserved(name)) {
1320 		Com_Printf("UI_ParseWindow: \"%s\" is a reserved token, we can't call a node with it (node \"%s\")\n", name, name);
1321 		return false;
1322 	}
1323 
1324 	/* search for windows with same name */
1325 	for (i = 0; i < ui_global.numWindows; i++)
1326 		if (!strncmp(name, ui_global.windows[i]->name, sizeof(ui_global.windows[i]->name)))
1327 			break;
1328 
1329 	if (i < ui_global.numWindows) {
1330 		Com_Printf("UI_ParseWindow: %s \"%s\" with same name found, second ignored\n", type, name);
1331 	}
1332 
1333 	if (ui_global.numWindows >= UI_MAX_WINDOWS) {
1334 		Com_Error(ERR_FATAL, "UI_ParseWindow: max windows exceeded (%i) - ignore '%s'\n", UI_MAX_WINDOWS, name);
1335 		return false;	/* never reached */
1336 	}
1337 
1338 	/* get window body */
1339 	token = Com_Parse(text);
1340 
1341 	/* does this window inherit data from another window? */
1342 	if (Q_streq(token, "extends")) {
1343 		uiNode_t* superWindow;
1344 		token = Com_Parse(text);
1345 		superWindow = UI_GetWindow(token);
1346 		if (superWindow == nullptr)
1347 			Sys_Error("Could not get the super window \"%s\"", token);
1348 		window = UI_CloneNode(superWindow, nullptr, true, name, false);
1349 		token = Com_Parse(text);
1350 	} else {
1351 		window = UI_AllocNode(name, type, false);
1352 		window->root = window;
1353 	}
1354 
1355 	UI_InsertWindow(window);
1356 
1357 	/* parse it's body */
1358 	bool result = UI_ParseNodeBody(window, text, &token, errhead);
1359 	if (!result) {
1360 		Com_Error(ERR_FATAL, "UI_ParseWindow: window \"%s\" has a bad body\n", window->name);
1361 	}
1362 
1363 	UI_Node_Loaded(window);
1364 	return true;
1365 }
1366 
1367 /**
1368  * @sa Com_MacroExpandString
1369  * @todo we should review this code, '*' doesn't work very well for all the needed things
1370  */
UI_GetReferenceString(const uiNode_t * const node,const char * ref)1371 const char* UI_GetReferenceString (const uiNode_t* const node, const char* ref)
1372 {
1373 	if (!ref)
1374 		return nullptr;
1375 
1376 	/* its a cvar */
1377 	if (ref[0] != '*')
1378 		return CL_Translate(ref);
1379 
1380 	/* get the reference and the name */
1381 	const char* token = Com_MacroExpandString(ref);
1382 	if (token)
1383 		return CL_Translate(token);
1384 
1385 	/* skip the star */
1386 	token = ref + 1;
1387 	if (token[0] == '\0')
1388 		return nullptr;
1389 
1390 	Sys_Error("UI_GetReferenceString: unknown reference %s", token);
1391 }
1392 
UI_GetReferenceFloat(const uiNode_t * const node,const void * ref)1393 float UI_GetReferenceFloat (const uiNode_t* const node, const void* ref)
1394 {
1395 	if (!ref)
1396 		return 0.0;
1397 	if (char const* const token = Q_strstart((char const*)ref, "*")) {
1398 		if (token[0] == '\0')
1399 			return 0.0;
1400 
1401 		if (char const* const cvar = Q_strstart(token, "cvar:")) {
1402 			return Cvar_GetValue(cvar);
1403 		}
1404 
1405 		Sys_Error("UI_GetReferenceFloat: unknown reference '%s' from node '%s'", token, node->name);
1406 	}
1407 
1408 	/* just get the data */
1409 	return *(const float*) ref;
1410 }
1411