You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
gnucash/lib/goffice/graph/gog-renderer.c

630 lines
16 KiB

/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* gog-renderer.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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*/
#include <goffice/goffice-config.h>
#include <goffice/graph/gog-renderer-impl.h>
#include <goffice/graph/gog-style.h>
#include <goffice/graph/gog-graph.h>
#include <goffice/graph/gog-view.h>
#include <goffice/utils/go-units.h>
#include <goffice/utils/go-font.h>
#include <goffice/utils/go-math.h>
#include <gsf/gsf-impl-utils.h>
#include <math.h>
/* We need to define an hair line width for the svg and gnome_print renderer.
* 0.24 pt is the dot size of a 300 dpi printer, if the plot is printed at scale 1:1 */
#define GOG_RENDERER_HAIR_LINE_WIDTH 0.24
enum {
RENDERER_PROP_0,
RENDERER_PROP_MODEL,
RENDERER_PROP_VIEW,
RENDERER_PROP_LOGICAL_WIDTH_PTS,
RENDERER_PROP_LOGICAL_HEIGHT_PTS,
RENDERER_PROP_ZOOM
};
enum {
RENDERER_SIGNAL_REQUEST_UPDATE,
RENDERER_SIGNAL_LAST
};
static gulong renderer_signals [RENDERER_SIGNAL_LAST] = { 0, };
static GObjectClass *parent_klass;
static void
gog_renderer_finalize (GObject *obj)
{
GogRenderer *rend = GOG_RENDERER (obj);
go_line_vpath_dash_free (rend->line_dash);
rend->line_dash = NULL;
go_line_vpath_dash_free (rend->outline_dash);
rend->outline_dash = NULL;
if (rend->clip_stack != NULL)
g_warning ("Missing calls to gog_renderer_pop_clip");
if (rend->cur_style != NULL) {
g_warning ("Missing calls to gog_renderer_style_pop left dangling style references");
g_slist_foreach (rend->style_stack,
(GFunc)g_object_unref, NULL);
g_slist_free (rend->style_stack);
rend->style_stack = NULL;
g_object_unref ((gpointer)rend->cur_style);
rend->cur_style = NULL;
}
if (rend->view != NULL) {
g_object_unref (rend->view);
rend->view = NULL;
}
if (rend->font_watcher != NULL) {
go_font_cache_unregister (rend->font_watcher);
g_closure_unref (rend->font_watcher);
rend->font_watcher = NULL;
}
(*parent_klass->finalize) (obj);
}
static void
gog_renderer_set_property (GObject *obj, guint param_id,
GValue const *value, GParamSpec *pspec)
{
GogRenderer *rend = GOG_RENDERER (obj);
switch (param_id) {
case RENDERER_PROP_MODEL:
rend->model = GOG_GRAPH (g_value_get_object (value));
if (rend->view != NULL)
g_object_unref (rend->view);
rend->view = g_object_new (gog_graph_view_get_type (),
"renderer", rend,
"model", rend->model,
NULL);
gog_renderer_request_update (rend);
break;
case RENDERER_PROP_LOGICAL_WIDTH_PTS:
rend->logical_width_pts = g_value_get_double (value);
break;
case RENDERER_PROP_LOGICAL_HEIGHT_PTS:
rend->logical_height_pts = g_value_get_double (value);
break;
case RENDERER_PROP_ZOOM:
rend->zoom = g_value_get_double (value);
break;
default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
return; /* NOTE : RETURN */
}
}
static void
gog_renderer_get_property (GObject *obj, guint param_id,
GValue *value, GParamSpec *pspec)
{
GogRenderer const *rend = GOG_RENDERER (obj);
switch (param_id) {
case RENDERER_PROP_MODEL:
g_value_set_object (value, rend->model);
break;
case RENDERER_PROP_VIEW:
g_value_set_object (value, rend->view);
break;
case RENDERER_PROP_LOGICAL_WIDTH_PTS:
g_value_set_double (value, rend->logical_width_pts);
break;
case RENDERER_PROP_LOGICAL_HEIGHT_PTS:
g_value_set_double (value, rend->logical_height_pts);
break;
case RENDERER_PROP_ZOOM:
g_value_set_double (value, rend->zoom);
break;
default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
break;
}
}
void
gog_renderer_request_update (GogRenderer *renderer)
{
g_return_if_fail (GOG_RENDERER (renderer) != NULL);
if (renderer->needs_update)
return;
renderer->needs_update = TRUE;
g_signal_emit (G_OBJECT (renderer),
renderer_signals [RENDERER_SIGNAL_REQUEST_UPDATE], 0);
}
void
gog_renderer_invalidate_size_requests (GogRenderer *rend)
{
g_return_if_fail (GOG_RENDERER (rend) != NULL);
if (rend->view)
gog_renderer_request_update (rend);
}
static void
update_dash (GogRenderer *rend)
{
double size;
go_line_vpath_dash_free (rend->line_dash);
rend->line_dash = NULL;
go_line_vpath_dash_free (rend->outline_dash);
rend->outline_dash = NULL;
if (rend->cur_style == NULL)
return;
size = gog_renderer_line_size (rend, rend->cur_style->line.width);
rend->line_dash = go_line_get_vpath_dash (rend->cur_style->line.dash_type, size);
size = gog_renderer_line_size (rend, rend->cur_style->outline.width);
rend->outline_dash = go_line_get_vpath_dash (rend->cur_style->outline.dash_type, size);
}
void
gog_renderer_push_style (GogRenderer *rend, GogStyle const *style)
{
GogRendererClass *klass = GOG_RENDERER_GET_CLASS (rend);
g_return_if_fail (klass != NULL);
g_return_if_fail (GOG_STYLE (style) != NULL);
if (rend->cur_style != NULL)
rend->style_stack = g_slist_prepend (
rend->style_stack, (gpointer)rend->cur_style);
g_object_ref ((gpointer)style);
rend->cur_style = style;
if (klass->push_style)
klass->push_style (rend, style);
update_dash (rend);
}
void
gog_renderer_pop_style (GogRenderer *rend)
{
GogRendererClass *klass = GOG_RENDERER_GET_CLASS (rend);
g_return_if_fail (klass != NULL);
g_return_if_fail (rend->cur_style != NULL);
g_object_unref ((gpointer)rend->cur_style);
if (rend->style_stack != NULL) {
rend->cur_style = rend->style_stack->data;
rend->style_stack = g_slist_remove (rend->style_stack,
rend->cur_style);
} else
rend->cur_style = NULL;
if (klass->pop_style)
klass->pop_style (rend);
update_dash (rend);
}
/**
* gog_renderer_clip_push :
* @rend : #GogRenderer
* @region: #GogViewAllocation
*
* region defines the current clipping region.
**/
void
gog_renderer_clip_push (GogRenderer *rend, GogViewAllocation const *region)
{
GogRendererClip *clip;
GogRendererClass *klass = GOG_RENDERER_GET_CLASS (rend);
g_return_if_fail (klass != NULL);
clip = g_new (GogRendererClip, 1);
clip->area = *region;
rend->clip_stack = g_slist_prepend (rend->clip_stack, clip);
rend->cur_clip = clip;
(klass->clip_push) (rend, clip);
}
/**
* gog_renderer_clip_pop :
* @rend : #GogRenderer
*
* End the current clipping.
**/
void
gog_renderer_clip_pop (GogRenderer *rend)
{
GogRendererClip *clip;
GogRendererClass *klass = GOG_RENDERER_GET_CLASS (rend);
g_return_if_fail (klass != NULL);
g_return_if_fail (rend->clip_stack != NULL);
clip = (GogRendererClip *) rend->clip_stack->data;
(klass->clip_pop) (rend, clip);
g_free (clip);
rend->clip_stack = g_slist_delete_link (rend->clip_stack, rend->clip_stack);
if (rend->clip_stack != NULL)
rend->cur_clip = (GogRendererClip *) rend->clip_stack->data;
else
rend->cur_clip = NULL;
}
/**
* gog_renderer_draw_sharp_polygon :
* @rend : #GogRenderer
* @path : #ArtVpath
* @bound : #GogViewAllocation optional clip
*
* Draws @path using the outline elements of the current style,
* trying to make line with sharp edge.
**/
void
gog_renderer_draw_sharp_path (GogRenderer *rend, ArtVpath *path,
GogViewAllocation const *bound)
{
GogRendererClass *klass = GOG_RENDERER_GET_CLASS (rend);
g_return_if_fail (klass != NULL);
g_return_if_fail (rend->cur_style != NULL);
if (klass->sharp_path) {
(klass->sharp_path) (rend, path,
gog_renderer_line_size (rend, rend->cur_style->line.width));
}
(klass->draw_path) (rend, path, bound);
}
/**
* gog_renderer_draw_polygon :
* @rend : #GogRenderer
* @path : #ArtVpath
* @bound : #GogViewAllocation optional clip
*
* Draws @path using the outline elements of the current style.
**/
void
gog_renderer_draw_path (GogRenderer *rend, ArtVpath const *path,
GogViewAllocation const *bound)
{
GogRendererClass *klass = GOG_RENDERER_GET_CLASS (rend);
g_return_if_fail (klass != NULL);
g_return_if_fail (rend->cur_style != NULL);
(klass->draw_path) (rend, path, bound);
}
/**
* gog_renderer_draw_sharp_polygon :
* @rend : #GogRenderer
* @path : #ArtVpath
* @narrow : if TRUE skip any outline the current style specifies.
* @bound : #GogViewAllocation optional clip
*
* Draws @path and fills it with the fill elements of the current style,
* trying to draw line with sharp edge.
* If @narrow is false it alos outlines it using the outline elements.
**/
void
gog_renderer_draw_sharp_polygon (GogRenderer *rend, ArtVpath *path, gboolean narrow,
GogViewAllocation const *bound)
{
GogRendererClass *klass = GOG_RENDERER_GET_CLASS (rend);
g_return_if_fail (klass != NULL);
g_return_if_fail (rend->cur_style != NULL);
if (klass->sharp_path) {
(klass->sharp_path) (rend, path,
gog_renderer_line_size (rend, rend->cur_style->outline.width));
}
(klass->draw_polygon) (rend, path, narrow, bound);
}
/**
* gog_renderer_draw_polygon :
* @rend : #GogRenderer
* @path : #ArtVpath
* @narrow : if TRUE skip any outline the current style specifies.
* @bound : #GogViewAllocation optional clip
*
* Draws @path and fills it with the fill elements of the current style.
* If @narrow is false it alos outlines it using the outline elements.
**/
void
gog_renderer_draw_polygon (GogRenderer *rend, ArtVpath const *path, gboolean narrow,
GogViewAllocation const *bound)
{
GogRendererClass *klass = GOG_RENDERER_GET_CLASS (rend);
g_return_if_fail (klass != NULL);
g_return_if_fail (rend->cur_style != NULL);
(klass->draw_polygon) (rend, path, narrow, bound);
}
/**
* gog_renderer_draw_text :
* @rend : #GogRenderer
* @text : the string to draw
* @pos : #GogViewAllocation
* @anchor : #GtkAnchorType how to draw relative to @pos
* @result : an optionally NULL #GogViewAllocation
*
* Have @rend draw @text in the at @pos.{x,y} anchored by the @anchor corner.
* If @pos.w or @pos.h are >= 0 then clip the results to less than that size.
* If @result is supplied it will recieve the actual position of the result.
**/
void
gog_renderer_draw_text (GogRenderer *rend, char const *text,
GogViewAllocation const *pos, GtkAnchorType anchor,
GogViewAllocation *result)
{
GogRendererClass *klass = GOG_RENDERER_GET_CLASS (rend);
g_return_if_fail (klass != NULL);
g_return_if_fail (rend->cur_style != NULL);
g_return_if_fail (text != NULL);
if (*text == '\0') {
if (result != NULL) {
result->x = pos->x;
result->y = pos->y;
result->w = 0.;
result->h = 0.;
}
return;
}
(klass->draw_text) (rend, text, pos, anchor, result);
}
/**
* gog_renderer_draw_marker :
* @rend : #GogRenderer
* @pos : #ArtPoint
**/
void
gog_renderer_draw_marker (GogRenderer *rend, double x, double y)
{
GogRendererClass *klass = GOG_RENDERER_GET_CLASS (rend);
g_return_if_fail (klass != NULL);
g_return_if_fail (rend->cur_style != NULL);
(klass->draw_marker) (rend, x, y);
}
/**
* gog_renderer_measure_text :
* @rend : #GogRenderer
* @text : the string to draw
* @size : #GogViewRequisition to store the size of @text.
**/
void
gog_renderer_measure_text (GogRenderer *rend,
char const *text, GogViewRequisition *size)
{
GogRendererClass *klass = GOG_RENDERER_GET_CLASS (rend);
g_return_if_fail (klass != NULL);
g_return_if_fail (rend->cur_style != NULL);
g_return_if_fail (text != NULL);
if (*text == '\0') {
/* Make sure invisible things don't skew size */
size->w = size->h = 0;
return;
}
(klass->measure_text) (rend, text, size);
/* Make sure invisible things don't skew size */
if (size->w == 0)
size->h = 0;
else if (size->h == 0)
size->w = 0;
}
static void
cb_font_removed (GogRenderer *rend, GOFont const *font)
{
GogRendererClass *klass = GOG_RENDERER_GET_CLASS (rend);
g_return_if_fail (klass != NULL);
gog_debug (0, g_warning ("notify a '%s' that %p is invalid",
G_OBJECT_TYPE_NAME (rend), font););
if (klass->font_removed)
(klass->font_removed) (rend, font);
}
static void
gog_renderer_class_init (GogRendererClass *renderer_klass)
{
GObjectClass *gobject_klass = (GObjectClass *)renderer_klass;
parent_klass = g_type_class_peek_parent (gobject_klass);
gobject_klass->finalize = gog_renderer_finalize;
gobject_klass->set_property = gog_renderer_set_property;
gobject_klass->get_property = gog_renderer_get_property;
renderer_klass->sharp_path = NULL;
renderer_klass->line_size = NULL;
g_object_class_install_property (gobject_klass, RENDERER_PROP_MODEL,
g_param_spec_object ("model", "model",
"the GogGraph this renderer displays",
GOG_GRAPH_TYPE, G_PARAM_READWRITE));
g_object_class_install_property (gobject_klass, RENDERER_PROP_VIEW,
g_param_spec_object ("view", "view",
"the GogView this renderer is displaying",
GOG_VIEW_TYPE, G_PARAM_READABLE));
g_object_class_install_property (gobject_klass, RENDERER_PROP_LOGICAL_WIDTH_PTS,
g_param_spec_double ("logical_width_pts", "Logical Width Pts",
"Logical width of the drawing area in pts",
0, G_MAXDOUBLE, 0, G_PARAM_READWRITE));
g_object_class_install_property (gobject_klass, RENDERER_PROP_LOGICAL_HEIGHT_PTS,
g_param_spec_double ("logical_height_pts", "Logical Height Pts",
"Logical height of the drawing area in pts",
0, G_MAXDOUBLE, 0, G_PARAM_READWRITE));
g_object_class_install_property (gobject_klass, RENDERER_PROP_ZOOM,
g_param_spec_double ("zoom", "zoom Height Pts",
"global scale factor",
1., G_MAXDOUBLE, 1., G_PARAM_READWRITE));
renderer_signals [RENDERER_SIGNAL_REQUEST_UPDATE] = g_signal_new ("request-update",
G_TYPE_FROM_CLASS (renderer_klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GogRendererClass, request_update),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
}
static void
gog_renderer_init (GogRenderer *rend)
{
rend->cur_clip = NULL;
rend->clip_stack = NULL;
rend->line_dash = NULL;
rend->outline_dash = NULL;
rend->needs_update = FALSE;
rend->cur_style = NULL;
rend->style_stack = NULL;
rend->zoom = rend->scale = rend->scale_x = rend->scale_y = 1.;
rend->logical_width_pts = GO_CM_TO_PT ((double)12);
rend->logical_height_pts = GO_CM_TO_PT ((double)8);
rend->font_watcher = g_cclosure_new_swap (G_CALLBACK (cb_font_removed),
rend, NULL);
go_font_cache_register (rend->font_watcher);
}
GSF_CLASS (GogRenderer, gog_renderer,
gog_renderer_class_init, gog_renderer_init,
G_TYPE_OBJECT)
/**
* gog_renderer_draw_rectangle :
* @renderer : #GogRenderer
* @rect : #GogViewAllocation
* @bound : #GogViewAllocation optional clip
*
* A utility routine to build a vpath in @rect.
**/
static void
draw_rectangle (GogRenderer *rend, GogViewAllocation const *rect,
GogViewAllocation const *bound, gboolean sharp)
{
gboolean const narrow = (rect->w < 3.) || (rect->h < 3.);
double o, o_2;
ArtVpath path[6];
if (!narrow) {
o = gog_renderer_line_size (rend, rend->cur_style->outline.width);
o_2 = o / 2.;
} else
o = o_2 = 0.;
path[0].code = ART_MOVETO;
path[1].code = ART_LINETO;
path[2].code = ART_LINETO;
path[3].code = ART_LINETO;
path[4].code = ART_LINETO;
path[5].code = ART_END;
path[0].x = path[1].x = path[4].x = rect->x + o_2;
path[2].x = path[3].x = path[0].x + rect->w - o;
path[0].y = path[3].y = path[4].y = rect->y + o_2;
path[1].y = path[2].y = path[0].y + rect->h - o;
if (sharp)
gog_renderer_draw_sharp_polygon (rend, path, narrow, bound);
else
gog_renderer_draw_polygon (rend, path, narrow, bound);
}
void
gog_renderer_draw_sharp_rectangle (GogRenderer *rend, GogViewAllocation const *rect,
GogViewAllocation const *bound)
{
draw_rectangle (rend, rect, bound, TRUE);
}
void
gog_renderer_draw_rectangle (GogRenderer *rend, GogViewAllocation const *rect,
GogViewAllocation const *bound)
{
draw_rectangle (rend, rect, bound, FALSE);
}
double
gog_renderer_line_size (GogRenderer const *rend, double width)
{
GogRendererClass *klass = GOG_RENDERER_GET_CLASS (rend);
if (klass->line_size)
return (klass->line_size) (rend, width);
if (go_sub_epsilon (width) <= 0.)
width = GOG_RENDERER_HAIR_LINE_WIDTH;
return width * rend->scale;
}
double
gog_renderer_pt2r_x (GogRenderer const *rend, double d)
{
return d * rend->scale_x;
}
double
gog_renderer_pt2r_y (GogRenderer const *rend, double d)
{
return d * rend->scale_y;
}
double
gog_renderer_pt2r (GogRenderer const *rend, double d)
{
return d * rend->scale;
}