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.
This commit is contained in:
@@ -31,6 +31,7 @@
|
||||
|
||||
/// Here the FreeCAD includes sorted by Base,App,Gui......
|
||||
#include <boost/math/special_functions/round.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
||||
#include <Base/Console.h>
|
||||
#include <Base/Exception.h>
|
||||
@@ -38,10 +39,15 @@
|
||||
#include <Base/Writer.h>
|
||||
#include <Base/Stream.h>
|
||||
#include <Base/Quantity.h>
|
||||
#include <Base/Tools.h>
|
||||
|
||||
#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<const PropertyIntegerList&>(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<std::pair<Property*,std::unique_ptr<Property> > > propChanges;
|
||||
std::string label;
|
||||
auto obj = dynamic_cast<DocumentObject*>(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<std::string> objectLabels;
|
||||
std::vector<App::DocumentObject*>::const_iterator it;
|
||||
std::vector<App::DocumentObject*> 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() << "<String value=\"" << val <<"\"/>" << std::endl;
|
||||
std::string val;
|
||||
auto obj = dynamic_cast<DocumentObject*>(getContainer());
|
||||
writer.Stream() << writer.ind() << "<String ";
|
||||
bool exported = false;
|
||||
if(obj && obj->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<DocumentObject*>(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<const PropertyString&>(from)._cValue;
|
||||
hasSetValue();
|
||||
setValue(dynamic_cast<const PropertyString&>(from)._cValue);
|
||||
}
|
||||
|
||||
unsigned int PropertyString::getMemSize (void) const
|
||||
@@ -1405,9 +1543,23 @@ unsigned int PropertyString::getMemSize (void) const
|
||||
return static_cast<unsigned int>(_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<bool>(value)?"True":"False");
|
||||
else if (value.type() == typeid(int))
|
||||
setValue(std::to_string(boost::any_cast<int>(value)));
|
||||
else if (value.type() == typeid(double))
|
||||
setValue(std::to_string(boost::math::round(App::any_cast<double>(value))));
|
||||
else if (value.type() == typeid(Quantity))
|
||||
setValue(boost::any_cast<Quantity>(value).getUserString().toUtf8().constData());
|
||||
else if (value.type() == typeid(std::string))
|
||||
setValue(boost::any_cast<const std::string &>(value));
|
||||
else {
|
||||
Base::PyGILStateLocker lock;
|
||||
setValue(pyObjectFromAny(value).as_string());
|
||||
}
|
||||
}
|
||||
|
||||
const boost::any PropertyString::getPathValue(const ObjectIdentifier &path) const
|
||||
|
||||
Reference in New Issue
Block a user