191 lines
6.7 KiB
C
191 lines
6.7 KiB
C
|
#include "TimerGraph.h"
|
||
|
|
||
|
#include <unistd.h>
|
||
|
#include <sys/wait.h>
|
||
|
|
||
|
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);
|
||
|
}
|