radford@calgary.UUCP (Radford Neal) (07/29/87)
In article <8587@ut-sally.UUCP>, brian@ut-sally.UUCP (Brian H. Powell) writes: > > I've been working on adding tabs to my TextEdit-based editor... > First, I looked at all the scary code in the November 1986 MacTutor... > ...Fortunately, my editor was simpler than his general case... > > [ various stuff... ] > > [ As part of a procedure for measuring text length: ] > > Else you'll have to back up and find the most recent CR. (Don't > forget you may be on the first line, so you want to stop when you > get to the beginning of the text. I had the current TEHandle > as a global, so this wasn't hard.) I don't know what the ultimate purpose of all this is, but if it's part of a serious program, it seems rather chancy. Where does it say that TextEdit won't copy some text to a temporary area and call the text measurement routine for that? Searching backwards for the beginning of the line certainly won't work then. I don't know why Apple might make a change of this sort, but it's certainly not impossible. As I recall, the MacTutor stuff had some dicey stuff too, but did rely only on features that were at least mentioned in IM. Life would be much easier if Apple had put in tab support to start with. Radford Neal
brian@ut-sally.UUCP (Brian H. Powell) (07/30/87)
In article <1041@vaxb.calgary.UUCP>, radford@calgary.UUCP (Radford Neal) writes: > I don't know what the ultimate purpose of all this is, but if it's part > of a serious program, it seems rather chancy. Where does it say that > TextEdit won't copy some text to a temporary area and call the text > measurement routine for that? Searching backwards for the beginning of > the line certainly won't work then. I don't know why Apple might make > a change of this sort, but it's certainly not impossible. I thought of this, too, so I checked when I first started to make sure they didn't. I seriously doubt TE would ever not work on the text in-place. (after all, when they call StdTxMeas, it's almost always in relation to a lineStart. It doesn't make much sense, if you've got all those lineStarts pointing to the right place, to copy text out to measure it. Of course, there may be a good reason five years from now. Oh well, my program won't run on that machine.) To do anything this deep, you're going to run into cases where IM doesn't assure you of certain behavior. (Or in the case of StdTxMeas, at least, they don't tell you of certain behavior.) Sure we're living on the edge, but I think this is a safe solution to the tab problem. There are several other ways to do it, and I considered many of them. Some are safer, but at the expense of hundreds of lines of code at least, and many have a high performance overhead. (my opinion, of course.) In short, you've got to take chances when you program on the Mac. By following IM and the TechNotes to the letter, you will improve your odds, but if you need to go beyond what IM and the TN's talk about, you shouldn't just stop programming. You need to move with at least as much care as when you were following IM. > As I recall, the MacTutor stuff had some dicey stuff too, but did rely > only on features that were at least mentioned in IM. Actually, they used some undocumented low-memory globals to call undocumented TE support routines. My stuff is UL-approved compared to that. I relied on two instances of undocumented behavior: * that the text I'd measure and draw was not a copy, but that I was looking at part of the whole text. * that TE would already have the fontinfo before I'd have to measure a line that consisted only of tabs. I think this is safe because TE has to show the insertion point before I can enter the tabs. This implies that TE already knows the fontinfo. This is the reason why StdTxMeas gets called early with byteCount==0 and textAddr==0, by the way. Even if this changes, you can take out one line of code and my stuff will start working again. (It's a performance issue.) There may be others, but I can't think of any right off the top of my head like this. You are right to challenge my work. Tabs, like marriages, are not something to be entered into lightly. Let these comments serve as warning to those who intend to use my algorithm. Brian
jww@sdcsvax.UCSD.EDU (Joel West) (07/31/87)
Don't forget, if you're doing software for commercial release, you must be very careful when backing up. Seems some folks can't write their language using only 256 characters, so Apple has accomodated them (even gave 'em a manager, though I dunno where 'Script' came from) by allowing two-character characters. I don't have the stuff in front of me, but it seems like parsing backwards ain't such a hot idea for these circumstances. -- Joel West, (c/o UCSD) Palomar Software, Inc., P.O. Box 2635, Vista, CA 92083 {ucbvax,ihnp4}!sdcsvax!jww jww@sdcsvax.ucsd.edu
brian@ut-sally.UUCP (Brian H. Powell) (08/06/87)
Enclosed please find everything you need to implement tabs in a TextEdit window. The language is C, the dialect LightSpeed, but I don't think you'll have to change anything for the other compilers. This stuff is intended for TextEdit editors that don't wrap lines. (That way, I don't have to wrap tabs.) I set crOnly to -1 and just to be sure, I use a destRect.right == 32767. If you want to wrap tabs, I suggest you see the MacTutor article in Vol. 2, No.11, November, 1986 titled "Extending TextEdit for TABS" by Bradley Nedrud. Thanks to Bart Geraci for giving me this issue of MacTutor. Caveats: * I don't guarantee this code will do anything. * I don't guarantee this code will work with an SE or a II; I haven't tried it, yet. It should work, though. * I don't guarantee this code will work with all future ROMs. It probably will, though. * Don't call CharWidth to measure a tab; it won't work. In particular, my code depends on two things: * That when tabTxMeas and tabText are called, they get pointers into the actual TextEdit text. This is so they can poke around in neighboring text to find out where the beginning of the line is. If TextEdit ever decides to measure text by copying it into a buffer, then measuring it, these routines will return funny values. * That TextEdit will never use the values returned by numer, denom and info after tabTxMeas has been called to measure a line consisting only of tabs. Currently, TextEdit sets up its variables (such as te_handle^^.lineHeight) during TENew by calling GetFontInfo, which calls tabTxMeas with byteCount == textAddr == 0. If tabTxMeas is ever called to measure a line consisting only of tabs, it figures out the width of the text on its own and never calls StdTxMeas. Therefore, numer, denom and info aren't touched. This isn't hard to change if you feel uncomfortable with it. I'd suggest initially calling StdTxMeas with count == 0. Then if tabTxMeas' input parameter byteCount was also zero, tabTxMeas can return zero safely, else do all the work. (Maybe I'll change this in my program, too.) Brian H. Powell UUCP: {ihnp4,seismo,ctvax}!ut-sally!brian ARPA: brian@sally.UTEXAS.EDU _Work_ _Not Work_ Department of Computer Sciences P.O. Box 5899 Taylor Hall 2.124 Austin, TX 78763-5899 The University of Texas at Austin (512) 346-0835 Austin, TX 78712-1188 (512) 471-9536 /* by Brian H. Powell brian@sally.UTEXAS.EDU brian@ut-sally.UUCP Copyright (c) 1987 This code may be freely distributed and used, provided my name and the copyright notice appear (at least in source comments, as above.) (i.e., if you're going to sell your program, then write it yourself, don't get me to do it for you.) Thanks to MacTutor, November, 1986, for the ideas, but not for the code. tab.c -- My routines to replace the low-level quickdraw routines for measuring and drawing text. They are used to implement tabs. The routines tabTxMeas and tabText should be installed in a QDProcs record pointed to by the grafProcs field of the window's grafPort. These general-purpose routines are designed to work only if the TextEdit record for the associated grafPort has an infinite right margin. (i.e., lines wrap only at carriage returns.) Also, there must be an infinite number of tabs possible: These routines don't try to handle the case where tabs cause a wrap to the next line. */ #include <MacTypes.h> #include <Quickdraw.h> #include <TextEdit.h> #define NULL 0 pascal int tabTxMeas(byteCount, textAddr, numer, denom, info) int byteCount; Ptr textAddr; Point *numer, *denom; FontInfo *info; { extern TEHandle current_textedit_hdl; /* A global handle to the TextEdit record for this window. Normally, current_textedit_hdl is the handle for the front window, but for update events (before calls to TEUpdate), we change current_textedit_hdl to the TERecord for the window being updated. (And we change it back when we're through. See the code for update events.) */ register int total_result, /* the total length of the text. */ current_line_length, /* the length from the beginning of the line up to traverse_ptr. It's used to know how far to skip to the next tab stop. */ result; /* a variable to hold intermediate results. */ register char *start_ptr, /* pointers that work their way */ *traverse_ptr, /* through the text looking for tabs. Start_ptr and traverse_ptr bound the current range of text to be measured. */ *text_limit, /* a pointer to the end of the text to be measured. */ *start_limit, /* a pointer to the beginning of the TextEdit text. This is obtained from the TEHandle. */ *last_cr; /* a pointer to the last carriage return encountered. (or to the last character we measured, whichever is later.) */ if (byteCount == 0) /* even though we know the answer to this is zero, we still want StdTxMeas to compute numer, denom and info for us. */ return(StdTxMeas(byteCount, textAddr, numer, denom, info)); total_result = 0; last_cr = NULL; /* we haven't found a CR yet. */ start_ptr = traverse_ptr = textAddr; text_limit = traverse_ptr + byteCount; start_limit = *((*current_textedit_hdl)->hText); /* In case you're wondering, we can dereference this handle without locking it: it should already be locked by TE, since StdTxMeas gets a pointer to part of the text. */ while (traverse_ptr < text_limit) { /* search for a tab, keeping track of the CR's we see. */ while ((traverse_ptr < text_limit) && (*traverse_ptr != '\t')) if (*traverse_ptr++ == '\r') { last_cr = traverse_ptr; current_line_length = 0; } /* If we made it to the end of the text without finding a tab, just call the StdTxMeas to measure the text. */ if (traverse_ptr >= text_limit) total_result += StdTxMeas(traverse_ptr - start_ptr, start_ptr, numer, denom, info); else { /* we found a tab. */ if (last_cr == NULL) { /* If we haven't noticed a CR yet, back up and find it. In this case, current_line_length doesn't have a defined value, so we need to measure from the beginning of the current line up to the beginning of the text to be measured. */ while ((*(start_ptr - 1) != '\r') && (start_ptr > start_limit)) start_ptr--; if (start_ptr < textAddr) { /* If we had to back up at all, recurse and find the length from the beginning of the line to the start of the text to be measured. It will never recurse more than one level. */ current_line_length = tabTxMeas(textAddr - start_ptr, start_ptr, numer, denom, info); start_ptr = textAddr; /* reset start_ptr. */ } else /* If we didn't have to back up, that means we were called starting from the beginning of the line. So we can set the current line length to zero. */ current_line_length = 0; } else { /* last_cr had a meaningful value, so measure from the beginning of the text to the beginning of the current line and add to our running total. For speed, don't call StdTxMeas unless the range is non-empty. */ if (last_cr > start_ptr) total_result += StdTxMeas(last_cr - start_ptr, start_ptr, numer, denom, info); start_ptr = last_cr; } /* Now measure from the beginning of the line to the tab. For speed, don't call StdTxMeas unless the range is non-empty. Add the result to both the total length and the current line length. */ if (traverse_ptr > start_ptr) { total_result += result = StdTxMeas(traverse_ptr - start_ptr, start_ptr, numer, denom, info); current_line_length += result; } /* To avoid measuring parts of the text twice, set last_cr to the current location (just past the tab). */ last_cr = start_ptr = ++traverse_ptr; /* skip the tab */ /* Now that we know the current line length, we can figure out where the next tabstop is. compute_tab_length returns the length of the tab in pixels. */ total_result += result = compute_tab_length(current_line_length); current_line_length += result; } /* else */ } /* while */ return(total_result); } pascal void tabText(byteCount, textBuf, numer, denom) int byteCount; Ptr textBuf; Point numer, denom; { FontInfo info; /* This is ignored, but since we call tabTxMeas, we need to pass a pointer to one of these records. */ register char *traverse_ptr, /* Pointers that move through the text */ *start_ptr, /* looking for tabs. */ *text_limit, /* A Pointer to the end of the text to draw. */ *line_start, /* A Pointer to the beginning of the line. */ *start_limit; /* A Pointer to the beginning of the TextEdit text. */ extern TEHandle current_textedit_hdl; /* A global handle to the TextEdit record for this window. */ /* This routine relies on the fact that it never has to draw a carriage return. TextEdit calls this procedure for each line. (or sometimes only part of a line.) */ line_start = NULL; /* search for first tab. */ traverse_ptr = start_ptr = textBuf; text_limit = textBuf + byteCount; while (traverse_ptr < text_limit) { while ((traverse_ptr < text_limit) && (*traverse_ptr != '\t')) traverse_ptr++; /* Call StdText for what we've found so far. */ StdText(traverse_ptr - start_ptr, start_ptr, numer, denom); /* If there's still some text left, we must have found a tab. */ if (traverse_ptr < text_limit) { /* we found a tab */ /* If this is the first tab we've found, we need to go back and find the beginning of the line so we can figure out how big the tab is supposed to be. */ if (line_start == NULL) { line_start = textBuf; start_limit = *((*current_textedit_hdl)->hText); while ((*(line_start - 1) != '\r') && (line_start > start_limit)) line_start--; } /* Now measure the text from the beginning of the line to the tab and call compute_tab_length. Feed this to Move(dh,dv) to move the pen that many pixels to the right. */ Move(compute_tab_length(tabTxMeas(traverse_ptr - line_start, line_start, &numer, &denom, &info)), 0); } start_ptr = ++traverse_ptr; /* skip the tab and continue drawing. */ } } int compute_tab_length(current_line_length) int current_line_length; /* This routine could be a whole lot more complicated. I only support one tab_size. It would not be too difficult to have an array of tabstops associated with each window. This routine could search for the next larger tabstop and return the difference between it and the current_line_length. */ { extern int tab_size; /* A global that contains the size in pixels of the tab stops. */ return(tab_size - current_line_length % tab_size); } ------- end of tab.c ------- In case you're wondering, here's how to set up the QDProcs: Do this once at initialization time. Since it allocates a non-relocatable block, you probably want this to be in the main segment. (Or else, make it a handle, MoveHHi, lock it and dereference it.) void setup_globals() { extern QDProcsPtr myQDProcs; /* It's a global variable. */ extern pascal void tabText(); extern pascal int tabTxMeas(); myQDProcs = (QDProcsPtr)NewPtr(sizeof(QDProcs)); SetStdProcs(myQDProcs); myQDProcs->textProc = tabText; /* You may have to cast these, */ myQDProcs->txMeasProc = tabTxMeas; /* depending on your development environment. */ } If you just have one size tab like I did, somewhere you need to set up tab_size. I felt more confident doing this before setting up myQDProcs in the current grafPort. I made it a global that never changes. (I only use one font and size.) It may be better to make it a global that changes depending on the font of the window being brought up. I'll let you decide. If you have an array of tabs, such as was described in MacTutor, you should rewrite compute_tab_length and you'll never need tab_size. extern int tab_size; tab_size = CHARS_PER_TAB * CharWidth('0'); /* For me, CHARS_PER_TAB is constant 8, but it's not hard to bring up a dialog to let the user specify. */ Then, for each window: void setup_te_port(new_window) WindowPtr new_window; { extern QDProcsPtr myQDProcs; (*new_window).grafProcs = myQDProcs; } ----- ----- It's been real. Brian "You know, I used to think people rewrote TextEdit to get tabs, multiple fonts and styles and the like. Now I realize they do it to get error checking." -- Brian H. Powell, August, 1987.
brian@ut-sally.UUCP (Brian H. Powell) (08/13/87)
I noticed that my code was pretty slow drawing a line with several tabs
in it, so I made a simple optimization that makes it quite a bit faster.
tabTxMeas still isn't as fast as I want it. If I optimize that much, I'll
post the changes to it, also.
This change keeps tabText from calling tabTxMeas more than once. It used
to call it to measure from the beginning of the line up to every tab. I don't
know why I didn't do this in the first place.
Insert this fragment into the proper place in tabText. You should be
able to figure it out. I put plus signs in front of the new code. (I hope I
got all the plus signs right.)
I pasted this in here, and our ethernet was running slow, so some char-
acters were lost. I think I put them all back right, but no guarantees.
pascal void tabText(byteCount, textBuf, numer, denom)
...
+ register int line_length, line_dh;
...
/* If this is the first tab we've found, we need to go back and find
the beginning of the line so we can figure out how big the tab is
supposed to be. */
if (line_start == NULL) {
line_start = textBuf;
start_limit = *((*current_textedit_hdl)->hText);
while ((*(line_start - 1) != '\r') &&
(line_start > start_limit))
line_start--;
+ line_length = tabTxMeas(traverse_ptr - line_start, line_start,
+ &numer, &denom, &info);
+ } else
+ /* just add in the length of the text we just drew. */
+ line_length += StdTxMeas(traverse_ptr - start_ptr, start_ptr,
+ &numer, &denom, &info);
+
+ /* find the length of the tab. */
+ line_length += line_dh = compute_tab_length(line_length);
+
+ /* Call Move(dh,dv) to move the pen line_dh pixels to the right. */
+ Move(line_dh, 0);
}
...
Brian H. Powell
UUCP: {ihnp4,seismo,ctvax}!ut-sally!brian
ARPA: brian@sally.UTEXAS.EDU
_Work_ _Not Work_
Department of Computer Sciences P.O. Box 5899
Taylor Hall 2.124 Austin, TX 78763-5899
The University of Texas at Austin (512) 346-0835
Austin, TX 78712-1188
(512) 471-9536
mjbo@mist.cs.orst.edu (Mark Borgerson) (01/12/88)
I have modified a textedit-based programming editor to support tabs using the Quickdraw StdText and StdTxtMeas routines. My patches to these routines treat tabs as variable width characters using a simplified version of the routines given in a MacTutor article from about a year ago. My routines work well on the Mac Plus and SE with system 3.2, 4.1 and 4.2. They also work on the Mac II ---but only if the Mac II is in 2-color mode. IF I switch to other modes, text is not properly displayed as input is entered. However, if the screen is invalidated and redrawn all is ok. Anyone have any ideas? What is different about the quickdraw routines when in other than 2-color mode? IM Vol five does not mention anything about stdText or stdTxtMeas acting differently in multi-bit modes? I also saw something on the net to the effect that modifying textedit to support tabs was verboten. There is nothing to this effect in the latest compatibility guidelines. Am I doomed to failure or what? Mark Borgerson Dept. Computer Science Oregon State University Disclaimers are for opinions. The material above is factual.
brian@ut-sally.UUCP (Brian H. Powell) (01/13/88)
In article <1860@orstcs.CS.ORST.EDU>, mjbo@mist.cs.orst.edu (Mark Borgerson) writes: > I also saw something on the net to the effect that > modifying textedit to support tabs was verboten. > There is nothing to this effect in the latest > compatibility guidelines. Am I doomed to failure > or what? I was the person who posted some information to the net about adding tabs to TextEdit a few months ago. At the time I was working on it, I sent mail to Apple asking them to clarify some of the issues involved with TextEdit. I got a letter from Cameron Birse, which said, in part, It wasn't entirely clear to me what exactly you meant with your StdText and StdTxMeas calls, but I can say that things not only can change in future ROMs, but probably will. For example, the new TextEdit routines allow for styled text (i.e., bold, italic, etc.), which obviously complicates measuring text. The point is, the only things you can depend on being supported in future revisions of ROMs are things that are clearly documented in Inside Macintosh Volumes 1-5. Let me say that first of all, the MacTutor article included some very questionable code which I'm sure has failed on SEs and IIs. When I wrote code to support tabs, I made some restrictions (in particular, I don't allow word-wrapping) that let me throw out much of the "dangerous" code from the MacTutor article. Also, I didn't use the "new" TextEdit; I'm using the "old" TextEdit that only supports one typeface. I also had to make some assumptions. Here is what I said in the original article I posted: ******************** In particular, my code depends on two things: * That when tabTxMeas and tabText are called, they get pointers into the actual TextEdit text. This is so they can poke around in neighboring text to find out where the beginning of the line is. If TextEdit ever decides to measure text by copying it into a buffer, then measuring it, these routines will return funny values. * That TextEdit will never use the values returned by numer, denom and info after tabTxMeas has been called to measure a line consisting only of tabs. Currently, TextEdit sets up its variables (such as te_handle^^.lineHeight) during TENew by calling GetFontInfo, which calls tabTxMeas with byteCount == textAddr == 0. If tabTxMeas is ever called to measure a line consisting only of tabs, it figures out the width of the text on its own and never calls StdTxMeas. Therefore, numer, denom and info aren't touched. This isn't hard to change if you feel uncomfortable with it. I'd suggest initially calling StdTxMeas with count == 0. Then if tabTxMeas' input parameter byteCount was also zero, tabTxMeas can return zero safely, else do all the work. (Maybe I'll change this in my program, too.) ******************** As I said at the time, it would be pretty stupid if Apple were to change TextEdit to invalidate my above assumptions. It would imply that they were throwing away a lot of useful information in their TERecord and recomputing it each time. If you did anything similar to what I did, then I suggest two things: Don't call CharWidth. (It's obvious why not if you think about it.) Check to make sure that my first assumption holds on calls to the bottlenecks. That's the most likely place for something to mess up. I'll try my code on a Mac II tomorrow or Thursday and let you know if it works. If you'd like, I could send you my original posting when I worked on it. Brian H. Powell UUCP: ...!uunet!ut-sally!brian ARPA: brian@sally.UTEXAS.EDU P.O. Box 5899 Austin, TX 78763-5899 (512) 346-0835
brian@im4u.UUCP (Brian H. Powell) (01/15/88)
I tried using my tabs code on a Mac II. (By "tabs code", I mean the code that I wrote which adds tabs to TextEdit.) I tried running the program in 2-bit, 4-bit and 16-bit mode (gray; I didn't try it on a color Mac II), and in all cases, it worked fine. It worked for both the initial typing, and for window updates. If anybody's interested, I can send them my original posting on the matter. (It included some code.) Brian H. Powell UUCP: ...!uunet!ut-sally!brian ARPA: brian@sally.UTEXAS.EDU P.O. Box 5899 Austin, TX 78763-5899 (512) 346-0835
tomc@mntgfx.mentor.com (Tom Carstensen) (01/15/88)
As far as I'm concerned, TextEdit is worthless because of it's 32K limit, non-handling of tabs, and the implementation is so slow. I originally added by on StdMeas and StdTxt routines to support tabs, but by doing this, selecting text was EXTREMELY sluggish and annoying. (Try typing in a line of 200 characters or so WITH tabs, and then selecting that line - moving the mouse back and forth - UGH!). I bagged TextEdit, and wrote my own which is overall, much faster. It's being incorperated into an Editor I'm developing, which will be finished soon. Since I rewrote TextEdit, I was able to support such things as rectangular selection (which I find most useful) and Hiding (folding in ) of text. :------------------------------------------------------------: : Tom Carstensen Usenet: tomc@mntgfx.MENTOR.COM : : Mentor Graphics GEnie: XPC23637 : : : : . . . and this shall be Max Headroom's finest hour. : : - Max Headroom : :------------------------------------------------------------:
brian@ut-sally.UUCP (Brian H. Powell) (01/16/88)
In a recent article, tomc@mntgfx.mentor.com (Tom Carstensen) writes: > As far as I'm concerned, TextEdit is worthless > because of it's 32K limit, non-handling of tabs, > and the implementation is so slow. But you left out the worst part: it doesn't do _any_ error checking. In particular, it loves to run out of memory and not tell you about it. If I were going to write a new TextEdit, I would do so to get error checking, not tabs, the 32K limit or anything else. (Of course, if you rewrite TextEdit, it's also easier to implement the arrow keys according to the user interface guidelines. (In particular, modifier keys with arrow keys.) It's not that hard to implement the guidelines with TE, but the solution is not at all intuitive. See a posting I made a few months ago about arrow keys.) (Of course, it may not be a good idea to implement the user interface guidelines; Apple's response to my work was along the lines of, "How dare you confuse the user by implementing the User Interface Guidelines and doing something different than what TextEdit does?" I guess Apple was feeling especially schizophrenic that day.) > I originally > added by on StdMeas and StdTxt routines to support > tabs, but by doing this, selecting text was > EXTREMELY sluggish and annoying. (Try typing > in a line of 200 characters or so WITH tabs, and then > selecting that line - moving the mouse back and forth - UGH!). Mine isn't terribly slow. It's certainly slower than "normal", but it's not intolerable. I even wrote mine in C. It's great that you took the effort to rewrite TextEdit. When I was working on this stuff, I certainly considered it, but I didn't need a powerful editor, so I just put up with TE. Brian