1 /* Copyright 2002 The gtkmm Development Team
2  *
3  * This library is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU Lesser General Public
5  * License as published by the Free Software Foundation; either
6  * version 2.1 of the License, or (at your option) any later version.
7  *
8  * This library is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * Lesser General Public License for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public
14  * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
15  */
16 
17 #include <glibmm/property.h>
18 
19 #include <glibmm/object.h>
20 #include <glibmm/class.h>
21 #include <cstddef>
22 #include <map>
23 
24 // Temporary hack till GLib gets fixed.
25 #undef G_STRLOC
26 #define G_STRLOC __FILE__ ":" G_STRINGIFY(__LINE__)
27 
28 namespace
29 {
30 // The task:
31 // ---------
32 // a) Autogenerate a property ID number for each custom property.  This is an
33 //    unsigned integer, which doesn't have to be assigned contiguously.  I.e.,
34 //    it can be everything but 0.
35 // b) If more than one object of the same class is instantiated, then of course
36 //    the already installed properties must be used.  That means, a property ID
37 //    must not be associated with a single Glib::Property<> instance.  Rather,
38 //    the ID has to be associated with the class somehow.
39 // c) With only a GObject pointer and a property ID (and perhaps GParamSpec*
40 //    if necessary), it must be possible to acquire a reference to the property
41 //    wrapper instance.
42 //
43 // The current solution:
44 // ---------------------
45 // a) Assign an ID to a Glib::PropertyBase by keeping track of the number of
46 //    properties that have been already installed. Since C++ always calls
47 //    the constructors of sub-objects in an object in the same order, we can
48 //    rely on the same ID being assigned to the same property.
49 // b) Store addresses to PropertyBase objects in a separate, per-object vector
50 //    and use the property ID as the index in that vector.
51 //
52 // Drawbacks:
53 // ----------
54 // a) An additional GQuark and a vector lookup need to be done to retrieve the
55 //    address of PropertyBase.
56 // b) In the given run of a program, all Glib::Property<> instances related to
57 //    a given Glib::Object must be constructed in the same order.
58 //
59 // Advantages:
60 // -----------
61 // a) Almost all conceivable use-cases are supported by this approach.
62 // b) It's comparatively efficient, and does not need a hash-table lookup.
63 
64 // Delete the interface property values when an object of a custom type is finalized.
65 void
destroy_notify_obj_iface_props(void * data)66 destroy_notify_obj_iface_props(void* data)
67 {
68   auto obj_iface_props = static_cast<Glib::Class::iface_properties_type*>(data);
69   if (obj_iface_props)
70   {
71     for (Glib::Class::iface_properties_type::size_type i = 0; i < obj_iface_props->size(); i++)
72     {
73       g_value_unset((*obj_iface_props)[i]);
74       g_free((*obj_iface_props)[i]);
75     }
76     delete obj_iface_props;
77   }
78 }
79 
80 struct custom_properties_type
81 {
82   // Pointers to the custom properties of custom types.
83   std::vector<Glib::PropertyBase*> prop_base_vector;
84 
85   // Property values, set by custom_set_property_callback() before a
86   // Glib::PropertyBase wrapper has been created. E.g. if the containing
87   // custom GObject has been created by GtkBuilder.
88   std::map<unsigned int, GValue*> prop_value_map;
89 };
90 
91 // The quark used for storing/getting the custom properties of custom types.
92 static const GQuark custom_properties_quark =
93   g_quark_from_string("gtkmm_CustomObject_custom_properties");
94 
95 // Delete the custom properties data when an object of a custom type is finalized.
destroy_notify_obj_custom_props(void * data)96 void destroy_notify_obj_custom_props(void* data)
97 {
98   auto obj_custom_props = static_cast<custom_properties_type*>(data);
99   // prop_base_vector does not own the objects pointed to.
100   // prop_value_map owns the objects pointed to.
101   const auto map_end = obj_custom_props->prop_value_map.end();
102   for (auto it = obj_custom_props->prop_value_map.begin(); it != map_end; ++it)
103   {
104     g_value_unset(it->second);
105     g_free(it->second);
106   }
107   delete obj_custom_props;
108 }
109 
110 custom_properties_type*
get_obj_custom_props(GObject * obj)111 get_obj_custom_props(GObject* obj)
112 {
113   auto obj_custom_props =
114     static_cast<custom_properties_type*>(g_object_get_qdata(obj, custom_properties_quark));
115   if (!obj_custom_props)
116   {
117     obj_custom_props = new custom_properties_type();
118     g_object_set_qdata_full(
119       obj, custom_properties_quark, obj_custom_props, destroy_notify_obj_custom_props);
120   }
121   return obj_custom_props;
122 }
123 
124 } // anonymous namespace
125 
126 namespace Glib
127 {
128 
129 void
custom_get_property_callback(GObject * object,unsigned int property_id,GValue * value,GParamSpec * param_spec)130 custom_get_property_callback(
131   GObject* object, unsigned int property_id, GValue* value, GParamSpec* param_spec)
132 {
133   // If the id is zero there is no property to get.
134   g_return_if_fail(property_id != 0);
135 
136   GType custom_type = G_OBJECT_TYPE(object);
137 
138   auto iface_props = static_cast<Class::iface_properties_type*>(
139     g_type_get_qdata(custom_type, Class::iface_properties_quark));
140 
141   Class::iface_properties_type::size_type iface_props_size = 0;
142 
143   if (iface_props)
144     iface_props_size = iface_props->size();
145 
146   if (property_id <= iface_props_size)
147   {
148     // Get the object's property value if there is one, else the class's default value.
149     auto obj_iface_props = static_cast<Class::iface_properties_type*>(
150       g_object_get_qdata(object, Class::iface_properties_quark));
151     if (obj_iface_props)
152       g_value_copy((*obj_iface_props)[property_id - 1], value);
153     else
154       g_value_copy((*iface_props)[property_id - 1], value);
155   }
156   else
157   {
158     auto obj_custom_props = get_obj_custom_props(object);
159     const unsigned index = property_id - iface_props_size - 1;
160 
161     if (Glib::ObjectBase* const wrapper = Glib::ObjectBase::_get_current_wrapper(object))
162     {
163       if (obj_custom_props && index < obj_custom_props->prop_base_vector.size())
164       {
165         const Glib::PropertyBase* prop_base = (obj_custom_props->prop_base_vector)[index];
166         if (prop_base->object_ == wrapper && prop_base->param_spec_ == param_spec)
167           g_value_copy(prop_base->value_.gobj(), value);
168         else
169           G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, param_spec);
170       }
171       else
172         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, param_spec);
173     }
174     else
175     {
176       // No C++ wrapper exists. Check if there is a value in obj_custom_props->prop_value_map.
177       auto it = obj_custom_props->prop_value_map.find(index);
178       if (it != obj_custom_props->prop_value_map.end())
179         g_value_copy(it->second, value);
180       else
181         // else return the property's default value.
182         g_value_copy(g_param_spec_get_default_value(param_spec), value);
183     }
184   }
185 }
186 
187 void
custom_set_property_callback(GObject * object,unsigned int property_id,const GValue * value,GParamSpec * param_spec)188 custom_set_property_callback(
189   GObject* object, unsigned int property_id, const GValue* value, GParamSpec* param_spec)
190 {
191   // If the id is zero there is no property to set.
192   g_return_if_fail(property_id != 0);
193 
194   GType custom_type = G_OBJECT_TYPE(object);
195 
196   auto iface_props = static_cast<Class::iface_properties_type*>(
197     g_type_get_qdata(custom_type, Class::iface_properties_quark));
198 
199   Class::iface_properties_type::size_type iface_props_size = 0;
200 
201   if (iface_props)
202     iface_props_size = iface_props->size();
203 
204   if (property_id <= iface_props_size)
205   {
206     // If the object does not have interface property values,
207     // copy the class's default values to the object.
208     auto obj_iface_props = static_cast<Class::iface_properties_type*>(
209       g_object_get_qdata(object, Class::iface_properties_quark));
210     if (!obj_iface_props)
211     {
212       obj_iface_props = new Class::iface_properties_type();
213       g_object_set_qdata_full(
214         object, Class::iface_properties_quark, obj_iface_props, destroy_notify_obj_iface_props);
215       for (Class::iface_properties_type::size_type p = 0; p < iface_props_size; ++p)
216       {
217         GValue* g_value = g_new0(GValue, 1);
218         g_value_init(g_value, G_VALUE_TYPE((*iface_props)[p]));
219         g_value_copy((*iface_props)[p], g_value);
220         obj_iface_props->emplace_back(g_value);
221       }
222     }
223 
224     g_value_copy(value, (*obj_iface_props)[property_id - 1]);
225     g_object_notify_by_pspec(object, param_spec);
226   }
227   else
228   {
229     auto obj_custom_props = get_obj_custom_props(object);
230     const unsigned index = property_id - iface_props_size - 1;
231 
232     if (Glib::ObjectBase* const wrapper = Glib::ObjectBase::_get_current_wrapper(object))
233     {
234       if (obj_custom_props && index < obj_custom_props->prop_base_vector.size())
235       {
236         Glib::PropertyBase* prop_base = (obj_custom_props->prop_base_vector)[index];
237         if (prop_base->object_ == wrapper && prop_base->param_spec_ == param_spec)
238         {
239           g_value_copy(value, prop_base->value_.gobj());
240           g_object_notify_by_pspec(object, param_spec);
241         }
242         else
243           G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, param_spec);
244       }
245       else
246         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, param_spec);
247     }
248     else
249     {
250       // No C++ wrapper exists. Store the value in obj_custom_props->prop_value_map.
251       auto it = obj_custom_props->prop_value_map.find(index);
252       if (it != obj_custom_props->prop_value_map.end())
253         g_value_copy(value, it->second);
254       else
255       {
256         GValue* g_value = g_new0(GValue, 1);
257         g_value_init(g_value, G_VALUE_TYPE(value));
258         g_value_copy(value, g_value);
259         obj_custom_props->prop_value_map[index] = g_value;
260       }
261       g_object_notify_by_pspec(object, param_spec);
262     }
263   }
264 }
265 
266 /**** Glib::PropertyBase ***************************************************/
267 
PropertyBase(Glib::Object & object,GType value_type)268 PropertyBase::PropertyBase(Glib::Object& object, GType value_type)
269 : object_(&object), value_(), param_spec_(nullptr)
270 {
271   value_.init(value_type);
272 }
273 
~PropertyBase()274 PropertyBase::~PropertyBase() noexcept
275 {
276   if (param_spec_)
277     g_param_spec_unref(param_spec_);
278 }
279 
280 bool
lookup_property(const Glib::ustring & name)281 PropertyBase::lookup_property(const Glib::ustring& name)
282 {
283   g_assert(param_spec_ == nullptr);
284 
285   param_spec_ = g_object_class_find_property(G_OBJECT_GET_CLASS(object_->gobj()), name.c_str());
286 
287   if (param_spec_)
288   {
289     // This property has already been installed, when another instance
290     // of the object_ class was constructed.
291     g_assert(G_PARAM_SPEC_VALUE_TYPE(param_spec_) == G_VALUE_TYPE(value_.gobj()));
292     g_param_spec_ref(param_spec_);
293 
294     auto obj_custom_props = get_obj_custom_props(object_->gobj());
295     const unsigned int pos_in_obj_custom_props = obj_custom_props->prop_base_vector.size();
296     obj_custom_props->prop_base_vector.emplace_back(this);
297 
298     // If a value has been set by a call to custom_set_property_callback()
299     // before this Glib::PropertyBase wrapper was creared, copy that value
300     // to value_.
301     auto it = obj_custom_props->prop_value_map.find(pos_in_obj_custom_props);
302     if (it != obj_custom_props->prop_value_map.end())
303       g_value_copy(it->second, value_.gobj());
304   }
305 
306   return (param_spec_ != nullptr);
307 }
308 
309 void
install_property(GParamSpec * param_spec)310 PropertyBase::install_property(GParamSpec* param_spec)
311 {
312   g_return_if_fail(param_spec != nullptr);
313 
314   // Ensure that there would not be id clashes with possible existing
315   // properties overridden from implemented interfaces if dealing with a custom
316   // type by offsetting the generated id with the number of already existing
317   // properties.
318 
319   GType gtype = G_OBJECT_TYPE(object_->gobj());
320   auto iface_props = static_cast<Class::iface_properties_type*>(
321     g_type_get_qdata(gtype, Class::iface_properties_quark));
322 
323   Class::iface_properties_type::size_type iface_props_size = 0;
324   if (iface_props)
325     iface_props_size = iface_props->size();
326 
327   auto obj_custom_props = get_obj_custom_props(object_->gobj());
328 
329   const unsigned int pos_in_obj_custom_props = obj_custom_props->prop_base_vector.size();
330   obj_custom_props->prop_base_vector.emplace_back(this);
331 
332   // We need to offset by 1 as zero is an invalid property id.
333   const unsigned int property_id = pos_in_obj_custom_props + iface_props_size + 1;
334 
335   g_object_class_install_property(G_OBJECT_GET_CLASS(object_->gobj()), property_id, param_spec);
336 
337   param_spec_ = param_spec;
338   g_param_spec_ref(param_spec_);
339 }
340 
341 const char*
get_name_internal() const342 PropertyBase::get_name_internal() const
343 {
344   const char* const name = g_param_spec_get_name(param_spec_);
345   g_return_val_if_fail(name != nullptr, "");
346   return name;
347 }
348 
349 Glib::ustring
get_name() const350 PropertyBase::get_name() const
351 {
352   return Glib::ustring(get_name_internal());
353 }
354 
355 Glib::ustring
get_nick() const356 PropertyBase::get_nick() const
357 {
358   return Glib::convert_const_gchar_ptr_to_ustring(
359     g_param_spec_get_nick(param_spec_));
360 }
361 
362 Glib::ustring
get_blurb() const363 PropertyBase::get_blurb() const
364 {
365   return Glib::convert_const_gchar_ptr_to_ustring(
366     g_param_spec_get_blurb(param_spec_));
367 }
368 
369 void
notify()370 PropertyBase::notify()
371 {
372   g_object_notify_by_pspec(object_->gobj(), param_spec_);
373 }
374 
375 } // namespace Glib
376