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