1/*
2 * Copyright © 2019 Red Hat, Inc.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * Authors: Matthias Clasen
18 */
19
20#include "config.h"
21
22#include "constraint-editor-window.h"
23#include "constraint-view.h"
24#include "constraint-editor.h"
25#include "guide-editor.h"
26
27struct _ConstraintEditorWindow
28{
29 GtkApplicationWindow parent_instance;
30
31 GtkWidget *paned;
32 GtkWidget *view;
33 GtkWidget *list;
34};
35
36G_DEFINE_TYPE(ConstraintEditorWindow, constraint_editor_window, GTK_TYPE_APPLICATION_WINDOW);
37
38static GtkConstraintTarget *
39find_target (GListModel *model,
40 GtkConstraintTarget *orig)
41{
42 const char *name;
43 const char *model_name;
44 gpointer item;
45 int i;
46
47 if (orig == NULL)
48 return NULL;
49
50 if (GTK_IS_LABEL (orig))
51 name = gtk_label_get_label (GTK_LABEL (orig));
52 else if (GTK_IS_CONSTRAINT_GUIDE (ptr: orig))
53 name = gtk_constraint_guide_get_name (guide: GTK_CONSTRAINT_GUIDE (ptr: orig));
54 else
55 {
56 g_warning ("Don't know how to handle %s targets", G_OBJECT_TYPE_NAME (orig));
57 return NULL;
58 }
59 for (i = 0; i < g_list_model_get_n_items (list: model); i++)
60 {
61 item = g_list_model_get_item (list: model, position: i);
62 g_object_unref (object: item);
63 if (GTK_IS_WIDGET (item))
64 model_name = gtk_widget_get_name (GTK_WIDGET (item));
65 else
66 model_name = gtk_constraint_guide_get_name (guide: GTK_CONSTRAINT_GUIDE (ptr: item));
67
68 if (strcmp (s1: name, s2: model_name) == 0)
69 return GTK_CONSTRAINT_TARGET (ptr: item);
70 }
71 g_warning ("Failed to find target '%s'", name);
72
73 return NULL;
74}
75
76gboolean
77constraint_editor_window_load (ConstraintEditorWindow *self,
78 GFile *file)
79{
80 char *path;
81 GtkBuilder *builder;
82 GError *error = NULL;
83 GtkWidget *view;
84 GtkLayoutManager *layout;
85 GtkWidget *child;
86 const char *name;
87 gpointer item;
88 int i;
89 GListModel *list;
90
91 path = g_file_get_path (file);
92
93 builder = gtk_builder_new ();
94 if (!gtk_builder_add_from_file (builder, filename: path, error: &error))
95 {
96 g_print (format: "Could not load %s: %s", path, error->message);
97 g_error_free (error);
98 g_free (mem: path);
99 g_object_unref (object: builder);
100 return FALSE;
101 }
102
103 view = GTK_WIDGET (gtk_builder_get_object (builder, "view"));
104 if (!GTK_IS_BOX (view))
105 {
106 g_print (format: "Could not load %s: No GtkBox named 'view'", path);
107 g_free (mem: path);
108 g_object_unref (object: builder);
109 return FALSE;
110 }
111 layout = gtk_widget_get_layout_manager (widget: view);
112 if (!GTK_IS_CONSTRAINT_LAYOUT (ptr: layout))
113 {
114 g_print (format: "Could not load %s: Widget 'view' does not use GtkConstraintLayout", path);
115 g_free (mem: path);
116 g_object_unref (object: builder);
117 return FALSE;
118 }
119
120 for (child = gtk_widget_get_first_child (widget: view);
121 child;
122 child = gtk_widget_get_next_sibling (widget: child))
123 {
124 if (!GTK_IS_LABEL (child))
125 {
126 g_print (format: "Skipping non-GtkLabel child\n");
127 continue;
128 }
129
130 name = gtk_label_get_label (GTK_LABEL (child));
131 constraint_view_add_child (view: CONSTRAINT_VIEW (ptr: self->view), name);
132 }
133
134 list = gtk_constraint_layout_observe_guides (layout: GTK_CONSTRAINT_LAYOUT (ptr: layout));
135 for (i = 0; i < g_list_model_get_n_items (list); i++)
136 {
137 GtkConstraintGuide *guide, *clone;
138 int w, h;
139
140 item = g_list_model_get_item (list, position: i);
141 guide = GTK_CONSTRAINT_GUIDE (ptr: item);
142
143 /* need to clone here, to attach to the right targets */
144 clone = gtk_constraint_guide_new ();
145 gtk_constraint_guide_set_name (guide: clone, name: gtk_constraint_guide_get_name (guide));
146 gtk_constraint_guide_set_strength (guide: clone, strength: gtk_constraint_guide_get_strength (guide));
147 gtk_constraint_guide_get_min_size (guide, width: &w, height: &h);
148 gtk_constraint_guide_set_min_size (guide: clone, width: w, height: h);
149 gtk_constraint_guide_get_nat_size (guide, width: &w, height: &h);
150 gtk_constraint_guide_set_nat_size (guide: clone, width: w, height: h);
151 gtk_constraint_guide_get_max_size (guide, width: &w, height: &h);
152 gtk_constraint_guide_set_max_size (guide: clone, width: w, height: h);
153 constraint_view_add_guide (view: CONSTRAINT_VIEW (ptr: self->view), guide: clone);
154 g_object_unref (object: guide);
155 g_object_unref (object: clone);
156 }
157 g_object_unref (object: list);
158
159 list = gtk_constraint_layout_observe_constraints (layout: GTK_CONSTRAINT_LAYOUT (ptr: layout));
160 for (i = 0; i < g_list_model_get_n_items (list); i++)
161 {
162 GtkConstraint *constraint;
163 GtkConstraint *clone;
164 GtkConstraintTarget *target;
165 GtkConstraintTarget *source;
166 GtkConstraintAttribute source_attr;
167
168 item = g_list_model_get_item (list, position: i);
169 constraint = GTK_CONSTRAINT (ptr: item);
170
171 target = gtk_constraint_get_target (constraint);
172 source = gtk_constraint_get_source (constraint);
173 source_attr = gtk_constraint_get_source_attribute (constraint);
174
175 if (source == NULL && source_attr == GTK_CONSTRAINT_ATTRIBUTE_NONE)
176 clone = gtk_constraint_new_constant (target: find_target (model: constraint_view_get_model (view: CONSTRAINT_VIEW (ptr: self->view)), orig: target),
177 target_attribute: gtk_constraint_get_target_attribute (constraint),
178 relation: gtk_constraint_get_relation (constraint),
179 constant: gtk_constraint_get_constant (constraint),
180 strength: gtk_constraint_get_strength (constraint));
181 else
182 clone = gtk_constraint_new (target: find_target (model: constraint_view_get_model (view: CONSTRAINT_VIEW (ptr: self->view)), orig: target),
183 target_attribute: gtk_constraint_get_target_attribute (constraint),
184 relation: gtk_constraint_get_relation (constraint),
185 source: find_target (model: constraint_view_get_model (view: CONSTRAINT_VIEW (ptr: self->view)), orig: source),
186 source_attribute: source_attr,
187 multiplier: gtk_constraint_get_multiplier (constraint),
188 constant: gtk_constraint_get_constant (constraint),
189 strength: gtk_constraint_get_strength (constraint));
190
191 constraint_view_add_constraint (view: CONSTRAINT_VIEW (ptr: self->view), constraint: clone);
192
193 g_object_unref (object: constraint);
194 g_object_unref (object: clone);
195 }
196 g_object_unref (object: list);
197
198 g_free (mem: path);
199 g_object_unref (object: builder);
200
201 return TRUE;
202}
203
204static void
205open_response_cb (GtkNativeDialog *dialog,
206 int response,
207 ConstraintEditorWindow *self)
208{
209 gtk_native_dialog_hide (self: dialog);
210
211 if (response == GTK_RESPONSE_ACCEPT)
212 {
213 GFile *file;
214
215 file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
216 constraint_editor_window_load (self, file);
217 g_object_unref (object: file);
218 }
219
220 gtk_native_dialog_destroy (self: dialog);
221}
222
223static void
224open_cb (GtkWidget *button,
225 ConstraintEditorWindow *self)
226{
227 GtkFileChooserNative *dialog;
228
229 dialog = gtk_file_chooser_native_new (title: "Open file",
230 GTK_WINDOW (self),
231 action: GTK_FILE_CHOOSER_ACTION_OPEN,
232 accept_label: "_Load",
233 cancel_label: "_Cancel");
234 gtk_native_dialog_set_modal (self: GTK_NATIVE_DIALOG (ptr: dialog), TRUE);
235
236 GFile *cwd = g_file_new_for_path (path: ".");
237 gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), file: cwd, NULL);
238 g_object_unref (object: cwd);
239
240 g_signal_connect (dialog, "response", G_CALLBACK (open_response_cb), self);
241 gtk_native_dialog_show (self: GTK_NATIVE_DIALOG (ptr: dialog));
242}
243
244static void
245serialize_child (GString *str,
246 int indent,
247 GtkWidget *child)
248{
249 const char *name;
250
251 name = gtk_widget_get_name (widget: child);
252 g_string_append_printf (string: str, format: "%*s<child>\n", indent, "");
253 g_string_append_printf (string: str, format: "%*s <object class=\"GtkLabel\" id=\"%s\">\n", indent, "", name);
254 g_string_append_printf (string: str, format: "%*s <property name=\"label\">%s</property>\n", indent, "", name);
255 g_string_append_printf (string: str, format: "%*s </object>\n", indent, "");
256 g_string_append_printf (string: str, format: "%*s</child>\n", indent, "");
257}
258
259static char *
260serialize_model (GListModel *list)
261{
262 GString *str = g_string_new (init: "");
263 int i;
264
265 g_string_append (string: str, val: "<interface>\n");
266 g_string_append (string: str, val: " <object class=\"GtkBox\" id=\"view\">\n");
267 g_string_append (string: str, val: " <property name=\"layout-manager\">\n");
268 g_string_append (string: str, val: " <object class=\"GtkConstraintLayout\">\n");
269 g_string_append (string: str, val: " <constraints>\n");
270 for (i = 0; i < g_list_model_get_n_items (list); i++)
271 {
272 gpointer item = g_list_model_get_item (list, position: i);
273 g_object_unref (object: item);
274 if (GTK_IS_CONSTRAINT (ptr: item))
275 constraint_editor_serialize_constraint (str, indent: 10, constraint: GTK_CONSTRAINT (ptr: item));
276 else if (GTK_IS_CONSTRAINT_GUIDE (ptr: item))
277 guide_editor_serialize_guide (str, indent: 10, guide: GTK_CONSTRAINT_GUIDE (ptr: item));
278 }
279 g_string_append (string: str, val: " </constraints>\n");
280 g_string_append (string: str, val: " </object>\n");
281 g_string_append (string: str, val: " </property>\n");
282 for (i = 0; i < g_list_model_get_n_items (list); i++)
283 {
284 gpointer item = g_list_model_get_item (list, position: i);
285 g_object_unref (object: item);
286 if (GTK_IS_WIDGET (item))
287 serialize_child (str, indent: 4, GTK_WIDGET (item));
288 }
289 g_string_append (string: str, val: " </object>\n");
290 g_string_append (string: str, val: "</interface>\n");
291
292 return g_string_free (string: str, FALSE);
293}
294
295
296static void
297save_response_cb (GtkNativeDialog *dialog,
298 int response,
299 ConstraintEditorWindow *self)
300{
301 gtk_native_dialog_hide (self: dialog);
302
303 if (response == GTK_RESPONSE_ACCEPT)
304 {
305 GListModel *model;
306 GFile *file;
307 char *text;
308 GError *error = NULL;
309
310 model = constraint_view_get_model (view: CONSTRAINT_VIEW (ptr: self->view));
311 text = serialize_model (list: model);
312 file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
313 g_file_replace_contents (file, contents: text, length: strlen (s: text),
314 NULL, FALSE,
315 flags: G_FILE_CREATE_NONE,
316 NULL,
317 NULL,
318 error: &error);
319 if (error != NULL)
320 {
321 GtkWidget *message_dialog;
322
323 message_dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (self))),
324 flags: GTK_DIALOG_MODAL|GTK_DIALOG_DESTROY_WITH_PARENT,
325 type: GTK_MESSAGE_INFO,
326 buttons: GTK_BUTTONS_OK,
327 message_format: "Saving failed");
328 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (message_dialog),
329 message_format: "%s", error->message);
330 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
331 gtk_widget_show (widget: message_dialog);
332 g_error_free (error);
333 }
334
335 g_free (mem: text);
336 g_object_unref (object: file);
337 }
338
339 gtk_native_dialog_destroy (self: dialog);
340}
341
342static void
343save_cb (GtkWidget *button,
344 ConstraintEditorWindow *self)
345{
346 GtkFileChooserNative *dialog;
347
348 dialog = gtk_file_chooser_native_new (title: "Save constraints",
349 GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (button))),
350 action: GTK_FILE_CHOOSER_ACTION_SAVE,
351 accept_label: "_Save",
352 cancel_label: "_Cancel");
353 gtk_native_dialog_set_modal (self: GTK_NATIVE_DIALOG (ptr: dialog), TRUE);
354
355 GFile *cwd = g_file_new_for_path (path: ".");
356 gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), file: cwd, NULL);
357 g_object_unref (object: cwd);
358
359 g_signal_connect (dialog, "response", G_CALLBACK (save_response_cb), self);
360 gtk_native_dialog_show (self: GTK_NATIVE_DIALOG (ptr: dialog));
361}
362
363static void
364constraint_editor_window_finalize (GObject *object)
365{
366 //ConstraintEditorWindow *self = (ConstraintEditorWindow *)object;
367
368 G_OBJECT_CLASS (constraint_editor_window_parent_class)->finalize (object);
369}
370
371static int child_counter;
372static int guide_counter;
373
374static void
375add_child (ConstraintEditorWindow *win)
376{
377 char *name;
378
379 child_counter++;
380 name = g_strdup_printf (format: "Child %d", child_counter);
381 constraint_view_add_child (view: CONSTRAINT_VIEW (ptr: win->view), name);
382 g_free (mem: name);
383}
384
385static void
386add_guide (ConstraintEditorWindow *win)
387{
388 char *name;
389 GtkConstraintGuide *guide;
390
391 guide_counter++;
392 name = g_strdup_printf (format: "Guide %d", guide_counter);
393 guide = gtk_constraint_guide_new ();
394 gtk_constraint_guide_set_name (guide, name);
395 g_free (mem: name);
396
397 constraint_view_add_guide (view: CONSTRAINT_VIEW (ptr: win->view), guide);
398}
399
400static void
401constraint_editor_done (ConstraintEditor *editor,
402 GtkConstraint *constraint,
403 ConstraintEditorWindow *win)
404{
405 GtkConstraint *old_constraint;
406
407 g_object_get (object: editor, first_property_name: "constraint", &old_constraint, NULL);
408
409 if (old_constraint)
410 constraint_view_remove_constraint (view: CONSTRAINT_VIEW (ptr: win->view), constraint: old_constraint);
411
412 constraint_view_add_constraint (view: CONSTRAINT_VIEW (ptr: win->view), constraint);
413
414 g_clear_object (&old_constraint);
415
416 gtk_window_destroy (GTK_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (editor), GTK_TYPE_WINDOW)));
417}
418
419static void
420edit_constraint (ConstraintEditorWindow *win,
421 GtkConstraint *constraint)
422{
423 GtkWidget *window;
424 ConstraintEditor *editor;
425 GListModel *model;
426
427 window = gtk_window_new ();
428 gtk_window_set_transient_for (GTK_WINDOW (window), GTK_WINDOW (win));
429 gtk_window_set_resizable (GTK_WINDOW (window), FALSE);
430 if (constraint)
431 gtk_window_set_title (GTK_WINDOW (window), title: "Edit Constraint");
432 else
433 gtk_window_set_title (GTK_WINDOW (window), title: "Create Constraint");
434
435 model = constraint_view_get_model (view: CONSTRAINT_VIEW (ptr: win->view));
436
437 editor = constraint_editor_new (model, constraint);
438
439 gtk_window_set_child (GTK_WINDOW (window), GTK_WIDGET (editor));
440
441 g_signal_connect (editor, "done", G_CALLBACK (constraint_editor_done), win);
442
443 gtk_widget_show (widget: window);
444}
445
446static void
447add_constraint (ConstraintEditorWindow *win)
448{
449 edit_constraint (win, NULL);
450}
451
452static void
453guide_editor_done (GuideEditor *editor,
454 GtkConstraintGuide *guide,
455 ConstraintEditorWindow *win)
456{
457 gtk_window_destroy (GTK_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (editor), GTK_TYPE_WINDOW)));
458}
459
460static void
461edit_guide (ConstraintEditorWindow *win,
462 GtkConstraintGuide *guide)
463{
464 GtkWidget *window;
465 GuideEditor *editor;
466
467 window = gtk_window_new ();
468 gtk_window_set_resizable (GTK_WINDOW (window), FALSE);
469 gtk_window_set_transient_for (GTK_WINDOW (window), GTK_WINDOW (win));
470 gtk_window_set_title (GTK_WINDOW (window), title: "Edit Guide");
471
472 editor = guide_editor_new (guide);
473 gtk_window_set_child (GTK_WINDOW (window), GTK_WIDGET (editor));
474
475 g_signal_connect (editor, "done", G_CALLBACK (guide_editor_done), win);
476 gtk_widget_show (widget: window);
477}
478
479static void
480row_activated (GtkListBox *list,
481 GtkListBoxRow *row,
482 ConstraintEditorWindow *win)
483{
484 GObject *item;
485
486 item = G_OBJECT (g_object_get_data (G_OBJECT (row), "item"));
487
488 if (GTK_IS_CONSTRAINT (ptr: item))
489 edit_constraint (win, constraint: GTK_CONSTRAINT (ptr: item));
490 else if (GTK_IS_CONSTRAINT_GUIDE (ptr: item))
491 edit_guide (win, guide: GTK_CONSTRAINT_GUIDE (ptr: item));
492}
493
494static void
495constraint_editor_window_class_init (ConstraintEditorWindowClass *class)
496{
497 GObjectClass *object_class = G_OBJECT_CLASS (class);
498 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
499
500 object_class->finalize = constraint_editor_window_finalize;
501
502 gtk_widget_class_set_template_from_resource (widget_class,
503 resource_name: "/org/gtk/gtk4/constraint-editor/constraint-editor-window.ui");
504
505 gtk_widget_class_bind_template_child (widget_class, ConstraintEditorWindow, paned);
506 gtk_widget_class_bind_template_child (widget_class, ConstraintEditorWindow, view);
507 gtk_widget_class_bind_template_child (widget_class, ConstraintEditorWindow, list);
508
509 gtk_widget_class_bind_template_callback (widget_class, open_cb);
510 gtk_widget_class_bind_template_callback (widget_class, save_cb);
511 gtk_widget_class_bind_template_callback (widget_class, add_child);
512 gtk_widget_class_bind_template_callback (widget_class, add_guide);
513 gtk_widget_class_bind_template_callback (widget_class, add_constraint);
514 gtk_widget_class_bind_template_callback (widget_class, row_activated);
515}
516
517static void
518row_edit (GtkButton *button,
519 ConstraintEditorWindow *win)
520{
521 GtkWidget *row;
522 GObject *item;
523
524 row = gtk_widget_get_ancestor (GTK_WIDGET (button), GTK_TYPE_LIST_BOX_ROW);
525 item = (GObject *)g_object_get_data (G_OBJECT (row), key: "item");
526 if (GTK_IS_CONSTRAINT (ptr: item))
527 edit_constraint (win, constraint: GTK_CONSTRAINT (ptr: item));
528 else if (GTK_IS_CONSTRAINT_GUIDE (ptr: item))
529 edit_guide (win, guide: GTK_CONSTRAINT_GUIDE (ptr: item));
530}
531
532static void
533mark_constraints_invalid (ConstraintEditorWindow *win,
534 gpointer removed)
535{
536 GtkWidget *child;
537 GObject *item;
538
539 for (child = gtk_widget_get_first_child (widget: win->list);
540 child;
541 child = gtk_widget_get_next_sibling (widget: child))
542 {
543 item = (GObject *)g_object_get_data (G_OBJECT (child), key: "item");
544 if (GTK_IS_CONSTRAINT (ptr: item))
545 {
546 GtkConstraint *constraint = GTK_CONSTRAINT (ptr: item);
547
548 if (gtk_constraint_get_target (constraint) == (GtkConstraintTarget *)removed ||
549 gtk_constraint_get_source (constraint) == (GtkConstraintTarget *)removed)
550 {
551 GtkWidget *button;
552 button = (GtkWidget *)g_object_get_data (G_OBJECT (child), key: "edit");
553 gtk_button_set_icon_name (GTK_BUTTON (button), icon_name: "dialog-warning-symbolic");
554 gtk_widget_set_tooltip_text (widget: button, text: "Constraint is invalid");
555 }
556 }
557 }
558}
559
560static void
561row_delete (GtkButton *button,
562 ConstraintEditorWindow *win)
563{
564 GtkWidget *row;
565 GObject *item;
566
567 row = gtk_widget_get_ancestor (GTK_WIDGET (button), GTK_TYPE_LIST_BOX_ROW);
568 item = (GObject *)g_object_get_data (G_OBJECT (row), key: "item");
569 if (GTK_IS_CONSTRAINT (ptr: item))
570 constraint_view_remove_constraint (view: CONSTRAINT_VIEW (ptr: win->view),
571 constraint: GTK_CONSTRAINT (ptr: item));
572 else if (GTK_IS_CONSTRAINT_GUIDE (ptr: item))
573 {
574 mark_constraints_invalid (win, removed: item);
575 constraint_view_remove_guide (view: CONSTRAINT_VIEW (ptr: win->view),
576 guide: GTK_CONSTRAINT_GUIDE (ptr: item));
577 }
578 else if (GTK_IS_WIDGET (item))
579 {
580 mark_constraints_invalid (win, removed: item);
581 constraint_view_remove_child (view: CONSTRAINT_VIEW (ptr: win->view),
582 GTK_WIDGET (item));
583 }
584}
585
586static GtkWidget *
587create_widget_func (gpointer item,
588 gpointer user_data)
589{
590 ConstraintEditorWindow *win = user_data;
591 const char *name;
592 char *freeme = NULL;
593 GtkWidget *row, *box, *label, *button;
594
595 if (GTK_IS_WIDGET (item))
596 name = gtk_widget_get_name (GTK_WIDGET (item));
597 else if (GTK_IS_CONSTRAINT_GUIDE (ptr: item))
598 name = gtk_constraint_guide_get_name (guide: GTK_CONSTRAINT_GUIDE (ptr: item));
599 else if (GTK_IS_CONSTRAINT (ptr: item))
600 name = freeme = constraint_editor_constraint_to_string (constraint: GTK_CONSTRAINT (ptr: item));
601 else
602 name = "";
603
604 row = gtk_list_box_row_new ();
605 g_object_set_data_full (G_OBJECT (row), key: "item", g_object_ref (item), destroy: g_object_unref);
606 box = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 0);
607 label = gtk_label_new (str: name);
608 if (GTK_IS_WIDGET (item) || GTK_IS_CONSTRAINT_GUIDE (ptr: item))
609 g_object_bind_property (source: item, source_property: "name",
610 target: label, target_property: "label",
611 flags: G_BINDING_DEFAULT);
612 gtk_widget_set_margin_start (widget: label, margin: 10);
613 gtk_widget_set_margin_end (widget: label, margin: 10);
614 gtk_widget_set_margin_top (widget: label, margin: 10);
615 gtk_widget_set_margin_bottom (widget: label, margin: 10);
616 gtk_label_set_xalign (GTK_LABEL (label), xalign: 0.0);
617 gtk_widget_set_hexpand (widget: label, TRUE);
618 gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (row), child: box);
619 gtk_box_append (GTK_BOX (box), child: label);
620
621 if (GTK_IS_CONSTRAINT (ptr: item) || GTK_IS_CONSTRAINT_GUIDE (ptr: item))
622 {
623 button = gtk_button_new_from_icon_name (icon_name: "document-edit-symbolic");
624 gtk_button_set_has_frame (GTK_BUTTON (button), FALSE);
625 g_signal_connect (button, "clicked", G_CALLBACK (row_edit), win);
626 g_object_set_data (G_OBJECT (row), key: "edit", data: button);
627 gtk_box_append (GTK_BOX (box), child: button);
628 button = gtk_button_new_from_icon_name (icon_name: "edit-delete-symbolic");
629 gtk_button_set_has_frame (GTK_BUTTON (button), FALSE);
630 g_signal_connect (button, "clicked", G_CALLBACK (row_delete), win);
631 gtk_box_append (GTK_BOX (box), child: button);
632 }
633 else if (GTK_IS_WIDGET (item))
634 {
635 button = gtk_button_new_from_icon_name (icon_name: "edit-delete-symbolic");
636 gtk_button_set_has_frame (GTK_BUTTON (button), FALSE);
637 g_signal_connect (button, "clicked", G_CALLBACK (row_delete), win);
638 gtk_box_append (GTK_BOX (box), child: button);
639 }
640
641 g_free (mem: freeme);
642
643 return row;
644}
645
646static void
647constraint_editor_window_init (ConstraintEditorWindow *self)
648{
649 gtk_widget_init_template (GTK_WIDGET (self));
650
651 gtk_list_box_bind_model (GTK_LIST_BOX (self->list),
652 model: constraint_view_get_model (view: CONSTRAINT_VIEW (ptr: self->view)),
653 create_widget_func,
654 user_data: self,
655 NULL);
656}
657
658ConstraintEditorWindow *
659constraint_editor_window_new (ConstraintEditorApplication *application)
660{
661 return g_object_new (CONSTRAINT_EDITOR_WINDOW_TYPE,
662 first_property_name: "application", application,
663 NULL);
664}
665

source code of gtk/demos/constraint-editor/constraint-editor-window.c