Blob Blame History Raw
/*
 * 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);

  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);
}