home *** CD-ROM | disk | FTP | other *** search
Text File | 1985-06-03 | 28.8 KB | 713 lines | [TEXT/ttxt] |
- {%describe 'a Skeleton demo program'}
- {$X-} {Turn automatic run-time stack expansion off - it's a Lisa concept.}
- {$R-} {Turn off range checking}
-
- PROGRAM Skel;
-
- { By Steve Maker
- Courseware Development Group
- Kiewit Computation Center
- Dartmouth College
- Hanover, N.H. 03755
-
-
- Copyright and support notice:
- SKEL may be copied and used by anyone, but not sold.
- Please send comments and significant changes back to me so that I
- may incorporate them into future versions. However, please seek
- support from your regular support channels; SKEL is distributed as-is.
-
- Why SKEL?
- Skel is a skeleton demo program. Its purpose is to illustrate
- in a clear fashion, isolated from any particular application,
- the basic code for handling a simple Macintosh user interface.
-
- It strives to be correct as far as it goes, without many short-cuts
- that would lead to trouble in larger applications.
-
- I think of SKEL as a program that does nothing, but does it well.
-
- What does SKEL do?
- It handles:
- Events, carefully handling only those which are its
- business, and passing the others on to their
- respective handlers.
- A Window, which is filled with Dark Gray, and can be
- activated or inactivated, updated, dragged
- and grown but NOT scrolled or closed.
- Menus, including the Apple Menu. An "About Skel" menu entry
- is provided. A File menu offers Rattle and Frighten,
- which just invoke dialog boxes, and Quit. Command
- key equivalents are supported.
- The Desk Accessories, carefully supported in the Apple menu,
- and correctly meshed with the other features.
- NOT supported are Undo, Cut, Copy, Paste and Clear
- for desk accessories.
- A Modal Dialog Box, used to communicate with the user.
- Special icons for the application and its related files.
- The Finder information (in the resource file).
-
- In accordance with Macintosh guidelines, everything possible is
- kept in the resource file: window description, menus,
- dialog specification, and the "About Skel" and other strings.
-
- In addition, the resource file handles the Bundle, File References,
- and Icons that determine what Skel's icon looks like, and other
- information for the Finder.
-
- How do I use SKEL?
- Study it. Modify it to test your knowledge. Steal working
- pieces of code for your own programs. Beat on it. Subject
- it to cruel and unusual experiments. Pay heed to its warnings.
-
- What do I study first in SKEL?
- Initially you should ignore several sections of SKEL, and the calls
- made to them. I recommend X-ing them out in your listing.
- The sections to ignore on the first round of study are:
-
- Report: ignore the implementation
- RunDeskAcc: ignore all of it
- SetUpMemory: ignore all of it
- DrawWindow: ignore the scroll bar and grow icon handling
- ReSize: ignore all of it
- DoCommand: ignore the Desk Accessory handling in the Apple Menu
- MainEventLoop:
- MouseDown handling: ignore inSysWindow, inDrag, inGrow
- keyDown, autoKey handling: ignore this.
-
- In the resource definition file, SKELR:
- Finder information (offset by asterisks):
- ignore this whole section, icons and all.
-
- What should I read in Inside Macintosh?
- You should read the following sections of Inside Macintosh,
- in the order given. At first, just lightly skim the sections
- with parenthesized names. Read the others in some depth. Read
- the starred (*) ones in great detail. Eventually, you will have
- read all sections thoroughly, and many many times, I promise you.
-
- To start:
- * Inside Macintosh: A Road Map
- (User Interface Guidelines)
- Structure of a Macintosh Application
- * Putting Together a Macintosh Application
-
- Then, (low-level sections are listed first):
- * Memory Mgr Intro
- (Memory Mgr)
- * Resource Mgr (through "Using the Resource Mgr")
- * QuickDraw
- (Desk Mgr)
- * Event Mgr (through "Event Mgr routines")
- Window Mgr
- Menu Mgr
- (Dialog Mgr)
-
- How do I get SKEL to run?
- Insert a Macintosh-formatted diskette into your Lisa.
- Then run one of the following exec files:
-
- <example/VanillaExec(example/Skel,'SKEL')
- or
- <example/Exec(example/Skel,,,'SKEL')
-
-
- What are the funny % describes for?
- They are formatting commands for a Pascal formatter used at Dartmouth
- on Lisa Pascal code, for producing a readable listing.
-
- What is the history of SKEL?
- v1.0 July 14, 1984 sm: major revision of earlier version
- Sept 30, 1984 sm: used \14 for apple symbol in res. file,
- bracketed OpenDeskAcc with Get and SetPort,
- Oct 11, 1984 sm: changed FREF, BNDL resources from HEXA
- to readable,
- nested some routines in SKEL,
- added constants for FILE menu items,
- v2.0 Nov 12, 1984 sm: made resources pre-loaded and/or purgeable,
- turned off range-checking,
- documented no Resume proc passed to InitDialogs,
- added SetUpMemory:
- calls MoreMasters, MaxApplZone,
- sets the NIL address to -1,
- lots of general memory doc.
- added a warning about passing doubly-
- dereferenced handles,
- removed en/disabling of Rattle and Frighten items,
- v2.1 Dec 4, 1984 sm: added menu key handling,
- put Rattle and Frighten strings in res file,
- rewrote intro documentation
-
- v2.2 Mar 6, 1985 sm: converted to % describes,
- fixed SKELX for both 2.0 and 3.0 workshop,
- set the event mask,
- v2.3 Apr 8, 1985 sm: fixed for Post-3.0 Pascal Workshop:
- (uses clause, Skelx),
- v2.4 May 9, 1985 sm: SkelR: changed ICN# = HEXA to ICN#, since RMaker
- now knows about icon lists, and pruned duplicate
- TYPE statements,
- made windowptr params in grow, update, and activate
- evt handling slightly more general,
- made the report box big enough for several lines
- of text (dialog mgr will handle word wrap),
- added windowptr params in DrawWindow, etc,
- added info to "About Skel",
- added RunDeskAcc to run a desk acc; it now checks
- memory carefully,
- cleaned up handling of About... and findWindow,
-
- What is left to do in Skel?
- - Support Edit menu, usable with desk accessories?
-
- }
-
- {%describe '(declarations)'}
-
-
- USES {$U-} {Turn off Lisa libraries}
- {$U Obj/MemTypes } MemTypes, {use type defs in MemTypes unit}
- {$U Obj/QuickDraw } QuickDraw, {Search "Obj/Quickdraw" for }
- {$U Obj/OSIntf } OSIntf, { the "QuickDraw" unit, etc.}
- {$U Obj/ToolIntf } ToolIntf,
-
- {these are not needed for SKEL, but may be useful later.}
- {$U Obj/PasLibIntf } PasLibIntf, {New Macintosh PasLib.}
- {$U Obj/PackIntf } PackIntf, {Packages}
- {$U Obj/SaneLib } Sane, {new Floating Point}
- {$U Obj/FixMath } FixMath, {fixed-pt math for graf3d}
- {$U Obj/Graf3D } Graf3D, {3D graphics}
- {$U Obj/MacPrint } MacPrint, {printing}
- {$U Obj/SpeechIntf } SpeechIntf; {Macintalk speech synthesis}
-
- CONST
- lastMenu = 2; { number of menus }
- appleMenu = 1; { menu ID for desk accessory menu }
- fileMenu = 2; { menu ID for File menu }
-
- iAbout = 1; {items in the Apple menu}
-
- iRattle = 1; {items in the File menu}
- iFrighten = 2;
- {--------}
- iQuit = 4;
-
- VAR
- screenPort: GrafPtr; {a port for the whole screen}
- myWindow: WindowPtr; {our one window}
- wRecord: WindowRecord; {storage for window record}
- dragRect: Rect; {rect to drag within}
- growRect: Rect; {bounds for the growth of the windows}
- myMenus: ARRAY [1..lastMenu] OF MenuHandle; {our menus}
-
-
- {%describe 'Print a string in dialog box'}
- {############################ Report #################################}
-
- { We put up a dialog box, show the string, and wait for user to hit OK.
-
- It's important not to pass Report a de-referenced handle;
- if Report were in another segment, loading it could cause a memory
- compaction; the de-referenced handle could become invalid. Watch out
- for this and similar nasties everywhere in your program.
- For more info, see the Memory Manager and the Segment Loader.
- }
-
- PROCEDURE Report(reportstr: str255);
- CONST RptBoxID = 257; {ID of our report dialog in resource file}
- rptText = 2; {Item # of dialog's report text}
-
- VAR
- itemhit: INTEGER; {which Item was clicked on (only OK avail)}
- ReportPtr: DialogPtr;
-
- BEGIN {Report}
-
- {set text to display}
- ParamText(reportStr, '', '', '');
-
- ReportPtr := getNewDialog(RptBoxID, NIL, pointer(-1)); {get from Resource file;
- NIL => use heap storage;
- -1 => make dlg frontmost}
- ModalDialog(NIL, itemHit); {carry out dialog;
- NIL => no FilterProc;
- return item Hit when done}
- DisposDialog(ReportPtr); {release storage and remove dialog from screen}
- END; {Report}
-
-
- {%describe 'run a desk accessory'}
- {############################ RunDeskAcc #################################}
-
- { Run the desk accessory with the passed menu item number
- (in the menu whose handle is passed).
- If we have any trouble, return FALSE, else TRUE.
- We must check that there is enough memory to do so,
- and save the port, since some da's may trash it.}
-
- FUNCTION RunDeskAcc(mHan: menuHandle; theItem: integer): boolean;
-
- CONST
- extraMem = 2000; {extra mem for desk acc to use}
-
- VAR
- refNum: integer;
- name: str255;
- savePort: grafPtr; {for saving current port in when opening a desk acc}
- DAHan: handle; {handle to the desk acc}
- junkPtr: Ptr; {just to alloc a ptr, then dispose}
-
- BEGIN {RunDeskAcc}
-
- {assume the worst}
- RunDeskAcc := false;
-
- {get name}
- GetItem(mHan, theItem, name);
-
- {reserve room for the DA, if necessary}
- setResLoad(false); {don't load it in}
- DAHan := GetNamedResource('DRVR', name); {get handle}
- setResLoad(true);
- if DAHan^ = NIL {if not in memory already, reserve room low in heap}
- then begin {reserve it; exit if not enough memory.
- ResrvMem just plain lies; it oftens returns
- "memFullErr" when there is really room. Thus we
- use NewPtr because it sets memError correctly.}
- junkPtr := NewPtr(extraMem + SizeResource(DAHan));
- if memError = memFullErr
- then exit(runDeskAcc)
- else disposPtr(junkPtr); {make room for the DA}
- end;
-
- {Run it; make sure port is preserved}
- getPort(savePort);
- refNum := OpenDeskAcc(name);
- setPort(savePort);
-
- {success}
- runDeskAcc := true;
-
- END; {RunDeskAcc}
-
-
- {%describe 'Once-only initialization for Skel'}
- {############################ SetUp #################################}
-
- { Initialize our program. It seems best to handle:
- Memory inits first, ToolBox inits second, then the program variables' inits.
- Note that the order of inits is important; see "Using the Dialog Manager"
- in the Dialog Mgr section.}
-
- { By the way...you want to make sure this is in the same segment as your main
- program (the blank segment) so that you don't create "holes" in memory.}
-
- PROCEDURE SetUp;
-
- CONST
- WindowID = 260; {Resource ID for my window}
-
- VAR
- screenRect: rect; {size of screen; could be machine-dependent}
-
-
- {%describe 'SetUps for handling memory'}
- {############################ SetUpMemory #################################}
- { This very important set of initializations can be left out of the first
- versions of a program. We are making sure that memory is laid out as
- we desire, with adequate protection against running out of memory, bad
- handles, etc.}
-
- Procedure SetUpMemory;
-
- CONST
- maxStackSize = 8192; {max size of stack; the heap gets the rest.
- Most appropriate for 128K Macs.}
-
- TYPE
- loMemPtr = ^longint; {a pointer to low memory locations}
-
- VAR
- nilPtr: loMemPtr; {will have value NIL}
- stackBasePtr: loMemPtr; {points to current stack base}
-
- BEGIN {SetUpMemory}
-
- {If you define a GrowZone function to handle bad memory problems,
- you should define it at the top level (not nested), and set it here.
- We don't.}
- (* SetGrowZone(@MyGrowZone);
- *)
-
- {Place a longint -1 (an odd and therefore illegal address) in the
- memory location that would be referenced by an accidentally-NIL
- handle, so the error will be caught at handle-reference time (as
- an Address error, ID=02) instead of later on.}
- nilPtr := NIL;
- nilPtr^ := -1;
-
- {If you needed to use an Application heap limit other than the default
- (which allows 8K for the stack), you'd set it here, possible using this
- technique of explicitly specifying the maximum stack size and allocating
- the rest to the heap. Should be independent of memory size. }
- stackBasePtr := loMemPtr($908); {CurStackBase from Tlasm/sysequ.text}
- SetApplLimit( pointer(stackBasePtr^ - maxStackSize) );
-
- {Expand the application heap zone to its maximum size, without purging
- any purgeable resources. This saves memory compactions and heap expansions later.}
- MaxApplZone;
-
- {get plenty of master pointers now; if we let the Memory Manager allocate
- them as needed, they'd form non-relocatable islands in the heap.}
- MoreMasters; MoreMasters; MoreMasters;
-
- {Here you might install bulwarks against running out of memory unexpectedly.
- One such (cheesy) technique is to here allocate a large handle, call it
- "CheeseBuf", which you can de-allocate in your GrowZone function, when
- you must obtain more memory to avoid a crash. While de-allocated,
- the program could prevent the user from doing anything requiring memory,
- and tell him he must discard windows or some such memory freeing action.
- Each time he does so, the program can try to re-allocate CheeseBuf; if it
- succeeds, the user can go on doing memory-eating operations.}
-
- END; {SetUpMemory}
-
-
- {%describe 'Once-only initialization for menus'}
- {############################ SetUpMenus #################################}
-
- { We read in all menus from the resource file, and install them,
- and all desk accessories (drivers).}
-
- PROCEDURE SetUpMenus;
-
- VAR
- i: INTEGER;
-
- BEGIN {SetUpMenus}
- for i := 1 to lastMenu do {get all my menus in}
- myMenus[i] := GetMenu(i); {use the fact that our menu ID's start at 1}
-
- AddResMenu(myMenus[appleMenu], 'DRVR'); {pull in all desk accessories }
-
- for i := 1 to lastMenu do
- InsertMenu(myMenus[i], 0); {insert menus; 0 => put at end}
-
- DrawMenuBar;
- END; {SetUpMenus }
-
-
- {%describe '(body of SetUp)'}
-
- BEGIN {SetUp}
-
- {init memory layout and protection}
- SetUpMemory;
-
- {init QuickDraw, and everybody else}
- InitGraf(@thePort);
- InitFonts;
- InitWindows;
- InitMenus;
- TEInit;
- InitDialogs(NIL); {NIL => no Restart proc; see Dialog Mgr and System Error Handler}
- InitCursor;
-
- {Init the system event mask, in case the previous program left it in
- a bad state. If you set it non-standard here, FIX IT BEFORE EXITING,
- because the Finder (1.1g) does NOT set it.}
- SetEventMask(everyEvent - keyUpMask); {standard setting}
-
- {Get the port which is the whole screen, to use when deactivating our window.
- This prevents the current grafPort pointer from ever dangling.}
- GetWMgrPort(screenPort); {get whole screen port that window mgr uses}
- SetPort(screenPort); {and start off with it}
-
- {get window: use wRecord storage. Port is set to that of the new window.
- GetNewWindow posts an update event for the new window,
- so it will be redrawn right away.}
- myWindow := GetNewWindow(windowID, @wRecord, POINTER(-1)); {-1 => frontmost window}
-
- {set up dragRect; we can drag the window within it}
- screenRect := screenBits.bounds; {don't assume screen size}
- {set drag rect to avoid menu bar, and keep at least 4 pixels on screen}
- SetRect(dragRect, 4, 24, screenRect.right-4, screenRect.bottom-4);
-
- {set up GrowRect, for limits on window growing}
- SetRect(growRect, 48, 14, screenRect.right-7, screenRect.bottom-7);
-
- {pull in and set up our menus}
- SetUpMenus;
-
- END; {SetUp}
-
-
- {%describe 'Redraw my window'}
- {############################ DrawWindow #################################}
-
- { We draw all the contents of our one window, myWindow. Note that this must
- include scroll bar areas and the grow icon; the Window Manager will NOT handle
- those for us. Since there are no scroll bars, we must erase the region they
- would be in. Echh.}
-
- PROCEDURE DrawWindow(aWindow: WindowPtr);
-
- VAR aRect: rect; {rectangle to erase}
-
- BEGIN {DrawWindow}
-
- {We only handle myWindow}
- if aWindow <> myWindow
- then exit(DrawWindow);
-
- {first, fill the window with dark gray; this fills scroll bars, too}
- FillRect(myWindow^.portRect, dkGray);
-
- {second, erase the scroll bars and draw the grow icon}
-
- {erase the horizontal scroll bar}
- SetRect(aRect, {cover the horizontal bar}
- myWindow^.portRect.left, myWindow^.portRect.bottom-15,
- myWindow^.portRect.right-15, myWindow^.portRect.bottom);
- FillRect(aRect, white); {fill with white}
-
- {erase the vertical scroll bar}
- SetRect(aRect, {cover the vertical bar}
- myWindow^.portRect.right-15, myWindow^.portRect.top,
- myWindow^.portRect.right, myWindow^.portRect.bottom-15);
- FillRect(aRect, white); {fill with white}
-
- DrawGrowIcon(myWindow); {draw the size box in the corner of the window}
-
- END; {DrawWindow}
-
-
- {%describe 'Update the contents of the given window'}
- {############################ UpdateWindow #################################}
-
- { This is our response to receipt of an update event for myWindow. Since the
- window is likely to be inactive, the current grafPort will be elsewhere. We
- must change it for drawing, yet leave it as it was.}
-
- PROCEDURE UpdateWindow(aWindow: WindowPtr);
-
- VAR
- savePort: GrafPtr; {to save and restore the old port}
-
- BEGIN {UpdateWindow}
-
- BeginUpdate(aWindow); {reset ClipRgn etc to only redraw what's necessary.}
-
- GetPort(savePort); {don't trash the port; we might be updating an inactive window}
- SetPort(aWindow); {work in the specified window}
-
- drawWindow(aWindow); {redraw contents of window}
-
- SetPort(savePort); {all nice and tidy as before}
-
- EndUpdate(aWindow);
-
- END; {UpdateWindow}
-
-
- {%describe 'Change the size of the given window'}
- {############################ ReSize #################################}
-
- { Called on a mouse-down in the grow box, this allows the user to change
- the size of the window. We change the size, then
- claim that the whole of the window contents is no longer validly drawn,
- because we're too lazy to do so for just the scroll bar regions
- that are actually subject to change. See the Window Manager.}
-
- PROCEDURE ReSize(a_window : WindowPtr; downPt: Point);
-
- VAR w, h : integer; {new width and height of the sized window}
- newSize : longint; {the new size}
-
- BEGIN {ReSize}
-
- newSize := GrowWindow(a_window, downPt, growRect); {find new size}
- w := LoWord(newSize); {find the width}
- h := HiWord(newSize); {find the height}
-
- SizeWindow(a_window, w, h, true); {change to the new window size}
-
- {place whole window into update region to be sure it all gets updated.
- This is more than is strictly necessary, but a lot easier than just
- invalidating the regions that actually may have changed.}
- InvalRect(a_window^.portRect);
-
- END; {ReSize}
-
-
- {%describe 'the main loop that handles events'}
- {############################ MainEventLoop #################################}
-
- { Brace yourself: here's where the action is. Most Mac programs just wait for events
- (as do we all), and then process them. There are two sorts of events: those directly
- initiated by the user, like key presses and mouse-downs, and those consequent events
- posted by the Event Manager, like update and activate events. The latter MUST be handled
- correctly and carefully. In particular, it's important for all events to make sure
- that the event occurred in or for the window you expect -- it's possible to get events
- which are not for one of our windows, despite GetNextEvent's return value. Similarly,
- be sure to check that the window it occured in is the active one, if it matters.
-
- A common mistake in handling update and activate events is in finding out which window
- they are for. It is "WindowPtr(myEvent.message)" that gives this information,
- NOT "whichWindow" (the WindowPtr returned by FindWindow). The latter pointer merely tells
- you what window the mouse was in at the time the event was posted -- completely irrelevant
- for Update and Activate events. Think it through carefully.}
-
- PROCEDURE MainEventLoop;
- VAR
- myEvent: EventRecord;
- whichWindow: WindowPtr; {points to window of MouseDown}
- userDone: boolean; {true when user wants to exit program}
-
-
- {%describe 'handle a command given through a menu selection'}
- {############################ DoCommand #################################}
-
- { We carry out the command indicated by mResult.
- If it was Quit, we return true, else false. Since the menu was highlighted by
- MenuSelect, we must finish by unhighlighting it to indicate we're done.}
-
- FUNCTION DoCommand(mResult: LongInt): boolean;
-
- CONST
- AboutSkelId = 1; {Resource ID of report strings}
- RattleID = 2;
- FrightenID = 3;
- InsuffMemID = 4;
-
- VAR
- theMenu, theItem: integer;
- aStr: str255; {a utility string}
-
- BEGIN {DoCommand}
- DoCommand := false; {assume Quit not selected}
- theMenu := HiWord(mResult); {get the menu selected}
- theItem := LoWord(mResult); {... and the item of that menu}
- CASE theMenu OF
-
- 0: ; {user made no selection; do nothing}
-
- appleMenu:
- CASE theItem OF
-
- iAbout: begin; {get string, and tell about Skel}
- aStr := GetString(AboutSkelId)^^; {see comment in Report}
- report(aStr);
- end;
-
- otherwise {run the indicated desk acc}
- if not runDeskAcc(myMenus[appleMenu], theItem)
- then begin {insuff memory; report it}
- aStr := GetString(InsuffMemID)^^;
- report(aStr);
- end;
-
- END; {AppleMenu case}
-
- fileMenu:
- CASE theItem OF
-
- iRattle: begin; {Rattle}
- aStr := GetString(RattleId)^^;
- report(aStr);
- end;
-
- iFrighten: begin; {Frighten}
- aStr := GetString(FrightenId)^^;
- report(aStr);
- end;
-
- iQuit: DoCommand := true {Quit}
-
- END; {fileMenu case}
-
- END; {menu case }
-
- HiliteMenu(0) {turn off hilighting on the menu just used}
-
- END; {DoCommand }
-
-
-
- {%describe '(body of MainEventLoop)'}
-
- BEGIN {MainEventLoop}
-
- FlushEvents(EveryEvent, 0); {discard leftover events}
-
- {get next event, and handle it appropriately, until user QUITs}
- userDone := false;
- REPEAT
-
- systemTask; {handle desk accessories}
-
- if GetNextEvent(everyEvent, myEvent) {get event; if for us...}
- then begin
- case myEvent.what of {handle each kind of event}
-
- mouseDown:
- begin
- {find what window the mouse went down in, and where in it}
- case FindWindow(myEvent.where, whichWindow) of
-
- inSysWindow: {handle the desk accessories}
- SystemClick(myEvent, whichWindow);
-
- inMenuBar: {handle the command}
- userDone := DoCommand(MenuSelect(myEvent.where));
-
- inDrag: {drag the window}
- DragWindow(whichWindow, myEvent.where, dragRect);
-
- inContent: {includes inGrow if window inactive. Activate window}
- if whichWindow = myWindow {moke sure it's for mine}
- then if whichWindow <> FrontWindow
- then SelectWindow(whichWindow); {make it active}
-
- inGrow: {window is already active; change its size}
- if whichWindow = myWindow {moke sure it's for mine}
- then ReSize(whichWindow, myEvent.where);
-
- inGoAway: {we don't have a GoAway region}
-
- end; {case on findWindow}
- end; {mouseDown handling}
-
- keyDown, autoKey: {if command key, pass the char to MenuKey}
- if BitAnd(myEvent.modifiers, cmdKey) <> 0
- then userDone := DoCommand(MenuKey(chr(BitAnd(myEvent.message, charCodeMask))));
-
- updateEvt: {if it's for our window, update it}
- if WindowPtr(myEvent.message) = myWindow
- then UpdateWindow(WindowPtr(myEvent.message)); {redraw the window contents}
-
- activateEvt: {if for our window, set port as nec.}
- if WindowPtr(myEvent.message) = myWindow {my window}
- then begin
- DrawGrowIcon(WindowPtr(myEvent.message)); {redraw grow icon to reflect new state}
- if odd(myEvent.modifiers) {odd means an activate event}
- then SetPort(WindowPtr(myEvent.message)) {activate evt: work in our own port}
- else SetPort(screenPort); {deactivate evt: our port is gone;
- keep port from dangling}
- end;
-
- end; {case myEvent.what}
- end; {THEN BEGIN for "it's our event"}
- UNTIL userDone;
-
- END; {MainEventLoop}
-
-
- {%describe '(body of Skel)'}
-
- BEGIN {Skel}
-
- SetUp;
- MainEventLoop;
-
- END. {Skel}
-