#include "TimerGraph.h" #include #include struct _TimerGraph { GtkBin parent; GtkWidget *drawingArea; GdkPixbuf *originalData; cairo_surface_t *tex; double gx; double gy; }; G_DEFINE_TYPE(TimerGraph, timer_graph, GTK_TYPE_BIN); static char *get_graph_format() { GSList *list = gdk_pixbuf_get_formats(); GSList *i; for (i = list; i != NULL; i = i->next) { GdkPixbufFormat *f = i->data; if (!gdk_pixbuf_format_is_disabled(f)) { gchar **exts = gdk_pixbuf_format_get_extensions(f); while (*exts != NULL) { if (strcmp(*exts, PLOTUTILS_PREFERRED_FORMAT) == 0){ g_slist_free(list); return PLOTUTILS_PREFERRED_FORMAT; } ++exts; } } } g_warning("fallback image forat ('" PLOTUTILS_FALLBACK_FORMAT "') used"); g_slist_free(list); return PLOTUTILS_FALLBACK_FORMAT; } static char *create_graph_bytes(const TimerGraphPoint *verts, gsize length, const char *format, const char *xLabel, const char *yLabel, const char *title, gsize *len) { GString *dataString = g_string_new(NULL); gsize i; for (i = 0; i < length; ++i) { g_string_append_printf(dataString, "%d.0 %d.0", verts[i].x, verts[i].y); if (i < length - 1) { g_string_append_c(dataString, '\n'); } } char *pointsString = g_string_free(dataString, FALSE); int ptoc[2]; if (pipe(ptoc) < 0) { g_free(pointsString); GtkWidget *diag = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "An internal error occured. Please try again."); gtk_window_set_position(GTK_WINDOW(diag), GTK_WIN_POS_MOUSE); gtk_dialog_run(GTK_DIALOG(diag)); gtk_widget_destroy(diag); return NULL; } int ctop[2]; if (pipe(ctop) < 0) { g_free(pointsString); close(ptoc[0]); close(ptoc[1]); GtkWidget *diag = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "An internal error occured. Please try again."); gtk_window_set_position(GTK_WINDOW(diag), GTK_WIN_POS_MOUSE); gtk_dialog_run(GTK_DIALOG(diag)); gtk_widget_destroy(diag); return NULL; } pid_t p = fork(); if (p == 0) { /* child */ g_free(pointsString); dup2(ptoc[0], fileno(stdin)); close(ptoc[1]); dup2(ctop[1], fileno(stdout)); close(ctop[0]); freopen("/dev/null", "w", stderr); execlp(PLOTUTILS_GRAPH_PATH, PLOTUTILS_GRAPH_PATH, "-T", format, "-X", xLabel, "-Y", yLabel, "-L", title, NULL); /* If graph(1) could not be executed */ exit(0); } else { /* parent */ int out = ptoc[1]; close(ptoc[0]); int in = ctop[0]; close(ctop[1]); write(out, pointsString, strlen(pointsString)); close(out); g_free(pointsString); int status; if (waitpid(p, &status, 0) < 0) { close(in); g_warning("graph(1) failed"); return NULL; } else if (WIFEXITED(status) && WEXITSTATUS(status) != 0) { close(in); g_warning("graph(1) failed"); return NULL; } GString *buff = g_string_new(NULL); char c; *len = 0; while (read(in, &c, 1) > 0) { g_string_append_c(buff, c); ++(*len); } close(in); char *resp = g_string_free(buff, FALSE); return resp; } } static GdkPixbuf *pixbuf_from_data(const char *data, gsize len) { GdkPixbufLoader *loader = gdk_pixbuf_loader_new(); GError *err = NULL; gdk_pixbuf_loader_write(loader, (const guchar *) data, len, &err); gdk_pixbuf_loader_close(loader, NULL); if (err != NULL) { GtkWidget *diag = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "An internal error occured. Please try again."); gtk_window_set_position(GTK_WINDOW(diag), GTK_WIN_POS_MOUSE); gtk_dialog_run(GTK_DIALOG(diag)); gtk_widget_destroy(diag); g_object_unref(loader); g_error_free(err); return NULL; } GdkPixbuf *pixbuf = gdk_pixbuf_loader_get_pixbuf(loader); g_object_ref(pixbuf); g_object_unref(loader); return pixbuf; } static void da_size_callback(GtkDrawingArea *self, GtkAllocation *allocation, TimerGraph *graph) { if (graph->originalData) { int size = MIN(allocation->width, allocation->height); if (graph->tex) { cairo_surface_destroy(graph->tex); } graph->gx = ((double) allocation->width / 2) - ((double) size / 2); graph->gy = ((double) allocation->height / 2) - ((double) size / 2); GdkPixbuf *scaled = gdk_pixbuf_scale_simple(graph->originalData, size, size, GDK_INTERP_BILINEAR); graph->tex = gdk_cairo_surface_create_from_pixbuf(scaled, 1, gtk_widget_get_window(GTK_WIDGET(self))); g_object_unref(scaled); } } static gboolean da_draw_callback(GtkDrawingArea *self, cairo_t *cr, TimerGraph *graph) { GtkStyleContext *cxt = gtk_widget_get_style_context(GTK_WIDGET(self)); guint width = gtk_widget_get_allocated_width(GTK_WIDGET(self)); guint height = gtk_widget_get_allocated_height(GTK_WIDGET(self)); gtk_render_background(cxt, cr, 0, 0, width, height); if (graph->tex) { cairo_set_source_surface(cr, graph->tex, graph->gx, graph->gy); } cairo_paint(cr); return TRUE; } GtkWidget *timer_graph_new(const TimerGraphPoint *verts, gsize length, const char *xLabel, const char *yLabel, const char *title) { TimerGraph *g = g_object_new(TIMER_TYPE_GRAPH, NULL); gsize len = 0; char *data = create_graph_bytes(verts, length, get_graph_format(), xLabel, yLabel, title, &len); g->originalData = pixbuf_from_data(data, len); g_free(data); return GTK_WIDGET(g); } static void timer_graph_finalize(GObject *obj) { TimerGraph *self = TIMER_GRAPH(obj); if (self->originalData) { g_object_unref(self->originalData); } if (self->tex) { cairo_surface_destroy(self->tex); } G_OBJECT_CLASS(timer_graph_parent_class)->finalize(obj); } static void timer_graph_class_init(TimerGraphClass *class) { G_OBJECT_CLASS(class)->finalize = timer_graph_finalize; } static void timer_graph_init(TimerGraph* self) { self->drawingArea = gtk_drawing_area_new(); gtk_container_add(GTK_CONTAINER(self), self->drawingArea); gtk_widget_set_size_request(GTK_WIDGET(self), 100, 100); g_signal_connect(self->drawingArea, "size-allocate", G_CALLBACK(da_size_callback), self); g_signal_connect(self->drawingArea, "draw", G_CALLBACK(da_draw_callback), self); }