/* This is -*- C -*- */
/* $Id: guppi-pie-item.c,v 1.27 2002/01/19 02:43:09 trow Exp $ */

/*
 * guppi-pie-item.c
 *
 * Copyright (C) 2000 EMC Capital Management, Inc.
 *
 * Developed by Jon Trowbridge <trow@gnu.org> and
 * 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 <libgnomeui/gnome-canvas.h>
#include <libgnomeui/gnome-canvas-util.h>

#include <math.h>
#include <libart_lgpl/libart.h>
#include <guppi-useful.h>
#include <guppi-raster-text.h>
#include "guppi-pie-common.h"
#include "guppi-pie-item.h"
#include "guppi-pie-state.h"
#include "guppi-pie-view.h"
#include "guppi-pie-tool.h"

static GtkObjectClass *parent_class = NULL;

enum {
  CLICKED_SLICE,
  LAST_SIGNAL
};

static guint pie_signals[LAST_SIGNAL] = { 0 };

static void
guppi_pie_item_destroy (GtkObject * obj)
{
  if (parent_class->destroy)
    parent_class->destroy (obj);
}

static void
guppi_pie_item_finalize (GtkObject * obj)
{
  GuppiPieItem *item = GUPPI_PIE_ITEM (obj);
  GList *iter;

  if (item->slice_fill_svp) {
    iter = item->slice_fill_svp;
    while (iter != NULL) {
      if (iter->data)
	art_svp_free ((ArtSVP *) iter->data);
      iter = g_list_next (iter);
    }
    g_list_free (item->slice_fill_svp);
    item->slice_fill_svp = NULL;
  }

  if (item->slice_edge_svp) {
    iter = item->slice_edge_svp;
    while (iter != NULL) {
      if (iter->data)
	art_svp_free ((ArtSVP *) iter->data);
      iter = g_list_next (iter);
    }
    g_list_free (item->slice_edge_svp);
    item->slice_edge_svp = NULL;
  }

  if (item->edge_percent_templates) {
    iter = item->edge_percent_templates;
    while (iter != NULL) {
      guppi_unref (iter->data);
      iter = g_list_next (iter);
    }
    g_list_free (item->edge_percent_templates);
    item->edge_percent_templates = NULL;
  }

  if (parent_class->finalize)
    parent_class->finalize (obj);
}

/**************************************************************************/

static void
update (GuppiCanvasItem * gci, double aff[6], ArtSVP * svp, gint flags)
{
  GuppiPieItem *item = GUPPI_PIE_ITEM (gci);
  GuppiPieView *view = GUPPI_PIE_VIEW (guppi_canvas_item_view (gci));
  GuppiPieState *state = GUPPI_PIE_STATE (guppi_canvas_item_state (gci));
  GList *iter;
  gint i, i0, i1;
  double sc = guppi_canvas_item_scale (gci);
  double sc_r, sc_ew, edge_width, base_angle;
  double run_ang;
  gint cx0, cx1, cy0, cy1;
  GnomeFont *label_font;
  gboolean show_perc;

  guppi_pie_state_slice_bounds (state, &i0, &i1);
    
  if (i0 > i1) {
    return;
  }

  guppi_element_state_get (GUPPI_ELEMENT_STATE (state),
			   "edge_width", &edge_width,
			   "label_font", &label_font,
			   "show_percentage", &show_perc,
			   "base_angle", &base_angle,
			   NULL);

  sc_r = guppi_pt2px (guppi_pie_view_effective_radius (view) * sc);
  sc_ew = guppi_pt2px (edge_width * sc);

  guppi_canvas_item_get_bbox_c (gci, &cx0, &cy0, &cx1, &cy1);

  /* Clear old SVPs */
  for (iter = item->slice_fill_svp; iter; iter= g_list_next (iter))
    art_svp_free ((ArtSVP *) iter->data);
  
  if (item->slice_fill_svp) {
    g_list_free (item->slice_fill_svp);
    item->slice_fill_svp = NULL;
  }

  for (iter = item->slice_edge_svp; iter; iter = g_list_next (iter))
    art_svp_free ((ArtSVP *) iter->data);
  
  if (item->slice_edge_svp) {
    g_list_free (item->slice_edge_svp);
    item->slice_edge_svp = NULL;
  }

  /* Clean out old edge percentage text rasterizations */
  if (item->edge_percent_template_count != (i1-i0+1)) {
    
    for (iter = item->edge_percent_templates; iter; iter = g_list_next (iter)) 
      guppi_unref (iter->data);

    if (item->edge_percent_templates) {
      g_list_free (item->edge_percent_templates);
      item->edge_percent_templates = NULL;
    }
  }

  /* If necessary, create a list w/ our empty raster text items. */
  if (show_perc && item->edge_percent_templates == NULL) {
    for (i = 0; i <= i1 - i0 + 1; ++i)
      item->edge_percent_templates =
	g_list_prepend (item->edge_percent_templates,
			guppi_raster_text_new (NULL));
    item->edge_percent_template_count = i1 - i0 + 1;
  }
  
  run_ang = base_angle;

  iter = item->edge_percent_templates;
  for (i = i0; i <= i1; ++i) {
    double perc = guppi_pie_state_slice_percentage (state, i);
    double th = 2 * M_PI * perc;
    ArtVpath *path;
    ArtSVP *svp;
    double sc_off = guppi_pie_state_slice_offset (state, i);
    sc_off = guppi_pt2px (sc_off * sc);

    path = guppi_pie_slice_vpath ((cx0 + cx1) / 2.0, (cy0 + cy1) / 2.0,
				  sc_off, sc_r, run_ang, run_ang + th, 0);
    svp = art_svp_from_vpath (path);
    item->slice_fill_svp = g_list_append (item->slice_fill_svp, svp);

    if (sc_ew > 0) {
      svp = art_svp_vpath_stroke (path,
				  ART_PATH_STROKE_JOIN_ROUND,
				  ART_PATH_STROKE_CAP_ROUND, sc_ew, 4, 0.25);

      item->slice_edge_svp = g_list_append (item->slice_edge_svp, svp);
    }

    guppi_free (path);

    /* Rasterize text for percentages to be shown at the edge
       of the pie slices. */
    if (show_perc) {
      GuppiRasterText *rt = iter ? GUPPI_RASTER_TEXT (iter->data) : NULL;
      double R, x, y, lab_th;
      gint w = 0, h = 0;

      if (rt) {

	gchar buffer[32];
	g_snprintf (buffer, 32, "%d%%", (gint) rint (100 * perc));

	guppi_raster_text_set_text (rt, buffer);
	guppi_raster_text_set_font (rt, label_font);
	guppi_raster_text_set_scale (rt, sc);

	if (guppi_raster_text_template (rt)) {
	  w = guppi_raster_text_template (rt)->width;
	  h = guppi_raster_text_template (rt)->height;
	}
	
	/* Calculate the label position. */
	
	/* That last term is an ad-hoc adjustment that seems to do a good job. */
	R = sc_off + sc_r + 72 / 32.0 + 0.667 * sqrt (w * w + h * h);
	
	lab_th = run_ang + th / 2;
	x = (cx0 + cx1) / 2 + R * cos (lab_th);
	y = (cy0 + cy1) / 2 + R * sin (lab_th);

	guppi_raster_text_set_position (rt, 
					(gint) rint (x) - w / 2,
					(gint) rint (y) - h / 2);
	
	iter = g_list_next (iter);
      }
    }
      
    run_ang += th;
  }

  guppi_unref (label_font);
}

static void
render (GuppiCanvasItem *gci, GnomeCanvasBuf *buf)
{
  GuppiPieItem *item = GUPPI_PIE_ITEM (gci);
  GuppiPieState *state = GUPPI_PIE_STATE (guppi_canvas_item_state (gci));
  GList *iter;
  gint i;
  gboolean show_perc;
  guint32 label_color, edge_color;

  guppi_element_state_get (GUPPI_ELEMENT_STATE (state),
			   "show_percentage", &show_perc,
			   "label_color", &label_color,
			   "edge_color", &edge_color,
			   NULL);

  /* Render the inside of the slices. */
  iter = item->slice_fill_svp;
  i = 0;
  while (iter) {
    ArtSVP *svp = (ArtSVP *) iter->data;
    gnome_canvas_render_svp (buf, svp, guppi_pie_state_slice_color (state, i));
    iter = g_list_next (iter);
    ++i;
  }

  /* Render the edges of the slices. */
  iter = item->slice_edge_svp;
  while (iter) {
    ArtSVP *svp = (ArtSVP *) iter->data;
    gnome_canvas_render_svp (buf, svp, edge_color);
    iter = g_list_next (iter);
  }

  /* Render our percentage labels. */
  if (show_perc) {
    guint r, g, b, a;

    UINT_TO_RGBA (label_color, &r, &g, &b, &a);

    iter = item->edge_percent_templates;

    while (iter) {
      GuppiRasterText *rt = GUPPI_RASTER_TEXT (iter->data);
      GuppiAlphaTemplate *atemp = guppi_raster_text_template (rt);

      if (atemp) {
	gint x, y;
	guppi_raster_text_position (rt, &x, &y);
	guppi_alpha_template_print (atemp, x, y, r, g, b, a, buf);
      }
      iter = g_list_next (iter);
    }
  }
}

static void
foreach_class_toolkit (GuppiCanvasItem * item,
		       void (*fn) (GuppiPlotToolkit *, gpointer),
		       gpointer user_data)
{
  GuppiPlotToolkit *tk;

  tk = guppi_pie_toolkit_explode ();
  fn (tk, user_data);
  guppi_unref (tk);

  tk = guppi_pie_toolkit_spin ();
  fn (tk, user_data);
  guppi_unref (tk);
}

static gboolean
double_click (GuppiCanvasItem * gci,
	      guint button, guint state, double pt_x, double pt_y)
{
  gint slice;
  gint c_x, c_y;

  guppi_canvas_item_pt2c (gci, pt_x, pt_y, &c_x, &c_y);

  if (guppi_pie_item_in_slice (GUPPI_PIE_ITEM (gci), c_x, c_y, &slice)) {
    gtk_signal_emit (GTK_OBJECT (gci), pie_signals[CLICKED_SLICE],
		     slice, button, state);
    return TRUE;
  }
  return FALSE;
}

static void
guppi_marshal_NONE__INT_UINT_UINT (GtkObject * obj,
				   GtkSignalFunc func,
				   gpointer func_data, GtkArg * args)
{
  ((void (*)(GtkObject *,
	     gint, guint, guint,
	     gpointer)) func) (obj,
			       GTK_VALUE_INT (args[0]),
			       GTK_VALUE_UINT (args[1]),
			       GTK_VALUE_UINT (args[2]), func_data);
}

static void
guppi_pie_item_class_init (GuppiPieItemClass * klass)
{
  GtkObjectClass *object_class = (GtkObjectClass *) klass;
  GuppiCanvasItemClass *gci_class = GUPPI_CANVAS_ITEM_CLASS (klass);

  parent_class = gtk_type_class (GUPPI_TYPE_CANVAS_ITEM);

  object_class->destroy = guppi_pie_item_destroy;
  object_class->finalize = guppi_pie_item_finalize;

  gci_class->guppi_update = update;
  gci_class->guppi_render = render;
  gci_class->foreach_class_toolkit = foreach_class_toolkit;
  gci_class->double_click = double_click;

  guppi_canvas_item_class_set_item_class_toolkit (gci_class,
						  guppi_pie_toolkit_default ());

  pie_signals[CLICKED_SLICE] =
    gtk_signal_new ("clicked_slice",
		    GTK_RUN_FIRST,
		    object_class->type,
		    GTK_SIGNAL_OFFSET (GuppiPieItemClass, clicked_slice),
		    guppi_marshal_NONE__INT_UINT_UINT,
		    GTK_TYPE_NONE, 3,
		    GTK_TYPE_INT, GTK_TYPE_UINT, GTK_TYPE_UINT);

  gtk_object_class_add_signals (object_class, pie_signals, LAST_SIGNAL);

}

static void
guppi_pie_item_init (GuppiPieItem * obj)
{

}

GtkType guppi_pie_item_get_type (void)
{
  static GtkType guppi_pie_item_type = 0;
  if (!guppi_pie_item_type) {
    static const GtkTypeInfo guppi_pie_item_info = {
      "GuppiPieItem",
      sizeof (GuppiPieItem),
      sizeof (GuppiPieItemClass),
      (GtkClassInitFunc) guppi_pie_item_class_init,
      (GtkObjectInitFunc) guppi_pie_item_init,
      NULL, NULL, (GtkClassInitFunc) NULL
    };
    guppi_pie_item_type =
      gtk_type_unique (GUPPI_TYPE_CANVAS_ITEM, &guppi_pie_item_info);
  }
  return guppi_pie_item_type;
}

GtkObject *
guppi_pie_item_new (void)
{
  return GTK_OBJECT (guppi_type_new (guppi_pie_item_get_type ()));
}

static gboolean
between_angle (double a1, double b, double a2)
{
  while (a1 < 0)
    a1 += 2 * M_PI;
  while (b < 0)
    b += 2 * M_PI;
  while (a2 < 0)
    a2 += 2 * M_PI;

  a1 = fmod (a1, 2 * M_PI);
  b = fmod (b, 2 * M_PI);
  a2 = fmod (a2, 2 * M_PI);

  if (a1 <= a2)
    return a1 <= b && b < a2;
  else
    return a1 <= b || b < a2;
}

gboolean
guppi_pie_item_in_slice (GuppiPieItem * item, gint x, gint y, gint * slice)
{
  GuppiCanvasItem *gci = GUPPI_CANVAS_ITEM (item);
  GuppiPieState *state = GUPPI_PIE_STATE (guppi_canvas_item_state (gci));
  GuppiPieView *view = GUPPI_PIE_VIEW (guppi_canvas_item_view (gci));
  double sc = guppi_canvas_item_scale (gci);
  double sc_r;
  gint cx0, cx1, cy0, cy1;
  double cx, cy, dx, dy, d2, offx, offy;
  double run_ang, next_ang;
  double pt_theta, off_theta;
  gint i, i0, i1;

  guppi_pie_state_slice_bounds (state, &i0, &i1);
  if (i0 > i1)
    return FALSE;

  sc_r = guppi_pie_view_effective_radius (view);
  sc_r = guppi_pt2px (sc_r * sc);

  guppi_canvas_item_get_bbox_c (gci, &cx0, &cy0, &cx1, &cy1);
  cx = (cx0 + cx1) / 2.0;
  cy = (cy0 + cy1) / 2.0;

  dx = x - cx;
  dy = y - cy;
  d2 = dx * dx + dy * dy;

  pt_theta = atan2 (dy, dx);

  guppi_element_state_get (GUPPI_ELEMENT_STATE (state),
			   "base_angle", &run_ang,
			   NULL);

  for (i = i0; i <= i1; ++i) {
    double th = 2 * M_PI * guppi_pie_state_slice_percentage (state, i);
    double sc_off = guppi_pie_state_slice_offset (state, i);
    sc_off = guppi_pt2px (sc_off * sc);

    next_ang = run_ang + th;

    if (d2 >= sc_off * sc_off &&
	d2 <= (sc_off + sc_r) * (sc_off + sc_r) &&
	between_angle (run_ang, pt_theta, next_ang)) {

      offx = cx + sc_off * cos (run_ang + th / 2);
      offy = cy + sc_off * sin (run_ang + th / 2);

      off_theta = atan2 (y - offy, x - offx);

      if (between_angle (run_ang, off_theta, next_ang)) {
	if (slice) {
	  *slice = i;
	return TRUE;
	}
      } else {
	g_message ("not in slice");
      }
    }

    run_ang = next_ang;
  }

  return FALSE;
}


/* $Id: guppi-pie-item.c,v 1.27 2002/01/19 02:43:09 trow Exp $ */


syntax highlighted by Code2HTML, v. 0.9.1