Merge lp:~thumper/nux/properties into lp:nux

Proposed by Tim Penhey on 2011-07-11
Status: Merged
Merged at revision: 384
Proposed branch: lp:~thumper/nux/properties
Merge into: lp:nux
Diff against target: 1223 lines (+796/-256) 3 files modified
To merge this branch: bzr merge lp:~thumper/nux/properties
Reviewer Review Type Date Requested Status
Jay Taoko (community) 2011-07-11 Approve on 2011-07-12
Review via email: mp+67479@code.launchpad.net

Description of the Change

This is nux::Properties Mark II.

The fundamental change here is that nux::Property is no longer constrained by
type, and it is not serializable. If you want a serializable property, use
the nux::SerializableProperty. This inherits from nux::Property.
SerializableProperty instances still require the type traits to be set, and as
such are limited in content type. nux::Property can now hold class instances
or smart pointers - in fact anything that implements a != operator.

This branch also introduces several nifty new changes:
 * nux::Property can now have a custom setter method
 * nux::ROProperty is a read only property provided by a method
 * nux::RWProperty is a read/write property with implementation provided by
 methods provided as slots.

I also changed the method names to be capitalised to conform to the coding
standard.

I also added a nice attribution to Lois, who was the author of the paper I
used as a basis for the design.

I've added many tests for this, and to get a good feel for how to use the
properties, you should look at the tests.

Tests of intereste are the following (IMO):
 * TestProperty.TestSetterConstructor
 * TestProperty.TestCustomSetterFunction
 * TestROProperty.TestGetterConstructor
 * TestROProperty.TestSetGetter
 * TestRWProperty.TestFunctionConstructor
 * TestRWProperty.TestPimplClassExample

To post a comment you must log in.

Had a quick skim - nice work Tim :-) I have two points I want to raise:

 a) If I understand correct the default behaviour for a ROProperty is to not signal on change. I think this is this is the wrong behaviour for two reasons:
   1) From my experience RO properties are used for things like a bool "connected" or "mapped" where they reflect unchangeable aspects of the underlying system. Normally you'll want change notifications on those.
   2) It is dangerous refactoring-wise that changing a property into a RWProperty causes a change in semantics. I can see many hours of lost debugging there - "why doesn't XYZ update anymore?!" :-)

Consumers that want to do changes without notifications can just change the underlying private member directly, and if you're worried about overhead for setting up sigc++ signals then one can maybe get away with some form of lazy setup?

 b) This one is not necessary for a first cut but we might wanna think about it at this stage nonetheless: In glib you have g_object_freeze/thaw_notify() which twiddles a freeze count +-1. If the freeze count is >0 all change notifications are queued up only to be emitted once it reaches 0. This is super useful to avoid reentrancy and threading issues. On GObjects the freeze count is global across all properties per instance, but one could imagine a per-property approach as well.
  - Anyway, *I* like (and use) the freeze/thaw functionality a lot, but I bet someone considers it a dangerous exposed implementation detail of GObject :-)

Tim Penhey (thumper) wrote :

On Mon, 11 Jul 2011 19:31:21 you wrote:
> Had a quick skim - nice work Tim :-) I have two points I want to raise:
>
> a) If I understand correct the default behaviour for a ROProperty is to
> not signal on change. I think this is this is the wrong behaviour for two
> reasons: 1) From my experience RO properties are used for things like a
> bool "connected" or "mapped" where they reflect unchangeable aspects of
> the underlying system. Normally you'll want change notifications on those.

My only real issue with ROProperty is that we don't know if it has changed or
not. So how do we know to signal?

Good point. Conceptually I'd say that one needs a private or protected
setter function that does this (which must be used by the consumers
just like a public setter would), but if I understand correctly
ROProps don't have setters in this setup?

Tim Penhey (thumper) wrote :

On Mon, 11 Jul 2011 21:46:29 you wrote:
> Good point. Conceptually I'd say that one needs a private or protected
> setter function that does this (which must be used by the consumers
> just like a public setter would), but if I understand correctly
> ROProps don't have setters in this setup?

Exactly. Given that the property is a member itself, you can't have a
protected setter that is accessible by the containing class.

Also - a clarification: If I read correctly the Property objects live per instance and not per class. In other words two instances a1 and a2 of a class A will both have an instance of the Property objects for A? The conventional way to do properties afaik is to have the properties live in the class scope instead avoiding the additional churn of the per-instance setup/teardown. Can you elaborate?

Neil J. Patel (njpatel) wrote :

The owner would emit the signal when they change the underlying store. As long as its possible to emit the changed signal, that should cover the use case that Mikkel mentioned.

Sent from my iPhone

On 11 Jul 2011, at 10:36, Tim Penhey <email address hidden> wrote:

> On Mon, 11 Jul 2011 19:31:21 you wrote:
>> Had a quick skim - nice work Tim :-) I have two points I want to raise:
>>
>> a) If I understand correct the default behaviour for a ROProperty is to
>> not signal on change. I think this is this is the wrong behaviour for two
>> reasons: 1) From my experience RO properties are used for things like a
>> bool "connected" or "mapped" where they reflect unchangeable aspects of
>> the underlying system. Normally you'll want change notifications on those.
>
> My only real issue with ROProperty is that we don't know if it has changed or
> not. So how do we know to signal?
>
> --
> https://code.launchpad.net/~thumper/nux/properties/+merge/67479
> Your team Unity Team is requested to review the proposed merge of lp:~thumper/nux/properties into lp:nux.

Neil, sure, that's my whole point :-) My point is that the following
change gives a lot of code to refactor:

 TeaCup::TeaCup() {
- _full_prop = new RWProperty<bool> ();
+ _full_prop = new ROProperty<bool> ();
 }

You need to refactor all Set() calls and make sure you manually emit
signals where appropriate. I was just arguing for a solution where the
above diff would be all there was to it.

Anyway. Maybe I am just bikeshedding this :-)

Tim Penhey (thumper) wrote :

On Tue, 12 Jul 2011 02:43:23 you wrote:
> The owner would emit the signal when they change the underlying store. As
> long as its possible to emit the changed signal, that should cover the use
> case that Mikkel mentioned.

What about the use case where the property is entirely calculated?

The result may well change but no changed event emitted.

This is why I didn't have the ROProperty inherit from the event base. There
is no changed event to connect to, so no expectation of changes.

I added the ROProperty class because Neil had said he'd like one, but I have a
feeling that the basic Property class with a custom set method covers 99% of
our desired use cases for simple properties.

Tim

Tim Penhey (thumper) wrote :

On Tue, 12 Jul 2011 07:41:23 you wrote:
> Neil, sure, that's my whole point :-) My point is that the following
> change gives a lot of code to refactor:
>
> TeaCup::TeaCup() {
> - _full_prop = new RWProperty<bool> ();
> + _full_prop = new ROProperty<bool> ();
> }

Aside: you don't use new with properties.

>
> You need to refactor all Set() calls and make sure you manually emit
> signals where appropriate. I was just arguing for a solution where the
> above diff would be all there was to it.

There are no Set calls for a ROProperty. If you are changing a RWProperty (or
Property) into a ROProperty, then yes you should refactor as the underlying
semantics have changed, and the usage sites should be looked at and evaluated.

> Anyway. Maybe I am just bikeshedding this :-)

Perhaps :-)

Tim Penhey (thumper) wrote :

On Mon, 11 Jul 2011 22:32:33 you wrote:
> Also - a clarification: If I read correctly the Property objects live per
> instance and not per class. In other words two instances a1 and a2 of a
> class A will both have an instance of the Property objects for A?

Yes.

> The conventional way to do properties afaik is to have the properties
> live in the class scope instead avoiding the additional churn of the
> per-instance setup/teardown. Can you elaborate?

Properties are supposed to be like special member variables, most often
public, that have changed events associated with them.

You can have static Property objects that belong to a class as well if you'd
like.

Ok, I know I am beating a dead horse here; you two agree so I am good
with that :-)

Nonetheless, I don't think my point has come across: My worry is that
this is a purely dynamical property system and that it is exactly
*unlike* member variables in that they are not related to (let alone
defined in) the class. So two instances a1 and a2 of class A could
easily have totally different sets of properties. I wanted to avoid
this by somehow making the property specs a part of the class
definition in the .h file (probably by using some magical static
initializers). This would also make property setup slightly
(massively?) lighter at runtime because it would be per class and not
per instance.

Tim Penhey (thumper) wrote :

On Tue, 12 Jul 2011 20:07:40 you wrote:
> Ok, I know I am beating a dead horse here; you two agree so I am good
> with that :-)
>
> Nonetheless, I don't think my point has come across: My worry is that
> this is a purely dynamical property system and that it is exactly
> *unlike* member variables in that they are not related to (let alone
> defined in) the class.

Ah... I think this is where we disagree. It isn't a dynamical property
system.

> So two instances a1 and a2 of class A could
> easily have totally different sets of properties.

No they couldn't.

> I wanted to avoid
> this by somehow making the property specs a part of the class
> definition in the .h file (probably by using some magical static
> initializers). This would also make property setup slightly
> (massively?) lighter at runtime because it would be per class and not
> per instance.

On 12 July 2011 11:16, Tim Penhey <email address hidden> wrote:
> On Tue, 12 Jul 2011 20:07:40 you wrote:
>> Ok, I know I am beating a dead horse here; you two agree so I am good
>> with that :-)
>>
>> Nonetheless, I don't think my point has come across: My worry is that
>> this is a purely dynamical property system and that it is exactly
>> *unlike* member variables in that they are not related to (let alone
>> defined in) the class.
>
> Ah... I think this is where we disagree.  It isn't a dynamical property
> system.
>
>> So two instances a1 and a2 of class A could
>> easily have totally different sets of properties.
>
> No they couldn't.

Hehe... and my mediocre C++ skills are revealed - Move along, nothing
to see here :-)

Jay Taoko (jaytaoko) wrote :

I spend most of the review on the tests as they are the easier to understand.

review: Approve
lp:~thumper/nux/properties updated on 2011-07-13
384. By Tim Penhey on 2011-07-13

Land nux::Properties Mark II.

Preview Diff

1=== modified file 'NuxCore/Property-inl.h'
2--- NuxCore/Property-inl.h 2011-06-27 14:18:42 +0000
3+++ NuxCore/Property-inl.h 2011-07-11 02:16:37 +0000
4@@ -24,165 +24,321 @@
5
6 namespace nux {
7
8-template <typename VALUE_TYPE>
9-ConnectableProperty<VALUE_TYPE>::ConnectableProperty()
10- : value_(VALUE_TYPE())
11- , notify_(true)
12-{}
13-
14-template <typename VALUE_TYPE>
15-ConnectableProperty<VALUE_TYPE>::ConnectableProperty(VALUE_TYPE const& initial)
16- : value_(initial)
17- , notify_(true)
18-{}
19-
20-template <typename VALUE_TYPE>
21-VALUE_TYPE const& ConnectableProperty<VALUE_TYPE>::operator=(VALUE_TYPE const& value)
22-{
23- set(value);
24- return value_;
25-}
26-
27-template <typename VALUE_TYPE>
28-ConnectableProperty<VALUE_TYPE>::operator VALUE_TYPE const&() const
29-{
30- return value_;
31-}
32-
33-template <typename VALUE_TYPE>
34-VALUE_TYPE const& ConnectableProperty<VALUE_TYPE>::operator()() const
35-{
36- return value_;
37-}
38-
39-template <typename VALUE_TYPE>
40-void ConnectableProperty<VALUE_TYPE>::operator()(VALUE_TYPE const& value)
41-{
42- set(value);
43-}
44-
45-template <typename VALUE_TYPE>
46-void ConnectableProperty<VALUE_TYPE>::disable_notifications()
47+
48+template <typename VALUE_TYPE>
49+PropertyChangedSignal<VALUE_TYPE>::PropertyChangedSignal()
50+ : notify_(true)
51+{}
52+
53+template <typename VALUE_TYPE>
54+void PropertyChangedSignal<VALUE_TYPE>::DisableNotifications()
55 {
56 notify_ = false;
57 }
58
59 template <typename VALUE_TYPE>
60-void ConnectableProperty<VALUE_TYPE>::enable_notifications()
61+void PropertyChangedSignal<VALUE_TYPE>::EnableNotifications()
62 {
63 notify_ = true;
64 }
65
66- // get and set access
67-template <typename VALUE_TYPE>
68-VALUE_TYPE const& ConnectableProperty<VALUE_TYPE>::get() const
69-{
70- return value_;
71-}
72-
73-template <typename VALUE_TYPE>
74-void ConnectableProperty<VALUE_TYPE>::set(VALUE_TYPE const& value)
75-{
76- if (value != value_) {
77- value_ = value;
78- if (notify_) {
79- changed.emit(value_);
80- }
81- }
82-}
83+template <typename VALUE_TYPE>
84+void PropertyChangedSignal<VALUE_TYPE>::EmitChanged(VALUE_TYPE const& new_value)
85+{
86+ if (notify_)
87+ changed.emit(new_value);
88+}
89+
90+
91+template <typename VALUE_TYPE>
92+Property<VALUE_TYPE>::Property()
93+ : value_(VALUE_TYPE())
94+ , setter_function_(sigc::mem_fun(this, &Property<VALUE_TYPE>::DefaultSetter))
95+{}
96+
97+template <typename VALUE_TYPE>
98+Property<VALUE_TYPE>::Property(VALUE_TYPE const& initial)
99+ : value_(initial)
100+ , setter_function_(sigc::mem_fun(this, &Property<VALUE_TYPE>::DefaultSetter))
101+{}
102+
103+template <typename VALUE_TYPE>
104+Property<VALUE_TYPE>::Property(VALUE_TYPE const& initial,
105+ SetterFunction setter_function)
106+ : value_(initial)
107+ , setter_function_(setter_function)
108+{}
109+
110+template <typename VALUE_TYPE>
111+VALUE_TYPE Property<VALUE_TYPE>::operator=(VALUE_TYPE const& value)
112+{
113+ return Set(value);
114+}
115+
116+template <typename VALUE_TYPE>
117+Property<VALUE_TYPE>::operator VALUE_TYPE() const
118+{
119+ return value_;
120+}
121+
122+template <typename VALUE_TYPE>
123+VALUE_TYPE Property<VALUE_TYPE>::operator()() const
124+{
125+ return value_;
126+}
127+
128+template <typename VALUE_TYPE>
129+VALUE_TYPE Property<VALUE_TYPE>::operator()(VALUE_TYPE const& value)
130+{
131+ return Set(value);
132+}
133+
134+template <typename VALUE_TYPE>
135+VALUE_TYPE Property<VALUE_TYPE>::Get() const
136+{
137+ return value_;
138+}
139+
140+template <typename VALUE_TYPE>
141+VALUE_TYPE Property<VALUE_TYPE>::Set(VALUE_TYPE const& value)
142+{
143+ if (setter_function_(value_, value))
144+ SignalBase::EmitChanged(value_);
145+ return value_;
146+}
147+
148+template <typename VALUE_TYPE>
149+bool Property<VALUE_TYPE>::DefaultSetter(VALUE_TYPE& target,
150+ VALUE_TYPE const& value)
151+{
152+ bool changed = false;
153+ if (target != value) {
154+ target = value;
155+ changed = true;
156+ }
157+ return changed;
158+}
159+
160+template <typename VALUE_TYPE>
161+void Property<VALUE_TYPE>::SetSetterFunction(SetterFunction setter_function)
162+{
163+ setter_function_ = setter_function;
164+}
165+
166+
167+template <typename VALUE_TYPE>
168+ROProperty<VALUE_TYPE>::ROProperty()
169+ : getter_function_(sigc::mem_fun(this, &ROProperty<VALUE_TYPE>::DefaultGetter))
170+{}
171+
172+template <typename VALUE_TYPE>
173+ROProperty<VALUE_TYPE>::ROProperty(GetterFunction getter_function)
174+ : getter_function_(getter_function)
175+{}
176+
177+template <typename VALUE_TYPE>
178+ROProperty<VALUE_TYPE>::operator VALUE_TYPE() const
179+{
180+ return getter_function_();
181+}
182+
183+template <typename VALUE_TYPE>
184+VALUE_TYPE ROProperty<VALUE_TYPE>::operator()() const
185+{
186+ return getter_function_();
187+}
188+
189+template <typename VALUE_TYPE>
190+VALUE_TYPE ROProperty<VALUE_TYPE>::Get() const
191+{
192+ return getter_function_();
193+}
194+
195+template <typename VALUE_TYPE>
196+VALUE_TYPE ROProperty<VALUE_TYPE>::DefaultGetter() const
197+{
198+ return VALUE_TYPE();
199+}
200+
201+template <typename VALUE_TYPE>
202+void ROProperty<VALUE_TYPE>::SetGetterFunction(GetterFunction getter_function)
203+{
204+ getter_function_ = getter_function;
205+}
206+
207+
208+template <typename VALUE_TYPE>
209+RWProperty<VALUE_TYPE>::RWProperty()
210+ : getter_function_(sigc::mem_fun(this, &RWProperty<VALUE_TYPE>::DefaultGetter))
211+ , setter_function_(sigc::mem_fun(this, &RWProperty<VALUE_TYPE>::DefaultSetter))
212+{}
213+
214+template <typename VALUE_TYPE>
215+RWProperty<VALUE_TYPE>::RWProperty(GetterFunction getter_function,
216+ SetterFunction setter_function)
217+ : getter_function_(getter_function)
218+ , setter_function_(setter_function)
219+{}
220+
221+template <typename VALUE_TYPE>
222+VALUE_TYPE RWProperty<VALUE_TYPE>::operator=(VALUE_TYPE const& value)
223+{
224+ return Set(value);
225+}
226+
227+template <typename VALUE_TYPE>
228+RWProperty<VALUE_TYPE>::operator VALUE_TYPE() const
229+{
230+ return getter_function_();
231+}
232+
233+template <typename VALUE_TYPE>
234+VALUE_TYPE RWProperty<VALUE_TYPE>::operator()() const
235+{
236+ return getter_function_();
237+}
238+
239+template <typename VALUE_TYPE>
240+VALUE_TYPE RWProperty<VALUE_TYPE>::operator()(VALUE_TYPE const& value)
241+{
242+ return Set(value);
243+}
244+
245+template <typename VALUE_TYPE>
246+VALUE_TYPE RWProperty<VALUE_TYPE>::Get() const
247+{
248+ return getter_function_();
249+}
250+
251+template <typename VALUE_TYPE>
252+VALUE_TYPE RWProperty<VALUE_TYPE>::Set(VALUE_TYPE const& value)
253+{
254+ if (setter_function_(value))
255+ {
256+ VALUE_TYPE new_value = getter_function_();
257+ SignalBase::EmitChanged(new_value);
258+ return new_value;
259+ }
260+ return getter_function_();
261+}
262+
263+template <typename VALUE_TYPE>
264+VALUE_TYPE RWProperty<VALUE_TYPE>::DefaultGetter() const
265+{
266+ return VALUE_TYPE();
267+}
268+
269+template <typename VALUE_TYPE>
270+bool RWProperty<VALUE_TYPE>::DefaultSetter(VALUE_TYPE const& value)
271+{
272+ return false;
273+}
274+
275+template <typename VALUE_TYPE>
276+void RWProperty<VALUE_TYPE>::SetSetterFunction(SetterFunction setter_function)
277+{
278+ setter_function_ = setter_function;
279+}
280+
281+template <typename VALUE_TYPE>
282+void RWProperty<VALUE_TYPE>::SetGetterFunction(GetterFunction getter_function)
283+{
284+ getter_function_ = getter_function;
285+}
286+
287
288 // We need to provide a default constructor since we hide the copy ctor.
289 inline Introspectable::Introspectable()
290 {}
291
292-inline void Introspectable::add_property(std::string const& name,
293- PropertyBase* property)
294-{
295- // check to see if it exists and if it does barf horribly as we can't
296- // have two properties with the same name;
297- properties_[name] = property;
298-}
299-
300-inline bool Introspectable::set_property(std::string const& name,
301- const char* value)
302-{
303- PropertyContainer::iterator i = properties_.find(name);
304- if (i == properties_.end())
305- return false;
306- else
307- return i->second->set_value(value);
308-}
309-
310-template <typename T>
311-bool Introspectable::set_property(std::string const& name, T const& value)
312-{
313- PropertyContainer::iterator i = properties_.find(name);
314- if (i == properties_.end())
315- return false;
316- else
317- {
318- return i->second->set_value(type::PropertyTrait<T>::to_string(value));
319- }
320-}
321-
322-template <typename T>
323-T Introspectable::get_property(std::string const& name, T* foo)
324-{
325- PropertyContainer::iterator i = properties_.find(name);
326- if (i == properties_.end())
327- return T();
328-
329- std::string s = i->second->get_serialized_value();
330- std::pair<T, bool> result = type::PropertyTrait<T>::from_string(s);
331- // If this is called with a template type that the property does not
332- // support nice conversion to, you'll get no error, but will get
333- // a default constructed T. We could use an exception here.
334- return result.first;
335-}
336-
337-
338-template <typename T>
339-Property<T>::Property(Introspectable* owner,
340- std::string const& name)
341-: Base()
342-, name_(name)
343-{
344- owner->add_property(name, this);
345-}
346-
347-template <typename T>
348-Property<T>::Property(Introspectable* owner,
349- std::string const& name,
350- T const& initial)
351-: Base(initial)
352-, name_(name)
353-{
354- owner->add_property(name, this);
355-}
356-
357-template <typename T>
358-bool Property<T>::set_value(std::string const& serialized_form)
359-{
360- std::pair<ValueType, bool> result = TraitType::from_string(serialized_form);
361- if (result.second) {
362- set(result.first);
363- }
364- return result.second;
365-}
366-
367-template <typename T>
368-std::string Property<T>::get_serialized_value() const
369-{
370- return TraitType::to_string(Base::get());
371-}
372-
373-template <typename T>
374-typename Property<T>::ValueType const& Property<T>::operator=(typename Property<T>::ValueType const& value)
375-{
376- set(value);
377- // There are no arguments to ‘get’ that depend on a template parameter,
378- // so we explicitly specify Base.
379- return Base::get();
380+inline void Introspectable::AddProperty(std::string const& name,
381+ PropertyBase* property)
382+{
383+ // check to see if it exists and if it does barf horribly as we can't
384+ // have two properties with the same name;
385+ properties_[name] = property;
386+}
387+
388+inline bool Introspectable::SetProperty(std::string const& name,
389+ const char* value)
390+{
391+ PropertyContainer::iterator i = properties_.find(name);
392+ if (i == properties_.end())
393+ return false;
394+ else
395+ return i->second->SetValue(value);
396+}
397+
398+template <typename T>
399+bool Introspectable::SetProperty(std::string const& name, T const& value)
400+{
401+ PropertyContainer::iterator i = properties_.find(name);
402+ if (i == properties_.end())
403+ return false;
404+ else
405+ {
406+ return i->second->SetValue(type::PropertyTrait<T>::to_string(value));
407+ }
408+}
409+
410+template <typename T>
411+T Introspectable::GetProperty(std::string const& name, T* foo)
412+{
413+ PropertyContainer::iterator i = properties_.find(name);
414+ if (i == properties_.end())
415+ return T();
416+
417+ std::string s = i->second->GetSerializedValue();
418+ std::pair<T, bool> result = type::PropertyTrait<T>::from_string(s);
419+ // If this is called with a template type that the property does not
420+ // support nice conversion to, you'll get no error, but will get
421+ // a default constructed T. We could use an exception here.
422+ return result.first;
423+}
424+
425+
426+template <typename T>
427+SerializableProperty<T>::SerializableProperty(Introspectable* owner,
428+ std::string const& name)
429+ : Base()
430+ , name_(name)
431+{
432+ owner->AddProperty(name, this);
433+}
434+
435+template <typename T>
436+SerializableProperty<T>::SerializableProperty(Introspectable* owner,
437+ std::string const& name,
438+ T const& initial)
439+ : Base(initial)
440+ , name_(name)
441+{
442+ owner->AddProperty(name, this);
443+}
444+
445+template <typename T>
446+bool SerializableProperty<T>::SetValue(std::string const& serialized_form)
447+{
448+ std::pair<ValueType, bool> result = TraitType::from_string(serialized_form);
449+ if (result.second) {
450+ Base::Set(result.first);
451+ }
452+ return result.second;
453+}
454+
455+template <typename T>
456+std::string SerializableProperty<T>::GetSerializedValue() const
457+{
458+ return TraitType::to_string(Base::Get());
459+}
460+
461+template <typename T>
462+T SerializableProperty<T>::operator=(T const& value)
463+{
464+ Base::Set(value);
465+ // There are no arguments to ‘get’ that depend on a template parameter,
466+ // so we explicitly specify Base.
467+ return Base::Get();
468 }
469
470
471
472=== modified file 'NuxCore/Property.h'
473--- NuxCore/Property.h 2011-06-27 14:18:42 +0000
474+++ NuxCore/Property.h 2011-07-11 02:16:37 +0000
475@@ -24,57 +24,174 @@
476
477 #include "PropertyTraits.h"
478
479+#include <string>
480 #include <map>
481 #include <sigc++/signal.h>
482
483-
484+/**
485+ * Much of this property work is based on the work by Lois Goldthwaite,
486+ * SC22/WG21/N1615=04-0055 - C++ Properties -- a Library Solution
487+ *
488+ * The basic ideas were extended to add update notifications, and
489+ * serialisation and introspection.
490+ */
491 namespace nux {
492
493-// TODO:
494-// object serialisation
495
496 template <typename VALUE_TYPE>
497-class ConnectableProperty
498+class PropertyChangedSignal
499 {
500 public:
501- typedef typename type::PropertyTrait<VALUE_TYPE> TraitType;
502- typedef typename TraitType::ValueType ValueType;
503-
504- ConnectableProperty();
505- ConnectableProperty(VALUE_TYPE const& initial);
506-
507- VALUE_TYPE const& operator=(VALUE_TYPE const& value);
508- operator VALUE_TYPE const & () const;
509-
510- // function call access
511- VALUE_TYPE const& operator()() const;
512- void operator()(VALUE_TYPE const& value);
513-
514- // get and set access
515- VALUE_TYPE const& get() const;
516- void set(VALUE_TYPE const& value);
517+ PropertyChangedSignal();
518
519 sigc::signal<void, VALUE_TYPE const&> changed;
520
521- void disable_notifications();
522- void enable_notifications();
523+ void DisableNotifications();
524+ void EnableNotifications();
525+
526+ void EmitChanged(VALUE_TYPE const& new_value);
527+
528+private:
529+ bool notify_;
530+};
531+
532+/**
533+ * A read/write property that stores the value type.
534+ *
535+ * The default setter emits the changed event if and only if the value
536+ * changes. A custom setter can be provided by passing in a setter function
537+ * using sigc::mem_fun or sigc::ptr_fun.
538+ */
539+template <typename VALUE_TYPE>
540+class Property : public PropertyChangedSignal<VALUE_TYPE>
541+{
542+public:
543+ typedef PropertyChangedSignal<VALUE_TYPE> SignalBase;
544+ typedef sigc::slot<bool, VALUE_TYPE&, VALUE_TYPE const&> SetterFunction;
545+
546+ Property();
547+ explicit Property(VALUE_TYPE const& initial);
548+ Property(VALUE_TYPE const& initial, SetterFunction setter_function);
549+
550+ VALUE_TYPE operator=(VALUE_TYPE const& value);
551+ operator VALUE_TYPE() const;
552+
553+ // function call access
554+ VALUE_TYPE operator()() const;
555+ VALUE_TYPE operator()(VALUE_TYPE const& value);
556+
557+ // get and set access
558+ VALUE_TYPE Get() const;
559+ VALUE_TYPE Set(VALUE_TYPE const& value);
560+
561+ void SetSetterFunction(SetterFunction setter_function);
562
563 private:
564 // Properties themselves are not copyable.
565- ConnectableProperty(ConnectableProperty const&);
566- ConnectableProperty& operator=(ConnectableProperty const&);
567+ Property(Property const&);
568+ Property& operator=(Property const&);
569+
570+ bool DefaultSetter(VALUE_TYPE& target, VALUE_TYPE const& value);
571
572 private:
573 VALUE_TYPE value_;
574- bool notify_;
575+ SetterFunction setter_function_;
576+};
577+
578+// We could easily add a Write Only Property if and when we need one.
579+
580+/**
581+ * A read only property that uses a function to get the value.
582+ *
583+ * The read only property does not have a changed signal.
584+ *
585+ * The default constructor creates a read only property that always returns
586+ * the default constructed VALUE_TYPE.
587+ */
588+template <typename VALUE_TYPE>
589+class ROProperty
590+{
591+public:
592+ typedef sigc::slot<VALUE_TYPE> GetterFunction;
593+
594+ ROProperty();
595+ explicit ROProperty(GetterFunction getter_function);
596+
597+ operator VALUE_TYPE() const;
598+ VALUE_TYPE operator()() const;
599+ VALUE_TYPE Get() const;
600+
601+ void SetGetterFunction(GetterFunction getter_function);
602+
603+private:
604+ // ROProperties themselves are not copyable.
605+ ROProperty(ROProperty const&);
606+ ROProperty& operator=(ROProperty const&);
607+
608+ VALUE_TYPE DefaultGetter() const;
609+
610+private:
611+ GetterFunction getter_function_;
612+};
613+
614+/**
615+ * A read/write property that uses a functions to get and set the value.
616+ *
617+ * The value type is not stored in the propery, but maintained by the setter
618+ * and getter functions.
619+ *
620+ * A changed signal is emitted if the setter function specifies that the value
621+ * has changed.
622+ *
623+ * The default setter does nothing and emits no signal, and the default getter
624+ * returns a default constructed VALUE_TYPE. The default getter and setter
625+ * should be overridden through either the constructor args or through the
626+ * SetGetterFunction / SetSetterFunction.
627+ */
628+template <typename VALUE_TYPE>
629+class RWProperty : public PropertyChangedSignal<VALUE_TYPE>
630+{
631+public:
632+ typedef PropertyChangedSignal<VALUE_TYPE> SignalBase;
633+ typedef sigc::slot<bool, VALUE_TYPE const&> SetterFunction;
634+ typedef sigc::slot<VALUE_TYPE> GetterFunction;
635+
636+ RWProperty();
637+ RWProperty(GetterFunction getter_function, SetterFunction setter_function);
638+
639+ VALUE_TYPE operator=(VALUE_TYPE const& value);
640+ operator VALUE_TYPE() const;
641+
642+ // function call access
643+ VALUE_TYPE operator()() const;
644+ VALUE_TYPE operator()(VALUE_TYPE const& value);
645+
646+ // get and set access
647+ VALUE_TYPE Get() const;
648+ VALUE_TYPE Set(VALUE_TYPE const& value);
649+
650+ void SetGetterFunction(GetterFunction getter_function);
651+ void SetSetterFunction(SetterFunction setter_function);
652+
653+private:
654+ // RWProperties themselves are not copyable.
655+ RWProperty(RWProperty const&);
656+ RWProperty& operator=(RWProperty const&);
657+
658+ VALUE_TYPE DefaultGetter() const;
659+ bool DefaultSetter(VALUE_TYPE const& value);
660+
661+private:
662+ GetterFunction getter_function_;
663+ SetterFunction setter_function_;
664 };
665
666
667 class PropertyBase
668 {
669 public:
670- virtual bool set_value(std::string const& serialized_form) = 0;
671- virtual std::string get_serialized_value() const = 0;
672+ virtual bool SetValue(std::string const& serialized_form) = 0;
673+ virtual std::string GetSerializedValue() const = 0;
674 };
675
676
677@@ -86,15 +203,15 @@
678
679 /// If the property was not able to be set with the value, the method
680 /// returns false.
681- bool set_property(std::string const& name, const char* value);
682-
683- template <typename T>
684- bool set_property(std::string const& name, T const& value);
685-
686- template <typename T>
687- T get_property(std::string const& name, T* foo = 0);
688-
689- void add_property(std::string const& name, PropertyBase* property);
690+ bool SetProperty(std::string const& name, const char* value);
691+
692+ template <typename T>
693+ bool SetProperty(std::string const& name, T const& value);
694+
695+ template <typename T>
696+ T GetProperty(std::string const& name, T* foo = 0);
697+
698+ void AddProperty(std::string const& name, PropertyBase* property);
699
700 private:
701 // Introspectable objects are not copyable.
702@@ -107,22 +224,25 @@
703 };
704
705
706-template <typename T>
707-class Property : public ConnectableProperty<T>, public PropertyBase
708+template <typename VALUE_TYPE>
709+class SerializableProperty : public Property<VALUE_TYPE>, public PropertyBase
710 {
711 public:
712- typedef ConnectableProperty<T> Base;
713- typedef typename Base::ValueType ValueType;
714- typedef typename Base::TraitType TraitType;
715-
716- Property(Introspectable* owner, std::string const& name);
717- Property(Introspectable* owner, std::string const& name, T const& initial);
718-
719- virtual bool set_value(std::string const& serialized_form);
720- virtual std::string get_serialized_value() const;
721+ typedef Property<VALUE_TYPE> Base;
722+ typedef typename type::PropertyTrait<VALUE_TYPE> TraitType;
723+ typedef typename TraitType::ValueType ValueType;
724+
725+ SerializableProperty(Introspectable* owner,
726+ std::string const& name);
727+ SerializableProperty(Introspectable* owner,
728+ std::string const& name,
729+ VALUE_TYPE const& initial);
730+
731+ virtual bool SetValue(std::string const& serialized_form);
732+ virtual std::string GetSerializedValue() const;
733
734 // Operator assignment is not inherited nicely, so redeclare it here.
735- ValueType const& operator=(ValueType const& value);
736+ VALUE_TYPE operator=(VALUE_TYPE const& value);
737
738 private:
739 std::string name_;
740
741=== modified file 'tests/test_properties.cpp'
742--- tests/test_properties.cpp 2011-06-27 14:18:42 +0000
743+++ tests/test_properties.cpp 2011-07-11 02:16:37 +0000
744@@ -1,11 +1,14 @@
745 #include "NuxCore/Property.h"
746
747+#include <boost/scoped_ptr.hpp>
748 #include <sigc++/trackable.h>
749
750-#include <gtest/gtest.h>
751+#include <gmock/gmock.h>
752 #include <vector>
753 #include <stdexcept>
754
755+using namespace testing;
756+
757 namespace {
758
759 template <typename T>
760@@ -98,62 +101,327 @@
761 }
762
763
764-TEST(TestConnectableProperty, TestConstruction) {
765- nux::ConnectableProperty<std::string> string_prop;
766- EXPECT_EQ("", string_prop());
767- EXPECT_EQ("", string_prop.get());
768- EXPECT_EQ("", static_cast<std::string>(string_prop));
769- nux::ConnectableProperty<std::string> string_prop_val("hello");
770- EXPECT_EQ("hello", string_prop_val());
771- EXPECT_EQ("hello", string_prop_val.get());
772- EXPECT_EQ("hello", static_cast<std::string>(string_prop_val));
773-}
774-
775 template <typename T>
776 struct ChangeRecorder : sigc::trackable
777 {
778+ typedef sigc::slot<void, T const&> Listener;
779+
780+ Listener listener()
781+ {
782+ return sigc::mem_fun(this, &ChangeRecorder<T>::value_changed);
783+ }
784+
785 void value_changed(T const& value)
786 {
787 changed_values.push_back(value);
788 }
789 typedef std::vector<T> ChangedValues;
790 ChangedValues changed_values;
791+
792+ int size() const { return changed_values.size(); }
793+ T last() const { return *changed_values.rbegin(); }
794 };
795
796-TEST(TestConnectableProperty, TestAssignmentNotification) {
797- nux::ConnectableProperty<std::string> string_prop;
798+
799+TEST(TestProperty, TestDefaultConstructor) {
800+ nux::Property<std::string> string_prop;
801+ // Need either an assignment or static cast to check the operator VALUE_TYPE
802+ // due to google-mock's template matching.
803+ std::string value = string_prop;
804+ EXPECT_THAT(value, Eq(""));
805+ EXPECT_THAT(string_prop.Get(), Eq(""));
806+ EXPECT_THAT(string_prop(), Eq(""));
807+}
808+
809+TEST(TestProperty, TestValueExplicitConstructor) {
810+ nux::Property<std::string> string_prop("Hello world!");
811+ // Need either an assignment or static cast to check the operator VALUE_TYPE
812+ // due to google-mock's template matching.
813+ std::string value = string_prop;
814+ EXPECT_THAT(value, Eq("Hello world!"));
815+ EXPECT_THAT(string_prop.Get(), Eq("Hello world!"));
816+ EXPECT_THAT(string_prop(), Eq("Hello world!"));
817+}
818+
819+TEST(TestProperty, TestAssignment) {
820+ nux::Property<std::string> string_prop;
821+ // Need either an assignment or static cast to check the operator VALUE_TYPE
822+ // due to google-mock's template matching.
823+ string_prop = "Assignment operator";
824+ std::string value = string_prop;
825+ EXPECT_THAT(value, Eq("Assignment operator"));
826+ EXPECT_THAT(string_prop.Get(), Eq("Assignment operator"));
827+ EXPECT_THAT(string_prop(), Eq("Assignment operator"));
828+
829+ string_prop.Set("Set method");
830+ value = string_prop;
831+ EXPECT_THAT(value, Eq("Set method"));
832+ EXPECT_THAT(string_prop.Get(), Eq("Set method"));
833+ EXPECT_THAT(string_prop(), Eq("Set method"));
834+
835+ string_prop("Function call assignment");
836+ value = string_prop;
837+ EXPECT_THAT(value, Eq("Function call assignment"));
838+ EXPECT_THAT(string_prop.Get(), Eq("Function call assignment"));
839+ EXPECT_THAT(string_prop(), Eq("Function call assignment"));
840+}
841+
842+TEST(TestProperty, TestChanged) {
843+ nux::Property<std::string> string_prop;
844 ChangeRecorder<std::string> recorder;
845- string_prop.changed.connect(
846- sigc::mem_fun(recorder, &ChangeRecorder<std::string>::value_changed));
847+ string_prop.changed.connect(recorder.listener());
848+
849 string_prop = "Hello world" ;
850- EXPECT_EQ(1, recorder.changed_values.size());
851- EXPECT_EQ("Hello world", recorder.changed_values[0]);
852+ EXPECT_THAT(1, Eq(recorder.size()));
853+ EXPECT_THAT("Hello world", Eq(recorder.last()));
854 // No notification if not changed.
855 string_prop = std::string("Hello world");
856- EXPECT_EQ(1, recorder.changed_values.size());
857+ EXPECT_THAT(1, Eq(recorder.size()));
858 }
859
860-TEST(TestConnectableProperty, TestEnableAndDisableNotification) {
861- nux::ConnectableProperty<std::string> string_prop;
862+TEST(TestProperty, TestEnableAndDisableNotifications) {
863+ nux::Property<std::string> string_prop;
864 ChangeRecorder<std::string> recorder;
865- string_prop.changed.connect(
866- sigc::mem_fun(recorder, &ChangeRecorder<std::string>::value_changed));
867- string_prop.disable_notifications();
868+ string_prop.changed.connect(recorder.listener());
869+
870+ string_prop.DisableNotifications();
871 string_prop = "Hello world" ;
872- EXPECT_EQ(0, recorder.changed_values.size());
873- string_prop.enable_notifications();
874+ EXPECT_THAT(0, Eq(recorder.size()));
875+
876+ string_prop.EnableNotifications();
877 // No notification if not changed.
878 string_prop = "Hello world" ;
879- EXPECT_EQ(0, recorder.changed_values.size());
880+ EXPECT_THAT(0, Eq(recorder.size()));
881
882 string_prop = "New value" ;
883- EXPECT_EQ(1, recorder.changed_values.size());
884- EXPECT_EQ("New value", recorder.changed_values[0]);
885-
886- nux::ConnectableProperty<TestEnum> enum_prop;
887- // This fails to compile.
888- // nux::ConnectableProperty<TestClass> class_prop;
889-}
890+ EXPECT_THAT(1, Eq(recorder.size()));
891+ EXPECT_THAT("New value", Eq(recorder.last()));
892+}
893+
894+bool string_prefix(std::string& target, std::string const& value)
895+{
896+ bool changed = false;
897+ std::string prefixed("prefix-" + value);
898+ if (target != prefixed)
899+ {
900+ target = prefixed;
901+ changed = true;
902+ }
903+ return changed;
904+}
905+
906+TEST(TestProperty, TestSetterConstructor) {
907+ nux::Property<std::string> string_prop("", sigc::ptr_fun(&string_prefix));
908+
909+ string_prop = "foo";
910+ // Need either an assignment or static cast to check the operator VALUE_TYPE
911+ // due to google-mock's template matching.
912+ std::string value = string_prop;
913+ EXPECT_THAT(value, Eq("prefix-foo"));
914+ EXPECT_THAT(string_prop.Get(), Eq("prefix-foo"));
915+ EXPECT_THAT(string_prop(), Eq("prefix-foo"));
916+}
917+
918+class FloatClamp
919+{
920+public:
921+ FloatClamp(float min, float max)
922+ : min_(min), max_(max)
923+ {
924+ }
925+ bool Set(float& target, float const& value)
926+ {
927+ bool changed = false;
928+ float new_val = std::min(max_, std::max(min_, value));
929+ if (target != new_val) {
930+ target = new_val;
931+ changed = true;
932+ }
933+ return changed;
934+ }
935+private:
936+ float min_;
937+ float max_;
938+};
939+
940+TEST(TestProperty, TestCustomSetterFunction) {
941+ nux::Property<float> float_prop;
942+ FloatClamp clamp(0, 1);
943+ float_prop.SetSetterFunction(sigc::mem_fun(&clamp, &FloatClamp::Set));
944+ ChangeRecorder<float> recorder;
945+ float_prop.changed.connect(recorder.listener());
946+
947+ // Since the default value for a float is zero, and we clamp at zero,
948+ // setting to a negative value will result in setting to zero, which will
949+ // not signal a changed event.
950+ float_prop = -2;
951+ EXPECT_THAT(float_prop(), Eq(0));
952+ EXPECT_THAT(0, Eq(recorder.size()));
953+
954+ float_prop = 0.5;
955+ EXPECT_THAT(float_prop(), Eq(0.5));
956+ EXPECT_THAT(1, Eq(recorder.size()));
957+ EXPECT_THAT(0.5, Eq(recorder.last()));
958+
959+ float_prop = 4;
960+ EXPECT_THAT(float_prop(), Eq(1));
961+ EXPECT_THAT(2, Eq(recorder.size()));
962+ EXPECT_THAT(1, Eq(recorder.last()));
963+}
964+
965+TEST(TestROProperty, TestDefaultConstructor) {
966+ nux::ROProperty<int> int_prop;
967+ int value = int_prop;
968+ EXPECT_THAT(value, Eq(0));
969+ EXPECT_THAT(int_prop(), Eq(0));
970+ EXPECT_THAT(int_prop.Get(), Eq(0));
971+
972+ nux::ROProperty<std::string> string_prop;
973+ std::string svalue = string_prop;
974+ EXPECT_THAT(svalue, Eq(""));
975+ EXPECT_THAT(string_prop(), Eq(""));
976+ EXPECT_THAT(string_prop.Get(), Eq(""));
977+}
978+
979+int simple_int_result()
980+{
981+ return 42;
982+}
983+
984+TEST(TestROProperty, TestGetterConstructor) {
985+ nux::ROProperty<int> int_prop(sigc::ptr_fun(&simple_int_result));
986+ int value = int_prop;
987+ EXPECT_THAT(value, Eq(42));
988+ EXPECT_THAT(int_prop(), Eq(42));
989+ EXPECT_THAT(int_prop.Get(), Eq(42));
990+}
991+
992+class Incrementer
993+{
994+public:
995+ Incrementer() : value_(0) {}
996+ int value() { return ++value_; }
997+private:
998+ int value_;
999+};
1000+
1001+TEST(TestROProperty, TestSetGetter) {
1002+ nux::ROProperty<int> int_prop;
1003+ Incrementer incrementer;
1004+ int_prop.SetGetterFunction(sigc::mem_fun(&incrementer, &Incrementer::value));
1005+
1006+ int value = int_prop;
1007+ EXPECT_THAT(value, Eq(1));
1008+ EXPECT_THAT(int_prop(), Eq(2));
1009+ EXPECT_THAT(int_prop.Get(), Eq(3));
1010+}
1011+
1012+
1013+TEST(TestRWProperty, TestDefaultConstructor) {
1014+ nux::RWProperty<int> int_prop;
1015+ ChangeRecorder<int> recorder;
1016+ int_prop.changed.connect(recorder.listener());
1017+
1018+ int_prop = 42;
1019+ int value = int_prop;
1020+ EXPECT_THAT(value, Eq(0));
1021+ EXPECT_THAT(int_prop(), Eq(0));
1022+ EXPECT_THAT(int_prop.Get(), Eq(0));
1023+ EXPECT_THAT(recorder.size(), Eq(0));
1024+}
1025+
1026+bool is_even(int const& value)
1027+{
1028+ return value % 2 == 0;
1029+}
1030+
1031+
1032+TEST(TestRWProperty, TestFunctionConstructor) {
1033+ // This is a somewhat convoluted example. The setter emits if the value is
1034+ // even, but the value being emitted is controlled by the incrementer.
1035+ Incrementer incrementer;
1036+ nux::RWProperty<int> int_prop(sigc::mem_fun(&incrementer, &Incrementer::value),
1037+ sigc::ptr_fun(&is_even));
1038+ ChangeRecorder<int> recorder;
1039+ int_prop.changed.connect(recorder.listener());
1040+
1041+ int_prop = 42;
1042+ EXPECT_THAT(recorder.size(), Eq(1));
1043+ EXPECT_THAT(recorder.last(), Eq(1));
1044+
1045+ // Catch the return value of the assignment. The getter is called.
1046+ int assign_result = int_prop = 13;
1047+ EXPECT_THAT(recorder.size(), Eq(1));
1048+ EXPECT_THAT(assign_result, Eq(2));
1049+
1050+ // each access increments the value.
1051+ int value = int_prop;
1052+ EXPECT_THAT(value, Eq(3));
1053+ EXPECT_THAT(int_prop(), Eq(4));
1054+ EXPECT_THAT(int_prop.Get(), Eq(5));
1055+}
1056+
1057+// This bit would normally be in the header file.
1058+class HiddenImpl
1059+{
1060+public:
1061+ HiddenImpl();
1062+
1063+ nux::RWProperty<std::string> name;
1064+private:
1065+ class Impl;
1066+ boost::scoped_ptr<Impl> pimpl;
1067+};
1068+
1069+// This bit is in the implementation file.
1070+class HiddenImpl::Impl
1071+{
1072+public:
1073+ bool set_name(std::string const& name) {
1074+ bool changed = false;
1075+ std::string new_name("Impl::" + name);
1076+ if (name_ != new_name) {
1077+ name_ = new_name;
1078+ changed = true;
1079+ }
1080+ return changed;
1081+ }
1082+ std::string get_name() const {
1083+ return name_;
1084+ }
1085+
1086+private:
1087+ std::string name_;
1088+};
1089+
1090+HiddenImpl::HiddenImpl()
1091+ : pimpl(new Impl())
1092+{
1093+ name.SetSetterFunction(sigc::mem_fun(pimpl.get(), &HiddenImpl::Impl::set_name));
1094+ name.SetGetterFunction(sigc::mem_fun(pimpl.get(), &HiddenImpl::Impl::get_name));
1095+}
1096+
1097+
1098+TEST(TestRWProperty, TestPimplClassExample) {
1099+ HiddenImpl hidden;
1100+ ChangeRecorder<std::string> recorder;
1101+ hidden.name.changed.connect(recorder.listener());
1102+
1103+ hidden.name = "NewName";
1104+ EXPECT_THAT(recorder.size(), Eq(1));
1105+ EXPECT_THAT(recorder.last(), Eq("Impl::NewName"));
1106+
1107+ // Since the name is updated before comparison, no event emitted.
1108+ hidden.name = "NewName";
1109+ EXPECT_THAT(recorder.size(), Eq(1));
1110+
1111+ std::string value = hidden.name;
1112+ EXPECT_THAT(value, Eq("Impl::NewName"));
1113+ EXPECT_THAT(hidden.name(), Eq("Impl::NewName"));
1114+ EXPECT_THAT(hidden.name.Get(), Eq("Impl::NewName"));
1115+}
1116+
1117+
1118
1119 struct TestProperties : nux::Introspectable
1120 {
1121@@ -162,15 +430,14 @@
1122 , index(this, "index")
1123 {}
1124
1125- nux::Property<std::string> name;
1126- nux::Property<int> index;
1127+ nux::SerializableProperty<std::string> name;
1128+ nux::SerializableProperty<int> index;
1129 };
1130
1131 TEST(TestIntrospectableProperty, TestSimplePropertyAccess) {
1132 TestProperties props;
1133 ChangeRecorder<std::string> recorder;
1134- props.name.changed.connect(
1135- sigc::mem_fun(recorder, &ChangeRecorder<std::string>::value_changed));
1136+ props.name.changed.connect(recorder.listener());
1137 EXPECT_EQ("", props.name());
1138 EXPECT_EQ(0, props.index());
1139 props.name = "Testing";
1140@@ -178,10 +445,10 @@
1141 EXPECT_EQ("Testing", props.name());
1142 props.name("New Value");
1143 EXPECT_EQ("New Value", props.name());
1144- props.name.set("Another");
1145+ props.name.Set("Another");
1146 EXPECT_EQ("Another", props.name());
1147
1148- EXPECT_EQ(3, recorder.changed_values.size());
1149+ EXPECT_EQ(3, recorder.size());
1150 EXPECT_EQ("Testing", recorder.changed_values[0]);
1151 EXPECT_EQ("New Value", recorder.changed_values[1]);
1152 EXPECT_EQ("Another", recorder.changed_values[2]);
1153@@ -191,52 +458,49 @@
1154 TestProperties props;
1155 ChangeRecorder<std::string> name_recorder;
1156 ChangeRecorder<int> index_recorder;
1157- props.name.changed.connect(
1158- sigc::mem_fun(name_recorder, &ChangeRecorder<std::string>::value_changed));
1159- props.index.changed.connect(
1160- sigc::mem_fun(index_recorder, &ChangeRecorder<int>::value_changed));
1161+ props.name.changed.connect(name_recorder.listener());
1162+ props.index.changed.connect(index_recorder.listener());
1163
1164 props.name = "Testing";
1165 props.index = 5;
1166- EXPECT_EQ("Testing", props.get_property<std::string>("name"));
1167- EXPECT_EQ("5", props.get_property<std::string>("index"));
1168- EXPECT_EQ(5, props.get_property<int>("index"));
1169+ EXPECT_EQ("Testing", props.GetProperty<std::string>("name"));
1170+ EXPECT_EQ("5", props.GetProperty<std::string>("index"));
1171+ EXPECT_EQ(5, props.GetProperty<int>("index"));
1172
1173- bool assigned = props.set_property("name", "New value");
1174+ bool assigned = props.SetProperty("name", "New value");
1175 EXPECT_TRUE(assigned);
1176 EXPECT_EQ("New value", props.name());
1177- EXPECT_EQ("New value", props.get_property<std::string>("name"));
1178+ EXPECT_EQ("New value", props.GetProperty<std::string>("name"));
1179 // A little dangreous, but legal.
1180- EXPECT_EQ(0, props.get_property<int>("name"));
1181+ EXPECT_EQ(0, props.GetProperty<int>("name"));
1182
1183- assigned = props.set_property("name", 42);
1184+ assigned = props.SetProperty("name", 42);
1185 EXPECT_TRUE(assigned);
1186 EXPECT_EQ("42", props.name());
1187- EXPECT_EQ("42", props.get_property<std::string>("name"));
1188+ EXPECT_EQ("42", props.GetProperty<std::string>("name"));
1189 // A little dangreous, but legal.
1190- EXPECT_EQ(42, props.get_property<int>("name"));
1191+ EXPECT_EQ(42, props.GetProperty<int>("name"));
1192
1193- assigned = props.set_property("index", 42);
1194+ assigned = props.SetProperty("index", 42);
1195 EXPECT_TRUE(assigned);
1196 EXPECT_EQ(42, props.index());
1197- EXPECT_EQ("42", props.get_property<std::string>("index"));
1198- EXPECT_EQ(42, props.get_property<int>("index"));
1199+ EXPECT_EQ("42", props.GetProperty<std::string>("index"));
1200+ EXPECT_EQ(42, props.GetProperty<int>("index"));
1201
1202- assigned = props.set_property("index", "hello");
1203+ assigned = props.SetProperty("index", "hello");
1204 EXPECT_FALSE(assigned);
1205 EXPECT_EQ(42, props.index());
1206- EXPECT_EQ("42", props.get_property<std::string>("index"));
1207- EXPECT_EQ(42, props.get_property<int>("index"));
1208+ EXPECT_EQ("42", props.GetProperty<std::string>("index"));
1209+ EXPECT_EQ(42, props.GetProperty<int>("index"));
1210
1211 // Gettin a non-existant property returns a default constructed instance.
1212- std::string surname = props.get_property<std::string>("surname");
1213+ std::string surname = props.GetProperty<std::string>("surname");
1214 EXPECT_EQ("", surname);
1215- int foo = props.get_property<int>("foo");
1216+ int foo = props.GetProperty<int>("foo");
1217 EXPECT_EQ(0, foo);
1218
1219- assigned = props.set_property("non-existant", "hello");
1220+ assigned = props.SetProperty("non-existant", "hello");
1221 EXPECT_FALSE(assigned);
1222-
1223 }
1224
1225

Subscribers

People subscribed via source and target branches