|Column Tag:||Ask Prof. Mac
By Steve Brecher, President, Software Supply, MacTutor Contributing Editor
This month we inaugurate a question-and-answer format for this column. You supply the questions, and I'll do my best to supply the answers. As a print publication, our lead time is too long to help you with those burning 4-in-the-morning do-or-die questions. But if you've come across something in Inside Macintosh that's been puzzling you; or you've been scratching your head about how make the Mac do something you're going to need it to do a few months hence; or you'd like an explanation of some aspect of an example you've seen in MacTutor (or elsewhere) -- write to Prof. Mac, c/o MacTutor.
We start off with a couple of genuine questions from MacTutor readers, followed by a few ringers.
David R. Hunt of Ypsilanti, MI had some questions about TextEdit after reading the Forth Forum in the June, 1985 issue. I'll address these questions with respect to TextEdit in general, rather than specifically with respect to MacFORTH. MacFORTH's interface to TextEdit is described in Section 3 of the Level Two MacFORTH manual.
Q.How big can a TE Record be? How much text can be in a TE Record?
A.First, note that a TERec (TextEdit record) contains information about the text to be edited, but it does not contain the text itself. The text is located elsewhere in memory; the TERec contains a handle to the text. The TextEdit routines maintain both the TERec and the text; you don't have to be concerned with the location of the text in memory.
Background on TextEdit
To preserve the Mac way of doing things, Apple defined a consistent approach to dealing with the entry, editing and display of text information that would operate cleanly with the graphics oriented hardware design built Input is a collection of ROM toolbox routines that handle simple text editing, and is known collectively as "TextEdit". Think of it as a mini-editor built into the ROM. The mystery of the Mac approach to text editing lies in the fact that the Mac does not have a traditional character generator. Everything is displayed on the screen and within the system in bit-mapped graphics via quickdraw. Thus each character of text is a graphics symbol made up of screen bits. This both provides the flexibility of the Mac in generating different fonts, sizes and orientations while at the same time making the control of text information more complex. The text edit record structure is a method of isolating the text itself and the information about how the text is to be displayed in two different data structures. The text is stored in a coded linear array while all the information about how that array of text should look is stored in the text edit record. If any of the text changes style or font, that text must be associated with another text edit record to describe how it is to be displayed. Thus if a document has many font changes, more and more text edit records must be generated for each section of text. (See Fig. 1)
(1) Insert new text
(2) Delete text via the backspace key
(3) Select text with the mouse
(4) Scroll text within a window
(5) Cut and Paste of text between the clipboard
(6) Display of selection bar and text hi-lighting
(7) Word wrapping of text
(8) Left, centered or right justified text
(1) Only one font per text edit record
(2) No fully justified text
(3) no tab support
(4) only simple cut and paste
The new phone book version of Inside Macintosh points out that cut or copying text with TextEdit places that text in a special TextEdit scrap that is only used within TextEdit. A different scrap is used to support the clipboard for moving text between applications. Documentation for the routines that move text into this second scrap has now been added to Inside Macintosh.
The important components of a TERec are shown on page 48 of the June issue; the complete TERec structure is detailed in Inside Macintosh, /TEXTEDIT/EDIT.2, p. 9. For a complete discussion of how text editing interfaces with windows, see Bob Denny's C column in the May 1985 issue of MacTutor.
And now back to the Question...
At the end of a TERec is a variable- length array called lineStarts; there is one 16-bit unsigned element in the array for each line of text. Each element contains the position (offset) within the text of the start of a line. The number of lines (and hence the number of elements in the lineStarts array) is contained in a 16-bit signed field of the TERec called nLines. The maximum value of nLines 32,767. Thus, the maximum number of lines in the text, and of elements in the lineStarts array, is 32,767.
Since each element of the lineStarts array is an unsigned 16-bit quantity, each element has a maximum value of 65,535. Thus, the position of the last line of the text cannot be more than 65,535 characters away from the beginning of the text.
Therefore, the text can have at most 32,767 lines, and the number of characters preceding the last line can be at most 65,535. The size of the TERec itself is the total of the sizes of its fixed-length fields -- 96 bytes -- plus the size of the lineStarts array (2 bytes for each line).
Q. Can I have more than 1 TE record in a window?
A. Strictly speaking, the TERec is not "in" a window; it is merely a data structure describing some text (located elsewhere in memory) and describing where and how the text is to be displayed. In fact, TextEdit does not "know" about windows at all. It displays the text within a grafPort; the grafPort is not necessarily one with which a window is associated (although in practice it almost always is a window's grafPort).
The grafPort in which the text assocatiated with a TERec will be drawn is the grafPort that was current at the time the TERec was created. Its address is stored in the inPort field of the TERec. TextEdit locates the display area for the text within the grafPort as specified by a destination rectangle whose coordinates (local to the grafPort) are contained in the destRect field of the TERec. The destRect is specified by the programmer as a parameter to the TENew routine (which creates a TERec).
There can be more than one TERec associated with the same grafPort, and hence with the same window. In this case, each TERec would have a different, non-overlapping destRect within the grafPort (or window).
Q. What happens if I update a window with multiple TE records?
A. Remember that updating is not an automatic process. Your application is notified that an update event has occurred, which means that a window needs to be updated. Generally speaking, you must do the actual updating, or redrawing of the contents of the window, yourself.
However, TextEdit makes it easy to update the text portion(s) of a window. You call TEUpdate, giving it a rectangle in which to (re)draw the text, and a handle to the TERec associated with the text. Usually, the rectangle you'd pass to TERec would be simply the portRect of the window. If there are multiple TERecs associated with a window, you'd call TEUpdate once for each TERec. All of the calls would be bracketed between calls to BeginUpdate and EndUpdate, which is the way window updating is done generally (regardless of whether text is involved).
Q. In my application, I want to issue text to a TE Record. How do I do it?
A. As noted above, the text itself is not contained in the TERec; rather, a handle to the text is contained in the TERec. If you have some preexisting text which you want to associate with a TERec (replacing any text already associated with it, if any), you call TESetText. The parameters to TESetText are the address of the text, its length, and a handle to the TERec. This establishes the connection between a linear array of text, the text edit record that describes how and where the text is to be displayed, and the screen area where a portion of that text may or may not be visible to the user.
To insert some new text into text already associated with a TERec, you call TEInsert, passing to it the address of the text to be inserted, its length, and a handle to the TERec. The insertion will occur just before the current selection range.
If the current selection range is an insertion point (indicated on the screen by a blinking vertical bar, or caret), you can insert a single character there by calling TEKey. The parameters to TEKey are the character, and a handle to the TERec. TEKey is so named because usually the character is one that was typed by the user and obtained from the event record associated with a keyDown event, but this is not necessarily the case: you can specify any character. If, when TEKey is called, the selection range is indeed a range (indicated on the screen by white-on-black text) instead of an insertion point, the range will be replaced by the character.
Q. Do I append the TE records together, or leave them separate?
A. I hope it's clear from the above that TERecs cannot be concatenated. Each is a separate structure, describing how and where to display text associated with it.
Q. My worst problem: TE records are utterly unlike text processing as I have previously known it on mainframes. Could you show me how to make this transition?
A. I hope the preceding answers have helped some. You may be suffering from an embarrassment of ROM riches: in most systems, a programmer must take care of all the details of text editing himself. But on the Mac, the TextEdit facility does almost all of the work for you. Figure 1 at the beginning of this article may help to present the big picture.
Mac Pascal Bug
In last month's Letters section, Jack Cassidy of San Diego provided a simple Macintosh Pascal program that bombs for no apparent reason. Our editor promised that I'd look into the problem.
Q. Why does the following program cause a system error (ID=02)?
simple: array[count] of integer;
A. Unfortunately, Jack has run into a bug in Mac Pascal: it does not handle array references correctly where the subscript is an enumerated type. A workaround is to do away with the enumerated type and replace it with a collection of constants. This retains some of the readability provided by enumeration, at the expense of some coding tedium. For example:
one = 1;
two = 2;
three = 3;
four = 4;
five = 5;
simple: array[one..five] of integer;
Note that in the program that bombs, ord(one)=0, but in the workaround version ord(one)=1. This makes no significant difference; I just wanted to avoid writing the apparently illogical "one = 0; two = 1;" etc. If the names of the constants were not those of numbers, then the first one could be given the value 0 without risk of confusion.
Now here's one of my own questions. The MaxApplZone procedure is marked "No trap macro" in Inside Macintosh, implying you can't use it from Assembly.
Q. How would I write a MaxApplZone in MDS assembler?
A. See the code below in fig. 2.
Include SysEqu.D Fig. 2
; "MaxApplZone expands the application heap zone to the application
; heap limit without purging any blocks currently in the zone. If
; the zone already extends to the limit, it won't be changed."
; --Inside Macintosh, specification of MaxApplZone
MinBlkSize Equ 12 ;minimum physical size of a block
Move.L ApplLimit,A0 ;A0 = application heap limit (pointer)
Move.L HeapEnd,A1 ;A1 = addr of current zone trailer
Sub.L A1,D0 ;D0 = size of unused space (incl. trailer)
MoveQ #MinBlkSize,D1 ;D1 = minimum physical size of a block
Cmp.L D1,D0 ;unused space < minimum size of a block?
Blo.S @0 ;if so, forget it, can't expand zone
Move.L A0,HeapEnd ;update HeapEnd to heap limit
Move.L D0,(A1) ;unused space := free block (header:=size)
Move.L ApplZone,A1 ;A1 --> zone header
Move.L A0,(A1) ;bkLim field of header := heap limit
Move.L D1,(A0) ;make new zone trailer (free, size=min)
Add.L D0,zcbFree(A1) ;update count of free bytes in zone
The fact that the MC68000 hardware divide instructions handle only 16-bit divisors and return 16-bit results presents another question:
Q. How would I do 32-bit unsigned division in MDS assembler?
A. See the code below.
the alternatate code is indicated as being faster in the chart, it will indeed be faster on the Mac. Those with an eye to the future should note that the relative slowness of CLR for a memory operand results from the 68000 doing a superfluous read of the operand before writing it; but this is not true of the MC68020.
; On entry:
; D0 = 32-bit unsigned divisor
; D1 = 32-bit unsigned dividend
; On exit:
; D0 = 32-bit unsigned quotient ($FFFFFFFF if divisor = 0)
; D1 = 32-bit unsigned remainder (dividend if divisor = 0)
; D2 = $FFFFFFFF
; D3 = D0
MoveQ #-1,D3 ;init complemented quotient
MoveQ #-1,D2 ;init complemented shift counter
Tst.L D0 ;divide by 0?
Beq.S @6 ;yes, quotient=max magnitude, ;remainder=dividend
@0 Cmp.L D0,D1 ;dividend < (shifted) divisor ?
Blo.S @1 ;br if so
Add.L D0,D0 ;shift divisor left
DBcs D2,@0 ;if didn't shift out, update counter
Roxr.L #1,D0 ;shifted out: undo shift...
Not D2 ;...get true shift count
Bra.S @3 ;...into loop (bypass first undo of
@1 Not D2 ;get true shift count
Bra.S @5 ;into loop
@2 Lsr.L #1,D0 ;undo a divisor shift
@3 Sub.L D0,D1 ;subtract (shifted) divisor from ;dividend
Bcc.S @4 ;skip if no borrow (X=0)
Add.L D0,D1 ;restore (and set X)
@4 Addx.L D3,D3 ;shift old quotient, add ;complement
of new bit
@5 Dbra D2,@2 ;loop until divisor shifts all undone
Not.L D3 ;uncomplement quotient
@6 Move.L D3,D0 ;return quotient in D0
MDS Asm bug
Q. Asm gave me an error message for CMP (A0)+,(A1)+. After I changed the opcode to CMPM, my program bombed later with an address error. Why?
A. The release version (1.0) of the MDS assembler does not automatically generate CMPM as it should when CMP is coded with two auto-increment operands; you must explictly tack on the "M." Further, if you code CMPM, it generates a CMPM.B; to compare words, you must code CMPM.W.