/[dtapublic]/projs/emts/trunk/src/c_tk_base_7_5_w_mods/tktextdisp.c
ViewVC logotype

Diff of /projs/emts/trunk/src/c_tk_base_7_5_w_mods/tktextdisp.c

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

to_be_filed/sf_code/esrgpcpj/shared/tk_base/tktextdisp.c revision 29 by dashley, Sat Oct 8 07:08:47 2016 UTC projs/emts/trunk/src/c_tk_base_7_5_w_mods/tktextdisp.c revision 269 by dashley, Sat Jun 1 21:29:58 2019 UTC
# Line 1  Line 1 
 /* $Header: /cvsroot/esrg/sfesrg/esrgpcpj/shared/tk_base/tktextdisp.c,v 1.1.1.1 2001/06/13 05:10:16 dtashley Exp $ */  
   
 /*  
  * tkTextDisp.c --  
  *  
  *      This module provides facilities to display text widgets.  It is  
  *      the only place where information is kept about the screen layout  
  *      of text widgets.  
  *  
  * Copyright (c) 1992-1994 The Regents of the University of California.  
  * Copyright (c) 1994-1997 Sun Microsystems, Inc.  
  *  
  * See the file "license.terms" for information on usage and redistribution  
  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.  
  *  
  * RCS: @(#) $Id: tktextdisp.c,v 1.1.1.1 2001/06/13 05:10:16 dtashley Exp $  
  */  
   
 #include "tkPort.h"  
 #include "tkInt.h"  
 #include "tkText.h"  
   
 #ifdef __WIN32__  
 #include "tkWinInt.h"  
 #endif  
   
 /*  
  * The following structure describes how to display a range of characters.  
  * The information is generated by scanning all of the tags associated  
  * with the characters and combining that with default information for  
  * the overall widget.  These structures form the hash keys for  
  * dInfoPtr->styleTable.  
  */  
   
 typedef struct StyleValues {  
     Tk_3DBorder border;         /* Used for drawing background under text.  
                                  * NULL means use widget background. */  
     int borderWidth;            /* Width of 3-D border for background. */  
     int relief;                 /* 3-D relief for background. */  
     Pixmap bgStipple;           /* Stipple bitmap for background.  None  
                                  * means draw solid. */  
     XColor *fgColor;            /* Foreground color for text. */  
     Tk_Font tkfont;             /* Font for displaying text. */  
     Pixmap fgStipple;           /* Stipple bitmap for text and other  
                                  * foreground stuff.   None means draw  
                                  * solid.*/  
     int justify;                /* Justification style for text. */  
     int lMargin1;               /* Left margin, in pixels, for first display  
                                  * line of each text line. */  
     int lMargin2;               /* Left margin, in pixels, for second and  
                                  * later display lines of each text line. */  
     int offset;                 /* Offset in pixels of baseline, relative to  
                                  * baseline of line. */  
     int overstrike;             /* Non-zero means draw overstrike through  
                                  * text. */  
     int rMargin;                /* Right margin, in pixels. */  
     int spacing1;               /* Spacing above first dline in text line. */  
     int spacing2;               /* Spacing between lines of dline. */  
     int spacing3;               /* Spacing below last dline in text line. */  
     TkTextTabArray *tabArrayPtr;/* Locations and types of tab stops (may  
                                  * be NULL). */  
     int underline;              /* Non-zero means draw underline underneath  
                                  * text. */  
     int elide;                  /* Non-zero means draw text */  
     TkWrapMode wrapMode;        /* How to handle wrap-around for this tag.  
                                  * One of TEXT_WRAPMODE_CHAR,  
                                  * TEXT_WRAPMODE_NONE or TEXT_WRAPMODE_WORD.*/  
 } StyleValues;  
   
 /*  
  * The following structure extends the StyleValues structure above with  
  * graphics contexts used to actually draw the characters.  The entries  
  * in dInfoPtr->styleTable point to structures of this type.  
  */  
   
 typedef struct TextStyle {  
     int refCount;               /* Number of times this structure is  
                                  * referenced in Chunks. */  
     GC bgGC;                    /* Graphics context for background.  None  
                                  * means use widget background. */  
     GC fgGC;                    /* Graphics context for foreground. */  
     StyleValues *sValuePtr;     /* Raw information from which GCs were  
                                  * derived. */  
     Tcl_HashEntry *hPtr;        /* Pointer to entry in styleTable.  Used  
                                  * to delete entry. */  
 } TextStyle;  
   
 /*  
  * The following macro determines whether two styles have the same  
  * background so that, for example, no beveled border should be drawn  
  * between them.  
  */  
   
 #define SAME_BACKGROUND(s1, s2) \  
     (((s1)->sValuePtr->border == (s2)->sValuePtr->border) \  
         && ((s1)->sValuePtr->borderWidth == (s2)->sValuePtr->borderWidth) \  
         && ((s1)->sValuePtr->relief == (s2)->sValuePtr->relief) \  
         && ((s1)->sValuePtr->bgStipple == (s2)->sValuePtr->bgStipple))  
   
 /*  
  * The following structure describes one line of the display, which may  
  * be either part or all of one line of the text.  
  */  
   
 typedef struct DLine {  
     TkTextIndex index;          /* Identifies first character in text  
                                  * that is displayed on this line. */  
     int byteCount;              /* Number of bytes accounted for by this  
                                  * display line, including a trailing space  
                                  * or newline that isn't actually displayed. */  
     int y;                      /* Y-position at which line is supposed to  
                                  * be drawn (topmost pixel of rectangular  
                                  * area occupied by line). */  
     int oldY;                   /* Y-position at which line currently  
                                  * appears on display.  -1 means line isn't  
                                  * currently visible on display and must be  
                                  * redrawn.  This is used to move lines by  
                                  * scrolling rather than re-drawing. */  
     int height;                 /* Height of line, in pixels. */  
     int baseline;               /* Offset of text baseline from y, in  
                                  * pixels. */  
     int spaceAbove;             /* How much extra space was added to the  
                                  * top of the line because of spacing  
                                  * options.  This is included in height  
                                  * and baseline. */  
     int spaceBelow;             /* How much extra space was added to the  
                                  * bottom of the line because of spacing  
                                  * options.  This is included in height. */  
     int length;                 /* Total length of line, in pixels. */  
     TkTextDispChunk *chunkPtr;  /* Pointer to first chunk in list of all  
                                  * of those that are displayed on this  
                                  * line of the screen. */  
     struct DLine *nextPtr;      /* Next in list of all display lines for  
                                  * this window.   The list is sorted in  
                                  * order from top to bottom.  Note:  the  
                                  * next DLine doesn't always correspond  
                                  * to the next line of text:  (a) can have  
                                  * multiple DLines for one text line, and  
                                  * (b) can have gaps where DLine's have been  
                                  * deleted because they're out of date. */  
     int flags;                  /* Various flag bits:  see below for values. */  
 } DLine;  
   
 /*  
  * Flag bits for DLine structures:  
  *  
  * HAS_3D_BORDER -              Non-zero means that at least one of the  
  *                              chunks in this line has a 3D border, so  
  *                              it potentially interacts with 3D borders  
  *                              in neighboring lines (see  
  *                              DisplayLineBackground).  
  * NEW_LAYOUT -                 Non-zero means that the line has been  
  *                              re-layed out since the last time the  
  *                              display was updated.  
  * TOP_LINE -                   Non-zero means that this was the top line  
  *                              in the window the last time that the window  
  *                              was laid out.  This is important because  
  *                              a line may be displayed differently if its  
  *                              at the top or bottom than if it's in the  
  *                              middle (e.g. beveled edges aren't displayed  
  *                              for middle lines if the adjacent line has  
  *                              a similar background).  
  * BOTTOM_LINE -                Non-zero means that this was the bottom line  
  *                              in the window the last time that the window  
  *                              was laid out.  
  * IS_DISABLED -                This Dline cannot be edited.  
  */  
   
 #define HAS_3D_BORDER   1  
 #define NEW_LAYOUT      2  
 #define TOP_LINE        4  
 #define BOTTOM_LINE     8  
 #define IS_DISABLED    16  
   
 /*  
  * Overall display information for a text widget:  
  */  
   
 typedef struct TextDInfo {  
     Tcl_HashTable styleTable;   /* Hash table that maps from StyleValues  
                                  * to TextStyles for this widget. */  
     DLine *dLinePtr;            /* First in list of all display lines for  
                                  * this widget, in order from top to bottom. */  
     GC copyGC;                  /* Graphics context for copying from off-  
                                  * screen pixmaps onto screen. */  
     GC scrollGC;                /* Graphics context for copying from one place  
                                  * in the window to another (scrolling):  
                                  * differs from copyGC in that we need to get  
                                  * GraphicsExpose events. */  
     int x;                      /* First x-coordinate that may be used for  
                                  * actually displaying line information.  
                                  * Leaves space for border, etc. */  
     int y;                      /* First y-coordinate that may be used for  
                                  * actually displaying line information.  
                                  * Leaves space for border, etc. */  
     int maxX;                   /* First x-coordinate to right of available  
                                  * space for displaying lines. */  
     int maxY;                   /* First y-coordinate below available  
                                  * space for displaying lines. */  
     int topOfEof;               /* Top-most pixel (lowest y-value) that has  
                                  * been drawn in the appropriate fashion for  
                                  * the portion of the window after the last  
                                  * line of the text.  This field is used to  
                                  * figure out when to redraw part or all of  
                                  * the eof field. */  
   
     /*  
      * Information used for scrolling:  
      */  
   
     int newByteOffset;          /* Desired x scroll position, measured as the  
                                  * number of average-size characters off-screen  
                                  * to the left for a line with no left  
                                  * margin. */  
     int curPixelOffset;         /* Actual x scroll position, measured as the  
                                  * number of pixels off-screen to the left. */  
     int maxLength;              /* Length in pixels of longest line that's  
                                  * visible in window (length may exceed window  
                                  * size).  If there's no wrapping, this will  
                                  * be zero. */  
     double xScrollFirst, xScrollLast;  
                                 /* Most recent values reported to horizontal  
                                  * scrollbar;  used to eliminate unnecessary  
                                  * reports. */  
     double yScrollFirst, yScrollLast;  
                                 /* Most recent values reported to vertical  
                                  * scrollbar;  used to eliminate unnecessary  
                                  * reports. */  
   
     /*  
      * The following information is used to implement scanning:  
      */  
   
     int scanMarkIndex;          /* Byte index of character that was at the  
                                  * left edge of the window when the scan  
                                  * started. */  
     int scanMarkX;              /* X-position of mouse at time scan started. */  
     int scanTotalScroll;        /* Total scrolling (in screen lines) that has  
                                  * occurred since scanMarkY was set. */  
     int scanMarkY;              /* Y-position of mouse at time scan started. */  
   
     /*  
      * Miscellaneous information:  
      */  
   
     int dLinesInvalidated;      /* This value is set to 1 whenever something  
                                  * happens that invalidates information in  
                                  * DLine structures;  if a redisplay  
                                  * is in progress, it will see this and  
                                  * abort the redisplay.  This is needed  
                                  * because, for example, an embedded window  
                                  * could change its size when it is first  
                                  * displayed, invalidating the DLine that  
                                  * is currently being displayed.  If redisplay  
                                  * continues, it will use freed memory and  
                                  * could dump core. */  
     int flags;                  /* Various flag values:  see below for  
                                  * definitions. */  
 } TextDInfo;  
   
 /*  
  * In TkTextDispChunk structures for character segments, the clientData  
  * field points to one of the following structures:  
  */  
   
 typedef struct CharInfo {  
     int numBytes;               /* Number of bytes to display. */  
     char chars[4];              /* UTF characters to display.  Actual size  
                                  * will be numBytes, not 4.  THIS MUST BE  
                                  * THE LAST FIELD IN THE STRUCTURE. */  
 } CharInfo;  
   
 /*  
  * Flag values for TextDInfo structures:  
  *  
  * DINFO_OUT_OF_DATE:           Non-zero means that the DLine structures  
  *                              for this window are partially or completely  
  *                              out of date and need to be recomputed.  
  * REDRAW_PENDING:              Means that a when-idle handler has been  
  *                              scheduled to update the display.  
  * REDRAW_BORDERS:              Means window border or pad area has  
  *                              potentially been damaged and must be redrawn.  
  * REPICK_NEEDED:               1 means that the widget has been modified  
  *                              in a way that could change the current  
  *                              character (a different character might be  
  *                              under the mouse cursor now).  Need to  
  *                              recompute the current character before  
  *                              the next redisplay.  
  */  
   
 #define DINFO_OUT_OF_DATE       1  
 #define REDRAW_PENDING          2  
 #define REDRAW_BORDERS          4  
 #define REPICK_NEEDED           8  
   
 /*  
  * The following counters keep statistics about redisplay that can be  
  * checked to see how clever this code is at reducing redisplays.  
  */  
   
 static int numRedisplays;       /* Number of calls to DisplayText. */  
 static int linesRedrawn;        /* Number of calls to DisplayDLine. */  
 static int numCopies;           /* Number of calls to XCopyArea to copy part  
                                  * of the screen. */  
   
 /*  
  * Forward declarations for procedures defined later in this file:  
  */  
   
 static void             AdjustForTab _ANSI_ARGS_((TkText *textPtr,  
                             TkTextTabArray *tabArrayPtr, int index,  
                             TkTextDispChunk *chunkPtr));  
 static void             CharBboxProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr,  
                             int index, int y, int lineHeight, int baseline,  
                             int *xPtr, int *yPtr, int *widthPtr,  
                             int *heightPtr));  
 static void             CharDisplayProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr,  
                             int x, int y, int height, int baseline,  
                             Display *display, Drawable dst, int screenY));  
 static int              CharMeasureProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr,  
                             int x));  
 static void             CharUndisplayProc _ANSI_ARGS_((TkText *textPtr,  
                             TkTextDispChunk *chunkPtr));  
   
 /*  
    Definitions of elided procs.  
    Compiler can't inline these since we use pointers to these functions.  
    ElideDisplayProc, ElideUndisplayProc special-cased for speed,  
    as potentially many elided DLine chunks if large, tag toggle-filled  
    elided region.  
 */  
 static void             ElideBboxProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr,  
                             int index, int y, int lineHeight, int baseline,  
                             int *xPtr, int *yPtr, int *widthPtr,  
                             int *heightPtr));  
 static int              ElideMeasureProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr,  
                             int x));  
   
 static void             DisplayDLine _ANSI_ARGS_((TkText *textPtr,  
                             DLine *dlPtr, DLine *prevPtr, Pixmap pixmap));  
 static void             DisplayLineBackground _ANSI_ARGS_((TkText *textPtr,  
                             DLine *dlPtr, DLine *prevPtr, Pixmap pixmap));  
 static void             DisplayText _ANSI_ARGS_((ClientData clientData));  
 static DLine *          FindDLine _ANSI_ARGS_((DLine *dlPtr,  
                             TkTextIndex *indexPtr));  
 static void             FreeDLines _ANSI_ARGS_((TkText *textPtr,  
                             DLine *firstPtr, DLine *lastPtr, int unlink));  
 static void             FreeStyle _ANSI_ARGS_((TkText *textPtr,  
                             TextStyle *stylePtr));  
 static TextStyle *      GetStyle _ANSI_ARGS_((TkText *textPtr,  
                             TkTextIndex *indexPtr));  
 static void             GetXView _ANSI_ARGS_((Tcl_Interp *interp,  
                             TkText *textPtr, int report));  
 static void             GetYView _ANSI_ARGS_((Tcl_Interp *interp,  
                             TkText *textPtr, int report));  
 static DLine *          LayoutDLine _ANSI_ARGS_((TkText *textPtr,  
                             TkTextIndex *indexPtr));  
 static int              MeasureChars _ANSI_ARGS_((Tk_Font tkfont,  
                             CONST char *source, int maxBytes, int startX,  
                             int maxX, int tabOrigin, int *nextXPtr));  
 static void             MeasureUp _ANSI_ARGS_((TkText *textPtr,  
                             TkTextIndex *srcPtr, int distance,  
                             TkTextIndex *dstPtr));  
 static int              NextTabStop _ANSI_ARGS_((Tk_Font tkfont, int x,  
                             int tabOrigin));  
 static void             UpdateDisplayInfo _ANSI_ARGS_((TkText *textPtr));  
 static void             ScrollByLines _ANSI_ARGS_((TkText *textPtr,  
                             int offset));  
 static int              SizeOfTab _ANSI_ARGS_((TkText *textPtr,  
                             TkTextTabArray *tabArrayPtr, int index, int x,  
                             int maxX));  
 static void             TextInvalidateRegion _ANSI_ARGS_((TkText *textPtr,  
                             TkRegion region));  
   
   
 /*  
  *----------------------------------------------------------------------  
  *  
  * TkTextCreateDInfo --  
  *  
  *      This procedure is called when a new text widget is created.  
  *      Its job is to set up display-related information for the widget.  
  *  
  * Results:  
  *      None.  
  *  
  * Side effects:  
  *      A TextDInfo data structure is allocated and initialized and attached  
  *      to textPtr.  
  *  
  *----------------------------------------------------------------------  
  */  
   
 void  
 TkTextCreateDInfo(textPtr)  
     TkText *textPtr;            /* Overall information for text widget. */  
 {  
     register TextDInfo *dInfoPtr;  
     XGCValues gcValues;  
   
     dInfoPtr = (TextDInfo *) ckalloc(sizeof(TextDInfo));  
     Tcl_InitHashTable(&dInfoPtr->styleTable, sizeof(StyleValues)/sizeof(int));  
     dInfoPtr->dLinePtr = NULL;  
     dInfoPtr->copyGC = None;  
     gcValues.graphics_exposures = True;  
     dInfoPtr->scrollGC = Tk_GetGC(textPtr->tkwin, GCGraphicsExposures,  
             &gcValues);  
     dInfoPtr->topOfEof = 0;  
     dInfoPtr->newByteOffset = 0;  
     dInfoPtr->curPixelOffset = 0;  
     dInfoPtr->maxLength = 0;  
     dInfoPtr->xScrollFirst = -1;  
     dInfoPtr->xScrollLast = -1;  
     dInfoPtr->yScrollFirst = -1;  
     dInfoPtr->yScrollLast = -1;  
     dInfoPtr->scanMarkIndex = 0;  
     dInfoPtr->scanMarkX = 0;  
     dInfoPtr->scanTotalScroll = 0;  
     dInfoPtr->scanMarkY = 0;  
     dInfoPtr->dLinesInvalidated = 0;  
     dInfoPtr->flags = DINFO_OUT_OF_DATE;  
     textPtr->dInfoPtr = dInfoPtr;  
 }  
   
 /*  
  *----------------------------------------------------------------------  
  *  
  * TkTextFreeDInfo --  
  *  
  *      This procedure is called to free up all of the private display  
  *      information kept by this file for a text widget.  
  *  
  * Results:  
  *      None.  
  *  
  * Side effects:  
  *      Lots of resources get freed.  
  *  
  *----------------------------------------------------------------------  
  */  
   
 void  
 TkTextFreeDInfo(textPtr)  
     TkText *textPtr;            /* Overall information for text widget. */  
 {  
     register TextDInfo *dInfoPtr = textPtr->dInfoPtr;  
   
     /*  
      * Be careful to free up styleTable *after* freeing up all the  
      * DLines, so that the hash table is still intact to free up the  
      * style-related information from the lines.  Once the lines are  
      * all free then styleTable will be empty.  
      */  
   
     FreeDLines(textPtr, dInfoPtr->dLinePtr, (DLine *) NULL, 1);  
     Tcl_DeleteHashTable(&dInfoPtr->styleTable);  
     if (dInfoPtr->copyGC != None) {  
         Tk_FreeGC(textPtr->display, dInfoPtr->copyGC);  
     }  
     Tk_FreeGC(textPtr->display, dInfoPtr->scrollGC);  
     if (dInfoPtr->flags & REDRAW_PENDING) {  
         Tcl_CancelIdleCall(DisplayText, (ClientData) textPtr);  
     }  
     ckfree((char *) dInfoPtr);  
 }  
   
 /*  
  *----------------------------------------------------------------------  
  *  
  * GetStyle --  
  *  
  *      This procedure creates all the information needed to display  
  *      text at a particular location.  
  *  
  * Results:  
  *      The return value is a pointer to a TextStyle structure that  
  *      corresponds to *sValuePtr.  
  *  
  * Side effects:  
  *      A new entry may be created in the style table for the widget.  
  *  
  *----------------------------------------------------------------------  
  */  
   
 static TextStyle *  
 GetStyle(textPtr, indexPtr)  
     TkText *textPtr;            /* Overall information about text widget. */  
     TkTextIndex *indexPtr;      /* The character in the text for which  
                                  * display information is wanted. */  
 {  
     TkTextTag **tagPtrs;  
     register TkTextTag *tagPtr;  
     StyleValues styleValues;  
     TextStyle *stylePtr;  
     Tcl_HashEntry *hPtr;  
     int numTags, new, i;  
     XGCValues gcValues;  
     unsigned long mask;  
   
     /*  
      * The variables below keep track of the highest-priority specification  
      * that has occurred for each of the various fields of the StyleValues.  
      */  
   
     int borderPrio, borderWidthPrio, reliefPrio, bgStipplePrio;  
     int fgPrio, fontPrio, fgStipplePrio;  
     int underlinePrio, elidePrio, justifyPrio, offsetPrio;  
     int lMargin1Prio, lMargin2Prio, rMarginPrio;  
     int spacing1Prio, spacing2Prio, spacing3Prio;  
     int overstrikePrio, tabPrio, wrapPrio;  
   
     /*  
      * Find out what tags are present for the character, then compute  
      * a StyleValues structure corresponding to those tags (scan  
      * through all of the tags, saving information for the highest-  
      * priority tag).  
      */  
   
     tagPtrs = TkBTreeGetTags(indexPtr, &numTags);  
     borderPrio = borderWidthPrio = reliefPrio = bgStipplePrio = -1;  
     fgPrio = fontPrio = fgStipplePrio = -1;  
     underlinePrio = elidePrio = justifyPrio = offsetPrio = -1;  
     lMargin1Prio = lMargin2Prio = rMarginPrio = -1;  
     spacing1Prio = spacing2Prio = spacing3Prio = -1;  
     overstrikePrio = tabPrio = wrapPrio = -1;  
     memset((VOID *) &styleValues, 0, sizeof(StyleValues));  
     styleValues.relief = TK_RELIEF_FLAT;  
     styleValues.fgColor = textPtr->fgColor;  
     styleValues.tkfont = textPtr->tkfont;  
     styleValues.justify = TK_JUSTIFY_LEFT;  
     styleValues.spacing1 = textPtr->spacing1;  
     styleValues.spacing2 = textPtr->spacing2;  
     styleValues.spacing3 = textPtr->spacing3;  
     styleValues.tabArrayPtr = textPtr->tabArrayPtr;  
     styleValues.wrapMode = textPtr->wrapMode;  
     styleValues.elide = 0;  
     for (i = 0 ; i < numTags; i++) {  
         tagPtr = tagPtrs[i];  
   
         /*  
          * On Windows and Mac, we need to skip the selection tag if  
          * we don't have focus.  
          */  
   
 #ifndef ALWAYS_SHOW_SELECTION  
         if ((tagPtr == textPtr->selTagPtr) && !(textPtr->flags & GOT_FOCUS)) {  
             continue;  
         }  
 #endif  
   
         if ((tagPtr->border != NULL) && (tagPtr->priority > borderPrio)) {  
             styleValues.border = tagPtr->border;  
             borderPrio = tagPtr->priority;  
         }  
         if ((tagPtr->bdString != NULL)  
                 && (tagPtr->priority > borderWidthPrio)) {  
             styleValues.borderWidth = tagPtr->borderWidth;  
             borderWidthPrio = tagPtr->priority;  
         }  
         if ((tagPtr->reliefString != NULL)  
                 && (tagPtr->priority > reliefPrio)) {  
             if (styleValues.border == NULL) {  
                 styleValues.border = textPtr->border;  
             }  
             styleValues.relief = tagPtr->relief;  
             reliefPrio = tagPtr->priority;  
         }  
         if ((tagPtr->bgStipple != None)  
                 && (tagPtr->priority > bgStipplePrio)) {  
             styleValues.bgStipple = tagPtr->bgStipple;  
             bgStipplePrio = tagPtr->priority;  
         }  
         if ((tagPtr->fgColor != None) && (tagPtr->priority > fgPrio)) {  
             styleValues.fgColor = tagPtr->fgColor;  
             fgPrio = tagPtr->priority;  
         }  
         if ((tagPtr->tkfont != None) && (tagPtr->priority > fontPrio)) {  
             styleValues.tkfont = tagPtr->tkfont;  
             fontPrio = tagPtr->priority;  
         }  
         if ((tagPtr->fgStipple != None)  
                 && (tagPtr->priority > fgStipplePrio)) {  
             styleValues.fgStipple = tagPtr->fgStipple;  
             fgStipplePrio = tagPtr->priority;  
         }  
         if ((tagPtr->justifyString != NULL)  
                 && (tagPtr->priority > justifyPrio)) {  
             styleValues.justify = tagPtr->justify;  
             justifyPrio = tagPtr->priority;  
         }  
         if ((tagPtr->lMargin1String != NULL)  
                 && (tagPtr->priority > lMargin1Prio)) {  
             styleValues.lMargin1 = tagPtr->lMargin1;  
             lMargin1Prio = tagPtr->priority;  
         }  
         if ((tagPtr->lMargin2String != NULL)  
                 && (tagPtr->priority > lMargin2Prio)) {  
             styleValues.lMargin2 = tagPtr->lMargin2;  
             lMargin2Prio = tagPtr->priority;  
         }  
         if ((tagPtr->offsetString != NULL)  
                 && (tagPtr->priority > offsetPrio)) {  
             styleValues.offset = tagPtr->offset;  
             offsetPrio = tagPtr->priority;  
         }  
         if ((tagPtr->overstrikeString != NULL)  
                 && (tagPtr->priority > overstrikePrio)) {  
             styleValues.overstrike = tagPtr->overstrike;  
             overstrikePrio = tagPtr->priority;  
         }  
         if ((tagPtr->rMarginString != NULL)  
                 && (tagPtr->priority > rMarginPrio)) {  
             styleValues.rMargin = tagPtr->rMargin;  
             rMarginPrio = tagPtr->priority;  
         }  
         if ((tagPtr->spacing1String != NULL)  
                 && (tagPtr->priority > spacing1Prio)) {  
             styleValues.spacing1 = tagPtr->spacing1;  
             spacing1Prio = tagPtr->priority;  
         }  
         if ((tagPtr->spacing2String != NULL)  
                 && (tagPtr->priority > spacing2Prio)) {  
             styleValues.spacing2 = tagPtr->spacing2;  
             spacing2Prio = tagPtr->priority;  
         }  
         if ((tagPtr->spacing3String != NULL)  
                 && (tagPtr->priority > spacing3Prio)) {  
             styleValues.spacing3 = tagPtr->spacing3;  
             spacing3Prio = tagPtr->priority;  
         }  
         if ((tagPtr->tabString != NULL)  
                 && (tagPtr->priority > tabPrio)) {  
             styleValues.tabArrayPtr = tagPtr->tabArrayPtr;  
             tabPrio = tagPtr->priority;  
         }  
         if ((tagPtr->underlineString != NULL)  
                 && (tagPtr->priority > underlinePrio)) {  
             styleValues.underline = tagPtr->underline;  
             underlinePrio = tagPtr->priority;  
         }  
         if ((tagPtr->elideString != NULL)  
                 && (tagPtr->priority > elidePrio)) {  
             styleValues.elide = tagPtr->elide;  
             elidePrio = tagPtr->priority;  
         }  
         if ((tagPtr->wrapMode != TEXT_WRAPMODE_NULL)  
                 && (tagPtr->priority > wrapPrio)) {  
             styleValues.wrapMode = tagPtr->wrapMode;  
             wrapPrio = tagPtr->priority;  
         }  
     }  
     if (tagPtrs != NULL) {  
         ckfree((char *) tagPtrs);  
     }  
   
     /*  
      * Use an existing style if there's one around that matches.  
      */  
   
     hPtr = Tcl_CreateHashEntry(&textPtr->dInfoPtr->styleTable,  
             (char *) &styleValues, &new);  
     if (!new) {  
         stylePtr = (TextStyle *) Tcl_GetHashValue(hPtr);  
         stylePtr->refCount++;  
         return stylePtr;  
     }  
   
     /*  
      * No existing style matched.  Make a new one.  
      */  
   
     stylePtr = (TextStyle *) ckalloc(sizeof(TextStyle));  
     stylePtr->refCount = 1;  
     if (styleValues.border != NULL) {  
         gcValues.foreground = Tk_3DBorderColor(styleValues.border)->pixel;  
         mask = GCForeground;  
         if (styleValues.bgStipple != None) {  
             gcValues.stipple = styleValues.bgStipple;  
             gcValues.fill_style = FillStippled;  
             mask |= GCStipple|GCFillStyle;  
         }  
         stylePtr->bgGC = Tk_GetGC(textPtr->tkwin, mask, &gcValues);  
     } else {  
         stylePtr->bgGC = None;  
     }  
     mask = GCFont;  
     gcValues.font = Tk_FontId(styleValues.tkfont);  
     mask |= GCForeground;  
     gcValues.foreground = styleValues.fgColor->pixel;  
     if (styleValues.fgStipple != None) {  
         gcValues.stipple = styleValues.fgStipple;  
         gcValues.fill_style = FillStippled;  
         mask |= GCStipple|GCFillStyle;  
     }  
     stylePtr->fgGC = Tk_GetGC(textPtr->tkwin, mask, &gcValues);  
     stylePtr->sValuePtr = (StyleValues *)  
             Tcl_GetHashKey(&textPtr->dInfoPtr->styleTable, hPtr);  
     stylePtr->hPtr = hPtr;  
     Tcl_SetHashValue(hPtr, stylePtr);  
     return stylePtr;  
 }  
   
 /*  
  *----------------------------------------------------------------------  
  *  
  * FreeStyle --  
  *  
  *      This procedure is called when a TextStyle structure is no longer  
  *      needed.  It decrements the reference count and frees up the  
  *      space for the style structure if the reference count is 0.  
  *  
  * Results:  
  *      None.  
  *  
  * Side effects:  
  *      The storage and other resources associated with the style  
  *      are freed up if no-one's still using it.  
  *  
  *----------------------------------------------------------------------  
  */  
   
 static void  
 FreeStyle(textPtr, stylePtr)  
     TkText *textPtr;                    /* Information about overall widget. */  
     register TextStyle *stylePtr;       /* Information about style to free. */  
   
 {  
     stylePtr->refCount--;  
     if (stylePtr->refCount == 0) {  
         if (stylePtr->bgGC != None) {  
             Tk_FreeGC(textPtr->display, stylePtr->bgGC);  
         }  
         if (stylePtr->fgGC != None) {  
             Tk_FreeGC(textPtr->display, stylePtr->fgGC);  
         }  
         Tcl_DeleteHashEntry(stylePtr->hPtr);  
         ckfree((char *) stylePtr);  
     }  
 }  
   
 /*  
  *----------------------------------------------------------------------  
  *  
  * LayoutDLine --  
  *  
  *      This procedure generates a single DLine structure for a display  
  *      line whose leftmost character is given by indexPtr.  
  *        
  * Results:  
  *      The return value is a pointer to a DLine structure desribing the  
  *      display line.  All fields are filled in and correct except for  
  *      y and nextPtr.  
  *  
  * Side effects:  
  *      Storage is allocated for the new DLine.  
  *  
  *----------------------------------------------------------------------  
  */  
   
 static DLine *  
 LayoutDLine(textPtr, indexPtr)  
     TkText *textPtr;            /* Overall information about text widget. */  
     TkTextIndex *indexPtr;      /* Beginning of display line.  May not  
                                  * necessarily point to a character segment. */  
 {  
     register DLine *dlPtr;              /* New display line. */  
     TkTextSegment *segPtr;              /* Current segment in text. */  
     TkTextDispChunk *lastChunkPtr;      /* Last chunk allocated so far  
                                          * for line. */  
     TkTextDispChunk *chunkPtr;          /* Current chunk. */  
     TkTextIndex curIndex;  
     TkTextDispChunk *breakChunkPtr;     /* Chunk containing best word break  
                                          * point, if any. */  
     TkTextIndex breakIndex;             /* Index of first character in  
                                          * breakChunkPtr. */  
     int breakByteOffset;                /* Byte offset of character within  
                                          * breakChunkPtr just to right of best  
                                          * break point. */  
     int noCharsYet;                     /* Non-zero means that no characters  
                                          * have been placed on the line yet. */  
     int justify;                        /* How to justify line: taken from  
                                          * style for the first character in  
                                          * line. */  
     int jIndent;                        /* Additional indentation (beyond  
                                          * margins) due to justification. */  
     int rMargin;                        /* Right margin width for line. */  
     TkWrapMode wrapMode;                /* Wrap mode to use for this line. */  
     int x = 0, maxX = 0;                /* Initializations needed only to  
                                          * stop compiler warnings. */  
     int wholeLine;                      /* Non-zero means this display line  
                                          * runs to the end of the text line. */  
     int tabIndex;                       /* Index of the current tab stop. */  
     int gotTab;                         /* Non-zero means the current chunk  
                                          * contains a tab. */  
     TkTextDispChunk *tabChunkPtr;       /* Pointer to the chunk containing  
                                          * the previous tab stop. */  
     int maxBytes;                       /* Maximum number of bytes to  
                                          * include in this chunk. */  
     TkTextTabArray *tabArrayPtr;        /* Tab stops for line; taken from  
                                          * style for the first character on  
                                          * line. */  
     int tabSize;                        /* Number of pixels consumed by current  
                                          * tab stop. */  
     TkTextDispChunk *lastCharChunkPtr;  /* Pointer to last chunk in display  
                                          * lines with numBytes > 0.  Used to  
                                          * drop 0-sized chunks from the end  
                                          * of the line. */  
     int byteOffset, ascent, descent, code, elide, elidesize;  
     StyleValues *sValuePtr;  
   
     /*  
      * Create and initialize a new DLine structure.  
      */  
   
     dlPtr = (DLine *) ckalloc(sizeof(DLine));  
     dlPtr->index = *indexPtr;  
     dlPtr->byteCount = 0;  
     dlPtr->y = 0;  
     dlPtr->oldY = -1;  
     dlPtr->height = 0;  
     dlPtr->baseline = 0;  
     dlPtr->chunkPtr = NULL;  
     dlPtr->nextPtr = NULL;  
     dlPtr->flags = NEW_LAYOUT;  
   
     /*  
      * Special case entirely elide line as there may be 1000s or more  
      */  
     elide = TkTextIsElided(textPtr, indexPtr);          /* save a malloc */  
     if (elide && indexPtr->byteIndex==0) {  
         maxBytes = 0;  
         for (segPtr = indexPtr->linePtr->segPtr;  
              elide && (segPtr != NULL);  
              segPtr = segPtr->nextPtr) {  
             if ((elidesize = segPtr->size) > 0) {  
                 maxBytes += elidesize;  
                 /*  
                  * If have we have a tag toggle, there is a chance  
                  * that invisibility state changed, so bail out  
                  */  
             } else if ((segPtr->typePtr == &tkTextToggleOffType)  
                     || (segPtr->typePtr == &tkTextToggleOnType)) {  
                 if (segPtr->body.toggle.tagPtr->elideString != NULL) {  
                     elide = (segPtr->typePtr == &tkTextToggleOffType)  
                         ^ segPtr->body.toggle.tagPtr->elide;  
                 }  
             }  
         }  
   
         if (elide) {  
             dlPtr->byteCount = maxBytes;  
             dlPtr->spaceAbove = dlPtr->spaceBelow = dlPtr->length = 0;  
             return dlPtr;  
         }  
     }  
   
     /*  
      * Each iteration of the loop below creates one TkTextDispChunk for  
      * the new display line.  The line will always have at least one  
      * chunk (for the newline character at the end, if there's nothing  
      * else available).  
      */  
   
     curIndex = *indexPtr;  
     lastChunkPtr = NULL;  
     chunkPtr = NULL;  
     noCharsYet = 1;  
     elide = 0;  
     breakChunkPtr = NULL;  
     breakByteOffset = 0;  
     justify = TK_JUSTIFY_LEFT;  
     tabIndex = -1;  
     tabChunkPtr = NULL;  
     tabArrayPtr = NULL;  
     rMargin = 0;  
     wrapMode = TEXT_WRAPMODE_CHAR;  
     tabSize = 0;  
     lastCharChunkPtr = NULL;  
   
     /*  
      * Find the first segment to consider for the line.  Can't call  
      * TkTextIndexToSeg for this because it won't return a segment  
      * with zero size (such as the insertion cursor's mark).  
      */  
   
     for (byteOffset = curIndex.byteIndex, segPtr = curIndex.linePtr->segPtr;  
          (byteOffset > 0) && (byteOffset >= segPtr->size);  
          byteOffset -= segPtr->size, segPtr = segPtr->nextPtr) {  
         /* Empty loop body. */  
     }  
   
     while (segPtr != NULL) {  
         /*  
          * Every line still gets at least one chunk due to expectations  
          * in the rest of the code, but we are able to skip elided portions  
          * of the line quickly.  
          * If current chunk is elided and last chunk was too, coalese  
          */  
         if (elide && (lastChunkPtr != NULL)  
                 && (lastChunkPtr->displayProc == NULL /*ElideDisplayProc*/)) {  
             if ((elidesize = segPtr->size - byteOffset) > 0) {  
                 curIndex.byteIndex += elidesize;  
                 lastChunkPtr->numBytes += elidesize;  
                 breakByteOffset = lastChunkPtr->breakIndex = lastChunkPtr->numBytes;  
                 /*  
                  * If have we have a tag toggle, there is a chance  
                  * that invisibility state changed, so bail out  
                  */  
             } else if ((segPtr->typePtr == &tkTextToggleOffType)  
                     || (segPtr->typePtr == &tkTextToggleOnType)) {  
                 if (segPtr->body.toggle.tagPtr->elideString != NULL) {  
                     elide = (segPtr->typePtr == &tkTextToggleOffType)  
                         ^ segPtr->body.toggle.tagPtr->elide;  
                 }  
             }  
   
             byteOffset = 0;  
             segPtr = segPtr->nextPtr;  
             if (segPtr == NULL && chunkPtr != NULL) {  
                 ckfree((char *) chunkPtr);  
             }  
             continue;  
         }  
   
         if (segPtr->typePtr->layoutProc == NULL) {  
             segPtr = segPtr->nextPtr;  
             byteOffset = 0;  
             continue;  
         }  
         if (chunkPtr == NULL) {  
             chunkPtr = (TkTextDispChunk *) ckalloc(sizeof(TkTextDispChunk));  
             chunkPtr->nextPtr = NULL;  
         }  
         chunkPtr->stylePtr = GetStyle(textPtr, &curIndex);  
         elide = chunkPtr->stylePtr->sValuePtr->elide;  
   
         /*  
          * Save style information such as justification and indentation,  
          * up until the first character is encountered, then retain that  
          * information for the rest of the line.  
          */  
   
         if (noCharsYet) {  
             tabArrayPtr = chunkPtr->stylePtr->sValuePtr->tabArrayPtr;  
             justify = chunkPtr->stylePtr->sValuePtr->justify;  
             rMargin = chunkPtr->stylePtr->sValuePtr->rMargin;  
             wrapMode = chunkPtr->stylePtr->sValuePtr->wrapMode;  
             x = ((curIndex.byteIndex == 0)  
                     ? chunkPtr->stylePtr->sValuePtr->lMargin1  
                     : chunkPtr->stylePtr->sValuePtr->lMargin2);  
             if (wrapMode == TEXT_WRAPMODE_NONE) {  
                 maxX = -1;  
             } else {  
                 maxX = textPtr->dInfoPtr->maxX - textPtr->dInfoPtr->x  
                         - rMargin;  
                 if (maxX < x) {  
                     maxX = x;  
                 }  
             }  
         }  
   
         /*  
          * See if there is a tab in the current chunk; if so, only  
          * layout characters up to (and including) the tab.  
          */  
   
         gotTab = 0;  
         maxBytes = segPtr->size - byteOffset;  
         if (!elide && justify == TK_JUSTIFY_LEFT) {  
             if (segPtr->typePtr == &tkTextCharType) {  
                 char *p;  
   
                 for (p = segPtr->body.chars  + byteOffset; *p != 0; p++) {  
                     if (*p == '\t') {  
                         maxBytes = (p + 1 - segPtr->body.chars) - byteOffset;  
                         gotTab = 1;  
                         break;  
                     }  
                 }  
             }  
         }  
         chunkPtr->x = x;  
         if (elide && maxBytes) {  
             /* don't free style here, as other code expects to be able to do that */  
             /*breakByteOffset =*/ chunkPtr->breakIndex = chunkPtr->numBytes = maxBytes;  
             chunkPtr->width = 0;  
             chunkPtr->minAscent = chunkPtr->minDescent = chunkPtr->minHeight = 0;  
   
             /* would just like to point to canonical empty chunk */  
             chunkPtr->displayProc = (Tk_ChunkDisplayProc *) NULL;  
             chunkPtr->undisplayProc = (Tk_ChunkUndisplayProc *) NULL;  
             chunkPtr->measureProc = ElideMeasureProc;  
             chunkPtr->bboxProc = ElideBboxProc;  
   
             code = 1;  
         } else  
         code = (*segPtr->typePtr->layoutProc)(textPtr, &curIndex, segPtr,  
                 byteOffset, maxX-tabSize, maxBytes, noCharsYet, wrapMode,  
                 chunkPtr);  
         if (code <= 0) {  
             FreeStyle(textPtr, chunkPtr->stylePtr);  
             if (code < 0) {  
                 /*  
                  * This segment doesn't wish to display itself (e.g. most  
                  * marks).  
                  */  
   
                 segPtr = segPtr->nextPtr;  
                 byteOffset = 0;  
                 continue;  
             }  
   
             /*  
              * No characters from this segment fit in the window: this  
              * means we're at the end of the display line.  
              */  
   
             if (chunkPtr != NULL) {  
                 ckfree((char *) chunkPtr);  
             }  
             break;  
         }  
         if (chunkPtr->numBytes > 0) {  
             noCharsYet = 0;  
             lastCharChunkPtr = chunkPtr;  
         }  
         if (lastChunkPtr == NULL) {  
             dlPtr->chunkPtr = chunkPtr;  
         } else {  
             lastChunkPtr->nextPtr = chunkPtr;  
         }  
         lastChunkPtr = chunkPtr;  
         x += chunkPtr->width;  
         if (chunkPtr->breakIndex > 0) {  
             breakByteOffset = chunkPtr->breakIndex;  
             breakIndex = curIndex;  
             breakChunkPtr = chunkPtr;  
         }  
         if (chunkPtr->numBytes != maxBytes) {  
             break;  
         }  
   
         /*  
          * If we're at a new tab, adjust the layout for all the chunks  
          * pertaining to the previous tab.  Also adjust the amount of  
          * space left in the line to account for space that will be eaten  
          * up by the tab.  
          */  
   
         if (gotTab) {  
             if (tabIndex >= 0) {  
                 AdjustForTab(textPtr, tabArrayPtr, tabIndex, tabChunkPtr);  
                 x = chunkPtr->x + chunkPtr->width;  
             }  
             tabIndex++;  
             tabChunkPtr = chunkPtr;  
             tabSize = SizeOfTab(textPtr, tabArrayPtr, tabIndex, x, maxX);  
             if ((maxX >= 0) && (tabSize >= maxX - x)) {  
                 break;  
             }  
         }  
         curIndex.byteIndex += chunkPtr->numBytes;  
         byteOffset += chunkPtr->numBytes;  
         if (byteOffset >= segPtr->size) {  
             byteOffset = 0;  
             segPtr = segPtr->nextPtr;  
         }  
   
         chunkPtr = NULL;  
     }  
     if (noCharsYet) {  
         panic("LayoutDLine couldn't place any characters on a line");  
     }  
     wholeLine = (segPtr == NULL);  
   
     /*  
      * We're at the end of the display line.  Throw away everything  
      * after the most recent word break, if there is one;  this may  
      * potentially require the last chunk to be layed out again.  
      */  
   
     if (breakChunkPtr == NULL) {  
         /*  
          * This code makes sure that we don't accidentally display  
          * chunks with no characters at the end of the line (such as  
          * the insertion cursor).  These chunks belong on the next  
          * line.  So, throw away everything after the last chunk that  
          * has characters in it.  
          */  
   
         breakChunkPtr = lastCharChunkPtr;  
         breakByteOffset = breakChunkPtr->numBytes;  
     }  
     if ((breakChunkPtr != NULL) && ((lastChunkPtr != breakChunkPtr)  
             || (breakByteOffset != lastChunkPtr->numBytes))) {  
         while (1) {  
             chunkPtr = breakChunkPtr->nextPtr;  
             if (chunkPtr == NULL) {  
                 break;  
             }  
             FreeStyle(textPtr, chunkPtr->stylePtr);  
             breakChunkPtr->nextPtr = chunkPtr->nextPtr;  
             (*chunkPtr->undisplayProc)(textPtr, chunkPtr);  
             ckfree((char *) chunkPtr);  
         }  
         if (breakByteOffset != breakChunkPtr->numBytes) {  
             (*breakChunkPtr->undisplayProc)(textPtr, breakChunkPtr);  
             segPtr = TkTextIndexToSeg(&breakIndex, &byteOffset);  
             (*segPtr->typePtr->layoutProc)(textPtr, &breakIndex,  
                     segPtr, byteOffset, maxX, breakByteOffset, 0,  
                     wrapMode, breakChunkPtr);  
         }  
         lastChunkPtr = breakChunkPtr;  
         wholeLine = 0;  
     }  
   
   
     /*  
      * Make tab adjustments for the last tab stop, if there is one.  
      */  
   
     if ((tabIndex >= 0) && (tabChunkPtr != NULL)) {  
         AdjustForTab(textPtr, tabArrayPtr, tabIndex, tabChunkPtr);  
     }  
   
     /*  
      * Make one more pass over the line to recompute various things  
      * like its height, length, and total number of bytes.  Also  
      * modify the x-locations of chunks to reflect justification.  
      * If we're not wrapping, I'm not sure what is the best way to  
      * handle left and center justification:  should the total length,  
      * for purposes of justification, be (a) the window width, (b)  
      * the length of the longest line in the window, or (c) the length  
      * of the longest line in the text?  (c) isn't available, (b) seems  
      * weird, since it can change with vertical scrolling, so (a) is  
      * what is implemented below.  
      */  
   
     if (wrapMode == TEXT_WRAPMODE_NONE) {  
         maxX = textPtr->dInfoPtr->maxX - textPtr->dInfoPtr->x - rMargin;  
     }  
     dlPtr->length = lastChunkPtr->x + lastChunkPtr->width;  
     if (justify == TK_JUSTIFY_LEFT) {  
         jIndent = 0;  
     } else if (justify == TK_JUSTIFY_RIGHT) {  
         jIndent = maxX - dlPtr->length;  
     } else {  
         jIndent = (maxX - dlPtr->length)/2;  
     }  
     ascent = descent = 0;  
     for (chunkPtr = dlPtr->chunkPtr; chunkPtr != NULL;  
             chunkPtr = chunkPtr->nextPtr) {  
         chunkPtr->x += jIndent;  
         dlPtr->byteCount += chunkPtr->numBytes;  
         if (chunkPtr->minAscent > ascent) {  
             ascent = chunkPtr->minAscent;  
         }  
         if (chunkPtr->minDescent > descent) {  
             descent = chunkPtr->minDescent;  
         }  
         if (chunkPtr->minHeight > dlPtr->height) {  
             dlPtr->height = chunkPtr->minHeight;  
         }  
         sValuePtr = chunkPtr->stylePtr->sValuePtr;  
         if ((sValuePtr->borderWidth > 0)  
                 && (sValuePtr->relief != TK_RELIEF_FLAT)) {  
             dlPtr->flags |= HAS_3D_BORDER;  
         }  
     }  
     if (dlPtr->height < (ascent + descent)) {  
         dlPtr->height = ascent + descent;  
         dlPtr->baseline = ascent;  
     } else {  
         dlPtr->baseline = ascent + (dlPtr->height - ascent - descent)/2;  
     }  
     sValuePtr = dlPtr->chunkPtr->stylePtr->sValuePtr;  
     if (dlPtr->index.byteIndex == 0) {  
         dlPtr->spaceAbove = sValuePtr->spacing1;  
     } else {  
         dlPtr->spaceAbove = sValuePtr->spacing2 - sValuePtr->spacing2/2;  
     }  
     if (wholeLine) {  
         dlPtr->spaceBelow = sValuePtr->spacing3;  
     } else {  
         dlPtr->spaceBelow = sValuePtr->spacing2/2;  
     }  
     dlPtr->height += dlPtr->spaceAbove + dlPtr->spaceBelow;  
     dlPtr->baseline += dlPtr->spaceAbove;  
   
     /*  
      * Recompute line length:  may have changed because of justification.  
      */  
   
     dlPtr->length = lastChunkPtr->x + lastChunkPtr->width;  
     return dlPtr;  
 }  
   
 /*  
  *----------------------------------------------------------------------  
  *  
  * UpdateDisplayInfo --  
  *  
  *      This procedure is invoked to recompute some or all of the  
  *      DLine structures for a text widget.  At the time it is called  
  *      the DLine structures still left in the widget are guaranteed  
  *      to be correct except that (a) the y-coordinates aren't  
  *      necessarily correct, (b) there may be missing structures  
  *      (the DLine structures get removed as soon as they are potentially  
  *      out-of-date), and (c) DLine structures that don't start at the  
  *      beginning of a line may be incorrect if previous information in  
  *      the same line changed size in a way that moved a line boundary  
  *      (DLines for any info that changed will have been deleted, but  
  *      not DLines for unchanged info in the same text line).  
  *  
  * Results:  
  *      None.  
  *  
  * Side effects:  
  *      Upon return, the DLine information for textPtr correctly reflects  
  *      the positions where characters will be displayed.  However, this  
  *      procedure doesn't actually bring the display up-to-date.  
  *  
  *----------------------------------------------------------------------  
  */  
   
 static void  
 UpdateDisplayInfo(textPtr)  
     TkText *textPtr;                    /* Text widget to update. */  
 {  
     register TextDInfo *dInfoPtr = textPtr->dInfoPtr;  
     register DLine *dlPtr, *prevPtr;  
     TkTextIndex index;  
     TkTextLine *lastLinePtr;  
     int y, maxY, pixelOffset, maxOffset;  
   
     if (!(dInfoPtr->flags & DINFO_OUT_OF_DATE)) {  
         return;  
     }  
     dInfoPtr->flags &= ~DINFO_OUT_OF_DATE;  
   
     /*  
      * Delete any DLines that are now above the top of the window.  
      */  
   
     index = textPtr->topIndex;  
     dlPtr = FindDLine(dInfoPtr->dLinePtr, &index);  
     if ((dlPtr != NULL) && (dlPtr != dInfoPtr->dLinePtr)) {  
         FreeDLines(textPtr, dInfoPtr->dLinePtr, dlPtr, 1);  
     }  
   
     /*  
      *--------------------------------------------------------------  
      * Scan through the contents of the window from top to bottom,  
      * recomputing information for lines that are missing.  
      *--------------------------------------------------------------  
      */  
   
     lastLinePtr = TkBTreeFindLine(textPtr->tree,  
             TkBTreeNumLines(textPtr->tree));  
     dlPtr = dInfoPtr->dLinePtr;  
     prevPtr = NULL;  
     y = dInfoPtr->y;  
     maxY = dInfoPtr->maxY;  
     while (1) {  
         register DLine *newPtr;  
   
         if (index.linePtr == lastLinePtr) {  
             break;  
         }  
   
         /*  
          * There are three possibilities right now:  
          * (a) the next DLine (dlPtr) corresponds exactly to the next  
          *     information we want to display: just use it as-is.  
          * (b) the next DLine corresponds to a different line, or to  
          *     a segment that will be coming later in the same line:  
          *     leave this DLine alone in the hopes that we'll be able  
          *     to use it later, then create a new DLine in front of  
          *     it.  
          * (c) the next DLine corresponds to a segment in the line we  
          *     want, but it's a segment that has already been processed  
          *     or will never be processed.  Delete the DLine and try  
          *     again.  
          *  
          * One other twist on all this.  It's possible for 3D borders  
          * to interact between lines (see DisplayLineBackground) so if  
          * a line is relayed out and has styles with 3D borders, its  
          * neighbors have to be redrawn if they have 3D borders too,  
          * since the interactions could have changed (the neighbors  
          * don't have to be relayed out, just redrawn).  
          */  
   
         if ((dlPtr == NULL) || (dlPtr->index.linePtr != index.linePtr)) {  
             /*  
              * Case (b) -- must make new DLine.  
              */  
   
             makeNewDLine:  
             if (tkTextDebug) {  
                 char string[TK_POS_CHARS];  
   
                 /*  
                  * Debugging is enabled, so keep a log of all the lines  
                  * that were re-layed out.  The test suite uses this  
                  * information.  
                  */  
   
                 TkTextPrintIndex(&index, string);  
                 Tcl_SetVar2(textPtr->interp, "tk_textRelayout", (char *) NULL,  
                         string,  
                         TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT);  
             }  
             newPtr = LayoutDLine(textPtr, &index);  
             if (prevPtr == NULL) {  
                 dInfoPtr->dLinePtr = newPtr;  
             } else {  
                 prevPtr->nextPtr = newPtr;  
                 if (prevPtr->flags & HAS_3D_BORDER) {  
                     prevPtr->oldY = -1;  
                 }  
             }  
             newPtr->nextPtr = dlPtr;  
             dlPtr = newPtr;  
         } else {  
             /*  
              * DlPtr refers to the line we want.  Next check the  
              * index within the line.  
              */  
   
             if (index.byteIndex == dlPtr->index.byteIndex) {  
                 /*  
                  * Case (a) -- can use existing display line as-is.  
                  */  
   
                 if ((dlPtr->flags & HAS_3D_BORDER) && (prevPtr != NULL)  
                         && (prevPtr->flags & (NEW_LAYOUT))) {  
                     dlPtr->oldY = -1;  
                 }  
                 goto lineOK;  
             }  
             if (index.byteIndex < dlPtr->index.byteIndex) {  
                 goto makeNewDLine;  
             }  
   
             /*  
              * Case (c) -- dlPtr is useless.  Discard it and start  
              * again with the next display line.  
              */  
   
             newPtr = dlPtr->nextPtr;  
             FreeDLines(textPtr, dlPtr, newPtr, 0);  
             dlPtr = newPtr;  
             if (prevPtr != NULL) {  
                 prevPtr->nextPtr = newPtr;  
             } else {  
                 dInfoPtr->dLinePtr = newPtr;  
             }  
             continue;  
         }  
   
         /*  
          * Advance to the start of the next line.  
          */  
   
         lineOK:  
         dlPtr->y = y;  
         y += dlPtr->height;  
         TkTextIndexForwBytes(&index, dlPtr->byteCount, &index);  
         prevPtr = dlPtr;  
         dlPtr = dlPtr->nextPtr;  
   
         /*  
          * If we switched text lines, delete any DLines left for the  
          * old text line.  
          */  
   
         if (index.linePtr != prevPtr->index.linePtr) {  
             register DLine *nextPtr;  
   
             nextPtr = dlPtr;  
             while ((nextPtr != NULL)  
                     && (nextPtr->index.linePtr == prevPtr->index.linePtr)) {  
                 nextPtr = nextPtr->nextPtr;  
             }  
             if (nextPtr != dlPtr) {  
                 FreeDLines(textPtr, dlPtr, nextPtr, 0);  
                 prevPtr->nextPtr = nextPtr;  
                 dlPtr = nextPtr;  
             }  
         }  
   
         /*  
          * It's important to have the following check here rather than in  
          * the while statement for the loop, so that there's always at least  
          * one DLine generated, regardless of how small the window is.  This  
          * keeps a lot of other code from breaking.  
          */  
   
         if (y >= maxY) {  
             break;  
         }  
     }  
   
     /*  
      * Delete any DLine structures that don't fit on the screen.  
      */  
   
     FreeDLines(textPtr, dlPtr, (DLine *) NULL, 1);  
   
     /*  
      *--------------------------------------------------------------  
      * If there is extra space at the bottom of the window (because  
      * we've hit the end of the text), then bring in more lines at  
      * the top of the window, if there are any, to fill in the view.  
      *--------------------------------------------------------------  
      */  
   
     if (y < maxY) {  
         int lineNum, spaceLeft, bytesToCount;  
         DLine *lowestPtr;  
   
         /*  
          * Layout an entire text line (potentially > 1 display line),  
          * then link in as many display lines as fit without moving  
          * the bottom line out of the window.  Repeat this until  
          * all the extra space has been used up or we've reached the  
          * beginning of the text.  
          */  
   
         spaceLeft = maxY - y;  
         lineNum = TkBTreeLineIndex(dInfoPtr->dLinePtr->index.linePtr);  
         bytesToCount = dInfoPtr->dLinePtr->index.byteIndex;  
         if (bytesToCount == 0) {  
             bytesToCount = INT_MAX;  
             lineNum--;  
         }  
         for ( ; (lineNum >= 0) && (spaceLeft > 0); lineNum--) {  
             index.linePtr = TkBTreeFindLine(textPtr->tree, lineNum);  
             index.byteIndex = 0;  
             lowestPtr = NULL;  
   
             do {  
                 dlPtr = LayoutDLine(textPtr, &index);  
                 dlPtr->nextPtr = lowestPtr;  
                 lowestPtr = dlPtr;  
                 if (dlPtr->length == 0 && dlPtr->height == 0) { bytesToCount--; break; }        /* elide */  
                 TkTextIndexForwBytes(&index, dlPtr->byteCount, &index);  
                 bytesToCount -= dlPtr->byteCount;  
             } while ((bytesToCount > 0)  
                     && (index.linePtr == lowestPtr->index.linePtr));  
   
             /*  
              * Scan through the display lines from the bottom one up to  
              * the top one.  
              */  
   
             while (lowestPtr != NULL) {  
                 dlPtr = lowestPtr;  
                 spaceLeft -= dlPtr->height;  
                 if (spaceLeft < 0) {  
                     break;  
                 }  
                 lowestPtr = dlPtr->nextPtr;  
                 dlPtr->nextPtr = dInfoPtr->dLinePtr;  
                 dInfoPtr->dLinePtr = dlPtr;  
                 if (tkTextDebug) {  
                     char string[TK_POS_CHARS];  
   
                     TkTextPrintIndex(&dlPtr->index, string);  
                     Tcl_SetVar2(textPtr->interp, "tk_textRelayout",  
                             (char *) NULL, string,  
                             TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT);  
                 }  
             }  
             FreeDLines(textPtr, lowestPtr, (DLine *) NULL, 0);  
             bytesToCount = INT_MAX;  
         }  
   
         /*  
          * Now we're all done except that the y-coordinates in all the  
          * DLines are wrong and the top index for the text is wrong.  
          * Update them.  
          */  
   
         textPtr->topIndex = dInfoPtr->dLinePtr->index;  
         y = dInfoPtr->y;  
         for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;  
                 dlPtr = dlPtr->nextPtr) {  
             if (y > dInfoPtr->maxY) {  
                 panic("Added too many new lines in UpdateDisplayInfo");  
             }  
             dlPtr->y = y;  
             y += dlPtr->height;  
         }  
     }  
   
     /*  
      *--------------------------------------------------------------  
      * If the old top or bottom line has scrolled elsewhere on the  
      * screen, we may not be able to re-use its old contents by  
      * copying bits (e.g., a beveled edge that was drawn when it was  
      * at the top or bottom won't be drawn when the line is in the  
      * middle and its neighbor has a matching background).  Similarly,  
      * if the new top or bottom line came from somewhere else on the  
      * screen, we may not be able to copy the old bits.  
      *--------------------------------------------------------------  
      */  
   
     dlPtr = dInfoPtr->dLinePtr;  
     if ((dlPtr->flags & HAS_3D_BORDER) && !(dlPtr->flags & TOP_LINE)) {  
         dlPtr->oldY = -1;  
     }  
     while (1) {  
         if ((dlPtr->flags & TOP_LINE) && (dlPtr != dInfoPtr->dLinePtr)  
                 && (dlPtr->flags & HAS_3D_BORDER)) {  
             dlPtr->oldY = -1;  
         }  
         if ((dlPtr->flags & BOTTOM_LINE) && (dlPtr->nextPtr != NULL)  
                 && (dlPtr->flags & HAS_3D_BORDER)) {  
             dlPtr->oldY = -1;  
         }  
         if (dlPtr->nextPtr == NULL) {  
             if ((dlPtr->flags & HAS_3D_BORDER)  
                     && !(dlPtr->flags & BOTTOM_LINE)) {  
                 dlPtr->oldY = -1;  
             }  
             dlPtr->flags &= ~TOP_LINE;  
             dlPtr->flags |= BOTTOM_LINE;  
             break;  
         }  
         dlPtr->flags &= ~(TOP_LINE|BOTTOM_LINE);  
         dlPtr = dlPtr->nextPtr;  
     }  
     dInfoPtr->dLinePtr->flags |= TOP_LINE;  
   
     /*  
      * Arrange for scrollbars to be updated.  
      */  
   
     textPtr->flags |= UPDATE_SCROLLBARS;  
   
     /*  
      *--------------------------------------------------------------  
      * Deal with horizontal scrolling:  
      * 1. If there's empty space to the right of the longest line,  
      *    shift the screen to the right to fill in the empty space.  
      * 2. If the desired horizontal scroll position has changed,  
      *    force a full redisplay of all the lines in the widget.  
      * 3. If the wrap mode isn't "none" then re-scroll to the base  
      *    position.  
      *--------------------------------------------------------------  
      */  
   
     dInfoPtr->maxLength = 0;  
     for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;  
             dlPtr = dlPtr->nextPtr) {  
         if (dlPtr->length > dInfoPtr->maxLength) {  
             dInfoPtr->maxLength = dlPtr->length;  
         }  
     }  
     maxOffset = (dInfoPtr->maxLength - (dInfoPtr->maxX - dInfoPtr->x)  
             + textPtr->charWidth - 1)/textPtr->charWidth;  
     if (dInfoPtr->newByteOffset > maxOffset) {  
         dInfoPtr->newByteOffset = maxOffset;  
     }  
     if (dInfoPtr->newByteOffset < 0) {  
         dInfoPtr->newByteOffset = 0;  
     }  
     pixelOffset = dInfoPtr->newByteOffset * textPtr->charWidth;  
     if (pixelOffset != dInfoPtr->curPixelOffset) {  
         dInfoPtr->curPixelOffset = pixelOffset;  
         for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;  
                 dlPtr = dlPtr->nextPtr) {  
             dlPtr->oldY = -1;  
         }  
     }  
 }  
   
 /*  
  *----------------------------------------------------------------------  
  *  
  * FreeDLines --  
  *  
  *      This procedure is called to free up all of the resources  
  *      associated with one or more DLine structures.  
  *  
  * Results:  
  *      None.  
  *  
  * Side effects:  
  *      Memory gets freed and various other resources are released.  
  *  
  *----------------------------------------------------------------------  
  */  
   
 static void  
 FreeDLines(textPtr, firstPtr, lastPtr, unlink)  
     TkText *textPtr;                    /* Information about overall text  
                                          * widget. */  
     register DLine *firstPtr;           /* Pointer to first DLine to free up. */  
     DLine *lastPtr;                     /* Pointer to DLine just after last  
                                          * one to free (NULL means everything  
                                          * starting with firstPtr). */  
     int unlink;                         /* 1 means DLines are currently linked  
                                          * into the list rooted at  
                                          * textPtr->dInfoPtr->dLinePtr and  
                                          * they have to be unlinked.  0 means  
                                          * just free without unlinking. */  
 {  
     register TkTextDispChunk *chunkPtr, *nextChunkPtr;  
     register DLine *nextDLinePtr;  
   
     if (unlink) {  
         if (textPtr->dInfoPtr->dLinePtr == firstPtr) {  
             textPtr->dInfoPtr->dLinePtr = lastPtr;  
         } else {  
             register DLine *prevPtr;  
             for (prevPtr = textPtr->dInfoPtr->dLinePtr;  
                     prevPtr->nextPtr != firstPtr; prevPtr = prevPtr->nextPtr) {  
                 /* Empty loop body. */  
             }  
             prevPtr->nextPtr = lastPtr;  
         }  
     }  
     while (firstPtr != lastPtr) {  
         nextDLinePtr = firstPtr->nextPtr;  
         for (chunkPtr = firstPtr->chunkPtr; chunkPtr != NULL;  
                 chunkPtr = nextChunkPtr) {  
             if (chunkPtr->undisplayProc != NULL) {  
                 (*chunkPtr->undisplayProc)(textPtr, chunkPtr);  
             }  
             FreeStyle(textPtr, chunkPtr->stylePtr);  
             nextChunkPtr = chunkPtr->nextPtr;  
             ckfree((char *) chunkPtr);  
         }  
         ckfree((char *) firstPtr);  
         firstPtr = nextDLinePtr;  
     }  
     textPtr->dInfoPtr->dLinesInvalidated = 1;  
 }  
   
 /*  
  *----------------------------------------------------------------------  
  *  
  * DisplayDLine --  
  *  
  *      This procedure is invoked to draw a single line on the  
  *      screen.  
  *  
  * Results:  
  *      None.  
  *  
  * Side effects:  
  *      The line given by dlPtr is drawn at its correct position in  
  *      textPtr's window.  Note that this is one *display* line, not  
  *      one *text* line.  
  *  
  *----------------------------------------------------------------------  
  */  
   
 static void  
 DisplayDLine(textPtr, dlPtr, prevPtr, pixmap)  
     TkText *textPtr;            /* Text widget in which to draw line. */  
     register DLine *dlPtr;      /* Information about line to draw. */  
     DLine *prevPtr;             /* Line just before one to draw, or NULL  
                                  * if dlPtr is the top line. */  
     Pixmap pixmap;              /* Pixmap to use for double-buffering.  
                                  * Caller must make sure it's large enough  
                                  * to hold line. */  
 {  
     register TkTextDispChunk *chunkPtr;  
     TextDInfo *dInfoPtr = textPtr->dInfoPtr;  
     Display *display;  
     int height, x;  
   
     if (dlPtr->chunkPtr == NULL) return;  
   
     /*  
      * First, clear the area of the line to the background color for the  
      * text widget.  
      */  
   
     display = Tk_Display(textPtr->tkwin);  
     Tk_Fill3DRectangle(textPtr->tkwin, pixmap, textPtr->border, 0, 0,  
             Tk_Width(textPtr->tkwin), dlPtr->height, 0, TK_RELIEF_FLAT);  
   
     /*  
      * Next, draw background information for the whole line.  
      */  
   
     DisplayLineBackground(textPtr, dlPtr, prevPtr, pixmap);  
   
     /*  
      * Make another pass through all of the chunks to redraw the  
      * insertion cursor, if it is visible on this line.  Must do  
      * it here rather than in the foreground pass below because  
      * otherwise a wide insertion cursor will obscure the character  
      * to its left.  
      */  
   
     if (textPtr->state == TK_STATE_NORMAL) {  
         for (chunkPtr = dlPtr->chunkPtr; (chunkPtr != NULL);  
                 chunkPtr = chunkPtr->nextPtr) {  
             x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curPixelOffset;  
             if (chunkPtr->displayProc == TkTextInsertDisplayProc) {  
                 (*chunkPtr->displayProc)(chunkPtr, x, dlPtr->spaceAbove,  
                         dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,  
                         dlPtr->baseline - dlPtr->spaceAbove, display, pixmap,  
                         dlPtr->y + dlPtr->spaceAbove);  
             }  
         }  
     }  
   
     /*  
      * Make yet another pass through all of the chunks to redraw all of  
      * foreground information.  Note:  we have to call the displayProc  
      * even for chunks that are off-screen.  This is needed, for  
      * example, so that embedded windows can be unmapped in this case.  
      * Conve  
      */  
   
     for (chunkPtr = dlPtr->chunkPtr; (chunkPtr != NULL);  
             chunkPtr = chunkPtr->nextPtr) {  
         if (chunkPtr->displayProc == TkTextInsertDisplayProc) {  
             /*  
              * Already displayed the insertion cursor above.  Don't  
              * do it again here.  
              */  
   
             continue;  
         }  
         x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curPixelOffset;  
         if ((x + chunkPtr->width <= 0) || (x >= dInfoPtr->maxX)) {  
             /*  
              * Note:  we have to call the displayProc even for chunks  
              * that are off-screen.  This is needed, for example, so  
              * that embedded windows can be unmapped in this case.  
              * Display the chunk at a coordinate that can be clearly  
              * identified by the displayProc as being off-screen to  
              * the left (the displayProc may not be able to tell if  
              * something is off to the right).  
              */  
   
             if (chunkPtr->displayProc != NULL)  
             (*chunkPtr->displayProc)(chunkPtr, -chunkPtr->width,  
                     dlPtr->spaceAbove,  
                     dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,  
                     dlPtr->baseline - dlPtr->spaceAbove, display, pixmap,  
                     dlPtr->y + dlPtr->spaceAbove);  
         } else {  
             /* don't call if elide.  This tax ok since not very many visible DLine's in  
                   an area, but potentially many elide ones */  
             if (chunkPtr->displayProc != NULL)  
             (*chunkPtr->displayProc)(chunkPtr, x, dlPtr->spaceAbove,  
                     dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,  
                     dlPtr->baseline - dlPtr->spaceAbove, display, pixmap,  
                     dlPtr->y + dlPtr->spaceAbove);  
         }  
         if (dInfoPtr->dLinesInvalidated) {  
             return;  
         }  
     }  
   
     /*  
      * Copy the pixmap onto the screen.  If this is the last line on  
      * the screen then copy a piece of the line, so that it doesn't  
      * overflow into the border area.  Another special trick:  copy the  
      * padding area to the left of the line;  this is because the  
      * insertion cursor sometimes overflows onto that area and we want  
      * to get as much of the cursor as possible.  
      */  
   
     height = dlPtr->height;  
     if ((height + dlPtr->y) > dInfoPtr->maxY) {  
         height = dInfoPtr->maxY - dlPtr->y;  
     }  
     XCopyArea(display, pixmap, Tk_WindowId(textPtr->tkwin), dInfoPtr->copyGC,  
             dInfoPtr->x, 0, (unsigned) (dInfoPtr->maxX - dInfoPtr->x),  
             (unsigned) height, dInfoPtr->x, dlPtr->y);  
     linesRedrawn++;  
 }  
   
 /*  
  *--------------------------------------------------------------  
  *  
  * DisplayLineBackground --  
  *  
  *      This procedure is called to fill in the background for  
  *      a display line.  It draws 3D borders cleverly so that  
  *      adjacent chunks with the same style (whether on the same  
  *      line or different lines) have a single 3D border around  
  *      the whole region.  
  *  
  * Results:  
  *      There is no return value.  Pixmap is filled in with background  
  *      information for dlPtr.  
  *  
  * Side effects:  
  *      None.  
  *  
  *--------------------------------------------------------------  
  */  
   
 static void  
 DisplayLineBackground(textPtr, dlPtr, prevPtr, pixmap)  
     TkText *textPtr;            /* Text widget containing line. */  
     register DLine *dlPtr;      /* Information about line to draw. */  
     DLine *prevPtr;             /* Line just above dlPtr, or NULL if dlPtr  
                                  * is the top-most line in the window. */  
     Pixmap pixmap;              /* Pixmap to use for double-buffering.  
                                  * Caller must make sure it's large enough  
                                  * to hold line.  Caller must also have  
                                  * filled it with the background color for  
                                  * the widget. */  
 {  
     TextDInfo *dInfoPtr = textPtr->dInfoPtr;  
     TkTextDispChunk *chunkPtr;  /* Pointer to chunk in the current line. */  
     TkTextDispChunk *chunkPtr2; /* Pointer to chunk in the line above or  
                                  * below the current one.  NULL if we're to  
                                  * the left of or to the right of the chunks  
                                  * in the line. */  
     TkTextDispChunk *nextPtr2;  /* Next chunk after chunkPtr2 (it's not the  
                                  * same as chunkPtr2->nextPtr in the case  
                                  * where chunkPtr2 is NULL because the line  
                                  * is indented). */  
     int leftX;                  /* The left edge of the region we're  
                                  * currently working on. */  
     int leftXIn;                /* 1 means beveled edge at leftX slopes right  
                                  * as it goes down, 0 means it slopes left  
                                  * as it goes down. */  
     int rightX;                 /* Right edge of chunkPtr. */  
     int rightX2;                /* Right edge of chunkPtr2. */  
     int matchLeft;              /* Does the style of this line match that  
                                  * of its neighbor just to the left of  
                                  * the current x coordinate? */  
     int matchRight;             /* Does line's style match its neighbor  
                                  * just to the right of the current x-coord? */  
     int minX, maxX, xOffset;  
     StyleValues *sValuePtr;  
     Display *display;  
   
   
     /*  
      * Pass 1: scan through dlPtr from left to right.  For each range of  
      * chunks with the same style, draw the main background for the style  
      * plus the vertical parts of the 3D borders (the left and right  
      * edges).  
      */  
   
     display = Tk_Display(textPtr->tkwin);  
     minX = dInfoPtr->curPixelOffset;  
     xOffset = dInfoPtr->x - minX;  
     maxX = minX + dInfoPtr->maxX - dInfoPtr->x;  
     chunkPtr = dlPtr->chunkPtr;  
   
     /*  
      * Note A: in the following statement, and a few others later in  
      * this file marked with "See Note A above", the right side of the  
      * assignment was replaced with 0 on 6/18/97.  This has the effect  
      * of highlighting the empty space to the left of a line whenever  
      * the leftmost character of the line is highlighted.  This way,  
      * multi-line highlights always line up along their left edges.  
      * However, this may look funny in the case where a single word is  
      * highlighted. To undo the change, replace "leftX = 0" with "leftX  
      * = chunkPtr->x" and "rightX2 = 0" with "rightX2 = nextPtr2->x"  
      * here and at all the marked points below.  This restores the old  
      * behavior where empty space to the left of a line is not  
      * highlighted, leaving a ragged left edge for multi-line  
      * highlights.  
      */  
   
     leftX = 0;  
     for (; leftX < maxX; chunkPtr = chunkPtr->nextPtr) {  
         if ((chunkPtr->nextPtr != NULL)  
                 && SAME_BACKGROUND(chunkPtr->nextPtr->stylePtr,  
                 chunkPtr->stylePtr)) {  
             continue;  
         }  
         sValuePtr = chunkPtr->stylePtr->sValuePtr;  
         rightX = chunkPtr->x + chunkPtr->width;  
         if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) {  
             rightX = maxX;  
         }  
         if (chunkPtr->stylePtr->bgGC != None) {  
             XFillRectangle(display, pixmap, chunkPtr->stylePtr->bgGC,  
                     leftX + xOffset, 0, (unsigned int) (rightX - leftX),  
                     (unsigned int) dlPtr->height);  
             if (sValuePtr->relief != TK_RELIEF_FLAT) {  
                 Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,  
                         leftX + xOffset, 0, sValuePtr->borderWidth,  
                         dlPtr->height, 1, sValuePtr->relief);  
                 Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,  
                         rightX - sValuePtr->borderWidth + xOffset,  
                         0, sValuePtr->borderWidth, dlPtr->height, 0,  
                         sValuePtr->relief);  
             }  
         }  
         leftX = rightX;  
     }  
   
     /*  
      * Pass 2: draw the horizontal bevels along the top of the line.  To  
      * do this, scan through dlPtr from left to right while simultaneously  
      * scanning through the line just above dlPtr.  ChunkPtr2 and nextPtr2  
      * refer to two adjacent chunks in the line above.  
      */  
   
     chunkPtr = dlPtr->chunkPtr;  
     leftX = 0;                          /* See Note A above. */  
     leftXIn = 1;  
     rightX = chunkPtr->x + chunkPtr->width;  
     if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) {  
         rightX = maxX;  
     }  
     chunkPtr2 = NULL;  
     if (prevPtr != NULL && prevPtr->chunkPtr != NULL) {  
         /*  
          * Find the chunk in the previous line that covers leftX.  
          */  
   
         nextPtr2 = prevPtr->chunkPtr;  
         rightX2 = 0;                    /* See Note A above. */  
         while (rightX2 <= leftX) {  
             chunkPtr2 = nextPtr2;  
             if (chunkPtr2 == NULL) {  
                 break;  
             }  
             nextPtr2 = chunkPtr2->nextPtr;  
             rightX2 = chunkPtr2->x + chunkPtr2->width;  
             if (nextPtr2 == NULL) {  
                 rightX2 = INT_MAX;  
             }  
         }  
     } else {  
         nextPtr2 = NULL;  
         rightX2 = INT_MAX;  
     }  
   
     while (leftX < maxX) {  
         matchLeft = (chunkPtr2 != NULL)  
                 && SAME_BACKGROUND(chunkPtr2->stylePtr, chunkPtr->stylePtr);  
         sValuePtr = chunkPtr->stylePtr->sValuePtr;  
         if (rightX <= rightX2) {  
             /*  
              * The chunk in our line is about to end.  If its style  
              * changes then draw the bevel for the current style.  
              */  
   
             if ((chunkPtr->nextPtr == NULL)  
                     || !SAME_BACKGROUND(chunkPtr->stylePtr,  
                     chunkPtr->nextPtr->stylePtr)) {  
                 if (!matchLeft && (sValuePtr->relief != TK_RELIEF_FLAT)) {  
                     Tk_3DHorizontalBevel(textPtr->tkwin, pixmap,  
                             sValuePtr->border, leftX + xOffset, 0,  
                             rightX - leftX, sValuePtr->borderWidth, leftXIn,  
                             1, 1, sValuePtr->relief);  
                 }  
                 leftX = rightX;  
                 leftXIn = 1;  
   
                 /*  
                  * If the chunk in the line above is also ending at  
                  * the same point then advance to the next chunk in  
                  * that line.  
                  */  
   
                 if ((rightX == rightX2) && (chunkPtr2 != NULL)) {  
                     goto nextChunk2;  
                 }  
             }  
             chunkPtr = chunkPtr->nextPtr;  
             if (chunkPtr == NULL) {  
                 break;  
             }  
             rightX = chunkPtr->x + chunkPtr->width;  
             if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) {  
                 rightX = maxX;  
             }  
             continue;  
         }  
   
         /*  
          * The chunk in the line above is ending at an x-position where  
          * there is no change in the style of the current line.  If the  
          * style above matches the current line on one side of the change  
          * but not on the other, we have to draw an L-shaped piece of  
          * bevel.  
          */  
   
         matchRight = (nextPtr2 != NULL)  
                 && SAME_BACKGROUND(nextPtr2->stylePtr, chunkPtr->stylePtr);  
         if (matchLeft && !matchRight) {  
             if (sValuePtr->relief != TK_RELIEF_FLAT) {  
                 Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,  
                         rightX2 - sValuePtr->borderWidth + xOffset, 0,  
                         sValuePtr->borderWidth, sValuePtr->borderWidth, 0,  
                         sValuePtr->relief);  
             }  
             leftX = rightX2 - sValuePtr->borderWidth;  
             leftXIn = 0;  
         } else if (!matchLeft && matchRight  
                 && (sValuePtr->relief != TK_RELIEF_FLAT)) {  
             Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,  
                     rightX2 + xOffset, 0, sValuePtr->borderWidth,  
                     sValuePtr->borderWidth, 1, sValuePtr->relief);  
             Tk_3DHorizontalBevel(textPtr->tkwin, pixmap, sValuePtr->border,  
                     leftX + xOffset, 0, rightX2 + sValuePtr->borderWidth -leftX,  
                     sValuePtr->borderWidth, leftXIn, 0, 1,  
                     sValuePtr->relief);  
         }  
   
         nextChunk2:  
         chunkPtr2 = nextPtr2;  
         if (chunkPtr2 == NULL) {  
             rightX2 = INT_MAX;  
         } else {  
             nextPtr2 = chunkPtr2->nextPtr;  
             rightX2 = chunkPtr2->x + chunkPtr2->width;  
             if (nextPtr2 == NULL) {  
                 rightX2 = INT_MAX;  
             }  
         }  
     }  
     /*  
      * Pass 3: draw the horizontal bevels along the bottom of the line.  
      * This uses the same approach as pass 2.  
      */  
   
     chunkPtr = dlPtr->chunkPtr;  
     leftX = 0;                          /* See Note A above. */  
     leftXIn = 0;  
     rightX = chunkPtr->x + chunkPtr->width;  
     if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) {  
         rightX = maxX;  
     }  
     chunkPtr2 = NULL;  
     if (dlPtr->nextPtr != NULL && dlPtr->nextPtr->chunkPtr != NULL) {  
         /*  
          * Find the chunk in the previous line that covers leftX.  
          */  
   
         nextPtr2 = dlPtr->nextPtr->chunkPtr;  
         rightX2 = 0;                    /* See Note A above. */  
         while (rightX2 <= leftX) {  
             chunkPtr2 = nextPtr2;  
             if (chunkPtr2 == NULL) {  
                 break;  
             }  
             nextPtr2 = chunkPtr2->nextPtr;  
             rightX2 = chunkPtr2->x + chunkPtr2->width;  
             if (nextPtr2 == NULL) {  
                 rightX2 = INT_MAX;  
             }  
         }  
     } else {  
         nextPtr2 = NULL;  
         rightX2 = INT_MAX;  
     }  
   
     while (leftX < maxX) {  
         matchLeft = (chunkPtr2 != NULL)  
                 && SAME_BACKGROUND(chunkPtr2->stylePtr, chunkPtr->stylePtr);  
         sValuePtr = chunkPtr->stylePtr->sValuePtr;  
         if (rightX <= rightX2) {  
             if ((chunkPtr->nextPtr == NULL)  
                     || !SAME_BACKGROUND(chunkPtr->stylePtr,  
                     chunkPtr->nextPtr->stylePtr)) {  
                 if (!matchLeft && (sValuePtr->relief != TK_RELIEF_FLAT)) {  
                     Tk_3DHorizontalBevel(textPtr->tkwin, pixmap,  
                             sValuePtr->border, leftX + xOffset,  
                             dlPtr->height - sValuePtr->borderWidth,  
                             rightX - leftX, sValuePtr->borderWidth, leftXIn,  
                             0, 0, sValuePtr->relief);  
                 }  
                 leftX = rightX;  
                 leftXIn = 0;  
                 if ((rightX == rightX2) && (chunkPtr2 != NULL)) {  
                     goto nextChunk2b;  
                 }  
             }  
             chunkPtr = chunkPtr->nextPtr;  
             if (chunkPtr == NULL) {  
                 break;  
             }  
             rightX = chunkPtr->x + chunkPtr->width;  
             if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) {  
                 rightX = maxX;  
             }  
             continue;  
         }  
   
         matchRight = (nextPtr2 != NULL)  
                 && SAME_BACKGROUND(nextPtr2->stylePtr, chunkPtr->stylePtr);  
         if (matchLeft && !matchRight) {  
             if (sValuePtr->relief != TK_RELIEF_FLAT) {  
                 Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,  
                         rightX2 - sValuePtr->borderWidth + xOffset,  
                         dlPtr->height - sValuePtr->borderWidth,  
                         sValuePtr->borderWidth, sValuePtr->borderWidth, 0,  
                         sValuePtr->relief);  
             }  
             leftX = rightX2 - sValuePtr->borderWidth;  
             leftXIn = 1;  
         } else if (!matchLeft && matchRight  
                 && (sValuePtr->relief != TK_RELIEF_FLAT)) {  
             Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,  
                     rightX2 + xOffset, dlPtr->height - sValuePtr->borderWidth,  
                     sValuePtr->borderWidth, sValuePtr->borderWidth,  
                     1, sValuePtr->relief);  
             Tk_3DHorizontalBevel(textPtr->tkwin, pixmap, sValuePtr->border,  
                     leftX + xOffset, dlPtr->height - sValuePtr->borderWidth,  
                     rightX2 + sValuePtr->borderWidth - leftX,  
                     sValuePtr->borderWidth, leftXIn, 1, 0, sValuePtr->relief);  
         }  
   
         nextChunk2b:  
         chunkPtr2 = nextPtr2;  
         if (chunkPtr2 == NULL) {  
             rightX2 = INT_MAX;  
         } else {  
             nextPtr2 = chunkPtr2->nextPtr;  
             rightX2 = chunkPtr2->x + chunkPtr2->width;  
             if (nextPtr2 == NULL) {  
                 rightX2 = INT_MAX;  
             }  
         }  
     }  
 }  
   
 /*  
  *----------------------------------------------------------------------  
  *  
  * DisplayText --  
  *  
  *      This procedure is invoked as a when-idle handler to update the  
  *      display.  It only redisplays the parts of the text widget that  
  *      are out of date.  
  *  
  * Results:  
  *      None.  
  *  
  * Side effects:  
  *      Information is redrawn on the screen.  
  *  
  *----------------------------------------------------------------------  
  */  
   
 static void  
 DisplayText(clientData)  
     ClientData clientData;      /* Information about widget. */  
 {  
     register TkText *textPtr = (TkText *) clientData;  
     TextDInfo *dInfoPtr = textPtr->dInfoPtr;  
     Tk_Window tkwin;  
     register DLine *dlPtr;  
     DLine *prevPtr;  
     Pixmap pixmap;  
     int maxHeight, borders;  
     int bottomY = 0;            /* Initialization needed only to stop  
                                  * compiler warnings. */  
     Tcl_Interp *interp;  
   
     if (textPtr->tkwin == NULL) {  
   
         /*  
          * The widget has been deleted.  Don't do anything.  
          */  
   
         return;  
     }  
   
     interp = textPtr->interp;  
     Tcl_Preserve((ClientData) interp);  
   
     if (tkTextDebug) {  
         Tcl_SetVar2(interp, "tk_textRelayout", (char *) NULL, "",  
                 TCL_GLOBAL_ONLY);  
     }  
   
     if (textPtr->tkwin == NULL) {  
   
         /*  
          * The widget has been deleted.  Don't do anything.  
          */  
   
         goto end;  
     }  
   
     if (!Tk_IsMapped(textPtr->tkwin) || (dInfoPtr->maxX <= dInfoPtr->x)  
             || (dInfoPtr->maxY <= dInfoPtr->y)) {  
         UpdateDisplayInfo(textPtr);  
         dInfoPtr->flags &= ~REDRAW_PENDING;  
         goto doScrollbars;  
     }  
     numRedisplays++;  
     if (tkTextDebug) {  
         Tcl_SetVar2(interp, "tk_textRedraw", (char *) NULL, "",  
                 TCL_GLOBAL_ONLY);  
     }  
   
     if (textPtr->tkwin == NULL) {  
   
         /*  
          * The widget has been deleted.  Don't do anything.  
          */  
   
         goto end;  
     }  
   
     /*  
      * Choose a new current item if that is needed (this could cause  
      * event handlers to be invoked, hence the preserve/release calls  
      * and the loop, since the handlers could conceivably necessitate  
      * yet another current item calculation).  The tkwin check is because  
      * the whole window could go away in the Tcl_Release call.  
      */  
   
     while (dInfoPtr->flags & REPICK_NEEDED) {  
         Tcl_Preserve((ClientData) textPtr);  
         dInfoPtr->flags &= ~REPICK_NEEDED;  
         TkTextPickCurrent(textPtr, &textPtr->pickEvent);  
         tkwin = textPtr->tkwin;  
         Tcl_Release((ClientData) textPtr);  
         if (tkwin == NULL) {  
             goto end;  
         }  
     }  
   
     /*  
      * First recompute what's supposed to be displayed.  
      */  
   
     UpdateDisplayInfo(textPtr);  
     dInfoPtr->dLinesInvalidated = 0;  
   
     /*  
      * See if it's possible to bring some parts of the screen up-to-date  
      * by scrolling (copying from other parts of the screen).  
      */  
   
     for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) {  
         register DLine *dlPtr2;  
         int offset, height, y, oldY;  
         TkRegion damageRgn;  
   
         if ((dlPtr->oldY == -1) || (dlPtr->y == dlPtr->oldY)  
                 || ((dlPtr->oldY + dlPtr->height) > dInfoPtr->maxY)) {  
             continue;  
         }  
   
         /*  
          * This line is already drawn somewhere in the window so it only  
          * needs to be copied to its new location.  See if there's a group  
          * of lines that can all be copied together.  
          */  
   
         offset = dlPtr->y - dlPtr->oldY;  
         height = dlPtr->height;  
         y = dlPtr->y;  
         for (dlPtr2 = dlPtr->nextPtr; dlPtr2 != NULL;  
                 dlPtr2 = dlPtr2->nextPtr) {  
             if ((dlPtr2->oldY == -1)  
                     || ((dlPtr2->oldY + offset) != dlPtr2->y)  
                     || ((dlPtr2->oldY + dlPtr2->height) > dInfoPtr->maxY)) {  
                 break;  
             }  
             height += dlPtr2->height;  
         }  
   
         /*  
          * Reduce the height of the area being copied if necessary to  
          * avoid overwriting the border area.  
          */  
   
         if ((y + height) > dInfoPtr->maxY) {  
             height = dInfoPtr->maxY -y;  
         }  
         oldY = dlPtr->oldY;  
   
         /*  
          * Update the lines we are going to scroll to show that they  
          * have been copied.  
          */  
   
         while (1) {  
             dlPtr->oldY = dlPtr->y;  
             if (dlPtr->nextPtr == dlPtr2) {  
                 break;  
             }  
             dlPtr = dlPtr->nextPtr;  
         }  
   
         /*  
          * Scan through the lines following the copied ones to see if  
          * we are going to overwrite them with the copy operation.  
          * If so, mark them for redisplay.  
          */  
   
         for ( ; dlPtr2 != NULL; dlPtr2 = dlPtr2->nextPtr) {  
             if ((dlPtr2->oldY != -1)  
                     && ((dlPtr2->oldY + dlPtr2->height) > y)  
                     && (dlPtr2->oldY < (y + height))) {  
                 dlPtr2->oldY = -1;  
             }  
         }  
   
         /*  
          * Now scroll the lines.  This may generate damage which we  
          * handle by calling TextInvalidateRegion to mark the display  
          * blocks as stale.  
          */  
   
         damageRgn = TkCreateRegion();  
         if (TkScrollWindow(textPtr->tkwin, dInfoPtr->scrollGC,  
                 dInfoPtr->x, oldY,  
                 (dInfoPtr->maxX - dInfoPtr->x), height,  
                 0, y - oldY, damageRgn)) {  
             TextInvalidateRegion(textPtr, damageRgn);  
         }  
         numCopies++;  
         TkDestroyRegion(damageRgn);  
     }  
   
     /*  
      * Clear the REDRAW_PENDING flag here.  This is actually pretty  
      * tricky.  We want to wait until *after* doing the scrolling,  
      * since that could generate more areas to redraw and don't  
      * want to reschedule a redisplay for them.  On the other hand,  
      * we can't wait until after all the redisplaying, because the  
      * act of redisplaying could actually generate more redisplays  
      * (e.g. in the case of a nested window with event bindings triggered  
      * by redisplay).  
      */  
   
     dInfoPtr->flags &= ~REDRAW_PENDING;  
   
     /*  
      * Redraw the borders if that's needed.  
      */  
   
     if (dInfoPtr->flags & REDRAW_BORDERS) {  
         if (tkTextDebug) {  
             Tcl_SetVar2(interp, "tk_textRedraw", (char *) NULL, "borders",  
                     TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT);  
         }  
   
         if (textPtr->tkwin == NULL) {  
   
             /*  
              * The widget has been deleted.  Don't do anything.  
              */  
   
             goto end;  
         }  
   
         Tk_Draw3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),  
                 textPtr->border, textPtr->highlightWidth,  
                 textPtr->highlightWidth,  
                 Tk_Width(textPtr->tkwin) - 2*textPtr->highlightWidth,  
                 Tk_Height(textPtr->tkwin) - 2*textPtr->highlightWidth,  
                 textPtr->borderWidth, textPtr->relief);  
         if (textPtr->highlightWidth != 0) {  
             GC fgGC, bgGC;  
       
             bgGC = Tk_GCForColor(textPtr->highlightBgColorPtr,  
                         Tk_WindowId(textPtr->tkwin));  
             if (textPtr->flags & GOT_FOCUS) {  
                 fgGC = Tk_GCForColor(textPtr->highlightColorPtr,  
                         Tk_WindowId(textPtr->tkwin));  
                 TkpDrawHighlightBorder(textPtr->tkwin, fgGC, bgGC,  
                         textPtr->highlightWidth, Tk_WindowId(textPtr->tkwin));  
             } else {  
                 TkpDrawHighlightBorder(textPtr->tkwin, bgGC, bgGC,  
                         textPtr->highlightWidth, Tk_WindowId(textPtr->tkwin));  
             }  
         }  
         borders = textPtr->borderWidth + textPtr->highlightWidth;  
         if (textPtr->padY > 0) {  
             Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),  
                     textPtr->border, borders, borders,  
                     Tk_Width(textPtr->tkwin) - 2*borders, textPtr->padY,  
                     0, TK_RELIEF_FLAT);  
             Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),  
                     textPtr->border, borders,  
                     Tk_Height(textPtr->tkwin) - borders - textPtr->padY,  
                     Tk_Width(textPtr->tkwin) - 2*borders,  
                     textPtr->padY, 0, TK_RELIEF_FLAT);  
         }  
         if (textPtr->padX > 0) {  
             Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),  
                     textPtr->border, borders, borders + textPtr->padY,  
                     textPtr->padX,  
                     Tk_Height(textPtr->tkwin) - 2*borders -2*textPtr->padY,  
                     0, TK_RELIEF_FLAT);  
             Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),  
                     textPtr->border,  
                     Tk_Width(textPtr->tkwin) - borders - textPtr->padX,  
                     borders + textPtr->padY, textPtr->padX,  
                     Tk_Height(textPtr->tkwin) - 2*borders -2*textPtr->padY,  
                     0, TK_RELIEF_FLAT);  
         }  
         dInfoPtr->flags &= ~REDRAW_BORDERS;  
     }  
   
     /*  
      * Now we have to redraw the lines that couldn't be updated by  
      * scrolling.  First, compute the height of the largest line and  
      * allocate an off-screen pixmap to use for double-buffered  
      * displays.  
      */  
   
     maxHeight = -1;  
     for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;  
             dlPtr = dlPtr->nextPtr) {  
         if ((dlPtr->height > maxHeight) && (dlPtr->oldY != dlPtr->y)) {  
             maxHeight = dlPtr->height;  
         }  
         bottomY = dlPtr->y + dlPtr->height;  
     }  
     if (maxHeight > dInfoPtr->maxY) {  
         maxHeight = dInfoPtr->maxY;  
     }  
     if (maxHeight > 0) {  
         pixmap = Tk_GetPixmap(Tk_Display(textPtr->tkwin),  
                 Tk_WindowId(textPtr->tkwin), Tk_Width(textPtr->tkwin),  
                 maxHeight, Tk_Depth(textPtr->tkwin));  
         for (prevPtr = NULL, dlPtr = textPtr->dInfoPtr->dLinePtr;  
                 (dlPtr != NULL) && (dlPtr->y < dInfoPtr->maxY);  
                 prevPtr = dlPtr, dlPtr = dlPtr->nextPtr) {  
             if (dlPtr->chunkPtr == NULL) continue;  
             if (dlPtr->oldY != dlPtr->y) {  
                 if (tkTextDebug) {  
                     char string[TK_POS_CHARS];  
                     TkTextPrintIndex(&dlPtr->index, string);  
                     Tcl_SetVar2(textPtr->interp, "tk_textRedraw",  
                             (char *) NULL, string,  
                             TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT);  
                 }  
                 DisplayDLine(textPtr, dlPtr, prevPtr, pixmap);  
                 if (dInfoPtr->dLinesInvalidated) {  
                     Tk_FreePixmap(Tk_Display(textPtr->tkwin), pixmap);  
                     return;  
                 }  
                 dlPtr->oldY = dlPtr->y;  
                 dlPtr->flags &= ~NEW_LAYOUT;  
             }  
             /*prevPtr = dlPtr;*/  
         }  
         Tk_FreePixmap(Tk_Display(textPtr->tkwin), pixmap);  
     }  
   
     /*  
      * See if we need to refresh the part of the window below the  
      * last line of text (if there is any such area).  Refresh the  
      * padding area on the left too, since the insertion cursor might  
      * have been displayed there previously).  
      */  
   
     if (dInfoPtr->topOfEof > dInfoPtr->maxY) {  
         dInfoPtr->topOfEof = dInfoPtr->maxY;  
     }  
     if (bottomY < dInfoPtr->topOfEof) {  
         if (tkTextDebug) {  
             Tcl_SetVar2(textPtr->interp, "tk_textRedraw",  
                     (char *) NULL, "eof",  
                     TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT);  
         }  
   
         if (textPtr->tkwin == NULL) {  
   
             /*  
              * The widget has been deleted.  Don't do anything.  
              */  
   
             goto end;  
         }  
   
         Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),  
                 textPtr->border, dInfoPtr->x - textPtr->padX, bottomY,  
                 dInfoPtr->maxX - (dInfoPtr->x - textPtr->padX),  
                 dInfoPtr->topOfEof-bottomY, 0, TK_RELIEF_FLAT);  
     }  
     dInfoPtr->topOfEof = bottomY;  
   
     doScrollbars:  
   
     /*  
      * Update the vertical scrollbar, if there is one.  Note:  it's  
      * important to clear REDRAW_PENDING here, just in case the  
      * scroll procedure does something that requires redisplay.  
      */  
       
     if (textPtr->flags & UPDATE_SCROLLBARS) {  
         textPtr->flags &= ~UPDATE_SCROLLBARS;  
         if (textPtr->yScrollCmd != NULL) {  
             GetYView(textPtr->interp, textPtr, 1);  
         }  
   
         if (textPtr->tkwin == NULL) {  
   
             /*  
              * The widget has been deleted.  Don't do anything.  
              */  
   
             goto end;  
         }  
   
         /*  
          * Update the horizontal scrollbar, if any.  
          */  
   
         if (textPtr->xScrollCmd != NULL) {  
             GetXView(textPtr->interp, textPtr, 1);  
         }  
     }  
   
 end:  
     Tcl_Release((ClientData) interp);  
 }  
   
 /*  
  *----------------------------------------------------------------------  
  *  
  * TkTextEventuallyRepick --  
  *  
  *      This procedure is invoked whenever something happens that  
  *      could change the current character or the tags associated  
  *      with it.  
  *  
  * Results:  
  *      None.  
  *  
  * Side effects:  
  *      A repick is scheduled as an idle handler.  
  *  
  *----------------------------------------------------------------------  
  */  
   
         /* ARGSUSED */  
 void  
 TkTextEventuallyRepick(textPtr)  
     TkText *textPtr;            /* Widget record for text widget. */  
 {  
     TextDInfo *dInfoPtr = textPtr->dInfoPtr;  
   
     dInfoPtr->flags |= REPICK_NEEDED;  
     if (!(dInfoPtr->flags & REDRAW_PENDING)) {  
         dInfoPtr->flags |= REDRAW_PENDING;  
         Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);  
     }  
 }  
   
 /*  
  *----------------------------------------------------------------------  
  *  
  * TkTextRedrawRegion --  
  *  
  *      This procedure is invoked to schedule a redisplay for a given  
  *      region of a text widget.  The redisplay itself may not occur  
  *      immediately:  it's scheduled as a when-idle handler.  
  *  
  * Results:  
  *      None.  
  *  
  * Side effects:  
  *      Information will eventually be redrawn on the screen.  
  *  
  *----------------------------------------------------------------------  
  */  
   
         /* ARGSUSED */  
 void  
 TkTextRedrawRegion(textPtr, x, y, width, height)  
     TkText *textPtr;            /* Widget record for text widget. */  
     int x, y;                   /* Coordinates of upper-left corner of area  
                                  * to be redrawn, in pixels relative to  
                                  * textPtr's window. */  
     int width, height;          /* Width and height of area to be redrawn. */  
 {  
     TextDInfo *dInfoPtr = textPtr->dInfoPtr;  
     TkRegion damageRgn = TkCreateRegion();  
     XRectangle rect;  
   
     rect.x = x;  
     rect.y = y;  
     rect.width = width;  
     rect.height = height;  
     TkUnionRectWithRegion(&rect, damageRgn, damageRgn);  
   
     TextInvalidateRegion(textPtr, damageRgn);  
   
     if (!(dInfoPtr->flags & REDRAW_PENDING)) {  
         dInfoPtr->flags |= REDRAW_PENDING;  
         Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);  
     }  
     TkDestroyRegion(damageRgn);  
 }  
   
 /*  
  *----------------------------------------------------------------------  
  *  
  * TextInvalidateRegion --  
  *  
  *      Mark a region of text as invalid.  
  *  
  * Results:  
  *      None.  
  *  
  * Side effects:  
  *      Updates the display information for the text widget.  
  *  
  *----------------------------------------------------------------------  
  */  
   
 static void  
 TextInvalidateRegion(textPtr, region)  
     TkText *textPtr;            /* Widget record for text widget. */  
     TkRegion region;            /* Region of area to redraw. */  
 {  
     register DLine *dlPtr;  
     TextDInfo *dInfoPtr = textPtr->dInfoPtr;  
     int maxY, inset;  
     XRectangle rect;  
   
     /*  
      * Find all lines that overlap the given region and mark them for  
      * redisplay.  
      */  
   
     TkClipBox(region, &rect);  
     maxY = rect.y + rect.height;  
     for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;  
             dlPtr = dlPtr->nextPtr) {  
         if ((dlPtr->oldY != -1) && (TkRectInRegion(region, rect.x, dlPtr->y,  
                 rect.width, (unsigned int) dlPtr->height) != RectangleOut)) {  
             dlPtr->oldY = -1;  
         }  
     }  
     if (dInfoPtr->topOfEof < maxY) {  
         dInfoPtr->topOfEof = maxY;  
     }  
   
     /*  
      * Schedule the redisplay operation if there isn't one already  
      * scheduled.  
      */  
   
     inset = textPtr->borderWidth + textPtr->highlightWidth;  
     if ((rect.x < (inset + textPtr->padX))  
             || (rect.y < (inset + textPtr->padY))  
             || ((int) (rect.x + rect.width) > (Tk_Width(textPtr->tkwin)  
                     - inset - textPtr->padX))  
             || (maxY > (Tk_Height(textPtr->tkwin) - inset - textPtr->padY))) {  
         dInfoPtr->flags |= REDRAW_BORDERS;  
     }  
 }  
   
 /*  
  *----------------------------------------------------------------------  
  *  
  * TkTextChanged --  
  *  
  *      This procedure is invoked when info in a text widget is about  
  *      to be modified in a way that changes how it is displayed (e.g.  
  *      characters were inserted or deleted, or tag information was  
  *      changed).  This procedure must be called *before* a change is  
  *      made, so that indexes in the display information are still  
  *      valid.  
  *  
  * Results:  
  *      None.  
  *  
  * Side effects:  
  *      The range of character between index1Ptr (inclusive) and  
  *      index2Ptr (exclusive) will be redisplayed at some point in the  
  *      future (the actual redisplay is scheduled as a when-idle handler).  
  *  
  *----------------------------------------------------------------------  
  */  
   
 void  
 TkTextChanged(textPtr, index1Ptr, index2Ptr)  
     TkText *textPtr;            /* Widget record for text widget. */  
     TkTextIndex *index1Ptr;     /* Index of first character to redisplay. */  
     TkTextIndex *index2Ptr;     /* Index of character just after last one  
                                  * to redisplay. */  
 {  
     TextDInfo *dInfoPtr = textPtr->dInfoPtr;  
     DLine *firstPtr, *lastPtr;  
     TkTextIndex rounded;  
   
     /*  
      * Schedule both a redisplay and a recomputation of display information.  
      * It's done here rather than the end of the procedure for two reasons:  
      *  
      * 1. If there are no display lines to update we'll want to return  
      *    immediately, well before the end of the procedure.  
      * 2. It's important to arrange for the redisplay BEFORE calling  
      *    FreeDLines.  The reason for this is subtle and has to do with  
      *    embedded windows.  The chunk delete procedure for an embedded  
      *    window will schedule an idle handler to unmap the window.  
      *    However, we want the idle handler for redisplay to be called  
      *    first, so that it can put the embedded window back on the screen  
      *    again (if appropriate).  This will prevent the window from ever  
      *    being unmapped, and thereby avoid flashing.  
      */  
   
     if (!(dInfoPtr->flags & REDRAW_PENDING)) {  
         Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);  
     }  
     dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;  
   
     /*  
      * Find the DLines corresponding to index1Ptr and index2Ptr.  There  
      * is one tricky thing here, which is that we have to relayout in  
      * units of whole text lines:  round index1Ptr back to the beginning  
      * of its text line, and include all the display lines after index2,  
      * up to the end of its text line.  This is necessary because the  
      * indices stored in the display lines will no longer be valid.  It's  
      * also needed because any edit could change the way lines wrap.  
      */  
   
     rounded = *index1Ptr;  
     rounded.byteIndex = 0;  
     firstPtr = FindDLine(dInfoPtr->dLinePtr, &rounded);  
     if (firstPtr == NULL) {  
         return;  
     }  
     lastPtr = FindDLine(dInfoPtr->dLinePtr, index2Ptr);  
     while ((lastPtr != NULL)  
             && (lastPtr->index.linePtr == index2Ptr->linePtr)) {  
         lastPtr = lastPtr->nextPtr;  
     }  
   
     /*  
      * Delete all the DLines from firstPtr up to but not including lastPtr.  
      */  
   
     FreeDLines(textPtr, firstPtr, lastPtr, 1);  
 }  
   
 /*  
  *----------------------------------------------------------------------  
  *  
  * TkTextRedrawTag --  
  *  
  *      This procedure is invoked to request a redraw of all characters  
  *      in a given range that have a particular tag on or off.  It's  
  *      called, for example, when tag options change.  
  *  
  * Results:  
  *      None.  
  *  
  * Side effects:  
  *      Information on the screen may be redrawn, and the layout of  
  *      the screen may change.  
  *  
  *----------------------------------------------------------------------  
  */  
   
 void  
 TkTextRedrawTag(textPtr, index1Ptr, index2Ptr, tagPtr, withTag)  
     TkText *textPtr;            /* Widget record for text widget. */  
     TkTextIndex *index1Ptr;     /* First character in range to consider  
                                  * for redisplay.  NULL means start at  
                                  * beginning of text. */  
     TkTextIndex *index2Ptr;     /* Character just after last one to consider  
                                  * for redisplay.  NULL means process all  
                                  * the characters in the text. */  
     TkTextTag *tagPtr;          /* Information about tag. */  
     int withTag;                /* 1 means redraw characters that have the  
                                  * tag, 0 means redraw those without. */  
 {  
     register DLine *dlPtr;  
     DLine *endPtr;  
     int tagOn;  
     TkTextSearch search;  
     TextDInfo *dInfoPtr = textPtr->dInfoPtr;  
     TkTextIndex *curIndexPtr;  
     TkTextIndex endOfText, *endIndexPtr;  
   
     /*  
      * Round up the starting position if it's before the first line  
      * visible on the screen (we only care about what's on the screen).  
      */  
   
     dlPtr = dInfoPtr->dLinePtr;  
     if (dlPtr == NULL) {  
         return;  
     }  
     if ((index1Ptr == NULL) || (TkTextIndexCmp(&dlPtr->index, index1Ptr) > 0)) {  
         index1Ptr = &dlPtr->index;  
     }  
   
     /*  
      * Set the stopping position if it wasn't specified.  
      */  
   
     if (index2Ptr == NULL) {  
         index2Ptr = TkTextMakeByteIndex(textPtr->tree,  
                 TkBTreeNumLines(textPtr->tree), 0, &endOfText);  
     }  
   
     /*  
      * Initialize a search through all transitions on the tag, starting  
      * with the first transition where the tag's current state is different  
      * from what it will eventually be.  
      */  
   
     TkBTreeStartSearch(index1Ptr, index2Ptr, tagPtr, &search);  
     /*  
      * Make our own curIndex because at this point search.curIndex  
      * may not equal index1Ptr->curIndex in the case the first tag toggle  
      * comes after index1Ptr (See the use of FindTagStart in TkBTreeStartSearch)  
      */  
     curIndexPtr = index1Ptr;  
     tagOn = TkBTreeCharTagged(index1Ptr, tagPtr);  
     if (tagOn != withTag) {  
         if (!TkBTreeNextTag(&search)) {  
             return;  
         }  
         curIndexPtr = &search.curIndex;  
     }  
   
     /*  
      * Schedule a redisplay and layout recalculation if they aren't  
      * already pending.  This has to be done before calling FreeDLines,  
      * for the reason given in TkTextChanged.  
      */  
   
     if (!(dInfoPtr->flags & REDRAW_PENDING)) {  
         Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);  
     }  
     dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;  
   
     /*  
      * Each loop through the loop below is for one range of characters  
      * where the tag's current state is different than its eventual  
      * state.  At the top of the loop, search contains information about  
      * the first character in the range.  
      */  
   
     while (1) {  
         /*  
          * Find the first DLine structure in the range.  Note: if the  
          * desired character isn't the first in its text line, then look  
          * for the character just before it instead.  This is needed to  
          * handle the case where the first character of a wrapped  
          * display line just got smaller, so that it now fits on the  
          * line before:  need to relayout the line containing the  
          * previous character.  
          */  
   
         if (curIndexPtr->byteIndex == 0) {  
             dlPtr = FindDLine(dlPtr, curIndexPtr);  
         } else {  
             TkTextIndex tmp;  
   
             tmp = *curIndexPtr;  
             tmp.byteIndex -= 1;  
             dlPtr = FindDLine(dlPtr, &tmp);  
         }  
         if (dlPtr == NULL) {  
             break;  
         }  
   
         /*  
          * Find the first DLine structure that's past the end of the range.  
          */  
   
         if (!TkBTreeNextTag(&search)) {  
             endIndexPtr = index2Ptr;  
         } else {  
             curIndexPtr = &search.curIndex;  
             endIndexPtr = curIndexPtr;  
         }  
         endPtr = FindDLine(dlPtr, endIndexPtr);  
         if ((endPtr != NULL) && (endPtr->index.linePtr == endIndexPtr->linePtr)  
                 && (endPtr->index.byteIndex < endIndexPtr->byteIndex)) {  
             endPtr = endPtr->nextPtr;  
         }  
   
         /*  
          * Delete all of the display lines in the range, so that they'll  
          * be re-layed out and redrawn.  
          */  
   
         FreeDLines(textPtr, dlPtr, endPtr, 1);  
         dlPtr = endPtr;  
   
         /*  
          * Find the first text line in the next range.  
          */  
   
         if (!TkBTreeNextTag(&search)) {  
             break;  
         }  
     }  
 }  
   
 /*  
  *----------------------------------------------------------------------  
  *  
  * TkTextRelayoutWindow --  
  *  
  *      This procedure is called when something has happened that  
  *      invalidates the whole layout of characters on the screen, such  
  *      as a change in a configuration option for the overall text  
  *      widget or a change in the window size.  It causes all display  
  *      information to be recomputed and the window to be redrawn.  
  *  
  * Results:  
  *      None.  
  *  
  * Side effects:  
  *      All the display information will be recomputed for the window  
  *      and the window will be redrawn.  
  *  
  *----------------------------------------------------------------------  
  */  
   
 void  
 TkTextRelayoutWindow(textPtr)  
     TkText *textPtr;            /* Widget record for text widget. */  
 {  
     TextDInfo *dInfoPtr = textPtr->dInfoPtr;  
     GC new;  
     XGCValues gcValues;  
   
     /*  
      * Schedule the window redisplay.  See TkTextChanged for the  
      * reason why this has to be done before any calls to FreeDLines.  
      */  
   
     if (!(dInfoPtr->flags & REDRAW_PENDING)) {  
         Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);  
     }  
     dInfoPtr->flags |= REDRAW_PENDING|REDRAW_BORDERS|DINFO_OUT_OF_DATE  
             |REPICK_NEEDED;  
   
     /*  
      * (Re-)create the graphics context for drawing the traversal  
      * highlight.  
      */  
   
     gcValues.graphics_exposures = False;  
     new = Tk_GetGC(textPtr->tkwin, GCGraphicsExposures, &gcValues);  
     if (dInfoPtr->copyGC != None) {  
         Tk_FreeGC(textPtr->display, dInfoPtr->copyGC);  
     }  
     dInfoPtr->copyGC = new;  
   
     /*  
      * Throw away all the current layout information.  
      */  
   
     FreeDLines(textPtr, dInfoPtr->dLinePtr, (DLine *) NULL, 1);  
     dInfoPtr->dLinePtr = NULL;  
   
     /*  
      * Recompute some overall things for the layout.  Even if the  
      * window gets very small, pretend that there's at least one  
      * pixel of drawing space in it.  
      */  
   
     if (textPtr->highlightWidth < 0) {  
         textPtr->highlightWidth = 0;  
     }  
     dInfoPtr->x = textPtr->highlightWidth + textPtr->borderWidth  
             + textPtr->padX;  
     dInfoPtr->y = textPtr->highlightWidth + textPtr->borderWidth  
             + textPtr->padY;  
     dInfoPtr->maxX = Tk_Width(textPtr->tkwin) - textPtr->highlightWidth  
             - textPtr->borderWidth - textPtr->padX;  
     if (dInfoPtr->maxX <= dInfoPtr->x) {  
         dInfoPtr->maxX = dInfoPtr->x + 1;  
     }  
     dInfoPtr->maxY = Tk_Height(textPtr->tkwin) - textPtr->highlightWidth  
             - textPtr->borderWidth - textPtr->padY;  
     if (dInfoPtr->maxY <= dInfoPtr->y) {  
         dInfoPtr->maxY = dInfoPtr->y + 1;  
     }  
     dInfoPtr->topOfEof = dInfoPtr->maxY;  
   
     /*  
      * If the upper-left character isn't the first in a line, recompute  
      * it.  This is necessary because a change in the window's size  
      * or options could change the way lines wrap.  
      */  
   
     if (textPtr->topIndex.byteIndex != 0) {  
         MeasureUp(textPtr, &textPtr->topIndex, 0, &textPtr->topIndex);  
     }  
   
     /*  
      * Invalidate cached scrollbar positions, so that scrollbars  
      * sliders will be udpated.  
      */  
   
     dInfoPtr->xScrollFirst = dInfoPtr->xScrollLast = -1;  
     dInfoPtr->yScrollFirst = dInfoPtr->yScrollLast = -1;  
 }  
   
 /*  
  *----------------------------------------------------------------------  
  *  
  * TkTextSetYView --  
  *  
  *      This procedure is called to specify what lines are to be  
  *      displayed in a text widget.  
  *  
  * Results:  
  *      None.  
  *  
  * Side effects:  
  *      The display will (eventually) be updated so that the position  
  *      given by "indexPtr" is visible on the screen at the position  
  *      determined by "pickPlace".  
  *  
  *----------------------------------------------------------------------  
  */  
   
 void  
 TkTextSetYView(textPtr, indexPtr, pickPlace)  
     TkText *textPtr;            /* Widget record for text widget. */  
     TkTextIndex *indexPtr;      /* Position that is to appear somewhere  
                                  * in the view. */  
     int pickPlace;              /* 0 means topLine must appear at top of  
                                  * screen.  1 means we get to pick where it  
                                  * appears:  minimize screen motion or else  
                                  * display line at center of screen. */  
 {  
     TextDInfo *dInfoPtr = textPtr->dInfoPtr;  
     register DLine *dlPtr;  
     int bottomY, close, lineIndex;  
     TkTextIndex tmpIndex, rounded;  
     Tk_FontMetrics fm;  
   
     /*  
      * If the specified position is the extra line at the end of the  
      * text, round it back to the last real line.  
      */  
   
     lineIndex = TkBTreeLineIndex(indexPtr->linePtr);  
     if (lineIndex == TkBTreeNumLines(indexPtr->tree)) {  
         TkTextIndexBackChars(indexPtr, 1, &rounded);  
         indexPtr = &rounded;  
     }  
   
     if (!pickPlace) {  
         /*  
          * The specified position must go at the top of the screen.  
          * Just leave all the DLine's alone: we may be able to reuse  
          * some of the information that's currently on the screen  
          * without redisplaying it all.  
          */  
   
         if (indexPtr->byteIndex == 0) {  
             textPtr->topIndex = *indexPtr;  
         } else {  
             MeasureUp(textPtr, indexPtr, 0, &textPtr->topIndex);  
         }  
         goto scheduleUpdate;  
     }  
   
     /*  
      * We have to pick where to display the index.  First, bring  
      * the display information up to date and see if the index will be  
      * completely visible in the current screen configuration.  If so  
      * then there's nothing to do.  
      */  
   
     if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {  
         UpdateDisplayInfo(textPtr);  
     }  
     dlPtr = FindDLine(dInfoPtr->dLinePtr, indexPtr);  
     if (dlPtr != NULL) {  
         if ((dlPtr->y + dlPtr->height) > dInfoPtr->maxY) {  
             /*  
              * Part of the line hangs off the bottom of the screen;  
              * pretend the whole line is off-screen.  
              */  
   
             dlPtr = NULL;  
         } else if ((dlPtr->index.linePtr == indexPtr->linePtr)  
                 && (dlPtr->index.byteIndex <= indexPtr->byteIndex)) {  
             return;  
         }  
     }  
   
     /*  
      * The desired line isn't already on-screen.  Figure out what  
      * it means to be "close" to the top or bottom of the screen.  
      * Close means within 1/3 of the screen height or within three  
      * lines, whichever is greater.  Add one extra line also, to  
      * account for the way MeasureUp rounds.  
      */  
   
     Tk_GetFontMetrics(textPtr->tkfont, &fm);  
     bottomY = (dInfoPtr->y + dInfoPtr->maxY + fm.linespace)/2;  
     close = (dInfoPtr->maxY - dInfoPtr->y)/3;  
     if (close < 3*fm.linespace) {  
         close = 3*fm.linespace;  
     }  
     close += fm.linespace;  
     if (dlPtr != NULL) {  
         /*  
          * The desired line is above the top of screen.  If it is  
          * "close" to the top of the window then make it the top  
          * line on the screen.  
          */  
   
         MeasureUp(textPtr, &textPtr->topIndex, close, &tmpIndex);  
         if (TkTextIndexCmp(&tmpIndex, indexPtr) <= 0) {  
             MeasureUp(textPtr, indexPtr, 0, &textPtr->topIndex);  
             goto scheduleUpdate;  
         }  
     } else {  
         /*  
          * The desired line is below the bottom of the screen.  If it is  
          * "close" to the bottom of the screen then position it at the  
          * bottom of the screen.  
          */  
   
         MeasureUp(textPtr, indexPtr, close, &tmpIndex);  
         if (FindDLine(dInfoPtr->dLinePtr, &tmpIndex) != NULL) {  
             bottomY = dInfoPtr->maxY - dInfoPtr->y;  
         }  
     }  
   
     /*  
      * Our job now is to arrange the display so that indexPtr appears  
      * as low on the screen as possible but with its bottom no lower  
      * than bottomY.  BottomY is the bottom of the window if the  
      * desired line is just below the current screen, otherwise it  
      * is a half-line lower than the center of the window.  
      */  
   
     MeasureUp(textPtr, indexPtr, bottomY, &textPtr->topIndex);  
   
     scheduleUpdate:  
     if (!(dInfoPtr->flags & REDRAW_PENDING)) {  
         Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);  
     }  
     dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;  
 }  
   
 /*  
  *--------------------------------------------------------------  
  *  
  * MeasureUp --  
  *  
  *      Given one index, find the index of the first character  
  *      on the highest display line that would be displayed no more  
  *      than "distance" pixels above the given index.  
  *  
  * Results:  
  *      *dstPtr is filled in with the index of the first character  
  *      on a display line.  The display line is found by measuring  
  *      up "distance" pixels above the pixel just below an imaginary  
  *      display line that contains srcPtr.  If the display line  
  *      that covers this coordinate actually extends above the  
  *      coordinate, then return the index of the next lower line  
  *      instead (i.e. the returned index will be completely visible  
  *      at or below the given y-coordinate).  
  *  
  * Side effects:  
  *      None.  
  *  
  *--------------------------------------------------------------  
  */  
   
 static void  
 MeasureUp(textPtr, srcPtr, distance, dstPtr)  
     TkText *textPtr;            /* Text widget in which to measure. */  
     TkTextIndex *srcPtr;        /* Index of character from which to start  
                                  * measuring. */  
     int distance;               /* Vertical distance in pixels measured  
                                  * from the pixel just below the lowest  
                                  * one in srcPtr's line. */  
     TkTextIndex *dstPtr;        /* Index to fill in with result. */  
 {  
     int lineNum;                /* Number of current line. */  
     int bytesToCount;           /* Maximum number of bytes to measure in  
                                  * current line. */  
     TkTextIndex bestIndex;      /* Best candidate seen so far for result. */  
     TkTextIndex index;  
     DLine *dlPtr, *lowestPtr;  
     int noBestYet;              /* 1 means bestIndex hasn't been set. */  
   
     noBestYet = 1;  
     bytesToCount = srcPtr->byteIndex + 1;  
     index.tree = srcPtr->tree;  
     for (lineNum = TkBTreeLineIndex(srcPtr->linePtr); lineNum >= 0;  
             lineNum--) {  
         /*  
          * Layout an entire text line (potentially > 1 display line).  
          * For the first line, which contains srcPtr, only layout the  
          * part up through srcPtr (bytesToCount is non-infinite to  
          * accomplish this).  Make a list of all the display lines  
          * in backwards order (the lowest DLine on the screen is first  
          * in the list).  
          */  
   
         index.linePtr = TkBTreeFindLine(srcPtr->tree, lineNum);  
         index.byteIndex = 0;  
         lowestPtr = NULL;  
         do {  
             dlPtr = LayoutDLine(textPtr, &index);  
             dlPtr->nextPtr = lowestPtr;  
             lowestPtr = dlPtr;  
             TkTextIndexForwBytes(&index, dlPtr->byteCount, &index);  
             bytesToCount -= dlPtr->byteCount;  
         } while ((bytesToCount > 0) && (index.linePtr == dlPtr->index.linePtr));  
   
         /*  
          * Scan through the display lines to see if we've covered enough  
          * vertical distance.  If so, save the starting index for the  
          * line at the desired location.  
          */  
   
         for (dlPtr = lowestPtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) {  
             distance -= dlPtr->height;  
             if (distance < 0) {  
                 *dstPtr = (noBestYet) ? dlPtr->index : bestIndex;  
                 break;  
             }  
             bestIndex = dlPtr->index;  
             noBestYet = 0;  
         }  
   
         /*  
          * Discard the display lines, then either return or prepare  
          * for the next display line to lay out.  
          */  
   
         FreeDLines(textPtr, lowestPtr, (DLine *) NULL, 0);  
         if (distance < 0) {  
             return;  
         }  
         bytesToCount = INT_MAX;         /* Consider all chars. in next line. */  
     }  
   
     /*  
      * Ran off the beginning of the text.  Return the first character  
      * in the text.  
      */  
   
     TkTextMakeByteIndex(textPtr->tree, 0, 0, dstPtr);  
 }  
   
 /*  
  *--------------------------------------------------------------  
  *  
  * TkTextSeeCmd --  
  *  
  *      This procedure is invoked to process the "see" option for  
  *      the widget command for text widgets. See the user documentation  
  *      for details on what it does.  
  *  
  * Results:  
  *      A standard Tcl result.  
  *  
  * Side effects:  
  *      See the user documentation.  
  *  
  *--------------------------------------------------------------  
  */  
   
 int  
 TkTextSeeCmd(textPtr, interp, argc, argv)  
     TkText *textPtr;            /* Information about text widget. */  
     Tcl_Interp *interp;         /* Current interpreter. */  
     int argc;                   /* Number of arguments. */  
     char **argv;                /* Argument strings.  Someone else has already  
                                  * parsed this command enough to know that  
                                  * argv[1] is "see". */  
 {  
     TextDInfo *dInfoPtr = textPtr->dInfoPtr;  
     TkTextIndex index;  
     int x, y, width, height, lineWidth, byteCount, oneThird, delta;  
     DLine *dlPtr;  
     TkTextDispChunk *chunkPtr;  
   
     if (argc != 3) {  
         Tcl_AppendResult(interp, "wrong # args: should be \"",  
                 argv[0], " see index\"", (char *) NULL);  
         return TCL_ERROR;  
     }  
     if (TkTextGetIndex(interp, textPtr, argv[2], &index) != TCL_OK) {  
         return TCL_ERROR;  
     }  
   
     /*  
      * If the specified position is the extra line at the end of the  
      * text, round it back to the last real line.  
      */  
   
     if (TkBTreeLineIndex(index.linePtr) == TkBTreeNumLines(index.tree)) {  
         TkTextIndexBackChars(&index, 1, &index);  
     }  
   
     /*  
      * First get the desired position into the vertical range of the window.  
      */  
   
     TkTextSetYView(textPtr, &index, 1);  
   
     /*  
      * Now make sure that the character is in view horizontally.  
      */  
   
     if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {  
         UpdateDisplayInfo(textPtr);  
     }  
     lineWidth = dInfoPtr->maxX - dInfoPtr->x;  
     if (dInfoPtr->maxLength < lineWidth) {  
         return TCL_OK;  
     }  
   
     /*  
      * Find the chunk that contains the desired index.  
      */  
   
     dlPtr = FindDLine(dInfoPtr->dLinePtr, &index);  
     byteCount = index.byteIndex - dlPtr->index.byteIndex;  
     for (chunkPtr = dlPtr->chunkPtr; chunkPtr!=NULL ; chunkPtr = chunkPtr->nextPtr) {  
         if (byteCount < chunkPtr->numBytes) {  
             break;  
         }  
         byteCount -= chunkPtr->numBytes;  
     }  
   
     /*  
      * Call a chunk-specific procedure to find the horizontal range of  
      * the character within the chunk.  
      */  
   
     if (chunkPtr!=NULL) {       /* chunkPtr==NULL iff trying to see in elided region */  
     (*chunkPtr->bboxProc)(chunkPtr, byteCount, dlPtr->y + dlPtr->spaceAbove,  
             dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,  
             dlPtr->baseline - dlPtr->spaceAbove, &x, &y, &width,  
             &height);  
     delta = x - dInfoPtr->curPixelOffset;  
     oneThird = lineWidth/3;  
     if (delta < 0) {  
         if (delta < -oneThird) {  
             dInfoPtr->newByteOffset = (x - lineWidth/2)/textPtr->charWidth;  
         } else {  
             dInfoPtr->newByteOffset -= ((-delta) + textPtr->charWidth - 1)  
                 / textPtr->charWidth;  
         }  
     } else {  
         delta -= (lineWidth - width);  
         if (delta > 0) {  
             if (delta > oneThird) {  
                 dInfoPtr->newByteOffset = (x - lineWidth/2)/textPtr->charWidth;  
             } else {  
                 dInfoPtr->newByteOffset += (delta + textPtr->charWidth - 1)  
                     / textPtr->charWidth;  
             }  
         } else {  
             return TCL_OK;  
         }  
     }}  
     dInfoPtr->flags |= DINFO_OUT_OF_DATE;  
     if (!(dInfoPtr->flags & REDRAW_PENDING)) {  
         dInfoPtr->flags |= REDRAW_PENDING;  
         Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);  
     }  
     return TCL_OK;  
 }  
   
 /*  
  *--------------------------------------------------------------  
  *  
  * TkTextXviewCmd --  
  *  
  *      This procedure is invoked to process the "xview" option for  
  *      the widget command for text widgets. See the user documentation  
  *      for details on what it does.  
  *  
  * Results:  
  *      A standard Tcl result.  
  *  
  * Side effects:  
  *      See the user documentation.  
  *  
  *--------------------------------------------------------------  
  */  
   
 int  
 TkTextXviewCmd(textPtr, interp, argc, argv)  
     TkText *textPtr;            /* Information about text widget. */  
     Tcl_Interp *interp;         /* Current interpreter. */  
     int argc;                   /* Number of arguments. */  
     char **argv;                /* Argument strings.  Someone else has already  
                                  * parsed this command enough to know that  
                                  * argv[1] is "xview". */  
 {  
     TextDInfo *dInfoPtr = textPtr->dInfoPtr;  
     int type, charsPerPage, count, newOffset;  
     double fraction;  
   
     if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {  
         UpdateDisplayInfo(textPtr);  
     }  
   
     if (argc == 2) {  
         GetXView(interp, textPtr, 0);  
         return TCL_OK;  
     }  
   
     newOffset = dInfoPtr->newByteOffset;  
     type = Tk_GetScrollInfo(interp, argc, argv, &fraction, &count);  
     switch (type) {  
         case TK_SCROLL_ERROR:  
             return TCL_ERROR;  
         case TK_SCROLL_MOVETO:  
             if (fraction > 1.0) {  
                 fraction = 1.0;  
             }  
             if (fraction < 0) {  
                 fraction = 0;  
             }  
             newOffset = (int) (((fraction * dInfoPtr->maxLength) / textPtr->charWidth)  
                     + 0.5);  
             break;  
         case TK_SCROLL_PAGES:  
             charsPerPage = ((dInfoPtr->maxX - dInfoPtr->x) / textPtr->charWidth)  
                     - 2;  
             if (charsPerPage < 1) {  
                 charsPerPage = 1;  
             }  
             newOffset += charsPerPage * count;  
             break;  
         case TK_SCROLL_UNITS:  
             newOffset += count;  
             break;  
     }  
   
     dInfoPtr->newByteOffset = newOffset;  
     dInfoPtr->flags |= DINFO_OUT_OF_DATE;  
     if (!(dInfoPtr->flags & REDRAW_PENDING)) {  
         dInfoPtr->flags |= REDRAW_PENDING;  
         Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);  
     }  
     return TCL_OK;  
 }  
   
 /*  
  *----------------------------------------------------------------------  
  *  
  * ScrollByLines --  
  *  
  *      This procedure is called to scroll a text widget up or down  
  *      by a given number of lines.  
  *  
  * Results:  
  *      None.  
  *  
  * Side effects:  
  *      The view in textPtr's window changes to reflect the value  
  *      of "offset".  
  *  
  *----------------------------------------------------------------------  
  */  
   
 static void  
 ScrollByLines(textPtr, offset)  
     TkText *textPtr;            /* Widget to scroll. */  
     int offset;                 /* Amount by which to scroll, in *screen*  
                                  * lines.  Positive means that information  
                                  * later in text becomes visible, negative  
                                  * means that information earlier in the  
                                  * text becomes visible. */  
 {  
     int i, bytesToCount, lineNum;  
     TkTextIndex new, index;  
     TkTextLine *lastLinePtr;  
     TextDInfo *dInfoPtr = textPtr->dInfoPtr;  
     DLine *dlPtr, *lowestPtr;  
   
     if (offset < 0) {  
         /*  
          * Must scroll up (to show earlier information in the text).  
          * The code below is similar to that in MeasureUp, except that  
          * it counts lines instead of pixels.  
          */  
   
         bytesToCount = textPtr->topIndex.byteIndex + 1;  
         index.tree = textPtr->tree;  
         offset--;                       /* Skip line containing topIndex. */  
         for (lineNum = TkBTreeLineIndex(textPtr->topIndex.linePtr);  
                 lineNum >= 0; lineNum--) {  
             index.linePtr = TkBTreeFindLine(textPtr->tree, lineNum);  
             index.byteIndex = 0;  
             lowestPtr = NULL;  
             do {  
                 dlPtr = LayoutDLine(textPtr, &index);  
                 dlPtr->nextPtr = lowestPtr;  
                 lowestPtr = dlPtr;  
                 TkTextIndexForwBytes(&index, dlPtr->byteCount, &index);  
                 bytesToCount -= dlPtr->byteCount;  
             } while ((bytesToCount > 0)  
                     && (index.linePtr == dlPtr->index.linePtr));  
   
             for (dlPtr = lowestPtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) {  
                 offset++;  
                 if (offset == 0) {  
                     textPtr->topIndex = dlPtr->index;  
                     break;  
                 }  
             }  
   
             /*  
              * Discard the display lines, then either return or prepare  
              * for the next display line to lay out.  
              */  
       
             FreeDLines(textPtr, lowestPtr, (DLine *) NULL, 0);  
             if (offset >= 0) {  
                 goto scheduleUpdate;  
             }  
             bytesToCount = INT_MAX;  
         }  
       
         /*  
          * Ran off the beginning of the text.  Return the first character  
          * in the text.  
          */  
   
         TkTextMakeByteIndex(textPtr->tree, 0, 0, &textPtr->topIndex);  
     } else {  
         /*  
          * Scrolling down, to show later information in the text.  
          * Just count lines from the current top of the window.  
          */  
   
         lastLinePtr = TkBTreeFindLine(textPtr->tree,  
                 TkBTreeNumLines(textPtr->tree));  
         for (i = 0; i < offset; i++) {  
             dlPtr = LayoutDLine(textPtr, &textPtr->topIndex);  
             if (dlPtr->length == 0 && dlPtr->height == 0) offset++;  
             dlPtr->nextPtr = NULL;  
             TkTextIndexForwBytes(&textPtr->topIndex, dlPtr->byteCount, &new);  
             FreeDLines(textPtr, dlPtr, (DLine *) NULL, 0);  
             if (new.linePtr == lastLinePtr) {  
                 break;  
             }  
             textPtr->topIndex = new;  
         }  
     }  
   
     scheduleUpdate:  
     if (!(dInfoPtr->flags & REDRAW_PENDING)) {  
         Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);  
     }  
     dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;  
 }  
   
 /*  
  *--------------------------------------------------------------  
  *  
  * TkTextYviewCmd --  
  *  
  *      This procedure is invoked to process the "yview" option for  
  *      the widget command for text widgets. See the user documentation  
  *      for details on what it does.  
  *  
  * Results:  
  *      A standard Tcl result.  
  *  
  * Side effects:  
  *      See the user documentation.  
  *  
  *--------------------------------------------------------------  
  */  
   
 int  
 TkTextYviewCmd(textPtr, interp, argc, argv)  
     TkText *textPtr;            /* Information about text widget. */  
     Tcl_Interp *interp;         /* Current interpreter. */  
     int argc;                   /* Number of arguments. */  
     char **argv;                /* Argument strings.  Someone else has already  
                                  * parsed this command enough to know that  
                                  * argv[1] is "yview". */  
 {  
     TextDInfo *dInfoPtr = textPtr->dInfoPtr;  
     int pickPlace, lineNum, type, bytesInLine;  
     Tk_FontMetrics fm;  
     int pixels, count;  
     size_t switchLength;  
     double fraction;  
     TkTextIndex index, new;  
     TkTextLine *lastLinePtr;  
     DLine *dlPtr;  
   
     if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {  
         UpdateDisplayInfo(textPtr);  
     }  
   
     if (argc == 2) {  
         GetYView(interp, textPtr, 0);  
         return TCL_OK;  
     }  
   
     /*  
      * Next, handle the old syntax: "pathName yview ?-pickplace? where"  
      */  
   
     pickPlace = 0;  
     if (argv[2][0] == '-') {  
         switchLength = strlen(argv[2]);  
         if ((switchLength >= 2)  
                 && (strncmp(argv[2], "-pickplace", switchLength) == 0)) {  
             pickPlace = 1;  
             if (argc != 4) {  
                 Tcl_AppendResult(interp, "wrong # args: should be \"",  
                         argv[0], " yview -pickplace lineNum|index\"",  
                         (char *) NULL);  
                 return TCL_ERROR;  
             }  
         }  
     }  
     if ((argc == 3) || pickPlace) {  
         if (Tcl_GetInt(interp, argv[2+pickPlace], &lineNum) == TCL_OK) {  
             TkTextMakeByteIndex(textPtr->tree, lineNum, 0, &index);  
             TkTextSetYView(textPtr, &index, 0);  
             return TCL_OK;  
         }  
       
         /*  
          * The argument must be a regular text index.  
          */  
       
         Tcl_ResetResult(interp);  
         if (TkTextGetIndex(interp, textPtr, argv[2+pickPlace],  
                 &index) != TCL_OK) {  
             return TCL_ERROR;  
         }  
         TkTextSetYView(textPtr, &index, pickPlace);  
         return TCL_OK;  
     }  
   
     /*  
      * New syntax: dispatch based on argv[2].  
      */  
   
     type = Tk_GetScrollInfo(interp, argc, argv, &fraction, &count);  
     switch (type) {  
         case TK_SCROLL_ERROR:  
             return TCL_ERROR;  
         case TK_SCROLL_MOVETO:  
             if (fraction > 1.0) {  
                 fraction = 1.0;  
             }  
             if (fraction < 0) {  
                 fraction = 0;  
             }  
             fraction *= TkBTreeNumLines(textPtr->tree);  
             lineNum = (int) fraction;  
             TkTextMakeByteIndex(textPtr->tree, lineNum, 0, &index);  
             bytesInLine = TkBTreeBytesInLine(index.linePtr);  
             index.byteIndex = (int)((bytesInLine * (fraction-lineNum)) + 0.5);  
             if (index.byteIndex >= bytesInLine) {  
                 TkTextMakeByteIndex(textPtr->tree, lineNum + 1, 0, &index);  
             }  
             TkTextSetYView(textPtr, &index, 0);  
             break;  
         case TK_SCROLL_PAGES:  
             /*  
              * Scroll up or down by screenfuls.  Actually, use the  
              * window height minus two lines, so that there's some  
              * overlap between adjacent pages.  
              */  
   
             Tk_GetFontMetrics(textPtr->tkfont, &fm);  
             if (count < 0) {  
                 pixels = (dInfoPtr->maxY - 2*fm.linespace - dInfoPtr->y)*(-count)  
                         + fm.linespace;  
                 MeasureUp(textPtr, &textPtr->topIndex, pixels, &new);  
                 if (TkTextIndexCmp(&textPtr->topIndex, &new) == 0) {  
                     /*  
                      * A page of scrolling ended up being less than one line.  
                      * Scroll one line anyway.  
                      */  
   
                     count = -1;  
                     goto scrollByLines;  
                 }  
                 textPtr->topIndex = new;  
             } else {  
                 /*  
                  * Scrolling down by pages.  Layout lines starting at the  
                  * top index and count through the desired vertical distance.  
                  */  
   
                 pixels = (dInfoPtr->maxY - 2*fm.linespace - dInfoPtr->y)*count;  
                 lastLinePtr = TkBTreeFindLine(textPtr->tree,  
                         TkBTreeNumLines(textPtr->tree));  
                 do {  
                     dlPtr = LayoutDLine(textPtr, &textPtr->topIndex);  
                     dlPtr->nextPtr = NULL;  
                     TkTextIndexForwBytes(&textPtr->topIndex, dlPtr->byteCount,  
                             &new);  
                     pixels -= dlPtr->height;  
                     FreeDLines(textPtr, dlPtr, (DLine *) NULL, 0);  
                     if (new.linePtr == lastLinePtr) {  
                         break;  
                     }  
                     textPtr->topIndex = new;  
                 } while (pixels > 0);  
             }  
             if (!(dInfoPtr->flags & REDRAW_PENDING)) {  
                 Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);  
             }  
             dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;  
             break;  
         case TK_SCROLL_UNITS:  
             scrollByLines:  
             ScrollByLines(textPtr, count);  
             break;  
     }  
     return TCL_OK;  
 }  
   
 /*  
  *--------------------------------------------------------------  
  *  
  * TkTextScanCmd --  
  *  
  *      This procedure is invoked to process the "scan" option for  
  *      the widget command for text widgets. See the user documentation  
  *      for details on what it does.  
  *  
  * Results:  
  *      A standard Tcl result.  
  *  
  * Side effects:  
  *      See the user documentation.  
  *  
  *--------------------------------------------------------------  
  */  
   
 int  
 TkTextScanCmd(textPtr, interp, argc, argv)  
     register TkText *textPtr;   /* Information about text widget. */  
     Tcl_Interp *interp;         /* Current interpreter. */  
     int argc;                   /* Number of arguments. */  
     char **argv;                /* Argument strings.  Someone else has already  
                                  * parsed this command enough to know that  
                                  * argv[1] is "scan". */  
 {  
     TextDInfo *dInfoPtr = textPtr->dInfoPtr;  
     TkTextIndex index;  
     int c, x, y, totalScroll, newByte, maxByte, gain=10;  
     Tk_FontMetrics fm;  
     size_t length;  
   
     if ((argc != 5) && (argc != 6)) {  
         Tcl_AppendResult(interp, "wrong # args: should be \"",  
                 argv[0], " scan mark x y\" or \"",  
                 argv[0], " scan dragto x y ?gain?\"", (char *) NULL);  
         return TCL_ERROR;  
     }  
     if (Tcl_GetInt(interp, argv[3], &x) != TCL_OK) {  
         return TCL_ERROR;  
     }  
     if (Tcl_GetInt(interp, argv[4], &y) != TCL_OK) {  
         return TCL_ERROR;  
     }  
     if ((argc == 6) && (Tcl_GetInt(interp, argv[5], &gain) != TCL_OK))  
         return TCL_ERROR;  
     c = argv[2][0];  
     length = strlen(argv[2]);  
     if ((c == 'd') && (strncmp(argv[2], "dragto", length) == 0)) {  
         /*  
          * Amplify the difference between the current position and the  
          * mark position to compute how much the view should shift, then  
          * update the mark position to correspond to the new view.  If we  
          * run off the edge of the text, reset the mark point so that the  
          * current position continues to correspond to the edge of the  
          * window.  This means that the picture will start dragging as  
          * soon as the mouse reverses direction (without this reset, might  
          * have to slide mouse a long ways back before the picture starts  
          * moving again).  
          */  
   
         newByte = dInfoPtr->scanMarkIndex + (gain*(dInfoPtr->scanMarkX - x))  
                 / (textPtr->charWidth);  
         maxByte = 1 + (dInfoPtr->maxLength - (dInfoPtr->maxX - dInfoPtr->x)  
                 + textPtr->charWidth - 1)/textPtr->charWidth;  
         if (newByte < 0) {  
             newByte = 0;  
             dInfoPtr->scanMarkIndex = 0;  
             dInfoPtr->scanMarkX = x;  
         } else if (newByte > maxByte) {  
             newByte = maxByte;  
             dInfoPtr->scanMarkIndex = maxByte;  
             dInfoPtr->scanMarkX = x;  
         }  
         dInfoPtr->newByteOffset = newByte;  
   
         Tk_GetFontMetrics(textPtr->tkfont, &fm);  
         totalScroll = (gain*(dInfoPtr->scanMarkY - y)) / fm.linespace;  
         if (totalScroll != dInfoPtr->scanTotalScroll) {  
             index = textPtr->topIndex;  
             ScrollByLines(textPtr, totalScroll-dInfoPtr->scanTotalScroll);  
             dInfoPtr->scanTotalScroll = totalScroll;  
             if ((index.linePtr == textPtr->topIndex.linePtr) &&  
                     (index.byteIndex == textPtr->topIndex.byteIndex)) {  
                 dInfoPtr->scanTotalScroll = 0;  
                 dInfoPtr->scanMarkY = y;  
             }  
         }  
     } else if ((c == 'm') && (strncmp(argv[2], "mark", length) == 0)) {  
         dInfoPtr->scanMarkIndex = dInfoPtr->newByteOffset;  
         dInfoPtr->scanMarkX = x;  
         dInfoPtr->scanTotalScroll = 0;  
         dInfoPtr->scanMarkY = y;  
     } else {  
         Tcl_AppendResult(interp, "bad scan option \"", argv[2],  
                 "\": must be mark or dragto", (char *) NULL);  
         return TCL_ERROR;  
     }  
     dInfoPtr->flags |= DINFO_OUT_OF_DATE;  
     if (!(dInfoPtr->flags & REDRAW_PENDING)) {  
         dInfoPtr->flags |= REDRAW_PENDING;  
         Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);  
     }  
     return TCL_OK;  
 }  
   
 /*  
  *----------------------------------------------------------------------  
  *  
  * GetXView --  
  *  
  *      This procedure computes the fractions that indicate what's  
  *      visible in a text window and, optionally, evaluates a  
  *      Tcl script to report them to the text's associated scrollbar.  
  *  
  * Results:  
  *      If report is zero, then the interp's result is filled in with  
  *      two real numbers separated by a space, giving the position of  
  *      the left and right edges of the window as fractions from 0 to  
  *      1, where 0 means the left edge of the text and 1 means the right  
  *      edge.  If report is non-zero, then the interp's result isn't modified  
  *      directly, but instead a script is evaluated in interp to report  
  *      the new horizontal scroll position to the scrollbar (if the scroll  
  *      position hasn't changed then no script is invoked).  
  *  
  * Side effects:  
  *      None.  
  *  
  *----------------------------------------------------------------------  
  */  
   
 static void  
 GetXView(interp, textPtr, report)  
     Tcl_Interp *interp;                 /* If "report" is FALSE, string  
                                          * describing visible range gets  
                                          * stored in the interp's result. */  
     TkText *textPtr;                    /* Information about text widget. */  
     int report;                         /* Non-zero means report info to  
                                          * scrollbar if it has changed. */  
 {  
     TextDInfo *dInfoPtr = textPtr->dInfoPtr;  
     char buffer[TCL_DOUBLE_SPACE * 2];  
     double first, last;  
     int code;  
   
     if (dInfoPtr->maxLength > 0) {  
         first = ((double) dInfoPtr->curPixelOffset)  
                 / dInfoPtr->maxLength;  
         last = first + ((double) (dInfoPtr->maxX - dInfoPtr->x))  
                 / dInfoPtr->maxLength;  
         if (last > 1.0) {  
             last = 1.0;  
         }  
     } else {  
         first = 0;  
         last = 1.0;  
     }  
     if (!report) {  
         sprintf(buffer, "%g %g", first, last);  
         Tcl_SetResult(interp, buffer, TCL_VOLATILE);  
         return;  
     }  
     if ((first == dInfoPtr->xScrollFirst) && (last == dInfoPtr->xScrollLast)) {  
         return;  
     }  
     dInfoPtr->xScrollFirst = first;  
     dInfoPtr->xScrollLast = last;  
     sprintf(buffer, " %g %g", first, last);  
     code = Tcl_VarEval(interp, textPtr->xScrollCmd,  
             buffer, (char *) NULL);  
     if (code != TCL_OK) {  
         Tcl_AddErrorInfo(interp,  
                 "\n    (horizontal scrolling command executed by text)");  
         Tcl_BackgroundError(interp);  
     }  
 }  
   
 /*  
  *----------------------------------------------------------------------  
  *  
  * GetYView --  
  *  
  *      This procedure computes the fractions that indicate what's  
  *      visible in a text window and, optionally, evaluates a  
  *      Tcl script to report them to the text's associated scrollbar.  
  *  
  * Results:  
  *      If report is zero, then the interp's result is filled in with  
  *      two real numbers separated by a space, giving the position of  
  *      the top and bottom of the window as fractions from 0 to 1, where  
  *      0 means the beginning of the text and 1 means the end.  If  
  *      report is non-zero, then the interp's result isn't modified directly,  
  *      but a script is evaluated in interp to report the new scroll  
  *      position to the scrollbar (if the scroll position hasn't changed  
  *      then no script is invoked).  
  *  
  * Side effects:  
  *      None.  
  *  
  *----------------------------------------------------------------------  
  */  
   
 static void  
 GetYView(interp, textPtr, report)  
     Tcl_Interp *interp;                 /* If "report" is FALSE, string  
                                          * describing visible range gets  
                                          * stored in the interp's result. */  
     TkText *textPtr;                    /* Information about text widget. */  
     int report;                         /* Non-zero means report info to  
                                          * scrollbar if it has changed. */  
 {  
     TextDInfo *dInfoPtr = textPtr->dInfoPtr;  
     char buffer[TCL_DOUBLE_SPACE * 2];  
     double first, last;  
     DLine *dlPtr;  
     int totalLines, code, count;  
   
     dlPtr = dInfoPtr->dLinePtr;  
     totalLines = TkBTreeNumLines(textPtr->tree);  
     first = (double) TkBTreeLineIndex(dlPtr->index.linePtr)  
             + (double) dlPtr->index.byteIndex  
                     / TkBTreeBytesInLine(dlPtr->index.linePtr);  
     first /= totalLines;  
     while (1) {  
         if ((dlPtr->y + dlPtr->height) > dInfoPtr->maxY) {  
             /*  
              * The last line is only partially visible, so don't  
              * count its characters in what's visible.  
              */  
             count = 0;  
             break;  
         }  
         if (dlPtr->nextPtr == NULL) {  
             count = dlPtr->byteCount;  
             break;  
         }  
         dlPtr = dlPtr->nextPtr;  
     }  
     last = ((double) TkBTreeLineIndex(dlPtr->index.linePtr))  
             + ((double) (dlPtr->index.byteIndex + count))  
                     / (TkBTreeBytesInLine(dlPtr->index.linePtr));  
     last /= totalLines;  
     if (!report) {  
         sprintf(buffer, "%g %g", first, last);  
         Tcl_SetResult(interp, buffer, TCL_VOLATILE);  
         return;  
     }  
     if ((first == dInfoPtr->yScrollFirst) && (last == dInfoPtr->yScrollLast)) {  
         return;  
     }  
     dInfoPtr->yScrollFirst = first;  
     dInfoPtr->yScrollLast = last;  
     sprintf(buffer, " %g %g", first, last);  
     code = Tcl_VarEval(interp, textPtr->yScrollCmd, buffer, (char *) NULL);  
     if (code != TCL_OK) {  
         Tcl_AddErrorInfo(interp,  
                 "\n    (vertical scrolling command executed by text)");  
         Tcl_BackgroundError(interp);  
     }  
 }  
   
 /*  
  *----------------------------------------------------------------------  
  *  
  * FindDLine --  
  *  
  *      This procedure is called to find the DLine corresponding to a  
  *      given text index.  
  *  
  * Results:  
  *      The return value is a pointer to the first DLine found in the  
  *      list headed by dlPtr that displays information at or after the  
  *      specified position.  If there is no such line in the list then  
  *      NULL is returned.  
  *  
  * Side effects:  
  *      None.  
  *  
  *----------------------------------------------------------------------  
  */  
   
 static DLine *  
 FindDLine(dlPtr, indexPtr)  
     register DLine *dlPtr;      /* Pointer to first in list of DLines  
                                  * to search. */  
     TkTextIndex *indexPtr;      /* Index of desired character. */  
 {  
     TkTextLine *linePtr;  
   
     if (dlPtr == NULL) {  
         return NULL;  
     }  
     if (TkBTreeLineIndex(indexPtr->linePtr)  
             < TkBTreeLineIndex(dlPtr->index.linePtr)) {  
         /*  
          * The first display line is already past the desired line.  
          */  
         return dlPtr;  
     }  
   
     /*  
      * Find the first display line that covers the desired text line.  
      */  
   
     linePtr = dlPtr->index.linePtr;  
     while (linePtr != indexPtr->linePtr) {  
         while (dlPtr->index.linePtr == linePtr) {  
             dlPtr = dlPtr->nextPtr;  
             if (dlPtr == NULL) {  
                 return NULL;  
             }  
         }  
         linePtr = TkBTreeNextLine(linePtr);  
         if (linePtr == NULL) {  
             panic("FindDLine reached end of text");  
         }  
     }  
     if (indexPtr->linePtr != dlPtr->index.linePtr) {  
         return dlPtr;  
     }  
   
     /*  
      * Now get to the right position within the text line.  
      */  
   
     while (indexPtr->byteIndex >= (dlPtr->index.byteIndex + dlPtr->byteCount)) {  
         dlPtr = dlPtr->nextPtr;  
         if ((dlPtr == NULL) || (dlPtr->index.linePtr != indexPtr->linePtr)) {  
             break;  
         }  
     }  
     return dlPtr;  
 }  
   
 /*  
  *----------------------------------------------------------------------  
  *  
  * TkTextPixelIndex --  
  *  
  *      Given an (x,y) coordinate on the screen, find the location of  
  *      the character closest to that location.  
  *  
  * Results:  
  *      The index at *indexPtr is modified to refer to the character  
  *      on the display that is closest to (x,y).  
  *  
  * Side effects:  
  *      None.  
  *  
  *----------------------------------------------------------------------  
  */  
   
 void  
 TkTextPixelIndex(textPtr, x, y, indexPtr)  
     TkText *textPtr;            /* Widget record for text widget. */  
     int x, y;                   /* Pixel coordinates of point in widget's  
                                  * window. */  
     TkTextIndex *indexPtr;      /* This index gets filled in with the  
                                  * index of the character nearest to (x,y). */  
 {  
     TextDInfo *dInfoPtr = textPtr->dInfoPtr;  
     register DLine *dlPtr, *validdlPtr;  
     register TkTextDispChunk *chunkPtr;  
   
     /*  
      * Make sure that all of the layout information about what's  
      * displayed where on the screen is up-to-date.  
      */  
   
     if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {  
         UpdateDisplayInfo(textPtr);  
     }  
   
     /*  
      * If the coordinates are above the top of the window, then adjust  
      * them to refer to the upper-right corner of the window.  If they're  
      * off to one side or the other, then adjust to the closest side.  
      */  
   
     if (y < dInfoPtr->y) {  
         y = dInfoPtr->y;  
         x = dInfoPtr->x;  
     }  
     if (x >= dInfoPtr->maxX) {  
         x = dInfoPtr->maxX - 1;  
     }  
     if (x < dInfoPtr->x) {  
         x = dInfoPtr->x;  
     }  
   
     /*  
      * Find the display line containing the desired y-coordinate.  
      */  
   
     for (dlPtr = validdlPtr = dInfoPtr->dLinePtr; y >= (dlPtr->y + dlPtr->height);  
             dlPtr = dlPtr->nextPtr) {  
         if (dlPtr->chunkPtr !=NULL) validdlPtr = dlPtr;  
         if (dlPtr->nextPtr == NULL) {  
             /*  
              * Y-coordinate is off the bottom of the displayed text.  
              * Use the last character on the last line.  
              */  
   
             x = dInfoPtr->maxX - 1;  
             break;  
         }  
     }  
     if (dlPtr->chunkPtr == NULL) dlPtr = validdlPtr;  
   
   
     /*  
      * Scan through the line's chunks to find the one that contains  
      * the desired x-coordinate.  Before doing this, translate the  
      * x-coordinate from the coordinate system of the window to the  
      * coordinate system of the line (to take account of x-scrolling).  
      */  
   
     *indexPtr = dlPtr->index;  
     x = x - dInfoPtr->x + dInfoPtr->curPixelOffset;  
     for (chunkPtr = dlPtr->chunkPtr; x >= (chunkPtr->x + chunkPtr->width);  
             indexPtr->byteIndex += chunkPtr->numBytes,  
             chunkPtr = chunkPtr->nextPtr) {  
         if (chunkPtr->nextPtr == NULL) {  
             indexPtr->byteIndex += chunkPtr->numBytes;  
             TkTextIndexBackChars(indexPtr, 1, indexPtr);  
             return;  
         }  
     }  
   
     /*  
      * If the chunk has more than one byte in it, ask it which  
      * character is at the desired location.  
      */  
   
     if (chunkPtr->numBytes > 1) {  
         indexPtr->byteIndex += (*chunkPtr->measureProc)(chunkPtr, x);  
     }  
 }  
   
 /*  
  *----------------------------------------------------------------------  
  *  
  * TkTextCharBbox --  
  *  
  *      Given an index, find the bounding box of the screen area  
  *      occupied by that character.  
  *  
  * Results:  
  *      Zero is returned if the character is on the screen.  -1  
  *      means the character isn't on the screen.  If the return value  
  *      is 0, then the bounding box of the part of the character that's  
  *      visible on the screen is returned to *xPtr, *yPtr, *widthPtr,  
  *      and *heightPtr.  
  *  
  * Side effects:  
  *      None.  
  *  
  *----------------------------------------------------------------------  
  */  
   
 int  
 TkTextCharBbox(textPtr, indexPtr, xPtr, yPtr, widthPtr, heightPtr)  
     TkText *textPtr;            /* Widget record for text widget. */  
     TkTextIndex *indexPtr;      /* Index of character whose bounding  
                                  * box is desired. */  
     int *xPtr, *yPtr;           /* Filled with character's upper-left  
                                  * coordinate. */  
     int *widthPtr, *heightPtr;  /* Filled in with character's dimensions. */  
 {  
     TextDInfo *dInfoPtr = textPtr->dInfoPtr;  
     DLine *dlPtr;  
     register TkTextDispChunk *chunkPtr;  
     int byteIndex;  
   
     /*  
      * Make sure that all of the screen layout information is up to date.  
      */  
   
     if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {  
         UpdateDisplayInfo(textPtr);  
     }  
   
     /*  
      * Find the display line containing the desired index.  
      */  
   
     dlPtr = FindDLine(dInfoPtr->dLinePtr, indexPtr);  
     if ((dlPtr == NULL) || (TkTextIndexCmp(&dlPtr->index, indexPtr) > 0)) {  
         return -1;  
     }  
   
     /*  
      * Find the chunk within the line that contains the desired  
      * index.  
      */  
   
     byteIndex = indexPtr->byteIndex - dlPtr->index.byteIndex;  
     for (chunkPtr = dlPtr->chunkPtr; ; chunkPtr = chunkPtr->nextPtr) {  
         if (chunkPtr == NULL) {  
             return -1;  
         }  
         if (byteIndex < chunkPtr->numBytes) {  
             break;  
         }  
         byteIndex -= chunkPtr->numBytes;  
     }  
   
     /*  
      * Call a chunk-specific procedure to find the horizontal range of  
      * the character within the chunk, then fill in the vertical range.  
      * The x-coordinate returned by bboxProc is a coordinate within a  
      * line, not a coordinate on the screen.  Translate it to reflect  
      * horizontal scrolling.  
      */  
   
     (*chunkPtr->bboxProc)(chunkPtr, byteIndex, dlPtr->y + dlPtr->spaceAbove,  
             dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,  
             dlPtr->baseline - dlPtr->spaceAbove, xPtr, yPtr, widthPtr,  
             heightPtr);  
     *xPtr = *xPtr + dInfoPtr->x - dInfoPtr->curPixelOffset;  
     if ((byteIndex == (chunkPtr->numBytes - 1)) && (chunkPtr->nextPtr == NULL)) {  
         /*  
          * Last character in display line.  Give it all the space up to  
          * the line.  
          */  
   
         if (*xPtr > dInfoPtr->maxX) {  
             *xPtr = dInfoPtr->maxX;  
         }  
         *widthPtr = dInfoPtr->maxX - *xPtr;  
     }  
     if ((*xPtr + *widthPtr) <= dInfoPtr->x) {  
         return -1;  
     }  
     if ((*xPtr + *widthPtr) > dInfoPtr->maxX) {  
         *widthPtr = dInfoPtr->maxX - *xPtr;  
         if (*widthPtr <= 0) {  
             return -1;  
         }  
     }  
     if ((*yPtr + *heightPtr) > dInfoPtr->maxY) {  
         *heightPtr = dInfoPtr->maxY - *yPtr;  
         if (*heightPtr <= 0) {  
             return -1;  
         }  
     }  
     return 0;  
 }  
   
 /*  
  *----------------------------------------------------------------------  
  *  
  * TkTextDLineInfo --  
  *  
  *      Given an index, return information about the display line  
  *      containing that character.  
  *  
  * Results:  
  *      Zero is returned if the character is on the screen.  -1  
  *      means the character isn't on the screen.  If the return value  
  *      is 0, then information is returned in the variables pointed  
  *      to by xPtr, yPtr, widthPtr, heightPtr, and basePtr.  
  *  
  * Side effects:  
  *      None.  
  *  
  *----------------------------------------------------------------------  
  */  
   
 int  
 TkTextDLineInfo(textPtr, indexPtr, xPtr, yPtr, widthPtr, heightPtr, basePtr)  
     TkText *textPtr;            /* Widget record for text widget. */  
     TkTextIndex *indexPtr;      /* Index of character whose bounding  
                                  * box is desired. */  
     int *xPtr, *yPtr;           /* Filled with line's upper-left  
                                  * coordinate. */  
     int *widthPtr, *heightPtr;  /* Filled in with line's dimensions. */  
     int *basePtr;               /* Filled in with the baseline position,  
                                  * measured as an offset down from *yPtr. */  
 {  
     TextDInfo *dInfoPtr = textPtr->dInfoPtr;  
     DLine *dlPtr;  
     int dlx;  
   
     /*  
      * Make sure that all of the screen layout information is up to date.  
      */  
   
     if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {  
         UpdateDisplayInfo(textPtr);  
     }  
   
     /*  
      * Find the display line containing the desired index.  
      */  
   
     dlPtr = FindDLine(dInfoPtr->dLinePtr, indexPtr);  
     if ((dlPtr == NULL) || (TkTextIndexCmp(&dlPtr->index, indexPtr) > 0)) {  
         return -1;  
     }  
   
     dlx = (dlPtr->chunkPtr != NULL? dlPtr->chunkPtr->x: 0);  
     *xPtr = dInfoPtr->x - dInfoPtr->curPixelOffset + dlx;  
     *widthPtr = dlPtr->length - dlx;  
     *yPtr = dlPtr->y;  
     if ((dlPtr->y + dlPtr->height) > dInfoPtr->maxY) {  
         *heightPtr = dInfoPtr->maxY - dlPtr->y;  
     } else {  
         *heightPtr = dlPtr->height;  
     }  
     *basePtr = dlPtr->baseline;  
     return 0;  
 }  
   
 static void  
 ElideBboxProc(chunkPtr, index, y, lineHeight, baseline, xPtr, yPtr,  
         widthPtr, heightPtr)  
     TkTextDispChunk *chunkPtr;          /* Chunk containing desired char. */  
     int index;                          /* Index of desired character within  
                                          * the chunk. */  
     int y;                              /* Topmost pixel in area allocated  
                                          * for this line. */  
     int lineHeight;                     /* Height of line, in pixels. */  
     int baseline;                       /* Location of line's baseline, in  
                                          * pixels measured down from y. */  
     int *xPtr, *yPtr;                   /* Gets filled in with coords of  
                                          * character's upper-left pixel.  
                                          * X-coord is in same coordinate  
                                          * system as chunkPtr->x. */  
     int *widthPtr;                      /* Gets filled in with width of  
                                          * character, in pixels. */  
     int *heightPtr;                     /* Gets filled in with height of  
                                          * character, in pixels. */  
 {  
     *xPtr = chunkPtr->x;  
     *yPtr = y;  
     *widthPtr = *heightPtr = 0;  
 }  
   
   
 static int  
 ElideMeasureProc(chunkPtr, x)  
     TkTextDispChunk *chunkPtr;          /* Chunk containing desired coord. */  
     int x;                              /* X-coordinate, in same coordinate  
                                          * system as chunkPtr->x. */  
 {  
     return 0 /*chunkPtr->numBytes - 1*/;  
 }  
   
 /*  
  *--------------------------------------------------------------  
  *  
  * TkTextCharLayoutProc --  
  *  
  *      This procedure is the "layoutProc" for character segments.  
  *  
 n * Results:  
  *      If there is something to display for the chunk then a  
  *      non-zero value is returned and the fields of chunkPtr  
  *      will be filled in (see the declaration of TkTextDispChunk  
  *      in tkText.h for details).  If zero is returned it means  
  *      that no characters from this chunk fit in the window.  
  *      If -1 is returned it means that this segment just doesn't  
  *      need to be displayed (never happens for text).  
  *  
  * Side effects:  
  *      Memory is allocated to hold additional information about  
  *      the chunk.  
  *  
  *--------------------------------------------------------------  
  */  
   
 int  
 TkTextCharLayoutProc(textPtr, indexPtr, segPtr, byteOffset, maxX, maxBytes,  
         noCharsYet, wrapMode, chunkPtr)  
     TkText *textPtr;            /* Text widget being layed out. */  
     TkTextIndex *indexPtr;      /* Index of first character to lay out  
                                  * (corresponds to segPtr and offset). */  
     TkTextSegment *segPtr;      /* Segment being layed out. */  
     int byteOffset;             /* Byte offset within segment of first  
                                  * character to consider. */  
     int maxX;                   /* Chunk must not occupy pixels at this  
                                  * position or higher. */  
     int maxBytes;               /* Chunk must not include more than this  
                                  * many characters. */  
     int noCharsYet;             /* Non-zero means no characters have been  
                                  * assigned to this display line yet. */  
     TkWrapMode wrapMode;        /* How to handle line wrapping: TEXT_WRAPMODE_CHAR,  
                                  * TEXT_WRAPMODE_NONE, or TEXT_WRAPMODE_WORD. */  
     register TkTextDispChunk *chunkPtr;  
                                 /* Structure to fill in with information  
                                  * about this chunk.  The x field has already  
                                  * been set by the caller. */  
 {  
     Tk_Font tkfont;  
     int nextX, bytesThatFit, count;  
     CharInfo *ciPtr;  
     char *p;  
     TkTextSegment *nextPtr;  
     Tk_FontMetrics fm;  
   
     /*  
      * Figure out how many characters will fit in the space we've got.  
      * Include the next character, even though it won't fit completely,  
      * if any of the following is true:  
      *   (a) the chunk contains no characters and the display line contains  
      *       no characters yet (i.e. the line isn't wide enough to hold  
      *       even a single character).  
      *   (b) at least one pixel of the character is visible, we haven't  
      *       already exceeded the character limit, and the next character  
      *       is a white space character.  
      */  
   
     p = segPtr->body.chars + byteOffset;  
     tkfont = chunkPtr->stylePtr->sValuePtr->tkfont;  
     bytesThatFit = MeasureChars(tkfont, p, maxBytes, chunkPtr->x, maxX, 0,  
             &nextX);  
     if (bytesThatFit < maxBytes) {  
         if ((bytesThatFit == 0) && noCharsYet) {  
             Tcl_UniChar ch;  
               
             bytesThatFit = MeasureChars(tkfont, p, Tcl_UtfToUniChar(p, &ch),  
                     chunkPtr->x, -1, 0, &nextX);  
         }  
         if ((nextX < maxX) && ((p[bytesThatFit] == ' ')  
                 || (p[bytesThatFit] == '\t'))) {  
             /*  
              * Space characters are funny, in that they are considered  
              * to fit if there is at least one pixel of space left on the  
              * line.  Just give the space character whatever space is left.  
              */  
   
             nextX = maxX;  
             bytesThatFit++;  
         }  
         if (p[bytesThatFit] == '\n') {  
             /*  
              * A newline character takes up no space, so if the previous  
              * character fits then so does the newline.  
              */  
   
             bytesThatFit++;  
         }  
         if (bytesThatFit == 0) {  
             return 0;  
         }  
     }  
           
     Tk_GetFontMetrics(tkfont, &fm);  
   
     /*  
      * Fill in the chunk structure and allocate and initialize a  
      * CharInfo structure.  If the last character is a newline  
      * then don't bother to display it.  
      */  
   
     chunkPtr->displayProc = CharDisplayProc;  
     chunkPtr->undisplayProc = CharUndisplayProc;  
     chunkPtr->measureProc = CharMeasureProc;  
     chunkPtr->bboxProc = CharBboxProc;  
     chunkPtr->numBytes = bytesThatFit;  
     chunkPtr->minAscent = fm.ascent + chunkPtr->stylePtr->sValuePtr->offset;  
     chunkPtr->minDescent = fm.descent - chunkPtr->stylePtr->sValuePtr->offset;  
     chunkPtr->minHeight = 0;  
     chunkPtr->width = nextX - chunkPtr->x;  
     chunkPtr->breakIndex = -1;  
     ciPtr = (CharInfo *) ckalloc((unsigned)  
             (sizeof(CharInfo) - 3 + bytesThatFit));  
     chunkPtr->clientData = (ClientData) ciPtr;  
     ciPtr->numBytes = bytesThatFit;  
     strncpy(ciPtr->chars, p, (size_t) bytesThatFit);  
     if (p[bytesThatFit - 1] == '\n') {  
         ciPtr->numBytes--;  
     }  
   
     /*  
      * Compute a break location.  If we're in word wrap mode, a  
      * break can occur after any space character, or at the end of  
      * the chunk if the next segment (ignoring those with zero size)  
      * is not a character segment.  
      */  
   
     if (wrapMode != TEXT_WRAPMODE_WORD) {  
         chunkPtr->breakIndex = chunkPtr->numBytes;  
     } else {  
         for (count = bytesThatFit, p += bytesThatFit - 1; count > 0;  
                 count--, p--) {  
             if (isspace(UCHAR(*p))) {  
                 chunkPtr->breakIndex = count;  
                 break;  
             }  
         }  
         if ((bytesThatFit + byteOffset) == segPtr->size) {  
             for (nextPtr = segPtr->nextPtr; nextPtr != NULL;  
                     nextPtr = nextPtr->nextPtr) {  
                 if (nextPtr->size != 0) {  
                     if (nextPtr->typePtr != &tkTextCharType) {  
                         chunkPtr->breakIndex = chunkPtr->numBytes;  
                     }  
                     break;  
                 }  
             }  
         }  
     }  
     return 1;  
 }  
   
 /*  
  *--------------------------------------------------------------  
  *  
  * CharDisplayProc --  
  *  
  *      This procedure is called to display a character chunk on  
  *      the screen or in an off-screen pixmap.  
  *  
  * Results:  
  *      None.  
  *  
  * Side effects:  
  *      Graphics are drawn.  
  *  
  *--------------------------------------------------------------  
  */  
   
 static void  
 CharDisplayProc(chunkPtr, x, y, height, baseline, display, dst, screenY)  
     TkTextDispChunk *chunkPtr;          /* Chunk that is to be drawn. */  
     int x;                              /* X-position in dst at which to  
                                          * draw this chunk (may differ from  
                                          * the x-position in the chunk because  
                                          * of scrolling). */  
     int y;                              /* Y-position at which to draw this  
                                          * chunk in dst. */  
     int height;                         /* Total height of line. */  
     int baseline;                       /* Offset of baseline from y. */  
     Display *display;                   /* Display to use for drawing. */  
     Drawable dst;                       /* Pixmap or window in which to draw  
                                          * chunk. */  
     int screenY;                        /* Y-coordinate in text window that  
                                          * corresponds to y. */  
 {  
     CharInfo *ciPtr = (CharInfo *) chunkPtr->clientData;  
     TextStyle *stylePtr;  
     StyleValues *sValuePtr;  
     int offsetBytes, offsetX;  
   
     if ((x + chunkPtr->width) <= 0) {  
         /*  
          * The chunk is off-screen.  
          */  
   
         return;  
     }  
   
     stylePtr = chunkPtr->stylePtr;  
     sValuePtr = stylePtr->sValuePtr;  
   
     /*  
      * If the text sticks out way to the left of the window, skip  
      * over the characters that aren't in the visible part of the  
      * window.  This is essential if x is very negative (such as  
      * less than 32K);  otherwise overflow problems will occur  
      * in servers that use 16-bit arithmetic, like X.  
      */  
   
     offsetX = x;  
     offsetBytes = 0;  
     if (x < 0) {  
         offsetBytes = MeasureChars(sValuePtr->tkfont, ciPtr->chars,  
             ciPtr->numBytes, x, 0, x - chunkPtr->x, &offsetX);  
     }  
   
     /*  
      * Draw the text, underline, and overstrike for this chunk.  
      */  
   
     if (!sValuePtr->elide && (ciPtr->numBytes > offsetBytes) && (stylePtr->fgGC != None)) {  
         int numBytes = ciPtr->numBytes - offsetBytes;  
         char *string = ciPtr->chars + offsetBytes;  
   
         if ((numBytes > 0) && (string[numBytes - 1] == '\t')) {  
             numBytes--;  
         }  
         Tk_DrawChars(display, dst, stylePtr->fgGC, sValuePtr->tkfont, string,  
                 numBytes, offsetX, y + baseline - sValuePtr->offset);  
         if (sValuePtr->underline) {  
             Tk_UnderlineChars(display, dst, stylePtr->fgGC, sValuePtr->tkfont,  
                     ciPtr->chars + offsetBytes, offsetX,  
                     y + baseline - sValuePtr->offset, 0, numBytes);  
   
         }  
         if (sValuePtr->overstrike) {  
             Tk_FontMetrics fm;  
               
             Tk_GetFontMetrics(sValuePtr->tkfont, &fm);  
             Tk_UnderlineChars(display, dst, stylePtr->fgGC, sValuePtr->tkfont,  
                     ciPtr->chars + offsetBytes, offsetX,  
                     y + baseline - sValuePtr->offset  
                             - fm.descent - (fm.ascent * 3) / 10,  
                     0, numBytes);  
         }  
     }  
 }  
   
 /*  
  *--------------------------------------------------------------  
  *  
  * CharUndisplayProc --  
  *  
  *      This procedure is called when a character chunk is no  
  *      longer going to be displayed.  It frees up resources  
  *      that were allocated to display the chunk.  
  *  
  * Results:  
  *      None.  
  *  
  * Side effects:  
  *      Memory and other resources get freed.  
  *  
  *--------------------------------------------------------------  
  */  
   
 static void  
 CharUndisplayProc(textPtr, chunkPtr)  
     TkText *textPtr;                    /* Overall information about text  
                                          * widget. */  
     TkTextDispChunk *chunkPtr;          /* Chunk that is about to be freed. */  
 {  
     CharInfo *ciPtr = (CharInfo *) chunkPtr->clientData;  
   
     ckfree((char *) ciPtr);  
 }  
   
 /*  
  *--------------------------------------------------------------  
  *  
  * CharMeasureProc --  
  *  
  *      This procedure is called to determine which character in  
  *      a character chunk lies over a given x-coordinate.  
  *  
  * Results:  
  *      The return value is the index *within the chunk* of the  
  *      character that covers the position given by "x".  
  *  
  * Side effects:  
  *      None.  
  *  
  *--------------------------------------------------------------  
  */  
   
 static int  
 CharMeasureProc(chunkPtr, x)  
     TkTextDispChunk *chunkPtr;          /* Chunk containing desired coord. */  
     int x;                              /* X-coordinate, in same coordinate  
                                          * system as chunkPtr->x. */  
 {  
     CharInfo *ciPtr = (CharInfo *) chunkPtr->clientData;  
     int endX;  
   
     return MeasureChars(chunkPtr->stylePtr->sValuePtr->tkfont, ciPtr->chars,  
             chunkPtr->numBytes - 1, chunkPtr->x, x, 0, &endX);  
                                                 /* CHAR OFFSET */  
 }  
   
 /*  
  *--------------------------------------------------------------  
  *  
  * CharBboxProc --  
  *  
  *      This procedure is called to compute the bounding box of  
  *      the area occupied by a single character.  
  *  
  * Results:  
  *      There is no return value.  *xPtr and *yPtr are filled in  
  *      with the coordinates of the upper left corner of the  
  *      character, and *widthPtr and *heightPtr are filled in with  
  *      the dimensions of the character in pixels.  Note:  not all  
  *      of the returned bbox is necessarily visible on the screen  
  *      (the rightmost part might be off-screen to the right,  
  *      and the bottommost part might be off-screen to the bottom).  
  *  
  * Side effects:  
  *      None.  
  *  
  *--------------------------------------------------------------  
  */  
   
 static void  
 CharBboxProc(chunkPtr, byteIndex, y, lineHeight, baseline, xPtr, yPtr,  
         widthPtr, heightPtr)  
     TkTextDispChunk *chunkPtr;          /* Chunk containing desired char. */  
     int byteIndex;                              /* Byte offset of desired character  
                                          * within the chunk. */  
     int y;                              /* Topmost pixel in area allocated  
                                          * for this line. */  
     int lineHeight;                     /* Height of line, in pixels. */  
     int baseline;                       /* Location of line's baseline, in  
                                          * pixels measured down from y. */  
     int *xPtr, *yPtr;                   /* Gets filled in with coords of  
                                          * character's upper-left pixel.  
                                          * X-coord is in same coordinate  
                                          * system as chunkPtr->x. */  
     int *widthPtr;                      /* Gets filled in with width of  
                                          * character, in pixels. */  
     int *heightPtr;                     /* Gets filled in with height of  
                                          * character, in pixels. */  
 {  
     CharInfo *ciPtr = (CharInfo *) chunkPtr->clientData;  
     int maxX;  
   
     maxX = chunkPtr->width + chunkPtr->x;  
     MeasureChars(chunkPtr->stylePtr->sValuePtr->tkfont, ciPtr->chars,  
             byteIndex, chunkPtr->x, -1, 0, xPtr);  
   
     if (byteIndex == ciPtr->numBytes) {  
         /*  
          * This situation only happens if the last character in a line  
          * is a space character, in which case it absorbs all of the  
          * extra space in the line (see TkTextCharLayoutProc).  
          */  
   
         *widthPtr = maxX - *xPtr;  
     } else if ((ciPtr->chars[byteIndex] == '\t')  
             && (byteIndex == ciPtr->numBytes - 1)) {  
         /*  
          * The desired character is a tab character that terminates a  
          * chunk;  give it all the space left in the chunk.  
          */  
   
         *widthPtr = maxX - *xPtr;  
     } else {  
         MeasureChars(chunkPtr->stylePtr->sValuePtr->tkfont,  
                 ciPtr->chars + byteIndex, 1, *xPtr, -1, 0, widthPtr);  
         if (*widthPtr > maxX) {  
             *widthPtr = maxX - *xPtr;  
         } else {  
             *widthPtr -= *xPtr;  
         }  
     }  
     *yPtr = y + baseline - chunkPtr->minAscent;  
     *heightPtr = chunkPtr->minAscent + chunkPtr->minDescent;  
 }  
   
 /*  
  *----------------------------------------------------------------------  
  *  
  * AdjustForTab --  
  *  
  *      This procedure is called to move a series of chunks right  
  *      in order to align them with a tab stop.  
  *  
  * Results:  
  *      None.  
  *  
  * Side effects:  
  *      The width of chunkPtr gets adjusted so that it absorbs the  
  *      extra space due to the tab.  The x locations in all the chunks  
  *      after chunkPtr are adjusted rightward to align with the tab  
  *      stop given by tabArrayPtr and index.  
  *  
  *----------------------------------------------------------------------  
  */  
   
 static void  
 AdjustForTab(textPtr, tabArrayPtr, index, chunkPtr)  
     TkText *textPtr;                    /* Information about the text widget as  
                                          * a whole. */  
     TkTextTabArray *tabArrayPtr;        /* Information about the tab stops  
                                          * that apply to this line.  May be  
                                          * NULL to indicate default tabbing  
                                          * (every 8 chars). */  
     int index;                          /* Index of current tab stop. */  
     TkTextDispChunk *chunkPtr;          /* Chunk whose last character is  
                                          * the tab;  the following chunks  
                                          * contain information to be shifted  
                                          * right. */  
   
 {  
     int x, desired, delta, width, decimal, i, gotDigit;  
     TkTextDispChunk *chunkPtr2, *decimalChunkPtr;  
     CharInfo *ciPtr;  
     int tabX, prev, spaceWidth;  
     char *p;  
     TkTextTabAlign alignment;  
   
     if (chunkPtr->nextPtr == NULL) {  
         /*  
          * Nothing after the actual tab;  just return.  
          */  
   
         return;  
     }  
   
     /*  
      * If no tab information has been given, do the usual thing:  
      * round up to the next boundary of 8 average-sized characters.  
      */  
   
     x = chunkPtr->nextPtr->x;  
     if ((tabArrayPtr == NULL) || (tabArrayPtr->numTabs == 0)) {  
         /*  
          * No tab information has been given, so use the default  
          * interpretation of tabs.  
          */  
   
         desired = NextTabStop(textPtr->tkfont, x, 0);  
         goto update;  
     }  
   
     if (index < tabArrayPtr->numTabs) {  
         alignment = tabArrayPtr->tabs[index].alignment;  
         tabX = tabArrayPtr->tabs[index].location;  
     } else {  
         /*  
          * Ran out of tab stops;  compute a tab position by extrapolating  
          * from the last two tab positions.  
          */  
   
         if (tabArrayPtr->numTabs > 1) {  
             prev = tabArrayPtr->tabs[tabArrayPtr->numTabs-2].location;  
         } else {  
             prev = 0;  
         }  
         alignment = tabArrayPtr->tabs[tabArrayPtr->numTabs-1].alignment;  
         tabX = tabArrayPtr->tabs[tabArrayPtr->numTabs-1].location  
                 + (index + 1 - tabArrayPtr->numTabs)  
                 * (tabArrayPtr->tabs[tabArrayPtr->numTabs-1].location - prev);  
     }  
   
     if (alignment == LEFT) {  
         desired = tabX;  
         goto update;  
     }  
   
     if ((alignment == CENTER) || (alignment == RIGHT)) {  
         /*  
          * Compute the width of all the information in the tab group,  
          * then use it to pick a desired location.  
          */  
   
         width = 0;  
         for (chunkPtr2 = chunkPtr->nextPtr; chunkPtr2 != NULL;  
                 chunkPtr2 = chunkPtr2->nextPtr) {  
             width += chunkPtr2->width;  
         }  
         if (alignment == CENTER) {  
             desired = tabX - width/2;  
         } else {  
             desired = tabX - width;  
         }  
         goto update;  
     }  
   
     /*  
      * Must be numeric alignment.  Search through the text to be  
      * tabbed, looking for the last , or . before the first character  
      * that isn't a number, comma, period, or sign.  
      */  
   
     decimalChunkPtr = NULL;  
     decimal = gotDigit = 0;  
     for (chunkPtr2 = chunkPtr->nextPtr; chunkPtr2 != NULL;  
             chunkPtr2 = chunkPtr2->nextPtr) {  
         if (chunkPtr2->displayProc != CharDisplayProc) {  
             continue;  
         }  
         ciPtr = (CharInfo *) chunkPtr2->clientData;  
         for (p = ciPtr->chars, i = 0; i < ciPtr->numBytes; p++, i++) {  
             if (isdigit(UCHAR(*p))) {  
                 gotDigit = 1;  
             } else if ((*p == '.') || (*p == ',')) {  
                 decimal = p-ciPtr->chars;  
                 decimalChunkPtr = chunkPtr2;  
             } else if (gotDigit) {  
                 if (decimalChunkPtr == NULL) {  
                     decimal = p-ciPtr->chars;  
                     decimalChunkPtr = chunkPtr2;  
                 }  
                 goto endOfNumber;  
             }  
         }  
     }  
     endOfNumber:  
     if (decimalChunkPtr != NULL) {  
         int curX;  
   
         ciPtr = (CharInfo *) decimalChunkPtr->clientData;  
         MeasureChars(decimalChunkPtr->stylePtr->sValuePtr->tkfont,  
                 ciPtr->chars, decimal, decimalChunkPtr->x, -1, 0, &curX);  
         desired = tabX - (curX - x);  
         goto update;  
     } else {  
         /*  
          * There wasn't a decimal point.  Right justify the text.  
          */  
       
         width = 0;  
         for (chunkPtr2 = chunkPtr->nextPtr; chunkPtr2 != NULL;  
                 chunkPtr2 = chunkPtr2->nextPtr) {  
             width += chunkPtr2->width;  
         }  
         desired = tabX - width;  
     }  
   
     /*  
      * Shift all of the chunks to the right so that the left edge is  
      * at the desired location, then expand the chunk containing the  
      * tab.  Be sure that the tab occupies at least the width of a  
      * space character.  
      */  
   
     update:  
     delta = desired - x;  
     MeasureChars(textPtr->tkfont, " ", 1, 0, -1, 0, &spaceWidth);  
     if (delta < spaceWidth) {  
         delta = spaceWidth;  
     }  
     for (chunkPtr2 = chunkPtr->nextPtr; chunkPtr2 != NULL;  
             chunkPtr2 = chunkPtr2->nextPtr) {  
         chunkPtr2->x += delta;  
     }  
     chunkPtr->width += delta;  
 }  
   
 /*  
  *----------------------------------------------------------------------  
  *  
  * SizeOfTab --  
  *  
  *      This returns an estimate of the amount of white space that will  
  *      be consumed by a tab.  
  *  
  * Results:  
  *      The return value is the minimum number of pixels that will  
  *      be occupied by the index'th tab of tabArrayPtr, assuming that  
  *      the current position on the line is x and the end of the  
  *      line is maxX.  For numeric tabs, this is a conservative  
  *      estimate.  The return value is always >= 0.  
  *  
  * Side effects:  
  *      None.  
  *  
  *----------------------------------------------------------------------  
  */  
   
 static int  
 SizeOfTab(textPtr, tabArrayPtr, index, x, maxX)  
     TkText *textPtr;                    /* Information about the text widget as  
                                          * a whole. */  
     TkTextTabArray *tabArrayPtr;        /* Information about the tab stops  
                                          * that apply to this line.  NULL  
                                          * means use default tabbing (every  
                                          * 8 chars.) */  
     int index;                          /* Index of current tab stop. */  
     int x;                              /* Current x-location in line. Only  
                                          * used if tabArrayPtr == NULL. */  
     int maxX;                           /* X-location of pixel just past the  
                                          * right edge of the line. */  
 {  
     int tabX, prev, result, spaceWidth;  
     TkTextTabAlign alignment;  
   
     if ((tabArrayPtr == NULL) || (tabArrayPtr->numTabs == 0)) {  
         tabX = NextTabStop(textPtr->tkfont, x, 0);  
         return tabX - x;  
     }  
     if (index < tabArrayPtr->numTabs) {  
         tabX = tabArrayPtr->tabs[index].location;  
         alignment = tabArrayPtr->tabs[index].alignment;  
     } else {  
         /*  
          * Ran out of tab stops;  compute a tab position by extrapolating  
          * from the last two tab positions.  
          */  
   
         if (tabArrayPtr->numTabs > 1) {  
             prev = tabArrayPtr->tabs[tabArrayPtr->numTabs-2].location;  
         } else {  
             prev = 0;  
         }  
         tabX = tabArrayPtr->tabs[tabArrayPtr->numTabs-1].location  
                 + (index + 1 - tabArrayPtr->numTabs)  
                 * (tabArrayPtr->tabs[tabArrayPtr->numTabs-1].location - prev);  
         alignment = tabArrayPtr->tabs[tabArrayPtr->numTabs-1].alignment;  
     }  
     if (alignment == CENTER) {  
         /*  
          * Be very careful in the arithmetic below, because maxX may  
          * be the largest positive number:  watch out for integer  
          * overflow.  
          */  
   
         if ((maxX-tabX) < (tabX - x)) {  
             result = (maxX - x) - 2*(maxX - tabX);  
         } else {  
             result = 0;  
         }  
         goto done;  
     }  
     if (alignment == RIGHT) {  
         result = 0;  
         goto done;  
     }  
   
     /*  
      * Note: this treats NUMERIC alignment the same as LEFT  
      * alignment, which is somewhat conservative.  However, it's  
      * pretty tricky at this point to figure out exactly where  
      * the damn decimal point will be.  
      */  
   
     if (tabX > x) {  
         result = tabX - x;  
     } else {  
         result = 0;  
     }  
   
     done:  
     MeasureChars(textPtr->tkfont, " ", 1, 0, -1, 0, &spaceWidth);  
     if (result < spaceWidth) {  
         result = spaceWidth;  
     }  
     return result;  
 }  
   
 /*  
  *---------------------------------------------------------------------------  
  *  
  * NextTabStop --  
  *  
  *      Given the current position, determine where the next default  
  *      tab stop would be located.  This procedure is called when the  
  *      current chunk in the text has no tabs defined and so the default  
  *      tab spacing for the font should be used.  
  *  
  * Results:  
  *      The location in pixels of the next tab stop.  
  *  
  * Side effects:  
  *      None.  
  *  
  *---------------------------------------------------------------------------  
  */  
   
 static int  
 NextTabStop(tkfont, x, tabOrigin)  
     Tk_Font tkfont;             /* Font in which chunk that contains tab  
                                  * stop will be drawn. */  
     int x;                      /* X-position in pixels where last  
                                  * character was drawn.  The next tab stop  
                                  * occurs somewhere after this location. */  
     int tabOrigin;              /* The origin for tab stops.  May be  
                                  * non-zero if text has been scrolled. */  
 {  
     int tabWidth, rem;  
       
     tabWidth = Tk_TextWidth(tkfont, "0", 1) * 8;  
     if (tabWidth == 0) {  
         tabWidth = 1;  
     }  
   
     x += tabWidth;  
     rem = (x - tabOrigin) % tabWidth;  
     if (rem < 0) {  
         rem += tabWidth;  
     }  
     x -= rem;  
     return x;  
 }  
   
 /*  
  *---------------------------------------------------------------------------  
  *  
  *  MeasureChars --  
  *  
  *      Determine the number of characters from the string that will fit  
  *      in the given horizontal span.  The measurement is done under the  
  *      assumption that Tk_DrawTextLayout will be used to actually display  
  *      the characters.  
  *  
  *      If tabs are encountered in the string, they will be expanded  
  *      to the next tab stop, unless the TK_IGNORE_TABS flag is specified.  
  *  
  *      If a newline is encountered in the string, the line will be  
  *      broken at that point, unless the TK_NEWSLINES_NOT_SPECIAL flag  
  *      is specified.    
  *  
  * Results:  
  *      The return value is the number of bytes from source  
  *      that fit in the span given by startX and maxX.  *nextXPtr  
  *      is filled in with the x-coordinate at which the first  
  *      character that didn't fit would be drawn, if it were to  
  *      be drawn.  
  *  
  * Side effects:  
  *      None.  
  *  
  *--------------------------------------------------------------  
  */  
   
 static int  
 MeasureChars(tkfont, source, maxBytes, startX, maxX, tabOrigin, nextXPtr)  
     Tk_Font tkfont;             /* Font in which to draw characters. */  
     CONST char *source;         /* Characters to be displayed.  Need not  
                                  * be NULL-terminated. */  
     int maxBytes;               /* Maximum # of bytes to consider from  
                                  * source. */  
     int startX;                 /* X-position at which first character will  
                                  * be drawn. */  
     int maxX;                   /* Don't consider any character that would  
                                  * cross this x-position. */  
     int tabOrigin;              /* X-location that serves as "origin" for  
                                  * tab stops. */  
     int *nextXPtr;              /* Return x-position of terminating  
                                  * character here. */  
 {  
     int curX, width, ch;  
     CONST char *special, *end, *start;  
   
     ch = 0;                     /* lint. */  
     curX = startX;  
     special = source;  
     end = source + maxBytes;  
     for (start = source; start < end; ) {  
         if (start >= special) {  
             /*  
              * Find the next special character in the string.  
              */  
   
             for (special = start; special < end; special++) {  
                 ch = *special;  
                 if ((ch == '\t') || (ch == '\n')) {  
                     break;  
                 }  
             }  
         }  
   
         /*  
          * Special points at the next special character (or the end of the  
          * string).  Process characters between start and special.  
          */  
   
         if ((maxX >= 0) && (curX >= maxX)) {  
             break;  
         }  
         start += Tk_MeasureChars(tkfont, start, special - start, maxX - curX,  
                 0, &width);  
         curX += width;  
         if (start < special) {  
             /*  
              * No more chars fit in line.  
              */  
   
             break;  
         }  
         if (special < end) {  
             if (ch == '\t') {  
                 start++;  
             } else {  
                 break;  
             }  
         }  
     }  
   
     *nextXPtr = curX;  
     return start - source;  
 }  
   
   
 /* $History: tkTextDisp.c $  
  *  
  * *****************  Version 1  *****************  
  * User: Dtashley     Date: 1/02/01    Time: 3:07a  
  * Created in $/IjuScripter, IjuConsole/Source/Tk Base  
  * Initial check-in.  
  */  
   
 /* End of TKTEXTDISP.C */  
1    /* $Header$ */
2    
3    /*
4     * tkTextDisp.c --
5     *
6     *      This module provides facilities to display text widgets.  It is
7     *      the only place where information is kept about the screen layout
8     *      of text widgets.
9     *
10     * Copyright (c) 1992-1994 The Regents of the University of California.
11     * Copyright (c) 1994-1997 Sun Microsystems, Inc.
12     *
13     * See the file "license.terms" for information on usage and redistribution
14     * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
15     *
16     * RCS: @(#) $Id: tktextdisp.c,v 1.1.1.1 2001/06/13 05:10:16 dtashley Exp $
17     */
18    
19    #include "tkPort.h"
20    #include "tkInt.h"
21    #include "tkText.h"
22    
23    #ifdef __WIN32__
24    #include "tkWinInt.h"
25    #endif
26    
27    /*
28     * The following structure describes how to display a range of characters.
29     * The information is generated by scanning all of the tags associated
30     * with the characters and combining that with default information for
31     * the overall widget.  These structures form the hash keys for
32     * dInfoPtr->styleTable.
33     */
34    
35    typedef struct StyleValues {
36        Tk_3DBorder border;         /* Used for drawing background under text.
37                                     * NULL means use widget background. */
38        int borderWidth;            /* Width of 3-D border for background. */
39        int relief;                 /* 3-D relief for background. */
40        Pixmap bgStipple;           /* Stipple bitmap for background.  None
41                                     * means draw solid. */
42        XColor *fgColor;            /* Foreground color for text. */
43        Tk_Font tkfont;             /* Font for displaying text. */
44        Pixmap fgStipple;           /* Stipple bitmap for text and other
45                                     * foreground stuff.   None means draw
46                                     * solid.*/
47        int justify;                /* Justification style for text. */
48        int lMargin1;               /* Left margin, in pixels, for first display
49                                     * line of each text line. */
50        int lMargin2;               /* Left margin, in pixels, for second and
51                                     * later display lines of each text line. */
52        int offset;                 /* Offset in pixels of baseline, relative to
53                                     * baseline of line. */
54        int overstrike;             /* Non-zero means draw overstrike through
55                                     * text. */
56        int rMargin;                /* Right margin, in pixels. */
57        int spacing1;               /* Spacing above first dline in text line. */
58        int spacing2;               /* Spacing between lines of dline. */
59        int spacing3;               /* Spacing below last dline in text line. */
60        TkTextTabArray *tabArrayPtr;/* Locations and types of tab stops (may
61                                     * be NULL). */
62        int underline;              /* Non-zero means draw underline underneath
63                                     * text. */
64        int elide;                  /* Non-zero means draw text */
65        TkWrapMode wrapMode;        /* How to handle wrap-around for this tag.
66                                     * One of TEXT_WRAPMODE_CHAR,
67                                     * TEXT_WRAPMODE_NONE or TEXT_WRAPMODE_WORD.*/
68    } StyleValues;
69    
70    /*
71     * The following structure extends the StyleValues structure above with
72     * graphics contexts used to actually draw the characters.  The entries
73     * in dInfoPtr->styleTable point to structures of this type.
74     */
75    
76    typedef struct TextStyle {
77        int refCount;               /* Number of times this structure is
78                                     * referenced in Chunks. */
79        GC bgGC;                    /* Graphics context for background.  None
80                                     * means use widget background. */
81        GC fgGC;                    /* Graphics context for foreground. */
82        StyleValues *sValuePtr;     /* Raw information from which GCs were
83                                     * derived. */
84        Tcl_HashEntry *hPtr;        /* Pointer to entry in styleTable.  Used
85                                     * to delete entry. */
86    } TextStyle;
87    
88    /*
89     * The following macro determines whether two styles have the same
90     * background so that, for example, no beveled border should be drawn
91     * between them.
92     */
93    
94    #define SAME_BACKGROUND(s1, s2) \
95        (((s1)->sValuePtr->border == (s2)->sValuePtr->border) \
96            && ((s1)->sValuePtr->borderWidth == (s2)->sValuePtr->borderWidth) \
97            && ((s1)->sValuePtr->relief == (s2)->sValuePtr->relief) \
98            && ((s1)->sValuePtr->bgStipple == (s2)->sValuePtr->bgStipple))
99    
100    /*
101     * The following structure describes one line of the display, which may
102     * be either part or all of one line of the text.
103     */
104    
105    typedef struct DLine {
106        TkTextIndex index;          /* Identifies first character in text
107                                     * that is displayed on this line. */
108        int byteCount;              /* Number of bytes accounted for by this
109                                     * display line, including a trailing space
110                                     * or newline that isn't actually displayed. */
111        int y;                      /* Y-position at which line is supposed to
112                                     * be drawn (topmost pixel of rectangular
113                                     * area occupied by line). */
114        int oldY;                   /* Y-position at which line currently
115                                     * appears on display.  -1 means line isn't
116                                     * currently visible on display and must be
117                                     * redrawn.  This is used to move lines by
118                                     * scrolling rather than re-drawing. */
119        int height;                 /* Height of line, in pixels. */
120        int baseline;               /* Offset of text baseline from y, in
121                                     * pixels. */
122        int spaceAbove;             /* How much extra space was added to the
123                                     * top of the line because of spacing
124                                     * options.  This is included in height
125                                     * and baseline. */
126        int spaceBelow;             /* How much extra space was added to the
127                                     * bottom of the line because of spacing
128                                     * options.  This is included in height. */
129        int length;                 /* Total length of line, in pixels. */
130        TkTextDispChunk *chunkPtr;  /* Pointer to first chunk in list of all
131                                     * of those that are displayed on this
132                                     * line of the screen. */
133        struct DLine *nextPtr;      /* Next in list of all display lines for
134                                     * this window.   The list is sorted in
135                                     * order from top to bottom.  Note:  the
136                                     * next DLine doesn't always correspond
137                                     * to the next line of text:  (a) can have
138                                     * multiple DLines for one text line, and
139                                     * (b) can have gaps where DLine's have been
140                                     * deleted because they're out of date. */
141        int flags;                  /* Various flag bits:  see below for values. */
142    } DLine;
143    
144    /*
145     * Flag bits for DLine structures:
146     *
147     * HAS_3D_BORDER -              Non-zero means that at least one of the
148     *                              chunks in this line has a 3D border, so
149     *                              it potentially interacts with 3D borders
150     *                              in neighboring lines (see
151     *                              DisplayLineBackground).
152     * NEW_LAYOUT -                 Non-zero means that the line has been
153     *                              re-layed out since the last time the
154     *                              display was updated.
155     * TOP_LINE -                   Non-zero means that this was the top line
156     *                              in the window the last time that the window
157     *                              was laid out.  This is important because
158     *                              a line may be displayed differently if its
159     *                              at the top or bottom than if it's in the
160     *                              middle (e.g. beveled edges aren't displayed
161     *                              for middle lines if the adjacent line has
162     *                              a similar background).
163     * BOTTOM_LINE -                Non-zero means that this was the bottom line
164     *                              in the window the last time that the window
165     *                              was laid out.
166     * IS_DISABLED -                This Dline cannot be edited.
167     */
168    
169    #define HAS_3D_BORDER   1
170    #define NEW_LAYOUT      2
171    #define TOP_LINE        4
172    #define BOTTOM_LINE     8
173    #define IS_DISABLED    16
174    
175    /*
176     * Overall display information for a text widget:
177     */
178    
179    typedef struct TextDInfo {
180        Tcl_HashTable styleTable;   /* Hash table that maps from StyleValues
181                                     * to TextStyles for this widget. */
182        DLine *dLinePtr;            /* First in list of all display lines for
183                                     * this widget, in order from top to bottom. */
184        GC copyGC;                  /* Graphics context for copying from off-
185                                     * screen pixmaps onto screen. */
186        GC scrollGC;                /* Graphics context for copying from one place
187                                     * in the window to another (scrolling):
188                                     * differs from copyGC in that we need to get
189                                     * GraphicsExpose events. */
190        int x;                      /* First x-coordinate that may be used for
191                                     * actually displaying line information.
192                                     * Leaves space for border, etc. */
193        int y;                      /* First y-coordinate that may be used for
194                                     * actually displaying line information.
195                                     * Leaves space for border, etc. */
196        int maxX;                   /* First x-coordinate to right of available
197                                     * space for displaying lines. */
198        int maxY;                   /* First y-coordinate below available
199                                     * space for displaying lines. */
200        int topOfEof;               /* Top-most pixel (lowest y-value) that has
201                                     * been drawn in the appropriate fashion for
202                                     * the portion of the window after the last
203                                     * line of the text.  This field is used to
204                                     * figure out when to redraw part or all of
205                                     * the eof field. */
206    
207        /*
208         * Information used for scrolling:
209         */
210    
211        int newByteOffset;          /* Desired x scroll position, measured as the
212                                     * number of average-size characters off-screen
213                                     * to the left for a line with no left
214                                     * margin. */
215        int curPixelOffset;         /* Actual x scroll position, measured as the
216                                     * number of pixels off-screen to the left. */
217        int maxLength;              /* Length in pixels of longest line that's
218                                     * visible in window (length may exceed window
219                                     * size).  If there's no wrapping, this will
220                                     * be zero. */
221        double xScrollFirst, xScrollLast;
222                                    /* Most recent values reported to horizontal
223                                     * scrollbar;  used to eliminate unnecessary
224                                     * reports. */
225        double yScrollFirst, yScrollLast;
226                                    /* Most recent values reported to vertical
227                                     * scrollbar;  used to eliminate unnecessary
228                                     * reports. */
229    
230        /*
231         * The following information is used to implement scanning:
232         */
233    
234        int scanMarkIndex;          /* Byte index of character that was at the
235                                     * left edge of the window when the scan
236                                     * started. */
237        int scanMarkX;              /* X-position of mouse at time scan started. */
238        int scanTotalScroll;        /* Total scrolling (in screen lines) that has
239                                     * occurred since scanMarkY was set. */
240        int scanMarkY;              /* Y-position of mouse at time scan started. */
241    
242        /*
243         * Miscellaneous information:
244         */
245    
246        int dLinesInvalidated;      /* This value is set to 1 whenever something
247                                     * happens that invalidates information in
248                                     * DLine structures;  if a redisplay
249                                     * is in progress, it will see this and
250                                     * abort the redisplay.  This is needed
251                                     * because, for example, an embedded window
252                                     * could change its size when it is first
253                                     * displayed, invalidating the DLine that
254                                     * is currently being displayed.  If redisplay
255                                     * continues, it will use freed memory and
256                                     * could dump core. */
257        int flags;                  /* Various flag values:  see below for
258                                     * definitions. */
259    } TextDInfo;
260    
261    /*
262     * In TkTextDispChunk structures for character segments, the clientData
263     * field points to one of the following structures:
264     */
265    
266    typedef struct CharInfo {
267        int numBytes;               /* Number of bytes to display. */
268        char chars[4];              /* UTF characters to display.  Actual size
269                                     * will be numBytes, not 4.  THIS MUST BE
270                                     * THE LAST FIELD IN THE STRUCTURE. */
271    } CharInfo;
272    
273    /*
274     * Flag values for TextDInfo structures:
275     *
276     * DINFO_OUT_OF_DATE:           Non-zero means that the DLine structures
277     *                              for this window are partially or completely
278     *                              out of date and need to be recomputed.
279     * REDRAW_PENDING:              Means that a when-idle handler has been
280     *                              scheduled to update the display.
281     * REDRAW_BORDERS:              Means window border or pad area has
282     *                              potentially been damaged and must be redrawn.
283     * REPICK_NEEDED:               1 means that the widget has been modified
284     *                              in a way that could change the current
285     *                              character (a different character might be
286     *                              under the mouse cursor now).  Need to
287     *                              recompute the current character before
288     *                              the next redisplay.
289     */
290    
291    #define DINFO_OUT_OF_DATE       1
292    #define REDRAW_PENDING          2
293    #define REDRAW_BORDERS          4
294    #define REPICK_NEEDED           8
295    
296    /*
297     * The following counters keep statistics about redisplay that can be
298     * checked to see how clever this code is at reducing redisplays.
299     */
300    
301    static int numRedisplays;       /* Number of calls to DisplayText. */
302    static int linesRedrawn;        /* Number of calls to DisplayDLine. */
303    static int numCopies;           /* Number of calls to XCopyArea to copy part
304                                     * of the screen. */
305    
306    /*
307     * Forward declarations for procedures defined later in this file:
308     */
309    
310    static void             AdjustForTab _ANSI_ARGS_((TkText *textPtr,
311                                TkTextTabArray *tabArrayPtr, int index,
312                                TkTextDispChunk *chunkPtr));
313    static void             CharBboxProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr,
314                                int index, int y, int lineHeight, int baseline,
315                                int *xPtr, int *yPtr, int *widthPtr,
316                                int *heightPtr));
317    static void             CharDisplayProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr,
318                                int x, int y, int height, int baseline,
319                                Display *display, Drawable dst, int screenY));
320    static int              CharMeasureProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr,
321                                int x));
322    static void             CharUndisplayProc _ANSI_ARGS_((TkText *textPtr,
323                                TkTextDispChunk *chunkPtr));
324    
325    /*
326       Definitions of elided procs.
327       Compiler can't inline these since we use pointers to these functions.
328       ElideDisplayProc, ElideUndisplayProc special-cased for speed,
329       as potentially many elided DLine chunks if large, tag toggle-filled
330       elided region.
331    */
332    static void             ElideBboxProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr,
333                                int index, int y, int lineHeight, int baseline,
334                                int *xPtr, int *yPtr, int *widthPtr,
335                                int *heightPtr));
336    static int              ElideMeasureProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr,
337                                int x));
338    
339    static void             DisplayDLine _ANSI_ARGS_((TkText *textPtr,
340                                DLine *dlPtr, DLine *prevPtr, Pixmap pixmap));
341    static void             DisplayLineBackground _ANSI_ARGS_((TkText *textPtr,
342                                DLine *dlPtr, DLine *prevPtr, Pixmap pixmap));
343    static void             DisplayText _ANSI_ARGS_((ClientData clientData));
344    static DLine *          FindDLine _ANSI_ARGS_((DLine *dlPtr,
345                                TkTextIndex *indexPtr));
346    static void             FreeDLines _ANSI_ARGS_((TkText *textPtr,
347                                DLine *firstPtr, DLine *lastPtr, int unlink));
348    static void             FreeStyle _ANSI_ARGS_((TkText *textPtr,
349                                TextStyle *stylePtr));
350    static TextStyle *      GetStyle _ANSI_ARGS_((TkText *textPtr,
351                                TkTextIndex *indexPtr));
352    static void             GetXView _ANSI_ARGS_((Tcl_Interp *interp,
353                                TkText *textPtr, int report));
354    static void             GetYView _ANSI_ARGS_((Tcl_Interp *interp,
355                                TkText *textPtr, int report));
356    static DLine *          LayoutDLine _ANSI_ARGS_((TkText *textPtr,
357                                TkTextIndex *indexPtr));
358    static int              MeasureChars _ANSI_ARGS_((Tk_Font tkfont,
359                                CONST char *source, int maxBytes, int startX,
360                                int maxX, int tabOrigin, int *nextXPtr));
361    static void             MeasureUp _ANSI_ARGS_((TkText *textPtr,
362                                TkTextIndex *srcPtr, int distance,
363                                TkTextIndex *dstPtr));
364    static int              NextTabStop _ANSI_ARGS_((Tk_Font tkfont, int x,
365                                int tabOrigin));
366    static void             UpdateDisplayInfo _ANSI_ARGS_((TkText *textPtr));
367    static void             ScrollByLines _ANSI_ARGS_((TkText *textPtr,
368                                int offset));
369    static int              SizeOfTab _ANSI_ARGS_((TkText *textPtr,
370                                TkTextTabArray *tabArrayPtr, int index, int x,
371                                int maxX));
372    static void             TextInvalidateRegion _ANSI_ARGS_((TkText *textPtr,
373                                TkRegion region));
374    
375    
376    /*
377     *----------------------------------------------------------------------
378     *
379     * TkTextCreateDInfo --
380     *
381     *      This procedure is called when a new text widget is created.
382     *      Its job is to set up display-related information for the widget.
383     *
384     * Results:
385     *      None.
386     *
387     * Side effects:
388     *      A TextDInfo data structure is allocated and initialized and attached
389     *      to textPtr.
390     *
391     *----------------------------------------------------------------------
392     */
393    
394    void
395    TkTextCreateDInfo(textPtr)
396        TkText *textPtr;            /* Overall information for text widget. */
397    {
398        register TextDInfo *dInfoPtr;
399        XGCValues gcValues;
400    
401        dInfoPtr = (TextDInfo *) ckalloc(sizeof(TextDInfo));
402        Tcl_InitHashTable(&dInfoPtr->styleTable, sizeof(StyleValues)/sizeof(int));
403        dInfoPtr->dLinePtr = NULL;
404        dInfoPtr->copyGC = None;
405        gcValues.graphics_exposures = True;
406        dInfoPtr->scrollGC = Tk_GetGC(textPtr->tkwin, GCGraphicsExposures,
407                &gcValues);
408        dInfoPtr->topOfEof = 0;
409        dInfoPtr->newByteOffset = 0;
410        dInfoPtr->curPixelOffset = 0;
411        dInfoPtr->maxLength = 0;
412        dInfoPtr->xScrollFirst = -1;
413        dInfoPtr->xScrollLast = -1;
414        dInfoPtr->yScrollFirst = -1;
415        dInfoPtr->yScrollLast = -1;
416        dInfoPtr->scanMarkIndex = 0;
417        dInfoPtr->scanMarkX = 0;
418        dInfoPtr->scanTotalScroll = 0;
419        dInfoPtr->scanMarkY = 0;
420        dInfoPtr->dLinesInvalidated = 0;
421        dInfoPtr->flags = DINFO_OUT_OF_DATE;
422        textPtr->dInfoPtr = dInfoPtr;
423    }
424    
425    /*
426     *----------------------------------------------------------------------
427     *
428     * TkTextFreeDInfo --
429     *
430     *      This procedure is called to free up all of the private display
431     *      information kept by this file for a text widget.
432     *
433     * Results:
434     *      None.
435     *
436     * Side effects:
437     *      Lots of resources get freed.
438     *
439     *----------------------------------------------------------------------
440     */
441    
442    void
443    TkTextFreeDInfo(textPtr)
444        TkText *textPtr;            /* Overall information for text widget. */
445    {
446        register TextDInfo *dInfoPtr = textPtr->dInfoPtr;
447    
448        /*
449         * Be careful to free up styleTable *after* freeing up all the
450         * DLines, so that the hash table is still intact to free up the
451         * style-related information from the lines.  Once the lines are
452         * all free then styleTable will be empty.
453         */
454    
455        FreeDLines(textPtr, dInfoPtr->dLinePtr, (DLine *) NULL, 1);
456        Tcl_DeleteHashTable(&dInfoPtr->styleTable);
457        if (dInfoPtr->copyGC != None) {
458            Tk_FreeGC(textPtr->display, dInfoPtr->copyGC);
459        }
460        Tk_FreeGC(textPtr->display, dInfoPtr->scrollGC);
461        if (dInfoPtr->flags & REDRAW_PENDING) {
462            Tcl_CancelIdleCall(DisplayText, (ClientData) textPtr);
463        }
464        ckfree((char *) dInfoPtr);
465    }
466    
467    /*
468     *----------------------------------------------------------------------
469     *
470     * GetStyle --
471     *
472     *      This procedure creates all the information needed to display
473     *      text at a particular location.
474     *
475     * Results:
476     *      The return value is a pointer to a TextStyle structure that
477     *      corresponds to *sValuePtr.
478     *
479     * Side effects:
480     *      A new entry may be created in the style table for the widget.
481     *
482     *----------------------------------------------------------------------
483     */
484    
485    static TextStyle *
486    GetStyle(textPtr, indexPtr)
487        TkText *textPtr;            /* Overall information about text widget. */
488        TkTextIndex *indexPtr;      /* The character in the text for which
489                                     * display information is wanted. */
490    {
491        TkTextTag **tagPtrs;
492        register TkTextTag *tagPtr;
493        StyleValues styleValues;
494        TextStyle *stylePtr;
495        Tcl_HashEntry *hPtr;
496        int numTags, new, i;
497        XGCValues gcValues;
498        unsigned long mask;
499    
500        /*
501         * The variables below keep track of the highest-priority specification
502         * that has occurred for each of the various fields of the StyleValues.
503         */
504    
505        int borderPrio, borderWidthPrio, reliefPrio, bgStipplePrio;
506        int fgPrio, fontPrio, fgStipplePrio;
507        int underlinePrio, elidePrio, justifyPrio, offsetPrio;
508        int lMargin1Prio, lMargin2Prio, rMarginPrio;
509        int spacing1Prio, spacing2Prio, spacing3Prio;
510        int overstrikePrio, tabPrio, wrapPrio;
511    
512        /*
513         * Find out what tags are present for the character, then compute
514         * a StyleValues structure corresponding to those tags (scan
515         * through all of the tags, saving information for the highest-
516         * priority tag).
517         */
518    
519        tagPtrs = TkBTreeGetTags(indexPtr, &numTags);
520        borderPrio = borderWidthPrio = reliefPrio = bgStipplePrio = -1;
521        fgPrio = fontPrio = fgStipplePrio = -1;
522        underlinePrio = elidePrio = justifyPrio = offsetPrio = -1;
523        lMargin1Prio = lMargin2Prio = rMarginPrio = -1;
524        spacing1Prio = spacing2Prio = spacing3Prio = -1;
525        overstrikePrio = tabPrio = wrapPrio = -1;
526        memset((VOID *) &styleValues, 0, sizeof(StyleValues));
527        styleValues.relief = TK_RELIEF_FLAT;
528        styleValues.fgColor = textPtr->fgColor;
529        styleValues.tkfont = textPtr->tkfont;
530        styleValues.justify = TK_JUSTIFY_LEFT;
531        styleValues.spacing1 = textPtr->spacing1;
532        styleValues.spacing2 = textPtr->spacing2;
533        styleValues.spacing3 = textPtr->spacing3;
534        styleValues.tabArrayPtr = textPtr->tabArrayPtr;
535        styleValues.wrapMode = textPtr->wrapMode;
536        styleValues.elide = 0;
537        for (i = 0 ; i < numTags; i++) {
538            tagPtr = tagPtrs[i];
539    
540            /*
541             * On Windows and Mac, we need to skip the selection tag if
542             * we don't have focus.
543             */
544    
545    #ifndef ALWAYS_SHOW_SELECTION
546            if ((tagPtr == textPtr->selTagPtr) && !(textPtr->flags & GOT_FOCUS)) {
547                continue;
548            }
549    #endif
550    
551            if ((tagPtr->border != NULL) && (tagPtr->priority > borderPrio)) {
552                styleValues.border = tagPtr->border;
553                borderPrio = tagPtr->priority;
554            }
555            if ((tagPtr->bdString != NULL)
556                    && (tagPtr->priority > borderWidthPrio)) {
557                styleValues.borderWidth = tagPtr->borderWidth;
558                borderWidthPrio = tagPtr->priority;
559            }
560            if ((tagPtr->reliefString != NULL)
561                    && (tagPtr->priority > reliefPrio)) {
562                if (styleValues.border == NULL) {
563                    styleValues.border = textPtr->border;
564                }
565                styleValues.relief = tagPtr->relief;
566                reliefPrio = tagPtr->priority;
567            }
568            if ((tagPtr->bgStipple != None)
569                    && (tagPtr->priority > bgStipplePrio)) {
570                styleValues.bgStipple = tagPtr->bgStipple;
571                bgStipplePrio = tagPtr->priority;
572            }
573            if ((tagPtr->fgColor != None) && (tagPtr->priority > fgPrio)) {
574                styleValues.fgColor = tagPtr->fgColor;
575                fgPrio = tagPtr->priority;
576            }
577            if ((tagPtr->tkfont != None) && (tagPtr->priority > fontPrio)) {
578                styleValues.tkfont = tagPtr->tkfont;
579                fontPrio = tagPtr->priority;
580            }
581            if ((tagPtr->fgStipple != None)
582                    && (tagPtr->priority > fgStipplePrio)) {
583                styleValues.fgStipple = tagPtr->fgStipple;
584                fgStipplePrio = tagPtr->priority;
585            }
586            if ((tagPtr->justifyString != NULL)
587                    && (tagPtr->priority > justifyPrio)) {
588                styleValues.justify = tagPtr->justify;
589                justifyPrio = tagPtr->priority;
590            }
591            if ((tagPtr->lMargin1String != NULL)
592                    && (tagPtr->priority > lMargin1Prio)) {
593                styleValues.lMargin1 = tagPtr->lMargin1;
594                lMargin1Prio = tagPtr->priority;
595            }
596            if ((tagPtr->lMargin2String != NULL)
597                    && (tagPtr->priority > lMargin2Prio)) {
598                styleValues.lMargin2 = tagPtr->lMargin2;
599                lMargin2Prio = tagPtr->priority;
600            }
601            if ((tagPtr->offsetString != NULL)
602                    && (tagPtr->priority > offsetPrio)) {
603                styleValues.offset = tagPtr->offset;
604                offsetPrio = tagPtr->priority;
605            }
606            if ((tagPtr->overstrikeString != NULL)
607                    && (tagPtr->priority > overstrikePrio)) {
608                styleValues.overstrike = tagPtr->overstrike;
609                overstrikePrio = tagPtr->priority;
610            }
611            if ((tagPtr->rMarginString != NULL)
612                    && (tagPtr->priority > rMarginPrio)) {
613                styleValues.rMargin = tagPtr->rMargin;
614                rMarginPrio = tagPtr->priority;
615            }
616            if ((tagPtr->spacing1String != NULL)
617                    && (tagPtr->priority > spacing1Prio)) {
618                styleValues.spacing1 = tagPtr->spacing1;
619                spacing1Prio = tagPtr->priority;
620            }
621            if ((tagPtr->spacing2String != NULL)
622                    && (tagPtr->priority > spacing2Prio)) {
623                styleValues.spacing2 = tagPtr->spacing2;
624                spacing2Prio = tagPtr->priority;
625            }
626            if ((tagPtr->spacing3String != NULL)
627                    && (tagPtr->priority > spacing3Prio)) {
628                styleValues.spacing3 = tagPtr->spacing3;
629                spacing3Prio = tagPtr->priority;
630            }
631            if ((tagPtr->tabString != NULL)
632                    && (tagPtr->priority > tabPrio)) {
633                styleValues.tabArrayPtr = tagPtr->tabArrayPtr;
634                tabPrio = tagPtr->priority;
635            }
636            if ((tagPtr->underlineString != NULL)
637                    && (tagPtr->priority > underlinePrio)) {
638                styleValues.underline = tagPtr->underline;
639                underlinePrio = tagPtr->priority;
640            }
641            if ((tagPtr->elideString != NULL)
642                    && (tagPtr->priority > elidePrio)) {
643                styleValues.elide = tagPtr->elide;
644                elidePrio = tagPtr->priority;
645            }
646            if ((tagPtr->wrapMode != TEXT_WRAPMODE_NULL)
647                    && (tagPtr->priority > wrapPrio)) {
648                styleValues.wrapMode = tagPtr->wrapMode;
649                wrapPrio = tagPtr->priority;
650            }
651        }
652        if (tagPtrs != NULL) {
653            ckfree((char *) tagPtrs);
654        }
655    
656        /*
657         * Use an existing style if there's one around that matches.
658         */
659    
660        hPtr = Tcl_CreateHashEntry(&textPtr->dInfoPtr->styleTable,
661                (char *) &styleValues, &new);
662        if (!new) {
663            stylePtr = (TextStyle *) Tcl_GetHashValue(hPtr);
664            stylePtr->refCount++;
665            return stylePtr;
666        }
667    
668        /*
669         * No existing style matched.  Make a new one.
670         */
671    
672        stylePtr = (TextStyle *) ckalloc(sizeof(TextStyle));
673        stylePtr->refCount = 1;
674        if (styleValues.border != NULL) {
675            gcValues.foreground = Tk_3DBorderColor(styleValues.border)->pixel;
676            mask = GCForeground;
677            if (styleValues.bgStipple != None) {
678                gcValues.stipple = styleValues.bgStipple;
679                gcValues.fill_style = FillStippled;
680                mask |= GCStipple|GCFillStyle;
681            }
682            stylePtr->bgGC = Tk_GetGC(textPtr->tkwin, mask, &gcValues);
683        } else {
684            stylePtr->bgGC = None;
685        }
686        mask = GCFont;
687        gcValues.font = Tk_FontId(styleValues.tkfont);
688        mask |= GCForeground;
689        gcValues.foreground = styleValues.fgColor->pixel;
690        if (styleValues.fgStipple != None) {
691            gcValues.stipple = styleValues.fgStipple;
692            gcValues.fill_style = FillStippled;
693            mask |= GCStipple|GCFillStyle;
694        }
695        stylePtr->fgGC = Tk_GetGC(textPtr->tkwin, mask, &gcValues);
696        stylePtr->sValuePtr = (StyleValues *)
697                Tcl_GetHashKey(&textPtr->dInfoPtr->styleTable, hPtr);
698        stylePtr->hPtr = hPtr;
699        Tcl_SetHashValue(hPtr, stylePtr);
700        return stylePtr;
701    }
702    
703    /*
704     *----------------------------------------------------------------------
705     *
706     * FreeStyle --
707     *
708     *      This procedure is called when a TextStyle structure is no longer
709     *      needed.  It decrements the reference count and frees up the
710     *      space for the style structure if the reference count is 0.
711     *
712     * Results:
713     *      None.
714     *
715     * Side effects:
716     *      The storage and other resources associated with the style
717     *      are freed up if no-one's still using it.
718     *
719     *----------------------------------------------------------------------
720     */
721    
722    static void
723    FreeStyle(textPtr, stylePtr)
724        TkText *textPtr;                    /* Information about overall widget. */
725        register TextStyle *stylePtr;       /* Information about style to free. */
726    
727    {
728        stylePtr->refCount--;
729        if (stylePtr->refCount == 0) {
730            if (stylePtr->bgGC != None) {
731                Tk_FreeGC(textPtr->display, stylePtr->bgGC);
732            }
733            if (stylePtr->fgGC != None) {
734                Tk_FreeGC(textPtr->display, stylePtr->fgGC);
735            }
736            Tcl_DeleteHashEntry(stylePtr->hPtr);
737            ckfree((char *) stylePtr);
738        }
739    }
740    
741    /*
742     *----------------------------------------------------------------------
743     *
744     * LayoutDLine --
745     *
746     *      This procedure generates a single DLine structure for a display
747     *      line whose leftmost character is given by indexPtr.
748     *      
749     * Results:
750     *      The return value is a pointer to a DLine structure desribing the
751     *      display line.  All fields are filled in and correct except for
752     *      y and nextPtr.
753     *
754     * Side effects:
755     *      Storage is allocated for the new DLine.
756     *
757     *----------------------------------------------------------------------
758     */
759    
760    static DLine *
761    LayoutDLine(textPtr, indexPtr)
762        TkText *textPtr;            /* Overall information about text widget. */
763        TkTextIndex *indexPtr;      /* Beginning of display line.  May not
764                                     * necessarily point to a character segment. */
765    {
766        register DLine *dlPtr;              /* New display line. */
767        TkTextSegment *segPtr;              /* Current segment in text. */
768        TkTextDispChunk *lastChunkPtr;      /* Last chunk allocated so far
769                                             * for line. */
770        TkTextDispChunk *chunkPtr;          /* Current chunk. */
771        TkTextIndex curIndex;
772        TkTextDispChunk *breakChunkPtr;     /* Chunk containing best word break
773                                             * point, if any. */
774        TkTextIndex breakIndex;             /* Index of first character in
775                                             * breakChunkPtr. */
776        int breakByteOffset;                /* Byte offset of character within
777                                             * breakChunkPtr just to right of best
778                                             * break point. */
779        int noCharsYet;                     /* Non-zero means that no characters
780                                             * have been placed on the line yet. */
781        int justify;                        /* How to justify line: taken from
782                                             * style for the first character in
783                                             * line. */
784        int jIndent;                        /* Additional indentation (beyond
785                                             * margins) due to justification. */
786        int rMargin;                        /* Right margin width for line. */
787        TkWrapMode wrapMode;                /* Wrap mode to use for this line. */
788        int x = 0, maxX = 0;                /* Initializations needed only to
789                                             * stop compiler warnings. */
790        int wholeLine;                      /* Non-zero means this display line
791                                             * runs to the end of the text line. */
792        int tabIndex;                       /* Index of the current tab stop. */
793        int gotTab;                         /* Non-zero means the current chunk
794                                             * contains a tab. */
795        TkTextDispChunk *tabChunkPtr;       /* Pointer to the chunk containing
796                                             * the previous tab stop. */
797        int maxBytes;                       /* Maximum number of bytes to
798                                             * include in this chunk. */
799        TkTextTabArray *tabArrayPtr;        /* Tab stops for line; taken from
800                                             * style for the first character on
801                                             * line. */
802        int tabSize;                        /* Number of pixels consumed by current
803                                             * tab stop. */
804        TkTextDispChunk *lastCharChunkPtr;  /* Pointer to last chunk in display
805                                             * lines with numBytes > 0.  Used to
806                                             * drop 0-sized chunks from the end
807                                             * of the line. */
808        int byteOffset, ascent, descent, code, elide, elidesize;
809        StyleValues *sValuePtr;
810    
811        /*
812         * Create and initialize a new DLine structure.
813         */
814    
815        dlPtr = (DLine *) ckalloc(sizeof(DLine));
816        dlPtr->index = *indexPtr;
817        dlPtr->byteCount = 0;
818        dlPtr->y = 0;
819        dlPtr->oldY = -1;
820        dlPtr->height = 0;
821        dlPtr->baseline = 0;
822        dlPtr->chunkPtr = NULL;
823        dlPtr->nextPtr = NULL;
824        dlPtr->flags = NEW_LAYOUT;
825    
826        /*
827         * Special case entirely elide line as there may be 1000s or more
828         */
829        elide = TkTextIsElided(textPtr, indexPtr);          /* save a malloc */
830        if (elide && indexPtr->byteIndex==0) {
831            maxBytes = 0;
832            for (segPtr = indexPtr->linePtr->segPtr;
833                 elide && (segPtr != NULL);
834                 segPtr = segPtr->nextPtr) {
835                if ((elidesize = segPtr->size) > 0) {
836                    maxBytes += elidesize;
837                    /*
838                     * If have we have a tag toggle, there is a chance
839                     * that invisibility state changed, so bail out
840                     */
841                } else if ((segPtr->typePtr == &tkTextToggleOffType)
842                        || (segPtr->typePtr == &tkTextToggleOnType)) {
843                    if (segPtr->body.toggle.tagPtr->elideString != NULL) {
844                        elide = (segPtr->typePtr == &tkTextToggleOffType)
845                            ^ segPtr->body.toggle.tagPtr->elide;
846                    }
847                }
848            }
849    
850            if (elide) {
851                dlPtr->byteCount = maxBytes;
852                dlPtr->spaceAbove = dlPtr->spaceBelow = dlPtr->length = 0;
853                return dlPtr;
854            }
855        }
856    
857        /*
858         * Each iteration of the loop below creates one TkTextDispChunk for
859         * the new display line.  The line will always have at least one
860         * chunk (for the newline character at the end, if there's nothing
861         * else available).
862         */
863    
864        curIndex = *indexPtr;
865        lastChunkPtr = NULL;
866        chunkPtr = NULL;
867        noCharsYet = 1;
868        elide = 0;
869        breakChunkPtr = NULL;
870        breakByteOffset = 0;
871        justify = TK_JUSTIFY_LEFT;
872        tabIndex = -1;
873        tabChunkPtr = NULL;
874        tabArrayPtr = NULL;
875        rMargin = 0;
876        wrapMode = TEXT_WRAPMODE_CHAR;
877        tabSize = 0;
878        lastCharChunkPtr = NULL;
879    
880        /*
881         * Find the first segment to consider for the line.  Can't call
882         * TkTextIndexToSeg for this because it won't return a segment
883         * with zero size (such as the insertion cursor's mark).
884         */
885    
886        for (byteOffset = curIndex.byteIndex, segPtr = curIndex.linePtr->segPtr;
887             (byteOffset > 0) && (byteOffset >= segPtr->size);
888             byteOffset -= segPtr->size, segPtr = segPtr->nextPtr) {
889            /* Empty loop body. */
890        }
891    
892        while (segPtr != NULL) {
893            /*
894             * Every line still gets at least one chunk due to expectations
895             * in the rest of the code, but we are able to skip elided portions
896             * of the line quickly.
897             * If current chunk is elided and last chunk was too, coalese
898             */
899            if (elide && (lastChunkPtr != NULL)
900                    && (lastChunkPtr->displayProc == NULL /*ElideDisplayProc*/)) {
901                if ((elidesize = segPtr->size - byteOffset) > 0) {
902                    curIndex.byteIndex += elidesize;
903                    lastChunkPtr->numBytes += elidesize;
904                    breakByteOffset = lastChunkPtr->breakIndex = lastChunkPtr->numBytes;
905                    /*
906                     * If have we have a tag toggle, there is a chance
907                     * that invisibility state changed, so bail out
908                     */
909                } else if ((segPtr->typePtr == &tkTextToggleOffType)
910                        || (segPtr->typePtr == &tkTextToggleOnType)) {
911                    if (segPtr->body.toggle.tagPtr->elideString != NULL) {
912                        elide = (segPtr->typePtr == &tkTextToggleOffType)
913                            ^ segPtr->body.toggle.tagPtr->elide;
914                    }
915                }
916    
917                byteOffset = 0;
918                segPtr = segPtr->nextPtr;
919                if (segPtr == NULL && chunkPtr != NULL) {
920                    ckfree((char *) chunkPtr);
921                }
922                continue;
923            }
924    
925            if (segPtr->typePtr->layoutProc == NULL) {
926                segPtr = segPtr->nextPtr;
927                byteOffset = 0;
928                continue;
929            }
930            if (chunkPtr == NULL) {
931                chunkPtr = (TkTextDispChunk *) ckalloc(sizeof(TkTextDispChunk));
932                chunkPtr->nextPtr = NULL;
933            }
934            chunkPtr->stylePtr = GetStyle(textPtr, &curIndex);
935            elide = chunkPtr->stylePtr->sValuePtr->elide;
936    
937            /*
938             * Save style information such as justification and indentation,
939             * up until the first character is encountered, then retain that
940             * information for the rest of the line.
941             */
942    
943            if (noCharsYet) {
944                tabArrayPtr = chunkPtr->stylePtr->sValuePtr->tabArrayPtr;
945                justify = chunkPtr->stylePtr->sValuePtr->justify;
946                rMargin = chunkPtr->stylePtr->sValuePtr->rMargin;
947                wrapMode = chunkPtr->stylePtr->sValuePtr->wrapMode;
948                x = ((curIndex.byteIndex == 0)
949                        ? chunkPtr->stylePtr->sValuePtr->lMargin1
950                        : chunkPtr->stylePtr->sValuePtr->lMargin2);
951                if (wrapMode == TEXT_WRAPMODE_NONE) {
952                    maxX = -1;
953                } else {
954                    maxX = textPtr->dInfoPtr->maxX - textPtr->dInfoPtr->x
955                            - rMargin;
956                    if (maxX < x) {
957                        maxX = x;
958                    }
959                }
960            }
961    
962            /*
963             * See if there is a tab in the current chunk; if so, only
964             * layout characters up to (and including) the tab.
965             */
966    
967            gotTab = 0;
968            maxBytes = segPtr->size - byteOffset;
969            if (!elide && justify == TK_JUSTIFY_LEFT) {
970                if (segPtr->typePtr == &tkTextCharType) {
971                    char *p;
972    
973                    for (p = segPtr->body.chars  + byteOffset; *p != 0; p++) {
974                        if (*p == '\t') {
975                            maxBytes = (p + 1 - segPtr->body.chars) - byteOffset;
976                            gotTab = 1;
977                            break;
978                        }
979                    }
980                }
981            }
982            chunkPtr->x = x;
983            if (elide && maxBytes) {
984                /* don't free style here, as other code expects to be able to do that */
985                /*breakByteOffset =*/ chunkPtr->breakIndex = chunkPtr->numBytes = maxBytes;
986                chunkPtr->width = 0;
987                chunkPtr->minAscent = chunkPtr->minDescent = chunkPtr->minHeight = 0;
988    
989                /* would just like to point to canonical empty chunk */
990                chunkPtr->displayProc = (Tk_ChunkDisplayProc *) NULL;
991                chunkPtr->undisplayProc = (Tk_ChunkUndisplayProc *) NULL;
992                chunkPtr->measureProc = ElideMeasureProc;
993                chunkPtr->bboxProc = ElideBboxProc;
994    
995                code = 1;
996            } else
997            code = (*segPtr->typePtr->layoutProc)(textPtr, &curIndex, segPtr,
998                    byteOffset, maxX-tabSize, maxBytes, noCharsYet, wrapMode,
999                    chunkPtr);
1000            if (code <= 0) {
1001                FreeStyle(textPtr, chunkPtr->stylePtr);
1002                if (code < 0) {
1003                    /*
1004                     * This segment doesn't wish to display itself (e.g. most
1005                     * marks).
1006                     */
1007    
1008                    segPtr = segPtr->nextPtr;
1009                    byteOffset = 0;
1010                    continue;
1011                }
1012    
1013                /*
1014                 * No characters from this segment fit in the window: this
1015                 * means we're at the end of the display line.
1016                 */
1017    
1018                if (chunkPtr != NULL) {
1019                    ckfree((char *) chunkPtr);
1020                }
1021                break;
1022            }
1023            if (chunkPtr->numBytes > 0) {
1024                noCharsYet = 0;
1025                lastCharChunkPtr = chunkPtr;
1026            }
1027            if (lastChunkPtr == NULL) {
1028                dlPtr->chunkPtr = chunkPtr;
1029            } else {
1030                lastChunkPtr->nextPtr = chunkPtr;
1031            }
1032            lastChunkPtr = chunkPtr;
1033            x += chunkPtr->width;
1034            if (chunkPtr->breakIndex > 0) {
1035                breakByteOffset = chunkPtr->breakIndex;
1036                breakIndex = curIndex;
1037                breakChunkPtr = chunkPtr;
1038            }
1039            if (chunkPtr->numBytes != maxBytes) {
1040                break;
1041            }
1042    
1043            /*
1044             * If we're at a new tab, adjust the layout for all the chunks
1045             * pertaining to the previous tab.  Also adjust the amount of
1046             * space left in the line to account for space that will be eaten
1047             * up by the tab.
1048             */
1049    
1050            if (gotTab) {
1051                if (tabIndex >= 0) {
1052                    AdjustForTab(textPtr, tabArrayPtr, tabIndex, tabChunkPtr);
1053                    x = chunkPtr->x + chunkPtr->width;
1054                }
1055                tabIndex++;
1056                tabChunkPtr = chunkPtr;
1057                tabSize = SizeOfTab(textPtr, tabArrayPtr, tabIndex, x, maxX);
1058                if ((maxX >= 0) && (tabSize >= maxX - x)) {
1059                    break;
1060                }
1061            }
1062            curIndex.byteIndex += chunkPtr->numBytes;
1063            byteOffset += chunkPtr->numBytes;
1064            if (byteOffset >= segPtr->size) {
1065                byteOffset = 0;
1066                segPtr = segPtr->nextPtr;
1067            }
1068    
1069            chunkPtr = NULL;
1070        }
1071        if (noCharsYet) {
1072            panic("LayoutDLine couldn't place any characters on a line");
1073        }
1074        wholeLine = (segPtr == NULL);
1075    
1076        /*
1077         * We're at the end of the display line.  Throw away everything
1078         * after the most recent word break, if there is one;  this may
1079         * potentially require the last chunk to be layed out again.
1080         */
1081    
1082        if (breakChunkPtr == NULL) {
1083            /*
1084             * This code makes sure that we don't accidentally display
1085             * chunks with no characters at the end of the line (such as
1086             * the insertion cursor).  These chunks belong on the next
1087             * line.  So, throw away everything after the last chunk that
1088             * has characters in it.
1089             */
1090    
1091            breakChunkPtr = lastCharChunkPtr;
1092            breakByteOffset = breakChunkPtr->numBytes;
1093        }
1094        if ((breakChunkPtr != NULL) && ((lastChunkPtr != breakChunkPtr)
1095                || (breakByteOffset != lastChunkPtr->numBytes))) {
1096            while (1) {
1097                chunkPtr = breakChunkPtr->nextPtr;
1098                if (chunkPtr == NULL) {
1099                    break;
1100                }
1101                FreeStyle(textPtr, chunkPtr->stylePtr);
1102                breakChunkPtr->nextPtr = chunkPtr->nextPtr;
1103                (*chunkPtr->undisplayProc)(textPtr, chunkPtr);
1104                ckfree((char *) chunkPtr);
1105            }
1106            if (breakByteOffset != breakChunkPtr->numBytes) {
1107                (*breakChunkPtr->undisplayProc)(textPtr, breakChunkPtr);
1108                segPtr = TkTextIndexToSeg(&breakIndex, &byteOffset);
1109                (*segPtr->typePtr->layoutProc)(textPtr, &breakIndex,
1110                        segPtr, byteOffset, maxX, breakByteOffset, 0,
1111                        wrapMode, breakChunkPtr);
1112            }
1113            lastChunkPtr = breakChunkPtr;
1114            wholeLine = 0;
1115        }
1116    
1117    
1118        /*
1119         * Make tab adjustments for the last tab stop, if there is one.
1120         */
1121    
1122        if ((tabIndex >= 0) && (tabChunkPtr != NULL)) {
1123            AdjustForTab(textPtr, tabArrayPtr, tabIndex, tabChunkPtr);
1124        }
1125    
1126        /*
1127         * Make one more pass over the line to recompute various things
1128         * like its height, length, and total number of bytes.  Also
1129         * modify the x-locations of chunks to reflect justification.
1130         * If we're not wrapping, I'm not sure what is the best way to
1131         * handle left and center justification:  should the total length,
1132         * for purposes of justification, be (a) the window width, (b)
1133         * the length of the longest line in the window, or (c) the length
1134         * of the longest line in the text?  (c) isn't available, (b) seems
1135         * weird, since it can change with vertical scrolling, so (a) is
1136         * what is implemented below.
1137         */
1138    
1139        if (wrapMode == TEXT_WRAPMODE_NONE) {
1140            maxX = textPtr->dInfoPtr->maxX - textPtr->dInfoPtr->x - rMargin;
1141        }
1142        dlPtr->length = lastChunkPtr->x + lastChunkPtr->width;
1143        if (justify == TK_JUSTIFY_LEFT) {
1144            jIndent = 0;
1145        } else if (justify == TK_JUSTIFY_RIGHT) {
1146            jIndent = maxX - dlPtr->length;
1147        } else {
1148            jIndent = (maxX - dlPtr->length)/2;
1149        }
1150        ascent = descent = 0;
1151        for (chunkPtr = dlPtr->chunkPtr; chunkPtr != NULL;
1152                chunkPtr = chunkPtr->nextPtr) {
1153            chunkPtr->x += jIndent;
1154            dlPtr->byteCount += chunkPtr->numBytes;
1155            if (chunkPtr->minAscent > ascent) {
1156                ascent = chunkPtr->minAscent;
1157            }
1158            if (chunkPtr->minDescent > descent) {
1159                descent = chunkPtr->minDescent;
1160            }
1161            if (chunkPtr->minHeight > dlPtr->height) {
1162                dlPtr->height = chunkPtr->minHeight;
1163            }
1164            sValuePtr = chunkPtr->stylePtr->sValuePtr;
1165            if ((sValuePtr->borderWidth > 0)
1166                    && (sValuePtr->relief != TK_RELIEF_FLAT)) {
1167                dlPtr->flags |= HAS_3D_BORDER;
1168            }
1169        }
1170        if (dlPtr->height < (ascent + descent)) {
1171            dlPtr->height = ascent + descent;
1172            dlPtr->baseline = ascent;
1173        } else {
1174            dlPtr->baseline = ascent + (dlPtr->height - ascent - descent)/2;
1175        }
1176        sValuePtr = dlPtr->chunkPtr->stylePtr->sValuePtr;
1177        if (dlPtr->index.byteIndex == 0) {
1178            dlPtr->spaceAbove = sValuePtr->spacing1;
1179        } else {
1180            dlPtr->spaceAbove = sValuePtr->spacing2 - sValuePtr->spacing2/2;
1181        }
1182        if (wholeLine) {
1183            dlPtr->spaceBelow = sValuePtr->spacing3;
1184        } else {
1185            dlPtr->spaceBelow = sValuePtr->spacing2/2;
1186        }
1187        dlPtr->height += dlPtr->spaceAbove + dlPtr->spaceBelow;
1188        dlPtr->baseline += dlPtr->spaceAbove;
1189    
1190        /*
1191         * Recompute line length:  may have changed because of justification.
1192         */
1193    
1194        dlPtr->length = lastChunkPtr->x + lastChunkPtr->width;
1195        return dlPtr;
1196    }
1197    
1198    /*
1199     *----------------------------------------------------------------------
1200     *
1201     * UpdateDisplayInfo --
1202     *
1203     *      This procedure is invoked to recompute some or all of the
1204     *      DLine structures for a text widget.  At the time it is called
1205     *      the DLine structures still left in the widget are guaranteed
1206     *      to be correct except that (a) the y-coordinates aren't
1207     *      necessarily correct, (b) there may be missing structures
1208     *      (the DLine structures get removed as soon as they are potentially
1209     *      out-of-date), and (c) DLine structures that don't start at the
1210     *      beginning of a line may be incorrect if previous information in
1211     *      the same line changed size in a way that moved a line boundary
1212     *      (DLines for any info that changed will have been deleted, but
1213     *      not DLines for unchanged info in the same text line).
1214     *
1215     * Results:
1216     *      None.
1217     *
1218     * Side effects:
1219     *      Upon return, the DLine information for textPtr correctly reflects
1220     *      the positions where characters will be displayed.  However, this
1221     *      procedure doesn't actually bring the display up-to-date.
1222     *
1223     *----------------------------------------------------------------------
1224     */
1225    
1226    static void
1227    UpdateDisplayInfo(textPtr)
1228        TkText *textPtr;                    /* Text widget to update. */
1229    {
1230        register TextDInfo *dInfoPtr = textPtr->dInfoPtr;
1231        register DLine *dlPtr, *prevPtr;
1232        TkTextIndex index;
1233        TkTextLine *lastLinePtr;
1234        int y, maxY, pixelOffset, maxOffset;
1235    
1236        if (!(dInfoPtr->flags & DINFO_OUT_OF_DATE)) {
1237            return;
1238        }
1239        dInfoPtr->flags &= ~DINFO_OUT_OF_DATE;
1240    
1241        /*
1242         * Delete any DLines that are now above the top of the window.
1243         */
1244    
1245        index = textPtr->topIndex;
1246        dlPtr = FindDLine(dInfoPtr->dLinePtr, &index);
1247        if ((dlPtr != NULL) && (dlPtr != dInfoPtr->dLinePtr)) {
1248            FreeDLines(textPtr, dInfoPtr->dLinePtr, dlPtr, 1);
1249        }
1250    
1251        /*
1252         *--------------------------------------------------------------
1253         * Scan through the contents of the window from top to bottom,
1254         * recomputing information for lines that are missing.
1255         *--------------------------------------------------------------
1256         */
1257    
1258        lastLinePtr = TkBTreeFindLine(textPtr->tree,
1259                TkBTreeNumLines(textPtr->tree));
1260        dlPtr = dInfoPtr->dLinePtr;
1261        prevPtr = NULL;
1262        y = dInfoPtr->y;
1263        maxY = dInfoPtr->maxY;
1264        while (1) {
1265            register DLine *newPtr;
1266    
1267            if (index.linePtr == lastLinePtr) {
1268                break;
1269            }
1270    
1271            /*
1272             * There are three possibilities right now:
1273             * (a) the next DLine (dlPtr) corresponds exactly to the next
1274             *     information we want to display: just use it as-is.
1275             * (b) the next DLine corresponds to a different line, or to
1276             *     a segment that will be coming later in the same line:
1277             *     leave this DLine alone in the hopes that we'll be able
1278             *     to use it later, then create a new DLine in front of
1279             *     it.
1280             * (c) the next DLine corresponds to a segment in the line we
1281             *     want, but it's a segment that has already been processed
1282             *     or will never be processed.  Delete the DLine and try
1283             *     again.
1284             *
1285             * One other twist on all this.  It's possible for 3D borders
1286             * to interact between lines (see DisplayLineBackground) so if
1287             * a line is relayed out and has styles with 3D borders, its
1288             * neighbors have to be redrawn if they have 3D borders too,
1289             * since the interactions could have changed (the neighbors
1290             * don't have to be relayed out, just redrawn).
1291             */
1292    
1293            if ((dlPtr == NULL) || (dlPtr->index.linePtr != index.linePtr)) {
1294                /*
1295                 * Case (b) -- must make new DLine.
1296                 */
1297    
1298                makeNewDLine:
1299                if (tkTextDebug) {
1300                    char string[TK_POS_CHARS];
1301    
1302                    /*
1303                     * Debugging is enabled, so keep a log of all the lines
1304                     * that were re-layed out.  The test suite uses this
1305                     * information.
1306                     */
1307    
1308                    TkTextPrintIndex(&index, string);
1309                    Tcl_SetVar2(textPtr->interp, "tk_textRelayout", (char *) NULL,
1310                            string,
1311                            TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT);
1312                }
1313                newPtr = LayoutDLine(textPtr, &index);
1314                if (prevPtr == NULL) {
1315                    dInfoPtr->dLinePtr = newPtr;
1316                } else {
1317                    prevPtr->nextPtr = newPtr;
1318                    if (prevPtr->flags & HAS_3D_BORDER) {
1319                        prevPtr->oldY = -1;
1320                    }
1321                }
1322                newPtr->nextPtr = dlPtr;
1323                dlPtr = newPtr;
1324            } else {
1325                /*
1326                 * DlPtr refers to the line we want.  Next check the
1327                 * index within the line.
1328                 */
1329    
1330                if (index.byteIndex == dlPtr->index.byteIndex) {
1331                    /*
1332                     * Case (a) -- can use existing display line as-is.
1333                     */
1334    
1335                    if ((dlPtr->flags & HAS_3D_BORDER) && (prevPtr != NULL)
1336                            && (prevPtr->flags & (NEW_LAYOUT))) {
1337                        dlPtr->oldY = -1;
1338                    }
1339                    goto lineOK;
1340                }
1341                if (index.byteIndex < dlPtr->index.byteIndex) {
1342                    goto makeNewDLine;
1343                }
1344    
1345                /*
1346                 * Case (c) -- dlPtr is useless.  Discard it and start
1347                 * again with the next display line.
1348                 */
1349    
1350                newPtr = dlPtr->nextPtr;
1351                FreeDLines(textPtr, dlPtr, newPtr, 0);
1352                dlPtr = newPtr;
1353                if (prevPtr != NULL) {
1354                    prevPtr->nextPtr = newPtr;
1355                } else {
1356                    dInfoPtr->dLinePtr = newPtr;
1357                }
1358                continue;
1359            }
1360    
1361            /*
1362             * Advance to the start of the next line.
1363             */
1364    
1365            lineOK:
1366            dlPtr->y = y;
1367            y += dlPtr->height;
1368            TkTextIndexForwBytes(&index, dlPtr->byteCount, &index);
1369            prevPtr = dlPtr;
1370            dlPtr = dlPtr->nextPtr;
1371    
1372            /*
1373             * If we switched text lines, delete any DLines left for the
1374             * old text line.
1375             */
1376    
1377            if (index.linePtr != prevPtr->index.linePtr) {
1378                register DLine *nextPtr;
1379    
1380                nextPtr = dlPtr;
1381                while ((nextPtr != NULL)
1382                        && (nextPtr->index.linePtr == prevPtr->index.linePtr)) {
1383                    nextPtr = nextPtr->nextPtr;
1384                }
1385                if (nextPtr != dlPtr) {
1386                    FreeDLines(textPtr, dlPtr, nextPtr, 0);
1387                    prevPtr->nextPtr = nextPtr;
1388                    dlPtr = nextPtr;
1389                }
1390            }
1391    
1392            /*
1393             * It's important to have the following check here rather than in
1394             * the while statement for the loop, so that there's always at least
1395             * one DLine generated, regardless of how small the window is.  This
1396             * keeps a lot of other code from breaking.
1397             */
1398    
1399            if (y >= maxY) {
1400                break;
1401            }
1402        }
1403    
1404        /*
1405         * Delete any DLine structures that don't fit on the screen.
1406         */
1407    
1408        FreeDLines(textPtr, dlPtr, (DLine *) NULL, 1);
1409    
1410        /*
1411         *--------------------------------------------------------------
1412         * If there is extra space at the bottom of the window (because
1413         * we've hit the end of the text), then bring in more lines at
1414         * the top of the window, if there are any, to fill in the view.
1415         *--------------------------------------------------------------
1416         */
1417    
1418        if (y < maxY) {
1419            int lineNum, spaceLeft, bytesToCount;
1420            DLine *lowestPtr;
1421    
1422            /*
1423             * Layout an entire text line (potentially > 1 display line),
1424             * then link in as many display lines as fit without moving
1425             * the bottom line out of the window.  Repeat this until
1426             * all the extra space has been used up or we've reached the
1427             * beginning of the text.
1428             */
1429    
1430            spaceLeft = maxY - y;
1431            lineNum = TkBTreeLineIndex(dInfoPtr->dLinePtr->index.linePtr);
1432            bytesToCount = dInfoPtr->dLinePtr->index.byteIndex;
1433            if (bytesToCount == 0) {
1434                bytesToCount = INT_MAX;
1435                lineNum--;
1436            }
1437            for ( ; (lineNum >= 0) && (spaceLeft > 0); lineNum--) {
1438                index.linePtr = TkBTreeFindLine(textPtr->tree, lineNum);
1439                index.byteIndex = 0;
1440                lowestPtr = NULL;
1441    
1442                do {
1443                    dlPtr = LayoutDLine(textPtr, &index);
1444                    dlPtr->nextPtr = lowestPtr;
1445                    lowestPtr = dlPtr;
1446                    if (dlPtr->length == 0 && dlPtr->height == 0) { bytesToCount--; break; }        /* elide */
1447                    TkTextIndexForwBytes(&index, dlPtr->byteCount, &index);
1448                    bytesToCount -= dlPtr->byteCount;
1449                } while ((bytesToCount > 0)
1450                        && (index.linePtr == lowestPtr->index.linePtr));
1451    
1452                /*
1453                 * Scan through the display lines from the bottom one up to
1454                 * the top one.
1455                 */
1456    
1457                while (lowestPtr != NULL) {
1458                    dlPtr = lowestPtr;
1459                    spaceLeft -= dlPtr->height;
1460                    if (spaceLeft < 0) {
1461                        break;
1462                    }
1463                    lowestPtr = dlPtr->nextPtr;
1464                    dlPtr->nextPtr = dInfoPtr->dLinePtr;
1465                    dInfoPtr->dLinePtr = dlPtr;
1466                    if (tkTextDebug) {
1467                        char string[TK_POS_CHARS];
1468    
1469                        TkTextPrintIndex(&dlPtr->index, string);
1470                        Tcl_SetVar2(textPtr->interp, "tk_textRelayout",
1471                                (char *) NULL, string,
1472                                TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT);
1473                    }
1474                }
1475                FreeDLines(textPtr, lowestPtr, (DLine *) NULL, 0);
1476                bytesToCount = INT_MAX;
1477            }
1478    
1479            /*
1480             * Now we're all done except that the y-coordinates in all the
1481             * DLines are wrong and the top index for the text is wrong.
1482             * Update them.
1483             */
1484    
1485            textPtr->topIndex = dInfoPtr->dLinePtr->index;
1486            y = dInfoPtr->y;
1487            for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
1488                    dlPtr = dlPtr->nextPtr) {
1489                if (y > dInfoPtr->maxY) {
1490                    panic("Added too many new lines in UpdateDisplayInfo");
1491                }
1492                dlPtr->y = y;
1493                y += dlPtr->height;
1494            }
1495        }
1496    
1497        /*
1498         *--------------------------------------------------------------
1499         * If the old top or bottom line has scrolled elsewhere on the
1500         * screen, we may not be able to re-use its old contents by
1501         * copying bits (e.g., a beveled edge that was drawn when it was
1502         * at the top or bottom won't be drawn when the line is in the
1503         * middle and its neighbor has a matching background).  Similarly,
1504         * if the new top or bottom line came from somewhere else on the
1505         * screen, we may not be able to copy the old bits.
1506         *--------------------------------------------------------------
1507         */
1508    
1509        dlPtr = dInfoPtr->dLinePtr;
1510        if ((dlPtr->flags & HAS_3D_BORDER) && !(dlPtr->flags & TOP_LINE)) {
1511            dlPtr->oldY = -1;
1512        }
1513        while (1) {
1514            if ((dlPtr->flags & TOP_LINE) && (dlPtr != dInfoPtr->dLinePtr)
1515                    && (dlPtr->flags & HAS_3D_BORDER)) {
1516                dlPtr->oldY = -1;
1517            }
1518            if ((dlPtr->flags & BOTTOM_LINE) && (dlPtr->nextPtr != NULL)
1519                    && (dlPtr->flags & HAS_3D_BORDER)) {
1520                dlPtr->oldY = -1;
1521            }
1522            if (dlPtr->nextPtr == NULL) {
1523                if ((dlPtr->flags & HAS_3D_BORDER)
1524                        && !(dlPtr->flags & BOTTOM_LINE)) {
1525                    dlPtr->oldY = -1;
1526                }
1527                dlPtr->flags &= ~TOP_LINE;
1528                dlPtr->flags |= BOTTOM_LINE;
1529                break;
1530            }
1531            dlPtr->flags &= ~(TOP_LINE|BOTTOM_LINE);
1532            dlPtr = dlPtr->nextPtr;
1533        }
1534        dInfoPtr->dLinePtr->flags |= TOP_LINE;
1535    
1536        /*
1537         * Arrange for scrollbars to be updated.
1538         */
1539    
1540        textPtr->flags |= UPDATE_SCROLLBARS;
1541    
1542        /*
1543         *--------------------------------------------------------------
1544         * Deal with horizontal scrolling:
1545         * 1. If there's empty space to the right of the longest line,
1546         *    shift the screen to the right to fill in the empty space.
1547         * 2. If the desired horizontal scroll position has changed,
1548         *    force a full redisplay of all the lines in the widget.
1549         * 3. If the wrap mode isn't "none" then re-scroll to the base
1550         *    position.
1551         *--------------------------------------------------------------
1552         */
1553    
1554        dInfoPtr->maxLength = 0;
1555        for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
1556                dlPtr = dlPtr->nextPtr) {
1557            if (dlPtr->length > dInfoPtr->maxLength) {
1558                dInfoPtr->maxLength = dlPtr->length;
1559            }
1560        }
1561        maxOffset = (dInfoPtr->maxLength - (dInfoPtr->maxX - dInfoPtr->x)
1562                + textPtr->charWidth - 1)/textPtr->charWidth;
1563        if (dInfoPtr->newByteOffset > maxOffset) {
1564            dInfoPtr->newByteOffset = maxOffset;
1565        }
1566        if (dInfoPtr->newByteOffset < 0) {
1567            dInfoPtr->newByteOffset = 0;
1568        }
1569        pixelOffset = dInfoPtr->newByteOffset * textPtr->charWidth;
1570        if (pixelOffset != dInfoPtr->curPixelOffset) {
1571            dInfoPtr->curPixelOffset = pixelOffset;
1572            for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
1573                    dlPtr = dlPtr->nextPtr) {
1574                dlPtr->oldY = -1;
1575            }
1576        }
1577    }
1578    
1579    /*
1580     *----------------------------------------------------------------------
1581     *
1582     * FreeDLines --
1583     *
1584     *      This procedure is called to free up all of the resources
1585     *      associated with one or more DLine structures.
1586     *
1587     * Results:
1588     *      None.
1589     *
1590     * Side effects:
1591     *      Memory gets freed and various other resources are released.
1592     *
1593     *----------------------------------------------------------------------
1594     */
1595    
1596    static void
1597    FreeDLines(textPtr, firstPtr, lastPtr, unlink)
1598        TkText *textPtr;                    /* Information about overall text
1599                                             * widget. */
1600        register DLine *firstPtr;           /* Pointer to first DLine to free up. */
1601        DLine *lastPtr;                     /* Pointer to DLine just after last
1602                                             * one to free (NULL means everything
1603                                             * starting with firstPtr). */
1604        int unlink;                         /* 1 means DLines are currently linked
1605                                             * into the list rooted at
1606                                             * textPtr->dInfoPtr->dLinePtr and
1607                                             * they have to be unlinked.  0 means
1608                                             * just free without unlinking. */
1609    {
1610        register TkTextDispChunk *chunkPtr, *nextChunkPtr;
1611        register DLine *nextDLinePtr;
1612    
1613        if (unlink) {
1614            if (textPtr->dInfoPtr->dLinePtr == firstPtr) {
1615                textPtr->dInfoPtr->dLinePtr = lastPtr;
1616            } else {
1617                register DLine *prevPtr;
1618                for (prevPtr = textPtr->dInfoPtr->dLinePtr;
1619                        prevPtr->nextPtr != firstPtr; prevPtr = prevPtr->nextPtr) {
1620                    /* Empty loop body. */
1621                }
1622                prevPtr->nextPtr = lastPtr;
1623            }
1624        }
1625        while (firstPtr != lastPtr) {
1626            nextDLinePtr = firstPtr->nextPtr;
1627            for (chunkPtr = firstPtr->chunkPtr; chunkPtr != NULL;
1628                    chunkPtr = nextChunkPtr) {
1629                if (chunkPtr->undisplayProc != NULL) {
1630                    (*chunkPtr->undisplayProc)(textPtr, chunkPtr);
1631                }
1632                FreeStyle(textPtr, chunkPtr->stylePtr);
1633                nextChunkPtr = chunkPtr->nextPtr;
1634                ckfree((char *) chunkPtr);
1635            }
1636            ckfree((char *) firstPtr);
1637            firstPtr = nextDLinePtr;
1638        }
1639        textPtr->dInfoPtr->dLinesInvalidated = 1;
1640    }
1641    
1642    /*
1643     *----------------------------------------------------------------------
1644     *
1645     * DisplayDLine --
1646     *
1647     *      This procedure is invoked to draw a single line on the
1648     *      screen.
1649     *
1650     * Results:
1651     *      None.
1652     *
1653     * Side effects:
1654     *      The line given by dlPtr is drawn at its correct position in
1655     *      textPtr's window.  Note that this is one *display* line, not
1656     *      one *text* line.
1657     *
1658     *----------------------------------------------------------------------
1659     */
1660    
1661    static void
1662    DisplayDLine(textPtr, dlPtr, prevPtr, pixmap)
1663        TkText *textPtr;            /* Text widget in which to draw line. */
1664        register DLine *dlPtr;      /* Information about line to draw. */
1665        DLine *prevPtr;             /* Line just before one to draw, or NULL
1666                                     * if dlPtr is the top line. */
1667        Pixmap pixmap;              /* Pixmap to use for double-buffering.
1668                                     * Caller must make sure it's large enough
1669                                     * to hold line. */
1670    {
1671        register TkTextDispChunk *chunkPtr;
1672        TextDInfo *dInfoPtr = textPtr->dInfoPtr;
1673        Display *display;
1674        int height, x;
1675    
1676        if (dlPtr->chunkPtr == NULL) return;
1677    
1678        /*
1679         * First, clear the area of the line to the background color for the
1680         * text widget.
1681         */
1682    
1683        display = Tk_Display(textPtr->tkwin);
1684        Tk_Fill3DRectangle(textPtr->tkwin, pixmap, textPtr->border, 0, 0,
1685                Tk_Width(textPtr->tkwin), dlPtr->height, 0, TK_RELIEF_FLAT);
1686    
1687        /*
1688         * Next, draw background information for the whole line.
1689         */
1690    
1691        DisplayLineBackground(textPtr, dlPtr, prevPtr, pixmap);
1692    
1693        /*
1694         * Make another pass through all of the chunks to redraw the
1695         * insertion cursor, if it is visible on this line.  Must do
1696         * it here rather than in the foreground pass below because
1697         * otherwise a wide insertion cursor will obscure the character
1698         * to its left.
1699         */
1700    
1701        if (textPtr->state == TK_STATE_NORMAL) {
1702            for (chunkPtr = dlPtr->chunkPtr; (chunkPtr != NULL);
1703                    chunkPtr = chunkPtr->nextPtr) {
1704                x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curPixelOffset;
1705                if (chunkPtr->displayProc == TkTextInsertDisplayProc) {
1706                    (*chunkPtr->displayProc)(chunkPtr, x, dlPtr->spaceAbove,
1707                            dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
1708                            dlPtr->baseline - dlPtr->spaceAbove, display, pixmap,
1709                            dlPtr->y + dlPtr->spaceAbove);
1710                }
1711            }
1712        }
1713    
1714        /*
1715         * Make yet another pass through all of the chunks to redraw all of
1716         * foreground information.  Note:  we have to call the displayProc
1717         * even for chunks that are off-screen.  This is needed, for
1718         * example, so that embedded windows can be unmapped in this case.
1719         * Conve
1720         */
1721    
1722        for (chunkPtr = dlPtr->chunkPtr; (chunkPtr != NULL);
1723                chunkPtr = chunkPtr->nextPtr) {
1724            if (chunkPtr->displayProc == TkTextInsertDisplayProc) {
1725                /*
1726                 * Already displayed the insertion cursor above.  Don't
1727                 * do it again here.
1728                 */
1729    
1730                continue;
1731            }
1732            x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curPixelOffset;
1733            if ((x + chunkPtr->width <= 0) || (x >= dInfoPtr->maxX)) {
1734                /*
1735                 * Note:  we have to call the displayProc even for chunks
1736                 * that are off-screen.  This is needed, for example, so
1737                 * that embedded windows can be unmapped in this case.
1738                 * Display the chunk at a coordinate that can be clearly
1739                 * identified by the displayProc as being off-screen to
1740                 * the left (the displayProc may not be able to tell if
1741                 * something is off to the right).
1742                 */
1743    
1744                if (chunkPtr->displayProc != NULL)
1745                (*chunkPtr->displayProc)(chunkPtr, -chunkPtr->width,
1746                        dlPtr->spaceAbove,
1747                        dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
1748                        dlPtr->baseline - dlPtr->spaceAbove, display, pixmap,
1749                        dlPtr->y + dlPtr->spaceAbove);
1750            } else {
1751                /* don't call if elide.  This tax ok since not very many visible DLine's in
1752                      an area, but potentially many elide ones */
1753                if (chunkPtr->displayProc != NULL)
1754                (*chunkPtr->displayProc)(chunkPtr, x, dlPtr->spaceAbove,
1755                        dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
1756                        dlPtr->baseline - dlPtr->spaceAbove, display, pixmap,
1757                        dlPtr->y + dlPtr->spaceAbove);
1758            }
1759            if (dInfoPtr->dLinesInvalidated) {
1760                return;
1761            }
1762        }
1763    
1764        /*
1765         * Copy the pixmap onto the screen.  If this is the last line on
1766         * the screen then copy a piece of the line, so that it doesn't
1767         * overflow into the border area.  Another special trick:  copy the
1768         * padding area to the left of the line;  this is because the
1769         * insertion cursor sometimes overflows onto that area and we want
1770         * to get as much of the cursor as possible.
1771         */
1772    
1773        height = dlPtr->height;
1774        if ((height + dlPtr->y) > dInfoPtr->maxY) {
1775            height = dInfoPtr->maxY - dlPtr->y;
1776        }
1777        XCopyArea(display, pixmap, Tk_WindowId(textPtr->tkwin), dInfoPtr->copyGC,
1778                dInfoPtr->x, 0, (unsigned) (dInfoPtr->maxX - dInfoPtr->x),
1779                (unsigned) height, dInfoPtr->x, dlPtr->y);
1780        linesRedrawn++;
1781    }
1782    
1783    /*
1784     *--------------------------------------------------------------
1785     *
1786     * DisplayLineBackground --
1787     *
1788     *      This procedure is called to fill in the background for
1789     *      a display line.  It draws 3D borders cleverly so that
1790     *      adjacent chunks with the same style (whether on the same
1791     *      line or different lines) have a single 3D border around
1792     *      the whole region.
1793     *
1794     * Results:
1795     *      There is no return value.  Pixmap is filled in with background
1796     *      information for dlPtr.
1797     *
1798     * Side effects:
1799     *      None.
1800     *
1801     *--------------------------------------------------------------
1802     */
1803    
1804    static void
1805    DisplayLineBackground(textPtr, dlPtr, prevPtr, pixmap)
1806        TkText *textPtr;            /* Text widget containing line. */
1807        register DLine *dlPtr;      /* Information about line to draw. */
1808        DLine *prevPtr;             /* Line just above dlPtr, or NULL if dlPtr
1809                                     * is the top-most line in the window. */
1810        Pixmap pixmap;              /* Pixmap to use for double-buffering.
1811                                     * Caller must make sure it's large enough
1812                                     * to hold line.  Caller must also have
1813                                     * filled it with the background color for
1814                                     * the widget. */
1815    {
1816        TextDInfo *dInfoPtr = textPtr->dInfoPtr;
1817        TkTextDispChunk *chunkPtr;  /* Pointer to chunk in the current line. */
1818        TkTextDispChunk *chunkPtr2; /* Pointer to chunk in the line above or
1819                                     * below the current one.  NULL if we're to
1820                                     * the left of or to the right of the chunks
1821                                     * in the line. */
1822        TkTextDispChunk *nextPtr2;  /* Next chunk after chunkPtr2 (it's not the
1823                                     * same as chunkPtr2->nextPtr in the case
1824                                     * where chunkPtr2 is NULL because the line
1825                                     * is indented). */
1826        int leftX;                  /* The left edge of the region we're
1827                                     * currently working on. */
1828        int leftXIn;                /* 1 means beveled edge at leftX slopes right
1829                                     * as it goes down, 0 means it slopes left
1830                                     * as it goes down. */
1831        int rightX;                 /* Right edge of chunkPtr. */
1832        int rightX2;                /* Right edge of chunkPtr2. */
1833        int matchLeft;              /* Does the style of this line match that
1834                                     * of its neighbor just to the left of
1835                                     * the current x coordinate? */
1836        int matchRight;             /* Does line's style match its neighbor
1837                                     * just to the right of the current x-coord? */
1838        int minX, maxX, xOffset;
1839        StyleValues *sValuePtr;
1840        Display *display;
1841    
1842    
1843        /*
1844         * Pass 1: scan through dlPtr from left to right.  For each range of
1845         * chunks with the same style, draw the main background for the style
1846         * plus the vertical parts of the 3D borders (the left and right
1847         * edges).
1848         */
1849    
1850        display = Tk_Display(textPtr->tkwin);
1851        minX = dInfoPtr->curPixelOffset;
1852        xOffset = dInfoPtr->x - minX;
1853        maxX = minX + dInfoPtr->maxX - dInfoPtr->x;
1854        chunkPtr = dlPtr->chunkPtr;
1855    
1856        /*
1857         * Note A: in the following statement, and a few others later in
1858         * this file marked with "See Note A above", the right side of the
1859         * assignment was replaced with 0 on 6/18/97.  This has the effect
1860         * of highlighting the empty space to the left of a line whenever
1861         * the leftmost character of the line is highlighted.  This way,
1862         * multi-line highlights always line up along their left edges.
1863         * However, this may look funny in the case where a single word is
1864         * highlighted. To undo the change, replace "leftX = 0" with "leftX
1865         * = chunkPtr->x" and "rightX2 = 0" with "rightX2 = nextPtr2->x"
1866         * here and at all the marked points below.  This restores the old
1867         * behavior where empty space to the left of a line is not
1868         * highlighted, leaving a ragged left edge for multi-line
1869         * highlights.
1870         */
1871    
1872        leftX = 0;
1873        for (; leftX < maxX; chunkPtr = chunkPtr->nextPtr) {
1874            if ((chunkPtr->nextPtr != NULL)
1875                    && SAME_BACKGROUND(chunkPtr->nextPtr->stylePtr,
1876                    chunkPtr->stylePtr)) {
1877                continue;
1878            }
1879            sValuePtr = chunkPtr->stylePtr->sValuePtr;
1880            rightX = chunkPtr->x + chunkPtr->width;
1881            if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) {
1882                rightX = maxX;
1883            }
1884            if (chunkPtr->stylePtr->bgGC != None) {
1885                XFillRectangle(display, pixmap, chunkPtr->stylePtr->bgGC,
1886                        leftX + xOffset, 0, (unsigned int) (rightX - leftX),
1887                        (unsigned int) dlPtr->height);
1888                if (sValuePtr->relief != TK_RELIEF_FLAT) {
1889                    Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
1890                            leftX + xOffset, 0, sValuePtr->borderWidth,
1891                            dlPtr->height, 1, sValuePtr->relief);
1892                    Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
1893                            rightX - sValuePtr->borderWidth + xOffset,
1894                            0, sValuePtr->borderWidth, dlPtr->height, 0,
1895                            sValuePtr->relief);
1896                }
1897            }
1898            leftX = rightX;
1899        }
1900    
1901        /*
1902         * Pass 2: draw the horizontal bevels along the top of the line.  To
1903         * do this, scan through dlPtr from left to right while simultaneously
1904         * scanning through the line just above dlPtr.  ChunkPtr2 and nextPtr2
1905         * refer to two adjacent chunks in the line above.
1906         */
1907    
1908        chunkPtr = dlPtr->chunkPtr;
1909        leftX = 0;                          /* See Note A above. */
1910        leftXIn = 1;
1911        rightX = chunkPtr->x + chunkPtr->width;
1912        if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) {
1913            rightX = maxX;
1914        }
1915        chunkPtr2 = NULL;
1916        if (prevPtr != NULL && prevPtr->chunkPtr != NULL) {
1917            /*
1918             * Find the chunk in the previous line that covers leftX.
1919             */
1920    
1921            nextPtr2 = prevPtr->chunkPtr;
1922            rightX2 = 0;                    /* See Note A above. */
1923            while (rightX2 <= leftX) {
1924                chunkPtr2 = nextPtr2;
1925                if (chunkPtr2 == NULL) {
1926                    break;
1927                }
1928                nextPtr2 = chunkPtr2->nextPtr;
1929                rightX2 = chunkPtr2->x + chunkPtr2->width;
1930                if (nextPtr2 == NULL) {
1931                    rightX2 = INT_MAX;
1932                }
1933            }
1934        } else {
1935            nextPtr2 = NULL;
1936            rightX2 = INT_MAX;
1937        }
1938    
1939        while (leftX < maxX) {
1940            matchLeft = (chunkPtr2 != NULL)
1941                    && SAME_BACKGROUND(chunkPtr2->stylePtr, chunkPtr->stylePtr);
1942            sValuePtr = chunkPtr->stylePtr->sValuePtr;
1943            if (rightX <= rightX2) {
1944                /*
1945                 * The chunk in our line is about to end.  If its style
1946                 * changes then draw the bevel for the current style.
1947                 */
1948    
1949                if ((chunkPtr->nextPtr == NULL)
1950                        || !SAME_BACKGROUND(chunkPtr->stylePtr,
1951                        chunkPtr->nextPtr->stylePtr)) {
1952                    if (!matchLeft && (sValuePtr->relief != TK_RELIEF_FLAT)) {
1953                        Tk_3DHorizontalBevel(textPtr->tkwin, pixmap,
1954                                sValuePtr->border, leftX + xOffset, 0,
1955                                rightX - leftX, sValuePtr->borderWidth, leftXIn,
1956                                1, 1, sValuePtr->relief);
1957                    }
1958                    leftX = rightX;
1959                    leftXIn = 1;
1960    
1961                    /*
1962                     * If the chunk in the line above is also ending at
1963                     * the same point then advance to the next chunk in
1964                     * that line.
1965                     */
1966    
1967                    if ((rightX == rightX2) && (chunkPtr2 != NULL)) {
1968                        goto nextChunk2;
1969                    }
1970                }
1971                chunkPtr = chunkPtr->nextPtr;
1972                if (chunkPtr == NULL) {
1973                    break;
1974                }
1975                rightX = chunkPtr->x + chunkPtr->width;
1976                if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) {
1977                    rightX = maxX;
1978                }
1979                continue;
1980            }
1981    
1982            /*
1983             * The chunk in the line above is ending at an x-position where
1984             * there is no change in the style of the current line.  If the
1985             * style above matches the current line on one side of the change
1986             * but not on the other, we have to draw an L-shaped piece of
1987             * bevel.
1988             */
1989    
1990            matchRight = (nextPtr2 != NULL)
1991                    && SAME_BACKGROUND(nextPtr2->stylePtr, chunkPtr->stylePtr);
1992            if (matchLeft && !matchRight) {
1993                if (sValuePtr->relief != TK_RELIEF_FLAT) {
1994                    Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
1995                            rightX2 - sValuePtr->borderWidth + xOffset, 0,
1996                            sValuePtr->borderWidth, sValuePtr->borderWidth, 0,
1997                            sValuePtr->relief);
1998                }
1999                leftX = rightX2 - sValuePtr->borderWidth;
2000                leftXIn = 0;
2001            } else if (!matchLeft && matchRight
2002                    && (sValuePtr->relief != TK_RELIEF_FLAT)) {
2003                Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
2004                        rightX2 + xOffset, 0, sValuePtr->borderWidth,
2005                        sValuePtr->borderWidth, 1, sValuePtr->relief);
2006                Tk_3DHorizontalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
2007                        leftX + xOffset, 0, rightX2 + sValuePtr->borderWidth -leftX,
2008                        sValuePtr->borderWidth, leftXIn, 0, 1,
2009                        sValuePtr->relief);
2010            }
2011    
2012            nextChunk2:
2013            chunkPtr2 = nextPtr2;
2014            if (chunkPtr2 == NULL) {
2015                rightX2 = INT_MAX;
2016            } else {
2017                nextPtr2 = chunkPtr2->nextPtr;
2018                rightX2 = chunkPtr2->x + chunkPtr2->width;
2019                if (nextPtr2 == NULL) {
2020                    rightX2 = INT_MAX;
2021                }
2022            }
2023        }
2024        /*
2025         * Pass 3: draw the horizontal bevels along the bottom of the line.
2026         * This uses the same approach as pass 2.
2027         */
2028    
2029        chunkPtr = dlPtr->chunkPtr;
2030        leftX = 0;                          /* See Note A above. */
2031        leftXIn = 0;
2032        rightX = chunkPtr->x + chunkPtr->width;
2033        if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) {
2034            rightX = maxX;
2035        }
2036        chunkPtr2 = NULL;
2037        if (dlPtr->nextPtr != NULL && dlPtr->nextPtr->chunkPtr != NULL) {
2038            /*
2039             * Find the chunk in the previous line that covers leftX.
2040             */
2041    
2042            nextPtr2 = dlPtr->nextPtr->chunkPtr;
2043            rightX2 = 0;                    /* See Note A above. */
2044            while (rightX2 <= leftX) {
2045                chunkPtr2 = nextPtr2;
2046                if (chunkPtr2 == NULL) {
2047                    break;
2048                }
2049                nextPtr2 = chunkPtr2->nextPtr;
2050                rightX2 = chunkPtr2->x + chunkPtr2->width;
2051                if (nextPtr2 == NULL) {
2052                    rightX2 = INT_MAX;
2053                }
2054            }
2055        } else {
2056            nextPtr2 = NULL;
2057            rightX2 = INT_MAX;
2058        }
2059    
2060        while (leftX < maxX) {
2061            matchLeft = (chunkPtr2 != NULL)
2062                    && SAME_BACKGROUND(chunkPtr2->stylePtr, chunkPtr->stylePtr);
2063            sValuePtr = chunkPtr->stylePtr->sValuePtr;
2064            if (rightX <= rightX2) {
2065                if ((chunkPtr->nextPtr == NULL)
2066                        || !SAME_BACKGROUND(chunkPtr->stylePtr,
2067                        chunkPtr->nextPtr->stylePtr)) {
2068                    if (!matchLeft && (sValuePtr->relief != TK_RELIEF_FLAT)) {
2069                        Tk_3DHorizontalBevel(textPtr->tkwin, pixmap,
2070                                sValuePtr->border, leftX + xOffset,
2071                                dlPtr->height - sValuePtr->borderWidth,
2072                                rightX - leftX, sValuePtr->borderWidth, leftXIn,
2073                                0, 0, sValuePtr->relief);
2074                    }
2075                    leftX = rightX;
2076                    leftXIn = 0;
2077                    if ((rightX == rightX2) && (chunkPtr2 != NULL)) {
2078                        goto nextChunk2b;
2079                    }
2080                }
2081                chunkPtr = chunkPtr->nextPtr;
2082                if (chunkPtr == NULL) {
2083                    break;
2084                }
2085                rightX = chunkPtr->x + chunkPtr->width;
2086                if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) {
2087                    rightX = maxX;
2088                }
2089                continue;
2090            }
2091    
2092            matchRight = (nextPtr2 != NULL)
2093                    && SAME_BACKGROUND(nextPtr2->stylePtr, chunkPtr->stylePtr);
2094            if (matchLeft && !matchRight) {
2095                if (sValuePtr->relief != TK_RELIEF_FLAT) {
2096                    Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
2097                            rightX2 - sValuePtr->borderWidth + xOffset,
2098                            dlPtr->height - sValuePtr->borderWidth,
2099                            sValuePtr->borderWidth, sValuePtr->borderWidth, 0,
2100                            sValuePtr->relief);
2101                }
2102                leftX = rightX2 - sValuePtr->borderWidth;
2103                leftXIn = 1;
2104            } else if (!matchLeft && matchRight
2105                    && (sValuePtr->relief != TK_RELIEF_FLAT)) {
2106                Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
2107                        rightX2 + xOffset, dlPtr->height - sValuePtr->borderWidth,
2108                        sValuePtr->borderWidth, sValuePtr->borderWidth,
2109                        1, sValuePtr->relief);
2110                Tk_3DHorizontalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
2111                        leftX + xOffset, dlPtr->height - sValuePtr->borderWidth,
2112                        rightX2 + sValuePtr->borderWidth - leftX,
2113                        sValuePtr->borderWidth, leftXIn, 1, 0, sValuePtr->relief);
2114            }
2115    
2116            nextChunk2b:
2117            chunkPtr2 = nextPtr2;
2118            if (chunkPtr2 == NULL) {
2119                rightX2 = INT_MAX;
2120            } else {
2121                nextPtr2 = chunkPtr2->nextPtr;
2122                rightX2 = chunkPtr2->x + chunkPtr2->width;
2123                if (nextPtr2 == NULL) {
2124                    rightX2 = INT_MAX;
2125                }
2126            }
2127        }
2128    }
2129    
2130    /*
2131     *----------------------------------------------------------------------
2132     *
2133     * DisplayText --
2134     *
2135     *      This procedure is invoked as a when-idle handler to update the
2136     *      display.  It only redisplays the parts of the text widget that
2137     *      are out of date.
2138     *
2139     * Results:
2140     *      None.
2141     *
2142     * Side effects:
2143     *      Information is redrawn on the screen.
2144     *
2145     *----------------------------------------------------------------------
2146     */
2147    
2148    static void
2149    DisplayText(clientData)
2150        ClientData clientData;      /* Information about widget. */
2151    {
2152        register TkText *textPtr = (TkText *) clientData;
2153        TextDInfo *dInfoPtr = textPtr->dInfoPtr;
2154        Tk_Window tkwin;
2155        register DLine *dlPtr;
2156        DLine *prevPtr;
2157        Pixmap pixmap;
2158        int maxHeight, borders;
2159        int bottomY = 0;            /* Initialization needed only to stop
2160                                     * compiler warnings. */
2161        Tcl_Interp *interp;
2162    
2163        if (textPtr->tkwin == NULL) {
2164    
2165            /*
2166             * The widget has been deleted.  Don't do anything.
2167             */
2168    
2169            return;
2170        }
2171    
2172        interp = textPtr->interp;
2173        Tcl_Preserve((ClientData) interp);
2174    
2175        if (tkTextDebug) {
2176            Tcl_SetVar2(interp, "tk_textRelayout", (char *) NULL, "",
2177                    TCL_GLOBAL_ONLY);
2178        }
2179    
2180        if (textPtr->tkwin == NULL) {
2181    
2182            /*
2183             * The widget has been deleted.  Don't do anything.
2184             */
2185    
2186            goto end;
2187        }
2188    
2189        if (!Tk_IsMapped(textPtr->tkwin) || (dInfoPtr->maxX <= dInfoPtr->x)
2190                || (dInfoPtr->maxY <= dInfoPtr->y)) {
2191            UpdateDisplayInfo(textPtr);
2192            dInfoPtr->flags &= ~REDRAW_PENDING;
2193            goto doScrollbars;
2194        }
2195        numRedisplays++;
2196        if (tkTextDebug) {
2197            Tcl_SetVar2(interp, "tk_textRedraw", (char *) NULL, "",
2198                    TCL_GLOBAL_ONLY);
2199        }
2200    
2201        if (textPtr->tkwin == NULL) {
2202    
2203            /*
2204             * The widget has been deleted.  Don't do anything.
2205             */
2206    
2207            goto end;
2208        }
2209    
2210        /*
2211         * Choose a new current item if that is needed (this could cause
2212         * event handlers to be invoked, hence the preserve/release calls
2213         * and the loop, since the handlers could conceivably necessitate
2214         * yet another current item calculation).  The tkwin check is because
2215         * the whole window could go away in the Tcl_Release call.
2216         */
2217    
2218        while (dInfoPtr->flags & REPICK_NEEDED) {
2219            Tcl_Preserve((ClientData) textPtr);
2220            dInfoPtr->flags &= ~REPICK_NEEDED;
2221            TkTextPickCurrent(textPtr, &textPtr->pickEvent);
2222            tkwin = textPtr->tkwin;
2223            Tcl_Release((ClientData) textPtr);
2224            if (tkwin == NULL) {
2225                goto end;
2226            }
2227        }
2228    
2229        /*
2230         * First recompute what's supposed to be displayed.
2231         */
2232    
2233        UpdateDisplayInfo(textPtr);
2234        dInfoPtr->dLinesInvalidated = 0;
2235    
2236        /*
2237         * See if it's possible to bring some parts of the screen up-to-date
2238         * by scrolling (copying from other parts of the screen).
2239         */
2240    
2241        for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) {
2242            register DLine *dlPtr2;
2243            int offset, height, y, oldY;
2244            TkRegion damageRgn;
2245    
2246            if ((dlPtr->oldY == -1) || (dlPtr->y == dlPtr->oldY)
2247                    || ((dlPtr->oldY + dlPtr->height) > dInfoPtr->maxY)) {
2248                continue;
2249            }
2250    
2251            /*
2252             * This line is already drawn somewhere in the window so it only
2253             * needs to be copied to its new location.  See if there's a group
2254             * of lines that can all be copied together.
2255             */
2256    
2257            offset = dlPtr->y - dlPtr->oldY;
2258            height = dlPtr->height;
2259            y = dlPtr->y;
2260            for (dlPtr2 = dlPtr->nextPtr; dlPtr2 != NULL;
2261                    dlPtr2 = dlPtr2->nextPtr) {
2262                if ((dlPtr2->oldY == -1)
2263                        || ((dlPtr2->oldY + offset) != dlPtr2->y)
2264                        || ((dlPtr2->oldY + dlPtr2->height) > dInfoPtr->maxY)) {
2265                    break;
2266                }
2267                height += dlPtr2->height;
2268            }
2269    
2270            /*
2271             * Reduce the height of the area being copied if necessary to
2272             * avoid overwriting the border area.
2273             */
2274    
2275            if ((y + height) > dInfoPtr->maxY) {
2276                height = dInfoPtr->maxY -y;
2277            }
2278            oldY = dlPtr->oldY;
2279    
2280            /*
2281             * Update the lines we are going to scroll to show that they
2282             * have been copied.
2283             */
2284    
2285            while (1) {
2286                dlPtr->oldY = dlPtr->y;
2287                if (dlPtr->nextPtr == dlPtr2) {
2288                    break;
2289                }
2290                dlPtr = dlPtr->nextPtr;
2291            }
2292    
2293            /*
2294             * Scan through the lines following the copied ones to see if
2295             * we are going to overwrite them with the copy operation.
2296             * If so, mark them for redisplay.
2297             */
2298    
2299            for ( ; dlPtr2 != NULL; dlPtr2 = dlPtr2->nextPtr) {
2300                if ((dlPtr2->oldY != -1)
2301                        && ((dlPtr2->oldY + dlPtr2->height) > y)
2302                        && (dlPtr2->oldY < (y + height))) {
2303                    dlPtr2->oldY = -1;
2304                }
2305            }
2306    
2307            /*
2308             * Now scroll the lines.  This may generate damage which we
2309             * handle by calling TextInvalidateRegion to mark the display
2310             * blocks as stale.
2311             */
2312    
2313            damageRgn = TkCreateRegion();
2314            if (TkScrollWindow(textPtr->tkwin, dInfoPtr->scrollGC,
2315                    dInfoPtr->x, oldY,
2316                    (dInfoPtr->maxX - dInfoPtr->x), height,
2317                    0, y - oldY, damageRgn)) {
2318                TextInvalidateRegion(textPtr, damageRgn);
2319            }
2320            numCopies++;
2321            TkDestroyRegion(damageRgn);
2322        }
2323    
2324        /*
2325         * Clear the REDRAW_PENDING flag here.  This is actually pretty
2326         * tricky.  We want to wait until *after* doing the scrolling,
2327         * since that could generate more areas to redraw and don't
2328         * want to reschedule a redisplay for them.  On the other hand,
2329         * we can't wait until after all the redisplaying, because the
2330         * act of redisplaying could actually generate more redisplays
2331         * (e.g. in the case of a nested window with event bindings triggered
2332         * by redisplay).
2333         */
2334    
2335        dInfoPtr->flags &= ~REDRAW_PENDING;
2336    
2337        /*
2338         * Redraw the borders if that's needed.
2339         */
2340    
2341        if (dInfoPtr->flags & REDRAW_BORDERS) {
2342            if (tkTextDebug) {
2343                Tcl_SetVar2(interp, "tk_textRedraw", (char *) NULL, "borders",
2344                        TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT);
2345            }
2346    
2347            if (textPtr->tkwin == NULL) {
2348    
2349                /*
2350                 * The widget has been deleted.  Don't do anything.
2351                 */
2352    
2353                goto end;
2354            }
2355    
2356            Tk_Draw3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),
2357                    textPtr->border, textPtr->highlightWidth,
2358                    textPtr->highlightWidth,
2359                    Tk_Width(textPtr->tkwin) - 2*textPtr->highlightWidth,
2360                    Tk_Height(textPtr->tkwin) - 2*textPtr->highlightWidth,
2361                    textPtr->borderWidth, textPtr->relief);
2362            if (textPtr->highlightWidth != 0) {
2363                GC fgGC, bgGC;
2364        
2365                bgGC = Tk_GCForColor(textPtr->highlightBgColorPtr,
2366                            Tk_WindowId(textPtr->tkwin));
2367                if (textPtr->flags & GOT_FOCUS) {
2368                    fgGC = Tk_GCForColor(textPtr->highlightColorPtr,
2369                            Tk_WindowId(textPtr->tkwin));
2370                    TkpDrawHighlightBorder(textPtr->tkwin, fgGC, bgGC,
2371                            textPtr->highlightWidth, Tk_WindowId(textPtr->tkwin));
2372                } else {
2373                    TkpDrawHighlightBorder(textPtr->tkwin, bgGC, bgGC,
2374                            textPtr->highlightWidth, Tk_WindowId(textPtr->tkwin));
2375                }
2376            }
2377            borders = textPtr->borderWidth + textPtr->highlightWidth;
2378            if (textPtr->padY > 0) {
2379                Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),
2380                        textPtr->border, borders, borders,
2381                        Tk_Width(textPtr->tkwin) - 2*borders, textPtr->padY,
2382                        0, TK_RELIEF_FLAT);
2383                Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),
2384                        textPtr->border, borders,
2385                        Tk_Height(textPtr->tkwin) - borders - textPtr->padY,
2386                        Tk_Width(textPtr->tkwin) - 2*borders,
2387                        textPtr->padY, 0, TK_RELIEF_FLAT);
2388            }
2389            if (textPtr->padX > 0) {
2390                Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),
2391                        textPtr->border, borders, borders + textPtr->padY,
2392                        textPtr->padX,
2393                        Tk_Height(textPtr->tkwin) - 2*borders -2*textPtr->padY,
2394                        0, TK_RELIEF_FLAT);
2395                Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),
2396                        textPtr->border,
2397                        Tk_Width(textPtr->tkwin) - borders - textPtr->padX,
2398                        borders + textPtr->padY, textPtr->padX,
2399                        Tk_Height(textPtr->tkwin) - 2*borders -2*textPtr->padY,
2400                        0, TK_RELIEF_FLAT);
2401            }
2402            dInfoPtr->flags &= ~REDRAW_BORDERS;
2403        }
2404    
2405        /*
2406         * Now we have to redraw the lines that couldn't be updated by
2407         * scrolling.  First, compute the height of the largest line and
2408         * allocate an off-screen pixmap to use for double-buffered
2409         * displays.
2410         */
2411    
2412        maxHeight = -1;
2413        for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
2414                dlPtr = dlPtr->nextPtr) {
2415            if ((dlPtr->height > maxHeight) && (dlPtr->oldY != dlPtr->y)) {
2416                maxHeight = dlPtr->height;
2417            }
2418            bottomY = dlPtr->y + dlPtr->height;
2419        }
2420        if (maxHeight > dInfoPtr->maxY) {
2421            maxHeight = dInfoPtr->maxY;
2422        }
2423        if (maxHeight > 0) {
2424            pixmap = Tk_GetPixmap(Tk_Display(textPtr->tkwin),
2425                    Tk_WindowId(textPtr->tkwin), Tk_Width(textPtr->tkwin),
2426                    maxHeight, Tk_Depth(textPtr->tkwin));
2427            for (prevPtr = NULL, dlPtr = textPtr->dInfoPtr->dLinePtr;
2428                    (dlPtr != NULL) && (dlPtr->y < dInfoPtr->maxY);
2429                    prevPtr = dlPtr, dlPtr = dlPtr->nextPtr) {
2430                if (dlPtr->chunkPtr == NULL) continue;
2431                if (dlPtr->oldY != dlPtr->y) {
2432                    if (tkTextDebug) {
2433                        char string[TK_POS_CHARS];
2434                        TkTextPrintIndex(&dlPtr->index, string);
2435                        Tcl_SetVar2(textPtr->interp, "tk_textRedraw",
2436                                (char *) NULL, string,
2437                                TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT);
2438                    }
2439                    DisplayDLine(textPtr, dlPtr, prevPtr, pixmap);
2440                    if (dInfoPtr->dLinesInvalidated) {
2441                        Tk_FreePixmap(Tk_Display(textPtr->tkwin), pixmap);
2442                        return;
2443                    }
2444                    dlPtr->oldY = dlPtr->y;
2445                    dlPtr->flags &= ~NEW_LAYOUT;
2446                }
2447                /*prevPtr = dlPtr;*/
2448            }
2449            Tk_FreePixmap(Tk_Display(textPtr->tkwin), pixmap);
2450        }
2451    
2452        /*
2453         * See if we need to refresh the part of the window below the
2454         * last line of text (if there is any such area).  Refresh the
2455         * padding area on the left too, since the insertion cursor might
2456         * have been displayed there previously).
2457         */
2458    
2459        if (dInfoPtr->topOfEof > dInfoPtr->maxY) {
2460            dInfoPtr->topOfEof = dInfoPtr->maxY;
2461        }
2462        if (bottomY < dInfoPtr->topOfEof) {
2463            if (tkTextDebug) {
2464                Tcl_SetVar2(textPtr->interp, "tk_textRedraw",
2465                        (char *) NULL, "eof",
2466                        TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT);
2467            }
2468    
2469            if (textPtr->tkwin == NULL) {
2470    
2471                /*
2472                 * The widget has been deleted.  Don't do anything.
2473                 */
2474    
2475                goto end;
2476            }
2477    
2478            Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),
2479                    textPtr->border, dInfoPtr->x - textPtr->padX, bottomY,
2480                    dInfoPtr->maxX - (dInfoPtr->x - textPtr->padX),
2481                    dInfoPtr->topOfEof-bottomY, 0, TK_RELIEF_FLAT);
2482        }
2483        dInfoPtr->topOfEof = bottomY;
2484    
2485        doScrollbars:
2486    
2487        /*
2488         * Update the vertical scrollbar, if there is one.  Note:  it's
2489         * important to clear REDRAW_PENDING here, just in case the
2490         * scroll procedure does something that requires redisplay.
2491         */
2492        
2493        if (textPtr->flags & UPDATE_SCROLLBARS) {
2494            textPtr->flags &= ~UPDATE_SCROLLBARS;
2495            if (textPtr->yScrollCmd != NULL) {
2496                GetYView(textPtr->interp, textPtr, 1);
2497            }
2498    
2499            if (textPtr->tkwin == NULL) {
2500    
2501                /*
2502                 * The widget has been deleted.  Don't do anything.
2503                 */
2504    
2505                goto end;
2506            }
2507    
2508            /*
2509             * Update the horizontal scrollbar, if any.
2510             */
2511    
2512            if (textPtr->xScrollCmd != NULL) {
2513                GetXView(textPtr->interp, textPtr, 1);
2514            }
2515        }
2516    
2517    end:
2518        Tcl_Release((ClientData) interp);
2519    }
2520    
2521    /*
2522     *----------------------------------------------------------------------
2523     *
2524     * TkTextEventuallyRepick --
2525     *
2526     *      This procedure is invoked whenever something happens that
2527     *      could change the current character or the tags associated
2528     *      with it.
2529     *
2530     * Results:
2531     *      None.
2532     *
2533     * Side effects:
2534     *      A repick is scheduled as an idle handler.
2535     *
2536     *----------------------------------------------------------------------
2537     */
2538    
2539            /* ARGSUSED */
2540    void
2541    TkTextEventuallyRepick(textPtr)
2542        TkText *textPtr;            /* Widget record for text widget. */
2543    {
2544        TextDInfo *dInfoPtr = textPtr->dInfoPtr;
2545    
2546        dInfoPtr->flags |= REPICK_NEEDED;
2547        if (!(dInfoPtr->flags & REDRAW_PENDING)) {
2548            dInfoPtr->flags |= REDRAW_PENDING;
2549            Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);
2550        }
2551    }
2552    
2553    /*
2554     *----------------------------------------------------------------------
2555     *
2556     * TkTextRedrawRegion --
2557     *
2558     *      This procedure is invoked to schedule a redisplay for a given
2559     *      region of a text widget.  The redisplay itself may not occur
2560     *      immediately:  it's scheduled as a when-idle handler.
2561     *
2562     * Results:
2563     *      None.
2564     *
2565     * Side effects:
2566     *      Information will eventually be redrawn on the screen.
2567     *
2568     *----------------------------------------------------------------------
2569     */
2570    
2571            /* ARGSUSED */
2572    void
2573    TkTextRedrawRegion(textPtr, x, y, width, height)
2574        TkText *textPtr;            /* Widget record for text widget. */
2575        int x, y;                   /* Coordinates of upper-left corner of area
2576                                     * to be redrawn, in pixels relative to
2577                                     * textPtr's window. */
2578        int width, height;          /* Width and height of area to be redrawn. */
2579    {
2580        TextDInfo *dInfoPtr = textPtr->dInfoPtr;
2581        TkRegion damageRgn = TkCreateRegion();
2582        XRectangle rect;
2583    
2584        rect.x = x;
2585        rect.y = y;
2586        rect.width = width;
2587        rect.height = height;
2588        TkUnionRectWithRegion(&rect, damageRgn, damageRgn);
2589    
2590        TextInvalidateRegion(textPtr, damageRgn);
2591    
2592        if (!(dInfoPtr->flags & REDRAW_PENDING)) {
2593            dInfoPtr->flags |= REDRAW_PENDING;
2594            Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);
2595        }
2596        TkDestroyRegion(damageRgn);
2597    }
2598    
2599    /*
2600     *----------------------------------------------------------------------
2601     *
2602     * TextInvalidateRegion --
2603     *
2604     *      Mark a region of text as invalid.
2605     *
2606     * Results:
2607     *      None.
2608     *
2609     * Side effects:
2610     *      Updates the display information for the text widget.
2611     *
2612     *----------------------------------------------------------------------
2613     */
2614    
2615    static void
2616    TextInvalidateRegion(textPtr, region)
2617        TkText *textPtr;            /* Widget record for text widget. */
2618        TkRegion region;            /* Region of area to redraw. */
2619    {
2620        register DLine *dlPtr;
2621        TextDInfo *dInfoPtr = textPtr->dInfoPtr;
2622        int maxY, inset;
2623        XRectangle rect;
2624    
2625        /*
2626         * Find all lines that overlap the given region and mark them for
2627         * redisplay.
2628         */
2629    
2630        TkClipBox(region, &rect);
2631        maxY = rect.y + rect.height;
2632        for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
2633                dlPtr = dlPtr->nextPtr) {
2634            if ((dlPtr->oldY != -1) && (TkRectInRegion(region, rect.x, dlPtr->y,
2635                    rect.width, (unsigned int) dlPtr->height) != RectangleOut)) {
2636                dlPtr->oldY = -1;
2637            }
2638        }
2639        if (dInfoPtr->topOfEof < maxY) {
2640            dInfoPtr->topOfEof = maxY;
2641        }
2642    
2643        /*
2644         * Schedule the redisplay operation if there isn't one already
2645         * scheduled.
2646         */
2647    
2648        inset = textPtr->borderWidth + textPtr->highlightWidth;
2649        if ((rect.x < (inset + textPtr->padX))
2650                || (rect.y < (inset + textPtr->padY))
2651                || ((int) (rect.x + rect.width) > (Tk_Width(textPtr->tkwin)
2652                        - inset - textPtr->padX))
2653                || (maxY > (Tk_Height(textPtr->tkwin) - inset - textPtr->padY))) {
2654            dInfoPtr->flags |= REDRAW_BORDERS;
2655        }
2656    }
2657    
2658    /*
2659     *----------------------------------------------------------------------
2660     *
2661     * TkTextChanged --
2662     *
2663     *      This procedure is invoked when info in a text widget is about
2664     *      to be modified in a way that changes how it is displayed (e.g.
2665     *      characters were inserted or deleted, or tag information was
2666     *      changed).  This procedure must be called *before* a change is
2667     *      made, so that indexes in the display information are still
2668     *      valid.
2669     *
2670     * Results:
2671     *      None.
2672     *
2673     * Side effects:
2674     *      The range of character between index1Ptr (inclusive) and
2675     *      index2Ptr (exclusive) will be redisplayed at some point in the
2676     *      future (the actual redisplay is scheduled as a when-idle handler).
2677     *
2678     *----------------------------------------------------------------------
2679     */
2680    
2681    void
2682    TkTextChanged(textPtr, index1Ptr, index2Ptr)
2683        TkText *textPtr;            /* Widget record for text widget. */
2684        TkTextIndex *index1Ptr;     /* Index of first character to redisplay. */
2685        TkTextIndex *index2Ptr;     /* Index of character just after last one
2686                                     * to redisplay. */
2687    {
2688        TextDInfo *dInfoPtr = textPtr->dInfoPtr;
2689        DLine *firstPtr, *lastPtr;
2690        TkTextIndex rounded;
2691    
2692        /*
2693         * Schedule both a redisplay and a recomputation of display information.
2694         * It's done here rather than the end of the procedure for two reasons:
2695         *
2696         * 1. If there are no display lines to update we'll want to return
2697         *    immediately, well before the end of the procedure.
2698         * 2. It's important to arrange for the redisplay BEFORE calling
2699         *    FreeDLines.  The reason for this is subtle and has to do with
2700         *    embedded windows.  The chunk delete procedure for an embedded
2701         *    window will schedule an idle handler to unmap the window.
2702         *    However, we want the idle handler for redisplay to be called
2703         *    first, so that it can put the embedded window back on the screen
2704         *    again (if appropriate).  This will prevent the window from ever
2705         *    being unmapped, and thereby avoid flashing.
2706         */
2707    
2708        if (!(dInfoPtr->flags & REDRAW_PENDING)) {
2709            Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);
2710        }
2711        dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;
2712    
2713        /*
2714         * Find the DLines corresponding to index1Ptr and index2Ptr.  There
2715         * is one tricky thing here, which is that we have to relayout in
2716         * units of whole text lines:  round index1Ptr back to the beginning
2717         * of its text line, and include all the display lines after index2,
2718         * up to the end of its text line.  This is necessary because the
2719         * indices stored in the display lines will no longer be valid.  It's
2720         * also needed because any edit could change the way lines wrap.
2721         */
2722    
2723        rounded = *index1Ptr;
2724        rounded.byteIndex = 0;
2725        firstPtr = FindDLine(dInfoPtr->dLinePtr, &rounded);
2726        if (firstPtr == NULL) {
2727            return;
2728        }
2729        lastPtr = FindDLine(dInfoPtr->dLinePtr, index2Ptr);
2730        while ((lastPtr != NULL)
2731                && (lastPtr->index.linePtr == index2Ptr->linePtr)) {
2732            lastPtr = lastPtr->nextPtr;
2733        }
2734    
2735        /*
2736         * Delete all the DLines from firstPtr up to but not including lastPtr.
2737         */
2738    
2739        FreeDLines(textPtr, firstPtr, lastPtr, 1);
2740    }
2741    
2742    /*
2743     *----------------------------------------------------------------------
2744     *
2745     * TkTextRedrawTag --
2746     *
2747     *      This procedure is invoked to request a redraw of all characters
2748     *      in a given range that have a particular tag on or off.  It's
2749     *      called, for example, when tag options change.
2750     *
2751     * Results:
2752     *      None.
2753     *
2754     * Side effects:
2755     *      Information on the screen may be redrawn, and the layout of
2756     *      the screen may change.
2757     *
2758     *----------------------------------------------------------------------
2759     */
2760    
2761    void
2762    TkTextRedrawTag(textPtr, index1Ptr, index2Ptr, tagPtr, withTag)
2763        TkText *textPtr;            /* Widget record for text widget. */
2764        TkTextIndex *index1Ptr;     /* First character in range to consider
2765                                     * for redisplay.  NULL means start at
2766                                     * beginning of text. */
2767        TkTextIndex *index2Ptr;     /* Character just after last one to consider
2768                                     * for redisplay.  NULL means process all
2769                                     * the characters in the text. */
2770        TkTextTag *tagPtr;          /* Information about tag. */
2771        int withTag;                /* 1 means redraw characters that have the
2772                                     * tag, 0 means redraw those without. */
2773    {
2774        register DLine *dlPtr;
2775        DLine *endPtr;
2776        int tagOn;
2777        TkTextSearch search;
2778        TextDInfo *dInfoPtr = textPtr->dInfoPtr;
2779        TkTextIndex *curIndexPtr;
2780        TkTextIndex endOfText, *endIndexPtr;
2781    
2782        /*
2783         * Round up the starting position if it's before the first line
2784         * visible on the screen (we only care about what's on the screen).
2785         */
2786    
2787        dlPtr = dInfoPtr->dLinePtr;
2788        if (dlPtr == NULL) {
2789            return;
2790        }
2791        if ((index1Ptr == NULL) || (TkTextIndexCmp(&dlPtr->index, index1Ptr) > 0)) {
2792            index1Ptr = &dlPtr->index;
2793        }
2794    
2795        /*
2796         * Set the stopping position if it wasn't specified.
2797         */
2798    
2799        if (index2Ptr == NULL) {
2800            index2Ptr = TkTextMakeByteIndex(textPtr->tree,
2801                    TkBTreeNumLines(textPtr->tree), 0, &endOfText);
2802        }
2803    
2804        /*
2805         * Initialize a search through all transitions on the tag, starting
2806         * with the first transition where the tag's current state is different
2807         * from what it will eventually be.
2808         */
2809    
2810        TkBTreeStartSearch(index1Ptr, index2Ptr, tagPtr, &search);
2811        /*
2812         * Make our own curIndex because at this point search.curIndex
2813         * may not equal index1Ptr->curIndex in the case the first tag toggle
2814         * comes after index1Ptr (See the use of FindTagStart in TkBTreeStartSearch)
2815         */
2816        curIndexPtr = index1Ptr;
2817        tagOn = TkBTreeCharTagged(index1Ptr, tagPtr);
2818        if (tagOn != withTag) {
2819            if (!TkBTreeNextTag(&search)) {
2820                return;
2821            }
2822            curIndexPtr = &search.curIndex;
2823        }
2824    
2825        /*
2826         * Schedule a redisplay and layout recalculation if they aren't
2827         * already pending.  This has to be done before calling FreeDLines,
2828         * for the reason given in TkTextChanged.
2829         */
2830    
2831        if (!(dInfoPtr->flags & REDRAW_PENDING)) {
2832            Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);
2833        }
2834        dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;
2835    
2836        /*
2837         * Each loop through the loop below is for one range of characters
2838         * where the tag's current state is different than its eventual
2839         * state.  At the top of the loop, search contains information about
2840         * the first character in the range.
2841         */
2842    
2843        while (1) {
2844            /*
2845             * Find the first DLine structure in the range.  Note: if the
2846             * desired character isn't the first in its text line, then look
2847             * for the character just before it instead.  This is needed to
2848             * handle the case where the first character of a wrapped
2849             * display line just got smaller, so that it now fits on the
2850             * line before:  need to relayout the line containing the
2851             * previous character.
2852             */
2853    
2854            if (curIndexPtr->byteIndex == 0) {
2855                dlPtr = FindDLine(dlPtr, curIndexPtr);
2856            } else {
2857                TkTextIndex tmp;
2858    
2859                tmp = *curIndexPtr;
2860                tmp.byteIndex -= 1;
2861                dlPtr = FindDLine(dlPtr, &tmp);
2862            }
2863            if (dlPtr == NULL) {
2864                break;
2865            }
2866    
2867            /*
2868             * Find the first DLine structure that's past the end of the range.
2869             */
2870    
2871            if (!TkBTreeNextTag(&search)) {
2872                endIndexPtr = index2Ptr;
2873            } else {
2874                curIndexPtr = &search.curIndex;
2875                endIndexPtr = curIndexPtr;
2876            }
2877            endPtr = FindDLine(dlPtr, endIndexPtr);
2878            if ((endPtr != NULL) && (endPtr->index.linePtr == endIndexPtr->linePtr)
2879                    && (endPtr->index.byteIndex < endIndexPtr->byteIndex)) {
2880                endPtr = endPtr->nextPtr;
2881            }
2882    
2883            /*
2884             * Delete all of the display lines in the range, so that they'll
2885             * be re-layed out and redrawn.
2886             */
2887    
2888            FreeDLines(textPtr, dlPtr, endPtr, 1);
2889            dlPtr = endPtr;
2890    
2891            /*
2892             * Find the first text line in the next range.
2893             */
2894    
2895            if (!TkBTreeNextTag(&search)) {
2896                break;
2897            }
2898        }
2899    }
2900    
2901    /*
2902     *----------------------------------------------------------------------
2903     *
2904     * TkTextRelayoutWindow --
2905     *
2906     *      This procedure is called when something has happened that
2907     *      invalidates the whole layout of characters on the screen, such
2908     *      as a change in a configuration option for the overall text
2909     *      widget or a change in the window size.  It causes all display
2910     *      information to be recomputed and the window to be redrawn.
2911     *
2912     * Results:
2913     *      None.
2914     *
2915     * Side effects:
2916     *      All the display information will be recomputed for the window
2917     *      and the window will be redrawn.
2918     *
2919     *----------------------------------------------------------------------
2920     */
2921    
2922    void
2923    TkTextRelayoutWindow(textPtr)
2924        TkText *textPtr;            /* Widget record for text widget. */
2925    {
2926        TextDInfo *dInfoPtr = textPtr->dInfoPtr;
2927        GC new;
2928        XGCValues gcValues;
2929    
2930        /*
2931         * Schedule the window redisplay.  See TkTextChanged for the
2932         * reason why this has to be done before any calls to FreeDLines.
2933         */
2934    
2935        if (!(dInfoPtr->flags & REDRAW_PENDING)) {
2936            Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);
2937        }
2938        dInfoPtr->flags |= REDRAW_PENDING|REDRAW_BORDERS|DINFO_OUT_OF_DATE
2939                |REPICK_NEEDED;
2940    
2941        /*
2942         * (Re-)create the graphics context for drawing the traversal
2943         * highlight.
2944         */
2945    
2946        gcValues.graphics_exposures = False;
2947        new = Tk_GetGC(textPtr->tkwin, GCGraphicsExposures, &gcValues);
2948        if (dInfoPtr->copyGC != None) {
2949            Tk_FreeGC(textPtr->display, dInfoPtr->copyGC);
2950        }
2951        dInfoPtr->copyGC = new;
2952    
2953        /*
2954         * Throw away all the current layout information.
2955         */
2956    
2957        FreeDLines(textPtr, dInfoPtr->dLinePtr, (DLine *) NULL, 1);
2958        dInfoPtr->dLinePtr = NULL;
2959    
2960        /*
2961         * Recompute some overall things for the layout.  Even if the
2962         * window gets very small, pretend that there's at least one
2963         * pixel of drawing space in it.
2964         */
2965    
2966        if (textPtr->highlightWidth < 0) {
2967            textPtr->highlightWidth = 0;
2968        }
2969        dInfoPtr->x = textPtr->highlightWidth + textPtr->borderWidth
2970                + textPtr->padX;
2971        dInfoPtr->y = textPtr->highlightWidth + textPtr->borderWidth
2972                + textPtr->padY;
2973        dInfoPtr->maxX = Tk_Width(textPtr->tkwin) - textPtr->highlightWidth
2974                - textPtr->borderWidth - textPtr->padX;
2975        if (dInfoPtr->maxX <= dInfoPtr->x) {
2976            dInfoPtr->maxX = dInfoPtr->x + 1;
2977        }
2978        dInfoPtr->maxY = Tk_Height(textPtr->tkwin) - textPtr->highlightWidth
2979                - textPtr->borderWidth - textPtr->padY;
2980        if (dInfoPtr->maxY <= dInfoPtr->y) {
2981            dInfoPtr->maxY = dInfoPtr->y + 1;
2982        }
2983        dInfoPtr->topOfEof = dInfoPtr->maxY;
2984    
2985        /*
2986         * If the upper-left character isn't the first in a line, recompute
2987         * it.  This is necessary because a change in the window's size
2988         * or options could change the way lines wrap.
2989         */
2990    
2991        if (textPtr->topIndex.byteIndex != 0) {
2992            MeasureUp(textPtr, &textPtr->topIndex, 0, &textPtr->topIndex);
2993        }
2994    
2995        /*
2996         * Invalidate cached scrollbar positions, so that scrollbars
2997         * sliders will be udpated.
2998         */
2999    
3000        dInfoPtr->xScrollFirst = dInfoPtr->xScrollLast = -1;
3001        dInfoPtr->yScrollFirst = dInfoPtr->yScrollLast = -1;
3002    }
3003    
3004    /*
3005     *----------------------------------------------------------------------
3006     *
3007     * TkTextSetYView --
3008     *
3009     *      This procedure is called to specify what lines are to be
3010     *      displayed in a text widget.
3011     *
3012     * Results:
3013     *      None.
3014     *
3015     * Side effects:
3016     *      The display will (eventually) be updated so that the position
3017     *      given by "indexPtr" is visible on the screen at the position
3018     *      determined by "pickPlace".
3019     *
3020     *----------------------------------------------------------------------
3021     */
3022    
3023    void
3024    TkTextSetYView(textPtr, indexPtr, pickPlace)
3025        TkText *textPtr;            /* Widget record for text widget. */
3026        TkTextIndex *indexPtr;      /* Position that is to appear somewhere
3027                                     * in the view. */
3028        int pickPlace;              /* 0 means topLine must appear at top of
3029                                     * screen.  1 means we get to pick where it
3030                                     * appears:  minimize screen motion or else
3031                                     * display line at center of screen. */
3032    {
3033        TextDInfo *dInfoPtr = textPtr->dInfoPtr;
3034        register DLine *dlPtr;
3035        int bottomY, close, lineIndex;
3036        TkTextIndex tmpIndex, rounded;
3037        Tk_FontMetrics fm;
3038    
3039        /*
3040         * If the specified position is the extra line at the end of the
3041         * text, round it back to the last real line.
3042         */
3043    
3044        lineIndex = TkBTreeLineIndex(indexPtr->linePtr);
3045        if (lineIndex == TkBTreeNumLines(indexPtr->tree)) {
3046            TkTextIndexBackChars(indexPtr, 1, &rounded);
3047            indexPtr = &rounded;
3048        }
3049    
3050        if (!pickPlace) {
3051            /*
3052             * The specified position must go at the top of the screen.
3053             * Just leave all the DLine's alone: we may be able to reuse
3054             * some of the information that's currently on the screen
3055             * without redisplaying it all.
3056             */
3057    
3058            if (indexPtr->byteIndex == 0) {
3059                textPtr->topIndex = *indexPtr;
3060            } else {
3061                MeasureUp(textPtr, indexPtr, 0, &textPtr->topIndex);
3062            }
3063            goto scheduleUpdate;
3064        }
3065    
3066        /*
3067         * We have to pick where to display the index.  First, bring
3068         * the display information up to date and see if the index will be
3069         * completely visible in the current screen configuration.  If so
3070         * then there's nothing to do.
3071         */
3072    
3073        if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
3074            UpdateDisplayInfo(textPtr);
3075        }
3076        dlPtr = FindDLine(dInfoPtr->dLinePtr, indexPtr);
3077        if (dlPtr != NULL) {
3078            if ((dlPtr->y + dlPtr->height) > dInfoPtr->maxY) {
3079                /*
3080                 * Part of the line hangs off the bottom of the screen;
3081                 * pretend the whole line is off-screen.
3082                 */
3083    
3084                dlPtr = NULL;
3085            } else if ((dlPtr->index.linePtr == indexPtr->linePtr)
3086                    && (dlPtr->index.byteIndex <= indexPtr->byteIndex)) {
3087                return;
3088            }
3089        }
3090    
3091        /*
3092         * The desired line isn't already on-screen.  Figure out what
3093         * it means to be "close" to the top or bottom of the screen.
3094         * Close means within 1/3 of the screen height or within three
3095         * lines, whichever is greater.  Add one extra line also, to
3096         * account for the way MeasureUp rounds.
3097         */
3098    
3099        Tk_GetFontMetrics(textPtr->tkfont, &fm);
3100        bottomY = (dInfoPtr->y + dInfoPtr->maxY + fm.linespace)/2;
3101        close = (dInfoPtr->maxY - dInfoPtr->y)/3;
3102        if (close < 3*fm.linespace) {
3103            close = 3*fm.linespace;
3104        }
3105        close += fm.linespace;
3106        if (dlPtr != NULL) {
3107            /*
3108             * The desired line is above the top of screen.  If it is
3109             * "close" to the top of the window then make it the top
3110             * line on the screen.
3111             */
3112    
3113            MeasureUp(textPtr, &textPtr->topIndex, close, &tmpIndex);
3114            if (TkTextIndexCmp(&tmpIndex, indexPtr) <= 0) {
3115                MeasureUp(textPtr, indexPtr, 0, &textPtr->topIndex);
3116                goto scheduleUpdate;
3117            }
3118        } else {
3119            /*
3120             * The desired line is below the bottom of the screen.  If it is
3121             * "close" to the bottom of the screen then position it at the
3122             * bottom of the screen.
3123             */
3124    
3125            MeasureUp(textPtr, indexPtr, close, &tmpIndex);
3126            if (FindDLine(dInfoPtr->dLinePtr, &tmpIndex) != NULL) {
3127                bottomY = dInfoPtr->maxY - dInfoPtr->y;
3128            }
3129        }
3130    
3131        /*
3132         * Our job now is to arrange the display so that indexPtr appears
3133         * as low on the screen as possible but with its bottom no lower
3134         * than bottomY.  BottomY is the bottom of the window if the
3135         * desired line is just below the current screen, otherwise it
3136         * is a half-line lower than the center of the window.
3137         */
3138    
3139        MeasureUp(textPtr, indexPtr, bottomY, &textPtr->topIndex);
3140    
3141        scheduleUpdate:
3142        if (!(dInfoPtr->flags & REDRAW_PENDING)) {
3143            Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);
3144        }
3145        dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;
3146    }
3147    
3148    /*
3149     *--------------------------------------------------------------
3150     *
3151     * MeasureUp --
3152     *
3153     *      Given one index, find the index of the first character
3154     *      on the highest display line that would be displayed no more
3155     *      than "distance" pixels above the given index.
3156     *
3157     * Results:
3158     *      *dstPtr is filled in with the index of the first character
3159     *      on a display line.  The display line is found by measuring
3160     *      up "distance" pixels above the pixel just below an imaginary
3161     *      display line that contains srcPtr.  If the display line
3162     *      that covers this coordinate actually extends above the
3163     *      coordinate, then return the index of the next lower line
3164     *      instead (i.e. the returned index will be completely visible
3165     *      at or below the given y-coordinate).
3166     *
3167     * Side effects:
3168     *      None.
3169     *
3170     *--------------------------------------------------------------
3171     */
3172    
3173    static void
3174    MeasureUp(textPtr, srcPtr, distance, dstPtr)
3175        TkText *textPtr;            /* Text widget in which to measure. */
3176        TkTextIndex *srcPtr;        /* Index of character from which to start
3177                                     * measuring. */
3178        int distance;               /* Vertical distance in pixels measured
3179                                     * from the pixel just below the lowest
3180                                     * one in srcPtr's line. */
3181        TkTextIndex *dstPtr;        /* Index to fill in with result. */
3182    {
3183        int lineNum;                /* Number of current line. */
3184        int bytesToCount;           /* Maximum number of bytes to measure in
3185                                     * current line. */
3186        TkTextIndex bestIndex;      /* Best candidate seen so far for result. */
3187        TkTextIndex index;
3188        DLine *dlPtr, *lowestPtr;
3189        int noBestYet;              /* 1 means bestIndex hasn't been set. */
3190    
3191        noBestYet = 1;
3192        bytesToCount = srcPtr->byteIndex + 1;
3193        index.tree = srcPtr->tree;
3194        for (lineNum = TkBTreeLineIndex(srcPtr->linePtr); lineNum >= 0;
3195                lineNum--) {
3196            /*
3197             * Layout an entire text line (potentially > 1 display line).
3198             * For the first line, which contains srcPtr, only layout the
3199             * part up through srcPtr (bytesToCount is non-infinite to
3200             * accomplish this).  Make a list of all the display lines
3201             * in backwards order (the lowest DLine on the screen is first
3202             * in the list).
3203             */
3204    
3205            index.linePtr = TkBTreeFindLine(srcPtr->tree, lineNum);
3206            index.byteIndex = 0;
3207            lowestPtr = NULL;
3208            do {
3209                dlPtr = LayoutDLine(textPtr, &index);
3210                dlPtr->nextPtr = lowestPtr;
3211                lowestPtr = dlPtr;
3212                TkTextIndexForwBytes(&index, dlPtr->byteCount, &index);
3213                bytesToCount -= dlPtr->byteCount;
3214            } while ((bytesToCount > 0) && (index.linePtr == dlPtr->index.linePtr));
3215    
3216            /*
3217             * Scan through the display lines to see if we've covered enough
3218             * vertical distance.  If so, save the starting index for the
3219             * line at the desired location.
3220             */
3221    
3222            for (dlPtr = lowestPtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) {
3223                distance -= dlPtr->height;
3224                if (distance < 0) {
3225                    *dstPtr = (noBestYet) ? dlPtr->index : bestIndex;
3226                    break;
3227                }
3228                bestIndex = dlPtr->index;
3229                noBestYet = 0;
3230            }
3231    
3232            /*
3233             * Discard the display lines, then either return or prepare
3234             * for the next display line to lay out.
3235             */
3236    
3237            FreeDLines(textPtr, lowestPtr, (DLine *) NULL, 0);
3238            if (distance < 0) {
3239                return;
3240            }
3241            bytesToCount = INT_MAX;         /* Consider all chars. in next line. */
3242        }
3243    
3244        /*
3245         * Ran off the beginning of the text.  Return the first character
3246         * in the text.
3247         */
3248    
3249        TkTextMakeByteIndex(textPtr->tree, 0, 0, dstPtr);
3250    }
3251    
3252    /*
3253     *--------------------------------------------------------------
3254     *
3255     * TkTextSeeCmd --
3256     *
3257     *      This procedure is invoked to process the "see" option for
3258     *      the widget command for text widgets. See the user documentation
3259     *      for details on what it does.
3260     *
3261     * Results:
3262     *      A standard Tcl result.
3263     *
3264     * Side effects:
3265     *      See the user documentation.
3266     *
3267     *--------------------------------------------------------------
3268     */
3269    
3270    int
3271    TkTextSeeCmd(textPtr, interp, argc, argv)
3272        TkText *textPtr;            /* Information about text widget. */
3273        Tcl_Interp *interp;         /* Current interpreter. */
3274        int argc;                   /* Number of arguments. */
3275        char **argv;                /* Argument strings.  Someone else has already
3276                                     * parsed this command enough to know that
3277                                     * argv[1] is "see". */
3278    {
3279        TextDInfo *dInfoPtr = textPtr->dInfoPtr;
3280        TkTextIndex index;
3281        int x, y, width, height, lineWidth, byteCount, oneThird, delta;
3282        DLine *dlPtr;
3283        TkTextDispChunk *chunkPtr;
3284    
3285        if (argc != 3) {
3286            Tcl_AppendResult(interp, "wrong # args: should be \"",
3287                    argv[0], " see index\"", (char *) NULL);
3288            return TCL_ERROR;
3289        }
3290        if (TkTextGetIndex(interp, textPtr, argv[2], &index) != TCL_OK) {
3291            return TCL_ERROR;
3292        }
3293    
3294        /*
3295         * If the specified position is the extra line at the end of the
3296         * text, round it back to the last real line.
3297         */
3298    
3299        if (TkBTreeLineIndex(index.linePtr) == TkBTreeNumLines(index.tree)) {
3300            TkTextIndexBackChars(&index, 1, &index);
3301        }
3302    
3303        /*
3304         * First get the desired position into the vertical range of the window.
3305         */
3306    
3307        TkTextSetYView(textPtr, &index, 1);
3308    
3309        /*
3310         * Now make sure that the character is in view horizontally.
3311         */
3312    
3313        if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
3314            UpdateDisplayInfo(textPtr);
3315        }
3316        lineWidth = dInfoPtr->maxX - dInfoPtr->x;
3317        if (dInfoPtr->maxLength < lineWidth) {
3318            return TCL_OK;
3319        }
3320    
3321        /*
3322         * Find the chunk that contains the desired index.
3323         */
3324    
3325        dlPtr = FindDLine(dInfoPtr->dLinePtr, &index);
3326        byteCount = index.byteIndex - dlPtr->index.byteIndex;
3327        for (chunkPtr = dlPtr->chunkPtr; chunkPtr!=NULL ; chunkPtr = chunkPtr->nextPtr) {
3328            if (byteCount < chunkPtr->numBytes) {
3329                break;
3330            }
3331            byteCount -= chunkPtr->numBytes;
3332        }
3333    
3334        /*
3335         * Call a chunk-specific procedure to find the horizontal range of
3336         * the character within the chunk.
3337         */
3338    
3339        if (chunkPtr!=NULL) {       /* chunkPtr==NULL iff trying to see in elided region */
3340        (*chunkPtr->bboxProc)(chunkPtr, byteCount, dlPtr->y + dlPtr->spaceAbove,
3341                dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
3342                dlPtr->baseline - dlPtr->spaceAbove, &x, &y, &width,
3343                &height);
3344        delta = x - dInfoPtr->curPixelOffset;
3345        oneThird = lineWidth/3;
3346        if (delta < 0) {
3347            if (delta < -oneThird) {
3348                dInfoPtr->newByteOffset = (x - lineWidth/2)/textPtr->charWidth;
3349            } else {
3350                dInfoPtr->newByteOffset -= ((-delta) + textPtr->charWidth - 1)
3351                    / textPtr->charWidth;
3352            }
3353        } else {
3354            delta -= (lineWidth - width);
3355            if (delta > 0) {
3356                if (delta > oneThird) {
3357                    dInfoPtr->newByteOffset = (x - lineWidth/2)/textPtr->charWidth;
3358                } else {
3359                    dInfoPtr->newByteOffset += (delta + textPtr->charWidth - 1)
3360                        / textPtr->charWidth;
3361                }
3362            } else {
3363                return TCL_OK;
3364            }
3365        }}
3366        dInfoPtr->flags |= DINFO_OUT_OF_DATE;
3367        if (!(dInfoPtr->flags & REDRAW_PENDING)) {
3368            dInfoPtr->flags |= REDRAW_PENDING;
3369            Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);
3370        }
3371        return TCL_OK;
3372    }
3373    
3374    /*
3375     *--------------------------------------------------------------
3376     *
3377     * TkTextXviewCmd --
3378     *
3379     *      This procedure is invoked to process the "xview" option for
3380     *      the widget command for text widgets. See the user documentation
3381     *      for details on what it does.
3382     *
3383     * Results:
3384     *      A standard Tcl result.
3385     *
3386     * Side effects:
3387     *      See the user documentation.
3388     *
3389     *--------------------------------------------------------------
3390     */
3391    
3392    int
3393    TkTextXviewCmd(textPtr, interp, argc, argv)
3394        TkText *textPtr;            /* Information about text widget. */
3395        Tcl_Interp *interp;         /* Current interpreter. */
3396        int argc;                   /* Number of arguments. */
3397        char **argv;                /* Argument strings.  Someone else has already
3398                                     * parsed this command enough to know that
3399                                     * argv[1] is "xview". */
3400    {
3401        TextDInfo *dInfoPtr = textPtr->dInfoPtr;
3402        int type, charsPerPage, count, newOffset;
3403        double fraction;
3404    
3405        if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
3406            UpdateDisplayInfo(textPtr);
3407        }
3408    
3409        if (argc == 2) {
3410            GetXView(interp, textPtr, 0);
3411            return TCL_OK;
3412        }
3413    
3414        newOffset = dInfoPtr->newByteOffset;
3415        type = Tk_GetScrollInfo(interp, argc, argv, &fraction, &count);
3416        switch (type) {
3417            case TK_SCROLL_ERROR:
3418                return TCL_ERROR;
3419            case TK_SCROLL_MOVETO:
3420                if (fraction > 1.0) {
3421                    fraction = 1.0;
3422                }
3423                if (fraction < 0) {
3424                    fraction = 0;
3425                }
3426                newOffset = (int) (((fraction * dInfoPtr->maxLength) / textPtr->charWidth)
3427                        + 0.5);
3428                break;
3429            case TK_SCROLL_PAGES:
3430                charsPerPage = ((dInfoPtr->maxX - dInfoPtr->x) / textPtr->charWidth)
3431                        - 2;
3432                if (charsPerPage < 1) {
3433                    charsPerPage = 1;
3434                }
3435                newOffset += charsPerPage * count;
3436                break;
3437            case TK_SCROLL_UNITS:
3438                newOffset += count;
3439                break;
3440        }
3441    
3442        dInfoPtr->newByteOffset = newOffset;
3443        dInfoPtr->flags |= DINFO_OUT_OF_DATE;
3444        if (!(dInfoPtr->flags & REDRAW_PENDING)) {
3445            dInfoPtr->flags |= REDRAW_PENDING;
3446            Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);
3447        }
3448        return TCL_OK;
3449    }
3450    
3451    /*
3452     *----------------------------------------------------------------------
3453     *
3454     * ScrollByLines --
3455     *
3456     *      This procedure is called to scroll a text widget up or down
3457     *      by a given number of lines.
3458     *
3459     * Results:
3460     *      None.
3461     *
3462     * Side effects:
3463     *      The view in textPtr's window changes to reflect the value
3464     *      of "offset".
3465     *
3466     *----------------------------------------------------------------------
3467     */
3468    
3469    static void
3470    ScrollByLines(textPtr, offset)
3471        TkText *textPtr;            /* Widget to scroll. */
3472        int offset;                 /* Amount by which to scroll, in *screen*
3473                                     * lines.  Positive means that information
3474                                     * later in text becomes visible, negative
3475                                     * means that information earlier in the
3476                                     * text becomes visible. */
3477    {
3478        int i, bytesToCount, lineNum;
3479        TkTextIndex new, index;
3480        TkTextLine *lastLinePtr;
3481        TextDInfo *dInfoPtr = textPtr->dInfoPtr;
3482        DLine *dlPtr, *lowestPtr;
3483    
3484        if (offset < 0) {
3485            /*
3486             * Must scroll up (to show earlier information in the text).
3487             * The code below is similar to that in MeasureUp, except that
3488             * it counts lines instead of pixels.
3489             */
3490    
3491            bytesToCount = textPtr->topIndex.byteIndex + 1;
3492            index.tree = textPtr->tree;
3493            offset--;                       /* Skip line containing topIndex. */
3494            for (lineNum = TkBTreeLineIndex(textPtr->topIndex.linePtr);
3495                    lineNum >= 0; lineNum--) {
3496                index.linePtr = TkBTreeFindLine(textPtr->tree, lineNum);
3497                index.byteIndex = 0;
3498                lowestPtr = NULL;
3499                do {
3500                    dlPtr = LayoutDLine(textPtr, &index);
3501                    dlPtr->nextPtr = lowestPtr;
3502                    lowestPtr = dlPtr;
3503                    TkTextIndexForwBytes(&index, dlPtr->byteCount, &index);
3504                    bytesToCount -= dlPtr->byteCount;
3505                } while ((bytesToCount > 0)
3506                        && (index.linePtr == dlPtr->index.linePtr));
3507    
3508                for (dlPtr = lowestPtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) {
3509                    offset++;
3510                    if (offset == 0) {
3511                        textPtr->topIndex = dlPtr->index;
3512                        break;
3513                    }
3514                }
3515    
3516                /*
3517                 * Discard the display lines, then either return or prepare
3518                 * for the next display line to lay out.
3519                 */
3520        
3521                FreeDLines(textPtr, lowestPtr, (DLine *) NULL, 0);
3522                if (offset >= 0) {
3523                    goto scheduleUpdate;
3524                }
3525                bytesToCount = INT_MAX;
3526            }
3527        
3528            /*
3529             * Ran off the beginning of the text.  Return the first character
3530             * in the text.
3531             */
3532    
3533            TkTextMakeByteIndex(textPtr->tree, 0, 0, &textPtr->topIndex);
3534        } else {
3535            /*
3536             * Scrolling down, to show later information in the text.
3537             * Just count lines from the current top of the window.
3538             */
3539    
3540            lastLinePtr = TkBTreeFindLine(textPtr->tree,
3541                    TkBTreeNumLines(textPtr->tree));
3542            for (i = 0; i < offset; i++) {
3543                dlPtr = LayoutDLine(textPtr, &textPtr->topIndex);
3544                if (dlPtr->length == 0 && dlPtr->height == 0) offset++;
3545                dlPtr->nextPtr = NULL;
3546                TkTextIndexForwBytes(&textPtr->topIndex, dlPtr->byteCount, &new);
3547                FreeDLines(textPtr, dlPtr, (DLine *) NULL, 0);
3548                if (new.linePtr == lastLinePtr) {
3549                    break;
3550                }
3551                textPtr->topIndex = new;
3552            }
3553        }
3554    
3555        scheduleUpdate:
3556        if (!(dInfoPtr->flags & REDRAW_PENDING)) {
3557            Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);
3558        }
3559        dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;
3560    }
3561    
3562    /*
3563     *--------------------------------------------------------------
3564     *
3565     * TkTextYviewCmd --
3566     *
3567     *      This procedure is invoked to process the "yview" option for
3568     *      the widget command for text widgets. See the user documentation
3569     *      for details on what it does.
3570     *
3571     * Results:
3572     *      A standard Tcl result.
3573     *
3574     * Side effects:
3575     *      See the user documentation.
3576     *
3577     *--------------------------------------------------------------
3578     */
3579    
3580    int
3581    TkTextYviewCmd(textPtr, interp, argc, argv)
3582        TkText *textPtr;            /* Information about text widget. */
3583        Tcl_Interp *interp;         /* Current interpreter. */
3584        int argc;                   /* Number of arguments. */
3585        char **argv;                /* Argument strings.  Someone else has already
3586                                     * parsed this command enough to know that
3587                                     * argv[1] is "yview". */
3588    {
3589        TextDInfo *dInfoPtr = textPtr->dInfoPtr;
3590        int pickPlace, lineNum, type, bytesInLine;
3591        Tk_FontMetrics fm;
3592        int pixels, count;
3593        size_t switchLength;
3594        double fraction;
3595        TkTextIndex index, new;
3596        TkTextLine *lastLinePtr;
3597        DLine *dlPtr;
3598    
3599        if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
3600            UpdateDisplayInfo(textPtr);
3601        }
3602    
3603        if (argc == 2) {
3604            GetYView(interp, textPtr, 0);
3605            return TCL_OK;
3606        }
3607    
3608        /*
3609         * Next, handle the old syntax: "pathName yview ?-pickplace? where"
3610         */
3611    
3612        pickPlace = 0;
3613        if (argv[2][0] == '-') {
3614            switchLength = strlen(argv[2]);
3615            if ((switchLength >= 2)
3616                    && (strncmp(argv[2], "-pickplace", switchLength) == 0)) {
3617                pickPlace = 1;
3618                if (argc != 4) {
3619                    Tcl_AppendResult(interp, "wrong # args: should be \"",
3620                            argv[0], " yview -pickplace lineNum|index\"",
3621                            (char *) NULL);
3622                    return TCL_ERROR;
3623                }
3624            }
3625        }
3626        if ((argc == 3) || pickPlace) {
3627            if (Tcl_GetInt(interp, argv[2+pickPlace], &lineNum) == TCL_OK) {
3628                TkTextMakeByteIndex(textPtr->tree, lineNum, 0, &index);
3629                TkTextSetYView(textPtr, &index, 0);
3630                return TCL_OK;
3631            }
3632        
3633            /*
3634             * The argument must be a regular text index.
3635             */
3636        
3637            Tcl_ResetResult(interp);
3638            if (TkTextGetIndex(interp, textPtr, argv[2+pickPlace],
3639                    &index) != TCL_OK) {
3640                return TCL_ERROR;
3641            }
3642            TkTextSetYView(textPtr, &index, pickPlace);
3643            return TCL_OK;
3644        }
3645    
3646        /*
3647         * New syntax: dispatch based on argv[2].
3648         */
3649    
3650        type = Tk_GetScrollInfo(interp, argc, argv, &fraction, &count);
3651        switch (type) {
3652            case TK_SCROLL_ERROR:
3653                return TCL_ERROR;
3654            case TK_SCROLL_MOVETO:
3655                if (fraction > 1.0) {
3656                    fraction = 1.0;
3657                }
3658                if (fraction < 0) {
3659                    fraction = 0;
3660                }
3661                fraction *= TkBTreeNumLines(textPtr->tree);
3662                lineNum = (int) fraction;
3663                TkTextMakeByteIndex(textPtr->tree, lineNum, 0, &index);
3664                bytesInLine = TkBTreeBytesInLine(index.linePtr);
3665                index.byteIndex = (int)((bytesInLine * (fraction-lineNum)) + 0.5);
3666                if (index.byteIndex >= bytesInLine) {
3667                    TkTextMakeByteIndex(textPtr->tree, lineNum + 1, 0, &index);
3668                }
3669                TkTextSetYView(textPtr, &index, 0);
3670                break;
3671            case TK_SCROLL_PAGES:
3672                /*
3673                 * Scroll up or down by screenfuls.  Actually, use the
3674                 * window height minus two lines, so that there's some
3675                 * overlap between adjacent pages.
3676                 */
3677    
3678                Tk_GetFontMetrics(textPtr->tkfont, &fm);
3679                if (count < 0) {
3680                    pixels = (dInfoPtr->maxY - 2*fm.linespace - dInfoPtr->y)*(-count)
3681                            + fm.linespace;
3682                    MeasureUp(textPtr, &textPtr->topIndex, pixels, &new);
3683                    if (TkTextIndexCmp(&textPtr->topIndex, &new) == 0) {
3684                        /*
3685                         * A page of scrolling ended up being less than one line.
3686                         * Scroll one line anyway.
3687                         */
3688    
3689                        count = -1;
3690                        goto scrollByLines;
3691                    }
3692                    textPtr->topIndex = new;
3693                } else {
3694                    /*
3695                     * Scrolling down by pages.  Layout lines starting at the
3696                     * top index and count through the desired vertical distance.
3697                     */
3698    
3699                    pixels = (dInfoPtr->maxY - 2*fm.linespace - dInfoPtr->y)*count;
3700                    lastLinePtr = TkBTreeFindLine(textPtr->tree,
3701                            TkBTreeNumLines(textPtr->tree));
3702                    do {
3703                        dlPtr = LayoutDLine(textPtr, &textPtr->topIndex);
3704                        dlPtr->nextPtr = NULL;
3705                        TkTextIndexForwBytes(&textPtr->topIndex, dlPtr->byteCount,
3706                                &new);
3707                        pixels -= dlPtr->height;
3708                        FreeDLines(textPtr, dlPtr, (DLine *) NULL, 0);
3709                        if (new.linePtr == lastLinePtr) {
3710                            break;
3711                        }
3712                        textPtr->topIndex = new;
3713                    } while (pixels > 0);
3714                }
3715                if (!(dInfoPtr->flags & REDRAW_PENDING)) {
3716                    Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);
3717                }
3718                dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;
3719                break;
3720            case TK_SCROLL_UNITS:
3721                scrollByLines:
3722                ScrollByLines(textPtr, count);
3723                break;
3724        }
3725        return TCL_OK;
3726    }
3727    
3728    /*
3729     *--------------------------------------------------------------
3730     *
3731     * TkTextScanCmd --
3732     *
3733     *      This procedure is invoked to process the "scan" option for
3734     *      the widget command for text widgets. See the user documentation
3735     *      for details on what it does.
3736     *
3737     * Results:
3738     *      A standard Tcl result.
3739     *
3740     * Side effects:
3741     *      See the user documentation.
3742     *
3743     *--------------------------------------------------------------
3744     */
3745    
3746    int
3747    TkTextScanCmd(textPtr, interp, argc, argv)
3748        register TkText *textPtr;   /* Information about text widget. */
3749        Tcl_Interp *interp;         /* Current interpreter. */
3750        int argc;                   /* Number of arguments. */
3751        char **argv;                /* Argument strings.  Someone else has already
3752                                     * parsed this command enough to know that
3753                                     * argv[1] is "scan". */
3754    {
3755        TextDInfo *dInfoPtr = textPtr->dInfoPtr;
3756        TkTextIndex index;
3757        int c, x, y, totalScroll, newByte, maxByte, gain=10;
3758        Tk_FontMetrics fm;
3759        size_t length;
3760    
3761        if ((argc != 5) && (argc != 6)) {
3762            Tcl_AppendResult(interp, "wrong # args: should be \"",
3763                    argv[0], " scan mark x y\" or \"",
3764                    argv[0], " scan dragto x y ?gain?\"", (char *) NULL);
3765            return TCL_ERROR;
3766        }
3767        if (Tcl_GetInt(interp, argv[3], &x) != TCL_OK) {
3768            return TCL_ERROR;
3769        }
3770        if (Tcl_GetInt(interp, argv[4], &y) != TCL_OK) {
3771            return TCL_ERROR;
3772        }
3773        if ((argc == 6) && (Tcl_GetInt(interp, argv[5], &gain) != TCL_OK))
3774            return TCL_ERROR;
3775        c = argv[2][0];
3776        length = strlen(argv[2]);
3777        if ((c == 'd') && (strncmp(argv[2], "dragto", length) == 0)) {
3778            /*
3779             * Amplify the difference between the current position and the
3780             * mark position to compute how much the view should shift, then
3781             * update the mark position to correspond to the new view.  If we
3782             * run off the edge of the text, reset the mark point so that the
3783             * current position continues to correspond to the edge of the
3784             * window.  This means that the picture will start dragging as
3785             * soon as the mouse reverses direction (without this reset, might
3786             * have to slide mouse a long ways back before the picture starts
3787             * moving again).
3788             */
3789    
3790            newByte = dInfoPtr->scanMarkIndex + (gain*(dInfoPtr->scanMarkX - x))
3791                    / (textPtr->charWidth);
3792            maxByte = 1 + (dInfoPtr->maxLength - (dInfoPtr->maxX - dInfoPtr->x)
3793                    + textPtr->charWidth - 1)/textPtr->charWidth;
3794            if (newByte < 0) {
3795                newByte = 0;
3796                dInfoPtr->scanMarkIndex = 0;
3797                dInfoPtr->scanMarkX = x;
3798            } else if (newByte > maxByte) {
3799                newByte = maxByte;
3800                dInfoPtr->scanMarkIndex = maxByte;
3801                dInfoPtr->scanMarkX = x;
3802            }
3803            dInfoPtr->newByteOffset = newByte;
3804    
3805            Tk_GetFontMetrics(textPtr->tkfont, &fm);
3806            totalScroll = (gain*(dInfoPtr->scanMarkY - y)) / fm.linespace;
3807            if (totalScroll != dInfoPtr->scanTotalScroll) {
3808                index = textPtr->topIndex;
3809                ScrollByLines(textPtr, totalScroll-dInfoPtr->scanTotalScroll);
3810                dInfoPtr->scanTotalScroll = totalScroll;
3811                if ((index.linePtr == textPtr->topIndex.linePtr) &&
3812                        (index.byteIndex == textPtr->topIndex.byteIndex)) {
3813                    dInfoPtr->scanTotalScroll = 0;
3814                    dInfoPtr->scanMarkY = y;
3815                }
3816            }
3817        } else if ((c == 'm') && (strncmp(argv[2], "mark", length) == 0)) {
3818            dInfoPtr->scanMarkIndex = dInfoPtr->newByteOffset;
3819            dInfoPtr->scanMarkX = x;
3820            dInfoPtr->scanTotalScroll = 0;
3821            dInfoPtr->scanMarkY = y;
3822        } else {
3823            Tcl_AppendResult(interp, "bad scan option \"", argv[2],
3824                    "\": must be mark or dragto", (char *) NULL);
3825            return TCL_ERROR;
3826        }
3827        dInfoPtr->flags |= DINFO_OUT_OF_DATE;
3828        if (!(dInfoPtr->flags & REDRAW_PENDING)) {
3829            dInfoPtr->flags |= REDRAW_PENDING;
3830            Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);
3831        }
3832        return TCL_OK;
3833    }
3834    
3835    /*
3836     *----------------------------------------------------------------------
3837     *
3838     * GetXView --
3839     *
3840     *      This procedure computes the fractions that indicate what's
3841     *      visible in a text window and, optionally, evaluates a
3842     *      Tcl script to report them to the text's associated scrollbar.
3843     *
3844     * Results:
3845     *      If report is zero, then the interp's result is filled in with
3846     *      two real numbers separated by a space, giving the position of
3847     *      the left and right edges of the window as fractions from 0 to
3848     *      1, where 0 means the left edge of the text and 1 means the right
3849     *      edge.  If report is non-zero, then the interp's result isn't modified
3850     *      directly, but instead a script is evaluated in interp to report
3851     *      the new horizontal scroll position to the scrollbar (if the scroll
3852     *      position hasn't changed then no script is invoked).
3853     *
3854     * Side effects:
3855     *      None.
3856     *
3857     *----------------------------------------------------------------------
3858     */
3859    
3860    static void
3861    GetXView(interp, textPtr, report)
3862        Tcl_Interp *interp;                 /* If "report" is FALSE, string
3863                                             * describing visible range gets
3864                                             * stored in the interp's result. */
3865        TkText *textPtr;                    /* Information about text widget. */
3866        int report;                         /* Non-zero means report info to
3867                                             * scrollbar if it has changed. */
3868    {
3869        TextDInfo *dInfoPtr = textPtr->dInfoPtr;
3870        char buffer[TCL_DOUBLE_SPACE * 2];
3871        double first, last;
3872        int code;
3873    
3874        if (dInfoPtr->maxLength > 0) {
3875            first = ((double) dInfoPtr->curPixelOffset)
3876                    / dInfoPtr->maxLength;
3877            last = first + ((double) (dInfoPtr->maxX - dInfoPtr->x))
3878                    / dInfoPtr->maxLength;
3879            if (last > 1.0) {
3880                last = 1.0;
3881            }
3882        } else {
3883            first = 0;
3884            last = 1.0;
3885        }
3886        if (!report) {
3887            sprintf(buffer, "%g %g", first, last);
3888            Tcl_SetResult(interp, buffer, TCL_VOLATILE);
3889            return;
3890        }
3891        if ((first == dInfoPtr->xScrollFirst) && (last == dInfoPtr->xScrollLast)) {
3892            return;
3893        }
3894        dInfoPtr->xScrollFirst = first;
3895        dInfoPtr->xScrollLast = last;
3896        sprintf(buffer, " %g %g", first, last);
3897        code = Tcl_VarEval(interp, textPtr->xScrollCmd,
3898                buffer, (char *) NULL);
3899        if (code != TCL_OK) {
3900            Tcl_AddErrorInfo(interp,
3901                    "\n    (horizontal scrolling command executed by text)");
3902            Tcl_BackgroundError(interp);
3903        }
3904    }
3905    
3906    /*
3907     *----------------------------------------------------------------------
3908     *
3909     * GetYView --
3910     *
3911     *      This procedure computes the fractions that indicate what's
3912     *      visible in a text window and, optionally, evaluates a
3913     *      Tcl script to report them to the text's associated scrollbar.
3914     *
3915     * Results:
3916     *      If report is zero, then the interp's result is filled in with
3917     *      two real numbers separated by a space, giving the position of
3918     *      the top and bottom of the window as fractions from 0 to 1, where
3919     *      0 means the beginning of the text and 1 means the end.  If
3920     *      report is non-zero, then the interp's result isn't modified directly,
3921     *      but a script is evaluated in interp to report the new scroll
3922     *      position to the scrollbar (if the scroll position hasn't changed
3923     *      then no script is invoked).
3924     *
3925     * Side effects:
3926     *      None.
3927     *
3928     *----------------------------------------------------------------------
3929     */
3930    
3931    static void
3932    GetYView(interp, textPtr, report)
3933        Tcl_Interp *interp;                 /* If "report" is FALSE, string
3934                                             * describing visible range gets
3935                                             * stored in the interp's result. */
3936        TkText *textPtr;                    /* Information about text widget. */
3937        int report;                         /* Non-zero means report info to
3938                                             * scrollbar if it has changed. */
3939    {
3940        TextDInfo *dInfoPtr = textPtr->dInfoPtr;
3941        char buffer[TCL_DOUBLE_SPACE * 2];
3942        double first, last;
3943        DLine *dlPtr;
3944        int totalLines, code, count;
3945    
3946        dlPtr = dInfoPtr->dLinePtr;
3947        totalLines = TkBTreeNumLines(textPtr->tree);
3948        first = (double) TkBTreeLineIndex(dlPtr->index.linePtr)
3949                + (double) dlPtr->index.byteIndex
3950                        / TkBTreeBytesInLine(dlPtr->index.linePtr);
3951        first /= totalLines;
3952        while (1) {
3953            if ((dlPtr->y + dlPtr->height) > dInfoPtr->maxY) {
3954                /*
3955                 * The last line is only partially visible, so don't
3956                 * count its characters in what's visible.
3957                 */
3958                count = 0;
3959                break;
3960            }
3961            if (dlPtr->nextPtr == NULL) {
3962                count = dlPtr->byteCount;
3963                break;
3964            }
3965            dlPtr = dlPtr->nextPtr;
3966        }
3967        last = ((double) TkBTreeLineIndex(dlPtr->index.linePtr))
3968                + ((double) (dlPtr->index.byteIndex + count))
3969                        / (TkBTreeBytesInLine(dlPtr->index.linePtr));
3970        last /= totalLines;
3971        if (!report) {
3972            sprintf(buffer, "%g %g", first, last);
3973            Tcl_SetResult(interp, buffer, TCL_VOLATILE);
3974            return;
3975        }
3976        if ((first == dInfoPtr->yScrollFirst) && (last == dInfoPtr->yScrollLast)) {
3977            return;
3978        }
3979        dInfoPtr->yScrollFirst = first;
3980        dInfoPtr->yScrollLast = last;
3981        sprintf(buffer, " %g %g", first, last);
3982        code = Tcl_VarEval(interp, textPtr->yScrollCmd, buffer, (char *) NULL);
3983        if (code != TCL_OK) {
3984            Tcl_AddErrorInfo(interp,
3985                    "\n    (vertical scrolling command executed by text)");
3986            Tcl_BackgroundError(interp);
3987        }
3988    }
3989    
3990    /*
3991     *----------------------------------------------------------------------
3992     *
3993     * FindDLine --
3994     *
3995     *      This procedure is called to find the DLine corresponding to a
3996     *      given text index.
3997     *
3998     * Results:
3999     *      The return value is a pointer to the first DLine found in the
4000     *      list headed by dlPtr that displays information at or after the
4001     *      specified position.  If there is no such line in the list then
4002     *      NULL is returned.
4003     *
4004     * Side effects:
4005     *      None.
4006     *
4007     *----------------------------------------------------------------------
4008     */
4009    
4010    static DLine *
4011    FindDLine(dlPtr, indexPtr)
4012        register DLine *dlPtr;      /* Pointer to first in list of DLines
4013                                     * to search. */
4014        TkTextIndex *indexPtr;      /* Index of desired character. */
4015    {
4016        TkTextLine *linePtr;
4017    
4018        if (dlPtr == NULL) {
4019            return NULL;
4020        }
4021        if (TkBTreeLineIndex(indexPtr->linePtr)
4022                < TkBTreeLineIndex(dlPtr->index.linePtr)) {
4023            /*
4024             * The first display line is already past the desired line.
4025             */
4026            return dlPtr;
4027        }
4028    
4029        /*
4030         * Find the first display line that covers the desired text line.
4031         */
4032    
4033        linePtr = dlPtr->index.linePtr;
4034        while (linePtr != indexPtr->linePtr) {
4035            while (dlPtr->index.linePtr == linePtr) {
4036                dlPtr = dlPtr->nextPtr;
4037                if (dlPtr == NULL) {
4038                    return NULL;
4039                }
4040            }
4041            linePtr = TkBTreeNextLine(linePtr);
4042            if (linePtr == NULL) {
4043                panic("FindDLine reached end of text");
4044            }
4045        }
4046        if (indexPtr->linePtr != dlPtr->index.linePtr) {
4047            return dlPtr;
4048        }
4049    
4050        /*
4051         * Now get to the right position within the text line.
4052         */
4053    
4054        while (indexPtr->byteIndex >= (dlPtr->index.byteIndex + dlPtr->byteCount)) {
4055            dlPtr = dlPtr->nextPtr;
4056            if ((dlPtr == NULL) || (dlPtr->index.linePtr != indexPtr->linePtr)) {
4057                break;
4058            }
4059        }
4060        return dlPtr;
4061    }
4062    
4063    /*
4064     *----------------------------------------------------------------------
4065     *
4066     * TkTextPixelIndex --
4067     *
4068     *      Given an (x,y) coordinate on the screen, find the location of
4069     *      the character closest to that location.
4070     *
4071     * Results:
4072     *      The index at *indexPtr is modified to refer to the character
4073     *      on the display that is closest to (x,y).
4074     *
4075     * Side effects:
4076     *      None.
4077     *
4078     *----------------------------------------------------------------------
4079     */
4080    
4081    void
4082    TkTextPixelIndex(textPtr, x, y, indexPtr)
4083        TkText *textPtr;            /* Widget record for text widget. */
4084        int x, y;                   /* Pixel coordinates of point in widget's
4085                                     * window. */
4086        TkTextIndex *indexPtr;      /* This index gets filled in with the
4087                                     * index of the character nearest to (x,y). */
4088    {
4089        TextDInfo *dInfoPtr = textPtr->dInfoPtr;
4090        register DLine *dlPtr, *validdlPtr;
4091        register TkTextDispChunk *chunkPtr;
4092    
4093        /*
4094         * Make sure that all of the layout information about what's
4095         * displayed where on the screen is up-to-date.
4096         */
4097    
4098        if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
4099            UpdateDisplayInfo(textPtr);
4100        }
4101    
4102        /*
4103         * If the coordinates are above the top of the window, then adjust
4104         * them to refer to the upper-right corner of the window.  If they're
4105         * off to one side or the other, then adjust to the closest side.
4106         */
4107    
4108        if (y < dInfoPtr->y) {
4109            y = dInfoPtr->y;
4110            x = dInfoPtr->x;
4111        }
4112        if (x >= dInfoPtr->maxX) {
4113            x = dInfoPtr->maxX - 1;
4114        }
4115        if (x < dInfoPtr->x) {
4116            x = dInfoPtr->x;
4117        }
4118    
4119        /*
4120         * Find the display line containing the desired y-coordinate.
4121         */
4122    
4123        for (dlPtr = validdlPtr = dInfoPtr->dLinePtr; y >= (dlPtr->y + dlPtr->height);
4124                dlPtr = dlPtr->nextPtr) {
4125            if (dlPtr->chunkPtr !=NULL) validdlPtr = dlPtr;
4126            if (dlPtr->nextPtr == NULL) {
4127                /*
4128                 * Y-coordinate is off the bottom of the displayed text.
4129                 * Use the last character on the last line.
4130                 */
4131    
4132                x = dInfoPtr->maxX - 1;
4133                break;
4134            }
4135        }
4136        if (dlPtr->chunkPtr == NULL) dlPtr = validdlPtr;
4137    
4138    
4139        /*
4140         * Scan through the line's chunks to find the one that contains
4141         * the desired x-coordinate.  Before doing this, translate the
4142         * x-coordinate from the coordinate system of the window to the
4143         * coordinate system of the line (to take account of x-scrolling).
4144         */
4145    
4146        *indexPtr = dlPtr->index;
4147        x = x - dInfoPtr->x + dInfoPtr->curPixelOffset;
4148        for (chunkPtr = dlPtr->chunkPtr; x >= (chunkPtr->x + chunkPtr->width);
4149                indexPtr->byteIndex += chunkPtr->numBytes,
4150                chunkPtr = chunkPtr->nextPtr) {
4151            if (chunkPtr->nextPtr == NULL) {
4152                indexPtr->byteIndex += chunkPtr->numBytes;
4153                TkTextIndexBackChars(indexPtr, 1, indexPtr);
4154                return;
4155            }
4156        }
4157    
4158        /*
4159         * If the chunk has more than one byte in it, ask it which
4160         * character is at the desired location.
4161         */
4162    
4163        if (chunkPtr->numBytes > 1) {
4164            indexPtr->byteIndex += (*chunkPtr->measureProc)(chunkPtr, x);
4165        }
4166    }
4167    
4168    /*
4169     *----------------------------------------------------------------------
4170     *
4171     * TkTextCharBbox --
4172     *
4173     *      Given an index, find the bounding box of the screen area
4174     *      occupied by that character.
4175     *
4176     * Results:
4177     *      Zero is returned if the character is on the screen.  -1
4178     *      means the character isn't on the screen.  If the return value
4179     *      is 0, then the bounding box of the part of the character that's
4180     *      visible on the screen is returned to *xPtr, *yPtr, *widthPtr,
4181     *      and *heightPtr.
4182     *
4183     * Side effects:
4184     *      None.
4185     *
4186     *----------------------------------------------------------------------
4187     */
4188    
4189    int
4190    TkTextCharBbox(textPtr, indexPtr, xPtr, yPtr, widthPtr, heightPtr)
4191        TkText *textPtr;            /* Widget record for text widget. */
4192        TkTextIndex *indexPtr;      /* Index of character whose bounding
4193                                     * box is desired. */
4194        int *xPtr, *yPtr;           /* Filled with character's upper-left
4195                                     * coordinate. */
4196        int *widthPtr, *heightPtr;  /* Filled in with character's dimensions. */
4197    {
4198        TextDInfo *dInfoPtr = textPtr->dInfoPtr;
4199        DLine *dlPtr;
4200        register TkTextDispChunk *chunkPtr;
4201        int byteIndex;
4202    
4203        /*
4204         * Make sure that all of the screen layout information is up to date.
4205         */
4206    
4207        if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
4208            UpdateDisplayInfo(textPtr);
4209        }
4210    
4211        /*
4212         * Find the display line containing the desired index.
4213         */
4214    
4215        dlPtr = FindDLine(dInfoPtr->dLinePtr, indexPtr);
4216        if ((dlPtr == NULL) || (TkTextIndexCmp(&dlPtr->index, indexPtr) > 0)) {
4217            return -1;
4218        }
4219    
4220        /*
4221         * Find the chunk within the line that contains the desired
4222         * index.
4223         */
4224    
4225        byteIndex = indexPtr->byteIndex - dlPtr->index.byteIndex;
4226        for (chunkPtr = dlPtr->chunkPtr; ; chunkPtr = chunkPtr->nextPtr) {
4227            if (chunkPtr == NULL) {
4228                return -1;
4229            }
4230            if (byteIndex < chunkPtr->numBytes) {
4231                break;
4232            }
4233            byteIndex -= chunkPtr->numBytes;
4234        }
4235    
4236        /*
4237         * Call a chunk-specific procedure to find the horizontal range of
4238         * the character within the chunk, then fill in the vertical range.
4239         * The x-coordinate returned by bboxProc is a coordinate within a
4240         * line, not a coordinate on the screen.  Translate it to reflect
4241         * horizontal scrolling.
4242         */
4243    
4244        (*chunkPtr->bboxProc)(chunkPtr, byteIndex, dlPtr->y + dlPtr->spaceAbove,
4245                dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
4246                dlPtr->baseline - dlPtr->spaceAbove, xPtr, yPtr, widthPtr,
4247                heightPtr);
4248        *xPtr = *xPtr + dInfoPtr->x - dInfoPtr->curPixelOffset;
4249        if ((byteIndex == (chunkPtr->numBytes - 1)) && (chunkPtr->nextPtr == NULL)) {
4250            /*
4251             * Last character in display line.  Give it all the space up to
4252             * the line.
4253             */
4254    
4255            if (*xPtr > dInfoPtr->maxX) {
4256                *xPtr = dInfoPtr->maxX;
4257            }
4258            *widthPtr = dInfoPtr->maxX - *xPtr;
4259        }
4260        if ((*xPtr + *widthPtr) <= dInfoPtr->x) {
4261            return -1;
4262        }
4263        if ((*xPtr + *widthPtr) > dInfoPtr->maxX) {
4264            *widthPtr = dInfoPtr->maxX - *xPtr;
4265            if (*widthPtr <= 0) {
4266                return -1;
4267            }
4268        }
4269        if ((*yPtr + *heightPtr) > dInfoPtr->maxY) {
4270            *heightPtr = dInfoPtr->maxY - *yPtr;
4271            if (*heightPtr <= 0) {
4272                return -1;
4273            }
4274        }
4275        return 0;
4276    }
4277    
4278    /*
4279     *----------------------------------------------------------------------
4280     *
4281     * TkTextDLineInfo --
4282     *
4283     *      Given an index, return information about the display line
4284     *      containing that character.
4285     *
4286     * Results:
4287     *      Zero is returned if the character is on the screen.  -1
4288     *      means the character isn't on the screen.  If the return value
4289     *      is 0, then information is returned in the variables pointed
4290     *      to by xPtr, yPtr, widthPtr, heightPtr, and basePtr.
4291     *
4292     * Side effects:
4293     *      None.
4294     *
4295     *----------------------------------------------------------------------
4296     */
4297    
4298    int
4299    TkTextDLineInfo(textPtr, indexPtr, xPtr, yPtr, widthPtr, heightPtr, basePtr)
4300        TkText *textPtr;            /* Widget record for text widget. */
4301        TkTextIndex *indexPtr;      /* Index of character whose bounding
4302                                     * box is desired. */
4303        int *xPtr, *yPtr;           /* Filled with line's upper-left
4304                                     * coordinate. */
4305        int *widthPtr, *heightPtr;  /* Filled in with line's dimensions. */
4306        int *basePtr;               /* Filled in with the baseline position,
4307                                     * measured as an offset down from *yPtr. */
4308    {
4309        TextDInfo *dInfoPtr = textPtr->dInfoPtr;
4310        DLine *dlPtr;
4311        int dlx;
4312    
4313        /*
4314         * Make sure that all of the screen layout information is up to date.
4315         */
4316    
4317        if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
4318            UpdateDisplayInfo(textPtr);
4319        }
4320    
4321        /*
4322         * Find the display line containing the desired index.
4323         */
4324    
4325        dlPtr = FindDLine(dInfoPtr->dLinePtr, indexPtr);
4326        if ((dlPtr == NULL) || (TkTextIndexCmp(&dlPtr->index, indexPtr) > 0)) {
4327            return -1;
4328        }
4329    
4330        dlx = (dlPtr->chunkPtr != NULL? dlPtr->chunkPtr->x: 0);
4331        *xPtr = dInfoPtr->x - dInfoPtr->curPixelOffset + dlx;
4332        *widthPtr = dlPtr->length - dlx;
4333        *yPtr = dlPtr->y;
4334        if ((dlPtr->y + dlPtr->height) > dInfoPtr->maxY) {
4335            *heightPtr = dInfoPtr->maxY - dlPtr->y;
4336        } else {
4337            *heightPtr = dlPtr->height;
4338        }
4339        *basePtr = dlPtr->baseline;
4340        return 0;
4341    }
4342    
4343    static void
4344    ElideBboxProc(chunkPtr, index, y, lineHeight, baseline, xPtr, yPtr,
4345            widthPtr, heightPtr)
4346        TkTextDispChunk *chunkPtr;          /* Chunk containing desired char. */
4347        int index;                          /* Index of desired character within
4348                                             * the chunk. */
4349        int y;                              /* Topmost pixel in area allocated
4350                                             * for this line. */
4351        int lineHeight;                     /* Height of line, in pixels. */
4352        int baseline;                       /* Location of line's baseline, in
4353                                             * pixels measured down from y. */
4354        int *xPtr, *yPtr;                   /* Gets filled in with coords of
4355                                             * character's upper-left pixel.
4356                                             * X-coord is in same coordinate
4357                                             * system as chunkPtr->x. */
4358        int *widthPtr;                      /* Gets filled in with width of
4359                                             * character, in pixels. */
4360        int *heightPtr;                     /* Gets filled in with height of
4361                                             * character, in pixels. */
4362    {
4363        *xPtr = chunkPtr->x;
4364        *yPtr = y;
4365        *widthPtr = *heightPtr = 0;
4366    }
4367    
4368    
4369    static int
4370    ElideMeasureProc(chunkPtr, x)
4371        TkTextDispChunk *chunkPtr;          /* Chunk containing desired coord. */
4372        int x;                              /* X-coordinate, in same coordinate
4373                                             * system as chunkPtr->x. */
4374    {
4375        return 0 /*chunkPtr->numBytes - 1*/;
4376    }
4377    
4378    /*
4379     *--------------------------------------------------------------
4380     *
4381     * TkTextCharLayoutProc --
4382     *
4383     *      This procedure is the "layoutProc" for character segments.
4384     *
4385    n * Results:
4386     *      If there is something to display for the chunk then a
4387     *      non-zero value is returned and the fields of chunkPtr
4388     *      will be filled in (see the declaration of TkTextDispChunk
4389     *      in tkText.h for details).  If zero is returned it means
4390     *      that no characters from this chunk fit in the window.
4391     *      If -1 is returned it means that this segment just doesn't
4392     *      need to be displayed (never happens for text).
4393     *
4394     * Side effects:
4395     *      Memory is allocated to hold additional information about
4396     *      the chunk.
4397     *
4398     *--------------------------------------------------------------
4399     */
4400    
4401    int
4402    TkTextCharLayoutProc(textPtr, indexPtr, segPtr, byteOffset, maxX, maxBytes,
4403            noCharsYet, wrapMode, chunkPtr)
4404        TkText *textPtr;            /* Text widget being layed out. */
4405        TkTextIndex *indexPtr;      /* Index of first character to lay out
4406                                     * (corresponds to segPtr and offset). */
4407        TkTextSegment *segPtr;      /* Segment being layed out. */
4408        int byteOffset;             /* Byte offset within segment of first
4409                                     * character to consider. */
4410        int maxX;                   /* Chunk must not occupy pixels at this
4411                                     * position or higher. */
4412        int maxBytes;               /* Chunk must not include more than this
4413                                     * many characters. */
4414        int noCharsYet;             /* Non-zero means no characters have been
4415                                     * assigned to this display line yet. */
4416        TkWrapMode wrapMode;        /* How to handle line wrapping: TEXT_WRAPMODE_CHAR,
4417                                     * TEXT_WRAPMODE_NONE, or TEXT_WRAPMODE_WORD. */
4418        register TkTextDispChunk *chunkPtr;
4419                                    /* Structure to fill in with information
4420                                     * about this chunk.  The x field has already
4421                                     * been set by the caller. */
4422    {
4423        Tk_Font tkfont;
4424        int nextX, bytesThatFit, count;
4425        CharInfo *ciPtr;
4426        char *p;
4427        TkTextSegment *nextPtr;
4428        Tk_FontMetrics fm;
4429    
4430        /*
4431         * Figure out how many characters will fit in the space we've got.
4432         * Include the next character, even though it won't fit completely,
4433         * if any of the following is true:
4434         *   (a) the chunk contains no characters and the display line contains
4435         *       no characters yet (i.e. the line isn't wide enough to hold
4436         *       even a single character).
4437         *   (b) at least one pixel of the character is visible, we haven't
4438         *       already exceeded the character limit, and the next character
4439         *       is a white space character.
4440         */
4441    
4442        p = segPtr->body.chars + byteOffset;
4443        tkfont = chunkPtr->stylePtr->sValuePtr->tkfont;
4444        bytesThatFit = MeasureChars(tkfont, p, maxBytes, chunkPtr->x, maxX, 0,
4445                &nextX);
4446        if (bytesThatFit < maxBytes) {
4447            if ((bytesThatFit == 0) && noCharsYet) {
4448                Tcl_UniChar ch;
4449                
4450                bytesThatFit = MeasureChars(tkfont, p, Tcl_UtfToUniChar(p, &ch),
4451                        chunkPtr->x, -1, 0, &nextX);
4452            }
4453            if ((nextX < maxX) && ((p[bytesThatFit] == ' ')
4454                    || (p[bytesThatFit] == '\t'))) {
4455                /*
4456                 * Space characters are funny, in that they are considered
4457                 * to fit if there is at least one pixel of space left on the
4458                 * line.  Just give the space character whatever space is left.
4459                 */
4460    
4461                nextX = maxX;
4462                bytesThatFit++;
4463            }
4464            if (p[bytesThatFit] == '\n') {
4465                /*
4466                 * A newline character takes up no space, so if the previous
4467                 * character fits then so does the newline.
4468                 */
4469    
4470                bytesThatFit++;
4471            }
4472            if (bytesThatFit == 0) {
4473                return 0;
4474            }
4475        }
4476            
4477        Tk_GetFontMetrics(tkfont, &fm);
4478    
4479        /*
4480         * Fill in the chunk structure and allocate and initialize a
4481         * CharInfo structure.  If the last character is a newline
4482         * then don't bother to display it.
4483         */
4484    
4485        chunkPtr->displayProc = CharDisplayProc;
4486        chunkPtr->undisplayProc = CharUndisplayProc;
4487        chunkPtr->measureProc = CharMeasureProc;
4488        chunkPtr->bboxProc = CharBboxProc;
4489        chunkPtr->numBytes = bytesThatFit;
4490        chunkPtr->minAscent = fm.ascent + chunkPtr->stylePtr->sValuePtr->offset;
4491        chunkPtr->minDescent = fm.descent - chunkPtr->stylePtr->sValuePtr->offset;
4492        chunkPtr->minHeight = 0;
4493        chunkPtr->width = nextX - chunkPtr->x;
4494        chunkPtr->breakIndex = -1;
4495        ciPtr = (CharInfo *) ckalloc((unsigned)
4496                (sizeof(CharInfo) - 3 + bytesThatFit));
4497        chunkPtr->clientData = (ClientData) ciPtr;
4498        ciPtr->numBytes = bytesThatFit;
4499        strncpy(ciPtr->chars, p, (size_t) bytesThatFit);
4500        if (p[bytesThatFit - 1] == '\n') {
4501            ciPtr->numBytes--;
4502        }
4503    
4504        /*
4505         * Compute a break location.  If we're in word wrap mode, a
4506         * break can occur after any space character, or at the end of
4507         * the chunk if the next segment (ignoring those with zero size)
4508         * is not a character segment.
4509         */
4510    
4511        if (wrapMode != TEXT_WRAPMODE_WORD) {
4512            chunkPtr->breakIndex = chunkPtr->numBytes;
4513        } else {
4514            for (count = bytesThatFit, p += bytesThatFit - 1; count > 0;
4515                    count--, p--) {
4516                if (isspace(UCHAR(*p))) {
4517                    chunkPtr->breakIndex = count;
4518                    break;
4519                }
4520            }
4521            if ((bytesThatFit + byteOffset) == segPtr->size) {
4522                for (nextPtr = segPtr->nextPtr; nextPtr != NULL;
4523                        nextPtr = nextPtr->nextPtr) {
4524                    if (nextPtr->size != 0) {
4525                        if (nextPtr->typePtr != &tkTextCharType) {
4526                            chunkPtr->breakIndex = chunkPtr->numBytes;
4527                        }
4528                        break;
4529                    }
4530                }
4531            }
4532        }
4533        return 1;
4534    }
4535    
4536    /*
4537     *--------------------------------------------------------------
4538     *
4539     * CharDisplayProc --
4540     *
4541     *      This procedure is called to display a character chunk on
4542     *      the screen or in an off-screen pixmap.
4543     *
4544     * Results:
4545     *      None.
4546     *
4547     * Side effects:
4548     *      Graphics are drawn.
4549     *
4550     *--------------------------------------------------------------
4551     */
4552    
4553    static void
4554    CharDisplayProc(chunkPtr, x, y, height, baseline, display, dst, screenY)
4555        TkTextDispChunk *chunkPtr;          /* Chunk that is to be drawn. */
4556        int x;                              /* X-position in dst at which to
4557                                             * draw this chunk (may differ from
4558                                             * the x-position in the chunk because
4559                                             * of scrolling). */
4560        int y;                              /* Y-position at which to draw this
4561                                             * chunk in dst. */
4562        int height;                         /* Total height of line. */
4563        int baseline;                       /* Offset of baseline from y. */
4564        Display *display;                   /* Display to use for drawing. */
4565        Drawable dst;                       /* Pixmap or window in which to draw
4566                                             * chunk. */
4567        int screenY;                        /* Y-coordinate in text window that
4568                                             * corresponds to y. */
4569    {
4570        CharInfo *ciPtr = (CharInfo *) chunkPtr->clientData;
4571        TextStyle *stylePtr;
4572        StyleValues *sValuePtr;
4573        int offsetBytes, offsetX;
4574    
4575        if ((x + chunkPtr->width) <= 0) {
4576            /*
4577             * The chunk is off-screen.
4578             */
4579    
4580            return;
4581        }
4582    
4583        stylePtr = chunkPtr->stylePtr;
4584        sValuePtr = stylePtr->sValuePtr;
4585    
4586        /*
4587         * If the text sticks out way to the left of the window, skip
4588         * over the characters that aren't in the visible part of the
4589         * window.  This is essential if x is very negative (such as
4590         * less than 32K);  otherwise overflow problems will occur
4591         * in servers that use 16-bit arithmetic, like X.
4592         */
4593    
4594        offsetX = x;
4595        offsetBytes = 0;
4596        if (x < 0) {
4597            offsetBytes = MeasureChars(sValuePtr->tkfont, ciPtr->chars,
4598                ciPtr->numBytes, x, 0, x - chunkPtr->x, &offsetX);
4599        }
4600    
4601        /*
4602         * Draw the text, underline, and overstrike for this chunk.
4603         */
4604    
4605        if (!sValuePtr->elide && (ciPtr->numBytes > offsetBytes) && (stylePtr->fgGC != None)) {
4606            int numBytes = ciPtr->numBytes - offsetBytes;
4607            char *string = ciPtr->chars + offsetBytes;
4608    
4609            if ((numBytes > 0) && (string[numBytes - 1] == '\t')) {
4610                numBytes--;
4611            }
4612            Tk_DrawChars(display, dst, stylePtr->fgGC, sValuePtr->tkfont, string,
4613                    numBytes, offsetX, y + baseline - sValuePtr->offset);
4614            if (sValuePtr->underline) {
4615                Tk_UnderlineChars(display, dst, stylePtr->fgGC, sValuePtr->tkfont,
4616                        ciPtr->chars + offsetBytes, offsetX,
4617                        y + baseline - sValuePtr->offset, 0, numBytes);
4618    
4619            }
4620            if (sValuePtr->overstrike) {
4621                Tk_FontMetrics fm;
4622                
4623                Tk_GetFontMetrics(sValuePtr->tkfont, &fm);
4624                Tk_UnderlineChars(display, dst, stylePtr->fgGC, sValuePtr->tkfont,
4625                        ciPtr->chars + offsetBytes, offsetX,
4626                        y + baseline - sValuePtr->offset
4627                                - fm.descent - (fm.ascent * 3) / 10,
4628                        0, numBytes);
4629            }
4630        }
4631    }
4632    
4633    /*
4634     *--------------------------------------------------------------
4635     *
4636     * CharUndisplayProc --
4637     *
4638     *      This procedure is called when a character chunk is no
4639     *      longer going to be displayed.  It frees up resources
4640     *      that were allocated to display the chunk.
4641     *
4642     * Results:
4643     *      None.
4644     *
4645     * Side effects:
4646     *      Memory and other resources get freed.
4647     *
4648     *--------------------------------------------------------------
4649     */
4650    
4651    static void
4652    CharUndisplayProc(textPtr, chunkPtr)
4653        TkText *textPtr;                    /* Overall information about text
4654                                             * widget. */
4655        TkTextDispChunk *chunkPtr;          /* Chunk that is about to be freed. */
4656    {
4657        CharInfo *ciPtr = (CharInfo *) chunkPtr->clientData;
4658    
4659        ckfree((char *) ciPtr);
4660    }
4661    
4662    /*
4663     *--------------------------------------------------------------
4664     *
4665     * CharMeasureProc --
4666     *
4667     *      This procedure is called to determine which character in
4668     *      a character chunk lies over a given x-coordinate.
4669     *
4670     * Results:
4671     *      The return value is the index *within the chunk* of the
4672     *      character that covers the position given by "x".
4673     *
4674     * Side effects:
4675     *      None.
4676     *
4677     *--------------------------------------------------------------
4678     */
4679    
4680    static int
4681    CharMeasureProc(chunkPtr, x)
4682        TkTextDispChunk *chunkPtr;          /* Chunk containing desired coord. */
4683        int x;                              /* X-coordinate, in same coordinate
4684                                             * system as chunkPtr->x. */
4685    {
4686        CharInfo *ciPtr = (CharInfo *) chunkPtr->clientData;
4687        int endX;
4688    
4689        return MeasureChars(chunkPtr->stylePtr->sValuePtr->tkfont, ciPtr->chars,
4690                chunkPtr->numBytes - 1, chunkPtr->x, x, 0, &endX);
4691                                                    /* CHAR OFFSET */
4692    }
4693    
4694    /*
4695     *--------------------------------------------------------------
4696     *
4697     * CharBboxProc --
4698     *
4699     *      This procedure is called to compute the bounding box of
4700     *      the area occupied by a single character.
4701     *
4702     * Results:
4703     *      There is no return value.  *xPtr and *yPtr are filled in
4704     *      with the coordinates of the upper left corner of the
4705     *      character, and *widthPtr and *heightPtr are filled in with
4706     *      the dimensions of the character in pixels.  Note:  not all
4707     *      of the returned bbox is necessarily visible on the screen
4708     *      (the rightmost part might be off-screen to the right,
4709     *      and the bottommost part might be off-screen to the bottom).
4710     *
4711     * Side effects:
4712     *      None.
4713     *
4714     *--------------------------------------------------------------
4715     */
4716    
4717    static void
4718    CharBboxProc(chunkPtr, byteIndex, y, lineHeight, baseline, xPtr, yPtr,
4719            widthPtr, heightPtr)
4720        TkTextDispChunk *chunkPtr;          /* Chunk containing desired char. */
4721        int byteIndex;                              /* Byte offset of desired character
4722                                             * within the chunk. */
4723        int y;                              /* Topmost pixel in area allocated
4724                                             * for this line. */
4725        int lineHeight;                     /* Height of line, in pixels. */
4726        int baseline;                       /* Location of line's baseline, in
4727                                             * pixels measured down from y. */
4728        int *xPtr, *yPtr;                   /* Gets filled in with coords of
4729                                             * character's upper-left pixel.
4730                                             * X-coord is in same coordinate
4731                                             * system as chunkPtr->x. */
4732        int *widthPtr;                      /* Gets filled in with width of
4733                                             * character, in pixels. */
4734        int *heightPtr;                     /* Gets filled in with height of
4735                                             * character, in pixels. */
4736    {
4737        CharInfo *ciPtr = (CharInfo *) chunkPtr->clientData;
4738        int maxX;
4739    
4740        maxX = chunkPtr->width + chunkPtr->x;
4741        MeasureChars(chunkPtr->stylePtr->sValuePtr->tkfont, ciPtr->chars,
4742                byteIndex, chunkPtr->x, -1, 0, xPtr);
4743    
4744        if (byteIndex == ciPtr->numBytes) {
4745            /*
4746             * This situation only happens if the last character in a line
4747             * is a space character, in which case it absorbs all of the
4748             * extra space in the line (see TkTextCharLayoutProc).
4749             */
4750    
4751            *widthPtr = maxX - *xPtr;
4752        } else if ((ciPtr->chars[byteIndex] == '\t')
4753                && (byteIndex == ciPtr->numBytes - 1)) {
4754            /*
4755             * The desired character is a tab character that terminates a
4756             * chunk;  give it all the space left in the chunk.
4757             */
4758    
4759            *widthPtr = maxX - *xPtr;
4760        } else {
4761            MeasureChars(chunkPtr->stylePtr->sValuePtr->tkfont,
4762                    ciPtr->chars + byteIndex, 1, *xPtr, -1, 0, widthPtr);
4763            if (*widthPtr > maxX) {
4764                *widthPtr = maxX - *xPtr;
4765            } else {
4766                *widthPtr -= *xPtr;
4767            }
4768        }
4769        *yPtr = y + baseline - chunkPtr->minAscent;
4770        *heightPtr = chunkPtr->minAscent + chunkPtr->minDescent;
4771    }
4772    
4773    /*
4774     *----------------------------------------------------------------------
4775     *
4776     * AdjustForTab --
4777     *
4778     *      This procedure is called to move a series of chunks right
4779     *      in order to align them with a tab stop.
4780     *
4781     * Results:
4782     *      None.
4783     *
4784     * Side effects:
4785     *      The width of chunkPtr gets adjusted so that it absorbs the
4786     *      extra space due to the tab.  The x locations in all the chunks
4787     *      after chunkPtr are adjusted rightward to align with the tab
4788     *      stop given by tabArrayPtr and index.
4789     *
4790     *----------------------------------------------------------------------
4791     */
4792    
4793    static void
4794    AdjustForTab(textPtr, tabArrayPtr, index, chunkPtr)
4795        TkText *textPtr;                    /* Information about the text widget as
4796                                             * a whole. */
4797        TkTextTabArray *tabArrayPtr;        /* Information about the tab stops
4798                                             * that apply to this line.  May be
4799                                             * NULL to indicate default tabbing
4800                                             * (every 8 chars). */
4801        int index;                          /* Index of current tab stop. */
4802        TkTextDispChunk *chunkPtr;          /* Chunk whose last character is
4803                                             * the tab;  the following chunks
4804                                             * contain information to be shifted
4805                                             * right. */
4806    
4807    {
4808        int x, desired, delta, width, decimal, i, gotDigit;
4809        TkTextDispChunk *chunkPtr2, *decimalChunkPtr;
4810        CharInfo *ciPtr;
4811        int tabX, prev, spaceWidth;
4812        char *p;
4813        TkTextTabAlign alignment;
4814    
4815        if (chunkPtr->nextPtr == NULL) {
4816            /*
4817             * Nothing after the actual tab;  just return.
4818             */
4819    
4820            return;
4821        }
4822    
4823        /*
4824         * If no tab information has been given, do the usual thing:
4825         * round up to the next boundary of 8 average-sized characters.
4826         */
4827    
4828        x = chunkPtr->nextPtr->x;
4829        if ((tabArrayPtr == NULL) || (tabArrayPtr->numTabs == 0)) {
4830            /*
4831             * No tab information has been given, so use the default
4832             * interpretation of tabs.
4833             */
4834    
4835            desired = NextTabStop(textPtr->tkfont, x, 0);
4836            goto update;
4837        }
4838    
4839        if (index < tabArrayPtr->numTabs) {
4840            alignment = tabArrayPtr->tabs[index].alignment;
4841            tabX = tabArrayPtr->tabs[index].location;
4842        } else {
4843            /*
4844             * Ran out of tab stops;  compute a tab position by extrapolating
4845             * from the last two tab positions.
4846             */
4847    
4848            if (tabArrayPtr->numTabs > 1) {
4849                prev = tabArrayPtr->tabs[tabArrayPtr->numTabs-2].location;
4850            } else {
4851                prev = 0;
4852            }
4853            alignment = tabArrayPtr->tabs[tabArrayPtr->numTabs-1].alignment;
4854            tabX = tabArrayPtr->tabs[tabArrayPtr->numTabs-1].location
4855                    + (index + 1 - tabArrayPtr->numTabs)
4856                    * (tabArrayPtr->tabs[tabArrayPtr->numTabs-1].location - prev);
4857        }
4858    
4859        if (alignment == LEFT) {
4860            desired = tabX;
4861            goto update;
4862        }
4863    
4864        if ((alignment == CENTER) || (alignment == RIGHT)) {
4865            /*
4866             * Compute the width of all the information in the tab group,
4867             * then use it to pick a desired location.
4868             */
4869    
4870            width = 0;
4871            for (chunkPtr2 = chunkPtr->nextPtr; chunkPtr2 != NULL;
4872                    chunkPtr2 = chunkPtr2->nextPtr) {
4873                width += chunkPtr2->width;
4874            }
4875            if (alignment == CENTER) {
4876                desired = tabX - width/2;
4877            } else {
4878                desired = tabX - width;
4879            }
4880            goto update;
4881        }
4882    
4883        /*
4884         * Must be numeric alignment.  Search through the text to be
4885         * tabbed, looking for the last , or . before the first character
4886         * that isn't a number, comma, period, or sign.
4887         */
4888    
4889        decimalChunkPtr = NULL;
4890        decimal = gotDigit = 0;
4891        for (chunkPtr2 = chunkPtr->nextPtr; chunkPtr2 != NULL;
4892                chunkPtr2 = chunkPtr2->nextPtr) {
4893            if (chunkPtr2->displayProc != CharDisplayProc) {
4894                continue;
4895            }
4896            ciPtr = (CharInfo *) chunkPtr2->clientData;
4897            for (p = ciPtr->chars, i = 0; i < ciPtr->numBytes; p++, i++) {
4898                if (isdigit(UCHAR(*p))) {
4899                    gotDigit = 1;
4900                } else if ((*p == '.') || (*p == ',')) {
4901                    decimal = p-ciPtr->chars;
4902                    decimalChunkPtr = chunkPtr2;
4903                } else if (gotDigit) {
4904                    if (decimalChunkPtr == NULL) {
4905                        decimal = p-ciPtr->chars;
4906                        decimalChunkPtr = chunkPtr2;
4907                    }
4908                    goto endOfNumber;
4909                }
4910            }
4911        }
4912        endOfNumber:
4913        if (decimalChunkPtr != NULL) {
4914            int curX;
4915    
4916            ciPtr = (CharInfo *) decimalChunkPtr->clientData;
4917            MeasureChars(decimalChunkPtr->stylePtr->sValuePtr->tkfont,
4918                    ciPtr->chars, decimal, decimalChunkPtr->x, -1, 0, &curX);
4919            desired = tabX - (curX - x);
4920            goto update;
4921        } else {
4922            /*
4923             * There wasn't a decimal point.  Right justify the text.
4924             */
4925        
4926            width = 0;
4927            for (chunkPtr2 = chunkPtr->nextPtr; chunkPtr2 != NULL;
4928                    chunkPtr2 = chunkPtr2->nextPtr) {
4929                width += chunkPtr2->width;
4930            }
4931            desired = tabX - width;
4932        }
4933    
4934        /*
4935         * Shift all of the chunks to the right so that the left edge is
4936         * at the desired location, then expand the chunk containing the
4937         * tab.  Be sure that the tab occupies at least the width of a
4938         * space character.
4939         */
4940    
4941        update:
4942        delta = desired - x;
4943        MeasureChars(textPtr->tkfont, " ", 1, 0, -1, 0, &spaceWidth);
4944        if (delta < spaceWidth) {
4945            delta = spaceWidth;
4946        }
4947        for (chunkPtr2 = chunkPtr->nextPtr; chunkPtr2 != NULL;
4948                chunkPtr2 = chunkPtr2->nextPtr) {
4949            chunkPtr2->x += delta;
4950        }
4951        chunkPtr->width += delta;
4952    }
4953    
4954    /*
4955     *----------------------------------------------------------------------
4956     *
4957     * SizeOfTab --
4958     *
4959     *      This returns an estimate of the amount of white space that will
4960     *      be consumed by a tab.
4961     *
4962     * Results:
4963     *      The return value is the minimum number of pixels that will
4964     *      be occupied by the index'th tab of tabArrayPtr, assuming that
4965     *      the current position on the line is x and the end of the
4966     *      line is maxX.  For numeric tabs, this is a conservative
4967     *      estimate.  The return value is always >= 0.
4968     *
4969     * Side effects:
4970     *      None.
4971     *
4972     *----------------------------------------------------------------------
4973     */
4974    
4975    static int
4976    SizeOfTab(textPtr, tabArrayPtr, index, x, maxX)
4977        TkText *textPtr;                    /* Information about the text widget as
4978                                             * a whole. */
4979        TkTextTabArray *tabArrayPtr;        /* Information about the tab stops
4980                                             * that apply to this line.  NULL
4981                                             * means use default tabbing (every
4982                                             * 8 chars.) */
4983        int index;                          /* Index of current tab stop. */
4984        int x;                              /* Current x-location in line. Only
4985                                             * used if tabArrayPtr == NULL. */
4986        int maxX;                           /* X-location of pixel just past the
4987                                             * right edge of the line. */
4988    {
4989        int tabX, prev, result, spaceWidth;
4990        TkTextTabAlign alignment;
4991    
4992        if ((tabArrayPtr == NULL) || (tabArrayPtr->numTabs == 0)) {
4993            tabX = NextTabStop(textPtr->tkfont, x, 0);
4994            return tabX - x;
4995        }
4996        if (index < tabArrayPtr->numTabs) {
4997            tabX = tabArrayPtr->tabs[index].location;
4998            alignment = tabArrayPtr->tabs[index].alignment;
4999        } else {
5000            /*
5001             * Ran out of tab stops;  compute a tab position by extrapolating
5002             * from the last two tab positions.
5003             */
5004    
5005            if (tabArrayPtr->numTabs > 1) {
5006                prev = tabArrayPtr->tabs[tabArrayPtr->numTabs-2].location;
5007            } else {
5008                prev = 0;
5009            }
5010            tabX = tabArrayPtr->tabs[tabArrayPtr->numTabs-1].location
5011                    + (index + 1 - tabArrayPtr->numTabs)
5012                    * (tabArrayPtr->tabs[tabArrayPtr->numTabs-1].location - prev);
5013            alignment = tabArrayPtr->tabs[tabArrayPtr->numTabs-1].alignment;
5014        }
5015        if (alignment == CENTER) {
5016            /*
5017             * Be very careful in the arithmetic below, because maxX may
5018             * be the largest positive number:  watch out for integer
5019             * overflow.
5020             */
5021    
5022            if ((maxX-tabX) < (tabX - x)) {
5023                result = (maxX - x) - 2*(maxX - tabX);
5024            } else {
5025                result = 0;
5026            }
5027            goto done;
5028        }
5029        if (alignment == RIGHT) {
5030            result = 0;
5031            goto done;
5032        }
5033    
5034        /*
5035         * Note: this treats NUMERIC alignment the same as LEFT
5036         * alignment, which is somewhat conservative.  However, it's
5037         * pretty tricky at this point to figure out exactly where
5038         * the damn decimal point will be.
5039         */
5040    
5041        if (tabX > x) {
5042            result = tabX - x;
5043        } else {
5044            result = 0;
5045        }
5046    
5047        done:
5048        MeasureChars(textPtr->tkfont, " ", 1, 0, -1, 0, &spaceWidth);
5049        if (result < spaceWidth) {
5050            result = spaceWidth;
5051        }
5052        return result;
5053    }
5054    
5055    /*
5056     *---------------------------------------------------------------------------
5057     *
5058     * NextTabStop --
5059     *
5060     *      Given the current position, determine where the next default
5061     *      tab stop would be located.  This procedure is called when the
5062     *      current chunk in the text has no tabs defined and so the default
5063     *      tab spacing for the font should be used.
5064     *
5065     * Results:
5066     *      The location in pixels of the next tab stop.
5067     *
5068     * Side effects:
5069     *      None.
5070     *
5071     *---------------------------------------------------------------------------
5072     */
5073    
5074    static int
5075    NextTabStop(tkfont, x, tabOrigin)
5076        Tk_Font tkfont;             /* Font in which chunk that contains tab
5077                                     * stop will be drawn. */
5078        int x;                      /* X-position in pixels where last
5079                                     * character was drawn.  The next tab stop
5080                                     * occurs somewhere after this location. */
5081        int tabOrigin;              /* The origin for tab stops.  May be
5082                                     * non-zero if text has been scrolled. */
5083    {
5084        int tabWidth, rem;
5085        
5086        tabWidth = Tk_TextWidth(tkfont, "0", 1) * 8;
5087        if (tabWidth == 0) {
5088            tabWidth = 1;
5089        }
5090    
5091        x += tabWidth;
5092        rem = (x - tabOrigin) % tabWidth;
5093        if (rem < 0) {
5094            rem += tabWidth;
5095        }
5096        x -= rem;
5097        return x;
5098    }
5099    
5100    /*
5101     *---------------------------------------------------------------------------
5102     *
5103     *  MeasureChars --
5104     *
5105     *      Determine the number of characters from the string that will fit
5106     *      in the given horizontal span.  The measurement is done under the
5107     *      assumption that Tk_DrawTextLayout will be used to actually display
5108     *      the characters.
5109     *
5110     *      If tabs are encountered in the string, they will be expanded
5111     *      to the next tab stop, unless the TK_IGNORE_TABS flag is specified.
5112     *
5113     *      If a newline is encountered in the string, the line will be
5114     *      broken at that point, unless the TK_NEWSLINES_NOT_SPECIAL flag
5115     *      is specified.  
5116     *
5117     * Results:
5118     *      The return value is the number of bytes from source
5119     *      that fit in the span given by startX and maxX.  *nextXPtr
5120     *      is filled in with the x-coordinate at which the first
5121     *      character that didn't fit would be drawn, if it were to
5122     *      be drawn.
5123     *
5124     * Side effects:
5125     *      None.
5126     *
5127     *--------------------------------------------------------------
5128     */
5129    
5130    static int
5131    MeasureChars(tkfont, source, maxBytes, startX, maxX, tabOrigin, nextXPtr)
5132        Tk_Font tkfont;             /* Font in which to draw characters. */
5133        CONST char *source;         /* Characters to be displayed.  Need not
5134                                     * be NULL-terminated. */
5135        int maxBytes;               /* Maximum # of bytes to consider from
5136                                     * source. */
5137        int startX;                 /* X-position at which first character will
5138                                     * be drawn. */
5139        int maxX;                   /* Don't consider any character that would
5140                                     * cross this x-position. */
5141        int tabOrigin;              /* X-location that serves as "origin" for
5142                                     * tab stops. */
5143        int *nextXPtr;              /* Return x-position of terminating
5144                                     * character here. */
5145    {
5146        int curX, width, ch;
5147        CONST char *special, *end, *start;
5148    
5149        ch = 0;                     /* lint. */
5150        curX = startX;
5151        special = source;
5152        end = source + maxBytes;
5153        for (start = source; start < end; ) {
5154            if (start >= special) {
5155                /*
5156                 * Find the next special character in the string.
5157                 */
5158    
5159                for (special = start; special < end; special++) {
5160                    ch = *special;
5161                    if ((ch == '\t') || (ch == '\n')) {
5162                        break;
5163                    }
5164                }
5165            }
5166    
5167            /*
5168             * Special points at the next special character (or the end of the
5169             * string).  Process characters between start and special.
5170             */
5171    
5172            if ((maxX >= 0) && (curX >= maxX)) {
5173                break;
5174            }
5175            start += Tk_MeasureChars(tkfont, start, special - start, maxX - curX,
5176                    0, &width);
5177            curX += width;
5178            if (start < special) {
5179                /*
5180                 * No more chars fit in line.
5181                 */
5182    
5183                break;
5184            }
5185            if (special < end) {
5186                if (ch == '\t') {
5187                    start++;
5188                } else {
5189                    break;
5190                }
5191            }
5192        }
5193    
5194        *nextXPtr = curX;
5195        return start - source;
5196    }
5197    
5198    /* End of tktextdisp.c */

Legend:
Removed from v.29  
changed lines
  Added in v.269

dashley@gmail.com
ViewVC Help
Powered by ViewVC 1.1.25