Blob Blame History Raw
/*
 * glade-gtk-notebook.c - GladeWidgetAdaptor for GtkNotebook
 *
 * Copyright (C) 2013 Tristan Van Berkom
 *
 * Authors:
 *      Tristan Van Berkom <tristan.van.berkom@gmail.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-gtk-notebook.h"
#include "glade-notebook-editor.h"

typedef struct
{
  gint pages;
  gint page;

  GList *children;
  GList *tabs;

  GList *extra_children;
  GList *extra_tabs;
} NotebookChildren;

static gboolean glade_gtk_notebook_setting_position = FALSE;


GladeEditable *
glade_gtk_notebook_create_editable (GladeWidgetAdaptor * adaptor,
				    GladeEditorPageType type)
{
  if (type == GLADE_PAGE_GENERAL)
    return (GladeEditable *) glade_notebook_editor_new ();

  return GWA_GET_CLASS (GTK_TYPE_CONTAINER)->create_editable (adaptor, type);
}

static gint
notebook_child_compare_func (GtkWidget * widget_a, GtkWidget * widget_b)
{
  GladeWidget *gwidget_a, *gwidget_b;
  gint pos_a = 0, pos_b = 0;

  gwidget_a = glade_widget_get_from_gobject (widget_a);
  gwidget_b = glade_widget_get_from_gobject (widget_b);

  g_assert (gwidget_a && gwidget_b);

  glade_widget_pack_property_get (gwidget_a, "position", &pos_a);
  glade_widget_pack_property_get (gwidget_b, "position", &pos_b);

  return pos_a - pos_b;
}

static gint
notebook_find_child (GtkWidget * check, gpointer cmp_pos_p)
{
  GladeWidget *gcheck;
  gint position = 0, cmp_pos = GPOINTER_TO_INT (cmp_pos_p);

  gcheck = glade_widget_get_from_gobject (check);
  g_assert (gcheck);

  glade_widget_pack_property_get (gcheck, "position", &position);

  return position - cmp_pos;
}

static gint
notebook_search_tab (GtkNotebook * notebook, GtkWidget * tab)
{
  GtkWidget *page;
  gint i;

  for (i = 0; i < gtk_notebook_get_n_pages (notebook); i++)
    {
      page = gtk_notebook_get_nth_page (notebook, i);

      if (tab == gtk_notebook_get_tab_label (notebook, page))
        return i;
    }
  g_critical ("Unable to find tab position in a notebook");
  return -1;
}

static GtkWidget *
notebook_get_filler (NotebookChildren * nchildren, gboolean page)
{
  GtkWidget *widget = NULL;

  if (page && nchildren->extra_children)
    {
      widget = nchildren->extra_children->data;
      nchildren->extra_children =
          g_list_remove (nchildren->extra_children, widget);
      g_assert (widget);
    }
  else if (!page && nchildren->extra_tabs)
    {
      widget = nchildren->extra_tabs->data;
      nchildren->extra_tabs = g_list_remove (nchildren->extra_tabs, widget);
      g_assert (widget);
    }

  if (widget == NULL)
    {
      /* Need explicit reference here */
      widget = glade_placeholder_new ();

      g_object_ref (G_OBJECT (widget));

      if (!page)
        g_object_set_data (G_OBJECT (widget), "special-child-type", "tab");

    }
  return widget;
}

static GtkWidget *
notebook_get_page (NotebookChildren * nchildren, gint position)
{
  GList *node;
  GtkWidget *widget = NULL;

  if ((node = g_list_find_custom
       (nchildren->children,
        GINT_TO_POINTER (position),
        (GCompareFunc) notebook_find_child)) != NULL)
    {
      widget = node->data;
      nchildren->children = g_list_remove (nchildren->children, widget);
    }
  else
    widget = notebook_get_filler (nchildren, TRUE);

  return widget;
}

static GtkWidget *
notebook_get_tab (NotebookChildren * nchildren, gint position)
{
  GList *node;
  GtkWidget *widget = NULL;

  if ((node = g_list_find_custom
       (nchildren->tabs,
        GINT_TO_POINTER (position),
        (GCompareFunc) notebook_find_child)) != NULL)
    {
      widget = node->data;
      nchildren->tabs = g_list_remove (nchildren->tabs, widget);
    }
  else
    widget = notebook_get_filler (nchildren, FALSE);

  return widget;
}

static NotebookChildren *
glade_gtk_notebook_extract_children (GtkWidget * notebook)
{
  NotebookChildren *nchildren;
  gchar *special_child_type;
  GList *list, *children =
      glade_util_container_get_all_children (GTK_CONTAINER (notebook));
  GladeWidget *gchild;
  gint position = 0;
  GtkNotebook *nb;

  nb = GTK_NOTEBOOK (notebook);
  nchildren = g_new0 (NotebookChildren, 1);
  nchildren->pages = gtk_notebook_get_n_pages (nb);
  nchildren->page = gtk_notebook_get_current_page (nb);

  /* Ref all the project widgets and build returned list first */
  for (list = children; list; list = list->next)
    {
      if ((gchild = glade_widget_get_from_gobject (list->data)) != NULL)
        {
          special_child_type =
              g_object_get_data (G_OBJECT (list->data), "special-child-type");

          glade_widget_pack_property_get (gchild, "position", &position);

          g_object_ref (G_OBJECT (list->data));

          /* Sort it into the proper struct member
           */
          if (special_child_type == NULL)
            {
              if (g_list_find_custom (nchildren->children,
                                      GINT_TO_POINTER (position),
                                      (GCompareFunc) notebook_find_child))
                nchildren->extra_children =
                    g_list_insert_sorted
                    (nchildren->extra_children, list->data,
                     (GCompareFunc) notebook_child_compare_func);
              else
                nchildren->children =
                    g_list_insert_sorted
                    (nchildren->children, list->data,
                     (GCompareFunc) notebook_child_compare_func);
            }
          else if (!strcmp (special_child_type, "tab"))
            {
              if (g_list_find_custom (nchildren->tabs,
                                      GINT_TO_POINTER (position),
                                      (GCompareFunc) notebook_find_child))
                nchildren->extra_tabs =
                    g_list_insert_sorted
                    (nchildren->extra_tabs, list->data,
                     (GCompareFunc) notebook_child_compare_func);
              else
                nchildren->tabs =
                    g_list_insert_sorted
                    (nchildren->tabs, list->data,
                     (GCompareFunc) notebook_child_compare_func);
            }
        }
    }

  /* Remove all pages, resulting in the unparenting of all widgets including tab-labels.
   */
  while (gtk_notebook_get_n_pages (nb) > 0)
    {
      GtkWidget *page = gtk_notebook_get_nth_page (nb, 0);
      GtkWidget *tab = gtk_notebook_get_tab_label (nb, page);

      if (tab)
	g_object_ref (tab);

      /* Explicitly remove the tab label first */
      gtk_notebook_set_tab_label (nb, page, NULL);

      /* FIXE: we need to unparent here to avoid anoying warning when reparenting */
      if (tab)
	{
	  gtk_widget_unparent (tab);
	  g_object_unref (tab);
	}

      gtk_notebook_remove_page (nb, 0);
    }

  if (children)
    g_list_free (children);

  return nchildren;
}

static void
glade_gtk_notebook_insert_children (GtkWidget * notebook,
                                    NotebookChildren * nchildren)
{
  gint i;

  /*********************************************************
   *                     INSERT PAGES                      *
   *********************************************************/
  for (i = 0; i < nchildren->pages; i++)
    {
      GtkWidget *page = notebook_get_page (nchildren, i);
      GtkWidget *tab = notebook_get_tab (nchildren, i);

      gtk_notebook_insert_page (GTK_NOTEBOOK (notebook), page, tab, i);

      g_object_unref (G_OBJECT (page));
      g_object_unref (G_OBJECT (tab));
    }

  /* Stay on the same page */
  gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), nchildren->page);

  /* Free the original lists now */
  if (nchildren->children)
    g_list_free (nchildren->children);

  if (nchildren->tabs)
    g_list_free (nchildren->tabs);

  if (nchildren->children ||
      nchildren->tabs || nchildren->extra_children || nchildren->extra_tabs)
    g_critical ("Unbalanced children when inserting notebook children"
                " (pages: %d tabs: %d extra pages: %d extra tabs %d)",
                g_list_length (nchildren->children),
                g_list_length (nchildren->tabs),
                g_list_length (nchildren->extra_children),
                g_list_length (nchildren->extra_tabs));

  g_free (nchildren);
}

static void
glade_gtk_notebook_switch_page (GtkNotebook * notebook,
                                GtkWidget * page,
                                guint page_num, gpointer user_data)
{
  GladeWidget *gnotebook = glade_widget_get_from_gobject (notebook);

  glade_widget_property_set (gnotebook, "page", page_num);

}

/* Track project selection to set the notebook pages to display
 * the selected widget.
 */
static void
glade_gtk_notebook_selection_changed (GladeProject * project,
                                      GladeWidget * gwidget)
{
  GList *list;
  gint i;
  GtkWidget *page, *sel_widget;
  GtkNotebook *notebook = GTK_NOTEBOOK (glade_widget_get_object (gwidget));

  if ((list = glade_project_selection_get (project)) != NULL &&
      g_list_length (list) == 1)
    {
      sel_widget = list->data;

      /* Check if selected widget is inside the notebook */
      if (GTK_IS_WIDGET (sel_widget) &&
          gtk_widget_is_ancestor (sel_widget, GTK_WIDGET (notebook)))
        {
          /* Find and activate the page */
          for (i = 0;
               i < gtk_notebook_get_n_pages (notebook);
               i++)
            {
              page = gtk_notebook_get_nth_page (notebook, i);

              if (sel_widget == page ||
                  gtk_widget_is_ancestor (sel_widget, GTK_WIDGET (page)))
                {
                  glade_widget_property_set (gwidget, "page", i);
                  return;
                }
            }
        }
    }
}

static void
glade_gtk_notebook_project_changed (GladeWidget * gwidget,
                                    GParamSpec * pspec, gpointer userdata)
{
  GladeProject
      * project = glade_widget_get_project (gwidget),
      *old_project =
      g_object_get_data (G_OBJECT (gwidget), "notebook-project-ptr");

  if (old_project)
    g_signal_handlers_disconnect_by_func (G_OBJECT (old_project),
                                          G_CALLBACK
                                          (glade_gtk_notebook_selection_changed),
                                          gwidget);

  if (project)
    g_signal_connect (G_OBJECT (project), "selection-changed",
                      G_CALLBACK (glade_gtk_notebook_selection_changed),
                      gwidget);

  g_object_set_data (G_OBJECT (gwidget), "notebook-project-ptr", project);
}

static void
glade_gtk_notebook_parse_finished (GladeProject * project, GObject * object)
{
  GtkWidget *action;

  action = gtk_notebook_get_action_widget (GTK_NOTEBOOK (object), GTK_PACK_START);
  glade_widget_property_set (glade_widget_get_from_gobject (object),
                             "has-action-start", action != NULL);
  action = gtk_notebook_get_action_widget (GTK_NOTEBOOK (object), GTK_PACK_END);
  glade_widget_property_set (glade_widget_get_from_gobject (object),
                             "has-action-end", action != NULL);
}

void
glade_gtk_notebook_post_create (GladeWidgetAdaptor * adaptor,
                                GObject * notebook, GladeCreateReason reason)
{
  GladeWidget *gwidget = glade_widget_get_from_gobject (notebook);
  GladeProject *project = glade_widget_get_project (gwidget);

  gtk_notebook_popup_disable (GTK_NOTEBOOK (notebook));

  g_signal_connect (G_OBJECT (gwidget), "notify::project",
                    G_CALLBACK (glade_gtk_notebook_project_changed), NULL);

  glade_gtk_notebook_project_changed (gwidget, NULL, NULL);

  g_signal_connect (G_OBJECT (notebook), "switch-page",
                    G_CALLBACK (glade_gtk_notebook_switch_page), NULL);

  if (reason == GLADE_CREATE_LOAD)
    g_signal_connect (project, "parse-finished",
                      G_CALLBACK (glade_gtk_notebook_parse_finished),
                      notebook);
}

static gint
glade_gtk_notebook_get_first_blank_page (GtkNotebook * notebook)
{
  GladeWidget *gwidget;
  GtkWidget *widget;
  gint position;

  for (position = 0; position < gtk_notebook_get_n_pages (notebook); position++)
    {
      widget = gtk_notebook_get_nth_page (notebook, position);
      if ((gwidget = glade_widget_get_from_gobject (widget)) != NULL)
        {
          GladeProperty *property =
              glade_widget_get_property (gwidget, "position");
          gint gwidget_position = g_value_get_int (glade_property_inline_value (property));

          if ((gwidget_position - position) > 0)
            return position;
        }
    }
  return position;
}

static GladeWidget *
glade_gtk_notebook_generate_tab (GladeWidget * notebook, gint page_id)
{
  static GladeWidgetAdaptor *wadaptor = NULL;
  gchar *str;
  GladeWidget *glabel;

  if (wadaptor == NULL)
    wadaptor = glade_widget_adaptor_get_by_type (GTK_TYPE_LABEL);

  glabel = glade_widget_adaptor_create_widget (wadaptor, FALSE,
                                               "parent", notebook,
                                               "project",
                                               glade_widget_get_project
                                               (notebook), NULL);

  str = g_strdup_printf ("page %d", page_id);
  glade_widget_property_set (glabel, "label", str);
  g_free (str);

  g_object_set_data (glade_widget_get_object (glabel), "special-child-type", "tab");
  gtk_widget_show (GTK_WIDGET (glade_widget_get_object (glabel)));

  return glabel;
}

static void
glade_gtk_notebook_set_n_pages (GObject * object, const GValue * value)
{
  GladeWidget *widget;
  GtkNotebook *notebook;
  GtkWidget *child_widget;
  gint new_size, i;
  gint old_size;

  notebook = GTK_NOTEBOOK (object);
  g_return_if_fail (GTK_IS_NOTEBOOK (notebook));

  widget = glade_widget_get_from_gobject (GTK_WIDGET (notebook));
  g_return_if_fail (widget != NULL);

  new_size = g_value_get_int (value);
  old_size = gtk_notebook_get_n_pages (GTK_NOTEBOOK (notebook));

  /* Ensure base size of notebook */
  if (glade_widget_superuser () == FALSE)
    {
      for (i = gtk_notebook_get_n_pages (notebook); i < new_size; i++)
        {
          gint position = glade_gtk_notebook_get_first_blank_page (notebook);
          GtkWidget *placeholder = glade_placeholder_new ();
          GladeWidget *gtab;

          gtk_notebook_insert_page (notebook, placeholder, NULL, position);

          /* XXX Ugly hack amongst many, this one only creates project widgets
           * when the 'n-pages' of a notebook is initially set, otherwise it puts
           * placeholders. (this makes the job easier when doing "insert before/after")
           */
          if (old_size == 0 && new_size > 1)
            {
              gtab = glade_gtk_notebook_generate_tab (widget, position + 1);

              /* Must pass through GladeWidget api so that packing props
               * are correctly assigned.
               */
              glade_widget_add_child (widget, gtab, FALSE);
            }
          else
            {
              GtkWidget *tab_placeholder = glade_placeholder_new ();

              g_object_set_data (G_OBJECT (tab_placeholder),
                                 "special-child-type", "tab");

              gtk_notebook_set_tab_label (GTK_NOTEBOOK (notebook), placeholder,
                                          tab_placeholder);
            }
        }
    }

  /*
   * Thing to remember is that GtkNotebook starts the
   * page numbers from 0, not 1 (C-style). So we need to do
   * old_size-1, where we're referring to "nth" widget.
   */
  while (old_size > new_size)
    {
      /* Get the last page and remove it (project objects have been cleared by
       * the action code already). */
      child_widget = gtk_notebook_get_nth_page (notebook, old_size - 1);

      /* Ok there shouldnt be widget in the content area, that's
       * the placeholder, we should clean up the project widget that
       * we put in the tab here though (this happens in the case where
       * we undo increasing the "pages" property).
       */
      if (glade_widget_get_from_gobject (child_widget))
        g_critical ("Bug in notebook_set_n_pages()");

      gtk_notebook_remove_page (notebook, old_size - 1);

      old_size--;
    }
}

void
glade_gtk_notebook_set_property (GladeWidgetAdaptor * adaptor,
                                 GObject * object,
                                 const gchar * id, const GValue * value)
{
  if (!strcmp (id, "pages"))
    glade_gtk_notebook_set_n_pages (object, value);
  else if (!strcmp (id, "has-action-start"))
    {
      if (g_value_get_boolean (value))
        {
          GtkWidget *action = gtk_notebook_get_action_widget (GTK_NOTEBOOK (object), GTK_PACK_START);
          if (!action)
            action = glade_placeholder_new ();
          g_object_set_data (G_OBJECT (action), "special-child-type", "action-start");
          gtk_notebook_set_action_widget (GTK_NOTEBOOK (object), action, GTK_PACK_START); 
        }
      else
        gtk_notebook_set_action_widget (GTK_NOTEBOOK (object), NULL, GTK_PACK_START); 
    }
  else if (!strcmp (id, "has-action-end"))
    {
      if (g_value_get_boolean (value))
        {
          GtkWidget *action = gtk_notebook_get_action_widget (GTK_NOTEBOOK (object), GTK_PACK_END);
          if (!action)
            action = glade_placeholder_new ();
          g_object_set_data (G_OBJECT (action), "special-child-type", "action-end");
          gtk_notebook_set_action_widget (GTK_NOTEBOOK (object), action, GTK_PACK_END); 
        }
      else
        gtk_notebook_set_action_widget (GTK_NOTEBOOK (object), NULL, GTK_PACK_END); 
    }
  else
    GWA_GET_CLASS (GTK_TYPE_CONTAINER)->set_property (adaptor, object,
                                                      id, value);
}

void
glade_gtk_notebook_get_property (GladeWidgetAdaptor * adaptor,
                            GObject * object, const gchar * id, GValue * value)
{
  if (!strcmp (id, "has-action-start"))
    {
      g_value_reset (value);
      g_value_set_boolean (value, gtk_notebook_get_action_widget (GTK_NOTEBOOK (object), GTK_PACK_START) != NULL);
    }
  else if (!strcmp (id, "has-action-end"))
    {
      g_value_reset (value);
      g_value_set_boolean (value, gtk_notebook_get_action_widget (GTK_NOTEBOOK (object), GTK_PACK_END) != NULL);
    }
  else
    GWA_GET_CLASS (GTK_TYPE_CONTAINER)->get_property (adaptor, object, id,
                                                      value);
}

static gboolean
glade_gtk_notebook_verify_n_pages (GObject * object, const GValue * value)
{
  GtkNotebook *notebook = GTK_NOTEBOOK (object);
  GtkWidget *child_widget, *tab_widget;
  gint old_size, new_size = g_value_get_int (value);

  for (old_size = gtk_notebook_get_n_pages (notebook);
       old_size > new_size; old_size--)
    {
      /* Get the last widget. */
      child_widget = gtk_notebook_get_nth_page (notebook, old_size - 1);
      tab_widget = gtk_notebook_get_tab_label (notebook, child_widget);

      /* 
       * If we got it, and its not a placeholder, remove it
       * from project.
       */
      if (glade_widget_get_from_gobject (child_widget) ||
          glade_widget_get_from_gobject (tab_widget))
        return FALSE;
    }
  return TRUE;
}

gboolean
glade_gtk_notebook_verify_property (GladeWidgetAdaptor * adaptor,
                                    GObject * object,
                                    const gchar * id, const GValue * value)
{
  if (!strcmp (id, "pages"))
    return glade_gtk_notebook_verify_n_pages (object, value);
  else if (GWA_GET_CLASS (GTK_TYPE_CONTAINER)->verify_property)
    GWA_GET_CLASS (GTK_TYPE_CONTAINER)->verify_property (adaptor, object,
                                                         id, value);

  return TRUE;
}

void
glade_gtk_notebook_add_child (GladeWidgetAdaptor * adaptor,
                              GObject * object, GObject * child)
{
  GtkNotebook *notebook;
  gint num_page, position = 0;
  GtkWidget *last_page;
  GladeWidget *gwidget;
  gchar *special_child_type;

  notebook = GTK_NOTEBOOK (object);

  num_page = gtk_notebook_get_n_pages (notebook);
  gwidget = glade_widget_get_from_gobject (object);

  special_child_type = g_object_get_data (child, "special-child-type");
  if (special_child_type && !strcmp (special_child_type, "action-start"))
    {
      gtk_notebook_set_action_widget (notebook, GTK_WIDGET (child), GTK_PACK_START);
    }
  else if (special_child_type && !strcmp (special_child_type, "action-end"))
    {
      gtk_notebook_set_action_widget (notebook, GTK_WIDGET (child), GTK_PACK_END);
    }
  else if (glade_widget_superuser ())
    {
      /* Just append pages blindly when loading/dupping */
      special_child_type = g_object_get_data (child, "special-child-type");
      if (special_child_type && !strcmp (special_child_type, "tab"))
        {
          last_page = gtk_notebook_get_nth_page (notebook, num_page - 1);
          gtk_notebook_set_tab_label (notebook, last_page, GTK_WIDGET (child));
        }
      else
        {
          gtk_container_add (GTK_CONTAINER (object), GTK_WIDGET (child));

          glade_widget_property_set (gwidget, "pages", num_page + 1);

          gwidget = glade_widget_get_from_gobject (child);
          if (gwidget && glade_widget_get_packing_properties (gwidget))
            glade_widget_pack_property_set (gwidget, "position", num_page);
        }
    }
  else
    {
      NotebookChildren *nchildren;

      /* Just destroy placeholders */
      if (GLADE_IS_PLACEHOLDER (child))
        gtk_widget_destroy (GTK_WIDGET (child));
      else
        {
          gwidget = glade_widget_get_from_gobject (child);
          g_assert (gwidget);

          glade_widget_pack_property_get (gwidget, "position", &position);

          nchildren =
              glade_gtk_notebook_extract_children (GTK_WIDGET (notebook));

          if (g_object_get_data (child, "special-child-type") != NULL)
            {
              if (g_list_find_custom (nchildren->tabs,
                                      GINT_TO_POINTER (position),
                                      (GCompareFunc) notebook_find_child))
                nchildren->extra_tabs =
                    g_list_insert_sorted
                    (nchildren->extra_tabs, child,
                     (GCompareFunc) notebook_child_compare_func);
              else
                nchildren->tabs =
                    g_list_insert_sorted
                    (nchildren->tabs, child,
                     (GCompareFunc) notebook_child_compare_func);
            }
          else
            {
              if (g_list_find_custom (nchildren->children,
                                      GINT_TO_POINTER (position),
                                      (GCompareFunc) notebook_find_child))
                nchildren->extra_children =
                    g_list_insert_sorted
                    (nchildren->extra_children, child,
                     (GCompareFunc) notebook_child_compare_func);
              else
                nchildren->children =
                    g_list_insert_sorted
                    (nchildren->children, child,
                     (GCompareFunc) notebook_child_compare_func);
            }

          /* Takes an explicit reference when sitting on the list */
          g_object_ref (child);

          glade_gtk_notebook_insert_children (GTK_WIDGET (notebook), nchildren);
        }
    }
}

void
glade_gtk_notebook_remove_child (GladeWidgetAdaptor * adaptor,
                                 GObject * object, GObject * child)
{
  NotebookChildren *nchildren;
  gchar *special_child_type;

  special_child_type = g_object_get_data (child, "special-child-type");
  if (special_child_type && !strcmp (special_child_type, "action-start"))
    {
      GtkWidget *placeholder = glade_placeholder_new ();
      g_object_set_data (G_OBJECT (placeholder), "special-child-type", "action-start");
      gtk_notebook_set_action_widget (GTK_NOTEBOOK (object), placeholder, GTK_PACK_START);
      return;
    }
  else if (special_child_type && !strcmp (special_child_type, "action-end"))
    {
      GtkWidget *placeholder = glade_placeholder_new ();
      g_object_set_data (G_OBJECT (placeholder), "special-child-type", "action-end");
      gtk_notebook_set_action_widget (GTK_NOTEBOOK (object), placeholder, GTK_PACK_END);
      return;
    }

  nchildren = glade_gtk_notebook_extract_children (GTK_WIDGET (object));

  if (g_list_find (nchildren->children, child))
    {
      nchildren->children = g_list_remove (nchildren->children, child);
      g_object_unref (child);
    }
  else if (g_list_find (nchildren->extra_children, child))
    {
      nchildren->extra_children =
          g_list_remove (nchildren->extra_children, child);
      g_object_unref (child);
    }
  else if (g_list_find (nchildren->tabs, child))
    {
      nchildren->tabs = g_list_remove (nchildren->tabs, child);
      g_object_unref (child);
    }
  else if (g_list_find (nchildren->extra_tabs, child))
    {
      nchildren->extra_tabs = g_list_remove (nchildren->extra_tabs, child);
      g_object_unref (child);
    }

  glade_gtk_notebook_insert_children (GTK_WIDGET (object), nchildren);

}

void
glade_gtk_notebook_replace_child (GladeWidgetAdaptor * adaptor,
                                  GtkWidget * container,
                                  GtkWidget * current, GtkWidget * new_widget)
{
  GtkNotebook *notebook;
  GladeWidget *gcurrent, *gnew;
  gint position = 0;
  gchar *special_child_type;

  notebook = GTK_NOTEBOOK (container);

  special_child_type = g_object_get_data (G_OBJECT (current), "special-child-type");
  g_object_set_data (G_OBJECT (new_widget), "special-child-type", special_child_type);
  if (!g_strcmp0 (special_child_type, "action-start"))
    {
      gtk_notebook_set_action_widget (notebook, GTK_WIDGET (new_widget), GTK_PACK_START);
      return;
    }
  else if (!g_strcmp0 (special_child_type, "action-end"))
    {
      gtk_notebook_set_action_widget (notebook, GTK_WIDGET (new_widget), GTK_PACK_END);
      return;
    }

  if ((gcurrent = glade_widget_get_from_gobject (current)) != NULL)
    glade_widget_pack_property_get (gcurrent, "position", &position);
  else
    {
      if ((position = gtk_notebook_page_num (notebook, current)) < 0)
        {
          position = notebook_search_tab (notebook, current);
          g_assert (position >= 0);
        }
    }

  glade_gtk_notebook_remove_child (adaptor,
                                   G_OBJECT (container), G_OBJECT (current));

  if (GLADE_IS_PLACEHOLDER (new_widget) == FALSE)
    {
      gnew = glade_widget_get_from_gobject (new_widget);

      glade_gtk_notebook_add_child (adaptor,
                                    G_OBJECT (container),
                                    G_OBJECT (new_widget));

      if (glade_widget_pack_property_set (gnew, "position", position) == FALSE)
        g_critical ("No position property found on new widget");
    }
  else
    gtk_widget_destroy (GTK_WIDGET (new_widget));
}

gboolean
glade_gtk_notebook_child_verify_property (GladeWidgetAdaptor * adaptor,
                                          GObject * container,
                                          GObject * child,
                                          const gchar * id, GValue * value)
{
  if (!strcmp (id, "position"))
    return g_value_get_int (value) >= 0 &&
        g_value_get_int (value) <
        gtk_notebook_get_n_pages (GTK_NOTEBOOK (container));
  else if (GWA_GET_CLASS (GTK_TYPE_CONTAINER)->child_verify_property)
    GWA_GET_CLASS
        (GTK_TYPE_CONTAINER)->child_verify_property (adaptor,
                                                     container, child,
                                                     id, value);

  return TRUE;
}

void
glade_gtk_notebook_set_child_property (GladeWidgetAdaptor * adaptor,
                                       GObject * container,
                                       GObject * child,
                                       const gchar * property_name,
                                       const GValue * value)
{
  NotebookChildren *nchildren;

  if (strcmp (property_name, "position") == 0)
    {
      /* If we are setting this internally, avoid feedback. */
      if (glade_gtk_notebook_setting_position || glade_widget_superuser ())
        return;

      /* Just rebuild the notebook, property values are already set at this point */
      nchildren = glade_gtk_notebook_extract_children (GTK_WIDGET (container));
      glade_gtk_notebook_insert_children (GTK_WIDGET (container), nchildren);
    }
  /* packing properties are unsupported on tabs ... except "position" */
  else if (g_object_get_data (child, "special-child-type") == NULL)
    GWA_GET_CLASS
        (GTK_TYPE_CONTAINER)->child_set_property (adaptor,
                                                  container, child,
                                                  property_name, value);
}

void
glade_gtk_notebook_get_child_property (GladeWidgetAdaptor * adaptor,
                                       GObject * container,
                                       GObject * child,
                                       const gchar * property_name,
                                       GValue * value)
{
  gint position;

  if (strcmp (property_name, "position") == 0)
    {
      if (g_strcmp0 (g_object_get_data (child, "special-child-type"), "tab") == 0)
        {
          if ((position = notebook_search_tab (GTK_NOTEBOOK (container),
                                               GTK_WIDGET (child))) >= 0)
            g_value_set_int (value, position);
          else
            g_value_set_int (value, 0);
        }
      else if (g_object_get_data (child, "special-child-type") != NULL)
        {
          g_value_set_int (value, 0);
        }
      else
        gtk_container_child_get_property (GTK_CONTAINER (container),
                                          GTK_WIDGET (child),
                                          property_name, value);
    }
  /* packing properties are unsupported on tabs ... except "position" */
  else if (g_object_get_data (child, "special-child-type") == NULL)
    gtk_container_child_get_property (GTK_CONTAINER (container),
                                      GTK_WIDGET (child), property_name, value);
}

void
glade_gtk_notebook_child_action_activate (GladeWidgetAdaptor *adaptor,
                                          GObject            *container,
                                          GObject            *object,
                                          const gchar        *action_path)
{
  if (strcmp (action_path, "insert_page_after") == 0)
    {
      glade_gtk_box_notebook_child_insert_remove_action (adaptor, container,
                                                         object, FALSE, TRUE);
    }
  else if (strcmp (action_path, "insert_page_before") == 0)
    {
      glade_gtk_box_notebook_child_insert_remove_action (adaptor, container,
                                                         object, FALSE, FALSE);
    }
  else if (strcmp (action_path, "remove_page") == 0)
    {
      glade_gtk_box_notebook_child_insert_remove_action (adaptor, container,
                                                         object, TRUE, TRUE);
    }
  else
    GWA_GET_CLASS (GTK_TYPE_CONTAINER)->child_action_activate (adaptor,
                                                               container,
                                                               object,
                                                               action_path);
}

/* Shared with glade-gtk-box.c */
void
glade_gtk_box_notebook_child_insert_remove_action (GladeWidgetAdaptor *adaptor,
                                                   GObject            *container,
                                                   GObject            *object,
                                                   gboolean            remove,
                                                   gboolean            after)
{
  gboolean is_notebook = GTK_IS_NOTEBOOK (container);
  const gchar *size_prop = (is_notebook) ? "pages" : "size";
  GladeWidget *parent;
  GList *children, *l;
  gint child_pos, size, offset;

  if (is_notebook && g_object_get_data (object, "special-child-type"))
    /* Its a Tab! */
    child_pos = notebook_search_tab (GTK_NOTEBOOK (container),
                                     GTK_WIDGET (object));
  else
    gtk_container_child_get (GTK_CONTAINER (container),
                             GTK_WIDGET (object), "position", &child_pos, NULL);

  parent = glade_widget_get_from_gobject (container);
  if (is_notebook)
    {
      if (remove)
        glade_command_push_group (_("Remove page from %s"), glade_widget_get_name (parent));
      else
        glade_command_push_group (_("Insert page on %s"), glade_widget_get_name (parent));
    }
  else
    {
      if (remove)
        glade_command_push_group (_("Remove placeholder from %s"), glade_widget_get_name (parent));
      else
        glade_command_push_group (_("Insert placeholder to %s"), glade_widget_get_name (parent));
    }

  /* Make sure widgets does not get destroyed */
  children = glade_widget_adaptor_get_children (adaptor, container);
  g_list_foreach (children, (GFunc) g_object_ref, NULL);

  glade_widget_property_get (parent, size_prop, &size);

  if (remove)
    {
      GList *del = NULL;
      offset = -1;
      /* Remove children first */
      for (l = children; l; l = g_list_next (l))
        {
          GladeWidget *gchild = glade_widget_get_from_gobject (l->data);
          gint pos;

          /* Skip placeholders */
          if (gchild == NULL)
            continue;

          glade_widget_pack_property_get (gchild, "position", &pos);
          if (pos == child_pos)
            del = g_list_prepend (del, gchild);
        }
      if (del)
        {
          glade_command_delete (del);
          g_list_free (del);
        }
    }
  else
    {
      /* Expand container */
      glade_command_set_property (glade_widget_get_property (parent, size_prop),
                                  size + 1);
      offset = 1;
    }

  /* Reoder children (fix the position property tracking widget positions) */
  for (l = g_list_last (children); l; l = g_list_previous (l))
    {
      GladeWidget *gchild = glade_widget_get_from_gobject (l->data);
      gint pos;

      /* Skip placeholders */
      if (gchild == NULL)
        continue;

      glade_widget_pack_property_get (gchild, "position", &pos);
      if ((after) ? pos > child_pos : pos >= child_pos)
        glade_command_set_property (glade_widget_get_pack_property
                                    (gchild, "position"), pos + offset);
    }

  if (remove)
    {
      /* Shrink container */
      glade_command_set_property (glade_widget_get_property (parent, size_prop),
                                  size - 1);
    }
  /* If it's a notebook we need to create an undoable tab now */
  else if (GTK_IS_NOTEBOOK (container))
    {
      gint new_pos = after ? child_pos + 1 : child_pos;
      GtkWidget *new_page;
      GtkWidget *tab_placeholder;
      GladeWidget *gtab;
      GList list = { 0, };

      new_page = gtk_notebook_get_nth_page (GTK_NOTEBOOK (container), new_pos);

      /* Deleting the project widget gives us a real placeholder now */
      new_page = gtk_notebook_get_nth_page (GTK_NOTEBOOK (container), new_pos);
      tab_placeholder =
          gtk_notebook_get_tab_label (GTK_NOTEBOOK (container), new_page);
      gtab = glade_gtk_notebook_generate_tab (parent, new_pos + 1);
      list.data = gtab;

      glade_command_paste (&list, parent, GLADE_PLACEHOLDER (tab_placeholder),
			   glade_widget_get_project (parent));
    }

  g_list_foreach (children, (GFunc) g_object_unref, NULL);
  g_list_free (children);
  glade_command_pop_group ();
}