1 /* Copyright (C) 2016 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"
22 #include "gui/scripting/JSInterface_GUITypes.h"
23 #include "gui/scripting/JSInterface_IGUIObject.h"
25 #include "ps/GameSetup/Config.h"
26 #include "ps/CLogger.h"
27 #include "ps/Profile.h"
28 #include "scriptinterface/ScriptInterface.h"
31 IGUIObject::IGUIObject()
32 : m_pGUI(NULL
), m_pParent(NULL
), m_MouseHovering(false), m_LastClickTime()
34 AddSetting(GUIST_bool
, "enabled");
35 AddSetting(GUIST_bool
, "hidden");
36 AddSetting(GUIST_CClientArea
, "size");
37 AddSetting(GUIST_CStr
, "style");
38 AddSetting(GUIST_CStr
, "hotkey");
39 AddSetting(GUIST_float
, "z");
40 AddSetting(GUIST_bool
, "absolute");
41 AddSetting(GUIST_bool
, "ghost");
42 AddSetting(GUIST_float
, "aspectratio");
43 AddSetting(GUIST_CStrW
, "tooltip");
44 AddSetting(GUIST_CStr
, "tooltip_style");
46 // Setup important defaults
47 GUI
<bool>::SetSetting(this, "hidden", false);
48 GUI
<bool>::SetSetting(this, "ghost", false);
49 GUI
<bool>::SetSetting(this, "enabled", true);
50 GUI
<bool>::SetSetting(this, "absolute", true);
53 IGUIObject::~IGUIObject()
55 for (const std::pair
<CStr
, SGUISetting
>& p
: m_Settings
)
56 switch (p
.second
.m_Type
)
58 // delete() needs to know the type of the variable - never delete a void*
59 #define TYPE(t) case GUIST_##t: delete (t*)p.second.m_pSetting; break;
63 debug_warn(L
"Invalid setting type");
67 JS_RemoveExtraGCRootsTracer(m_pGUI
->GetScriptInterface()->GetJSRuntime(), Trace
, this);
70 //-------------------------------------------------------------------
72 //-------------------------------------------------------------------
73 void IGUIObject::SetGUI(CGUI
* const& pGUI
)
76 JS_AddExtraGCRootsTracer(pGUI
->GetScriptInterface()->GetJSRuntime(), Trace
, this);
80 void IGUIObject::AddChild(IGUIObject
* pChild
)
84 pChild
->SetParent(this);
86 m_Children
.push_back(pChild
);
88 // If this (not the child) object is already attached
89 // to a CGUI, it pGUI pointer will be non-null.
90 // This will mean we'll have to check if we're using
91 // names already used.
96 // Atomic function, if it fails it won't
97 // have changed anything
99 pChild
->GetGUI()->UpdateObjects();
103 // If anything went wrong, reverse what we did and throw
104 // an exception telling it never added a child
105 m_Children
.erase(m_Children
.end()-1);
113 void IGUIObject::AddToPointersMap(map_pObjects
& ObjectMap
)
115 // Just don't do anything about the top node
116 if (m_pParent
== NULL
)
119 // Now actually add this one
120 // notice we won't add it if it's doesn't have any parent
121 // (i.e. being the base object)
124 throw PSERROR_GUI_ObjectNeedsName();
126 if (ObjectMap
.count(m_Name
) > 0)
128 throw PSERROR_GUI_NameAmbiguity(m_Name
.c_str());
132 ObjectMap
[m_Name
] = this;
136 void IGUIObject::Destroy()
138 // Is there anything besides the children to destroy?
141 void IGUIObject::AddSetting(const EGUISettingType
& Type
, const CStr
& Name
)
143 // Is name already taken?
144 if (m_Settings
.count(Name
) >= 1)
147 // Construct, and set type
148 m_Settings
[Name
].m_Type
= Type
;
154 m_Settings[Name].m_pSetting = new type(); \
157 // Construct the setting.
158 #include "GUItypes.h"
163 debug_warn(L
"IGUIObject::AddSetting failed, type not recognized!");
169 bool IGUIObject::MouseOver()
172 throw PSERROR_GUI_OperationNeedsGUIObject();
174 return m_CachedActualSize
.PointInside(GetMousePos());
177 bool IGUIObject::MouseOverIcon()
182 CPos
IGUIObject::GetMousePos() const
185 return GetGUI()->m_MousePos
;
190 void IGUIObject::UpdateMouseOver(IGUIObject
* const& pMouseOver
)
192 if (pMouseOver
== this)
194 if (!m_MouseHovering
)
195 SendEvent(GUIM_MOUSE_ENTER
, "mouseenter");
197 m_MouseHovering
= true;
199 SendEvent(GUIM_MOUSE_OVER
, "mousemove");
205 m_MouseHovering
= false;
206 SendEvent(GUIM_MOUSE_LEAVE
, "mouseleave");
211 bool IGUIObject::SettingExists(const CStr
& Setting
) const
213 // Because GetOffsets will direct dynamically defined
214 // classes with polymorphism to respective ms_SettingsInfo
215 // we need to make no further updates on this function
216 // in derived classes.
217 //return (GetSettingsInfo().count(Setting) >= 1);
218 return (m_Settings
.count(Setting
) >= 1);
221 PSRETURN
IGUIObject::SetSetting(const CStr
& Setting
, const CStrW
& Value
, const bool& SkipMessage
)
223 if (!SettingExists(Setting
))
224 return PSRETURN_GUI_InvalidSetting
;
226 SGUISetting set
= m_Settings
[Setting
];
229 else if (set.m_Type == GUIST_##type) \
232 if (!GUI<type>::ParseString(Value, _Value)) \
233 return PSRETURN_GUI_UnableToParse; \
234 GUI<type>::SetSetting(this, Setting, _Value, SkipMessage); \
239 #include "GUItypes.h"
243 // Why does it always fail?
245 return LogInvalidSettings(Setting
);
252 PSRETURN
IGUIObject::GetSettingType(const CStr
& Setting
, EGUISettingType
& Type
) const
254 if (!SettingExists(Setting
))
255 return LogInvalidSettings(Setting
);
257 if (m_Settings
.find(Setting
) == m_Settings
.end())
258 return LogInvalidSettings(Setting
);
260 Type
= m_Settings
.find(Setting
)->second
.m_Type
;
266 void IGUIObject::ChooseMouseOverAndClosest(IGUIObject
*& pObject
)
271 // Check if we've got competition at all
279 if (GetBufferedZ() >= pObject
->GetBufferedZ())
286 IGUIObject
* IGUIObject::GetParent() const
288 // Important, we're not using GetParent() for these
289 // checks, that could screw it up
292 if (m_pParent
->m_pParent
== NULL
)
299 void IGUIObject::UpdateCachedSize()
302 GUI
<bool>::GetSetting(this, "absolute", absolute
);
304 float aspectratio
= 0.f
;
305 GUI
<float>::GetSetting(this, "aspectratio", aspectratio
);
308 GUI
<CClientArea
>::GetSetting(this, "size", ca
);
310 // If absolute="false" and the object has got a parent,
311 // use its cached size instead of the screen. Notice
312 // it must have just been cached for it to work.
313 if (absolute
== false && m_pParent
&& !IsRootObject())
314 m_CachedActualSize
= ca
.GetClientArea(m_pParent
->m_CachedActualSize
);
316 m_CachedActualSize
= ca
.GetClientArea(CRect(0.f
, 0.f
, g_xres
/ g_GuiScale
, g_yres
/ g_GuiScale
));
318 // In a few cases, GUI objects have to resize to fill the screen
319 // but maintain a constant aspect ratio.
320 // Adjust the size to be the max possible, centered in the original size:
323 if (m_CachedActualSize
.GetWidth() > m_CachedActualSize
.GetHeight()*aspectratio
)
325 float delta
= m_CachedActualSize
.GetWidth() - m_CachedActualSize
.GetHeight()*aspectratio
;
326 m_CachedActualSize
.left
+= delta
/2.f
;
327 m_CachedActualSize
.right
-= delta
/2.f
;
331 float delta
= m_CachedActualSize
.GetHeight() - m_CachedActualSize
.GetWidth()/aspectratio
;
332 m_CachedActualSize
.bottom
-= delta
/2.f
;
333 m_CachedActualSize
.top
+= delta
/2.f
;
338 void IGUIObject::LoadStyle(CGUI
& GUIinstance
, const CStr
& StyleName
)
341 if (GUIinstance
.m_Styles
.count(StyleName
) == 1)
343 LoadStyle(GUIinstance
.m_Styles
[StyleName
]);
347 debug_warn(L
"IGUIObject::LoadStyle failed");
351 void IGUIObject::LoadStyle(const SGUIStyle
& Style
)
353 // Iterate settings, it won't be able to set them all probably, but that doesn't matter
354 for (const std::pair
<CStr
, CStrW
>& p
: Style
.m_SettingsDefaults
)
356 // Try set setting in object
357 SetSetting(p
.first
, p
.second
);
359 // It doesn't matter if it fail, it's not suppose to be able to set every setting.
360 // since it's generic.
362 // The beauty with styles is that it can contain more settings
363 // than exists for the objects using it. So if the SetSetting
364 // fails, don't care.
368 float IGUIObject::GetBufferedZ() const
371 GUI
<bool>::GetSetting(this, "absolute", absolute
);
374 GUI
<float>::GetSetting(this, "z", Z
);
381 return GetParent()->GetBufferedZ() + Z
;
384 // In philosophy, a parentless object shouldn't be able to have a relative sizing,
385 // but we'll accept it so that absolute can be used as default without a complaint.
386 // Also, you could consider those objects children to the screen resolution.
392 void IGUIObject::RegisterScriptHandler(const CStr
& Action
, const CStr
& Code
, CGUI
* pGUI
)
395 throw PSERROR_GUI_OperationNeedsGUIObject();
397 JSContext
* cx
= pGUI
->GetScriptInterface()->GetContext();
398 JSAutoRequest
rq(cx
);
399 JS::RootedValue
globalVal(cx
, pGUI
->GetGlobalObject());
400 JS::RootedObject
globalObj(cx
, &globalVal
.toObject());
402 const int paramCount
= 1;
403 const char* paramNames
[paramCount
] = { "mouse" };
405 // Location to report errors from
406 CStr CodeName
= GetName()+" "+Action
;
408 // Generate a unique name
411 sprintf_s(buf
, ARRAY_SIZE(buf
), "__eventhandler%d (%s)", x
++, Action
.c_str());
413 JS::CompileOptions
options(cx
);
414 options
.setFileAndLine(CodeName
.c_str(), 0);
415 options
.setCompileAndGo(true);
417 JS::RootedFunction
func(cx
);
418 JS::AutoObjectVector
emptyScopeChain(cx
);
419 if (!JS::CompileFunction(cx
, emptyScopeChain
, options
, buf
, paramCount
, paramNames
, Code
.c_str(), Code
.length(), &func
))
421 LOGERROR("RegisterScriptHandler: Failed to compile the script for %s", Action
.c_str());
425 JS::RootedObject
funcObj(cx
, JS_GetFunctionObject(func
));
426 SetScriptHandler(Action
, funcObj
);
429 void IGUIObject::SetScriptHandler(const CStr
& Action
, JS::HandleObject Function
)
431 // m_ScriptHandlers is only rooted after SetGUI() has been called (which sets up the GC trace callbacks),
432 // so we can't safely store objects in it if the GUI hasn't been set yet.
433 ENSURE(m_pGUI
&& "A GUI must be associated with the GUIObject before adding ScriptHandlers!");
434 m_ScriptHandlers
[Action
] = JS::Heap
<JSObject
*>(Function
);
437 InReaction
IGUIObject::SendEvent(EGUIMessageType type
, const CStr
& EventName
)
439 PROFILE2_EVENT("gui event");
440 PROFILE2_ATTR("type: %s", EventName
.c_str());
441 PROFILE2_ATTR("object: %s", m_Name
.c_str());
443 SGUIMessage
msg(type
);
446 ScriptEvent(EventName
);
448 return (msg
.skipped
? IN_PASS
: IN_HANDLED
);
451 void IGUIObject::ScriptEvent(const CStr
& Action
)
453 std::map
<CStr
, JS::Heap
<JSObject
*>>::iterator it
= m_ScriptHandlers
.find(Action
);
454 if (it
== m_ScriptHandlers
.end())
457 JSContext
* cx
= m_pGUI
->GetScriptInterface()->GetContext();
458 JSAutoRequest
rq(cx
);
460 // Set up the 'mouse' parameter
461 JS::RootedValue
mouse(cx
);
462 m_pGUI
->GetScriptInterface()->Eval("({})", &mouse
);
463 m_pGUI
->GetScriptInterface()->SetProperty(mouse
, "x", m_pGUI
->m_MousePos
.x
, false);
464 m_pGUI
->GetScriptInterface()->SetProperty(mouse
, "y", m_pGUI
->m_MousePos
.y
, false);
465 m_pGUI
->GetScriptInterface()->SetProperty(mouse
, "buttons", m_pGUI
->m_MouseButtons
, false);
467 JS::AutoValueVector
paramData(cx
);
468 paramData
.append(mouse
);
469 JS::RootedObject
obj(cx
, GetJSObject());
470 JS::RootedValue
handlerVal(cx
, JS::ObjectValue(*it
->second
));
471 JS::RootedValue
result(cx
);
472 bool ok
= JS_CallFunctionValue(cx
, obj
, handlerVal
, paramData
, &result
);
475 // We have no way to propagate the script exception, so just ignore it
476 // and hope the caller checks JS_IsExceptionPending
480 void IGUIObject::ScriptEvent(const CStr
& Action
, JS::HandleValue Argument
)
482 std::map
<CStr
, JS::Heap
<JSObject
*>>::iterator it
= m_ScriptHandlers
.find(Action
);
483 if (it
== m_ScriptHandlers
.end())
486 JSContext
* cx
= m_pGUI
->GetScriptInterface()->GetContext();
487 JSAutoRequest
rq(cx
);
488 JS::AutoValueVector
paramData(cx
);
489 paramData
.append(Argument
.get());
490 JS::RootedObject
obj(cx
, GetJSObject());
491 JS::RootedValue
handlerVal(cx
, JS::ObjectValue(*it
->second
));
492 JS::RootedValue
result(cx
);
493 bool ok
= JS_CallFunctionValue(cx
, obj
, handlerVal
, paramData
, &result
);
496 JS_ReportError(cx
, "Errors executing script action \"%s\"", Action
.c_str());
500 JSObject
* IGUIObject::GetJSObject()
502 JSContext
* cx
= m_pGUI
->GetScriptInterface()->GetContext();
503 JSAutoRequest
rq(cx
);
504 // Cache the object when somebody first asks for it, because otherwise
505 // we end up doing far too much object allocation. TODO: Would be nice to
506 // not have these objects hang around forever using up memory, though.
507 if (!m_JSObject
.initialized())
509 m_JSObject
.init(cx
, m_pGUI
->GetScriptInterface()->CreateCustomObject("GUIObject"));
510 JS_SetPrivate(m_JSObject
.get(), this);
512 return m_JSObject
.get();
515 CStr
IGUIObject::GetPresentableName() const
517 // __internal(), must be at least 13 letters to be able to be
519 if (m_Name
.length() <= 12)
522 if (m_Name
.substr(0, 10) == "__internal")
523 return CStr("[unnamed object]");
528 void IGUIObject::SetFocus()
530 GetGUI()->m_FocusedObject
= this;
533 bool IGUIObject::IsFocused() const
535 return GetGUI()->m_FocusedObject
== this;
538 bool IGUIObject::IsRootObject() const
540 return GetGUI() != 0 && m_pParent
== GetGUI()->m_BaseObject
;
543 void IGUIObject::TraceMember(JSTracer
* trc
)
545 for (std::pair
<const CStr
, JS::Heap
<JSObject
*>>& handler
: m_ScriptHandlers
)
546 JS_CallObjectTracer(trc
, &handler
.second
, "IGUIObject::m_ScriptHandlers");
549 PSRETURN
IGUIObject::LogInvalidSettings(const CStr8
& Setting
) const
551 LOGWARNING("IGUIObject: setting %s was not found on an object", Setting
.c_str());
552 return PSRETURN_GUI_InvalidSetting
;