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. <eventParam>) by a value
201 * @note The injection identifier can be every node value - e.g. <image> or <width>.
202 * It's also possible to do something like
203 * @code
204 * cmd "set someCvar <min>/<max>"
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