


*****Listing 1*****

/*
 * this is a very simple drawing program that 
 * illustrates how to build a DCUWCU application
 *
 * Copyright 1989 Mark A. Johnson 
 */

#include <stdio.h>
#include <graphics.h>

#define M_POINTER	0	/* mouse shapes */
#define M_CROSS		1

#define ON		1
#define OFF		0

#define MAX_OBJECT	100
#define ESC	27

#define BOX	'b'	/* object types we support */
#define ELLIPSE	'e'
#define LINE	'l'
#define TEXT	't'

#define M_MAIN	1	/* handles for the menus */
#define M_FILE	2
#define M_OBJ	3
#define M_ACT	4

#define A_COPY	1	/* action requests for button() */
#define A_MOVE	2
#define A_EDIT	3

#define min(a,b) ((a) < (b) ? (a) : (b))
#define max(a,b) ((a) > (b) ? (a) : (b))

typedef struct { int type, l, t, r, b; char select, *data; } Object;

Object objects[MAX_OBJECT];	/* the table of objects defined so far */
int last_object;		/* the end of the object table	*/

int map[] = { 			/* maps a M_OBJ menu item to an object */
	0, BOX, ELLIPSE, LINE, TEXT };

char *about = 			/* form used on the M_MAIN About item */
	"  Draw This!|     by|Mark A. Johnson|  %{continue}";

char *help = 			/* help message for wrong keyboard input */
 "quit refresh : box line ellipse text : delete copy move edit";

char filename[20];		/* save the filename we're working with */
char text[100];			/* extra buffer for text i/o */

int actn_obj = 0;		/* flag for button(), some action req */
int make_obj = 0;		/* flag for button(), need to create */
int slct_cnt = 0;		/* count of selected objects */
int first;			/* helps make_object collect points */
int grid = 0;			/* grid displayed, snap coords */

extern int Maxx, Maxy, MaxColor;

/* start routine, called by the application driver, gets things going */

start(argc, argv) char **argv; {
	add_menu(M_MAIN, "Main:About|Quit|Refresh|Grid");
	add_menu(M_FILE, "File:Read|Write|Save|Print");
	add_menu(M_OBJ, "Objects:Box|Ellipse|Line|Text");
	add_menu(M_ACT, "Actions:Delete|Copy|Move|Edit");
	menu_state(M_ACT, 0);
	if (argc > 1) {
		strcpy(filename, argv[1]);
		read_objects();
	}
}

/* no timers in this application , but DCUWCU needs an entry anyway */

timer() {}

/* button routine called every time button 1 is depressed */

button(b, x, y) {
	if (make_obj) { /* need points to make an object */
		make_object(x, y);
	}
	else if (actn_obj) { /* got a point for a copy or move */
		action_object(x, y);
	}
	else	{ /* do a selection */
		select_object(in_object(x, y));
	}
	check_menu();
}

/* menu routine called every time a menu item is selected */

menu(m, i) {
	char junk = 0, on;
	switch (m) {
	case M_MAIN: /* main menu */
		switch (i) {
			case 1: form(about, &junk); break;
			case 2: quit(); break;
			case 3: refresh(); break;
			case 4: do_grid(); break;
		}
		break;
	case M_FILE: /* file menu */
		if (i < 3 && !get_name())
			break;
		switch (i) {
			case 1: read_objects(); break;
			case 2: case 3: write_objects(); break;
			case 4: print(); break;
		}
		break;
	case M_OBJ: /* objects */
		start_make(map[i]);
		break;
	case M_ACT: /* actions */
		switch (i) {
			case 1: kill_object(); break;
			case 2: start_actn(A_COPY); break;
			case 3: start_actn(A_MOVE); break;
			case 4: start_edit(); break;
		}
		break;
	}
}

/* routine called everytime a key is struck */

keyboard(c) {
	switch (c) {
		case 'p': print(); break;
		case 'g': do_grid(); break;
		case 'r': refresh(); break;
		case 'q': quit(); break;
		case 'b': start_make(BOX); break;
		case 't': start_make(TEXT); break;
		case 'l': start_make(LINE); break;
		case 'm': start_actn(A_MOVE); break;
		case 'c': start_actn(A_COPY); break;
		case 'd': kill_object(); break;
		case 'e':
			if (slct_cnt)
				start_edit();
			else	start_make(ELLIPSE);
			break;
		default: msg(help);
	}
}

/* time to go, see if they really want to */

quit() {
	char yes = 0, no = 0;
	char *f_exit = "Are you sure?| %{yes} %{no}";
	if (form(f_exit, &yes, &no) && no == 0)
		finish();
}

/*
 * miscellaneous support routines
 */

/* reset the current grid size */

do_grid() {
	char gridval, ok = 0, nok = 0, x;
	switch (grid) {
		case 8: gridval = 1; break;
		case 16: gridval = 2; break;
		default: gridval = 0; break;
	}
	x = form("Change Grid Size|  %[none:8:16]| %{ok} %{cancel}",
		&gridval, &ok, &nok);
	if (x == 0 || nok) return;
	grid = gridval * 8;
	refresh(); 
}

/* print the current screen somewhere, Epson-compatible graphics mode */ 

print() {
	static char grhd[] = { ESC, 'L', 0, 0 }; /* 960 bit graphics */
	static char grlf[] = { ESC, 'J', 24, '\r' }; /* line feed */
	static char prbuf[960];
	int x, y, i, b, n, any, pixel, max;
	max = min(Maxx, 960);
	grhd[2] = max;
	grhd[3] = max >> 8;
	mouse_state(OFF);
	b = 0x80;
	any = 0;
	for (y = 0; y < Maxy; y++) {
		for (x = 0; x < max; x++) {
			if (getpixel(x, y)) {
				any = 1;
				prbuf[x] |= b;
			}
		}
		b >>= 1;
		if (b == 0) { /* out it goes */
			if (any) {
				prn(grhd, 4);
				prn(prbuf, max);
			}
			prn(grlf, 4);
			b = 0x80;
			any = 0;
			for (x = 0; x < max; x++)
				prbuf[x] = 0;
		}
	}
	mouse_state(ON);
}

/* print the n bytes out the printer port */

prn(s, n) char *s; { while (n--) biosprint(0, *s++, 0); }

/* select or de-select an object */

select_object(obj) {
	int i;
	Object *o;
	if (obj == -1) { /* de-select all */
		for (i = 0; i < last_object; i++) {
			o = &objects[i];
			if (o->select) {
				o->select = 0;
				highlight(o, 0);
			}
		}
		slct_cnt = 0;
	}
	else	{
		o = &objects[obj];
		o->select = !o->select;
		highlight(o, o->select);
		slct_cnt += o->select ? 1 : -1;
	}
}

/* get a filename from the user, return 0 if abort */

get_name() {
	return form("Path: %20s", filename);
}

/* based on current select state, set the top-most menu */

check_menu() {
	menu_state(M_ACT, slct_cnt > 0);
	menu_state(M_OBJ, slct_cnt <= 0);
}

/* start to make an object by collecting points */

start_make(type) {
	char *s;
	switch (make_obj = type) {
		case BOX: s = "box: top left corner..."; break;
		case ELLIPSE: s = "ellipse: top left corner..."; break;
		case LINE: s = "line: one end..."; break;
		case TEXT: s = "text: starting..."; break;
	}
	msg(s);
	mouse_shape(M_CROSS);
	first = 1;
}

/* if enough points have been collected, make the object */

make_object(x, y) {
	static int fx, fy;

	if (grid) snap(&x, &y);

	switch (make_obj) {
	case TEXT:
		*text = 0;
		form("text: %20s", text);
		add_object(TEXT, x, y, x + strlen(text)*8, y+8, text);
		make_obj = 0;
		mouse_shape(M_POINTER);
		msg("");
		break;
	default:
		if (first) {
			fx = x;
			fy = y;
			first = 0;
			line(x-3, y, x+3, y);
			line(x, y-3, x, y+3);
			if (make_obj == LINE)
				msg("other end...");
			else	msg("bottom right corner...");
		}
		else	{
			add_object(make_obj, fx, fy, x, y, 0L);
			msg("");
			make_obj = 0;
			mouse_shape(M_POINTER);
		}
	}
}

/* snap the coordinates to the nearest grid point */

snap(xp, yp) int *xp, *yp; {
	int g2 = grid/2, g4 = grid/4, x = *xp, y = *yp;
	x = ((x + g2) / grid) * grid;
	y = ((y + g4) / g2) * g2;
	msg("x %d->%d  y %d->%d", *xp, x, *yp, y);
	*xp = x;
	*yp = y;
}

/* move, copy, or edit a figure */

action_object(x, y) {
	int i, dx, dy;
	Object *o;

	if (grid) snap(&x, &y);

	/* find reference point and compute distance moved */
	dx = dy = (actn_obj == A_EDIT ? 0 : 10000);
	for (i = 0; i < last_object; i++) {
		o = &objects[i];
		if (o->select) {
			if (actn_obj == A_EDIT) {
				dx = max(o->r, dx);
				dy = max(o->b, dy);
			}
			else	{
				dx = min(o->l, dx);
				dy = min(o->t, dy);
			}
		}
	}
	dx = x - dx;
	dy = y - dy;

	/* do it to all selected items, de-selecting as you go */
	for (i = 0; i < last_object; i++) {
		o = &objects[i];
		if (o->select) {
			o->select = 0;
			highlight(o, 0);
			switch (actn_obj) {
			case A_COPY:
				highlight(o, 0);
				add_object(o->type,
					o->l + dx, o->t + dy,
					o->r + dx, o->b + dy,
					o->data);
				break;
			case A_MOVE:
				draw_object(o, 0);
				o->l += dx;
				o->t += dy;
				o->r += dx;
				o->b += dy;
				draw_object(o, 1);
				break;
			case A_EDIT:
				draw_object(o, 0);
				set_coords(o, 
					o->l, o->t, 
					o->r + dx, o->b + dy);
				draw_object(o, 1);
				break;
			}
		}
	}

	/* deselect all and reset the mouse */
	actn_obj = 0;
	slct_cnt = 0;
	mouse_shape(M_POINTER);
	msg("");
	check_menu();
}

/* read objects from a file */

read_objects() {
	int type, t, l, r, b;
	FILE *f = fopen(filename, "r");
	if (f != NULL) {
		last_object = 0;
		while (fgets(text, 100, f)) {
			sscanf(text, "%c %d %d %d %d '%[^']\n", 
				&type, &l, &t, &r, &b, text);
			add_object(type, l, t, r, b, text);
		}
		fclose(f);
		msg("%d objects loaded", last_object);
	}
	else	msg("can't open '%s'", filename);
}

/* write objects to a file */

write_objects() {
	int i;
	Object *o;
	FILE *f;
	if (*filename == 0 && !get_name())
		return;
	if ((f = fopen(filename, "w")) != NULL) {
		for (i = 0; i < last_object; i++) {
			o = &objects[i];
			fprintf(f, "%c %d %d %d %d '%s'\n",
				o->type, o->l, o->t, o->r, o->b, 
				o->type == TEXT ? o->data : "");
		}
		fclose(f);
	}
	else	msg("can't write '%s'", filename);
}

/* save the given string in malloc'ed memory */

char *
strsave(s) char *s; {
	char *malloc();
	char *r = malloc(strlen(s)+1);
	if (r) strcpy(r, s);
	else msg("out of memory!!!");
	return r;
}

/* re-display all the objects on the screen */

refresh() {
	int i, x, y, gy;
	Object *o;
	clearviewport();
	setcolor(MaxColor);
	if (grid) {
		gy = grid/2;
		for (x = grid; x < Maxx; x += grid)
		for (y = gy; y < Maxy; y += gy)
			putpixel(x, y, 1);
	}
	for (i = 0; i < last_object; i++) {
		o = &objects[i];
		draw_object(o, 1);
		if (o->select) highlight(o, 1);
	}
}

/* (de)highlight the current selected item */

highlight(o, color) Object *o; {
	setcolor(color);
	rectangle(o->l-2, o->t-2, o->l+2, o->t+2);
	rectangle(o->r-2, o->b-2, o->r+2, o->b+2);
}

/* give the user some feedback */

msg(fmt, a, b, c, d) char *fmt; {
	static int lastback = 0;
	setfillstyle(EMPTY_FILL, 0);
	bar(0, 0, lastback, 8);
	sprintf(text, fmt, a, b, c, d);
	setcolor(MaxColor);
	outtextxy(0, 0, text);
	lastback = strlen(text) * 8;
}

/* 
 * object handling 
 */

/* see if x,y are in an object, begin looking at start + 1 */

in_object(x, y) {
	static int last = 0;
	int l, r, t, b;
	Object *o;
	int i = last+1, n = last_object;
	while (n--) {
		if (i >= last_object) i = 0;
		o = &objects[i];
		l = min(o->l, o->r);
		r = max(o->l, o->r);
		t = min(o->t, o->b);
		b = max(o->t, o->b);
		if (x >= l && x <= r && y >= t && y <= b)
			return (last = i);
		i++;
	}
	return (last = -1);
}

/* add an object to the object table */

add_object(type, l, t, r, b, data) char *data; {
	Object *o = &objects[last_object++];
	char *s;
	o->type = type;
	set_coords(o, l, t, r, b);
	o->select = 0;
	if (type == TEXT)
		o->data = strsave(data);
	draw_object(o, 1);
}

/* set the coordinates properly */

set_coords(o, l, t, r, b) Object *o; {
	if (o->type == LINE) { /* no fixup on these */
		o->l = l; 
		o->t = t; 
		o->r = r; 
		o->b = b;
	}
	else	{
		o->l = min(l, r);
		o->t = min(t, b);
		o->r = max(l, r);
		o->b = max(t, b);
	}
}

/* draw an object on the screen */

draw_object(o, color) Object *o; {
	int x, y, xrad, yrad;
	setcolor(color);
	switch (o->type) {
	case TEXT:
		x = strlen(o->data) * 8;
		setfillstyle(EMPTY_FILL, 0);
		bar(o->l, o->t, o->l + x, o->t + 8);
		outtextxy(o->l, o->t, o->data);
		break;
	case BOX:
		rectangle(o->l, o->t, o->r, o->b);
		break;
	case LINE:
		line(o->l, o->t, o->r, o->b);
		break;
	case ELLIPSE:
		x = o->l + (o->r - o->l)/2;
		y = o->t + (o->b - o->t)/2;
		xrad = o->r - x;
		yrad = o->b - y;
		ellipse(x, y, 0, 360, xrad, yrad);
		break;
	}
}

/* delete an object */

kill_object() {
	int i, j;
	Object *o;
	for (i = j = 0; i < last_object; i++) {
		o = &objects[i];
		if (o->select) {
			highlight(o, 0);		
			draw_object(o, 0);
			o->select = 0;
		}
		else	{
			if (i > j)
				objects[j++] = objects[i];
			else	j++;
		}
	}
	last_object = j;
	slct_cnt = 0;
	check_menu();
}

/* start an edit on the selected objects */

start_edit() {
	int i;
	Object *o;
	/* edit the text objects now */
	for (i = 0; i < last_object; i++) {
		o =  &objects[i];
		if (o->type == TEXT && o->select) {
			o->select = 0;
			highlight(o, 0);
			draw_object(o, 0);
			strcpy(text, o->data);
			if (form("edit: %20s", text)) {
				free(o->data);
				o->data = strsave(text);
				o->r = o->l + strlen(text)*8;
			}
			draw_object(o, 1);
			slct_cnt--;
		}
	}
	if (slct_cnt > 0) { /* must be other stuff */
		start_actn(A_EDIT);
	}
	check_menu();
}

/* initiate an action on selected objects */

start_actn(actn) {
	switch (actn) {
	case A_COPY: 
		msg("copy to..."); 
		break;
	case A_MOVE: 
		msg("move to..."); 
		break;
	case A_EDIT:
		msg("editing...");
		break;
	}
	actn_obj = actn;
	mouse_shape(M_CROSS);
}
