practice-timer/TimerGraph.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);
}