1/*
2 * Copyright © 2019 Benjamin Otte
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * Authors: Benjamin Otte <otte@gnome.org>
18 */
19
20#include "config.h"
21
22#include "gtkbuilderscopeprivate.h"
23
24#include "gtkbuilder.h"
25#include "gtktestutils.h"
26
27/**
28 * GtkBuilderScope:
29 *
30 * `GtkBuilderScope` is an interface to provide language binding support
31 * to `GtkBuilder`.
32 *
33 * The goal of `GtkBuilderScope` is to look up programming-language-specific
34 * values for strings that are given in a `GtkBuilder` UI file.
35 *
36 * The primary intended audience is bindings that want to provide deeper
37 * integration of `GtkBuilder` into the language.
38 *
39 * A `GtkBuilderScope` instance may be used with multiple `GtkBuilder` objects,
40 * even at once.
41 *
42 * By default, GTK will use its own implementation of `GtkBuilderScope`
43 * for the C language which can be created via [ctor@Gtk.BuilderCScope.new].
44 */
45
46/**
47 * GtkBuilderCScope:
48 *
49 * A `GtkBuilderScope` implementation for the C language.
50 *
51 * `GtkBuilderCScope` instances use symbols explicitly added to @builder
52 * with prior calls to [method@Gtk.BuilderCScope.add_callback_symbol].
53 * If developers want to do that, they are encouraged to create their
54 * own scopes for that purpose.
55 *
56 * In the case that symbols are not explicitly added; GTK will uses
57 * `GModule`’s introspective features (by opening the module %NULL) to
58 * look at the application’s symbol table. From here it tries to match
59 * the signal function names given in the interface description with
60 * symbols in the application.
61 *
62 * Note that unless [method@Gtk.BuilderCScope.add_callback_symbol] is
63 * called for all signal callbacks which are referenced by the loaded XML,
64 * this functionality will require that `GModule` be supported on the platform.
65 */
66
67
68G_DEFINE_INTERFACE (GtkBuilderScope, gtk_builder_scope, G_TYPE_OBJECT)
69
70static GType
71gtk_builder_scope_default_get_type_from_name (GtkBuilderScope *self,
72 GtkBuilder *builder,
73 const char *type_name)
74{
75 GType type;
76
77 type = g_type_from_name (name: type_name);
78 if (type != G_TYPE_INVALID)
79 return type;
80
81 gtk_test_register_all_types ();
82 return g_type_from_name (name: type_name);
83}
84
85static GType
86gtk_builder_scope_default_get_type_from_function (GtkBuilderScope *self,
87 GtkBuilder *builder,
88 const char *type_name)
89{
90 return G_TYPE_INVALID;
91}
92
93static GClosure *
94gtk_builder_scope_default_create_closure (GtkBuilderScope *self,
95 GtkBuilder *builder,
96 const char *function_name,
97 GtkBuilderClosureFlags flags,
98 GObject *object,
99 GError **error)
100{
101 g_set_error (err: error,
102 GTK_BUILDER_ERROR,
103 code: GTK_BUILDER_ERROR_INVALID_FUNCTION,
104 format: "Creating closures is not supported by %s",
105 G_OBJECT_TYPE_NAME (self));
106 return NULL;
107}
108
109static void
110gtk_builder_scope_default_init (GtkBuilderScopeInterface *iface)
111{
112 iface->get_type_from_name = gtk_builder_scope_default_get_type_from_name;
113 iface->get_type_from_function = gtk_builder_scope_default_get_type_from_function;
114 iface->create_closure = gtk_builder_scope_default_create_closure;
115}
116
117GType
118gtk_builder_scope_get_type_from_name (GtkBuilderScope *self,
119 GtkBuilder *builder,
120 const char *type_name)
121{
122 g_return_val_if_fail (GTK_IS_BUILDER_SCOPE (self), G_TYPE_INVALID);
123 g_return_val_if_fail (GTK_IS_BUILDER (builder), G_TYPE_INVALID);
124 g_return_val_if_fail (type_name != NULL, G_TYPE_INVALID);
125
126 return GTK_BUILDER_SCOPE_GET_IFACE (ptr: self)->get_type_from_name (self, builder, type_name);
127}
128
129GType
130gtk_builder_scope_get_type_from_function (GtkBuilderScope *self,
131 GtkBuilder *builder,
132 const char *function_name)
133{
134 g_return_val_if_fail (GTK_IS_BUILDER_SCOPE (self), G_TYPE_INVALID);
135 g_return_val_if_fail (GTK_IS_BUILDER (builder), G_TYPE_INVALID);
136 g_return_val_if_fail (function_name != NULL, G_TYPE_INVALID);
137
138 return GTK_BUILDER_SCOPE_GET_IFACE (ptr: self)->get_type_from_function (self, builder, function_name);
139}
140
141GClosure *
142gtk_builder_scope_create_closure (GtkBuilderScope *self,
143 GtkBuilder *builder,
144 const char *function_name,
145 GtkBuilderClosureFlags flags,
146 GObject *object,
147 GError **error)
148{
149 g_return_val_if_fail (GTK_IS_BUILDER_SCOPE (self), NULL);
150 g_return_val_if_fail (GTK_IS_BUILDER (builder), NULL);
151 g_return_val_if_fail (function_name != NULL, NULL);
152 g_return_val_if_fail (object == NULL || G_IS_OBJECT (object), NULL);
153 g_return_val_if_fail (error == NULL || *error == NULL, NULL);
154
155 return GTK_BUILDER_SCOPE_GET_IFACE (ptr: self)->create_closure (self, builder, function_name, flags, object, error);
156}
157
158/*** GTK_BUILDER_CSCOPE ***/
159
160typedef struct _GtkBuilderCScopePrivate GtkBuilderCScopePrivate;
161
162struct _GtkBuilderCScopePrivate
163{
164 GModule *module;
165 GHashTable *callbacks;
166};
167
168static void gtk_builder_cscope_scope_init (GtkBuilderScopeInterface *iface);
169
170G_DEFINE_TYPE_WITH_CODE (GtkBuilderCScope, gtk_builder_cscope, G_TYPE_OBJECT,
171 G_ADD_PRIVATE(GtkBuilderCScope)
172 G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDER_SCOPE,
173 gtk_builder_cscope_scope_init))
174
175static GModule *
176gtk_builder_cscope_get_module (GtkBuilderCScope *self)
177{
178 GtkBuilderCScopePrivate *priv = gtk_builder_cscope_get_instance_private (self);
179
180 if (priv->module == NULL)
181 {
182 if (!g_module_supported ())
183 return NULL;
184
185 priv->module = g_module_open (NULL, flags: G_MODULE_BIND_LAZY);
186 }
187
188 return priv->module;
189}
190
191/*
192 * Try to map a type name to a _get_type function
193 * and call it, eg:
194 *
195 * GtkWindow -> gtk_window_get_type
196 * GtkHBox -> gtk_hbox_get_type
197 * GtkUIManager -> gtk_ui_manager_get_type
198 * GWeatherLocation -> gweather_location_get_type (split_first_cap == FALSE)
199 * GThemedIcon -> g_themed_icon_get_type (slit_first_cap == TRUE)
200 *
201 * Keep in sync with testsuite/gtk/typename.c !
202 */
203static char *
204type_name_mangle (const char *name,
205 gboolean split_first_cap)
206{
207 GString *symbol_name = g_string_new (init: "");
208 int i;
209
210 for (i = 0; name[i] != '\0'; i++)
211 {
212 /* skip if uppercase, first or previous is uppercase */
213 if ((name[i] == g_ascii_toupper (c: name[i]) &&
214 ((i > 0 && name[i-1] != g_ascii_toupper (c: name[i-1])) ||
215 (i == 1 && name[0] == g_ascii_toupper (c: name[0]) && split_first_cap))) ||
216 (i > 2 && name[i] == g_ascii_toupper (c: name[i]) &&
217 name[i-1] == g_ascii_toupper (c: name[i-1]) &&
218 name[i-2] == g_ascii_toupper (c: name[i-2])))
219 g_string_append_c (symbol_name, '_');
220 g_string_append_c (symbol_name, g_ascii_tolower (name[i]));
221 }
222 g_string_append (string: symbol_name, val: "_get_type");
223
224 return g_string_free (string: symbol_name, FALSE);
225}
226
227static GType
228gtk_builder_cscope_resolve_type_lazily (GtkBuilderCScope *self,
229 const char *name)
230{
231 GModule *module;
232 GType (*func) (void);
233 char *symbol;
234 GType gtype = G_TYPE_INVALID;
235
236 module = gtk_builder_cscope_get_module (self);
237 if (!module)
238 return G_TYPE_INVALID;
239
240 symbol = type_name_mangle (name, TRUE);
241
242 if (g_module_symbol (module, symbol_name: symbol, symbol: (gpointer)&func))
243 gtype = func ();
244
245 g_free (mem: symbol);
246
247 symbol = type_name_mangle (name, FALSE);
248
249 if (g_module_symbol (module, symbol_name: symbol, symbol: (gpointer)&func))
250 gtype = func ();
251
252 g_free (mem: symbol);
253
254 return gtype;
255}
256
257static GType
258gtk_builder_cscope_get_type_from_name (GtkBuilderScope *scope,
259 GtkBuilder *builder,
260 const char *type_name)
261{
262 GtkBuilderCScope *self = GTK_BUILDER_CSCOPE (ptr: scope);
263 GType type;
264
265 type = g_type_from_name (name: type_name);
266 if (type != G_TYPE_INVALID)
267 return type;
268
269 type = gtk_builder_cscope_resolve_type_lazily (self, name: type_name);
270 if (type != G_TYPE_INVALID)
271 return type;
272
273 gtk_test_register_all_types ();
274 type = g_type_from_name (name: type_name);
275
276 return type;
277}
278
279static GCallback
280gtk_builder_cscope_get_callback (GtkBuilderCScope *self,
281 const char *function_name,
282 GError **error)
283{
284 GModule *module;
285 GCallback func;
286
287 func = gtk_builder_cscope_lookup_callback_symbol (self, callback_name: function_name);
288 if (func)
289 return func;
290
291 module = gtk_builder_cscope_get_module (self);
292 if (module == NULL)
293 {
294 g_set_error (err: error,
295 GTK_BUILDER_ERROR,
296 code: GTK_BUILDER_ERROR_INVALID_FUNCTION,
297 format: "Could not look up function `%s`: GModule is not supported.",
298 function_name);
299 return NULL;
300 }
301
302 if (!g_module_symbol (module, symbol_name: function_name, symbol: (gpointer)&func))
303 {
304 g_set_error (err: error,
305 GTK_BUILDER_ERROR,
306 code: GTK_BUILDER_ERROR_INVALID_FUNCTION,
307 format: "No function named `%s`.",
308 function_name);
309 return NULL;
310 }
311
312 return func;
313}
314
315static GType
316gtk_builder_cscope_get_type_from_function (GtkBuilderScope *scope,
317 GtkBuilder *builder,
318 const char *function_name)
319{
320 GtkBuilderCScope *self = GTK_BUILDER_CSCOPE (ptr: scope);
321 GType (* type_func) (void);
322
323 type_func = (GType (*) (void)) gtk_builder_cscope_get_callback (self, function_name, NULL);
324 if (!type_func)
325 return G_TYPE_INVALID;
326
327 return type_func();
328}
329
330static GClosure *
331gtk_builder_cscope_create_closure_for_funcptr (GtkBuilderCScope *self,
332 GtkBuilder *builder,
333 GCallback callback,
334 gboolean swapped,
335 GObject *object)
336{
337 GClosure *closure;
338
339 if (object == NULL)
340 object = gtk_builder_get_current_object (builder);
341
342 if (object)
343 {
344 if (swapped)
345 closure = g_cclosure_new_object_swap (callback_func: callback, object);
346 else
347 closure = g_cclosure_new_object (callback_func: callback, object);
348 }
349 else
350 {
351 if (swapped)
352 closure = g_cclosure_new_swap (callback_func: callback, NULL, NULL);
353 else
354 closure = g_cclosure_new (callback_func: callback, NULL, NULL);
355 }
356
357 return closure;
358}
359
360static GClosure *
361gtk_builder_cscope_create_closure (GtkBuilderScope *scope,
362 GtkBuilder *builder,
363 const char *function_name,
364 GtkBuilderClosureFlags flags,
365 GObject *object,
366 GError **error)
367{
368 GtkBuilderCScope *self = GTK_BUILDER_CSCOPE (ptr: scope);
369 GCallback func;
370 gboolean swapped;
371
372 swapped = flags & GTK_BUILDER_CLOSURE_SWAPPED;
373
374 func = gtk_builder_cscope_get_callback (self, function_name, error);
375 if (!func)
376 return NULL;
377
378 return gtk_builder_cscope_create_closure_for_funcptr (self, builder, callback: func, swapped, object);
379}
380
381static void
382gtk_builder_cscope_scope_init (GtkBuilderScopeInterface *iface)
383{
384 iface->get_type_from_name = gtk_builder_cscope_get_type_from_name;
385 iface->get_type_from_function = gtk_builder_cscope_get_type_from_function;
386 iface->create_closure = gtk_builder_cscope_create_closure;
387}
388
389static void
390gtk_builder_cscope_finalize (GObject *object)
391{
392 GtkBuilderCScope *self = GTK_BUILDER_CSCOPE (ptr: object);
393 GtkBuilderCScopePrivate *priv = gtk_builder_cscope_get_instance_private (self);
394
395 g_clear_pointer (&priv->callbacks, g_hash_table_destroy);
396 g_clear_pointer (&priv->module, g_module_close);
397
398 G_OBJECT_CLASS (gtk_builder_cscope_parent_class)->finalize (object);
399}
400
401static void
402gtk_builder_cscope_class_init (GtkBuilderCScopeClass *klass)
403{
404 GObjectClass *object_class = G_OBJECT_CLASS (klass);
405
406 object_class->finalize = gtk_builder_cscope_finalize;
407}
408
409static void
410gtk_builder_cscope_init (GtkBuilderCScope *self)
411{
412}
413
414/**
415 * gtk_builder_cscope_new:
416 *
417 * Creates a new `GtkBuilderCScope` object to use with future
418 * `GtkBuilder` instances.
419 *
420 * Calling this function is only necessary if you want to add
421 * custom callbacks via [method@Gtk.BuilderCScope.add_callback_symbol].
422 *
423 * Returns: (transfer full) (type GtkBuilderCScope): a new `GtkBuilderCScope`
424 */
425GtkBuilderScope *
426gtk_builder_cscope_new (void)
427{
428 return g_object_new (GTK_TYPE_BUILDER_CSCOPE, NULL);
429}
430
431/**
432 * gtk_builder_cscope_add_callback_symbol:
433 * @self: a `GtkBuilderCScope`
434 * @callback_name: The name of the callback, as expected in the XML
435 * @callback_symbol: (scope async): The callback pointer
436 *
437 * Adds the @callback_symbol to the scope of @builder under the
438 * given @callback_name.
439 *
440 * Using this function overrides the behavior of
441 * [method@Gtk.Builder.create_closure] for any callback symbols that
442 * are added. Using this method allows for better encapsulation as it
443 * does not require that callback symbols be declared in the global
444 * namespace.
445 */
446void
447gtk_builder_cscope_add_callback_symbol (GtkBuilderCScope *self,
448 const char *callback_name,
449 GCallback callback_symbol)
450{
451 GtkBuilderCScopePrivate *priv = gtk_builder_cscope_get_instance_private (self);
452
453 g_return_if_fail (GTK_IS_BUILDER_CSCOPE (self));
454 g_return_if_fail (callback_name && callback_name[0]);
455 g_return_if_fail (callback_symbol != NULL);
456
457 if (!priv->callbacks)
458 priv->callbacks = g_hash_table_new_full (hash_func: g_str_hash, key_equal_func: g_str_equal,
459 key_destroy_func: g_free, NULL);
460
461 g_hash_table_insert (hash_table: priv->callbacks, key: g_strdup (str: callback_name), value: callback_symbol);
462}
463
464/**
465 * gtk_builder_cscope_add_callback_symbols: (skip)
466 * @self: a `GtkBuilderCScope`
467 * @first_callback_name: The name of the callback, as expected in the XML
468 * @first_callback_symbol: (scope async): The callback pointer
469 * @...: A list of callback name and callback symbol pairs terminated with %NULL
470 *
471 * A convenience function to add many callbacks.
472 *
473 * This is equivalent to calling [method@Gtk.BuilderCScope.add_callback_symbol]
474 * for each symbol.
475 */
476void
477gtk_builder_cscope_add_callback_symbols (GtkBuilderCScope *self,
478 const char *first_callback_name,
479 GCallback first_callback_symbol,
480 ...)
481{
482 va_list var_args;
483 const char *callback_name;
484 GCallback callback_symbol;
485
486 g_return_if_fail (GTK_IS_BUILDER_CSCOPE (self));
487 g_return_if_fail (first_callback_name && first_callback_name[0]);
488 g_return_if_fail (first_callback_symbol != NULL);
489
490 callback_name = first_callback_name;
491 callback_symbol = first_callback_symbol;
492
493 va_start (var_args, first_callback_symbol);
494
495 do {
496
497 gtk_builder_cscope_add_callback_symbol (self, callback_name, callback_symbol);
498
499 callback_name = va_arg (var_args, const char *);
500
501 if (callback_name)
502 callback_symbol = va_arg (var_args, GCallback);
503
504 } while (callback_name != NULL);
505
506 va_end (var_args);
507}
508
509/**
510 * gtk_builder_cscope_lookup_callback_symbol: (skip)
511 * @self: a `GtkBuilderCScope`
512 * @callback_name: The name of the callback
513 *
514 * Fetches a symbol previously added with
515 * gtk_builder_cscope_add_callback_symbol().
516 *
517 * Returns: (nullable) (transfer none): The callback symbol
518 * in @builder for @callback_name
519 */
520GCallback
521gtk_builder_cscope_lookup_callback_symbol (GtkBuilderCScope *self,
522 const char *callback_name)
523{
524 GtkBuilderCScopePrivate *priv = gtk_builder_cscope_get_instance_private (self);
525
526 g_return_val_if_fail (GTK_IS_BUILDER_CSCOPE (self), NULL);
527 g_return_val_if_fail (callback_name && callback_name[0], NULL);
528
529 if (priv->callbacks == NULL)
530 return NULL;
531
532 return g_hash_table_lookup (hash_table: priv->callbacks, key: callback_name);
533}
534
535

source code of gtk/gtk/gtkbuilderscope.c