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