1
/***************************************************************************
2
* Copyright (c) 2002 Jürgen Riegel <juergen.riegel@web.de> *
4
* This file is part of the FreeCAD CAx development system. *
6
* This library is free software; you can redistribute it and/or *
7
* modify it under the terms of the GNU Library General Public *
8
* License as published by the Free Software Foundation; either *
9
* version 2 of the License, or (at your option) any later version. *
11
* This library is distributed in the hope that it will be useful, *
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14
* GNU Library General Public License for more details. *
16
* You should have received a copy of the GNU Library General Public *
17
* License along with this library; see the file COPYING.LIB. If not, *
18
* write to the Free Software Foundation, Inc., 59 Temple Place, *
19
* Suite 330, Boston, MA 02111-1307, USA *
21
***************************************************************************/
24
#include "PreCompiled.h"
26
#include <Base/Console.h>
27
#include <Base/Exception.h>
28
#include <Base/Reader.h>
29
#include <Base/Writer.h>
32
#include "PropertyContainer.h"
35
FC_LOG_LEVEL_INIT("App",true,true)
41
TYPESYSTEM_SOURCE(App::PropertyContainer,Base::Persistence)
44
//**************************************************************************
45
// Construction/Destruction
47
// Here's the implementation! Description should take place in the header file!
48
PropertyContainer::PropertyContainer()
50
propertyData.parentPropertyData = nullptr;
53
PropertyContainer::~PropertyContainer() = default;
55
unsigned int PropertyContainer::getMemSize () const
57
std::map<std::string,Property*> Map;
59
std::map<std::string,Property*>::const_iterator It;
60
unsigned int size = 0;
61
for (It = Map.begin(); It != Map.end();++It)
62
size += It->second->getMemSize();
67
App::Property* PropertyContainer::addDynamicProperty(
68
const char* type, const char* name, const char* group, const char* doc,
69
short attr, bool ro, bool hidden)
71
return dynamicProps.addDynamicProperty(*this,type,name,group,doc,attr,ro,hidden);
74
Property *PropertyContainer::getPropertyByName(const char* name) const
76
auto prop = dynamicProps.getDynamicPropertyByName(name);
80
return getPropertyData().getPropertyByName(this,name);
83
void PropertyContainer::getPropertyMap(std::map<std::string,Property*> &Map) const
85
dynamicProps.getPropertyMap(Map);
86
getPropertyData().getPropertyMap(this,Map);
89
void PropertyContainer::getPropertyList(std::vector<Property*> &List) const
91
dynamicProps.getPropertyList(List);
92
getPropertyData().getPropertyList(this,List);
95
void PropertyContainer::getPropertyNamedList(std::vector<std::pair<const char*, Property*> > &List) const
97
dynamicProps.getPropertyNamedList(List);
98
getPropertyData().getPropertyNamedList(this,List);
101
void PropertyContainer::setPropertyStatus(unsigned char bit,bool value)
103
std::vector<Property*> List;
104
getPropertyList(List);
106
it->StatusBits.set(bit,value);
109
short PropertyContainer::getPropertyType(const Property* prop) const
111
return prop?prop->getType():0;
114
short PropertyContainer::getPropertyType(const char *name) const
116
return getPropertyType(getPropertyByName(name));
119
const char* PropertyContainer::getPropertyGroup(const Property* prop) const
121
auto group = dynamicProps.getPropertyGroup(prop);
124
return getPropertyData().getGroup(this,prop);
127
const char* PropertyContainer::getPropertyGroup(const char *name) const
129
auto group = dynamicProps.getPropertyGroup(name);
132
return getPropertyData().getGroup(this,name);
135
const char* PropertyContainer::getPropertyDocumentation(const Property* prop) const
137
auto doc = dynamicProps.getPropertyDocumentation(prop);
140
return getPropertyData().getDocumentation(this,prop);
143
const char* PropertyContainer::getPropertyDocumentation(const char *name) const
145
auto doc = dynamicProps.getPropertyDocumentation(name);
148
return getPropertyData().getDocumentation(this,name);
151
bool PropertyContainer::isReadOnly(const Property* prop) const
153
return (getPropertyType(prop) & Prop_ReadOnly) == Prop_ReadOnly;
156
bool PropertyContainer::isReadOnly(const char *name) const
158
return (getPropertyType(name) & Prop_ReadOnly) == Prop_ReadOnly;
161
bool PropertyContainer::isHidden(const Property* prop) const
163
return (getPropertyType(prop) & Prop_Hidden) == Prop_Hidden;
166
bool PropertyContainer::isHidden(const char *name) const
168
return (getPropertyType(name) & Prop_Hidden) == Prop_Hidden;
171
const char* PropertyContainer::getPropertyName(const Property* prop)const
173
auto res = dynamicProps.getPropertyName(prop);
175
res = getPropertyData().getName(this,prop);
179
const PropertyData * PropertyContainer::getPropertyDataPtr(){return &propertyData;}
180
const PropertyData & PropertyContainer::getPropertyData() const{return propertyData;}
184
* @brief PropertyContainer::handleChangedPropertyName is called during restore to possibly
185
* fix reading of older versions of this property container. This method is typically called
186
* if the property on file has changed its name in more recent versions.
188
* The default implementation does nothing.
190
* @param reader The XML stream to read from.
191
* @param TypeName Name of property type on file.
192
* @param PropName Name of property on file that does not exist in the container anymore.
195
void PropertyContainer::handleChangedPropertyName(Base::XMLReader &reader, const char * TypeName, const char *PropName)
203
* @brief PropertyContainer::handleChangedPropertyType is called during restore to possibly
204
* fix reading of older versions of the property container. This method is typically called
205
* if the property on file has changed its type in more recent versions.
207
* The default implementation does nothing.
209
* @param reader The XML stream to read from.
210
* @param TypeName Name of property type on file.
211
* @param prop Pointer to property to restore. Its type differs from TypeName.
214
void PropertyContainer::handleChangedPropertyType(XMLReader &reader, const char *TypeName, Property *prop)
221
PropertyData PropertyContainer::propertyData;
223
void PropertyContainer::beforeSave() const
225
std::map<std::string, Property*> Map;
227
for (auto& entry : Map) {
228
auto prop = entry.second;
229
if (!prop->testStatus(Property::PropDynamic)
230
&& (prop->testStatus(Property::Transient)
231
|| ((getPropertyType(prop) & Prop_Transient) != 0))) {
240
void PropertyContainer::Save (Base::Writer &writer) const
242
std::map<std::string,Property*> Map;
245
std::vector<Property*> transients;
246
for(auto it=Map.begin();it!=Map.end();) {
247
auto prop = it->second;
248
if(prop->testStatus(Property::PropNoPersist)) {
252
if(!prop->testStatus(Property::PropDynamic)
253
&& (prop->testStatus(Property::Transient) ||
254
getPropertyType(prop) & Prop_Transient))
256
transients.push_back(prop);
263
writer.incInd(); // indentation for 'Properties Count'
264
writer.Stream() << writer.ind() << "<Properties Count=\"" << Map.size()
265
<< "\" TransientCount=\"" << transients.size() << "\">" << endl;
267
// First store transient properties to persist their status value. We use
268
// a new element named "_Property" so that the save file can be opened by
269
// older versions of FC.
271
for(auto prop : transients) {
272
writer.Stream() << writer.ind() << "<_Property name=\"" << prop->getName()
273
<< "\" type=\"" << prop->getTypeId().getName()
274
<< "\" status=\"" << prop->getStatus() << "\"/>" << std::endl;
278
// Now store normal properties
279
for (const auto& it : Map)
281
writer.incInd(); // indentation for 'Property name'
282
writer.Stream() << writer.ind() << "<Property name=\"" << it.first << "\" type=\""
283
<< it.second->getTypeId().getName();
285
dynamicProps.save(it.second,writer);
287
auto status = it.second->getStatus();
289
writer.Stream() << "\" status=\"" << status;
290
writer.Stream() << "\">";
292
if(it.second->testStatus(Property::Transient)
293
|| it.second->getType() & Prop_Transient)
296
writer.Stream() << "</Property>" << std::endl;
300
writer.Stream() << std::endl;
302
writer.incInd(); // indentation for the actual property
305
// We must make sure to handle all exceptions accordingly so that
306
// the project file doesn't get invalidated. In the error case this
307
// means to proceed instead of aborting the write operation.
308
it.second->Save(writer);
310
catch (const Base::Exception &e) {
311
Base::Console().Error("%s\n", e.what());
313
catch (const std::exception &e) {
314
Base::Console().Error("%s\n", e.what());
316
catch (const char* e) {
317
Base::Console().Error("%s\n", e);
321
Base::Console().Error("PropertyContainer::Save: Unknown C++ exception thrown. Try to continue...\n");
324
writer.decInd(); // indentation for the actual property
325
writer.Stream() << writer.ind() << "</Property>" << endl;
326
writer.decInd(); // indentation for 'Property name'
328
writer.Stream() << writer.ind() << "</Properties>" << endl;
329
writer.decInd(); // indentation for 'Properties Count'
332
void PropertyContainer::Restore(Base::XMLReader &reader)
334
reader.clearPartialRestoreProperty();
335
reader.readElement("Properties");
336
int Cnt = reader.getAttributeAsInteger("Count");
338
int transientCount = 0;
339
if(reader.hasAttribute("TransientCount"))
340
transientCount = reader.getAttributeAsUnsigned("TransientCount");
342
for (int i=0;i<transientCount; ++i) {
343
reader.readElement("_Property");
344
Property* prop = getPropertyByName(reader.getAttribute("name"));
346
FC_TRACE("restore transient '" << prop->getName() << "'");
347
if(prop && reader.hasAttribute("status"))
348
prop->setStatusValue(reader.getAttributeAsUnsigned("status"));
351
for (int i=0 ;i<Cnt ;i++) {
352
reader.readElement("Property");
353
std::string PropName = reader.getAttribute("name");
354
std::string TypeName = reader.getAttribute("type");
355
// NOTE: We must also check the type of the current property because a
356
// subclass of PropertyContainer might change the type of a property but
357
// not its name. In this case we would force to read-in a wrong property
358
// type and the behaviour would be undefined.
360
auto prop = getPropertyByName(PropName.c_str());
361
if (!prop || prop->getContainer() != this) {
362
prop = dynamicProps.restore(*this,PropName.c_str(),TypeName.c_str(),reader);
365
decltype(Property::StatusBits) status;
366
if(reader.hasAttribute("status")) {
367
status = decltype(status)(reader.getAttributeAsUnsigned("status"));
369
prop->setStatusValue(status.to_ulong());
371
// name and type match
372
if (prop && strcmp(prop->getTypeId().getName(), TypeName.c_str()) == 0) {
373
if (!prop->testStatus(Property::Transient)
374
&& !status.test(Property::Transient)
375
&& !status.test(Property::PropTransient)
376
&& !prop->testStatus(Property::PropTransient))
378
FC_TRACE("restore property '" << prop->getName() << "'");
379
prop->Restore(reader);
381
FC_TRACE("skip transient '" << prop->getName() << "'");
383
// name matches but not the type
385
handleChangedPropertyType(reader, TypeName.c_str(), prop);
387
// name doesn't match, the sub-class then has to know
388
// if the property has been renamed or removed
390
handleChangedPropertyName(reader, TypeName.c_str(), PropName.c_str());
393
if (reader.testStatus(Base::XMLReader::ReaderStatus::PartialRestoreInProperty)) {
394
Base::Console().Error("Property %s of type %s was subject to a partial restore.\n",PropName.c_str(),TypeName.c_str());
395
reader.clearPartialRestoreProperty();
398
catch (const Base::XMLParseException&) {
401
catch (const Base::RestoreError &) {
402
reader.setPartialRestore(true);
403
reader.clearPartialRestoreProperty();
404
Base::Console().Error("Property %s of type %s was subject to a partial restore.\n",PropName.c_str(),TypeName.c_str());
406
catch (const Base::Exception &e) {
407
Base::Console().Error("%s\n", e.what());
409
catch (const std::exception &e) {
410
Base::Console().Error("%s\n", e.what());
412
catch (const char* e) {
413
Base::Console().Error("%s\n", e);
417
Base::Console().Error("PropertyContainer::Restore: Unknown C++ exception thrown\n");
420
reader.readEndElement("Property");
422
reader.readEndElement("Properties");
425
void PropertyContainer::onPropertyStatusChanged(const Property &prop, unsigned long oldStatus)
431
void PropertyData::addProperty(OffsetBase offsetBase,const char* PropName, Property *Prop, const char* PropertyGroup , PropertyType Type, const char* PropertyDocu)
437
short offset = offsetBase.getOffsetTo(Prop);
439
throw Base::RuntimeError("Invalid static property");
440
auto &index = propertyData.get<1>();
441
auto it = index.find(PropName);
442
if(it == index.end()) {
444
throw Base::RuntimeError("Cannot add static property");
445
index.emplace(PropName, PropertyGroup, PropertyDocu, offset, Type);
448
if(it->Offset != offset) {
449
FC_ERR("Duplicate property '" << PropName << "'");
455
Prop->syncType(Type);
456
Prop->myName = PropName;
459
void PropertyData::merge(PropertyData *other) const {
461
other = const_cast<PropertyData*>(parentPropertyData);
462
if(other == parentPropertyData) {
469
auto &index = propertyData.get<0>();
470
for(const auto &spec : other->propertyData.get<0>())
471
index.push_back(spec);
475
void PropertyData::split(PropertyData *other) {
476
if(other == parentPropertyData) {
479
parentMerged = false;
482
auto &index = propertyData.get<2>();
483
for(const auto &spec : other->propertyData.get<0>())
484
index.erase(spec.Offset);
488
const PropertyData::PropertySpec *PropertyData::findProperty(OffsetBase offsetBase,const char* PropName) const
492
auto &index = propertyData.get<1>();
493
auto it = index.find(PropName);
494
if(it != index.end())
499
const PropertyData::PropertySpec *PropertyData::findProperty(OffsetBase offsetBase,const Property* prop) const
502
int diff = offsetBase.getOffsetTo(prop);
506
auto &index = propertyData.get<2>();
507
auto it = index.find(diff);
514
const char* PropertyData::getName(OffsetBase offsetBase,const Property* prop) const
516
const PropertyData::PropertySpec* Spec = findProperty(offsetBase,prop);
524
short PropertyData::getType(OffsetBase offsetBase,const Property* prop) const
526
const PropertyData::PropertySpec* Spec = findProperty(offsetBase,prop);
534
short PropertyData::getType(OffsetBase offsetBase,const char* name) const
536
const PropertyData::PropertySpec* Spec = findProperty(offsetBase,name);
544
const char* PropertyData::getGroup(OffsetBase offsetBase,const Property* prop) const
546
const PropertyData::PropertySpec* Spec = findProperty(offsetBase,prop);
554
const char* PropertyData::getGroup(OffsetBase offsetBase,const char* name) const
556
const PropertyData::PropertySpec* Spec = findProperty(offsetBase,name);
564
const char* PropertyData::getDocumentation(OffsetBase offsetBase,const Property* prop) const
566
const PropertyData::PropertySpec* Spec = findProperty(offsetBase,prop);
574
const char* PropertyData::getDocumentation(OffsetBase offsetBase,const char* name) const
576
const PropertyData::PropertySpec* Spec = findProperty(offsetBase,name);
584
Property *PropertyData::getPropertyByName(OffsetBase offsetBase,const char* name) const
586
const PropertyData::PropertySpec* Spec = findProperty(offsetBase,name);
589
return reinterpret_cast<Property *>(Spec->Offset + offsetBase.getOffset());
594
void PropertyData::getPropertyMap(OffsetBase offsetBase,std::map<std::string,Property*> &Map) const
597
for(auto &spec : propertyData.get<0>())
598
Map[spec.Name] = reinterpret_cast<Property *>(spec.Offset + offsetBase.getOffset());
601
void PropertyData::getPropertyList(OffsetBase offsetBase,std::vector<Property*> &List) const
604
size_t base = List.size();
605
List.reserve(base+propertyData.size());
606
for (auto &spec : propertyData.get<0>())
607
List.push_back(reinterpret_cast<Property *>(spec.Offset + offsetBase.getOffset()));
610
void PropertyData::getPropertyNamedList(OffsetBase offsetBase,
611
std::vector<std::pair<const char*,Property*> > &List) const
614
size_t base = List.size();
615
List.reserve(base+propertyData.size());
616
for (auto &spec : propertyData.get<0>()) {
617
auto prop = reinterpret_cast<Property *>(spec.Offset + offsetBase.getOffset());
618
List.emplace_back(prop->getName(),prop);
624
/** \defgroup PropFrame Property framework
626
\brief System to access object properties
628
The property framework introduces the ability to access attributes (member variables) of a class by name without
629
knowing the class type. It's like the reflection mechanism of Java or C#.
630
This ability is introduced by the App::PropertyContainer class and can be used by all derived classes.
632
This makes it possible in the first place to make an automatic mapping to python (e.g. in App::FeaturePy) and
633
abstract editing properties in Gui::PropertyEditor.
637
Here some little examples how to use it:
640
// search in PropertyList
641
Property *prop = _pcFeature->getPropertyByName(attr);
644
return prop->getPyObject();
651
void PropertyContainer::Restore(Base::Reader &reader)
653
reader.readElement("Properties");
654
int Cnt = reader.getAttributeAsInteger("Count");
656
for(int i=0 ;i<Cnt ;i++)
658
reader.readElement("Property");
659
string PropName = reader.getAttribute("name");
660
Property* prop = getPropertyByName(PropName.c_str());
662
prop->Restore(reader);
664
reader.readEndElement("Property");
666
reader.readEndElement("Properties");