1 /*
2 * Copyright (C) 2020 Alexander Mikhaylenko <alexm@gnome.org>
3 *
4 * SPDX-License-Identifier: LGPL-2.1+
5 *
6 * Based on
7 * glade-gtk-stack.c - GladeWidgetAdaptor for GtkStack
8 * Copyright (C) 2014 Red Hat, Inc.
9 */
10
11 #include <config.h>
12 #include <glib/gi18n-lib.h>
13
14 #include "glade-hdy-leaflet.h"
15
16 #include <gladeui/glade.h>
17 #include "glade-hdy-utils.h"
18
19 #define PAGE_DISABLED_MESSAGE _("This property only applies when the leaflet is folded")
20
21 static void
selection_changed_cb(GladeProject * project,GladeWidget * gwidget)22 selection_changed_cb (GladeProject *project,
23 GladeWidget *gwidget)
24 {
25 GList *list;
26 GtkWidget *page, *sel_widget;
27 GtkContainer *container = GTK_CONTAINER (glade_widget_get_object (gwidget));
28 gint index;
29
30 if ((list = glade_project_selection_get (project)) != NULL &&
31 g_list_length (list) == 1) {
32 sel_widget = list->data;
33
34 if (GTK_IS_WIDGET (sel_widget) &&
35 gtk_widget_is_ancestor (sel_widget, GTK_WIDGET (container))) {
36 g_autoptr (GList) children = gtk_container_get_children (container);
37 GList *l;
38
39 index = 0;
40 for (l = children; l; l = l->next) {
41 page = l->data;
42 if (sel_widget == page ||
43 gtk_widget_is_ancestor (sel_widget, page)) {
44 glade_widget_property_set (gwidget, "page", index);
45
46 break;
47 }
48
49 index++;
50 }
51 }
52 }
53 }
54
55 static void
project_changed_cb(GladeWidget * gwidget,GParamSpec * pspec,gpointer userdata)56 project_changed_cb (GladeWidget *gwidget,
57 GParamSpec *pspec,
58 gpointer userdata)
59 {
60 GladeProject *project = glade_widget_get_project (gwidget);
61 GladeProject *old_project = g_object_get_data (G_OBJECT (gwidget),
62 "project-ptr");
63
64 if (old_project)
65 g_signal_handlers_disconnect_by_func (G_OBJECT (old_project),
66 G_CALLBACK (selection_changed_cb),
67 gwidget);
68
69 if (project)
70 g_signal_connect (G_OBJECT (project),
71 "selection-changed",
72 G_CALLBACK (selection_changed_cb),
73 gwidget);
74
75 g_object_set_data (G_OBJECT (gwidget), "project-ptr", project);
76 }
77
78 static void
add_named(GtkContainer * container,GtkWidget * child,const gchar * name)79 add_named (GtkContainer *container,
80 GtkWidget *child,
81 const gchar *name)
82 {
83 gtk_container_add_with_properties (container,
84 child,
85 "name", name,
86 NULL);
87 }
88
89 static void
folded_changed_cb(HdyLeaflet * leaflet,GParamSpec * pspec,gpointer userdata)90 folded_changed_cb (HdyLeaflet *leaflet,
91 GParamSpec *pspec,
92 gpointer userdata)
93 {
94 GladeWidget *gwidget = glade_widget_get_from_gobject (leaflet);
95 gboolean folded = hdy_leaflet_get_folded (leaflet);
96
97 glade_widget_property_set_sensitive (gwidget,
98 "page",
99 folded,
100 folded ? NULL : PAGE_DISABLED_MESSAGE);
101 }
102
103 void
glade_hdy_leaflet_post_create(GladeWidgetAdaptor * adaptor,GObject * container,GladeCreateReason reason)104 glade_hdy_leaflet_post_create (GladeWidgetAdaptor *adaptor,
105 GObject *container,
106 GladeCreateReason reason)
107 {
108 GladeWidget *gwidget = glade_widget_get_from_gobject (container);
109
110 if (reason == GLADE_CREATE_USER)
111 add_named (GTK_CONTAINER (container),
112 glade_placeholder_new (),
113 "page0");
114
115 g_signal_connect (G_OBJECT (gwidget),
116 "notify::project",
117 G_CALLBACK (project_changed_cb),
118 NULL);
119
120 project_changed_cb (gwidget, NULL, NULL);
121
122 if (HDY_IS_LEAFLET (container)) {
123 g_signal_connect (container,
124 "notify::folded",
125 G_CALLBACK (folded_changed_cb),
126 NULL);
127
128 folded_changed_cb (HDY_LEAFLET (container), NULL, NULL);
129 }
130 }
131
132 static GtkWidget *
get_child_by_name(GtkContainer * container,const gchar * name)133 get_child_by_name (GtkContainer *container,
134 const gchar *name)
135 {
136 g_autoptr (GList) children = gtk_container_get_children (container);
137 GList *l;
138
139 for (l = children; l; l = l->next) {
140 const gchar *child_name;
141
142 gtk_container_child_get (container, l->data, "name", &child_name, NULL);
143
144 if (child_name && !strcmp (child_name, name))
145 return l->data;
146 }
147
148 return NULL;
149 }
150
151 static gchar *
get_unused_name(GtkContainer * container)152 get_unused_name (GtkContainer *container)
153 {
154 gint i = 0;
155
156 while (TRUE) {
157 g_autofree gchar *name = g_strdup_printf ("page%d", i);
158
159 if (get_child_by_name (container, name) == NULL)
160 return g_steal_pointer (&name);
161
162 i++;
163 }
164
165 return NULL;
166 }
167
168 void
glade_hdy_leaflet_child_action_activate(GladeWidgetAdaptor * adaptor,GObject * container,GObject * object,const gchar * action_path)169 glade_hdy_leaflet_child_action_activate (GladeWidgetAdaptor *adaptor,
170 GObject *container,
171 GObject *object,
172 const gchar *action_path)
173 {
174 if (!strcmp (action_path, "insert_page_after") ||
175 !strcmp (action_path, "insert_page_before")) {
176 GladeWidget *parent = glade_widget_get_from_gobject (container);
177 GladeProperty *property;
178 g_autofree gchar *name = NULL;
179 GtkWidget *new_widget;
180 gint pages, index;
181
182 glade_widget_property_get (parent, "pages", &pages);
183
184 glade_command_push_group (_("Insert placeholder to %s"),
185 glade_widget_get_name (parent));
186
187 index = glade_hdy_get_child_index (GTK_CONTAINER (container), GTK_WIDGET (object));
188
189 if (!strcmp (action_path, "insert_page_after"))
190 index++;
191
192 name = get_unused_name (GTK_CONTAINER (container));
193 new_widget = glade_placeholder_new ();
194 add_named (GTK_CONTAINER (container), new_widget, name);
195 glade_hdy_reorder_child (GTK_CONTAINER (container), new_widget, index);
196 g_object_set (container, "visible-child", new_widget, NULL);
197
198 glade_hdy_sync_child_positions (GTK_CONTAINER (container));
199
200 property = glade_widget_get_property (parent, "pages");
201 glade_command_set_property (property, pages + 1);
202
203 property = glade_widget_get_property (parent, "page");
204 glade_command_set_property (property, index);
205
206 glade_command_pop_group ();
207 } else if (strcmp (action_path, "remove_page") == 0) {
208 GladeWidget *parent = glade_widget_get_from_gobject (container);
209 GladeProperty *property;
210 gint pages, index;
211
212 glade_widget_property_get (parent, "pages", &pages);
213
214 glade_command_push_group (_("Remove placeholder from %s"),
215 glade_widget_get_name (parent));
216 g_assert (GLADE_IS_PLACEHOLDER (object));
217 gtk_container_remove (GTK_CONTAINER (container), GTK_WIDGET (object));
218
219 glade_hdy_sync_child_positions (GTK_CONTAINER (container));
220
221 property = glade_widget_get_property (parent, "pages");
222 glade_command_set_property (property, pages - 1);
223
224 glade_widget_property_get (parent, "page", &index);
225 property = glade_widget_get_property (parent, "page");
226 glade_command_set_property (property, index);
227
228 glade_command_pop_group ();
229 } else {
230 GLADE_WIDGET_ADAPTOR_GET_ADAPTOR_CLASS (GTK_TYPE_CONTAINER)->child_action_activate (adaptor,
231 container,
232 object,
233 action_path);
234 }
235 }
236
237 typedef struct {
238 gint size;
239 gboolean include_placeholders;
240 } ChildData;
241
242 static void
count_child(GtkWidget * child,gpointer data)243 count_child (GtkWidget *child,
244 gpointer data)
245 {
246 ChildData *cdata = data;
247
248 if (cdata->include_placeholders || !GLADE_IS_PLACEHOLDER (child))
249 cdata->size++;
250 }
251
252 static gint
get_n_pages(GtkContainer * container,gboolean include_placeholders)253 get_n_pages (GtkContainer *container,
254 gboolean include_placeholders)
255 {
256 ChildData data;
257
258 data.size = 0;
259 data.include_placeholders = include_placeholders;
260 gtk_container_foreach (container, count_child, &data);
261
262 return data.size;
263 }
264
265 static void
set_n_pages(GObject * object,const GValue * value)266 set_n_pages (GObject *object,
267 const GValue *value)
268 {
269 GladeWidget *gbox;
270 GtkContainer *container = GTK_CONTAINER (object);
271 GtkWidget *child;
272 gint new_size = g_value_get_int (value);
273 gint old_size = get_n_pages (container, TRUE);
274 gint i, page;
275
276 if (old_size == new_size)
277 return;
278
279 for (i = old_size; i < new_size; i++) {
280 g_autofree gchar *name = get_unused_name (container);
281 child = glade_placeholder_new ();
282 add_named (container, child, name);
283 }
284
285 for (i = old_size; i > 0; i--) {
286 if (old_size <= new_size)
287 break;
288
289 child = glade_hdy_get_nth_child (container, i - 1);
290 if (GLADE_IS_PLACEHOLDER (child)) {
291 gtk_container_remove (container, child);
292 old_size--;
293 }
294 }
295
296 gbox = glade_widget_get_from_gobject (container);
297 glade_widget_property_get (gbox, "page", &page);
298 glade_widget_property_set (gbox, "page", page);
299 }
300
301 static void
set_page(GObject * object,const GValue * value)302 set_page (GObject *object,
303 const GValue *value)
304 {
305 gint new_page = g_value_get_int (value);
306 GtkWidget *child = glade_hdy_get_nth_child (GTK_CONTAINER (object), new_page);
307
308 if (!child)
309 return;
310
311 g_object_set (object, "visible-child", child, NULL);
312 }
313
314 static gint
get_page(GtkContainer * container)315 get_page (GtkContainer *container)
316 {
317 GtkWidget *child;
318
319 g_object_get (container, "visible-child", &child, NULL);
320
321 return glade_hdy_get_child_index (container, child);
322 }
323
324 void
glade_hdy_leaflet_set_property(GladeWidgetAdaptor * adaptor,GObject * object,const gchar * id,const GValue * value)325 glade_hdy_leaflet_set_property (GladeWidgetAdaptor *adaptor,
326 GObject *object,
327 const gchar *id,
328 const GValue *value)
329 {
330 if (!strcmp (id, "pages"))
331 set_n_pages (object, value);
332 else if (!strcmp (id, "page"))
333 set_page (object, value);
334 else
335 GLADE_WIDGET_ADAPTOR_GET_ADAPTOR_CLASS (GTK_TYPE_CONTAINER)->set_property (adaptor, object, id, value);
336 }
337
338 void
glade_hdy_leaflet_get_property(GladeWidgetAdaptor * adaptor,GObject * object,const gchar * id,GValue * value)339 glade_hdy_leaflet_get_property (GladeWidgetAdaptor *adaptor,
340 GObject *object,
341 const gchar *id,
342 GValue *value)
343 {
344 if (!strcmp (id, "pages")) {
345 g_value_reset (value);
346 g_value_set_int (value, get_n_pages (GTK_CONTAINER (object), TRUE));
347 } else if (!strcmp (id, "page")) {
348 g_value_reset (value);
349 g_value_set_int (value, get_page (GTK_CONTAINER (object)));
350 } else {
351 GLADE_WIDGET_ADAPTOR_GET_ADAPTOR_CLASS (GTK_TYPE_CONTAINER)->get_property (adaptor, object, id, value);
352 }
353 }
354
355 static gboolean
verify_n_pages(GObject * object,const GValue * value)356 verify_n_pages (GObject *object,
357 const GValue *value)
358 {
359 gint new_size = g_value_get_int (value);
360 gint old_size = get_n_pages (GTK_CONTAINER (object), FALSE);
361
362 return old_size <= new_size;
363 }
364
365 static gboolean
verify_page(GObject * object,const GValue * value)366 verify_page (GObject *object,
367 const GValue *value)
368 {
369 gint page = g_value_get_int (value);
370 gint pages = get_n_pages (GTK_CONTAINER (object), TRUE);
371
372 if (page < 0 && page >= pages)
373 return FALSE;
374
375 if (HDY_IS_LEAFLET (object)) {
376 GtkWidget *child = glade_hdy_get_nth_child (GTK_CONTAINER (object), page);
377 gboolean navigatable;
378
379 gtk_container_child_get (GTK_CONTAINER (object), child,
380 "navigatable", &navigatable,
381 NULL);
382
383 if (!navigatable)
384 return FALSE;
385 }
386
387 return TRUE;
388 }
389
390 gboolean
glade_hdy_leaflet_verify_property(GladeWidgetAdaptor * adaptor,GObject * object,const gchar * id,const GValue * value)391 glade_hdy_leaflet_verify_property (GladeWidgetAdaptor *adaptor,
392 GObject *object,
393 const gchar *id,
394 const GValue *value)
395 {
396 if (!strcmp (id, "pages"))
397 return verify_n_pages (object, value);
398 else if (!strcmp (id, "page"))
399 return verify_page (object, value);
400 else if (GLADE_WIDGET_ADAPTOR_GET_ADAPTOR_CLASS (GTK_TYPE_CONTAINER)->verify_property)
401 return GLADE_WIDGET_ADAPTOR_GET_ADAPTOR_CLASS (GTK_TYPE_CONTAINER)->verify_property (adaptor, object, id, value);
402
403 return TRUE;
404 }
405
406
407 void
glade_hdy_leaflet_get_child_property(GladeWidgetAdaptor * adaptor,GObject * container,GObject * child,const gchar * property_name,GValue * value)408 glade_hdy_leaflet_get_child_property (GladeWidgetAdaptor *adaptor,
409 GObject *container,
410 GObject *child,
411 const gchar *property_name,
412 GValue *value)
413 {
414 if (strcmp (property_name, "position") == 0)
415 g_value_set_int (value, glade_hdy_get_child_index (GTK_CONTAINER (container),
416 GTK_WIDGET (child)));
417 else
418 GLADE_WIDGET_ADAPTOR_GET_ADAPTOR_CLASS (GTK_TYPE_CONTAINER)->child_get_property (adaptor,
419 container,
420 child,
421 property_name,
422 value);
423 }
424
425 void
glade_hdy_leaflet_set_child_property(GladeWidgetAdaptor * adaptor,GObject * container,GObject * child,const gchar * property_name,GValue * value)426 glade_hdy_leaflet_set_child_property (GladeWidgetAdaptor *adaptor,
427 GObject *container,
428 GObject *child,
429 const gchar *property_name,
430 GValue *value)
431 {
432 if (strcmp (property_name, "position") == 0) {
433 glade_hdy_reorder_child (GTK_CONTAINER (container),
434 GTK_WIDGET (child),
435 g_value_get_int (value));
436
437 glade_hdy_sync_child_positions (GTK_CONTAINER (container));
438 } else {
439 GLADE_WIDGET_ADAPTOR_GET_ADAPTOR_CLASS (GTK_TYPE_CONTAINER)->child_set_property (adaptor,
440 container,
441 child,
442 property_name,
443 value);
444 }
445 }
446
447 void
glade_hdy_leaflet_add_child(GladeWidgetAdaptor * adaptor,GObject * object,GObject * child)448 glade_hdy_leaflet_add_child (GladeWidgetAdaptor *adaptor,
449 GObject *object,
450 GObject *child)
451 {
452 GladeWidget *gbox, *gchild;
453 gint pages, page;
454
455 if (!glade_widget_superuser () && !GLADE_IS_PLACEHOLDER (child)) {
456 g_autoptr (GList) children = gtk_container_get_children (GTK_CONTAINER (object));
457 GList *l;
458
459 for (l = g_list_last (children); l; l = l->prev) {
460 GtkWidget *widget = l->data;
461 if (GLADE_IS_PLACEHOLDER (widget)) {
462 gtk_container_remove (GTK_CONTAINER (object), widget);
463
464 break;
465 }
466 }
467 }
468
469 gtk_container_add (GTK_CONTAINER (object), GTK_WIDGET (child));
470
471 glade_hdy_sync_child_positions (GTK_CONTAINER (object));
472
473 gchild = glade_widget_get_from_gobject (child);
474 if (gchild != NULL)
475 glade_widget_set_pack_action_visible (gchild, "remove_page", FALSE);
476
477 gbox = glade_widget_get_from_gobject (object);
478 glade_widget_property_get (gbox, "pages", &pages);
479 glade_widget_property_set (gbox, "pages", pages);
480 glade_widget_property_get (gbox, "page", &page);
481 glade_widget_property_set (gbox, "page", page);
482 }
483
484 void
glade_hdy_leaflet_remove_child(GladeWidgetAdaptor * adaptor,GObject * object,GObject * child)485 glade_hdy_leaflet_remove_child (GladeWidgetAdaptor *adaptor,
486 GObject *object,
487 GObject *child)
488 {
489 GladeWidget *gbox;
490 gint pages, page;
491
492 gtk_container_remove (GTK_CONTAINER (object), GTK_WIDGET (child));
493
494 glade_hdy_sync_child_positions (GTK_CONTAINER (object));
495
496 gbox = glade_widget_get_from_gobject (object);
497 glade_widget_property_get (gbox, "pages", &pages);
498 glade_widget_property_set (gbox, "pages", pages);
499 glade_widget_property_get (gbox, "page", &page);
500 glade_widget_property_set (gbox, "page", page);
501 }
502
503 void
glade_hdy_leaflet_replace_child(GladeWidgetAdaptor * adaptor,GObject * container,GObject * current,GObject * new_widget)504 glade_hdy_leaflet_replace_child (GladeWidgetAdaptor *adaptor,
505 GObject *container,
506 GObject *current,
507 GObject *new_widget)
508 {
509 GladeWidget *gchild;
510 GladeWidget *gbox;
511 gint pages, page, index;
512
513 index = glade_hdy_get_child_index (GTK_CONTAINER (container), GTK_WIDGET (current));
514 gtk_container_remove (GTK_CONTAINER (container), GTK_WIDGET (current));
515 gtk_container_add (GTK_CONTAINER (container), GTK_WIDGET (new_widget));
516 glade_hdy_reorder_child (GTK_CONTAINER (container), GTK_WIDGET (new_widget), index);
517
518 glade_hdy_sync_child_positions (GTK_CONTAINER (container));
519
520 gbox = glade_widget_get_from_gobject (container);
521
522 gchild = glade_widget_get_from_gobject (new_widget);
523 if (gchild != NULL)
524 glade_widget_set_pack_action_visible (gchild, "remove_page", FALSE);
525
526 /* NOTE: make sure to sync this at the end because new_widget could be
527 * a placeholder and syncing these properties could destroy it.
528 */
529 glade_widget_property_get (gbox, "pages", &pages);
530 glade_widget_property_set (gbox, "pages", pages);
531 glade_widget_property_get (gbox, "page", &page);
532 glade_widget_property_set (gbox, "page", page);
533 }
534