/*
**  GXSNMP - An snmp managment application
**  Copyright (C) 1998 Gregory McLean
**
**  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, Cambridge, MA 02139, USA.
**
**  The map widget
*/

#ifdef HAVE_CONFIG
#include <config.h>
#endif
#include <gnome.h>
#include <math.h>
#include "dbapi.h"
#include "gxsnmp/gxsnmp_dbapi.h"
#include "gxsnmp_map.h"
#include "gxsnmp_map_item.h"
#include "gxsnmp_window.h"

#include "debug.h"

enum {
  MAP_ARG_0,
  MAP_ARG_WIDTH,
  MAP_ARG_HEIGHT,
  MAP_ARG_VWIDTH,
  MAP_ARG_VHEIGHT,
  MAP_ARG_GRID_X,
  MAP_ARG_GRID_Y,
  MAP_ARG_NAME
};
/*****************************************************************************
 * The 50% grey stipple for the rubberbanding rectangle
 ***************************************************************************/
#define gray50_width  2
#define gray50_height 2
static const char gray50_bits[] = {
  0x02, 0x01
};

/******************************************************************************
**
**  Forward references
**
******************************************************************************/

static void	map_class_init          (GXsnmp_mapClass  * klass);
static void	map_init                (GXsnmp_map       * map);
static void 	map_map_destroy             (GtkObject        * object);
static void	map_set_arg             (GtkObject        * object,
					 GtkArg           * arg,
					 guint		    arg_id);
static void	map_get_arg             (GtkObject        * object,
					 GtkArg           * arg,
					 guint              arg_id);
static gint	map_enter_event  	(GtkWidget        * widget,
					 GdkEvent         * e,
					 gpointer           data);
static gint	map_leave_event  	(GtkWidget	  * widget,
					 GdkEvent	  * e,
					 gpointer	    data);
static gint	map_mouse        	(GXsnmp_map	  * map,
                                  	 GdkEvent  	  * event,
                                  	 gpointer    	    data);
static gint     map_key_press           (GXsnmp_map       * map,
					 GdkEventKey      * e,
					 gpointer           data);
static gint     map_canvas_button       (GtkWidget        * widget,
					 GdkEventButton   * event);
static gint     map_scroll_timeout      (gpointer           data);
G_INLINE_FUNC void map_check_scroll     (GXsnmp_map       * map,
					 GdkEventMotion   * event);
G_INLINE_FUNC void map_snap 		(GXsnmp_map       * map, 
					 gdouble 	  * x, 
					 gdouble 	  * y);


/******************************************************************************
**
**  gxsnmp_map_get_type ()
**
******************************************************************************/

GtkType
gxsnmp_map_get_type()
{
  static GtkType map_type = 0;

  if (!map_type)
    {
      GtkTypeInfo map_info =
      {
        "GXsnmp_map",
        sizeof (GXsnmp_map),
        sizeof (GXsnmp_mapClass),
        (GtkClassInitFunc) map_class_init,
        (GtkObjectInitFunc) map_init,
        /* reserved 1 */ NULL,
        /* reserved 2 */ NULL,
	(GtkClassInitFunc) NULL,
      };
      map_type = gtk_type_unique (gnome_canvas_get_type (), &map_info);
    }
  return map_type;
}

/******************************************************************************
**
**  The class initialization subroutine
**
******************************************************************************/

static GnomeCanvas *parent_class;

static void
map_class_init (GXsnmp_mapClass *klass)
{
  GtkObjectClass    *object_class;
  GtkWidgetClass    *widget_class;
  D_FUNC_START;
  object_class = (GtkObjectClass *)klass;
  widget_class = (GtkWidgetClass *)klass;

  parent_class = gtk_type_class (gnome_canvas_get_type ());

  /* Add the arg types here w/gtk_arg_add_arg_type () */

  gtk_object_add_arg_type ("GXsnmp_map::name", 
			   GTK_TYPE_STRING, 
			   GTK_ARG_READWRITE, 
			   MAP_ARG_NAME);
  gtk_object_add_arg_type ("GXsnmp_map::width",
			   GTK_TYPE_INT,
			   GTK_ARG_READWRITE,
			   MAP_ARG_WIDTH);
  gtk_object_add_arg_type ("GXsnmp_map::height",
			   GTK_TYPE_INT,
			   GTK_ARG_READWRITE,
			   MAP_ARG_HEIGHT);
  /* Object methods */
  object_class->destroy      = map_map_destroy;
  object_class->set_arg      = map_set_arg;
  object_class->get_arg      = map_get_arg;
  /* Widget methods */
  /* Actually with a little thought we could override alot of the
   * widget methods and get a bit more efficent re-use of code... 
   */
  widget_class->button_press_event        = map_canvas_button;
  /* Class methods */
  D_FUNC_END;
}

static void 
map_set_arg (GtkObject *object, GtkArg *arg, guint arg_id)
{
  GXsnmp_map *map;
  D_FUNC_START;
  g_return_if_fail (GXSNMP_IS_MAP (object));
  map = GXSNMP_MAP (object);
  switch (arg_id)
    {
    case MAP_ARG_WIDTH:
      map->width = GTK_VALUE_INT (*arg);
      break;
    case MAP_ARG_HEIGHT:
      map->height = GTK_VALUE_INT (*arg);
      break;
    case MAP_ARG_NAME:
      gxsnmp_map_set_name (map, GTK_VALUE_STRING (*arg) ? GTK_VALUE_STRING (*arg) : "");
      break;
    default:
      break;
    }
  D_FUNC_END;
}

static void
map_get_arg (GtkObject *object, GtkArg *arg, guint arg_id)
{
  GXsnmp_map   *map;
  D_FUNC_START;
  g_return_if_fail (GXSNMP_IS_MAP (object));
  map = GXSNMP_MAP (object);
  switch (arg_id)
    {
    case MAP_ARG_WIDTH:
      GTK_VALUE_INT (*arg) = map->width;
      break;
    case MAP_ARG_HEIGHT:
      GTK_VALUE_INT (*arg) = map->height;
      break;
    case MAP_ARG_NAME:
      if (map->name)
	GTK_VALUE_STRING (*arg) = g_strdup (map->name);
      else
	GTK_VALUE_STRING (*arg) = NULL;
      break;
    default:
      arg->type = GTK_TYPE_INVALID;
      break;
    }
  D_FUNC_END;
}

static void
map_map_destroy (GtkObject *object)
{
  GXsnmp_map   *map;
  D_FUNC_START;
  g_return_if_fail (object != NULL);
  g_return_if_fail (GXSNMP_IS_MAP (object));
  map = GXSNMP_MAP (object);
  if (map->name)
    g_free (map->name);
  if (map->selected)				/* Free the selected-items */
    g_list_free (map->selected);		/* list if present */
  if (map->stipple)
    gdk_bitmap_unref (map->stipple);

  /* Call the parent method */
  if (GTK_OBJECT_CLASS (parent_class)->destroy)
    (*GTK_OBJECT_CLASS (parent_class)->destroy) (object);
  D_FUNC_END;
}

/*****************************************************************************
**
**  The widget initialization subroutine
**
*****************************************************************************/

static void
map_init (GXsnmp_map *map)
{
  D_FUNC_START;
  g_return_if_fail (map != NULL);
  g_return_if_fail (GXSNMP_IS_MAP (map));
/* 
**  Do the widget type initialization.
*/

  map->selected   = NULL;		/* No items are selected */
  map->rubberband = NULL;		/* We have no rubberband rectangle */
  map->x1 = map->y1 = 0.0;		/* The selection area is 0,0,0,0 */
  map->x2 = map->y2 = 0.0;
  map->grid_x       = 6.0;
  map->grid_y       = 6.0;
  map->width        = 6000;
  map->height       = 6000;
  map->mouse_state  = MOUSE_STATE_NONE;	/* The mouse is in the base state */
  map->map_state    = MAP_STATE_NONE;   /* Map is in the base state */
  map->name 	    = NULL;		/* This map is unnamed */
  map->timer_tag    = -1;               /* We have no scroll timer running. */
  map->do_snapping  = TRUE;
  map->stipple      = gdk_bitmap_create_from_data(NULL, gray50_bits,
						  gray50_width, gray50_height);
  gtk_widget_set_name (GTK_WIDGET (map), "gxsnmp_map");	  /* For RC styles */

  gtk_signal_connect (GTK_OBJECT (map), "enter_notify_event",
		      (GtkSignalFunc) map_enter_event, NULL);
  gtk_signal_connect (GTK_OBJECT (map), "key_press_event",
		      (GtkSignalFunc) map_key_press, NULL);
  gtk_signal_connect (GTK_OBJECT (map), "leave_notify_event",
		      (GtkSignalFunc) map_leave_event, NULL);
  gtk_signal_connect (GTK_OBJECT (map), "event",
		      (GtkSignalFunc) map_mouse, NULL);
  gxsnmp_map_set_geometry (map, map->width, map->height);
  GTK_WIDGET_SET_FLAGS (map, GTK_CAN_FOCUS);
  gtk_widget_grab_focus (GTK_WIDGET (map));
  gtk_widget_show_all (GTK_WIDGET (map));
  d_print (DEBUG_TRACE, "Created new map, %s\n", map->name);
  D_FUNC_END;
}

/******************************************************************************
**
**  Private function to select an item, invoking the select method.
**
**  If the select method returns TRUE, then the item is selectable.
**  If the select method returns FALSE, then the item is not selectable.
**
******************************************************************************/

static void
map_select (GXsnmp_map * map, GXsnmp_map_item * item)
{
  D_FUNC_START;
  if (g_list_find (map->selected, item))  /* If item is already selected on */
    {
      d_print (DEBUG_TRACE, "Item already selected.\n");
      D_FUNC_END;
      return;				  /* this map then quickly return */
    }
  d_print (DEBUG_TRACE, "Invoking select method.\n");
  if (gxsnmp_map_item_select (GXSNMP_MAP_ITEM (item)))
    {
      map->selected  = g_list_append (map->selected, item);
      item->selected = g_list_append (item->selected, map);
    }
  D_FUNC_END;
}

/*****************************************************************************
**
**  Private function to deselect an item, invoking the select method.
**
**  If the select method returns TRUE, then the item is deselectable.
**  If the select method returns FALSE, then the item is not deselectable.
**
*****************************************************************************/

static void
map_deselect (GXsnmp_map * map, GXsnmp_map_item * item)
{
  D_FUNC_START;
  if (!g_list_find (map->selected, item))  /* If item is not selected on */
    {
      d_print (DEBUG_TRACE, "Item was not selected.");
      D_FUNC_END;
      return;				   /* this map then quickly return */
    }
  d_print (DEBUG_TRACE, "Invoking deselect method.\n");
  if (gxsnmp_map_item_deselect (GXSNMP_MAP_ITEM (item)))
    {
      map->selected  = g_list_remove (map->selected, item);
      item->selected = g_list_remove (item->selected, map);
    }
  D_FUNC_END;
}

/******************************************************************************
**
**  Private function to deselect all items in one swell foop.
**
******************************************************************************/

static void
map_deselect_all (GXsnmp_map * map)
{
  D_FUNC_START;
  while (map->selected)
    map_deselect (map, GXSNMP_MAP_ITEM (map->selected->data));
  D_FUNC_END;
}

/******************************************************************************
**
**  Private function to decide if two rectangles overlap
**
******************************************************************************/

G_INLINE_FUNC gboolean
map_overlap (gdouble x1, gdouble y1, gdouble x2, gdouble y2,  /* Rectangle 1 */
	     gdouble x3, gdouble y3, gdouble x4, gdouble y4)  /* Rectangle 2 */
{
  gboolean xoverlap, yoverlap;

  xoverlap = ((x1 > x3) && (x1 < x4)) ||
	     ((x2 > x3) && (x2 < x4)) ||
	     ((x3 > x1) && (x3 < x2)) || 
	     ((x4 > x1) && (x4 < x2));

  yoverlap = ((y1 > y3) && (y1 < y4)) ||
	     ((y2 > y3) && (y2 < y4)) ||
	     ((y3 > y1) && (y3 < y2)) || 
	     ((y4 > y1) && (y4 < y2));

  return xoverlap && yoverlap;
}

/******************************************************************************
**
**  This callback is invoked whenever the mouse enters the map area.
**  It ensures that the map canvas always has focus so that the keyboard  
**  shortcuts always work.
**
******************************************************************************/

static gint
map_enter_event (GtkWidget * widget, GdkEvent * e, gpointer data)
{
  D_FUNC_START;
  gtk_widget_grab_focus (widget);
  D_FUNC_END;
  return FALSE;
}

/*******************************************************************************
**
**  This callback is invoked whenever the mouse leaves the canvas area.
**  FIXME: It should somehow remove the ruler position arrows.
**
*******************************************************************************/

static gint
map_leave_event (GtkWidget * widget, GdkEvent * e, gpointer data)
{
  GnomeCanvas    *map_canvas;
  GXsnmp_map     *map;
  D_FUNC_START;
  g_return_val_if_fail (widget != NULL, FALSE);
  g_return_val_if_fail (GXSNMP_IS_MAP (widget), FALSE);
  map = GXSNMP_MAP (widget);
  map_canvas = GNOME_CANVAS (widget);
  switch (map->mouse_state)
    {
    case MOUSE_STATE_LEFTCLICK:
      /* do nothing? */
      break;
    case MOUSE_STATE_RUBBERBAND:
    case MOUSE_STATE_DRAGGING:
      /* We are dragging and we want to scroll the canvas..
       * Best way to handle this is to set a flag to let the motion_notify
       * function know that it needs to scroll the canvas along with the
       * rest of its actions.
       */
      map->map_state = MAP_STATE_NEED_DRAG;
      break;
    default:
      break;
    }
  D_FUNC_END;
  return FALSE;
}
/****************************************************************************
 * Our private button_press_handler.
 * Idea here is we call the parent class handler, if that returns true 
 * event was dealt with don't do anything. Else invoke the code in this
 * function. This is a better way to seperate out the events and have
 * events on items do one thing while events on the canvas do other things.
 * What we can also do is over ride _each_ method if needed...
 */
static gint
map_canvas_button (GtkWidget *widget, GdkEventButton *e)
{
  D_FUNC_START;
  /* Call the parent method and see if it was handled there 
   * Most likely this will be a gnome_canvas_item
   */
  if (!(GTK_WIDGET_CLASS (parent_class)->button_press_event (widget, e)))
    {
      if (e->type == GDK_BUTTON_PRESS)
	{
	  if (e->button == 3)
	    {
	      D_FUNC_END;
	      return menu_raise_map_popup ((GdkEventButton *)e, NULL);
	    }
	}
    }
  D_FUNC_END;
  return FALSE;
}

/****************************************************************************
 * The following two routines handle the auto scroll of the canvas.
 * scroll_timeout is hackish, but works.
 */
static gint
map_scroll_timeout (gpointer data)
{
  GXsnmp_map     *map;
  GtkAdjustment  *vadj;
  GtkAdjustment  *hadj;
  int            hvalue, vvalue;
  
  D_FUNC_START;
  g_return_val_if_fail (data != NULL, FALSE);
  g_return_val_if_fail (GXSNMP_IS_MAP (data), FALSE);
  GDK_THREADS_ENTER ();
  map = (GXsnmp_map *)data;
  vadj = GTK_LAYOUT (map)->vadjustment;
  /* vertical */
  vvalue = vadj->value + map->vvalue_diff;
  if (vvalue > vadj->upper - vadj->page_size)
      vvalue = vadj->upper - vadj->page_size;

  gtk_adjustment_set_value (vadj, vvalue);

  hadj = GTK_LAYOUT (map)->hadjustment;
  /* horizontal */
  hvalue = hadj->value + map->hvalue_diff;
  if (hvalue > hadj->upper - hadj->page_size)
    hvalue = hadj->upper - hadj->page_size;
  gtk_adjustment_set_value (hadj, hvalue);

  GDK_THREADS_LEAVE ();
  D_FUNC_END;
  return TRUE;
}
/****************************************************************************
 * Check the pointer location relative to the map canvas and determine if
 * A scroll is warranted.
 */
G_INLINE_FUNC void
map_check_scroll (GXsnmp_map *map, GdkEventMotion *event)
{
  /* Vertical */
  if (event->y < 0 || event->y > GTK_WIDGET (map)->allocation.height)
    {
      if (map->timer_tag == -1)
	map->timer_tag = gtk_timeout_add (30, map_scroll_timeout, map);
      if (event->y < 0)
	map->vvalue_diff = event->y;
      else
	map->vvalue_diff = event->y - GTK_WIDGET (map)->allocation.height;
      map->event_last_x = event->x;
      map->event_last_y = event->y;
      /*
       * Make the steppings be relative to the mouse distance
       * from the canvas.  Also notice the timeout above is small
       * to give a more smooth movement
       */
      map->vvalue_diff /= 5;
    }
  /* Horizontal scroll */
  else if (event->x < 0 || event->x > GTK_WIDGET (map)->allocation.width)
    {
      if (map->timer_tag == -1)
	map->timer_tag = gtk_timeout_add (30, map_scroll_timeout, map);
      if (event->x < 0)
	map->hvalue_diff = event->x;
      else
	map->hvalue_diff = event->x - GTK_WIDGET (map)->allocation.width;
      map->event_last_x = event->x;
      map->event_last_y = event->y;
      /*
       * Make the steppings be relative to the mouse distance
       * from the canvas.  Also notice the timeout above is small
       * to give a more smooth movement
       */
      map->hvalue_diff /= 5;
    }
  else
    {
      if (map->timer_tag != -1) 
	{
	  gtk_timeout_remove (map->timer_tag);
	  map->timer_tag = -1;
	}
    }
  
}

/******************************************************************************
**
**  map_snap -- called to "snap-to-grid" a point
**
******************************************************************************/

G_INLINE_FUNC void
map_snap (GXsnmp_map * map, gdouble * x, gdouble * y)
{
  if (!map->do_snapping)
    return;

  *x -= fmod (*x + map->grid_x / 2.0, map->grid_x);
  *y -= fmod (*y + map->grid_y / 2.0, map->grid_y);
}

/******************************************************************************
**
**  map_mouse -- Callback to implement left mouse button functions on the
**	  	 map canvas.
**
**  This method provides the following mouse button capabilities:
**  (LEFT CLICKS)
**
**  Click over item, release -- 
**
**	The item under the mouse is selected.  
**
**  Click over item, shift key held down, release, --
**
**      The item under the mouse is added to the selection list.
**
**  Click over canvas, release --
**
**	Any selected items are deselected.
**
**  Click over canvas, drag, release -- 
**
**	A rectangle is drawn as the mouse is dragged.  Any items that overlap
**	the rectangle are selected.  Any items that fall outside of the 
**	rectangle are deselected.  When the button is released, the rectangle
**	is hidden and the select list is frozen.
**
**  Click over item, drag, release -- 
**
**	All items on the select list are moved along with the mouse pointer.
**	When the mouse button is released, the items are dropped, but remain
**	selected.
**
**  The map_mouse callback should be attached to the "event" signal of the
**  gnome_canvas.
**
**  The map_mouse_select callback should be attached to the "event" signal of
**  all selectable items in the canvas.
**
******************************************************************************/

static gint           
map_mouse (GXsnmp_map * map, GdkEvent * event, gpointer data)
{
  GnomeCanvas       *map_canvas;
  GnomeCanvasGroup  *root_group;
  GnomeCanvasItem   *item;
  GdkCursor         *cursor;
  GList		    *gl;
  gint		     ix, iy;
  gdouble  	     cx, cy, 
		     x3, y3, 
		     x4, y4, 
		     dx, dy,
                     sx, sy;

  D_FUNC_START;
  g_return_val_if_fail (map != NULL, FALSE);
  /*
  **  Compute some values that are needed for nearly every callback instance:
  */

  map_canvas = GNOME_CANVAS (map);
  root_group = gnome_canvas_root (map_canvas);


  gnome_canvas_get_scroll_offsets (map_canvas,     /* Correction values for */
                                   &ix, &iy);      /* the scrolling and */
  cx = ix * map_canvas->pixels_per_unit;       	   /* scaling amounts */
  cy = iy * map_canvas->pixels_per_unit;    

/*
**  The following block is executed whenever the user left-clicks the mouse.
**
**  Because the canvas receives the GDK_BUTTON_PRESS signal before the
**  canvas_item does, we can't yet tell if the user has left-clicked on the 
**  canvas or on a canvas_item.  Fortunately, we don't need to.  All we 
**  need to do is store the X/Y location, and change the mouse_state to 
**  MOUSE_STATE_LEFTCLICK.  The decision as to what to do next is deferred 
**  until the first motion event.
*/

  /* Handle the initial mouse clicks */
  switch (event->type)
    {
    case GDK_BUTTON_PRESS:
      switch (event->button.button)
	{
	case 1:
	  /* A click somewhere on the canvas, store the point where
	   * the click occured, then set the map mode. Return at this point
	   * as we are done with the event.
	   */
	  map->x1 = map->x2 = event->button.x + cx;	 
	  map->y1 = map->y2 = event->button.y + cy;	 
	  map->mouse_state = MOUSE_STATE_LEFTCLICK;
	  D_FUNC_END;
	  return FALSE;				   
	  break;
	case 2:
	  /* Set up for doing scrolling around with the middle button. 
	   * (Or that silly wheel on the new MS mouses)
	   */
	  break;
	case 3:
	  /* Button 3 will do a popup menu for the canvas.  Selected items
	   * should deal with this beforehand and stop the emission of the
	   * signal.  However, if the user right-clicks on the background
	   * canvas, then selects 'add host' or 'add network', then we 
	   * need to remember the mouse click location so that the new host
	   * or network can be correctly placed.  Hence, we save the
	   * current mouse coordinates in x1 and y1 for use by the menu
	   * callback.
	   */
	  map->x1 = event->button.x + cx;
	  map->y1 = event->button.y + cy;
          break;
	default:
	  break;   /* keep gcc pacified. */
	}
      break;
    case GDK_MOTION_NOTIFY:
      switch (map->mouse_state)
	{
	case MOUSE_STATE_LEFTCLICK:
	  if (map->rubberband)
	    {
	      /* reset to a 0x0 rect and then show it, eliminates the
	       * flicker of the old box. */
	      gnome_canvas_item_set (map->rubberband,
				     "x1", map->x1,
				     "y1", map->y1,
				     "x2", map->x2,
				     "y2", map->y2,
				     NULL);
	      gnome_canvas_item_show (map->rubberband);
	    }
	  else
	    map->rubberband = gnome_canvas_item_new 
	                      (root_group,
			       gnome_canvas_rect_get_type (),
			       "outline_color", "black",
			       "width_pixels", 1, 
			       "outline_stipple", map->stipple,
			       NULL);
	  cursor = gdk_cursor_new (GDK_CROSSHAIR);
	  gnome_canvas_item_grab (map->rubberband,	
				  GDK_POINTER_MOTION_MASK | 
				  GDK_BUTTON_RELEASE_MASK,  
			      cursor, event->motion.time);
	  gdk_cursor_destroy (cursor);
	  map->mouse_state = MOUSE_STATE_RUBBERBAND;
	  map_check_scroll (map, (GdkEventMotion *)event);
	  D_FUNC_END;
	  return FALSE;
	  break;                                   /* start ruberbanding */
	case MOUSE_STATE_RUBBERBAND:
	  map->x2 = event->motion.x + cx;
	  map->y2 = event->motion.y + cy;
	  x3 = MIN (map->x1, map->x2);
	  y3 = MIN (map->y1, map->y2);
	  x4 = MAX (map->x1, map->x2);
	  y4 = MAX (map->y1, map->y2);
	  gnome_canvas_item_set (map->rubberband, 
				 "x1", x3, "y1", y3,
				 "x2", x4, "y2", y4,
				 NULL);
	  gl = root_group->item_list;		     
	  while (gl) 
	    {
	      item = GNOME_CANVAS_ITEM (gl->data);	    
	      if (GXSNMP_IS_MAP_ITEM (item))
		{
		  if (map_overlap (x3, y3, x4, y4,
				   item->x1, item->y1, 
				   item->x2, item->y2))
		    map_select (map, GXSNMP_MAP_ITEM(item)); 
		  else
		    map_deselect (map, GXSNMP_MAP_ITEM(item)); 
		}
	      gl = gl->next;
	    }
	  map_check_scroll (map, (GdkEventMotion *)event);
	  D_FUNC_END;
	  return FALSE;
	  break;                                /* rubberbanding */
	case MOUSE_STATE_DRAGGING:              /* dragging item(s) */
          x3 = event->motion.x + cx;		/* Obtain the current */
	  y3 = event->motion.y + cy;		/* mouse location */
	  map_snap (map, &x3, &y3);             /* the desired position */
	  dx = x3 - map->x2;			/* gnome_canvas_item_move */
	  dy = y3 - map->y2;			/* wants dx and dy, so we */  
	  map->x2 = x3;              	        /* need to keep track of */
	  map->y2 = y3;             		/* the last mouse location. */
	  gl = map->selected;			/* Move all selected items */
	  sx = dx; sy = dy;                     /* save the orginal offset */
	  while (gl)
	    {
	      gdouble  ax, ay,
	               rx, ry;
	     
	      ax = ay = rx = ry = 0.0;
	      /* dx and dy at the point have the snapped position offset
	       * That each item needs to move. now we need to compute
	       * the individual item's proper snapped position. And the
	       * snapped delta to move to.
	       */
	      gxsnmp_map_item_get_location (GXSNMP_MAP_ITEM (gl->data),
					    &rx, &ry);
	      ax = rx;
	      ay = ry;
	      rx += dx;
	      ry += dy;
	      /* we have the new location Lets snap to that*/
	      map_snap (map, &rx, &ry);
	      dx = rx - ax;
	      dy = ry - ay;
	      gtk_signal_emit_by_name (GTK_OBJECT 
				       (GNOME_CANVAS_ITEM (gl->data)), 
				       "move", dx, dy);
	      dx = sx; dy = sy;                   /* restore the orginal */
	      gl = gl->next;
	    }
	  map_check_scroll (map, (GdkEventMotion *)event);
	  D_FUNC_END;
	  return FALSE;			    
	  break;
	default:  /* Hush gcc */
	  break;
	}
    case GDK_BUTTON_RELEASE:
      if (map->timer_tag != -1)                 /* Stop scrolling the map */
	{
	  gtk_timeout_remove (map->timer_tag);
	  map->timer_tag = -1;
	}
      switch (map->mouse_state)			/* Four different states */
	{
        case MOUSE_STATE_LEFTCLICK:		/* was in LEFTCLICK state */
          map_deselect_all (map);               /* Deselect everything */
	  break;				/* and continue */
        case MOUSE_STATE_RUBBERBAND:             /* was in RUBBERBAND state */
          gnome_canvas_item_hide (map->rubberband);      /* Hide and ungrab */
          gnome_canvas_item_ungrab (map->rubberband,     /* the rubberband */
			            event->button.time); 
	  break;
        case MOUSE_STATE_DRAGGING:		  /* was in DRAGGING state */
	  gnome_canvas_item_ungrab (map->grabbed,  	/* Ungrab the */
                                  event->button.time);  /* grabbed item */
	  break;
	default:
	  break;
	}	 /* switch (map->mouse_state) */
      map->grabbed     = NULL;
      map->mouse_state = MOUSE_STATE_NONE;   /* Reset the mouse mode */
      map->map_state   = MAP_STATE_NONE;
      break;
    default:
      break;
    }
  D_FUNC_END;
  return FALSE;	
}
/****************************************************************************
 * Keypress handling for keyboard shortcuts.
 */
static gint
map_key_press (GXsnmp_map *map, GdkEventKey *e, gpointer data)
{
  GnomeCanvas  *map_canvas;
  gint         x, y;
  gdouble      xx, yy;
  gint         offset;
  gboolean     stop;
  D_FUNC_START;
  map_canvas = GNOME_CANVAS (map);
  gnome_canvas_get_scroll_offsets (map_canvas, &x, &y);
  offset = (e->state && GDK_SHIFT_MASK) ? 40 : 20;
  stop = FALSE;
  /* For some screwy reason we don't/can't get keypress status in the mouse
   * button event handler... so we cache it here and use our own.. 
   */
  switch (e->keyval)
    {
    case 0x3d:             /* '+' (zoom in) */
    case GDK_KP_Add:
      gxsnmp_map_zoom_in (map);
      break;
    case 0x2d:             /* '-' (zoom out) */
    case GDK_KP_Subtract:
      gxsnmp_map_zoom_out (map);
      break;
      /* Scroll around the canvas */
    case GDK_Up:
    case GDK_KP_Up:
      gnome_canvas_scroll_to (map_canvas, x, y - offset);
      stop = TRUE;
      break;
    case GDK_Down:
    case GDK_KP_Down:
      stop = TRUE;
      gnome_canvas_scroll_to (map_canvas, x, y + offset);
      break;
    case GDK_Left:
    case GDK_KP_Left:
      stop = TRUE;
      gnome_canvas_scroll_to (map_canvas, x - offset, y);
      break;
    case GDK_Right:
    case GDK_KP_Right:
      stop = TRUE;
      gnome_canvas_scroll_to (map_canvas, x + offset, y);
      break;
    case GDK_Page_Up:
    case GDK_KP_Page_Up:
      gnome_canvas_scroll_to (map_canvas, x, 
			      y - GTK_WIDGET (map)->allocation.height);
      stop = TRUE;
      break;
    case GDK_Page_Down:
    case GDK_KP_Page_Down:
      gnome_canvas_scroll_to (map_canvas, x,
			      y + GTK_WIDGET (map)->allocation.height);
      stop = TRUE;
      break;
    case GDK_Home:
    case GDK_KP_Home:
      gnome_canvas_scroll_to (map_canvas, 0, 0);
      stop = TRUE;
      break;
    case GDK_End:
    case GDK_KP_End:
      gnome_canvas_get_scroll_region (map_canvas, NULL, NULL, &yy, &xx);
      gnome_canvas_scroll_to (map_canvas, (gint)xx, (gint)yy);
      stop = TRUE;
      break;
    }
  /* We need to stop the emission of the signal or we will loose focus on
   * the map canvas. But only stop the emission if we handled the keypress.
   */
  if (stop)
    gtk_signal_emit_stop_by_name (GTK_OBJECT (map), "key_press_event");
  D_FUNC_END;
  return stop;
}

/******************************************************************************
**
**  Callback to implement mouse clicks on an item
**
**  This callback is attached to the "event" signal of each GXsnmp_map_item.
**  It is used to differentiate between left-clicks on an item, and
**  left-clicks on the canvas itself.
**
**  Prior to this callback, the gnome_canvas will have sent a signal to the
**  canvas.  The canvas event handler will have set map->mouse_state to
**  MOUSE_STATE_LEFTCLICK.  If the user clicked on an object, we will change
**  the mouse state to MOUSE_STATE_DRAGGING.
**
******************************************************************************/
gint
gxsnmp_map_mouse_select (GXsnmp_map_item * item,
                         GdkEvent        * event,
                         gpointer          data)
{
  GXsnmp_map            *map;
  GnomeCanvas           *map_canvas;
  GnomeCanvasGroup      *root_group;
  GdkCursor             *fleur;

  D_FUNC_START;
  map_canvas = GNOME_CANVAS_ITEM (item)->canvas;
  map        = GXSNMP_MAP        (map_canvas);
  root_group = gnome_canvas_root (map_canvas);
  switch (event->type)
    {
    case GDK_BUTTON_PRESS:
      switch (event->button.button)
	{
	case 1:
	  g_assert (map->mouse_state == MOUSE_STATE_LEFTCLICK);
	  if (item->selected && event->button.state && GDK_MOD1_MASK)
	    map_deselect (map, item);
	  else if (!item->selected)                 /* Clicked on an unselected */
	    {					/*   object? */
	      if (!event->button.state && GDK_SHIFT_MASK) 
		map_deselect_all (map);               /* Yes - Select just this */
	      map_select (map, item);               /*    item */
	    }
	  if (!map->grabbed)
	    {
	      /* Remember what we selected, because we also need to grab on it
	       * or we loose motion and release events when the mouse leaves the
	       * canvas area. BUT we only do a grab on one item (the first one)
	       * This only becomes an issue when manually adding to the selection
	       * list. Also we set the mouse pointer for visual cues on dragging.
	       */
	      map->grabbed = GNOME_CANVAS_ITEM (item);
	      fleur = gdk_cursor_new (GDK_FLEUR);
	      gnome_canvas_item_grab (map->grabbed,                   
				  GDK_POINTER_MOTION_MASK |       
				      GDK_BUTTON_RELEASE_MASK,        
				      fleur, event->motion.time);     
	      gdk_cursor_destroy (fleur);
	    }
	  map->mouse_state = MOUSE_STATE_DRAGGING;
	  break;
	case 2:
	  /* Middle button on an object. */
	  break;
	case 3:
	  /* Item specific popup */
	  if ( GXSNMP_MAP_ITEM_CLASS (GTK_OBJECT (item)->klass)->popup_menu )
	    {
	      d_print (DEBUG_TRACE, "Invoking item popup_menu method.");
	      D_FUNC_END;
	      return (*GXSNMP_MAP_ITEM_CLASS 
		      (GTK_OBJECT(item)->klass)->popup_menu)(event, item);
	    }
	  break;
	default:
	  break;
	}
      break;
    default:
      break;
    }
  D_FUNC_END;
  return FALSE;
}

/****************************************************************************
**
**  Public function to create a new map.  The visual and colormap are
**  pushed and popped as described in gnome_canvas_new.c.
**
****************************************************************************/

GtkWidget *
gxsnmp_map_new ()
{
  GXsnmp_map * map;
  D_FUNC_START;
  gtk_widget_push_visual (gdk_rgb_get_visual ());
  gtk_widget_push_colormap (gdk_rgb_get_cmap ());
  map = gtk_type_new (gxsnmp_map_get_type ());
//  GNOME_CANVAS (map)->aa = 1;
  gtk_widget_pop_colormap ();
  gtk_widget_pop_visual ();
  D_FUNC_END;
  return GTK_WIDGET (map);
}

/******************************************************************************
**
** Public function to change the map's name
**
******************************************************************************/

void
gxsnmp_map_set_name (GXsnmp_map *map, gchar *map_name)
{
  D_FUNC_START;
  g_return_if_fail (map != NULL);
  g_return_if_fail (map_name != NULL);

  if (map->name)
    g_free (map->name);
  map->name = g_strdup (map_name);
  d_print (DEBUG_DUMP, "Map name set '%s'\n", map->name);
  D_FUNC_END;
}

/*****************************************************************************
 * Public function to set the width and height of a map
 */
void 
gxsnmp_map_set_geometry (GXsnmp_map *map, gint width, gint height)
{
  D_FUNC_START;
  g_return_if_fail (map != NULL);

  gtk_widget_set_usize (GTK_WIDGET (map), width, height);
  map->width  = width;
  map->height = height;
  gnome_canvas_set_scroll_region (GNOME_CANVAS (map),
				  0, 0, width, height);
  D_FUNC_END;
}

/****************************************************************************
 * The snap to grid callback handler.
 */
void
toggle_snap_menu_cb (GtkWidget *widget, gpointer data)
{
  GXsnmp_map    *map;
  D_FUNC_START;
  d_print (DEBUG_TRACE, "Getting map.\n");
  map = gxsnmp_window_get_current_map (GXSNMP_WINDOW (data));
  if (map)
    {
      d_print (DEBUG_TRACE, "Got map.\n");
      if (GTK_CHECK_MENU_ITEM (widget)->active)
	map->do_snapping = TRUE;
      else
	map->do_snapping = FALSE;
    }
  D_FUNC_END;
}
/****************************************************************************
 * gxsnmp_map_zoom_in -- Zoom in one step on the map.
 * Convience function 
 ***************************************************************************/
void
gxsnmp_map_zoom_in (GXsnmp_map *map)
{
  double pix = GNOME_CANVAS (map)->pixels_per_unit;
  D_FUNC_START;
  if (pix < 10.0)
    {
      pix += 0.5;
      gnome_canvas_set_pixels_per_unit (GNOME_CANVAS (map), pix);
    }
  D_FUNC_END;
}
/****************************************************************************
 * gxsnmp_map_zoom_out -- Zoom out one step on the map.
 ***************************************************************************/
void 
gxsnmp_map_zoom_out (GXsnmp_map *map)
{
  double pix = GNOME_CANVAS (map)->pixels_per_unit;
  D_FUNC_START;
  if (pix > 1.0)
    {
      pix -= 0.5;
      gnome_canvas_set_pixels_per_unit (GNOME_CANVAS (map), pix);
    }
  D_FUNC_END;
}
/* EOF */

