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