/*  xfce4
 *  Copyright (C) 2002 Olivier Fourdan (fourdan@xfce.org)
 *
 * This 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.
 *
 * This 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 this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif

#include <gdk/gdk.h>
#include <gdk/gdkcursor.h>
#include <gdk/gdkx.h>
#include <gtk/gtkwindow.h>

#include <libxfce4util/libxfce4util.h>

#include "xfce_movehandler.h"
#include "xfce_marshal.h"

#define DECOR_WIDTH  6
#define DECOR_HEIGHT 6

#define XFCE_MOVEHANDLER_DEFAULT_SIZE 2*DECOR_WIDTH

static const unsigned char dark_bits[] = { 0x00, 0x0e, 0x02, 0x02, 0x00, 0x00, };
static const unsigned char light_bits[] = { 0x00, 0x00, 0x10, 0x10, 0x1c, 0x00, };
static const unsigned char mid_bits[] = { 0x00, 0x00, 0x0c, 0x0c, 0x00, 0x00, };

enum
{
    SIGNAL_MOVE_START,
    SIGNAL_MOVE,
    SIGNAL_MOVE_END,
    SIGNAL_LAST
};

/* Forward declarations */

static void xfce_movehandler_class_init (XfceMovehandlerClass * class);
static void xfce_movehandler_realize (GtkWidget * widget);
static void xfce_movehandler_init (XfceMovehandler * movehandler);
static void xfce_movehandler_destroy (GtkObject * object);
static void xfce_movehandler_finalize (GObject * object);
static void xfce_movehandler_size_request (GtkWidget * widget,
					   GtkRequisition * requisition);
static gint xfce_movehandler_expose (GtkWidget * widget,
				     GdkEventExpose * event);
static void xfce_movehandler_draw (GtkWidget * widget, GdkRectangle * area);
static gint xfce_movehandler_button_changed (GtkWidget * widget,
					     GdkEventButton * event);
static gint xfce_movehandler_motion (GtkWidget * widget,
				     GdkEventMotion * event);

static guint signals[SIGNAL_LAST] = { 0 };

/* Local data */

static GtkWidgetClass *parent_class = NULL;

GtkType
xfce_movehandler_get_type (void)
{
    static GtkType movehandler_type = 0;

    if (!movehandler_type)
    {
	static const GTypeInfo movehandler_info = {
	    sizeof (XfceMovehandlerClass),
	    NULL,		/* base_init */
	    NULL,		/* base_finalize */
	    (GClassInitFunc) xfce_movehandler_class_init,
	    NULL,		/* class_finalize */
	    NULL,		/* class_data */
	    sizeof (XfceMovehandler),
	    0,			/* n_preallocs */
	    (GInstanceInitFunc) xfce_movehandler_init,
	    NULL		/* value_table */
	};

	movehandler_type =
	    g_type_register_static (GTK_TYPE_WIDGET, "XfceMovehandler",
				    &movehandler_info, 0);
    }

    return movehandler_type;
}

static void
xfce_movehandler_class_init (XfceMovehandlerClass * class)
{
    GtkObjectClass *object_class;
    GObjectClass *gobject_class;
    GtkWidgetClass *widget_class;

    object_class = (GtkObjectClass *) class;
    widget_class = (GtkWidgetClass *) class;
    gobject_class = G_OBJECT_CLASS (class);

    parent_class = gtk_type_class (gtk_widget_get_type ());

    object_class->destroy = xfce_movehandler_destroy;
    gobject_class->finalize = xfce_movehandler_finalize;

    widget_class->realize = xfce_movehandler_realize;
    widget_class->size_request = xfce_movehandler_size_request;
    widget_class->expose_event = xfce_movehandler_expose;
    widget_class->button_press_event = xfce_movehandler_button_changed;
    widget_class->button_release_event = xfce_movehandler_button_changed;
    widget_class->motion_notify_event = xfce_movehandler_motion;

    signals[SIGNAL_MOVE_START] =
	g_signal_new ("move-start", G_OBJECT_CLASS_TYPE (widget_class),
		      G_SIGNAL_RUN_LAST,
		      G_STRUCT_OFFSET (XfceMovehandlerClass, move_start),
		      NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE,
		      0);

    signals[SIGNAL_MOVE] =
	g_signal_new ("move", G_OBJECT_CLASS_TYPE (widget_class),
		      G_SIGNAL_RUN_LAST,
		      G_STRUCT_OFFSET (XfceMovehandlerClass, move), NULL,
		      NULL, p_xfce_marshal_VOID__INT_INT, G_TYPE_NONE, 2,
		      G_TYPE_INT, G_TYPE_INT);

    signals[SIGNAL_MOVE_END] =
	g_signal_new ("move-end", G_OBJECT_CLASS_TYPE (widget_class),
		      G_SIGNAL_RUN_LAST,
		      G_STRUCT_OFFSET (XfceMovehandlerClass, move_end), NULL,
		      NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
}

static void
xfce_movehandler_realize (GtkWidget * widget)
{
    XfceMovehandler *movehandler;
    GdkWindowAttr attributes;
    gint attributes_mask;

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

    GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
    movehandler = XFCE_MOVEHANDLER (widget);

    attributes.x = widget->allocation.x;
    attributes.y = widget->allocation.y;
    attributes.width = widget->allocation.width;
    attributes.height = widget->allocation.height;
    attributes.wclass = GDK_INPUT_OUTPUT;
    attributes.window_type = GDK_WINDOW_CHILD;
    attributes.event_mask =
	gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK |
	GDK_BUTTON1_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK |
	GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK;
    ;
    attributes.visual = gtk_widget_get_visual (widget);
    attributes.colormap = gtk_widget_get_colormap (widget);

    attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
    widget->window =
	gdk_window_new (widget->parent->window, &attributes, attributes_mask);

    widget->style = gtk_style_attach (widget->style, widget->window);

    gdk_window_set_user_data (widget->window, widget);

    gtk_style_set_background (widget->style, widget->window, widget->state);

    if (!(movehandler->dark_bmap))
    {
	movehandler->dark_bmap =
	    gdk_bitmap_create_from_data (widget->window, (gchar*) dark_bits,
					 DECOR_WIDTH, DECOR_HEIGHT);
    }
    if (!(movehandler->mid_bmap))
    {
	movehandler->mid_bmap =
	    gdk_bitmap_create_from_data (widget->window, (gchar*) mid_bits,
					 DECOR_WIDTH, DECOR_HEIGHT);
    }
    if (!(movehandler->light_bmap))
    {
	movehandler->light_bmap =
	    gdk_bitmap_create_from_data (widget->window, (gchar*) light_bits,
					 DECOR_WIDTH, DECOR_HEIGHT);
    }
    movehandler->float_window =
	(gtk_widget_get_toplevel (movehandler->gtk_window))->window;
}

static void
xfce_movehandler_init (XfceMovehandler * movehandler)
{
    movehandler->in_drag = FALSE;
    movehandler->dark_bmap = NULL;
    movehandler->mid_bmap = NULL;
    movehandler->light_bmap = NULL;
    movehandler->move = NULL;
    movehandler->move_data = NULL;
}

GtkWidget *
xfce_movehandler_new (GtkWidget * window)
{
    XfceMovehandler *movehandler;

    g_return_val_if_fail (window != NULL, NULL);

    movehandler = g_object_new (xfce_movehandler_get_type (), NULL);
    movehandler->gtk_window = window;

    return GTK_WIDGET (movehandler);
}

void 
xfce_movehandler_set_move_func (XfceMovehandler *handler, XfceMoveFunc move,
				gpointer data)
{
    g_return_if_fail (XFCE_IS_MOVEHANDLER (handler));

    handler->move = move;
    handler->move_data = data;
}

static void
xfce_movehandler_destroy (GtkObject * object)
{
    g_return_if_fail (object != NULL);
    g_return_if_fail (XFCE_IS_MOVEHANDLER (object));

    if (GTK_OBJECT_CLASS (parent_class)->destroy)
	(*GTK_OBJECT_CLASS (parent_class)->destroy) (object);
}

static void
xfce_movehandler_finalize (GObject * object)
{
    XfceMovehandler *movehandler;

    g_return_if_fail (object != NULL);
    g_return_if_fail (XFCE_IS_MOVEHANDLER (object));

    movehandler = XFCE_MOVEHANDLER (object);

    if (movehandler->dark_bmap)
    {
	g_object_unref (G_OBJECT (movehandler->dark_bmap));
	movehandler->dark_bmap = NULL;
    }
    if (movehandler->mid_bmap)
    {
	g_object_unref (G_OBJECT (movehandler->mid_bmap));
	movehandler->mid_bmap = NULL;
    }
    if (movehandler->light_bmap)
    {
	g_object_unref (G_OBJECT (movehandler->light_bmap));
	movehandler->light_bmap = NULL;
    }
    G_OBJECT_CLASS (parent_class)->finalize (object);
}

static void
xfce_movehandler_size_request (GtkWidget * widget,
			       GtkRequisition * requisition)
{
    requisition->width = XFCE_MOVEHANDLER_DEFAULT_SIZE;
    requisition->height = XFCE_MOVEHANDLER_DEFAULT_SIZE;
}

static gint
xfce_movehandler_expose (GtkWidget * widget, GdkEventExpose * event)
{
    g_return_val_if_fail (widget != NULL, FALSE);
    g_return_val_if_fail (XFCE_IS_MOVEHANDLER (widget), FALSE);
    g_return_val_if_fail (event != NULL, FALSE);
    g_return_val_if_fail (GTK_WIDGET_DRAWABLE (widget), FALSE);
    g_return_val_if_fail (!GTK_WIDGET_NO_WINDOW (widget), FALSE);

    xfce_movehandler_draw (widget, &event->area);

    return FALSE;
}

static void
xfce_movehandler_draw (GtkWidget * widget, GdkRectangle * area)
{
    XfceMovehandler *movehandler;

    g_return_if_fail (widget != NULL);
    g_return_if_fail (XFCE_IS_MOVEHANDLER (widget));
    g_return_if_fail (GTK_WIDGET_DRAWABLE (widget));
    g_return_if_fail (!GTK_WIDGET_NO_WINDOW (widget));

    movehandler = XFCE_MOVEHANDLER (widget);

    if (GTK_WIDGET_DRAWABLE (widget))
    {
	gint x, y, w, h;
	guint width = widget->allocation.width;
	guint height = widget->allocation.height;
	guint rows, columns;

	if (area)
	{
	    gdk_gc_set_clip_rectangle (widget->style->light_gc[widget->state],
				       area);
	    gdk_gc_set_clip_rectangle (widget->style->dark_gc[widget->state],
				       area);
	    gdk_gc_set_clip_rectangle (widget->style->mid_gc[widget->state],
				       area);
	}

	columns = MAX (width / DECOR_WIDTH, 1);
	rows = MAX (height / DECOR_HEIGHT, 1);

	w = columns * DECOR_WIDTH;
	h = rows * DECOR_HEIGHT;
	x = (width - w) / 2;
	y = (height - h) / 2;


	gdk_gc_set_stipple (widget->style->light_gc[widget->state],
			    movehandler->light_bmap);
	gdk_gc_set_stipple (widget->style->mid_gc[widget->state],
			    movehandler->mid_bmap);
	gdk_gc_set_stipple (widget->style->dark_gc[widget->state],
			    movehandler->dark_bmap);

	gdk_gc_set_fill (widget->style->light_gc[widget->state],
			 GDK_STIPPLED);
	gdk_gc_set_fill (widget->style->mid_gc[widget->state], GDK_STIPPLED);
	gdk_gc_set_fill (widget->style->dark_gc[widget->state], GDK_STIPPLED);

	gdk_gc_set_ts_origin (widget->style->light_gc[widget->state], x, y);
	gdk_gc_set_ts_origin (widget->style->mid_gc[widget->state], x, y);
	gdk_gc_set_ts_origin (widget->style->dark_gc[widget->state], x, y);

	gdk_draw_rectangle (widget->window,
			    widget->style->light_gc[widget->state], TRUE, x,
			    y, w, h);
	gdk_draw_rectangle (widget->window,
			    widget->style->mid_gc[widget->state], TRUE, x, y,
			    w, h);
	gdk_draw_rectangle (widget->window,
			    widget->style->dark_gc[widget->state], TRUE, x, y,
			    w, h);

	gdk_gc_set_fill (widget->style->light_gc[widget->state], GDK_SOLID);
	gdk_gc_set_fill (widget->style->mid_gc[widget->state], GDK_SOLID);
	gdk_gc_set_fill (widget->style->dark_gc[widget->state], GDK_SOLID);

	if (area)
	{
	    gdk_gc_set_clip_rectangle (widget->style->light_gc[widget->state],
				       NULL);
	    gdk_gc_set_clip_rectangle (widget->style->dark_gc[widget->state],
				       NULL);
	    gdk_gc_set_clip_rectangle (widget->style->mid_gc[widget->state],
				       NULL);
	}
    }
}

static gint
xfce_movehandler_button_changed (GtkWidget * widget, GdkEventButton * event)
{
    XfceMovehandler *movehandler;
    gboolean event_handled;
    GdkCursor *fleur;

    g_return_val_if_fail (widget != NULL, FALSE);
    g_return_val_if_fail (XFCE_IS_MOVEHANDLER (widget), FALSE);
    g_return_val_if_fail (GTK_WIDGET_DRAWABLE (widget), FALSE);
    g_return_val_if_fail (!GTK_WIDGET_NO_WINDOW (widget), FALSE);

    movehandler = XFCE_MOVEHANDLER (widget);

    event_handled = FALSE;
    if ((event->button == 1)
	&& (event->type == GDK_BUTTON_PRESS
	    || event->type == GDK_2BUTTON_PRESS))
    {
	if (event->window != widget->window)
	{
	    return FALSE;
	}
	if (event->type == GDK_BUTTON_PRESS)
	{
	    gint desk_x, desk_y;
	    gint root_x, root_y;

	    gdk_window_get_root_origin (movehandler->float_window, &desk_x,
					&desk_y);
	    gdk_window_get_origin (movehandler->float_window, &root_x,
				   &root_y);

	    movehandler->float_allocation.x = root_x - event->x_root;
	    movehandler->float_allocation.y = root_y - event->y_root;

	    movehandler->deskoff_x = desk_x - root_x;
	    movehandler->deskoff_y = desk_y - root_y;

	    movehandler->in_drag = TRUE;
	    fleur = gdk_cursor_new (GDK_FLEUR);
	    if (gdk_pointer_grab
		(widget->window, FALSE,
		 (GDK_BUTTON1_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK |
		  GDK_BUTTON_RELEASE_MASK), NULL, fleur,
		 GDK_CURRENT_TIME) != 0)
	    {
		movehandler->in_drag = FALSE;
	    }
	    gdk_cursor_unref (fleur);
	    event_handled = TRUE;

	    g_signal_emit (G_OBJECT (movehandler), signals[SIGNAL_MOVE_START],
			   0);
	}
    }
    else if ((event->type == GDK_BUTTON_RELEASE) && (movehandler->in_drag))
    {
	if (event->window != widget->window)
	{
	    return FALSE;
	}
	gdk_pointer_ungrab (GDK_CURRENT_TIME);
	movehandler->in_drag = FALSE;
	event_handled = TRUE;

	g_signal_emit (G_OBJECT (movehandler), signals[SIGNAL_MOVE_END], 0);
    }

    return event_handled;
}

static gint
xfce_movehandler_motion (GtkWidget * widget, GdkEventMotion * event)
{
    XfceMovehandler *movehandler;
    gint new_x, new_y;

    g_return_val_if_fail (widget != NULL, FALSE);
    g_return_val_if_fail (XFCE_IS_MOVEHANDLER (widget), FALSE);
    g_return_val_if_fail (GTK_WIDGET_DRAWABLE (widget), FALSE);
    g_return_val_if_fail (!GTK_WIDGET_NO_WINDOW (widget), FALSE);

    movehandler = XFCE_MOVEHANDLER (widget);

    if ((!movehandler->in_drag) || (event->window != widget->window))
    {
	return FALSE;
    }

    new_x = 0;
    new_y = 0;
    gdk_window_get_pointer (NULL, &new_x, &new_y, NULL);
    new_x += movehandler->float_allocation.x + movehandler->deskoff_x;
    new_y += movehandler->float_allocation.y + movehandler->deskoff_y;

    if (movehandler->move)
    {
	movehandler->move (movehandler->gtk_window, &new_x, &new_y,
			   movehandler->move_data);
    }

    gdk_window_move (movehandler->float_window, new_x, new_y);
    gdk_window_raise (movehandler->float_window);

    g_signal_emit (G_OBJECT (movehandler), signals[SIGNAL_MOVE], 0, new_x,
		   new_y);

    return TRUE;
}


