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

@@ -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()
{
}

View File

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

View File

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

View File

@@ -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());
}

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

View File

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