|
Volume Number: | 6 | |||
Issue Number: | 3 | |||
Column Tag: | Intermediate Mac'ing |
Related Info: Script Manager TextEdit Scrap Manager
Control Manager
Mouse Track and Field, Part II
By Martin Minow, Arlington, VA
Introduction
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 <return>, 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); }
- SPREAD THE WORD:
- Slashdot
- Digg
- Del.icio.us
- Newsvine