Nux

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

Proposed by Tim Penhey
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
NuxCore/Property-inl.h (+301/-145)
NuxCore/Property.h (+168/-48)
tests/test_properties.cpp (+327/-63)
To merge this branch: bzr merge lp:~thumper/nux/properties
Reviewer Review Type Date Requested Status
Jay Taoko (community) Approve
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.
Revision history for this message
Mikkel Kamstrup Erlandsen (kamstrup) 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.
   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 :-)

Revision history for this message
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?

Revision history for this message
Mikkel Kamstrup Erlandsen (kamstrup) 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?

Revision history for this message
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.

Revision history for this message
Mikkel Kamstrup Erlandsen (kamstrup) 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? 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?

Revision history for this message
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.

Revision history for this message
Mikkel Kamstrup Erlandsen (kamstrup) 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> ();
 }

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 :-)

Revision history for this message
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

Revision history for this message
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 :-)

Revision history for this message
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.

Revision history for this message
Mikkel Kamstrup Erlandsen (kamstrup) 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. 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.

Revision history for this message
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.

Revision history for this message
Mikkel Kamstrup Erlandsen (kamstrup) wrote :

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 :-)

Revision history for this message
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
384. By Tim Penhey

Land nux::Properties Mark II.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'NuxCore/Property-inl.h'
--- NuxCore/Property-inl.h 2011-06-27 14:18:42 +0000
+++ NuxCore/Property-inl.h 2011-07-11 02:16:37 +0000
@@ -24,165 +24,321 @@
2424
25namespace nux {25namespace nux {
2626
27template <typename VALUE_TYPE>27
28ConnectableProperty<VALUE_TYPE>::ConnectableProperty()28template <typename VALUE_TYPE>
29 : value_(VALUE_TYPE())29PropertyChangedSignal<VALUE_TYPE>::PropertyChangedSignal()
30 , notify_(true)30 : notify_(true)
31{}31{}
3232
33template <typename VALUE_TYPE>33template <typename VALUE_TYPE>
34ConnectableProperty<VALUE_TYPE>::ConnectableProperty(VALUE_TYPE const& initial)34void PropertyChangedSignal<VALUE_TYPE>::DisableNotifications()
35 : value_(initial)
36 , notify_(true)
37{}
38
39template <typename VALUE_TYPE>
40VALUE_TYPE const& ConnectableProperty<VALUE_TYPE>::operator=(VALUE_TYPE const& value)
41{
42 set(value);
43 return value_;
44}
45
46template <typename VALUE_TYPE>
47ConnectableProperty<VALUE_TYPE>::operator VALUE_TYPE const&() const
48{
49 return value_;
50}
51
52template <typename VALUE_TYPE>
53VALUE_TYPE const& ConnectableProperty<VALUE_TYPE>::operator()() const
54{
55 return value_;
56}
57
58template <typename VALUE_TYPE>
59void ConnectableProperty<VALUE_TYPE>::operator()(VALUE_TYPE const& value)
60{
61 set(value);
62}
63
64template <typename VALUE_TYPE>
65void ConnectableProperty<VALUE_TYPE>::disable_notifications()
66{35{
67 notify_ = false;36 notify_ = false;
68}37}
6938
70template <typename VALUE_TYPE>39template <typename VALUE_TYPE>
71void ConnectableProperty<VALUE_TYPE>::enable_notifications()40void PropertyChangedSignal<VALUE_TYPE>::EnableNotifications()
72{41{
73 notify_ = true;42 notify_ = true;
74}43}
7544
76 // get and set access45template <typename VALUE_TYPE>
77template <typename VALUE_TYPE>46void PropertyChangedSignal<VALUE_TYPE>::EmitChanged(VALUE_TYPE const& new_value)
78VALUE_TYPE const& ConnectableProperty<VALUE_TYPE>::get() const47{
79{48 if (notify_)
80 return value_;49 changed.emit(new_value);
81}50}
8251
83template <typename VALUE_TYPE>52
84void ConnectableProperty<VALUE_TYPE>::set(VALUE_TYPE const& value)53template <typename VALUE_TYPE>
85{54Property<VALUE_TYPE>::Property()
86 if (value != value_) {55 : value_(VALUE_TYPE())
87 value_ = value;56 , setter_function_(sigc::mem_fun(this, &Property<VALUE_TYPE>::DefaultSetter))
88 if (notify_) {57{}
89 changed.emit(value_);58
90 }59template <typename VALUE_TYPE>
91 }60Property<VALUE_TYPE>::Property(VALUE_TYPE const& initial)
92}61 : value_(initial)
62 , setter_function_(sigc::mem_fun(this, &Property<VALUE_TYPE>::DefaultSetter))
63{}
64
65template <typename VALUE_TYPE>
66Property<VALUE_TYPE>::Property(VALUE_TYPE const& initial,
67 SetterFunction setter_function)
68 : value_(initial)
69 , setter_function_(setter_function)
70{}
71
72template <typename VALUE_TYPE>
73VALUE_TYPE Property<VALUE_TYPE>::operator=(VALUE_TYPE const& value)
74{
75 return Set(value);
76}
77
78template <typename VALUE_TYPE>
79Property<VALUE_TYPE>::operator VALUE_TYPE() const
80{
81 return value_;
82}
83
84template <typename VALUE_TYPE>
85VALUE_TYPE Property<VALUE_TYPE>::operator()() const
86{
87 return value_;
88}
89
90template <typename VALUE_TYPE>
91VALUE_TYPE Property<VALUE_TYPE>::operator()(VALUE_TYPE const& value)
92{
93 return Set(value);
94}
95
96template <typename VALUE_TYPE>
97VALUE_TYPE Property<VALUE_TYPE>::Get() const
98{
99 return value_;
100}
101
102template <typename VALUE_TYPE>
103VALUE_TYPE Property<VALUE_TYPE>::Set(VALUE_TYPE const& value)
104{
105 if (setter_function_(value_, value))
106 SignalBase::EmitChanged(value_);
107 return value_;
108}
109
110template <typename VALUE_TYPE>
111bool Property<VALUE_TYPE>::DefaultSetter(VALUE_TYPE& target,
112 VALUE_TYPE const& value)
113{
114 bool changed = false;
115 if (target != value) {
116 target = value;
117 changed = true;
118 }
119 return changed;
120}
121
122template <typename VALUE_TYPE>
123void Property<VALUE_TYPE>::SetSetterFunction(SetterFunction setter_function)
124{
125 setter_function_ = setter_function;
126}
127
128
129template <typename VALUE_TYPE>
130ROProperty<VALUE_TYPE>::ROProperty()
131 : getter_function_(sigc::mem_fun(this, &ROProperty<VALUE_TYPE>::DefaultGetter))
132{}
133
134template <typename VALUE_TYPE>
135ROProperty<VALUE_TYPE>::ROProperty(GetterFunction getter_function)
136 : getter_function_(getter_function)
137{}
138
139template <typename VALUE_TYPE>
140ROProperty<VALUE_TYPE>::operator VALUE_TYPE() const
141{
142 return getter_function_();
143}
144
145template <typename VALUE_TYPE>
146VALUE_TYPE ROProperty<VALUE_TYPE>::operator()() const
147{
148 return getter_function_();
149}
150
151template <typename VALUE_TYPE>
152VALUE_TYPE ROProperty<VALUE_TYPE>::Get() const
153{
154 return getter_function_();
155}
156
157template <typename VALUE_TYPE>
158VALUE_TYPE ROProperty<VALUE_TYPE>::DefaultGetter() const
159{
160 return VALUE_TYPE();
161}
162
163template <typename VALUE_TYPE>
164void ROProperty<VALUE_TYPE>::SetGetterFunction(GetterFunction getter_function)
165{
166 getter_function_ = getter_function;
167}
168
169
170template <typename VALUE_TYPE>
171RWProperty<VALUE_TYPE>::RWProperty()
172 : getter_function_(sigc::mem_fun(this, &RWProperty<VALUE_TYPE>::DefaultGetter))
173 , setter_function_(sigc::mem_fun(this, &RWProperty<VALUE_TYPE>::DefaultSetter))
174{}
175
176template <typename VALUE_TYPE>
177RWProperty<VALUE_TYPE>::RWProperty(GetterFunction getter_function,
178 SetterFunction setter_function)
179 : getter_function_(getter_function)
180 , setter_function_(setter_function)
181{}
182
183template <typename VALUE_TYPE>
184VALUE_TYPE RWProperty<VALUE_TYPE>::operator=(VALUE_TYPE const& value)
185{
186 return Set(value);
187}
188
189template <typename VALUE_TYPE>
190RWProperty<VALUE_TYPE>::operator VALUE_TYPE() const
191{
192 return getter_function_();
193}
194
195template <typename VALUE_TYPE>
196VALUE_TYPE RWProperty<VALUE_TYPE>::operator()() const
197{
198 return getter_function_();
199}
200
201template <typename VALUE_TYPE>
202VALUE_TYPE RWProperty<VALUE_TYPE>::operator()(VALUE_TYPE const& value)
203{
204 return Set(value);
205}
206
207template <typename VALUE_TYPE>
208VALUE_TYPE RWProperty<VALUE_TYPE>::Get() const
209{
210 return getter_function_();
211}
212
213template <typename VALUE_TYPE>
214VALUE_TYPE RWProperty<VALUE_TYPE>::Set(VALUE_TYPE const& value)
215{
216 if (setter_function_(value))
217 {
218 VALUE_TYPE new_value = getter_function_();
219 SignalBase::EmitChanged(new_value);
220 return new_value;
221 }
222 return getter_function_();
223}
224
225template <typename VALUE_TYPE>
226VALUE_TYPE RWProperty<VALUE_TYPE>::DefaultGetter() const
227{
228 return VALUE_TYPE();
229}
230
231template <typename VALUE_TYPE>
232bool RWProperty<VALUE_TYPE>::DefaultSetter(VALUE_TYPE const& value)
233{
234 return false;
235}
236
237template <typename VALUE_TYPE>
238void RWProperty<VALUE_TYPE>::SetSetterFunction(SetterFunction setter_function)
239{
240 setter_function_ = setter_function;
241}
242
243template <typename VALUE_TYPE>
244void RWProperty<VALUE_TYPE>::SetGetterFunction(GetterFunction getter_function)
245{
246 getter_function_ = getter_function;
247}
248
93249
94// We need to provide a default constructor since we hide the copy ctor.250// We need to provide a default constructor since we hide the copy ctor.
95inline Introspectable::Introspectable()251inline Introspectable::Introspectable()
96{}252{}
97253
98inline void Introspectable::add_property(std::string const& name,254inline void Introspectable::AddProperty(std::string const& name,
99 PropertyBase* property)255 PropertyBase* property)
100{256{
101 // check to see if it exists and if it does barf horribly as we can't257 // check to see if it exists and if it does barf horribly as we can't
102 // have two properties with the same name;258 // have two properties with the same name;
103 properties_[name] = property;259 properties_[name] = property;
104}260}
105261
106inline bool Introspectable::set_property(std::string const& name,262inline bool Introspectable::SetProperty(std::string const& name,
107 const char* value)263 const char* value)
108{264{
109 PropertyContainer::iterator i = properties_.find(name);265 PropertyContainer::iterator i = properties_.find(name);
110 if (i == properties_.end())266 if (i == properties_.end())
111 return false;267 return false;
112 else268 else
113 return i->second->set_value(value);269 return i->second->SetValue(value);
114}270}
115271
116template <typename T>272template <typename T>
117bool Introspectable::set_property(std::string const& name, T const& value)273bool Introspectable::SetProperty(std::string const& name, T const& value)
118{274{
119 PropertyContainer::iterator i = properties_.find(name);275 PropertyContainer::iterator i = properties_.find(name);
120 if (i == properties_.end())276 if (i == properties_.end())
121 return false;277 return false;
122 else278 else
123 {279 {
124 return i->second->set_value(type::PropertyTrait<T>::to_string(value));280 return i->second->SetValue(type::PropertyTrait<T>::to_string(value));
125 }281 }
126}282}
127283
128template <typename T>284template <typename T>
129T Introspectable::get_property(std::string const& name, T* foo)285T Introspectable::GetProperty(std::string const& name, T* foo)
130{286{
131 PropertyContainer::iterator i = properties_.find(name);287 PropertyContainer::iterator i = properties_.find(name);
132 if (i == properties_.end())288 if (i == properties_.end())
133 return T();289 return T();
134290
135 std::string s = i->second->get_serialized_value();291 std::string s = i->second->GetSerializedValue();
136 std::pair<T, bool> result = type::PropertyTrait<T>::from_string(s);292 std::pair<T, bool> result = type::PropertyTrait<T>::from_string(s);
137 // If this is called with a template type that the property does not293 // If this is called with a template type that the property does not
138 // support nice conversion to, you'll get no error, but will get294 // support nice conversion to, you'll get no error, but will get
139 // a default constructed T. We could use an exception here.295 // a default constructed T. We could use an exception here.
140 return result.first;296 return result.first;
141}297}
142298
143299
144template <typename T>300template <typename T>
145Property<T>::Property(Introspectable* owner,301SerializableProperty<T>::SerializableProperty(Introspectable* owner,
146 std::string const& name)302 std::string const& name)
147: Base()303 : Base()
148, name_(name)304 , name_(name)
149{305{
150 owner->add_property(name, this);306 owner->AddProperty(name, this);
151}307}
152308
153template <typename T>309template <typename T>
154Property<T>::Property(Introspectable* owner,310SerializableProperty<T>::SerializableProperty(Introspectable* owner,
155 std::string const& name,311 std::string const& name,
156 T const& initial)312 T const& initial)
157: Base(initial)313 : Base(initial)
158, name_(name)314 , name_(name)
159{315{
160 owner->add_property(name, this);316 owner->AddProperty(name, this);
161}317}
162318
163template <typename T>319template <typename T>
164bool Property<T>::set_value(std::string const& serialized_form)320bool SerializableProperty<T>::SetValue(std::string const& serialized_form)
165{321{
166 std::pair<ValueType, bool> result = TraitType::from_string(serialized_form);322 std::pair<ValueType, bool> result = TraitType::from_string(serialized_form);
167 if (result.second) {323 if (result.second) {
168 set(result.first);324 Base::Set(result.first);
169 }325 }
170 return result.second;326 return result.second;
171}327}
172328
173template <typename T>329template <typename T>
174std::string Property<T>::get_serialized_value() const330std::string SerializableProperty<T>::GetSerializedValue() const
175{331{
176 return TraitType::to_string(Base::get());332 return TraitType::to_string(Base::Get());
177}333}
178334
179template <typename T>335template <typename T>
180typename Property<T>::ValueType const& Property<T>::operator=(typename Property<T>::ValueType const& value)336T SerializableProperty<T>::operator=(T const& value)
181{337{
182 set(value);338 Base::Set(value);
183 // There are no arguments to ‘get’ that depend on a template parameter,339 // There are no arguments to ‘get’ that depend on a template parameter,
184 // so we explicitly specify Base.340 // so we explicitly specify Base.
185 return Base::get();341 return Base::Get();
186}342}
187343
188344
189345
=== modified file 'NuxCore/Property.h'
--- NuxCore/Property.h 2011-06-27 14:18:42 +0000
+++ NuxCore/Property.h 2011-07-11 02:16:37 +0000
@@ -24,57 +24,174 @@
2424
25#include "PropertyTraits.h"25#include "PropertyTraits.h"
2626
27#include <string>
27#include <map>28#include <map>
28#include <sigc++/signal.h>29#include <sigc++/signal.h>
2930
3031/**
32 * Much of this property work is based on the work by Lois Goldthwaite,
33 * SC22/WG21/N1615=04-0055 - C++ Properties -- a Library Solution
34 *
35 * The basic ideas were extended to add update notifications, and
36 * serialisation and introspection.
37 */
31namespace nux {38namespace nux {
3239
33// TODO:
34// object serialisation
3540
36template <typename VALUE_TYPE>41template <typename VALUE_TYPE>
37class ConnectableProperty42class PropertyChangedSignal
38{43{
39public:44public:
40 typedef typename type::PropertyTrait<VALUE_TYPE> TraitType;45 PropertyChangedSignal();
41 typedef typename TraitType::ValueType ValueType;
42
43 ConnectableProperty();
44 ConnectableProperty(VALUE_TYPE const& initial);
45
46 VALUE_TYPE const& operator=(VALUE_TYPE const& value);
47 operator VALUE_TYPE const & () const;
48
49 // function call access
50 VALUE_TYPE const& operator()() const;
51 void operator()(VALUE_TYPE const& value);
52
53 // get and set access
54 VALUE_TYPE const& get() const;
55 void set(VALUE_TYPE const& value);
5646
57 sigc::signal<void, VALUE_TYPE const&> changed;47 sigc::signal<void, VALUE_TYPE const&> changed;
5848
59 void disable_notifications();49 void DisableNotifications();
60 void enable_notifications();50 void EnableNotifications();
51
52 void EmitChanged(VALUE_TYPE const& new_value);
53
54private:
55 bool notify_;
56};
57
58/**
59 * A read/write property that stores the value type.
60 *
61 * The default setter emits the changed event if and only if the value
62 * changes. A custom setter can be provided by passing in a setter function
63 * using sigc::mem_fun or sigc::ptr_fun.
64 */
65template <typename VALUE_TYPE>
66class Property : public PropertyChangedSignal<VALUE_TYPE>
67{
68public:
69 typedef PropertyChangedSignal<VALUE_TYPE> SignalBase;
70 typedef sigc::slot<bool, VALUE_TYPE&, VALUE_TYPE const&> SetterFunction;
71
72 Property();
73 explicit Property(VALUE_TYPE const& initial);
74 Property(VALUE_TYPE const& initial, SetterFunction setter_function);
75
76 VALUE_TYPE operator=(VALUE_TYPE const& value);
77 operator VALUE_TYPE() const;
78
79 // function call access
80 VALUE_TYPE operator()() const;
81 VALUE_TYPE operator()(VALUE_TYPE const& value);
82
83 // get and set access
84 VALUE_TYPE Get() const;
85 VALUE_TYPE Set(VALUE_TYPE const& value);
86
87 void SetSetterFunction(SetterFunction setter_function);
6188
62private:89private:
63 // Properties themselves are not copyable.90 // Properties themselves are not copyable.
64 ConnectableProperty(ConnectableProperty const&);91 Property(Property const&);
65 ConnectableProperty& operator=(ConnectableProperty const&);92 Property& operator=(Property const&);
93
94 bool DefaultSetter(VALUE_TYPE& target, VALUE_TYPE const& value);
6695
67private:96private:
68 VALUE_TYPE value_;97 VALUE_TYPE value_;
69 bool notify_;98 SetterFunction setter_function_;
99};
100
101// We could easily add a Write Only Property if and when we need one.
102
103/**
104 * A read only property that uses a function to get the value.
105 *
106 * The read only property does not have a changed signal.
107 *
108 * The default constructor creates a read only property that always returns
109 * the default constructed VALUE_TYPE.
110 */
111template <typename VALUE_TYPE>
112class ROProperty
113{
114public:
115 typedef sigc::slot<VALUE_TYPE> GetterFunction;
116
117 ROProperty();
118 explicit ROProperty(GetterFunction getter_function);
119
120 operator VALUE_TYPE() const;
121 VALUE_TYPE operator()() const;
122 VALUE_TYPE Get() const;
123
124 void SetGetterFunction(GetterFunction getter_function);
125
126private:
127 // ROProperties themselves are not copyable.
128 ROProperty(ROProperty const&);
129 ROProperty& operator=(ROProperty const&);
130
131 VALUE_TYPE DefaultGetter() const;
132
133private:
134 GetterFunction getter_function_;
135};
136
137/**
138 * A read/write property that uses a functions to get and set the value.
139 *
140 * The value type is not stored in the propery, but maintained by the setter
141 * and getter functions.
142 *
143 * A changed signal is emitted if the setter function specifies that the value
144 * has changed.
145 *
146 * The default setter does nothing and emits no signal, and the default getter
147 * returns a default constructed VALUE_TYPE. The default getter and setter
148 * should be overridden through either the constructor args or through the
149 * SetGetterFunction / SetSetterFunction.
150 */
151template <typename VALUE_TYPE>
152class RWProperty : public PropertyChangedSignal<VALUE_TYPE>
153{
154public:
155 typedef PropertyChangedSignal<VALUE_TYPE> SignalBase;
156 typedef sigc::slot<bool, VALUE_TYPE const&> SetterFunction;
157 typedef sigc::slot<VALUE_TYPE> GetterFunction;
158
159 RWProperty();
160 RWProperty(GetterFunction getter_function, SetterFunction setter_function);
161
162 VALUE_TYPE operator=(VALUE_TYPE const& value);
163 operator VALUE_TYPE() const;
164
165 // function call access
166 VALUE_TYPE operator()() const;
167 VALUE_TYPE operator()(VALUE_TYPE const& value);
168
169 // get and set access
170 VALUE_TYPE Get() const;
171 VALUE_TYPE Set(VALUE_TYPE const& value);
172
173 void SetGetterFunction(GetterFunction getter_function);
174 void SetSetterFunction(SetterFunction setter_function);
175
176private:
177 // RWProperties themselves are not copyable.
178 RWProperty(RWProperty const&);
179 RWProperty& operator=(RWProperty const&);
180
181 VALUE_TYPE DefaultGetter() const;
182 bool DefaultSetter(VALUE_TYPE const& value);
183
184private:
185 GetterFunction getter_function_;
186 SetterFunction setter_function_;
70};187};
71188
72189
73class PropertyBase190class PropertyBase
74{191{
75public:192public:
76 virtual bool set_value(std::string const& serialized_form) = 0;193 virtual bool SetValue(std::string const& serialized_form) = 0;
77 virtual std::string get_serialized_value() const = 0;194 virtual std::string GetSerializedValue() const = 0;
78};195};
79196
80197
@@ -86,15 +203,15 @@
86203
87 /// If the property was not able to be set with the value, the method204 /// If the property was not able to be set with the value, the method
88 /// returns false.205 /// returns false.
89 bool set_property(std::string const& name, const char* value);206 bool SetProperty(std::string const& name, const char* value);
90207
91 template <typename T>208 template <typename T>
92 bool set_property(std::string const& name, T const& value);209 bool SetProperty(std::string const& name, T const& value);
93210
94 template <typename T>211 template <typename T>
95 T get_property(std::string const& name, T* foo = 0);212 T GetProperty(std::string const& name, T* foo = 0);
96213
97 void add_property(std::string const& name, PropertyBase* property);214 void AddProperty(std::string const& name, PropertyBase* property);
98215
99private:216private:
100 // Introspectable objects are not copyable.217 // Introspectable objects are not copyable.
@@ -107,22 +224,25 @@
107};224};
108225
109226
110template <typename T>227template <typename VALUE_TYPE>
111class Property : public ConnectableProperty<T>, public PropertyBase228class SerializableProperty : public Property<VALUE_TYPE>, public PropertyBase
112{229{
113public:230public:
114 typedef ConnectableProperty<T> Base;231 typedef Property<VALUE_TYPE> Base;
115 typedef typename Base::ValueType ValueType;232 typedef typename type::PropertyTrait<VALUE_TYPE> TraitType;
116 typedef typename Base::TraitType TraitType;233 typedef typename TraitType::ValueType ValueType;
117234
118 Property(Introspectable* owner, std::string const& name);235 SerializableProperty(Introspectable* owner,
119 Property(Introspectable* owner, std::string const& name, T const& initial);236 std::string const& name);
120237 SerializableProperty(Introspectable* owner,
121 virtual bool set_value(std::string const& serialized_form);238 std::string const& name,
122 virtual std::string get_serialized_value() const;239 VALUE_TYPE const& initial);
240
241 virtual bool SetValue(std::string const& serialized_form);
242 virtual std::string GetSerializedValue() const;
123243
124 // Operator assignment is not inherited nicely, so redeclare it here.244 // Operator assignment is not inherited nicely, so redeclare it here.
125 ValueType const& operator=(ValueType const& value);245 VALUE_TYPE operator=(VALUE_TYPE const& value);
126246
127private:247private:
128 std::string name_;248 std::string name_;
129249
=== modified file 'tests/test_properties.cpp'
--- tests/test_properties.cpp 2011-06-27 14:18:42 +0000
+++ tests/test_properties.cpp 2011-07-11 02:16:37 +0000
@@ -1,11 +1,14 @@
1#include "NuxCore/Property.h"1#include "NuxCore/Property.h"
22
3#include <boost/scoped_ptr.hpp>
3#include <sigc++/trackable.h>4#include <sigc++/trackable.h>
45
5#include <gtest/gtest.h>6#include <gmock/gmock.h>
6#include <vector>7#include <vector>
7#include <stdexcept>8#include <stdexcept>
89
10using namespace testing;
11
9namespace {12namespace {
1013
11template <typename T>14template <typename T>
@@ -98,62 +101,327 @@
98}101}
99102
100103
101TEST(TestConnectableProperty, TestConstruction) {
102 nux::ConnectableProperty<std::string> string_prop;
103 EXPECT_EQ("", string_prop());
104 EXPECT_EQ("", string_prop.get());
105 EXPECT_EQ("", static_cast<std::string>(string_prop));
106 nux::ConnectableProperty<std::string> string_prop_val("hello");
107 EXPECT_EQ("hello", string_prop_val());
108 EXPECT_EQ("hello", string_prop_val.get());
109 EXPECT_EQ("hello", static_cast<std::string>(string_prop_val));
110}
111
112template <typename T>104template <typename T>
113struct ChangeRecorder : sigc::trackable105struct ChangeRecorder : sigc::trackable
114{106{
107 typedef sigc::slot<void, T const&> Listener;
108
109 Listener listener()
110 {
111 return sigc::mem_fun(this, &ChangeRecorder<T>::value_changed);
112 }
113
115 void value_changed(T const& value)114 void value_changed(T const& value)
116 {115 {
117 changed_values.push_back(value);116 changed_values.push_back(value);
118 }117 }
119 typedef std::vector<T> ChangedValues;118 typedef std::vector<T> ChangedValues;
120 ChangedValues changed_values;119 ChangedValues changed_values;
120
121 int size() const { return changed_values.size(); }
122 T last() const { return *changed_values.rbegin(); }
121};123};
122124
123TEST(TestConnectableProperty, TestAssignmentNotification) {125
124 nux::ConnectableProperty<std::string> string_prop;126TEST(TestProperty, TestDefaultConstructor) {
127 nux::Property<std::string> string_prop;
128 // Need either an assignment or static cast to check the operator VALUE_TYPE
129 // due to google-mock's template matching.
130 std::string value = string_prop;
131 EXPECT_THAT(value, Eq(""));
132 EXPECT_THAT(string_prop.Get(), Eq(""));
133 EXPECT_THAT(string_prop(), Eq(""));
134}
135
136TEST(TestProperty, TestValueExplicitConstructor) {
137 nux::Property<std::string> string_prop("Hello world!");
138 // Need either an assignment or static cast to check the operator VALUE_TYPE
139 // due to google-mock's template matching.
140 std::string value = string_prop;
141 EXPECT_THAT(value, Eq("Hello world!"));
142 EXPECT_THAT(string_prop.Get(), Eq("Hello world!"));
143 EXPECT_THAT(string_prop(), Eq("Hello world!"));
144}
145
146TEST(TestProperty, TestAssignment) {
147 nux::Property<std::string> string_prop;
148 // Need either an assignment or static cast to check the operator VALUE_TYPE
149 // due to google-mock's template matching.
150 string_prop = "Assignment operator";
151 std::string value = string_prop;
152 EXPECT_THAT(value, Eq("Assignment operator"));
153 EXPECT_THAT(string_prop.Get(), Eq("Assignment operator"));
154 EXPECT_THAT(string_prop(), Eq("Assignment operator"));
155
156 string_prop.Set("Set method");
157 value = string_prop;
158 EXPECT_THAT(value, Eq("Set method"));
159 EXPECT_THAT(string_prop.Get(), Eq("Set method"));
160 EXPECT_THAT(string_prop(), Eq("Set method"));
161
162 string_prop("Function call assignment");
163 value = string_prop;
164 EXPECT_THAT(value, Eq("Function call assignment"));
165 EXPECT_THAT(string_prop.Get(), Eq("Function call assignment"));
166 EXPECT_THAT(string_prop(), Eq("Function call assignment"));
167}
168
169TEST(TestProperty, TestChanged) {
170 nux::Property<std::string> string_prop;
125 ChangeRecorder<std::string> recorder;171 ChangeRecorder<std::string> recorder;
126 string_prop.changed.connect(172 string_prop.changed.connect(recorder.listener());
127 sigc::mem_fun(recorder, &ChangeRecorder<std::string>::value_changed));173
128 string_prop = "Hello world" ;174 string_prop = "Hello world" ;
129 EXPECT_EQ(1, recorder.changed_values.size());175 EXPECT_THAT(1, Eq(recorder.size()));
130 EXPECT_EQ("Hello world", recorder.changed_values[0]);176 EXPECT_THAT("Hello world", Eq(recorder.last()));
131 // No notification if not changed.177 // No notification if not changed.
132 string_prop = std::string("Hello world");178 string_prop = std::string("Hello world");
133 EXPECT_EQ(1, recorder.changed_values.size());179 EXPECT_THAT(1, Eq(recorder.size()));
134}180}
135181
136TEST(TestConnectableProperty, TestEnableAndDisableNotification) {182TEST(TestProperty, TestEnableAndDisableNotifications) {
137 nux::ConnectableProperty<std::string> string_prop;183 nux::Property<std::string> string_prop;
138 ChangeRecorder<std::string> recorder;184 ChangeRecorder<std::string> recorder;
139 string_prop.changed.connect(185 string_prop.changed.connect(recorder.listener());
140 sigc::mem_fun(recorder, &ChangeRecorder<std::string>::value_changed));186
141 string_prop.disable_notifications();187 string_prop.DisableNotifications();
142 string_prop = "Hello world" ;188 string_prop = "Hello world" ;
143 EXPECT_EQ(0, recorder.changed_values.size());189 EXPECT_THAT(0, Eq(recorder.size()));
144 string_prop.enable_notifications();190
191 string_prop.EnableNotifications();
145 // No notification if not changed.192 // No notification if not changed.
146 string_prop = "Hello world" ;193 string_prop = "Hello world" ;
147 EXPECT_EQ(0, recorder.changed_values.size());194 EXPECT_THAT(0, Eq(recorder.size()));
148195
149 string_prop = "New value" ;196 string_prop = "New value" ;
150 EXPECT_EQ(1, recorder.changed_values.size());197 EXPECT_THAT(1, Eq(recorder.size()));
151 EXPECT_EQ("New value", recorder.changed_values[0]);198 EXPECT_THAT("New value", Eq(recorder.last()));
152199}
153 nux::ConnectableProperty<TestEnum> enum_prop;200
154 // This fails to compile.201bool string_prefix(std::string& target, std::string const& value)
155 // nux::ConnectableProperty<TestClass> class_prop;202{
156}203 bool changed = false;
204 std::string prefixed("prefix-" + value);
205 if (target != prefixed)
206 {
207 target = prefixed;
208 changed = true;
209 }
210 return changed;
211}
212
213TEST(TestProperty, TestSetterConstructor) {
214 nux::Property<std::string> string_prop("", sigc::ptr_fun(&string_prefix));
215
216 string_prop = "foo";
217 // Need either an assignment or static cast to check the operator VALUE_TYPE
218 // due to google-mock's template matching.
219 std::string value = string_prop;
220 EXPECT_THAT(value, Eq("prefix-foo"));
221 EXPECT_THAT(string_prop.Get(), Eq("prefix-foo"));
222 EXPECT_THAT(string_prop(), Eq("prefix-foo"));
223}
224
225class FloatClamp
226{
227public:
228 FloatClamp(float min, float max)
229 : min_(min), max_(max)
230 {
231 }
232 bool Set(float& target, float const& value)
233 {
234 bool changed = false;
235 float new_val = std::min(max_, std::max(min_, value));
236 if (target != new_val) {
237 target = new_val;
238 changed = true;
239 }
240 return changed;
241 }
242private:
243 float min_;
244 float max_;
245};
246
247TEST(TestProperty, TestCustomSetterFunction) {
248 nux::Property<float> float_prop;
249 FloatClamp clamp(0, 1);
250 float_prop.SetSetterFunction(sigc::mem_fun(&clamp, &FloatClamp::Set));
251 ChangeRecorder<float> recorder;
252 float_prop.changed.connect(recorder.listener());
253
254 // Since the default value for a float is zero, and we clamp at zero,
255 // setting to a negative value will result in setting to zero, which will
256 // not signal a changed event.
257 float_prop = -2;
258 EXPECT_THAT(float_prop(), Eq(0));
259 EXPECT_THAT(0, Eq(recorder.size()));
260
261 float_prop = 0.5;
262 EXPECT_THAT(float_prop(), Eq(0.5));
263 EXPECT_THAT(1, Eq(recorder.size()));
264 EXPECT_THAT(0.5, Eq(recorder.last()));
265
266 float_prop = 4;
267 EXPECT_THAT(float_prop(), Eq(1));
268 EXPECT_THAT(2, Eq(recorder.size()));
269 EXPECT_THAT(1, Eq(recorder.last()));
270}
271
272TEST(TestROProperty, TestDefaultConstructor) {
273 nux::ROProperty<int> int_prop;
274 int value = int_prop;
275 EXPECT_THAT(value, Eq(0));
276 EXPECT_THAT(int_prop(), Eq(0));
277 EXPECT_THAT(int_prop.Get(), Eq(0));
278
279 nux::ROProperty<std::string> string_prop;
280 std::string svalue = string_prop;
281 EXPECT_THAT(svalue, Eq(""));
282 EXPECT_THAT(string_prop(), Eq(""));
283 EXPECT_THAT(string_prop.Get(), Eq(""));
284}
285
286int simple_int_result()
287{
288 return 42;
289}
290
291TEST(TestROProperty, TestGetterConstructor) {
292 nux::ROProperty<int> int_prop(sigc::ptr_fun(&simple_int_result));
293 int value = int_prop;
294 EXPECT_THAT(value, Eq(42));
295 EXPECT_THAT(int_prop(), Eq(42));
296 EXPECT_THAT(int_prop.Get(), Eq(42));
297}
298
299class Incrementer
300{
301public:
302 Incrementer() : value_(0) {}
303 int value() { return ++value_; }
304private:
305 int value_;
306};
307
308TEST(TestROProperty, TestSetGetter) {
309 nux::ROProperty<int> int_prop;
310 Incrementer incrementer;
311 int_prop.SetGetterFunction(sigc::mem_fun(&incrementer, &Incrementer::value));
312
313 int value = int_prop;
314 EXPECT_THAT(value, Eq(1));
315 EXPECT_THAT(int_prop(), Eq(2));
316 EXPECT_THAT(int_prop.Get(), Eq(3));
317}
318
319
320TEST(TestRWProperty, TestDefaultConstructor) {
321 nux::RWProperty<int> int_prop;
322 ChangeRecorder<int> recorder;
323 int_prop.changed.connect(recorder.listener());
324
325 int_prop = 42;
326 int value = int_prop;
327 EXPECT_THAT(value, Eq(0));
328 EXPECT_THAT(int_prop(), Eq(0));
329 EXPECT_THAT(int_prop.Get(), Eq(0));
330 EXPECT_THAT(recorder.size(), Eq(0));
331}
332
333bool is_even(int const& value)
334{
335 return value % 2 == 0;
336}
337
338
339TEST(TestRWProperty, TestFunctionConstructor) {
340 // This is a somewhat convoluted example. The setter emits if the value is
341 // even, but the value being emitted is controlled by the incrementer.
342 Incrementer incrementer;
343 nux::RWProperty<int> int_prop(sigc::mem_fun(&incrementer, &Incrementer::value),
344 sigc::ptr_fun(&is_even));
345 ChangeRecorder<int> recorder;
346 int_prop.changed.connect(recorder.listener());
347
348 int_prop = 42;
349 EXPECT_THAT(recorder.size(), Eq(1));
350 EXPECT_THAT(recorder.last(), Eq(1));
351
352 // Catch the return value of the assignment. The getter is called.
353 int assign_result = int_prop = 13;
354 EXPECT_THAT(recorder.size(), Eq(1));
355 EXPECT_THAT(assign_result, Eq(2));
356
357 // each access increments the value.
358 int value = int_prop;
359 EXPECT_THAT(value, Eq(3));
360 EXPECT_THAT(int_prop(), Eq(4));
361 EXPECT_THAT(int_prop.Get(), Eq(5));
362}
363
364// This bit would normally be in the header file.
365class HiddenImpl
366{
367public:
368 HiddenImpl();
369
370 nux::RWProperty<std::string> name;
371private:
372 class Impl;
373 boost::scoped_ptr<Impl> pimpl;
374};
375
376// This bit is in the implementation file.
377class HiddenImpl::Impl
378{
379public:
380 bool set_name(std::string const& name) {
381 bool changed = false;
382 std::string new_name("Impl::" + name);
383 if (name_ != new_name) {
384 name_ = new_name;
385 changed = true;
386 }
387 return changed;
388 }
389 std::string get_name() const {
390 return name_;
391 }
392
393private:
394 std::string name_;
395};
396
397HiddenImpl::HiddenImpl()
398 : pimpl(new Impl())
399{
400 name.SetSetterFunction(sigc::mem_fun(pimpl.get(), &HiddenImpl::Impl::set_name));
401 name.SetGetterFunction(sigc::mem_fun(pimpl.get(), &HiddenImpl::Impl::get_name));
402}
403
404
405TEST(TestRWProperty, TestPimplClassExample) {
406 HiddenImpl hidden;
407 ChangeRecorder<std::string> recorder;
408 hidden.name.changed.connect(recorder.listener());
409
410 hidden.name = "NewName";
411 EXPECT_THAT(recorder.size(), Eq(1));
412 EXPECT_THAT(recorder.last(), Eq("Impl::NewName"));
413
414 // Since the name is updated before comparison, no event emitted.
415 hidden.name = "NewName";
416 EXPECT_THAT(recorder.size(), Eq(1));
417
418 std::string value = hidden.name;
419 EXPECT_THAT(value, Eq("Impl::NewName"));
420 EXPECT_THAT(hidden.name(), Eq("Impl::NewName"));
421 EXPECT_THAT(hidden.name.Get(), Eq("Impl::NewName"));
422}
423
424
157425
158struct TestProperties : nux::Introspectable426struct TestProperties : nux::Introspectable
159{427{
@@ -162,15 +430,14 @@
162 , index(this, "index")430 , index(this, "index")
163 {}431 {}
164432
165 nux::Property<std::string> name;433 nux::SerializableProperty<std::string> name;
166 nux::Property<int> index;434 nux::SerializableProperty<int> index;
167};435};
168436
169TEST(TestIntrospectableProperty, TestSimplePropertyAccess) {437TEST(TestIntrospectableProperty, TestSimplePropertyAccess) {
170 TestProperties props;438 TestProperties props;
171 ChangeRecorder<std::string> recorder;439 ChangeRecorder<std::string> recorder;
172 props.name.changed.connect(440 props.name.changed.connect(recorder.listener());
173 sigc::mem_fun(recorder, &ChangeRecorder<std::string>::value_changed));
174 EXPECT_EQ("", props.name());441 EXPECT_EQ("", props.name());
175 EXPECT_EQ(0, props.index());442 EXPECT_EQ(0, props.index());
176 props.name = "Testing";443 props.name = "Testing";
@@ -178,10 +445,10 @@
178 EXPECT_EQ("Testing", props.name());445 EXPECT_EQ("Testing", props.name());
179 props.name("New Value");446 props.name("New Value");
180 EXPECT_EQ("New Value", props.name());447 EXPECT_EQ("New Value", props.name());
181 props.name.set("Another");448 props.name.Set("Another");
182 EXPECT_EQ("Another", props.name());449 EXPECT_EQ("Another", props.name());
183450
184 EXPECT_EQ(3, recorder.changed_values.size());451 EXPECT_EQ(3, recorder.size());
185 EXPECT_EQ("Testing", recorder.changed_values[0]);452 EXPECT_EQ("Testing", recorder.changed_values[0]);
186 EXPECT_EQ("New Value", recorder.changed_values[1]);453 EXPECT_EQ("New Value", recorder.changed_values[1]);
187 EXPECT_EQ("Another", recorder.changed_values[2]);454 EXPECT_EQ("Another", recorder.changed_values[2]);
@@ -191,52 +458,49 @@
191 TestProperties props;458 TestProperties props;
192 ChangeRecorder<std::string> name_recorder;459 ChangeRecorder<std::string> name_recorder;
193 ChangeRecorder<int> index_recorder;460 ChangeRecorder<int> index_recorder;
194 props.name.changed.connect(461 props.name.changed.connect(name_recorder.listener());
195 sigc::mem_fun(name_recorder, &ChangeRecorder<std::string>::value_changed));462 props.index.changed.connect(index_recorder.listener());
196 props.index.changed.connect(
197 sigc::mem_fun(index_recorder, &ChangeRecorder<int>::value_changed));
198463
199 props.name = "Testing";464 props.name = "Testing";
200 props.index = 5;465 props.index = 5;
201 EXPECT_EQ("Testing", props.get_property<std::string>("name"));466 EXPECT_EQ("Testing", props.GetProperty<std::string>("name"));
202 EXPECT_EQ("5", props.get_property<std::string>("index"));467 EXPECT_EQ("5", props.GetProperty<std::string>("index"));
203 EXPECT_EQ(5, props.get_property<int>("index"));468 EXPECT_EQ(5, props.GetProperty<int>("index"));
204469
205 bool assigned = props.set_property("name", "New value");470 bool assigned = props.SetProperty("name", "New value");
206 EXPECT_TRUE(assigned);471 EXPECT_TRUE(assigned);
207 EXPECT_EQ("New value", props.name());472 EXPECT_EQ("New value", props.name());
208 EXPECT_EQ("New value", props.get_property<std::string>("name"));473 EXPECT_EQ("New value", props.GetProperty<std::string>("name"));
209 // A little dangreous, but legal.474 // A little dangreous, but legal.
210 EXPECT_EQ(0, props.get_property<int>("name"));475 EXPECT_EQ(0, props.GetProperty<int>("name"));
211476
212 assigned = props.set_property("name", 42);477 assigned = props.SetProperty("name", 42);
213 EXPECT_TRUE(assigned);478 EXPECT_TRUE(assigned);
214 EXPECT_EQ("42", props.name());479 EXPECT_EQ("42", props.name());
215 EXPECT_EQ("42", props.get_property<std::string>("name"));480 EXPECT_EQ("42", props.GetProperty<std::string>("name"));
216 // A little dangreous, but legal.481 // A little dangreous, but legal.
217 EXPECT_EQ(42, props.get_property<int>("name"));482 EXPECT_EQ(42, props.GetProperty<int>("name"));
218483
219 assigned = props.set_property("index", 42);484 assigned = props.SetProperty("index", 42);
220 EXPECT_TRUE(assigned);485 EXPECT_TRUE(assigned);
221 EXPECT_EQ(42, props.index());486 EXPECT_EQ(42, props.index());
222 EXPECT_EQ("42", props.get_property<std::string>("index"));487 EXPECT_EQ("42", props.GetProperty<std::string>("index"));
223 EXPECT_EQ(42, props.get_property<int>("index"));488 EXPECT_EQ(42, props.GetProperty<int>("index"));
224489
225 assigned = props.set_property("index", "hello");490 assigned = props.SetProperty("index", "hello");
226 EXPECT_FALSE(assigned);491 EXPECT_FALSE(assigned);
227 EXPECT_EQ(42, props.index());492 EXPECT_EQ(42, props.index());
228 EXPECT_EQ("42", props.get_property<std::string>("index"));493 EXPECT_EQ("42", props.GetProperty<std::string>("index"));
229 EXPECT_EQ(42, props.get_property<int>("index"));494 EXPECT_EQ(42, props.GetProperty<int>("index"));
230495
231 // Gettin a non-existant property returns a default constructed instance.496 // Gettin a non-existant property returns a default constructed instance.
232 std::string surname = props.get_property<std::string>("surname");497 std::string surname = props.GetProperty<std::string>("surname");
233 EXPECT_EQ("", surname);498 EXPECT_EQ("", surname);
234 int foo = props.get_property<int>("foo");499 int foo = props.GetProperty<int>("foo");
235 EXPECT_EQ(0, foo);500 EXPECT_EQ(0, foo);
236501
237 assigned = props.set_property("non-existant", "hello");502 assigned = props.SetProperty("non-existant", "hello");
238 EXPECT_FALSE(assigned);503 EXPECT_FALSE(assigned);
239
240}504}
241505
242506

Subscribers

People subscribed via source and target branches