#!/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)

