/*
 Copyright (C) 2007-2009 Christian Dywan <christian@twotoasts.de>

 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public
 License as published by the Free Software Foundation; either
 version 2.1 of the License, or (at your option) any later version.

 See the file COPYING for the full license text.
*/

#include "midori-panel.h"

#include "midori-view.h"

#include "marshal.h"
#include "sokoke.h"

#include <glib/gi18n.h>

#include "config.h"

#ifdef HAVE_HILDON_2_2
    #include <hildon/hildon.h>
#endif

struct _MidoriPanel
{
    GtkHBox parent_instance;

    GtkWidget* labelbar;
    GtkWidget* toolbar;
    GtkToolItem* button_align;
    GtkWidget* toolbar_label;
    GtkWidget* frame;
    GtkWidget* toolbook;
    GtkWidget* notebook;
    GtkActionGroup* action_group;

    gboolean show_titles;
    gboolean show_controls;
    gboolean right_aligned;
    gboolean open_panels_in_windows;
};

struct _MidoriPanelClass
{
    GtkHBoxClass parent_class;

    /* Signals */
    gboolean
    (*close)                  (MidoriPanel*   panel);
};

G_DEFINE_TYPE (MidoriPanel, midori_panel, GTK_TYPE_HBOX)

enum
{
    PROP_0,

    PROP_SHADOW_TYPE,
    PROP_ACTION_GROUP,
    PROP_PAGE,
    PROP_SHOW_TITLES,
    PROP_SHOW_CONTROLS,
    PROP_RIGHT_ALIGNED,
    PROP_OPEN_PANELS_IN_WINDOWS,
};

enum {
    CLOSE,
    SWITCH_PAGE,

    LAST_SIGNAL
};

static guint signals[LAST_SIGNAL];

static void
midori_panel_finalize (GObject* object);

static void
midori_panel_set_property (GObject*      object,
                           guint         prop_id,
                           const GValue* value,
                           GParamSpec*   pspec);

static void
midori_panel_get_property (GObject*    object,
                           guint       prop_id,
                           GValue*     value,
                           GParamSpec* pspec);

static gboolean
midori_panel_close (MidoriPanel* panel)
{
    gtk_widget_hide (GTK_WIDGET (panel));
    return FALSE;
}

static void
midori_panel_class_init (MidoriPanelClass* class)
{
    GObjectClass* gobject_class;
    GParamFlags flags;

    signals[CLOSE] = g_signal_new (
        "close",
        G_TYPE_FROM_CLASS (class),
        (GSignalFlags)(G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION),
        G_STRUCT_OFFSET (MidoriPanelClass, close),
        g_signal_accumulator_true_handled,
        NULL,
        midori_cclosure_marshal_BOOLEAN__VOID,
        G_TYPE_BOOLEAN, 0);

    signals[SWITCH_PAGE] = g_signal_new (
        "switch-page",
        G_TYPE_FROM_CLASS (class),
        (GSignalFlags)(G_SIGNAL_RUN_LAST),
        0,
        0,
        NULL,
        g_cclosure_marshal_VOID__INT,
        G_TYPE_NONE, 1,
        G_TYPE_INT);

    gobject_class = G_OBJECT_CLASS (class);
    gobject_class->finalize = midori_panel_finalize;
    gobject_class->set_property = midori_panel_set_property;
    gobject_class->get_property = midori_panel_get_property;

    class->close = midori_panel_close;

    flags = G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS;

    g_object_class_install_property (gobject_class,
                                     PROP_SHADOW_TYPE,
                                     g_param_spec_enum (
                                     "shadow-type",
                                     "Shadow Type",
                                     "Appearance of the shadow around each panel",
                                     GTK_TYPE_SHADOW_TYPE,
                                     GTK_SHADOW_NONE,
                                     flags));

    /**
     * MidoriWebSettings:action-group:
     *
     * This is the action group the panel will add actions
     * corresponding to pages to.
     *
     * Since: 0.2.1
     */
    g_object_class_install_property (gobject_class,
                                     PROP_ACTION_GROUP,
                                     g_param_spec_object (
                                     "action-group",
                                     "Action Group",
                                     "The action group the panel will add actions to",
                                     GTK_TYPE_ACTION_GROUP,
                                     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

    g_object_class_install_property (gobject_class,
                                     PROP_PAGE,
                                     g_param_spec_int (
                                     "page",
                                     "Page",
                                     "The index of the current page",
                                     -1, G_MAXINT, -1,
                                     flags));

    /**
     * MidoriWebSettings:show-titles:
     *
     * Whether to show panel titles.
     *
     * Deprecated: 0.2.3
     */
    g_object_class_install_property (gobject_class,
                                     PROP_SHOW_TITLES,
                                     g_param_spec_boolean (
                                     "show-titles",
                                     "Show Titles",
                                     "Whether to show panel titles",
                                     TRUE,
                                     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

    /**
     * MidoriWebSettings:show-controls:
     *
     * Whether to show operating controls.
     *
     * Since: 0.1.9
     *
     * Deprecated: 0.3.0
     */
    g_object_class_install_property (gobject_class,
                                     PROP_SHOW_CONTROLS,
                                     g_param_spec_boolean (
                                     "show-controls",
                                     "Show Controls",
                                     "Whether to show operating controls",
                                     TRUE,
                                     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

    /**
    * MidoriWebSettings:right-aligned:
    *
    * Whether to align the panel on the right.
    *
    * Since: 0.1.3
    */
    g_object_class_install_property (gobject_class,
                                     PROP_RIGHT_ALIGNED,
                                     g_param_spec_boolean (
                                     "right-aligned",
                                     "Right aligned",
                                     "Whether the panel is aligned to the right",
                                     FALSE,
                                     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

    /**
     * MidoriWebSettings:open-panels-in-windows:
     *
     * Whether to open panels in separate windows.
     *
     * Since: 0.2.2
     */
    g_object_class_install_property (gobject_class,
                                     PROP_OPEN_PANELS_IN_WINDOWS,
                                     g_param_spec_boolean (
                                     "open-panels-in-windows",
                                     "Open panels in windows",
        "Whether to open panels in standalone windows by default",
                                     FALSE,
                                     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}

static void
midori_panel_button_close_clicked_cb (GtkWidget*   toolitem,
                                      MidoriPanel* panel)
{
    gboolean return_value;
    g_signal_emit (panel, signals[CLOSE], 0, &return_value);
}

static GtkToolItem*
midori_panel_construct_tool_item (MidoriPanel*    panel,
                                  MidoriViewable* viewable);

static GtkWidget*
_midori_panel_child_for_scrolled (MidoriPanel* panel,
                                  GtkWidget*   scrolled);

static gboolean
midori_panel_detached_window_delete_event_cb (GtkWidget*   window,
                                              GdkEvent*    event,
                                              MidoriPanel* panel)
{
    /* FIXME: The panel will not end up at its original position */
    /* FIXME: The menuitem may be mispositioned */
    GtkWidget* vbox = gtk_bin_get_child (GTK_BIN (window));
    GtkWidget* scrolled = g_object_get_data (G_OBJECT (window), "scrolled");
    GtkWidget* toolbar = g_object_get_data (G_OBJECT (scrolled), "panel-toolbar");
    GtkWidget* menuitem = g_object_get_data (G_OBJECT (scrolled), "panel-menuitem");
    GtkWidget* viewable = _midori_panel_child_for_scrolled (panel, scrolled);
    GtkToolItem* toolitem;
    gint n;

    g_object_ref (toolbar);
    gtk_container_remove (GTK_CONTAINER (vbox), toolbar);
    gtk_container_add (GTK_CONTAINER (panel->toolbook), toolbar);
    g_object_unref (toolbar);
    g_object_ref (scrolled);
    gtk_container_remove (GTK_CONTAINER (vbox), scrolled);
    n = gtk_notebook_append_page (GTK_NOTEBOOK (panel->notebook), scrolled, NULL);
    g_object_unref (scrolled);
    toolitem = midori_panel_construct_tool_item (panel, MIDORI_VIEWABLE (viewable));
    if (menuitem)
        g_object_set_data (G_OBJECT (menuitem), "toolitem", toolitem);
    midori_panel_set_current_page (panel, n);
    return FALSE;
}

static void
midori_panel_widget_destroy_cb (GtkWidget* viewable,
                                GtkWidget* widget)
{
    gtk_widget_destroy (widget);
    g_signal_handlers_disconnect_by_func (
        viewable, midori_panel_widget_destroy_cb, widget);
}

static void
midori_panel_detach_page (MidoriPanel* panel,
                          gint         n)
{
    GtkToolItem* toolitem = gtk_toolbar_get_nth_item (
        GTK_TOOLBAR (panel->toolbar), n);
    const gchar* title = gtk_tool_button_get_label (GTK_TOOL_BUTTON (toolitem));
    GtkWidget* toolbar = gtk_notebook_get_nth_page (
        GTK_NOTEBOOK (panel->toolbook), n);
    GtkWidget* scrolled = gtk_notebook_get_nth_page (
        GTK_NOTEBOOK (panel->notebook), n);
    GtkWidget* vbox = gtk_vbox_new (FALSE, 0);
    #if HAVE_HILDON
    GtkWidget* window = hildon_window_new ();
    hildon_program_add_window (hildon_program_get_instance (), HILDON_WINDOW (window));
    #else
    GtkWidget* window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    gtk_window_set_skip_taskbar_hint (GTK_WINDOW (window), TRUE);
    gtk_window_set_default_size (GTK_WINDOW (window), 250, 400);
    gtk_window_set_transient_for (GTK_WINDOW (window),
        GTK_WINDOW (gtk_widget_get_toplevel (panel->notebook)));
    #endif
    gtk_widget_show (vbox);
    gtk_container_add (GTK_CONTAINER (window), vbox);
    g_object_set_data (G_OBJECT (window), "scrolled", scrolled);
    gtk_window_set_title (GTK_WINDOW (window), title);
    g_signal_handlers_disconnect_by_func (
        _midori_panel_child_for_scrolled (panel, scrolled),
        midori_panel_widget_destroy_cb, toolitem);
    gtk_container_remove (GTK_CONTAINER (panel->toolbar), GTK_WIDGET (toolitem));
    g_object_ref (toolbar);
    gtk_container_remove (GTK_CONTAINER (panel->toolbook), toolbar);
    #if HAVE_HILDON
    hildon_window_add_toolbar (HILDON_WINDOW (window), GTK_TOOLBAR (toolbar));
    #else
    gtk_box_pack_start (GTK_BOX (vbox), toolbar, FALSE, FALSE, 0);
    #endif
    g_object_unref (toolbar);
    g_object_set_data (G_OBJECT (scrolled), "panel-toolbar", toolbar);
    g_object_ref (scrolled);
    gtk_container_remove (GTK_CONTAINER (panel->notebook), scrolled);
    gtk_box_pack_start (GTK_BOX (vbox), scrolled, TRUE, TRUE, 0);
    g_object_unref (scrolled);
    midori_panel_set_current_page (panel, n > 0 ? n - 1 : 0);
    toolitem = gtk_toolbar_get_nth_item (GTK_TOOLBAR (panel->toolbar),
                                         n > 0 ? n - 1 : 0);
    g_signal_connect (window, "delete-event",
        G_CALLBACK (midori_panel_detached_window_delete_event_cb), panel);
    gtk_widget_show (window);
}

static void
midori_panel_button_align_clicked_cb (GtkWidget*   toolitem,
                                      MidoriPanel* panel)
{
    midori_panel_set_right_aligned (panel, !panel->right_aligned);
}

static void
midori_panel_destroy_cb (MidoriPanel* panel)
{
    /* Destroy pages first, so they don't need special care */
    gtk_container_foreach (GTK_CONTAINER (panel->notebook),
                           (GtkCallback) gtk_widget_destroy, NULL);
}

static void
midori_panel_init (MidoriPanel* panel)
{
    GtkWidget* vbox;
    GtkWidget* labelbar;
    GtkToolItem* toolitem;

    panel->action_group = NULL;
    panel->show_titles = TRUE;
    panel->show_controls = TRUE;
    panel->right_aligned = FALSE;

    /* Create the sidebar */
    panel->toolbar = gtk_toolbar_new ();
    gtk_toolbar_set_icon_size (GTK_TOOLBAR (panel->toolbar), GTK_ICON_SIZE_BUTTON);
    gtk_toolbar_set_show_arrow (GTK_TOOLBAR (panel->toolbar), FALSE);
    gtk_widget_show_all (panel->toolbar);
    vbox = gtk_vbox_new (FALSE, 0);
    gtk_box_pack_start (GTK_BOX (panel), vbox, TRUE, TRUE, 0);
    gtk_box_pack_end (GTK_BOX (vbox), panel->toolbar, FALSE, FALSE, 0);

    /* Create the titlebar */
    labelbar = gtk_toolbar_new ();
    panel->labelbar = labelbar;
    gtk_toolbar_set_icon_size (GTK_TOOLBAR (labelbar), GTK_ICON_SIZE_MENU);
    gtk_toolbar_set_style (GTK_TOOLBAR (labelbar), GTK_TOOLBAR_ICONS);
    toolitem = gtk_tool_item_new ();
    gtk_tool_item_set_expand (toolitem, TRUE);
    panel->toolbar_label = gtk_label_new (NULL);
    gtk_label_set_ellipsize (GTK_LABEL (panel->toolbar_label), PANGO_ELLIPSIZE_END);
    gtk_misc_set_alignment (GTK_MISC (panel->toolbar_label), 0, 0.5);
    gtk_container_add (GTK_CONTAINER (toolitem), panel->toolbar_label);
    gtk_container_set_border_width (GTK_CONTAINER (toolitem), 6);
    gtk_toolbar_insert (GTK_TOOLBAR (labelbar), toolitem, -1);
    toolitem = gtk_tool_button_new_from_stock (GTK_STOCK_GO_FORWARD);
    gtk_tool_button_set_label (GTK_TOOL_BUTTON (toolitem),
        _("Align sidepanel to the right"));
    gtk_tool_item_set_tooltip_text (GTK_TOOL_ITEM (toolitem),
        _("Align sidepanel to the right"));
    g_signal_connect (toolitem, "clicked",
        G_CALLBACK (midori_panel_button_align_clicked_cb), panel);
    #if HAVE_OSX
    gtk_toolbar_insert (GTK_TOOLBAR (labelbar), toolitem, 0);
    #else
    gtk_toolbar_insert (GTK_TOOLBAR (labelbar), toolitem, -1);
    #endif
    panel->button_align = toolitem;
    toolitem = gtk_tool_button_new_from_stock (GTK_STOCK_CLOSE);
    gtk_tool_button_set_label (GTK_TOOL_BUTTON (toolitem), _("Close panel"));
    gtk_tool_item_set_tooltip_text (GTK_TOOL_ITEM (toolitem), _("Close panel"));
    g_signal_connect (toolitem, "clicked",
        G_CALLBACK (midori_panel_button_close_clicked_cb), panel);
    #if HAVE_OSX
    gtk_toolbar_insert (GTK_TOOLBAR (labelbar), toolitem, 0);
    #else
    gtk_toolbar_insert (GTK_TOOLBAR (labelbar), toolitem, -1);
    #endif
    gtk_box_pack_start (GTK_BOX (vbox), labelbar, FALSE, FALSE, 0);
    gtk_widget_show_all (vbox);

    /* Create the toolbook */
    panel->toolbook = gtk_notebook_new ();
    gtk_notebook_set_show_border (GTK_NOTEBOOK (panel->toolbook), FALSE);
    gtk_notebook_set_show_tabs (GTK_NOTEBOOK (panel->toolbook), FALSE);
    gtk_box_pack_start (GTK_BOX (vbox), panel->toolbook, FALSE, FALSE, 0);

    /* Create the notebook */
    panel->notebook = gtk_notebook_new ();
    gtk_notebook_set_show_border (GTK_NOTEBOOK (panel->notebook), FALSE);
    gtk_notebook_set_show_tabs (GTK_NOTEBOOK (panel->notebook), FALSE);
    panel->frame = gtk_frame_new (NULL);
    gtk_container_add (GTK_CONTAINER (panel->frame), panel->notebook);
    gtk_box_pack_start (GTK_BOX (vbox), panel->frame, TRUE, TRUE, 0);
    gtk_widget_show_all (panel->frame);

    g_signal_connect (panel, "destroy",
                      G_CALLBACK (midori_panel_destroy_cb), NULL);
}

static void
midori_panel_finalize (GObject* object)
{
    G_OBJECT_CLASS (midori_panel_parent_class)->finalize (object);
}

static void
midori_panel_set_property (GObject*      object,
                           guint         prop_id,
                           const GValue* value,
                           GParamSpec*   pspec)
{
    MidoriPanel* panel = MIDORI_PANEL (object);

    switch (prop_id)
    {
    case PROP_SHADOW_TYPE:
        gtk_frame_set_shadow_type (GTK_FRAME (panel->frame),
                                   g_value_get_enum (value));
        break;
    case PROP_ACTION_GROUP:
        katze_object_assign (panel->action_group, g_value_dup_object (value));
        break;
    case PROP_PAGE:
        midori_panel_set_current_page (panel, g_value_get_int (value));
        break;
    case PROP_SHOW_TITLES:
        panel->show_titles = g_value_get_boolean (value);
        /* Ignore */
        break;
    case PROP_SHOW_CONTROLS:
        panel->show_controls = g_value_get_boolean (value);
        break;
    case PROP_RIGHT_ALIGNED:
        midori_panel_set_right_aligned (panel, g_value_get_boolean (value));
        break;
    case PROP_OPEN_PANELS_IN_WINDOWS:
        panel->open_panels_in_windows = g_value_get_boolean (value);
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
        break;
    }
}

static void
midori_panel_get_property (GObject*    object,
                           guint       prop_id,
                           GValue*     value,
                           GParamSpec* pspec)
{
    MidoriPanel* panel = MIDORI_PANEL (object);

    switch (prop_id)
    {
    case PROP_SHADOW_TYPE:
        g_value_set_enum (value,
            gtk_frame_get_shadow_type (GTK_FRAME (panel->frame)));
        break;
    case PROP_ACTION_GROUP:
        g_value_set_object (value, panel->action_group);
        break;
    case PROP_PAGE:
        g_value_set_int (value, midori_panel_get_current_page (panel));
        break;
    case PROP_SHOW_TITLES:
        g_value_set_boolean (value, panel->show_titles);
        break;
    case PROP_SHOW_CONTROLS:
        g_value_set_boolean (value, panel->show_controls);
        break;
    case PROP_RIGHT_ALIGNED:
        g_value_set_boolean (value, panel->right_aligned);
        break;
    case PROP_OPEN_PANELS_IN_WINDOWS:
        g_value_set_boolean (value, panel->open_panels_in_windows);
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
        break;
    }
}

/**
 * midori_panel_new:
 *
 * Creates a new empty panel.
 *
 * Return value: a new #MidoriPanel
 **/
GtkWidget*
midori_panel_new (void)
{
    MidoriPanel* panel = g_object_new (MIDORI_TYPE_PANEL,
                                       NULL);

    return GTK_WIDGET (panel);
}

/**
 * midori_panel_set_right_aligned:
 * @right_aligned: %TRUE if the panel should be aligned to the right
 *
 * Determines if the panel should be right aligned.
 *
 * Since: 0.1.3
 **/
void
midori_panel_set_right_aligned (MidoriPanel* panel,
                                gboolean     right_aligned)
{
    GtkWidget* box;

    g_return_if_fail (MIDORI_IS_PANEL (panel));

    box = gtk_widget_get_parent (panel->toolbar);
    gtk_box_reorder_child (GTK_BOX (box), panel->toolbar,
        right_aligned ? -1 : 0);
    gtk_tool_button_set_stock_id (GTK_TOOL_BUTTON (panel->button_align),
        right_aligned ? GTK_STOCK_GO_BACK : GTK_STOCK_GO_FORWARD);
    panel->right_aligned = right_aligned;
    gtk_tool_button_set_label (GTK_TOOL_BUTTON (panel->button_align),
        !panel->right_aligned ? _("Align sidepanel to the right")
            : _("Align sidepanel to the left"));
    gtk_tool_item_set_tooltip_text (GTK_TOOL_ITEM (panel->button_align),
        !panel->right_aligned ? _("Align sidepanel to the right")
            : _("Align sidepanel to the left"));
    g_object_notify (G_OBJECT (panel), "right-aligned");
}

/* Private function, used by MidoriBrowser */
/* static */ GtkWidget*
midori_panel_construct_menu_item (MidoriPanel*    panel,
                                  MidoriViewable* viewable)
{
    GtkAction* action;
    GtkWidget* menuitem;

    action = g_object_get_data (G_OBJECT (viewable), "midori-panel-action");
    menuitem = gtk_action_create_menu_item (action);
    g_object_set_data (G_OBJECT (menuitem), "page", viewable);

    if (gtk_widget_get_visible (GTK_WIDGET (viewable)))
        gtk_widget_show (menuitem);
    return menuitem;
}

static void
midori_panel_viewable_destroy_cb (GtkWidget*   viewable,
                                  MidoriPanel* panel)
{
    gint i = gtk_notebook_page_num (GTK_NOTEBOOK (panel->notebook),
                g_object_get_data (G_OBJECT (viewable), "parent"));
    if (i > -1)
        gtk_notebook_remove_page (GTK_NOTEBOOK (panel->notebook), i);
    g_signal_handlers_disconnect_by_func (
        viewable, midori_panel_viewable_destroy_cb, panel);
}

static GtkToolItem*
midori_panel_construct_tool_item (MidoriPanel*    panel,
                                  MidoriViewable* viewable)
{
    GtkAction* action;
    GtkWidget* toolitem;

    action = g_object_get_data (G_OBJECT (viewable), "midori-panel-action");
    toolitem = gtk_action_create_tool_item (action);
    g_object_set_data (G_OBJECT (toolitem), "page", viewable);
    gtk_toolbar_insert (GTK_TOOLBAR (panel->toolbar), GTK_TOOL_ITEM (toolitem), -1);
    g_signal_connect (viewable, "destroy",
                      G_CALLBACK (midori_panel_widget_destroy_cb), toolitem);

    return GTK_TOOL_ITEM (toolitem);
}

static void
midori_panel_action_activate_cb (GtkRadioAction* action,
                                 MidoriPanel*    panel)
{
    GtkWidget* viewable = g_object_get_data (G_OBJECT (action), "viewable");
    gint n = midori_panel_page_num (panel, viewable);

    /* If the panel is detached, focus the window */
    if (n == -1)
    {
        GtkWidget* toplevel = gtk_widget_get_toplevel (viewable);
        gtk_window_present (GTK_WINDOW (toplevel));
        return;
    }

    if (panel->open_panels_in_windows
     && gtk_radio_action_get_current_value (action)
     == katze_object_get_int (action, "value"))
        midori_panel_detach_page (panel, n);
    else
    {
        midori_panel_set_current_page (panel, n);
        g_signal_emit (panel, signals[SWITCH_PAGE], 0, n);
        gtk_widget_show (GTK_WIDGET (panel));
    }
}

/**
 * midori_panel_append_page:
 * @panel: a #MidoriPanel
 * @viewable: a viewable widget
 * @toolbar: a toolbar widget, or %NULL
 * @stock_id: a stock ID
 * @label: a string to use as the label
 *
 * Appends a new page to the panel. If @toolbar is specified it will
 * be packed above @viewable.
 *
 * Since 0.1.3 destroying the @viewable implicitly removes
 * the page including the menu and eventual toolbar.
 *
 * Since 0.2.1 a hidden @viewable will not be shown in the panel.
 *
 * Since 0.2.1 an action with an accelerator is created implicitly.
 *
 * In the case of an error, -1 is returned.
 *
 * Return value: the index of the new page, or -1
 **/
gint
midori_panel_append_page (MidoriPanel*    panel,
                          MidoriViewable* viewable)
{
    GtkWidget* scrolled;
    GObjectClass* gobject_class;
    GtkWidget* widget;
    GtkWidget* toolbar;
    GtkToolItem* toolitem;
    guint n;
    gchar* action_name;
    GtkAction* action;

    g_return_val_if_fail (MIDORI_IS_PANEL (panel), -1);
    g_return_val_if_fail (MIDORI_IS_VIEWABLE (viewable), -1);

    if (GTK_IS_SCROLLED_WINDOW (viewable))
        scrolled = (GtkWidget*)viewable;
    else
    {
        scrolled = gtk_scrolled_window_new (NULL, NULL);
        gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
                                        GTK_POLICY_AUTOMATIC,
                                        GTK_POLICY_AUTOMATIC);
        gtk_widget_set_can_focus (scrolled, TRUE);
        gtk_widget_show (scrolled);
        gobject_class = G_OBJECT_GET_CLASS (viewable);
        if (GTK_WIDGET_CLASS (gobject_class)->set_scroll_adjustments_signal)
            widget = (GtkWidget*)viewable;
        else
        {
            widget = gtk_viewport_new (NULL, NULL);
            gtk_widget_show (widget);
            gtk_container_add (GTK_CONTAINER (widget), GTK_WIDGET (viewable));
        }
        gtk_container_add (GTK_CONTAINER (scrolled), widget);
    }
    gtk_container_add (GTK_CONTAINER (panel->notebook), scrolled);

    toolbar = midori_viewable_get_toolbar (viewable);
    gtk_widget_show (toolbar);
    gtk_container_add (GTK_CONTAINER (panel->toolbook), toolbar);
    g_signal_connect (viewable, "destroy",
                      G_CALLBACK (midori_panel_widget_destroy_cb), toolbar);

    n = midori_panel_get_n_pages (panel) - 1;
    /* FIXME: Use something better than the stock ID */
    action_name = g_strconcat ("PanelPage",
        midori_viewable_get_stock_id (viewable), NULL);
    action = (GtkAction*)gtk_radio_action_new (action_name,
        midori_viewable_get_label (viewable),
        midori_viewable_get_label (viewable),
        midori_viewable_get_stock_id (viewable), n);
    g_object_set_data (G_OBJECT (action), "viewable", viewable);
    g_signal_connect (action, "activate",
        G_CALLBACK (midori_panel_action_activate_cb), panel);
    if (panel->action_group)
    {
        GtkWidget* toplevel = gtk_widget_get_toplevel (GTK_WIDGET (panel));
        GSList* groups = gtk_accel_groups_from_object (G_OBJECT (toplevel));
        gtk_action_set_accel_group (action, g_slist_nth_data (groups, 0));
        gtk_action_group_add_action_with_accel (panel->action_group,
                                                action, NULL);
        gtk_action_connect_accelerator (action);
    }
    if (n > 0)
        g_object_set (action, "group", g_object_get_data (
            G_OBJECT (midori_panel_get_nth_page (panel, 0)),
            "midori-panel-action"), NULL);
    g_object_set_data (G_OBJECT (viewable), "midori-panel-action", action);
    g_free (action_name);

    g_object_set_data (G_OBJECT (viewable), "parent", scrolled);
    toolitem = midori_panel_construct_tool_item (panel, viewable);
    g_signal_connect (viewable, "destroy",
                      G_CALLBACK (midori_panel_viewable_destroy_cb), panel);

    if (!gtk_widget_get_visible (GTK_WIDGET (viewable)))
    {
        gtk_widget_hide (scrolled);
        gtk_widget_hide (GTK_WIDGET (toolitem));
    }

    return n;
}

/**
 * midori_panel_get_current_page:
 * @panel: a #MidoriPanel
 *
 * Retrieves the index of the currently selected page.
 *
 * If @panel has no children, -1 is returned.
 *
 * Return value: the index of the current page, or -1
 **/
gint
midori_panel_get_current_page (MidoriPanel* panel)
{
    g_return_val_if_fail (MIDORI_IS_PANEL (panel), -1);

    return gtk_notebook_get_current_page (GTK_NOTEBOOK (panel->notebook));
}

static GtkWidget*
_midori_panel_child_for_scrolled (MidoriPanel* panel,
                                  GtkWidget*   scrolled)
{
    GtkWidget* child;

    /* This is a lazy hack, we should have a way of determining
       whether the scrolled is the actual child. */
    if (MIDORI_IS_VIEW (scrolled))
        return scrolled;
    child = gtk_bin_get_child (GTK_BIN (scrolled));
    if (GTK_IS_VIEWPORT (child))
        child = gtk_bin_get_child (GTK_BIN (child));
    return child;
}

/**
 * midori_panel_get_nth_page:
 * @panel: a #MidoriPanel
 *
 * Retrieves the child widget of the nth page.
 *
 * If @panel has no children, %NULL is returned.
 *
 * Return value: the child widget of the new page, or %NULL
 **/
GtkWidget*
midori_panel_get_nth_page (MidoriPanel* panel,
                           guint        page_num)
{
    GtkWidget* scrolled;

    g_return_val_if_fail (MIDORI_IS_PANEL (panel), NULL);

    scrolled = gtk_notebook_get_nth_page (
        GTK_NOTEBOOK (panel->notebook), page_num);
    if (scrolled)
        return _midori_panel_child_for_scrolled (panel, scrolled);
    return NULL;
}

/**
 * midori_panel_get_n_pages:
 * @panel: a #MidoriPanel
 *
 * Retrieves the number of pages contained in the panel.
 *
 * Return value: the number of pages
 **/
guint
midori_panel_get_n_pages (MidoriPanel* panel)
{
    g_return_val_if_fail (MIDORI_IS_PANEL (panel), 0);

    return gtk_notebook_get_n_pages (GTK_NOTEBOOK (panel->notebook));
}

static GtkWidget*
_midori_panel_scrolled_for_child (MidoriPanel* panel,
                                  GtkWidget*   child)
{
    GtkWidget* scrolled;

    /* This is a lazy hack, we should have a way of determining
       whether the scrolled is the actual child. */
    if (MIDORI_IS_VIEW (child))
        return child;
    scrolled = gtk_widget_get_parent (GTK_WIDGET (child));
    if (GTK_IS_VIEWPORT (scrolled))
        scrolled = gtk_widget_get_parent (scrolled);
    return scrolled;
}

/**
 * midori_panel_page_num:
 * @panel: a #MidoriPanel
 *
 * Retrieves the index of the page associated to @widget.
 *
 * If @panel has no children, -1 is returned.
 *
 * Return value: the index of page associated to @widget, or -1
 **/
gint
midori_panel_page_num (MidoriPanel* panel,
                       GtkWidget*   child)
{
    GtkWidget* scrolled;

    g_return_val_if_fail (MIDORI_IS_PANEL (panel), -1);
    g_return_val_if_fail (GTK_IS_WIDGET (child), -1);

    scrolled = _midori_panel_scrolled_for_child (panel, child);
    return gtk_notebook_page_num (GTK_NOTEBOOK (panel->notebook), scrolled);
}

/**
 * midori_panel_set_current_page:
 * @panel: a #MidoriPanel
 * @n: index of the page to switch to, or -1 to mean the last page
 *
 * Switches to the page with the given index.
 *
 * The child must be visible, otherwise the underlying GtkNotebook will
 * silently ignore the attempt to switch the page.
 *
 * Since 0.1.8 the "page" property is notifying changes.
 *
 * Since 0.2.1 switching to hidden pages fails silently.
 **/
void
midori_panel_set_current_page (MidoriPanel* panel,
                               gint         n)
{
    GtkWidget* viewable;

    g_return_if_fail (MIDORI_IS_PANEL (panel));

    if ((viewable = midori_panel_get_nth_page (panel, n)))
    {
        GtkWidget* toolbar;
        GList* items;
        const gchar* label;

        if (!gtk_widget_get_visible (viewable))
            return;

        gtk_notebook_set_current_page (GTK_NOTEBOOK (panel->toolbook), n);
        toolbar = gtk_notebook_get_nth_page (GTK_NOTEBOOK (panel->toolbook), n);
        items = gtk_container_get_children (GTK_CONTAINER (toolbar));
        sokoke_widget_set_visible (panel->toolbook, items != NULL);
        g_list_free (items);
        gtk_notebook_set_current_page (GTK_NOTEBOOK (panel->notebook), n);
        label = midori_viewable_get_label (MIDORI_VIEWABLE (viewable));
        g_object_set (panel->toolbar_label, "label", label, NULL);
        g_object_notify (G_OBJECT (panel), "page");
    }
}

typedef struct
{
    GtkAlignment parent_instance;

    gchar* label;
    gchar* stock_id;
    GtkWidget* toolbar;
} MidoriDummyViewable;

typedef struct
{
    GtkAlignmentClass parent_class;
}  MidoriDummyViewableClass;

GType midori_dummy_viewable_get_type (void) G_GNUC_CONST;
#define MIDORI_TYPE_DUMMY_VIEWABLE (midori_dummy_viewable_get_type ())
#define MIDORI_DUMMY_VIEWABLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
    MIDORI_TYPE_DUMMY_VIEWABLE, MidoriDummyViewable))
#define MIDORI_IS_DUMMY_VIEWABLE(obj) \
    (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MIDORI_TYPE_DUMMY_VIEWABLE))

static void
midori_dummy_viewable_iface_init (MidoriViewableIface* iface);

static void
midori_dummy_viewable_finalize (GObject* object);

G_DEFINE_TYPE_WITH_CODE (MidoriDummyViewable, midori_dummy_viewable,
                         GTK_TYPE_ALIGNMENT,
                         G_IMPLEMENT_INTERFACE (MIDORI_TYPE_VIEWABLE,
                                                midori_dummy_viewable_iface_init));

static void
midori_dummy_viewable_class_init (MidoriDummyViewableClass* class)
{
    GObjectClass* gobject_class;

    gobject_class = G_OBJECT_CLASS (class);
    gobject_class->finalize = midori_dummy_viewable_finalize;
}

static const gchar*
midori_dummy_viewable_get_label (MidoriViewable* viewable)
{
    return MIDORI_DUMMY_VIEWABLE (viewable)->label;
}

static const gchar*
midori_dummy_viewable_get_stock_id (MidoriViewable* viewable)
{
    return MIDORI_DUMMY_VIEWABLE (viewable)->stock_id;
}

static GtkWidget*
midori_dummy_viewable_get_toolbar (MidoriViewable* viewable)
{
    return MIDORI_DUMMY_VIEWABLE (viewable)->toolbar;
}

static void
midori_dummy_viewable_iface_init (MidoriViewableIface* iface)
{
    iface->get_stock_id = midori_dummy_viewable_get_stock_id;
    iface->get_label = midori_dummy_viewable_get_label;
    iface->get_toolbar = midori_dummy_viewable_get_toolbar;
}

static void
midori_dummy_viewable_init (MidoriDummyViewable* viewable)
{
    viewable->stock_id = NULL;
    viewable->label = NULL;
    viewable->toolbar = NULL;
}

static void
midori_dummy_viewable_finalize (GObject* object)
{
    MidoriDummyViewable* viewable = MIDORI_DUMMY_VIEWABLE (object);

    katze_assign (viewable->stock_id, NULL);
    katze_assign (viewable->label, NULL);

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

/* static */ GtkWidget*
midori_dummy_viewable_new (const gchar* stock_id,
                           const gchar* label,
                           GtkWidget*   toolbar)
{
    GtkWidget* viewable = g_object_new (MIDORI_TYPE_DUMMY_VIEWABLE, NULL);

    MIDORI_DUMMY_VIEWABLE (viewable)->stock_id = g_strdup (stock_id);
    MIDORI_DUMMY_VIEWABLE (viewable)->label = g_strdup (label);
    MIDORI_DUMMY_VIEWABLE (viewable)->toolbar = toolbar;

    return viewable;
}

/**
 * midori_panel_append_widget:
 * @panel: a #MidoriPanel
 * @widget: the child widget
 * @stock_id: a stock ID
 * @label: a string to use as the label, or %NULL
 * @toolbar: a toolbar widget, or %NULL
 *
 * Appends an arbitrary widget to the panel by wrapping it
 * in a #MidoriViewable created on the fly.
 *
 * Actually implementing #MidoriViewable instead of using
 * this convenience is recommended.
 *
 * Since 0.1.3 destroying the @widget implicitly removes
 * the page including the menu and eventual toolbar.
 *
 * In the case of an error, -1 is returned.
 *
 * Return value: the index of the new page, or -1
 **/
gint
midori_panel_append_widget (MidoriPanel* panel,
                            GtkWidget*   widget,
                            const gchar* stock_id,
                            const gchar* label,
                            GtkWidget*   toolbar)
{
    GtkWidget* viewable;

    g_return_val_if_fail (MIDORI_IS_PANEL (panel), -1);
    g_return_val_if_fail (GTK_IS_WIDGET (widget), -1);

    g_return_val_if_fail (stock_id != NULL, -1);
    g_return_val_if_fail (!toolbar || GTK_IS_WIDGET (toolbar), -1);

    viewable = midori_dummy_viewable_new (stock_id, label, toolbar);
    gtk_widget_show (viewable);
    gtk_container_add (GTK_CONTAINER (viewable), widget);
    g_signal_connect (widget, "destroy",
                      G_CALLBACK (midori_panel_widget_destroy_cb), viewable);
    return midori_panel_append_page (panel, MIDORI_VIEWABLE (viewable));
}

/* Private function, used by MidoriBrowser */
void
midori_panel_set_toolbar_style (MidoriPanel*    panel,
                                GtkToolbarStyle style)
{
    g_return_if_fail (MIDORI_IS_PANEL (panel));

    gtk_toolbar_set_style (GTK_TOOLBAR (panel->toolbar), style);
}

