Track, Field 2
Volume Number: 6
Issue Number: 3
Column Tag: Intermediate Mac'ing
Mouse Track and Field, Part II 
By Martin Minow, Arlington, VA
About a year ago, I wrote a terminal emulator that used the mouse to select text
(such as the name of a file I wanted to download). Thinking that other MacTutor
readers would be interested, I reimplemented the algorithm as a separate subroutine
(fixing numerous bugs along the way) and added a small sample program. When I
showed the draft article to a colleague for review, he suggested I add a few additional
routines to create a TextEdit replacement library. Naively thinking this wouldn’t be
too much additional work, I went off and started typing. And typing. And typing. When
I came up for air, I had most of TextEdit (I didn’t add styles or multi-directional text).
Perhaps more importantly, the library is written in C so it could be adapted to other
uses.
Part I described the central mouse selection algorithm in somewhat obsessive
detail. It also includes a sample program listing. This article describes the library
itself in rather general terms, and includes the complete TrackEdit source library
listing, except the library routines that were printed in Part I.
The TrackEdit Library
The TrackEdit library can be used as a direct replacement for TextEdit (only
TextBox was left out). If I did my work well, you should be able to adapt these routines
to any “editing” application that can’t use TextEdit. The routines take essentially the
same arguments as their TextEdit counterparts.
Table 1 shows the correspondence between TextEdit and TrackEdit:
TextEdit Track
TEAutoView TrackAutoView
TEInit TrackInit
TENew TrackNew
TEDispose TrackDispose
TESetText TrackSetText
TEGetText TrackGetText
TEIdle TrackIdle
TEClick TrackClick
TESetSelect TrackSetSelect
TEActivate TrackActivate
TEDeactivate TrackDeactivate
TEKey TrackKey
TECut TrackCut
TECopy TrackCopy
TEDelete TrackDelete
TEGetHeight TrackGetHeight
TEGetOffset TrackGetOffset
TEGetPoint TrackGetPoint
TEInsert TrackInsert
TEPinScroll TrackPinScroll
TESelView TrackSelView
TESetJust TrackSetJust
TEUpdate TrackUpdate
TextBox not implemented
TEScroll TrackScroll
TEFromScrap TrackFromScrap
TEToScrap TrackToScrap
TEScrapHandle TrackScrapHandle
TEGetScrapLen TrackGetScrapLen
TESetScrapLen TrackSetScrapLen
SetWordBreak TrackSetWordBreak
SetClikLoop TrackSetClikLoop
TECalText TrackCalText
not implemented TrackGetSelectLength
Table 1: Comparison of TextEdit and Track Routines
If you have a basic understanding of TextEdit, you should be able to understand
how to call the Track library routines.
The Track library stores all information needed to track the mouse in a data
structure whose elements are described in Table 2.
Element Usage
topPixel offset of top line (part of the destination rectangle in TextEdit)
leftPixel offset of top line (part of the destination rectangle in TextEdit)
lineWidth width of text line (computed from the destination rectangle in
TextEdit)
viewRect view (clip) rectangle
lineHeight line spacing for display and mouse tracking
fontAscent position text for display
fontDescent position caret for display
selPoint used to display insertion point
selStart selection start
selEnd selection end
clikLoop click loop routine pointer
clickTime mouse-up time to detect double-clicks
clickLoc mouse position to detect double-clicks
caretTime when to invert the caret
just justification flags
textLength number of bytes of text
hText text handle
crOnly break lines at carriage-returns only
txFont text font
txFace character style
txMode pen mode
txSize font size
inPort grafPort
flags used internally
highHook hilight routine pointer
caretHook insertion mark routine
refCon structure reference value
nLines number of lines of text
lineStarts[] positions of line starts
Table 2: TrackRecord Elements
Most of this information is identical to the identically-named fields in the
TextRecord used by TextEdit. There are a few differences worth noting:
• topPixel, leftPixel, and lineWidth replace TextEdit’s destRect. This works
around a TextEdit text size limitation. topPixel and leftPixel are affected by
scrolling, while lineWidth is used to compute line wrapping. topPixel and
leftPixel are long integers to work around a limitation of TextEdit. If you are
replacing TextEdit in your editor, you may have to adjust your scrolling routines
to use these variables.
• The Track routines store line pointers in long integer arrays. This means that
they can deal with text longer than 32,000 bytes. Also nLines is a long integer.
A “real” text editor should use a more efficient data structure (with paragraphs
residing in a disk scratch file until needed) -- TrackEdit is just enough to get
you going.
In addition to the externally-visible functions, the library has a number of
private functions and two global variables (used to manage the desk scrap). These
begin with the character sequence _Track_.
The Track library functions provide a scaffold for the mouse selection algorithm
which, in turn, assigns values to two elements of the TrackRecord structure, selStart
and selEnd. These delimit the selection range: selStart indicates the first character in
the selection, and selEnd indicates the first character after the selection. In a real
application, your program would use a more complex object to access its data: for
example, my terminal emulator uses a structure containing a handle to the start of a
line and an integer giving the offset to a particular character. This more elegant
organization simplifies the storage allocation algorithms at the cost of making selection
slightly more complex.
The program assumes that data are organized in a linear first-to-last
arrangement that would naturally be top-to-bottom and left-to-right for English text,
but is really dependent on the data you are presenting. (It gets more complicated for
multi-lingual text, such as mixed Hebrew and English.)
Locating the Mouse
_Track_mouse_to_dot takes a “raw” mouse coordinate and converts it to a
selection. The code is fairly straightforward. First, it uses lineHeight to compute the
row. Then, starting from the character at the left edge of this row, the sum of the
display width of each character determines the location of dot. If the mouse is past the
middle of the character, dot is set to the next character. I use the term dot to refer to a
value that uniquely identifies each character in the document: the term comes from
TECO, one of the first text editors. (Note that dot is actually between two characters.)
_Track_word takes the actual location of the mouse in window coordinates and
determines the dot value for the start and end of the word. It was not clear to me (for
the first few months) that I needed the mouse location in order to compute the word
start: the problem is due to the behavior at the end of a line, when the mouse may be at
the end of one line or at the beginning of the next line: in both cases, the dot value is the
same, but word start and word end are quite different.
When the mouse was in the right half of the last character on a line, dot, when
converted back to window coordinates, referenced the start of the next line. This
inadvertently extended the selection over the first word in the following line when the
program scanned forward for white-space. The problem was solved by blocking the
scan if mouse is in row r and dot is in row r+1. While this sounds like a simple
insight, I had to fall asleep over the code one night to discover it..
Instead of sleeping, I would have been better off reading Inside Mac, volume V,
where the FindWord toolbox procedure is described. This locates the start and end of a
word in a language-independent manner. FindWord can also handle selection for
multi-directional text, such as mixed English and Hebrew, however this library does
not take advantage of this capability. (Any volunteers?)
Autoscrolling
When you examine the mouse selection routine in TrackClick.c, note that the
inner “follow-the-mouse” loop calls an automatic scrolling routine that moves the
display if the mouse goes outside of the visual display (the viewRect). This should be
overridden by your application if the text window has scroll bars. I.e., the functions in
TrackScroll.c and TrackAutoScroll.c do only part of the work.
Displaying the Selection
There are two functions used to display mouse selections: _Track_hilite and
_Track_caret.
_Track_hilite takes two selection delimiters and inverts the screen image that
encloses them. The routine is straightforward, but note that it must understand that a
selection may extend over more than one row. This implementation differs slightly
from other Macintosh selection routines in that it doesn’t invert the entire screen out
to the window edges.
If the user clicked the mouse to set an insertion point, selStart will equal selEnd.
In this case, _Track_caret will draw (or erase) the selection marker. As with
_TrackWord, it needs the actual mouse location to distinguish between “end of line”
and “beginning of next line,” since these have the same dot value.
Intelligent Cut and Paste
The cut and paste routines in TrackEdit.c implement Apple’s “Intelligent Cut and
Paste” algorithm described in Inside Macintosh Volume I and the Apple Human
Interface Guidelines. The routines are straightforward, though you should read Apple’s
description to understand why they work as they do. They can be turned off by a menu
option in the demo program (and are turned off if your system doesn’t support the
Script Manager). Note that TrackKey suppresses “intelligent cut.” You might want to
extend TrackKey to support arrow-key selection as discussed in Inside Mac, Vol IV,
Chapter 1.
Real Life
If you need to replace TextEdit, you should be able to use these routines with a
minimum of fuss. Of course, they are not needed for many TextEdit uses, such as
entering data in dialogs.
The way you decide to store your data will affect the low-level routines in
TrackLocate.c and TrackPosition.c (these translate between the data-specific dot value
and the corresponding position in window-local coordinates). You will also have to
make small changes in several other modules, notably TrackEdit.c and TrackUpdate.c
as these must access the actual data.
Also, if you use a structure to identify text (perhaps containing a line handle and
offset), you’ll have to write a comparison function that replaces my simple if
statements. Also, you ought to add the remaining TextEdit capabilities, such as styles,
tabs, and multi-language support.
Adapting the routines for multi-directional text is not trivial. Apple has
published some documentation on the ScriptManager subroutines that you should
consult in addition to the description in Inside Macintosh.
When you do adapt the routines to your own needs, don’t forget to put some effort
into error recovery and filling in the missing pieces. Also, a “real” application should
handle display updating better (i..e., faster).
References and Acknowledgements
Inside Macintosh, Volumes I-V, and the Apple Human Interface Guidelines are
published by Addison-Wesley Publishing Company.
Thanks to Mike Morton for the suggestion to rewrite the subroutines as a
coherent unit and to Shirley Marotta for the mouse drawing.
Copyright and Ownership
These routines are Copyright © 1989, Martin Minow and MacTutor. You may
copy, modify, and use this software in your applications without restriction as long as
all copyright notices remain intact and the applications are not sold for profit. You
may not redistribute the source for profit without permission. Please contact me c/o
MacTutor if you wish to use these routines in a commercial or shareware product.
Listing: Document Activation
/* TrackActivate.c */
/*
* Copyright © 1989 Martin Minow and MacTutor. All rights reserved.
*
* void
* TrackActivate(track_ handle)
* TrackHandle track_ handle;
*
* void
* TrackDeactivate(track_ handle)
* TrackHandle track_ handle;
*
* These routines are called when the application program
* receives an activate or deactivate event. They may
* also be called temporarily to erase any current
* selection if, for example, the application program
* needs to change the hiliting characteristics.
*/
#include “TrackEdit.h”
#define TR (*tr)
void TrackActivate(track_ handle)
TrackHandle track_ handle;
{
register TrackPtr tr;
_Track_state state;
tr = _Track_lock(track_ handle, &state);
SetPort(TR.inPort);
if (!_Track_is_set(tr, _Track_is_active)) {
/*
* Activating: call the hiliters to invert any
* selection.
*/
_Track_set(tr, _Track_is_active);
_Track_hilite(tr, TR.selStart, TR.selEnd);
_Track_caret(tr, _Track_caret_on);
}
_Track_unlock(&state);
}
void TrackDeactivate(track_ handle)
TrackHandle track_ handle;
{
register TrackPtr tr;
_Track_state state;
tr = _Track_lock(track_ handle, &state);
if (_Track_is_set(tr, _Track_is_active)) {
/*
* Deactivating: invert any selection to
* erase it from the window.
*/
_Track_hilite(tr, TR.selStart, TR.selEnd);
_Track_caret(tr, _Track_caret_off);
_Track_clear(tr, _Track_is_active);
}
_Track_unlock(&state);
}
Listing: Automatic Scrolling
/* TrackAutoScroll.c */
/*
* Copyright © 1989 Martin Minow and MacTutor. All rights reserved.
*
* Manage automatic scrolling.
*
* void
* TrackSelView(track_ handle)
* TrackHandle track_ handle;
*
* If automatic scrolling has been enabled, make sure the
* start of the selection range is visible, scrolling
* it into view if necessary. If automatic scrolling
* is disabled, TrackSelView does nothing.
*
* void
* TrackAutoView(auto, track_ handle)
* Boolean auto;
* TrackHandle track_ handle;
*
* Enable or disable automatic scrolling.
*/
#include “TrackEdit.h”
#define TR (*tr)
/*
* Calculate the width or height of a rect.
*/
#define width(r) ((r).right - (r).left)
#define height(r) ((r).bottom - (r).top)
/* Scroll the selection start into view.*/
void TrackSelView(track_ handle)
TrackHandle track_ handle;
{
register TrackPtr tr;
_Track_state state;
LONGINT hpixel, vpixel;
LONGINT hdelta, vdelta;
tr = _Track_lock(track_ handle, &state);
if (!_Track_is_set(tr, _Track_do_autoscroll))
return;
TrackGetPoint(
TR.selStart, track_ handle, &hpixel, &vpixel);
vpixel -= TR.lineHeight;
hdelta = vdelta = 0;
if (vpixel < (LONGINT) TR.viewRect.top
|| vpixel > (LONGINT) TR.viewRect.bottom)
vdelta = vpixel - TR.viewRect.top;
if (hpixel < (LONGINT) TR.viewRect.left
|| hpixel > (LONGINT) TR.viewRect.right)
hdelta = hpixel - TR.viewRect.left;
if (hdelta != 0 || vdelta != 0)
_Track_do_scroll(tr, hdelta, vdelta);
_Track_unlock(&state);
}
/* Turn on/off automatic scrolling. */
void TrackAutoView(enable, track_ handle)
Boolean enable;
TrackHandle track_ handle;
{
if (enable)
_Track_set((*track_ handle), _Track_do_autoscroll);
else {
_Track_clear((*track_ handle), _Track_do_autoscroll);
}
}
/* _Track_autoscroll()
* Make sure the mouse is within the viewRect, scrolling
* the text if necessary. This should “pin” the text
* so it doesn’t scroll out of the window.*/
void _Track_autoscroll(tr, mousep)
register TrackPtr tr;
register Point *mousep;
{
LONGINT deltah, deltav;
LONGINT max_horiz, max_vert;
if (!_Track_is_set(tr, _Track_do_autoscroll))
return;
deltah = 0;
deltav = 0;
if (mousep->v < TR.viewRect.top
&& TR.topPixel > 0) {
--deltav;
mousep->v = TR.viewRect.top;
}
else if (mousep->v > TR.viewRect.bottom) {
max_vert = (TR.nLines * TR.lineHeight)
- height(TR.viewRect);
if (TR.topPixel < max_vert) {
++deltav;
mousep->v = TR.viewRect.bottom;
}
}
if (mousep->h < TR.viewRect.left
&& TR.leftPixel > 0) {
--deltah;
mousep->h = TR.viewRect.left;
}
else if (mousep->h > TR.viewRect.right) {
max_horiz = TR.lineWidth - width(TR.viewRect);
if (TR.crOnly < 0 || TR.leftPixel < max_horiz) {
++deltah;
mousep->h = TR.viewRect.right;
}
}
/* Each pass through autoscroll moves the window
* a few pixels at a time -- this slows things down
* so the user can stop before the text disappears.*/
if (deltav != 0 || deltah != 0) {
_Track_do_scroll(tr, deltah * TR.lineHeight,
deltav * TR.lineHeight);
}
}
Listing: Recalculate Line Widths
/* TrackCalText.c
* Copyright © 1989 Martin Minow and MacTutor. All rights reserved.
* void TrackCalText(track_ handle)
* TrackHandle track_ handle;
* Recalculate line starts. Call this after you change
* the font characteristics. */
#include “TrackEdit.h”
#define TR (*tr)
void TrackCalText(track_ handle)
TrackHandle track_ handle;
{
register TrackPtr tr;
_Track_state state;
tr = _Track_lock(track_ handle, &state);
_Track_rebuild(track_ handle, 0);
_Track_unlock(&state);
}
Listing: Edit Menu Subroutines
/* TrackEdit.c
* Copyright © 1989 Martin Minow and MacTutor. All rights reserved.
* TrackCut(track_ handle)
* track_handle The TrackRecord handle
* Cut the selection to the Track private scrap.
* Anything currently in the scrap is deleted. If there
* is no selection, the scrap is emptied.
*
* TrackCopy(track_ handle)
* track_handle The TrackRecord handle
* Copy the selection to the Track private scrap.
* Anything currently in the scrap is deleted. If there
* is no selection, the scrap is emptied.
*
* TrackPaste(track_ handle)
* track_handle The TrackRecord handle
* Replace the selection range with the contents of the
* Track private scrap. If the scrap is empty, the
* selection range is deleted. The private scrap is
* not changed.
*
* TrackDelete(track_ handle)
* track_handle The TrackRecord handle
* Delete the selection range, but do not copy it to
* the Track private scrap. */
#include “TrackEdit.h”
#define TR (*tr)
#define LEFT FALSE
#define RIGHT TRUE
#define NIL 0L
#define NOT_NIL 1L
static void do_paste(TrackHandle);
static void do_clear(TrackHandle, Boolean);
static Boolean is_at_space(TrackPtr, Boolean);
static Boolean is_at_word(TrackPtr, DOT, DOT, Boolean);
/* TrackCut(track_ handle)
* Copy the selection to the scrap, then remove it. */
void TrackCut(track_ handle)
TrackHandle track_ handle;
{
TrackCopy(track_ handle);
TrackDelete(track_ handle);
}
/* TrackCopy(track_ handle)
* Copy the selection to the scrap.
* This is the only routine to change TrackScrpHandle.*/
void TrackCopy(track_ handle)
TrackHandle track_ handle;
{
register TrackPtr tr;
_Track_state state;
register long size;
tr = _Track_lock(track_ handle, &state);
size = TR.selEnd - TR.selStart;
SetHandleSize(TrackScrpHandle, size);
if (MemErr == noErr) {
BlockMove(&(*TR.hText)[TR.selStart],
*TrackScrpHandle, size);
TrackScrpLength = size;
}
_Track_unlock(&state);
}
/* TrackPaste(track_ handle)
* Replace the selection with the scrap*/
void TrackPaste(track_ handle)
TrackHandle track_ handle;
{
_Track_state state;
_Track_lock(track_ handle, &state);
_Track_do_paste(track_ handle);
_Track_unlock(&state);
}
/* TrackDelete(track_ handle)
* Remove the selection.*/
void TrackDelete(track_ handle)
TrackHandle track_ handle;
{
_Track_state state;
_Track_lock(track_ handle, &state);
_Track_do_clear(track_ handle, TRUE, TRUE);
_Track_unlock(&state);
}
/* _Track_do_paste(track_ handle)
* If a selection exists, remove it; then insert the
* current scrap. Select the newly-inserted data.
* Use “intelligent paste” if available. Note that
* _Track_do_paste() may unlock the TrackRecord
* temporarily. */
void _Track_do_paste(track_ handle)
TrackHandle track_ handle;
{
register TrackPtr tr;
_Track_state state;
DOT start;
tr = *track_ handle;
_Track_caret(tr, _Track_caret_off);
_Track_do_clear(track_ handle, FALSE, TRUE);
tr = *track_ handle;
start = TR.selStart;
/* This is needed only for “intelligent paste.”
* Insert a space before the selection if there
* is a word before the selection.*/
if (is_at_word(tr, 0, TR.selStart, RIGHT)) {
_Track_do_insert(tr, TR.selStart, “ “, 1L);
TR.selStart++;
}
HLock(TrackScrpHandle);
_Track_do_insert(
tr, TR.selStart, *TrackScrpHandle, TrackScrpLength);
HUnlock(TrackScrpHandle);
TR.selEnd = TR.selStart + TrackScrpLength;
/* This is needed only for “intelligent paste.”
* Insert a space after the selection if there
* is a word after the selection.*/
if (is_at_word(tr, TR.selEnd, TR.textLength, LEFT))
_Track_do_insert(tr, TR.selEnd, “ “, 1L);
_Track_rebuild(track_ handle, start);
}
/* _Track_do_insert(track_ptr, offset, src, size)
* Stuff something into the text. */
void _Track_do_insert(tr, offset, src, size)
TrackPtr tr;
long offset;
Ptr src;
long size;
{
Munger(TR.hText, offset, NIL, 0L, src, size);
if (MemErr == noErr)
TR.textLength = GetHandleSize(TR.hText);
}
/* _Track_do_clear(track_ handle, rebuild, smart)
* If a selection exists, delete it; then rebuild the
* lineStarts vector if requested. Use “intelligent cut”
* if available and smart is TRUE. _Track_do_clear() may
* temporarily unlock the TrackRecord.*/
void _Track_do_clear(track_ handle, rebuild, smart)
TrackHandle track_ handle;
Boolean rebuild;
Boolean smart;
{
register TrackPtr tr;
long size;
tr = (*track_ handle);
if (TR.selEnd != TR.selStart) {
/* There is a selection. If the character to the
* left or right of the current selection is a
* space, cut it along with the selection.
* is_at_space() fails if “intelligent cut and
* paste” is disabled.*/
if (smart) {
if (is_at_space(tr, LEFT))
--TR.selStart;
else if (is_at_space(tr, RIGHT))
++TR.selEnd;
}
size = TR.selEnd - TR.selStart;
Munger(TR.hText, TR.selStart, NIL, size, NOT_NIL, 0);
TR.textLength -= size;
SetHandleSize(TR.hText, TR.textLength);
TR.selEnd = TR.selStart;
if (rebuild)
_Track_rebuild(track_ handle, TR.selStart);
}
}
/* is_at_space(direction)
* Use the ScriptMgr FindWord procedure to determine if
* the designated end of the selection is a word with an
* adjacent space. It is needed only for “intelligent
* cut and paste.”*/
static Boolean is_at_space(tr, direction)
register TrackPtr tr;
Boolean direction;
{
if (direction == LEFT) {
if (TR.selStart > 0
&& is_at_word(tr, TR.selStart, TR.selEnd, LEFT)
&& _Track_is_white(tr, *(TR.hText), TR.selStart-1))
return (TRUE);
}
else /* RIGHT */ {
if (TR.selEnd < TR.textLength
&& is_at_word(tr, TR.selStart, TR.selEnd, RIGHT)
&& _Track_is_white(tr, *(TR.hText), TR.selEnd))
return (TRUE);
}
return (FALSE);
}
/* is_at_word(track_ptr, start, end, direction)
* Use FindWord to decide whether start..end is a word at
* the designated end. This routine is needed only if
* you want “intelligent cut and paste.” */
static Boolean is_at_word(tr, start, end, direction)
register TrackPtr tr;
DOT start;
DOT end;
Boolean direction;
{
OffsetTable offsets;
/* This table has a bit set for characters that form
* words. The bits are “numbered” from the left.
* We need it to check on one-character selections
* as they might be punctuation.*/
static long word_break[] = {
0x00000000, 0x0000FFC0, /* 00..3F (allow digits) */
0x7FFFFFE1, 0x7FFFFFE0, /* 40..7F (allow letters) */
0xFFFFFFFF, 0x01030003, /* 80..BF (int’l letters) */
0x001F0080, 0x00000000 /* C0..FF (a few more) */
};
if (TR.wordBreak != NIL) {
if (direction == LEFT)
return (_Track_is_white(tr, *TR.hText, start));
else {
return (_Track_is_white(tr, *TR.hText, end));
}
}
if (!_Track_is_set(tr, _Track_use_script_ manager)
|| !_Track_is_set(tr, _Track_use_smart_cut_paste))
return (FALSE);
if (direction == LEFT)
FindWord(*TR.hText, end, start, TRUE, NIL, offsets);
else {
FindWord(*TR.hText, end, end, FALSE, NIL, offsets);
}
#if smgrVers >= 0x0210 /* See ScriptMgr.h */
/* In Think C version 4, the offset table is defined
* as a 3-element structure.*/
#define START offsets[0].offFirst
#define END offsets[0].offSecond
#else
/* This is specific to Think C version 3, where the
* offset table is defined as a 6-element short vector.
* Warning: no longer tested.*/
#define START offsets[0]
#define END offsets[1]
#endif
if ((END - START) == 1) {
return (BitTst(word_break, (*TR.hText)[START]));
}
else {
return ((END > START) ? TRUE : FALSE);
}
}
Listing: Hilite Selection Subroutines
/* TrackHilite.c
* Copyright © 1989 Martin Minow and MacTutor. All rights reserved.
* These routines invert the selection or draw insertion
* point. When called, _TrackPtr points to the locked
* TrackRecord. These routines are not called directly
* by the application program. */
#include “TrackEdit.h”
#include “Color.h”
#define TR (*tr)
#define NIL 0
static void invert_rect(
TrackPtr, LONGINT, LONGINT, LONGINT);
/* _Track_hilite(track_ptr, start, end)
* Invert the selection between start and end, both
* given as indices into *(_TrackPtr->string). Note:
* start is not necessarily “earlier” than end.*/
void _Track_hilite(tr, dot_start, dot_end)
register TrackPtr tr;
DOT dot_start;
DOT dot_end;
{
LONGINT row_start, row_end;
INTEGER col_start, col_end, start, end;
DOT temp;
/* If there is no selection, there’s nothing to
* invert. Note that we’re only called if the
* window is active.*/
if (dot_start == dot_end)
return; /* No hiliting needed */
/* It’s a real selection: normalize the selection
* so start is north-east of end and convert to
* pixel coordinates.*/
if (dot_start > dot_end) {
temp = dot_start;
dot_start = dot_end;
dot_end = temp;
}
row_start = _Track_row(tr, dot_start);
row_end = _Track_row(tr, dot_end);
col_start = _Track_dot_to_col(tr, dot_start);
col_end = _Track_dot_to_col(tr, dot_end);
/* If everything is on one row, do it.*/
if (row_start == row_end)
invert_rect(tr, row_start, col_start, col_end);
else {
/* We have several lines to invert. Start with the
* right end of the first line.*/
end = _Track_dot_to_eol(tr, row_start);
invert_rect(tr, row_start, col_start, end);
/* Do any complete rows. This can be optimized
* for many applications.*/
while (++row_start < row_end) {
start = _Track_dot_to_bol(tr, row_start);
end = _Track_dot_to_eol(tr, row_start);
invert_rect(tr, row_start, start, end);
}
/* Finally, do the last row.*/
start = _Track_dot_to_bol(tr, row_end);
invert_rect(tr, row_end, start, col_end);
}
}
/* _Track_caret(track_ptr, desired_state)
* Draw (or erase) the selection marker at selStart.
* (Make sure it’s in the mouse row.)
* Call the user’s caret routine if requested.*/
void _Track_caret(tr, state)
register TrackPtr tr;
INTEGER state;
{
/* If there is a real selection, or the text is not
* active, just return. Likewise if the caret state
* shouldn’t change. Otherwise, invert the caret.*/
if (!_Track_is_set(tr, _Track_is_active)
|| TR.selStart != TR.selEnd)
; /* No caret wanted */
else if (state == _Track_caret_on
&& _Track_is_set(tr, _Track_caret_visible))
; /* Want on and it’s still on */
else if (state == _Track_caret_off
&& !_Track_is_set(tr, _Track_caret_visible))
; /* Want off and it’s still off */
else {
/* Invert the caret on screen.*/
_Track_flip(tr, _Track_caret_visible);
_Track_invert_caret(tr);
}
}
void _Track_invert_caret(tr)
register TrackPtr tr;
{
PenState pen_state;
Rect caret;
LONGINT row;
LONGINT col;
DOT dot;
if (TR.selStart != TR.selEnd)
return;
row = _Track_row(tr, TR.selStart);
col = _Track_dot_to_col(tr, TR.selStart);
if (col == TR.viewRect.left && TR.selRow < row) {
/* Hack: if dot is at the start of a line, the true
* mouse point might be at the end of the previous
* line. In this case, the mouse row won’t equal
* dot’s row. Force the caret point so it’s to
* the right of the last character on the previous
* row.
* Further hack, if selStart is at the end of the
* text, look at the last character. If it’s
* a , caret is really on the next line.
* Grumble. */
if (TR.selStart != (DOT) TR.textLength
|| TR.textLength == 0
|| (*TR.hText)[TR.selStart - 1] != ‘\r’) {
row = TR.selRow;
col = _Track_dot_to_col(tr, TR.selStart - 1)
+ CharWidth((*TR.hText)[TR.selStart - 1]);
}
}
/* Convert to the equivalent pixel coordinate and
* draw it if it might be visible. */
row = _Track_row_pixel(tr, row);
if (row < (LONGINT) TR.viewRect.top - TR.lineHeight
|| row > (LONGINT) TR.viewRect.bottom + TR.lineHeight
|| col < (LONGINT) TR.viewRect.left
|| col > (LONGINT) TR.viewRect.right)
return; /* Invisible */
if (TR.caretHook != NIL) {
SetRect(&caret, (int) col, (int) row - TR.fontAscent,
(int) col + 1, (int) row + TR.fontDescent);
CallPascal(&caret, tr, TR.caretHook);
}
else {
/* Fancy “caret.”*/
GetPenState(&pen_state);
PenNormal();
PenMode(patXor);
MoveTo(col, (int) row - TR.fontAscent);
Line(0, TR.fontAscent);
Line(-TR.fontDescent, TR.fontDescent);
Move(TR.fontDescent * 2, 0);
Line(-TR.fontDescent, -TR.fontDescent);
SetPenState(&pen_state);
}
}
/* invert_rect(tr, row, start, end)
* Invert the screen rectangle on the specified row
* between the specified horizontal pixels.
* Note: end points just to the right of the last pixel
* to invert. Also, note that this routine understands
* that points are normalized, extending them in the
* appropriate direction to cover the character.*/
static void invert_rect(tr, row, start, end)
register TrackPtr tr;
LONGINT row;
LONGINT start;
LONGINT end;
{
Rect box;
/* Convert row to the equivalent pixel coordinate.*/
row = _Track_row_pixel(tr, row);
if (start == end
|| row < (LONGINT) TR.viewRect.top - TR.lineHeight
|| row > (LONGINT) TR.viewRect.bottom + TR.lineHeight)
return;
if (start < (LONGINT) TR.viewRect.left)
start = (LONGINT) TR.viewRect.left;
if (end > (LONGINT) TR.viewRect.right)
end = (LONGINT) TR.viewRect.right;
if (start >= end)
return;
box.left = (int) start;
box.right = (int) end;
box.top = (int) row + TR.fontDescent - TR.lineHeight;
box.bottom = (int) row + TR.fontDescent;
if (SectRect(&box, &TR.viewRect, &box)) {
if (TR.highHook != NIL) /* Use user’s */
CallPascal(&box, tr, TR.highHook); /* hiliter */
else {
/* Properly hilite color screens (ok for b/w, too)
* See Inside Mac V, p. 62.*/
HiliteMode &= ~(1 << hiliteBit);
InvertRect(&box);
}
}
}
Listing: Idle Subroutine
/* TrackIdle.c
* Copyright © 1989 Martin Minow and MacTutor. All rights reserved.
* void TrackIdle(track_ handle)
* TrackHandle track_ handle;
* Flash the cursor.*/
#include “TrackEdit.h”
#define TR (*tr)
void TrackIdle(track_ handle)
TrackHandle track_ handle;
{
register TrackPtr tr;
_Track_state state;
register LONGINT now;
tr = _Track_lock(track_ handle, &state);
if (TR.selStart == TR.selEnd) {
now = TickCount();
if (now > TR.caretTime) {
_Track_caret(tr, _Track_caret_invert);
TR.caretTime = now + CaretTime;
}
}
_Track_unlock(&state);
}
Listing: Initialization Subroutines
/* TrackInit.c
* Copyright © 1989 Martin Minow and MacTutor. All rights reserved.
* void TrackInit()
*
* TrackHandle TrackNew(dest, view)
* Rect *dest;
* Rect *view;
*
* void TrackDispose(track_ handle)
* TrackHandle track_ handle;
* Initialize, create, and destroy TrackRecords. */
#include “TrackEdit.h”
#define TR (*tr)
/* Track library private data. */
Handle TrackScrpHandle; /* Track Scrap */
LONGINT TrackScrpLength; /* Scrap length */
void TrackInit()
{
TrackScrpHandle = NewHandle(0);
TrackScrpLength = 0;
}
TrackHandle TrackNew(line_width, viewRectp)
INTEGER line_width;
Rect *viewRectp;
{
TrackHandle track_ handle;
TrackPtr tr;
register char *ptr;
register int i;
FontInfo info;
Handle temp;
track_handle =
(TrackHandle) NewHandle(sizeof (TrackRecord));
/* Initialize the record by clearing out all fields.
* Note that we can’t call _Track_lock here.*/
HLock(track_ handle);
tr = (*track_ handle);
ptr = (char *) tr;
for (i = 0; i < sizeof (TrackRecord); i++)
*ptr++ = 0;
TR.inPort = thePort;
TR.hText = NewHandle(0L);
/* Test whether the ScriptManager is installed,
* Note that the ROMS must not change while the
* program is running.*/
#define Unimplemented 0x9F
#define ScriptUtil 0xB5
if ((GetTrapAddress(Unimplemented)
!= GetTrapAddress(ScriptUtil))
&& GetEnvirons(smEnabled)) {
_Track_set(tr, _Track_has_script_ manager);
_Track_set(tr, _Track_use_script_ manager);
_Track_set(tr, _Track_use_smart_cut_paste);
}
TR.lineWidth = line_width;
TR.viewRect = *viewRectp;
TR.txFont = thePort->txFont;
TR.txFace = thePort->txFace;
TR.txMode = thePort->txMode;
TR.txSize = thePort->txSize;
GetFontInfo(&info);
TR.fontAscent = info.ascent;
TR.fontDescent = info.descent;
TR.lineHeight =
info.ascent + info.descent + info.leading;
HUnlock(track_ handle);
return (track_ handle);
}
void TrackDispose(track_ handle)
TrackHandle track_ handle;
{
DisposHandle((*track_ handle)->hText);
DisposHandle(track_ handle);
}
Listing: Insert Text Subroutine
/* TrackInsert.c
* Copyright © 1989 Martin Minow and MacTutor. All rights reserved.
* TrackInsert(text, length, track_ handle)
* Ptr text;
* LONGINT length;
* TrackHandle track_ handle;
* Insert the specified text just before the selection
* range. Do not change either the selection range or
* the Track private scrap.*/
#include “TrackEdit.h”
#define TR (*tr)
void TrackInsert(text, length, track_ handle)
Ptr text;
LONGINT length;
TrackHandle track_ handle;
{
register TrackPtr tr;
_Track_state state;
DOT start;
tr = _Track_lock(track_ handle, &state);
start = TR.selStart;
_Track_do_insert(tr, TR.selStart, text, length);
TR.selStart += length;
TR.selEnd += length;
_Track_rebuild(track_ handle, start);
_Track_unlock(&state);
}
Listing: Insert Character Subroutine
/* TrackKey.c
* Copyright © 1989 Martin Minow and MacTutor. All rights reserved.
*
* TrackKey(key, track_ handle)
* key The character to insert
* track_handle The TrackRecord handle.
* Replace the selection character, leaving an insertion
* point just past the inserted charcter. If the key
* is backspace (hex 08), the selection or character
* before the insertion point is deleted.
*
* Note: we don’t do “arrow-key” selection as described
* in Inside Mac, volume IV.*/
#include “TrackEdit.h”
#define TR (*tr)
void TrackKey(c, track_ handle)
CHAR c;
TrackHandle track_ handle;
{
register TrackPtr tr;
_Track_state state;
DOT start;
char text[1];
tr = _Track_lock(track_ handle, &state);
start = TR.selStart;
if (c != ‘\b’) {
_Track_do_clear(track_ handle, FALSE, FALSE);
tr = (*track_ handle);
text[0] = c;
_Track_do_insert(tr, TR.selStart, text, 1L);
TR.selStart++;
TR.selEnd = TR.selStart;
}
else {
/* Backspace cuts the selection, or, if there is
* none, the preceeding character. */
if (TR.selEnd == TR.selStart
&& TR.selStart > 0)
--TR.selStart;
start = TR.selStart;
_Track_do_clear(track_ handle, FALSE, FALSE);
}
_Track_rebuild(track_ handle, start);
_Track_unlock(&state);
}
Listing: Locate Selection Subroutines
/* TrackLocation.c
* Copyright © 1989 Martin Minow and MacTutor. All rights reserved.
* These routines convert from display (mouse) coordinates
* to the internal DOT value that designates data.
*
* DOT TrackGetOffset(point, track_ handle)
* Point point;
* TrackHandle track_ handle;
* TrackGetOffset returns the index of the character
* corresponding to the given point (expressed in
* window-local coordinates).
*
* _Track_mouse_to_dot() Convert window-local mouse
* coordinates to a character designator.
* _Track_word() Extend window-local coodinates
* to designate the start and end of a word.
* _Track_is_white() Determine if a character is
* “whitespace”.
* _Track_pixel_row() Locate the row (line of text)
* that is designated by the mouse vertical loc. */
#include “TrackEdit.h”
#define TR (*tr)
#define NIL 0L
DOT TrackGetOffset(point, track_ handle)
Point point;
TrackHandle track_ handle;
{
register TrackPtr tr;
_Track_state state;
DOT result;
tr = _Track_lock(track_ handle, &state);
result =_Track_mouse_to_dot(tr, point);
_Track_unlock(&state);
return (result);
}
/* _Track_mouse_to_dot(mouse)
* Convert the mouse position (given in window-local
* coordinates) to an index to the specified character in
* the string. If the mouse is in the left half of the
* character, the index refers to the selected character;
* otherwise it refers to the following character.*/
DOT _Track_mouse_to_dot(tr, mouse)
register TrackPtr tr;
Point mouse;
{
register LONGINT row;
register INTEGER col;
register int i;
register DOT dot;
register DOT next_row;
register int width;
row = _Track_pixel_row(tr, mouse.v);
if (row < 0)
return (0);
else if (row >= TR.nLines)
return (TR.textLength);
else {
dot = TR.lineStarts[row];
next_row = TR.lineStarts[row + 1];
col = mouse.h - _Track_h_origin(tr, row);
width = 0;
while (dot < next_row
&& (*TR.hText)[dot] != ‘\r’) {
width = CharWidth((*TR.hText)[dot]);
if (col < width)
break;
col -= width;
dot++;
}
/* If the mouse is in the right-half of the
* character, (and it’s not at the end of the row),
* move it forward: note that the DOT value is
* between two characters.*/
if (dot < next_row && col >= (width / 2))
dot++;
}
return (dot);
}
/* _Track_word(tr, mouse, DOT *, DOT *)
* Extend dot in both directions to the nearest word
* boundary. Note that the last character on the line is
* treated specially. Bug alert: FindWord uses a 16 bit
* integer for textLength and offset. Thus, as our indices
* are longs (so we can have a lot of text), we should
* really fiddle with *hText so FindWord only sees one
* line of text.*/
void _Track_word(tr, mouse, word)
register TrackPtr tr;
Point mouse;
_Track_Loc *word;
{
register DOT dot;
register LONGINT row;
register DOT end;
OffsetTable offsets;
dot = _Track_mouse_to_dot(tr, mouse);
if (_Track_is_set(tr, _Track_use_script_ manager)) {
FindWord(*TR.hText, (INTEGER) TR.textLength,
(INTEGER) dot, TRUE, NIL, offsets);
#if smgrVers >= 0x0210 /* See ScriptMgr.h */
/* In Think C version 4, the offset table is defined
* as a 3-element structure.*/
#define START offsets[0].offFirst
#define END offsets[0].offSecond
#else
/* This is specific to Think C version 3, where the
* offset table is defined as a 6-element short vector.
* Warning: no longer tested.*/
#define START offsets[0]
#define END offsets[1]
#endif
word->start = (DOT) START;
word->end = (DOT) END;
}
else {
row = _Track_row(tr, dot);
end = TR.lineStarts[row];
word->start = dot;
while (word->start > end
&& !_Track_is_white(
tr, *(TR.hText), word->start - 1)) {
--(word->start);
}
word->end = dot;
if (_Track_pixel_row(tr, mouse.v) == row) {
/* Scan for the whitespace that follows this word.
* Note that we don’t scan if dot has crept into
* the next line. */
end = TR.lineStarts[row + 1];
while (word->end < end
&& !_Track_is_white(tr, (*TR.hText), word->end))
(word->end)++;
}
}
}
/* _Track_is_white(track_pointer, text_pointer, index)
* Return TRUE if the character at this location is
* whitespace, calling the application’s wordbreak
* routine if one is present.*/
Boolean _Track_is_white(tr, ptr, index)
register TrackPtr tr;
char *ptr;
DOT index;
{
if (TR.wordBreak == 0)
return (((unsigned) ptr[index]) <= ‘ ‘);
else {
return (CallPascalB(ptr, index, TR.wordBreak));
}
}
/* Determine the row that contains this mouse location
* (in window-local coordinates). */
LONGINT _Track_pixel_row(tr, vpixel)
register TrackPtr tr;
INTEGER vpixel;
{
return (
((LONGINT) vpixel - TR.viewRect.top + TR.topPixel)
/ TR.lineHeight);
}
Listing: Lock TrackRecord Subroutine
/* TrackLock.c
* Copyright © 1989 Martin Minow and MacTutor. All rights reserved.
* _Track_lock(track_ handle, &state)
* TrackHandle track_ handle;
* _Track_unlock(&state)
* _Track_lock() is called at the beginning of all
* functions to lock the TrackHandle in memory, save
* the old port, clip region and text parameters; and
* and set our port, clipRect, and text parameters.
* _Track_unlock() is called at the end to restore
* the original parameters.*/
#include “TrackEdit.h”
#define TR (*tr)
/* _Track_lock is called at the start of all
* user-callable functions to lock the TrackRecord
* in memory. It returns a pointer to the record.*/
TrackPtr _Track_lock(track_ handle, sp)
TrackHandle track_ handle;
register _Track_state *sp;
{
register TrackPtr tr;
sp->track_handle = track_ handle;
sp->oldHState = HGetState(track_ handle);
MoveHHi(track_ handle);
HLock(track_ handle);
tr = (*track_ handle);
GetPort(&sp->oldPort);
SetPort(TR.inPort);
/* Save the old clip region and set the clip region to
* the intersection of the old region and the viewRect.*/
sp->oldClip = NewRgn();
GetClip(sp->oldClip);
ClipRect(&TR.viewRect);
SectRgn(
sp->oldClip, thePort->clipRgn, thePort->clipRgn);
/*Save the old drawing parameters. (Perhaps even color?)*/
sp->oldFont = thePort->txFont; TextFont(TR.txFont);
sp->oldFace = thePort->txFace; TextFace(TR.txFace);
sp->oldMode = thePort->txMode; TextMode(TR.txMode);
sp->oldSize = thePort->txSize; TextSize(TR.txSize);
return (tr);
}
/* _Track_unlock()
* Restore the track handle to the state it had on entrance.*/
void _Track_unlock(sp)
register _Track_state *sp;
{
TextSize(sp->oldSize);
TextMode(sp->oldMode);
TextFace(sp->oldFace);
TextFont(sp->oldFont);
SetClip(sp->oldClip);
DisposeRgn(sp->oldClip);
SetPort(sp->oldPort);
HSetState(sp->track_ handle, sp->oldHState);
}
Listing: Position Cursor Subroutines
/* TrackPosition.c
* Copyright © 1989 Martin Minow and MacTutor. All rights reserved.
* Convert from textual to positional coordinates.
* void TrackGetPosition(dot, track_ handle, hpixel, vpixel)
* DOT dot;
* TrackHandle track_ handle;
* LONGINT *hpixel;
* LONGINT *vpixel;
* TrackGetPosition returns the point (in window-local
* coordinates) of the character at the indicated
* position. Note that the results are long integers.
* You should not assume that the point is visible.
*
* LONGINT TrackGetHeight(endline, startline, track_ handle)
* LONGINT endline;
* LONGINT startline;
* TrackHandle track_ handle;
* TrackGetHeight returns the total height of all of
* the lines in the text between and including startline
* and endline.
* _Track_dot_to_col() determines the horizontal pixel
* coordinate for the specified DOT.
* _Track_dot_to_bol() locates the pixel coordinates at
* the beginning of the line containing DOT.
* _Track_dot_to_eol() locates the pixel coordinates at
* the end of the line containing DOT.
* _Track_row() determines the row (index into lineStarts)
* containing DOT.
* _Track_row_pixel() determines the vertical location
* in window-local coordinates for the given row. */
#include “TrackEdit.h”
#define TR (*tr)
static LONGINT h_pixel(TrackPtr, LONGINT, INTEGER);
static INTEGER line_length(TrackPtr, LONGINT);
/* Return the window-position of dot. */
void TrackGetPoint(dot, track_ handle, hpixel, vpixel)
DOT dot;
TrackHandle track_ handle;
LONGINT *hpixel;
LONGINT *vpixel;
{
register TrackPtr tr;
_Track_state state;
LONGINT row;
INTEGER col;
tr = _Track_lock(track_ handle, &state);
row = _Track_row(tr, dot);
col = (INTEGER) (dot - TR.lineStarts[row]);
*hpixel = h_pixel(tr, row, col);
*vpixel = _Track_row_pixel(tr, row);
_Track_unlock(&state);
}
/* Return the height of the selection (in pixels). */
LONGINT TrackGetHeight(end, start, track_ handle)
LONGINT end;
LONGINT start;
TrackHandle track_ handle;
{
register TrackPtr tr;
_Track_state state;
LONGINT result;
tr = _Track_lock(track_ handle, &state);
result = _Track_row_pixel(tr, _Track_row(tr, end))
- _Track_row_pixel(tr, _Track_row(tr, start));
_Track_unlock(&state);
return (result);
}
/* _Track_dot_to_col()
* Compute the horizontal position of the DOT in the window.*/
LONGINT _Track_dot_to_col(tr, dot)
register TrackPtr tr;
DOT dot;
{
LONGINT row;
INTEGER col;
row = _Track_row(tr, dot);
col = (INTEGER) (dot - TR.lineStarts[row]);
return (h_pixel(tr, row, col));
}
/* _Track_dot_to_bol()
* Compute the horizontal position of the beginning of
* the line.*/
LONGINT _Track_dot_to_bol(tr, row)
register TrackPtr tr;
LONGINT row;
{
return (_Track_h_origin(tr, row));
}
/* _Track_dot_to_eol()
* Compute the horizontal position of the end of the line.*/
LONGINT _Track_dot_to_eol(tr, row)
register TrackPtr tr;
LONGINT row;
{
INTEGER col;
col = line_length(tr, row);
return (h_pixel(tr, row, col));
}
/* _Track_row(tr, dot)
* Return the row that contains dot. The ancient and
* honorable binary-chop table lookup algorithm.*/
LONGINT _Track_row(tr, dot)
register TrackPtr tr;
DOT dot;
{
register LONGINT mid;
register LONGINT low;
register LONGINT high;
low = 0;
high = TR.nLines - 1;
while (low <= high) {
mid = low + (high - low) / 2;
if (dot < TR.lineStarts[mid])
high = mid - 1;
else if (dot >= TR.lineStarts[mid + 1])
low = mid + 1;
else {
return (mid);
}
}
/* Don’t return beyond the last value. */
if (low >= TR.nLines && TR.nLines > 0)
low = TR.nLines - 1;
return (low);
}
LONGINT _Track_row_pixel(tr, row)
register TrackPtr tr;
LONGINT row;
{
return (
(row * TR.lineHeight)
+ TR.viewRect.top /* To window-local space */
+ TR.fontAscent /* To character origin */
- TR.topPixel /* Offset by scrolling */
);
}
/* h_pixel()
* Compute the horizontal pixel position in window-local
* coordinates of the specified [row, col]. */
LONGINT h_pixel(tr, row, col)
register TrackPtr tr;
LONGINT row;
INTEGER col;
{
LONGINT pixel;
Ptr line_start;
INTEGER state;
state = HGetState(TR.hText);
HLock(TR.hText);
line_start = (*TR.hText) + TR.lineStarts[row];
pixel = TextWidth(line_start, 0, col)
+ _Track_h_origin(tr, row);
HSetState(TR.hText, state);
return (pixel);
}
/* _Track_h_origin()
* Compute the horizontal pixel position in window-local
* coordinates of the first (i.e., leftmost) character
* in this row. This is the only function that
* knows about text justification.*/
LONGINT _Track_h_origin(tr, row)
register TrackPtr tr;
LONGINT row;
{
LONGINT pixel;
INTEGER window_width;
INTEGER text_width;
INTEGER state;
pixel = TR.viewRect.left - TR.leftPixel;
if (TR.just != 0) { /* Not left justified? */
window_width = TR.viewRect.right - TR.viewRect.left;
state = HGetState(TR.hText);
HLock(TR.hText);
text_width = TextWidth((*TR.hText) + TR.lineStarts[row],
0, line_length(tr, row));
HSetState(TR.hText, state);
if (TR.just < 0) /* Right justified */
pixel += ( window_width - text_width);
else { /* Center justified */
pixel += (( window_width - text_width) / 2);
}
}
return (pixel);
}
/* line_length(tr, row)
* Return the number of characters in the specified row. */
static int line_length(tr, row)
register TrackPtr tr;
LONGINT row;
{
if (row >= TR.nLines)
return (0);
return (TR.lineStarts[row + 1] - TR.lineStarts[row]);
}
Listing: Rebuild Line Pointers Subroutine
/* TrackRebuild.c
* Copyright © 1989 Martin Minow and MacTutor. All rights reserved.
* This routine is called whenever the contents of
* the TrackRecord text changes. It rebuilds the
* line start vector and repaints the display.*/
#include “TrackEdit.h”
#define TR (*tr)
#define NIL 0L
#define TR_SIZE(tr)
sizeof (TrackRecord) + (sizeof (DOT) * (*tr).nLines);
static TrackPtr newline(TrackHandle, DOT);
/* _Track_rebuild(track_ handle, start)
*
* _Track_rebuild() is called whenever the content of
* the TrackRecord text string changes. It rebuilds
* the lineStarts vector and forces the text to be
* redrawn on the screen. _Track_rebuild is the only
* routine that understands line breaks and such.
* Walk through the string to build a vector that locates
* the start of each line. There is always one extra entry
* so the number of characters in the last line can be
* computed. The display window is the current port.
* Note that the word/line algorithm only notices
* whitespace. The start parameter identifies the first
* character that has changed.
*
* Since _Track_rebuild temporarily unlocks the
* TrackRecord, it returns the current value of the
* relocked record.*/
TrackPtr _Track_rebuild(track_ handle, start)
TrackHandle track_ handle;
DOT start;
{
register TrackPtr tr;
register unsigned c;
register DOT index;
register DOT line_break; /* space here */
register DOT line_start; /* -> start of line */
register INTEGER line_width; /* Current width */
LONGINT size;
LONGINT row;
LONGINT old_row, new_row;
Rect box;
tr = *track_ handle;
MoveHHi(TR.hText); /* Lock the text */
HLock(TR.hText); /* record. */
if (start == 0) {
TR.nLines = 0;
index = 0;
}
else {
/* Start at the previous line since, if we delete
* the end of the first word on a line, it might
* fit on the previous line. */
TR.nLines = _Track_row(tr, start);
if (TR.nLines > 0)
--TR.nLines;
index = TR.lineStarts[TR.nLines];
}
/* Get the screen position *before* munging the lines.*/
old_row = _Track_row(tr, start);
tr = newline(track_ handle, index);
line_start = index;
line_width = 0;
line_break = 0;
/* Each pass through this loop eats one character.*/
while (index < TR.textLength) {
c = (*TR.hText)[index++]; /* Grab the byte */
if (c == ‘\r’)
goto do_newline;
line_width += CharWidth(c);
if (TR.crOnly < 0 || line_width <= TR.lineWidth) {
/*
* This byte fits. If it’s a word break, remember
* its location for the end of line test.
*/
if (_Track_is_white(tr, *TR.hText, index - 1))
line_break = index;
}
else {
/* We’re at the end of the line. If we’ve seen
* a word, break the line there. Else, break
* at the previous byte (if there is one on this
* line, if the first byte on the line doesn’t
* fit, stuff it in so we don’t loop forever.*/
if (line_break != 0)
index = line_break; /* Break at word */
else if (index > (line_start + 1)) {
--index; /* Rescan this one */
}
do_newline:
tr = newline(track_ handle, index);
line_start = index;
line_width = 0;
line_break = 0;
}
} /* Loop on characters */
TR.lineStarts[TR.nLines] = TR.textLength;
HUnlock(TR.hText);
/* All of the line lengths have been set. Adjust
* the TrackRecord size in case it’s shrunk.*/
size = TR_SIZE(tr)
if (size > GetHandleSize(track_ handle)) {
HUnlock(track_ handle);
SetHandleSize(track_ handle, size);
MoveHHi(track_ handle);
HLock(track_ handle);
tr = *track_ handle;
}
/* Locate this row on the screen. If it (or anything
* later on) will be visible, repaint as little
* as possible. Note that we may have to repaint
* the row that the selection *was* on before we
* started messing with the lines.*/
new_row = _Track_row(tr, start);
if (new_row < old_row)
old_row = new_row;
row = _Track_row_pixel(tr, old_row) - TR.fontAscent;
box = TR.viewRect;
if (row <= (LONGINT) box.bottom) {
if (row > (LONGINT) box.top)
box.top = row;
EraseRect(&box);
_Track_do_update(tr, &box);
ValidRect(&box);
}
return (tr);
}
/* newline()
* Called when a new line is ready: this sets the
* handle size and stores the index to the first
* character in this line.*/
static TrackPtr newline(track_ handle, index)
TrackHandle track_ handle;
DOT index;
{
register TrackPtr tr;
_Track_state state;
LONGINT size;
tr = *track_ handle;
++TR.nLines;
size = TR_SIZE(tr);
if (size > GetHandleSize(track_ handle)) {
/*
* Reallocate the track handle to its proper size.
* You can allocate a chunk of lines here without
* problems.
*/
HUnlock(track_ handle);
SetHandleSize(track_ handle, size);
MoveHHi(track_ handle);
HLock(track_ handle);
tr = *track_ handle;
}
TR.lineStarts[TR.nLines - 1] = index;
return (tr);
}
Listing: Scrap Management Subroutines
/* TrackScrap.c
* Copyright © 1989 Martin Minow and MacTutor. All rights reserved.
*
* OSErr TrackFromScrap()
* Copy the desk scrap to the Track private scrap.
* Return noErr if successful, else an error code.
*
* OSErr TrackToScrap()
* Copy the Track private scrap to the desk scrap.
* Return noErr if successful, else an error code.
*
* Handle TrackScrapHandle()
* Return a handle to the Track private scrap.
*
* LONGINT TrackGetScrapLen()
* Return the length of the Track private scrap in bytes.
*
* TrackSetScrapLen(length)
* LONGINT length;
* Set the size of the Track private scrap. This will
* call SetHandleSize() on the track handle. */
#include “TrackEdit.h”
/* TrackFromScrap()
* If there is a TEXT item in the desk scrap, read it in
* and store it in the Track private scrap. Return
* the error status (noErr is normal, noTypErr means
* there wasn’t any TEXT in the scrap, anything is
* is trouble.*/
OSErr TrackFromScrap()
{
long offset;
Handle scrap;
long status;
SetHandleSize(TrackScrpHandle, 0);
status = GetScrap(TrackScrpHandle, ‘TEXT’, &offset);
if (status >= 0) {
TrackScrpLength = status;
status = noErr;
}
return (status);
}
/* TrackToScrap()
* Copy the current selection to the Desk scrap. Return
* noErr if ok, else an error code.*/
OSErr TrackToScrap()
{
OSErr status;
MoveHHi(TrackScrpHandle);
HLock(TrackScrpHandle);
status = PutScrap(
TrackScrpLength, ‘TEXT’, *TrackScrpHandle);
HUnlock(TrackScrpHandle);
return (status);
}
/* Handle
* TrackScrapHandle()
* Return a handle to the Track private scrap. Note:
* this is the *real* handle, not a copy.*/
Handle TrackScrapHandle()
{
return (TrackScrpHandle);
}
/* LONGINT TrackGetScrapLen()
* Return the length of the Track private scrap in bytes. */
LONGINT TrackGetScrapLen()
{
return (TrackScrpLength);
}
/* TrackSetScrapLen(length)
* LONGINT length;
* Set the size of the Track private scrap. This will
* call SetHandleSize() on the track handle.*/
void TrackSetScrapLen(length)
LONGINT length;
{
SetHandleSize(TrackScrpHandle, length);
TrackScrpLength = length;
}
Listing: Scroll Text Subroutine
/* TrackScroll.c
* Copyright © 1989 Martin Minow and MacTutor. All rights reserved.
* void TrackScroll(horiz, vert, track_ handle)
* LONGINT horiz;
* LONGINT vert;
* TrackHandle track_ handle;
* Scroll the TrackRecord text. Both values are in
* pixels.
*
* void TrackPinScroll(horiz, vert, track_ handle)
* LONGINT horiz;
* LONGINT vert;
* TrackHandle track_ handle;
* Like TrackScroll, but stop scrolling when the last line
* is visible.
*/
#include “TrackEdit.h”
#define TR (*tr)
#define abs(x) ((x) < 0 ? (-(x)) : (x))
#define height(r) ((r).bottom - (r).top)
#define width(r) ((r).right - (r).left)
#define pin(delta, current, max) (
(delta < 0) ? ((current <= 0) ? 0 : delta)
: ((current >= max) ? 0 : delta)
)
void TrackScroll(hscroll, vscroll, track_ handle)
LONGINT hscroll;
LONGINT vscroll;
TrackHandle track_ handle;
{
register TrackPtr tr;
_Track_state state;
tr = _Track_lock(track_ handle, &state);
_Track_do_scroll(tr, hscroll, vscroll);
_Track_unlock(&state);
}
void TrackPinScroll(hscroll, vscroll, track_ handle)
LONGINT hscroll;
LONGINT vscroll;
TrackHandle track_ handle;
{
register TrackPtr tr;
_Track_state state;
LONGINT hmax, vmax;
tr = _Track_lock(track_ handle, &state);
hmax = TR.lineWidth - width(TR.viewRect);
vmax = (TR.nLines * TR.lineHeight)
+ TR.fontDescent - height(TR.viewRect);
vscroll = pin(vscroll, TR.topPixel, vmax);
if (TR.crOnly >= 0)
hscroll = pin(hscroll, TR.leftPixel, hmax);
else if (hscroll < 0 && TR.leftPixel <= 0)
hscroll = 0;
_Track_do_scroll(tr, hscroll, vscroll);
_Track_unlock(&state);
}
void _Track_do_scroll(tr, hscroll, vscroll)
register TrackPtr tr;
LONGINT hscroll;
LONGINT vscroll;
{
RgnHandle old_clip;
RgnHandle scroll_region;
Rect scroll_rect;
/* Save the old clip region so we can be called
* multiple times between update events. This
* is needed for autoscrolling.*/
old_clip = NewRgn();
GetClip(old_clip);
scroll_region = NewRgn();
TR.topPixel += vscroll;
TR.leftPixel += hscroll;
if (abs(vscroll) > height(TR.viewRect)
|| abs(hscroll) > width(TR.viewRect)) {
EraseRect(&TR.viewRect);
scroll_rect = TR.viewRect;
RectRgn(scroll_region, &scroll_rect);
}
else {
ScrollRect(&TR.viewRect, (INTEGER) -hscroll,
(INTEGER) -vscroll, scroll_region);
scroll_rect = (*scroll_region)->rgnBBox;
SetClip(scroll_region);
}
_Track_do_update(tr, &scroll_rect);
ValidRgn(scroll_region);
DisposeRgn(scroll_region);
SetClip(old_clip);
DisposeRgn(old_clip);
}
Listing: Set Justification Subroutine
/* TrackSetJust.c
* Copyright © 1989 Martin Minow and MacTutor. All rights reserved.
* void
* TrackSetJust(just, track_ handle)
* INTEGER just;
* TrackHandle track_ handle;
* Flash the cursor.
*/
#include “TrackEdit.h”
#define TR (*tr)
void TrackSetJust(just, track_ handle)
INTEGER just;
TrackHandle track_ handle;
{
register TrackPtr tr;
_Track_state state;
tr = _Track_lock(track_ handle, &state);
TR.just = just;
InvalRect(&TR.viewRect);
_Track_unlock(&state);
}
Listing: Set Selection Subroutine
/* TrackSetSelect.c
* Copyright © 1989 Martin Minow and MacTutor. All rights reserved.
* void TrackSetSelect(start, end, track_ handle)
* DOT start;
* DOT end;
* TrackHandle track_ handle;
* Set the selection range.
*/
#include “TrackEdit.h”
#define TR (*tr)
void TrackSetSelect(sel_start, sel_end, track_ handle)
DOT sel_start;
DOT sel_end;
TrackHandle track_ handle;
{
register TrackPtr tr;
_Track_state state;
static Point zero_point;
TrackDeactivate(track_ handle); /* Undraw selection */
tr = (*track_ handle); /* Lock unnecessary */
if ((TR.selStart = sel_start) < 0)
TR.selStart = 0;
if ((TR.selEnd = sel_end) > TR.textLength)
TR.selEnd = TR.textLength;
TR.selRow = _Track_row(tr, TR.selStart);
TrackActivate(track_ handle); /* Redraw selection */
}
Listing: Set Text Subroutine
/* TrackSetText.c
* Copyright © 1989 Martin Minow and MacTutor. All rights reserved.
* TrackSetText(text, length, track_ handle)
* text Data to insert
* length The number of bytes to insert
* track_handle The TrackRecord handle
* Copy the specified text into the TrackRecord. Any
* text currently in the TrackRecord is lost. The
* selection range is set to an insertion point at the
* end of the text. (Note: TextEdit TESetText “doesn’t
* dispose of any text currently in the edit record.”)
* The text origin is reset to “upper-left” corner.
*/
#include “TrackEdit.h”
#define TR (*tr)
void TrackSetText(text, length, track_ handle)
Ptr text;
LONGINT length;
TrackHandle track_ handle;
{
register TrackPtr tr;
_Track_state state;
Boolean track_was_active;
/* Dump the selection, stuff the text, then
* recreate the selection.*/
track_was_active =
_Track_is_set((*track_ handle), _Track_is_active);
if (track_was_active)
TrackDeactivate(track_ handle);
tr = _Track_lock(track_ handle, &state);
SetHandleSize(TR.hText, length);
BlockMove(text, *TR.hText, length);
TR.selStart = TR.selEnd = length;
TR.textLength = length;
TR.topPixel = 0;
TR.leftPixel = 0;
_Track_rebuild(track_ handle, 0L);
_Track_unlock(&state);
if (track_was_active)
TrackActivate(track_ handle);
}
Listing: Update Event Subroutines
/* TrackUpdate.c
* Copyright © 1989 Martin Minow and MacTutor. All rights reserved.
* Update the track data. User programs call TrackUpdate,
* internally, _Track_do_update is called.
* void TrackUpdate(rectp, track_ handle)
* Rect *rectp;
* TrackHandle track_ handle;
*
* void _Track_do_update(tr, rectp)
* TrackPtr tr;
* Rect *rectp;
*/
#include “TrackEdit.h”
#define TR (*tr)
static void drawtext(TrackPtr, Rect *);
void TrackUpdate(update_rect_ptr, track_ handle)
Rect *update_rect_ptr;
TrackHandle track_ handle;
{
register TrackPtr tr;
_Track_state state;
tr = _Track_lock(track_ handle, &state);
_Track_do_update(tr, update_rect_ptr);
_Track_unlock(&state);
}
void _Track_do_update(tr, update_rect_ptr)
register TrackPtr tr;
Rect *update_rect_ptr;
{
RgnHandle clip;
Rect view;
/* Get a copy of the current grafPort's clipping
* region (TrackLock has already clipped the window
* to the TrackRecord's viewRect) and intersect that
* with the update_rect. Don't bother restoring
* the clipRgn: TrackUnlock will do that.*/
clip = NewRgn();
RectRgn(clip, update_rect_ptr);
SectRgn(clip, thePort->clipRgn, thePort->clipRgn);
/* Draw any text inside the update rectangle, then.
* if the window is active, invert any hilited text.*/
drawtext(tr, update_rect_ptr);
if (_Track_is_set(tr, _Track_is_active)) {
_Track_hilite(tr, TR.selStart, TR.selEnd);
if (_Track_is_set(tr, _Track_caret_visible))
_Track_invert_caret(tr);
}
DisposeRgn(clip);
}
/* draw_text(track_ptr, draw_rect)
* Draw text onto the screen. The caller has set
* the grafPort's clip rect. The draw_rect defines
* the top and bottom of the data to be drawn.*/
static void drawtext(tr, view)
register TrackPtr tr;
Rect *view;
{
register int vpixel;
register long first, last;
register int t_width, w_width;
register int hpixel;
/* Locate the first and last rows to be drawn.
* Note that we may have a partial last line,
* so we try to draw one extra.*/
first = _Track_pixel_row(tr, view->top);
last = _Track_pixel_row(tr, view->bottom) + 1;
if (last >= TR.nLines)
last = TR.nLines;
/* Since the view rectange we are presented may not
* be aligned with our line grid, we compute our
* pen position so it's aligned with the top row.*/
vpixel = _Track_row_pixel(tr, first);
MoveHHi(TR.hText);
HLock(TR.hText);
while (first < last) {
if (first >= 0) {
MoveTo((INTEGER) _Track_h_origin(tr, first), vpixel);
DrawText(*TR.hText,TR.lineStarts[first],
TR.lineStarts[first + 1] - TR.lineStarts[first]);
}
vpixel += TR.lineHeight;
first++;
}
HUnlock(TR.hText);
}