/
Property.h
554 lines (475 loc) · 20.8 KB
/
Property.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
/***************************************************************************
* Copyright (c) 2002 Jürgen Riegel <juergen.riegel@web.de> *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 59 Temple Place, *
* Suite 330, Boston, MA 02111-1307, USA *
* *
***************************************************************************/
#ifndef APP_PROPERTY_H
#define APP_PROPERTY_H
// Std. configurations
#include <Base/Exception.h>
#include <Base/Persistence.h>
#ifndef BOOST_105400
#include <boost/any.hpp>
#else
#include <boost_any_1_55.hpp>
#endif
#include <string>
#include <bitset>
namespace Py {
class Object;
}
namespace App
{
class PropertyContainer;
class ObjectIdentifier;
/** Base class of all properties
* This is the father of all properties. Properties are objects which are used
* in the document tree to parametrize e.g. features and their graphical output.
* They are also used to gain access from the scripting facility.
* /par
* This abstract base class defines all methods shared by all
* possible properties. It is also possible to define user properties
* and use them in the framework...
*/
class AppExport Property : public Base::Persistence
{
TYPESYSTEM_HEADER_WITH_OVERRIDE();
public:
enum Status
{
Touched = 0, // touched property
Immutable = 1, // can't modify property
ReadOnly = 2, // for property editor
Hidden = 3, // for property editor
Transient = 4, // for property container save
MaterialEdit = 5, // to turn ON PropertyMaterial edit
NoMaterialListEdit = 6, // to turn OFF PropertyMaterialList edit
Output = 7, // same effect as Prop_Output
LockDynamic = 8, // prevent being removed from dynamic property
NoModify = 9, // prevent causing Gui::Document::setModified()
PartialTrigger = 10, // allow change in partial doc
NoRecompute = 11, // touch owner for recompute on property change
Single = 12, // for save/load of floating point numbers
Ordered = 13, // for PropertyLists whether the order of the elements is
// relevant for the container using it
EvalOnRestore = 14, // In case of expression binding, evaluate the
// expression on restore and touch the object on value change.
// The following bits are corresponding to PropertyType set when the
// property added. These types are meant to be static, and cannot be
// changed in runtime. It is mirrored here to save the linear search
// required in PropertyContainer::getPropertyType()
//
PropStaticBegin = 21,
PropDynamic = 21, // indicating the property is dynamically added
PropNoPersist = 22, // corresponding to Prop_NoPersist
PropNoRecompute = 23, // corresponding to Prop_NoRecompute
PropReadOnly = 24, // corresponding to Prop_ReadOnly
PropTransient= 25, // corresponding to Prop_Transient
PropHidden = 26, // corresponding to Prop_Hidden
PropOutput = 27, // corresponding to Prop_Output
PropStaticEnd = 28,
User1 = 28, // user-defined status
User2 = 29, // user-defined status
User3 = 30, // user-defined status
User4 = 31 // user-defined status
};
Property();
virtual ~Property();
/// For safe deleting of a dynamic property
static void destroy(Property *p);
/** This method is used to get the size of objects
* It is not meant to have the exact size, it is more or less an estimation
* which runs fast! Is it two bytes or a GB?
* This method is defined in Base::Persistence
* @see Base::Persistence
*/
virtual unsigned int getMemSize (void) const override {
// you have to implement this method in all property classes!
return sizeof(father) + sizeof(StatusBits);
}
/// get the name of this property in the belonging container
const char* getName(void) const;
std::string getFullName() const;
/// Get the class name of the associated property editor item
virtual const char* getEditorName(void) const { return ""; }
/// Get the type of the property in the container
short getType(void) const;
/// Get the group of this property
const char* getGroup(void) const;
/// Get the documentation of this property
const char* getDocumentation(void) const;
/// Is called by the framework to set the father (container)
void setContainer(PropertyContainer *Father);
/// Get a pointer to the PropertyContainer derived class the property belongs to
PropertyContainer *getContainer(void) const {return father;}
/// Set value of property
virtual void setPathValue(const App::ObjectIdentifier & path, const boost::any & value);
/// Get value of property
virtual const boost::any getPathValue(const App::ObjectIdentifier & path) const;
/// Get Python value of property
virtual bool getPyPathValue(const App::ObjectIdentifier &, Py::Object &) const {
return false;
}
/// Convert p to a canonical representation of it
virtual App::ObjectIdentifier canonicalPath(const App::ObjectIdentifier & p) const;
/// Get valid paths for this property; used by auto completer
virtual void getPaths(std::vector<App::ObjectIdentifier> & paths) const;
/** Called at the beginning of Document::afterRestore()
*
* This function is called without dependency sorting, because some
* types of link property can only reconstructs the linking information
* inside this function.
*
* One example use case of this function is PropertyLinkSub that uses
* afterRestore() to parse and restore subname references, which may
* contain sub-object reference from external document, and there will be
* special mapping required during object import.
*
* Another example is PropertyExpressionEngine which only parse the
* restored expression in afterRestore(). The reason, in addition to
* subname mapping like PropertyLinkSub, is that it can handle document
* name adjustment as well. It internally relies on PropertyXLink to store
* the external document path for external linking. When the external
* document is restored, its internal name may change due to name conflict
* with existing documents. PropertyExpressionEngine can now auto adjust
* external references without any problem.
*/
virtual void afterRestore() {}
/** Called before calling DocumentObject::onDocumentRestored()
*
* This function is called after finished calling Property::afterRestore()
* of all properties of objects. By then, the object dependency information
* is assumed ready. So, unlike Property::afterRestore(), this function is
* called on objects with dependency order.
*/
virtual void onContainerRestored() {}
/** Property status handling
*/
//@{
/// Set the property touched
void touch();
/// Test if this property is touched
inline bool isTouched(void) const {
return StatusBits.test(Touched);
}
/// Reset this property touched
inline void purgeTouched(void) {
StatusBits.reset(Touched);
}
/// return the status bits
inline unsigned long getStatus() const {
return StatusBits.to_ulong();
}
inline bool testStatus(Status pos) const {
return StatusBits.test(static_cast<size_t>(pos));
}
void setStatus(Status pos, bool on);
void setStatusValue(unsigned long status);
///Sets property editable/grayed out in property editor
void setReadOnly(bool readOnly);
inline bool isReadOnly() const {
return testStatus(App::Property::ReadOnly);
}
/// Sets precision of properties using floating point
/// numbers to single, the default is double.
void setSinglePrecision(bool single) {
setStatus(App::Property::Single, single);
}
/// Gets precision of properties using floating point numbers
inline bool isSinglePrecision() const {
return testStatus(App::Property::Single);
}
//@}
/// Returns a new copy of the property (mainly for Undo/Redo and transactions)
virtual Property *Copy(void) const = 0;
/// Paste the value from the property (mainly for Undo/Redo and transactions)
virtual void Paste(const Property &from) = 0;
/// Called when a child property has changed value
virtual void hasSetChildValue(Property &) {}
/// Called before a child property changing value
virtual void aboutToSetChildValue(Property &) {}
/** Return a unique ID for the property
*
* The ID of a property is generated from an monotonically increasing
* internal counter. The intention of the ID is to be used as a key for
* mapping, instead of using the raw pointer. Because, it is possible for
* the runtime memory allocator to reuse just deleted memory, which will
* cause hard to debug problem if use pointer as key.
*/
long getID() const {return _id;}
friend class PropertyContainer;
friend struct PropertyData;
friend class DynamicProperty;
protected:
/** Status bits of the property
* The first 8 bits are used for the base system the rest can be used in
* descendent classes to mark special statuses on the objects.
* The bits and their meaning are listed below:
* 0 - object is marked as 'touched'
* 1 - object is marked as 'immutable'
* 2 - object is marked as 'read-only' (for property editor)
* 3 - object is marked as 'hidden' (for property editor)
*/
std::bitset<32> StatusBits;
protected:
/// Gets called by all setValue() methods after the value has changed
virtual void hasSetValue(void);
/// Gets called by all setValue() methods before the value has changed
virtual void aboutToSetValue(void);
/// Verify a path for the current property
virtual void verifyPath(const App::ObjectIdentifier & p) const;
private:
// forbidden
Property(const Property&);
Property& operator = (const Property&);
// Sync status with Property_Type
void syncType(unsigned type);
private:
PropertyContainer *father;
const char *myName;
long _id;
};
/** A template class that is used to inhibit multiple nested calls to aboutToSetValue/hasSetValue for properties.
*
* A template class that is used to inhibit multiple nested calls to
* aboutToSetValue/hasSetValue for properties, and only invoke it on change and
* last time it is needed. This is useful in cases where you want to change multiple
* values in a property "atomically", using possibly multiple primitive functions
* that normally would trigger aboutToSetValue/hasSetValue calls on their own.
*
* To use, inherit privately from the AtomicPropertyChangeInterface class, using
* your class name as the template argument. In all cases where you normally would
* call aboutToSetValue/hasSetValue before and after a change, create an
* AtomicPropertyChange object. The default constructor assume you are about to
* change the property and will call property's aboutToSetValue() if the
* property has not been marked as changed before by any other
* AtomicPropertyChange instances in current call stack. You can pass 'false'
* as the a second argument to the constructor, and manually call
* AtomicPropertyChange::aboutToChange() before actual change, this enables you
* to prevent unnecessary property copy for undo/redo where there is actual
* changes. AtomicPropertyChange will guaranetee calling hasSetValue() when the
* last instance in the current call stack is destroyed.
*
* One thing to take note is that, because C++ does not allow throwing
* exception in destructor, any exception thrown when calling property's
* hasSetValue() will be caught and swallowed. To allow exception propagation,
* you can manually call AtomicPropertyChange::tryInvoke(). If the condition is
* satisfied, it will call hasSetValue() that allows exception propagation.
*/
template<class P> class AtomicPropertyChangeInterface {
protected:
AtomicPropertyChangeInterface() : signalCounter(0), hasChanged(false) { }
public:
class AtomicPropertyChange {
public:
/** Constructor
*
* @param prop: the property
* @param markChange: If true, marks the property as changed if it
* hasn't been marked before, and calls its
* aboutToSetValue().
*/
AtomicPropertyChange(P & prop, bool markChange=true) : mProp(prop) {
mProp.signalCounter++;
if (markChange)
aboutToChange();
}
/** Mark the property as changed
*
* It will mark the property as changed only if it has been marked
* before, and only then will it call the property's aboutToSetValue().
*/
void aboutToChange() {
if (!mProp.hasChanged) {
mProp.hasChanged = true;
mProp.aboutToSetValue();
}
}
/** Destructor
*
* If the property is marked as changed, and this is the last instance
* of the class in current call stack, it will call property's
* hasSetValue()
*/
~AtomicPropertyChange() {
// Signal counter == 1? meaning we are the last one. Invoke
// hasSetValue() before decrease counter to prevent recursive call
// triggered by another AtomicPropertyChange created inside
// hasSetValue(), as it has now been changed to a virtual function.
if (mProp.signalCounter == 1 && mProp.hasChanged) {
// Must make sure to not throw in a destructor
try {
mProp.hasSetValue();
} catch(Base::Exception &e) {
e.ReportException();
} catch(...) {}
mProp.hasChanged = false;
}
if (mProp.signalCounter>0)
mProp.signalCounter--;
}
/** Check and invoke property's hasSetValue()
*
* Check if this is the last instance and the property has been marked
* as changed. If so, invoke property's hasSetValue().
*/
// Destructor cannot throw. So we provide this function to allow error
// propagation.
void tryInvoke() {
if (mProp.signalCounter==1 && mProp.hasChanged) {
mProp.hasSetValue();
if (mProp.signalCounter>0)
--mProp.signalCounter;
mProp.hasChanged = false;
}
}
private:
P & mProp; /**< Referenced to property we work on */
};
protected:
int signalCounter; /**< Counter for invoking transaction start/stop */
bool hasChanged;
};
/** Helper class to construct list like properties
*
* This class is not derived from Property so that we can have more that one
* base class for list like properties, e.g. see PropertyList, and
* PropertyLinkListBase
*/
class AppExport PropertyListsBase
{
public:
virtual void setSize(int newSize)=0;
virtual int getSize(void) const =0;
const std::set<int> &getTouchList() const {
return _touchList;
}
void clearTouchList() {
_touchList.clear();
}
protected:
virtual void setPyValues(const std::vector<PyObject*> &vals, const std::vector<int> &indices) {
(void)vals;
(void)indices;
throw Base::NotImplementedError("not implemented");
}
void _setPyObject(PyObject *);
protected:
std::set<int> _touchList;
};
/** Base class of all property lists.
* The PropertyLists class is the base class for properties which can contain
* multiple values, not only a single value.
* All property types which may contain more than one value inherits this class.
*/
class AppExport PropertyLists : public Property, public PropertyListsBase
{
TYPESYSTEM_HEADER_WITH_OVERRIDE();
public:
virtual void setPyObject(PyObject *obj) override {
_setPyObject(obj);
}
// if the order of the elements in the list relevant?
// if yes, certain operations, like restoring must make sure that the
// order is kept despite errors.
inline void setOrderRelevant(bool on) { this->setStatus(Status::Ordered,on); };
inline bool isOrderRelevant() const { return this->testStatus(Status::Ordered);}
};
/** Helper class to implement PropertyLists */
template<class T, class ListT = std::vector<T>, class ParentT = PropertyLists >
class PropertyListsT: public ParentT
, public AtomicPropertyChangeInterface<PropertyListsT<T,ListT,ParentT> >
{
public:
typedef typename ListT::const_reference const_reference;
typedef ListT list_type;
typedef ParentT parent_type;
typedef typename AtomicPropertyChangeInterface<
PropertyListsT<T,ListT,ParentT> >::AtomicPropertyChange atomic_change;
friend atomic_change;
virtual void setSize(int newSize, const_reference def) {
_lValueList.resize(newSize,def);
}
virtual void setSize(int newSize) override {
_lValueList.resize(newSize);
}
virtual int getSize(void) const override {
return static_cast<int>(_lValueList.size());
}
void setValue(const_reference value) {
ListT vals;
vals.resize(1,value);
setValues(vals);
}
virtual void setValues(const ListT &newValues = ListT()) {
atomic_change guard(*this);
this->_touchList.clear();
this->_lValueList = newValues;
guard.tryInvoke();
}
void setValue(const ListT &newValues = ListT()) {
setValues(newValues);
}
const ListT &getValues(void) const{return _lValueList;}
// alias to getValues
const ListT &getValue(void) const{return getValues();}
const_reference operator[] (int idx) const {return _lValueList[idx];}
virtual void setPyObject(PyObject *value) override {
try {
setValue(getPyValue(value));
return;
}catch(...){}
parent_type::setPyObject(value);
}
virtual void set1Value(int index, const_reference value) {
int size = getSize();
if (index<-1 || index>size)
throw Base::RuntimeError("index out of bound");
atomic_change guard(*this);
if (index==-1 || index == size) {
index = size;
setSize(index+1,value);
} else
_lValueList[index] = value;
this->_touchList.insert(index);
guard.tryInvoke();
}
protected:
void setPyValues(const std::vector<PyObject*> &vals, const std::vector<int> &indices) override
{
if (indices.empty()) {
ListT values;
values.resize(vals.size());
for (std::size_t i=0,count=vals.size();i<count;++i)
values[i] = getPyValue(vals[i]);
setValues(std::move(values));
return;
}
assert(vals.size()==indices.size());
atomic_change guard(*this);
for (int i=0,count=indices.size();i<count;++i)
set1Value(indices[i],getPyValue(vals[i]));
guard.tryInvoke();
}
virtual T getPyValue(PyObject *item) const = 0;
protected:
ListT _lValueList;
};
} // namespace App
#endif // APP_PROPERTY_H