1 | /* Copyright (C) 2019 Red Hat, Inc. |
2 | * |
3 | * This library is free software; you can redistribute it and/or |
4 | * modify it under the terms of the GNU Lesser General Public |
5 | * License as published by the Free Software Foundation; either |
6 | * version 2 of the License, or (at your option) any later version. |
7 | * |
8 | * This library 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 this library. If not, see <http://www.gnu.org/licenses/>. |
15 | */ |
16 | |
17 | #include <gtk/gtk.h> |
18 | #include "constraint-view.h" |
19 | |
20 | struct _ConstraintView |
21 | { |
22 | GtkWidget parent; |
23 | |
24 | GListModel *model; |
25 | |
26 | GtkWidget *drag_widget; |
27 | }; |
28 | |
29 | G_DEFINE_TYPE (ConstraintView, constraint_view, GTK_TYPE_WIDGET); |
30 | |
31 | static void |
32 | constraint_view_dispose (GObject *object) |
33 | { |
34 | ConstraintView *view = CONSTRAINT_VIEW (ptr: object); |
35 | GtkWidget *child; |
36 | |
37 | while ((child = gtk_widget_get_first_child (GTK_WIDGET (view))) != NULL) |
38 | gtk_widget_unparent (widget: child); |
39 | |
40 | g_clear_object (&view->model); |
41 | |
42 | G_OBJECT_CLASS (constraint_view_parent_class)->dispose (object); |
43 | } |
44 | |
45 | static void |
46 | constraint_view_class_init (ConstraintViewClass *klass) |
47 | { |
48 | GObjectClass *object_class = G_OBJECT_CLASS (klass); |
49 | GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); |
50 | |
51 | object_class->dispose = constraint_view_dispose; |
52 | |
53 | gtk_widget_class_set_css_name (widget_class, name: "constraintview" ); |
54 | } |
55 | |
56 | static void |
57 | update_weak_position (ConstraintView *self, |
58 | GtkWidget *child, |
59 | double x, |
60 | double y) |
61 | { |
62 | GtkLayoutManager *manager; |
63 | GtkConstraint *constraint; |
64 | |
65 | manager = gtk_widget_get_layout_manager (GTK_WIDGET (self)); |
66 | constraint = (GtkConstraint *)g_object_get_data (G_OBJECT (child), key: "x-constraint" ); |
67 | if (constraint) |
68 | { |
69 | gtk_constraint_layout_remove_constraint (layout: GTK_CONSTRAINT_LAYOUT (ptr: manager), |
70 | constraint); |
71 | g_object_set_data (G_OBJECT (child), key: "x-constraint" , NULL); |
72 | } |
73 | if (x != -100) |
74 | { |
75 | constraint = gtk_constraint_new_constant (target: child, |
76 | target_attribute: GTK_CONSTRAINT_ATTRIBUTE_CENTER_X, |
77 | relation: GTK_CONSTRAINT_RELATION_EQ, |
78 | constant: x, |
79 | strength: GTK_CONSTRAINT_STRENGTH_WEAK); |
80 | g_object_set_data (G_OBJECT (constraint), key: "internal" , data: (char *)"yes" ); |
81 | gtk_constraint_layout_add_constraint (layout: GTK_CONSTRAINT_LAYOUT (ptr: manager), |
82 | constraint); |
83 | g_object_set_data (G_OBJECT (child), key: "x-constraint" , data: constraint); |
84 | } |
85 | |
86 | constraint = (GtkConstraint *)g_object_get_data (G_OBJECT (child), key: "y-constraint" ); |
87 | if (constraint) |
88 | { |
89 | gtk_constraint_layout_remove_constraint (layout: GTK_CONSTRAINT_LAYOUT (ptr: manager), |
90 | constraint); |
91 | g_object_set_data (G_OBJECT (child), key: "y-constraint" , NULL); |
92 | } |
93 | if (y != -100) |
94 | { |
95 | constraint = gtk_constraint_new_constant (target: child, |
96 | target_attribute: GTK_CONSTRAINT_ATTRIBUTE_CENTER_Y, |
97 | relation: GTK_CONSTRAINT_RELATION_EQ, |
98 | constant: y, |
99 | strength: GTK_CONSTRAINT_STRENGTH_WEAK); |
100 | g_object_set_data (G_OBJECT (constraint), key: "internal" , data: (char *)"yes" ); |
101 | gtk_constraint_layout_add_constraint (layout: GTK_CONSTRAINT_LAYOUT (ptr: manager), |
102 | constraint); |
103 | g_object_set_data (G_OBJECT (child), key: "y-constraint" , data: constraint); |
104 | } |
105 | } |
106 | |
107 | static void |
108 | drag_begin (GtkGestureDrag *drag, |
109 | double start_x, |
110 | double start_y, |
111 | ConstraintView *self) |
112 | { |
113 | GtkWidget *widget; |
114 | |
115 | widget = gtk_widget_pick (GTK_WIDGET (self), x: start_x, y: start_y, flags: GTK_PICK_DEFAULT); |
116 | |
117 | if (GTK_IS_LABEL (widget)) |
118 | { |
119 | widget = gtk_widget_get_ancestor (widget, GTK_TYPE_FRAME); |
120 | if (widget && |
121 | gtk_widget_get_parent (widget) == (GtkWidget *)self) |
122 | { |
123 | self->drag_widget = widget; |
124 | } |
125 | } |
126 | } |
127 | |
128 | static void |
129 | drag_update (GtkGestureDrag *drag, |
130 | double offset_x, |
131 | double offset_y, |
132 | ConstraintView *self) |
133 | { |
134 | double x, y; |
135 | |
136 | if (!self->drag_widget) |
137 | return; |
138 | |
139 | gtk_gesture_drag_get_start_point (gesture: drag, x: &x, y: &y); |
140 | update_weak_position (self, child: self->drag_widget, x: x + offset_x, y: y + offset_y); |
141 | } |
142 | |
143 | static void |
144 | drag_end (GtkGestureDrag *drag, |
145 | double offset_x, |
146 | double offset_y, |
147 | ConstraintView *self) |
148 | { |
149 | self->drag_widget = NULL; |
150 | } |
151 | |
152 | static gboolean |
153 | omit_internal (gpointer item, gpointer user_data) |
154 | { |
155 | if (g_object_get_data (G_OBJECT (item), key: "internal" )) |
156 | return FALSE; |
157 | |
158 | return TRUE; |
159 | } |
160 | |
161 | static void |
162 | constraint_view_init (ConstraintView *self) |
163 | { |
164 | GtkLayoutManager *manager; |
165 | GtkEventController *controller; |
166 | GListStore *list; |
167 | GListModel *all_children; |
168 | GListModel *all_constraints; |
169 | GListModel *guides; |
170 | GListModel *children; |
171 | GListModel *constraints; |
172 | GtkFilter *filter; |
173 | |
174 | manager = gtk_constraint_layout_new (); |
175 | gtk_widget_set_layout_manager (GTK_WIDGET (self), layout_manager: manager); |
176 | |
177 | guides = gtk_constraint_layout_observe_guides (layout: GTK_CONSTRAINT_LAYOUT (ptr: manager)); |
178 | |
179 | all_constraints = gtk_constraint_layout_observe_constraints (layout: GTK_CONSTRAINT_LAYOUT (ptr: manager)); |
180 | filter = GTK_FILTER (ptr: gtk_custom_filter_new (match_func: omit_internal, NULL, NULL)); |
181 | constraints = (GListModel *)gtk_filter_list_model_new (model: all_constraints, filter); |
182 | |
183 | all_children = gtk_widget_observe_children (GTK_WIDGET (self)); |
184 | filter = GTK_FILTER (ptr: gtk_custom_filter_new (match_func: omit_internal, NULL, NULL)); |
185 | children = (GListModel *)gtk_filter_list_model_new (model: all_children, filter); |
186 | |
187 | list = g_list_store_new (G_TYPE_LIST_MODEL); |
188 | g_list_store_append (store: list, item: children); |
189 | g_list_store_append (store: list, item: guides); |
190 | g_list_store_append (store: list, item: constraints); |
191 | g_object_unref (object: children); |
192 | g_object_unref (object: guides); |
193 | g_object_unref (object: constraints); |
194 | |
195 | self->model = G_LIST_MODEL (ptr: gtk_flatten_list_model_new (model: G_LIST_MODEL (ptr: list))); |
196 | |
197 | controller = (GtkEventController *)gtk_gesture_drag_new (); |
198 | g_signal_connect (controller, "drag-begin" , G_CALLBACK (drag_begin), self); |
199 | g_signal_connect (controller, "drag-update" , G_CALLBACK (drag_update), self); |
200 | g_signal_connect (controller, "drag-end" , G_CALLBACK (drag_end), self); |
201 | gtk_widget_add_controller (GTK_WIDGET (self), controller); |
202 | } |
203 | |
204 | ConstraintView * |
205 | constraint_view_new (void) |
206 | { |
207 | return g_object_new (CONSTRAINT_VIEW_TYPE, NULL); |
208 | } |
209 | |
210 | void |
211 | constraint_view_add_child (ConstraintView *view, |
212 | const char *name) |
213 | { |
214 | GtkWidget *frame; |
215 | GtkWidget *label; |
216 | |
217 | label = gtk_label_new (str: name); |
218 | frame = gtk_frame_new (NULL); |
219 | gtk_widget_add_css_class (widget: frame, css_class: "child" ); |
220 | gtk_widget_set_name (widget: frame, name); |
221 | gtk_frame_set_child (GTK_FRAME (frame), child: label); |
222 | gtk_widget_set_parent (widget: frame, GTK_WIDGET (view)); |
223 | |
224 | update_weak_position (self: view, child: frame, x: 100, y: 100); |
225 | } |
226 | |
227 | void |
228 | constraint_view_remove_child (ConstraintView *view, |
229 | GtkWidget *child) |
230 | { |
231 | update_weak_position (self: view, child, x: -100, y: -100); |
232 | gtk_widget_unparent (widget: child); |
233 | } |
234 | |
235 | void |
236 | constraint_view_add_guide (ConstraintView *view, |
237 | GtkConstraintGuide *guide) |
238 | { |
239 | GtkConstraintLayout *layout; |
240 | GtkWidget *frame; |
241 | GtkWidget *label; |
242 | const char *name; |
243 | GtkConstraint *constraint; |
244 | struct { |
245 | const char *name; |
246 | GtkConstraintAttribute attr; |
247 | } names[] = { |
248 | { "left-constraint" , GTK_CONSTRAINT_ATTRIBUTE_LEFT }, |
249 | { "top-constraint" , GTK_CONSTRAINT_ATTRIBUTE_TOP }, |
250 | { "width-constraint" , GTK_CONSTRAINT_ATTRIBUTE_WIDTH }, |
251 | { "height-constraint" , GTK_CONSTRAINT_ATTRIBUTE_HEIGHT }, |
252 | }; |
253 | int i; |
254 | |
255 | name = gtk_constraint_guide_get_name (guide); |
256 | label = gtk_label_new (str: name); |
257 | g_object_bind_property (source: guide, source_property: "name" , |
258 | target: label, target_property: "label" , |
259 | flags: G_BINDING_DEFAULT); |
260 | |
261 | frame = gtk_frame_new (NULL); |
262 | gtk_widget_add_css_class (widget: frame, css_class: "guide" ); |
263 | g_object_set_data (G_OBJECT (frame), key: "internal" , data: (char *)"yes" ); |
264 | gtk_frame_set_child (GTK_FRAME (frame), child: label); |
265 | gtk_widget_insert_after (widget: frame, GTK_WIDGET (view), NULL); |
266 | |
267 | g_object_set_data (G_OBJECT (guide), key: "frame" , data: frame); |
268 | |
269 | layout = GTK_CONSTRAINT_LAYOUT (ptr: gtk_widget_get_layout_manager (GTK_WIDGET (view))); |
270 | gtk_constraint_layout_add_guide (layout, g_object_ref (guide)); |
271 | |
272 | for (i = 0; i < G_N_ELEMENTS (names); i++) |
273 | { |
274 | constraint = gtk_constraint_new (target: frame, |
275 | target_attribute: names[i].attr, |
276 | relation: GTK_CONSTRAINT_RELATION_EQ, |
277 | source: guide, |
278 | source_attribute: names[i].attr, |
279 | multiplier: 1.0, constant: 0.0, |
280 | strength: GTK_CONSTRAINT_STRENGTH_REQUIRED); |
281 | g_object_set_data (G_OBJECT (constraint), key: "internal" , data: (char *)"yes" ); |
282 | gtk_constraint_layout_add_constraint (layout, constraint); |
283 | g_object_set_data (G_OBJECT (guide), key: names[i].name, data: constraint); |
284 | } |
285 | |
286 | update_weak_position (self: view, child: frame, x: 150, y: 150); |
287 | } |
288 | |
289 | void |
290 | constraint_view_remove_guide (ConstraintView *view, |
291 | GtkConstraintGuide *guide) |
292 | { |
293 | GtkConstraintLayout *layout; |
294 | GtkWidget *frame; |
295 | GtkConstraint *constraint; |
296 | const char *names[] = { |
297 | "left-constraint" , |
298 | "top-constraint" , |
299 | "width-constraint" , |
300 | "height-constraint" |
301 | }; |
302 | int i; |
303 | |
304 | layout = GTK_CONSTRAINT_LAYOUT (ptr: gtk_widget_get_layout_manager (GTK_WIDGET (view))); |
305 | |
306 | for (i = 0; i < G_N_ELEMENTS (names); i++) |
307 | { |
308 | constraint = (GtkConstraint*)g_object_get_data (G_OBJECT (guide), key: names[i]); |
309 | gtk_constraint_layout_remove_constraint (layout, constraint); |
310 | } |
311 | |
312 | frame = (GtkWidget *)g_object_get_data (G_OBJECT (guide), key: "frame" ); |
313 | update_weak_position (self: view, child: frame, x: -100, y: -100); |
314 | gtk_widget_unparent (widget: frame); |
315 | |
316 | gtk_constraint_layout_remove_guide (layout, guide); |
317 | } |
318 | |
319 | void |
320 | constraint_view_add_constraint (ConstraintView *view, |
321 | GtkConstraint *constraint) |
322 | { |
323 | GtkLayoutManager *manager; |
324 | |
325 | manager = gtk_widget_get_layout_manager (GTK_WIDGET (view)); |
326 | gtk_constraint_layout_add_constraint (layout: GTK_CONSTRAINT_LAYOUT (ptr: manager), |
327 | g_object_ref (constraint)); |
328 | } |
329 | |
330 | void |
331 | constraint_view_remove_constraint (ConstraintView *view, |
332 | GtkConstraint *constraint) |
333 | { |
334 | GtkLayoutManager *manager; |
335 | |
336 | manager = gtk_widget_get_layout_manager (GTK_WIDGET (view)); |
337 | gtk_constraint_layout_remove_constraint (layout: GTK_CONSTRAINT_LAYOUT (ptr: manager), |
338 | constraint); |
339 | } |
340 | |
341 | GListModel * |
342 | constraint_view_get_model (ConstraintView *view) |
343 | { |
344 | return view->model; |
345 | } |
346 | |