mirror of
https://gitlab.gnome.org/GNOME/glade.git
synced 2025-11-18 00:13:18 -05:00
The code in glade_gtk_stack_add_child was not robust enough against disappearing placeholders. https://bugzilla.gnome.org/show_bug.cgi?id=740668
561 lines
16 KiB
C
561 lines
16 KiB
C
/*
|
|
* glade-gtk-stack.c - GladeWidgetAdaptor for GtkStack
|
|
*
|
|
* Copyright (C) 2014 Red Hat, Inc
|
|
*
|
|
* Authors:
|
|
* Matthias Clasen <mclasen@redhat.com>
|
|
*
|
|
* This library is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU Lesser General Public License as
|
|
* published by the Free Software Foundation; either version 2.1 of
|
|
* the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
#include <config.h>
|
|
#include <glib/gi18n-lib.h>
|
|
#include <gladeui/glade.h>
|
|
|
|
#include "glade-stack-editor.h"
|
|
|
|
static void
|
|
glade_gtk_stack_selection_changed (GladeProject * project,
|
|
GladeWidget * gwidget)
|
|
{
|
|
GList *list;
|
|
GtkWidget *page, *sel_widget;
|
|
GtkStack *stack = GTK_STACK (glade_widget_get_object (gwidget));
|
|
gint position;
|
|
|
|
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 (stack)))
|
|
{
|
|
GList *children, *l;
|
|
|
|
children = gtk_container_get_children (GTK_CONTAINER (stack));
|
|
for (l = children; l; l = l->next)
|
|
{
|
|
page = l->data;
|
|
if (sel_widget == page ||
|
|
gtk_widget_is_ancestor (sel_widget, page))
|
|
{
|
|
gtk_stack_set_visible_child (stack, page);
|
|
gtk_container_child_get (GTK_CONTAINER (stack), page, "position", &position, NULL);
|
|
glade_widget_property_set (gwidget, "page", position);
|
|
break;
|
|
}
|
|
}
|
|
g_list_free (children);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
glade_gtk_stack_project_changed (GladeWidget * gwidget,
|
|
GParamSpec * pspec,
|
|
gpointer userdata)
|
|
{
|
|
GladeProject * project = glade_widget_get_project (gwidget);
|
|
GladeProject * old_project = g_object_get_data (G_OBJECT (gwidget), "stack-project-ptr");
|
|
|
|
if (old_project)
|
|
g_signal_handlers_disconnect_by_func (G_OBJECT (old_project),
|
|
G_CALLBACK (glade_gtk_stack_selection_changed),
|
|
gwidget);
|
|
|
|
if (project)
|
|
g_signal_connect (G_OBJECT (project), "selection-changed",
|
|
G_CALLBACK (glade_gtk_stack_selection_changed),
|
|
gwidget);
|
|
|
|
g_object_set_data (G_OBJECT (gwidget), "stack-project-ptr", project);
|
|
}
|
|
|
|
void
|
|
glade_gtk_stack_post_create (GladeWidgetAdaptor *adaptor,
|
|
GObject *container,
|
|
GladeCreateReason reason)
|
|
{
|
|
GladeWidget *gwidget = glade_widget_get_from_gobject (container);
|
|
|
|
if (reason == GLADE_CREATE_USER)
|
|
gtk_stack_add_titled (GTK_STACK (container), glade_placeholder_new (),
|
|
"page0", "page0");
|
|
|
|
g_signal_connect (G_OBJECT (gwidget), "notify::project",
|
|
G_CALLBACK (glade_gtk_stack_project_changed), NULL);
|
|
|
|
glade_gtk_stack_project_changed (gwidget, NULL, NULL);
|
|
|
|
}
|
|
|
|
static gchar *
|
|
get_unused_name (GtkStack *stack)
|
|
{
|
|
gchar *name;
|
|
gint i;
|
|
|
|
i = 0;
|
|
while (TRUE)
|
|
{
|
|
name = g_strdup_printf ("page%d", i);
|
|
if (gtk_stack_get_child_by_name (stack, name) == NULL)
|
|
return name;
|
|
g_free (name);
|
|
i++;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
update_position_with_command (GtkWidget *widget, gpointer data)
|
|
{
|
|
GtkContainer *parent = data;
|
|
GladeWidget *gwidget;
|
|
GladeProperty *property;
|
|
gint position;
|
|
|
|
gwidget = glade_widget_get_from_gobject (widget);
|
|
if (!gwidget)
|
|
return;
|
|
|
|
property = glade_widget_get_pack_property (gwidget, "position");
|
|
gtk_container_child_get (parent, widget, "position", &position, NULL);
|
|
glade_command_set_property (property, position);
|
|
}
|
|
|
|
void
|
|
glade_gtk_stack_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;
|
|
GladeProperty *property;
|
|
gint position;
|
|
gchar *name;
|
|
GtkWidget *new_widget;
|
|
gint pages;
|
|
|
|
parent = glade_widget_get_from_gobject (container);
|
|
glade_widget_property_get (parent, "pages", &pages);
|
|
|
|
glade_command_push_group (_("Insert placeholder to %s"), glade_widget_get_name (parent));
|
|
|
|
gtk_container_child_get (GTK_CONTAINER (container), GTK_WIDGET (object),
|
|
"position", &position, NULL);
|
|
|
|
if (!strcmp (action_path, "insert_page_after"))
|
|
position++;
|
|
|
|
name = get_unused_name (GTK_STACK (container));
|
|
new_widget = glade_placeholder_new ();
|
|
gtk_stack_add_titled (GTK_STACK (container), new_widget, name, name);
|
|
gtk_container_child_set (GTK_CONTAINER (container), new_widget,
|
|
"position", position, NULL);
|
|
gtk_stack_set_visible_child (GTK_STACK (container), new_widget);
|
|
|
|
property = glade_widget_get_property (parent, "pages");
|
|
glade_command_set_property (property, pages + 1);
|
|
|
|
gtk_container_forall (GTK_CONTAINER (container), update_position_with_command, container);
|
|
|
|
property = glade_widget_get_property (parent, "page");
|
|
glade_command_set_property (property, position);
|
|
|
|
glade_command_pop_group ();
|
|
|
|
g_free (name);
|
|
}
|
|
else if (strcmp (action_path, "remove_page") == 0)
|
|
{
|
|
GladeWidget *parent;
|
|
GladeProperty *property;
|
|
gint pages;
|
|
gint position;
|
|
|
|
parent = glade_widget_get_from_gobject (container);
|
|
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));
|
|
|
|
property = glade_widget_get_property (parent, "pages");
|
|
glade_command_set_property (property, pages - 1);
|
|
|
|
gtk_container_forall (GTK_CONTAINER (container), update_position_with_command, container);
|
|
|
|
glade_widget_property_get (parent, "page", &position);
|
|
property = glade_widget_get_property (parent, "page");
|
|
glade_command_set_property (property, position);
|
|
|
|
glade_command_pop_group ();
|
|
}
|
|
else
|
|
GWA_GET_CLASS (GTK_TYPE_CONTAINER)->child_action_activate (adaptor,
|
|
container,
|
|
object,
|
|
action_path);
|
|
}
|
|
|
|
GladeEditable *
|
|
glade_gtk_stack_create_editable (GladeWidgetAdaptor * adaptor,
|
|
GladeEditorPageType type)
|
|
{
|
|
if (type == GLADE_PAGE_GENERAL)
|
|
return (GladeEditable *) glade_stack_editor_new ();
|
|
|
|
return GWA_GET_CLASS (GTK_TYPE_CONTAINER)->create_editable (adaptor, type);
|
|
}
|
|
|
|
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
|
|
gtk_stack_get_n_pages (GtkStack *stack,
|
|
gboolean include_placeholders)
|
|
{
|
|
ChildData data;
|
|
|
|
data.size = 0;
|
|
data.include_placeholders = include_placeholders;
|
|
gtk_container_forall (GTK_CONTAINER (stack), count_child, &data);
|
|
return data.size;
|
|
}
|
|
|
|
static GtkWidget *
|
|
gtk_stack_get_nth_child (GtkStack *stack,
|
|
gint n)
|
|
{
|
|
GList *children;
|
|
GtkWidget *child;
|
|
|
|
children = gtk_container_get_children (GTK_CONTAINER (stack));
|
|
child = g_list_nth_data (children, n);
|
|
g_list_free (children);
|
|
|
|
return child;
|
|
}
|
|
|
|
static void
|
|
update_position (GtkWidget *widget, gpointer data)
|
|
{
|
|
GtkContainer *parent = data;
|
|
GladeWidget *gwidget;
|
|
gint position;
|
|
|
|
gwidget = glade_widget_get_from_gobject (widget);
|
|
if (gwidget)
|
|
{
|
|
gtk_container_child_get (parent, widget, "position", &position, NULL);
|
|
glade_widget_pack_property_set (gwidget, "position", position);
|
|
}
|
|
}
|
|
|
|
static void
|
|
glade_gtk_stack_set_n_pages (GObject * object,
|
|
const GValue * value)
|
|
{
|
|
GladeWidget *gbox;
|
|
GtkStack *stack;
|
|
GtkWidget *child;
|
|
gint new_size, i;
|
|
gint old_size;
|
|
gchar *name;
|
|
gint page;
|
|
|
|
stack = GTK_STACK (object);
|
|
|
|
new_size = g_value_get_int (value);
|
|
old_size = gtk_stack_get_n_pages (stack, TRUE);
|
|
|
|
if (old_size == new_size)
|
|
return;
|
|
|
|
for (i = old_size; i < new_size; i++)
|
|
{
|
|
name = get_unused_name (stack);
|
|
child = glade_placeholder_new ();
|
|
gtk_stack_add_titled (stack, child, name, name);
|
|
g_free (name);
|
|
}
|
|
|
|
for (i = old_size; i > 0; i--)
|
|
{
|
|
if (old_size <= new_size)
|
|
break;
|
|
child = gtk_stack_get_nth_child (stack, i - 1);
|
|
if (GLADE_IS_PLACEHOLDER (child))
|
|
{
|
|
gtk_container_remove (GTK_CONTAINER (stack), child);
|
|
old_size--;
|
|
}
|
|
}
|
|
|
|
gtk_container_forall (GTK_CONTAINER (stack), update_position, stack);
|
|
|
|
gbox = glade_widget_get_from_gobject (stack);
|
|
glade_widget_property_get (gbox, "page", &page);
|
|
glade_widget_property_set (gbox, "page", page);
|
|
}
|
|
|
|
static void
|
|
glade_gtk_stack_set_page (GObject *object,
|
|
const GValue *value)
|
|
{
|
|
gint new_page;
|
|
GList *children;
|
|
GtkWidget *child;
|
|
|
|
new_page = g_value_get_int (value);
|
|
children = gtk_container_get_children (GTK_CONTAINER (object));
|
|
child = g_list_nth_data (children, new_page);
|
|
|
|
if (child)
|
|
gtk_stack_set_visible_child (GTK_STACK (object), child);
|
|
|
|
g_list_free (children);
|
|
}
|
|
|
|
static gint
|
|
gtk_stack_get_page (GtkStack *stack)
|
|
{
|
|
GtkWidget *child;
|
|
gint page;
|
|
|
|
child = gtk_stack_get_visible_child (stack);
|
|
gtk_container_child_get (GTK_CONTAINER (stack), child, "position", &page, NULL);
|
|
return page;
|
|
}
|
|
|
|
void
|
|
glade_gtk_stack_set_property (GladeWidgetAdaptor * adaptor,
|
|
GObject * object,
|
|
const gchar * id,
|
|
const GValue * value)
|
|
{
|
|
if (!strcmp (id, "pages"))
|
|
glade_gtk_stack_set_n_pages (object, value);
|
|
else if (!strcmp (id, "page"))
|
|
glade_gtk_stack_set_page (object, value);
|
|
else
|
|
GWA_GET_CLASS (GTK_TYPE_CONTAINER)->set_property (adaptor, object, id, value);
|
|
}
|
|
|
|
void
|
|
glade_gtk_stack_get_property (GladeWidgetAdaptor * adaptor,
|
|
GObject * object,
|
|
const gchar * id,
|
|
GValue * value)
|
|
{
|
|
if (!strcmp (id, "pages"))
|
|
{
|
|
g_value_reset (value);
|
|
g_value_set_int (value, gtk_stack_get_n_pages (GTK_STACK (object), TRUE));
|
|
}
|
|
else if (!strcmp (id, "page"))
|
|
{
|
|
g_value_reset (value);
|
|
g_value_set_int (value, gtk_stack_get_page (GTK_STACK (object)));
|
|
}
|
|
else
|
|
GWA_GET_CLASS (GTK_TYPE_CONTAINER)->get_property (adaptor, object, id, value);
|
|
}
|
|
|
|
static void
|
|
glade_gtk_stack_set_child_position (GObject * container,
|
|
GObject * child,
|
|
const GValue * value)
|
|
{
|
|
static gboolean recursion = FALSE;
|
|
gint new_position, old_position;
|
|
GladeWidget *gbox;
|
|
gint page;
|
|
|
|
if (recursion)
|
|
return;
|
|
|
|
gtk_container_child_get (GTK_CONTAINER (container), GTK_WIDGET (child), "position", &old_position, NULL);
|
|
new_position = g_value_get_int (value);
|
|
|
|
if (old_position == new_position)
|
|
return;
|
|
|
|
recursion = TRUE;
|
|
gtk_container_child_set (GTK_CONTAINER (container), GTK_WIDGET (child),
|
|
"position", new_position,
|
|
NULL);
|
|
gtk_container_forall (GTK_CONTAINER (container), update_position, container);
|
|
recursion = FALSE;
|
|
|
|
gbox = glade_widget_get_from_gobject (container);
|
|
glade_widget_property_get (gbox, "page", &page);
|
|
glade_widget_property_set (gbox, "page", page);
|
|
}
|
|
|
|
void
|
|
glade_gtk_stack_set_child_property (GladeWidgetAdaptor * adaptor,
|
|
GObject * container,
|
|
GObject * child,
|
|
const gchar * id,
|
|
GValue * value)
|
|
{
|
|
if (!strcmp (id, "position"))
|
|
glade_gtk_stack_set_child_position (container, child, value);
|
|
else
|
|
GWA_GET_CLASS (GTK_TYPE_CONTAINER)->child_set_property (adaptor, container, child, id, value);
|
|
}
|
|
|
|
static gboolean
|
|
glade_gtk_stack_verify_n_pages (GObject * object,
|
|
const GValue *value)
|
|
{
|
|
gint new_size, old_size;
|
|
|
|
new_size = g_value_get_int (value);
|
|
old_size = gtk_stack_get_n_pages (GTK_STACK (object), FALSE);
|
|
|
|
return old_size <= new_size;
|
|
}
|
|
|
|
static gboolean
|
|
glade_gtk_stack_verify_page (GObject *object,
|
|
const GValue *value)
|
|
{
|
|
gint page;
|
|
gint pages;
|
|
|
|
page = g_value_get_int (value);
|
|
pages = gtk_stack_get_n_pages (GTK_STACK (object), TRUE);
|
|
|
|
return 0 <= page && page < pages;
|
|
}
|
|
|
|
gboolean
|
|
glade_gtk_stack_verify_property (GladeWidgetAdaptor * adaptor,
|
|
GObject * object,
|
|
const gchar * id,
|
|
const GValue * value)
|
|
{
|
|
if (!strcmp (id, "pages"))
|
|
return glade_gtk_stack_verify_n_pages (object, value);
|
|
else if (!strcmp (id, "page"))
|
|
return glade_gtk_stack_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_gtk_stack_add_child (GladeWidgetAdaptor * adaptor,
|
|
GObject * object,
|
|
GObject * child)
|
|
{
|
|
GladeWidget *gbox, *gchild;
|
|
gint pages, page;
|
|
|
|
if (!glade_widget_superuser () && !GLADE_IS_PLACEHOLDER (child))
|
|
{
|
|
GList *l, *children;
|
|
|
|
children = gtk_container_get_children (GTK_CONTAINER (object));
|
|
|
|
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;
|
|
}
|
|
}
|
|
g_list_free (children);
|
|
}
|
|
|
|
gtk_container_add (GTK_CONTAINER (object), GTK_WIDGET (child));
|
|
|
|
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_gtk_stack_remove_child (GladeWidgetAdaptor * adaptor,
|
|
GObject * object,
|
|
GObject * child)
|
|
{
|
|
GladeWidget *gbox;
|
|
gint pages, page;
|
|
|
|
gtk_container_remove (GTK_CONTAINER (object), GTK_WIDGET (child));
|
|
|
|
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_gtk_stack_replace_child (GladeWidgetAdaptor * adaptor,
|
|
GObject * container,
|
|
GObject * current,
|
|
GObject * new_widget)
|
|
{
|
|
GladeWidget *gchild;
|
|
GladeWidget *gbox;
|
|
gint pages, page;
|
|
|
|
GWA_GET_CLASS (GTK_TYPE_CONTAINER)->replace_child (adaptor,
|
|
container,
|
|
current,
|
|
new_widget);
|
|
|
|
gbox = glade_widget_get_from_gobject (container);
|
|
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);
|
|
|
|
gchild = glade_widget_get_from_gobject (new_widget);
|
|
if (gchild != NULL)
|
|
glade_widget_set_pack_action_visible (gchild, "remove_page", FALSE);
|
|
|
|
}
|