/*
 * gtkpanel.c - implementation of the `Panels' GTK layout container.
 */

#include "gtkpanel.h"

static void panels_init(Panels *panels);
static void panels_class_init(PanelsClass *klass);
static void panels_map(GtkWidget *widget);
static void panels_unmap(GtkWidget *widget);
static void panels_draw(GtkWidget *widget, GdkRectangle *area);
static gint panels_expose(GtkWidget *widget, GdkEventExpose *event);
static void panels_base_add(GtkContainer *container, GtkWidget *widget);
static void panels_remove(GtkContainer *container, GtkWidget *widget);
static void panels_forall(GtkContainer *container, gboolean include_internals,
                           GtkCallback callback, gpointer callback_data);
static GtkType panels_child_type(GtkContainer *container);
static void panels_size_request(GtkWidget *widget, GtkRequisition *req);
static void panels_size_allocate(GtkWidget *widget, GtkAllocation *alloc);

static GtkContainerClass *parent_class = NULL;

GtkType panels_get_type(void)
{
    static GtkType panels_type = 0;

    if (!panels_type) {
        static const GtkTypeInfo panels_info = {
            "Panels",
            sizeof(Panels),
            sizeof(PanelsClass),
            (GtkClassInitFunc) panels_class_init,
            (GtkObjectInitFunc) panels_init,
            /* reserved_1 */ NULL,
            /* reserved_2 */ NULL,
            (GtkClassInitFunc) NULL,
        };

        panels_type = gtk_type_unique(GTK_TYPE_CONTAINER, &panels_info);
    }

    return panels_type;
}

static void panels_class_init(PanelsClass *klass)
{
    GtkObjectClass *object_class;
    GtkWidgetClass *widget_class;
    GtkContainerClass *container_class;

    object_class = (GtkObjectClass *)klass;
    widget_class = (GtkWidgetClass *)klass;
    container_class = (GtkContainerClass *)klass;

    parent_class = gtk_type_class(GTK_TYPE_CONTAINER);

    /*
     * FIXME: do we have to do all this faffing with set_arg,
     * get_arg and child_arg_type? Ick.
     */

    widget_class->map = panels_map;
    widget_class->unmap = panels_unmap;
    widget_class->draw = panels_draw;
    widget_class->expose_event = panels_expose;
    widget_class->size_request = panels_size_request;
    widget_class->size_allocate = panels_size_allocate;

    container_class->add = panels_base_add;
    container_class->remove = panels_remove;
    container_class->forall = panels_forall;
    container_class->child_type = panels_child_type;
}

static void panels_init(Panels *panels)
{
    GTK_WIDGET_SET_FLAGS(panels, GTK_NO_WINDOW);

    panels->children = NULL;
}

/*
 * These appear to be thoroughly tedious functions; the only reason
 * we have to reimplement them at all is because we defined our own
 * format for our GList of children...
 */
static void panels_map(GtkWidget *widget)
{
    Panels *panels;
    GtkWidget *child;
    GList *children;

    g_return_if_fail(widget != NULL);
    g_return_if_fail(IS_PANELS(widget));

    panels = PANELS(widget);
    GTK_WIDGET_SET_FLAGS(panels, GTK_MAPPED);

    for (children = panels->children;
         children && (child = children->data);
         children = children->next) {
        if (child &&
	    GTK_WIDGET_VISIBLE(child) &&
            !GTK_WIDGET_MAPPED(child))
            gtk_widget_map(child);
    }
}
static void panels_unmap(GtkWidget *widget)
{
    Panels *panels;
    GtkWidget *child;
    GList *children;

    g_return_if_fail(widget != NULL);
    g_return_if_fail(IS_PANELS(widget));

    panels = PANELS(widget);
    GTK_WIDGET_UNSET_FLAGS(panels, GTK_MAPPED);

    for (children = panels->children;
         children && (child = children->data);
         children = children->next) {
        if (child &&
	    GTK_WIDGET_VISIBLE(child) &&
            GTK_WIDGET_MAPPED(child))
            gtk_widget_unmap(child);
    }
}
static void panels_draw(GtkWidget *widget, GdkRectangle *area)
{
    Panels *panels;
    GtkWidget *child;
    GList *children;
    GdkRectangle child_area;

    g_return_if_fail(widget != NULL);
    g_return_if_fail(IS_PANELS(widget));

    if (GTK_WIDGET_DRAWABLE(widget)) {
        panels = PANELS(widget);

        for (children = panels->children;
             children && (child = children->data);
             children = children->next) {
            if (child &&
		GTK_WIDGET_DRAWABLE(child) &&
                gtk_widget_intersect(child, area, &child_area))
                gtk_widget_draw(child, &child_area);
        }
    }
}
static gint panels_expose(GtkWidget *widget, GdkEventExpose *event)
{
    Panels *panels;
    GtkWidget *child;
    GList *children;
    GdkEventExpose child_event;

    g_return_val_if_fail(widget != NULL, FALSE);
    g_return_val_if_fail(IS_PANELS(widget), FALSE);
    g_return_val_if_fail(event != NULL, FALSE);

    if (GTK_WIDGET_DRAWABLE(widget)) {
        panels = PANELS(widget);
        child_event = *event;

        for (children = panels->children;
             children && (child = children->data);
             children = children->next) {
            if (child &&
		GTK_WIDGET_DRAWABLE(child) &&
                GTK_WIDGET_NO_WINDOW(child) &&
                gtk_widget_intersect(child, &event->area,
                                     &child_event.area))
                gtk_widget_event(child, (GdkEvent *)&child_event);
        }
    }
    return FALSE;
}

static void panels_base_add(GtkContainer *container, GtkWidget *widget)
{
    Panels *panels;

    g_return_if_fail(container != NULL);
    g_return_if_fail(IS_PANELS(container));
    g_return_if_fail(widget != NULL);

    panels = PANELS(container);

    panels_add(panels, widget);
}

static void panels_remove(GtkContainer *container, GtkWidget *widget)
{
    Panels *panels;
    GtkWidget *child;
    GList *children;
    gboolean was_visible;

    g_return_if_fail(container != NULL);
    g_return_if_fail(IS_PANELS(container));
    g_return_if_fail(widget != NULL);

    panels = PANELS(container);

    for (children = panels->children;
         children && (child = children->data);
         children = children->next) {
        if (child != widget)
            continue;

        was_visible = GTK_WIDGET_VISIBLE(widget);
        gtk_widget_unparent(widget);
        panels->children = g_list_remove_link(panels->children, children);
        g_list_free(children);
        if (was_visible)
            gtk_widget_queue_resize(GTK_WIDGET(container));
        break;
    }
}

static void panels_forall(GtkContainer *container, gboolean include_internals,
                           GtkCallback callback, gpointer callback_data)
{
    Panels *panels;
    GtkWidget *child;
    GList *children, *next;

    g_return_if_fail(container != NULL);
    g_return_if_fail(IS_PANELS(container));
    g_return_if_fail(callback != NULL);

    panels = PANELS(container);

    for (children = panels->children;
         children && (child = children->data);
         children = next) {
        /*
         * We can't wait until after the callback to assign
         * `children = children->next', because the callback might
         * be gtk_widget_destroy, which would remove the link
         * `children' from the list! So instead we must get our
         * hands on the value of the `next' pointer _before_ the
         * callback.
         */
        next = children->next;
	if (child)
	    callback(child, callback_data);
    }
}

static GtkType panels_child_type(GtkContainer *container)
{
    return GTK_TYPE_WIDGET;
}

GtkWidget *panels_new(void)
{
    Panels *panels;

    panels = gtk_type_new(panels_get_type());

    return GTK_WIDGET(panels);
}

void panels_add(Panels *panels, GtkWidget *child)
{
    g_return_if_fail(panels != NULL);
    g_return_if_fail(IS_PANELS(panels));
    g_return_if_fail(child != NULL);
    g_return_if_fail(child->parent == NULL);

    panels->children = g_list_append(panels->children, child);

    gtk_widget_set_parent(child, GTK_WIDGET(panels));

    if (GTK_WIDGET_REALIZED(panels))
        gtk_widget_realize(child);

    if (GTK_WIDGET_VISIBLE(panels)) {
        if (GTK_WIDGET_MAPPED(panels))
            gtk_widget_map(child);
        gtk_widget_queue_resize(child);
    }
}

void panels_switch_to(Panels *panels, GtkWidget *target)
{
    GtkWidget *child = NULL;
    GList *children;
    gboolean changed = FALSE;

    g_return_if_fail(panels != NULL);
    g_return_if_fail(IS_PANELS(panels));
    g_return_if_fail(target != NULL);
    g_return_if_fail(target->parent == GTK_WIDGET(panels));

    for (children = panels->children;
         children && (child = children->data);
         children = children->next) {

	if (!child)
            continue;

        if (child == target) {
            if (!GTK_WIDGET_VISIBLE(child)) {
                gtk_widget_show(child);
                changed = TRUE;
            }
        } else {
            if (GTK_WIDGET_VISIBLE(child)) {
                gtk_widget_hide(child);
                changed = TRUE;
            }
        }
    }
    if (changed)
        gtk_widget_queue_resize(child);
}

/*
 * Now here comes the interesting bit. The actual layout part is
 * done in the following two functions:
 * 
 * panels_size_request() examines the list of widgets held in the
 * Panels, and returns a requisition stating the absolute minimum
 * size it can bear to be.
 * 
 * panels_size_allocate() is given an allocation telling it what
 * size the whole container is going to be, and it calls
 * gtk_widget_size_allocate() on all of its (visible) children to
 * set their size and position relative to the top left of the
 * container.
 */

static void panels_size_request(GtkWidget *widget, GtkRequisition *req)
{
    Panels *panels;
    GtkWidget *child;
    GList *children;

    g_return_if_fail(widget != NULL);
    g_return_if_fail(IS_PANELS(widget));
    g_return_if_fail(req != NULL);

    panels = PANELS(widget);

    req->width = 0;
    req->height = 0;

    for (children = panels->children;
         children && (child = children->data);
         children = children->next) {
        GtkRequisition creq;

        gtk_widget_size_request(child, &creq);
        if (req->width < creq.width)
            req->width = creq.width;
        if (req->height < creq.height)
            req->height = creq.height;
    }

    req->width += 2*GTK_CONTAINER(panels)->border_width;
    req->height += 2*GTK_CONTAINER(panels)->border_width;
}

static void panels_size_allocate(GtkWidget *widget, GtkAllocation *alloc)
{
    Panels *panels;
    GtkWidget *child;
    GList *children;
    gint border;

    g_return_if_fail(widget != NULL);
    g_return_if_fail(IS_PANELS(widget));
    g_return_if_fail(alloc != NULL);

    panels = PANELS(widget);
    widget->allocation = *alloc;
    border = GTK_CONTAINER(panels)->border_width;

    for (children = panels->children;
         children && (child = children->data);
         children = children->next) {
        GtkAllocation call;

        call.x = alloc->x + border;
        call.width = alloc->width - 2*border;
        call.y = alloc->y + border;
        call.height = alloc->height - 2*border;

        gtk_widget_size_allocate(child, &call);
    }
}