1 // SuperTuxKart - a fun racing game with go-kart
2 // Copyright (C) 2009-2015 Marianne Gagnon
3 //
4 // This program is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU General Public License
6 // as published by the Free Software Foundation; either version 3
7 // of the License, or (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program; if not, write to the Free Software
16 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17
18 #include "guiengine/widget.hpp"
19
20 #include <iostream>
21 #include <sstream>
22
23 #include <IGUIElement.h>
24
25 using namespace irr;
26 using namespace core;
27 using namespace video;
28 using namespace io;
29 using namespace gui;
30
31 #include "guiengine/engine.hpp"
32 #include "guiengine/scalable_font.hpp"
33 #include "guiengine/screen.hpp"
34 #include "io/file_manager.hpp"
35 #include "utils/string_utils.hpp"
36 #include "utils/translation.hpp"
37 #include "utils/vs.hpp"
38
39 namespace GUIEngine
40 {
41
42 static bool g_is_within_a_text_box = false;
isWithinATextBox()43 bool isWithinATextBox()
44 {
45 return g_is_within_a_text_box;
46 }
setWithinATextBox(bool in)47 void setWithinATextBox(bool in)
48 {
49 g_is_within_a_text_box = in;
50 }
51 }
52 using namespace GUIEngine;
53
54 // -----------------------------------------------------------------------------
55
Widget(WidgetType type,bool reserve_id)56 Widget::Widget(WidgetType type, bool reserve_id)
57 {
58 // Accept all input by default
59 m_active_event_callback.flip();
60 m_active_event_callback[Input::IT_NONE] = false;
61 m_magic_number = 0xCAFEC001;
62
63 m_x = -1;
64 m_y = -1;
65 m_w = -1;
66 m_h = -1;
67 m_id = -1;
68 m_badge_x_shift = 0;
69 m_element = NULL;
70 m_title_font = false;
71 m_type = type;
72 m_parent = NULL;
73 m_focusable = true;
74 m_bottom_bar = false;
75 m_top_bar = false;
76 m_event_handler = NULL;
77 m_reserve_id = reserve_id;
78 m_show_bounding_box = false;
79 m_supports_multiplayer = false;
80 m_is_bounding_box_round = false;
81 m_has_tooltip = false;
82 m_uncollapsed_height = 0;
83 m_is_collapsed = false;
84
85 m_absolute_x = m_absolute_y = m_absolute_w = m_absolute_h = -1;
86 m_relative_x = m_relative_y = m_relative_w = m_relative_h = -1;
87 m_absolute_reverse_x = m_absolute_reverse_y = -1;
88
89
90 m_tab_down_root = -1;
91 m_tab_up_root = -1;
92
93 for (unsigned int n=0; n<MAX_PLAYER_COUNT; n++)
94 {
95 m_player_focus[n] = false;
96 m_selected[n] = false;
97 }
98
99 m_reserved_id = -1;
100 m_deactivated = false;
101 m_is_visible = true;
102 m_badges = 0;
103
104 // set a default value, derivates can override this as they wish
105 m_check_inside_me = (m_type == WTYPE_DIV);
106 }
107
108 // -----------------------------------------------------------------------------
109
~Widget()110 Widget::~Widget()
111 {
112 assert(m_magic_number == 0xCAFEC001);
113
114 // If any player focused this widget, unset that focus
115 for (unsigned int n=0; n<MAX_PLAYER_COUNT; n++)
116 {
117 if (m_player_focus[n])
118 {
119 GUIEngine::focusNothingForPlayer(n);
120 }
121 }
122
123 m_magic_number = 0xDEADBEEF;
124 }
125
126 // -----------------------------------------------------------------------------
setEventCallbackActive(Input::InputType type,bool active)127 void Widget::setEventCallbackActive(Input::InputType type, bool active)
128 {
129 m_active_event_callback[type] = active;
130 }
131
132 // -----------------------------------------------------------------------------
isEventCallbackActive(Input::InputType type) const133 bool Widget::isEventCallbackActive(Input::InputType type) const
134 {
135 return m_active_event_callback[type];
136 }
137
138 // -----------------------------------------------------------------------------
setText(const irr::core::stringw & s)139 void Widget::setText(const irr::core::stringw &s)
140 {
141 m_text = s;
142 if(m_element)
143 m_element->setText(s);
144 } // setText
145
146 // -----------------------------------------------------------------------------
147
elementRemoved()148 void Widget::elementRemoved()
149 {
150 assert(m_magic_number == 0xCAFEC001);
151
152 m_element = NULL;
153 m_is_visible = true;
154
155 // If any player focused this widget, unset that focus
156 for (unsigned int n=0; n<MAX_PLAYER_COUNT; n++)
157 {
158 if (m_player_focus[n])
159 {
160 GUIEngine::focusNothingForPlayer(n);
161 }
162 }
163
164 }
165
166 // -----------------------------------------------------------------------------
167
setActive(bool active)168 void Widget::setActive(bool active)
169 {
170 // even if this one is already active, do it anyway on purpose, maybe the
171 // children widgets need to be updated
172 m_deactivated = !active;
173 const int count = m_children.size();
174 for (int n=0; n<count; n++)
175 {
176 m_children[n].setActive(active);
177 }
178 }
179
180 // -----------------------------------------------------------------------------
181
deleteChild(const char * id)182 bool Widget::deleteChild(const char* id)
183 {
184 const int count = m_children.size();
185 for (int n=0; n<count; n++)
186 {
187 if (m_children[n].m_properties[PROP_ID] == id)
188 {
189 m_children.erase(n);
190 return true;
191 }
192 }
193 return false;
194 }
195
196 // -----------------------------------------------------------------------------
197 namespace GUIEngine
198 {
199 // IDs must not start at 0, since it appears their GUI engine hardcodes some ID values
200 const unsigned int FOCUSABLE_IDS_BASE = 100;
201 const unsigned int UNFOCUSABLE_IDS_BASE = 1000;
202
203 /** Used to assign irrLicht IDs to widgets dynamically */
204 static unsigned int id_counter = FOCUSABLE_IDS_BASE;
205
206 /** for items that can't be reached with keyboard navigation but can be clicked */
207 static unsigned int id_counter_2 = UNFOCUSABLE_IDS_BASE;
208 }
209
getNewID()210 int Widget::getNewID()
211 {
212 return id_counter++;
213 }
getNewNoFocusID()214 int Widget::getNewNoFocusID()
215 {
216 return id_counter_2++;
217 }
218
219
isFocusableId(const int id)220 bool Widget::isFocusableId(const int id)
221 {
222 if (id < 0) return false;
223
224 if ((unsigned int)id >= UNFOCUSABLE_IDS_BASE) return false;
225 else return true;
226 }
227
228 // -----------------------------------------------------------------------------
229
230 /** When switching to a new screen, this function will be called to reset ID counters
231 * (so we start again from ID 0, and don't grow to big numbers) */
resetIDCounters()232 void Widget::resetIDCounters()
233 {
234 id_counter = 100;
235 id_counter_2 = 1000;
236 }
237
238 // -----------------------------------------------------------------------------
239
add()240 void Widget::add()
241 {
242 assert(m_magic_number == 0xCAFEC001);
243 if (m_reserve_id)
244 {
245 m_reserved_id = getNewID();
246 }
247 }
248
249 // -----------------------------------------------------------------------------
250 /**
251 * \param playerID ID of the player you want to set/unset focus for, starting from 0
252 * Since the code tracks focus from main player, this will most likely be used only
253 * for additionnal players
254 */
setFocusForPlayer(const int playerID)255 void Widget::setFocusForPlayer(const int playerID)
256 {
257 assert(m_magic_number == 0xCAFEC001);
258
259 // Unset focus flag on previous widget that had focus
260 Widget* previous_focus = GUIEngine::getFocusForPlayer(playerID);
261 if (previous_focus != NULL)
262 {
263 previous_focus->unfocused(playerID, this);
264 previous_focus->m_player_focus[playerID] = false;
265 }
266
267 m_player_focus[playerID] = true;
268 GUIEngine::Private::g_focus_for_player[playerID] = this;
269
270 // Callback
271 this->focused(playerID);
272
273 Screen* screen = GUIEngine::getCurrentScreen();
274 if(screen)
275 screen->onFocusChanged(previous_focus, this, playerID);
276 }
277
278 // -----------------------------------------------------------------------------
279
unsetFocusForPlayer(const int playerID)280 void Widget::unsetFocusForPlayer(const int playerID)
281 {
282 assert(m_magic_number == 0xCAFEC001);
283
284 if (m_player_focus[playerID]) this->unfocused(playerID, NULL);
285 m_player_focus[playerID] = false;
286 }
287
288 // -----------------------------------------------------------------------------
289
290 /**
291 * \param playerID ID of the player you want to set/unset focus for, starting from 0
292 */
isFocusedForPlayer(const int playerID)293 bool Widget::isFocusedForPlayer(const int playerID)
294 {
295 assert(m_magic_number == 0xCAFEC001);
296
297 return m_player_focus[playerID];
298 }
299
300 // -----------------------------------------------------------------------------
301
move(const int x,const int y,const int w,const int h)302 void Widget::move(const int x, const int y, const int w, const int h)
303 {
304 assert(m_magic_number == 0xCAFEC001);
305
306 m_x = x;
307 m_y = y;
308 m_w = w;
309 m_h = h;
310
311 if (m_element != NULL)
312 m_element->setRelativePosition( core::rect < s32 > (x, y, x+w, y+h) );
313 }
314
315 // -----------------------------------------------------------------------------
316
setParent(IGUIElement * parent)317 void Widget::setParent(IGUIElement* parent)
318 {
319 assert(m_magic_number == 0xCAFEC001);
320 m_parent = parent;
321 }
322
323 // -----------------------------------------------------------------------------
324
isVisible() const325 bool Widget::isVisible() const
326 {
327 if (m_element != NULL)
328 {
329 // repair mismatch
330 if (m_element->isVisible() != m_is_visible)
331 m_element->setVisible(m_is_visible);
332 }
333 return m_is_visible;
334 }
335
336 // -----------------------------------------------------------------------------
337
isActivated() const338 bool Widget::isActivated() const
339 {
340 if (isVisible())
341 return !m_deactivated;
342 return false;
343 }
344
345 // -----------------------------------------------------------------------------
346
setVisible(bool visible)347 void Widget::setVisible(bool visible)
348 {
349 if (m_element != NULL)
350 {
351 m_element->setVisible(visible);
352 }
353 m_is_visible = visible;
354
355 const int childrenCount = m_children.size();
356 for (int n=0; n<childrenCount; n++)
357 {
358 m_children[n].setVisible(visible);
359 }
360 }
361
362 // -----------------------------------------------------------------------------
363
setCollapsed(const bool collapsed,Screen * calling_screen)364 void Widget::setCollapsed(const bool collapsed, Screen* calling_screen)
365 {
366 if (!m_is_collapsed)
367 m_uncollapsed_height = m_h;
368
369 setCollapsed(collapsed, m_uncollapsed_height, calling_screen);
370 }
371
372 // -----------------------------------------------------------------------------
373 //TODO : Support recalculating layout for dialogs
374
setCollapsed(const bool collapsed,const int uncollapsed_height,Screen * calling_screen)375 void Widget::setCollapsed(const bool collapsed, const int uncollapsed_height, Screen* calling_screen)
376 {
377 m_uncollapsed_height = uncollapsed_height;
378
379 setVisible(!collapsed);
380
381 if (collapsed)
382 m_properties[GUIEngine::PROP_HEIGHT] = "0";
383 else
384 m_properties[GUIEngine::PROP_HEIGHT] = StringUtils::toString(m_uncollapsed_height);
385
386 m_is_collapsed = collapsed;
387
388 if (calling_screen != NULL)
389 calling_screen->calculateLayout();
390 }
391
392 // -----------------------------------------------------------------------------
393
moveIrrlichtElement()394 void Widget::moveIrrlichtElement()
395 {
396 if (m_element != NULL)
397 {
398 m_element->setRelativePosition( irr::core::recti(irr::core::position2di(m_x, m_y),
399 irr::core::dimension2di(m_w, m_h) ) );
400 }
401 }
402