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