1 /* Copyright (C) 2017 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 "JSInterface_IGUIObject.h"
21 #include "JSInterface_GUITypes.h"
23 #include "gui/IGUIObject.h"
25 #include "gui/IGUIScrollBar.h"
26 #include "gui/CList.h"
27 #include "gui/GUIManager.h"
29 #include "ps/CLogger.h"
31 #include "scriptinterface/ScriptInterface.h"
32 #include "scriptinterface/ScriptExtraHeaders.h"
34 JSClass
JSI_IGUIObject::JSI_class
= {
35 "GUIObject", JSCLASS_HAS_PRIVATE
,
37 JSI_IGUIObject::getProperty
, JSI_IGUIObject::setProperty
,
38 nullptr, nullptr, nullptr, nullptr,
39 nullptr, nullptr, JSI_IGUIObject::construct
, nullptr
42 JSPropertySpec
JSI_IGUIObject::JSI_props
[] =
47 JSFunctionSpec
JSI_IGUIObject::JSI_methods
[] =
49 JS_FS("toString", JSI_IGUIObject::toString
, 0, 0),
50 JS_FS("focus", JSI_IGUIObject::focus
, 0, 0),
51 JS_FS("blur", JSI_IGUIObject::blur
, 0, 0),
52 JS_FS("getComputedSize", JSI_IGUIObject::getComputedSize
, 0, 0),
56 bool JSI_IGUIObject::getProperty(JSContext
* cx
, JS::HandleObject obj
, JS::HandleId id
, JS::MutableHandleValue vp
)
59 ScriptInterface
* pScriptInterface
= ScriptInterface::GetScriptInterfaceAndCBData(cx
)->pScriptInterface
;
61 IGUIObject
* e
= (IGUIObject
*)JS_GetInstancePrivate(cx
, obj
, &JSI_IGUIObject::JSI_class
, NULL
);
65 JS::RootedValue
idval(cx
);
66 if (!JS_IdToValue(cx
, id
, &idval
))
70 if (!ScriptInterface::FromJSVal(cx
, idval
, propName
))
73 // Skip some things which are known to be functions rather than properties.
74 // ("constructor" *must* be here, else it'll try to GetSettingType before
75 // the private IGUIObject* has been set (and thus crash). The others are
76 // partly for efficiency, and also to allow correct reporting of attempts to
77 // access nonexistent properties.)
78 if (propName
== "constructor" ||
79 propName
== "prototype" ||
80 propName
== "toString" ||
81 propName
== "toJSON" ||
82 propName
== "focus" ||
84 propName
== "getComputedSize"
88 // Use onWhatever to access event handlers
89 if (propName
.substr(0, 2) == "on")
91 CStr
eventName(CStr(propName
.substr(2)).LowerCase());
92 std::map
<CStr
, JS::Heap
<JSObject
*>>::iterator it
= e
->m_ScriptHandlers
.find(eventName
);
93 if (it
== e
->m_ScriptHandlers
.end())
96 vp
.setObject(*it
->second
.get());
100 if (propName
== "parent")
102 IGUIObject
* parent
= e
->GetParent();
105 vp
.set(JS::ObjectValue(*parent
->GetJSObject()));
107 vp
.set(JS::NullValue());
111 else if (propName
== "children")
113 JS::RootedObject
obj(cx
, JS_NewArrayObject(cx
, JS::HandleValueArray::empty()));
116 for (size_t i
= 0; i
< e
->m_Children
.size(); ++i
)
118 JS::RootedValue
val(cx
);
119 ScriptInterface::ToJSVal(cx
, &val
, e
->m_Children
[i
]);
120 JS_SetElement(cx
, obj
, i
, val
);
125 else if (propName
== "name")
127 vp
.set(JS::StringValue(JS_NewStringCopyZ(cx
, e
->GetName().c_str())));
132 // Retrieve the setting's type (and make sure it actually exists)
133 EGUISettingType Type
;
134 if (e
->GetSettingType(propName
, Type
) != PSRETURN_OK
)
136 JS_ReportError(cx
, "Invalid GUIObject property '%s'", propName
.c_str());
140 // (All the cases are in {...} to avoid scoping problems)
146 GUI
<bool>::GetSetting(e
, propName
, value
);
147 vp
.set(JS::BooleanValue(value
));
154 GUI
<int>::GetSetting(e
, propName
, value
);
155 vp
.set(JS::Int32Value(value
));
162 GUI
<float>::GetSetting(e
, propName
, value
);
163 // Create a garbage-collectable double
164 vp
.set(JS::NumberValue(value
));
171 GUI
<CColor
>::GetSetting(e
, propName
, color
);
172 JS::RootedObject
obj(cx
, pScriptInterface
->CreateCustomObject("GUIColor"));
174 JS::RootedValue
c(cx
);
175 // Attempt to minimise ugliness through macrosity
177 c = JS::NumberValue(color.x); \
180 JS_SetProperty(cx, obj, #x, c)
190 case GUIST_CClientArea
:
193 GUI
<CClientArea
>::GetSetting(e
, propName
, area
);
195 JS::RootedObject
obj(cx
, pScriptInterface
->CreateCustomObject("GUISize"));
199 #define P(x, y, z) pScriptInterface->SetProperty(vp, #z, area.x.y, false, true)
200 P(pixel
, left
, left
);
202 P(pixel
, right
, right
);
203 P(pixel
, bottom
, bottom
);
204 P(percent
, left
, rleft
);
205 P(percent
, top
, rtop
);
206 P(percent
, right
, rright
);
207 P(percent
, bottom
, rbottom
);
210 catch (PSERROR_Scripting_ConversionFailed
&)
212 debug_warn(L
"Error creating size object!");
219 case GUIST_CGUIString
:
222 GUI
<CGUIString
>::GetSetting(e
, propName
, value
);
223 ScriptInterface::ToJSVal(cx
, vp
, value
.GetOriginalString());
230 GUI
<CStr
>::GetSetting(e
, propName
, value
);
231 ScriptInterface::ToJSVal(cx
, vp
, value
);
238 GUI
<CStrW
>::GetSetting(e
, propName
, value
);
239 ScriptInterface::ToJSVal(cx
, vp
, value
);
243 case GUIST_CGUISpriteInstance
:
245 CGUISpriteInstance
*value
;
246 GUI
<CGUISpriteInstance
>::GetSettingPointer(e
, propName
, value
);
247 ScriptInterface::ToJSVal(cx
, vp
, value
->GetName());
254 GUI
<EAlign
>::GetSetting(e
, propName
, value
);
258 case EAlign_Left
: word
= "left"; break;
259 case EAlign_Right
: word
= "right"; break;
260 case EAlign_Center
: word
= "center"; break;
261 default: debug_warn(L
"Invalid EAlign!"); word
= "error"; break;
263 ScriptInterface::ToJSVal(cx
, vp
, word
);
270 GUI
<EVAlign
>::GetSetting(e
, propName
, value
);
274 case EVAlign_Top
: word
= "top"; break;
275 case EVAlign_Bottom
: word
= "bottom"; break;
276 case EVAlign_Center
: word
= "center"; break;
277 default: debug_warn(L
"Invalid EVAlign!"); word
= "error"; break;
279 ScriptInterface::ToJSVal(cx
, vp
, word
);
286 GUI
<CGUIList
>::GetSetting(e
, propName
, value
);
287 ScriptInterface::ToJSVal(cx
, vp
, value
.m_Items
);
291 case GUIST_CGUISeries
:
294 GUI
<CGUISeries
>::GetSetting(e
, propName
, value
);
295 ScriptInterface::ToJSVal(cx
, vp
, value
.m_Series
);
300 JS_ReportError(cx
, "Setting '%s' uses an unimplemented type", propName
.c_str());
301 DEBUG_WARN_ERR(ERR::LOGIC
);
309 bool JSI_IGUIObject::setProperty(JSContext
* cx
, JS::HandleObject obj
, JS::HandleId id
, bool UNUSED(strict
), JS::MutableHandleValue vp
)
311 IGUIObject
* e
= (IGUIObject
*)JS_GetInstancePrivate(cx
, obj
, &JSI_IGUIObject::JSI_class
, NULL
);
315 JSAutoRequest
rq(cx
);
316 JS::RootedValue
idval(cx
);
317 if (!JS_IdToValue(cx
, id
, &idval
))
320 std::string propName
;
321 if (!ScriptInterface::FromJSVal(cx
, idval
, propName
))
324 if (propName
== "name")
327 if (!ScriptInterface::FromJSVal(cx
, vp
, value
))
333 JS::RootedObject
vpObj(cx
);
335 vpObj
= &vp
.toObject();
337 // Use onWhatever to set event handlers
338 if (propName
.substr(0, 2) == "on")
340 if (vp
.isPrimitive() || vp
.isNull() || !JS_ObjectIsFunction(cx
, &vp
.toObject()))
342 JS_ReportError(cx
, "on- event-handlers must be functions");
346 CStr
eventName(CStr(propName
.substr(2)).LowerCase());
347 e
->SetScriptHandler(eventName
, vpObj
);
352 // Retrieve the setting's type (and make sure it actually exists)
353 EGUISettingType Type
;
354 if (e
->GetSettingType(propName
, Type
) != PSRETURN_OK
)
356 JS_ReportError(cx
, "Invalid setting '%s'", propName
.c_str());
365 if (!ScriptInterface::FromJSVal(cx
, vp
, value
))
368 GUI
<CStr
>::SetSetting(e
, propName
, value
);
375 if (!ScriptInterface::FromJSVal(cx
, vp
, value
))
378 GUI
<CStrW
>::SetSetting(e
, propName
, value
);
382 case GUIST_CGUISpriteInstance
:
385 if (!ScriptInterface::FromJSVal(cx
, vp
, value
))
388 GUI
<CGUISpriteInstance
>::SetSetting(e
, propName
, CGUISpriteInstance(value
));
392 case GUIST_CGUIString
:
395 if (!ScriptInterface::FromJSVal(cx
, vp
, value
))
400 GUI
<CGUIString
>::SetSetting(e
, propName
, str
);
407 if (!ScriptInterface::FromJSVal(cx
, vp
, value
))
411 if (value
== "left") a
= EAlign_Left
;
412 else if (value
== "right") a
= EAlign_Right
;
413 else if (value
== "center" || value
== "centre") a
= EAlign_Center
;
416 JS_ReportError(cx
, "Invalid alignment (should be 'left', 'right' or 'center')");
419 GUI
<EAlign
>::SetSetting(e
, propName
, a
);
426 if (!ScriptInterface::FromJSVal(cx
, vp
, value
))
430 if (value
== "top") a
= EVAlign_Top
;
431 else if (value
== "bottom") a
= EVAlign_Bottom
;
432 else if (value
== "center" || value
== "centre") a
= EVAlign_Center
;
435 JS_ReportError(cx
, "Invalid alignment (should be 'top', 'bottom' or 'center')");
438 GUI
<EVAlign
>::SetSetting(e
, propName
, a
);
445 if (ScriptInterface::FromJSVal(cx
, vp
, value
))
446 GUI
<int>::SetSetting(e
, propName
, value
);
449 JS_ReportError(cx
, "Cannot convert value to int");
458 if (JS::ToNumber(cx
, vp
, &value
) == true)
459 GUI
<float>::SetSetting(e
, propName
, (float)value
);
462 JS_ReportError(cx
, "Cannot convert value to float");
470 bool value
= JS::ToBoolean(vp
);
471 GUI
<bool>::SetSetting(e
, propName
, value
);
475 case GUIST_CClientArea
:
480 if (!ScriptInterface::FromJSVal(cx
, vp
, value
))
483 if (e
->SetSetting(propName
, value
) != PSRETURN_OK
)
485 JS_ReportError(cx
, "Invalid value for setting '%s'", propName
.c_str());
489 else if (vp
.isObject() && JS_InstanceOf(cx
, vpObj
, &JSI_GUISize::JSI_class
, NULL
))
492 GUI
<CClientArea
>::GetSetting(e
, propName
, area
);
494 ScriptInterface
* pScriptInterface
= ScriptInterface::GetScriptInterfaceAndCBData(cx
)->pScriptInterface
;
495 #define P(x, y, z) pScriptInterface->GetProperty(vp, #z, area.x.y)
496 P(pixel
, left
, left
);
498 P(pixel
, right
, right
);
499 P(pixel
, bottom
, bottom
);
500 P(percent
, left
, rleft
);
501 P(percent
, top
, rtop
);
502 P(percent
, right
, rright
);
503 P(percent
, bottom
, rbottom
);
506 GUI
<CClientArea
>::SetSetting(e
, propName
, area
);
510 JS_ReportError(cx
, "Size only accepts strings or GUISize objects");
521 if (!ScriptInterface::FromJSVal(cx
, vp
, value
))
524 if (e
->SetSetting(propName
, value
) != PSRETURN_OK
)
526 JS_ReportError(cx
, "Invalid value for setting '%s'", propName
.c_str());
530 else if (vp
.isObject() && JS_InstanceOf(cx
, vpObj
, &JSI_GUIColor::JSI_class
, NULL
))
533 JS::RootedValue
t(cx
);
536 JS_GetProperty(cx, vpObj, #x, &t); \
545 GUI
<CColor
>::SetSetting(e
, propName
, color
);
549 JS_ReportError(cx
, "Color only accepts strings or GUIColor objects");
558 if (ScriptInterface::FromJSVal(cx
, vp
, list
.m_Items
))
559 GUI
<CGUIList
>::SetSetting(e
, propName
, list
);
562 JS_ReportError(cx
, "Failed to get list '%s'", propName
.c_str());
568 case GUIST_CGUISeries
:
571 if (ScriptInterface::FromJSVal(cx
, vp
, series
.m_Series
))
572 GUI
<CGUISeries
>::SetSetting(e
, propName
, series
);
575 JS_ReportError(cx
, "Invalid value for chart series '%s'", propName
.c_str());
582 JS_ReportError(cx
, "Setting '%s' uses an unimplemented type", propName
.c_str());
586 return !JS_IsExceptionPending(cx
);
590 bool JSI_IGUIObject::construct(JSContext
* cx
, uint argc
, JS::Value
* vp
)
592 JSAutoRequest
rq(cx
);
593 JS::CallArgs args
= JS::CallArgsFromVp(argc
, vp
);
594 ScriptInterface
* pScriptInterface
= ScriptInterface::GetScriptInterfaceAndCBData(cx
)->pScriptInterface
;
596 if (args
.length() == 0)
598 JS_ReportError(cx
, "GUIObject has no default constructor");
602 JS::RootedObject
obj(cx
, pScriptInterface
->CreateCustomObject("GUIObject"));
604 // Store the IGUIObject in the JS object's 'private' area
605 IGUIObject
* guiObject
= (IGUIObject
*)args
[0].get().toPrivate();
606 JS_SetPrivate(obj
, guiObject
);
608 args
.rval().setObject(*obj
);
612 void JSI_IGUIObject::init(ScriptInterface
& scriptInterface
)
614 scriptInterface
.DefineCustomObjectType(&JSI_class
, construct
, 1, JSI_props
, JSI_methods
, NULL
, NULL
);
617 bool JSI_IGUIObject::toString(JSContext
* cx
, uint
UNUSED(argc
), JS::Value
* vp
)
619 JSAutoRequest
rq(cx
);
620 JS::CallReceiver rec
= JS::CallReceiverFromVp(vp
);
622 JS::RootedObject
thisObj(cx
, JS_THIS_OBJECT(cx
, vp
));
624 IGUIObject
* e
= (IGUIObject
*)JS_GetInstancePrivate(cx
, thisObj
, &JSI_IGUIObject::JSI_class
, NULL
);
629 snprintf(buffer
, 256, "[GUIObject: %s]", e
->GetName().c_str());
631 rec
.rval().setString(JS_NewStringCopyZ(cx
, buffer
));
635 bool JSI_IGUIObject::focus(JSContext
* cx
, uint
UNUSED(argc
), JS::Value
* vp
)
637 JSAutoRequest
rq(cx
);
638 JS::CallReceiver rec
= JS::CallReceiverFromVp(vp
);
640 JS::RootedObject
thisObj(cx
, JS_THIS_OBJECT(cx
, vp
));
642 IGUIObject
* e
= (IGUIObject
*)JS_GetInstancePrivate(cx
, thisObj
, &JSI_IGUIObject::JSI_class
, NULL
);
646 e
->GetGUI()->SetFocusedObject(e
);
648 rec
.rval().setUndefined();
652 bool JSI_IGUIObject::blur(JSContext
* cx
, uint
UNUSED(argc
), JS::Value
* vp
)
654 JSAutoRequest
rq(cx
);
655 JS::CallReceiver rec
= JS::CallReceiverFromVp(vp
);
657 JS::RootedObject
thisObj(cx
, JS_THIS_OBJECT(cx
, vp
));
659 IGUIObject
* e
= (IGUIObject
*)JS_GetInstancePrivate(cx
, thisObj
, &JSI_IGUIObject::JSI_class
, NULL
);
663 e
->GetGUI()->SetFocusedObject(NULL
);
665 rec
.rval().setUndefined();
669 bool JSI_IGUIObject::getComputedSize(JSContext
* cx
, uint
UNUSED(argc
), JS::Value
* vp
)
671 JSAutoRequest
rq(cx
);
672 JS::CallReceiver rec
= JS::CallReceiverFromVp(vp
);
674 JS::RootedObject
thisObj(cx
, JS_THIS_OBJECT(cx
, vp
));
676 IGUIObject
* e
= (IGUIObject
*)JS_GetInstancePrivate(cx
, thisObj
, &JSI_IGUIObject::JSI_class
, NULL
);
680 e
->UpdateCachedSize();
681 CRect size
= e
->m_CachedActualSize
;
683 JS::RootedValue
objVal(cx
, JS::ObjectValue(*JS_NewPlainObject(cx
)));
686 ScriptInterface
* pScriptInterface
= ScriptInterface::GetScriptInterfaceAndCBData(cx
)->pScriptInterface
;
687 pScriptInterface
->SetProperty(objVal
, "left", size
.left
, false, true);
688 pScriptInterface
->SetProperty(objVal
, "right", size
.right
, false, true);
689 pScriptInterface
->SetProperty(objVal
, "top", size
.top
, false, true);
690 pScriptInterface
->SetProperty(objVal
, "bottom", size
.bottom
, false, true);
692 catch (PSERROR_Scripting_ConversionFailed
&)
694 debug_warn(L
"Error creating size object!");
698 rec
.rval().set(objVal
);