/* -*- 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-gtk-extensions.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;
};

/* 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 };

/* Ancestor methods */
GtkWidget *    find_windowed_ancestor               (GtkWidget          *widget);
#if GTK_CHECK_VERSION (3, 0, 0)
static void    signal_connect_while_realized        (GtkWidget          *object,
#else
static void    signal_connect_while_realized        (GtkObject          *object,
#endif
    						     const char         *name,
    						     GCallback           callback,
    						     gpointer            callback_data,
    						     GtkWidget          *realized_widget);
/* 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);

G_DEFINE_TYPE (EelImageTable, eel_image_table, EEL_TYPE_WRAP_TABLE)

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

    image_table->details = G_TYPE_INSTANCE_GET_PRIVATE (image_table,
    							EEL_TYPE_IMAGE_TABLE,
    							EelImageTableDetails);
}

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

    image_table = EEL_IMAGE_TABLE (object);

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

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

    g_assert (EEL_IS_IMAGE_TABLE (widget));

    /* Chain realize */
    GTK_WIDGET_CLASS (eel_image_table_parent_class)->realize (widget);

    windowed_ancestor = 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);

#if !GTK_CHECK_VERSION (3, 0, 0)
#define windowed_ancestor GTK_OBJECT(windowed_ancestor)
#endif

    signal_connect_while_realized (windowed_ancestor,
    				   "enter_notify_event",
    				   G_CALLBACK (ancestor_enter_notify_event),
    				   widget,
    				   widget);

    signal_connect_while_realized (windowed_ancestor,
    				   "leave_notify_event",
    				   G_CALLBACK (ancestor_leave_notify_event),
    				   widget,
    				   widget);

    signal_connect_while_realized (windowed_ancestor,
    				   "motion_notify_event",
    				   G_CALLBACK (ancestor_motion_notify_event),
    				   widget,
    				   widget);

    signal_connect_while_realized (windowed_ancestor,
    				   "button_press_event",
    				   G_CALLBACK (ancestor_button_press_event),
    				   widget,
    				   widget);

    signal_connect_while_realized (windowed_ancestor,
    				   "button_release_event",
    				   G_CALLBACK (ancestor_button_release_event),
    				   widget,
    				   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;
    }

    GTK_CONTAINER_CLASS (eel_image_table_parent_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
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;

    /* 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);

    g_type_class_add_private (image_table_class, sizeof (EelImageTableDetails));
}

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);
    }
}

GtkWidget *
find_windowed_ancestor (GtkWidget *widget)
{
    g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);

    while (widget && !gtk_widget_get_has_window (widget))
    {
        widget = gtk_widget_get_parent (widget);
    }

    return widget;
}

typedef struct
{
    GObject *object;
    guint object_destroy_handler;

    GtkWidget *realized_widget;
    guint realized_widget_destroy_handler;
    guint realized_widget_unrealized_handler;

    guint signal_handler;
} RealizeDisconnectInfo;

static void
while_realized_disconnecter (GObject *object,
                             RealizeDisconnectInfo *info)
{
    g_assert (G_IS_OBJECT (object));
    g_assert (info != NULL);
    g_assert (G_IS_OBJECT (info->object));
    g_assert (info->object_destroy_handler != 0);
    g_assert (info->object_destroy_handler != 0);
    g_assert (info->realized_widget_destroy_handler != 0);
    g_assert (info->realized_widget_unrealized_handler != 0);

    g_signal_handler_disconnect (info->object, info->object_destroy_handler);
    g_signal_handler_disconnect (info->object, info->signal_handler);
    g_signal_handler_disconnect (info->realized_widget, info->realized_widget_destroy_handler);
    g_signal_handler_disconnect (info->realized_widget, info->realized_widget_unrealized_handler);
    g_free (info);
}

/**
 * signal_connect_while_realized:
 *
 * @object: Object to connect to.
 * @name: Name of signal to connect to.
 * @callback: Caller's callback.
 * @callback_data: Caller's callback_data.
 * @realized_widget: Widget to monitor for realized state.  Signal is connected
 *                   while this wigget is realized.
 *
 * Connect to a signal of an object while another widget is realized.  This is
 * useful for non windowed widgets that need to monitor events in their ancestored
 * windowed widget.  The signal is automatically disconnected when &widget is
 * unrealized.  Also, the signal is automatically disconnected when either &object
 * or &widget are destroyed.
 **/
static void
#if GTK_CHECK_VERSION (3, 0, 0)
signal_connect_while_realized (GtkWidget *object,
#else
signal_connect_while_realized (GtkObject *object,
#endif
    				const char *name,
    				GCallback callback,
    				gpointer callback_data,
    				GtkWidget *realized_widget)
{
    RealizeDisconnectInfo *info;

#if GTK_CHECK_VERSION (3, 0, 0)
    g_return_if_fail (GTK_IS_WIDGET (object));
#else
    g_return_if_fail (GTK_IS_OBJECT (object));
#endif
    g_return_if_fail (name != NULL);
    g_return_if_fail (name[0] != '\0');
    g_return_if_fail (callback != NULL);
    g_return_if_fail (GTK_IS_WIDGET (realized_widget));
    g_return_if_fail (gtk_widget_get_realized (realized_widget));

    info = g_new0 (RealizeDisconnectInfo, 1);

    info->object = G_OBJECT (object);
    info->object_destroy_handler =
        g_signal_connect (G_OBJECT (info->object),
                          "destroy",
                          G_CALLBACK (while_realized_disconnecter),
                          info);

    info->realized_widget = realized_widget;
    info->realized_widget_destroy_handler =
        g_signal_connect (G_OBJECT (info->realized_widget),
                          "destroy",
                          G_CALLBACK (while_realized_disconnecter),
                          info);
    info->realized_widget_unrealized_handler =
        g_signal_connect_after (G_OBJECT (info->realized_widget),
                                "unrealize",
                                G_CALLBACK (while_realized_disconnecter),
                                info);

    info->signal_handler = g_signal_connect (G_OBJECT (info->object),
                           name, callback, callback_data);
}

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;
}
