From d3c063f88c6e45f7b460bd6ea4d1a08c40864a8a Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Tue, 22 Nov 2022 21:54:44 +0800 Subject: [PATCH] Base/App: add new signal interface to Parameter Added new signal interface using boost::signals2 signalParamChanged. Exposed to Python as ParameterGrpPy.AttachManager() to monitor changes to all parameters, sub groups under the referring group. Added new attribute for ParameterGrp(Py) to query the Parent and Manager of the referring group. --- src/App/Application.cpp | 23 +- src/App/Application.h | 8 +- src/Base/Handle.cpp | 7 + src/Base/Handle.h | 16 +- src/Base/Parameter.cpp | 700 ++++++++++++++++++++++++++++-------- src/Base/Parameter.h | 99 ++++- src/Base/ParameterPy.cpp | 122 ++++++- src/Gui/DlgParameterImp.cpp | 2 +- 8 files changed, 794 insertions(+), 183 deletions(-) diff --git a/src/App/Application.cpp b/src/App/Application.cpp index 8e89022a06..fdbac649a5 100644 --- a/src/App/Application.cpp +++ b/src/App/Application.cpp @@ -154,8 +154,8 @@ namespace sp = std::placeholders; // Application //========================================================================== -ParameterManager *App::Application::_pcSysParamMngr; -ParameterManager *App::Application::_pcUserParamMngr; +Base::Reference App::Application::_pcSysParamMngr; +Base::Reference App::Application::_pcUserParamMngr; Base::ConsoleObserverStd *Application::_pConsoleObserverStd = nullptr; Base::ConsoleObserverFile *Application::_pConsoleObserverFile = nullptr; @@ -1215,21 +1215,22 @@ ParameterManager & Application::GetUserParameter() ParameterManager * Application::GetParameterSet(const char* sName) const { - std::map::const_iterator it = mpcPramManager.find(sName); + auto it = mpcPramManager.find(sName); if ( it != mpcPramManager.end() ) return it->second; else return nullptr; } -const std::map & Application::GetParameterSetList() const +const std::map> & +Application::GetParameterSetList(void) const { return mpcPramManager; } void Application::AddParameterSet(const char* sName) { - std::map::const_iterator it = mpcPramManager.find(sName); + auto it = mpcPramManager.find(sName); if ( it != mpcPramManager.end() ) return; mpcPramManager[sName] = new ParameterManager(); @@ -1237,11 +1238,10 @@ void Application::AddParameterSet(const char* sName) void Application::RemoveParameterSet(const char* sName) { - std::map::iterator it = mpcPramManager.find(sName); + auto it = mpcPramManager.find(sName); // Must not delete user or system parameter if ( it == mpcPramManager.end() || it->second == _pcUserParamMngr || it->second == _pcSysParamMngr ) return; - delete it->second; mpcPramManager.erase(it); } @@ -1260,7 +1260,7 @@ Base::Reference Application::GetParameterGroupByPath(const char* cName.erase(0,pos+1); // test if name is valid - std::map::iterator It = mpcPramManager.find(cTemp.c_str()); + auto It = mpcPramManager.find(cTemp.c_str()); if (It == mpcPramManager.end()) throw Base::ValueError("Application::GetParameterGroupByPath() unknown parameter set name specified"); @@ -1651,8 +1651,8 @@ void Application::destruct() Base::Console().Log("Saving user parameter...done\n"); // now save all other parameter files - std::map& paramMgr = _pcSingleton->mpcPramManager; - for (std::map::iterator it = paramMgr.begin(); it != paramMgr.end(); ++it) { + auto& paramMgr = _pcSingleton->mpcPramManager; + for (auto it = paramMgr.begin(); it != paramMgr.end(); ++it) { if ((it->second != _pcSysParamMngr) && (it->second != _pcUserParamMngr)) { if (it->second->HasSerializer()) { Base::Console().Log("Saving %s...\n", it->first.c_str()); @@ -1660,9 +1660,6 @@ void Application::destruct() Base::Console().Log("Saving %s...done\n", it->first.c_str()); } } - - // clean up - delete it->second; } paramMgr.clear(); diff --git a/src/App/Application.h b/src/App/Application.h index 1cd0fcf2e2..a1616bad1a 100644 --- a/src/App/Application.h +++ b/src/App/Application.h @@ -332,7 +332,7 @@ public: Base::Reference GetParameterGroupByPath(const char* sName); ParameterManager * GetParameterSet(const char* sName) const; - const std::map & GetParameterSetList() const; + const std::map> & GetParameterSetList(void) const; void AddParameterSet(const char* sName); void RemoveParameterSet(const char* sName); //@} @@ -503,8 +503,8 @@ private: /** @name member for parameter */ //@{ - static ParameterManager *_pcSysParamMngr; - static ParameterManager *_pcUserParamMngr; + static Base::Reference _pcSysParamMngr; + static Base::Reference _pcUserParamMngr; //@} //--------------------------------------------------------------------- @@ -605,7 +605,7 @@ private: std::vector _mExportTypes; std::map DocMap; mutable std::map DocFileMap; - std::map mpcPramManager; + std::map> mpcPramManager; std::map &_mConfig; App::Document* _pActiveDoc; diff --git a/src/Base/Handle.cpp b/src/Base/Handle.cpp index c02189b83f..a74f76c376 100644 --- a/src/Base/Handle.cpp +++ b/src/Base/Handle.cpp @@ -64,6 +64,13 @@ void Handled::unref() const } } +int Handled::unrefNoDelete() const +{ + int res = _lRefCount->deref(); + assert(res>=0); + return res; +} + int Handled::getRefCount() const { return static_cast(*_lRefCount); diff --git a/src/Base/Handle.h b/src/Base/Handle.h index c3e05ffb80..0816719462 100644 --- a/src/Base/Handle.h +++ b/src/Base/Handle.h @@ -115,21 +115,6 @@ public: return _toHandle; } - /** Lower operator, needed for sorting in maps and sets */ - bool operator<(const Reference& p) const { - return _toHandle < p._toHandle; - } - - /** Equal operator */ - bool operator==(const Reference& p) const { - return _toHandle == p._toHandle; - } - - bool operator!=(const Reference& p) const { - return _toHandle != p._toHandle; - } - - //************************************************************************** // checking on the state @@ -165,6 +150,7 @@ public: void ref() const; void unref() const; + int unrefNoDelete() const; int getRefCount() const; const Handled& operator = (const Handled&); diff --git a/src/Base/Parameter.cpp b/src/Base/Parameter.cpp index e96793a276..75a23dc648 100644 --- a/src/Base/Parameter.cpp +++ b/src/Base/Parameter.cpp @@ -44,11 +44,15 @@ # include #endif +#include + #include "Parameter.h" #include "Parameter.inl" #include "Console.h" #include "Exception.h" +#include "Tools.h" +FC_LOG_LEVEL_INIT("Parameter", true, true) //#ifdef XERCES_HAS_CPP_NAMESPACE // using namespace xercesc; @@ -173,25 +177,41 @@ inline bool DOMTreeErrorReporter::getSawErrors() const /** Default construction */ -ParameterGrp::ParameterGrp(XERCES_CPP_NAMESPACE_QUALIFIER DOMElement *GroupNode,const char* sName) - : Base::Handled(), Subject(),_pGroupNode(GroupNode) +ParameterGrp::ParameterGrp(XERCES_CPP_NAMESPACE_QUALIFIER DOMElement *GroupNode, + const char* sName, + ParameterGrp *Parent) + : Base::Handled(), Subject() + , _pGroupNode(GroupNode) + , _Parent(Parent) { if (sName) _cName=sName; + if (_Parent) _Manager = _Parent->_Manager; } /** Destruction * complete destruction of the object */ -ParameterGrp::~ParameterGrp() = default; +ParameterGrp::~ParameterGrp() +{ + for (auto &v : _GroupMap) { + v.second->_Parent = nullptr; + v.second->_Manager = nullptr; + } + if (_Detached && _pGroupNode) + _pGroupNode->release(); +} //************************************************************************** // Access methods void ParameterGrp::copyTo(Base::Reference Grp) { + if (Grp == this) + return; + // delete previous content - Grp->Clear(); + Grp->Clear(true); // copy all insertTo(Grp); @@ -199,6 +219,9 @@ void ParameterGrp::copyTo(Base::Reference Grp) void ParameterGrp::insertTo(Base::Reference Grp) { + if (Grp == this) + return; + // copy group std::vector > Grps = GetGroups(); std::vector >::iterator It1; @@ -242,10 +265,13 @@ void ParameterGrp::exportTo(const char* FileName) Mngr.CreateDocument(); + Mngr.ref(); + // copy all into the new document - insertTo(Mngr.GetGroup("BaseApp")); + insertTo(&Mngr); Mngr.SaveDocument(FileName); + Mngr.unrefNoDelete(); } void ParameterGrp::importFrom(const char* FileName) @@ -255,7 +281,9 @@ void ParameterGrp::importFrom(const char* FileName) if (Mngr.LoadDocument(FileName) != 1) throw FileException("ParameterGrp::import() cannot load document", FileName); - Mngr.GetGroup("BaseApp")->copyTo(Base::Reference(this)); + ref(); + Mngr.copyTo(Base::Reference(this)); + unrefNoDelete(); } void ParameterGrp::insert(const char* FileName) @@ -265,69 +293,169 @@ void ParameterGrp::insert(const char* FileName) if (Mngr.LoadDocument(FileName) != 1) throw FileException("ParameterGrp::import() cannot load document", FileName); - Mngr.GetGroup("root")->insertTo(Base::Reference(this)); + ref(); + Mngr.insertTo(Base::Reference(this)); + unrefNoDelete(); +} + +void ParameterGrp::revert(const char* FileName) +{ + ParameterManager Mngr; + + if (Mngr.LoadDocument(FileName) != 1) + throw FileException("ParameterGrp::revert() cannot load document", FileName); + + Mngr.ref(); + revert(Base::Reference(&Mngr)); + Mngr.unrefNoDelete(); +} + +void ParameterGrp::revert(Base::Reference Grp) +{ + if (Grp == this) + return; + + for (auto &grp : Grp->GetGroups()) { + if (HasGroup(grp->GetGroupName())) + GetGroup(grp->GetGroupName())->revert(grp); + } + + for (const auto &v : Grp->GetASCIIMap()) { + if (GetASCII(v.first.c_str(), v.second.c_str()) == v.second) + RemoveASCII(v.first.c_str()); + } + + for (const auto &v : Grp->GetBoolMap()) { + if (GetBool(v.first.c_str(), v.second) == v.second) + RemoveBool(v.first.c_str()); + } + + for (const auto &v : Grp->GetIntMap()) { + if (GetInt(v.first.c_str(), v.second) == v.second) + RemoveInt(v.first.c_str()); + } + + for (const auto &v : Grp->GetUnsignedMap()) { + if (GetUnsigned(v.first.c_str(), v.second) == v.second) + RemoveUnsigned(v.first.c_str()); + } + + for (const auto &v : Grp->GetFloatMap()) { + if (GetFloat(v.first.c_str(), v.second) == v.second) + RemoveFloat(v.first.c_str()); + } +} + +XERCES_CPP_NAMESPACE_QUALIFIER DOMElement * +ParameterGrp::CreateElement(XERCES_CPP_NAMESPACE_QUALIFIER DOMElement *Start, const char* Type, const char* Name) +{ + if (XMLString::compareString(Start->getNodeName(), XStr("FCParamGroup").unicodeForm()) != 0 && + XMLString::compareString(Start->getNodeName(), XStr("FCParameters").unicodeForm()) != 0) { + Base::Console().Warning("CreateElement: %s cannot have the element %s of type %s\n", StrX(Start->getNodeName()).c_str(), Name, Type); + return nullptr; + } + + if (_Detached && _Parent) { + // re-attach the group + _Parent->_GetGroup(_cName.c_str()); + } + + XERCES_CPP_NAMESPACE_QUALIFIER DOMDocument *pDocument = Start->getOwnerDocument(); + + auto pcElem = pDocument->createElement(XStr(Type).unicodeForm()); + pcElem-> setAttribute(XStr("Name").unicodeForm(), XStr(Name).unicodeForm()); + Start->appendChild(pcElem); + + return pcElem; } Base::Reference ParameterGrp::GetGroup(const char* Name) { - std::string cName = Name; - if (cName.empty()) + if (!Name) throw Base::ValueError("Empty group name"); - // Remove all leading slashes - std::string::size_type beg = cName.find_first_not_of('/'); - if (beg > 0) { - cName.erase(0, beg); - } - - // Remove all trailing slashes - std::string::size_type end = cName.find_last_not_of('/'); - if (end+1 < cName.size()) { - cName.erase(end+1); - } - - std::string::size_type pos = cName.find('/'); - - // is there a path separator ? - if (pos == std::string::npos) { - return _GetGroup(cName.c_str()); - } - else { - // path, split the first path - std::string cTemp; - // getting the first part - cTemp.assign(cName, 0, pos); - // removing the first part from the original - cName.erase(0, pos+1); - //subsequent call - return _GetGroup(cTemp.c_str())->GetGroup(cName.c_str()); + Base::Reference hGrp = this; + std::vector tokens; + boost::split(tokens, Name, boost::is_any_of("/")); + for (auto &token : tokens) { + boost::trim(token); + if (token.empty()) + continue; + hGrp = hGrp->_GetGroup(token.c_str()); + if (!hGrp) { + // The group is clearing. Return some dummy group to avoid caller + // crashing for backward compatibility. + hGrp = new ParameterGrp(); + hGrp->_cName = Name; + break; + } } + if (hGrp == this) + throw Base::ValueError("Empty group name"); + return hGrp; } Base::Reference ParameterGrp::_GetGroup(const char* Name) { Base::Reference rParamGrp; - - // already created? - if ((rParamGrp=_GroupMap[Name]).isValid()) { - // just return the already existing Group handle + if (!_pGroupNode) { + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) + FC_WARN("Adding group " << Name << " in an orphan group " << _cName); + return rParamGrp; + } + if (_Clearing) { + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) + FC_WARN("Adding group " << Name << " while clearing " << GetPath()); return rParamGrp; } - // search if Group node already there - DOMElement *pcTemp = FindOrCreateElement(_pGroupNode,"FCParamGroup",Name); + DOMElement *pcTemp; - // create and register handle - rParamGrp = Base::Reference (new ParameterGrp(pcTemp,Name)); - _GroupMap[Name] = rParamGrp; + // search if Group node already there + pcTemp = FindElement(_pGroupNode,"FCParamGroup",Name); + + // already created? + if (!(rParamGrp=_GroupMap[Name]).isValid()) { + if (!pcTemp) + pcTemp = CreateElement(_pGroupNode,"FCParamGroup",Name); + // create and register handle + rParamGrp = Base::Reference (new ParameterGrp(pcTemp,Name,this)); + _GroupMap[Name] = rParamGrp; + } else if (!pcTemp) { + _pGroupNode->appendChild(rParamGrp->_pGroupNode); + rParamGrp->_Detached = false; + if (this->_Detached && this->_Parent) { + // Re-attach the group. Note that this may fail if the parent is + // clearing. That's why we check this->_Detached below. + this->_Parent->_GetGroup(_cName.c_str()); + } + } + + if (!pcTemp && !this->_Detached) + _Notify(ParamType::FCGroup, Name, Name); return rParamGrp; } +std::string ParameterGrp::GetPath() const +{ + std::string path; + if (_Parent && _Parent != _Manager) + path = _Parent->GetPath(); + if (path.size() && _cName.size()) + path += "/"; + path += _cName; + return path; +} + std::vector > ParameterGrp::GetGroups() { Base::Reference rParamGrp; std::vector > vrParamGrp; + + if (!_pGroupNode) + return vrParamGrp; + std::string Name; DOMElement *pcTemp = FindElement(_pGroupNode,"FCParamGroup"); @@ -335,7 +463,7 @@ std::vector > ParameterGrp::GetGroups() Name = StrX(pcTemp->getAttributes()->getNamedItem(XStr("Name").unicodeForm())->getNodeValue()).c_str(); // already created? if (!(rParamGrp=_GroupMap[Name]).isValid()) { - rParamGrp = Base::Reference (new ParameterGrp(pcTemp,Name.c_str())); + rParamGrp = Base::Reference (new ParameterGrp(pcTemp,Name.c_str(),this)); _GroupMap[Name] = rParamGrp; } vrParamGrp.push_back( rParamGrp ); @@ -349,7 +477,7 @@ std::vector > ParameterGrp::GetGroups() /// test if this group is empty bool ParameterGrp::IsEmpty() const { - if (_pGroupNode->getFirstChild()) + if ( _pGroupNode && _pGroupNode->getFirstChild() ) return false; else return true; @@ -361,14 +489,189 @@ bool ParameterGrp::HasGroup(const char* Name) const if (_GroupMap.find(Name) != _GroupMap.end()) return true; - if (FindElement(_pGroupNode,"FCParamGroup",Name)) + if (_pGroupNode && FindElement(_pGroupNode,"FCParamGroup",Name) != 0) return true; return false; } +const char *ParameterGrp::TypeName(ParamType Type) +{ + switch(Type) { + case ParamType::FCBool: + return "FCBool"; + case ParamType::FCInt: + return "FCInt"; + case ParamType::FCUInt: + return "FCUInt"; + case ParamType::FCText: + return "FCText"; + case ParamType::FCFloat: + return "FCFloat"; + case ParamType::FCGroup: + return "FCParamGroup"; + default: + return nullptr; + } +} + +ParameterGrp::ParamType ParameterGrp::TypeValue(const char *Name) +{ + if (Name) { + if (boost::equals(Name, "FCBool")) + return ParamType::FCBool; + if (boost::equals(Name, "FCInt")) + return ParamType::FCInt; + if (boost::equals(Name, "FCUInt")) + return ParamType::FCUInt; + if (boost::equals(Name, "FCText")) + return ParamType::FCText; + if (boost::equals(Name, "FCFloat")) + return ParamType::FCFloat; + if (boost::equals(Name, "FCParamGroup")) + return ParamType::FCGroup; + } + return ParamType::FCInvalid; +} + +void ParameterGrp::SetAttribute(ParamType Type, const char *Name, const char *Value) +{ + switch(Type) { + case ParamType::FCBool: + case ParamType::FCInt: + case ParamType::FCUInt: + case ParamType::FCFloat: + return _SetAttribute(Type, Name, Value); + case ParamType::FCText: + return SetASCII(Name, Value); + case ParamType::FCGroup: + RenameGrp(Name, Value); + break; + default: + break; + } +} + +void ParameterGrp::RemoveAttribute(ParamType Type, const char *Name) +{ + switch(Type) { + case ParamType::FCBool: + return RemoveBool(Name); + case ParamType::FCInt: + return RemoveInt(Name); + case ParamType::FCUInt: + return RemoveUnsigned(Name); + case ParamType::FCText: + return RemoveASCII(Name); + case ParamType::FCFloat: + return RemoveFloat(Name); + case ParamType::FCGroup: + return RemoveGrp(Name); + default: + break; + } +} + +const char * +ParameterGrp::GetAttribute(ParamType Type, + const char *Name, + std::string &Value, + const char *Default) const +{ + if (!_pGroupNode) + return Default; + + const char *T = TypeName(Type); + if (!T) + return Default; + + DOMElement *pcElem = FindElement(_pGroupNode,T,Name); + if (!pcElem) + return Default; + + if (Type == ParamType::FCText) + Value = GetASCII(Name, Default); + else if (Type != ParamType::FCGroup) + Value = StrX(pcElem->getAttribute(XStr("Value").unicodeForm())).c_str(); + return Value.c_str(); +} + +std::vector> +ParameterGrp::GetAttributeMap(ParamType Type, const char *sFilter) const +{ + std::vector> res; + if (!_pGroupNode) + return res; + + const char *T = TypeName(Type); + if (!T) + return res; + + std::string Name; + + DOMElement *pcTemp = FindElement(_pGroupNode, T); + while ( pcTemp) { + Name = StrX(static_cast( + pcTemp)->getAttributes()->getNamedItem( + XStr("Name").unicodeForm())->getNodeValue()).c_str(); + // check on filter condition + if (!sFilter || Name.find(sFilter)!= std::string::npos) { + if (Type == ParamType::FCGroup) + res.emplace_back(Name, std::string()); + else + res.emplace_back(Name, StrX(static_cast(pcTemp)->getAttribute( + XStr("Value").unicodeForm())).c_str()); + } + pcTemp = FindNextElement(pcTemp,T); + } + return res; +} + +void ParameterGrp::_Notify(ParamType Type, const char *Name, const char *Value) +{ + if (_Manager) + _Manager->signalParamChanged(this, Type, Name, Value); +} + +void ParameterGrp::_SetAttribute(ParamType T, const char *Name, const char *Value) +{ + const char *Type = TypeName(T); + if (!Type) + return; + if (!_pGroupNode) { + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) + FC_WARN("Setting attribute " << Type << ":" + << Name << " in an orphan group " << _cName); + return; + } + if (_Clearing) { + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) + FC_WARN("Adding attribute " << Type << ":" + << Name << " while clearing " << GetPath()); + return; + } + + // find or create the Element + DOMElement *pcElem = FindOrCreateElement(_pGroupNode,Type,Name); + if (pcElem) { + XStr attr("Value"); + // set the value only if different + if (strcmp(StrX(pcElem->getAttribute(attr.unicodeForm())).c_str(),Value)!=0) { + pcElem->setAttribute(attr.unicodeForm(), XStr(Value).unicodeForm()); + // trigger observer + _Notify(T, Name, Value); + } + // For backward compatibility, old observer gets notified regardless of + // value changes or not. + Notify(Name); + } +} + bool ParameterGrp::GetBool(const char* Name, bool bPreset) const { + if (!_pGroupNode) + return bPreset; + // check if Element in group DOMElement *pcElem = FindElement(_pGroupNode,"FCBool",Name); // if not return preset @@ -383,19 +686,15 @@ bool ParameterGrp::GetBool(const char* Name, bool bPreset) const void ParameterGrp::SetBool(const char* Name, bool bValue) { - // find or create the Element - DOMElement *pcElem = FindOrCreateElement(_pGroupNode,"FCBool",Name); - if (pcElem) { - // and set the value - pcElem->setAttribute(XStr("Value").unicodeForm(), XStr(bValue?"1":"0").unicodeForm()); - // trigger observer - Notify(Name); - } + _SetAttribute(ParamType::FCBool, Name, bValue?"1":"0"); } std::vector ParameterGrp::GetBools(const char * sFilter) const { std::vector vrValues; + if (!_pGroupNode) + return vrValues; + std::string Name; DOMElement *pcTemp = FindElement(_pGroupNode,"FCBool"); @@ -417,6 +716,9 @@ std::vector ParameterGrp::GetBools(const char * sFilter) const std::vector > ParameterGrp::GetBoolMap(const char * sFilter) const { std::vector > vrValues; + if (!_pGroupNode) + return vrValues; + std::string Name; DOMElement *pcTemp = FindElement(_pGroupNode,"FCBool"); @@ -437,6 +739,9 @@ std::vector > ParameterGrp::GetBoolMap(const char * long ParameterGrp::GetInt(const char* Name, long lPreset) const { + if (!_pGroupNode) + return lPreset; + // check if Element in group DOMElement *pcElem = FindElement(_pGroupNode,"FCInt",Name); // if not return preset @@ -449,20 +754,16 @@ long ParameterGrp::GetInt(const char* Name, long lPreset) const void ParameterGrp::SetInt(const char* Name, long lValue) { char cBuf[256]; - // find or create the Element - DOMElement *pcElem = FindOrCreateElement(_pGroupNode,"FCInt",Name); - if (pcElem) { - // and set the value - sprintf(cBuf,"%li",lValue); - pcElem->setAttribute(XStr("Value").unicodeForm(), XStr(cBuf).unicodeForm()); - // trigger observer - Notify(Name); - } + sprintf(cBuf,"%li",lValue); + _SetAttribute(ParamType::FCInt, Name, cBuf); } std::vector ParameterGrp::GetInts(const char * sFilter) const { std::vector vrValues; + if (!_pGroupNode) + return vrValues; + std::string Name; DOMElement *pcTemp = FindElement(_pGroupNode,"FCInt") ; @@ -481,6 +782,9 @@ std::vector ParameterGrp::GetInts(const char * sFilter) const std::vector > ParameterGrp::GetIntMap(const char * sFilter) const { std::vector > vrValues; + if (!_pGroupNode) + return vrValues; + std::string Name; DOMElement *pcTemp = FindElement(_pGroupNode,"FCInt") ; @@ -499,6 +803,9 @@ std::vector > ParameterGrp::GetIntMap(const char * s unsigned long ParameterGrp::GetUnsigned(const char* Name, unsigned long lPreset) const { + if (!_pGroupNode) + return lPreset; + // check if Element in group DOMElement *pcElem = FindElement(_pGroupNode,"FCUInt",Name); // if not return preset @@ -511,20 +818,16 @@ unsigned long ParameterGrp::GetUnsigned(const char* Name, unsigned long lPreset) void ParameterGrp::SetUnsigned(const char* Name, unsigned long lValue) { char cBuf[256]; - // find or create the Element - DOMElement *pcElem = FindOrCreateElement(_pGroupNode,"FCUInt",Name); - if (pcElem) { - // and set the value - sprintf(cBuf,"%lu",lValue); - pcElem->setAttribute(XStr("Value").unicodeForm(), XStr(cBuf).unicodeForm()); - // trigger observer - Notify(Name); - } + sprintf(cBuf,"%lu",lValue); + _SetAttribute(ParamType::FCUInt, Name, cBuf); } std::vector ParameterGrp::GetUnsigneds(const char * sFilter) const { std::vector vrValues; + if (!_pGroupNode) + return vrValues; + std::string Name; DOMElement *pcTemp = FindElement(_pGroupNode,"FCUInt"); @@ -543,6 +846,9 @@ std::vector ParameterGrp::GetUnsigneds(const char * sFilter) cons std::vector > ParameterGrp::GetUnsignedMap(const char * sFilter) const { std::vector > vrValues; + if (!_pGroupNode) + return vrValues; + std::string Name; DOMElement *pcTemp = FindElement(_pGroupNode,"FCUInt"); @@ -561,6 +867,9 @@ std::vector > ParameterGrp::GetUnsignedMap( double ParameterGrp::GetFloat(const char* Name, double dPreset) const { + if (!_pGroupNode) + return dPreset; + // check if Element in group DOMElement *pcElem = FindElement(_pGroupNode,"FCFloat",Name); // if not return preset @@ -573,20 +882,16 @@ double ParameterGrp::GetFloat(const char* Name, double dPreset) const void ParameterGrp::SetFloat(const char* Name, double dValue) { char cBuf[256]; - // find or create the Element - DOMElement *pcElem = FindOrCreateElement(_pGroupNode,"FCFloat",Name); - if (pcElem) { - // and set the value - sprintf(cBuf,"%.12f",dValue); // use %.12f instead of %f to handle values < 1.0e-6 - pcElem->setAttribute(XStr("Value").unicodeForm(), XStr(cBuf).unicodeForm()); - // trigger observer - Notify(Name); - } + sprintf(cBuf,"%.12f",dValue); // use %.12f instead of %f to handle values < 1.0e-6 + _SetAttribute(ParamType::FCFloat, Name, cBuf); } std::vector ParameterGrp::GetFloats(const char * sFilter) const { std::vector vrValues; + if (!_pGroupNode) + return vrValues; + std::string Name; DOMElement *pcTemp = FindElement(_pGroupNode,"FCFloat") ; @@ -605,6 +910,9 @@ std::vector ParameterGrp::GetFloats(const char * sFilter) const std::vector > ParameterGrp::GetFloatMap(const char * sFilter) const { std::vector > vrValues; + if (!_pGroupNode) + return vrValues; + std::string Name; DOMElement *pcTemp = FindElement(_pGroupNode,"FCFloat") ; @@ -635,8 +943,25 @@ void ParameterGrp::GetBlob(const char* /*Name*/, void* /*pBuf*/, long /*lMaxLeng void ParameterGrp::SetASCII(const char* Name, const char *sValue) { - // find or create the Element - DOMElement *pcElem = FindOrCreateElement(_pGroupNode,"FCText",Name); + if (!_pGroupNode) { + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) + FC_WARN("Setting attribute " << "FCText:" + << Name << " in an orphan group " << _cName); + return; + } + if (_Clearing) { + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) + FC_WARN("Adding attribute " << "FCText:" + << Name << " while clearing " << GetPath()); + return; + } + + bool isNew = false; + DOMElement *pcElem = FindElement(_pGroupNode,"FCText",Name); + if (!pcElem) { + pcElem = CreateElement(_pGroupNode,"FCText",Name); + isNew = true; + } if (pcElem) { // and set the value DOMNode *pcElem2 = pcElem->getFirstChild(); @@ -644,9 +969,12 @@ void ParameterGrp::SetASCII(const char* Name, const char *sValue) XERCES_CPP_NAMESPACE_QUALIFIER DOMDocument *pDocument = _pGroupNode->getOwnerDocument(); DOMText *pText = pDocument->createTextNode(XUTF8Str(sValue).unicodeForm()); pcElem->appendChild(pText); + if (isNew || sValue[0]!=0) + _Notify(ParamType::FCText, Name, sValue); } - else { + else if (strcmp(StrXUTF8(pcElem2->getNodeValue()).c_str(), sValue)!=0) { pcElem2->setNodeValue(XUTF8Str(sValue).unicodeForm()); + _Notify(ParamType::FCText, Name, sValue); } // trigger observer Notify(Name); @@ -655,6 +983,9 @@ void ParameterGrp::SetASCII(const char* Name, const char *sValue) std::string ParameterGrp::GetASCII(const char* Name, const char * pPreset) const { + if (!_pGroupNode) + return pPreset ? pPreset : ""; + // check if Element in group DOMElement *pcElem = FindElement(_pGroupNode,"FCText",Name); // if not return preset @@ -675,6 +1006,9 @@ std::string ParameterGrp::GetASCII(const char* Name, const char * pPreset) const std::vector ParameterGrp::GetASCIIs(const char * sFilter) const { std::vector vrValues; + if (!_pGroupNode) + return vrValues; + std::string Name; DOMElement *pcTemp = FindElement(_pGroupNode,"FCText"); @@ -698,6 +1032,9 @@ std::vector ParameterGrp::GetASCIIs(const char * sFilter) const std::vector > ParameterGrp::GetASCIIMap(const char * sFilter) const { std::vector > vrValues; + if (!_pGroupNode) + return vrValues; + std::string Name; DOMElement *pcTemp = FindElement(_pGroupNode,"FCText"); @@ -723,6 +1060,9 @@ std::vector > ParameterGrp::GetASCIIMap(const void ParameterGrp::RemoveASCII(const char* Name) { + if (!_pGroupNode) + return; + // check if Element in group DOMElement *pcElem = FindElement(_pGroupNode,"FCText",Name); // if not return @@ -733,11 +1073,15 @@ void ParameterGrp::RemoveASCII(const char* Name) node->release(); // trigger observer + _Notify(ParamType::FCText, Name, nullptr); Notify(Name); } void ParameterGrp::RemoveBool(const char* Name) { + if (!_pGroupNode) + return; + // check if Element in group DOMElement *pcElem = FindElement(_pGroupNode,"FCBool",Name); // if not return @@ -748,6 +1092,7 @@ void ParameterGrp::RemoveBool(const char* Name) node->release(); // trigger observer + _Notify(ParamType::FCBool, Name, nullptr); Notify(Name); } @@ -766,6 +1111,9 @@ void ParameterGrp::RemoveBlob(const char* /*Name*/) void ParameterGrp::RemoveFloat(const char* Name) { + if (!_pGroupNode) + return; + // check if Element in group DOMElement *pcElem = FindElement(_pGroupNode,"FCFloat",Name); // if not return @@ -776,11 +1124,15 @@ void ParameterGrp::RemoveFloat(const char* Name) node->release(); // trigger observer + _Notify(ParamType::FCFloat,Name, nullptr); Notify(Name); } void ParameterGrp::RemoveInt(const char* Name) { + if (!_pGroupNode) + return; + // check if Element in group DOMElement *pcElem = FindElement(_pGroupNode,"FCInt",Name); // if not return @@ -791,11 +1143,15 @@ void ParameterGrp::RemoveInt(const char* Name) node->release(); // trigger observer + _Notify(ParamType::FCInt, Name, nullptr); Notify(Name); } void ParameterGrp::RemoveUnsigned(const char* Name) { + if (!_pGroupNode) + return; + // check if Element in group DOMElement *pcElem = FindElement(_pGroupNode,"FCUInt",Name); // if not return @@ -806,37 +1162,34 @@ void ParameterGrp::RemoveUnsigned(const char* Name) node->release(); // trigger observer + _Notify(ParamType::FCUInt, Name, nullptr); Notify(Name); } void ParameterGrp::RemoveGrp(const char* Name) { + if (!_pGroupNode) + return; + auto it = _GroupMap.find(Name); if (it == _GroupMap.end()) return; - // if this or any of its children is referenced by an observer - // it cannot be deleted -#if 1 - if (!it->second->ShouldRemove()) { - it->second->Clear(); + // If this or any of its children is referenced by an observer we do not + // delete the handle, just in case the group is later added again, or else + // those existing observer won't get any notification. BUT, we DO delete + // the underlying xml elements, so that we don't save the empty group + // later. + it->second->Clear(false); + if (!it->second->_Detached) { + it->second->_Detached = true; + _pGroupNode->removeChild(it->second->_pGroupNode); } - else { -#endif - // check if Element in group - DOMElement *pcElem = FindElement(_pGroupNode,"FCParamGroup",Name); - // if not return - if (!pcElem) - return; - - // remove group handle - _GroupMap.erase(Name); - - DOMNode* node = _pGroupNode->removeChild(pcElem); - node->release(); -#if 1 + if (it->second->ShouldRemove()) { + it->second->_Parent = nullptr; + it->second->_Manager = nullptr; + _GroupMap.erase(it); } -#endif // trigger observer Notify(Name); @@ -844,6 +1197,9 @@ void ParameterGrp::RemoveGrp(const char* Name) bool ParameterGrp::RenameGrp(const char* OldName, const char* NewName) { + if (!_pGroupNode) + return false; + auto it = _GroupMap.find(OldName); if (it == _GroupMap.end()) return false; @@ -861,43 +1217,58 @@ bool ParameterGrp::RenameGrp(const char* OldName, const char* NewName) if (pcElem) pcElem-> setAttribute(XStr("Name").unicodeForm(), XStr(NewName).unicodeForm()); + _Notify(ParamType::FCGroup, NewName, OldName); return true; } -void ParameterGrp::Clear() +void ParameterGrp::Clear(bool notify) { - std::vector vecNodes; + if (!_pGroupNode) + return; + + Base::StateLocker guard(_Clearing); + + // early trigger notification of group removal when all its children + // hierarchies are intact. + _Notify(ParamType::FCGroup, nullptr, nullptr); // checking on references - std::vector removeGrp; - for (auto it = _GroupMap.begin();it!=_GroupMap.end();++it) { - // If a group is referenced by some observer then do not remove it - // but clear it - if (!it->second->ShouldRemove()) { - it->second->Clear(); + for (auto it = _GroupMap.begin();it!=_GroupMap.end();) { + // If a group handle is referenced by some observer, then do not remove + // it but clear it, so that any existing observer can still get + // notification if the group is later on add back. We do remove the + // underlying xml element from its parent so that we won't save this + // empty group. + it->second->Clear(notify); + if (!it->second->_Detached) { + it->second->_Detached = true; + _pGroupNode->removeChild(it->second->_pGroupNode); } + if (!it->second->ShouldRemove()) + ++it; else { - removeGrp.push_back(it->first); + it->second->_Parent = nullptr; + it->second->_Manager = nullptr; + it = _GroupMap.erase(it); } } - // remove group handles - for (const auto& it : removeGrp) { - auto pos = _GroupMap.find(it); - vecNodes.push_back(pos->second->_pGroupNode); - _GroupMap.erase(pos->first); + // Remove the rest of non-group nodes; + std::vector> params; + for (DOMNode *child = _pGroupNode->getFirstChild(), *next = child; child != 0; child = next) { + next = next->getNextSibling(); + ParamType type = TypeValue(StrX(child->getNodeName()).c_str()); + if (type != ParamType::FCInvalid && type != ParamType::FCGroup) + params.emplace_back(type, StrX(child->getAttributes()->getNamedItem( + XStr("Name").unicodeForm())->getNodeValue()).c_str()); + DOMNode *node = _pGroupNode->removeChild(child); + node->release(); } - // searching all non-group nodes - for (DOMNode *child = _pGroupNode->getFirstChild(); child != nullptr; child = child->getNextSibling()) { - if (XMLString::compareString(child->getNodeName(), XStr("FCParamGroup").unicodeForm()) != 0) - vecNodes.push_back(child); - } - - // deleting the nodes - for (auto it = vecNodes.begin(); it != vecNodes.end(); ++it) { - DOMNode *child = _pGroupNode->removeChild(*it); - child->release(); + for (auto &v : params) { + _Notify(v.first, v.second.c_str(), nullptr); + if (notify) + Notify(v.second.c_str()); } // trigger observer @@ -963,26 +1334,14 @@ XERCES_CPP_NAMESPACE_QUALIFIER DOMElement *ParameterGrp::FindNextElement(XERCES_ return nullptr; } -XERCES_CPP_NAMESPACE_QUALIFIER DOMElement *ParameterGrp::FindOrCreateElement(XERCES_CPP_NAMESPACE_QUALIFIER DOMElement *Start, const char* Type, const char* Name) const +XERCES_CPP_NAMESPACE_QUALIFIER DOMElement *ParameterGrp::FindOrCreateElement(XERCES_CPP_NAMESPACE_QUALIFIER DOMElement *Start, const char* Type, const char* Name) { // first try to find it DOMElement *pcElem = FindElement(Start,Type,Name); if (pcElem) return pcElem; - if (XMLString::compareString(Start->getNodeName(), XStr("FCParamGroup").unicodeForm()) != 0 && - XMLString::compareString(Start->getNodeName(), XStr("FCParameters").unicodeForm()) != 0) { - Base::Console().Warning("FindOrCreateElement: %s cannot have the element %s of type %s\n", StrX(Start->getNodeName()).c_str(), Name, Type); - return nullptr; - } - - XERCES_CPP_NAMESPACE_QUALIFIER DOMDocument *pDocument = _pGroupNode->getOwnerDocument(); - - pcElem = pDocument->createElement(XStr(Type).unicodeForm()); - pcElem-> setAttribute(XStr("Name").unicodeForm(), XStr(Name).unicodeForm()); - Start->appendChild(pcElem); - - return pcElem; + return CreateElement(Start,Type,Name); } XERCES_CPP_NAMESPACE_QUALIFIER DOMNode *ParameterGrp::FindAttribute(XERCES_CPP_NAMESPACE_QUALIFIER DOMNode *Node, const char* Name) const @@ -994,6 +1353,33 @@ XERCES_CPP_NAMESPACE_QUALIFIER DOMNode *ParameterGrp::FindAttribute(XERCES_CPP_N return nullptr; } +std::vector > +ParameterGrp::GetParameterNames(const char * sFilter) const +{ + std::vector > res; + if (!_pGroupNode) + return res; + + std::string Name; + + for (DOMNode *clChild = _pGroupNode->getFirstChild(); + clChild != 0; clChild = clChild->getNextSibling()) { + if (clChild->getNodeType() == DOMNode::ELEMENT_NODE) { + StrX type(clChild->getNodeName()); + ParamType Type = TypeValue(type.c_str()); + if (Type != ParamType::FCInvalid && Type != ParamType::FCGroup) { + if (clChild->getAttributes()->getLength() > 0) { + StrX name(clChild->getAttributes()->getNamedItem( + XStr("Name").unicodeForm())->getNodeValue()); + if (!sFilter || strstr(name.c_str(), sFilter)) + res.emplace_back(Type, name.c_str()); + } + } + } + } + return res; +} + void ParameterGrp::NotifyAll() { // get all ints and notify @@ -1022,6 +1408,13 @@ void ParameterGrp::NotifyAll() Notify(It5->first.c_str()); } +void ParameterGrp::_Reset() +{ + _pGroupNode = nullptr; + for (auto &v : _GroupMap) + v.second->_Reset(); +} + //************************************************************************** //************************************************************************** // ParameterSerializer @@ -1063,6 +1456,8 @@ static XercesDOMParser::ValSchemes gValScheme = XercesDOMParser::Val_Au ParameterManager::ParameterManager() : ParameterGrp(), _pDocument(nullptr), paramSerializer(nullptr) { + _Manager = this; + // initialize the XML system Init(); @@ -1126,6 +1521,7 @@ ParameterManager::ParameterManager() */ ParameterManager::~ParameterManager() { + _Reset(); delete _pDocument; delete paramSerializer; } @@ -1175,6 +1571,12 @@ bool ParameterManager::HasSerializer() const return (paramSerializer != nullptr); } +const std::string & ParameterManager::GetSerializeFileName() const +{ + static const std::string _dummy; + return paramSerializer ? paramSerializer->GetFileName() : _dummy; +} + int ParameterManager::LoadDocument() { if (paramSerializer) @@ -1356,7 +1758,8 @@ void ParameterManager::SaveDocument(XMLFormatTarget* pFormatTarget) const if (gUseFilter) { myFilter.reset(new DOMPrintFilter(DOMNodeFilter::SHOW_ELEMENT | DOMNodeFilter::SHOW_ATTRIBUTE | - DOMNodeFilter::SHOW_DOCUMENT_TYPE + DOMNodeFilter::SHOW_DOCUMENT_TYPE | + DOMNodeFilter::SHOW_TEXT )); theSerializer->setFilter(myFilter.get()); } @@ -1536,6 +1939,13 @@ DOMPrintFilter::FilterAction DOMPrintFilter::acceptNode(const DOMNode* node) con break; } case DOMNode::TEXT_NODE: { + // Filter out text element if it is under a group node. Note text xml + // element is plain text in between tags, and we do not store any text + // there. + auto parent = node->getParentNode(); + if (parent && XMLString::compareString( + parent->getNodeName(), XStr("FCParamGroup").unicodeForm()) == 0) + return DOMNodeFilter::FILTER_REJECT; return DOMNodeFilter::FILTER_ACCEPT; break; } diff --git a/src/Base/Parameter.h b/src/Base/Parameter.h index 42cede3726..0d138e696b 100644 --- a/src/Base/Parameter.h +++ b/src/Base/Parameter.h @@ -51,6 +51,7 @@ using PyObject = struct _object; #include #include +#include #include #include "Handle.h" @@ -105,6 +106,10 @@ public: void importFrom(const char* FileName); /// insert from a file to this group, overwrite only the similar void insert(const char* FileName); + /// revert to default value by deleting any parameter that has the same value in the given file + void revert(const char* FileName); + /// revert to default value by deleting any parameter that has the same value in the given group + void revert(Base::Reference); //@} /** @name methods for group handling */ @@ -124,7 +129,37 @@ public: /// rename a sub group from this group bool RenameGrp(const char* OldName, const char* NewName); /// clears everything in this group (all types) - void Clear(); + /// @param notify: whether to notify on deleted parameters using the Observer interface. + void Clear(bool notify = false); + //@} + + /** @name methods for generic attribute handling */ + //@{ + enum class ParamType { + FCInvalid = 0, + FCText = 1, + FCBool = 2, + FCInt = 3, + FCUInt = 4, + FCFloat = 5, + FCGroup = 6, + }; + static const char *TypeName(ParamType type); + static ParamType TypeValue(const char *); + void SetAttribute(ParamType Type, const char *Name, const char *Value); + void RemoveAttribute(ParamType Type, const char *Name); + const char *GetAttribute(ParamType Type, + const char *Name, + std::string &Value, + const char *Default) const; + std::vector> + GetAttributeMap(ParamType Type, const char * sFilter = NULL) const; + /** Return the type and name of all parameters with optional filter + * @param sFilter only strings which name includes sFilter are put in the vector + * @return std::vector of pair(type, name) + */ + std::vector> + GetParameterNames(const char * sFilter = NULL) const; //@} /** @name methods for bool handling */ @@ -201,6 +236,8 @@ public: //@{ /// set a string value void SetASCII(const char* Name, const char *sValue); + /// set a string value + void SetASCII(const char* Name, const std::string &sValue) { SetASCII(Name, sValue.c_str()); } /// read a string values std::string GetASCII(const char* Name, const char * pPreset=nullptr) const; /// remove a string value from this group @@ -222,19 +259,33 @@ public: return _cName.c_str(); } + /// return the full path of this group + std::string GetPath() const; + void GetPath(std::string &) const; + /** Notifies all observers for all entries except of sub-groups. */ void NotifyAll(); + ParameterGrp *Parent() const {return _Parent;} + ParameterManager *Manager() const {return _Manager;} + protected: /// constructor is protected (handle concept) - ParameterGrp(XERCES_CPP_NAMESPACE_QUALIFIER DOMElement *GroupNode=nullptr,const char* sName=nullptr); + ParameterGrp(XERCES_CPP_NAMESPACE_QUALIFIER DOMElement *GroupNode=nullptr, + const char* sName=nullptr, + ParameterGrp *Parent=nullptr); /// destructor is protected (handle concept) ~ParameterGrp() override; /// helper function for GetGroup Base::Reference _GetGroup(const char* Name); bool ShouldRemove() const; + void _Reset(); + + void _SetAttribute(ParamType Type, const char *Name, const char *Value); + void _Notify(ParamType Type, const char *Name, const char *Value); + XERCES_CPP_NAMESPACE_QUALIFIER DOMElement *FindNextElement(XERCES_CPP_NAMESPACE_QUALIFIER DOMNode *Prev, const char* Type) const; /** Find an element specified by Type and Name @@ -250,7 +301,9 @@ protected: * element of Type and with the attribute Name=Name. On success it returns * the pointer to that element, otherwise it creates the element and returns the pointer. */ - XERCES_CPP_NAMESPACE_QUALIFIER DOMElement *FindOrCreateElement(XERCES_CPP_NAMESPACE_QUALIFIER DOMElement *Start, const char* Type, const char* Name) const; + XERCES_CPP_NAMESPACE_QUALIFIER DOMElement *FindOrCreateElement(XERCES_CPP_NAMESPACE_QUALIFIER DOMElement *Start, const char* Type, const char* Name); + + XERCES_CPP_NAMESPACE_QUALIFIER DOMElement *CreateElement(XERCES_CPP_NAMESPACE_QUALIFIER DOMElement *Start, const char* Type, const char* Name); /** Find an attribute specified by Name */ @@ -262,7 +315,15 @@ protected: std::string _cName; /// map of already exported groups std::map > _GroupMap; - + ParameterGrp * _Parent = nullptr; + ParameterManager *_Manager = nullptr; + /// Means this group xml element has not been added to its parent yet. + bool _Detached = false; + /** Indicate this group is currently being cleared + * + * This is used to prevent anynew value/sub-group to be added in observer + */ + bool _Clearing = false; }; /** The parameter serializer class @@ -281,6 +342,7 @@ public: virtual void SaveDocument(const ParameterManager&); virtual int LoadDocument(ParameterManager&); virtual bool LoadOrCreateDocument(ParameterManager&); + const std::string &GetFileName() const {return filename;} protected: std::string filename; @@ -299,6 +361,33 @@ public: static void Init(); static void Terminate(); + /** Signal on parameter changes + * + * The signal is triggered on adding, removing, renaming or modifying on + * all individual parameters and group. The signature of the signal is + * \code + * void (ParameterGrp *param, ParamType type, const char *name, const char *value) + * \endcode + * where 'param' is the parameter group causing the change, 'type' is the + * type of the parameter, 'name' is the name of the parameter, and 'value' + * is the current value. + * + * The possible values of 'type' are, 'FCBool', 'FCInt', 'FCUint', + * 'FCFloat', 'FCText', and 'FCParamGroup'. The notification is triggered + * when value is changed, in which case 'value' contains the new value in + * text form, or, when the parameter is removed, in which case 'value' is + * empty. + * + * For 'FCParamGroup' type, the observer will be notified in the following events. + * - Group creation: both 'name' and 'value' contain the name of the new group + * - Group removal: both 'name' and 'value' are empty + * - Group rename: 'name' is the new name, and 'value' is the old name + */ + boost::signals2::signal signalParamChanged; + int LoadDocument(const char* sFileName); int LoadDocument(const XERCES_CPP_NAMESPACE_QUALIFIER InputSource&); bool LoadOrCreateDocument(const char* sFileName); @@ -313,6 +402,8 @@ public: void SetSerializer(ParameterSerializer*); /// Returns true if a serializer is set, otherwise false is returned. bool HasSerializer() const; + /// Returns the filename of the serialize. + const std::string & GetSerializeFileName() const; /// Loads an XML document by calling the serializer's load method. int LoadDocument(); /// Loads or creates an XML document by calling the serializer's load method. diff --git a/src/Base/ParameterPy.cpp b/src/Base/ParameterPy.cpp index c5a50b5020..8a0a3dcb58 100644 --- a/src/Base/ParameterPy.cpp +++ b/src/Base/ParameterPy.cpp @@ -52,10 +52,15 @@ public: { inst = obj; } + ParameterGrpObserver(const Py::Object& obj, const Py::Object &callable, ParameterGrp *target) + : callable(callable), _target(target), inst(obj) + { + } ~ParameterGrpObserver() override { Base::PyGILStateLocker lock; inst = Py::None(); + callable = Py::None(); } void OnChange(ParameterGrp::SubjectType &rCaller,ParameterGrp::MessageType Reason) override { @@ -81,6 +86,11 @@ public: return this->inst.is(obj); } +public: + Py::Object callable; + boost::signals2::scoped_connection conn; + ParameterGrp *_target = nullptr; // no reference counted, do not access + private: Py::Object inst; }; @@ -103,10 +113,14 @@ public: Py::Object remGroup(const Py::Tuple&); Py::Object hasGroup(const Py::Tuple&); + Py::Object getManager(const Py::Tuple&); + Py::Object getParent(const Py::Tuple&); + Py::Object isEmpty(const Py::Tuple&); Py::Object clear(const Py::Tuple&); Py::Object attach(const Py::Tuple&); + Py::Object attachManager(const Py::Tuple& args); Py::Object detach(const Py::Tuple&); Py::Object notify(const Py::Tuple&); Py::Object notifyAll(const Py::Tuple&); @@ -165,10 +179,31 @@ void ParameterGrpPy::init_type() add_varargs_method("RemGroup",&ParameterGrpPy::remGroup,"RemGroup(str)"); add_varargs_method("HasGroup",&ParameterGrpPy::hasGroup,"HasGroup(str)"); + add_varargs_method("Manager",&ParameterGrpPy::getManager,"Manager()"); + add_varargs_method("Parent",&ParameterGrpPy::getParent,"Parent()"); + add_varargs_method("IsEmpty",&ParameterGrpPy::isEmpty,"IsEmpty()"); add_varargs_method("Clear",&ParameterGrpPy::clear,"Clear()"); add_varargs_method("Attach",&ParameterGrpPy::attach,"Attach()"); + add_varargs_method("AttachManager",&ParameterGrpPy::attachManager, + "AttachManager(observer) -- attach parameter manager for notification\n\n" + "This method attaches a user defined observer to the manager (i.e. the root)\n" + "of the current parameter group to receive notification of all its parameters\n" + "and those from its sub-groups\n\n" + "The method expects the observer to have a callable attribute as shown below\n" + " slotParamChanged(param, tp, name, value)\n" + "where 'param' is the parameter group causing the change, 'tp' is the type of\n" + "the parameter, 'name' is the name of the parameter, and 'value' is the current\n" + "value.\n\n" + "The possible value of type are, 'FCBool', 'FCInt', 'FCUint', 'FCFloat', 'FCText',\n" + "and 'FCParamGroup'. The notification is triggered when value is changed, in which\n" + "case 'value' contains the new value in text form, or, when the parameter is removed,\n" + "in which case 'value' is empty.\n\n" + "For 'FCParamGroup' type, the observer will be notified in the following events.\n" + "* Group creation: both 'name' and 'value' contain the name of the new group\n" + "* Group removal: both 'name' and 'value' are empty\n" + "* Group rename: 'name' is the new name, and 'value' is the old name"); add_varargs_method("Detach",&ParameterGrpPy::detach,"Detach()"); add_varargs_method("Notify",&ParameterGrpPy::notify,"Notify()"); add_varargs_method("NotifyAll",&ParameterGrpPy::notifyAll,"NotifyAll()"); @@ -214,7 +249,8 @@ ParameterGrpPy::~ParameterGrpPy() { for (ParameterGrpObserverList::iterator it = _observers.begin(); it != _observers.end(); ++it) { ParameterGrpObserver* obs = *it; - _cParamGrp->Detach(obs); + if (!obs->_target) + _cParamGrp->Detach(obs); delete obs; } } @@ -281,6 +317,40 @@ Py::Object ParameterGrpPy::getGroup(const Py::Tuple& args) } } +Py::Object ParameterGrpPy::getManager(const Py::Tuple& args) +{ + if (!PyArg_ParseTuple(args.ptr(), "")) + throw Py::Exception(); + + // get the Handle of the wanted group + Base::Reference handle = _cParamGrp->Manager(); + if (handle.isValid()) { + // create a python wrapper class + ParameterGrpPy *pcParamGrp = new ParameterGrpPy(handle); + // increment the ref count + return Py::asObject(pcParamGrp); + } + else + return Py::Object(); +} + +Py::Object ParameterGrpPy::getParent(const Py::Tuple& args) +{ + if (!PyArg_ParseTuple(args.ptr(), "")) + throw Py::Exception(); + + // get the Handle of the wanted group + Base::Reference handle = _cParamGrp->Parent(); + if (handle.isValid()) { + // create a python wrapper class + ParameterGrpPy *pcParamGrp = new ParameterGrpPy(handle); + // increment the ref count + return Py::asObject(pcParamGrp); + } + else + return Py::Object(); +} + Py::Object ParameterGrpPy::getGroupName(const Py::Tuple& args) { if (!PyArg_ParseTuple(args.ptr(), "")) @@ -593,6 +663,56 @@ Py::Object ParameterGrpPy::attach(const Py::Tuple& args) return Py::None(); } +Py::Object ParameterGrpPy::attachManager(const Py::Tuple& args) +{ + PyObject* obj; + if (!PyArg_ParseTuple(args.ptr(), "O", &obj)) + throw Py::Exception(); + + if (!_cParamGrp->Manager()) + throw Py::RuntimeError("Parameter has no manager"); + + Py::Object o(obj); + if (!o.hasAttr(std::string("slotParamChanged"))) + throw Py::TypeError("Object has no slotParamChanged attribute"); + + Py::Object attr(o.getAttr("slotParamChanged")); + if (!attr.isCallable()) + throw Py::TypeError("Object has no slotParamChanged callable attribute"); + + for (ParameterGrpObserverList::iterator it = _observers.begin(); it != _observers.end(); ++it) { + if ((*it)->isEqual(o)) { + throw Py::RuntimeError("Object is already attached."); + } + } + + ParameterGrpObserver* obs = new ParameterGrpObserver(o, attr, _cParamGrp); + obs->conn = _cParamGrp->Manager()->signalParamChanged.connect( + [obs](ParameterGrp *Param, ParameterGrp::ParamType Type, const char *Name, const char *Value) { + if (!Param) return; + for (auto p = Param; p; p = p->Parent()) { + if (p == obs->_target) { + Base::PyGILStateLocker lock; + Py::TupleN args( + Py::asObject(new ParameterGrpPy(Param)), + Py::String(ParameterGrp::TypeName(Type)), + Py::String(Name ? Name : ""), + Py::String(Value ? Value : "")); + try { + Py::Callable(obs->callable).apply(args); + } catch (Py::Exception &) { + Base::PyException e; + e.ReportException(); + } + break; + } + } + }); + + _observers.push_back(obs); + return Py::None(); +} + Py::Object ParameterGrpPy::detach(const Py::Tuple& args) { PyObject* obj; diff --git a/src/Gui/DlgParameterImp.cpp b/src/Gui/DlgParameterImp.cpp index fc702d5454..3b4457497b 100644 --- a/src/Gui/DlgParameterImp.cpp +++ b/src/Gui/DlgParameterImp.cpp @@ -92,7 +92,7 @@ DlgParameterImp::DlgParameterImp( QWidget* parent, Qt::WindowFlags fl ) #endif ParameterManager* sys = App::GetApplication().GetParameterSet("System parameter"); - const std::map& rcList = App::GetApplication().GetParameterSetList(); + const auto& rcList = App::GetApplication().GetParameterSetList(); for (const auto & it : rcList) { if (it.second != sys) // for now ignore system parameters because they are nowhere used ui->parameterSet->addItem(tr(it.first.c_str()), QVariant(QByteArray(it.first.c_str())));