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 4: 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(tape) {
   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, 0-->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-->1, 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-->1, 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);
}