/* -*- Mode: C -*-
 * $Id: gxsnmp_map.c,v 1.12 2000/03/07 03:08:39 gregm Exp $
 *  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 "gxsnmp_map.h"
#include "gnome-canvas-grid.h"

#include "gnome-canvas-handle.h"
#include "gxsnmp_host_item.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
};
extern gint aa_canvas;
/******************************************************************************
 *
 *  Forward references
 *
 **/
static void	map_class_init          (GxSNMPMapClass     *klass);
static void	map_init                (GxSNMPMap          *map);
static void 	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     gxsnmp_map_key_press    (GtkWidget          *widget,
					 GdkEventKey        *event);
static gint     gxsnmp_map_button       (GtkWidget          *widget,
					 GdkEventButton     *event);
static gint     gxsnmp_map_motion       (GtkWidget          *widget,
					 GdkEventMotion     *event);
/******************************************************************************
 *
 *  gxsnmp_map_get_type ()
 *
 **/
GtkType
gxsnmp_map_get_type()
{
  static GtkType map_type = 0;
  D_FUNC_START;
  if (!map_type)
    {
      GtkTypeInfo map_info =
      {
        "GxSNMPMap",
        sizeof (GxSNMPMap),
        sizeof (GxSNMPMapClass),
        (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);
    }
  D_FUNC_END;
  return map_type;
}

/******************************************************************************
 *
 *  The class initialization subroutine
 *
 **/
static GnomeCanvas *parent_class;

static void
map_class_init (GxSNMPMapClass *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 ("GxSNMPMap::name", 
			   GTK_TYPE_STRING, 
			   GTK_ARG_READWRITE, 
			   MAP_ARG_NAME);
  gtk_object_add_arg_type ("GxSNMPMap::width",
			   GTK_TYPE_INT,
			   GTK_ARG_READWRITE,
			   MAP_ARG_WIDTH);
  gtk_object_add_arg_type ("GxSNMPMap::height",
			   GTK_TYPE_INT,
			   GTK_ARG_READWRITE,
			   MAP_ARG_HEIGHT);
  gtk_object_add_arg_type ("GxSNMPMap::grid_x",
			   GTK_TYPE_INT,
			   GTK_ARG_READWRITE,
			   MAP_ARG_GRID_X);
  gtk_object_add_arg_type ("GxSNMPMap::grid_y",
			   GTK_TYPE_INT,
			   GTK_ARG_READWRITE,
			   MAP_ARG_GRID_Y);
  /* Object methods */
  object_class->destroy      = map_destroy;
  object_class->set_arg      = map_set_arg;
  object_class->get_arg      = map_get_arg;

  /* Class methods */
  widget_class->key_press_event      = gxsnmp_map_key_press;
  widget_class->button_press_event   = gxsnmp_map_button;
  widget_class->button_release_event = gxsnmp_map_button;
  widget_class->motion_notify_event  = gxsnmp_map_motion;
  D_FUNC_END;
}
/****************************************************************************
 * set_arg handler.
 **/
static void 
map_set_arg (GtkObject *object, GtkArg *arg, guint arg_id)
{
  GxSNMPMap *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;
    case MAP_ARG_GRID_X:
      map->grid_x = GTK_VALUE_INT (*arg);
      if (map->grid)
	gnome_canvas_item_set (map->grid, "grid_x", (int)map->grid_x, NULL);
      break;
    case MAP_ARG_GRID_Y:
      map->grid_y = GTK_VALUE_INT (*arg);
      if (map->grid)
	gnome_canvas_item_set (map->grid, "grid_y", (int)map->grid_y, NULL);
      break;
    default:
      break;
    }
  D_FUNC_END;
}
/****************************************************************************
 * get_arg handler.
 **/
static void
map_get_arg (GtkObject *object, GtkArg *arg, guint arg_id)
{
  GxSNMPMap   *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;
    case MAP_ARG_GRID_X:
      GTK_VALUE_INT (*arg) = map->grid_x;
      break;
    case MAP_ARG_GRID_Y:
      GTK_VALUE_INT (*arg) = map->grid_y;
      break;
    default:
      arg->type = GTK_TYPE_INVALID;
      break;
    }
  D_FUNC_END;
}
/****************************************************************************
 * destroy
 **/
static void
map_destroy (GtkObject *object)
{
  GxSNMPMap   *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 */

  /* 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 (GxSNMPMap *map)
{
  GdkImlibImage     *im;
  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;
  map->grid_x       = 25;
  map->grid_y       = 25;
  map->grid_visible = TRUE;
 
  gtk_widget_set_name (GTK_WIDGET (map), "GxSNMPMap");	  /* For RC styles */

  gxsnmp_map_set_geometry (map, map->width, map->height);
  map->grid = gnome_canvas_item_new (gnome_canvas_root (GNOME_CANVAS (map)),
				     gnome_canvas_grid_get_type (),
				     "grid_x", (int)map->grid_x,
				     "grid_y", (int)map->grid_y,
				     NULL);
  /** Test */
  im = gdk_imlib_load_image (gnome_pixmap_file ("gxsnmp/terminal.png"));
  gnome_canvas_item_new (gnome_canvas_root (GNOME_CANVAS (map)),
			 gnome_canvas_handle_box_get_type (),
                         "x1", 10.0,
                         "y1", 10.0,
			 "x2", 20.0,
			 "y2", 20.0,
			 NULL); 
  gnome_canvas_item_new (gnome_canvas_root (GNOME_CANVAS (map)),
                         gnome_canvas_handle_line_get_type (),
                         "x1", 15.0,
                         "y1", 15.0,
                         "x2", 40.0,
                         "y2", 45.0,
                          NULL);

  gtk_widget_show_all (GTK_WIDGET (map));
  gtk_widget_grab_focus (GTK_WIDGET (map));
  d_print (DEBUG_TRACE, "Created new map, %s\n", map->name);
  D_FUNC_END;
}


/*****************************************************************************
 *
 *  Private function to decide if two rectangles overlap
 *
 **/
G_INLINE_FUNC gboolean
map_overlap (gdouble px1, gdouble py1, gdouble px2, gdouble py2,
	     gdouble px3, gdouble py3, gdouble px4, gdouble py4)
{
  gboolean xoverlap, yoverlap;

  xoverlap = ((px1 > px3) && (px1 < px4)) ||
	     ((px2 > px3) && (px2 < px4)) ||
	     ((px3 > px1) && (px3 < px2)) || 
	     ((px4 > px1) && (px4 < px2));

  yoverlap = ((py1 > py3) && (py1 < py4)) ||
	     ((py2 > py3) && (py2 < py4)) ||
	     ((py3 > py1) && (py3 < py2)) || 
	     ((py4 > py1) && (py4 < py2));

  return xoverlap && yoverlap;
}


/****************************************************************************
 * The following two routines handle the auto scroll of the canvas.
 * scroll_timeout is hackish, but works.
 */
static gint
map_scroll_timeout (gpointer data)
{
  GxSNMPMap      *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 (GxSNMPMap *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;
	}
    }
  
}
/*****************************************************************************
 * key-press-event handler.
 **/
static gint
gxsnmp_map_key_press (GtkWidget *widget, GdkEventKey *e)
{
  GnomeCanvas    *canvas;
  gint           x, y;
  gint           offset;
  D_FUNC_START;
  canvas = (GnomeCanvas *)widget;
  if (e->window != canvas->layout.bin_window)
    return FALSE;                               /* Not our event */
  offset = (e->state && GDK_SHIFT_MASK) ? 40 : 20; 
  switch (e->keyval)
    {
    case GDK_Up:
    case GDK_KP_Up:
      return TRUE;
    case GDK_Down:
    case GDK_KP_Down:
      return TRUE;
    default:
      return FALSE;
    }
  D_FUNC_END;
  return FALSE; /* Not reached? */
}
/****************************************************************************
 * gxsnmp_map_motion -- Motion notify handler.
 **/
static gint 
gxsnmp_map_motion (GtkWidget *widget, GdkEventMotion *event)
{
  GnomeCanvas   *canvas;
  GxSNMPMap     *map;

  D_FUNC_START;
  g_return_val_if_fail (widget != NULL, FALSE);
  g_return_val_if_fail (event != NULL, FALSE);
  canvas = (GnomeCanvas *)widget;
  map    = (GxSNMPMap *)widget;
  if (event->window != canvas->layout.bin_window)
    return FALSE;
  switch (map->map_state)
    {
    case MAP_STATE_NONE:
      if (GTK_WIDGET_CLASS (parent_class)->motion_notify_event)
	{
	  if ( (* GTK_WIDGET_CLASS (parent_class)->motion_notify_event) 
	       (widget, event))
	    {
	      d_print (DEBUG_DUMP, "Event processed by a child item.");
	      D_FUNC_END;
	      return TRUE;
	    }
	}
      return FALSE;
    case MAP_STATE_RUBBERBAND:
      d_print (DEBUG_DUMP, "Rubberband.\n");
      return TRUE;
    case MAP_STATE_MOUSE_SCROLL:
      d_print (DEBUG_DUMP, "Scroll..\n");
      return TRUE;
    }
  D_FUNC_END;
  return TRUE;
}
/****************************************************************************
 * gxsnmp_map_button -- Button event handler.
 **/
static gint
gxsnmp_map_button (GtkWidget *widget, GdkEventButton *event)
{
  GnomeCanvas   *canvas;
  GxSNMPMap     *map;
  D_FUNC_START;
  g_return_val_if_fail (widget != NULL, FALSE);
  g_return_val_if_fail (GXSNMP_IS_MAP (widget), FALSE);
  g_return_val_if_fail (event != NULL, FALSE);

  canvas = (GnomeCanvas *)widget;
  map    = (GxSNMPMap *)widget;
  if (event->window != canvas->layout.bin_window)
    return FALSE;
  switch (event->type)
    {
    case GDK_BUTTON_PRESS:
      /* Check the parent first. This will allow the other items
       * on the canvas to process events. A false on return is for us.
       * This allows us to do rubber banding and 'root' type menu popup.
       */
      if (GTK_WIDGET_CLASS (parent_class)->button_press_event)
	if ((* GTK_WIDGET_CLASS (parent_class)->button_press_event)
	   (widget, event))
	  return TRUE;                         /* Handled. */
      d_print (DEBUG_DUMP, "In my button_press handler.\n");
      switch (event->button)
	{
	case 1:
	  map->map_state = MAP_STATE_RUBBERBAND;
	  d_print (DEBUG_DUMP, "Start rubber band.\n");
	  D_FUNC_END;
	  return TRUE;                         /* Handled. */
	case 2:
	  map->map_state = MAP_STATE_MOUSE_SCROLL;
	  d_print (DEBUG_DUMP, "Mouse scroll..\n");
	  D_FUNC_END;
	  return TRUE;
	case 3:
	  d_print (DEBUG_DUMP, "Do root map menu popup.\n");
	  gxsnmp_menu_raise_map_popup (event, map);
	  D_FUNC_END;
	  return TRUE;                         /* Handled. */
	default:
	  return FALSE;                        /* Unhandled. */
	}
    case GDK_BUTTON_RELEASE:
      if (GTK_WIDGET_CLASS (parent_class)->button_release_event)
	(* GTK_WIDGET_CLASS (parent_class)->button_release_event)
	  (widget, event);
      map->map_state = MAP_STATE_NONE;        /* clear our state.*/
      return TRUE;
    default:
      g_assert_not_reached ();
    }
  d_print (DEBUG_DUMP, "Event not handled.\n");
  D_FUNC_END;
  return FALSE;   /* not handled let parent handle it */
}
/****************************************************************************
 *
 *  map_snap -- called to "snap-to-grid" a point
 *
 **/
G_INLINE_FUNC void
map_snap (gdouble *x, gdouble *y, GxSNMPMap *map)
{
  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);
}

/****************************************************************************
 *
 *  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 ()
{
  GxSNMPMap * 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 ());
  if (aa_canvas)
    d_print (DEBUG_DUMP, "Antialias canvas enabled.\n");
  GNOME_CANVAS (map)->aa = aa_canvas; 
  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 (GxSNMPMap *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 (GxSNMPMap *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;
}

/****************************************************************************
 * gxsnmp_map_zoom_in -- Zoom in one step on the map.
 * Convience function 
 **/
void
gxsnmp_map_zoom_in (GxSNMPMap *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 (GxSNMPMap *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;
}
/**
 * gxsnmp_map_toggle_grid:
 * @map: A pointer to a @GxSNMPMap.
 *
 * This will toggle the visiblity of the grid on the @map passed in.
 *
 * Return value: None.
 **/
void
gxsnmp_map_toggle_grid (GxSNMPMap *map)
{
  g_return_if_fail (map != NULL);
  g_return_if_fail (GXSNMP_IS_MAP (map));
  if (map->grid_visible)
    {
      gnome_canvas_item_hide (map->grid);
      map->grid_visible = FALSE;
    }
  else
    {
      gnome_canvas_item_show (map->grid);
      map->grid_visible = TRUE;
    }
}
/**
 * gxsnmp_map_set_grid_visiblity:
 * @map: The @GxSNMPMap pointer of the map to effect.
 * @flag: Value to set the visibilty of the grid to.
 *
 * Will set the visibilty flag of grid on the map in question explicity.
 *
 * Return values: None.
 **/
void
gxsnmp_map_set_grid_visiblity (GxSNMPMap *map, guint flag)
{
  g_return_if_fail (map != NULL);
  g_return_if_fail (GXSNMP_IS_MAP (map));
  map->grid_visible = flag;

  if (map->grid_visible)
    gnome_canvas_item_show (map->grid);
  else
    gnome_canvas_item_hide (map->grid);
}

/* EOF */
