/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- * * Copyright (C) 2006-2007 Richard Hughes <richard@hughsie.com> * * Licensed under the GNU General Public License Version 2 * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "config.h" #include <gtk/gtk.h> #include <pango/pangocairo.h> #include <glib/gi18n.h> #include <stdlib.h> #include "gpm-common.h" #include "gpm-point-obj.h" #include "gpm-graph-widget.h" #include "egg-debug.h" #include "egg-color.h" #include "egg-precision.h" #if GTK_CHECK_VERSION (3, 0, 0) #include <math.h> #endif G_DEFINE_TYPE (GpmGraphWidget, gpm_graph_widget, GTK_TYPE_DRAWING_AREA); #define GPM_GRAPH_WIDGET_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GPM_TYPE_GRAPH_WIDGET, GpmGraphWidgetPrivate)) #define GPM_GRAPH_WIDGET_FONT "Sans 8" struct GpmGraphWidgetPrivate { gboolean use_grid; gboolean use_legend; gboolean autorange_x; gboolean autorange_y; GSList *key_data; /* lines */ gint stop_x; gint stop_y; gint start_x; gint start_y; gint box_x; /* size of the white box, not the widget */ gint box_y; gint box_width; gint box_height; gfloat unit_x; /* 10th width of graph */ gfloat unit_y; /* 10th width of graph */ GpmGraphWidgetType type_x; GpmGraphWidgetType type_y; gchar *title; cairo_t *cr; PangoLayout *layout; GPtrArray *data_list; GPtrArray *plot_list; }; #if GTK_CHECK_VERSION (3, 0, 0) static gboolean gpm_graph_widget_draw (GtkWidget *graph, cairo_t *cr); #else static gboolean gpm_graph_widget_expose (GtkWidget *graph, GdkEventExpose *event); #endif static void gpm_graph_widget_finalize (GObject *object); enum { PROP_0, PROP_USE_LEGEND, PROP_USE_GRID, PROP_TYPE_X, PROP_TYPE_Y, PROP_AUTORANGE_X, PROP_AUTORANGE_Y, PROP_START_X, PROP_START_Y, PROP_STOP_X, PROP_STOP_Y, }; /** * gpm_graph_widget_key_data_clear: **/ static gboolean gpm_graph_widget_key_data_clear (GpmGraphWidget *graph) { GpmGraphWidgetKeyData *keyitem; guint i; g_return_val_if_fail (GPM_IS_GRAPH_WIDGET (graph), FALSE); /* remove items in list and free */ for (i=0; i<g_slist_length (graph->priv->key_data); i++) { keyitem = (GpmGraphWidgetKeyData *) g_slist_nth_data (graph->priv->key_data, i); g_free (keyitem->desc); g_free (keyitem); } g_slist_free (graph->priv->key_data); graph->priv->key_data = NULL; return TRUE; } /** * gpm_graph_widget_key_data_add: **/ gboolean gpm_graph_widget_key_data_add (GpmGraphWidget *graph, guint32 color, const gchar *desc) { GpmGraphWidgetKeyData *keyitem; g_return_val_if_fail (GPM_IS_GRAPH_WIDGET (graph), FALSE); egg_debug ("add to list %s", desc); keyitem = g_new0 (GpmGraphWidgetKeyData, 1); keyitem->color = color; keyitem->desc = g_strdup (desc); graph->priv->key_data = g_slist_append (graph->priv->key_data, (gpointer) keyitem); return TRUE; } /** * up_graph_get_property: **/ static void up_graph_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GpmGraphWidget *graph = GPM_GRAPH_WIDGET (object); switch (prop_id) { case PROP_USE_LEGEND: g_value_set_boolean (value, graph->priv->use_legend); break; case PROP_USE_GRID: g_value_set_boolean (value, graph->priv->use_grid); break; case PROP_TYPE_X: g_value_set_uint (value, graph->priv->type_x); break; case PROP_TYPE_Y: g_value_set_uint (value, graph->priv->type_y); break; case PROP_AUTORANGE_X: g_value_set_boolean (value, graph->priv->autorange_x); break; case PROP_AUTORANGE_Y: g_value_set_boolean (value, graph->priv->autorange_y); break; case PROP_START_X: g_value_set_int (value, graph->priv->start_x); break; case PROP_START_Y: g_value_set_int (value, graph->priv->start_y); break; case PROP_STOP_X: g_value_set_int (value, graph->priv->stop_x); break; case PROP_STOP_Y: g_value_set_int (value, graph->priv->stop_y); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } /** * up_graph_set_property: **/ static void up_graph_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GpmGraphWidget *graph = GPM_GRAPH_WIDGET (object); switch (prop_id) { case PROP_USE_LEGEND: graph->priv->use_legend = g_value_get_boolean (value); break; case PROP_USE_GRID: graph->priv->use_grid = g_value_get_boolean (value); break; case PROP_TYPE_X: graph->priv->type_x = g_value_get_uint (value); break; case PROP_TYPE_Y: graph->priv->type_y = g_value_get_uint (value); break; case PROP_AUTORANGE_X: graph->priv->autorange_x = g_value_get_boolean (value); break; case PROP_AUTORANGE_Y: graph->priv->autorange_y = g_value_get_boolean (value); break; case PROP_START_X: graph->priv->start_x = g_value_get_int (value); break; case PROP_START_Y: graph->priv->start_y = g_value_get_int (value); break; case PROP_STOP_X: graph->priv->stop_x = g_value_get_int (value); break; case PROP_STOP_Y: graph->priv->stop_y = g_value_get_int (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } /* refresh widget */ gtk_widget_hide (GTK_WIDGET (graph)); gtk_widget_show (GTK_WIDGET (graph)); } /** * gpm_graph_widget_class_init: * @class: This graph class instance **/ static void gpm_graph_widget_class_init (GpmGraphWidgetClass *class) { GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); GObjectClass *object_class = G_OBJECT_CLASS (class); #if GTK_CHECK_VERSION (3, 0, 0) widget_class->draw = gpm_graph_widget_draw; #else widget_class->expose_event = gpm_graph_widget_expose; #endif object_class->get_property = up_graph_get_property; object_class->set_property = up_graph_set_property; object_class->finalize = gpm_graph_widget_finalize; g_type_class_add_private (class, sizeof (GpmGraphWidgetPrivate)); /* properties */ g_object_class_install_property (object_class, PROP_USE_LEGEND, g_param_spec_boolean ("use-legend", NULL, NULL, FALSE, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_USE_GRID, g_param_spec_boolean ("use-grid", NULL, NULL, TRUE, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_TYPE_X, g_param_spec_uint ("type-x", NULL, NULL, GPM_GRAPH_WIDGET_TYPE_INVALID, GPM_GRAPH_WIDGET_TYPE_UNKNOWN, GPM_GRAPH_WIDGET_TYPE_TIME, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_TYPE_Y, g_param_spec_uint ("type-y", NULL, NULL, GPM_GRAPH_WIDGET_TYPE_INVALID, GPM_GRAPH_WIDGET_TYPE_UNKNOWN, GPM_GRAPH_WIDGET_TYPE_PERCENTAGE, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_AUTORANGE_X, g_param_spec_boolean ("autorange-x", NULL, NULL, TRUE, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_AUTORANGE_Y, g_param_spec_boolean ("autorange-y", NULL, NULL, TRUE, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_START_X, g_param_spec_int ("start-x", NULL, NULL, G_MININT, G_MAXINT, 0, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_START_Y, g_param_spec_int ("start-y", NULL, NULL, G_MININT, G_MAXINT, 0, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_STOP_X, g_param_spec_int ("stop-x", NULL, NULL, G_MININT, G_MAXINT, 60, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_STOP_Y, g_param_spec_int ("stop-y", NULL, NULL, G_MININT, G_MAXINT, 100, G_PARAM_READWRITE)); } /** * gpm_graph_widget_init: * @graph: This class instance **/ static void gpm_graph_widget_init (GpmGraphWidget *graph) { PangoFontMap *fontmap; PangoContext *context; PangoFontDescription *desc; graph->priv = GPM_GRAPH_WIDGET_GET_PRIVATE (graph); graph->priv->start_x = 0; graph->priv->start_y = 0; graph->priv->stop_x = 60; graph->priv->stop_y = 100; graph->priv->use_grid = TRUE; graph->priv->use_legend = FALSE; graph->priv->data_list = g_ptr_array_new_with_free_func ((GDestroyNotify) g_ptr_array_unref); graph->priv->plot_list = g_ptr_array_new (); graph->priv->key_data = NULL; graph->priv->type_x = GPM_GRAPH_WIDGET_TYPE_TIME; graph->priv->type_y = GPM_GRAPH_WIDGET_TYPE_PERCENTAGE; /* do pango stuff */ fontmap = pango_cairo_font_map_get_default (); context = pango_font_map_create_context (PANGO_FONT_MAP (fontmap)); pango_context_set_base_gravity (context, PANGO_GRAVITY_AUTO); graph->priv->layout = pango_layout_new (context); desc = pango_font_description_from_string (GPM_GRAPH_WIDGET_FONT); pango_layout_set_font_description (graph->priv->layout, desc); pango_font_description_free (desc); } /** * gpm_graph_widget_data_clear: **/ gboolean gpm_graph_widget_data_clear (GpmGraphWidget *graph) { g_return_val_if_fail (GPM_IS_GRAPH_WIDGET (graph), FALSE); g_ptr_array_set_size (graph->priv->data_list, 0); g_ptr_array_set_size (graph->priv->plot_list, 0); return TRUE; } /** * gpm_graph_widget_finalize: * @object: This graph class instance **/ static void gpm_graph_widget_finalize (GObject *object) { PangoContext *context; GpmGraphWidget *graph = (GpmGraphWidget*) object; /* clear key and data */ gpm_graph_widget_key_data_clear (graph); gpm_graph_widget_data_clear (graph); /* free data */ g_ptr_array_unref (graph->priv->data_list); g_ptr_array_unref (graph->priv->plot_list); context = pango_layout_get_context (graph->priv->layout); g_object_unref (graph->priv->layout); g_object_unref (context); G_OBJECT_CLASS (gpm_graph_widget_parent_class)->finalize (object); } /** * gpm_graph_widget_data_assign: * @graph: This class instance * @data: an array of GpmPointObj's * * Sets the data for the graph **/ gboolean gpm_graph_widget_data_assign (GpmGraphWidget *graph, GpmGraphWidgetPlot plot, GPtrArray *data) { GPtrArray *copy; GpmPointObj *obj; guint i; g_return_val_if_fail (data != NULL, FALSE); g_return_val_if_fail (GPM_IS_GRAPH_WIDGET (graph), FALSE); /* make a deep copy */ copy = g_ptr_array_new_with_free_func ((GDestroyNotify) gpm_point_obj_free); for (i=0; i<data->len; i++) { obj = gpm_point_obj_copy (g_ptr_array_index (data, i)); g_ptr_array_add (copy, obj); } /* get the new data */ g_ptr_array_add (graph->priv->data_list, copy); g_ptr_array_add (graph->priv->plot_list, GUINT_TO_POINTER(plot)); /* refresh */ gtk_widget_queue_draw (GTK_WIDGET (graph)); return TRUE; } /** * gpm_get_axis_label: * @axis: The axis type, e.g. GPM_GRAPH_WIDGET_TYPE_TIME * @value: The data value, e.g. 120 * * Unit is: * GPM_GRAPH_WIDGET_TYPE_TIME: seconds * GPM_GRAPH_WIDGET_TYPE_POWER: Wh (not Ah) * GPM_GRAPH_WIDGET_TYPE_PERCENTAGE: % * * Return value: a string value depending on the axis type and the value. **/ static gchar * gpm_get_axis_label (GpmGraphWidgetType axis, gfloat value) { gchar *text = NULL; if (axis == GPM_GRAPH_WIDGET_TYPE_TIME) { gint time_s = abs((gint) value); gint minutes = time_s / 60; gint seconds = time_s - (minutes * 60); gint hours = minutes / 60; gint days = hours / 24; minutes = minutes - (hours * 60); hours = hours - (days * 24); if (days > 0) { if (hours == 0) { /*Translators: This is %i days*/ text = g_strdup_printf (_("%id"), days); } else { /*Translators: This is %i days %02i hours*/ text = g_strdup_printf (_("%id%02ih"), days, hours); } } else if (hours > 0) { if (minutes == 0) { /*Translators: This is %i hours*/ text = g_strdup_printf (_("%ih"), hours); } else { /*Translators: This is %i hours %02i minutes*/ text = g_strdup_printf (_("%ih%02im"), hours, minutes); } } else if (minutes > 0) { if (seconds == 0) { /*Translators: This is %2i minutes*/ text = g_strdup_printf (_("%2im"), minutes); } else { /*Translators: This is %2i minutes %02i seconds*/ text = g_strdup_printf (_("%2im%02i"), minutes, seconds); } } else { /*Translators: This is %2i seconds*/ text = g_strdup_printf (_("%2is"), seconds); } } else if (axis == GPM_GRAPH_WIDGET_TYPE_PERCENTAGE) { /*Translators: This is %i Percentage*/ text = g_strdup_printf (_("%i%%"), (gint) value); } else if (axis == GPM_GRAPH_WIDGET_TYPE_POWER) { /*Translators: This is %.1f Watts*/ text = g_strdup_printf (_("%.1fW"), value); } else if (axis == GPM_GRAPH_WIDGET_TYPE_FACTOR) { text = g_strdup_printf ("%.1f", value); } else if (axis == GPM_GRAPH_WIDGET_TYPE_VOLTAGE) { /*Translators: This is %.1f Volts*/ text = g_strdup_printf (_("%.1fV"), value); } else { text = g_strdup_printf ("%i", (gint) value); } return text; } /** * gpm_graph_widget_draw_grid: * @graph: This class instance * @cr: Cairo drawing context * * Draw the 10x10 dotted grid onto the graph. **/ static void gpm_graph_widget_draw_grid (GpmGraphWidget *graph, cairo_t *cr) { guint i; gfloat b; gdouble dotted[] = {1., 2.}; gfloat divwidth = (gfloat)graph->priv->box_width / 10.0f; gfloat divheight = (gfloat)graph->priv->box_height / 10.0f; cairo_save (cr); cairo_set_line_width (cr, 1); cairo_set_dash (cr, dotted, 2, 0.0); /* do vertical lines */ cairo_set_source_rgb (cr, 0.1, 0.1, 0.1); for (i=1; i<10; i++) { b = graph->priv->box_x + ((gfloat) i * divwidth); cairo_move_to (cr, (gint)b + 0.5f, graph->priv->box_y); cairo_line_to (cr, (gint)b + 0.5f, graph->priv->box_y + graph->priv->box_height); cairo_stroke (cr); } /* do horizontal lines */ for (i=1; i<10; i++) { b = graph->priv->box_y + ((gfloat) i * divheight); cairo_move_to (cr, graph->priv->box_x, (gint)b + 0.5f); cairo_line_to (cr, graph->priv->box_x + graph->priv->box_width, (int)b + 0.5f); cairo_stroke (cr); } cairo_restore (cr); } /** * gpm_graph_widget_draw_labels: * @graph: This class instance * @cr: Cairo drawing context * * Draw the X and the Y labels onto the graph. **/ static void gpm_graph_widget_draw_labels (GpmGraphWidget *graph, cairo_t *cr) { guint i; gfloat b; gchar *text; gfloat value; gfloat divwidth = (gfloat)graph->priv->box_width / 10.0f; gfloat divheight = (gfloat)graph->priv->box_height / 10.0f; gint length_x = graph->priv->stop_x - graph->priv->start_x; gint length_y = graph->priv->stop_y - graph->priv->start_y; PangoRectangle ink_rect, logical_rect; gfloat offsetx = 0; gfloat offsety = 0; cairo_save (cr); /* do x text */ cairo_set_source_rgb (cr, 0, 0, 0); for (i=0; i<11; i++) { b = graph->priv->box_x + ((gfloat) i * divwidth); value = ((length_x / 10.0f) * (gfloat) i) + (gfloat) graph->priv->start_x; text = gpm_get_axis_label (graph->priv->type_x, value); pango_layout_set_text (graph->priv->layout, text, -1); pango_layout_get_pixel_extents (graph->priv->layout, &ink_rect, &logical_rect); /* have data points 0 and 10 bounded, but 1..9 centered */ if (i == 0) offsetx = 2.0; else if (i == 10) offsetx = ink_rect.width; else offsetx = (ink_rect.width / 2.0f); cairo_move_to (cr, b - offsetx, graph->priv->box_y + graph->priv->box_height + 2.0); pango_cairo_show_layout (cr, graph->priv->layout); g_free (text); } /* do y text */ for (i=0; i<11; i++) { b = graph->priv->box_y + ((gfloat) i * divheight); value = ((gfloat) length_y / 10.0f) * (10 - (gfloat) i) + graph->priv->start_y; text = gpm_get_axis_label (graph->priv->type_y, value); pango_layout_set_text (graph->priv->layout, text, -1); pango_layout_get_pixel_extents (graph->priv->layout, &ink_rect, &logical_rect); /* have data points 0 and 10 bounded, but 1..9 centered */ if (i == 10) offsety = 0; else if (i == 0) offsety = ink_rect.height; else offsety = (ink_rect.height / 2.0f); offsetx = ink_rect.width + 7; offsety -= 10; cairo_move_to (cr, graph->priv->box_x - offsetx - 2, b + offsety); pango_cairo_show_layout (cr, graph->priv->layout); g_free (text); } cairo_restore (cr); } /** * gpm_graph_widget_get_y_label_max_width: * @graph: This class instance * @cr: Cairo drawing context * * Draw the X and the Y labels onto the graph. **/ static guint gpm_graph_widget_get_y_label_max_width (GpmGraphWidget *graph, cairo_t *cr) { guint i; gchar *text; gint value; gint length_y = graph->priv->stop_y - graph->priv->start_y; PangoRectangle ink_rect, logical_rect; guint biggest = 0; /* do y text */ for (i=0; i<11; i++) { value = (length_y / 10) * (10 - (gfloat) i) + graph->priv->start_y; text = gpm_get_axis_label (graph->priv->type_y, value); pango_layout_set_text (graph->priv->layout, text, -1); pango_layout_get_pixel_extents (graph->priv->layout, &ink_rect, &logical_rect); if (ink_rect.width > (gint) biggest) biggest = ink_rect.width; g_free (text); } return biggest; } /** * gpm_graph_widget_autorange_x: * @graph: This class instance * * Autoranges the graph axis depending on the axis type, and the maximum * value of the data. We have to be careful to choose a number that gives good * resolution but also a number that scales "well" to a 10x10 grid. **/ static void gpm_graph_widget_autorange_x (GpmGraphWidget *graph) { gfloat biggest_x = G_MINFLOAT; gfloat smallest_x = G_MAXFLOAT; guint rounding_x = 1; GPtrArray *data; GpmPointObj *point; guint i, j; guint len = 0; GPtrArray *array; array = graph->priv->data_list; /* find out if we have no data */ for (j=0; j<array->len; j++) { data = g_ptr_array_index (array, j); len = data->len; if (len > 0) break; } /* no data in any array */ if (len == 0) { egg_debug ("no data"); graph->priv->start_x = 0; graph->priv->stop_x = 10; return; } /* get the range for the graph */ for (j=0; j<array->len; j++) { data = g_ptr_array_index (array, j); for (i=0; i < data->len; i++) { point = (GpmPointObj *) g_ptr_array_index (data, i); if (point->x > biggest_x) biggest_x = point->x; if (point->x < smallest_x) smallest_x = point->x; } } egg_debug ("Data range is %f<x<%f", smallest_x, biggest_x); /* don't allow no difference */ if (biggest_x - smallest_x < 0.0001) { biggest_x++; smallest_x--; } if (graph->priv->type_x == GPM_GRAPH_WIDGET_TYPE_PERCENTAGE) { rounding_x = 10; } else if (graph->priv->type_x == GPM_GRAPH_WIDGET_TYPE_FACTOR) { rounding_x = 1; } else if (graph->priv->type_x == GPM_GRAPH_WIDGET_TYPE_POWER) { rounding_x = 10; } else if (graph->priv->type_x == GPM_GRAPH_WIDGET_TYPE_VOLTAGE) { rounding_x = 1000; } else if (graph->priv->type_x == GPM_GRAPH_WIDGET_TYPE_TIME) { if (biggest_x-smallest_x < 150) rounding_x = 150; else if (biggest_x-smallest_x < 5*60) rounding_x = 5 * 60; else rounding_x = 10 * 60; } graph->priv->start_x = egg_precision_round_down (smallest_x, rounding_x); graph->priv->stop_x = egg_precision_round_up (biggest_x, rounding_x); egg_debug ("Processed(1) range is %i<x<%i", graph->priv->start_x, graph->priv->stop_x); /* if percentage, and close to the end points, then extend */ if (graph->priv->type_x == GPM_GRAPH_WIDGET_TYPE_PERCENTAGE) { if (graph->priv->stop_x >= 90) graph->priv->stop_x = 100; if (graph->priv->start_x > 0 && graph->priv->start_x <= 10) graph->priv->start_x = 0; } else if (graph->priv->type_x == GPM_GRAPH_WIDGET_TYPE_TIME) { if (graph->priv->start_x > 0 && graph->priv->start_x <= 60*10) graph->priv->start_x = 0; } egg_debug ("Processed range is %i<x<%i", graph->priv->start_x, graph->priv->stop_x); } /** * gpm_graph_widget_autorange_y: * @graph: This class instance * * Autoranges the graph axis depending on the axis type, and the maximum * value of the data. We have to be careful to choose a number that gives good * resolution but also a number that scales "well" to a 10x10 grid. **/ static void gpm_graph_widget_autorange_y (GpmGraphWidget *graph) { gfloat biggest_y = G_MINFLOAT; gfloat smallest_y = G_MAXFLOAT; guint rounding_y = 1; GPtrArray *data; GpmPointObj *point; guint i, j; guint len = 0; GPtrArray *array; array = graph->priv->data_list; /* find out if we have no data */ for (j=0; j<array->len; j++) { data = g_ptr_array_index (array, j); len = data->len; if (len > 0) break; } /* no data in any array */ if (len == 0) { egg_debug ("no data"); graph->priv->start_y = 0; graph->priv->stop_y = 10; return; } /* get the range for the graph */ for (j=0; j<array->len; j++) { data = g_ptr_array_index (array, j); for (i=0; i < data->len; i++) { point = (GpmPointObj *) g_ptr_array_index (data, i); if (point->y > biggest_y) biggest_y = point->y; if (point->y < smallest_y) smallest_y = point->y; } } egg_debug ("Data range is %f<y<%f", smallest_y, biggest_y); /* don't allow no difference */ if (biggest_y - smallest_y < 0.0001) { biggest_y++; smallest_y--; } if (graph->priv->type_y == GPM_GRAPH_WIDGET_TYPE_PERCENTAGE) { rounding_y = 10; } else if (graph->priv->type_y == GPM_GRAPH_WIDGET_TYPE_FACTOR) { rounding_y = 1; } else if (graph->priv->type_y == GPM_GRAPH_WIDGET_TYPE_POWER) { rounding_y = 10; } else if (graph->priv->type_y == GPM_GRAPH_WIDGET_TYPE_VOLTAGE) { rounding_y = 1000; } else if (graph->priv->type_y == GPM_GRAPH_WIDGET_TYPE_TIME) { if (biggest_y-smallest_y < 150) rounding_y = 150; else if (biggest_y < 5*60) rounding_y = 5 * 60; else rounding_y = 10 * 60; } graph->priv->start_y = egg_precision_round_down (smallest_y, rounding_y); graph->priv->stop_y = egg_precision_round_up (biggest_y, rounding_y); /* a factor graph always is centered around zero */ if (graph->priv->type_y == GPM_GRAPH_WIDGET_TYPE_FACTOR) { if (abs (graph->priv->stop_y) > abs (graph->priv->start_y)) graph->priv->start_y = -graph->priv->stop_y; else graph->priv->stop_y = -graph->priv->start_y; } egg_debug ("Processed(1) range is %i<y<%i", graph->priv->start_y, graph->priv->stop_y); if (graph->priv->type_y == GPM_GRAPH_WIDGET_TYPE_PERCENTAGE) { if (graph->priv->stop_y >= 90) graph->priv->stop_y = 100; if (graph->priv->start_y > 0 && graph->priv->start_y <= 10) graph->priv->start_y = 0; } else if (graph->priv->type_y == GPM_GRAPH_WIDGET_TYPE_TIME) { if (graph->priv->start_y <= 60*10) graph->priv->start_y = 0; } egg_debug ("Processed range is %i<y<%i", graph->priv->start_y, graph->priv->stop_y); } /** * gpm_graph_widget_set_color: * @cr: Cairo drawing context * @color: The color enum **/ static void gpm_graph_widget_set_color (cairo_t *cr, guint32 color) { guint8 r, g, b; egg_color_to_rgb (color, &r, &g, &b); cairo_set_source_rgb (cr, ((gdouble) r)/256.0f, ((gdouble) g)/256.0f, ((gdouble) b)/256.0f); } /** * gpm_graph_widget_draw_legend_line: * @cr: Cairo drawing context * @x: The X-coordinate for the center * @y: The Y-coordinate for the center * @color: The color enum * * Draw the legend line on the graph of a specified color **/ static void gpm_graph_widget_draw_legend_line (cairo_t *cr, gfloat x, gfloat y, guint32 color) { gfloat width = 10; gfloat height = 2; /* background */ cairo_rectangle (cr, (int) (x - (width/2)) + 0.5, (int) (y - (height/2)) + 0.5, width, height); gpm_graph_widget_set_color (cr, color); cairo_fill (cr); /* solid outline box */ cairo_rectangle (cr, (int) (x - (width/2)) + 0.5, (int) (y - (height/2)) + 0.5, width, height); cairo_set_source_rgb (cr, 0.1, 0.1, 0.1); cairo_set_line_width (cr, 1); cairo_stroke (cr); } /** * gpm_graph_widget_get_pos_on_graph: * @graph: This class instance * @data_x: The data X-coordinate * @data_y: The data Y-coordinate * @x: The returned X position on the cairo surface * @y: The returned Y position on the cairo surface **/ static void gpm_graph_widget_get_pos_on_graph (GpmGraphWidget *graph, gfloat data_x, gfloat data_y, float *x, float *y) { *x = graph->priv->box_x + (graph->priv->unit_x * (data_x - graph->priv->start_x)) + 1; *y = graph->priv->box_y + (graph->priv->unit_y * (gfloat)(graph->priv->stop_y - data_y)) + 1.5; } /** * gpm_graph_widget_draw_dot: **/ static void gpm_graph_widget_draw_dot (cairo_t *cr, gfloat x, gfloat y, guint32 color) { gfloat width; /* box */ width = 2.0; cairo_rectangle (cr, (gint)x + 0.5f - (width/2), (gint)y + 0.5f - (width/2), width, width); gpm_graph_widget_set_color (cr, color); cairo_fill (cr); cairo_rectangle (cr, (gint)x + 0.5f - (width/2), (gint)y + 0.5f - (width/2), width, width); cairo_set_source_rgb (cr, 0, 0, 0); cairo_set_line_width (cr, 1); cairo_stroke (cr); } /** * gpm_graph_widget_draw_line: * @graph: This class instance * @cr: Cairo drawing context * * Draw the data line onto the graph with a big green line. We should already * limit the data to < ~100 values, so this shouldn't take too long. **/ static void gpm_graph_widget_draw_line (GpmGraphWidget *graph, cairo_t *cr) { gfloat oldx, oldy; gfloat newx, newy; GPtrArray *data; GPtrArray *array; GpmGraphWidgetPlot plot; GpmPointObj *point; guint i, j; if (graph->priv->data_list->len == 0) { egg_debug ("no data"); return; } cairo_save (cr); array = graph->priv->data_list; /* do each line */ for (j=0; j<array->len; j++) { data = g_ptr_array_index (array, j); if (data->len == 0) continue; plot = GPOINTER_TO_UINT (g_ptr_array_index (graph->priv->plot_list, j)); /* get the very first point so we can work out the old */ point = (GpmPointObj *) g_ptr_array_index (data, 0); oldx = 0; oldy = 0; gpm_graph_widget_get_pos_on_graph (graph, point->x, point->y, &oldx, &oldy); if (plot == GPM_GRAPH_WIDGET_PLOT_POINTS || plot == GPM_GRAPH_WIDGET_PLOT_BOTH) gpm_graph_widget_draw_dot (cr, oldx, oldy, point->color); for (i=1; i < data->len; i++) { point = (GpmPointObj *) g_ptr_array_index (data, i); gpm_graph_widget_get_pos_on_graph (graph, point->x, point->y, &newx, &newy); /* ignore white lines */ if (point->color == 0xffffff) { oldx = newx; oldy = newy; continue; } /* draw line */ if (plot == GPM_GRAPH_WIDGET_PLOT_LINE || plot == GPM_GRAPH_WIDGET_PLOT_BOTH) { cairo_move_to (cr, oldx, oldy); cairo_line_to (cr, newx, newy); cairo_set_line_width (cr, 1.5); gpm_graph_widget_set_color (cr, point->color); cairo_stroke (cr); } /* draw data dot */ if (plot == GPM_GRAPH_WIDGET_PLOT_POINTS || plot == GPM_GRAPH_WIDGET_PLOT_BOTH) gpm_graph_widget_draw_dot (cr, newx, newy, point->color); /* save old */ oldx = newx; oldy = newy; } } cairo_restore (cr); } /** * gpm_graph_widget_draw_bounding_box: * @cr: Cairo drawing context * @x: The X-coordinate for the top-left * @y: The Y-coordinate for the top-left * @width: The item width * @height: The item height **/ static void gpm_graph_widget_draw_bounding_box (cairo_t *cr, gint x, gint y, gint width, gint height) { /* background */ cairo_rectangle (cr, x, y, width, height); cairo_set_source_rgb (cr, 1, 1, 1); cairo_fill (cr); /* solid outline box */ cairo_rectangle (cr, x + 0.5f, y + 0.5f, width - 1, height - 1); cairo_set_source_rgb (cr, 0.1, 0.1, 0.1); cairo_set_line_width (cr, 1); cairo_stroke (cr); } /** * gpm_graph_widget_draw_legend: * @cr: Cairo drawing context * @x: The X-coordinate for the top-left * @y: The Y-coordinate for the top-left * @width: The item width * @height: The item height **/ static void gpm_graph_widget_draw_legend (GpmGraphWidget *graph, gint x, gint y, gint width, gint height) { cairo_t *cr = graph->priv->cr; gint y_count; guint i; GpmGraphWidgetKeyData *keydataitem; gpm_graph_widget_draw_bounding_box (cr, x, y, width, height); y_count = y + 10; /* add the line colors to the legend */ for (i=0; i<g_slist_length (graph->priv->key_data); i++) { keydataitem = (GpmGraphWidgetKeyData *) g_slist_nth_data (graph->priv->key_data, i); if (keydataitem == NULL) { /* this shouldn't ever happen */ egg_warning ("keydataitem NULL!"); break; } gpm_graph_widget_draw_legend_line (cr, x + 8, y_count, keydataitem->color); cairo_move_to (cr, x + 8 + 10, y_count - 6); cairo_set_source_rgb (cr, 0, 0, 0); pango_layout_set_text (graph->priv->layout, keydataitem->desc, -1); pango_cairo_show_layout (cr, graph->priv->layout); y_count = y_count + GPM_GRAPH_WIDGET_LEGEND_SPACING; } } /** * gpm_graph_widget_legend_calculate_width: * @graph: This class instance * @cr: Cairo drawing context * Return value: The width of the legend, including borders. * * We have to find the maximum size of the text so we know the width of the * legend box. We can't hardcode this as the dpi or font size might differ * from machine to machine. **/ static gboolean gpm_graph_widget_legend_calculate_size (GpmGraphWidget *graph, cairo_t *cr, guint *width, guint *height) { guint i; PangoRectangle ink_rect, logical_rect; GpmGraphWidgetKeyData *keydataitem; g_return_val_if_fail (GPM_IS_GRAPH_WIDGET (graph), FALSE); /* set defaults */ *width = 0; *height = 0; /* add the line colors to the legend */ for (i=0; i<g_slist_length (graph->priv->key_data); i++) { keydataitem = (GpmGraphWidgetKeyData *) g_slist_nth_data (graph->priv->key_data, i); *height = *height + GPM_GRAPH_WIDGET_LEGEND_SPACING; pango_layout_set_text (graph->priv->layout, keydataitem->desc, -1); pango_layout_get_pixel_extents (graph->priv->layout, &ink_rect, &logical_rect); if ((gint) *width < ink_rect.width) *width = ink_rect.width; } /* have we got no entries? */ if (*width == 0 && *height == 0) return TRUE; /* add for borders */ *width += 25; *height += 3; return TRUE; } /** * gpm_graph_widget_draw_graph: * @graph: This class instance * @cr: Cairo drawing context * * Draw the complete graph, with the box, the grid, the labels and the line. **/ static void gpm_graph_widget_draw_graph (GtkWidget *graph_widget, cairo_t *cr) { GtkAllocation allocation; gint legend_x = 0; gint legend_y = 0; guint legend_height = 0; guint legend_width = 0; gfloat data_x; gfloat data_y; GpmGraphWidget *graph = (GpmGraphWidget*) graph_widget; g_return_if_fail (graph != NULL); g_return_if_fail (GPM_IS_GRAPH_WIDGET (graph)); gpm_graph_widget_legend_calculate_size (graph, cr, &legend_width, &legend_height); cairo_save (cr); /* we need this so we know the y text */ if (graph->priv->autorange_x) gpm_graph_widget_autorange_x (graph); if (graph->priv->autorange_y) gpm_graph_widget_autorange_y (graph); graph->priv->box_x = gpm_graph_widget_get_y_label_max_width (graph, cr) + 10; graph->priv->box_y = 5; gtk_widget_get_allocation (graph_widget, &allocation); graph->priv->box_height = allocation.height - (20 + graph->priv->box_y); /* make size adjustment for legend */ if (graph->priv->use_legend && legend_height > 0) { graph->priv->box_width = allocation.width - (3 + legend_width + 5 + graph->priv->box_x); legend_x = graph->priv->box_x + graph->priv->box_width + 6; legend_y = graph->priv->box_y; } else { graph->priv->box_width = allocation.width - (3 + graph->priv->box_x); } /* graph background */ gpm_graph_widget_draw_bounding_box (cr, graph->priv->box_x, graph->priv->box_y, graph->priv->box_width, graph->priv->box_height); if (graph->priv->use_grid) gpm_graph_widget_draw_grid (graph, cr); /* -3 is so we can keep the lines inside the box at both extremes */ data_x = graph->priv->stop_x - graph->priv->start_x; data_y = graph->priv->stop_y - graph->priv->start_y; graph->priv->unit_x = (float)(graph->priv->box_width - 3) / (float) data_x; graph->priv->unit_y = (float)(graph->priv->box_height - 3) / (float) data_y; gpm_graph_widget_draw_labels (graph, cr); gpm_graph_widget_draw_line (graph, cr); if (graph->priv->use_legend && legend_height > 0) gpm_graph_widget_draw_legend (graph, legend_x, legend_y, legend_width, legend_height); cairo_restore (cr); } /** * gpm_graph_widget_expose: * @graph: This class instance * @event: The expose event * * Just repaint the entire graph widget on expose. **/ static gboolean #if GTK_CHECK_VERSION (3, 0, 0) gpm_graph_widget_draw (GtkWidget *graph, cairo_t *cr) #else gpm_graph_widget_expose (GtkWidget *graph, GdkEventExpose *event) #endif { #if GTK_CHECK_VERSION (3, 0, 0) GdkRectangle area; gdouble x1, y1, x2, y2; cairo_clip_extents (cr, &x1, &y1, &x2, &y2); area.x = floor (x1); area.y = floor (y1); area.width = ceil (x2) - area.x; area.height = ceil (y2) - area.y; cairo_rectangle (cr, area.x, area.y, area.width, area.height); #else cairo_t *cr; /* get a cairo_t */ cr = gdk_cairo_create (gtk_widget_get_window (graph)); cairo_rectangle (cr, event->area.x, event->area.y, event->area.width, event->area.height); #endif cairo_clip (cr); ((GpmGraphWidget *)graph)->priv->cr = cr; gpm_graph_widget_draw_graph (graph, cr); #if !GTK_CHECK_VERSION (3, 0, 0) cairo_destroy (cr); #endif return FALSE; } /** * gpm_graph_widget_new: * Return value: A new GpmGraphWidget object. **/ GtkWidget * gpm_graph_widget_new (void) { return g_object_new (GPM_TYPE_GRAPH_WIDGET, NULL); }