Relay-Version: version B 2.10 5/3/83; site utzoo.UUCP Path: utzoo!mnetor!uunet!seismo!ut-sally!brian From: brian@ut-sally.UUCP (Brian H. Powell) Newsgroups: comp.sys.mac Subject: Tabs in TextEdit Message-ID: <8681@ut-sally.UUCP> Date: Wed, 5-Aug-87 19:03:14 EDT Article-I.D.: ut-sally.8681 Posted: Wed Aug 5 19:03:14 1987 Date-Received: Sat, 8-Aug-87 05:42:24 EDT Organization: U. Texas CS Dept., Austin, Texas Lines: 341 Keywords: The code you've all been waiting for... 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 #include #include #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.