aboutsummaryrefslogtreecommitdiff
path: root/deps/uber-graph/uber-graph.c
diff options
context:
space:
mode:
Diffstat (limited to 'deps/uber-graph/uber-graph.c')
-rw-r--r--deps/uber-graph/uber-graph.c2166
1 files changed, 2166 insertions, 0 deletions
diff --git a/deps/uber-graph/uber-graph.c b/deps/uber-graph/uber-graph.c
new file mode 100644
index 00000000..9118f263
--- /dev/null
+++ b/deps/uber-graph/uber-graph.c
@@ -0,0 +1,2166 @@
+/* uber-graph.c
+ *
+ * Copyright (C) 2010 Christian Hergert <chris@dronelabs.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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <glib/gi18n.h>
+#include <gdk/gdk.h>
+#include <math.h>
+#include <string.h>
+
+#include "uber-graph.h"
+#include "uber-scale.h"
+#include "uber-frame-source.h"
+
+#define WIDGET_CLASS (GTK_WIDGET_CLASS(uber_graph_parent_class))
+#define RECT_RIGHT(r) ((r).x + (r).width)
+#define RECT_BOTTOM(r) ((r).y + (r).height)
+#define UNSET_SURFACE(p) \
+ G_STMT_START { \
+ if (p) { \
+ cairo_surface_destroy (p); \
+ p = NULL; \
+ } \
+ } G_STMT_END
+#define CLEAR_CAIRO(c, a) \
+ G_STMT_START { \
+ cairo_save(c); \
+ cairo_rectangle(c, 0, 0, a.width, a.height); \
+ cairo_set_operator(c, CAIRO_OPERATOR_CLEAR); \
+ cairo_fill(c); \
+ cairo_restore(c); \
+ } G_STMT_END
+
+/**
+ * SECTION:uber-graph.h
+ * @title: UberGraph
+ * @short_description: Graphing of realtime data.
+ *
+ * #UberGraph is an abstract base class for building realtime graphs. It
+ * handles scrolling the graph based on the required time intervals and tries
+ * to render as little data as possible.
+ *
+ * Subclasses are responsible for acquiring data when #UberGraph notifies them
+ * of their next data sample. Additionally, there are two rendering methods.
+ * UberGraph::render is a render of the full scene. UberGraph::render_fast is
+ * a rendering of just a new data sample. Ideally, UberGraph::render_fast is
+ * going to be called.
+ *
+ * #UberGraph uses a #cairo_surface_t as a ring buffer to store the contents of the
+ * graph. Upon destructive changes to the widget such as allocation changed
+ * or a new #GtkStyle set, a full rendering of the graph will be required.
+ */
+
+
+struct _UberGraphPrivate
+{
+ cairo_surface_t *fg_surface;
+ cairo_surface_t *bg_surface;
+
+ GdkRectangle content_rect; /* Content area rectangle. */
+ GdkRectangle nonvis_rect; /* Non-visible drawing area larger than
+ * content rect. Used to draw over larger
+ * area so we can scroll and not fall out
+ * of view.
+ */
+ UberGraphFormat format; /* Data type format. */
+ gboolean paused; /* Is the graph paused. */
+ gboolean have_rgba; /* Do we support 32-bit RGBA colormaps. */
+ gint x_slots; /* Number of data points on x axis. */
+ gint fps; /* Desired frames per second. */
+ gint fps_real; /* Milleseconds between FPS callbacks. */
+ gfloat fps_each; /* How far to move in each FPS tick. */
+ guint fps_handler; /* Timeout for moving the content. */
+ gfloat dps; /* Desired data points per second. */
+ gint dps_slot; /* Which slot in the surface buffer. */
+ gfloat dps_each; /* How many pixels between data points. */
+ GTimeVal dps_tv; /* Timeval of last data point. */
+ guint dps_handler; /* Timeout for getting new data. */
+ guint dps_downscale; /* Count since last downscale. */
+ gboolean fg_dirty; /* Does the foreground need to be redrawn. */
+ gboolean bg_dirty; /* Does the background need to be redrawn. */
+ guint tick_len; /* How long should axis-ticks be. */
+ gboolean show_xlines; /* Show X axis lines. */
+ gboolean show_xlabels; /* Show X axis labels. */
+ gboolean show_ylines; /* Show Y axis lines. */
+ gboolean full_draw; /* Do we need to redraw all foreground content.
+ * If false, draws will try to only add new
+ * content to the back buffer.
+ */
+ GtkWidget *labels; /* Container for graph labels. */
+ GtkWidget *align; /* Alignment for labels. */
+ gint fps_count; /* Track actual FPS. */
+};
+
+G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE(UberGraph, uber_graph, GTK_TYPE_DRAWING_AREA)
+
+static gboolean show_fps = FALSE;
+
+enum
+{
+ PROP_0,
+ PROP_FORMAT,
+};
+
+/**
+ * uber_graph_new:
+ *
+ * Creates a new instance of #UberGraph.
+ *
+ * Returns: the newly created instance of #UberGraph.
+ * Side effects: None.
+ */
+GtkWidget*
+uber_graph_new (void)
+{
+ UberGraph *graph;
+
+ graph = g_object_new(UBER_TYPE_GRAPH, NULL);
+ return GTK_WIDGET(graph);
+}
+
+/**
+ * uber_graph_fps_timeout:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: %TRUE always.
+ * Side effects: None.
+ */
+static gboolean
+uber_graph_fps_timeout (UberGraph *graph) /* IN */
+{
+ UberGraphPrivate *priv;
+
+ g_return_val_if_fail(UBER_IS_GRAPH(graph), FALSE);
+
+ priv = graph->priv;
+ gtk_widget_queue_draw_area(GTK_WIDGET(graph),
+ priv->content_rect.x,
+ priv->content_rect.y,
+ priv->content_rect.width,
+ priv->content_rect.height);
+ return TRUE;
+}
+
+/**
+ * uber_graph_get_content_area:
+ * @graph: A #UberGraph.
+ *
+ * Retrieves the content area of the graph.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+uber_graph_get_content_area (UberGraph *graph, /* IN */
+ GdkRectangle *rect) /* OUT */
+{
+ UberGraphPrivate *priv;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+ g_return_if_fail(rect != NULL);
+
+ priv = graph->priv;
+ *rect = priv->content_rect;
+}
+
+/**
+ * uber_graph_get_show_xlines:
+ * @graph: A #UberGraph.
+ *
+ * Retrieves if the X grid lines should be shown.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+gboolean
+uber_graph_get_show_xlines (UberGraph *graph) /* IN */
+{
+ UberGraphPrivate *priv;
+
+ g_return_val_if_fail(UBER_IS_GRAPH(graph), FALSE);
+
+ priv = graph->priv;
+ return priv->show_xlines;
+}
+
+/**
+ * uber_graph_set_show_xlines:
+ * @graph: A #UberGraph.
+ * @show_xlines: Show x lines.
+ *
+ * Sets if the x lines should be shown.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+uber_graph_set_show_xlines (UberGraph *graph, /* IN */
+ gboolean show_xlines) /* IN */
+{
+ UberGraphPrivate *priv;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+ priv = graph->priv;
+ priv->show_xlines = show_xlines;
+ priv->bg_dirty = TRUE;
+ gtk_widget_queue_draw(GTK_WIDGET(graph));
+}
+
+/**
+ * uber_graph_get_show_ylines:
+ * @graph: A #UberGraph.
+ *
+ * Retrieves if the X grid lines should be shown.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+gboolean
+uber_graph_get_show_ylines (UberGraph *graph) /* IN */
+{
+ UberGraphPrivate *priv;
+
+ g_return_val_if_fail(UBER_IS_GRAPH(graph), FALSE);
+
+ priv = graph->priv;
+ return priv->show_ylines;
+}
+
+/**
+ * uber_graph_set_show_ylines:
+ * @graph: A #UberGraph.
+ * @show_ylines: Show x lines.
+ *
+ * Sets if the x lines should be shown.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+uber_graph_set_show_ylines (UberGraph *graph, /* IN */
+ gboolean show_ylines) /* IN */
+{
+ UberGraphPrivate *priv;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+ priv = graph->priv;
+ priv->show_ylines = show_ylines;
+ priv->bg_dirty = TRUE;
+ gtk_widget_queue_draw(GTK_WIDGET(graph));
+}
+
+/**
+ * uber_graph_get_labels:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+GtkWidget*
+uber_graph_get_labels (UberGraph *graph) /* IN */
+{
+ UberGraphPrivate *priv;
+
+ g_return_val_if_fail(UBER_IS_GRAPH(graph), NULL);
+
+ priv = graph->priv;
+ return priv->align;
+}
+
+/**
+ * uber_graph_scale_changed:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+uber_graph_scale_changed (UberGraph *graph) /* IN */
+{
+ UberGraphPrivate *priv;
+ GtkAllocation alloc;
+ GdkRectangle rect;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+ priv = graph->priv;
+ if (!priv->paused) {
+ priv->fg_dirty = TRUE;
+ priv->bg_dirty = TRUE;
+ priv->full_draw = TRUE;
+ gtk_widget_get_allocation(GTK_WIDGET(graph), &alloc);
+ rect.x = 0;
+ rect.y = 0;
+ rect.width = alloc.width;
+ rect.height = alloc.height;
+ gdk_window_invalidate_rect(gtk_widget_get_window(GTK_WIDGET(graph)),
+ &rect, TRUE);
+ }
+}
+
+/**
+ * uber_graph_get_next_data:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static inline gboolean
+uber_graph_get_next_data (UberGraph *graph) /* IN */
+{
+ UberGraphPrivate *priv;
+ gboolean ret = TRUE;
+
+ g_return_val_if_fail(UBER_IS_GRAPH(graph), FALSE);
+
+ /*
+ * Get the current time for this data point. This is used to calculate
+ * the proper offset in the FPS callback.
+ */
+ priv = graph->priv;
+ g_get_current_time(&priv->dps_tv);
+ /*
+ * Notify the subclass to retrieve the data point.
+ */
+ if (UBER_GRAPH_GET_CLASS(graph)->get_next_data) {
+ ret = UBER_GRAPH_GET_CLASS(graph)->get_next_data(graph);
+ }
+ return ret;
+}
+
+/**
+ * uber_graph_init_texture:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_graph_init_texture (UberGraph *graph) /* IN */
+{
+ UberGraphPrivate *priv;
+ GtkAllocation alloc;
+ GdkWindow *window;
+ cairo_t *cr;
+ gint width;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+ priv = graph->priv;
+ gtk_widget_get_allocation(GTK_WIDGET(graph), &alloc);
+ window = gtk_widget_get_window(GTK_WIDGET(graph));
+ /*
+ * Get drawable to base surface upon.
+ */
+ if (!window) {
+ g_critical("%s() called before GdkWindow is allocated.", G_STRFUNC);
+ return;
+ }
+ /*
+ * Initialize foreground and background surface.
+ */
+ width = MAX(priv->nonvis_rect.x + priv->nonvis_rect.width, alloc.width);
+ priv->fg_surface = gdk_window_create_similar_surface(window, CAIRO_CONTENT_COLOR_ALPHA, width, alloc.height);
+ /*
+ * Clear foreground contents.
+ */
+ cr = cairo_create(priv->fg_surface);
+ CLEAR_CAIRO(cr, alloc);
+ cairo_destroy(cr);
+}
+
+/**
+ * uber_graph_init_bg:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_graph_init_bg (UberGraph *graph) /* IN */
+{
+ UberGraphPrivate *priv;
+ GtkAllocation alloc;
+ GdkWindow *window;
+ cairo_t *cr;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+ priv = graph->priv;
+ gtk_widget_get_allocation(GTK_WIDGET(graph), &alloc);
+ window = gtk_widget_get_window(GTK_WIDGET(graph));
+ /*
+ * Get drawable for surface.
+ */
+ if (!window) {
+ g_critical("%s() called before GdkWindow is allocated.", G_STRFUNC);
+ return;
+ }
+ /*
+ * Create the server-side surface.
+ */
+ priv->bg_surface = gdk_window_create_similar_surface(window, CAIRO_CONTENT_COLOR_ALPHA, alloc.width, alloc.height);
+ /*
+ * Clear background contents.
+ */
+ cr = cairo_create(priv->bg_surface);
+ CLEAR_CAIRO(cr, alloc);
+ cairo_destroy(cr);
+}
+
+/**
+ * uber_graph_calculate_rects:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_graph_calculate_rects (UberGraph *graph) /* IN */
+{
+ UberGraphPrivate *priv;
+ GtkAllocation alloc;
+ PangoLayout *layout;
+ PangoFontDescription *font_desc;
+ GdkWindow *window;
+ gint pango_width;
+ gint pango_height;
+ cairo_t *cr;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+ priv = graph->priv;
+ gtk_widget_get_allocation(GTK_WIDGET(graph), &alloc);
+ window = gtk_widget_get_window(GTK_WIDGET(graph));
+ /*
+ * We can't calculate rectangles before we have a GdkWindow.
+ */
+ if (!window) {
+ return;
+ }
+ /*
+ * Determine the pixels required for labels.
+ */
+ cr = gdk_cairo_create(window);
+ layout = pango_cairo_create_layout(cr);
+ font_desc = pango_font_description_new();
+ pango_font_description_set_family_static(font_desc, "Monospace");
+ pango_font_description_set_size(font_desc, 6 * PANGO_SCALE);
+ pango_layout_set_font_description(layout, font_desc);
+ pango_layout_set_text(layout, "XXXXXXXXXX", -1);
+ pango_layout_get_pixel_size(layout, &pango_width, &pango_height);
+ pango_font_description_free(font_desc);
+ g_object_unref(layout);
+ cairo_destroy(cr);
+ /*
+ * Calculate content area rectangle.
+ */
+ priv->content_rect.x = priv->tick_len + pango_width + 1.5;
+ priv->content_rect.y = (pango_height / 2.) + 1.5;
+ priv->content_rect.width = alloc.width - priv->content_rect.x - 3.0;
+ priv->content_rect.height = alloc.height - priv->tick_len - pango_height
+ - (pango_height / 2.) - 3.0;
+ if (!priv->show_xlabels) {
+ priv->content_rect.height += pango_height;
+ }
+ /*
+ * Adjust label offset.
+ */
+ /*
+ * Calculate FPS/DPS adjustments.
+ */
+ priv->dps_each = ceil((gfloat)priv->content_rect.width
+ / (gfloat)(priv->x_slots - 1));
+ priv->fps_each = priv->dps_each
+ / ((gfloat)priv->fps / (gfloat)priv->dps);
+ /*
+ * XXX: Small hack to make things a bit smoother at small scales.
+ */
+ if (priv->fps_each < .5) {
+ priv->fps_each = 1;
+ priv->fps_real = (1000. / priv->dps_each) / 2.;
+ } else {
+ priv->fps_real = 1000. / priv->fps;
+ }
+ /*
+ * Update FPS callback.
+ */
+ if (priv->fps_handler) {
+ g_source_remove(priv->fps_handler);
+ priv->fps_handler = uber_frame_source_add(priv->fps,
+ (GSourceFunc)uber_graph_fps_timeout,
+ graph);
+ }
+ /*
+ * Calculate the non-visible area that drawing should happen within.
+ */
+ priv->nonvis_rect = priv->content_rect;
+ priv->nonvis_rect.width = priv->dps_each * priv->x_slots;
+ /*
+ * Update positioning for label alignment.
+ */
+ gtk_widget_set_margin_top(GTK_WIDGET(priv->align), 6);
+ gtk_widget_set_margin_bottom(GTK_WIDGET(priv->align), 6);
+ gtk_widget_set_margin_start(GTK_WIDGET(priv->align), priv->content_rect.x);
+ gtk_widget_set_margin_end(GTK_WIDGET(priv->align), 0);
+}
+
+/**
+ * uber_graph_get_show_xlabels:
+ * @graph: A #UberGraph.
+ *
+ * Retrieves if the X grid labels should be shown.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+gboolean
+uber_graph_get_show_xlabels (UberGraph *graph) /* IN */
+{
+ UberGraphPrivate *priv;
+
+ g_return_val_if_fail(UBER_IS_GRAPH(graph), FALSE);
+
+ priv = graph->priv;
+ return priv->show_xlabels;
+}
+
+/**
+ * uber_graph_set_show_xlabels:
+ * @graph: A #UberGraph.
+ * @show_xlabels: Show x labels.
+ *
+ * Sets if the x labels should be shown.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+uber_graph_set_show_xlabels (UberGraph *graph, /* IN */
+ gboolean show_xlabels) /* IN */
+{
+ UberGraphPrivate *priv;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+ priv = graph->priv;
+ priv->show_xlabels = show_xlabels;
+ priv->bg_dirty = TRUE;
+ uber_graph_calculate_rects(graph);
+ gtk_widget_queue_draw(GTK_WIDGET(graph));
+}
+
+/**
+ * uber_graph_dps_timeout:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: %TRUE always.
+ * Side effects: None.
+ */
+static gboolean
+uber_graph_dps_timeout (UberGraph *graph) /* IN */
+{
+ UberGraphPrivate *priv;
+
+ g_return_val_if_fail(UBER_IS_GRAPH(graph), FALSE);
+
+ priv = graph->priv;
+ if (!uber_graph_get_next_data(graph)) {
+ /*
+ * XXX: How should we handle failed data retrieval.
+ */
+ }
+ if (G_UNLIKELY(show_fps)) {
+ g_print("UberGraph[%p] %02d FPS\n", graph, priv->fps_count);
+ priv->fps_count = 0;
+ }
+ /*
+ * Make sure the content is re-rendered.
+ */
+ if (!priv->paused) {
+ priv->fg_dirty = TRUE;
+ /*
+ * We do not queue a draw here since the next FPS callback will happen
+ * when it is the right time to show the frame.
+ */
+ }
+ /*
+ * Try to downscale the graph. We do this whether or not we are paused
+ * as redrawing is deferred if we are in a paused state.
+ */
+ priv->dps_downscale++;
+ if (priv->dps_downscale >= 5) {
+ if (UBER_GRAPH_GET_CLASS(graph)->downscale) {
+ if (UBER_GRAPH_GET_CLASS(graph)->downscale(graph)) {
+ if (!priv->paused) {
+ uber_graph_redraw(graph);
+ }
+ }
+ }
+ priv->dps_downscale = 0;
+ }
+ return TRUE;
+}
+
+/**
+ * uber_graph_register_dps_handler:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_graph_register_dps_handler (UberGraph *graph) /* IN */
+{
+ UberGraphPrivate *priv;
+ guint dps_freq;
+ gboolean do_now = TRUE;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+ priv = graph->priv;
+ if (priv->dps_handler) {
+ g_source_remove(priv->dps_handler);
+ do_now = FALSE;
+ }
+ /*
+ * Calculate the update frequency.
+ */
+ dps_freq = 1000 / priv->dps;
+ /*
+ * Install the data handler.
+ */
+ priv->dps_handler = g_timeout_add(dps_freq,
+ (GSourceFunc)uber_graph_dps_timeout,
+ graph);
+ /*
+ * Call immediately.
+ */
+ if (do_now) {
+ uber_graph_dps_timeout(graph);
+ }
+}
+
+/**
+ * uber_graph_register_fps_handler:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_graph_register_fps_handler (UberGraph *graph) /* IN */
+{
+ UberGraphPrivate *priv;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+ priv = graph->priv;
+ /*
+ * Remove any existing FPS handler.
+ */
+ if (priv->fps_handler) {
+ g_source_remove(priv->fps_handler);
+ }
+ /*
+ * Install the FPS timeout.
+ */
+ priv->fps_handler = uber_frame_source_add(priv->fps,
+ (GSourceFunc)uber_graph_fps_timeout,
+ graph);
+}
+
+/**
+ * uber_graph_set_dps:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+uber_graph_set_dps (UberGraph *graph, /* IN */
+ gfloat dps) /* IN */
+{
+ UberGraphPrivate *priv;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+ priv = graph->priv;
+ priv->dps = dps;
+ /*
+ * TODO: Does this belong somewhere else?
+ */
+ if (UBER_GRAPH_GET_CLASS(graph)->set_stride) {
+ UBER_GRAPH_GET_CLASS(graph)->set_stride(graph, priv->x_slots);
+ }
+ /*
+ * Recalculate frame rates and timeouts.
+ */
+ uber_graph_calculate_rects(graph);
+ uber_graph_register_dps_handler(graph);
+ uber_graph_register_fps_handler(graph);
+}
+
+/**
+ * uber_graph_set_fps:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+uber_graph_set_fps (UberGraph *graph, /* IN */
+ guint fps) /* IN */
+{
+ UberGraphPrivate *priv;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+ priv = graph->priv;
+ priv->fps = fps;
+ uber_graph_register_fps_handler(graph);
+}
+
+/**
+ * uber_graph_realize:
+ * @widget: A #GtkWidget.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_graph_realize (GtkWidget *widget) /* IN */
+{
+ UberGraph *graph;
+ UberGraphPrivate *priv;
+
+ g_return_if_fail(UBER_IS_GRAPH(widget));
+
+ graph = UBER_GRAPH(widget);
+ priv = graph->priv;
+ WIDGET_CLASS->realize(widget);
+ /*
+ * Calculate new layout based on allocation.
+ */
+ uber_graph_calculate_rects(graph);
+ /*
+ * Re-initialize textures for updated sizes.
+ */
+ UNSET_SURFACE(priv->bg_surface);
+ UNSET_SURFACE(priv->fg_surface);
+ uber_graph_init_bg(graph);
+ uber_graph_init_texture(graph);
+ /*
+ * Notify subclass of current data stride (points per graph).
+ */
+ if (UBER_GRAPH_GET_CLASS(widget)->set_stride) {
+ UBER_GRAPH_GET_CLASS(widget)->set_stride(UBER_GRAPH(widget),
+ priv->x_slots);
+ }
+ /*
+ * Install the data collector.
+ */
+ uber_graph_register_dps_handler(graph);
+}
+
+/**
+ * uber_graph_unrealize:
+ * @widget: A #GtkWidget.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_graph_unrealize (GtkWidget *widget) /* IN */
+{
+ UberGraph *graph;
+ UberGraphPrivate *priv;
+
+ g_return_if_fail(UBER_IS_GRAPH(widget));
+
+ graph = UBER_GRAPH(widget);
+ priv = graph->priv;
+ /*
+ * Unregister any data acquisition handlers.
+ */
+ if (priv->dps_handler) {
+ g_source_remove(priv->dps_handler);
+ priv->dps_handler = 0;
+ }
+ /*
+ * Destroy textures.
+ */
+ UNSET_SURFACE(priv->bg_surface);
+ UNSET_SURFACE(priv->fg_surface);
+}
+
+/**
+ * uber_graph_screen_changed:
+ * @widget: A #GtkWidget.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_graph_screen_changed (GtkWidget *widget, /* IN */
+ GdkScreen *old_screen) /* IN */
+{
+ UberGraphPrivate *priv;
+ GdkScreen *screen;
+ GdkVisual *visual;
+
+ g_return_if_fail(UBER_IS_GRAPH(widget));
+
+ screen = gtk_widget_get_screen (GTK_WIDGET (widget));
+ visual = gdk_screen_get_rgba_visual (screen);
+
+ priv = UBER_GRAPH(widget)->priv;
+ /*
+ * Check if we have RGBA colormaps available.
+ */
+ priv->have_rgba = visual != NULL;
+}
+
+/**
+ * uber_graph_show:
+ * @widget: A #GtkWidget.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_graph_show (GtkWidget *widget) /* IN */
+{
+ g_return_if_fail(UBER_IS_GRAPH(widget));
+
+ WIDGET_CLASS->show(widget);
+ /*
+ * Only run the FPS timeout when we are visible.
+ */
+ uber_graph_register_fps_handler(UBER_GRAPH(widget));
+}
+
+/**
+ * uber_graph_hide:
+ * @widget: A #GtkWIdget.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_graph_hide (GtkWidget *widget) /* IN */
+{
+ UberGraphPrivate *priv;
+
+ g_return_if_fail(UBER_IS_GRAPH(widget));
+
+ priv = UBER_GRAPH(widget)->priv;
+ /*
+ * Disable the FPS timeout when we are not visible.
+ */
+ if (priv->fps_handler) {
+ g_source_remove(priv->fps_handler);
+ priv->fps_handler = 0;
+ }
+}
+
+static inline void
+uber_graph_get_pixmap_rect (UberGraph *graph, /* IN */
+ GdkRectangle *rect) /* OUT */
+{
+ UberGraphPrivate *priv;
+ GtkAllocation alloc;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+ priv = graph->priv;
+ gtk_widget_get_allocation(GTK_WIDGET(graph), &alloc);
+ rect->x = 0;
+ rect->y = 0;
+ rect->width = MAX(alloc.width,
+ priv->nonvis_rect.x + priv->nonvis_rect.width);
+ rect->height = alloc.height;
+}
+
+/**
+ * uber_graph_render_fg:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_graph_render_fg (UberGraph *graph) /* IN */
+{
+ UberGraphPrivate *priv;
+ GtkAllocation alloc;
+ GdkRectangle rect;
+ cairo_t *cr;
+ gfloat each;
+ gfloat x_epoch;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+ /*
+ * Acquire resources.
+ */
+ priv = graph->priv;
+ gtk_widget_get_allocation(GTK_WIDGET(graph), &alloc);
+ uber_graph_get_pixmap_rect(graph, &rect);
+ cr = cairo_create(priv->fg_surface);
+ /*
+ * Render to texture if needed.
+ */
+ if (priv->fg_dirty) {
+ /*
+ * Caclulate relative positionings for use in renderers.
+ */
+ each = priv->content_rect.width / (gfloat)(priv->x_slots - 1);
+ x_epoch = RECT_RIGHT(priv->nonvis_rect);
+ /*
+ * If we are in a fast draw, lets copy the content from the other
+ * buffer at the next offset.
+ */
+ if (!priv->full_draw && UBER_GRAPH_GET_CLASS(graph)->render_fast) {
+ /*
+ * Determine next rendering slot.
+ */
+ rect.x = priv->content_rect.x
+ + (priv->dps_each * priv->dps_slot);
+ rect.width = priv->dps_each;
+ rect.y = priv->content_rect.y;
+ rect.height = priv->content_rect.height;
+ priv->dps_slot = (priv->dps_slot + 1) % priv->x_slots;
+ x_epoch = RECT_RIGHT(rect);
+ /*
+ * Clear content area.
+ */
+ cairo_save(cr);
+ cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
+ gdk_cairo_rectangle(cr, &rect);
+ cairo_fill(cr);
+ cairo_restore(cr);
+
+#if 0
+ /*
+ * XXX: Draw line helper for debugging.
+ */
+ cairo_save(cr);
+ cairo_set_source_rgb(cr, .3, .3, .3);
+ cairo_rectangle(cr,
+ rect.x,
+ rect.y + (rect.height / (gfloat)priv->x_slots * priv->dps_slot),
+ rect.width,
+ rect.height / priv->x_slots);
+ cairo_fill(cr);
+ cairo_restore(cr);
+#endif
+
+ /*
+ * Render new content clipped.
+ */
+ cairo_save(cr);
+ cairo_reset_clip(cr);
+ gdk_cairo_rectangle(cr, &rect);
+ cairo_clip(cr);
+ /*
+ * Determine area for this draw.
+ */
+ UBER_GRAPH_GET_CLASS(graph)->render_fast(graph,
+ cr,
+ &rect,
+ x_epoch,
+ each + .5);
+ cairo_restore(cr);
+ } else {
+ /*
+ * Clear content area.
+ */
+ cairo_save(cr);
+ cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
+ gdk_cairo_rectangle(cr, &rect);
+ cairo_fill(cr);
+ cairo_restore(cr);
+ /*
+ * Draw the entire foreground.
+ */
+ if (UBER_GRAPH_GET_CLASS(graph)->render) {
+ priv->dps_slot = 0;
+ cairo_save(cr);
+ gdk_cairo_rectangle(cr, &priv->nonvis_rect);
+ cairo_clip(cr);
+ UBER_GRAPH_GET_CLASS(graph)->render(graph,
+ cr,
+ &priv->nonvis_rect,
+ x_epoch,
+ each);
+ cairo_restore(cr);
+ }
+ }
+ }
+ /*
+ * Foreground is no longer dirty.
+ */
+ priv->fg_dirty = FALSE;
+ priv->full_draw = FALSE;
+ /*
+ * Cleanup.
+ */
+ cairo_destroy(cr);
+}
+
+/**
+ * uber_graph_redraw:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+uber_graph_redraw (UberGraph *graph) /* IN */
+{
+ UberGraphPrivate *priv;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+ priv = graph->priv;
+ priv->fg_dirty = TRUE;
+ priv->bg_dirty = TRUE;
+ priv->full_draw = TRUE;
+ gtk_widget_queue_draw(GTK_WIDGET(graph));
+}
+
+/**
+ * uber_graph_get_yrange:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static inline void
+uber_graph_get_yrange (UberGraph *graph, /* IN */
+ UberRange *range) /* OUT */
+{
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+ g_return_if_fail(range != NULL);
+
+ memset(range, 0, sizeof(*range));
+ if (UBER_GRAPH_GET_CLASS(graph)->get_yrange) {
+ UBER_GRAPH_GET_CLASS(graph)->get_yrange(graph, range);
+ }
+}
+
+/**
+ * uber_graph_set_format:
+ * @graph: A UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+uber_graph_set_format (UberGraph *graph, /* IN */
+ UberGraphFormat format) /* IN */
+{
+ UberGraphPrivate *priv;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+ priv = graph->priv;
+ priv->format = format;
+ priv->bg_dirty = TRUE;
+ gtk_widget_queue_draw(GTK_WIDGET(graph));
+}
+
+static void
+uber_graph_render_x_axis (UberGraph *graph, /* IN */
+ cairo_t *cr) /* IN */
+{
+ UberGraphPrivate *priv;
+ const gdouble dashes[] = { 1.0, 2.0 };
+ PangoFontDescription *fd;
+ PangoLayout *pl;
+ GtkStyleContext *style;
+ GdkRGBA fg_color;
+ gfloat each;
+ gfloat x;
+ gfloat y;
+ gfloat h;
+ gchar text[16] = { 0 };
+ gint count;
+ gint wi;
+ gint hi;
+ gint i;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+ priv = graph->priv;
+ style = gtk_widget_get_style_context(GTK_WIDGET(graph));
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &fg_color);
+
+ count = priv->x_slots / 10;
+ each = priv->content_rect.width / (gfloat)count;
+ /*
+ * Draw ticks.
+ */
+ cairo_save(cr);
+ pl = pango_cairo_create_layout(cr);
+ fd = pango_font_description_new();
+ pango_font_description_set_family_static(fd, "Monospace");
+ pango_font_description_set_size(fd, 6 * PANGO_SCALE);
+ pango_layout_set_font_description(pl, fd);
+ gdk_cairo_set_source_rgba(cr, &fg_color);
+ cairo_set_line_width(cr, 1.0);
+ cairo_set_dash(cr, dashes, G_N_ELEMENTS(dashes), 0);
+ for (i = 0; i <= count; i++) {
+ x = RECT_RIGHT(priv->content_rect) - (gint)(i * each) + .5;
+ if (priv->show_xlines && (i != 0 && i != count)) {
+ y = priv->content_rect.y;
+ h = priv->content_rect.height + priv->tick_len;
+ } else {
+ y = priv->content_rect.y + priv->content_rect.height;
+ h = priv->tick_len;
+ }
+ if (i != 0 && i != count) {
+ cairo_move_to(cr, x, y);
+ cairo_line_to(cr, x, y + h);
+ cairo_stroke(cr);
+ }
+ /*
+ * Render the label.
+ */
+ if (priv->show_xlabels) {
+ g_snprintf(text, sizeof(text), "%d", i * 10);
+ pango_layout_set_text(pl, text, -1);
+ pango_layout_get_pixel_size(pl, &wi, &hi);
+ if (i != 0 && i != count) {
+ cairo_move_to(cr, x - (wi / 2), y + h);
+ } else if (i == 0) {
+ cairo_move_to(cr,
+ RECT_RIGHT(priv->content_rect) - (wi / 2),
+ RECT_BOTTOM(priv->content_rect) + priv->tick_len);
+ } else if (i == count) {
+ cairo_move_to(cr,
+ priv->content_rect.x - (wi / 2),
+ RECT_BOTTOM(priv->content_rect) + priv->tick_len);
+ }
+ pango_cairo_show_layout(cr, pl);
+ }
+ }
+ g_object_unref(pl);
+ pango_font_description_free(fd);
+ cairo_restore(cr);
+}
+
+static void G_GNUC_PRINTF(6, 7)
+uber_graph_render_y_line (UberGraph *graph, /* IN */
+ cairo_t *cr, /* IN */
+ gint y, /* IN */
+ gboolean tick_only, /* IN */
+ gboolean no_label, /* IN */
+ const gchar *format, /* IN */
+ ...) /* IN */
+{
+ UberGraphPrivate *priv;
+ const gdouble dashes[] = { 1.0, 2.0 };
+ PangoFontDescription *fd;
+ PangoLayout *pl;
+ GtkStyleContext *style;
+ GdkRGBA fg_color;
+ va_list args;
+ gchar *text;
+ gint width;
+ gint height;
+ gfloat real_y = y + .5;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+ g_return_if_fail(cr != NULL);
+ g_return_if_fail(format != NULL);
+
+ priv = graph->priv;
+ style = gtk_widget_get_style_context(GTK_WIDGET(graph));
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &fg_color);
+ /*
+ * Draw grid line.
+ */
+ cairo_save(cr);
+ cairo_set_dash(cr, dashes, G_N_ELEMENTS(dashes), 0);
+ cairo_set_line_width(cr, 1.0);
+ gdk_cairo_set_source_rgba(cr, &fg_color);
+ cairo_move_to(cr, priv->content_rect.x - priv->tick_len, real_y);
+ if (tick_only) {
+ cairo_line_to(cr, priv->content_rect.x, real_y);
+ } else {
+ cairo_line_to(cr, RECT_RIGHT(priv->content_rect), real_y);
+ }
+ cairo_stroke(cr);
+ cairo_restore(cr);
+ /*
+ * Show label.
+ */
+ if (!no_label) {
+ cairo_save(cr);
+ gdk_cairo_set_source_rgba(cr, &fg_color);
+ /*
+ * Format text.
+ */
+ va_start(args, format);
+ text = g_strdup_vprintf(format, args);
+ va_end(args);
+ /*
+ * Render pango layout.
+ */
+ pl = pango_cairo_create_layout(cr);
+ fd = pango_font_description_new();
+ pango_font_description_set_family_static(fd, "Monospace");
+ pango_font_description_set_size(fd, 6 * PANGO_SCALE);
+ pango_layout_set_font_description(pl, fd);
+ pango_layout_set_text(pl, text, -1);
+ pango_layout_get_pixel_size(pl, &width, &height);
+ cairo_move_to(cr, priv->content_rect.x - priv->tick_len - width - 3,
+ real_y - height / 2);
+ pango_cairo_show_layout(cr, pl);
+ /*
+ * Cleanup resources.
+ */
+ g_free(text);
+ pango_font_description_free(fd);
+ g_object_unref(pl);
+ cairo_restore(cr);
+ }
+}
+
+static void
+uber_graph_render_y_axis_percent (UberGraph *graph, /* IN */
+ cairo_t *cr, /* IN */
+ UberRange *range, /* IN */
+ UberRange *pixel_range, /* IN */
+ gint n_lines) /* IN */
+{
+ static const gchar format[] = "%0.0f %%";
+ UberGraphPrivate *priv;
+ gdouble value;
+ gdouble y;
+ gint i;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+ g_return_if_fail(range != NULL);
+ g_return_if_fail(pixel_range != NULL);
+ g_return_if_fail(n_lines >= 0);
+ g_return_if_fail(n_lines < 6);
+
+ priv = graph->priv;
+ /*
+ * Render top and bottom lines.
+ */
+ uber_graph_render_y_line(graph, cr,
+ priv->content_rect.y - 1,
+ TRUE, FALSE, format, 100.);
+ uber_graph_render_y_line(graph, cr,
+ RECT_BOTTOM(priv->content_rect),
+ TRUE, FALSE, format, 0.);
+ /*
+ * Render lines between the edges.
+ */
+ for (i = 1; i < n_lines; i++) {
+ value = (n_lines - i) / (gfloat)n_lines;
+ y = pixel_range->end - (pixel_range->range * value);
+ value *= 100.;
+ uber_graph_render_y_line(graph, cr, y,
+ !priv->show_ylines, FALSE,
+ format, value);
+ }
+}
+
+/**
+ * uber_graph_render_y_axis_direct:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_graph_render_y_axis_direct (UberGraph *graph, /* IN */
+ cairo_t *cr, /* IN */
+ UberRange *range, /* IN */
+ UberRange *pixel_range, /* IN */
+ gint n_lines, /* IN */
+ gboolean kibi) /* IN */
+{
+ static const gchar format[] = "%0.1f%s";
+ const gchar *modifier = "";
+ UberGraphPrivate *priv;
+ gdouble value;
+ gdouble y;
+ gint i;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+#define CONDENSE(v) \
+G_STMT_START { \
+ if (kibi) { \
+ if ((v) >= 1073741824.) { \
+ (v) /= 1073741824.; \
+ modifier = " Gi"; \
+ } else if ((v) >= 1048576.) { \
+ (v) /= 1048576.; \
+ modifier = " Mi"; \
+ } else if ((v) >= 1024.) { \
+ (v) /= 1024.; \
+ modifier = " Ki"; \
+ } else { \
+ modifier = ""; \
+ } \
+ } else { \
+ if ((v) >= 1000000000.) { \
+ (v) /= 1000000000.; \
+ modifier = " G"; \
+ } else if ((v) >= 1000000.) { \
+ (v) /= 1000000.; \
+ modifier = " M"; \
+ } else if ((v) >= 1000.) { \
+ (v) /= 1000.; \
+ modifier = " K"; \
+ } else { \
+ modifier = ""; \
+ } \
+ } \
+} G_STMT_END
+
+ priv = graph->priv;
+ /*
+ * Render top and bottom lines.
+ */
+ value = range->end;
+ CONDENSE(value);
+ uber_graph_render_y_line(graph, cr,
+ priv->content_rect.y - 1,
+ TRUE, FALSE, format, value, modifier);
+ value = range->begin;
+ CONDENSE(value);
+ uber_graph_render_y_line(graph, cr,
+ RECT_BOTTOM(priv->content_rect),
+ TRUE, FALSE, format, value, modifier);
+ /*
+ * Render lines between the edges.
+ */
+ for (i = 1; i < n_lines; i++) {
+ y = value = range->range / (gfloat)(n_lines) * (gfloat)i;
+ /*
+ * TODO: Use proper scale.
+ */
+ uber_scale_linear(range, pixel_range, &y, NULL);
+ if (y == 0 || y == pixel_range->begin || y == pixel_range->end) {
+ continue;
+ }
+ y = pixel_range->end - y;
+ CONDENSE(value);
+ uber_graph_render_y_line(graph, cr, y,
+ !priv->show_ylines,
+ (range->begin == range->end),
+ format, value, modifier);
+ }
+}
+
+/**
+ * uber_graph_render_y_axis:
+ * @graph: A #UberGraph.
+ *
+ * Render the Y axis grid lines and labels.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_graph_render_y_axis (UberGraph *graph, /* IN */
+ cairo_t *cr) /* IN */
+{
+ UberGraphPrivate *priv;
+ UberRange pixel_range;
+ UberRange range;
+ gint n_lines;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+ priv = graph->priv;
+ /*
+ * Calculate ranges.
+ */
+ uber_graph_get_yrange(graph, &range);
+ pixel_range.begin = priv->content_rect.y;
+ pixel_range.end = priv->content_rect.y + priv->content_rect.height;
+ pixel_range.range = pixel_range.end - pixel_range.begin;
+ /*
+ * Render grid lines for given format.
+ */
+ n_lines = MIN(priv->content_rect.height / 25, 5);
+ switch (priv->format) {
+ case UBER_GRAPH_FORMAT_PERCENT:
+ uber_graph_render_y_axis_percent(graph, cr, &range, &pixel_range,
+ n_lines);
+ break;
+ case UBER_GRAPH_FORMAT_DIRECT:
+ uber_graph_render_y_axis_direct(graph, cr, &range, &pixel_range,
+ n_lines, FALSE);
+ break;
+ case UBER_GRAPH_FORMAT_DIRECT1024:
+ uber_graph_render_y_axis_direct(graph, cr, &range, &pixel_range,
+ n_lines, TRUE);
+ break;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+/**
+ * uber_graph_render_bg:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_graph_render_bg (UberGraph *graph) /* IN */
+{
+ const gdouble dashes[] = { 1.0, 2.0 };
+ UberGraphPrivate *priv;
+ GtkAllocation alloc;
+ GtkStyleContext *style;
+ GdkRGBA fg_color;
+ GdkRGBA light_color;
+ cairo_t *cr;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+ /*
+ * Acquire resources.
+ */
+ priv = graph->priv;
+ gtk_widget_get_allocation(GTK_WIDGET(graph), &alloc);
+ style = gtk_widget_get_style_context(GTK_WIDGET(graph));
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &fg_color);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_SELECTED, &light_color);
+ cr = cairo_create(priv->bg_surface);
+ /*
+ * Ensure valid resources.
+ */
+ g_assert(style);
+ g_assert(priv->bg_surface);
+ /*
+ * Clear entire background. Hopefully this looks okay for RGBA themes
+ * that are translucent.
+ */
+ cairo_save(cr);
+ cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
+ cairo_rectangle(cr, 0, 0, alloc.width, alloc.height);
+ cairo_fill(cr);
+ cairo_restore(cr);
+ /*
+ * Paint the content area background.
+ */
+ cairo_save(cr);
+ gdk_cairo_set_source_rgba(cr, &light_color);
+ gdk_cairo_rectangle(cr, &priv->content_rect);
+ cairo_fill(cr);
+ cairo_restore(cr);
+ /*
+ * Stroke the border around the content area.
+ */
+ cairo_save(cr);
+ gdk_cairo_set_source_rgba(cr, &fg_color);
+ cairo_set_line_width(cr, 1.0);
+ cairo_set_dash(cr, dashes, G_N_ELEMENTS(dashes), 0);
+ cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
+ cairo_rectangle(cr,
+ priv->content_rect.x - .5,
+ priv->content_rect.y - .5,
+ priv->content_rect.width + 1.0,
+ priv->content_rect.height + 1.0);
+ cairo_stroke(cr);
+ cairo_restore(cr);
+ /*
+ * Render the axis ticks.
+ */
+ uber_graph_render_y_axis(graph, cr);
+ uber_graph_render_x_axis(graph, cr);
+ /*
+ * Background is no longer dirty.
+ */
+ priv->bg_dirty = FALSE;
+ /*
+ * Cleanup.
+ */
+ cairo_destroy(cr);
+}
+
+static inline void
+g_time_val_subtract (GTimeVal *a, /* IN */
+ GTimeVal *b, /* IN */
+ GTimeVal *c) /* OUT */
+{
+ g_return_if_fail(a != NULL);
+ g_return_if_fail(b != NULL);
+ g_return_if_fail(c != NULL);
+
+ c->tv_sec = a->tv_sec - b->tv_sec;
+ c->tv_usec = a->tv_usec - b->tv_usec;
+ if (c->tv_usec < 0) {
+ c->tv_usec += G_USEC_PER_SEC;
+ c->tv_sec -= 1;
+ }
+}
+
+/**
+ * uber_graph_get_fps_offset:
+ * @graph: A #UberGraph.
+ *
+ * Calculates the number of pixels that the foreground should be rendered
+ * from the origin.
+ *
+ * Returns: The pixel offset to render the foreground.
+ * Side effects: None.
+ */
+static gfloat
+uber_graph_get_fps_offset (UberGraph *graph) /* IN */
+{
+ UberGraphPrivate *priv;
+ GTimeVal rel = { 0 };
+ GTimeVal tv;
+ gfloat f;
+
+ g_return_val_if_fail(UBER_IS_GRAPH(graph), 0.);
+
+ priv = graph->priv;
+ g_get_current_time(&tv);
+ g_time_val_subtract(&tv, &priv->dps_tv, &rel);
+ f = ((rel.tv_sec * 1000) + (rel.tv_usec / 1000))
+ / (1000. / priv->dps) /* MSec Per Data Point */
+ * priv->dps_each; /* Pixels Per Data Point */
+ return MIN(f, (priv->dps_each - priv->fps_each));
+}
+
+/**
+ * uber_graph_draw:
+ * @widget: A #GtkWidget.
+ *
+ * XXX
+ *
+ * Returns: %FALSE always.
+ * Side effects: None.
+ */
+static gboolean
+uber_graph_draw (GtkWidget *widget, /* IN */
+ cairo_t *cr) /* IN */
+{
+ UberGraphPrivate *priv;
+ GtkAllocation alloc;
+// cairo_t *cr;
+ gfloat offset;
+ gint x;
+
+ g_return_val_if_fail(UBER_IS_GRAPH(widget), FALSE);
+
+ priv = UBER_GRAPH(widget)->priv;
+ gtk_widget_get_allocation(widget, &alloc);
+ priv->fps_count++;
+ /*
+ * Ensure that the texture is initialized.
+ */
+ g_assert(priv->fg_surface);
+ g_assert(priv->bg_surface);
+ /*
+ * Clear window background.
+ */
+#if 0
+ gdk_window_clear_area(expose->window,
+ expose->area.x,
+ expose->area.y,
+ expose->area.width,
+ expose->area.height);
+ /*
+ * Allocate resources.
+ */
+ cr = gdk_cairo_create(expose->window);
+ /*
+ * Clip to exposure area.
+ */
+ gdk_cairo_rectangle(cr, &expose->area);
+ cairo_clip(cr);
+#endif
+ /*
+ * Render background or foreground if needed.
+ */
+ if (priv->bg_dirty) {
+ uber_graph_render_bg(UBER_GRAPH(widget));
+ }
+ if (priv->fg_dirty) {
+ uber_graph_render_fg(UBER_GRAPH(widget));
+ }
+ /*
+ * Paint the background to the exposure area.
+ */
+ cairo_save(cr);
+ cairo_set_source_surface(cr, priv->bg_surface, 0, 0);
+ cairo_rectangle(cr, 0, 0, alloc.width, alloc.height);
+ cairo_fill(cr);
+ cairo_restore(cr);
+ /*
+ * Draw the foreground.
+ */
+ offset = uber_graph_get_fps_offset(UBER_GRAPH(widget));
+ if (priv->have_rgba) {
+ cairo_save(cr);
+ /*
+ * Clip exposure to the content area.
+ */
+ cairo_reset_clip(cr);
+ gdk_cairo_rectangle(cr, &priv->content_rect);
+ cairo_clip(cr);
+ /*
+ * Data in the fg surface is a ring bufer. Render the first portion
+ * at its given offset.
+ */
+ x = ((priv->x_slots - priv->dps_slot) * priv->dps_each) - offset;
+ cairo_set_source_surface(cr, priv->fg_surface, (gint)x, 0);
+ gdk_cairo_rectangle(cr, &priv->content_rect);
+ cairo_fill(cr);
+ /*
+ * Render the second part of the ring surface buffer.
+ */
+ x = (priv->dps_each * -priv->dps_slot) - offset;
+ cairo_set_source_surface(cr, priv->fg_surface, (gint)x, 0);
+ gdk_cairo_rectangle(cr, &priv->content_rect);
+ cairo_fill(cr);
+ /*
+ * Cleanup.
+ */
+ cairo_restore(cr);
+ } else {
+ /*
+ * TODO: Use XOR command for fallback.
+ */
+ g_warn_if_reached();
+ }
+ /*
+ * Cleanup resources.
+ */
+ //cairo_destroy(cr);
+ return FALSE;
+}
+
+/**
+ * uber_graph_style_set:
+ * @widget: A #GtkWidget.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_graph_style_set (GtkWidget *widget, /* IN */
+ GtkStyle *old_style) /* IN */
+{
+ UberGraphPrivate *priv;
+
+ g_return_if_fail(UBER_IS_GRAPH(widget));
+
+ priv = UBER_GRAPH(widget)->priv;
+ WIDGET_CLASS->style_set(widget, old_style);
+ priv->fg_dirty = TRUE;
+ priv->bg_dirty = TRUE;
+ gtk_widget_queue_draw(widget);
+}
+
+/**
+ * uber_graph_size_allocate:
+ * @widget: A #GtkWidget.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_graph_size_allocate (GtkWidget *widget, /* IN */
+ GtkAllocation *alloc) /* IN */
+{
+ UberGraph *graph;
+ UberGraphPrivate *priv;
+
+ g_return_if_fail(UBER_IS_GRAPH(widget));
+
+ graph = UBER_GRAPH(widget);
+ priv = graph->priv;
+ WIDGET_CLASS->size_allocate(widget, alloc);
+ /*
+ * If there is no window yet, we can defer setup.
+ */
+ if (!gtk_widget_get_window(widget)) {
+ return;
+ }
+ /*
+ * Recalculate rectangles.
+ */
+ uber_graph_calculate_rects(graph);
+ /*
+ * Recreate server side surface.
+ */
+ UNSET_SURFACE(priv->bg_surface);
+ UNSET_SURFACE(priv->fg_surface);
+ uber_graph_init_bg(graph);
+ uber_graph_init_texture(graph);
+ /*
+ * Mark foreground and background as dirty.
+ */
+ priv->fg_dirty = TRUE;
+ priv->bg_dirty = TRUE;
+ priv->full_draw = TRUE;
+ gtk_widget_queue_draw(widget);
+}
+
+static void
+uber_graph_size_request (GtkWidget *widget, /* IN */
+ GtkRequisition *req) /* OUT */
+{
+ g_return_if_fail(req != NULL);
+
+ req->width = 150;
+ req->height = 50;
+}
+
+static void
+uber_graph_get_preferred_width (GtkWidget *widget, /* IN */
+ gint *minimal_width,
+ gint *natural_width)
+{
+ GtkRequisition requisition;
+ uber_graph_size_request(widget, &requisition);
+ *minimal_width = * natural_width = requisition.width;
+}
+
+static void
+uber_graph_get_preferred_height (GtkWidget *widget, /* IN */
+ gint *minimal_height,
+ gint *natural_height)
+{
+ GtkRequisition requisition;
+ uber_graph_size_request(widget, &requisition);
+ *minimal_height = * natural_height = requisition.height;
+}
+
+/**
+ * uber_graph_add_label:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+uber_graph_add_label (UberGraph *graph, /* IN */
+ UberLabel *label) /* IN */
+{
+ UberGraphPrivate *priv;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+ g_return_if_fail(UBER_IS_LABEL(label));
+
+ priv = graph->priv;
+ gtk_box_pack_start(GTK_BOX(priv->labels), GTK_WIDGET(label),
+ TRUE, TRUE, 0);
+ gtk_widget_show(GTK_WIDGET(label));
+}
+
+/**
+ * uber_graph_take_screenshot:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_graph_take_screenshot (UberGraph *graph) /* IN */
+{
+ GtkWidget *widget;
+ GtkWidget *dialog;
+ GtkAllocation alloc;
+ const gchar *filename;
+ cairo_status_t status;
+ cairo_surface_t *surface;
+ cairo_t *cr;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+ widget = GTK_WIDGET(graph);
+ gtk_widget_get_allocation(widget, &alloc);
+
+ /*
+ * Create save dialog and ask user for filename.
+ */
+ dialog = gtk_file_chooser_dialog_new(_("Save As"),
+ GTK_WINDOW(gtk_widget_get_toplevel(widget)),
+ GTK_FILE_CHOOSER_ACTION_SAVE,
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ _("_Save"), GTK_RESPONSE_ACCEPT,
+ NULL);
+ if (GTK_RESPONSE_ACCEPT == gtk_dialog_run(GTK_DIALOG(dialog))) {
+ /*
+ * Create surface and cairo context.
+ */
+ surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
+ alloc.width, alloc.height);
+ cr = cairo_create(surface);
+ cairo_rectangle(cr, 0, 0, alloc.width, alloc.height);
+ cairo_clip(cr);
+
+ /* Paint to the image surface instead of the screen */
+ uber_graph_draw(widget, cr);
+
+ /*
+ * Save surface to png.
+ */
+ filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
+ status = cairo_surface_write_to_png(surface, filename);
+ if (status != CAIRO_STATUS_SUCCESS) {
+ g_critical("Failed to save pixmap to file.");
+ goto cleanup;
+ }
+ /*
+ * Cleanup resources.
+ */
+ cleanup:
+ cairo_destroy(cr);
+ cairo_surface_destroy(surface);
+ }
+ gtk_widget_destroy(dialog);
+}
+
+/**
+ * uber_graph_toggle_paused:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_graph_toggle_paused (UberGraph *graph) /* IN */
+{
+ UberGraphPrivate *priv;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+ priv = graph->priv;
+ priv->paused = !priv->paused;
+ if (priv->fps_handler) {
+ g_source_remove(priv->fps_handler);
+ priv->fps_handler = 0;
+ } else {
+ if (!priv->paused) {
+ uber_graph_redraw(graph);
+ }
+ uber_graph_register_fps_handler(graph);
+ }
+}
+
+/**
+ * uber_graph_button_press:
+ * @widget: A #GtkWidget.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static gboolean
+uber_graph_button_press_event (GtkWidget *widget, /* IN */
+ GdkEventButton *button) /* IN */
+{
+ g_return_val_if_fail(UBER_IS_GRAPH(widget), FALSE);
+
+ switch (button->button) {
+ case 2: /* Middle Click */
+ if (button->state & GDK_CONTROL_MASK) {
+ uber_graph_take_screenshot(UBER_GRAPH(widget));
+ } else {
+ uber_graph_toggle_paused(UBER_GRAPH(widget));
+ }
+ break;
+ default:
+ break;
+ }
+ return FALSE;
+}
+
+/**
+ * uber_graph_finalize:
+ * @object: A #UberGraph.
+ *
+ * Finalizer for a #UberGraph instance. Frees any resources held by
+ * the instance.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_graph_finalize (GObject *object) /* IN */
+{
+ G_OBJECT_CLASS(uber_graph_parent_class)->finalize(object);
+}
+
+/**
+ * uber_graph_dispose:
+ * @object: A #GObject.
+ *
+ * Dispose callback for @object. This method releases references held
+ * by the #GObject instance.
+ *
+ * Returns: None.
+ * Side effects: Plenty.
+ */
+static void
+uber_graph_dispose (GObject *object) /* IN */
+{
+ UberGraph *graph;
+ UberGraphPrivate *priv;
+
+ graph = UBER_GRAPH(object);
+ priv = graph->priv;
+ /*
+ * Stop any timeout handlers.
+ */
+ if (priv->fps_handler) {
+ g_source_remove(priv->fps_handler);
+ }
+ if (priv->dps_handler) {
+ g_source_remove(priv->dps_handler);
+ }
+ /*
+ * Destroy textures.
+ */
+ UNSET_SURFACE(priv->bg_surface);
+ UNSET_SURFACE(priv->fg_surface);
+ /*
+ * Call base class.
+ */
+ G_OBJECT_CLASS(uber_graph_parent_class)->dispose(object);
+}
+
+/**
+ * uber_graph_set_property:
+ * @object: (in): A #GObject.
+ * @prop_id: (in): The property identifier.
+ * @value: (out): The given property.
+ * @pspec: (in): A #ParamSpec.
+ *
+ * Get a given #GObject property.
+ */
+static void
+uber_graph_get_property (GObject *object, /* IN */
+ guint prop_id, /* IN */
+ GValue *value, /* OUT */
+ GParamSpec *pspec) /* IN */
+{
+ UberGraph *graph = UBER_GRAPH(object);
+
+ switch (prop_id) {
+ case PROP_FORMAT:
+ g_value_set_uint(value, graph->priv->format);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ }
+}
+
+/**
+ * uber_graph_set_property:
+ * @object: (in): A #GObject.
+ * @prop_id: (in): The property identifier.
+ * @value: (in): The given property.
+ * @pspec: (in): A #ParamSpec.
+ *
+ * Set a given #GObject property.
+ */
+static void
+uber_graph_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ UberGraph *graph = UBER_GRAPH(object);
+
+ switch (prop_id) {
+ case PROP_FORMAT:
+ uber_graph_set_format(graph, g_value_get_uint(value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ }
+}
+
+/**
+ * uber_graph_class_init:
+ * @klass: A #UberGraphClass.
+ *
+ * Initializes the #UberGraphClass and prepares the vtable.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_graph_class_init (UberGraphClass *klass) /* IN */
+{
+ GObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+
+ object_class = G_OBJECT_CLASS(klass);
+ object_class->dispose = uber_graph_dispose;
+ object_class->finalize = uber_graph_finalize;
+ object_class->get_property = uber_graph_get_property;
+ object_class->set_property = uber_graph_set_property;
+
+ widget_class = GTK_WIDGET_CLASS(klass);
+ widget_class->draw = uber_graph_draw;
+ widget_class->hide = uber_graph_hide;
+ widget_class->realize = uber_graph_realize;
+ widget_class->screen_changed = uber_graph_screen_changed;
+ widget_class->show = uber_graph_show;
+ widget_class->size_allocate = uber_graph_size_allocate;
+ widget_class->style_set = uber_graph_style_set;
+ widget_class->unrealize = uber_graph_unrealize;
+ widget_class->get_preferred_width = uber_graph_get_preferred_width;
+ widget_class->get_preferred_height = uber_graph_get_preferred_height;
+ widget_class->button_press_event = uber_graph_button_press_event;
+
+ show_fps = !!g_getenv("UBER_SHOW_FPS");
+
+ /*
+ * FIXME: Use enum.
+ */
+ g_object_class_install_property(object_class,
+ PROP_FORMAT,
+ g_param_spec_uint("format",
+ "format",
+ "format",
+ 0,
+ UBER_GRAPH_FORMAT_PERCENT,
+ 0,
+ G_PARAM_READWRITE));
+}
+
+/**
+ * uber_graph_init:
+ * @graph: A #UberGraph.
+ *
+ * Initializes the newly created #UberGraph instance.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_graph_init (UberGraph *graph) /* IN */
+{
+ UberGraphPrivate *priv;
+
+ /*
+ * Store pointer to private data allocation.
+ */
+ graph->priv = uber_graph_get_instance_private(graph);
+
+ priv = graph->priv;
+ /*
+ * Enable required events.
+ */
+ gtk_widget_set_events(GTK_WIDGET(graph), GDK_BUTTON_PRESS_MASK);
+ /*
+ * Prepare default values.
+ */
+ priv->tick_len = 10;
+ priv->fps = 20;
+ priv->fps_real = 1000. / priv->fps;
+ priv->dps = 1.;
+ priv->x_slots = 60;
+ priv->fg_dirty = TRUE;
+ priv->bg_dirty = TRUE;
+ priv->full_draw = TRUE;
+ priv->show_xlines = TRUE;
+ priv->show_ylines = TRUE;
+ /*
+ * TODO: Support labels in a grid.
+ */
+ priv->labels = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 3);
+ gtk_box_set_homogeneous (GTK_BOX(priv->labels), TRUE);
+ priv->align = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+ gtk_container_add(GTK_CONTAINER(priv->align), priv->labels);
+ gtk_widget_show(priv->labels);
+}