Don't crash while in the lobby when receiving an error IQ stanza without an error...
[0ad.git] / source / gui / scripting / JSInterface_IGUIObject.cpp
bloba6d31590b8249d0238b2937e7ab96e6d38965cab
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"
24 #include "gui/CGUI.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,
36 nullptr, nullptr,
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[] =
44 { 0 }
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),
53 JS_FS_END
56 bool JSI_IGUIObject::getProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp)
58 JSAutoRequest rq(cx);
59 ScriptInterface* pScriptInterface = ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface;
61 IGUIObject* e = (IGUIObject*)JS_GetInstancePrivate(cx, obj, &JSI_IGUIObject::JSI_class, NULL);
62 if (!e)
63 return false;
65 JS::RootedValue idval(cx);
66 if (!JS_IdToValue(cx, id, &idval))
67 return false;
69 std::string propName;
70 if (!ScriptInterface::FromJSVal(cx, idval, propName))
71 return false;
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" ||
83 propName == "blur" ||
84 propName == "getComputedSize"
86 return true;
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())
94 vp.setNull();
95 else
96 vp.setObject(*it->second.get());
97 return true;
100 if (propName == "parent")
102 IGUIObject* parent = e->GetParent();
104 if (parent)
105 vp.set(JS::ObjectValue(*parent->GetJSObject()));
106 else
107 vp.set(JS::NullValue());
109 return true;
111 else if (propName == "children")
113 JS::RootedObject obj(cx, JS_NewArrayObject(cx, JS::HandleValueArray::empty()));
114 vp.setObject(*obj);
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);
123 return true;
125 else if (propName == "name")
127 vp.set(JS::StringValue(JS_NewStringCopyZ(cx, e->GetName().c_str())));
128 return true;
130 else
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());
137 return false;
140 // (All the cases are in {...} to avoid scoping problems)
141 switch (Type)
143 case GUIST_bool:
145 bool value;
146 GUI<bool>::GetSetting(e, propName, value);
147 vp.set(JS::BooleanValue(value));
148 break;
151 case GUIST_int:
153 int value;
154 GUI<int>::GetSetting(e, propName, value);
155 vp.set(JS::Int32Value(value));
156 break;
159 case GUIST_float:
161 float value;
162 GUI<float>::GetSetting(e, propName, value);
163 // Create a garbage-collectable double
164 vp.set(JS::NumberValue(value));
165 return !vp.isNull();
168 case GUIST_CColor:
170 CColor color;
171 GUI<CColor>::GetSetting(e, propName, color);
172 JS::RootedObject obj(cx, pScriptInterface->CreateCustomObject("GUIColor"));
173 vp.setObject(*obj);
174 JS::RootedValue c(cx);
175 // Attempt to minimise ugliness through macrosity
176 #define P(x) \
177 c = JS::NumberValue(color.x); \
178 if (c.isNull()) \
179 return false; \
180 JS_SetProperty(cx, obj, #x, c)
182 P(r);
183 P(g);
184 P(b);
185 P(a);
186 #undef P
187 break;
190 case GUIST_CClientArea:
192 CClientArea area;
193 GUI<CClientArea>::GetSetting(e, propName, area);
195 JS::RootedObject obj(cx, pScriptInterface->CreateCustomObject("GUISize"));
196 vp.setObject(*obj);
199 #define P(x, y, z) pScriptInterface->SetProperty(vp, #z, area.x.y, false, true)
200 P(pixel, left, left);
201 P(pixel, top, top);
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);
208 #undef P
210 catch (PSERROR_Scripting_ConversionFailed&)
212 debug_warn(L"Error creating size object!");
213 break;
216 break;
219 case GUIST_CGUIString:
221 CGUIString value;
222 GUI<CGUIString>::GetSetting(e, propName, value);
223 ScriptInterface::ToJSVal(cx, vp, value.GetOriginalString());
224 break;
227 case GUIST_CStr:
229 CStr value;
230 GUI<CStr>::GetSetting(e, propName, value);
231 ScriptInterface::ToJSVal(cx, vp, value);
232 break;
235 case GUIST_CStrW:
237 CStrW value;
238 GUI<CStrW>::GetSetting(e, propName, value);
239 ScriptInterface::ToJSVal(cx, vp, value);
240 break;
243 case GUIST_CGUISpriteInstance:
245 CGUISpriteInstance *value;
246 GUI<CGUISpriteInstance>::GetSettingPointer(e, propName, value);
247 ScriptInterface::ToJSVal(cx, vp, value->GetName());
248 break;
251 case GUIST_EAlign:
253 EAlign value;
254 GUI<EAlign>::GetSetting(e, propName, value);
255 CStr word;
256 switch (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);
264 break;
267 case GUIST_EVAlign:
269 EVAlign value;
270 GUI<EVAlign>::GetSetting(e, propName, value);
271 CStr word;
272 switch (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);
280 break;
283 case GUIST_CGUIList:
285 CGUIList value;
286 GUI<CGUIList>::GetSetting(e, propName, value);
287 ScriptInterface::ToJSVal(cx, vp, value.m_Items);
288 break;
291 case GUIST_CGUISeries:
293 CGUISeries value;
294 GUI<CGUISeries>::GetSetting(e, propName, value);
295 ScriptInterface::ToJSVal(cx, vp, value.m_Series);
296 break;
299 default:
300 JS_ReportError(cx, "Setting '%s' uses an unimplemented type", propName.c_str());
301 DEBUG_WARN_ERR(ERR::LOGIC);
302 return false;
305 return true;
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);
312 if (!e)
313 return false;
315 JSAutoRequest rq(cx);
316 JS::RootedValue idval(cx);
317 if (!JS_IdToValue(cx, id, &idval))
318 return false;
320 std::string propName;
321 if (!ScriptInterface::FromJSVal(cx, idval, propName))
322 return false;
324 if (propName == "name")
326 std::string value;
327 if (!ScriptInterface::FromJSVal(cx, vp, value))
328 return false;
329 e->SetName(value);
330 return true;
333 JS::RootedObject vpObj(cx);
334 if (vp.isObject())
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");
343 return false;
346 CStr eventName(CStr(propName.substr(2)).LowerCase());
347 e->SetScriptHandler(eventName, vpObj);
349 return true;
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());
357 return true;
360 switch (Type)
362 case GUIST_CStr:
364 std::string value;
365 if (!ScriptInterface::FromJSVal(cx, vp, value))
366 return false;
368 GUI<CStr>::SetSetting(e, propName, value);
369 break;
372 case GUIST_CStrW:
374 std::wstring value;
375 if (!ScriptInterface::FromJSVal(cx, vp, value))
376 return false;
378 GUI<CStrW>::SetSetting(e, propName, value);
379 break;
382 case GUIST_CGUISpriteInstance:
384 std::string value;
385 if (!ScriptInterface::FromJSVal(cx, vp, value))
386 return false;
388 GUI<CGUISpriteInstance>::SetSetting(e, propName, CGUISpriteInstance(value));
389 break;
392 case GUIST_CGUIString:
394 std::wstring value;
395 if (!ScriptInterface::FromJSVal(cx, vp, value))
396 return false;
398 CGUIString str;
399 str.SetValue(value);
400 GUI<CGUIString>::SetSetting(e, propName, str);
401 break;
404 case GUIST_EAlign:
406 std::string value;
407 if (!ScriptInterface::FromJSVal(cx, vp, value))
408 return false;
410 EAlign a;
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;
414 else
416 JS_ReportError(cx, "Invalid alignment (should be 'left', 'right' or 'center')");
417 return false;
419 GUI<EAlign>::SetSetting(e, propName, a);
420 break;
423 case GUIST_EVAlign:
425 std::string value;
426 if (!ScriptInterface::FromJSVal(cx, vp, value))
427 return false;
429 EVAlign a;
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;
433 else
435 JS_ReportError(cx, "Invalid alignment (should be 'top', 'bottom' or 'center')");
436 return false;
438 GUI<EVAlign>::SetSetting(e, propName, a);
439 break;
442 case GUIST_int:
444 int value;
445 if (ScriptInterface::FromJSVal(cx, vp, value))
446 GUI<int>::SetSetting(e, propName, value);
447 else
449 JS_ReportError(cx, "Cannot convert value to int");
450 return false;
452 break;
455 case GUIST_float:
457 double value;
458 if (JS::ToNumber(cx, vp, &value) == true)
459 GUI<float>::SetSetting(e, propName, (float)value);
460 else
462 JS_ReportError(cx, "Cannot convert value to float");
463 return false;
465 break;
468 case GUIST_bool:
470 bool value = JS::ToBoolean(vp);
471 GUI<bool>::SetSetting(e, propName, value);
472 break;
475 case GUIST_CClientArea:
477 if (vp.isString())
479 std::wstring value;
480 if (!ScriptInterface::FromJSVal(cx, vp, value))
481 return false;
483 if (e->SetSetting(propName, value) != PSRETURN_OK)
485 JS_ReportError(cx, "Invalid value for setting '%s'", propName.c_str());
486 return false;
489 else if (vp.isObject() && JS_InstanceOf(cx, vpObj, &JSI_GUISize::JSI_class, NULL))
491 CClientArea area;
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);
497 P(pixel, top, top);
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);
504 #undef P
506 GUI<CClientArea>::SetSetting(e, propName, area);
508 else
510 JS_ReportError(cx, "Size only accepts strings or GUISize objects");
511 return false;
513 break;
516 case GUIST_CColor:
518 if (vp.isString())
520 std::wstring value;
521 if (!ScriptInterface::FromJSVal(cx, vp, value))
522 return false;
524 if (e->SetSetting(propName, value) != PSRETURN_OK)
526 JS_ReportError(cx, "Invalid value for setting '%s'", propName.c_str());
527 return false;
530 else if (vp.isObject() && JS_InstanceOf(cx, vpObj, &JSI_GUIColor::JSI_class, NULL))
532 CColor color;
533 JS::RootedValue t(cx);
534 double s;
535 #define PROP(x) \
536 JS_GetProperty(cx, vpObj, #x, &t); \
537 s = t.toDouble(); \
538 color.x = (float)s
540 PROP(r);
541 PROP(g);
542 PROP(b);
543 PROP(a);
544 #undef PROP
545 GUI<CColor>::SetSetting(e, propName, color);
547 else
549 JS_ReportError(cx, "Color only accepts strings or GUIColor objects");
550 return false;
552 break;
555 case GUIST_CGUIList:
557 CGUIList list;
558 if (ScriptInterface::FromJSVal(cx, vp, list.m_Items))
559 GUI<CGUIList>::SetSetting(e, propName, list);
560 else
562 JS_ReportError(cx, "Failed to get list '%s'", propName.c_str());
563 return false;
565 break;
568 case GUIST_CGUISeries:
570 CGUISeries series;
571 if (ScriptInterface::FromJSVal(cx, vp, series.m_Series))
572 GUI<CGUISeries>::SetSetting(e, propName, series);
573 else
575 JS_ReportError(cx, "Invalid value for chart series '%s'", propName.c_str());
576 return false;
578 break;
581 default:
582 JS_ReportError(cx, "Setting '%s' uses an unimplemented type", propName.c_str());
583 break;
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");
599 return false;
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);
609 return true;
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);
625 if (!e)
626 return false;
628 char buffer[256];
629 snprintf(buffer, 256, "[GUIObject: %s]", e->GetName().c_str());
630 buffer[255] = 0;
631 rec.rval().setString(JS_NewStringCopyZ(cx, buffer));
632 return true;
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);
643 if (!e)
644 return false;
646 e->GetGUI()->SetFocusedObject(e);
648 rec.rval().setUndefined();
649 return true;
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);
660 if (!e)
661 return false;
663 e->GetGUI()->SetFocusedObject(NULL);
665 rec.rval().setUndefined();
666 return true;
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);
677 if (!e)
678 return false;
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!");
695 return false;
698 rec.rval().set(objVal);
699 return true;