1 /**
2  * @file
3  */
4 
5 /*
6 Copyright (C) 2002-2013 UFO: Alien Invasion.
7 
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License
10 as published by the Free Software Foundation; either version 2
11 of the License, or (at your option) any later version.
12 
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16 
17 See the GNU General Public License for more details.
18 
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
22 
23 */
24 
25 #include "ui_main.h"
26 #include "ui_internal.h"
27 #include "ui_parse.h"
28 #include "ui_input.h"
29 #include "ui_node.h"
30 #include "ui_actions.h"
31 #include "node/ui_node_abstractnode.h"
32 
33 #include "../cl_language.h"
34 
35 typedef struct {
36 	const char* token;
37 	int type;
38 	int group;
39 } ui_typedActionToken_t;
40 
41 /**
42  * @brief List of typed token
43  * @note token ordered by alphabet, because it use a dichotomic search
44  */
45 static const ui_typedActionToken_t actionTokens[] = {
46 
47 	/* 0x2x */
48 	{"!", EA_OPERATOR_NOT, EA_UNARYOPERATOR},
49 	{"!=", EA_OPERATOR_NE, EA_BINARYOPERATOR},
50 	{"%", EA_OPERATOR_MOD, EA_BINARYOPERATOR},
51 	{"&&", EA_OPERATOR_AND, EA_BINARYOPERATOR},
52 	{"*", EA_OPERATOR_MUL, EA_BINARYOPERATOR},
53 	{"+", EA_OPERATOR_ADD, EA_BINARYOPERATOR},
54 	{"-", EA_OPERATOR_SUB, EA_BINARYOPERATOR},
55 	{"/", EA_OPERATOR_DIV, EA_BINARYOPERATOR},
56 
57 	/* 0x3x */
58 	{"<", EA_OPERATOR_LT, EA_BINARYOPERATOR},
59 	{"<=", EA_OPERATOR_LE, EA_BINARYOPERATOR},
60 	{"==", EA_OPERATOR_EQ, EA_BINARYOPERATOR},
61 	{">", EA_OPERATOR_GT, EA_BINARYOPERATOR},
62 	{">=", EA_OPERATOR_GE, EA_BINARYOPERATOR},
63 
64 	/* 0x5x */
65 	{"^", EA_OPERATOR_XOR, EA_BINARYOPERATOR},
66 
67 	/* 'a'..'z' */
68 	{"and", EA_OPERATOR_AND, EA_BINARYOPERATOR},
69 	{"call", EA_CALL, EA_ACTION},
70 	{"cmd", EA_CMD, EA_ACTION},
71 	{"delete", EA_DELETE, EA_ACTION},
72 	{"elif", EA_ELIF, EA_ACTION},
73 	{"else", EA_ELSE, EA_ACTION},
74 	{"eq", EA_OPERATOR_STR_EQ, EA_BINARYOPERATOR},
75 	{"exists", EA_OPERATOR_EXISTS, EA_UNARYOPERATOR},
76 	{"if", EA_IF, EA_ACTION},
77 	{"ne", EA_OPERATOR_STR_NE, EA_BINARYOPERATOR},
78 	{"not", EA_OPERATOR_NOT, EA_UNARYOPERATOR},
79 	{"or", EA_OPERATOR_OR, EA_BINARYOPERATOR},
80 	{"while", EA_WHILE, EA_ACTION},
81 
82 	/* 0x7x */
83 	{"||", EA_OPERATOR_OR, EA_BINARYOPERATOR},
84 };
85 
86 static void UI_ExecuteActions(const uiAction_t* firstAction, uiCallContext_t* context);
87 
88 /**
89  * @brief Check if the action token list is sorted by alphabet,
90  * else dichotomic search can't work
91  */
UI_CheckActionTokenTypeSanity(void)92 static void UI_CheckActionTokenTypeSanity (void)
93 {
94 	int i;
95 	for (i = 0; i < lengthof(actionTokens) - 1; i++) {
96 		const char* a = actionTokens[i].token;
97 		const char* b = actionTokens[i + 1].token;
98 		if (strcmp(a, b) >= 0) {
99 			Sys_Error("UI_CheckActionTokenTypeSanity: '%s' is before '%s'. actionList must be sorted by alphabet", a, b);
100 		}
101 	}
102 }
103 
104 /**
105  * @brief return an action type from a token, and a group
106  * @param[in] token Requested token
107  * @param[in] group Requested group, EA_ACTION, EA_BINARYOPERATOR, or EA_UNARYOPERATOR
108  * @sa actionTokens
109  * @return a action type from the requested group, else EA_NULL
110  */
UI_GetActionTokenType(const char * token,int group)111 int UI_GetActionTokenType (const char* token, int group)
112 {
113 	unsigned char min = 0;
114 	unsigned char max = lengthof(actionTokens);
115 
116 	while (min != max) {
117 		const int mid = (min + max) >> 1;
118 		const int diff = strcmp(actionTokens[mid].token, token);
119 		assert(mid < max);
120 		assert(mid >= min);
121 
122 		if (diff == 0) {
123 			if (actionTokens[mid].group == group)
124 				return actionTokens[mid].type;
125 			else
126 				return EA_NULL;
127 		}
128 
129 		if (diff > 0)
130 			max = mid;
131 		else
132 			min = mid + 1;
133 	}
134 	return EA_NULL;
135 }
136 
137 /**
138  * @brief read a property name from an input buffer to an output
139  * @return last position in the input buffer if we find the property, nullptr otherwise
140  */
UI_GenCommandReadProperty(const char * input,char * output,int outputSize)141 static inline const char* UI_GenCommandReadProperty (const char* input, char* output, int outputSize)
142 {
143 	assert(input[0] == '<');
144 	outputSize--;
145 	input++;
146 
147 	while (outputSize && *input != '\0' && *input != ' ' && *input != '>') {
148 		*output++ = *input++;
149 		outputSize--;
150 	}
151 
152 	if (input[0] != '>')
153 		return nullptr;
154 
155 	output[0] = '\0';
156 	return ++input;
157 }
158 
159 /**
160  * Get the number of param from an execution context
161  * @param[in] context The execution context
162  * @return The requested param
163  */
UI_GetParamNumber(const uiCallContext_t * context)164 int UI_GetParamNumber (const uiCallContext_t* context)
165 {
166 	if (context->useCmdParam)
167 		return Cmd_Argc() - 1;
168 	return context->paramNumber;
169 }
170 
171 /**
172  * Get a param from an execution context
173  * @param[in] context The execution context
174  * @param[in] paramID The ID of the requested param (first param is integer 1)
175  * @return The requested param
176  */
UI_GetParam(const uiCallContext_t * context,int paramID)177 const char* UI_GetParam (const uiCallContext_t* context, int paramID)
178 {
179 	linkedList_t* current;
180 	assert(paramID >= 1);
181 
182 	if (context->useCmdParam)
183 		return Cmd_Argv(paramID);
184 
185 	if (paramID > context->paramNumber) {
186 		Com_Printf("UI_GetParam: %i out of range\n", paramID);
187 		return "";
188 	}
189 
190 	current = context->params;
191 	while (paramID > 1) {
192 		current = current->next;
193 		paramID--;
194 	}
195 
196 	return (const char*) current->data;
197 }
198 
199 /**
200  * @brief Replace injection identifiers (e.g. &lt;eventParam&gt;) by a value
201  * @note The injection identifier can be every node value - e.g. &lt;image&gt; or &lt;width&gt;.
202  * It's also possible to do something like
203  * @code
204  * cmd "set someCvar &lt;min&gt;/&lt;max&gt;"
205  * @endcode
206  */
UI_GenInjectedString(const char * input,bool addNewLine,const uiCallContext_t * context)207 const char* UI_GenInjectedString (const char* input, bool addNewLine, const uiCallContext_t* context)
208 {
209 	static char cmd[256];
210 	int length = sizeof(cmd) - (addNewLine ? 2 : 1);
211 	static char propertyName[256];
212 	const char* cin = input;
213 	char* cout = cmd;
214 
215 	while (length && cin[0] != '\0') {
216 		if (cin[0] == '<') {
217 			/* read propertyName between '<' and '>' */
218 			const char* next = UI_GenCommandReadProperty(cin, propertyName, sizeof(propertyName));
219 			if (next) {
220 				/* cvar injection */
221 				if (char const* const rest = Q_strstart(propertyName, "cvar:")) {
222 					cvar_t const* const cvar = Cvar_Get(rest);
223 					const int l = snprintf(cout, length, "%s", cvar->string);
224 					cout += l;
225 					cin = next;
226 					length -= l;
227 					continue;
228 				} else if (char const* const path = Q_strstart(propertyName, "node:")) {
229 					uiNode_t* node;
230 					const value_t* property;
231 					const char* string;
232 					int l;
233 					UI_ReadNodePath(path, context->source, &node, &property);
234 					if (!node) {
235 						Com_Printf("UI_GenInjectedString: Node '%s' wasn't found; '' returned\n", path);
236 #ifdef DEBUG
237 						Com_Printf("UI_GenInjectedString: Path relative to '%s'\n", UI_GetPath(context->source));
238 #endif
239 						string = "";
240 					} else if (!property) {
241 						Com_Printf("UI_GenInjectedString: Property '%s' wasn't found; '' returned\n", path);
242 						string = "";
243 					} else {
244 						string = UI_GetStringFromNodeProperty(node, property);
245 						if (string == nullptr) {
246 							Com_Printf("UI_GenInjectedString: String getter for '%s' property do not exists; '' injected\n", path);
247 							string = "";
248 						}
249 					}
250 
251 					l = snprintf(cout, length, "%s", string);
252 					cout += l;
253 					cin = next;
254 					length -= l;
255 					continue;
256 				/* source path injection */
257 				} else if (char const* const command = Q_strstart(propertyName, "path:")) {
258 					if (context->source) {
259 						const uiNode_t* node = nullptr;
260 						if (Q_streq(command, "root"))
261 							node = context->source->root;
262 						else if (Q_streq(command, "this"))
263 							node = context->source;
264 						else if (Q_streq(command, "parent"))
265 							node = context->source->parent;
266 						else
267 							Com_Printf("UI_GenCommand: Command '%s' for path injection unknown\n", command);
268 
269 						if (node) {
270 							const int l = snprintf(cout, length, "%s", UI_GetPath(node));
271 							cout += l;
272 							cin = next;
273 							length -= l;
274 							continue;
275 						}
276 					}
277 
278 				/* no prefix */
279 				} else {
280 					/* source property injection */
281 					if (context->source) {
282 						/* find property definition */
283 						const value_t* property = UI_GetPropertyFromBehaviour(context->source->behaviour, propertyName);
284 						if (property) {
285 							const char* value;
286 							int l;
287 							/* inject the property value */
288 							value = UI_GetStringFromNodeProperty(context->source, property);
289 							if (value == nullptr)
290 								value = "";
291 							l = snprintf(cout, length, "%s", value);
292 							cout += l;
293 							cin = next;
294 							length -= l;
295 							continue;
296 						}
297 					}
298 
299 					/* param injection */
300 					if (UI_GetParamNumber(context) != 0) {
301 						int arg;
302 						const int checked = sscanf(propertyName, "%d", &arg);
303 						if (checked == 1 && arg >= 1 && arg <= UI_GetParamNumber(context)) {
304 							const int l = snprintf(cout, length, "%s", UI_GetParam(context, arg));
305 							cout += l;
306 							cin = next;
307 							length -= l;
308 							continue;
309 						}
310 					}
311 				}
312 			}
313 		}
314 		*cout++ = *cin++;
315 		length--;
316 	}
317 
318 	/* is buffer too small? */
319 	assert(cin[0] == '\0');
320 
321 	if (addNewLine)
322 		*cout++ = '\n';
323 
324 	*cout++ = '\0';
325 
326 	/* copy the result into a free va slot */
327 	return va("%s", cmd);
328 }
329 
330 /**
331  * Apply an action value to a node property. If the tuple property/value allow it, the function
332  * pre compute the value and update the action value to speed up the next call.
333  * @param node Node to edit
334  * @param property Property of the node to edit
335  * @param value Action value containing the value to set to the node property
336  * @param context Call context of the script
337  * @todo refactoring it to remove "context", we should only call that function when the action
338  * value is a leaf (then a value, and not an expression)
339  */
UI_NodeSetPropertyFromActionValue(uiNode_t * node,const value_t * property,const uiCallContext_t * context,uiAction_t * value)340 static void UI_NodeSetPropertyFromActionValue (uiNode_t* node, const value_t* property, const uiCallContext_t* context, uiAction_t* value)
341 {
342 	/* @todo we can use a new EA_VALUE type to flag already parsed values, we dont need to do it again and again */
343 	/* pre compute value if possible */
344 	if (value->type == EA_VALUE_STRING) {
345 		const char* string = value->d.terminal.d1.constString;
346 		if ((property->type & V_UI_MASK) == V_UI_CVAR && Q_strstart(string, "*cvar:")) {
347 			Com_GetValue<void*>(node, property) = value->d.terminal.d1.data;
348 		} else {
349 			/** @todo here we must catch error in a better way, and using cvar for error code to create unittest automations */
350 			UI_InitRawActionValue(value, node, property, string);
351 		}
352 	}
353 
354 	/* decode RAW value */
355 	if (value->type == EA_VALUE_RAW) {
356 		const void* rawValue = value->d.terminal.d1.constData;
357 		const int rawType = value->d.terminal.d2.integer;
358 		UI_NodeSetPropertyFromRAW(node, property, rawValue, rawType);
359 	}
360 	/* else it is an expression */
361 	else {
362 		/** @todo we should improve if when the prop is a boolean/int/float */
363 		const char* string = UI_GetStringFromExpression(value, context);
364 		UI_NodeSetProperty(node, property, string);
365 	}
366 }
367 
UI_ExecuteSetAction(const uiAction_t * action,const uiCallContext_t * context)368 static inline void UI_ExecuteSetAction (const uiAction_t* action, const uiCallContext_t* context)
369 {
370 	const char* path;
371 	uiNode_t* node;
372 	const value_t* property;
373 	const uiAction_t* left;
374 	uiAction_t* right;
375 
376 	left = action->d.nonTerminal.left;
377 	if (left == nullptr) {
378 		Com_Printf("UI_ExecuteSetAction: Action without left operand skipped.\n");
379 		return;
380 	}
381 
382 	right = action->d.nonTerminal.right;
383 	if (right == nullptr) {
384 		Com_Printf("UI_ExecuteSetAction: Action without right operand skipped.\n");
385 		return;
386 	}
387 
388 	if (left->type == EA_VALUE_CVARNAME || left->type == EA_VALUE_CVARNAME_WITHINJECTION) {
389 		const char* cvarName;
390 
391 		if (left->type == EA_VALUE_CVARNAME)
392 			cvarName = left->d.terminal.d1.constString;
393 		else
394 			cvarName = UI_GenInjectedString(left->d.terminal.d1.constString, false, context);
395 
396 		const char* textValue = CL_Translate(UI_GetStringFromExpression(right, context));
397 		Cvar_ForceSet(cvarName, textValue);
398 		return;
399 	}
400 
401 	/* search the node */
402 	if (left->type == EA_VALUE_PATHPROPERTY)
403 		path = left->d.terminal.d1.constString;
404 	else if (left->type == EA_VALUE_PATHPROPERTY_WITHINJECTION)
405 		path = UI_GenInjectedString(left->d.terminal.d1.constString, false, context);
406 	else
407 		Com_Error(ERR_FATAL, "UI_ExecuteSetAction: Property setter with wrong type '%d'", left->type);
408 
409 	UI_ReadNodePath(path, context->source, &node, &property);
410 	if (!node) {
411 		Com_Printf("UI_ExecuteSetAction: node \"%s\" doesn't exist (source: %s)\n", path, UI_GetPath(context->source));
412 		return;
413 	}
414 	if (!property) {
415 		Com_Printf("UI_ExecuteSetAction: property \"%s\" doesn't exist (source: %s)\n", path, UI_GetPath(context->source));
416 		return;
417 	}
418 
419 	UI_NodeSetPropertyFromActionValue(node, property, context, right);
420 }
421 
UI_ExecuteCallAction(const uiAction_t * action,const uiCallContext_t * context)422 static inline void UI_ExecuteCallAction (const uiAction_t* action, const uiCallContext_t* context)
423 {
424 	uiNode_t* callNode = nullptr;
425 	uiAction_t* param;
426 	uiAction_t* left = action->d.nonTerminal.left;
427 	uiCallContext_t newContext;
428 	const value_t* callProperty = nullptr;
429 	const char* path = left->d.terminal.d1.constString;
430 
431 	if (left->type == EA_VALUE_PATHPROPERTY || left->type == EA_VALUE_PATHNODE)
432 		path = left->d.terminal.d1.constString;
433 	else if (left->type == EA_VALUE_PATHPROPERTY_WITHINJECTION || left->type == EA_VALUE_PATHNODE_WITHINJECTION)
434 		path = UI_GenInjectedString(left->d.terminal.d1.constString, false, context);
435 	UI_ReadNodePath(path, context->source, &callNode, &callProperty);
436 
437 	if (callNode == nullptr) {
438 		Com_Printf("UI_ExecuteCallAction: Node from path \"%s\" not found (relative to \"%s\").\n", path, UI_GetPath(context->source));
439 		return;
440 	}
441 
442 	if (callProperty != nullptr && callProperty->type != V_UI_ACTION && callProperty->type != V_UI_NODEMETHOD) {
443 		Com_Printf("UI_ExecuteCallAction: Call operand %d unsupported. (%s)\n", callProperty->type, UI_GetPath(callNode));
444 		return;
445 	}
446 
447 	newContext.source = callNode;
448 	newContext.params = nullptr;
449 	newContext.paramNumber = 0;
450 	newContext.varNumber = 0;
451 	newContext.varPosition = context->varPosition + context->varNumber;
452 
453 	if (action->type == EA_LISTENER) {
454 		newContext.useCmdParam = context->useCmdParam;
455 
456 		if (!newContext.useCmdParam) {
457 			linkedList_t* p = context->params;
458 			while (p) {
459 				const char* value = (char*) p->data;
460 				LIST_AddString(&newContext.params, value);
461 				newContext.paramNumber++;
462 				p = p->next;
463 			}
464 		}
465 	} else {
466 		newContext.useCmdParam = false;
467 
468 		param = action->d.nonTerminal.right;
469 		while (param) {
470 			const char* value;
471 			value = UI_GetStringFromExpression(param, context);
472 			LIST_AddString(&newContext.params, value);
473 			newContext.paramNumber++;
474 			param = param->next;
475 		}
476 	}
477 
478 	if (callProperty == nullptr || callProperty->type == V_UI_ACTION) {
479 		uiAction_t const* const actionsRef = callProperty ? Com_GetValue<uiAction_t*>(callNode, callProperty) : callNode->onClick;
480 		UI_ExecuteActions(actionsRef, &newContext);
481 	} else if (callProperty->type == V_UI_NODEMETHOD) {
482 		uiNodeMethod_t func = (uiNodeMethod_t) callProperty->ofs;
483 		func(callNode, &newContext);
484 	} else {
485 		/* unreachable, already checked few line before */
486 		assert(false);
487 	}
488 
489 	LIST_Delete(&newContext.params);
490 }
491 
492 /**
493  * @brief Return a variable from the context
494  * @param context Call context
495  * @param relativeVarId id of the variable relative to the context
496  */
UI_GetVariable(const uiCallContext_t * context,int relativeVarId)497 uiValue_t* UI_GetVariable (const uiCallContext_t* context, int relativeVarId)
498 {
499 	const int varId = context->varPosition + relativeVarId;
500 	assert(relativeVarId >= 0);
501 	assert(relativeVarId < context->varNumber);
502 	return &(ui_global.variableStack[varId]);
503 }
504 
UI_ReleaseVariable(uiValue_t * variable)505 static void UI_ReleaseVariable (uiValue_t* variable)
506 {
507 	/** @todo most of cases here are useless, it only should be EA_VALUE_STRING */
508 	switch (variable->type) {
509 	case EA_VALUE_STRING:
510 		UI_FreeStringProperty(variable->value.string);
511 		break;
512 	case EA_VALUE_FLOAT:
513 	case EA_VALUE_NODE:
514 	case EA_VALUE_CVAR:
515 		/* nothing */
516 		break;
517 	default:
518 		Com_Error(ERR_FATAL, "UI_ReleaseVariable: Variable type \"%d\" unsupported", variable->type);
519 	}
520 
521 	/* bug safe, but useless */
522 	OBJZERO(*variable);
523 }
524 
525 /**
526  * @brief Execute an action from a source
527  * @param[in] context Context node
528  * @param[in] action Action to execute
529  */
UI_ExecuteAction(const uiAction_t * action,uiCallContext_t * context)530 static void UI_ExecuteAction (const uiAction_t* action, uiCallContext_t* context)
531 {
532 	switch (action->type) {
533 	case EA_NULL:
534 		/* do nothing */
535 		break;
536 
537 	case EA_CMD:
538 		/* execute a command */
539 		if (action->d.terminal.d1.constString)
540 			Cbuf_AddText("%s\n", UI_GenInjectedString(action->d.terminal.d1.constString, true, context));
541 		break;
542 
543 	case EA_CALL:
544 	case EA_LISTENER:
545 		UI_ExecuteCallAction(action, context);
546 		break;
547 
548 	case EA_POPVARS:
549 		{
550 			int i;
551 			const int number = action->d.terminal.d1.integer;
552 			assert(number <= context->varNumber);
553 			for (i = 0; i < number; i++) {
554 				const int varId = context->varPosition + context->varNumber - i - 1;
555 				UI_ReleaseVariable(&(ui_global.variableStack[varId]));
556 			}
557 			context->varNumber -= number;
558 		}
559 		break;
560 
561 	case EA_PUSHVARS:
562 #ifdef DEBUG
563 		/* check sanity */
564 		/** @todo check var slots should be empty */
565 #endif
566 		context->varNumber += action->d.terminal.d1.integer;
567 		if (context->varNumber >= UI_MAX_VARIABLESTACK)
568 			Com_Error(ERR_FATAL, "UI_ExecuteAction: Variable stack full. UI_MAX_VARIABLESTACK hit.");
569 		break;
570 
571 	case EA_ASSIGN:
572 		UI_ExecuteSetAction(action, context);
573 		break;
574 
575 	case EA_DELETE:
576 		{
577 			const char* cvarname = action->d.nonTerminal.left->d.terminal.d1.constString;
578 			Cvar_Delete(cvarname);
579 			break;
580 		}
581 
582 	case EA_WHILE:
583 		{
584 			int loop = 0;
585 			while (UI_GetBooleanFromExpression(action->d.nonTerminal.left, context)) {
586 				UI_ExecuteActions(action->d.nonTerminal.right, context);
587 				if (loop > 1000) {
588 					Com_Printf("UI_ExecuteAction: Infinite loop. Force breaking 'while'\n");
589 					break;
590 				}
591 				loop++;
592 			}
593 			break;
594 		}
595 
596 	case EA_IF:
597 		if (UI_GetBooleanFromExpression(action->d.nonTerminal.left, context)) {
598 			UI_ExecuteActions(action->d.nonTerminal.right, context);
599 			return;
600 		}
601 		action = action->next;
602 		while (action && action->type == EA_ELIF) {
603 			if (UI_GetBooleanFromExpression(action->d.nonTerminal.left, context)) {
604 				UI_ExecuteActions(action->d.nonTerminal.right, context);
605 				return;
606 			}
607 			action = action->next;
608 		}
609 		if (action && action->type == EA_ELSE) {
610 			UI_ExecuteActions(action->d.nonTerminal.right, context);
611 		}
612 		break;
613 
614 	/** @todo Skipping actions like that is a bad way. the function should return the next action,
615 	 * or we should move IF,ELSE,ELIF... in a IF block and not interleave it with default actions
616 	 */
617 	case EA_ELSE:
618 	case EA_ELIF:
619 		/* previous EA_IF execute this action */
620 		break;
621 
622 	default:
623 		Com_Error(ERR_FATAL, "UI_ExecuteAction: Unknown action type %i", action->type);
624 	}
625 }
626 
UI_ExecuteActions(const uiAction_t * firstAction,uiCallContext_t * context)627 static void UI_ExecuteActions (const uiAction_t* firstAction, uiCallContext_t* context)
628 {
629 	static int callnumber = 0;
630 	const uiAction_t* action;
631 	if (callnumber++ > 20) {
632 		Com_Printf("UI_ExecuteActions: Break possible infinite recursion\n");
633 		return;
634 	}
635 	for (action = firstAction; action; action = action->next) {
636 		UI_ExecuteAction(action, context);
637 	}
638 	callnumber--;
639 }
640 
641 /**
642  * @brief allow to inject command param into cmd of confunc command
643  */
UI_ExecuteConFuncActions(uiNode_t * source,const uiAction_t * firstAction)644 void UI_ExecuteConFuncActions (uiNode_t* source, const uiAction_t* firstAction)
645 {
646 	uiCallContext_t context;
647 	OBJZERO(context);
648 	context.source = source;
649 	context.useCmdParam = true;
650 	UI_ExecuteActions(firstAction, &context);
651 }
652 
UI_ExecuteEventActions(uiNode_t * source,const uiAction_t * firstAction)653 void UI_ExecuteEventActions (uiNode_t* source, const uiAction_t* firstAction)
654 {
655 	uiCallContext_t context;
656 	OBJZERO(context);
657 	context.source = source;
658 	context.useCmdParam = false;
659 	UI_ExecuteActions(firstAction, &context);
660 }
661 
UI_ExecuteEventActionsEx(uiNode_t * source,const uiAction_t * firstAction,linkedList_t * params)662 void UI_ExecuteEventActionsEx (uiNode_t* source, const uiAction_t* firstAction, linkedList_t* params)
663 {
664 	uiCallContext_t context;
665 	OBJZERO(context);
666 	context.source = source;
667 	context.useCmdParam = false;
668 	context.params = params;
669 	context.paramNumber = LIST_Count(params);
670 	UI_ExecuteActions(firstAction, &context);
671 }
672 
673 /**
674  * @brief Test if a string use an injection syntax
675  * @param[in] string The string to check for injection
676  * @return True if we find the following syntax in the string "<" {thing without space} ">"
677  */
UI_IsInjectedString(const char * string)678 bool UI_IsInjectedString (const char* string)
679 {
680 	const char* c = string;
681 	assert(string);
682 	while (*c != '\0') {
683 		if (*c == '<') {
684 			const char* d = c + 1;
685 			if (*d != '>') {
686 				while (*d) {
687 					if (*d == '>')
688 						return true;
689 					if (*d == ' ' || *d == '\t' || *d == '\n' || *d == '\r')
690 						break;
691 					d++;
692 				}
693 			}
694 		}
695 		c++;
696 	}
697 	return false;
698 }
699 
700 /**
701  * @brief Free a string property if it is allocated into ui_dynStringPool
702  * @param[in,out] pointer The pointer to the data that should be freed
703  * @sa ui_dynStringPool
704  */
UI_FreeStringProperty(void * pointer)705 void UI_FreeStringProperty (void* pointer)
706 {
707 	/* skip const string */
708 	if ((uintptr_t)ui_global.adata <= (uintptr_t)pointer && (uintptr_t)pointer < (uintptr_t)ui_global.adata + (uintptr_t)ui_global.adataize)
709 		return;
710 
711 	/* skip pointer out of ui_dynStringPool */
712 	if (!_Mem_AllocatedInPool(ui_dynStringPool, pointer))
713 		return;
714 
715 	Mem_Free(pointer);
716 }
717 
718 /**
719  * @brief Allocate and initialize a command action
720  * @param[in] command A command for the action
721  * @return An initialised action
722  */
UI_AllocStaticCommandAction(const char * command)723 uiAction_t* UI_AllocStaticCommandAction (const char* command)
724 {
725 	uiAction_t* action = UI_AllocStaticAction();
726 	action->type = EA_CMD;
727 	action->d.terminal.d1.constString = command;
728 	return action;
729 }
730 
731 /**
732  * @brief Set a new action to a @c uiAction_t pointer
733  * @param[in,out] action Allocated action
734  * @param[in] type Only @c EA_CMD is supported
735  * @param[in] data The data for this action - in case of @c EA_CMD this is the commandline
736  * @note You first have to free existing node actions - only free those that are
737  * not static in @c ui_global.actions array
738  * @todo we should create a function to free the memory. We can use a tag in the Mem_PoolAlloc
739  * calls and use use Mem_FreeTag.
740  */
UI_PoolAllocAction(uiAction_t ** action,int type,const void * data)741 void UI_PoolAllocAction (uiAction_t** action, int type, const void* data)
742 {
743 	if (*action)
744 		Com_Error(ERR_FATAL, "There is already an action assigned");
745 	*action = Mem_PoolAllocType(uiAction_t, ui_sysPool);
746 	(*action)->type = type;
747 	switch (type) {
748 	case EA_CMD:
749 		(*action)->d.terminal.d1.constString = Mem_PoolStrDup((const char*)data, ui_sysPool, 0);
750 		break;
751 	default:
752 		Com_Error(ERR_FATAL, "Action type %i is not yet implemented", type);
753 	}
754 }
755 
756 /**
757  * @brief Add a callback of a function into a node event. There can be more than on listener.
758  * @param[in,out] node The node to add the listener to.
759  * @param[in] property The property of the node to add the listener to.
760  * @param[in] functionNode The node of the listener callback.
761  */
UI_AddListener(uiNode_t * node,const value_t * property,const uiNode_t * functionNode)762 void UI_AddListener (uiNode_t* node, const value_t* property, const uiNode_t* functionNode)
763 {
764 	if (node->dynamic) {
765 		Com_Printf("UI_AddListener: '%s' is a dynamic node. We can't listen it.\n", UI_GetPath(node));
766 		return;
767 	}
768 	if (functionNode->dynamic) {
769 		Com_Printf("UI_AddListener: '%s' is a dynamic node. It can't be a listener callback.\n", UI_GetPath(functionNode));
770 		return;
771 	}
772 
773 	/* create the call action */
774 	uiAction_t* const action = Mem_PoolAllocType(uiAction_t, ui_sysPool);
775 	uiAction_t* const value  = Mem_PoolAllocType(uiAction_t, ui_sysPool);
776 	value->d.terminal.d1.constString = Mem_PoolStrDup(UI_GetPath(functionNode), ui_sysPool, 0);
777 	value->next = nullptr;
778 	action->type = EA_LISTENER;
779 	action->d.nonTerminal.left = value;
780 	/** @todo It is a hack, we should remove that */
781 	action->d.terminal.d2.constData = &functionNode->onClick;
782 	action->next = nullptr;
783 
784 	/* insert the action */
785 	uiAction_t** anchor = &Com_GetValue<uiAction_t*>(node, property);
786 	while (*anchor)
787 		anchor = &(*anchor)->next;
788 	*anchor = action;
789 }
790 
791 /**
792  * @brief add a call of a function into a node event
793  */
UI_AddListener_f(void)794 static void UI_AddListener_f (void)
795 {
796 	uiNode_t* node;
797 	uiNode_t* function;
798 	const value_t* property;
799 
800 	if (Cmd_Argc() != 3) {
801 		Com_Printf("Usage: %s <pathnode@event> <pathnode>\n", Cmd_Argv(0));
802 		return;
803 	}
804 
805 	UI_ReadNodePath(Cmd_Argv(1), nullptr, &node, &property);
806 	if (node == nullptr) {
807 		Com_Printf("UI_AddListener_f: '%s' node not found.\n", Cmd_Argv(1));
808 		return;
809 	}
810 	if (property == nullptr || property->type != V_UI_ACTION) {
811 		Com_Printf("UI_AddListener_f: '%s' property not found, or is not an event.\n", Cmd_Argv(1));
812 		return;
813 	}
814 
815 	function = UI_GetNodeByPath(Cmd_Argv(2));
816 	if (function == nullptr) {
817 		Com_Printf("UI_AddListener_f: '%s' node not found.\n", Cmd_Argv(2));
818 		return;
819 	}
820 
821 	UI_AddListener(node, property, function);
822 }
823 
824 /**
825  * @brief Remove a function callback from a node event. There can be more than on listener.
826  * @param[in,out] node The node to remove the listener from.
827  * @param[in] property The property of the node to remove the listener from.
828  * @param[in] functionNode The node of the listener callback.
829  */
UI_RemoveListener(uiNode_t * node,const value_t * property,uiNode_t * functionNode)830 void UI_RemoveListener (uiNode_t* node, const value_t* property, uiNode_t* functionNode)
831 {
832 	void* data;
833 
834 	/* data we must remove */
835 	data = (void*) &functionNode->onClick;
836 
837 	/* remove the action */
838 	uiAction_t*& action = Com_GetValue<uiAction_t*>(node, property);
839 	if (uiAction_t* lastAction = action) {
840 		uiAction_t* tmp = nullptr;
841 		if (lastAction->type == EA_LISTENER && lastAction->d.terminal.d2.constData == data) {
842 			tmp = lastAction;
843 			action = lastAction->next;
844 		} else {
845 			while (lastAction->next) {
846 				if (lastAction->next->type == EA_LISTENER && lastAction->next->d.terminal.d2.constData == data)
847 					break;
848 				lastAction = lastAction->next;
849 			}
850 			if (lastAction->next) {
851 				tmp = lastAction->next;
852 				lastAction->next = lastAction->next->next;
853 			}
854 		}
855 		if (tmp) {
856 			uiAction_t* value = tmp->d.nonTerminal.left;
857 			Mem_Free(value->d.terminal.d1.data);
858 			Mem_Free(value);
859 			Mem_Free(tmp);
860 		}
861 		else
862 			Com_Printf("UI_RemoveListener_f: '%s' into '%s' not found.\n", Cmd_Argv(2), Cmd_Argv(1));
863 	}
864 }
865 
866 /**
867  * @brief Remove a function callback from a node event
868  */
UI_RemoveListener_f(void)869 static void UI_RemoveListener_f (void)
870 {
871 	uiNode_t* node;
872 	uiNode_t* function;
873 	const value_t* property;
874 
875 	if (Cmd_Argc() != 3) {
876 		Com_Printf("Usage: %s <pathnode@event> <pathnode>\n", Cmd_Argv(0));
877 		return;
878 	}
879 
880 	UI_ReadNodePath(Cmd_Argv(1), nullptr, &node, &property);
881 	if (node == nullptr) {
882 		Com_Printf("UI_RemoveListener_f: '%s' node not found.\n", Cmd_Argv(1));
883 		return;
884 	}
885 	if (property == nullptr || property->type != V_UI_ACTION) {
886 		Com_Printf("UI_RemoveListener_f: '%s' property not found, or is not an event.\n", Cmd_Argv(1));
887 		return;
888 	}
889 
890 	function = UI_GetNodeByPath(Cmd_Argv(2));
891 	if (function == nullptr) {
892 		Com_Printf("UI_RemoveListener_f: '%s' node not found.\n", Cmd_Argv(2));
893 		return;
894 	}
895 
896 	UI_RemoveListener(node, property, function);
897 }
898 
UI_CvarChangeListener(const char * cvarName,const char * oldValue,const char * newValue,void * data)899 static void UI_CvarChangeListener (const char* cvarName, const char* oldValue, const char* newValue, void* data)
900 {
901 	linkedList_t* list = static_cast<linkedList_t*>(data);
902 	LIST_Foreach(list, char const, confunc) {
903 		UI_ExecuteConfunc("%s %s %s", confunc, oldValue, newValue);
904 	}
905 }
906 
UI_AddCvarListener_f(void)907 static void UI_AddCvarListener_f (void)
908 {
909 	const char* cvarName;
910 	const char* confuncName;
911 	cvarChangeListener_t* l;
912 
913 	if (Cmd_Argc() != 3) {
914 		Com_Printf("Usage: %s <cvar> <confunc>\n", Cmd_Argv(0));
915 		return;
916 	}
917 
918 	cvarName = Cmd_Argv(1);
919 	confuncName = Cmd_Argv(2);
920 	l = Cvar_RegisterChangeListener(cvarName, UI_CvarChangeListener);
921 	if (LIST_ContainsString(static_cast<linkedList_t*>(l->data), confuncName) == nullptr)
922 		LIST_AddString(reinterpret_cast<linkedList_t**>(&l->data), confuncName);
923 }
924 
UI_RemoveCvarListener_f(void)925 static void UI_RemoveCvarListener_f (void)
926 {
927 	const char* cvarName;
928 	const char* confuncName;
929 	cvar_t* var;
930 
931 	if (Cmd_Argc() != 3) {
932 		Com_Printf("Usage: %s <cvar> <confunc>\n", Cmd_Argv(0));
933 		return;
934 	}
935 
936 	cvarName = Cmd_Argv(1);
937 	confuncName = Cmd_Argv(2);
938 
939 	var = Cvar_FindVar(cvarName);
940 	if (var == nullptr)
941 		return;
942 
943 	cvarChangeListener_t* l = var->changeListener;
944 	while (l) {
945 		if (l->exec == UI_CvarChangeListener) {
946 			linkedList_t* entry = const_cast<linkedList_t*>(LIST_ContainsString(static_cast<linkedList_t*>(l->data), confuncName));
947 			if (entry != nullptr)
948 				LIST_RemoveEntry(reinterpret_cast<linkedList_t**>(&l->data), entry);
949 			if (LIST_IsEmpty(static_cast<linkedList_t*>(l->data))) {
950 				Cvar_UnRegisterChangeListener(cvarName, UI_CvarChangeListener);
951 			}
952 			return;
953 		}
954 		l = l->next;
955 	}
956 }
957 
UI_InitActions(void)958 void UI_InitActions (void)
959 {
960 	UI_CheckActionTokenTypeSanity();
961 	Cmd_AddCommand("ui_addcvarlistener", UI_AddCvarListener_f, "Add a confunc to a cvar change event");
962 	Cmd_AddCommand("ui_removecvarlistener", UI_RemoveCvarListener_f, "Remove a confunc from a cvar change event");
963 	/** @todo rework these commands to use a script language way */
964 	Cmd_AddCommand("ui_addlistener", UI_AddListener_f, "Add a function into a node event");
965 	Cmd_AddCommand("ui_removelistener", UI_RemoveListener_f, "Remove a function from a node event");
966 }
967