/* IGUIObject by Gustav Larsson gee@pyro.nu */ #include "precompiled.h" #include "GUI.h" ///// janwas: again, including etiquette? #include "Parser.h" #include ///// #include "gui/scripting/JSInterface_IGUIObject.h" #include "gui/scripting/JSInterface_GUITypes.h" extern int g_xres, g_yres; using namespace std; //------------------------------------------------------------------- // Implementation Macros //------------------------------------------------------------------- //------------------------------------------------------------------- // Constructor / Destructor //------------------------------------------------------------------- IGUIObject::IGUIObject() : m_pGUI(NULL), m_pParent(NULL), m_MouseHovering(false) { AddSetting(GUIST_bool, "enabled"); AddSetting(GUIST_bool, "hidden"); AddSetting(GUIST_CClientArea, "size"); AddSetting(GUIST_CStr, "style"); AddSetting(GUIST_CStr, "hotkey" ); AddSetting(GUIST_float, "z"); AddSetting(GUIST_bool, "absolute"); AddSetting(GUIST_bool, "ghost"); // Setup important defaults GUI::SetSetting(this, "hidden", false); GUI::SetSetting(this, "ghost", false); GUI::SetSetting(this, "enabled", true); GUI::SetSetting(this, "absolute", true); bool hidden=true; GUI::GetSetting(this, "hidden", hidden); } IGUIObject::~IGUIObject() { // delete() needs to know the type of the variable - never delete a void* #define TYPE(t) case GUIST_##t: delete (t*)it->second.m_pSetting; break; map::iterator it; for (it = m_Settings.begin(); it != m_Settings.end(); ++it) switch (it->second.m_Type) { #include "GUItypes.h" default: debug_warn("Invalid setting type"); } #undef TYPE } //------------------------------------------------------------------- // Functions //------------------------------------------------------------------- void IGUIObject::AddChild(IGUIObject *pChild) { // // assert(pChild); pChild->SetParent(this); m_Children.push_back(pChild); // If this (not the child) object is already attached // to a CGUI, it pGUI pointer will be non-null. // This will mean we'll have to check if we're using // names already used. if (pChild->GetGUI()) { try { // Atomic function, if it fails it won't // have changed anything //UpdateObjects(); pChild->GetGUI()->UpdateObjects(); } catch (PS_RESULT e) { // If anything went wrong, reverse what we did and throw // an exception telling it never added a child m_Children.erase( m_Children.end()-1 ); // We'll throw the same exception for easier // error handling throw e; } } // else do nothing } void IGUIObject::AddToPointersMap(map_pObjects &ObjectMap) { // Just don't do anything about the top node if (m_pParent == NULL) return; // Now actually add this one // notice we won't add it if it's doesn't have any parent // (i.e. being the base object) if (m_Name == string()) { throw PS_NEEDS_NAME; } if (ObjectMap.count(m_Name) > 0) { throw PS_NAME_AMBIGUITY; } else { ObjectMap[m_Name] = this; } } void IGUIObject::Destroy() { // Is there anything besides the children to destroy? } // Notice if using this, the naming convention of GUIST_ should be strict. #define TYPE(type) \ case GUIST_##type: \ m_Settings[Name].m_pSetting = new type(); \ break; void IGUIObject::AddSetting(const EGUISettingType &Type, const CStr& Name) { // Is name already taken? if (m_Settings.count(Name) >= 1) return; // Construct, and set type m_Settings[Name].m_Type = Type; switch (Type) { // Construct the setting. #include "GUItypes.h" default: debug_warn("IGUIObject::AddSetting failed, type not recognized!"); break; } } #undef TYPE bool IGUIObject::MouseOver() { if(!GetGUI()) throw PS_NEEDS_PGUI; return m_CachedActualSize.PointInside(GetMousePos()); } CPos IGUIObject::GetMousePos() const { return ((GetGUI())?(GetGUI()->m_MousePos):CPos()); } void IGUIObject::UpdateMouseOver(IGUIObject * const &pMouseOver) { // Check if this is the object being hovered. if (pMouseOver == this) { if (!m_MouseHovering) { // It wasn't hovering, so that must mean it just entered HandleMessage(GUIM_MOUSE_ENTER); ScriptEvent("mouseenter"); } // Either way, set to true m_MouseHovering = true; // call mouse over HandleMessage(GUIM_MOUSE_OVER); ScriptEvent("mouseover"); } else // Some other object (or none) is hovered { if (m_MouseHovering) { m_MouseHovering = false; HandleMessage(GUIM_MOUSE_LEAVE); ScriptEvent("mouseleave"); } } } bool IGUIObject::SettingExists(const CStr& Setting) const { // Because GetOffsets will direct dynamically defined // classes with polymorphism to respective ms_SettingsInfo // we need to make no further updates on this function // in derived classes. //return (GetSettingsInfo().count(Setting) >= 1); return (m_Settings.count(Setting) >= 1); } #define TYPE(type) \ else \ if (set.m_Type == GUIST_##type) \ { \ type _Value; \ if (!GUI::ParseString(Value, _Value)) \ return PS_FAIL; \ \ GUI::SetSetting(this, Setting, _Value); \ } PS_RESULT IGUIObject::SetSetting(const CStr& Setting, const CStr& Value) { if (!SettingExists(Setting)) { return PS_FAIL; } // Get setting SGUISetting set = m_Settings[Setting]; if (0); // else... #include "GUItypes.h" else { return PS_FAIL; } return PS_OK; } #undef TYPE PS_RESULT IGUIObject::GetSettingType(const CStr& Setting, EGUISettingType &Type) const { if (!SettingExists(Setting)) return PS_SETTING_FAIL; if (m_Settings.find(Setting) == m_Settings.end()) return PS_FAIL; Type = m_Settings.find(Setting)->second.m_Type; return PS_OK; } void IGUIObject::ChooseMouseOverAndClosest(IGUIObject* &pObject) { if (MouseOver()) { // Check if we've got competition at all if (pObject == NULL) { pObject = this; return; } // Or if it's closer if (GetBufferedZ() >= pObject->GetBufferedZ()) { pObject = this; return; } } } IGUIObject *IGUIObject::GetParent() const { // Important, we're not using GetParent() for these // checks, that could screw it up if (m_pParent) { if (m_pParent->m_pParent == NULL) return NULL; } return m_pParent; } void IGUIObject::UpdateCachedSize() { bool absolute; GUI::GetSetting(this, "absolute", absolute); CClientArea ca; GUI::GetSetting(this, "size", ca); // If absolute="false" and the object has got a parent, // use its cached size instead of the screen. Notice // it must have just been cached for it to work. if (absolute == false && m_pParent) m_CachedActualSize = ca.GetClientArea(m_pParent->m_CachedActualSize); else m_CachedActualSize = ca.GetClientArea(CRect(0.f, 0.f, (float)g_xres, (float)g_yres)); } void IGUIObject::LoadStyle(CGUI &GUIinstance, const CStr& StyleName) { // Fetch style if (GUIinstance.m_Styles.count(StyleName)==1) { LoadStyle(GUIinstance.m_Styles[StyleName]); } else { debug_warn("IGUIObject::LoadStyle failed"); } } void IGUIObject::LoadStyle(const SGUIStyle &Style) { // Iterate settings, it won't be able to set them all probably, but that doesn't matter std::map::const_iterator cit; for (cit = Style.m_SettingsDefaults.begin(); cit != Style.m_SettingsDefaults.end(); ++cit) { // Try set setting in object SetSetting(cit->first, cit->second); // It doesn't matter if it fail, it's not suppose to be able to set every setting. // since it's generic. // The beauty with styles is that it can contain more settings // than exists for the objects using it. So if the SetSetting // fails, don't care. } } float IGUIObject::GetBufferedZ() const { bool absolute; GUI::GetSetting(this, "absolute", absolute); float Z; GUI::GetSetting(this, "z", Z); if (absolute) return Z; else { if (GetParent()) return GetParent()->GetBufferedZ() + Z; else { debug_warn("IGUIObject::LoadStyle failed"); // TODO Gee: Error, no object should be relative without a parent! return Z; } } } // TODO Gee: keep this function and all??? void IGUIObject::CheckSettingsValidity() { bool hidden; GUI::GetSetting(this, "hidden", hidden); // If we hide an object, reset many of its parts if (hidden) { // Simulate that no object is hovered for this object and all its children // why? because it's try { GUI::RecurseObject(0, this, &IGUIObject::UpdateMouseOver, NULL); } catch (...) {} } try { // Send message to itself HandleMessage(GUIM_SETTINGS_UPDATED); ScriptEvent("update"); } catch (...) { } } void IGUIObject::RegisterScriptHandler(const CStr& Action, const CStr& Code, CGUI* pGUI) { const int paramCount = 1; const char* paramNames[paramCount] = { "mouse" }; // Location to report errors from CStr CodeName = GetName()+" "+Action; // Generate a unique name static int x=0; char buf[64]; sprintf(buf, "__eventhandler%d", x++); JSFunction* func = JS_CompileFunction(g_ScriptingHost.getContext(), pGUI->m_ScriptObject, buf, paramCount, paramNames, (const char*)Code, Code.Length(), CodeName, 0); assert(func); // TODO: Handle errors m_ScriptHandlers[Action] = func; } void IGUIObject::ScriptEvent(const CStr& Action) { map::iterator it = m_ScriptHandlers.find(Action); if (it == m_ScriptHandlers.end()) return; // PRIVATE_TO_JSVAL assumes two-byte alignment, // so make sure that's always true assert(! ((jsval)this & JSVAL_INT)); // The IGUIObject needs to be stored inside the script's object jsval guiObject = PRIVATE_TO_JSVAL(this); // Make a 'this', allowing access to the IGUIObject JSObject* jsGuiObject = JS_ConstructObjectWithArguments(g_ScriptingHost.getContext(), &JSI_IGUIObject::JSI_class, NULL, m_pGUI->m_ScriptObject, 1, &guiObject); assert(jsGuiObject); // TODO: Handle errors // Prevent it from being garbage-collected before it's passed into the function JS_AddRoot(g_ScriptingHost.getContext(), &jsGuiObject); // Set up the 'mouse' parameter jsval mouseParams[3]; mouseParams[0] = INT_TO_JSVAL(m_pGUI->m_MousePos.x); mouseParams[1] = INT_TO_JSVAL(m_pGUI->m_MousePos.y); mouseParams[2] = INT_TO_JSVAL(m_pGUI->m_MouseButtons); JSObject* mouseObj = JS_ConstructObjectWithArguments(g_ScriptingHost.getContext(), &JSI_GUIMouse::JSI_class, NULL, m_pGUI->m_ScriptObject, 3, mouseParams); assert(mouseObj); // TODO: Handle errors // Don't garbage collect the mouse JS_AddRoot(g_ScriptingHost.getContext(), &mouseObj); const int paramCount = 1; jsval paramData[paramCount]; paramData[0] = OBJECT_TO_JSVAL(mouseObj); jsval result; JSBool ok = JS_CallFunction(g_ScriptingHost.getContext(), jsGuiObject, it->second, 1, paramData, &result); if (!ok) { JS_ReportError(g_ScriptingHost.getContext(), "Errors executing script action \"%s\"", Action.c_str()); } // Allow the temporary parameters to be garbage-collected JS_RemoveRoot(g_ScriptingHost.getContext(), &mouseObj); JS_RemoveRoot(g_ScriptingHost.getContext(), &jsGuiObject); } CStr IGUIObject::GetPresentableName() const { // __internal(), must be at least 13 letters to be able to be // an internal name if (m_Name.Length() <= 12) return m_Name; if (m_Name.GetSubstring(0, 10) == "__internal") return CStr("[unnamed object]"); else return m_Name; } void IGUIObject::SetFocus() { GetGUI()->m_FocusedObject = this; } bool IGUIObject::IsFocused() const { return GetGUI()->m_FocusedObject == this; }