/* Canvas Rubberband object
 *
 * Copyright (C) 1998 Free Software Foundation
 *
 * Developed by Havoc Pennington <hp@pobox.com>
 *
 * This program is free software; you can redistribute it and/or 
 * modify it under the terms of the GNU General Public License as 
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 */


#include <config.h>

#include "gnome-canvas-rubberband.h"

#include <gtk/gtksignal.h>
#include <libgnomeui/gnome-canvas-rect-ellipse.h>

static void gnome_canvas_rubberband_class_init (GnomeCanvasRubberbandClass *class);
static void gnome_canvas_rubberband_init       (GnomeCanvasRubberband      *rubberband);
static void gnome_canvas_rubberband_destroy    (GtkObject            *object);
static void gnome_canvas_rubberband_set_arg    (GtkObject            *object,
						GtkArg               *arg,
						guint                 arg_id);
static void gnome_canvas_rubberband_get_arg    (GtkObject            *object,
						GtkArg               *arg,
						guint                 arg_id);

static gint item_event(GnomeCanvasItem* item, GdkEvent* event, 
		       GnomeCanvasRubberband* rubberband);

static void gtk_marshal_NONE__DOUBLE_DOUBLE_DOUBLE_DOUBLE (GtkObject * object,
							   GtkSignalFunc func,
							   gpointer func_data,
							   GtkArg * args);

enum {
  ARG_0
};


enum {
  MOVED,
  SNAPPED,
  CANCELLED,
  LAST_SIGNAL
};

static GnomeCanvasRubberbandClass *parent_class;

static gint rubberband_signals[LAST_SIGNAL] = { 0,0,0 };

GtkType
gnome_canvas_rubberband_get_type (void)
{
  static GtkType rubberband_type = 0;

  if (!rubberband_type) {
    GtkTypeInfo rubberband_info = {
      "GnomeCanvasRubberband",
      sizeof (GnomeCanvasRubberband),
      sizeof (GnomeCanvasRubberbandClass),
      (GtkClassInitFunc) gnome_canvas_rubberband_class_init,
      (GtkObjectInitFunc) gnome_canvas_rubberband_init,
      NULL, /* reserved_1 */
      NULL, /* reserved_2 */
      (GtkClassInitFunc) NULL
    };

    rubberband_type = gtk_type_unique (gtk_object_get_type (), &rubberband_info);
  }

  return rubberband_type;
}

static void
gnome_canvas_rubberband_class_init (GnomeCanvasRubberbandClass *klass)
{
  GtkObjectClass *object_class;

  object_class = (GtkObjectClass *) klass;

  rubberband_signals[MOVED] =
    gtk_signal_new ("moved",
		    GTK_RUN_LAST,
		    object_class->type,
		    GTK_SIGNAL_OFFSET (GnomeCanvasRubberbandClass, moved),
		    gtk_marshal_NONE__DOUBLE_DOUBLE_DOUBLE_DOUBLE,
		    GTK_TYPE_NONE, 4, 
		    GTK_TYPE_DOUBLE, GTK_TYPE_DOUBLE, 
		    GTK_TYPE_DOUBLE, GTK_TYPE_DOUBLE);

  rubberband_signals[SNAPPED] =
    gtk_signal_new ("snapped",
		    GTK_RUN_LAST,
		    object_class->type,
		    GTK_SIGNAL_OFFSET (GnomeCanvasRubberbandClass, snapped),
		    gtk_marshal_NONE__DOUBLE_DOUBLE_DOUBLE_DOUBLE,
		    GTK_TYPE_NONE, 4, 
		    GTK_TYPE_DOUBLE, GTK_TYPE_DOUBLE, 
		    GTK_TYPE_DOUBLE, GTK_TYPE_DOUBLE);

  rubberband_signals[CANCELLED] =
    gtk_signal_new ("cancelled",
		    GTK_RUN_LAST,
		    object_class->type,
		    GTK_SIGNAL_OFFSET (GnomeCanvasRubberbandClass, cancelled),
		    gtk_marshal_NONE__NONE,
		    GTK_TYPE_NONE, 0);

  gtk_object_class_add_signals (object_class, rubberband_signals, 
				LAST_SIGNAL);

  klass->moved     = NULL;
  klass->snapped   = NULL;
  klass->cancelled = NULL;
  
  parent_class = gtk_type_class (gtk_object_get_type ());

  object_class->destroy = gnome_canvas_rubberband_destroy;
  object_class->set_arg = gnome_canvas_rubberband_set_arg;
  object_class->get_arg = gnome_canvas_rubberband_get_arg;
}

static void
gnome_canvas_rubberband_init (GnomeCanvasRubberband *rubberband)
{
  rubberband->item     = NULL;
  rubberband->rect     = NULL;

  rubberband->x_origin   =
    rubberband->y_origin = 0.0;

  rubberband->snap_func      = NULL;
  rubberband->snap_func_data = NULL;
}

static void
reorder(double* a, double* b)
{
  double tmp;
  if (*a > *b) {
    tmp = *a;
    *a = *b;
    *b = tmp;
  }
}

GnomeCanvasRubberband* 
gnome_canvas_rubberband_new(GnomeCanvasItem* item,
			    gdouble      x,
			    gdouble      y,
			    guint32      event_time,
			    GnomeCanvasSnapFunc snap_func,
			    gpointer            snap_func_data)
{
  GdkCursor* ptr;
  GnomeCanvasRubberband* rubberband;

  rubberband = 
    GNOME_CANVAS_RUBBERBAND(gtk_type_new(gnome_canvas_rubberband_get_type()));

  rubberband->snap_func      = snap_func;
  rubberband->snap_func_data = snap_func_data;

  if (snap_func) 
    {
      gnome_canvas_item_w2i(item, &x, &y);
      (*snap_func)(&x, &y, snap_func_data);
    }    
  
  rubberband->x_origin   = x;

  rubberband->y_origin   = y;

  rubberband->item = item;

  /* Notice: we create the rubberband with the same parent as item, 
     so rubberband has the same item coordinates; then we can connect
     our event handler to the rubberband. The code depends on this. */
  rubberband->rect =  gnome_canvas_item_new(GNOME_CANVAS_GROUP(item->parent),
					    gnome_canvas_rect_get_type(), 
					    "outline_color", "white",
					    "width_pixels", (guint)1,
					    "x1", x, "y1", y, 
					    "x2", x, "y2", y,
					    NULL);
  
  /* We are now going to assume the rect item is realized. */
  if (GNOME_CANVAS_RE(rubberband->rect)->outline_gc) 
    {
      gdk_gc_set_function (GNOME_CANVAS_RE(rubberband->rect)->outline_gc, 
			   GDK_XOR);
    }
  else 
    {
      g_warning("Rubberband rectangle not realized; not using XOR draw");
    }
  
  ptr = gdk_cursor_new(GDK_HAND2);
  
  gnome_canvas_item_grab(rubberband->rect,
			 GDK_POINTER_MOTION_MASK | 
			 GDK_BUTTON_RELEASE_MASK | 
			 GDK_BUTTON_PRESS_MASK,
			 ptr,
			 event_time);
  
  gdk_cursor_destroy(ptr);
  
  gtk_signal_connect(GTK_OBJECT(rubberband->rect), 
		     "event",
		     GTK_SIGNAL_FUNC(item_event),
		     rubberband);
  
  return rubberband;
}


static gint 
item_event(GnomeCanvasItem* item, GdkEvent* event, 
	   GnomeCanvasRubberband* rubberband)
{  
  double x1, y1, x2, y2;

  switch (event->type) 
    {
    case GDK_BUTTON_PRESS:
      /* I don't understand why we get button 1 press events,
	 but whatever. */
      if (event->button.button == 1) 
	return FALSE;
      
      rubberband->last_time = event->button.time;
      
      gtk_signal_emit(GTK_OBJECT(rubberband), 
		      rubberband_signals[CANCELLED]);
      break;
      
    case GDK_MOTION_NOTIFY:
      x2 = event->button.x;
      y2 = event->button.y;
      gnome_canvas_item_w2i(item, &x2, &y2);
      
      if (rubberband->snap_func)
	(*rubberband->snap_func)(&x2, &y2, rubberband->snap_func_data);
      
      x1 = rubberband->x_origin;
      y1 = rubberband->y_origin;
      
      reorder(&x1, &x2);
      reorder(&y1, &y2);
      
      gnome_canvas_item_set(rubberband->rect, "x1", x1, "y1", y1, 
			    "x2", x2, "y2", y2, NULL);
      
      rubberband->last_time = event->motion.time;
      
      gtk_signal_emit(GTK_OBJECT(rubberband), 
		      rubberband_signals[MOVED],
		      x1, y1, x2, y2);
      break;
      
    case GDK_BUTTON_RELEASE:
      if (event->button.button != 1) return FALSE;
      
      x2 = event->button.x;
      y2 = event->button.y;
      gnome_canvas_item_w2i(item, &x2, &y2);
      
      if (rubberband->snap_func)
	(*rubberband->snap_func)(&x2, &y2, rubberband->snap_func_data);
      
      x1 = rubberband->x_origin;
      y1 = rubberband->y_origin;
      
      rubberband->last_time = event->button.time;
      
      reorder(&x1, &x2);
      reorder(&y1, &y2);
      
      gtk_signal_emit(GTK_OBJECT(rubberband), 
		      rubberband_signals[SNAPPED],
		      x1, y1, x2, y2);
      break;
      
    default:
      return FALSE;
    }
  
  return TRUE;
}

static void
gnome_canvas_rubberband_destroy (GtkObject *object)
{
  GnomeCanvasRubberband *rubberband;
  
  g_return_if_fail (object != NULL);
  g_return_if_fail (GNOME_IS_CANVAS_RUBBERBAND (object));
  
  rubberband = GNOME_CANVAS_RUBBERBAND (object);
  
  if (rubberband->rect) 
    {
      gtk_object_destroy(GTK_OBJECT(rubberband->rect));
      rubberband->rect = NULL;
    }
  
  gnome_canvas_item_ungrab(rubberband->item, rubberband->last_time);
  
  rubberband->item = NULL;
  
  if (GTK_OBJECT_CLASS (parent_class)->destroy)
    (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
}

static void
gnome_canvas_rubberband_set_arg (GtkObject *object, GtkArg *arg, guint arg_id)
{
  GnomeCanvasItem *item;
  GnomeCanvasRubberband *rubberband;
  
  item = GNOME_CANVAS_ITEM (object);
  rubberband = GNOME_CANVAS_RUBBERBAND (object);
  
  /* Someday useful? */
  
  switch (arg_id) 
    {
      
    default:
      g_warning("GnomeCanvasRubberband got an unknown arg type.");
      break;
    }
}


static void
gnome_canvas_rubberband_get_arg (GtkObject *object, GtkArg *arg, guint arg_id)
{
  GnomeCanvasRubberband *rubberband;

  rubberband = GNOME_CANVAS_RUBBERBAND (object);

  /* Someday useful? */

  switch (arg_id) 
    {

    default:
      arg->type = GTK_TYPE_INVALID;
      break;
    }
}


typedef void (*GtkSignal_NONE__DOUBLE_DOUBLE_DOUBLE_DOUBLE) (GtkObject * object,
							     gdouble arg1,
							     gdouble arg2,
							     gdouble arg3,
							     gdouble arg4,
							     gpointer user_data);

static void gtk_marshal_NONE__DOUBLE_DOUBLE_DOUBLE_DOUBLE (GtkObject * object,
							   GtkSignalFunc func,
							   gpointer func_data,
							   GtkArg * args)
{
  GtkSignal_NONE__DOUBLE_DOUBLE_DOUBLE_DOUBLE rfunc;
  rfunc = (GtkSignal_NONE__DOUBLE_DOUBLE_DOUBLE_DOUBLE) func;
  (*rfunc) (object,
	    GTK_VALUE_DOUBLE (args[0]),
	    GTK_VALUE_DOUBLE (args[1]),
	    GTK_VALUE_DOUBLE (args[2]),
	    GTK_VALUE_DOUBLE (args[3]),
	    func_data);
}

