Text Editor
Volume Number: 3
Issue Number: 1
Column Tag: Intermediate Mac'ing
The Generic Multi-Window Text Editor 
By David E. Smith, Editor & Publisher
Nostalgia
This is a column in nostalgia. A throwback to the good old days of 1986 when Mac
programs were done by hand with event loops, menu bars and custom designed scroll
routines. Back when men were men and programmers who knew how to write a
multi-window text editor were looked up to as heros of the Mac programming cult. Yes,
back in the dark ages before MacApp made all of what you are about to read obsolete. So
take a ride with us into the stone age of Mac programming and see how it used to be
when programmers struggled to code up a multi-window application from scratch.
Programming Puberty
Before MacApp, it was taken for granted that to reach programming puberty, you
had to write a multi-window editor. The reason for this is such a program involves
nearly all of the Mac user interface. Multiple windows, update events, scroll bars, text
edit, cut and paste, and so on. A number of these generic editor shells have been
published in both shareware, books and magazine articles. Bob Denny wrote one of the
first such articles for MacTutor in May 1985 with a discussion of text edit and
scrolling windows that remains a classic on the subject. Several previous articles in
that series delt with editing using the C language. All of those articles are available in
the Best of MacTutor, Volume 1 book.
The Chernicoff book, Macintosh Revealed, Volume 2, provided an entire book on
the subject of the multi-window text editor in Pascal. This is a very complete
description of such a program and covers most of the material in Inside Macintosh, vol.
1-3. Another book is the very good series by Dan Weston, The Complete Book of
Macintosh Assembly Language Programming, Volumes 1 and 2. The first volume covers
the generic text editor in assembly, and volume 2, just released, goes farther in
explaining new programming details of using the switcher, HFS, clipboard and other
topics generally discussed in Inside Macintosh Volume 4. Most of the material in
volume 2 of Dan Weston's book has never been published and represents a very
important book regardless of what language you program in. Be sure to buy this book
without delay. Scott, Foreman and Company are the publishers.
Finally, one of the most complete multi-window text editors is the source listing
supplied with LightSpeed Pascal from Think Technologies. This programming example
is very complete and will provide an excellent Pascal reference. It is generally based
on the Chernicoff example but expands on it in many areas.
Which brings us to this rendition of the generic multi-window text edit example.
Not wanting to miss out on my own opportunity at programming manhood, I decided to
write my own text editor using all the references cited above as guides. The result is
the subject of this month's column. Although it took three months, and I think is a very
good start, I was a bit taken back when Harvey Alcabes of Apple Computer showed off
MacApp and demonstrated the simplest "do nothing" MacApp application. It was an
advanced version of my program! Since this may become a lost art, I present my
version of the generic text editor for those who wish to bash heads with the toolbox
before moving on to MacApp.
What Our Example Does
As an application, our program is very complete. It opens four windows for text
editing. Scrolling is supported vertically. All the window operations are supported
including grow window, close box, and zoom box. When either the grow box or zoom
box is selected, the text is wrapped to the new window size so there is no need for
horizontal scrolling. A neat addition would be to include an option in the format menu
where the user could select between the two modes of having the text wrap when the
window changed size, or having the window scroll to view the text. The first mode is
ideal for paragraph writing, like the text you are reading now. The second mode is
better suited for program text or tables where placement of the text remains fixed at
tab stops. Of course, Text Edit doesn't support tabs so this mode would require more
work. The November 1986 issue of MacTutor presented a possible solution for
extending Text Edit to handle tabs.
Figure 2. The custom size dialog box.
Text presented in any of the four windows may be selected and changed to all the
quickdraw / text edit possibilities. A font menu gives access to all the fonts in your
system file. A size menu allows size changes from 6 point to 72 point with a dialog box
for custom font sizes up to the quickdraw limit of 127. A style menu gives all the
quickdraw styles available to text without any custom programming including
condensed and extended text. Finally, the mode menu allows the three quickdraw
recommended modes for drawing text. After trying the different modes, I concluded that
this is probably useless. But at least all the quickdraw text capability is supported in
this text edit example.
Text Edit Limitations
As you know, text edit is a set of ROM routines which implement a simple text
edit capability based on the ability of quickdraw. Only one font, style, size and mode is
allowed for all the text in a given text edit record. Text Edit is also limited to 32K
bytes of text and in fact the limit is even less depending on the font and size chosen. A
more limiting factor is the destination rectangle that encloses the text. The coordinates
of the rectangle are limited to integer values, but these can be quickly exceeded when a
large font size, a small window, and many lines of text are combined at one time. This
may explain why few programs allow complete freedom in text size selection. After
playing with the size menu, I discovered that when my window was made narrow, and
the size increased, the destination rectange limits were being exceeded in my routine to
re-wrap the text to the new window. The product of the number of text lines calculated
by Text Edit and the number of pixels per line, a function of the font size, quickly
becomes greater than an integer value, making proper scrolling impossible. TEScroll
generates an error since it is limited to an integer offset for the scrolling distance. In
our program, when this happens, it beeps and the displayed text is scrolled to 32K,
the max, which may not result in a correct window display. Reducing the font size or
enlarging the window will correct the display.
In figure 1, we see a screen shot of our program showing four windows open,
each with a different font, size, and style. The edit menu supports cut, copy and paste to
the Text Edit scrap. Since this scrap is a private scrap known only to text edit, I copy
the TEScrap to the deskscrap whenever a copy or cut operation is performed. Likewise,
when a paste is done, it is taken from the deskscrap. Thus the deskscrap is used at all
times so that the private text edit scrap and the clipboard scrap are always the same.
This allows an easy way to cut and paste between our windows and desk accessories or
other applications without the problem of trying to figure out when to convert the
clipboard.
Figure 2 shows how our custom font size dialog works. The current size is
displayed from a parameter variable in low memory and an edit item is provided so
that a new value may be inserted.
Our edit menu includes two useful functions shown in figure 3. These are a show
clipboard, and a select all function. The select all highlights all the text in the text edit
record and copies it into the deskscrap so that a subsequent paste operation will paste
the selected text. This seemed the most natural to me and eliminates the extra step of
choosing copy from the edit menu. For simplicity, the clipboard does not show the text
in the selected font, nor does it scroll. You may wish to turn the clipboard window into
another text type window so that it shows the font and style of the selected text.
The search and format menus do nothing. We have already made a suggestion for
the format menu, to select between word wrapping or horizontal scrolling. The search
menu can be implemented by using the munger trap call to search for arbitrary
strings in the text edit record. The font, style, and mode menus are fully implemented
to the limit of quickdraw's built-in text commands. The transfer menu is not
implemented but is ready to set up a standard file dialog box for launching another
application. How this is done was covered in an earlier issue of MacTutor by Chris
Yerga, available in the Best of book. The edit menu is fully operational except for
UNDO. A future article on how to implement undo is waiting publication in MacTutor.
The apple menu shows a simple about box dialog with an icon and the desk accessories
are available. The file menu has a new and close command, which open and close up to
four new windows. A single global variable, MaxWindows, determines the number
available. This method was chosen so that all the window data structures could be set up
low in the heap, since the window record is not re-locatable. The normal file and
printing operations for opening and saving the file, and printing the file are not
implemented. There is only so much one can do in a single article on a topic which
others have written entire books about. Both of these subjects are well treated in Dan
Weston's new volume 2 on assembly programming, previously mentioned.
LightSpeed Pascal
Figure 4 shows the files created by the LightSpeed Pascal system. The four text
files shown can be read directly by Edit if you are using another Pascal system. As
explained in last month's article by Tom Scheiderich, there is very little difference in
running this program in TML or LS Pascal. Only the segmentation is different. The
program code is contained in a short "MyWrite Main" file. This includes the init code,
the event loop and the first level of event subroutines. Two other files complete the
program. These are the "myWriteStuff" unit and the "scrollstuff" unit. Finally, the
file "Editor Globals" contain the global constant and variable declarations. David
Wilson, the Apple Programming guru, recommends that the init code be placed in the
first segment so that it is always locked in memory. Apparently trying to place the init
code in another segment that may be unlocked or purged can cause problems with the
quickdraw global assignments. That is why I included the init stuff in my main
program rather than in another unit as some people do. Resources are handled in the
normal way. An edit file called "MyWrite.R" contains the RMaker format resources.
These must be assembled into a resource file named "MyWrite.RSRC". The actual
program is contained as a project in "MyWrite Project", shown hi-lited in figure 4,
and in a stand alone file created with the "build" command. The switcher test case file
was used to test the switcher event to see that the clipboard worked properly from
switcher. The ease with which you can get into and out of a development task by just
clicking on the project file really makes working with LS Pascal a snap. And the
observe window, which allows examining local variables makes debugging fast and
easy. I love it!
Figure 4. Edit program files
Figure 5 shows the implied link order for our program. The globals and main
files are linked together in segment 1 along with all the various Pascal system files.
ROM85 is the library of new 128K ROM calls, included just so I wouldn't have to think
about it. I really don't know if I used any new calls or not. Most of the code is contained
in "myWrite Stuff", the main chunk of segment 2. At the end of the event loop, an
"unloadSeg" trap call is made to unload these two units, making them re-locatable. This
type of segmentation by unit boundaries is unique to LS Pascal and you may wish to
modify this if you are using another linker.
MyWrite Program Code
We begin our discussion of this program with the main segment, shown in
figures 6, 7 and 8. The main program calls three routines to init everything, set up
the menu bar, and perform the event loop, as illustrated in figure 6. The events our
program responds to are shown in figure 7. These are a mouse down event, a keypress,
and the update and activate events for windows. This represents the minimum response
for a typical Mac application. We have included a fifth event, the switcher event,
number 15, to detect when the switcher suspends and activates us. This was taken from
Dan Weston's volume 2 book, which explains in great detail how the switcher works.
Since we use the system scrap at all times, we really don't need this event since no
clipboard conversion is required.
Figure 6. The main program
Figure 7.The Event Loop
Figure 8. The Mouse Down Event
All the action takes place in the mouse down event, where we perform the menu
bar functions. Figure 8 shows the various places the mouse can be and the implied
action we must take. When a click happens in the content region of a window, we have
to find out if that is a scroll event, since the scroll bars are part of the content region
of a window. So scrolling is done as part of a content region mouse down event. Grow
and Zoom events are also handled. These events change the window size, and in our
application, cause all the text to be re-formatted to wrap to the new view rectangle.
This proved to be the hardest part of the program, getting grow window to work right.
The little grow icon is apparently a lost child and it took quite a while to figure out how
the window is grown and updated properly under all conditions.
Data Structures
Most of the published text edit examples use a custom data type called a window
control block or some such thing to manage all the data structures associated with the
window. Since I didn't want to confuse the toolbox data structures, which I was trying
to learn, with the custom ones, I did not use this approach. Instead, I set up five arrays
shown below:
myWindows: array[1..MaxWindows] of Windowptr;
GoodbyList: array[1..MaxWindows] of Integer;
myVControls: array[1..MaxWindows] of ControlHandle;
myHControls: array[1..MaxWindows] of ControlHandle;
myText: array[1..MaxWindows] of TEHandle;
The window array holds the window pointers to the window records. The text
array holds the handles to the text edit records. The vertical and horizontal control
arrays hold the handles to the scroller controls. Finally, the GoodbyList holds the
status of each window, whether it is available or being used. The proper association of
each control or text edit record to the proper window is done by the array index. The
currently active index is contained in the global variable " currentWindow".
MaxWindows is set at 4 but this can be changed in the global variables. From my
background, this approach was the most logical to me and eliminated the need to create
a custom variable type. However, now that I've written the program and understand
how the Mac works, I can see that it would be cleaner to combine all these arrays into a
new data structure that would tie the various components to a "window object". This
approach would also lead into a MacApp conversion more easily. The trick is you can't
appreciate this until after you've written such a program and understand more clearly
how all the pieces fit together.
Main Program Notes
The first thing we do in our main program is init everything. Notice that we set
up a bomb routine with our InitDialogs trap so that if a system error is generated, we
get a resume box that will return us to our program where we can exit to the finder.
Our bomb routine is called crash, and simply calls the toolbox routine ExitToShell.
Scott Knaster, in his book How to Write Macintosh Software, goes into great length on
how to write these saver routines. However there is some controversy on just how
much such a crash routine should do. Trying to write to the disk to save a file after a
crash could be bad news if the system is so fouled up that a hard disk is destroyed in the
process. So I think it is better to assume the worse and just exit rather than try and be
nice to the user only to find out you bombed his hard disk! Incidently, this is a very
good book, which is not described by its title. It really is a book on how to debug Mac
programs and is a must for everyone. The book is being distributed free if you join
APDA, the official Apple developer's Association.
Rectangles are very important. Our program uses several global rectanges for
the window stuff. These are:
screen screen display rectangle
DragArea window drag rectangle
GrowArea window grow rectangle
DefaultWindow new window size default
ZoomRect zoom area rectangle
ClipBdRect clipboard window rectangle
ViewRect vew rectangle for text edit
DestRect destination rectangle for text edit
VCRect vertical scroller area
HCRect horizontal scroller area
GrowRect grow box region
All of these are set up in our init routine and are calculated from the system
global ScreenBits.Bounds, so that they all change appropriately if a large screen is
used. I tested the program on a large screen Mac and it worked perfectly. The Zoom
window zoomed to the size of the large screen because of how these rectangles are
defined relative to ScreenBits.Bounds.
Another important point is the order in which things are done during init. The
window must be defined first, followed by the controls and then text edit. Text Edit
requires the current port be set properly to the window whoose text edit record is
being defined. That is how the assocation is made between the text edit record and the
window record. So you must define the window first, and then do a SetPort to that
window before calling TENew to create the text edit record.