/* 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