OSDN Git Service

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