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