/* GIMP - The GNU Image Manipulation Program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * gimpdockbook.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 "libgimpbase/gimpbase.h"
#include "libgimpwidgets/gimpwidgets.h"

#include "widgets-types.h"

#include "config/gimpguiconfig.h"

#include "core/gimp.h"
#include "core/gimpcontext.h"

#include "gimpactiongroup.h"
#include "gimpdialogfactory.h"
#include "gimpdnd.h"
#include "gimpdock.h"
#include "gimpdockable.h"
#include "gimpdockbook.h"
#include "gimpdocked.h"
#include "gimpdockcontainer.h"
#include "gimpdockwindow.h"
#include "gimphelp-ids.h"
#include "gimpmenufactory.h"
#include "gimppanedbox.h"
#include "gimpstringaction.h"
#include "gimpuimanager.h"
#include "gimpview.h"
#include "gimpwidgets-utils.h"

#include "gimp-log.h"
#include "gimp-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 GIMP_DOCKABLE_DETACH_REF_KEY "gimp-dockable-detach-ref"


enum
{
  DOCKABLE_ADDED,
  DOCKABLE_REMOVED,
  DOCKABLE_REORDERED,
  LAST_SIGNAL
};


typedef struct
{
  GimpDockbookDragCallback callback;
  gpointer                 data;
} GimpDockbookDragCallbackData;

struct _GimpDockbookPrivate
{
  GimpDock       *dock;
  GimpUIManager  *ui_manager;

  guint           tab_hover_timeout;
  GimpDockable   *tab_hover_dockable;

  GimpPanedBox   *drag_handler;

  GtkWidget      *menu_button;
};


static void         gimp_dockbook_finalize          (GObject        *object);

static void         gimp_dockbook_drag_begin        (GtkWidget      *widget,
                                                     GdkDragContext *context);
static void         gimp_dockbook_drag_end          (GtkWidget      *widget,
                                                     GdkDragContext *context);
static gboolean     gimp_dockbook_drag_motion       (GtkWidget      *widget,
                                                     GdkDragContext *context,
                                                     gint            x,
                                                     gint            y,
                                                     guint           time);
static gboolean     gimp_dockbook_drag_drop         (GtkWidget      *widget,
                                                     GdkDragContext *context,
                                                     gint            x,
                                                     gint            y,
                                                     guint           time);
static gboolean     gimp_dockbook_popup_menu        (GtkWidget      *widget);

static GtkNotebook *gimp_dockbook_create_window     (GtkNotebook    *notebook,
                                                     GtkWidget      *page,
                                                     gint            x,
                                                     gint            y);
static void         gimp_dockbook_page_added        (GtkNotebook    *notebook,
                                                     GtkWidget      *child,
                                                     guint           page_num);
static void         gimp_dockbook_page_removed      (GtkNotebook    *notebook,
                                                     GtkWidget      *child,
                                                     guint           page_num);
static void         gimp_dockbook_page_reordered    (GtkNotebook    *notebook,
                                                     GtkWidget      *child,
                                                     guint           page_num);

static gboolean     gimp_dockbook_menu_button_press (GimpDockbook   *dockbook,
                                                     GdkEventButton *bevent,
                                                     GtkWidget      *button);
static gboolean     gimp_dockbook_show_menu         (GimpDockbook   *dockbook);
static void         gimp_dockbook_menu_end          (GimpDockable   *dockable);
static void         gimp_dockbook_tab_locked_notify (GimpDockable   *dockable,
                                                     GParamSpec     *pspec,
                                                     GimpDockbook   *dockbook);
static void         gimp_dockbook_help_func         (const gchar    *help_id,
                                                     gpointer        help_data);


G_DEFINE_TYPE_WITH_PRIVATE (GimpDockbook, gimp_dockbook, GTK_TYPE_NOTEBOOK)

#define parent_class gimp_dockbook_parent_class

static guint dockbook_signals[LAST_SIGNAL] = { 0 };

static const GtkTargetEntry dialog_target_table[] = { GIMP_TARGET_NOTEBOOK_TAB };

static GList *drag_callbacks = NULL;


static void
gimp_dockbook_class_init (GimpDockbookClass *klass)
{
  GObjectClass     *object_class   = G_OBJECT_CLASS (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 (GimpDockbookClass, dockable_added),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  GIMP_TYPE_DOCKABLE);

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

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

  object_class->finalize         = gimp_dockbook_finalize;

  widget_class->drag_begin       = gimp_dockbook_drag_begin;
  widget_class->drag_end         = gimp_dockbook_drag_end;
  widget_class->drag_motion      = gimp_dockbook_drag_motion;
  widget_class->drag_drop        = gimp_dockbook_drag_drop;
  widget_class->popup_menu       = gimp_dockbook_popup_menu;

  notebook_class->create_window  = gimp_dockbook_create_window;
  notebook_class->page_added     = gimp_dockbook_page_added;
  notebook_class->page_removed   = gimp_dockbook_page_removed;
  notebook_class->page_reordered = gimp_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,
                                                              GIMP_PARAM_READABLE));
}

static void
gimp_dockbook_init (GimpDockbook *dockbook)
{
  GtkNotebook *notebook = GTK_NOTEBOOK (dockbook);
  GtkWidget   *image;

  dockbook->p = gimp_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, "gimp-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 (GIMP_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), GIMP_ICON_MENU_LEFT,
                                GTK_ICON_SIZE_MENU);
  gtk_container_add (GTK_CONTAINER (dockbook->p->menu_button), image);
  gtk_widget_show (image);

  gimp_help_set_help_data (dockbook->p->menu_button, _("Configure this tab"),
                           GIMP_HELP_DOCK_TAB_MENU);

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

static void
gimp_dockbook_finalize (GObject *object)
{
  GimpDockbook *dockbook = GIMP_DOCKBOOK (object);

  g_clear_object (&dockbook->p->ui_manager);

  G_OBJECT_CLASS (parent_class)->finalize (object);
}

static void
gimp_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)
    {
      GimpDockbookDragCallbackData *callback_data = iter->data;

      iter = g_list_next (iter);

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

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

  iter = drag_callbacks;

  while (iter)
    {
      GimpDockbookDragCallbackData *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
gimp_dockbook_drag_motion (GtkWidget      *widget,
                           GdkDragContext *context,
                           gint            x,
                           gint            y,
                           guint           time)
{
  GimpDockbook *dockbook = GIMP_DOCKBOOK (widget);

  if (gimp_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
gimp_dockbook_drag_drop (GtkWidget      *widget,
                         GdkDragContext *context,
                         gint            x,
                         gint            y,
                         guint           time)
{
  GimpDockbook *dockbook = GIMP_DOCKBOOK (widget);

  if (gimp_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
gimp_dockbook_popup_menu (GtkWidget *widget)
{
  return gimp_dockbook_show_menu (GIMP_DOCKBOOK (widget));
}

static GtkNotebook *
gimp_dockbook_create_window (GtkNotebook *notebook,
                             GtkWidget   *page,
                             gint         x,
                             gint         y)
{
  GimpDockbook      *dockbook = GIMP_DOCKBOOK (notebook);
  GimpDialogFactory *dialog_factory;
  GimpMenuFactory   *menu_factory;
  GimpDockWindow    *src_dock_window;
  GimpDock          *src_dock;
  GtkWidget         *new_dock;
  GimpDockWindow    *new_dock_window;
  GtkWidget         *new_dockbook;

  src_dock        = gimp_dockbook_get_dock (dockbook);
  src_dock_window = gimp_dock_window_from_dock (src_dock);

  dialog_factory = gimp_dock_get_dialog_factory (src_dock);
  menu_factory   = gimp_dialog_factory_get_menu_factory (dialog_factory);

  new_dock = gimp_dock_with_window_new (dialog_factory,
                                        gimp_widget_get_monitor (page),
                                        FALSE);

  new_dock_window = gimp_dock_window_from_dock (GIMP_DOCK (new_dock));
  gtk_window_set_position (GTK_WINDOW (new_dock_window), GTK_WIN_POS_MOUSE);
  if (src_dock_window)
    gimp_dock_window_setup (new_dock_window, src_dock_window);

  new_dockbook = gimp_dockbook_new (menu_factory);

  gimp_dock_add_book (GIMP_DOCK (new_dock), GIMP_DOCKBOOK (new_dockbook), 0);

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

  return GTK_NOTEBOOK (new_dockbook);
}

static void
gimp_dockbook_page_added (GtkNotebook *notebook,
                          GtkWidget   *child,
                          guint        page_num)
{
  GimpDockbook *dockbook = GIMP_DOCKBOOK (notebook);
  GimpDockable *dockable = GIMP_DOCKABLE (child);
  GtkWidget    *tab_widget;
  GtkWidget    *menu_widget;

  GIMP_LOG (DND, "GimpDockable %p added to GimpDockbook %p",
            dockable, dockbook);

  tab_widget = gimp_dockbook_create_tab_widget (dockbook, dockable);

  /* For the notebook right-click menu, always use the icon style */
  menu_widget =
    gimp_dockable_create_tab_widget (dockable,
                                     gimp_dock_get_context (dockbook->p->dock),
                                     GIMP_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 (! gimp_dockable_get_locked (dockable))
    {
      gtk_notebook_set_tab_reorderable (notebook, child, TRUE);
      gtk_notebook_set_tab_detachable (notebook, child, TRUE);
    }

  gimp_dockable_set_dockbook (dockable, dockbook);

  gimp_dockable_set_context (dockable,
                             gimp_dock_get_context (dockbook->p->dock));

  g_signal_connect (dockable, "notify::locked",
                    G_CALLBACK (gimp_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
gimp_dockbook_page_removed (GtkNotebook *notebook,
                            GtkWidget   *child,
                            guint        page_num)
{
  GimpDockbook *dockbook = GIMP_DOCKBOOK (notebook);
  GimpDockable *dockable = GIMP_DOCKABLE (child);

  GIMP_LOG (DND, "GimpDockable removed %p from GimpDockbook %p",
            dockable, dockbook);

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

  gimp_dockable_set_dockbook (dockable, NULL);

  gimp_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)
        gimp_dock_remove_book (dockbook->p->dock, dockbook);

      g_list_free (children);
    }
}

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

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

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

  return handled;
}

static gboolean
gimp_dockbook_show_menu (GimpDockbook *dockbook)
{
  GimpUIManager *dockbook_ui_manager;
  GimpUIManager *dialog_ui_manager;
  const gchar   *dialog_ui_path;
  gpointer       dialog_popup_data;
  GtkWidget     *parent_menu_widget;
  GimpAction    *parent_menu_action;
  GimpDockable  *dockable;
  gint           page_num;

  dockbook_ui_manager = gimp_dockbook_get_ui_manager (dockbook);

  if (! dockbook_ui_manager)
    return FALSE;

  parent_menu_widget =
    gimp_ui_manager_get_widget (dockbook_ui_manager,
                                "/dockable-popup/dockable-menu");
  parent_menu_action =
    gimp_ui_manager_get_action (dockbook_ui_manager,
                                "/dockable-popup/dockable-menu");

  if (! parent_menu_widget || ! parent_menu_action)
    return FALSE;

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

  if (! dockable)
    return FALSE;

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

  if (dialog_ui_manager && dialog_ui_path)
    {
      GtkWidget  *child_menu_widget;
      GimpAction *child_menu_action;
      gchar      *label;

      child_menu_widget =
        gimp_ui_manager_get_widget (dialog_ui_manager, dialog_ui_path);

      if (! child_menu_widget)
        {
          g_warning ("%s: UI manager '%s' has no widget at path '%s'",
                     G_STRFUNC, dialog_ui_manager->name, dialog_ui_path);
          return FALSE;
        }

      child_menu_action =
        gimp_ui_manager_get_action (dialog_ui_manager,
                                    dialog_ui_path);

      if (! child_menu_action)
        {
          g_warning ("%s: UI manager '%s' has no action at path '%s'",
                     G_STRFUNC, dialog_ui_manager->name, dialog_ui_path);
          return FALSE;
        }

      g_object_get (child_menu_action,
                    "label", &label,
                    NULL);

      g_object_set (parent_menu_action,
                    "label",     label,
                    "icon-name", gimp_dockable_get_icon_name (dockable),
                    "visible",   TRUE,
                    NULL);

      g_free (label);

      if (! GTK_IS_MENU (child_menu_widget))
        {
          g_warning ("%s: child_menu_widget (%p) is not a GtkMenu",
                     G_STRFUNC, child_menu_widget);
          return FALSE;
        }

      {
        GtkWidget *image = gimp_dockable_get_icon (dockable,
                                                   GTK_ICON_SIZE_MENU);
        gimp_menu_item_set_image (GTK_MENU_ITEM (parent_menu_widget), image);
        gtk_widget_show (image);
      }

      gtk_menu_item_set_submenu (GTK_MENU_ITEM (parent_menu_widget),
                                 child_menu_widget);

      gimp_ui_manager_update (dialog_ui_manager, dialog_popup_data);
    }
  else
    {
      g_object_set (parent_menu_action, "visible", FALSE, NULL);
    }

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

  gimp_ui_manager_update (dockbook_ui_manager, dockable);

  gimp_ui_manager_ui_popup_at_widget (dockbook_ui_manager,
                                      "/dockable-popup",
                                      dockbook->p->menu_button,
                                      GDK_GRAVITY_WEST,
                                      GDK_GRAVITY_NORTH_EAST,
                                      NULL,
                                      (GDestroyNotify) gimp_dockbook_menu_end,
                                      dockable);

  return TRUE;
}

static void
gimp_dockbook_menu_end (GimpDockable *dockable)
{
  GimpUIManager *dialog_ui_manager;
  const gchar   *dialog_ui_path;
  gpointer       dialog_popup_data;

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

  if (dialog_ui_manager && dialog_ui_path)
    {
      GtkWidget *child_menu_widget =
        gimp_ui_manager_get_widget (dialog_ui_manager, dialog_ui_path);

      if (child_menu_widget)
        gtk_menu_detach (GTK_MENU (child_menu_widget));
    }

  /*  release gimp_dockbook_show_menu()'s references  */
  g_object_set_data (G_OBJECT (dockable), GIMP_DOCKABLE_DETACH_REF_KEY, NULL);
  g_object_unref (dockable);
}


/*  public functions  */

GtkWidget *
gimp_dockbook_new (GimpMenuFactory *menu_factory)
{
  GimpDockbook *dockbook;

  g_return_val_if_fail (GIMP_IS_MENU_FACTORY (menu_factory), NULL);

  dockbook = g_object_new (GIMP_TYPE_DOCKBOOK, NULL);

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

  gimp_help_connect (GTK_WIDGET (dockbook), gimp_dockbook_help_func,
                     GIMP_HELP_DOCK, dockbook, NULL);

  return GTK_WIDGET (dockbook);
}

GimpDock *
gimp_dockbook_get_dock (GimpDockbook *dockbook)
{
  g_return_val_if_fail (GIMP_IS_DOCKBOOK (dockbook), NULL);

  return dockbook->p->dock;
}

void
gimp_dockbook_set_dock (GimpDockbook *dockbook,
                        GimpDock     *dock)
{
  g_return_if_fail (GIMP_IS_DOCKBOOK (dockbook));
  g_return_if_fail (dock == NULL || GIMP_IS_DOCK (dock));

  dockbook->p->dock = dock;
}

GimpUIManager *
gimp_dockbook_get_ui_manager (GimpDockbook *dockbook)
{
  g_return_val_if_fail (GIMP_IS_DOCKBOOK (dockbook), NULL);

  return dockbook->p->ui_manager;
}

/**
 * gimp_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 *
gimp_dockbook_add_from_dialog_factory (GimpDockbook *dockbook,
                                       const gchar  *identifiers)
{
  GtkWidget *dockable;
  GimpDock  *dock;
  gchar     *identifier;
  gchar     *p;

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

  identifier = g_strdup (identifiers);

  p = strchr (identifier, '|');

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

  dock     = gimp_dockbook_get_dock (dockbook);
  dockable = gimp_dialog_factory_dockable_new (gimp_dock_get_dialog_factory (dock),
                                               dock,
                                               identifier, -1);

  g_free (identifier);

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

  return dockable;
}

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

  for (iter = children; iter; iter = g_list_next (iter))
    {
      GimpDockable *dockable = GIMP_DOCKABLE (iter->data);

      gimp_dockable_set_context (dockable, context);
    }

  g_list_free (children);
}

GtkWidget *
gimp_dockbook_create_tab_widget (GimpDockbook *dockbook,
                                 GimpDockable *dockable)
{
  GtkWidget      *tab_widget;
  GimpDockWindow *dock_window;
  GimpAction     *action = NULL;
  GtkIconSize     tab_size = DEFAULT_TAB_ICON_SIZE;

  gtk_widget_style_get (GTK_WIDGET (dockbook),
                        "tab-icon-size", &tab_size,
                        NULL);
  tab_widget =
    gimp_dockable_create_tab_widget (dockable,
                                     gimp_dock_get_context (dockbook->p->dock),
                                     gimp_dockable_get_tab_style (dockable),
                                     tab_size);

  if (GIMP_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 = gimp_dock_window_from_dock (dockbook->p->dock);
  if (dock_window &&
      gimp_dock_container_get_ui_manager (GIMP_DOCK_CONTAINER (dock_window)))
    {
      const gchar *dialog_id;

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

      if (dialog_id)
        {
          GimpDockContainer *dock_container;
          GimpActionGroup   *group;

          dock_container = GIMP_DOCK_CONTAINER (dock_window);

          group = gimp_ui_manager_get_action_group
            (gimp_dock_container_get_ui_manager (dock_container), "dialogs");

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

              actions = gimp_action_group_list_actions (group);

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

              g_list_free (actions);
            }
        }
    }

  if (action)
    gimp_widget_set_accel_help (tab_widget, action);
  else
    gimp_help_set_help_data (tab_widget,
                             gimp_dockable_get_blurb (dockable),
                             gimp_dockable_get_help_id (dockable));

  return tab_widget;
}

/**
 * gimp_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
gimp_dockbook_set_drag_handler (GimpDockbook *dockbook,
                                GimpPanedBox *drag_handler)
{
  g_return_if_fail (GIMP_IS_DOCKBOOK (dockbook));

  dockbook->p->drag_handler = drag_handler;
}

void
gimp_dockbook_add_drag_callback (GimpDockbookDragCallback callback,
                                 gpointer                 data)
{
  GimpDockbookDragCallbackData *callback_data;

  callback_data = g_slice_new (GimpDockbookDragCallbackData);

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

  drag_callbacks = g_list_prepend (drag_callbacks, callback_data);
}

void
gimp_dockbook_remove_drag_callback (GimpDockbookDragCallback callback,
                                    gpointer                 data)
{
  GList *iter;

  iter = drag_callbacks;

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

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

          drag_callbacks = g_list_delete_link (drag_callbacks, iter);
        }

      iter = next;
    }
}


/*  private functions  */

static void
gimp_dockbook_tab_locked_notify (GimpDockable *dockable,
                                 GParamSpec   *pspec,
                                 GimpDockbook *dockbook)
{
  gboolean locked = gimp_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
gimp_dockbook_help_func (const gchar *help_id,
                         gpointer     help_data)
{
  GimpDockbook *dockbook = GIMP_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 (GIMP_IS_DOCKABLE (dockable))
    gimp_standard_help_func (gimp_dockable_get_help_id (GIMP_DOCKABLE (dockable)),
                             NULL);
  else
    gimp_standard_help_func (GIMP_HELP_DOCK, NULL);
}
