1 #ifndef ATTRIBUTE__H
2 #define ATTRIBUTE__H
3
4 #include <functional>
5 #include <stdexcept>
6
7 #include "attribute.h"
8 #include "object.h"
9 #include "rectangle.h"
10 #include "signal.h"
11 #include "x11-types.h" // for Color
12
13 class Completion;
14
15 template<typename T>
16 class Attribute_ : public Attribute {
17 public:
18 // function that validates a new attribute value against the current state
19 // if the attribute value is valid, returns the empty string.
20 // if the attribute value is invalid, returns an error message to be
21 // escalated to the user.
22 using Validator = std::function<std::string(T)>;
23
24 /** The constructors for Attribute_ and also for DynAttribute_ follow
25 * the a common pattern:
26 *
27 * - The first argument is the owner object
28 *
29 * - The second argument is the name displayed to the user
30 *
31 * - The third argument the reading behaviour of the attribute:
32 * * for a plain Attribute_ this is simply the initial value
33 * * for a DynAttribute_ this is a member of the owner (or a lambda)
34 * that returns the value of the attribute.
35 *
36 * - The fourth argument describes the writing behaviour of the
37 * attribute: If the fourth argument is absent, the attribute is
38 * read-only.
39 * * The fourth argument for an Attribute_ is a member of owner (or
40 * lambda) that validates a new value of the attribute and returns an
41 * error message if the new value is not acceptable for this
42 * attribute (E.g. because a name is already taken or does not
43 * respect a certain format).
44 * * The fourth argument of a DynAttribute_ is a member of owner (or
45 * lambda) that internally processes the new value (e.g. parsing) and
46 * returns an error message if the new value is not acceptable.
47 */
48
49 //! A read-only attribute of owner of type T
Attribute_(Object * owner,const std::string & name,const T & payload)50 Attribute_(Object* owner, const std::string &name, const T &payload)
51 : Attribute(name, false)
52 , validator_({})
53 , payload_ (payload)
54 , defaultValue_ (payload)
55 {
56 // the following will call Attribute::setOwner()
57 // maybe this should be changed at some point,
58 // e.g. when we got rid of Object::wireAttributes()
59 owner->addAttribute(this);
60 }
61
62 //! A writable attribute of owner of type T
63 template <typename Owner>
Attribute_(Owner * owner,const std::string & name,const T & payload,std::string (Owner::* validator)(T))64 Attribute_(Owner* owner, const std::string &name, const T &payload,
65 std::string(Owner::*validator)(T))
66 : Attribute(name, true)
67 , validator_(std::bind(validator, owner, std::placeholders::_1))
68 , payload_ (payload)
69 , defaultValue_ (payload)
70 {
71 // the following will call Attribute::setOwner()
72 // maybe this should be changed at some point,
73 // e.g. when we got rid of Object::wireAttributes()
74 owner->addAttribute(this);
75 }
76 //! A writable attribute of owner of type T
Attribute_(Object * owner,const std::string & name,const T & payload,Validator validator)77 Attribute_(Object* owner, const std::string &name, const T &payload,
78 Validator validator)
79 : Attribute(name, true)
80 , validator_(validator)
81 , payload_ (payload)
82 , defaultValue_ (payload)
83 {
84 // the following will call Attribute::setOwner()
85 // maybe this should be changed at some point,
86 // e.g. when we got rid of Object::wireAttributes()
87 owner->addAttribute(this);
88 }
89
90 //! Deprecated constructor, that will be removed and only remains for
91 //compatibility
Attribute_(const std::string & name,const T & payload)92 Attribute_(const std::string &name, const T &payload)
93 : Attribute(name, false)
94 , payload_ (payload)
95 , defaultValue_ (payload)
96 {
97 }
98
99 // set the method called for validation of external changes
100 // this implicitely makes the attribute writable
setValidator(Validator v)101 void setValidator(Validator v) {
102 validator_ = v;
103 writable_ = true;
104 }
105
106 // delegate type() to a static methods in specializations,
107 // so it can also be used by DynAttribute_<T>
type()108 Type type() override { return Attribute_<T>::staticType(); }
109 static Type staticType();
110
111 // wrap Converter::str() for convenience
str()112 std::string str() override { return Converter<T>::str(payload_); }
113
complete(Completion & complete)114 void complete(Completion& complete) override {
115 Converter<T>::complete(complete, &payload_);
116 }
117
118 //! a signal that is emitted whenever the value changes
changed()119 Signal_<T>& changed() override { return changed_; }
120 //! a signal that is emitted when the user changes this attribute
changedByUser()121 Signal_<T>& changedByUser() { return changedByUser_; }
122
resetValue()123 bool resetValue() override {
124 operator=(defaultValue_);
125 return true;
126 }
127
128 // accessors only to be used by owner!
T()129 operator T() { return payload_; }
T()130 operator const T() const { return payload_; }
131 // operator= is only used by the owner and us
132 void operator=(const T &payload) {
133 payload_ = payload;
134 notifyHooks();
135 changed_.emit(payload);
136 }
137
138 bool operator==(const T &payload) {
139 return payload_ == payload;
140 }
141 bool operator!=(const T &payload) {
142 return payload_ != payload;
143 }
144
change(const std::string & payload_str)145 std::string change(const std::string &payload_str) override {
146 if (!writable()) {
147 return "attribute is read-only";
148 }
149 try {
150 T new_payload = Converter<T>::parse(payload_str, payload_); // throws
151
152 // validate, if needed
153 if (validator_) {
154 auto error_message = (validator_)(new_payload);
155 if (!error_message.empty()) {
156 return error_message;
157 }
158 }
159
160 // set and trigger stuff
161 if (new_payload != payload_) {
162 this->operator=(new_payload);
163 changedByUser_.emit(payload_);
164 }
165 } catch (std::invalid_argument const& e) {
166 return std::string("invalid argument: ") + e.what();
167 } catch (std::out_of_range const& e) {
168 return std::string("out of range: ") + e.what();
169 }
170 return {}; // all good
171 }
172
173 const T& operator*() const {
174 return payload_;
175 }
176 const T* operator->() const {
177 return &payload_;
178 }
operator()179 const T& operator()() const {
180 return payload_;
181 }
182
183 protected:
notifyHooks()184 void notifyHooks() {
185 if (owner_) {
186 owner_->notifyHooks(HookEvent::ATTRIBUTE_CHANGED, name_);
187 }
188 }
189
190 Validator validator_;
191 Signal_<T> changed_;
192 Signal_<T> changedByUser_;
193 T payload_;
194 T defaultValue_;
195 };
196
197 /** Type mappings **/
198 template<>
staticType()199 inline Type Attribute_<int>::staticType() { return Type::INT; }
200 template<>
staticType()201 inline Type Attribute_<unsigned long>::staticType() { return Type::ULONG; }
202 template<>
staticType()203 inline Type Attribute_<bool>::staticType() { return Type::BOOL; }
204 template<>
staticType()205 inline Type Attribute_<std::string>::staticType() { return Type::STRING; }
206 template<>
staticType()207 inline Type Attribute_<Color>::staticType() { return Type::COLOR; }
208 template<>
staticType()209 inline Type Attribute_<Rectangle>::staticType() { return Type::RECTANGLE; }
210
211 template<typename T>
212 class DynAttribute_ : public Attribute {
213 public:
214 // each time a dynamic attribute is read, the getter_ is called in order to
215 // get the actual value
DynAttribute_(Object * owner,const std::string & name,std::function<T ()> getter)216 DynAttribute_(Object* owner, const std::string &name, std::function<T()> getter)
217 : Attribute(name, false)
218 , getter_(getter)
219 {
220 hookable_ = false;
221 owner->addAttribute(this);
222 }
223
224 // in this case, also write operations are delegated
DynAttribute_(const std::string & name,std::function<T ()> getter,std::function<std::string (T)> setter)225 DynAttribute_(const std::string &name, std::function<T()> getter, std::function<std::string(T)> setter)
226 : Attribute(name, true)
227 , getter_(getter)
228 , setter_(setter)
229 {
230 hookable_ = false;
231 writable_ = true;
232 }
233
complete(Completion & completion)234 void complete(Completion& completion) override {
235 Converter<T>::complete(completion, nullptr);
236 }
237
238 // each time a dynamic attribute is read, the getter_ is called in order to
239 // get the actual value. Here, we use C++-style member function pointers such that
240 // the type checker fills the template argument 'Owner' automatically
241 template <typename Owner>
DynAttribute_(Owner * owner,const std::string & name,T (Owner::* getter)())242 DynAttribute_(Owner* owner, const std::string &name,
243 // std::function<T(Owner*)> getter // this does not work!
244 T (Owner::*getter)()
245 )
246 : Attribute(name, false)
247 , getter_(std::bind(getter, owner))
248 {
249 hookable_ = false;
250 // the following will call Attribute::setOwner()
251 // maybe this should be changed at some point,
252 // e.g. when we got rid of Object::wireAttributes()
253 owner->addAttribute(this);
254 }
255
256 //! same as above, but only with a const getter member function
257 template <typename Owner>
DynAttribute_(Owner * owner,const std::string & name,T (Owner::* getter)()const)258 DynAttribute_(Owner* owner, const std::string &name,
259 T (Owner::*getter)() const
260 )
261 : Attribute(name, false)
262 , getter_(std::bind(getter, owner))
263 {
264 hookable_ = false;
265 // the following will call Attribute::setOwner()
266 // maybe this should be changed at some point,
267 // e.g. when we got rid of Object::wireAttributes()
268 owner->addAttribute(this);
269 }
270
271 template <typename Owner>
DynAttribute_(Owner * owner,const std::string & name,T (Owner::* getter)(),std::string (Owner::* setter)(T))272 DynAttribute_(Owner* owner, const std::string &name,
273 T (Owner::*getter)(),
274 std::string (Owner::*setter)(T)
275 )
276 : Attribute(name, false)
277 , getter_(std::bind(getter, owner))
278 , setter_(std::bind(setter, owner, std::placeholders::_1))
279 {
280 hookable_ = false;
281 writable_ = true;
282 // the following will call Attribute::setOwner()
283 // maybe this should be changed at some point,
284 // e.g. when we got rid of Object::wireAttributes()
285 owner->addAttribute(this);
286 }
287
type()288 Type type() override { return Attribute_<T>::staticType(); }
289
changed()290 Signal_<T>& changed() override {
291 throw new std::logic_error(
292 "No change signalling on dynamic attributes.");
293 }
294
str()295 std::string str() override {
296 return Converter<T>::str(getter_());
297 }
298
change(const std::string & payload_str)299 std::string change(const std::string &payload_str) override {
300 if (!writable()) {
301 return "attribute is read-only";
302 }
303 try {
304 // TODO: we _could_ use getter() here to allow relative changes.
305 T new_payload = Converter<T>::parse(payload_str); // throws
306 return setter_(new_payload);
307 } catch (std::invalid_argument const& e) {
308 return std::string("invalid argument: ") + e.what();
309 } catch (std::out_of_range const& e) {
310 return std::string("out of range: ") + e.what();
311 }
312 }
313
314 private:
315 std::function<T()> getter_;
316 std::function<std::string(T)> setter_;
317 };
318
319 #endif // ATTRIBUTE__H
320
321