/* uber-graph.c * * Copyright (C) 2010 Christian Hergert * * 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 . */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #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); }