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