Rez 103
by: Tim Swihart

This time around, we'll look at Parts 2.5 and 3 of the resource tutorial.
We're taking on two parts instead of the traditional one like Rez 101 and
Rez 102 covered since both Part 2.5 and Part 3 are fairly short.  


Part 2.5
The quick summary of Part 2.5 is that it's simply Part 2 with an event loop 
added.  If you're totally lost as to what an event loop is then you're going
to have a VERY hard time writing desktop applications and really should get 
your hands on "Programmer's Introduction to the Apple IIGS" from Addison-
Wesley (ISBN #: 0-201-17745-5).  That book explains in full detail what is 
required to create a desktop application for the IIGS.  Among the many 
concepts it explains are event loops.  To refresh those of you who are 
vaguely familiar with event loops, that's where the action is in a desktop 
application!

Unlike the apps of many older CPU's, desktop applications allow the user to 
be in control instead of the computer.  What's the difference?  All menu
items are always handy instead of having to go into a menu mode and from
there navigate through several submenus to get things done.  Desktop 
applications therefore can't make a lot of assumptions that text-screen apps 
generally do.

To cope with this difference, the application spends its spare time in an 
event loop.  That's just a small loop of code that waits for things to happen 
(such as mouse clicks, key presses, etc) and executes various pieces of code 
to cope with each of the different types of events.

To keep things simple, Part 2.5 the event loop I added only handles three 
types of events,  They are:  wInGoAway (meaning that the user clicked in 
the close box of an open window), wInSpecial, and wInMenuBar (the user 
clicked the mouse on the menu bar - we need to open the menus for the user 
and let them pick something).  All other events are either ignored or handled 
for us by TaskMaster.


By Who?
TaskMaster is an extension to the Window Manager in the IIGS Toolbox that 
ALL desktop computers should have (but they don't!).  TaskMaster will 
handle all of the "generic" events that it can (windows being moved, 
windows being zoomed, scroll bars being clicked, update events, etc.).  
Having TaskMaster around makes writing desktop applications a LOT easier.

TaskMaster is the big reason that my event loop only cares about a couple of 
events and yet still fully supports NDA's, updates the window's contents 
correctly, can cut/copy/paste, etc.  Without TaskMaster, you would have to 
write the code to do all that yourself.  No doubt you'd have a bug or two or 
would simply not have some of the functionality that your users expect.  So, 
don't call "GetNextEvent" to find out what your app needs to do - call 
"TaskMaster" instead and you'll have a MUCH better app as a result!

More details on TaskMaster can be found in "Programmer's Introduction to 
the IIGS", and Volumes 2 and 3 of the IIGS Toolbox Reference manual.  All of 
these books should be in your library if you're serious about writing desktop 
software for the IIGS.  You should also read them - just having them in your 
library doesn't do much good if you never read them...  <grin>


How Did You Do It?
In order to add the event loop to the application, I had to create several new 
procedures.  The first one is named "do_main_event" and is called just after 
we start up the tools.  This is where the application's "heart" is - every
time the app finishes handling an event, it comes back to the event loop to
see if there are other events waiting to be handled.

Inside of "do_main_event", we call TaskMaster to find out if there are any 
events waiting.  TaskMaster requires two things as input - a mask that tells 
it which events you want to know about (each bit controls a different event) 
and an event record to put the detailed information about the event into.  
TaskMaster also returns an event code.  We use the event code as part of a 
switch statement ("case" statement if you're using Pascal) to determine what 
to do with the task code.  We only get task codes for things that TaskMaster 
couldn't handle.

If a user clicks in the close box of a window, we don't really care - this app 
has no windows of its own yet, and TaskMaster will close NDA windows for 
us.

We really only need to watch for the user clicking in the menu bar.  If he 
does, we call "do_menu_events".  This is one of the new procedures added 
since Part 2.  "do_menu_events" figures out which menu item the user 
selected.  We use another switch (case for Pascal folks) statement to handle 
all of the different menu items.  Since we're smart about how we program 
and used constants to represent each menu item, it's very easy to see how 
the switch statement for the menu items works - it simply transfers control 
to a different subroutine for each menu items.

Most of the menu items actually result in nothing happening.  The only 
"active" menu items in Part 2.5 are the About box (do_show_about gets 
called) and Quit (do_quit_app gets called).  Part 3 adds a window to the app 
and Part 3.2 adds an AlertWindow for the About box.  For now, selecting the 
About box menu item will result in SysBeep being called.  When you're 
creating an application for the first time, it's worth spending some extra time 
being certain that your menu items all work correctly (to catch tiny errors in 
your source).  The easiest way to do this is to have each items bring up a 
dialog box saying what item you just picked.  It only takes a couple lines of 
code and can catch some pretty silly errors that would slip by otherwise.

The call to TaskMaster is done inside a loop (hence the origin of the name 
"event loop") and we stay in that loop until the user picks "Quit" from the 
menu.  Picking "Quit" causes the procedure "do_quit_app" to be called.  
Remember, "do_quit_app" is several levels deep inside the app.  We can't 
just quit from here - the stack would be messed up, the tools have to be 
shutdown, etc.  We need a graceful way to tell the event loop to quit running 
so control will be returned to the main application where we've already seen 
(in the earlier parts) that things are shutdown nicely.


Remember Your Booleans!
Think hard now - remember the boolean from Part 1 that we set during the 
"do_init_rom" routine?  Remember that it was used to control whether or not 
the app should be run?  We're going to use it again!  (in case you don't 
remember, I'm referring to "gPunt") If you look closely at the start of the 
event loop ("while(!gPunt)") you'll see that as long as gPunt is set to False, 
the event loop will keep running and the app won't shut down.

So, all we really need to do in "do_quit_app" is change the value of gPunt to 
indicate that it's time to shut things down.  That causes the event loop to 
terminate and returns control to the main app - letting the tools be shut 
down and then the app itself is done.


Why "do_quit_app"?
Seems a little wasteful to have a procedure that does nothing more than set 
a boolean!  Why not just set it on the line where we called "do_quit_app" 
instead?  Simple - what happens in a complete app if you've made changes 
to the current document and you pick "Quit" from the menu?  The app 
prompts you to save the changes!  Where would that come from?  Why not 
from a routine like "do_quit_app" that's already set up to handle 
EVERYTHING that's needed to prepare the app for being shut down.

What if the user is prompted for saving their changes and picks "cancel"?  
How would "do_quit_app" handle that?  Simple - just leave the boolean 
"gPunt" set to False and the event loop would NOT terminate.  Everything 
would just keep running as though the user never picked "Quit" at all.  Nice, 
eh?

Structuring your program carefully results in an elegant design and an easy 
to extend application.  If we'd just set the boolean right there in the switch 
statement, then we'd have to squeeze in a lot of extra code later when the 
app needed to handle other things before quitting.  That would destroy the 
readability of our source code!  The easier it is to read your source code, the 
easier it is to debug it!


Wrap-up 2.5:
So, Part 2.5 is pretty simple.  Yet, adding the event loop turned our app into 
one that supports NDA's, and "feels" like a desktop application.  Prior to Part 
2.5 the app just started up and shut down - never giving you a chance to do 
anything other than watch.  TaskMaster handles all of the things necessary 
to make NDA's work for us (so, NDA support is essentially free) and we 
really only needed to implement ONE menu item at this stage (the "Quit" 
item).  Nothing new in the way of resources was added to go from Part 2 to 
Part 2.5.  Part 2.5 was named that because it's about halfway between Part 2 
and Part 3.


Part 3:
This part adds a window with a couple of controls in it to the application.  
Using the controls in the window is very simple since we're using 
TaskMaster.  Someday, when you're bored and have a LOT of spare time, try 
implement just up to Part 3 WITHOUT using TaskMaster.  It's possible, but 
it's a LOT of effort!

The window is stored as a resource (naturally) and the controls in are also 
resources.  We could make the controls automatically be installed in the 
window when it's open, but I did it in a separate step for two reasons.  The 
first one is to show you how to do it (using NewControl2) and the second 
reason is to avoid a bug in the System Disk 5.0.2 Window Manager that's 
documented in IIGS Tech Note #82 (the tech notes are available online, so I 
won't repeat it here).

To make the window show up, we have to add a couple of new routines to 
our application.  The first one is "do_make_window" and it's called from 
within "main" so that we have a window in our application BEFORE the event 
loop gets called.  We create the actual window by making ONE tool call -> 
"NewWindow2".  NewWindow2 needs quite a bit of information in order to 
do its stuff.  First off, it needs to if you want to use a custom title for
the window (we want to use the one in the resource, so put a NIL here).  Next, 
NewWindow2 needs to know if it should use a custom refCon or not - again, 
we want the default, so put a NIL here.  

TaskMaster will call our window update routine IF we put a pointer to that 
routine in the third field of the NewWindow2 call.  Always do this if at all 
possible - it will make your windows behave much nicer than if you try to 
manually manage all update events that come along.

The fourth item to pass NewWindow2 is a NIL since we do NOT want to use a 
custom def proc for our window.  If you're not sure what a def proc is, that's 
what draws the window itself (the frame, title, etc).  If you wanted to have a 
round window, you would have to write a custom def proc and set this field 
to point to the def proc.

Fifth, NewWindow2 needs to know what kind of reference it's going to get in 
the sixth field.  References were described in Rez 102, so go back and re-
read about them if you forgot what a reference is!  Since we're using 
resources, we put the resource ID of our window template in the sixth field.

The final field of the NewWindow2 call tells the Window Manager what 
template it should use to create the window.  There are two different 
standard resource templates that can be used to create windows from 
resources.  We're using the first one.  If you want to explore the other one, 
go right ahead - it's documented in Apple IIGS Toolbox Reference manual, 
volume 3.

When NewWindow2 is done, it will return a pointer to the newly created 
GrafPort.  We need that point for the next couple of tool calls, but otherwise 
we can toss it out (i.e.:  it doesn't have to be kept in a global variable).
If we need it back, we can get it by using tool calls such as FrontWindow, 
GetNextWindow, GetFirstWindow, etc.


Watch Out For The Bug!
The bug I mentioned earlier is simply that the GrafPort isn't set right in 
certain cases before controls are installed in a window.  To avoid it, we 
simply set the current port (using the tool call "SetPort") to the newly 
created window's GrafPort (which NewWindow2 returned a couple of 
paragraphs ago).

Now that the port is set right, we can add our controls to the window's 
contents by calling "NewControl2".  NewControl2 loads controls from the 
resource fork and attaches them to a given window.  If you want to put only 
one or tons of controls in a window, NewControl2 is for you!


Elaborate Please...
NewControl2 wants the GrafPort of the window it's supposed to put controls 
in, it wants to know what kind of reference it's going to get, and it wants
the actual reference (resource ID of the control list in our case).

NewControl2 looks for a list of controls and installs all of them with ONE tool 
call.  To keep our app simple, we're adding only two controls - a push button 
(pretty common) and a TextEdit field (to show the power of TaskMaster).


TaskMaster Again?
Yep - TaskMaster makes it VERY easy to track controls in a window.  Without
TaskMaster, you would get a mouseDown event (telling you the mouse was
clicked).  You'd have to find out WHERE it was clicked (in the menu bar?  in
the window? etc).  If it was clicked in the window's content area, then you'd
have to call FindControl (to see if it was clicked over a control such as a
push button, radio button, etc).  If it was clicked over a control, you have
to call TrackControl to be sure the mouse is over the same control when it's
released!  Then you have to do whatever action that controls calls for.  UGH!!!

With TaskMaster, you're told AFTER the mouse comes up over the control 
that you need to do whatever action that control calls for.  No more figuring 
out where the mouse went down at!  No more FindControl/TrackControl!  
YAY!

In the case of the TextEdit field, Cut/Copy/Paste/Clear/Undo are all done by 
TaskMaster!  Look closely at the do_menu_events routine and you'll see that 
our application does NOTHING in response to the user picking Undo, Cut, 
Copy, Paste, or Clear from the menus!  Sure saves a lot of code, eh?

Like I said, TaskMaster is your friend - learn to let it do what it does best 
and spend your effort on the stuff that's unique to the application you're 
writing.


Drawing The Contents
TaskMaster will call our window content drawing routine for us as soon as 
there's some spare time.  This spare time is known as "null events" and it 
occurs when the computer gets bored (because it's waiting on the user to do 
something that generates an event).  Since all of our contents this time 
around are controls, we can draw then with ONE tool call!

Before we call that one tool, we have to know what GrafPort to draw them in, 
so we call GetPort to find that out.  See, tool call names are pretty easy to 
anticipate - that makes desktop programming MUCH easier!

The one call that draws it all is "DrawControls".  We tell it what GrafPort to 
draw in and it figures out what all the controls are, loads them from the 
resource fork if needed, and draws them!  Nice, simple, compact, complete, 
easy!  The way it should be.

Remember - the application itself doesn't call the content drawing procedure, 
TaskMaster does!  So if you're digging through the source code and can't 
figure out who's calling these routine, so you want to throw it out - think 
again!


Play With It!
Run the application that results from compiling the source code to rTutor 
Part 3.  Try dragging the window around - naturally it works, but there's no 
code in our application to do that.  TaskMaster does the dragging for us 
(without TaskMaster, we'd have to do it ourselves - UGH!)  Try cutting and 
pasting from the TextEdit field in the window.  It works like you'd expect - 
thanks to TaskMaster.  Try clicking on the push button - works like it should 
thanks to TaskMaster.

If you haven't caught on by now (I know, some of the hints were pretty 
subtle), then I'll be more explicit ->  Use TaskMaster whenever possible!  
Your application will "feel" better to the user!  Your application will be able 
to do more of the "little things" that users expect.  TaskMaster is part of the 
System, so why waste disk space duplicating its effort within your 
application?  Just use TaskMaster and all will be right with the world...


Wrap Up Time:
OK, enough reading - get to it!  Spend some time studying the source code.  
Look up each of the tool calls that you're not familiar with (you'll need Apple 
IIGS Toolbox Reference Volume 3 for the calls that deal with resources).  If 
you're hopelessly lost, post a plea for help on either GEnie or America 
Online's developer forums.  On GEnie, post in A2Pro.  On America Online, post 
in ADV.  If you're on another network, find a way to get to where I am or 
post there - I'm not the only one who can field questions on resources.  Lots 
of folks can deal with it just fine (they're just too lazy to write detailed
docs like these).  <grin>


If you don't have the following manuals yet, go get them!  They're all 
Addison-Wesley books, so your local bookstore (B. Dalton's, Walden's, etc) 
should either have them or can order them if you bring them the ISBN 
number:


Book Title                                      ISBN Number
-------------------------------------------------------------
Programmer's Introduction to the Apple IIGS     0-201-17745-5
Apple IIGS Toolbox Reference, Volume 3          0-201-55019-9
Apple IIGS Toolbox Reference, Volume 2          0-201-17747-1
Apple IIGS Toolbox Reference, Volume 1          0-201-17746-3


Enjoy!
Tim S.
