/* 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 and * Havoc Pennington . * * 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 #include #include #include #include #include #include #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 $ */