🚀 go-pugleaf

RetroBBS NetNews Server

Inspired by RockSolid Light RIP Retro Guy

Article View: comp.sys.mac
Article #17882

Tabs in TextEdit

#17882
From: Brian H. Powell
Date: Wed, 05 Aug 1987 23:03
341 lines
14264 bytes
     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.
Technical Details
Message-ID: <8681@ut-sally.UUCP>
Path: arc1-ca.pugleaf.net!utzoo!mnetor!uunet!seismo!ut-sally!brian

Show Full Headers
Complete Article Headers
Relay-Version: version B 2.10 5/3/83; site utzoo.UUCP
Path: arc1-ca.pugleaf.net!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...