/*
 *   moopaned.c
 *
 *   Copyright (C) 2004-2010 by Yevgen Muntyan <emuntyan@users.sourceforge.net>
 *
 *   This file is part of medit.  medit 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.
 *
 *   You should have received a copy of the GNU Lesser General Public
 *   License along with medit.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * class:MooPaned: (parent GtkBin) (constructable) (moo.lua 0) (moo.private 1)
 **/

/**
 * boxed:MooPaneLabel: (moo.lua 0) (moo.private 1)
 **/

/**
 * boxed:MooPaneParams: (moo.lua 0) (moo.private 1)
 **/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "marshals.h"
#include "moopaned.h"

#include <string.h>
#include <gdk/gdkkeysyms.h>
#include <gtk/gtk.h>

# include "mooutils-misc.h"
# include "moocompat.h"


#define MIN_PANE_SIZE 10


typedef enum {
    FOCUS_NONE = 0,
    FOCUS_CHILD,
    FOCUS_PANE,
    FOCUS_BUTTON
} FocusPosition;

struct _MooPanedPrivate {
    MooPanePosition pane_position;

    GdkWindow   *bin_window;
    GdkWindow   *handle_window;
    GdkWindow   *pane_window;

    /* XXX weak pointer */
    gpointer     focus_child; /* focused grandchild of bin->child */
    MooPane     *focus_pane;
    FocusPosition focus;
    gboolean     button_real_focus; /* button was focused by keyboard navigation */
    gboolean     dont_move_focus;   /* do not try to move focus in open_pane/hide_pane */

    MooPane     *current_pane;
    GSList      *panes;

    gboolean     close_on_child_focus;

    int          position;

    gboolean     button_box_visible;
    int          button_box_size;
    gboolean     handle_visible;
    int          handle_size;
    gboolean     pane_widget_visible;
    int          pane_widget_size;
    gboolean     enable_border;
    int          border_size;
    gboolean     sticky;

    gboolean     handle_prelit;
    gboolean     in_drag;
    int          drag_start;

    gboolean     enable_handle_drag;
    gboolean     handle_in_drag;
    gboolean     handle_button_pressed;
    int          handle_drag_start_x;
    int          handle_drag_start_y;
    GdkCursorType handle_cursor_type;

    guint        enable_detaching : 1;

    guint        forall_bottom_to_top : 1;
};


static void     moo_paned_set_property      (GObject        *object,
                                             guint           prop_id,
                                             const GValue   *value,
                                             GParamSpec     *pspec);
static void     moo_paned_get_property      (GObject        *object,
                                             guint           prop_id,
                                             GValue         *value,
                                             GParamSpec     *pspec);
static GObject *moo_paned_constructor       (GType                  type,
                                             guint                  n_construct_properties,
                                             GObjectConstructParam *construct_properties);

static void     moo_paned_destroy           (GtkObject      *object);

static void     moo_paned_realize           (GtkWidget      *widget);
static void     moo_paned_unrealize         (GtkWidget      *widget);
static void     moo_paned_map               (GtkWidget      *widget);
static void     moo_paned_unmap             (GtkWidget      *widget);
static gboolean moo_paned_focus             (GtkWidget      *widget,
                                             GtkDirectionType direction);
static void     moo_paned_style_set         (GtkWidget      *widget,
                                             GtkStyle       *old_style);

static void     moo_paned_set_focus_child   (GtkContainer *container,
                                             GtkWidget      *widget);

static void     moo_paned_size_request      (GtkWidget      *widget,
                                             GtkRequisition *requisition);
static void     moo_paned_size_allocate     (GtkWidget      *widget,
                                             GtkAllocation  *allocation);

static gboolean moo_paned_expose            (GtkWidget      *widget,
                                             GdkEventExpose *event);
static gboolean moo_paned_motion            (GtkWidget      *widget,
                                             GdkEventMotion *event);
static gboolean moo_paned_enter             (GtkWidget      *widget,
                                             GdkEventCrossing *event);
static gboolean moo_paned_leave             (GtkWidget      *widget,
                                             GdkEventCrossing *event);
static gboolean moo_paned_button_press      (GtkWidget      *widget,
                                             GdkEventButton *event);
static gboolean moo_paned_button_release    (GtkWidget      *widget,
                                             GdkEventButton *event);
static gboolean moo_paned_key_press         (GtkWidget      *widget,
                                             GdkEventKey    *event);

static int      pane_index                  (MooPaned       *paned,
                                             MooPane        *pane);
static MooPane *get_nth_pane                (MooPaned       *paned,
                                             guint           index_);

static void     moo_paned_open_pane_real    (MooPaned       *paned,
                                             guint           index);
static void     moo_paned_hide_pane_real    (MooPaned       *paned);
static void     moo_paned_set_pane_size_real(MooPaned       *paned,
                                             int             size);

static void     moo_paned_forall            (GtkContainer   *container,
                                             gboolean        include_internals,
                                             GtkCallback     callback,
                                             gpointer        callback_data);
static void     moo_paned_add               (GtkContainer   *container,
                                             GtkWidget      *widget);
static void     moo_paned_remove            (GtkContainer   *container,
                                             GtkWidget      *widget);

static void     realize_handle              (MooPaned       *paned);
static void     realize_pane                (MooPaned       *paned);
static void     draw_handle                 (MooPaned       *paned,
                                             GdkEventExpose *event);
static void     draw_border                 (MooPaned       *paned,
                                             GdkEventExpose *event);
static void     button_box_visible_notify   (MooPaned     *paned);

static void     pane_button_toggled         (GtkToggleButton *button,
                                             MooPaned       *paned);

static gboolean handle_button_press         (GtkWidget      *widget,
                                             GdkEventButton *event,
                                             MooPaned       *paned);
static gboolean handle_button_release       (GtkWidget      *widget,
                                             GdkEventButton *event,
                                             MooPaned       *paned);
static gboolean handle_motion               (GtkWidget      *widget,
                                             GdkEventMotion *event,
                                             MooPaned       *paned);
static gboolean handle_expose               (GtkWidget      *widget,
                                             GdkEventExpose *event,
                                             MooPaned       *paned);
static void     handle_realize              (GtkWidget      *widget,
                                             MooPaned       *paned);

static void     moo_paned_set_handle_cursor_type
                                            (MooPaned       *paned,
                                             GdkCursorType   cursor_type,
                                             gboolean        really_set);
static void     moo_paned_set_enable_detaching
                                            (MooPaned       *paned,
                                             gboolean        enable);

G_DEFINE_TYPE (MooPaned, moo_paned, GTK_TYPE_BIN)

enum {
    PANED_PROP_0,
    PANED_PROP_ACTIVE_PANE,
    PANED_PROP_PANE_POSITION,
    PANED_PROP_CLOSE_PANE_ON_CHILD_FOCUS,
    PANED_PROP_STICKY_PANE,
    PANED_PROP_ENABLE_HANDLE_DRAG,
    PANED_PROP_HANDLE_CURSOR_TYPE,
    PANED_PROP_ENABLE_DETACHING,
    PANED_PROP_ENABLE_BORDER
};

enum {
    PANED_SET_PANE_SIZE,
    PANED_HANDLE_DRAG_START,
    PANED_HANDLE_DRAG_MOTION,
    PANED_HANDLE_DRAG_END,
    PANED_PANE_PARAMS_CHANGED,
    PANED_NUM_SIGNALS
};

static guint paned_signals[PANED_NUM_SIGNALS];

static void
moo_paned_class_init (MooPanedClass *klass)
{
    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
    GtkObjectClass *gtkobject_class = GTK_OBJECT_CLASS (klass);
    GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
    GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);

    g_type_class_add_private (klass, sizeof (MooPanedPrivate));

    gobject_class->set_property = moo_paned_set_property;
    gobject_class->get_property = moo_paned_get_property;
    gobject_class->constructor = moo_paned_constructor;

    gtkobject_class->destroy = moo_paned_destroy;

    widget_class->realize = moo_paned_realize;
    widget_class->unrealize = moo_paned_unrealize;
    widget_class->style_set = moo_paned_style_set;
    widget_class->map = moo_paned_map;
    widget_class->unmap = moo_paned_unmap;
    widget_class->expose_event = moo_paned_expose;
    widget_class->size_request = moo_paned_size_request;
    widget_class->size_allocate = moo_paned_size_allocate;
    widget_class->motion_notify_event = moo_paned_motion;
    widget_class->enter_notify_event = moo_paned_enter;
    widget_class->leave_notify_event = moo_paned_leave;
    widget_class->button_press_event = moo_paned_button_press;
    widget_class->button_release_event = moo_paned_button_release;
    widget_class->focus = moo_paned_focus;
    widget_class->key_press_event = moo_paned_key_press;

    container_class->forall = moo_paned_forall;
    container_class->set_focus_child = moo_paned_set_focus_child;
    container_class->remove = moo_paned_remove;
    container_class->add = moo_paned_add;

    klass->set_pane_size = moo_paned_set_pane_size_real;

    g_object_class_install_property (gobject_class, PANED_PROP_ACTIVE_PANE,
        g_param_spec_object ("active-pane", "active-pane", "active-pane",
                             MOO_TYPE_PANE, (GParamFlags) G_PARAM_READWRITE));

    g_object_class_install_property (gobject_class, PANED_PROP_PANE_POSITION,
        g_param_spec_enum ("pane-position", "pane-position", "pane-position",
                           MOO_TYPE_PANE_POSITION, MOO_PANE_POS_LEFT,
                           (GParamFlags) (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)));

    g_object_class_install_property (gobject_class, PANED_PROP_CLOSE_PANE_ON_CHILD_FOCUS,
        g_param_spec_boolean ("close-pane-on-child-focus", "close-pane-on-child-focus",
                              "close-pane-on-child-focus",
                              TRUE, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_CONSTRUCT)));

    g_object_class_install_property (gobject_class, PANED_PROP_STICKY_PANE,
        g_param_spec_boolean ("sticky-pane", "sticky-pane", "sticky-pane",
                              FALSE, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_CONSTRUCT)));

    g_object_class_install_property (gobject_class, PANED_PROP_ENABLE_HANDLE_DRAG,
        g_param_spec_boolean ("enable-handle-drag", "enable-handle-drag", "enable-handle-drag",
                              FALSE, (GParamFlags) G_PARAM_READWRITE));

    g_object_class_install_property (gobject_class, PANED_PROP_ENABLE_DETACHING,
        g_param_spec_boolean ("enable-detaching", "enable-detaching", "enable-detaching",
                              FALSE, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_CONSTRUCT)));

    g_object_class_install_property (gobject_class, PANED_PROP_HANDLE_CURSOR_TYPE,
        g_param_spec_enum ("handle-cursor-type", "handle-cursor-type", "handle-cursor-type",
                           GDK_TYPE_CURSOR_TYPE, GDK_HAND2, (GParamFlags) (G_PARAM_CONSTRUCT | G_PARAM_READWRITE)));

    g_object_class_install_property (gobject_class, PANED_PROP_ENABLE_BORDER,
        g_param_spec_boolean ("enable-border", "enable-border", "enable-border",
                              TRUE, (GParamFlags) (G_PARAM_CONSTRUCT | G_PARAM_READWRITE)));

    gtk_widget_class_install_style_property (widget_class,
        g_param_spec_int ("handle-size", "handle-size", "handle-size",
                          0, G_MAXINT, 5, G_PARAM_READABLE));

    gtk_widget_class_install_style_property (widget_class,
        g_param_spec_int ("button-spacing", "button-spacing", "button-spacing",
                          0, G_MAXINT, 0, G_PARAM_READABLE));

    paned_signals[PANED_SET_PANE_SIZE] =
            g_signal_new ("set-pane-size",
                          G_OBJECT_CLASS_TYPE (klass),
                          G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
                          G_STRUCT_OFFSET (MooPanedClass, set_pane_size),
                          NULL, NULL,
                          _moo_marshal_VOID__INT,
                          G_TYPE_NONE, 1,
                          G_TYPE_INT);

    paned_signals[PANED_HANDLE_DRAG_START] =
            g_signal_new ("handle-drag-start",
                          G_OBJECT_CLASS_TYPE (klass),
                          G_SIGNAL_RUN_FIRST,
                          G_STRUCT_OFFSET (MooPanedClass, handle_drag_start),
                          NULL, NULL,
                          _moo_marshal_VOID__OBJECT,
                          G_TYPE_NONE, 1,
                          GTK_TYPE_WIDGET);

    paned_signals[PANED_HANDLE_DRAG_MOTION] =
            g_signal_new ("handle-drag-motion",
                          G_OBJECT_CLASS_TYPE (klass),
                          G_SIGNAL_RUN_FIRST,
                          G_STRUCT_OFFSET (MooPanedClass, handle_drag_motion),
                          NULL, NULL,
                          _moo_marshal_VOID__OBJECT,
                          G_TYPE_NONE, 1,
                          GTK_TYPE_WIDGET);

    paned_signals[PANED_HANDLE_DRAG_END] =
            g_signal_new ("handle-drag-end",
                          G_OBJECT_CLASS_TYPE (klass),
                          G_SIGNAL_RUN_FIRST,
                          G_STRUCT_OFFSET (MooPanedClass, handle_drag_end),
                          NULL, NULL,
                          _moo_marshal_VOID__OBJECT_BOOL,
                          G_TYPE_NONE, 2,
                          GTK_TYPE_WIDGET,
                          G_TYPE_BOOLEAN);

    paned_signals[PANED_PANE_PARAMS_CHANGED] =
            g_signal_new ("pane-params-changed",
                          G_OBJECT_CLASS_TYPE (klass),
                          G_SIGNAL_RUN_LAST,
                          G_STRUCT_OFFSET (MooPanedClass, pane_params_changed),
                          NULL, NULL,
                          _moo_marshal_VOID__UINT,
                          G_TYPE_NONE, 1,
                          G_TYPE_UINT);
}


static void
moo_paned_init (MooPaned *paned)
{
    GTK_WIDGET_SET_NO_WINDOW (paned);

    paned->priv = G_TYPE_INSTANCE_GET_PRIVATE (paned,
                                               MOO_TYPE_PANED,
                                               MooPanedPrivate);

    paned->button_box = NULL;

    /* TRUE means default and broken tooltips,
     * FALSE mean inverted order and working tooltips
     * http://bugzilla.gnome.org/show_bug.cgi?id=550345 */
    paned->priv->forall_bottom_to_top = TRUE;

    paned->priv->pane_position = -1;
    paned->priv->handle_window = NULL;
    paned->priv->pane_window = NULL;
    paned->priv->bin_window = NULL;
    paned->priv->current_pane = NULL;
    paned->priv->panes = NULL;
    paned->priv->enable_border = TRUE;
    paned->priv->button_box_visible = FALSE;
    paned->priv->button_box_size = 0;
    paned->priv->handle_visible = FALSE;
    paned->priv->handle_size = 0;
    paned->priv->pane_widget_visible = FALSE;
    paned->priv->pane_widget_size = 0;
    paned->priv->sticky = FALSE;
    paned->priv->position = 200;
    paned->priv->handle_prelit = FALSE;
    paned->priv->in_drag = FALSE;
    paned->priv->drag_start = -1;
}


static GObject*
moo_paned_constructor (GType                  type,
                       guint                  n_construct_properties,
                       GObjectConstructParam *construct_properties)
{
    GObject *object;
    MooPaned *paned;
    int button_spacing;

    object = G_OBJECT_CLASS(moo_paned_parent_class)->constructor (type,
                                n_construct_properties, construct_properties);
    paned = MOO_PANED (object);

    gtk_widget_style_get (GTK_WIDGET (paned),
                          "button-spacing", &button_spacing, NULL);

    switch (paned->priv->pane_position)
    {
        case MOO_PANE_POS_LEFT:
        case MOO_PANE_POS_RIGHT:
            paned->button_box = gtk_vbox_new (FALSE, button_spacing);
            break;
        case MOO_PANE_POS_TOP:
        case MOO_PANE_POS_BOTTOM:
            paned->button_box = gtk_hbox_new (FALSE, button_spacing);
            break;
        default:
            g_warning ("invalid 'pane-position' property value '%u',"
                       "falling back to MOO_PANE_POS_LEFT",
                       paned->priv->pane_position);
            paned->priv->pane_position = MOO_PANE_POS_LEFT;
            paned->button_box = gtk_vbox_new (FALSE, button_spacing);
            break;
    }

    g_object_ref_sink (paned->button_box);
    gtk_widget_set_parent_window (paned->button_box, paned->priv->bin_window);
    gtk_widget_set_parent (paned->button_box, GTK_WIDGET (paned));
    gtk_widget_show (paned->button_box);
    g_signal_connect_swapped (paned->button_box, "notify::visible",
                              G_CALLBACK (button_box_visible_notify),
                              paned);

    return object;
}


static void
moo_paned_set_property (GObject        *object,
                        guint           prop_id,
                        const GValue   *value,
                        GParamSpec     *pspec)
{
    MooPaned *paned = MOO_PANED (object);

    switch (prop_id)
    {
        case PANED_PROP_ACTIVE_PANE:
            moo_paned_open_pane (paned, g_value_get_object (value));
            break;

        case PANED_PROP_PANE_POSITION:
            paned->priv->pane_position = g_value_get_enum (value);
            break;

        case PANED_PROP_CLOSE_PANE_ON_CHILD_FOCUS:
            paned->priv->close_on_child_focus =
                    g_value_get_boolean (value);
            g_object_notify (object, "close-pane-on-child-focus");
            break;

        case PANED_PROP_ENABLE_BORDER:
            paned->priv->enable_border = g_value_get_boolean (value);
            gtk_widget_queue_resize (GTK_WIDGET (paned));
            g_object_notify (object, "enable_border");
            break;

        case PANED_PROP_STICKY_PANE:
            moo_paned_set_sticky_pane (paned,
                                       g_value_get_boolean (value));
            break;

        case PANED_PROP_ENABLE_HANDLE_DRAG:
            paned->priv->enable_handle_drag = g_value_get_boolean (value);
            if (!paned->priv->enable_handle_drag)
                moo_paned_set_handle_cursor_type (paned, 0, FALSE);
            else
                moo_paned_set_handle_cursor_type (paned, paned->priv->handle_cursor_type, TRUE);
            g_object_notify (object, "enable-handle-drag");
            break;

        case PANED_PROP_ENABLE_DETACHING:
            moo_paned_set_enable_detaching (paned, g_value_get_boolean (value));
            break;

        case PANED_PROP_HANDLE_CURSOR_TYPE:
            if (paned->priv->enable_handle_drag)
                moo_paned_set_handle_cursor_type (paned, g_value_get_enum (value), TRUE);
            else
                paned->priv->handle_cursor_type = g_value_get_enum (value);
            break;

        default:
            G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}


static void
moo_paned_get_property (GObject        *object,
                        guint           prop_id,
                        GValue         *value,
                        GParamSpec     *pspec)
{
    MooPaned *paned = MOO_PANED (object);

    switch (prop_id)
    {
        case PANED_PROP_ACTIVE_PANE:
            g_value_set_object (value, paned->priv->current_pane);
            break;

        case PANED_PROP_PANE_POSITION:
            g_value_set_enum (value, paned->priv->pane_position);
            break;

        case PANED_PROP_CLOSE_PANE_ON_CHILD_FOCUS:
            g_value_set_boolean (value, paned->priv->close_on_child_focus);
            break;

        case PANED_PROP_STICKY_PANE:
            g_value_set_boolean (value, paned->priv->sticky);
            break;

        case PANED_PROP_ENABLE_HANDLE_DRAG:
            g_value_set_boolean (value, paned->priv->enable_handle_drag);
            break;

        case PANED_PROP_ENABLE_BORDER:
            g_value_set_boolean (value, paned->priv->enable_border);
            break;

        case PANED_PROP_ENABLE_DETACHING:
            g_value_set_boolean (value, paned->priv->enable_detaching ? TRUE : FALSE);
            break;

        case PANED_PROP_HANDLE_CURSOR_TYPE:
            g_value_set_enum (value, paned->priv->handle_cursor_type);
            break;

        default:
            G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}


static void
moo_paned_destroy (GtkObject      *object)
{
    GSList *l;
    MooPaned *paned = MOO_PANED (object);

    for (l = paned->priv->panes; l != NULL; l = l->next)
        gtk_object_destroy (l->data);

    GTK_OBJECT_CLASS(moo_paned_parent_class)->destroy (object);

    for (l = paned->priv->panes; l != NULL; l = l->next)
        g_object_unref (l->data);

    g_slist_free (paned->priv->panes);
    paned->priv->panes = NULL;
}


GtkWidget*
moo_paned_new (MooPanePosition pane_position)
{
    return GTK_WIDGET (g_object_new (MOO_TYPE_PANED,
                       "pane-position", pane_position,
                       (const char*) NULL));
}


MooPanePosition
_moo_paned_get_position (MooPaned *paned)
{
    g_return_val_if_fail (MOO_IS_PANED (paned), 0);
    return paned->priv->pane_position;
}


static void
moo_paned_style_set (GtkWidget *widget,
                     G_GNUC_UNUSED GtkStyle *old_style)
{
    MooPaned *paned = MOO_PANED (widget);

    if (widget->style)
    {
        if (paned->priv->bin_window)
            gtk_style_set_background (widget->style,
                                      paned->priv->bin_window,
                                      GTK_STATE_NORMAL);
        if (paned->priv->handle_window)
            gtk_style_set_background (widget->style,
                                      paned->priv->handle_window,
                              GTK_STATE_NORMAL);
        if (paned->priv->pane_window)
            gtk_style_set_background (widget->style,
                                      paned->priv->pane_window,
                                      GTK_STATE_NORMAL);
    }
}

static void
moo_paned_realize (GtkWidget *widget)
{
    static GdkWindowAttr attributes;
    gint attributes_mask;
    MooPaned *paned;

    paned = MOO_PANED (widget);

    widget->window = gtk_widget_get_parent_window (widget);
    g_object_ref (widget->window);

    attributes.x = widget->allocation.x;
    attributes.y = widget->allocation.y;
    attributes.width = widget->allocation.width;
    attributes.height = widget->allocation.height;
    attributes.window_type = GDK_WINDOW_CHILD;
    attributes.event_mask = gtk_widget_get_events (widget)
            | GDK_EXPOSURE_MASK;

    attributes.visual = gtk_widget_get_visual (widget);
    attributes.colormap = gtk_widget_get_colormap (widget);
    attributes.wclass = GDK_INPUT_OUTPUT;

    attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;

    paned->priv->bin_window = gdk_window_new (gtk_widget_get_parent_window (widget),
                                              &attributes, attributes_mask);
    gdk_window_set_user_data (paned->priv->bin_window, widget);

    widget->style = gtk_style_attach (widget->style, widget->window);
    gtk_style_set_background (widget->style, paned->priv->bin_window, GTK_STATE_NORMAL);

    GTK_WIDGET_SET_REALIZED (widget);

    realize_pane (paned);

    if (paned->button_box)
        gtk_widget_set_parent_window (paned->button_box, paned->priv->bin_window);
    if (GTK_BIN(paned)->child)
        gtk_widget_set_parent_window (GTK_BIN(paned)->child, paned->priv->bin_window);
}


static void
realize_handle (MooPaned *paned)
{
    static GdkWindowAttr attributes;
    gint attributes_mask;
    GtkWidget *widget = GTK_WIDGET (paned);

    switch (paned->priv->pane_position)
    {
        case MOO_PANE_POS_LEFT:
        case MOO_PANE_POS_RIGHT:
            attributes.y = 0;
            attributes.width = paned->priv->handle_size;
            attributes.height = widget->allocation.height;
            break;
        case MOO_PANE_POS_TOP:
        case MOO_PANE_POS_BOTTOM:
            attributes.x = 0;
            attributes.width = widget->allocation.width;
            attributes.height = paned->priv->handle_size;
            break;
    }

    switch (paned->priv->pane_position)
    {
        case MOO_PANE_POS_LEFT:
            attributes.x = paned->priv->pane_widget_size;
            break;
        case MOO_PANE_POS_RIGHT:
            attributes.x = 0;
            break;
        case MOO_PANE_POS_TOP:
            attributes.y = paned->priv->pane_widget_size;
            break;
        case MOO_PANE_POS_BOTTOM:
            attributes.y = 0;
            break;
    }

    attributes.window_type = GDK_WINDOW_CHILD;
    attributes.event_mask = gtk_widget_get_events (widget)
            | GDK_POINTER_MOTION_HINT_MASK
            | GDK_POINTER_MOTION_MASK
            | GDK_BUTTON_PRESS_MASK
            | GDK_BUTTON_RELEASE_MASK
            | GDK_EXPOSURE_MASK
            | GDK_ENTER_NOTIFY_MASK
            | GDK_LEAVE_NOTIFY_MASK;

    attributes.visual = gtk_widget_get_visual (widget);
    attributes.colormap = gtk_widget_get_colormap (widget);
    attributes.wclass = GDK_INPUT_OUTPUT;

    switch (paned->priv->pane_position)
    {
        case MOO_PANE_POS_LEFT:
        case MOO_PANE_POS_RIGHT:
            attributes.cursor = gdk_cursor_new (GDK_SB_H_DOUBLE_ARROW);
            break;
        case MOO_PANE_POS_TOP:
        case MOO_PANE_POS_BOTTOM:
            attributes.cursor = gdk_cursor_new (GDK_SB_V_DOUBLE_ARROW);
            break;
    }

    attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL |
            GDK_WA_COLORMAP | GDK_WA_CURSOR;

    paned->priv->handle_window = gdk_window_new (paned->priv->pane_window,
            &attributes, attributes_mask);
    gdk_window_set_user_data (paned->priv->handle_window, widget);

    gtk_style_set_background (widget->style,
                              paned->priv->handle_window,
                              GTK_STATE_NORMAL);

    gdk_cursor_unref (attributes.cursor);
}


static void
get_pane_window_rect (MooPaned     *paned,
                      GdkRectangle *rect)
{
    *rect = GTK_WIDGET(paned)->allocation;

    switch (paned->priv->pane_position)
    {
        case MOO_PANE_POS_LEFT:
        case MOO_PANE_POS_RIGHT:
            rect->width = paned->priv->pane_widget_size + paned->priv->handle_size;
            break;
        case MOO_PANE_POS_TOP:
        case MOO_PANE_POS_BOTTOM:
            rect->height = paned->priv->pane_widget_size + paned->priv->handle_size;
            break;
    }

    switch (paned->priv->pane_position)
    {
        case MOO_PANE_POS_LEFT:
            rect->x += paned->priv->button_box_size;
            break;
        case MOO_PANE_POS_RIGHT:
            rect->x += GTK_WIDGET(paned)->allocation.width - rect->width -
                            paned->priv->button_box_size;
            break;
        case MOO_PANE_POS_TOP:
            rect->y += paned->priv->button_box_size;
            break;
        case MOO_PANE_POS_BOTTOM:
            rect->y += GTK_WIDGET(paned)->allocation.height - rect->height -
                            paned->priv->button_box_size;
            break;
    }
}


static void
realize_pane (MooPaned *paned)
{
    static GdkWindowAttr attributes;
    gint attributes_mask;
    GtkWidget *widget = GTK_WIDGET (paned);
    GdkRectangle rect;

    get_pane_window_rect (paned, &rect);
    attributes.x = rect.x;
    attributes.y = rect.y;
    attributes.width = rect.width;
    attributes.height = rect.height;

    attributes.window_type = GDK_WINDOW_CHILD;
    attributes.event_mask = gtk_widget_get_events (widget)
            | GDK_EXPOSURE_MASK;

    attributes.visual = gtk_widget_get_visual (widget);
    attributes.colormap = gtk_widget_get_colormap (widget);
    attributes.wclass = GDK_INPUT_OUTPUT;

    attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL |
            GDK_WA_COLORMAP;

    paned->priv->pane_window =
            gdk_window_new (widget->window, &attributes, attributes_mask);
    gdk_window_set_user_data (paned->priv->pane_window, widget);

    gtk_style_set_background (widget->style,
                              paned->priv->pane_window,
                              GTK_STATE_NORMAL);

    realize_handle (paned);

    if (paned->priv->current_pane)
        _moo_pane_set_parent (paned->priv->current_pane, paned,
                              paned->priv->pane_window);
}


static void
moo_paned_unrealize (GtkWidget *widget)
{
    MooPaned *paned = MOO_PANED (widget);

    if (paned->priv->handle_window)
    {
        gdk_window_set_user_data (paned->priv->handle_window, NULL);
        gdk_window_destroy (paned->priv->handle_window);
        paned->priv->handle_window = NULL;
        paned->priv->handle_visible = FALSE;
        paned->priv->handle_size = 0;
    }

    if (paned->priv->pane_window)
    {
        gdk_window_set_user_data (paned->priv->pane_window, NULL);
        gdk_window_destroy (paned->priv->pane_window);
        paned->priv->pane_window = NULL;
        paned->priv->pane_widget_visible = FALSE;
        paned->priv->pane_widget_size = 0;
    }

    if (paned->priv->bin_window)
    {
        gdk_window_set_user_data (paned->priv->bin_window, NULL);
        gdk_window_destroy (paned->priv->bin_window);
        paned->priv->bin_window = NULL;
    }

    GTK_WIDGET_CLASS(moo_paned_parent_class)->unrealize (widget);
}


static void
add_button_box_requisition (MooPaned *paned,
                            GtkRequisition *requisition,
                            GtkRequisition *child_requisition)
{
    switch (paned->priv->pane_position)
    {
        case MOO_PANE_POS_LEFT:
        case MOO_PANE_POS_RIGHT:
            requisition->width += child_requisition->width;
            requisition->height = MAX (child_requisition->height, requisition->height);
            paned->priv->button_box_size = child_requisition->width;
            break;
        case MOO_PANE_POS_TOP:
        case MOO_PANE_POS_BOTTOM:
            requisition->height += child_requisition->height;
            requisition->width = MAX (child_requisition->width, requisition->width);
            paned->priv->button_box_size = child_requisition->height;
            break;
    }
}


static void
add_handle_requisition (MooPaned       *paned,
                        GtkRequisition *requisition)
{
    gtk_widget_style_get (GTK_WIDGET (paned),
                          "handle_size", &paned->priv->handle_size,
                          NULL);

    switch (paned->priv->pane_position)
    {
        case MOO_PANE_POS_LEFT:
        case MOO_PANE_POS_RIGHT:
            requisition->width += paned->priv->handle_size;
            break;
        case MOO_PANE_POS_TOP:
        case MOO_PANE_POS_BOTTOM:
            requisition->height += paned->priv->handle_size;
            break;
    }
}


static void
add_pane_widget_requisition (MooPaned       *paned,
                             GtkRequisition *requisition,
                             GtkRequisition *child_requisition)
{
    switch (paned->priv->pane_position)
    {
        case MOO_PANE_POS_LEFT:
        case MOO_PANE_POS_RIGHT:
            requisition->height = MAX (child_requisition->height, requisition->height);

            if (paned->priv->sticky)
            {
                requisition->width += child_requisition->width;
            }
            else
            {
                requisition->width = MAX (child_requisition->width +
                        paned->priv->button_box_size +
                        paned->priv->handle_size, requisition->width);
            }

            break;

        case MOO_PANE_POS_TOP:
        case MOO_PANE_POS_BOTTOM:
            requisition->width = MAX (child_requisition->width, requisition->width);

            if (paned->priv->sticky)
            {
                requisition->height += child_requisition->height;
            }
            else
            {
                requisition->height = MAX (child_requisition->height +
                        paned->priv->button_box_size +
                        paned->priv->handle_size, requisition->height);
            }

            break;
    }

    switch (paned->priv->pane_position)
    {
        case MOO_PANE_POS_LEFT:
        case MOO_PANE_POS_RIGHT:
            paned->priv->pane_widget_size = MAX (paned->priv->position,
                    child_requisition->width);
            paned->priv->position = paned->priv->pane_widget_size;
            break;
        case MOO_PANE_POS_TOP:
        case MOO_PANE_POS_BOTTOM:
            paned->priv->pane_widget_size = MAX (paned->priv->position,
                    child_requisition->height);
            paned->priv->position = paned->priv->pane_widget_size;
            break;
    }
}


static void
moo_paned_size_request (GtkWidget      *widget,
                        GtkRequisition *requisition)
{
    GtkBin *bin = GTK_BIN (widget);
    MooPaned *paned = MOO_PANED (widget);
    GtkRequisition child_requisition;

    requisition->width = 0;
    requisition->height = 0;

    if (bin->child && GTK_WIDGET_VISIBLE (bin->child))
    {
        gtk_widget_size_request (bin->child, &child_requisition);
        requisition->width += child_requisition.width;
        requisition->height += child_requisition.height;
    }

    if (paned->priv->button_box_visible)
    {
        gtk_widget_size_request (paned->button_box, &child_requisition);
        add_button_box_requisition (paned, requisition, &child_requisition);
    }
    else
    {
        paned->priv->button_box_size = 0;
    }

    if (paned->priv->handle_visible)
        add_handle_requisition (paned, requisition);
    else
        paned->priv->handle_size = 0;

    if (paned->priv->pane_widget_visible)
    {
        _moo_pane_size_request (paned->priv->current_pane, &child_requisition);
        add_pane_widget_requisition (paned, requisition, &child_requisition);
    }
    else
    {
        paned->priv->pane_widget_size = 0;
    }

    if (paned->priv->enable_border)
    {
        switch (paned->priv->pane_position)
        {
            case MOO_PANE_POS_LEFT:
            case MOO_PANE_POS_RIGHT:
                paned->priv->border_size = widget->style->xthickness;
                requisition->width += paned->priv->border_size;
                break;
            case MOO_PANE_POS_TOP:
            case MOO_PANE_POS_BOTTOM:
                paned->priv->border_size = widget->style->ythickness;
                requisition->height += paned->priv->border_size;
                break;
        }
    }
    else
    {
        paned->priv->border_size = 0;
    }
}


static void
get_pane_widget_allocation (MooPaned        *paned,
                            GtkAllocation   *allocation)
{
    allocation->x = 0;
    allocation->y = 0;
    allocation->width = GTK_WIDGET(paned)->allocation.width;
    allocation->height = GTK_WIDGET(paned)->allocation.height;

    switch (paned->priv->pane_position)
    {
        case MOO_PANE_POS_RIGHT:
            allocation->x += paned->priv->handle_size;
            /* fall through */
        case MOO_PANE_POS_LEFT:
            allocation->width = paned->priv->pane_widget_size;
            break;

        case MOO_PANE_POS_BOTTOM:
            allocation->y += paned->priv->handle_size;
            /* fall through */
        case MOO_PANE_POS_TOP:
            allocation->height = paned->priv->pane_widget_size;
            break;
    }
}


static void
get_button_box_allocation (MooPaned        *paned,
                           GtkAllocation   *allocation)
{
    switch (paned->priv->pane_position)
    {
        case MOO_PANE_POS_LEFT:
        case MOO_PANE_POS_RIGHT:
            allocation->y = 0;
            allocation->height = GTK_WIDGET(paned)->allocation.height;
            allocation->width = paned->priv->button_box_size;
            break;
        case MOO_PANE_POS_TOP:
        case MOO_PANE_POS_BOTTOM:
            allocation->x = 0;
            allocation->width = GTK_WIDGET(paned)->allocation.width;
            allocation->height = paned->priv->button_box_size;
            break;
    }

    switch (paned->priv->pane_position)
    {
        case MOO_PANE_POS_LEFT:
            allocation->x = 0;
            break;
        case MOO_PANE_POS_RIGHT:
            allocation->x = GTK_WIDGET(paned)->allocation.width - allocation->width;
            break;
        case MOO_PANE_POS_TOP:
            allocation->y = 0;
            break;
        case MOO_PANE_POS_BOTTOM:
            allocation->y = GTK_WIDGET(paned)->allocation.height - allocation->height;
            break;
    }
}


static void
get_bin_child_allocation (MooPaned        *paned,
                          GtkAllocation   *allocation)
{
    switch (paned->priv->pane_position)
    {
        case MOO_PANE_POS_LEFT:
        case MOO_PANE_POS_RIGHT:
            allocation->y = 0;
            allocation->height = GTK_WIDGET(paned)->allocation.height;
            allocation->width = GTK_WIDGET(paned)->allocation.width -
                                paned->priv->button_box_size -
                                paned->priv->border_size;
            break;
        case MOO_PANE_POS_TOP:
        case MOO_PANE_POS_BOTTOM:
            allocation->x = 0;
            allocation->width = GTK_WIDGET(paned)->allocation.width;
            allocation->height = GTK_WIDGET(paned)->allocation.height -
                                 paned->priv->button_box_size -
                                 paned->priv->border_size;
            break;
    }

    switch (paned->priv->pane_position)
    {
        case MOO_PANE_POS_LEFT:
            allocation->x = paned->priv->button_box_size +
                            paned->priv->border_size;
            break;
        case MOO_PANE_POS_RIGHT:
            allocation->x = 0;
            break;
        case MOO_PANE_POS_TOP:
            allocation->y = paned->priv->button_box_size +
                            paned->priv->border_size;
            break;
        case MOO_PANE_POS_BOTTOM:
            allocation->y = 0;
            break;
    }

    if (paned->priv->sticky)
    {
        int add = paned->priv->handle_size + paned->priv->pane_widget_size;

        switch (paned->priv->pane_position)
        {
            case MOO_PANE_POS_LEFT:
                allocation->x += add;
                allocation->width -= add;
                break;
            case MOO_PANE_POS_RIGHT:
                allocation->width -= add;
                break;
            case MOO_PANE_POS_TOP:
                allocation->y += add;
                allocation->height -= add;
                break;
            case MOO_PANE_POS_BOTTOM:
                allocation->height -= add;
                break;
        }
    }
}


static void
clamp_handle_size (MooPaned *paned)
{
    switch (paned->priv->pane_position)
    {
        case MOO_PANE_POS_LEFT:
        case MOO_PANE_POS_RIGHT:
            paned->priv->handle_size = CLAMP (paned->priv->handle_size, 0,
                                              GTK_WIDGET(paned)->allocation.width);
            break;
        case MOO_PANE_POS_TOP:
        case MOO_PANE_POS_BOTTOM:
            paned->priv->handle_size = CLAMP (paned->priv->handle_size, 0,
                                              GTK_WIDGET(paned)->allocation.height);
            break;
    }
}


static void
clamp_button_box_size (MooPaned *paned)
{
    switch (paned->priv->pane_position)
    {
        case MOO_PANE_POS_LEFT:
        case MOO_PANE_POS_RIGHT:
            paned->priv->button_box_size = CLAMP (paned->priv->button_box_size, 0,
                    GTK_WIDGET(paned)->allocation.width -
                            paned->priv->handle_size);
            break;
        case MOO_PANE_POS_TOP:
        case MOO_PANE_POS_BOTTOM:
            paned->priv->button_box_size = CLAMP (paned->priv->button_box_size, 0,
                    GTK_WIDGET(paned)->allocation.height -
                            paned->priv->handle_size);
            break;
    }
}


static void
clamp_child_requisition (MooPaned *paned,
                         GtkRequisition *requisition)
{
    switch (paned->priv->pane_position)
    {
        case MOO_PANE_POS_LEFT:
        case MOO_PANE_POS_RIGHT:
            requisition->width = CLAMP (requisition->width, 0,
                                        GTK_WIDGET(paned)->allocation.width -
                                                paned->priv->handle_size -
                                                paned->priv->button_box_size -
                                                paned->priv->border_size);
            break;
        case MOO_PANE_POS_TOP:
        case MOO_PANE_POS_BOTTOM:
            requisition->height = CLAMP (requisition->height, 0,
                                        GTK_WIDGET(paned)->allocation.height -
                                                paned->priv->handle_size -
                                                paned->priv->button_box_size -
                                                paned->priv->border_size);
            break;
    }
}


static void
clamp_pane_widget_size (MooPaned       *paned,
                        GtkRequisition *child_requisition)
{
    int min_size;
    int max_size = 0;

    switch (paned->priv->pane_position)
    {
        case MOO_PANE_POS_LEFT:
        case MOO_PANE_POS_RIGHT:
            max_size = GTK_WIDGET(paned)->allocation.width -
                                      paned->priv->handle_size -
                                      paned->priv->button_box_size;
            if (paned->priv->sticky)
                max_size -= child_requisition->width;
            break;
        case MOO_PANE_POS_TOP:
        case MOO_PANE_POS_BOTTOM:
            max_size = GTK_WIDGET(paned)->allocation.height -
                                      paned->priv->handle_size -
                                      paned->priv->button_box_size;
            if (paned->priv->sticky)
                max_size -= child_requisition->height;
            break;
    }

    min_size = CLAMP (MIN_PANE_SIZE, 0, max_size);

    paned->priv->pane_widget_size =
        CLAMP (paned->priv->pane_widget_size, min_size, max_size);
}


static void
get_handle_window_rect (MooPaned      *paned,
                        GdkRectangle  *rect)
{
    switch (paned->priv->pane_position)
    {
        case MOO_PANE_POS_LEFT:
        case MOO_PANE_POS_RIGHT:
            rect->y = 0;
            rect->width = paned->priv->handle_size;
            rect->height = GTK_WIDGET(paned)->allocation.height;
            break;
        case MOO_PANE_POS_TOP:
        case MOO_PANE_POS_BOTTOM:
            rect->x = 0;
            rect->height = paned->priv->handle_size;
            rect->width = GTK_WIDGET(paned)->allocation.width;
            break;
    }

    switch (paned->priv->pane_position)
    {
        case MOO_PANE_POS_LEFT:
            rect->x = paned->priv->pane_widget_size;
            break;
        case MOO_PANE_POS_RIGHT:
            rect->x = 0;
            break;
        case MOO_PANE_POS_TOP:
            rect->y = paned->priv->pane_widget_size;
            break;
        case MOO_PANE_POS_BOTTOM:
            rect->y = 0;
            break;
    }
}


static void
moo_paned_size_allocate (GtkWidget     *widget,
                         GtkAllocation *allocation)
{
    GtkBin *bin;
    MooPaned *paned;
    GtkAllocation child_allocation;
    GtkRequisition child_requisition = {0, 0};

    widget->allocation = *allocation;
    bin = GTK_BIN (widget);
    paned = MOO_PANED (widget);

    if (!paned->priv->handle_visible)
        paned->priv->handle_size = 0;
    if (!paned->priv->button_box_visible)
        paned->priv->button_box_size = 0;
    if (!paned->priv->pane_widget_visible)
        paned->priv->pane_widget_size = 0;

    if (bin->child && GTK_WIDGET_VISIBLE (bin->child))
        gtk_widget_get_child_requisition (bin->child, &child_requisition);

    if (paned->priv->handle_visible)
        clamp_handle_size (paned);

    if (paned->priv->button_box_visible)
        clamp_button_box_size (paned);

    clamp_child_requisition (paned, &child_requisition);

    if (paned->priv->pane_widget_visible)
    {
        clamp_pane_widget_size (paned, &child_requisition);
        paned->priv->position = paned->priv->pane_widget_size;
    }

    if (GTK_WIDGET_REALIZED (widget))
        gdk_window_move_resize (paned->priv->bin_window,
                                allocation->x,
                                allocation->y,
                                allocation->width,
                                allocation->height);

    if (paned->priv->button_box_visible)
    {
        get_button_box_allocation (paned, &child_allocation);
        gtk_widget_size_allocate (paned->button_box, &child_allocation);
    }

    if (bin->child)
    {
        get_bin_child_allocation (paned, &child_allocation);
        gtk_widget_size_allocate (bin->child, &child_allocation);
    }

    if (GTK_WIDGET_REALIZED (widget))
    {
        GdkRectangle rect = { 0 };

        if (paned->priv->pane_widget_visible)
        {
            get_pane_window_rect (paned, &rect);
            gdk_window_move_resize (paned->priv->pane_window,
                                    rect.x, rect.y,
                                    rect.width, rect.height);
        }

        if (paned->priv->handle_visible)
        {
            get_handle_window_rect (paned, &rect);
            gdk_window_move_resize (paned->priv->handle_window,
                                    rect.x, rect.y,
                                    rect.width, rect.height);
        }
    }

    if (paned->priv->pane_widget_visible)
    {
        get_pane_widget_allocation (paned, &child_allocation);
        _moo_pane_size_allocate (paned->priv->current_pane, &child_allocation);
    }
}


static void
moo_paned_map (GtkWidget *widget)
{
    MooPaned *paned = MOO_PANED (widget);

    gdk_window_show (paned->priv->bin_window);

    GTK_WIDGET_CLASS(moo_paned_parent_class)->map (widget);

    if (paned->priv->handle_visible)
    {
        gdk_window_show (paned->priv->pane_window);
        gdk_window_show (paned->priv->handle_window);
    }
}


static void
moo_paned_unmap (GtkWidget *widget)
{
    MooPaned *paned = MOO_PANED (widget);

    if (paned->priv->handle_window)
        gdk_window_hide (paned->priv->handle_window);
    if (paned->priv->pane_window)
        gdk_window_hide (paned->priv->pane_window);
    if (paned->priv->bin_window)
        gdk_window_hide (paned->priv->bin_window);

    GTK_WIDGET_CLASS(moo_paned_parent_class)->unmap (widget);
}


static void
forall_internals (MooPaned    *paned,
                  GtkCallback  callback,
                  gpointer     callback_data)
{
    GSList *l;

    callback (paned->button_box, callback_data);

    for (l = paned->priv->panes; l != NULL; l = l->next)
        callback (_moo_pane_get_frame (l->data), callback_data);
}

static void
moo_paned_forall (GtkContainer   *container,
                  gboolean        include_internals,
                  GtkCallback     callback,
                  gpointer        callback_data)
{
    MooPaned *paned = MOO_PANED (container);
    GtkBin *bin = GTK_BIN (container);

    if (!paned->priv->forall_bottom_to_top && include_internals)
        forall_internals (paned, callback, callback_data);

    if (bin->child)
        callback (bin->child, callback_data);

    if (paned->priv->forall_bottom_to_top && include_internals)
        forall_internals (paned, callback, callback_data);
}


static gboolean
moo_paned_expose (GtkWidget      *widget,
                  GdkEventExpose *event)
{
    MooPaned *paned = MOO_PANED (widget);

    if (paned->priv->button_box_visible)
        gtk_container_propagate_expose (GTK_CONTAINER (paned),
                                        paned->button_box, event);

    if (GTK_BIN(paned)->child && GTK_WIDGET_DRAWABLE (GTK_BIN(paned)->child))
        gtk_container_propagate_expose (GTK_CONTAINER (paned),
                                        GTK_BIN(paned)->child,
                                        event);

    if (paned->priv->pane_widget_visible)
        gtk_container_propagate_expose (GTK_CONTAINER (paned),
                                        _moo_pane_get_frame (paned->priv->current_pane),
                                        event);

    if (paned->priv->handle_visible && event->window == paned->priv->handle_window)
        draw_handle (paned, event);

    if (paned->priv->button_box_visible && !paned->priv->pane_widget_visible &&
        paned->priv->border_size && event->window == paned->priv->bin_window)
            draw_border (paned, event);

    return TRUE;
}


#if 0
/* TODO */
static GdkEventExpose *clip_bin_child_event (MooPaned       *paned,
                                             GdkEventExpose *event)
{
    GtkAllocation child_alloc;
    GdkRegion *child_rect;
    GdkEventExpose *child_event;

    get_bin_child_allocation (paned, &child_alloc);
    child_rect = gdk_region_rectangle ((GdkRectangle*) &child_alloc);

    child_event = (GdkEventExpose*) gdk_event_copy ((GdkEvent*) event);
    gdk_region_intersect (child_event->region, child_rect);
    gdk_region_get_clipbox (child_event->region, &child_event->area);

    gdk_region_destroy (child_rect);
    return child_event;
}
#endif


static void
moo_paned_add (GtkContainer   *container,
               GtkWidget      *child)
{
    GtkBin *bin = GTK_BIN (container);

    g_return_if_fail (GTK_IS_WIDGET (child));

    if (bin->child != NULL)
    {
        g_warning ("Attempting to add a widget with type %s to a %s, "
                   "but as a GtkBin subclass a %s can only contain one widget at a time; "
                   "it already contains a widget of type %s",
        g_type_name (G_OBJECT_TYPE (child)),
        g_type_name (G_OBJECT_TYPE (bin)),
        g_type_name (G_OBJECT_TYPE (bin)),
        g_type_name (G_OBJECT_TYPE (bin->child)));
        return;
    }

    gtk_widget_set_parent_window (child, MOO_PANED(container)->priv->bin_window);
    gtk_widget_set_parent (child, GTK_WIDGET (bin));
    bin->child = child;
}


static void
moo_paned_remove (GtkContainer   *container,
                  GtkWidget      *widget)
{
    MooPaned *paned = MOO_PANED (container);

    g_return_if_fail (widget == GTK_BIN(paned)->child);

    GTK_CONTAINER_CLASS(moo_paned_parent_class)->remove (container, widget);
}


static void
draw_handle (MooPaned       *paned,
             GdkEventExpose *event)
{
    GtkWidget *widget = GTK_WIDGET (paned);
    GtkStateType state;
    GdkRectangle area = {0, 0, 0, 0};
    GtkOrientation orientation = GTK_ORIENTATION_VERTICAL;
    int shadow_size = 0;

    switch (paned->priv->pane_position)
    {
        case MOO_PANE_POS_LEFT:
        case MOO_PANE_POS_RIGHT:
            shadow_size = widget->style->xthickness;
            area.width = paned->priv->handle_size;
            area.height = widget->allocation.height;
            if (area.width <= 3*shadow_size)
                shadow_size = 0;
            area.x += shadow_size;
            area.width -= shadow_size;
            orientation = GTK_ORIENTATION_VERTICAL;
            break;
        case MOO_PANE_POS_TOP:
        case MOO_PANE_POS_BOTTOM:
            shadow_size = widget->style->ythickness;
            area.width = widget->allocation.width;
            area.height = paned->priv->handle_size;
            if (area.height <= 3 * shadow_size)
                shadow_size = 0;
            area.y += shadow_size;
            area.height -= shadow_size;
            orientation = GTK_ORIENTATION_HORIZONTAL;
            break;
    }

    if (gtk_widget_is_focus (widget))
        state = GTK_STATE_SELECTED;
    else if (paned->priv->handle_prelit)
        state = GTK_STATE_PRELIGHT;
    else
        state = GTK_WIDGET_STATE (widget);

    gtk_paint_handle (widget->style,
                      paned->priv->handle_window,
                      state,
                      GTK_SHADOW_NONE,
                      &event->area,
                      widget,
                      "paned",
                      area.x, area.y, area.width, area.height,
                      orientation);

    if (shadow_size)
    {
        if (orientation == GTK_ORIENTATION_VERTICAL)
        {
            area.x -= shadow_size;
            area.width = shadow_size;

            gtk_paint_vline (widget->style,
                             paned->priv->handle_window,
                             GTK_STATE_NORMAL,
                             &event->area,
                             widget,
                             "moo-paned",
                             area.y,
                             area.y + area.height,
                             area.x);

            area.x = paned->priv->handle_size - shadow_size;

            gtk_paint_vline (widget->style,
                             paned->priv->handle_window,
                             GTK_STATE_NORMAL,
                             &event->area,
                             widget,
                             "moo-paned",
                             area.y,
                             area.y + area.height,
                             area.x);
        }
        else
        {
            area.y -= shadow_size;
            area.height = shadow_size;

            gtk_paint_hline (widget->style,
                             paned->priv->handle_window,
                             GTK_STATE_NORMAL,
                             &event->area,
                             widget,
                             "moo-paned",
                             area.x,
                             area.x + area.width,
                             area.y);

            area.y = paned->priv->handle_size - shadow_size;

            gtk_paint_hline (widget->style,
                             paned->priv->handle_window,
                             GTK_STATE_NORMAL,
                             &event->area,
                             widget,
                             "moo-paned",
                             area.x,
                             area.x + area.width,
                             area.y);
        }
    }
}


static void
draw_border (MooPaned       *paned,
             GdkEventExpose *event)
{
    GdkRectangle rect;
    GtkWidget *widget = GTK_WIDGET (paned);

    rect.x = paned->priv->button_box_size;
    rect.y = paned->priv->button_box_size;

    switch (paned->priv->pane_position)
    {
        case MOO_PANE_POS_RIGHT:
            rect.x = widget->allocation.width -
                        paned->priv->button_box_size -
                        paned->priv->border_size;
            // fallthrough
        case MOO_PANE_POS_LEFT:
            rect.y = 0;
            rect.height = widget->allocation.height;
            rect.width = paned->priv->border_size;

            gtk_paint_vline (widget->style,
                             paned->priv->bin_window,
                             GTK_STATE_NORMAL,
                             &event->area,
                             widget,
                             "moo-paned",
                             rect.y,
                             rect.y + rect.height,
                             rect.x);
            break;

        case MOO_PANE_POS_BOTTOM:
            rect.y = widget->allocation.height -
                    paned->priv->button_box_size -
                    paned->priv->border_size;
            // fallthrough
        case MOO_PANE_POS_TOP:
            rect.x = 0;
            rect.width = widget->allocation.width;
            rect.height = paned->priv->border_size;

            gtk_paint_hline (widget->style,
                             paned->priv->bin_window,
                             GTK_STATE_NORMAL,
                             &event->area,
                             widget,
                             "moo-paned",
                             rect.x,
                             rect.x + rect.width,
                             rect.y);
            break;
    }
}


void
moo_paned_set_sticky_pane (MooPaned   *paned,
                           gboolean    sticky)
{
    g_return_if_fail (MOO_IS_PANED (paned));

    if (paned->priv->sticky != sticky)
    {
        paned->priv->sticky = sticky;

        if (GTK_WIDGET_REALIZED (paned))
            gtk_widget_queue_resize (GTK_WIDGET (paned));

        g_object_notify (G_OBJECT (paned), "sticky-pane");
    }
}


MooPane *
moo_paned_get_nth_pane (MooPaned *paned,
                        guint     n)
{
    g_return_val_if_fail (MOO_IS_PANED (paned), NULL);
    return get_nth_pane (paned, n);
}


MooPane *
moo_paned_get_pane (MooPaned  *paned,
                    GtkWidget *widget)
{
    MooPane *pane;

    g_return_val_if_fail (MOO_IS_PANED (paned), NULL);
    g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);

    pane = g_object_get_data (G_OBJECT (widget), "moo-pane");

    if (pane && _moo_pane_get_parent (pane) == paned)
        return pane;
    else
        return NULL;
}


int
moo_paned_get_pane_num (MooPaned  *paned,
                        GtkWidget *widget)
{
    MooPane *pane;

    g_return_val_if_fail (MOO_IS_PANED (paned), -1);
    g_return_val_if_fail (GTK_IS_WIDGET (widget), -1);

    pane = g_object_get_data (G_OBJECT (widget), "moo-pane");

    if (pane)
        return pane_index (paned, pane);
    else
        return -1;
}


static gboolean
moo_paned_motion (GtkWidget      *widget,
                  G_GNUC_UNUSED GdkEventMotion *event)
{
    MooPaned *paned = MOO_PANED (widget);

    if (paned->priv->in_drag)
    {
        int size = 0;
        GtkRequisition requisition;

        _moo_pane_get_size_request (paned->priv->current_pane, &requisition);

        switch (paned->priv->pane_position)
        {
            case MOO_PANE_POS_LEFT:
            case MOO_PANE_POS_RIGHT:
                gdk_window_get_pointer (paned->priv->bin_window, &size, NULL, NULL);

                if (paned->priv->pane_position == MOO_PANE_POS_RIGHT)
                    size = widget->allocation.width - size;

                size -= (paned->priv->drag_start + paned->priv->button_box_size);
                size = CLAMP (size, requisition.width,
                              widget->allocation.width - paned->priv->button_box_size -
                                      paned->priv->handle_size);
                break;

            case MOO_PANE_POS_TOP:
            case MOO_PANE_POS_BOTTOM:
                gdk_window_get_pointer (paned->priv->bin_window, NULL, &size, NULL);

                if (paned->priv->pane_position == MOO_PANE_POS_BOTTOM)
                    size = widget->allocation.height - size;

                size -= (paned->priv->drag_start + paned->priv->button_box_size);
                size = CLAMP (size, requisition.height,
                              widget->allocation.height - paned->priv->button_box_size -
                                      paned->priv->handle_size);
                break;
        }

        if (size != paned->priv->pane_widget_size)
            moo_paned_set_pane_size (paned, size);
    }

    return FALSE;
}


static void
get_handle_rect (MooPaned     *paned,
                 GdkRectangle *rect)
{
    rect->x = rect->y = 0;

    switch (paned->priv->pane_position)
    {
        case MOO_PANE_POS_LEFT:
        case MOO_PANE_POS_RIGHT:
            rect->width = paned->priv->handle_size;
            rect->height = GTK_WIDGET(paned)->allocation.height;
            break;
        case MOO_PANE_POS_TOP:
        case MOO_PANE_POS_BOTTOM:
            rect->height = paned->priv->handle_size;
            rect->width = GTK_WIDGET(paned)->allocation.width;
            break;
    }
}


static gboolean
moo_paned_enter (GtkWidget      *widget,
                 GdkEventCrossing *event)
{
    MooPaned *paned = MOO_PANED (widget);
    GdkRectangle rect;

    if (event->window == paned->priv->handle_window &&
        !paned->priv->in_drag)
    {
        paned->priv->handle_prelit = TRUE;
        get_handle_rect (paned, &rect);
        gdk_window_invalidate_rect (paned->priv->handle_window,
                                         &rect, FALSE);
        return TRUE;
    }

    return FALSE;
}


static gboolean moo_paned_leave     (GtkWidget      *widget,
                                     GdkEventCrossing *event)
{
    MooPaned *paned = MOO_PANED (widget);
    GdkRectangle rect;

    if (event->window == paned->priv->handle_window &&
        !paned->priv->in_drag)
    {
        paned->priv->handle_prelit = FALSE;
        get_handle_rect (paned, &rect);
        gdk_window_invalidate_rect (paned->priv->handle_window,
                                         &rect, FALSE);
        return TRUE;
    }

    return FALSE;
}


static gboolean
moo_paned_button_press (GtkWidget      *widget,
                        GdkEventButton *event)
{
    MooPaned *paned = MOO_PANED (widget);

    if (!paned->priv->in_drag &&
         (event->window == paned->priv->handle_window) &&
         (event->button == 1) &&
         paned->priv->pane_widget_visible)
    {
        paned->priv->in_drag = TRUE;

        /* This is copied from gtkpaned.c */
        gdk_pointer_grab (paned->priv->handle_window, FALSE,
                          GDK_POINTER_MOTION_HINT_MASK
                                  | GDK_BUTTON1_MOTION_MASK
                                  | GDK_BUTTON_RELEASE_MASK
                                  | GDK_ENTER_NOTIFY_MASK
                                  | GDK_LEAVE_NOTIFY_MASK,
                          NULL, NULL,
                          event->time);

        switch (paned->priv->pane_position)
        {
            case MOO_PANE_POS_LEFT:
            case MOO_PANE_POS_RIGHT:
                paned->priv->drag_start = event->x;
                break;
            case MOO_PANE_POS_TOP:
            case MOO_PANE_POS_BOTTOM:
                paned->priv->drag_start = event->y;
                break;
        }

        return TRUE;
    }

    return FALSE;
}


static gboolean
moo_paned_button_release (GtkWidget      *widget,
                          GdkEventButton *event)
{
    MooPaned *paned = MOO_PANED (widget);

    if (paned->priv->in_drag && (event->button == 1))
    {
        paned->priv->in_drag = FALSE;
        paned->priv->drag_start = -1;
        gdk_display_pointer_ungrab (gtk_widget_get_display (widget),
                                    event->time);
        return TRUE;
    }

    return FALSE;
}


int
moo_paned_get_pane_size (MooPaned   *paned)
{
    g_return_val_if_fail (MOO_IS_PANED (paned), 0);
    return paned->priv->position;
}


int
moo_paned_get_button_box_size (MooPaned *paned)
{
    g_return_val_if_fail (MOO_IS_PANED (paned), 0);
    return paned->priv->button_box_size;
}


static void
compute_window_offset (MooPaned  *paned,
                       GdkWindow *parent,
                       int       *x,
                       int       *y)
{
    GdkWindow *window;

    *x = *y = 0;

    for (window = paned->button_box->window; window != NULL && window != parent; )
    {
        int x_local, y_local;
        gdk_window_get_position (window, &x_local, &y_local);
        *x += x_local;
        *y += y_local;
        window = gdk_window_get_parent (window);
    }

    if (!window)
        g_critical ("oops");
}

void
_moo_paned_get_button_position (MooPaned     *paned,
                                int           index,
                                GdkRectangle *rect,
                                GdkWindow    *reference)
{
    GtkWidget *button = NULL;
    GtkWidget *last_button = NULL;
    GList *buttons;
    int x_offset, y_offset;

    g_return_if_fail (MOO_IS_PANED (paned));
    g_return_if_fail (rect != NULL);

    buttons = gtk_container_get_children (GTK_CONTAINER (paned->button_box));

    if (index >= 0)
        button = g_list_nth_data (buttons, index);
    if (!button && buttons)
        last_button = g_list_last (buttons)->data;

    if (button)
    {
        rect->x = button->allocation.x;
        rect->y = button->allocation.y;
        rect->width = button->allocation.width;
        rect->height = button->allocation.height;
    }
    else if (last_button)
    {
        int button_spacing;
        GtkWidget *widget = GTK_WIDGET (paned);

        gtk_widget_style_get (widget,
                              "button-spacing", &button_spacing,
                              NULL);

        rect->x = last_button->allocation.x;
        rect->y = last_button->allocation.y;
        rect->width = last_button->allocation.width;
        rect->height = last_button->allocation.height;

        switch (paned->priv->pane_position)
        {
            case MOO_PANE_POS_LEFT:
            case MOO_PANE_POS_RIGHT:
                rect->y += rect->height + button_spacing;
                rect->height = MIN (60, widget->allocation.height - rect->y);
                rect->height = MAX (rect->height, 10);
                break;
            case MOO_PANE_POS_TOP:
            case MOO_PANE_POS_BOTTOM:
                rect->x += rect->width + button_spacing;
                rect->width = MIN (60, widget->allocation.width - rect->x);
                rect->width = MAX (rect->width, 10);
                break;
        }
    }
    else
    {
        GtkWidget *widget = GTK_WIDGET (paned);

        rect->x = 0;
        rect->y = 0;

        switch (paned->priv->pane_position)
        {
            case MOO_PANE_POS_RIGHT:
                rect->x = widget->allocation.width - 30;
                // fallthrough
            case MOO_PANE_POS_LEFT:
                rect->height = 60;
                rect->width = 30;
                break;
            case MOO_PANE_POS_BOTTOM:
                rect->y = widget->allocation.height - 30;
                // fallthrough
            case MOO_PANE_POS_TOP:
                rect->width = 60;
                rect->height = 30;
                break;
        }
    }

    compute_window_offset (paned, reference, &x_offset, &y_offset);
    rect->x += x_offset;
    rect->y += y_offset;

//     if (paned->priv->pane_position == MOO_PANE_POS_BOTTOM)
//         g_print ("%d, %d, %d, %d\n", rect->x, rect->y, rect->width, rect->height);

    g_list_free (buttons);
}

int
_moo_paned_get_button (MooPaned  *paned,
                       int        x,
                       int        y,
                       GdkWindow *reference)
{
    GList *buttons;
    int button_index = 0;
    int x_offset, y_offset;

    buttons = gtk_container_get_children (GTK_CONTAINER (paned->button_box));

    if (!buttons)
        return -1;

    compute_window_offset (paned, reference, &x_offset, &y_offset);
    x -= x_offset;
    y -= y_offset;

    while (buttons != NULL)
    {
        gboolean in_button = FALSE;
        GtkWidget *button = buttons->data;

        switch (paned->priv->pane_position)
        {
            case MOO_PANE_POS_LEFT:
            case MOO_PANE_POS_RIGHT:
                if (y < button->allocation.y + button->allocation.height)
                    in_button = TRUE;
                break;
            case MOO_PANE_POS_TOP:
            case MOO_PANE_POS_BOTTOM:
                if (x < button->allocation.x + button->allocation.width)
                    in_button = TRUE;
                break;
        }

        if (in_button)
            break;

        button_index += 1;
        buttons = g_list_delete_link (buttons, buttons);

        if (!buttons)
            button_index = -1;
    }

    g_list_free (buttons);
    return button_index;
}

int
_moo_paned_get_open_pane_index (MooPaned *paned)
{
    if (paned->priv->current_pane)
        return pane_index (paned, paned->priv->current_pane);
    else
        return -1;
}


static void
moo_paned_set_pane_size_real (MooPaned   *paned,
                              int         size)
{
    GtkWidget *widget;
    GdkRectangle rect;

    g_return_if_fail (MOO_IS_PANED (paned));

    if (!GTK_WIDGET_REALIZED (paned))
    {
        paned->priv->position = size;
        return;
    }

    widget = GTK_WIDGET (paned);

    switch (paned->priv->pane_position)
    {
        case MOO_PANE_POS_LEFT:
        case MOO_PANE_POS_RIGHT:
            size = CLAMP (size, 0,
                          widget->allocation.width - paned->priv->button_box_size -
                                  paned->priv->handle_size);
            break;
        case MOO_PANE_POS_TOP:
        case MOO_PANE_POS_BOTTOM:
            size = CLAMP (size, 0,
                          widget->allocation.height - paned->priv->button_box_size -
                                  paned->priv->handle_size);
            break;
    }

    if (size == paned->priv->position)
        return;

    paned->priv->position = size;

    if (!paned->priv->pane_widget_visible)
        return;

    /* button box redrawing is too slow */
    if (!paned->priv->button_box_visible)
    {
        gtk_widget_queue_resize (widget);
        return;
    }

    rect.x = widget->allocation.x;
    rect.y = widget->allocation.y;
    rect.width = widget->allocation.width;
    rect.height = widget->allocation.height;

    switch (paned->priv->pane_position)
    {
        case MOO_PANE_POS_LEFT:
            rect.x += paned->priv->button_box_size;
            rect.width -= paned->priv->button_box_size;
            break;
        case MOO_PANE_POS_RIGHT:
            rect.width -= paned->priv->button_box_size;
            break;
        case MOO_PANE_POS_TOP:
            rect.y += paned->priv->button_box_size;
            rect.height -= paned->priv->button_box_size;
            break;
        case MOO_PANE_POS_BOTTOM:
            rect.height -= paned->priv->button_box_size;
            break;
    }

    gtk_widget_queue_resize_no_redraw (widget);
}


void
moo_paned_set_pane_size (MooPaned       *paned,
                         int             size)
{
    g_return_if_fail (MOO_IS_PANED (paned));
    g_signal_emit (paned, paned_signals[PANED_SET_PANE_SIZE], 0, size);
}


static void
button_box_visible_notify (MooPaned *paned)
{
    gboolean visible = GTK_WIDGET_VISIBLE (paned->button_box);

    if (paned->priv->button_box_visible == visible)
        return;

    if (paned->priv->panes)
        paned->priv->button_box_visible = visible;

    if (GTK_WIDGET_REALIZED (paned))
        gtk_widget_queue_resize (GTK_WIDGET (paned));
}


void
_moo_paned_insert_pane (MooPaned *paned,
                        MooPane  *pane,
                        int       position)
{
    GtkWidget *handle, *small_handle;

    g_return_if_fail (MOO_IS_PANED (paned));
    g_return_if_fail (MOO_IS_PANE (pane));
    g_return_if_fail (_moo_pane_get_parent (pane) == NULL);

    g_object_ref_sink (pane);
    _moo_pane_set_parent (pane, paned, paned->priv->pane_window);

    if (position < 0 || position > (int) moo_paned_n_panes (paned))
        position = moo_paned_n_panes (paned);

    gtk_container_add_with_properties (GTK_CONTAINER (paned->button_box),
                                       _moo_pane_get_button (pane),
                                       "expand", FALSE,
                                       "fill", FALSE,
                                       "pack-type", GTK_PACK_START,
                                       "position", position,
                                       NULL);

    paned->priv->panes = g_slist_insert (paned->priv->panes,
                                         pane, position);

    g_signal_connect (_moo_pane_get_button (pane), "toggled",
                      G_CALLBACK (pane_button_toggled), paned);

    _moo_pane_get_handle (pane, &handle, &small_handle);
    g_signal_connect (handle, "button-press-event",
                      G_CALLBACK (handle_button_press), paned);
    g_signal_connect (handle, "button-release-event",
                      G_CALLBACK (handle_button_release), paned);
    g_signal_connect (handle, "motion-notify-event",
                      G_CALLBACK (handle_motion), paned);
    g_signal_connect (small_handle, "expose-event",
                      G_CALLBACK (handle_expose), paned);

    gtk_widget_show (paned->button_box);
    paned->priv->button_box_visible = TRUE;

    if (GTK_WIDGET_VISIBLE (paned))
        gtk_widget_queue_resize (GTK_WIDGET (paned));
}

void
_moo_paned_reorder_child (MooPaned *paned,
                          MooPane  *pane,
                          int       position)
{
    g_return_if_fail (MOO_IS_PANED (paned));
    g_return_if_fail (MOO_IS_PANE (pane));
    g_return_if_fail (_moo_pane_get_parent (pane) == paned);

    paned->priv->panes = g_slist_remove (paned->priv->panes, pane);
    paned->priv->panes = g_slist_insert (paned->priv->panes, pane, position);
    gtk_box_reorder_child (GTK_BOX (paned->button_box),
                           _moo_pane_get_button (pane),
                           position);
}


MooPane *
moo_paned_insert_pane (MooPaned       *paned,
                       GtkWidget      *pane_widget,
                       MooPaneLabel   *pane_label,
                       int             position)
{
    MooPane *pane;

    g_return_val_if_fail (MOO_IS_PANED (paned), NULL);
    g_return_val_if_fail (GTK_IS_WIDGET (pane_widget), NULL);
    g_return_val_if_fail (pane_label != NULL, NULL);
    g_return_val_if_fail (pane_widget->parent == NULL, NULL);

    pane = _moo_pane_new (pane_widget, pane_label);
    _moo_paned_insert_pane (paned, pane, position);

    return pane;
}


gboolean
moo_paned_remove_pane (MooPaned  *paned,
                       GtkWidget *pane_widget)
{
    GtkWidget *handle, *small_handle;
    MooPane *pane;
    int index;

    g_return_val_if_fail (MOO_IS_PANED (paned), FALSE);
    g_return_val_if_fail (GTK_IS_WIDGET (pane_widget), FALSE);

    pane = g_object_get_data (G_OBJECT (pane_widget), "moo-pane");
    g_return_val_if_fail (pane != NULL, FALSE);
    g_return_val_if_fail (g_slist_find (paned->priv->panes, pane) != NULL, FALSE);

    if (paned->priv->current_pane == pane)
    {
        index = pane_index (paned, pane);
        if (index > 0)
            index = index - 1;
        else if (moo_paned_n_panes (paned) > 1)
            index = 1;
        else
            index = -1;

        if (index >= 0)
            moo_paned_open_pane (paned, get_nth_pane (paned, index));
        else
            moo_paned_hide_pane (paned);
    }

    if (_moo_pane_get_detached (pane))
    {
        _moo_pane_freeze_params (pane);
        moo_paned_attach_pane (paned, pane);
        _moo_pane_thaw_params (pane);
    }

    g_signal_handlers_disconnect_by_func (_moo_pane_get_button (pane),
                                          (gpointer) pane_button_toggled,
                                          paned);

    _moo_pane_get_handle (pane, &handle, &small_handle);
    g_signal_handlers_disconnect_by_func (handle,
                                          (gpointer) handle_button_press,
                                          paned);
    g_signal_handlers_disconnect_by_func (handle,
                                          (gpointer) handle_button_release,
                                          paned);
    g_signal_handlers_disconnect_by_func (handle,
                                          (gpointer) handle_motion,
                                          paned);
    g_signal_handlers_disconnect_by_func (handle,
                                          (gpointer) handle_realize,
                                          paned);
    g_signal_handlers_disconnect_by_func (small_handle,
                                          (gpointer) handle_expose,
                                          paned);

    gtk_container_remove (GTK_CONTAINER (paned->button_box), _moo_pane_get_button (pane));
    paned->priv->panes = g_slist_remove (paned->priv->panes, pane);

    _moo_pane_unparent (pane);
    g_object_unref (pane);

    if (!moo_paned_n_panes (paned))
    {
        paned->priv->handle_visible = FALSE;
        paned->priv->handle_size = 0;
        if (paned->priv->pane_window)
            gdk_window_hide (paned->priv->pane_window);
        gtk_widget_hide (paned->button_box);
        paned->priv->button_box_visible = FALSE;
    }

    gtk_widget_queue_resize (GTK_WIDGET (paned));

    return TRUE;
}


guint
moo_paned_n_panes (MooPaned *paned)
{
    g_return_val_if_fail (MOO_IS_PANED (paned), 0);
    return g_slist_length (paned->priv->panes);
}


static GtkWidget *
find_focus (GtkWidget *widget)
{
    GtkWidget *focus_child, *window;

    if (!widget)
        return NULL;

    window = gtk_widget_get_toplevel (widget);

    if (!GTK_IS_WINDOW (window))
        return NULL;

    focus_child = gtk_window_get_focus (GTK_WINDOW (window));

    if (focus_child && gtk_widget_is_ancestor (focus_child, widget))
        return focus_child;
    else
        return NULL;
}


static GtkWidget *
find_focus_child (MooPaned *paned)
{
    return find_focus (GTK_BIN(paned)->child);
}


static void
moo_paned_set_focus_child (GtkContainer *container,
                           GtkWidget    *widget)
{
    MooPaned *paned = MOO_PANED (container);
    FocusPosition new_focus = FOCUS_NONE;
    MooPane *new_focus_pane = NULL;
    MooPane *old_focus_pane = paned->priv->focus_pane;
    GSList *l;

    if (widget)
    {
        if (widget == GTK_BIN(paned)->child)
            new_focus = FOCUS_CHILD;
        else if (widget == paned->button_box)
            new_focus = FOCUS_BUTTON;

        if (!new_focus)
        {
            for (l = paned->priv->panes; l != NULL; l = l->next)
            {
                MooPane *pane = l->data;

                if (widget == _moo_pane_get_frame (pane))
                {
                    new_focus_pane = pane;
                    new_focus = FOCUS_PANE;
                    break;
                }
            }
        }

        if (!new_focus)
        {
            g_critical ("oops");
            GTK_CONTAINER_CLASS(moo_paned_parent_class)->set_focus_child (container, widget);
            paned->priv->focus = FOCUS_NONE;
            paned->priv->focus_pane = NULL;
            g_return_if_reached ();
        }
    }

    if (new_focus != FOCUS_BUTTON)
        paned->priv->button_real_focus = FALSE;

    switch (paned->priv->focus)
    {
        case FOCUS_NONE:
        case FOCUS_BUTTON:
            break;

        case FOCUS_CHILD:
            if (new_focus != FOCUS_CHILD)
            {
                if (paned->priv->focus_child)
                    g_object_remove_weak_pointer (paned->priv->focus_child,
                                                  &paned->priv->focus_child);

                paned->priv->focus_child = find_focus_child (paned);

                if (paned->priv->focus_child)
                    g_object_add_weak_pointer (paned->priv->focus_child,
                                               &paned->priv->focus_child);
            }
            break;

        case FOCUS_PANE:
            g_assert (old_focus_pane != NULL);
            if (new_focus_pane != old_focus_pane)
                _moo_pane_update_focus_child (old_focus_pane);
            break;
    }

    GTK_CONTAINER_CLASS(moo_paned_parent_class)->set_focus_child (container, widget);

    paned->priv->focus = new_focus;
    paned->priv->focus_pane = new_focus_pane;

    if (new_focus == FOCUS_CHILD &&
        paned->priv->close_on_child_focus &&
        !paned->priv->sticky)
    {
        paned->priv->dont_move_focus = TRUE;
        moo_paned_hide_pane (paned);
        paned->priv->dont_move_focus = FALSE;
    }
}


static gboolean
focus_to_child (MooPaned         *paned,
                GtkDirectionType  direction)
{
    return GTK_BIN(paned)->child &&
            gtk_widget_child_focus (GTK_BIN(paned)->child, direction);
}


static gboolean
focus_to_pane (MooPaned         *paned,
               GtkDirectionType  direction)
{
    MooPane *pane = paned->priv->current_pane;

    if (!pane)
        return FALSE;
    else if (find_focus (_moo_pane_get_frame (pane)))
        return gtk_widget_child_focus (_moo_pane_get_frame (pane), direction);
    else
        return gtk_widget_child_focus (moo_pane_get_child (pane), direction) ||
                gtk_widget_child_focus (_moo_pane_get_frame (pane), direction);
}


static gboolean
focus_to_button (MooPaned         *paned,
                 GtkDirectionType  direction)
{
    if (gtk_widget_child_focus (paned->button_box, direction))
    {
        paned->priv->button_real_focus = TRUE;
        return TRUE;
    }
    else
    {
        return FALSE;
    }
}


static gboolean
moo_left_paned_focus (MooPaned         *paned,
                      GtkDirectionType  direction)
{
    switch (paned->priv->focus)
    {
        case FOCUS_NONE:
            switch (direction)
            {
                case GTK_DIR_LEFT:
                case GTK_DIR_UP:
                    return focus_to_child (paned, direction) ||
                            (paned->priv->current_pane && focus_to_pane (paned, direction)) ||
                            focus_to_button (paned, direction);
                case GTK_DIR_RIGHT:
                case GTK_DIR_DOWN:
                    return focus_to_button (paned, direction) ||
                            focus_to_child (paned, direction);
                default:
                    g_return_val_if_reached (FALSE);
            }

        case FOCUS_CHILD:
            if (focus_to_child (paned, direction))
                return TRUE;

            switch (direction)
            {
                case GTK_DIR_LEFT:
                    return (paned->priv->current_pane && focus_to_pane (paned, direction)) ||
                            focus_to_button (paned, direction);
                default:
                    return FALSE;
            }

        case FOCUS_PANE:
            if (focus_to_pane (paned, direction))
                return TRUE;

            switch (direction)
            {
                case GTK_DIR_LEFT:
                    return focus_to_button (paned, direction);
                case GTK_DIR_RIGHT:
                    return focus_to_child (paned, direction);
                default:
                    return FALSE;
            }

        case FOCUS_BUTTON:
            if (focus_to_button (paned, direction))
                return TRUE;

            switch (direction)
            {
                case GTK_DIR_RIGHT:
                    return focus_to_pane (paned, direction) ||
                            focus_to_child (paned, direction);
                default:
                    return FALSE;
            }
    }

    g_return_val_if_reached (FALSE);
}


static gboolean
moo_left_paned_tab_focus (MooPaned         *paned,
                          gboolean          forward,
                          GtkDirectionType  direction)
{
    if (forward)
    {
        switch (paned->priv->focus)
        {
            case FOCUS_NONE:
                return focus_to_button (paned, direction) ||
                        focus_to_child (paned, direction);
            case FOCUS_CHILD:
                return focus_to_child (paned, direction);
            case FOCUS_PANE:
                return focus_to_pane (paned, direction) ||
                        focus_to_child (paned, direction);
            case FOCUS_BUTTON:
                return focus_to_button (paned, direction) ||
                        focus_to_pane (paned, direction) ||
                        focus_to_child (paned, direction);
        }
    }
    else
    {
        switch (paned->priv->focus)
        {
            case FOCUS_NONE:
            case FOCUS_CHILD:
                return focus_to_child (paned, direction) ||
                        focus_to_pane (paned, direction) ||
                        focus_to_button (paned, direction);
            case FOCUS_PANE:
                return focus_to_pane (paned, direction) ||
                        focus_to_button (paned, direction);
            case FOCUS_BUTTON:
                return focus_to_button (paned, direction);
        }
    }

    g_return_val_if_reached (FALSE);
}


static gboolean
moo_top_paned_tab_focus (MooPaned         *paned,
                         GtkDirectionType  direction)
{
    switch (paned->priv->focus)
    {
        case FOCUS_NONE:
            return focus_to_button (paned, direction) ||
                    focus_to_child (paned, direction);
        case FOCUS_BUTTON:
            return focus_to_button (paned, direction) ||
                    focus_to_pane (paned, direction) ||
                    focus_to_child (paned, direction);
        case FOCUS_PANE:
            return focus_to_pane (paned, direction) ||
                    focus_to_child (paned, direction);
        case FOCUS_CHILD:
            return focus_to_child (paned, direction);
    }

    g_return_val_if_reached (FALSE);
}


static gboolean
moo_paned_focus (GtkWidget       *widget,
                 GtkDirectionType direction)
{
    MooPaned *paned = MOO_PANED (widget);
    gboolean invert = FALSE;
    gboolean flip = FALSE;

    paned->priv->button_real_focus = FALSE;

    if (direction == GTK_DIR_TAB_FORWARD ||
        direction == GTK_DIR_TAB_BACKWARD)
    {
        switch (paned->priv->pane_position)
        {
            case MOO_PANE_POS_RIGHT:
                return moo_left_paned_tab_focus (paned, direction != GTK_DIR_TAB_FORWARD,
                                                 direction);
            case MOO_PANE_POS_LEFT:
                return moo_left_paned_tab_focus (paned, direction == GTK_DIR_TAB_FORWARD,
                                                 direction);

            case MOO_PANE_POS_BOTTOM:
            case MOO_PANE_POS_TOP:
                return moo_top_paned_tab_focus (paned, direction);
        }
    }

    switch (paned->priv->pane_position)
    {
        case MOO_PANE_POS_RIGHT:
            invert = TRUE;
            /* fall through */
        case MOO_PANE_POS_LEFT:
            break;

        case MOO_PANE_POS_BOTTOM:
            invert = TRUE;
            /* fall through */
        case MOO_PANE_POS_TOP:
            flip = TRUE;
            break;
    }

    if (invert)
    {
        switch (direction)
        {
            case GTK_DIR_RIGHT:
                direction = GTK_DIR_LEFT;
                break;
            case GTK_DIR_LEFT:
                direction = GTK_DIR_RIGHT;
                break;
            case GTK_DIR_UP:
                direction = GTK_DIR_DOWN;
                break;
            case GTK_DIR_DOWN:
                direction = GTK_DIR_UP;
                break;
            default:
                g_return_val_if_reached (FALSE);
        }
    }

    if (flip)
    {
        switch (direction)
        {
            case GTK_DIR_RIGHT:
                direction = GTK_DIR_DOWN;
                break;
            case GTK_DIR_DOWN:
                direction = GTK_DIR_RIGHT;
                break;
            case GTK_DIR_LEFT:
                direction = GTK_DIR_UP;
                break;
            case GTK_DIR_UP:
                direction = GTK_DIR_LEFT;
                break;
            default:
                g_return_val_if_reached (FALSE);
        }
    }

    return moo_left_paned_focus (paned, direction);
}


void
moo_paned_present_pane (MooPaned *paned,
                        MooPane  *pane)
{
    g_return_if_fail (MOO_IS_PANED (paned));
    g_return_if_fail (MOO_IS_PANE (pane));
    g_return_if_fail (_moo_pane_get_parent (pane) == paned);

    if (paned->priv->current_pane == pane)
    {
        paned->priv->dont_move_focus = FALSE;

        if (!find_focus (moo_pane_get_child (pane)))
        {
            if (_moo_pane_get_focus_child (pane))
            {
                gtk_widget_grab_focus (_moo_pane_get_focus_child (pane));
            }
            else if (!gtk_widget_child_focus (moo_pane_get_child (pane), GTK_DIR_TAB_FORWARD))
            {
                paned->priv->button_real_focus = FALSE;
                gtk_widget_grab_focus (_moo_pane_get_button (pane));
            }
        }

        return;
    }
    else if (_moo_pane_get_detached (pane))
    {
        gtk_window_present (GTK_WINDOW (_moo_pane_get_window (pane)));
    }
    else
    {
        moo_paned_open_pane (paned, pane);
    }
}


static void
moo_paned_open_pane_real (MooPaned *paned,
                          guint     index)
{
    MooPane *pane;
    FocusPosition old_focus;

    g_return_if_fail (index < moo_paned_n_panes (paned));

    pane = get_nth_pane (paned, index);
    g_return_if_fail (pane != NULL);

    if (paned->priv->current_pane == pane)
        return;

    old_focus = paned->priv->focus;

    if (paned->priv->current_pane)
    {
        MooPane *old_pane = paned->priv->current_pane;
        paned->priv->current_pane = NULL;
        gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (_moo_pane_get_button (old_pane)), FALSE);
        gtk_widget_hide (_moo_pane_get_frame (old_pane));
    }

    if (GTK_WIDGET_MAPPED (paned))
    {
        gdk_window_show (paned->priv->pane_window);
        gdk_window_show (paned->priv->handle_window);
    }

    gtk_widget_set_parent_window (_moo_pane_get_frame (pane), paned->priv->pane_window);

    paned->priv->current_pane = pane;
    gtk_widget_show (_moo_pane_get_frame (pane));

    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (_moo_pane_get_button (pane)), TRUE);

    if (_moo_pane_get_detached (pane))
        moo_paned_attach_pane (paned, pane);

    paned->priv->handle_visible = TRUE;
    paned->priv->pane_widget_visible = TRUE;
    if (paned->priv->position > 0)
        paned->priv->pane_widget_size = paned->priv->position;

    /* XXX it's wrong, it should look if button was clicked */
    if (!paned->priv->dont_move_focus &&
        (old_focus != FOCUS_BUTTON || !paned->priv->button_real_focus))
    {
        if (_moo_pane_get_focus_child (pane))
        {
            gtk_widget_grab_focus (_moo_pane_get_focus_child (pane));
        }
        else if (!gtk_widget_child_focus (moo_pane_get_child (pane), GTK_DIR_TAB_FORWARD))
        {
            paned->priv->button_real_focus = FALSE;
            gtk_widget_grab_focus (_moo_pane_get_button (pane));
        }
    }

    g_object_notify (G_OBJECT (paned), "active-pane");
    gtk_widget_queue_resize (GTK_WIDGET (paned));
}


/* XXX invalidate space under the pane */
static void
moo_paned_hide_pane_real (MooPaned *paned)
{
    GtkWidget *button;
    FocusPosition old_focus;

    if (!paned->priv->current_pane)
        return;

    button = _moo_pane_get_button (paned->priv->current_pane);
    old_focus = paned->priv->focus;

    gtk_widget_hide (_moo_pane_get_frame (paned->priv->current_pane));

    if (GTK_WIDGET_REALIZED (paned))
    {
        gdk_window_hide (paned->priv->handle_window);
        gdk_window_hide (paned->priv->pane_window);
    }

    paned->priv->current_pane = NULL;
    paned->priv->pane_widget_visible = FALSE;
    paned->priv->handle_visible = FALSE;
    gtk_widget_queue_resize (GTK_WIDGET (paned));

    /* XXX it's wrong, it should look if button was clicked */
    if (!paned->priv->dont_move_focus &&
        old_focus != FOCUS_NONE &&
        (old_focus != FOCUS_BUTTON || !paned->priv->button_real_focus))
    {
        if (paned->priv->focus_child)
        {
            gtk_widget_grab_focus (paned->priv->focus_child);
        }
        else if (!GTK_BIN(paned)->child ||
                  !gtk_widget_child_focus (GTK_BIN(paned)->child, GTK_DIR_TAB_FORWARD))
        {
            if (GTK_WIDGET_VISIBLE (button))
                gtk_widget_grab_focus (button);
        }
        else
        {
            GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (paned));
            gtk_widget_child_focus (toplevel, GTK_DIR_TAB_FORWARD);
        }
    }

    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), FALSE);
    g_object_notify (G_OBJECT (paned), "active-pane");
}


static void
pane_button_toggled (GtkToggleButton *button,
                     MooPaned        *paned)
{
    MooPane *pane;

    pane = g_object_get_data (G_OBJECT (button), "moo-pane");
    g_return_if_fail (MOO_IS_PANE (pane));

    if (!gtk_toggle_button_get_active (button))
    {
        if (paned->priv->current_pane == pane)
            moo_paned_hide_pane (paned);
    }
    else if (!paned->priv->current_pane || paned->priv->current_pane != pane)
    {
        moo_paned_open_pane (paned, pane);
    }
}


void
moo_paned_hide_pane (MooPaned *paned)
{
    g_return_if_fail (MOO_IS_PANED (paned));
    moo_paned_hide_pane_real (paned);
}


void
moo_paned_open_pane (MooPaned *paned,
                     MooPane  *pane)
{
    g_return_if_fail (MOO_IS_PANED (paned));
    g_return_if_fail (MOO_IS_PANE (pane));
    g_return_if_fail (_moo_pane_get_parent (pane) == paned);
    moo_paned_open_pane_real (paned, pane_index (paned, pane));
}


static int
pane_index (MooPaned *paned,
            MooPane  *pane)
{
    return g_slist_index (paned->priv->panes, pane);
}


static MooPane *
get_nth_pane (MooPaned *paned,
              guint     index_)
{
    return g_slist_nth_data (paned->priv->panes, index_);
}


MooPane *
moo_paned_get_open_pane (MooPaned *paned)
{
    g_return_val_if_fail (MOO_IS_PANED (paned), NULL);
    return paned->priv->current_pane;
}


gboolean
moo_paned_is_open (MooPaned *paned)
{
    g_return_val_if_fail (MOO_IS_PANED (paned), FALSE);
    return paned->priv->current_pane != NULL;
}


GType
moo_pane_position_get_type (void)
{
    static GType type = 0;

    if (G_UNLIKELY (!type))
    {
        static const GEnumValue values[] = {
            { MOO_PANE_POS_LEFT, (char*) "MOO_PANE_POS_LEFT", (char*) "left" },
            { MOO_PANE_POS_RIGHT, (char*) "MOO_PANE_POS_RIGHT", (char*) "right" },
            { MOO_PANE_POS_TOP, (char*) "MOO_PANE_POS_TOP", (char*) "top" },
            { MOO_PANE_POS_BOTTOM, (char*) "MOO_PANE_POS_BOTTOM", (char*) "bottom" },
            { 0, NULL, NULL }
        };
        type = g_enum_register_static ("MooPanePosition", values);
    }

    return type;
}


GSList *
moo_paned_list_panes (MooPaned *paned)
{
    g_return_val_if_fail (MOO_IS_PANED (paned), NULL);
    return g_slist_copy (paned->priv->panes);
}


static gboolean
handle_button_press (GtkWidget      *widget,
                     GdkEventButton *event,
                     MooPaned       *paned)
{
#if 1
    GdkCursor *cursor;
#endif

    if (event->button != 1 || event->type != GDK_BUTTON_PRESS)
        return FALSE;

    if (!paned->priv->enable_handle_drag)
        return FALSE;

    g_return_val_if_fail (!paned->priv->handle_in_drag, FALSE);
    g_return_val_if_fail (!paned->priv->handle_button_pressed, FALSE);

    paned->priv->handle_button_pressed = TRUE;
    paned->priv->handle_drag_start_x = event->x;
    paned->priv->handle_drag_start_y = event->y;

#if 1
    cursor = gdk_cursor_new (paned->priv->handle_cursor_type);
    g_return_val_if_fail (cursor != NULL, TRUE);
    gdk_window_set_cursor (widget->window, cursor);
    gdk_cursor_unref (cursor);
#endif

    return TRUE;
}


static gboolean
handle_motion (GtkWidget      *widget,
               GdkEventMotion *event,
               MooPaned       *paned)
{
    MooPane *pane;
    GtkWidget *child;

    if (!paned->priv->handle_button_pressed)
        return FALSE;

    pane = g_object_get_data (G_OBJECT (widget), "moo-pane");
    child = moo_pane_get_child (pane);
    g_return_val_if_fail (child != NULL, FALSE);

    if (!paned->priv->handle_in_drag)
    {
        if (!gtk_drag_check_threshold (widget,
                                       paned->priv->handle_drag_start_x,
                                       paned->priv->handle_drag_start_y,
                                       (int) event->x,
                                       (int) event->y))
            return FALSE;

        paned->priv->handle_in_drag = TRUE;

        g_signal_emit (paned, paned_signals[PANED_HANDLE_DRAG_START], 0, child);
    }

    g_signal_emit (paned, paned_signals[PANED_HANDLE_DRAG_MOTION], 0, child);
    return TRUE;
}


static void
finish_handle_drag (MooPaned  *paned,
                    GtkWidget *child,
                    gboolean   drop)
{
    if (paned->priv->handle_in_drag)
    {
        if (!drop)
            paned->priv->handle_button_pressed = FALSE;

        paned->priv->handle_in_drag = FALSE;
        g_signal_emit (paned, paned_signals[PANED_HANDLE_DRAG_END],
                       0, child, drop);
    }
}

static gboolean
handle_button_release (GtkWidget      *widget,
                       G_GNUC_UNUSED GdkEventButton *event,
                       MooPaned       *paned)
{
    MooPane *pane;
    GtkWidget *child;

    if (paned->priv->handle_button_pressed)
    {
#if 1
        gdk_window_set_cursor (widget->window, NULL);
#endif
        paned->priv->handle_button_pressed = FALSE;

        pane = g_object_get_data (G_OBJECT (widget), "moo-pane");
        child = moo_pane_get_child (pane);
        g_return_val_if_fail (child != NULL, FALSE);

        finish_handle_drag (paned, child, TRUE);
    }

    return TRUE;
}


#define HANDLE_HEIGHT 12

static gboolean
handle_expose (GtkWidget      *widget,
               GdkEventExpose *event,
               MooPaned       *paned)
{
    int height;

    if (!paned->priv->enable_handle_drag)
        return FALSE;

    height = MIN (widget->allocation.height, HANDLE_HEIGHT);

    gtk_paint_handle (widget->style,
                      widget->window,
                      widget->state,
                      GTK_SHADOW_ETCHED_IN,
                      &event->area,
                      widget,
                      "moo-pane-handle",
                      0,
                      (widget->allocation.height - height) / 2,
                      MIN (widget->allocation.width, 20),
                      height,
                      GTK_ORIENTATION_HORIZONTAL);
    return TRUE;
}


static void
handle_realize (G_GNUC_UNUSED GtkWidget *widget,
                MooPaned  *paned)
{
#if 0
    GdkCursor *cursor;
#endif

    g_return_if_fail (MOO_IS_PANED (paned));
    g_return_if_fail (MOO_PANED(paned)->priv->bin_window != NULL);

    if (!paned->priv->enable_handle_drag)
        return;

#if 0
    cursor = gdk_cursor_new (paned->priv->handle_cursor_type);
    g_return_if_fail (cursor != NULL);
    gdk_window_set_cursor (widget->window, cursor);
    gdk_cursor_unref (cursor);
#endif
}


static void
moo_paned_set_handle_cursor_type (MooPaned     *paned,
                                  GdkCursorType cursor_type,
                                  gboolean      set)
{
    if (set)
    {
        paned->priv->handle_cursor_type = cursor_type;
        g_object_notify (G_OBJECT (paned), "handle-cursor-type");
    }
}


/**
 * moo_pane_label_new: (constructor-of MooPaneLabel)
 *
 * @icon_name: (type const-utf8) (allow-none) (default NULL):
 * @icon_pixbuf: (allow-none) (default NULL):
 * @label_text: (type const-utf8) (allow-none) (default NULL):
 * @window_title: (type const-utf8) (allow-none) (default NULL):
 */
MooPaneLabel *
moo_pane_label_new (const char     *icon_stock_id,
                    GdkPixbuf      *pixbuf,
                    const char     *text,
                    const char     *window_title)
{
    MooPaneLabel *label = g_new0 (MooPaneLabel, 1);

    label->icon_stock_id = g_strdup (icon_stock_id);
    label->label = g_strdup (text);
    label->window_title = g_strdup (window_title);

    if (pixbuf)
        label->icon_pixbuf = g_object_ref (pixbuf);

    return label;
}


MooPaneLabel*
moo_pane_label_copy (MooPaneLabel   *label)
{
    MooPaneLabel *copy;

    g_return_val_if_fail (label != NULL, NULL);

    copy = g_new0 (MooPaneLabel, 1);

    copy->icon_stock_id = g_strdup (label->icon_stock_id);
    copy->label = g_strdup (label->label);
    copy->window_title = g_strdup (label->window_title);

    if (label->icon_pixbuf)
        copy->icon_pixbuf = g_object_ref (label->icon_pixbuf);

    return copy;
}


void
moo_pane_label_free (MooPaneLabel *label)
{
    if (label)
    {
        g_free (label->icon_stock_id);
        g_free (label->label);
        g_free (label->window_title);

        if (label->icon_pixbuf)
            g_object_unref (label->icon_pixbuf);

        g_free (label);
    }
}


void
moo_paned_detach_pane (MooPaned *paned,
                       MooPane  *pane)
{
    g_return_if_fail (MOO_IS_PANED (paned));
    g_return_if_fail (MOO_IS_PANE (pane));
    g_return_if_fail (_moo_pane_get_parent (pane) == paned);

    if (_moo_pane_get_detached (pane))
        return;

    if (pane == paned->priv->current_pane)
        moo_paned_hide_pane (paned);

    _moo_pane_detach (pane);
    gtk_widget_queue_resize (GTK_WIDGET (paned));
}


void
moo_paned_attach_pane (MooPaned *paned,
                       MooPane  *pane)
{
    g_return_if_fail (MOO_IS_PANED (paned));
    g_return_if_fail (MOO_IS_PANE (pane));
    g_return_if_fail (_moo_pane_get_parent (pane) == paned);

    if (!_moo_pane_get_detached (pane))
        return;

    _moo_pane_attach (pane);
    gtk_widget_queue_resize (GTK_WIDGET (paned));
}


static void
moo_paned_set_enable_detaching (MooPaned *paned,
                                gboolean  enable)
{
    if (paned->priv->enable_detaching != enable)
    {
        paned->priv->enable_detaching = enable != 0;
        g_object_notify (G_OBJECT (paned), "enable-detaching");
    }
}


void
_moo_paned_attach_pane (MooPaned *paned,
                        MooPane  *pane)
{
    GtkWidget *focus_child;

    g_return_if_fail (MOO_IS_PANED (paned));

    moo_paned_attach_pane (paned, pane);

    paned->priv->dont_move_focus = TRUE;
    moo_paned_open_pane (paned, pane);
    paned->priv->dont_move_focus = TRUE;

    focus_child = _moo_pane_get_focus_child (pane);

    if (focus_child)
        gtk_widget_grab_focus (focus_child);
    else if (!gtk_widget_child_focus (moo_pane_get_child (pane), GTK_DIR_TAB_FORWARD))
        gtk_widget_grab_focus (_moo_pane_get_button (pane));
}


/**
 * moo_pane_params_new: (constructor-of MooPaneParams)
 */
MooPaneParams *
moo_pane_params_new (GdkRectangle *window_position,
                     gboolean      detached,
                     gboolean      maximized,
                     gboolean      keep_on_top)
{
    MooPaneParams *p;

    p = g_new0 (MooPaneParams, 1);
    if (window_position)
        p->window_position = *window_position;
    else
        p->window_position.width = p->window_position.height = -1;
    p->detached = detached != 0;
    p->maximized = maximized != 0;
    p->keep_on_top = keep_on_top != 0;

    return p;
}


MooPaneParams*
moo_pane_params_copy (MooPaneParams *params)
{
    return g_memdup (params, sizeof *params);
}


void
moo_pane_params_free (MooPaneParams *params)
{
    g_free (params);
}


GType
moo_pane_label_get_type (void)
{
    static GType type = 0;

    if (G_UNLIKELY (!type))
        type = g_boxed_type_register_static ("MooPaneLabel",
                                             (GBoxedCopyFunc) moo_pane_label_copy,
                                             (GBoxedFreeFunc) moo_pane_label_free);

    return type;
}


GType
moo_pane_params_get_type (void)
{
    static GType type = 0;

    if (G_UNLIKELY (!type))
        type = g_boxed_type_register_static ("MooPaneParams",
                                             (GBoxedCopyFunc) moo_pane_params_copy,
                                             (GBoxedFreeFunc) moo_pane_params_free);

    return type;
}


static gboolean
moo_paned_key_press (GtkWidget   *widget,
                     GdkEventKey *event)
{
    static int delta = 5;
    int add = 0;
    guint mask = GDK_CONTROL_MASK | GDK_SHIFT_MASK | GDK_MOD1_MASK | GDK_META_MASK;
    MooPaned *paned = MOO_PANED (widget);

    if (paned->priv->handle_in_drag &&
        event->keyval == GDK_Escape)
    {
        finish_handle_drag (paned, NULL, FALSE);
        return TRUE;
    }

    if ((event->state & mask) == (GDK_CONTROL_MASK | GDK_MOD1_MASK))
    {
        switch (event->keyval)
        {
            case GDK_Up:
            case GDK_KP_Up:
                if (paned->priv->pane_position == MOO_PANE_POS_TOP)
                    add = -delta;
                else if (paned->priv->pane_position == MOO_PANE_POS_BOTTOM)
                    add = delta;
                break;
            case GDK_Down:
            case GDK_KP_Down:
                if (paned->priv->pane_position == MOO_PANE_POS_TOP)
                    add = delta;
                else if (paned->priv->pane_position == MOO_PANE_POS_BOTTOM)
                    add = -delta;
                break;
            case GDK_Left:
            case GDK_KP_Left:
                if (paned->priv->pane_position == MOO_PANE_POS_LEFT)
                    add = -delta;
                else if (paned->priv->pane_position == MOO_PANE_POS_RIGHT)
                    add = delta;
                break;
            case GDK_Right:
            case GDK_KP_Right:
                if (paned->priv->pane_position == MOO_PANE_POS_LEFT)
                    add = delta;
                else if (paned->priv->pane_position == MOO_PANE_POS_RIGHT)
                    add = -delta;
                break;
        }
    }

    if (!add)
        return GTK_WIDGET_CLASS(moo_paned_parent_class)->key_press_event (widget, event);

    moo_paned_set_pane_size (paned, moo_paned_get_pane_size (paned) + add);
    return TRUE;
}
