/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * gog-legend.c : * * Copyright (C) 2003-2004 Jody Goldberg (jody@gnome.org) * * This program is free software; you can redistribute it and/or * modify it under the terms of version 2 of the GNU General Public * License as published by the Free Software Foundation. * * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 * USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct _GogLegend { GogOutlinedObject base; double swatch_size_pts; double swatch_padding_pts; gulong chart_cardinality_handle; gulong chart_child_name_changed_handle; unsigned cached_count; gboolean names_changed; }; typedef GogStyledObjectClass GogLegendClass; enum { LEGEND_PROP_0, LEGEND_SWATCH_SIZE_PTS, LEGEND_SWATCH_PADDING_PTS }; static GType gog_legend_view_get_type (void); static GObjectClass *parent_klass; static void gog_legend_set_property (GObject *obj, guint param_id, GValue const *value, GParamSpec *pspec) { GogLegend *legend = GOG_LEGEND (obj); switch (param_id) { case LEGEND_SWATCH_SIZE_PTS : legend->swatch_size_pts = g_value_get_double (value); break; case LEGEND_SWATCH_PADDING_PTS : legend->swatch_padding_pts = g_value_get_double (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec); return; /* NOTE : RETURN */ } } static void gog_legend_get_property (GObject *obj, guint param_id, GValue *value, GParamSpec *pspec) { GogLegend *legend = GOG_LEGEND (obj); switch (param_id) { case LEGEND_SWATCH_SIZE_PTS : g_value_set_double (value, legend->swatch_size_pts); break; case LEGEND_SWATCH_PADDING_PTS : g_value_set_double (value, legend->swatch_padding_pts); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec); break; } } static void cb_chart_names_changed (GogLegend *legend) { if (legend->names_changed) return; legend->names_changed = TRUE; gog_object_request_update (GOG_OBJECT (legend)); } static void gog_legend_parent_changed (GogObject *obj, gboolean was_set) { GogObjectClass *gog_object_klass = GOG_OBJECT_CLASS (parent_klass); GogLegend *legend = GOG_LEGEND (obj); if (was_set) { if (legend->chart_cardinality_handle == 0) legend->chart_cardinality_handle = g_signal_connect_object (G_OBJECT (obj->parent), "notify::cardinality-valid", G_CALLBACK (gog_object_request_update), legend, G_CONNECT_SWAPPED); if (legend->chart_child_name_changed_handle == 0) legend->chart_child_name_changed_handle = g_signal_connect_object (G_OBJECT (obj->parent), "child-name-changed", G_CALLBACK (cb_chart_names_changed), legend, G_CONNECT_SWAPPED); } else { if (legend->chart_cardinality_handle != 0) { g_signal_handler_disconnect (G_OBJECT (obj->parent), legend->chart_cardinality_handle); legend->chart_cardinality_handle = 0; } if (legend->chart_child_name_changed_handle != 0) { g_signal_handler_disconnect (G_OBJECT (obj->parent), legend->chart_child_name_changed_handle); legend->chart_child_name_changed_handle = 0; } } gog_object_klass->parent_changed (obj, was_set); } static void gog_legend_update (GogObject *obj) { GogLegend *legend = GOG_LEGEND (obj); unsigned visible; gog_chart_get_cardinality (GOG_CHART (obj->parent), NULL, &visible); if (legend->cached_count != visible) legend->cached_count = visible; else if (!legend->names_changed) return; legend->names_changed = FALSE; gog_object_emit_changed (obj, TRUE); } static void gog_legend_populate_editor (GogObject *gobj, GogEditor *editor, GogDataAllocator *dalloc, GOCmdContext *cc) { static guint legend_pref_page = 0; (GOG_OBJECT_CLASS(parent_klass)->populate_editor) (gobj, editor, dalloc, cc); gog_editor_set_store_page (editor, &legend_pref_page); } static void gog_legend_init_style (GogStyledObject *gso, GogStyle *style) { style->interesting_fields = GOG_STYLE_OUTLINE | GOG_STYLE_FILL | GOG_STYLE_FONT; gog_theme_fillin_style (gog_object_get_theme (GOG_OBJECT (gso)), style, GOG_OBJECT (gso), 0, FALSE); } static void gog_legend_class_init (GogLegendClass *klass) { static GogObjectRole const roles[] = { { N_("Title"), "GogLabel", 0, GOG_POSITION_COMPASS, GOG_POSITION_N|GOG_POSITION_ALIGN_CENTER, GOG_OBJECT_NAME_BY_ROLE, NULL, NULL, NULL, NULL, NULL, NULL }, }; GObjectClass *gobject_klass = (GObjectClass *) klass; GogObjectClass *gog_klass = (GogObjectClass *) klass; GogStyledObjectClass *style_klass = (GogStyledObjectClass *) klass; parent_klass = g_type_class_peek_parent (klass); gobject_klass->set_property = gog_legend_set_property; gobject_klass->get_property = gog_legend_get_property; gog_klass->parent_changed = gog_legend_parent_changed; gog_klass->update = gog_legend_update; gog_klass->populate_editor = gog_legend_populate_editor; gog_klass->view_type = gog_legend_view_get_type (); style_klass->init_style = gog_legend_init_style; gog_object_register_roles (gog_klass, roles, G_N_ELEMENTS (roles)); g_object_class_install_property (gobject_klass, LEGEND_SWATCH_SIZE_PTS, g_param_spec_double ("swatch_size_pts", "Swatch Size pts", "size of the swatches in pts.", 0, G_MAXDOUBLE, 0, G_PARAM_READWRITE|GOG_PARAM_PERSISTENT)); g_object_class_install_property (gobject_klass, LEGEND_SWATCH_PADDING_PTS, g_param_spec_double ("swatch_padding_pts", "Swatch Padding pts", "padding between the swatches in pts.", 0, G_MAXDOUBLE, 0, G_PARAM_READWRITE|GOG_PARAM_PERSISTENT)); } static void gog_legend_init (GogLegend *legend) { legend->swatch_size_pts = GO_CM_TO_PT ((double).25); legend->swatch_padding_pts = GO_CM_TO_PT ((double).2); legend->cached_count = 0; } GSF_CLASS (GogLegend, gog_legend, gog_legend_class_init, gog_legend_init, GOG_OUTLINED_OBJECT_TYPE) typedef struct { GogOutlinedView base; gboolean is_vertical; double element_width; double element_height; unsigned element_per_blocks; unsigned num_blocks; gboolean uses_lines; double label_offset; } GogLegendView; typedef GogOutlinedViewClass GogLegendViewClass; #define GOG_LEGEND_VIEW_TYPE (gog_legend_view_get_type ()) #define GOG_LEGEND_VIEW(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOG_LEGEND_VIEW_TYPE, GogLegendView)) #define IS_GOG_LEGEND_VIEW(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOG_LEGEND_VIEW_TYPE)) static GogViewClass *lview_parent_klass; typedef struct { GogView const *view; GogViewRequisition maximum; gboolean uses_lines; GogStyle *legend_style; } SizeClosure; static void cb_size_elements (unsigned i, GogStyle const *style, char const *name, SizeClosure *data) { GOGeometryAABR aabr; gog_renderer_push_style (data->view->renderer, data->legend_style); gog_renderer_get_text_AABR (data->view->renderer, name, &aabr); gog_renderer_pop_style (data->view->renderer); if (data->maximum.w < aabr.w) data->maximum.w = aabr.w; if (data->maximum.h < aabr.h) data->maximum.h = aabr.h; if (!data->uses_lines && (style->interesting_fields & GOG_STYLE_LINE)) data->uses_lines = TRUE; } static void gog_legend_view_size_request (GogView *v, GogViewRequisition const *available, GogViewRequisition *req) { GogChart *chart = GOG_CHART (v->model->parent); GogLegendView *glv = GOG_LEGEND_VIEW (v); GogLegend *l = GOG_LEGEND (v->model); GogViewRequisition child_req, residual; SizeClosure data; double available_space, element_size, swatch_padding; unsigned num_elements; residual = *available; req->w = req->h = 0; gog_view_size_child_request (v, available, req, &child_req); lview_parent_klass->size_request (v, available, req); residual.w -= req->w; residual.h -= req->h; glv->is_vertical = gog_object_get_position_flags (GOG_OBJECT (l), GOG_POSITION_COMPASS) & (GOG_POSITION_E | GOG_POSITION_W); gog_chart_get_cardinality (chart, NULL, &num_elements); data.view = v; data.maximum.w = 0.; data.maximum.h = gog_renderer_pt2r_y (v->renderer, l->swatch_size_pts) * 1.2; data.uses_lines = FALSE; data.legend_style = GOG_STYLED_OBJECT (l)->style; gog_chart_foreach_elem (chart, TRUE, (GogEnumFunc) cb_size_elements, &data); swatch_padding = gog_renderer_pt2r_x (v->renderer, l->swatch_padding_pts); glv->label_offset = gog_renderer_pt2r_x (v->renderer, (data.uses_lines ? 3 : 1) * l->swatch_size_pts + swatch_padding * .5); data.maximum.w += glv->label_offset + swatch_padding; glv->element_height = data.maximum.h; glv->element_width = data.maximum.w; glv->uses_lines = data.uses_lines; available_space = glv->is_vertical ? residual.h : residual.w; element_size = glv->is_vertical ? data.maximum.h : data.maximum.w; glv->element_per_blocks = available_space > 0. ? floor (available_space / element_size) : 0; if (glv->element_per_blocks < 1) { req->w = req->h = -1; return; } glv->num_blocks = floor ((num_elements - 1) / glv->element_per_blocks) + 1; if (glv->is_vertical) { req->h += MIN (glv->element_per_blocks, num_elements) * data.maximum.h; req->w += glv->num_blocks * data.maximum.w - swatch_padding; } else { req->h += glv->num_blocks * data.maximum.h; req->w += MIN (glv->element_per_blocks, num_elements) * data.maximum.w - swatch_padding; } req->w = MAX (child_req.w, req->w); req->h = MAX (child_req.h, req->h); } typedef struct { GogView const *view; double x, y; double element_step_x, element_step_y; double block_step_x, block_step_y; GogViewAllocation swatch; ArtVpath line_path[3]; } RenderClosure; static void cb_render_elements (unsigned i, GogStyle const *base_style, char const *name, RenderClosure *data) { GogView const *view = data->view; GogLegendView *glv = GOG_LEGEND_VIEW (view); GogRenderer *renderer = view->renderer; GogStyledObject *obj = GOG_STYLED_OBJECT (data->view->model); GogStyle *style = NULL; GogStyle *legend_style = obj->style; GogViewAllocation pos, rectangle; if (i > 0) { if ((i % glv->element_per_blocks) != 0) { data->x += data->element_step_x; data->y += data->element_step_y; } else { data->x += data->block_step_x; data->y += data->block_step_y; } } if (base_style->interesting_fields & GOG_STYLE_LINE) { /* line and marker */ style = (GogStyle *)base_style; gog_renderer_push_style (renderer, style); data->line_path[0].x = data->x; data->line_path[1].x = data->x + data->swatch.w * 3.; data->line_path[0].y = data->line_path[1].y = data->y + glv->element_height / 2.; gog_renderer_draw_sharp_path (renderer, data->line_path); gog_renderer_draw_marker (renderer, data->x + data->swatch.w * 1.5, data->line_path[0].y); } else { /* area swatch */ style = gog_style_dup (base_style); style->outline.width = 0; style->outline.color = RGBA_BLACK; rectangle = data->swatch; rectangle.x += data->x; rectangle.y += data->y; gog_renderer_push_style (renderer, style); gog_renderer_draw_sharp_rectangle (renderer, &rectangle); } gog_renderer_pop_style (renderer); pos.x = data->x + glv->label_offset; pos.y = data->y + glv->element_height / 2.0; pos.w = pos.h = -1; gog_renderer_push_style (renderer, legend_style); gog_renderer_draw_text (renderer, name, &pos, GTK_ANCHOR_W, NULL); gog_renderer_pop_style (renderer); if (style != base_style && style != NULL) g_object_unref (style); } static void gog_legend_view_render (GogView *v, GogViewAllocation const *bbox) { GogLegendView *glv = GOG_LEGEND_VIEW (v); GogLegend *l = GOG_LEGEND (v->model); RenderClosure data; (lview_parent_klass->render) (v, bbox); if (glv->element_per_blocks < 1) return; if (glv->uses_lines) { data.line_path[0].code = ART_MOVETO; data.line_path[1].code = ART_LINETO; data.line_path[2].code = ART_END; } data.view = v; data.x = v->residual.x; data.y = v->residual.y; data.element_step_x = glv->is_vertical ? 0 : glv->element_width; data.element_step_y = glv->is_vertical ? glv->element_height : 0; data.block_step_x = glv->is_vertical ? + glv->element_width : - glv->element_width * (glv->element_per_blocks - 1); data.block_step_y = glv->is_vertical ? - glv->element_height * (glv->element_per_blocks - 1) : + glv->element_height; data.swatch.w = gog_renderer_pt2r_x (v->renderer, l->swatch_size_pts); data.swatch.h = gog_renderer_pt2r_y (v->renderer, l->swatch_size_pts); data.swatch.x = (glv->label_offset - data.swatch.w - gog_renderer_pt2r_x (v->renderer, l->swatch_padding_pts) * .5) * .5; data.swatch.y = (glv->element_height - data.swatch.h) * .5; gog_chart_foreach_elem (GOG_CHART (v->model->parent), TRUE, (GogEnumFunc) cb_render_elements, &data); } static void gog_legend_view_class_init (GogLegendViewClass *gview_klass) { GogViewClass *view_klass = (GogViewClass *) gview_klass; lview_parent_klass = g_type_class_peek_parent (gview_klass); view_klass->size_request = gog_legend_view_size_request; view_klass->render = gog_legend_view_render; view_klass->clip = TRUE; } static GSF_CLASS (GogLegendView, gog_legend_view, gog_legend_view_class_init, NULL, GOG_OUTLINED_VIEW_TYPE)