Support deleting GUI Object event handlers following rP666, refs #5387.
[0ad.git] / source / gui / ObjectBases / IGUIObject.cpp
blob52232ee88f3a7fdfba01a4ede330564eae994a97
1 /* Copyright (C) 2019 Wildfire Games.
2 * This file is part of 0 A.D.
4 * 0 A.D. is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
9 * 0 A.D. 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.
14 * You should have received a copy of the GNU General Public License
15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
18 #include "precompiled.h"
20 #include "IGUIObject.h"
22 #include "gui/CGUI.h"
23 #include "gui/CGUISetting.h"
24 #include "ps/CLogger.h"
25 #include "ps/GameSetup/Config.h"
26 #include "ps/Profile.h"
27 #include "scriptinterface/ScriptInterface.h"
28 #include "soundmanager/ISoundManager.h"
30 IGUIObject::IGUIObject(CGUI& pGUI)
31 : m_pGUI(pGUI),
32 m_pParent(),
33 m_MouseHovering(),
34 m_LastClickTime(),
35 m_Enabled(),
36 m_Hidden(),
37 m_Size(),
38 m_Style(),
39 m_Hotkey(),
40 m_Z(),
41 m_Absolute(),
42 m_Ghost(),
43 m_AspectRatio(),
44 m_Tooltip(),
45 m_TooltipStyle()
47 RegisterSetting("enabled", m_Enabled);
48 RegisterSetting("hidden", m_Hidden);
49 RegisterSetting("size", m_Size);
50 RegisterSetting("style", m_Style);
51 RegisterSetting("hotkey", m_Hotkey);
52 RegisterSetting("z", m_Z);
53 RegisterSetting("absolute", m_Absolute);
54 RegisterSetting("ghost", m_Ghost);
55 RegisterSetting("aspectratio", m_AspectRatio);
56 RegisterSetting("tooltip", m_Tooltip);
57 RegisterSetting("tooltip_style", m_TooltipStyle);
59 // Setup important defaults
60 // TODO: Should be in the default style?
61 SetSetting<bool>("hidden", false, true);
62 SetSetting<bool>("ghost", false, true);
63 SetSetting<bool>("enabled", true, true);
64 SetSetting<bool>("absolute", true, true);
67 IGUIObject::~IGUIObject()
69 for (const std::pair<CStr, IGUISetting*>& p : m_Settings)
70 delete p.second;
72 if (!m_ScriptHandlers.empty())
73 JS_RemoveExtraGCRootsTracer(m_pGUI.GetScriptInterface()->GetJSRuntime(), Trace, this);
75 // m_Children is deleted along all other GUI Objects in the CGUI destructor
78 void IGUIObject::AddChild(IGUIObject& pChild)
80 pChild.SetParent(this);
81 m_Children.push_back(&pChild);
84 template<typename T>
85 void IGUIObject::RegisterSetting(const CStr& Name, T& Value)
87 if (SettingExists(Name))
88 LOGERROR("The setting '%s' already exists on the object '%s'!", Name.c_str(), GetPresentableName().c_str());
89 else
90 m_Settings.emplace(Name, new CGUISetting<T>(*this, Name, Value));
93 bool IGUIObject::SettingExists(const CStr& Setting) const
95 return m_Settings.find(Setting) != m_Settings.end();
98 template <typename T>
99 T& IGUIObject::GetSetting(const CStr& Setting)
101 return static_cast<CGUISetting<T>* >(m_Settings.at(Setting))->m_pSetting;
104 template <typename T>
105 const T& IGUIObject::GetSetting(const CStr& Setting) const
107 return static_cast<CGUISetting<T>* >(m_Settings.at(Setting))->m_pSetting;
110 bool IGUIObject::SetSettingFromString(const CStr& Setting, const CStrW& Value, const bool SendMessage)
112 const std::map<CStr, IGUISetting*>::iterator it = m_Settings.find(Setting);
113 if (it == m_Settings.end())
115 LOGERROR("GUI object '%s' has no property called '%s', can't set parse and set value '%s'", GetPresentableName().c_str(), Setting.c_str(), Value.ToUTF8().c_str());
116 return false;
118 return it->second->FromString(Value, SendMessage);
121 template <typename T>
122 void IGUIObject::SetSetting(const CStr& Setting, T& Value, const bool SendMessage)
124 PreSettingChange(Setting);
125 static_cast<CGUISetting<T>* >(m_Settings.at(Setting))->m_pSetting = std::move(Value);
126 SettingChanged(Setting, SendMessage);
129 template <typename T>
130 void IGUIObject::SetSetting(const CStr& Setting, const T& Value, const bool SendMessage)
132 PreSettingChange(Setting);
133 static_cast<CGUISetting<T>* >(m_Settings.at(Setting))->m_pSetting = Value;
134 SettingChanged(Setting, SendMessage);
137 void IGUIObject::PreSettingChange(const CStr& Setting)
139 if (Setting == "hotkey")
140 m_pGUI.UnsetObjectHotkey(this, GetSetting<CStr>(Setting));
143 void IGUIObject::SettingChanged(const CStr& Setting, const bool SendMessage)
145 if (Setting == "size")
147 // If setting was "size", we need to re-cache itself and all children
148 RecurseObject(nullptr, &IGUIObject::UpdateCachedSize);
150 else if (Setting == "hidden")
152 // Hiding an object requires us to reset it and all children
153 if (m_Hidden)
154 RecurseObject(nullptr, &IGUIObject::ResetStates);
156 else if (Setting == "hotkey")
157 m_pGUI.SetObjectHotkey(this, GetSetting<CStr>(Setting));
159 if (SendMessage)
161 SGUIMessage msg(GUIM_SETTINGS_UPDATED, Setting);
162 HandleMessage(msg);
166 bool IGUIObject::IsMouseOver() const
168 return m_CachedActualSize.PointInside(m_pGUI.GetMousePos());
171 bool IGUIObject::MouseOverIcon()
173 return false;
176 void IGUIObject::UpdateMouseOver(IGUIObject* const& pMouseOver)
178 if (pMouseOver == this)
180 if (!m_MouseHovering)
181 SendEvent(GUIM_MOUSE_ENTER, "mouseenter");
183 m_MouseHovering = true;
185 SendEvent(GUIM_MOUSE_OVER, "mousemove");
187 else
189 if (m_MouseHovering)
191 m_MouseHovering = false;
192 SendEvent(GUIM_MOUSE_LEAVE, "mouseleave");
197 void IGUIObject::ChooseMouseOverAndClosest(IGUIObject*& pObject)
199 if (!IsMouseOver())
200 return;
202 // Check if we've got competition at all
203 if (pObject == nullptr)
205 pObject = this;
206 return;
209 // Or if it's closer
210 if (GetBufferedZ() >= pObject->GetBufferedZ())
212 pObject = this;
213 return;
217 IGUIObject* IGUIObject::GetParent() const
219 // Important, we're not using GetParent() for these
220 // checks, that could screw it up
221 if (m_pParent && m_pParent->m_pParent == nullptr)
222 return nullptr;
224 return m_pParent;
227 void IGUIObject::ResetStates()
229 // Notify the gui that we aren't hovered anymore
230 UpdateMouseOver(nullptr);
233 void IGUIObject::UpdateCachedSize()
235 // If absolute="false" and the object has got a parent,
236 // use its cached size instead of the screen. Notice
237 // it must have just been cached for it to work.
238 if (!m_Absolute && m_pParent && !IsRootObject())
239 m_CachedActualSize = m_Size.GetSize(m_pParent->m_CachedActualSize);
240 else
241 m_CachedActualSize = m_Size.GetSize(CRect(0.f, 0.f, g_xres / g_GuiScale, g_yres / g_GuiScale));
243 // In a few cases, GUI objects have to resize to fill the screen
244 // but maintain a constant aspect ratio.
245 // Adjust the size to be the max possible, centered in the original size:
246 if (m_AspectRatio)
248 if (m_CachedActualSize.GetWidth() > m_CachedActualSize.GetHeight() * m_AspectRatio)
250 float delta = m_CachedActualSize.GetWidth() - m_CachedActualSize.GetHeight() * m_AspectRatio;
251 m_CachedActualSize.left += delta/2.f;
252 m_CachedActualSize.right -= delta/2.f;
254 else
256 float delta = m_CachedActualSize.GetHeight() - m_CachedActualSize.GetWidth() / m_AspectRatio;
257 m_CachedActualSize.bottom -= delta/2.f;
258 m_CachedActualSize.top += delta/2.f;
263 void IGUIObject::LoadStyle(const CStr& StyleName)
265 if (!m_pGUI.HasStyle(StyleName))
266 debug_warn(L"IGUIObject::LoadStyle failed");
268 // The default style may specify settings for any GUI object.
269 // Other styles are reported if they specify a Setting that does not exist,
270 // so that the XML author is informed and can correct the style.
272 for (const std::pair<CStr, CStrW>& p : m_pGUI.GetStyle(StyleName).m_SettingsDefaults)
274 if (SettingExists(p.first))
275 SetSettingFromString(p.first, p.second, true);
276 else if (StyleName != "default")
277 LOGWARNING("GUI object has no setting \"%s\", but the style \"%s\" defines it", p.first, StyleName.c_str());
281 float IGUIObject::GetBufferedZ() const
283 if (m_Absolute)
284 return m_Z;
286 if (GetParent())
287 return GetParent()->GetBufferedZ() + m_Z;
289 // In philosophy, a parentless object shouldn't be able to have a relative sizing,
290 // but we'll accept it so that absolute can be used as default without a complaint.
291 // Also, you could consider those objects children to the screen resolution.
292 return m_Z;
295 void IGUIObject::RegisterScriptHandler(const CStr& Action, const CStr& Code, CGUI& pGUI)
297 JSContext* cx = pGUI.GetScriptInterface()->GetContext();
298 JSAutoRequest rq(cx);
299 JS::RootedValue globalVal(cx, pGUI.GetGlobalObject());
300 JS::RootedObject globalObj(cx, &globalVal.toObject());
302 const int paramCount = 1;
303 const char* paramNames[paramCount] = { "mouse" };
305 // Location to report errors from
306 CStr CodeName = GetName()+" "+Action;
308 // Generate a unique name
309 static int x = 0;
310 char buf[64];
311 sprintf_s(buf, ARRAY_SIZE(buf), "__eventhandler%d (%s)", x++, Action.c_str());
313 JS::CompileOptions options(cx);
314 options.setFileAndLine(CodeName.c_str(), 0);
315 options.setIsRunOnce(false);
317 JS::RootedFunction func(cx);
318 JS::AutoObjectVector emptyScopeChain(cx);
319 if (!JS::CompileFunction(cx, emptyScopeChain, options, buf, paramCount, paramNames, Code.c_str(), Code.length(), &func))
321 LOGERROR("RegisterScriptHandler: Failed to compile the script for %s", Action.c_str());
322 return;
325 JS::RootedObject funcObj(cx, JS_GetFunctionObject(func));
326 SetScriptHandler(Action, funcObj);
329 void IGUIObject::SetScriptHandler(const CStr& Action, JS::HandleObject Function)
331 if (m_ScriptHandlers.empty())
332 JS_AddExtraGCRootsTracer(m_pGUI.GetScriptInterface()->GetJSRuntime(), Trace, this);
334 m_ScriptHandlers[Action] = JS::Heap<JSObject*>(Function);
337 void IGUIObject::UnsetScriptHandler(const CStr& Action)
339 std::map<CStr, JS::Heap<JSObject*> >::iterator it = m_ScriptHandlers.find(Action);
341 if (it == m_ScriptHandlers.end())
342 return;
344 m_ScriptHandlers.erase(it);
346 if (m_ScriptHandlers.empty())
347 JS_RemoveExtraGCRootsTracer(m_pGUI.GetScriptInterface()->GetJSRuntime(), Trace, this);
350 InReaction IGUIObject::SendEvent(EGUIMessageType type, const CStr& EventName)
352 PROFILE2_EVENT("gui event");
353 PROFILE2_ATTR("type: %s", EventName.c_str());
354 PROFILE2_ATTR("object: %s", m_Name.c_str());
356 SGUIMessage msg(type);
357 HandleMessage(msg);
359 ScriptEvent(EventName);
361 return (msg.skipped ? IN_PASS : IN_HANDLED);
364 void IGUIObject::ScriptEvent(const CStr& Action)
366 std::map<CStr, JS::Heap<JSObject*> >::iterator it = m_ScriptHandlers.find(Action);
367 if (it == m_ScriptHandlers.end())
368 return;
370 JSContext* cx = m_pGUI.GetScriptInterface()->GetContext();
371 JSAutoRequest rq(cx);
373 // Set up the 'mouse' parameter
374 JS::RootedValue mouse(cx);
376 const CPos& mousePos = m_pGUI.GetMousePos();
378 ScriptInterface::CreateObject(
380 &mouse,
381 "x", mousePos.x,
382 "y", mousePos.y,
383 "buttons", m_pGUI.GetMouseButtons());
385 JS::AutoValueVector paramData(cx);
386 paramData.append(mouse);
387 JS::RootedObject obj(cx, GetJSObject());
388 JS::RootedValue handlerVal(cx, JS::ObjectValue(*it->second));
389 JS::RootedValue result(cx);
390 bool ok = JS_CallFunctionValue(cx, obj, handlerVal, paramData, &result);
391 if (!ok)
393 // We have no way to propagate the script exception, so just ignore it
394 // and hope the caller checks JS_IsExceptionPending
398 void IGUIObject::ScriptEvent(const CStr& Action, const JS::HandleValueArray& paramData)
400 std::map<CStr, JS::Heap<JSObject*> >::iterator it = m_ScriptHandlers.find(Action);
401 if (it == m_ScriptHandlers.end())
402 return;
404 JSContext* cx = m_pGUI.GetScriptInterface()->GetContext();
405 JSAutoRequest rq(cx);
406 JS::RootedObject obj(cx, GetJSObject());
407 JS::RootedValue handlerVal(cx, JS::ObjectValue(*it->second));
408 JS::RootedValue result(cx);
410 if (!JS_CallFunctionValue(cx, obj, handlerVal, paramData, &result))
411 JS_ReportError(cx, "Errors executing script action \"%s\"", Action.c_str());
414 void IGUIObject::CreateJSObject()
416 JSContext* cx = m_pGUI.GetScriptInterface()->GetContext();
417 JSAutoRequest rq(cx);
419 m_JSObject.init(cx, m_pGUI.GetScriptInterface()->CreateCustomObject("GUIObject"));
420 JS_SetPrivate(m_JSObject.get(), this);
422 RegisterScriptFunctions();
425 JSObject* IGUIObject::GetJSObject()
427 // Cache the object when somebody first asks for it, because otherwise
428 // we end up doing far too much object allocation.
429 if (!m_JSObject.initialized())
430 CreateJSObject();
432 return m_JSObject.get();
435 bool IGUIObject::IsEnabled() const
437 return m_Enabled;
440 bool IGUIObject::IsHidden() const
442 return m_Hidden;
445 bool IGUIObject::IsHiddenOrGhost() const
447 return m_Hidden || m_Ghost;
450 void IGUIObject::PlaySound(const CStrW& soundPath) const
452 if (g_SoundManager && !soundPath.empty())
453 g_SoundManager->PlayAsUI(soundPath.c_str(), false);
456 CStr IGUIObject::GetPresentableName() const
458 // __internal(), must be at least 13 letters to be able to be
459 // an internal name
460 if (m_Name.length() <= 12)
461 return m_Name;
463 if (m_Name.substr(0, 10) == "__internal")
464 return CStr("[unnamed object]");
465 else
466 return m_Name;
469 void IGUIObject::SetFocus()
471 m_pGUI.SetFocusedObject(this);
474 bool IGUIObject::IsFocused() const
476 return m_pGUI.GetFocusedObject() == this;
479 bool IGUIObject::IsBaseObject() const
481 return this == &m_pGUI.GetBaseObject();
484 bool IGUIObject::IsRootObject() const
486 return m_pParent == &m_pGUI.GetBaseObject();
489 void IGUIObject::TraceMember(JSTracer* trc)
491 // Please ensure to adapt the Tracer enabling and disabling in accordance with the GC things traced!
493 for (std::pair<const CStr, JS::Heap<JSObject*>>& handler : m_ScriptHandlers)
494 JS_CallObjectTracer(trc, &handler.second, "IGUIObject::m_ScriptHandlers");
497 // Instantiate templated functions:
498 // These functions avoid copies by working with a reference and move semantics.
499 #define TYPE(T) \
500 template void IGUIObject::RegisterSetting<T>(const CStr& Name, T& Value); \
501 template T& IGUIObject::GetSetting<T>(const CStr& Setting); \
502 template const T& IGUIObject::GetSetting<T>(const CStr& Setting) const; \
503 template void IGUIObject::SetSetting<T>(const CStr& Setting, T& Value, const bool SendMessage); \
505 #include "gui/GUISettingTypes.h"
506 #undef TYPE
508 // Copying functions - discouraged except for primitives.
509 #define TYPE(T) \
510 template void IGUIObject::SetSetting<T>(const CStr& Setting, const T& Value, const bool SendMessage); \
512 #define GUITYPE_IGNORE_NONCOPYABLE
513 #include "gui/GUISettingTypes.h"
514 #undef GUITYPE_IGNORE_NONCOPYABLE
515 #undef TYPE