From 00166c23461367bb5dac540ea49386a83a82de68 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Thu, 4 Jul 2019 17:25:55 +0800 Subject: [PATCH] DocumentObject: add allowDuplicateLabel/onBeforeChangeLabel() These two APIs allow an object to control its own labeling rules. The previous auto re-labeling for uniqueness logic is moved from ObjectLabelObserver (resides in Application.cpp) to PropertyString. DocumentObject::allowDuplicateLabel() is used to inform PropertyString that the object allows duplicate label regardless of application setting. onBeforeChangeLabel() is called before actual label change to give object a chance to override the new label. --- src/App/Application.cpp | 106 ------------------ src/App/DocumentObject.h | 11 ++ src/App/DocumentObjectPy.xml | 6 ++ src/App/DocumentObjectPyImp.cpp | 4 + src/App/PropertyStandard.cpp | 184 +++++++++++++++++++++++++++++--- src/App/PropertyStandard.h | 2 +- 6 files changed, 190 insertions(+), 123 deletions(-) diff --git a/src/App/Application.cpp b/src/App/Application.cpp index 439e3b53ef..846db470ef 100644 --- a/src/App/Application.cpp +++ b/src/App/Application.cpp @@ -155,35 +155,6 @@ using namespace Base; using namespace App; using namespace std; -/** Observer that watches relabeled objects and make sure that the labels inside - * a document are unique. - * @note In the FreeCAD design it is explicitly allowed to have duplicate labels - * (i.e. the user visible text e.g. in the tree view) while the internal names - * are always guaranteed to be unique. - */ -class ObjectLabelObserver -{ -public: - /// The one and only instance. - static ObjectLabelObserver* instance(); - /// Destructs the sole instance. - static void destruct (); - - /** Checks the new label of the object and relabel it if needed - * to make it unique document-wide - */ - void slotRelabelObject(const App::DocumentObject&, const App::Property&); - -private: - static ObjectLabelObserver* _singleton; - - ObjectLabelObserver(); - ~ObjectLabelObserver(); - const App::DocumentObject* current; - ParameterGrp::handle _hPGrp; -}; - - //========================================================================== // Application //========================================================================== @@ -1915,7 +1886,6 @@ void Application::initApplication(void) try { Interpreter().runString(Base::ScriptFactory().ProduceScript("CMakeVariables")); Interpreter().runString(Base::ScriptFactory().ProduceScript("FreeCADInit")); - ObjectLabelObserver::instance(); } catch (const Base::Exception& e) { e.ReportException(); @@ -2862,79 +2832,3 @@ std::string Application::FindHomePath(const char* sCall) # error "std::string Application::FindHomePath(const char*) not implemented" #endif -ObjectLabelObserver* ObjectLabelObserver::_singleton = 0; - -ObjectLabelObserver* ObjectLabelObserver::instance() -{ - if (!_singleton) - _singleton = new ObjectLabelObserver; - return _singleton; -} - -void ObjectLabelObserver::destruct () -{ - delete _singleton; - _singleton = 0; -} - -void ObjectLabelObserver::slotRelabelObject(const App::DocumentObject& obj, const App::Property& prop) -{ - // observe only the Label property - if (&prop == &obj.Label) { - // have we processed this (or another?) object right now? - if (current) { - return; - } - - std::string label = obj.Label.getValue(); - App::Document* doc = obj.getDocument(); - if (doc && !_hPGrp->GetBool("DuplicateLabels")) { - std::vector objectLabels; - std::vector::const_iterator it; - std::vector objs = doc->getObjects(); - bool match = false; - - for (it = objs.begin();it != objs.end();++it) { - if (*it == &obj) - continue; // don't compare object with itself - std::string objLabel = (*it)->Label.getValue(); - if (!match && objLabel == label) - match = true; - objectLabels.push_back(objLabel); - } - - // make sure that there is a name conflict otherwise we don't have to do anything - if (match && !label.empty()) { - // remove number from end to avoid lengthy names - size_t lastpos = label.length()-1; - while (label[lastpos] >= 48 && label[lastpos] <= 57) { - // if 'lastpos' becomes 0 then all characters are digits. In this case we use - // the complete label again - if (lastpos == 0) { - lastpos = label.length()-1; - break; - } - lastpos--; - } - - label = label.substr(0, lastpos+1); - label = Base::Tools::getUniqueName(label, objectLabels, 3); - this->current = &obj; - const_cast(obj).Label.setValue(label); - this->current = 0; - } - } - } -} - -ObjectLabelObserver::ObjectLabelObserver() : current(0) -{ - App::GetApplication().signalChangedObject.connect(boost::bind - (&ObjectLabelObserver::slotRelabelObject, this, _1, _2)); - _hPGrp = App::GetApplication().GetUserParameter().GetGroup("BaseApp"); - _hPGrp = _hPGrp->GetGroup("Preferences")->GetGroup("Document"); -} - -ObjectLabelObserver::~ObjectLabelObserver() -{ -} diff --git a/src/App/DocumentObject.h b/src/App/DocumentObject.h index fc620242b8..88a2d63d11 100644 --- a/src/App/DocumentObject.h +++ b/src/App/DocumentObject.h @@ -341,6 +341,17 @@ public: /* Return true to cause PropertyView to show linked object's property */ virtual bool canLinkProperties() const {return true;} + /* Return true to bypass duplicate label checking */ + virtual bool allowDuplicateLabel() const {return false;} + + /*** Called to let object itself control relabeling + * + * @param newLabel: input as the new label, which can be modified by object itself + * + * This function is is called before onBeforeChange() + */ + virtual void onBeforeChangeLabel(std::string &newLabel) {(void)newLabel;} + friend class Document; friend class Transaction; friend class ObjectExecution; diff --git a/src/App/DocumentObjectPy.xml b/src/App/DocumentObjectPy.xml index 77ab7a99c2..e023fb8c67 100644 --- a/src/App/DocumentObjectPy.xml +++ b/src/App/DocumentObjectPy.xml @@ -262,5 +262,11 @@ or None if the GUI is not up + + + Contains the old label before change + + + diff --git a/src/App/DocumentObjectPyImp.cpp b/src/App/DocumentObjectPyImp.cpp index b6911a60fa..50f3d65ae7 100644 --- a/src/App/DocumentObjectPyImp.cpp +++ b/src/App/DocumentObjectPyImp.cpp @@ -834,3 +834,7 @@ PyObject *DocumentObjectPy::adjustRelativeLinks(PyObject *args) { PyObject_IsTrue(recursive)?&visited:nullptr))); }PY_CATCH } + +Py::String DocumentObjectPy::getOldLabel() const { + return Py::String(getDocumentObjectPtr()->getOldLabel()); +} diff --git a/src/App/PropertyStandard.cpp b/src/App/PropertyStandard.cpp index 9d4edecbaf..063d59df99 100644 --- a/src/App/PropertyStandard.cpp +++ b/src/App/PropertyStandard.cpp @@ -31,6 +31,7 @@ /// Here the FreeCAD includes sorted by Base,App,Gui...... #include +#include #include #include @@ -38,10 +39,15 @@ #include #include #include +#include #include "PropertyStandard.h" +#include "PropertyLinks.h" #include "MaterialPy.h" #include "ObjectIdentifier.h" +#include "Application.h" +#include "Document.h" +#include "DocumentObject.h" using namespace App; using namespace Base; @@ -767,7 +773,7 @@ Property *PropertyIntegerList::Copy(void) const void PropertyIntegerList::Paste(const Property &from) { - hasSetValue(); + setValues(dynamic_cast(from)._lValueList); } unsigned int PropertyIntegerList::getMemSize (void) const @@ -1319,20 +1325,125 @@ PropertyString::~PropertyString() } -void PropertyString::setValue(const char* sString) +void PropertyString::setValue(const char* newLabel) { - if (sString) { - aboutToSetValue(); - _cValue = sString; - hasSetValue(); + if(!newLabel) return; + + if(_cValue == newLabel) + return; + + std::string _newLabel; + + std::vector > > propChanges; + std::string label; + auto obj = dynamic_cast(getContainer()); + bool commit = false; + + if(obj && obj->getNameInDocument() && this==&obj->Label && + (!obj->getDocument()->testStatus(App::Document::Restoring)|| + obj->getDocument()->testStatus(App::Document::Importing)) && + !obj->getDocument()->isPerformingTransaction()) + { + // allow object to control label change + + static ParameterGrp::handle _hPGrp; + if(!_hPGrp) { + _hPGrp = GetApplication().GetUserParameter().GetGroup("BaseApp"); + _hPGrp = _hPGrp->GetGroup("Preferences")->GetGroup("Document"); + } + App::Document* doc = obj->getDocument(); + if(doc && !_hPGrp->GetBool("DuplicateLabels") && !obj->allowDuplicateLabel()) { + std::vector objectLabels; + std::vector::const_iterator it; + std::vector objs = doc->getObjects(); + bool match = false; + for (it = objs.begin();it != objs.end();++it) { + if (*it == obj) + continue; // don't compare object with itself + std::string objLabel = (*it)->Label.getValue(); + if (!match && objLabel == newLabel) + match = true; + objectLabels.push_back(objLabel); + } + + // make sure that there is a name conflict otherwise we don't have to do anything + if (match && *newLabel) { + label = newLabel; + // remove number from end to avoid lengthy names + size_t lastpos = label.length()-1; + while (label[lastpos] >= 48 && label[lastpos] <= 57) { + // if 'lastpos' becomes 0 then all characters are digits. In this case we use + // the complete label again + if (lastpos == 0) { + lastpos = label.length()-1; + break; + } + lastpos--; + } + + bool changed = false; + label = label.substr(0,lastpos+1); + if(label != obj->getNameInDocument() + && boost::starts_with(obj->getNameInDocument(),label)) + { + // In case the label has the same base name as object's + // internal name, use it as the label instead. + const char *objName = obj->getNameInDocument(); + const char *c = &objName[lastpos+1]; + for(;*c;++c) { + if(*c<48 || *c>57) + break; + } + if(*c == 0 && std::find(objectLabels.begin(), objectLabels.end(), + obj->getNameInDocument())==objectLabels.end()) + { + label = obj->getNameInDocument(); + changed = true; + } + } + if(!changed) + label = Base::Tools::getUniqueName(label, objectLabels, 3); + } + } + + if(label.empty()) + label = newLabel; + obj->onBeforeChangeLabel(label); + newLabel = label.c_str(); + + if(!obj->getDocument()->testStatus(App::Document::Restoring)) { + // Only update label reference if we are not restoring. When + // importing (which also counts as restoring), it is possible the + // new object changes its label. However, we cannot update label + // references here, because object restoring is not based on + // dependency order. It can only be done in afterRestore(). + // + // See PropertyLinkBase::restoreLabelReference() for more details. + propChanges = PropertyLinkBase::updateLabelReferences(obj,newLabel); + } + + if(propChanges.size() && !GetApplication().getActiveTransaction()) { + commit = true; + std::ostringstream str; + str << "Change " << obj->getNameInDocument() << ".Label"; + GetApplication().setActiveTransaction(str.str().c_str()); + } } + + aboutToSetValue(); + _cValue = newLabel; + hasSetValue(); + + for(auto &change : propChanges) + change.first->Paste(*change.second.get()); + + if(commit) + GetApplication().closeActiveTransaction(); } void PropertyString::setValue(const std::string &sString) { - aboutToSetValue(); - _cValue = sString; - hasSetValue(); + setValue(sString.c_str()); } const char* PropertyString::getValue(void) const @@ -1374,8 +1485,24 @@ void PropertyString::setPyObject(PyObject *value) void PropertyString::Save (Base::Writer &writer) const { - std::string val = encodeAttribute(_cValue); - writer.Stream() << writer.ind() << "" << std::endl; + std::string val; + auto obj = dynamic_cast(getContainer()); + writer.Stream() << writer.ind() << "getNameInDocument() && + obj->isExporting() && &obj->Label==this) + { + if(obj->allowDuplicateLabel()) + writer.Stream() <<"restore=\"1\" "; + else if(_cValue==obj->getNameInDocument()) { + writer.Stream() <<"restore=\"0\" "; + val = encodeAttribute(obj->getExportName()); + exported = true; + } + } + if(!exported) + val = encodeAttribute(_cValue); + writer.Stream() <<"value=\"" << val <<"\"/>" << std::endl; } void PropertyString::Restore(Base::XMLReader &reader) @@ -1383,7 +1510,20 @@ void PropertyString::Restore(Base::XMLReader &reader) // read my Element reader.readElement("String"); // get the value of my Attribute - setValue(reader.getAttribute("value")); + auto obj = dynamic_cast(getContainer()); + if(obj && &obj->Label==this) { + if(reader.hasAttribute("restore")) { + int restore = reader.getAttributeAsInteger("restore"); + if(restore == 1) { + aboutToSetValue(); + _cValue = reader.getAttribute("value"); + hasSetValue(); + }else + setValue(reader.getName(reader.getAttribute("value"))); + } else + setValue(reader.getAttribute("value")); + }else + setValue(reader.getAttribute("value")); } Property *PropertyString::Copy(void) const @@ -1395,9 +1535,7 @@ Property *PropertyString::Copy(void) const void PropertyString::Paste(const Property &from) { - aboutToSetValue(); - _cValue = dynamic_cast(from)._cValue; - hasSetValue(); + setValue(dynamic_cast(from)._cValue); } unsigned int PropertyString::getMemSize (void) const @@ -1405,9 +1543,23 @@ unsigned int PropertyString::getMemSize (void) const return static_cast(_cValue.size()); } -void PropertyString::setPathValue(const ObjectIdentifier &path, const boost::any & /*value*/) +void PropertyString::setPathValue(const ObjectIdentifier &path, const boost::any &value) { verifyPath(path); + if (value.type() == typeid(bool)) + setValue(boost::any_cast(value)?"True":"False"); + else if (value.type() == typeid(int)) + setValue(std::to_string(boost::any_cast(value))); + else if (value.type() == typeid(double)) + setValue(std::to_string(boost::math::round(App::any_cast(value)))); + else if (value.type() == typeid(Quantity)) + setValue(boost::any_cast(value).getUserString().toUtf8().constData()); + else if (value.type() == typeid(std::string)) + setValue(boost::any_cast(value)); + else { + Base::PyGILStateLocker lock; + setValue(pyObjectFromAny(value).as_string()); + } } const boost::any PropertyString::getPathValue(const ObjectIdentifier &path) const diff --git a/src/App/PropertyStandard.h b/src/App/PropertyStandard.h index 0670202b9a..b8eaf1798d 100644 --- a/src/App/PropertyStandard.h +++ b/src/App/PropertyStandard.h @@ -660,7 +660,7 @@ public: void setPathValue(const App::ObjectIdentifier &path, const boost::any &value); const boost::any getPathValue(const App::ObjectIdentifier &path) const; -private: +protected: std::string _cValue; };