/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */

/* eel-image-table.c - An image table.

   Copyright (C) 2000 Eazel, Inc.

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

   The Mate Library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with the Mate Library; see the file COPYING.LIB.  If not,
   write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
   Boston, MA 02110-1301, USA.

   Authors: Ramiro Estrugo <ramiro@eazel.com>
*/

#include <config.h>
#include "eel-image-table.h"

#include "eel-art-extensions.h"
#include "eel-art-gtk-extensions.h"
#include "eel-debug-drawing.h"
#include "eel-gtk-extensions.h"
#include "eel-gtk-macros.h"
#include "eel-labeled-image.h"
#include "eel-marshal.h"
#include <gtk/gtk.h>

/* Arguments */
enum
{
    ARG_0,
    ARG_CHILD_UNDER_POINTER
};

/* Detail member struct */
struct EelImageTableDetails
{
    GtkWidget *child_under_pointer;
    GtkWidget *child_being_pressed;
    GdkGC     *clear_gc;
};

/* Signals */
typedef enum
{
    CHILD_ENTER,
    CHILD_LEAVE,
    CHILD_PRESSED,
    CHILD_RELEASED,
    CHILD_CLICKED,
    LAST_SIGNAL
} ImageTableSignals;

/* Signals */
static guint image_table_signals[LAST_SIGNAL] = { 0 };

static void    eel_image_table_class_init     (EelImageTableClass *image_table_class);
static void    eel_image_table_init           (EelImageTable      *image);

/* GObjectClass methods */
static void    eel_image_table_finalize             (GObject            *object);

/* GtkWidgetClass methods */
static void    eel_image_table_realize              (GtkWidget          *widget);
static void    eel_image_table_unrealize            (GtkWidget          *widget);

/* GtkContainerClass methods */
static void    eel_image_table_remove               (GtkContainer       *container,
        GtkWidget          *widget);
static GType   eel_image_table_child_type           (GtkContainer       *container);

/* Private EelImageTable methods */
static void    image_table_emit_signal              (EelImageTable      *image_table,
        GtkWidget          *child,
        guint               signal_index,
        int                 x,
        int                 y,
        int                 button,
        guint               state,
        GdkEvent           *event);

/* Ancestor callbacks */
static int     ancestor_enter_notify_event          (GtkWidget          *widget,
        GdkEventCrossing   *event,
        gpointer            event_data);
static int     ancestor_leave_notify_event          (GtkWidget          *widget,
        GdkEventCrossing   *event,
        gpointer            event_data);
static int     ancestor_motion_notify_event         (GtkWidget          *widget,
        GdkEventMotion     *event,
        gpointer            event_data);
static int     ancestor_button_press_event          (GtkWidget          *widget,
        GdkEventButton     *event,
        gpointer            event_data);
static int     ancestor_button_release_event        (GtkWidget          *widget,
        GdkEventButton     *event,
        gpointer            event_data);

EEL_CLASS_BOILERPLATE (EelImageTable, eel_image_table, EEL_TYPE_WRAP_TABLE)

static void
eel_image_table_class_init (EelImageTableClass *image_table_class)
{
    GObjectClass *object_class = G_OBJECT_CLASS (image_table_class);
    GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (image_table_class);
    GtkContainerClass *container_class = GTK_CONTAINER_CLASS (image_table_class);

    /* GObjectClass */
    object_class->finalize = eel_image_table_finalize;

    /* GtkWidgetClass */
    widget_class->realize = eel_image_table_realize;
    widget_class->unrealize = eel_image_table_unrealize;

    /* GtkContainerClass */
    container_class->remove = eel_image_table_remove;
    container_class->child_type = eel_image_table_child_type;

    /* Signals */
    image_table_signals[CHILD_ENTER] = g_signal_new ("child_enter",
                                       G_TYPE_FROM_CLASS (object_class),
                                       G_SIGNAL_RUN_LAST,
                                       G_STRUCT_OFFSET (EelImageTableClass, child_enter),
                                       NULL, NULL,
                                       eel_marshal_VOID__OBJECT_POINTER,
                                       G_TYPE_NONE,
                                       2,
                                       GTK_TYPE_WIDGET,
                                       G_TYPE_POINTER);
    image_table_signals[CHILD_LEAVE] = g_signal_new ("child_leave",
                                       G_TYPE_FROM_CLASS (object_class),
                                       G_SIGNAL_RUN_LAST,
                                       G_STRUCT_OFFSET (EelImageTableClass, child_leave),
                                       NULL, NULL,
                                       eel_marshal_VOID__OBJECT_POINTER,
                                       G_TYPE_NONE,
                                       2,
                                       GTK_TYPE_WIDGET,
                                       G_TYPE_POINTER);
    image_table_signals[CHILD_PRESSED] = g_signal_new ("child_pressed",
                                         G_TYPE_FROM_CLASS (object_class),
                                         G_SIGNAL_RUN_LAST,
                                         G_STRUCT_OFFSET (EelImageTableClass, child_pressed),
                                         NULL, NULL,
                                         eel_marshal_VOID__OBJECT_POINTER,
                                         G_TYPE_NONE,
                                         2,
                                         GTK_TYPE_WIDGET,
                                         G_TYPE_POINTER);
    image_table_signals[CHILD_RELEASED] = g_signal_new ("child_released",
                                          G_TYPE_FROM_CLASS (object_class),
                                          G_SIGNAL_RUN_LAST,
                                          G_STRUCT_OFFSET (EelImageTableClass, child_released),
                                          NULL, NULL,
                                          eel_marshal_VOID__OBJECT_POINTER,
                                          G_TYPE_NONE,
                                          2,
                                          GTK_TYPE_WIDGET,
                                          G_TYPE_POINTER);
    image_table_signals[CHILD_CLICKED] = g_signal_new ("child_clicked",
                                         G_TYPE_FROM_CLASS (object_class),
                                         G_SIGNAL_RUN_LAST,
                                         G_STRUCT_OFFSET (EelImageTableClass, child_clicked),
                                         NULL, NULL,
                                         eel_marshal_VOID__OBJECT_POINTER,
                                         G_TYPE_NONE,
                                         2,
                                         GTK_TYPE_WIDGET,
                                         G_TYPE_POINTER);
}

static void
eel_image_table_init (EelImageTable *image_table)
{
    gtk_widget_set_has_window (GTK_WIDGET (image_table), FALSE);

    image_table->details = g_new0 (EelImageTableDetails, 1);
}

/* GObjectClass methods */
static void
eel_image_table_finalize (GObject *object)
{
    EelImageTable *image_table;

    image_table = EEL_IMAGE_TABLE (object);

    g_free (image_table->details);

    EEL_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
}

static void
eel_image_table_realize (GtkWidget *widget)
{
    GtkWidget *windowed_ancestor;

    g_assert (EEL_IS_IMAGE_TABLE (widget));

    /* Chain realize */
    EEL_CALL_PARENT (GTK_WIDGET_CLASS, realize, (widget));

    windowed_ancestor = eel_gtk_widget_find_windowed_ancestor (widget);
    g_assert (GTK_IS_WIDGET (windowed_ancestor));

    gtk_widget_add_events (windowed_ancestor,
                           GDK_BUTTON_PRESS_MASK
                           | GDK_BUTTON_RELEASE_MASK
                           | GDK_BUTTON_MOTION_MASK
                           | GDK_ENTER_NOTIFY_MASK
                           | GDK_LEAVE_NOTIFY_MASK
                           | GDK_POINTER_MOTION_MASK);

    eel_gtk_signal_connect_while_realized (GTK_OBJECT (windowed_ancestor),
                                           "enter_notify_event",
                                           G_CALLBACK (ancestor_enter_notify_event),
                                           widget,
                                           widget);

    eel_gtk_signal_connect_while_realized (GTK_OBJECT (windowed_ancestor),
                                           "leave_notify_event",
                                           G_CALLBACK (ancestor_leave_notify_event),
                                           widget,
                                           widget);

    eel_gtk_signal_connect_while_realized (GTK_OBJECT (windowed_ancestor),
                                           "motion_notify_event",
                                           G_CALLBACK (ancestor_motion_notify_event),
                                           widget,
                                           widget);

    eel_gtk_signal_connect_while_realized (GTK_OBJECT (windowed_ancestor),
                                           "button_press_event",
                                           G_CALLBACK (ancestor_button_press_event),
                                           widget,
                                           widget);

    eel_gtk_signal_connect_while_realized (GTK_OBJECT (windowed_ancestor),
                                           "button_release_event",
                                           G_CALLBACK (ancestor_button_release_event),
                                           widget,
                                           widget);
}

static void
eel_image_table_unrealize (GtkWidget *widget)
{
    EelImageTable *image_table;

    g_assert (EEL_IS_IMAGE_TABLE (widget));

    image_table = EEL_IMAGE_TABLE (widget);

    if (image_table->details->clear_gc != NULL)
    {
        g_object_unref (image_table->details->clear_gc);
        image_table->details->clear_gc = NULL;
    }

    /* Chain unrealize */
    EEL_CALL_PARENT (GTK_WIDGET_CLASS, unrealize, (widget));
}

/* GtkContainerClass methods */
static void
eel_image_table_remove (GtkContainer *container,
                        GtkWidget *child)
{
    EelImageTable *image_table;

    g_assert (EEL_IS_IMAGE_TABLE (container));
    g_assert (EEL_IS_LABELED_IMAGE (child));

    image_table = EEL_IMAGE_TABLE (container);

    if (child == image_table->details->child_under_pointer)
    {
        image_table->details->child_under_pointer = NULL;
    }

    if (child == image_table->details->child_being_pressed)
    {
        image_table->details->child_being_pressed = NULL;
    }

    EEL_CALL_PARENT (GTK_CONTAINER_CLASS, remove, (container, child));
}

static GType
eel_image_table_child_type (GtkContainer *container)
{
    return EEL_TYPE_LABELED_IMAGE;
}

/* Private EelImageTable methods */

static void
image_table_emit_signal (EelImageTable *image_table,
                         GtkWidget *child,
                         guint signal_index,
                         int x,
                         int y,
                         int button,
                         guint state,
                         GdkEvent *gdk_event)
{
    EelImageTableEvent event;

    g_assert (EEL_IS_IMAGE_TABLE (image_table));
    g_assert (GTK_IS_WIDGET (child));
    g_assert (signal_index < LAST_SIGNAL);

    event.x = x;
    event.y = y;
    event.button = button;
    event.state = state;
    event.event = gdk_event;

    g_signal_emit (image_table,
                   image_table_signals[signal_index],
                   0,
                   child,
                   &event);
}

static void
image_table_handle_motion (EelImageTable *image_table,
                           int x,
                           int y,
                           GdkEvent *event)
{
    GtkWidget *child;
    GtkWidget *leave_emit_child = NULL;
    GtkWidget *enter_emit_child = NULL;

    g_assert (EEL_IS_IMAGE_TABLE (image_table));

    child = eel_wrap_table_find_child_at_event_point (EEL_WRAP_TABLE (image_table), x, y);

    if (child && !gtk_widget_get_sensitive (child))
    {
        return;
    }

    if (child == image_table->details->child_under_pointer)
    {
        return;
    }

    if (child != NULL)
    {
        if (image_table->details->child_under_pointer != NULL)
        {
            leave_emit_child = image_table->details->child_under_pointer;
        }

        image_table->details->child_under_pointer = child;
        enter_emit_child = image_table->details->child_under_pointer;
    }
    else
    {
        if (image_table->details->child_under_pointer != NULL)
        {
            leave_emit_child = image_table->details->child_under_pointer;
        }

        image_table->details->child_under_pointer = NULL;
    }

    if (leave_emit_child != NULL)
    {
        image_table_emit_signal (image_table,
                                 leave_emit_child,
                                 CHILD_LEAVE,
                                 x,
                                 y,
                                 0,
                                 0,
                                 (GdkEvent *)event);
    }

    if (enter_emit_child != NULL)
    {
        image_table_emit_signal (image_table,
                                 enter_emit_child,
                                 CHILD_ENTER,
                                 x,
                                 y,
                                 0,
                                 0,
                                 (GdkEvent *)event);
    }
}

static int
ancestor_enter_notify_event (GtkWidget *widget,
                             GdkEventCrossing *event,
                             gpointer event_data)
{
    g_assert (GTK_IS_WIDGET (widget));
    g_assert (EEL_IS_IMAGE_TABLE (event_data));
    g_assert (event != NULL);

    image_table_handle_motion (EEL_IMAGE_TABLE (event_data), event->x, event->y, (GdkEvent *) event);

    return FALSE;
}

static int
ancestor_leave_notify_event (GtkWidget *widget,
                             GdkEventCrossing *event,
                             gpointer event_data)
{
    EelIRect bounds;
    int x = -1;
    int y = -1;

    g_assert (GTK_IS_WIDGET (widget));
    g_assert (EEL_IS_IMAGE_TABLE (event_data));
    g_assert (event != NULL);

    bounds = eel_gtk_widget_get_bounds (GTK_WIDGET (event_data));

    if (eel_irect_contains_point (bounds, event->x, event->y))
    {
        x = event->x;
        y = event->y;
    }

    image_table_handle_motion (EEL_IMAGE_TABLE (event_data), x, y, (GdkEvent *) event);

    return FALSE;
}

static int
ancestor_motion_notify_event (GtkWidget *widget,
                              GdkEventMotion *event,
                              gpointer event_data)
{
    g_assert (GTK_IS_WIDGET (widget));
    g_assert (EEL_IS_IMAGE_TABLE (event_data));
    g_assert (event != NULL);

    image_table_handle_motion (EEL_IMAGE_TABLE (event_data), (int) event->x, (int) event->y, (GdkEvent *) event);

    return FALSE;
}

static int
ancestor_button_press_event (GtkWidget *widget,
                             GdkEventButton *event,
                             gpointer event_data)
{
    EelImageTable *image_table;
    GtkWidget *child;

    g_assert (GTK_IS_WIDGET (widget));
    g_assert (EEL_IS_IMAGE_TABLE (event_data));
    g_assert (event != NULL);

    image_table = EEL_IMAGE_TABLE (event_data);

    child = eel_wrap_table_find_child_at_event_point (EEL_WRAP_TABLE (image_table), event->x, event->y);

    if (child && !gtk_widget_get_sensitive (child))
    {
        return FALSE;
    }

    if (child != NULL)
    {
        if (child == image_table->details->child_under_pointer)
        {
            image_table->details->child_being_pressed = child;
            image_table_emit_signal (image_table,
                                     child,
                                     CHILD_PRESSED,
                                     event->x,
                                     event->y,
                                     event->button,
                                     event->state,
                                     (GdkEvent *)event);
        }
    }

    return FALSE;
}

static int
ancestor_button_release_event (GtkWidget *widget,
                               GdkEventButton *event,
                               gpointer event_data)
{
    EelImageTable *image_table;
    GtkWidget *child;
    GtkWidget *released_emit_child = NULL;
    GtkWidget *clicked_emit_child = NULL;

    g_assert (GTK_IS_WIDGET (widget));
    g_assert (EEL_IS_IMAGE_TABLE (event_data));
    g_assert (event != NULL);

    image_table = EEL_IMAGE_TABLE (event_data);

    child = eel_wrap_table_find_child_at_event_point (EEL_WRAP_TABLE (image_table), event->x, event->y);

    if (child && !gtk_widget_get_sensitive (child))
    {
        return FALSE;
    }

    if (image_table->details->child_being_pressed != NULL)
    {
        released_emit_child = image_table->details->child_being_pressed;
    }

    if (child != NULL)
    {
        if (child == image_table->details->child_being_pressed)
        {
            clicked_emit_child = child;
        }
    }

    image_table->details->child_being_pressed = NULL;

    if (released_emit_child != NULL)
    {
        image_table_emit_signal (image_table,
                                 released_emit_child,
                                 CHILD_RELEASED,
                                 event->x,
                                 event->y,
                                 event->button,
                                 event->state,
                                 (GdkEvent *)event);
    }

    if (clicked_emit_child != NULL)
    {

        image_table_emit_signal (image_table,
                                 clicked_emit_child,
                                 CHILD_CLICKED,
                                 event->x,
                                 event->y,
                                 event->button,
                                 event->state,
                                 (GdkEvent *)event);
    }

    return FALSE;
}

/**
 * eel_image_table_new:
 */
GtkWidget*
eel_image_table_new (gboolean homogeneous)
{
    EelImageTable *image_table;

    image_table = EEL_IMAGE_TABLE (gtk_widget_new (eel_image_table_get_type (), NULL));

    eel_wrap_table_set_homogeneous (EEL_WRAP_TABLE (image_table), homogeneous);

    return GTK_WIDGET (image_table);
}

/**
 * eel_image_table_add_empty_child:
 * @image_table: A EelImageTable.
 *
 * Add a "empty" child to the table.  Useful when you want to have
 * empty space between 2 children.
 *
 * Returns: The empty child - A EelLabeledImage widget with no label
 *          or pixbuf.
 */
GtkWidget *
eel_image_table_add_empty_image (EelImageTable *image_table)
{
    GtkWidget *empty;

    g_return_val_if_fail (EEL_IS_IMAGE_TABLE (image_table), NULL);

    empty = eel_labeled_image_new (NULL, NULL);
    gtk_container_add (GTK_CONTAINER (image_table), empty);
    gtk_widget_set_sensitive (empty, FALSE);

    return empty;
}