diff options
Diffstat (limited to 'subprojects/libhandy/glade/glade-hdy-leaflet.c')
-rw-r--r-- | subprojects/libhandy/glade/glade-hdy-leaflet.c | 533 |
1 files changed, 533 insertions, 0 deletions
diff --git a/subprojects/libhandy/glade/glade-hdy-leaflet.c b/subprojects/libhandy/glade/glade-hdy-leaflet.c new file mode 100644 index 0000000..4032f9f --- /dev/null +++ b/subprojects/libhandy/glade/glade-hdy-leaflet.c @@ -0,0 +1,533 @@ +/* + * Copyright (C) 2020 Alexander Mikhaylenko <alexm@gnome.org> + * + * SPDX-License-Identifier: LGPL-2.1+ + * + * Based on + * glade-gtk-stack.c - GladeWidgetAdaptor for GtkStack + * Copyright (C) 2014 Red Hat, Inc. + */ + +#include <config.h> +#include <glib/gi18n-lib.h> + +#include "glade-hdy-leaflet.h" + +#include <gladeui/glade.h> +#include "glade-hdy-utils.h" + +#define PAGE_DISABLED_MESSAGE _("This property only applies when the leaflet is folded") + +static void +selection_changed_cb (GladeProject *project, + GladeWidget *gwidget) +{ + GList *list; + GtkWidget *page, *sel_widget; + GtkContainer *container = GTK_CONTAINER (glade_widget_get_object (gwidget)); + gint index; + + if ((list = glade_project_selection_get (project)) != NULL && + g_list_length (list) == 1) { + sel_widget = list->data; + + if (GTK_IS_WIDGET (sel_widget) && + gtk_widget_is_ancestor (sel_widget, GTK_WIDGET (container))) { + g_autoptr (GList) children = gtk_container_get_children (container); + GList *l; + + index = 0; + for (l = children; l; l = l->next) { + page = l->data; + if (sel_widget == page || + gtk_widget_is_ancestor (sel_widget, page)) { + glade_widget_property_set (gwidget, "page", index); + + break; + } + + index++; + } + } + } +} + +static void +project_changed_cb (GladeWidget *gwidget, + GParamSpec *pspec, + gpointer userdata) +{ + GladeProject *project = glade_widget_get_project (gwidget); + GladeProject *old_project = g_object_get_data (G_OBJECT (gwidget), + "project-ptr"); + + if (old_project) + g_signal_handlers_disconnect_by_func (G_OBJECT (old_project), + G_CALLBACK (selection_changed_cb), + gwidget); + + if (project) + g_signal_connect (G_OBJECT (project), + "selection-changed", + G_CALLBACK (selection_changed_cb), + gwidget); + + g_object_set_data (G_OBJECT (gwidget), "project-ptr", project); +} + +static void +add_named (GtkContainer *container, + GtkWidget *child, + const gchar *name) +{ + gtk_container_add_with_properties (container, + child, + "name", name, + NULL); +} + +static void +folded_changed_cb (HdyLeaflet *leaflet, + GParamSpec *pspec, + gpointer userdata) +{ + GladeWidget *gwidget = glade_widget_get_from_gobject (leaflet); + gboolean folded = hdy_leaflet_get_folded (leaflet); + + glade_widget_property_set_sensitive (gwidget, + "page", + folded, + folded ? NULL : PAGE_DISABLED_MESSAGE); +} + +void +glade_hdy_leaflet_post_create (GladeWidgetAdaptor *adaptor, + GObject *container, + GladeCreateReason reason) +{ + GladeWidget *gwidget = glade_widget_get_from_gobject (container); + + if (reason == GLADE_CREATE_USER) + add_named (GTK_CONTAINER (container), + glade_placeholder_new (), + "page0"); + + g_signal_connect (G_OBJECT (gwidget), + "notify::project", + G_CALLBACK (project_changed_cb), + NULL); + + project_changed_cb (gwidget, NULL, NULL); + + if (HDY_IS_LEAFLET (container)) { + g_signal_connect (container, + "notify::folded", + G_CALLBACK (folded_changed_cb), + NULL); + + folded_changed_cb (HDY_LEAFLET (container), NULL, NULL); + } +} + +static GtkWidget * +get_child_by_name (GtkContainer *container, + const gchar *name) +{ + g_autoptr (GList) children = gtk_container_get_children (container); + GList *l; + + for (l = children; l; l = l->next) { + const gchar *child_name; + + gtk_container_child_get (container, l->data, "name", &child_name, NULL); + + if (child_name && !strcmp (child_name, name)) + return l->data; + } + + return NULL; +} + +static gchar * +get_unused_name (GtkContainer *container) +{ + gint i = 0; + + while (TRUE) { + g_autofree gchar *name = g_strdup_printf ("page%d", i); + + if (get_child_by_name (container, name) == NULL) + return g_steal_pointer (&name); + + i++; + } + + return NULL; +} + +void +glade_hdy_leaflet_child_action_activate (GladeWidgetAdaptor *adaptor, + GObject *container, + GObject *object, + const gchar *action_path) +{ + if (!strcmp (action_path, "insert_page_after") || + !strcmp (action_path, "insert_page_before")) { + GladeWidget *parent = glade_widget_get_from_gobject (container); + GladeProperty *property; + g_autofree gchar *name = NULL; + GtkWidget *new_widget; + gint pages, index; + + glade_widget_property_get (parent, "pages", &pages); + + glade_command_push_group (_("Insert placeholder to %s"), + glade_widget_get_name (parent)); + + index = glade_hdy_get_child_index (GTK_CONTAINER (container), GTK_WIDGET (object)); + + if (!strcmp (action_path, "insert_page_after")) + index++; + + name = get_unused_name (GTK_CONTAINER (container)); + new_widget = glade_placeholder_new (); + add_named (GTK_CONTAINER (container), new_widget, name); + glade_hdy_reorder_child (GTK_CONTAINER (container), new_widget, index); + g_object_set (container, "visible-child", new_widget, NULL); + + glade_hdy_sync_child_positions (GTK_CONTAINER (container)); + + property = glade_widget_get_property (parent, "pages"); + glade_command_set_property (property, pages + 1); + + property = glade_widget_get_property (parent, "page"); + glade_command_set_property (property, index); + + glade_command_pop_group (); + } else if (strcmp (action_path, "remove_page") == 0) { + GladeWidget *parent = glade_widget_get_from_gobject (container); + GladeProperty *property; + gint pages, index; + + glade_widget_property_get (parent, "pages", &pages); + + glade_command_push_group (_("Remove placeholder from %s"), + glade_widget_get_name (parent)); + g_assert (GLADE_IS_PLACEHOLDER (object)); + gtk_container_remove (GTK_CONTAINER (container), GTK_WIDGET (object)); + + glade_hdy_sync_child_positions (GTK_CONTAINER (container)); + + property = glade_widget_get_property (parent, "pages"); + glade_command_set_property (property, pages - 1); + + glade_widget_property_get (parent, "page", &index); + property = glade_widget_get_property (parent, "page"); + glade_command_set_property (property, index); + + glade_command_pop_group (); + } else { + GWA_GET_CLASS (GTK_TYPE_CONTAINER)->child_action_activate (adaptor, + container, + object, + action_path); + } +} + +typedef struct { + gint size; + gboolean include_placeholders; +} ChildData; + +static void +count_child (GtkWidget *child, + gpointer data) +{ + ChildData *cdata = data; + + if (cdata->include_placeholders || !GLADE_IS_PLACEHOLDER (child)) + cdata->size++; +} + +static gint +get_n_pages (GtkContainer *container, + gboolean include_placeholders) +{ + ChildData data; + + data.size = 0; + data.include_placeholders = include_placeholders; + gtk_container_foreach (container, count_child, &data); + + return data.size; +} + +static void +set_n_pages (GObject *object, + const GValue *value) +{ + GladeWidget *gbox; + GtkContainer *container = GTK_CONTAINER (object); + GtkWidget *child; + gint new_size = g_value_get_int (value); + gint old_size = get_n_pages (container, TRUE); + gint i, page; + + if (old_size == new_size) + return; + + for (i = old_size; i < new_size; i++) { + g_autofree gchar *name = get_unused_name (container); + child = glade_placeholder_new (); + add_named (container, child, name); + } + + for (i = old_size; i > 0; i--) { + if (old_size <= new_size) + break; + + child = glade_hdy_get_nth_child (container, i - 1); + if (GLADE_IS_PLACEHOLDER (child)) { + gtk_container_remove (container, child); + old_size--; + } + } + + gbox = glade_widget_get_from_gobject (container); + glade_widget_property_get (gbox, "page", &page); + glade_widget_property_set (gbox, "page", page); +} + +static void +set_page (GObject *object, + const GValue *value) +{ + gint new_page = g_value_get_int (value); + GtkWidget *child = glade_hdy_get_nth_child (GTK_CONTAINER (object), new_page); + + if (!child) + return; + + g_object_set (object, "visible-child", child, NULL); +} + +static gint +get_page (GtkContainer *container) +{ + GtkWidget *child; + + g_object_get (container, "visible-child", &child, NULL); + + return glade_hdy_get_child_index (container, child); +} + +void +glade_hdy_leaflet_set_property (GladeWidgetAdaptor *adaptor, + GObject *object, + const gchar *id, + const GValue *value) +{ + if (!strcmp (id, "pages")) + set_n_pages (object, value); + else if (!strcmp (id, "page")) + set_page (object, value); + else + GWA_GET_CLASS (GTK_TYPE_CONTAINER)->set_property (adaptor, object, id, value); +} + +void +glade_hdy_leaflet_get_property (GladeWidgetAdaptor *adaptor, + GObject *object, + const gchar *id, + GValue *value) +{ + if (!strcmp (id, "pages")) { + g_value_reset (value); + g_value_set_int (value, get_n_pages (GTK_CONTAINER (object), TRUE)); + } else if (!strcmp (id, "page")) { + g_value_reset (value); + g_value_set_int (value, get_page (GTK_CONTAINER (object))); + } else { + GWA_GET_CLASS (GTK_TYPE_CONTAINER)->get_property (adaptor, object, id, value); + } +} + +static gboolean +verify_n_pages (GObject *object, + const GValue *value) +{ + gint new_size = g_value_get_int (value); + gint old_size = get_n_pages (GTK_CONTAINER (object), FALSE); + + return old_size <= new_size; +} + +static gboolean +verify_page (GObject *object, + const GValue *value) +{ + gint page = g_value_get_int (value); + gint pages = get_n_pages (GTK_CONTAINER (object), TRUE); + + if (page < 0 && page >= pages) + return FALSE; + + if (HDY_IS_LEAFLET (object)) { + GtkWidget *child = glade_hdy_get_nth_child (GTK_CONTAINER (object), page); + gboolean navigatable; + + gtk_container_child_get (GTK_CONTAINER (object), child, + "navigatable", &navigatable, + NULL); + + if (!navigatable) + return FALSE; + } + + return TRUE; +} + +gboolean +glade_hdy_leaflet_verify_property (GladeWidgetAdaptor *adaptor, + GObject *object, + const gchar *id, + const GValue *value) +{ + if (!strcmp (id, "pages")) + return verify_n_pages (object, value); + else if (!strcmp (id, "page")) + return verify_page (object, value); + else if (GWA_GET_CLASS (GTK_TYPE_CONTAINER)->verify_property) + return GWA_GET_CLASS (GTK_TYPE_CONTAINER)->verify_property (adaptor, object, id, value); + + return TRUE; +} + + +void +glade_hdy_leaflet_get_child_property (GladeWidgetAdaptor *adaptor, + GObject *container, + GObject *child, + const gchar *property_name, + GValue *value) +{ + if (strcmp (property_name, "position") == 0) + g_value_set_int (value, glade_hdy_get_child_index (GTK_CONTAINER (container), + GTK_WIDGET (child))); + else + GWA_GET_CLASS (GTK_TYPE_CONTAINER)->child_get_property (adaptor, + container, + child, + property_name, + value); +} + +void +glade_hdy_leaflet_set_child_property (GladeWidgetAdaptor *adaptor, + GObject *container, + GObject *child, + const gchar *property_name, + GValue *value) +{ + if (strcmp (property_name, "position") == 0) { + glade_hdy_reorder_child (GTK_CONTAINER (container), + GTK_WIDGET (child), + g_value_get_int (value)); + + glade_hdy_sync_child_positions (GTK_CONTAINER (container)); + } else { + GWA_GET_CLASS (GTK_TYPE_CONTAINER)->child_set_property (adaptor, + container, + child, + property_name, + value); + } +} + +void +glade_hdy_leaflet_add_child (GladeWidgetAdaptor *adaptor, + GObject *object, + GObject *child) +{ + GladeWidget *gbox, *gchild; + gint pages, page; + + if (!glade_widget_superuser () && !GLADE_IS_PLACEHOLDER (child)) { + g_autoptr (GList) children = gtk_container_get_children (GTK_CONTAINER (object)); + GList *l; + + for (l = g_list_last (children); l; l = l->prev) { + GtkWidget *widget = l->data; + if (GLADE_IS_PLACEHOLDER (widget)) { + gtk_container_remove (GTK_CONTAINER (object), widget); + + break; + } + } + } + + gtk_container_add (GTK_CONTAINER (object), GTK_WIDGET (child)); + + glade_hdy_sync_child_positions (GTK_CONTAINER (object)); + + gchild = glade_widget_get_from_gobject (child); + if (gchild != NULL) + glade_widget_set_pack_action_visible (gchild, "remove_page", FALSE); + + gbox = glade_widget_get_from_gobject (object); + glade_widget_property_get (gbox, "pages", &pages); + glade_widget_property_set (gbox, "pages", pages); + glade_widget_property_get (gbox, "page", &page); + glade_widget_property_set (gbox, "page", page); +} + +void +glade_hdy_leaflet_remove_child (GladeWidgetAdaptor *adaptor, + GObject *object, + GObject *child) +{ + GladeWidget *gbox; + gint pages, page; + + gtk_container_remove (GTK_CONTAINER (object), GTK_WIDGET (child)); + + glade_hdy_sync_child_positions (GTK_CONTAINER (object)); + + gbox = glade_widget_get_from_gobject (object); + glade_widget_property_get (gbox, "pages", &pages); + glade_widget_property_set (gbox, "pages", pages); + glade_widget_property_get (gbox, "page", &page); + glade_widget_property_set (gbox, "page", page); +} + +void +glade_hdy_leaflet_replace_child (GladeWidgetAdaptor *adaptor, + GObject *container, + GObject *current, + GObject *new_widget) +{ + GladeWidget *gchild; + GladeWidget *gbox; + gint pages, page, index; + + index = glade_hdy_get_child_index (GTK_CONTAINER (container), GTK_WIDGET (current)); + gtk_container_remove (GTK_CONTAINER (container), GTK_WIDGET (current)); + gtk_container_add (GTK_CONTAINER (container), GTK_WIDGET (new_widget)); + glade_hdy_reorder_child (GTK_CONTAINER (container), GTK_WIDGET (new_widget), index); + + glade_hdy_sync_child_positions (GTK_CONTAINER (container)); + + gbox = glade_widget_get_from_gobject (container); + + gchild = glade_widget_get_from_gobject (new_widget); + if (gchild != NULL) + glade_widget_set_pack_action_visible (gchild, "remove_page", FALSE); + + /* NOTE: make sure to sync this at the end because new_widget could be + * a placeholder and syncing these properties could destroy it. + */ + glade_widget_property_get (gbox, "pages", &pages); + glade_widget_property_set (gbox, "pages", pages); + glade_widget_property_get (gbox, "page", &page); + glade_widget_property_set (gbox, "page", page); +} |