File: gtk_extra_def.hpp

package info (click to toggle)
cppgir 2.0%2Bgit20250629.2a7d9ce-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,220 kB
  • sloc: cpp: 16,451; ansic: 355; python: 86; makefile: 13; sh: 9
file content (412 lines) | stat: -rw-r--r-- 13,477 bytes parent folder | download
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
402
403
404
405
406
407
408
409
410
411
412
#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