home *** CD-ROM | disk | FTP | other *** search
- {%describe 'a Skeleton demo program'}
- {$X-} {Turn automatic run-time stack expansion off - it's a Lisa concept.}
- {$R-} {Turn off range checking; it can cause crashes.}
- PROGRAM Skel;
- { By Steve Maker
- Academic Computing
- Kiewit Computation Center
- Dartmouth College
- July 14, 1984
- Copyright notice:
- SKEL may be copied and used by anyone, so long as that use
- is not for commercial purposes. Please send significant changes
- back to me so that I may incorporate them into future versions.
- 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, supported in the Apple menu,
- and correctly meshed with the other features.
- NOT supported are Undo, Cut, Copy, Paste and Clear
- (and keyboard equivalents) 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
- 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?
- The best way is to use the special EXEC file SKELX, and insert a
- Macintosh diskette into your Lisa. SKELX will write on it with
- MacCom, and will set all info correctly so that the icon will appear.
- You may also use Apple's EXEC file, or the Dartmouth exec files
- T/EXEC or M/MACCOM. The first two will not set the icon correctly,
- while M/MACCOM will do that right, and also refrain from recompiling
- or repeating other steps if they are unnecessary.
- 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
- {%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,
- {$U Obj/PackIntf } PackIntf, {these are not needed for SKEL,}
- {$U Obj/Sane } Sane, { but may be useful later.}
- {$U Obj/Elems } Elems, {Assume normal SANE, not SANELIB}
- {$U Obj/Graf3D } Graf3D,
- {$U Obj/MacPrint } MacPrint;
- CONST
- lastMenu = 2; { number of menus }
- appleMenu = 1; { menu ID for desk accessory menu }
- fileMenu = 2; { menu ID for File 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.}
- 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 '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.}
- 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}
- 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;
- VAR aRect: rect; {rectangle to erase}
- BEGIN {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; {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}
- windowCode: integer; {what mouse was in when event posted}
- 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 the string}
- RattleID = 2;
- FrightenID = 3;
- VAR
- refNum: integer;
- theMenu, theItem: integer;
- name: str255;
- savePort: grafPtr; {for saving current port in when opening a desk acc}
- 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:
- begin
- if theItem = 1
- then begin; {get string, and tell about Skel}
- {It's important not to pass Report a de-referenced handle;
- if Report were in another segment, loading it could
- caused a memory compaction; the de-referenced handle
- could become invalid. Watch out for this and similar
- nasties everywhere in your program.
- See the Memory Manager and the Segment Loader.}
- aStr := GetString(AboutSkelId)^^;
- report(aStr);
- end
- else begin {run a desk accessory; make sure port is preserved}
- getPort(savePort);
- GetItem(myMenus[appleMenu], theItem, name); {get name}
- refNum := OpenDeskAcc(name); {run the desk accessory}
- setPort(savePort);
- end;
- end;
- 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 out what window the mouse went down in, and where in it}
- windowCode := FindWindow(myEvent.where, whichWindow);
- case windowCode of {handle mouse-down for each place}
- 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(myWindow, myEvent.where);
- inGoAway: {we don't have a GoAway region}
- end; {case windowCode}
- 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(myWindow); {redraw the window contents}
- activateEvt: {if for our window, set port as nec.}
- if WindowPtr(myEvent.message) = myWindow {my window}
- then begin
- DrawGrowIcon(myWindow); {redraw grow icon to reflect new state}
- if odd(myEvent.modifiers) {odd means an activate event}
- then SetPort(myWindow) {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}
-
-