Index: ps/trunk/source/tools/atlas/AtlasObject/AtlasObject.h =================================================================== --- ps/trunk/source/tools/atlas/AtlasObject/AtlasObject.h (revision 22160) +++ ps/trunk/source/tools/atlas/AtlasObject/AtlasObject.h (revision 22161) @@ -1,196 +1,196 @@ -/* Copyright (C) 2015 Wildfire Games. +/* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ // Public interface to almost all of AtlasObject. // (See AtlasObjectText for the rest of it). // // Tries to include as few headers as possible, to minimise its impact // on compile times. #ifndef INCLUDED_ATLASOBJECT #define INCLUDED_ATLASOBJECT #if defined(_WIN32) # define _CRT_SECURE_NO_WARNINGS // Disable deprecation warning in VS2008 #endif #include // for wchar_t #include class wxString; ////////////////////////////////////////////////////////////////////////// // Mostly-private bits: // Helper class to let us define a conversion operator only for AtSmartPtr template struct ConstCastHelper { operator ConstSmPtr (); }; template struct ConstCastHelper { }; // Simple reference-counted pointer. class T must contain a reference count, // initialised to 0. An external implementation (in AtlasObjectImpl.cpp) // provides the inc_ref and dec_ref methods, so that this header file doesn't // need to know their implementations. template class AtSmartPtr : public ConstCastHelper, T> { friend struct ConstCastHelper, T>; private: void inc_ref(); void dec_ref(); T* ptr; public: // Constructors AtSmartPtr() : ptr(NULL) {} explicit AtSmartPtr(T* p) : ptr(p) { inc_ref(); } // Copy constructor AtSmartPtr(const AtSmartPtr& r) : ptr(r.ptr) { inc_ref(); } // Assignment operators AtSmartPtr& operator=(T* p) { dec_ref(); ptr = p; inc_ref(); return *this; } AtSmartPtr& operator=(const AtSmartPtr& r) { if (&r != this) { dec_ref(); ptr = r.ptr; inc_ref(); } return *this; } // Destructor ~AtSmartPtr() { dec_ref(); } // Allow conversion from non-const T* to const T* //operator AtSmartPtr () { return AtSmartPtr(ptr); } // (actually provided by ConstCastHelper) // Override -> T* operator->() const { return ptr; } // Test whether the pointer is pointing to anything - explicit operator bool() const { return ptr != NULL; } + explicit operator bool() const { return ptr != nullptr; } }; template ConstCastHelper::operator ConstSmPtr () { return ConstSmPtr(static_cast*>(this)->ptr); } // A few required declarations class AtObj; class AtNode; class AtIterImpl; ////////////////////////////////////////////////////////////////////////// // Public bits: // AtIter is an iterator over AtObjs - use it like: // // for (AtIter thing = whatever["thing"]; thing.defined(); ++thing) // DoStuff(thing); // // to handle XML data like: // // // Stuff 1 // Stuff 2 // class AtIter { public: // Increment the iterator; or make it undefined, if there weren't any // AtObjs left to iterate over AtIter& operator++ (); // Return whether this iterator has an AtObj to point to bool defined() const; // Return whether this iterator is pointing to a non-contentless AtObj bool hasContent() const; // Return the number of AtObjs that will be iterated over (including the current one) size_t count() const; // Return an iterator to the children matching 'key'. (That is, children // of the AtObj currently pointed to by this iterator) const AtIter operator[] (const char* key) const; // Return the AtObj currently pointed to by this iterator const AtObj operator* () const; // Return the string value of the AtObj currently pointed to by this iterator operator const wchar_t* () const; // Private implementation. (But not 'private:', because it's a waste of time // adding loads of friend functions) - AtSmartPtr p; + AtSmartPtr m_Impl; }; class AtObj { public: AtObj() {} - AtObj(const AtObj& r) : p(r.p) {} + AtObj(const AtObj& r) : m_Node(r.m_Node) {} // Return an iterator to the children matching 'key' const AtIter operator[] (const char* key) const; // Return the string value of this object operator const wchar_t* () const; // Return the floating point value of this object double getDouble() const; // Return the integer value of this object int getInt() const; // Check whether the object contains anything (even if those things are empty) - bool defined() const { return (bool)p; } + bool defined() const { return static_cast(m_Node); } // Check recursively whether there's actually any non-empty data in the object bool hasContent() const; // Add or set a child. The wchar_t* and wxString& versions create a new // AtObj with the appropriate string value, then use that as the child. // // These alter the AtObj's internal pointer, and the pointed-to data is // never actually altered. Copies of this AtObj (including copies stored // inside other AtObjs) will not be affected. void add(const char* key, const wchar_t* value); void add(const char* key, const wxString& value); void add(const char* key, AtObj& data); void set(const char* key, const wchar_t* value); void set(const char* key, const wxString& value); void set(const char* key, AtObj& data); void setBool(const char* key, bool value); void setDouble(const char* key, double value); void setInt(const char* key, int value); void setString(const wchar_t* value); void addOverlay(AtObj& data); - AtSmartPtr p; + AtSmartPtr m_Node; }; // Miscellaneous utility functions: namespace AtlasObject { // Returns AtObj() on failure - test with AtObj::defined() AtObj LoadFromXML(const std::string& xml); // Returns AtObj() on failure - test with AtObj::defined() AtObj LoadFromJSON(const std::string& json); // Returns UTF-8-encoded XML document string. // Returns empty string on failure. std::string SaveToXML(AtObj& obj); // Returns UTF-8-encoded JSON string. // Returns empty string on failure. std::string SaveToJSON(AtObj& obj); AtObj TrimEmptyChildren(AtObj& obj); } #endif // INCLUDED_ATLASOBJECT Index: ps/trunk/source/tools/atlas/AtlasObject/AtlasObjectImpl.cpp =================================================================== --- ps/trunk/source/tools/atlas/AtlasObject/AtlasObjectImpl.cpp (revision 22160) +++ ps/trunk/source/tools/atlas/AtlasObject/AtlasObjectImpl.cpp (revision 22161) @@ -1,347 +1,346 @@ -/* Copyright (C) 2015 Wildfire Games. +/* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "AtlasObject.h" #include "AtlasObjectImpl.h" #include #include #include #define ATSMARTPTR_IMPL(T) \ template<> void AtSmartPtr::inc_ref() \ { \ - if (ptr) ++ptr->refcount; \ + if (ptr) ++ptr->m_Refcount; \ } \ \ template<> void AtSmartPtr::dec_ref() \ { \ - if (ptr && --ptr->refcount == 0) \ + if (ptr && --ptr->m_Refcount == 0) \ delete ptr; \ } // (Don't set ptr=NULL, since it should never be possible for an // unreferenced pointer to exist; I would rather see it die in debug // mode if that ever happens, instead of just silently ignoring the error.) ATSMARTPTR_IMPL(AtNode) ATSMARTPTR_IMPL(const AtNode) ATSMARTPTR_IMPL(AtIterImpl) ////////////////////////////////////////////////////////////////////////// const AtIter AtIter::operator [] (const char* key) const { - if (p) - return p->iter->second->getChild(key); + if (m_Impl) + return m_Impl->iter->second->getChild(key); else return AtIter(); } AtIter::operator const wchar_t* () const { - if (p) - return p->iter->second->value.c_str(); + if (m_Impl) + return m_Impl->iter->second->m_Value.c_str(); else return L""; } //AtIter::operator const AtObj () const const AtObj AtIter::operator * () const { - if (p) + if (m_Impl) { AtObj ret; - ret.p = p->iter->second; + ret.m_Node = m_Impl->iter->second; return ret; } else return AtObj(); } AtIter& AtIter::operator ++ () { - assert(p); + assert(m_Impl); // Increment the internal iterator, and stop if we've run out children // to iterate over. - if (p && ++p->iter == p->iter_upperbound) - p = NULL; + if (m_Impl && ++m_Impl->iter == m_Impl->iter_upperbound) + m_Impl = nullptr; return *this; } bool AtIter::defined() const { - return (bool)p; + return static_cast(m_Impl); } bool AtIter::hasContent() const { - if (!p) + if (!m_Impl) return false; - if (! p->iter->second) + if (!m_Impl->iter->second) return false; - return p->iter->second->hasContent(); + return m_Impl->iter->second->hasContent(); } size_t AtIter::count() const { - if (!p) + if (!m_Impl) return 0; - return std::distance(p->iter, p->iter_upperbound); + return std::distance(m_Impl->iter, m_Impl->iter_upperbound); } ////////////////////////////////////////////////////////////////////////// const AtIter AtObj::operator [] (const char* key) const { - if (p) - return p->getChild(key); + if (m_Node) + return m_Node->getChild(key); else // This object doesn't exist, so return another object that doesn't exist return AtIter(); } AtObj::operator const wchar_t* () const { - if (p) - return p->value.c_str(); + if (m_Node) + return m_Node->m_Value.c_str(); else return L""; } double AtObj::getDouble() const { double val = 0; - if (p) + if (m_Node) { std::wstringstream s; - s << p->value; + s << m_Node->m_Value; s >> val; } return val; } int AtObj::getInt() const { int val = 0; - if (p) + if (m_Node) { std::wstringstream s; - s << p->value; + s << m_Node->m_Value; s >> val; } return val; } void AtObj::add(const char* key, AtObj& data) { - if (!p) - p = new AtNode(); + if (!m_Node) + m_Node = new AtNode(); - p = p->addChild(key, data.p); + m_Node = m_Node->addChild(key, data.m_Node); } void AtObj::add(const char* key, const wxString& value) { add(key, value.wc_str()); } void AtObj::add(const char* key, const wchar_t* value) { const AtNode* o = new AtNode(value); - if (!p) - p = new AtNode(); + if (!m_Node) + m_Node = new AtNode(); - p = p->addChild(key, AtNode::Ptr(o)); + m_Node = m_Node->addChild(key, AtNode::Ptr(o)); } void AtObj::set(const char* key, AtObj& data) { - if (!p) - p = new AtNode(); + if (!m_Node) + m_Node = new AtNode(); - p = p->setChild(key, data.p); + m_Node = m_Node->setChild(key, data.m_Node); } void AtObj::set(const char* key, const wxString& value) { set(key, value.wc_str()); } void AtObj::set(const char* key, const wchar_t* value) { const AtNode* o = new AtNode(value); - if (!p) - p = new AtNode(); + if (!m_Node) + m_Node = new AtNode(); - p = p->setChild(key, AtNode::Ptr(o)); + m_Node = m_Node->setChild(key, AtNode::Ptr(o)); } void AtObj::setBool(const char* key, bool value) { AtNode* o = new AtNode(value ? L"true" : L"false"); - o->children.insert(AtNode::child_pairtype("@boolean", AtNode::Ptr(new AtNode()))); + o->m_Children.insert(AtNode::child_pairtype("@boolean", AtNode::Ptr(new AtNode()))); - if (!p) - p = new AtNode(); + if (!m_Node) + m_Node = new AtNode(); - p = p->setChild(key, AtNode::Ptr(o)); + m_Node = m_Node->setChild(key, AtNode::Ptr(o)); } void AtObj::setDouble(const char* key, double value) { std::wstringstream str; str << value; AtNode* o = new AtNode(str.str().c_str()); - o->children.insert(AtNode::child_pairtype("@number", AtNode::Ptr(new AtNode()))); + o->m_Children.insert(AtNode::child_pairtype("@number", AtNode::Ptr(new AtNode()))); - if (!p) - p = new AtNode(); + if (!m_Node) + m_Node = new AtNode(); - p = p->setChild(key, AtNode::Ptr(o)); + m_Node = m_Node->setChild(key, AtNode::Ptr(o)); } void AtObj::setInt(const char* key, int value) { std::wstringstream str; str << value; AtNode* o = new AtNode(str.str().c_str()); - o->children.insert(AtNode::child_pairtype("@number", AtNode::Ptr(new AtNode()))); + o->m_Children.insert(AtNode::child_pairtype("@number", AtNode::Ptr(new AtNode()))); - if (!p) - p = new AtNode(); + if (!m_Node) + m_Node = new AtNode(); - p = p->setChild(key, AtNode::Ptr(o)); + m_Node = m_Node->setChild(key, AtNode::Ptr(o)); } void AtObj::setString(const wchar_t* value) { - if (!p) - p = new AtNode(); + if (!m_Node) + m_Node = new AtNode(); - p = p->setValue(value); + m_Node = m_Node->setValue(value); } void AtObj::addOverlay(AtObj& data) { - if (!p) - p = new AtNode(); + if (!m_Node) + m_Node = new AtNode(); - p = p->addOverlay(data.p); + m_Node = m_Node->addOverlay(data.m_Node); } bool AtObj::hasContent() const { - if (!p) + if (!m_Node) return false; - return p->hasContent(); + return m_Node->hasContent(); } ////////////////////////////////////////////////////////////////////////// const AtIter AtNode::getChild(const char* key) const { // Find the range of matching children - AtNode::child_maptype::const_iterator it = children.lower_bound(key); - AtNode::child_maptype::const_iterator it_upper = children.upper_bound(key); + AtNode::child_maptype::const_iterator it = m_Children.lower_bound(key); + AtNode::child_maptype::const_iterator it_upper = m_Children.upper_bound(key); if (it == it_upper) // No match found return AtIter(); AtIter obj; - obj.p = new AtIterImpl(it, it_upper); + obj.m_Impl = new AtIterImpl(it, it_upper); return obj; } bool AtNode::hasContent() const { - if (value.length()) + if (m_Value.length()) return true; - for (child_maptype::const_iterator it = children.begin(); it != children.end(); ++it) - if (it->second && it->second->hasContent()) + for (const child_maptype::value_type& child : m_Children) + if (child.second && child.second->hasContent()) return true; return false; } const AtNode::Ptr AtNode::setValue(const wchar_t* value) const { AtNode* newNode = new AtNode(); - newNode->children = children; - newNode->value = value; + newNode->m_Children = m_Children; + newNode->m_Value = value; return AtNode::Ptr(newNode); } const AtNode::Ptr AtNode::setChild(const char* key, const AtNode::Ptr &data) const { AtNode* newNode = new AtNode(this); - newNode->children.erase(key); - newNode->children.insert(AtNode::child_pairtype(key, data)); + newNode->m_Children.erase(key); + newNode->m_Children.insert(AtNode::child_pairtype(key, data)); return AtNode::Ptr(newNode); } const AtNode::Ptr AtNode::addChild(const char* key, const AtNode::Ptr &data) const { AtNode* newNode = new AtNode(this); - newNode->children.insert(AtNode::child_pairtype(key, data)); + newNode->m_Children.insert(AtNode::child_pairtype(key, data)); return AtNode::Ptr(newNode); } const AtNode::Ptr AtNode::addOverlay(const AtNode::Ptr &data) const { AtNode* newNode = new AtNode(this); // Delete old childs that are also in the overlay - for (AtNode::child_maptype::const_iterator it = data->children.begin(); it != data->children.end(); ++it) - newNode->children.erase(it->first); + for (AtNode::child_maptype::const_iterator it = data->m_Children.begin(); it != data->m_Children.end(); ++it) + newNode->m_Children.erase(it->first); // Add the overlay childs back in - for (AtNode::child_maptype::const_iterator it = data->children.begin(); it != data->children.end(); ++it) - newNode->children.insert(*it); + for (AtNode::child_maptype::const_iterator it = data->m_Children.begin(); it != data->m_Children.end(); ++it) + newNode->m_Children.insert(*it); return AtNode::Ptr(newNode); } ////////////////////////////////////////////////////////////////////////// AtObj AtlasObject::TrimEmptyChildren(AtObj& obj) { AtObj ret; - for (AtNode::child_maptype::const_iterator it = obj.p->children.begin(); - it != obj.p->children.end(); ++it) + for (const AtNode::child_maptype::value_type& child : obj.m_Node->m_Children) { - if (it->second && it->second->hasContent()) + if (child.second && child.second->hasContent()) { AtObj node; - node.p = it->second; - ret.add(it->first.c_str(), node); + node.m_Node = child.second; + ret.add(child.first.c_str(), node); } } return ret; } Index: ps/trunk/source/tools/atlas/AtlasObject/AtlasObjectImpl.h =================================================================== --- ps/trunk/source/tools/atlas/AtlasObject/AtlasObjectImpl.h (revision 22160) +++ ps/trunk/source/tools/atlas/AtlasObject/AtlasObjectImpl.h (revision 22161) @@ -1,86 +1,86 @@ -/* Copyright (C) 2009 Wildfire Games. +/* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "AtlasObject.h" #include #ifdef _MSC_VER // Avoid complaints about unreachable code; the optimiser is realising // that some code is incapable of throwing, then warning about the catch // block that will never be executed. #pragma warning(disable: 4702) #include #pragma warning(default: 4702) #else #include #endif // AtNode is an immutable tree node, with a string and and multimap of children. class AtNode { friend class AtSmartPtr; friend class AtSmartPtr; public: typedef AtSmartPtr Ptr; - AtNode() : refcount(0) {} - explicit AtNode(const AtNode* n) { *this = *n; refcount = 0; } - explicit AtNode(const wchar_t* text) : refcount(0), value(text) {} + AtNode() : m_Refcount(0) {} + explicit AtNode(const AtNode* n) { *this = *n; m_Refcount = 0; } + explicit AtNode(const wchar_t* text) : m_Refcount(0), m_Value(text) {} // Create a new AtNode (since AtNodes are immutable, so it's not possible // to just change this one), with the relevant alterations to its content. const AtNode::Ptr setValue(const wchar_t* value) const; const AtNode::Ptr addChild(const char* key, const AtNode::Ptr &data) const; const AtNode::Ptr setChild(const char* key, const AtNode::Ptr &data) const; const AtNode::Ptr addOverlay(const AtNode::Ptr &data) const; const AtIter getChild(const char* key) const; // Check recursively for any 'value' data bool hasContent() const; //private: // (but not actually private, since I'm still too lazy to waste // time with dozens of friends) - std::wstring value; + std::wstring m_Value; typedef std::multimap child_maptype; typedef std::pair child_pairtype; - child_maptype children; + child_maptype m_Children; private: - mutable unsigned int refcount; + mutable unsigned int m_Refcount; }; // Implementation of AtIter class AtIterImpl { friend class AtSmartPtr; public: - AtIterImpl() : refcount(0) {} + AtIterImpl() : m_Refcount(0) {} AtIterImpl(AtNode::child_maptype::const_iterator it, AtNode::child_maptype::const_iterator up) - : refcount(0), iter(it), iter_upperbound(up) {} + : m_Refcount(0), iter(it), iter_upperbound(up) {} AtNode::child_maptype::const_iterator iter, iter_upperbound; private: - mutable unsigned int refcount; + mutable unsigned int m_Refcount; }; Index: ps/trunk/source/tools/atlas/AtlasObject/AtlasObjectJS.cpp =================================================================== --- ps/trunk/source/tools/atlas/AtlasObject/AtlasObjectJS.cpp (revision 22160) +++ ps/trunk/source/tools/atlas/AtlasObject/AtlasObjectJS.cpp (revision 22161) @@ -1,195 +1,194 @@ -/* Copyright (C) 2017 Wildfire Games. +/* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "AtlasObject.h" #include "AtlasObjectImpl.h" #include "JSONSpiritInclude.h" #if defined(_MSC_VER) # pragma warning(disable:4996) // deprecated CRT #endif #include "wx/log.h" #include static AtSmartPtr ConvertNode(json_spirit::Value node); AtObj AtlasObject::LoadFromJSON(const std::string& json) { json_spirit::Value rootnode; json_spirit::read_string(json, rootnode); AtObj obj; - obj.p = ConvertNode(rootnode); + obj.m_Node = ConvertNode(rootnode); return obj; } // Convert from a JSON to an AtNode static AtSmartPtr ConvertNode(json_spirit::Value node) { AtSmartPtr obj (new AtNode()); if (node.type() == json_spirit::str_type) { - obj->value = std::wstring(node.get_str().begin(),node.get_str().end()); + obj->m_Value = std::wstring(node.get_str().begin(),node.get_str().end()); } else if (node.type() == json_spirit::int_type || node.type() == json_spirit::real_type) { std::wstringstream stream; if (node.type() == json_spirit::int_type) stream << node.get_int(); if (node.type() == json_spirit::real_type) stream << node.get_real(); - obj->value = stream.str().c_str(); - obj->children.insert(AtNode::child_pairtype( + obj->m_Value = stream.str().c_str(); + obj->m_Children.insert(AtNode::child_pairtype( "@number", AtSmartPtr(new AtNode()) )); } else if (node.type() == json_spirit::bool_type) { if (node.get_bool()) - obj->value = L"true"; + obj->m_Value = L"true"; else - obj->value = L"false"; + obj->m_Value = L"false"; - obj->children.insert(AtNode::child_pairtype( + obj->m_Children.insert(AtNode::child_pairtype( "@boolean", AtSmartPtr(new AtNode()) )); } else if (node.type() == json_spirit::array_type) { - obj->children.insert(AtNode::child_pairtype( + obj->m_Children.insert(AtNode::child_pairtype( "@array", AtSmartPtr(new AtNode()) )); json_spirit::Array nodeChildren = node.get_array(); json_spirit::Array::iterator itr = nodeChildren.begin(); for (; itr != nodeChildren.end(); ++itr) { - obj->children.insert(AtNode::child_pairtype( + obj->m_Children.insert(AtNode::child_pairtype( "item", ConvertNode(*itr) )); } } else if (node.type() == json_spirit::obj_type) { json_spirit::Object objectProperties = node.get_obj(); json_spirit::Object::iterator itr = objectProperties.begin(); for (; itr != objectProperties.end(); ++itr) { - obj->children.insert(AtNode::child_pairtype( + obj->m_Children.insert(AtNode::child_pairtype( itr->name_, ConvertNode(itr->value_) )); } } else if (node.type() == json_spirit::null_type) { return obj; } else { assert(! "Unimplemented type found when parsing JSON!"); } return obj; } json_spirit::Value BuildJSONNode(AtNode::Ptr p) { if (!p) { json_spirit::Value rval; return rval; } // Special case for numbers/booleans to allow round-tripping - if (p->children.count("@number")) + if (p->m_Children.count("@number")) { // Convert to double std::wstringstream str; - str << p->value; + str << p->m_Value; double val = 0; str >> val; json_spirit::Value rval(val); return rval; } - else if (p->children.count("@boolean")) + else if (p->m_Children.count("@boolean")) { bool val = false; - if (p->value == L"true") + if (p->m_Value == L"true") val = true; json_spirit::Value rval(val); return rval; } // If no children, then use the value string instead - if (p->children.empty()) + if (p->m_Children.empty()) { - json_spirit::Value rval(std::string(p->value.begin(), p->value.end())); + json_spirit::Value rval(std::string(p->m_Value.begin(), p->m_Value.end())); return rval; } - if (p->children.find("@array") != p->children.end()) + if (p->m_Children.find("@array") != p->m_Children.end()) { json_spirit::Array rval; // Find the children - AtNode::child_maptype::const_iterator lower = p->children.lower_bound("item"); - AtNode::child_maptype::const_iterator upper = p->children.upper_bound("item"); + AtNode::child_maptype::const_iterator lower = p->m_Children.lower_bound("item"); + AtNode::child_maptype::const_iterator upper = p->m_Children.upper_bound("item"); unsigned int idx = 0; for (AtNode::child_maptype::const_iterator it = lower; it != upper; ++it) { json_spirit::Value child = BuildJSONNode(it->second); rval.push_back(child); ++idx; } return rval; } else { json_spirit::Object rval; - for (AtNode::child_maptype::const_iterator it = p->children.begin(); it != p->children.end(); ++it) + for (AtNode::child_maptype::const_iterator it = p->m_Children.begin(); it != p->m_Children.end(); ++it) { json_spirit::Value child = BuildJSONNode(it->second); // We don't serialize childs with null value. // Instead of something like this we omit the whole property: "StartingCamera": null // There's no special reason for that, it's just the same behaviour the previous implementations had. if (child.type() != json_spirit::null_type) rval.push_back(json_spirit::Pair(it->first.c_str(), child)); } return rval; } } std::string AtlasObject::SaveToJSON(AtObj& obj) { - json_spirit::Value root = BuildJSONNode(obj.p); - std::string ret = json_spirit::write_string(root, 0); - return ret; + json_spirit::Value root = BuildJSONNode(obj.m_Node); + return json_spirit::write_string(root, 0); } Index: ps/trunk/source/tools/atlas/AtlasObject/AtlasObjectText.cpp =================================================================== --- ps/trunk/source/tools/atlas/AtlasObject/AtlasObjectText.cpp (revision 22160) +++ ps/trunk/source/tools/atlas/AtlasObject/AtlasObjectText.cpp (revision 22161) @@ -1,73 +1,71 @@ -/* Copyright (C) 2009 Wildfire Games. +/* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "AtlasObjectText.h" #include "AtlasObjectImpl.h" #include "AtlasObject.h" static std::wstring ConvertRecursive(const AtNode::Ptr obj, bool use_brackets = true) { // Convert (1, ()) into "1" // Convert (3, (d: (...), e: (...))) into "3 (conv(...), conv(...))" // etc // resulting in data-loss [because of the key names], and a rather arbitrary // [alphabetical by key] ordering of children, but at least it's fairly readable if (! obj) return L""; std::wstring result; - bool has_value = (obj->value.length() != 0); - bool has_children = (obj->children.size() != 0); + bool has_value = !obj->m_Value.empty(); + bool has_children = !obj->m_Children.empty(); if (has_value && has_children) - result = obj->value + L" "; + result = obj->m_Value + L" "; else if (has_value) - result = obj->value; + result = obj->m_Value; // else no value; result = L"" if (has_children) { if (use_brackets) result += L"("; bool first_child = true; // so we can add ", " in appropriate places - for (AtNode::child_maptype::const_iterator it = obj->children.begin(); - it != obj->children.end(); - ++it) + for (const AtNode::child_maptype::value_type& child : obj->m_Children) { - if (! first_child) + if (!first_child) result += L", "; else first_child = false; - result += ConvertRecursive(it->second); + result += ConvertRecursive(child.second); } if (use_brackets) result += L")"; } return result; } std::wstring AtlasObject::ConvertToString(const AtObj& obj) { - return ConvertRecursive(obj.p, false); + return ConvertRecursive(obj.m_Node, false); } Index: ps/trunk/source/tools/atlas/AtlasObject/AtlasObjectXML.cpp =================================================================== --- ps/trunk/source/tools/atlas/AtlasObject/AtlasObjectXML.cpp (revision 22160) +++ ps/trunk/source/tools/atlas/AtlasObject/AtlasObjectXML.cpp (revision 22161) @@ -1,249 +1,251 @@ -/* Copyright (C) 2009 Wildfire Games. +/* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "AtlasObject.h" #include "AtlasObjectImpl.h" #include #include #include #include #include // UTF conversion code adapted from http://www.unicode.org/Public/PROGRAMS/CVTUTF/ConvertUTF.c static const unsigned char firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; static const char trailingBytesForUTF8[256] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 }; static const unsigned long offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL, 0x03C82080UL, 0xFA082080UL, 0x82082080UL }; class toXmlChar { public: toXmlChar(const std::wstring& str) { - for (size_t i = 0; i < str.length(); ++i) + for(wchar_t ch : str) { unsigned short bytesToWrite; - wchar_t ch = str[i]; if (ch < 0x80) bytesToWrite = 1; else if (ch < 0x800) bytesToWrite = 2; else if (ch < 0x10000) bytesToWrite = 3; else if (ch < 0x110000) bytesToWrite = 4; else bytesToWrite = 3, ch = 0xFFFD; // replacement character char buf[4]; char* target = &buf[bytesToWrite]; // GCC sometimes warns "array subscript is above array bounds [-Warray-bounds]" // for the above line, which is a false positive - the C++ standard allows a // pointer to just after the last element in an array, as long as it's not // dereferenced (which it isn't here) switch (bytesToWrite) { case 4: *--target = ((ch | 0x80) & 0xBF); ch >>= 6; case 3: *--target = ((ch | 0x80) & 0xBF); ch >>= 6; case 2: *--target = ((ch | 0x80) & 0xBF); ch >>= 6; case 1: *--target = (char)(ch | firstByteMark[bytesToWrite]); } data += std::string(buf, bytesToWrite); } } operator const xmlChar*() { return (const xmlChar*)data.c_str(); } private: std::string data; }; std::wstring fromXmlChar(const xmlChar* str) { std::wstring result; const xmlChar* source = str; const xmlChar* sourceEnd = str + strlen((const char*)str); while (source < sourceEnd) { unsigned long ch = 0; int extraBytesToRead = trailingBytesForUTF8[*source]; assert(source + extraBytesToRead < sourceEnd); switch (extraBytesToRead) { case 5: ch += *source++; ch <<= 6; case 4: ch += *source++; ch <<= 6; case 3: ch += *source++; ch <<= 6; case 2: ch += *source++; ch <<= 6; case 1: ch += *source++; ch <<= 6; case 0: ch += *source++; } ch -= offsetsFromUTF8[extraBytesToRead]; // Make sure it fits in a 16-bit wchar_t if (ch > 0xFFFF) ch = 0xFFFD; result += (wchar_t)ch; } return result; } // TODO: replace most of the asserts below (e.g. for when it fails to load // a file) with some proper logging/reporting system static AtSmartPtr ConvertNode(xmlNodePtr node); AtObj AtlasObject::LoadFromXML(const std::string& xml) { xmlDocPtr doc = xmlReadMemory(xml.c_str(), xml.length(), "noname.xml", NULL, XML_PARSE_NONET|XML_PARSE_NOCDATA); if (doc == NULL) return AtObj(); // TODO: Need to report the error message somehow xmlNodePtr root = xmlDocGetRootElement(doc); AtObj obj; - obj.p = ConvertNode(root); + obj.m_Node = ConvertNode(root); AtObj rootObj; rootObj.set((const char*)root->name, obj); xmlFreeDoc(doc); return rootObj; } // Convert from a DOMElement to an AtNode static AtSmartPtr ConvertNode(xmlNodePtr node) { AtSmartPtr obj (new AtNode()); // Loop through all attributes for (xmlAttrPtr cur_attr = node->properties; cur_attr; cur_attr = cur_attr->next) { std::string name ("@"); name += (const char*)cur_attr->name; xmlChar* content = xmlNodeGetContent(cur_attr->children); std::wstring value (fromXmlChar(content)); xmlFree(content); AtNode* newNode = new AtNode(value.c_str()); - obj->children.insert(AtNode::child_pairtype( + obj->m_Children.insert(AtNode::child_pairtype( name.c_str(), AtNode::Ptr(newNode) )); } // Loop through all child elements for (xmlNodePtr cur_node = node->children; cur_node; cur_node = cur_node->next) { if (cur_node->type == XML_ELEMENT_NODE) { - obj->children.insert(AtNode::child_pairtype( + obj->m_Children.insert(AtNode::child_pairtype( (const char*)cur_node->name, ConvertNode(cur_node) )); } else if (cur_node->type == XML_TEXT_NODE) { xmlChar* content = xmlNodeGetContent(cur_node); std::wstring value (fromXmlChar(content)); xmlFree(content); - obj->value += value; + obj->m_Value += value; } } // Trim whitespace surrounding the string value const std::wstring whitespace = L" \t\r\n"; - size_t first = obj->value.find_first_not_of(whitespace); + size_t first = obj->m_Value.find_first_not_of(whitespace); if (first == std::wstring::npos) - obj->value = L""; + obj->m_Value = L""; else { - size_t last = obj->value.find_last_not_of(whitespace); - obj->value = obj->value.substr(first, 1+last-first); + size_t last = obj->m_Value.find_last_not_of(whitespace); + obj->m_Value = obj->m_Value.substr(first, 1+last-first); } return obj; } // Build a DOM node from a given AtNode static void BuildDOMNode(xmlDocPtr doc, xmlNodePtr node, AtNode::Ptr p) { if (p) { - if (p->value.length()) - xmlNodeAddContent(node, toXmlChar(p->value)); + if (p->m_Value.length()) + xmlNodeAddContent(node, toXmlChar(p->m_Value)); - for (AtNode::child_maptype::const_iterator it = p->children.begin(); it != p->children.end(); ++it) + for (const AtNode::child_maptype::value_type& child : p->m_Children) { + const xmlChar* first_child = reinterpret_cast(child.first.c_str()); + // Test for attribute nodes (whose names start with @) - if (it->first.length() && it->first[0] == '@') + if (child.first.length() && child.first[0] == '@') { - assert(it->second); - assert(it->second->children.empty()); - xmlNewProp(node, (const xmlChar*)it->first.c_str()+1, toXmlChar(it->second->value)); + assert(child.second); + assert(child.second->m_Children.empty()); + xmlNewProp(node, first_child + 1, toXmlChar(child.second->m_Value)); } else { - if (node == NULL) // first node in the document - needs to be made the root node + // First node in the document - needs to be made the root node + if (node == nullptr) { - xmlNodePtr root = xmlNewNode(NULL, (const xmlChar*)it->first.c_str()); + xmlNodePtr root = xmlNewNode(nullptr, first_child); xmlDocSetRootElement(doc, root); - BuildDOMNode(doc, root, it->second); + BuildDOMNode(doc, root, child.second); } else { - xmlNodePtr child = xmlNewChild(node, NULL, (const xmlChar*)it->first.c_str(), NULL); - BuildDOMNode(doc, child, it->second); + xmlNodePtr newChild = xmlNewChild(node, nullptr, first_child, nullptr); + BuildDOMNode(doc, newChild, child.second); } } } } } std::string AtlasObject::SaveToXML(AtObj& obj) { - if (!obj.p || obj.p->children.size() != 1) + if (!obj.m_Node || obj.m_Node->m_Children.size() != 1) { assert(! "SaveToXML: root must only have one child"); return ""; } - AtNode::Ptr firstChild (obj.p->children.begin()->second); + AtNode::Ptr firstChild (obj.m_Node->m_Children.begin()->second); xmlDocPtr doc = xmlNewDoc((const xmlChar*)"1.0"); - BuildDOMNode(doc, NULL, obj.p); + BuildDOMNode(doc, nullptr, obj.m_Node); xmlChar* buf; int size; xmlDocDumpFormatMemoryEnc(doc, &buf, &size, "utf-8", 1); std::string ret((const char*)buf, size); xmlFree(buf); xmlFreeDoc(doc); // TODO: handle errors better return ret; } Index: ps/trunk/source/tools/atlas/AtlasUI/CustomControls/MapDialog/MapDialog.cpp =================================================================== --- ps/trunk/source/tools/atlas/AtlasUI/CustomControls/MapDialog/MapDialog.cpp (revision 22160) +++ ps/trunk/source/tools/atlas/AtlasUI/CustomControls/MapDialog/MapDialog.cpp (revision 22161) @@ -1,258 +1,258 @@ -/* Copyright (C) 2018 Wildfire Games. +/* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "MapDialog.h" #include "GameInterface/MessagePasser.h" #include "GameInterface/Messages.h" #include #include enum { ID_MapDialogFilename = 1, ID_MapDialogNotebook, ID_ScenarioPage, ID_SkirmishPage, ID_TutorialPage }; static const wxString scenarioPath(L"maps/scenarios/"); static const wxString skirmishPath(L"maps/skirmishes/"); static const wxString tutorialPath(L"maps/tutorials/"); MapDialog::MapDialog(wxWindow* parent, MapDialogType type, const wxIcon& icon) : wxDialog(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(600,400), wxCAPTION|wxRESIZE_BORDER|wxCLOSE_BOX|wxSYSTEM_MENU), m_Type(type) { Freeze(); SetIcon(icon); if (m_Type == MAPDIALOG_OPEN) SetTitle(_("Choose map to open")); else // MAPDIALOG_SAVE SetTitle(_("Choose map to save")); AtlasMessage::qGetMapList qry; qry.Post(); wxSizer* sizer = new wxBoxSizer(wxVERTICAL); wxNotebook* notebook = new wxNotebook(this, ID_MapDialogNotebook); { wxPanel* page = new wxPanel(notebook, ID_ScenarioPage); wxSizer* pageSizer = new wxBoxSizer(wxVERTICAL); // TODO: Should display something nicer than raw VFS paths wxListBox* listBox = new wxListBox(page, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, NULL, wxLB_SINGLE|wxLB_HSCROLL); for (const std::wstring& filename : *qry.scenarioFilenames) { wxString name = filename.substr(scenarioPath.Length()); listBox->Append(name, new wxStringClientData(filename)); } pageSizer->Add(listBox, wxSizerFlags().Proportion(1).Expand()); page->SetSizer(pageSizer); notebook->AddPage(page, _("Scenarios")); } { wxPanel* page = new wxPanel(notebook, ID_SkirmishPage); wxSizer* pageSizer = new wxBoxSizer(wxVERTICAL); // TODO: Should display something nicer than raw VFS paths wxListBox* listBox = new wxListBox(page, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, NULL, wxLB_SINGLE|wxLB_HSCROLL); for (const std::wstring& filename : *qry.skirmishFilenames) { wxString name = filename.substr(skirmishPath.Length()); listBox->Append(name, new wxStringClientData(filename)); } pageSizer->Add(listBox, wxSizerFlags().Proportion(1).Expand()); page->SetSizer(pageSizer); notebook->AddPage(page, _("Skirmishes")); } { wxPanel* page = new wxPanel(notebook, ID_TutorialPage); wxSizer* pageSizer = new wxBoxSizer(wxVERTICAL); // TODO: Should display something nicer than raw VFS paths wxListBox* listBox = new wxListBox(page, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, NULL, wxLB_SINGLE | wxLB_HSCROLL); for (const std::wstring& filename : *qry.tutorialFilenames) { wxString name = filename.substr(tutorialPath.Length()); listBox->Append(name, new wxStringClientData(filename)); } pageSizer->Add(listBox, wxSizerFlags().Proportion(1).Expand()); page->SetSizer(pageSizer); notebook->AddPage(page, _("Tutorials")); } notebook->SetSelection(0); sizer->Add(notebook, wxSizerFlags().Proportion(1).Expand()); sizer->AddSpacer(5); wxSizer* filenameSizer = new wxBoxSizer(wxHORIZONTAL); filenameSizer->AddSpacer(10); filenameSizer->Add(new wxStaticText(this, wxID_ANY, _(m_Type == MAPDIALOG_SAVE ? "Map name: " : "Map path: ")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL)); wxTextCtrl* filename = new wxTextCtrl(this, ID_MapDialogFilename, wxEmptyString, wxDefaultPosition, wxDefaultSize, m_Type == MAPDIALOG_OPEN ? wxTE_READONLY : 0L); filenameSizer->Add(filename, wxSizerFlags().Proportion(1).Expand()); sizer->Add(filenameSizer, wxSizerFlags().Expand()); sizer->AddSpacer(10); wxSizer* buttonSizer = new wxBoxSizer(wxHORIZONTAL); if (m_Type == MAPDIALOG_OPEN) buttonSizer->Add(new wxButton(this, wxID_OPEN, _("Open"))); else // MAPDIALOG_SAVE buttonSizer->Add(new wxButton(this, wxID_SAVE, _("Save"))); buttonSizer->AddSpacer(5); buttonSizer->Add(new wxButton(this, wxID_CANCEL, _("Cancel"))); sizer->Add(buttonSizer, wxSizerFlags().Align(wxALIGN_RIGHT).Border(wxRIGHT|wxBOTTOM, 10)); SetSizer(sizer); Layout(); Thaw(); } wxString MapDialog::GetSelectedFilePath() const { wxNotebook* notebook = wxDynamicCast(FindWindow(ID_MapDialogNotebook), wxNotebook); if (!notebook) return wxEmptyString; wxFileName fileName(m_FileName, wxPATH_UNIX); fileName.SetExt(L"xml"); switch (notebook->GetSelection()) { case 0: return scenarioPath + fileName.GetFullPath(wxPATH_UNIX); case 1: return skirmishPath + fileName.GetFullPath(wxPATH_UNIX); case 2: return tutorialPath + fileName.GetFullPath(wxPATH_UNIX); default: return wxEmptyString; } } void MapDialog::OnListBox(wxCommandEvent& evt) { if (evt.GetInt() < 0) m_FileName = wxEmptyString; else m_FileName = evt.GetString(); if (m_Type == MAPDIALOG_SAVE) wxDynamicCast(FindWindow(ID_MapDialogFilename), wxTextCtrl)->ChangeValue(m_FileName); else { wxString filePath = GetSelectedFilePath(); - AtlasMessage::qVFSFileExists qry(filePath.wc_str()); - qry.Post(); - if (!filePath.IsEmpty() && qry.exists) + AtlasMessage::qVFSFileExists fileExistsQuery(filePath.wc_str()); + fileExistsQuery.Post(); + if (!filePath.IsEmpty() && fileExistsQuery.exists) { - AtlasMessage::qVFSFileRealPath qry(filePath.wc_str()); - qry.Post(); - wxDynamicCast(FindWindow(ID_MapDialogFilename), wxTextCtrl)->ChangeValue(*qry.realPath); + AtlasMessage::qVFSFileRealPath pathQuery(filePath.wc_str()); + pathQuery.Post(); + wxDynamicCast(FindWindow(ID_MapDialogFilename), wxTextCtrl)->ChangeValue(*pathQuery.realPath); } } if (evt.GetEventType() == wxEVT_COMMAND_LISTBOX_DOUBLECLICKED) { if (m_Type == MAPDIALOG_OPEN) OpenFile(); else SaveFile(); } } void MapDialog::OnCancel(wxCommandEvent& WXUNUSED(evt)) { EndModal(wxID_CANCEL); } void MapDialog::OnOpen(wxCommandEvent& WXUNUSED(evt)) { OpenFile(); } void MapDialog::OnSave(wxCommandEvent& WXUNUSED(evt)) { SaveFile(); } void MapDialog::OnFilename(wxCommandEvent& evt) { m_FileName = evt.GetString(); } void MapDialog::OnNotebookChanged(wxBookCtrlEvent& WXUNUSED(evt)) { if (m_Type != MAPDIALOG_OPEN) return; wxWindow* window = FindWindow(ID_MapDialogFilename); if (window) wxDynamicCast(window, wxTextCtrl)->ChangeValue(wxEmptyString); } void MapDialog::OpenFile() { wxString filePath = GetSelectedFilePath(); if (filePath.empty()) return; AtlasMessage::qVFSFileExists qry(filePath.wc_str()); qry.Post(); if (!qry.exists) return; EndModal(wxID_OK); } void MapDialog::SaveFile() { wxString filePath = GetSelectedFilePath(); if (filePath.empty()) return; // TODO: this test would work better outside the VFS AtlasMessage::qVFSFileExists qry(filePath.wc_str()); qry.Post(); if (qry.exists) { if (wxMessageBox(_("WARNING: '") + filePath + _("' already exists, it may be overwritten. Continue?"), _("Overwrite map confirmation"), wxICON_EXCLAMATION | wxYES_NO) != wxYES) return; } EndModal(wxID_OK); } BEGIN_EVENT_TABLE(MapDialog, wxDialog) EVT_BUTTON (wxID_CANCEL, MapDialog::OnCancel) EVT_BUTTON (wxID_OPEN, MapDialog::OnOpen) EVT_BUTTON (wxID_SAVE, MapDialog::OnSave) EVT_LISTBOX (wxID_ANY, MapDialog::OnListBox) EVT_LISTBOX_DCLICK (wxID_ANY, MapDialog::OnListBox) EVT_TEXT (ID_MapDialogFilename, MapDialog::OnFilename) EVT_NOTEBOOK_PAGE_CHANGED (ID_MapDialogNotebook, MapDialog::OnNotebookChanged) END_EVENT_TABLE() Index: ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp =================================================================== --- ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp (revision 22160) +++ ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp (revision 22161) @@ -1,1141 +1,1141 @@ -/* Copyright (C) 2018 Wildfire Games. +/* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "ScenarioEditor.h" #include "wx/busyinfo.h" #include "wx/clipbrd.h" #include "wx/config.h" #include "wx/dir.h" #include "wx/evtloop.h" #include "wx/ffile.h" #include "wx/filename.h" #include "wx/image.h" #include "wx/sstream.h" #include "wx/sysopt.h" #include "wx/tooltip.h" #include "wx/xml/xml.h" #include "General/AtlasEventLoop.h" #include "General/Datafile.h" #include "CustomControls/Buttons/ToolButton.h" #include "CustomControls/Canvas/Canvas.h" #include "CustomControls/HighResTimer/HighResTimer.h" #include "CustomControls/MapDialog/MapDialog.h" #include "GameInterface/MessagePasser.h" #include "GameInterface/Messages.h" #include "Misc/KeyMap.h" #include "Tools/Common/Tools.h" #include "Tools/Common/Brushes.h" #include "Tools/Common/MiscState.h" static HighResTimer g_Timer; /** * wxWidgets only registers the double click on mousedown. */ static int g_Clicks = 1; using namespace AtlasMessage; ////////////////////////////////////////////////////////////////////////// // GL functions exported from DLL, and called by game (in a separate // thread to the standard wx one) ATLASDLLIMPEXP void Atlas_GLSetCurrent(void* canvas) { static_cast(canvas)->SetCurrent(); } ATLASDLLIMPEXP void Atlas_GLSwapBuffers(void* canvas) { static_cast(canvas)->SwapBuffers(); } ////////////////////////////////////////////////////////////////////////// class GameCanvas : public Canvas { public: GameCanvas(ScenarioEditor& scenarioEditor, wxWindow* parent, int* attribList) : Canvas(parent, attribList, wxWANTS_CHARS), m_ScenarioEditor(scenarioEditor), m_MouseState(NONE), m_LastMouseState(NONE) { } private: bool KeyScroll(wxKeyEvent& evt, bool enable) { int dir; switch (evt.GetKeyCode()) { case 'A': case WXK_LEFT: dir = eScrollConstantDir::LEFT; break; case 'D': case WXK_RIGHT: dir = eScrollConstantDir::RIGHT; break; case 'W': case WXK_UP: dir = eScrollConstantDir::FORWARDS; break; case 'S': case WXK_DOWN: dir = eScrollConstantDir::BACKWARDS; break; case 'Q': case '[': dir = eScrollConstantDir::CLOCKWISE; break; case 'E': case ']': dir = eScrollConstantDir::ANTICLOCKWISE; break; case WXK_SHIFT: case WXK_CONTROL: dir = -1; break; default: return false; } float speed = 120.f * ScenarioEditor::GetSpeedModifier(); if (dir == -1) // changed modifier keys - update all currently-scrolling directions { if (wxGetKeyState(WXK_LEFT)) POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::LEFT, speed)); if (wxGetKeyState(WXK_RIGHT)) POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::RIGHT, speed)); if (wxGetKeyState(WXK_UP)) POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::FORWARDS, speed)); if (wxGetKeyState(WXK_DOWN)) POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::BACKWARDS, speed)); if (wxGetKeyState((wxKeyCode)'[')) POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::CLOCKWISE, speed)); if (wxGetKeyState((wxKeyCode)']')) POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::ANTICLOCKWISE, speed)); return false; } else { POST_MESSAGE(ScrollConstant, (eRenderView::GAME, dir, enable ? speed : 0.0f)); return true; } } void OnKeyDown(wxKeyEvent& evt) { if (m_ScenarioEditor.GetToolManager().GetCurrentTool()->OnKey(evt, ITool::KEY_DOWN)) { // Key event has been handled by the tool, so don't try // to use it for camera motion too return; } if (KeyScroll(evt, true)) return; // Slight hack: Only pass 'special' keys; normal keys will generate a translated Char event instead if (evt.GetKeyCode() >= 256) POST_MESSAGE(GuiKeyEvent, (GetSDLKeyFromWxKeyCode(evt.GetKeyCode()), evt.GetUnicodeKey(), true)); evt.Skip(); } void OnKeyUp(wxKeyEvent& evt) { if (m_ScenarioEditor.GetToolManager().GetCurrentTool()->OnKey(evt, ITool::KEY_UP)) return; if (KeyScroll(evt, false)) return; // Slight hack: Only pass 'special' keys; normal keys will generate a translated Char event instead if (evt.GetKeyCode() >= 256) POST_MESSAGE(GuiKeyEvent, (GetSDLKeyFromWxKeyCode(evt.GetKeyCode()), evt.GetUnicodeKey(), false)); evt.Skip(); } void OnChar(wxKeyEvent& evt) { if (m_ScenarioEditor.GetToolManager().GetCurrentTool()->OnKey(evt, ITool::KEY_CHAR)) return; // Alt+enter toggles fullscreen if (evt.GetKeyCode() == WXK_RETURN && wxGetKeyState(WXK_ALT)) { if (m_ScenarioEditor.IsFullScreen()) m_ScenarioEditor.ShowFullScreen(false); else m_ScenarioEditor.ShowFullScreen(true, wxFULLSCREEN_NOBORDER | wxFULLSCREEN_NOCAPTION); return; } if (evt.GetKeyCode() == 'c') { POST_MESSAGE(CameraReset, ()); return; } int dir = 0; if (evt.GetKeyCode() == '-' || evt.GetKeyCode() == '_') dir = -1; else if (evt.GetKeyCode() == '+' || evt.GetKeyCode() == '=') dir = +1; // TODO: internationalisation (-/_ and +/= don't always share a key) if (dir) { float speed = 16.f * ScenarioEditor::GetSpeedModifier(); POST_MESSAGE(SmoothZoom, (eRenderView::GAME, speed*dir)); } else { // Slight hack: Only pass 'normal' keys; special keys will generate a KeyDown/KeyUp event instead if (evt.GetKeyCode() < 256) POST_MESSAGE(GuiCharEvent, (GetSDLKeyFromWxKeyCode(evt.GetKeyCode()), evt.GetUnicodeKey())); evt.Skip(); } } void OnKillFocus(wxFocusEvent& evt) { // Stop any scrolling, since otherwise we'll carry on forever if // we lose focus and the KeyUp events go to a different window POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::LEFT, 0.0f)); POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::RIGHT, 0.0f)); POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::FORWARDS, 0.0f)); POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::BACKWARDS, 0.0f)); POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::CLOCKWISE, 0.0f)); POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::ANTICLOCKWISE, 0.0f)); evt.Skip(); } virtual void HandleMouseEvent(wxMouseEvent& evt) { // TODO or at least to think about: When using other controls in the // editor, it's annoying that keyboard/scrollwheel no longer navigate // around the world until you click on it. // Setting focus back whenever the mouse moves over the GL window // feels like a fairly natural solution to me, since I can use // e.g. brush-editing controls normally, and then move the mouse to // see the brush outline and magically get given back full control // of the camera. if (evt.Moving()) SetFocus(); if (m_ScenarioEditor.GetToolManager().GetCurrentTool()->OnMouse(evt)) { // Mouse event has been handled by the tool, so don't try // to use it for camera motion too return; } // Global mouse event handlers (for camera motion) if (evt.GetWheelRotation()) { float speed = 16.f * ScenarioEditor::GetSpeedModifier(); POST_MESSAGE(SmoothZoom, (eRenderView::GAME, evt.GetWheelRotation() * speed / evt.GetWheelDelta())); } else { if (evt.MiddleIsDown()) { if (wxGetKeyState(WXK_CONTROL) || evt.RightIsDown()) m_MouseState = ROTATEAROUND; else m_MouseState = SCROLL; } else m_MouseState = NONE; if (m_MouseState != m_LastMouseState) { switch (m_MouseState) { case NONE: break; case SCROLL: POST_MESSAGE(Scroll, (eRenderView::GAME, eScrollType::FROM, evt.GetPosition())); break; case ROTATEAROUND: POST_MESSAGE(RotateAround, (eRenderView::GAME, eRotateAroundType::FROM, evt.GetPosition())); break; default: wxFAIL; } m_LastMouseState = m_MouseState; } else if (evt.Dragging()) { switch (m_MouseState) { case NONE: break; case SCROLL: POST_MESSAGE(Scroll, (eRenderView::GAME, eScrollType::TO, evt.GetPosition())); break; case ROTATEAROUND: POST_MESSAGE(RotateAround, (eRenderView::GAME, eRotateAroundType::TO, evt.GetPosition())); break; default: wxFAIL; } } } // Button down and double click appear to be mutually exclusive events, // meaning a second button down event is not sent before a double click if (evt.ButtonDown() || evt.ButtonDClick()) { g_Clicks = evt.ButtonDClick() ? 2 : 1; POST_MESSAGE(GuiMouseButtonEvent, (evt.GetButton(), true, evt.GetPosition(), g_Clicks)); } else if (evt.ButtonUp()) POST_MESSAGE(GuiMouseButtonEvent, (evt.GetButton(), false, evt.GetPosition(), g_Clicks)); else if (evt.GetEventType() == wxEVT_MOTION) POST_MESSAGE(GuiMouseMotionEvent, (evt.GetPosition())); } enum { NONE, SCROLL, ROTATEAROUND }; int m_MouseState, m_LastMouseState; ScenarioEditor& m_ScenarioEditor; DECLARE_EVENT_TABLE(); }; BEGIN_EVENT_TABLE(GameCanvas, Canvas) EVT_KEY_DOWN(GameCanvas::OnKeyDown) EVT_KEY_UP(GameCanvas::OnKeyUp) EVT_CHAR(GameCanvas::OnChar) EVT_KILL_FOCUS(GameCanvas::OnKillFocus) END_EVENT_TABLE() ////////////////////////////////////////////////////////////////////////// volatile bool g_FrameHasEnded; // Called from game thread ATLASDLLIMPEXP void Atlas_NotifyEndOfFrame() { g_FrameHasEnded = true; } enum { ID_Quit = 1, ID_New, ID_Open, ID_Save, ID_SaveAs, ID_ImportHeightmap, ID_Copy, ID_Paste, ID_Wireframe, ID_MessageTrace, ID_Screenshot, ID_BigScreenshot, ID_JavaScript, ID_CameraReset, ID_RenderPathFixed, ID_RenderPathShader, ID_DumpState, ID_DumpBinaryState, ID_Manual, ID_ReportBug, ID_Toolbar // must be last in the list }; BEGIN_EVENT_TABLE(ScenarioEditor, wxFrame) EVT_CLOSE(ScenarioEditor::OnClose) EVT_TIMER(wxID_ANY, ScenarioEditor::OnTimer) EVT_MENU(ID_New, ScenarioEditor::OnNew) EVT_MENU(ID_Open, ScenarioEditor::OnOpen) EVT_MENU(ID_Save, ScenarioEditor::OnSave) EVT_MENU(ID_SaveAs, ScenarioEditor::OnSaveAs) EVT_MENU(ID_ImportHeightmap, ScenarioEditor::OnImportHeightmap) EVT_MENU_RANGE(wxID_FILE1, wxID_FILE9, ScenarioEditor::OnMRUFile) EVT_MENU(ID_Quit, ScenarioEditor::OnQuit) EVT_MENU(wxID_UNDO, ScenarioEditor::OnUndo) EVT_MENU(wxID_REDO, ScenarioEditor::OnRedo) EVT_MENU(ID_Copy, ScenarioEditor::OnCopy) EVT_MENU(ID_Paste, ScenarioEditor::OnPaste) EVT_MENU(ID_Wireframe, ScenarioEditor::OnWireframe) EVT_MENU(ID_MessageTrace, ScenarioEditor::OnMessageTrace) EVT_MENU(ID_Screenshot, ScenarioEditor::OnScreenshot) EVT_MENU(ID_BigScreenshot, ScenarioEditor::OnScreenshot) EVT_MENU(ID_JavaScript, ScenarioEditor::OnJavaScript) EVT_MENU(ID_CameraReset, ScenarioEditor::OnCameraReset) EVT_MENU(ID_DumpState, ScenarioEditor::OnDumpState) EVT_MENU(ID_DumpBinaryState, ScenarioEditor::OnDumpState) EVT_MENU(ID_RenderPathFixed, ScenarioEditor::OnRenderPath) EVT_MENU(ID_RenderPathShader, ScenarioEditor::OnRenderPath) EVT_MENU(ID_Manual, ScenarioEditor::OnHelp) EVT_MENU(ID_ReportBug, ScenarioEditor::OnHelp) EVT_MENU_OPEN(ScenarioEditor::OnMenuOpen) EVT_IDLE(ScenarioEditor::OnIdle) END_EVENT_TABLE() static AtlasWindowCommandProc g_CommandProc; AtlasWindowCommandProc& ScenarioEditor::GetCommandProc() { return g_CommandProc; } ScenarioEditor::ScenarioEditor(wxWindow* parent) : wxFrame(parent, wxID_ANY, _T(""), wxDefaultPosition, wxSize(1024, 768)) , m_FileHistory(_T("Scenario Editor")) , m_ObjectSettings(g_SelectedObjects, AtlasMessage::eRenderView::GAME) , m_ToolManager(this) { // Global application initialisation: wxImage::AddHandler(new wxICOHandler); /* "osx.openfiledialog.always-show-types: Per default a wxFileDialog with wxFD_OPEN does not show a types-popup on OSX but allows the selection of files from any of the supported types. Setting this to 1 shows a wxChoice for selection (if there is more than one supported filetype)." */ wxSystemOptions::SetOption(_T("osx.openfiledialog.always-show-types"), 1); // has global effect // wxLog::SetTraceMask(wxTraceMessages); g_SelectedTexture = _T("grass1_spring"); g_SelectedTexture.NotifyObservers(); SetOpenFilename(_T("")); #if defined(__WXMSW__) m_Icon = wxIcon(_T("ICON_ScenarioEditor")); // load from atlas.rc #else { const wxString relativePath (_T("tools/atlas/icons/ScenarioEditor.ico")); wxFileName filename (relativePath, wxPATH_UNIX); filename.MakeAbsolute(Datafile::GetDataDirectory()); m_Icon = wxIcon(filename.GetFullPath(), wxBITMAP_TYPE_ICO); } #endif SetIcon(m_Icon); wxToolTip::Enable(true); wxImage::AddHandler(new wxPNGHandler); ////////////////////////////////////////////////////////////////////////// // Do some early game initialisation: // (This must happen before constructing the GL canvas.) POST_MESSAGE(Init, ()); // Wait for it to finish running Init qPing qry; qry.Post(); ////////////////////////////////////////////////////////////////////////// // Menu wxMenuBar* menuBar = new wxMenuBar; SetMenuBar(menuBar); wxMenu *menuFile = new wxMenu; menuBar->Append(menuFile, _("&File")); { menuFile->Append(ID_New, _("&New...")); menuFile->Append(ID_Open, _("&Open...")); menuFile->Append(ID_Save, _("&Save")); menuFile->Append(ID_SaveAs, _("Save &As...")); menuFile->AppendSeparator();//----------- menuFile->Append(ID_ImportHeightmap, _("&Import Heightmap...")); menuFile->AppendSeparator();//----------- menuFile->Append(ID_Quit, _("E&xit")); m_FileHistory.UseMenu(menuFile);//------- m_FileHistory.AddFilesToMenu(); } // m_menuItem_Save = menuFile->FindItem(ID_Save); // remember this item, to let it be greyed out // wxASSERT(m_menuItem_Save); wxMenu *menuEdit = new wxMenu; menuBar->Append(menuEdit, _("&Edit")); { menuEdit->Append(wxID_UNDO, _("&Undo")); menuEdit->Append(wxID_REDO, _("&Redo")); menuEdit->AppendSeparator(); menuEdit->Append(ID_Copy, _("&Copy")); menuEdit->Enable(ID_Copy, false); menuEdit->Append(ID_Paste, _("&Paste")); menuEdit->Enable(ID_Paste, false); } g_SelectedObjects.RegisterObserver(0, &ScenarioEditor::OnSelectedObjectsChange, this); GetCommandProc().SetEditMenu(menuEdit); GetCommandProc().Initialize(); wxMenu *menuMisc = new wxMenu; menuBar->Append(menuMisc, _("&Misc hacks")); { menuMisc->AppendCheckItem(ID_Wireframe, _("&Wireframe")); menuMisc->AppendCheckItem(ID_MessageTrace, _("Message debug trace")); menuMisc->Append(ID_Screenshot, _("&Screenshot")); menuMisc->Append(ID_BigScreenshot, _("Big screenshot")); menuMisc->Append(ID_JavaScript, _("&JS console")); menuMisc->Append(ID_CameraReset, _("&Reset camera")); wxMenu *menuSS = new wxMenu; menuMisc->AppendSubMenu(menuSS, _("Si&mulation state")); menuSS->Append(ID_DumpState, _("&Dump to disk")); menuSS->Append(ID_DumpBinaryState, _("Dump &binary to disk")); wxMenu *menuRP = new wxMenu; menuMisc->AppendSubMenu(menuRP, _("Render &path")); menuRP->Append(ID_RenderPathFixed, _("&Fixed function")); menuRP->Append(ID_RenderPathShader, _("&Shader (default)")); } wxMenu *menuHelp = new wxMenu; menuBar->Append(menuHelp, _("&Help")); { wxFileName helpPath (_T("tools/atlas/")); helpPath.MakeAbsolute(Datafile::GetDataDirectory()); helpPath.SetFullName("help.json"); if (wxFileExists(helpPath.GetFullPath())) { wxFFile helpFile(helpPath.GetFullPath()); wxString helpData; helpFile.ReadAll(&helpData); AtObj data = AtlasObject::LoadFromJSON(std::string(helpData)); #define ADD_HELP_ITEM(id) \ do { \ if (!data[#id].hasContent()) \ break; \ if (!data[#id]["title"].hasContent() || !data[#id]["url"].hasContent()) \ break; \ menuHelp->Append(ID_##id, _(wxString(data[#id]["title"])), _(wxString(data[#id]["tooltip"]))); \ m_HelpData.insert(std::make_pair( \ ID_##id, \ HelpItem(wxString(data[#id]["title"]), wxString(data[#id]["tooltip"]), wxString(data[#id]["url"])) \ )); \ } while (0) ADD_HELP_ITEM(Manual); ADD_HELP_ITEM(ReportBug); #undef ADD_HELP_ITEM } else wxLogError(_("'%ls' does not exist"), helpPath.GetFullPath().c_str()); } m_FileHistory.LoadFromSubDir(*wxConfigBase::Get()); m_SectionLayout.SetWindow(this); // Toolbar: // wxOSX/Cocoa 2.9 doesn't seem to like SetToolBar, so we use CreateToolBar which implicitly associates // the toolbar with the frame, and use OnCreateToolBar to construct our custom toolbar // (this should be equivalent behavior on all platforms) CreateToolBar(wxNO_BORDER|wxTB_FLAT|wxTB_HORIZONTAL, ID_Toolbar)->Realize(); // Set the default tool to be selected m_ToolManager.SetCurrentTool(_T("")); // Set up GL canvas: int glAttribList[] = { WX_GL_RGBA, WX_GL_DOUBLEBUFFER, WX_GL_DEPTH_SIZE, 24, // TODO: wx documentation doesn't say 24 is valid WX_GL_STENCIL_SIZE, 8, WX_GL_BUFFER_SIZE, 24, // color bits WX_GL_MIN_ALPHA, 8, // alpha bits 0 }; Canvas* canvas = new GameCanvas(*this, m_SectionLayout.GetCanvasParent(), glAttribList); m_SectionLayout.SetCanvas(canvas); // Set up sidebars: m_SectionLayout.Build(*this); #if defined(__WXMSW__) // The canvas' context gets made current on creation; but it can only be // current for one thread at a time, and it needs to be current for the // thread that is doing the draw calls, so disable it for this one. wglMakeCurrent(NULL, NULL); #elif defined(__WXGTK__) || defined(__WXOSX__) || defined(__WXMAC__) // Need to make sure the canvas is realised, so that its context is valid // this solves the "invalid drawable" error Show(true); Raise(); #endif #ifdef __WXGTK__ // TODO: wxSafeYield causes issues on wxOSX 2.9, is it necessary? wxSafeYield(); #endif // Send setup messages to game engine: POST_MESSAGE(InitSDL, ()); POST_MESSAGE(SetCanvas, (static_cast(canvas), canvas->GetClientSize().GetWidth(), canvas->GetClientSize().GetHeight())); POST_MESSAGE(InitGraphics, ()); canvas->InitSize(); // Start with a blank map (so that the editor can assume there's always // a valid map loaded) POST_MESSAGE(LoadMap, (_T("maps/scenarios/_default.xml"))); POST_MESSAGE(SimPlay, (0.f, false)); // Select the initial sidebar (after the map has loaded) m_SectionLayout.SelectPage(_T("MapSidebar")); // Wait for blank map qry.Post(); POST_MESSAGE(RenderEnable, (eRenderView::GAME)); // Set up a timer to make sure tool-updates happen frequently (in addition // to the idle handler (which makes them happen more frequently if there's nothing // else to do)) m_Timer.SetOwner(this); m_Timer.Start(20); } wxToolBar* ScenarioEditor::OnCreateToolBar(long style, wxWindowID id, const wxString& WXUNUSED(name)) { ToolButtonBar* toolbar = new ToolButtonBar(m_ToolManager, this, &m_SectionLayout, id, style); // TODO: configurable small vs large icon images // (button label; tooltip text; image; internal tool name; section to switch to) toolbar->AddToolButton(_("Default"), _("Default"), _T("default.png"), _T(""), _T("")); toolbar->AddToolButton(_("Move"), _("Move/rotate object"), _T("moveobject.png"), _T("TransformObject"), _T("")/*_T("ObjectSidebar")*/); toolbar->AddToolButton(_("Elevation"), _("Alter terrain elevation"), _T("alterelevation.png"), _T("AlterElevation"), _T("")/*_T("TerrainSidebar")*/); toolbar->AddToolButton(_("Smooth"), _("Smooth terrain elevation"), _T("smoothelevation.png"), _T("SmoothElevation"), _T("")/*_T("TerrainSidebar")*/); toolbar->AddToolButton(_("Flatten"), _("Flatten terrain elevation"), _T("flattenelevation.png"), _T("FlattenElevation"), _T("")/*_T("TerrainSidebar")*/); toolbar->AddToolButton(_("Paint Terrain"), _("Paint terrain texture"), _T("paintterrain.png"), _T("PaintTerrain"), _T("")/*_T("TerrainSidebar")*/); toolbar->AddToolButton(_("Move"), _("Move cinema path nodes"), _T("movepath.png"), _T("TransformPath"), _T("")/*_T("CinemaSidebar")*/); return toolbar; } float ScenarioEditor::GetSpeedModifier() { if (wxGetKeyState(WXK_SHIFT) && wxGetKeyState(WXK_CONTROL)) return 1.f/64.f; else if (wxGetKeyState(WXK_CONTROL)) return 1.f/4.f; else if (wxGetKeyState(WXK_SHIFT)) return 4.f; else return 1.f; } void ScenarioEditor::OnClose(wxCloseEvent& event) { if (event.CanVeto() && GetCommandProc().IsDirty()) { if (wxMessageBox(_T("You have unsaved changes. Are you sure you want to quit?"), _T("Discard unsaved changes?"), wxICON_QUESTION | wxYES_NO) != wxYES) { event.Veto(); return; } } m_ToolManager.SetCurrentTool(_T("")); m_FileHistory.SaveToSubDir(*wxConfigBase::Get()); POST_MESSAGE(Shutdown, ()); qExit().Post(); // blocks until engine has noticed the message, so we won't be // destroying the GLCanvas while it's still rendering Destroy(); } static void UpdateTool(ToolManager& toolManager) { // Don't keep posting events if the game can't keep up if (g_FrameHasEnded) { g_FrameHasEnded = false; // (thread safety doesn't matter here) // TODO: Smoother timing stuff? static double last = g_Timer.GetTime(); double time = g_Timer.GetTime(); toolManager.GetCurrentTool()->OnTick(time-last); last = time; } } void ScenarioEditor::OnTimer(wxTimerEvent&) { UpdateTool(m_ToolManager); } void ScenarioEditor::OnIdle(wxIdleEvent&) { UpdateTool(m_ToolManager); } void ScenarioEditor::OnQuit(wxCommandEvent&) { Close(); } void ScenarioEditor::OnUndo(wxCommandEvent&) { GetCommandProc().Undo(); } void ScenarioEditor::OnRedo(wxCommandEvent&) { GetCommandProc().Redo(); } void ScenarioEditor::OnCopy(wxCommandEvent& WXUNUSED(event)) { if (GetToolManager().GetCurrentToolName() == _T("TransformObject")) GetToolManager().GetCurrentTool()->OnCommand(_T("copy"), NULL); } void ScenarioEditor::OnPaste(wxCommandEvent& WXUNUSED(event)) { if (GetToolManager().GetCurrentToolName() != _T("TransformObject")) GetToolManager().SetCurrentTool(_T("TransformObject"), NULL); GetToolManager().GetCurrentTool()->OnCommand(_T("paste"), NULL); } ////////////////////////////////////////////////////////////////////////// void ScenarioEditor::OnNew(wxCommandEvent& WXUNUSED(event)) { if (wxMessageBox(_("Discard current map and start blank new map?"), _("New map"), wxOK|wxCANCEL|wxICON_QUESTION, this) == wxOK) OpenFile(_T(""), _T("maps/scenarios/_default.xml")); } bool ScenarioEditor::OpenFile(const wxString& name, const wxString& filename) { wxBusyInfo busy(_("Loading ") + name); wxBusyCursor busyc; AtlasMessage::qVFSFileExists qry(filename.wc_str()); qry.Post(); if (!qry.exists) return false; // Deactivate tools, so they don't carry forwards into the new CWorld // and crash. m_ToolManager.SetCurrentTool(_T("")); // TODO: clear the undo buffer, etc std::wstring map(filename.wc_str()); POST_MESSAGE(LoadMap, (map)); SetOpenFilename(name); { // Wait for it to load, while the wxBusyInfo is telling the user that we're doing that - qPing qry; - qry.Post(); + qPing pingQuery; + pingQuery.Post(); } NotifyOnMapReload(); GetCommandProc().ClearCommands(); return true; // TODO: Make this a non-undoable command } // TODO (eventually): replace all this file-handling stuff with the Workspace Editor void ScenarioEditor::OnOpen(wxCommandEvent& WXUNUSED(event)) { if (DiscardChangesDialog()) return; MapDialog dlg (NULL, MAPDIALOG_OPEN, m_Icon); if (dlg.ShowModal() == wxID_OK) { wxString filePath = dlg.GetSelectedFilePath(); if (!OpenFile(filePath, filePath)) wxLogError(_("Map '%ls' does not exist"), filePath.c_str()); } // TODO: Make this a non-undoable command } void ScenarioEditor::OnImportHeightmap(wxCommandEvent& WXUNUSED(event)) { if (DiscardChangesDialog()) return; wxFileDialog dlg (NULL, wxFileSelectorPromptStr, _T(""), _T(""), _T("Valid image files (*.png, *.bmp)|*.png;*.bmp|All files (*.*)|*.*"), wxFD_OPEN); // Set default filter dlg.SetFilterIndex(0); if (dlg.ShowModal() != wxID_OK) return; OpenFile(_T(""), _T("maps/scenarios/_default.xml")); std::wstring image(dlg.GetPath().wc_str()); POST_MESSAGE(ImportHeightmap, (image)); // TODO: Make this a non-undoable command } void ScenarioEditor::OnMRUFile(wxCommandEvent& event) { wxString filename(m_FileHistory.GetHistoryFile(event.GetId() - wxID_FILE1)); // Handle old MRU filenames if (filename.Mid(0, 5) != _T("maps/")) { filename = L"maps/scenarios/" + filename; m_FileHistory.RemoveFileFromHistory(event.GetId() - wxID_FILE1); } if (DiscardChangesDialog()) return; if (!OpenFile(filename, filename)) { // Missing or invalid - warn and remove from MRU wxLogError(_("Map '%ls' does not exist"), filename.c_str()); m_FileHistory.RemoveFileFromHistory(event.GetId() - wxID_FILE1); } } void ScenarioEditor::OnSave(wxCommandEvent& event) { if (m_OpenFilename.IsEmpty()) { OnSaveAs(event); } else { wxBusyInfo busy(_("Saving ") + m_OpenFilename); wxBusyCursor busyc; // Deactivate tools, so things like unit previews don't get saved. // (TODO: Would be nicer to leave the tools active, and just not save // the preview units.) m_ToolManager.SetCurrentTool(_T("")); std::wstring map(m_OpenFilename.wc_str()); POST_MESSAGE(SaveMap, (map)); // Wait for it to finish saving qPing qry; qry.Post(); GetCommandProc().MarkAsSaved(); } } void ScenarioEditor::OnSaveAs(wxCommandEvent& WXUNUSED(event)) { MapDialog dlg(NULL, MAPDIALOG_SAVE, m_Icon); if (dlg.ShowModal() == wxID_OK) { wxString filePath(dlg.GetSelectedFilePath()); wxBusyInfo busy(_("Saving ") + filePath); wxBusyCursor busyc; m_ToolManager.SetCurrentTool(_T("")); std::wstring map(filePath.wc_str()); POST_MESSAGE(SaveMap, (map)); SetOpenFilename(filePath); // Wait for it to finish saving qPing qry; qry.Post(); GetCommandProc().MarkAsSaved(); } } void ScenarioEditor::SetOpenFilename(const wxString& filename) { SetTitle(wxString::Format(_("Atlas - Scenario Editor - %s"), (filename.IsEmpty() ? wxString(_("(untitled)")) : filename).c_str())); m_OpenFilename = filename; if (! filename.IsEmpty()) m_FileHistory.AddFileToHistory(filename); } void ScenarioEditor::NotifyOnMapReload() { m_SectionLayout.OnMapReload(); // Notify observers, here so it's independent of individual panels m_MapSettings.NotifyObservers(); } bool ScenarioEditor::DiscardChangesDialog() { return GetCommandProc().IsDirty() && wxMessageBox(_T("You have unsaved changes. Are you sure you want to open another map?"), _T("Discard unsaved changes?"), wxICON_QUESTION | wxYES_NO) != wxYES; } ////////////////////////////////////////////////////////////////////////// void ScenarioEditor::OnWireframe(wxCommandEvent& event) { POST_MESSAGE(RenderStyle, (event.IsChecked())); } void ScenarioEditor::OnMessageTrace(wxCommandEvent& event) { POST_MESSAGE(MessageTrace, (event.IsChecked())); } void ScenarioEditor::OnScreenshot(wxCommandEvent& event) { switch (event.GetId()) { case ID_BigScreenshot: POST_MESSAGE(Screenshot, (true, 10)); break; case ID_Screenshot: POST_MESSAGE(Screenshot, (false, 0)); break; } } void ScenarioEditor::OnJavaScript(wxCommandEvent& WXUNUSED(event)) { wxString cmd = ::wxGetTextFromUser(_T(""), _("JS command"), _T(""), this); if (cmd.IsEmpty()) return; POST_MESSAGE(JavaScript, ((std::wstring)cmd.wc_str())); } void ScenarioEditor::OnCameraReset(wxCommandEvent& WXUNUSED(event)) { POST_MESSAGE(CameraReset, ()); } void ScenarioEditor::OnRenderPath(wxCommandEvent& event) { switch (event.GetId()) { case ID_RenderPathFixed: POST_MESSAGE(SetViewParamS, (eRenderView::GAME, L"renderpath", L"fixed")); break; case ID_RenderPathShader: POST_MESSAGE(SetViewParamS, (eRenderView::GAME, L"renderpath", L"shader")); break; } } void ScenarioEditor::OnDumpState(wxCommandEvent& event) { wxDateTime time = wxDateTime::Now(); wxString filename; bool doBinary = false; switch (event.GetId()) { case ID_DumpState: filename = wxString::Format(_T("sim-dump-%d.txt"), time.GetTicks()); break; case ID_DumpBinaryState: doBinary = true; filename = wxString::Format(_T("sim-dump-%d.dat"), time.GetTicks()); break; } qSimStateDebugDump q(doBinary); q.Post(); std::wstring dump = *q.dump; wxString state(dump.c_str()); wxFFile file(filename.c_str(), _T("w")); if (file.IsOpened() && !file.Error()) { file.Write(state); file.Close(); } else { wxLogError(_("Error writing to file '%ls'"), filename.c_str()); } } void ScenarioEditor::OnSelectedObjectsChange(const std::vector& selectedObjects) { GetMenuBar()->Enable(ID_Copy, !selectedObjects.empty()); } void ScenarioEditor::OnHelp(wxCommandEvent& event) { std::map::const_iterator it = m_HelpData.find(event.GetId()); if (it == m_HelpData.end()) return; wxMessageDialog* dialog = new wxMessageDialog( nullptr, _("Do you want to open '" + it->second.m_URL + "'?"), _("Atlas"), wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION ); if (dialog->ShowModal() == wxID_YES) wxLaunchDefaultBrowser(it->second.m_URL); } void ScenarioEditor::OnMenuOpen(wxMenuEvent& event) { // This could be done far more elegantly if wxMenuItem had changeable id. wxMenu* pasteMenuItem = NULL; event.GetMenu()->FindItem(ID_Paste, &pasteMenuItem); GetMenuBar()->Enable(ID_Paste, false); if (!pasteMenuItem) return; wxString content; if (wxTheClipboard->Open()) { if (wxTheClipboard->IsSupported(wxDF_TEXT)) { wxTextDataObject data; wxTheClipboard->GetData(data); content = data.GetText(); } wxTheClipboard->Close(); } if (content.empty()) return; wxInputStream* is = new wxStringInputStream(content); wxXmlDocument doc; { wxLogNull stopComplaining; static_cast(stopComplaining); if (!doc.Load(*is)) return; } wxXmlNode* root = doc.GetRoot(); if (!root || root->GetName() != wxT("Entities")) return; GetMenuBar()->Enable(ID_Paste, true); } ////////////////////////////////////////////////////////////////////////// Position::Position(const wxPoint& pt) : type(1) { type1.x = pt.x; type1.y = pt.y; } ////////////////////////////////////////////////////////////////////////// /* Disabled (and should be removed if it turns out to be unnecessary) - see MessagePasserImpl.cpp for information static void QueryCallback() { // If this thread completely blocked on the semaphore inside Query, it would // never respond to window messages, and the system deadlocks if the // game tries to display an assertion failure dialog. (See // WaitForSingleObject on MSDN.) // So, this callback is called occasionally, and gives wx a change to // handle messages. // This is kind of like wxYield, but without the ProcessPendingEvents - // it's enough to make Windows happy and stop deadlocking, without actually // calling the event handlers (which could lead to nasty recursion) // while (wxEventLoop::GetActive()->Pending()) // wxEventLoop::GetActive()->Dispatch(); // Oh dear, we can't use that either - it (at least in wx 2.6.3) still // processes messages, which causes reentry into various things that we // don't want to be reentrant. So do it all manually, accepting Windows // messages and sticking them on a list for later processing (in a custom // event loop class): // (TODO: Rethink this entire process on Linux) // (Alt TODO: Could we make the game never pop up windows (or use the Win32 // GUI in any other way) when it's running under Atlas, so we wouldn't need // to do any message processing here at all?) #ifdef _WIN32 AtlasEventLoop* evtLoop = (AtlasEventLoop*)wxEventLoop::GetActive(); // evtLoop might be NULL, particularly if we're still initialising windows // and haven't got into the normal event loop yet. But we'd have to process // messages anyway, to avoid the deadlocks that this is for. So, don't bother // with that and just crash instead. // (Maybe it could be solved better by constructing/finding an event loop // object here and setting it as the global one, assuming it's not overwritten // later by wx.) while (evtLoop->Pending()) { // Based on src/msw/evtloop.cpp's wxEventLoop::Dispatch() MSG msg; BOOL rc = ::GetMessage(&msg, (HWND) NULL, 0, 0); if (rc == 0) { // got WM_QUIT return; } if (rc == -1) { wxLogLastError(wxT("GetMessage")); return; } // Our special bits: if (msg.message == WM_PAINT) { // "GetMessage does not remove WM_PAINT messages from the queue. // The messages remain in the queue until processed." // So let's process them, to avoid infinite loops... PAINTSTRUCT paint; ::BeginPaint(msg.hwnd, &paint); ::EndPaint(msg.hwnd, &paint); // Remember that some painting was needed - we'll just repaint // the whole screen when this is finished. evtLoop->NeedsPaint(); } else { // Add this message to a queue for later processing. (That's // probably kind of valid, at least in most cases.) MSG* pMsg = new MSG(msg); evtLoop->AddMessage(pMsg); } } #endif } */ void QueryMessage::Post() { // g_MessagePasser->Query(this, &QueryCallback); g_MessagePasser->Query(this, NULL); } Index: ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/Object.cpp =================================================================== --- ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/Object.cpp (revision 22160) +++ ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/Object.cpp (revision 22161) @@ -1,644 +1,643 @@ -/* Copyright (C) 2017 Wildfire Games. +/* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "Object.h" #include "Buttons/ToolButton.h" #include "General/Datafile.h" #include "ScenarioEditor/ScenarioEditor.h" #include "ScenarioEditor/Tools/Common/ObjectSettings.h" #include "ScenarioEditor/Tools/Common/MiscState.h" #include "VariationControl.h" #include "GameInterface/Messages.h" #include "wx/busyinfo.h" enum { ID_ObjectType = 1, ID_ObjectFilter, ID_PlayerSelect, ID_SelectObject, ID_ToggleViewer, ID_ViewerWireframe, ID_ViewerMove, ID_ViewerGround, ID_ViewerWater, ID_ViewerShadows, ID_ViewerPolyCount, ID_ViewerAnimation, ID_ViewerBoundingBox, ID_ViewerAxesMarker, ID_ViewerPropPoints, ID_ViewerPlay, ID_ViewerPause, ID_ViewerSlow }; // Helper function for adding tooltips static wxWindow* Tooltipped(wxWindow* window, const wxString& tip) { window->SetToolTip(tip); return window; } class ObjectBottomBar : public wxPanel { public: ObjectBottomBar( wxWindow* parent, Observable& objectSettings, Observable& mapSettings, ObjectSidebarImpl* p ); void OnFirstDisplay(); void ShowActorViewer(bool show); void OnSelectedObjectsChange(const std::vector& selectedObjects); private: void OnViewerSetting(wxCommandEvent& evt); void OnSelectAnim(wxCommandEvent& evt); void OnSpeed(wxCommandEvent& evt); bool m_ViewerWireframe; bool m_ViewerMove; bool m_ViewerGround; bool m_ViewerWater; bool m_ViewerShadows; bool m_ViewerPolyCount; bool m_ViewerBoundingBox; bool m_ViewerAxesMarker; int m_ViewerPropPointsMode; // 0 disabled, 1 for point markers, 2 for point markers + axes wxPanel* m_ViewerPanel; wxScrolledWindow* m_TemplateNames; ObjectSidebarImpl* p; DECLARE_EVENT_TABLE(); }; struct ObjectSidebarImpl { ObjectSidebarImpl(ScenarioEditor& scenarioEditor) : m_ObjectListBox(NULL), m_ActorViewerActive(false), m_ActorViewerEntity(_T("actor|structures/fndn_1x1.xml")), m_ActorViewerAnimation(_T("idle")), m_ActorViewerSpeed(0.f), m_ObjectSettings(scenarioEditor.GetObjectSettings()) { } wxListBox* m_ObjectListBox; std::vector m_Objects; ObservableScopedConnection m_ToolConn; bool m_ActorViewerActive; wxString m_ActorViewerEntity; wxString m_ActorViewerAnimation; float m_ActorViewerSpeed; Observable& m_ObjectSettings; void ActorViewerPostToGame() { POST_MESSAGE(SetActorViewer, ((std::wstring)m_ActorViewerEntity.wc_str(), (std::wstring)m_ActorViewerAnimation.wc_str(), m_ObjectSettings.GetPlayerID(), m_ActorViewerSpeed, false)); } }; ObjectSidebar::ObjectSidebar( ScenarioEditor& scenarioEditor, wxWindow* sidebarContainer, wxWindow* bottomBarContainer ) : Sidebar(scenarioEditor, sidebarContainer, bottomBarContainer), - p(new ObjectSidebarImpl(scenarioEditor)) + m_Impl(new ObjectSidebarImpl(scenarioEditor)) { wxSizer* scrollSizer = new wxBoxSizer(wxVERTICAL); wxScrolledWindow* scrolledWindow = new wxScrolledWindow(this); scrolledWindow->SetScrollRate(10, 10); scrolledWindow->SetSizer(scrollSizer); m_MainSizer->Add(scrolledWindow, wxSizerFlags().Proportion(1).Expand()); wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(new wxStaticText(scrolledWindow, wxID_ANY, _("Filter")), wxSizerFlags().Align(wxALIGN_CENTER)); sizer->Add( Tooltipped( new wxTextCtrl(scrolledWindow, ID_ObjectFilter), _("Enter text to filter object list") ), wxSizerFlags().Expand().Proportion(1) ); scrollSizer->Add(sizer, wxSizerFlags().Expand()); scrollSizer->AddSpacer(3); // ------------------------------------------------------------------------------------------ wxArrayString strings; strings.Add(_("Entities")); strings.Add(_("Actors (all)")); wxChoice* objectType = new wxChoice(scrolledWindow, ID_ObjectType, wxDefaultPosition, wxDefaultSize, strings); objectType->SetSelection(0); scrollSizer->Add(objectType, wxSizerFlags().Expand()); scrollSizer->AddSpacer(3); // ------------------------------------------------------------------------------------------ - p->m_ObjectListBox = new wxListBox(scrolledWindow, ID_SelectObject, wxDefaultPosition, wxDefaultSize, 0, NULL, wxLB_SINGLE|wxLB_HSCROLL); - scrollSizer->Add(p->m_ObjectListBox, wxSizerFlags().Proportion(1).Expand()); + m_Impl->m_ObjectListBox = new wxListBox(scrolledWindow, ID_SelectObject, wxDefaultPosition, wxDefaultSize, 0, nullptr, wxLB_SINGLE|wxLB_HSCROLL); + scrollSizer->Add(m_Impl->m_ObjectListBox, wxSizerFlags().Proportion(1).Expand()); scrollSizer->AddSpacer(3); // ------------------------------------------------------------------------------------------ scrollSizer->Add(new wxButton(scrolledWindow, ID_ToggleViewer, _("Switch to Actor Viewer")), wxSizerFlags().Expand()); // ------------------------------------------------------------------------------------------ m_BottomBar = new ObjectBottomBar( bottomBarContainer, scenarioEditor.GetObjectSettings(), scenarioEditor.GetMapSettings(), - p + m_Impl ); - p->m_ToolConn = scenarioEditor.GetToolManager().GetCurrentTool().RegisterObserver(0, &ObjectSidebar::OnToolChange, this); + m_Impl->m_ToolConn = scenarioEditor.GetToolManager().GetCurrentTool().RegisterObserver(0, &ObjectSidebar::OnToolChange, this); } ObjectSidebar::~ObjectSidebar() { - delete p; + delete m_Impl; } void ObjectSidebar::OnToolChange(ITool* tool) { if (wxString(tool->GetClassInfo()->GetClassName()) == _T("ActorViewerTool")) { - p->m_ActorViewerActive = true; - p->ActorViewerPostToGame(); + m_Impl->m_ActorViewerActive = true; + m_Impl->ActorViewerPostToGame(); wxDynamicCast(FindWindow(ID_ToggleViewer), wxButton)->SetLabel(_("Return to game view")); } else { - p->m_ActorViewerActive = false; + m_Impl->m_ActorViewerActive = false; wxDynamicCast(FindWindow(ID_ToggleViewer), wxButton)->SetLabel(_("Switch to Actor Viewer")); } - static_cast(m_BottomBar)->ShowActorViewer(p->m_ActorViewerActive); + static_cast(m_BottomBar)->ShowActorViewer(m_Impl->m_ActorViewerActive); } void ObjectSidebar::OnFirstDisplay() { static_cast(m_BottomBar)->OnFirstDisplay(); wxBusyInfo busy (_("Loading list of objects")); // Get the list of objects from the game AtlasMessage::qGetObjectsList qry; qry.Post(); - p->m_Objects = *qry.objects; + m_Impl->m_Objects = *qry.objects; // Display first group of objects FilterObjects(); } void ObjectSidebar::FilterObjects() { int filterType = wxDynamicCast(FindWindow(ID_ObjectType), wxChoice)->GetSelection(); wxString filterName = wxDynamicCast(FindWindow(ID_ObjectFilter), wxTextCtrl)->GetValue(); - p->m_ObjectListBox->Freeze(); - p->m_ObjectListBox->Clear(); - for (std::vector::iterator it = p->m_Objects.begin(); it != p->m_Objects.end(); ++it) + m_Impl->m_ObjectListBox->Freeze(); + m_Impl->m_ObjectListBox->Clear(); + for (std::vector::iterator it = m_Impl->m_Objects.begin(); it != m_Impl->m_Objects.end(); ++it) { if (it->type == filterType) { wxString id = it->id.c_str(); wxString name = it->name.c_str(); if (name.Lower().Find(filterName.Lower()) != wxNOT_FOUND) { - p->m_ObjectListBox->Append(name, new wxStringClientData(id)); + m_Impl->m_ObjectListBox->Append(name, new wxStringClientData(id)); } } } - p->m_ObjectListBox->Thaw(); + m_Impl->m_ObjectListBox->Thaw(); } void ObjectSidebar::OnToggleViewer(wxCommandEvent& WXUNUSED(evt)) { - if (p->m_ActorViewerActive) + if (m_Impl->m_ActorViewerActive) { m_ScenarioEditor.GetToolManager().SetCurrentTool(_T(""), NULL); } else { m_ScenarioEditor.GetToolManager().SetCurrentTool(_T("ActorViewerTool"), NULL); } } void ObjectSidebar::OnSelectType(wxCommandEvent& WXUNUSED(evt)) { FilterObjects(); } void ObjectSidebar::OnSelectObject(wxCommandEvent& evt) { if (evt.GetInt() < 0) return; wxString id = static_cast(evt.GetClientObject())->GetData(); // Always update the actor viewer's state even if it's inactive, // so it will be correct when first enabled - p->m_ActorViewerEntity = id; + m_Impl->m_ActorViewerEntity = id; - if (p->m_ActorViewerActive) + if (m_Impl->m_ActorViewerActive) { - p->ActorViewerPostToGame(); + m_Impl->ActorViewerPostToGame(); } else { // On selecting an object, enable the PlaceObject tool with this object m_ScenarioEditor.GetToolManager().SetCurrentTool(_T("PlaceObject"), &id); } } void ObjectSidebar::OnSelectFilter(wxCommandEvent& WXUNUSED(evt)) { FilterObjects(); } BEGIN_EVENT_TABLE(ObjectSidebar, Sidebar) EVT_CHOICE(ID_ObjectType, ObjectSidebar::OnSelectType) EVT_TEXT(ID_ObjectFilter, ObjectSidebar::OnSelectFilter) EVT_LISTBOX(ID_SelectObject, ObjectSidebar::OnSelectObject) EVT_BUTTON(ID_ToggleViewer, ObjectSidebar::OnToggleViewer) END_EVENT_TABLE(); ////////////////////////////////////////////////////////////////////////// class PlayerComboBox : public wxComboBox { public: PlayerComboBox(wxWindow* parent, Observable& objectSettings, Observable& mapSettings) : wxComboBox(parent, ID_PlayerSelect, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, 0, wxCB_READONLY) , m_ObjectSettings(objectSettings), m_MapSettings(mapSettings) { m_ObjectConn = m_ObjectSettings.RegisterObserver(1, &PlayerComboBox::OnObjectSettingsChange, this); m_MapConn = m_MapSettings.RegisterObserver(1, &PlayerComboBox::OnMapSettingsChange, this); } void SetPlayers(wxArrayString& names) { m_Players = names; OnMapSettingsChange(m_MapSettings); } private: ObservableScopedConnection m_ObjectConn; Observable& m_ObjectSettings; ObservableScopedConnection m_MapConn; Observable& m_MapSettings; wxArrayString m_Players; void SetSelection(int playerID) { // This control may not be loaded yet (before first display) // or may have less items than we expect, which could cause // an assertion failure, so handle that here if ((unsigned int)playerID < GetCount()) { wxComboBox::SetSelection(playerID); } else { // Invalid selection wxComboBox::SetSelection(wxNOT_FOUND); } } void OnObjectSettingsChange(const ObjectSettings& settings) { SetSelection(settings.GetPlayerID()); } void OnMapSettingsChange(const AtObj& settings) { // Reload displayed player names Clear(); size_t numPlayers = settings["PlayerData"]["item"].count(); for (size_t i = 0; i <= numPlayers && i < m_Players.Count(); ++i) { Append(m_Players[i]); } SetSelection(m_ObjectSettings.GetPlayerID()); } void OnSelect(wxCommandEvent& evt) { m_ObjectSettings.SetPlayerID(evt.GetInt()); m_ObjectSettings.NotifyObserversExcept(m_ObjectConn); } DECLARE_EVENT_TABLE(); }; BEGIN_EVENT_TABLE(PlayerComboBox, wxComboBox) EVT_COMBOBOX(wxID_ANY, PlayerComboBox::OnSelect) END_EVENT_TABLE(); ////////////////////////////////////////////////////////////////////////// ObjectBottomBar::ObjectBottomBar( wxWindow* parent, Observable& objectSettings, Observable& mapSettings, ObjectSidebarImpl* p ) : wxPanel(parent, wxID_ANY), p(p) { m_ViewerWireframe = false; m_ViewerMove = false; m_ViewerGround = true; m_ViewerWater = false; m_ViewerShadows = true; m_ViewerPolyCount = false; m_ViewerBoundingBox = false; m_ViewerAxesMarker = false; m_ViewerPropPointsMode = 0; wxSizer* mainSizer = new wxBoxSizer(wxHORIZONTAL); // --- viewer options panel ------------------------------------------------------------------------------- m_ViewerPanel = new wxPanel(this, wxID_ANY); wxSizer* viewerSizer = new wxBoxSizer(wxHORIZONTAL); wxSizer* viewerButtonsSizer = new wxStaticBoxSizer(wxHORIZONTAL, m_ViewerPanel, _("Display settings")); { wxSizer* viewerButtonsLeft = new wxBoxSizer(wxVERTICAL); viewerButtonsLeft->SetMinSize(110, -1); viewerButtonsLeft->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerWireframe, _("Wireframe")), _("Toggle wireframe / solid rendering")), wxSizerFlags().Expand()); viewerButtonsLeft->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerMove, _("Move")), _("Toggle movement along ground when playing walk/run animations")), wxSizerFlags().Expand()); viewerButtonsLeft->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerGround, _("Ground")), _("Toggle the ground plane")), wxSizerFlags().Expand()); // TODO: disabled until http://trac.wildfiregames.com/ticket/2692 is fixed wxButton* waterButton = new wxButton(m_ViewerPanel, ID_ViewerWater, _("Water")); waterButton->Enable(false); viewerButtonsLeft->Add(Tooltipped(waterButton, _("Toggle the water plane")), wxSizerFlags().Expand()); viewerButtonsLeft->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerShadows, _("Shadows")), _("Toggle shadow rendering")), wxSizerFlags().Expand()); viewerButtonsLeft->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerPolyCount, _("Poly count")), _("Toggle polygon-count statistics - turn off ground and shadows for more useful data")), wxSizerFlags().Expand()); wxSizer* viewerButtonsRight = new wxBoxSizer(wxVERTICAL); viewerButtonsRight->SetMinSize(110,-1); viewerButtonsRight->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerBoundingBox, _("Bounding Boxes")), _("Toggle bounding boxes")), wxSizerFlags().Expand()); viewerButtonsRight->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerAxesMarker, _("Axes Marker")), _("Toggle the axes marker (R=X, G=Y, B=Z)")), wxSizerFlags().Expand()); viewerButtonsRight->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerPropPoints, _("Prop Points")), _("Toggle prop points (works best in wireframe mode)")), wxSizerFlags().Expand()); viewerButtonsSizer->Add(viewerButtonsLeft, wxSizerFlags().Expand()); viewerButtonsSizer->Add(viewerButtonsRight, wxSizerFlags().Expand()); } viewerSizer->Add(viewerButtonsSizer, wxSizerFlags().Expand()); viewerSizer->AddSpacer(3); // --- animations panel ------------------------------------------------------------------------------- wxSizer* viewerAnimSizer = new wxStaticBoxSizer(wxVERTICAL, m_ViewerPanel, _("Animation")); // TODO: this list should come from the actor wxArrayString animChoices; AtObj anims (Datafile::ReadList("animations")); for (AtIter a = anims["item"]; a.defined(); ++a) { animChoices.Add(wxString(*a)); } wxChoice* viewerAnimSelector = new wxChoice(m_ViewerPanel, ID_ViewerAnimation, wxDefaultPosition, wxDefaultSize, animChoices); viewerAnimSelector->SetSelection(0); viewerAnimSizer->Add(viewerAnimSelector, wxSizerFlags().Expand()); wxSizer* viewerAnimSpeedSizer = new wxBoxSizer(wxHORIZONTAL); viewerAnimSpeedSizer->Add(new wxButton(m_ViewerPanel, ID_ViewerPlay, _("Play"), wxDefaultPosition, wxSize(50, -1)), wxSizerFlags().Expand()); viewerAnimSpeedSizer->Add(new wxButton(m_ViewerPanel, ID_ViewerPause, _("Pause"), wxDefaultPosition, wxSize(50, -1)), wxSizerFlags().Expand()); viewerAnimSpeedSizer->Add(new wxButton(m_ViewerPanel, ID_ViewerSlow, _("Slow"), wxDefaultPosition, wxSize(50, -1)), wxSizerFlags().Expand()); viewerAnimSizer->Add(viewerAnimSpeedSizer); viewerSizer->Add(viewerAnimSizer, wxSizerFlags().Expand()); // --- add viewer-specific options ------------------------------------------------------------------------------- m_ViewerPanel->SetSizer(viewerSizer); mainSizer->Add(m_ViewerPanel, wxSizerFlags().Expand()); m_ViewerPanel->Layout(); // prevents strange visibility glitch of the animation buttons on my machine (Vista 32-bit SP1) -- vtsj m_ViewerPanel->Show(false); // --- add player/variation selection ------------------------------------------------------------------------------- wxSizer* playerSelectionSizer = new wxBoxSizer(wxHORIZONTAL); wxSizer* playerVariationSizer = new wxBoxSizer(wxVERTICAL); // TODO: make this a wxChoice instead wxComboBox* playerSelect = new PlayerComboBox(this, objectSettings, mapSettings); playerSelectionSizer->Add(new wxStaticText(this, wxID_ANY, _("Player:")), wxSizerFlags().Align(wxALIGN_CENTER)); playerSelectionSizer->AddSpacer(3); playerSelectionSizer->Add(playerSelect); playerVariationSizer->Add(playerSelectionSizer); playerVariationSizer->AddSpacer(3); wxWindow* variationSelect = new VariationControl(this, objectSettings); variationSelect->SetMinSize(wxSize(160, -1)); wxSizer* variationSizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Variation")); variationSizer->Add(variationSelect, wxSizerFlags().Proportion(1).Expand()); playerVariationSizer->Add(variationSizer, wxSizerFlags().Proportion(1)); mainSizer->AddSpacer(3); mainSizer->Add(playerVariationSizer, wxSizerFlags().Expand()); // ---------------------------------------------------------------------------------- // --- display template name wxSizer* displaySizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Selected entities")); m_TemplateNames = new wxScrolledWindow(this); m_TemplateNames->SetMinSize(wxSize(250, -1)); m_TemplateNames->SetScrollRate(0, 5); wxSizer* scrollwindowSizer = new wxBoxSizer(wxVERTICAL); m_TemplateNames->SetSizer(scrollwindowSizer); displaySizer->Add(m_TemplateNames, wxSizerFlags().Proportion(1).Expand()); m_TemplateNames->Layout(); mainSizer->AddSpacer(3); mainSizer->Add(displaySizer, wxSizerFlags().Proportion(1).Expand()); g_SelectedObjects.RegisterObserver(0, &ObjectBottomBar::OnSelectedObjectsChange, this); SetSizer(mainSizer); } static wxControl* CreateTemplateNameObject(wxWindow* parent, const std::string& templateName, int counterTemplate) { wxString idTemplate(wxString::FromUTF8(templateName.c_str())); if (counterTemplate > 1) idTemplate.Append(wxString::Format(wxT(" (%i)"), counterTemplate)); wxStaticText* templateNameObject = new wxStaticText(parent, wxID_ANY, idTemplate); return templateNameObject; } void ObjectBottomBar::OnSelectedObjectsChange(const std::vector& selectedObjects) { Freeze(); wxSizer* sizer = m_TemplateNames->GetSizer(); sizer->Clear(true); AtlasMessage::qGetSelectedObjectsTemplateNames objectTemplatesName(selectedObjects); objectTemplatesName.Post(); std::vector names = *objectTemplatesName.names; int counterTemplate = 0; std::string lastTemplateName = ""; for (std::vector::const_iterator it = names.begin(); it != names.end(); ++it) { if (lastTemplateName == "") lastTemplateName = (*it); if (lastTemplateName == (*it)) { ++counterTemplate; continue; } sizer->Add(CreateTemplateNameObject(m_TemplateNames, lastTemplateName, counterTemplate), wxSizerFlags().Align(wxALIGN_LEFT)); lastTemplateName = (*it); counterTemplate = 1; } // Add the remaining template sizer->Add(CreateTemplateNameObject(m_TemplateNames, lastTemplateName, counterTemplate), wxSizerFlags().Align(wxALIGN_LEFT)); Thaw(); sizer->FitInside(m_TemplateNames); } void ObjectBottomBar::OnFirstDisplay() { // We use messages here because the simulation is not init'd otherwise (causing a crash) // Get player names wxArrayString players; AtlasMessage::qGetPlayerDefaults qryPlayers; qryPlayers.Post(); AtObj playerData = AtlasObject::LoadFromJSON(*qryPlayers.defaults); AtObj playerDefs = *playerData["PlayerData"]; - for (AtIter p = playerDefs["item"]; p.defined(); ++p) - { - players.Add(wxString(p["Name"])); - } + for (AtIter iterator = playerDefs["item"]; iterator.defined(); ++iterator) + players.Add(wxString(iterator["Name"])); + wxDynamicCast(FindWindow(ID_PlayerSelect), PlayerComboBox)->SetPlayers(players); // Initialise the game with the default settings POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"wireframe", m_ViewerWireframe)); POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"walk", m_ViewerMove)); POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"ground", m_ViewerGround)); POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"water", m_ViewerWater)); POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"shadows", m_ViewerShadows)); POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"stats", m_ViewerPolyCount)); POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"bounding_box", m_ViewerBoundingBox)); POST_MESSAGE(SetViewParamI, (AtlasMessage::eRenderView::ACTOR, L"prop_points", m_ViewerPropPointsMode)); } void ObjectBottomBar::ShowActorViewer(bool show) { m_ViewerPanel->Show(show); Layout(); } void ObjectBottomBar::OnViewerSetting(wxCommandEvent& evt) { switch (evt.GetId()) { case ID_ViewerWireframe: m_ViewerWireframe = !m_ViewerWireframe; POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"wireframe", m_ViewerWireframe)); break; case ID_ViewerMove: m_ViewerMove = !m_ViewerMove; POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"walk", m_ViewerMove)); break; case ID_ViewerGround: m_ViewerGround = !m_ViewerGround; POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"ground", m_ViewerGround)); break; case ID_ViewerWater: m_ViewerWater = !m_ViewerWater; POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"water", m_ViewerWater)); break; case ID_ViewerShadows: m_ViewerShadows = !m_ViewerShadows; POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"shadows", m_ViewerShadows)); break; case ID_ViewerPolyCount: m_ViewerPolyCount = !m_ViewerPolyCount; POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"stats", m_ViewerPolyCount)); break; case ID_ViewerBoundingBox: m_ViewerBoundingBox = !m_ViewerBoundingBox; POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"bounding_box", m_ViewerBoundingBox)); break; case ID_ViewerAxesMarker: m_ViewerAxesMarker = !m_ViewerAxesMarker; POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"axes_marker", m_ViewerAxesMarker)); break; case ID_ViewerPropPoints: m_ViewerPropPointsMode = (m_ViewerPropPointsMode+1) % 3; POST_MESSAGE(SetViewParamI, (AtlasMessage::eRenderView::ACTOR, L"prop_points", m_ViewerPropPointsMode)); break; } } void ObjectBottomBar::OnSelectAnim(wxCommandEvent& evt) { p->m_ActorViewerAnimation = evt.GetString(); p->ActorViewerPostToGame(); } void ObjectBottomBar::OnSpeed(wxCommandEvent& evt) { switch (evt.GetId()) { case ID_ViewerPlay: p->m_ActorViewerSpeed = 1.0f; break; case ID_ViewerPause: p->m_ActorViewerSpeed = 0.0f; break; case ID_ViewerSlow: p->m_ActorViewerSpeed = 0.1f; break; } p->ActorViewerPostToGame(); } BEGIN_EVENT_TABLE(ObjectBottomBar, wxPanel) EVT_BUTTON(ID_ViewerWireframe, ObjectBottomBar::OnViewerSetting) EVT_BUTTON(ID_ViewerMove, ObjectBottomBar::OnViewerSetting) EVT_BUTTON(ID_ViewerGround, ObjectBottomBar::OnViewerSetting) EVT_BUTTON(ID_ViewerWater, ObjectBottomBar::OnViewerSetting) EVT_BUTTON(ID_ViewerShadows, ObjectBottomBar::OnViewerSetting) EVT_BUTTON(ID_ViewerPolyCount, ObjectBottomBar::OnViewerSetting) EVT_CHOICE(ID_ViewerAnimation, ObjectBottomBar::OnSelectAnim) EVT_BUTTON(ID_ViewerPlay, ObjectBottomBar::OnSpeed) EVT_BUTTON(ID_ViewerPause, ObjectBottomBar::OnSpeed) EVT_BUTTON(ID_ViewerSlow, ObjectBottomBar::OnSpeed) EVT_BUTTON(ID_ViewerBoundingBox, ObjectBottomBar::OnViewerSetting) EVT_BUTTON(ID_ViewerAxesMarker, ObjectBottomBar::OnViewerSetting) EVT_BUTTON(ID_ViewerPropPoints, ObjectBottomBar::OnViewerSetting) END_EVENT_TABLE(); Index: ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/Object.h =================================================================== --- ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/Object.h (revision 22160) +++ ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/Object.h (revision 22161) @@ -1,43 +1,43 @@ -/* Copyright (C) 2011 Wildfire Games. +/* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "../Common/Sidebar.h" class ITool; struct ObjectSidebarImpl; class ObjectSidebar : public Sidebar { public: ObjectSidebar(ScenarioEditor& scenarioEditor, wxWindow* sidebarContainer, wxWindow* bottomBarContainer); ~ObjectSidebar(); void FilterObjects(); protected: virtual void OnFirstDisplay(); private: void OnToolChange(ITool* tool); void OnToggleViewer(wxCommandEvent& evt); void OnSelectType(wxCommandEvent& evt); void OnSelectFilter(wxCommandEvent& evt); void OnSelectObject(wxCommandEvent& evt); - ObjectSidebarImpl* p; + ObjectSidebarImpl* m_Impl; DECLARE_EVENT_TABLE(); };