#!/usr/local/bin/cz -- # graph editor, by Sam Watkins # include files ---------------------------------------------- use b #export gr #use m io error alloc #export gr util #use io m error cstr alloc #use stdlib.h unistd.h # separate files were inlined here, to avoid the broken bk build system! # vector ----------------------------------------------------- # The first field in a vector element must be an int which is # normally non-negative. This field is used for the free list # in non-allocated elements. struct Vector int n, space, first_free size_t element_size void *elements struct VectorIterator int count size_t element_size void *element enum {not_free = -1} vector_init(Vector *v, size_t element_size) v->n = 0 v->space = 0 v->first_free = not_free v->element_size = element_size v->elements = NULL static boolean is_free(void *e) return *(int *)e < 0 static int next_free(void *e) return -2-*(int *)e static void set_next_free(void *e, int i) *(int *)e = -2-i void *vector_alloc(Vector *v) int i void *e if v->first_free == not_free i = v->n if i == v->space v->space = v->space * 2 + 8 Realloc(v->elements, v->element_size * v->space) e = vector_ref(v, i) else i = v->first_free e = vector_ref(v, i) v->first_free = next_free(e) ++v->n return e vector_dealloc(Vector *v, void *e) int i = vector_index(v, e) set_next_free(e, v->first_free) v->first_free = i --v->n void *vector_ref(Vector *v, int i) return (char *)v->elements + i * v->element_size int vector_index(Vector *v, void *e) return ((char *)e - (char *)v->elements) / v->element_size vector_iterator_init(Vector *v, VectorIterator *i) i->count = v->n i->element_size = v->element_size i->element = v->elements void *vector_iterator_next(VectorIterator *i) void *e if i->count-- == 0 return NULL while is_free(i->element) i->element = ((char *)i->element) + i->element_size e = i->element i->element = ((char *)i->element) + i->element_size return e use stddef.h use error types alloc # shape ------------------------------------------------------ struct Point double x, y struct Shape int n_points Point *points struct Transform double xx, xy, yx, yy, dx, dy Transform id_trans = { 1.0, 0.0, 0.0, 1.0, 0.0, 0.0 } int temporary_shape_points = 0 Shape temporary_shape = { 0, NULL } Shape *get_temporary_shape(int n_points) if n_points > temporary_shape_points temporary_shape_points *= 2 if temporary_shape_points < n_points temporary_shape_points = n_points Realloc(temporary_shape.points, sizeof(Point) * temporary_shape_points) temporary_shape.n_points = n_points return &temporary_shape int temporary_xpoints_points = 0 XPoint *temporary_xpoints = NULL XPoint *get_temporary_xpoints(int n_points) if n_points > temporary_xpoints_points temporary_xpoints_points *= 2 if temporary_xpoints_points < n_points temporary_xpoints_points = n_points Realloc(temporary_xpoints, sizeof(XPoint) * temporary_xpoints_points) return temporary_xpoints transform_point(Point *p0, Point *p1, Transform *trans) double p0x = p0->x, p0y = p0->y p1->x = p0x * trans->xx + p0y * trans->yx + trans->dx p1->y = p0x * trans->xy + p0y * trans->yy + trans->dy Shape *transform_shape(Shape *src, Shape *dest, Transform *trans) int n = src->n_points Point *p0 = src->points, *p1 if dest == NULL dest = get_temporary_shape(n) p1 = dest->points for ; n>0; --n, ++p0, ++p1 transform_point(p0, p1, trans) return dest round_point(Point *p0, XPoint *p1) p1->x = (int)(p0->x+0.5) p1->y = (int)(p0->y+0.5) XPoint *round_shape(Shape *src, XPoint *dest) int n = src->n_points Point *p0 = src->points XPoint *p1 if dest == NULL dest = get_temporary_xpoints(n+1) p1 = dest for ; n>0; --n, ++p0, ++p1 round_point(p0, p1) *p1 = *dest return dest scale_transform(Transform *src, Transform *dest, double mag) dest->xx = src->xx * mag dest->xy = src->xy * mag dest->yx = src->yx * mag dest->yy = src->yy * mag dest->dx = src->dx * mag dest->dy = src->dy * mag translate_transform(Transform *src, Transform *dest, double dx, double dy) dest->xx = src->xx dest->xy = src->xy dest->yx = src->yx dest->yy = src->yy dest->dx = src->dx + dx dest->dy = src->dy + dy make_rotate_transform(Transform *dest, double angle) double s = sin(angle), c = cos(angle) dest->xx = c dest->xy = s dest->yx = -s dest->yy = c dest->dx = 0 dest->dy = 0 make_transform(Transform *dest, double angle, double scale, double x, double y, boolean flip) make_rotate_transform(dest, angle) scale_transform(dest, dest, scale) translate_transform(dest, dest, x, y) if flip flip_transform(dest) make_inverse_transform(Transform *dest, double angle, double scale, double x, double y, boolean flip) Transform tmp tmp = id_trans if flip flip_transform(&tmp) translate_transform(&tmp, dest, -x, -y) scale_transform(dest, dest, 1.0/scale) make_rotate_transform(&tmp, -angle) compose_transform(&tmp, dest, dest) flip_transform(Transform *toflip) toflip->xy = -toflip->xy toflip->yy = -toflip->yy toflip->dy = -toflip->dy compose_transform(Transform *src0, Transform *src1, Transform *dest) double xx0 = src0->xx, xx1 = src1->xx, xy0 = src0->xy, xy1 = src1->xy, yx0 = src0->yx, yx1 = src1->yx, yy0 = src0->yy, yy1 = src1->yy, dx0 = src0->dx, dx1 = src1->dx, dy0 = src0->dy, dy1 = src1->dy dest->xx = xx0 * xx1 + yx0 * xy1 dest->xy = xy0 * xx1 + yy0 * xy1 dest->yx = xx0 * yx1 + yx0 * yy1 dest->yy = xy0 * yx1 + yy0 * yy1 dest->dx = xx0 * dx1 + yx0 * dy1 + dx0 dest->dy = xy0 * dx1 + yy0 * dy1 + dy0 print_transform(Transform *t) Sayf("%7.2f %7.2f", t->xx, t->xy) Sayf("%7.2f %7.2f", t->yx, t->yy) Sayf("%7.2f %7.2f", t->dx, t->dy) # structure -------------------------------------------------- struct Node int index # negative means free, and indicates next free char *name Shape *shape # NULL means a unit circle double angle, scale Point o Transform transform Region region XPoint so # note: the index field is redundant, could be calculated: # index = node - nodes struct Arc int from # negative means free, and indicates next free int to char *name # nodes and arcs --------------------------------------------- static Vector struct_nodes Vector *nodes = &struct_nodes static Vector struct_arcs Vector *arcs = &struct_arcs structure_init() vector_init(nodes, sizeof(Node)) vector_init(arcs, sizeof(Arc)) Node *new_node() Node *node = vector_alloc(nodes) node->index = vector_index(nodes, node) return node Arc *new_arc(int from, int to) Arc *arc = vector_alloc(arcs) arc->from = from; arc->to = to return arc delete_node(Node *node) draw_node(node, False) for_each_arc_to_or_from(node, delete_arc) if node->region != NULL XDestroyRegion(node->region) # XXX free(node->name) ? use atoms? vector_dealloc(nodes, node) delete_arc(Arc *arc) draw_arc(arc) # XXX free(arc->name) ? use atoms? vector_dealloc(arcs, arc) for_each_arc_to_or_from(Node *node, void (*f)(Arc *)) int index = node->index VectorIterator i Arc *arc vector_iterator_init(arcs, &i) while (arc = vector_iterator_next(&i)) != NULL if arc->to == index || arc->from == index f(arc) # control ---------------------------------------------------- typedef void (*EventHandler)(int x, int y) typedef EventHandler ButtonController[3] struct Controller ButtonController *button[3] void (*keyboard)(int keycode, int state) Node *node double drag_x, drag_y, drag_scale double drag_angle, old_angle, old_scale static void start_move_node(int x, int y) # delete ----------------------------------------------------- delete_press(int x, int y) node = lookup_node(x, y) if node != NULL delete_node(node) delete_motion(int x, int y) node = lookup_node(x, y) if node != NULL delete_node(node) # add node ------------------------------------------------ add_node_press(int x, int y) node = add_node(x, y) start_move_node(x, y) # add arc ---------------------------------------------------- add_arc_press(int x, int y) node = lookup_node(x, y) if node != NULL drag_x = x drag_y = y draw_rubber_band(node->so.x, node->so.y, x, y) add_arc_motion(int x, int y) if node != NULL draw_rubber_band(node->so.x, node->so.y, drag_x, drag_y) draw_rubber_band(node->so.x, node->so.y, x, y) drag_x = x; drag_y = y add_arc_release(int x, int y) if node != NULL Node *node1 = lookup_node(x, y) draw_rubber_band(node->so.x, node->so.y, drag_x, drag_y) if node1 != NULL add_arc(node, node1) # move ------------------------------------------------------- static void start_move_node(int x, int y) Point p p.x = x; p.y = y transform_point(&p, &p, &inverse_viewpoint_transform) drag_x = node->o.x - p.x drag_y = node->o.y - p.y move_press(int x, int y) if (node = lookup_node(x, y)) != NULL start_move_node(x, y) else drag_x = viewpoint_x - x drag_y = viewpoint_y + y move_motion(int x, int y) if node != NULL Point p p.x = x; p.y = y transform_point(&p, &p, &inverse_viewpoint_transform) draw_node_and_arcs(node, False) node->o.x = p.x + drag_x node->o.y = p.y + drag_y calculate_node_transform(node) draw_node_and_arcs(node, True) else # redraw(False, False) viewpoint_x = drag_x + x viewpoint_y = drag_y - y calculate_viewpoint_transform() # redraw(True, False) redraw(True, True) # rotate ----------------------------------------------------- rotate_press(int x, int y) if (node = lookup_node(x, y)) != NULL drag_angle = node->angle - -atan2(y-node->so.y, x-node->so.x) else old_angle = viewpoint_angle drag_angle = -atan2(y-(double)(window_height/2), x-(double)(window_width/2)) drag_x = viewpoint_x drag_y = viewpoint_y rotate_motion(int x, int y) double angle if node != NULL draw_node(node, False) angle = -atan2(y-node->so.y, x-node->so.x) node->angle = angle + drag_angle calculate_node_transform(node) draw_node(node, True) else double sina, cosa # redraw(False, False) angle = -atan2(y-(double)(window_height/2), x-(double)(window_width/2)) - drag_angle viewpoint_angle = old_angle + angle sina = sin(angle); cosa = cos(angle) viewpoint_x = drag_x * cosa - drag_y * sina viewpoint_y = drag_x * sina + drag_y * cosa calculate_viewpoint_transform() # redraw(True, False) redraw(True, True) # scale ------------------------------------------------------ scale_press(int x, int y) if (node = lookup_node(x, y)) != NULL double dx = x-node->so.x, dy = y-node->so.y, d = sqrt(dx*dx + dy*dy) drag_scale = node->scale / (d>0 ? d : 1.0) else double dx = x-(double)(window_width/2), dy = y-(double)(window_height/2) old_scale = viewpoint_scale drag_scale = sqrt(dx*dx + dy*dy) if drag_scale == 0.0 drag_scale = 1.0 drag_x = viewpoint_x drag_y = viewpoint_y scale_motion(int x, int y) if node != NULL double dx = x-node->so.x, dy = y-node->so.y, factor = sqrt(dx*dx + dy*dy) if factor == 0.0 factor = 1.0 draw_node(node, False) node->scale = drag_scale * factor calculate_node_transform(node) draw_node(node, True) else double dx = x-(double)(window_width/2), dy = y-(double)(window_height/2), factor = sqrt(dx*dx + dy*dy) if factor == 0.0 factor = 1.0 factor /= drag_scale viewpoint_scale = old_scale * factor viewpoint_x = drag_x * factor viewpoint_y = drag_y * factor calculate_viewpoint_transform() redraw(True, True) # graph ------------------------------------------------------ # test shapes and diagram ------------------------------------ Point triang_points[] = {-2.0, 0.0}, {1.0, 1.0}, {1.0,-1.0} Shape triang = 3, triang_points Point rectangle_points[] = {-1.0, -0.5}, {1.0, -0.5}, {1.0, 0.5}, {-1.0, 0.5} Shape rectangle = 4, rectangle_points Point add_points[] = {-1.0, 0}, {-0.75, -0.25}, {-0.5, -0.3}, {0, -0.35}, {0.5, -0.3}, {0.75, -0.25}, {1, 0}, {0.75, 0.25}, {0.5, 0.3}, {0, 0.35}, {-0.5, 0.3}, {-0.75, 0.25} Shape add = 12, add_points Node _nodes[] = { .index=0, .name="hello", .shape=&triang, .angle=M_PI/4.0, .scale=1, .o={1.0, 0.5} }, { .index=1, .name="world", .shape=&rectangle, .angle=-M_PI/6.0, .scale=1.5, .o={-0.5, -0.8} }, { .index=2, .name="addy", .shape=&add, .angle=0.0, .scale=2, .o={-2, 2} }, { .index=3, .name="circle", .shape=NULL, .angle=0.0, .scale=1, .o={-2, -2} } int _n_nodes = array_size(_nodes) test_diagram() int i, j Node *node for i=0; i<_n_nodes; ++i node = new_node() j = node->index *node = _nodes[i] node->index = j int add_node_i = 0 Node *add_node(int x, int y) Node *node = new_node() int j Point p p.x = x; p.y = y transform_point(&p, &p, &inverse_viewpoint_transform) j = node->index *node = _nodes[add_node_i] node->index = j node->o.x = p.x node->o.y = p.y calculate_node_transform(node) node->region = NULL draw_node(node, True) add_node_i = (add_node_i+1) % _n_nodes return node Arc *add_arc(Node *n0, Node *n1) Arc *arc arc = new_arc(n0->index, n1->index) draw_arc(arc) return arc # viewpoint -------------------------------------------------- unsigned int window_width = 800, window_height = 600 double viewpoint_angle = 0.0 double viewpoint_scale = 30.0 double viewpoint_x = 0.0 double viewpoint_y = 0.0 double min_node_scale_for_point = 0.25 double min_node_scale_for_outline = 1.0 double min_node_scale_for_label = 15.0 double max_node_scale_to_manipulate = 200.0 Transform viewpoint_transform, inverse_viewpoint_transform calculate_viewpoint_transform() int dx = (int)(window_width/2) + viewpoint_x int dy = -(int)(window_height/2) + viewpoint_y make_transform(&viewpoint_transform, viewpoint_angle, viewpoint_scale, dx, dy, True) make_inverse_transform(&inverse_viewpoint_transform, viewpoint_angle, viewpoint_scale, dx, dy, True) # ------------------------------------------------------------ calculate_node_transform(Node *node) make_transform(&node->transform, node->angle, node->scale, node->o.x, node->o.y, False) Node *lookup_node(int x, int y) VectorIterator i Node *node, *best_node = NULL double scale, best_scale = max_node_scale_to_manipulate/viewpoint_scale boolean match vector_iterator_init(nodes, &i) while (node = vector_iterator_next(&i)) != NULL scale = node->scale match = node->shape == NULL ? (x-node->so.x)*(x-node->so.x) + (y-node->so.y)*(y-node->so.y) <= (int)(scale*scale*viewpoint_scale*viewpoint_scale+0.5) : node->region != NULL && XPointInRegion(node->region, x, y) if match && best_scale > scale best_node = node best_scale = scale return best_node # global X data ---------------------------------------------- Display *display Window window Colormap colormap GC gc XFontStruct *_font const char *font_name = "-adobe-helvetica-medium-r-normal--11-80-100-100-p-56-iso8859-1" # interface state -------------------------------------------- typedef void (*Thunk)(void) XEvent event int first_keycode, last_keycode Thunk *key_handlers_ # the main program ------------------------------------------- Main() Window root # XColor color XGCValues values int screen_number long black, white VectorIterator i Node *node structure_init() test_diagram() vector_iterator_init(nodes, &i) while (node = vector_iterator_next(&i)) != NULL calculate_node_transform(node) node->region = NULL calculate_viewpoint_transform() if (display = XOpenDisplay(NULL)) == NULL error("cannot open display") XDisplayKeycodes(display, &first_keycode, &last_keycode) key_handlers_ = Calloc(last_keycode-first_keycode+1, sizeof(Thunk)) set_key_handler(XStringToKeysym("Q"), quit_) set_key_handler(XStringToKeysym("M"), motion_mode) set_key_handler(XStringToKeysym("S"), structure_mode) screen_number = DefaultScreen(display) white = WhitePixel(display, screen_number) black = BlackPixel(display, screen_number) colormap = DefaultColormap(display, screen_number) if XAllocNamedColor(display, colormap, "red", &color, &color) red = color.pixel else red = white if (_font = XLoadQueryFont(display, font_name)) == NULL error("cannot load font") gc = DefaultGC(display, screen_number) values.function = GXxor values.foreground = white^black values.cap_style = CapNotLast values.line_width = 0 values.font = _font->fid XChangeGC(display, gc, GCFunction|GCForeground|GCCapStyle|GCLineWidth|GCFont, &values) root = DefaultRootWindow(display) window = XCreateSimpleWindow(display, root, 0, 0, window_width, window_height, 0, white, black) XSelectInput(display, window, ExposureMask|ButtonPressMask|ButtonReleaseMask|ButtonMotionMask|KeyPressMask|StructureNotifyMask) XMapWindow(display, window) motion_mode() event_loop() # this is unreachable Exit(1) # finalisation ----------------------------------------------- quit_() Free(key_handlers_) XUnmapWindow(display, window) # XFree(keyboard_map) XFreeColors(display, colormap, (unsigned long *)&red, 1, 0) XFreeFont(display, _font) XDestroyWindow(display, window) XCloseDisplay(display) exit(0) # key handlers ----------------------------------------------- set_key_handler(KeySym keysym, void (*handler)()) int keycode = XKeysymToKeycode(display, keysym) key_handlers_[keycode - first_keycode] = handler command_keyboard(int keycode, int state) int shift = state & ShiftMask ? 1 : 0 Thunk handler = key_handlers_[keycode-first_keycode] if handler != NULL (*handler)() else warn("unhandled keypress: %s", XKeysymToString(XKeycodeToKeysym(display, keycode, shift))) # modes ------------------------------------------------------ ignore(int x, int y) use(x) ; use(y) Controller *the_controller ButtonController ignore_button = { ignore, ignore, ignore }, move_button = { move_press, move_motion, ignore }, rotate_button = { rotate_press, rotate_motion, ignore }, scale_button = { scale_press, scale_motion, ignore }, add_node_button = { add_node_press, move_motion, ignore }, add_arc_button = { add_arc_press, add_arc_motion, add_arc_release }, delete_button = { delete_press, delete_motion, ignore } Controller motion_controller = { {&move_button, &rotate_button, &scale_button}, command_keyboard }, structure_controller = { {&add_node_button, &add_arc_button, &delete_button}, command_keyboard } motion_mode() Say("motion mode") the_controller = &motion_controller structure_mode() Say("structure mode") the_controller = &structure_controller # event dispatch loop ---------------------------------------- # TODO select only events relevant to current mode # (e.g. not motion unless needed) button_event(int button, int type, int x, int y) if button >= 1 && button <= 3 ButtonController *bc = the_controller->button[button-1] ((*bc)[type])(x, y) else warn("unknown button %d", button) event_loop() uint button = 0 while 1 XNextEvent(display, &event) which event.type Expose . Sayf("expose event - count: %d", event.xexpose.count) if event.xexpose.count == 0 redraw(True, True) ConfigureNotify . configure_notify() ButtonPress . if button != 0 warn("press %d then press %d - latter ignored\n", button, event.xbutton.button) else button = event.xbutton.button button_event(button, 0, event.xbutton.x, event.xbutton.y) MotionNotify . # We skip all but the most recent motion event. # This might be a bit dodgy, we could skip past a # release/press pair... while XCheckTypedEvent(display, MotionNotify, &event) button_event(button, 1, event.xmotion.x, event.xmotion.y) ButtonRelease . if button == 0 warn("no press then release b%d - release ignored", button, event.xbutton.button) else if event.xbutton.button != button warn("press b%d then release b%d - release ignored", button, event.xbutton.button) else button_event(button, 2, event.xbutton.x, event.xbutton.y) button = 0 KeyPress . (*the_controller->keyboard)(event.xkey.keycode, event.xkey.state) MapNotify . UnmapNotify . ReparentNotify . else . warn("unhandled event, type: 0x%04x\n", event.type) # this is unreachable # redrawing -------------------------------------------------- redraw(boolean moved, boolean clear) VectorIterator i Node *node; Arc *arc # Say("redrawing") if clear XClearWindow(display, window) vector_iterator_init(nodes, &i) while (node = vector_iterator_next(&i)) != NULL draw_node(node, moved) vector_iterator_init(arcs, &i) while (arc = vector_iterator_next(&i)) != NULL draw_arc(arc) draw_node(Node *node, boolean moved) XPoint *points Shape *shape Transform trans boolean is_polygon = node->shape != NULL double node_scale = node->scale * viewpoint_scale if moved Point p transform_point(&node->o, &p, &viewpoint_transform) round_point(&p, &node->so) if node_scale < min_node_scale_for_outline if is_polygon if node->region != NULL XDestroyRegion(node->region) node->region = NULL if node_scale >= min_node_scale_for_point XDrawPoint(display, window, gc, node->so.x, node->so.y) else # draw the node outline if is_polygon compose_transform(&viewpoint_transform, &node->transform, &trans) shape = transform_shape(node->shape, NULL, &trans) points = round_shape(shape, NULL) if moved if node->region != NULL XDestroyRegion(node->region) node->region = XPolygonRegion(points, shape->n_points+1, WindingRule) XDrawLines(display, window, gc, points, shape->n_points+1, CoordModeOrigin) else # circle unsigned int r = (int)(node->scale * viewpoint_scale + 0.5) XDrawArc(display, window, gc, node->so.x-r, node->so.y-r, r*2, r*2, 0, 64*360) if node_scale >= min_node_scale_for_label int width, dy char *str = node->name int len = strlen(node->name) width = XTextWidth(_font, str, len) dy = (_font->ascent - _font->descent) / 2 XDrawString(display, window, gc, node->so.x-width/2, node->so.y+dy, str, len) draw_arc(Arc *arc) Node *n0 = vector_ref(nodes, arc->from), *n1 = vector_ref(nodes, arc->to) XDrawLine(display, window, gc, n0->so.x, n0->so.y, n1->so.x, n1->so.y) draw_node_and_arcs(Node *node, boolean moved) draw_node(node, moved) for_each_arc_to_or_from(node, draw_arc) draw_rubber_band(int x0, int y0, int x1, int y1) XDrawLine(display, window, gc, x0, y0, x1, y1) # recentering when the window is resized --------------------- configure_notify() int _x, _y Window _root unsigned int _border, _depth, new_width, new_height Say("configure notify") XGetGeometry(display, window, &_root, &_x, &_y, &new_width, &new_height, &_border, &_depth) if new_width != window_width || new_height != window_height window_width = new_width; window_height = new_height calculate_viewpoint_transform() redraw(True, True)