More Event Trapping
Volume Number: 6
Issue Number: 6
Column Tag: XCMD Corner
More on Event Trapping 
By Donald Koscheka, Ernst & Young, MacTutor Contributing Editor
Note: Source code files accompanying article are located on MacTech CD-ROM orsource code disks.
This issue marks the second anniversary of this column and I thought it might be
a good time to share my Macintosh wish list with you. My list doesn’t contain the
usual items that you might expect of a programmer - a faster CPU, more RAM, larger
disk space because I know these things are coming; our technology just keeps getting
better and better.
I have only one wish and it is a rather strange one: I wish that the Macintosh will
once again become a personal computer in the manner of its great ancestor, the Apple
][. Because of the Apple ][, we no longer think of computers as threatening monoliths,
rather they are regarded as benign if not beneficial tools of civilization.
The Apple ][ accomplished this in two ways; it was affordable and “user
friendly”. Affordability, it turns out, is the keystone to user friendliness. Apple
wanted the Apple ][ to be a computer for the masses, not only would the hardware have
to be cheap, but also the software, the training and the service. Every aspect of the
Apple ][ experience was intended to be “friendly” -- from locally accessible support
groups to the schematics that were published in the manuals so that real dilettantes
could service their own equipment.
By contrast, the Macintosh has become the “Institutional Apple”. Apple no
longer seems concerned with the individual successes of its users and everything about
the Macintosh seems geared towards corporate sales and megalithic installations. The
“user-friendly” infrastructure that Apple so carefully fostered with the Apple ][ has
evaporated. Let me explain with a story:
Recently, my Mac IIcx had a main logic board failure because one of those little
jumpers on the board came loose (how that happened remains an endless source of
speculation -- the computer is never moved about). I was shocked to discover that a
new main logic board (we used to call them “mother boards”) would cost me $600 and
that’s with a trade-in allowance!
While the average institution may not balk at a $600 repair cost, I could not help
but feel that Apple’s service policy borders on usury. Think about it. My computer
fails because its designers did not anticipate a particular failure mode. Although the
machine is barely six months old, the factory does not warrant such repairs. My
computer is down, I’m out and no where can I find a sympathetic ear, the entire system
is simply indifferent to the needs of one despondent user.
What the Macintosh has lost is that “sense of peopleness” that we had with the
Apple ][. I know because I worked in a computer store during the heyday of the Apple
][. Once, a despondent customer called to explain that he had installed a card
“backwards” in his Apple and it shorted something on the motherboard (we call them
main logic boards now) blowing a hole through the board! I quoted him the price of a
new motherboard (about $400 plus trade-in) and that that poor man just broke down
in tears. I told him to bring the computer in and I’d see what I could do. Being an
electrical engineer, and knowing that the Apple ][ was a simple two-layer board, I
methodically re-wired the blown out traces with some ordinary hook-up wire.
Remarkably, the board worked (no chips were blown). I called the customer and told
him the good news - that the damage was just cosmetic and that, while looking like a
high school science project, the board worked fine. The cost: $50.00; the customer:
ecstatic.
Those days are gone. I called half a dozen computer stores and they all quoted me
between $600 and $700. No one suggested my bringing it in, that they would have a
look at it. No one suggested that I might get a discount, even after asking for one. The
attitude was just so institutional - your computer is broken and now you’re going to
have to pay to get it working again. Some maintenance maven at Apple is sure to say
that this situation cannot be helped - that the Macintosh has just gotten too complicated
for the ordinary service technician and that this is a small price to pay for
technological excellence. I say hogwash (but I mean worse) - if the Mac is that
complicated, then make it simpler to repair. And if ordinary service technicians can’t
fix it, then get extraordinary ones. Heavens knows, we’re paying them enough;
computer repair people charge almost twice as much as auto mechanics. Yet the latter
have to know more about cars to do a good job, and few car repairs last weeks as is
often the case with computers.
I can’t help but feel bitter about this whole experience, mainly because Apple
seems so indifferent to its “personal” users. Perhaps this insensitivity comes with
being a multi-billion dollar company. Too bad, I somehow wanted to believe that Apple
would be different.
My story does have a happy ending in that I did find a sympathetic technician,
Mike Cerrone of the Computer Factory, Stamford, CT, who worked with me in isolating
the problem to the little jumper. In so doing, he saved me nearly $600 in repairs.
People like Mike are what this business is all about and I hope his bedside manner
comes back into style.
More on Event Trapping
Two months ago I introduced the concept of intercepting the event trap in
Hypercard. This technique could be used to add custom controls, external windows, key
click filters and the like to a stack. I seem to have struck a nerve among the Hypercard
programming community because several readers have “wired” asking for more
information. I will show you how to maintain lists in windows that are external to
Hypercard. This work is based on the March ’90 issue and you may want to refer back
to it.
This XCMD contains a lot of source code, so I’m just publishing the highlights, the
rest of the code is available from MacTutor.
Listing 1 contains the main code for the XCMD, “floating list.c”. We accept
commands of the form:
`1
get FloatingList( COMMAND, )
where < window, parameters> are optional and COMMAND is one of the following:
INSTALL: installs the patch to get next event and initialize the external window
system. You should invoke this command only once during a stack session.
REMOVE: removes the patch to getnext event. call only when you are done with the
windowing system.
OPEN: open a new window and add the items passed to the list. The window is
opened with the name provided in the window parameter. This window will have a list
associated with it. Open takes several parameters:
Parameter 3: the rectangle for this window
Parameter 4: the window type.
Parameter 5: 1 if you want the window visible, 0 otherwise.
Parameter 6: data to put into the list. Currently lists are implemented as single
column multi-row lists but you can modify the code in do_open() to fit your needs
(see listing 2, “Window Commands.c” ).
`2
put FloatingList( “OPEN”, windowname, “50 10 300 210”,
“DOCUMENT”, 1, card field “your list name” ) into it
The window types and command names are stored in string# resources with the
following format: Each line contains the name followed by a comma followed by the
number (token) that codes for that name. For example, the install command is coded
as: Install, 0. The source disk contains a sample stack with these resources.
CLOSE: close a window by name. The window must have been previously opened
using the window command.
SHOW: show the window whose name is passed.
HIDE: hide the window whose name is passed.
I’ve left room for three more commands: add, delete and clear to allow you to add
an item to the list, to delete an item and to clear an item. These will be the subject of a
future column. For now, you pass a complete list to window on open.
Each command is handled by a routine in “Window Commands.c” (listing 2).
This system allows you to open windows external to Hypercard and to track
events within those windows. This is a powerful but easy-to-use system. How it’s
done is really quite simple as I will explain.
The information for the external windows is stored off a structure called
“wdGlobals” for window globals. This structure is allocated and intialized as a
non-relocatable block in the system heap by do_init() (see listing 2). If the block can
be allocated, we then load the event trap patch in as a resource of type ‘EXEC’ and then
move it to the system heap. At this point the event trap is ready to start responding to
events outside of the Hypercard window.
External windows are created using the Open command and the code is
implemented in the routine do_open() (the parser that this routine uses was covered
in the March issue). Once allocated, windows are referenced via a structure that I call
the ‘post’ named in honor of the post-it note, the greatest invention since cellophane
tape.
The structure of the post record is depicted in figure 1. The doubly-linked list
allows us to have as many windows open as memory will allow. Note that I allocate the
window memory in the post structure in the field “window store”. This is so that the
window record gets allocated in the system heap out of Hypercard’s way. The
windowPtr is a standard window manager structure which should point to the window
store sub record. List is a standard list handle. Name is the name of the window (called
out separately for convenience - this field is redundant with the name field in the
window record itself). Is_color determines whether this is a color window and id is
currently not used but you can use it to assign unique ids to each window.
Figure 1. Structure of the Post record.
The post structure gets stored in the list field of the wdGlobals structure. Now
this structure acts as our global memory since we need to access the structure from
both the XCMD and from the event patch itself. In March, I showed you how to save
such data in the system resource file. I’ve since had a change of heart and now choose to
store the structure in one of the extra fields in the trap header. The routine,
SetWindowHeader (Listing 4) places our record at byte offset 10 (decimal) from the
start of the patch. If our patch was patched, offset 10 may not point to the correct
structure in the future. For this reason, I set the signature of the patch to ‘isme’.
Before accessing the window globals, I check to make sure that the patch has the
correct signature. If so, then the window globals at offset 10 are valid. This may seem
klunky but it’s a heck of a lot better than messing with the system resource file which
seems to have a mind of its own.
The patch was described in a previous article so I won’t go into great detail. Note
that for most purposes, it performs as a standard event loop. The main difference is
that we first look at the event to see if it belongs to one of the “post” notes. If so, we
handle it and inform Hypercard that no event occurred on the last invocation of
getnext event. If the event is not for one of our windows, we pass it back to Hypercard
and let it decide what to do with it. The code in listing 2 is a “tail patch” in that we
first invoke the original trap and then take some sort of action. While many
programmers frown on tail-patches, this one does work and we do need to know what
the most recent event is in order to process it.
Happy hacking and I hope you find this article interesting. We will explore this
area further in future issues.
Listing 1:
/********************************/
/* File: Floating List.c */
/* */
/* Window with drawing commands */
/* */
/* Paramters: */
/* param0 = command */
/* param1 = name of the window */
/* param2..n= window parameters */
/* INSTALL */
/* OPEN */
/* CLOSE */
/* SHOW */
/* HIDE */
/* ADDITEM */
/* DELETEITEM */
/* CLEAR */
/* REMOVE */
/* */
/* Out: */
/* Responds to the given command*/
/* ---------------------------- */
/* © 1989 Donald Koscheka */
/* All Rights Reserved */
/********************************/
#include “WindowUtils.h”
#include
#include “WindowCommands.h”
pascal void main( paramPtr )
XCmdBlockPtr paramPtr;
{
wdGlobalsPtr windP;
short theToken;
Str31 command;
Str31 str;
char name[256];
/*** empty is the default answer ***/
paramPtr->returnValue = 0L;
/*** (1) Parse for the command type ***/
theToken = matchToken( paramPtr->params[0], 1020 );