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:
Zheng, Lei
2019-07-04 17:25:55 +08:00
committed by wmayer
parent b4751145b4
commit 00166c2346
6 changed files with 190 additions and 123 deletions

View File

@@ -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