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