1 | /* gtkbuilderparser.c |
2 | * Copyright (C) 2006-2007 Async Open Source, |
3 | * Johan Dahlin <jdahlin@async.com.br> |
4 | * |
5 | * This library is free software; you can redistribute it and/or |
6 | * modify it under the terms of the GNU Library General Public |
7 | * License as published by the Free Software Foundation; either |
8 | * version 2 of the License, or (at your option) any later version. |
9 | * |
10 | * This library is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | * Library General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU Library General Public |
16 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. |
17 | */ |
18 | |
19 | #include "config.h" |
20 | |
21 | #include "gtkbuilderprivate.h" |
22 | |
23 | #include "gtkbuildableprivate.h" |
24 | #include "gtkbuilderscopeprivate.h" |
25 | #include "gtkdebug.h" |
26 | #include "gtkintl.h" |
27 | #include "gtktypebuiltins.h" |
28 | #include "gtkversion.h" |
29 | #include "gdkprofilerprivate.h" |
30 | |
31 | #include "gtkprivate.h" |
32 | |
33 | #include <gio/gio.h> |
34 | #include <string.h> |
35 | |
36 | |
37 | typedef struct |
38 | { |
39 | const GtkBuildableParser *last_parser; |
40 | gpointer last_user_data; |
41 | int last_depth; |
42 | } GtkBuildableParserStack; |
43 | |
44 | static void |
45 | pop_subparser_stack (GtkBuildableParseContext *context) |
46 | { |
47 | GtkBuildableParserStack *stack = &g_array_index (context->subparser_stack, GtkBuildableParserStack, |
48 | context->subparser_stack->len - 1); |
49 | |
50 | context->awaiting_pop = TRUE; |
51 | context->held_user_data = context->user_data; |
52 | |
53 | context->user_data = stack->last_user_data; |
54 | context->parser = stack->last_parser; |
55 | |
56 | g_array_set_size (array: context->subparser_stack, length: context->subparser_stack->len - 1); |
57 | } |
58 | |
59 | static void |
60 | possibly_finish_subparser (GtkBuildableParseContext *context) |
61 | { |
62 | GtkBuildableParserStack *stack; |
63 | |
64 | if (!context->subparser_stack || |
65 | context->subparser_stack->len == 0) |
66 | return; |
67 | |
68 | stack = &g_array_index (context->subparser_stack, GtkBuildableParserStack, |
69 | context->subparser_stack->len - 1); |
70 | |
71 | if (stack->last_depth == context->tag_stack->len) |
72 | pop_subparser_stack (context); |
73 | } |
74 | |
75 | static void |
76 | proxy_start_element (GMarkupParseContext *gm_context, |
77 | const char *element_name, |
78 | const char **attribute_names, |
79 | const char **attribute_values, |
80 | gpointer user_data, |
81 | GError **error) |
82 | { |
83 | GtkBuildableParseContext *context = user_data; |
84 | |
85 | // Due to the way GMarkup works we're sure this will live until the end_element callback |
86 | g_ptr_array_add (array: context->tag_stack, data: (char *)element_name); |
87 | |
88 | if (context->parser->start_element) |
89 | context->parser->start_element (context, element_name, |
90 | attribute_names, attribute_values, |
91 | context->user_data, error); |
92 | } |
93 | |
94 | static void |
95 | proxy_end_element (GMarkupParseContext *gm_context, |
96 | const char *element_name, |
97 | gpointer user_data, |
98 | GError **error) |
99 | { |
100 | GtkBuildableParseContext *context = user_data; |
101 | |
102 | possibly_finish_subparser (context); |
103 | |
104 | if (context->parser->end_element) |
105 | context->parser->end_element (context, element_name, context->user_data, error); |
106 | |
107 | g_ptr_array_set_size (array: context->tag_stack, length: context->tag_stack->len - 1); |
108 | } |
109 | |
110 | static void |
111 | proxy_text (GMarkupParseContext *gm_context, |
112 | const char *text, |
113 | gsize text_len, |
114 | gpointer user_data, |
115 | GError **error) |
116 | { |
117 | GtkBuildableParseContext *context = user_data; |
118 | |
119 | if (context->parser->text) |
120 | context->parser->text (context, text, text_len, context->user_data, error); |
121 | } |
122 | |
123 | static void |
124 | proxy_error (GMarkupParseContext *gm_context, |
125 | GError *error, |
126 | gpointer user_data) |
127 | { |
128 | GtkBuildableParseContext *context = user_data; |
129 | |
130 | if (context->parser->error) |
131 | context->parser->error (context, error, context->user_data); |
132 | |
133 | /* report the error all the way up to free all the user-data */ |
134 | |
135 | if (!context->subparser_stack) |
136 | return; |
137 | |
138 | while (context->subparser_stack->len > 0) |
139 | { |
140 | pop_subparser_stack (context); |
141 | context->awaiting_pop = FALSE; /* already been freed */ |
142 | |
143 | if (context->parser->error) |
144 | context->parser->error (context, error, context->user_data); |
145 | } |
146 | } |
147 | |
148 | static const GMarkupParser gmarkup_parser = { |
149 | proxy_start_element, |
150 | proxy_end_element, |
151 | proxy_text, |
152 | NULL, |
153 | proxy_error, |
154 | }; |
155 | |
156 | static void |
157 | gtk_buildable_parse_context_init (GtkBuildableParseContext *context, |
158 | const GtkBuildableParser *parser, |
159 | gpointer user_data) |
160 | { |
161 | context->internal_callbacks = &gmarkup_parser; |
162 | context->ctx = NULL; |
163 | |
164 | context->parser = parser; |
165 | context->user_data = user_data; |
166 | |
167 | context->subparser_stack = NULL; |
168 | context->tag_stack = g_ptr_array_new (); |
169 | context->held_user_data = NULL; |
170 | context->awaiting_pop = FALSE; |
171 | } |
172 | |
173 | static void |
174 | gtk_buildable_parse_context_free (GtkBuildableParseContext *context) |
175 | { |
176 | if (context->subparser_stack) |
177 | g_array_unref (array: context->subparser_stack); |
178 | |
179 | g_ptr_array_unref (array: context->tag_stack); |
180 | } |
181 | |
182 | static gboolean |
183 | gtk_buildable_parse_context_parse (GtkBuildableParseContext *context, |
184 | const char *text, |
185 | gssize text_len, |
186 | GError **error) |
187 | { |
188 | gboolean res; |
189 | |
190 | if (_gtk_buildable_parser_is_precompiled (data: text, data_len: text_len)) |
191 | { |
192 | res = _gtk_buildable_parser_replay_precompiled (context, data: text, data_len: text_len, error); |
193 | } |
194 | else |
195 | { |
196 | context->ctx = g_markup_parse_context_new (parser: context->internal_callbacks, |
197 | flags: G_MARKUP_TREAT_CDATA_AS_TEXT, |
198 | user_data: context, NULL); |
199 | res = g_markup_parse_context_parse (context: context->ctx, text, text_len, error); |
200 | g_markup_parse_context_free (context: context->ctx); |
201 | } |
202 | |
203 | return res; |
204 | } |
205 | |
206 | |
207 | /** |
208 | * gtk_buildable_parse_context_push: |
209 | * @context: a `GtkBuildableParseContext` |
210 | * @parser: a `GtkBuildableParser` |
211 | * @user_data: user data to pass to `GtkBuildableParser` functions |
212 | * |
213 | * Temporarily redirects markup data to a sub-parser. |
214 | * |
215 | * This function may only be called from the start_element handler of |
216 | * a `GtkBuildableParser`. It must be matched with a corresponding call to |
217 | * gtk_buildable_parse_context_pop() in the matching end_element handler |
218 | * (except in the case that the parser aborts due to an error). |
219 | * |
220 | * All tags, text and other data between the matching tags is |
221 | * redirected to the subparser given by @parser. @user_data is used |
222 | * as the user_data for that parser. @user_data is also passed to the |
223 | * error callback in the event that an error occurs. This includes |
224 | * errors that occur in subparsers of the subparser. |
225 | * |
226 | * The end tag matching the start tag for which this call was made is |
227 | * handled by the previous parser (which is given its own user_data) |
228 | * which is why gtk_buildable_parse_context_pop() is provided to allow "one |
229 | * last access" to the @user_data provided to this function. In the |
230 | * case of error, the @user_data provided here is passed directly to |
231 | * the error callback of the subparser and gtk_buildable_parse_context_pop() |
232 | * should not be called. In either case, if @user_data was allocated |
233 | * then it ought to be freed from both of these locations. |
234 | * |
235 | * This function is not intended to be directly called by users |
236 | * interested in invoking subparsers. Instead, it is intended to be |
237 | * used by the subparsers themselves to implement a higher-level |
238 | * interface. |
239 | * |
240 | * For an example of how to use this, see g_markup_parse_context_push() which |
241 | * has the same kind of API. |
242 | **/ |
243 | void |
244 | gtk_buildable_parse_context_push (GtkBuildableParseContext *context, |
245 | const GtkBuildableParser *parser, |
246 | gpointer user_data) |
247 | { |
248 | GtkBuildableParserStack stack = { 0 }; |
249 | |
250 | stack.last_parser = context->parser; |
251 | stack.last_user_data = context->user_data; |
252 | stack.last_depth = context->tag_stack->len; // If at end_element time we're this deep, then pop it |
253 | |
254 | context->parser = parser; |
255 | context->user_data = user_data; |
256 | |
257 | if (!context->subparser_stack) |
258 | context->subparser_stack = g_array_new (FALSE, FALSE, element_size: sizeof (GtkBuildableParserStack)); |
259 | |
260 | g_array_append_val (context->subparser_stack, stack); |
261 | } |
262 | |
263 | /** |
264 | * gtk_buildable_parse_context_pop: |
265 | * @context: a `GtkBuildableParseContext` |
266 | * |
267 | * Completes the process of a temporary sub-parser redirection. |
268 | * |
269 | * This function exists to collect the user_data allocated by a |
270 | * matching call to gtk_buildable_parse_context_push(). It must be called |
271 | * in the end_element handler corresponding to the start_element |
272 | * handler during which gtk_buildable_parse_context_push() was called. |
273 | * You must not call this function from the error callback -- the |
274 | * @user_data is provided directly to the callback in that case. |
275 | * |
276 | * This function is not intended to be directly called by users |
277 | * interested in invoking subparsers. Instead, it is intended to |
278 | * be used by the subparsers themselves to implement a higher-level |
279 | * interface. |
280 | * |
281 | * Returns: the user data passed to gtk_buildable_parse_context_push() |
282 | */ |
283 | gpointer |
284 | gtk_buildable_parse_context_pop (GtkBuildableParseContext *context) |
285 | { |
286 | gpointer user_data; |
287 | |
288 | if (!context->awaiting_pop) |
289 | possibly_finish_subparser (context); |
290 | |
291 | g_assert (context->awaiting_pop); |
292 | |
293 | context->awaiting_pop = FALSE; |
294 | |
295 | user_data = context->held_user_data; |
296 | context->held_user_data = NULL; |
297 | |
298 | return user_data; |
299 | } |
300 | |
301 | /** |
302 | * gtk_buildable_parse_context_get_element: |
303 | * @context: a `GtkBuildablParseContext` |
304 | * |
305 | * Retrieves the name of the currently open element. |
306 | * |
307 | * If called from the start_element or end_element handlers this will |
308 | * give the element_name as passed to those functions. For the parent |
309 | * elements, see gtk_buildable_parse_context_get_element_stack(). |
310 | * |
311 | * Returns: (nullable): the name of the currently open element |
312 | */ |
313 | const char * |
314 | gtk_buildable_parse_context_get_element (GtkBuildableParseContext *context) |
315 | { |
316 | if (context->tag_stack->len > 0) |
317 | return g_ptr_array_index (context->tag_stack, context->tag_stack->len - 1); |
318 | return NULL; |
319 | } |
320 | |
321 | /** |
322 | * gtk_buildable_parse_context_get_element_stack: |
323 | * @context: a `GtkBuildableParseContext` |
324 | * |
325 | * Retrieves the element stack from the internal state of the parser. |
326 | * |
327 | * The returned `GPtrArray` is an array of strings where the last item is |
328 | * the currently open tag (as would be returned by |
329 | * gtk_buildable_parse_context_get_element()) and the previous item is its |
330 | * immediate parent. |
331 | * |
332 | * This function is intended to be used in the start_element and |
333 | * end_element handlers where gtk_buildable_parse_context_get_element() |
334 | * would merely return the name of the element that is being |
335 | * processed. |
336 | * |
337 | * Returns: (transfer none) (element-type utf8): the element stack, which must not be modified |
338 | */ |
339 | GPtrArray * |
340 | gtk_buildable_parse_context_get_element_stack (GtkBuildableParseContext *context) |
341 | { |
342 | return context->tag_stack; |
343 | } |
344 | |
345 | /** |
346 | * gtk_buildable_parse_context_get_position: |
347 | * @context: a `GtkBuildableParseContext` |
348 | * @line_number: (out) (optional): return location for a line number |
349 | * @char_number: (out) (optional): return location for a char-on-line number |
350 | * |
351 | * Retrieves the current line number and the number of the character on |
352 | * that line. Intended for use in error messages; there are no strict |
353 | * semantics for what constitutes the "current" line number other than |
354 | * "the best number we could come up with for error messages." |
355 | */ |
356 | void |
357 | gtk_buildable_parse_context_get_position (GtkBuildableParseContext *context, |
358 | int *line_number, |
359 | int *char_number) |
360 | |
361 | { |
362 | if (context->ctx) |
363 | g_markup_parse_context_get_position (context: context->ctx, line_number, char_number); |
364 | else |
365 | { |
366 | if (line_number) |
367 | *line_number = 0; |
368 | if (char_number) |
369 | *char_number = 0; |
370 | } |
371 | } |
372 | |
373 | static void free_property_info (PropertyInfo *info); |
374 | static void free_object_info (ObjectInfo *info); |
375 | |
376 | |
377 | static inline void |
378 | state_push (ParserData *data, gpointer info) |
379 | { |
380 | g_ptr_array_add (array: data->stack, data: info); |
381 | } |
382 | |
383 | static inline gpointer |
384 | state_peek (ParserData *data) |
385 | { |
386 | if (!data->stack || |
387 | data->stack->len == 0) |
388 | return NULL; |
389 | |
390 | return g_ptr_array_index (data->stack, data->stack->len - 1); |
391 | } |
392 | |
393 | static inline gpointer |
394 | state_pop (ParserData *data) |
395 | { |
396 | gpointer old = NULL; |
397 | |
398 | g_assert (data->stack); |
399 | |
400 | old = state_peek (data); |
401 | g_assert (old); |
402 | data->stack->len --; |
403 | return old; |
404 | } |
405 | #define state_peek_info(data, st) ((st*)state_peek(data)) |
406 | #define state_pop_info(data, st) ((st*)state_pop(data)) |
407 | |
408 | static void |
409 | error_missing_attribute (ParserData *data, |
410 | const char *tag, |
411 | const char *attribute, |
412 | GError **error) |
413 | { |
414 | int line, col; |
415 | |
416 | gtk_buildable_parse_context_get_position (context: &data->ctx, line_number: &line, char_number: &col); |
417 | |
418 | g_set_error (err: error, |
419 | GTK_BUILDER_ERROR, |
420 | code: GTK_BUILDER_ERROR_MISSING_ATTRIBUTE, |
421 | format: "%s:%d:%d <%s> requires attribute '%s'" , |
422 | data->filename, line, col, tag, attribute); |
423 | } |
424 | |
425 | static void |
426 | error_invalid_tag (ParserData *data, |
427 | const char *tag, |
428 | const char *expected, |
429 | GError **error) |
430 | { |
431 | int line, col; |
432 | |
433 | gtk_buildable_parse_context_get_position (context: &data->ctx, line_number: &line, char_number: &col); |
434 | |
435 | if (expected) |
436 | g_set_error (err: error, |
437 | GTK_BUILDER_ERROR, |
438 | code: GTK_BUILDER_ERROR_INVALID_TAG, |
439 | format: "%s:%d:%d <%s> is not a valid tag here, expected a <%s> tag" , |
440 | data->filename, line, col, tag, expected); |
441 | else |
442 | g_set_error (err: error, |
443 | GTK_BUILDER_ERROR, |
444 | code: GTK_BUILDER_ERROR_INVALID_TAG, |
445 | format: "%s:%d:%d <%s> is not a valid tag here" , |
446 | data->filename, line, col, tag); |
447 | } |
448 | |
449 | static void |
450 | error_unhandled_tag (ParserData *data, |
451 | const char *tag, |
452 | GError **error) |
453 | { |
454 | int line, col; |
455 | |
456 | gtk_buildable_parse_context_get_position (context: &data->ctx, line_number: &line, char_number: &col); |
457 | g_set_error (err: error, |
458 | GTK_BUILDER_ERROR, |
459 | code: GTK_BUILDER_ERROR_UNHANDLED_TAG, |
460 | format: "%s:%d:%d Unhandled tag: <%s>" , |
461 | data->filename, line, col, tag); |
462 | } |
463 | |
464 | static GObject * |
465 | builder_construct (ParserData *data, |
466 | ObjectInfo *object_info, |
467 | GError **error) |
468 | { |
469 | GObject *object; |
470 | |
471 | g_assert (object_info != NULL); |
472 | |
473 | if (object_info->object == NULL) |
474 | { |
475 | object = _gtk_builder_construct (builder: data->builder, info: object_info, error); |
476 | if (!object) |
477 | return NULL; |
478 | } |
479 | else |
480 | { |
481 | /* We're building a template, the object is already set and |
482 | * we just want to resolve the properties at the right time |
483 | */ |
484 | object = object_info->object; |
485 | _gtk_builder_apply_properties (builder: data->builder, info: object_info, error); |
486 | } |
487 | |
488 | g_assert (G_IS_OBJECT (object)); |
489 | |
490 | object_info->object = object; |
491 | |
492 | return object; |
493 | } |
494 | |
495 | static void |
496 | parse_requires (ParserData *data, |
497 | const char *element_name, |
498 | const char **names, |
499 | const char **values, |
500 | GError **error) |
501 | { |
502 | RequiresInfo *req_info; |
503 | const char *library = NULL; |
504 | const char *version = NULL; |
505 | char **split; |
506 | int version_major = 0; |
507 | int version_minor = 0; |
508 | |
509 | if (!g_markup_collect_attributes (element_name, attribute_names: names, attribute_values: values, error, |
510 | first_type: G_MARKUP_COLLECT_STRING, first_attr: "lib" , &library, |
511 | G_MARKUP_COLLECT_STRING, "version" , &version, |
512 | G_MARKUP_COLLECT_INVALID)) |
513 | { |
514 | _gtk_builder_prefix_error (builder: data->builder, context: &data->ctx, error); |
515 | return; |
516 | } |
517 | |
518 | if (!(split = g_strsplit (string: version, delimiter: "." , max_tokens: 2)) || !split[0] || !split[1]) |
519 | { |
520 | g_set_error (err: error, |
521 | GTK_BUILDER_ERROR, |
522 | code: GTK_BUILDER_ERROR_INVALID_VALUE, |
523 | format: "'version' attribute has malformed value '%s'" , version); |
524 | _gtk_builder_prefix_error (builder: data->builder, context: &data->ctx, error); |
525 | return; |
526 | } |
527 | version_major = g_ascii_strtoll (nptr: split[0], NULL, base: 10); |
528 | version_minor = g_ascii_strtoll (nptr: split[1], NULL, base: 10); |
529 | g_strfreev (str_array: split); |
530 | |
531 | req_info = g_slice_new0 (RequiresInfo); |
532 | req_info->library = g_strdup (str: library); |
533 | req_info->major = version_major; |
534 | req_info->minor = version_minor; |
535 | state_push (data, info: req_info); |
536 | req_info->tag_type = TAG_REQUIRES; |
537 | } |
538 | |
539 | static gboolean |
540 | is_requested_object (const char *object, |
541 | ParserData *data) |
542 | { |
543 | int i; |
544 | |
545 | for (i = 0; data->requested_objects[i]; ++i) |
546 | { |
547 | if (g_strcmp0 (str1: data->requested_objects[i], str2: object) == 0) |
548 | return TRUE; |
549 | } |
550 | |
551 | return FALSE; |
552 | } |
553 | |
554 | static void |
555 | parse_object (GtkBuildableParseContext *context, |
556 | ParserData *data, |
557 | const char *element_name, |
558 | const char **names, |
559 | const char **values, |
560 | GError **error) |
561 | { |
562 | ObjectInfo *object_info; |
563 | ChildInfo* child_info; |
564 | GType object_type = G_TYPE_INVALID; |
565 | const char *object_class = NULL; |
566 | const char *constructor = NULL; |
567 | const char *type_func = NULL; |
568 | const char *object_id = NULL; |
569 | char *internal_id = NULL; |
570 | int line; |
571 | gpointer line_ptr; |
572 | gboolean has_duplicate; |
573 | |
574 | child_info = state_peek_info (data, ChildInfo); |
575 | if (child_info && child_info->tag_type == TAG_OBJECT) |
576 | { |
577 | error_invalid_tag (data, tag: element_name, NULL, error); |
578 | return; |
579 | } |
580 | |
581 | /* Even though 'class' is a mandatory attribute, we don't flag its |
582 | * absence here because it's supposed to throw |
583 | * GTK_BUILDER_ERROR_MISSING_ATTRIBUTE, not |
584 | * G_MARKUP_ERROR_MISSING_ATTRIBUTE. It's handled immediately |
585 | * afterwards. |
586 | */ |
587 | if (!g_markup_collect_attributes (element_name, attribute_names: names, attribute_values: values, error, |
588 | first_type: G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, first_attr: "class" , &object_class, |
589 | G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "constructor" , &constructor, |
590 | G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "type-func" , &type_func, |
591 | G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "id" , &object_id, |
592 | G_MARKUP_COLLECT_INVALID)) |
593 | { |
594 | _gtk_builder_prefix_error (builder: data->builder, context: &data->ctx, error); |
595 | return; |
596 | } |
597 | |
598 | if (!object_class) |
599 | { |
600 | error_missing_attribute (data, tag: element_name, attribute: "class" , error); |
601 | return; |
602 | } |
603 | |
604 | if (type_func) |
605 | { |
606 | /* Call the GType function, and return the GType, it's guaranteed afterwards |
607 | * that g_type_from_name on the name will return our GType |
608 | */ |
609 | object_type = gtk_builder_scope_get_type_from_function (self: gtk_builder_get_scope (builder: data->builder), builder: data->builder, function_name: type_func); |
610 | if (object_type == G_TYPE_INVALID) |
611 | { |
612 | g_set_error (err: error, |
613 | GTK_BUILDER_ERROR, |
614 | code: GTK_BUILDER_ERROR_INVALID_TYPE_FUNCTION, |
615 | format: "Invalid type function '%s'" , type_func); |
616 | _gtk_builder_prefix_error (builder: data->builder, context, error); |
617 | return; |
618 | } |
619 | } |
620 | else |
621 | { |
622 | g_assert_nonnull (object_class); |
623 | |
624 | object_type = gtk_builder_get_type_from_name (builder: data->builder, type_name: object_class); |
625 | if (object_type == G_TYPE_INVALID) |
626 | { |
627 | g_set_error (err: error, |
628 | GTK_BUILDER_ERROR, |
629 | code: GTK_BUILDER_ERROR_INVALID_VALUE, |
630 | format: "Invalid object type '%s'" , object_class); |
631 | _gtk_builder_prefix_error (builder: data->builder, context, error); |
632 | return; |
633 | } |
634 | } |
635 | |
636 | if (!object_id) |
637 | { |
638 | internal_id = g_strdup_printf (format: "___object_%d___" , ++data->object_counter); |
639 | object_id = internal_id; |
640 | } |
641 | |
642 | ++data->cur_object_level; |
643 | |
644 | /* check if we reached a requested object (if it is specified) */ |
645 | if (data->requested_objects && !data->inside_requested_object) |
646 | { |
647 | if (is_requested_object (object: object_id, data)) |
648 | { |
649 | data->requested_object_level = data->cur_object_level; |
650 | |
651 | GTK_NOTE (BUILDER, |
652 | g_message ("requested object \"%s\" found at level %d" , |
653 | object_id, data->requested_object_level)); |
654 | |
655 | data->inside_requested_object = TRUE; |
656 | } |
657 | else |
658 | { |
659 | g_free (mem: internal_id); |
660 | return; |
661 | } |
662 | } |
663 | |
664 | object_info = g_slice_new0 (ObjectInfo); |
665 | object_info->tag_type = TAG_OBJECT; |
666 | object_info->type = object_type; |
667 | object_info->oclass = g_type_class_ref (type: object_type); |
668 | object_info->id = (internal_id) ? internal_id : g_strdup (str: object_id); |
669 | object_info->constructor = g_strdup (str: constructor); |
670 | object_info->parent = (CommonInfo*)child_info; |
671 | state_push (data, info: object_info); |
672 | |
673 | has_duplicate = g_hash_table_lookup_extended (hash_table: data->object_ids, lookup_key: object_id, NULL, value: &line_ptr); |
674 | if (has_duplicate != 0) |
675 | { |
676 | g_set_error (err: error, |
677 | GTK_BUILDER_ERROR, |
678 | code: GTK_BUILDER_ERROR_DUPLICATE_ID, |
679 | format: "Duplicate object ID '%s' (previously on line %d)" , |
680 | object_id, GPOINTER_TO_INT (line_ptr)); |
681 | _gtk_builder_prefix_error (builder: data->builder, context, error); |
682 | return; |
683 | } |
684 | |
685 | gtk_buildable_parse_context_get_position (context, line_number: &line, NULL); |
686 | g_hash_table_insert (hash_table: data->object_ids, key: g_strdup (str: object_id), GINT_TO_POINTER (line)); |
687 | } |
688 | |
689 | static void |
690 | parse_template (GtkBuildableParseContext *context, |
691 | ParserData *data, |
692 | const char *element_name, |
693 | const char **names, |
694 | const char **values, |
695 | GError **error) |
696 | { |
697 | ObjectInfo *object_info; |
698 | const char *object_class = NULL; |
699 | const char *parent_class = NULL; |
700 | int line; |
701 | gpointer line_ptr; |
702 | gboolean has_duplicate; |
703 | GType template_type; |
704 | GType parsed_type; |
705 | |
706 | template_type = _gtk_builder_get_template_type (builder: data->builder); |
707 | |
708 | if (!g_markup_collect_attributes (element_name, attribute_names: names, attribute_values: values, error, |
709 | first_type: G_MARKUP_COLLECT_STRING, first_attr: "class" , &object_class, |
710 | G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "parent" , &parent_class, |
711 | G_MARKUP_COLLECT_INVALID)) |
712 | { |
713 | _gtk_builder_prefix_error (builder: data->builder, context: &data->ctx, error); |
714 | return; |
715 | } |
716 | |
717 | if (template_type == 0) |
718 | { |
719 | g_set_error (err: error, |
720 | GTK_BUILDER_ERROR, |
721 | code: GTK_BUILDER_ERROR_UNHANDLED_TAG, |
722 | format: "Template declaration (class '%s', parent '%s') where templates aren't supported" , |
723 | object_class, parent_class ? parent_class : "GtkWidget" ); |
724 | _gtk_builder_prefix_error (builder: data->builder, context, error); |
725 | return; |
726 | } |
727 | else if (state_peek (data) != NULL) |
728 | { |
729 | error_invalid_tag (data, tag: "template" , NULL, error); |
730 | return; |
731 | } |
732 | |
733 | parsed_type = g_type_from_name (name: object_class); |
734 | if (template_type != parsed_type) |
735 | { |
736 | g_set_error (err: error, |
737 | GTK_BUILDER_ERROR, |
738 | code: GTK_BUILDER_ERROR_TEMPLATE_MISMATCH, |
739 | format: "Parsed template definition for type '%s', expected type '%s'" , |
740 | object_class, g_type_name (type: template_type)); |
741 | _gtk_builder_prefix_error (builder: data->builder, context, error); |
742 | return; |
743 | } |
744 | |
745 | if (parent_class) |
746 | { |
747 | GType parent_type = g_type_from_name (name: parent_class); |
748 | GType expected_type = g_type_parent (type: parsed_type); |
749 | |
750 | if (parent_type == G_TYPE_INVALID) |
751 | { |
752 | g_set_error (err: error, GTK_BUILDER_ERROR, |
753 | code: GTK_BUILDER_ERROR_INVALID_VALUE, |
754 | format: "Invalid template parent type '%s'" , parent_class); |
755 | _gtk_builder_prefix_error (builder: data->builder, context, error); |
756 | return; |
757 | } |
758 | if (parent_type != expected_type) |
759 | { |
760 | g_set_error (err: error, GTK_BUILDER_ERROR, |
761 | code: GTK_BUILDER_ERROR_TEMPLATE_MISMATCH, |
762 | format: "Template parent type '%s' does not match instance parent type '%s'." , |
763 | parent_class, g_type_name (type: expected_type)); |
764 | _gtk_builder_prefix_error (builder: data->builder, context, error); |
765 | return; |
766 | } |
767 | } |
768 | |
769 | ++data->cur_object_level; |
770 | |
771 | object_info = g_slice_new0 (ObjectInfo); |
772 | object_info->tag_type = TAG_TEMPLATE; |
773 | object_info->type = parsed_type; |
774 | object_info->oclass = g_type_class_ref (type: parsed_type); |
775 | object_info->id = g_strdup (str: object_class); |
776 | object_info->object = gtk_builder_get_object (builder: data->builder, name: object_class); |
777 | state_push (data, info: object_info); |
778 | |
779 | has_duplicate = g_hash_table_lookup_extended (hash_table: data->object_ids, lookup_key: object_class, NULL, value: &line_ptr); |
780 | if (has_duplicate != 0) |
781 | { |
782 | g_set_error (err: error, |
783 | GTK_BUILDER_ERROR, |
784 | code: GTK_BUILDER_ERROR_DUPLICATE_ID, |
785 | format: "Duplicate object ID '%s' (previously on line %d)" , |
786 | object_class, GPOINTER_TO_INT (line_ptr)); |
787 | _gtk_builder_prefix_error (builder: data->builder, context, error); |
788 | return; |
789 | } |
790 | |
791 | gtk_buildable_parse_context_get_position (context, line_number: &line, NULL); |
792 | g_hash_table_insert (hash_table: data->object_ids, key: g_strdup (str: object_class), GINT_TO_POINTER (line)); |
793 | } |
794 | |
795 | |
796 | static void |
797 | free_object_info (ObjectInfo *info) |
798 | { |
799 | /* Do not free the signal items, which GtkBuilder takes ownership of */ |
800 | g_type_class_unref (g_class: info->oclass); |
801 | if (info->signals) |
802 | g_ptr_array_free (array: info->signals, TRUE); |
803 | if (info->properties) |
804 | g_ptr_array_free (array: info->properties, TRUE); |
805 | g_free (mem: info->constructor); |
806 | g_free (mem: info->id); |
807 | g_slice_free (ObjectInfo, info); |
808 | } |
809 | |
810 | static void |
811 | parse_child (ParserData *data, |
812 | const char *element_name, |
813 | const char **names, |
814 | const char **values, |
815 | GError **error) |
816 | |
817 | { |
818 | ObjectInfo* object_info; |
819 | ChildInfo *child_info; |
820 | const char *type = NULL; |
821 | const char *internal_child = NULL; |
822 | |
823 | object_info = state_peek_info (data, ObjectInfo); |
824 | if (!object_info || |
825 | !(object_info->tag_type == TAG_OBJECT || |
826 | object_info->tag_type == TAG_TEMPLATE)) |
827 | { |
828 | error_invalid_tag (data, tag: element_name, NULL, error); |
829 | return; |
830 | } |
831 | |
832 | if (!g_markup_collect_attributes (element_name, attribute_names: names, attribute_values: values, error, |
833 | first_type: G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, first_attr: "type" , &type, |
834 | G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "internal-child" , &internal_child, |
835 | G_MARKUP_COLLECT_INVALID)) |
836 | { |
837 | _gtk_builder_prefix_error (builder: data->builder, context: &data->ctx, error); |
838 | return; |
839 | } |
840 | |
841 | child_info = g_slice_new0 (ChildInfo); |
842 | child_info->tag_type = TAG_CHILD; |
843 | child_info->type = g_strdup (str: type); |
844 | child_info->internal_child = g_strdup (str: internal_child); |
845 | child_info->parent = (CommonInfo*)object_info; |
846 | state_push (data, info: child_info); |
847 | |
848 | object_info->object = builder_construct (data, object_info, error); |
849 | } |
850 | |
851 | static void |
852 | free_child_info (ChildInfo *info) |
853 | { |
854 | g_free (mem: info->type); |
855 | g_free (mem: info->internal_child); |
856 | g_slice_free (ChildInfo, info); |
857 | } |
858 | |
859 | static void |
860 | parse_property (ParserData *data, |
861 | const char *element_name, |
862 | const char **names, |
863 | const char **values, |
864 | GError **error) |
865 | { |
866 | PropertyInfo *info; |
867 | const char *name = NULL; |
868 | const char *context = NULL; |
869 | const char *bind_source = NULL; |
870 | const char *bind_property = NULL; |
871 | const char *bind_flags_str = NULL; |
872 | GBindingFlags bind_flags = G_BINDING_DEFAULT; |
873 | gboolean translatable = FALSE; |
874 | ObjectInfo *object_info; |
875 | GParamSpec *pspec = NULL; |
876 | int line, col; |
877 | |
878 | object_info = state_peek_info (data, ObjectInfo); |
879 | if (!object_info || |
880 | !(object_info->tag_type == TAG_OBJECT || |
881 | object_info->tag_type == TAG_TEMPLATE)) |
882 | { |
883 | error_invalid_tag (data, tag: element_name, NULL, error); |
884 | return; |
885 | } |
886 | |
887 | if (!g_markup_collect_attributes (element_name, attribute_names: names, attribute_values: values, error, |
888 | first_type: G_MARKUP_COLLECT_STRING, first_attr: "name" , &name, |
889 | G_MARKUP_COLLECT_BOOLEAN|G_MARKUP_COLLECT_OPTIONAL, "translatable" , &translatable, |
890 | G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "comments" , NULL, |
891 | G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "context" , &context, |
892 | G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "bind-source" , &bind_source, |
893 | G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "bind-property" , &bind_property, |
894 | G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "bind-flags" , &bind_flags_str, |
895 | G_MARKUP_COLLECT_INVALID)) |
896 | { |
897 | _gtk_builder_prefix_error (builder: data->builder, context: &data->ctx, error); |
898 | return; |
899 | } |
900 | |
901 | pspec = g_object_class_find_property (oclass: object_info->oclass, property_name: name); |
902 | |
903 | if (!pspec) |
904 | { |
905 | g_set_error (err: error, |
906 | GTK_BUILDER_ERROR, |
907 | code: GTK_BUILDER_ERROR_INVALID_PROPERTY, |
908 | format: "Invalid property: %s.%s" , |
909 | g_type_name (type: object_info->type), name); |
910 | _gtk_builder_prefix_error (builder: data->builder, context: &data->ctx, error); |
911 | return; |
912 | } |
913 | |
914 | if (bind_flags_str) |
915 | { |
916 | if (!_gtk_builder_flags_from_string (G_TYPE_BINDING_FLAGS, string: bind_flags_str, value: &bind_flags, error)) |
917 | { |
918 | _gtk_builder_prefix_error (builder: data->builder, context: &data->ctx, error); |
919 | return; |
920 | } |
921 | } |
922 | |
923 | gtk_buildable_parse_context_get_position (context: &data->ctx, line_number: &line, char_number: &col); |
924 | |
925 | if (bind_source) |
926 | { |
927 | BindingInfo *binfo; |
928 | |
929 | binfo = g_slice_new0 (BindingInfo); |
930 | binfo->tag_type = TAG_BINDING; |
931 | binfo->target = NULL; |
932 | binfo->target_pspec = pspec; |
933 | binfo->source = g_strdup (str: bind_source); |
934 | binfo->source_property = bind_property ? g_strdup (str: bind_property) : g_strdup (str: name); |
935 | binfo->flags = bind_flags; |
936 | binfo->line = line; |
937 | binfo->col = col; |
938 | |
939 | object_info->bindings = g_slist_prepend (list: object_info->bindings, data: binfo); |
940 | } |
941 | else if (bind_property) |
942 | { |
943 | error_missing_attribute (data, tag: element_name, |
944 | attribute: "bind-source" , |
945 | error); |
946 | return; |
947 | } |
948 | |
949 | info = g_slice_new0 (PropertyInfo); |
950 | info->tag_type = TAG_PROPERTY; |
951 | info->pspec = pspec; |
952 | info->text = g_string_new (init: "" ); |
953 | info->translatable = translatable; |
954 | info->bound = bind_source != NULL; |
955 | info->context = g_strdup (str: context); |
956 | info->line = line; |
957 | info->col = col; |
958 | |
959 | state_push (data, info); |
960 | } |
961 | |
962 | static void |
963 | parse_binding (ParserData *data, |
964 | const char *element_name, |
965 | const char **names, |
966 | const char **values, |
967 | GError **error) |
968 | { |
969 | BindingExpressionInfo *info; |
970 | const char *name = NULL; |
971 | const char *object_name = NULL; |
972 | ObjectInfo *object_info; |
973 | GParamSpec *pspec = NULL; |
974 | |
975 | object_info = state_peek_info (data, ObjectInfo); |
976 | if (!object_info || |
977 | !(object_info->tag_type == TAG_OBJECT || |
978 | object_info->tag_type == TAG_TEMPLATE)) |
979 | { |
980 | error_invalid_tag (data, tag: element_name, NULL, error); |
981 | return; |
982 | } |
983 | |
984 | if (!g_markup_collect_attributes (element_name, attribute_names: names, attribute_values: values, error, |
985 | first_type: G_MARKUP_COLLECT_STRING, first_attr: "name" , &name, |
986 | G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "object" , &object_name, |
987 | G_MARKUP_COLLECT_INVALID)) |
988 | { |
989 | _gtk_builder_prefix_error (builder: data->builder, context: &data->ctx, error); |
990 | return; |
991 | } |
992 | |
993 | pspec = g_object_class_find_property (oclass: object_info->oclass, property_name: name); |
994 | |
995 | if (!pspec) |
996 | { |
997 | g_set_error (err: error, |
998 | GTK_BUILDER_ERROR, |
999 | code: GTK_BUILDER_ERROR_INVALID_PROPERTY, |
1000 | format: "Invalid property: %s.%s" , |
1001 | g_type_name (type: object_info->type), name); |
1002 | _gtk_builder_prefix_error (builder: data->builder, context: &data->ctx, error); |
1003 | return; |
1004 | } |
1005 | else if (pspec->flags & G_PARAM_CONSTRUCT_ONLY) |
1006 | { |
1007 | g_set_error (err: error, |
1008 | GTK_BUILDER_ERROR, |
1009 | code: GTK_BUILDER_ERROR_INVALID_PROPERTY, |
1010 | format: "%s.%s is a construct-only property" , |
1011 | g_type_name (type: object_info->type), name); |
1012 | _gtk_builder_prefix_error (builder: data->builder, context: &data->ctx, error); |
1013 | return; |
1014 | } |
1015 | else if (!(pspec->flags & G_PARAM_WRITABLE)) |
1016 | { |
1017 | g_set_error (err: error, |
1018 | GTK_BUILDER_ERROR, |
1019 | code: GTK_BUILDER_ERROR_INVALID_PROPERTY, |
1020 | format: "%s.%s is a non-writable property" , |
1021 | g_type_name (type: object_info->type), name); |
1022 | _gtk_builder_prefix_error (builder: data->builder, context: &data->ctx, error); |
1023 | return; |
1024 | } |
1025 | |
1026 | |
1027 | info = g_slice_new0 (BindingExpressionInfo); |
1028 | info->tag_type = TAG_BINDING_EXPRESSION; |
1029 | info->target = NULL; |
1030 | info->target_pspec = pspec; |
1031 | info->object_name = g_strdup (str: object_name); |
1032 | gtk_buildable_parse_context_get_position (context: &data->ctx, line_number: &info->line, char_number: &info->col); |
1033 | |
1034 | state_push (data, info); |
1035 | } |
1036 | |
1037 | static void |
1038 | free_property_info (PropertyInfo *info) |
1039 | { |
1040 | if (info->value) |
1041 | { |
1042 | if (G_PARAM_SPEC_VALUE_TYPE (info->pspec) == GTK_TYPE_EXPRESSION) |
1043 | gtk_expression_unref (self: info->value); |
1044 | else |
1045 | g_assert_not_reached(); |
1046 | } |
1047 | g_string_free (string: info->text, TRUE); |
1048 | g_free (mem: info->context); |
1049 | g_slice_free (PropertyInfo, info); |
1050 | } |
1051 | |
1052 | static void |
1053 | free_expression_info (ExpressionInfo *info) |
1054 | { |
1055 | switch (info->expression_type) |
1056 | { |
1057 | case EXPRESSION_EXPRESSION: |
1058 | g_clear_pointer (&info->expression, gtk_expression_unref); |
1059 | break; |
1060 | |
1061 | case EXPRESSION_CONSTANT: |
1062 | g_string_free (string: info->constant.text, TRUE); |
1063 | break; |
1064 | |
1065 | case EXPRESSION_CLOSURE: |
1066 | g_free (mem: info->closure.function_name); |
1067 | g_free (mem: info->closure.object_name); |
1068 | g_slist_free_full (list: info->closure.params, free_func: (GDestroyNotify) free_expression_info); |
1069 | break; |
1070 | |
1071 | case EXPRESSION_PROPERTY: |
1072 | g_clear_pointer (&info->property.expression, free_expression_info); |
1073 | g_free (mem: info->property.property_name); |
1074 | break; |
1075 | |
1076 | default: |
1077 | g_assert_not_reached (); |
1078 | break; |
1079 | } |
1080 | g_slice_free (ExpressionInfo, info); |
1081 | } |
1082 | |
1083 | static gboolean |
1084 | check_expression_parent (ParserData *data) |
1085 | { |
1086 | CommonInfo *common_info = state_peek_info (data, CommonInfo); |
1087 | |
1088 | if (common_info == NULL) |
1089 | return FALSE; |
1090 | |
1091 | if (common_info->tag_type == TAG_PROPERTY) |
1092 | { |
1093 | PropertyInfo *prop_info = (PropertyInfo *) common_info; |
1094 | |
1095 | return G_PARAM_SPEC_VALUE_TYPE (prop_info->pspec) == GTK_TYPE_EXPRESSION; |
1096 | } |
1097 | else if (common_info->tag_type == TAG_BINDING_EXPRESSION) |
1098 | { |
1099 | BindingExpressionInfo *expr_info = (BindingExpressionInfo *) common_info; |
1100 | |
1101 | return expr_info->expr == NULL; |
1102 | } |
1103 | else if (common_info->tag_type == TAG_EXPRESSION) |
1104 | { |
1105 | ExpressionInfo *expr_info = (ExpressionInfo *) common_info; |
1106 | |
1107 | switch (expr_info->expression_type) |
1108 | { |
1109 | case EXPRESSION_CLOSURE: |
1110 | return TRUE; |
1111 | case EXPRESSION_CONSTANT: |
1112 | return FALSE; |
1113 | case EXPRESSION_PROPERTY: |
1114 | return expr_info->property.expression == NULL; |
1115 | case EXPRESSION_EXPRESSION: |
1116 | default: |
1117 | g_assert_not_reached (); |
1118 | return FALSE; |
1119 | } |
1120 | } |
1121 | |
1122 | return FALSE; |
1123 | } |
1124 | |
1125 | static void |
1126 | parse_constant_expression (ParserData *data, |
1127 | const char *element_name, |
1128 | const char **names, |
1129 | const char **values, |
1130 | GError **error) |
1131 | { |
1132 | ExpressionInfo *info; |
1133 | const char *type_name = NULL; |
1134 | GType type; |
1135 | |
1136 | if (!check_expression_parent (data)) |
1137 | { |
1138 | error_invalid_tag (data, tag: element_name, NULL, error); |
1139 | return; |
1140 | } |
1141 | |
1142 | if (!g_markup_collect_attributes (element_name, attribute_names: names, attribute_values: values, error, |
1143 | first_type: G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, first_attr: "type" , &type_name, |
1144 | G_MARKUP_COLLECT_INVALID)) |
1145 | { |
1146 | _gtk_builder_prefix_error (builder: data->builder, context: &data->ctx, error); |
1147 | return; |
1148 | } |
1149 | |
1150 | if (type_name == NULL) |
1151 | type = G_TYPE_INVALID; |
1152 | else |
1153 | { |
1154 | type = gtk_builder_get_type_from_name (builder: data->builder, type_name); |
1155 | if (type == G_TYPE_INVALID) |
1156 | { |
1157 | g_set_error (err: error, |
1158 | GTK_BUILDER_ERROR, |
1159 | code: GTK_BUILDER_ERROR_INVALID_VALUE, |
1160 | format: "Invalid type '%s'" , type_name); |
1161 | _gtk_builder_prefix_error (builder: data->builder, context: &data->ctx, error); |
1162 | return; |
1163 | } |
1164 | } |
1165 | |
1166 | info = g_slice_new0 (ExpressionInfo); |
1167 | info->tag_type = TAG_EXPRESSION; |
1168 | info->expression_type = EXPRESSION_CONSTANT; |
1169 | info->constant.type = type; |
1170 | info->constant.text = g_string_new (NULL); |
1171 | |
1172 | state_push (data, info); |
1173 | } |
1174 | |
1175 | static void |
1176 | parse_closure_expression (ParserData *data, |
1177 | const char *element_name, |
1178 | const char **names, |
1179 | const char **values, |
1180 | GError **error) |
1181 | { |
1182 | ExpressionInfo *info; |
1183 | const char *type_name; |
1184 | const char *function_name; |
1185 | const char *object_name = NULL; |
1186 | gboolean swapped = -1; |
1187 | GType type; |
1188 | |
1189 | if (!check_expression_parent (data)) |
1190 | { |
1191 | error_invalid_tag (data, tag: element_name, NULL, error); |
1192 | return; |
1193 | } |
1194 | |
1195 | if (!g_markup_collect_attributes (element_name, attribute_names: names, attribute_values: values, error, |
1196 | first_type: G_MARKUP_COLLECT_STRING, first_attr: "type" , &type_name, |
1197 | G_MARKUP_COLLECT_STRING, "function" , &function_name, |
1198 | G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "object" , &object_name, |
1199 | G_MARKUP_COLLECT_TRISTATE|G_MARKUP_COLLECT_OPTIONAL, "swapped" , &swapped, |
1200 | G_MARKUP_COLLECT_INVALID)) |
1201 | { |
1202 | _gtk_builder_prefix_error (builder: data->builder, context: &data->ctx, error); |
1203 | return; |
1204 | } |
1205 | |
1206 | type = gtk_builder_get_type_from_name (builder: data->builder, type_name); |
1207 | if (type == G_TYPE_INVALID) |
1208 | { |
1209 | g_set_error (err: error, |
1210 | GTK_BUILDER_ERROR, |
1211 | code: GTK_BUILDER_ERROR_INVALID_VALUE, |
1212 | format: "Invalid type '%s'" , type_name); |
1213 | _gtk_builder_prefix_error (builder: data->builder, context: &data->ctx, error); |
1214 | return; |
1215 | } |
1216 | |
1217 | /* Swapped defaults to FALSE except when object is set */ |
1218 | if (swapped == -1) |
1219 | { |
1220 | if (object_name) |
1221 | swapped = TRUE; |
1222 | else |
1223 | swapped = FALSE; |
1224 | } |
1225 | |
1226 | info = g_slice_new0 (ExpressionInfo); |
1227 | info->tag_type = TAG_EXPRESSION; |
1228 | info->expression_type = EXPRESSION_CLOSURE; |
1229 | info->closure.type = type; |
1230 | info->closure.swapped = swapped; |
1231 | info->closure.function_name = g_strdup (str: function_name); |
1232 | info->closure.object_name = g_strdup (str: object_name); |
1233 | |
1234 | state_push (data, info); |
1235 | } |
1236 | |
1237 | static void |
1238 | parse_lookup_expression (ParserData *data, |
1239 | const char *element_name, |
1240 | const char **names, |
1241 | const char **values, |
1242 | GError **error) |
1243 | { |
1244 | ExpressionInfo *info; |
1245 | const char *property_name; |
1246 | const char *type_name = NULL; |
1247 | GType type; |
1248 | |
1249 | if (!check_expression_parent (data)) |
1250 | { |
1251 | error_invalid_tag (data, tag: element_name, NULL, error); |
1252 | return; |
1253 | } |
1254 | |
1255 | if (!g_markup_collect_attributes (element_name, attribute_names: names, attribute_values: values, error, |
1256 | first_type: G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, first_attr: "type" , &type_name, |
1257 | G_MARKUP_COLLECT_STRING, "name" , &property_name, |
1258 | G_MARKUP_COLLECT_INVALID)) |
1259 | { |
1260 | _gtk_builder_prefix_error (builder: data->builder, context: &data->ctx, error); |
1261 | return; |
1262 | } |
1263 | |
1264 | if (type_name == NULL) |
1265 | { |
1266 | type = G_TYPE_INVALID; |
1267 | } |
1268 | else |
1269 | { |
1270 | type = gtk_builder_get_type_from_name (builder: data->builder, type_name); |
1271 | if (type == G_TYPE_INVALID) |
1272 | { |
1273 | g_set_error (err: error, |
1274 | GTK_BUILDER_ERROR, |
1275 | code: GTK_BUILDER_ERROR_INVALID_VALUE, |
1276 | format: "Invalid type '%s'" , type_name); |
1277 | _gtk_builder_prefix_error (builder: data->builder, context: &data->ctx, error); |
1278 | return; |
1279 | } |
1280 | } |
1281 | |
1282 | info = g_slice_new0 (ExpressionInfo); |
1283 | info->tag_type = TAG_EXPRESSION; |
1284 | info->expression_type = EXPRESSION_PROPERTY; |
1285 | info->property.this_type = type; |
1286 | info->property.property_name = g_strdup (str: property_name); |
1287 | |
1288 | state_push (data, info); |
1289 | } |
1290 | |
1291 | GtkExpression * |
1292 | expression_info_construct (GtkBuilder *builder, |
1293 | ExpressionInfo *info, |
1294 | GError **error) |
1295 | { |
1296 | switch (info->expression_type) |
1297 | { |
1298 | case EXPRESSION_EXPRESSION: |
1299 | break; |
1300 | |
1301 | case EXPRESSION_CONSTANT: |
1302 | { |
1303 | GtkExpression *expr; |
1304 | |
1305 | if (info->constant.type == G_TYPE_INVALID) |
1306 | { |
1307 | GObject *o = gtk_builder_lookup_object (builder, name: info->constant.text->str, line: 0, col: 0, error); |
1308 | if (o == NULL) |
1309 | return NULL; |
1310 | |
1311 | expr = gtk_object_expression_new (object: o); |
1312 | } |
1313 | else |
1314 | { |
1315 | GValue value = G_VALUE_INIT; |
1316 | |
1317 | if (!gtk_builder_value_from_string_type (builder, |
1318 | type: info->constant.type, |
1319 | string: info->constant.text->str, |
1320 | value: &value, |
1321 | error)) |
1322 | return NULL; |
1323 | |
1324 | if (G_VALUE_HOLDS_OBJECT (&value)) |
1325 | expr = gtk_object_expression_new (object: g_value_get_object (value: &value)); |
1326 | else |
1327 | expr = gtk_constant_expression_new_for_value (value: &value); |
1328 | |
1329 | g_value_unset (value: &value); |
1330 | } |
1331 | |
1332 | g_string_free (string: info->constant.text, TRUE); |
1333 | info->expression_type = EXPRESSION_EXPRESSION; |
1334 | info->expression = expr; |
1335 | } |
1336 | break; |
1337 | |
1338 | case EXPRESSION_CLOSURE: |
1339 | { |
1340 | GObject *object; |
1341 | GClosure *closure; |
1342 | guint i, n_params; |
1343 | GtkExpression **params; |
1344 | GtkExpression *expression; |
1345 | GSList *l; |
1346 | |
1347 | if (info->closure.object_name) |
1348 | { |
1349 | object = gtk_builder_lookup_object (builder, name: info->closure.object_name, line: 0, col: 0, error); |
1350 | if (object == NULL) |
1351 | return NULL; |
1352 | } |
1353 | else |
1354 | { |
1355 | object = NULL; |
1356 | } |
1357 | |
1358 | closure = gtk_builder_create_closure (builder, |
1359 | function_name: info->closure.function_name, |
1360 | flags: info->closure.swapped, |
1361 | object, |
1362 | error); |
1363 | if (closure == NULL) |
1364 | return NULL; |
1365 | n_params = g_slist_length (list: info->closure.params); |
1366 | params = g_newa (GtkExpression *, n_params); |
1367 | i = n_params; |
1368 | for (l = info->closure.params; l; l = l->next) |
1369 | { |
1370 | params[--i] = expression_info_construct (builder, info: l->data, error); |
1371 | if (params[i] == NULL) |
1372 | return NULL; |
1373 | } |
1374 | expression = gtk_closure_expression_new (value_type: info->closure.type, closure, n_params, params); |
1375 | g_free (mem: info->closure.function_name); |
1376 | g_free (mem: info->closure.object_name); |
1377 | g_slist_free_full (list: info->closure.params, free_func: (GDestroyNotify) free_expression_info); |
1378 | info->expression_type = EXPRESSION_EXPRESSION; |
1379 | info->expression = expression; |
1380 | } |
1381 | break; |
1382 | |
1383 | case EXPRESSION_PROPERTY: |
1384 | { |
1385 | GtkExpression *expression; |
1386 | GType type; |
1387 | GParamSpec *pspec; |
1388 | |
1389 | if (info->property.expression) |
1390 | { |
1391 | expression = expression_info_construct (builder, info: info->property.expression, error); |
1392 | if (expression == NULL) |
1393 | return NULL; |
1394 | g_clear_pointer (&info->property.expression, free_expression_info); |
1395 | } |
1396 | else |
1397 | expression = NULL; |
1398 | |
1399 | if (info->property.this_type != G_TYPE_INVALID) |
1400 | type = info->property.this_type; |
1401 | else if (expression != NULL) |
1402 | type = gtk_expression_get_value_type (self: expression); |
1403 | else |
1404 | { |
1405 | g_set_error (err: error, |
1406 | GTK_BUILDER_ERROR, |
1407 | code: GTK_BUILDER_ERROR_MISSING_ATTRIBUTE, |
1408 | format: "Lookups require a type attribute if they don't have an expression." ); |
1409 | return NULL; |
1410 | } |
1411 | |
1412 | if (g_type_is_a (type, G_TYPE_OBJECT)) |
1413 | { |
1414 | GObjectClass *class = g_type_class_ref (type); |
1415 | pspec = g_object_class_find_property (oclass: class, property_name: info->property.property_name); |
1416 | g_type_class_unref (g_class: class); |
1417 | } |
1418 | else if (g_type_is_a (type, G_TYPE_INTERFACE)) |
1419 | { |
1420 | GTypeInterface *iface = g_type_default_interface_ref (g_type: type); |
1421 | pspec = g_object_interface_find_property (g_iface: iface, property_name: info->property.property_name); |
1422 | g_type_default_interface_unref (g_iface: iface); |
1423 | } |
1424 | else |
1425 | { |
1426 | g_set_error (err: error, |
1427 | GTK_BUILDER_ERROR, |
1428 | code: GTK_BUILDER_ERROR_MISSING_ATTRIBUTE, |
1429 | format: "Type `%s` does not support properties" , |
1430 | g_type_name (type)); |
1431 | return NULL; |
1432 | } |
1433 | |
1434 | if (pspec == NULL) |
1435 | { |
1436 | g_set_error (err: error, |
1437 | GTK_BUILDER_ERROR, |
1438 | code: GTK_BUILDER_ERROR_MISSING_ATTRIBUTE, |
1439 | format: "Type `%s` does not have a property name `%s`" , |
1440 | g_type_name (type), info->property.property_name); |
1441 | return NULL; |
1442 | } |
1443 | |
1444 | expression = gtk_property_expression_new_for_pspec (expression, pspec); |
1445 | |
1446 | g_free (mem: info->property.property_name); |
1447 | info->expression_type = EXPRESSION_EXPRESSION; |
1448 | info->expression = expression; |
1449 | } |
1450 | break; |
1451 | |
1452 | default: |
1453 | g_return_val_if_reached (NULL); |
1454 | } |
1455 | |
1456 | return gtk_expression_ref (self: info->expression); |
1457 | } |
1458 | |
1459 | static void |
1460 | parse_signal (ParserData *data, |
1461 | const char *element_name, |
1462 | const char **names, |
1463 | const char **values, |
1464 | GError **error) |
1465 | { |
1466 | SignalInfo *info; |
1467 | const char *name; |
1468 | const char *handler = NULL; |
1469 | const char *object = NULL; |
1470 | gboolean after = FALSE; |
1471 | gboolean swapped = -1; |
1472 | ObjectInfo *object_info; |
1473 | guint id = 0; |
1474 | GQuark detail = 0; |
1475 | |
1476 | object_info = state_peek_info (data, ObjectInfo); |
1477 | if (!object_info || |
1478 | !(object_info->tag_type == TAG_OBJECT|| |
1479 | object_info->tag_type == TAG_TEMPLATE)) |
1480 | { |
1481 | error_invalid_tag (data, tag: element_name, NULL, error); |
1482 | return; |
1483 | } |
1484 | |
1485 | if (!g_markup_collect_attributes (element_name, attribute_names: names, attribute_values: values, error, |
1486 | first_type: G_MARKUP_COLLECT_STRING, first_attr: "name" , &name, |
1487 | G_MARKUP_COLLECT_STRING, "handler" , &handler, |
1488 | G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "object" , &object, |
1489 | G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "last_modification_time" , NULL, |
1490 | G_MARKUP_COLLECT_BOOLEAN|G_MARKUP_COLLECT_OPTIONAL, "after" , &after, |
1491 | G_MARKUP_COLLECT_TRISTATE|G_MARKUP_COLLECT_OPTIONAL, "swapped" , &swapped, |
1492 | G_MARKUP_COLLECT_INVALID)) |
1493 | { |
1494 | _gtk_builder_prefix_error (builder: data->builder, context: &data->ctx, error); |
1495 | return; |
1496 | } |
1497 | |
1498 | if (!g_signal_parse_name (detailed_signal: name, itype: object_info->type, signal_id_p: &id, detail_p: &detail, FALSE)) |
1499 | { |
1500 | g_set_error (err: error, |
1501 | GTK_BUILDER_ERROR, |
1502 | code: GTK_BUILDER_ERROR_INVALID_SIGNAL, |
1503 | format: "Invalid signal '%s' for type '%s'" , |
1504 | name, g_type_name (type: object_info->type)); |
1505 | _gtk_builder_prefix_error (builder: data->builder, context: &data->ctx, error); |
1506 | return; |
1507 | } |
1508 | |
1509 | /* Swapped defaults to FALSE except when object is set */ |
1510 | if (swapped == -1) |
1511 | { |
1512 | if (object) |
1513 | swapped = TRUE; |
1514 | else |
1515 | swapped = FALSE; |
1516 | } |
1517 | |
1518 | info = g_slice_new0 (SignalInfo); |
1519 | info->id = id; |
1520 | info->detail = detail; |
1521 | info->handler = g_strdup (str: handler); |
1522 | if (after) |
1523 | info->flags |= G_CONNECT_AFTER; |
1524 | if (swapped) |
1525 | info->flags |= G_CONNECT_SWAPPED; |
1526 | info->connect_object_name = g_strdup (str: object); |
1527 | state_push (data, info); |
1528 | |
1529 | info->tag_type = TAG_SIGNAL; |
1530 | } |
1531 | |
1532 | /* Called by GtkBuilder */ |
1533 | void |
1534 | _free_signal_info (SignalInfo *info, |
1535 | gpointer user_data) |
1536 | { |
1537 | g_free (mem: info->handler); |
1538 | g_free (mem: info->connect_object_name); |
1539 | g_free (mem: info->object_name); |
1540 | g_slice_free (SignalInfo, info); |
1541 | } |
1542 | |
1543 | void |
1544 | _free_binding_info (BindingInfo *info, |
1545 | gpointer user) |
1546 | { |
1547 | g_free (mem: info->source); |
1548 | g_free (mem: info->source_property); |
1549 | g_slice_free (BindingInfo, info); |
1550 | } |
1551 | |
1552 | void |
1553 | free_binding_expression_info (BindingExpressionInfo *info) |
1554 | { |
1555 | if (info->expr) |
1556 | free_expression_info (info: info->expr); |
1557 | g_free (mem: info->object_name); |
1558 | g_slice_free (BindingExpressionInfo, info); |
1559 | } |
1560 | |
1561 | static void |
1562 | free_requires_info (RequiresInfo *info, |
1563 | gpointer user_data) |
1564 | { |
1565 | g_free (mem: info->library); |
1566 | g_slice_free (RequiresInfo, info); |
1567 | } |
1568 | |
1569 | static void |
1570 | parse_interface (ParserData *data, |
1571 | const char *element_name, |
1572 | const char **names, |
1573 | const char **values, |
1574 | GError **error) |
1575 | { |
1576 | const char *domain = NULL; |
1577 | |
1578 | if (!g_markup_collect_attributes (element_name, attribute_names: names, attribute_values: values, error, |
1579 | first_type: G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, first_attr: "domain" , &domain, |
1580 | G_MARKUP_COLLECT_INVALID)) |
1581 | { |
1582 | _gtk_builder_prefix_error (builder: data->builder, context: &data->ctx, error); |
1583 | return; |
1584 | } |
1585 | |
1586 | if (domain) |
1587 | { |
1588 | if (data->domain && strcmp (s1: data->domain, s2: domain) != 0) |
1589 | { |
1590 | g_warning ("%s: interface domain '%s' overrides programmatic value '%s'" , |
1591 | data->filename, domain, data->domain); |
1592 | g_free (mem: data->domain); |
1593 | } |
1594 | |
1595 | data->domain = g_strdup (str: domain); |
1596 | gtk_builder_set_translation_domain (builder: data->builder, domain: data->domain); |
1597 | } |
1598 | } |
1599 | |
1600 | static SubParser * |
1601 | create_subparser (GObject *object, |
1602 | GObject *child, |
1603 | const char *element_name, |
1604 | GtkBuildableParser *parser, |
1605 | gpointer user_data) |
1606 | { |
1607 | SubParser *subparser; |
1608 | |
1609 | subparser = g_slice_new0 (SubParser); |
1610 | subparser->object = object; |
1611 | subparser->child = child; |
1612 | subparser->tagname = g_strdup (str: element_name); |
1613 | subparser->start = element_name; |
1614 | subparser->parser = g_memdup2 (mem: parser, byte_size: sizeof (GtkBuildableParser)); |
1615 | subparser->data = user_data; |
1616 | |
1617 | return subparser; |
1618 | } |
1619 | |
1620 | static void |
1621 | free_subparser (SubParser *subparser) |
1622 | { |
1623 | g_free (mem: subparser->tagname); |
1624 | g_slice_free (SubParser, subparser); |
1625 | } |
1626 | |
1627 | static gboolean |
1628 | subparser_start (GtkBuildableParseContext *context, |
1629 | const char *element_name, |
1630 | const char **names, |
1631 | const char **values, |
1632 | ParserData *data, |
1633 | GError **error) |
1634 | { |
1635 | SubParser *subparser = data->subparser; |
1636 | |
1637 | if (!subparser->start && |
1638 | strcmp (s1: element_name, s2: subparser->tagname) == 0) |
1639 | subparser->start = element_name; |
1640 | |
1641 | if (subparser->start) |
1642 | { |
1643 | if (subparser->parser->start_element) |
1644 | subparser->parser->start_element (context, |
1645 | element_name, names, values, |
1646 | subparser->data, error); |
1647 | return FALSE; |
1648 | } |
1649 | return TRUE; |
1650 | } |
1651 | |
1652 | static void |
1653 | subparser_end (GtkBuildableParseContext *context, |
1654 | const char *element_name, |
1655 | ParserData *data, |
1656 | GError **error) |
1657 | { |
1658 | if (data->subparser->parser->end_element) |
1659 | data->subparser->parser->end_element (context, element_name, |
1660 | data->subparser->data, error); |
1661 | |
1662 | if (*error) |
1663 | return; |
1664 | |
1665 | if (strcmp (s1: data->subparser->start, s2: element_name) != 0) |
1666 | return; |
1667 | |
1668 | gtk_buildable_custom_tag_end (GTK_BUILDABLE (data->subparser->object), |
1669 | builder: data->builder, |
1670 | child: data->subparser->child, |
1671 | tagname: element_name, |
1672 | data: data->subparser->data); |
1673 | g_free (mem: data->subparser->parser); |
1674 | |
1675 | if (_gtk_builder_lookup_failed (builder: data->builder, error)) |
1676 | return; |
1677 | |
1678 | if (GTK_BUILDABLE_GET_IFACE (data->subparser->object)->custom_finished) |
1679 | data->custom_finalizers = g_slist_prepend (list: data->custom_finalizers, |
1680 | data: data->subparser); |
1681 | else |
1682 | free_subparser (subparser: data->subparser); |
1683 | |
1684 | data->subparser = NULL; |
1685 | } |
1686 | |
1687 | static gboolean |
1688 | parse_custom (GtkBuildableParseContext *context, |
1689 | const char *element_name, |
1690 | const char **names, |
1691 | const char **values, |
1692 | ParserData *data, |
1693 | GError **error) |
1694 | { |
1695 | CommonInfo* parent_info; |
1696 | GtkBuildableParser parser; |
1697 | gpointer subparser_data; |
1698 | GObject *object; |
1699 | GObject *child; |
1700 | |
1701 | parent_info = state_peek_info (data, CommonInfo); |
1702 | if (!parent_info) |
1703 | return FALSE; |
1704 | |
1705 | if (parent_info->tag_type == TAG_OBJECT || |
1706 | parent_info->tag_type == TAG_TEMPLATE) |
1707 | { |
1708 | ObjectInfo* object_info = (ObjectInfo*)parent_info; |
1709 | if (!object_info->object) |
1710 | { |
1711 | object_info->object = builder_construct (data, object_info, error); |
1712 | if (!object_info->object) |
1713 | return TRUE; /* A GError is already set */ |
1714 | } |
1715 | g_assert (object_info->object); |
1716 | object = object_info->object; |
1717 | child = NULL; |
1718 | } |
1719 | else if (parent_info->tag_type == TAG_CHILD) |
1720 | { |
1721 | ChildInfo* child_info = (ChildInfo*)parent_info; |
1722 | |
1723 | _gtk_builder_add (builder: data->builder, child_info); |
1724 | |
1725 | object = ((ObjectInfo*)child_info->parent)->object; |
1726 | child = child_info->object; |
1727 | } |
1728 | else |
1729 | return FALSE; |
1730 | |
1731 | if (!gtk_buildable_custom_tag_start (GTK_BUILDABLE (object), |
1732 | builder: data->builder, |
1733 | child, |
1734 | tagname: element_name, |
1735 | parser: &parser, |
1736 | data: &subparser_data)) |
1737 | return FALSE; |
1738 | |
1739 | data->subparser = create_subparser (object, child, element_name, |
1740 | parser: &parser, user_data: subparser_data); |
1741 | |
1742 | if (parser.start_element) |
1743 | parser.start_element (context, |
1744 | element_name, names, values, |
1745 | subparser_data, error); |
1746 | return TRUE; |
1747 | } |
1748 | |
1749 | static void |
1750 | start_element (GtkBuildableParseContext *context, |
1751 | const char *element_name, |
1752 | const char **names, |
1753 | const char **values, |
1754 | gpointer user_data, |
1755 | GError **error) |
1756 | { |
1757 | ParserData *data = (ParserData*)user_data; |
1758 | |
1759 | #ifdef G_ENABLE_DEBUG |
1760 | if (GTK_DEBUG_CHECK (BUILDER)) |
1761 | { |
1762 | GString *tags = g_string_new (init: "" ); |
1763 | int i; |
1764 | for (i = 0; names[i]; i++) |
1765 | g_string_append_printf (string: tags, format: "%s=\"%s\" " , names[i], values[i]); |
1766 | |
1767 | if (i) |
1768 | { |
1769 | g_string_insert_c (string: tags, pos: 0, c: ' '); |
1770 | g_string_truncate (string: tags, len: tags->len - 1); |
1771 | } |
1772 | g_message ("<%s%s>" , element_name, tags->str); |
1773 | g_string_free (string: tags, TRUE); |
1774 | } |
1775 | #endif |
1776 | |
1777 | if (!data->last_element && strcmp (s1: element_name, s2: "interface" ) != 0) |
1778 | { |
1779 | error_unhandled_tag (data, tag: element_name, error); |
1780 | return; |
1781 | } |
1782 | data->last_element = element_name; |
1783 | |
1784 | if (data->subparser) |
1785 | { |
1786 | if (!subparser_start (context, element_name, names, values, data, error)) |
1787 | return; |
1788 | } |
1789 | |
1790 | if (strcmp (s1: element_name, s2: "object" ) == 0) |
1791 | parse_object (context, data, element_name, names, values, error); |
1792 | else if (data->requested_objects && !data->inside_requested_object) |
1793 | { |
1794 | /* If outside a requested object, simply ignore this tag */ |
1795 | } |
1796 | else if (strcmp (s1: element_name, s2: "property" ) == 0) |
1797 | parse_property (data, element_name, names, values, error); |
1798 | else if (strcmp (s1: element_name, s2: "binding" ) == 0) |
1799 | parse_binding (data, element_name, names, values, error); |
1800 | else if (strcmp (s1: element_name, s2: "child" ) == 0) |
1801 | parse_child (data, element_name, names, values, error); |
1802 | else if (strcmp (s1: element_name, s2: "signal" ) == 0) |
1803 | parse_signal (data, element_name, names, values, error); |
1804 | else if (strcmp (s1: element_name, s2: "template" ) == 0) |
1805 | parse_template (context, data, element_name, names, values, error); |
1806 | else if (strcmp (s1: element_name, s2: "requires" ) == 0) |
1807 | parse_requires (data, element_name, names, values, error); |
1808 | else if (strcmp (s1: element_name, s2: "interface" ) == 0) |
1809 | parse_interface (data, element_name, names, values, error); |
1810 | else if (strcmp (s1: element_name, s2: "constant" ) == 0) |
1811 | parse_constant_expression (data, element_name, names, values, error); |
1812 | else if (strcmp (s1: element_name, s2: "closure" ) == 0) |
1813 | parse_closure_expression (data, element_name, names, values, error); |
1814 | else if (strcmp (s1: element_name, s2: "lookup" ) == 0) |
1815 | parse_lookup_expression (data, element_name, names, values, error); |
1816 | else if (strcmp (s1: element_name, s2: "menu" ) == 0) |
1817 | _gtk_builder_menu_start (parser_data: data, element_name, attribute_names: names, attribute_values: values, error); |
1818 | else if (strcmp (s1: element_name, s2: "placeholder" ) == 0) |
1819 | { |
1820 | /* placeholder has no special treatmeant, but it needs an |
1821 | * if clause to avoid an error below. |
1822 | */ |
1823 | } |
1824 | else if (!parse_custom (context, element_name, names, values, data, error)) |
1825 | error_unhandled_tag (data, tag: element_name, error); |
1826 | } |
1827 | |
1828 | const char * |
1829 | _gtk_builder_parser_translate (const char *domain, |
1830 | const char *context, |
1831 | const char *text) |
1832 | { |
1833 | const char *s; |
1834 | |
1835 | if (context) |
1836 | s = g_dpgettext2 (domain, context, msgid: text); |
1837 | else |
1838 | s = g_dgettext (domain, msgid: text); |
1839 | |
1840 | return s; |
1841 | } |
1842 | |
1843 | static void |
1844 | end_element (GtkBuildableParseContext *context, |
1845 | const char *element_name, |
1846 | gpointer user_data, |
1847 | GError **error) |
1848 | { |
1849 | ParserData *data = (ParserData*)user_data; |
1850 | |
1851 | GTK_NOTE (BUILDER, g_message ("</%s>" , element_name)); |
1852 | |
1853 | if (data->subparser && data->subparser->start) |
1854 | { |
1855 | subparser_end (context, element_name, data, error); |
1856 | return; |
1857 | } |
1858 | |
1859 | if (data->requested_objects && !data->inside_requested_object) |
1860 | { |
1861 | /* If outside a requested object, simply ignore this tag */ |
1862 | } |
1863 | else if (strcmp (s1: element_name, s2: "property" ) == 0) |
1864 | { |
1865 | PropertyInfo *prop_info = state_pop_info (data, PropertyInfo); |
1866 | CommonInfo *info = state_peek_info (data, CommonInfo); |
1867 | |
1868 | g_assert (info != NULL); |
1869 | |
1870 | /* Normal properties */ |
1871 | if (info->tag_type == TAG_OBJECT || |
1872 | info->tag_type == TAG_TEMPLATE) |
1873 | { |
1874 | ObjectInfo *object_info = (ObjectInfo*)info; |
1875 | |
1876 | if (prop_info->translatable && prop_info->text->len) |
1877 | { |
1878 | const char *translated; |
1879 | |
1880 | translated = _gtk_builder_parser_translate (domain: data->domain, |
1881 | context: prop_info->context, |
1882 | text: prop_info->text->str); |
1883 | g_string_assign (string: prop_info->text, rval: translated); |
1884 | } |
1885 | |
1886 | if (G_UNLIKELY (!object_info->properties)) |
1887 | object_info->properties = g_ptr_array_new_with_free_func (element_free_func: (GDestroyNotify)free_property_info); |
1888 | |
1889 | g_ptr_array_add (array: object_info->properties, data: prop_info); |
1890 | } |
1891 | else |
1892 | g_assert_not_reached (); |
1893 | } |
1894 | else if (strcmp (s1: element_name, s2: "binding" ) == 0) |
1895 | { |
1896 | BindingExpressionInfo *binfo = state_pop_info (data, BindingExpressionInfo); |
1897 | CommonInfo *info = state_peek_info (data, CommonInfo); |
1898 | |
1899 | g_assert (info != NULL); |
1900 | |
1901 | if (binfo->expr == NULL) |
1902 | { |
1903 | g_set_error (err: error, |
1904 | GTK_BUILDER_ERROR, |
1905 | code: GTK_BUILDER_ERROR_INVALID_TAG, |
1906 | format: "Binding tag requires an expression" ); |
1907 | free_binding_expression_info (info: binfo); |
1908 | } |
1909 | else if (info->tag_type == TAG_OBJECT || |
1910 | info->tag_type == TAG_TEMPLATE) |
1911 | { |
1912 | ObjectInfo *object_info = (ObjectInfo*)info; |
1913 | object_info->bindings = g_slist_prepend (list: object_info->bindings, data: binfo); |
1914 | } |
1915 | else |
1916 | g_assert_not_reached (); |
1917 | } |
1918 | else if (strcmp (s1: element_name, s2: "object" ) == 0 || |
1919 | strcmp (s1: element_name, s2: "template" ) == 0) |
1920 | { |
1921 | ObjectInfo *object_info = state_pop_info (data, ObjectInfo); |
1922 | ChildInfo* child_info = state_peek_info (data, ChildInfo); |
1923 | PropertyInfo* prop_info = state_peek_info (data, PropertyInfo); |
1924 | |
1925 | if (child_info && child_info->tag_type != TAG_CHILD) |
1926 | child_info = NULL; |
1927 | if (prop_info && prop_info->tag_type != TAG_PROPERTY) |
1928 | prop_info = NULL; |
1929 | |
1930 | if (data->requested_objects && data->inside_requested_object && |
1931 | (data->cur_object_level == data->requested_object_level)) |
1932 | { |
1933 | GTK_NOTE (BUILDER, |
1934 | g_message ("requested object end found at level %d" , |
1935 | data->requested_object_level)); |
1936 | |
1937 | data->inside_requested_object = FALSE; |
1938 | } |
1939 | |
1940 | --data->cur_object_level; |
1941 | |
1942 | g_assert (data->cur_object_level >= 0); |
1943 | |
1944 | object_info->object = builder_construct (data, object_info, error); |
1945 | if (!object_info->object) |
1946 | { |
1947 | free_object_info (info: object_info); |
1948 | return; |
1949 | } |
1950 | if (child_info) |
1951 | child_info->object = object_info->object; |
1952 | if (prop_info) |
1953 | g_string_assign (string: prop_info->text, rval: object_info->id); |
1954 | |
1955 | if (GTK_IS_BUILDABLE (object_info->object) && |
1956 | GTK_BUILDABLE_GET_IFACE (object_info->object)->parser_finished) |
1957 | g_ptr_array_add (array: data->finalizers, data: object_info->object); |
1958 | |
1959 | if (object_info->signals) |
1960 | { |
1961 | _gtk_builder_add_signals (builder: data->builder, signals: object_info->signals); |
1962 | object_info->signals = NULL; |
1963 | } |
1964 | |
1965 | if (object_info->bindings) |
1966 | { |
1967 | gtk_builder_take_bindings (builder: data->builder, target: object_info->object, bindings: object_info->bindings); |
1968 | object_info->bindings = NULL; |
1969 | } |
1970 | |
1971 | free_object_info (info: object_info); |
1972 | } |
1973 | else if (strcmp (s1: element_name, s2: "child" ) == 0) |
1974 | { |
1975 | ChildInfo *child_info = state_pop_info (data, ChildInfo); |
1976 | |
1977 | _gtk_builder_add (builder: data->builder, child_info); |
1978 | |
1979 | free_child_info (info: child_info); |
1980 | } |
1981 | else if (strcmp (s1: element_name, s2: "signal" ) == 0) |
1982 | { |
1983 | SignalInfo *signal_info = state_pop_info (data, SignalInfo); |
1984 | ObjectInfo *object_info = (ObjectInfo*)state_peek_info (data, CommonInfo); |
1985 | g_assert (object_info != NULL); |
1986 | signal_info->object_name = g_strdup (str: object_info->id); |
1987 | |
1988 | if (G_UNLIKELY (!object_info->signals)) |
1989 | object_info->signals = g_ptr_array_new (); |
1990 | |
1991 | g_ptr_array_add (array: object_info->signals, data: signal_info); |
1992 | } |
1993 | else if (strcmp (s1: element_name, s2: "constant" ) == 0 || |
1994 | strcmp (s1: element_name, s2: "closure" ) == 0 || |
1995 | strcmp (s1: element_name, s2: "lookup" ) == 0) |
1996 | { |
1997 | ExpressionInfo *expression_info = state_pop_info (data, ExpressionInfo); |
1998 | CommonInfo *parent_info = state_peek_info (data, CommonInfo); |
1999 | g_assert (parent_info != NULL); |
2000 | |
2001 | if (parent_info->tag_type == TAG_BINDING_EXPRESSION) |
2002 | { |
2003 | BindingExpressionInfo *expr_info = (BindingExpressionInfo *) parent_info; |
2004 | |
2005 | expr_info->expr = expression_info; |
2006 | } |
2007 | else if (parent_info->tag_type == TAG_PROPERTY) |
2008 | { |
2009 | PropertyInfo *prop_info = (PropertyInfo *) parent_info; |
2010 | |
2011 | prop_info->value = expression_info_construct (builder: data->builder, info: expression_info, error); |
2012 | free_expression_info (info: expression_info); |
2013 | } |
2014 | else if (parent_info->tag_type == TAG_EXPRESSION) |
2015 | { |
2016 | ExpressionInfo *expr_info = (ExpressionInfo *) parent_info; |
2017 | |
2018 | switch (expr_info->expression_type) |
2019 | { |
2020 | case EXPRESSION_CLOSURE: |
2021 | expr_info->closure.params = g_slist_prepend (list: expr_info->closure.params, data: expression_info); |
2022 | break; |
2023 | case EXPRESSION_PROPERTY: |
2024 | expr_info->property.expression = expression_info; |
2025 | break; |
2026 | case EXPRESSION_EXPRESSION: |
2027 | case EXPRESSION_CONSTANT: |
2028 | default: |
2029 | g_assert_not_reached (); |
2030 | break; |
2031 | } |
2032 | } |
2033 | else |
2034 | { |
2035 | g_assert_not_reached (); |
2036 | } |
2037 | } |
2038 | else if (strcmp (s1: element_name, s2: "requires" ) == 0) |
2039 | { |
2040 | RequiresInfo *req_info = state_pop_info (data, RequiresInfo); |
2041 | |
2042 | /* TODO: Allow third party widget developers to check their |
2043 | * required versions, possibly throw a signal allowing them |
2044 | * to check their library versions here. |
2045 | */ |
2046 | if (!strcmp (s1: req_info->library, s2: "gtk" )) |
2047 | { |
2048 | if (req_info->major == 4 && req_info->minor == 0) |
2049 | { |
2050 | /* We allow 3.99.x to pass as 4.0 */ |
2051 | } |
2052 | else if (gtk_check_version (required_major: req_info->major, required_minor: req_info->minor, required_micro: 0) != NULL) |
2053 | { |
2054 | g_set_error (err: error, |
2055 | GTK_BUILDER_ERROR, |
2056 | code: GTK_BUILDER_ERROR_VERSION_MISMATCH, |
2057 | format: "Required GTK version %d.%d, current version is %d.%d" , |
2058 | req_info->major, req_info->minor, |
2059 | GTK_MAJOR_VERSION, GTK_MINOR_VERSION); |
2060 | _gtk_builder_prefix_error (builder: data->builder, context, error); |
2061 | } |
2062 | } |
2063 | free_requires_info (info: req_info, NULL); |
2064 | } |
2065 | else if (strcmp (s1: element_name, s2: "interface" ) == 0) |
2066 | { |
2067 | } |
2068 | else if (strcmp (s1: element_name, s2: "menu" ) == 0) |
2069 | { |
2070 | _gtk_builder_menu_end (parser_data: data); |
2071 | } |
2072 | else if (strcmp (s1: element_name, s2: "placeholder" ) == 0) |
2073 | { |
2074 | } |
2075 | else |
2076 | { |
2077 | g_set_error (err: error, |
2078 | GTK_BUILDER_ERROR, |
2079 | code: GTK_BUILDER_ERROR_UNHANDLED_TAG, |
2080 | format: "Unhandled tag: <%s>" , element_name); |
2081 | _gtk_builder_prefix_error (builder: data->builder, context, error); |
2082 | } |
2083 | } |
2084 | |
2085 | /* Called for character data */ |
2086 | /* text is not nul-terminated */ |
2087 | static void |
2088 | text (GtkBuildableParseContext *context, |
2089 | const char *text, |
2090 | gsize text_len, |
2091 | gpointer user_data, |
2092 | GError **error) |
2093 | { |
2094 | ParserData *data = (ParserData*)user_data; |
2095 | CommonInfo *info; |
2096 | |
2097 | if (data->subparser && data->subparser->start) |
2098 | { |
2099 | GError *tmp_error = NULL; |
2100 | |
2101 | if (data->subparser->parser->text) |
2102 | data->subparser->parser->text (context, text, text_len, |
2103 | data->subparser->data, &tmp_error); |
2104 | if (tmp_error) |
2105 | g_propagate_error (dest: error, src: tmp_error); |
2106 | return; |
2107 | } |
2108 | |
2109 | if (!data->stack || data->stack->len == 0) |
2110 | return; |
2111 | |
2112 | info = state_peek_info (data, CommonInfo); |
2113 | g_assert (info != NULL); |
2114 | |
2115 | if (strcmp (s1: gtk_buildable_parse_context_get_element (context), s2: "property" ) == 0) |
2116 | { |
2117 | PropertyInfo *prop_info = (PropertyInfo*)info; |
2118 | |
2119 | g_string_append_len (string: prop_info->text, val: text, len: text_len); |
2120 | } |
2121 | else if (strcmp (s1: gtk_buildable_parse_context_get_element (context), s2: "constant" ) == 0) |
2122 | { |
2123 | ExpressionInfo *expr_info = (ExpressionInfo *) info; |
2124 | |
2125 | g_string_append_len (string: expr_info->constant.text, val: text, len: text_len); |
2126 | } |
2127 | else if (strcmp (s1: gtk_buildable_parse_context_get_element (context), s2: "lookup" ) == 0) |
2128 | { |
2129 | ExpressionInfo *expr_info = (ExpressionInfo *) info; |
2130 | |
2131 | while (g_ascii_isspace (*text) && text_len > 0) |
2132 | { |
2133 | text++; |
2134 | text_len--; |
2135 | } |
2136 | while (text_len > 0 && g_ascii_isspace (text[text_len - 1])) |
2137 | text_len--; |
2138 | if (expr_info->property.expression == NULL && text_len > 0) |
2139 | { |
2140 | ExpressionInfo *constant = g_slice_new0 (ExpressionInfo); |
2141 | constant->tag_type = TAG_EXPRESSION; |
2142 | constant->expression_type = EXPRESSION_CONSTANT; |
2143 | constant->constant.type = G_TYPE_INVALID; |
2144 | constant->constant.text = g_string_new_len (init: text, len: text_len); |
2145 | expr_info->property.expression = constant; |
2146 | } |
2147 | } |
2148 | } |
2149 | |
2150 | static const GtkBuildableParser parser = { |
2151 | start_element, |
2152 | end_element, |
2153 | text, |
2154 | NULL, |
2155 | }; |
2156 | |
2157 | void |
2158 | _gtk_builder_parser_parse_buffer (GtkBuilder *builder, |
2159 | const char *filename, |
2160 | const char *buffer, |
2161 | gssize length, |
2162 | const char **requested_objs, |
2163 | GError **error) |
2164 | { |
2165 | const char * domain; |
2166 | ParserData data; |
2167 | GSList *l; |
2168 | gint64 before = GDK_PROFILER_CURRENT_TIME; |
2169 | |
2170 | /* Store the original domain so that interface domain attribute can be |
2171 | * applied for the builder and the original domain can be restored after |
2172 | * parsing has finished. This allows subparsers to translate elements with |
2173 | * gtk_builder_get_translation_domain() without breaking the ABI or API |
2174 | */ |
2175 | domain = gtk_builder_get_translation_domain (builder); |
2176 | |
2177 | memset (s: &data, c: 0, n: sizeof (ParserData)); |
2178 | data.builder = builder; |
2179 | data.filename = filename; |
2180 | data.domain = g_strdup (str: domain); |
2181 | data.object_ids = g_hash_table_new_full (hash_func: g_str_hash, key_equal_func: g_str_equal, |
2182 | key_destroy_func: (GDestroyNotify)g_free, NULL); |
2183 | data.stack = g_ptr_array_new (); |
2184 | data.finalizers = g_ptr_array_new (); |
2185 | |
2186 | if (requested_objs) |
2187 | { |
2188 | data.inside_requested_object = FALSE; |
2189 | data.requested_objects = requested_objs; |
2190 | } |
2191 | else |
2192 | { |
2193 | /* get all the objects */ |
2194 | data.inside_requested_object = TRUE; |
2195 | } |
2196 | |
2197 | gtk_buildable_parse_context_init (context: &data.ctx, parser: &parser, user_data: &data); |
2198 | |
2199 | if (!gtk_buildable_parse_context_parse (context: &data.ctx, text: buffer, text_len: length, error)) |
2200 | goto out; |
2201 | |
2202 | if (_gtk_builder_lookup_failed (builder, error)) |
2203 | goto out; |
2204 | |
2205 | if (!_gtk_builder_finish (builder, error)) |
2206 | goto out; |
2207 | |
2208 | /* Custom parser_finished */ |
2209 | data.custom_finalizers = g_slist_reverse (list: data.custom_finalizers); |
2210 | for (l = data.custom_finalizers; l; l = l->next) |
2211 | { |
2212 | SubParser *sub = (SubParser*)l->data; |
2213 | |
2214 | gtk_buildable_custom_finished (GTK_BUILDABLE (sub->object), |
2215 | builder, |
2216 | child: sub->child, |
2217 | tagname: sub->tagname, |
2218 | data: sub->data); |
2219 | if (_gtk_builder_lookup_failed (builder, error)) |
2220 | goto out; |
2221 | } |
2222 | |
2223 | /* Common parser_finished, for all created objects */ |
2224 | for (guint i = 0; i < data.finalizers->len; i++) |
2225 | { |
2226 | GtkBuildable *buildable = g_ptr_array_index (data.finalizers, i); |
2227 | |
2228 | gtk_buildable_parser_finished (GTK_BUILDABLE (buildable), builder); |
2229 | if (_gtk_builder_lookup_failed (builder, error)) |
2230 | goto out; |
2231 | } |
2232 | |
2233 | out: |
2234 | |
2235 | g_slist_free_full (list: data.custom_finalizers, free_func: (GDestroyNotify)free_subparser); |
2236 | g_free (mem: data.domain); |
2237 | g_hash_table_destroy (hash_table: data.object_ids); |
2238 | g_ptr_array_free (array: data.stack, TRUE); |
2239 | g_ptr_array_free (array: data.finalizers, TRUE); |
2240 | gtk_buildable_parse_context_free (context: &data.ctx); |
2241 | |
2242 | /* restore the original domain */ |
2243 | gtk_builder_set_translation_domain (builder, domain); |
2244 | |
2245 | if (GDK_PROFILER_IS_RUNNING) |
2246 | { |
2247 | guint64 after = GDK_PROFILER_CURRENT_TIME; |
2248 | if (after - before > 500000) /* half a millisecond */ |
2249 | { |
2250 | gdk_profiler_add_mark (before, after - before, "builder load" , filename); |
2251 | } |
2252 | } |
2253 | } |
2254 | |