/* PIKA - Photo and Image Kooker Application
 * a rebranding of The GNU Image Manipulation Program (created with heckimp)
 * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
 *
 * Original copyright, applying to most contents (license remains unchanged): 
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * pikadockbook.c
 * Copyright (C) 2001-2007 Michael Natterer <mitch@gimp.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

#include "config.h"

#include <string.h>

#include <gegl.h>
#undef GTK_DISABLE_DEPRECATED
#include <gtk/gtk.h>

#include "libpikabase/pikabase.h"
#include "libpikawidgets/pikawidgets.h"

#include "widgets-types.h"

#include "config/pikaguiconfig.h"

#include "core/pika.h"
#include "core/pikacontext.h"

#include "menus/menus.h"

#include "pikaactiongroup.h"
#include "pikadialogfactory.h"
#include "pikadnd.h"
#include "pikadock.h"
#include "pikadockable.h"
#include "pikadockbook.h"
#include "pikadocked.h"
#include "pikadockcontainer.h"
#include "pikadockwindow.h"
#include "pikahelp-ids.h"
#include "pikamenufactory.h"
#include "pikapanedbox.h"
#include "pikastringaction.h"
#include "pikauimanager.h"
#include "pikaview.h"
#include "pikawidgets-utils.h"

#include "pika-log.h"
#include "pika-intl.h"


#define DEFAULT_TAB_ICON_SIZE        GTK_ICON_SIZE_BUTTON
#define DND_WIDGET_ICON_SIZE         GTK_ICON_SIZE_BUTTON
#define MENU_WIDGET_SPACING          4
#define TAB_HOVER_TIMEOUT            500
#define PIKA_DOCKABLE_DETACH_REF_KEY "pika-dockable-detach-ref"


enum
{
  DOCKABLE_ADDED,
  DOCKABLE_REMOVED,
  DOCKABLE_REORDERED,
  LAST_SIGNAL
};


typedef struct
{
  PikaDockbookDragCallback callback;
  gpointer                 data;
} PikaDockbookDragCallbackData;

struct _PikaDockbookPrivate
{
  PikaDock       *dock;
  PikaUIManager  *ui_manager;

  guint           tab_hover_timeout;
  PikaDockable   *tab_hover_dockable;

  PikaPanedBox   *drag_handler;

  GtkWidget      *menu_button;
};


static void         pika_dockbook_style_updated     (GtkWidget       *widget);
static void         pika_dockbook_drag_begin        (GtkWidget      *widget,
                                                     GdkDragContext *context);
static void         pika_dockbook_drag_end          (GtkWidget      *widget,
                                                     GdkDragContext *context);
static gboolean     pika_dockbook_drag_motion       (GtkWidget      *widget,
                                                     GdkDragContext *context,
                                                     gint            x,
                                                     gint            y,
                                                     guint           time);
static gboolean     pika_dockbook_drag_drop         (GtkWidget      *widget,
                                                     GdkDragContext *context,
                                                     gint            x,
                                                     gint            y,
                                                     guint           time);
static gboolean     pika_dockbook_popup_menu        (GtkWidget      *widget);

static GtkNotebook *pika_dockbook_create_window     (GtkNotebook    *notebook,
                                                     GtkWidget      *page,
                                                     gint            x,
                                                     gint            y);
static void         pika_dockbook_page_added        (GtkNotebook    *notebook,
                                                     GtkWidget      *child,
                                                     guint           page_num);
static void         pika_dockbook_page_removed      (GtkNotebook    *notebook,
                                                     GtkWidget      *child,
                                                     guint           page_num);
static void         pika_dockbook_page_reordered    (GtkNotebook    *notebook,
                                                     GtkWidget      *child,
                                                     guint           page_num);

static gboolean     pika_dockbook_menu_button_press (PikaDockbook   *dockbook,
                                                     GdkEventButton *bevent,
                                                     GtkWidget      *button);
static gboolean     pika_dockbook_show_menu         (PikaDockbook   *dockbook);
static void         pika_dockbook_menu_end          (PikaDockable   *dockable);
static void         pika_dockbook_tab_locked_notify (PikaDockable   *dockable,
                                                     GParamSpec     *pspec,
                                                     PikaDockbook   *dockbook);

static void         pika_dockbook_help_func         (const gchar    *help_id,
                                                     gpointer        help_data);


G_DEFINE_TYPE_WITH_PRIVATE (PikaDockbook, pika_dockbook, GTK_TYPE_NOTEBOOK)

#define parent_class pika_dockbook_parent_class

static guint dockbook_signals[LAST_SIGNAL] = { 0 };

static const GtkTargetEntry dialog_target_table[] = { PIKA_TARGET_NOTEBOOK_TAB };

static GList *drag_callbacks = NULL;


static void
pika_dockbook_class_init (PikaDockbookClass *klass)
{
  GtkWidgetClass   *widget_class   = GTK_WIDGET_CLASS (klass);
  GtkNotebookClass *notebook_class = GTK_NOTEBOOK_CLASS (klass);

  dockbook_signals[DOCKABLE_ADDED] =
    g_signal_new ("dockable-added",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (PikaDockbookClass, dockable_added),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  PIKA_TYPE_DOCKABLE);

  dockbook_signals[DOCKABLE_REMOVED] =
    g_signal_new ("dockable-removed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (PikaDockbookClass, dockable_removed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  PIKA_TYPE_DOCKABLE);

  dockbook_signals[DOCKABLE_REORDERED] =
    g_signal_new ("dockable-reordered",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (PikaDockbookClass, dockable_reordered),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  PIKA_TYPE_DOCKABLE);

  widget_class->style_updated    = pika_dockbook_style_updated;
  widget_class->drag_begin       = pika_dockbook_drag_begin;
  widget_class->drag_end         = pika_dockbook_drag_end;
  widget_class->drag_motion      = pika_dockbook_drag_motion;
  widget_class->drag_drop        = pika_dockbook_drag_drop;
  widget_class->popup_menu       = pika_dockbook_popup_menu;

  notebook_class->create_window  = pika_dockbook_create_window;
  notebook_class->page_added     = pika_dockbook_page_added;
  notebook_class->page_removed   = pika_dockbook_page_removed;
  notebook_class->page_reordered = pika_dockbook_page_reordered;

  klass->dockable_added          = NULL;
  klass->dockable_removed        = NULL;
  klass->dockable_reordered      = NULL;

  gtk_widget_class_install_style_property (widget_class,
                                           g_param_spec_enum ("tab-icon-size",
                                                              NULL, NULL,
                                                              GTK_TYPE_ICON_SIZE,
                                                              DEFAULT_TAB_ICON_SIZE,
                                                              PIKA_PARAM_READABLE));
}

static void
pika_dockbook_init (PikaDockbook *dockbook)
{
  GtkNotebook *notebook = GTK_NOTEBOOK (dockbook);
  GtkWidget   *image;

  dockbook->p = pika_dockbook_get_instance_private (dockbook);

  /* Various init */
  gtk_notebook_popup_enable (notebook);
  gtk_notebook_set_scrollable (notebook, TRUE);
  gtk_notebook_set_show_border (notebook, FALSE);
  gtk_notebook_set_show_tabs (notebook, TRUE);
  gtk_notebook_set_group_name (notebook, "pika-dockbook");

  gtk_drag_dest_set (GTK_WIDGET (dockbook),
                     0,
                     dialog_target_table, G_N_ELEMENTS (dialog_target_table),
                     GDK_ACTION_MOVE);

  /* Menu button */
  dockbook->p->menu_button = gtk_button_new ();
  gtk_widget_set_can_focus (dockbook->p->menu_button, FALSE);
  gtk_button_set_relief (GTK_BUTTON (dockbook->p->menu_button),
                         GTK_RELIEF_NONE);
  gtk_notebook_set_action_widget (notebook,
                                  dockbook->p->menu_button,
                                  GTK_PACK_END);
  gtk_widget_show (dockbook->p->menu_button);

  image = gtk_image_new_from_icon_name (PIKA_ICON_MENU_LEFT,
                                        GTK_ICON_SIZE_MENU);
  gtk_image_set_pixel_size (GTK_IMAGE (image), 12);
  gtk_image_set_from_icon_name (GTK_IMAGE (image), PIKA_ICON_MENU_LEFT,
                                GTK_ICON_SIZE_MENU);
  gtk_container_add (GTK_CONTAINER (dockbook->p->menu_button), image);
  gtk_widget_show (image);

  pika_help_set_help_data (dockbook->p->menu_button, _("Configure this tab"),
                           PIKA_HELP_DOCK_TAB_MENU);

  g_signal_connect_swapped (dockbook->p->menu_button, "button-press-event",
                            G_CALLBACK (pika_dockbook_menu_button_press),
                            dockbook);
}

static void
pika_dockbook_style_updated (GtkWidget *widget)
{
  PikaDockbook *dockbook = PIKA_DOCKBOOK (widget);
  PikaContext  *context;
  GtkWidget    *tab_widget;
  GList        *children;
  GList        *iter;

  GTK_WIDGET_CLASS (parent_class)->style_updated (widget);

  if (! dockbook->p->dock ||
      ! (context = pika_dock_get_context (dockbook->p->dock)))
    return;

  children = gtk_container_get_children (GTK_CONTAINER (dockbook));
  for (iter = children; iter; iter = g_list_next (iter))
    {
      PikaDockable *dockable = PIKA_DOCKABLE (iter->data);

      tab_widget = pika_dockbook_create_tab_widget (dockbook, dockable);
      gtk_notebook_set_tab_label (GTK_NOTEBOOK (dockbook),
                                  GTK_WIDGET (dockable),
                                  tab_widget);
    }
  g_list_free (children);

  pika_dock_invalidate_geometry (PIKA_DOCK (dockbook->p->dock));
}

static void
pika_dockbook_drag_begin (GtkWidget      *widget,
                          GdkDragContext *context)
{
  GList *iter;

  if (GTK_WIDGET_CLASS (parent_class)->drag_begin)
    GTK_WIDGET_CLASS (parent_class)->drag_begin (widget, context);

  iter = drag_callbacks;

  while (iter)
    {
      PikaDockbookDragCallbackData *callback_data = iter->data;

      iter = g_list_next (iter);

      callback_data->callback (context, TRUE, callback_data->data);
    }
}

static void
pika_dockbook_drag_end (GtkWidget      *widget,
                        GdkDragContext *context)
{
  GList *iter;

  iter = drag_callbacks;

  while (iter)
    {
      PikaDockbookDragCallbackData *callback_data = iter->data;

      iter = g_list_next (iter);

      callback_data->callback (context, FALSE, callback_data->data);
    }

  if (GTK_WIDGET_CLASS (parent_class)->drag_end)
    GTK_WIDGET_CLASS (parent_class)->drag_end (widget, context);
}

static gboolean
pika_dockbook_drag_motion (GtkWidget      *widget,
                           GdkDragContext *context,
                           gint            x,
                           gint            y,
                           guint           time)
{
  PikaDockbook *dockbook = PIKA_DOCKBOOK (widget);

  if (pika_paned_box_will_handle_drag (dockbook->p->drag_handler,
                                       widget,
                                       context,
                                       x, y,
                                       time))
    {
      gdk_drag_status (context, 0, time);

      return FALSE;
    }

  return GTK_WIDGET_CLASS (parent_class)->drag_motion (widget, context,
                                                       x, y, time);
}

static gboolean
pika_dockbook_drag_drop (GtkWidget      *widget,
                         GdkDragContext *context,
                         gint            x,
                         gint            y,
                         guint           time)
{
  PikaDockbook *dockbook = PIKA_DOCKBOOK (widget);

  if (pika_paned_box_will_handle_drag (dockbook->p->drag_handler,
                                       widget,
                                       context,
                                       x, y,
                                       time))
    {
      return FALSE;
    }

  if (widget == gtk_drag_get_source_widget (context))
    {
      GList *children   = gtk_container_get_children (GTK_CONTAINER (widget));
      gint   n_children = g_list_length (children);

      g_list_free (children);

      /* we dragged the only tab, and want to drop it back on the same
       * notebook, this would remove and add the page, causing the
       * dockbook to be destroyed in the process because it becomes
       * empty
       */
      if (n_children == 1)
        return FALSE;
    }

  return GTK_WIDGET_CLASS (parent_class)->drag_drop (widget, context,
                                                     x, y, time);
}

static gboolean
pika_dockbook_popup_menu (GtkWidget *widget)
{
  return pika_dockbook_show_menu (PIKA_DOCKBOOK (widget));
}

static GtkNotebook *
pika_dockbook_create_window (GtkNotebook *notebook,
                             GtkWidget   *page,
                             gint         x,
                             gint         y)
{
  PikaDockbook      *dockbook = PIKA_DOCKBOOK (notebook);
  PikaDialogFactory *dialog_factory;
  PikaMenuFactory   *menu_factory;
  PikaDockWindow    *src_dock_window;
  PikaDock          *src_dock;
  GtkWidget         *new_dock;
  PikaDockWindow    *new_dock_window;
  GtkWidget         *new_dockbook;

  src_dock        = pika_dockbook_get_dock (dockbook);
  src_dock_window = pika_dock_window_from_dock (src_dock);

  dialog_factory = pika_dock_get_dialog_factory (src_dock);
  menu_factory   = menus_get_global_menu_factory (pika_dialog_factory_get_context (dialog_factory)->pika);

  new_dock = pika_dock_with_window_new (dialog_factory,
                                        pika_widget_get_monitor (page),
                                        FALSE);

  new_dock_window = pika_dock_window_from_dock (PIKA_DOCK (new_dock));
  gtk_window_set_position (GTK_WINDOW (new_dock_window), GTK_WIN_POS_MOUSE);
  if (src_dock_window)
    pika_dock_window_setup (new_dock_window, src_dock_window);

  new_dockbook = pika_dockbook_new (menu_factory);

  pika_dock_add_book (PIKA_DOCK (new_dock), PIKA_DOCKBOOK (new_dockbook), 0);

  gtk_widget_show (GTK_WIDGET (new_dock_window));
  gtk_widget_show (new_dock);

  return GTK_NOTEBOOK (new_dockbook);
}

static void
pika_dockbook_page_added (GtkNotebook *notebook,
                          GtkWidget   *child,
                          guint        page_num)
{
  PikaDockbook *dockbook = PIKA_DOCKBOOK (notebook);
  PikaDockable *dockable = PIKA_DOCKABLE (child);
  GtkWidget    *tab_widget;
  GtkWidget    *menu_widget;

  PIKA_LOG (DND, "PikaDockable %p added to PikaDockbook %p",
            dockable, dockbook);

  tab_widget = pika_dockbook_create_tab_widget (dockbook, dockable);

  /* For the notebook right-click menu, always use the icon style */
  menu_widget =
    pika_dockable_create_tab_widget (dockable,
                                     pika_dock_get_context (dockbook->p->dock),
                                     PIKA_TAB_STYLE_ICON_BLURB,
                                     GTK_ICON_SIZE_MENU);

  gtk_notebook_set_tab_label (notebook, child, tab_widget);
  gtk_notebook_set_menu_label (notebook, child, menu_widget);

  if (! pika_dockable_get_locked (dockable))
    {
      gtk_notebook_set_tab_reorderable (notebook, child, TRUE);
      gtk_notebook_set_tab_detachable (notebook, child, TRUE);
    }

  pika_dockable_set_dockbook (dockable, dockbook);

  pika_dockable_set_context (dockable,
                             pika_dock_get_context (dockbook->p->dock));

  g_signal_connect (dockable, "notify::locked",
                    G_CALLBACK (pika_dockbook_tab_locked_notify),
                    dockbook);

  gtk_widget_show (child);
  gtk_notebook_set_current_page (notebook, page_num);

  g_signal_emit (dockbook, dockbook_signals[DOCKABLE_ADDED], 0, dockable);
}

static void
pika_dockbook_page_removed (GtkNotebook *notebook,
                            GtkWidget   *child,
                            guint        page_num)
{
  PikaDockbook *dockbook = PIKA_DOCKBOOK (notebook);
  PikaDockable *dockable = PIKA_DOCKABLE (child);

  PIKA_LOG (DND, "PikaDockable removed %p from PikaDockbook %p",
            dockable, dockbook);

  g_signal_handlers_disconnect_by_func (dockable,
                                        G_CALLBACK (pika_dockbook_tab_locked_notify),
                                        dockbook);

  pika_dockable_set_dockbook (dockable, NULL);

  pika_dockable_set_context (dockable, NULL);

  g_signal_emit (dockbook, dockbook_signals[DOCKABLE_REMOVED], 0, dockable);

  if (dockbook->p->dock)
    {
      GList *children = gtk_container_get_children (GTK_CONTAINER (dockbook));

      if (! children)
        pika_dock_remove_book (dockbook->p->dock, dockbook);

      g_list_free (children);
    }
}

static void
pika_dockbook_page_reordered (GtkNotebook *notebook,
                              GtkWidget   *child,
                              guint        page_num)
{
}

static gboolean
pika_dockbook_menu_button_press (PikaDockbook   *dockbook,
                                 GdkEventButton *bevent,
                                 GtkWidget      *button)
{
  gboolean handled = FALSE;

  if (bevent->button == 1 && bevent->type == GDK_BUTTON_PRESS)
    handled = pika_dockbook_show_menu (dockbook);

  return handled;
}

static gboolean
pika_dockbook_show_menu (PikaDockbook *dockbook)
{
  PikaUIManager *dockbook_ui_manager;
  PikaUIManager *dialog_ui_manager;
  const gchar   *dialog_ui_path;
  gpointer       dialog_popup_data;
  PikaDockable  *dockable;
  gint           page_num;

  dockbook_ui_manager = dockbook->p->ui_manager;

  if (! dockbook_ui_manager)
    return FALSE;

  page_num = gtk_notebook_get_current_page (GTK_NOTEBOOK (dockbook));
  dockable = PIKA_DOCKABLE (gtk_notebook_get_nth_page (GTK_NOTEBOOK (dockbook),
                                                       page_num));

  if (! dockable)
    return FALSE;

  dialog_ui_manager = pika_dockable_get_menu (dockable,
                                              &dialog_ui_path,
                                              &dialog_popup_data);

  /*  an action callback may destroy both dockable and dockbook, so
   *  reference them for pika_dockbook_menu_end()
   */
  g_object_ref (dockable);
  g_object_set_data_full (G_OBJECT (dockable), PIKA_DOCKABLE_DETACH_REF_KEY,
                          g_object_ref (dockbook),
                          g_object_unref);

  if (dialog_ui_manager)
    pika_ui_manager_update (dialog_ui_manager, dialog_popup_data);

  pika_ui_manager_update (dockbook_ui_manager, dockable);

  pika_ui_manager_ui_popup_at_widget (dockbook_ui_manager,
                                      "/dockable-popup",
                                      dialog_ui_manager,
                                      dialog_ui_path,
                                      dockbook->p->menu_button,
                                      GDK_GRAVITY_WEST,
                                      GDK_GRAVITY_NORTH_EAST,
                                      NULL,
                                      (GDestroyNotify) pika_dockbook_menu_end,
                                      dockable);

  return TRUE;
}

static void
pika_dockbook_menu_end (PikaDockable *dockable)
{
  /*  release pika_dockbook_show_menu()'s references  */
  g_object_set_data (G_OBJECT (dockable), PIKA_DOCKABLE_DETACH_REF_KEY, NULL);
  g_object_unref (dockable);
}


/*  public functions  */

GtkWidget *
pika_dockbook_new (PikaMenuFactory *menu_factory)
{
  PikaDockbook *dockbook;

  g_return_val_if_fail (PIKA_IS_MENU_FACTORY (menu_factory), NULL);

  dockbook = g_object_new (PIKA_TYPE_DOCKBOOK, NULL);

  dockbook->p->ui_manager = pika_menu_factory_get_manager (menu_factory,
                                                           "<Dockable>",
                                                           dockbook);

  pika_help_connect (GTK_WIDGET (dockbook), pika_dockbook_help_func,
                     PIKA_HELP_DOCK, dockbook, NULL);

  return GTK_WIDGET (dockbook);
}

PikaDock *
pika_dockbook_get_dock (PikaDockbook *dockbook)
{
  g_return_val_if_fail (PIKA_IS_DOCKBOOK (dockbook), NULL);

  return dockbook->p->dock;
}

void
pika_dockbook_set_dock (PikaDockbook *dockbook,
                        PikaDock     *dock)
{
  PikaContext *context;

  g_return_if_fail (PIKA_IS_DOCKBOOK (dockbook));
  g_return_if_fail (dock == NULL || PIKA_IS_DOCK (dock));

  if (dockbook->p->dock &&
      (context = pika_dock_get_context (dockbook->p->dock)) != NULL)
    {
      g_signal_handlers_disconnect_by_func (PIKA_GUI_CONFIG (context->pika->config),
                                            G_CALLBACK (pika_dockbook_style_updated),
                                            dockbook);
    }

  dockbook->p->dock = dock;

  if (dockbook->p->dock &&
      (context = pika_dock_get_context (dockbook->p->dock)) != NULL)
    {
      g_signal_connect_object (PIKA_GUI_CONFIG (context->pika->config),
                               "notify::theme",
                               G_CALLBACK (pika_dockbook_style_updated),
                               dockbook, G_CONNECT_AFTER | G_CONNECT_SWAPPED);
      g_signal_connect_object (PIKA_GUI_CONFIG (context->pika->config),
                               "notify::override-theme-icon-size",
                               G_CALLBACK (pika_dockbook_style_updated),
                               dockbook, G_CONNECT_AFTER | G_CONNECT_SWAPPED);
      g_signal_connect_object (PIKA_GUI_CONFIG (context->pika->config),
                               "notify::custom-icon-size",
                               G_CALLBACK (pika_dockbook_style_updated),
                               dockbook, G_CONNECT_AFTER | G_CONNECT_SWAPPED);
    }
}

/**
 * pika_dockbook_add_from_dialog_factory:
 * @dockbook:    The #DockBook
 * @identifiers: The dockable identifier(s)
 *
 * Add a dockable from the dialog factory associated with the dockbook.
 **/
GtkWidget *
pika_dockbook_add_from_dialog_factory (PikaDockbook *dockbook,
                                       const gchar  *identifiers)
{
  GtkWidget *dockable;
  PikaDock  *dock;
  gchar     *identifier;
  gchar     *p;

  g_return_val_if_fail (PIKA_IS_DOCKBOOK (dockbook), NULL);
  g_return_val_if_fail (identifiers != NULL, NULL);

  identifier = g_strdup (identifiers);

  p = strchr (identifier, '|');

  if (p)
    *p = '\0';

  dock     = pika_dockbook_get_dock (dockbook);
  dockable = pika_dialog_factory_dockable_new (pika_dock_get_dialog_factory (dock),
                                               dock,
                                               identifier, -1);

  g_free (identifier);

  /*  Maybe pika_dialog_factory_dockable_new() returned an already
   *  existing singleton dockable, so check if it already is
   *  attached to a dockbook.
   */
  if (dockable && ! pika_dockable_get_dockbook (PIKA_DOCKABLE (dockable)))
    gtk_notebook_append_page (GTK_NOTEBOOK (dockbook),
                              dockable, NULL);

  return dockable;
}

/**
 * pika_dockbook_update_with_context:
 * @dockbook:
 * @context:
 *
 * Set @context on all dockables in @dockbook.
 **/
void
pika_dockbook_update_with_context (PikaDockbook *dockbook,
                                   PikaContext  *context)
{
  GList *children = gtk_container_get_children (GTK_CONTAINER (dockbook));
  GList *iter;

  for (iter = children; iter; iter = g_list_next (iter))
    {
      PikaDockable *dockable = PIKA_DOCKABLE (iter->data);

      pika_dockable_set_context (dockable, context);
    }

  g_list_free (children);
}

GtkWidget *
pika_dockbook_create_tab_widget (PikaDockbook *dockbook,
                                 PikaDockable *dockable)
{
  GtkWidget      *tab_widget;
  PikaDockWindow *dock_window;
  PikaAction     *action = NULL;
  GtkIconSize     tab_size = DEFAULT_TAB_ICON_SIZE;

  gtk_widget_style_get (GTK_WIDGET (dockbook),
                        "tab-icon-size", &tab_size,
                        NULL);
  tab_widget =
    pika_dockable_create_tab_widget (dockable,
                                     pika_dock_get_context (dockbook->p->dock),
                                     pika_dockable_get_tab_style (dockable),
                                     tab_size);

  if (PIKA_IS_VIEW (tab_widget))
    {
      GtkWidget *event_box;

      event_box = gtk_event_box_new ();
      gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
      gtk_event_box_set_above_child (GTK_EVENT_BOX (event_box), TRUE);
      gtk_container_add (GTK_CONTAINER (event_box), tab_widget);
      gtk_widget_show (tab_widget);

      tab_widget = event_box;
    }

  /* EEK */
  dock_window = pika_dock_window_from_dock (dockbook->p->dock);
  if (dock_window &&
      pika_dock_container_get_ui_manager (PIKA_DOCK_CONTAINER (dock_window)))
    {
      const gchar *dialog_id;

      dialog_id = g_object_get_data (G_OBJECT (dockable),
                                     "pika-dialog-identifier");

      if (dialog_id)
        {
          PikaDockContainer *dock_container;
          PikaActionGroup   *group;

          dock_container = PIKA_DOCK_CONTAINER (dock_window);

          group = pika_ui_manager_get_action_group
            (pika_dock_container_get_ui_manager (dock_container), "dialogs");

          if (group)
            {
              GList *actions;
              GList *list;

              actions = pika_action_group_list_actions (group);

              for (list = actions; list; list = g_list_next (list))
                {
                  if (PIKA_IS_STRING_ACTION (list->data) &&
                      strstr (PIKA_STRING_ACTION (list->data)->value,
                              dialog_id))
                    {
                      action = list->data;
                      break;
                    }
                }

              g_list_free (actions);
            }
        }
    }

  if (action)
    pika_widget_set_accel_help (tab_widget, action);
  else
    pika_help_set_help_data (tab_widget,
                             pika_dockable_get_blurb (dockable),
                             pika_dockable_get_help_id (dockable));

  return tab_widget;
}

/**
 * pika_dockable_set_drag_handler:
 * @dockable:
 * @handler:
 *
 * Set a drag handler that will be asked if it will handle drag events
 * before the dockbook handles the event itself.
 **/
void
pika_dockbook_set_drag_handler (PikaDockbook *dockbook,
                                PikaPanedBox *drag_handler)
{
  g_return_if_fail (PIKA_IS_DOCKBOOK (dockbook));

  dockbook->p->drag_handler = drag_handler;
}

void
pika_dockbook_add_drag_callback (PikaDockbookDragCallback callback,
                                 gpointer                 data)
{
  PikaDockbookDragCallbackData *callback_data;

  callback_data = g_slice_new (PikaDockbookDragCallbackData);

  callback_data->callback = callback;
  callback_data->data     = data;

  drag_callbacks = g_list_prepend (drag_callbacks, callback_data);
}

void
pika_dockbook_remove_drag_callback (PikaDockbookDragCallback callback,
                                    gpointer                 data)
{
  GList *iter;

  iter = drag_callbacks;

  while (iter)
    {
      PikaDockbookDragCallbackData *callback_data = iter->data;
      GList                        *next          = g_list_next (iter);

      if (callback_data->callback == callback &&
          callback_data->data     == data)
        {
          g_slice_free (PikaDockbookDragCallbackData, callback_data);

          drag_callbacks = g_list_delete_link (drag_callbacks, iter);
        }

      iter = next;
    }
}


/*  private functions  */

static void
pika_dockbook_tab_locked_notify (PikaDockable *dockable,
                                 GParamSpec   *pspec,
                                 PikaDockbook *dockbook)
{
  gboolean locked = pika_dockable_get_locked (dockable);

  gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (dockbook),
                                    GTK_WIDGET (dockable), ! locked);
  gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (dockbook),
                                   GTK_WIDGET (dockable), ! locked);
}

static void
pika_dockbook_help_func (const gchar *help_id,
                         gpointer     help_data)
{
  PikaDockbook *dockbook = PIKA_DOCKBOOK (help_data);
  GtkWidget    *dockable;
  gint          page_num;

  page_num = gtk_notebook_get_current_page (GTK_NOTEBOOK (dockbook));

  dockable = gtk_notebook_get_nth_page (GTK_NOTEBOOK (dockbook), page_num);

  if (PIKA_IS_DOCKABLE (dockable))
    pika_standard_help_func (pika_dockable_get_help_id (PIKA_DOCKABLE (dockable)),
                             NULL);
  else
    pika_standard_help_func (PIKA_HELP_DOCK, NULL);
}
