1 | /* Copyright 2015 Red Hat, Inc. |
2 | * |
3 | * GTK+ is free software; you can redistribute it and/or modify it |
4 | * under the terms of the GNU Lesser General Public License as |
5 | * published by the Free Software Foundation; either version 2 of the |
6 | * License, or (at your option) any later version. |
7 | * |
8 | * GLib is distributed in the hope that it will be useful, |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
11 | * Lesser General Public License for more details. |
12 | * |
13 | * You should have received a copy of the GNU Lesser General Public |
14 | * License along with GTK+; see the file COPYING. If not, |
15 | * see <http://www.gnu.org/licenses/>. |
16 | * |
17 | * Author: Matthias Clasen |
18 | */ |
19 | |
20 | #include <stdlib.h> |
21 | #include <string.h> |
22 | #include <errno.h> |
23 | |
24 | #include <glib/gi18n.h> |
25 | #include <glib/gprintf.h> |
26 | #include <glib/gstdio.h> |
27 | #include <gtk/gtk.h> |
28 | #include "gtkbuilderprivate.h" |
29 | #include "gtk-builder-tool.h" |
30 | |
31 | typedef struct Element Element; |
32 | struct Element { |
33 | Element *parent; |
34 | char *element_name; |
35 | char **attribute_names; |
36 | char **attribute_values; |
37 | char *data; |
38 | GList *children; |
39 | |
40 | int line_number; |
41 | int char_number; |
42 | }; |
43 | |
44 | static void |
45 | free_element (gpointer data) |
46 | { |
47 | Element *element = data; |
48 | g_list_free_full (list: element->children, free_func: free_element); |
49 | g_free (mem: element->element_name); |
50 | g_strfreev (str_array: element->attribute_names); |
51 | g_strfreev (str_array: element->attribute_values); |
52 | g_free (mem: element->data); |
53 | g_free (mem: element); |
54 | } |
55 | |
56 | typedef struct { |
57 | Element *root; |
58 | Element *current; |
59 | GString *value; |
60 | GtkBuilder *builder; |
61 | const char *input_filename; |
62 | char *output_filename; |
63 | FILE *output; |
64 | gboolean convert3to4; |
65 | gboolean has_gtk_requires; |
66 | } MyParserData; |
67 | |
68 | static void |
69 | start_element (GMarkupParseContext *context, |
70 | const char *element_name, |
71 | const char **attribute_names, |
72 | const char **attribute_values, |
73 | gpointer user_data, |
74 | GError **error) |
75 | { |
76 | MyParserData *data = user_data; |
77 | Element *elt; |
78 | |
79 | elt = g_new0 (Element, 1); |
80 | elt->parent = data->current; |
81 | elt->element_name = g_strdup (str: element_name); |
82 | elt->attribute_names = g_strdupv (str_array: (char **)attribute_names); |
83 | elt->attribute_values = g_strdupv (str_array: (char **)attribute_values); |
84 | |
85 | g_markup_parse_context_get_position (context, line_number: &elt->line_number, char_number: &elt->char_number); |
86 | |
87 | if (data->current) |
88 | data->current->children = g_list_append (list: data->current->children, data: elt); |
89 | data->current = elt; |
90 | |
91 | if (data->root == NULL) |
92 | data->root = elt; |
93 | |
94 | g_string_truncate (string: data->value, len: 0); |
95 | } |
96 | |
97 | static void |
98 | end_element (GMarkupParseContext *context, |
99 | const char *element_name, |
100 | gpointer user_data, |
101 | GError **error) |
102 | { |
103 | MyParserData *data = user_data; |
104 | |
105 | data->current->data = g_strdup (str: data->value->str); |
106 | |
107 | data->current = data->current->parent; |
108 | } |
109 | |
110 | static void |
111 | text (GMarkupParseContext *context, |
112 | const char *text, |
113 | gsize text_len, |
114 | gpointer user_data, |
115 | GError **error) |
116 | { |
117 | MyParserData *data = user_data; |
118 | |
119 | if (data->value) |
120 | { |
121 | g_string_append_len (string: data->value, val: text, len: text_len); |
122 | return; |
123 | } |
124 | } |
125 | |
126 | static GMarkupParser parser = { |
127 | start_element, |
128 | end_element, |
129 | text, |
130 | NULL, |
131 | NULL |
132 | }; |
133 | |
134 | static const char * |
135 | canonical_boolean_value (MyParserData *data, |
136 | const char *string) |
137 | { |
138 | GValue value = G_VALUE_INIT; |
139 | gboolean b = FALSE; |
140 | |
141 | if (gtk_builder_value_from_string_type (builder: data->builder, G_TYPE_BOOLEAN, string, value: &value, NULL)) |
142 | b = g_value_get_boolean (value: &value); |
143 | |
144 | return b ? "1" : "0" ; |
145 | } |
146 | |
147 | typedef enum { |
148 | PROP_KIND_OBJECT, |
149 | PROP_KIND_PACKING, |
150 | PROP_KIND_CELL_PACKING, |
151 | PROP_KIND_LAYOUT |
152 | } PropKind; |
153 | |
154 | static PropKind |
155 | get_prop_kind (Element *element) |
156 | { |
157 | g_assert (g_str_equal (element->element_name, "property" )); |
158 | |
159 | if (g_str_equal (v1: element->parent->element_name, v2: "packing" )) |
160 | return PROP_KIND_PACKING; |
161 | else if (g_str_equal (v1: element->parent->element_name, v2: "layout" )) |
162 | return PROP_KIND_LAYOUT; |
163 | else if (g_str_equal (v1: element->parent->element_name, v2: "cell-packing" )) |
164 | return PROP_KIND_CELL_PACKING; |
165 | else |
166 | return PROP_KIND_OBJECT; |
167 | } |
168 | |
169 | /* A number of properties unfortunately can't be omitted even |
170 | * if they are nominally set to their default value. In many |
171 | * cases, this is due to subclasses not overriding the default |
172 | * value from the superclass. |
173 | */ |
174 | static gboolean |
175 | needs_explicit_setting (GParamSpec *pspec, |
176 | PropKind kind) |
177 | { |
178 | struct _Prop { |
179 | const char *class; |
180 | const char *property; |
181 | PropKind kind; |
182 | } props[] = { |
183 | { "GtkAboutDialog" , "program-name" , PROP_KIND_OBJECT }, |
184 | { "GtkCalendar" , "year" , PROP_KIND_OBJECT }, |
185 | { "GtkCalendar" , "month" , PROP_KIND_OBJECT }, |
186 | { "GtkCalendar" , "day" , PROP_KIND_OBJECT }, |
187 | { "GtkPlacesSidebar" , "show-desktop" , PROP_KIND_OBJECT }, |
188 | { "GtkRadioButton" , "draw-indicator" , PROP_KIND_OBJECT }, |
189 | { "GtkWidget" , "hexpand" , PROP_KIND_OBJECT }, |
190 | { "GtkWidget" , "vexpand" , PROP_KIND_OBJECT }, |
191 | { "GtkGridLayoutChild" , "row" , PROP_KIND_LAYOUT }, |
192 | { "GtkGridLayoutChild" , "column" , PROP_KIND_LAYOUT }, |
193 | }; |
194 | gboolean found; |
195 | int k; |
196 | const char *class_name; |
197 | |
198 | class_name = g_type_name (type: pspec->owner_type); |
199 | |
200 | found = FALSE; |
201 | for (k = 0; k < G_N_ELEMENTS (props); k++) |
202 | { |
203 | if (strcmp (s1: class_name, s2: props[k].class) == 0 && |
204 | strcmp (s1: pspec->name, s2: props[k].property) == 0 && |
205 | kind == props[k].kind) |
206 | { |
207 | found = TRUE; |
208 | break; |
209 | } |
210 | } |
211 | |
212 | return found; |
213 | } |
214 | |
215 | static gboolean |
216 | has_attribute (Element *elt, |
217 | const char *name, |
218 | const char *value) |
219 | { |
220 | int i; |
221 | |
222 | for (i = 0; elt->attribute_names[i]; i++) |
223 | { |
224 | if (strcmp (s1: elt->attribute_names[i], s2: name) == 0 && |
225 | (value == NULL || strcmp (s1: elt->attribute_values[i], s2: value) == 0)) |
226 | return TRUE; |
227 | } |
228 | |
229 | return FALSE; |
230 | } |
231 | |
232 | static gboolean |
233 | is_cdata_property (Element *element) |
234 | { |
235 | if (g_str_equal (v1: element->element_name, v2: "property" ) && |
236 | has_attribute (elt: element, name: "name" , value: "bytes" ) && |
237 | element->parent != NULL && |
238 | g_str_equal (v1: element->parent->element_name, v2: "object" ) && |
239 | has_attribute (elt: element->parent, name: "class" , value: "GtkBuilderListItemFactory" )) |
240 | return TRUE; |
241 | |
242 | return FALSE; |
243 | } |
244 | |
245 | static gboolean |
246 | is_pcdata_element (Element *element) |
247 | { |
248 | /* elements that can contain text */ |
249 | const char *names[] = { |
250 | "property" , |
251 | "attribute" , |
252 | "action-widget" , |
253 | "pattern" , |
254 | "mime-type" , |
255 | "col" , |
256 | "item" , |
257 | "mark" , |
258 | "lookup" , |
259 | NULL, |
260 | }; |
261 | |
262 | if (g_str_equal (v1: element->element_name, v2: "property" ) && |
263 | (g_strv_contains (strv: (const char * const *)element->attribute_names, str: "bind-source" ) || |
264 | g_strv_contains (strv: (const char * const *)element->attribute_names, str: "bind_source" ))) |
265 | return FALSE; |
266 | |
267 | if (g_strv_contains (strv: names, str: element->element_name)) |
268 | return TRUE; |
269 | |
270 | return FALSE; |
271 | } |
272 | |
273 | static gboolean |
274 | is_container_element (Element *element) |
275 | { |
276 | /* elements that just hold a list of things and |
277 | * can be omitted when they have no children |
278 | */ |
279 | const char *names[] = { |
280 | "packing" , |
281 | "layout" , |
282 | "cell-packing" , |
283 | "attributes" , |
284 | "action-widgets" , |
285 | "patterns" , |
286 | "mime-types" , |
287 | "attributes" , |
288 | "row" , |
289 | "items" , |
290 | NULL |
291 | }; |
292 | |
293 | if (g_strv_contains (strv: names, str: element->element_name)) |
294 | return TRUE; |
295 | |
296 | return FALSE; |
297 | } |
298 | |
299 | static void |
300 | canonicalize_key (char *key) |
301 | { |
302 | char *p; |
303 | |
304 | for (p = key; *p != 0; p++) |
305 | { |
306 | char c = *p; |
307 | |
308 | /* We may meet something like AtkObject::accessible-name */ |
309 | if (c == ':' && ((p > key && p[-1] == ':') || p[1] == ':')) |
310 | continue; |
311 | |
312 | if (c != '-' && |
313 | (c < '0' || c > '9') && |
314 | (c < 'A' || c > 'Z') && |
315 | (c < 'a' || c > 'z')) |
316 | *p = '-'; |
317 | } |
318 | } |
319 | |
320 | static struct { |
321 | const char *class; |
322 | const char *layout_manager; |
323 | } layout_managers[] = { |
324 | { "GtkBox" , "GtkBoxLayout" }, |
325 | { "GtkGrid" , "GtkGridLayout" }, |
326 | { "GtkFixed" , "GtkFixedLayout" }, |
327 | { "GtkFileChooserButton" , "GtkBinLayout" }, |
328 | { "GtkFileChooserWidget" , "GtkBinLayout" }, |
329 | { "GtkOverlay" , "GtkOverlayLayout" } |
330 | }; |
331 | |
332 | static GParamSpec * |
333 | get_property_pspec (MyParserData *data, |
334 | const char *class_name, |
335 | const char *property_name, |
336 | PropKind kind) |
337 | { |
338 | GType type; |
339 | GObjectClass *class; |
340 | GParamSpec *pspec; |
341 | char *canonical_name; |
342 | |
343 | type = g_type_from_name (name: class_name); |
344 | if (type == G_TYPE_INVALID) |
345 | { |
346 | type = gtk_builder_get_type_from_name (builder: data->builder, type_name: class_name); |
347 | if (type == G_TYPE_INVALID) |
348 | return NULL; |
349 | } |
350 | |
351 | class = g_type_class_ref (type); |
352 | canonical_name = g_strdup (str: property_name); |
353 | canonicalize_key (key: canonical_name); |
354 | switch (kind) |
355 | { |
356 | case PROP_KIND_OBJECT: |
357 | pspec = g_object_class_find_property (oclass: class, property_name: canonical_name); |
358 | break; |
359 | |
360 | case PROP_KIND_PACKING: |
361 | pspec = NULL; |
362 | break; |
363 | |
364 | case PROP_KIND_CELL_PACKING: |
365 | { |
366 | GObjectClass *cell_class; |
367 | |
368 | /* We're just assuming that the cell layout is using a GtkCellAreaBox. */ |
369 | cell_class = g_type_class_ref (GTK_TYPE_CELL_AREA_BOX); |
370 | pspec = gtk_cell_area_class_find_cell_property (GTK_CELL_AREA_CLASS (cell_class), property_name: canonical_name); |
371 | g_type_class_unref (g_class: cell_class); |
372 | } |
373 | break; |
374 | |
375 | case PROP_KIND_LAYOUT: |
376 | { |
377 | int i; |
378 | const char *layout_manager = NULL; |
379 | |
380 | pspec = NULL; |
381 | |
382 | for (i = 0; i < G_N_ELEMENTS (layout_managers); i++) |
383 | { |
384 | if (g_str_equal (v1: layout_managers[i].class, v2: class_name)) |
385 | { |
386 | layout_manager = layout_managers[i].layout_manager; |
387 | break; |
388 | } |
389 | } |
390 | |
391 | if (layout_manager) |
392 | { |
393 | GtkLayoutManagerClass *layout_manager_class; |
394 | |
395 | layout_manager_class = GTK_LAYOUT_MANAGER_CLASS (ptr: g_type_class_ref (type: g_type_from_name (name: layout_manager))); |
396 | if (layout_manager_class->layout_child_type != G_TYPE_INVALID) |
397 | { |
398 | GObjectClass *layout_child_class; |
399 | layout_child_class = g_type_class_ref (type: layout_manager_class->layout_child_type); |
400 | pspec = g_object_class_find_property (oclass: layout_child_class, property_name: canonical_name); |
401 | g_type_class_unref (g_class: layout_child_class); |
402 | } |
403 | g_type_class_unref (g_class: layout_manager_class); |
404 | } |
405 | } |
406 | break; |
407 | |
408 | default: |
409 | g_assert_not_reached (); |
410 | } |
411 | g_free (mem: canonical_name); |
412 | g_type_class_unref (g_class: class); |
413 | |
414 | return pspec; |
415 | } |
416 | |
417 | static const char *get_class_name (Element *element); |
418 | |
419 | static gboolean |
420 | value_is_default (Element *element, |
421 | MyParserData *data, |
422 | GParamSpec *pspec, |
423 | const char *value_string) |
424 | { |
425 | GValue value = { 0, }; |
426 | gboolean ret; |
427 | GError *error = NULL; |
428 | |
429 | if (g_type_is_a (G_PARAM_SPEC_VALUE_TYPE (pspec), G_TYPE_OBJECT)) |
430 | return FALSE; |
431 | |
432 | if (g_type_is_a (G_PARAM_SPEC_VALUE_TYPE (pspec), G_TYPE_BOXED)) |
433 | return FALSE; |
434 | |
435 | if (!value_string) |
436 | return FALSE; |
437 | |
438 | if (!gtk_builder_value_from_string (builder: data->builder, pspec, string: value_string, value: &value, error: &error)) |
439 | { |
440 | g_printerr (_("%s:%d: Couldn’t parse value for property '%s': %s\n" ), data->input_filename, element->line_number, pspec->name, error->message); |
441 | g_error_free (error); |
442 | ret = FALSE; |
443 | } |
444 | else |
445 | { |
446 | /* GtkWidget::visible has a 'smart' default */ |
447 | if (pspec->owner_type == GTK_TYPE_WIDGET && |
448 | g_str_equal (v1: pspec->name, v2: "visible" )) |
449 | { |
450 | const char *class_name = get_class_name (element); |
451 | GType type = g_type_from_name (name: class_name); |
452 | gboolean default_value; |
453 | |
454 | if (g_type_is_a (type, GTK_TYPE_ROOT) || |
455 | g_type_is_a (type, GTK_TYPE_POPOVER)) |
456 | default_value = FALSE; |
457 | else |
458 | default_value = TRUE; |
459 | |
460 | ret = g_value_get_boolean (value: &value) == default_value; |
461 | } |
462 | else if (pspec->owner_type == GTK_TYPE_WINDOW && |
463 | (g_str_equal (v1: pspec->name, v2: "default-width" ) || |
464 | g_str_equal (v1: pspec->name, v2: "default-height" ))) |
465 | { |
466 | int default_size; |
467 | |
468 | default_size = g_value_get_int (value: &value); |
469 | ret = default_size <= 0; |
470 | } |
471 | else |
472 | ret = g_param_value_defaults (pspec, value: &value); |
473 | } |
474 | |
475 | g_value_reset (value: &value); |
476 | |
477 | return ret; |
478 | } |
479 | |
480 | static const char * |
481 | get_attribute_value (Element *element, |
482 | const char *name) |
483 | { |
484 | int i; |
485 | |
486 | for (i = 0; element->attribute_names[i]; i++) |
487 | { |
488 | if (g_str_equal (v1: element->attribute_names[i], v2: name)) |
489 | return element->attribute_values[i]; |
490 | } |
491 | |
492 | return "" ; |
493 | } |
494 | |
495 | static void |
496 | set_attribute_value (Element *element, |
497 | const char *name, |
498 | const char *value) |
499 | { |
500 | int i; |
501 | int len; |
502 | |
503 | for (i = 0; element->attribute_names[i]; i++) |
504 | { |
505 | if (g_str_equal (v1: element->attribute_names[i], v2: name)) |
506 | { |
507 | g_free (mem: element->attribute_values[i]); |
508 | element->attribute_values[i] = g_strdup (str: value); |
509 | return; |
510 | } |
511 | } |
512 | |
513 | len = g_strv_length (str_array: element->attribute_names); |
514 | element->attribute_names = g_realloc_n (mem: element->attribute_names, n_blocks: len + 2, n_block_bytes: sizeof (char *)); |
515 | element->attribute_values = g_realloc_n (mem: element->attribute_values, n_blocks: len + 2, n_block_bytes: sizeof (char *)); |
516 | element->attribute_names[len] = g_strdup (str: name); |
517 | element->attribute_values[len] = g_strdup (str: value); |
518 | element->attribute_names[len + 1] = NULL; |
519 | element->attribute_values[len + 1] = NULL; |
520 | } |
521 | |
522 | static gboolean |
523 | element_is_object_or_template (Element *element) |
524 | { |
525 | return g_str_equal (v1: element->element_name, v2: "object" ) || |
526 | g_str_equal (v1: element->element_name, v2: "template" ); |
527 | } |
528 | |
529 | static const char * |
530 | get_class_name (Element *element) |
531 | { |
532 | Element *parent = element->parent; |
533 | |
534 | if (element_is_object_or_template (element)) |
535 | parent = element; |
536 | |
537 | if (g_str_equal (v1: parent->element_name, v2: "packing" )) |
538 | parent = parent->parent->parent; /* child - object */ |
539 | else if (g_str_equal (v1: parent->element_name, v2: "layout" )) |
540 | parent = parent->parent->parent->parent; /* object - child - object */ |
541 | |
542 | |
543 | if (g_str_equal (v1: parent->element_name, v2: "object" )) |
544 | { |
545 | return get_attribute_value (element: parent, name: "class" ); |
546 | } |
547 | else if (g_str_equal (v1: parent->element_name, v2: "template" )) |
548 | { |
549 | if (get_attribute_value (element: parent, name: "parent" )) |
550 | return get_attribute_value (element: parent, name: "parent" ); |
551 | else |
552 | return get_attribute_value (element: parent, name: "class" ); |
553 | } |
554 | |
555 | return "" ; |
556 | } |
557 | |
558 | static gboolean |
559 | property_is_boolean (Element *element, |
560 | MyParserData *data) |
561 | { |
562 | GParamSpec *pspec = NULL; |
563 | const char *class_name; |
564 | const char *property_name; |
565 | int i; |
566 | PropKind kind; |
567 | |
568 | kind = get_prop_kind (element); |
569 | class_name = get_class_name (element); |
570 | property_name = "" ; |
571 | |
572 | for (i = 0; element->attribute_names[i]; i++) |
573 | { |
574 | if (strcmp (s1: element->attribute_names[i], s2: "name" ) == 0) |
575 | property_name = (const char *)element->attribute_values[i]; |
576 | } |
577 | |
578 | if (class_name && property_name) |
579 | pspec = get_property_pspec (data, class_name, property_name, kind); |
580 | if (pspec) |
581 | return G_PARAM_SPEC_VALUE_TYPE (pspec) == G_TYPE_BOOLEAN; |
582 | |
583 | return FALSE; |
584 | } |
585 | |
586 | static gboolean |
587 | property_is_enum (Element *element, |
588 | MyParserData *data, |
589 | GType *type) |
590 | { |
591 | GParamSpec *pspec = NULL; |
592 | const char *class_name; |
593 | const char *property_name; |
594 | int i; |
595 | PropKind kind; |
596 | |
597 | kind = get_prop_kind (element); |
598 | class_name = get_class_name (element); |
599 | property_name = "" ; |
600 | |
601 | for (i = 0; element->attribute_names[i]; i++) |
602 | { |
603 | if (strcmp (s1: element->attribute_names[i], s2: "name" ) == 0) |
604 | property_name = (const char *)element->attribute_values[i]; |
605 | } |
606 | |
607 | if (class_name && property_name) |
608 | pspec = get_property_pspec (data, class_name, property_name, kind); |
609 | if (pspec && G_TYPE_IS_ENUM (G_PARAM_SPEC_VALUE_TYPE (pspec))) |
610 | { |
611 | *type = G_PARAM_SPEC_VALUE_TYPE (pspec); |
612 | return TRUE; |
613 | } |
614 | |
615 | *type = G_TYPE_NONE; |
616 | return FALSE; |
617 | } |
618 | |
619 | static char * |
620 | canonical_enum_value (MyParserData *data, |
621 | GType type, |
622 | const char *string) |
623 | { |
624 | GValue value = G_VALUE_INIT; |
625 | |
626 | if (gtk_builder_value_from_string_type (builder: data->builder, type, string, value: &value, NULL)) |
627 | { |
628 | GEnumClass *eclass = g_type_class_ref (type); |
629 | GEnumValue *evalue = g_enum_get_value (enum_class: eclass, value: g_value_get_enum (value: &value)); |
630 | |
631 | if (evalue) |
632 | return g_strdup (str: evalue->value_nick); |
633 | else |
634 | return g_strdup_printf (format: "%d" , g_value_get_enum (value: &value)); |
635 | } |
636 | |
637 | return NULL; |
638 | } |
639 | |
640 | static void |
641 | warn_missing_property (Element *element, |
642 | MyParserData *data, |
643 | const char *class_name, |
644 | const char *property_name, |
645 | PropKind kind) |
646 | { |
647 | const char *kind_str[] = { "" , "Packing " , "Cell " , "Layout " }; |
648 | |
649 | g_printerr (_("%s:%d: %sproperty %s::%s not found\n" ), |
650 | data->input_filename, element->line_number, kind_str[kind], class_name, property_name); |
651 | } |
652 | |
653 | static gboolean |
654 | property_can_be_omitted (Element *element, |
655 | MyParserData *data) |
656 | { |
657 | int i; |
658 | gboolean bound; |
659 | gboolean translatable; |
660 | const char *class_name; |
661 | const char *property_name; |
662 | const char *value_string; |
663 | GParamSpec *pspec; |
664 | PropKind kind; |
665 | |
666 | kind = get_prop_kind (element); |
667 | class_name = get_class_name (element); |
668 | property_name = "" ; |
669 | value_string = element->data; |
670 | |
671 | bound = FALSE; |
672 | translatable = FALSE; |
673 | for (i = 0; element->attribute_names[i]; i++) |
674 | { |
675 | if (strcmp (s1: element->attribute_names[i], s2: "bind-source" ) == 0 || |
676 | strcmp (s1: element->attribute_names[i], s2: "bind_source" ) == 0) |
677 | bound = TRUE; |
678 | else if (strcmp (s1: element->attribute_names[i], s2: "translatable" ) == 0) |
679 | translatable = TRUE; |
680 | else if (strcmp (s1: element->attribute_names[i], s2: "name" ) == 0) |
681 | property_name = (const char *)element->attribute_values[i]; |
682 | } |
683 | |
684 | if (translatable) |
685 | return FALSE; |
686 | |
687 | if (bound) |
688 | return FALSE; |
689 | |
690 | pspec = get_property_pspec (data, class_name, property_name, kind); |
691 | if (pspec == NULL) |
692 | { |
693 | warn_missing_property (element, data, class_name, property_name, kind); |
694 | return FALSE; |
695 | } |
696 | |
697 | if (needs_explicit_setting (pspec, kind)) |
698 | return FALSE; |
699 | |
700 | return value_is_default (element, data, pspec, value_string); |
701 | } |
702 | |
703 | static gboolean |
704 | property_has_been_removed (Element *element, |
705 | MyParserData *data) |
706 | { |
707 | const char *class_name; |
708 | const char *property_name; |
709 | struct _Prop { |
710 | const char *class; |
711 | const char *property; |
712 | PropKind kind; |
713 | } props[] = { |
714 | { "GtkActionBar" , "position" , PROP_KIND_PACKING }, |
715 | { "GtkButtonBox" , "secondary" , PROP_KIND_PACKING }, |
716 | { "GtkButtonBox" , "non-homogeneous" , PROP_KIND_PACKING }, |
717 | { "GtkBox" , "position" , PROP_KIND_PACKING }, |
718 | { "GtkBox" , "pack-type" , PROP_KIND_PACKING }, |
719 | { "GtkHeaderBar" , "position" , PROP_KIND_PACKING }, |
720 | { "GtkPopoverMenu" , "position" ,PROP_KIND_PACKING }, |
721 | { "GtkCheckButton" , "draw-indicator" , PROP_KIND_OBJECT }, |
722 | }; |
723 | char *canonical_name; |
724 | gboolean found; |
725 | int i, k; |
726 | PropKind kind; |
727 | |
728 | kind = get_prop_kind (element); |
729 | |
730 | class_name = get_class_name (element); |
731 | property_name = "" ; |
732 | |
733 | for (i = 0; element->attribute_names[i]; i++) |
734 | { |
735 | if (strcmp (s1: element->attribute_names[i], s2: "name" ) == 0) |
736 | property_name = (const char *)element->attribute_values[i]; |
737 | } |
738 | |
739 | canonical_name = g_strdup (str: property_name); |
740 | g_strdelimit (string: canonical_name, delimiters: "_" , new_delimiter: '-'); |
741 | |
742 | found = FALSE; |
743 | for (k = 0; k < G_N_ELEMENTS (props); k++) |
744 | { |
745 | if (strcmp (s1: class_name, s2: props[k].class) == 0 && |
746 | strcmp (s1: canonical_name, s2: props[k].property) == 0 && |
747 | kind == props[k].kind) |
748 | { |
749 | found = TRUE; |
750 | break; |
751 | } |
752 | } |
753 | |
754 | g_free (mem: canonical_name); |
755 | |
756 | return found; |
757 | } |
758 | |
759 | static void |
760 | maybe_rename_property (Element *element, MyParserData *data) |
761 | { |
762 | const char *class_name; |
763 | const char *property_name; |
764 | struct _Prop { |
765 | const char *class; |
766 | const char *property; |
767 | GType type; |
768 | PropKind kind; |
769 | const char *new_name; |
770 | const char *alt_names[3]; |
771 | } props[] = { |
772 | /* the "replacement" property is placed *after* the "added" properties */ |
773 | { "GtkPopover" , "modal" , GTK_TYPE_POPOVER, PROP_KIND_OBJECT, "autohide" , { NULL, NULL, NULL } }, |
774 | { "GtkWidget" , "expand" , GTK_TYPE_WIDGET, PROP_KIND_OBJECT, "vexpand" , { "hexpand" , NULL, NULL } }, |
775 | { "GtkWidget" , "margin" , GTK_TYPE_WIDGET, PROP_KIND_OBJECT, "margin-bottom" , { "margin-start" , "margin-end" , "margin-top" } }, |
776 | { "GtkWidget" , "margin-left" , GTK_TYPE_WIDGET, PROP_KIND_OBJECT, "margin-start" , { NULL, NULL, NULL } }, |
777 | { "GtkWidget" , "margin-right" , GTK_TYPE_WIDGET, PROP_KIND_OBJECT, "margin-end" , { NULL, NULL, NULL } }, |
778 | { "GtkHeaderBar" , "show-close-button" , GTK_TYPE_HEADER_BAR, PROP_KIND_OBJECT, "show-title-buttons" , { NULL, NULL, NULL } }, |
779 | { "GtkHeaderBar" , "custom-title" , GTK_TYPE_HEADER_BAR, PROP_KIND_OBJECT, "title-widget" , { NULL, NULL, NULL } }, |
780 | { "GtkStack" , "homogeneous" , GTK_TYPE_STACK, PROP_KIND_OBJECT, "hhomogeneous" , { "vhomogeneous" , NULL, NULL } }, |
781 | { "GtkImage" , "pixbuf" , GTK_TYPE_IMAGE, PROP_KIND_OBJECT, "file" , { NULL, NULL, NULL } }, |
782 | { "GtkWidget" , "can-focus" , GTK_TYPE_WIDGET, PROP_KIND_OBJECT, "focusable" , { NULL, NULL, NULL } }, |
783 | }; |
784 | int i, k, l; |
785 | PropKind kind; |
786 | int prop_name_index = 0; |
787 | GType type; |
788 | char *canonical_name; |
789 | |
790 | kind = get_prop_kind (element); |
791 | |
792 | class_name = get_class_name (element); |
793 | property_name = NULL; |
794 | |
795 | for (i = 0; element->attribute_names[i]; i++) |
796 | { |
797 | if (strcmp (s1: element->attribute_names[i], s2: "name" ) == 0) |
798 | { |
799 | prop_name_index = i; |
800 | property_name = (const char *)element->attribute_values[i]; |
801 | } |
802 | } |
803 | |
804 | if (property_name == NULL) |
805 | return; |
806 | |
807 | type = g_type_from_name (name: class_name); |
808 | |
809 | canonical_name = g_strdup (str: property_name); |
810 | g_strdelimit (string: canonical_name, delimiters: "_" , new_delimiter: '-'); |
811 | |
812 | for (k = 0; k < G_N_ELEMENTS (props); k++) |
813 | { |
814 | if (g_type_is_a (type, is_a_type: props[k].type) && |
815 | strcmp (s1: canonical_name, s2: props[k].property) == 0 && |
816 | kind == props[k].kind) |
817 | { |
818 | g_free (mem: element->attribute_values[prop_name_index]); |
819 | element->attribute_values[prop_name_index] = g_strdup (str: props[k].new_name); |
820 | for (l = 0; l < 3 && props[k].alt_names[l]; l++) |
821 | { |
822 | Element *elt; |
823 | GList *sibling; |
824 | |
825 | elt = g_new0 (Element, 1); |
826 | elt->parent = element->parent; |
827 | elt->element_name = g_strdup (str: element->element_name); |
828 | elt->attribute_names = g_strdupv (str_array: (char **) element->attribute_names); |
829 | elt->attribute_values = g_strdupv (str_array: (char **) element->attribute_values); |
830 | elt->data = g_strdup (str: element->data); |
831 | |
832 | g_free (mem: elt->attribute_values[prop_name_index]); |
833 | elt->attribute_values[prop_name_index] = g_strdup (str: props[k].alt_names[l]); |
834 | |
835 | sibling = g_list_find (list: element->parent->children, data: element); |
836 | element->parent->children = g_list_insert_before (list: element->parent->children, |
837 | sibling, |
838 | data: elt); |
839 | } |
840 | break; |
841 | } |
842 | } |
843 | |
844 | g_free (mem: canonical_name); |
845 | } |
846 | |
847 | static Element * |
848 | rewrite_stack_child (Element *child, MyParserData *data) |
849 | { |
850 | Element *object = NULL; |
851 | Element *packing = NULL; |
852 | Element *new_object; |
853 | Element *prop; |
854 | GList *l; |
855 | |
856 | if (!g_str_equal (v1: child->element_name, v2: "child" )) |
857 | return child; |
858 | |
859 | for (l = child->children; l; l = l->next) |
860 | { |
861 | Element *elt = l->data; |
862 | if (g_str_equal (v1: elt->element_name, v2: "object" )) |
863 | object = elt; |
864 | else if (g_str_equal (v1: elt->element_name, v2: "packing" )) |
865 | packing = elt; |
866 | } |
867 | |
868 | if (!packing) |
869 | return child; |
870 | |
871 | new_object = g_new0 (Element, 1); |
872 | new_object->element_name = g_strdup (str: "object" ); |
873 | new_object->attribute_names = g_new0 (char *, 2); |
874 | new_object->attribute_names[0] = g_strdup (str: "class" ); |
875 | new_object->attribute_values = g_new0 (char *, 2); |
876 | new_object->attribute_values[0] = g_strdup (str: "GtkStackPage" ); |
877 | new_object->children = packing->children; |
878 | new_object->parent = child; |
879 | packing->children = NULL; |
880 | |
881 | prop = g_new0 (Element, 1); |
882 | prop->element_name = g_strdup (str: "property" ); |
883 | prop->attribute_names = g_new0 (char *, 2); |
884 | prop->attribute_names[0] = g_strdup (str: "name" ); |
885 | prop->attribute_values = g_new0 (char *, 2); |
886 | prop->attribute_values[0] = g_strdup (str: "child" ); |
887 | prop->children = g_list_append (list: prop->children, data: object); |
888 | prop->parent = new_object; |
889 | new_object->children = g_list_append (list: new_object->children, data: prop); |
890 | |
891 | g_list_free (list: child->children); |
892 | child->children = g_list_append (NULL, data: new_object); |
893 | |
894 | return child; |
895 | } |
896 | |
897 | static void |
898 | rewrite_stack (Element *element, |
899 | MyParserData *data) |
900 | { |
901 | GList *l, *new_children; |
902 | |
903 | new_children = NULL; |
904 | for (l = element->children; l; l = l->next) |
905 | { |
906 | Element *child = l->data; |
907 | new_children = g_list_append (list: new_children, data: rewrite_stack_child (child, data)); |
908 | } |
909 | |
910 | g_list_free (list: element->children); |
911 | element->children = new_children; |
912 | } |
913 | |
914 | static Element * |
915 | rewrite_assistant_child (Element *child, MyParserData *data) |
916 | { |
917 | Element *object = NULL; |
918 | Element *packing = NULL; |
919 | Element *new_object; |
920 | Element *prop; |
921 | GList *l; |
922 | |
923 | if (!g_str_equal (v1: child->element_name, v2: "child" )) |
924 | return child; |
925 | |
926 | for (l = child->children; l; l = l->next) |
927 | { |
928 | Element *elt = l->data; |
929 | if (g_str_equal (v1: elt->element_name, v2: "object" )) |
930 | object = elt; |
931 | else if (g_str_equal (v1: elt->element_name, v2: "packing" )) |
932 | packing = elt; |
933 | } |
934 | |
935 | if (!packing) |
936 | return child; |
937 | |
938 | new_object = g_new0 (Element, 1); |
939 | new_object->element_name = g_strdup (str: "object" ); |
940 | new_object->attribute_names = g_new0 (char *, 2); |
941 | new_object->attribute_names[0] = g_strdup (str: "class" ); |
942 | new_object->attribute_values = g_new0 (char *, 2); |
943 | new_object->attribute_values[0] = g_strdup (str: "GtkAssistantPage" ); |
944 | new_object->children = packing->children; |
945 | new_object->parent = child; |
946 | packing->children = NULL; |
947 | |
948 | prop = g_new0 (Element, 1); |
949 | prop->element_name = g_strdup (str: "property" ); |
950 | prop->attribute_names = g_new0 (char *, 2); |
951 | prop->attribute_names[0] = g_strdup (str: "name" ); |
952 | prop->attribute_values = g_new0 (char *, 2); |
953 | prop->attribute_values[0] = g_strdup (str: "child" ); |
954 | prop->children = g_list_append (list: prop->children, data: object); |
955 | prop->parent = new_object; |
956 | new_object->children = g_list_append (list: new_object->children, data: prop); |
957 | |
958 | g_list_free (list: child->children); |
959 | child->children = g_list_append (NULL, data: new_object); |
960 | |
961 | return child; |
962 | } |
963 | |
964 | static void |
965 | rewrite_assistant (Element *element, |
966 | MyParserData *data) |
967 | { |
968 | GList *l, *new_children; |
969 | |
970 | new_children = NULL; |
971 | for (l = element->children; l; l = l->next) |
972 | { |
973 | Element *child = l->data; |
974 | new_children = g_list_append (list: new_children, data: rewrite_assistant_child (child, data)); |
975 | } |
976 | |
977 | g_list_free (list: element->children); |
978 | element->children = new_children; |
979 | } |
980 | |
981 | static Element * |
982 | rewrite_notebook_page (Element *child, Element *tab, MyParserData *data) |
983 | { |
984 | Element *object = NULL; |
985 | Element *tab_obj = NULL; |
986 | Element *packing = NULL; |
987 | Element *new_object; |
988 | Element *prop; |
989 | GList *l; |
990 | |
991 | if (!g_str_equal (v1: child->element_name, v2: "child" )) |
992 | return child; |
993 | |
994 | if (has_attribute (elt: child, name: "type" , value: "tab" ) || |
995 | has_attribute (elt: child, name: "type" , value: "action-start" ) || |
996 | has_attribute (elt: child, name: "type" , value: "action-end" )) |
997 | return child; |
998 | |
999 | for (l = child->children; l; l = l->next) |
1000 | { |
1001 | Element *elt = l->data; |
1002 | if (g_str_equal (v1: elt->element_name, v2: "object" )) |
1003 | object = elt; |
1004 | else if (g_str_equal (v1: elt->element_name, v2: "packing" )) |
1005 | packing = elt; |
1006 | else if (g_str_equal (v1: elt->element_name, v2: "placeholder" )) |
1007 | return child; |
1008 | } |
1009 | |
1010 | if (!packing && !tab) |
1011 | return child; |
1012 | |
1013 | if (tab) |
1014 | { |
1015 | for (l = tab->children; l; l = l->next) |
1016 | { |
1017 | Element *elt = l->data; |
1018 | if (g_str_equal (v1: elt->element_name, v2: "object" )) |
1019 | tab_obj = elt; |
1020 | } |
1021 | } |
1022 | |
1023 | new_object = g_new0 (Element, 1); |
1024 | new_object->element_name = g_strdup (str: "object" ); |
1025 | new_object->attribute_names = g_new0 (char *, 2); |
1026 | new_object->attribute_names[0] = g_strdup (str: "class" ); |
1027 | new_object->attribute_values = g_new0 (char *, 2); |
1028 | new_object->attribute_values[0] = g_strdup (str: "GtkNotebookPage" ); |
1029 | new_object->parent = child; |
1030 | if (packing) |
1031 | { |
1032 | new_object->children = packing->children; |
1033 | packing->children = NULL; |
1034 | } |
1035 | |
1036 | prop = g_new0 (Element, 1); |
1037 | prop->element_name = g_strdup (str: "property" ); |
1038 | prop->attribute_names = g_new0 (char *, 2); |
1039 | prop->attribute_names[0] = g_strdup (str: "name" ); |
1040 | prop->attribute_values = g_new0 (char *, 2); |
1041 | prop->attribute_values[0] = g_strdup (str: "child" ); |
1042 | prop->children = g_list_append (list: prop->children, data: object); |
1043 | prop->parent = new_object; |
1044 | new_object->children = g_list_append (list: new_object->children, data: prop); |
1045 | |
1046 | if (tab_obj) |
1047 | { |
1048 | prop = g_new0 (Element, 1); |
1049 | prop->element_name = g_strdup (str: "property" ); |
1050 | prop->attribute_names = g_new0 (char *, 2); |
1051 | prop->attribute_names[0] = g_strdup (str: "name" ); |
1052 | prop->attribute_values = g_new0 (char *, 2); |
1053 | prop->attribute_values[0] = g_strdup (str: "tab" ); |
1054 | prop->children = g_list_append (list: prop->children, data: tab_obj); |
1055 | prop->parent = new_object; |
1056 | new_object->children = g_list_append (list: new_object->children, data: prop); |
1057 | } |
1058 | |
1059 | g_list_free (list: child->children); |
1060 | child->children = g_list_append (NULL, data: new_object); |
1061 | |
1062 | return child; |
1063 | } |
1064 | |
1065 | static void |
1066 | rewrite_notebook (Element *element, |
1067 | MyParserData *data) |
1068 | { |
1069 | GList *l, *new_children; |
1070 | |
1071 | new_children = NULL; |
1072 | for (l = element->children; l; l = l->next) |
1073 | { |
1074 | Element *child = l->data; |
1075 | Element *tab = l->next ? l->next->data : NULL; |
1076 | |
1077 | if (tab && has_attribute (elt: tab, name: "type" , value: "tab" )) |
1078 | { |
1079 | new_children = g_list_append (list: new_children, data: rewrite_notebook_page (child, tab, data)); |
1080 | l = l->next; /* skip the tab */ |
1081 | } |
1082 | else |
1083 | new_children = g_list_append (list: new_children, data: rewrite_notebook_page (child, NULL, data)); |
1084 | } |
1085 | |
1086 | g_list_free (list: element->children); |
1087 | element->children = new_children; |
1088 | } |
1089 | |
1090 | static void |
1091 | rewrite_pack_type_child (Element *element, |
1092 | MyParserData *data) |
1093 | { |
1094 | Element *pack_type = NULL; |
1095 | GList *l, *ll; |
1096 | |
1097 | if (!g_str_equal (v1: element->element_name, v2: "child" )) |
1098 | return; |
1099 | |
1100 | for (l = element->children; l; l = l->next) |
1101 | { |
1102 | Element *elt = l->data; |
1103 | |
1104 | if (g_str_equal (v1: elt->element_name, v2: "packing" )) |
1105 | { |
1106 | for (ll = elt->children; ll; ll = ll->next) |
1107 | { |
1108 | Element *elt2 = ll->data; |
1109 | |
1110 | if (g_str_equal (v1: elt2->element_name, v2: "property" ) && |
1111 | has_attribute (elt: elt2, name: "name" , value: "pack-type" )) |
1112 | { |
1113 | pack_type = elt2; |
1114 | elt->children = g_list_remove (list: elt->children, data: pack_type); |
1115 | if (elt->children == NULL) |
1116 | { |
1117 | element->children = g_list_remove (list: element->children, data: elt); |
1118 | free_element (data: elt); |
1119 | } |
1120 | break; |
1121 | } |
1122 | } |
1123 | } |
1124 | |
1125 | if (pack_type) |
1126 | break; |
1127 | } |
1128 | |
1129 | if (pack_type) |
1130 | { |
1131 | char **attnames = g_new0 (char *, g_strv_length (element->attribute_names) + 2); |
1132 | char **attvalues = g_new0 (char *, g_strv_length (element->attribute_names) + 2); |
1133 | int i; |
1134 | |
1135 | for (i = 0; element->attribute_names[i]; i++) |
1136 | { |
1137 | attnames[i] = g_strdup (str: element->attribute_names[i]); |
1138 | attvalues[i] = g_strdup (str: element->attribute_values[i]); |
1139 | } |
1140 | |
1141 | attnames[i] = g_strdup (str: "type" ); |
1142 | attvalues[i] = g_strdup (str: pack_type->data); |
1143 | |
1144 | g_strfreev (str_array: element->attribute_names); |
1145 | g_strfreev (str_array: element->attribute_values); |
1146 | |
1147 | element->attribute_names = attnames; |
1148 | element->attribute_values = attvalues; |
1149 | |
1150 | free_element (data: pack_type); |
1151 | } |
1152 | } |
1153 | |
1154 | static void |
1155 | rewrite_pack_type (Element *element, |
1156 | MyParserData *data) |
1157 | { |
1158 | GList *l; |
1159 | |
1160 | for (l = element->children; l; l = l->next) |
1161 | { |
1162 | Element *elt = l->data; |
1163 | if (g_str_equal (v1: elt->element_name, v2: "child" )) |
1164 | rewrite_pack_type_child (element: elt, data); |
1165 | } |
1166 | } |
1167 | |
1168 | static void |
1169 | rewrite_paned_child (Element *element, |
1170 | MyParserData *data, |
1171 | Element *child, |
1172 | const char *suffix) |
1173 | { |
1174 | Element *resize = NULL; |
1175 | Element *shrink = NULL; |
1176 | GList *l, *ll; |
1177 | |
1178 | for (l = child->children; l; l = l->next) |
1179 | { |
1180 | Element *elt = l->data; |
1181 | |
1182 | if (g_str_equal (v1: elt->element_name, v2: "packing" )) |
1183 | { |
1184 | for (ll = elt->children; ll; ll = ll->next) |
1185 | { |
1186 | Element *elt2 = ll->data; |
1187 | |
1188 | if (g_str_equal (v1: elt2->element_name, v2: "property" ) && |
1189 | has_attribute (elt: elt2, name: "name" , value: "resize" )) |
1190 | resize = elt2; |
1191 | else if (g_str_equal (v1: elt2->element_name, v2: "property" ) && |
1192 | has_attribute (elt: elt2, name: "name" , value: "shrink" )) |
1193 | shrink = elt2; |
1194 | } |
1195 | if (resize) |
1196 | elt->children = g_list_remove (list: elt->children, data: resize); |
1197 | if (shrink) |
1198 | elt->children = g_list_remove (list: elt->children, data: shrink); |
1199 | if (elt->children == NULL) |
1200 | { |
1201 | child->children = g_list_remove (list: child->children, data: elt); |
1202 | free_element (data: elt); |
1203 | } |
1204 | } |
1205 | |
1206 | if (resize || shrink) |
1207 | break; |
1208 | } |
1209 | |
1210 | if (resize) |
1211 | { |
1212 | Element *elt; |
1213 | |
1214 | elt = g_new0 (Element, 1); |
1215 | elt->parent = element; |
1216 | elt->element_name = g_strdup (str: "property" ); |
1217 | elt->attribute_names = g_new0 (char *, 2); |
1218 | elt->attribute_names[0] = g_strdup (str: "name" ); |
1219 | elt->attribute_values = g_new0 (char *, 2); |
1220 | elt->attribute_values[0] = g_strconcat (string1: "resize-" , suffix, NULL); |
1221 | elt->data = g_strdup (str: resize->data); |
1222 | |
1223 | element->children = g_list_prepend (list: element->children, data: elt); |
1224 | |
1225 | free_element (data: resize); |
1226 | } |
1227 | |
1228 | if (shrink) |
1229 | { |
1230 | Element *elt; |
1231 | |
1232 | elt = g_new0 (Element, 1); |
1233 | elt->parent = element; |
1234 | elt->element_name = g_strdup (str: "property" ); |
1235 | elt->attribute_names = g_new0 (char *, 2); |
1236 | elt->attribute_names[0] = g_strdup (str: "name" ); |
1237 | elt->attribute_values = g_new0 (char *, 2); |
1238 | elt->attribute_values[0] = g_strconcat (string1: "shrink-" , suffix, NULL); |
1239 | elt->data = g_strdup (str: shrink->data); |
1240 | |
1241 | element->children = g_list_prepend (list: element->children, data: elt); |
1242 | |
1243 | free_element (data: shrink); |
1244 | } |
1245 | } |
1246 | |
1247 | static void |
1248 | rewrite_paned (Element *element, |
1249 | MyParserData *data) |
1250 | { |
1251 | Element *child1 = NULL; |
1252 | Element *child2 = NULL; |
1253 | GList *l; |
1254 | |
1255 | for (l = element->children; l; l = l->next) |
1256 | { |
1257 | Element *elt = l->data; |
1258 | |
1259 | if (g_str_equal (v1: elt->element_name, v2: "child" )) |
1260 | { |
1261 | if (child1 == NULL) |
1262 | child1 = elt; |
1263 | else if (child2 == NULL) |
1264 | child2 = elt; |
1265 | else |
1266 | break; |
1267 | } |
1268 | } |
1269 | |
1270 | if (child1) |
1271 | rewrite_paned_child (element, data, child: child1, suffix: "start-child" ); |
1272 | |
1273 | if (child2) |
1274 | rewrite_paned_child (element, data, child: child2, suffix: "end-child" ); |
1275 | } |
1276 | |
1277 | static void |
1278 | rewrite_dialog (Element *element, |
1279 | MyParserData *data) |
1280 | { |
1281 | Element *content_area = NULL; |
1282 | Element *vbox = NULL; |
1283 | Element *action_area = NULL; |
1284 | GList *l; |
1285 | |
1286 | for (l = element->children; l; l = l->next) |
1287 | { |
1288 | Element *elt = l->data; |
1289 | |
1290 | if (g_str_equal (v1: elt->element_name, v2: "child" ) && |
1291 | g_strcmp0 (str1: get_attribute_value (element: elt, name: "internal-child" ), str2: "vbox" ) == 0) |
1292 | { |
1293 | content_area = elt; |
1294 | break; |
1295 | } |
1296 | } |
1297 | |
1298 | if (!content_area || !content_area->children) |
1299 | return; |
1300 | |
1301 | vbox = content_area->children->data; |
1302 | |
1303 | for (l = vbox->children; l; l = l->next) |
1304 | { |
1305 | Element *elt = l->data; |
1306 | |
1307 | if (g_str_equal (v1: elt->element_name, v2: "child" ) && |
1308 | g_strcmp0 (str1: get_attribute_value (element: elt, name: "internal-child" ), str2: "action_area" ) == 0) |
1309 | { |
1310 | action_area = elt; |
1311 | break; |
1312 | } |
1313 | } |
1314 | |
1315 | if (!action_area) |
1316 | return; |
1317 | |
1318 | set_attribute_value (element: content_area, name: "internal-child" , value: "content_area" ); |
1319 | vbox->children = g_list_remove (list: vbox->children, data: action_area); |
1320 | action_area->parent = element; |
1321 | element->children = g_list_append (list: element->children, data: action_area); |
1322 | |
1323 | for (l = action_area->children; l; l = l->next) |
1324 | { |
1325 | Element *elt = l->data; |
1326 | |
1327 | if (g_str_equal (v1: elt->element_name, v2: "packing" )) |
1328 | { |
1329 | action_area->children = g_list_remove (list: action_area->children, data: elt); |
1330 | free_element (data: elt); |
1331 | break; |
1332 | } |
1333 | } |
1334 | |
1335 | } |
1336 | |
1337 | static void |
1338 | rewrite_grid_layout_prop (Element *element, |
1339 | const char *attr_name, |
1340 | const char *old_value, |
1341 | const char *new_value) |
1342 | { |
1343 | if (g_str_equal (v1: element->element_name, v2: "property" )) |
1344 | { |
1345 | char *canonical_name; |
1346 | |
1347 | canonical_name = g_strdup (str: old_value); |
1348 | g_strdelimit (string: canonical_name, delimiters: "_" , new_delimiter: '-'); |
1349 | |
1350 | if (has_attribute (elt: element, name: attr_name, value: old_value) || |
1351 | has_attribute (elt: element, name: attr_name, value: canonical_name)) |
1352 | { |
1353 | set_attribute_value (element, name: attr_name, value: new_value); |
1354 | } |
1355 | |
1356 | g_free (mem: canonical_name); |
1357 | } |
1358 | } |
1359 | |
1360 | static void |
1361 | rewrite_grid_layout (Element *element, |
1362 | MyParserData *data) |
1363 | { |
1364 | struct _Prop { |
1365 | const char *attr_name; |
1366 | const char *old_value; |
1367 | const char *new_value; |
1368 | } props[] = { |
1369 | { "name" , "left_attach" , "column" , }, |
1370 | { "name" , "top_attach" , "row" , }, |
1371 | { "name" , "width" , "column-span" , }, |
1372 | { "name" , "height" , "row-span" , }, |
1373 | }; |
1374 | GList *l, *ll; |
1375 | |
1376 | for (l = element->children; l; l = l->next) |
1377 | { |
1378 | Element *child = l->data; |
1379 | |
1380 | if (g_str_equal (v1: child->element_name, v2: "child" )) |
1381 | { |
1382 | Element *object = NULL; |
1383 | Element *packing = NULL; |
1384 | |
1385 | for (ll = child->children; ll; ll = ll->next) |
1386 | { |
1387 | Element *elt2 = ll->data; |
1388 | |
1389 | if (g_str_equal (v1: elt2->element_name, v2: "object" )) |
1390 | object = elt2; |
1391 | |
1392 | if (g_str_equal (v1: elt2->element_name, v2: "packing" )) |
1393 | packing = elt2; |
1394 | } |
1395 | |
1396 | if (object && packing) |
1397 | { |
1398 | int i; |
1399 | |
1400 | child->children = g_list_remove (list: child->children, data: packing); |
1401 | |
1402 | g_free (mem: packing->element_name); |
1403 | packing->element_name = g_strdup (str: "layout" ); |
1404 | |
1405 | packing->parent = object; |
1406 | object->children = g_list_append (list: object->children, data: packing); |
1407 | |
1408 | for (ll = packing->children; ll; ll = ll->next) |
1409 | { |
1410 | Element *elt = ll->data; |
1411 | |
1412 | for (i = 0; i < G_N_ELEMENTS (props); i++) |
1413 | rewrite_grid_layout_prop (element: elt, |
1414 | attr_name: props[i].attr_name, |
1415 | old_value: props[i].old_value, |
1416 | new_value: props[i].new_value); |
1417 | } |
1418 | } |
1419 | } |
1420 | } |
1421 | } |
1422 | |
1423 | static Element * |
1424 | add_element (Element *parent, |
1425 | const char *element_name) |
1426 | { |
1427 | Element *child; |
1428 | |
1429 | child = g_new0 (Element, 1); |
1430 | child->parent = parent; |
1431 | child->element_name = g_strdup (str: element_name); |
1432 | child->attribute_names = g_new0 (char *, 1); |
1433 | child->attribute_values = g_new0 (char *, 1); |
1434 | parent->children = g_list_prepend (list: parent->children, data: child); |
1435 | |
1436 | return child; |
1437 | } |
1438 | |
1439 | static Element * |
1440 | write_box_prop (Element *element, |
1441 | Element *parent, |
1442 | const char *name, |
1443 | const char *value) |
1444 | { |
1445 | |
1446 | if (element) |
1447 | g_free (mem: element->data); |
1448 | else |
1449 | { |
1450 | element = add_element (parent, element_name: "property" ); |
1451 | set_attribute_value (element, name: "name" , value: name); |
1452 | } |
1453 | element->data = g_strdup (str: value); |
1454 | |
1455 | return element; |
1456 | } |
1457 | |
1458 | static void |
1459 | rewrite_box (Element *element, |
1460 | MyParserData *data) |
1461 | { |
1462 | GList *l, *ll; |
1463 | GtkOrientation orientation = GTK_ORIENTATION_HORIZONTAL; |
1464 | |
1465 | if (g_str_equal (v1: get_class_name (element), v2: "GtkVBox" )) |
1466 | write_box_prop (NULL, parent: element, name: "orientation" , value: "vertical" ); |
1467 | |
1468 | if (!g_str_equal (v1: get_class_name (element), v2: "GtkBox" )) |
1469 | set_attribute_value (element, name: "class" , value: "GtkBox" ); |
1470 | |
1471 | for (l = element->children; l; l = l->next) |
1472 | { |
1473 | Element *child = l->data; |
1474 | |
1475 | if (g_str_equal (v1: child->element_name, v2: "property" )) |
1476 | { |
1477 | if (has_attribute (elt: child, name: "name" , value: "orientation" )) |
1478 | { |
1479 | GValue value = G_VALUE_INIT; |
1480 | |
1481 | if (gtk_builder_value_from_string_type (builder: data->builder, |
1482 | type: GTK_TYPE_ORIENTATION, |
1483 | string: child->data, |
1484 | value: &value, |
1485 | NULL)) |
1486 | orientation = g_value_get_enum (value: &value); |
1487 | } |
1488 | } |
1489 | } |
1490 | |
1491 | for (l = element->children; l; l = l->next) |
1492 | { |
1493 | Element *child = l->data; |
1494 | if (g_str_equal (v1: child->element_name, v2: "child" )) |
1495 | { |
1496 | Element *object = NULL; |
1497 | Element *packing = NULL; |
1498 | |
1499 | for (ll = child->children; ll; ll = ll->next) |
1500 | { |
1501 | Element *elt2 = ll->data; |
1502 | |
1503 | if (g_str_equal (v1: elt2->element_name, v2: "object" )) |
1504 | object = elt2; |
1505 | |
1506 | if (g_str_equal (v1: elt2->element_name, v2: "packing" )) |
1507 | packing = elt2; |
1508 | } |
1509 | |
1510 | if (object && packing) |
1511 | { |
1512 | Element *halign = NULL; |
1513 | Element *hexpand = NULL; |
1514 | Element *valign = NULL; |
1515 | Element *vexpand = NULL; |
1516 | |
1517 | gboolean expand = FALSE; |
1518 | gboolean fill = TRUE; |
1519 | |
1520 | for (ll = object->children; ll; ll = ll->next) |
1521 | { |
1522 | Element *elt = ll->data; |
1523 | if (g_str_equal (v1: elt->element_name, v2: "property" )) |
1524 | { |
1525 | if (has_attribute (elt, name: "name" , value: "halign" )) |
1526 | halign = elt; |
1527 | else if (has_attribute (elt, name: "name" , value: "hexpand" )) |
1528 | hexpand = elt; |
1529 | else if (has_attribute (elt, name: "name" , value: "valign" )) |
1530 | valign = elt; |
1531 | else if (has_attribute (elt, name: "name" , value: "vexpand" )) |
1532 | vexpand = elt; |
1533 | } |
1534 | } |
1535 | |
1536 | for (ll = packing->children; ll; ll = ll->next) |
1537 | { |
1538 | Element *elt = ll->data; |
1539 | |
1540 | if (has_attribute (elt, name: "name" , value: "expand" )) |
1541 | { |
1542 | GValue value = G_VALUE_INIT; |
1543 | |
1544 | if (gtk_builder_value_from_string_type (builder: data->builder, |
1545 | G_TYPE_BOOLEAN, |
1546 | string: elt->data, |
1547 | value: &value, |
1548 | NULL)) |
1549 | expand = g_value_get_boolean (value: &value); |
1550 | } |
1551 | |
1552 | if (has_attribute (elt, name: "name" , value: "fill" )) |
1553 | { |
1554 | GValue value = G_VALUE_INIT; |
1555 | |
1556 | if (gtk_builder_value_from_string_type (builder: data->builder, |
1557 | G_TYPE_BOOLEAN, |
1558 | string: elt->data, |
1559 | value: &value, |
1560 | NULL)) |
1561 | fill = g_value_get_boolean (value: &value); |
1562 | } |
1563 | } |
1564 | |
1565 | if (orientation == GTK_ORIENTATION_HORIZONTAL) |
1566 | { |
1567 | if (expand) |
1568 | write_box_prop (element: hexpand, parent: object, name: "hexpand" , value: "1" ); |
1569 | if (!fill) |
1570 | write_box_prop (element: halign, parent: object, name: "halign" , value: "center" ); |
1571 | } |
1572 | else if (orientation == GTK_ORIENTATION_VERTICAL) |
1573 | { |
1574 | if (expand) |
1575 | write_box_prop (element: vexpand, parent: object, name: "vexpand" , value: "1" ); |
1576 | if (!fill) |
1577 | write_box_prop (element: valign, parent: object, name: "valign" , value: "center" ); |
1578 | } |
1579 | |
1580 | child->children = g_list_remove (list: child->children, data: packing); |
1581 | free_element (data: packing); |
1582 | } |
1583 | } |
1584 | } |
1585 | } |
1586 | |
1587 | static void |
1588 | rewrite_bin_child (Element *element, |
1589 | MyParserData *data) |
1590 | { |
1591 | GList *l, *ll; |
1592 | const char *class_name; |
1593 | GType type; |
1594 | |
1595 | for (l = element->children; l; l = l->next) |
1596 | { |
1597 | Element *child = l->data; |
1598 | Element *object = NULL; |
1599 | |
1600 | if (!g_str_equal (v1: child->element_name, v2: "child" ) || |
1601 | has_attribute (elt: child, name: "type" , NULL)) |
1602 | continue; |
1603 | |
1604 | for (ll = child->children; ll; ll = ll->next) |
1605 | { |
1606 | Element *elem = ll->data; |
1607 | |
1608 | if (!g_str_equal (v1: elem->element_name, v2: "object" )) |
1609 | continue; |
1610 | |
1611 | class_name = get_attribute_value (element: elem, name: "class" ); |
1612 | if (!class_name) |
1613 | continue; |
1614 | |
1615 | type = g_type_from_name (name: class_name); |
1616 | if (!g_type_is_a (type, GTK_TYPE_WIDGET)) |
1617 | continue; |
1618 | |
1619 | object = elem; |
1620 | } |
1621 | |
1622 | if (object) |
1623 | { |
1624 | g_free (mem: child->element_name); |
1625 | g_strfreev (str_array: child->attribute_names); |
1626 | g_strfreev (str_array: child->attribute_values); |
1627 | child->element_name = g_strdup (str: "property" ); |
1628 | child->attribute_names = g_new0 (char *, 2); |
1629 | child->attribute_names[0] = g_strdup (str: "name" ); |
1630 | child->attribute_values = g_new0 (char *, 2); |
1631 | child->attribute_values[0] = g_strdup (str: "child" ); |
1632 | break; |
1633 | } |
1634 | } |
1635 | } |
1636 | |
1637 | static gboolean |
1638 | remove_boolean_prop (Element *element, |
1639 | MyParserData *data, |
1640 | const char *prop_name, |
1641 | gboolean *value) |
1642 | { |
1643 | GList *l; |
1644 | |
1645 | for (l = element->children; l; l = l->next) |
1646 | { |
1647 | Element *child = l->data; |
1648 | |
1649 | if (g_str_equal (v1: child->element_name, v2: "property" ) && |
1650 | has_attribute (elt: child, name: "name" , value: prop_name)) |
1651 | { |
1652 | *value = strcmp (s1: canonical_boolean_value (data, string: child->data), s2: "1" ) == 0; |
1653 | element->children = g_list_remove (list: element->children, data: child); |
1654 | free_element (data: child); |
1655 | return TRUE; |
1656 | } |
1657 | } |
1658 | |
1659 | return FALSE; |
1660 | } |
1661 | |
1662 | static void |
1663 | rewrite_radio_button (Element *element, |
1664 | MyParserData *data) |
1665 | { |
1666 | gboolean draw_indicator = TRUE; |
1667 | |
1668 | if (!remove_boolean_prop (element, data, prop_name: "draw-indicator" , value: &draw_indicator)) |
1669 | remove_boolean_prop (element, data, prop_name: "draw_indicator" , value: &draw_indicator); |
1670 | |
1671 | if (draw_indicator) |
1672 | set_attribute_value (element, name: "class" , value: "GtkCheckButton" ); |
1673 | else |
1674 | set_attribute_value (element, name: "class" , value: "GtkToggleButton" ); |
1675 | |
1676 | } |
1677 | |
1678 | static gboolean |
1679 | has_prop (Element *element, |
1680 | MyParserData *data, |
1681 | const char *prop_name) |
1682 | { |
1683 | GList *l; |
1684 | |
1685 | for (l = element->children; l; l = l->next) |
1686 | { |
1687 | Element *child = l->data; |
1688 | |
1689 | if (g_str_equal (v1: child->element_name, v2: "property" ) && |
1690 | has_attribute (elt: child, name: "name" , value: prop_name)) |
1691 | return TRUE; |
1692 | } |
1693 | |
1694 | return FALSE; |
1695 | } |
1696 | |
1697 | static void |
1698 | rewrite_scale (Element *element, |
1699 | MyParserData *data) |
1700 | { |
1701 | if (!has_prop (element, data, prop_name: "draw-value" ) && |
1702 | !has_prop (element, data, prop_name: "draw_value" )) |
1703 | { |
1704 | Element *child; |
1705 | child = add_element (parent: element, element_name: "property" ); |
1706 | set_attribute_value (element: child, name: "name" , value: "draw-value" ); |
1707 | child->data = g_strdup (str: "1" ); |
1708 | } |
1709 | } |
1710 | |
1711 | static void |
1712 | rewrite_requires (Element *element, |
1713 | MyParserData *data) |
1714 | { |
1715 | if (has_attribute (elt: element, name: "lib" , value: "gtk+" )) |
1716 | { |
1717 | set_attribute_value (element, name: "lib" , value: "gtk" ); |
1718 | set_attribute_value (element, name: "version" , value: "4.0" ); |
1719 | } |
1720 | } |
1721 | |
1722 | static void |
1723 | rewrite_overlay (Element *element, |
1724 | MyParserData *data) |
1725 | { |
1726 | GList *l, *ll; |
1727 | |
1728 | for (l = element->children; l; l = l->next) |
1729 | { |
1730 | Element *child = l->data; |
1731 | |
1732 | if (g_str_equal (v1: child->element_name, v2: "child" )) |
1733 | { |
1734 | Element *object = NULL; |
1735 | Element *packing = NULL; |
1736 | |
1737 | for (ll = child->children; ll; ll = ll->next) |
1738 | { |
1739 | Element *elt2 = ll->data; |
1740 | |
1741 | if (g_str_equal (v1: elt2->element_name, v2: "object" )) |
1742 | object = elt2; |
1743 | |
1744 | if (g_str_equal (v1: elt2->element_name, v2: "packing" )) |
1745 | packing = elt2; |
1746 | } |
1747 | |
1748 | if (object && packing) |
1749 | { |
1750 | child->children = g_list_remove (list: child->children, data: packing); |
1751 | |
1752 | for (ll = packing->children; ll; ll = ll->next) |
1753 | { |
1754 | Element *elt2 = ll->data; |
1755 | |
1756 | if (g_str_equal (v1: elt2->element_name, v2: "property" ) && |
1757 | (has_attribute (elt: elt2, name: "name" , value: "pass-through" ) || |
1758 | has_attribute (elt: elt2, name: "name" , value: "pass_through" ))) |
1759 | { |
1760 | const char *b = canonical_boolean_value (data, string: elt2->data); |
1761 | if (g_str_equal (v1: b, v2: "1" )) |
1762 | { |
1763 | Element *new_prop; |
1764 | |
1765 | new_prop = add_element (parent: object, element_name: "property" ); |
1766 | set_attribute_value (element: new_prop, name: "name" , value: "can-target" ); |
1767 | new_prop->data = g_strdup (str: "0" ); |
1768 | } |
1769 | break; |
1770 | } |
1771 | } |
1772 | |
1773 | free_element (data: packing); |
1774 | } |
1775 | } |
1776 | } |
1777 | } |
1778 | |
1779 | static void |
1780 | rewrite_toolbar (Element *element, |
1781 | MyParserData *data) |
1782 | { |
1783 | GList *l, *ll; |
1784 | |
1785 | set_attribute_value (element, name: "class" , value: "GtkBox" ); |
1786 | |
1787 | for (l = element->children; l; l = l->next) |
1788 | { |
1789 | Element *child = l->data; |
1790 | |
1791 | if (g_str_equal (v1: child->element_name, v2: "property" ) && |
1792 | (has_attribute (elt: child, name: "name" , value: "toolbar_style" ) || |
1793 | has_attribute (elt: child, name: "name" , value: "toolbar-style" ))) |
1794 | { |
1795 | element->children = g_list_remove (list: element->children, data: child); |
1796 | free_element (data: child); |
1797 | break; |
1798 | } |
1799 | } |
1800 | |
1801 | for (l = element->children; l; l = l->next) |
1802 | { |
1803 | Element *child = l->data; |
1804 | Element *object = NULL; |
1805 | Element *packing = NULL; |
1806 | |
1807 | if (!g_str_equal (v1: child->element_name, v2: "child" )) |
1808 | continue; |
1809 | |
1810 | for (ll = child->children; ll; ll = ll->next) |
1811 | { |
1812 | Element *elt2 = ll->data; |
1813 | |
1814 | if (g_str_equal (v1: elt2->element_name, v2: "object" )) |
1815 | object = elt2; |
1816 | |
1817 | if (g_str_equal (v1: elt2->element_name, v2: "packing" )) |
1818 | packing = elt2; |
1819 | } |
1820 | |
1821 | if (object) |
1822 | { |
1823 | const char *class_name; |
1824 | |
1825 | class_name = get_class_name (element: object); |
1826 | |
1827 | if (g_str_equal (v1: class_name, v2: "GtkToolButton" )) |
1828 | { |
1829 | set_attribute_value (element: object, name: "class" , value: "GtkButton" ); |
1830 | } |
1831 | else if (g_str_equal (v1: class_name, v2: "GtkToggleToolButton" ) || |
1832 | g_str_equal (v1: class_name, v2: "GtkRadioToolButton" )) |
1833 | { |
1834 | set_attribute_value (element: object, name: "class" , value: "GtkToggleButton" ); |
1835 | } |
1836 | else if (g_str_equal (v1: class_name, v2: "GtkSeparatorToolItem" )) |
1837 | { |
1838 | Element *prop; |
1839 | |
1840 | set_attribute_value (element: object, name: "class" , value: "GtkSeparator" ); |
1841 | prop = add_element (parent: object, element_name: "property" ); |
1842 | set_attribute_value (element: prop, name: "name" , value: "orientation" ); |
1843 | prop->data = g_strdup (str: "vertical" ); |
1844 | } |
1845 | } |
1846 | |
1847 | if (packing) |
1848 | child->children = g_list_remove (list: child->children, data: packing); |
1849 | } |
1850 | |
1851 | { |
1852 | Element *child; |
1853 | |
1854 | child = add_element (parent: element, element_name: "property" ); |
1855 | set_attribute_value (element: child, name: "name" , value: "css-classes" ); |
1856 | child->data = g_strdup (str: "toolbar" ); |
1857 | } |
1858 | } |
1859 | |
1860 | static void |
1861 | rewrite_fixed (Element *element, |
1862 | MyParserData *data) |
1863 | { |
1864 | GList *l, *ll; |
1865 | |
1866 | for (l = element->children; l; l = l->next) |
1867 | { |
1868 | Element *child = l->data; |
1869 | |
1870 | if (g_str_equal (v1: child->element_name, v2: "child" )) |
1871 | { |
1872 | Element *object = NULL; |
1873 | Element *packing = NULL; |
1874 | |
1875 | for (ll = child->children; ll; ll = ll->next) |
1876 | { |
1877 | Element *elt2 = ll->data; |
1878 | |
1879 | if (g_str_equal (v1: elt2->element_name, v2: "object" )) |
1880 | object = elt2; |
1881 | |
1882 | if (g_str_equal (v1: elt2->element_name, v2: "packing" )) |
1883 | packing = elt2; |
1884 | } |
1885 | |
1886 | if (object && packing) |
1887 | { |
1888 | int x = 0; |
1889 | int y = 0; |
1890 | Element *layout; |
1891 | Element *new_prop; |
1892 | GskTransform *transform; |
1893 | |
1894 | for (ll = packing->children; ll; ll = ll->next) |
1895 | { |
1896 | Element *elt2 = ll->data; |
1897 | GValue value = G_VALUE_INIT; |
1898 | |
1899 | if (has_attribute (elt: elt2, name: "name" , value: "x" )) |
1900 | { |
1901 | if (gtk_builder_value_from_string_type (builder: data->builder, G_TYPE_INT, string: elt2->data, value: &value, NULL)) |
1902 | x = g_value_get_int (value: &value); |
1903 | } |
1904 | else if (has_attribute (elt: elt2, name: "name" , value: "y" )) |
1905 | { |
1906 | if (gtk_builder_value_from_string_type (builder: data->builder, G_TYPE_INT, string: elt2->data, value: &value, NULL)) |
1907 | y = g_value_get_int (value: &value); |
1908 | } |
1909 | } |
1910 | |
1911 | child->children = g_list_remove (list: child->children, data: packing); |
1912 | free_element (data: packing); |
1913 | |
1914 | layout = add_element (parent: object, element_name: "layout" ); |
1915 | new_prop = add_element (parent: layout, element_name: "property" ); |
1916 | set_attribute_value (element: new_prop, name: "name" , value: "transform" ); |
1917 | |
1918 | transform = gsk_transform_translate (NULL, point: &GRAPHENE_POINT_INIT (x, y)); |
1919 | new_prop->data = gsk_transform_to_string (self: transform); |
1920 | gsk_transform_unref (self: transform); |
1921 | } |
1922 | } |
1923 | } |
1924 | } |
1925 | |
1926 | /* returns TRUE to remove the element from the parent */ |
1927 | static gboolean |
1928 | simplify_element (Element *element, |
1929 | MyParserData *data) |
1930 | { |
1931 | GList *l; |
1932 | GType type; |
1933 | |
1934 | if (!is_pcdata_element (element)) |
1935 | { |
1936 | g_clear_pointer (&element->data, g_free); |
1937 | } |
1938 | else if (g_str_equal (v1: element->element_name, v2: "property" )) |
1939 | { |
1940 | if (property_is_boolean (element, data)) |
1941 | { |
1942 | const char *b = canonical_boolean_value (data, string: element->data); |
1943 | g_free (mem: element->data); |
1944 | element->data = g_strdup (str: b); |
1945 | } |
1946 | else if (property_is_enum (element, data, type: &type)) |
1947 | { |
1948 | char *e = canonical_enum_value (data, type, string: element->data); |
1949 | g_free (mem: element->data); |
1950 | element->data = e; |
1951 | } |
1952 | |
1953 | for (int i = 0; element->attribute_names[i]; i++) |
1954 | { |
1955 | if (g_str_equal (v1: element->attribute_names[i], v2: "translatable" )) |
1956 | { |
1957 | const char *b = canonical_boolean_value (data, string: element->attribute_values[i]); |
1958 | g_free (mem: element->attribute_values[i]); |
1959 | element->attribute_values[i] = g_strdup (str: b); |
1960 | break; |
1961 | } |
1962 | } |
1963 | } |
1964 | |
1965 | l = element->children; |
1966 | while (l) |
1967 | { |
1968 | GList *next = l->next; |
1969 | Element *child = l->data; |
1970 | if (simplify_element (element: child, data)) |
1971 | { |
1972 | element->children = g_list_remove (list: element->children, data: child); |
1973 | free_element (data: child); |
1974 | } |
1975 | l = next; |
1976 | } |
1977 | |
1978 | if (is_container_element (element) && element->children == NULL) |
1979 | return TRUE; |
1980 | |
1981 | if (g_str_equal (v1: element->element_name, v2: "property" ) && |
1982 | property_can_be_omitted (element, data)) |
1983 | return TRUE; |
1984 | |
1985 | if (g_str_equal (v1: element->element_name, v2: "binding" )) |
1986 | { |
1987 | const char *property_name = get_attribute_value (element, name: "name" ); |
1988 | const char *class_name = get_class_name (element); |
1989 | if (!get_property_pspec (data, class_name, property_name, kind: PROP_KIND_OBJECT)) |
1990 | warn_missing_property (element, data, class_name, property_name, kind: PROP_KIND_OBJECT); |
1991 | } |
1992 | |
1993 | return FALSE; |
1994 | } |
1995 | |
1996 | static void |
1997 | simplify_tree (MyParserData *data) |
1998 | { |
1999 | simplify_element (element: data->root, data); |
2000 | } |
2001 | |
2002 | static gboolean |
2003 | rewrite_element (Element *element, |
2004 | MyParserData *data) |
2005 | { |
2006 | GList *l; |
2007 | |
2008 | l = element->children; |
2009 | while (l) |
2010 | { |
2011 | GList *next = l->next; |
2012 | Element *child = l->data; |
2013 | if (rewrite_element (element: child, data)) |
2014 | { |
2015 | element->children = g_list_remove (list: element->children, data: child); |
2016 | free_element (data: child); |
2017 | } |
2018 | l = next; |
2019 | } |
2020 | |
2021 | if (element_is_object_or_template (element) && |
2022 | g_str_equal (v1: get_class_name (element), v2: "GtkStack" )) |
2023 | rewrite_stack (element, data); |
2024 | |
2025 | if (element_is_object_or_template (element) && |
2026 | g_str_equal (v1: get_class_name (element), v2: "GtkAssistant" )) |
2027 | rewrite_assistant (element, data); |
2028 | |
2029 | if (element_is_object_or_template (element) && |
2030 | g_str_equal (v1: get_class_name (element), v2: "GtkNotebook" )) |
2031 | rewrite_notebook (element, data); |
2032 | |
2033 | if (element_is_object_or_template (element) && |
2034 | (g_str_equal (v1: get_class_name (element), v2: "GtkActionBar" ) || |
2035 | g_str_equal (v1: get_class_name (element), v2: "GtkHeaderBar" ))) |
2036 | rewrite_pack_type (element, data); |
2037 | |
2038 | if (element_is_object_or_template (element) && |
2039 | g_str_equal (v1: get_class_name (element), v2: "GtkToolbar" )) |
2040 | rewrite_toolbar (element, data); |
2041 | |
2042 | if (element_is_object_or_template (element) && |
2043 | g_str_equal (v1: get_class_name (element), v2: "GtkPaned" )) |
2044 | rewrite_paned (element, data); |
2045 | |
2046 | if (element_is_object_or_template (element) && |
2047 | g_str_equal (v1: get_class_name (element), v2: "GtkDialog" )) |
2048 | rewrite_dialog (element, data); |
2049 | |
2050 | if (element_is_object_or_template (element) && |
2051 | g_str_equal (v1: get_class_name (element), v2: "GtkOverlay" )) |
2052 | rewrite_overlay (element, data); |
2053 | |
2054 | if (element_is_object_or_template (element) && |
2055 | g_str_equal (v1: get_class_name (element), v2: "GtkGrid" )) |
2056 | rewrite_grid_layout (element, data); |
2057 | |
2058 | if (element_is_object_or_template (element) && |
2059 | (g_str_equal (v1: get_class_name (element), v2: "GtkHBox" ) || |
2060 | g_str_equal (v1: get_class_name (element), v2: "GtkVBox" ) || |
2061 | g_str_equal (v1: get_class_name (element), v2: "GtkBox" ))) |
2062 | rewrite_box (element, data); |
2063 | |
2064 | if (element_is_object_or_template (element) && |
2065 | g_str_equal (v1: get_class_name (element), v2: "GtkFixed" )) |
2066 | rewrite_fixed (element, data); |
2067 | |
2068 | if (element_is_object_or_template (element) && |
2069 | (g_str_equal (v1: get_class_name (element), v2: "GtkAspectFrame" ) || |
2070 | g_str_equal (v1: get_class_name (element), v2: "GtkComboBox" ) || |
2071 | g_str_equal (v1: get_class_name (element), v2: "GtkComboBoxText" ) || |
2072 | g_str_equal (v1: get_class_name (element), v2: "GtkFlowBoxChild" ) || |
2073 | g_str_equal (v1: get_class_name (element), v2: "GtkFrame" ) || |
2074 | g_str_equal (v1: get_class_name (element), v2: "GtkListBoxRow" ) || |
2075 | g_str_equal (v1: get_class_name (element), v2: "GtkOverlay" ) || |
2076 | g_str_equal (v1: get_class_name (element), v2: "GtkPopover" ) || |
2077 | g_str_equal (v1: get_class_name (element), v2: "GtkPopoverMenu" ) || |
2078 | g_str_equal (v1: get_class_name (element), v2: "GtkRevealer" ) || |
2079 | g_str_equal (v1: get_class_name (element), v2: "GtkScrolledWindow" ) || |
2080 | g_str_equal (v1: get_class_name (element), v2: "GtkSearchBar" ) || |
2081 | g_str_equal (v1: get_class_name (element), v2: "GtkViewport" ) || |
2082 | g_str_equal (v1: get_class_name (element), v2: "GtkWindow" ))) |
2083 | rewrite_bin_child (element, data); |
2084 | |
2085 | if (element_is_object_or_template (element) && |
2086 | g_str_equal (v1: get_class_name (element), v2: "GtkRadioButton" )) |
2087 | rewrite_radio_button (element, data); |
2088 | |
2089 | if (element_is_object_or_template (element) && |
2090 | g_str_equal (v1: get_class_name (element), v2: "GtkScale" )) |
2091 | rewrite_scale (element, data); |
2092 | |
2093 | if (g_str_equal (v1: element->element_name, v2: "property" )) |
2094 | maybe_rename_property (element, data); |
2095 | |
2096 | if (g_str_equal (v1: element->element_name, v2: "property" ) && |
2097 | property_has_been_removed (element, data)) |
2098 | return TRUE; |
2099 | |
2100 | if (g_str_equal (v1: element->element_name, v2: "requires" )) |
2101 | rewrite_requires (element, data); |
2102 | |
2103 | return FALSE; |
2104 | } |
2105 | |
2106 | static void |
2107 | rewrite_tree (MyParserData *data) |
2108 | { |
2109 | rewrite_element (element: data->root, data); |
2110 | } |
2111 | |
2112 | /* For properties which have changed their default |
2113 | * value between 3 and 4, we make sure that their |
2114 | * old default value is present in the tree before |
2115 | * simplifying it. |
2116 | * |
2117 | * So far, this is just GtkWidget::visible, |
2118 | * changing its default from 0 to 1. |
2119 | */ |
2120 | static void |
2121 | add_old_default_properties (Element *element, |
2122 | MyParserData *data) |
2123 | { |
2124 | const char *class_name; |
2125 | GType type; |
2126 | |
2127 | if (!g_str_equal (v1: element->element_name, v2: "object" )) |
2128 | return; |
2129 | |
2130 | class_name = get_class_name (element); |
2131 | type = g_type_from_name (name: class_name); |
2132 | if (g_type_is_a (type, GTK_TYPE_WIDGET)) |
2133 | { |
2134 | GList *l; |
2135 | gboolean has_visible = FALSE; |
2136 | |
2137 | for (l = element->children; l; l = l->next) |
2138 | { |
2139 | Element *prop = l->data; |
2140 | const char *name = get_attribute_value (element: prop, name: "name" ); |
2141 | |
2142 | if (g_str_equal (v1: prop->element_name, v2: "property" ) && |
2143 | g_str_equal (v1: name, v2: "visible" )) |
2144 | has_visible = TRUE; |
2145 | } |
2146 | |
2147 | if (!has_visible) |
2148 | { |
2149 | Element *new_prop; |
2150 | |
2151 | new_prop = add_element (parent: element, element_name: "property" ); |
2152 | set_attribute_value (element: new_prop, name: "name" , value: "visible" ); |
2153 | new_prop->data = g_strdup (str: "0" ); |
2154 | } |
2155 | } |
2156 | } |
2157 | |
2158 | static void |
2159 | enhance_element (Element *element, |
2160 | MyParserData *data) |
2161 | { |
2162 | GList *l; |
2163 | |
2164 | if (strcmp (s1: element->element_name, s2: "requires" ) == 0 && |
2165 | has_attribute (elt: element, name: "lib" , value: "gtk+" )) |
2166 | { |
2167 | data->has_gtk_requires = TRUE; |
2168 | } |
2169 | |
2170 | add_old_default_properties (element, data); |
2171 | |
2172 | for (l = element->children; l; l = l->next) |
2173 | { |
2174 | Element *child = l->data; |
2175 | enhance_element (element: child, data); |
2176 | } |
2177 | |
2178 | if (element == data->root && !data->has_gtk_requires) |
2179 | { |
2180 | Element *requires = add_element (parent: element, element_name: "requires" ); |
2181 | set_attribute_value (element: requires, name: "lib" , value: "gtk+" ); |
2182 | set_attribute_value (element: requires, name: "version" , value: "3.0" ); |
2183 | } |
2184 | } |
2185 | |
2186 | static void |
2187 | enhance_tree (MyParserData *data) |
2188 | { |
2189 | enhance_element (element: data->root, data); |
2190 | } |
2191 | |
2192 | static void |
2193 | dump_element (Element *element, |
2194 | FILE *output, |
2195 | int indent) |
2196 | { |
2197 | g_fprintf (file: output, format: "%*s<%s" , indent, "" , element->element_name); |
2198 | if (element->attribute_names[0]) |
2199 | { |
2200 | int i; |
2201 | for (i = 0; element->attribute_names[i]; i++) |
2202 | { |
2203 | char *escaped = g_markup_escape_text (text: element->attribute_values[i], length: -1); |
2204 | g_fprintf (file: output, format: " %s=\"%s\"" , element->attribute_names[i], escaped); |
2205 | g_free (mem: escaped); |
2206 | } |
2207 | } |
2208 | if (element->children || element->data) |
2209 | { |
2210 | g_fprintf (file: output, format: ">" ); |
2211 | |
2212 | if (element->children) |
2213 | { |
2214 | GList *l; |
2215 | |
2216 | g_fprintf (file: output, format: "\n" ); |
2217 | for (l = element->children; l; l = l->next) |
2218 | { |
2219 | Element *child = l->data; |
2220 | dump_element (element: child, output, indent: indent + 2); |
2221 | } |
2222 | g_fprintf (file: output, format: "%*s" , indent, "" ); |
2223 | } |
2224 | else |
2225 | { |
2226 | if (is_cdata_property (element)) |
2227 | { |
2228 | g_fprintf (file: output, format: "<![CDATA[" ); |
2229 | g_fprintf (file: output, format: "%s" , element->data); |
2230 | g_fprintf (file: output, format: "]]>" ); |
2231 | } |
2232 | else |
2233 | { |
2234 | char *escaped = g_markup_escape_text (text: element->data, length: -1); |
2235 | g_fprintf (file: output, format: "%s" , escaped); |
2236 | g_free (mem: escaped); |
2237 | } |
2238 | } |
2239 | g_fprintf (file: output, format: "</%s>\n" , element->element_name); |
2240 | } |
2241 | else |
2242 | g_fprintf (file: output, format: "/>\n" ); |
2243 | } |
2244 | |
2245 | static void |
2246 | write_xml_declaration (FILE *output) |
2247 | { |
2248 | g_fprintf (file: output, format: "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" ); |
2249 | } |
2250 | |
2251 | static void |
2252 | dump_tree (MyParserData *data) |
2253 | { |
2254 | write_xml_declaration (output: data->output); |
2255 | dump_element (element: data->root, output: data->output, indent: 0); |
2256 | } |
2257 | |
2258 | static gboolean |
2259 | simplify_file (const char *filename, |
2260 | gboolean replace, |
2261 | gboolean convert3to4) |
2262 | { |
2263 | GMarkupParseContext *context; |
2264 | char *buffer; |
2265 | MyParserData data; |
2266 | GError *error = NULL; |
2267 | |
2268 | data.input_filename = filename; |
2269 | data.output_filename = NULL; |
2270 | data.convert3to4 = convert3to4; |
2271 | data.has_gtk_requires = FALSE; |
2272 | |
2273 | if (replace) |
2274 | { |
2275 | int fd; |
2276 | fd = g_file_open_tmp (tmpl: "gtk-builder-tool-XXXXXX" , name_used: &data.output_filename, NULL); |
2277 | data.output = fdopen (fd: fd, modes: "w" ); |
2278 | } |
2279 | else |
2280 | { |
2281 | data.output = stdout; |
2282 | } |
2283 | |
2284 | if (!g_file_get_contents (filename, contents: &buffer, NULL, error: &error)) |
2285 | { |
2286 | g_printerr (_("Can’t load “%s”: %s\n" ), filename, error->message); |
2287 | return FALSE; |
2288 | } |
2289 | |
2290 | data.root = NULL; |
2291 | data.current = NULL; |
2292 | data.value = g_string_new (init: "" ); |
2293 | |
2294 | context = g_markup_parse_context_new (parser: &parser, flags: G_MARKUP_TREAT_CDATA_AS_TEXT, user_data: &data, NULL); |
2295 | if (!g_markup_parse_context_parse (context, text: buffer, text_len: -1, error: &error)) |
2296 | { |
2297 | g_printerr (_("Can’t parse “%s”: %s\n" ), filename, error->message); |
2298 | return FALSE; |
2299 | } |
2300 | |
2301 | if (!g_markup_parse_context_end_parse (context, error: &error)) |
2302 | { |
2303 | g_printerr (_("Can’t parse “%s”: %s\n" ), filename, error->message); |
2304 | return FALSE; |
2305 | } |
2306 | |
2307 | if (data.root == NULL) |
2308 | { |
2309 | g_printerr (_("Can’t parse “%s”\n" ), filename); |
2310 | return FALSE; |
2311 | } |
2312 | |
2313 | data.builder = gtk_builder_new (); |
2314 | |
2315 | if (data.convert3to4) |
2316 | { |
2317 | enhance_tree (data: &data); |
2318 | rewrite_tree (data: &data); |
2319 | } |
2320 | simplify_tree (data: &data); |
2321 | |
2322 | dump_tree (data: &data); |
2323 | |
2324 | fclose (stream: data.output); |
2325 | |
2326 | if (data.output_filename) |
2327 | { |
2328 | char *content; |
2329 | gsize length; |
2330 | |
2331 | if (!g_file_get_contents (filename: data.output_filename, contents: &content, length: &length, error: &error)) |
2332 | { |
2333 | g_printerr (_("Failed to read “%s”: %s\n" ), data.output_filename, error->message); |
2334 | return FALSE; |
2335 | } |
2336 | |
2337 | if (!g_file_set_contents (filename: data.input_filename, contents: content, length, error: &error)) |
2338 | { |
2339 | g_printerr (_("Failed to write %s: “%s”\n" ), data.input_filename, error->message); |
2340 | return FALSE; |
2341 | } |
2342 | } |
2343 | |
2344 | return TRUE; |
2345 | } |
2346 | |
2347 | void |
2348 | do_simplify (int *argc, |
2349 | const char ***argv) |
2350 | { |
2351 | gboolean replace = FALSE; |
2352 | gboolean convert3to4 = FALSE; |
2353 | char **filenames = NULL; |
2354 | GOptionContext *ctx; |
2355 | const GOptionEntry entries[] = { |
2356 | { "replace" , 0, 0, G_OPTION_ARG_NONE, &replace, NULL, NULL }, |
2357 | { "3to4" , 0, 0, G_OPTION_ARG_NONE, &convert3to4, NULL, NULL }, |
2358 | { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames, NULL, NULL }, |
2359 | { NULL, } |
2360 | }; |
2361 | GError *error = NULL; |
2362 | int i; |
2363 | |
2364 | ctx = g_option_context_new (NULL); |
2365 | g_option_context_set_help_enabled (context: ctx, FALSE); |
2366 | g_option_context_add_main_entries (context: ctx, entries, NULL); |
2367 | |
2368 | if (!g_option_context_parse (context: ctx, argc, argv: (char ***)argv, error: &error)) |
2369 | { |
2370 | g_printerr (format: "%s\n" , error->message); |
2371 | g_error_free (error); |
2372 | exit (status: 1); |
2373 | } |
2374 | |
2375 | g_option_context_free (context: ctx); |
2376 | |
2377 | if (filenames == NULL) |
2378 | { |
2379 | g_printerr (_("No .ui file specified\n" )); |
2380 | exit (status: 1); |
2381 | } |
2382 | |
2383 | if (g_strv_length (str_array: filenames) > 1 && !replace) |
2384 | { |
2385 | g_printerr (_("Can only simplify a single .ui file without --replace\n" )); |
2386 | exit (status: 1); |
2387 | } |
2388 | |
2389 | for (i = 0; filenames[i]; i++) |
2390 | { |
2391 | if (!simplify_file (filename: filenames[i], replace, convert3to4)) |
2392 | exit (status: 1); |
2393 | } |
2394 | } |
2395 | |