| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679 |
- #ifndef GI_OBJECT_HPP
- #define GI_OBJECT_HPP
- #include "callback.hpp"
- #include "container.hpp"
- #include "exception.hpp"
- #include "objectbase.hpp"
- #include "paramspec.hpp"
- #include "value.hpp"
- namespace gi
- {
- namespace detail
- {
- // helper
- // signal argument connect/emit helper;
- // turn (C++) argument into a GType or GValue
- // most arguments are inputs (with specific GType),
- // but some arguments are used as output with G_TYPE_POINTER (e.g. int*)
- // which are mapped to (e.g.) int* or int& in C++ signature
- template<typename Arg, bool DECAY>
- struct signal_arg
- {
- static GType get_type() { return traits::gtype<Arg>::get_type(); }
- static detail::Value make(Arg arg)
- {
- return detail::Value(std::forward<Arg>(arg));
- }
- };
- // re-route e.g. const std::string& cases
- template<typename Arg>
- struct signal_arg<const Arg &, false> : public signal_arg<const Arg &, true>
- {};
- template<typename Arg>
- struct signal_arg<Arg &, false>
- {
- static GType get_type() { return G_TYPE_POINTER; }
- static detail::Value make(Arg &arg)
- {
- return signal_arg<Arg *, false>::make(&arg);
- }
- };
- template<typename Arg>
- struct signal_arg<Arg *, false>
- {
- static GType get_type() { return G_TYPE_POINTER; }
- static detail::Value make(Arg *arg)
- {
- // (size of) wrapper argument should match wrappee
- static_assert(sizeof(typename traits::ctype<Arg>::type) == sizeof(Arg), "");
- // the above should suffice for proper handling
- // however, in these output cases, transfer should also be considered,
- // which is not (yet) available here
- // (but could be passed along similar to callback argument info)
- // so, restrict to plain cases for now
- static_assert(traits::is_plain<Arg>::value, "");
- return detail::Value(gpointer(arg));
- }
- };
- // returns -size if signed numeric, +size if unsigned numeric, otherwise 0
- inline int
- get_number_size_signed(GType type)
- {
- // note; these are generally lower (absolute) bounds
- // at least it works in the context where it is used below
- #define GI_HANDLE_TYPE_SWITCH(cpptype, g_type, factor) \
- case g_type: \
- return factor * int(sizeof(cpptype));
- switch (type) {
- GI_HANDLE_TYPE_SWITCH(gchar, G_TYPE_CHAR, -1)
- GI_HANDLE_TYPE_SWITCH(guchar, G_TYPE_UCHAR, 1)
- GI_HANDLE_TYPE_SWITCH(gint, G_TYPE_INT, -1)
- GI_HANDLE_TYPE_SWITCH(guint, G_TYPE_UINT, 1)
- GI_HANDLE_TYPE_SWITCH(glong, G_TYPE_LONG, -1)
- GI_HANDLE_TYPE_SWITCH(gulong, G_TYPE_ULONG, 1)
- GI_HANDLE_TYPE_SWITCH(gint64, G_TYPE_INT64, -1)
- GI_HANDLE_TYPE_SWITCH(guint64, G_TYPE_UINT64, 1)
- }
- #undef GI_HANDLE_TYPE_SWITCH
- return 0;
- }
- // glib type systems treats G_TYPE_INT64 as distinct from the other types
- // in practice, however, quite likely C gint64 == long
- inline bool
- compatible_type(GType expected, GType actual)
- {
- if (expected == G_TYPE_BOOLEAN)
- return std::abs(get_number_size_signed(actual)) == sizeof(gboolean);
- auto ssa_e = get_number_size_signed(expected);
- auto ssa_a = get_number_size_signed(actual);
- return ssa_e == ssa_a;
- }
- inline void
- check_signal_type(GType tp, const gi::cstring_v name, GType return_type,
- GType *param_types, guint n_params)
- {
- const char *errmsg("expected ");
- auto check_types = [tp, &name, &errmsg](const std::string &desc,
- GType expected, GType actual) {
- // normalize
- expected &= ~G_SIGNAL_TYPE_STATIC_SCOPE;
- actual &= ~G_SIGNAL_TYPE_STATIC_SCOPE;
- if (expected == actual || compatible_type(expected, actual) ||
- g_type_is_a(expected, actual))
- return;
- std::string msg = errmsg;
- msg += desc + " type ";
- msg += detail::make_string(g_type_name(expected)) + " != ";
- msg += detail::make_string(g_type_name(actual));
- detail::try_throw(invalid_signal_callback_error(tp, name, msg));
- };
- // determine signal (detail)
- guint id;
- GQuark detail;
- if (!g_signal_parse_name(name.c_str(), tp, &id, &detail, false) || (id == 0))
- detail::try_throw(unknown_signal_error(tp, name));
- // get signal info
- GSignalQuery query;
- g_signal_query(id, &query);
- // check
- if (n_params != query.n_params + 1) {
- auto msg = std::string(errmsg) + "argument count ";
- msg += std::to_string(query.n_params);
- msg += " != " + std::to_string(n_params);
- detail::try_throw(invalid_signal_callback_error(tp, name, msg));
- }
- check_types("return", query.return_type, return_type);
- check_types("instance", query.itype, param_types[0]);
- const std::string arg("argument ");
- for (guint i = 0; i < query.n_params; ++i)
- check_types(
- arg + std::to_string(i + 1), query.param_types[i], param_types[i + 1]);
- }
- template<typename G>
- struct signal_type;
- template<typename R, typename... Args>
- struct signal_type<R(Args...)>
- {
- static void check(GType tp, const gi::cstring_v name)
- {
- // capture type info and delegate
- const int argcount = sizeof...(Args);
- GType ti[] = {signal_arg<Args, false>::get_type()...};
- check_signal_type(tp, name, traits::gtype<R>::get_type(), ti, argcount);
- }
- };
- // like GParameter, but with extra Value trimming
- struct Parameter
- {
- const char *name;
- detail::Value value;
- };
- #ifdef GI_OBJECT_NEWV
- GI_DISABLE_DEPRECATED_WARN_BEGIN
- static_assert(sizeof(Parameter) == sizeof(GParameter), "");
- GI_DISABLE_DEPRECATED_WARN_END
- #endif
- inline void
- fill_parameters(Parameter *)
- {
- // no-op
- }
- template<typename Arg, typename... Args>
- inline void
- fill_parameters(Parameter *param, const char *name, Arg &&arg, Args &&...args)
- {
- param->name = name;
- param->value.init<typename std::remove_reference<Arg>::type>();
- set_value(¶m->value, std::forward<Arg>(arg));
- fill_parameters(param + 1, std::forward<Args>(args)...);
- }
- } // namespace detail
- #if GLIB_CHECK_VERSION(2, 54, 0)
- #define GI_GOBJECT_PROPERTY_VALUE 1
- #endif
- namespace repository
- {
- /* if you have arrived here due to an ambiguous GObject reference
- * (both the C typedef GObject and this namespace)
- * then that can be worked-around by:
- * + using _GObject (struct name instead)
- * + adjust 'using namespace' style imports e.g. alias
- * namespace GObject_ = gi::GObject;
- * or simply do not mention GObject at all and simply use the wrappers ;-)
- */
- namespace GObject
- {
- typedef std::vector<detail::Parameter> construct_params;
- template<typename... Args>
- construct_params
- make_construct_params(Args &&...args)
- {
- const int nparams = sizeof...(Args) / 2;
- construct_params parameters;
- parameters.resize(nparams);
- detail::fill_parameters(parameters.data(), std::forward<Args>(args)...);
- return parameters;
- }
- class Object : public detail::ObjectBase
- {
- typedef Object self;
- typedef detail::ObjectBase super_type;
- public:
- typedef ::GObject BaseObjectType;
- Object(std::nullptr_t = nullptr) : super_type() {}
- BaseObjectType *gobj_() { return (BaseObjectType *)super_type::gobj_(); }
- const BaseObjectType *gobj_() const
- {
- return (const BaseObjectType *)super_type::gobj_();
- }
- BaseObjectType *gobj_copy_() const
- {
- return (BaseObjectType *)super_type::gobj_copy_();
- }
- // class type
- static GType get_type_() { return G_TYPE_OBJECT; }
- // instance type
- GType gobj_type_() const { return G_OBJECT_TYPE(gobj_()); }
- // type-erased generic object creation
- // transfer full return
- static gpointer new_(GType gtype, const construct_params ¶ms)
- {
- #ifdef GI_OBJECT_NEWV
- GI_DISABLE_DEPRECATED_WARN_BEGIN
- auto result =
- g_object_newv(gtype, params.size(), (GParameter *)params.data());
- GI_DISABLE_DEPRECATED_WARN_END
- #else
- std::vector<const char *> names;
- std::vector<GValue> values;
- names.reserve(params.size());
- values.reserve(params.size());
- // ownership remains in params
- for (auto &&p : params) {
- names.push_back(p.name);
- values.emplace_back(p.value);
- }
- auto result = g_object_new_with_properties(
- gtype, params.size(), names.data(), values.data());
- #endif
- // GIR says transfer full, but let's be careful and really make it so
- // if likely still floating, then we assume ownership
- // but if it is no longer, then it has already been stolen (e.g.
- // GtkWindow), and we need to add one here
- if (g_type_is_a(gtype, G_TYPE_INITIALLY_UNOWNED))
- g_object_ref_sink(result);
- return result;
- }
- // type-based generic object creation
- template<typename TYPE, typename... Args>
- static TYPE new_(Args &&...args)
- {
- auto parameters = make_construct_params(std::forward<Args>(args)...);
- typename TYPE::BaseObjectType *result =
- (typename TYPE::BaseObjectType *)new_(TYPE::get_type_(), parameters);
- return gi::wrap(result, transfer_full);
- }
- // property stuff
- // generic type unsafe
- template<typename V>
- self &set_property(ParamSpec _pspec, V &&val)
- {
- // additional checks
- // allows for basic conversion between arithmetic types
- // without worrying about those details
- auto pspec = _pspec.gobj_();
- detail::Value v(std::forward<V>(val));
- detail::Value dest;
- GValue *p = &v;
- if (G_VALUE_TYPE(&v) != pspec->value_type) {
- g_value_init(&dest, pspec->value_type);
- if (!g_value_transform(&v, &dest))
- detail::try_throw(
- detail::transform_error(pspec->value_type, pspec->name));
- p = &dest;
- }
- g_object_set_property(gobj_(), pspec->name, p);
- return *this;
- }
- template<typename V>
- self &set_property(const gi::cstring_v propname, V &&val)
- {
- return set_property<V>(find_property(propname, true), std::forward<V>(val));
- }
- template<typename V>
- self &set_properties(const gi::cstring_v propname, V &&val)
- {
- return set_property<V>(propname, std::forward<V>(val));
- }
- // set a number of props
- template<typename V, typename... Args>
- self &set_properties(const gi::cstring_v propname, V &&val, Args... args)
- {
- g_object_freeze_notify(gobj_());
- #if GI_CONFIG_EXCEPTIONS
- try {
- #endif
- set_property(propname, std::forward<V>(val));
- set_properties(std::forward<Args>(args)...);
- #if GI_CONFIG_EXCEPTIONS
- } catch (...) {
- g_object_thaw_notify(gobj_());
- throw;
- }
- #endif
- g_object_thaw_notify(gobj_());
- return *this;
- }
- #ifdef GI_GOBJECT_PROPERTY_VALUE
- self &set_property(const gi::cstring_v propname, Value val)
- {
- g_object_set_property(gobj_(), propname.c_str(), val.gobj_());
- return *this;
- }
- #endif
- template<typename V>
- V get_property(const char *propname) const
- {
- // this would return a ref to what is owned by stack-local v below
- static_assert(!traits::is_reftype<V>::value, "dangling ref");
- detail::Value v;
- v.init<V>();
- // the _get_ already tries to transform
- // also close enough to const
- g_object_get_property(const_cast<::GObject *>(gobj_()), propname, &v);
- return detail::get_value<V>(&v);
- }
- template<typename V>
- V get_property(const gi::cstring_v propname) const
- {
- return get_property<V>(propname.c_str());
- }
- #ifdef GI_GOBJECT_PROPERTY_VALUE
- Value get_property(const gi::cstring_v propname) const
- {
- Value result;
- const gchar *name = propname.c_str();
- GValue *val = result.gobj_();
- g_object_getv(const_cast<::GObject *>(gobj_()), 1, &name, val);
- return result;
- }
- #endif
- ParamSpec find_property(
- const gi::cstring_v propname, bool _throw = false) const
- {
- GParamSpec *spec;
- if (g_type_is_a(gobj_type_(), G_TYPE_INTERFACE)) {
- // interface should be loaded if we have an instance here
- auto vtable = g_type_default_interface_peek(gobj_type_());
- spec = g_object_interface_find_property(vtable, propname.c_str());
- } else {
- spec = g_object_class_find_property(
- G_OBJECT_GET_CLASS(gobj_()), propname.c_str());
- }
- if (_throw && !spec)
- detail::try_throw(
- detail::unknown_property_error(gobj_type_(), propname.c_str()));
- return gi::wrap(spec, transfer_none);
- }
- gi::Collection<gi::DSpan, GParamSpec *, gi::transfer_container_t>
- list_properties() const
- {
- GParamSpec **specs;
- guint nspecs = 0;
- if (g_type_is_a(gobj_type_(), G_TYPE_INTERFACE)) {
- // interface should be loaded if we have an instance here
- auto vtable = g_type_default_interface_peek(gobj_type_());
- specs = g_object_interface_list_properties(vtable, &nspecs);
- } else {
- specs =
- g_object_class_list_properties(G_OBJECT_GET_CLASS(gobj_()), &nspecs);
- }
- return wrap_to<
- gi::Collection<gi::DSpan, GParamSpec *, gi::transfer_container_t>>(
- specs, nspecs, transfer_container);
- }
- // signal stuff
- private:
- template<typename F, typename Functor>
- gulong connect_data(
- const gi::cstring_v signal, Functor &&f, GConnectFlags flags)
- {
- // runtime signature check
- detail::signal_type<F>::check(gobj_type_(), signal);
- auto w = new detail::transform_signal_wrapper<F>(std::forward<Functor>(f));
- // mind gcc's -Wcast-function-type
- return g_signal_connect_data(gobj_(), signal.c_str(),
- (GCallback)&w->wrapper, w, (GClosureNotify)(GCallback)&w->destroy,
- flags);
- }
- public:
- template<typename F, typename Functor>
- gulong connect(const gi::cstring_v signal, Functor &&f)
- {
- return connect_data<F, Functor>(
- signal, std::forward<Functor>(f), (GConnectFlags)0);
- }
- template<typename F, typename Functor>
- gulong connect_after(const gi::cstring_v signal, Functor &&f)
- {
- return connect_data<F, Functor>(
- signal, std::forward<Functor>(f), G_CONNECT_AFTER);
- }
- // TODO the object variants ??
- // in case of unsupported signal signature
- // connect using a plain C signature without check/transform (wrap/unwrap)
- template<typename F, typename Functor>
- gulong connect_unchecked(
- const gi::cstring_v signal, Functor &&f, GConnectFlags flags = {})
- {
- auto w = new detail::callback_wrapper<F>(std::forward<Functor>(f));
- // mind gcc's -Wcast-function-type
- return g_signal_connect_data(gobj_(), signal.c_str(),
- (GCallback)&w->wrapper, w, (GClosureNotify)(GCallback)&w->destroy,
- flags);
- }
- void disconnect(gulong id) { g_signal_handler_disconnect(gobj_(), id); }
- // Args... may be explicitly specified or deduced
- // if deduced; arrange to decay/strip reference below
- // if not deduced; may need to considere specified type as-is
- template<typename R, bool DECAY = true, typename... Args>
- R emit(const gi::cstring_v signal, Args &&...args)
- {
- // static constexpr bool DECAY = true;
- guint id;
- GQuark detail;
- if (!g_signal_parse_name(signal.c_str(), gobj_type_(), &id, &detail, true))
- detail::try_throw(std::out_of_range(std::string("unknown signal name: ") +
- detail::make_string(signal.c_str())));
- detail::Value values[] = {detail::Value(*this),
- detail::signal_arg<Args, DECAY>::make(std::forward<Args>(args))...};
- detail::Value retv;
- retv.init<R>();
- g_signal_emitv(values, id, detail, &retv);
- return detail::get_value<R>(&retv);
- }
- void handler_block(gulong handler_id)
- {
- g_signal_handler_block(gobj_(), handler_id);
- }
- void handler_unblock(gulong handler_id)
- {
- g_signal_handler_unblock(gobj_(), handler_id);
- }
- bool handler_is_connected(gulong handler_id)
- {
- return g_signal_handler_is_connected(gobj_(), handler_id);
- }
- void stop_emission(guint id, GQuark detail)
- {
- g_signal_stop_emission(gobj_(), id, detail);
- }
- void stop_emission_by_name(const gi::cstring_v signal)
- {
- g_signal_stop_emission_by_name(gobj_(), signal.c_str());
- }
- };
- } // namespace GObject
- template<>
- struct declare_cpptype_of<::GObject>
- {
- typedef repository::GObject::Object type;
- };
- namespace GLib
- {
- // predefined
- typedef detail::callback<void(), gi::transfer_none_t> DestroyNotify;
- } // namespace GLib
- } // namespace repository
- // type safe signal connection
- template<typename T, typename Base = repository::GObject::Object>
- class signal_proxy;
- template<typename R, typename Instance, typename... Args, typename Base>
- class signal_proxy<R(Instance, Args...), Base>
- {
- protected:
- typedef R(CppSig)(Instance, Args...);
- Base object_;
- gi::cstring name_;
- public:
- typedef CppSig function_type;
- typedef detail::connectable<function_type> slot_type;
- signal_proxy(Base owner, gi::cstring name)
- : object_(owner), name_(std::move(name))
- {}
- template<typename Functor>
- gulong connect(Functor &&f)
- {
- return object_.template connect<CppSig>(name_, std::forward<Functor>(f));
- }
- template<typename Functor>
- gulong connect_after(Functor &&f)
- {
- return object_.template connect_after<CppSig>(
- name_, std::forward<Functor>(f));
- }
- R emit(Args... args)
- {
- return object_.template emit<R, false, Args...>(
- name_, std::forward<Args>(args)...);
- }
- template<typename Functor>
- slot_type slot(Functor &&f)
- {
- return slot_type(std::forward<Functor>(f));
- }
- };
- // type safe property setting
- template<typename T, typename Base = repository::GObject::Object>
- class property_proxy
- {
- typedef property_proxy self;
- typedef repository::GObject::ParamSpec ParamSpec;
- protected:
- Base object_;
- ParamSpec pspec_;
- public:
- property_proxy(Base owner, ParamSpec pspec) : object_(owner), pspec_(pspec) {}
- property_proxy(Base owner, const gi::cstring_v name)
- : property_proxy(owner, owner.find_property(name, true))
- {}
- void set(T v) { object_.set_property(pspec_, std::move(v)); }
- self &operator=(T v)
- {
- set(v);
- return *this;
- }
- T get() const
- {
- return object_.template get_property<T>(pspec_.gobj_()->name);
- }
- ParamSpec param_spec() const { return pspec_; }
- signal_proxy<void(Base, ParamSpec)> signal_notify() const
- {
- return signal_proxy<void(Base, ParamSpec)>(
- object_, gi::cstring_v("notify::") + gi::cstring_v(pspec_.name_()));
- }
- };
- template<typename T, typename Base = repository::GObject::Object>
- class property_proxy_read : private property_proxy<T, Base>
- {
- typedef property_proxy<T, Base> super;
- public:
- using super::get;
- using super::property_proxy;
- };
- template<typename T, typename Base = repository::GObject::Object>
- class property_proxy_write : private property_proxy<T, Base>
- {
- typedef property_proxy<T, Base> super;
- public:
- using super::property_proxy;
- using super::set;
- using super::operator=;
- };
- // interface (ptr) is wrapped the same way,
- // as it is essentially a ptr to implementing object
- // TODO use other intermediate base ??
- using InterfaceBase = repository::GObject::Object;
- namespace repository
- {
- namespace GObject
- {
- // connection helpers
- namespace internal
- {
- class SignalConnection : public detail::connection_impl
- {
- public:
- SignalConnection(gulong id, detail::connection_status s, Object object)
- : connection_impl(id, s), object_(object)
- {}
- void disconnect() { object_.disconnect(id_); }
- private:
- Object object_;
- };
- } // namespace internal
- using SignalConnection = detail::connection<internal::SignalConnection>;
- using SignalScopedConnection = detail::scoped_connection<SignalConnection>;
- } // namespace GObject
- } // namespace repository
- // connection callback type
- template<typename G>
- using slot = detail::connectable<G>;
- template<typename G>
- inline repository::GObject::SignalConnection
- make_connection(
- gulong id, const gi::slot<G> &s, repository::GObject::Object object)
- {
- using repository::GObject::SignalConnection;
- return SignalConnection(id, s.connection(), object);
- }
- } // namespace gi
- #endif // GI_OBJECT_HPP
|