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

Diff of /projs/trunk/shared_source/c_tk_base_7_5_w_mods/tktextdisp.c

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

projs/trunk/shared_source/tk_base/tktextdisp.c revision 42 by dashley, Fri Oct 14 01:50:00 2016 UTC projs/trunk/shared_source/c_tk_base_7_5_w_mods/tktextdisp.c revision 71 by dashley, Sat Nov 5 11:07:06 2016 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            }