diff options
author | Lucas de Castro Borges <lucas@gnuabordo.com.br> | 2024-04-22 00:35:56 -0300 |
---|---|---|
committer | Lucas de Castro Borges <lucas@gnuabordo.com.br> | 2024-04-22 00:35:56 -0300 |
commit | 754b5d1114f096778e483f8a6f3a5dc333225e26 (patch) | |
tree | 30911ec9da4cfd2f5572c27f7288fcbfa4cd212d /deps/uber-graph/uber-graph.c | |
parent | 35c2857da302ab8b3c308052f2cd1674fb4141a6 (diff) | |
parent | 5f01c706267c595de92406a32e7f31ef5056c2d0 (diff) |
Update upstream source from tag 'upstream/2.0.3pre'
Update to upstream version '2.0.3pre'
with Debian dir 6683980bf6b5c02f6847fd56765833301f75f4f3
Diffstat (limited to 'deps/uber-graph/uber-graph.c')
-rw-r--r-- | deps/uber-graph/uber-graph.c | 2166 |
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); +} |