1 /**
2  * @file
3  * @todo move it as an inheritance of panel behaviour?
4  */
5 
6 /*
7 Copyright (C) 2002-2013 UFO: Alien Invasion.
8 
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License
11 as published by the Free Software Foundation; either version 2
12 of the License, or (at your option) any later version.
13 
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17 
18 See the GNU General Public License for more details.
19 
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
23 
24 */
25 
26 #include "../ui_main.h"
27 #include "../ui_parse.h"
28 #include "../ui_font.h"
29 #include "../ui_nodes.h"
30 #include "../ui_internal.h"
31 #include "../ui_render.h"
32 #include "../ui_sprite.h"
33 #include "ui_node_window.h"
34 #include "ui_node_panel.h"
35 #include "ui_node_abstractnode.h"
36 
37 #include "../../client.h" /* gettext _() */
38 
39 #define EXTRADATA_TYPE windowExtraData_t
40 #define EXTRADATA(node) UI_EXTRADATA(node, EXTRADATA_TYPE)
41 #define EXTRADATACONST(node) UI_EXTRADATACONST(node, EXTRADATA_TYPE)
42 
43 #define TOP_HEIGHT 46
44 
45 static const int CONTROLS_IMAGE_DIMENSIONS = 25;
46 static const int CONTROLS_PADDING = 18;
47 
48 static const vec4_t modalBackground = {0, 0, 0, 0.6};
49 static const vec4_t anamorphicBorder = {0, 0, 0, 1};
50 
51 /**
52  * @brief Get a node from child index
53  * @return A child node by his name, else nullptr
54  */
UI_WindowNodeGetIndexedChild(uiNode_t * const node,const char * childName)55 uiNode_t* UI_WindowNodeGetIndexedChild (uiNode_t* const node, const char* childName)
56 {
57 	node_index_t* a;
58 	unsigned int hash;
59 
60 	hash = Com_HashKey(childName, INDEXEDCHILD_HASH_SIZE);
61 	for (a = EXTRADATA(node).index_hash[hash]; a; a = a->hash_next) {
62 		if (Q_streq(childName, a->node->name)) {
63 			return a->node;
64 		}
65 	}
66 	return nullptr;
67 }
68 
69 /**
70  * @brief Add a node to the child index
71  */
UI_WindowNodeAddIndexedNode(uiNode_t * const node,uiNode_t * const child)72 bool UI_WindowNodeAddIndexedNode (uiNode_t* const node, uiNode_t* const child)
73 {
74 	node_index_t* a;
75 	unsigned int hash;
76 
77 	hash = Com_HashKey(child->name, INDEXEDCHILD_HASH_SIZE);
78 	for (a = EXTRADATA(node).index_hash[hash]; a; a = a->hash_next) {
79 		if (Q_streq(child->name, a->node->name)) {
80 			/** @todo display a warning, we must not override a node name here */
81 			break;
82 		}
83 	}
84 
85 	if (!a) {
86 		a = Mem_PoolAllocType(node_index_t, ui_sysPool);
87 		a->next = EXTRADATA(node).index;
88 		a->hash_next = EXTRADATA(node).index_hash[hash];
89 		EXTRADATA(node).index_hash[hash] = a;
90 		EXTRADATA(node).index = a;
91 	}
92 
93 	return false;
94 }
95 
96 /**
97  * @brief Remove a node from the child index
98  */
UI_WindowNodeRemoveIndexedNode(uiNode_t * const node,uiNode_t * const child)99 bool UI_WindowNodeRemoveIndexedNode (uiNode_t* const node, uiNode_t* const child)
100 {
101 	/** @todo FIXME implement it */
102 	return false;
103 }
104 
105 /**
106  * @brief Check if a window is fullscreen or not
107  */
UI_WindowIsFullScreen(const uiNode_t * const node)108 bool UI_WindowIsFullScreen (const uiNode_t* const node)
109 {
110 	assert(UI_NodeInstanceOf(node, "window"));
111 	return EXTRADATACONST(node).isFullScreen;
112 }
113 
draw(uiNode_t * node)114 void uiWindowNode::draw (uiNode_t* node)
115 {
116 	const char* text;
117 	vec2_t pos;
118 	const char* font = UI_GetFontFromNode(node);
119 
120 	UI_GetNodeAbsPos(node, pos);
121 
122 	/* black border for anamorphic mode */
123 	/** @todo it should be over the window */
124 	/** @todo why not using glClear here with glClearColor set to black here? */
125 	if (UI_WindowIsFullScreen(node)) {
126 		/* top */
127 		if (pos[1] != 0)
128 			UI_DrawFill(0, 0, viddef.virtualWidth, pos[1], anamorphicBorder);
129 		/* left-right */
130 		if (pos[0] != 0)
131 			UI_DrawFill(0, pos[1], pos[0], node->box.size[1], anamorphicBorder);
132 		if (pos[0] + node->box.size[0] < viddef.virtualWidth) {
133 			const int width = viddef.virtualWidth - (pos[0] + node->box.size[0]);
134 			UI_DrawFill(viddef.virtualWidth - width, pos[1], width, node->box.size[1], anamorphicBorder);
135 		}
136 		/* bottom */
137 		if (pos[1] + node->box.size[1] < viddef.virtualHeight) {
138 			const int height = viddef.virtualHeight - (pos[1] + node->box.size[1]);
139 			UI_DrawFill(0, viddef.virtualHeight - height, viddef.virtualWidth, height, anamorphicBorder);
140 		}
141 	}
142 
143 	/* hide background if window is modal */
144 	if (EXTRADATA(node).modal && ui_global.windowStack[ui_global.windowStackPos - 1] == node)
145 		UI_DrawFill(0, 0, viddef.virtualWidth, viddef.virtualHeight, modalBackground);
146 
147 	if (EXTRADATA(node).background) {
148 		UI_DrawSpriteInBox(false, EXTRADATA(node).background, SPRITE_STATUS_NORMAL, pos[0], pos[1], node->box.size[0], node->box.size[1]);
149 	}
150 
151 	/* draw the title */
152 	text = UI_GetReferenceString(node, node->text);
153 	if (text)
154 		UI_DrawStringInBox(font, ALIGN_CC, pos[0] + node->padding, pos[1] + node->padding, node->box.size[0] - node->padding - node->padding, TOP_HEIGHT + 10 - node->padding - node->padding, text);
155 }
156 
doLayout(uiNode_t * node)157 void uiWindowNode::doLayout (uiNode_t* node)
158 {
159 	if (!node->invalidated)
160 		return;
161 
162 	/* use a the space */
163 	if (EXTRADATA(node).fill) {
164 		if (node->box.size[0] != viddef.virtualWidth) {
165 			node->box.size[0] = viddef.virtualWidth;
166 		}
167 		if (node->box.size[1] != viddef.virtualHeight) {
168 			node->box.size[1] = viddef.virtualHeight;
169 		}
170 	}
171 
172 	/* move fullscreen window on the center of the screen */
173 	if (UI_WindowIsFullScreen(node)) {
174 		node->box.pos[0] = (int) ((viddef.virtualWidth - node->box.size[0]) / 2);
175 		node->box.pos[1] = (int) ((viddef.virtualHeight - node->box.size[1]) / 2);
176 	}
177 
178 	/** @todo check and fix here window outside the screen */
179 
180 	if (EXTRADATA(node).starLayout) {
181 		UI_StarLayout(node);
182 	}
183 
184 	/* super */
185 	uiLocatedNode::doLayout(node);
186 }
187 
188 /**
189  * @brief Called when we init the node on the screen
190  */
onWindowOpened(uiNode_t * node,linkedList_t * params)191 void uiWindowNode::onWindowOpened (uiNode_t* node, linkedList_t* params)
192 {
193 	uiLocatedNode::onWindowOpened(node, nullptr);
194 
195 	/* script callback */
196 	if (EXTRADATA(node).onWindowOpened)
197 		UI_ExecuteEventActionsEx(node, EXTRADATA(node).onWindowOpened, params);
198 
199 	UI_Invalidate(node);
200 }
201 
202 /**
203  * @brief Called when we close the node on the screen
204  */
onWindowClosed(uiNode_t * node)205 void uiWindowNode::onWindowClosed (uiNode_t* node)
206 {
207 	uiLocatedNode::onWindowClosed(node);
208 
209 	/* script callback */
210 	if (EXTRADATA(node).onWindowClosed)
211 		UI_ExecuteEventActions(node, EXTRADATA(node).onWindowClosed);
212 }
213 
214 /**
215  * @brief Called when a windows gets active again after some other window was popped from the stack
216  */
onWindowActivate(uiNode_t * node)217 void uiWindowNode::onWindowActivate (uiNode_t* node)
218 {
219 	uiLocatedNode::onWindowActivate(node);
220 
221 	/* script callback */
222 	if (EXTRADATA(node).onWindowActivate)
223 		UI_ExecuteEventActions(node, EXTRADATA(node).onWindowActivate);
224 }
225 
226 /**
227  * @brief Called at the begin of the load from script
228  */
onLoading(uiNode_t * node)229 void uiWindowNode::onLoading (uiNode_t* node)
230 {
231 	node->box.size[0] = VID_NORM_WIDTH;
232 	node->box.size[1] = VID_NORM_HEIGHT;
233 	node->font = "f_big";
234 	node->padding = 5;
235 }
236 
237 /**
238  * @brief Called at the end of the load from script
239  */
onLoaded(uiNode_t * node)240 void uiWindowNode::onLoaded (uiNode_t* node)
241 {
242 	/* create a drag zone, if it is requested */
243 	if (EXTRADATA(node).dragButton) {
244 		uiNode_t* control = UI_AllocNode("move_window_button", "controls", node->dynamic);
245 		control->root = node;
246 		control->box.size[0] = node->box.size[0];
247 		control->box.size[1] = TOP_HEIGHT;
248 		control->box.pos[0] = 0;
249 		control->box.pos[1] = 0;
250 		control->tooltip = _("Drag to move window");
251 		UI_AppendNode(node, control);
252 	}
253 
254 	/* create a close button, if it is requested */
255 	if (EXTRADATA(node).closeButton) {
256 		uiNode_t* button = UI_AllocNode("close_window_button", "button", node->dynamic);
257 		const int positionFromRight = CONTROLS_PADDING;
258 		static const char* closeCommand = "ui_close <path:root>;";
259 
260 		button->root = node;
261 		UI_NodeSetProperty(button, UI_GetPropertyFromBehaviour(button->behaviour, "icon"), "icons/system_close");
262 		/** @todo Once @c image_t is known on the client, use @c image->width resp. @c image->height here */
263 		button->box.size[0] = CONTROLS_IMAGE_DIMENSIONS;
264 		button->box.size[1] = CONTROLS_IMAGE_DIMENSIONS;
265 		button->box.pos[0] = node->box.size[0] - positionFromRight - button->box.size[0];
266 		button->box.pos[1] = CONTROLS_PADDING;
267 		button->tooltip = _("Close the window");
268 		button->onClick = UI_AllocStaticCommandAction(closeCommand);
269 		UI_AppendNode(node, button);
270 	}
271 
272 	EXTRADATA(node).isFullScreen = node->box.size[0] == VID_NORM_WIDTH
273 			&& node->box.size[1] == VID_NORM_HEIGHT;
274 
275 	if (EXTRADATA(node).starLayout)
276 		UI_Invalidate(node);
277 }
278 
clone(const uiNode_t * source,uiNode_t * clone)279 void uiWindowNode::clone (const uiNode_t* source, uiNode_t* clone)
280 {
281 	/* clean up index */
282 	EXTRADATA(clone).index = nullptr;
283 	OBJZERO(EXTRADATA(clone).index_hash);
284 }
285 
286 /**
287  * @brief Get the noticePosition from a window node.
288  * @param node A window node
289  * @return A position, else nullptr if no notice position
290  */
UI_WindowNodeGetNoticePosition(uiNode_t * node)291 vec_t* UI_WindowNodeGetNoticePosition(uiNode_t* node)
292 {
293 	if (Vector2Empty(EXTRADATA(node).noticePos))
294 		return nullptr;
295 	return EXTRADATA(node).noticePos;
296 }
297 
298 /**
299  * @brief True if the window is a drop down.
300  * @param node A window node
301  * @return True if the window is a drop down.
302  */
UI_WindowIsDropDown(uiNode_t const * const node)303 bool UI_WindowIsDropDown(uiNode_t const* const node)
304 {
305 	return EXTRADATACONST(node).dropdown;
306 }
307 
308 /**
309  * @brief True if the window is a modal.
310  * @param node A window node
311  * @return True if the window is a modal.
312  */
UI_WindowIsModal(uiNode_t const * const node)313 bool UI_WindowIsModal(uiNode_t const* const node)
314 {
315 	return EXTRADATACONST(node).modal;
316 }
317 
318 /**
319  * @brief Add a key binding to a window node.
320  * Window node store key bindings for his node child.
321  * @param node A window node
322  * @param binding Key binding to link with the window (structure should not be already linked somewhere)
323  * @todo Rework that function to remove possible wrong use of that function
324  */
UI_WindowNodeRegisterKeyBinding(uiNode_t * node,uiKeyBinding_t * binding)325 void UI_WindowNodeRegisterKeyBinding (uiNode_t* node, uiKeyBinding_t* binding)
326 {
327 	assert(UI_NodeInstanceOf(node, "window"));
328 	binding->next = EXTRADATA(node).keyList;
329 	EXTRADATA(node).keyList = binding;
330 }
331 
332 const uiKeyBinding_t* binding;
333 
334 /**
335  * @brief Search a a key binding from a window node.
336  * Window node store key bindings for his node child.
337  * @param node A window node
338  * @param key A key code, either K_ value or lowercase ascii
339  */
UI_WindowNodeGetKeyBinding(uiNode_t const * const node,unsigned int key)340 uiKeyBinding_t* UI_WindowNodeGetKeyBinding (uiNode_t const* const node, unsigned int key)
341 {
342 	uiKeyBinding_t* binding = EXTRADATACONST(node).keyList;
343 	assert(UI_NodeInstanceOf(node, "window"));
344 	while (binding) {
345 		if (binding->key == key)
346 			break;
347 		binding = binding->next;
348 	}
349 	return binding;
350 }
351 
UI_RegisterWindowNode(uiBehaviour_t * behaviour)352 void UI_RegisterWindowNode (uiBehaviour_t* behaviour)
353 {
354 	behaviour->name = "window";
355 	behaviour->manager = UINodePtr(new uiWindowNode());
356 	behaviour->extraDataSize = sizeof(EXTRADATA_TYPE);
357 
358 	/* In windows where notify messages appear (like e.g. the video options window when you have to restart the game until
359 	 * the settings take effects) you can define the position of those messages with this option. */
360 	UI_RegisterExtradataNodeProperty(behaviour, "noticepos", V_POS, windowExtraData_t, noticePos);
361 	/* Create subnode allowing to move the window when we click on the header. Updating this attribute at runtime will change nothing. */
362 	UI_RegisterExtradataNodeProperty(behaviour, "dragbutton", V_BOOL, windowExtraData_t, dragButton);
363 	/* Add a button on the top right the window to close it. Updating this attribute at runtime will change nothing. */
364 	UI_RegisterExtradataNodeProperty(behaviour, "closebutton", V_BOOL, windowExtraData_t, closeButton);
365 	/* If true, the user can't select something outside the modal window. He must first close the window. */
366 	UI_RegisterExtradataNodeProperty(behaviour, "modal", V_BOOL, windowExtraData_t, modal);
367 	/* If true, the window will be closed if the user clicks outside of the window. */
368 	UI_RegisterExtradataNodeProperty(behaviour, "dropdown", V_BOOL, windowExtraData_t, dropdown);
369 	/* If true, the user can't use ''ESC'' key to close the window. */
370 	UI_RegisterExtradataNodeProperty(behaviour, "preventtypingescape", V_BOOL, windowExtraData_t, preventTypingEscape);
371 	/* If true, the window is filled according to the widescreen. */
372 	UI_RegisterExtradataNodeProperty(behaviour, "fill", V_BOOL, windowExtraData_t, fill);
373 	/* If true, the window content position is updated according to the "star" layout when the window size change.
374 	 * @todo Need more documentation.
375 	 */
376 	UI_RegisterExtradataNodeProperty(behaviour, "starlayout", V_BOOL, windowExtraData_t, starLayout);
377 
378 	/* Invoked when the window is added to the rendering stack. */
379 	UI_RegisterExtradataNodeProperty(behaviour, "onWindowOpened", V_UI_ACTION, windowExtraData_t, onWindowOpened);
380 	/* Invoked when the window is removed from the rendering stack. */
381 	UI_RegisterExtradataNodeProperty(behaviour, "onWindowClosed", V_UI_ACTION, windowExtraData_t, onWindowClosed);
382 	/* Called when a windows gets active again after some other window was popped from the stack. */
383 	UI_RegisterExtradataNodeProperty(behaviour, "onWindowActivate", V_UI_ACTION, windowExtraData_t, onWindowActivate);
384 	/* Invoked after all UI scripts are loaded. */
385 	UI_RegisterExtradataNodeProperty(behaviour, "onScriptLoaded", V_UI_ACTION, windowExtraData_t, onScriptLoaded);
386 
387 	/* Sprite used to display the background */
388 	UI_RegisterExtradataNodeProperty(behaviour, "background", V_UI_SPRITEREF, EXTRADATA_TYPE, background);
389 }
390