

|
Volume Number: | 2 | |
Issue Number: | 11 | |
Column Tag: | Forth Forum |
An Edit Task for Forth
By Jörg Langowski, MacTutor Editorial Board, Juri Munkki, Helsinki, Finland
An Editor Task For Mach2
By Juri Munkki, with Jörg Langowski
[Since this column is the Forth Forum, we must give others space for contributions. This month we received an article by one of our readers, Juri Munkki from Helsinki, who devised a rather practical text editor to run under Forth. Evidently they lack a desk accessory editor like MockPackage+ which will run with HFS. With the multi-tasking Mach2, an editor task helps a lot in developing programs; I had the same problems as Juri trying to use Edit 2.0 in connection with Mach2. Mach2 and Edit just don't want to run together under Switcher - even with the cache switched off - on a Mac Plus. Also, Edit is overkill for Forth development since individual program pieces tend to be rather short.
The 32K text size limitation for this editor is therefore no big drawback; in fact, it is more than the 28K of MockWrite. Read the code carefully for some interesting additions to Mach2, like ON, OFF, ANEW, scrap handling words, an on-window palette menu and auto-indentation on text entry.
Enough of a foreword, I'll pass the keyboard to Juri now. J.L.]
Editing a Problem in Mach 2
Mach 2 is a great language, but it certainly has some flaws. The first one that every programmer confronts is the lack of an integrated editor.
Palo Alto Shipping suggests that Edit and Forth are used with Switcher, but I don't like Edit and and it takes most of my 512K Mac's memory to hold Mach I, Edit and Switcher. The first thing that came to my mind was MockWrite. MockWrite is a very good text editor DA, but the versions everybody have here in Finland trash files under HFS. (I had a lot of fun finding that out.)
I didn't know of any other DA editors when I got Mach I, so I thought writing a small editor would not take too much time.
I still had to find a working combination in order to be able to write the editor. The best solution I found, was to use Edit and Mach without the Switcher. In Edit, I could use "Other" to launch Forth and in Mach, I used an FKEY to launch Edit. I modified the menu in Edit so that I could switch programs with a command key combination. With the cache on, it wasn't unbearably slow, but it wasn't very interactive.
I have done most of my Macintosh programming in MacForth because it was the first good language available. Mach I is different, but learning it was easy since the manual is good and I already knew the toolbox. I wouldn't recommend Mach I to a beginner, but it is very good as a second language. I still miss many of the nice little commands that MacForth has.
I extended Mach I with some MacForth words that I thought would be useful. Anew was probably the most important. In.Heap would also have been nice, but HeapVar was the best I could do with my knowledge of Forth. Heapvar is used to return the heap space that is pointed to by the handle in a variable. I also added RECT, !RECT, ON and OFF. On and Off are very simple, but they make code much more readable and speed up programs.
Example of "HeapVar":
HeapVar A.Variable Anew MyProgram Variable A.Variable 100 Call NewHandle Drop A.Variable !
The editor uses the ROM TextEdit engine and is thus limited to a maximum of 64K of text or 32767 lines. I have limited the editor to 32K of text in order to avoid checking for the maximum amount of lines. The commented source code of TEDDY is about 25K. This limits the amount of source code to 32K, but code segments are not allowed to be larger than 32K.
Teddy is not the first Editor I have written. I wrote a similar program in MacForth when I thought it would be a nice addition to my terminal program. The MacForth version of Teddy is even simpler than MachTeddy, but the experience helped me avoid some bugs and problems.
The program conforms closely to the Macintosh user interface guidelines. The only nonstandard thing I can think of is the lack of an I-beam cursor. I forgot the cursor at first, but when I thought of it, it no longer seemed worth adding. This is a programmer's tool, after all. My MacForth Teddy has an I-Beam cursor, since it is used by non-programmers who expect it.
The comments in the program demonstrate the things that have been less documented in MacTutor. Scrap handling is heavily documented because the program does it 100% according to Inside Macintosh and it has not been discussed in MacTutor. Inside Macintosh gives very specific rules on scrap management.
The scrap is usually held in two places. The desk accessories expect it to be in the desk scrap and every program usually has its own clipboard format. Text Edit has two global variables to handle the clipboard. The TE-scrap must be converted to the global scrap after a cut or paste has been made.
Teddy avoids useless scrap conversion. If a program first converts the scrap to text and then puts it back without a change, all others' formats are lost. Consider a situation where a user has three programs under Switcher and two of these understand pictures. If a picture is copied and then the user switches to the other program, by first switching to the text application, the text application should not delete the picture just because it doesn't like pictures. Destroy the desk scrap only when you have to change it!
Teddy keeps a flag to monitor changes in the text edit scrap. If a cut or copy is made, the flag is set and the scrap will be converted when the window is deactivated. Desk scrap is always moved to the text edit scrap if the editor window is activated.
Menus are a very good invention, but sometimes a palette on a window is easier and faster to use. My first experiment with palettes was with the MacForth version of Teddy. I have used them since then on many of my programs. It would be very easy to add keyboard equivalents to these functions, but Teddy has none. A palette is very simple to implement: it is a collection of boxes that act like buttons. I first tried to use a collection of buttons, but a button takes too much space.
Teddy scrolls automatically if the user tries to type outside the screen. The selection range is used to determine if the screen should be scrolled. Cursor keys work well, because the person who wrote Text Edit was clever enough to implement them.
Clickloop procedures are very hard to do in Forth, so I wrote the scroll routine in assembler. The clickloop routine changes the setting of the scroll bar, but the changes are not drawn during the scrolling because Text Edit sets the clipping rectangle to the text edit view rectangle. If you want to be able to monitor the selection from the scroll bar, insert a few toolbox calls to change the clipping rectangle.
The Macintosh File Manager is easy to use (once you figure out how), but requires a large amount of initialization. I haven't figured how to use the Mach 2 file system, so it wasn't really hard to write the routines in "pure toolbox" instead of Forth. Note that HFS is supported simply by using the volrefnum from the sfreply and putting it in the parmblock. The current (last used) filename and volrefnum are remembered for later use. Loading and saving stop multitasking.
The "Enter" key is useless in MacWrite, but in this editor it provides an indenting alternative to the "Return" key. You might want to switch the meaning of return and enter. Enter looks at the line you are on, adds a return and a few spaces to align the new line with the previous line. Tab generates four spaces.
When I write programs, I use a workspace that contains Teddy and some extensions. The resources (DLOG, ALRT, DITL, PICT) are in the Mach 2 file because that way they aren't deleted when the workspace is updated. Teddy should be snapshotted before it is called, or the workspace will not work.
Teddy might serve as a basis for creating more specialized editors. You may use this source code in any program you write, but it would be nice to mention me in the "About" dialog.
I usually edit a program, save it and load it from a file because this forces me to make constant backups to the disk. Even a very small cache is enough to keep the file in RAM and compiling is just as fast as compiling from the clipboard.
Let's program!
Installation Notes - J.L.
To install the editor in your Mach2 (or Mach1) system, you should create a Workspace immediately after loading the file by either saying workspace ted or new-segment ted (the latter leaving a full 32K of code space after the editor has been invoked). Make sure the workspace file is on the same disk as the copy of Mach2 that it is run with, otherwise the workspace won't work.
You can then, as usual, click the workspace icon and call the editor from within Mach by typing ted. The resources used by the editor should be in the Mach file (see above). Listing 2 shows an Rmaker input to create those resources, which can then be pasted with ResEdit. The PICT ID=900 is not printed here. However, it is on the resource file Teddy.rsrc on our source code disk. I'd recommend you to get that disk anyway instead of typing in all the code.
Listing 1: Teddy, A Text Editor Task for Mach1. ( Teddy -- Text Editor ) ( Contains MacForth-like extensions to Mach I in addition to the editor. Type TED to call the editor at any time. The MacForth-style extensions are mostly undocumented here. Look for examples in this source. ) ( Anew: Used in the form: ANEW PROGRAM_NAME. It tries to find the PROGRAM_NAME and forget it if it is found. It then creates PROGRAM_NAME and continues. It should be used in the beginning of the program. Old versions are then automatically forgotten, if they exist. ) ONLY FORTH DEFINITIONS ALSO MAC ALSO ASSEMBLER : ANEW { | LEN } 32 WORD DUP C@ 1+ NEGATE -> LEN FIND SWAP DROP IF LEN >IN +! FORGET CALL DRAWMENUBAR THEN LEN >IN +! CREATE DOES> DROP ; ( Heapvar: Used in the form: HEAPVAR VARIABLE_NAME. If VARIABLE_NAME exists, it returns the handle from VARIABLE_NAME to the heap. It should be used before ANEW to free space from the heap. ) : HEAPVAR 32 WORD FIND IF LINK>BODY EXECUTE @ DUP IF DUP CALL HUNLOCK DROP CALL DISPOSHANDLE DROP ELSE DROP THEN ELSE DROP THEN ; : RECT CREATE SWAP 2SWAP SWAP W, W, W, W, ; GLOBAL CODE !RECT MOVE.L (A6)+,A0 MOVE.W 14(A6),(A0)+ MOVE.W 10(A6),(A0)+ MOVE.W 6(A6),(A0)+ MOVE.W 2(A6),(A0)+ ADDA.L #16,A6 RTS END-CODE GLOBAL CODE OFF MOVEA.L (A6)+,A0 CLR.L (A0) RTS END-CODE MACH GLOBAL CODE ON MOVEA.L (A6)+,A0 MOVE.L #-1,(A0) RTS END-CODE GLOBAL CODE SCALE MOVE.L (A6)+,D0 BMI.S @1 MOVE.L (A6),D1 ASL.L D0,D1 MOVE.L D1,(A6) RTS @1 MOVE.L (A6),D1 NEG.L D0 ASR.L D0,D1 MOVE.L D1,(A6) RTS END-CODE GLOBAL CODE @MOUSE SUBQ.L #4,A6 MOVE.L A6,-(A7) _GETMOUSE RTS END-CODE HEADER TEDDY.W2 DC.L 0 HEADER TEDDY.T2 DC.L 0 HEADER TEDDY.S2 DC.L 0 CODE CLICKPROC MOVEM.L D1-D3/A0-A4,-(A7) CLR.L -(A7) MOVE.L A7,-(A7) _GETMOUSE ( Where is the mouse cursor? ) MOVE.L (A7)+,D0 SWAP.W D0 ( Get the Y-location to D0.W ) CMP.W #18,D0 ( Is Mouse.Y smaller than 18? ) BLT.S @1 MOVE.L TEDDY.W2,A0 MOVE.W 20(A0),D1 SUB.W #16,D1 CMP.W D1,D0 ( Is Mouse.Y below the text? ) BGE.S @2 @4 MOVEM.L (A7)+,D1-D3/A0-A4 MOVEQ.L #1,D0 @3 RTS @1 CLR.W -(A7) ( Are we allowed to scroll down? ) MOVE.L TEDDY.S2,-(A7) _GETCTLVALUE MOVE.W (A7)+,D0 BEQ.S @4 ( If we are on top, do nothing ) SUBQ.W #1,D0 ( Scroll one line up ) MOVE.L TEDDY.S2,-(A7) MOVE.W D0,-(A7) _SETCTLVALUE CLR.W -(A7) MOVE.W #11,-(A7) ( One line = 11 pixels ) MOVE.L TEDDY.T2,-(A7) _TESCROLL ( Scroll the text ) MOVEM.L (A7)+,D1-D3/A0-A4 MOVEQ.L #1,D0 RTS @2 CLR.W -(A7) MOVE.L TEDDY.S2,-(A7) _GETCTLVALUE ( Where are we? ) MOVE.W (A7)+,D3 CLR.W -(A7) MOVE.L TEDDY.S2,-(A7) _GETMAXCTL ( How high can we go? ) MOVE.W (A7)+,D0 CMP.W D0,D3 BGE.S @4 ADDQ.W #1,D3 ( Scroll one line... ) MOVE.L TEDDY.S2,-(A7) MOVE.W D3,-(A7) _SETCTLVALUE CLR.W -(A7) MOVE.W #-11,-(A7) MOVE.L TEDDY.T2,-(A7) _TESCROLL MOVEM.L (A7)+,D1-D3/A0-A4 MOVEQ.L #1,D0 RTS END-CODE ( The following routine is quite simple. All it does is search a string for another one ignoring case and it returns the offset or a flag. ) CODE FINDER ( ?STR ?LEN SEARCHSTR SEARCHLEN -- OFFSET ) MOVEM.L D0-D7/A0-A4,-(A7) MOVE.L (A6)+,D0 MOVE.L (A6)+,A0 MOVE.L (A6)+,D1 MOVE.L (A6)+,A1 MOVE.W D0,D2 SUB.W D1,D2 CLR.L D7 @1 CLR.W D3 @2 MOVE.B 0(A0,D3.W),D4 BMI.S @3 CMP.B #96,D4 BLT.S @3 SUB.B #32,D4 ( Remove case ) @3 MOVE.B 0(A1,D3.W),D5 BMI.S @4 CMP.B #96,D5 BLT.S @4 SUB.B #32,D5 ( Remove case ) @4 CMP.B D4,D5 ( Is a char equal to another? ) BNE.S @5 ADDQ.W #1,D3 ( It was, one match ) CMP.W D1,D3 ( Have we found the string? ) BLT.S @2 MOVE.L D7,-(A6) MOVEM.L (A7)+,D0-D7/A0-A4 RTS @5 ADDQ.L #1,A0 ( No match...yet ) ADDQ.L #1,D7 DBRA D2,@1 ( Look again? ) MOVE.L #-1,-(A6) ( No match...return -1 ) MOVEM.L (A7)+,D0-D7/A0-A4 RTS END-CODE ( 4ASCII nnnn converts the 4 character string into its numeric value. It can only be used in the immediate mode. Examples below ) : 4ASCII 0 4 0 DO 8 SCALE 0 WORD 1+ C@ + LOOP ; ONLY FORTH ALSO MAC 4ASCII TEXT CONSTANT "TEXT 4ASCII DRVR CONSTANT DRIVER 4ASCII MACA CONSTANT "MACA HEX AB0 CONSTANT TESCRAP.LEN ( Global TeEdit private scrap variables ) AB4 CONSTANT TESCRAP.HANDLE DECIMAL NEW.WINDOW TEDDY.W " Text Editor" TEDDY.W TITLE 50 0 304 480 TEDDY.W BOUNDS ZOOM VISIBLE CLOSEBOX GROWBOX TEDDY.W ITEMS 400 4000 TERMINAL TEDDY.TASK NEW.MBAR TEDDY.BAR 900 CONSTANT APPLEID NEW.MENU APPLEMENU HERE 1 C, 20 C, APPLEMENU TITLE " About Edit...;(-" APPLEMENU ITEMS ( Add DAs later ) 0 APPLEID APPLEMENU BOUNDS 901 CONSTANT TFILEID NEW.MENU TFILE " File" TFILE TITLE " Open/O;Save/S;Save as..." TFILE ITEMS 0 TFILEID TFILE BOUNDS 902 CONSTANT TEDITID NEW.MENU TEDITMENU " Edit" TEDITMENU TITLE " Cut/X;Copy/C;Paste/V;Select All & Copy;-(;Find/F;Again/G(" TEDITMENU ITEMS 0 TEDITID TEDITMENU BOUNDS : ADD.DRVRS ( Add desk accessories ) APPLEMENU @ DRIVER CALL ADDRESMENU ; NEW.CONTROL TEDDY.SB VSCROLLBAR VISIBLE 100 0 TEDDY.SB ITEMS : DAHANDLER { ITEM | Daname } ITEM 2 > IF ( We must open a desk accessory ) 256 CALL NEWPTR -> DANAME ( Get us a STR255 for the name ) APPLEMENU @ ITEM DANAME CALL GETITEM DANAME CALL OPENDESKACC DROP ( Open the desk accessory ) DANAME CALL DISPOSPTR ( Give the String back ) ELSE ITEM 1 = ( The about edit alert should be shown. The resource must be added separately to Mach 2. ) IF 900 0 CALL ALERT DROP THEN THEN ; HEX 44 CONSTANT txFont ( Offsets in a window record ) 46 CONSTANT txFace 48 CONSTANT txMode 4A CONSTANT txSize 6C CONSTANT WindowKind DECIMAL VARIABLE TEDDY.T ( PLACEHOLDER FOR TEXT HANDLE ) VARIABLE ACTIVE? ( ACTIVE FLAG ) VARIABLE MUSTCONVERT ( SCRAP CONVERSION FLAG ) 20 CONSTANT UPARROW ( Part codes ) 21 CONSTANT DOWNARROW 22 CONSTANT PAGEUP 23 CONSTANT PAGEDOWN 129 CONSTANT THUMB VARIABLE CURMAX ( Current scroll bar range ) VARIABLE CURSET ( Current scroll bar setting ) : CORRECT.CONTROL.RANGE CURMAX @ TEDDY.T @ @ 94 + W@ 1- 0 MAX DUP CURMAX ! = NOT IF TEDDY.SB @ CURMAX @ CALL SETMAXCTL THEN ; : CORRECT.CONTROL ( Set scroll bar ) CURSET @ ( Look at the destination RECT for the position ) 18 TEDDY.T @ @ W@ L_EXT - 11 / DUP CURSET ! = NOT IF TEDDY.SB @ CURSET @ CALL SETCTLVALUE THEN ; : TOO.HIGH.TEDDY ( Autoscroll, when typing ) 0 TEDDY.W 20 + W@ 40 - TEDDY.T @ @ 16 + W@ L_EXT - DUP 11 MOD - ?DUP IF ( If tescroll is called with 0 0, the caret disappears! ) TEDDY.T @ CALL TESCROLL ELSE DROP THEN CORRECT.CONTROL ; : TOO.LOW.TEDDY ( Autoscroll, when typing ) 0 29 TEDDY.T @ @ 16 + W@ L_EXT - DUP 11 MOD - TEDDY.T @ CALL TESCROLL CORRECT.CONTROL ; : CORRECTSCROLL ( If the user is typing, check if we should scroll. ) TEDDY.T @ @ 32 + DUP W@ SWAP 2+ W@ = ( Do we have a caret? ) IF TEDDY.T @ @ 16 + W@ L_EXT 29 < IF TOO.LOW.TEDDY ELSE TEDDY.T @ @ 16 + W@ L_EXT TEDDY.W 20 + W@ 40 - > IF TOO.HIGH.TEDDY THEN THEN THEN ; CREATE TEMR 8 ALLOT ( Temporary storage ) : SCRAP->TE ( Convert global scrap to TeScrap ) 0 "TEXT TEMR CALL GETSCRAP 0> ( Is there text? ) IF TESCRAP.HANDLE @ "TEXT TEMR CALL GETSCRAP TESCRAP.LEN W! THEN MUSTCONVERT OFF ( The scrap does not have to be converted just now ) ; : TE->SCRAP MUSTCONVERT @ ( Are there any changes after SCRAP->TE? ) IF CALL ZEROSCRAP DROP ( Zero scrap to clear non-text entries ) TESCRAP.LEN W@ "TEXT TESCRAP.HANDLE @ @ CALL PUTSCRAP DROP THEN ; : CLEAR.TESCRAP ( Word used to clear tescrap when it is not needed ) TESCRAP.HANDLE @ 0 CALL SETHANDLESIZE DROP 0 TESCRAP.LEN W! ; VARIABLE OLDPORT ( Used to save the current window before a dialog ) CREATE DLOG900 0 , ( Handle storage for our "FIND" dialog ) VARIABLE DEVENT ( Dialog "event" ) ( You can set the following strings from Forth and then use "aGain" to replace any untypeable characters. Do a find or replace, then set teddy.f1 and f2 and choose "aGain". This will do the previous operation with the new strings! ) CREATE TEDDY.F1 256 ALLOT ( String to find ) CREATE TEDDY.F2 256 ALLOT ( Replace string ) ( The following part finds Teddy.F1 from the text ) : TFIND.REALLY { | SELEND STRSTART } TEDDY.T @ @ 34 + W@ -> SELEND TEDDY.T @ @ 62 + @ @ -> STRSTART TEDDY.F1 COUNT ?DUP IF STRSTART SELEND + TEDDY.T @ @ 60 + W@ SELEND - DUP TEDDY.F1 C@ > IF FINDER DUP 0< IF DROP 10 CALL SYSBEEP 0 0 TEDDY.T @ CALL TESETSELECT ELSE SELEND + TEDDY.F1 C@ + DUP TEDDY.T @ CALL TESETSELECT THEN ELSE 2DROP 2DROP 10 CALL SYSBEEP 0 0 TEDDY.T @ CALL TESETSELECT THEN CORRECTSCROLL ELSE DROP THEN ; ( The following finds Teddy.F1 and replaces it with Teddy.F2 ) : TEDDY.REPLACE { | SELEND STRSTART } TEDDY.T @ @ 34 + W@ -> SELEND TEDDY.T @ @ 62 + @ @ -> STRSTART TEDDY.F1 COUNT ?DUP IF STRSTART SELEND + TEDDY.T @ @ 60 + W@ SELEND - DUP TEDDY.F1 C@ > IF FINDER DUP 0< IF DROP 10 CALL SYSBEEP 0 0 TEDDY.T @ CALL TESETSELECT ELSE DUP SELEND + TEDDY.F1 C@ OVER + TEDDY.T @ CALL TESETSELECT TEDDY.T @ CALL TEDELETE TEDDY.F2 COUNT TEDDY.T @ CALL TEINSERT SELEND + TEDDY.F2 C@ + DUP TEDDY.T @ CALL TESETSELECT THEN ELSE 2DROP 2DROP 10 CALL SYSBEEP 0 0 TEDDY.T @ CALL TESETSELECT THEN CORRECTSCROLL ELSE DROP THEN ; : TEDDYFIND.SUB ( Find or replace according to button ) DEVENT W@ CASE 1 OF TFIND.REALLY ENDOF 2 OF TEDDY.REPLACE ENDOF ENDCASE ; : TEDDYFIND TE->SCRAP ( Forth receives an activate when the dialog is gone. ) ( The scrap must be saved to preserve it. ) TEDDY.T @ CALL TEDEACTIVATE DLOG900 @ 0= IF 900 0 -1 CALL GETNEWDIALOG DLOG900 ! ELSE DLOG900 @ CALL BRINGTOFRONT DLOG900 @ CALL SHOWWINDOW THEN OLDPORT CALL GETPORT DLOG900 @ CALL SETPORT ( Set the dialog port ) BEGIN 0 DEVENT CALL MODALDIALOG ( Call this until the user has finished ) DEVENT W@ 4 < UNTIL OLDPORT @ CALL SETPORT ( Reset "predialog" environment ) DLOG900 @ 5 PAD PAD 4 + PAD 8 + CALL GETDITEM PAD 4 + @ TEDDY.F1 CALL GETITEXT ( Set Teddy.F1 ) DLOG900 @ 6 PAD PAD 4 + PAD 8 + CALL GETDITEM PAD 4 + @ TEDDY.F2 CALL GETITEXT ( Set Teddy.F2 ) DLOG900 @ CALL HIDEWINDOW TEDDY.T @ CALL TEACTIVATE TEDITMENU @ 7 CALL ENABLEITEM TEDDYFIND.SUB ; ( Handle Cut/Copy/Paste and others for Teddy and DAs ) : TEDITHANDLER { ITEM } CALL FRONTWINDOW TEDDY.W = IF ( Editor cut/paste ) ITEM CASE 1 OF TEDDY.T @ CALL TECUT MUSTCONVERT ON ENDOF 2 OF TEDDY.T @ CALL TECOPY MUSTCONVERT ON ENDOF 3 OF TESCRAP.LEN W@ TEDDY.T @ @ 60 + W@ TEDDY.T @ @ 34 + W@ TEDDY.T @ @ 32 + W@ - - + 32767 < IF TEDDY.T @ CALL TEPASTE ELSE 5 CALL SYSBEEP 5 CALL SYSBEEP THEN ENDOF 4 OF 0 TEDDY.T @ @ 60 + W@ TEDDY.T @ CALL TESETSELECT TEDDY.T @ CALL TECOPY MUSTCONVERT ON ENDOF 6 OF TEDDYFIND ENDOF 7 OF TEDDYFIND.SUB ENDOF ENDCASE CORRECT.CONTROL.RANGE CORRECTSCROLL ELSE ( DA cut/copy/paste...Undo is left for you to add... ) CALL FRONTWINDOW WINDOWKIND + W@ L_EXT 0< IF ITEM 4 < IF ITEM 1+ CALL SYSEDIT DROP THEN THEN THEN ; ALSO ASSEMBLER ( Here we have support for SFGETFILE and SFPUTFILE; these routines are similar to the ones in the Mach 2 manual. ) HEADER TYPES DC.B 'TEXT' HEADER GPROMPT DC.B 20 DC.B 'Please select a file' HEADER PPROMPT DC.B 18 DC.B 'Please type a name' CODE TEDDYGETFILE MOVE.W #50,-(A7) MOVE.W #50,-(A7) PEA GPROMPT CLR.L -(A7) MOVE.W #1,-(A7) PEA TYPES CLR.L -(A7) MOVE.L (A6)+,-(A7) MOVE.W #2,-(A7) _PACK3 RTS END-CODE CODE TEDDYPUTFILE MOVE.W #50,-(A7) MOVE.W #50,-(A7) PEA PPROMPT MOVE.L (A6)+,-(A7) CLR.L -(A7) MOVE.L (A6)+,-(A7) MOVE.W #1,-(A7) _PACK3 RTS END-CODE ONLY FORTH ALSO MAC ALSO TALKING 230 USER PARMBLK CREATE FNAME 0 C, 63 ALLOT ( Our file has a name. This is where it is kept) CREATE FPLACE 0 , ( This is the folder of our file. HFS! ) ( Here we do some "dirty" programming. I use the file manager directly. This works, but the code is not very clear. Once the PARaMeterBLocK is set, it doesn't need to be changed much. Read Inside Macintosh for details on parameter blocks and the file system. ) : TEDDYLOAD ( Replace selection range with a file ) TE->SCRAP PAD TEDDYGETFILE ( Use PAD as SFREPLY ) PAD C@ IF PAD 10 + FNAME 64 CMOVE PAD 6 + W@ FPLACE ! PARMBLK 12 + OFF PAD 10 + PARMBLK 18 + ! PAD 6 + W@ PARMBLK 22 + W! 0 PARMBLK 26 + W! PARMBLK 28 + OFF PARMBLK CALL OPEN IF 10 CALL SYSBEEP ( Ouch! File Error ) ELSE PARMBLK CALL GETEOF DROP PARMBLK 28 + @ TEDDY.T @ @ 60 + W@ TEDDY.T @ @ 34 + W@ TEDDY.T @ @ 32 + W@ - - + 32767 < ( Does the result fit? ) IF TESCRAP.HANDLE @ DUP DUP PARMBLK 28 + @ CALL SETHANDLESIZE DROP CALL GETHANDLESIZE TESCRAP.LEN W! CALL HLOCK DROP TESCRAP.HANDLE @ @ PARMBLK 32 + ! TESCRAP.LEN W@ PARMBLK 36 + ! 0 PARMBLK 44 + W! 0 PARMBLK 46 + ! PARMBLK CALL READ DROP TESCRAP.HANDLE @ CALL HUNLOCK DROP PARMBLK CALL CLOSE DROP TEDDY.T @ CALL TEPASTE CORRECT.CONTROL.RANGE CORRECTSCROLL ELSE 5 CALL SYSBEEP 5 CALL SYSBEEP ( Ouch! Text too long ) THEN THEN THEN ; : TEDDYSAVE ( Save selection range ) TE->SCRAP PAD FNAME TEDDYPUTFILE PAD C@ IF PAD 10 + FNAME 64 CMOVE PAD 6 + W@ FPLACE ! PARMBLK 12 + OFF PAD 10 + PARMBLK 18 + ! PAD 6 + W@ PARMBLK 22 + W! 0 PARMBLK 26 + W! PARMBLK 28 + OFF PARMBLK CALL CREATE DROP PARMBLK CALL OPEN DROP TEDDY.T @ @ 34 + W@ TEDDY.T @ @ 32 + W@ - ?DUP 0= IF TEDDY.T @ @ 60 + W@ THEN PARMBLK 28 + ! PARMBLK CALL SETEOF DROP TEDDY.T @ @ 34 + W@ TEDDY.T @ @ 32 + W@ - ?DUP 0= IF TEDDY.T @ @ 60 + W@ PARMBLK 36 + ! TEDDY.T @ @ 62 + @ @ PARMBLK 32 + ! ELSE PARMBLK 36 + ! TEDDY.T @ @ 62 + @ @ TEDDY.T @ @ 32 + W@ + PARMBLK 32 + ! THEN 0 PARMBLK 44 + W! PARMBLK 46 + OFF PARMBLK CALL WRITE PARMBLK CALL FLUSHFILE DROP PARMBLK CALL CLOSE DROP IF 10 CALL SYSBEEP PARMBLK CALL DELETE ELSE PARMBLK CALL GETFILEINFO DROP "TEXT PARMBLK 32 + ! ( Text filestype TEXT! ) "MACA PARMBLK 36 + ! ( MacWrite files ) PARMBLK CALL SETFILEINFO DROP THEN PARMBLK 18 + OFF PARMBLK CALL FLUSHVOL DROP THEN ; : TEDDYSAVEALL ( Save the whole file ) TEDDY.T @ @ 60 + W@ IF TE->SCRAP FNAME C@ IF PARMBLK 12 + OFF FNAME PARMBLK 18 + ! FPLACE @ PARMBLK 22 + W! 0 PARMBLK 26 + W! PARMBLK 28 + OFF PARMBLK CALL CREATE DROP PARMBLK CALL OPEN DROP TEDDY.T @ @ 60 + W@ PARMBLK 28 + ! PARMBLK CALL SETEOF DROP TEDDY.T @ @ 60 + W@ PARMBLK 36 + ! TEDDY.T @ @ 62 + @ @ PARMBLK 32 + ! 0 PARMBLK 44 + W! PARMBLK 46 + OFF PARMBLK CALL WRITE PARMBLK CALL FLUSHFILE DROP PARMBLK CALL CLOSE DROP IF 10 CALL SYSBEEP PARMBLK CALL DELETE ELSE PARMBLK CALL GETFILEINFO DROP "TEXT PARMBLK 32 + ! "MACA PARMBLK 36 + ! PARMBLK CALL SETFILEINFO DROP THEN PARMBLK 18 + OFF PARMBLK CALL FLUSHVOL DROP THEN THEN ; : TFILEHANDLER ( Handle the file menu ) CASE 1 OF 0 TEDDY.T @ @ 60 + W@ TEDDY.T @ CALL TESETSELECT TEDDYLOAD ENDOF 2 OF TEDDYSAVEALL ENDOF 3 OF TEDDY.T @ @ 32 + @ 0 TEDDY.T @ @ 32 + ! TEDDYSAVE TEDDY.T @ @ 32 + ! ENDOF ENDCASE ; : TEDDYMENUS ( Menu events are delivered here ) 0 CALL HILITEMENU CASE APPLEID OF DAHANDLER ENDOF TFILEID OF TFILEHANDLER ENDOF TEDITID OF TEDITHANDLER ENDOF ENDCASE ; ( This program has a menu on its window. There are 5 items on this menu and the names of these items have to be kept somewhere. This was a simple way to create an array of strings. ) : TITLES CASE 0 OF " Select All" ENDOF 1 OF " Select Forward" ENDOF 2 OF " Select Backward" ENDOF 3 OF " Copy From Disk" ENDOF 4 OF " Save Selection" ENDOF ENDCASE ; : DRAWTITLES ( Draw palette items ) PAD CALL GETPORT ( Get our window ) PAD @ TXFONT + W@ ( Save text characteristics ) PAD @ TXSIZE + W@ PAD @ TXMODE + W@ 1 CALL TEXTFONT ( Geneva ) 9 CALL TEXTSIZE ( 9 point ) 1 CALL TEXTMODE 5 0 DO ( 5 items in our palette ) 2 I 90 * 2+ 15 OVER 91 + TEMR !RECT TEMR CALL ERASERECT TEMR CALL FRAMERECT I 90 * 47 + I TITLES CALL STRINGWIDTH 2/ - ( Center the string ) 12 CALL MOVETO I TITLES CALL DRAWSTRING LOOP CALL TEXTMODE ( Reset text charasteristics ) CALL TEXTSIZE CALL TEXTFONT ; 168 USER UPDATE-HOOK ( Mach 2 has a lot of stupid hooks ) 152 USER CONTENT-HOOK ( I have to live with them ) 172 USER ACTIVATE-HOOK ( even if I do not like them ) CREATE SPORT 4 ALLOT ( Saved Port ) : GROWB ( Set the view rectangle ) TEDDY.W 20 + W@ 16 - 16 SCALE TEDDY.W 22 + W@ 16 - + TEDDY.T @ @ 12 + ! ; : TEDDYUP ( Update events are delivered here ) ( Note that the zoom box also generates an update event! ) SPORT CALL GETPORT ( Save some external window ) TEDDY.W CALL SETPORT ( Use the text editor window for updates ) TEDDY.W CALL BEGINUPDATE ( Inside Mac says this must be done ) GROWB TEDDY.W 16 + CALL ERASERECT ( Erase area to be updated ) DRAWTITLES ( Draw palette titles ) TEDDY.W 16 + TEDDY.T @ CALL TEUPDATE TEDDY.W CALL DRAWCONTROLS ( We have a scroll bar to update ) TEDDY.W CALL DRAWGROWICON TEDDY.W CALL ENDUPDATE SPORT @ CALL SETPORT ( Restore the port before the update ) ; ( Given the number of the palette item that the mouse was pressed in, this procedure tracks the mouse to see what the user really wants. ) : DOPALETTE.SUB { SELECTED | SLOC } 3 SELECTED 90 * 3 + 14 OVER 89 + TEMR !RECT 0 -> SLOC BEGIN CALL STILLDOWN WHILE @MOUSE TEMR CALL PTINRECT 0= 0= SLOC XOR IF TEMR CALL INVERTRECT SLOC NOT -> SLOC THEN REPEAT SLOC IF TEMR CALL INVERTRECT SELECTED 1+ ELSE 0 THEN ; : DOPALETTE ( There is a mousedown in the palette ) @MOUSE L_EXT 2 - 90 / DOPALETTE.SUB CASE 1 OF 0 TEDDY.T @ @ 60 + W@ TEDDY.T @ CALL TESETSELECT ENDOF 2 OF TEDDY.T @ @ 32 + W@ TEDDY.T @ @ 60 + W@ TEDDY.T @ CALL TESETSELECT ENDOF 3 OF 0 TEDDY.T @ @ 34 + W@ TEDDY.T @ CALL TESETSELECT ENDOF 4 OF TEDDYLOAD ENDOF 5 OF TEDDYSAVE ENDOF ENDCASE ; ( Dotextclick looks at the shift key and calls TeClick. 0= 0= is the equivalent of MacForth's "Boolean". ) : DOTEXTCLICK ( MOUSEPT -- Click...no ammo in a mouse... ) EVENT-RECORD 14 + W@ 512 AND 0= 0= TEDDY.T @ CALL TECLICK TEDDY.W CALL DRAWCONTROLS ; 2 2 15 452 RECT BUTTONRECT ( This is the rect of our palette ) : CONTENTCLICK { | MOUSEPT } RUN-CONTENT TEDDY.W CALL SETPORT EVENT-RECORD 10 + @ PAD ! PAD CALL GLOBALTOLOCAL PAD @ -> MOUSEPT MOUSEPT BUTTONRECT CALL PTINRECT IF DOPALETTE ELSE MOUSEPT TEDDY.T @ @ 8 + CALL PTINRECT IF MOUSEPT DOTEXTCLICK THEN THEN ; ( We set the dest and view rectangles ) : INITTEXT 18 4 TEDDY.W 20 + W@ 16 - TEDDY.W 22 + W@ 16 - TEMR !RECT TEMR PAD 8 CMOVE 1 PAD 2+ W! TEMR PAD CALL TENEW TEDDY.T ! -1 TEDDY.T @ @ 72 + W! ; ( The following code handles the scroll bar ) ( The thumb is called separately...Mach I manual for details ) : DOTHUMB TEDDY.T @ @ W@ L_EXT 18 - NEGATE TEDDY.SB @ CALL GETCTLVALUE 11 * - 0 SWAP TEDDY.T @ CALL TESCROLL ; : DOARROW TEDDY.SB @ CALL GETCTLVALUE SWAP OVER + TEDDY.SB @ SWAP CALL SETCTLVALUE TEDDY.SB @ CALL GETCTLVALUE - 11 * 0 SWAP TEDDY.T @ CALL TESCROLL ; : TEDDYBAR CASE UPARROW OF -1 DOARROW ENDOF DOWNARROW OF 1 DOARROW ENDOF PAGEUP OF TEDDY.W 20 + W@ 40 - -11 / -1 MIN DOARROW ENDOF PAGEDOWN OF TEDDY.W 20 + W@ 40 - 11 / 1 MAX DOARROW ENDOF ENDCASE ; : TEDDYCONTROL ( Control ) CASE ( In case of multiple controls... ) TEDDY.SB @ OF TEDDYBAR ENDOF ENDCASE ; : TEDDYCONTROL2 ( Control/Part ) CASE ( In case of multiple controls and parts... ) TEDDY.SB @ OF CASE THUMB OF DOTHUMB ENDOF ENDCASE ENDOF ENDCASE ; ( We go to sleep when we are not in use. Deactivate events look like Activate events if the program doesn't look hard enough ) : ACTIVATE-HANDLER RUN-ACTIVATE EVENT-RECORD 14 + W@ 1 AND IF WAKE STATUS TASK-> TEDDY.TASK W! ACTIVE? ON TEDDY.T @ CALL TEACTIVATE SCRAP->TE ELSE SLEEP STATUS TASK-> TEDDY.TASK W! TEDDY.T @ CALL TEDEACTIVATE ACTIVE? OFF TE->SCRAP CLEAR.TESCRAP THEN ; ( The Enter key does indentation, return doesn't ) : TEDDY.ENTER { | LOCATION CNTER NSPACES } TEDDY.T @ @ 62 + @ @ -> LOCATION TEDDY.T @ @ 32 + W@ 1- -> CNTER 0 -> NSPACES BEGIN LOCATION CNTER + C@ 13 = NOT CNTER 1+ 0> AND WHILE LOCATION CNTER + C@ 32 = IF NSPACES 1+ -> NSPACES ELSE 0 -> NSPACES THEN CNTER 1- -> CNTER REPEAT 13 TEDDY.T @ CALL TEKEY NSPACES 0> IF NSPACES 0 DO 32 TEDDY.T @ CALL TEKEY LOOP THEN ; ( These are done only once, so we have a flag to show if the routine must be called. Always Workspace before testing TEDDY or you will save the flag in the wrong state! ) CREATE CONFIGFLAG 0 , : CONFIGURE.TEDDY TEDDY.W ADD TEDDY.W TEDDY.TASK BUILD TEDDY.BAR ADD TEDDY.BAR APPLEMENU ADD TEDDY.BAR TFILE ADD TEDDY.BAR TEDITMENU ADD TEDDY.W TEDDY.SB ADD INITTEXT ADD.DRVRS TEDDY.BAR TEDDY.TASK MBAR>TASK TEDDY.TASK CONFIGFLAG ON ; ( The following can be done the first time ) : TEDDYGO CONFIGFLAG @ NOT IF CONFIGURE.TEDDY ACTIVATE THEN ACTIVE? OFF ['] TEDDYMENUS MENU-VECTOR ! ['] TEDDYUP UPDATE-HOOK ! ['] CONTENTCLICK CONTENT-HOOK ! ['] ACTIVATE-HANDLER ACTIVATE-HOOK ! ['] TEDDYCONTROL TEDDY.SB 4 + ! ['] TEDDYCONTROL2 CONTROL-VECTOR ! ['] CLICKPROC TEDDY.T @ @ 42 + ! 100 CURMAX ! CORRECT.CONTROL.RANGE TEDDY.SB @ ['] TEDDY.S2 ! TEDDY.W ['] TEDDY.W2 ! TEDDY.T @ ['] TEDDY.T2 ! BEGIN ( This is the beginning of our "Event" loop ) ACTIVE? @ IF TEDDY.T @ CALL TEIDLE ( Caret blink, blink, blink...) ?TERMINAL ?DUP IF 1 24 SCALE AND IF ( Is it a cmd key? ) KEY CALL MENUKEY DROP ELSE KEY CASE 3 OF TEDDY.ENTER ENDOF 9 OF 4 0 DO 32 TEDDY.T @ CALL TEKEY LOOP ENDOF TEDDY.T @ CALL TEKEY 0 ( EndCase drops! ) ENDCASE CORRECTSCROLL ( Autoscrolling ) CORRECT.CONTROL.RANGE CORRECT.CONTROL THEN THEN THEN PAUSE ( This is the equivalent of GetNextEvent ) AGAIN ; : TED ( TED always starts the editor...even if you hide the window ) CONFIGFLAG @ NOT IF TEDDYGO THEN TEDDY.W CALL SHOWWINDOW TEDDY.W CALL SELECTWINDOW TEDDY.BAR @ CALL SETMENUBAR CALL DRAWMENUBAR QUIT ; Listing 2: Teddy.R, resource listing for editor. [This file should be compiled with Rmaker and the resulting resources pasted into your Mach2 file with ResEdit. The picture is contained within the file Teddy.rsrc which is available on the MacTutor source disk for this issue. JL] * ReditX 1.1, resource decompile option. * * Resource listing from file: "Teddy.RSRC". * Teddy.RSRC.rsrc Type DLOG ,900 40 40 130 472 Visible 1 NoGoAway 0 900 New Dialog Type DITL ,10822 5 * 1 BtnItem Enabled 168 312 189 344 Ok * 2 PicItem Disabled 8 16 147 99 901 * 3 StatText Disabled 16 168 32 248 Teddy V1.0\0D * 4 StatText Disabled 40 144 56 280 Mach 2 ™ text editor\0D * 5 StatText Disabled 64 120 80 320 Copyright ©1986, Juri Munkki ,900 8 * 1 BtnItem Enabled 64 8 88 96 Find * 2 BtnItem Enabled 64 104 88 192 Replace * 3 StatText Enabled 8 8 24 48 Find: * 4 StatText Disabled 32 8 48 70 Replace: * 5 EditText Enabled 8 80 24 424 * 6 EditText Enabled 32 80 48 424 * 7 BtnItem Enabled 120 104 144 192 Replace All * 8 StatText Disabled 56 296 89 423 Editor 1.0\0D©1986 Juri Munkki Type ALRT ,900 40 80 200 432 10822 4444 Type PICT ,901 * No format specification available.

- SPREAD THE WORD:
- Slashdot
- Digg
- Del.icio.us
- Newsvine