NNNNNNp NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNONNNNNNNNNNNNNNNNNNNNNNnO` /@! #%`')+-1 35`;=?A C@E`GIKMOQ S@U`WY[]_ac@e`gikmoq su`wy{} @` @ ` @ ` @ ` @ o ǀ ɠ @ ` ׀ ٠ @` @o!Oa  !!#%a')1!o9;=?A!CAKMO/SAUaWY[O` /@! #%`')+-1 35`;=?A C@E`GIKMOQ S@U`WY[]_ac@e`gikmoq su`wy{} @` @ ` @ ` @ ` @ o ǀ ɠ @ ` ׀ ٠ @` @o!Oa  !!#%a')1!o9;=?A!CAKMO/SAUaWY[ST_77.P6 FEEDBACK t SOURCE_CODEt TEXT t 9READ ME Ct KEXTRAS kt AUTO t \READ 1STt ^7ESKTOP INF +W.  t..  tFEEDBACKDECt b FEEDBACKFEBt 5FEEDBACKJANt 2 ANTIC ONLINE ANT-1967 Permission to reprint or excerpt is granted only if the following line appears at the top of the article: ANTIC PUBLISHING INC., COPYRIGHT 1985. REPRINTED BY PERMISSION. FAN MAIL FROM SOME FLOUNDER? No, it's the ANTIC ONLINE Feedback. Time to answer some of the queries which have accumulated in the electronic mailbag. I've reworded some of the questions to be of more general interest, but they are otherwise just as you asked. Q: The ST RCS does not seem to support multiple screen resolutions properly. The spacing between boxes and text is not right. Is this a buggy RCS? Press for more !s A: Yes, there is a known bug in the RCS with color systems. It is (incorrectly) trying to use the character size for monochrome mode, which has twice as many vertical pixels. Digital Research is working on a fixed version, but for now you must either use a a monochrome system with RCS, or do some patches by hand. Q: I have created a menu bar using the RCS, and now I need to display it using C. How do I go about it? A: First, you must find the address of the menu tree using: rsrc_gaddr(R_TREE, MYMENU, &ad_menu); This assumes that MYMENU is the name you gave your menu in the RCS, and that ad_menu is a LONG variable. Then you make the call: menu_bar(ad_menu, TRUE); which will cause GEM to draw the menu and begin sending menu messages to your application. When your program is done, remove the menu bar with: menu_bar(0x0L, FALSE); before terminating. (Note: ST PRO GEM #7 will feature a full length discussion of menu structure and messages.) Q: What is the WM_NEWTOP message? How is it different from WM_TOPPED? A: WM_NEWTOP is a message which is sent to an application when one of its windows is about to be REMOVED from the top position. Most programs may safely ignore this message. It is meant for use by "Paint-like" programs which use a window as an active buffer area and must be able to save away its contents before they are overlaid by a desk accessory or another window. WM_NEWTOP contrasts with WM_TOPPED, which is sent when the window is about to be PLACED in the top position. Q: In RCS, what is the format of the .ICN files which can be used with the LOAD option for icons and bit images? A: The RCS expects the .ICN files to be in an obscure ASCII hexadecimal format which is produced by the GEM IconEditor. Unfortunately, the IconEditor has not yet been ported to the ST. So that you can get an idea of this format, I have included the images of two .ICN files within the download. You might try to extract the text and load them into RCS as the data and mask for an icon. If you do it right, you should see a clock face. If you want to build your own .ICN files, you must keep the rigid four number per line format while adding and removing data lines. You may change the vertical and horizontal pixel counts, but the horizontal count must always be a multiple of sixteen. Finally, the RCS expects the images to be in device-independent, monochrome format. If this last explanation is so much Greek to you, stay tuned for the next installment of ST PRO GEM, which will be entirely devoted to VDI bit-map operations. Until then, keep the feedback coming! Last page ! ANTIC ONLINE ANT-2307 Q: Why are there separate startup procedures for applications and desk accessories (apstart.s and accstart.s)? A: A desk accessory is started up automatically when GEM is booted, and becomes more or less part of the system. An application is only run at the user's request, after the system has finished loading. Since they start at different times, they have a different "environment" to contend with. For instance, the application startup code has to release memory following the program, while the accessory has this done for it by GEM. Q: Can a desk accessory use a resource? A: In general, no. This is because the accessories are loaded at system initialization time. If one later makes a rsrc_load() call for its resource, the system would allocate the resource's buffer in the user area of memory. This would result in a memory allocation error when the next application attempted to run. However, it is still possible to use AES object trees within an accessory. You should use the RCS to create the trees, then use the C output option to create an image of them. This image may be included and compiled in the accessory's source code. At run time, you will have to adjust the size of the objects using rsrc_obfix(), as well as fix up the inter-structure pointers. You can use the source code of STCREATE.C and the discussion of resource structure in a prior column as guides. Q: Are there problems with the -f (floating point) option in Alcyon C? A: Yes, there are a number of bugs in the Alcyon floating point library. In many cases, the precision of operations is poor. The only solution is to switch to Megamax or Lattice C to develop your floating point application. Q: I've noticed that the HEAD, TAIL, and NEXT pointers in an object tree are actually numbers relative to the base of the tree. Does this mean that an object must be pre-allocated within the tree if I plan to objc_add() it later? Can an application do true dynamic allocation of objects without setting aside space in the tree? A: Yes, you must preallocate objects if you want to add them without moving the tree from the resource. The alternative is to create a working area from your program and copy the tree to be altered to this buffer. Then you can allocate new objects following those which exist, and link them in. The new objects must be created on 24-byte boundaries starting at the root of the tree. You do NOT need to copy over structures such as TEDINFOs, unless you plan to modify them also. Q: I am using Hippo C, and I can't get your downloads to work. I'm also having problems with the programs in the print edition of ANTIC. What's going on? A: Haba Hippo-C has chosen to ignore a number of ST standards, including the normal names and calling sequences of the GEM VDI and AES calls. It also uses source file formats and an underlying operating system which are incompatible with TOS and the Developer's Kit. Due to a lack of time, and a desire to promote standardization, I will not be supporting Hippo C in this column. If you don't want to switch to Toolkit (Alcyon), Lattice, or Megamax C, you will have to translate the GEM calls from the downloads to the Hippo equivalents and probably retype the source code into the Hippo editor. Q: I'm using the Atari Developer's Kit, and LINK68 seems to crash when my programs get large. Is this a bug? A: Yes, there is a known bug in using Alcyon C and LINK68 when a program's code area approaches 64K. It appears that jump tables generated by SWITCH and GOTO statements are not being properly relocated. Often the difficulty can be avoided by putting such modules at the start of the link. Otherwise, you will need to to recode SWITCH statements as IFs, and eliminate the GOTOs (a good practice anyway). 3 ANTIC ONLINE ANT-1129 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. Now I will answer some of the question received recently in the ANTIC ONLINE Feedback (which is considerable faster than the regular mail this time of year). Q: The AES manual states "when the application is first loaded into memory, it should make a DOS call to modify the application's memory allocation". How is this done? A: When a GEM application is first loaded by the AES, all of memory is allocated to it. It must then release that portion which is past the end of its field length. This is necessary since the AES file selector and the VDI open workstation and load fonts calls allocate memory, and the application itself may need to do its own memory management. Press for more !s Fortunately, the standard routine APSTART will release memory for you if it is included as the first item in your link. It uses a setblock call to TOS, after determining the proper length of your application. To do so (quoting from APSTART), all "segment" lengths in the base page are totaled and 0x100 is added for the base page length. Q: When opening a blank window for a text screen is there a way of disabling the mouse so that you don't get a green patch when you inadvertantly move it? A: Yes, you can turn off the mouse using the call graf_mouse(M_OFF, 0x0L); Do this before clearing the window to white. If you later need to use the mouse, you can restore it with graf_mouse(M_ON, 0x0L); Q: Is it possible to patch GEM so that you must first click on the menu bar to active a drop-down, rather than just touching it with the cursor? I find it mildly irritating to open a drop-down menu accidently when I am merely moving the cursor around. A: As you may have guessed from the preceding discussion, the GEM menu algorithm is embedded deeply in the AES code and is not patchable. The "drop-down" architecture was chosen over "pull-downs" because it proved easier for novices. Unfortunately, it does cause problems for experienced users who mouse around the screen much faster. One way to partially avoid the problem is to design applications which have some separation space between the menu bar and selectable objects. On the Desktop, you can move file windows away from the bar and then save the new layout. Q: How can I support multiple resolutions if I need to include icons and images in my resource? Do I need to have an entire separate resource for each resolution, or is there a simpler way? A: It is certainly possible to have an alternate resource for each screen mode, but it introduces problems in keeping their structure and naming identical. Instead, you might consider building separate files containing only the bit image data for your resource, with one version for each resolution. You might do this by copying the image definitions from the .C file emitted by the RCS. After you have loaded the resource, allocate some memory and read in the appropriate image file. You will then have to link in the image data by modifying the pointers in your resource's BITBLK and ICONBLK structures. You will need to determine in advance which objects must be modified, and in what order their data occurs in the image file. Last page !.  t..  tGEMCL02 C t GEMCL03 C t GEMCL04 C t *GEMCL05 C t $GEMCL06 C t *GEMCL07 C t /GEMCL09 C t 4 GEMCL10 C t 7>>>>>>>>>>>>>>>>>>>>>>>>>> Sample Redraw Code <<<<<<<<<<<<<<<<<<<<<<<<<<<< VOID do_redraw(wh, area) /* wh = window handle from msg[3] */ WORD wh; /* area = pointer to redraw rect- */ GRECT *area; /* tangle in msg[4] thru msg[7] */ { GRECT box; graf_mouse(M_OFF, 0x0L); wind_update(BEG_UPDATE); wind_get(wh, WF_FIRSTXYWH, &box.g_x, &box.g_y, &box.g_w, &box.g_h); while ( box.g_w && box.g_h ) { if (rc_intersect(full, &box)) /* Full is entire screen */ if (rc_intersect(area, &box)) { if (wh == w1_handle) /* Test for window 1 handle */ { /* AES redraw example */ objc_draw(w1_tree, ROOT, MAX_DEPTH, box.g_x, box.g_y, box.g_w, box.g_h); } else if (wh == w2_handle) /* Test for window 2 handle */ { /* VDI redraw example */ set_clip(TRUE, &box); /* Put VDI drawing calls here */ } /* add more windows here */ } wind_get(wh, WF_NEXTXYWH, &box.g_x, &box.g_y, &box.g_w, &box.g_h); } wind_update(END_UPDATE); graf_mouse(M_ON, 0x0L); } >>>>>>>>>>>>>>>>>>>>>>>>> Utilities used in do_redraw <<<<<<<<<<<<<<<<<<<<<<< VOID set_clip(clip_flag, area) /* set clip to specified area */ WORD clip_flag; GRECT *area; { WORD pxy[4]; grect_to_array(area, pxy); vs_clip(vdi_handle, clip_flag, pxy); } VOID grect_to_array(area, array) /* convert x,y,w,h to upr lt x,y and */ GRECT *area; /* lwr rt x,y */ WORD *array; { *array++ = area->g_x; *array++ = area->g_y; *array++ = area->g_x + area->g_w - 1; *array = area->g_y + area->g_h - 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) ); } >>>>>>>>>>>>>>>>>>>>>>>>> "Self-redraw" Utility <<<<<<<<<<<<<<<<<<<<<<<<<<< VOID send_redraw(wh, p) WORD wh; GRECT *p; { WORD msg[8]; msg[0] = WM_REDRAW; /* Defined in GEMBIND.H */ msg[1] = gl_apid; /* As returned by appl_init */ msg[2] = 0; msg[3] = wh; /* Handle of window to redraw */ msg[4] = p->g_x; msg[5] = p->g_y; msg[6] = p->g_w; msg[7] = p->g_h; appl_write(gl_apid, 16, &msg); /* Use ADDR(msg) for portability */ } >>>>>>>>>>>>>>>>>>>>>> Utilities for Window Requests <<<<<<<<<<<<<<<<<<<< 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; } WORD align(x,n) /* Snap position x to an n-bit grid */ WORD x, n; /* Use n = 16 for horizontal word alignment */ { x += (n >> 2) - 1; /* Round and... */ x = n * (x / n); /* remove residue */ return (x); } >>>>>>>>>>>>>>>>>>>>>>>>> Window full utility <<<<<<<<<<<<<<<<<<<<<<<<<< VOID hndl_full(wh) /* depending on current window state, make window */ WORD wh; /* full size -or- return to previous shrunken size */ { /* graf_ calls are optional special effects. */ GRECT prev; GRECT curr; GRECT full; wind_get(wh, WF_CXYWH, &curr.g_x, &curr.g_y, &curr.g_w, &curr.g_h); wind_get(wh, WF_PXYWH, &prev.g_x, &prev.g_y, &prev.g_w, &prev.g_h); wind_get(wh, WF_FXYWH, &full.g_x, &full.g_y, &full.g_w, &full.g_h); if ( rc_equal(&curr, &full) ) { /* Is full, change to previous */ graf_shrinkbox(prev.g_x, prev.g_y, prev.g_w, prev.g_h, full.g_x, full.g_y, full.g_w, full.g_h); wind_set(wh, WF_CXYWH, prev.g_x, prev.g_y, prev.g_w, prev.g_h); /* put send_redraw here if you need it */ } else { /* is not full, so set to full */ graf_growbox(curr.g_x, curr.g_y, curr.g_w, curr.g_h, full.g_x, full.g_y, full.g_w, full.g_h); wind_set(wh, WF_CXYWH, full.g_x, full.g_y, full.g_w, full.g_h); } } WORD rc_equal(p1, p2) /* tests for two rectangles equal */ GRECT *p1, *p2; { if ((p1->g_x != p2->g_x) || (p1->g_y != p2->g_y) || (p1->g_w != p2->g_w) || (p1->g_h != p2->g_h)) return(FALSE); return(TRUE); } >>>>>>>>>>>>>>>>>>>>>>> Basic Dialog Handler <<<<<<<<<<<<<<<<<<<<<<< WORD hndl_dial(tree, def, x, y, w, h) LONG tree; WORD def; WORD x, y, w, h; { WORD xdial, ydial, wdial, hdial, exitobj; 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); } >>>>>>>>>>>>>>>>>>>>>>> Object rectangle utility <<<<<<<<<<<<<<<<<<<<<<<<< VOID objc_xywh(tree, obj, p) /* get x,y,w,h for specified object */ 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)); } >>>>>>>>>>>>>>>>>>>>>>> Object flag utilities <<<<<<<<<<<<<<<<<<<<<<<<<<< VOID undo_obj(tree, which, bit) /* clear specified bit in object state */ LONG tree; WORD which, bit; { WORD state; state = LWGET(OB_STATE(which)); LWSET(OB_STATE(which), state & ~bit); } VOID desel_obj(tree, which) /* turn off selected bit of spcfd object*/ LONG tree; WORD which; { undo_obj(tree, which, SELECTED); } VOID do_obj(tree, which, bit) /* set specified bit in object state */ LONG tree; WORD which, bit; { WORD state; state = LWGET(OB_STATE(which)); LWSET(OB_STATE(which), state | bit); } VOID sel_obj(tree, which) /* turn on selected bit of spcfd object */ LONG tree; WORD which; { do_obj(tree, which, SELECTED); } BOOLEAN statep(tree, which, bit) LONG tree; WORD which; WORD bit; { return ( (LWGET(OB_STATE(which)) & bit) != 0); } BOOLEAN selectp(tree, which) LONG tree; WORD which; { return statep(tree, which, SELECTED); } >>>>>>>>>>>>>>>>>>>>>> Sample radio buttons after dialog <<<<<<<<<<<<<<<<<<<< WORD encode(tree, ob1st, num) LONG tree; WORD ob1st, num; { for (; num--; ) if (selectp(ob1st+num)) return(num); return (-1); } >>>>>>>>>>>>>>>>>>>>>>>>>> Sample C output file from RCS <<<<<<<<<<<<<<<<<<<< /* (Comments added) */ BYTE *rs_strings[] = { /* ASCII data */ "Title String", "Exit", "Centered Text", "", "", "Butt", "Tokyo", "", "Time: __:__:__", "999999", "", "Time: __:__:__ ", "999999", "New York"}; WORD IMAG0[] = { /* Bitmap for G_IMAGE */ 0x7FF, 0xFFFF, 0xFF80, 0xC00, 0x0, 0xC0, 0x183F, 0xF03F, 0xF060, 0x187F, 0xF860, 0x1860, 0x187F, 0xF860, 0x1860, 0x187F, 0xF860, 0x1860, 0x187F, 0xF860, 0x1860, 0x187F, 0xF860, 0x1860, 0x187F, 0xF860, 0x1860, 0x187F, 0xF860, 0x1860, 0x187F, 0xF860, 0x1860, 0x187F, 0xF860, 0x1860, 0x187F, 0xF860, 0x1860, 0x187F, 0xF860, 0x1860, 0x183F, 0xF03F, 0xF060, 0xC00, 0x0, 0xC0, 0x7FF, 0xFFFF, 0xFF80, 0x0, 0x0, 0x0, 0x3F30, 0xC787, 0x8FE0, 0xC39, 0xCCCC, 0xCC00, 0xC36, 0xCFCC, 0xF80, 0xC30, 0xCCCD, 0xCC00, 0x3F30, 0xCCC7, 0xCFE0, 0x0, 0x0, 0x0}; WORD IMAG1[] = { /* Mask for first icon */ 0x0, 0x0, 0x0, 0x0, 0x7FFE, 0x0, 0x1F, 0xFFFF, 0xFC00, 0xFF, 0xFFFF, 0xFF00, 0x3FF, 0xFFFF, 0xFFC0, 0xFFF, 0xFFFF, 0xFFF0, 0x3FFF, 0xFFFF, 0xFFFC, 0x7FFF, 0xFFFF, 0xFFFE, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x7FFF, 0xFFFF, 0xFFFE, 0x3FFF, 0xFFFF, 0xFFFC, 0xFFF, 0xFFFF, 0xFFF0, 0x3FF, 0xFFFF, 0xFFC0, 0xFF, 0xFFFF, 0xFF00, 0x1F, 0xFFFF, 0xF800, 0x0, 0x7FFE, 0x0}; WORD IMAG2[] = { /* Data for first icon */ 0x0, 0x0, 0x0, 0x0, 0x3FFC, 0x0, 0xF, 0xC003, 0xF000, 0x78, 0x180, 0x1E00, 0x180, 0x180, 0x180, 0x603, 0x180, 0xC060, 0x1C00, 0x6, 0x38, 0x3000, 0x18C, 0xC, 0x60C0, 0x198, 0x306, 0x6000, 0x1B0, 0x6, 0x4000, 0x1E0, 0x2, 0xC000, 0x1C0, 0x3, 0xCFC0, 0x180, 0x3F3, 0xC000, 0x0, 0x3, 0x4000, 0x0, 0x2, 0x6000, 0x0, 0x6, 0x60C0, 0x0, 0x306, 0x3000, 0x0, 0xC, 0x1C00, 0x0, 0x38, 0x603, 0x180, 0xC060, 0x180, 0x180, 0x180, 0x78, 0x180, 0x1E00, 0xF, 0xC003, 0xF000, 0x0, 0x3FFC, 0x0}; WORD IMAG3[] = { /* Mask for second icon */ 0x0, 0x0, 0x0, 0x0, 0x7FFE, 0x0, 0x1F, 0xFFFF, 0xFC00, 0xFF, 0xFFFF, 0xFF00, 0x3FF, 0xFFFF, 0xFFC0, 0xFFF, 0xFFFF, 0xFFF0, 0x3FFF, 0xFFFF, 0xFFFC, 0x7FFF, 0xFFFF, 0xFFFE, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x7FFF, 0xFFFF, 0xFFFE, 0x3FFF, 0xFFFF, 0xFFFC, 0xFFF, 0xFFFF, 0xFFF0, 0x3FF, 0xFFFF, 0xFFC0, 0xFF, 0xFFFF, 0xFF00, 0x1F, 0xFFFF, 0xF800, 0x0, 0x7FFE, 0x0}; WORD IMAG4[] = { /* Data for second icon */ 0x0, 0x0, 0x0, 0x0, 0x3FFC, 0x0, 0xF, 0xC003, 0xF000, 0x78, 0x180, 0x1E00, 0x180, 0x180, 0x180, 0x603, 0x180, 0xC060, 0x1C00, 0x6, 0x38, 0x3000, 0x18C, 0xC, 0x60C0, 0x198, 0x306, 0x6000, 0x1B0, 0x6, 0x4000, 0x1E0, 0x2, 0xC000, 0x1C0, 0x3, 0xCFC0, 0x180, 0x3F3, 0xC000, 0x0, 0x3, 0x4000, 0x0, 0x2, 0x6000, 0x0, 0x6, 0x60C0, 0x0, 0x306, 0x3000, 0x0, 0xC, 0x1C00, 0x0, 0x38, 0x603, 0x180, 0xC060, 0x180, 0x180, 0x180, 0x78, 0x180, 0x1E00, 0xF, 0xC003, 0xF000, 0x0, 0x3FFC, 0x0}; LONG rs_frstr[] = { /* Free string index - unused */ 0}; BITBLK rs_bitblk[] = { /* First entry is index to image data */ 0L, 6, 24, 0, 0, 0}; LONG rs_frimg[] = { /* Free image index - unused */ 0}; ICONBLK rs_iconblk[] = { 1L, 2L, 10L, 4096,0,0, 0,0,48,24, 9,24,30,8, /* First pointer is mask */ 3L, 4L, 17L, 4864,0,0, 0,0,48,24, 0,24,48,8}; /* Second is data, third */ /* is to title string */ TEDINFO rs_tedinfo[] = { 2L, 3L, 4L, 3, 6, 2, 0x1180, 0x0, -1, 14,1, /* First pointer is text */ 7L, 8L, 9L, 3, 6, 2, 0x2072, 0x0, -3, 11,1, /* Second is template */ 11L, 12L, 13L, 3, 6, 0, 0x1180, 0x0, -1, 1,15, /* Third is validation */ 14L, 15L, 16L, 3, 6, 1, 0x1173, 0x0, 0, 1,17}; OBJECT rs_object[] = { -1, 1, 3, G_BOX, NONE, OUTLINED, 0x21100L, 0,0, 18,12, /* Pointers are to: */ 2, -1, -1, G_STRING, NONE, NORMAL, 0x0L, 3,1, 12,1, /* rs_strings */ 3, -1, -1, G_BUTTON, 0x7, NORMAL, 0x1L, 5,9, 8,1, /* rs_strings */ 0, 4, 4, G_BOX, NONE, NORMAL, 0xFF1172L, 3,3, 12,5, 3, -1, -1, G_IMAGE, LASTOB, NORMAL, 0x0L, 3,1, 6,3, /* rs_bitblk */ -1, 1, 6, G_BOX, NONE, OUTLINED, 0x21100L, 0,0, 23,12, 2, -1, -1, G_TEXT, NONE, NORMAL, 0x0L, 0,1, 23,1, /* rs_tedinfo */ 6, 3, 5, G_IBOX, NONE, NORMAL, 0x1100L, 6,3, 11,5, 4, -1, -1, G_BUTTON, 0x11, NORMAL, 0x5L, 0,0, 11,1, /* rs_strings */ 5, -1, -1, G_BUTTON, 0x11, NORMAL, 0x6L, 0,2, 11,1, /* rs_strings */ 2, -1, -1, G_BOXCHAR, 0x11, NORMAL, 0x43FF1400L, 0,4, 11,1, 0, -1, -1, G_BOXTEXT, 0x27, NORMAL, 0x1L, 5,9, 13,1, /* rs_tedinfo */ -1, 1, 3, G_BOX, NONE, OUTLINED, 0x21100L, 0,0, 32,11, 2, -1, -1, G_ICON, NONE, NORMAL, 0x0L, 4,1, 6,4, /* rs_iconblk */ 3, -1, -1, G_FTEXT, EDITABLE, NORMAL, 0x2L, 12,2, 14,1, /* rs_tedinfo */ 0, 4, 4, G_FBOXTEXT, 0xE, NORMAL, 0x3L, 3,5, 25,4, /* rs_tedinfo */ 3, -1, -1, G_ICON, LASTOB, NORMAL, 0x1L, 1,0, 6,4}; /* rs_iconblk */ LONG rs_trindex[] = { /* Points to start of trees in */ 0L, /* rs_object */ 5L, 12L}; struct foobar { /* Temporary structure used by */ WORD dummy; /* RSCREATE when setting up image */ WORD *image; /* pointers. */ } rs_imdope[] = { 0, &IMAG0[0], 0, &IMAG1[0], 0, &IMAG2[0], 0, &IMAG3[0], 0, &IMAG4[0]}; /* Counts of structures defined */ #define NUM_STRINGS 18 #define NUM_FRSTR 0 #define NUM_IMAGES 5 #define NUM_BB 1 #define NUM_FRIMG 0 #define NUM_IB 2 #define NUM_TI 4 #define NUM_OBS 17 #define NUM_TREE 3 BYTE pname[] = "DEMO.RSC"; >>>>>>>>>>>>>>>>>>>>>>>>>>>>> Title change utility <<<<<<<<<<<<<<<<<<<<< VOID set_text(tree, obj, str) LONG tree, str; WORD obj; { LONG obspec; obspec = LLGET(OB_SPEC(obj)); /* Get TEDINFO address */ LLSET(TE_PTEXT(obspec), str); /* Set new text pointer */ LWSET(TE_TXTLEN(obspec), LSTRLEN(str)); /* Set new length */ } >>>>>>>>>>>>>>>>>>>>>> Text edit code segment <<<<<<<<<<<<<<<<<<<<<<<<<< LONG tree, obspec; BYTE text[41]; rsrc_gaddr(R_TREE, DIALOG, &tree); /* Get tree address */ obspec = LLGET(OB_SPEC(EDITOBJ)); /* Get TEDINFO address */ LLSET(TE_PTEXT(obspec), ADDR(str)); /* Set new text pointer */ LWSET(TE_TXTLEN(obspec), 41); /* Set max length */ text[0] = '\0'; /* Make empty string */ >>>>>>>>>>>>>>>>>>>> Sample 68K only source code <<<<<<<<<<<<<<<<<<<<<< VOID set_text(tree, obj, str) OBJECT *tree; WORD obj; BYTE *str; { 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 */ } >>>>>>>>>>>>>>>>>>>>>>>>>>>> Symbol definitions <<<<<<<<<<<<<<<<<<<<<<<<< /* Window parts */ #define NAME 0x0001 #define CLOSER 0x0002 #define FULLER 0x0004 #define MOVER 0x0008 #define INFO 0x0010 #define SIZER 0x0020 #define UPARROW 0x0040 #define DNARROW 0x0080 #define VSLIDE 0x0100 #define LFARROW 0x0200 #define RTARROW 0x0400 #define HSLIDE 0x0800 #define WF_KIND 1 /* wind_get/set parameters */ #define WF_NAME 2 #define WF_INFO 3 #define WF_WXYWH 4 #define WF_CXYWH 5 #define WF_PXYWH 6 #define WF_FXYWH 7 #define WF_HSLIDE 8 #define WF_VSLIDE 9 #define WF_TOP 10 #define WF_FIRSTXYWH 11 #define WF_NEXTXYWH 12 #define WF_NEWDESK 14 #define WF_HSLSIZ 15 #define WF_VSLSIZ 16 /* window messages */ #define WM_REDRAW 20 #define WM_TOPPED 21 #define WM_CLOSED 22 #define WM_FULLED 23 #define WM_ARROWED 24 #define WM_HSLID 25 #define WM_VSLID 26 #define WM_SIZED 27 #define WM_MOVED 28 #define WM_NEWTOP 29 /* arrow messages */ #define WA_UPPAGE 0 #define WA_DNPAGE 1 #define WA_UPLINE 2 #define WA_DNLINE 3 #define WA_LFPAGE 4 #define WA_RTPAGE 5 #define WA_LFLINE 6 #define WA_RTLINE 7 #define R_TREE 0 /* Redraw definitions */ #define ROOT 0 #define MAX_DEPTH 8 /* update flags */ #define END_UPDATE 0 #define BEG_UPDATE 1 #define END_MCTRL 2 #define BEG_MCTRL 3 /* Mouse state changes */ #define M_OFF 256 #define M_ON 257 /* Object flags */ #define NONE 0x0 #define SELECTABLE 0x1 #define DEFAULT 0x2 #define EXIT 0x4 #define EDITABLE 0x8 #define RBUTTON 0x10 /* Object states */ #define SELECTED 0x1 #define CROSSED 0x2 #define CHECKED 0x4 #define DISABLED 0x8 #define OUTLINED 0x10 #define SHADOWED 0x20 #define G_BOX 20 #define G_TEXT 21 #define G_BOXTEXT 22 #define G_IMAGE 23 #define G_IBOX 25 #define G_BUTTON 26 #define G_BOXCHAR 27 #define G_STRING 28 #define G_FTEXT 29 #define G_FBOXTEXT 30 #define G_ICON 31 #define G_TITLE 32 /* Data structures */ typedef struct grect { int g_x; int g_y; int g_w; int g_h; } GRECT; typedef struct object { int ob_next; /* -> object's next sibling */ int ob_head; /* -> head of object's children */ int ob_tail; /* -> tail of object's children */ unsigned int ob_type; /* type of object- BOX, CHAR,...*/ unsigned int ob_flags; /* flags */ unsigned int ob_state; /* state- SELECTED, OPEN, ... */ long ob_spec; /* "out"- -> anything else */ int ob_x; /* upper left corner of object */ int ob_y; /* upper left corner of object */ int ob_width; /* width of obj */ int ob_height; /* height of obj */ } OBJECT; typedef struct text_edinfo { long te_ptext; /* ptr to text (must be 1st) */ long te_ptmplt; /* ptr to template */ long te_pvalid; /* ptr to validation chrs. */ int te_font; /* font */ int te_junk1; /* junk word */ int te_just; /* justification- left, right...*/ int te_color; /* color information word */ int te_junk2; /* junk word */ int te_thickness; /* border thickness */ int te_txtlen; /* length of text string */ int te_tmplen; /* length of template string */ } TEDINFO; /* "Portable" data definitions */ #define OB_NEXT(x) (tree + (x) * sizeof(OBJECT) + 0) #define OB_HEAD(x) (tree + (x) * sizeof(OBJECT) + 2) #define OB_TAIL(x) (tree + (x) * sizeof(OBJECT) + 4) #define OB_TYPE(x) (tree + (x) * sizeof(OBJECT) + 6) #define OB_FLAGS(x) (tree + (x) * sizeof(OBJECT) + 8) #define OB_STATE(x) (tree + (x) * sizeof(OBJECT) + 10) #define OB_SPEC(x) (tree + (x) * sizeof(OBJECT) + 12) #define OB_X(x) (tree + (x) * sizeof(OBJECT) + 16) #define OB_Y(x) (tree + (x) * sizeof(OBJECT) + 18) #define OB_WIDTH(x) (tree + (x) * sizeof(OBJECT) + 20) #define OB_HEIGHT(x) (tree + (x) * sizeof(OBJECT) + 22) #define TE_PTEXT(x) (x) #define TE_TXTLEN(x) (x + 24) >>>>>>>>>>>>>>>>>>>>>>>>>>> Sample object trees <<<<<<<<<<<<<<<<<<<<<<<< OBJECT rs_object[] = { -1, 1, 3, G_BOX, NONE, OUTLINED, 0x21100L, 0,0, 18,12, /* Tree # 1 */ 2, -1, -1, G_STRING, NONE, NORMAL, 0x0L, 3,1, 12,1, 3, -1, -1, G_BUTTON, 0x7, NORMAL, 0x1L, 5,9, 8,1, 0, 4, 4, G_BOX, NONE, NORMAL, 0xFF1172L, 3,3, 12,5, 3, -1, -1, G_IMAGE, LASTOB, NORMAL, 0x0L, 3,1, 6,3, -1, 1, 6, G_BOX, NONE, OUTLINED, 0x21100L, 0,0, 23,12, /* Tree # 2 */ 2, -1, -1, G_TEXT, NONE, NORMAL, 0x0L, 0,1, 23,1, 6, 3, 5, G_IBOX, NONE, NORMAL, 0x1100L, 6,3, 11,5, 4, -1, -1, G_BUTTON, 0x11, NORMAL, 0x5L, 0,0, 11,1, 5, -1, -1, G_BUTTON, 0x11, NORMAL, 0x6L, 0,2, 11,1, 2, -1, -1, G_BOXCHAR, 0x11, NORMAL, 0x43FF1400L, 0,4, 11,1, 0, -1, -1, G_BOXTEXT, 0x27, NORMAL, 0x1L, 5,9, 13,1, -1, 1, 3, G_BOX, NONE, OUTLINED, 0x21100L, 0,0, 32,11, /* Tree # 3 */ 2, -1, -1, G_ICON, NONE, NORMAL, 0x0L, 4,1, 6,4, 3, -1, -1, G_FTEXT, EDITABLE, NORMAL, 0x2L, 12,2, 14,1, 0, 4, 4, G_FBOXTEXT, 0xE, NORMAL, 0x3L, 3,5, 25,4, 3, -1, -1, G_ICON, LASTOB, NORMAL, 0x1L, 1,0, 6,4}; >>>>>>>>>>>>>>>>>>>>>>>>>> Object tree walk utility <<<<<<<<<<<<<<<<<<<<<< VOID map_tree(tree, this, last, routine) LONG 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 (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)); } } >>>>>>>>>>>>>>>>>> Sample routine to use with map_tree() <<<<<<<<<<<<<<< VOID undo_obj(tree, which, bit) /* clear specified bit in object state */ LONG tree; WORD which, bit; { WORD state; state = LWGET(OB_STATE(which)); LWSET(OB_STATE(which), state & ~bit); } VOID desel_obj(tree, which) /* turn off selected bit of spcfd object*/ LONG tree; WORD which; { undo_obj(tree, which, SELECTED); return (TRUE); } >>>>>>>>>>>>>>>>>>>>>>>>>> Sample .ICN Files <<<<<<<<<<<<<<<<<<<<<<<<<<<<<< >>>>>>>>>> Save everything between >>><<< lines as CLOCK.ICN <<<<<<<<<<<<<< /* GEM Icon Definition: */ #define ICON_W 0x0030 #define ICON_H 0x0018 #define DATASIZE 0x0048 UWORD clock[DATASIZE] = { 0x0000, 0x0000, 0x0000, 0x0000, 0x3FFC, 0x0000, 0x000F, 0xC003, 0xF000, 0x0078, 0x0180, 0x1E00, 0x0180, 0x0180, 0x0180, 0x0603, 0x0180, 0xC060, 0x1C00, 0x0006, 0x0038, 0x3000, 0x018C, 0x000C, 0x60C0, 0x0198, 0x0306, 0x6000, 0x01B0, 0x0006, 0x4000, 0x01E0, 0x0002, 0xC000, 0x01C0, 0x0003, 0xCFC0, 0x0180, 0x03F3, 0xC000, 0x0000, 0x0003, 0x4000, 0x0000, 0x0002, 0x6000, 0x0000, 0x0006, 0x60C0, 0x0000, 0x0306, 0x3000, 0x0000, 0x000C, 0x1C00, 0x0000, 0x0038, 0x0603, 0x0180, 0xC060, 0x0180, 0x0180, 0x0180, 0x0078, 0x0180, 0x1E00, 0x000F, 0xC003, 0xF000, 0x0000, 0x3FFC, 0x0000 }; >>>>>>>>>>>>>>>>>>>>>>>>>>>>>> End of CLOCK.ICN <<<<<<<<<<<<<<<<<<<<<<<<<< >>>>>>>>> Save everything between >>>><<<<< lines as CLOCKM.ICN <<<<<<<<<< /* GEM Icon Definition: */ #define ICON_W 0x0030 #define ICON_H 0x0018 #define DATASIZE 0x0048 UWORD clockm[DATASIZE] = { 0x0000, 0x0000, 0x0000, 0x0000, 0x7FFE, 0x0000, 0x001F, 0xFFFF, 0xFC00, 0x00FF, 0xFFFF, 0xFF00, 0x03FF, 0xFFFF, 0xFFC0, 0x0FFF, 0xFFFF, 0xFFF0, 0x3FFF, 0xFFFF, 0xFFFC, 0x7FFF, 0xFFFF, 0xFFFE, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x7FFF, 0xFFFF, 0xFFFE, 0x3FFF, 0xFFFF, 0xFFFC, 0x0FFF, 0xFFFF, 0xFFF0, 0x03FF, 0xFFFF, 0xFFC0, 0x00FF, 0xFFFF, 0xFF00, 0x001F, 0xFFFF, 0xF800, 0x0000, 0x7FFE, 0x0000 }; >>>>>>>>>>>>>>>>>>>>>>>>> End of CLOCKM.ICN <<<<<<<<<<<<<<<<<<<<<<<<<<<<< >>>>>>>>>>>>>>>>>>>>>>>>>>>>> MFDB Structure <<<<<<<<<<<<<<<<<<<<<<<<<<<<< /* Memory Form Definition Block */ typedef struct fdbstr { long fd_addr; /* Form address */ int fd_w; /* Form width in pixels */ int fd_h; /* Form height in pixels */ int fd_wdwidth; /* Form width in memory words */ int fd_stand; /* Standard form flag */ int fd_nplanes; /* Number of color planes */ int fd_r1; /* Dummy locations: */ int fd_r2; /* Reserved for future use */ int fd_r3; } MFDB; >>>>>>>>>>>>>>>>>>>>>>> Resource Transform Utilities <<<<<<<<<<<<<<<<<<<<< /*------------------------------*/ /* vdi_fix */ /*------------------------------*/ VOID vdi_fix(pfd, theaddr, wb, h) /* This routine loads the MFDB */ MFDB *pfd; /* Input values are the MFDB's */ LONG theaddr; /* address, the form's address,*/ WORD wb, h; /* the form's width in bytes, */ { /* and the height in pixels */ pfd->fww = wb >> 1; pfd->fwp = wb << 3; pfd->fh = h; pfd->np = 1; /* Monochrome assumed */ pfd->mp = theaddr; } /*------------------------------*/ /* vdi_trans */ /*------------------------------*/ WORD vdi_trans(saddr, swb, daddr, dwb, h) /* Transform the standard form */ LONG saddr; /* pointed at by saddr and */ UWORD swb; /* store in the form at daddr */ LONG daddr; /* Byte widths and pixel height*/ UWORD dwb; /* are given */ UWORD h; { MFDB src, dst; /* These are on-the-fly MFDBs */ vdi_fix(&src, saddr, swb, h); /* Load the source MFDB */ src.ff = TRUE; /* Set it's std form flag */ vdi_fix(&dst, daddr, dwb, h); /* Load the destination MFDB */ dst.ff = FALSE; /* Clear the std flag */ vr_trnfm(vdi_handle, &src, &dst ); /* Call the VDI */ } /*------------------------------*/ /* trans_bitblk */ /*------------------------------*/ VOID trans_bitblk(obspec) /* Transform the image belonging */ LONG obspec; /* to the bitblk pointed to by */ { /* obspec. This routine may also*/ LONG taddr; /* be used with free images */ WORD wb, hl; if ( (taddr = LLGET(BI_PDATA(obspec))) == -1L) return; /* Get and validate image address */ wb = LWGET(BI_WB(obspec)); /* Extract image dimensions */ hl = LWGET(BI_HL(obspec)); vdi_trans(taddr, wb, taddr, wb, hl); /* Perform a transform */ } /* in place */ /*------------------------------*/ /* trans_obj */ /*------------------------------*/ VOID trans_obj(tree, obj) /* Examine the input object. If */ LONG tree; /* it is an icon or image, trans- */ WORD obj; /* form the associated raster */ { /* forms in place. */ WORD type, wb, hl; /* This routine may be used with */ LONG taddr, obspec; /* map_tree() to transform an */ /* entire resource tree */ type = LLOBT(LWGET(OB_TYPE(obj))); /* Load object type */ if ( (obspec = LLGET(OB_SPEC(obj))) == -1L) /* Load and check */ return (TRUE); /* ob_spec pointer */ switch (type) { case G_IMAGE: trans_bitblk(obspec); /* Transform image */ return (TRUE); case G_ICON: /* Load icon size */ hl = LWGET(IB_HICON(obspec)); wb = (LWGET(IB_WICON(obspec)) + 7) >> 3; /* Transform data */ if ( (taddr = LLGET(IB_PDATA(obspec))) != -1L) vdi_trans(taddr, wb, taddr, wb, hl); /* Transform mask */ if ( (taddr = LLGET(IB_PMASK(obspec))) != -1L) vdi_trans(taddr, wb, taddr, wb, hl); return (TRUE); default: return (TRUE); } } >>>>>>>>>>>>>>>>>>> Macro definitions for the code above <<<<<<<<<<<<<<<<<< #define BI_PDATA(x) (x) #define BI_WB(x) (x + 4) #define BI_HL(x) (x + 6) #define OB_TYPE(x) (tree + (x) * sizeof(OBJECT) + 6) #define OB_SPEC(x) (tree + (x) * sizeof(OBJECT) + 12) #define IB_PMASK(x) (x) #define IB_PDATA(x) (x + 4) #define IB_WICON(x) (x + 22) #define IB_HICON(x) (x + 24) >>>>>>>>>>>>>>>>>>>>>>>>>>> VDI Copy Mode Table <<<<<<<<<<<<<<<<<<<<<<<<<<<< Symbols: N = new destination pixel value (0 or 1) D = old destination pixel value (0 or 1) S = source pixel value (0 or 1) ~ = Boolean not (inversion) & = Boolean and | = Boolean or ^ = Boolean xor (exclusive-or) Mode Number Action ---------- ------ 0 N = 0 (USE V_BAR INSTEAD) 1 N = S & D 2 N = S & ~D 3 N = S (REPLACE) 4 N = ~S & D (ERASE) 5 N = D (USELESS) 6 N = S ^ D (XOR) 7 N = S | D (TRANSPARENT) 8 N = ~ (S | D) 9 N = ~ (S ^ D) 10 N = ~D (USE V_BAR INSTEAD) 11 N = S | ~D 12 N = ~S 13 N = ~S | D (REVERSE TRANSPARENT) 14 N = ~ (S & D) 15 N = 1 (USE V_BAR INSTEAD) >>>>>>>>>>>>>>>>>>>>>>>>>>> END OF DOWNLOAD <<<<<<<<<<<<<<<<<<<<<<<<<<<<< >>>>>>>>>>>>>>>>>>>>>> Download file for GEM column #7 <<<<<<<<<<<<<<<<<<<<< >>>>>>>>>>>>>>>>>>>>>>>>>>>> Sample Menu Tree <<<<<<<<<<<<<<<<<<<<<<<<<<<<<< -1, 1, 6, G_IBOX, NONE, NORMAL, 0x0L, 0,0, 80,25, /* ROOT */ 6, 2, 2, G_BOX, NONE, NORMAL, 0x1100L, 0,0, 80,513, /* THE BAR */ 1, 3, 5, G_IBOX, NONE, NORMAL, 0x0L, 2,0, 20,769, /* THE ACTIVE */ 4, -1, -1, G_TITLE, NONE, NORMAL, 0x0L, 0,0, 6,769, /* Title #1 */ 5, -1, -1, G_TITLE, NONE, NORMAL, 0x1L, 6,0, 6,769, /* Title #2 */ 2, -1, -1, G_TITLE, NONE, NORMAL, 0x2L, 12,0, 8,769, /* Title #3 */ 0, 7, 22, G_IBOX, NONE, NORMAL, 0x0L, 0,769, 80,19, /* THE SCREEN */ 16, 8, 15, G_BOX, NONE, NORMAL, 0xFF1100L, 2,0, 20,8, /* Drop-down #1 */ 9, -1, -1, G_STRING, NONE, NORMAL, 0x3L, 0,0, 19,1, /* About... entry */ 10, -1, -1, G_STRING, NONE, DISABLED, 0x4L, 0,1, 20,1, 11, -1, -1, G_STRING, NONE, NORMAL, 0x5L, 0,2, 20,1, /* Desk acc entries */ 12, -1, -1, G_STRING, NONE, NORMAL, 0x6L, 0,3, 20,1, 13, -1, -1, G_STRING, NONE, NORMAL, 0x7L, 0,4, 20,1, 14, -1, -1, G_STRING, NONE, NORMAL, 0x8L, 0,5, 20,1, 15, -1, -1, G_STRING, NONE, NORMAL, 0x9L, 0,6, 20,1, 7, -1, -1, G_STRING, NONE, NORMAL, 0xAL, 0,7, 20,1, 22, 17, 21, G_BOX, NONE, NORMAL, 0xFF1100L, 8,0, 13,5, /* Drop-down #2 */ 18, -1, -1, G_STRING, NONE, NORMAL, 0xBL, 0,0, 13,1, 19, -1, -1, G_STRING, NONE, DISABLED, 0xCL, 0,1, 13,1, 20, -1, -1, G_STRING, NONE, NORMAL, 0xDL, 0,4, 13,1, 21, -1, -1, G_STRING, NONE, NORMAL, 0xEL, 0,2, 13,1, 16, -1, -1, G_STRING, NONE, DISABLED, 0xFL, 0,3, 13,1, 6, 23, 25, G_BOX, NONE, NORMAL, 0xFF1100L, 14,0, 26,3, /* Drop down #3 */ 24, -1, -1, G_STRING, NONE, NORMAL, 0x10L, 0,2, 26,1, 25, -1, -1, G_STRING, NONE, NORMAL, 0x11L, 0,0, 26,1, 22, -1, -1, G_STRING, LASTOB, DISABLED, 0x12L, 0,1, 26,1 >>>>>>>>>>>>>>>>>>>>>>>> Menu enable/disable utility <<<<<<<<<<<<<<<<<<<<<< /*------------------------------*/ /* undo_obj */ /*------------------------------*/ VOID undo_obj(tree, which, bit) LONG tree; WORD which; UWORD bit; { WORD state; state = LWGET(OB_STATE(which)); LWSET(OB_STATE(which), state & ~bit); } /*------------------------------*/ /* enab_obj */ /*------------------------------*/ WORD enab_obj(tree, which) LONG tree; WORD which; { undo_obj(tree, which, (UWORD) DISABLED); return (TRUE); } /*------------------------------*/ /* do_obj */ /*------------------------------*/ VOID do_obj(tree, which, bit) LONG tree; WORD which; UWORD bit; { WORD state; state = LWGET(OB_STATE(which)); LWSET(OB_STATE(which), state | bit); } /*------------------------------*/ /* disab_obj */ /*------------------------------*/ WORD disab_obj(tree, which) LONG tree; WORD which; { do_obj(tree, which, (UWORD) DISABLED); return (TRUE); } /*------------------------------*/ /* set_menu */ /*------------------------------*/ VOID set_menu(tree, change) /* change[0] TRUE selects all entries*/ LONG tree; /* FALSE deselects all. Change list */ WORD *change; /* of items is then toggled. */ { WORD dflt, screen, drop, obj; dflt = *change++; /* What is default? */ screen = LWGET(OB_TAIL(ROOT)); /* Get SCREEN */ drop = LWGET(OB_HEAD(screen)); /* Get DESK drop-down */ /* and skip it */ for (; (drop = LWGET(OB_NEXT(drop))) != screen; ) { obj = LWGET(OB_HEAD(drop)); if (obj != NIL) if (dflt) map_tree(tree, obj, drop, enab_obj); else map_tree(tree, obj, drop, disab_obj); } for (; *change; change++) if (dflt) disab_obj(tree, *change); else enab_obj(tree, *change); } >>>>>>>>>>>>>>>>>>>>> Definitions used in this article <<<<<<<<<<<<<<<<<<<<<< #define ROOT 0 #define G_IBOX 25 #define G_STRING 28 #define G_TITLE 32 #define R_TREE 0 #define MN_SELECTED 10 #define CHECKED 0x4 #define DISABLED 0x8 #define OB_NEXT(x) (tree + (x) * sizeof(OBJECT) + 0) #define OB_HEAD(x) (tree + (x) * sizeof(OBJECT) + 2) #define OB_TAIL(x) (tree + (x) * sizeof(OBJECT) + 4) #define OB_TYPE(x) (tree + (x) * sizeof(OBJECT) + 6) #define OB_FLAGS(x) (tree + (x) * sizeof(OBJECT) + 8) #define OB_STATE(x) (tree + (x) * sizeof(OBJECT) + 10) #define OB_SPEC(x) (tree + (x) * sizeof(OBJECT) + 12) #define OB_X(x) (tree + (x) * sizeof(OBJECT) + 16) #define OB_Y(x) (tree + (x) * sizeof(OBJECT) + 18) #define OB_WIDTH(x) (tree + (x) * sizeof(OBJECT) + 20) #define OB_HEIGHT(x) (tree + (x) * sizeof(OBJECT) + 22) #define M_OFF 256 #define M_ON 257 >>>>>>>>>>>>>>>> Routines to set clip to a GRECT <<<<<<<<<<<<<<<< VOID grect_to_array(area, array) /* convert x,y,w,h to upr lt x,y and */ GRECT *area; /* lwr rt x,y */ WORD *array; { *array++ = area->g_x; *array++ = area->g_y; *array++ = area->g_x + area->g_w - 1; *array = area->g_y + area->g_h - 1; } VOID set_clip(clip_flag, s_area) /* set clip to specified area */ WORD clip_flag; GRECT *s_area; { WORD pxy[4]; grect_to_array(s_area, pxy); vs_clip(vdi_handle, clip_flag, pxy); } >>>>>>>>>> Routines to set attributes before output <<<<<<<<<<<< VOID rr_perim(mode, color, type, width, pxy) /* Draw a rounded */ WORD mode, color, width, *pxy; /* rectangle outline */ { vswr_mode(vdi_handle, mode); vsl_color(vdi_handle, color); vsl_type(vdi_handle, type); vsl_width(vdi_handle, width); v_rbox(vdi_handle, pxy); vswr_mode(vdi_handle, MD_REPLACE); } VOID pl_perim(mode, type, color, width, npts, pxy) /* Draw a polygonal */ /* figure */ WORD mode, type, color, width, npts, *pxy; { vswr_mode(vdi_handle, mode); vsl_type(vdi_handle, type); vsl_color(vdi_handle, color); vsl_width(vdi_handle, width); v_pline(vdi_handle, npts, pxy); } VOID /* Draw a filled polygonal area */ pl_fill(mode, perim, color, interior, style, npts, pxy) WORD mode, perim, color, interior, style, npts, *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_fillarea(vdi_handle, npts, pxy); } VOID /* Draw a filled rectangle */ rect_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); vr_recfl(vdi_handle, pxy); } >>>>>>>>>>> Demonstration of byte alignment of window interior <<<<<<<<<<< #define FEATURES 0x0fef /* what border features are used */ WORD msg[8]; /* message from evnt_multi */ GRECT work_area; /* defines working area */ WORD w_hndl; /* handle for window being changed */ wind_calc(1, FEATURES, msg[4], msg[5], msg[6], msg[7], &work_area.g_x, &work_area.g_y, &work_area.g_w, &work_area.g_h); work_area.g_x = align_x(work_area.g_x); work_area.g_w = align_x(work_area.g_w); wind_calc(0, FEATURES, work_area.g_x, work_area.g_y, work_area.g_w, work_area.g_h, &msg[4], &msg[5], &msg[6], &msg[7]); wind_set(w_hndl, WF_CXYWH, msg[4], msg[5], msg[6], msg[7]); >>>>>>>>>>>>>>>>>>>>> Subroutine for above <<<<<<<<<<<<<<<<<<<<<<<<<<<<< WORD align_x(x) /* forces word alignment for column position */ WORD x; /* rounding to nearest word */ { return((x & 0xfff0) + ((x & 0x0008) ? 0x0010 : 0)); } >>>>>>>>>>>>>>>>>>>>> Standard v_gtext binding <<<<<<<<<<<<<<<<<<<<<<<<< WORD v_gtext( handle, x, y, string) WORD handle, x, y; BYTE *string; { WORD i; ptsin[0] = x; ptsin[1] = y; i = 0; while (intin[i++] = *string++) /* Copy characters to intin */ ; /* There is NO error checking! */ contrl[0] = 8; contrl[1] = 1; contrl[3] = --i; contrl[6] = handle; vdi(); } .  t9..  tWINDOWS1001 t :DWINDOWS2002 t L:UDIALOG 003t bxFRSCFILES004t tMMRSCTREES005 t HRASTERS 006%t XMENUSTRC007+t IUSERINTR0080t zhVDIGRAPH0096t RVDITEXT 010<t 4 ANTIC PUBLISHING INC., COPYRIGHT 1985. REPRINTED BY PERMISSION. ** Professional GEM ** by Tim Oren Column 1: WINDOWS, part I 10/7/85 ANTIC is proud to present the first of Tim Oren's bi-monthly columns exploring the GEM programming environment. These columns are aimed at professional ST developers, but we encourage everyone to join in and collect the columns for future reference. HELLO, WORLD! For those whom I have not met in person or electronically, an introduction is in order. I am a former member of the GEM programming team at Digital Research, Inc., where I designed and implemented the GEM Resource Construction Set and other parts of the GEM Programmer's Toolkit. I have since left DRI to become the user interface designer for Activenture, a startup company which is developing CD-ROM technology for use with the Atari ST and other systems. The purpose of Professional GEM is to pass along some of the information and tricks I have accumulated about GEM, and explore some of the user interface techniques which a powerful graphics processor such as the ST makes possible. GROUND RULES I am going to assume that you have both a working knowledge of the C programming language and a copy of the ST Programmer's Toolkit with documentation (available from Atari). If you lack either, don't panic. You can read the columns to get the flavor of programming the ST, and come back for a more serious visit later on. For now, I will be using code samples that will run with the Atari-supplied C compiler, also known as DR C-68K, or Alcyon C. I will be using the portability macros supplied with the Toolkit, so that the code will also be transferable to other GEM systems. Both of these items are subject to change, depending on reader feedback and the availability of better products. If you do not have a copy of the source to the DOODLE.C GEM example program, you should consider downloading a copy from SIG*ATARI. Although it is poorly documented, it shows real-life examples of many of the techniques I will discuss. Getting started with a windowed graphics system seems to be like getting into an ice-cold swimming pool: it's best done all at once. Page 1 Anyone who has looked at "Inside Macintosh" has probably noticed that you have to have read most of it to understand any of it. GEM isn't really much different. You have all the reference guides in your hand, but nothing to show how it all works together. I am hoping to help this situation by leading a series of short tours through the GEM jungle. Each time we'll go out with a particular goal in mind and follow the path that leads there. We'll look at the pitfalls and strange bugs that lurk for the unwary, and show off a few tricks to amaze the natives. The first trip leaves immediately; our mission is to get a window onto the ST screen, with all of its parts properly initialized. WE DO WINDOWS One of the most important services which a graphics interface system provides for the user and programmer is window management. Windows allow the user to perform more than one activity on the same screen, to freely reallocate areas of the screen for each task, and even to pile the information up like pages of paper to make more room. The price for this increased freedom is (as usual) paid by you, the programmer, who must master a more complex method of interacting with the "outside world". The windowing routines provided by ST GEM are the most comprehensive yet available in a low-cost microcomputer. This article is a guide to using these services in an effective manner. IN THE BEGINNING In GEM, creating a window and displaying it are two different functions. The creation function is called wind_create, and its calling sequence is: handle = wind_create(parts, xfull, yfull, wfull, hfull); This function asks GEM to reserve space in its memory for a new window description, and to return a code or "handle" which you can use to refer to the window in the future. Valid window handles are positive integers; they are not memory pointers. GEM can run out of window handles. If it does so, the value returned is negative. Your code should always check for this situation and ask the program's user to close some windows and retry if possible. Handle zero is special. It refers to the "desktop", which is predefined as light green (or gray) on the ST. Window zero is always present and may be used, but never deleted, by the programmer. The xfull, yfull, wfull, and hfull parameters are integers which determine the maximum size of the window. Xfull and yfull define the upper left corner of the window, and wfull and hfull specify its width Page 2 and height. (Note that all of the window coordinates which we use are in pixel units.) GEM saves these values so that the program can get them later when processing FULL requests. Usually the best maximum size for a window is the entire desktop area, excepting the menu bar. You can find this by asking wind_get for the working area of the desktop (handle zero, remember): wind_get(0, WF_WXYWH, &xfull, &yfull, &wfull, &hfull); Note that WF_WXYWH, and all of the other mnemonics used in this article, are defined in the GEMDEFS.H file in the ST Toolkit. The parts parameter of wind_create defines what features will be included in the window when it is drawn. It is a word of single bit flags which indicate the presence/absence of each feature. To request multiple features, the flags are "or-ed" together. The flags' mnemonics and meanings are: NAME - A one character high title bar at the top of the window. INFO - A second character line below the NAME. MOVER - This lets the user move the window around by "dragging" in the NAME area. NAME also needs to be defined. CLOSER - A square box at the upper left. Clicking this control point asks that the window be removed from the screen. FULLER - A diamond at upper right. Clicking this control point requests that the window grow to its maximum size, or shrink back down if it is already big. SIZER - An arrow at bottom right. Dragging the SIZER lets the user choose a new size for the window. VSLIDE - defines a right-hand scroll box and bar for the window. By dragging the scroll bar, the user requests that the window's "viewport" into the information be moved. Clicking on the gray box above the bar requests that the window be moved up one "page". Clicking below the bar requests a down page movement. You have to define what constitutes a page or line in the context of your application. UPARROW - An arrow above the right scroll bar. Clicking here requests that the window be moved up one "line". Sliders and arrows almost always appear together. DNARROW - An arrow below the right scroll bar. Requests that window be moved down a line. HSLIDE, RTARROW, LTARROW - These features are the horizontal equivalent of the VSLIDE above. They appear at the bottom of the window. Arrows LFARROW usually indicate "character" sized movement left and right. Page 3 "Page" sized movement has to be defined by each application. It is important to understand the correspondence between window features and event messages which are sent to the application by the GEM window manager. If a feature is not included in a window's creation, the user cannot perform the corresponding action, and your application will never receive the matching message type. For example, a window without a MOVER may not be dragged by the user, and your app will never get a WM_MOVED message for that window. Another important principle is that the application itself is responsible for implementing the user's window action request when a message is received. This gives the application a chance to accept, modify, or reject the user's request. As an example, if a WM_MOVED message is received, it indicates that the user has dragged the window. You might want to byte or word align the requested position before proceeding to move the window. The wind_set calls used to perform the actual movements will be described in the next article. OPEN, SESAME! The wind_open call is used to actually make the window appear on the screen. It animates a "zoom box" on the screen and then draws in the window's frame. The calling sequence is: wind_open(handle, x, y, w, h); The handle is the one returned by wind_create. Parameters x, y, w, and h define the initial location and size of the window. Note that these measurements INCLUDE all of the window frame parts which you have requested. To find out the size of the area inside the frame, you can use wind_get(handle, WF_WXYWH, &inner_x, &inner_y, &inner_w, &inner_h); Whatever size you choose for the window display, it cannot be any larger than the full size declared in wind_create. Here is a good place to take note of a useful utility for calculating window sizes. If you know the "parts list" for a window, and its inner or outer size, you can find the other size with the wind_calc call: wind_calc(parts, kind, input_x, input_y, input_w, input_h, &output_x, &output_y, &output_w, &output_h); Kind is set to zero if the input coordinates are the inner area, and you are calculating the outer size. Kind is one if the inputs are the outer size and you want the equivalent inner size. Parts are just the same as in wind_create. There is one common bug in using wind_open. If the NAME feature is specified, then the window title must be initialized BEFORE opening the Page 4 window: wind_set(handle, WF_NAME, ADDR(title), 0, 0); If you don't do this, you may get gibberish in the NAME area or the system may crash. Likewise, if you have specified the INFO feature, you must make a wind_set call for WF_INFO before opening the window. Note that ADDR() specifies the 32-bit address of title. This expression is portable to other (Intel-based) GEM systems. If you don't care about portability, then &title[0], or just title alone will work fine on the ST. CLEANING UP. When you are done with a window, it should be closed and deleted. The call wind_close(handle); takes the window off the screen, redraws the desktop underneath it, and animates a "zoom down" box. It doesn't delete the window's definition, so you can reopen it later. Deleting the window removes its definition from the system, and makes that handle available for reuse. Always close windows before deleting, or you may leave a "dead" picture on the screen. Also be sure to delete all of your windows before ending the program, or your app may "eat" window handles. The syntax for deleting a window is: wind_delete(handle); THOSE FAT SLIDERS. One of ST GEM's unique features is the proportional slider bar. Unlike other windowing systems, this type of bar gives visual feedback on the fraction of a document which is being viewed, as well as the position within the document. The catch, of course, is that you have two variables to maintain for each scroll bar: size and position. Both bar size and position range from 1 to 1000. A bar size of 1000 fills the slide box, and a value of one gets the minimum bar size. To compute the proper size, you can use the formula: size = min(1000, 1000 * seen_doc / total_doc) Seen_doc and total_doc are the visible and total size of the document respectively, in whatever units are appropriate. As an example, if your window could show 20 lines of a 100 line text file, you should set a slider size of 200. Since the window might be bigger than the total document at some points, you need the maximum function. If the document size is zero, force the slider size to 1000. (Note: You will probably need to do the computation above with 32-bit arithmetic to avoid overflow problems.) Page 5 Once you have computed the size, use the wind_set function to configure the scroll bar: wind_set(handle, WF_VSLSIZE, size, 0, 0, 0); This call sets the vertical (right hand) scroll bar. Use WF_HSLSIZE for the horizontal scroller. All of these examples are done for the vertical dimension, but the principles are identical in the other direction. Bar positioning is a little tougher. The most confusing aspect is that the 1-1000 range does not set an absolute position of the bar within the scroll box. Instead, it positions the TOP of the bar within its possible range of variation. Let's look at our text file example again to make this clearer. If there are always 20 lines of a 100 line file visible, then the top of the window must be always be somewhere between line 1 and line 81. This 80 line range is the actual freedom of movement of the window. So, if the window were actually positioned with its top at line 61, it would be at the three-quarter position within the range, and we should set a scroll bar position of 750. The actual formula for computing the position is: pos = 1000 * (top_wind - top_doc) / (total_doc - seen_doc) Top_wind and top_doc are the top line in the current window and the whole document, respectively. Obviously, if seen_doc is greater or equal to total_doc, you need to force a zero value for pos. This calculation may seem rather convoluted the first time through, but is easy once you have done it. When you have computed the position, wind_set configures the scroll bar: wind_set(handle, WF_VSLIDE, pos, 0, 0, 0); WF_HSLIDE is the equivalent for horizontal scrolling. It is a good practice to avoid setting the slider size or position if they are already at the value which you need. This avoids an annoying redraw flash on the screen when it is not necessary. You can check on the current value of a slider parameter with wind_get: wind_get(handle, WF_VSLIDE, &curr_value, &foo, &foo, &foo); Foo is a dummy variable which needs to be there, but is not used. Substitute WF_VSLIDE with whatever parameter you are checking. One philosophical note on the use of sliders: It is probably best to avoid the use of both sliders at once unless it is clearly appropriate to the type of data which is being viewed. Page 6 Since Write and Paint programs make use of the sheet-of-paper metaphor, moving the window around in both dimensions is reasonable. However, if the data is more randomly organized, such as a tableau of icons, then it is probably better to only scroll in the vertical dimension and "reshuffle" if the window's width is changed. Then the user only needs to manipulate one control to find information which is off-screen. Anyone who has had trouble finding a file or folder within a Desktop window will recognize this problem. COMING UP NEXT In my next column in Antic Online, we'll conclude the tour of the ST's windowing system. I'll discuss the correct way to redraw a window's contents, and how to handle the various messages which an application receives from the window manager. Finally, we'll look at a way to redesign the desktop background to your own specifications. FEEDBACK One of the beauties of an on-line column is that you can make your comments known immediately. To register your opinions, select ST FEEDBACK, enter your message, leave your name, and enter a blank line to exit. I am interested in hearing proposals for topics, feedback on the technical level of the column, and reports on bugs and other "features" in both the column and the ST itself. Your comments will be read by the ANTIC staff and myself and, though we might not answer individual questions, they will be used to steer the course of future columns. Page 7 ANTIC PUBLISHING INC., COPYRIGHT 1985. REPRINTED BY PERMISSION. ** Professional GEM ** by Tim Oren Column 2: Windows, part II 10/21/85 EXCELSIOR! In this installment, we continue the exploration of GEM's window manager by finding out how to process the messages received by an application when it has a window defined on the screen. Also, beginning with this column, sample C code demonstrating the techniques discussed will be available on SIG*ATARI in DL5. This will allow you to download the code without interference by the CIS text-formatter used by ANTIC ONLINE output. The file for this column is GEMCL2.XMO. All references to non-GEM routines in this column refer to this file. Please note that these files will not contain entire programs. Instead, they consist of small pieces of utility code which you may copy and modify in your own programs. REDRAWING WINDOWS One of the most misunderstood parts of GEM is the correct method for drawing within a window. Most requests for redrawing are generated by the GEM system, and arrive as messages (read with evnt_multi) which contain the handle of the window, and the screen rectangle which is "dirty" and needs to be redrawn. Screen areas may become dirty as a result of windows being closed, sized down, or moved, thus "exposing" an area underneath. The completion of a dialog, or closing of a desk accessory may also free up a screen area which needs to be redrawn. When GEM detects the presence of a dirty rectangle, it checks its list of open windows, and sends the application a redraw message for each of its windows which intersects the dirty area. CAVEAT EMPTOR GEM does not "clip" the rectangle which it sends to the application; that is, the rectangle may not lie entirely within the portion of the window which is exposed on the screen. It is the job of the application to determine in what portion of the rectangle it may safely draw. This is done by examining the "rectangle list" associated with the window. A rectangle list is maintained by GEM for each active window. It contains the portions of the window's interior which are exposed, i.e., Page 1 topmost, on the screen and within which the app may draw. Let's consider an example to make this clear. Suppose an app has opened two windows, and there are no desk accessory windows open. The window which is topmost will always have only one rectangle in its list. If the two are separate on the screen, then the second window will also have one rectangle. If they overlap, then the top window will "break" the rectangle of the bottom one. If the overlap is at a corner, two rectangles will be generated for the bottom window. If the overlap is on a side only, then three rectangles are required to cover the exposed portion of the bottom window. Finally, if the first window is entirely within the second, it requires four rectangles in the list to tile the second window. Try working out a few rectangle examples with pencil and paper to get the feel of it. You will see that the possible combinations with more than two windows are enormous. This, by the way, is the reason that GEM does not send one message for each rectangle on the list: With multiple windows, the number of messages generated would quickly fill up the application's message queue. Finally, note that every app MUST use this method, even if it only uses a single window, because there may be desk accessories with their own windows in the system at the same time. If you do not use the rectangle lists, you may overwrite an accessory's window. INTO THE BITS First, we should note that the message type for a redraw request is WM_REDRAW, which is stored in msg[0], the first location of the message returned by evnt_multi. The window handle is stored in msg[3]. These locations are the same for all of the message types being discuss. The rectangle which needs to be redrawn is stored in msg[4] through msg[7]. Now let's examine the sample redraw code in more detail. The redraw loop is bracketed with mouse off and mouse on calls. If you forget to do this, the mouse pointer will be over-written if it is within the window and the next movement of the mouse will leave a rectangular blotch on the screen as a piece of the "old" screen is incorrectly restored. The other necessary step is to set the window update flag. This prevents the menu manager from dropping a menu on top of the screen portion being redrawn. You must release this flag at the end of the redraw, or the you will be unable to use any menus afterwards. The window rectangles are retrieved using a get-first, get-next scheme which will be familiar if you have used the GEM DOS or PC-DOS wildcard file calls. The end of the rectangle list has been reached when both the width and height returned are zero. Since some part of a window might be off-screen (unless you have clamped its position - see below), Page 2 the retrieved rectangle is intersected with the desktop's area, and then with the screen area for which a redraw was requested. Now you have the particular area of the screen in which it is legal to draw. Unless there is only one window in your application, you will have to test the handle in the redraw request to figure out what to put in the rectangle. Depending on the app, you may be drawing an AES object tree, or executing VDI calls, or some combination of the two. In the AES case, the computed rectangle is used to specify the bounds of the objc_draw. For VDI work, the rectangle is used to set the clipping area before executing the VDI calls. A SMALL CONFESSION At the beginning of this discussion, I deliberately omitted one class of redraws: those initiated by the application itself. In some cases a part of the screen must be redrawn immediately to give feedback to the user following a keystroke, button, or mouse action. In these cases, the application could call do_redraw directly, without waiting for a message. The only time you can bypass do_redraw, and draw without walking the rectangle list, is when you can be sure that the target window is on top, and that the figure being drawn is entirely contained within it. In many cases, however, an application initiated redraw happens because of a computed change, for instance, a spreadsheet update, and its timing is not crucial. In this instance, you may wish to have the app send ITSELF a redraw request. The main advantage of this approach is that the AES is smart enough to see if there is already a redraw request for the same window in the queue, and, if so, to merge the requests by doing a union of their rectangles. In this fashion, the "blinky" appearance of multiple redraws is avoided, without the need to include logic for merging redraws within the program. A utility routine for sending the "self-redraw" is included in the down-load for this article. WINDOW CONTROL REQUESTS An application is notified by the AES, via the message system, when the user manipulates one of the window control points. Remember that you must have specified each control point when the window was created, or will not receive the associated control message. The most important thing to understand about window control is that the change which the user requested does not take place until the application forwards it to the AES. While this makes for a little Page 3 extra work, it gives the program a chance to intervene and validate or modify the request to suit. A second thing to keep in mind is that not all window updates cause a redraw request to be generated for the window, because the AES attempts to save time with raster moves on the screen. Now let's look at each window control request in detail. The message code for a window move is WM_MOVED. If you are willing to accept any such request, just do: wind_set(wh, WF_CXYWH, msg[4], msg[5], msg[6], msg[7]); (Remember that wh, the window handle, is always in msg[3]). The AES will not request a redraw of the window following this call, unless the window is being moved from a location which is partially "off-screen". Instead, it will do a "blit" (raster copy) of the window and its contents to the new location without intervention by the app. There are two constraints which you may often wish to apply to the user's move request. The first is to force the new location to lie entirely within the desktop, rather than partially off-screen. You can do this with the rc_constrain utility by executing: rc_constrain(&full, &msg[4]); before making the wind_set call. (Full is assumed to contain the desktop dimensions.) The second common constraint is to "snap" the x-dimension location of the new location to a word boundary. This operation will speed up GEM's "blit" because no shifting or masking will need to be done when moving the window. To perform this operation, use align() before the wind_set call: msg[4] = align(msg[4], 16); The message code for a window size request is WM_SIZED. Again, if you are willing to accept any request, you can just "turn it around" with the same wind_set call as given for WM_MOVED. Actually, GEM enforces a couple of constraints on sizing. First, the window may not be sized off screen. Second, there is a minimum window size which is dependent on the window components specified when it was created. This prevents features like scroll arrows from being squeezed into oblivion. The most common application constraint on sizing is to snap the size to horizontal words (as above) and/or vertical character lines. In the latter case, the vertical dimension of the output font is used with align(). Also, be aware that the size message which you receive specifies the EXTERNAL dimensions of the window. To assure an "even" size for the Page 4 INTERNAL dimensions, you must make a wind_calc call to compute them, use align() on the computed values, back out the corresponding external dimensions with the reverse wind_calc, and then make the wind_set call with this set of values. A window resize will only cause a redraw request for the window if the size is being increased in at least one dimension. This is satisfactory for most applications, but if you must "reshuffle" the window after a size-down, you should send yourself a redraw (as described above) after you make the wind_set call. This will guarantee that the display is updated correctly. Also note that the sizing or movement of one window may cause redraw requests to be generated for other windows which are uncovered by the change. The window full request, with code WM_FULLED, is actually a toggle. If the window is already at its full size (as specified in the wind_create), then this is a request to shrink to its previous size. If the window is currently small, then the request is to grow to full size. Since the AES records the current, previous, and maximum window size, you can use wind_get calls to determine which situation pertains. The hndl_full utility in the down-load (modified from Doodle), shows how to do this. The "zoom box" effects when changing size are optional, and can be removed to speed things up. Again, if the window's size is decreasing, no redraw is generated, so you must send yourself one if necessary. You should not have to perform any constraint or "snap" operations here, since (presumably) the full and previous sizes have had these checks applied to them already. The WM_CLOSED message is received when the close box is clicked. What action you perform depends on the application. If you want to remove the window, use wind_close as described in the last column. In many applications, however, the close message may indicate that a file is to be saved, or a directory or editing level is to be closed. In these cases, the message is used to trigger this action before or instead of the wind_close. (Folders on the Desktop are an example of this situation.) The WM_TOPPED message indicates that the AES wants to bring the indicated window to the "top" and make it active. This happens if the user clicks within a window which is not on top, or if the currently topped window is closed by its application or desk accessory. Normally, the application should respond to this message with: wind_set(wh, WF_TOP, 0, 0); and allow the process to complete. In a few instances, a window may be used in an output only mode, such as a status display, with at least one other window present for input. In this case, a WM_TOPPED message for the status window may be ignored. In all other cases, you must handle the WM_TOPPED message even if your Page 5 application has only one window: Invocation of a desk accessory could always place another window on top. If you fail to do so, subsequent redraws for your window may not be processed correctly. WINDOW SLIDER MESSAGES If you specify all of the slider bar parts for your window, you may receive up to five different message types for each of the two sets of sliders. To simplify things a little, I will discuss everything in terms of the vertical (right hand side) sliders. If you are also using the horizontal sliders, the same techniques will work, just use the alternate mnemonics. The WM_VSLID message indicates that the user has dragged the slider bar within its box, indicating a new relative position within the document. Along with the window handle, this message includes the relative position between 1 and 1000 in msg[4]. Recall from last column's discussion that this interval corresponds to the "freedom of movement" of the slider. If you want to accept the user's request, just make the call: wind_set(wh, WF_VSLIDE, msg[4], 0, 0, 0); (Corresponding horizontal mnemonics are WM_HSLID and WF_HSLIDE). Note that this wind_set call will not cause a redraw message to be sent. You must update the display to reflect the new scrolled position, either by executing a redraw directly, or by sending yourself a message. If the document within the window has some structure, you may not wish to accept all slider positions. Instead you may want to force the scroll position to the nearest text line (for instance). Using terms defined in the last column, you may convert the slider position to "document units" with: top_wind = msg[4] * (total_doc - seen_doc) / 1000 + top_doc (This will probably require 32-bit arithmetic). After rounding off or otherwise modifying the request, convert it back to slider units and make the WF_VSLIDE request. The other four slider requests all share one message code: WM_ARROWED. They are distinguished by sub-codes stored in msg[4]: WA_UPPAGE, WA_DNPAGE, WA_UPLINE, and WA_DNLINE. These are produced by clicking above and below the slider, and on the Page 6 up and down arrows, respectively. (I have no idea why sub-codes were used in this one instance.) The corresponding horizontal slider codes are: WA_LFPAGE, WA_RTPAGE, WA_LFLINE, and WA_RTLINE. What interpretation you give to these requests will depend on the application. In the most common instance, text documents, the customary method is to change the top of window position (top_wind) by one line for a WA_UPLINE or WA_DNLINE, and by seen_doc (the number of lines in the window) for a WA_UPPAGE or WA_DNPAGE. After making the change, compute a new slider position, and make the wind_set call as given above. If the document's length is not an even multiple of "lines" or "pages" you will have to be careful that incrementing or decrementing top_wind does not exceed its range of freedom: top_doc to (top_doc + total_doc - seen_doc). If you have such an odd size document, you will also have to make a decision on whether to violate the line positioning rule so that the slider may be put at its bottom-most position, or to follow the rule but make it impossible to get the slider to the extreme of its range. A COMMON BUG It is easy to forget that user clicks are not the only things that affect slider position. If the window size changes as a result of a WM_SIZED or WM_FULLED message, the app must also update its sliders (if they are present). This is a good reason to keep the top of window information in "document units". You can just redo the position calculation with the new "seen_doc" value, and call wind_set. Also remember that changing the size of the underlying document (adding or deleting a bottom line, for instance) must also cause the sliders to be adjusted. DEPT. OF DIRTY TRICKS There are two remaining window calls which are useful to advanced programmers. They require techniques which I have not yet discussed, so you may need to file them for future reference. The AES maintains a quarter-screen sized buffer which is used to save the area under alerts and menu drop-downs. It is occasionally useful for the application to gain access to this buffer for its own use in saving screen areas with raster copies. To do so, use: wind_get(0, WF_SCREEN, &loaddr, &hiaddr, &lolen, &hilen); Page 7 Hiaddr and loaddr are the top and bottom 16-bits (respectively) of the 32-bit address of the buffer. Hilen and lolen are the two halves of its length. Due to a preculiarity of the binding you have to reassemble these pieces before using them. (The actual value of WF_SCREEN is 17; this does not appear in some versions of the GEMDEFS.H file.) If you use this buffer, you MUST prevent menus from dropping down by using either the BEG_UPDATE or BEG_MCTRL wind_update calls. Failure to do so will result in your data being destroyed. Remember to use the matching wind_update: END_UPDATE or END_MCTRL, when you are done. The other useful call enables you to replace the system's desktop definition with a resource of your choosing. The call: wind_set(0, WF_NEWDESK, tree, 0, 0); where tree is the 32-bit address of the object tree, will cause the AES to draw your definition instead of the usual gray or green background. Not only that, it will continue to redraw this tree with no intervention on your part. Obviously, the new definition must be carefully built to fit the desktop area exactly or garbage will be left around the edges. For the truly sophisticated, a user-defined object could be used in this tree, with the result that your application's code would be entered from the AES whenever the desktop was redrawn. This would allow you to put VDI pictures or complex images onto the desktop background. A SIN OF OMISSION In the last column, I neglected to mention that strings whose addresses are passed in the WF_NAME and WF_INFO wind_set calls must be allocated in a static data area. Since the AES remembers the addresses (not the characters), a disaster may result if the storage has been reused when the window manager next attempts to draw the window title area. COMING SOON... This concludes our tour of GEM's basic window management techniques. There have been some unavoidable glimpses of paths not yet taken (forward references), but we will return in time. On our next excursion, we will take a look at techniques for handling simple dialog boxes, and start exploring the mysteries of resources and object trees. Page 8 ANTIC PUBLISHING INC., COPYRIGHT 1985. REPRINTED BY PERMISSION. Professional GEM by Tim Oren Column 3: THE DIALOG HANDLER 11/7/85 A MEANINGFUL DIALOG This issue of ST PRO GEM begins an exploration of ST GEM's dialog handler. I will discuss basic system calls for presenting the dialog, and then continue with techniques for initializing and reading on/off button and "radio" button objects. We will also take some short side-trips into the operation of the GEM Resource Construction Set to assist you in building these dialogs. There are a number of short C routines which accompany this column. These are stored as file GEMCL3.SRC. DEFINING TERMS A dialog box is an "interactive form" in which the user may enter text and indicate selections by pointing with the mouse. Dialogs in GEM are "modal", that is, when a dialog is activated other screen functions such as menus and window controls are suspended until the dialog is completed. In most cases, the visual structure of a GEM dialog is specified within your application's resource file. The GEM Resource Construction Set (RCS) is used to build a picture of the dialog. When the RCS writes out a resource, it converts that picture into a tree of GEM drawing objects and stores this data structure within the resource. Before your application can display the dialog, it must load this resource file and find the address of the tree which defines the dialog. To load a resource, the AES checks its size and allocates memory for the load. It then reads in the resource, adjusting internal pointers to reflect the load address. Finally, the object sizes stored in the resource are converted from characters to pixels using the system font size. (A note for those with Macintosh experience: Although Mac and GEM resources share a name, there are fundamental differences which can be misleading. A Mac resource is a fork within a file; a GEM resource is a TOS file by itself. Mac resources may be paged in and out of memory; GEM resources are monolithic. GEM resources are internally tree structured; Mac resources are not. Finally, Mac resources include font information, while ST GEM does this with font loading at the VDI level.) Page 1 The resource load is done with the GEM AES call: ok = rsrc_load(ADDR("MYAPP.RSC")); "MYAPP" should be replaced with the name of your program. Resources conventionally have the same primary name as their application, with the RSC extent name instead of PRG. The ok flag returned by rsrc_load will be FALSE is anything went wrong during the load. The most common causes of failure are the resource not being in the application's subdirectory, or lack of sufficient memory for GEM to allocate space for the resource. If this happens, you must terminate the program immediately. Once you have loaded the resource, you find the address of a dialog's object tree with: rsrc_gaddr(R_TREE, MYDIALOG, &tree); Tree is a 32-bit variable which will receive the address of the root node of the tree. The mnemonic MYDIALOG should be replaced with the name you gave your dialog when defining it in the RCS. At the same time that it writes the resource, RCS generates a corresponding .H file containing tree and object names. In order to use these mnemonics within your program, you must include the name file in your compile: #include "MYAPP.H" BUG ALERT! When using the DRI/Alcyon C compiler, .H files must be in the compiler's home directory or they will not be found. This is especially annoying using a two floppy drive ST development system. The only way around this is to explicitly reference an alternate disk in the #include, for instance: "B:MYAPP.H" Now that the address of the dialog tree has been found, you are ready to display it. The standard (and minimal) sequence for doing so is given in routine hndl_dial() in the download. We will now walk through each step in this procedure. The form_center call establishes the location of the dialog on the screen. Dialog trees generated by the RCS have an undefined origin (upper-left corner). Form_center computes the upper-left location necessary to center the dialog on the screen, and inserts it into the OB_X and OB_Y fields of the ROOT object of the tree. It also computes the screen rectangle which the dialog will occupy on screen and writes its pixel coordinates into variables xdial, ydial, wdial, and hdial. There is one peculiarity of form_center which occasionally causes Page 2 trouble. Normally the rectangle returned in xdial, etc., is exactly the same size as the basic dialog box. However, when the OUTLINED enhancement has been specified for the box, form_center adds a three pixel margin to the rectangle returned. This causes the screen area under the outline to be correctly redrawn later (see below). Note that OUTLINED is part of the standard dialog box in the RCS. Other enhancements, such as SHADOWED or "outside" borders are NOT handled in this fashion, and you must compensate for them in your code. The next part of the sequence is a form_dial call with a zero parameter. This reserves the screen for the dialog action about to occur. Note that the C binding given for form_dial in the DRI documents is in error: there are nine parameters, not five. The first set of xywh arguments is actually used with form_dial calls 1 and 2 only, but place holders must be supplied in all cases. The succeeding form_dial call (parameter one) animates a "zoom box" on the screen which moves and grows from the first screen rectangle given to the second rectangle, where the dialog will be displayed. The use of this call is entirely optional. In choosing whether to use it or not, you should consider whether the origin of the "zoom" is relevant to the operation. For instance, a zoom from the menu bar is relatively meaningless, while a zoom from an object about to be edited in the dialog provides visual feedback to the user, showing whether the correct object was chosen. If the origin is not relevant, then the zoom is just a time-waster. If you decide to include these effects, consider a "preferences" option in your app which will allow the experienced and jaded user to turn them off in the interests of speed. The objc_draw call actually displays the dialog on the screen. Note that the address of the tree, the beginning drawing object, and the drawing depth are passed as arguments, as well as the rectangle allotted for the dialog. In general, dialogs (and parts of dialogs) are ALWAYS drawn beginning at the ROOT (object zero). When you want to draw only a portion of the dialog, adjust the clipping rectangle, but not the object number. This ensures that the background of the dialog is always drawn correctly. The objc_xywh() utility in the download can be used to find the clipping rectangle for any object within a dialog, though you may have to allow an extra margin is you have used shadows, outlines, or outside borders with the object. Calling form_do transfers control to the AES, which animates the dialog for user interaction. The address of the dialog tree is passed as a parameter. The second paramter is the number of the editable object at which the text cursor will first be positioned. If you have no text fields, pass a zero. Note that again the DRI documents are in error: Page 3 passing a -1 default may crash the system. Also be careful that the default which you specify is actually a text field; no error checking is performed. The form_do call returns the number of the object on which the clicked to terminate the dialog. Usually this is a button type object with the EXIT and SELECTABLE attributes set. Setting the DEFAULT attribute as well will cause an exit on that object is a carriage return is struck while in the dialog. If the top bit of the return is set, it indicates that the exit object had the TOUCHEXIT attribute and was selected with a double-click. Since very few dialogs use this combination, the sample code simply masks off the top bit. The next form_dial call reverses the "zoom box", moving it from the dialog's location back to the given x,y,w,h. The same cautions apply here as above. The final form_dial call tells GEM that the dialog is complete, and that the screen area occupied by the dialog is now considered "dirty" and needs to be redrawn. Using the methods described in our last column, GEM then sends redraws to all windows which were overlaid, and does any necessary redrawing of the menu or desktop itself. There is one notable "feature" of form_dial(3): It always redraws an area which is two pixels wider and higher than your request! This was probably included to make sure that drop-shadows were cleaned up, and is usually innocuous. A HANDY TRICK Use of the form_dial(3) call is not limited to dialogs. You can use it to force the system to redraw any part of the screen. The advantage of this method is that the redraw area need not lie entirely within a window, as was necessary with the send_redraw method detailed in the last column. A disadvantage is that this method is somewhat slower, since the AES has to decide who gets the redraws. CLEAN UP As a last step, you need to clear the SELECTED flag in the object which was clicked. If you do not do this, the object will be drawn inverted the next time you call the dialog. You could clear the flag with the GEM objc_change call, but it is inefficient since you do not need to redraw the object. Instead, use the desel_obj() code in the download, which modifies the object's OB_STATE field directly. Assuming that ret_obj contains the exit object returned by hndl_dial, the call: desel_obj(tree, ret_obj); Page 4 will do the trick. RECAP The basic dialog handling method I have described contains three steps: initialization (rsrc_gaddr), dialog presentation (hndl_dial), and cleanup (desel_obj). As we build more advanced dialogs, these same basic steps will be performed, but they will grow more complex. The initialization will include setting up proper object text and states, and the cleanup phase will also interrogate the final states of objects to find out what the user did. BUTTON, BUTTON The simple dialogs described above contain only exit buttons as active objects. As such, they are little more than glorified alert boxes. We will now increase the complexity a little by considering non-exit buttons. These are constructed by setting the SELECTABLE attribute on a button object. At run-time, such an object will toggle its state between selected (highlighted) and non-selected whenever the user clicks on it. (You can set the SELECTABLE attribute of other types of objects and use them instead of actual buttons, but be sure that the user will be able to figure out what you intend!) Having non-exit buttons forces us to consider the problem of initializing them before the dialog, and interrogating and resetting them afterward. Since a button is a toggle, it is usually associated with a flag variable in the program. As part of the initialization, you should test the flag variable, and if true call: sel_obj(tree, BTNOBJ); which will cause the button to appear highlighted when the dialog is first drawn. Sel_obj() is in the download. BTNOBJ is replaced with the name you gave your button when you defined it in the RCS. Since the button starts out deselected, you don't have to do anything if your flag variable is false. After the dialog has completed, you need to check the object's state. The selectp() utility does so by masking the OB_STATE field. You can simply assign the result of this test to your flag variable, but be sure that the dialog was exited with an OK button, not with a CANCEL! Again, remember to clean up the button with desel_obj(). (It's often easiest to deselect all buttons just before you leave the dialog routine, regardless of the final dialog state.) Page 5 WHO'S GOT THE BUTTON? Another common use of buttons in a dialog is to select one of a set of possible options. In GEM, such objects are called radio buttons. This term recalls automobile radio tuners where pushing in one button pops out any others. In like fashion, selecting any one of a set of radio buttons automatically deselects all of the others. To use the radio button feature, you must do some careful work with the Resource Construction Set. First, each member of a set of radio buttons must be children of the same parent object within the object tree. To create this structure, put a hollow box type object in the dialog, make it big enough to hold all of the buttons, and then put the buttons into the box one at a time. By nesting the buttons within the box object, you force them to be its children. Each of the buttons must have both the SELECTABLE and RADIO BUTTON attributes set. When you are done, you may make the containing box invisible by setting its border to zero, but do not FLATTEN it! Since each radio button represents a different option, you must usually assign a name to each object. When initializing the dialog, you must check which option is currently set, and turn on the corresponding button only. A chain of if-then-else structures assures that only one button will be selected. At the conclusion of the dialog, you must check each button with selectp() and make the appropriate adjustments to internal variables. Again, an if-then-else chain is appropriate since only one button may be selected. Either deselect the chosen button within this chain or do them all at the end. There is one common use of radio buttons in which you may short-cut this procedure. If the buttons each represent one possible value of a numeric variable, for instance, a set of selector buttons representing colors from zero to seven, then you can compute the initial object directly. In order for this technique to work, you must use a special capability of the RCS. Insert the object corresponding to a zero value at the top (or left) of your array of buttons, then put the "one" button below (or right) of it, and so on. When the buttons are complete, the SORT operation is used to guarantee that the top/left object is in fact the first child of the parent box with the others following in order. Due to the details of object tree structure (to be discussed in the next column), this will guarantee that these objects are contiguous in the resource. If you assign a name (say BUTTON1) to the first button, then you can initialize the correct button with the call: Page 6 sel_obj(tree, BUTTON1 + field); where field is the variable of interest. When the dialog is complete, you can scan the radio buttons to compute the new value for the underlying variable. The encode() procedure in the download will do this. As always, remember to deselect the buttons at the end. You can use offsets or multipliers if your variable's values don't start with zero or increment by one. If the values are irregular you may be able to use a lookup table, at the cost of additional code. COMING UP NEXT In the next column, I will discuss the internal structure of object trees. Then we'll use that knowledge to build a piece of code which will "walk" an entire tree and apply a function to each object. We'll apply this code to do all of the button deselects with a single call! I'll also look at handling editable text fields and discuss some ways to alter a dialog's appearance at run-time. DISPELL GREMLINS An editing error caused an omission in the first installment of ST PRO GEM. The window components RTARROW and DNARROW should have been listed along with HSLIDE as the horizontal equivalents of the vertical slider components which were discussed. Page 7 ANTIC PUBLISHING INC., COPYRIGHT 1985. REPRINTED BY PERMISSION. ** Professional GEM ** by Tim Oren Column 4: Resource Files Welcome to the fourth installment of ST PRO GEM. We are about to delve into the mysteries of GEM resource structure, and then use this knowledge to create some useful utilities for handling dialogs. As with the past columns, there is once again an associated file called GEMCL4.SRC. The first and largest part of this file contains a C image of a sample resource file. To create this listing, I used the GEM Resource Construction Set to create a dummy resource with three dialogs including examples of all object types, then enabled the C output option and saved the resource. If you have access to a copy of RCS, I suggest that you create your own listing in order to get a feel for the results. Then, using either listing as a roadmap to the resource, you can follow along as we enter... A MAZE OF TWISTY LITTLE PASSAGES. While a GEM resource is loaded as a block of binary information, it is actually composed of a number of different data structures. These structures are linked together in a rather tangled hierarchy. Our first job is to map this linkage system. The topmost structure in a resource file is the resource header. This is an array of words containing the size and offset within the resource of the other structures which follow. This information is used by GEM during the resource load process, and you should never need to access it. (The resource header does not appear in the C output file; it is generated by the RSCREATE utility if the C file is used to recreate the resource.) The next structure of interest is the tree index. This is an array of long pointers, each of which addresses the beginning of an object tree. Again, you wouldn't normally access this structure directly. The GEM rsrc_gaddr call uses it when finding trees' addresses. This structure is called "rs_trindex" in the C output. If you look at the contents of rs_trindex you will notice that the values are integers, instead of the pointers I described. What has happened is that RCS has converted the pointers to indices into the object array. (If you actually used the C file to recreate the resource file, then the pointers would be regenerated by RSCREATE.) Now you can follow the link from rs_trindex to the objects stored in rs_object. Take (for instance) the second entry in rs_trindex and count down that many lines in rs_object. The following line (object) should start with a -1. This indicates that it is the root object of a Page 1 tree. The following objects down to the next root belong to that tree. We'll pass over the details of inter-object linkage for now, leaving it for a later column. There are a number of different fields in an object, but right now we'll concentrate on two of them: OB_TYPE and OB_SPEC. The OB_TYPE is the field which contains mnemonics like G_STRING and G_BOX indicating the type of the object. The OB_SPEC is the only field in each object which is a LONG - you can tell it by the L after the number. What's in OB_SPEC depends on the object type, so we need to talk about what kinds of objects are available, what you might use them for, and finally how they use the OB_SPEC field. The box type objects are G_BOX, G_IBOX, and G_BOXCHAR. A G_BOX is an opaque rectangle, with an optional border. It's used to create a solid patch of color or pattern on which to place other objects. For instance, the background of a dialog is a G_BOX. A G_IBOX is a hollow box which has only a border. (If the border has no thickness, then the box is "invisible", hence the name.) The favorite use for IBOXes is to hold radio buttons. There is also one neat trick you can play with an IBOX. If you have more than one object (say an image and a string) which you would like to have selected all at once, you can insert them in a dialog, then cover them with an IBOX. Since the box is transparent, they will show through. If you now make the box selectable, clicking on it will highlight the whole area at once! The G_BOXCHAR is just like a G_BOX, except that a single character is drawn in its center. They are mostly used as "control points": the FULLER, CLOSER, SIZER, and arrows in GEM windows are BOXCHARs, as are the components of the color selection gadgets in the RCS. The OB_SPEC for box type objects is a packed bit array. Its various fields contain the background color and pattern, the border thickness and color, and the optional character and its color. The string type objects are G_STRING, G_BUTTON, and G_TITLE. G_STRINGs (in addition to being a bad pun) are for setting up static explanatory text within dialogs. The characters are always written in the "system font": full size, black, with no special effects. We have already discussed many of the uses of G_BUTTONs. They add a border around the text. The thickness of a G_BUTTON's border is determined by what flags are set for the object. All buttons start out with a border thickness of one pixel. One pixel is added if the EXIT attribute is set, and one more is added if the DEFAULT attribute is set. The G_TITLE type is a specially formatted text string used only in the title bar of menus. This type is needed to make sure that the menus redraw correctly. The Resource Construction Set automatically handles inserting G_TITLEs, so you will seldom use them directly. Page 2 In a resource, the OB_SPEC for all string objects is a long pointer to a null terminated ASCII string. The string data in the C file is shown in the BYTE array rs_strings. Again you will notice that the OB_SPECs in the C file have been converted to indices into rs_string. To find the string which matches the object, take the value of OB_SPEC and count down that many lines in rs_strings. The next line is the correct string. The formatted text object types are G_TEXT, G_BOXTEXT, G_FTEXT, and G_FBOXTEXT. G_TEXTs are a lot like strings, except that you can specify a color, different sizes, and a positioning rule for the text. Since they require more memory than G_STRINGs, G_TEXTs should be used sparingly to draw attention to important information within a dialog. G_TEXTs are also useful for automatic centering of dialog text which is changed at run-time. I will describe this technique in detail later on. The G_BOXTEXT type adds a solid background and border to the G_TEXT type. These objects are occasionally used in place of G_BUTTONs when their color will draw attention to an important object. The G_FTEXT object is an editable text field. You are able to specify a constant "template" of characters, a validation field for those characters which are to be typed in, and an initial value for the input characters. You may also select color, size, and positioning rule for G_FTEXTs. We'll discuss text editing at length below. The G_FBOXTEXT object, as you might suspect, is the same as G_FTEXT with the addition of background and border. This type is seldom used: the extra appearance details distract attention from the text being edited. The OB_SPEC for a formatted text object is a pointer to yet another type of structure: a TEDINFO. In the C file, you will find these in rs_tedinfo. Take the OB_SPEC value from each text type object and count down that many entries in rs_tedinfo, finding the matching TEDINFO on the next line. Each contains pointers to ASCII strings for the template, validation, and initialization. You can find these strings in rs_strings, just as above. There are also fields for the optional background and border details, and for the length of the template and text. As we will see when discussing editing, the most important TEDINFO fields are the TE_PTEXT pointer to initialized text and the TE_TXTLEN field which gives its length. The G_IMAGE object type is the only one of its kind. A G_IMAGE is a monochrome bit image. For examples, see the images within the various GEM alert boxes. Note that monochrome does not necessarily mean black. The image may be any color, but all parts of it are the SAME color. G_IMAGEs are used as visual cues in dialogs. They are seldom used as selectable items because their entire rectangle is inverted when they are clicked. This effect is seldom visually pleasing, particularly if the image is colored. Page 3 G_IMAGE objects have an OB_SPEC which is a pointer to a further structure type: the BITBLK. By now, you should guess that you will find it in the C file in the array rs_bitblk. The BITBLK contains fields describing the height and width of the image in pixels, its color,nd it also contains a long pointer to the actual bits which make up the image. In the C file, the images are encoded as hexadecimal words and stored in arrays named IMAG0, IMAG1, and so on. The last type of object is the G_ICON. Like the G_IMAGE, the G_ICON is a bit image, but it adds a mask array which selects what portions of the image will be drawn, as well as an explanatory text field. A G_ICON may also specify different colors for its "foreground" pixels (the ones that are normally black), and its "background" pixels (which are normally white). The pictures which you see in Desktop windows are G_ICONs, and so are the disks and trashcan on the desktop surface. With the latter you will notice the effects of the mask. The desktop shows through right up to the edge of the G_ICON, and only the icon itself (not a rectangle) is inverted when a disk is selected. The OB_SPEC of an icon points to another structure called an ICONBLK. It is shown in the C file as rs_iconblk. The ICONBLK contains long pointers to its foreground bit array, to the mask bit array, and to the ASCII string of explanatory text. It also has the foreground and background colors as well as the location of the text area from the upper left of the icon. The most common use of G_ICONs and ICONBLKs is not in dialogs, instead they are used frequently in trees which are build at run-time, such as Desktop windows. In a future article, we will return to a discussion of building such "on-the-fly" trees with G_ICONs. Now, let's recap the hierarchy of resource structures: The highest level structures are the resource header, and then the tree index. The tree index points to the beginning of each object tree. The objects making up the tree are of several types, and depending on that type, they may contain pointers to ASCII strings, or to TEDINFO, ICONBLK, or BITBLK structures. TEDINFOs contain further pointers to strings; BITBLKs have pointers to bit images; and ICONBLKs have both. PUTTING IT TO WORK. The most common situations requiring you to understand resource structures involve the use of text and editable text objects in dialogs. We'll look at two such techniques. Often an application requires two or more dialogs which are very similar except for one or two title lines. In this circumstance, you can save a good deal of resource space by building only one dialog, and changing the title at run time. It is easy to go wrong with this practice, however, because the obvious tactic of using a G_STRING and writing over its text at run time can go wrong. The first problem is that you must know in advance the longest Page 4 title to be used, and put a string that long into the resource. If you don't you will damage other objects in the resource as you copy in characters. The other problem is that a G_STRING is always drawn at the same place in a dialog. If the length of the title changes from time to time, the dialog will have an unbalanced and sloppy appearance. A better way to do this is to exploit the G_TEXT object type, and the TEDINFO structure. The set_text() routine in the download shows how. The parameters provided are the tree address, the object number, and the 32-bit address of the string to be substituted. For this to work, the object referenced should be defined as a G_TEXT type object. Additionally, the Centered text type should be chosen, and the object should have been "stretched" so that it fills the dialog box from side to side. In the code, the first action is to get the OB_SPEC from the object which was referenced. Since we know that the object is a G_TEXT, the OB_SPEC must point to a TEDINFO. We need to change two fields in the TEDINFO. The TE_PTEXT field is the pointer to the actual string to be displayed; we replace it with the address of our new string. The TE_TXTLEN field is loaded with the new string's length. Since the Centered attribute was specified for the object, changing the TE_TXTLEN will cause the string to be correctly positioned in the middle of the dialog! Editing text also requires working with the TEDINFO structure. One way of doing this is shown in the download. The object to be used (EDITOBJ) is assumed to be a G_FTEXT or G_FBOXTEXT. Since we will replace the initialized text at run time, that field may be left empty when building the object in the RCS. The basic trick of this code is to point the TEDINFO's TE_PTEXT at a string which is defined in your code's local stack. The advantages of this technique are that you save resource space, save static data by putting the string in reusable stack memory, and automatically create a scratch string which may be discarded if the dialog is cancelled. The text string shown is arbitrarily 41 characters long. You should give yours a length equal to the number of blanks in the object's template field plus one. Note that the code is shown as a segment, rather than a subroutine. This is required because the text string must be allocated within the context of dialog handling routine itself, rather than a routine which it calls! After the tree address is found, the code proceeds to find the TEDINFO and modify its TE_PTEXT as described above. However, the length which is inserted into TE_TXTLEN must be the maximum string length, including the null! The final line of code inserts a null into the first character of the uninitialized string. This will produce an empty editing field when the dialog is displayed. If there is an existing value for the object, you should instead use strcpy() to move it into text[]. Once the dialog is complete, you should check its final status as described in the last Page 5 article. If an "OK" button was clicked, you will then use strcpy() to move the value in text[] back to its static location. Although I prefer this method of handling editable text, another method deserves mention also. This procedure allocates a full length text string of blanks when creating the editable object in the RCS. At run-time, the TE_PTEXT link is followed to find this string's location in the resource, and any pre-existing value is copied in. After the dialog is run, the resulting value is copied back out if the dialog completed successfully. Note that in both editing techniques a copy of the current string value is kept within the application's data area. Threading the resource whenever you need to check a string's value is extremely wasteful. One final note on editable text objects: GEM's editor uses the commercial at sign '@' as a "meta-character". If it is the first byte of the initialized text, then the field is displayed blank no matter what follows. This can be useful, but is sometimes confusing when a user in all innocence enters an @ and has his text disappear the next time the dialog is drawn! LETTERS, WE GET LETTERS. The Feedback section on ANTIC ST ONLINE is now functional and is producing a gratifying volume of response. A number of requests were made for topics such as ST hardware and ST BASIC which are beyond the intended scope of this column. These have been referred to ANTIC's editorial staff for action. So many good GEM questions were received that I will devote part of the next column to answering several of general interest. Also, your requests have resulting in scheduling future columns on VDI text output and on the principles (or mythology) of designing GEM application interfaces. Finally, a tip of the hat to the anonymous reader who suggested including the actual definitions of all macro symbols, so that those without the appropriate H files can follow along. As a result of this suggestion, the definitions for this column and the previous three are included at the end of the download. Future articles will continue this practice. STRAW POLL! I'd like to make a practice of using the Feedback to get your opinions on the column's format. As a first trial, I'd like to know your feelings about my use of "portability macros" in the sample code. These macros, LLGET for example, are used for compatibility between 68K GEM systems like the ST, and Intel based systems like the IBM PC. This may be important to many developers. On the other hand, omitting them results in more natural looking C code. For instance, in the download you will find a second version of set_text() as described above, but without the portability macros. So, I would like to know if you think we should (A) Keep the macros - portability is important to serious Page 6 developers, (B) Get rid of them - who cares about Intel chips anyway, or (C) Who cares? I'll tally the votes in two weeks and announce the results here. STAY TUNED! As well as answers to feedback questions, the next column will discuss how GEM objects are linked to form trees, and how to use AES calls and your own code to manipulate them for fun and profit. In the following installment, we'll look at the VDI raster operations (also known as "blit" functions). Page 7 ANTIC PUBLISHING INC., COPYRIGHT 1985. REPRINTED BY PERMISSION. ** PROFESSIONAL GEM ** by Tim Oren Column 5: Resource Tree Structures This is the fifth issue of ST PROFESSIONAL GEM, concluding our trek through GEM dialogs and resources with a look at the internal structure of object trees. Also, I'll answer a number of questions of general interest which have been received via the ANTIC ONLINE FEEDBACK. As always, there is a file associated with this column: GEMCL5.SRC. In the last installment, we established that resources trees are pointed to by the tree index, and that they are composed of objects which contain pointers onward to other structures. However, we passed over the issue of linkage among the objects within a tree. It is now time to go back and cure this omission. The technical term for the linkage scheme of an object tree is a "right-threaded binary tree". If you already know what this is, you can skim over the next few paragraphs. If you happen to have access to a copy of the book "FUNDAMENTAL ALGORITHMS", which is part of the series THE ART OF COMPUTER PROGRAMMING by Donald E. Knuth, you might want to read his excellent discussion of binary trees beginning on page 332. For the following discussion, you should have a listing of the C image of a resource tree in front of you. For those who do not have the listing from the last column, I have included a fragment at the beginning of the download. Before we begin, I should warn you of one peculiarity of "computer trees": They grow upside-down! That is, when they are diagrammed or described, their root is at the top, and the "leaves" grow downward. You will see this both in the listing, and in the way the following discussion talks about moving through trees. Each GEM object tree begins at its ROOT object, numbered zero, which is the object pointed at by the tree index. There are three link fields at the beginning of each object. They are called OB_NEXT, OB_HEAD, and OB_TAIL, which is the order in which they appear. Each of the links is shown as an index relative to the root of the current tree. This means that the link '0' would refer to the root of the tree, while '2' would indicate the object two lines below it. The special link -1 is called NIL, and means that there is no link in the given direction. Each object, or node, in a tree may have "offspring" or nodes which are nested below it. If it does, then its OB_HEAD will point to its first (or "leftmost") "child", while the OB_TAIL will point to the last ("rightmost") of its offspring. The OB_NEXT pointer links the children together, with the OB_NEXT of the first pointing to the second, and so on, until the OB_NEXT of the last finally points back to its parent, the object at which we started. Page 1 Remember that each of these children may in turn have offspring of their own, so that the original "parent" may have a large and complex collection of "descendents". Let's look at the first tree in the download to see an example of this structure. The very first object is the ROOT. Note that its OB_NEXT is NIL, meaning that there are no more objects in the tree: the ROOT is both the beginning and the end of the tree. In this case, the OB_HEAD is 1 and the OB_TAIL is 3, showing that there are at least two different children. Following OB_HEAD down to the next line, we can trace through the OB_NEXT links (2, 3, 0) as they lead through a total of three children and back to the ROOT. You will notice that the first two children have NIL for the OB_HEAD and OB_TAILs, indicating that they have no further offspring. However, node three, the last child of the ROOT, does have the value 4 for both its OB_HEAD and OB_TAIL. By this we can tell that it has one, and only one, offspring. Sure enough, when we look at node four, we see that its OB_NEXT leads immediately back to node three. Additionally, it has no further offspring because its OB_HEAD and OB_TAIL are NIL. You will find that object trees are always written out by the Resource Construction Set in "pre-order". (Again, see Knuth if you have a copy.) This means that the ROOT is always written first, then its offspring left to right. This rule is applied recursively, that is, we go down to the next level and write out each of these nodes, then THEIR children left to right, and so on. For a further example, look at the next tree in rs_object in the download. You will see that the ROOT has an OB_HEAD of 1 and an OB_TAIL of 6, but that it actually has only three offspring (nodes 1, 2 and 6). We see that node 2 itself had children, and applying the rule given above, they were written out before continuing with the next child of the ROOT. Why was this seemingly complex structure chosen for GEM? The reason has do with the tasks of drawing objects in their proper locations on the screen, and determining which object was "hit" when a mouse click is detected. To find out how this works, we must look at four more fields found in each object: OB_X, OB_Y, OB_WIDTH, and OB_HEIGHT. These fields are the last four on each line in the sample trees. Each object in a tree "owns" a rectangle on the screen. These fields define that rectangle. When a resource is stored "outside" the program the fields are in character units, so that an object with OB_WIDTH of 10 and OB_HEIGHT of 2 (for instance) would define a screen area 10 characters wide and 2 high. When the resource is read into memory with an rsrc_load call, GEM multiplies the appropriate character dimension in pixels into each of Page 2 these fields. In this way portability is achieved: the same resource file works for any of the ST's three resolutions. Knowing how rsrc_load works, your code should treat these fields as pixel coordinates. I have committed one oversimplification above. If an object is not created on a character boundary in the RCS, then the external storage method described will not work. In this case, the lower byte of each rectangle field is used to store the nearest character position, while the upper byte stores the pixel remainder to be added after the character size is multiplied in. Non-character-boundary objects may only be created in the "FREE" tree mode of the Resource Construction Set (also called "PANEL" in RCS 2.0). You should use them only in programs which will run in a single ST screen mode, because pixel coordinates are not portable between resolutions.) The first real secret of object rectangles is that each OB_X and OB_Y is specified RELATIVE to the X and Y coordinate of its parent object within the tree. This is the first property we have seen that is actually "inherited" from level to level within the tree. The second secret is more subtle: Every object's rectangle must be entirely contained within the rectangle of its parent. This principle goes by the names "bounding rectangles" or "visual hierarchy". We'll see in a moment how useful it is when detecting mouse/object collisions. HOW GEM DOES IT. Knowing these secrets, and the linkage structure of object trees, we can deduce how a number of the GEM operations must work. For instance, consider objc_offset, which returns the actual screen X and Y of an object. We can see now that simply loading the OB_X and OB_Y fields of the object does not suffice: they only give the offset relative to the parent object. So, objc_offset must BEGIN with these values, and then work its way back up to the ROOT of the tree, adding in the offsets found at each level. This can be done by following the OB_NEXT links from the chosen object. Whenever OB_NEXT points to an object whose OB_TAIL points right back to the same location, then the new node is another level, or "parent" in the tree, and objc_offset adds its OB_X and OB_Y into the running totals. When OB_NEXT becomes NIL, then the ROOT has been reached and the totals are the values to return. (By the way, remember that the OB_X and OB_Y of the ROOT are undefined until form_center has been called for the tree. They are shown as zeroes in the sample trees.) We can also figure out objc_draw. It works its way DOWN the tree, drawing each object as it comes to it. It, too, must keep a running X and Y variable, adding in object offsets as it descends tree levels (using OB_HEAD), and subtracting them again as it returns from each level. Since the larger objects are nearer the ROOT, we can now see Page 3 why they are drawn first, with smaller objects drawn later or "on top of" them. (If you write an application which needs to move portions of a dialog or screen with respect to each other, you can take advantage of inheritance of screen position in objc_draw. Simply by changing the OB_X and/or OB_Y of an object, you can move it and its entire sub-tree to a new location in the dialog. For instance, changing the coordinates of the parent box of a set of radio buttons will cause all of the buttons to move along with it.) Objc_draw also gives us an example of the uses of visual hierarchy. Recall that a clipping rectangle is specified when calling objc_draw. At each level of the tree we know that all objects below are contained in the screen rectangle of the current object. If the current rectangle falls completely outside the specified clipping rectangle, we know immediately that we need not draw the object, or any of its descendents! This ability to ignore an entire subtree is called "trivial rejection". Now it's rather easy to figure out objc_find. It starts out by setting its "object found" variable to NIL. It begins a "walk" through the entire object tree, following OB_HEAD and OB_NEXT links, and keeping a current X and Y, just like objc_draw. At each node visited, it simply checks to see if the "mouse" X,Y specified in the call are inside the current object's rectangle. If they are, that object becomes the found object, and the tree walk continues with the object's offspring, and then siblings. Notice how this checking of offspring makes sure that a smaller object nested within, i.e., below, a larger object is found correctly. If the mouse X,Y position is not within the object being checked, then by visual hierarchy it cannot be within any of its offspring, either. Trivial rejection wins again, and the entire sub-tree is skipped! Objc_find moves on to the OB_NEXT of the rejected object. THOUGHT EXPERIMENTS. Thinking about the objc_find algorithm reveals some information about its performance, and a few tricks we may use in improving the appearance of dialogs and other object trees. First consider the problem of a dialog which contains many objects. If we lay them all out "side-by-side", then they will all be immediate offspring of the ROOT object. In this situation, the trivial rejection method will gain nothing. The time objc_find takes to complete will vary linearly with the total number of objects. This is called an "Order N" process. Suppose that instead we broke up the dialog into two areas with invisible boxes, then broke up each of these areas in a like fashion, and so on until we got down to the size of the individual selectable objects. The number of bottom level objects in this scheme is a power Page 4 of two equal to the depth of the tree. Trivial rejection is used to its fullest in this case. It is called an "Order Log N" process, and is much more efficient for large numbers of objects. In practice, the speed of the ST will allow you to ignore this distinction for most dialogs and other trees. But if you get into a situation when speed is critical in searching a large tree, remember that nesting objects can improve performance dramatically. If you have been following closely, you may have also noticed a hole in the visual hierarchy rule. It says that all of a node's children must lie within its rectangle, but it does NOT guarantee that the children's rectangles will be disjoint, that is, not overlap one another. This peculiarity is the basis of several useful tricks. First, remember that objc_find always tries to scan the entire tree. That is, it doesn't quit when it finds the first object on the given coordinates. As mentioned above, this normally guarantees that nested objects will be found. Consider, however, what happens when the mouse coordinates are on a point where two or more objects AT THE SAME LEVEL overlap: they will replace one another as the "found object" until objc_find returns with the one which is "last", that is, rightmost in the tree. This quirk can be used to advantage in a number of cases. Suppose that you have in a dialog an image and a string which you would like to be selected together when either is clicked. Nesting within a common parent achieves nothing in this case. Instead, knowing that form_do must use objc_find, you could use our trick. You have to know that the Resource Construction Set normally adds objects in a tree left to right, in the order in which you inserted them. You proceed to build the dialog in the following order: insert the image first, the string next, then carefully add an invisible box which is not nested within either, and size it to cover them both. Set the SELECTABLE attribute for the box, and the dialog manager will find it, and invert the whole area, when either the image or string is clicked. By the way, remember that the SORT option in the RCS will change the order of an object's offspring. If you are going to try this trick, don't use SORT! It will undo all of your careful work. A TREEWALKER OF OUR OWN. Since the GEM system gets so much mileage out of walking object trees, it seems reasonable that the same method should be useful in application programs. In the download you will find map_tree(). As many LISP veterans might guess from the name, this code will traverse all or part of an object tree, applying a function to each node. It also allows the function to return a true/false value specifying whether the sub-tree below a particular node should be ignored. Let's examine map_tree() in more detail as a final review of object tree structure. Page 5 First, look at the parameters. "tree" is the long address of the object tree of interest, as retrieved by rsrc_gaddr. "this" is the node at which to begin the traverse, and "last" is the node at which to terminate. In most cases, the beginning node will be ROOT, and the final value will be NIL. This will result in the entire tree being traversed. You may use other values, but be sure that you CAN get to "last" from "this" by following tree links! Although map_tree() includes a safety check to prevent "running off" the tree, you could get some very strange results from incorrect parameters. The declaration for the final parameter, "routine", makes use of C construct which may be new to some. It is a pointer to a subroutine which returns a WORD as a result. Map_tree() begins by initializing a temporary variable, tmp1, which is used to store the number of the last node visited. Since no node will follow itself, setting tmp1 to the starting node is safe. The main loop of the routine simply repeats visiting a new node until the last value is reached, or the safety check for end of tree is satisfied. At any node visited, we can be in one of two conditions. Either we are at a node which is "new", that is, not previously visited, or else we are returning to a parent node which has already been processed. We can detect the latter condition by comparing the last node visited (tmp1) with the OB_TAIL pointer of the current node. If the node is "old", it is not processed a second time, we simply update tmp1 and continue. If the node is new, we call "routine" to process it, sending the tree address and object number as parameters. If a FALSE is returned, we will ignore any subtree below this node. On a TRUE return, we load up the OB_HEAD pointer and follow it if a subtree exists. (If you don't care about rejecting subtrees, simply remove the if condition.) Finally, if the new node had no subtree, or was rejected by "routine", we follow along its OB_NEXT link to the next node. A simple application of our new tool shows its power. From a previous column you may recall the tedium of deselecting every button inside a dialog after it was completed. Using map_tree(), you can deselect EVERY OBJECT in the tree by using map_tree(tree, ROOT, NIL, desel_obj); You must use a slightly modified version of desel_obj() (included in the download) which always returns TRUE. Be sure to define or declare desel_obj() in your code BEFORE making the map_tree call! In future columns, I will return to map_tree() and show how it can be used for advanced techniques such as animated dialogs. In the meantime, experiment and enjoy! Page 6 ANTIC PUBLISHING INC.,COPYRIGHT 1985. REPRINTED BY PERMISSION. ** PROFESSIONAL GEM ** by Tim Oren Column 6: RASTER OPERATIONS SEASONS GREETINGS! This is the Yuletide installment of ST PRO GEM, devoted to explaining the raster, or "bit-blit" portion of the Atari ST's VDI functions. Please note that this is NOT an attempt to show how to write directly to the video memory, although you will be able to deduce a great deal from the discussion. As usual, there is a file with this column called GEMCL6.SRC. DEFINING TERMS To understand VDI raster operations, you need to understand the jargon used to describe them. (Many programmers will be tempted to skip this section and go directly to the code. Please don't do it this time: Learning the jargon is the larger half of understanding the raster operations!) In VDI terms a raster area is simply a chunk of contiguous words of memory, defining a bit image. This chunk is called a "form". A form may reside in the ST's video map area or it may be in the data area of your application. Forms are roughly analogous to "blits" or "sprites" on other systems. (Note, however, that there is no sprite hardware on the ST.) Unlike other systems, there is NO predefined organization of the raster form. Instead, you determine the internal layout of the form with an auxiliary data structure called the MFDB, or Memory Form Definition Block. Before going into the details of the MFDB, we need to look at the various format options. Their distinguishing features are monochrome vs. color, standard vs. device-specific and even-word vs. fringed. MONOCHROME VS. COLOR Although these terms are standard, it might be better to say "single-color vs. multi-color". What we are actually defining is the number of bits which correspond to each dot, or pixel, on the screen. In the ST, there are three possible answers. The high-resolution mode has one bit per pixel, because there is only one "color": white. In the medium resolution color mode, there are four possible colors for each pixel. Therefore, it takes two bits to represent each dot on the screen. (The actual colors which appear are determined by the settings of the ST's pallette registers.) Page 1 In the low resolution color mode, sixteen colors are generated, requiring four bits per pixel. Notice that as the number of bits per pixel has been doubled for each mode, so the number of pixels on the screen has been halved: 640 by 400 for monochrome, 640 by 200 for medium-res, and 320 by 200 by low-res. In this way the ST always uses the same amount of video RAM: 32K. Now we have determined how many bits are needed for each pixel, but not how they are laid out within the form. To find this out, we have to see whether the form is device-dependent or not. STANDARD VS. DEVICE-SPECIFIC FORMAT The standard raster form format is a constant layout which is the same for all GEM systems. A device-specific form is one which is stored in the internal format of a particular GEM system. Just as the ST has three different screen modes, so it has three different device-specific form formats. We will look at standard form first, then the ST-specific forms. First, it's reasonable to ask why a standard format is used. Its main function is to establish a portability method between various GEM systems. For instance, an icon created in standard format on an IBM PC GEM setup can be moved to the ST, or a GEM Paint picture from an AT&T 6300 could be loaded into the ST version of Paint. The standard format has some uses even if you only work with the ST, because it gives a method of moving your application's icons and images amongst the three different screen modes. To be sure, there are limits to this. Since there are different numbers of pixels in the different modes, an icon built in the high-resolution mode will appear twice as large in low-res mode, and would appear oblong in medium-res. (You can see this effect in the ST Desktop's icons.) Also, colors defined in the lower resolutions will be useless in monochrome. The standard monochrome format uses a one-bit to represent black, and uses a zero for white. It is assumed that the form begins at the upper left of the raster area, and is written a word at a time left to right on each row, with the rows being output top to bottom. Within each word, the most significant bit is the left-most on the screen. The standard color form uses a storage method called "color planes". The high-order bits for all of the pixels are stored just as for monochrome, followed by the next-lowest bit in another contiguous block, and so on until all of the necessary color bits have been stored. For example, on a 16-color system, there would be four different planes. The color of the upper-leftmost bit in the form would be determined by concatenating the high-order bit in the first word of each plane of the form. The system dependent form for the ST's monochrome mode is very simple: it is identical to the standard form! This occurs because the ST uses Page 2 a "reverse-video" setup in monochrome mode, with the background set to white. The video organization of the ST's color modes is more complicated. It uses an "interleaved plane" system to store the bits which make up a pixel. In the low-resolution mode, every four words define the values of 16 pixels. The high-order bits of the four words are merged to form the left-most pixel, followed by the next lower bit of each word, and so on. This method is called interleaving because the usually separate color planes described above have been shuffled together in memory. The organization of the ST's medium-resolution mode is similar to low-res, except the only two words are taken at a time. These are merged to create the two bits needed to address four colors. You should note that the actual color produced by a particular pixel value is NOT fixed. The ST uses a color remapping system called a palette. The pixel value in memory is used to address a hardware register in the palette which contains the actual RGB levels to be sent to the display. Programs may set the palette registers with BIOS calls, or the user may alter its settings with the Control Panel desk accessory. Generally, palette zero (background) is left as white, and the highest numbered palette is black. EVEN-WORD VS. FRINGES A form always begins on a word boundary, and is always stored with an integral number of words per row. However, it is possible to use only a portion of the final word. This partial word is called a "fringe". If, for instance, you had a form 40 pixels wide, it would be stored with four words per row: three whole words, and one word with the eight pixel fringe in its upper byte. MFDBs Now we can intelligently define the elements of the MFDB. Its exact C structure definition will be found in the download. The fd_nplanes entry determines the color scheme: a value of one is monochrome, more than one denotes a color form. If fd_stand is zero, then the form is device-specific, otherwise it is in standard format. The fd_w and fd_h fields contain the pixel width and height of the form respectively. Fd_wdwidth is the width of a row in words. If fd_w is not exactly equal to sixteen times fd_wdwidth, then the form has a fringe. Finally, fd_addr is the 32-bit memory address of the form itself. Zero is a special value for fd_addr. It denotes that this MFDB is for the video memory itself. In this case, the VDI substitutes the actual address of the screen, and it ignores ALL of the other parameters. They are replaced with the size of the whole screen and number of planes in the current mode, and the form is (of course) in device-specific format. Page 3 This implies that any MFDB which points at the screen can only address the entire screen. This is not a problem, however, since the the VDI raster calls allow you to select a rectangular region within the form. (A note to advanced programmers: If this situation is annoying, you can retrieve the address of the ST's video area from low memory, add an appropriate offset, and substitute it into the MFDB yourself to address a portion of the screen.) LET'S OPERATE Now we can look at the VDI raster operations themselves. There are actually three: transform form, copy raster opaque, and copy raster transparent. Both copy raster functions can perform a variety of logic operatoins during the copy. TRANSFORM FORM The purpose of this operation is to change the format of a form: from standard to device-specific, or vice-versa. The calling sequence is: vr_trnfm(vdi_handle, source, dest); where source and dest are each pointers to MFDBs. They ARE allowed to be the same. Transform form checks the fd_stand flag in the source MFDB, toggles it and writes it into the destination MFDB after rewriting the form itself. Note that transform form CANNOT change the number of color planes in a form: fd_nplanes must be identical in the two MFDBs. If you are writing an application to run on the ST only, you will probably be able to avoid transform form entirely. Images and icons are stored within resources as standard forms, but since they are monochrome, they will work "as is" with the ST. If you may want to move your program or picture files to another GEM system, then you will need transform form. Screen images can be transformed to standard format and stored to disk. Another system with the same number of color planes could the read the files, and transform the image to ITS internal format with transform form. A GEM application which will be moved to other systems needs to contain code to transform the images and icons within its resource, since standard and device-specific formats will not always coincide. If you are in this situation, you will find several utilities in the download which you can use to transform G_ICON and G_IMAGE objects. There is also a routine which may be used with map_tree() from the last column in order to transform all of the images and icons in a resource tree at once. COPY RASTER OPAQUE Page 4 This operation copies all or part of the source form into the destination form. Both the source and destination forms must be in device-specific form. Copy raster opaque is for moving information between "like" forms, that is, it can copy from monochrome to monochrome, or between color forms with the same number of planes. The calling format is: vro_cpyfm(vdi_handle, mode, pxy, source, dest); As above, the source and dest parameters are pointers to MFDBs (which in turn point to the actual forms). The two MFDBs may point to memory areas which overlap. In this case, the VDI will perform the move in a non-destructive order. Mode determines how the pixel values in the source and destination areas will be combined. I will discuss it separately later on. The pxy parameter is a pointer to an eight-word integer array. This array defines the area within each form which will be affected. Pxy[0] and pxy[1] contain, respectively, the X and Y coordinates of the upper left corner of the source rectangle. These are given as positive pixel displacements from the upper left of the form. Pxy[2] and pxy[3] contain the X and Y displacements for the lower right of the source rectangle. Pxy[4] through pxy[7] contain the destination rectangle in the same format. Normally, the destination and source should be the same size. If not, the size given for the source rules, and the whole are is transferred beginning at the upper left given for the destination. This all sounds complex, but is quite simple in many cases. Consider an example where you want to move a 32 by 32 pixel area from one part of the display to another. You would need to allocate only one MFDB, with a zero in the fd_addr field. The VDI will take care of counting color planes and so on. The upper left raster coordinates of the source and destination rectangles go into pxy[0], pxy[1] and pxy[4], pxy[5] respectively. You add 32 to each of these values and insert the results in the corresponding lower right entries, then make the copy call using the same MFDB for both source and destination. The VDI takes care of any overlaps. COPY RASTER TRANSPARENT This operation is used for copying from a monochrome form to a color form. It is called transparent because it "writes through" to all of the color planes. Again, the forms need to be in device-specific form. The calling format is: vrt_cpyfm(vdi_handle, mode, pxy, source, dest, color); All of the parameters are the same as copy opaque, except that color has been added. Color is a pointer to a two word integer array. Page 5 Color[0] contains the color index which will be used when a one appears in the source form, and color[1] contains the index for use when a zero occurs. Incidentally, copy transparent is used by the AES to draw G_ICONs and G_IMAGEs onto the screen. This explains why you do not need to convert them to color forms yourself. (A note for advanced VDI programmers: The pxy parameter in both copy opaque and transparent may be given in normalized device coordinates (NDC) if the workstation associated with vdi_handle was opened for NDC work.) THE MODE PARAMETER The mode variable used in both of the copy functions is an integer with a value between zero and fifteen. It is used to select how the copy function will merge the pixel values of the source and destination forms. The complete table of functions is given in the download. Since a number of these are of obscure or questionable usefulness, I will only discuss the most commonly used modes. REPLACE MODE A mode of 3 results in a straight-forward copy: every destination pixel is replaced with the corresponding source form value. ERASE MODE A mode value of 4 will erase every destination pixel which corresponds to a one in the source form. (This mode corresponds to the "eraser" in a Paint program.) A mode value of 1 will erase every destination pixel which DOES NOT correspond to a one in the source. XOR MODE A mode value of 6 will cause the destination pixel to be toggled if the corresponding source bit is a one. This operation is invertable, that is, executing it again will reverse the effects. For this reason it is often used for "software sprites" which must be shown and then removed from the screens. There are some problems with this in color operations, though - see below. TRANSPARENT MODE Don't confuse this term with the copy transparent function itself. In this case it simply means that ONLY those destination pixels corresponding with ones in the source form will be modified by the operation. If a copy transparent is being performed, the value of color[0] is substituted for each one bit in the source form. A mode Page 6 value of 7 selects transparent mode. REVERSE TRANSPARENT MODE This is like transparent mode except that only those destination pixels corresponding to source ZEROS are modified. In a copy transparent, the value of color[1] is substituted for each zero bit. Mode 13 selects reverse transparent. THE PROBLEM OF COLOR I have discussed the various modes as if they deal with one and zero pixel values only. This is exactly true when both forms are monochrome, but is more complex when one or both are color forms. When both forms are color, indicating that a copy opaque is being performed, then the color planes are combined bit-by-bit using the rule for that mode. That is, for each corresponding source and destination pixel, the VDI extracts the top order bits and processes them, then operates on the next lower bit, and so on, stuffing each bit back into the destination form as the copy progresses. For example, an XOR operation on pixels valued 7 and 10 would result in a pixel value of 13. In the case of a copy transparent, the situation is more complex. The source form consists of one plane, and the destination form has two or more. In order to match these up, the color[] array is used. Whenever a one pixel is found, the value of color[0] is extracted and used in the bit-by-bit merge process described in the last paragraph. When a zero is found, the value of color[1] is merged into the destination form. As you can probably see, a raster copy using a mode which combines the source and destination can be quite complex when color planes are used! The situation is compounded on the ST, since the actual color values may be remapped by the palette at any time. In many cases, just using black and white in color[] may achieve the effects you desire. If need to use full color, experimentation is the best guide to what looks good on the screen and what is garish or illegible. OPTIMIZING RASTER OPERATIONS Because the VDI raster functions are extremely generalized, they are also slower than hand-coded screen drivers which you might write for your own special cases. If you want to speed up your application's raster operations without writing assembl language drivers, the following hints will help you increase the VDI's performance. AVOID MERGED COPIES These are copy modes, such as XOR, which require that words be read Page 7 from the destination form. This extra memory access increases the running time by up to fifty percent. MOVE TO CORRESPONDING PIXELS The bit position within a word of the destination rectangle should correspond with the bit position of the source rectangle's left edge. For instance, if the source's left edge is one pixel in, then the destination's edge could be at one, seventeen, thirty-three, and so. Copies which do not obey this rule force the VDI to shift each word of the form as it is moved. AVOID FRINGES Put the left edge of the source and destination rectangles on an even word boundary, and make their widths even multiples of sixteen. The VDI then does not have to load and modify partial words within the destination forms. USE ANOTHER METHOD Sometimes a raster operation is not the fastest way to accomplish your task. For instance, filling a rectangle with zeros or ones may be accomplished by using raster copy modes zero and fifteen, but it is faster to use the VDI v_bar function instead. Likewise, inverting an area on the screen may be done more quickly with v_bar by using BLACK in XOR mode. Unfortunately, v_bar cannot affect memory which is not in the video map, so these alternatives do not always work. FEEDBACK RESULTS The results of the poll on keeping or dropping the use of portability macros are in. By a slim margin, you have voted to keep them. The vote was close enough that in future columns I will try to include ST-only versions of routines which make heavy use of the macros. C purists and dedicated Atarians may then use the alternate code. THE NEXT QUESTION This time I'd like to ask you to drop by the Feedback Section and tell me whether the technical level of the columns has been: A) Too hard! Who do you think we are, anyway? B) Too easy! Don't underestimate Atarians. C) About right, on the average. If you have the time, it would also help to know a little about your background, for instance, whether you are a professional programmer, how long you have been computing, if you owned an 8-bit Atari, and so on. Page 8 COMING UP SOON The next column will deal with GEM menus: How they are constructed, how to decipher menu messages, and how to change menu entries at run-time. The following issue will contain more feedback response, and a discussion on designing user interfaces for GEM programs. Page 9 ANTIC PUBLISHING INC.,COPYRIGHT 1986. REPRINTED BY PERMISSION. PROFESSIONAL GEM By Tim Oren Column #7 - Menu Structures HAPPY NEW YEAR! This is article number seven in the ST PRO GEM series, and the first for 1986. In this installment, I will be discussing GEM menu structures and how to use them in your application. There is also a short Feedback response section. You will find the download file containing the code for this column in the file GEMCL7.C in DL3 of the ATARI16 SIG (PCS-58). MENU BASICS. In ST GEM, the menu consists of a bar across the top of the screen which displays several sub-menu titles. Touching one of the titles causes it to highlight, in your application. There is also a short Feedback response section. You will find the download file containing the code for this column in the file GEMCL7.C in DL3 of the ATARI16 SIG (PCS-58). MENU BASICS. In ST GEM, the menu consists of a bar across the top of the screen which displays several sub-menu titles. Touching one of the titles causes it to highlight, and an associated "drop-down" to be drawn directly below on the screen. This drop-down may be dismissed by moving to another title, or by clicking the mouse off of the drop-down. To make a selection, the mouse is moved over the drop-down. Each valid selection is highlighted when the mouse touches it. Clicking the mouse while over one of these selections picks that item. GEM then undraws the drop-down, and sends a message to your application giving the object number of the title bar entry, and the object number of the drop-down item which were selected by the user. The selected title entry is left highlighted while your code processes the request. MENU STRUCTURES. The data structure which defines a GEM menu is (surprise!) an object tree, just like the dialogs and panels which we have discussed before. However, the operations of the GEM menu manager are quite different from those of the form manager, so the internal design of the menu tree has some curious constraints. The best way to understand these constraints is to look at an example. The first item in the download is the object structure (only) of the menu tree from the GEM Doodle/Demo sample application. The ROOT of a menu tree is sized to fit the entire screen. To satisfy the visual hierarchy principle (see article #5), the screen is divided into two parts: THE BAR, containing the menu titles, and THE SCREEN, while contains the drop-downs when they are drawn. Each of these areas is defined by an object of the same name, which are the only two objects linked directly below the ROOT of a menu tree. You will notice an important implication of this structure: The menu titles and their associated drop-downs are stored in entirely different subtrees of the menu! While examining THE BAR in the example listing, you may notice that its OB_HEIGHT is very large (513). In hexadecimal this is 0x0201. This defines a height for THE BAR of one character plus two pixels used for spacing. THE BAR and its subtree are the only objects which are drawn on the screen in the menu's quiescent state. The only offspring object of THE BAR is THE ACTIVE. This object defines the part of THE BAR which is covered by menu titles. The screen rectangle belonging to THE ACTIVE is used by the GEM screen manager when it waits for the mouse to enter an active menu title. Notice that THE ACTIVE and its offspring also have OB_HEIGHTs with pixel residues. The actual menu titles are linked left to right in order below THE ACTIVE. Their OB_Xs and OB_WIDTHs are arranged so that they completely cover THE ACTIVE. Normally, the title objects are typed G_TITLE, a special type which assures that the title bar margins are correctly drawn. THE SCREEN is the parent object of the drop-down boxes themselves. They are linked left to right in an order identical with their titles, so that the menu manager can make the correct correspondence at run-time. The OB_X of each drop-down is set so that it is positioned below its title on the screen. Notice that it is safe to overlap the drop-downs within a menu, since only one of them will be displayed at any time. There is one constraint on the boxes however: They must be no greater than a quarter screen in total size. This is the size of the off-screen blit buffer which is used by GEM to store the screen contents when the drop-down is drawn. If you exceed this size, not all the screen under the drop-down will be restored, or the ST may crash! The entries within a drop-down are usually G_STRINGs, which are optimized for drawing speed. The rectangles of these entries must completely cover the drop-down, or the entire drop-down will be inverted when the mouse touches an uncovered area! Techniques for using objects other than G_STRINGs are discussed later in this column. The first title and its corresponding drop-down are special. The title name, by custom, is set to DESK. The drop-down must contain exactly eight G_STRING objects. The first (again by custom) is the INFO entry, which usually leads to a dialog displaying author and copyright information for your application. The next is a separator string of dashes with the DISABLED flag set. The following six objects are dummy strings which GEM fills in with the names of desk accessories when your menu is loaded. The purpose of this description of menu trees is to give you an understanding of what lies "behind the scenes" in the next section, which describes the run-time menu library calls. In practice, the Resource Construction Set provides "blank menus" which include all of the required elements, and it also enforces the constraints on internal structure. You only need to worry about these if you modify the menu tree "on-the-fly". USING THE MENU. Once you have loaded the application's resource, you can ask the AES to install your menu. You must first get the address of the menu tree within the resource using: rsrc_gaddr(R_TREE, MENUTREE, &ad_menu); assuming that MENUTREE is the name you gave the menu in the RCS, and that ad_menu is a LONG which will receive the address. Then you call the AES to establish the menu: menu_bar(ad_menu, TRUE); At this point, the AES draws your menu bar on the screen and animates it when the user moves the mouse into the title area. The AES indicates that the user has made a menu selection by sending your application a message. The message type is MN_SELECTED, which will be stored in msg[0], the first location in the message returned by evnt_multi(). The AES also stores the object number of the selected menu's title in msg[3], and the object number of the selected menu item in msg[4]. Generally, your application will process menu messages with nested C switch statements. The outer switch will have one case for each menu title, and the inner switch statements will have a case for each entry within the selected menu. (This implies that you must give a name to each title and to each menu entry when you create the menu in the RCS.) After the user has made a menu selection, the AES leaves the title of the chosen menu in reverse video to indicate that your application is busy processing the message. When you done with whatever action is indicated, you need to return the title to a normal state. This is done with menu_tnormal(ad_menu, msg[3], TRUE); (Remember that msg[3] is the title's object number.) When your application is ready to terminate, it should delete its menu bar. Do this with the call: menu_bar(ad_menu, FALSE); GETTING FANCY. The techniques above represent the bare minimum to handle menus. In most cases, however, you will want your menus to be more "intelligent" in displaying the user's options. For instance, you can prevent many user errors by disabling inappropriate choices, or you can save space on drop-downs by showing only one line for a toggle and altering its text or placing and removing a check mark when the state is changed. This section discusses these and other advanced techniques. It is a truism of user interface design that the best way to deal with an error is not to let it happen in the first place. It many cases, you can apply this principle to GEM menus by disabling choices which should not be used. If your application uses a "selection precedes action" type of interface, the type of object selected may give the information needed to do this. Alternately, the state of the underlying program may render certain menu choices illegal. GEM provides a call to disable and re-enable menu options. The call is: menu_ienable(ad_menu, ENTRY, FALSE); to disable a selection. The entry will be grayed out when it is drawn, and will not invert under the mouse and will not be selected by the user. Substituting TRUE for FALSE re-enables the option. ENTRY is the name of the object which is being affected, as assigned in the RCS. Note that menu_ienable() will not normally affect the appearance or operation of menu TITLE entries. However, there is an undocumented feature which allows this. If ENTRY is replaced by the object number of a title bar entry with its top bit set, then the entire associated drop-down will be disabled or re-enabled as requested, and the title's appearance will be changed. But, be warned that this feature did not work reliably in some early versions of GEM. Test it on your copy of ST GEM, and use it with caution when you cannot control the version under which your application may run. It is also possible to disable menu entries by directly altering the DISABLED attribute within the OB_STATE word. The routines enab_obj() and disab_obj() in the download show how this is done. They are also used in set_menu(), which follows them immediately. Set_menu() is a utility which is useful when you wish to simultaneously enable or disable many entries in the menu when the program's state changes or a new object is selected by the user. It is called with set_menu(ad_menu, vector); where vector is a pointer to an array of WORDs. The first word of the array determines the default state of menu entries. If it is TRUE, then set_menu() enables all entries in every drop-down of the menu tree, except that the DESK drop-down is unaffected. If it is FALSE, then every menu entry is disabled. The following entries in the array are the numbers of menu entries which are to be toggled to the reverse of the default state. This list is terminated by a zero entry. The advantage of set_menu() is that it allows you to build a collection of menu state arrays, and associate one with each type of user-selected object, program state, and so on. Changing the status of the menu tree may then be accomplished with a single call. CHECK, PLEASE? One type of state indicator which may appear within a drop-down is a checkmark next to an entry. You can add the checkmark with the call: menu_icheck(ad_menu, ENTRY, TRUE); and remove it by replacing the TRUE with FALSE. As above, ENTRY is the name of the menu entry of interest. The checkmark appears inside the left boundary of the entry object, so leave some space for it. The menu_icheck() call is actually changing the state of the CHECKED flag within the entry object's OB_STATE word. If necessary, you may alter the flag directly using do_obj() and undo_obj() from the download. NOW YOU SEE IT, NOW YOU DON'T. You can also alter the text which appears in a particular menu entry (assuming that the entry is a G_STRING object). The call menu_text(ad_menu, ENTRY, ADDR(text)); will substitute the null-terminated string pointed to by text for whatever is currently in ENTRY. Remember to make the drop-down wide enough to handle the largest text string which you may substitute. In the interests of speed, G_STRINGs drawn within drop-downs are not clipped, so you may get garbage characters on the desktop if you do not size the drop-down properly! The menu_text() call actually alters the OB_SPEC field of the menu entry object to point to the string which you specify. Since the menu tree is a static data structure which may be directly accessed by the AES at any time, be sure that the string is also statically allocated and that it is not modified without first being delinked from the menu tree. Failure to do this may result in random crashes when the user accesses the drop-down! LUNCH AND DINNER MENUS. Some applications may have such a wide range of operations that they need more than one menu bar at different times. There is no problem with having more than one menu tree in a resource, but the AES can only keep track of one at a time. Therefore, to switch menus you need to use menu_bar(ad_menu1, FALSE); to release the first menu, then use menu_bar(ad_menu2, TRUE); to load the second menu tree. Changing the entire menu is a drastic action. Out of consideration for your user, it should be associated with some equally obvious change in the application which has just been manually requested. An example might be changing from spreadsheet to data graphing mode in a multi-function program. DO IT YOURSELF. In a future column, I will discuss how to set up user-defined drawing objects. If you have already discovered them on your own, you can use them within a drop-down or as a title entry. If the user-defined object is within a drop-down, its associated drawing code will be called once when the drop-down is first drawn. It will then be called in "state-change" mode when the entry is highlighted (inverted). This allows you to use non-standard methods to show selection, such as outlines. If you try to insert a user-defined object within the menu title area, remember that the G_TITLE object which you are replacing includes part of the dark margin of the bar. You will need to experiment with your object drawing code to replicate this effect. MAKE PRETTY. There are a number of menu formatting conventions which have become standard practice. Using these gives your application a recognizable "look-and-feel" and helps users learn it. The following section reviews these conventions, and supplies a few hints and tricks to obtain a better appearance for you menus. The second drop-down is customarily used as the FILE menu. It contains options related to loading and saving the files used by the application, as well as entries for clearing the workspace and terminating the program. You should avoid crowding the menu bar. Leave a couple of spaces between each entry, and try not to use more than 70% of the bar. Not only does this look better, but you will have space for longer words if you translate your application to a foreign language. Similarly, avoid cluttering menu drop-downs. Try to keep the number of options to no more than ten unless they are clearly related, such as colors. Separate off dissimilar entries with the standard disabled dashes line. (If you are using set_menu(), remember to consider the separators when setting up the state vectors.) If the number of options grows beyond this bound, it may be time to move them to a dialog box. If so, it is a convention to put three dots following each menu entry which leads to a dialog. Also, allow a margin on the menu entries. Two leading blanks and a minimum of one trailing blank is standard, and allows room for checkmarks if they are used. Dangerous menu options should be far away from common used entries, and are best separated with dashed lines. Such options should either lead to a confirming go/no-go alert, or should have associated "undo" options. After you have finished defining a menu drop-down with the RCS, be sure that its entries cover the entire box. Then use ctrl-click to select the drop-down itself, and SORT the entries top to bottom. This way the drop-down draws in smoothly top to bottom. Finally, it is possible to put entries other than G_STRINGs into drop-downs. In the RCS, you will need to import them via the clipboard from the Dialog mode. Some non-string object, such as icons and images, will look odd when they are inverted under the mouse. There is a standard trick for dealing with this problem. Insert the icon or whatever in the drop-down first. Then get a G_IBOX object and position and size it so that it covers the first object as well as the extra area you would like to be inverted. Edit the G_IBOX to remove its border, and assign the entry name to it. Since the menu manager uses objc_find(), it will detect and invert this second object when the mouse moves into the drop-down. (To see why, refer to article #5.) Finally, DO NOT SORT a drop-down which has been set up this way! THAT'S IT FOR NOW! The next column will discuss some of the principles of designing GEM interfaces for applications. This topic is irreverantly known as GEM mythology or interface religion. The subject for the following column is undecided. I am considering mouse and keyboard messages, VDI drawing primitives, and the file selector as topics. Let me know your preferences in the Feedback! ANTIC PUBLISHING INC.,COPYRIGHT 1986. REPRINTED BY PERMISSION. PROFESSIONAL GEM By Tim Oren Column #8: USER INTERFACES: HOMILY #1 AND NOW FOR SOMETHING COMPLETELY DIFFERENT! In response to a number of requests, this installment of ST PRO GEM will be devoted to examining a few of the principles of computer/human interface design, or "religion" as some would have it. I'm going to start with basic ergonomic laws, and try to draw some conclusions which are fairly specific to designing for the ST. If this article meets with general approval, further "homilies" may appear at irregular intervals as part of the ST PRO GEM series. For those who did NOT ask for this topic, it seems fair to explain why your diet of hard-core technical information has been interrupted by a sermon! As a motivater, we might consider why some programs are said by reviewers to have a "hot" feel (and hence sell well!) while others are "confusing" or "boring". Alan Kay has said that "user interface is theatre". I think we may be able to take it further, and suggest that a successful program works a bit of magic, persuading the user to suspend his disbelief and enter an imaginary world behind the screen, whether it is the mathematical world of a spreadsheet, or the land of Pacman pursued by ghosts. A reader of a novel or science fiction story also suspends disbelief to participate in the work. Bad grammar and clumsy plotting by the author are jarring, and break down the illusion. Similarly, a programmer who fails to pay attention to making his interface fast and consistent will annoy the user, and distract him from whatever care has been lavished on the functional core of the program. CREDIT WHERE IT'S DUE. Before launching into the discussion of user interface, I should mention that the general treatment and many of the specific research results are drawn from Card, Newell, and Moran's landmark book on the topic, which is cited at the end of the article. Any errors in interpretation and application to GEM and the ST are entirely my own, however. FINGERTIPS. We'll start right at the user's fingers with the basic equation governing positioning of the mouse, Fitt's Law, which is given as T = I * LOG2( D / S + .5) where T is the amount of time to move to a target, D is the distance of the target from the current position, and S is the size of the target, stated in equivalent units. LOG2 is the base 2 (binary) logarithm function, and I is a proportionality constant, about 100 milliseconds per bit, which corresponds to the human's "clock rate" for making incremental movements. We can squeeze an amazing amount of information out of this formula when attempting to speed up an interface. Since motion time goes up with distance, we should arrange the screen with the usual working area near the center, so the mouse will have to move a smaller distance on average from a selected object to a menu or panel. Likewise, any items which are usually used together should be placed together. The most common operations will have the greater impact on speed, so they should be closest to the working area and perhaps larger than other icons or menu entries. If you want to have all other operations take about the same time, then the targets farthest from the working area should be larger, and those closer may be proportionately smaller. Consider also the implications for dialogs. Small check boxes are out. Large buttons which are easy to hit are in. There should be ample space between selectable items to allow for positioning error. Dangerous options should be widely separated from common selections. MUSCLES. Anyone who has used the ST Desktop for any period of time has probably noticed that his fingers now know where to find the File menu. This phenomenon is sometimes called "muscle memory", and its rate of onset is given by the Power Law of Practice: T(n) = T(1) * n ** (-a) where T(n) is the time on the nth trial, T(1) is the time on the first trial, and a is approximately 0.4. (I have appropriated ** from Fortran as an exponentiation operator, since C lacks one.) This first thing to note about the Power Law is that it only works if a target stays in the same place! This should be a potent argument against rearranging icons, menus, or dialogs without some explicit request by the user. The time to hit a target which moves around arbitrarily will always be T(1)! In many cases, the Power Law will also work for sequences of operations to even greater effect. If you are a touch typist, you can observe this effect by comparing how fast you can enter "the" in comparison to three random letters. We'll come back shortly to consider what we can do to encourage this phenomenon. EYES. Just as fingers are the way the user sends data to the computer, so the eyes are his channel from the machine. The rate at which information may be passed to the user is determined by the "cycle time" of his visual processor. Experimental results show that this time ranges between 50 and 200 milliseconds. Events separated by 50 milliseconds or less are always perceived as a single event. Those separated by more than 200 milliseconds are always seen as separate. We can use these facts in optimizing user of the computer's power when driving the interface. Suppose your application's interface contains an icon which should be inverted when the mouse passes over it. We now know that flipping it within one twentieth of a second is necessary and sufficient. Therefore, if a "first cut" at the program achieves this performance, there is no need for further optimization, unless you want to interleave other operations. If it falls short, it will be necessary to do some assembly coding to achieve a smooth feel. On the other hand, two actions which you want to appear distinct or convey two different pieces of information must be separated by an absolute minimum of a fifth of a second, even assuming that they occur in an identical location on which the user's attention is already focused. We are able to influence the visual processing rate within the 50 to 200 millisecond range by changing the intensity of the stimulus presented. This can be done with color, by flashing a target, or by more subtle enhancements such as bold face type. For instance, most people using GEM soon become accustomed to the "paper white" background of most windows and dialogs. A dialog which uses a reverse color scheme, white letters on black, is visually shocking in its starkness, and will immediately draw the user's eyes. It should be quickly added that stimulus enhancement will only work when it unambiguously draws attention to the target. Three or four blinking objects scattered around the screen are confusing, and worse than no enhancement at all! SHORT-TERM MEMORY. Both the information gathered by the eyes and movement commands on their way to the hand pass through short-term memory (also called working memory). The amount of information which can be held in short-term memory at any one time is limited. You can demonstrate this limit on yourself by attempting to type a sheet of random numbers by looking back and forth from the numbers to the screen. If you are like most people, you will be able to remember between five and nine numbers at a time. So universal is this finding that it is sometimes called "the magic number seven, plus or minus two". This short-term capacity sets a limit on the number of choices which the user can be expected to grasp at once. It suggests that the number of independent choices in a menu, for instance, should be around seven, and never exceed nine. If this limit is violated, then the user will have to take several glances, with pauses to think, in order to make a choice. CHUNKING. The effective capacity of short-term memory can be increased when several related items are mentally grouped as a "chunk". Humans automatically adopt this strategy to save themselves time. For instance, random numbers had to be used instead of text in the example above, because people do not type their native language as individual characters. Instead, they combine the letters into words and remember these chunks instead. Put another way, the characters are no longer considered as individual choices. A well designed interface should promote the use of chunking as a strategy by the user. One easy way is to gather together related options in a single place. This is one reason that like commands are grouped into a single menu which is hidden except for its title. If all of the menu options were "in the open", the user would be overwhelmed with dozens of alternatives at once. Instead, a "Show Info" command, for instance, becomes two chunks: pick File menu, then pick Show. Sometimes the interface can accomplish the chunking for the user. Consider the difference between a slider bar in a GEM program, and a three digit entry field in a text mode application. Obviously, the GEM user has fewer decisions to make in order to set the associated variable. THINK! While we are puttering around trying to speed up the keyboard, the mouse, and the screen, the user is actually trying to get some work done. We need to back off now, and look at the ways of thinking, or cognitive processes, that go into accomplishing the job. The user's goal may be to enter and edit a letter, to retrieve information from a database, or simply draw a picture, but it probably has very little to do with programming. In fact, the Problem Space Principle says that the task can be described as a set of states of knowledge, a set of operators and associated constraints for changing the states, and the knowledge to choose the appropriate operator, which resides in the user's head. Those with a background in systems theory can consider this as a somewhat abstract, but straightforward, statement in terms of state variables and operators. A programmer might compare the knowledge states to the values of variables, the operators to arithmetic and logic operations, the constraints to the rules of syntax, and the user's knowledge to the algorithm embodied by a program. ARE WE NOT MEN? A rational person will try to attain his goals (get the job done) by changing the state of his problem space from its initial state to the goal state. The initial state, for instance, might be a blank word processor screen. The desired final state is to have a completed business letter on the screen. The Rationality Principle says that the user's behavior in typing, mousing, and so on, can be explained by considering the tasks required to achieve the goal, the operators available to carry out the tasks, and the limitations on the user's knowledge, observations, and processing capacity. This sounds like the typical user of a computer program must spend a good deal of time scratching his head and wondering what to do next. In fact, one of Card and Moran's key results is that this is NOT what takes place. What happens, in fact, is that the trained user strikes a sort of "modus vivendi" with his tool and adopts a set of repetitive, trained behavior patterns as the best way to get the job done. He may go so far as to ignore some functions of the program in order to set up a reliable pattern. What we are looking for is a way of measuring and predicting the "quality" of this trained behavior. Since using computers is a human endeavor, we should consider not only the speed with which the task is completed, but the degree of annoyance or pleasure associated with the process. Card and Moran constructed a series of behavioral models which they called GOMS models, for Goals-Operators-Methods-Selection. These models suggested that in the training process the user learned to combine the basic operators in sequences (chunks!) which then became methods for reaching the goals. Then these first level methods might be combined again into second level methods, and so forth, as the learning progressed. The GOMS models were tested in a lengthy series of trials at Xerox PARC using a variety of word processing software. (Among the subjects of these experiments were the inventors of the windowing methods used in GEM!) The results were again surprising: the level of detail in the models was really unimportant! It turned out to be sufficient to merely count up the number of keystrokes, mouse movements, and thought intervals required by each task. After summing up all of the tasks, any extra time for the computer to respond, or the user to move his hands from keyboard to mouse, or eyes from screen to printed page is added in. This simplified version is called the Keystroke-Level Model. As an example of the Keystroke Model, consider the task of changing a mistyped letter on the screen of a GEM word processor. This might be broken down as follows: 1) find the letter on the screen; 2) move hand to mouse; 3) point to letter; 4) click mouse button; 5) move hand to keyboard; 6) strike "Delete" key; 7) strike key for new character. The sufficiency of the Keystroke Model is great news for our attempt to design faster interfaces. It says we can concentrate our efforts on minimizing the number of total actions to be taken, and making sure that each action is as fast as possible. We have already discussed some ways to speed up the mouse and keyboard actions, so let's now consider how to speed up the thought intervals, and cut the number of actions. One way to cut down "think time" is to make sure that the capacity of short-term memory is not exceeded during the course of a task. For example, the fix-a-letter task described above required the user to remember 1) his place in the overall job of typing the document; 2) the task he is about to perform; 3) where the bad character appeared, and 4) what the new character was. When this total of items creeps toward seven, the user often loses his place and commits errors. You can appreciate the ubiquity of this problem by considering how many times you have made mistakes nesting parentheses, or had to go back to count them, because too many things happened while typing the line to remember the nesting levels. The moral is that operations with long strings of operands should be avoided when designing an interface. The single most important factor in making an interface comfortable to use is increasing its predictability, and decreasing the amount of indecision present at each step during a task. There is (inevitably) an Uncertainty Principle which relates the number of choices at each step to the associated time for thought: T = I * LOG2 ( N + 1) where LOG2 is the binary logarithm function, N is the number of equally probable choices, and I is a constant of approximately 140 msec/bit. When the alternates are not equally probable, the function is more complex: T = I * SUM-FOR-i-FROM-1-TO-N (P(i) * LOG2( 1 / P(i) + 1) ) where the P(i) are the probabilities of each of the choices (which must sum to one). (SUM-FOR-i... is the best I can do for a sigma operator on-line!) Those of you with some information theory background will recognize this formula as the entropy of the decision; we'll come back to that later. So what can we learn from this hash? It turns out, as we might expect, that we can decrease the decision time by making some of the user's choices more probable than others. We do that by means of feedback cues from the interface. The important of reliable, continuous meaningful feedback cannot be emphasized enough. It helps the beginner learn the system, and its predictability makes the program comfortable for the expert. Programs with no feedback, or unreliable cues, produce confusion, dissonance, and frustration in the user. This principle is so important that I going to give several examples from common GEM practice. The Desktop provides several instances. When an object is selected and a menu drops down, only those choices which are legal for the object are in black. The others are dimmed to grey, and are therefore removed from the decision. When a pick is made from the menu, the bar entry remains black until the operation is complete, reassuring the user that the correct choice was made. In both the Desktop and the RCS, items which are double-clicked open up with a "zoom box" from the object, again showing that the right object was picked. Other techniques are useful when operator icons are exposed on the screen. When an object is picked, the legal operations might be outlined, or the bad choices might be dimmed. If the screen flashing produced by this is objectionable, the legal icons can be made mouse sensitive, so they will "light up" when the cursor passes over - again showing the user which choices are legal. The desire for feedback is so strong that it should be provided even while the computer is doing an operation on its own. The hour glass mouse form is a primitive example of this. More sophisticated are "progress indicators" such as animated thermometer bars, clocks, or text displays of the processing steps. The ST Desktop provides examples in the Format and Disk Copy functions. The purpose of all of these is to reassure the user that the operation is progressing normally. Their lack can lead to amusing spectacles such as secretaries leaning over to hear if their disk drives are working! Another commonly overlooked feature is error prevention and correction. Card and Moran's results showed that in order to go faster, people will tolerate error rates of up to 30% in their work. Any program which does not give a fast way to fix mistakes will be frustrating indeed! The best way to cope with an error is to "make it didn't happen", to quote a common child's phrase. The same feedback methods discussed above are also effective in preventing the user from picking inappropriate combinations of objects and operations. Replacement of numeric type-ins with sliders or other visual controls eliminates the common "Range Error". The use of radio buttons prevents the user from picking incompatible options. When such techniques are used consistently, the beginner also gains confidence that he may explore the program without blundering into errors. Once an error has occured, the best solution is to have an "inverse operation" immediately available. For instance, the way to fix a bad character is to hit the backspace key. If a line is inadvertantly deleted, there should be a way to restore it. Sometimes the mechanics of providing true inverses are impractical, or end up cluttering the interface themselves. In these cases, a global "Undo" command should be provided to reverse the effect of the last operation, no matter what it was. OF MODES AND BANDWIDTH. Now I am going to depart from the Card, Newell and Moran thread of discussion to consider how we can minimize the number of operations in a task by altering the modes of the interface. Although "no modes" has been a watchword of Macintosh developers, the term may need definition for Atarians. Simply stated, a mode exists any time you cannot get to all of the capabilities of the program without taking some intermediate step. Familiar examples are old-style "menu-driven" programs, in which user must make selections from a number of nested menus in order to perform any operation. The options of any one menu are unavailable from the others. Recall that the user is trying to accomplish work in his own problem space, by altering its states. A mode in the program adds additional states to the problem space, which he is forced to consider in order to get the job done. We might call an interface which is completely modeless "transparent", because it adds no states between the user and his work. One of the best examples of a transparent program is the 15-puzzle in the Macintosh desk accessory set. The problem space of rearranging the tiles is identical between the program and a physical puzzle. Unfortunately, most programmers find themselves forced to put modes of some sort into their programs. These often arise due to technological limitations, such as memory space, screen "real estate", or performance limitations of peripherals. The question is how the modes can be made least offensive. I will make the general claim that the frustration which a mode produces is directly proportional to the amount of the user's bandwidth which it consumes. In other words, we need to consider how many keystrokes, mouse clicks, eye movements, and so on, are going into manipulating the true problem states, and how many are being absorbed by the modes of the program. If the interface is wasting a large amount of the user's effort, it will be perceived as slow and annoying. Here we can consider again the hierarchy of goals and methods which the user employs. When the mode is low in the hierarchy, and close to the user's "fingertips", it is encountered the most frequently. For instance, consider how frustrating it would be to have to hit a function key before typing in each character! The "menu-driven" style of programs mentioned above are almost as bad, since usually only one piece of information is collected at each menu. Such a program becomes a labyrinth of states better suited to an adventure game! The least offensive modes are found at the higher, goal related levels of the hierarchy. The better they align with changes in the state of the original problem, the more they are tolerated. For example, a word processing program might have one screen layout for program editing, another for writing letters, and yet another while printing the documents. A multi-function business package might have one set of menus for the spreadsheet, another for a graphing module, and a third for a database. In some cases the problem solved by the program has convenient "fracture lines" which can be used to define the modes. An example in my own past is the RCS, where the editing of each type of resource tree forms its own mode, with each of the modes nested within the overall mode and problem of composing the entire resource tree. TO DO IS TO BE! Any narrative description of user interface is bound to be lacking. There is no way text can convey the vibrancy and tactile pleasure of a good interface, or the sullen boredom of a bad one. Therefore, I encourage you to experiment. Get out your favorite arcade game and see if you can spot some of the elements I have described. Dig into your slush pile for the most annoying program you have ever seen, run it and see if you can see mistakes. How would you fix them? Then... go do it to your own program! AMEN... This concludes the sermon. I'd like some Feedback as to whether you found this Boring Beyond Belief or Really Hot Stuff. If enough people are interested, homily number two will appear a few episodes from now. The very next installment of ST PRO GEM will go back to basics to explore VDI drawing primitives. In the meantime, you might investigate some of the Good Books on interface design referenced below. REFERENCES. Stuart K. Card, Thomas P. Moran, and Allen Newell, THE PSYCHOLOGY OF HUMAN-COMPUTER INTERACTION, Lawrence Erlbaum Associates, Hillsdale, New Jersey, 1983. (Fundamental and indispensible. The volume of experimental results make it weighty. The Good Parts are at the beginning and end.) "Macintosh User Interface Guidelines", in INSIDE MACINTOSH, Apple Computer, Inc., 1984. (Yes, Atarians, we have something to learn here. Though not everything "translates", this is a fine piece of principled design work. Read and appreciate.) James D. Foley, Victor L. Wallace, and Peggy Chan, "The Human Factors of Computer Graphics Interaction Techniques", IEEE Computer Graphics (CG & A), November 1984, pp. 13-48. (A good overview, including higher level topics which I have postponed to a later article. Excellent bibliography.) J. D. Foley and A. Van Dam, FUNDAMENTALS OF INTERACTIVE COMPUTER GRAPHICS, Addison Wesley, 1984, Chapters 5 and 6. (If you can't get the article above, read this. If you are designing graphics apps, buy the whole book! Staggering bibliography.) Ben Schneidermann, "Direct Manipulation: A Step Beyond Programming Languages", IEEE Computer, August 1983, pp. 57-69. (What do Pacman and Visicalc have in common? Schneidermann's analysis is vital to creating hot interfaces.) ANTIC PUBLISHING INC.,COPYRIGHT 1986. REPRINTED BY PERMISSION. PROFESSIONAL GEM By Tim Oren Column #9 - VDI Graphics: Lines and Solids This issue of ST PRO GEM is the first in a series of two which will explore the fundamentals of VDI graphics output. In this installment, we will take a look at the commands necessary to output simple graphics such as lines, squares and circles as well as more complex figures such as polygons. The following episode will take a first look at graphics text output, with an emphasis on ways to optimize its drawing speed. It will also include another installment of ONLINE Feedback. As usual, there is a download with this column. You should find it under the name GEMCL9.C in DL3 of ATARI16 (PCS-58). A BIT OF HISTORY One of the reasons that the VDI can be confusing is that drawing anything at all, even a simple line, can involve setting around four different VDI parameters before making the draw call! (Given the state of the GEM documents, just FINDING them can be fun!) Looking backwards a bit sheds some light on why the VDI is structured this way, and also gives us a framework for organizing a discussion of graphics output. The GEM VDI closely follows the so-called GKS standard, which defines capabilities and calling sequences for a standardized graphic input/output system. GKS is itself an evolution from an early system called "Core". Both of these standards were born in the days when pen plotters, vectored graphics displays, and minicomputers were the latest items. So, if you wonder why setting the drawing pen color is a separate command, just think back a few years when it actually meant what it says! (The cynical may choose instead to ponder the benefits of standardization.) When doing VDI output, it helps if you pretend that the display screen really is a plotter or some other separate device, which has its own internal parameters which you can set up and read back. The class of VDI commands called Attribute Functions let you set the parameters. Output Functions cause the "device" to actually draw someone once it is configured. The Inquire Functions let you read back the parameters if necessary. There are two parameters which are relevant no matter what type of object you are trying to draw. They are the writing mode and the clipping rectangle. The writing mode is similar to that discussed in the column on raster operations. It determines what effect the figure you are drawing will have on data already on the screen. The writing mode is set with the call: vswr_mode(vdi_handle, mode); (Vdi_handle, here and below, is the handle obtained from graf_handle at the beginning of the program. Mode is a word which may be one of: 1 - Replace Mode 2 - Transparent Mode 3 - XOR mode 4 - Reverse Transparent Mode In replace mode, whatever is on the screen is overwritten. If you are writing characters, this means the background of each character cell will be erased. In transparent mode, only the pixels directly under the "positive" part of the image, that is, where 1-bits are being written, will be changed. When writing characters, the background of the cell will be left intact. In XOR mode, an exclusive or is performed between the screen contents and what is being written. The effect is to reverse the image under areas where a 1-bit occurs. Reverse transparent is like transparent, but with a "reverse color scheme". That is, only places where a 0-bit is to be put are changed to the current writing color. When you write characters in reverse transparent (over white), the effect is reverse video. The other common parameter is the clipping rectangle. It defines the area on the screen where the VDI is permitted to draw. Any output which would fall outside of this area is ignored; it is effectively a null operation. The clip rectangle is set with the call: vs_clip(vdi_handle, flag, pxy); Pxy is a four-word array. Pxy[0] and pxy[1] are the X and Y screen coordinates, respectively, of one corner of your clipping rectangle. Pxy[2] and pxy[3] are the coordinates of the diagonally opposite corner of the rectangle. (When working with the AES, use of a GRECT to define the clip is often more convenient. The routine set_clip() in the download does this.) Flag is set to TRUE if clipping is to be used. If you set it to FALSE, the entire screen is assumed to be fair game. Normally, you should walk the rectangle list for the current window to obtain your clipping rectangles. (See ST PRO GEM #2 for more details.) However, turning off the clip speeds up all output operations, particularly text. You may do this ONLY when you are absolutely certain that the figure you are drawing will not extend out of the top-most window, or out of a dialog. THE LINE FORMS ON THE LEFT The VDI line drawing operations include polyline, arc, elliptical arc, and rounded rectangle. I'll first look at the Attribute Functions for line drawing, then go through the drawing primitives themselves. The most common used line attributes are color and width. The color is set with: vsl_color(vdi_handle, color); where color is one of the standard VDI color indices, ranging from zero to 15. (As discussed in column #6, the color which actually appears will depend on the pallette setting of your ST.) The line width may only be set to ODD positive values, for reasons of symmetry. If you try to set an even value, the VDI will take the next lower odd value. The call is: vsl_width(vdi_handle, width); The two less used line parameters are the end style and pattern. With the end style you can cause the output line to have rounded ends or arrowhead ends. The call is: vsl_ends(vdi_handle, begin_style, end_style); Begin_style and end_style are each words which may have the values zero for square ends (the default), one for arrowed ends, or two for rounded ends. They determine the styles for the starting and finishing ends of the line, respectively. The line pattern attribute can select dotted or dashed lines as well as more complicated patterns. Before continuing, you should note one warning: VDI line output DOES NOT compensate for pixel aspect ratio. That is, the dashes on a line will look twice as long drawn vertically on a medium-res ST screen as they do when drawn horizontally. The command for setting the pattern is: vsl_type(vdi_handle, style); Style is a word with a value between 1 and 7. The styles selected are: 1 - Solid (the default) 2 - Long Dash 3 - Dot 4 - Dash, Dot 5 - Dash 6 - Dash, Dot, Dot 7 - (User defined style) The user defined style is determined by a 16-bit pattern supplied by the application. A one bit in the pattern turns a pixel on, a zero bit leaves it off. The pattern is cycled through repeatedly, using the high bit first. To use a custom style, you must make the call: vsl_udsty(vdi_handle, pattern); before doing vsl_type(). As I mentioned above, the line type Output Functions available are polyline, circular and ellliptical arc, and rounded rectangle. Each has its own calling sequence. The call for a polyline is: v_pline(vdi_handle, points, pxy); Points tells how many vertices will appear on the polyline. For instance, a straight line has two vertices: the end and the beginning. A closed square would have five, with the first and last identical. (There is no requirement that the figure described be closed.) The pxy array contains the X and Y raster coordinates for the vertices, with a total of 2 * points entries. Pxy[0] and pxy[1] are the first X-Y pair, and so on. If you happen to be using the XOR drawing mode, remember that drawing twice at a point is equivalent to no drawing at all. Therefore, for a figure to appear closed in XOR mode, the final stroke should actually stop one pixel short of the origin of the figure. You may notice that in the GEM VDI manual the rounded rectangle and arc commands are referred to as GDPs (Generalized Drawing Primitives). This denotation is historical in nature, and has no effect unless you are writing your own VDI bindings. The rounded rectangle is nice to use for customized buttons in windows and dialogs. It gives a "softer" look to the screen than the standard square objects. The drawing command is: v_rbox(vdi_handle, pxy); Pxy is a four word array giving opposite corners of the rectangle, just as for the vs_clip() call. The corner rounding occurs within the confines of this rectangle. Nothing will protrude unless you specify a line thickness greater than one. The corner rounding is approximately circular; there is no user control over the degree or shape of rounding. Both the arc and elliptical arc commands use a curious method of specifying angles. The units are tenths of degrees, so an entire circle is 3600 units. The count starts at ninety degrees right of vertical, and proceeds counterclockwise. This means that "3 o'clock" is 0 units, "noon" is 900 units, "9 o'clock" is 1800 units, and 2700 units is at "half-past". 3600 units take you back to "3 o'clock". The command for drawing a circular arc is: v_arc(vdi_handle, x, y, radius, begin, end); X and y specify the raster coordinates of the center of the circle. Radius specifies the distance from center to all points on the arc. Begin and end are angles given in units as described above, both with values between 0 and 3600. The drawing of the arc ALWAYS proceeds counterclockwise, in the direction of increasing arc number. So values of 0 and 900 for begin and end would draw a quarter circle from "three o'clock" to "noon". Reversing the values would draw the other three quarters of the circle. A v_arc() command which specifies a "full turn" is the fastest way to draw a complete circle on the screen. Be warned, however, that the circle drawing algorithm used in the VDI seems to have some serious shortcomings at small radii! You can experiment with the CIRCLE primitive in ST Logo, which uses v_arc(), to see what I mean. Notice that if you want an arc to strike one or more given points on the screen, then you are in for some trigonometry. If your math is a bit rusty, I highly recommend the book "A Programmer's Geometry", by Bowyer and Woodwark, published by Butterworths (London, Boston, Toronto). Finally, the elliptical arc is generated with: v_ellarc(vdi_handle, x, y, xrad, yrad, begin, end); X, y, begin, and end are just as before. Xrad and yrad give the horizontal and vertical radii of the defining ellipse. This means that the distance of the arc from center will be yrad pixels at "noon" and "half-past", and it will be xrad pixels at "3 and 9 o'clock". Again, the arc is always drawn counterclockwise. There are a number of approaches to keeping the VDI's attributes "in sync" with the actual output operations. Probably the LEAST efficient is to use the Inquire Functions to determine the current attributes. For this reason, I have omitted a discussion of these calls from this column. Another idea is to keep a local copy of all significant attributes, use a test-before-set method to minimize the number of Attribute Functions which need to be called. This puts a burden on the programmer to be sure that the local attribute variables are correctly maintained. Failure to do so may result in obscure drawing bugs. If your application employs user defined AES objects, you must be very careful because GEM might call your draw code in the middle of a VDI operation (particularly if the user defined objects are in the menu). Always setting the attributes is a simplistic method, but often proves most effective. The routines pl_perim() and rr_perim() in the download exhibit this approach. Modification for other primitives is straightforward. This style is most useful when drawing operations are scattered throughout the program, so that keeping track of the current attribute status is difficult. Although inherently inefficient, the difference is not very noticable if the drawing operation requested is itself time consuming. In many applications, such as data graphing programs or "Draw" packages, the output operations are centralized, forming the primary functionality of the code. In this case, it is both easy and efficient to keep track of attribute status between successive drawing operations. SOLIDS There are a wider variety of VDI calls for drawing solid figures. They include rectangle or bar, disk, pie, ellipse, elliptical pie, filled rounded rectangle, and filled polygonal area. Of course, filled figure calls also have their own set of attributes which you will need to set. The fill color index determines what pen color will be used to draw the solid. It is set with: vsf_color(vdi_handle, color); Color is just the same as for line drawing. A solid may or may not have a visible border. This is determined with the call: vsf_perimeter(vdi_handle, vis); Vis is a Boolean. If it is true, the figure will be given a solid one pixel outline in the current fill color index. This is often useful to improve the appearance of solids drawn with a dithered fill pattern. If vis is false, then no outline is drawn. There are two parameters which together determine the pattern used to fill your figure. They are called interior style and interior index. The style determines the general type of fill, and the index is used to select a particular pattern if necessary. The style is set with the command: vsf_interior(vdi_handle, style); where style is a value from zero through four. Zero selects a hollow style: the fill is performed in color zero, which is usually white. Style one selects a solid fill with the current fill color. A style of two is called "pattern" and a three is called "hatch", which are terms somewhat suggestive of the options which can then be selected using the interior index. Style four selects the user defined pattern, which is described below. The interior index is only significant for styles two and three. To set it, use: vsf_style(vdi_handle, index); (Be careful here: it is very easy to confuse this call with the one above due to the unfortunate choice of name.) The index selects the actual drawing pattern. The GEM VDI manual shows fill patterns corresponding to index values from 1 to 24 under style 2, and from 1 to 12 under style 3. However, some of these are implemented differently on the ST. Rather than try to describe them all here, I would suggest that you experiment. You can do so easily in ST Logo by opening the Graphics Settings dialog and playing with the style and index values there. The user defined style gives you some interesting options for multi-color fills. It is set with: vsf_udpat(vdi_handle, pattern, planes); Planes determines the number of color planes in the pattern which you supply. It is set to one if you are setting a monochrome pattern. (Remember, monochrome is not necessarily black). It may be set to higher values on color systems: two for ST medium-res mode, or four for low-res mode. If you use a number lower than four under low-res, the other planes are zero filled. The pattern parameter is an array of words which is a multiple of 16 words long. The pattern determined is 16 by 16 pixels, with each word forming one row of the pattern. The rows are arranged top to bottom, with the most significant bit to the left. If you have selected a multi-plane pattern, the entire first plane is stored, then the second, and so on. Note that to use a multi-plane pattern, you set the writing mode to replace using vswr_mode(). Since the each plane can be different, you can produce multi-colored patterns. If you use a writing color other than black, some of the planes may "disappear". Most of the solids Output Functions have analogous line drawing commands. The polyline command corresponds to the filled area primitive. The filled area routine is: v_fillarea(vdi_handle, count, pxy); Count and pxy are just the same as for v_pline(). If the polygon defined by pxy is not closed, then the VDI will force closure with a straight line from the last to the first point. The polygon may be concave or self-intersecting. If perimeter show is on, the area will be outlined. One note of caution is necessary for both v_fillarea() and v_pline(). There is a limit on the number of points which may be stored in pxy[]. This limit occurs because the contents of pxy[] are copied to the intin[] binding array before the VDI is called. You can determine the maximum number of vertices by checking intout[14] after using the extended inquire function vq_extnd(). For reasons unknown to this writer, there are TWO different filled rectangle commands in the VDI. The first is vr_recfl(vdi_handle, pxy); Pxy is a four word array defining two opposite corners of the rectangle, just as in vs_clip(). Vr_recfl() uses the fill attribute settings, except that it NEVER draws a perimeter. The other rectangle routine is v_bar(), with exactly the same arguments as vr_recfl(). The only difference is that the perimeter setting IS respected. These two routines are the fastest way to produce a solid rectangle using the VDI. They may be used in XOR mode with a BLACK fill color to quickly invert an area of the screen. You can improve the speed even further by turning off the clip (if possible), and byte aligning the left and right edges of the rectangle. Separate commands are provided for solid circle and ellipse. The circle call is: v_circle(vdi_handle, x, y, radius); and the ellipse command is: v_ellipse(vdi_handle, x, y, xrad, yrad); All of the parameters are identical to those given above for v_arc() and v_ellarc(). The solid analogue of an arc is a "pie slice". The VDI pie commands are: v_pieslice(vdi_handle, x, y, radius, begin, end); for a slice from a circular pie, and v_ellpie(vdi_handle, x, y, xrad, yrad, begin, end); for a slice from a "squashed" pie. Again, the parameters are identical to those in v_arc() and v_ellarc(). The units and drawing order of angles are also the same. The final solids Output Function is: v_rfbox(vdi_handle, pxy); which draws a filled rounded rectangle. The pxy array defines two two opposite corners of the bounding box, as shown for vs_clip(). The issues involved in correctly setting the VDI attributes for a fill operation are identical to those in drawing lines. For those who want to employ the "always set" method, I have again included two skeleton routines in the download, which can be modified as desired. TO BE CONTINUED This concludes the first part of our expedition through basic VDI operations. The next issue will tackle the problems of drawing bit mapped text at a reasonable speed. This first pass will not attempt to tackle alternate or proportional fonts, or alternate font sizes. Instead, I will concentrate on techniques for squeezing greater performance out of the standard monospaced system fonts. ANTIC PUBLISHING INC.,COPYRIGHT 1986. REPRINTED BY PERMISSION. PROFESSIONAL GEM by Tim Oren Column #10 - VDI Graphics: Text Output This issue of ST PRO GEM concludes the two column series on VDI with a look at simple VDI text output, and ways to optimize its speed. There is also a Feedback section. You may find the associated download file under the name GMCL10.C in DL3 of the ATARI16 SIG (PCS-58). To keep the size of this first discussion of text within reason, I am going to restrict it to use of the mono-spaced system font in its default size and orientation. Discussion of alternate and proportionally spaced fonts, baseline rotation, and character scaling will become a later article in this series. DEFINITIONS. This article makes use of some terminology which may be unfamiliar if you have not used digital typefaces. A mono-spaced font is one in which each character occupies an identically wide space on the screen. A proportional font has characters which occupy different widths. For instance, an 'l' would probably be narrower than a 'w'. Text may be "justified" right, left, or center. This means that the right character, left character, or center position of the text string is constrained to a given location. In common usage, a page of text is "ragged right" if its lines are left justified only. The text page is "fully justified", "justified" or (ambiguously) "right justified" if BOTH the left and right characters are contrained to fixed columns. Full justification is produced by inserting extra blank characters in the case of a mono-spaced font, or by adding extra pixel columns in the case of proportional output. A text character (in a monospaced font) is written inside a standard sized cell or box. Vertically, the cell extends from the "top line" down to the "bottom line". If there are one or more blank lines at the top or bottom, they are called "leading" and are used to separate lines of text. The characters themselves always fall between the "ascent line", which is the highest line reached by characters such as 'd' and 'l', and the "descent line", which is the lowest line in characters like 'q' and 'g'. Other locations of interest are the "half line", which is the top of characters like 'a' or 'n', and the "base line", which is the bottom of characters which do not have descenders. Before plunging into the Attribute Functions for text, you should note that the writing mode (vswr_mode) and clipping rectangle (vs_clip) attributes discussed in the last column (#9) also pertain to text. Since much of the discussion of text optimization will center on these attributes, you may want to review them. TEXT ATTRIBUTES The writing color for graphics text is set with the command: vst_color(vdi_handle, color); Vdi_handle is always the handle returned from graf_handle() at application startup. Color is a word value between 0 and 15 which designates the output color index. As discussed in previous columns, the actual color which appears is dependent on the current palette settings. In applications such as word and outline processors it is important that characters and their background provide good contrast to avoid eyestrain. In these situations, you may want to use the setPalette and/or setColor XBIOS functions to force the palette to a known state before starting the application. You can choose a variety of special output effects for your text with the call: vst_effects(vdi_handle, effects); Effects is a single flag word, with the bits having the following significance: 0 - Thicken 1 - Lighten 2 - Skew 3 - Underline 4 - Outline 5 - Shadow In each case, turning the bit on selects the effect. Otherwise, the effect is off. Any number of multiple effects may be selected, but the result may not always be pleasing or legible. The "thicken" effect widens the character strokes by one pixel, resulting in the appearance of boldface type. The "lighten" effect superimposes a half-tone dither on the character. This mode is useful for indicating non-selectable text items, but is not legible enough for other purposes. The skew effect shifts the rows of the character the right, with the greatest displacement at the top. This results in the appearance of italic text. You should be aware that the VDI does not compensate for this effect. This means that a skewed italic character which is immediately followed by a normal blank will be overstruck, and part of the top of the character will disappear. Likewise, a skewed character written to the left of an existing normal character will overstrike part of it. There is a related bug in the VDI clipping logic which may cause some parts of a skewed character not to be redrawn if they fall at the edge of a clipping rectangle, even though they should fall within the region. The outline effect produces output which is a one pixel "halo" around the normal character. The shadow effect attempts to create a "drop shadow" to the side of the character. These effects should be used very sparingly with default sized fonts. They often result in illegible output. When graphics text is written, a screen coordinate must be specified for the output. The relationship of the text to the screen point is determined by the call: vst_alignment(vdi_handle, hin, vin, &hout, &vout); Hin and vin are each words, with values specifying the desired horizontal and vertical alignment, respectively. Hout and vout receive the actual values set by the VDI. If they differ from the requested values, an error has occurred. Hin may be set to zero for left justification, one for center justification, or two for right justification. The coordinate given when text is written becomes the "anchor point" as described in the definitions above. The default justification is left. Vin determines what reference line of the text is positioned at the output coordinate. The selection values are: 0 - baseline (default) 1 - half line 2 - ascent line 3 - bottom line 4 - descent line 5 - top line A common combination of alignments is left (0) and top line (5). This mode guarantees that all text output will lie to the right and below the output coordinate. This corresponds with the AES object and GRECT coordinate systems. Finally, the call to do the actual output is: v_gtext(vdi_handle, x, y, string); X and y define the screen coordinate to be used as the alignment point. String is a pointer to a null terminated string, which must be total eighty characters or less, exclusive of the null. This limit is imposed by the size of the intin[] array in the VDI binding. Be warned that it is NOT checked in the standard binding! Exceeding it may cause memory to be overwritten. One Inquire Function is useful with text output. The call vqt_attributes(vdi_handle, attrib); reads back the current attribute settings into the 10 word array attrib[]. The main items of interest are attrib[6] through attrib[9], which contain the width and height of characters, and the width and height of the character cell in the current font. You should rely on this function to obtain size information, rather than using the output of the graf_handle() function. On the ST, graf_handle() always returns sizes for the monochrome mode system font, which will be incorrect in the color screen modes. Attrib[1] will contain the current graphics text color as set by vst_color(). Attrib[3] and [4] contain the horizontal and vertical alignment settings, respectively. Attrib[5] contains the current writing mode, as set by vswr_mode(). OPTIMIZATION The most common complaint about using bit maps for character output is lack of speed. This section suggests ways to speed things up. By adopting all of these methods, you can realize an improvement of two to three times in speed. BYTE ALIGNMENT Since writing graphic text is essentially a bit-blit operation, characters which have "fringes", that is, do not align evenly with byte boundaries, will suffer performance penalities. The default system fonts in all resolutions of the ST are a multiple of eight pixels wide, so the problem reduces to assuring that each characters starts at a byte boundary in the screen bit map. This will be true if the horizontal pixel address of the left edge of the character is evenly divisible by eight. Obviously, byte alignment is easiest to enforce when the horizontal justification is right or left. Doing so with centered text is possible, but requires adding padding blanks to odd length strings. When writing text within windows, it is helpful to assure that the edges of the window working area are byte aligned. There is a section of code in the download which shows a technique for converting a user requested window position and/or size to its working dimensions, byte-aligning the width and horizontal position, and computing the adjusted external window coordinates. WRITING MODE The fastest text output mode is replace. All other modes require reading in the target raster area and merging it with the new information. You may find that you must use transparent or reverse transparent mode, for instance, to use or preserve an underlying background color other than white. In this case, you can still do some optimization by filling in the background color for the entire string with a v_bar() call, rather than doing it one character cell at a time. CLIPPING VDI output always proceeds faster when the clipping rectangle is turned off, and text output is no exception. Remember that you may only do this if you are drawing into a dialog box, or into the interior of a window which you know is on top. (You can use the WM_TOPPED and WM_NEWTOP messages for keeping track of the top window, or use the WF_TOP wind_get() call to find the current top.) In both of these cases, you will know the width of the drawing area, and you can truncate the output string to fit exactly, rather than setting the clipping rectangle. For this to work, you must have used the byte alignment technique to assure that the width of the writing area is a multiple of eight. BINDINGS The normal binding for v_gtext() is inefficient. It copies the string which you supply character-by-character into intin[] before it calls the VDI itself. In many cases, it will be more efficient for your application to place characters directly into intin[] and make the VDI trap call directly. To give you a start, the code for the standard v_gtext() binding has been included in the download. When setting up intin[], be sure not to load more than 80 characters, or you will probably crash the system! MOVING TEXT When performing text editing on the screen, you should avoid rewriting the string under edit whenever possible. It is always more efficient to use the raster operations to move a string to the right or left, assuming that you have obeyed the byte alignment rule. If you are deleting characters, blit the unchanged part of the screen to the left, and overstrike the last character in the string with a blank. If inserting characters, blit the trailing portion of the string to the right before writing in the new character. THAT'S IT FOR NOW This concludes the two article series on simple VDI output. Future columns may explore more complex VDI topics such as proportional text. If there is something you would like to see, please use the Online Feedback to let me know! Meanwhile, the next column will give out the locations of some of the "hooks" and "trapdoors" built into the AES object structure, including how to set up user-defined AES drawing objects. 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 . l t.. l tADVANCED t SETINIT [t 4VERIFY ht ESTARTGEM t HAUTODISK t P.  t ..  tBUCKET TOSt LBUCKET TXTt  KILLER PRGt `KILLER TXTt OPRINT TOSt PRINT TXTt SCACH_ t S_CODE t UU__CODE t *THIN TOSt -THIN TXTt .UEDIT C t /cUEDIT DOCt 3`&HN N *O*m - ЭЭм!"ҍ¼.A// Bg?<JNA m"m -So Q m -SBQ(m)M*mN lHhN~X/,?,N\?<N?<A?<NAXNuBUKTA01!HG& fNoJof20:g,0 @g @fa0g &`a$A PL؈/?NsA PL؈/? zNG SmBNupNuBUKT0:`gC`$Q^mAP"CR"NuAfA@|<@"@r @r@FNuA NuAt NuA NuNV n ( n!@ n ( n!@ N^NuNVvH n ( ngHl?< N *\`HHl?< N \|PHn?< N \> @f`ABA&HHn?<N\Bg/ ?<NNP gHl.?< N\`A* E* n ( nH=@v voHlD?< Nx\` oBg/ ?<=N\P< @lHlb?< ND\`b n/(/??<?N* gHl|?< N\`2 n ( n!@ n (Ѕ n!@ Hl?< N\LN^NuNVH n*( nH nHl?< N\`Hl?< N\|PHn?< N\> @f`xABA&HBg/ ?<\`@ n/(/??<@N$ gHl?< N\`Hl?< N\LN^NuNVH0Hl4?< N\B?< N\-@ |-P/.?< N\ .&@ BUKTfBn``=|/ G0m G9n??<?<N\|0 Gf BgNT0H-@/ Gam Gzn|??<?<N\Hl?< N\ GAf6 GDfBS GPf7| GSfBk GEf / NN4X9@g0<`0<N^NuNV?.?<LNXN^NuNV/ AB&H`0+ |g/ N*X AB"Ҽm?.NT&_N^NuNV/ &n/ NDXJ@g0<`00+ |g /+NXBk ?+ N,TJ@g0<`0<&_N^NuNVH&n0+ |f0<`.0+ |gV0+ |f0<`v0+ |g?<B?+ NhP?/+?+ NP @f0<`@k 0Hѫ`$ k o?<0+D@H/?+ NP'@&Bk0<LN^NuNV nl <`?.?./. ?<BN -@ m .`?<?.B?<BN` -@?<?.B?<BNF -@ nf .Ю -@ `$ nf .Ю -@ `0.g <`b . oHn . /?.?<@N Bg?./. ?<BN ]|9@g <` `Bl . N^NuNV/.?<IN\N^NuNVBn`&0.A0nf0.ABPRn nLmN^NuNV?>.=|0A"HPgXHf0(>N^Nu)I)J)_NM"l$l/,Nu)I)J)_NA"l$l/,NuNV`4 nH| f?< ?<NX nRH??<NX0. Sn J@fN^NuNV` nRH??<NX0. Sn J@fN^NuNV` nRH??<NRX0. Sn J@fN^NuNVH0&n $KA-H nf?./ N4\=n`L nf?./ N`\=n`. nf?./ Np\=n`-KBn?.NvTJ@f`H| f |o@ H-@/./.?.?<@N| 9@Hg0<`0,n/./<?.?<@NF 9@ @g0<`tRnR -@`R ned` 6.ƼЃ$@ H-@/./.?.?<@N 9@Hg0<`0,nBl0.L N^NuCB"2"2 2"2"2"22"2"2"22"2x9|@Nu Empty bucket first! Enter filename: File not found! File too big for bucket! Error opening file! Error reading file! [file read, no errors.] Nothing to write! Enter filename: Error creating file! Error writing file! [file written, no errors.] BUCKET by Moshe Braner Not enough memory for a bucket! Installing bucket - choose size in units of 16K (1-9, 0 to cancel): Not enough memory for such a bucket! Sorry, no background printing! Bucket installed. Deactivate bucket Activate bucket Stop printing Print bucket in background Empty bucket Write bucket to a file Read a file into bucket Quit Choose by first letter: CON:AUX:PRT:  Captured DOCs for BUCKET.TOS From mimsy!seismo!rochester!cornell!batcomputer!braner Tue Jan 13 16:30:46 EST 1987 Consider the following situations:You're trying to do some fancy printout, but your word processor doesn'tprint what you think you told it to. You would like to be able to seeexactly what control characters were sent to the printer...You want to print on a remote printer, send a document e-mail, or input itto another program, but your word processor (or whatever) will only savedocuments in its own wierd format, not as a formatted ASCII file...You have a printer-driver to print graphics from program X on printer A.But you would like to use printer B. You have the documentation forboth printers. You could easily write a "post-processor" program thatwill replace one set of control chars with another. But first you needa method of capturing the output for printer A in a file...You want to print long files without holding your computer up in themeanwhile...It is all possible with the program "BUCKET" (binary posted separately,source (in AL and C, compilable with Megamax) available upon request).BUCKET intercepts BIOS calls, and when they concern the printer itredirects them to a RAM buffer. It achieves background printing byinstalling a routine in the VBL queue (i.e. only about 60 cps).When you run BUCKET for the first time in a session (from the desktop or fromthe auto folder) it installs a RAM buffer plus some resident code. You enterthe desired size of the buffer in units of 16K: a number between 1 and 9.Then, and every time you run the (same) program later in the session, you getto choose items from a menu: Activate bucket - save printouts in RAM Deactivate bucket - stop saving Empty bucket - make room for new stuff Write bucket to a disk file - file the RAM buffer Read a file into bucket - for background printing Print bucket in background - after Activity or Reading Stop printing - but can resume later Quit - but leave settings intact The bucket is automatically emptied whenever background printing catches upwith the saved stuff. It is NOT emptied upon writing to a file, so if you want to do both you should write the file first.Enjoy!- Moshe Braner `pHz?<&NN\Bg/<?<1NAHz!Nuf 8f /| ZN( Captured DOCs for KILLER.TOS Reminder: after running this program ONCE, holding down Control, Alternate and BOTH Shift keys and pressing any other key will stop the program running at the time, as longas it is in user mode. Since programs call OS routines which execute in supervisor mode, you may have repeat pressing the last key several times. Also, now, caps lock MUST be off for it to work. `*o <(// Bg?<JNA JfdHy:?< NA\I&<2atHy?<NA\BgHy?<NNAPJ@f"BgHy?<=NAPJ@k 3EGBGBFe afW`X  fe afW`@  f8`  g0  f:< aJRF0@f`a8RF`aBfWJg  gv:< a:< a Jf``rH??<?<NM\NuJgNAXNuB?<?<NMX g. g& gB mֶgHRa<`JgS<a(< a <a`a< aB4HNu??<?<NM\Nu Enter name of file to print: sfirst ok TOS error occurred! $  Many printers don't know how to handle tab characters (ASCII 9), or at least require that you set up tab stops explicitly. A pain when you want a quick printout of, say, a program listing. Here is a little utility that will expand tabs to spaces and print the file. It assumes a tab stop every 8 columns. Note that embedded control characters (besides cr/lf and tabs) will confuse the column count for the rest of that line. Note that this program replaces "lpr.ttp" (in micro-C-Shell). - Moshe Braner .  t..  t SCACHASMPRGt SCACHC PRGt  SCACHE PRGt SCACHE TXTt  `Hy?<&NN\Hy?< NA\BR  @mBg/<3?<1NA f f0<""""QNu0<QNuCACH>/ Gm yjN&y~?NTG~F0/ ?G Go 0B3R@EfAz Hy&`T/9zJfH/gE F G~ oC0@@IhaRFEmBNu"|v Q ( CACHg An!Q"Nu Disk Cache installed jDD & T`2HN N*O*m - ЭЭм!"ҍ¼.A// Bg?<JNA m"m -So Q m -SBQ(m)M*mN lHhNX/,?,N\?<N ?<A?<NAXNu f f0<""""QNu0<QNuCACH>/|m zN&x~?NTGF0/ ?G Go 0B3R@EfAZ Hz``/:LJfD/g| | G, oC<0@@I`aRFEmBNu"|v Q ( CACHg A!Q"NuNVHz?<&N\Hl?< N\B`R PmBg/<4?<1NdPN^NuNuNV?.NT nn0<`?.?<>N4X9@g0<`0<N^NuNV?.?<LNXN^NuNV/ AB&H`0+ |g/ N*X AB"Ҽm?.NT&_N^NuNV/ &n/ NDXJ@g0<`00+ |g /+NXBk ?+ N,TJ@g0<`0<&_N^NuNVH&n0+ |f0<`.0+ |gV0+ |f0<`v0+ |g?<B?+ NhP?/+?+ NP @f0<`@k 0Hѫ`$ k o?<0+D@H/?+ NP'@&Bk0<LN^NuNV nl <`?.?./. ?<BN -@ m .`?<?.B?<BN` -@?<?.B?<BNF -@ nf .Ю -@ `$ nf .Ю -@ `0.g <`b . oHn . /?.?<@N Bg?./. ?<BN ]|9@g <` `Bl . N^NuNV/.?<IN\N^NuNVBn`&0.A0nf0.ABPRn nLmN^NuNV?>.=|0A"HPgXHf0(>N^Nu)I)J)_NN"l$l/,Nu)I)J)_NA"l$l/,NuNV`4 nH| f?< ?<NX nRH??<NX0. Sn J@fN^NuNV` nRH??<NX0. Sn J@fN^NuNV` nRH??<NRX0. Sn J@fN^NuNVH0&n $KA.-H nf?./ N4\=n`L nf?./ N`\=n`. nf?./ Np\=n`-KBn?.NvTJ@f`H| f |o@ H-@/./.?.?<@N| 9@Hg0<`0,n/./<?.?<@NF 9@ @g0<`tRnR -@`R ned` 6.ƼЃ$@ H-@/./.?.?<@N 9@Hg0<`0,nBl0.L N^NuCB"2"2 2"2"2"22"2"2"22"2x9|@Nu Disk Cache installed CON:AUX:PRT: `Hy?<&NN\Hy?< NA\BR  @mJ9fBgNABg/<3?<1NA f f0<""""QNu0<QNuCACH>/ Gm yvN&y~?NTGF0/ ?G Go 0B3R@EfA Hy2`T/9JfH/gE F G oC0@@IhaRFEmBNu"|v Q ( CACHVfNuAz!Q"Nu Disk Cache installed ZDD & R DISK CACHING PROGRAM by MOSHE BRANER WARNING: USE AT YOU OWN RISK! ~~~~~~~ ~~~~~~~~~~~~~~~~~~~ This program seems to work, but if it ends up trashing your disk don't blame me! Treat it for now as EXPERIMENTAL. Here is a simple disk-cache program. It is rather conservative: it only caches the FAT and the root directory for drives A and B. It also uses the RAM cache for disk-reads only, all writes are done immediately. Using it you will still need a RAM disk, but you gain 3 ways: (1) Floppy-disk operations are slightly faster, especially when dealing with many small files. For example, copying 11 files scattered over a floppy (about 100K bytes) took 29 seconds, but only 21 with this program installed - a 28% cut in the time, although still 4 times the theoretical minimum time... (2) Some nuisance disk-turning is avoided. For example, pressing when a floppy's desktop window is open does not turn the disk. But closing the window and reopening still does cause disk-turning, even when the disk was NOT removed, due to the way TOS works and the conservative nature of this program: it assumes the disk was removed if the boot sector is accessed at all. Similarly, closing a folder's window still turns the disk to reopen the underlying (root directory) window. There is room for improvement... Things are brighter from inside micro-C-Shell: No matter how many times you type "ls a:\" and/or "cd a:\", interspersed with RAM-disk operations, the disk does not turn (unless it's been removed). (3) Most important: The FAT and the root directory are accessed very often, and most disk-failures are in these sectors. Using this program will avoid a lot of those accesses, and should lengthen the life of your floppies. To use SCACHE ('S' for simple) run it from the desktop or from the AUTOfolder. Due to the way TOS senses disk-removal, the diskette write-protect tab has to be closed (not protected) for the cache to yield any gains. Following is the binary (uuencoded), the source (mostly in AL) will be mailed upon request. I hope this will encourage somebody to write a REAL disk cache! Happy New Year!!- Moshe Braner .  t..  t SCODE TOSt 6 SDECODE TOSt $` P N N *O*m - ЭЭм!"ҍ¼.A// Bg?<JNA m"m -So Q m -SBQ(m)M*mN lHhNX/,?,N\?<N?<A?<NAXNuCODENqNqM-J g x :g f\/DfR19`-@=@|0AHn?<NA\BgHz?<NNAPJ@fA ZmA`R`BgHz?<NAXJGf0<|F4Q0<|F4Q$nNu20H@a`K0HHC`2HHaJ0HaB0a<|m/H/?.?<@NA GfBGB@NuB4@MH4@MѮNu|?| |+n@A`|:m|@n@5` |[m@pRGRn n@m p pTGBnNuDUMPA.CSnccPNVH0&n``??<?<N$\Hf $@ @fL N^NuNVH0HlfNX$|B?< N\.&R/?< N\ CODEf4HlRNzX?<?<NX< Fyg FYg BgNTA &@B?< N\. '@$/?< N\Hl2NX?<?<NTXBg/<p?<1N^PL N^NuNuNV?.NT nn0<`?.?<>N*X9@0g0<`0<N^NuNV?.?<LNXN^NuNV/ A~&H`0+ |g/ N*X A~"Ҽm?.NT&_N^NuNV/ &n/ NDXJ@g0<`00+ |g /+NXBk ?+ N,TJ@g0<`0<&_N^NuNVH&n0+ |f0<`.0+ |gL0+ |f0<`l0+ |g?<B?+ N^P?/+?+ NP @f0<`6k -K k o?<B?+ N P n!@&Bk0<LN^NuNV nl <`?.?./. ?<BN -@ m .`?<?.B?<BN` -@?<?.B?<BNF -@ nf .Ю -@ `$ nf .Ю -@ `0.g <`b . oHn . /?.?<@N Bg?./. ?<BN ]|9@0g <` `Bl0 . N^NuNV/.?<IN\N^NuNVBn`&0.A0nf0.ABPRn nLmN^NuNV?>.=|0A"HPgXHf0(>N^Nu)I)J)_NM"l$l/,Nu)I)J)_NA"l$l/,NuNV0. Sn J@g6 nH| f?< ?<NX nRH??<NX`N^NuNV0. Sn J@g nRH??<NvX`N^NuNV0. Sn J@g nRH??<NHX`N^NuNVH0&n $KAL-H nf?./ N4\=n`H nf?./ N`\=n`* nf?./ Np\=n` -KBn?.NvTJ@f ndH| f |o@ H-@/./.?.?<@Nt 9@0Hg0<`0,0n/./<?.?<@N> 9@0 @g0<`hRnR -@`R``` 6.ƼЃ$@ H-@/./.?.?<@N =@9@0Hg0<`Bl00.L N^NuCfA"A"A&"AF"Af"A"A"A"A"CRA"A"A6"A:"AZ"C2A\"At"Ax"A"A"A"A*"A8"C~"2"2 2"2"2"22"2"2"22"2x9||NuE +--------------------------+ | SCREEN DUMP PROGRAM | | (Compressed format) | | for | | Atari ST with TOS in ROM | | by Moshe Braner, 8610 | +--------------------------+ Program not (re)installed. (Seems like it has been installed before!) Install (again) anyway? (y/n) Program installed. (Later: press Alt-Help for a screen dump. Dumps into files DUMPA.CSn, DUMPB.CSn, ... where n = 0, 1 or 2 for lo, med or hi res. Blinks screen if successful.) Hit any key CON:AUX:PRT: `6n+xN N*O*m - ЭЭм!"ҍ¼.A// Bg?<JNA m"m -So Q m -SBQ(m)M*mN lHhN X/,?,N\?<N ?<A?<NAXNuNVHn?<Nv\Bg/.?<NNfP g 0<`fA  @)P n 0<`FBg/.?<=N$P9@ @lBl0<`A)HA)HBl0<`N^NuNV lo?,?<>NXBlN^NuNVH0,g 0<` ,m^ gR $l ,,`,<$Hl/?,?<?Nj f A)HA І)@ ,m9|0<`b lRH G,m Gzo`` Gal 0| `0 Gml 0|a` Gvl 0|U` 0|;`LN^NuNVHN@@>N@@N@0`LN^NuNV0. l0,HѬRl0,ְlm.Rl0,԰lm 0<` ,6,HЃ)@Bl0<`N^NuNVH>.0Ѭ0H|?N|T @f 0<`0|?N^T`LN^NuNVH>.0@@>N@@N@0,g 0<`?NxT`LN^NuNVH>.0|@>N<0H@0@@<N@@N@0,g 0<`0`?NT @f 0<`0SGJ@f0<`LN^NuNVbH0?<N T-@?<N T-@BF` ? @f`,0AdBHndNX @fHl ?< N \`NL|>0)@ Ge GcHl>?< N \N`02,AgHlf?< N \ GfHlz?< N \`* GfHl~?< N \`Hl?< N ~\Hl?< N p\Hl?< N b\?<?<N X> @yg GYg NT`"Hl?< N ,\BF`"N^>0A00ѬRF FeBlBl)l`. Gc?N@T<` ?NT< Fg`N> @fN/N" ¼Ё*0,gHl?< N z\` ,аgHl?< N ^\NpHl?< N L\Hl?< N >\?<?<N XHn?<N \? @yg GYg`Hn?<N v\? @yg GYg` lfVHl?< N\?<?<NJX> Gdg GDf =|` Gng GNf Bn``rHl?< N<\|PbHnb?< N(\> @f`B0AdBBgHnd?<=NP o4Hl?< N\?<?<NX> @yg GYg`BgHnd?<NX fBF`0.|gBF`0 l RF F}e<<Hz/<?,?<@N fd/,/<}?,?<@N }f@Hz6/<T?,?<@Nt Tf?,?<>NZX fBF0g"?,?<>N:XHl?< N,\`PHl?< N\Hl:?< N \?<?<NX> @yg GYf`v/.NXL N^NuNuw3 6pr`P7wWw . ANvisionA!$  w3 6pr`P7wWw,w3 6pr`P7wWwNV?.NPT nn0<`?.?<>NX9@g0<`0<N^NuNV?.?<LNXN^NuNV/ A&H`0+ |g/ N*X A"Ҽm?.NT&_N^NuNV/ &n/ NDXJ@g0<`00+ |g /+N~XBk ?+ N,TJ@g0<`0<&_N^NuNVH&n0+ |f0<`.0+ |gL0+ |f0<`l0+ |g?<B?+ N^P?/+?+ NP @f0<`6k -K k o?<B?+ N P n!@&Bk0<LN^NuNV nl <`?.?./. ?<BN6 -@ m .`?<?.B?<BN -@?<?.B?<BN -@ nf .Ю -@ `$ nf .Ю -@ `0.g <`b . oHn . /?.?<@N Bg?./. ?<BNv ]|9@g <` `Bl . N^NuNVH0><2.|SA¼0?N8T&@f <` $@5G \/NX ,L N^NuNVH0><2.\ASA¼A ,$@ fA $@)@)@Bl&R0+Ge*0+Gf$`0k0+7G)J \`, f?NT&@ f <` $@ &@`L N^NuNVH0 .]&@$l` m n l`$R o l 2+Ёf R0(k R&`& 2*Ёf 0+j$`$)JL N^NuNV/.?<IN\N^NuNVBn`&0.AԔ0nf0.AԔBPRn nLmN^NuNV?>.=|0AԔ"HPgXHf0(>N^Nu)IԐ)JԌ)_ԈNM"lԐ$lԌ/,ԈNu)IԐ)JԌ)_ԈNN"lԐ$lԌ/,ԈNu)IԐ)JԌ)_ԈNA"lԐ$lԌ/,ԈNuNV0./?<HN\-@ .f-| .N^NuNV0. Sn J@g6 nH| f?< ?<NX nRH??<NpX`N^NuNV0. Sn J@g nRH??<NBX`N^NuNV0. Sn J@g nRH??<NX`N^NuNVH0&n $KAj-H nf?./ N4\=n`H nf?./ N`\=n`* nf?./ Np\=n` -KBn?.N&TJ@f ndH| f |o@ H-@/./.?.?<@N@ 9@Hg0<`0,n/./<?.?<@N 9@ @g0<`hRnR -@`R``` 6.ƼЃ$@ H-@/./.?.?<@N =@9@Hg0<`Bl0.L N^Nu9|C"2"2 2"2"2"22"2"2"22"2x9|)|Nu Name of file to be decoded: Unable to open that file! Not a valid Compressed Screen file! That file is for LOWMEDIUMHIGH resolution! Decode anyway? (y/n) Decoding... Error reading file! Checksum doesn't fit! Press any key to see picture, later press any key to return. Print decoded picture? (y/n) Save decoded picture to disk? (y/n) DEGAS or N-VISION format (d/n): Name of file to be written: Overwrite existing file? (y/n) Unable to open that file! Error writing file! File written succesfully. Decode another file? (y/n) CON:AUX:PRT: .  t*..  t UUDECODETOSt +UUENCODETOSt ,`:*o <L// Bg?<JNA JfHy?< NA\C>$<2a:BgHy>?<=NAPJ@k3:aBy`a&a2a>?<NAT`aaa&BgNAHy"?< NA\Nu?9:?<>NAXNu?9NAXNuH瀀B?<NAT g. g& gB mڴgRa@`JgS<a,< a$<a`a< a B1LNu??<NAXNu Enter name of file to decode: TOS error occurred! $ (   D`f*o <L// Bg?<JNA JfHy?< NA\Cj$<2a2Hy?<NA\BgHyj?<NNAPJ@fBgHyj?<=NAPJ@k3fHy?< NA\C$<2aBgHy?<< Dg$BQx? QJGVJGgCB`C?fM0g `Rg eaZ`JGgjaL`JgNAXNu?9h?<>NAXNuB?<NAT g. g& gB mڴgRa<`JgS<a(< a <a`a< aB1Nu??<NAXNu Enter name of file to encode: Enter name of output file: begin 644 end TOS error occurred! $ V 2  `HyD?<&NN\Hy?< NA\BR  @mJ9fBgNABg/<?<1NA"|v Q ( THINVfNuAv!Q"NuTHIN0/ @l&0/g @f0/ @mo @ no yrN 'THIN' installed. (Reboot when done!) , F Captured DOC's for THIN.TOS This program may help salvaging data from a defective disk. In many cases, the defect is in the main FAT area. After running this program, TOS will use the spare copy of the FAT (sectors 1-5) rather than the main copy (sectors 6-10), when reading from drive A or B. After you copy the files off the disk you should reboot. After the (uuencoded) program: An updated version of 'scache': this one avoids reinstalling itself in RAM if you run it again. - Moshe Braner /* UEDIT - program to edit linked chains of RAM-resident utilities conforming to the suggested protocol. By Moshe Braner 870102 */ #include int hex[] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; int nibble(c) register int c; { register int i; if (c>='a' && c<='f') c += ('A'-'a'); for (i=0; i<16; i++) if (c == hex[i]) break; return (i); } long gethex() { register long v; register int d; register char *cp; char str[80]; str[0] = 76; Cconrs(str); str[str[1]+2] = '\0'; cp = &str[2]; v = 0; while ((d=nibble(*cp++)) < 16) v = (v<<4) + d; return (v); } puthex(v) register long v; { register char *cp; char str[7]; str[6] = '\0'; cp = &str[6]; while (--cp >= str) { *cp = hex[v&0xF]; v >>= 4; } Cconws(str); } main() { register int c, level, valid; register long address; long vector, temp, stack; long over[9]; char str[80]; str[0] = 76; new: Cconws("\033E\r\n\n\tUEDIT\tby Moshe Braner"); Cconws("\r\n\n\tEnter vector address, in HEX: "); vector = gethex(); if (vector <= 0) goto new; level = 0; stack = Super(0L); /* get into supervisor mode */ address = *((long *) vector); Super(stack); /* back to user mode */ repeat: Cconws("\033E\r\n\n\tVector address: "); puthex(vector); Cconws("\r\n\tLink level: "); Bconout(2,level+'1'); Cconws("\r\n\tUtility address: "); puthex(address); valid = 0; if (address<0x001000 || address>0xFEFFFF || (address&1==1)) Cconws("\r\n\t(Not a valid utility address)"); else { temp = *((long *)(address-4)); if (temp<0x001000 || temp>0xFEFFFF || (temp&1==1)) Cconws("\r\n\t(No valid next address)"); else { Cconws("\r\n\tMagic here: \""); Bconout(5,*((char *)(address-8))); Bconout(5,*((char *)(address-7))); Bconout(5,*((char *)(address-6))); Bconout(5,*((char *)(address-5))); Bconout(2,'\"'); Cconws("\r\n\tNext address: "); puthex(*((long *)(address-4))); valid = 1; } } if (level) { Cconws("\r\n\tPrevious address: "); puthex(over[level]); } Cconws("\r\n"); if (level) Cconws("\r\n\t\tPrevious utility"); if (valid) { Cconws("\r\n\t\tNext utility"); Cconws("\r\n\t\tUnlink this utility"); } Cconws("\r\n\t\tLink in a utility (over this one)"); Cconws("\r\n\t\tQuit"); Cconws("\r\n\t\t(default: restart)"); Cconws("\r\n\n\t\tEnter first letter of choice: "); Cconrs(str); if (str[1] == 0) goto new; c = str[2]; if ((c=='p' || c=='P') && level) address = over[level--]; else if ((c=='n' || c=='N') && valid && level<8) { over[++level] = address; address = *((long *)(address-4)); } else if ((c=='u' || c=='U') && valid) { Cconws("\r\n\n\t\tUnlink? (y/n) "); Cconrs(str); c = str[2]; if (c!='y' && c!='Y') goto repeat; address = *((long *)(address-4)); if (level == 0) { /* over==vector */ stack = Super(0L); *((long *) vector) = address; Super(stack); } else *((long *)(over[level]-4)) = address; } else if (c=='l' || c=='L') { Cconws("\r\n\n\t\tLink in? (y/n) "); Cconrs(str); c = str[2]; if (c!='y' && c!='Y') goto repeat; Cconws("\r\n\n\t\tEnter utility address, in HEX: "); temp = gethex(); if (temp == 0) goto repeat; *((long *)(temp-4)) = address; if (level == 0) { stack = Super(0L); *((long *) vector) = temp; Super(stack); } else *((long *)(over[level]-4)) = temp; address = temp; } else if (c=='q' || c=='Q') exit (0); goto repeat; } From mimsy!seismo!rutgers!mit-eddie!genrad!decvax!tektronix!uw-beaver!cornell!batcomputer!braner Mon Jan 5 17:00:53 EST 1987 [] To follow up on my suggestions for a RAM-resident-utilities-installation- protocol, here is a program (C source code, ARCed and uuencoded) that lets you scan the chain of utilities linked to a given system vector, and unlink (and relink) them at will. Try vector $118 when KILLER is installed, or $476 when SCACHE is installed. WARNING: do not "Link in" utilities which do not conform to my "protocol", as the 4 bytes before the address you type in will be overwritten! A "reprint" of the protocol, and the binary for this program, are available upon request. - Moshe Braner . \ t4.. \ tSETINIT DOC`t 5SETINIT PRGct 76This program should be put in your auto folder before all other auto files except the hard disk interface program. The first screen displays all of the auto programs while the second screen displays the desk accessories. By selecting the appropriate character you can activate or deactivate the auto prg / desk acc. Simply press return or the space bar to go to the next screen (or exit program). Please note that under the Supra hard disk interface the auto files are in the 'auto' folder in 'A:' and the desk accessories are under drive 'C:' To change these drive settings: OFFSET Contents Description $3024: 41 3A (hex) Source drive for AUTO files A : (ASCII) $302A: 43 3A (hex) Source drive for desk accessories C : (ASCII) Ken Lightbody Analysts International Corporation (Chicago) (312) 882-4673 UUCP: ihnp4!aicchi!ken GENIE: K.LIGHTBODY of this file was written by: Jeffrey Spidle Systems Analyst Office of Continuing Education Iowa State University Ames, IA 50011 Since there is a similarity in operation between his program and this one, I saw no reason to "re-invent the wheel". This a utility called 'MAKE' and is a much simplified version of the MAKE utility on UNIX (a trademark or something of AT&T). This program was ori- ginally written by Larry Campbell of DEC using the CI-C86 compiler. I have re- written it using the Lattice C compiler Ver. 2.14 running under MS/PC-DOS 2.1x. Added features include macro capability, command line parsing of macros, silent operation (-s option), ignore/obey errors (-i option), faster operation, and the ability to run any DOS-level command. -- March 31, 1985 Mike Hickey Systems Programmer `.%T*O*m#4b - ЭЭм"ҍ¼.A// Bg?<JNA y4b"h#4fE?/ NN"/0<NBNu o AdpNu#4fBNuNV0/"/ NB4fd0< A/"NB0<NBN^Nu o2/0/ HSoQBNu o0/JfBNuf SNuNV#44.4?<NT.0/<4N#X.0/<4N#.XNJy0o$>GN~>G/<0 /<0NPBy0.0/<4N#X.0'/<4N#.XNJy0o$>CN~>C/<0./<0NP.1?<NT.1?<ENTBWN N^NuNV>/<4?<NN\=@`L>/94090м4/N#XPRy0 y0g>ON=@0.HJgN^NuNV.1?<NT.1?<ENTBn`0.м4.N#0@SH2.4H. g@0.м4.?<q?<?<p?<?.Wa/<00N`(0.м4.?.Wa/<0NN\Rn0.y0mV.1?<NT.1?<YNT.1?<7NT.1?< NT.1?<NT.1?<pNT.0dN.1?<NT.1?<qNTN^NuNV>N=@0.|=@ n g n m n onaJnmt0.y0lf./N#X. /N#.X0.м4./N#.X./N#X0.м4.N#0@SH2.4 1fN0.м4.N#0@SH2.4.N#0@SHn`J0.м4.N#0@SH2.41.N#0@SH|1./Bg?<VNPJg.0N.H>N~`FN^Nu#4lNN/94lNu#4lNM/94lNu#4lNA/94lNuNVHN BW/</NjX>/</NjX>/</NjX n2n B*n`&HHм2 @g H| `HRJf> /.NXJL N^NuNV. /./<1N&PN^NuNV./. /.N&PN^NuNVH*n`.1H?NT|fp`Jf.1?< NTJL N^NuNVH BWN#4r#4vBy4p./a*n`N`RJgHHм2 @fJg2 "g 'fFH>/ RNX(@ f.0/ aVX H> M2GBRG.Ra`BG`RG M2GJg5pHHм2 @gJ5pg M2GBRGH`BWN FBW/ RNXJ@g.R/<0aX`l>N F ->f@>/ TNX|f>B?<N\|f.R/<0a|X`$BW/ RN rX|g.R/<0aVX`>?/ NXJf>*/ NXJg-|<.8?<NT>/ ?<N!\<f.0/ aX`^.H?/.aZ\.N#>RWN$b(@./ N#X.a>/ ?<N!\<f`.a`|g`JfBaSy4p.4vNz|f.1 /<0a*XB/94r?94pN\>N JL0N^NuNV|./N#X. /N#.X.1 /N#.X.?< NT>N N^NuNVH*n y4v X4vRy4pJL N^NuNVH*n. (nGVfJL8N^NuNVHN>|fp`>N,08*@62JnfU.//.N#XJ@f U0`R`.0/.N#XJ@fU0`2>/.?N!\J@g3#234j2p`U0JL N^NuNVBW?. /.a:\N^NuNVBW?. /.a"\N^NuNV>?. /.a\N^NuNVN >NN^NuNVHBG`0м1.N RG|mJLN^NuNVH*n0-|g*.N -g .NzB@H+@+@Bm m>N FJL N^NuNVH>.>N*@ f3 234j2p`NBF0|f>?<>N%T<l|>N,>NJFf0``3234j2pJL N^NuNVN^NuNVH*n0-| |f, -<o >/-?N\>Gg mp`J-gJg-g;| `;| `>0- D@H/?N\Bm +mB@JL N^NuNVHN>|fp`>N,08*@62Jn fUJnfU.//.N#XJ@fU;n 0``.0/.N#XJ@fU0`d>/.?N!\J@g>N3234j2p`0U>B-H?N\BWB-H?N\0JL N^NuNVBW?. /.a\N^NuNVBW?. /.a\N^NuNV>?. /.a\N^NuNVH>N*@ f3 234j2p`$>?-/. ?<BN%P+@U -JL N^NuNV>B?.a\N^NuNVH*nBnJ gh``BE-n `RRE nJg n %fJEo.?/. Nl\-n n n %@R DfBn n H|-@R Df n R Rn| <0fG n R =|<*f-M n=PT n R `8`*JnlBnH2. A|=@ n R <0m<9o|<.f BF n R <*f-M n<T n R `*`H2 A<| n R <0m<9oBn<lg<LfRn n R A-HH` RnJng <z` <0#4z.4z?<?< // N Jngp`pH`RnJng <z` <0#4z.4zBg?< // N Jngp`pH`zRnJng <z` <0#4z.4zBg?<// N Jngp`pH`&RnJng <z` <0#4z.4zBg?<// N Jngp`pH`-M n-PX`-M n0|@B.T`H>?// N X|`~H>?// N X|`XH>?// N" X|`4.H?NTRn``|C|5b@0@1 PN.N#:ElJFm:0.E=@JnfX .0f* n -f SE. nH?NTRRn`..H?NTRn0.SnJ@n.?/.Nl\n`..H?NTRn0.SnJ@n`0.JL N^NuNVJnlp`0.=@ n -@>/. /.N%PN^NuNVJnlp`0.=@ n -@>/. /.N(PN^NuNV>/. /.a~P-@. N#2.^AAo>/. /.aP-@ .N^NuNVH *n>. (n,g$Bl >/ ?N\Gg lp`*B@`&`.H?NT|fp` 0SGJ@fB@JL0N^NuNVH. *n Sm mH"m|R``.H?N"TJL N^NuNVH. *n BF:-fp`$JfV-fN>N+@+@fm`2m>NpJ@gm@`;| H"mR`-gA+H +@ mR-gz>/-?N\<Bm `n-g>< g -мb" -:>/-?N\<+mBm `( -:>/-?N\<;| +mFg mp`H|JL N^NuNVH>N*@ fB@`-fB@`pJL N^NuNVH>N*@ fB@`0|JL N^NuNV>aJ@g </`BN^NuNVH>.^GORG>a*@ fB` >/ aXJL N^NuNVH (y2*T`ZB@0-BA2-@F@J@g>NB`:B@0-ne `*2f>a*@ f>NB`(M*U`JL0N^NuNVH n*PB@0. X@me n `F(MB@0. HH@B@H@B@0-n 9@B@0,F@9@( n ;n B@0-F@;@#2 PJL0N^NuNVH >.|?GG0@>N*@fB`* R*@(M9GB@0,F@9@.Pa 92JL0N^NuNVH *nQB@0-BA2-@F@J@g>Np`(y2λeeecd(T`e2 BA2-IHABAHAЁ" BB4,JHBBBHB҂b #2B@`n BA2-IHABAHAЁf T0(mB@0-F@;@ T*`* BA2,IHABAHAЁfB@0-lB@0,F@9@(`(#2B@JL0N^NuNVH *n.a>. ^GORG>a-@fB`J n(PPg2d`Sn Jn f`B0. B0. `%Sn Jn f>/.aXJL0N^NuNVN^NuNVN^NuNVH /?.?./ /. nN*@ мfB(n `%H|0|9o^G мfB JL0N^NuNVH-|3|*n<.H n. nfz` |SEJgJEf`h nf$z ` |SEJgJEfJEf-`*n<.JngJGlB@0D@> n P-"n R`B0H@B0>JGf JL N^NuNVH >.HμgR*y4f(G4f.Nz|f3 234j2p`>Bg/ N\ JL0N^NuNVH>N*@ fp`vJnfB@`j-g3 234j2p`L0|g>/. / NP`0-g>/. / NP``>/. / N PJL N^NuNVH|BG` 2f 20`RG|m3234j2pJLN^NuNVp2.`F@H2B@N^NuNVHBG`>aRG|mJLN^NuNVH 0.8*@620.@BUB-+| BB> Bg/ N\> ?< / N\JL0N^NuNVH>.|e3 234j2B`0B@08*@62-f3 234j2B` JL N^NuNVH *n(n >.B@=@=@``Rnnc L2n  fB@0.ncf>?.B@0.W B2.Ё//-/ N%X=@B0.ѭJnf3234j2p`^=n`8Rn>?</<2/-/ N%X=@B0.ѭnb4 -o+mB@0.JL0N^NuNVH*n>?./. /-/ N%X=@Jnf3234j2p` B0.ѭ -o+mB@0.JL N^NuNV=|Bn n(g -|!H` n(g-| n(g .м-@ n0(| =@Bn=n`=|` n  f.=|Jng 0.R@|l N2n| Rn`\ n  fRJngLp2.|A=@0.n|l^0.n` N2n| Rn0.SnJ@fR ` N2n"n QR RnSnRn nlJnf>0.S@@/ nNXJnfB@0.N^NuNVH*nH|=G`H>Bg?<aX0SGJ@n0.JL N^NuNV#YNM/9YNuN^NuNVH*nH=@ M2n$BG-M`H M2G $f: n $g.?< NT.$?<NT 2HЁR-@RGnm 2HЁg.?< NT0.JL N^NuNVH*n 0.8м62-@~.a&M`RJg :fJgc .Am .On*K`K0.`BW/ ?<o n1GBG`BW/ ?<NN%\JgB@`0<>`d>ON%JgB@`0<>`J.?<=N%T>o n1GBG`,.?<AN%T>``||b@0@2 PN0JL8N^NuNV n am n zn n nHRJfN^NuNVH *n (n`RJff .JL0N^NuNVH *n (n`RR0.SnJ@ofRn`B0.SnJ@f .JL0N^NuNVH *n (nf .JL0N^NuNVH *n(M`RJf HJL0N^NuNVN^NuNVH *n(n `$H>a0H>a&op`lp` JfJfB@JL0N^NuNVH>.|am |zn|0JLN^Nu _B0Z"y4fCCbNC NNVH..,. Jf#3| <`Hc #3|B`:fzB`(xe 〼b`BJge`#3| JLN^NuJg .NuNV n=h.0n/0n/N%P/?.?<?N%PN^NuNV n=h.0n/0n/N%P/?.?<@N%PN^Nu#4~NA/94~NuNVBBJlDRBJ lD RB0. -@0.2. An=@ .gDN^NuNVHJnn=|` no=|*n B/.N*dPl-/.N+X-@B/.N*dPf*0Jng.`00.SnJ@fB . `BG`SG/<D/.N+P-@/<A/.N*dPm`RG/<D/.N*P-@/<A/.N*dPl0nRH"<3/0/.N*DP-@/<A/.N*dPm/<D/.N*P-@RGJGn0`P/<D/.N+P-@/.N+2X<0H/N*X//.N+P-@0|0HSGJGnJngv.` 0SnRGJGlJnf`N/<D/.N+P-@/.N+2X<0H/N*X//.N+P-@0|0H0.SnJ@nB . JL N^NuNVHJnn=|` no=|*n B/.N*dPl-/.N+X-@B/.N*dPf00.`00.SnJ@fe00B . `BG`SG/<D/.N+P-@/<A/.N*dPm`RG/<D/.N*P-@/<A/.N*dPlSG0nRH"<4/0/.N*DP-@/<A/.N*dPm/<D/.N*P-@RG/<D/.N+P-@/.N+2X<0H/N*X//.N+P-@0|0H.`N/<D/.N+P-@/.N+2X<0H/N*X//.N+P-@0|0H0.SnJ@neJGl 0D@>-0H |0H0H H@|0HB . JL N^NuNVH..,. N,8 LN^NuNVH..,. N- LN^NuNVH..,. N-J LN^NuNVHJl| .D-@`BFJfB`^~` .-@R .f` .-@S. g .-@޼@ JFg .JLN^NuNVH .м<JgJFlB`V .:|oJEg <` <`0..μ|`RFJFm`SFJFnJEg D. JLN^NuNVH..,. N- LN^NuNVH..N, LN^NuNVH..,. N,& LN^Nu<NuJg NugR kjklf`>k^g>k^g2k8<d,&B<ރeNuRid~S<Nu.NuJNu:ڼ.gNugRghEDvi^E]HE:BB8HD&HC؃HF&؃BDHDHGHFHEބj ޼gNuSiex@ބއdRgNu~NujJ<Numc68343 floating point firmware (c) copyright 1981 by motorola inc. Stack Overflow$C runtimeCON:LST:A:C:\AUTO\*.PR?\AUTO\\*.AC?\%c --> %c%cINACTIVE%c%c %s %c --> ACTIVE %s ^-- Type in character of file to reverse********* Error on rename *********: unmatched quoteCannot open Cannot append Cannot create : No matchStack Overflow $   (Djn(Djr22 """j"""""H!!!!"CP/M-68K(tm), Version 1.2, Copyright (c) 1983, Digital Research XXXX-0000-654321@< 9n6ѷ2Ŭ/7,ֿ(w%p_".$|敔 w@< 9n6ѷ2Ŭ/7,ֿ(w%p_".$|敔 w6$ N                  F(   ,"    ,D( 0 <    >  h 0HN&  j^***`&$ D"V8*R$ZF \@(B4.J".86 Jp &""@2&"fNR4*R"(<@* D"H`J&v :  lar, they may not collect royalties on any version of this program which includes these portions. */ /* Certain portions of this software are Copyright (c) 1986 by John Chapman, 3635 Lozells Ave., Burnaby B.C., Canada, namely all those lines which differ between versions 2.11 and 3.00. These changes were/are necessary to make this program function on the Atari 5. i tE.. i tVEROFF PRGkt FGVERON PRGnt GG`&DOfB?< NA\3D/?< NA\BNAne, will simply make the first target in the makefile. Remove the command line symbol definition option. Changed the line continuation character to \ (it was - ) Now symbol definition lines do NOT begin with $. Fixed numerous bugs dealing with character arrays and their lengths. Now a shell command line which begins with @ is not echoed. A shell command line beginning with + is executed through command.com ; this makes io redirection and pipes available, but the exit code of the program cannot be checked due to a misfeature of command.com. A shell command line beginning with - may return a nonzero exit code without halting 'make'. Fixed it so a target:prerequisite line followed by no how-to lines is interpreted not as an error, and not as sharing the how-to lines following the next target:prerequisite line, but is considered fulfilled by no action other than making all the p`&DOfB?< NA\3D/?< NA\BNAmmand was never dicovered. This resulted from using "system", which uses "command.com", which hides the return code of the program it runs. Resident commands can still be used, nevertheless. Error messages now include the line number of the makefile, if relevant. Made the return code of the command print out if nonzero. Now the copyright notice only prints when the usage appears. Convert to Microsoft vers 3.00, large memory model. - dan grayson * 2.10 Fix bug in abort routine, update copyright notice * 2.09 Set up for command line parsing of macros * 2.08 Remove edit 2.05; keep debug a compile-time option * 2.07 Finish macro parsing * 2.06 Add initial code for macro handling * 2.05 Add -d (debug) switch * 2.04 Add error message handling (doserror). * 2.03 Add -i (ignore errors) switch. * 2.02 Add -s (silent run.  tH..  tSTARTGEMPRGt ISTARTGEMS t J`bAd Hz2?< NA\OtF?<%NNTOQB?< NA\Ot#,E$#/?< NA\OABg/?<1NAp,g2B y,C"C #, h h$h C"Nu o PfT#, zCfHy?<NA\?<'Hz?<NNAPJ@f o PgTgf z0) dumppr(targ[0]); #endif if (*sp==0 || *sp=='!' || *sp=='#') continue; /* ignore comment lines and blank lines */ if (isspace(*input)) { /* if leading space, then this is a shell line */ if (targi == 0) error("Target line must come before shell lines"); sp = input; passpace(&sp); for (i = 0; i < targi; i++) NewShellLine (targ[i], sp); continue; } { /* substitute for symbols - this will be done later for shell lines, to take special symbols like $* into account, which can only be known at run time */ breakout_symbols(&input); } { /*** check for the form 'name = value' ***/ char *endword; sp=input; password(&sp); endword = sp; passpace(&sp); if (*sp == '=') { targi=0; sp++; *endword = EOS; SetSymbol (input, sp) ; continue; } } /* end of macro parsing */ /**** now we know this is a 'targets : prerequisite.  tP..  tAUTODISKC t QAUTODISKPRGt Xx/* AUTODISK - program to copy floppy disk to RAM disk upon system boot, and also set the system clock. By Moshe Braner 861223 FUNCTION This program is to be placed in the \AUTO folder on the boot disk, AFTER the RAMdisk program. (It assumes the reset-immune RAM disk is already installed.) After asking the user for the current time and date, and setting the ST's two clocks accordingly, this program copies the whole floppy disk data, FATs, directory and all, onto the RAMdisk. (It first finds out which sector is the last one actually holding data, and copies all sectors up to that one.) HINTS For best results: Freshly format a disk, make an AUTO directory, put in it first RAMDISK.PRG and then AUTODISK.PRG, then put on the disk all other files you want to load to the RAMdisk at boot time, but no others. You can set these files up in folders if you want: first make the folders, then put the files on the disk directly into the folders. For maximum speed do not make any deletions of files, nor copy files from the disk to itself. You may save the desktop (with the RAMdisk icon installed, and perhaps the RAMdisk's window open) on the disk, too. Make sure the RAMdisk is more than big enough to hold all those files. ACKNOWLEDGEMENTS This program made possible in part by Eric Terrell, who posted "eternal.s". The method of setting the ST's clocks is borrowed from "settime", posted by Allan Pratt of Atari. WARNINGS This program is for booting off a floppy disk. For hard disks (or future, very large, floppies) the program (and/or the RAMdisk program) needs some tweaking, at least at the points marked ">>>>". This program will not work with "copy protected" disk formats, including the "FAST" format with its "dead" sectors. */ #include #define SECSIZE 512 /* >>>> for now 512-byte sectors only */ #define WORD int /* 16 bits: 'int' in Megamax */ #define OK 0 #define READ 0 /* read unsigned bytes and Intel-style integers */ #define US(p,o) (p[o]&0xFF) #define UI(p,o) (US(p,o)+256*US(p,o+1)) #define fixup(s) (s[s[1]+2] = '\0') /* null-terminate a GEMDOS string */ int strlen(s) char *s; { register int i=0; while (s[i++]); return (i-1); } error(msg) char msg[]; { Cconws(msg); Cconws("\r\n\n\tHit any key "); Cconin(); exit(0); } int dotime(s) register char *s; { register int len; register int hour, minute, second; len = strlen(s); if (len < 4 || len == 5 || len > 6) goto badtime; hour = (s[0]-'0') * 10 + (s[1]-'0'); minute = (s[2]-'0') * 10 + (s[3]-'0'); if (len == 6) second = (s[4]-'0') * 10 + (s[5]-'0'); else second = 0; if (hour < 0 || hour > 23 || minute < 0 || minute > 59 || second < 0 || second > 59) goto badtime; if (Tsettime((hour << 11) + (minute << 5) + (second >> 1))==0) return (0); badtime: Cconws("\r\nIllegal time (bad format or out of range)\r\n"); return (1); } int dodate(s) register char *s; { register int len; register int month,day,year; len = strlen(s); if (len != 6) goto baddate; year = (s[0]-'0') * 10 + (s[1]-'0') - 80; month = (s[2]-'0') * 10 + (s[3]-'0'); day = (s[4]-'0') * 10 + (s[5]-'0'); if (year < 0 || year > 119 || month < 1 || month > 12 || day < 1 || day > 31) goto baddate; if (Tsetdate((year << 9) + (month << 5) + day)==0) return (0); baddate: Cconws("\r\nIllegal date (bad format or out of range)\r\n"); return (1); } main() { register int i, res, secs; register char *buf; int bps, spc, fats, dir, spd, spf, spt, drive, sides; WORD date, time; WORD *bpb; char boot[SECSIZE], str[10]; long stack, datime; char *msg = "\nError reading disk!"; Cconws("\033E\r\n\n\tAUTODISK\tby Moshe Braner\r\n\n"); /* see if IKBD time is valid */ datime = Gettime(); /* get ikbd date & time */ date = datime >> 16; time = datime & 0xffff; if (((date & 0x1f) != 0) && (((date >> 5) & 0x0f) != 0)) if (Tsetdate(date)==0 && Tsettime(time)==0) goto timedone; /* we want to prompt the user */ str[0] = 7; /* set up buffer for Cconrs call */ do { Cconws("\r\n\tEnter the time (hhmm[ss]): "); Cconrs(str); fixup(str); } while (dotime(str+2)); do { Cconws("\r\n\tEnter the date (yymmdd): "); Cconrs(str); fixup(str); } while (dodate(str+2)); datime = ((long)Tgetdate() << 16) + Tgettime(); /* get GEM's time */ Settime(datime); /* update the ikbd's time */ Cconws("\r\n"); timedone: drive = Dgetdrv(); /* use current drive */ /* get disk parameters from boot sector */ if (Rwabs (READ, boot, 1, 0, drive) != OK) error("\nError reading boot sector!"); bps = UI(boot, 11); /* bytes per sector */ spc = US(boot, 13); /* sectors per cluster */ res = UI(boot, 14); /* no. of reserved sec */ fats = US(boot, 16); /* no. of FATs */ dir = UI(boot, 17); /* no. of dir entries */ spd = UI(boot, 19); /* sectors per disk */ spf = UI(boot, 22); /* sectors per FAT */ spt = UI(boot, 24); /* sectors per track */ sides = UI(boot, 26); /* sides of disk */ secs = 32 * dir; /* >>>> 32-byte dir entries */ secs /= bps; /* length of dir in sectors */ /* find start address of RAM disk */ stack = Super(0L); /* get into supervisor mode */ buf = *((char **) 0x42E); /* physical top of memory */ Super(stack); /* back to user mode */ /* adjust RAMdisk BPB */ bpb = (WORD *) buf; /* RAMdisk BPB area */ if (bpb[0] != bps) error("\nDisk and RAMdisk incompatible!"); /* >>>> */ if (bpb[255] == 0x4144) /* our own magic number */ error("\n\tOld RAM disk!"); bpb[255] = 0x4144; /* "AD" */ bpb[1] = spc; bpb[2] = bps*spc; bpb[3] = secs; bpb[4] = spf; bpb[5] = spf + res; bpb[6] = 2*spf + bpb[3] + res; /* >>>> assumes 2 FATs */ /* copy the data from floppy to RAM */ Cconws("\n\tCopying data...\r\n"); buf += 512; /* start of RAMdisk data area */ buf += bps*res; /* start of FAT area */ secs += fats*spf; /* read FATs + dir */ if (Rwabs (READ, buf, secs, res, drive) != OK) error(msg); res += secs; /* no. of sectors already read */ i = 3*(spd-res); /* >>>> 12-bit FAT entries */ i = 3 + i/spc/2; while (buf[--i] == 0); /* search for last used sector */ buf += bps*secs; /* next place to put stuff */ i *= spc*2; secs = i/3; /* no. of sectors left to read */ if (secs > spd-res) /* safety check */ secs = spd-res; if (Rwabs (READ, buf, secs, res, drive) != OK) error(msg); Cconws("\n\tAUTODISK finished, no errors\r\n"); for (datime=0; datime<50000; datime++); } (prereq) { for ( ; prereq; prereq = prereq->next) { ulong date; make (prereq->file->fname); /* recursively make */ date = getdatetime(prereq->file->fname,LONGPOSINF); if (date > NewestPreq) NewestPreq = date; } } targtime = getdatetime(targ->file->fname, LONGNEGINF); #ifdef DEBUG ` HN N `*O*m - ЭЭм!"ҍ¼.A// Bg?<JNA m"m -So Q m -SBQ(m)M*mN lHhNX/,?,Nx\?<NP?<A?<NAXNuNV?BG`0RG nf0S@`>N^NuNV/.?< N *\Hl?< N \?<N TBgNTN^NuNVH&n/ NX> Gm Gg Go`H|0 +H|0A+H|0 +H|0A Gf +H|0 +H|0A`BD Fm* Fn" Em E;n Dm D;o`20@@2AA2AA??<-N (X f 0<`Hl?< N \0<`LN^NuNVH&n/ NX> Gg`H|0 +H|0A|P+H|0 +H|0A+H|0 +H|0A Dm* Dwn" Fm F n Em Eo`.0@@2AAE??<+N8X f 0<`Hl@?< N\0<`LN^NuNVHAn-HHl?< N\?<NT-@ .=@ .=@0.| @gH0.@| @g6?.?<+NX f?.?<-NzX f`|Hl?< NX\Hn?< NJ\.HABA T/N*XJ@fHl?< N\Hn?< N \.HABA T/NXJ@f?<*NTH/?<,NT" HЁ-@/.?<N\Hl?< N\?<NT=@?.Bg?<HnBg?<NB g HlN,X2<.H|.H|A=@.H|=@<<.H|=F.H|n.H|=@2<.H|.H|A=@2<.H|.H|A=@2<.H|.H|A=@2<.H|.H|A=@2<.H|.H|A=@:< 0H:B?< NH\-@ |.&P/.?< N.\ -@ n0ng HlNX n hADf Hl$NX n1|AD n1n0. n1@ n1E n1n0.F n1@ 0< nhF n1@ Hl4?< N\0.H0.@?.??/ Bg?<N$ g /.NXE><2.F0<2HHA>`SG0 KH|g0.H2.0>:Hŋ02.FAo:.F?.??/ Bg?<N g /.NrXHlH?< N\B`R PmLN^NuNuNV?.NT nn0<`?.?<>NPX9@g0<`0<N^NuNV?.?<LN*XN^NuNV/ AB&H`0+ |g/ N*X AB"Ҽm?.NT&_N^NuNV/ &n/ NDXJ@g0<`00+ |g /+NXBk ?+ N,TJ@g0<`0<&_N^NuNVH&n0+ |f0<`.0+ |gV0+ |f0<`v0+ |g?<B?+ NhP?/+?+ NP @f0<`@k 0Hѫ`$ k o?<0+D@H/?+ NP'@&Bk0<LN^NuNV nl <`?.?./. ?<BN -@ m .`?<?.B?<BN| -@?<?.B?<BNb -@ nf .Ю -@ `$ nf .Ю -@ `0.g <`b . oHn . /?.?<@N Bg?./. ?<BN ]|9@g <` `Bl . N^NuNV/.?<IN\N^NuNVBn`&0.A0nf0.ABPRn nLmN^NuNV?>.=|0A"HPgXHf0(>N^Nu)I)J)_NM"l$l/,Nu)I)J)_NN"l$l/,Nu)I)J)_NA"l$l/,NuNV`4 nH| f?< ?<NX nRH??<NX0. Sn J@fN^NuNV` nRH??<NX0. Sn J@fN^NuNV` nRH??<NRX0. Sn J@fN^NuNVH0&n $KA|-H nf?./ N4\=n`L nf?./ N`\=n`. nf?./ Np\=n`-KBn?.NZTJ@f`H| f |o@ H-@/./.?.?<@N| 9@Hg0<`0,n/./<?.?<@NF 9@ @g0<`tRnR -@`R ned` 6.ƼЃ$@ H-@/./.?.?<@N 9@Hg0<`0,nBl0.L N^NuCB"2"2 2"2"2"22"2"2"22"2x9|@Nu Hit any key Illegal time (bad format or out of range) Illegal date (bad format or out of range) Error reading disk!E AUTODISK by Moshe Braner Enter the time (hhmm[ss]): Enter the date (yymmdd): Error reading boot sector! Disk and RAMdisk incompatible! Old RAM disk! Copying data... AUTODISK finished, no errors CON:AUX:PRT: fputs(name,stdout); } else error("path name too long"); } else { /* resident co.  t\..  tMEDRZ2 PRGt ]`> ?<NNT g*?</?<NN\BgNAwp"3DU .g; #ifdef DEBUG printf("lookup_target: %s\n",name); #endif for ( targ = target_list; targ ; targ = targ->next) { #ifdef DEBUG printf(" %8lx %s \n",targ,targ->file->fname); #endif if (strcmp (name, targ->file->fname) == 0) break; }; #ifdef DEBUG printf("\n lookup_target returns %lx\n",targ); #endif return targ; } breakout_symbols (cmdlinptr) char **cmdlinptr; { char *cmdlin = *cmdlinptr, *cmd = talloc(LINESIZE+100); symbptr sym; char symcmp[SYMLEN]; int i, paren, cmdptr; #ifdef DEBUG printf("breakout_symbols (\"%s\")\n", cmdlin); #endif /* this routine doesn't check for overflow of the line ! */ strcpy ( cmd, ""); cmdptr = 0; while (1) { while (*cmdlin != '$' && *cmdlin != EOS) { if (cmdptr >= LINESIZE) error ("Line too long after symbol substitution"); cmd[cmdptr++] = *cmdlin++; } i ST 77 - 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! , symcmp ); assert (sym->magic == SYMBOL_MAGIC); if (strcmp (sym->token, symcmp) == 0) break; } strcpy ( cmd + cmdptr , sym->value ); cmdptr = strlen ( cmd ) ; } free(*cmdlinptr); *cmdlinptr = strperm(cmd); free(cmd); #ifdef DEBUG printf ("breakout_symbols returning (\"%s\")\n", *cmdlinptr); #endif } strlwr(p) char *p; { while (*p) { *p= toas