summaryrefslogtreecommitdiff
path: root/util/show-events.c
diff options
context:
space:
mode:
authorChris Wilson <chris@chris-wilson.co.uk>2008-12-17 09:32:16 +0000
committerChris Wilson <chris@chris-wilson.co.uk>2009-08-29 08:08:28 +0100
commitf8bb3617c3a7ec598c42eff1f8562e3ccc95127f (patch)
treef6c8f949ab44ca126053fb5cea2b9b88a4748cb8 /util/show-events.c
parent7c499db8afe8a7cf8c512ec166fe7dbf11a25c02 (diff)
downloadcairo-f8bb3617c3a7ec598c42eff1f8562e3ccc95127f.tar.gz
Eliminate self-intersecting strokes.
We refactor the surface fallbacks to convert full strokes and fills to the intermediate polygon representation (as opposed to before where we returned the trapezoidal representation). This allow greater flexibility to choose how then to rasterize the polygon. Where possible we use the local spans rasteriser for its increased performance, but still have the option to use the tessellator instead (for example, with the current Render protocol which does not yet have a polygon image). In order to accommodate this, the spans interface is tweaked to accept whole polygons instead of a path and the tessellator is tweaked for speed. Performance Impact ================== ... Still measuring, expecting some severe regressions. ...
Diffstat (limited to 'util/show-events.c')
-rw-r--r--util/show-events.c845
1 files changed, 845 insertions, 0 deletions
diff --git a/util/show-events.c b/util/show-events.c
new file mode 100644
index 000000000..8bff3efc4
--- /dev/null
+++ b/util/show-events.c
@@ -0,0 +1,845 @@
+#define _GNU_SOURCE
+#include <gtk/gtk.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <gdk/gdkkeysyms.h>
+#include <math.h>
+
+typedef struct _point {
+ gdouble x, y;
+} point_t;
+typedef struct _box {
+ point_t p1, p2;
+} box_t;
+typedef struct _line {
+ point_t p1, p2;
+} line_t;
+
+typedef struct _edge {
+ gulong id;
+ line_t line;
+ gdouble top, bottom;
+ point_t p1, p2;
+ int dir;
+} edge_t;
+typedef struct _trapezoid {
+ gdouble top, bottom;
+ const edge_t *left, *right;
+} trapezoid_t;
+typedef struct _traps {
+ int num_traps;
+ int size;
+ trapezoid_t traps[0];
+} traps_t;
+
+typedef struct _edges {
+ GHashTable *ht;
+
+ int num_edges;
+ int size;
+ edge_t edges[0];
+} edges_t;
+
+typedef struct _event {
+ enum {
+ START_EDGE,
+ END_EDGE,
+ INTERSECTION,
+ START_TRAP,
+ END_TRAP,
+ } type;
+
+ int x, y; /* (top, bottom) for trap */
+ long e1, e2;
+} event_t;
+
+typedef struct _events {
+ struct _events *prev, *next;
+
+ box_t extents;
+ edges_t *edges;
+ traps_t *prototraps;
+ traps_t *traps;
+
+ int current_event;
+ int num_events;
+ int size_events;
+ event_t *events;
+} events_t;
+
+typedef struct _EventView {
+ GtkWidget widget;
+
+ events_t *events_list;
+ events_t *current_events;
+
+ double px, py;
+
+ gint mag_x, mag_y;
+ gint mag_size;
+ gdouble mag_zoom;
+ gboolean in_mag_drag;
+ gint mag_drag_x, mag_drag_y;
+} EventView;
+
+typedef struct _EventViewClass {
+ GtkWidgetClass parent_class;
+} EventViewClass;
+
+G_DEFINE_TYPE (EventView, event_view, GTK_TYPE_WIDGET)
+
+static edge_t *
+edges_lookup (edges_t *edges, gulong id)
+{
+ return &edges->edges[GPOINTER_TO_UINT(g_hash_table_lookup (edges->ht,
+ GUINT_TO_POINTER
+ (id)))];
+}
+
+static gdouble
+_compute_intersection_x_for_y (const line_t *line,
+ gdouble y)
+{
+ gdouble dx = line->p2.x - line->p1.x;
+ gdouble dy = line->p2.y - line->p1.y;
+ gdouble x;
+
+ if (y == line->p1.y)
+ return line->p1.x;
+ if (y == line->p2.y)
+ return line->p2.x;
+
+ x = line->p1.x;
+ if (dy != 0)
+ x += (y - line->p1.y)*dx/dy;
+ return x;
+}
+
+static void
+_compute_intersection_point (const line_t *line,
+ gdouble y,
+ point_t *p)
+{
+ p->x = _compute_intersection_x_for_y (line, p->y = y);
+}
+
+static void
+_edge_path (cairo_t *cr, const cairo_matrix_t *m, const edge_t *e)
+{
+ double x, y;
+
+ x = e->p1.x; y = e->p1.y;
+ cairo_matrix_transform_point (m, &x, &y);
+ cairo_move_to (cr, x, y);
+
+ x = e->p2.x; y = e->p2.y;
+ cairo_matrix_transform_point (m, &x, &y);
+ cairo_line_to (cr, x, y);
+
+ if (e->dir < 0) {
+ cairo_set_source_rgb (cr, 0, 0, 1);
+ } else {
+ cairo_set_source_rgb (cr, 1, 0, 0);
+ }
+}
+
+static void
+_events_draw (events_t *events, cairo_t *cr, cairo_matrix_t *m)
+{
+ double dash[2] = {8, 8};
+ point_t p;
+ int n;
+
+ /* first the existing and proto-traps */
+ cairo_save (cr); {
+ cairo_set_matrix (cr, m);
+
+ cairo_set_source_rgba (cr, 1, 0, 0, .15);
+ for (n = 0; n < events->prototraps->num_traps; n++) {
+ const trapezoid_t *t = &events->prototraps->traps[n];
+
+ _compute_intersection_point (&t->left->line, t->top, &p);
+ cairo_move_to (cr, p.x, p.y);
+ _compute_intersection_point (&t->right->line, t->top, &p);
+ cairo_line_to (cr, p.x, p.y);
+ _compute_intersection_point (&t->right->line, t->bottom, &p);
+ cairo_line_to (cr, p.x, p.y);
+ _compute_intersection_point (&t->left->line, t->bottom, &p);
+ cairo_line_to (cr, p.x, p.y);
+ cairo_close_path (cr);
+ cairo_fill (cr);
+ }
+
+ cairo_set_source_rgba (cr, 0, 1, 0, .2);
+ for (n = 0; n < events->traps->num_traps; n++) {
+ const trapezoid_t *t = &events->traps->traps[n];
+
+ _compute_intersection_point (&t->left->line, t->top, &p);
+ cairo_move_to (cr, p.x, p.y);
+ _compute_intersection_point (&t->right->line, t->top, &p);
+ cairo_line_to (cr, p.x, p.y);
+ _compute_intersection_point (&t->right->line, t->bottom, &p);
+ cairo_line_to (cr, p.x, p.y);
+ _compute_intersection_point (&t->left->line, t->bottom, &p);
+ cairo_line_to (cr, p.x, p.y);
+ cairo_close_path (cr);
+ cairo_fill (cr);
+ }
+ } cairo_restore (cr);
+
+ /* known edges */
+ cairo_save (cr);
+ cairo_set_line_width (cr, 1.);
+ for (n = 0; n < events->edges->num_edges; n++) {
+ const edge_t *e = &events->edges->edges[n];
+ double x, y;
+
+ x = e->p1.x; y = e->p1.y;
+ cairo_matrix_transform_point (m, &x, &y);
+ cairo_move_to (cr, x, y);
+
+ x = e->p2.x; y = e->p2.y;
+ cairo_matrix_transform_point (m, &x, &y);
+ cairo_line_to (cr, x, y);
+
+ if (e->dir < 0) {
+ cairo_set_source_rgba (cr, 0, 0, 1., .4);
+ cairo_set_dash (cr, dash, 2, fmod (e->p1.x, dash[0]+dash[1]) + dash[0]);
+ } else {
+ cairo_set_source_rgba (cr, 1, 0, 0., 4);
+ cairo_set_dash (cr, dash, 2, fmod (e->p1.x, dash[0]+dash[1]));
+ }
+
+ cairo_stroke (cr);
+
+ x = e->p1.x; y = e->p1.y;
+ cairo_matrix_transform_point (m, &x, &y);
+ cairo_arc (cr, x, y, 2., 0, 2 * G_PI);
+
+ x = e->p2.x; y = e->p2.y;
+ cairo_matrix_transform_point (m, &x, &y);
+ cairo_arc (cr, x, y, 2., 0, 2 * G_PI);
+
+ cairo_fill (cr);
+ }
+ cairo_restore (cr);
+
+ /* event time */
+ cairo_save (cr); {
+ event_t *e;
+ double x, y;
+
+ e = &events->events[events->current_event];
+
+ cairo_set_line_width (cr, 2.);
+ cairo_set_matrix (cr, m);
+ cairo_move_to (cr,
+ events->extents.p1.x,
+ e->y);
+ cairo_line_to (cr,
+ events->extents.p2.x,
+ e->y);
+ cairo_identity_matrix (cr);
+ cairo_stroke (cr);
+
+ x = e->x; y = e->y;
+ cairo_matrix_transform_point (m, &x, &y);
+ switch (e->type) {
+ case START_EDGE:
+ case END_EDGE:
+ case INTERSECTION:
+ cairo_arc (cr, x, y, 4., 0, 2 * G_PI);
+ break;
+ case START_TRAP:
+ case END_TRAP:
+ break;
+ }
+ switch (e->type) {
+ case START_EDGE:
+ cairo_set_source_rgb (cr, 1, 0, 0);
+ break;
+ case END_EDGE:
+ cairo_set_source_rgb (cr, 0, 0, 1);
+ break;
+ case INTERSECTION:
+ cairo_set_source_rgb (cr, 1, 0, 1);
+ break;
+ case START_TRAP:
+ case END_TRAP:
+ break;
+ }
+ cairo_fill (cr);
+
+ cairo_set_line_width (cr, 1.);
+ switch (e->type) {
+ case START_EDGE:
+ _edge_path (cr, m, edges_lookup (events->edges, e->e1));
+ cairo_stroke (cr);
+ break;
+ case END_EDGE:
+ _edge_path (cr, m, edges_lookup (events->edges, e->e1));
+ cairo_stroke (cr);
+ break;
+ case INTERSECTION:
+ _edge_path (cr, m, edges_lookup (events->edges, e->e1));
+ cairo_stroke (cr);
+ _edge_path (cr, m, edges_lookup (events->edges, e->e2));
+ cairo_stroke (cr);
+ break;
+ }
+ } cairo_restore (cr);
+}
+
+static void
+event_view_draw (EventView *self, cairo_t *cr)
+{
+ events_t *events;
+ gdouble sf_x, sf_y, sf;
+ gdouble mid, dim;
+ gdouble x0, x1, y0, y1;
+ cairo_matrix_t m;
+
+ cairo_save (cr);
+ cairo_set_source_rgb (cr, 1, 1, 1);
+ cairo_paint (cr);
+ cairo_restore (cr);
+
+ events = self->current_events;
+ if (events == NULL)
+ return;
+
+ mid = (events->extents.p2.x + events->extents.p1.x) / 2.;
+ dim = (events->extents.p2.x - events->extents.p1.x) / 2. * 1.2;
+ sf_x = self->widget.allocation.width / dim / 2;
+
+ mid = (events->extents.p2.y + events->extents.p1.y) / 2.;
+ dim = (events->extents.p2.y - events->extents.p1.y) / 2. * 1.2;
+ sf_y = self->widget.allocation.height / dim / 2;
+
+ sf = MIN (sf_x, sf_y);
+
+ mid = (events->extents.p2.x + events->extents.p1.x) / 2.;
+ dim = sf_x / sf * (events->extents.p2.x - events->extents.p1.x) / 2. * 1.2;
+ x0 = mid - dim;
+ x1 = mid + dim;
+ mid = (events->extents.p2.y + events->extents.p1.y) / 2.;
+ dim = sf_y / sf * (events->extents.p2.y - events->extents.p1.y) / 2. * 1.2;
+ y0 = mid - dim;
+ y1 = mid + dim;
+
+ cairo_matrix_init_scale (&m, sf, sf);
+ cairo_matrix_translate (&m, -x0, -y0);
+ _events_draw (events, cr, &m);
+
+ /* draw a zoom view of the area around the mouse */
+ cairo_save (cr); {
+ double zoom = self->mag_zoom;
+ int size = self->mag_size;
+
+ /* bottom right */
+ cairo_rectangle (cr, self->mag_x, self->mag_y, size, size);
+ cairo_stroke_preserve (cr);
+ cairo_set_source_rgb (cr, 1, 1, 1);
+ cairo_fill_preserve (cr);
+ cairo_clip (cr);
+
+ /* compute roi in extents */
+ cairo_translate (cr, self->mag_x + size/2, self->mag_y + size/2);
+
+ cairo_matrix_init_scale (&m, zoom, zoom);
+ cairo_matrix_translate (&m, -(self->px / sf + x0), -(self->py /sf + y0));
+ _events_draw (events, cr, &m);
+
+ /* grid */
+ cairo_save (cr); {
+ int i;
+
+ cairo_translate (cr,
+ -zoom*fmod (self->px/sf + x0, 1.),
+ -zoom*fmod (self->py/sf + y0, 1.));
+ for (i = -size/2/zoom - 1; i <= size/2/zoom + 1; i++) {
+ cairo_move_to (cr, zoom*i, -size/2);
+ cairo_line_to (cr, zoom*i, size/2 + zoom);
+ cairo_move_to (cr, -size/2, zoom*i);
+ cairo_line_to (cr, size/2 + zoom, zoom*i);
+ }
+ cairo_set_source_rgba (cr, .7, .7, .7, .5);
+ cairo_set_line_width (cr, 1.);
+ cairo_stroke (cr);
+ } cairo_restore (cr);
+ } cairo_restore (cr);
+}
+
+static void
+event_view_draw_labels (EventView *self, cairo_t *cr)
+{
+ events_t *events;
+
+ events = self->current_events;
+ if (events == NULL)
+ return;
+
+}
+
+static gboolean
+event_view_expose (GtkWidget *w, GdkEventExpose *ev)
+{
+ EventView *self = (EventView *) w;
+ cairo_t *cr;
+
+ cr = gdk_cairo_create (w->window);
+ gdk_cairo_region (cr, ev->region);
+ cairo_clip (cr);
+
+ event_view_draw (self, cr);
+ event_view_draw_labels (self, cr);
+
+ cairo_destroy (cr);
+ return FALSE;
+}
+
+static void
+traps_clear (traps_t *traps)
+{
+ traps->num_traps = 0;
+}
+
+static traps_t *
+traps_add (traps_t *traps, int top, int bot, const edge_t *e1, const edge_t *e2)
+{
+ trapezoid_t *t;
+
+ if (traps->num_traps == traps->size) {
+ traps->size *= 2;
+ traps = g_realloc (traps,
+ sizeof (traps_t) + traps->size*sizeof (trapezoid_t));
+ }
+
+ t = &traps->traps[traps->num_traps++];
+ t->top = top;
+ if (bot > e1->bottom)
+ bot = e1->bottom;
+ if (bot > e2->bottom)
+ bot = e2->bottom;
+ t->bottom = bot;
+
+ t->left = e1;
+ t->right = e2;
+
+ return traps;
+}
+
+static void
+traps_remove (traps_t *traps, int top, const edge_t *e1, const edge_t *e2)
+{
+ int n;
+
+ for (n = 0; n < traps->num_traps; n++) {
+ trapezoid_t *t = &traps->traps[n];
+ if (t->top == top && t->left == e1 && t->right == e2)
+ break;
+ }
+ if (n < traps->num_traps) {
+ g_memmove (&traps->traps[n],
+ &traps->traps[n+1],
+ (traps->num_traps-n+1) * sizeof (trapezoid_t));
+ traps->num_traps--;
+ }
+}
+
+static void
+event_next (EventView *self)
+{
+ events_t *events;
+ event_t *e;
+
+ events = self->current_events;
+ if (++events->current_event == events->num_events) {
+ return;
+ } else if (events->current_event >= events->num_events) {
+ traps_clear (events->prototraps);
+ traps_clear (events->traps);
+ events->current_event = 0;
+
+ self->current_events = events->next;
+ if (self->current_events == NULL)
+ self->current_events = self->events_list;
+ events = self->current_events;
+ }
+
+ e = &events->events[events->current_event];
+ switch (e->type) {
+ case START_TRAP:
+ events->prototraps = traps_add (events->prototraps,
+ e->x, G_MAXINT,
+ edges_lookup (events->edges, e->e1),
+ edges_lookup (events->edges, e->e2));
+ break;
+ case END_TRAP:
+ traps_remove (events->prototraps,
+ e->x,
+ edges_lookup (events->edges, e->e1),
+ edges_lookup (events->edges, e->e2));
+ events->traps = traps_add (events->traps,
+ e->x, e->y,
+ edges_lookup (events->edges, e->e1),
+ edges_lookup (events->edges, e->e2));
+ break;
+ }
+}
+
+static gboolean
+event_view_button_press (GtkWidget *w, GdkEventButton *ev)
+{
+ EventView *self = (EventView *) w;
+
+ if (ev->x < self->mag_x ||
+ ev->y < self->mag_y ||
+ ev->x > self->mag_x + self->mag_size ||
+ ev->y > self->mag_y + self->mag_size)
+ {
+ if (ev->type == GDK_BUTTON_PRESS) {
+ event_next (self);
+ gtk_widget_queue_draw (w);
+ }
+ }
+ else
+ {
+ self->in_mag_drag = TRUE;
+ self->mag_drag_x = ev->x;
+ self->mag_drag_y = ev->y;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+event_view_button_release (GtkWidget *w, GdkEventButton *ev)
+{
+ EventView *self = (EventView *) w;
+
+ self->in_mag_drag = FALSE;
+
+ return FALSE;
+}
+
+static gboolean
+event_view_motion (GtkWidget *w, GdkEventMotion *ev)
+{
+ EventView *self = (EventView *) w;
+
+ if (self->in_mag_drag) {
+ self->mag_x += ev->x - self->mag_drag_x;
+ self->mag_y += ev->y - self->mag_drag_y;
+
+ gtk_widget_queue_draw (&self->widget);
+
+ self->mag_drag_x = ev->x;
+ self->mag_drag_y = ev->y;
+ } else if (ev->x < self->mag_x ||
+ ev->y < self->mag_y ||
+ ev->x > self->mag_x + self->mag_size ||
+ ev->y > self->mag_y + self->mag_size)
+ {
+ self->px = ev->x;
+ self->py = ev->y;
+
+ gtk_widget_queue_draw (&self->widget);
+ }
+
+ return FALSE;
+}
+
+static void
+event_view_realize (GtkWidget *widget)
+{
+ GdkWindowAttr attributes;
+
+ GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
+
+ attributes.window_type = GDK_WINDOW_CHILD;
+ attributes.x = widget->allocation.x;
+ attributes.y = widget->allocation.y;
+ attributes.width = widget->allocation.width;
+ attributes.height = widget->allocation.height;
+ attributes.wclass = GDK_INPUT_OUTPUT;
+ attributes.visual = gtk_widget_get_visual (widget);
+ attributes.colormap = gtk_widget_get_colormap (widget);
+ attributes.event_mask = gtk_widget_get_events (widget) |
+ GDK_BUTTON_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK |
+ GDK_KEY_PRESS_MASK |
+ GDK_KEY_RELEASE_MASK |
+ GDK_POINTER_MOTION_MASK |
+ GDK_BUTTON_MOTION_MASK |
+ GDK_EXPOSURE_MASK;
+
+ widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
+ &attributes,
+ GDK_WA_X | GDK_WA_Y |
+ GDK_WA_VISUAL | GDK_WA_COLORMAP);
+ gdk_window_set_user_data (widget->window, widget);
+
+ widget->style = gtk_style_attach (widget->style, widget->window);
+ gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
+}
+
+static void
+event_view_size_allocate (GtkWidget *w, GdkRectangle *r)
+{
+ EventView *self = (EventView *) w;
+
+ GTK_WIDGET_CLASS (event_view_parent_class)->size_allocate (w, r);
+
+ self->mag_x = w->allocation.width - self->mag_size - 10;
+ self->mag_y = w->allocation.height - self->mag_size - 10;
+}
+
+static void
+event_view_finalize (GObject *obj)
+{
+ G_OBJECT_CLASS (event_view_parent_class)->finalize (obj);
+}
+
+static void
+event_view_class_init (EventViewClass *klass)
+{
+ GObjectClass *object_class = (GObjectClass *) klass;
+ GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;
+
+ object_class->finalize = event_view_finalize;
+
+ widget_class->realize = event_view_realize;
+ widget_class->size_allocate = event_view_size_allocate;
+ widget_class->expose_event = event_view_expose;
+ widget_class->button_press_event = event_view_button_press;
+ widget_class->button_release_event = event_view_button_release;
+ widget_class->motion_notify_event = event_view_motion;
+}
+
+static void
+event_view_init (EventView *self)
+{
+ self->mag_zoom = 10;
+ self->mag_size = 200;
+}
+
+static traps_t *
+traps_new (void)
+{
+ traps_t *t;
+
+ t = g_malloc (sizeof (traps_t) + 16 * sizeof (trapezoid_t));
+
+ t->size = 16;
+ t->num_traps = 0;
+
+ return t;
+}
+
+static edges_t *
+_edges_add_edge (edges_t *edges, edge_t *e, box_t *extents)
+{
+ if (e->top < extents->p1.y)
+ extents->p1.y = e->top;
+ if (e->bottom > extents->p2.y)
+ extents->p2.y = e->bottom;
+
+ _compute_intersection_point (&e->line, e->top, &e->p1);
+ _compute_intersection_point (&e->line, e->bottom, &e->p2);
+
+ if (e->p1.x < extents->p1.x)
+ extents->p1.x = e->p1.x;
+ if (e->p2.x < extents->p1.x)
+ extents->p1.x = e->p2.x;
+
+ if (e->p1.x > extents->p2.x)
+ extents->p2.x = e->p1.x;
+ if (e->p2.x > extents->p2.x)
+ extents->p2.x = e->p2.x;
+
+ if (edges->num_edges == edges->size) {
+ edges->size *= 2;
+ edges = g_realloc (edges,
+ sizeof (edges_t) + edges->size * sizeof (edge_t));
+ }
+
+ g_hash_table_insert (edges->ht,
+ GUINT_TO_POINTER (e->id),
+ GUINT_TO_POINTER (edges->num_edges));
+ edges->edges[edges->num_edges++] = *e;
+
+ return edges;
+}
+
+static void
+_events_add_event (events_t *events,
+ int type,
+ int x, int y,
+ gulong e1, gulong e2)
+{
+ event_t *e;
+
+ if (events->num_events == events->size_events) {
+ int newsize = 2 * events->size_events;
+ void *newevents;
+
+ newevents = g_renew (event_t, events->events, newsize);
+ events->events = newevents;
+ events->size_events = newsize;
+ }
+
+ e = &events->events[events->num_events++];
+ e->type = type;
+ e->x = x;
+ e->y = y;
+ e->e1 = e1;
+ e->e2 = e2;
+}
+
+static edges_t *
+edges_new (void)
+{
+ edges_t *t;
+
+ t = g_malloc (sizeof (edges_t) + 16 * sizeof (edge_t));
+ t->ht = g_hash_table_new (NULL, NULL);
+ t->size = 16;
+ t->num_edges = 0;
+
+ return t;
+}
+
+static events_t *
+events_new (void)
+{
+ events_t *events;
+
+ events = g_malloc (sizeof (events_t));
+
+ events->next = NULL;
+ events->prev = NULL;
+
+ events->events = g_new (event_t, 16);
+ events->size_events = 16;
+ events->num_events = 0;
+ events->current_event = 0;
+
+ events->edges = edges_new ();
+ events->prototraps = traps_new ();
+ events->traps = traps_new ();
+
+ events->extents.p1.x = G_MAXDOUBLE;
+ events->extents.p1.y = G_MAXDOUBLE;
+ events->extents.p2.x = -G_MAXDOUBLE;
+ events->extents.p2.y = -G_MAXDOUBLE;
+
+ return events;
+}
+
+static void
+events_read (EventView *ev, const char *filename)
+{
+ FILE *file;
+
+ file = fopen (filename, "r");
+ if (file != NULL) {
+ char *line = NULL;
+ size_t len = 0;
+ events_t *events;
+
+ events = ev->events_list = events_new ();
+ while (getline (&line, &len, file) != -1) {
+ line = g_strstrip (line);
+ if (*line == '\0') {
+ events->next = events_new ();
+ events->next->prev = events;
+ events = events->next;
+ } else if (g_str_has_prefix (line, "edge:")) {
+ edge_t edge;
+
+ sscanf (line, "edge: %lu (%lf, %lf) (%lf, %lf) (%lf, %lf) %d",
+ &edge.id,
+ &edge.line.p1.x,
+ &edge.line.p1.y,
+ &edge.line.p2.x,
+ &edge.line.p2.y,
+ &edge.top,
+ &edge.bottom,
+ &edge.dir);
+
+ events->edges = _edges_add_edge (events->edges,
+ &edge,
+ &events->extents);
+ } else if (g_str_has_prefix (line, "event:")) {
+ int type;
+ int x,y;
+ gulong e1, e2;
+
+ sscanf (line, "event: %d (%d, %d) %lu %lu",
+ &type, &x, &y,
+ &e1, &e2);
+
+ _events_add_event (events, type, x, y, e1, e2);
+ } else if (g_str_has_prefix (line, "begin trap:")) {
+ int top;
+ gulong e1, e2;
+
+ sscanf (line, "begin trap: %lu %lu %u", &e1, &e2, &top);
+
+ _events_add_event (events, START_TRAP, top, 0, e1, e2);
+ } else if (g_str_has_prefix (line, "end trap:")) {
+ int top, bottom;
+ gulong e1, e2;
+
+ sscanf (line, "end trap: %lu %lu %d %d",
+ &e1, &e2, &top, &bottom);
+
+ _events_add_event (events, END_TRAP, top, bottom, e1, e2);
+ }
+ }
+
+ ev->current_events = ev->events_list;
+
+ free (line);
+ fclose (file);
+ }
+}
+
+static gboolean
+timeout_advance (EventView *self)
+{
+ event_next (self);
+ gtk_widget_queue_draw (&self->widget);
+ return TRUE;
+}
+
+int
+main (int argc, char **argv)
+{
+ EventView *ev;
+ GtkWidget *window, *hbox;
+
+ gtk_init (&argc, &argv);
+
+ hbox = gtk_hbox_new (TRUE, 0);
+
+ ev = g_object_new (event_view_get_type (), NULL);
+ gtk_box_pack_start (GTK_BOX (hbox), &ev->widget, TRUE, TRUE, 0);
+ gtk_widget_show (&ev->widget);
+
+ events_read (ev, argv[1]);
+ g_timeout_add (750, (GSourceFunc) timeout_advance, ev);
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_widget_set_size_request (window, 800, 800);
+ g_signal_connect (window, "delete-event",
+ G_CALLBACK (gtk_main_quit), NULL);
+ gtk_container_add (GTK_CONTAINER (window), hbox);
+ gtk_widget_show (hbox);
+ gtk_widget_show (window);
+
+ gtk_main ();
+ return 0;
+}