Files
tdesktop/cmake/external/glib/cppgir/override/gtk/gtk_extra_def.hpp
allhaileris afb81b8278
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
init
2026-02-16 15:50:16 +03:00

413 lines
13 KiB
C++

#ifndef _GI_GTK_GTK_EXTRA_DEF_HPP_
#define _GI_GTK_GTK_EXTRA_DEF_HPP_
#include <map>
#include <string>
namespace gi
{
namespace repository
{
namespace Gtk
{
namespace base
{
#ifdef _GI_GTK_LIST_STORE_EXTRA_DEF_HPP_
// deprecated (as of about 4.9.1)
// so only define implementation here if declaration has been included
template<typename... Args>
Gtk::ListStore
ListStoreExtra::new_type_() noexcept
{
GType columns[] = {traits::gtype<Args>::get_type()...};
return ListStoreBase::new_(G_N_ELEMENTS(columns), columns);
};
#endif
} // namespace base
// class generated parts are needed
#if !GTK_CHECK_VERSION(4, 0, 0) || GI_CLASS_IMPL
namespace impl
{
// gtk widget template helper
class WidgetTemplateHelper
{
public:
enum class ConnectObject { NONE, TAIL, HEAD };
protected:
using ConnectData =
std::map<GType, std::map<std::string, gi::repository::GObject::Closure>>;
// inner namespace
class Internal
{
template<typename TUPLE>
struct tuple_strip_last;
template<typename... T>
struct tuple_strip_last<std::tuple<T...>>
{
protected:
using Tuple = std::tuple<T...>;
template<typename W>
struct inner;
template<std::size_t... Index>
struct inner<std::index_sequence<Index...>>
{
using type = std::tuple<
typename std::tuple_element<Index, std::tuple<T...>>::type...>;
};
public:
using type =
typename inner<std::make_index_sequence<sizeof...(T) - 1>>::type;
};
public:
struct ProxyClosure
{
GClosure base;
// actual target closure
GObject::Closure actual;
// duplicated from actual, holds ownership
GCallback cb;
gpointer user_data;
// perhaps through a thunk call helper
GObject::Closure thunk;
// no ref, guarded by watch
::GObject *object;
gboolean swapped;
};
// FUNCTOR represents C helper that transforms to Cpp call
// so it has C-types of FUNCTOR follows by functor user_data
template<ConnectObject CONNECT, typename FUNCTOR>
struct make_thunk : public std::false_type
{
struct call_type
{
// call should accept (signal emit object, signal args, proxy user_data)
// and transform this to a call in FUNCTOR form
// (suitably adding extra tail object and optionally swapping)
static void call() {}
};
};
// essentially SWAPPED case
template<typename R, typename FirstArg, typename... Args>
struct make_thunk<ConnectObject::HEAD, R (*)(FirstArg, Args...)>
{
// ... so FirstArg is extra object
static_assert(std::is_pointer<FirstArg>::value, "");
// remove user data
using ArgsType = typename tuple_strip_last<std::tuple<Args...>>::type;
// signal emit object
using ObjectArgType =
typename std::tuple_element<sizeof...(Args) - 2, ArgsType>::type;
static_assert(std::is_pointer<ObjectArgType>::value, "");
using SignalArgsType = typename tuple_strip_last<ArgsType>::type;
template<typename T>
struct thunk;
template<typename... TArgs>
struct thunk<std::tuple<TArgs...>>
{
// transform to call to
// (extra object, signal args, signal emit object, functor user_data)
static R call(ObjectArgType arg, TArgs... args, gpointer user_data)
{
auto tdata = (ProxyClosure *)(user_data);
typedef R (*func_type)(
::GObject *, TArgs..., ObjectArgType, gpointer);
return ((func_type)(tdata->cb))(
tdata->object, args..., arg, tdata->user_data);
}
};
using call_type = thunk<SignalArgsType>;
};
// essentially non-SWAPPED case
template<typename R, typename... Args>
struct make_thunk<ConnectObject::TAIL, R (*)(Args...)>
{
// remove user data
using AllArgsType = typename tuple_strip_last<std::tuple<Args...>>::type;
// extra object
using ObjectArgType =
typename std::tuple_element<sizeof...(Args) - 2, AllArgsType>::type;
static_assert(std::is_pointer<ObjectArgType>::value, "");
template<typename T>
struct thunk;
template<typename... TArgs>
struct thunk<std::tuple<TArgs...>>
{
// transform to call to
// (signal emit object, signal args, extra object, functor user_data)
static R call(TArgs... args, gpointer user_data)
{
auto tdata = (ProxyClosure *)(user_data);
typedef R (*func_type)(TArgs..., ::GObject *, gpointer);
return ((func_type)(tdata->cb))(
args..., tdata->object, tdata->user_data);
}
};
using SignalArgsType = typename tuple_strip_last<AllArgsType>::type;
using call_type = thunk<SignalArgsType>;
};
static void proxy_marshal(GClosure *closure, GValue *return_value,
guint n_param_values, const GValue *param_values,
gpointer invocation_hint, gpointer marshal_data)
{
// should only be used internally in gclosure for class closure cases
g_return_if_fail(marshal_data == NULL);
// call actual target if one has been provided
auto pclosure = (ProxyClosure *)(closure);
if (pclosure->actual) {
// might have to go through thunk if extra object
g_assert(!!pclosure->thunk == !!pclosure->object);
auto tclosure = pclosure->thunk ? pclosure->thunk.gobj_()
: pclosure->actual.gobj_();
g_closure_invoke(tclosure, return_value, n_param_values, param_values,
invocation_hint);
}
}
static void proxy_finalize(gpointer, GClosure *closure)
{
auto pclosure = (ProxyClosure *)(closure);
pclosure->actual = nullptr;
pclosure->thunk = nullptr;
}
static GObject::Closure make_proxy_closure(
::GObject *object = nullptr, bool swapped = false)
{
auto closure = g_closure_new_simple(sizeof(ProxyClosure), nullptr);
g_closure_set_marshal(closure, proxy_marshal);
g_closure_sink(g_closure_ref(closure));
g_closure_add_finalize_notifier(closure, nullptr, proxy_finalize);
// extra tracking
auto pclosure = (ProxyClosure *)closure;
if (object) {
g_object_watch_closure(object, closure);
pclosure->object = object;
// only relevant if object
pclosure->swapped = swapped;
}
return gi::wrap(closure, gi::transfer_full);
}
// work-around missing inline support
static ConnectData *&get_connect_data()
{
thread_local ConnectData *connect_data;
return connect_data;
}
static GQuark get_template_quark()
{
static const char *KEY = "GIOBJECT_TEMPLATE";
static GQuark q = g_quark_from_static_string(KEY);
return q;
}
static void builder_connect_function(GtkBuilder *builder, ::GObject *object,
const gchar *signal_name, const gchar *handler_name,
::GObject *connect_object, GConnectFlags flags, gpointer user_data)
{
(void)builder;
(void)user_data;
auto closure = Internal::make_proxy_closure(
connect_object, flags & G_CONNECT_SWAPPED);
g_signal_connect_closure(
object, signal_name, closure.gobj_(), flags & G_CONNECT_AFTER);
auto connect_data = get_connect_data();
g_assert(connect_data);
if (connect_data) {
(*connect_data)[G_OBJECT_TYPE(object)][handler_name] = closure;
}
}
};
#if GTK_CHECK_VERSION(4, 0, 0)
class BuilderScope : public Gtk::impl::BuilderCScopeImpl
{
public:
BuilderScope() : Gtk::impl::BuilderCScopeImpl(this) {}
GObject::Closure create_closure_(Gtk::Builder builder,
const gi::cstring_v function_name, Gtk::BuilderClosureFlags flags,
GObject::Object object, GLib::Error *_error) override
{
(void)_error;
// current object is the template'd one being init'ed
// a direct call is used as a wrapped call might fiddle with refs
// while the object is still being init'ed, which does not end well
auto current = gtk_builder_get_current_object(builder.gobj_());
auto connect_data = current ? (ConnectData *)g_object_get_qdata(
current, Internal::get_template_quark())
: nullptr;
g_assert(connect_data);
if (connect_data) {
auto wrapped = Internal::make_proxy_closure(
object.gobj_(), (flags & Gtk::BuilderClosureFlags::SWAPPED_) ==
Gtk::BuilderClosureFlags::SWAPPED_);
(*connect_data)[G_OBJECT_TYPE(current)][function_name] = wrapped;
return wrapped;
}
return nullptr;
}
};
#endif
// owned by qdata
ConnectData *connect_data_;
public:
WidgetTemplateHelper(gi::repository::GObject::Object object)
{
if (object) {
connect_data_ = (ConnectData *)g_object_get_qdata(
object.gobj_(), Internal::get_template_quark());
g_assert(connect_data_);
}
}
template<GType (*typefunc)() = nullptr>
static void custom_class_init(gpointer klass, gpointer)
{
G_OBJECT_CLASS(klass)->dispose = custom_dispose<typefunc>;
#if GTK_CHECK_VERSION(4, 0, 0)
auto builder = gi::make_ref<BuilderScope>();
gtk_widget_class_set_template_scope(
GTK_WIDGET_CLASS(klass), GTK_BUILDER_SCOPE(builder->gobj_()));
#else
gtk_widget_class_set_connect_func(GTK_WIDGET_CLASS(klass),
Internal::builder_connect_function, nullptr, nullptr);
#endif
}
static void custom_init(GtkWidget *instance, gpointer)
{
// avoid namespace interference
using GObject = ::GObject;
auto tq = Internal::get_template_quark();
// prepare storage
// in case of multi-level subclassing, it is used for all
ConnectData *cdp;
if (!(cdp = (ConnectData *)g_object_get_qdata(G_OBJECT(instance), tq))) {
auto deleter = [](gpointer d) { delete (ConnectData *)d; };
auto cd = std::make_unique<ConnectData>();
cdp = cd.get();
g_object_set_qdata_full(G_OBJECT(instance), tq, cd.release(), deleter);
}
#if GTK_CHECK_VERSION(4, 0, 0)
(void)cdp;
gtk_widget_init_template(GTK_WIDGET(instance));
#else
// unfortunate API (which does not provide access to instance)
// make ConnectData instance active on this thread
Internal::get_connect_data() = cdp;
gtk_widget_init_template(GTK_WIDGET(instance));
// so we got all that during above _init call
Internal::get_connect_data() = nullptr;
#endif
}
template<GType (*typefunc)()>
static void custom_dispose(::GObject *gobject)
{
// this intermediate step avoids a -Waddress warning on recent gcc
auto tf = typefunc;
auto gtype = tf ? typefunc() : G_OBJECT_TYPE(gobject);
#if GTK_CHECK_VERSION(4, 8, 0)
gtk_widget_dispose_template(GTK_WIDGET(gobject), gtype);
#endif
// need to know our place in the type chain
// (in C, this is usually handled by the parent_class variable)
// fallback to the leaf type if all else fails, but that too may fail
G_OBJECT_CLASS(g_type_class_peek(g_type_parent(gtype)))->dispose(gobject);
}
template<typename F, ConnectObject c = ConnectObject::NONE, typename Functor>
bool set_handler(gi::cstring_v handler_name, Functor &&f, GType gtype = 0)
{
auto _cd = (ConnectData *)connect_data_;
if (!_cd) {
g_assert_not_reached();
return false;
}
auto cd = *_cd;
// find name in collected data
Internal::ProxyClosure *proxy{};
for (auto &e : cd) {
if (gtype != 0 && gtype != e.first)
continue;
auto it = e.second.find(handler_name);
if (it != e.second.end()) {
// perhaps found one already
if (proxy) {
g_warning("ambiguous template entry for %s", handler_name.c_str());
return false;
}
proxy = (Internal::ProxyClosure *)(it->second.gobj_());
// continue search to check for conflicts in case of wildcard type
if (gtype != 0)
break;
}
}
if (!proxy)
return false;
// sanity checks; provided parameters match UI data
g_return_val_if_fail(!!proxy->object == (c != ConnectObject::NONE), FALSE);
g_return_val_if_fail(
!proxy->object || proxy->swapped == (c == ConnectObject::HEAD), FALSE);
// go go
// standard closure, also owns functor data
auto w =
new gi::detail::transform_signal_wrapper<F>(std::forward<Functor>(f));
auto closure = g_cclosure_new(
(GCallback)&w->wrapper, w, (GClosureNotify)(GCallback)&w->destroy);
g_closure_sink(g_closure_ref(closure));
g_closure_set_marshal(closure, g_cclosure_marshal_generic);
proxy->cb = (GCallback)&w->wrapper;
proxy->user_data = w;
proxy->actual = gi::wrap(closure, gi::transfer_full);
proxy->thunk = nullptr;
// optional thunk closure to invoke helper thunk to handle object
if (proxy->object) {
auto cb_thunk = (GCallback)&Internal::make_thunk<c,
decltype(&w->wrapper)>::call_type::call;
auto thunk = g_cclosure_new(cb_thunk, proxy, nullptr);
g_closure_set_marshal(thunk, g_cclosure_marshal_generic);
g_closure_sink(g_closure_ref(thunk));
proxy->thunk = gi::wrap(thunk, gi::transfer_full);
}
return true;
}
};
} // namespace impl
#endif
} // namespace Gtk
} // namespace repository
} // namespace gi
#endif