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 | |
68 | G_DEFINE_INTERFACE (GtkBuilderScope, gtk_builder_scope, G_TYPE_OBJECT) |
69 | |
70 | static GType |
71 | gtk_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 | |
85 | static GType |
86 | gtk_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 | |
93 | static GClosure * |
94 | gtk_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 | |
109 | static void |
110 | gtk_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 | |
117 | GType |
118 | gtk_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 | |
129 | GType |
130 | gtk_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 | |
141 | GClosure * |
142 | gtk_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 | |
160 | typedef struct _GtkBuilderCScopePrivate GtkBuilderCScopePrivate; |
161 | |
162 | struct _GtkBuilderCScopePrivate |
163 | { |
164 | GModule *module; |
165 | GHashTable *callbacks; |
166 | }; |
167 | |
168 | static void gtk_builder_cscope_scope_init (GtkBuilderScopeInterface *iface); |
169 | |
170 | G_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 | |
175 | static GModule * |
176 | gtk_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 | */ |
203 | static char * |
204 | type_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 | |
227 | static GType |
228 | gtk_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 | |
257 | static GType |
258 | gtk_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 | |
279 | static GCallback |
280 | gtk_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 | |
315 | static GType |
316 | gtk_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 | |
330 | static GClosure * |
331 | gtk_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 | |
360 | static GClosure * |
361 | gtk_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 | |
381 | static void |
382 | gtk_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 | |
389 | static void |
390 | gtk_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 | |
401 | static void |
402 | gtk_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 | |
409 | static void |
410 | gtk_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 | */ |
425 | GtkBuilderScope * |
426 | gtk_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 | */ |
446 | void |
447 | gtk_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 | */ |
476 | void |
477 | gtk_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 | */ |
520 | GCallback |
521 | gtk_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 | |