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:
@@ -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<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 == 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<App::DocumentObject&>(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()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -262,5 +262,11 @@ or None if the GUI is not up</UserDocu>
|
||||
</Documentation>
|
||||
<Parameter Name="Parents" Type="List"/>
|
||||
</Attribute>
|
||||
<Attribute Name="OldLabel" ReadOnly="true">
|
||||
<Documentation>
|
||||
<UserDocu>Contains the old label before change</UserDocu>
|
||||
</Documentation>
|
||||
<Parameter Name="OldLabel" Type="String"/>
|
||||
</Attribute>
|
||||
</PythonExport>
|
||||
</GenerateModel>
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user