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 "../cl_shared.h"
26 #include "../cl_language.h"
27 #include "ui_main.h"
28 #include "ui_internal.h"
29 #include "ui_nodes.h"
30 #include "node/ui_node_linechart.h"
31 #include "node/ui_node_option.h"
32
33 #include "../cl_language.h"
34
35 /**
36 * @brief
37 */
38 static const char* const ui_sharedDataIDNames[] = {
39 "", /**< nullptr value */
40 STRINGIFY(TEXT_STANDARD),
41 STRINGIFY(TEXT_LIST),
42 STRINGIFY(TEXT_LIST2),
43 STRINGIFY(TEXT_UFOPEDIA),
44 STRINGIFY(TEXT_UFOPEDIA_REQUIREMENT),
45 STRINGIFY(TEXT_BUILDINGS),
46 STRINGIFY(TEXT_BUILDING_INFO),
47 STRINGIFY(TEXT_RESEARCH),
48 STRINGIFY(TEXT_POPUP),
49 STRINGIFY(TEXT_POPUP_INFO),
50 STRINGIFY(TEXT_AIRCRAFT_LIST),
51 STRINGIFY(TEXT_AIRCRAFT_INFO),
52 STRINGIFY(TEXT_MULTISELECTION),
53 STRINGIFY(TEXT_PRODUCTION_LIST),
54 STRINGIFY(TEXT_PRODUCTION_AMOUNT),
55 STRINGIFY(TEXT_PRODUCTION_INFO),
56 STRINGIFY(TEXT_EMPLOYEE),
57 STRINGIFY(TEXT_MOUSECURSOR_RIGHT),
58 STRINGIFY(TEXT_PRODUCTION_QUEUED),
59 STRINGIFY(TEXT_STATS_MISSION),
60 STRINGIFY(TEXT_STATS_BASES),
61 STRINGIFY(TEXT_STATS_NATIONS),
62 STRINGIFY(TEXT_STATS_EMPLOYEES),
63 STRINGIFY(TEXT_STATS_COSTS),
64 STRINGIFY(TEXT_STATS_INSTALLATIONS),
65 STRINGIFY(TEXT_STATS_7),
66 STRINGIFY(TEXT_BASE_LIST),
67 STRINGIFY(TEXT_BASE_INFO),
68 STRINGIFY(TEXT_TRANSFER_LIST),
69 STRINGIFY(TEXT_TRANSFER_LIST_AMOUNT),
70 STRINGIFY(TEXT_TRANSFER_LIST_TRANSFERED),
71 STRINGIFY(TEXT_MOUSECURSOR_PLAYERNAMES),
72 STRINGIFY(TEXT_CARGO_LIST),
73 STRINGIFY(TEXT_CARGO_LIST_AMOUNT),
74 STRINGIFY(TEXT_UFOPEDIA_MAILHEADER),
75 STRINGIFY(TEXT_UFOPEDIA_MAIL),
76 STRINGIFY(TEXT_MARKET_NAMES),
77 STRINGIFY(TEXT_MARKET_STORAGE),
78 STRINGIFY(TEXT_MARKET_MARKET),
79 STRINGIFY(TEXT_MARKET_PRICES),
80 STRINGIFY(TEXT_CHAT_WINDOW),
81 STRINGIFY(TEXT_AIREQUIP_1),
82 STRINGIFY(TEXT_AIREQUIP_2),
83 STRINGIFY(TEXT_BASEDEFENCE_LIST),
84 STRINGIFY(TEXT_TIPOFTHEDAY),
85 STRINGIFY(TEXT_GENERIC),
86 STRINGIFY(TEXT_XVI),
87 STRINGIFY(TEXT_MOUSECURSOR_TOP),
88 STRINGIFY(TEXT_MOUSECURSOR_BOTTOM),
89 STRINGIFY(TEXT_MOUSECURSOR_LEFT),
90 STRINGIFY(TEXT_MESSAGEOPTIONS),
91 STRINGIFY(TEXT_UFORECOVERY_NATIONS),
92 STRINGIFY(TEXT_UFORECOVERY_UFOYARDS),
93 STRINGIFY(TEXT_UFORECOVERY_CAPACITIES),
94 STRINGIFY(TEXT_MATERIAL_STAGES),
95 STRINGIFY(TEXT_IRCCONTENT),
96 STRINGIFY(TEXT_IRCUSERS),
97 STRINGIFY(TEXT_MULTIPLAYER_USERLIST),
98 STRINGIFY(TEXT_MULTIPLAYER_USERTEAM),
99 STRINGIFY(TEXT_ITEMDESCRIPTION),
100 STRINGIFY(TEXT_MISSIONBRIEFING),
101 STRINGIFY(TEXT_MISSIONBRIEFING_TITLE),
102 STRINGIFY(TEXT_MISSIONBRIEFING_VICTORY_CONDITIONS),
103
104 STRINGIFY(OPTION_LANGUAGES),
105 STRINGIFY(OPTION_JOYSTICKS),
106 STRINGIFY(OPTION_VIDEO_RESOLUTIONS),
107 STRINGIFY(OPTION_SINGLEPLAYER_SKINS),
108 STRINGIFY(OPTION_MULTIPLAYER_SKINS),
109 STRINGIFY(OPTION_UFOPEDIA),
110 STRINGIFY(OPTION_UFOS),
111 STRINGIFY(OPTION_DROPSHIPS),
112 STRINGIFY(OPTION_BASELIST),
113 STRINGIFY(OPTION_TEAMDEFS),
114 STRINGIFY(OPTION_PRODUCTION_REQUIREMENTS),
115 STRINGIFY(OPTION_CAMPAIGN_LIST),
116
117 STRINGIFY(LINESTRIP_FUNDING),
118 STRINGIFY(LINESTRIP_COLOR)
119 };
120 CASSERT(lengthof(ui_sharedDataIDNames) == UI_MAX_DATAID);
121
122 /**
123 * @brief Return a dataId by name
124 * @return A dataId if data found, else -1
125 */
UI_GetDataIDByName(const char * name)126 int UI_GetDataIDByName (const char* name)
127 {
128 int num;
129 for (num = 0; num < UI_MAX_DATAID; num++)
130 if (Q_streq(name, ui_sharedDataIDNames[num]))
131 return num;
132
133 return -1;
134 }
135
136 /**
137 * @brief share a text with a data id
138 * @note The UI code doesn't manage the text memory, it only save a pointer
139 */
UI_RegisterText(int dataId,const char * text)140 void UI_RegisterText (int dataId, const char* text)
141 {
142 UI_ResetData(dataId);
143
144 if (!text)
145 return;
146
147 ui_global.sharedData[dataId].type = UI_SHARED_TEXT;
148 ui_global.sharedData[dataId].data.text = text;
149 ui_global.sharedData[dataId].versionId++;
150 }
151
152 /**
153 * @brief share a linked list of text with a data id
154 * @note The UI code manage the linked list memory (linked list is freed by the UI code)
155 */
UI_RegisterLinkedListText(int dataId,linkedList_t * text)156 void UI_RegisterLinkedListText (int dataId, linkedList_t* text)
157 {
158 /** @todo FIXME It is a hack to disable release memory, if we only want to update the same list */
159 if (ui_global.sharedData[dataId].type == UI_SHARED_LINKEDLISTTEXT && ui_global.sharedData[dataId].data.linkedListText == text) {
160 ui_global.sharedData[dataId].versionId++;
161 return;
162 }
163 UI_ResetData(dataId);
164 ui_global.sharedData[dataId].type = UI_SHARED_LINKEDLISTTEXT;
165 ui_global.sharedData[dataId].data.linkedListText = text;
166 ui_global.sharedData[dataId].versionId++;
167 }
168
UI_GetText(int textId)169 const char* UI_GetText (int textId)
170 {
171 if (ui_global.sharedData[textId].type != UI_SHARED_TEXT)
172 return nullptr;
173 return CL_Translate(ui_global.sharedData[textId].data.text);
174 }
175
UI_GetTextFromList(int textId,int line)176 const char* UI_GetTextFromList (int textId, int line)
177 {
178 if (ui_global.sharedData[textId].type != UI_SHARED_LINKEDLISTTEXT)
179 return nullptr;
180 linkedList_t* list = ui_global.sharedData[textId].data.linkedListText;
181 return static_cast<const char*>(LIST_GetByIdx(list, line));
182 }
183
UI_GetDataVersion(int textId)184 int UI_GetDataVersion (int textId)
185 {
186 return ui_global.sharedData[textId].versionId;
187 }
188
189 /**
190 * @brief Append an option to an option list.
191 * @param[in,out] tree first option of the list/tree of options
192 * @param[in] name name of the option (should be unique in the option list)
193 * @param[in] label label displayed
194 * @param[in] value value used when this option is selected
195 * @return The new option
196 */
UI_AddOption(uiNode_t ** tree,const char * name,const char * label,const char * value)197 uiNode_t* UI_AddOption (uiNode_t** tree, const char* name, const char* label, const char* value)
198 {
199 uiNode_t* last;
200 uiNode_t* option;
201 assert(tree != nullptr);
202
203 option = UI_AllocOptionNode(name, label, value);
204
205 /* append the option */
206 last = *tree;
207 if (last != nullptr) {
208 while (last->next)
209 last = last->next;
210 }
211
212 if (last)
213 last->next = option;
214 else
215 *tree = option;
216
217 return option;
218 }
219
220 /**
221 * @warning If we use it with real option node, i will crash the code cause
222 * relation with parent node are not updated
223 * @param tree Root of nodes we want to delete
224 */
UI_DeleteOption(uiNode_t * tree)225 static void UI_DeleteOption (uiNode_t* tree)
226 {
227 while (tree) {
228 uiNode_t* del = tree;
229 tree = tree->next;
230 UI_DeleteNode(del);
231 }
232 }
233
234 /**
235 * @brief Reset a shared data. Type became NONE and value became nullptr
236 */
UI_ResetData(int dataId)237 void UI_ResetData (int dataId)
238 {
239 assert(dataId < UI_MAX_DATAID);
240 assert(dataId >= 0);
241
242 switch (ui_global.sharedData[dataId].type) {
243 case UI_SHARED_LINKEDLISTTEXT:
244 LIST_Delete(&ui_global.sharedData[dataId].data.linkedListText);
245 break;
246 case UI_SHARED_OPTION:
247 if (_Mem_AllocatedInPool(com_genericPool, ui_global.sharedData[dataId].data.option)) {
248 UI_DeleteOption(ui_global.sharedData[dataId].data.option);
249 }
250 break;
251 default:
252 break;
253 }
254
255 ui_global.sharedData[dataId].type = UI_SHARED_NONE;
256 ui_global.sharedData[dataId].data.text = nullptr;
257 ui_global.sharedData[dataId].versionId++;
258 }
259
260 /**
261 * @brief Remove the higher element (in alphabet) from a list
262 * @todo option should start with '_' if we need to translate it
263 * @warning update parent
264 */
UI_OptionNodeRemoveHigherOption(uiNode_t ** option)265 static uiNode_t* UI_OptionNodeRemoveHigherOption (uiNode_t** option)
266 {
267 uiNode_t* prev = *option;
268 uiNode_t* prevfind = nullptr;
269 uiNode_t* search = (*option)->next;
270 const char* label = CL_Translate(OPTIONEXTRADATA(*option).label);
271
272 /* search the smaller element */
273 while (search) {
274 const char* searchlabel = CL_Translate(OPTIONEXTRADATA(search).label);
275 if (strcmp(label, searchlabel) < 0) {
276 prevfind = prev;
277 label = searchlabel;
278 }
279 prev = search;
280 search = search->next;
281 }
282
283 /* remove the first element */
284 if (prevfind == nullptr) {
285 uiNode_t* tmp = *option;
286 *option = (*option)->next;
287 return tmp;
288 } else {
289 uiNode_t* tmp = prevfind->next;
290 prevfind->next = tmp->next;
291 return tmp;
292 }
293 }
294
295 /**
296 * @brief Sort options by alphabet
297 */
UI_SortOptions(uiNode_t ** first)298 void UI_SortOptions (uiNode_t** first)
299 {
300 uiNode_t* option;
301
302 /* unlink the unsorted list */
303 option = *first;
304 if (option == nullptr)
305 return;
306 *first = nullptr;
307
308 /* construct a sorted list */
309 while (option) {
310 uiNode_t* element;
311 element = UI_OptionNodeRemoveHigherOption(&option);
312 element->next = *first;
313 *first = element;
314 }
315 }
316
317 /**
318 * @brief Unhide those options that are stored in the linked list and hide the others
319 * @param[in,out] option Option list we want to update
320 * @param[in] stringList List of option name (ID) we want to display
321 */
UI_UpdateInvisOptions(uiNode_t * option,const linkedList_t * stringList)322 void UI_UpdateInvisOptions (uiNode_t* option, const linkedList_t* stringList)
323 {
324 if (option == nullptr || stringList == nullptr)
325 return;
326
327 while (option) {
328 if (LIST_ContainsString(stringList, option->name))
329 option->invis = false;
330 else
331 option->invis = true;
332 option = option->next;
333 }
334 }
335
UI_RegisterOption(int dataId,uiNode_t * option)336 void UI_RegisterOption (int dataId, uiNode_t* option)
337 {
338 /** Hack to disable release option memory, if we only want to update the same option */
339 if (ui_global.sharedData[dataId].type == UI_SHARED_OPTION && ui_global.sharedData[dataId].data.option == option) {
340 ui_global.sharedData[dataId].versionId++;
341 return;
342 }
343 UI_ResetData(dataId);
344 ui_global.sharedData[dataId].type = UI_SHARED_OPTION;
345 ui_global.sharedData[dataId].data.option = option;
346 ui_global.sharedData[dataId].versionId++;
347 }
348
UI_RegisterLineStrip(int dataId,lineStrip_t * lineStrip)349 void UI_RegisterLineStrip (int dataId, lineStrip_t* lineStrip)
350 {
351 UI_ResetData(dataId);
352 ui_global.sharedData[dataId].type = UI_SHARED_LINESTRIP;
353 ui_global.sharedData[dataId].data.lineStrip = lineStrip;
354 ui_global.sharedData[dataId].versionId++;
355 }
356
UI_GetOption(int dataId)357 uiNode_t* UI_GetOption (int dataId)
358 {
359 if (ui_global.sharedData[dataId].type == UI_SHARED_OPTION) {
360 return ui_global.sharedData[dataId].data.option;
361 }
362 return nullptr;
363 }
364
365 /**
366 * @brief find an option why index (0 is the first option)
367 * @param[in] index Requested index (0 is the first option)
368 * @param[in] option First element of options (it can be a tree)
369 * @param[in,out] iterator need an initialised iterator, and update it into the write index
370 */
UI_FindOptionAtIndex(int index,uiNode_t * option,uiOptionIterator_t * iterator)371 static uiNode_t* UI_FindOptionAtIndex (int index, uiNode_t* option, uiOptionIterator_t* iterator)
372 {
373 while (option) {
374 assert(option->behaviour == ui_optionBehaviour);
375 if (option->invis) {
376 option = option->next;
377 continue;
378 }
379
380 /* we are on the right element */
381 if (index == 0) {
382 iterator->option = option;
383 return option;
384 }
385
386 /* not the parent */
387 index--;
388
389 if (OPTIONEXTRADATA(option).collapsed) {
390 option = option->next;
391 continue;
392 }
393
394 /* its a child */
395 if (index < OPTIONEXTRADATA(option).childCount) {
396 if (iterator->depthPos >= MAX_DEPTH_OPTIONITERATORCACHE)
397 assert(false);
398 iterator->depthCache[iterator->depthPos] = option;
399 iterator->depthPos++;
400 return UI_FindOptionAtIndex(index, option->firstChild, iterator);
401 }
402 index -= OPTIONEXTRADATA(option).childCount;
403 option = option->next;
404 }
405
406 iterator->option = nullptr;
407 return nullptr;
408 }
409
410 /**
411 * @brief Init an option iterator at an index
412 * @note invis option are skipped, and child are counted
413 * @param[in] index Requested index (0 is the first option)
414 * @param[in] option First element of options (it can be a tree)
415 * @param[out] iterator Initialised iterator
416 * @return the first option element found (current position of the iterator)
417 * @code
418 * uiOptionIterator_t iterator;
419 * UI_InitOptionIteratorAtIndex(index, firstOption, &iterator); // also return the option
420 * while (iterator.option) {
421 * ...
422 * UI_OptionIteratorNextOption(&iterator); // also return the option
423 * }
424 * @endcode
425 * @todo Rework that code, we should split "Init" and "AtIndex"
426 */
UI_InitOptionIteratorAtIndex(int index,uiNode_t * option,uiOptionIterator_t * iterator)427 uiNode_t* UI_InitOptionIteratorAtIndex (int index, uiNode_t* option, uiOptionIterator_t* iterator)
428 {
429 assert(option == nullptr || option->behaviour == ui_optionBehaviour);
430 OBJZERO(*iterator);
431 iterator->skipCollapsed = true;
432 iterator->skipInvisible = true;
433 return UI_FindOptionAtIndex(index, option, iterator);
434 }
435
436 /**
437 * @brief Find the next element from the iterator
438 * Iterator skipCollapsed and skipInvisible attribute can control the option flow
439 */
UI_OptionIteratorNextOption(uiOptionIterator_t * iterator)440 uiNode_t* UI_OptionIteratorNextOption (uiOptionIterator_t* iterator)
441 {
442 uiNode_t* option;
443
444 option = iterator->option;
445 assert(iterator->depthPos < MAX_DEPTH_OPTIONITERATORCACHE);
446 iterator->depthCache[iterator->depthPos] = option;
447 iterator->depthPos++;
448
449 if (OPTIONEXTRADATA(option).collapsed && iterator->skipCollapsed)
450 option = nullptr;
451 else
452 option = option->firstChild;
453
454 while (true) {
455 while (option) {
456 if (!option->invis || !iterator->skipInvisible) {
457 iterator->option = option;
458 return option;
459 }
460 option = option->next;
461 }
462 if (iterator->depthPos == 0)
463 break;
464 iterator->depthPos--;
465 option = iterator->depthCache[iterator->depthPos]->next;
466 }
467
468 iterator->option = nullptr;
469 return nullptr;
470 }
471
472 /**
473 * @brief Find an option (and all his parents) by is value.
474 * @param[in,out] iterator If it found an option, the iterator contain all option parent
475 * @param[in] value The value we search
476 * @return The right option, else nullptr
477 */
UI_FindOptionByValue(uiOptionIterator_t * iterator,const char * value)478 uiNode_t* UI_FindOptionByValue (uiOptionIterator_t* iterator, const char* value)
479 {
480 while (iterator->option) {
481 assert(iterator->option->behaviour == ui_optionBehaviour);
482 if (Q_streq(OPTIONEXTRADATA(iterator->option).value, value))
483 return iterator->option;
484 UI_OptionIteratorNextOption(iterator);
485 }
486 return nullptr;
487 }
488
489 /**
490 * @brief Find an option position from an option iterator
491 * @param[in,out] iterator Context of the iteration. If it found an option, the iterator contain all option parent
492 * @param[in] option The value we search
493 * @return The option index, else -1
494 */
UI_FindOptionPosition(uiOptionIterator_t * iterator,const uiNode_t * option)495 int UI_FindOptionPosition (uiOptionIterator_t* iterator, const uiNode_t* option)
496 {
497 int i = 0;
498 while (iterator->option) {
499 if (iterator->option == option)
500 return i;
501 i++;
502 UI_OptionIteratorNextOption(iterator);
503 }
504 return -1;
505 }
506
507 /**
508 * @brief Resets the ui_global.sharedData pointers from a func node
509 * @note You can give this function a parameter to only delete a specific data
510 * @sa ui_sharedDataIDNames
511 */
UI_ResetData_f(void)512 static void UI_ResetData_f (void)
513 {
514 if (Cmd_Argc() == 2) {
515 const char* dataId = Cmd_Argv(1);
516 const int id = UI_GetDataIDByName(dataId);
517 if (id == -1)
518 Com_Printf("%s: invalid data ID: %s\n", Cmd_Argv(0), dataId);
519 else
520 UI_ResetData(id);
521 } else {
522 int i;
523 for (i = 0; i < UI_MAX_DATAID; i++)
524 UI_ResetData(i);
525 }
526 }
527
528 /**
529 * @brief Initialize console command about UI shared data
530 * @note called by UI_Init
531 */
UI_InitData(void)532 void UI_InitData (void)
533 {
534 Cmd_AddCommand("ui_resetdata", UI_ResetData_f, "Resets memory and data used by a UI data id");
535 }
536