trb@drutx.UUCP (BuckleyTR) (02/03/85)
This Lisa Pascal source code for "FILE" has done more to help me understand programming this confusing thing than anything else. This code is not necessarily straight forward. The DEBUG stuff is designed for an external terminal (9600 baud, modem port, set up with RESET procedure, end of program). Tom Buckley AT&T Information Systems (303) 538-3442 ihnp4!drutx!trb ------------------ Cut Here ------------------------------------------ { File -- Example code for a text editor } { -- by Cary Clark, Macintosh Technical Support } PROGRAM MyFile; { Please read 'more about File,' included on the MacStuff 2 disk. } {$DECL BUG} {$SETC BUG := 0} {One good way of debugging code is to write status information to one of the serial ports. Even while debugging code which uses one of the ports, the other can be used for transmitting information to an external terminal. In this program, the compile time variable BUG is set to either -1, 0 or 1 according to the extent of the debugging information required. Since compile time variables or constants are used, setting a single flag should cause the resulting program to have no more code than is required by the debugging level requested. If BUG is set equal to -1, then no debugging information appears; this is as you would want the end user to see your product. BUG set to 0 provides an additional menu bar called 'debug' that can display the amount of memory available, compact memory, and discard segments and resources resident in memory. You can do something similar to display some debugging information on the Mac itself if you do not have a terminal, but the penalty here is that you may spend much of your time debugging the code which is intended to debug some other part of the program. Obviously, creating and maintaining a window on a screen full of other windows in untested code is a difficult thing to do. BUG set to 1 adds an additional item to the 'debug' menu that writes various runtime information to an external terminal. This is the preferred method of debugging, since it does not interfere with the Macintosh display. Even if you do not have a separate terminal, you can use the LISA terminal program to act as one. Since writing a lot of debugging information to a serial port can slow the program down, I would recommend a way of turning the information on and off. In this program, the variable DEBUG is set to true or false in the beginning of one of the first procedures executed, SETUP, to provide debugging information. The DEBUG variable may also be set by the bottom item on the rightmost menu.} {$U-} {Turn off the Lisa Libraries. This is required by Workshop.} {$X-} {Turn off stack expansion. This is a Lisa concept, not needed on Mac.} {$R-} {Turn off range checking.} {$IFC BUG > -1} {$D+} {Put the procedures name just after it in the code, to help in debugging} {$ELSEC} {$D-} {Do not include the procedure name in the 'production' code} {$ENDC} {$L-} {don't list the interfaces} USES {$U Obj/MemTypes } MemTypes, {$U Obj/QuickDraw } QuickDraw, {$U Obj/OSIntf } OSIntf, {$U Obj/ToolIntf } ToolIntf, {$U Obj/PackIntf } PackIntf, {$U Obj/MacPrint } MacPrint; {$L+} {just list the program} CONST appleMenu = 1; FileMenu = 2; EditMenu = 3; DebugMenu = 4; {These constants are declared for this application to distinguish between the various types of windows that it can create. The number is stored in the window field windowkind.} MyDocument = 8; Clipboard = 9; FreeMemory = 10; {$IFC BUG = -1} lastMenu = 3; { number of menus w/o debug} {$ELSEC} lastMenu = 4; { number of menus w/ debug} {$ENDC} lf = CHR(10); { At present, information written to the external terminal needs its own linefeed.} {$IFC BUG < 1} debug = FALSE; { compiler will discard code after 'If debug ...'} {$ENDC} TYPE ProcOrFunc = (proc, func, neither); edset = set of 1..9; LomemPtr = ^LongInt; MyData = RECORD {each document window keeps a handle to this in WRefCon} TERecord: TEHandle; {the text associated with this document} FileVolume: INTEGER; {which volume, if loaded from disk} changed: BOOLEAN; {the document is 'dirty'} titled: BOOLEAN; {the document has never been saved to disk} END; MyDataPtr = ^MyData; MyDataHandle = ^MyDataPtr; { <<< this little beauty does a form feed when you print this out. Copy and Paste it to move it to your source code} {Here are a ton of global variables. This is not a good programming example. You professionals, of course, will keep the number of globals in your own programs to a much smaller number than shown here.} {these first six values are changed as windows are activated} VAR MyWindow: WindowPtr; MyPeek: WindowPeek; {MyPeek is the same as MyWindow} WindowData: MyDataHandle; {this record is pointed to by the WRefCon.} hTE: TEHandle; {The active text edit handle} vScroll: ControlHandle; {The active vertical scroll bar.} whichpart: INTEGER; {The last button pressed} topline: INTEGER; {the value of VScroll, also the visible top line.} printhdl: THPrint; {initialized in SetUp, used by MyPrint} myMenus: ARRAY [1..lastMenu] OF MenuHandle; {Handles to all of the menus} growRect, {contains how big and small the window can grow} dragRect: Rect; {contains where the window can be dragged} tempwindow: WindowPtr; {window referenced by GetNextEvent (bad pgmming.)} theChar: CHAR; {keyboard input goes here} myPoint: Point; {the point where an event took place} lastCount: INTEGER; {last scrap count, to see if it has changed} lastPaste: INTEGER; {the scrap value when the last paste was made} doneFlag: BOOLEAN; {set when the user quits the program} myEvent: EventRecord; {returned by GetNextEvent} scrapwind: WindowPtr; {the ClipBoard window, which contains the scrap} iBeamHdl: CursHandle; {the text editing cursor} watchHdl: CursHandle; {the wait cursor} windownum: LongInt; {the # of untitled windows opened} windowpos: LongInt; {the # of windows opened} MyFileTypes: SFTypeList; {same as txtfile, in a format for Standard File} firstchar: INTEGER; {position of first character on top visible line} printflag: BOOLEAN; {the user selected 'PrintI' from the File menu} finderprint: BOOLEAN; {the user selected 'Print' from the Finder} Dlogptr: DialogPtr; {the dialog box used when printing from Finder} printing: BOOLEAN; {printing is currently in progress} printport: grafptr; {port preserved during background printing} numfiles: INTEGER; {number of files selected in finder} applLimit, {a pointer to the bottom of the stack area} heapend: LomemPtr; {a pointer to the end of the application heap} dummy: Handle; {a temporary handle used to grow the heap} {$IFC BUG > -1} FreeWind: WindowPtr; {the free memory window} oldmem: LongInt; {the last amount of free memory} {$ENDC} {$IFC BUG = 1} debug: BOOLEAN; {$ENDC} debugger: text; {the external terminal file} {------------------------------------------------------------------------------------} PROCEDURE AutoScroll; EXTERNAL; {This assembly routine is called by the innerds of TextEdit when the user drags a selection range outside of the current window.} FUNCTION NewPtrClear (byteCount: Size): Ptr; EXTERNAL; {The NewPtr and NewHandle traps have a bit that clears the memory reserved by the call when set. This assembly sets that bit for the NewPtr trap.} PROCEDURE MainEventLoop; FORWARD; {This is declared forward so the printing can take the main event loop as a procedure to execute while it is idleing} FUNCTION MyGrowZone (cbNeeded: Size) : Size; FORWARD; {This is declared forward so that it can be resident in the blank segment, which is always loaded, and still be referenced by the SetUp procedure} {$S Utilities} {------------------------------------------------------------------------------------} PROCEDURE DebugInProc (prockind: ProcOrFunc; where: str255; location: ptr); {This procedure writes the executing routine's name and location in memory on the external terminal. The location is especially important in a program like this that has segments.} BEGIN {$IFC BUG = 1} Write (debugger, 'in '); IF prockind = proc THEN Write (debugger, 'Procedure '); IF prockind = func THEN Write (debugger, 'Function '); Writeln (debugger, where, ' @ ', ORD4(location), lf) {$ENDC} END; {------------------------------------------------------------------------------------} PROCEDURE SetScrollMax; Var cr : INTEGER; return : char; max: INTEGER; BEGIN {This adjusts the scroll value so that the scroll bar range is not allowed to exceed the end of the text. Also, the scroll bar is disabled if the max is set equal to the min, which is zero. The formula for determining the range is somewhat complex. Sorry.} IF debug THEN debuginproc (proc, 'SetScrollMax', @SetScrollMax); WITH hTE^^, hTE^^.viewrect DO BEGIN cr := 0; return := CHR(13); IF teLength > 0 THEN IF Munger (htext, teLength-1, Pointer(ORD4(@return)+1), 1, NIL, 1) > 0 THEN cr := 1; max := nLines + cr - (bottom - top+1) DIV lineHeight; IF max < 0 THEN max := 0; SetCtlMax (VScroll, max); IF debug THEN Writeln (debugger, 'vscrollmax =', max,lf); topline := -destrect.top DIV lineheight; SetCtlValue (vscroll, topline); IF debug THEN Writeln (debugger, 'topline =',topline,lf) END; END; {------------------------------------------------------------------------------------} PROCEDURE ScrollText (showcaret: BOOLEAN); {called to either show the caret after an action like 'Copy'; also called to adjust the text within the window after the window is resized. The same formula used in SetScrollMax is used here as well. Don't worry about how this works, too much. This possibly could be made much simpler.} Var bottomline, viewlines, SelLine, scrlAmount, numlines, blanklines, newtop ,return: INTEGER; BEGIN IF debug THEN DebugInProc (proc, 'ScrollText', @ScrollText); WITH hTE^^ DO BEGIN scrlAmount := 0; numlines := nlines; {if the last character is a carriage return, add 1 to numlines} return := $0D00; IF teLength > 0 THEN IF Munger (htext, teLength-1, @return, 1, NIL, 1) > 0 THEN numlines := numlines + 1; WITH HTE^^.viewrect DO viewlines := (bottom - top+1) DIV lineHeight; {don't count partial lines} topline := -destrect.top DIV lineheight; bottomline := topline + viewlines - 1; IF showcaret THEN BEGIN selLine := 0; WHILE (selLine+1 < nlines) AND (selstart >=linestarts[selLine+1]) DO selLine := selLine + 1; {if selstart = selend is @ a cr, then add 1 to selstline} IF (selstart = selend) AND (selstart > 0) THEN IF Munger (htext, selstart-1, Pointer(ORD4(@return)+1), 1, NIL, 1) = selstart-1 THEN selLine := selLine + 1; IF debug THEN BEGIN Write (debugger, 'selstart=',selstart:5,'; selLine=',selLine:5); IF selstart > 0 THEN END; IF SelLine > bottomline THEN BEGIN scrlAmount := bottomline - SelLine; IF numlines - SelLine > viewlines DIV 2 THEN scrlAmount := scrlAmount - viewlines DIV 2 ELSE ScrlAMount := ScrlAmount - numlines + SelLine + 1 END; IF SelLine < topline THEN BEGIN scrlAmount := topline - SelLine; IF selLine > viewlines DIV 2 THEN scrlAmount := scrlAmount + viewlines DIV 2 ELSE ScrlAMount := ScrlAmount + selLine END END; IF scrlAmount = 0 THEN BEGIN blanklines := viewlines - numlines + topline; IF blanklines < 0 THEN blanklines := 0; IF (blanklines > 0) AND (topline > 0) THEN BEGIN scrlAmount := blanklines; IF scrlAmount > topline THEN scrlAmount := topline END; IF NOT showcaret THEN BEGIN newtop := 0; WHILE (newtop+1 < nlines) AND (firstchar >= linestarts[newtop+1]) DO newtop := newtop + 1; IF (newtop <> topline) AND (ABS(newtop - topline) > ABS(scrlAmount)) THEN scrlAmount := topline - newtop END END; IF debug THEN BEGIN Write (debugger, 'newtop=',newtop:4,'; blanklines=',blanklines:4); Writeln (debugger, '; newtop - topline=',newtop - topline,lf) END; IF scrlamount <> 0 THEN BEGIN IF selstart = selend THEN TEDeactivate (hTE); TEScroll (0, scrlAmount * lineheight, hTE); IF selstart = selend THEN TEActivate (hTE) END; IF debug THEN Writeln (debugger, 'scrlAmount=',scrlAmount:4,lf); SetScrollMax END END; {------------------------------------------------------------------------------------} PROCEDURE ToggleScrap; Var temppeek: windowPeek; getwhich: INTEGER; showhidestr: Str255; BEGIN {The clipboard comes and goes, here. The last item in the editmenu is alternately made to read, 'Show Clipboard' and 'Hide Clipboard'.} IF debug THEN DebugInProc (proc, 'ToggleScrap', @ToggleScrap); IF ScrapWind = NIL THEN {make it appear} BEGIN scrapwind := GetNewWindow (257, NIL, Pointer (-1)); Temppeek := windowPeek (scrapwind); Temppeek^.windowkind := Clipboard; SetPort (scrapwind); InvalRect (scrapwind^.Portrect); GetWhich := 5 {hide clipboard} END ELSE {make it disappear} BEGIN DisposeWindow (scrapwind); Scrapwind := NIL; GetWhich := 4 {show clipboard} END; GetIndString (showhidestr, 256, getwhich); SetItem (myMenus[EditMenu], 9, showhidestr); END; {$IFC BUG > -1} {------------------------------------------------------------------------------------} PROCEDURE ToggleFree; Var temppeek: windowpeek; getwhich: INTEGER; showhidestr: Str255; BEGIN {just about the same as ToggleClipboard, above. This is just for debugging fun.} IF debug THEN DebugInProc (proc, 'ToggleFree', @ToggleFree); IF FreeWind = NIL THEN {make it appear} BEGIN Freewind := GetNewWindow (258, NIL, Pointer (-1)); Temppeek := windowPeek (Freewind); Temppeek^.windowkind := FreeMemory; SetPort (Freewind); InvalRect (Freewind^.Portrect); GetWhich := 3; END ELSE {make it disappear} BEGIN DisposeWindow (Freewind); Freewind := NIL; GetWhich := 2 END; GetIndString (showhidestr, 257, getwhich); SetItem (myMenus[DebugMenu], 1, showhidestr); END; {$ENDC} {------------------------------------------------------------------------------------} PROCEDURE SetViewRect; BEGIN {text edit's view rect is inset in the content of the window, to prevent it from running into the lefthand side or the scroll bar.} IF debug THEN DebugInProc (proc, 'SetViewRect', @SetViewRect); WITH hTE^^.viewrect DO BEGIN hTE^^.viewrect := MyWindow^.portRect; left := left +4; right := right -15 END END; {------------------------------------------------------------------------------------} PROCEDURE MoveScrollBar; BEGIN {When the window is resized, the scroll bar needs to be stretched to fit.} IF debug THEN DebugInProc (proc, 'MoveScrollBar', @MoveScrollBar); WITH MyWindow^.portRect DO BEGIN HideControl(vScroll); MoveControl(vScroll,right-15,top-1); SizeControl(vScroll,16,bottom-top-13); ShowControl(vScroll) END END; {------------------------------------------------------------------------------------} PROCEDURE GrowWnd; { Handles growing and sizing the window and manipulating the update region. } VAR longStuff: RECORD CASE INTEGER OF 1 : (longResult : LONGINT); {Information returned by MenuSelect} 2 : (height, {Which menu was selected} width : INTEGER) {Which item within the menu} END; height, width, newvert, oldstart: INTEGER; tRect, oldportrect: Rect; BEGIN IF debug THEN DebugInProc (proc, 'GrowWnd', @GrowWnd); WITH longStuff DO BEGIN longResult := GrowWindow(MyWindow,myEvent.where,growRect); IF longResult = 0 THEN EXIT(GrowWnd); Setcursor (watchhdl^^); {because the word wrap could take a second or two} SizeWindow(MyWindow,width,height,TRUE); { Now draw the newly sized window. } InvalRect (MyWindow^.portrect); IF MyPeek^.windowkind = MyDocument THEN {it's not the clipboard} BEGIN MoveScrollBar; WITH MyWindow^.portRect DO BEGIN width := right-left-19; height := bottom-top END; WITH HTE^^ DO BEGIN destrect.right := destrect.left + width; viewrect.right := viewrect.left + width; viewrect.bottom := viewrect.top + height; firstchar := hTE^^.linestarts [topline]; TECalText (hTE); {re-wrap the text to fit the new screen.} {if the rectangle is grown such that there is now blank space on the bottom of the screen, backpedal the screen to fill it back up, if there is enough scrolled off the screen to do so. Otherwise, the first character in the top line on the screen should continue to be somewhere on the top line after resizing} ScrollText (FALSE); END END END END; { of GrowWnd } {------------------------------------------------------------------------------------} PROCEDURE MyActivate; VAR tRect : rect; BEGIN {activate events occur when one window appears in front of another. This takes care of hiliting the scroll bar and deactivating the insertion caret or the text selection.} IF debug THEN DebugInProc (proc, 'MyActivate', @MyActivate); MyWindow := windowPtr (MyEvent.message); MyPeek := windowPeek (MyWindow); IF MyPeek^.windowkind in [MyDocument,Clipboard] THEN BEGIN {redraw the scrollbar area, if a document or the clipboard} SetPort (MyWindow); tRect := MyWindow^.portRect; tRect.left := tRect.right-16; InvalRect(tRect) END; IF MyPeek^.windowkind = MyDocument THEN BEGIN {make global variables point to the information associated with this window} WindowData := MyDataHandle (GetWRefCon (MyWindow)); VScroll := ControlHandle (MyPeek^.ControlList); hTE := WindowData^^.TERecord; IF ODD (myEvent.modifiers) THEN BEGIN {this window is now top most} TEActivate(hTE); ShowControl (VScroll); topline := GetCtlValue (VScroll) END ELSE BEGIN {this window is no longer top most} HideControl (VScroll); TEDeactivate(hTE); hTE := NIL {a document is no longer on top} END END END; { of activateEvt } {------------------------------------------------------------------------------------} PROCEDURE DialogueDeactivate; var temprect: rect; BEGIN {This routine takes care of cases where, for instance, a modal dialog is about to pop up in front of all the other windows. Since the Dialog Manager handles all activate events for you, you do not get a chance to 'turn off' the controls associated with the window. This routine is called just before the dialog box makes its appearance, and takes care of the hiliting as if an activate event had occured.} IF debug THEN DebugInProc (proc, 'DialogueDeactivate', @DialogueDeactivate); IF hTE <> NIL THEN {for documents, only} BEGIN TEDeactivate(hTE); HideControl (VScroll); SetCursor (arrow) END; IF (frontwindow <> NIL) AND (Mypeek^.windowkind IN [MyDocument, Clipboard]) THEN BEGIN {this is a little kludgy, but it works.} Mypeek^.hilited := FALSE; {DrawGrowIcon will now unhilite.} temprect := MyWindow^.PortRect; temprect.left := temprect.right - 15; Cliprect (temprect); {clipaway the horizontal scrollbar part} DrawGrowIcon (MyWindow); Cliprect (MyWindow^.PortRect); Mypeek^.hilited := TRUE {fix things back} END END; {$S READFILE} {------------------------------------------------------------------------------------} Function ReadFile (VrefNo: INTEGER; FName : str255) : BOOLEAN; Var refNo, io : INTEGER; logEOF: LongInt; errin: str255; {------------------------------------------------------------------------------------} Procedure DiskRErr (io : INTEGER); Var str: str255; readfromstr, loadedstr, str1: Str255; dummy: INTEGER; BEGIN {A generic error is reported to the user if something goes wrong. Amazingly little can go wrong, since the user does not get the chance to do things like type file names, remove the disk himself, and so on. About the only error that could happen is: an error occured while reading the disk (damaged media or hardware) Can you think of anything else? A similar routine further down handles writing to disk. Note that in both reading and writing, the entire file is handled by a single read/write call, and no 'disk buffer' needs to be specified by the programmer.} IF debug THEN BEGIN DebugInProc (func, 'DiskRErr', @DiskRErr); Writeln (debugger, errin, ' err = ', io, lf) END; GetIndString (readfromstr, 256, 9); {this says 'reading from'} GetIndString (loadedstr ,256, 11); {this says 'loaded'} IF io = IOErr THEN GetIndString (str, 256, 21) {this says 'IO error'} ELSE BEGIN NumToString (io, str1); GetIndString (str, 256, 22); {this is the generic 'ID ='} str := Concat (str, str1) END; Paramtext (readfromstr, FName, loadedstr, str); SetCursor (arrow); dummy := StopAlert (256, NIL); {discribe error to user in generic way.} Exit (readfile) END; BEGIN IF debug THEN BEGIN DebugInProc (func, 'ReadFile', @ReadFile); writeln (debugger, 'volume = ', vrefno, '; file = ', fname, lf) END; SetCursor (watchHdl^^); ReadFile := FALSE; io := FSOpen (Fname, VRefNo, RefNo); {$IFC BUG = 1} {these debugging statements are for the external terminal, only} errin := 'FSOpen'; {$ENDC} IF io <> 0 THEN DiskRErr (io); io := GetEOF (RefNo, logEOF); {$IFC BUG = 1} errin := 'GetEOF'; {$ENDC} IF io <> 0 THEN DiskRErr (io); {add code here: if file is too large, then notify user and truncate} SetHandleSize (hTE^^.hText, logEOF); IF debug THEN IF memerror<>0 THEN Writeln (debugger, 'memerr = ',memerror:4); io := FSRead (refNo, logEOF, hTE^^.hText^); {$IFC BUG = 1} errin := 'FSRead'; {$ENDC} IF io <> 0 THEN DiskRErr (io); io := FSClose (refNo); {$IFC BUG = 1} errin := 'FSClose'; {$ENDC} IF io <> 0 THEN DiskRErr (io); hTE^^.teLength := logEOF; IF NOT finderprint THEN {if printing from the finder, no window or editing information is needed} BEGIN TESetSelect (0,0,hTE); TECalText (hTE); Invalrect (hTE^^.viewrect); SetScrollMax; WindowData^^.titled := TRUE; WindowData^^.changed := FALSE; WindowData^^.FileVolume := VRefNo END; ReadFile := TRUE {everything worked out OK} END; {------------------------------------------------------------------------------------} PROCEDURE MakeAWindow (str : str255; disk : BOOLEAN); Var bounds: rect; BEGIN {A window is created here, and all associated data structures are linked to it} IF debug THEN DebugInProc (proc, 'MakeAWindow', @MakeAWindow); windowpos := windowpos + 1; {this position it is created to on the screen} bounds.left := windowpos MOD 16 * 20 + 5; bounds.top := windowpos MOD 11 * 20 + 45; bounds.right := bounds.left + 200; bounds.bottom := bounds.top + 100; MyWindow := NewWindow(NIL, bounds, str, TRUE, 0, Pointer(-1), TRUE, 0); SetPort (MyWindow); Mypeek := windowPeek (MyWindow); TextFont (applFont); DrawChar (' '); SetFontLock (TRUE); Mypeek^.windowkind := MyDocument; {a number > 8 identifies the type of window} hTE := TENew(MyWindow^.portRect, MyWindow^.portRect); WindowData := MyDataHandle (NewHandle (8)); {1 handle, an integer, and 2 booleans} SetWRefCon (MyWindow, ORD(WindowData)); WindowData^^.TERecord := hTE; SetViewRect; hTE^^.destrect := hTE^^.viewrect; WindowData^^.changed := FALSE; WindowData^^.titled := FALSE; vScroll := GetNewControl(256,MyWindow); MoveScrollBar; topline := 0; hTE^^.clikLoop := @AutoScroll END; {------------------------------------------------------------------------------------} PROCEDURE MyGetFile; Var reply: SFReply; wher: point; temprect: rect; tempport:grafptr; copyIt, foundIt : BOOLEAN; {if the name is already in use, this will be true} temppeek: Windowpeek; tempstr, oldfname: str255; str: str255; tempdata: MyDataHandle; BEGIN {This calls Standard File to allow the user to choose the document on disk that she wishes to edit.} IF debug THEN DebugInProc (proc, 'MyGetFile', @MyGetFile); wher.h := 90; wher.v := 100; DialogueDeactivate; SFGetFile (wher, '', NIL, 1, MyFileTypes, NIL, reply); WITH Reply DO IF good THEN BEGIN {check to see if this name already resides on a document window. If so, change the title to 'Copy of I' and remember to check it as untitled? after the readfile} foundit := FALSE; oldfname := fname; REPEAT temppeek := windowPeek(Frontwindow); copyIt := FALSE; IF temppeek <> NIL THEN REPEAT GetWTitle (windowPtr (temppeek), tempstr); IF tempstr = fname THEN BEGIN tempdata := MyDataHandle(temppeek^.refCon); IF tempdata^^.FileVolume = vrefnum THEN BEGIN copyIt := TRUE; foundIt := TRUE END END; temppeek := temppeek^.nextwindow UNTIL (temppeek = NIL) OR copyIt; GetIndString (str, 256, 16);{copy of} IF copyIt THEN fname := Concat (str,fname); UNTIL NOT copyIt; IF foundIt THEN BEGIN Paramtext (fname,'','',''); copyIt := (NoteAlert (258, NIL) = OK) END; IF NOT foundIt OR copyIt THEN BEGIN MakeAWindow (fname, TRUE); IF ReadFile (vrefnum, oldfname) THEN BEGIN IF foundIt THEN windowdata^^.titled := FALSE END ELSE BEGIN TEDispose (hTE); hTE := NIL; DisposHandle (Handle (WindowData)); IF debug THEN Writeln (debugger, 'dispose WindowData; memerr = ', MemError, lf); DisposeWindow (MyWindow) END END END END; {------------------------------------------------------------------------------------} PROCEDURE OpenAWindow; VAR s: str255; untitled: Str255; BEGIN {this creates a new window that is untitled and empty.} IF debug THEN DebugInProc (proc, 'OpenAWindow', @OpenAWindow); {see if enough mem exists to open a window} NumToString(windownum, s); windownum := windownum + 1; GetIndString (untitled, 256, 1); MakeAWindow (Concat (untitled, s), FALSE); END; {$S WRITFILE} {------------------------------------------------------------------------------------} FUNCTION WriteFile (vRefNo: INTEGER; fName : str255) : BOOLEAN; var refNo, io : INTEGER; txtlength : longint; errin : str255; {------------------------------------------------------------------------------------} PROCEDURE DiskWErr (io : INTEGER); Var str:str255; writetostr, savedstr, str1: Str255; dummy, errstr: INTEGER; BEGIN {This is just about the same as DiskRErr (read). Since a few more errors can happen during a write, the structure is just a little different} IF debug THEN BEGIN DebugInProc (proc, 'DiskWErr', @DiskWErr); Writeln (debugger, errin, ' err = ', io, lf) END; GetIndString (writetostr,256,10);{read resource for writeto} GetIndString (savedstr,256,12);{read resource for saved} errstr := 0; Case io of DskFulErr : errstr := 17; DirFulErr : errstr := 18; FLckdErr : errstr := 19; VLckdErr, WPrErr : errstr := 20; IOErr : errstr := 21; OTHERWISE BEGIN NumToString (io, str); GetIndString (str1,256,22);{ID = } str := Concat (str1,str) END END; IF errstr <> 0 THEN GetIndString (str,256,errstr); Paramtext (writetostr,FName,savedstr,str); SetCursor (arrow); dummy := StopAlert (256, NIL); io := FSClose (refNo); Exit (writefile) END; BEGIN {this isn't very different from read file. The only complication is finding out if the file exists. If it doesn't, create it. Also, assign the information that the finder needs to properly associate it with this application.} IF debug THEN DebugInProc (proc, 'WriteFile', @WriteFile); SetCursor (watchHdl^^); WriteFile := FALSE; io := FSOpen(FName, VRefNo, refNo); {$IFC BUG = 1} errin := 'FSOpen'; {once again, these only benefit the external debugger.} {$ENDC} IF debug THEN Writeln (debugger, 'file RefNum =', refNo, lf); IF io = {file not found Err} -43 THEN BEGIN io := Create (FName,VRefNo,'CARY','TEXT'); {$IFC BUG = 1} errin := 'Create'; {$ENDC} IF io <> 0 THEN DiskWErr (io); io := FSOpen(FName, VRefNo, refNo); {$IFC BUG = 1} errin := 'FSOpen'; {$ENDC} IF debug THEN Writeln (debugger, 'file RefNum = ', refNo, lf); IF io <> 0 THEN DiskWErr (io) END {Create} ELSE IF io <> 0 THEN DiskWErr (io); WITH hTE^^ DO BEGIN txtLength := teLength; io := FSWrite (refNo, txtLength, hText^); END; {$IFC BUG = 1} errin := 'FSWrite'; {$ENDC} IF io <> 0 THEN DiskWErr (io); io := SetEOF (refNo, txtlength); {$IFC BUG = 1} errin := 'SetEOF'; {$ENDC} IF io <> 0 THEN DiskWErr (io); io := FSClose (refNo); {$IFC BUG = 1} errin := 'FSClose'; {$ENDC} IF io <> 0 THEN DiskWErr (io); io := FlushVol (NIL, VrefNo); {this is important; without it, if the program died (not possible as a result of a programming mistake, of course), the directory information on the disk would not be accurate.} {$IFC BUG = 1} errin := 'FlushVol'; {$ENDC} IF io <> 0 THEN DiskWErr (io); IF not windowdata^^.titled THEN BEGIN SetWTitle(MyWindow, FName); WindowData^^.filevolume := VRefNo END; WindowData^^.titled := TRUE; WindowData^^.changed := FALSE; WriteFile := TRUE {everything is OK.} END; {------------------------------------------------------------------------------------} FUNCTION MyPutFile (Filename: str255): BOOLEAN; Var reply: SFReply; wher: point; Namestr: Str255; temprect: rect; tempport:grafptr; BEGIN {The user can select the name of the file that they wish to save the document with.} IF debug THEN DebugInProc (func, 'MyPutFile', @MyPutFile); MyPutFile := FALSE; GetIndString (namestr,256,2); wher.h := 100; wher.v := 100; DialogueDeactivate; SFPutFile (wher, Namestr, Filename, NIL, reply); WITH Reply DO BEGIN IF debug THEN Writeln (debugger, 'reply.good = ', good, lf); IF good THEN MyPutFile := WriteFile (vrefnum, fname) END; IF debug THEN Writeln (debugger, 'release reserror = ', reserror, lf) END; {------------------------------------------------------------------------------------} PROCEDURE CloseAWindow; VAR itemhit: INTEGER; DBoxPtr: DialogPtr; str,str1: str255; Goodwrite: BOOLEAN; temprect: rect; NamePtr: ^Str255; typ: INTEGER; itemhdl: handle; box:rect; BEGIN {All sorts of windows can be closed through this single routine, which is accessed by the user through the go-away box on the window, or the Close item in the File menu, or by quitting the program.} IF debug THEN DebugInProc (proc, 'CloseAWindow', @CloseAWindow); MyPeek := windowPeek (FrontWindow); Case Mypeek^.windowkind of MyDocument : BEGIN GetWTitle (MyWindow, str); itemhit := 0; IF WindowData^^.changed THEN {give the user the chance to save his data before you throw it away.} BEGIN DialogueDeactivate; IF doneflag THEN BEGIN GetIndString (str1,256,8); IF debug THEN Writeln (debugger, 'err = ', Reserror, lf); END ELSE str1 := ''; Paramtext (str,str1,'',''); ItemHit := CautionAlert (259, NIL) END; IF debug THEN Writeln (debugger, 'itemhit = ', itemhit, lf); Goodwrite := FALSE; IF NOT windowdata^^.titled THEN str := ''; IF itemhit = OK {save} THEN IF WindowData^^.titled THEN GoodWrite := WriteFile (WindowData^^.FileVolume, str) ELSE Goodwrite := MyPutFile (str); IF GoodWrite OR (itemhit IN [0, 3] {discard}) THEN BEGIN TEDispose (hTE); hTE := NIL; DisposHandle (Handle (WindowData)); DisposeWindow (MyWindow) END; IF itemhit = Cancel THEN doneflag := FALSE END; Clipboard : ToggleScrap; {$IFC BUG > -1} FreeMemory: ToggleFree; {$ENDC} OTHERWISE CloseDeskAcc (MyPeek^.windowkind) {can't be anything else} END {Case} END; {$S AboutMyPgm} {------------------------------------------------------------------------------------} PROCEDURE AboutMyEditor; const mousekey = mDownMask+keyDownMask; var str1hdl: stringHandle; str2: Str255; MyWindow: WindowPtr; width, height, counter, strwidth, strdepth, factor, remainder, adjust: INTEGER; newcount: longint; txtinfo: fontinfo; temprect, trect1: rect; offscreen, tempbits: bitmap; sz: size; BEGIN {this bit of fluff shows an inadequate method of telling the user something about my program, but it was fun to do.} IF debug THEN DebugInProc (proc, 'AboutMyEditor', @AboutMyEditor); DialogueDeactivate; str1hdl := stringHandle(GetResource('CARY',0)); IF debug THEN Writeln (debugger, 'err = ', Reserror, lf); GetIndString (str2,256,3); IF debug THEN Writeln (debugger, 'err = ', Reserror, lf); HLock (Handle(str1hdl)); MyWindow := GetNewWindow (256, NIL, Pointer (-1)); SetPort (MyWindow); TextFont (NewYork); TextSize (12); GetFontInfo (TxtInfo); width := MyWindow^.portrect.right - MyWindow^.portrect.left; height := MyWindow^.portrect.bottom - MyWindow^.portrect.top; strwidth := StringWidth (str1hdl^^); IF StringWidth (str2) > strwidth THEN strwidth := StringWidth (str2); WITH TxtInfo DO BEGIN strdepth := ascent*2+descent*2+leading+1; WITH offscreen DO BEGIN rowbytes := (strwidth + 15) DIV 16 * 2;{rowbytes needs to be even} SetRect (bounds, 0,0,strwidth,strdepth); baseaddr := NewPtrClear (rowbytes * strdepth); IF debug THEN Writeln (debugger, 'err = ', Memerror, lf); END; tempbits := MyWindow^.portbits; SetPortBits (offscreen); MoveTo ((strwidth - StringWidth (str1hdl^^)) DIV 2, ascent); DrawString (str1hdl^^); MoveTo ((strwidth - StringWidth (str2)) DIV 2, strdepth-descent); DrawString (str2) END; HUnlock (Handle (str1hdl)); SetPortBits (tempbits); factor := strwidth DIV strdepth; remainder := strwidth MOD strdepth; SetRect (trect1, (width - remainder) DIV 2 - factor, height DIV 2 - 1, (width + remainder) DIV 2 + factor, height DIV 2 + 1); counter := 1; REPEAT SystemTask; CopyBits (offscreen, MyWindow^.portbits, offscreen.bounds, trect1, srcCopy, NIL); InsetRect (trect1, -factor, -1); counter := counter + 2; UNTIL EventAvail (mousekey, MyEvent) OR (counter >= strdepth); newcount := TickCount + 300; {5 seconds} REPEAT SystemTask UNTIL EventAvail (mousekey, MyEvent) OR (TickCount > newcount); temprect := offscreen.bounds; OffsetRect (temprect, (width-strwidth) DIV 2, (height-strdepth) DIV 2); trect1 := offscreen.bounds; WITH MyWindow^, temprect DO WHILE NOT EventAvail(mousekey,MyEvent) AND (trect1.right-factor * 2> trect1.left) DO BEGIN SystemTask; {the clock still ticks!} factor := trect1.right DIV strdepth; IF left > 0 THEN InsetRect (temprect, -factor, -2) ELSE IF top > 0 THEN BEGIN InsetRect (trect1, factor, 0); InsetRect (temprect, 0, -2) END ELSE InsetRect (trect1, factor, 2); CopyBits (offscreen, portbits, trect1, temprect, srcCopy, NIL); END; DisposPtr(offscreen.baseaddr); IF debug THEN Writeln (debugger, 'err = ', Memerror, lf); DisposeWindow (MyWindow) END; {$S MyPrint } {------------------------------------------------------------------------------------} PROCEDURE CheckButton; var bool : BOOLEAN; item : INTEGER; BEGIN bool := GetNextEvent (mDownMask+keyDownMask, MyEvent); item := 0; IF (myEvent.what = keydown) AND (BitAnd (myEvent.message, 255) = 13) THEN item := 1 ELSE IF IsDialogEvent (myEvent) THEN bool := DialogSelect (myEvent, dlogptr, item); IF item = 1 THEN PrSetError (iPrAbort); END; {------------------------------------------------------------------------------------} PROCEDURE MyPrint(finderFile:INTEGER; filename: str255); Const bottommargin = 20; {amount of space on the margins of the page in pixels} leftmargin = 30; rightmargin = 10; Var MyPPort: TPPrPort; txt: handle; txtptr: ptr; pglen, start, finish, counter, count2, loop, io, numpages: INTEGER; temprect, tmprect2, pagerect: rect; status: TPrStatus; userOK, canceldialog: BOOLEAN; s: string[1]; str: str255; numToGo, numdone: str255; temp: str255; MyLngth: array [1..99] of INTEGER; BEGIN {For heavyweight programmers only. All modes of printing are handled by Macprint. The only things you have to do are: image each page, using QuickDraw (or something that uses QuickDraw); Do it once for the number of copies the user specified in draft mode only. You do not have to worry with: copies in normal or high res. which pages the user chose to print. tall, wide, etc. Remember, these Page Setup dialog is printer specific. It will not always be the same, so don't write any code around it. The reason this program is heavily segmented is that printing normal or high-res on line takes gobs of memory (in this example, up to 25K.) You may minimize the by omitting 1 line below and creating a spooled file instead. The finderprint boolean determines whether printing is has been selected while the user is running the application, or whether it was selected from the finder. In the application, printing is done in the background. From the finder, a simple dialog is presented instead. Because printing takes a large amount of memory, up to 25K, background printing is only possible if the memory required by the foreground process can be kept to a minimum. Since this program does not yet have strong memory full checking, you should set the debugging compile time variable DEBUG to -1, and remove MacsBug from the Mac disk, to give the program a realistic amount of free memory. MacsBug, when active, can use up to 16K. Printing is not re-entrant. If your main program loop is the print idle proc, as below, disable the Page Setup item and change 'PrintI' to 'Stop Printing' in the File menu.} IF debug THEN DebugInProc (proc, 'MyPrint', @MyPrint); printflag := FALSE; IF debug THEN writeln (debugger, 'finderPrint =', finderprint, '; finderFile =', finderfile, lf); userOK := TRUE; IF finderfile = 1 THEN BEGIN SetCursor (arrow); userOK := PrJobDialog (PrintHdl) END; IF userOK THEN BEGIN {try to see if enough memory exists to 1) duplicate the text portion of the te record 2) allow the printing pieces to be resident 3) allow the largest possible segment to be loaded by the main event loop if so, allow the printing to go on in the background. Otherwise, put up the 'press a button to cancel' dialog} SetCursor (watchhdl^^); IF NOT finderprint THEN numfiles := 1; canceldialog := finderprint; IF NOT canceldialog THEN BEGIN txt := NewHandle (hte^^.telength+16000); {this calculation should be made considering: the current font size the printing mode (draft, normal, hires) the textstyle overhead, if any blank segment overhead largest segment + largest local data global data overhead --- 16000 is a crude, unprofessional approximation} IF txt = NIL THEN canceldialog := TRUE ELSE BEGIN disposHandle (txt); txt := hte^^.hText; ResrvMem (hte^^.teLength); io := HandToHand (txt); END END; IF canceldialog THEN BEGIN NumToString (finderFile, numToGo); NumToString (numfiles, numdone); Paramtext (filename,numToGo,numdone,''); dlogptr := GetNewDialog (257, NIL, Pointer(-1)); DrawDialog (dlogptr); printHdl^^.prJob.pIdleProc := @CheckButton; txt := hte^^.hText END ELSE BEGIN GetIndString (temp,256,15); {change to 'Stop Printing'} SetItem (myMenus[fileMenu], 8, temp); printing := TRUE; printHdl^^.prJob.pIdleProc := @MainEventLoop; GetPort (printport); {get the port to be restored at the top of the main event loop} END; {for now, approximate a full page} MyPPort := PrOpenDoc (PrintHdl, NIL, NIL); WITH hTE^^, printhdl^^.prinfo DO BEGIN pagerect := rpage; pagerect.left := pagerect.left + leftmargin; pagerect.right := pagerect.right - rightmargin; pagerect.bottom := pagerect.bottom - bottommargin - (pagerect.bottom - bottommargin) MOD lineheight {get rid of partial line}; temprect := destrect; destrect := pagerect; TECalText (hTE) END; {TECalText could cause the memory manager to move the hTE and PrintHdl handles. So, the 'WITH' statement is required below; the alternative would be to use 1 'WITH' and 'HLock' the handles. Note that 'WITH' is much more than a lexical convenience. It actually causes the compiler to optimize code about the fields of hTE^^ and printhdl^^.prinfo} WITH hTE^^, printhdl^^.prinfo DO BEGIN tmprect2 := viewrect; pglen := (rpage.bottom - rpage.top - bottommargin) DIV lineheight; finish := nlines; start := 0; counter := 1; WHILE start < finish DO BEGIN IF finish - start > pglen THEN MyLngth[counter] := linestarts[start + pglen] - linestarts[start] ELSE MyLngth[counter] := teLength - linestarts[start]; IF debug THEN BEGIN Writeln (debugger,'MyLngth[',counter:1,'] = ', MyLngth[counter]:5, '; start = ', start:5, '; pglen = ', pglen:5, lf); Writeln (debugger, 'finish = ', finish:5, '; teLength = ', teLength:5, '; ORD(txt) = ', ord4(txt),lf) END; start := start + pglen; counter := counter + 1; END; {While start < finish} numpages := counter - 1; IF NOT finderprint THEN BEGIN destrect := temprect; TECalText (hTE) END END; IF debug THEN Writeln (debugger,'BJDocLoop = ', PrintHdl^^.prjob.BJDocLoop,lf); IF PrintHdl^^.prjob.BJDocLoop = BSpoolLoop THEN loop := 1 ELSE loop := PrintHdl^^.prjob.iCopies; SetPort (GrafPtr(MyPPort)); TextFont (applFont); DrawChar (' '); SetFontLock (TRUE); IF PrintHdl^^.prjob.BJDocLoop <> BSpoolLoop THEN SetCursor (arrow); FOR counter := 1 to loop DO BEGIN Hlock (txt); txtptr := txt^; FOR count2 := 1 to numpages DO BEGIN {if background printing, duplicate txt handle before starting} PrOpenPage (MyPPort, NIL); TextBox (txtptr, MyLngth[count2], pagerect, teJustLeft); PrClosePage (MyPPort); txtptr := Pointer (ORD4(txtptr) + MyLngth[count2]); start := start + pglen END; {For count2I} HUnlock (txt); END; {For counterI} PrCloseDoc (MyPPort); IF PrintHdl^^.prjob.BJDocLoop = BSpoolLoop THEN BEGIN SetCursor (arrow); PRPicFile (Printhdl, NIL, NIL, NIL, status) {omit this for spooled files.} END; IF canceldialog THEN DisposDialog (dlogptr) ELSE BEGIN disposHandle (txt); printing := FALSE; GetIndString (temp,256,14); {change to 'PrintI'} SetItem (myMenus[fileMenu], 8, temp); SetPort (printport) END END END; {$S EditMenu} {------------------------------------------------------------------------------------} Procedure EditMain (theItem: INTEGER; commandkey : BOOLEAN); const undo = 1; cut = 3; kopy = 4; {'Copy' is a Pascal string function} paste = 5; clear = 6; selectAll = 7; clipbored = 9; {'ClipBoard' is already used as a windowkind constant} VAR DeskAccUp , dummy: BOOLEAN; Dscrap: PScrapStuff; off: LongInt; ticks: LongInt; tempport: grafptr; box: rect; itemhdl, hdl: handle; typ, io, tempstart, tempend: INTEGER; tempptr: ptr; TextLength: INTEGER; Ptr2ScrapLength: LomemPtr; topwindow: WindowPeek; BEGIN {Since the Edit menu does so much, it has been broken up into a separate procedure. It does not yet support undo, but does support Cutting, Copying and Pasting between the Desk Scrap and the TextEdit Scrap.} DeskAccUp := FALSE; IF (theItem < selectAll) and NOT CommandKey THEN DeskAccUp := SystemEdit(theItem-1); topwindow := WindowPeek(FrontWindow); IF (theItem > Clear) OR NOT DeskAccUp THEN BEGIN IF theItem in [cut, kopy] THEN BEGIN tempend := hTE^^.selend; tempstart := hte^^.selstart END; IF debug THEN Writeln (debugger, 'not system edit', lf); { Delay so menu title will stay lit a little only if Command key } { equivalent was typed. } IF commandkey THEN BEGIN ticks := TickCount + 10; REPEAT UNTIL ticks <= TickCount END; {** see if enough memory exists for move} CASE theItem OF undo: ; { no Undo/Z in this example } cut: TECut(hTE); { Cut/X } kopy: TECopy(hTE); { Copy/C } paste: BEGIN { Paste/V } DScrap := InfoScrap; IF DScrap^.scrapState <> LastPaste THEN BEGIN LastPaste := DScrap^.scrapState; io := GetScrap (NIL, 'TEXT', off); IF debug THEN Writeln (debugger, 'io = ', io); IF io > 0 THEN {**?? enough space to paste} BEGIN io := GetScrap (TEScrapHandle, 'TEXT', off); Ptr2ScrapLength := LomemPtr ($AB0); Ptr2ScrapLength^ := BitShift (io, 16);{***???***} END END; TEPaste(hTE); END; clear: TEDelete(hTE); { Clear } selectall: TeSetSelect(0,65535,hTE); { Select All/A } clipbored: ToggleScrap { Show, Hide Clipboard } END; { of item case } IF theItem in [cut,kopy] THEN BEGIN io := ZeroScrap; IF debug THEN Writeln (debugger, 'zero scrap err =', io, lf); TextLength := GetHandleSize (TEScrapHandle); IF debug THEN Writeln (debugger, 'TEScrapHandle @',ORD4(TEScrapHandle^),'; TextLength = ',textlength,lf); Hlock (TEScrapHandle); io := PutScrap (TextLength, 'TEXT', TEScrapHandle^); IF debug THEN Writeln (debugger, 'put scrap err =', io, lf); HUnlock (TEScrapHandle) END; IF theItem in [cut,clear,paste] THEN Windowdata^^.changed := TRUE; IF (theItem in [cut..clear]) THEN ScrollText (TRUE) END {not systemedit} END; { of editMain } {$S Command } {------------------------------------------------------------------------------------} PROCEDURE MyDisable; const newitem = 1; openitem = 2; closeitem = 3; saveitem = 4; saveasitem = 5; revertitem = 6; pagesetupitem = 7; printitem = 8; quititem = 9; undoitem = 1; cutitem = 3; copyitem = 4; pasteitem = 5; clearitem = 6; selectallitem = 7; clipboreditem = 9; var counter: INTEGER; DScrap: PScrapStuff; temppeek: windowpeek; stycount: styleitem; off : LongInt; {------------------------------------------------------------------------------------} PROCEDURE KillFE (fileitems, edititems : edset); var counter : INTEGER; BEGIN {This guy disables the items in the File and Edit menus. This approach has a real disadvantage: If an entire menu should be disabled at some given time, there is no convenient way to do a DrawMenuBar here to disable the item in the bar itself.} IF debug THEN BEGIN DebugInProc (proc, 'KillFE', @KillFE); Write (debugger, 'file:'); FOR counter := newitem to quititem DO IF counter in fileitems THEN Write (debugger, counter:2, ','); Write (debugger, '; edit:'); FOR counter := undoitem to clipboreditem DO IF counter in edititems THEN Write (debugger, counter:2, ','); Writeln (debugger, lf) END; FOR counter := 1 to 9 DO BEGIN IF counter in fileitems THEN DisableItem (myMenus[FileMenu], counter); IF counter in edititems THEN DisableItem (myMenus[EditMenu], counter); END END; BEGIN {This part goes through all of the applicable elements of the frontmost window, if any and from that decides what operations are allowable at this time.} IF debug THEN DebugInProc (proc, 'MyDisable', @MyDisable); FOR counter := 1 to 9 DO BEGIN EnableItem (myMenus[FileMenu], counter); IF counter in [UndoItem,CutItem..SelectAllItem,ClipboredItem] THEN EnableItem (myMenus[EditMenu], counter) END; IF printing THEN KillFE ([PageSetupItem],[]); {page setup, if printing} IF Frontwindow = Nil THEN KillFE ([CloseItem..PrintItem],[UndoItem..SelectAllItem]) ELSE BEGIN Mypeek := windowPeek (FrontWindow); Case Mypeek^.windowkind of MyDocument: BEGIN KillFE ([], [UndoItem]); IF NOT WindowData^^.titled THEN KillFE ([SaveItem,RevertItem], []); IF NOT WindowData^^.changed THEN KillFE ([SaveItem,RevertItem], []); IF hTE^^.teLength = 0 THEN KillFE ([SaveItem,SaveAsItem,PageSetupItem,PrintItem], [SelectAllItem]); IF hTE^^.selstart = hTE^^.selend THEN KillFE ([], [CutItem,CopyItem,ClearItem]); IF GetScrap (NIL, 'TEXT', off) = noTypeErr THEN KillFE ([], [PasteItem]); END; Clipboard,FreeMemory: KillFE ([SaveItem..PrintItem], [UndoItem, CutItem..SelectAllItem]); OTHERWISE KillFE ([SaveItem..PrintItem], [SelectAllItem]) {system window} END {Case} END; IF printing THEN EnableItem (MyMenus[filemenu], PrintItem) {stop printing} END; {------------------------------------------------------------------------------------} PROCEDURE DoCommand (commandkey: BOOLEAN); VAR name, s, str: str255; bstr: string[5]; dummy: size; err : BOOLEAN; num, refnum, theMenu, theItem: INTEGER; tempPeek: WindowPeek; mresult, ticks: longint; dipeek: DialogPeek; box: rect; itemhdl: handle; typ: INTEGER; menuStuff: RECORD CASE INTEGER OF 1 : (menuResult : LONGINT); {Information returned by MenuSelect} 2 : (theMenu, {Which menu was selected} theItem : INTEGER) {Which item within the menu} END; BEGIN {This handles the actions that are initiated through the Menu Manager} IF debug THEN DebugInProc (proc, 'DoCommand', @DoCommand); MyDisable; WITH menuStuff DO BEGIN IF Commandkey THEN menuResult := MenuKey(theChar) ELSE menuResult := MenuSelect (myEvent.where); CASE theMenu OF appleMenu: {enough memory to allow desk accessory to open} BEGIN IF theItem = 1 THEN AboutMyEditor ELSE BEGIN GetItem(myMenus[appleMenu],theItem,name); refNum := OpenDeskAcc(name) END END; FileMenu: BEGIN IF FrontWindow <> NIL THEN IF MyPeek^.WindowKind = MyDocument THEN IF windowdata^^.titled THEN GetWTitle (FrontWindow, str) ELSE str := ''; Case TheItem of 1: OpenAWindow; { New } 2: MyGetFile; { OpenI } 3: CloseAWindow; { Close } 4: err := { Save } WriteFile (windowdata^^.FileVolume, str); 5: err := MyPutFile (str); { Save AsI } 6: BEGIN { Revert to Saved } IF CautionAlert(257, NIL)=OK THEN err := ReadFile (windowdata^^.FileVolume, str); ScrollText (FALSE) {which is the user interfacy thing to do? display the top of the file, or display the position in the file the user was looking @ when he said revert. Should I also maintain the flashing caret position?} END; 7: BEGIN PrOpen; IF PrStlDialog (PrintHdl) { Page SetupI } THEN ; PrClose END; {eventually, store info in document resource fork} 8: IF NOT printing { Print } THEN Printflag := TRUE ELSE PrSetError(iPrAbort); 9: doneFlag := TRUE; { Quit } END END; EditMenu: EditMain (theItem, commandkey); {$IFC BUG > -1} 100: Case theItem OF 1: ToggleFree; 2: dummy := MaxMem (dummy); {$IFC BUG = 1} 3: BEGIN debug := NOT debug; CheckItem (MyMenus[DebugMenu], 3, debug) END {$ENDC} END { of debug } {$ENDC} END; { of menu case } HiliteMenu(0) END END; { of DoCommand } {------------------------------------------------------------------------------------} PROCEDURE DrawWindow; VAR tempPort : GrafPtr; tempscrap: handle; scraplength, off: longint; temprect, rectToErase: rect; str: str255; tempPeek: WindowPeek; whichwindow: windowptr; temphTE: TEHandle; tempdata: mydatahandle; BEGIN { Draws the content region of the given window, after erasing whatever was there before. } IF debug THEN DebugInProc (proc, 'DrawWindow', @DrawWindow); WhichWindow := WindowPtr (MyEvent.message); BeginUpdate(WhichWindow); GetPort (tempPort); SetPort (WhichWindow); tempPeek := WindowPeek (WhichWindow); Case tempPeek^.windowkind of MyDocument : BEGIN temprect := WhichWindow^.portrect; tempData := MyDataHandle (GetWRefCon (WhichWindow)); temphTE := tempData^^.TERecord; IF tempPeek^.hilited THEN temprect.top := temprect.bottom - 15; temprect.left := temprect.right - 15; ClipRect (temprect); DrawGrowIcon(WhichWindow); Cliprect (WhichWindow^.portrect); DrawControls (WhichWindow); {this only erases the window past the end of text, if any} WITH temphTE^^ DO IF nlines - topline < (viewrect.bottom - viewrect.top + lineheight) DIV lineheight THEN BEGIN rectToErase := viewrect; rectToErase.top := (nlines - topline) * lineheight; EraseRect (rectToErase) END; TEUpdate(WhichWindow^.visRgn^^.rgnBBox, temphTE) END; ClipBoard : BEGIN tempscrap := NewHandle (0); ScrapLength := GetScrap (tempscrap, 'TEXT', off); EraseRect (WhichWindow^.portrect); temprect := Whichwindow^.portrect; temprect.left := temprect.left + 4; temprect.right := temprect.right-15; IF ScrapLength > 0 THEN BEGIN HLock (tempScrap); Textbox (tempscrap^, scrapLength, temprect, teJustLeft); HUnlock (tempScrap) END; DisposHandle (tempscrap); temprect := WhichWindow^.portrect; temprect.left := temprect.right - 15; ClipRect (temprect); DrawGrowIcon (WhichWindow); ClipRect (whichwindow^.portrect) END; {$IFC BUG > -1} FreeMemory: BEGIN EraseRect(whichwindow^.portrect); MoveTo (5, 12); Write (FreeMem); END; {$ENDC} END; {Case} SetPort (tempPort); EndUpdate(WhichWindow) END; { of DrawWindow } {$S CONTROL} {------------------------------------------------------------------------------------} PROCEDURE ScrollBits; VAR oldvert: INTEGER; BEGIN {If the visible information has changed, scroll the window here.} IF debug THEN DebugInProc (proc, 'ScrollBits', @ScrollBits); oldvert := topline; topline := GetCtlValue(vScroll); TEScroll (0, (oldvert - topline)*hTE^^.lineheight, hTE) END; {------------------------------------------------------------------------------------} PROCEDURE TrackScroll(theControl: ControlHandle; partCode: INTEGER); {This routine adjusts the value of the scrollbar.} Var amount, StartValue : INTEGER; up : BOOLEAN; BEGIN up := partcode IN [inUpButton, inPageUp]; {TRUE if scrolling page up} StartValue := GetCtlValue (theControl); {the initial control value} IF {the scrollbar value is decreased, and it is not already at the minimum} ((up AND (StartValue > GetCtlMin (theControl))) OR {the scrollbar value is increased, and it is not already at the maximum} ((NOT up) AND (StartValue < GetCtlMax (theControl)))) AND {to prevent tracking as the page up or down area disappears} (whichpart = partCode) THEN BEGIN IF up THEN amount := -1 ELSE amount := 1; {set the direction} IF partCode IN [inPageUp, inPageDown] THEN {change the movement to a full page} WITH HTE^^.viewrect DO amount := amount * (bottom - top) DIV hTE^^.lineheight; SetCtlValue(theControl, StartValue+amount); ScrollBits END END; {of TrackScroll} {------------------------------------------------------------------------------------} PROCEDURE MyControls; Var t, code: INTEGER; AControl: ControlHandle; dummy: INTEGER; BEGIN {controls} {This routine handles the scrollbar} IF debug THEN DebugInProc (proc, 'MyControls', @MyControls); whichPart := FindControl (MyPoint, MyWindow, AControl); IF debug THEN Writeln (debugger, 'whichpart = ', whichpart, lf); IF debug THEN Writeln (debugger, 'ORD( AControl = ', ORD ( AControl), lf); {adjust scrollbar range} IF AControl <> NIL THEN BEGIN VScroll := AControl; IF whichPart = inThumb THEN BEGIN dummy := TrackControl (VScroll, MyPoint, NIL); ScrollBits END {of whichpart} ELSE dummy := TrackControl (VScroll, MyPoint, @TrackScroll) END {AControl <> NIL} END; {controls} {$S Initial } {------------------------------------------------------------------------------------} PROCEDURE SetUp; VAR counter, vRefNum : INTEGER; DScrap : PScrapStuff; hdl, hAppparms : handle; off : longint; apName : Str255; NameHdl : Handle; strhdl : StringHandle; dummyrect : rect; dummy : BOOLEAN; FinderFile : INTEGER; myport : GrafPtr; message : INTEGER; document : appFile; temprgn: rgnhandle; extdebug: Str255; BEGIN {Initialization for a variety of things is done here. This code is 'discarded' after it is executed by an UnLoadSeg. Another good way of initializing a large number of variables would be to create a custom resource which contains initial values for all globals. Then, if the globals are fields in a handle, a single 'GetResource' would initialize all fields.} InitFonts; {I need fonts} FlushEvents(everyEvent,0); {start with a clean slate} TEInit; {I need TextEdit} InitDialogs(NIL); {and I need dialogs, even when printing from Finder} PrintHdl := THPrint (NewHandle (SizeOf(TPrint))); PrOpen; PrintDefault (PrintHdl); getAppParms(apName,vRefNum,hAppParms); {** one day, get file info for apName, to use folder info as appropriate} iBeamHdl := GetCursor(IBeamCursor); HNoPurge (Handle(iBeamHdl));{???} watchHdl := GetCursor(WatchCursor); HNoPurge (Handle(watchHdl));{???} CountAppFiles(message, numfiles); IF debug THEN Writeln (debugger, 'numfiles=',numfiles,lf); finderprint := (message = 1); IF finderprint {User selected 'print' from the Finder} THEN BEGIN GetWMgrPort (myPort); SetPort (myPort); temprgn := NewRgn; GetClip (temprgn); dummyrect := screenbits.bounds; dummyrect.bottom := dummyrect.top + 16; ClipRect (dummyrect); TextBox (pointer(ORD(@apName)+1),ORD(Length(apName)),dummyrect,teJustCenter); SetClip (temprgn); DisposeRgn (temprgn); FOR FinderFile := 1 to numfiles DO BEGIN GetAppFiles(FinderFile, document); WITH document DO IF ftype = 'TEXT' THEN BEGIN dummyrect := screenbits.bounds; dummyrect.bottom := dummyrect.top + 16; InsetRect (dummyrect,10,2); SetPort (myPort); {to allow text measure in TeCalText} hTE := TENew(dummyrect, dummyrect); dummy := ReadFile (vRefNum, fName); {assume that page setup is read in as well} Unloadseg (@ReadFile); MyPrint(FinderFile, fName); SetCursor (watchhdl^^); TEDispose (hTE); {dispose of text edit stuff} ClrAppFiles (FinderFile) END END; hTE := NIL; PrClose END ELSE BEGIN PrClose; InitMenus; { initialize Menu Manager } myMenus[appleMenu] := GetMenu(appleMenu); AddResMenu(myMenus[1],'DRVR'); { desk accessories } FOR counter := FileMenu to EditMenu DO myMenus[counter] := GetMenu(counter); {$IFC BUG > -1} myMenus[DebugMenu] := GetMenu(100); { temporary debug menu } {$ENDC} {$IFC BUG = 1} GetIndString (extdebug,257,1); AppendMenu (myMenus[DebugMenu], extdebug); CheckItem (MyMenus[DebugMenu], 3, debug); {$ENDC} FOR counter:=1 TO lastMenu DO InsertMenu(myMenus[counter],0); DrawMenuBar; dragRect := screenbits.bounds; dragrect.top := dragrect.top + 20; {leave room for menu bar} growRect := dragRect; InsetRect (dragrect, 4, 4); {leave some of dragged rectangle on screen} growrect.left := {replace this with the max font width + constant} 80; growrect.top := 80 {18 + 16*3 + slop?}; doneFlag := FALSE; printflag := FALSE; printing := FALSE; windownum := 1; windowpos := 0; MyFileTypes[0] := 'TEXT'; DScrap := InfoScrap; LastCount := DScrap^.scrapCount - 1; LastPaste := LastCount; Scrapwind := NIL; FOR counter := 1 to numfiles DO BEGIN GetAppFiles (counter, document); WITH document DO IF ftype = 'TEXT' THEN BEGIN MakeAWindow (fName, TRUE); {**could async open while this is going on} IF counter < numfiles THEN DialogueDeactivate; IF NOT ReadFile (vRefNum, fName) THEN BEGIN TEDispose (hTE); hTE := NIL; DisposHandle (Handle (WindowData)); DisposeWindow (MyWindow) END END END; IF Frontwindow = NIL THEN OpenaWindow; {$IFC BUG > -1} Freewind := NIL {$ENDC} END END; { of SetUp} {$S } {------------------------------------------------------------------------------------} PROCEDURE CursorAdjust; VAR mousePt: Point; tempport: grafptr; temppeek: Windowpeek; BEGIN { Take care of application tasks which should be executed when the machine has nothing else to do, like changing the cursor from an arrow to an I-Beam when it is over text that can be edited. } {$IFC BUG >-1} { If the amount of free memory is being displayed in its own window, and if it has changed, then create an update event so that the correct value will be displayed.} IF (FreeWind <> NIL) AND (FreeMem <> OldMem) THEN BEGIN OldMem := FreeMem; GetPort (tempport); SetPort (FreeWind); InvalRect (FreeWind^.portrect); SetPort (tempport) END; {$ENDC} GetMouse(mousePt); {where the cursor is, currently (local to the topmost window)} IF hTE <> NIL {if text edit is currently active, (document window is topmost)} THEN BEGIN TEIdle (hTE); IF (PtInRect(mousePt, hTE^^.viewrect)) {In the text edit viewrect area,} THEN SetCursor(iBeamHdl^^) { make the cursor an I-beam.} ELSE SetCursor(arrow) END ELSE BEGIN {let desk accessories set their own?} temppeek := windowPeek(FrontWindow); IF temppeek = NIL THEN SetCursor (arrow) ELSE IF temppeek^.windowkind > 1 THEN SetCursor (Arrow) END END; {------------------------------------------------------------------------------------} FUNCTION MyGrowZone; BEGIN {This function is called by the memory manager whenever more memory is requested than available. The only time you'll see it in this program is when it initally runs (which is normal) and when it is not checking memory availability when it should. Your program should not rely on resolving memory problems here, because it could be called by the ROM, where, at present, insufficient memory cases are not always handled gracefully.} IF GZCritical THEN BEGIN IF debug THEN Writeln (debugger, 'myGrow cbneeded = ', cbneeded, lf); {Make all data stuctures, including user data, that can be safely released, purgable. If the user has data in memory that has not yet been saved, and if you were not expecting this routine to be called, then the call came from ROM and is important to give the user the chance to save their work. Even if their data is successfully saved, it is likely that the program will have to restart or quit to the Finder.} {could unload segment EditMain and others? Zero scrap?} SetFontLock (FALSE); {at least let go of the application font} END; MyGrowZone := 0 {for now, the memory requests fails unconditionally} END; {------------------------------------------------------------------------------------} PROCEDURE MainEventLoop; Var code: INTEGER; {the type of mousedown event} dummy: BOOLEAN; str : str255; tempport : Grafptr; DScrap: PScrapstuff; BEGIN {This event loop handles most of the communications between this program and events taking place in the outside world. This procedure is also called as the printer idle procedure so that the program appears to be doing background printing.} IF printing THEN BEGIN getport (tempport); setport (printport) END; REPEAT CursorAdjust; SystemTask; {See if a desk accessory has changed the scrap. If so, create an update event to redraw the clipboard.} DScrap := InfoScrap; IF (DScrap^.scrapCount <> LastCount) AND (ScrapWind <> NIL) THEN BEGIN LastCount := DScrap^.scrapCount; Getport (tempport); Setport (scrapwind); InvalRect (scrapwind^.portrect); Setport (tempport) END; IF printflag THEN BEGIN GetWTitle (MyWindow, str); PrOpen; Myprint(1, str); {number of files to print, what to call it} PrClose END; IF GetNextEvent(everyEvent,myEvent) THEN CASE myEvent.what OF mouseDown: BEGIN code := FindWindow(myEvent.where,tempWindow); CASE code OF inMenuBar: DoCommand(FALSE); inSysWindow: SystemClick(myEvent,tempWindow); inDrag: DragWindow(tempWindow,myEvent.where,dragRect); inGoAway: IF TrackGoAway(tempWindow,myEvent.where) THEN CloseAWindow; inGrow: IF Mypeek^.windowkind in [MyDocument,Clipboard] THEN GrowWnd; inContent: BEGIN IF tempWindow <> FrontWindow THEN SelectWindow (tempWindow) ELSE IF hTE <> NIL THEN BEGIN MyPoint := MyEvent.where; GlobalToLocal (MyPoint); IF PtInRect (MyPoint, hTE^^.viewrect) THEN BEGIN IF debug THEN Writeln (debugger, 'point in HTE viewrect', lf); IF (BitAnd (myEvent.modifiers, ShiftKey) <> 0 ) { Shift key pressed } THEN TEClick (MyPoint, TRUE, hTE) ELSE TEClick (MyPoint, FALSE, hTE); END ELSE MyControls END { hTE <> NIL } END { in Content } END { of code case } END; { of mouseDown } keyDown, autoKey: BEGIN theChar := CHR(BitAnd(myEvent.message,255)); { Mac characters use 8 bits } IF BitAnd(myEvent.modifiers,CmdKey) <> 0 { Command key pressed } THEN DoCommand(TRUE) ELSE IF hTE <> NIL THEN BEGIN TEKey(theChar,hTE); windowdata^^.changed := TRUE; ScrollText (TRUE); END END; { of keyDown } activateEvt: MyActivate; updateEvt: DrawWindow; END { of event case } ELSE IF (Myevent.what = nullEvent) AND doneflag AND (FrontWindow <> NIL) THEN CloseAWindow; UnloadSeg (@ScrollText); {segment Utilities} UnloadSeg (@ReadFile); {segment ReadFile} UnloadSeg (@WriteFile); {segment WritFile} UnloadSeg (@AboutMyEditor); {segment AboutMyPgm} UnloadSeg (@DoCommand); {segment DoCommand} UnloadSeg (@EditMain); {segment EditMenu} UnloadSeg (@MyControls); {segment Control} IF NOT printing THEN UnloadSeg (@MyPrint); UNTIL (doneFlag AND (FrontWindow = NIL)) OR Printing; IF doneFlag AND (FrontWindow = NIL) THEN BEGIN ClearMenuBar; {prevent the user from doing anything until printing is through} DrawMenuBar; SetCursor (watchhdl^^) END; IF printing THEN BEGIN getport (printport); setport (tempport) END END; BEGIN { main program } {Some things need to be set up outside of the initialization segment, to allow the nonrelocatable objects that they create to be located as low in memory as possible.} {$IFC BUG = 1} {This code is only included for external terminal debugging} debug := FALSE;{if you want debugging on as soon as the program starts, set it here} Reset (debugger, '.BOUT'); {the serial port not used for downloading from Lisa} {$ENDC} IF debug THEN BEGIN Writeln (debugger, lf, lf); DebugInProc (proc, 'SetUp', @Setup) END; {The program only executes the code when it is first run, but it could have gotten here in two ways. The user may have opened the application or one of its documents, or the user may have chosen to print a document. In any case, some common initialization is needed.} SetGrowZone (@MyGrowZone); {just in case something goes wrong..} MaxApplZone; {The application needs to grow the heap to the maximum size, but does not want to purge any of the preloaded resources. } MoreMasters; MoreMasters; MoreMasters; {Each call to MoreMasters creates a block of master ponters. The system will call MoreMasters when it needs master pointers, but since master pointer blocks are non-relocatable, explicitly calling it a few times early in a program helps to prevent heap fragmentation. } InitGraf(@thePort); {I need QuickDraw} InitWindows; {I need windows} SetUp; UnloadSeg (@Setup); IF NOT finderprint THEN MainEventLoop; SetCursor (watchHdl^^); END.