NNNNNN|p NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNONNNNNNNNNNNNNNNNNNNNNN7O`  @! #+/1 35`79;=?A CoIKMOQS@Y[]_a c@e`gikoq s@u`wy{} @` @ ` @ ` @ @ ǀ ɠ @ ` ׀ ٠   @`!Aa !Aa!!#Ao'-/1!3A5a79;=?A!CAEaGIKMQ!SAoWYO`  @! #+/1 35`79;=?A CoIKMOQS@Y[]_a c@e`gikoq s@u`wy{} @` @ ` @ ` @ @ ǀ ɠ @ ` ׀ ٠   @`!Aa !Aa!!#Ao'-/1!3A5a79;=?A!CAEaGIKMQ!SAoWYST_78.P6 SOURCE_CODE%t TEXT `t WBLOCK TXTt YREAD ME t KAUTO t READ 1STt Z7EXTRAS t DESKTOP INF&t [. % t.. % tGEMCL11 C &t GEMCL13 C *t 8GEMCL15 C .t  GEMCL16 C 1t $GEMCL16 DFN5t $GEMCL16 H 7t %GEMCL16 RSC9t &GEMCL16 RSH<t 'RSCVFILEC @t ) RSCVLIB C Dt -RSCVMAINC Gt 4=RSCONV DFNLt D`RSCONV H Nt E RSCONV PRGQt G)RSCONV RSCUt R` RSCVLINKSH Yt U*SYMLINK SH \t V>>>>>>>>>>>>>>> Sample code for initializing User Objects <<<<<<<<<<<<<<<< GLOBAL USERBLK extobjs[MAX_OBJS]; /* APPLBLK defined in OBDEFS.H */ GLOBAL WORD n_extobjs; /* Set MAX_OBJS to total user */ /* objects in resource */ VOID obj_init() /* Scan whole resource for user */ { /* objects. Uses map_tree() */ LONG tree, obspec; /* from GEMCL5.C */ WORD itree, i, obj; n_extobjs = 0; /* Replace TREE0 with your first */ /* tree, TREEN with the last */ for (itree = TREE0; itree <= TREEN; itree++) { rsrc_gaddr(R_TREE, itree, &tree); map_tree(tree, ROOT, NIL, fix_obj); } } WORD fix_obj(tree, obj) /* Code to check and fix up */ LONG tree; /* a user defined object */ WORD obj; { WORD hibyte; hibyte = LWGET(OB_TYPE(obj)) & 0xff00; /* check extended */ if (!hibyte) /* type - if none */ return (TRUE); /* ignore object */ extobjs[n_extobjs].ub_code = dr_code; /* set drawcode */ extobjs[n_extobjs].ub_parm = LLGET(OB_SPEC(obj)); /* copy obspec */ LLSET(OB_SPEC(obj), ADDR(&extobjs[n_extobjs])); /* point obspec */ LWSET(OB_TYPE(obj), G_USERDEF | hibyte); /* to userblk & */ n_extobjs++; /* patch type */ return (TRUE); } >>>>>>>>>>>>>>>>>>>>>> Sample User Object Drawing Code <<<<<<<<<<<<<<<<<<<< >>>>>>>>>>>>>>>>>>>>>> Implements Rounded Box based <<<<<<<<<<<<<<<<<<<< >>>>>>>>>>>>>>>>>>>>>> on G_BOX type <<<<<<<<<<<<<<<<<<<< WORD dr_code(pb) /* Sample user object drawing */ PARMBLK *pb; /* code. Caution: NOT portable */ { /* to Intel small data models */ LONG tree, obspec; WORD slct, flip, type, ext_type, flags; WORD pxy[4]; WORD bgc, interior, style, bdc, width, chc; tree = pb->pb_tree; obspec = LLGET(pb->pb_parm); /* original obspec from USERBLK */ ext_type = LHIBT(LWGET(OB_TYPE(pb->pb_obj))); slct = SELECTED & pb->pb_currstate; flip = SELECTED & (pb->pb_currstate ^ pb->pb_prevstate); set_clip(TRUE, &pb->pb_xc); /* These two routines in GEMCL9.C */ grect_to_array(&pb->pb_x, pxy); switch (ext_type) { case 1: /* Rounded box */ /* Crack color word */ get_colrwd(obspec, &bgc, &style, &interior, &bdc, &width, &chc); /* For select effect, use char color */ if (slct) /* In place of background */ bgc = chc; /* Fill in background */ rr_fill(MD_REPLACE, (width? 0: 1), bgc, interior, style, pxy); /* Do perimeter if needed */ /* rr_perim is in GEMCL9.C */ if (width && !flip) { pxy[0] -= width; pxy[2] += width; rr_perim(MD_REPLACE, bdc, FIS_SOLID, width, pxy); } break; default: /* Add more types here */ break; } return (0); } VOID /* Cracks the obspec color word */ get_colrwd(obspec, bgc, style, interior, bdc, width, chc) LONG obspec; WORD *bgc, *style, *interior, *bdc, *width, *chc, *chmode; { WORD colorwd; colorwd = LLOWD(obspec); *bgc = colorwd & 0xf; *style = (colorwd & 0x70) >> 4; if ( !(*style) ) *interior = 0; else if (*style == 7) *interior = 1; else if (colorwd & 0x80) /* HACK: Uses character writing mode */ *interior = 3; /* bit to select alternate interior */ else /* styles! */ *interior = 2; *bdc = (colorwd & 0xf000) >> 12; *width = LHIWD(obspec) & 0xff; if (*width > 127) *width = 256 - *width; if (*width && !(*width & 0x1)) /* VDI only renders odd */ (*width)--; /* widths! */ *chc = (colorwd & 0x0f00) >> 8; /* used for select effect */ } VOID /* Fill a rounded rectangle */ rr_fill(mode, perim, color, interior, style, pxy) WORD mode, perim, color, style, interior, *pxy; { vswr_mode(vdi_handle, mode); vsf_color(vdi_handle, color); vsf_style(vdi_handle, style); vsf_interior(vdi_handle, interior); vsf_perimeter(vdi_handle, perim); v_rfbox(vdi_handle, pxy); } #include "portab.h" /* portable coding conv */ #include "machine.h" /* machine depndnt conv */ #include "obdefs.h" /* object definitions */ #include "gembind.h" /* gem binding structs */ #include "taddr.h" #define M1_ENTER 0x0000 #define M1_EXIT 0x0001 #define BS 0x0008 #define TAB 0x0009 #define CR 0x000D #define ESC 0x001B #define BTAB 0x0f00 #define UP 0x4800 #define DOWN 0x5000 #define DEL 0x5300 /* Global variables used by */ /* 'mapped' functions */ MLOCAL GRECT br_rect; /* Current break rectangle */ MLOCAL WORD br_mx, br_my, br_togl; /* Break mouse posn & flag */ MLOCAL WORD fn_obj; /* Found tabable object */ MLOCAL WORD fn_last; /* Object tabbing from */ MLOCAL WORD fn_prev; /* Last EDITABLE obj seen */ MLOCAL WORD fn_dir; /* 1 = TAB, 0 = BACKTAB */ /************* Utility routines for new forms manager ***************/ VOID objc_toggle(tree, obj) /* Reverse the SELECT state */ LONG tree; /* of an object, and redraw */ WORD obj; /* it immediately. */ { WORD state, newstate; GRECT root, ob_rect; objc_xywh(tree, ROOT, &root); state = LWGET(OB_STATE(obj)); newstate = state ^ SELECTED; objc_change(tree, obj, 0, root.g_x, root.g_y, root.g_w, root.g_h, newstate, 1); } VOID /* If the object is not already */ objc_sel(tree, obj) /* SELECTED, make it so. */ LONG tree; WORD obj; { if ( !(LWGET(OB_STATE(obj)) & SELECTED) ) objc_toggle(tree, obj); } VOID /* If the object is SELECTED, */ objc_dsel(tree, obj) /* deselect it. */ LONG tree; WORD obj; { if (LWGET(OB_STATE(obj)) & SELECTED) objc_toggle(tree, obj); } VOID /* Return the object's GRECT */ objc_xywh(tree, obj, p) /* through 'p' */ LONG tree; WORD obj; GRECT *p; { objc_offset(tree, obj, &p->g_x, &p->g_y); p->g_w = LWGET(OB_WIDTH(obj)); p->g_h = LWGET(OB_HEIGHT(obj)); } VOID /* Non-cursive traverse of an */ map_tree(tree, this, last, routine) /* object tree. This routine */ LONG tree; /* is described in PRO GEM #5. */ WORD this, last; WORD (*routine)(); { WORD tmp1; tmp1 = this; /* Initialize to impossible value: */ /* TAIL won't point to self! */ /* Look until final node, or off */ /* the end of tree */ while (this != last && this != NIL) /* Did we 'pop' into this node */ /* for the second time? */ if (LWGET(OB_TAIL(this)) != tmp1) { tmp1 = this; /* This is a new node */ this = NIL; /* Apply operation, testing */ /* for rejection of sub-tree */ if ((*routine)(tree, tmp1)) this = LWGET(OB_HEAD(tmp1)); /* Subtree path not taken, */ /* so traverse right */ if (this == NIL) this = LWGET(OB_NEXT(tmp1)); } else /* Revisiting parent: */ /* No operation, move right */ { tmp1 = this; this = LWGET(OB_NEXT(tmp1)); } } WORD /* Find the parent object of */ get_parent(tree, obj) /* by traversing right until */ LONG tree; /* we find nodes whose NEXT */ WORD obj; /* and TAIL links point to */ { /* each other. */ WORD pobj; if (obj == NIL) return (NIL); pobj = LWGET(OB_NEXT(obj)); if (pobj != NIL) { while( LWGET(OB_TAIL(pobj)) != obj ) { obj = pobj; pobj = LWGET(OB_NEXT(obj)); } } return(pobj); } WORD inside(x, y, pt) /* determine if x,y is in rectangle */ WORD x, y; GRECT *pt; { if ( (x >= pt->g_x) && (y >= pt->g_y) && (x < pt->g_x + pt->g_w) && (y < pt->g_y + pt->g_h) ) return(TRUE); else return(FALSE); } WORD rc_intersect(p1, p2) /* compute intersection of two GRECTs */ GRECT *p1, *p2; { WORD tx, ty, tw, th; tw = min(p2->g_x + p2->g_w, p1->g_x + p1->g_w); th = min(p2->g_y + p2->g_h, p1->g_y + p1->g_h); tx = max(p2->g_x, p1->g_x); ty = max(p2->g_y, p1->g_y); p2->g_x = tx; p2->g_y = ty; p2->g_w = tw - tx; p2->g_h = th - ty; return( (tw > tx) && (th > ty) ); } VOID rc_copy(psbox, pdbox) /* copy source to destination rectangle */ GRECT *psbox; GRECT *pdbox; { pdbox->g_x = psbox->g_x; pdbox->g_y = psbox->g_y; pdbox->g_w = psbox->g_w; pdbox->g_h = psbox->g_h; } /************* "Hot-spot" manager and subroutines ***************/ WORD break_x(pxy) WORD *pxy; { /* Breaking object is right of */ if (br_mx < pxy[0]) /* mouse. Reduce width of */ { /* bounding rectangle. */ br_rect.g_w = pxy[0] - br_rect.g_x; return (TRUE); } if (br_mx > pxy[2]) /* Object to left. Reduce width*/ { /* and move rect. to right */ br_rect.g_w += br_rect.g_x - pxy[2] - 1; br_rect.g_x = pxy[2] + 1; return (TRUE); } return (FALSE); /* Mouse within object segment. */ } /* Break attempt fails. */ WORD break_y(pxy) WORD *pxy; { if (br_my < pxy[1]) /* Object below mouse. Reduce */ { /* height of bounding rect. */ br_rect.g_h = pxy[1] - br_rect.g_y; return (TRUE); } if (br_my > pxy[3]) /* Object above mouse. Reduce */ { /* height and shift downward. */ br_rect.g_h += br_rect.g_y - pxy[3] - 1; br_rect.g_y = pxy[3] + 1; return (TRUE); } /* Emergency escape test! Protection vs. turkeys who nest */ /* non-selectable objects inside of selectables. */ if (br_mx >= pxy[0] && br_mx <= pxy[1]) { /* Will X break fail? */ br_rect.g_x = br_mx; /* If so, punt! */ br_rect.g_y = br_my; br_rect.g_w = br_rect.g_h = 1; return (TRUE); } return (FALSE); } WORD break_obj(tree, obj) /* Called once per object to */ LONG tree; /* check if the bounding rect. */ WORD obj; /* needs to be modified. */ { GRECT s; WORD flags, broken, pxy[4]; objc_xywh(tree, obj, &s); grect_to_array(&s, pxy); if (!rc_intersect(&br_rect, &s)) return (FALSE); /* Trivial rejection case */ flags = LWGET(OB_FLAGS(obj)); /* Is this object a potential */ if (flags & HIDETREE) /* hot-spot? */ return (FALSE); if ( !(flags & SELECTABLE) ) return (TRUE); if (LWGET(OB_STATE(obj)) & DISABLED) return (TRUE); for (broken = FALSE; !broken; ) /* This could take two passes */ { /* if the first break fails. */ if (br_togl) broken = break_x(pxy); else broken = break_y(pxy); br_togl = !br_togl; } return (TRUE); } WORD /* Manages mouse rectangle events */ form_hot(tree, hot_obj, mx, my, rect, mode) LONG tree; WORD hot_obj, mx, my, *mode; GRECT *rect; { GRECT root; WORD state; objc_xywh(tree, ROOT, &root); /* If there is already a hot-spot */ if (hot_obj != NIL) /* turn it off. */ objc_toggle(tree, hot_obj); if (!(inside(mx, my, &root)) ) /* Mouse has moved outside of */ { /* the dialog. Wait for return. */ *mode = M1_ENTER; rc_copy(&root, rect); return (NIL); } /* What object is mouse over? */ /* (Hit is guaranteed.) */ hot_obj = objc_find(tree, ROOT, MAX_DEPTH, mx, my); /* Is this object a hot-spot? */ state = LWGET(OB_STATE(hot_obj)); if (LWGET(OB_FLAGS(hot_obj)) & SELECTABLE) if ( !(state & DISABLED) ) { /* Yes! Set up wait state. */ *mode = M1_EXIT; objc_xywh(tree, hot_obj, rect); if (state & SELECTED) /* But only toggle if it's not */ return (NIL); /* already SELECTED! */ else { objc_toggle(tree, hot_obj); return (hot_obj); } } rc_copy(&root, &br_rect); /* No hot object, so compute */ br_mx = mx; /* mouse bounding rectangle. */ br_my = my; br_togl = 0; map_tree(tree, ROOT, NIL, break_obj); rc_copy(&br_rect, rect); /* Then return to wait state. */ *mode = M1_EXIT; return (NIL); } /************* Keyboard manager and subroutines ***************/ WORD find_def(tree, obj) /* Check if the object is DEFAULT */ LONG tree; WORD obj; { /* Is sub-tree hidden? */ if (HIDETREE & LWGET(OB_FLAGS(obj))) return (FALSE); /* Must be DEFAULT and not DISABLED */ if (DEFAULT & LWGET(OB_FLAGS(obj))) if ( !(DISABLED & LWGET(OB_STATE(obj))) ) fn_obj = obj; /* Record object number */ return (TRUE); } WORD find_tab(tree, obj) /* Look for target of TAB operation. */ LONG tree; WORD obj; { /* Check for hiddens subtree. */ if (HIDETREE & LWGET(OB_FLAGS(obj))) return (FALSE); /* If not EDITABLE, who cares? */ if ( !(EDITABLE & LWGET(OB_FLAGS(obj))) ) return (TRUE); /* Check for forward tab match */ if (fn_dir && fn_prev == fn_last) fn_obj = obj; /* Check for backward tab match */ if (!fn_dir && obj == fn_last) fn_obj = fn_prev; fn_prev = obj; /* Record object for next call. */ return (TRUE); } WORD form_keybd(tree, edit_obj, next_obj, kr, out_obj, okr) LONG tree; WORD edit_obj, next_obj, kr, *out_obj, *okr; { if (LLOBT(kr)) /* If lower byte valid, mask out */ kr &= 0xff; /* extended code byte. */ fn_dir = 0; /* Default tab direction if backward. */ switch (kr) { case CR: /* Zap character. */ *okr = 0; /* Look for a DEFAULT object. */ fn_obj = NIL; map_tree(tree, ROOT, NIL, find_def); /* If found, SELECT and force exit. */ if (fn_obj != NIL) { objc_sel(tree, fn_obj); *out_obj = fn_obj; return (FALSE); } /* Falls through to */ case TAB: /* tab if no default */ case DOWN: fn_dir = 1; /* Set fwd direction */ case BTAB: case UP: *okr = 0; /* Zap character */ fn_last = edit_obj; fn_prev = fn_obj = NIL; /* Look for TAB object */ map_tree(tree, ROOT, NIL, find_tab); if (fn_obj == NIL) /* try to wrap around */ map_tree(tree, ROOT, NIL, find_tab); if (fn_obj != NIL) *out_obj = fn_obj; break; default: /* Pass other chars */ return (TRUE); } return (TRUE); } /************* Mouse button manager and subroutines ***************/ WORD do_radio(tree, obj) LONG tree; WORD obj; { GRECT root; WORD pobj, sobj, state; objc_xywh(tree, ROOT, &root); pobj = get_parent(tree, obj); /* Get the object's parent */ for (sobj = LWGET(OB_HEAD(pobj)); sobj != pobj; sobj = LWGET(OB_NEXT(sobj)) ) { /* Deselect all but... */ if (sobj != obj) objc_dsel(tree, sobj); } objc_sel(tree, obj); /* the one being SELECTED */ } WORD /* Mouse button handler */ form_button(tree, obj, clicks, next_obj, hot_obj) LONG tree; WORD obj, clicks, *next_obj, *hot_obj; { WORD flags, state, hibit, texit, sble, dsbld, edit; WORD in_out, in_state; flags = LWGET(OB_FLAGS(obj)); /* Get flags and states */ state = LWGET(OB_STATE(obj)); texit = flags & TOUCHEXIT; sble = flags & SELECTABLE; dsbld = state & DISABLED; edit = flags & EDITABLE; if (!texit && (!sble || dsbld) && !edit) /* This is not an */ { /* interesting object */ *next_obj = 0; return (TRUE); } if (texit && clicks == 2) /* Preset special flag */ hibit = 0x8000; else hibit = 0x0; if (sble && !dsbld) /* Hot stuff! */ { if (flags & RBUTTON) /* Process radio buttons*/ do_radio(tree, obj); /* immediately! */ else if (!texit) { in_state = (obj == *hot_obj)? /* Already toggled ? */ state: state ^ SELECTED; if (!graf_watchbox(tree, obj, in_state, in_state ^ SELECTED)) { /* He gave up... */ *next_obj = 0; *hot_obj = NIL; return (TRUE); } } else /* if (texit) */ if (obj != *hot_obj) /* Force SELECTED */ objc_toggle(tree, obj); } if (obj == *hot_obj) /* We're gonna do it! So don't */ *hot_obj = NIL; /* turn it off later. */ if (texit || (flags & EXIT) ) /* Exit conditions. */ { *next_obj = obj | hibit; return (FALSE); /* Time to leave! */ } else if (!edit) /* Clear object unless tabbing */ *next_obj = 0; return (TRUE); } /************* New forms manager: Entry point and main loop *************/ WORD form_do(tree, start_fld) REG LONG tree; WORD *start_fld; { REG WORD edit_obj; WORD next_obj, hot_obj, hot_mode; WORD which, cont; WORD idx; WORD mx, my, mb, ks, kr, br; GRECT hot_rect; WORD (*valid)(); /* Init. editing */ next_obj = *start_fld; edit_obj = 0; /* Initial hotspot cndx */ hot_obj = NIL; hot_mode = M1_ENTER; objc_xywh(tree, ROOT, &hot_rect); /* Main event loop */ cont = TRUE; while (cont) { /* position cursor on */ /* the selected */ /* editting field */ if (edit_obj != next_obj) if (next_obj != 0) { edit_obj = next_obj; next_obj = 0; objc_edit(tree, edit_obj, 0, &idx, EDINIT); } /* wait for button or */ /* key or rectangle */ which = evnt_multi(MU_KEYBD | MU_BUTTON | MU_M1, 0x02, 0x01, 0x01, hot_mode, hot_rect.g_x, hot_rect.g_y, hot_rect.g_w, hot_rect.g_h, 0, 0, 0, 0, 0, 0x0L, 0, 0, &mx, &my, &mb, &ks, &kr, &br); if (which & MU_M1) /* handle rect. event */ hot_obj = form_hot(tree, hot_obj, mx, my, &hot_rect, &hot_mode); /* handle keyboard event*/ if (which & MU_KEYBD) { /* Control char filter */ cont = form_keybd(tree, edit_obj, next_obj, kr, &next_obj, &kr); if (kr && edit_obj) /* Add others to object */ objc_edit(tree, edit_obj, kr, &idx, EDCHAR); } /* handle button event */ if (which & MU_BUTTON) { /* Which object hit? */ next_obj = objc_find(tree, ROOT, MAX_DEPTH, mx, my); if (next_obj == NIL) next_obj = 0; else /* Process a click */ cont = form_button(tree, next_obj, br, &next_obj, &hot_obj); } /* handle end of field */ /* clean up */ if (!cont || (next_obj != edit_obj && next_obj != 0)) if (edit_obj != 0) objc_edit(tree, edit_obj, 0, &idx, EDEND); } /* If defaulted, may */ /* need to clear hotspot*/ if (hot_obj != (next_obj & 0x7fff)) if (hot_obj != NIL) objc_toggle(tree, hot_obj); /* return exit object */ /* hi bit may be set */ /* if exit obj. was */ /* double-clicked */ *start_fld = edit_obj; return(next_obj); } /*------------------------------*/ /* includes */ /*------------------------------*/ #include "portab.h" /* portable coding conv */ #include "machine.h" /* machine depndnt conv */ #include "osbind.h" /* BDOS defintions */ #include "gemdefs.h" /*------------------------------*/ /* open_file */ /*------------------------------*/ WORD open_file(file_name) BYTE *file_name; { LONG dos_hndl; FOREVER { dos_hndl = Fopen(file_name, 0); if (dos_hndl >= 0) return ((WORD) dos_hndl); if ( !dos_error((WORD) dos_hndl) ) return (-1); } return (-1); /* Appease lint */ } /*------------------------------*/ /* create_file */ /*------------------------------*/ WORD create_file(file_name) BYTE *file_name; { LONG dos_hndl; FOREVER { dos_hndl = Fcreate(file_name, 0); if (dos_hndl >= 0) return ((WORD) dos_hndl); if ( !dos_error((WORD) dos_hndl) ) return (-1); } return (-1); /* Appease lint */ } /*------------------------------*/ /* dos_error */ /*------------------------------*/ WORD dos_error(tos_err) WORD tos_err; { WORD f_ret; graf_mouse(ARROW, 0x0L); if (tos_err > -50) { tos_err += 31; tos_err = -tos_err; } f_ret = form_error(tos_err); return (f_ret); } /*------------------------------*/ /* get_file */ /*------------------------------*/ WORD get_file(extnt, got_file) BYTE *extnt, *got_file; { WORD butn, ii; BYTE tmp_path[64], tmp_name[13]; tmp_name[0] = '\0'; tmp_path[0] = '\0'; if (*got_file) parse_fname(got_file, tmp_path, tmp_name, extnt); if (!tmp_path[0]) get_path(&tmp_path[0], extnt); fsel_input(tmp_path, tmp_name, &butn); if (butn) { strcpy(got_file, tmp_path); for (ii = 0; got_file[ii] && got_file[ii] != '*'; ii++); got_file[ii - 1] = '\0'; strcat (got_file, "\\"); strcat(got_file, tmp_name); return (TRUE); } else return (FALSE); } /*------------------------------*/ /* parse_fname */ /*------------------------------*/ VOID parse_fname(full, path, name, extnt) BYTE *full, *path, *name, *extnt; { WORD i, j; BYTE *s, *d; for (i = strlen(full); i--; ) /* scan for end of path */ if (full[i] == '\\' || full[i] == ':') break; if (i == -1) strcpy(name, full); /* "Naked" file name */ else { strcpy(name, &full[i+1]); for (s = full, d = path, j = 0; j++ < i + 1; *d++ = *s++); strcpy(&path[i+1], "*."); strcat(path, extnt); } } /*------------------------------*/ /* get_path */ /*------------------------------*/ VOID get_path(tmp_path, spec) BYTE *tmp_path, *spec; { WORD cur_drv; cur_drv = Dgetdrv(); tmp_path[0] = cur_drv + 'A'; tmp_path[1] = ':'; Dgetpath(&tmp_path[2], 0); if (strlen(tmp_path) > 3) strcat(tmp_path, "\\"); else tmp_path[2] = '\0'; strcat(tmp_path, "*."); strcat(tmp_path, spec); } /*------------------------------*/ /* new_ext */ /*------------------------------*/ VOID new_ext(o_fname, n_fname, ext) BYTE *o_fname, *n_fname, *ext; { WORD ii, jj; strcpy(n_fname, o_fname); for (ii = (jj = strlen(n_fname)) - 1; ii && n_fname[ii] != '.'; ii--); if (!ii) n_fname[ii = jj] = '.'; strcpy(&n_fname[++ii], ext); } >>>>>>>>>>>>>>>>>>>>> Progress box setup and cleanup <<<<<<<<<<<<<<<<<<<<< /*------------------------------*/ /* beg_prog */ /*------------------------------*/ VOID beg_prog(rect) GRECT *rect; { OBJECT *tree; WORD xdial, ydial, wdial, hdial; rsrc_gaddr(R_TREE, PROGRESS, &tree); form_center(tree, &rect->g_x, &rect->g_y, &rect->g_w, &rect->g_h); form_dial(0, 0, 0, 0, 0, rect->g_x, rect->g_y, rect->g_w, rect->g_h); objc_draw(tree, ROOT, MAX_DEPTH, rect->g_x, rect->g_y, rect->g_w, rect->g_h); } /*------------------------------*/ /* end_prog */ /*------------------------------*/ VOID end_prog(rect) GRECT *rect; { form_dial(3, 0, 0, 0, 0, rect->g_x, rect->g_y, rect->g_w, rect->g_h); } >>>>>>>>>>>>>>>>>>>> Text line progress indicator <<<<<<<<<<<<<<<<<<<<<<< /*------------------------------*/ /* set_prog */ /*------------------------------*/ VOID set_prog(strno) UWORD strno; { OBJECT *tree; BYTE *saddr; rsrc_gaddr(R_TREE, STRINGS, &tree); saddr = (BYTE *) (tree + strno)->ob_spec; rsrc_gaddr(R_TREE, PROGRESS, &tree); set_text(tree, PLINE, saddr); disp_obj(tree, PLINE); } >>>>>>>>>>>>>>>>>>>> Moving bar progress indicator <<<<<<<<<<<<<<<<<<<<<< /*------------------------------*/ /* set_prog */ /*------------------------------*/ VOID set_prog(value, maxc) WORD value, maxc; { WORD wnew, wold; OBJECT *tree; GRECT box; rsrc_gaddr(R_TREE, PROGRESS, &tree); wold = (tree + PROBOX)->ob_width - 1; /* Take border into account */ wnew = wold + 1; if (maxc) wnew = max(1, ((LONG) value * (LONG) wnew) / maxc); (tree + PROBAR)->ob_width = wnew; if (value) { objc_xywh(tree, PROBAR, &box); box.g_x += wold; box.g_w -= wold; objc_draw(tree, ROOT, MAX_DEPTH, box.g_x, box.g_y, box.g_w, box.g_h); } } >>>>>>>>>>>>>>>>>>>> Progress indicator check for abort <<<<<<<<<<<<<<<< /*------------------------------*/ /* esc_prog */ /*------------------------------*/ WORD esc_prog() { WORD which, kr; WORD mx, my, mb, ks, br; /* Not used, but needed */ FOREVER { which = evnt_multi(MU_KEYBD | MU_TIMER, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0L, 0, 0, /* Zero timer delay */ &mx, &my, &mb, &ks, &kr, &br); if (which & MU_KEYBD) { if ((kr & 0xff) == 0x1B) /* ESC? */ return (TRUE); /* else try again */ } else /* if (which & MU_TIMER) */ return (FALSE); } return (TRUE); /* Keeps lint happy */ } >>>>>>>>>>>>>>>>>>>>>>> Progress subroutines <<<<<<<<<<<<<<<<<<<<<<< VOID set_text(tree, obj, str) OBJECT *tree; BYTE *str; WORD obj; { TEDINFO *obspec; obspec = (TEDINFO *) (tree + obj)->ob_spec; /* Get TEDINFO address */ obspec->te_ptext = str; /* Set new text pointer */ obspec->te_txtlen = strlen(str); /* Set new length */ } VOID disp_obj(tree, obj) OBJECT *tree; WORD obj; { GRECT box; objc_xywh(tree, obj, &box); objc_draw(tree, ROOT, MAX_DEPTH, box.g_x, box.g_y, box.g_w, box.g_h); } VOID objc_xywh(tree, obj, p) /* get x,y,w,h for specified object */ OBJECT *tree; WORD obj; GRECT *p; { objc_offset(tree, obj, &p->g_x, &p->g_y); p->g_w = (tree + obj)->ob_width; p->g_h = (tree + obj)->ob_height; } >>>>>>>>>>>>>>>>>>>>>> Box mover examples <<<<<<<<<<<<<<<<<<<<<<<<<< /*------------------------------*/ /* fourway_box */ /*------------------------------*/ VOID fourway_box(vdi_handle, rubber, limit) WORD vdi_handle; GRECT *rubber, *limit; { UWORD ox, oy, mx, my, foo, down; vswr_mode(vdi_handle, MD_XOR); /* Set VDI modes for box */ vsl_color(vdi_handle, BLACK); wind_update(BEG_MCTRL); /* Capture mouse */ ox = rubber->g_x; oy = rubber->g_y; /* Save off input corner */ graf_mkstate(&mx, &my, &foo, &foo); /* Initialize mouse posn */ do { rubber->g_x = min(ox, mx); /* Choose UL corner */ rubber->g_y = min(oy, my); rubber->g_w = max(ox, mx) - rubber->g_x + 1; rubber->g_h = max(oy, my) - rubber->g_y + 1; rc_intersect(limit, rubber); /* Lock into limit rect */ down = rub_wait(vdi_handle, rubber, &mx, &my); } while (down); wind_update(END_MCTRL); /* Release mouse to GEM */ } /*------------------------------*/ /* hot_dragbox */ /*------------------------------*/ WORD hot_dragbox(vdi_handle, box, limit, tree) WORD vdi_handle; GRECT *box, *limit; OBJECT *tree; { UWORD ox, oy, mx, my, foo, down; WORD hover_obj, ret_obj; vswr_mode(vdi_handle, MD_XOR); /* Set VDI modes for box */ vsl_color(vdi_handle, BLACK); wind_update(BEG_MCTRL); /* Capture mouse */ graf_mkstate(&mx, &my, &foo, &foo); /* Initialize mouse posn */ ox = min(box->g_w, max(0, mx - box->g_x) ); oy = min(box->g_h, max(0, my - box->g_y) ); hover_obj = NIL; do { box->g_x = mx - ox; box->g_y = my - oy; rc_constrain(limit, box); /* Lock into limit rect */ down = rub_wait(vdi_handle, box, &mx, &my); if (!inside(mx, my, limit)) ret_obj = NIL; else { ret_obj = objc_find(tree, ROOT, NIL, mx, my); if (ret_obj != NIL) if ( !(SELECTABLE & (tree + ret_obj)->ob_flags) ) ret_obj = NIL; } if (ret_obj != hover_obj) { if (hover_obj != NIL) objc_toggle(tree, hover_obj); hover_obj = ret_obj; if (hover_obj != NIL) objc_toggle(tree, hover_obj); } } while (down); wind_update(END_MCTRL); /* Release mouse to GEM */ if (hover_obj != NIL) objc_toggle(tree, hover_obj); return (hover_obj); } /*------------------------------*/ /* rub_wait */ /*------------------------------*/ WORD rub_wait(vdi_handle, box, mx, my) WORD vdi_handle; GRECT *box; WORD *mx, *my; { WORD which, kr; WORD mb, ks, br; /* Not used, but needed */ graf_mouse(M_OFF, 0x0L); vdi_xbox(vdi_handle, box); /* Draw waiting box */ graf_mouse(M_ON, 0x0L); which = evnt_multi(MU_BUTTON | MU_M1, 0x01, 0x01, 0x00, /* Wait for button up */ TRUE, *mx, *my, 1, 1, /* or mouse move */ 0, 0, 0, 0, 0, 0L, 0, 0, mx, my, &mb, &ks, &kr, &br); graf_mouse(M_OFF, 0x0L); vdi_xbox(vdi_handle, box); /* Take down waiting box */ graf_mouse(M_ON, 0x0L); return (!(which & MU_BUTTON)); /* TRUE if still dragging */ } >>>>>>>>>>>>>>>>>>>>>>>> Box Mover Utilities <<<<<<<<<<<<<<<<<<<<<<<<< VOID objc_toggle(tree, obj) OBJECT *tree; WORD obj; { WORD state, newstate; GRECT root, ob_rect; objc_xywh(tree, ROOT, &root); newstate = (tree + obj)->ob_state ^ SELECTED; objc_change(tree, obj, 0, root.g_x, root.g_y, root.g_w, root.g_h, newstate, 1); } VOID vdi_xbox(vdi_handle, pt) WORD vdi_handle; GRECT *pt; { WORD pxy[10]; vdi_bxpts(pt, pxy); vdi_xline(vdi_handle, 5, pxy); } VOID vdi_bxpts(pt, pxy) GRECT *pt; WORD *pxy; { pxy[0] = pt->g_x; pxy[1] = pt->g_y; pxy[2] = pt->g_x + pt->g_w - 1; pxy[3] = pt->g_y; pxy[4] = pt->g_x + pt->g_w - 1; pxy[5] = pt->g_y + pt->g_h - 1; pxy[6] = pt->g_x; pxy[7] = pt->g_y + pt->g_h - 1; pxy[8] = pt->g_x; pxy[9] = pt->g_y; } MLOCAL WORD hztltbl[2] = { 0x5555, 0xaaaa }; MLOCAL WORD verttbl[4] = { 0x5555, 0xaaaa, 0xaaaa, 0x5555 }; VOID vdi_xline(vdi_handle, ptscount, ppoints) WORD vdi_handle, ptscount, *ppoints; { WORD *linexy,i; WORD st; for ( i = 1; i < ptscount; i++ ) { if ( *ppoints == *(ppoints + 2) ) { st = verttbl[( (( *ppoints) & 1) | ((*(ppoints + 1) & 1 ) << 1))]; } else { linexy = ( *ppoints < *( ppoints + 2 )) ? ppoints : ppoints + 2; st = hztltbl[( *(linexy + 1) & 1)]; } vsl_udsty(vdi_handle, st); vsl_type(vdi_handle, 7); v_pline(vdi_handle, 2, ppoints); ppoints += 2; } vsl_type(vdi_handle, 1); } WORD rc_intersect(p1, p2) /* compute intersect of two rectangles */ GRECT *p1, *p2; { WORD tx, ty, tw, th; tw = min(p2->g_x + p2->g_w, p1->g_x + p1->g_w); th = min(p2->g_y + p2->g_h, p1->g_y + p1->g_h); tx = max(p2->g_x, p1->g_x); ty = max(p2->g_y, p1->g_y); p2->g_x = tx; p2->g_y = ty; p2->g_w = tw - tx; p2->g_h = th - ty; return( (tw > tx) && (th > ty) ); } VOID rc_union(p1, p2) GRECT *p1, *p2; { WORD tx, ty, tw, th; tw = max(p1->g_x + p1->g_w, p2->g_x + p2->g_w); th = max(p1->g_y + p1->g_h, p2->g_y + p2->g_h); tx = min(p1->g_x, p2->g_x); ty = min(p1->g_y, p2->g_y); p2->g_x = tx; p2->g_y = ty; p2->g_w = tw - tx; p2->g_h = th - ty; } VOID rc_constrain(pc, pt) GRECT *pc; GRECT *pt; { if (pt->g_x < pc->g_x) pt->g_x = pc->g_x; if (pt->g_y < pc->g_y) pt->g_y = pc->g_y; if ((pt->g_x + pt->g_w) > (pc->g_x + pc->g_w)) pt->g_x = (pc->g_x + pc->g_w) - pt->g_w; if ((pt->g_y + pt->g_h) > (pc->g_y + pc->g_h)) pt->g_y = (pc->g_y + pc->g_h) - pt->g_h; } BOOLEAN inside(x, y, pt) /* determine if x,y is in rectangle */ UWORD x, y; GRECT *pt; { if ( (x >= pt->g_x) && (y >= pt->g_y) && (x < pt->g_x + pt->g_w) && (y < pt->g_y + pt->g_h) ) return(TRUE); else return(FALSE); } /* inside */ PROGRESSPLINEPROBOXPROBARSTRINGSP1P2PN#define PROGRESS 0 /* TREE */ #define PLINE 2 /* OBJECT in TREE #0 */ #define PROBOX 3 /* OBJECT in TREE #0 */ #define PROBAR 4 /* OBJECT in TREE #0 */ #define STRINGS 1 /* TREE */ #define P1 1 /* OBJECT in TREE #1 */ #define P2 2 /* OBJECT in TREE #1 */ #define PN 3 /* OBJECT in TREE #1 */ $x Sample Progress BoxVariable Text LineProgress Event OneProgress Event TwoProgress Event N8KL)$ )p" rM` s#define T0OBJ 0 #define T1OBJ 5 #define FREEBB 0 #define FREEIMG 0 #define FREESTR 7 BYTE *rs_strings[] = { "Sample Progress Box", "Variable Text Line", "", "", "Progress Event One", "Progress Event Two", "Progress Event N"}; LONG rs_frstr[] = { 0}; BITBLK rs_bitblk[] = { 0}; LONG rs_frimg[] = { 0}; ICONBLK rs_iconblk[] = { 0}; TEDINFO rs_tedinfo[] = { 1L, 2L, 3L, 3, 6, 2, 0x1380, 0x0, -1, 19,1}; OBJECT rs_object[] = { -1, 1, 3, G_BOX, NONE, OUTLINED, 0x21100L, 0,0, 41,8, 2, -1, -1, G_STRING, NONE, NORMAL, 0x0L, 11,1, 19,1, 3, -1, -1, G_TEXT, NONE, NORMAL, 0x0L, 0,3, 41,1, 0, 4, 4, G_BOX, NONE, NORMAL, 0xFF1170L, 3,5, 34,1, 3, -1, -1, G_BOX, LASTOB, NORMAL, 0xFF1172L, 0,0, 6,1, -1, 1, 3, G_BOX, NONE, OUTLINED, 0x21100L, 0,0, 27,7, 2, -1, -1, G_STRING, NONE, NORMAL, 0x4L, 2,1, 18,1, 3, -1, -1, G_STRING, NONE, NORMAL, 0x5L, 2,3, 18,1, 0, -1, -1, G_STRING, LASTOB, NORMAL, 0x6L, 2,5, 18,1}; LONG rs_trindex[] = { 0L, 5L}; struct foobar { WORD dummy; WORD *image; } rs_imdope[] = { 0}; #define NUM_STRINGS 7 #define NUM_FRSTR 0 #define NUM_IMAGES 0 #define NUM_BB 0 #define NUM_FRIMG 0 #define NUM_IB 0 #define NUM_TI 1 #define NUM_OBS 9 #define NUM_TREE 2 BYTE pname[] = "GMCL16.RSC";/*------------------------------*/ /* includes */ /*------------------------------*/ #include "portab.h" /* portable coding conv */ #include "machine.h" /* machine depndnt conv */ #include "osbind.h" /* BDOS defintions */ #include "gemdefs.h" /*------------------------------*/ /* open_file */ /*------------------------------*/ WORD open_file(file_name) BYTE *file_name; { LONG dos_hndl; FOREVER { dos_hndl = Fopen(file_name, 0); if (dos_hndl >= 0) return ((WORD) dos_hndl); if ( !dos_error((WORD) dos_hndl) ) return (-1); } return (-1); /* Appease lint */ } /*------------------------------*/ /* create_file */ /*------------------------------*/ WORD create_file(file_name) BYTE *file_name; { LONG dos_hndl; FOREVER { dos_hndl = Fcreate(file_name, 0); if (dos_hndl >= 0) return ((WORD) dos_hndl); if ( !dos_error((WORD) dos_hndl) ) return (-1); } return (-1); /* Appease lint */ } /*------------------------------*/ /* dos_error */ /*------------------------------*/ WORD dos_error(tos_err) WORD tos_err; { WORD f_ret; graf_mouse(ARROW, 0x0L); if (tos_err > -50) { tos_err += 31; tos_err = -tos_err; } f_ret = form_error(tos_err); close_files(); return (f_ret); } /*------------------------------*/ /* get_file */ /*------------------------------*/ WORD get_file(extnt, got_file) BYTE *extnt, *got_file; { WORD butn, ii; BYTE tmp_path[64], tmp_name[13]; tmp_name[0] = '\0'; tmp_path[0] = '\0'; if (*got_file) parse_fname(got_file, tmp_path, tmp_name, extnt); if (!tmp_path[0]) get_path(&tmp_path[0], extnt); fsel_input(tmp_path, tmp_name, &butn); if (butn) { strcpy(got_file, tmp_path); for (ii = 0; got_file[ii] && got_file[ii] != '*'; ii++); got_file[ii - 1] = '\0'; strcat (got_file, "\\"); strcat(got_file, tmp_name); return (TRUE); } else return (FALSE); } /*------------------------------*/ /* parse_fname */ /*------------------------------*/ VOID parse_fname(full, path, name, extnt) BYTE *full, *path, *name, *extnt; { WORD i, j; BYTE *s, *d; for (i = strlen(full); i--; ) /* scan for end of path */ if (full[i] == '\\' || full[i] == ':') break; if (i == -1) strcpy(name, full); /* "Naked" file name */ else { strcpy(name, &full[i+1]); for (s = full, d = path, j = 0; j++ < i + 1; *d++ = *s++); strcpy(&path[i+1], "*."); strcat(path, extnt); } } /*------------------------------*/ /* get_path */ /*------------------------------*/ VOID get_path(tmp_path, spec) BYTE *tmp_path, *spec; { WORD cur_drv; cur_drv = Dgetdrv(); tmp_path[0] = cur_drv + 'A'; tmp_path[1] = ':'; Dgetpath(&tmp_path[2], 0); if (strlen(tmp_path) > 3) strcat(tmp_path, "\\"); else tmp_path[2] = '\0'; strcat(tmp_path, "*."); strcat(tmp_path, spec); } /*------------------------------*/ /* new_ext */ /*------------------------------*/ VOID new_ext(o_fname, n_fname, ext) BYTE *o_fname, *n_fname, *ext; { WORD ii, jj; strcpy(n_fname, o_fname); for (ii = (jj = strlen(n_fname)) - 1; ii && n_fname[ii] != '.'; ii--); if (!ii) n_fname[ii = jj] = '.'; strcpy(&n_fname[++ii], ext); } /*------------------------------*/ /* includes */ /*------------------------------*/ #include "portab.h" /* portable coding conv */ #include "machine.h" /* machine depndnt conv */ #include "obdefs.h" /* object definitions */ #include "gembind.h" /* gem binding structs */ #include "taddr.h" #include "rsconv.h" /*------------------------------*/ /* defines */ /*------------------------------*/ #define NIL -1 #define DESK 0 #define ARROW 0 #define HOUR_GLASS 2 #define END_UPDATE 0 #define BEG_UPDATE 1 #define TE_PTEXT(x) (x) #define TE_TXTLEN(x) ((x) + 24) /*------------------------------*/ /* do_obj */ /*------------------------------*/ VOID do_obj(tree, which, bit) /* set specified bit in object state */ OBJECT *tree; WORD which, bit; { (tree + which)->ob_state |= bit; } /*------------------------------*/ /* undo_obj */ /*------------------------------*/ VOID undo_obj(tree, which, bit) /* clear specified bit in object state */ OBJECT *tree; WORD which, bit; { (tree + which)->ob_state &= (~bit); } /*------------------------------*/ /* sel_obj */ /*------------------------------*/ WORD sel_obj(tree, which) OBJECT *tree; WORD which; { do_obj(tree, which, SELECTED); return (TRUE); } /*------------------------------*/ /* desel_obj */ /*------------------------------*/ WORD desel_obj(tree, which) OBJECT *tree; WORD which; { undo_obj(tree, which, SELECTED); return (TRUE); } /*------------------------------*/ /* disab_obj */ /*------------------------------*/ WORD disab_obj(tree, obj) OBJECT *tree; WORD obj; { undo_obj(tree, obj, DISABLED); return (TRUE); } /*------------------------------*/ /* objc_xywh */ /*------------------------------*/ VOID objc_xywh(tree, obj, p) OBJECT *tree; WORD obj; GRECT *p; { objc_offset(tree, obj, &p->g_x, &p->g_y); p->g_w = (tree + obj)->ob_width; p->g_h = (tree + obj)->ob_height; } /*------------------------------*/ /* disp_obj */ /*------------------------------*/ VOID disp_obj(tree, obj) OBJECT *tree; WORD obj; { GRECT box; objc_xywh(tree, obj, &box); objc_draw(tree, ROOT, MAX_DEPTH, box.g_x, box.g_y, box.g_w, box.g_h); } /*------------------------------*/ /* selected */ /*------------------------------*/ WORD selected(tree, obj) OBJECT *tree; WORD obj; { return (SELECTED & (tree + obj)->ob_state)? TRUE: FALSE; } /*------------------------------*/ /* map_tree */ /*------------------------------*/ VOID map_tree(tree, this, last, routine) OBJECT *tree; WORD this, last; WORD (*routine)(); { WORD tmp1; tmp1 = this; /* Initialize to impossible value: */ /* TAIL won't point to self! */ /* Look until final node, or off */ /* the end of tree */ while (this != last && this != NIL) /* Did we 'pop' into this node */ /* for the second time? */ if ((tree + this)->ob_tail != tmp1) { tmp1 = this; /* This is a new node */ this = NIL; /* Apply operation, testing */ /* for rejection of sub-tree */ if ((*routine)(tree, tmp1)) this = (tree + tmp1)->ob_head; /* Subtree path not taken, */ /* so traverse right */ if (this == NIL) this = (tree + tmp1)->ob_next; } else /* Revisiting parent: */ /* No operation, move right */ { tmp1 = this; this = (tree + tmp1)->ob_next; } } /*------------------------------*/ /* hndl_dial */ /*------------------------------*/ VOID hndl_dial(tree, def, x, y, w, h) OBJECT *tree; WORD def; WORD x, y, w, h; { WORD xdial, ydial, wdial, hdial, exitobj; UWORD xtype; form_center(tree, &xdial, &ydial, &wdial, &hdial); form_dial(0, x, y, w, h, xdial, ydial, wdial, hdial); form_dial(1, x, y, w, h, xdial, ydial, wdial, hdial); objc_draw(tree, ROOT, MAX_DEPTH, xdial, ydial, wdial, hdial); exitobj = form_do(tree, def) & 0x7FFF; form_dial(2, x, y, w, h, xdial, ydial, wdial, hdial); form_dial(3, x, y, w, h, xdial, ydial, wdial, hdial); return (exitobj); } /*------------------------------*/ /* set_text */ /*------------------------------*/ VOID set_text(tree, obj, str, len) OBJECT *tree; BYTE *str; WORD obj, len; { TEDINFO *obspec; obspec = (TEDINFO *) (tree + obj)->ob_spec; obspec->te_ptext = str; obspec->te_txtlen = len; } /*------------------------------*/ /* beg_prog */ /*------------------------------*/ VOID beg_prog(rect) GRECT *rect; { OBJECT *tree; WORD xdial, ydial, wdial, hdial; rsrc_gaddr(R_TREE, PROGRESS, &tree); form_center(tree, &rect->g_x, &rect->g_y, &rect->g_w, &rect->g_h); form_dial(0, 0, 0, 0, 0, rect->g_x, rect->g_y, rect->g_w, rect->g_h); objc_draw(tree, ROOT, MAX_DEPTH, rect->g_x, rect->g_y, rect->g_w, rect->g_h); } /*------------------------------*/ /* end_prog */ /*------------------------------*/ VOID end_prog(rect) GRECT *rect; { form_dial(3, 0, 0, 0, 0, rect->g_x, rect->g_y, rect->g_w, rect->g_h); } /*------------------------------*/ /* set_prog */ /*------------------------------*/ VOID set_prog(strno) UWORD strno; { OBJECT *tree; BYTE *saddr; rsrc_gaddr(R_TREE, STRINGS, &tree); saddr = (BYTE *) (tree + strno)->ob_spec; rsrc_gaddr(R_TREE, PROGRESS, &tree); set_text(tree, PLINE, saddr); disp_obj(tree, PLINE); } /*------------------------------*/ /* includes */ /*------------------------------*/ #include "portab.h" /* portable coding conv */ #include "machine.h" /* machine depndnt conv */ #include "obdefs.h" /* object definitions */ #include "gembind.h" /* gem binding structs */ #include "osbind.h" /* BDOS definitions */ #include "gemdefs.h" #include "rsconv.h" /*------------------------------*/ /* defines */ /*------------------------------*/ #define NIL -1 #define DESK 0 #define ARROW 0 #define HOUR_GLASS 2 #define END_UPDATE 0 #define BEG_UPDATE 1 typedef struct memform { WORD *mp; WORD fwp; WORD fh; WORD fww; WORD ff; WORD np; WORD r1; WORD r2; WORD r3; } MFDB; /*------------------------------*/ /* Global */ /*------------------------------*/ GLOBAL WORD gl_apid; GLOBAL WORD contrl[11]; /* control inputs */ GLOBAL WORD intin[80]; /* max string length */ GLOBAL WORD ptsin[256]; /* polygon fill points */ GLOBAL WORD intout[45]; /* open workstation output */ GLOBAL WORD ptsout[12]; GLOBAL WORD gl_wchar; /* character width */ GLOBAL WORD gl_hchar; /* character height */ GLOBAL WORD gl_wbox; /* box (cell) width */ GLOBAL WORD gl_hbox; /* box (cell) height */ GLOBAL WORD gem_handle; /* GEM handle */ GLOBAL WORD vdi_handle; /* VDI handle */ GLOBAL WORD work_out[57]; /* open virt workstation values */ GLOBAL GRECT scrn_area; /* scrn area */ GLOBAL MFDB scrn_mfdb; /* scrn memory def'n for blt */ GLOBAL GRECT full; /* desktop size */ /*------------------------------*/ /* External */ /*------------------------------*/ EXTERN WORD desel_obj(); /*------------------------------*/ /* Local */ /*------------------------------*/ MLOCAL WORD native_in = TRUE; /* TRUE: input .RSC is native */ /* FALSE: input .RSC is foreign */ MLOCAL WORD conv_def = TRUE; /* TRUE: convert .DEF also */ MLOCAL WORD new_dfn = FALSE; /* TRUE: use new symbol fmt */ MLOCAL BYTE old_rsc[4] = "RSC"; /* new resource file extent */ MLOCAL BYTE new_rsc[4] = "RS2"; /* new resource file extent */ MLOCAL BYTE old_def[4] = "DEF"; /* new definition file extend */ MLOCAL BYTE new_def[4] = "DF2"; /* new definition file extend */ MLOCAL BYTE r_file[81]; /* resource file name */ MLOCAL BYTE d_file[81]; /* definition file name */ MLOCAL BYTE r2_file[81]; /* output resource name */ MLOCAL BYTE d2_file[81]; /* output definition name */ MLOCAL WORD r_hndl = -1; /* resource file handle */ MLOCAL WORD d_hndl = -1; /* definition file handle */ MLOCAL WORD r2_hndl = -1; /* output resource handle */ MLOCAL WORD d2_hndl = -1; /* output definition handle */ MLOCAL LONG f_err; /* file error */ MLOCAL BYTE *head; /* location of buffer */ MLOCAL LONG buff_size; /* size of buffer */ MLOCAL BYTE buff[20]; /* def file work area */ MLOCAL UWORD img_offset, addr; /* for image fixup */ MLOCAL WORD img_odd; /* image fixup needed? */ MLOCAL GRECT prog_rect; /* rectangle for prog indicator */ /*------------------------------*/ /* swap_bytes */ /*------------------------------*/ VOID swap_bytes(where, len) BYTE *where; WORD len; { BYTE swap; for (; len > 0; len -= 2) { swap = *where; *where = *(where + 1); *(where + 1) = swap; where += 2; } } /*------------------------------*/ /* swap_words */ /*------------------------------*/ VOID swap_words(where, len) WORD *where; WORD len; { UWORD swap; for (; len > 0; len -= 4) { swap = *where; *where = *(where + 1); *(where + 1) = swap; where += 2; } } /*------------------------------*/ /* swap_images */ /*------------------------------*/ VOID swap_images() { BITBLK *where; ICONBLK *where2; BYTE *taddr; WORD num; WORD wb, hl; where = (BITBLK *) (head + ((RSHDR *) head)->rsh_bitblk); num = ((RSHDR *) head)->rsh_nbb; for (; num--; where++) { taddr = where->bi_pdata; wb = where->bi_wb; hl = where->bi_hl; if ((LONG) taddr != -1L) { if (img_odd) where->bi_pdata = ++taddr; swap_bytes(head + taddr, wb * hl); } } where2 = (ICONBLK *) (head + ((RSHDR *) head)->rsh_iconblk); num = ((RSHDR *) head)->rsh_nib; for (; num--; where2++) { wb = (where2->ib_wicon + 7) >> 3; hl = where2->ib_hicon; taddr = where2->ib_pdata; if ((LONG) taddr != -1L) { if (img_odd) where2->ib_pdata = ++taddr; swap_bytes(head + taddr, wb * hl); } taddr = where2->ib_pmask; if ((LONG) taddr != -1L) { if (img_odd) where2->ib_pmask = ++taddr; swap_bytes(head + taddr, wb * hl); } } } /*------------------------------*/ /* swap_trees */ /*------------------------------*/ VOID swap_trees() { BYTE *where; WORD size; where = head + ((RSHDR *) head)->rsh_trindex; size = ((RSHDR *) head)->rsh_ntree * sizeof(LONG); swap_bytes(where, size); swap_words((WORD *) where, size); } /*------------------------------*/ /* swap_objs */ /*------------------------------*/ VOID swap_objs() { OBJECT *where; WORD num; where = (OBJECT *) (head + ((RSHDR *) head)->rsh_object); num = ((RSHDR *) head)->rsh_nobs; swap_bytes((BYTE *) where, num * sizeof(OBJECT)); for (; num--; where++) swap_words((WORD *) &where->ob_spec, sizeof(LONG)); } /*------------------------------*/ /* swap_teds */ /*------------------------------*/ VOID swap_teds() { TEDINFO *where; WORD num; where = (TEDINFO *) (head + ((RSHDR *) head)->rsh_tedinfo); num = ((RSHDR *) head)->rsh_nted; swap_bytes((BYTE *) where, num * sizeof(TEDINFO)); for (; num--; where++) { swap_words((WORD *) &where->te_ptext, sizeof(LONG)); swap_words((WORD *) &where->te_ptmplt, sizeof(LONG)); swap_words((WORD *) &where->te_pvalid, sizeof(LONG)); } } /*------------------------------*/ /* swap_ibs */ /*------------------------------*/ VOID swap_ibs() { ICONBLK *where; WORD num; where = (ICONBLK *) (head + ((RSHDR *) head)->rsh_iconblk); num = ((RSHDR *) head)->rsh_nib; swap_bytes((BYTE *) where, num * sizeof(ICONBLK)); for (; num--; where++) { swap_words((WORD *) &where->ib_pdata, sizeof(LONG)); swap_words((WORD *) &where->ib_pmask, sizeof(LONG)); swap_words((WORD *) &where->ib_ptext, sizeof(LONG)); } } /*------------------------------*/ /* swap_bbs */ /*------------------------------*/ VOID swap_bbs() { BITBLK *where; WORD num; where = (BITBLK *) (head + ((RSHDR *) head)->rsh_bitblk); num = ((RSHDR *) head)->rsh_nbb; swap_bytes((BYTE *) where, num * sizeof(BITBLK)); for (; num--; where++) swap_words((WORD *) &where->bi_pdata, sizeof(LONG)); } /*------------------------------*/ /* swap_fstr */ /*------------------------------*/ VOID swap_fstr() { BYTE *where; WORD size; where = head + ((RSHDR *) head)->rsh_frstr; size = ((RSHDR *) head)->rsh_nstring * sizeof(LONG); swap_bytes(where, size); swap_words((WORD *) where, size); } /*------------------------------*/ /* swap_fimg */ /*------------------------------*/ VOID swap_fimg() { LONG where; WORD size; where = head + ((RSHDR *) head)->rsh_frimg; size = ((RSHDR *) head)->rsh_nimages * sizeof(LONG); swap_bytes(where, size); swap_words((WORD *) where, size); } /*------------------------------*/ /* close_files */ /*------------------------------*/ VOID close_files() { if (r_hndl != -1) Fclose(r_hndl); if (d_hndl != -1) Fclose(d_hndl); if (r2_hndl != -1) Fclose(r2_hndl); if (d2_hndl != -1) Fclose(d2_hndl); r_hndl = d_hndl = r2_hndl = d2_hndl = -1; } /*------------------------------*/ /* do_conv */ /*------------------------------*/ WORD do_conv() { BYTE *str; LONG size; WORD reply, nsym; r_file[0] = '\0'; if (!get_file(old_rsc, r_file)) return; if ((r_hndl = open_file(r_file)) == -1) return; if (conv_def) { new_ext(r_file, d_file, old_def); FOREVER { d_hndl = (WORD) Fopen(d_file, 0); if (d_hndl >= 0) break; rsrc_gaddr(R_STRING, NODEF, &str); reply = form_alert(1, str); if (reply == 3) { close_files(); return; } if (reply == 1) { conv_def = FALSE; break; } /* if (reply == 2) */ if (!get_file(old_def, d_file)) break; } } graf_mouse(HOUR_GLASS, 0x0L); beg_prog(&prog_rect); set_prog(RSCREAD); f_err = Fread(r_hndl, (LONG) sizeof(RSHDR), head); if ( f_err < 0) { dos_error((WORD) f_err); return; } if (!native_in) swap_bytes(head, sizeof(RSHDR)); size = ((RSHDR *) head)->rsh_rssize; if (buff_size < size) { graf_mouse(ARROW, 0x0L); /* Before alert */ rsrc_gaddr(R_STRING, NOMEM, &str); form_alert(1, str); close_files(); return; } f_err = Fread(r_hndl, size - sizeof(RSHDR), head + sizeof(RSHDR)); if ( f_err < 0) { dos_error((WORD) f_err); return; } img_offset = ((RSHDR *) head)->rsh_imdata; if ( (img_odd = img_offset & 0x0001) ) { set_prog(IMGALIGN); ((RSHDR *) head)->rsh_rssize = img_offset + 1; for (addr = ((RSHDR *) head)->rsh_frstr; --addr > img_offset; ) *(head + addr) = *(head + addr - 1); } set_prog(BYTESWAP); if (native_in) swap_images(); swap_trees(); swap_objs(); swap_teds(); swap_ibs(); swap_bbs(); swap_fstr(); swap_fimg(); if (native_in) swap_bytes(head, sizeof(RSHDR)); else swap_images(); set_prog(RSCWRITE); new_ext(r_file, r2_file, new_rsc); if ( (r2_hndl = create_file(r2_file)) == -1) { close_files(); return; } graf_mouse(HOUR_GLASS, 0x0L); /* Create_file could reset */ f_err = Fwrite(r2_hndl, size, head); if ( f_err < 0) { dos_error((WORD) f_err); return; } if (!conv_def) { close_files(); end_prog(&prog_rect); return; } set_prog(DEFREAD); new_ext(r_file, d2_file, new_def); if ( (d2_hndl = create_file(d2_file)) == -1) { close_files(); return; } f_err = Fread(d_hndl, (LONG) sizeof(WORD), &nsym); if ( f_err < 0) { dos_error((WORD) f_err); return; } set_prog(BYTESWAP); reply = nsym; swap_bytes(&reply, 2); f_err = Fwrite(d2_hndl, (LONG) sizeof(WORD), new_dfn? &nsym: &reply); if ( f_err < 0) { dos_error((WORD) f_err); return; } if (!native_in || new_dfn) nsym = reply; for (; nsym--; ) { if (!new_dfn) /* Spare words only if */ /* using old format */ if (!native_in) { /* insert spare word */ reply = 0; f_err = Fwrite(d2_hndl, (LONG) sizeof(WORD), &reply); if ( f_err < 0) { dos_error((WORD) f_err); return; } } else { /* delete extra word */ f_err = Fread(d_hndl, (LONG) sizeof(WORD), buff); if ( f_err < 0) { dos_error((WORD) f_err); return; } } f_err = Fread(d_hndl, (LONG) sizeof(WORD), buff); if ( f_err < 0) { dos_error((WORD) f_err); return; } if (!new_dfn) /* Just copy for new fmt */ swap_bytes(buff, 2); f_err = Fwrite(d2_hndl, (LONG) sizeof(WORD), buff); if ( f_err < 0) { dos_error((WORD) f_err); return; } f_err = Fread(d_hndl, (LONG) (sizeof(WORD) + 10), buff); if ( f_err < 0) { dos_error((WORD) f_err); return; } if (!new_dfn) swap_bytes(buff, 2); /* only swap value */ f_err = Fwrite(d2_hndl, (LONG) (sizeof(WORD) + 10), buff); if ( f_err < 0) { dos_error((WORD) f_err); return; } } set_prog(DEFWRITE); close_files(); end_prog(&prog_rect); } /*------------------------------*/ /* do_help */ /*------------------------------*/ VOID do_help() { OBJECT *tree; WORD obj; rsrc_gaddr(R_TREE, HELP, &tree); obj = hndl_dial(tree, 0, 0, 0, 0, 0); desel_obj(tree, obj); } /*------------------------------*/ /* do_mode */ /*------------------------------*/ WORD do_mode() { OBJECT *tree; WORD obj; WORD xdial, ydial, wdial, hdial; rsrc_gaddr(R_TREE, MODE, &tree); sel_obj(tree, native_in? N2F: F2N); if (!conv_def) sel_obj(tree, DEFNO); else if (!new_dfn) sel_obj(tree, DEFYES); else sel_obj(tree, DFNYES); set_text(tree, RSC1, old_rsc, 4); set_text(tree, RSC2, new_rsc, 4); set_text(tree, DEF1, old_def, 4); set_text(tree, DEF2, new_def, 4); form_center(tree, &xdial, &ydial, &wdial, &hdial); form_dial(0, 0, 0, 0, 0, xdial, ydial, wdial, hdial); form_dial(1, 0, 0, 0, 0, xdial, ydial, wdial, hdial); objc_draw(tree, ROOT, MAX_DEPTH, xdial, ydial, wdial, hdial); while (TRUE) { obj = form_do(tree, 0) & 0x7FFF; if (obj == DEFYES) { if (strcmp(old_def, "DFN") == 0) { strcpy(old_def, "DEF"); disp_obj(tree, DEF1); } } else if (obj == DFNYES) { if (strcmp(old_def, "DEF") == 0) { strcpy(old_def, "DFN"); disp_obj(tree, DEF1); } } else break; } form_dial(2, 0, 0, 0, 0, xdial, ydial, wdial, hdial); form_dial(3, 0, 0, 0, 0, xdial, ydial, wdial, hdial); native_in = selected(tree, N2F); conv_def = !selected(tree, DEFNO); new_dfn = selected(tree, DFNYES); map_tree(tree, ROOT, NIL, desel_obj); return (obj); } /*------------------------------*/ /* rscv_run */ /*------------------------------*/ WORD rscv_run() { WORD obj; FOREVER { obj = do_mode(); if (obj == HELPMODE) do_help(); else if (obj == CONVMODE) { do_conv(); graf_mouse(ARROW, 0x0L); } else break; } } /*------------------------------*/ /* rscv_term */ /*------------------------------*/ VOID rscv_term(term_type) WORD term_type; { switch (term_type) /* NOTE: all cases fall through */ { case (0 /* normal termination */): Mfree(head); case (2): v_clsvwk( vdi_handle ); case (3): rsrc_free(); case (4): if (term_type) wind_update(END_UPDATE); appl_exit(); case (5): break; } } /*------------------------------*/ /* rscv_init */ /*------------------------------*/ WORD rscv_init() { WORD i; WORD work_in[11]; LONG tree; appl_init(); /* initialize libraries */ if (gl_apid == -1) return (5); /* No handles ! */ graf_mouse(HOUR_GLASS, 0x0L); wind_update(BEG_UPDATE); if (!rsrc_load( "RSCONV.RSC" )) { graf_mouse(ARROW, 0x0L); form_alert(1, "[3][Fatal Error !|RSCONV.RSC|File Not Found][ Abort ]" ); return(4); /* Can't find resource */ } for (i=0; i<10; i++) { work_in[i]=1; } work_in[10]=2; gem_handle = graf_handle(&gl_wchar,&gl_hchar,&gl_wbox,&gl_hbox); vdi_handle = gem_handle; v_opnvwk(work_in,&vdi_handle,work_out); /* open virtual work stn*/ if (vdi_handle == 0) return (3); vqt_attributes (vdi_handle, work_in); scrn_area.g_x = 0; scrn_area.g_y = 0; scrn_area.g_w = work_out[0] + 1; scrn_area.g_h = work_out[1] + 1; scrn_mfdb.np = work_out[4]; scrn_mfdb.mp = 0x0L; wind_get(DESK, WF_WXYWH, &full.g_x, &full.g_y, &full.g_w, &full.g_h); vst_height(vdi_handle, work_in[7], &gl_wchar, &gl_hchar, &gl_wbox, &gl_hbox); buff_size = Malloc(-1L); head = Malloc(buff_size - 4000L); graf_mouse(ARROW,0x0L); wind_update(END_UPDATE); return(0); } /*------------------------------*/ /* main */ /*------------------------------*/ main() { WORD rscv_code; if ( !(rscv_code = rscv_init()) ) /* initialization */ rscv_run(); rscv_term(rscv_code); /* termination */ } gggggggggggggggggggggggggMODEN2FF2NDEF2HELPMODECONVMODEQUITMODENODEFDEFYESDEFNOHELPNOMEMDFNYES DEF1 RSC1 RSC2PROGRESSSTRINGSPLINEIMGALIGNRSCREADBYTESWAPRSCWRITEDEFREADDEFWRITE#define MODE 0 /* TREE */ #define N2F 7 /* OBJECT in TREE #0 */ #define F2N 8 /* OBJECT in TREE #0 */ #define DEF2 15 /* OBJECT in TREE #0 */ #define HELPMODE 21 /* OBJECT in TREE #0 */ #define CONVMODE 22 /* OBJECT in TREE #0 */ #define QUITMODE 23 /* OBJECT in TREE #0 */ #define NODEF 0 /* STRING */ #define DEFYES 18 /* OBJECT in TREE #0 */ #define DEFNO 19 /* OBJECT in TREE #0 */ #define HELP 1 /* TREE */ #define NOMEM 1 /* STRING */ #define DFNYES 20 /* OBJECT in TREE #0 */ #define DEF1 13 /* OBJECT in TREE #0 */ #define RSC1 10 /* OBJECT in TREE #0 */ #define RSC2 12 /* OBJECT in TREE #0 */ #define PROGRESS 2 /* TREE */ #define STRINGS 3 /* TREE */ #define PLINE 2 /* OBJECT in TREE #2 */ #define IMGALIGN 2 /* OBJECT in TREE #3 */ #define RSCREAD 1 /* OBJECT in TREE #3 */ #define BYTESWAP 3 /* OBJECT in TREE #3 */ #define RSCWRITE 4 /* OBJECT in TREE #3 */ #define DEFREAD 5 /* OBJECT in TREE #3 */ #define DEFWRITE 6 /* OBJECT in TREE #3 */ `  *O.|%*m - ЭЭм// ??<JNA N/<NA"/0<NBNuNV`& nP n"n nnTUn Jn nN^NuNV`& n=P n"n0 n1nXYn Jn nN^NuNV y'>0(Hй'>-@ y'>=h`^ n-P n=h n=h g0Jy'^g R n 0.> 9'>Ю/aX0.SnJ@f y'>0(Hй'>-@ y'>=h` n0(^@@=@ n=h n-h g2Jy'^gR n!n0.> 9'>Ю/aX n-P g0Jy'^g R n 0.> 9'>Ю/a>X"0.SnJ@fJN^NuNV y'>0(Hй'>-@ y'>0(@=@>/.aX>/.aXN^NuNV y'>0(Hй'>-@ y'>=h0.>/.aX`>/. aX0.SnJ@fN^NuNV y'>0(Hй'>-@ y'>=h0.>/.a8X`6>/.a^X>/.XaNX>/.Pa>X0.SnJ@fN^NuNV y'>0(Hй'>-@ y'>=h0.">/.aX`6>/.XaX>/.aX>/.PaX"0.SnJ@fN^NuNV y'>0(Hй'>-@ y'>=h0.>/.a4X`>/.aZX0.SnJ@fN^NuNV y'>0( Hй'>-@ y'>0(@=@>/.aX>/.aXN^NuNV y'>0(Hй'>-@ y'>0( @=@>/.aX>/.aXN^NuNV y!rg>!r?<>N!t?<>N!v?<>N!x?<>NNv.'>/<$?9!r?<?NN6`0Jy!\f>$/9'>aX y'>0("H-@ 9'Bl8BBgN.T.Y?<?<NX.?<N2Ta`.'>$/.?9!r?<?NN6`| y'>3'ZB@09'Z|3'^gh>NvB@09'ZR@"y'>3@" y'>3 '\`$ y'>2y'\"y'>4y'\Sy'\B@09'\y'Zb>NvJy!\gaa$ajaaLaa(anJy!\g>$/9'>aX`az>Nv.!f/<&/<%NP.&N3!v|faZ`^B?<N.T.'>/.?9!v?<@NN6`Jy!^fa.'`N8`>Nv.!n/<&/<%NP.&N3!x|fa`./<?9!t?<?NN6`t>Nv=n>/aXJy!`g .`./<?9!x?<@NN6`Jy!\gJy!`g=n`Jy!`fJy!\fFBn./<?9!x?<@NN6``>.'F/<?9!t?<?NN6`P.'F/<?9!t?<?NN6`Jy!`f>/<'FahX.'F/<?9!x?<@NN6`.'F/< ?9!t?<?NN6`~Jy!`f>/<'FaX.'F/< ?9!x?<@NN6`(0.SnJ@f8>Nva.'`N8N^NuNV.Y?<BgNXBWBgBgBgBg/.N: =@>/.NXN^NuNV.YBgBgNXJy!\g>`>/.NXJy!^f>/.NX`*Jy!`f>/.NX`>/.NX>/<!b?< /.Nb >/<!f?< /.Nb >/<!j?< /.Nb >/<!n?</.Nb .///Q/.Nn>?.?.?.BgBgBgBgBgN>?.?.?.BgBgBgBg?<N>?.?.?.?<Bg/.NP`BW/.NX|=@ nf>.!/<!jNvXJ@f$.!/<!jNX> /.N4X`H nf>.!/<!jNvXJ@f$.!/<!jNX> /.N4X```Z>?.?.?.BgBgBgBg?<N>?.?.?.BgBgBgBg?<N>/.NrX3!\>/.NrXJ@gB@`p3!^>/.NrX3!`.??<IN+NNJngBWNN``|b@0@!z PNN^NuNVNB y+fp`B?<N.T>N.!NJ@f$BBgN.T.!?<N2Tp`zBn` N2n1|Rn n m=|.(z/<*/<+&/<+N 3+3++.+(/<+/NPJy+fp`.?9+NNTBy(By(09+(R@3(09+*R@3(3+0(B(.*/<*/<*/<*?<BgN8.(z/<*/<+&/<+?.?9+N.?<HNBBgN.TBWNB@N^NuNVa=@fat>aN^NuNVBW/.?<=N<\-@Jm .` .>aNJ@fp``pN^NuNVBW/.?<aJ@fp``pN^NuNVBBgN.T non0.D@=@>NT=@N0.N^NuNVB.B. n Jg".///. a J.f./aX.U//NPJngt./. NXBn`Rn0n"n J0g n 2n *f0n B(.!/. NLX./. NLXp``B@N^NuNV.N=@` n2n \g n2n :g 0.SnJ@f nf./.NX` .2.HЁ.R/.NX-n-n Bn` n"nRR0.2.RAA@RnDm.! . 2.HЁ/RNX./. NLXN^NuNV>N<=@0.|A"n n|:BW/.T?<GN<\.Nڰ|o.!/.NLX` nB(.!/.NLX. /.NLXN^NuNV./. NX. N=@S@=@`SnJng n 2n .fJnf0n=H ..Rn0.HЮ /NXN^NuNV0.2. Ү"Ai N^NuNV0.F@2. Ү"Ai N^NuNV>?. /.a\pN^NuNV>?. /.a\pN^NuNV>?. /.a\pN^NuNV.T/.?. /.N n2. Ү"A1i n2. Ү"A1iN^NuNV.Q?. /.a\>?.?.?.?<Bg/.NPN^NuNV0. Ю @( gp`B@N^NuNV=n ` n2. 00ngP=n =| ?./. nN\J@g0.Ю @=h n f0.Ю @=P `=n 0.Ю @=P 0. ng n fpN^NuNV.Q/]/Y/U/.Nn>?.?.?.?.?.?.?.BgN>?.?.?.?.?.?.?.?<N>?.?.?.?<Bg/.NP> /.NX|=@>?.?.?.?.?.?.?.?<N>?.?.?.?.?.?.?.?<N0.N^NuNV0. Ю @-h n  n1nN^NuNV.Y?<BgNX.\/.X/.T/./.Nn n> n?( n?( n?BgBgBgBgBgN n> n?( n?( n??<Bg/.NPN^NuNV n> n?( n?( n?BgBgBgBg?<NN^NuNV.Y?<BgNXB@0.Ю @-h .Y?<BgNX.?</.a\>/.adXN^NuNV3*0.|Hм -@=|` nH2n*2RRn nm.+N>B@09+N^NuNV#*(|#*(#*(#+(#+(#+(#(|+> aF3++pN^NuNV>a*pN^NuNV#+3 *>2NN^NuNV3*3 *3 *3*3+3+3+3+3+>3NN^NuNV3*# +>4NN^NuNV3*>5NN^NuNV#+>6N n 0+ n0+ n0+ n0+ B@09+N^NuNV#+# +>ZN n0+B@09+N^NuNV>MN n0+ n 0+ n0+ n0+ B@09+N^NuNV3*# +>NNN^NuNV#+3 *3*3*3*3+3+>*NN^NuNV#+3 *>,N n0+ n0+B@09+N^NuNV#+>nNN^NuNV>oNN^NuNV3*3 *>pN n +B@09+N^NuNV3*3 *>hN n 0+ n0+ n0+ n0+ B@09+N^NuNV3*>kNN^NuNV3e+By+By+3+NN^NuNV#'l#'t .мZ#'x3d+By+3 + n 3+N n 0+#''l#( 't#*'x#('pN^NuNV# 't . м #'x3&+By+By+3+N#( 't#*'xN^NuNVBy(3 (3 +3+By+3+N n 0* n0* n0* n0*N^Nu#+'h"<'hpsNBNu#'|NN/9'|Nu#'|NM/9'|Nu#'|NA/9'|NuNVH *n (n`RJff .JL0N^NuNVH *n(n `op`lp`JgJfHHAJL0N^NuNVH *n (nf .JL0N^NuNVH *n(M`RJf HJL0N^Nu RSCRS2DEFDF2fxDFNDEFDEFDFNRSCONV.RSC[3][Fatal Error !|RSCONV.RSC|File Not Found][ Abort ]\*.\*._crystal>_ctrl_cn _swap_byJ_swap_wo_swap_im_swap_tr*_swap_obt_swap_te_swap_ib^_swap_bb_swap_fsB_swap_fi_close_f_do_convh_do_help _do_mode *_rscv_ru _rscv_te\_rscv_in_main_open_fi_dos_err6_create__get_filz_parse_fd_get_patH_new_ext_do_objR_undo_obp_sel_obj_desel_o_disab_o_objc_xy_disp_ob4_selecter_map_tre_hndl_di:_set_texb_beg_pro_end_pro8_set_prov_crys_if_appl_inB_appl_ex_form_do_form_di_form_al2_form_erT_form_cen_fsel_in_graf_ha_graf_mo._objc_drP_objc_of_rsrc_lo_rsrc_fr_rsrc_ga_wind_ge8_wind_up_v_clsvw_v_opnvw_vqt_attN_vst_hei_gsx1_gsx2_iioff'l_iooff't_pioff'p_pooff'x_vdi_gemdos<_bios,_xbios_strcatL_strcmpv_strcpy_strlen_intin'_intout( _gl_hbox(z_c(|_scrn_ar(_scrn_mf(_ptsin(_ptsout*_gl_wbox*_global*_full*_control*_int_in*_int_out+_gl_hcha+&_work_ou+(_ad_c+_gl_apid+_gem_han+_contrl+_addr_in+_gl_wcha+_vdi_han+_addr_ou+. 4& > $0 4 R l l L 4 6      $         (   "             ,&(&  ,(:   6    8>8$ p< H B.   Vn.0&40P"44>(2 $                  8,,,$$$ P1 `GEM Intel/Motorola Resource ConverterCopyright (c) 1985, 1986Timothy R. OrenSelect Operation:Mode:68000 -> IntelIntel -> 68000Filenames:___FFF->___FFF___FFF->___FFFSymbols:DEF(OFF)DFNHelpConvertQuitGEM Intel/Motorola Resource ConverterCopyright (c) 1985, 1986 Timothy R. OrenThis program converts the internal format of a GEMresource (.RSC) file created on an Intel 8086 or Motorola68000 series processor so that it will run on the othermachine.MODE selects whether the existing resource wascreated on the Atari ST (68000 -> Intel) or on an IBM PCor other Intel based microcomputer (Intel -> 68000).The default file extents are RSC and DEF (for theoptional symbol file) on input, and RS2 and DF2 on output.You may edit these extent names, and select the old (DEF),or new (DFN) symbol format, or disable symbol conversion.ContinueResource Conversion in ProgressReading Resource FileReading Resource FileAligning ImagesSwapping BytesWriting Resource FileReading Definition FileWriting Definition File[2][There is no symbol file for|this resource. Do you wish|to continue the conversion,|choose another name, or stop?][ Continue |New Name|Stop][3][Sorry, there isn't enough|memory to load that resource.][ Stop ]N,$%J cs     ,    H d   Q Q% ?%- )W499  4 > 9 x 4  4 : :X9'2 % %   6  ` symlink rsconv rscvmain,rscvfile,rscvlib link68 [s,u] $1.68k=apstart,$2,aesbind,vdibind,osbind,gemlib if (-e $1.68k) then relmod $1 if (-e $1.prg) rm $1.68K endif . a tW.. a tHOOKHACK011bt X&SGEMEVENT012gt mEFORMMANG013lt HUSRINTR2014rt /UGEMDOS 015wt O6POTPOURI016|t >PCSTPORT017t X ANTIC PUBLISHING INC.,COPYRIGHT 1986. REPRINTED BY PERMISSION. PROFESSIONAL GEM by Tim Oren Column #11 - GEM Hooks and Hacks An Insider's AES Tricks Welcome to the eleventh episode of ST PRO GEM, which is devoted to exploring some of the little documented, but powerful, features of GEM. Like the authors of most complex systems, the GEM programmers left behind a set of "hooks", powerful features which would aid them in enhancing the system later. I am going to lay out a number of these methods which have served me well in making creative use of the AES. You will find that most of them concern the object and form libraries, since I was most involved in those parts of GEM. There are probably many more useful tricks waiting to be found in other parts of GEM, so if you happen onto one, please let me know in the Feedback! Before you begin,be sure to pick up the download for this column: GMCL11.C in DL3 of PCS- 57. POWERFUL OBJECTS The first four tricks all involve augmenting standard AES objects. This is a powerful technique for two reasons. First, you can take advantage of the regular AES object and form libraries to draw and handle most of your objects, so that your program need only process the exceptions. Second, you can use the RCS to copy the special objects into multiple dialogs or resources. These four tricks are Extended Object Types, User- defined Objects, TOUCHEXIT, and INDIRECT. Let's look at each of them in turn. EXTENDED OBJECT TYPES If you look at the AES Object Library documentation, you will notice that the values for the OB_TYPE field in an object are all 32 or less. This means that a number of bits are unused in this field. In fact, the AES completely ignores the top byte of the OB_TYPE field. In addition, the RCS ignores the top byte, but it also preserves its value when an object is read, written, or copied. This gives you one byte per object to use as you see fit. Since the processing of an object or dialog is (so far) in the hands of the AES, the best uses of Extended Types are in flagging methods for initializing an object or handling its value when the dialog completes. For example, you might have several dialogs containing editable numeric fields. The Extended Type of each numeric field could be set to the index of the corresponding value in an array. Then your application's dialog initialization code could scan the object tree for such objects, pick up the appropriate value from the array and convert it to ASCII, storing the result in the resource's string area. When the dialog was finished, another pass could be made to reconvert the ASCII to binary and store away the results in the array. (Note that the map_tree() utility from column #5 will scan an entire resource tree.) Another application is to assign uniform codes to exit buttons in dialogs. If you give every "OK" button an Extended Type of one, and every "Cancel" button an Extended Type of two, then you needn't worry about naming every exit object. Just examine the Extended Type of the object returned by form_do, and proceed accordingly. The catch, of course, is that you have to find a way to enter the Extended Type code in the first place. Version 1.0 of the RCS, as shipped with the Atari developer's kit, makes no provision for this. So you have your choice of two methods for creating the first object with each Extended Type code. First, you can dump out a C source of a resource, insert the new type code by hand, and regenerate the resource with STCREATE. Alternately, you could carefully modify the binary resource using SID. You will probably want to reread the AES object manual, ST PRO GEM #4 and #5, and use the C source as a guide when doing so. In both cases, you should make things easy on yourself by creating a one dialog resource with only a single object other than the root. Version 2.0 of the RCS will let you directly enter an Extended Type, but it has not yet been released for the ST by DRI. Once you have created a prototype extended object by either method, you can use the RCS to propogate it. The safest way is to use the MERGE option to add the modified tree to the resource you are building. Then copy the prototype object via the clipboard to your dialog(s), deleting the extra tree when you are done. If you are using several different extended objects, you can use MERGE and clipboard copies to get them all into one tree which will then become your own object library. The second way of using RCS is easier, but more dangerous. If you want to try the following technique, BACK UP YOUR RCS DISK FIRST! Put simply, the RCS does not care what is in its dialog partbox. It will make copies of anything that it finds there! This gives you the option of using the RCS on ITS OWN RESOURCE in order to add your customized objects to the partbox. To do this, open RCS.RSC from the RCS. Since there is no DEF file, you will get a collection of question mark icons. Use the NAME option to make TREE5 into a DIALOG. Open it, and you will see the dialog partbox. Now you can use the MERGE technique described above to insert your customized objects. Then SAVE the modified resource, and rerun the RCS. Your new objects should now appear in the partbox. If you added several, you may have to stretch the partbox to see them all. You can now make copies of the new objects just like any other part. (Note: DO NOT modify the alert or menu partboxes, you will probably crash the RCS.) USER-DEFINED OBJECTS The one type of object which was not discussed in the earlier articles on AES objects was G_USERDEF, the programmer defined object. This is the hook for creating objects with other appearances beyond those provided by the standard AES. By the way, you should note that the G_PROGDEF and APPLBLK mnemonics used in the AES documents are incorrect; the actual names as used defined OBDEFS.H are G_USERDEF and USERBLK. The RCS does not support the creation of G_USERDEF objects, since it has no idea how they will be drawn by your program. Instead, you must insert a dummy object into your resource where you want the G_USERDEF to appear, and patch it after your application performs its resource load. You must replace the object's existing OB_TYPE with G_USERDEF, though you may still use the upper byte as an Extended Type. You must also change the OB_SPEC field to be a 32-bit pointer to a USERBLK structure. An USERBLK is simply two LONG (32-bit) fields. The first is the address of the drawing code associated with the user defined object. The second is an arbitrary 32-bit value assigned to the object by your application. You can designate objects for conversion to G_USERDEFs in the normal fashion by assigning them names which are referenced one by one in your initialization code. You can also combine two tricks by using the Extended Type field as a designator for objects to be converted to G_USERDEF. Each tree can then be scanned for objects to be converted. There is a short code segment in the download which demonstrates this technique. My usual convention is to define new drawing objects as variants of existing objects, using the Extended Type field to designate the particular variation. Thus an Extended Type of one might designate a G_BUTTON with rounded corners, while a value of two could flag a G_STRING of boldface text. When using this technique, the RCS can be used to build a rough facsimile of the dialog by inserting the basic object type as placeholders. The existing OB_SPEC information can then be copied to the second position in the USERBLK when the object is initialized. One final note before moving on: There is no reason that the USERBLK cannot be extended beyond two fields. You might want to add extra words to store more information related to drawing the object, such as its original type. The AES will call your drawing code whenever the G_USERDEF needs to be drawn. This occurs when you make an objc_draw call for its tree, or when an objc_change occurs for that object. If your user-defined object is in a menu drop-drop, then your drawing code will be called any time the user exposes that menu. Before getting into the details of the AES to application calling sequence, some warnings are in order. First, remember that your drawing code will execute in the AES' context, using its stack. Therefore, be careful not to overuse the stack with deep recursion, long parameter lists, or large dynamic arrays. Second, the AES is NOT re-entrant, so you may not make ANY calls to it from within a G_USERDEF procedure. You may, of course, call the VDI. Finally, realize that drawing code associated with a menu object may be called by the AES at any time. Exercise great care in sharing data space between such code and the rest of the application! When your drawing code is called by the AES, the stack is set up as if a normal procedure call had occured. There will be one parameter on the stack: a 32-bit pointer to a PARMBLK structure. This structure lies in the AES' data space, so do not write beyond its end! The PARMBLK contains 15 words. The first two are the long address of the object tree being drawn, and the third word is the number of the G_USERDEF object. You may need these values if the same drawing code is used for more than one object at a time. Words four and five contain the previous and current OB_STATE values of the object. If these values are different, your drawing code is being called in response to an objc_change request. Otherwise, the active AES call is an objc_draw. Words six through nine contain the object's rectangle on the screen. Remember that you cannot call objc_offset within the drawing code, so you will need these values! The next four words contain the clipping rectangle specified in the active objc_change or objc_draw call. You should set the VDI clip rectangle to this value before doing any output to the screen. The last two words in the PARMBLK contain a copy of the extra 32-bit parameter from the object's USERBLK. If you have followed the method of copying an OB_SPEC into this location, these words will be your pointer to a string, or BITBLK, or whatever. When your drawing routine is done, it should return a zero value to the AES. This is a "magic" value; anything else will stop the drawing operation. The download contains a sample drawing routine which defines one extended drawing object, a rounded rectangle button. You can use this procedure as a starting point for your own User Defined objects. PUT ANYTHING YOU WANT ON THE DESKTOP! In ST PRO GEM #2, I described the use of the WF_NEWDESK wind_set call to substitute your own object tree for the normal green desktop background. If the tree you supply contains User Defined objects, you can draw whatever you want on the desktop! Some of the things you might try are free hand drawings imported in metafile format from EasyDraw, or whole screen bit images generated by Degas. If you do the latter, you will have to store the entire image off screen and blit parts of it to the display as requested. In any case, remember that your desktop drawing code can be called any time that a window is moved, so exercise the same care as with a menu drawer. Also, be aware that making the WF_NEWDESK call does not force an immediate redraw of the desktop. To do that, do a form_dial(3) call for the entire desktop rectangle. THE TOUCHEXIT FLAG The TOUCHEXIT attribute is an alternative to the more usual EXIT. When the TOUCHEXIT bit is set in an object's OB_FLAG word, the form_do routine will exit immediately when the mouse button is pressed with the cursor over the object. Your code can immediately take control of the mouse and display, without waiting for the release of the button. This method is used for generating effects such as slider bars within otherwise normal dialogs. The easiest way to code a TOUCHEXIT handler is to place a loop around the form_do call. If the object number returned is TOUCHEXIT, then the animation procedure is called, followed by a resumption of the form_do (WITHOUT recalling form_dial or objc_draw!). If the object returned is a normal EXIT, the dialog is complete and control flows to the cleanup code. There is one idiosyncrasy of TOUCHEXIT which should be noted. When the AES "notices" that the mouse button has been pressed over a TOUCHEXIT, it immediately retests the button state. If it has already been released, it waits to see if a double click is performed. If so, the object number returned by form_do will have its high bit set. If you don't care about double clicks, your code should mask off this flag. However, you may want to use the double click to denote some enhanced action. For example, the GEM file selector uses a double click on one of the file name objects to indicate a selection plus immediate exit. THE INDIRECT FLAG If the INDIRECT bit is set in an object's OB_STATE word, the AES interprets the 32-bit OB_SPEC field as a pointer to the memory location in which the ACTUAL OB_SPEC is to be found. Like User Defined objects, this capability is not supported by the RCS, so you have to set up the INDIRECT bit and alter the OB_SPEC at run time. The value of INDIRECT is that you can use it to associate an AES object with other data or code. The general technique is to set up a table with a spare 32-bit location at its beginning. Then, when initializing the application's resource, you move the true OB_SPEC into this location, set the INDIRECT flag, and replace the OB_SPEC field with a pointer to the table. The object behaves normally during drawing and form handling. However, if you receive its number from form_do or objc_find, you have an immediate pointer to the associated table, without having to go through a lookup procedure. This technique works well in programs like the GEM Desktop. Each G_ICON object is set up with INDIRECT. Its OB_SPEC goes to the beginning of a data area defining the associated file. The blank location at the beginning of file table is filled up with the former OB_SPEC, which points to a ICONBLK. You can also combine INDIRECT with TOUCHEXIT when creating objects that must change when they are clicked by a user. For instance, a color selection box might be linked to a table containing the various OB_SPEC values through which the program will cycle. Each time the user clicked on the box, the TOUCHEXIT routine would advance the table pointer, copy the next value into the dummy OB_SPEC location at the front of the table, and redraw the object in its new appearance. A programmer who wanted to follow a truly object-oriented "Smalltalkish" approach could use the INDIRECT method to bind AES drawing object to tables of associated procedures or "methods". For instance, one procedure could be flagged for use when the user clicked on the object, one when the object was dragged, one for double-click, and so on. If the table structure was capable of indicating that the true method was stored in another table, a rudimentary form of class inheritance could be obtained. INSTANT CO-ROUTINES We turn to the AES event and message system for this trick. While some languages like Modula 2 provide a method for implementing co-routines, there is no such capability in C. However, we can effectively fake it by using the AES event library. As already seen in an earlier column, an application can write a message to its own event queue using the appl_write call. Usually, this is a redraw message, but there is nothing to prevent you from using this facility to send messages from one routine in your program to another. To set up co-routines using this method, they would be coded as separate procedures called from the application's main event loop. When one of the co-routines wanted to call the other, it would post a message containing the request and any associated parameters into the application's queue and then return. The main loop would find the message and make the appropriate call to the second co-routine. It it was necessary to then re-enter the first co-routine at the calling point, the original message could contain an imbedded reply message to be sent back when the request was complete. A simple switch structure could then be used to resume at the appropriate point. There are two potential problems in using this method. The first is the limited capacity of the application event queue. The queue contains eight entries. While the AES economizes this space by merging redraws and multiple events, it cannot merge messages. Because of this limit, you must be extremely careful when one message received has the potential to generate two or more messages sent. Unless this situation is carefully managed, you can get a sort of "cancer" which will overflow the queue and probably crash your application. The second danger involves race conditions. Message sent by the application are posted to the end of the queue. If other events have occurred, such as mouse clicks or keyboard presses, they will be seen and processed ahead of the application generated message. This implies that you cannot use this method if the program must complete its action before a new user generated event can be processed. THAT'S ALL FOR NOW Hopefully these hints will keep you profitably occupied for a while. ST PRO GEM number twelve will return to the topic of user interfaces. Reaction to the first article on this subject was mostly positive, but a lot of folks wanted to see real code as well. In response to your feedback, there will also be code for implemented your own "mouse sensitive" objects which highlight when the cursor touches them. This will be presented as part of an alternate form manager. UPDATE: ATARI ST I have recently gotten more information on some topics mentioned in earlier articles. These notes will bring you up to date: A number of developers reported that they were unable to get the self-redraw technique described in ST PRO GEM #2 to work. This is usually due to a bug in the appl_init binding in Alcyon C. The value returned from the function, which would normally be assigned to gl_apid, is coming back as garbage. To work around the problem, declare EXTERN WORD gl_apid; in your program and DO NOT assign the value from appl_init. The binding WILL make the assignment. A tip of the hat to Russ Wetmore for this report. The last column mentioned that turning off the clip rectangle while drawing graphics text will speed things up. It turns out that the VDI will also run at the non-clipped speed if the ENTIRE string to be written is within the current clip rectangle. To compound the problem, there is a one-off bug in the detection algorithm for the right edge. That is, the clip rectangle has to be one pixel BEYOND the right edge of the text for the fast write to work. The Feedback in ST PRO GEM #10 mentioned that there are known bugs in the Alcyon C floating point library. In fact, this library has been replaced with a new, debugged version in recent shipments of the Toolkit. If you need to use floating point and have run into this bug, you should be able to get an update from Atari. Also, check the Atari Developer's SIG (PCS-57) for a possible download. In addition, it turns out there is an undocumented feature in Alcyon C which allows you to imbed assembly code in-line. Try using: asm("....."); where the dots are replaced with an assembly instruction. You get one instruction per asm(), one asm() per line. Thanks to Leonard Tramiel for both of the above tidbits. ANTIC PUBLISHING INC.,COPYRIGHT 1986. REPRINTED BY PERMISSION. PROFESSIONAL GEM By Tim Oren Column #12: GEM Events and Program Structure So I fibbed a little. This issue (#12) of ST PRO GEM started out to be another discussion of interface issues. But, as Tolkien once said, the tale grew in the telling, and this is now the first of a series of three articles. This part will discuss AES event handling and its implications for GEM program structure. The following article will contain a "home brew" dialog handler with some new features, and the third will, finally, take up the discussion of interface design, using the dialog handler as an example. (There is no download for this article. The downloads will return, with a vengeance, in ST PRO GEM #13.) ALL FOR ONE, AND ONE FOR ALL A quick inspection of the AES documents shows that there are five routines devoted to waiting for individual types of events, and one routine, evnt_multi, which is used when more than one type is desired. This article will discuss ONLY evnt_multi for two reasons. First, it is the most frequently used of the routines. Second, waiting for one type of event is a bad practice. Any event call turns the system over to the AES and suspends the application and its interaction with the user. In such cases, some "escape clause", such as a timer, should be inserted to revive the program and prompt the user if no event is forthcoming. Otherwise, the application may end up apparently (or actually) hung, with a resulting reboot, and probably a very annoyed user. STARTING AHEAD One possible type of event is a message. Messages are usually sent to the application by the AES, and are associated with windows or the menu. Two previous articles in this series have discussed such messages. ST PRO GEM number two considered window messages, and number seven handled menu messages. You may want to review these topics before proceeding. The actual evnt_multi call is a horrendous thing: ev_which = evnt_multi(ev_flags, btn_clicks, btn_mask, btn_state, r1_flags, r1_x, r1_y, r1_w, r1_h, r2_flags, r2_x, r2_y, r2_w, r2_h, &msg_buff, time_lo, time_hi, &mx, &my, &btn, &kbd, &char, &clicks); Each of the lines in the call relate to a different event, and they will be discussed in the order in which they appear. Note that a call with this number of parameters causes some overhead due to stacking and retrieval of the values. In most cases, this should be of little concern on a machine as fast as the ST. However, where throughput is a concern, such as in close tracking of the mouse cursor, you may want to write a customized binding for evnt_multi which dispenses with the parameter list. This can be accomplished by maintaining the values in a static array and moving them as a block into the binding arrays int_in (for all values but &msg_buff), and addr_in (for &msg_buff). Note that you may NOT simply leave the values in int_in; other AES bindings reuse this space. Ev_flags and ev_which are both 16-bit integers composed of flag bits. Bits set in ev_flags determine which event(s) the call will wait for; those set in ev_which indicate what event(s) actually occurred. Both use the following flag bit mnemonics and functions: 0x0001 - MU_KEYBD - Keyboard input 0x0002 - MU_BUTTON - Mouse button(s) 0x0004 - MU_M1 - Mouse rectangle #1 0x0008 - MU_M2 - Mouse rectangle #2 0x0010 - MU_MESAG - AES message 0x0020 - MU_TIMER - Timer The appropriate mnemonics are ORed together to create the proper ev_flags value. There is one common pitfall here. Notice that multiple events may be reported from one evnt_multi. Event merging is performed by the AES in order to save space on the application's event queue. If events have been merged, more than one bit will be set in the ev_which word. Your application must check ALL of the bits before returning to a new evnt_multi call. If you don't do this, some events may be effectively lost. The first event to be considered is the mouse button. This is probably the most difficult event to understand and use, and it has one major shortcoming. The parameter btn_clicks tells GEM the maximum number of clicks which you are interested in seeing. This value is usually two, if your program uses the double-click method, or one if only single clicks are used. The AES returns the number of clicks which caused the event through &clicks, which must be a pointer to a word. GEM determines the number of clicks by the following method. When the first button-down is detected, a time delay is begun. If another complete button-up, button-down cycle is detected before the time expires, then the result is a double click. Otherwise, the event is a single click. Note that the final state of the buttons is returned via &btn, as described below. By checking this final state, you may determine whether a single click event ended with the button up (a full click), or with the button still down (which may be interpreted as the beginning of a drag operation). Double clicking is meaningless, and not checked, if the evnt_multi is waiting on more than one button (see below). The double-click detection delay is variable, and may be set by your program using the call ev_dspeed = ev_dclick(ev_dnew, ev_dfunc); Ev_dfunc is a flag which determines the purpose of the call. If it is zero, the current double click speed is returned in ev_dspeed. If ev_dfunc is non-zero, then ev_dnew becomes the new double-click speed. Both ev_dspeed and ev_dnew are words containing a "magic number" between zero and four. Zero is the slowest (i.e., longest) double-click, and four is the fastest. (These correspond to the slow-fast range in the Desktop's Preferences dialog.) In general, you should not reset the click speed unless specifically requested, because such a change can throw off the user's timing and destroy the hand/eye coordination involved in using the mouse. GEM was originally designed to work with a single button input device. This allows GEM applications to function well with devices such as light pens and digitizing tablets. However, some features are available for dealing with multi-button mice like the ST's. The evnt_multi parameters btn_mask and btn_state are words containing flag bits corresponding to buttons. The lowest order bit corresponds to the left-most button, and so on. A bit is set in the btn_mask parameter if the AES is to watch a particular button. The corresponding bit in btn_state is set to the value for which the program is waiting. The word returned via &btn uses the same bit system to show the state of the buttons at completion. It is important to notice that all of the target states in btn_state must occur SIMULTANEOUSLY for the event to be triggered. Note the limiting nature of this last statement. It prevents a program from waiting for EITHER the left or right button to be pressed. Instead, it must wait for BOTH to be pressed, which is a difficult operation at best. As a result, the standard mouse button procedure is practically useless if you want to take full advantage of both buttons on the ST mouse. In this case, your program must "poll" the mouse state and determine double-clicks itself. (More on polling later.) By the way, many designers (myself included) believe that using both buttons is inherently confusing and should be avoided anyway. MOUSE RECTANGLES One of GEM's nicer features is its ability to watch the mouse pointer's position for you, and report an event only when it enters or departs a given screen region. Since you don't have to track the mouse pixel by pixel, this eliminates a lot of application overhead. The evnt_multi call gives you the ability to specify one or two rectangular areas which will be watched. An event can be generated either when the mouse pointer enters the rectangle, or when it leaves the rectangle. The "r1_" series of parameters specifies one of the rectangles, and the "r2_" series specifies the other, as follows: r1_flag, r2_flag - zero if waiting to enter rectangle, one if waiting to leave rectangle r1_x, r2_x - upper left X raster coordinate of wait rectangle r1_y, r2_y - upper left Y raster coordinate of wait rectangle r1_w, r2_w - width of wait rectangle in pixels r1_h, r2_h - height of wait rectangle in pixels Each rectangle wait will only be active if its associated flag (MU_M1 or MU_M2) was set in ev_flags. There are two common uses of rectangle waits. The first is used when creating mouse-sensitive regions on the screen. Mouse- sensitive regions, also called "hot spots", are objects which show a visual effect, such as inversion or outlining, when the mouse cursor moves over them. The items in a menu dropdown, or the inversion of Desktop icons during a drag operation, are common examples. Hot spots are commonly created by grouping the sensitive objects into one or two areas, and then setting up a mouse rectangle wait for entering the area. When the event is generated, the &mx and &my returns may be examined to find the true mouse coordinates, and objc_find or some other search will determine the affected object. The object is then highlighted, and a new wait for exiting the object rectangle is posted. (ST PRO GEM #13 will show how to create more complex effects with rectangle waits.) The second common use of rectangle waits is in animating a drag operation. In many cases, you can use standard AES animation routines such as graf_dragbox or graf_rubberbox. In other cases, you may want a figure other than a simple box, or desire to combine waits for other conditions such as keystrokes or collision with hotspots. Then you will need to implement the drag operation yourself, using the mouse rectangles to track the cursor. If you want to track the cursor closely, simply wait for exit on a one pixel rectangle at the current position, and perform the animation routine at each event. If the drag operation only works on a grid, such as character positions, you can specify a larger wait rectangle and only update the display when a legal boundary is crossed. MESSAGES The &msg_buff parameter of evnt_multi gives the address of a 16 byte buffer to receive an AES message. As noted above, I have discussed standard AES messages elsewhere. The last column also mentioned that messages may be used to simulate co-routines within a single GEM program. A further possibility which bears examination is the use of messages to coordinate the activities of multiple programs. In single-tasking GEM, at least one of these programs would have to be a desk accessory. In any such use of the GEM messages, you should pay careful attention to the possibility of overloading the queue. Only eight slots are provided per task, and messages, unlike events, cannot be merged by the AES. TIMER The timer event gives you a way of pacing action on the screen, clocking out messages, or providing a time-out exit for an operation. Evnt_multi has two 16-bit timer input parameters, time_hi and time_lo, which are the top and bottom halves, respectively, of a 32-bit millisecond count. However, this documented time resolution must be taken with a grain of salt on the ST, considering that its internal clock frequency is 200Hz! The timer event is also extremely useful for polling the event queue. A "poll" tests the queue for completed events without going into a wait state if none are present. In GEM, this is done by generating a null event which always occurs immediately. A timer count of zero will do just that. Therefore, you can poll for any set of events by specifying them in the evnt_multi parameters. A zero timer wait is then added to ensure immediate completion. Upon return, if any event bit(s) OTHER than MU_TIMER are set, a significant event was found on the queue. If only MU_TIMER is set, the poll failed to find an event. KEYBOARD There are no input parameters for the keyboard event. The character which is read is returned as a 16-bit quantity through the &char parameter. For historical reasons, the codes which are returned are compatible with the IBM PC's BIOS level scan codes. You can find this character table in Appendix D of the GEM VDI manual. In general, the high byte need only be considered if the lower byte is zero. If the low byte is non-zero, it is a valid ASCII character. Evnt_multi also returns the status of several modifier keys through the &kbd parameter. This word contains four significant bits as follows: 0x0001 - Right hand shift key 0x0002 - Left hand shift key 0x0004 - Control key 0x0008 - ALT key If a bit is one, the key was depressed when the event was generated. Otherwise, the key was up. Since the state of these keys is already taken into account in generating the &char scan code, the &kbd word is most useful when creating enhanced mouse functions, such as shift-click or control-drag. RANDOM NOTES ON EVENTS Although the &mx, &my, &btn, and &kbd returns are nominally associated with particular event types, they are valid on any return from evnt_multi and reflect the last event which was merged into that return by the AES. If you want more current values, you may use graf_mkstate to resample them. Whichever method you choose, be consistent within the application, since the point of sampling has an effect on mouse and keyboard timing. Although this and preceding columns have been presented in terms of a GEM application, the event system has many interesting implications for desk accessories. Since the AES scheduler uses non-preemptive dispatching, accessories have an event priority effectively equal to the main application. Though "typical" accessories wait only for AC_OPEN or AC_CLOSE messages when in their quiescent state, this is not a requirement of the system. Timer and other events may also be requested by an accessory. (Indeed, there is no absolute requirement that an accessory advertise its presence with a menu_register call.) The aspiring GEM hacker might consider how these facts could be used to create accessories similar to "BUGS" on the Mac, or to the "Crabs" program described in the September, 1985 issue of Scientific American. EVENTS AND GEM PROGRAM STRUCTURE Although the evnt_multi call might seem to be a small part of the entire GEM system, its usage has deep implications for the structure of any application. It is generally true that each use of evnt_multi corresponds to a mode in the program. For instance, form_do contains its own evnt_multi, and its invocation creates a moded dialog. While the dialog is in progress, other features such as windows and menus are unusable. The graf_dragbox, graf_rubberbox, and graf_slidebox routines also contain evnt_multi calls. They create a mode which is sometimes called "spring-loaded", since the mode vanishes when some continuing condition (a depressed mouse button) is removed. In consequence, a well-designed, non-modal GEM program will contain only one explicit evnt_multi call. This call is part of a top-level loop which decodes events as they are received and dispatches control to the appropriate handling routine. The dispatcher must always distinguish between event types. In programs where multiple windows are used, it may also need to determine which local data structure is associated with the active window. This construction is sometimes called a "push" program structure, because it allows the user to drive the application by generating events in any order. This contrasts with the "pull" structure of traditional command line or menu programs, where the application is in control and demands input at each step before it proceeds. "Push" structure promotes consistent use of the user interface and a feeling of control on the part of the user. The next ST PRO GEM column will look more closely at events and program structure in the context of a large piece of code. The code implements an alternate dialog handler, incorporating mouse-sensitive objects as part of the standard interface. Since this code is "open", it may be modified and merged with any application's main event loop, resulting in non-modal dialogs. ANTIC PUBLISHING INC.,COPYRIGHT 1986. REPRINTED BY PERMISSION. PROFESSIONAL GEM By Tim Oren Column #13 - A New Form Manager This is the 13th installment of ST PRO GEM, and the first devoted to explaining a large piece of code. This article is also the second in a series of three concerning GEM user interface techniques. The code is an alternate form (dialog) manager for GEM. It is stored as GMCL13.C in DL3 of PCS-58. You should go and download it now, or you will have no hope of following this discussion. What is unique about this version of the form manager? First, it implements all of the functions of the standard GEM form_do routine, as well as adding a "hot spots" feature which causes selectable objects to become mouse-sensitive, just like the entries in menu dropdowns. The second (and obvious) difference is that this form manager is provided in source code form. This gives you the freedom to examine it and change it to suit your own needs. I have several purposes in presenting this code. It is intended as an example of GEM program structure, and an application of some of the techniques presented in earlier columns. It is also relevant to the continuing thread discussing the necessity of feedback when constructing a user interface. Also, this issue represents the beginning of a fundamental change in direction for ST PRO GEM. Since this column began last August, Atari ST developers have increased not only in number, but in sophistication. A number of books, as well as back issues of ST PRO GEM, are now available to explain the basics of the ST and GEM. Quick answers to common questions are available here on Compuserve's PCS-57 from Atari itself, or from helpful members of the developer community. To reflect these changes, future columns will discuss more advanced topics in greater depth, with an accent on code which can be adapted by developers. The program presented in this issue will be a basis for a number of these discussions. There will be fewer "encyclopediac" treatments of AES and VDI function calls. Finally, to give me the time required to create this code or clean up tools from my "bag of tricks", ST PRO GEM will probably convert to a monthly format around the start of summer. ON WITH THE SHOW. Taking your listing in hand, you will quickly notice two things. First, this program uses the infamous portability macros, so that it may be used with Intel versions of GEM. Second, the routines are arranged "bottom up", with the main at the end, and subroutines going toward the beginning. (This is a carry-over from my days with ALGOL and PASCAL.) You should now turn to the form_do entry point near the end of the code. One change has been made in the standard calling sequence for form_do. The starting edit field is now a pointer to a value, rather than the value itself. The new form_do overwrites the initial value with the number of the object being edited when the dialog terminated. Using this information, your program can restore the situation when the dialog is next called. As before, if there is NO editable field, the initial value should be zero. There are several local variables which maintain vital state information during the dialog interaction. Edit_obj is the number of the editable object currently receiving keystrokes. Next_obj is set when the mouse is clicked over an object. If the object happens to be editable, next_obj becomes the new edit_obj. Three variables are associated with the "hot-spot" feature. If hot_mode is set to M1_ENTER, then the mouse is outside the area of the dialog. If it equals M1_EXIT, then the mouse is currently in the dialog. If it is in the dialog, hot_obj indicates whether there is an active "hot" object. If its value is NIL (-1), then there is no active object. Otherwise, it is equal to the number of the object which is currently "hot", that is, inverted on the screen. Finally, hot_rect is the current wait rectangle. If the mouse is outside of the window, then the wait rectangle equals the dialog's ROOT. If there is a current hot object, then hot_rect equals that object's screen rectangle. If the mouse is in the dialog, but not within a hot object, then the wait rectangle defines the area within which no further collision checks are necessary. This is arrived at through an algorithm explained below. Form_do's initialization code sets up the hot-spot variables to trigger if the mouse is within the dialog. It also sets starting values for edit_obj and next_obj which will cause the edit startup code to be activated. The main portion of form_do is a loop, exhibiting the type of event driven structure discussed in the last column. Before entering the evnt_multi wait, the status of next_obj and edit_obj are checked to see if a new object should be initialized for editing. If so, objc_edit is called with the EDINIT function code. NOTE: the objc_edit calling sequence used in this program differs from the one given in the AES manual, which is incorrect! You should check the bindings you are using to be sure they will work with this code, and modify as necessary. The evnt_multi is set up to wait for a mouse click (single or double), for a keyboard input, or for the mouse to make a "significant" movement, as discussed above. Notice that since form_do is used as a subroutine, it does not handle messages which are normally processed by the main loop of your application. Notice that this creates a mode, and that this routine as written handles modal dialogs. You could, however, use this code as the basis for a non-modal dialog handler by drawing the dialog within a window, and combining the main loop of form_do with the main loop of your application. (This possibility may be examined in future columns. In the meantime, it is left as an exercise for the reader.) The event bit vector is returned to the variable "which". Since events are not mutually exclusive, each possible event type must be examined in turn before returning to the evnt_multi call. The form manager's event handling routines are form_hot, for mouse rectangle event, form_keybd, for character input, and form_button, for mouse clicks. Form_keybd and form_button are allowed to terminate the dialog by returning a value of false to the loop control variable "cont". If termination is imminent, or the user has clicked on a new editable object, objc_edit is called with EDEND to remove the cursor from the old object. The normal flow of control then returns to edit setup and evnt_multi. A few cleanup actions are performed upon termination. If the terminating object (stored in next_obj) is not the same as the hot_obj, then a race condition has occured and the hot object must be cleared with objc_toggle before exiting. After this test, the final edit_obj value is passed back via the parameter, and the terminating object is returned as the function value. RELAXEN UND WATCHEN DAS BLINKENLICHTEN. Form_hot is responsible for maintaining on-screen hot-spots, and correctly updating the internal hot-spot variables. It is about halfway through the listing. The first action in form_hot is to determine if the mouse has just exited an object which is "hot. In this case, objc_toggle is called to unhighlight the object and reset the SELECTED flag. The current mouse position is passed to form_hot by form_do. It is checked against the root rectangle of the dialog to see if the mouse is inside the dialog. If not, the program must wait for it to re-enter, so form_hot sets the rectangle and waiting mode accordingly, and returns NIL as the new hot_obj. When the mouse is within the dialog, a regular objc_find call determines the object at which it is pointing. For an object to be mouse-sensitive, it must be SELECTABLE and not DISABLED. If the found object meets these tests, the mouse will "hover" over the object, waiting to leave its screen rectangle. Since the object might already be SELECTED (and hence drawn reversed), this is checked before objc_toggle is called to do the highlighting and selection of the object, which becomes the hot_obj. (If the object was already SELECTED, the hot_obj becomes NIL.) The toughest condition is when the mouse is within the dialog, but not over a mouse-sensitive object. The regular GEM event structure will not work, because it can only wait on two rectangles, and there may be many more selectable objects in a dialog tree. I have found a way around this limitation using a combination of the map_tree utility (introduced in ST PRO GEM #5) with the principle of visual hierarchy in object trees. In summary, the algorithm attempts to find the largest bounding rectangle around the current mouse position, within which there are no mouse-sensitive objects. It starts with a rectangle equal to the dialog root, and successively "breaks" it with the rectangle of each mouse-sensitive object. The next few paragraphs examine this method in detail. Since C lacks the dynamic scoping of LISP, from which map_tree was derived, it is necessary to set up some globals to be used during the rectangle break process. Br_rect is the GRECT of the current bounding rectangle. Br_mx and br_my hold the current mouse position. Br_togl is a switch which determines whether the next break will be attempted horizontally or vertically. After initializing these variables, form_hot uses map_tree to invoke the break_obj routine for every object in the dialog. Break_obj first intersects the rectangle of the object with the current bounding rectangle. If they are disjoint, then neither the object nor any of its offspring can possible affect the operation, so FALSE is returned, causing map_tree to ignore the subtree. The object is next checked to see if it is mouse-sensitive. As before, it must be SELECTABLE and not DISABLED, and it must not be hidden (this was checked automatically by objc_find before). If these conditions are met, then the object intrudes into the current bounding rectangle. To maintain the desired condition, part of the rectangle must be removed or "broken away". In many cases, the break operation can be done either horizontally or vertically. Since we have no prior information as to which way the mouse will move next, break_obj uses the br_togl flag to alternate which direction it will try first. This should yield the most nearly square rectangle. The break_x and break_y routines are very similar. In each case, the segment occupied by the breaking object is compared to the point occupied by the mouse. If the point is within the segment, there is no possible break in this dimension, and FALSE is returned. If the point lies outside the segment, then the rectangle may be successfully broken by reducing this dimension. This is done, and TRUE is returned to report success. The break_y routine also employs a look-ahead test to prevent a possible infinite loop. It is conceivable, though not likely, that someone might nest a non-SELECTABLE object completely within another SELECTABLE object(s). If the mouse point is within such an object, the algorithm will not be able to select a break dimension. In the current version, the mouse rectangle is simply forced to a single pixel for this case. (Note that is is NOT sufficent to simply wait on the non-selectable object's rectangle, since other SELECTABLE objects may overlap it and follow it in tree order.) Since map_tree examines all possible objects, br_rect will be the correct bounding rectangle at completion. Note that you can readily adapt this technique to other uses, such as hot-spotting while dragging objects. It is much less demanding of CPU resources than other methods, such as repetitive objc_finds. WHAT A CHARACTER! The form_keybd routine acts as a filter on character input. When it recognizes a control character, it processes it and zeroes the keyboard word. Other chararacters are passed on to objc_edit to be inserted in edit_obj. If there is no editing object, the character goes to the bit bucket. The form_keybd given implements the standard GEM functionality with two minor additions. First, a carriage return in a dialog with no DEFAULT exit object is taken as a tab. This allows to be used "naturally" in dialogs with several lines of text input. Second, tabs and backtabs "wrap around" from top to bottom of the dialog, and are done by "walking the tree", rather than relying on the LASTOB flag to signal the end of the dialog. This allows the new form manager to handle dialog trees which are not contiguous in memory. The code sets up several global variables for use by mapped functions. Fn_obj is the output from both find_tab and find_def. Fn_dir is an input to find_tab. Fn_last, fn_prev, and fn_last are used while searching for tab characters. A carriage return results in a search of the entire tree, using map_tree and find_def, for an object with its DEFAULT flag set. Its SELECTED flag is set and it is inverted on the screen to indicate the action taken. Form_keybd returns a FALSE to force termination of the main form_do loop. If no DEFAULT is found, control passes to the tab code. The tabbing procedure is somewhat complicated because the same code is used for forward and backward tabbing. The old value of edit_obj (the object being tabbed FROM) is placed into fn_last. Fn_dir is set to one for a forward tab, and zero for a backward tab. The general strategy is to scan the entire tree for EDITABLE objects, always saving the last one found in fn_prev. When tabbing forward fn_last is checked against fn_prev. A match indicates that the current object is the one desired. When tabbing backward the current object is checked against fn_last. If they match, fn_prev is the desired object. This procedure requires two passes when the tab "wraps around" the tree, that is, when the desired object as at the opposite end of the traverse from the old editing object. The result of the tab operation is written back into form_do's next_obj parameter, and becomes the new editing object at the beginning of the next loop. BUTTON DOWN. The form_button procedure is lengthy because it must recognize and handle mouse clicks on several types of objects: EDITABLE, SELECTABLE, and TOUCHEXIT. The first section of code rejects other objects, which cannot accept a click. The next piece of form_button makes a special check for a double click on a TOUCHEXIT object. This will cause the high bit of the returned object number to be set. (By the way, this also occurs in the standard form_do.) This flag allows user dialog code to perform special processing on the object. The largest piece of form_button handles the various cases in which the SELECTABLE flag may be set. Setting the RBUTTON (radio button) flag causes all of the object's siblings in the tree to be deselected at the time the object is clicked. The do_radio routine uses the get_parent utility to find the ancestor, and then performs the deselect/select operation. If the SELECTABLE object is not TOUCHEXIT, then graf_watchbox is used to make sure that the mouse button comes back up while it is within the object. Otherwise, the click is cancelled. Care is necessary here, since the hot-spot code may have already set the SELECTED flag for the object. (We cannot be sure of this, for a race condition may have occurred!) If the SELECTABLE object is TOUCHEXIT, then the application has requested that form_do exit without waiting for the button to go back up. In both this and regular form_do, TOUCHEXIT objects are used when you want to provide immediate response (animation) within the context of a dialog. The final parts of form_button do cleanup. If the clicked object was already hot-spotted, hot_obj must be reset to NIL, otherwise form_do will carefully unselect the object which has just been selected! If the EXIT or TOUCHEXIT flags are in force, form_button returns FALSE to force the completion of form_do. For EDITABLE objects, next_obj is left intact to replace edit_obj during the next loop. Otherwise, next_obj has done its job and is zeroed, and form_button returns TRUE for continuation. This concludes the tour of the alternate form_do. The best cure for any confusion in this explanation is to compile the code into an application and watch how it runs with different resources, or attack it with a debugger. OPERATORS ARE STANDING BY. I encourage you to modify this code to meet your particular needs and incorporate it into your application. I would like to request than anyone who comes up with significant improvements (or bug fixes) send them to me so they can be made generally available. You can do this via the ANTIC ONLINE Feedback, or by sending E-mail to 76703,202. Speaking of Feedback, I would also like comments on the proposed change of direction for the column, and more suggestions for future topics. The next installment will be a further discussion of interface design. Topics now queued for future articles include the file selector and DOS error handling, a new object editor, and customized drag box and rubber box routines. Discussions on VDI workstations and printer output are on hold pending release of the GDOS by Atari. If there are items which you want to appear here, you must let me know! ANTIC PUBLISHING INC.,COPYRIGHT 1986. REPRINTED BY PERMISSION. PROFESSIONAL GEM By Tim Oren Column #14 - USER INTERFACES, Part 2 This issue of ST PRO GEM (#14) continues the discussion of user interface design which began in episode eight. It begins where we left off, with a further treatment of the mode problem, and proceeds into topics such as visual grammar and layered interfaces. Note that there is no download for this column. The downloads will return with the next issue, a discussion of using the GEM DOS file system within a GEM application. Specifically, it will include sample code for using the file selector, the GEM form_error alerts, and some utilities for manipulating file and path names. There will also be a feedback section. The following two columns will be devoted to "graphics potpourri", a collection of small but useful GEM utilities such as popup menus, string editing, and source code for drag and rubber box operations. MODES AGAIN. If a program is modeless, it acts predictably, which turns out to be very important. On the other hand, a good definition for "modes" is hard to find. In column eight, I suggested that a mode exists when you cannot use all of the capabilities of the program without performing some intermediate step. If this is less than clear, here are two alternate definitions offering different views of the problem. THE "TWO USER TEST". Consider the following thought experiment: Imagine that your ST (and GEM) had two mice, two cursors, and two users. Could they both effectively use the program at the same time? If so, the application is modeless. If there are points where one user can be "locked out" by the actions of the other, then a mode exists at that point. Let's consider some examples of this test. In any program which uses the GEM menu system, one user could stop the other by touching a menu hotspot and dropping a menu. This constitutes an inherent mode in the GEM architecture. On the GEM Desktop, two users could open windows and view files without interference. However, as soon as one person tries to delete a file (assuming the verify option is on), the other is brought to a halt as a dialog appears. Thus, we have found a modal dialog. In many "Paint-type" programs, such as MacPaint, PC Paint, and GEM Paint, two artists could co-exist quite well, utilizing the on-screen palette and tool selection. Of course, these programs also contain modal dialogs for such operations as file and brush shape selection. In contrast, consider the paint program DEGAS for the ST. Here, two artists could only work together as long as neither wanted to change tool or color. Then the display would have to be flipped to the selection screen, stopping the other user. This is a mode in the DEGAS interface. (By the way, this test is not just academic. The grand-daddy of all mouse based systems, NLS, demonstrated by Doug Englebart in 1968, had two mice and two users, one of whom was physically remote. Cooperative techniques such as this are still largely unexplored and unexploited.) ONE LINER. Here's a terse definition by Jef Raskin: A program is modeless if a given action has one and only one result. Again, let's run a few examples. The menu dropdowns are clearly modal by this definition. Before the menu was activated, window control points could be activated with a click. However, when the dropdown is visible, a click action is interpreted as a menu selection or a dismissal of the dropdown. Similarly, dialogs are modal because the action of moving the mouse into the menu bar no longer causes the dropdown to appear. I am typing this using the First Word editor program. It has a nice desktop level box full of characters where I can click to get symbols which the ST keyboard won't produce. However, if I invoke the find or replace string dialog, the click-in-the-box action doesn't work anymore. This is a mode in the First Word interface. Finally, consider an "old style" menu program, the kind where you type in the number of the desired action from a list. Since the number "2" might mean "Insert the record" in one menu, and "Purge the file" in another, such a program is clearly modal by Raskin's definition. These three definitions say almost the same thing, but from different viewpoints. Depending on the situation, one or the other may be more intuitive for you. The goal of this type of analysis is to root out unnecessary modes, and to make sure that those which remain only appear when requested by the user, offer some visual cue such as a rubber line or standard dialog box, and are used consistently throughout the application. PREDICTABILITY FOREVER AND EVER AND EVER. As Raskin's definition makes clear, when the modes go away, the interface becomes predictable. Predictability leads to the formation of habits of use. Habits reduce "think time" and become progressively faster due to the Power Law of Practice discussed in column eight. This is exactly what we want! There is another benefit of predictability. A habit learned in one part of a program with a consistent interface can be transferred and used elsewhere in the application. If several programs share the same style of interface, the same habits can be used across a complete set of products. Learning time for the new functions becomes shorter, and the user is more likely to use the new feature. IS A BOGEYMAN! Most casual users are scared silly of computers and programs. (If you have any doubt, eavesdrop on a secretary with a new word processor, or the doctor's receptionist coping with an insurance data entry program.) In most cases, they have a right to be frightened. Even experienced programmers, prone to toss the manuals and hack away, know that moderate paranoia is the best way to deal with an unknown program. How must this feel to someone whose ability to perform (or lose) their job depends on an unpredictable (aha!) black box. So here's another way in which predictability works. But to produce a truly fearless user, we need other qualities as well. One is robustness, meaning that the program will not crash given normal or even bizarre actions by the user. Another is feedback, which shuts off invalid options, reinforces correct actions, and gives reassurance that an operation is proceeding normally. Finally, we need forgiveness, in the form of inverse operations or Undo options, when the inevitable mistake is made. The ultimate goal is make the program discoverable. This means the user should be able to safely "wing it" after a short session with the application and its interface. This practice ought to be considered the norm anyway, since the manual is always across the office or missing when an esoteric and half-forgotten feature is needed. If it is possible to muddle through such a situation by trial and error, without causing damage, the immediate problem will be solved, and the user will gain confidence. GOOD GRAMMAR OR... So exactly what are these habits that are supposed to be so helpful? One of the most useful patterns is a consistent command grammar for the program. This may sound strange, since we have supposedly abandoned command line interfaces in the graphics world, but in fact, the same type of rules apply. For instance, in the world of A> we might issue the command: copy a:foobar.txt b: By analogy to Englist grammar, this command contains a verb, "copy", a file as subject: "a:foobar.txt", and a location as an object: "b:". The equivalent GEM Desktop operation is: - Move mouse to foobar.txt icon in a: window - Press mouse button - Move mouse to b: icon - Release mouse button The operation can be described as a select-drag-drop sequence, with the select designating the subject file, the drag denoting the operation (copy), and the location of the drop showing the object. A grammar still exists, but its "terminal symbols" are composed of mouse actions interpreted in the context of the current screen display, rather than typed characters. One useful way to analyze simple grammars, including those used as command languages, is to separate them into prefix, postfix, and infix forms. In a prefix grammer, the operation to be performed precedes its operands, that is, its subject(s) and object(s). The DOS copy command given above is an example of a prefix command. LISP is an example of a language which uses prefix specification for its commands. Postfix grammars specify the action after all of the operands have been given. This command pattern is familiar to many as the way in which Hewlett-Packard calculators work. FORTH is an example of a language which uses a postfix grammar. Infix notation places the verb, or operator, between its subject and object. Conventional algebraic notation is infix, as are most computer languages such as C or PASCAL. The example GEM command given above is also infix, since the selection of a subject file preceded the action, which was followed by the designation of an object. The "standard" GEM command grammar, as used in the products produced by Digital Research, is in fact infix. This is not to say that GEM enforces such a convention, or that it is rigorously followed. However, when there is no pressing reason for a change, adoption of an infix command grammar will make your application feel most like others which users may have seen. The general problem of specifying a graphic command language can be difficult, but much of the problem has already been handled on the ST. Part of the solution is by constraint: the input and output hardware of the ST are predefined, so most developers will not need to worry about choosing a pointing device or screen resolution. The other part of the standard solution is the GEM convention for mouse usage. I am going to review these rules, and then describe of the situations in which they have been bent, and finally some alternate approaches which may prove useful to some developers. SPECIFYING A SUBJECT. There are really two sets of methods for designating what is to be affected by an operation. One set is used when distinct objects are to be affected. Examples are file and disk icons in the Desktop and trees in the RCS. Another set of designation methods is used when continuous material, such as text or bit images, is being handled. When dealing with objects, a single mouse click (down and up) over the object selects it. The application should show that the selection has occurred by changing the appearance of the object. The most common methods are inverting the object, or drawing "handles" around it. Many operations allow "plural", or multiple object, selections. The GEM convention is that a click on an object while the shift key is held down extends the selection by adding that object. If the shift-clicked object was already selected, it is deleted from the selection list. Another way to select multiple objects is to use a "rubber box" to enclose them. This operation begins with drag on a part of the view where no object is present. The application then animates a rubber box on the screen as long as the mouse button is held down. When the button is released, all objects within the current extent of the box are selected. A shift-drag combination could be used to add the objects to an existing selection list. Selecting part of a text or bit plane display is also done with a rubber box. Since there are no "objects" in the view, any mouse drag is interpreted as the beginning of a selection operation. In the simplest case, a bit plane, the rectangle within the box when the button is released is the selected extent. When the underlying data has structure, such as words and lines of text, the display should reflect this fact during the selection operation. Typically, text selection is indicated by inversion of the characters rather than a rubber box. The selection extends along the starting line so long as the mouse stays within the line. If the mouse move off the starting text line, the implied selection is all characters between the starting character and the character currently under the mouse, which is not necessarily a rectangular area. An extended "plural" selection may be supported in text editing. The use of the shift key is also conventional in this application. ACTION. With the subject designated, the user can now choose an operation. In many cases, this will be picked from the menu, in which case the entire command is complete. Some menu selections will lead to dialogs, in which the interaction methods are regulated by the GEM form manager. When the command is completed, it is often helpful if the application leaves the objects (or areas) selected and ready for another operation. A single click away from any object is interpreted as cancelling the selections. Many operations are indicated by gestures on the screen. Usually, this is some variant of a drag operation. The interpretation of the gesture may depend on the type and location of the selected subject, which part of it is under the mouse, and in what location the drag terminates. "Handles" are small boxes or dot displayed around an object when it is selected. A drag beginning with the mouse on a handle is usually interpreted as a resizing operation, if this is appropriate. The pointing finger mouse form is displayed to indicate the operation in progress, and a rubber version of the object is animated on the screen to show the user the result if the button were released. In some cases, where an underlying "snap" grid exists, the animated object may change size in discrete steps. Dragging a non-handle area of a selected object is usually interpreted as the beginning of a move function. In most applications, a move of a single object may be started without pre-selection. Simply beginning the drag on the object is taken to imply selection. The spread hand, or "grabber", mouse form is typically displayed during a drag operation. Dragging may denote copying or movement, or more complex functions such as instantiation or generalization. The operation implied by movement on the screen will differ among applications, and often within the same application, depending on target location. This target is the recipient of the command's action, or its object, in an English grammar sense. For example, a drag from window to window in the Desktop denotes a copy. On the other hand, dragging the same icon to the trashcan deletes it completely. Dragging an object from the RCS partbox to the editing view creates a new copy of that prototype object. Dragging the same object within the edit view simply changes its placement. There are some mouse actions which are conventional "abbreviations". A double click on an object is interpreted as both a selection and an action. Usually, the double click action is the same as the Open entry in the "File" menu. When the usual interpretation of a drag is movement, then shift-drag may be used as an enhanced varient implying copying. For instance, shift-dragging an object within the RCS editing window makes a copy of the object and places it in the final location. To return to the beginning of this discussion, the reason for adopting these conventional usages is to build an interface that promotes habits. Particularly, a standard grammar for giving commands helps answer the question "What comes next?". It breaks the user's actions into logical phrases, or chunks, which may be thought of a whole, rather than one action at a time. DIFFERENT FOLKS, DIFFERENT STROKES. There are always exceptions to a rule, or so it seems. In this case, consistency of the interface grammar is sometimes traded off against consistency of metaphor, preservation of screen space, and "fast path" methods for experts. One example is the use of "tools" in Paint and Draw programs. In such programs, an initial click is made on a tool icon, denoting the operation to be applied to all following selections. This is an prefix style of grammar, and stands in contrast to the usual method of selecting subject object(s) first. Because of this contrast, it is sometimes called "moding the cursor". (Try applying the tests above to be sure it really is a mode.) In these cases, there are two reasons for accepting the nonstandard method. The first is consistency of metaphor. The "user model" portrayed in the programs is an artist's work table, with tools, palette, and so on. The cursor moding action is equivalent to picking up a working tool. The second reason is speed. In a Paint program, the "canvas" is often modified, and speed in creating or changing the bits is important. In more object oriented applications such as Desktop or RCS, the objects are more persistent. Speed is then more essential when adding or changing properties of the objects. When command styles are mixed in this fashion, you must design very carefully to avoid conflicts or apparent side-effects in the command language. For example, in GEM Draw picking an action from the Edit menu cancels the current cursor mode without warning. Confusion from such side-effects may cancel out the benefits of the mixed grammar. The subject of command further attention. While the novice approaching a program needs full feedback, a person who uses it day in and day out will learn the program, and want faster ways to get the job done, even if they are more arcane. The gives rise to a "layered" style of interface. A layered interface is designed so that the visual grammar is obvious, as we have discussed. However, there are one or more sets of "accelerators" built into the program, which may be harder to find but faster to use. One example is condensed mouse actions such as the double-click. For instance, attempting to select a block of text which extends beyond a window is impossible using the basic metaphor. The novice will simply do the operation in pieces. A layered interface might put a less obvious Mark Begin and Mark End option in the menus. Another way is to take a drag which extends outside the window as a request to begin scrolling in that direction, while extending the current selection. One of the most common and useful accelerator methods is function keys. Using this approach, single key equivalents to actions are listed in the menu. Striking this key when an object is selected will cause the action to occur. Note that this is most useful if some keyboard driven method of object selection, such as tabbing, is also available. Otherwise, the time switching from the mouse, used to select the object, to the keyboard for command input, may well cancel any advantage. Finally, radical departures from the GEM metaphor may be useful when attempting to replicate the look of another system, or trying to meet severe constraints, such as display space. One example would be discarding the standard GEM menus in favor of "popup" menus which appear next to the current mouse position in response to a click on the second button. This method has the advantage of preserving the menu space at the top of the screen, and is potentially faster because the menu appears right next to the current mouse position. The drawbacks are lack of a visual cue for naive users trying to find the commands, and the need for custom coding to build the popups. MORE TO COME. We have reached the end of the second sermon on user interface. In a future column, I will look at "higher level" topics relating to the design of the application's user metaphor. These include issues of object orientation, direct manipulation, and the construction of microworlds. In the meantime, several of the more practical columns will present implementions of techniques such as accelarator keys and popup menus which I have discussed this time. THANKS AND APOLOGIES to the following people whose public and published remarks have formed part of the basis of this discussion: Jef Raskin, Bill Buxton, Adele Goldberg, James Foley, and Ben Schneidermann. As always, any errors are my own. GEM Professional #15 Permission to reprint or excerpt is granted only if the following line appears at the top of the article: ANTIC PUBLISHING INC.,COPYRIGHT 1986. REPRINTED BY PERMISSION. Coping with GEMDOS While it's fun playing with windows and object trees, one of the day-to-day realities of working with the ST is its operating system, GEMDOS. A successful application should insulate the user from the foibles and occasional calamities of the machine's file system. The GEM environment provides some minimal tools for doing this, but a good deal of responsibility still rests with you, the programmer. This column (#15 in the ST PRO GEM series) tries to address the GEM/DOS integration problem by providing you some stock code for common functions, along with a discussion of some of the worst "gotchas" lurking for the unwary. The download for this column is GMCL15.C, and it can be found in DL3 of PCS-58. You should obtain and list this file before proceeding. A BIT OF HISTORY. There has been a good deal of confusion in the Atari press and among developers over what GEMDOS is, and how it relates to TOS and CP/M-68K. It's important to clear this up, so you can get a true picture of what GEMDOS is intended to do. The best way is to tell the story of GEMDOS' origins, which I can do, because I was there. As most developers are aware, GEM was first implemented on the IBM PC. PC GEM performed two functions. The first was a windowed graphics extension to the PC environment. The second was a visual shell, the Desktop, which ran on top of the existing operating system, PC-DOS. When work started on moving GEM to the ST, there were two big problems. First, no STs actually existed. Second, there was no operating system on the 68000 with which GEM and the Desktop could run. Unix was too large, and CP/M-68K lacked a number of capabilities, such as hierarchical files, which were needed to support GEM. Work on porting the graphics parts of GEM to the 68000 had to start immediately to meet schedules. Therefore, CP/M-68K running on Apple Lisa's was used to get this part of the project off the ground. Naturally, the Alcyon C compiler and other tools which were native to this environment were used. In parallel, an effort was begun to write a new operating system for the 68000, which would ultimately become the ST's file system. It was designed to be a close clone of PC-DOS, since it would perform the same functions for GEM in the new environment. At this point, the term TOS was introduced. TOS really meant "the operating system, whatever it may be, that will run on the ST", since not even the specifications, let alone the code, were complete at that time. The first engineer to work on "TOS" at Digital Research was Jason Loveman. This name leaked to the press, and in some distorted fashion generated a rumor about "Jason DOS", which was still just the same unfinished project. As "TOS" became more solid, the developer's tools were ported to the new environment one by one, and the GEM programming moved with them. CP/M-68K was completely abandoned, though the old manuals for C and the tools lived on and are still found in the Atari developer's kit. All of this work had been done on Lisas or Compupro systems fitted with 68000 boards. At this point, workable ST prototypes became available. An implementation of "TOS" for the target machine was begun, even before the basic operating system was fully completed. The other intent for the new operating system was to be a base for GEM on other 68000 systems as well as the ST. Because of this, Digital Research named it GEMDOS when it was finally complete, thus providing the final bit of nomenclature. "TOS" as now found in the ST is in fact a particular implementation of generic GEMDOS, including the ST specific BIOS. So, GEMDOS is a PC-DOS clone, but, not quite. There are enough differences to cause problems if they are ignored. (Remember, it looks like a duck, and quacks like a duck, but it's not a duck.) GOING FOR IT. As a first example, consider the routines open_file() and create_file() at the beginning of the download. They make use of the GEMDOS calls Fopen() and Fcreate(). You will notice that these names are not the ones specified in the Digital Research GEMDOS manual. Developers who have used PC GEM will also observe that they are radically different from the function names in the PC-DOS bindings. In fact, all of the GEMDOS function calls on the ST are defined as macros in the file osbind.h, distributed with the developer's kit. At compile time they are turned into calls to the assembly language routine gemdos(), part of the osbind.o binary. So, if you find the naming conventions to be particularly offensive for some reason, just edit the appropriate macros in osbind.h. In DRI's PC-DOS bindings, any error codes were returned in the global variable DOS_ERR. In the GEMDOS bindings, the operation result or an error code is returned as the value of the calling function. In the case of Fopen() and Fcreate(), the result is a valid file handle if it is positive. A negative result is always an error code, indicating that the operation failed. An application which encounters a GEMDOS error should display an alert, and query for retry or abort. The type of loop structure exemplified by open_file() and create_file() should be usable with most GEMDOS functions which might fail. The AES provides a function, form_error, which implements a set of "canned" error alerts appropriate to the various possible errors. However, this is where the fun starts. For unknown reasons, the form_error on the ST expects to see PC-DOS, not GEMDOS, error codes as it's input! Therefore you need a routine to translate one into the other. The routine dos_error() in the download provides this function. The GEMDOS errors are in the same sequence as those for PC-DOS, but their numerical order is reversed and shifted. Notice also that dos_error() does NOT perform the translation if the error code is less than -50. These codes have no PC-DOS equivalent; computing a bogus translation will cause form_error to crash. Instead, they are passed through verbatim, resulting in a "generic" alert which gives only the error number. The other major task in integrating a GEM application with the file system is selecting file names for input and output. Again, the AES provides some assistance with the fsel_input call, which invokes the standard file selector dialog. There are several drawbacks to the standard file selector. One is that the "ITEM SELECTOR" title is constant and cannot be changed by the application. This could cause confusion for the user, since it may not be clear which of several functions, closely spaced in the FILE menu, was actually invoked. While it might be possible to find and "rewire" the AES resource that defines the file selector, it is unlikely that such an approach would be portable to a later version of ST GEM. A viable approach to eliminating confusion is to display a small "marquee" box, with a message defining the operation, on the screen just above the file selector. To do this, you must initialize the location of the box so that it is outside of the file selector's bounds, and then draw it just before invoking the file selector. This way they will appear together. Before returning to its main event loop, the application should post a redraw message for the "marquee" area. The AES will merge this redraw with the one generated by fsel_input, and the result will be received by the application's evnt_multi. Another problem with the file selector is that it resets your application's virtual workstation clip rectangle without warning. There are other AES functions, such as objc_draw, which also do this, but the file selector can be troublesome because it may be the only AES call used by some VDI-based ST applications. The veteran developer will also notice that the file selector takes and returns the path and filename as two separate strings, while the GEMDOS file functions require a fully pathed file name. Also, the file selector doesn't remember its "home" directory; you are responsible for determining the default directory, and keeping track of any changes. The remainder of the download and column is devoted to set of utilities which should alleviate some of the "grunt work" of these chores. The top level routine in this collection is get_file(). It is called with two string arguments. The first must point to a four byte string area containing the desired file name extension (three characters plus a null). The second is the default file name. If the default file name is non-null, then get_file() invokes parse_fname() to break it into path and name. Parse_fname() also adds the necessary "wild card" file specification to the path, using the extent name given as input. If no default file was supplied, or the default did not contain a path, the routine get_path() is invoked to find the current default directory and construct a legal path string for it. The results of these manipulations are supplied to fsel_input. Notice that the result of the file selector is returned via its third argument, rather than as a function value. If the result is TRUE, get_file() merges the temporary path and file string, storing the result via the second input parameter. This result string is suitable for use with Fopen, and may be resubmitted to get_file() when the next operation is invoked by the user. Parse_fname() is straight-forward C. It looks backward along the file to find the first character which is part of the path. The tail of the filename is copied off, and its former location is overlaid with the wild card specification. Get_path() is a bit more interesting. It makes use of two GEMDOS functions, Dgetdrv() and Dgetpath() to obtain the default disk drive and directory, respectively. Note that Dgetpath() will return a null string if the current default is the root, but it puts a back-slash at the beginning of the path otherwise. This forces a check for insertion in the root case, since the file selector wants to see something like "A:\*.RSC", rather than "A:*.RSC". After making this fix, get_path() concatenates the wild card specification derived from the input extent. The last routine in the download is new_ext(). This utility is useful if your application uses more than one associated file at a time. For instance, the Resource Construction Set uses both an RSC and a DEF file, with the same base name. New_ext() takes a fully formed file name, and replaces its old extent with the new one which you supply. This lets you quickly generate both file names after one call to the file selector. Notice that new_ext() looks BACKWARD along the name to find the delimiting period, since this character can also be part of a subdirectory name in the path. So we reach the end of the code and this column. Hopefully both will keep you profitably occupied for a while. July's column will return to graphics topics, with a look at writing customized rubber box and drag box routines, and ways to implement your own "pop-up" menus. August will bring techniques for displaying progress indicators, associating dialog and menu entries with keystrokes, and customizing objc_edit. I CAN'T HEAR YOU! The Feedback mailbag has been noticably flat of late. There have been a number of compliments on the column, which are much appreciated, and some suggestions for topics which fall outside the bounds of this series. The latter have been passed on to Antic for possible inclusion in their new ST quarterly, START. One recurring problem is finding the downloads. A number of the earlier columns say they are in PCS-132 (the old SIG*ATARI), and one says PCS-57 (mea culpa). In fact, ALL of the downloads are now in DL3 of PCS-58 (ATARI16). Filenames for first nine columns are all in the form GEMCLx.C, where x is the column's digit. For reasons unknown to me, the next two files were named GEMC10.C and GEMC11.C; the latest two downloads are called GMCL13.C and GMCL15.C. The latter naming pattern should continue into the future. Undoubtedly, one reason for the shortage of questions is the amazing ability to get a quick answer on the Developer's SIG, PCS- 57. This is a good sign of a strong Atari community on Compuserve. However, the SIG message style doesn't really lend itself to lengthy explanation, so suggestions for longer topics are always welcome here. Finally, I am now beginning the process of collecting these columns and some additional material into a book. In doing so, it would be helpful to know if you feel that any part of GEM has been slighted in my discussions. If so, let me know. Your suggestions will appear in future columns and finally make their way into the book. ANTIC PUBLISHING INC.,COPYRIGHT 1986. REPRINTED BY PERMISSION. PROFESSIONAL GEM By Tim Oren Column #16 - Interface Potpourri #1 This issue of ST PRO GEM, number 16 in the series, presents code implementing several user interface techniques: progress indicators, rubber boxes, and draggable boxes with mouse sensitive targets. The code also includes some utility routines for handling resources, event calls, and VDI line drawing. The downloads for this column are available on DL3 of the ATARI16 SIG: PCS-58. Note the plural - in addition to the usual C sources stored in GMCL16.C, the files GMCL16.RSC, GMCL16.DFN, GMCL16.H, and GMCL16.RSH are a template resource for building progress boxes. GMCL16.RSC is the resource binary, and GMCL16.H is its symbol binding file, to be used with GMCL16.C. The RSH file is a C image of the resource - you would need STCREATE to regenerate it. GMCL16.DFN is the binary symbol file for the resource. It is in the format used by the NEW ST Resource Construction Set. If you are a developer, you should download this new version from DL7 of PCS-57. It fixes a number of bugs, and has a much faster user interface. MAKING PROGRESS. The need for feedback in interface designs has been discussed in previous columns. One instance which is often necessary is the so-called progress indicator. A progress indicator is used when your application is doing a long operation. It shows that the function is continuing satisfactorily, and is not hung in a loop. When possible, it also gives an indication of the fraction of the operation which has been completed. The thermometer bars on the Desktop format and copy operations are examples. The sample code shows two types of progress indicator. Both are built within the structure of a dialog resource. The first type uses a variable line of text to describe each phase of an operation as it occurs. The rewriting of the text provides action on the screen; the fact that it is different each time gives reassurance that the program is not hung. The second type of indicator is the thermometer bar. This is more useful when the operation is uniform, allowing you to estimate the fraction completed. Let's look at the code. The routines beg_prog() and end_prog() are common to the two types. The code is very similar to the standard dialog handling procedure, but is broken into two parts. Beg_prog() assumes that the progress indicator box is defined by a dialog tree named PROGRESS. Such a tree is provided in GMCL16.RSC. Beg_prog() makes the usual calls to center and draw the box. The rectangle computed in the centering operation is stored via a GRECT pointer passed in the parameter. This rectangle compensates for the outline around the box, and must be supplied to end_prog() when the operation is complete. The first version of set_prog() in the download implements the changing text progress indicator. It looks in a tree labelled STRINGS for the object number which is passed as a parameter. It is assumed that this object is a G_STRING. The address of the new text is loaded from the object's ob_spec field. (For those with the new RCS, it would be easy to alter this routine to use free strings. Simply replace the first two lines with: rsrc_gaddr(R_STRING, strno, &saddr); and supply parameters which are the names of strings in a FRSTR box.) Once the new text is found, the set_text() utility is called to update the TEDINFO attached to object PLINE in the PROGRESS tree. Set_text() will insert the new text address in te_ptext, and the new text length in te_txtlen. Disp_obj() is then used to redraw only the rectangle belonging to PLINE. PLINE must be defined as a G_BOXTEXT object with a solid white background, and with the CENTERED attribute set. It must extend entirely across the progress box. This guarantees that the previous text will be covered over, and the new text will be centered in the box. The second version of set_prog() implements the thermometer bar progress indicator. The PROGRESS tree also includes an object PROBOX which defines the outline of the thermometer. It is a G_BOX object with a solid white background, and a one-outside border. The object PROBAR is nested inside it, with the left edges matching. PROBAR is also a G_BOX, with a solid red background and a one-outside border as well. Set_prog() creates the thermometer effect by growing and redrawing PROBAR. Set_prog() requires two parameters. Maxc is an estimate of the total duration of the operation, in arbitrary units. Value is the (new) amount completed, in the same units. Set_prog performs two operations. First, it computes the fraction value/maxc, and sets PROBAR to that fraction of the width of PROBOX. Second, it computes the rectangle which is the difference between the old and new widths of PROBAR, and redraws only that part of the progress box. This prevents an annoying flash on the screen when the indicator is updated. These two types of progress indicators have been presented in separate routines for convenience in explanation. You can easily combine them in a single procedure to create an indicator with both effects. The final progress indicator routine is called esc_prog(). During many lengthy operations is desirable to provide an abort option to the user. Esc_prog() lets you do this by polling the keyboard for an escape (ESC) character. A zero timer value is used to guarantee an immediate return if no character is found. Characters other than escape are ignored. Esc_prog() returns TRUE if an abort is requested, and FALSE if the operation is to continue. In your application, you can either pair calls to set_prog() and esc_prog(), or recode set_prog() to automatically make the abort check. In any case, you should add an information line to the progress box, telling the user how the operation may be halted. Of course, this type of progress indicator is not the only option available on the ST. Other ideas such as changing window titles, or displaying a succession of differing icons are equally valid. Sometimes the nature of your application may suggest an alternate metaphor. For instance, the progress of recalculating a spreadsheet might be indicated by darkening successive columns in a miniature image of the sheet. Occasionally, the computing operation is visual itself, and will not require an explicit indicator. An example is redisplaying objects in a 2D or 3D drawing program. BOXED IN. The second part of the download implements two types of user interaction using the mouse. The first creates a "rubber box" on the screen, that is, a box whose size is controlled by moving the mouse. This is similar to the AES graf_rubberbox call, but allows the box to move in any direction from its origin, while the GEM function only allows movement to the lower right. The second technique allows the user to drag the outline of a box around the screen using the mouse. Again, this is similar to the AES graf_dragbox call, but this version is augmented with code which "hotspots" selectable objects when the mouse and object pass over them. These routines are another illustration of the usage of the evnt_multi function, and its combination with VDI drawing to create new interaction techniques. The "rubber box" subroutine is called fourway_box(). Its parameters are the current VDI handle (NOT a window handle!), and two GRECT pointers. The first GRECT must have its g_x and g_y initialized with the fixed point of the rubber box. The second GRECT contains an outer bound box for the stretching action. Fourway_box() begins by setting the VDI drawing mode and color. The exclusive or, black combination guarantees that redrawing a figure twice in the same location will exactly erase it. Next, the routine asserts the mouse control flag. This stops the window manager from tracking the mouse, with the effect that menus will not drop down during the operation. The fixed coordinates are saved in the variables ox and oy, and an initial mouse reading is obtained with graf_mkstate. At this point, the event loop is entered. At each iteration, the loop finds the upper left most of the fixed vertex and the current mouse position, and updates the tracking GRECT accordingly. A call to the utility rc_intersect() is used to restrict the size of the rubber box to the given limiting rectangle. Note that if you need a lower limit to the size of the rubber box, it can be achieved by adding another GRECT pointer "lower" to the parameter list, and using the call rc_union(lower, rubber); This works because the union operation selects the larger of two rectangles if they are nested. Rub_wait() will be described in detail below. Its returns are the new mouse position, and an indication of the current mouse button state. If the button remains down, the loop continues. When the button is released, the rubber box terminates, since it is a "spring-loaded" modal operation. Before ending, fourway_box() returns mouse control to the window manager. The return from the routine is found in the rubber GRECT, and is the final extent of the box. Rub_wait() is a utility used by both box techniques. Its purpose is to do one step of the box animation, and wait for a mouse movement, or the release of the button. Rub_wait() preserves the state of the screen. The first action is to draw an exclusive or'ed dotted line box at the given rectangle. Next, rub_wait() calls evnt_multi to wait for the mouse button to come up, or the mouse to move out of a one pixel rectangle. When the event is detected, the same code is used to remove the box. A value of TRUE is returned if the mouse button is still down; the curious logical construction is necessary since BOTH events could occur at once. A short examination of the vdi_xbox() code is also useful. After converting the rectangle to polyline format, the vdi_xline() routine is called. Vdi_xline draws a dotted line, but does not use the VDI line style attribute. This is avoided because the VDI has problems with corner points when drawing styled lines in XOR mode. Instead, a selection is made from a set of user defined line styles, based on the direction of the stroke, and the odd/evenness of the starting horizontal pixel. This assures that the figure will be exactly erasable. HOT STUFF? The drag box routine is more subtle, because care is needed to correctly synchronize the movement of the mouse cursor and the box, and the highlighting of target objects. The parameters vdi_handle and limit are identical to those in fourway_box(). The GRECT pointed to by box contains the width and height of the movable box when hot_dragbox() is entered. On exit it also contains the last x,y coordinates of the box. The variable tree is a pointer to the root of a resource tree defining the hot spots for the drag operation. Only objects tagged SELECTABLE are hotspotted. Hot_dragbox() returns the number of such an object if the box is "dropped" on it, otherwise a NIL is returned. Initialization proceeds as above, until the graf_mkstate call. Here is the first potential synchronization problem. If the user moves the mouse very quickly after initiating the drag, it may already be outside the box by the time graf_mkstate samples the position. The min/max operations given lock the box onto the cursor, no matter where it has strayed. The mouse/box offsets, ox and oy, will remain constant for the rest of the operation. Hover_obj will contain the number of the object which is currently highlighted. It is initialized to NIL, indicating no object is currently marked. Hot_dragbox() now enters a loop with termination conditions identical to the rubber box. The current desired position of the box is computed by subtracting the box/mouse offset from the current mouse position. The rc_constrain() call ensures that the box will not leave the bounding rectangle. Note that rc_intersect would not work here - it would alter the size of the draggable box, rather than "nudging" it back into the bounds. Upon return from rub_wait(), a number of conditions must be checked to determine the correct object to highlight, if any. First, we must make sure that the mouse is actually within the legal bounds. If not, there may be an ambiguous selection, with the mouse over one object and the box over another. We choose to do nothing in this case, and set hover_obj to NIL. If the mouse is in bounds, objc_find looks for a target object. If one exists, it must be SELECTABLE, or it is forced to NIL. Next the new object, stored in ret_obj, is compared to the old highlighted object, in hover_obj. If they are different, a switch must be made. Since either could be NIL, a check is necessary before calling obj_toggle to invert/reinvert the screen image of the object. When the loop is complete, the final hover_obj is returned to normal state before its number is returned. You may notice that this method of highlighting objects is different from the incremental tree descent and rectangles method presented in column 13. While not as efficient, the objc_find technique is simpler to code and may be adequate for many uses. If your program will make heavy use of the drag box routine, or will have large trees of target objects, you may wish to integrate the incremental hotspotting algorithm with hot_dragbox(). This would be simple to do; just use evnt_multi's second mouse rectangle for the states associated with the hot- spotter. The single pixel rectangles would have to remain, in order to maintain the animation effects. A FEW CHANGES. The observant may have noticed that the promised code for popup menus did not make it into this column. Instead, it will appear in column 18 along with more "graphics potpourri" and feedback replies. The intervening installment, number 17, will present and document the source code for a complete IBM/Atari GEM Resource conversion program. This will appear concurrently with Mark Skapinker's article on IBM/ST GEM conversions in the second issue of START. While this program will be of direct use to only a minority of ST developers, it will contain utility code useful to all, as well as demonstrations of dialog handling and the internal structure of resources. Finally, you may also notice that the so-called portability macros have disappeared from the download. Indeed, they are gone for good. Since the beginning of this column, the growth of the ST GEM developer community has outstripped that on the PC. It no longer seems appropriate to inconvenience ST developers and violate standard C syntax for the sake of Intel's design flaws. Those who still need compatibility with the PC may achieve it by compiling under Intel large model, or by writing "sed" scripts to translate (tree+obj)->ob_spec and the like to their macro equivalents. ANTIC PUBLISHING INC.,COPYRIGHT 1986. REPRINTED BY PERMISSION. PROFESSIONAL GEM By Tim Oren Column #17: PC/ST Resource Converter This the seventeenth installment of ST PRO GEM, the first to feature a complete GEM application. The program converts resource files between the ST (68000) and IBM PC (Intel) formats used by the respective versions of GEM and the Resource Construction Set. The Resource Converter will, for the most part, be of direct use only if you are doing cross-development of GEM software on these two systems. In this case, you will want to read this entire article, starting with the "What It Does" heading. You may also be interested in an article on PC/ST conversion written by Mark Skapinker of Batteries Included, which appears in the Fall issue of START magazine (available September 1986). If you are not doing cross machine development, you can still get something out of this column. Reading the program code and following the discussion, you will find practical examples of using GEM dialogs and following the internal structure of a resource file. Finally, there is a good deal of standard code for GEM initialization, AES utility functions, and GEMDOS file handling which you can extract for your own uses. If you fall in this category, skip the first parts of the column, and resume reading at the "How it Works" heading. The download for this column is a set of source and binary files located on DL3 of the ATARI16 SIG, PCS-58. The files and their contents are: RCMAIN.C - Download and rename to RSCVMAIN.C This is the C source for main routine RCFILE.C - Download and rename to RSCVFILE.C This is the C source of GEMDOS utilities RCLIB.C - Download and rename to RSCVLIB.C C source of AES utility functions RSCONV.PRG - Linked binary of converter RSCONV.RSC - Binary resource image (ST format) RSCONV.DFN - Symbol file for resource (ST format for RCS 2.0) RSCONV.H - Object/tree name file for resource RLINK.SH - Download and rename to RSCVLINK.SH C-shell script for linking RSCONV SYLINK.SH - Download and rename to SYMLINK.SH Another script, used by RSCVLINK.SH The final two files will be of use only if you are using Dave Beckemeyer's Micro C-Shell environment. If not, you can simply translate them to .BAT and/or .INP form for use with the Atari batch program and linker. WHAT IT DOES A converter program is necessary because of the differing order of storage on the 68000 and Intel chips: the order of significance of bytes within words is reversed, as well as the order of words within long (32-bit) quantities. Attempting to load an unconverted resource on a GEM system with the other architecture will result in a crash, because all of the pointer and integer fields will be incorrect. In addition, the format of symbol definition (DEF) files differs between the PC and ST implementations of the RCS in its first version. In the latest version from DRI, the formats do correspond, and the files now carry an extent name of DFN. However, the byte swapping problem must be corrected in either format for the symbols to be correctly loaded. As final touch, the converter also checks to be sure that a resource being loaded from the PC has its bit images synchronized to an even byte boundary. If this is not corrected, the ST will suffer a bus error when attempted to address the images with a word type operation, which will certainly happen in either the RCS or the application itself on the target system. OPERATION When the Resource Converter loads, it presents an initial dialog box. (The converter does not use the menu bar.) The dialog has action buttons at the bottom labelled "Help", "Convert" and "Quit". Clicking the Help button will obtain a screen which summarizes these operating instructions. The Quit button terminates the program. Clicking Convert initiates the program's action, according to the options you have set in the rest of the dialog. The top two buttons in the dialog establish the direction of the conversion. If you have built a resource on the PC and want to move it to the ST, click Intel->68000. If you are moving the file from the ST to the PC, click 68000->Intel. The next two lines in the dialog establish the file extent names which will be used for the conversion. The input extent names are always on the left, no matter which conversion mode you selected above. By default, they are RSC and DEF. The output extents, on the right, are RS2 and DF2 by default. The RSC/RS2 file extents are used for the resource, and DEF/DF2 extents are for the symbol file. Be careful that the input files you use are actually of the type (Intel or 68000) which you specified. If you get it backwards, the ST will most likely crash, though the input files will not be harmed. The three buttons below the file extents determine the format of the input and output symbol files. If you click on DEF, the Resource Converter will assume that the input and output symbol files are in the old (that is, version 1.1) format used by the RCS. If you click on DFN, the program expects the new (version 2.0) symbol format. If the input symbol file extent is still set to "DEF", clicking this button will also change it appropriately. (Note that the DEF and DFN formats are in fact IDENTICAL on the PC ONLY. To "change formats" ON THE PC, you need only change the file's extent name, for example, rename FOOBAZ.DEF to FOOBAZ.DFN. On the ST, there is a real difference,and you must use the DRI DEF2DFN.PRG utility to go from old to new.) The last button option is (OFF). Clicking this button disables the symbol file conversion. You may use this option to keep better control over versions of your resource. By establishing the main version on either the PC or ST, and converting only the resource file, not the symbols, you can ensure that no one will use the RCS on the destination machine to create a version which is different from the source. When the Convert button is clicked to begin execution, the standard Item Selector is displayed. Use it to find and pick the input resource file which you want to convert. The Converter will use its main filename, substituting the various extents, to find the input definition file, and produce the output file names. If the definition file cannot be found in the same directory under the generated name, you will be presented with an alert, asking you to abort the conversion, continue without the symbol file, or specify a new name and/or directory for the file. If the output names coincide with existing files, they will be overwritten. While the conversion operation is occurring, a progress indicator box will be displayed on the screen, with text messages indicating the current phase of the operation. When the conversion is complete, you are returned to the initial dialog, where you may quit or perform another conversion. One last note on conversion, with a caution: it is possible to move files between Intel and 68000 even if you have two different versions of the Resource Construction Set, for instance, Version 2.0 on the PC and Version 1.1 on the ST. In this situation, you can take advantage of the identity between symbol file formats on the PC. Make a copy of the PC symbol file into another file with the DEF extent, and run the converter. The output should load on the ST version correctly. However, if you move symbols between different RCS versions, you MUST NOT use the "free strings" and "free images" features of version 2 and then move the symbols to version 1. Doing so may result in spurious assignment of the "free" symbols to trees and objects, and you will (of course) not be able to edit the free images and/or strings in the resource. HOW IT WORKS For the ST-only developers now rejoining the discussion, I will now take a look at the standard GEM initialization code and the special dialog handling techniques which I have used in the Converter. Then we'll look at the guts of the code, which threads through the resource to do the actual conversion. For those interested in the supporting code libraries, the GEMDOS interface utilities in RSCVFILE.C were described in column number 15. The progress indicator functions of RCSLIB.C were detailed in column 16, and map_tree() and standard dialog handling techniques were discussed in columns three through five. There are a number of useful facts in rscv_init(), the initialization code. First, notice that the result of appl_init is NOT assigned directly to gl_apid. Due to a bug in the appl_init binding the application ID is not returned as the function value. Instead it is returned to the global variable gl_apid. This bug wouldn't cause direct problems in the resource converter, but it will in any program which uses the ID when sending messages to its own pipe. Next, an alert string to be displayed if the resource is not found has been explicitly included, to avoid a "Catch-22" situation. You can easily create such a string by building the alert in the RCS, using the C output option, and extracting the string from the resulting file. Rscv_init() then sets up its VDI handle, by getting the physical workstation handle from graf_handle, and passing it to v_opnvwk (open virtual workstation), which returns the virtual workstation handle for use in VDI calls. V_opnvwk also returns the dimensions of the screen, which are copied to GRECT scrn_area, and the number of color planes, which is used to set up an MFDB for the screen: scrn_mfdb. The wind_get call for the working area of the DESK window (number zero) results in the usable GRECT of the screen. This is different from scrn_area, because the menu bar's rectangle is reserved for the system. You may want to take particular notice of the vst_height call, which reloads the character dimension variables. The values returned earlier by graf_handle are ALWAYS for the monochrome (high resolution) system font, and they are not appropriate for the low and medium resolution modes of the ST. Performing the vst_height call AFTER opening the virtual workstation will get the correct dimensions. Some of the environment variables which are set up by rscv_init(), are not actually used in the resource converter. I have included them because they are part of my "generic" initialization procedures. Finally, rscv_init() uses Malloc to reserve most of memory as a working buffer for the resource conversion. Notice that 4K are left free. You must leave at least 2K, and preferably a safety factor, or the AES will be unable to allocate memory for the file selector dialog, and your application will hang when calling fsel_input. By the way, an open virtual workstation uses about 8K, so you should do your Mallocs after the workstation call. We now turn our attention to do_mode(), which handles the main dialog of the converter. After getting the root address of the dialog tree, the states of global variables conv_def and new_dfn are used to set up the radio button array for symbol file conversion. Native_in is used to set up the conversion type radio buttons. The last initialization step is to use the set_text() utility to link the file extent strings into the appropriate editable objects. Notice that this version of set_text() allows the string length to be explicitly supplied. For an editable text field object (G_FTEXT or G_FBOXTEXT), the length must be one greater than the number of blanks in the editing template. Since there are TOUCHEXIT objects in the dialog, we cannot use the standard hndl_dial() routine. Instead, the form setup and draw calls are coded inline. The actual form_do call is placed inside a loop. If the object returned is not one of the TOUCHEXIT objects, the loop is terminated. The two objects DEFYES and DFNYES, which select the old and new symbol file formats, respectively, have been made TOUCHEXIT. If either is clicked, the radio button processing is done by the AES, but then control is returned from form_do. Do_mode() then tests the current input symbol file extent. If it is "DEF" and we are switching from old to new mode, the extent is changed to "DFN". When switching from new to old mode, the reverse check is made. In either case, the disp_obj() utility is used to force an immediate redraw before returning to form_do for further user input. When the loop terminates, do_mode() cleans up the screen with form_dial, and uses the selected() utility to retrieve the status of the radio button arrays and update the global variables. Finally, map_tree() is used to apply the deselection utility to all objects in the tree, leaving it clean for the next invocation of do_mode(). THE BELLY OF THE BEAST The routine dconv() contains the code for actually converting the resource. About half of do_conv() is devoted to accessing disk files, and correctly handling error conditions which might arise. The error recovery could perhaps be handled more concisely with artifices like "setjmp", but I have left it in-line for the sake of clarity. Do_conv() also uses a collection of routines whose names begin with "swap_" Swap_bytes() and swap_words() are the workhorse routines. They simply run through a given area of memory, reversing every pair of bytes or words, respectively. (Remember that the word swapper will only work on even byte boundaries, or a bus fault will occur.) The other swap routines do the fix up for one type of resource structure each. I'll take apart a couple in detail, so that you can see the similarities in the others. Now let's follow the do_conv() code. The first item of business is to use the get_file() utility, followed by open_file(), to find and open the input resource file. Again, you can see column 15 for a a discussion of these routines, which will handle DOS errors if they occur. The Boolean conv_def is true if we need to open a symbols file. If everything goes right, just substituting a new extent name (from variable old_def), and doing the file open will get the file. If things foul up, do_conv() has to recover gracefully. So, it puts up the three button alert NODEF. The third button is an abort option; if it's picked we punt, closing the one open file and returning to the main dialog. Button one allows a continuation without the definition file (conv_def is forced FALSE so the user won't make the same mistake twice). Button two says try again, so get_file() is called to pick another file, and the whole process repeats. Once the input file(s) are open, the operation will run to completion, assuming no disk errors. The mouse form is switched to an hour glass, and a progress indicator box is initialized on the screen. The first action is to bring in the resource header only. If the resource is not native, that is, is not in 68000 format, its bytes must be swapped before using any of the counts and offsets. Note: if you've never dissected a resource before, this would be a good time take a look at the header format given in "gemdefs.h" in the Developer's Kit, and the resource structure definitions in "obdefs.h". After the swap, if necessary, the resource size field can be used. Before reading the rest of the resource, the size must be compared against the size of the buffer allocated during initialization. If there isn't enough room, the NOMEM alert is displayed and control returns to the main dialog. (Since the biggest resource is 64K, and the Converter is rather small, this shouldn't ever happen. But, some people load up lots of desk accessories, so best to be prepared.) After completing the resource input, an (admittedly kludgy) patch is made for the odd-byte-images problem. If the image offset pointer is odd, there will be problems on the ST. The fix made here relies on two facts. First, all images are an even number of bytes in size. Second, the old version of RCS DOES do an even byte synchronization AFTER writing the images. Therefore, if the images start on an odd byte, they will end on an even byte, and have one unused odd-addressed byte immediately following. The fixup strategy is to move all of the images up one byte, patch the offset in the header accordingly, and leave the img_odd flag set so that the bit image swap routine will also increment pointers in the BITBLK and ICONBLKs which use the images. If the resource being converted is native, we can do the image fixup immediately. If not, all of the other swaps have to take first, because the long pointers in the BITBLKs and ICONBLKs are in the wrong order. (We don't have to swap string data; it's always stored in ascending order.) Do_conv() calls subroutines to swap the tree index, objects, TEDINFOs, ICONBLKs, BITBLKs, and free string and images, in that order. We'll look at their actual code later. When this is finished, one of two cleanup actions is needed. If the resource WAS native, everything is now swapped except the header, which is finally reversed. If it used to be foreign (Intel), the pointers are now in proper 68000 order, so swap_images() can be called successfully. Now it's time to write the converted resource, taking care that no error results. If no symbol file conversion is needed, do_conv() is done. The files are closed, the progress indicator completed, and control returns to rscv_run, which will set the cursor back to the arrow. Otherwise, it's time to handle the symbols file. The first two bytes in the definition file are always the symbol count. They are read into nsym, and a scratch copy is made in reply, and swapped. Just which version is written to the output file depends on whether it is a DFN or DEF file. The new (DFN) format keeps a similar word order on both the ST and PC. For these files, the following code amounts to a verbatim copy. Nsym is now used as the control variable of a loop which reads, converts, and writes one symbol entry on each iteration. In the old (DEF) file format, there was an extra word in the value field of each symbol's entry on the ST. Going from PC to ST, two padding zero bytes are added. Going the other way, they are read and discarded. For the old format, both the significant word of this field, as well as the following word field (the type) are swapped. The ten symbol bytes are passed through verbatim in both new and old format. After swapping (old format) or simply copying (new format) of the symbol file is completed, file and progress box cleanup is done, and control returns. THE OL' SWITCHEROO As promised, let's now go and look at a sampling of the structure swap routines: one of the simplest (tree index), one of medium complexity (objects), and the worst of the bunch (bit images). Swap_trees() is responsible for fixing the tree index. Its location is found by adding an offset from the header (rsh_trindex) to the base address of the resource, stored in head. (Notice that head was defined as a byte pointer when the buffer was allocated, so I need to type coerce it to a resource header pointer before making structure references.) The number of trees is stored in the rsh_ntree header field. Each index entry is a long pointer of 32-bits, so the total size of the index may be found by multiplication. Finally, the swapping is done, first bytes within words, then words within the long pointers. (The order doesn't matter - work it out!) Swap_objs() takes care of swapping fields inside of object structures. It begins similarly to swap_trees(), computing the base address of the objects from rsh_object, and taking the count from rsh_nobs. Since all of the objects are contiguous in a resource produced by RCS, and all fields of an object are words or longs, a byte swap is performed on the entire block at once. Now there are a couple of tricks. Notice that "where" in this routine is typed as an object pointer. As the number of objects is counted down in the loop, "where" is incremented. Because of its type, its actual pointer value will increase by sizeof(OBJECT) at each iteration. Inside the loop, only one long field, the ob_spec, needs to be word swapped. When the loop completes, all object structures are fixed. Swap_fstr() and swap_fimg() use code similar to swap_trees(). The other resource structures, TEDINFOs, ICONBLKs, and BITBLKs, are transformed by code similar to swap_objs(). There is one subtlety here. These structures would normally be reaching by following an object's ob_spec pointer. To finesse a "swap now or later" ordering problem with the objects, I have instead used the resource header to find the structure arrays. You SHOULD NOT use this technique at run time, for two reasons. First, if you do any object patching on the fly, the correspondence between objects and reference strucutres will be destroyed. Second, there is no way to name a non-object structure with the RCS, and therefore no way to reliably retrieve it with rsrc_gaddr at execution time. Last but not least, swap_images() not only takes care of switching bit image words, but also fixes image pointers as necessary if an odd byte problem has been detected. For these reasons, swap_images DOES follow pointers from the ICONBLKs and BITBLKs. The loop structure of the two sections of code will be familiar from swap_ibs() and swap_bbs(). Size fields within the structure are used to determine the length of each image. If the img_odd flag has been set, the data pointer must be incremented BEFORE doing the swap, since the underlying data has already been moved. PHEW! This column was deliberately long, in hopes that even those with no immediate use for this program will gain from the discussion. Do to space limits, the feedback is postponed to episode number 18, which will be another helping of "interface potpourri". Ingredients will include the popup menu code (finally), along with generic code for scrolling windows, and building your own scroll bars within dialogs. PROFESSIONAL GEM ARTICLES ARCHIVES: 1 Intro. & Windows Pt.1 10/85 2 GEM Windows, Pt.2 10/85 3 The DIALOG HANDLER 11/85 4 The RESOURCE file 11/85 5 RESOURCE Trees 12/85 6 RASTER Interrupts 12/85 7 MENU Structures 1/86 8 User Interfaces 1/86 9 VDI: Lines & Solids 2/86 10 VDI: Text Output 2/86 11 GEM Hooks & Hacks 3/86 12 GEM Events 4/86 13 Form Manager 5/86 14 Interfaces, Pt.2 5/86 15 GEMDOS 7/86 16 Interface Potpourri #1 8/86 17 Resource Converter 9/86 Current Notes ST Disk Library Tim Oren's Professional GEM Tim Oren's Professional GEM was/is provided by Antic Online. The series of files has been placed on two disks. This file is the same on disks for those people that for some reason only get one of the two disks and want to know what the complete series covers. Many thanks to Antic for providing this series on Compuserve. There are three folders on the first disk and two folders on the second disk. The extra folder is for the three FEEDBACK files. The other folders are TEXT for the columns themselves and SOURCE_C.ODE for the source code and programs for columns 16 and 17. The text portion of the columns uses a portion of the topic as the filename and the column number as the filename's extension. ANTIC PUBLISHING INC.,COPYRIGHT 1985, 1986. REPRINTED BY PERMISSION. GEM Professional Disk #1 - Columns 1 - 10 GEM Professional Disk #2 - Columns 11 - 17 Column 1 10/7/85 Windows, Part 1 An introduction to the creation of windows. No sample code associated with this column. Column 2 10/21/85 Windows Part 2 Digs a little deeper into the techniques of using windows. One source file: GEMCL02.C Column 3 11/7/85 The Dialog Handler Discusses the coding that goes with dialog boxes set up using a Resource Construction Set. One source file: GEMCL03.C Column 4 11/85 Resource Files Takes a look at some of the attributes of 'objects' used within a resource file and some of the things to watch out for when building one. Column 5 12/85 Resource Tree Structure Discusses the tree structure of a resource file and provides the code for a tree traversal routine. One source file: GEMCL05.C Column 6 12/85 Raster Operations Covers the VDI's raster functions. One source file: GEMCL06.C Column 7 1/86 Menu Structures Discusses menu structures and how to use them - it is assumed one has a resource construction set. One source file: GEMCL07.C Column 8 1/86 User Interfaces: Homily #1 Tim Oren discusses his reasoning behind a programmer making a program comfortable for the end user. Column 9 2/86 VDI Graphics: Lines and Solids Covers the code required to output the above graphics. One source file: GEMCL09.C Column 10 2/86 VDI Graphics: Text Output A look at some VDI text output and ways to optimize it. One source file: GEMCL10.C Column 11 3/86 GEM Hook's and Hacks An Insider's AES Tricks The title says it all. One source file: GEMCL11.C Column 12 4/86 GEM Events and Program Structure Covers AES event handling. No sample source code associated with this column. Column 13 5/86 A New Form Manager Provides an alternate dialog form manager and discusses it. One source file: GEMCL13.C Column 14 5/86 User Interfaces, Part 2 A continuing discussion on interfacing with the end user. No sample source code associated with this column. Column 15 7/86 Coping with GEMDOS A look at some troublesome areas in using GEMDOS and some useful code to make using GEMDOS a little easier. One source file: GEMCL15.C Column 16 8/86 Interface Potpourri #1 Covers the code included for several types of user interfaces. Five associated files: GEMCL16.C GEMCL16.DFN GEMCL16.H GEMCL16.RSC GEMCL16.RSH Column 17 9/86 PC/ST Resource Converter The last installment of Tim Oren's Professional GEM series. This one includes a program to convert resource construction set files between 68000 and IBM formats. Nine associated files: RSCVFILE.C - source for main file RSCVMAIN.C - GEMDOS utilities RSCVLINK.SH- for use with Micro C Shell RSCVLIB.C - source of AES utility func. RSCONV.DFN - symbol file for resource RSCONV.H - resource header header RSCONV.PRG - conversion program RSCONV.RSC SYMLINK.SH - for use with MMMicro C Shell .  t..  tMEDRZ2 PRGt `> ?<NNT g*?</?<NN\BgNAwp"3DU .. ( t.. ( t STARTUP DOC*t w!STARTUP INF/t OSTARTUP PRG2t ~. ....................................................... / : \ : STARTUP.PRG : Batch Startup Program Version 1.1 : : : by Murray Levine : : : CIS # 74435,1015 : : : : \................:....................................../ STARTUP.PRG is a Batch Startup program that executes commands from the file startup.inf upon booting your ST. STARTUP.PRG must be placed in the \AUTO\ folder on boot drive (usually A). It first looks for the file STARTUP.INF in the same directory and if it can't find it, it tries to locate it in the main directory (ex. A:STARTUP.INF). From now on, these two files can be the only files you need in the AUTO directory because all other files to be executed can be located in their original drive and folder and can even have parameters past to them. You no longer have to make sure of the order that you copied your files to the AUTO directory because STARTUP executes programs as they are listed in STARTUP.INF. The format for pragrams to be executes is to list the complete program name followed by any parameters to be passed. any command or program name can be followed by a comment. Any text on a line that follows a ';' will be treated as a comment. For example, a:\supboot.prg ; Supra Hard Drive driver program c:\bin\touch.prg a:startup.inf ; Update timestamp for info file would first load the hard drive driver program supboot,prg from drive A: and then update the timestamp for teh file a:startup.inf. There are a few unix-like commands that are used in the STARTUP.INF file as well as some other special commands. All commands must be in lower case, however, the filenames may be in lower or upper case. The allowable commands are as follows: Copy Files: cp file1 file2 - copy file1 to file2 cp file1 ... dir - copy file1 and other files to the directory dir The cp command is useful for copying files to a ramdisk. Wildcards are also accepted. cp b:\src\*.c c:\ ; Copy source files to ramdisk c: Remove files: rm file ... The rm command removes the listed files. As with cp, wildcards are also accepted. Be carefull, though, about doing an rm *.* so you don't wipe out a disk by accident. Create directory mkdir dir ... The mkdir command creates the subdirectory dir. Remove directories rmdir dir ... The rmdir command removes the specifies subdirectories. An error will occur if the specified subdirectory is not empty or does not exist. Display files: cat file ... The cat command displays the listed files on the console. Useful for displaying any title screen information. Once again, wildcards are accepted. Pressing ctrl-C during the display of a file aborts displaying that file and goes on to the next file if there is one. Change directory: cd dir The cd command changes the default directory to either a disk drive or a subdirectory. cd b: ; changes default drive to b: cd b:\auto ; change to drive b:, subdirectory auto The cd command is usefull if when running prgrams you don't wan't to list the complete filename (drive and directory) and if the program being executed expects to find certain files (data files) in the default directory. Display text: echo string echo -n string echo -i string The echo command displays the following string (list of words) on the screen followed by a carriage return. If the -n option is used, the carriage return is not printed. If the -i option is used, the string is printed in inverse video. The echo command is useful for displaying what is happening at different points of the boot process. As an example, I use the following: echo -n The time is c:\bin\date.prg ; display the current time This will display: The time is Sat Jan 24 1987 12:24pm Setting variables set var = string - set variable var to string set var = $< - set variable var to a string entered from keyboard set - display all variables The set command allows the user to set up to 20 variables whose variable names can be up to 8 characters in length. If string is to be more than than one word then it should be in quotes, e.g. "This is a string". If The string is $<, then the variable waits for a string to be entered from the keyboard. This string can then be tested later using the if command. Using set without any arguments will display all of the variables and the string values associated with them. Conditional statements if (string1 cond string2) then commands endif if (string1 cond string2) then commands else commands endif The if command is used to compare two strings based on the specified condition cond. If the comparison is true, the commands follwing the then statement will be executed. An optional else statement can be used if the comparision is false. The if command must end with the endif statement. There can be as many as 9 nested if statements, however be sure that there are enough endif statements to match them. String1 and string2 can be either strings or variables. The possible conditions are as follows: == is equal to != is not equal to < is less than > is greater than <= is less than or equal to >= is greater than or equal to The special commands used by Startup are as follows: Use medium or low resolution: res medium res low The res command will set a color system to medium resolution or low resolution during the boot process. On monochrome systems this command has no effect. Turn the cursor on or off: cursor on cursor off The cursor command turns the cursor on or off again. Since during the boot process TOS dosn't turn on the cursor, you can now see where the cursor is for entering text. You can even turn it back off again after running a certain program if you wish. Turn the keyclick on or off: keyclick on keyclick off The keyclick command trurns off the keyclick if you get annoyed by it while entering input and let's you turn it back on if you want after running a certain program. Clear the screen: clear The clear command clears the screen and places the cursor in the upper left hand corner. This can be followed by the cat command to display a title page. Display of each line display on display off The display command turns on or of the display flag. When the display is on, the each line will be displayed in full including comments until the display is turned off. The default is display off. Ask if program should be run ask filename [arg ...] The ask command asks the user if he wishes to run the program denoted by filename. The output is as follows: Execute autotime.prg (Y/N)? Typing a Y will execute the program, any other key will skip the execution of the program. This can be useful for example if a user does not wish to load GDOS when he boots up, but wishes to load GDOS the next time he boots teh system. System variables $res - contains the screen resolution ("low","medium","high") $desktop - contains the screen resolution in the desktop.inf file ("low","medium","high"). If the desktop.inf file does not exist, the variable is set to "none". $cwd - current working directory or pathname The following sample startup.inf file is the one I currently use during my boot process. res medium ; Use meduim res when booting in color cursor on display on c:\supboot.prg ; Supra Hard Disk booting program d:\rtx\rtxboot.prg ; Micro RTX kernal d:\utility\hdaccel.prg ; Hard Disk Accellorator d:\utility\autotime.prg ; Logikhron Clock Card time retriever display off if ($res != $desktop) then ; Check desktop.inf resolution echo copying desktop.inf for $res resolution if ($res == high) then cp c:\desktop.hi c:\desktop.inf ; set desktop.inf for high res settings else cp c:\desktop.med c:\desktop.inf ; set desktop.inf for med res settings endif endif echo -n The date is c:\bin1\date.prg ask e:\degelite\auto\gdos.prg ; GDOS 1.1 for Degas Elite Any comments or suggestions for a future version are welcome. Murray Levine CIS # 74435,1015 ;P4s!i3^_.w䬇+`a!%0µbƁ wBD Vqc4΅a!yF19l LLH$pn-Hl!zu${n`ҁo|68wL)ړ xdې6VU|wue8)N0%B {ݎVcNiV{/n(d8phDd )ÍL(ʝB {)P I `8k7@@ghxt81X+2h{ fO 4Q$pu*%\*@1mxNpm]mΰa!Zn$Pw/ua8EK(QNK -ҩxByg)8)Ź粝`_\*X@.!dCp}H%(er|`elib4khaqo9iadu3IGul*M ?ޢ/yGxe|9}ڥHvS tqhF +[\{ B%!p4Fޫe14̝iR^ft5B ή{res medium ; Use meduim res when booting in color cursor on display on c:\supboot.prg ; Supra Hard Disk booting program d:\rtx\rtxboot.prg ; Micro RTX kernal d:\utility\hdaccel.prg ; Hard Disk Accellorator d:\utility\autotime.prg ; Logikhron Clock Card time retriever display off if ($res != $desktop) then echo copying desktop.inf for $res resolution if ($res == high) then cp c:\desktop.hi c:\desktop.inf else cp c:\desktop.med c:\desktop.inf endif endif echo -n The date is c:\bin1\date.prg ; e:\degelite\auto\gdos.prg ; GDOS 1.1 for Degas Elite  S11R o‰\ cPBf4đpJ1e$uF%j }|1†Ab@Pq ɱ~ B 1GaJx"XǡQGiK}`  !irШg)JAʑꪭڤLp\t 㵓nDI(+,Ɩl2ֳEgd Tj H'[J#uzbp!1wDIt SoExx20! +! ,j-{eR Y@-چ k sŚcqDoHtP2y{P4l\ybH( 9Q*d!{M28O@} 5B|N 0,4q HrĴP/Z s,o6`&r,oO/r <Ю ЮЮ//Bg?<JNAO N*BgNA"/0<NBNuNV`p n @o n [lp`B@J@gF n @o n [lp`B@J@g nH| ` nH"nR nJfN^NuNV> N$|f">N$fp`B@`N^NuNV` n"n R R n Jf nBN^NuNV`R nJf` n"n R R n Jf nBN^NuNVBn`Rn nHRJf0.`N^NuNV. Nl?/.NlX_gp`h`V nH"n HAo p`H` nH"n HAlp`( nHR n HR nJfB@`N^NuNV 9'\#;V#(L;.;?< N$T.(k?< N$T.(?< N$T.(?< N$T.;?< N$T.(?< N$TN% Nv>N$n`N.'/9'N%:X`J.'/9'N%:X`2.'/9'N%:X``J@g|g°|g`BW/<(?<=N$\3N$T096B`N.'/9'N%:X`J.'/9'N%:X`2.'/9'N%:X``J@g|g°|g`.:?<N$T#;/zBW/9'?<NN$\J@f BW/9'?<=N$\3<`xBW/9;V?<NN$\J@f BW/9;V?<=N$\3<`@.(?< N$T.;V?< N$T.(?< N$TBWN$ y/z#A.A?<HN$T#5.5/9A?9N$T#55B<`> y5<  g y5<  f y5096Pм7p2y65"096Pм7p @BRy6 y 6m`R5 y5  g` y5 "fB7dR5`.09APй7dм7p @"y5R5R7d y5Jg y5 "f y5 "fR509APм7pй7d @B`6 y5 $fPB7dR5`.09APй7dм7p @"y5R5R7d y5Jgd y5  gV y5 =gH y5 (g: y5 )g, y5 !g y5 >g y5 >fh09APм7pй7d @B09APм7p.N%#fB7d09APй7dм7p @"y5R5R7d`.09APй7dм7p @"y5R5R7d y5 =g09APм7pй7d @B`B7d`.09APй7dм7p @"y5R5R7d y5Jgd y5  gV y5 =gH y5 (g: y5 )g, y5 !g y5 >g y5 >fh09APм7pй7d @BRyA`R5 y5  g y5Jf.7pNB0y("<( Jpg.(/<7pNXJ@fJy(oSy(`x.)/<7pNXJ@f,0y(SH( JPf0y(( BP`4.)/<7pNXJ@fRy(0y(( 0`.) /<7pNXJ@f.5?9AN(T``0Jy(g&.@?< N$T.)?< N$T.)/<7pNXJ@f>HNb>JNb`4.)/<7pNXJ@f.5?9ANXT`.) /<7pNXJ@f0y(( 0`.)%/<7pNXJ@fJy(oSy(`.)+/<7pNXJ@f.5?9ANlT`r.)./<7pNXJ@f.5?9AN pT`@.)1/<7pNXJ@f .5?9AN8TNv`.)4/<7pNXJ@f.5?9AN#T`.)8/<7pNXJ@f.5?9ANT`.)?/<7pNXJ@f.5?9ANT`r.'/<7pNXJ@f.5?9ANT`@.)H/<7pNXJ@f.5?9ANT`.)K/<7pNXJ@f.5?9ANRT`.)Q/<7pNXJ@f.5?9ANT`.)W/<7pNXJ@f.5?9ANT`x.)[/<7pNXJ@f`j.)`/<7pNXJ@f.5?9ANT`(.7pNlJ@g.7h/<7pNX 96Am.)d?< N$T.5?<IN$TN^NuNVB/. /.Bg?<KN$ Jl6.)?< N$T.?< N$T.)?< N$TN^NuNV-|5 nol=|`> n 2.H./.N0X0.S@no.)/.N0XRn0.nm.NlH5`B95.)?< N$T n .?< N$T no$> ?<N$T.5?< N$T.)?< N$T>N$H@.)?< N$T .Yg .yf.5 n /(NXN^NuNV=|BnX n -P n -fL`4 nH`Bn` =|``|ig|ng`R nJfSn`Y Jng.)?< N$T`2X n .?< N$T no> ?<N$TSnfJng.)?< N$TJng.)?< N$T`> ?<N$TN^NuNV-|)-|) nmBn-|;>Sn n 2.H/0?<NN$\=@Jnf =n`^ n 2.H.Nl|g n 2.H.Nl|f" n 2n p (:f=nBWBg n /(?<CN$P=@ nf n g JnfBnJnf0 nn&BW n /(?<=N$\=@l<.?< N$T n .?< N$T.)?< N$T`BW n /(?<?<>N$T`f`.?<>N$T>?<>N$T`Jng n 2.H./<;NX.;Nl=@0nSH; \g0n;\0n;B(-|;`BWX n /?<NN$\=@`` n ./.NX.;/<?<>N$T`p`.?<>N$T>?<>N$T>ON$=@JngSnftN^NuNV-|;`BWX n /?<NN$\=@`p n ./.NX.:?<AN$TJg8.)?< N$T.:?< N$T.)?< N$T>ON$=@JngSnffN^NuNV`TX n .?<9N$TJl8.*?< N$T n .?< N$T.*?< N$TSnfN^NuNV`X n .?<:N$T=@l| nf<.*(?< N$T n .?< N$T.*:?< N$T`8.*G?< N$T n .?< N$T.*`?< N$TSnfbN^NuNV>?<?<N$~X. H>?<?<N$~XN^NuNV-|*m ng.?< N$T`f.* n /(NXJ@f>eNb`<.* n /(NXJ@f>fNb`.?< N$TN^NuNV-|* ng.?< N$T``.* n /(NXJ@f3(`8.* n /(NXJ@f By(`.?< N$TN^NuNV-|-|* ng.?< N$T`.* n /(NXJ@f0B?< N$T-@ n.?< N$T`\.* n /(NXJ@f0B?< N$T-@ n.?< N$T`.?< N$TN^NuNV-|*>N$n|g ng.?< N$T`.' n /(NXJ@f6BW// ?<?<N$~X y7d"<<0H>?<?<N$~XNJ@fR7d 97dm 97dm&. ?<?<N$~X> ?<?<N$~X>?<>N$T>ON$=@JngSnn|N^NuNV-|*-|+ nf n h (:f> N$~=@ n h `o n h {lp`B@J@g n hH|` n hHH@.H|nf&.H>?<N$T.?< N$T.H>W?<N$T n .T?<;N$TJg.?< N$T`, n .?<;N$TJg.?< N$TN^NuNV>N$|AH@|:BW/?<GN$\.NB./<+N%:XN^NuNV nB( . /<:NX.:NlS@=@`Jng Sn` B9:`$0n: \g0n: :fJngRn0n:B./<:N0XN^NuNV nfV.+ n /(NXJ@f:.+ n /(NXJ@f.+ n /(NXJ@g.+%?< N$T`.=y(.+8 n /( NXJ@g:.+; n /( NXJ@g.+> n /( NXJ@fV0.R@3( n . n /(NXJ@f0y(( BP``0y(( 0.+A n /( NXJ@fR0.R@3( n . n /(NXJ@g0y(( BP`0y(( 0.+D n /( NXJ@g.+F n /( NXJ@fR0.R@3( n . n /(NXJ@l0y(( BP`0y(( 0.+I n /( NXJ@g.+K n /( NXJ@fR0.R@3( n . n /(NXJ@o0y(( BP`0y(( 0N^NuNV nf N%` nf.+N n /(NXJ@g.+P?< N$T`.' n /(NXJ@g|.' n /(NXJ@g`.+d n /(NXJ@gD.+h n /( NXJ@f n . N$ n . n /(N%:XN^NuNV|N.?< N$T=|` n2nRRn.HT@nn nB.+k?< N$TN^Nu#/vNN/9/vNu#/vNM/9/vNu#/vNA/9/vNuNVBn`R0.Hм6./.NXJ@f 0.`60.Hм6 @Jf0.R@D@`Rn nmp`N^NuNVBn`0.Hм6 @BRn nmN^NuNV.N$=@ nfB@`RJnl0.R@D@=@.0.Hм6/NX. 0.Pм/~/NX`N^NuNV.N$=@JnlB`0.Pм/~`N^NuNVBn`v0.Hм6 @Jgh0.Hм6.?< N$T> ?<N$T0.Pм/~.?< N$T.+n?< N$TRn nmN^Nu ((0(4(<(A(Hw\AUTO\STARTUP.INFresdesktophighmediumlow:::::::::::::::::::::::::::: :: Batch Startup (V1.1) :: :: by :: :: Murray Levine :: \desktop.infnone Startup file not found $endifelseifdisplay clearechoelseendifcpifcdsetcursorkeyclickrmmkdirrmdircatexitask Time to run the desktop... ->Program not found Execute (Y/N)? pq cp: can't open cp: can't create rm: Could not remove mkdir: Could not create directory rmdir: Directory not empty rmdir: Could not delete directory cursor: syntax error onoffdisplay: syntax error onoffkeyclick: syntax error onoffres: syntax error cat: can't open No such directory : No such drive cwd()thenif: syntax error ==<=>=!=<<=>===set: syntax error cwd$< (          &         $                                                                        0        $.>L  $         "    2  (   $        "        "(|$          "<  D": "   t b`jQ 0[mW #] v]!@;z 0 0ǀv~: zBp`x`e0' {b``@`L`e@@``#p a Pyѣ=?=?;x/p<&k1 ؖ:\J?=?;x/p<&k1 /r:bj+BA!EDR RD. 0q 0 =fF&ݭ@ Ȑ4@>      `.  t..  t LESS DOCt 2LESS TTPt 0u LESS - opposite of more less [-cdepstwmMqQuU] [-hn] [-b[fp]n] [-xn] [+cmd] [name(s)] LESS is a program similar to MORE, but which allows backwards movement in the file as well as forward movement. Also, LESS does not have to read the entire input file before starting, so with large input files it starts up fas- ter than text editors like vix. Commands are based on both more and vi. Commands may be preceeded by a decimal number, called N in the descrip- tions below. The number is used by some commands, as indi- cated. As of this posting, LESS works a) from the desktop and b) in the Mark Williams shell. It hasn't been tested with the Breckmeyer or any other shells. All references to environment variables in the rest of this doc refer to Msh environment variables. Shell escapes and invocations of the editor also work only in the Msh. h Help: display a summary of these commands. If you for- get all the other commands, remember this one. SPACEScroll forward N lines, default one screen. If N is more than the screen size, only one screenful is displayed. f Same as SPACE. b Scroll backward N lines, default one screen. If N is more than the screen size, only one screenful is displayed. RETURNScroll forward N lines, default 1. If N is more than the screen size, the entire N lines are displayed. e Same as RETURN. j Also the same as RETURN. y Scroll backward N lines, default 1. If N is more than the screen size, the entire N lines are displayed. k Same as y. d Scroll forward N lines, default 10. If N is specified, it becomes the new default for all d and u commands. u Scroll backward N lines, default 10. If N is speci- fied, it becomes the new default for all d and u com- mands. r Repaint the screen. R Repaint the screen, discarding any buffered input. Useful if the file is changing while it is being viewed. g Go to line N in the file, default 1 (beginning of file). (Warning: this may be slow if N is large.) G Go to line N in the file, default the end of the file. (Warning: this may be slow if standard input, rather than a file, is being read.) p Go to a position N percent into the file. N should be between 0 and 100. (This is possible if standard input is being read, but only if LESS has already read to the end of the file. It is always fast, but not always useful.) % Same as p. m Followed by any lowercase letter, marks the current position with that letter. ' Followed by any lowercase letter, returns to the posi- tion which was previously marked with that letter. All marks are lost when a new file is examined. /pattern Search forward in the file for the N-th occurence of the pattern. N defaults to 1. The pattern is a regu- lar expression (well, it works most of the time..). The search starts at the second line displayed (but see the -t option, which changes this). ?pattern Search backward in the file for the N-th occurence of the pattern. The search starts at the line immediately before the top line displayed. n Repeat previous search, for N-th occurence of the last pattern. E Examine a new file. If the filename is missing, the "current" file (see the N and P commands below) from the list of files in the command line is re-examined. N Examine the next file (from the list of files given in the command line). If a number N is specified (not to be confused with the command N), the N-th next file is examined. P Examine the previous file. If a number N is specified, the N-th previous file is examined. = Prints the name of the file being viewed and the byte offset of the bottom line being displayed. If possi- ble, it also prints the length of the file and the per- cent of the file above the last displayed line. - Followed by one of the command line option letters (see below), this will toggle the setting of that option and print a message describing the new setting. V Prints the version number of LESS being run. q Exits LESS. The following two commands at present work only with the 'msh' of Mark Willaims C (when LESS is run from the desktop, a 'Shell not found' message is printed out. A fix for non-shell environments is in the offing). v Invokes an editor to edit the current file being viewed. The editor is taken from the environment vari- able EDITOR, or defaults to "vi". ! shell-command Invokes a shell to run the shell-command given. Command line options are described below. Options are also taken from the environment variable "LESS". (The environ- ment variable is parsed before the command line, so command line options override the LESS environment variable. Options may be changed while LESS is running via the "-" command.) For example, if you like more-style prompting, to avoid typing "less -pm ..." each time LESS is invoked, you might tell Msh (the Mark Williams shell): setenv LESS=-pm -s The -s flag causes consecutive blank lines to be squeezed into a single blank line. This is useful when viewing nroff output. -t Normally, forward searches start just after the top displayed line (that is, at the second displayed line). Thus forward searches include the currently displayed screen. The -t command line option causes forward searches to start just after the bottom line displayed, thus skipping the currently displayed screen. -m The -m command line option causes LESS to prompt less verbosely like more, printing the file name and percent into the file, or just with a colon. -M The -M command line option causes LESS to prompt even more verbosely than more (this is the default). -q Normally, if an attempt is made to scroll past the end of the file or before the beginning of the file, the terminal bell is rung to indicate this fact. The -q command line option tells LESS not to ring the bell at such times. If the terminal has a "visual bell", it is used instead. -Q Even if -q is given, LESS will ring the bell on certain other errors, such as typing an invalid character. The -Q command line option tells LESS to be quiet all the time. -e Normally the only way to exit LESS is via the "q" com- mand. The -e command line option tells LESS to automatically exit the second time it reaches end-of- file. -u If the -u command line option is given, backspaces are treated as printable characters; that is, they are sent to the terminal when they appear in the input. -U If the -U command line option is given, backspaces are printed as the two character sequence "^H". If neither -u nor -U is given, backspaces which appear adjacent to an underscore character are treated specially: the underlined text is displayed using the terminal's hardware underlining capability. -w Normally, LESS uses a tilde character to represent lines past the end of the file. The -w option causes blank lines to be used instead. -p Normally, LESS will repaint the screen by scrolling from the bottom of the screen. If the -p flag is set, when LESS needs to change the entire display, it will clear the screen and paint from the top line down. -h Normally, LESS will scroll backwards when backwards movement is necessary. The -h option specifies a max- imum number of lines to scroll backwards. If it is necessary to move backwards more than this many lines, the screen is repainted in a forward direction. -x The -xn command line option sets tab stops every n positions. The default for n is 8. -b The -bn command line option tells LESS to use a non- standard buffer size. There are two standard (default) buffer sizes, one is used when a file is being read and the other when a pipe (standard input) is being read. The current defaults are 5 buffers for files and 12 for pipes. (Buffers are 1024 bytes.) The number n speci- fies a different number of buffers to use. The -b may be followed by "f", in which case only the file default is changed, or by "p" in which case only the pipe default is changed. Otherwise, both are changed. -c Normally, when data is read by LESS, it is scanned to ensure that bit 7 (the high order bit) is turned off in each byte read, and to ensure that there are no null (zero) bytes in the data (null bytes are turned into "@" characters). If the data is known to be "clean", the -c command line option will tell LESS to skip this checking, causing an imperceptible speed improvement. (However, if the data is not "clean", unpredicatable results may occur.) + If a command line option begins with +, the remainder of that option is taken to be an initial command to LESS. For example, +G tells LESS to start at the end of the file rather than the beginning, and +/xyz tells it to start at the first occurence of "xyz" in the file. As a special case, + acts like +g; that is, it starts the display at the specified line number (however, see the caveat under the "g" command above). If the option starts with ++, the initial command applies to every file being viewed, not just the first one. When used on standard input (rather than a file), you can move backwards only a finite amount, corresponding to that portion of the file which is still buffered. Bugs: The program has problems when it is fed input as standard input, say from a pipe. eg. the following line (in Msh) will eventually cause something to bomb: $ cat foo | less .o 574 __pc_rea __pc_wri rta.o 88 _trap lmul.o 336 lmul ------------------------------------------------------------------------------- .  t ..  tARXX t START1_1 't LESS t BUFFERS Et N.  t ..  t ARXX ARC t cARXX DOCt % ARXX INFt )ARXXGEMLDIRt +ARCHIVE.HE ( /T4 1h)M T<8p2aȔc& 2 ̼ <%BH0H:hFH@v|P2e֤L@6d)2J!.$ b@"0j|񰬂;y" Ҹ`@1lICDr$ɐ,OD${6 B%ɐ"іQ-91е+0޾A<2ʗg.ㆌcsȩ31j\bOw);G5Tb۷ gDllI@ :s)"x Ȅ&û/fA}7~g0Gm ~e̱108xF8 ^GDa C֜AmAFtwi!Ay"u|BtnF[ Cb [<١g!sD8 @8nu`b'Fo! X$Io&8@)v&ȝafNl! d\ =!TdE0a 1!@ɑwD`7s|2g !HtifaC :j_\ h#BZ%TVŚo|i4R+EPHvY2H+E0aU .E`AA AE8ܻWH1yUAig@*鸃d?A QnY C<E>ARDEL.CH(E M0 /T4PBH!S1 F4!2tʀM0nȀ0FxLxp@:tДԑ7 N9bʔY31AM(Cpؐ*E"T&Ci0Z1#ҸæCЪeV#7sĨ%s7k۾p 4>83 S& )v ;rd-Z@a)D@c̖1dtّa@ݼYr8ybnLs U[6mrꌡsRu2h:<3l҈05ghRjgy(,paQFf J&xe̡au `e0mt } !"9$9PA 6`Ar *l(B,f`g GMyFstHAPv <9@̉XHd\ĜIoYx#nXFEX9qefp]AvГgF鍱ZwpGIdJk! (_Af L$h)¢FuȁWk^JhR Y@yscr%d|d^l" +=YnC*B ](  |+VFV86\oN۲5 pczp^#>i.:Ggr8V*_(J:afd0# jH 0vbk(q/~z ¾$"uetjolflKG Gj#1HrFIVCb6B n+2nk p]e|4r˩n-cلtxB_!MQ_=R )`90}n;|{κ{&t xt,#46VJ /xp`D#%I6>Y|1dh#YjV35 oecVV3j1f؊\ܰg{uY-4M(u0W)Br`XVE(LAREXTR.CUE % /T4PBH)ch!Ca1M6aܐa H u1#7n@ ≝2rĔ)bP!H)T q!$ 'EM#1`Q⋁Gq3M2e@CL7.H۸<L޽ Բu cФWZ+Frڼ!LI&#A@l҈0u-}h <QqyƋG0[ ز݄iSzjxݱ:\&+O!Yɿ1^'P1BU^wvea!|qFkZuau^7L$0a60|%FHdFI ac*~eQ=`DLLQVH-aF V!qI^R@Bȍ#Gv i&f Ę `\Uw ^'י)BVuȑX-IhZeR Y@Q)ARPq ɡ 0nZE6Y\%",EX~Ca&BwH# w>j4ds&Gd^uNT-uaN<1PdEr *lƚm؊kJx+l8YowV !bPl7aڔӧ2):cM:eЙb(StBޑfҰq#6VXv2ܽ-]qN]͞bwQ!EEs-p1Ƃi !BW\) 3͐,gkWp7} [W35j ԜM mp@ {D1-#NF\C0!vgqm9vfZBW20W /߀fߟ/K' iќjH~nMiϡBoA tjj9ƧȊ[;ϼtClz?aq F Уe 3ȆW< a}RG0a09IqI!SB4$sR7T |o{OmVfmclKDxh8Y6Ƈ.+nњު׼S| l +` ]@b{ڱ؟C>R+vngGS]PB3k}_ au-{rV>1zaCծc% MAEl8 + %X>yPevf$ |C*{ t[7 =à)8|@ن" (`@`DqSf)CD7@+spD7Hy$g6vC@@ "|)AЇS &CId:~ hcT&';DG/"I݂jS\&ҙT#ɕtQYݪ@IRljs 4Ȱ -Zar>"8"ƒp *;WfrC d Wphf2B[rļ!D4A@ctDC,J(뮵*nO9@pp*l9:Dԫ$GIt8v}]ԨK=#2*JTsFR%,LG],".# S(Bt [4 I9K <Ԩf59%B 3 sHX TO)ãMU CcHrLC*w@ F!}'B&: DAC~z ~_K  6$L=(VJGv '6˩P:yLΰ A+%NLꔐ(tJ,Q[2)l (F0]M=BeQC҅K("K(IuiRUʬGa5=qzfm(]SǢ Q|~.7بAssXS8n(N‡@Y WR3 ZE+gatVϺvDZ*@49;p V`ZǷ C4(rMs=ZӝA<BYC 7l={&N Qa6#jf0E2#\6ГQLoes.W,R1rF¦MdHY^#)x2z$Ip$D41tG>&EfI7ԿM&]. ($i(C@28PB]D %Z7ݔmQqZ?uk6d֫*;^I$m,c7-ވ:0Hm萛ma~SW;^Kg"yoIK$.*dL$S3 dTH(J| =KVPlդGALj T6j|e,gYK:ǚ/C[9j s)ӎL1& >'Ц}o3iX'9s.i,nWW7}5sm[!c}hޘ4곘ODV6hHk*l9!(FQxk!jYm:ۏ0 iۺ4⩳| Td~Ot&gv%z]oU;XtN{-?l*dUqK? PԀ_Ԫѕ$?W[6\M( ǕArӰ/^]9;\' >w5y;nmC|b_+R |)pi: _x[~WlU#R'ɃYv'*ꗘ((|L c{)i j'"pyedOyi'eBߤ8YF:\&^,fgxUfQ[M09fmp6rFg,pr:|VGh"h}jXhi?҂hi0G7cs-69~;6HB2RHGlWt4҄]5&jgH%"dp-3ZRDX4"aP&KfrDRPE/.~(t!-W|afaH.`Ya[b؇= Od&06xeC,WvKߖ&M pF~]>cZ[6Iv1av8rVpO#.4.NCPuFKxUS\1D^a0yGm%!auo|e:ux Ww91v]瘎rBPFH!Q,'epBne͸wThCH(戜H{&&W " 9bY;UK) WR-f>$Rgց{TF⢄AP?=Br/9Y 01@x5{dut0,Y@WED{i,,xp&e1gP!cZB4jʦCjp5$l@ib)xzzZNF:QTkJC{V&V& T !8DB_ZiUӨ`Y[‘L}=Ah6N7rJiv Ds1]i" ,6g*\:Țb/ s}̺Mz9.0u505oRUs05M:%݃Q)d@1,8UHIYAh؜H*2a;顨P?I<h@AMCM \ocENv蔠T*Ѡtp";pe(j"kaßAY2cH<BYԔ?Hd%hʳ4೾p?V sv@]Ja`tAi(`DUt97r0;=300.U3vqoS[{:Rdː}3l(0hkFnP. hfⅆe77s=*41q-鹞oLI{!&op걟Pk[;"ɵdl.'4qCCf ng~%i-.2@;"{u0n)~rӓ:#Ddc W~7$3;#9h7c&ARUTIL.CE ة\ /T4PBHȩC' ch:p༑C7 Ӹ)3 CA٨#7n@ 2rĔ)bP!H)T q!$ 'EM#1`Q⋁Gæ2 x#F$h|]- ǠI F5#.)UpM1#AL Ȏf!F 4<ɰ)"A@eԑ ҘڍjBl /T4PBHŘ!D&9 C'ME:yXS0cʴ)7f@(CăpDFT޸a''vS BÆ EP)2ą$T5AU[ĀF U8pD7c!S9tȤyw1w>.^|AP8MEU&](#RT=f pm[IIX)r 5 8od7S3uV|.M9sI +qf%cьG$@X B*GoBY4FQ}w͡lfa\Oͥ~8u$pGtB bGGBuh*4JsBi >׎eat`㇐hPxЁQB NHЃf 1䂚psSF&}y&Bɨԁ8g!\A膠tR!Fɗ]tzjn}}7ZT[ЅU7Iat 4F7%N&ce RTQ1`0e}F5ګ/Ӭ'AK+a#M:o$Q6\C=\Ǭݵ! Gw%9y֝a +􆓀ƽ ; lBj׍9 &nםÛ'pB |2p3*<ߧ4^50L%0ljtt pT7ܐ$0Fp' ` TѠ~ t0'ǛL$ \TFk_た EDI(BYJ2wrHZE@DC砋s_BX{}3/8Ļq@DLeȇ> WfKEZ_?G|;EG1K} NwH(K"4!c.]NpỂrݥOy۠-1pkSw;x/|e!p ծwiD_F҅6+CL#lE {Phjۦ9 e@A v6qL!;8 b`N(0m$3i\cd)OmZD w o&(\ 0Dv W4A?tb⽤7 FT Hpы`\[یFDq#g9y@ЦKRA#d@pH4srE GIRҒd44uea(5zʞdc4T|e.Gn&fIjfr #G"\c>:~€f( 6 lv@PΑ35 H1sZ15z!DGQқ6@]80(Ta`*5 1QJw5cIIN0kzF֚@"Y]m*\B+Ǫ(jP)0s J|h&. rd)3f !CξILKr4Il\T(281ttFx1AaN"A䂰bE9nV2Ԥ&Bّ)B8%U&ql)4u *rhE#$F :Xr\)_LQ(1NjjjI R ! Ź18ZzS`G]k*4EƈY.$wpd"2T2dxxxX get.!I:y5J>-ٱfk& xF s5SzO#à'%b0U8(&-hYmV pO)#RX.fujT[nDؙ=:oN\HM`ZF ưiZΫbɧARXX.INPCQE [ [氨D9x𸰁cM3e̡s /afF9eX9ɔy:mސa@:tҌf4nfBlQLMARXX.PRG#+J C\= `4 @OT@`D:hV(| 0^ " /9%N|py02iHHZ h8tr$@ +KM0|"s"Io(@]! d׉0n"p @_kFB\ d!Gu.^2$G@o @?~]+Y 3ׁ [ɹZH?xS@Ue!nCO!^ (+ B$@~ $5"" B !=lg 9(Qg ȭ8 U`!%#S7#aAm l ,\o 4HMg0x @@J- 7@{G ʛ@H7$9^.$?/(HB}#/[OP@#=;l s. >@d pRj~Z>Pl:+k4R,&l tO:.8b%1 `E:Y #J­+l)㖫,C#Қ- JzfwZ+$£0 4, :Cδ[,d++8Ҧo{7-=³:LoNp1TQhK@\5_=vNk$! ^ f.}*޳+9;5,$m1ϼqT@)@bS0 QzP'稆ΪƏx+OAdqࠫ'L|jo.$,5 .kJU\LZ*иD\?A)\>{ă ^87 U?Z +o(8 +oI@,L%'0IP[x(H(00G9 "Pܦk UcCqBLPL@O"{!H8Āq' @`,zтbY5Ƣ(H#!& A}Bx&HB $,j @P@ Yd!pK@  (e2z5B4)Qͬ^R&!,)G0LHTzC<azc@"@BdaG<0T@8M c'Sdӷ Oؕg?d '̀ˆ5k0|@pq<Gi 3Q|B%\" 6hd0Ȁb*4`>jP*Hph\J(}Hr?|BR4SXjQKzFH~H@+zgd]^iVX A)zӓ{qFWOB; O/E̴uVT\ 4l_U"Π`m셑5DB Z+dĀQ@[ ?B J.N5@V3[C @Ж= * 2H= !@6H#GDQiR4tB8|tܭȜ.KE/8 ^ v>B> h/OR@@3|كg@ќrBCò[0TC(?jAo <F uB,;tYJyP`S̀n(Tp| ps3ңxcX<йVIc2f,:9`C-nWD$\a6,0m|uA M_0=0FǍs`iW/Iq@} N7C6 `/`a+%eN~Bxu=q7?f<C(4nt'kHe wD/U0l4v ?qn <@}pX8z4Xl,R?,62V*b*P#Bp ;!z^ڢ}h }uLׅo{0iah0cR~[NhW f(qpg;uF:wI1YBHc~n!y ]S-TXiGzB4eք6w(kV|%y\8s`j f GFи~NoQele8x@]Ux|vf7X541*3&EX^7eY0f1JD<ZA%Z>AZ330Q1(yg Qc0/0 4gq0CHUcucp>R sE`J5˜P(ggY7F_jwr"?Tv),ɇ~(?[ؔx~@j:Wp0 8S;)s:4s>Q <|x'irǂf #Wh> `>tCZ4P,  :CY`,q=_AL7pr| uy`n?`>FT'iO+Z,  QZ5pYgМ@`)l閅*雂HO +ԠQ#Nx!Y za zkAŲ^G*20` ?, Cr) 1+ZᢏP#akVyqg 3<=YT=| ir Eq^O0 R,* P %e=3we *''d4`&JPHR9` `PCpа*2њG >0 `( fpM#9 91f@ P>VM GRp.zW2pBl G }&Jaq~PpZPg Jp Zav9!Z00*pfq35$V@=R8@p<'9*P,. >jЪ-" j @kذyްL3f*4˚jI W WP,04ܐ/fLa-4۴_4'Q `&k|nUĠ ǐkPx _e S0B#f`փ1tB5k] /H|~jYakK:{Y+`Sz+flIs: A(@} J а `y 0 $|RK*лb(pG` f_B ~+1$ Y ٖY DDŽ` m@ !"իŲ>  s> C>2LB0 m]!>  <mP,,*` `pS19 3:@ VLCF >–hl |JF` %G`X\ ^C!P1 +o>`/Rb Rp xEEJ`nټ4DB 0r1=KB :,JP,7 t`,8@;C@XaDLAi0s$";laL3[> 0K`hY|E4]uL T,Q9p`f1sQH,rӪ:mCW5X4HԜ*NK Tց 0M SL p҄ӿHY005lP<aCLG 4XU#IKpb{4#Pù$:p2aN:0ګ;Q,# ClV`FܻPTzBh'E)nR+ E@½ߚp'PfJPo-1Y G@P#%P kB  IAA}D;paNқa0pff-9PTp&6 8 Mo% @ A~+.m`o o.MB`BN!pS*@6n9^<DGo LnA -M$ / >n>)@h:wB{ dqyx% pB>Pp陾<@N` ~Bz@n떎1b 5>ƎGcx阾nCH! X$o$ʥ0..pЂ.>"%O!vX$ߺN*a8.݈sc%C:$:3;KP@&ޒ:аE Ex{0 E`XNafP] -` 0E~- ԦڏPP~aDKnym*`?#ݏ0޵Fo*?]Oq Tլ݃hpTҁ6 4gh=JPpPp˯LWlga@A A@U`1lѾ0 x˰$J\0ʯ P+,?*s&.@x `Sp4H"P.R@7` ?+z1zR` zӅ<*@k8 n> 0LjҰ "`1>PL?1,(`1>PL?ˎL Fʫ$S5N ssx}^LP>cg0d~% }mymG`OJ QJL`A< ڻ(0X9 *P:ZBÜA {= @`4]x.N  4 pRpҴ `_e bŔxi~*P`@ DhB X:\n' P `` B $C@ BLl9  HF9#p0aðF;#`ba0P G`*bĻS@~ 0O9p6 ; HF9#oُt9 ڻBÜA {= @`4];zİTĀȰ P P P 0 0(P: P20B2HBH `*4pJB H  b# V >c-A |IA A=@ f @0 BLF;#N*0 ҧpB2pIA A=; By0`@>b(P ҇0`ȰBN- mG`OJ"_oP p2 @n a p % Ρ9kQsN0¹+bC85HʙRb0! fо,PHpް0 G 21d+G԰VvK<TH` 6()q@w"mM2Az`p`"mMUz,5-`"`l`]'p- &D0 %.  `if:։G6Zqo`=YK<T"gJpl d\]'p- &`a`  PJpf@ 0\mH<Hp `=׭wz*0XG Z} ehjQ54/*cGLPLyds~T|ր:)T<(bho+,  VB@sM0׏@P ̠09 `A~* x>[`p'rW[a;[r]ܿG`Yr}<PC C;9  < l;ë`P=۾L`o@*`'y"J@f@| `0 A\;'`&/Ko@P fԁ'`4'=̻־P=̻pë`M0Ԍ۾L$0$&`o@*'`ѧr J@f@ +(F a n@- ]C10ѿ b`jQ 0[mW #] v]!@;z 0 0ǀv~: zBp`x`e0' {b``@`L`e@@``#p a Pyѣ=?=?;x/p<&k1 ؖ:\J?=?;x/p<&k1 /r:bj+BA!EDR RD. 0q 0 =fF&ݭ@ Ȑ4@>      ` !`  02q  & PQ!l!a Ȑ ` S@a0kO`e fop$0 utmP#N0L0T0!APBPDPRPTP!WPXP+"bPdPrPtPvPwPxt0 B0"11t0 B0"1101010AJ01AJ0AJ0AJ0AJ0@a x.@ma x:cno@xe i rr:%0rx 0antc etP i rr:%0rx prtP Pr o x Psa x:cno@ 0rPa@e%0PsgP:a xAr@xp[`][a xmd i  o@1md  [ `ierx Ps@o@ na ci`efra@rx eWr o x Ps-20 P6dP%90*V%P801a x:%0ti r0hvP `o mta x:cno@xe Psa x:%0tax!jPc@ `iepz Psrx 0antpPn%0rx Ps@o@ nbe0tflP Ps  Ps1 Psrx 0iee rrn%0,epPc@ez%d c@ul%da x:e rrloibo  0ie~_s0a x:%0ti r0hvP `o mta x:mdPlP Ps@o@ `oPn@rx 0antc etP Psx%0 a x:%0ti i rr `o mta x:mdPlP Ps@o@ `oPn@z%0 0a x:ny1mdPlP lopezi rxwa x:u@pPtmPs@ erPdrPc@ezfra xqrx Ps@o@ na ci`efra@rx o@ue%0tfudw%0rx eWr o x Psa x:w i@ee rrn%0rx 0ePke rrn%0*qumt0hPutPNmt0Mc?tc vPr`lw0 $ & n 0 `  0ѐg   s!0! v0!`v0!`v0!`v0 v0!`v0 v0 v0!`0!`v0!`v0!`v0!`v0!`v0 0!0! v0!`v0!`v0!`v0 ov0!`v0 v0 v0!``.V@ P. 0.`0. 0.T!! " `Đ @ 0PM(@m,VPr0in12 0oy iph@ c 93 @ipi@a e0er00 a5@3 16`$n  & `` 4(` @&`(`,6 `a`&@`h``j``"`j_0e F2"@``<@`  D`{`@&P(``"``h&`,֨@@@$ 0 ` ` ` n `D@ ` ` `M&  ';Dc`D&`  @@jCj`^`0M`0M`0M`&@@  R@Z \( ."`.6pC`$``` @ `@"```  @ *J5E `  6 @-------------------ARXX.DOC---------------------------------------------------- NAME arxx - an object library maintainer, replacement for ar68 SYNOPSIS arxx -trdwx[v] [-ab opmod] lib mod1 mod2 .... [>file] DESCRIPTION With arxx you can create and maintain object libraries to be used with LINK68. It has been written as a replacement for ar68, which did not function satisfactorily. In the command specify only one of the trdwx options. With the r option only one of the ab options may be specified. For details see below. 1. Listing the contents of the library: arxx -t[v] lib [>listfile] The t option will give you a list of the names and sizes of the object modules in the library on the standard output. When the v option is specified the global symbols for each module are also listed. Global symbols in the data segment are preceded by an '*'and external global symbols (common data, C external variables) are preceded by a '%'. 2. Replacing modules in the library: arxx -r[v] lib mod1 mod2 .... modn [>listfile] The library is scanned once and the modules are replaced in the order specified on the command line. When a module is not found in the library it is added at the end. It is possible to get the same module more than once in the library by specifying the wrong order. This is also the command to create a new library. With the v option the modules copied and replaced are listed on the standard output. 3. Inserting modules in the library: arxx -r[v] -ab opmod lib mod1 mod2 .... modn [>listfile] When you want to insert modules in the library use the a (after) or b (before) option (Only one allowed). This option has to be followed by the name of the library module after or before which the new modules have to be inserted. 3. Deleting modules from the library. arxx -d[v] lib mod1 mod2 ... modn [>listfile] Deletes the modules from the library. The modules have to be specified in library order because the library is only scanned once. 4. Extracting modules from the library. arxx -x[v] lib mod1 mod2 ... modn [>listfile] The library is searched only once to extract the modules specified on the command line. The object files are created in the default directory with the same names as in the library. If you want an other name and/or directory use the w option. 5. Writing modules from the library to an other directory/file. arxx -w[v] lib mod >newfile This works the same as the x option, but only one module may be extracted. This module is written to the standard output which has to be redirected to the diskfile of your choice. REMARKS Output redirection (necessary for -w, optional for the others)) has to be performed by the command interpreter. When using arxx as a TTP application from the desktop you are out of luck. When no v option is specified arxx performs it work silently (except for the t option). With the v option all actions (copying, adding, extracting, writing, deleting modules) are listed on the standard output. This is part 1 of a 2 part posting of ARXX. I wrote ARXX because my version of AR68 was unusable to make own object libraries for use with LINK68. AR68 could list the contents of a library and extract modules from the library, but replacing and adding new modules did not work correctly (e.g. all module names were converted to upper case when adding to the library, while extracting forced names to lower case -> impossible to extract modules you added yourself). Below you will find ARXX.DOC and an example of the output of the -tv option (list contents). Part 2 will consist of the uuencoded ARXX.ARC containing the sourcefiles, the program and ARXX.DOC. So if you can use it you only need part 2. Some things I found out while writing and testting ARXX: - stderr in ALCYON C (=Toolkit C compiler) uses GEMDOS handle 1, as does stdout. So redirecting stdout in Micro C-Shell redirects both stdout and stderr. - You can prevent redirection for the standard GEMDOS handles by using negative handles (word size). So writing to handle -1 is what I use to write error messages to the screen. - Detecting if GEMDOS handle 1 is redirected can be done by using the GEMDOS FSeek function (trap #1, function $42). When asking for the current position FSeek always returns 0 when handle 1 is used for the screen. Otherwise it returns a number greater than 0. Here follows as an example the output of 'arxx -tv gemlib' ------------------------------------------------------------------------------- channel5.o 1356 %__fds _maxfile *__chvec __allocc __freec __chinit ___chini __chkc xmainnw.o 2632 ___main _nowildc mallocdb.o 912 __errmal _malloc_ noascii.o 340 _noascii *___noasc __rdasc __wrtasc nobinary.o 344 _nobinar *___nobin __rdbin __wrtbin nodisk.o 492 _nodisk *___nodis __rdasc __rdbin __wrtasc __wrtbin nofilesz.o 152 _nofiles __filesz nofloat.o 496 _nofloat *___noflo __petoa __pftoa __pgtoa __atof nolong.o 260 _nolong *___nolon ___prtld nottyin.o 240 _nottyin __ttyin access.o 576 _access _chmod _chown atoi.o 500 _atoi atol.o 548 _atol calloc.o 476 _zalloc _calloc exec.o 588 _execl fdopen.o 660 _fdopen fgets.o 392 _fgets fopen.o 1432 __fopen _fopen _fopena _fopenb fputs.o 324 _fputs fread.o 492 _fread freopen.o 1408 __freope _freopen _freopa _freopb fseek.o 476 _fseek _rewind ftell.o 880 _ftell fwrite.o 468 _fwrite getl.o 280 _getl getpass.o 1224 _getpass gets.o 356 _gets getw.o 240 _getw main.o 404 __main mktemp.o 536 _mktemp getpid.o 96 _getpid optoff.o 516 __optoff perror.o 2456 *_sys_ner _perror *_sys_err printf.o 344 _printf _fprintf putl.o 356 _putl puts.o 320 _puts putw.o 356 _putw qsort.o 1316 _qsort rand.o 660 _rand _srand readl.o 428 _readl rename.o 400 _rename strrchr.o 492 _strrchr _rindex scanf.o 344 _scanf _fscanf setbuf.o 288 _setbuf sgtty.o 584 _stty _gtty sscanf.o 420 _sscanf doscan.o 5572 __doscan fgetc.o 252 _fgetc filbuf.o 844 __filbuf read.o 634 _read readasc.o 1140 __rdasc __cr_col readbin.o 444 __rdbin swab.o 292 _swab ttyin.o 976 __ttyin ttyinraw.o 716 __ttyinr _ttyinra ungetc.o 304 _ungetc unlink.o 336 _unlink writel.o 428 _writel xmain.o 3184 ___main creat.o 1088 __creat _creat _creata _creatb exit.o 160 _exit cleanup.o 268 __cleanu fclose.o 344 _fclose close.o 596 _close fdecls.o 548 *__iob ___fdecl fflush.o 560 _fflush open.o 1272 __open _open _opena _openb lseek.o 508 _lseek _tell filesz.o 392 __filesz sprintf.o 372 _sprintf doprt.o 4300 __doprt doprtfp.o 820 __pftoa __petoa __pgtoa fputn.o 464 _fputn fputc.o 296 _fputc flsbuf.o 1060 __flsbuf isatty.o 584 _isatty _isdev _ttyname malloc.o 2704 *__afreeb *__aflist _malloc _free _realloc mallocnd.o 144 _malloc_ __errmal prtint.o 496 ___prtin prtld.o 856 ___prtld prtshort.o 500 ___prtsh sbrk.o 464 _sbrk write.o 548 _write channels.o 1300 %__fds *__chvec __allocc __freec __chinit ___chini __chkc writeasc.o 968 __wrtasc writebin.o 468 __wrtbin blkio.o 524 __blkio osattr.o 180 *_os_abil *_os_vers _osattr wrtchr.o 1284 __wrtchr lstout.o 320 __lstout ttyout.o 576 __ttyout xopen.o 956 ___open chkuser.o 208 __chkuse __uchkus errno.o 80 *_errno *__errcpm strchr.o 404 _strchr _index parsefn.o 420 __parsef blkfill.o 240 _blkfill blkmove.o 448 _blkmove strcat.o 296 _strcat strcmp.o 344 _strcmp strncat.o 360 _strncat strncmp.o 328 _strncmp strncpy.o 384 _strncpy strcpy.o 224 _strcpy strlen.o 232 _strlen ctype.o 356 *___atab ____atab xstrcmp.o 496 __strcmp yesfloat.o 88 _nofloat yesstart.o 88 _nostart xatof.o 136 __atof abort.o 88 _abort aldiv.o 192 _aldiv aldiv almul.o 108 almul alrem.o 204 %_ldivr _alrem alrem ldiv.o 652 %_ldivr _ldiv ldiv lrem.o 188 %_ldivr _lrem lrem salloc.o 176 __salloc setjmp.o 136 _setjmp _longjmp uldiv.o 552 *_uldivr _uldiv signal.o 1272 _signal __setvec xsignal.o 308 __illins __trace __trap __buserr __arith blivot.o 280 _sw_ pcr.o 574 __pc_rea __pc_wri rta.o 88 _trap lmul.o 336 lmul ------------------------------------------------------------------------------- `[vT*o m$m,B"H"&J$YJf"Jg< Af Rf Gf Vf =f Jg#mB "&J$YJf"JfB&f2 "n$EHB2 r ggSJ"g fB*`B" n*#n*.I+I/ #^//SA?/ / Bg?<JNA Jf p?NGp>BgNG>NHXON< ?NF?<LNANVH *yoog -og$*U`*yo +yo 9o-@Jy:gLRo 9oogL/9o 9oS//<^HnN;OHnN ^XON`Bg/.?9^NHvPOBF0<F?0HЍ/0< Hї /?9^NI.PO>o G0 @mJGl/<_N ^XON Fl0HЮ#o0FB( Jyf$SFm 0F( 0FJ( f0Fp@@ ` U!m m *o+|o yo!M#o Mo( HL N^NuNVH *yoog -fp`*U`B@L N^NuNVH../</NQPO-@Jy:g 9oRg/.NXOJ@g"#o/</NRBPO3oB@`pLN^NuNVJy:g NJ@g"`p?B?9^NHvPO/NXOHB@N^NuNVJy:g 9o`p?B?9^NHvPON^NuNV29oH 9oЁN^NuNVH yo (of yoo( H`N>JGgRyo09o @m ByoRo0LN^NuNVHSyol>JooJy:g 9oS/NXOJ@f ByoB@`>3oSo yo (of yoo( H`N>0LN^NuNVH 09TnlJ^g/9^N9XO?< ?.N7XO#^3TJ^fzp??9T/<_ HnN;O HnN ^XO?< p?N7XO#^p3TJ^f*p?/<_;HnN;O HnN ^XON*y^09T й^ܰcA *A+Hp+@ `09TS@ @^ <o y^!@#^o09TS@ й^#o p#op#oBNXOHL N^NuNV#oovN^NuNV oovfp`8Sov yovH?NTOJ@g N4SyozN4SyozB@N^NuNVN3N4,9o|H?N TOp3ozN^NuNV0.yg yRo p?NjTON"N3N4,NN>J9o|g9o|H @:g G g G f yovBN9o|H @Ef&-|o n  fR`/.NXO`P9o|H @!f(/<oN5@XO/<_jN ^XO#_p`?/<o9o|H?N(POB9o|`9o|H @:f& G0m G9oyB9o|yyyN 4XO/<deN 4XO/<d/<dHnN;O HnN 4XO/<dN ^XON^NuNVHBG Gl4N4/<dN 4XO0J@g @g `N `N RG`LN^NuNVH g/.NXOJ@gp`N>JGgN G gJGf N-@`"?N0TOJ@g NS`N>`BgN0TOHJyg. yJf$N> @ gJGgN,HN-@ .LN^NuNV gJo .S/NXOJ@gp`JygHNHN=@N,HN,H n fN,=@ @ gJngNHN,=@ n fNR-@` JnfB/.NXOJ@fp-nN N=@R n g4?.N0TOJ@gBgN0TOHN,HS` .mBgN0TOH .N^NuNV <o#s#BysBysN^NuNVHJnfd09sS@ @bDH0@]p PN`4"ys ys ys|Rs` ysRsBys ysB` stscp`JyLf09s @b~H0@]| PN`l osc^ ys (fN n_g ys (_g Us`.09NyXysR@yBlvJyXo6 osc* ys ( f ys|09XS@ys`0"ys ysi ys|09XysRs n_f ys(H=@Usp3s`x09NysR@yBlp` nfp`"ys ys ys|09NysJyXo ys  fSys`RsBys n fXJysg09N`B@ysR@yBn<09sR@3s09sHH@J@f ysRsp ` nfh yLfFJysg09N`B@ysT@yBn09sT@3s ysRs`Sys ysRsp`?.NTOJ@gRJysg09N`B@ysT@yBnd09sT@3s&ysRs?.NTO@`DJysg09N`B@ysR@yBn09sR@3s ysRs0.B@LN^NuNVH g/.NXOJ@f N>fp`J*|o G gJGf N-@`se NS`N>`B#o .L N^NuNVH gJo .S/NXOJ@gp`D*|sB%N,> G fNR-@`JGfB` oc`# .L N^NuNVHJg nJf809Vy,m/<e*N ^XO``29VH AH -@/<e:/.N:POJ@fBGJy^o?9^NGlTOp38/./<N:POJGfp`B@3:3^Jy:g?9`?9>NTON'J.g #.JyPgp3e&Jye(g/.N ^XOBye(Jg yJfp?N%TO`dN!`\Bg/.NH\O>l/.N @ ?/<egN/<eMN ^`$0.yV09VH @H/NXON^NuNV09Vnl/<eaN ^`$0.yV09VH @H/NXON^NuNVHN>g ?N TO`NLN^NuNVND/<eyN7XO/NVXOX Sno8"n Q -g "n Q +f "n QJ(g n X /NVXO`/<e~N7XO#4J4g y4Jf #e43,# HByVp?N5TO3PJyPfV y,l/<eNXON`,BNXOJy^mNRyV09Vy,mBgNFTON24N6N3Jyl09S@3JygSy/<N:`XOJ@gN y,l/<eNXO`DBNXOJy^l2p ?N TOp ?N TONRyV09Vy,mJy^mNhNN^NuNVN3N4,N3:NBgNFTON^NuNVH0B.Bp3>p 3(|@pb*|eJg& m0-H@g H@ `BL0N^NuNVH 0. @bg*|eJgV-H@gHnf-H@@f mJPfp m0 m0H @/(`*`?.NTOJ@g?.NTO?/<i` ?./<iHnN;O /<@Hn/<iHnN;O`?9T/<iw`-H@g*Hnf -H@@f m0 @f>B@`:-H@g,H@ nf-H@@f m0 @gp`-H@gHnf-H@@f m?/-HnN;O HnN ^XOL N^NuNVH Jg nJg~ nRH> |]2<`XHXW PN` n +fR .#.#`0 nH @fg& @pg6pb?HnN\O33>`vRpb?HnN\O`Rpb?HnN\O3`B*|eJg-H@gH0Af Jmg"B@` -H@gH0Af mgp m0`-H@gHA 0Af mgp`-H@gH0Af?HnN\O``^?/<iN;\Op?NFTOL N^NuNVH n *@ 0m 9o?. /<iN;\Op?NFTOBG 0m 9nH0 A@0>` n 0L N^NuNVH JfJyg <i` <i#BF*yJgH> |]2<`XHXW PN`N4yX`N4yN`xp ?N TORF0HH@J@f`XN5SF`N0@g:0@ @Mfp ?N TO`,p^?N TO0@?N TOTF` ?N TORFR`>yBmJy@gJyDg p ?N TOL N^NuNV n m nfp`B@N^NuNV nfp?`0.@@N^NuNV 9is?/<sp?NNPO#siN^NuNV#siN^NuNV wieN yiRi0.N^NuNVH *nJgH?N TO`L N^NuNVHN3N4,N4N/.N 4XO/<iN 4XON4pN6> G g G g G gw#w/.N;XOyBoN%xLN^NuNV29JGo&2H"Aw2SAH Aw "SG`#wLN^NuNVHBGyl0H @wp RG`LN^NuNVH .wm*~yl 0H @w .l0S@`RG`pLN^NuNVJyfN3`N3N^NuNVp?N!TO-@ g NfRyRN^NuNVH>.BnBnJng09S@Glp`B@=@JnfJyg 09S@GnN4N3^p=@` N3N4,p?N!TO g.BnynJng09S@Glp`B@=@ByRSGmP/. NXO-@ f Jng0B/. N!XORnJnfN3^N3Nd`N"JnfN"j` JngN%xLN^NuNVp?N!TO-@ fN"jRyR`?. Bg/.?.N"O N^NuNVBgN!TO-@ fN"j`?. Bg/.?.N$O N^NuNVByRBgBg/.09S@?N"O N^NuNVBgN!TO/N%NXON^NuNVNBJ@g/<jN ^XO`>N-@N4N!/.N!XOBgBg/.09S@?N$O N^NuNVH>.BNXOJ@g/<j,N ^`,SGoN< @ gJFf/<jL`N/N%NXOLN^NuNVN-@ f/<jbN ^`0pd/Hn0.H/NRPO/NQPO-@/.N&XON^NuNVHBgN!TO-@ .l|ynF/.NXO-@ g .nBgp?BgN!TO/?N$`RF`/.NXOJ@g/<j|N ^XO``N,> @ gJGf G fNHN/N%N`/.N" XO<mBgp?p?N!TO/?N"O LN^NuNVBn nl0.H @yp Rn`N^NuNV nam nzo/<jN ^XOp`B@N^NuNVH?.N'TOJ@f 0.@aH&@yBgN!TO&LN^NuNV?.N'TOJ@f<0.@aH @y -@ f/<jN ^` /.N&XON^NuNVH>. n/fp`B@</. N,XO-@g/.N ^`BgN!TO fB`$JFfBg`Jygp`p?N!TO-@ f/<j`JFg-n/.NXO-@`/.NNXO-@-n f /<j`d/9N/XO @fSGn/.N&XOLN^NuNVJy:f./</<j/<yN;XOHy/N;O N^NuNV y,o8?9,09VR@?/<j/<yN;XOHy/N;O N^NuNVp?N!TO-@ gh/./<k/<yN;XOHy/N;O N-@Jo,/./<k /<yN;XOHy/N;O N^NuNVp?N!TO-@N-@JoT gJHnpd//.NRPO/NQPO//<k/<yN;XOHy/N;O ` JngN* N^NuNV/<k/<yN:~PO09VR@y,l>09VR@H @H//<k/<yN;XOHy/N;O N^NuNVB9yN)N)N* BgN*TON 0@yB <yN^NuNVB9y09* @bH0@] PN`rJy8g N)N)JyRgVN+2`NJy8g N)N)JyRfp?`$N)Jy8gN)N* JyRfBgN*TOBy8J9yfB`09BU@0@yB <yN^NuNVH0*n(|{vBnBA-H gJfJf <kL`0B`* ^f p}R`B9}}veB9{vBn 9k,`H>f0 /0<H gB9{vBn <kk`p B` G*g-L0 |]2<`XHXW PN`p`Jg n gt n  gh n`VJfVp `pBp=@H> @^f H>p@JGfB9{vBn <kx` G-fDJ,g>H> @]f p-Rn`D,H0Ao,HR@RRn}ve`Rn}vdH> @]f0. n@`H> @(f: n mB9{vBn <k` nR0.p0.Rn` G)f8 /0<H eB9{vBn <k`6p S n` G1m G:lp0@1`xp`L0N^NuNVH0*n(|{vBG G l$0H @}vB0H @}BRG`J9}g/ / N/PO`N f0,H>H0Af/ / N/PO=@g0.`Jf`/ / N/PO=@fJfB@L0N^NuNVH8*n(n HS@ @bH0@] PN`HHAgB@`Jf`J`p`p?H?/ N1POJ@gH`Bg`HH @}v `HH @}`H=@H @}Jfp` / ?.N1\OJ@gp2.H"A}v2.H A} `H=@H @}Jg&M0.H"@}v0.H @} =@/ ?.N1\OJ@g`e/ / N/PO=@g0.`h`&MJg@`&MHHAgR`,&M ,fp?`BgH?/ N1POJ@fHS/ / N/PO=@fb`fL8N^NuNVH0>.*n 2H A}v (@HHAf0H @}ep`B@L0N^NuNVH *n>. JGg$H<SFgH0Af0.` Jnfp`B@L N^NuNVp3HR@0@kH@f0 > .f@RHR@0@kH@g H@0@HR@0@kH@gR` *f R0 >JgH? nNTO`JGg.|0H@0H>JGo9kH? nNTOSG`L N^NuNVp3}N^NuNVNp?NQTO@.H@N^NuNVH *nBEBGH< @ g F fR` F-fz` F+fRH< @0m F9n2A00 A>`JEf0D@>0L N^NuNVH0.. Hn/NRPO./N8XO*@ g (MJgBS` L0N^NuNV0. H/0.H/N7PON^NuNVH8&y^ g.*[ g((nHHAfJgR`Jf -=f `BL8N^NuNVH8..P b d.</NJNXO*@ gbJlf&M #lt#lp`( 9lf QP&m`(ylQ L (&@)M Q@* Ѝ#l(@Q LB)KL8N^NuNVH0B .\S@,dB`Й*ylp g| .gR g @є .*L(Md<  d @*`* Ѝ#lp ylp /0<Hї `dJg @Ѝ*@`*mlpf g#lpRylx09lx @e Sylx`N/N8@XO/.N8XO*@Sylx L0N^NuNVB0./N8XON^NuNVH ./0<H *@Jf8BG0GlzJg$0RG0@lzH?p?p?NQ\O`NGTL N^Nu o B@Nu o0/,X.X.NuNVH0*n (nJfSf .L0N^NuNVH0*n(n BAB@AfJfB@` BA$B@AL0N^NuNVH0*n (nf .L0N^NuNVH0*n(MJf S@L0N^NuNVH0#l/<lN7XO#lf #ll/<lN7XO*@ f*|lp?/9l/ NZO (@ g/9^/<l/ NJO `p ?/<lp?NNPOpL0N^NuNVHn/<mHN<2PON^NuNVHn /.N<2PON^NuNVHn?</.N@O Hn HnN<2POHnBgN@~\ON^NuNVH0-n n (@XH> @%gJGg8/.?N@~\O`p =@p=@p=@H> G-fp=@H>`Bn G0f p0=@H> G*f( nT0=@lp=@0.D@=@H>`,Bn G0m" G9n2. 0A@0=@H>` G.fNH> G*f nT0=@H>`,Bn G0m" G9n2. 0A@0=@H>` Glf&H> Gdg Gog Gug Gxf0@> /0<^H -@*@0 |^R2<`XHXW PN`N nT0=@Jnl0.D@=@p-p ??./ N?PO*@`p ? nT?`p`p` n -@XJl .D-@p-p ?/./ N@ O `p ? n// N@ O *@X`p`p` n "(-@-AP/ ?././.?NGO`P n -@f-|^JX .-@*@JgJnm noS`* nT0@` n//.N<2PO`X ./0.Hї =@lBnJnf0.SnJ@g/.?.N@~\O` .d/. nRH?N@~\O`Jng0.SnJ@g/.?.N@~\O`L0N^NuNVH >. *NB%0<g"0H@H@B@H@ @^>`?Bg _^Jg nR` .L N^NuNVH0*n>.IB$B0//. NRPO-@g$B0//. NSPO @^-n ` n ^Jg` L0N^NuNVH >.*n G f-H@g/ p ?N@~\OSm l/ ? mN\O` UR0L N^NuNVH *n .*+@+|ED0. D@;@ m l;| +|AD`+|A( L N^NuNVH *n Bm pL N^NuNVH *n ;| UR0.L N^NuNVH *nJfL-H@f.-H?N5TO>gmHf?<N9TO+@f+|D+|E`^-H@f"JGgmHf+|C+|F( -`&+|C+|E`-H?NB0TOHЭ*+@Bm L N^NuNVHp?B?.NHvPO. fB@`0H@LN^NuNVH *n/ NAjXO/ mNXOL N^NuNVH >.*n / NAjXO/ ? mN\OL N^NuNVH *|m*leJg /NBXOY`L N^NuNVH *n-H@fp`@/ NCbXO>-H?NGlTOJg-H@f /-N9XOB-0L N^NuNVH *nBm By^-H@fj >o.?/--H?NNPO20Ag09^H f,JGm$ -/0<Hї f -*+@`+UB@`-pL N^NuNVH *n/ NCbXOJ@gp` F(mZf/<mHNCbXOBy^ -/0<Hї ?/--H?NI.POD@;@ @f09^H g-Bm `Jm f -@`z0- Rm m+H URB@L N^NuNVH *n F(mZf/<mHNCbXOBm By^p?Hn-H?NI.PO @g J@g B@.` 09^H g-`-@pL N^NuNVH *nBm pL N^NuNVH *n / NFXOJ@gp`$ -/0<Hї S@;@ UR0.L N^NuNVH >.*n GBm By^-H@fF/ NFXOJ@f8p?Hn-H?NNPO @f0`09^H g-pL N^NuNVH *n Bm -/0<Hї f/ NCbXOJ@f UR0. @ f/ NCbXOJ@gp`0.L N^NuNVH *n-H@g / mNXO -b / NCbXO`4p? H/-H?NHvPO fp` +UBm B@L N^NuNVNB?.NGTTON^NuNV/<m|/<mbN;PO/<m/<mbN;POp?NFTON^NuNV?.pL?NQXON^NuNV nn?.NO$TO @Cg,p???.NO~XO?.p>?NQXO/NP XO`B@N^NuNVH?.pE?NQXO>l0H/NP XO`"?.NO$TO=@m?.?NO~XO0LN^NuNVH?.?. rF?NQ\O>J@f(?.NO$TO=@m?.?. NO~XO0. `0H/NP XOLN^NuNV?.?./. pB?NQO /NP XON^NuNVH?. /.p=?NQPO>lF Gm Gn?NOTO>`:Hn/.NMZPOJ@m&?. /.p=?NQPO>mpF??NO~XO0H/NP XOLN^NuNVH ?.NO$TO @CfBF9}H>9}HH}*@SnmSGllpQ}B9}/<}p ?NQ\O>l0H`p ?p?p?NQ\Op}9}H>9}HH}*@0Gp H: EfBG` n R RF E ff }}}0`(/. 0.H/?.p??NQO /NP XOL N^NuNVH..Jl <`4Jf 9`(R @./pH?NQ\O,g І# LN^NuNV0.H/NJNXON^NuNVH0*n(n H>gH0AgB@`pL0N^NuNVH8/9mN;XO@>Jg(&n*S g/<m/ NJPOJ@fRGJfX`&n *S g RGJfX`0@@0H/pH?NQ\O(@-@fp`NJg&&n*S g/<m/ NJPOJ@ffX`*|mfS*ymH<g`-L&n *S gfX`B gR-Lp*nJfJg* ./0<Hї cJg`R MJgp ` ./0<Hї cB`BgNLTO/././.BgpK?NQO>l2Hn/.NMZPOJ@m/././.BgpK?NQO>p?NLTO/.pI?NQ\O0H/NP XOL8N^NuNVN^NuNVH *nHH. :gB@``a  e  d03~V0r.J~Rf p?NQTO?p?NQXO#~R ~RL N^NuNVH *n "|~X M0<`RHRIQ/<~vp?NQ\OBFp?/.pN?NQPO>lJFff/.NLXOJ@fp?NQTO3~VmB?9~Vp ?NQXOJo,09~V@am?</<mpN?NQPORF`~/.NLXOJ@g n (\fT n \g n .f* nJ(g n (.f nJ(f p;@B@`v0H/NP XO`f9~H;@+y~/9~NPBXO+@+@+@9~HA9~HHA:9~HA9~HHA;@0L N^NuNV/. 0.H/?.p@?NQO /NP XON^NuNVH >.0V@ @bH0@^ PN`*pP`&pA`"pC`JGm*ymSGmJfp`JgHL N^NuNVH >.JGm*ymSGmJfp` Jg0. L N^NuNVH >.0V@ @bJH0@^ PN*ymH0Ag Jfp`&R` m?NGTO`~C`~A`~P`pL N^NuNVJl .D3^p` .N^NuNVH .>0@HH@ </<3B0/NRPO*0@H@H@B@H@ @m ڀ/<QRF2I0@A@H@B@H@/NRPOڀRF0@f0@H @cQ .>0@H6CK<@/<0@HHH@B@H@/NRPO?BgПڀ LN^NuNVH HnNWXXO*@4B2-I0-HHABH.p".Rm2-I0- @PHHAmHހ L N^Nu#~NA y~N#~NM y~N"o`CH0"/ jD$jDANRrJ/ j JgRJk`JjDL Nu o"`"/H8(jD$/jDFANRJjDLNu"o`CH0$jD"/ ANRr g J/ j L NupJfpNd⒲ed`<dFN0&HCHCBCЃ&HCHCBCЃN"o`CH0"/ $ANRrL Nu o"`"/H0$/ ANRL Nu"o`CH0$"/ ANRr L NuNVH009nRynJ@n/<nN7XO*@ gBn.(|n2Jg :g nQd`BJg :fp @eBG0H@B@H@n(@p  n0(> @ eBG0H@B@H@n(@p  n0(> @ e0 @0`p 0 H@@0p  n0(> @00 H@@0p: n0(> @00 H@@0p: n0> @00 H@@0p  n0( @l>0@00H@>0d@00dH@>0 @00 H@@0p B <nL0N^NuNVH n -@lB/<Q/.NQPO=@/<Q/.NRBPO-@/</.NQPO3~/</.NRBPO-@p.0HH@J@g.0HH@J@g0HdH@J@g0HH@J@fp`B@LN^NuNV nf?. NXTOJ@gp`p`0nnHN^NuNVHJ9nRg9nH09~Am9nH09~Af8/<nNZ0XO>y~g y~lpp`n9nH09~AmZ`9nH09~Am9nH09~Af4/<nNZ0XO>y~g y~o`9nHS@y~nB@LN^NuNVH *n-H<f-H`L-H09~y~A>JFoJGo_G`^GSFn` 0y~nH0Al^G`_GRFm0L N^NuNVH8*n g(|~H>g G,g O>fU>FX>jc>d=e>nf>ng>no>r>s>u=x> D<^0123456789ABCDEFOJONORPP PPipe error: last %ld, want %ld read errorCannot allocate %d buffers. Using %d buffers.Cannot even allocate %d buffers! Quitting. !donerdit: Cannot edit standard input%s %sRmark: goto mark: f, SPACE Forward one screen. b Backward one screen. e, j, CR * Forward N lines, default 1. y, k * Backward N lines, default 1. d * Forward N lines, default 10 or last N to d or u command. u * Backward N lines, default 10 or last N to d or u command. r Repaint screen. g * Go to line N, default 1. G * Like g, but default is last line in file. = Print current file name /pattern * Search forward for N-th occurence of pattern. ?pattern * Search backward for N-th occurence of pattern. n * Repeat previous search (for N-th occurence). q Exit. More help...R Repaint screen, discarding buffered input. p, % * Position to N percent into the file. m Mark the current position with . ' Return to a previously marked position. -X Toggle a flag (X may be one of "%s"). E [file] Examine a new file. N Examine the next file (from the command line). P Examine the previous file (from the command line). V Print version number. !command Passes the command to a shell to be executed. v Edit the current file with $EDITOR (default %s). viCommands marked with * may be preceeded by a number, N. No current file-Cannot open %.*sNo (N-th) next fileNo (N-th) previous fileLESSEDITORvi--cffdA2e>ffhfpgg<xgcsg}gtggwhh>m*hdhxhqhhiuLii@i`Don't assume data is cleanAssume data is cleanDon't quit at end-of-fileQuit at end-of-fileBackwards scroll limit is %d linesRepaint by scrolling from bottom of screenRepaint by painting from top of screenTab stops every %d spacesDon't squeeze multiple blank linesSqueeze multiple blank linesForward search starts from bottom of screenForward search starts from top of screenDisplay nothing for lines after end-of-fileDisplay ~ for lines after end-of-filePrompt with a colonPrompt with a messagePrompt with a verbose messageRing the bell for errors AND at eof/bofRing the bell for errors but not at eof/bofNever ring the bellUnderlined text displayed in underline modeAll backspaces cause overstrikeBackspaces print as ^H%d buffers^%c%c"-%s": no such flag. Use one of "%s""-%c": invalid flag number is required after -%c s (press RETURN)~ ...skipping... Cannot seek to end of fileCannot get to beginning of fileFile is not that longDon't know length of fileCannot seek to that positionChoose a letter between 'a' and 'z'mark not setNothing to searchPattern not found%s (file %d of %d) byte %ld/%ld (%ld%%) END - Next: %s k0Regular expression too longNo previous regular expressionunmatched \(missing ]too many \(\) pairsunmatched \)KEY%+ %+ pqHY8 LD! con:@(#) less version ST.1@@@@@@@@@PPPPP@@@@@@@@@@@@@@@@@@    @Bad pointer in free. llNo shell msh.prg-cSHELLmsh.prgPATH,\bin,\usr\binm.mHmbBpB BpB BpB You must compile with the -f flag to include printf() floating point. ARGV=?:\*.*mCCAP????????????????????????(ހMȀv4~@\gUn(?GMT-1.1.4:-1.1.10:2:60...... AAA AAA DD DD:DD:DD DDDD SunMonTueWedThuFriSatJanFebMarAprMayJunJulAugSepOctNovDecTIMEZONEH"2,    &: :           $                                      *   4                         .   4 *              @          Pd :.&  2 D  " P &   $ ( $   $               (     .( ,         &      "  & ~ H: B T &@:"N8d              2*("T& :l "BJ.  .   >>HRBNB$(JF DD | 4<.&4(*\(\ TVHJ ,   (&    6 .F<& Z   `   4 &4@ 2B *f(X(H.z$$   $        $0d2&: " t          88   &0       rJlDV^R 0[D2T. F tN.. F t BUFFERS MODHt OBUFFERS PRGLt U README JKLPt X(****************************************************************) (* *) (* LaPeer Systems, Inc. has developed this software for the *) (* benefit of the Public Domain. For our benefit, please *) (* credit LSI whenever it is used. *) (* *) (* Jerry K. LaPeer *) (* *) (* Compu-Serve 76657,754 *) (* GEnie J.K.LAPEER *) (* *) (****************************************************************) MODULE Buffers; (*$A- *) (*$F- *) (*$T- *) (*$S- *) FROM SYSTEM IMPORT ADDRESS, ADR, TSIZE, NULL, CODE; FROM GEMX IMPORT BasePageAddress, BasePageType; FROM XBIOS IMPORT SuperExec; FROM GEMDOS IMPORT Alloc, TermRes; IMPORT GEMDOS, SYSCIO; CONST NumbOfFatBuffers = 16; NumbOfDirBuffers = 16; TYPE BCBPtr = POINTER TO BCBDef; BCBDef = RECORD blink : BCBPtr; (* next BCB *) bbufdrv : INTEGER; (* drive#, or -1 *) bbuftyp : INTEGER; (* buffer type *) bbufrec : INTEGER; (* record# cached here *) bdirty : INTEGER; (* dirty flag *) bdm : ADDRESS; (* Ptr to Drive Media *) bbufr : ADDRESS; (* Ptr to buffer itself *) END; DiskBuffDef = ARRAY[0..511] OF CHAR; VAR BCBfatBuf : POINTER TO ARRAY[1..NumbOfFatBuffers] OF BCBDef; DiskFatBuff : POINTER TO ARRAY[1..NumbOfFatBuffers] OF DiskBuffDef; BCBDirBuf : POINTER TO ARRAY[1..NumbOfDirBuffers] OF BCBDef; DiskDirBuff : POINTER TO ARRAY[1..NumbOfDirBuffers] OF DiskBuffDef; VAR SysFatBCB [000004b2H] : BCBPtr; SysDirBCB[000004b6H] : BCBPtr; TempBCB : BCBPtr; (* The following compiler directive stops the compiler from generating the normal Modula-2 entry/exit code for the next procedure. This is needed as this routine is called in supervisor mode *) (*$P- Stop entry/exit code for next procedure *) PROCEDURE InstallFatBuffers; CONST RTS = 04E75H;(* ML return from subroutine opcode *) BEGIN (* InstallFatBuffers *) IF LONGCARD(SysFatBCB) = 0 THEN HALT; END; TempBCB := SysFatBCB; WHILE LONGCARD(TempBCB^.blink) # 0 DO TempBCB := TempBCB^.blink; END; TempBCB^.blink := ADDRESS(BCBfatBuf); CODE(RTS); (* code to return to calling BIOS function *) END InstallFatBuffers; (*$P+ Resume previous entry/exit code *) (*$P- Stop entry/exit code for next procedure *) PROCEDURE InstallDirBuffers; CONST RTS = 04E75H;(* ML return from subroutine opcode *) BEGIN (* InstallDirBuffers *) IF LONGCARD(SysDirBCB) = 0 THEN HALT; END; TempBCB := SysDirBCB; WHILE LONGCARD(TempBCB^.blink) # 0 DO TempBCB := TempBCB^.blink; END; TempBCB^.blink := ADDRESS(BCBDirBuf); CODE(RTS); (* code to return to calling BIOS function *) END InstallDirBuffers; (*$P+ Resume previous entry/exit code *) PROCEDURE Initialise () : BOOLEAN; VAR c : CARDINAL; BEGIN Alloc(LONGCARD(TSIZE(BCBDef) * NumbOfFatBuffers), BCBfatBuf); Alloc(LONGCARD(TSIZE(DiskBuffDef) * NumbOfFatBuffers), DiskFatBuff); FOR c := 1 TO NumbOfFatBuffers DO WITH BCBfatBuf^[c] DO blink := ADR(BCBfatBuf^[c + 1]); bbufdrv := -1; bbuftyp := 0; bbufrec := 0; bdirty := 0; bdm := ADDRESS(0); bbufr := ADR(DiskFatBuff^[c]); END; END; BCBfatBuf^[NumbOfFatBuffers].blink := BCBPtr(0); Alloc(LONGCARD(TSIZE(BCBDef) * NumbOfDirBuffers), BCBDirBuf); Alloc(LONGCARD(TSIZE(DiskBuffDef) * NumbOfDirBuffers), DiskDirBuff); FOR c := 1 TO NumbOfDirBuffers DO WITH BCBDirBuf^[c] DO blink := ADR(BCBDirBuf^[c + 1]); bbufdrv := -1; bbuftyp := 1; bbufrec := -1; bdirty := 0; bdm := ADDRESS(0); bbufr := ADR(DiskDirBuff^[c]); END; END; BCBDirBuf^[NumbOfDirBuffers].blink := BCBPtr(0); SuperExec(PROC(InstallFatBuffers)); (* Install the Fat Disk Buffers *) SuperExec(PROC(InstallDirBuffers)); (* Install the Dir Disk Buffers *) RETURN TRUE; END Initialise; BEGIN (* body of Buffers *) IF Initialise() THEN WITH BasePageAddress^ DO TermRes(CodeLen + (*Tell GEMDOS to terminate us*) DataLen + (*but let us remain in memory*) BssLen + LONGCARD(CodeBase - LowTPA), 0); END; END; END Buffers. ` fLN  "pNGNuNV*x ڄ=E*P-E@-M-VNh-H ng nf-n*.P-ENB nf Nh-hN. nf BBN n e Nh-PN-n I n8B,)n9n)n )n)n)n|.n 9 j/@>,VNhYN`NsN^NuNVBn:.EIBtPRn nfI* -EI* -EI* -EI* -EI* -Ez-E=| Bn=|BnBn <I* "NB=|N=|=|=|Bn=|z-E <I* "NB=|4=|=|=|Bn=|(mI* -E <I* "NB=|Bn=|BnBn <I* "NBNpL?09 nNAN^NuNV/-+NIvGz+WBn:.IJ4PgRn`=y n n d:.I0PN(z:. HEBEHE HEx0D8.I@Rnz:. HEx0D8.I@RnIGzWBn:.I8.nGP@Rn:.IJ4Pg`N+_N^NuF'/NNn-/=-/H"Q ,IL?/^>/^Nf,oNsF'/NNn-/=-/H"Q G*- =/^Nf,oNs/NNn-/=/-/H _"h$",HL?/^>/^Nf,oNsF'SSfWWNsSf>NsSfF NsSfNqNsSf NNsNhBBB 9 !B*H$C! `N <L? <NAN`WNsF'pNNsF'pNNsF'pNNsF'pNNsF'pNNsF'pNNsNV . /2.??<NM*-E n b:.EI )PN:.E EI )PN^NuNV/9 ?<N\/9 ?<N\/9 ?<N\/9 ?<N\/9 ?<N\/9 ?<N\/9 ?<'N\/9 ?<&N\/9 ?<%N\N^NuNqNqNq O h*# f(y f# ( ШШ(y *,Ѕ.@//??<JNA (y f*,ڬ ڬڬ# /<\?<N\/<N?<N\/<j?<N\/<x?<N\/<?<N\/<?<N\/<?<'N\/<"?<&N\/<V?<%N\#. jpNGN $GEMXModula-2/ST (c) Copyright Modula 2 Software Ltd. 1985,1986,1987. (c) Copyright TDI Software Inc. 1985,1986,1987. The team : Chris Hall, Paul Curtis, and Phil Camp .[3][Modula-2 Run Time Error : | | #][OK]NV/.?<&NNN^NuNV/.?. NA*-EN^NuNV?./. ?.NA*-EN^NuNVY?<1/. ?.aP-_N^NuNVY?<H/. a\(n(N^NuJfpN # (y Jg(y # `(y ( NuJfpN # (y Jg(y # `(y ( NuNVHx@Hy NPHx Hy NP=|(y :.IIP&y :.REGGP( (9|BlBlBl B &y :.p eGGP( )DRn nf(y B,Hx@Hy NPHx Hy NP=|(y :.IIP&y :.REGGP( (9|9|9|Bl B &y :.p eGGP( )DRn nf(y B,*<,/NX*<h/NX|N^NuNNVUNJg*(y f/ *, ڬڬ(,ڄ/BgN\(_N^Nu<@ *lD&2         (          t   2( 6(  This program explores the Terminate but Stay Resident techniques of GemDos. Its purpose is to extend the GemDos buffers from 2 FAT buffers, and 2 DIR buffers to 18 FAT buffers, and 18 DIR buffers. In theory this should reduce the number of disk re-reads of the FileAllocationTable records, and the DIRectory records. Well it does do this untill a program terminates. Then GemDos apparently considers all the buffers as empty. So not much help here for programs that load-up, then execute without doing much file searching, but if you have programs that do a lot of searching for files then you should see an improvement. To best understand what the buffer extensions do for you take a directory of a disk drive, then do it again -- notice there should be no disk access. Now execute any program and have it terminate, now when you take a directory of the above disk drive you should notice the system is accessing the disk drive. To use this program either place it in your AUTO\ folder (I wouldn't do this untill you are really sure it is what you want to do), or execute it from the Desk Top. Oh well the program may be of dubious benefit, but the tecniques for doing TSR's should be of benefit. A general observation on TSR's, if you use an file.loader such as STARTUP.prg, be aware that if you use it to load and execute a TSR then the memory occupied by file.loader is lost to the system, an example: LowMemory.....StartUp...TSR1...TSR2...User memory.......EndOfMemory In other words StartUp may terminate like a regular program but the memory it occupied is un-accessable because of the TSR's mapped above it. This does not mean don't do it, rather just be aware that it will happen, on the other hand if you put the TSR's in the AUTO\ folder you get this: StartUp memory is now reclaimed LowMemory.....TSR1...TSR2...User memory.................EndOfMemory You can get the exact same affect by executing the TSR's from the Desk Top. Jerry K. LaPeer ST 78 - UTILITIES 4 The Professional GEM files on this disk were collected together from Compuserve by the Current Notes library in the USA. As the disk was not full we have added several miscellaneous utilities that programmers may find useful. These are generally more advanced utilities or those specificlly for programming. As they are added as a 'bonus' you are on your own if they are not documented! on your own if they are not documented! #a000000 #b000000 #c7770007000600070055200505552220770557075055507703111103 #d #E 1B 02 #W 06 00 1A 01 15 09 08 A:\*.*@ #W 00 00 0D 08 2A 0B 00 @ #W 00 00 0E 09 2A 0B 00 @ #W 00 00 0F 0A 2A 0B 00 @ #M 00 00 00 FF A FLOPPY DISK@ @ #M 00 01 00 FF B FLOPPY DISK@ @ #T 00 03 02 FF TRASH@ @ #F FF 04 @ *.*@ #D FF 01 @ *.*@ #G 03 FF *.APP@ @ #G 03 FF *.PRG@ @ #F 03 04 *.TOS@ @ #P 03 04 *.TTP@ @