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 | |
27 | struct _ConstraintEditorWindow |
28 | { |
29 | GtkApplicationWindow parent_instance; |
30 | |
31 | GtkWidget *paned; |
32 | GtkWidget *view; |
33 | GtkWidget *list; |
34 | }; |
35 | |
36 | G_DEFINE_TYPE(ConstraintEditorWindow, constraint_editor_window, GTK_TYPE_APPLICATION_WINDOW); |
37 | |
38 | static GtkConstraintTarget * |
39 | find_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 | |
76 | gboolean |
77 | constraint_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 | |
204 | static void |
205 | open_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 | |
223 | static void |
224 | open_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 | |
244 | static void |
245 | serialize_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 | |
259 | static char * |
260 | serialize_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 | |
296 | static void |
297 | save_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 | |
342 | static void |
343 | save_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 | |
363 | static void |
364 | constraint_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 | |
371 | static int child_counter; |
372 | static int guide_counter; |
373 | |
374 | static void |
375 | add_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 | |
385 | static void |
386 | add_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 | |
400 | static void |
401 | constraint_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 | |
419 | static void |
420 | edit_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 | |
446 | static void |
447 | add_constraint (ConstraintEditorWindow *win) |
448 | { |
449 | edit_constraint (win, NULL); |
450 | } |
451 | |
452 | static void |
453 | guide_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 | |
460 | static void |
461 | edit_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 | |
479 | static void |
480 | row_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 | |
494 | static void |
495 | constraint_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 | |
517 | static void |
518 | row_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 | |
532 | static void |
533 | mark_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 | |
560 | static void |
561 | row_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 | |
586 | static GtkWidget * |
587 | create_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 | |
646 | static void |
647 | constraint_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 | |
658 | ConstraintEditorWindow * |
659 | constraint_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 | |