/* -*- Mode: C -*-
 * $Id: gxsnmp_host_item.c,v 1.6 2000/02/02 04:10:50 gregm Exp $
 *
 * GXSNMP -- An snmp mangament application
 * Copyright 1998,1999 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.
 *
 * If this looks alot like a combination of multiple canvas widgets, thats
 * cause it is :)
 *
 */ 
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <gnome.h>
#include <libart_lgpl/art_misc.h>
#include <libart_lgpl/art_affine.h>
#include <libart_lgpl/art_pixbuf.h>
#include <libart_lgpl/art_rgb_pixbuf_affine.h>
#include <libgnomeui/gnome-canvas-util.h>

#include "gxsnmp_host_item.h"

#include "debug.h"
/****************************************************************************
 * Forward references
 **/
static void       gxsnmp_host_item_class_init   (GxSNMPHostItemClass *klass);
static void       gxsnmp_host_item_init         (GxSNMPHostItem      *item);
static void       gxsnmp_host_item_destroy      (GtkObject           *object);
static void       gxsnmp_host_item_set_arg      (GtkObject           *object,
						 GtkArg              *arg,
						 guint               arg_id);
static void       gxsnmp_host_item_get_arg      (GtkObject           *object,
						 GtkArg              *arg,
						 guint               arg_id);
static void       gxsnmp_host_item_realize      (GnomeCanvasItem     *item);
static void       gxsnmp_host_item_unrealize    (GnomeCanvasItem     *item);
static void       gxsnmp_host_item_draw         (GnomeCanvasItem     *item,
						 GdkDrawable         *drawable,
						 int                 xx,
						 int                 yy,
						 int                 width,
						 int                 height);
static void       gxsnmp_host_item_render       (GnomeCanvasItem     *item,
						 GnomeCanvasBuf      *buf);
static void       gxsnmp_host_item_update       (GnomeCanvasItem     *item,
						 double              *affline,
						 ArtSVP              *clip,
						 int                 flags);
static ArtPixBuf  *pixbuf_from_imlib_image      (GdkImlibImage *im);

/****************************************************************************
 * Local definitions
 **/
/** Arguments */
enum {
  ARG_0,
  ARG_SENSITIVE,
  ARG_X,
  ARG_Y,
  ARG_IMAGE
};
/** Signals */
enum {
  CHANGED,
  SELECT,
  UNSELECT,
  LAST_SIGNAL
};
/** Parent class */
static GnomeCanvasItemClass         *parent_class;
/**
 * gxsnmp_host_item_get_type:
 *
 * Registers the &GxSNMPHostItem class if necessary, and returns the unique
 * type ID associated to it.
 *
 * Return value: The unique type ID of the &GxSNMPHostItem class.
 **/
GtkType
gxsnmp_host_item_get_type (void)
{
  static GtkType host_item_type = 0;
  D_FUNC_START;
  if (!host_item_type)
    {
      static const GtkTypeInfo host_item_info = {
	"GxSNMPHostItem",
	sizeof (GxSNMPHostItem),
	sizeof (GxSNMPHostItemClass),
	(GtkClassInitFunc) gxsnmp_host_item_class_init,
	(GtkObjectInitFunc) gxsnmp_host_item_init,
	NULL, /* reserved_1 */
	NULL, /* reserved_2 */
	(GtkClassInitFunc) NULL
      };
      host_item_type = gtk_type_unique (gnome_canvas_item_get_type (),
				       &host_item_info);
    }
  D_FUNC_END;
  return host_item_type;
}
/****************************************************************************
 * Class initialization.
 **/
static void
gxsnmp_host_item_class_init (GxSNMPHostItemClass *klass)
{
  GtkObjectClass        *object_class;
  GnomeCanvasItemClass  *item_class;
  D_FUNC_START;
  object_class = (GtkObjectClass *)klass;
  item_class   = (GnomeCanvasItemClass *)klass;
  parent_class = gtk_type_class (gnome_canvas_item_get_type ());

  gtk_object_add_arg_type ("GxSNMPHostItem::x", GTK_TYPE_DOUBLE,
			   GTK_ARG_READWRITE, ARG_X);
  gtk_object_add_arg_type ("GxSNMPHostItem::y", GTK_TYPE_DOUBLE,
			   GTK_ARG_READWRITE, ARG_Y);
  gtk_object_add_arg_type ("GxSNMPHostItem::sensitive", GTK_TYPE_BOOL,
			   GTK_ARG_READWRITE, ARG_SENSITIVE);
  gtk_object_add_arg_type ("GxSNMPHostItem::image", GTK_TYPE_GDK_IMLIB_IMAGE,
			   GTK_ARG_READWRITE, ARG_IMAGE);
  /* Object methods */
  object_class->destroy  = gxsnmp_host_item_destroy;
  object_class->set_arg  = gxsnmp_host_item_set_arg;
  object_class->get_arg  = gxsnmp_host_item_get_arg;

  /* canvas item methods */
  item_class->realize    = gxsnmp_host_item_realize;
  item_class->unrealize  = gxsnmp_host_item_unrealize;
  item_class->draw       = gxsnmp_host_item_draw;
  item_class->render     = gxsnmp_host_item_render;
  item_class->update     = gxsnmp_host_item_update;

  D_FUNC_END;
}
/****************************************************************************
 * Object initialization
 **/
static void
gxsnmp_host_item_init (GxSNMPHostItem *item)
{
  D_FUNC_START;
  item->xpos   = 0.0;
  item->ypos   = 0.0;
  item->width  = 0.0;
  item->height = 0.0;
  /* item->anchor = GTK_ANCHOR_CENTER -- Always center unless someone can
   * convince me otherwise. Greg. */
  D_FUNC_END;
}
/****************************************************************************
 * Support function - free pixmap
 **/
static void
free_pixmap (GxSNMPHostItem *item)
{
  if (item->pixmap)
    gdk_imlib_free_pixmap (item->pixmap);
  item->pixmap  = NULL;
  item->mask    = NULL;
  item->cwidth  = 0;
  item->cheight = 0;
}

/****************************************************************************
 * destroy handler.
 **/
static void
gxsnmp_host_item_destroy (GtkObject *object)
{
  GxSNMPHostItem   *item;
  D_FUNC_START;
  g_return_if_fail (object != NULL);
  item = (GxSNMPHostItem *)object;
  free_pixmap (item);
  if (item->pixbuf) 
    {
      art_pixbuf_free (item->pixbuf);
      item->pixbuf  = NULL;
    }
  if (GTK_OBJECT_CLASS (parent_class)->destroy)
    (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
  D_FUNC_END;
}
/****************************************************************************
 * set_arg handler.
 **/
static void
gxsnmp_host_item_set_arg (GtkObject *object, GtkArg *arg, guint arg_id)
{
  GxSNMPHostItem   *host_item;
  GnomeCanvasItem  *item;
  gboolean         update;
  D_FUNC_START;
  host_item = GXSNMP_HOST_ITEM (object);
  item      = (GnomeCanvasItem *)object;
  update    = FALSE;
  switch (arg_id)
    {
    case ARG_X:
      host_item->xpos = GTK_VALUE_DOUBLE (*arg);
      gnome_canvas_item_request_update ((GnomeCanvasItem *)host_item);
      d_print (DEBUG_DUMP, "host_item->xpos set to %.2f\n", host_item->xpos);
      break;
    case ARG_Y:
      host_item->ypos = GTK_VALUE_DOUBLE (*arg);
      gnome_canvas_item_request_update ((GnomeCanvasItem *)host_item);
      d_print (DEBUG_DUMP, "host_item->xpos set to %.2f\n", host_item->xpos);
      break;
    case ARG_SENSITIVE:
      host_item->sensitive = GTK_VALUE_BOOL (*arg);
      break;
    case ARG_IMAGE:
      host_item->im = GTK_VALUE_POINTER (*arg);
      if (item->canvas->aa)
	{
	  if (host_item->pixbuf != NULL)
	    {
	      d_print (DEBUG_DUMP, "Freeing old image\n");
	      art_pixbuf_free (host_item->pixbuf);
	    }
	  host_item->pixbuf = pixbuf_from_imlib_image (host_item->im);
	  
	}
      update = TRUE;
      break;
    default:
      g_warning ("GxSNMPHostItem got an unknown arg type\n");
      break;
    }
  /* recalc_bounds (host_item); */
  if (update)
    gnome_canvas_item_request_update (host_item);
  D_FUNC_END;
}
/****************************************************************************
 * get_arg handler.
 **/
static void
gxsnmp_host_item_get_arg (GtkObject *object, GtkArg *arg, guint arg_id)
{
  GxSNMPHostItem   *host_item;
  D_FUNC_START;
  host_item = GXSNMP_HOST_ITEM (object);
  switch (arg_id)
    {
    case ARG_X:
      GTK_VALUE_DOUBLE (*arg) = host_item->xpos;
      break;
    case ARG_Y:
      GTK_VALUE_DOUBLE (*arg) = host_item->ypos;
      break;
    case ARG_SENSITIVE:
      GTK_VALUE_BOOL (*arg) = host_item->sensitive;
      break;
    case ARG_IMAGE:
      GTK_VALUE_POINTER (*arg) = host_item->im;
      break;
    default:
      arg->type = GTK_TYPE_INVALID;
      g_warning ("GxSNMPHostItem got an unhandled arg type.");
      break;
    }
  D_FUNC_END;
}
/****************************************************************************
 * Get the item's bounds expressed as item-relative coordinates 
 **/
static void
get_bounds_item_relative (GxSNMPHostItem *item, 
			  double *px1, double *py1, double *px2, double *py2)
{
  double         x, y;
  D_FUNC_START;
  /* Get the item coordinates */
  x = item->xpos - item->width / 2;
  y = item->ypos;
  /* Bounds */
  *px1 = x;
  *py1 = y;
  *px2 = x + item->width;
  *py2 = y + item->height;
  D_FUNC_END;
}
/****************************************************************************
 * Get bounds
 **/
static void
get_bounds (GxSNMPHostItem *item, 
	    double *px1, double *py1, double *px2, double *py2)
{
  double    wx, wy;
  double    i2c[6];
  ArtDRect  i_bbox, c_bbox;
  D_FUNC_START;
  gnome_canvas_item_i2c_affine (item, i2c);
  get_bounds_item_relative (item, &i_bbox.x0, &i_bbox.y0, &i_bbox.x1, 
			    &i_bbox.y1);
  art_drect_affine_transform (&c_bbox, &i_bbox, i2c);
  /* add a fudge factor */
  *px1 = c_bbox.x0 - 1;
  *py1 = c_bbox.y0 - 1;
  *px2 = c_bbox.x1 + 1;
  *py2 = c_bbox.y1 + 1;
  D_FUNC_END;
}
/****************************************************************************
 * recalc if needed.
 **/
static void
recalc_if_needed (GxSNMPHostItem *item)
{
  D_FUNC_START;
  if (!item->need_recalc)
    return;
  
  get_bounds (item, &item->item.x1, &item->item.y1, &item->item.x2, 
	      &item->item.y2);
  
  if (item->im && item->cwidth != 0 && item->cheight != 0) 
    {
      gdk_imlib_render (item->im, item->cwidth, item->cheight);
      
      item->pixmap = gdk_imlib_move_image (item->im);
      g_assert (item->pixmap != NULL);
      item->mask = gdk_imlib_move_mask (item->im);
      
      if (item->gc)
	gdk_gc_set_clip_mask (item->gc, item->mask);
    }
  
  item->need_recalc = FALSE;
  D_FUNC_END;
}

/****************************************************************************
 * realize handler.
 **/
static void
gxsnmp_host_item_realize (GnomeCanvasItem *item)
{
  GxSNMPHostItem    *host_item;

  D_FUNC_START;
  host_item = GXSNMP_HOST_ITEM (item);
  if (parent_class->realize)
    (* parent_class->realize) (item);
  if (!item->canvas->aa)
    {
      host_item->gc       = gdk_gc_new (item->canvas->layout.bin_window);
    }
  D_FUNC_END;
}
/****************************************************************************
 * unrealize handler.
 **/
static void
gxsnmp_host_item_unrealize (GnomeCanvasItem *item)
{
  GxSNMPHostItem   *host_item;
  D_FUNC_START;
  host_item = GXSNMP_HOST_ITEM (item);
  /* Clean up after ourselves */
  if (!item->canvas->aa)
    {
      gdk_gc_unref (host_item->gc);
      host_item->gc = NULL;
    }
  /* Let the parent do the same. */
  if (parent_class->unrealize)
    (* parent_class->unrealize) (item);

  D_FUNC_END;
}
/****************************************************************************
 * draw handler. (Gdk method)
 **/
static void
gxsnmp_host_item_draw (GnomeCanvasItem *item, GdkDrawable *drawable,
		       int xx, int yy, int width, int height)
{
  GxSNMPHostItem   *host;
  D_FUNC_START;
  host = GXSNMP_HOST_ITEM (item);
  if (host->im)
    return;
  recalc_if_needed (host);
  if (host->mask)
    gdk_gc_set_clip_origin (host->gc, host->cx - xx, host->cy - yy);
  if (host->pixmap)
    gdk_draw_pixmap (drawable,
		     host->gc,
		     host->pixmap,
		     0, 0,
		     host->cx - xx,
		     host->cy - yy,
		     host->cwidth,
		     host->cheight);
  D_FUNC_END;
}
/****************************************************************************
 * render hanlder. (aa canvas)
 **/
static void
gxsnmp_host_item_render (GnomeCanvasItem *item, GnomeCanvasBuf *buf)
{
  GxSNMPHostItem    *host;
  D_FUNC_START;
  host = (GxSNMPHostItem *)item;
  gnome_canvas_buf_ensure_buf (buf);
  d_print (DEBUG_DUMP, "Rect = (%d, %d) - (%d, %d)\n", 
	   buf->rect.x0, buf->rect.y0, buf->rect.x1, buf->rect.y1);
  if (host->pixbuf)
    {
      art_rgb_pixbuf_affine (buf->buf,
			     buf->rect.x0, buf->rect.y0, buf->rect.x1, buf->rect.y1,
			     buf->buf_rowstride,
			     host->pixbuf,
			     host->affine,
			     ART_FILTER_NEAREST, NULL);
      
      buf->is_bg = 0;
    }

  D_FUNC_END;
}
/****************************************************************************
 * update handler.
 **/
static void
gxsnmp_host_item_update (GnomeCanvasItem *item, double *affine, ArtSVP *clip,
			 gint flags)
{
  GxSNMPHostItem  *host;
  double          x1, y1, x2, y2;
  ArtDRect        i_bbox, c_bbox;
  int             w, h;
  
  D_FUNC_START;
  
  if (parent_class->update)
    (* parent_class->update) (item, affine, clip, flags);
  host = (GxSNMPHostItem *)item;
  free_pixmap (host);
  host->cwidth  = (int) (host->width * affine[0] + 0.5);
  host->cheight = (int) (host->height * affine[3] + 0.5);
  if (host->im || host->pixbuf)
    host->need_recalc = TRUE;
  get_bounds_item_relative (host, &i_bbox.x0, &i_bbox.y0, &i_bbox.x1, 
			    &i_bbox.y1);
  art_drect_affine_transform (&c_bbox, &i_bbox, affine);
  
  /* these values only make sense in the non-rotated, non-skewed case */
  host->cx = c_bbox.x0;
  host->cy = c_bbox.y0;
  
  /* add a fudge factor */
  c_bbox.x0--;
  c_bbox.y0--;
  c_bbox.x1++;
  c_bbox.y1++;
  
  gnome_canvas_update_bbox (item, c_bbox.x0, c_bbox.y0, c_bbox.x1, c_bbox.y1);
  if (host->im) 
    {
      w = host->im->rgb_width;
      h = host->im->rgb_height;
    } 
  else if (host->pixbuf) 
    {
      w = host->pixbuf->width;
      h = host->pixbuf->height;
    } 
  else
    w = h = 1;
  
  host->affine[0] = (affine[0] * host->width) / w;
  host->affine[1] = (affine[1] * host->height) / h;
  host->affine[2] = (affine[2] * host->width) / w;
  host->affine[3] = (affine[3] * host->height) / h;
  host->affine[4] = i_bbox.x0 * affine[0] + i_bbox.y0 * affine[2] + affine[4];
  host->affine[5] = i_bbox.x0 * affine[1] + i_bbox.y0 * affine[3] + affine[5];
  
  D_FUNC_END;
}
/****************************************************************************
 * Bounds handler.
 **/
static void
gxsnmp_host_item_bounds (GnomeCanvasItem *item, 
			 double *px1, double *py1, double *px2, double *py2)
{
  GxSNMPHostItem   *host;
  D_FUNC_START;
  host = (GxSNMPHostItem *)item;
  *px1 = host->xpos  - host->width / 2.0;
  *py1 = host->ypos;
  *px2 = *px1 + host->width;
  *py2 = *py1 + host->height;
}
/* This creates a new pixbuf from the imlib image. */
static ArtPixBuf *
pixbuf_from_imlib_image (GdkImlibImage *im)
{
        art_u8 *pixels;
        int width, height, rowstride;
        int x, y;
        unsigned char *p_src, *p_alpha;
        art_u8 *p_dst;
        art_u8 r, g, b, alpha;
        art_u8 xr, xg, xb;

        if (im->alpha_data) {
                /* image has alpha data (not presently implemented in imlib as
                   of 15 Dec 1998, but should happen soon. */
                width = im->rgb_width;
                height = im->rgb_height;
                rowstride = width * 4;
                pixels = art_alloc (rowstride * height);
                p_src = im->rgb_data;
                p_alpha = im->alpha_data;
                p_dst = pixels;
               for (y = 0; y < height; y++)
                        for (x = 0; x < width; x++) {
                                r = p_src[0];
                                g = p_src[1];
                                b = p_src[2];
                                alpha = p_alpha[0];

                                        p_dst[0] = r;
                                        p_dst[1] = g;
                                        p_dst[2] = b;
                                        p_dst[3] = alpha;

                                p_src += 3;
                                p_alpha += 1;
                                p_dst += 4;
                        }
                return art_pixbuf_new_rgba (pixels, width, height, rowstride);
        } else if (im->shape_color.r >= 0 && im->shape_color.g >= 0 && im->shape_color.b >= 0) {
                /* image has one transparent color */
                width = im->rgb_width;
                height = im->rgb_height;
                rowstride = width * 4;
              pixels = art_alloc (rowstride * height);
                p_src = im->rgb_data;
                p_dst = pixels;
                xr = im->shape_color.r;
                xg = im->shape_color.g;
                xb = im->shape_color.b;
                for (y = 0; y < height; y++)
                        for (x = 0; x < width; x++) {
                                r = p_src[0];
                                g = p_src[1];
                                b = p_src[2];
                                if (r == xr && g == xg && b == xb) {
                                        ((art_u32 *)p_dst)[0] = 0;
                                } else {
                                        p_dst[0] = r;
                                        p_dst[1] = g;
                                        p_dst[2] = b;
                                        p_dst[3] = 255;
                                }
                                p_src += 3;
                                p_dst += 4;
                        }
                return art_pixbuf_new_rgba (pixels, width, height, rowstride);

       } else {
                /* image is solid rgb */
                width = im->rgb_width;
                height = im->rgb_height;
                rowstride = (width * 3 + 3) & -4;
                pixels = art_alloc (rowstride * height);
                p_src = im->rgb_data;
                p_dst = pixels;
                for (y = 0; y < height; y++) {
                        memcpy (p_dst, p_src, width * 3);
                        p_src += width * 3;
                        p_dst += rowstride;
                }
                return art_pixbuf_new_rgb (pixels, width, height, rowstride);
        }
}

/* EOF */
