1/*
2 * tkTextDisp.c --
3 *
4 *	This module provides facilities to display text widgets. It is the
5 *	only place where information is kept about the screen layout of text
6 *	widgets. (Well, strictly, each TkTextLine and B-tree node caches its
7 *	last observed pixel height, but that information originates here).
8 *
9 * Copyright (c) 1992-1994 The Regents of the University of California.
10 * Copyright (c) 1994-1997 Sun Microsystems, Inc.
11 *
12 * See the file "license.terms" for information on usage and redistribution of
13 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
14 *
15 * RCS: @(#) $Id$
16 */
17
18#include "tkInt.h"
19#include "tkText.h"
20
21#ifdef __WIN32__
22#include "tkWinInt.h"
23#endif
24
25#ifdef MAC_OSX_TK
26#include "tkMacOSXInt.h"
27#endif
28
29/*
30 * "Calculations of line pixel heights and the size of the vertical
31 * scrollbar."
32 *
33 * Given that tag, font and elide changes can happen to large numbers of
34 * diverse chunks in a text widget containing megabytes of text, it is not
35 * possible to recalculate all affected height information immediately any
36 * such change takes place and maintain a responsive user-experience. Yet, for
37 * an accurate vertical scrollbar to be drawn, we must know the total number
38 * of vertical pixels shown on display versus the number available to be
39 * displayed.
40 *
41 * The way the text widget solves this problem is by maintaining cached line
42 * pixel heights (in the BTree for each logical line), and having asynchronous
43 * timer callbacks (i) to iterate through the logical lines recalculating
44 * their heights, and (ii) to recalculate the vertical scrollbar's position
45 * and size.
46 *
47 * Typically this works well but there are some situations where the overall
48 * functional design of this file causes some problems. These problems can
49 * only arise because the calculations used to display lines on screen are not
50 * connected to those in the iterating-line- recalculation-process.
51 *
52 * The reason for this disconnect is that the display calculations operate in
53 * display lines, and the iteration and cache operates in logical lines.
54 * Given that the display calculations both need not contain complete logical
55 * lines (at top or bottom of display), and that they do not actually keep
56 * track of logical lines (for simplicity of code and historical design), this
57 * means a line may be known and drawn with a different pixel height to that
58 * which is cached in the BTree, and this might cause some temporary
59 * undesirable mismatch between display and the vertical scrollbar.
60 *
61 * All such mismatches should be temporary, however, since the asynchronous
62 * height calculations will always catch up eventually.
63 *
64 * For further details see the comments before and within the following
65 * functions below: LayoutDLine, AsyncUpdateLineMetrics, GetYView,
66 * GetYPixelCount, TkTextUpdateOneLine, TkTextUpdateLineMetrics.
67 *
68 * For details of the way in which the BTree keeps track of pixel heights, see
69 * tkTextBTree.c. Basically the BTree maintains two pieces of information: the
70 * logical line indices and the pixel height cache.
71 */
72
73/*
74 * TK_LAYOUT_WITH_BASE_CHUNKS:
75 *
76 *	With this macro set, collect all char chunks that have no holes
77 *	between them, that are on the same line and use the same font and font
78 *	size. Allocate the chars of all these chunks, the so-called "stretch",
79 *	in a DString in the first chunk, the so-called "base chunk". Use the
80 *	base chunk string for measuring and drawing, so that these actions are
81 *	always performed with maximum context.
82 *
83 *	This is necessary for text rendering engines that provide ligatures
84 *	and sub-pixel layout, like ATSU on Mac. If we don't do this, the
85 *	measuring will change all the time, leading to an ugly "tremble and
86 *	shiver" effect. This is because of the continuous splitting and
87 *	re-merging of chunks that goes on in a text widget, when the cursor or
88 *	the selection move.
89 *
90 * Side effects:
91 *
92 *	Memory management changes. Instead of attaching the character data to
93 *	the clientData structures of the char chunks, an additional DString is
94 *	used. The collection process will even lead to resizing this DString
95 *	for large stretches (> TCL_DSTRING_STATIC_SIZE == 200). We could
96 *	reduce the overall memory footprint by copying the result to a plain
97 *	char array after the line breaking process, but that would complicate
98 *	the code and make performance even worse speedwise. See also TODOs.
99 *
100 * TODOs:
101 *
102 *    -	Move the character collection process from the LayoutProc into
103 *	LayoutDLine(), so that the collection can be done before actual
104 *	layout. In this way measuring can look at the following text, too,
105 *	right from the beginning. Memory handling can also be improved with
106 *	this. Problem: We don't easily know which chunks are adjacent until
107 *	all the other chunks have calculated their width. Apparently marks
108 *	would return width==0. A separate char collection loop would have to
109 *	know these things.
110 *
111 *    -	Use a new context parameter to pass the context from LayoutDLine() to
112 *	the LayoutProc instead of using a global variable like now. Not
113 *	pressing until the previous point gets implemented.
114 */
115
116/*
117 * The following structure describes how to display a range of characters.
118 * The information is generated by scanning all of the tags associated with
119 * the characters and combining that with default information for the overall
120 * widget. These structures form the hash keys for dInfoPtr->styleTable.
121 */
122
123typedef struct StyleValues {
124    Tk_3DBorder border;		/* Used for drawing background under text.
125				 * NULL means use widget background. */
126    int borderWidth;		/* Width of 3-D border for background. */
127    int relief;			/* 3-D relief for background. */
128    Pixmap bgStipple;		/* Stipple bitmap for background. None means
129				 * draw solid. */
130    XColor *fgColor;		/* Foreground color for text. */
131    Tk_Font tkfont;		/* Font for displaying text. */
132    Pixmap fgStipple;		/* Stipple bitmap for text and other
133				 * foreground stuff. None means draw solid.*/
134    int justify;		/* Justification style for text. */
135    int lMargin1;		/* Left margin, in pixels, for first display
136				 * line of each text line. */
137    int lMargin2;		/* Left margin, in pixels, for second and
138				 * later display lines of each text line. */
139    int offset;			/* Offset in pixels of baseline, relative to
140				 * baseline of line. */
141    int overstrike;		/* Non-zero means draw overstrike through
142				 * text. */
143    int rMargin;		/* Right margin, in pixels. */
144    int spacing1;		/* Spacing above first dline in text line. */
145    int spacing2;		/* Spacing between lines of dline. */
146    int spacing3;		/* Spacing below last dline in text line. */
147    TkTextTabArray *tabArrayPtr;/* Locations and types of tab stops (may be
148				 * NULL). */
149    int tabStyle;		/* One of TABULAR or WORDPROCESSOR. */
150    int underline;		/* Non-zero means draw underline underneath
151				 * text. */
152    int elide;			/* Zero means draw text, otherwise not. */
153    TkWrapMode wrapMode;	/* How to handle wrap-around for this tag.
154				 * One of TEXT_WRAPMODE_CHAR,
155				 * TEXT_WRAPMODE_NONE or TEXT_WRAPMODE_WORD.*/
156} StyleValues;
157
158/*
159 * The following structure extends the StyleValues structure above with
160 * graphics contexts used to actually draw the characters. The entries in
161 * dInfoPtr->styleTable point to structures of this type.
162 */
163
164typedef struct TextStyle {
165    int refCount;		/* Number of times this structure is
166				 * referenced in Chunks. */
167    GC bgGC;			/* Graphics context for background. None means
168				 * use widget background. */
169    GC fgGC;			/* Graphics context for foreground. */
170    StyleValues *sValuePtr;	/* Raw information from which GCs were
171				 * derived. */
172    Tcl_HashEntry *hPtr;	/* Pointer to entry in styleTable. Used to
173				 * delete entry. */
174} TextStyle;
175
176/*
177 * The following macro determines whether two styles have the same background
178 * so that, for example, no beveled border should be drawn between them.
179 */
180
181#define SAME_BACKGROUND(s1, s2) \
182    (((s1)->sValuePtr->border == (s2)->sValuePtr->border) \
183	&& ((s1)->sValuePtr->borderWidth == (s2)->sValuePtr->borderWidth) \
184	&& ((s1)->sValuePtr->relief == (s2)->sValuePtr->relief) \
185	&& ((s1)->sValuePtr->bgStipple == (s2)->sValuePtr->bgStipple))
186
187/*
188 * The following macro is used to compare two floating-point numbers to within
189 * a certain degree of scale. Direct comparison fails on processors where the
190 * processor and memory representations of FP numbers of a particular
191 * precision is different (e.g. Intel)
192 */
193
194#define FP_EQUAL_SCALE(double1, double2, scaleFactor) \
195    (fabs((double1)-(double2))*((scaleFactor)+1.0) < 0.3)
196
197/*
198 * Macro to make debugging/testing logging a little easier.
199 */
200
201#define LOG(toVar,what) \
202    Tcl_SetVar2(textPtr->interp, toVar, NULL, (what), \
203	    TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT)
204
205/*
206 * The following structure describes one line of the display, which may be
207 * either part or all of one line of the text.
208 */
209
210typedef struct DLine {
211    TkTextIndex index;		/* Identifies first character in text that is
212				 * displayed on this line. */
213    int byteCount;		/* Number of bytes accounted for by this
214				 * display line, including a trailing space or
215				 * newline that isn't actually displayed. */
216    int logicalLinesMerged;	/* Number of extra logical lines merged into
217				 * this one due to elided newlines. */
218    int y;			/* Y-position at which line is supposed to be
219				 * drawn (topmost pixel of rectangular area
220				 * occupied by line). */
221    int oldY;			/* Y-position at which line currently appears
222				 * on display. This is used to move lines by
223				 * scrolling rather than re-drawing. If
224				 * 'flags' have the OLD_Y_INVALID bit set,
225				 * then we will never examine this field
226				 * (which means line isn't currently visible
227				 * on display and must be redrawn). */
228    int height;			/* Height of line, in pixels. */
229    int baseline;		/* Offset of text baseline from y, in
230				 * pixels. */
231    int spaceAbove;		/* How much extra space was added to the top
232				 * of the line because of spacing options.
233				 * This is included in height and baseline. */
234    int spaceBelow;		/* How much extra space was added to the
235				 * bottom of the line because of spacing
236				 * options. This is included in height. */
237    int length;			/* Total length of line, in pixels. */
238    TkTextDispChunk *chunkPtr;	/* Pointer to first chunk in list of all of
239				 * those that are displayed on this line of
240				 * the screen. */
241    struct DLine *nextPtr;	/* Next in list of all display lines for this
242				 * window. The list is sorted in order from
243				 * top to bottom. Note: the next DLine doesn't
244				 * always correspond to the next line of text:
245				 * (a) can have multiple DLines for one text
246				 * line, and (b) can have gaps where DLine's
247				 * have been deleted because they're out of
248				 * date. */
249    int flags;			/* Various flag bits: see below for values. */
250} DLine;
251
252/*
253 * Flag bits for DLine structures:
254 *
255 * HAS_3D_BORDER -		Non-zero means that at least one of the chunks
256 *				in this line has a 3D border, so it
257 *				potentially interacts with 3D borders in
258 *				neighboring lines (see DisplayLineBackground).
259 * NEW_LAYOUT -			Non-zero means that the line has been
260 *				re-layed out since the last time the display
261 *				was updated.
262 * TOP_LINE -			Non-zero means that this was the top line in
263 *				in the window the last time that the window
264 *				was laid out. This is important because a line
265 *				may be displayed differently if its at the top
266 *				or bottom than if it's in the middle
267 *				(e.g. beveled edges aren't displayed for
268 *				middle lines if the adjacent line has a
269 *				similar background).
270 * BOTTOM_LINE -		Non-zero means that this was the bottom line
271 *				in the window the last time that the window
272 *				was laid out.
273 * OLD_Y_INVALID -		The value of oldY in the structure is not
274 *				valid or useful and should not be examined.
275 *				'oldY' is only useful when the DLine is
276 *				currently displayed at a different position
277 *				and we wish to re-display it via scrolling, so
278 *				this means the DLine needs redrawing.
279 */
280
281#define HAS_3D_BORDER	1
282#define NEW_LAYOUT	2
283#define TOP_LINE	4
284#define BOTTOM_LINE	8
285#define OLD_Y_INVALID  16
286
287/*
288 * Overall display information for a text widget:
289 */
290
291typedef struct TextDInfo {
292    Tcl_HashTable styleTable;	/* Hash table that maps from StyleValues to
293				 * TextStyles for this widget. */
294    DLine *dLinePtr;		/* First in list of all display lines for this
295				 * widget, in order from top to bottom. */
296    int topPixelOffset;		/* Identifies first pixel in top display line
297				 * to display in window. */
298    int newTopPixelOffset;	/* Desired first pixel in top display line to
299				 * display in window. */
300    GC copyGC;			/* Graphics context for copying from off-
301				 * screen pixmaps onto screen. */
302    GC scrollGC;		/* Graphics context for copying from one place
303				 * in the window to another (scrolling):
304				 * differs from copyGC in that we need to get
305				 * GraphicsExpose events. */
306    int x;			/* First x-coordinate that may be used for
307				 * actually displaying line information.
308				 * Leaves space for border, etc. */
309    int y;			/* First y-coordinate that may be used for
310				 * actually displaying line information.
311				 * Leaves space for border, etc. */
312    int maxX;			/* First x-coordinate to right of available
313				 * space for displaying lines. */
314    int maxY;			/* First y-coordinate below available space
315				 * for displaying lines. */
316    int topOfEof;		/* Top-most pixel (lowest y-value) that has
317				 * been drawn in the appropriate fashion for
318				 * the portion of the window after the last
319				 * line of the text. This field is used to
320				 * figure out when to redraw part or all of
321				 * the eof field. */
322
323    /*
324     * Information used for scrolling:
325     */
326
327    int newXPixelOffset;	/* Desired x scroll position, measured as the
328				 * number of pixels off-screen to the left for
329				 * a line with no left margin. */
330    int curXPixelOffset;	/* Actual x scroll position, measured as the
331				 * number of pixels off-screen to the left. */
332    int maxLength;		/* Length in pixels of longest line that's
333				 * visible in window (length may exceed window
334				 * size). If there's no wrapping, this will be
335				 * zero. */
336    double xScrollFirst, xScrollLast;
337				/* Most recent values reported to horizontal
338				 * scrollbar; used to eliminate unnecessary
339				 * reports. */
340    double yScrollFirst, yScrollLast;
341				/* Most recent values reported to vertical
342				 * scrollbar; used to eliminate unnecessary
343				 * reports. */
344
345    /*
346     * The following information is used to implement scanning:
347     */
348
349    int scanMarkXPixel;		/* Pixel index of left edge of the window when
350				 * the scan started. */
351    int scanMarkX;		/* X-position of mouse at time scan started. */
352    int scanTotalYScroll;	/* Total scrolling (in screen pixels) that has
353				 * occurred since scanMarkY was set. */
354    int scanMarkY;		/* Y-position of mouse at time scan started. */
355
356    /*
357     * Miscellaneous information:
358     */
359
360    int dLinesInvalidated;	/* This value is set to 1 whenever something
361				 * happens that invalidates information in
362				 * DLine structures; if a redisplay is in
363				 * progress, it will see this and abort the
364				 * redisplay. This is needed because, for
365				 * example, an embedded window could change
366				 * its size when it is first displayed,
367				 * invalidating the DLine that is currently
368				 * being displayed. If redisplay continues, it
369				 * will use freed memory and could dump
370				 * core. */
371    int flags;			/* Various flag values: see below for
372				 * definitions. */
373    /*
374     * Information used to handle the asynchronous updating of the y-scrollbar
375     * and the vertical height calculations:
376     */
377
378    int lineMetricUpdateEpoch;	/* Stores a number which is incremented each
379				 * time the text widget changes in a
380				 * significant way (e.g. resizing or
381				 * geometry-influencing tag changes). */
382    int currentMetricUpdateLine;/* Stores a counter which is used to iterate
383				 * over the logical lines contained in the
384				 * widget and update their geometry
385				 * calculations, if they are out of date. */
386    TkTextIndex metricIndex;	/* If the current metric update line wraps
387				 * into very many display lines, then this is
388				 * used to keep track of what index we've got
389				 * to so far... */
390    int metricPixelHeight;	/* ...and this is for the height calculation
391				 * so far...*/
392    int metricEpoch;		/* ...and this for the epoch of the partial
393				 * calculation so it can be cancelled if
394				 * things change once more. This field will be
395				 * -1 if there is no long-line calculation in
396				 * progress, and take a non-negative value if
397				 * there is such a calculation in progress. */
398    int lastMetricUpdateLine;	/* When the current update line reaches this
399				 * line, we are done and should stop the
400				 * asychronous callback mechanism. */
401    Tcl_TimerToken lineUpdateTimer;
402				/* A token pointing to the current line metric
403				 * update callback. */
404    Tcl_TimerToken scrollbarTimer;
405				/* A token pointing to the current scrollbar
406				 * update callback. */
407} TextDInfo;
408
409/*
410 * In TkTextDispChunk structures for character segments, the clientData field
411 * points to one of the following structures:
412 */
413
414#if !TK_LAYOUT_WITH_BASE_CHUNKS
415
416typedef struct CharInfo {
417    int numBytes;		/* Number of bytes to display. */
418    char chars[4];		/* UTF characters to display. Actual size will
419				 * be numBytes, not 4. THIS MUST BE THE LAST
420				 * FIELD IN THE STRUCTURE. */
421} CharInfo;
422
423#else /* TK_LAYOUT_WITH_BASE_CHUNKS */
424
425typedef struct CharInfo {
426    TkTextDispChunk *baseChunkPtr;
427    int baseOffset;		/* Starting offset in base chunk
428				 * baseChars. */
429    int numBytes;		/* Number of bytes that belong to this
430				 * chunk. */
431    const char *chars;		/* UTF characters to display. Actually points
432				 * into the baseChars of the base chunk. Only
433				 * valid after FinalizeBaseChunk(). */
434} CharInfo;
435
436/*
437 * The BaseCharInfo is a CharInfo with some additional data added.
438 */
439
440typedef struct BaseCharInfo {
441    CharInfo ci;
442    Tcl_DString baseChars;	/* Actual characters for the stretch of text
443				 * represented by this base chunk. */
444    int width;			/* Width in pixels of the whole string, if
445				 * known, else -1. Valid during
446				 * LayoutDLine(). */
447} BaseCharInfo;
448
449static TkTextDispChunk *baseCharChunkPtr = NULL;
450
451#endif /* TK_LAYOUT_WITH_BASE_CHUNKS */
452
453/*
454 * Flag values for TextDInfo structures:
455 *
456 * DINFO_OUT_OF_DATE:		Non-zero means that the DLine structures for
457 *				this window are partially or completely out of
458 *				date and need to be recomputed.
459 * REDRAW_PENDING:		Means that a when-idle handler has been
460 *				scheduled to update the display.
461 * REDRAW_BORDERS:		Means window border or pad area has
462 *				potentially been damaged and must be redrawn.
463 * REPICK_NEEDED:		1 means that the widget has been modified in a
464 *				way that could change the current character (a
465 *				different character might be under the mouse
466 *				cursor now). Need to recompute the current
467 *				character before the next redisplay.
468 */
469
470#define DINFO_OUT_OF_DATE	1
471#define REDRAW_PENDING		2
472#define REDRAW_BORDERS		4
473#define REPICK_NEEDED		8
474
475/*
476 * Action values for FreeDLines:
477 *
478 * DLINE_FREE:		Free the lines, but no need to unlink them from the
479 *			current list of actual display lines.
480 * DLINE_UNLINK:	Free and unlink from current display.
481 * DLINE_FREE_TEMP:	Free, but don't unlink, and also don't set
482 *			'dLinesInvalidated'.
483 */
484
485#define DLINE_FREE	  0
486#define DLINE_UNLINK	  1
487#define DLINE_FREE_TEMP	  2
488
489/*
490 * The following counters keep statistics about redisplay that can be checked
491 * to see how clever this code is at reducing redisplays.
492 */
493
494static int numRedisplays;	/* Number of calls to DisplayText. */
495static int linesRedrawn;	/* Number of calls to DisplayDLine. */
496static int numCopies;		/* Number of calls to XCopyArea to copy part
497				 * of the screen. */
498static int lineHeightsRecalculated;
499				/* Number of line layouts purely for height
500				 * calculation purposes.*/
501/*
502 * Forward declarations for functions defined later in this file:
503 */
504
505static void		AdjustForTab(TkText *textPtr,
506			    TkTextTabArray *tabArrayPtr, int index,
507			    TkTextDispChunk *chunkPtr);
508static void		CharBboxProc(TkText *textPtr,
509			    TkTextDispChunk *chunkPtr, int index, int y,
510			    int lineHeight, int baseline, int *xPtr,
511			    int *yPtr, int *widthPtr, int *heightPtr);
512static int		CharChunkMeasureChars(TkTextDispChunk *chunkPtr,
513			    const char *chars, int charsLen,
514			    int start, int end, int startX, int maxX,
515			    int flags, int *nextX);
516static void		CharDisplayProc(TkText *textPtr,
517			    TkTextDispChunk *chunkPtr, int x, int y,
518			    int height, int baseline, Display *display,
519			    Drawable dst, int screenY);
520static int		CharMeasureProc(TkTextDispChunk *chunkPtr, int x);
521static void		CharUndisplayProc(TkText *textPtr,
522			    TkTextDispChunk *chunkPtr);
523#if TK_LAYOUT_WITH_BASE_CHUNKS
524static void		FinalizeBaseChunk(TkTextDispChunk *additionalChunkPtr);
525static void		FreeBaseChunk(TkTextDispChunk *baseChunkPtr);
526static int		IsSameFGStyle(TextStyle *style1, TextStyle *style2);
527static void		RemoveFromBaseChunk(TkTextDispChunk *chunkPtr);
528#endif
529/*
530 * Definitions of elided procs. Compiler can't inline these since we use
531 * pointers to these functions. ElideDisplayProc and ElideUndisplayProc are
532 * special-cased for speed, as potentially many elided DLine chunks if large,
533 * tag toggle-filled elided region.
534 */
535static void		ElideBboxProc(TkText *textPtr,
536			    TkTextDispChunk *chunkPtr, int index, int y,
537			    int lineHeight, int baseline, int *xPtr,
538			    int *yPtr, int *widthPtr, int *heightPtr);
539static int		ElideMeasureProc(TkTextDispChunk *chunkPtr, int x);
540static void		DisplayDLine(TkText *textPtr, DLine *dlPtr,
541			    DLine *prevPtr, Pixmap pixmap);
542static void		DisplayLineBackground(TkText *textPtr, DLine *dlPtr,
543			    DLine *prevPtr, Pixmap pixmap);
544static void		DisplayText(ClientData clientData);
545static DLine *		FindDLine(DLine *dlPtr, CONST TkTextIndex *indexPtr);
546static void		FreeDLines(TkText *textPtr, DLine *firstPtr,
547			    DLine *lastPtr, int action);
548static void		FreeStyle(TkText *textPtr, TextStyle *stylePtr);
549static TextStyle *	GetStyle(TkText *textPtr, CONST TkTextIndex *indexPtr);
550static void		GetXView(Tcl_Interp *interp, TkText *textPtr,
551			    int report);
552static void		GetYView(Tcl_Interp *interp, TkText *textPtr,
553			    int report);
554static int		GetYPixelCount(TkText *textPtr, DLine *dlPtr);
555static DLine *		LayoutDLine(TkText *textPtr,
556			    CONST TkTextIndex *indexPtr);
557static int		MeasureChars(Tk_Font tkfont, CONST char *source,
558			    int maxBytes, int rangeStart, int rangeLength,
559			    int startX, int maxX, int flags, int *nextXPtr);
560static void		MeasureUp(TkText *textPtr,
561			    CONST TkTextIndex *srcPtr, int distance,
562			    TkTextIndex *dstPtr, int *overlap);
563static int		NextTabStop(Tk_Font tkfont, int x, int tabOrigin);
564static void		UpdateDisplayInfo(TkText *textPtr);
565static void		YScrollByLines(TkText *textPtr, int offset);
566static void		YScrollByPixels(TkText *textPtr, int offset);
567static int		SizeOfTab(TkText *textPtr, int tabStyle,
568			    TkTextTabArray *tabArrayPtr, int *indexPtr, int x,
569			    int maxX);
570static void		TextChanged(TkText *textPtr,
571			    CONST TkTextIndex *index1Ptr,
572			    CONST TkTextIndex *index2Ptr);
573static void		TextInvalidateRegion(TkText *textPtr, TkRegion region);
574static void		TextRedrawTag(TkText *textPtr,
575			    TkTextIndex *index1Ptr, TkTextIndex *index2Ptr,
576			    TkTextTag *tagPtr, int withTag);
577static void		TextInvalidateLineMetrics(TkText *textPtr,
578			    TkTextLine *linePtr, int lineCount, int action);
579static int		CalculateDisplayLineHeight(TkText *textPtr,
580			    CONST TkTextIndex *indexPtr, int *byteCountPtr,
581			    int *mergedLinePtr);
582static void		DlineIndexOfX(TkText *textPtr,
583			    DLine *dlPtr, int x, TkTextIndex *indexPtr);
584static int		DlineXOfIndex(TkText *textPtr,
585			    DLine *dlPtr, int byteIndex);
586static int		TextGetScrollInfoObj(Tcl_Interp *interp,
587			    TkText *textPtr, int objc,
588			    Tcl_Obj *CONST objv[], double *dblPtr,
589			    int *intPtr);
590static void		AsyncUpdateLineMetrics(ClientData clientData);
591static void		AsyncUpdateYScrollbar(ClientData clientData);
592
593/*
594 * Result values returned by TextGetScrollInfoObj:
595 */
596
597#define TKTEXT_SCROLL_MOVETO	1
598#define TKTEXT_SCROLL_PAGES	2
599#define TKTEXT_SCROLL_UNITS	3
600#define TKTEXT_SCROLL_ERROR	4
601#define TKTEXT_SCROLL_PIXELS	5
602
603/*
604 *----------------------------------------------------------------------
605 *
606 * TkTextCreateDInfo --
607 *
608 *	This function is called when a new text widget is created. Its job is
609 *	to set up display-related information for the widget.
610 *
611 * Results:
612 *	None.
613 *
614 * Side effects:
615 *	A TextDInfo data structure is allocated and initialized and attached
616 *	to textPtr.
617 *
618 *----------------------------------------------------------------------
619 */
620
621void
622TkTextCreateDInfo(
623    TkText *textPtr)		/* Overall information for text widget. */
624{
625    register TextDInfo *dInfoPtr;
626    XGCValues gcValues;
627
628    dInfoPtr = (TextDInfo *) ckalloc(sizeof(TextDInfo));
629    Tcl_InitHashTable(&dInfoPtr->styleTable, sizeof(StyleValues)/sizeof(int));
630    dInfoPtr->dLinePtr = NULL;
631    dInfoPtr->copyGC = None;
632    gcValues.graphics_exposures = True;
633    dInfoPtr->scrollGC = Tk_GetGC(textPtr->tkwin, GCGraphicsExposures,
634	    &gcValues);
635    dInfoPtr->topOfEof = 0;
636    dInfoPtr->newXPixelOffset = 0;
637    dInfoPtr->curXPixelOffset = 0;
638    dInfoPtr->maxLength = 0;
639    dInfoPtr->xScrollFirst = -1;
640    dInfoPtr->xScrollLast = -1;
641    dInfoPtr->yScrollFirst = -1;
642    dInfoPtr->yScrollLast = -1;
643    dInfoPtr->scanMarkXPixel = 0;
644    dInfoPtr->scanMarkX = 0;
645    dInfoPtr->scanTotalYScroll = 0;
646    dInfoPtr->scanMarkY = 0;
647    dInfoPtr->dLinesInvalidated = 0;
648    dInfoPtr->flags = DINFO_OUT_OF_DATE;
649    dInfoPtr->topPixelOffset = 0;
650    dInfoPtr->newTopPixelOffset = 0;
651    dInfoPtr->currentMetricUpdateLine = -1;
652    dInfoPtr->lastMetricUpdateLine = -1;
653    dInfoPtr->lineMetricUpdateEpoch = 1;
654    dInfoPtr->metricEpoch = -1;
655    dInfoPtr->metricIndex.textPtr = NULL;
656    dInfoPtr->metricIndex.linePtr = NULL;
657
658    /*
659     * Add a refCount for each of the idle call-backs.
660     */
661
662    textPtr->refCount++;
663    dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(0,
664	    AsyncUpdateLineMetrics, (ClientData) textPtr);
665    textPtr->refCount++;
666    dInfoPtr->scrollbarTimer = Tcl_CreateTimerHandler(200,
667	    AsyncUpdateYScrollbar, (ClientData) textPtr);
668
669    textPtr->dInfoPtr = dInfoPtr;
670}
671
672/*
673 *----------------------------------------------------------------------
674 *
675 * TkTextFreeDInfo --
676 *
677 *	This function is called to free up all of the private display
678 *	information kept by this file for a text widget.
679 *
680 * Results:
681 *	None.
682 *
683 * Side effects:
684 *	Lots of resources get freed.
685 *
686 *----------------------------------------------------------------------
687 */
688
689void
690TkTextFreeDInfo(
691    TkText *textPtr)		/* Overall information for text widget. */
692{
693    register TextDInfo *dInfoPtr = textPtr->dInfoPtr;
694
695    /*
696     * Be careful to free up styleTable *after* freeing up all the DLines, so
697     * that the hash table is still intact to free up the style-related
698     * information from the lines. Once the lines are all free then styleTable
699     * will be empty.
700     */
701
702    FreeDLines(textPtr, dInfoPtr->dLinePtr, NULL, DLINE_UNLINK);
703    Tcl_DeleteHashTable(&dInfoPtr->styleTable);
704    if (dInfoPtr->copyGC != None) {
705	Tk_FreeGC(textPtr->display, dInfoPtr->copyGC);
706    }
707    Tk_FreeGC(textPtr->display, dInfoPtr->scrollGC);
708    if (dInfoPtr->flags & REDRAW_PENDING) {
709	Tcl_CancelIdleCall(DisplayText, (ClientData) textPtr);
710    }
711    if (dInfoPtr->lineUpdateTimer != NULL) {
712	Tcl_DeleteTimerHandler(dInfoPtr->lineUpdateTimer);
713	textPtr->refCount--;
714	dInfoPtr->lineUpdateTimer = NULL;
715    }
716    if (dInfoPtr->scrollbarTimer != NULL) {
717	Tcl_DeleteTimerHandler(dInfoPtr->scrollbarTimer);
718	textPtr->refCount--;
719	dInfoPtr->scrollbarTimer = NULL;
720    }
721    ckfree((char *) dInfoPtr);
722}
723
724/*
725 *----------------------------------------------------------------------
726 *
727 * GetStyle --
728 *
729 *	This function creates all the information needed to display text at a
730 *	particular location.
731 *
732 * Results:
733 *	The return value is a pointer to a TextStyle structure that
734 *	corresponds to *sValuePtr.
735 *
736 * Side effects:
737 *	A new entry may be created in the style table for the widget.
738 *
739 *----------------------------------------------------------------------
740 */
741
742static TextStyle *
743GetStyle(
744    TkText *textPtr,		/* Overall information about text widget. */
745    CONST TkTextIndex *indexPtr)/* The character in the text for which display
746				 * information is wanted. */
747{
748    TkTextTag **tagPtrs;
749    register TkTextTag *tagPtr;
750    StyleValues styleValues;
751    TextStyle *stylePtr;
752    Tcl_HashEntry *hPtr;
753    int numTags, isNew, i;
754    XGCValues gcValues;
755    unsigned long mask;
756    /*
757     * The variables below keep track of the highest-priority specification
758     * that has occurred for each of the various fields of the StyleValues.
759     */
760    int borderPrio, borderWidthPrio, reliefPrio, bgStipplePrio;
761    int fgPrio, fontPrio, fgStipplePrio;
762    int underlinePrio, elidePrio, justifyPrio, offsetPrio;
763    int lMargin1Prio, lMargin2Prio, rMarginPrio;
764    int spacing1Prio, spacing2Prio, spacing3Prio;
765    int overstrikePrio, tabPrio, tabStylePrio, wrapPrio;
766
767    /*
768     * Find out what tags are present for the character, then compute a
769     * StyleValues structure corresponding to those tags (scan through all of
770     * the tags, saving information for the highest-priority tag).
771     */
772
773    tagPtrs = TkBTreeGetTags(indexPtr, textPtr, &numTags);
774    borderPrio = borderWidthPrio = reliefPrio = bgStipplePrio = -1;
775    fgPrio = fontPrio = fgStipplePrio = -1;
776    underlinePrio = elidePrio = justifyPrio = offsetPrio = -1;
777    lMargin1Prio = lMargin2Prio = rMarginPrio = -1;
778    spacing1Prio = spacing2Prio = spacing3Prio = -1;
779    overstrikePrio = tabPrio = tabStylePrio = wrapPrio = -1;
780    memset(&styleValues, 0, sizeof(StyleValues));
781    styleValues.relief = TK_RELIEF_FLAT;
782    styleValues.fgColor = textPtr->fgColor;
783    styleValues.tkfont = textPtr->tkfont;
784    styleValues.justify = TK_JUSTIFY_LEFT;
785    styleValues.spacing1 = textPtr->spacing1;
786    styleValues.spacing2 = textPtr->spacing2;
787    styleValues.spacing3 = textPtr->spacing3;
788    styleValues.tabArrayPtr = textPtr->tabArrayPtr;
789    styleValues.tabStyle = textPtr->tabStyle;
790    styleValues.wrapMode = textPtr->wrapMode;
791    styleValues.elide = 0;
792
793    for (i = 0 ; i < numTags; i++) {
794	Tk_3DBorder border;
795
796	tagPtr = tagPtrs[i];
797	border = tagPtr->border;
798
799	/*
800	 * If this is the selection tag, and inactiveSelBorder is NULL (the
801	 * default on Windows), then we need to skip it if we don't have the
802	 * focus.
803	 */
804
805	if ((tagPtr == textPtr->selTagPtr) && !(textPtr->flags & GOT_FOCUS)) {
806	    if (textPtr->inactiveSelBorder == NULL
807#ifdef MAC_OSX_TK
808		    /* Don't show inactive selection in disabled widgets. */
809		    || textPtr->state == TK_TEXT_STATE_DISABLED
810#endif
811	    ) {
812		continue;
813	    }
814	    border = textPtr->inactiveSelBorder;
815	}
816
817	if ((border != NULL) && (tagPtr->priority > borderPrio)) {
818	    styleValues.border = border;
819	    borderPrio = tagPtr->priority;
820	}
821	if ((tagPtr->borderWidthPtr != NULL)
822		&& (Tcl_GetString(tagPtr->borderWidthPtr)[0] != '\0')
823		&& (tagPtr->priority > borderWidthPrio)) {
824	    styleValues.borderWidth = tagPtr->borderWidth;
825	    borderWidthPrio = tagPtr->priority;
826	}
827	if ((tagPtr->reliefString != NULL)
828		&& (tagPtr->priority > reliefPrio)) {
829	    if (styleValues.border == NULL) {
830		styleValues.border = textPtr->border;
831	    }
832	    styleValues.relief = tagPtr->relief;
833	    reliefPrio = tagPtr->priority;
834	}
835	if ((tagPtr->bgStipple != None)
836		&& (tagPtr->priority > bgStipplePrio)) {
837	    styleValues.bgStipple = tagPtr->bgStipple;
838	    bgStipplePrio = tagPtr->priority;
839	}
840	if ((tagPtr->fgColor != None) && (tagPtr->priority > fgPrio)) {
841	    styleValues.fgColor = tagPtr->fgColor;
842	    fgPrio = tagPtr->priority;
843	}
844	if ((tagPtr->tkfont != None) && (tagPtr->priority > fontPrio)) {
845	    styleValues.tkfont = tagPtr->tkfont;
846	    fontPrio = tagPtr->priority;
847	}
848	if ((tagPtr->fgStipple != None)
849		&& (tagPtr->priority > fgStipplePrio)) {
850	    styleValues.fgStipple = tagPtr->fgStipple;
851	    fgStipplePrio = tagPtr->priority;
852	}
853	if ((tagPtr->justifyString != NULL)
854		&& (tagPtr->priority > justifyPrio)) {
855	    styleValues.justify = tagPtr->justify;
856	    justifyPrio = tagPtr->priority;
857	}
858	if ((tagPtr->lMargin1String != NULL)
859		&& (tagPtr->priority > lMargin1Prio)) {
860	    styleValues.lMargin1 = tagPtr->lMargin1;
861	    lMargin1Prio = tagPtr->priority;
862	}
863	if ((tagPtr->lMargin2String != NULL)
864		&& (tagPtr->priority > lMargin2Prio)) {
865	    styleValues.lMargin2 = tagPtr->lMargin2;
866	    lMargin2Prio = tagPtr->priority;
867	}
868	if ((tagPtr->offsetString != NULL)
869		&& (tagPtr->priority > offsetPrio)) {
870	    styleValues.offset = tagPtr->offset;
871	    offsetPrio = tagPtr->priority;
872	}
873	if ((tagPtr->overstrikeString != NULL)
874		&& (tagPtr->priority > overstrikePrio)) {
875	    styleValues.overstrike = tagPtr->overstrike;
876	    overstrikePrio = tagPtr->priority;
877	}
878	if ((tagPtr->rMarginString != NULL)
879		&& (tagPtr->priority > rMarginPrio)) {
880	    styleValues.rMargin = tagPtr->rMargin;
881	    rMarginPrio = tagPtr->priority;
882	}
883	if ((tagPtr->spacing1String != NULL)
884		&& (tagPtr->priority > spacing1Prio)) {
885	    styleValues.spacing1 = tagPtr->spacing1;
886	    spacing1Prio = tagPtr->priority;
887	}
888	if ((tagPtr->spacing2String != NULL)
889		&& (tagPtr->priority > spacing2Prio)) {
890	    styleValues.spacing2 = tagPtr->spacing2;
891	    spacing2Prio = tagPtr->priority;
892	}
893	if ((tagPtr->spacing3String != NULL)
894		&& (tagPtr->priority > spacing3Prio)) {
895	    styleValues.spacing3 = tagPtr->spacing3;
896	    spacing3Prio = tagPtr->priority;
897	}
898	if ((tagPtr->tabStringPtr != NULL)
899		&& (tagPtr->priority > tabPrio)) {
900	    styleValues.tabArrayPtr = tagPtr->tabArrayPtr;
901	    tabPrio = tagPtr->priority;
902	}
903	if ((tagPtr->tabStyle != TK_TEXT_TABSTYLE_NONE)
904		&& (tagPtr->priority > tabStylePrio)) {
905	    styleValues.tabStyle = tagPtr->tabStyle;
906	    tabStylePrio = tagPtr->priority;
907	}
908	if ((tagPtr->underlineString != NULL)
909		&& (tagPtr->priority > underlinePrio)) {
910	    styleValues.underline = tagPtr->underline;
911	    underlinePrio = tagPtr->priority;
912	}
913	if ((tagPtr->elideString != NULL)
914		&& (tagPtr->priority > elidePrio)) {
915	    styleValues.elide = tagPtr->elide;
916	    elidePrio = tagPtr->priority;
917	}
918	if ((tagPtr->wrapMode != TEXT_WRAPMODE_NULL)
919		&& (tagPtr->priority > wrapPrio)) {
920	    styleValues.wrapMode = tagPtr->wrapMode;
921	    wrapPrio = tagPtr->priority;
922	}
923    }
924    if (tagPtrs != NULL) {
925	ckfree((char *) tagPtrs);
926    }
927
928    /*
929     * Use an existing style if there's one around that matches.
930     */
931
932    hPtr = Tcl_CreateHashEntry(&textPtr->dInfoPtr->styleTable,
933	    (char *) &styleValues, &isNew);
934    if (!isNew) {
935	stylePtr = (TextStyle *) Tcl_GetHashValue(hPtr);
936	stylePtr->refCount++;
937	return stylePtr;
938    }
939
940    /*
941     * No existing style matched. Make a new one.
942     */
943
944    stylePtr = (TextStyle *) ckalloc(sizeof(TextStyle));
945    stylePtr->refCount = 1;
946    if (styleValues.border != NULL) {
947	gcValues.foreground = Tk_3DBorderColor(styleValues.border)->pixel;
948	mask = GCForeground;
949	if (styleValues.bgStipple != None) {
950	    gcValues.stipple = styleValues.bgStipple;
951	    gcValues.fill_style = FillStippled;
952	    mask |= GCStipple|GCFillStyle;
953	}
954	stylePtr->bgGC = Tk_GetGC(textPtr->tkwin, mask, &gcValues);
955    } else {
956	stylePtr->bgGC = None;
957    }
958    mask = GCFont;
959    gcValues.font = Tk_FontId(styleValues.tkfont);
960    mask |= GCForeground;
961    gcValues.foreground = styleValues.fgColor->pixel;
962    if (styleValues.fgStipple != None) {
963	gcValues.stipple = styleValues.fgStipple;
964	gcValues.fill_style = FillStippled;
965	mask |= GCStipple|GCFillStyle;
966    }
967    stylePtr->fgGC = Tk_GetGC(textPtr->tkwin, mask, &gcValues);
968    stylePtr->sValuePtr = (StyleValues *)
969	    Tcl_GetHashKey(&textPtr->dInfoPtr->styleTable, hPtr);
970    stylePtr->hPtr = hPtr;
971    Tcl_SetHashValue(hPtr, stylePtr);
972    return stylePtr;
973}
974
975/*
976 *----------------------------------------------------------------------
977 *
978 * FreeStyle --
979 *
980 *	This function is called when a TextStyle structure is no longer
981 *	needed. It decrements the reference count and frees up the space for
982 *	the style structure if the reference count is 0.
983 *
984 * Results:
985 *	None.
986 *
987 * Side effects:
988 *	The storage and other resources associated with the style are freed up
989 *	if no-one's still using it.
990 *
991 *----------------------------------------------------------------------
992 */
993
994static void
995FreeStyle(
996    TkText *textPtr,		/* Information about overall widget. */
997    register TextStyle *stylePtr)
998				/* Information about style to free. */
999{
1000    stylePtr->refCount--;
1001    if (stylePtr->refCount == 0) {
1002	if (stylePtr->bgGC != None) {
1003	    Tk_FreeGC(textPtr->display, stylePtr->bgGC);
1004	}
1005	if (stylePtr->fgGC != None) {
1006	    Tk_FreeGC(textPtr->display, stylePtr->fgGC);
1007	}
1008	Tcl_DeleteHashEntry(stylePtr->hPtr);
1009	ckfree((char *) stylePtr);
1010    }
1011}
1012
1013/*
1014 *----------------------------------------------------------------------
1015 *
1016 * LayoutDLine --
1017 *
1018 *	This function generates a single DLine structure for a display line
1019 *	whose leftmost character is given by indexPtr.
1020 *
1021 * Results:
1022 *	The return value is a pointer to a DLine structure desribing the
1023 *	display line. All fields are filled in and correct except for y and
1024 *	nextPtr.
1025 *
1026 * Side effects:
1027 *	Storage is allocated for the new DLine.
1028 *
1029 *	See the comments in 'GetYView' for some thoughts on what the side-
1030 *	effects of this call (or its callers) should be; the synchronisation
1031 *	of TkTextLine->pixelHeight with the sum of the results of this
1032 *	function operating on all display lines within each logical line.
1033 *	Ideally the code should be refactored to ensure the cached pixel
1034 *	height is never behind what is known when this function is called
1035 *	elsewhere.
1036 *
1037 *	Unfortunately, this function is currently called from many different
1038 *	places, not just to layout a display line for actual display, but also
1039 *	simply to calculate some metric or other of one or more display lines
1040 *	(typically the height). It would be a good idea to do some profiling
1041 *	of typical text widget usage and the way in which this is called and
1042 *	see if some optimization could or should be done.
1043 *
1044 *----------------------------------------------------------------------
1045 */
1046
1047static DLine *
1048LayoutDLine(
1049    TkText *textPtr,		/* Overall information about text widget. */
1050    CONST TkTextIndex *indexPtr)/* Beginning of display line. May not
1051				 * necessarily point to a character
1052				 * segment. */
1053{
1054    register DLine *dlPtr;	/* New display line. */
1055    TkTextSegment *segPtr;	/* Current segment in text. */
1056    TkTextDispChunk *lastChunkPtr;
1057				/* Last chunk allocated so far for line. */
1058    TkTextDispChunk *chunkPtr;	/* Current chunk. */
1059    TkTextIndex curIndex;
1060    TkTextDispChunk *breakChunkPtr;
1061				/* Chunk containing best word break point, if
1062				 * any. */
1063    TkTextIndex breakIndex;	/* Index of first character in
1064				 * breakChunkPtr. */
1065    int breakByteOffset;	/* Byte offset of character within
1066				 * breakChunkPtr just to right of best break
1067				 * point. */
1068    int noCharsYet;		/* Non-zero means that no characters have been
1069				 * placed on the line yet. */
1070    int paragraphStart;		/* Non-zero means that we are on the first
1071				 * line of a paragraph (used to choose between
1072				 * lmargin1, lmargin2). */
1073    int justify;		/* How to justify line: taken from style for
1074				 * the first character in line. */
1075    int jIndent;		/* Additional indentation (beyond margins) due
1076				 * to justification. */
1077    int rMargin;		/* Right margin width for line. */
1078    TkWrapMode wrapMode;	/* Wrap mode to use for this line. */
1079    int x = 0, maxX = 0;	/* Initializations needed only to stop
1080				 * compiler warnings. */
1081    int wholeLine;		/* Non-zero means this display line runs to
1082				 * the end of the text line. */
1083    int tabIndex;		/* Index of the current tab stop. */
1084    int gotTab;			/* Non-zero means the current chunk contains a
1085				 * tab. */
1086    TkTextDispChunk *tabChunkPtr;
1087				/* Pointer to the chunk containing the
1088				 * previous tab stop. */
1089    int maxBytes;		/* Maximum number of bytes to include in this
1090				 * chunk. */
1091    TkTextTabArray *tabArrayPtr;/* Tab stops for line; taken from style for
1092				 * the first character on line. */
1093    int tabStyle;		/* One of TABULAR or WORDPROCESSOR. */
1094    int tabSize;		/* Number of pixels consumed by current tab
1095				 * stop. */
1096    TkTextDispChunk *lastCharChunkPtr;
1097				/* Pointer to last chunk in display lines with
1098				 * numBytes > 0. Used to drop 0-sized chunks
1099				 * from the end of the line. */
1100    int byteOffset, ascent, descent, code, elide, elidesize;
1101    StyleValues *sValuePtr;
1102    TkTextElideInfo info;	/* Keep track of elide state. */
1103
1104    /*
1105     * Create and initialize a new DLine structure.
1106     */
1107
1108    dlPtr = (DLine *) ckalloc(sizeof(DLine));
1109    dlPtr->index = *indexPtr;
1110    dlPtr->byteCount = 0;
1111    dlPtr->y = 0;
1112    dlPtr->oldY = 0;		/* Only set to avoid compiler warnings. */
1113    dlPtr->height = 0;
1114    dlPtr->baseline = 0;
1115    dlPtr->chunkPtr = NULL;
1116    dlPtr->nextPtr = NULL;
1117    dlPtr->flags = NEW_LAYOUT | OLD_Y_INVALID;
1118    dlPtr->logicalLinesMerged = 0;
1119
1120    /*
1121     * This is not necessarily totally correct, where we have merged logical
1122     * lines. Fixing this would require a quite significant overhaul, though,
1123     * so currently we make do with this.
1124     */
1125
1126    paragraphStart = (indexPtr->byteIndex == 0);
1127
1128    /*
1129     * Special case entirely elide line as there may be 1000s or more.
1130     */
1131
1132    elide = TkTextIsElided(textPtr, indexPtr, &info);
1133    if (elide && indexPtr->byteIndex == 0) {
1134	maxBytes = 0;
1135	for (segPtr = info.segPtr; segPtr != NULL; segPtr = segPtr->nextPtr) {
1136	    if (segPtr->size > 0) {
1137		if (elide == 0) {
1138		    /*
1139		     * We toggled a tag and the elide state changed to
1140		     * visible, and we have something of non-zero size.
1141		     * Therefore we must bail out.
1142		     */
1143
1144		    break;
1145		}
1146		maxBytes += segPtr->size;
1147
1148		/*
1149		 * Reset tag elide priority, since we're on a new character.
1150		 */
1151
1152	    } else if ((segPtr->typePtr == &tkTextToggleOffType)
1153		    || (segPtr->typePtr == &tkTextToggleOnType)) {
1154		TkTextTag *tagPtr = segPtr->body.toggle.tagPtr;
1155
1156		/*
1157		 * The elide state only changes if this tag is either the
1158		 * current highest priority tag (and is therefore being
1159		 * toggled off), or it's a new tag with higher priority.
1160		 */
1161
1162		if (tagPtr->elideString != NULL) {
1163		    info.tagCnts[tagPtr->priority]++;
1164		    if (info.tagCnts[tagPtr->priority] & 1) {
1165			info.tagPtrs[tagPtr->priority] = tagPtr;
1166		    }
1167		    if (tagPtr->priority >= info.elidePriority) {
1168			if (segPtr->typePtr == &tkTextToggleOffType) {
1169			    /*
1170			     * If it is being toggled off, and it has an elide
1171			     * string, it must actually be the current highest
1172			     * priority tag, so this check is redundant:
1173			     */
1174
1175			    if (tagPtr->priority != info.elidePriority) {
1176				Tcl_Panic("Bad tag priority being toggled off");
1177			    }
1178
1179			    /*
1180			     * Find previous elide tag, if any (if not then
1181			     * elide will be zero, of course).
1182			     */
1183
1184			    elide = 0;
1185			    while (--info.elidePriority > 0) {
1186				if (info.tagCnts[info.elidePriority] & 1) {
1187				    elide = info.tagPtrs[info.elidePriority]
1188					    ->elide;
1189				    break;
1190				}
1191			    }
1192			} else {
1193			    elide = tagPtr->elide;
1194			    info.elidePriority = tagPtr->priority;
1195			}
1196		    }
1197		}
1198	    }
1199	}
1200
1201	if (elide) {
1202	    dlPtr->byteCount = maxBytes;
1203	    dlPtr->spaceAbove = dlPtr->spaceBelow = dlPtr->length = 0;
1204	    if (dlPtr->index.byteIndex == 0) {
1205		/*
1206		 * Elided state goes from beginning to end of an entire
1207		 * logical line. This means we can update the line's pixel
1208		 * height, and bring its pixel calculation up to date.
1209		 */
1210
1211		TkBTreeLinePixelEpoch(textPtr, dlPtr->index.linePtr)
1212			= textPtr->dInfoPtr->lineMetricUpdateEpoch;
1213
1214		if (TkBTreeLinePixelCount(textPtr,dlPtr->index.linePtr) != 0) {
1215		    TkBTreeAdjustPixelHeight(textPtr,
1216			    dlPtr->index.linePtr, 0, 0);
1217		}
1218	    }
1219	    TkTextFreeElideInfo(&info);
1220	    return dlPtr;
1221	}
1222    }
1223    TkTextFreeElideInfo(&info);
1224
1225    /*
1226     * Each iteration of the loop below creates one TkTextDispChunk for the
1227     * new display line. The line will always have at least one chunk (for the
1228     * newline character at the end, if there's nothing else available).
1229     */
1230
1231    curIndex = *indexPtr;
1232    lastChunkPtr = NULL;
1233    chunkPtr = NULL;
1234    noCharsYet = 1;
1235    elide = 0;
1236    breakChunkPtr = NULL;
1237    breakByteOffset = 0;
1238    justify = TK_JUSTIFY_LEFT;
1239    tabIndex = -1;
1240    tabChunkPtr = NULL;
1241    tabArrayPtr = NULL;
1242    tabStyle = TK_TEXT_TABSTYLE_TABULAR;
1243    rMargin = 0;
1244    wrapMode = TEXT_WRAPMODE_CHAR;
1245    tabSize = 0;
1246    lastCharChunkPtr = NULL;
1247
1248    /*
1249     * Find the first segment to consider for the line. Can't call
1250     * TkTextIndexToSeg for this because it won't return a segment with zero
1251     * size (such as the insertion cursor's mark).
1252     */
1253
1254  connectNextLogicalLine:
1255    byteOffset = curIndex.byteIndex;
1256    segPtr = curIndex.linePtr->segPtr;
1257    while ((byteOffset > 0) && (byteOffset >= segPtr->size)) {
1258	byteOffset -= segPtr->size;
1259	segPtr = segPtr->nextPtr;
1260
1261	if (segPtr == NULL) {
1262	    /*
1263	     * Two logical lines merged into one display line through eliding
1264	     * of a newline.
1265	     */
1266
1267	    TkTextLine *linePtr = TkBTreeNextLine(NULL, curIndex.linePtr);
1268	    if (linePtr != NULL) {
1269		dlPtr->logicalLinesMerged++;
1270		curIndex.byteIndex = 0;
1271		curIndex.linePtr = linePtr;
1272		segPtr = curIndex.linePtr->segPtr;
1273	    } else {
1274		break;
1275	    }
1276	}
1277    }
1278
1279    while (segPtr != NULL) {
1280	/*
1281	 * Every logical line still gets at least one chunk due to
1282	 * expectations in the rest of the code, but we are able to skip
1283	 * elided portions of the line quickly.
1284	 *
1285	 * If current chunk is elided and last chunk was too, coalese.
1286	 *
1287	 * This also means that each logical line which is entirely elided
1288	 * still gets laid out into a DLine, but with zero height. This isn't
1289	 * particularly a problem, but it does seem somewhat unnecessary. We
1290	 * may wish to redesign the code to remove these zero height DLines in
1291	 * the future.
1292	 */
1293
1294	if (elide && (lastChunkPtr != NULL)
1295		&& (lastChunkPtr->displayProc == NULL /*ElideDisplayProc*/)) {
1296	    elidesize = segPtr->size - byteOffset;
1297	    if (elidesize > 0) {
1298		curIndex.byteIndex += elidesize;
1299		lastChunkPtr->numBytes += elidesize;
1300		breakByteOffset = lastChunkPtr->breakIndex
1301			= lastChunkPtr->numBytes;
1302
1303		/*
1304		 * If have we have a tag toggle, there is a chance that
1305		 * invisibility state changed, so bail out.
1306		 */
1307	    } else if ((segPtr->typePtr == &tkTextToggleOffType)
1308		    || (segPtr->typePtr == &tkTextToggleOnType)) {
1309		if (segPtr->body.toggle.tagPtr->elideString != NULL) {
1310		    elide = (segPtr->typePtr == &tkTextToggleOffType)
1311			    ^ segPtr->body.toggle.tagPtr->elide;
1312		}
1313	    }
1314
1315	    byteOffset = 0;
1316	    segPtr = segPtr->nextPtr;
1317
1318	    if (segPtr == NULL) {
1319		/*
1320		 * Two logical lines merged into one display line through
1321		 * eliding of a newline.
1322		 */
1323
1324		TkTextLine *linePtr = TkBTreeNextLine(NULL, curIndex.linePtr);
1325
1326		if (linePtr != NULL) {
1327		    dlPtr->logicalLinesMerged++;
1328		    curIndex.byteIndex = 0;
1329		    curIndex.linePtr = linePtr;
1330		    goto connectNextLogicalLine;
1331		}
1332	    }
1333
1334	    /*
1335	     * Code no longer needed, now that we allow logical lines to merge
1336	     * into a single display line.
1337	     *
1338	    if (segPtr == NULL && chunkPtr != NULL) {
1339		ckfree((char *) chunkPtr);
1340		chunkPtr = NULL;
1341	    }
1342	     */
1343
1344	    continue;
1345	}
1346
1347	if (segPtr->typePtr->layoutProc == NULL) {
1348	    segPtr = segPtr->nextPtr;
1349	    byteOffset = 0;
1350	    continue;
1351	}
1352	if (chunkPtr == NULL) {
1353	    chunkPtr = (TkTextDispChunk *) ckalloc(sizeof(TkTextDispChunk));
1354	    chunkPtr->nextPtr = NULL;
1355	    chunkPtr->clientData = NULL;
1356	}
1357	chunkPtr->stylePtr = GetStyle(textPtr, &curIndex);
1358	elide = chunkPtr->stylePtr->sValuePtr->elide;
1359
1360	/*
1361	 * Save style information such as justification and indentation, up
1362	 * until the first character is encountered, then retain that
1363	 * information for the rest of the line.
1364	 */
1365
1366	if (!elide && noCharsYet) {
1367	    tabArrayPtr = chunkPtr->stylePtr->sValuePtr->tabArrayPtr;
1368	    tabStyle = chunkPtr->stylePtr->sValuePtr->tabStyle;
1369	    justify = chunkPtr->stylePtr->sValuePtr->justify;
1370	    rMargin = chunkPtr->stylePtr->sValuePtr->rMargin;
1371	    wrapMode = chunkPtr->stylePtr->sValuePtr->wrapMode;
1372
1373	    /*
1374	     * See above - this test may not be entirely correct where we have
1375	     * partially elided lines (and therefore merged logical lines).
1376	     * In such a case a byteIndex of zero doesn't necessarily mean the
1377	     * beginning of a logical line.
1378	     */
1379
1380	    if (paragraphStart) {
1381		/*
1382		 * Beginning of logical line.
1383		 */
1384
1385		x = chunkPtr->stylePtr->sValuePtr->lMargin1;
1386	    } else {
1387		/*
1388		 * Beginning of display line.
1389		 */
1390
1391		x = chunkPtr->stylePtr->sValuePtr->lMargin2;
1392	    }
1393	    if (wrapMode == TEXT_WRAPMODE_NONE) {
1394		maxX = -1;
1395	    } else {
1396		maxX = textPtr->dInfoPtr->maxX - textPtr->dInfoPtr->x
1397			- rMargin;
1398		if (maxX < x) {
1399		    maxX = x;
1400		}
1401	    }
1402	}
1403
1404	gotTab = 0;
1405	maxBytes = segPtr->size - byteOffset;
1406	if (segPtr->typePtr == &tkTextCharType) {
1407
1408	    /*
1409	     * See if there is a tab in the current chunk; if so, only layout
1410	     * characters up to (and including) the tab.
1411	     */
1412
1413	    if (!elide && justify == TK_JUSTIFY_LEFT) {
1414		char *p;
1415
1416		for (p = segPtr->body.chars + byteOffset; *p != 0; p++) {
1417		    if (*p == '\t') {
1418			maxBytes = (p + 1 - segPtr->body.chars) - byteOffset;
1419			gotTab = 1;
1420			break;
1421		    }
1422		}
1423	    }
1424
1425#if TK_LAYOUT_WITH_BASE_CHUNKS
1426	    if (baseCharChunkPtr != NULL) {
1427		int expectedX =
1428			((BaseCharInfo *) baseCharChunkPtr->clientData)->width
1429			+ baseCharChunkPtr->x;
1430
1431		if ((expectedX != x) || !IsSameFGStyle(
1432			baseCharChunkPtr->stylePtr, chunkPtr->stylePtr)) {
1433		    FinalizeBaseChunk(NULL);
1434		}
1435	    }
1436#endif /* TK_LAYOUT_WITH_BASE_CHUNKS */
1437	}
1438	chunkPtr->x = x;
1439	if (elide /*&& maxBytes*/) {
1440	    /*
1441	     * Don't free style here, as other code expects to be able to do
1442	     * that.
1443	     */
1444
1445	    /* breakByteOffset =*/
1446	    chunkPtr->breakIndex = chunkPtr->numBytes = maxBytes;
1447	    chunkPtr->width = 0;
1448	    chunkPtr->minAscent = chunkPtr->minDescent
1449		    = chunkPtr->minHeight = 0;
1450
1451	    /*
1452	     * Would just like to point to canonical empty chunk.
1453	     */
1454
1455	    chunkPtr->displayProc = NULL;
1456	    chunkPtr->undisplayProc = NULL;
1457	    chunkPtr->measureProc = ElideMeasureProc;
1458	    chunkPtr->bboxProc = ElideBboxProc;
1459
1460	    code = 1;
1461	} else {
1462	    code = (*segPtr->typePtr->layoutProc)(textPtr, &curIndex, segPtr,
1463		    byteOffset, maxX-tabSize, maxBytes, noCharsYet, wrapMode,
1464		    chunkPtr);
1465	}
1466	if (code <= 0) {
1467	    FreeStyle(textPtr, chunkPtr->stylePtr);
1468	    if (code < 0) {
1469		/*
1470		 * This segment doesn't wish to display itself (e.g. most
1471		 * marks).
1472		 */
1473
1474		segPtr = segPtr->nextPtr;
1475		byteOffset = 0;
1476		continue;
1477	    }
1478
1479	    /*
1480	     * No characters from this segment fit in the window: this means
1481	     * we're at the end of the display line.
1482	     */
1483
1484	    if (chunkPtr != NULL) {
1485		ckfree((char *) chunkPtr);
1486	    }
1487	    break;
1488	}
1489
1490	/*
1491	 * We currently say we have some characters (and therefore something
1492	 * from which to examine tag values for the first character of the
1493	 * line) even if those characters are actually elided. This behaviour
1494	 * is not well documented, and it might be more consistent to
1495	 * completely ignore such elided characters and their tags. To do so
1496	 * change this to:
1497	 *
1498	 * if (!elide && chunkPtr->numBytes > 0).
1499	 */
1500
1501	if (!elide && chunkPtr->numBytes > 0) {
1502	    noCharsYet = 0;
1503	    lastCharChunkPtr = chunkPtr;
1504	}
1505	if (lastChunkPtr == NULL) {
1506	    dlPtr->chunkPtr = chunkPtr;
1507	} else {
1508	    lastChunkPtr->nextPtr = chunkPtr;
1509	}
1510	lastChunkPtr = chunkPtr;
1511	x += chunkPtr->width;
1512	if (chunkPtr->breakIndex > 0) {
1513	    breakByteOffset = chunkPtr->breakIndex;
1514	    breakIndex = curIndex;
1515	    breakChunkPtr = chunkPtr;
1516	}
1517	if (chunkPtr->numBytes != maxBytes) {
1518	    break;
1519	}
1520
1521	/*
1522	 * If we're at a new tab, adjust the layout for all the chunks
1523	 * pertaining to the previous tab. Also adjust the amount of space
1524	 * left in the line to account for space that will be eaten up by the
1525	 * tab.
1526	 */
1527
1528	if (gotTab) {
1529	    if (tabIndex >= 0) {
1530		AdjustForTab(textPtr, tabArrayPtr, tabIndex, tabChunkPtr);
1531		x = chunkPtr->x + chunkPtr->width;
1532	    }
1533	    tabChunkPtr = chunkPtr;
1534	    tabSize = SizeOfTab(textPtr, tabStyle, tabArrayPtr, &tabIndex, x,
1535		    maxX);
1536	    if ((maxX >= 0) && (tabSize >= maxX - x)) {
1537		break;
1538	    }
1539	}
1540	curIndex.byteIndex += chunkPtr->numBytes;
1541	byteOffset += chunkPtr->numBytes;
1542	if (byteOffset >= segPtr->size) {
1543	    byteOffset = 0;
1544	    segPtr = segPtr->nextPtr;
1545	    if (elide && segPtr == NULL) {
1546		/*
1547		 * An elided section started on this line, and carries on
1548		 * until the newline. Hence the newline is actually elided,
1549		 * and we want to merge the display of the next logical line
1550		 * with this one.
1551		 */
1552
1553		TkTextLine *linePtr = TkBTreeNextLine(NULL, curIndex.linePtr);
1554
1555		if (linePtr != NULL) {
1556		    dlPtr->logicalLinesMerged++;
1557		    curIndex.byteIndex = 0;
1558		    curIndex.linePtr = linePtr;
1559		    chunkPtr = NULL;
1560		    goto connectNextLogicalLine;
1561		}
1562	    }
1563	}
1564
1565	chunkPtr = NULL;
1566    }
1567#if TK_LAYOUT_WITH_BASE_CHUNKS
1568    FinalizeBaseChunk(NULL);
1569#endif /* TK_LAYOUT_WITH_BASE_CHUNKS */
1570    if (noCharsYet) {
1571	dlPtr->spaceAbove = 0;
1572	dlPtr->spaceBelow = 0;
1573	dlPtr->length = 0;
1574
1575	/*
1576	 * We used to Tcl_Panic here, saying that LayoutDLine couldn't place
1577	 * any characters on a line, but I believe a more appropriate response
1578	 * is to return a DLine with zero height. With elided lines, tag
1579	 * transitions and asynchronous line height calculations, it is hard
1580	 * to avoid this situation ever arising with the current code design.
1581	 */
1582
1583	return dlPtr;
1584    }
1585    wholeLine = (segPtr == NULL);
1586
1587    /*
1588     * We're at the end of the display line. Throw away everything after the
1589     * most recent word break, if there is one; this may potentially require
1590     * the last chunk to be layed out again.
1591     */
1592
1593    if (breakChunkPtr == NULL) {
1594	/*
1595	 * This code makes sure that we don't accidentally display chunks with
1596	 * no characters at the end of the line (such as the insertion
1597	 * cursor). These chunks belong on the next line. So, throw away
1598	 * everything after the last chunk that has characters in it.
1599	 */
1600
1601	breakChunkPtr = lastCharChunkPtr;
1602	breakByteOffset = breakChunkPtr->numBytes;
1603    }
1604    if ((breakChunkPtr != NULL) && ((lastChunkPtr != breakChunkPtr)
1605	    || (breakByteOffset != lastChunkPtr->numBytes))) {
1606	while (1) {
1607	    chunkPtr = breakChunkPtr->nextPtr;
1608	    if (chunkPtr == NULL) {
1609		break;
1610	    }
1611	    FreeStyle(textPtr, chunkPtr->stylePtr);
1612	    breakChunkPtr->nextPtr = chunkPtr->nextPtr;
1613	    if (chunkPtr->undisplayProc != NULL) {
1614		(*chunkPtr->undisplayProc)(textPtr, chunkPtr);
1615	    }
1616	    ckfree((char *) chunkPtr);
1617	}
1618	if (breakByteOffset != breakChunkPtr->numBytes) {
1619	    if (breakChunkPtr->undisplayProc != NULL) {
1620		(*breakChunkPtr->undisplayProc)(textPtr, breakChunkPtr);
1621	    }
1622	    segPtr = TkTextIndexToSeg(&breakIndex, &byteOffset);
1623	    (*segPtr->typePtr->layoutProc)(textPtr, &breakIndex,
1624		    segPtr, byteOffset, maxX, breakByteOffset, 0,
1625		    wrapMode, breakChunkPtr);
1626#if TK_LAYOUT_WITH_BASE_CHUNKS
1627	    FinalizeBaseChunk(NULL);
1628#endif /* TK_LAYOUT_WITH_BASE_CHUNKS */
1629	}
1630	lastChunkPtr = breakChunkPtr;
1631	wholeLine = 0;
1632    }
1633
1634    /*
1635     * Make tab adjustments for the last tab stop, if there is one.
1636     */
1637
1638    if ((tabIndex >= 0) && (tabChunkPtr != NULL)) {
1639	AdjustForTab(textPtr, tabArrayPtr, tabIndex, tabChunkPtr);
1640    }
1641
1642    /*
1643     * Make one more pass over the line to recompute various things like its
1644     * height, length, and total number of bytes. Also modify the x-locations
1645     * of chunks to reflect justification. If we're not wrapping, I'm not sure
1646     * what is the best way to handle left and center justification: should
1647     * the total length, for purposes of justification, be (a) the window
1648     * width, (b) the length of the longest line in the window, or (c) the
1649     * length of the longest line in the text? (c) isn't available, (b) seems
1650     * weird, since it can change with vertical scrolling, so (a) is what is
1651     * implemented below.
1652     */
1653
1654    if (wrapMode == TEXT_WRAPMODE_NONE) {
1655	maxX = textPtr->dInfoPtr->maxX - textPtr->dInfoPtr->x - rMargin;
1656    }
1657    dlPtr->length = lastChunkPtr->x + lastChunkPtr->width;
1658    if (justify == TK_JUSTIFY_LEFT) {
1659	jIndent = 0;
1660    } else if (justify == TK_JUSTIFY_RIGHT) {
1661	jIndent = maxX - dlPtr->length;
1662    } else {
1663	jIndent = (maxX - dlPtr->length)/2;
1664    }
1665    ascent = descent = 0;
1666    for (chunkPtr = dlPtr->chunkPtr; chunkPtr != NULL;
1667	    chunkPtr = chunkPtr->nextPtr) {
1668	chunkPtr->x += jIndent;
1669	dlPtr->byteCount += chunkPtr->numBytes;
1670	if (chunkPtr->minAscent > ascent) {
1671	    ascent = chunkPtr->minAscent;
1672	}
1673	if (chunkPtr->minDescent > descent) {
1674	    descent = chunkPtr->minDescent;
1675	}
1676	if (chunkPtr->minHeight > dlPtr->height) {
1677	    dlPtr->height = chunkPtr->minHeight;
1678	}
1679	sValuePtr = chunkPtr->stylePtr->sValuePtr;
1680	if ((sValuePtr->borderWidth > 0)
1681		&& (sValuePtr->relief != TK_RELIEF_FLAT)) {
1682	    dlPtr->flags |= HAS_3D_BORDER;
1683	}
1684    }
1685    if (dlPtr->height < (ascent + descent)) {
1686	dlPtr->height = ascent + descent;
1687	dlPtr->baseline = ascent;
1688    } else {
1689	dlPtr->baseline = ascent + (dlPtr->height - ascent - descent)/2;
1690    }
1691    sValuePtr = dlPtr->chunkPtr->stylePtr->sValuePtr;
1692    if (dlPtr->index.byteIndex == 0) {
1693	dlPtr->spaceAbove = sValuePtr->spacing1;
1694    } else {
1695	dlPtr->spaceAbove = sValuePtr->spacing2 - sValuePtr->spacing2/2;
1696    }
1697    if (wholeLine) {
1698	dlPtr->spaceBelow = sValuePtr->spacing3;
1699    } else {
1700	dlPtr->spaceBelow = sValuePtr->spacing2/2;
1701    }
1702    dlPtr->height += dlPtr->spaceAbove + dlPtr->spaceBelow;
1703    dlPtr->baseline += dlPtr->spaceAbove;
1704
1705    /*
1706     * Recompute line length: may have changed because of justification.
1707     */
1708
1709    dlPtr->length = lastChunkPtr->x + lastChunkPtr->width;
1710
1711    return dlPtr;
1712}
1713
1714/*
1715 *----------------------------------------------------------------------
1716 *
1717 * UpdateDisplayInfo --
1718 *
1719 *	This function is invoked to recompute some or all of the DLine
1720 *	structures for a text widget. At the time it is called the DLine
1721 *	structures still left in the widget are guaranteed to be correct
1722 *	except that (a) the y-coordinates aren't necessarily correct, (b)
1723 *	there may be missing structures (the DLine structures get removed as
1724 *	soon as they are potentially out-of-date), and (c) DLine structures
1725 *	that don't start at the beginning of a line may be incorrect if
1726 *	previous information in the same line changed size in a way that moved
1727 *	a line boundary (DLines for any info that changed will have been
1728 *	deleted, but not DLines for unchanged info in the same text line).
1729 *
1730 * Results:
1731 *	None.
1732 *
1733 * Side effects:
1734 *	Upon return, the DLine information for textPtr correctly reflects the
1735 *	positions where characters will be displayed. However, this function
1736 *	doesn't actually bring the display up-to-date.
1737 *
1738 *----------------------------------------------------------------------
1739 */
1740
1741static void
1742UpdateDisplayInfo(
1743    TkText *textPtr)		/* Text widget to update. */
1744{
1745    register TextDInfo *dInfoPtr = textPtr->dInfoPtr;
1746    register DLine *dlPtr, *prevPtr;
1747    TkTextIndex index;
1748    TkTextLine *lastLinePtr;
1749    int y, maxY, xPixelOffset, maxOffset, lineHeight;
1750
1751    if (!(dInfoPtr->flags & DINFO_OUT_OF_DATE)) {
1752	return;
1753    }
1754    dInfoPtr->flags &= ~DINFO_OUT_OF_DATE;
1755
1756    /*
1757     * Delete any DLines that are now above the top of the window.
1758     */
1759
1760    index = textPtr->topIndex;
1761    dlPtr = FindDLine(dInfoPtr->dLinePtr, &index);
1762    if ((dlPtr != NULL) && (dlPtr != dInfoPtr->dLinePtr)) {
1763	FreeDLines(textPtr, dInfoPtr->dLinePtr, dlPtr, DLINE_UNLINK);
1764    }
1765    if (index.byteIndex == 0) {
1766	lineHeight = 0;
1767    } else {
1768	lineHeight = -1;
1769    }
1770
1771    /*
1772     * Scan through the contents of the window from top to bottom, recomputing
1773     * information for lines that are missing.
1774     */
1775
1776    lastLinePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr,
1777	    TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr));
1778    dlPtr = dInfoPtr->dLinePtr;
1779    prevPtr = NULL;
1780    y = dInfoPtr->y - dInfoPtr->newTopPixelOffset;
1781    maxY = dInfoPtr->maxY;
1782    while (1) {
1783	register DLine *newPtr;
1784
1785	if (index.linePtr == lastLinePtr) {
1786	    break;
1787	}
1788
1789	/*
1790	 * There are three possibilities right now:
1791	 * (a) the next DLine (dlPtr) corresponds exactly to the next
1792	 *     information we want to display: just use it as-is.
1793	 * (b) the next DLine corresponds to a different line, or to a segment
1794	 *     that will be coming later in the same line: leave this DLine
1795	 *     alone in the hopes that we'll be able to use it later, then
1796	 *     create a new DLine in front of it.
1797	 * (c) the next DLine corresponds to a segment in the line we want,
1798	 *     but it's a segment that has already been processed or will
1799	 *     never be processed. Delete the DLine and try again.
1800	 *
1801	 * One other twist on all this. It's possible for 3D borders to
1802	 * interact between lines (see DisplayLineBackground) so if a line is
1803	 * relayed out and has styles with 3D borders, its neighbors have to
1804	 * be redrawn if they have 3D borders too, since the interactions
1805	 * could have changed (the neighbors don't have to be relayed out,
1806	 * just redrawn).
1807	 */
1808
1809	if ((dlPtr == NULL) || (dlPtr->index.linePtr != index.linePtr)) {
1810	    /*
1811	     * Case (b) -- must make new DLine.
1812	     */
1813
1814	makeNewDLine:
1815	    if (tkTextDebug) {
1816		char string[TK_POS_CHARS];
1817
1818		/*
1819		 * Debugging is enabled, so keep a log of all the lines that
1820		 * were re-layed out. The test suite uses this information.
1821		 */
1822
1823		TkTextPrintIndex(textPtr, &index, string);
1824		LOG("tk_textRelayout", string);
1825	    }
1826	    newPtr = LayoutDLine(textPtr, &index);
1827	    if (prevPtr == NULL) {
1828		dInfoPtr->dLinePtr = newPtr;
1829	    } else {
1830		prevPtr->nextPtr = newPtr;
1831		if (prevPtr->flags & HAS_3D_BORDER) {
1832		    prevPtr->flags |= OLD_Y_INVALID;
1833		}
1834	    }
1835	    newPtr->nextPtr = dlPtr;
1836	    dlPtr = newPtr;
1837	} else {
1838	    /*
1839	     * DlPtr refers to the line we want. Next check the index within
1840	     * the line.
1841	     */
1842
1843	    if (index.byteIndex == dlPtr->index.byteIndex) {
1844		/*
1845		 * Case (a) - can use existing display line as-is.
1846		 */
1847
1848		if ((dlPtr->flags & HAS_3D_BORDER) && (prevPtr != NULL)
1849			&& (prevPtr->flags & (NEW_LAYOUT))) {
1850		    dlPtr->flags |= OLD_Y_INVALID;
1851		}
1852		goto lineOK;
1853	    }
1854	    if (index.byteIndex < dlPtr->index.byteIndex) {
1855		goto makeNewDLine;
1856	    }
1857
1858	    /*
1859	     * Case (c) - dlPtr is useless. Discard it and start again with
1860	     * the next display line.
1861	     */
1862
1863	    newPtr = dlPtr->nextPtr;
1864	    FreeDLines(textPtr, dlPtr, newPtr, DLINE_FREE);
1865	    dlPtr = newPtr;
1866	    if (prevPtr != NULL) {
1867		prevPtr->nextPtr = newPtr;
1868	    } else {
1869		dInfoPtr->dLinePtr = newPtr;
1870	    }
1871	    continue;
1872	}
1873
1874	/*
1875	 * Advance to the start of the next line.
1876	 */
1877
1878    lineOK:
1879	dlPtr->y = y;
1880	y += dlPtr->height;
1881	if (lineHeight != -1) {
1882	    lineHeight += dlPtr->height;
1883	}
1884	TkTextIndexForwBytes(textPtr, &index, dlPtr->byteCount, &index);
1885	prevPtr = dlPtr;
1886	dlPtr = dlPtr->nextPtr;
1887
1888	/*
1889	 * If we switched text lines, delete any DLines left for the old text
1890	 * line.
1891	 */
1892
1893	if (index.linePtr != prevPtr->index.linePtr) {
1894	    register DLine *nextPtr;
1895
1896	    nextPtr = dlPtr;
1897	    while ((nextPtr != NULL)
1898		    && (nextPtr->index.linePtr == prevPtr->index.linePtr)) {
1899		nextPtr = nextPtr->nextPtr;
1900	    }
1901	    if (nextPtr != dlPtr) {
1902		FreeDLines(textPtr, dlPtr, nextPtr, DLINE_FREE);
1903		prevPtr->nextPtr = nextPtr;
1904		dlPtr = nextPtr;
1905	    }
1906
1907	    if ((lineHeight != -1) && (TkBTreeLinePixelCount(textPtr,
1908		    prevPtr->index.linePtr) != lineHeight)) {
1909		/*
1910		 * The logical line height we just calculated is actually
1911		 * differnt to the currently cached height of the text line.
1912		 * That is fine (the text line heights are only calculated
1913		 * asynchronously), but we must update the cached height so
1914		 * that any counts made with DLine pointers are the same as
1915		 * counts made through the BTree. This helps to ensure that
1916		 * the scrollbar size corresponds accurately to that displayed
1917		 * contents, even as the window is re-sized.
1918		 */
1919
1920		TkBTreeAdjustPixelHeight(textPtr, prevPtr->index.linePtr,
1921			lineHeight, 0);
1922
1923		/*
1924		 * I believe we can be 100% sure that we started at the
1925		 * beginning of the logical line, so we can also adjust the
1926		 * 'pixelCalculationEpoch' to mark it as being up to date.
1927		 * There is a slight concern that we might not have got this
1928		 * right for the first line in the re-display.
1929		 */
1930
1931		TkBTreeLinePixelEpoch(textPtr, prevPtr->index.linePtr) =
1932			dInfoPtr->lineMetricUpdateEpoch;
1933	    }
1934	    lineHeight = 0;
1935	}
1936
1937	/*
1938	 * It's important to have the following check here rather than in the
1939	 * while statement for the loop, so that there's always at least one
1940	 * DLine generated, regardless of how small the window is. This keeps
1941	 * a lot of other code from breaking.
1942	 */
1943
1944	if (y >= maxY) {
1945	    break;
1946	}
1947    }
1948
1949    /*
1950     * Delete any DLine structures that don't fit on the screen.
1951     */
1952
1953    FreeDLines(textPtr, dlPtr, NULL, DLINE_UNLINK);
1954
1955    /*
1956     * If there is extra space at the bottom of the window (because we've hit
1957     * the end of the text), then bring in more lines at the top of the
1958     * window, if there are any, to fill in the view.
1959     *
1960     * Since the top line may only be partially visible, we try first to
1961     * simply show more pixels from that line (newTopPixelOffset). If that
1962     * isn't enough, we have to layout more lines.
1963     */
1964
1965    if (y < maxY) {
1966	/*
1967	 * This counts how many vertical pixels we have left to fill by
1968	 * pulling in more display pixels either from the first currently
1969	 * displayed, or the lines above it.
1970	 */
1971
1972	int spaceLeft = maxY - y;
1973
1974	if (spaceLeft <= dInfoPtr->newTopPixelOffset) {
1975	    /*
1976	     * We can full up all the needed space just by showing more of the
1977	     * current top line.
1978	     */
1979
1980	    dInfoPtr->newTopPixelOffset -= spaceLeft;
1981	    y += spaceLeft;
1982	    spaceLeft = 0;
1983	} else {
1984	    int lineNum, bytesToCount;
1985	    DLine *lowestPtr;
1986
1987	    /*
1988	     * Add in all of the current top line, which won't be enough to
1989	     * bring y up to maxY (if it was we would be in the 'if' block
1990	     * above).
1991	     */
1992
1993	    y += dInfoPtr->newTopPixelOffset;
1994	    dInfoPtr->newTopPixelOffset = 0;
1995
1996	    /*
1997	     * Layout an entire text line (potentially > 1 display line), then
1998	     * link in as many display lines as fit without moving the bottom
1999	     * line out of the window. Repeat this until all the extra space
2000	     * has been used up or we've reached the beginning of the text.
2001	     */
2002
2003	    spaceLeft = maxY - y;
2004	    if (dInfoPtr->dLinePtr == NULL) {
2005		/*
2006		 * No lines have been laid out. This must be an empty peer
2007		 * widget.
2008		 */
2009
2010		lineNum = -1;
2011		bytesToCount = 0;	/* Stop compiler warning. */
2012	    } else {
2013		lineNum = TkBTreeLinesTo(textPtr,
2014			dInfoPtr->dLinePtr->index.linePtr);
2015		bytesToCount = dInfoPtr->dLinePtr->index.byteIndex;
2016		if (bytesToCount == 0) {
2017		    bytesToCount = INT_MAX;
2018		    lineNum--;
2019		}
2020	    }
2021	    for ( ; (lineNum >= 0) && (spaceLeft > 0); lineNum--) {
2022		int pixelHeight = 0;
2023
2024		index.linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree,
2025			textPtr, lineNum);
2026		index.byteIndex = 0;
2027		lowestPtr = NULL;
2028
2029		do {
2030		    dlPtr = LayoutDLine(textPtr, &index);
2031		    pixelHeight += dlPtr->height;
2032		    dlPtr->nextPtr = lowestPtr;
2033		    lowestPtr = dlPtr;
2034		    if (dlPtr->length == 0 && dlPtr->height == 0) {
2035			bytesToCount--;
2036			break;
2037		    }	/* elide */
2038		    TkTextIndexForwBytes(textPtr, &index, dlPtr->byteCount,
2039			    &index);
2040		    bytesToCount -= dlPtr->byteCount;
2041		} while ((bytesToCount > 0)
2042			&& (index.linePtr == lowestPtr->index.linePtr));
2043
2044		/*
2045		 * We may not have examined the entire line (depending on the
2046		 * value of 'bytesToCount', so we only want to set this if it
2047		 * is genuinely bigger).
2048		 */
2049
2050		if (pixelHeight > TkBTreeLinePixelCount(textPtr,
2051			lowestPtr->index.linePtr)) {
2052		    TkBTreeAdjustPixelHeight(textPtr,
2053			    lowestPtr->index.linePtr, pixelHeight, 0);
2054		    if (index.linePtr != lowestPtr->index.linePtr) {
2055			/*
2056			 * We examined the entire line, so can update the
2057			 * epoch.
2058			 */
2059
2060			TkBTreeLinePixelEpoch(textPtr,
2061				lowestPtr->index.linePtr) =
2062				dInfoPtr->lineMetricUpdateEpoch;
2063		    }
2064		}
2065
2066		/*
2067		 * Scan through the display lines from the bottom one up to
2068		 * the top one.
2069		 */
2070
2071		while (lowestPtr != NULL) {
2072		    dlPtr = lowestPtr;
2073		    spaceLeft -= dlPtr->height;
2074		    lowestPtr = dlPtr->nextPtr;
2075		    dlPtr->nextPtr = dInfoPtr->dLinePtr;
2076		    dInfoPtr->dLinePtr = dlPtr;
2077		    if (tkTextDebug) {
2078			char string[TK_POS_CHARS];
2079
2080			TkTextPrintIndex(textPtr, &dlPtr->index, string);
2081			LOG("tk_textRelayout", string);
2082		    }
2083		    if (spaceLeft <= 0) {
2084			break;
2085		    }
2086		}
2087		FreeDLines(textPtr, lowestPtr, NULL, DLINE_FREE);
2088		bytesToCount = INT_MAX;
2089	    }
2090
2091	    /*
2092	     * We've either filled in the space we wanted to or we've run out
2093	     * of display lines at the top of the text. Note that we already
2094	     * set dInfoPtr->newTopPixelOffset to zero above.
2095	     */
2096
2097	    if (spaceLeft < 0) {
2098		/*
2099		 * We've laid out a few too many vertical pixels at or above
2100		 * the first line. Therefore we only want to show part of the
2101		 * first displayed line, so that the last displayed line just
2102		 * fits in the window.
2103		 */
2104
2105		dInfoPtr->newTopPixelOffset = -spaceLeft;
2106		if (dInfoPtr->newTopPixelOffset>=dInfoPtr->dLinePtr->height) {
2107		    /*
2108		     * Somehow the entire first line we laid out is shorter
2109		     * than the new offset. This should not occur and would
2110		     * indicate a bad problem in the logic above.
2111		     */
2112
2113		    Tcl_Panic("Error in pixel height consistency while filling in spacesLeft");
2114		}
2115	    }
2116	}
2117
2118	/*
2119	 * Now we're all done except that the y-coordinates in all the DLines
2120	 * are wrong and the top index for the text is wrong. Update them.
2121	 */
2122
2123	if (dInfoPtr->dLinePtr != NULL) {
2124	    textPtr->topIndex = dInfoPtr->dLinePtr->index;
2125	    y = dInfoPtr->y - dInfoPtr->newTopPixelOffset;
2126	    for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
2127		    dlPtr = dlPtr->nextPtr) {
2128		if (y > dInfoPtr->maxY) {
2129		    Tcl_Panic("Added too many new lines in UpdateDisplayInfo");
2130		}
2131		dlPtr->y = y;
2132		y += dlPtr->height;
2133	    }
2134	}
2135    }
2136
2137    /*
2138     * If the old top or bottom line has scrolled elsewhere on the screen, we
2139     * may not be able to re-use its old contents by copying bits (e.g., a
2140     * beveled edge that was drawn when it was at the top or bottom won't be
2141     * drawn when the line is in the middle and its neighbor has a matching
2142     * background). Similarly, if the new top or bottom line came from
2143     * somewhere else on the screen, we may not be able to copy the old bits.
2144     */
2145
2146    dlPtr = dInfoPtr->dLinePtr;
2147    if (dlPtr != NULL) {
2148	if ((dlPtr->flags & HAS_3D_BORDER) && !(dlPtr->flags & TOP_LINE)) {
2149	    dlPtr->flags |= OLD_Y_INVALID;
2150	}
2151	while (1) {
2152	    if ((dlPtr->flags & TOP_LINE) && (dlPtr != dInfoPtr->dLinePtr)
2153		    && (dlPtr->flags & HAS_3D_BORDER)) {
2154		dlPtr->flags |= OLD_Y_INVALID;
2155	    }
2156
2157	    /*
2158	     * If the old top-line was not completely showing (i.e. the
2159	     * pixelOffset is non-zero) and is no longer the top-line, then we
2160	     * must re-draw it.
2161	     */
2162
2163	    if ((dlPtr->flags & TOP_LINE) &&
2164		    dInfoPtr->topPixelOffset!=0 && dlPtr!=dInfoPtr->dLinePtr) {
2165		dlPtr->flags |= OLD_Y_INVALID;
2166	    }
2167	    if ((dlPtr->flags & BOTTOM_LINE) && (dlPtr->nextPtr != NULL)
2168		    && (dlPtr->flags & HAS_3D_BORDER)) {
2169		dlPtr->flags |= OLD_Y_INVALID;
2170	    }
2171	    if (dlPtr->nextPtr == NULL) {
2172		if ((dlPtr->flags & HAS_3D_BORDER)
2173			&& !(dlPtr->flags & BOTTOM_LINE)) {
2174		    dlPtr->flags |= OLD_Y_INVALID;
2175		}
2176		dlPtr->flags &= ~TOP_LINE;
2177		dlPtr->flags |= BOTTOM_LINE;
2178		break;
2179	    }
2180	    dlPtr->flags &= ~(TOP_LINE|BOTTOM_LINE);
2181	    dlPtr = dlPtr->nextPtr;
2182	}
2183	dInfoPtr->dLinePtr->flags |= TOP_LINE;
2184	dInfoPtr->topPixelOffset = dInfoPtr->newTopPixelOffset;
2185    }
2186
2187    /*
2188     * Arrange for scrollbars to be updated.
2189     */
2190
2191    textPtr->flags |= UPDATE_SCROLLBARS;
2192
2193    /*
2194     * Deal with horizontal scrolling:
2195     * 1. If there's empty space to the right of the longest line, shift the
2196     *	  screen to the right to fill in the empty space.
2197     * 2. If the desired horizontal scroll position has changed, force a full
2198     *	  redisplay of all the lines in the widget.
2199     * 3. If the wrap mode isn't "none" then re-scroll to the base position.
2200     */
2201
2202    dInfoPtr->maxLength = 0;
2203    for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
2204	    dlPtr = dlPtr->nextPtr) {
2205	if (dlPtr->length > dInfoPtr->maxLength) {
2206	    dInfoPtr->maxLength = dlPtr->length;
2207	}
2208    }
2209    maxOffset = dInfoPtr->maxLength - (dInfoPtr->maxX - dInfoPtr->x);
2210
2211    xPixelOffset = dInfoPtr->newXPixelOffset;
2212    if (xPixelOffset > maxOffset) {
2213	xPixelOffset = maxOffset;
2214    }
2215    if (xPixelOffset < 0) {
2216	xPixelOffset = 0;
2217    }
2218
2219    /*
2220     * Here's a problem: see the tests textDisp-29.2.1-4
2221     *
2222     * If the widget is being created, but has not yet been configured it will
2223     * have a maxY of 1 above, and we we won't have examined all the lines
2224     * (just the first line, in fact), and so maxOffset will not be a true
2225     * reflection of the widget's lines. Therefore we must not overwrite the
2226     * original newXPixelOffset in this case.
2227     */
2228
2229    if (!(((Tk_FakeWin *) (textPtr->tkwin))->flags & TK_NEED_CONFIG_NOTIFY)) {
2230	dInfoPtr->newXPixelOffset = xPixelOffset;
2231    }
2232
2233    if (xPixelOffset != dInfoPtr->curXPixelOffset) {
2234	dInfoPtr->curXPixelOffset = xPixelOffset;
2235	for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
2236		dlPtr = dlPtr->nextPtr) {
2237	    dlPtr->flags |= OLD_Y_INVALID;
2238	}
2239    }
2240}
2241
2242/*
2243 *----------------------------------------------------------------------
2244 *
2245 * FreeDLines --
2246 *
2247 *	This function is called to free up all of the resources associated
2248 *	with one or more DLine structures.
2249 *
2250 * Results:
2251 *	None.
2252 *
2253 * Side effects:
2254 *	Memory gets freed and various other resources are released.
2255 *
2256 *----------------------------------------------------------------------
2257 */
2258
2259static void
2260FreeDLines(
2261    TkText *textPtr,		/* Information about overall text widget. */
2262    register DLine *firstPtr,	/* Pointer to first DLine to free up. */
2263    DLine *lastPtr,		/* Pointer to DLine just after last one to
2264				 * free (NULL means everything starting with
2265				 * firstPtr). */
2266    int action)			/* DLINE_UNLINK means DLines are currently
2267				 * linked into the list rooted at
2268				 * textPtr->dInfoPtr->dLinePtr and they have
2269				 * to be unlinked. DLINE_FREE means just free
2270				 * without unlinking. DLINE_FREE_TEMP means
2271				 * the DLine given is just a temporary one and
2272				 * we shouldn't invalidate anything for the
2273				 * overall widget. */
2274{
2275    register TkTextDispChunk *chunkPtr, *nextChunkPtr;
2276    register DLine *nextDLinePtr;
2277
2278    if (action == DLINE_FREE_TEMP) {
2279	lineHeightsRecalculated++;
2280	if (tkTextDebug) {
2281	    char string[TK_POS_CHARS];
2282
2283	    /*
2284	     * Debugging is enabled, so keep a log of all the lines whose
2285	     * height was recalculated. The test suite uses this information.
2286	     */
2287
2288	    TkTextPrintIndex(textPtr, &firstPtr->index, string);
2289	    LOG("tk_textHeightCalc", string);
2290	}
2291    } else if (action == DLINE_UNLINK) {
2292	if (textPtr->dInfoPtr->dLinePtr == firstPtr) {
2293	    textPtr->dInfoPtr->dLinePtr = lastPtr;
2294	} else {
2295	    register DLine *prevPtr;
2296
2297	    for (prevPtr = textPtr->dInfoPtr->dLinePtr;
2298		    prevPtr->nextPtr != firstPtr; prevPtr = prevPtr->nextPtr) {
2299		/* Empty loop body. */
2300	    }
2301	    prevPtr->nextPtr = lastPtr;
2302	}
2303    }
2304    while (firstPtr != lastPtr) {
2305	nextDLinePtr = firstPtr->nextPtr;
2306	for (chunkPtr = firstPtr->chunkPtr; chunkPtr != NULL;
2307		chunkPtr = nextChunkPtr) {
2308	    if (chunkPtr->undisplayProc != NULL) {
2309		(*chunkPtr->undisplayProc)(textPtr, chunkPtr);
2310	    }
2311	    FreeStyle(textPtr, chunkPtr->stylePtr);
2312	    nextChunkPtr = chunkPtr->nextPtr;
2313	    ckfree((char *) chunkPtr);
2314	}
2315	ckfree((char *) firstPtr);
2316	firstPtr = nextDLinePtr;
2317    }
2318    if (action != DLINE_FREE_TEMP) {
2319	textPtr->dInfoPtr->dLinesInvalidated = 1;
2320    }
2321}
2322
2323/*
2324 *----------------------------------------------------------------------
2325 *
2326 * DisplayDLine --
2327 *
2328 *	This function is invoked to draw a single line on the screen.
2329 *
2330 * Results:
2331 *	None.
2332 *
2333 * Side effects:
2334 *	The line given by dlPtr is drawn at its correct position in textPtr's
2335 *	window. Note that this is one *display* line, not one *text* line.
2336 *
2337 *----------------------------------------------------------------------
2338 */
2339
2340static void
2341DisplayDLine(
2342    TkText *textPtr,		/* Text widget in which to draw line. */
2343    register DLine *dlPtr,	/* Information about line to draw. */
2344    DLine *prevPtr,		/* Line just before one to draw, or NULL if
2345				 * dlPtr is the top line. */
2346    Pixmap pixmap)		/* Pixmap to use for double-buffering. Caller
2347				 * must make sure it's large enough to hold
2348				 * line. */
2349{
2350    register TkTextDispChunk *chunkPtr;
2351    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
2352    Display *display;
2353    int height, y_off;
2354#ifndef TK_NO_DOUBLE_BUFFERING
2355    const int y = 0;
2356#else
2357    const int y = dlPtr->y;
2358#endif /* TK_NO_DOUBLE_BUFFERING */
2359
2360    if (dlPtr->chunkPtr == NULL) return;
2361
2362    display = Tk_Display(textPtr->tkwin);
2363
2364    height = dlPtr->height;
2365    if ((height + dlPtr->y) > dInfoPtr->maxY) {
2366	height = dInfoPtr->maxY - dlPtr->y;
2367    }
2368    if (dlPtr->y < dInfoPtr->y) {
2369	y_off = dInfoPtr->y - dlPtr->y;
2370	height -= y_off;
2371    } else {
2372	y_off = 0;
2373    }
2374
2375#ifdef TK_NO_DOUBLE_BUFFERING
2376    TkpClipDrawableToRect(display, pixmap, dInfoPtr->x, y + y_off,
2377	    dInfoPtr->maxX - dInfoPtr->x, height);
2378#endif /* TK_NO_DOUBLE_BUFFERING */
2379
2380    /*
2381     * First, clear the area of the line to the background color for the text
2382     * widget.
2383     */
2384
2385    Tk_Fill3DRectangle(textPtr->tkwin, pixmap, textPtr->border, 0, y,
2386	    Tk_Width(textPtr->tkwin), dlPtr->height, 0, TK_RELIEF_FLAT);
2387
2388    /*
2389     * Next, draw background information for the whole line.
2390     */
2391
2392    DisplayLineBackground(textPtr, dlPtr, prevPtr, pixmap);
2393
2394    /*
2395     * Make another pass through all of the chunks to redraw the insertion
2396     * cursor, if it is visible on this line. Must do it here rather than in
2397     * the foreground pass below because otherwise a wide insertion cursor
2398     * will obscure the character to its left.
2399     */
2400
2401    if (textPtr->state == TK_TEXT_STATE_NORMAL) {
2402	for (chunkPtr = dlPtr->chunkPtr; (chunkPtr != NULL);
2403		chunkPtr = chunkPtr->nextPtr) {
2404	    if (chunkPtr->displayProc == TkTextInsertDisplayProc) {
2405		int x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curXPixelOffset;
2406
2407		(*chunkPtr->displayProc)(textPtr, chunkPtr, x,
2408			y + dlPtr->spaceAbove,
2409			dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
2410			dlPtr->baseline - dlPtr->spaceAbove, display, pixmap,
2411			dlPtr->y + dlPtr->spaceAbove);
2412	    }
2413	}
2414    }
2415
2416    /*
2417     * Make yet another pass through all of the chunks to redraw all of
2418     * foreground information. Note: we have to call the displayProc even for
2419     * chunks that are off-screen. This is needed, for example, so that
2420     * embedded windows can be unmapped in this case.
2421     */
2422
2423    for (chunkPtr = dlPtr->chunkPtr; (chunkPtr != NULL);
2424	    chunkPtr = chunkPtr->nextPtr) {
2425	if (chunkPtr->displayProc == TkTextInsertDisplayProc) {
2426	    /*
2427	     * Already displayed the insertion cursor above. Don't do it again
2428	     * here.
2429	     */
2430
2431	    continue;
2432	}
2433
2434	/*
2435	 * Don't call if elide. This tax OK since not very many visible DLines
2436	 * in an area, but potentially many elide ones.
2437	 */
2438
2439	if (chunkPtr->displayProc != NULL) {
2440	    int x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curXPixelOffset;
2441
2442	    if ((x + chunkPtr->width <= 0) || (x >= dInfoPtr->maxX)) {
2443		/*
2444		 * Note: we have to call the displayProc even for chunks that
2445		 * are off-screen. This is needed, for example, so that
2446		 * embedded windows can be unmapped in this case. Display the
2447		 * chunk at a coordinate that can be clearly identified by the
2448		 * displayProc as being off-screen to the left (the
2449		 * displayProc may not be able to tell if something is off to
2450		 * the right).
2451		 */
2452
2453		x = -chunkPtr->width;
2454	    }
2455	    (*chunkPtr->displayProc)(textPtr, chunkPtr, x,
2456		    y + dlPtr->spaceAbove, dlPtr->height - dlPtr->spaceAbove -
2457		    dlPtr->spaceBelow, dlPtr->baseline - dlPtr->spaceAbove,
2458		    display, pixmap, dlPtr->y + dlPtr->spaceAbove);
2459	}
2460
2461	if (dInfoPtr->dLinesInvalidated) {
2462	    return;
2463	}
2464    }
2465
2466#ifndef TK_NO_DOUBLE_BUFFERING
2467    /*
2468     * Copy the pixmap onto the screen. If this is the first or last line on
2469     * the screen then copy a piece of the line, so that it doesn't overflow
2470     * into the border area. Another special trick: copy the padding area to
2471     * the left of the line; this is because the insertion cursor sometimes
2472     * overflows onto that area and we want to get as much of the cursor as
2473     * possible.
2474     */
2475
2476    XCopyArea(display, pixmap, Tk_WindowId(textPtr->tkwin), dInfoPtr->copyGC,
2477	    dInfoPtr->x, y + y_off, (unsigned) (dInfoPtr->maxX - dInfoPtr->x),
2478	    (unsigned) height, dInfoPtr->x, dlPtr->y + y_off);
2479#else
2480    TkpClipDrawableToRect(display, pixmap, 0, 0, -1, -1);
2481#endif /* TK_NO_DOUBLE_BUFFERING */
2482    linesRedrawn++;
2483}
2484
2485/*
2486 *--------------------------------------------------------------
2487 *
2488 * DisplayLineBackground --
2489 *
2490 *	This function is called to fill in the background for a display line.
2491 *	It draws 3D borders cleverly so that adjacent chunks with the same
2492 *	style (whether on the same line or different lines) have a single 3D
2493 *	border around the whole region.
2494 *
2495 * Results:
2496 *	There is no return value. Pixmap is filled in with background
2497 *	information for dlPtr.
2498 *
2499 * Side effects:
2500 *	None.
2501 *
2502 *--------------------------------------------------------------
2503 */
2504
2505static void
2506DisplayLineBackground(
2507    TkText *textPtr,		/* Text widget containing line. */
2508    register DLine *dlPtr,	/* Information about line to draw. */
2509    DLine *prevPtr,		/* Line just above dlPtr, or NULL if dlPtr is
2510				 * the top-most line in the window. */
2511    Pixmap pixmap)		/* Pixmap to use for double-buffering. Caller
2512				 * must make sure it's large enough to hold
2513				 * line. Caller must also have filled it with
2514				 * the background color for the widget. */
2515{
2516    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
2517    TkTextDispChunk *chunkPtr;	/* Pointer to chunk in the current line. */
2518    TkTextDispChunk *chunkPtr2;	/* Pointer to chunk in the line above or below
2519				 * the current one. NULL if we're to the left
2520				 * of or to the right of the chunks in the
2521				 * line. */
2522    TkTextDispChunk *nextPtr2;	/* Next chunk after chunkPtr2 (it's not the
2523				 * same as chunkPtr2->nextPtr in the case
2524				 * where chunkPtr2 is NULL because the line is
2525				 * indented). */
2526    int leftX;			/* The left edge of the region we're currently
2527				 * working on. */
2528    int leftXIn;		/* 1 means beveled edge at leftX slopes right
2529				 * as it goes down, 0 means it slopes left as
2530				 * it goes down. */
2531    int rightX;			/* Right edge of chunkPtr. */
2532    int rightX2;		/* Right edge of chunkPtr2. */
2533    int matchLeft;		/* Does the style of this line match that of
2534				 * its neighbor just to the left of the
2535				 * current x coordinate? */
2536    int matchRight;		/* Does line's style match its neighbor just
2537				 * to the right of the current x-coord? */
2538    int minX, maxX, xOffset;
2539    StyleValues *sValuePtr;
2540    Display *display;
2541#ifndef TK_NO_DOUBLE_BUFFERING
2542    const int y = 0;
2543#else
2544    const int y = dlPtr->y;
2545#endif /* TK_NO_DOUBLE_BUFFERING */
2546
2547    /*
2548     * Pass 1: scan through dlPtr from left to right. For each range of chunks
2549     * with the same style, draw the main background for the style plus the
2550     * vertical parts of the 3D borders (the left and right edges).
2551     */
2552
2553    display = Tk_Display(textPtr->tkwin);
2554    minX = dInfoPtr->curXPixelOffset;
2555    xOffset = dInfoPtr->x - minX;
2556    maxX = minX + dInfoPtr->maxX - dInfoPtr->x;
2557    chunkPtr = dlPtr->chunkPtr;
2558
2559    /*
2560     * Note A: in the following statement, and a few others later in this file
2561     * marked with "See Note A above", the right side of the assignment was
2562     * replaced with 0 on 6/18/97. This has the effect of highlighting the
2563     * empty space to the left of a line whenever the leftmost character of
2564     * the line is highlighted. This way, multi-line highlights always line up
2565     * along their left edges. However, this may look funny in the case where
2566     * a single word is highlighted. To undo the change, replace "leftX = 0"
2567     * with "leftX = chunkPtr->x" and "rightX2 = 0" with "rightX2 =
2568     * nextPtr2->x" here and at all the marked points below. This restores the
2569     * old behavior where empty space to the left of a line is not
2570     * highlighted, leaving a ragged left edge for multi-line highlights.
2571     */
2572
2573    leftX = 0;
2574    for (; leftX < maxX; chunkPtr = chunkPtr->nextPtr) {
2575	if ((chunkPtr->nextPtr != NULL)
2576		&& SAME_BACKGROUND(chunkPtr->nextPtr->stylePtr,
2577		chunkPtr->stylePtr)) {
2578	    continue;
2579	}
2580	sValuePtr = chunkPtr->stylePtr->sValuePtr;
2581	rightX = chunkPtr->x + chunkPtr->width;
2582	if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) {
2583	    rightX = maxX;
2584	}
2585	if (chunkPtr->stylePtr->bgGC != None) {
2586	    /*
2587	     * Not visible - bail out now.
2588	     */
2589
2590	    if (rightX + xOffset <= 0) {
2591		leftX = rightX;
2592		continue;
2593	    }
2594
2595	    /*
2596	     * Trim the start position for drawing to be no further away than
2597	     * -borderWidth. The reason is that on many X servers drawing from
2598	     * -32768 (or less) to +something simply does not display
2599	     * correctly. [Patch #541999]
2600	     */
2601
2602	    if ((leftX + xOffset) < -(sValuePtr->borderWidth)) {
2603		leftX = -sValuePtr->borderWidth - xOffset;
2604	    }
2605	    if ((rightX - leftX) > 32767) {
2606		rightX = leftX + 32767;
2607	    }
2608
2609	    XFillRectangle(display, pixmap, chunkPtr->stylePtr->bgGC,
2610		    leftX + xOffset, y, (unsigned int) (rightX - leftX),
2611		    (unsigned int) dlPtr->height);
2612	    if (sValuePtr->relief != TK_RELIEF_FLAT) {
2613		Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
2614			leftX + xOffset, y, sValuePtr->borderWidth,
2615			dlPtr->height, 1, sValuePtr->relief);
2616		Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
2617			rightX - sValuePtr->borderWidth + xOffset,
2618			y, sValuePtr->borderWidth, dlPtr->height, 0,
2619			sValuePtr->relief);
2620	    }
2621	}
2622	leftX = rightX;
2623    }
2624
2625    /*
2626     * Pass 2: draw the horizontal bevels along the top of the line. To do
2627     * this, scan through dlPtr from left to right while simultaneously
2628     * scanning through the line just above dlPtr. ChunkPtr2 and nextPtr2
2629     * refer to two adjacent chunks in the line above.
2630     */
2631
2632    chunkPtr = dlPtr->chunkPtr;
2633    leftX = 0;				/* See Note A above. */
2634    leftXIn = 1;
2635    rightX = chunkPtr->x + chunkPtr->width;
2636    if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) {
2637	rightX = maxX;
2638    }
2639    chunkPtr2 = NULL;
2640    if (prevPtr != NULL && prevPtr->chunkPtr != NULL) {
2641	/*
2642	 * Find the chunk in the previous line that covers leftX.
2643	 */
2644
2645	nextPtr2 = prevPtr->chunkPtr;
2646	rightX2 = 0;			/* See Note A above. */
2647	while (rightX2 <= leftX) {
2648	    chunkPtr2 = nextPtr2;
2649	    if (chunkPtr2 == NULL) {
2650		break;
2651	    }
2652	    nextPtr2 = chunkPtr2->nextPtr;
2653	    rightX2 = chunkPtr2->x + chunkPtr2->width;
2654	    if (nextPtr2 == NULL) {
2655		rightX2 = INT_MAX;
2656	    }
2657	}
2658    } else {
2659	nextPtr2 = NULL;
2660	rightX2 = INT_MAX;
2661    }
2662
2663    while (leftX < maxX) {
2664	matchLeft = (chunkPtr2 != NULL)
2665		&& SAME_BACKGROUND(chunkPtr2->stylePtr, chunkPtr->stylePtr);
2666	sValuePtr = chunkPtr->stylePtr->sValuePtr;
2667	if (rightX <= rightX2) {
2668	    /*
2669	     * The chunk in our line is about to end. If its style changes
2670	     * then draw the bevel for the current style.
2671	     */
2672
2673	    if ((chunkPtr->nextPtr == NULL)
2674		    || !SAME_BACKGROUND(chunkPtr->stylePtr,
2675		    chunkPtr->nextPtr->stylePtr)) {
2676		if (!matchLeft && (sValuePtr->relief != TK_RELIEF_FLAT)) {
2677		    Tk_3DHorizontalBevel(textPtr->tkwin, pixmap,
2678			    sValuePtr->border, leftX + xOffset, y,
2679			    rightX - leftX, sValuePtr->borderWidth, leftXIn,
2680			    1, 1, sValuePtr->relief);
2681		}
2682		leftX = rightX;
2683		leftXIn = 1;
2684
2685		/*
2686		 * If the chunk in the line above is also ending at the same
2687		 * point then advance to the next chunk in that line.
2688		 */
2689
2690		if ((rightX == rightX2) && (chunkPtr2 != NULL)) {
2691		    goto nextChunk2;
2692		}
2693	    }
2694	    chunkPtr = chunkPtr->nextPtr;
2695	    if (chunkPtr == NULL) {
2696		break;
2697	    }
2698	    rightX = chunkPtr->x + chunkPtr->width;
2699	    if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) {
2700		rightX = maxX;
2701	    }
2702	    continue;
2703	}
2704
2705	/*
2706	 * The chunk in the line above is ending at an x-position where there
2707	 * is no change in the style of the current line. If the style above
2708	 * matches the current line on one side of the change but not on the
2709	 * other, we have to draw an L-shaped piece of bevel.
2710	 */
2711
2712	matchRight = (nextPtr2 != NULL)
2713		&& SAME_BACKGROUND(nextPtr2->stylePtr, chunkPtr->stylePtr);
2714	if (matchLeft && !matchRight) {
2715	    if (sValuePtr->relief != TK_RELIEF_FLAT) {
2716		Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
2717			rightX2 - sValuePtr->borderWidth + xOffset, y,
2718			sValuePtr->borderWidth, sValuePtr->borderWidth, 0,
2719			sValuePtr->relief);
2720	    }
2721	    leftX = rightX2 - sValuePtr->borderWidth;
2722	    leftXIn = 0;
2723	} else if (!matchLeft && matchRight
2724		&& (sValuePtr->relief != TK_RELIEF_FLAT)) {
2725	    Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
2726		    rightX2 + xOffset, y, sValuePtr->borderWidth,
2727		    sValuePtr->borderWidth, 1, sValuePtr->relief);
2728	    Tk_3DHorizontalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
2729		    leftX + xOffset, y, rightX2 + sValuePtr->borderWidth -
2730		    leftX, sValuePtr->borderWidth, leftXIn, 0, 1,
2731		    sValuePtr->relief);
2732	}
2733
2734    nextChunk2:
2735	chunkPtr2 = nextPtr2;
2736	if (chunkPtr2 == NULL) {
2737	    rightX2 = INT_MAX;
2738	} else {
2739	    nextPtr2 = chunkPtr2->nextPtr;
2740	    rightX2 = chunkPtr2->x + chunkPtr2->width;
2741	    if (nextPtr2 == NULL) {
2742		rightX2 = INT_MAX;
2743	    }
2744	}
2745    }
2746
2747    /*
2748     * Pass 3: draw the horizontal bevels along the bottom of the line. This
2749     * uses the same approach as pass 2.
2750     */
2751
2752    chunkPtr = dlPtr->chunkPtr;
2753    leftX = 0;				/* See Note A above. */
2754    leftXIn = 0;
2755    rightX = chunkPtr->x + chunkPtr->width;
2756    if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) {
2757	rightX = maxX;
2758    }
2759    chunkPtr2 = NULL;
2760    if (dlPtr->nextPtr != NULL && dlPtr->nextPtr->chunkPtr != NULL) {
2761	/*
2762	 * Find the chunk in the previous line that covers leftX.
2763	 */
2764
2765	nextPtr2 = dlPtr->nextPtr->chunkPtr;
2766	rightX2 = 0;			/* See Note A above. */
2767	while (rightX2 <= leftX) {
2768	    chunkPtr2 = nextPtr2;
2769	    if (chunkPtr2 == NULL) {
2770		break;
2771	    }
2772	    nextPtr2 = chunkPtr2->nextPtr;
2773	    rightX2 = chunkPtr2->x + chunkPtr2->width;
2774	    if (nextPtr2 == NULL) {
2775		rightX2 = INT_MAX;
2776	    }
2777	}
2778    } else {
2779	nextPtr2 = NULL;
2780	rightX2 = INT_MAX;
2781    }
2782
2783    while (leftX < maxX) {
2784	matchLeft = (chunkPtr2 != NULL)
2785		&& SAME_BACKGROUND(chunkPtr2->stylePtr, chunkPtr->stylePtr);
2786	sValuePtr = chunkPtr->stylePtr->sValuePtr;
2787	if (rightX <= rightX2) {
2788	    if ((chunkPtr->nextPtr == NULL)
2789		    || !SAME_BACKGROUND(chunkPtr->stylePtr,
2790		    chunkPtr->nextPtr->stylePtr)) {
2791		if (!matchLeft && (sValuePtr->relief != TK_RELIEF_FLAT)) {
2792		    Tk_3DHorizontalBevel(textPtr->tkwin, pixmap,
2793			    sValuePtr->border, leftX + xOffset,
2794			    y + dlPtr->height - sValuePtr->borderWidth,
2795			    rightX - leftX, sValuePtr->borderWidth, leftXIn,
2796			    0, 0, sValuePtr->relief);
2797		}
2798		leftX = rightX;
2799		leftXIn = 0;
2800		if ((rightX == rightX2) && (chunkPtr2 != NULL)) {
2801		    goto nextChunk2b;
2802		}
2803	    }
2804	    chunkPtr = chunkPtr->nextPtr;
2805	    if (chunkPtr == NULL) {
2806		break;
2807	    }
2808	    rightX = chunkPtr->x + chunkPtr->width;
2809	    if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) {
2810		rightX = maxX;
2811	    }
2812	    continue;
2813	}
2814
2815	matchRight = (nextPtr2 != NULL)
2816		&& SAME_BACKGROUND(nextPtr2->stylePtr, chunkPtr->stylePtr);
2817	if (matchLeft && !matchRight) {
2818	    if (sValuePtr->relief != TK_RELIEF_FLAT) {
2819		Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
2820			rightX2 - sValuePtr->borderWidth + xOffset,
2821			y + dlPtr->height - sValuePtr->borderWidth,
2822			sValuePtr->borderWidth, sValuePtr->borderWidth, 0,
2823			sValuePtr->relief);
2824	    }
2825	    leftX = rightX2 - sValuePtr->borderWidth;
2826	    leftXIn = 1;
2827	} else if (!matchLeft && matchRight
2828		&& (sValuePtr->relief != TK_RELIEF_FLAT)) {
2829	    Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
2830		    rightX2 + xOffset, y + dlPtr->height -
2831		    sValuePtr->borderWidth, sValuePtr->borderWidth,
2832		    sValuePtr->borderWidth, 1, sValuePtr->relief);
2833	    Tk_3DHorizontalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
2834		    leftX + xOffset, y + dlPtr->height -
2835		    sValuePtr->borderWidth, rightX2 + sValuePtr->borderWidth -
2836		    leftX, sValuePtr->borderWidth, leftXIn, 1, 0,
2837		    sValuePtr->relief);
2838	}
2839
2840    nextChunk2b:
2841	chunkPtr2 = nextPtr2;
2842	if (chunkPtr2 == NULL) {
2843	    rightX2 = INT_MAX;
2844	} else {
2845	    nextPtr2 = chunkPtr2->nextPtr;
2846	    rightX2 = chunkPtr2->x + chunkPtr2->width;
2847	    if (nextPtr2 == NULL) {
2848		rightX2 = INT_MAX;
2849	    }
2850	}
2851    }
2852}
2853
2854/*
2855 *----------------------------------------------------------------------
2856 *
2857 * AsyncUpdateLineMetrics --
2858 *
2859 *	This function is invoked as a background handler to update the pixel-
2860 *	height calculations of individual lines in an asychronous manner.
2861 *
2862 *	Currently a timer-handler is used for this purpose, which continuously
2863 *	reschedules itself. It may well be better to use some other approach
2864 *	(e.g., a background thread). We can't use an idle-callback because of
2865 *	a known bug in Tcl/Tk in which idle callbacks are not allowed to
2866 *	re-schedule themselves. This just causes an effective infinite loop.
2867 *
2868 * Results:
2869 *	None.
2870 *
2871 * Side effects:
2872 *	Line heights may be recalculated.
2873 *
2874 *----------------------------------------------------------------------
2875 */
2876
2877static void
2878AsyncUpdateLineMetrics(
2879    ClientData clientData)	/* Information about widget. */
2880{
2881    register TkText *textPtr = (TkText *) clientData;
2882    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
2883    int lineNum;
2884
2885    dInfoPtr->lineUpdateTimer = NULL;
2886
2887    if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)) {
2888	/*
2889	 * The widget has been deleted. Don't do anything.
2890	 */
2891
2892	if (--textPtr->refCount == 0) {
2893	    ckfree((char *) textPtr);
2894	}
2895	return;
2896    }
2897
2898    if (dInfoPtr->flags & REDRAW_PENDING) {
2899	dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1,
2900		AsyncUpdateLineMetrics, clientData);
2901	return;
2902    }
2903
2904    lineNum = dInfoPtr->currentMetricUpdateLine;
2905    if (dInfoPtr->lastMetricUpdateLine == -1) {
2906	dInfoPtr->lastMetricUpdateLine =
2907		TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr);
2908    }
2909
2910    /*
2911     * Update the lines in blocks of about 24 recalculations, or 250+ lines
2912     * examined, so we pass in 256 for 'doThisMuch'.
2913     */
2914
2915    lineNum = TkTextUpdateLineMetrics(textPtr, lineNum,
2916	    dInfoPtr->lastMetricUpdateLine, 256);
2917
2918    if (tkTextDebug) {
2919	char buffer[2 * TCL_INTEGER_SPACE + 1];
2920
2921	sprintf(buffer, "%d %d", lineNum, dInfoPtr->lastMetricUpdateLine);
2922	LOG("tk_textInvalidateLine", buffer);
2923    }
2924
2925    /*
2926     * If we're not in the middle of a long-line calculation (metricEpoch==-1)
2927     * and we've reached the last line, then we're done.
2928     */
2929
2930    if (dInfoPtr->metricEpoch == -1
2931	    && lineNum == dInfoPtr->lastMetricUpdateLine) {
2932	/*
2933	 * We have looped over all lines, so we're done. We must release our
2934	 * refCount on the widget (the timer token was already set to NULL
2935	 * above).
2936	 */
2937
2938	textPtr->refCount--;
2939	if (textPtr->refCount == 0) {
2940	    ckfree((char *) textPtr);
2941	}
2942	return;
2943    }
2944    dInfoPtr->currentMetricUpdateLine = lineNum;
2945
2946    /*
2947     * Re-arm the timer. We already have a refCount on the text widget so no
2948     * need to adjust that.
2949     */
2950
2951    dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1,
2952	    AsyncUpdateLineMetrics, (ClientData) textPtr);
2953}
2954
2955/*
2956 *----------------------------------------------------------------------
2957 *
2958 * TkTextUpdateLineMetrics --
2959 *
2960 *	This function updates the pixel height calculations of a range of
2961 *	lines in the widget. The range is from lineNum to endLine, but, if
2962 *	doThisMuch is positive, then the function may return earlier, once a
2963 *	certain number of lines has been examined. The line counts are from 0.
2964 *
2965 *	If doThisMuch is -1, then all lines in the range will be updated. This
2966 *	will potentially take quite some time for a large text widget.
2967 *
2968 *	Note: with bad input for lineNum and endLine, this function can loop
2969 *	indefinitely.
2970 *
2971 * Results:
2972 *	The index of the last line examined (or -1 if we are about to wrap
2973 *	around from end to beginning of the widget, and the next line will be
2974 *	the first line).
2975 *
2976 * Side effects:
2977 *	Line heights may be recalculated.
2978 *
2979 *----------------------------------------------------------------------
2980 */
2981
2982int
2983TkTextUpdateLineMetrics(
2984    TkText *textPtr,		/* Information about widget. */
2985    int lineNum,		/* Start at this line. */
2986    int endLine,		/* Go no further than this line. */
2987    int doThisMuch)		/* How many lines to check, or how many 10s of
2988				 * lines to recalculate. If '-1' then do
2989				 * everything in the range (which may take a
2990				 * while). */
2991{
2992    TkTextLine *linePtr = NULL;
2993    int count = 0;
2994    int totalLines = TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr);
2995
2996    if (totalLines == 0) {
2997	/*
2998	 * Empty peer widget.
2999	 */
3000
3001	return endLine;
3002    }
3003
3004    while (1) {
3005	/*
3006	 * Get a suitable line.
3007	 */
3008
3009	if (lineNum == -1 && linePtr == NULL) {
3010	    lineNum = 0;
3011	    linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr,
3012		    lineNum);
3013	} else {
3014	    if (lineNum == -1 || linePtr == NULL) {
3015		if (lineNum == -1) {
3016		    lineNum = 0;
3017		}
3018		linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree,
3019			textPtr, lineNum);
3020	    } else {
3021		lineNum++;
3022		linePtr = TkBTreeNextLine(textPtr, linePtr);
3023	    }
3024
3025	    /*
3026	     * If we're in the middle of a partial-line height calculation,
3027	     * then we can't be done.
3028	     */
3029
3030	    if (textPtr->dInfoPtr->metricEpoch == -1 && lineNum == endLine) {
3031		/*
3032		 * We have looped over all lines, so we're done.
3033		 */
3034
3035		break;
3036	    }
3037	}
3038
3039	if (lineNum < totalLines) {
3040	    if (tkTextDebug) {
3041		char buffer[4 * TCL_INTEGER_SPACE + 3];
3042
3043		sprintf(buffer, "%d %d %d %d",
3044			lineNum, endLine, totalLines, count);
3045		LOG("tk_textInvalidateLine", buffer);
3046	    }
3047
3048	    /*
3049	     * Now update the line's metrics if necessary.
3050	     */
3051
3052	    if (TkBTreeLinePixelEpoch(textPtr, linePtr)
3053		!= textPtr->dInfoPtr->lineMetricUpdateEpoch) {
3054		if (doThisMuch == -1) {
3055		    count += 8 * TkTextUpdateOneLine(textPtr, linePtr, 0,
3056			    NULL, 0);
3057		} else {
3058		    TkTextIndex index;
3059		    TkTextIndex *indexPtr;
3060		    int pixelHeight;
3061
3062		    /*
3063		     * If the metric epoch is the same as the widget's epoch,
3064		     * then we know that indexPtrs are still valid, and if the
3065		     * cached metricIndex (if any) is for the same line as we
3066		     * wish to examine, then we are looking at a long line
3067		     * wrapped many times, which we will examine in pieces.
3068		     */
3069
3070		    if (textPtr->dInfoPtr->metricEpoch ==
3071			    textPtr->sharedTextPtr->stateEpoch &&
3072			    textPtr->dInfoPtr->metricIndex.linePtr==linePtr) {
3073			indexPtr = &textPtr->dInfoPtr->metricIndex;
3074			pixelHeight = textPtr->dInfoPtr->metricPixelHeight;
3075		    } else {
3076			/*
3077			 * We must reset the partial line height calculation
3078			 * data here, so we don't use it when it is out of
3079			 * date.
3080			 */
3081
3082			textPtr->dInfoPtr->metricEpoch = -1;
3083			index.tree = textPtr->sharedTextPtr->tree;
3084			index.linePtr = linePtr;
3085			index.byteIndex = 0;
3086			index.textPtr = NULL;
3087			indexPtr = &index;
3088			pixelHeight = 0;
3089		    }
3090
3091		    /*
3092		     * Update the line and update the counter, counting 8 for
3093		     * each display line we actually re-layout.
3094		     */
3095
3096		    count += 8 * TkTextUpdateOneLine(textPtr, linePtr,
3097			    pixelHeight, indexPtr, 1);
3098
3099		    if (indexPtr->linePtr == linePtr) {
3100			/*
3101			 * We didn't complete the logical line, because it
3102			 * produced very many display lines - it must be a
3103			 * long line wrapped many times. So we must cache as
3104			 * far as we got for next time around.
3105			 */
3106
3107			if (pixelHeight == 0) {
3108			    /*
3109			     * These have already been stored, unless we just
3110			     * started the new line.
3111			     */
3112
3113			    textPtr->dInfoPtr->metricIndex = index;
3114			    textPtr->dInfoPtr->metricEpoch =
3115				    textPtr->sharedTextPtr->stateEpoch;
3116			}
3117			textPtr->dInfoPtr->metricPixelHeight =
3118				TkBTreeLinePixelCount(textPtr, linePtr);
3119			break;
3120		    } else {
3121			/*
3122			 * We're done with this long line.
3123			 */
3124
3125			textPtr->dInfoPtr->metricEpoch = -1;
3126		    }
3127		}
3128	    } else {
3129		/*
3130		 * This line is already up to date. That means there's nothing
3131		 * to do here.
3132		 */
3133	    }
3134	} else {
3135	    /*
3136	     * We must never recalculate the height of the last artificial
3137	     * line. It must stay at zero, and if we recalculate it, it will
3138	     * change.
3139	     */
3140
3141	    if (endLine >= totalLines) {
3142		lineNum = endLine;
3143		break;
3144	    }
3145
3146	    /*
3147	     * Set things up for the next loop through.
3148	     */
3149
3150	    lineNum = -1;
3151	}
3152	count++;
3153
3154	if (doThisMuch != -1 && count >= doThisMuch) {
3155	    break;
3156	}
3157    }
3158    if (doThisMuch == -1) {
3159	/*
3160	 * If we were requested to provide a full update, then also update the
3161	 * scrollbar.
3162	 */
3163
3164	GetYView(textPtr->interp, textPtr, 1);
3165    }
3166    return lineNum;
3167}
3168
3169/*
3170 *----------------------------------------------------------------------
3171 *
3172 * TkTextInvalidateLineMetrics, TextInvalidateLineMetrics --
3173 *
3174 *	Mark a number of text lines as having invalid line metric
3175 *	calculations. Never call this with linePtr as the last (artificial)
3176 *	line in the text. Depending on 'action' which indicates whether the
3177 *	given lines are simply invalid or have been inserted or deleted, the
3178 *	pre-existing asynchronous line update range may need to be adjusted.
3179 *
3180 *	If linePtr is NULL then 'lineCount' and 'action' are ignored and all
3181 *	lines are invalidated.
3182 *
3183 * Results:
3184 *	None.
3185 *
3186 * Side effects:
3187 *	May schedule an asychronous callback.
3188 *
3189 *----------------------------------------------------------------------
3190 */
3191
3192void
3193TkTextInvalidateLineMetrics(
3194    TkSharedText *sharedTextPtr,/* Shared widget section for all peers, or
3195				 * NULL. */
3196    TkText *textPtr,		/* Widget record for text widget. */
3197    TkTextLine *linePtr,	/* Invalidation starts from this line. */
3198    int lineCount,		/* And includes this many following lines. */
3199    int action)			/* Indicates what type of invalidation
3200				 * occurred (insert, delete, or simple). */
3201{
3202    if (sharedTextPtr == NULL) {
3203	TextInvalidateLineMetrics(textPtr, linePtr, lineCount, action);
3204    } else {
3205	textPtr = sharedTextPtr->peers;
3206	while (textPtr != NULL) {
3207	    TextInvalidateLineMetrics(textPtr, linePtr, lineCount, action);
3208	    textPtr = textPtr->next;
3209	}
3210    }
3211}
3212
3213static void
3214TextInvalidateLineMetrics(
3215    TkText *textPtr,		/* Widget record for text widget. */
3216    TkTextLine *linePtr,	/* Invalidation starts from this line. */
3217    int lineCount,		/* And includes this many following lines. */
3218    int action)			/* Indicates what type of invalidation
3219				 * occurred (insert, delete, or simple). */
3220{
3221    int fromLine;
3222    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
3223
3224    if (linePtr != NULL) {
3225	int counter = lineCount;
3226
3227	fromLine = TkBTreeLinesTo(textPtr, linePtr);
3228
3229	/*
3230	 * Invalidate the height calculations of each line in the given range.
3231	 */
3232
3233	TkBTreeLinePixelEpoch(textPtr, linePtr) = 0;
3234	while (counter > 0 && linePtr != 0) {
3235	    linePtr = TkBTreeNextLine(textPtr, linePtr);
3236	    if (linePtr != NULL) {
3237		TkBTreeLinePixelEpoch(textPtr, linePtr) = 0;
3238	    }
3239	    counter--;
3240	}
3241
3242	/*
3243	 * Now schedule an examination of each line in the union of the old
3244	 * and new update ranges, including the (possibly empty) range in
3245	 * between. If that between range is not-empty, then we are examining
3246	 * more lines than is strictly necessary (but the examination of the
3247	 * extra lines should be quick, since their pixelCalculationEpoch will
3248	 * be up to date). However, to keep track of that would require more
3249	 * complex record-keeping that what we have.
3250	 */
3251
3252	if (dInfoPtr->lineUpdateTimer == NULL) {
3253	    dInfoPtr->currentMetricUpdateLine = fromLine;
3254	    if (action == TK_TEXT_INVALIDATE_DELETE) {
3255		lineCount = 0;
3256	    }
3257	    dInfoPtr->lastMetricUpdateLine = fromLine + lineCount + 1;
3258	} else {
3259	    int toLine = fromLine + lineCount + 1;
3260
3261	    if (action == TK_TEXT_INVALIDATE_DELETE) {
3262		if (toLine <= dInfoPtr->currentMetricUpdateLine) {
3263		    dInfoPtr->currentMetricUpdateLine = fromLine;
3264		    if (dInfoPtr->lastMetricUpdateLine != -1) {
3265			dInfoPtr->lastMetricUpdateLine -= lineCount;
3266		    }
3267		} else if (fromLine <= dInfoPtr->currentMetricUpdateLine) {
3268		    dInfoPtr->currentMetricUpdateLine = fromLine;
3269		    if (toLine <= dInfoPtr->lastMetricUpdateLine) {
3270			dInfoPtr->lastMetricUpdateLine -= lineCount;
3271		    }
3272		} else {
3273		    if (dInfoPtr->lastMetricUpdateLine != -1) {
3274			dInfoPtr->lastMetricUpdateLine = toLine;
3275		    }
3276		}
3277	    } else if (action == TK_TEXT_INVALIDATE_INSERT) {
3278		if (toLine <= dInfoPtr->currentMetricUpdateLine) {
3279		    dInfoPtr->currentMetricUpdateLine = fromLine;
3280		    if (dInfoPtr->lastMetricUpdateLine != -1) {
3281			dInfoPtr->lastMetricUpdateLine += lineCount;
3282		    }
3283		} else if (fromLine <= dInfoPtr->currentMetricUpdateLine) {
3284		    dInfoPtr->currentMetricUpdateLine = fromLine;
3285		    if (toLine <= dInfoPtr->lastMetricUpdateLine) {
3286			dInfoPtr->lastMetricUpdateLine += lineCount;
3287		    }
3288		    if (toLine > dInfoPtr->lastMetricUpdateLine) {
3289			dInfoPtr->lastMetricUpdateLine = toLine;
3290		    }
3291		} else {
3292		    if (dInfoPtr->lastMetricUpdateLine != -1) {
3293			dInfoPtr->lastMetricUpdateLine = toLine;
3294		    }
3295		}
3296	    } else {
3297		if (fromLine < dInfoPtr->currentMetricUpdateLine) {
3298		    dInfoPtr->currentMetricUpdateLine = fromLine;
3299		}
3300		if (dInfoPtr->lastMetricUpdateLine != -1
3301			&& toLine > dInfoPtr->lastMetricUpdateLine) {
3302		    dInfoPtr->lastMetricUpdateLine = toLine;
3303		}
3304	    }
3305	}
3306    } else {
3307	/*
3308	 * This invalidates the height of all lines in the widget.
3309	 */
3310
3311	if ((++dInfoPtr->lineMetricUpdateEpoch) == 0) {
3312	    dInfoPtr->lineMetricUpdateEpoch++;
3313	}
3314
3315	/*
3316	 * This has the effect of forcing an entire new loop of update checks
3317	 * on all lines in the widget.
3318	 */
3319
3320	if (dInfoPtr->lineUpdateTimer == NULL) {
3321	    dInfoPtr->currentMetricUpdateLine = -1;
3322	}
3323	dInfoPtr->lastMetricUpdateLine = dInfoPtr->currentMetricUpdateLine;
3324    }
3325
3326    /*
3327     * Now re-set the current update calculations.
3328     */
3329
3330    if (dInfoPtr->lineUpdateTimer == NULL) {
3331	textPtr->refCount++;
3332	dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1,
3333		AsyncUpdateLineMetrics, (ClientData) textPtr);
3334    }
3335}
3336
3337/*
3338 *----------------------------------------------------------------------
3339 *
3340 * TkTextFindDisplayLineEnd --
3341 *
3342 *	This function is invoked to find the index of the beginning or end of
3343 *	the particular display line on which the given index sits, whether
3344 *	that line is displayed or not.
3345 *
3346 *	If 'end' is zero, we look for the start, and if 'end' is one we look
3347 *	for the end.
3348 *
3349 *	If the beginning of the current display line is elided, and we are
3350 *	looking for the start of the line, then the returned index will be the
3351 *	first elided index on the display line.
3352 *
3353 *	Similarly if the end of the current display line is elided and we are
3354 *	looking for the end, then the returned index will be the last elided
3355 *	index on the display line.
3356 *
3357 * Results:
3358 *	Modifies indexPtr to point to the given end.
3359 *
3360 *	If xOffset is non-NULL, it is set to the x-pixel offset of the given
3361 *	original index within the given display line.
3362 *
3363 * Side effects:
3364 *	The combination of 'LayoutDLine' and 'FreeDLines' seems like a rather
3365 *	time-consuming way of gathering the information we need, so this would
3366 *	be a good place to look to speed up the calculations. In particular
3367 *	these calls will map and unmap embedded windows respectively, which I
3368 *	would hope isn't exactly necessary!
3369 *
3370 *----------------------------------------------------------------------
3371 */
3372
3373void
3374TkTextFindDisplayLineEnd(
3375    TkText *textPtr,		/* Widget record for text widget. */
3376    TkTextIndex *indexPtr,	/* Index we will adjust to the display line
3377				 * start or end. */
3378    int end,			/* 0 = start, 1 = end. */
3379    int *xOffset)		/* NULL, or used to store the x-pixel offset
3380				 * of the original index within its display
3381				 * line. */
3382{
3383    if (!end && indexPtr->byteIndex == 0) {
3384	/*
3385	 * Nothing to do.
3386	 */
3387
3388	if (xOffset != NULL) {
3389	    *xOffset = 0;
3390	}
3391	return;
3392    } else {
3393	TkTextIndex index = *indexPtr;
3394
3395	index.byteIndex = 0;
3396	index.textPtr = NULL;
3397
3398	while (1) {
3399	    TkTextIndex endOfLastLine;
3400
3401	    if (TkTextIndexBackBytes(textPtr, &index, 1, &endOfLastLine)) {
3402		/*
3403		 * Reached beginning of text.
3404		 */
3405
3406		break;
3407	    }
3408
3409	    if (!TkTextIsElided(textPtr, &endOfLastLine, NULL)) {
3410		/*
3411		 * The eol is not elided, so 'index' points to the start of a
3412		 * display line (as well as logical line).
3413		 */
3414
3415		break;
3416	    }
3417
3418	    /*
3419	     * indexPtr's logical line is actually merged with the previous
3420	     * logical line whose eol is elided. Continue searching back to
3421	     * get a real line start.
3422	     */
3423
3424	    index = endOfLastLine;
3425	    index.byteIndex = 0;
3426	}
3427
3428	while (1) {
3429	    DLine *dlPtr;
3430	    int byteCount;
3431	    TkTextIndex nextLineStart;
3432
3433	    dlPtr = LayoutDLine(textPtr, &index);
3434	    byteCount = dlPtr->byteCount;
3435
3436	    TkTextIndexForwBytes(textPtr, &index, byteCount, &nextLineStart);
3437
3438	    /*
3439	     * 'byteCount' goes up to the beginning of the next display line,
3440	     * so equality here says we need one more line. We try to perform
3441	     * a quick comparison which is valid for the case where the
3442	     * logical line is the same, but otherwise fall back on a full
3443	     * TkTextIndexCmp.
3444	     */
3445
3446	    if (((index.linePtr == indexPtr->linePtr)
3447		    && (index.byteIndex + byteCount > indexPtr->byteIndex))
3448		    || (dlPtr->logicalLinesMerged > 0
3449		    && TkTextIndexCmp(&nextLineStart, indexPtr) > 0)) {
3450		/*
3451		 * It's on this display line.
3452		 */
3453
3454		if (xOffset != NULL) {
3455		    /*
3456		     * This call takes a byte index relative to the start of
3457		     * the current _display_ line, not logical line. We are
3458		     * about to overwrite indexPtr->byteIndex, so we must do
3459		     * this now.
3460		     */
3461
3462		    *xOffset = DlineXOfIndex(textPtr, dlPtr,
3463			    indexPtr->byteIndex - dlPtr->index.byteIndex);
3464		}
3465		if (end) {
3466		    /*
3467		     * The index we want is one less than the number of bytes
3468		     * in the display line.
3469		     */
3470
3471		    TkTextIndexBackBytes(textPtr, &nextLineStart, 1, indexPtr);
3472		} else {
3473		    *indexPtr = index;
3474		}
3475		FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP);
3476		return;
3477	    }
3478	    FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP);
3479	    index = nextLineStart;
3480	}
3481    }
3482}
3483
3484/*
3485 *----------------------------------------------------------------------
3486 *
3487 * CalculateDisplayLineHeight --
3488 *
3489 *	This function is invoked to recalculate the height of the particular
3490 *	display line which starts with the given index, whether that line is
3491 *	displayed or not.
3492 *
3493 *	This function does not, in itself, update any cached information about
3494 *	line heights. That should be done, where necessary, by its callers.
3495 *
3496 *	The behaviour of this function is _undefined_ if indexPtr is not
3497 *	currently at the beginning of a display line.
3498 *
3499 * Results:
3500 *	The number of vertical pixels used by the display line.
3501 *
3502 *	If 'byteCountPtr' is non-NULL, then returns in that pointer the number
3503 *	of byte indices on the given display line (which can be used to update
3504 *	indexPtr in a loop).
3505 *
3506 *	If 'mergedLinePtr' is non-NULL, then returns in that pointer the
3507 *	number of extra logical lines merged into the given display line.
3508 *
3509 * Side effects:
3510 *	The combination of 'LayoutDLine' and 'FreeDLines' seems like a rather
3511 *	time-consuming way of gathering the information we need, so this would
3512 *	be a good place to look to speed up the calculations. In particular
3513 *	these calls will map and unmap embedded windows respectively, which I
3514 *	would hope isn't exactly necessary!
3515 *
3516 *----------------------------------------------------------------------
3517 */
3518
3519static int
3520CalculateDisplayLineHeight(
3521    TkText *textPtr,		/* Widget record for text widget. */
3522    CONST TkTextIndex *indexPtr,/* The index at the beginning of the display
3523				 * line of interest. */
3524    int *byteCountPtr,		/* NULL or used to return the number of byte
3525				 * indices on the given display line. */
3526    int *mergedLinePtr)		/* NULL or used to return if the given display
3527				 * line merges with a following logical line
3528				 * (because the eol is elided). */
3529{
3530    DLine *dlPtr;
3531    int pixelHeight;
3532
3533    /*
3534     * Special case for artificial last line. May be better to move this
3535     * inside LayoutDLine.
3536     */
3537
3538    if (indexPtr->byteIndex == 0
3539	    && TkBTreeNextLine(textPtr, indexPtr->linePtr) == NULL) {
3540	if (byteCountPtr != NULL) {
3541	    *byteCountPtr = 0;
3542	}
3543	if (mergedLinePtr != NULL) {
3544	    *mergedLinePtr = 0;
3545	}
3546	return 0;
3547    }
3548
3549    /*
3550     * Layout, find the information we need and then free the display-line we
3551     * laid-out. We must use 'FreeDLines' because it will actually call the
3552     * relevant code to unmap any embedded windows which were mapped in the
3553     * LayoutDLine call!
3554     */
3555
3556    dlPtr = LayoutDLine(textPtr, indexPtr);
3557    pixelHeight = dlPtr->height;
3558    if (byteCountPtr != NULL) {
3559	*byteCountPtr = dlPtr->byteCount;
3560    }
3561    if (mergedLinePtr != NULL) {
3562	*mergedLinePtr = dlPtr->logicalLinesMerged;
3563    }
3564    FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP);
3565
3566    return pixelHeight;
3567}
3568
3569/*
3570 *----------------------------------------------------------------------
3571 *
3572 * TkTextIndexYPixels --
3573 *
3574 *	This function is invoked to calculate the number of vertical pixels
3575 *	between the first index of the text widget and the given index. The
3576 *	range from first logical line to given logical line is determined
3577 *	using the cached values, and the range inside the given logical line
3578 *	is calculated on the fly.
3579 *
3580 * Results:
3581 *	The pixel distance between first pixel in the widget and the
3582 *	top of the index's current display line (could be zero).
3583 *
3584 * Side effects:
3585 *	Just those of 'CalculateDisplayLineHeight'.
3586 *
3587 *----------------------------------------------------------------------
3588 */
3589
3590int
3591TkTextIndexYPixels(
3592    TkText *textPtr,		/* Widget record for text widget. */
3593    CONST TkTextIndex *indexPtr)/* The index of which we want the pixel
3594				 * distance from top of logical line to top of
3595				 * index. */
3596{
3597    int pixelHeight;
3598    TkTextIndex index;
3599
3600    pixelHeight = TkBTreePixelsTo(textPtr, indexPtr->linePtr);
3601
3602    /*
3603     * Iterate through all display-lines corresponding to the single logical
3604     * line belonging to indexPtr, adding up the pixel height of each such
3605     * display line as we go along, until we go past 'indexPtr'.
3606     */
3607
3608    if (indexPtr->byteIndex == 0) {
3609	return pixelHeight;
3610    }
3611
3612    index.tree = textPtr->sharedTextPtr->tree;
3613    index.linePtr = indexPtr->linePtr;
3614    index.byteIndex = 0;
3615    index.textPtr = NULL;
3616
3617    while (1) {
3618	int bytes, height;
3619
3620	/*
3621	 * Currently this call doesn't have many side-effects. However, if in
3622	 * the future we change the code so there are side-effects (such as
3623	 * adjusting linePtr->pixelHeight), then the code might not quite work
3624	 * as intended, specifically the 'linePtr->pixelHeight == pixelHeight'
3625	 * test below this while loop.
3626	 */
3627
3628	height = CalculateDisplayLineHeight(textPtr, &index, &bytes, NULL);
3629
3630	index.byteIndex += bytes;
3631
3632	if (index.byteIndex > indexPtr->byteIndex) {
3633	    return pixelHeight;
3634	}
3635
3636	if (height > 0) {
3637	    pixelHeight += height;
3638	}
3639
3640	if (index.byteIndex == indexPtr->byteIndex) {
3641	    return pixelHeight;
3642	}
3643    }
3644}
3645
3646/*
3647 *----------------------------------------------------------------------
3648 *
3649 * TkTextUpdateOneLine --
3650 *
3651 *	This function is invoked to recalculate the height of a particular
3652 *	logical line, whether that line is displayed or not.
3653 *
3654 *	It must NEVER be called for the artificial last TkTextLine which is
3655 *	used internally for administrative purposes only. That line must
3656 *	retain its initial height of 0 otherwise the pixel height calculation
3657 *	maintained by the B-tree will be wrong.
3658 *
3659 * Results:
3660 *	The number of display lines in the logical line. This could be zero if
3661 *	the line is totally elided.
3662 *
3663 * Side effects:
3664 *	Line heights may be recalculated, and a timer to update the scrollbar
3665 *	may be installed. Also see the called function
3666 *	CalculateDisplayLineHeight for its side effects.
3667 *
3668 *----------------------------------------------------------------------
3669 */
3670
3671int
3672TkTextUpdateOneLine(
3673    TkText *textPtr,		/* Widget record for text widget. */
3674    TkTextLine *linePtr,	/* The line of which to calculate the
3675				 * height. */
3676    int pixelHeight,		/* If indexPtr is non-NULL, then this is the
3677				 * number of pixels in the logical line
3678				 * linePtr, up to the index which has been
3679				 * given. */
3680    TkTextIndex *indexPtr,	/* Either NULL or an index at the start of a
3681				 * display line belonging to linePtr, at which
3682				 * we wish to start (e.g. up to which we have
3683				 * already calculated). On return this will be
3684				 * set to the first index on the next line. */
3685    int partialCalc)		/* Set to 1 if we are allowed to do partial
3686				 * height calculations of long-lines. In this
3687				 * case we'll only return what we know so
3688				 * far. */
3689{
3690    TkTextIndex index;
3691    int displayLines;
3692    int mergedLines;
3693
3694    if (indexPtr == NULL) {
3695	index.tree = textPtr->sharedTextPtr->tree;
3696	index.linePtr = linePtr;
3697	index.byteIndex = 0;
3698	index.textPtr = NULL;
3699	indexPtr = &index;
3700	pixelHeight = 0;
3701    }
3702
3703    /*
3704     * Iterate through all display-lines corresponding to the single logical
3705     * line 'linePtr', adding up the pixel height of each such display line as
3706     * we go along. The final total is, therefore, the height of the logical
3707     * line.
3708     */
3709
3710    displayLines = 0;
3711    mergedLines = 0;
3712
3713    while (1) {
3714	int bytes, height, logicalLines;
3715
3716	/*
3717	 * Currently this call doesn't have many side-effects. However, if in
3718	 * the future we change the code so there are side-effects (such as
3719	 * adjusting linePtr->pixelHeight), then the code might not quite work
3720	 * as intended, specifically the 'linePtr->pixelHeight == pixelHeight'
3721	 * test below this while loop.
3722	 */
3723
3724	height = CalculateDisplayLineHeight(textPtr, indexPtr, &bytes,
3725		&logicalLines);
3726
3727	if (height > 0) {
3728	    pixelHeight += height;
3729	    displayLines++;
3730	}
3731
3732	mergedLines += logicalLines;
3733
3734	if (TkTextIndexForwBytes(textPtr, indexPtr, bytes, indexPtr)) {
3735	    break;
3736	}
3737
3738	if (logicalLines == 0) {
3739	    if (indexPtr->linePtr != linePtr) {
3740		/*
3741		 * If we reached the end of the logical line, then either way
3742		 * we don't have a partial calculation.
3743		 */
3744
3745		partialCalc = 0;
3746		break;
3747	    }
3748	} else if (indexPtr->byteIndex != 0) {
3749	    /*
3750	     * We must still be on the same wrapped line.
3751	     */
3752	} else {
3753	    /*
3754	     * Must check if indexPtr is really a new logical line which is
3755	     * not merged with the previous line. The only code that would
3756	     * really know this is LayoutDLine, which doesn't pass the
3757	     * information on, so we have to check manually here.
3758	     */
3759
3760	    TkTextIndex idx;
3761
3762	    TkTextIndexBackChars(textPtr, indexPtr, 1, &idx, COUNT_INDICES);
3763	    if (!TkTextIsElided(textPtr, &idx, NULL)) {
3764		/*
3765		 * We've ended a logical line.
3766		 */
3767
3768		partialCalc = 0;
3769		break;
3770	    }
3771
3772	    /*
3773	     * We must still be on the same wrapped line.
3774	     */
3775	}
3776	if (partialCalc && displayLines > 50 && mergedLines == 0) {
3777	    /*
3778	     * Only calculate 50 display lines at a time, to avoid huge
3779	     * delays. In any case it is very rare that a single line wraps 50
3780	     * times!
3781	     *
3782	     * If we have any merged lines, we must complete the full logical
3783	     * line layout here and now, because the partial-calculation code
3784	     * isn't designed to handle merged logical lines. Hence the
3785	     * 'mergedLines == 0' check.
3786	     */
3787
3788	    break;
3789	}
3790    }
3791
3792    if (!partialCalc) {
3793	int changed = 0;
3794
3795	/*
3796	 * Cancel any partial line height calculation state.
3797	 */
3798
3799	textPtr->dInfoPtr->metricEpoch = -1;
3800
3801	/*
3802	 * Mark the logical line as being up to date (caution: it isn't yet up
3803	 * to date, that will happen in TkBTreeAdjustPixelHeight just below).
3804	 */
3805
3806	TkBTreeLinePixelEpoch(textPtr, linePtr)
3807		= textPtr->dInfoPtr->lineMetricUpdateEpoch;
3808	if (TkBTreeLinePixelCount(textPtr, linePtr) != pixelHeight) {
3809	    changed = 1;
3810	}
3811
3812	if (mergedLines > 0) {
3813	    int i = mergedLines;
3814	    TkTextLine *mergedLinePtr;
3815
3816	    /*
3817	     * Loop over all merged logical lines, marking them up to date
3818	     * (again, the pixel count setting will actually happen in
3819	     * TkBTreeAdjustPixelHeight).
3820	     */
3821
3822	    mergedLinePtr = linePtr;
3823	    while (i-- > 0) {
3824		mergedLinePtr = TkBTreeNextLine(textPtr, mergedLinePtr);
3825		TkBTreeLinePixelEpoch(textPtr, mergedLinePtr)
3826			= textPtr->dInfoPtr->lineMetricUpdateEpoch;
3827		if (TkBTreeLinePixelCount(textPtr, mergedLinePtr) != 0) {
3828		    changed = 1;
3829		}
3830	    }
3831	}
3832
3833	if (!changed) {
3834	    /*
3835	     * If there's nothing to change, then we can already return.
3836	     */
3837
3838	    return displayLines;
3839	}
3840    }
3841
3842    /*
3843     * We set the line's height, but the return value is now the height of the
3844     * entire widget, which may be used just below for reporting/debugging
3845     * purposes.
3846     */
3847
3848    pixelHeight = TkBTreeAdjustPixelHeight(textPtr, linePtr, pixelHeight,
3849	    mergedLines);
3850
3851    if (tkTextDebug) {
3852	char buffer[2 * TCL_INTEGER_SPACE + 1];
3853
3854	if (TkBTreeNextLine(textPtr, linePtr) == NULL) {
3855	    Tcl_Panic("Mustn't ever update line height of last artificial line");
3856	}
3857
3858	sprintf(buffer, "%d %d", TkBTreeLinesTo(textPtr,linePtr), pixelHeight);
3859	LOG("tk_textNumPixels", buffer);
3860    }
3861    if (textPtr->dInfoPtr->scrollbarTimer == NULL) {
3862	textPtr->refCount++;
3863	textPtr->dInfoPtr->scrollbarTimer = Tcl_CreateTimerHandler(200,
3864		AsyncUpdateYScrollbar, (ClientData) textPtr);
3865    }
3866    return displayLines;
3867}
3868
3869/*
3870 *----------------------------------------------------------------------
3871 *
3872 * DisplayText --
3873 *
3874 *	This function is invoked as a when-idle handler to update the display.
3875 *	It only redisplays the parts of the text widget that are out of date.
3876 *
3877 * Results:
3878 *	None.
3879 *
3880 * Side effects:
3881 *	Information is redrawn on the screen.
3882 *
3883 *----------------------------------------------------------------------
3884 */
3885
3886static void
3887DisplayText(
3888    ClientData clientData)	/* Information about widget. */
3889{
3890    register TkText *textPtr = (TkText *) clientData;
3891    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
3892    register DLine *dlPtr;
3893    DLine *prevPtr;
3894    Pixmap pixmap;
3895    int maxHeight, borders;
3896    int bottomY = 0;		/* Initialization needed only to stop compiler
3897				 * warnings. */
3898    Tcl_Interp *interp;
3899
3900    if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)) {
3901	/*
3902	 * The widget has been deleted.	 Don't do anything.
3903	 */
3904
3905	return;
3906    }
3907
3908    interp = textPtr->interp;
3909    Tcl_Preserve((ClientData) interp);
3910
3911    if (tkTextDebug) {
3912	Tcl_SetVar2(interp, "tk_textRelayout", NULL, "", TCL_GLOBAL_ONLY);
3913    }
3914
3915    if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)) {
3916	/*
3917	 * The widget has been deleted.	 Don't do anything.
3918	 */
3919
3920	goto end;
3921    }
3922
3923    if (!Tk_IsMapped(textPtr->tkwin) || (dInfoPtr->maxX <= dInfoPtr->x)
3924	    || (dInfoPtr->maxY <= dInfoPtr->y)) {
3925	UpdateDisplayInfo(textPtr);
3926	dInfoPtr->flags &= ~REDRAW_PENDING;
3927	goto doScrollbars;
3928    }
3929    numRedisplays++;
3930    if (tkTextDebug) {
3931	Tcl_SetVar2(interp, "tk_textRedraw", NULL, "", TCL_GLOBAL_ONLY);
3932    }
3933
3934    if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)) {
3935	/*
3936	 * The widget has been deleted. Don't do anything.
3937	 */
3938
3939	goto end;
3940    }
3941
3942    /*
3943     * Choose a new current item if that is needed (this could cause event
3944     * handlers to be invoked, hence the preserve/release calls and the loop,
3945     * since the handlers could conceivably necessitate yet another current
3946     * item calculation). The tkwin check is because the whole window could go
3947     * away in the Tcl_Release call.
3948     */
3949
3950    while (dInfoPtr->flags & REPICK_NEEDED) {
3951	textPtr->refCount++;
3952	dInfoPtr->flags &= ~REPICK_NEEDED;
3953	TkTextPickCurrent(textPtr, &textPtr->pickEvent);
3954	if (--textPtr->refCount == 0) {
3955	    ckfree((char *) textPtr);
3956	    goto end;
3957	}
3958	if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)) {
3959	    goto end;
3960	}
3961    }
3962
3963    /*
3964     * First recompute what's supposed to be displayed.
3965     */
3966
3967    UpdateDisplayInfo(textPtr);
3968    dInfoPtr->dLinesInvalidated = 0;
3969
3970    /*
3971     * See if it's possible to bring some parts of the screen up-to-date by
3972     * scrolling (copying from other parts of the screen). We have to be
3973     * particularly careful with the top and bottom lines of the display,
3974     * since these may only be partially visible and therefore not helpful for
3975     * some scrolling purposes.
3976     */
3977
3978    for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) {
3979	register DLine *dlPtr2;
3980	int offset, height, y, oldY;
3981	TkRegion damageRgn;
3982
3983	/*
3984	 * These tests are, in order:
3985	 *
3986	 * 1. If the line is already marked as invalid
3987	 * 2. If the line hasn't moved
3988	 * 3. If the line overlaps the bottom of the window and we are
3989	 *    scrolling up.
3990	 * 4. If the line overlaps the top of the window and we are scrolling
3991	 *    down.
3992	 *
3993	 * If any of these tests are true, then we can't scroll this line's
3994	 * part of the display.
3995	 *
3996	 * Note that even if tests 3 or 4 aren't true, we may be able to
3997	 * scroll the line, but we still need to be sure to call embedded
3998	 * window display procs on top and bottom lines if they have any
3999	 * portion non-visible (see below).
4000	 */
4001
4002	if ((dlPtr->flags & OLD_Y_INVALID)
4003		|| (dlPtr->y == dlPtr->oldY)
4004		|| (((dlPtr->oldY + dlPtr->height) > dInfoPtr->maxY)
4005		    && (dlPtr->y < dlPtr->oldY))
4006		|| ((dlPtr->oldY < dInfoPtr->y) && (dlPtr->y > dlPtr->oldY))) {
4007	    continue;
4008	}
4009
4010	/*
4011	 * This line is already drawn somewhere in the window so it only needs
4012	 * to be copied to its new location. See if there's a group of lines
4013	 * that can all be copied together.
4014	 */
4015
4016	offset = dlPtr->y - dlPtr->oldY;
4017	height = dlPtr->height;
4018	y = dlPtr->y;
4019	for (dlPtr2 = dlPtr->nextPtr; dlPtr2 != NULL;
4020		dlPtr2 = dlPtr2->nextPtr) {
4021	    if ((dlPtr2->flags & OLD_Y_INVALID)
4022		    || ((dlPtr2->oldY + offset) != dlPtr2->y)
4023		    || ((dlPtr2->oldY + dlPtr2->height) > dInfoPtr->maxY)) {
4024		break;
4025	    }
4026	    height += dlPtr2->height;
4027	}
4028
4029	/*
4030	 * Reduce the height of the area being copied if necessary to avoid
4031	 * overwriting the border area.
4032	 */
4033
4034	if ((y + height) > dInfoPtr->maxY) {
4035	    height = dInfoPtr->maxY -y;
4036	}
4037	oldY = dlPtr->oldY;
4038	if (y < dInfoPtr->y) {
4039	    /*
4040	     * Adjust if the area being copied is going to overwrite the top
4041	     * border of the window (so the top line is only half onscreen).
4042	     */
4043
4044	    int y_off = dInfoPtr->y - dlPtr->y;
4045	    height -= y_off;
4046	    oldY += y_off;
4047	    y = dInfoPtr->y;
4048	}
4049
4050	/*
4051	 * Update the lines we are going to scroll to show that they have been
4052	 * copied.
4053	 */
4054
4055	while (1) {
4056	    /*
4057	     * The DLine already has OLD_Y_INVALID cleared.
4058	     */
4059
4060	    dlPtr->oldY = dlPtr->y;
4061	    if (dlPtr->nextPtr == dlPtr2) {
4062		break;
4063	    }
4064	    dlPtr = dlPtr->nextPtr;
4065	}
4066
4067	/*
4068	 * Scan through the lines following the copied ones to see if we are
4069	 * going to overwrite them with the copy operation. If so, mark them
4070	 * for redisplay.
4071	 */
4072
4073	for ( ; dlPtr2 != NULL; dlPtr2 = dlPtr2->nextPtr) {
4074	    if ((!(dlPtr2->flags & OLD_Y_INVALID))
4075		    && ((dlPtr2->oldY + dlPtr2->height) > y)
4076		    && (dlPtr2->oldY < (y + height))) {
4077		dlPtr2->flags |= OLD_Y_INVALID;
4078	    }
4079	}
4080
4081	/*
4082	 * Now scroll the lines. This may generate damage which we handle by
4083	 * calling TextInvalidateRegion to mark the display blocks as stale.
4084	 */
4085
4086	damageRgn = TkCreateRegion();
4087	if (TkScrollWindow(textPtr->tkwin, dInfoPtr->scrollGC, dInfoPtr->x,
4088		oldY, dInfoPtr->maxX-dInfoPtr->x, height, 0, y-oldY,
4089		damageRgn)) {
4090	    TextInvalidateRegion(textPtr, damageRgn);
4091	}
4092	numCopies++;
4093	TkDestroyRegion(damageRgn);
4094    }
4095
4096    /*
4097     * Clear the REDRAW_PENDING flag here. This is actually pretty tricky. We
4098     * want to wait until *after* doing the scrolling, since that could
4099     * generate more areas to redraw and don't want to reschedule a redisplay
4100     * for them. On the other hand, we can't wait until after all the
4101     * redisplaying, because the act of redisplaying could actually generate
4102     * more redisplays (e.g. in the case of a nested window with event
4103     * bindings triggered by redisplay).
4104     */
4105
4106    dInfoPtr->flags &= ~REDRAW_PENDING;
4107
4108    /*
4109     * Redraw the borders if that's needed.
4110     */
4111
4112    if (dInfoPtr->flags & REDRAW_BORDERS) {
4113	if (tkTextDebug) {
4114	    LOG("tk_textRedraw", "borders");
4115	}
4116
4117	if (textPtr->tkwin == NULL) {
4118	    /*
4119	     * The widget has been deleted. Don't do anything.
4120	     */
4121
4122	    goto end;
4123	}
4124
4125	Tk_Draw3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),
4126		textPtr->border, textPtr->highlightWidth,
4127		textPtr->highlightWidth,
4128		Tk_Width(textPtr->tkwin) - 2*textPtr->highlightWidth,
4129		Tk_Height(textPtr->tkwin) - 2*textPtr->highlightWidth,
4130		textPtr->borderWidth, textPtr->relief);
4131	if (textPtr->highlightWidth != 0) {
4132	    GC fgGC, bgGC;
4133
4134	    bgGC = Tk_GCForColor(textPtr->highlightBgColorPtr,
4135		    Tk_WindowId(textPtr->tkwin));
4136	    if (textPtr->flags & GOT_FOCUS) {
4137		fgGC = Tk_GCForColor(textPtr->highlightColorPtr,
4138			Tk_WindowId(textPtr->tkwin));
4139		TkpDrawHighlightBorder(textPtr->tkwin, fgGC, bgGC,
4140			textPtr->highlightWidth, Tk_WindowId(textPtr->tkwin));
4141	    } else {
4142		TkpDrawHighlightBorder(textPtr->tkwin, bgGC, bgGC,
4143			textPtr->highlightWidth, Tk_WindowId(textPtr->tkwin));
4144	    }
4145	}
4146	borders = textPtr->borderWidth + textPtr->highlightWidth;
4147	if (textPtr->padY > 0) {
4148	    Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),
4149		    textPtr->border, borders, borders,
4150		    Tk_Width(textPtr->tkwin) - 2*borders, textPtr->padY,
4151		    0, TK_RELIEF_FLAT);
4152	    Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),
4153		    textPtr->border, borders,
4154		    Tk_Height(textPtr->tkwin) - borders - textPtr->padY,
4155		    Tk_Width(textPtr->tkwin) - 2*borders,
4156		    textPtr->padY, 0, TK_RELIEF_FLAT);
4157	}
4158	if (textPtr->padX > 0) {
4159	    Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),
4160		    textPtr->border, borders, borders + textPtr->padY,
4161		    textPtr->padX,
4162		    Tk_Height(textPtr->tkwin) - 2*borders -2*textPtr->padY,
4163		    0, TK_RELIEF_FLAT);
4164	    Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),
4165		    textPtr->border,
4166		    Tk_Width(textPtr->tkwin) - borders - textPtr->padX,
4167		    borders + textPtr->padY, textPtr->padX,
4168		    Tk_Height(textPtr->tkwin) - 2*borders -2*textPtr->padY,
4169		    0, TK_RELIEF_FLAT);
4170	}
4171	dInfoPtr->flags &= ~REDRAW_BORDERS;
4172    }
4173
4174    /*
4175     * Now we have to redraw the lines that couldn't be updated by scrolling.
4176     * First, compute the height of the largest line and allocate an off-
4177     * screen pixmap to use for double-buffered displays.
4178     */
4179
4180    maxHeight = -1;
4181    for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
4182	    dlPtr = dlPtr->nextPtr) {
4183	if ((dlPtr->height > maxHeight) &&
4184		((dlPtr->flags&OLD_Y_INVALID) || (dlPtr->oldY != dlPtr->y))) {
4185	    maxHeight = dlPtr->height;
4186	}
4187	bottomY = dlPtr->y + dlPtr->height;
4188    }
4189
4190    /*
4191     * There used to be a line here which restricted 'maxHeight' to be no
4192     * larger than 'dInfoPtr->maxY', but this is incorrect for the case where
4193     * individual lines may be taller than the widget _and_ we have smooth
4194     * scrolling. What we can do is restrict maxHeight to be no larger than
4195     * 'dInfoPtr->maxY + dInfoPtr->topPixelOffset'.
4196     */
4197
4198    if (maxHeight > (dInfoPtr->maxY + dInfoPtr->topPixelOffset)) {
4199	maxHeight = (dInfoPtr->maxY + dInfoPtr->topPixelOffset);
4200    }
4201
4202    if (maxHeight > 0) {
4203#ifndef TK_NO_DOUBLE_BUFFERING
4204	pixmap = Tk_GetPixmap(Tk_Display(textPtr->tkwin),
4205		Tk_WindowId(textPtr->tkwin), Tk_Width(textPtr->tkwin),
4206		maxHeight, Tk_Depth(textPtr->tkwin));
4207#else
4208	pixmap = Tk_WindowId(textPtr->tkwin);
4209#endif /* TK_NO_DOUBLE_BUFFERING */
4210	for (prevPtr = NULL, dlPtr = textPtr->dInfoPtr->dLinePtr;
4211		(dlPtr != NULL) && (dlPtr->y < dInfoPtr->maxY);
4212		prevPtr = dlPtr, dlPtr = dlPtr->nextPtr) {
4213	    if (dlPtr->chunkPtr == NULL) {
4214		continue;
4215	    }
4216	    if ((dlPtr->flags & OLD_Y_INVALID) || dlPtr->oldY != dlPtr->y) {
4217		if (tkTextDebug) {
4218		    char string[TK_POS_CHARS];
4219
4220		    TkTextPrintIndex(textPtr, &dlPtr->index, string);
4221		    LOG("tk_textRedraw", string);
4222		}
4223		DisplayDLine(textPtr, dlPtr, prevPtr, pixmap);
4224		if (dInfoPtr->dLinesInvalidated) {
4225#ifndef TK_NO_DOUBLE_BUFFERING
4226		    Tk_FreePixmap(Tk_Display(textPtr->tkwin), pixmap);
4227#endif /* TK_NO_DOUBLE_BUFFERING */
4228		    return;
4229		}
4230		dlPtr->oldY = dlPtr->y;
4231		dlPtr->flags &= ~(NEW_LAYOUT | OLD_Y_INVALID);
4232	    } else if (dlPtr->chunkPtr != NULL && ((dlPtr->y < 0)
4233		    || (dlPtr->y + dlPtr->height > dInfoPtr->maxY))) {
4234		register TkTextDispChunk *chunkPtr;
4235
4236		/*
4237		 * It's the first or last DLine which are also overlapping the
4238		 * top or bottom of the window, but we decided above it wasn't
4239		 * necessary to display them (we were able to update them by
4240		 * scrolling). This is fine, except that if the lines contain
4241		 * any embedded windows, we must still call the display proc
4242		 * on them because they might need to be unmapped or they
4243		 * might need to be moved to reflect their new position.
4244		 * Otherwise, everything else moves, but the embedded window
4245		 * doesn't!
4246		 *
4247		 * So, we loop through all the chunks, calling the display
4248		 * proc of embedded windows only.
4249		 */
4250
4251		for (chunkPtr = dlPtr->chunkPtr; (chunkPtr != NULL);
4252			chunkPtr = chunkPtr->nextPtr) {
4253		    int x;
4254		    if (chunkPtr->displayProc != TkTextEmbWinDisplayProc) {
4255			continue;
4256		    }
4257		    x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curXPixelOffset;
4258		    if ((x + chunkPtr->width <= 0) || (x >= dInfoPtr->maxX)) {
4259			/*
4260			 * Note: we have to call the displayProc even for
4261			 * chunks that are off-screen. This is needed, for
4262			 * example, so that embedded windows can be unmapped
4263			 * in this case. Display the chunk at a coordinate
4264			 * that can be clearly identified by the displayProc
4265			 * as being off-screen to the left (the displayProc
4266			 * may not be able to tell if something is off to the
4267			 * right).
4268			 */
4269
4270			x = -chunkPtr->width;
4271		    }
4272		    TkTextEmbWinDisplayProc(textPtr, chunkPtr, x,
4273			    dlPtr->spaceAbove,
4274			    dlPtr->height-dlPtr->spaceAbove-dlPtr->spaceBelow,
4275			    dlPtr->baseline - dlPtr->spaceAbove, NULL,
4276			    (Drawable) None, dlPtr->y + dlPtr->spaceAbove);
4277		}
4278
4279	    }
4280	}
4281#ifndef TK_NO_DOUBLE_BUFFERING
4282	Tk_FreePixmap(Tk_Display(textPtr->tkwin), pixmap);
4283#endif /* TK_NO_DOUBLE_BUFFERING */
4284    }
4285
4286    /*
4287     * See if we need to refresh the part of the window below the last line of
4288     * text (if there is any such area). Refresh the padding area on the left
4289     * too, since the insertion cursor might have been displayed there
4290     * previously).
4291     */
4292
4293    if (dInfoPtr->topOfEof > dInfoPtr->maxY) {
4294	dInfoPtr->topOfEof = dInfoPtr->maxY;
4295    }
4296    if (bottomY < dInfoPtr->topOfEof) {
4297	if (tkTextDebug) {
4298	    LOG("tk_textRedraw", "eof");
4299	}
4300
4301	if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)) {
4302	    /*
4303	     * The widget has been deleted. Don't do anything.
4304	     */
4305
4306	    goto end;
4307	}
4308
4309	Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),
4310		textPtr->border, dInfoPtr->x - textPtr->padX, bottomY,
4311		dInfoPtr->maxX - (dInfoPtr->x - textPtr->padX),
4312		dInfoPtr->topOfEof-bottomY, 0, TK_RELIEF_FLAT);
4313    }
4314    dInfoPtr->topOfEof = bottomY;
4315
4316    /*
4317     * Update the vertical scrollbar, if there is one. Note: it's important to
4318     * clear REDRAW_PENDING here, just in case the scroll function does
4319     * something that requires redisplay.
4320     */
4321
4322  doScrollbars:
4323    if (textPtr->flags & UPDATE_SCROLLBARS) {
4324	textPtr->flags &= ~UPDATE_SCROLLBARS;
4325	if (textPtr->yScrollCmd != NULL) {
4326	    GetYView(textPtr->interp, textPtr, 1);
4327	}
4328
4329	if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)) {
4330	    /*
4331	     * The widget has been deleted. Don't do anything.
4332	     */
4333
4334	    goto end;
4335	}
4336
4337	/*
4338	 * Update the horizontal scrollbar, if any.
4339	 */
4340
4341	if (textPtr->xScrollCmd != NULL) {
4342	    GetXView(textPtr->interp, textPtr, 1);
4343	}
4344    }
4345
4346  end:
4347    Tcl_Release((ClientData) interp);
4348}
4349
4350/*
4351 *----------------------------------------------------------------------
4352 *
4353 * TkTextEventuallyRepick --
4354 *
4355 *	This function is invoked whenever something happens that could change
4356 *	the current character or the tags associated with it.
4357 *
4358 * Results:
4359 *	None.
4360 *
4361 * Side effects:
4362 *	A repick is scheduled as an idle handler.
4363 *
4364 *----------------------------------------------------------------------
4365 */
4366
4367void
4368TkTextEventuallyRepick(
4369    TkText *textPtr)		/* Widget record for text widget. */
4370{
4371    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
4372
4373    dInfoPtr->flags |= REPICK_NEEDED;
4374    if (!(dInfoPtr->flags & REDRAW_PENDING)) {
4375	dInfoPtr->flags |= REDRAW_PENDING;
4376	Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);
4377    }
4378}
4379
4380/*
4381 *----------------------------------------------------------------------
4382 *
4383 * TkTextRedrawRegion --
4384 *
4385 *	This function is invoked to schedule a redisplay for a given region of
4386 *	a text widget. The redisplay itself may not occur immediately: it's
4387 *	scheduled as a when-idle handler.
4388 *
4389 * Results:
4390 *	None.
4391 *
4392 * Side effects:
4393 *	Information will eventually be redrawn on the screen.
4394 *
4395 *----------------------------------------------------------------------
4396 */
4397
4398void
4399TkTextRedrawRegion(
4400    TkText *textPtr,		/* Widget record for text widget. */
4401    int x, int y,		/* Coordinates of upper-left corner of area to
4402				 * be redrawn, in pixels relative to textPtr's
4403				 * window. */
4404    int width, int height)	/* Width and height of area to be redrawn. */
4405{
4406    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
4407    TkRegion damageRgn = TkCreateRegion();
4408    XRectangle rect;
4409
4410    rect.x = x;
4411    rect.y = y;
4412    rect.width = width;
4413    rect.height = height;
4414    TkUnionRectWithRegion(&rect, damageRgn, damageRgn);
4415
4416    TextInvalidateRegion(textPtr, damageRgn);
4417
4418    if (!(dInfoPtr->flags & REDRAW_PENDING)) {
4419	dInfoPtr->flags |= REDRAW_PENDING;
4420	Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);
4421    }
4422    TkDestroyRegion(damageRgn);
4423}
4424
4425/*
4426 *----------------------------------------------------------------------
4427 *
4428 * TextInvalidateRegion --
4429 *
4430 *	Mark a region of text as invalid.
4431 *
4432 * Results:
4433 *	None.
4434 *
4435 * Side effects:
4436 *	Updates the display information for the text widget.
4437 *
4438 *----------------------------------------------------------------------
4439 */
4440
4441static void
4442TextInvalidateRegion(
4443    TkText *textPtr,		/* Widget record for text widget. */
4444    TkRegion region)		/* Region of area to redraw. */
4445{
4446    register DLine *dlPtr;
4447    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
4448    int maxY, inset;
4449    XRectangle rect;
4450
4451    /*
4452     * Find all lines that overlap the given region and mark them for
4453     * redisplay.
4454     */
4455
4456    TkClipBox(region, &rect);
4457    maxY = rect.y + rect.height;
4458    for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
4459	    dlPtr = dlPtr->nextPtr) {
4460	if ((!(dlPtr->flags & OLD_Y_INVALID))
4461		&& (TkRectInRegion(region, rect.x, dlPtr->y,
4462		rect.width, (unsigned int) dlPtr->height) != RectangleOut)) {
4463	    dlPtr->flags |= OLD_Y_INVALID;
4464	}
4465    }
4466    if (dInfoPtr->topOfEof < maxY) {
4467	dInfoPtr->topOfEof = maxY;
4468    }
4469
4470    /*
4471     * Schedule the redisplay operation if there isn't one already scheduled.
4472     */
4473
4474    inset = textPtr->borderWidth + textPtr->highlightWidth;
4475    if ((rect.x < (inset + textPtr->padX))
4476	    || (rect.y < (inset + textPtr->padY))
4477	    || ((int) (rect.x + rect.width) > (Tk_Width(textPtr->tkwin)
4478		    - inset - textPtr->padX))
4479	    || (maxY > (Tk_Height(textPtr->tkwin) - inset - textPtr->padY))) {
4480	dInfoPtr->flags |= REDRAW_BORDERS;
4481    }
4482}
4483
4484/*
4485 *----------------------------------------------------------------------
4486 *
4487 * TkTextChanged, TextChanged --
4488 *
4489 *	This function is invoked when info in a text widget is about to be
4490 *	modified in a way that changes how it is displayed (e.g. characters
4491 *	were inserted or deleted, or tag information was changed). This
4492 *	function must be called *before* a change is made, so that indexes in
4493 *	the display information are still valid.
4494 *
4495 *	Note: if the range of indices may change geometry as well as simply
4496 *	requiring redisplay, then the caller should also call
4497 *	TkTextInvalidateLineMetrics.
4498 *
4499 * Results:
4500 *	None.
4501 *
4502 * Side effects:
4503 *	The range of character between index1Ptr (inclusive) and index2Ptr
4504 *	(exclusive) will be redisplayed at some point in the future (the
4505 *	actual redisplay is scheduled as a when-idle handler).
4506 *
4507 *----------------------------------------------------------------------
4508 */
4509
4510void
4511TkTextChanged(
4512    TkSharedText *sharedTextPtr,/* Shared widget section, or NULL. */
4513    TkText *textPtr,		/* Widget record for text widget, or NULL. */
4514    CONST TkTextIndex*index1Ptr,/* Index of first character to redisplay. */
4515    CONST TkTextIndex*index2Ptr)/* Index of character just after last one to
4516				 * redisplay. */
4517{
4518    if (sharedTextPtr == NULL) {
4519	TextChanged(textPtr, index1Ptr, index2Ptr);
4520    } else {
4521	textPtr = sharedTextPtr->peers;
4522	while (textPtr != NULL) {
4523	    TextChanged(textPtr, index1Ptr, index2Ptr);
4524	    textPtr = textPtr->next;
4525	}
4526    }
4527}
4528
4529static void
4530TextChanged(
4531    TkText *textPtr,		/* Widget record for text widget, or NULL. */
4532    CONST TkTextIndex*index1Ptr,/* Index of first character to redisplay. */
4533    CONST TkTextIndex*index2Ptr)/* Index of character just after last one to
4534				 * redisplay. */
4535{
4536    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
4537    DLine *firstPtr, *lastPtr;
4538    TkTextIndex rounded;
4539
4540    /*
4541     * Schedule both a redisplay and a recomputation of display information.
4542     * It's done here rather than the end of the function for two reasons:
4543     *
4544     * 1. If there are no display lines to update we'll want to return
4545     *	  immediately, well before the end of the function.
4546     * 2. It's important to arrange for the redisplay BEFORE calling
4547     *	  FreeDLines. The reason for this is subtle and has to do with
4548     *	  embedded windows. The chunk delete function for an embedded window
4549     *	  will schedule an idle handler to unmap the window. However, we want
4550     *	  the idle handler for redisplay to be called first, so that it can
4551     *	  put the embedded window back on the screen again (if appropriate).
4552     *	  This will prevent the window from ever being unmapped, and thereby
4553     *	  avoid flashing.
4554     */
4555
4556    if (!(dInfoPtr->flags & REDRAW_PENDING)) {
4557	Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);
4558    }
4559    dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;
4560
4561    /*
4562     * Find the DLines corresponding to index1Ptr and index2Ptr. There is one
4563     * tricky thing here, which is that we have to relayout in units of whole
4564     * text lines: round index1Ptr back to the beginning of its text line, and
4565     * include all the display lines after index2, up to the end of its text
4566     * line. This is necessary because the indices stored in the display lines
4567     * will no longer be valid. It's also needed because any edit could change
4568     * the way lines wrap.
4569     */
4570
4571    rounded = *index1Ptr;
4572    rounded.byteIndex = 0;
4573    firstPtr = FindDLine(dInfoPtr->dLinePtr, &rounded);
4574    if (firstPtr == NULL) {
4575	return;
4576    }
4577    lastPtr = FindDLine(dInfoPtr->dLinePtr, index2Ptr);
4578    while ((lastPtr != NULL)
4579	    && (lastPtr->index.linePtr == index2Ptr->linePtr)) {
4580	lastPtr = lastPtr->nextPtr;
4581    }
4582
4583    /*
4584     * Delete all the DLines from firstPtr up to but not including lastPtr.
4585     */
4586
4587    FreeDLines(textPtr, firstPtr, lastPtr, DLINE_UNLINK);
4588}
4589
4590/*
4591 *----------------------------------------------------------------------
4592 *
4593 * TkTextRedrawTag, TextRedrawTag --
4594 *
4595 *	This function is invoked to request a redraw of all characters in a
4596 *	given range that have a particular tag on or off. It's called, for
4597 *	example, when tag options change.
4598 *
4599 * Results:
4600 *	None.
4601 *
4602 * Side effects:
4603 *	Information on the screen may be redrawn, and the layout of the screen
4604 *	may change.
4605 *
4606 *----------------------------------------------------------------------
4607 */
4608
4609void
4610TkTextRedrawTag(
4611    TkSharedText *sharedTextPtr,/* Shared widget section, or NULL. */
4612    TkText *textPtr,		/* Widget record for text widget. */
4613    TkTextIndex *index1Ptr,	/* First character in range to consider for
4614				 * redisplay. NULL means start at beginning of
4615				 * text. */
4616    TkTextIndex *index2Ptr,	/* Character just after last one to consider
4617				 * for redisplay. NULL means process all the
4618				 * characters in the text. */
4619    TkTextTag *tagPtr,		/* Information about tag. */
4620    int withTag)		/* 1 means redraw characters that have the
4621				 * tag, 0 means redraw those without. */
4622{
4623    if (sharedTextPtr == NULL) {
4624	TextRedrawTag(textPtr, index1Ptr, index2Ptr, tagPtr, withTag);
4625    } else {
4626	textPtr = sharedTextPtr->peers;
4627	while (textPtr != NULL) {
4628	    TextRedrawTag(textPtr, index1Ptr, index2Ptr, tagPtr, withTag);
4629	    textPtr = textPtr->next;
4630	}
4631    }
4632}
4633
4634static void
4635TextRedrawTag(
4636    TkText *textPtr,		/* Widget record for text widget. */
4637    TkTextIndex *index1Ptr,	/* First character in range to consider for
4638				 * redisplay. NULL means start at beginning of
4639				 * text. */
4640    TkTextIndex *index2Ptr,	/* Character just after last one to consider
4641				 * for redisplay. NULL means process all the
4642				 * characters in the text. */
4643    TkTextTag *tagPtr,		/* Information about tag. */
4644    int withTag)		/* 1 means redraw characters that have the
4645				 * tag, 0 means redraw those without. */
4646{
4647    register DLine *dlPtr;
4648    DLine *endPtr;
4649    int tagOn;
4650    TkTextSearch search;
4651    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
4652    TkTextIndex *curIndexPtr;
4653    TkTextIndex endOfText, *endIndexPtr;
4654
4655    /*
4656     * Invalidate the pixel calculation of all lines in the given range. This
4657     * may be a bit over-aggressive, so we could consider more subtle
4658     * techniques here in the future. In particular, when we create a tag for
4659     * the first time with '.t tag configure foo -font "Arial 20"', say, even
4660     * though that obviously can't apply to anything at all (the tag didn't
4661     * exist a moment ago), we invalidate every single line in the widget.
4662     */
4663
4664    if (tagPtr->affectsDisplayGeometry) {
4665	TkTextLine *startLine, *endLine;
4666	int lineCount;
4667
4668	if (index2Ptr == NULL) {
4669	    endLine = NULL;
4670	    lineCount = TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr);
4671	} else {
4672	    endLine = index2Ptr->linePtr;
4673	    lineCount = TkBTreeLinesTo(textPtr, endLine);
4674	}
4675	if (index1Ptr == NULL) {
4676	    startLine = NULL;
4677	} else {
4678	    startLine = index1Ptr->linePtr;
4679	    lineCount -= TkBTreeLinesTo(textPtr, startLine);
4680	}
4681	TkTextInvalidateLineMetrics(NULL, textPtr, startLine, lineCount,
4682		TK_TEXT_INVALIDATE_ONLY);
4683    }
4684
4685    /*
4686     * Round up the starting position if it's before the first line visible on
4687     * the screen (we only care about what's on the screen).
4688     */
4689
4690    dlPtr = dInfoPtr->dLinePtr;
4691    if (dlPtr == NULL) {
4692	return;
4693    }
4694    if ((index1Ptr == NULL) || (TkTextIndexCmp(&dlPtr->index, index1Ptr)>0)) {
4695	index1Ptr = &dlPtr->index;
4696    }
4697
4698    /*
4699     * Set the stopping position if it wasn't specified.
4700     */
4701
4702    if (index2Ptr == NULL) {
4703	int lastLine = TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr);
4704
4705	index2Ptr = TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr,
4706		lastLine, 0, &endOfText);
4707    }
4708
4709    /*
4710     * Initialize a search through all transitions on the tag, starting with
4711     * the first transition where the tag's current state is different from
4712     * what it will eventually be.
4713     */
4714
4715    TkBTreeStartSearch(index1Ptr, index2Ptr, tagPtr, &search);
4716
4717    /*
4718     * Make our own curIndex because at this point search.curIndex may not
4719     * equal index1Ptr->curIndex in the case the first tag toggle comes after
4720     * index1Ptr (See the use of FindTagStart in TkBTreeStartSearch).
4721     */
4722
4723    curIndexPtr = index1Ptr;
4724    tagOn = TkBTreeCharTagged(index1Ptr, tagPtr);
4725    if (tagOn != withTag) {
4726	if (!TkBTreeNextTag(&search)) {
4727	    return;
4728	}
4729	curIndexPtr = &search.curIndex;
4730    }
4731
4732    /*
4733     * Schedule a redisplay and layout recalculation if they aren't already
4734     * pending. This has to be done before calling FreeDLines, for the reason
4735     * given in TkTextChanged.
4736     */
4737
4738    if (!(dInfoPtr->flags & REDRAW_PENDING)) {
4739	Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);
4740    }
4741    dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;
4742
4743    /*
4744     * Each loop through the loop below is for one range of characters where
4745     * the tag's current state is different than its eventual state. At the
4746     * top of the loop, search contains information about the first character
4747     * in the range.
4748     */
4749
4750    while (1) {
4751	/*
4752	 * Find the first DLine structure in the range. Note: if the desired
4753	 * character isn't the first in its text line, then look for the
4754	 * character just before it instead. This is needed to handle the case
4755	 * where the first character of a wrapped display line just got
4756	 * smaller, so that it now fits on the line before: need to relayout
4757	 * the line containing the previous character.
4758	 */
4759
4760	if (curIndexPtr->byteIndex == 0) {
4761	    dlPtr = FindDLine(dlPtr, curIndexPtr);
4762	} else {
4763	    TkTextIndex tmp;
4764
4765	    tmp = *curIndexPtr;
4766	    tmp.byteIndex -= 1;
4767	    dlPtr = FindDLine(dlPtr, &tmp);
4768	}
4769	if (dlPtr == NULL) {
4770	    break;
4771	}
4772
4773	/*
4774	 * Find the first DLine structure that's past the end of the range.
4775	 */
4776
4777	if (!TkBTreeNextTag(&search)) {
4778	    endIndexPtr = index2Ptr;
4779	} else {
4780	    curIndexPtr = &search.curIndex;
4781	    endIndexPtr = curIndexPtr;
4782	}
4783	endPtr = FindDLine(dlPtr, endIndexPtr);
4784	if ((endPtr != NULL) && (endPtr->index.linePtr == endIndexPtr->linePtr)
4785		&& (endPtr->index.byteIndex < endIndexPtr->byteIndex)) {
4786	    endPtr = endPtr->nextPtr;
4787	}
4788
4789	/*
4790	 * Delete all of the display lines in the range, so that they'll be
4791	 * re-layed out and redrawn.
4792	 */
4793
4794	FreeDLines(textPtr, dlPtr, endPtr, DLINE_UNLINK);
4795	dlPtr = endPtr;
4796
4797	/*
4798	 * Find the first text line in the next range.
4799	 */
4800
4801	if (!TkBTreeNextTag(&search)) {
4802	    break;
4803	}
4804    }
4805}
4806
4807/*
4808 *----------------------------------------------------------------------
4809 *
4810 * TkTextRelayoutWindow --
4811 *
4812 *	This function is called when something has happened that invalidates
4813 *	the whole layout of characters on the screen, such as a change in a
4814 *	configuration option for the overall text widget or a change in the
4815 *	window size. It causes all display information to be recomputed and
4816 *	the window to be redrawn.
4817 *
4818 * Results:
4819 *	None.
4820 *
4821 * Side effects:
4822 *	All the display information will be recomputed for the window and the
4823 *	window will be redrawn.
4824 *
4825 *----------------------------------------------------------------------
4826 */
4827
4828void
4829TkTextRelayoutWindow(
4830    TkText *textPtr,		/* Widget record for text widget. */
4831    int mask)			/* OR'd collection of bits showing what has
4832				 * changed. */
4833{
4834    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
4835    GC newGC;
4836    XGCValues gcValues;
4837
4838    /*
4839     * Schedule the window redisplay. See TkTextChanged for the reason why
4840     * this has to be done before any calls to FreeDLines.
4841     */
4842
4843    if (!(dInfoPtr->flags & REDRAW_PENDING)) {
4844	Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);
4845    }
4846    dInfoPtr->flags |= REDRAW_PENDING|REDRAW_BORDERS|DINFO_OUT_OF_DATE
4847	    |REPICK_NEEDED;
4848
4849    /*
4850     * (Re-)create the graphics context for drawing the traversal highlight.
4851     */
4852
4853    gcValues.graphics_exposures = False;
4854    newGC = Tk_GetGC(textPtr->tkwin, GCGraphicsExposures, &gcValues);
4855    if (dInfoPtr->copyGC != None) {
4856	Tk_FreeGC(textPtr->display, dInfoPtr->copyGC);
4857    }
4858    dInfoPtr->copyGC = newGC;
4859
4860    /*
4861     * Throw away all the current layout information.
4862     */
4863
4864    FreeDLines(textPtr, dInfoPtr->dLinePtr, NULL, DLINE_UNLINK);
4865    dInfoPtr->dLinePtr = NULL;
4866
4867    /*
4868     * Recompute some overall things for the layout. Even if the window gets
4869     * very small, pretend that there's at least one pixel of drawing space in
4870     * it.
4871     */
4872
4873    if (textPtr->highlightWidth < 0) {
4874	textPtr->highlightWidth = 0;
4875    }
4876    dInfoPtr->x = textPtr->highlightWidth + textPtr->borderWidth
4877	    + textPtr->padX;
4878    dInfoPtr->y = textPtr->highlightWidth + textPtr->borderWidth
4879	    + textPtr->padY;
4880    dInfoPtr->maxX = Tk_Width(textPtr->tkwin) - textPtr->highlightWidth
4881	    - textPtr->borderWidth - textPtr->padX;
4882    if (dInfoPtr->maxX <= dInfoPtr->x) {
4883	dInfoPtr->maxX = dInfoPtr->x + 1;
4884    }
4885
4886    /*
4887     * This is the only place where dInfoPtr->maxY is set.
4888     */
4889
4890    dInfoPtr->maxY = Tk_Height(textPtr->tkwin) - textPtr->highlightWidth
4891	    - textPtr->borderWidth - textPtr->padY;
4892    if (dInfoPtr->maxY <= dInfoPtr->y) {
4893	dInfoPtr->maxY = dInfoPtr->y + 1;
4894    }
4895    dInfoPtr->topOfEof = dInfoPtr->maxY;
4896
4897    /*
4898     * If the upper-left character isn't the first in a line, recompute it.
4899     * This is necessary because a change in the window's size or options
4900     * could change the way lines wrap.
4901     */
4902
4903    if (textPtr->topIndex.byteIndex != 0) {
4904	TkTextFindDisplayLineEnd(textPtr, &textPtr->topIndex, 0, NULL);
4905    }
4906
4907    /*
4908     * Invalidate cached scrollbar positions, so that scrollbars sliders will
4909     * be udpated.
4910     */
4911
4912    dInfoPtr->xScrollFirst = dInfoPtr->xScrollLast = -1;
4913    dInfoPtr->yScrollFirst = dInfoPtr->yScrollLast = -1;
4914
4915    if (mask & TK_TEXT_LINE_GEOMETRY) {
4916	/*
4917	 * Set up line metric recalculation.
4918	 *
4919	 * Avoid the special zero value, since that is used to mark individual
4920	 * lines as being out of date.
4921	 */
4922
4923	if ((++dInfoPtr->lineMetricUpdateEpoch) == 0) {
4924	    dInfoPtr->lineMetricUpdateEpoch++;
4925	}
4926
4927	dInfoPtr->currentMetricUpdateLine = -1;
4928
4929	/*
4930	 * Also cancel any partial line-height calculations (for long-wrapped
4931	 * lines) in progress.
4932	 */
4933
4934	dInfoPtr->metricEpoch = -1;
4935
4936	if (dInfoPtr->lineUpdateTimer == NULL) {
4937	    textPtr->refCount++;
4938	    dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1,
4939		    AsyncUpdateLineMetrics, (ClientData) textPtr);
4940	}
4941    }
4942}
4943
4944/*
4945 *----------------------------------------------------------------------
4946 *
4947 * TkTextSetYView --
4948 *
4949 *	This function is called to specify what lines are to be displayed in a
4950 *	text widget.
4951 *
4952 * Results:
4953 *	None.
4954 *
4955 * Side effects:
4956 *	The display will (eventually) be updated so that the position given by
4957 *	"indexPtr" is visible on the screen at the position determined by
4958 *	"pickPlace".
4959 *
4960 *----------------------------------------------------------------------
4961 */
4962
4963void
4964TkTextSetYView(
4965    TkText *textPtr,		/* Widget record for text widget. */
4966    TkTextIndex *indexPtr,	/* Position that is to appear somewhere in the
4967				 * view. */
4968    int pickPlace)		/* 0 means the given index must appear exactly
4969				 * at the top of the screen. TK_TEXT_PICKPLACE
4970				 * (-1) means we get to pick where it appears:
4971				 * minimize screen motion or else display line
4972				 * at center of screen. TK_TEXT_NOPIXELADJUST
4973				 * (-2) indicates to make the given index the
4974				 * top line, but if it is already the top
4975				 * line, don't nudge it up or down by a few
4976				 * pixels just to make sure it is entirely
4977				 * displayed. Positive numbers indicate the
4978				 * number of pixels of the index's line which
4979				 * are to be off the top of the screen. */
4980{
4981    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
4982    register DLine *dlPtr;
4983    int bottomY, close, lineIndex;
4984    TkTextIndex tmpIndex, rounded;
4985    int lineHeight;
4986
4987    /*
4988     * If the specified position is the extra line at the end of the text,
4989     * round it back to the last real line.
4990     */
4991
4992    lineIndex = TkBTreeLinesTo(textPtr, indexPtr->linePtr);
4993    if (lineIndex == TkBTreeNumLines(indexPtr->tree, textPtr)) {
4994	TkTextIndexBackChars(textPtr, indexPtr, 1, &rounded, COUNT_INDICES);
4995	indexPtr = &rounded;
4996    }
4997
4998    if (pickPlace == TK_TEXT_NOPIXELADJUST) {
4999	if (textPtr->topIndex.linePtr == indexPtr->linePtr
5000		&& textPtr->topIndex.byteIndex == indexPtr->byteIndex) {
5001	    pickPlace = dInfoPtr->topPixelOffset;
5002	} else {
5003	    pickPlace = 0;
5004	}
5005    }
5006
5007    if (pickPlace != TK_TEXT_PICKPLACE) {
5008	/*
5009	 * The specified position must go at the top of the screen. Just leave
5010	 * all the DLine's alone: we may be able to reuse some of the
5011	 * information that's currently on the screen without redisplaying it
5012	 * all.
5013	 */
5014
5015	textPtr->topIndex = *indexPtr;
5016	if (indexPtr->byteIndex != 0) {
5017	    TkTextFindDisplayLineEnd(textPtr, &textPtr->topIndex, 0, NULL);
5018	}
5019	dInfoPtr->newTopPixelOffset = pickPlace;
5020	goto scheduleUpdate;
5021    }
5022
5023    /*
5024     * We have to pick where to display the index. First, bring the display
5025     * information up to date and see if the index will be completely visible
5026     * in the current screen configuration. If so then there's nothing to do.
5027     */
5028
5029    if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
5030	UpdateDisplayInfo(textPtr);
5031    }
5032    dlPtr = FindDLine(dInfoPtr->dLinePtr, indexPtr);
5033    if (dlPtr != NULL) {
5034	if ((dlPtr->y + dlPtr->height) > dInfoPtr->maxY) {
5035	    /*
5036	     * Part of the line hangs off the bottom of the screen; pretend
5037	     * the whole line is off-screen.
5038	     */
5039
5040	    dlPtr = NULL;
5041	} else if ((dlPtr->index.linePtr == indexPtr->linePtr)
5042		&& (dlPtr->index.byteIndex <= indexPtr->byteIndex)) {
5043	    if (dInfoPtr->dLinePtr == dlPtr && dInfoPtr->topPixelOffset != 0) {
5044		/*
5045		 * It is on the top line, but that line is hanging off the top
5046		 * of the screen. Change the top overlap to zero and update.
5047		 */
5048
5049		dInfoPtr->newTopPixelOffset = 0;
5050		goto scheduleUpdate;
5051	    }
5052	    return;
5053	}
5054    }
5055
5056    /*
5057     * The desired line isn't already on-screen. Figure out what it means to
5058     * be "close" to the top or bottom of the screen. Close means within 1/3
5059     * of the screen height or within three lines, whichever is greater.
5060     *
5061     * If the line is not close, place it in the center of the window.
5062     */
5063
5064    lineHeight = CalculateDisplayLineHeight(textPtr, indexPtr, NULL, NULL);
5065
5066    /*
5067     * It would be better if 'bottomY' were calculated using the actual height
5068     * of the given line, not 'textPtr->charHeight'.
5069     */
5070
5071    bottomY = (dInfoPtr->y + dInfoPtr->maxY + lineHeight)/2;
5072    close = (dInfoPtr->maxY - dInfoPtr->y)/3;
5073    if (close < 3*textPtr->charHeight) {
5074	close = 3*textPtr->charHeight;
5075    }
5076    if (dlPtr != NULL) {
5077	int overlap;
5078
5079	/*
5080	 * The desired line is above the top of screen. If it is "close" to
5081	 * the top of the window then make it the top line on the screen.
5082	 * MeasureUp counts from the bottom of the given index upwards, so we
5083	 * add an extra half line to be sure we count far enough.
5084	 */
5085
5086	MeasureUp(textPtr, &textPtr->topIndex, close + textPtr->charHeight/2,
5087		&tmpIndex, &overlap);
5088	if (TkTextIndexCmp(&tmpIndex, indexPtr) <= 0) {
5089	    textPtr->topIndex = *indexPtr;
5090	    TkTextFindDisplayLineEnd(textPtr, &textPtr->topIndex, 0, NULL);
5091	    dInfoPtr->newTopPixelOffset = 0;
5092	    goto scheduleUpdate;
5093	}
5094    } else {
5095	int overlap;
5096
5097	/*
5098	 * The desired line is below the bottom of the screen. If it is
5099	 * "close" to the bottom of the screen then position it at the bottom
5100	 * of the screen.
5101	 */
5102
5103	MeasureUp(textPtr, indexPtr, close + lineHeight
5104		- textPtr->charHeight/2, &tmpIndex, &overlap);
5105	if (FindDLine(dInfoPtr->dLinePtr, &tmpIndex) != NULL) {
5106	    bottomY = dInfoPtr->maxY - dInfoPtr->y;
5107	}
5108    }
5109
5110    /*
5111     * Our job now is to arrange the display so that indexPtr appears as low
5112     * on the screen as possible but with its bottom no lower than bottomY.
5113     * BottomY is the bottom of the window if the desired line is just below
5114     * the current screen, otherwise it is a half-line lower than the center
5115     * of the window.
5116     */
5117
5118    MeasureUp(textPtr, indexPtr, bottomY, &textPtr->topIndex,
5119	    &dInfoPtr->newTopPixelOffset);
5120
5121  scheduleUpdate:
5122    if (!(dInfoPtr->flags & REDRAW_PENDING)) {
5123	Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);
5124    }
5125    dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;
5126}
5127
5128/*
5129 *--------------------------------------------------------------
5130 *
5131 * TkTextMeasureDown --
5132 *
5133 *	Given one index, find the index of the first character on the highest
5134 *	display line that would be displayed no more than "distance" pixels
5135 *	below the top of the given index.
5136 *
5137 * Results:
5138 *	The srcPtr is manipulated in place to reflect the new position. We
5139 *	return the number of pixels by which 'distance' overlaps the srcPtr.
5140 *
5141 * Side effects:
5142 *	None.
5143 *
5144 *--------------------------------------------------------------
5145 */
5146
5147int
5148TkTextMeasureDown(
5149    TkText *textPtr,		/* Text widget in which to measure. */
5150    TkTextIndex *srcPtr,	/* Index of character from which to start
5151				 * measuring. */
5152    int distance)		/* Vertical distance in pixels measured from
5153				 * the top pixel in srcPtr's logical line. */
5154{
5155    TkTextLine *lastLinePtr;
5156    DLine *dlPtr;
5157    TkTextIndex loop;
5158
5159    lastLinePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr,
5160	    TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr));
5161
5162    do {
5163	dlPtr = LayoutDLine(textPtr, srcPtr);
5164	dlPtr->nextPtr = NULL;
5165
5166	if (distance < dlPtr->height) {
5167	    FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP);
5168	    break;
5169	}
5170	distance -= dlPtr->height;
5171	TkTextIndexForwBytes(textPtr, srcPtr, dlPtr->byteCount, &loop);
5172	FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP);
5173	if (loop.linePtr == lastLinePtr) {
5174	    break;
5175	}
5176	*srcPtr = loop;
5177    } while (distance > 0);
5178
5179    return distance;
5180}
5181
5182/*
5183 *--------------------------------------------------------------
5184 *
5185 * MeasureUp --
5186 *
5187 *	Given one index, find the index of the first character on the highest
5188 *	display line that would be displayed no more than "distance" pixels
5189 *	above the given index.
5190 *
5191 *	If this function is called with distance=0, it simply finds the first
5192 *	index on the same display line as srcPtr. However, there is a another
5193 *	function TkTextFindDisplayLineEnd designed just for that task which is
5194 *	probably better to use.
5195 *
5196 * Results:
5197 *	*dstPtr is filled in with the index of the first character on a
5198 *	display line. The display line is found by measuring up "distance"
5199 *	pixels above the pixel just below an imaginary display line that
5200 *	contains srcPtr. If the display line that covers this coordinate
5201 *	actually extends above the coordinate, then return any excess pixels
5202 *	in *overlap, if that is non-NULL.
5203 *
5204 * Side effects:
5205 *	None.
5206 *
5207 *--------------------------------------------------------------
5208 */
5209
5210static void
5211MeasureUp(
5212    TkText *textPtr,		/* Text widget in which to measure. */
5213    CONST TkTextIndex *srcPtr,	/* Index of character from which to start
5214				 * measuring. */
5215    int distance,		/* Vertical distance in pixels measured from
5216				 * the pixel just below the lowest one in
5217				 * srcPtr's line. */
5218    TkTextIndex *dstPtr,	/* Index to fill in with result. */
5219    int *overlap)		/* Used to store how much of the final index
5220				 * returned was not covered by 'distance'. */
5221{
5222    int lineNum;		/* Number of current line. */
5223    int bytesToCount;		/* Maximum number of bytes to measure in
5224				 * current line. */
5225    TkTextIndex index;
5226    DLine *dlPtr, *lowestPtr;
5227
5228    bytesToCount = srcPtr->byteIndex + 1;
5229    index.tree = srcPtr->tree;
5230    for (lineNum = TkBTreeLinesTo(textPtr, srcPtr->linePtr); lineNum >= 0;
5231	    lineNum--) {
5232	/*
5233	 * Layout an entire text line (potentially > 1 display line).
5234	 *
5235	 * For the first line, which contains srcPtr, only layout the part up
5236	 * through srcPtr (bytesToCount is non-infinite to accomplish this).
5237	 * Make a list of all the display lines in backwards order (the lowest
5238	 * DLine on the screen is first in the list).
5239	 */
5240
5241	index.linePtr = TkBTreeFindLine(srcPtr->tree, textPtr, lineNum);
5242	index.byteIndex = 0;
5243	lowestPtr = NULL;
5244	do {
5245	    dlPtr = LayoutDLine(textPtr, &index);
5246	    dlPtr->nextPtr = lowestPtr;
5247	    lowestPtr = dlPtr;
5248	    TkTextIndexForwBytes(textPtr, &index, dlPtr->byteCount, &index);
5249	    bytesToCount -= dlPtr->byteCount;
5250	} while (bytesToCount>0 && index.linePtr==dlPtr->index.linePtr);
5251
5252	/*
5253	 * Scan through the display lines to see if we've covered enough
5254	 * vertical distance. If so, save the starting index for the line at
5255	 * the desired location. If distance was zero to start with then we
5256	 * simply get the first index on the same display line as the original
5257	 * index.
5258	 */
5259
5260	for (dlPtr = lowestPtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) {
5261	    distance -= dlPtr->height;
5262	    if (distance <= 0) {
5263		*dstPtr = dlPtr->index;
5264		if (overlap != NULL) {
5265		    *overlap = -distance;
5266		}
5267		break;
5268	    }
5269	}
5270
5271	/*
5272	 * Discard the display lines, then either return or prepare for the
5273	 * next display line to lay out.
5274	 */
5275
5276	FreeDLines(textPtr, lowestPtr, NULL, DLINE_FREE);
5277	if (distance <= 0) {
5278	    return;
5279	}
5280	bytesToCount = INT_MAX;		/* Consider all chars. in next line. */
5281    }
5282
5283    /*
5284     * Ran off the beginning of the text. Return the first character in the
5285     * text.
5286     */
5287
5288    TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, 0, 0, dstPtr);
5289    if (overlap != NULL) {
5290	*overlap = 0;
5291    }
5292}
5293
5294/*
5295 *--------------------------------------------------------------
5296 *
5297 * TkTextSeeCmd --
5298 *
5299 *	This function is invoked to process the "see" option for the widget
5300 *	command for text widgets. See the user documentation for details on
5301 *	what it does.
5302 *
5303 * Results:
5304 *	A standard Tcl result.
5305 *
5306 * Side effects:
5307 *	See the user documentation.
5308 *
5309 *--------------------------------------------------------------
5310 */
5311
5312int
5313TkTextSeeCmd(
5314    TkText *textPtr,		/* Information about text widget. */
5315    Tcl_Interp *interp,		/* Current interpreter. */
5316    int objc,			/* Number of arguments. */
5317    Tcl_Obj *CONST objv[])	/* Argument objects. Someone else has already
5318				 * parsed this command enough to know that
5319				 * objv[1] is "see". */
5320{
5321    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
5322    TkTextIndex index;
5323    int x, y, width, height, lineWidth, byteCount, oneThird, delta;
5324    DLine *dlPtr;
5325    TkTextDispChunk *chunkPtr;
5326
5327    if (objc != 3) {
5328	Tcl_WrongNumArgs(interp, 2, objv, "index");
5329	return TCL_ERROR;
5330    }
5331    if (TkTextGetObjIndex(interp, textPtr, objv[2], &index) != TCL_OK) {
5332	return TCL_ERROR;
5333    }
5334
5335    /*
5336     * If the specified position is the extra line at the end of the text,
5337     * round it back to the last real line.
5338     */
5339
5340    if (TkBTreeLinesTo(textPtr, index.linePtr)
5341	    == TkBTreeNumLines(index.tree, textPtr)) {
5342	TkTextIndexBackChars(textPtr, &index, 1, &index, COUNT_INDICES);
5343    }
5344
5345    /*
5346     * First get the desired position into the vertical range of the window.
5347     */
5348
5349    TkTextSetYView(textPtr, &index, TK_TEXT_PICKPLACE);
5350
5351    /*
5352     * Now make sure that the character is in view horizontally.
5353     */
5354
5355    if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
5356	UpdateDisplayInfo(textPtr);
5357    }
5358    lineWidth = dInfoPtr->maxX - dInfoPtr->x;
5359    if (dInfoPtr->maxLength < lineWidth) {
5360	return TCL_OK;
5361    }
5362
5363    /*
5364     * Find the chunk that contains the desired index. dlPtr may be NULL if
5365     * the widget is not mapped. [Bug #641778]
5366     */
5367
5368    dlPtr = FindDLine(dInfoPtr->dLinePtr, &index);
5369    if (dlPtr == NULL) {
5370	return TCL_OK;
5371    }
5372
5373    byteCount = index.byteIndex - dlPtr->index.byteIndex;
5374    for (chunkPtr = dlPtr->chunkPtr; chunkPtr != NULL ;
5375	    chunkPtr = chunkPtr->nextPtr) {
5376	if (byteCount < chunkPtr->numBytes) {
5377	    break;
5378	}
5379	byteCount -= chunkPtr->numBytes;
5380    }
5381
5382    /*
5383     * Call a chunk-specific function to find the horizontal range of the
5384     * character within the chunk. chunkPtr is NULL if trying to see in elided
5385     * region.
5386     */
5387
5388    if (chunkPtr != NULL) {
5389	(*chunkPtr->bboxProc)(textPtr, chunkPtr, byteCount,
5390		dlPtr->y + dlPtr->spaceAbove,
5391		dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
5392		dlPtr->baseline - dlPtr->spaceAbove, &x, &y, &width,
5393		&height);
5394	delta = x - dInfoPtr->curXPixelOffset;
5395	oneThird = lineWidth/3;
5396	if (delta < 0) {
5397	    if (delta < -oneThird) {
5398		dInfoPtr->newXPixelOffset = (x - lineWidth/2);
5399	    } else {
5400		dInfoPtr->newXPixelOffset -= ((-delta) );
5401	    }
5402	} else {
5403	    delta -= (lineWidth - width);
5404	    if (delta > 0) {
5405		if (delta > oneThird) {
5406		    dInfoPtr->newXPixelOffset = (x - lineWidth/2);
5407		} else {
5408		    dInfoPtr->newXPixelOffset += (delta );
5409		}
5410	    } else {
5411		return TCL_OK;
5412	    }
5413	}
5414    }
5415    dInfoPtr->flags |= DINFO_OUT_OF_DATE;
5416    if (!(dInfoPtr->flags & REDRAW_PENDING)) {
5417	dInfoPtr->flags |= REDRAW_PENDING;
5418	Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);
5419    }
5420    return TCL_OK;
5421}
5422
5423/*
5424 *--------------------------------------------------------------
5425 *
5426 * TkTextXviewCmd --
5427 *
5428 *	This function is invoked to process the "xview" option for the widget
5429 *	command for text widgets. See the user documentation for details on
5430 *	what it does.
5431 *
5432 * Results:
5433 *	A standard Tcl result.
5434 *
5435 * Side effects:
5436 *	See the user documentation.
5437 *
5438 *--------------------------------------------------------------
5439 */
5440
5441int
5442TkTextXviewCmd(
5443    TkText *textPtr,		/* Information about text widget. */
5444    Tcl_Interp *interp,		/* Current interpreter. */
5445    int objc,			/* Number of arguments. */
5446    Tcl_Obj *CONST objv[])	/* Argument objects. Someone else has already
5447				 * parsed this command enough to know that
5448				 * objv[1] is "xview". */
5449{
5450    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
5451    int type, count;
5452    double fraction;
5453
5454    if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
5455	UpdateDisplayInfo(textPtr);
5456    }
5457
5458    if (objc == 2) {
5459	GetXView(interp, textPtr, 0);
5460	return TCL_OK;
5461    }
5462
5463    type = TextGetScrollInfoObj(interp, textPtr, objc, objv,
5464	    &fraction, &count);
5465    switch (type) {
5466    case TKTEXT_SCROLL_ERROR:
5467	return TCL_ERROR;
5468    case TKTEXT_SCROLL_MOVETO:
5469	if (fraction > 1.0) {
5470	    fraction = 1.0;
5471	}
5472	if (fraction < 0) {
5473	    fraction = 0;
5474	}
5475	dInfoPtr->newXPixelOffset = (int)
5476		(fraction * dInfoPtr->maxLength + 0.5);
5477	break;
5478    case TKTEXT_SCROLL_PAGES: {
5479	int pixelsPerPage;
5480
5481	pixelsPerPage = (dInfoPtr->maxX-dInfoPtr->x) - 2*textPtr->charWidth;
5482	if (pixelsPerPage < 1) {
5483	    pixelsPerPage = 1;
5484	}
5485	dInfoPtr->newXPixelOffset += pixelsPerPage * count;
5486	break;
5487    }
5488    case TKTEXT_SCROLL_UNITS:
5489	dInfoPtr->newXPixelOffset += count * textPtr->charWidth;
5490	break;
5491    case TKTEXT_SCROLL_PIXELS:
5492	dInfoPtr->newXPixelOffset += count;
5493	break;
5494    }
5495
5496    dInfoPtr->flags |= DINFO_OUT_OF_DATE;
5497    if (!(dInfoPtr->flags & REDRAW_PENDING)) {
5498	dInfoPtr->flags |= REDRAW_PENDING;
5499	Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);
5500    }
5501    return TCL_OK;
5502}
5503
5504/*
5505 *----------------------------------------------------------------------
5506 *
5507 * YScrollByPixels --
5508 *
5509 *	This function is called to scroll a text widget up or down by a given
5510 *	number of pixels.
5511 *
5512 * Results:
5513 *	None.
5514 *
5515 * Side effects:
5516 *	The view in textPtr's window changes to reflect the value of "offset".
5517 *
5518 *----------------------------------------------------------------------
5519 */
5520
5521static void
5522YScrollByPixels(
5523    TkText *textPtr,		/* Widget to scroll. */
5524    int offset)			/* Amount by which to scroll, in pixels.
5525				 * Positive means that information later in
5526				 * text becomes visible, negative means that
5527				 * information earlier in the text becomes
5528				 * visible. */
5529{
5530    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
5531
5532    if (offset < 0) {
5533	/*
5534	 * Now we want to measure up this number of pixels from the top of the
5535	 * screen. But the top line may not be totally visible. Note that
5536	 * 'count' is negative here.
5537	 */
5538
5539	offset -= CalculateDisplayLineHeight(textPtr,
5540		&textPtr->topIndex, NULL, NULL) - dInfoPtr->topPixelOffset;
5541	MeasureUp(textPtr, &textPtr->topIndex, -offset,
5542		&textPtr->topIndex, &dInfoPtr->newTopPixelOffset);
5543    } else if (offset > 0) {
5544	DLine *dlPtr;
5545	TkTextLine *lastLinePtr;
5546	TkTextIndex newIdx;
5547
5548	/*
5549	 * Scrolling down by pixels. Layout lines starting at the top index
5550	 * and count through the desired vertical distance.
5551	 */
5552
5553	lastLinePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr,
5554		TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr));
5555	offset += dInfoPtr->topPixelOffset;
5556	dInfoPtr->newTopPixelOffset = 0;
5557	while (offset > 0) {
5558	    dlPtr = LayoutDLine(textPtr, &textPtr->topIndex);
5559	    dlPtr->nextPtr = NULL;
5560	    TkTextIndexForwBytes(textPtr, &textPtr->topIndex,
5561		    dlPtr->byteCount, &newIdx);
5562	    if (offset <= dlPtr->height) {
5563		/*
5564		 * Adjust the top overlap accordingly.
5565		 */
5566
5567		dInfoPtr->newTopPixelOffset = offset;
5568	    }
5569	    offset -= dlPtr->height;
5570	    FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP);
5571	    if (newIdx.linePtr == lastLinePtr || offset <= 0) {
5572		break;
5573	    }
5574	    textPtr->topIndex = newIdx;
5575	}
5576    } else {
5577	/*
5578	 * offset = 0, so no scrolling required.
5579	 */
5580
5581	return;
5582    }
5583    if (!(dInfoPtr->flags & REDRAW_PENDING)) {
5584	Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);
5585    }
5586    dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;
5587}
5588
5589/*
5590 *----------------------------------------------------------------------
5591 *
5592 * YScrollByLines --
5593 *
5594 *	This function is called to scroll a text widget up or down by a given
5595 *	number of lines.
5596 *
5597 * Results:
5598 *	None.
5599 *
5600 * Side effects:
5601 *	The view in textPtr's window changes to reflect the value of "offset".
5602 *
5603 *----------------------------------------------------------------------
5604 */
5605
5606static void
5607YScrollByLines(
5608    TkText *textPtr,		/* Widget to scroll. */
5609    int offset)			/* Amount by which to scroll, in display
5610				 * lines. Positive means that information
5611				 * later in text becomes visible, negative
5612				 * means that information earlier in the text
5613				 * becomes visible. */
5614{
5615    int i, bytesToCount, lineNum;
5616    TkTextIndex newIdx, index;
5617    TkTextLine *lastLinePtr;
5618    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
5619    DLine *dlPtr, *lowestPtr;
5620
5621    if (offset < 0) {
5622	/*
5623	 * Must scroll up (to show earlier information in the text). The code
5624	 * below is similar to that in MeasureUp, except that it counts lines
5625	 * instead of pixels.
5626	 */
5627
5628	bytesToCount = textPtr->topIndex.byteIndex + 1;
5629	index.tree = textPtr->sharedTextPtr->tree;
5630	offset--;		/* Skip line containing topIndex. */
5631	for (lineNum = TkBTreeLinesTo(textPtr, textPtr->topIndex.linePtr);
5632		lineNum >= 0; lineNum--) {
5633	    index.linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree,
5634		    textPtr, lineNum);
5635	    index.byteIndex = 0;
5636	    lowestPtr = NULL;
5637	    do {
5638		dlPtr = LayoutDLine(textPtr, &index);
5639		dlPtr->nextPtr = lowestPtr;
5640		lowestPtr = dlPtr;
5641		TkTextIndexForwBytes(textPtr, &index, dlPtr->byteCount,&index);
5642		bytesToCount -= dlPtr->byteCount;
5643	    } while ((bytesToCount > 0)
5644		    && (index.linePtr == dlPtr->index.linePtr));
5645
5646	    for (dlPtr = lowestPtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) {
5647		offset++;
5648		if (offset == 0) {
5649		    textPtr->topIndex = dlPtr->index;
5650		    break;
5651		}
5652	    }
5653
5654	    /*
5655	     * Discard the display lines, then either return or prepare for
5656	     * the next display line to lay out.
5657	     */
5658
5659	    FreeDLines(textPtr, lowestPtr, NULL, DLINE_FREE);
5660	    if (offset >= 0) {
5661		goto scheduleUpdate;
5662	    }
5663	    bytesToCount = INT_MAX;
5664	}
5665
5666	/*
5667	 * Ran off the beginning of the text. Return the first character in
5668	 * the text, and make sure we haven't left anything overlapping the
5669	 * top window border.
5670	 */
5671
5672	TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, 0, 0,
5673		&textPtr->topIndex);
5674	dInfoPtr->newTopPixelOffset = 0;
5675    } else {
5676	/*
5677	 * Scrolling down, to show later information in the text. Just count
5678	 * lines from the current top of the window.
5679	 */
5680
5681	lastLinePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr,
5682		TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr));
5683	for (i = 0; i < offset; i++) {
5684	    dlPtr = LayoutDLine(textPtr, &textPtr->topIndex);
5685	    if (dlPtr->length == 0 && dlPtr->height == 0) {
5686		offset++;
5687	    }
5688	    dlPtr->nextPtr = NULL;
5689	    TkTextIndexForwBytes(textPtr, &textPtr->topIndex,
5690		    dlPtr->byteCount, &newIdx);
5691	    FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE);
5692	    if (newIdx.linePtr == lastLinePtr) {
5693		break;
5694	    }
5695	    textPtr->topIndex = newIdx;
5696	}
5697    }
5698
5699  scheduleUpdate:
5700    if (!(dInfoPtr->flags & REDRAW_PENDING)) {
5701	Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);
5702    }
5703    dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;
5704}
5705
5706/*
5707 *--------------------------------------------------------------
5708 *
5709 * TkTextYviewCmd --
5710 *
5711 *	This function is invoked to process the "yview" option for the widget
5712 *	command for text widgets. See the user documentation for details on
5713 *	what it does.
5714 *
5715 * Results:
5716 *	A standard Tcl result.
5717 *
5718 * Side effects:
5719 *	See the user documentation.
5720 *
5721 *--------------------------------------------------------------
5722 */
5723
5724int
5725TkTextYviewCmd(
5726    TkText *textPtr,		/* Information about text widget. */
5727    Tcl_Interp *interp,		/* Current interpreter. */
5728    int objc,			/* Number of arguments. */
5729    Tcl_Obj *CONST objv[])	/* Argument objects. Someone else has already
5730				 * parsed this command enough to know that
5731				 * objv[1] is "yview". */
5732{
5733    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
5734    int pickPlace, type;
5735    int pixels, count;
5736    int switchLength;
5737    double fraction;
5738    TkTextIndex index;
5739
5740    if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
5741	UpdateDisplayInfo(textPtr);
5742    }
5743
5744    if (objc == 2) {
5745	GetYView(interp, textPtr, 0);
5746	return TCL_OK;
5747    }
5748
5749    /*
5750     * Next, handle the old syntax: "pathName yview ?-pickplace? where"
5751     */
5752
5753    pickPlace = 0;
5754    if (Tcl_GetString(objv[2])[0] == '-') {
5755	register CONST char *switchStr =
5756		Tcl_GetStringFromObj(objv[2], &switchLength);
5757
5758	if ((switchLength >= 2) && (strncmp(switchStr, "-pickplace",
5759		(unsigned) switchLength) == 0)) {
5760	    pickPlace = 1;
5761	    if (objc != 4) {
5762		Tcl_WrongNumArgs(interp, 3, objv, "lineNum|index");
5763		return TCL_ERROR;
5764	    }
5765	}
5766    }
5767    if ((objc == 3) || pickPlace) {
5768	int lineNum;
5769
5770	if (Tcl_GetIntFromObj(interp, objv[2+pickPlace], &lineNum) == TCL_OK) {
5771	    TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr,
5772		    lineNum, 0, &index);
5773	    TkTextSetYView(textPtr, &index, 0);
5774	    return TCL_OK;
5775	}
5776
5777	/*
5778	 * The argument must be a regular text index.
5779	 */
5780
5781	Tcl_ResetResult(interp);
5782	if (TkTextGetObjIndex(interp, textPtr, objv[2+pickPlace],
5783		&index) != TCL_OK) {
5784	    return TCL_ERROR;
5785	}
5786	TkTextSetYView(textPtr, &index, (pickPlace ? TK_TEXT_PICKPLACE : 0));
5787	return TCL_OK;
5788    }
5789
5790    /*
5791     * New syntax: dispatch based on objv[2].
5792     */
5793
5794    type = TextGetScrollInfoObj(interp, textPtr, objc,objv, &fraction, &count);
5795    switch (type) {
5796    case TKTEXT_SCROLL_ERROR:
5797	return TCL_ERROR;
5798    case TKTEXT_SCROLL_MOVETO: {
5799	int numPixels = TkBTreeNumPixels(textPtr->sharedTextPtr->tree,
5800		textPtr);
5801	int topMostPixel;
5802
5803	if (numPixels == 0) {
5804	    /*
5805	     * If the window is totally empty no scrolling is needed, and the
5806	     * TkTextMakePixelIndex call below will fail.
5807	     */
5808
5809	    break;
5810	}
5811	if (fraction > 1.0) {
5812	    fraction = 1.0;
5813	}
5814	if (fraction < 0) {
5815	    fraction = 0;
5816	}
5817
5818	/*
5819	 * Calculate the pixel count for the new topmost pixel in the topmost
5820	 * line of the window. Note that the interpretation of 'fraction' is
5821	 * that it counts from 0 (top pixel in buffer) to 1.0 (one pixel past
5822	 * the last pixel in buffer).
5823	 */
5824
5825	topMostPixel = (int) (0.5 + fraction * numPixels);
5826	if (topMostPixel >= numPixels) {
5827	    topMostPixel = numPixels -1;
5828	}
5829
5830	/*
5831	 * This function returns the number of pixels by which the given line
5832	 * should overlap the top of the visible screen.
5833	 *
5834	 * This is then used to provide smooth scrolling.
5835	 */
5836
5837	pixels = TkTextMakePixelIndex(textPtr, topMostPixel, &index);
5838	TkTextSetYView(textPtr, &index, pixels);
5839	break;
5840    }
5841    case TKTEXT_SCROLL_PAGES: {
5842	/*
5843	 * Scroll up or down by screenfuls. Actually, use the window height
5844	 * minus two lines, so that there's some overlap between adjacent
5845	 * pages.
5846	 */
5847
5848	int height = dInfoPtr->maxY - dInfoPtr->y;
5849
5850	if (textPtr->charHeight * 4 >= height) {
5851	    /*
5852	     * A single line is more than a quarter of the display. We choose
5853	     * to scroll by 3/4 of the height instead.
5854	     */
5855
5856	    pixels = 3*height/4;
5857	    if (pixels < textPtr->charHeight) {
5858		/*
5859		 * But, if 3/4 of the height is actually less than a single
5860		 * typical character height, then scroll by the minimum of the
5861		 * linespace or the total height.
5862		 */
5863
5864		if (textPtr->charHeight < height) {
5865		    pixels = textPtr->charHeight;
5866		} else {
5867		    pixels = height;
5868		}
5869	    }
5870	    pixels *= count;
5871	} else {
5872	    pixels = (height - 2*textPtr->charHeight)*count;
5873	}
5874	YScrollByPixels(textPtr, pixels);
5875	break;
5876    }
5877    case TKTEXT_SCROLL_PIXELS:
5878	YScrollByPixels(textPtr, count);
5879	break;
5880    case TKTEXT_SCROLL_UNITS:
5881	YScrollByLines(textPtr, count);
5882	break;
5883    }
5884    return TCL_OK;
5885}
5886
5887/*
5888 *--------------------------------------------------------------
5889 *
5890 * TkTextScanCmd --
5891 *
5892 *	This function is invoked to process the "scan" option for the widget
5893 *	command for text widgets. See the user documentation for details on
5894 *	what it does.
5895 *
5896 * Results:
5897 *	A standard Tcl result.
5898 *
5899 * Side effects:
5900 *	See the user documentation.
5901 *
5902 *--------------------------------------------------------------
5903 */
5904
5905int
5906TkTextScanCmd(
5907    register TkText *textPtr,	/* Information about text widget. */
5908    Tcl_Interp *interp,		/* Current interpreter. */
5909    int objc,			/* Number of arguments. */
5910    Tcl_Obj *CONST objv[])	/* Argument objects. Someone else has already
5911				 * parsed this command enough to know that
5912				 * objv[1] is "scan". */
5913{
5914    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
5915    TkTextIndex index;
5916    int c, x, y, totalScroll, gain=10;
5917    size_t length;
5918
5919    if ((objc != 5) && (objc != 6)) {
5920	Tcl_WrongNumArgs(interp, 2, objv, "mark x y");
5921	Tcl_AppendResult(interp, " or \"", Tcl_GetString(objv[0]),
5922		" scan dragto x y ?gain?\"", NULL);
5923	/*
5924	 * Ought to be:
5925	 * Tcl_WrongNumArgs(interp, 2, objc, "dragto x y ?gain?");
5926	 */
5927	return TCL_ERROR;
5928    }
5929    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK) {
5930	return TCL_ERROR;
5931    }
5932    if (Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
5933	return TCL_ERROR;
5934    }
5935    if ((objc == 6) && (Tcl_GetIntFromObj(interp, objv[5], &gain) != TCL_OK)) {
5936	return TCL_ERROR;
5937    }
5938    c = Tcl_GetString(objv[2])[0];
5939    length = strlen(Tcl_GetString(objv[2]));
5940    if (c=='d' && strncmp(Tcl_GetString(objv[2]), "dragto", length)==0) {
5941	int newX, maxX;
5942
5943	/*
5944	 * Amplify the difference between the current position and the mark
5945	 * position to compute how much the view should shift, then update the
5946	 * mark position to correspond to the new view. If we run off the edge
5947	 * of the text, reset the mark point so that the current position
5948	 * continues to correspond to the edge of the window. This means that
5949	 * the picture will start dragging as soon as the mouse reverses
5950	 * direction (without this reset, might have to slide mouse a long
5951	 * ways back before the picture starts moving again).
5952	 */
5953
5954	newX = dInfoPtr->scanMarkXPixel + gain*(dInfoPtr->scanMarkX - x);
5955	maxX = 1 + dInfoPtr->maxLength - (dInfoPtr->maxX - dInfoPtr->x);
5956	if (newX < 0) {
5957	    newX = 0;
5958	    dInfoPtr->scanMarkXPixel = 0;
5959	    dInfoPtr->scanMarkX = x;
5960	} else if (newX > maxX) {
5961	    newX = maxX;
5962	    dInfoPtr->scanMarkXPixel = maxX;
5963	    dInfoPtr->scanMarkX = x;
5964	}
5965	dInfoPtr->newXPixelOffset = newX;
5966
5967	totalScroll = gain*(dInfoPtr->scanMarkY - y);
5968	if (totalScroll != dInfoPtr->scanTotalYScroll) {
5969	    index = textPtr->topIndex;
5970	    YScrollByPixels(textPtr, totalScroll-dInfoPtr->scanTotalYScroll);
5971	    dInfoPtr->scanTotalYScroll = totalScroll;
5972	    if ((index.linePtr == textPtr->topIndex.linePtr) &&
5973		    (index.byteIndex == textPtr->topIndex.byteIndex)) {
5974		dInfoPtr->scanTotalYScroll = 0;
5975		dInfoPtr->scanMarkY = y;
5976	    }
5977	}
5978	dInfoPtr->flags |= DINFO_OUT_OF_DATE;
5979	if (!(dInfoPtr->flags & REDRAW_PENDING)) {
5980	    dInfoPtr->flags |= REDRAW_PENDING;
5981	    Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);
5982	}
5983    } else if (c=='m' && strncmp(Tcl_GetString(objv[2]), "mark", length)==0) {
5984	dInfoPtr->scanMarkXPixel = dInfoPtr->newXPixelOffset;
5985	dInfoPtr->scanMarkX = x;
5986	dInfoPtr->scanTotalYScroll = 0;
5987	dInfoPtr->scanMarkY = y;
5988    } else {
5989	Tcl_AppendResult(interp, "bad scan option \"", Tcl_GetString(objv[2]),
5990		"\": must be mark or dragto", NULL);
5991	return TCL_ERROR;
5992    }
5993    return TCL_OK;
5994}
5995
5996/*
5997 *----------------------------------------------------------------------
5998 *
5999 * GetXView --
6000 *
6001 *	This function computes the fractions that indicate what's visible in a
6002 *	text window and, optionally, evaluates a Tcl script to report them to
6003 *	the text's associated scrollbar.
6004 *
6005 * Results:
6006 *	If report is zero, then the interp's result is filled in with two real
6007 *	numbers separated by a space, giving the position of the left and
6008 *	right edges of the window as fractions from 0 to 1, where 0 means the
6009 *	left edge of the text and 1 means the right edge. If report is
6010 *	non-zero, then the interp's result isn't modified directly, but
6011 *	instead a script is evaluated in interp to report the new horizontal
6012 *	scroll position to the scrollbar (if the scroll position hasn't
6013 *	changed then no script is invoked).
6014 *
6015 * Side effects:
6016 *	None.
6017 *
6018 *----------------------------------------------------------------------
6019 */
6020
6021static void
6022GetXView(
6023    Tcl_Interp *interp,		/* If "report" is FALSE, string describing
6024				 * visible range gets stored in the interp's
6025				 * result. */
6026    TkText *textPtr,		/* Information about text widget. */
6027    int report)			/* Non-zero means report info to scrollbar if
6028				 * it has changed. */
6029{
6030    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
6031    double first, last;
6032    int code;
6033    Tcl_Obj *listObj;
6034
6035    if (dInfoPtr->maxLength > 0) {
6036	first = ((double) dInfoPtr->curXPixelOffset)
6037		/ dInfoPtr->maxLength;
6038	last = ((double) (dInfoPtr->curXPixelOffset + dInfoPtr->maxX
6039		- dInfoPtr->x))/dInfoPtr->maxLength;
6040	if (last > 1.0) {
6041	    last = 1.0;
6042	}
6043    } else {
6044	first = 0;
6045	last = 1.0;
6046    }
6047    if (!report) {
6048	listObj = Tcl_NewListObj(0, NULL);
6049	Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(first));
6050	Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(last));
6051	Tcl_SetObjResult(interp, listObj);
6052	return;
6053    }
6054    if (FP_EQUAL_SCALE(first, dInfoPtr->xScrollFirst, dInfoPtr->maxLength) &&
6055	    FP_EQUAL_SCALE(last, dInfoPtr->xScrollLast, dInfoPtr->maxLength)) {
6056	return;
6057    }
6058    dInfoPtr->xScrollFirst = first;
6059    dInfoPtr->xScrollLast = last;
6060    if (textPtr->xScrollCmd != NULL) {
6061	char buf1[TCL_DOUBLE_SPACE+1];
6062	char buf2[TCL_DOUBLE_SPACE+1];
6063
6064	buf1[0] = ' ';
6065	buf2[0] = ' ';
6066	Tcl_PrintDouble(NULL, first, buf1+1);
6067	Tcl_PrintDouble(NULL, last, buf2+1);
6068	code = Tcl_VarEval(interp, textPtr->xScrollCmd, buf1, buf2, NULL);
6069	if (code != TCL_OK) {
6070	    Tcl_AddErrorInfo(interp,
6071		    "\n    (horizontal scrolling command executed by text)");
6072	    Tcl_BackgroundError(interp);
6073	}
6074    }
6075}
6076
6077/*
6078 *----------------------------------------------------------------------
6079 *
6080 * GetYPixelCount --
6081 *
6082 *	How many pixels are there between the absolute top of the widget and
6083 *	the top of the given DLine.
6084 *
6085 *	While this function will work for any valid DLine, it is only ever
6086 *	called when dlPtr is the first display line in the widget (by
6087 *	'GetYView'). This means that usually this function is a very quick
6088 *	calculation, since it can use the pre-calculated linked-list of DLines
6089 *	for height information.
6090 *
6091 *	The only situation where this breaks down is if dlPtr's logical line
6092 *	wraps enough times to fill the text widget's current view - in this
6093 *	case we won't have enough dlPtrs in the linked list to be able to
6094 *	subtract off what we want.
6095 *
6096 * Results:
6097 *	The number of pixels.
6098 *
6099 *	This value has a valid range between '0' (the very top of the widget)
6100 *	and the number of pixels in the total widget minus the pixel-height of
6101 *	the last line.
6102 *
6103 * Side effects:
6104 *	None.
6105 *
6106 *----------------------------------------------------------------------
6107 */
6108
6109static int
6110GetYPixelCount(
6111    TkText *textPtr,		/* Information about text widget. */
6112    DLine *dlPtr)		/* Information about the layout of a given
6113				 * index. */
6114{
6115    TkTextLine *linePtr = dlPtr->index.linePtr;
6116    int count;
6117
6118    /*
6119     * Get the pixel count to the top of dlPtr's logical line. The rest of the
6120     * function is then concerned with updating 'count' for any difference
6121     * between the top of the logical line and the display line.
6122     */
6123
6124    count = TkBTreePixelsTo(textPtr, linePtr);
6125
6126    /*
6127     * For the common case where this dlPtr is also the start of the logical
6128     * line, we can return right away. Note the implicit assumption here that
6129     * the start of a logical line is always the start of a display line (if
6130     * the 'elide won't elide first newline' bug is fixed, this will no longer
6131     * necessarily be true).
6132     */
6133
6134    if (dlPtr->index.byteIndex == 0) {
6135	return count;
6136    }
6137
6138    /*
6139     * Add on the logical line's height to reach one pixel beyond the bottom
6140     * of the logical line. And then subtract off the heights of all the
6141     * display lines from dlPtr to the end of its logical line.
6142     *
6143     * A different approach would be to lay things out from the start of the
6144     * logical line until we reach dlPtr, but since none of those are
6145     * pre-calculated, it'll usually take a lot longer. (But there are cases
6146     * where it would be more efficient: say if we're on the second of 1000
6147     * wrapped lines all from a single logical line - but that sort of
6148     * optimization is left for the future).
6149     */
6150
6151    count += TkBTreeLinePixelCount(textPtr, linePtr);
6152
6153    do {
6154	count -= dlPtr->height;
6155	if (dlPtr->nextPtr == NULL) {
6156	    /*
6157	     * We've run out of pre-calculated display lines, so we have to
6158	     * lay them out ourselves until the end of the logical line. Here
6159	     * is where we could be clever and ask: what's faster, to layout
6160	     * all lines from here to line-end, or all lines from the original
6161	     * dlPtr to the line-start? We just assume the former.
6162	     */
6163
6164	    TkTextIndex index;
6165	    int notFirst = 0;
6166
6167	    while (1) {
6168		TkTextIndexForwBytes(textPtr, &dlPtr->index,
6169			dlPtr->byteCount, &index);
6170		if (notFirst) {
6171		    FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP);
6172		}
6173		if (index.linePtr != linePtr) {
6174		    break;
6175		}
6176		dlPtr = LayoutDLine(textPtr, &index);
6177
6178		if (tkTextDebug) {
6179		    char string[TK_POS_CHARS];
6180
6181		    /*
6182		     * Debugging is enabled, so keep a log of all the lines
6183		     * whose height was recalculated. The test suite uses this
6184		     * information.
6185		     */
6186
6187		    TkTextPrintIndex(textPtr, &index, string);
6188		    LOG("tk_textHeightCalc", string);
6189		}
6190		count -= dlPtr->height;
6191		notFirst = 1;
6192	    }
6193	    break;
6194	} else {
6195	    dlPtr = dlPtr->nextPtr;
6196	}
6197    } while (dlPtr->index.linePtr == linePtr);
6198
6199    return count;
6200}
6201
6202/*
6203 *----------------------------------------------------------------------
6204 *
6205 * GetYView --
6206 *
6207 *	This function computes the fractions that indicate what's visible in a
6208 *	text window and, optionally, evaluates a Tcl script to report them to
6209 *	the text's associated scrollbar.
6210 *
6211 * Results:
6212 *	If report is zero, then the interp's result is filled in with two real
6213 *	numbers separated by a space, giving the position of the top and
6214 *	bottom of the window as fractions from 0 to 1, where 0 means the
6215 *	beginning of the text and 1 means the end. If report is non-zero, then
6216 *	the interp's result isn't modified directly, but a script is evaluated
6217 *	in interp to report the new scroll position to the scrollbar (if the
6218 *	scroll position hasn't changed then no script is invoked).
6219 *
6220 * Side effects:
6221 *	None.
6222 *
6223 *----------------------------------------------------------------------
6224 */
6225
6226static void
6227GetYView(
6228    Tcl_Interp *interp,		/* If "report" is FALSE, string describing
6229				 * visible range gets stored in the interp's
6230				 * result. */
6231    TkText *textPtr,		/* Information about text widget. */
6232    int report)			/* Non-zero means report info to scrollbar if
6233				 * it has changed. */
6234{
6235    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
6236    double first, last;
6237    DLine *dlPtr;
6238    int totalPixels, code, count;
6239    Tcl_Obj *listObj;
6240
6241    dlPtr = dInfoPtr->dLinePtr;
6242
6243    if (dlPtr == NULL) {
6244	return;
6245    }
6246
6247    totalPixels = TkBTreeNumPixels(textPtr->sharedTextPtr->tree, textPtr);
6248
6249    if (totalPixels == 0) {
6250	first = 0.0;
6251	last = 1.0;
6252    } else {
6253	/*
6254	 * Get the pixel count for the first visible pixel of the first
6255	 * visible line. If the first visible line is only partially visible,
6256	 * then we use 'topPixelOffset' to get the difference.
6257	 */
6258
6259	count = GetYPixelCount(textPtr, dlPtr);
6260	first = (count + dInfoPtr->topPixelOffset) / (double) totalPixels;
6261
6262	/*
6263	 * Add on the total number of visible pixels to get the count to one
6264	 * pixel _past_ the last visible pixel. This is how the 'yview'
6265	 * command is documented, and also explains why we are dividing by
6266	 * 'totalPixels' and not 'totalPixels-1'.
6267	 */
6268
6269	while (1) {
6270	    int extra;
6271
6272	    count += dlPtr->height;
6273	    extra = dlPtr->y + dlPtr->height - dInfoPtr->maxY;
6274	    if (extra > 0) {
6275		/*
6276		 * This much of the last line is not visible, so don't count
6277		 * these pixels. Since we've reached the bottom of the window,
6278		 * we break out of the loop.
6279		 */
6280
6281		count -= extra;
6282		break;
6283	    }
6284	    if (dlPtr->nextPtr == NULL) {
6285		break;
6286	    }
6287	    dlPtr = dlPtr->nextPtr;
6288	}
6289
6290	if (count > totalPixels) {
6291	    /*
6292	     * It can be possible, if we do not update each line's pixelHeight
6293	     * cache when we lay out individual DLines that the count
6294	     * generated here is more up-to-date than that maintained by the
6295	     * BTree. In such a case, the best we can do here is to fix up
6296	     * 'count' and continue, which might result in small, temporary
6297	     * perturbations to the size of the scrollbar. This is basically
6298	     * harmless, but in a perfect world we would not have this
6299	     * problem.
6300	     *
6301	     * For debugging purposes, if anyone wishes to improve the text
6302	     * widget further, the following 'panic' can be activated. In
6303	     * principle it should be possible to ensure the BTree is always
6304	     * at least as up to date as the display, so in the future we
6305	     * might be able to leave the 'panic' in permanently when we
6306	     * believe we have resolved the cache synchronisation issue.
6307	     *
6308	     * However, to achieve that goal would, I think, require a fairly
6309	     * substantial refactorisation of the code in this file so that
6310	     * there is much more obvious and explicit coordination between
6311	     * calls to LayoutDLine and updating of each TkTextLine's
6312	     * pixelHeight. The complicated bit is that LayoutDLine deals with
6313	     * individual display lines, but pixelHeight is for a logical
6314	     * line.
6315	     */
6316
6317#if 0
6318	    Tcl_Panic("Counted more pixels (%d) than expected (%d) total "
6319		    "pixels in text widget scroll bar calculation.", count,
6320		    totalPixels);
6321#endif
6322	    count = totalPixels;
6323	}
6324
6325	last = ((double) count)/((double)totalPixels);
6326    }
6327
6328    if (!report) {
6329	listObj = Tcl_NewListObj(0,NULL);
6330	Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(first));
6331	Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(last));
6332	Tcl_SetObjResult(interp, listObj);
6333	return;
6334    }
6335
6336    if (FP_EQUAL_SCALE(first, dInfoPtr->yScrollFirst, totalPixels) &&
6337	    FP_EQUAL_SCALE(last, dInfoPtr->yScrollLast, totalPixels)) {
6338	return;
6339    }
6340
6341    dInfoPtr->yScrollFirst = first;
6342    dInfoPtr->yScrollLast = last;
6343    if (textPtr->yScrollCmd != NULL) {
6344	char buf1[TCL_DOUBLE_SPACE+1];
6345	char buf2[TCL_DOUBLE_SPACE+1];
6346
6347	buf1[0] = ' ';
6348	buf2[0] = ' ';
6349	Tcl_PrintDouble(NULL, first, buf1+1);
6350	Tcl_PrintDouble(NULL, last, buf2+1);
6351	code = Tcl_VarEval(interp, textPtr->yScrollCmd, buf1, buf2, NULL);
6352	if (code != TCL_OK) {
6353	    Tcl_AddErrorInfo(interp,
6354		    "\n    (vertical scrolling command executed by text)");
6355	    Tcl_BackgroundError(interp);
6356	}
6357    }
6358}
6359
6360/*
6361 *----------------------------------------------------------------------
6362 *
6363 * AsyncUpdateYScrollbar --
6364 *
6365 *	This function is called to update the vertical scrollbar asychronously
6366 *	as the pixel height calculations progress for lines in the widget.
6367 *
6368 * Results:
6369 *	None.
6370 *
6371 * Side effects:
6372 *	See 'GetYView'. In particular the scrollbar position and size may be
6373 *	changed.
6374 *
6375 *----------------------------------------------------------------------
6376 */
6377
6378static void
6379AsyncUpdateYScrollbar(
6380    ClientData clientData)	/* Information about widget. */
6381{
6382    register TkText *textPtr = (TkText *) clientData;
6383
6384    textPtr->dInfoPtr->scrollbarTimer = NULL;
6385
6386    if (!(textPtr->flags & DESTROYED)) {
6387	GetYView(textPtr->interp, textPtr, 1);
6388    }
6389
6390    if (--textPtr->refCount == 0) {
6391	ckfree((char *) textPtr);
6392    }
6393}
6394
6395/*
6396 *----------------------------------------------------------------------
6397 *
6398 * FindDLine --
6399 *
6400 *	This function is called to find the DLine corresponding to a given
6401 *	text index.
6402 *
6403 * Results:
6404 *	The return value is a pointer to the first DLine found in the list
6405 *	headed by dlPtr that displays information at or after the specified
6406 *	position. If there is no such line in the list then NULL is returned.
6407 *
6408 * Side effects:
6409 *	None.
6410 *
6411 *----------------------------------------------------------------------
6412 */
6413
6414static DLine *
6415FindDLine(
6416    register DLine *dlPtr,	/* Pointer to first in list of DLines to
6417				 * search. */
6418    CONST TkTextIndex *indexPtr)/* Index of desired character. */
6419{
6420    TkTextLine *linePtr;
6421
6422    if (dlPtr == NULL) {
6423	return NULL;
6424    }
6425    if (TkBTreeLinesTo(NULL, indexPtr->linePtr)
6426	    < TkBTreeLinesTo(NULL, dlPtr->index.linePtr)) {
6427	/*
6428	 * The first display line is already past the desired line.
6429	 */
6430
6431	return dlPtr;
6432    }
6433
6434    /*
6435     * Find the first display line that covers the desired text line.
6436     */
6437
6438    linePtr = dlPtr->index.linePtr;
6439    while (linePtr != indexPtr->linePtr) {
6440	while (dlPtr->index.linePtr == linePtr) {
6441	    dlPtr = dlPtr->nextPtr;
6442	    if (dlPtr == NULL) {
6443		return NULL;
6444	    }
6445	}
6446
6447	/*
6448	 * VMD: some concern here as to whether this logic, or the caller's
6449	 * logic will work well with partial peer widgets.
6450	 */
6451
6452	linePtr = TkBTreeNextLine(NULL, linePtr);
6453	if (linePtr == NULL) {
6454	    Tcl_Panic("FindDLine reached end of text");
6455	}
6456    }
6457    if (indexPtr->linePtr != dlPtr->index.linePtr) {
6458	return dlPtr;
6459    }
6460
6461    /*
6462     * Now get to the right position within the text line.
6463     */
6464
6465    while (indexPtr->byteIndex >= (dlPtr->index.byteIndex+dlPtr->byteCount)) {
6466	dlPtr = dlPtr->nextPtr;
6467	if ((dlPtr == NULL) || (dlPtr->index.linePtr != indexPtr->linePtr)) {
6468	    break;
6469	}
6470    }
6471    return dlPtr;
6472}
6473
6474/*
6475 *----------------------------------------------------------------------
6476 *
6477 * TkTextPixelIndex --
6478 *
6479 *	Given an (x,y) coordinate on the screen, find the location of the
6480 *	character closest to that location.
6481 *
6482 * Results:
6483 *	The index at *indexPtr is modified to refer to the character on the
6484 *	display that is closest to (x,y).
6485 *
6486 * Side effects:
6487 *	None.
6488 *
6489 *----------------------------------------------------------------------
6490 */
6491
6492void
6493TkTextPixelIndex(
6494    TkText *textPtr,		/* Widget record for text widget. */
6495    int x, int y,		/* Pixel coordinates of point in widget's
6496				 * window. */
6497    TkTextIndex *indexPtr,	/* This index gets filled in with the index of
6498				 * the character nearest to (x,y). */
6499    int *nearest)		/* If non-NULL then gets set to 0 if (x,y) is
6500				 * actually over the returned index, and 1 if
6501				 * it is just nearby (e.g. if x,y is on the
6502				 * border of the widget). */
6503{
6504    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
6505    register DLine *dlPtr, *validDlPtr;
6506    int nearby = 0;
6507
6508    /*
6509     * Make sure that all of the layout information about what's displayed
6510     * where on the screen is up-to-date.
6511     */
6512
6513    if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
6514	UpdateDisplayInfo(textPtr);
6515    }
6516
6517    /*
6518     * If the coordinates are above the top of the window, then adjust them to
6519     * refer to the upper-right corner of the window. If they're off to one
6520     * side or the other, then adjust to the closest side.
6521     */
6522
6523    if (y < dInfoPtr->y) {
6524	y = dInfoPtr->y;
6525	x = dInfoPtr->x;
6526	nearby = 1;
6527    }
6528    if (x >= dInfoPtr->maxX) {
6529	x = dInfoPtr->maxX - 1;
6530	nearby = 1;
6531    }
6532    if (x < dInfoPtr->x) {
6533	x = dInfoPtr->x;
6534	nearby = 1;
6535    }
6536
6537    /*
6538     * Find the display line containing the desired y-coordinate.
6539     */
6540
6541    if (dInfoPtr->dLinePtr == NULL) {
6542	if (nearest != NULL) {
6543	    *nearest = 1;
6544	}
6545	*indexPtr = textPtr->topIndex;
6546	return;
6547    } else {
6548	for (dlPtr = validDlPtr = dInfoPtr->dLinePtr;
6549		y >= (dlPtr->y + dlPtr->height);
6550		dlPtr = dlPtr->nextPtr) {
6551	    if (dlPtr->chunkPtr != NULL) {
6552		validDlPtr = dlPtr;
6553	    }
6554	    if (dlPtr->nextPtr == NULL) {
6555		/*
6556		 * Y-coordinate is off the bottom of the displayed text. Use
6557		 * the last character on the last line.
6558		 */
6559
6560		x = dInfoPtr->maxX - 1;
6561		nearby = 1;
6562		break;
6563	    }
6564	}
6565	if (dlPtr->chunkPtr == NULL) dlPtr = validDlPtr;
6566    }
6567
6568    if (nearest != NULL) {
6569	*nearest = nearby;
6570    }
6571
6572    DlineIndexOfX(textPtr, dlPtr, x, indexPtr);
6573}
6574
6575/*
6576 *----------------------------------------------------------------------
6577 *
6578 * DlineIndexOfX --
6579 *
6580 *	Given an x coordinate in a display line, find the index of the
6581 *	character closest to that location.
6582 *
6583 *	This is effectively the opposite of DlineXOfIndex.
6584 *
6585 * Results:
6586 *	The index at *indexPtr is modified to refer to the character on the
6587 *	display line that is closest to x.
6588 *
6589 * Side effects:
6590 *	None.
6591 *
6592 *----------------------------------------------------------------------
6593 */
6594
6595static void
6596DlineIndexOfX(
6597    TkText *textPtr,		/* Widget record for text widget. */
6598    DLine *dlPtr,		/* Display information for this display
6599				 * line. */
6600    int x,			/* Pixel x coordinate of point in widget's
6601				 * window. */
6602    TkTextIndex *indexPtr)	/* This index gets filled in with the index of
6603				 * the character nearest to x. */
6604{
6605    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
6606    register TkTextDispChunk *chunkPtr;
6607
6608    /*
6609     * Scan through the line's chunks to find the one that contains the
6610     * desired x-coordinate. Before doing this, translate the x-coordinate
6611     * from the coordinate system of the window to the coordinate system of
6612     * the line (to take account of x-scrolling).
6613     */
6614
6615    *indexPtr = dlPtr->index;
6616    x = x - dInfoPtr->x + dInfoPtr->curXPixelOffset;
6617    chunkPtr = dlPtr->chunkPtr;
6618
6619    if (chunkPtr == NULL || x == 0) {
6620	/*
6621	 * This may occur if everything is elided, or if we're simply already
6622	 * at the beginning of the line.
6623	 */
6624
6625	return;
6626    }
6627
6628    while (x >= (chunkPtr->x + chunkPtr->width)) {
6629	/*
6630	 * Note that this forward then backward movement of the index can be
6631	 * problematic at the end of the buffer (we can't move forward, and
6632	 * then when we move backward, we do, leading to the wrong position).
6633	 * Hence when x == 0 we take special action above.
6634	 */
6635
6636	if (TkTextIndexForwBytes(NULL,indexPtr,chunkPtr->numBytes,indexPtr)) {
6637	    /*
6638	     * We've reached the end of the text.
6639	     */
6640
6641	    return;
6642	}
6643	if (chunkPtr->nextPtr == NULL) {
6644	    TkTextIndexBackChars(NULL, indexPtr, 1, indexPtr, COUNT_INDICES);
6645	    return;
6646	}
6647	chunkPtr = chunkPtr->nextPtr;
6648    }
6649
6650    /*
6651     * If the chunk has more than one byte in it, ask it which character is at
6652     * the desired location. In this case we can manipulate
6653     * 'indexPtr->byteIndex' directly, because we know we're staying inside a
6654     * single logical line.
6655     */
6656
6657    if (chunkPtr->numBytes > 1) {
6658	indexPtr->byteIndex += (*chunkPtr->measureProc)(chunkPtr, x);
6659    }
6660}
6661
6662/*
6663 *----------------------------------------------------------------------
6664 *
6665 * TkTextIndexOfX --
6666 *
6667 *	Given a logical x coordinate (i.e. distance in pixels from the
6668 *	beginning of the display line, not taking into account any information
6669 *	about the window, scrolling etc.) on the display line starting with
6670 *	the given index, adjust that index to refer to the object under the x
6671 *	coordinate.
6672 *
6673 * Results:
6674 *	None.
6675 *
6676 * Side effects:
6677 *	None.
6678 *
6679 *----------------------------------------------------------------------
6680 */
6681
6682void
6683TkTextIndexOfX(
6684    TkText *textPtr,		/* Widget record for text widget. */
6685    int x,			/* The x coordinate for which we want the
6686				 * index. */
6687    TkTextIndex *indexPtr)	/* Index of display line start, which will be
6688				 * adjusted to the index under the given x
6689				 * coordinate. */
6690{
6691    DLine *dlPtr = LayoutDLine(textPtr, indexPtr);
6692    DlineIndexOfX(textPtr, dlPtr, x + textPtr->dInfoPtr->x
6693		- textPtr->dInfoPtr->curXPixelOffset, indexPtr);
6694    FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP);
6695}
6696
6697/*
6698 *----------------------------------------------------------------------
6699 *
6700 * DlineXOfIndex --
6701 *
6702 *	Given a relative byte index on a given display line (i.e. the number
6703 *	of byte indices from the beginning of the given display line), find
6704 *	the x coordinate of that index within the abstract display line,
6705 *	without adjusting for the x-scroll state of the line.
6706 *
6707 *	This is effectively the opposite of DlineIndexOfX.
6708 *
6709 *	NB. The 'byteIndex' is relative to the display line, NOT the logical
6710 *	line.
6711 *
6712 * Results:
6713 *	The x coordinate.
6714 *
6715 * Side effects:
6716 *	None.
6717 *
6718 *----------------------------------------------------------------------
6719 */
6720
6721static int
6722DlineXOfIndex(
6723    TkText *textPtr,		/* Widget record for text widget. */
6724    DLine *dlPtr,		/* Display information for this display
6725				 * line. */
6726    int byteIndex)		/* The byte index for which we want the
6727				 * coordinate. */
6728{
6729    register TkTextDispChunk *chunkPtr = dlPtr->chunkPtr;
6730    int x;
6731
6732    if (byteIndex == 0 || chunkPtr == NULL) {
6733	return 0;
6734    }
6735
6736    /*
6737     * Scan through the line's chunks to find the one that contains the
6738     * desired byte index.
6739     */
6740
6741    chunkPtr = dlPtr->chunkPtr;
6742    while (byteIndex > 0) {
6743	if (byteIndex < chunkPtr->numBytes) {
6744	    int y, width, height;
6745
6746	    (*chunkPtr->bboxProc)(textPtr, chunkPtr, byteIndex,
6747		    dlPtr->y + dlPtr->spaceAbove,
6748		    dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
6749		    dlPtr->baseline - dlPtr->spaceAbove, &x, &y, &width,
6750		    &height);
6751	    break;
6752	} else {
6753	    byteIndex -= chunkPtr->numBytes;
6754	}
6755	if (chunkPtr->nextPtr == NULL || byteIndex == 0) {
6756	    x = chunkPtr->x + chunkPtr->width;
6757	    break;
6758	}
6759	chunkPtr = chunkPtr->nextPtr;
6760    }
6761
6762    return x;
6763}
6764
6765/*
6766 *----------------------------------------------------------------------
6767 *
6768 * TkTextIndexBbox --
6769 *
6770 *	Given an index, find the bounding box of the screen area occupied by
6771 *	the entity (character, window, image) at that index.
6772 *
6773 * Results:
6774 *	Zero is returned if the index is on the screen. -1 means the index is
6775 *	not on the screen. If the return value is 0, then the bounding box of
6776 *	the part of the index that's visible on the screen is returned to
6777 *	*xPtr, *yPtr, *widthPtr, and *heightPtr.
6778 *
6779 * Side effects:
6780 *	None.
6781 *
6782 *----------------------------------------------------------------------
6783 */
6784
6785int
6786TkTextIndexBbox(
6787    TkText *textPtr,		/* Widget record for text widget. */
6788    CONST TkTextIndex *indexPtr,/* Index whose bounding box is desired. */
6789    int *xPtr, int *yPtr,	/* Filled with index's upper-left
6790				 * coordinate. */
6791    int *widthPtr, int *heightPtr,
6792				/* Filled in with index's dimensions. */
6793    int *charWidthPtr)		/* If the 'index' is at the end of a display
6794				 * line and therefore takes up a very large
6795				 * width, this is used to return the smaller
6796				 * width actually desired by the index. */
6797{
6798    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
6799    DLine *dlPtr;
6800    register TkTextDispChunk *chunkPtr;
6801    int byteIndex;
6802
6803    /*
6804     * Make sure that all of the screen layout information is up to date.
6805     */
6806
6807    if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
6808	UpdateDisplayInfo(textPtr);
6809    }
6810
6811    /*
6812     * Find the display line containing the desired index.
6813     */
6814
6815    dlPtr = FindDLine(dInfoPtr->dLinePtr, indexPtr);
6816    if ((dlPtr == NULL) || (TkTextIndexCmp(&dlPtr->index, indexPtr) > 0)) {
6817	return -1;
6818    }
6819
6820    /*
6821     * Find the chunk within the line that contains the desired index.
6822     */
6823
6824    byteIndex = indexPtr->byteIndex - dlPtr->index.byteIndex;
6825    for (chunkPtr = dlPtr->chunkPtr; ; chunkPtr = chunkPtr->nextPtr) {
6826	if (chunkPtr == NULL) {
6827	    return -1;
6828	}
6829	if (byteIndex < chunkPtr->numBytes) {
6830	    break;
6831	}
6832	byteIndex -= chunkPtr->numBytes;
6833    }
6834
6835    /*
6836     * Call a chunk-specific function to find the horizontal range of the
6837     * character within the chunk, then fill in the vertical range. The
6838     * x-coordinate returned by bboxProc is a coordinate within a line, not a
6839     * coordinate on the screen. Translate it to reflect horizontal scrolling.
6840     */
6841
6842    (*chunkPtr->bboxProc)(textPtr, chunkPtr, byteIndex,
6843	    dlPtr->y + dlPtr->spaceAbove,
6844	    dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
6845	    dlPtr->baseline - dlPtr->spaceAbove, xPtr, yPtr, widthPtr,
6846	    heightPtr);
6847    *xPtr = *xPtr + dInfoPtr->x - dInfoPtr->curXPixelOffset;
6848    if ((byteIndex == chunkPtr->numBytes-1) && (chunkPtr->nextPtr == NULL)) {
6849	/*
6850	 * Last character in display line. Give it all the space up to the
6851	 * line.
6852	 */
6853
6854	if (charWidthPtr != NULL) {
6855	    *charWidthPtr = dInfoPtr->maxX - *xPtr;
6856	}
6857	if (*xPtr > dInfoPtr->maxX) {
6858	    *xPtr = dInfoPtr->maxX;
6859	}
6860	*widthPtr = dInfoPtr->maxX - *xPtr;
6861    } else {
6862	if (charWidthPtr != NULL) {
6863	    *charWidthPtr = *widthPtr;
6864	}
6865    }
6866    if (*widthPtr == 0) {
6867	/*
6868	 * With zero width (e.g. elided text) we just need to make sure it is
6869	 * onscreen, where the '=' case here is ok.
6870	 */
6871
6872	if (*xPtr < dInfoPtr->x) {
6873	    return -1;
6874	}
6875    } else {
6876	if ((*xPtr + *widthPtr) <= dInfoPtr->x) {
6877	    return -1;
6878	}
6879    }
6880    if ((*xPtr + *widthPtr) > dInfoPtr->maxX) {
6881	*widthPtr = dInfoPtr->maxX - *xPtr;
6882	if (*widthPtr <= 0) {
6883	    return -1;
6884	}
6885    }
6886    if ((*yPtr + *heightPtr) > dInfoPtr->maxY) {
6887	*heightPtr = dInfoPtr->maxY - *yPtr;
6888	if (*heightPtr <= 0) {
6889	    return -1;
6890	}
6891    }
6892    return 0;
6893}
6894
6895/*
6896 *----------------------------------------------------------------------
6897 *
6898 * TkTextDLineInfo --
6899 *
6900 *	Given an index, return information about the display line containing
6901 *	that character.
6902 *
6903 * Results:
6904 *	Zero is returned if the character is on the screen. -1 means the
6905 *	character isn't on the screen. If the return value is 0, then
6906 *	information is returned in the variables pointed to by xPtr, yPtr,
6907 *	widthPtr, heightPtr, and basePtr.
6908 *
6909 * Side effects:
6910 *	None.
6911 *
6912 *----------------------------------------------------------------------
6913 */
6914
6915int
6916TkTextDLineInfo(
6917    TkText *textPtr,		/* Widget record for text widget. */
6918    CONST TkTextIndex *indexPtr,/* Index of character whose bounding box is
6919				 * desired. */
6920    int *xPtr, int *yPtr,	/* Filled with line's upper-left
6921				 * coordinate. */
6922    int *widthPtr, int *heightPtr,
6923				/* Filled in with line's dimensions. */
6924    int *basePtr)		/* Filled in with the baseline position,
6925				 * measured as an offset down from *yPtr. */
6926{
6927    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
6928    DLine *dlPtr;
6929    int dlx;
6930
6931    /*
6932     * Make sure that all of the screen layout information is up to date.
6933     */
6934
6935    if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
6936	UpdateDisplayInfo(textPtr);
6937    }
6938
6939    /*
6940     * Find the display line containing the desired index.
6941     */
6942
6943    dlPtr = FindDLine(dInfoPtr->dLinePtr, indexPtr);
6944    if ((dlPtr == NULL) || (TkTextIndexCmp(&dlPtr->index, indexPtr) > 0)) {
6945	return -1;
6946    }
6947
6948    dlx = (dlPtr->chunkPtr != NULL? dlPtr->chunkPtr->x: 0);
6949    *xPtr = dInfoPtr->x - dInfoPtr->curXPixelOffset + dlx;
6950    *widthPtr = dlPtr->length - dlx;
6951    *yPtr = dlPtr->y;
6952    if ((dlPtr->y + dlPtr->height) > dInfoPtr->maxY) {
6953	*heightPtr = dInfoPtr->maxY - dlPtr->y;
6954    } else {
6955	*heightPtr = dlPtr->height;
6956    }
6957    *basePtr = dlPtr->baseline;
6958    return 0;
6959}
6960
6961/*
6962 * Get bounding-box information about an elided chunk.
6963 */
6964
6965static void
6966ElideBboxProc(
6967    TkText *textPtr,
6968    TkTextDispChunk *chunkPtr,	/* Chunk containing desired char. */
6969    int index,			/* Index of desired character within the
6970				 * chunk. */
6971    int y,			/* Topmost pixel in area allocated for this
6972				 * line. */
6973    int lineHeight,		/* Height of line, in pixels. */
6974    int baseline,		/* Location of line's baseline, in pixels
6975				 * measured down from y. */
6976    int *xPtr, int *yPtr,	/* Gets filled in with coords of character's
6977				 * upper-left pixel. X-coord is in same
6978				 * coordinate system as chunkPtr->x. */
6979    int *widthPtr,		/* Gets filled in with width of character, in
6980				 * pixels. */
6981    int *heightPtr)		/* Gets filled in with height of character, in
6982				 * pixels. */
6983{
6984    *xPtr = chunkPtr->x;
6985    *yPtr = y;
6986    *widthPtr = *heightPtr = 0;
6987}
6988
6989/*
6990 * Measure an elided chunk.
6991 */
6992
6993static int
6994ElideMeasureProc(
6995    TkTextDispChunk *chunkPtr,	/* Chunk containing desired coord. */
6996    int x)			/* X-coordinate, in same coordinate system as
6997				 * chunkPtr->x. */
6998{
6999    return 0 /*chunkPtr->numBytes - 1*/;
7000}
7001
7002/*
7003 *--------------------------------------------------------------
7004 *
7005 * TkTextCharLayoutProc --
7006 *
7007 *	This function is the "layoutProc" for character segments.
7008 *
7009 * Results:
7010 *	If there is something to display for the chunk then a non-zero value
7011 *	is returned and the fields of chunkPtr will be filled in (see the
7012 *	declaration of TkTextDispChunk in tkText.h for details). If zero is
7013 *	returned it means that no characters from this chunk fit in the
7014 *	window. If -1 is returned it means that this segment just doesn't need
7015 *	to be displayed (never happens for text).
7016 *
7017 * Side effects:
7018 *	Memory is allocated to hold additional information about the chunk.
7019 *
7020 *--------------------------------------------------------------
7021 */
7022
7023int
7024TkTextCharLayoutProc(
7025    TkText *textPtr,		/* Text widget being layed out. */
7026    TkTextIndex *indexPtr,	/* Index of first character to lay out
7027				 * (corresponds to segPtr and offset). */
7028    TkTextSegment *segPtr,	/* Segment being layed out. */
7029    int byteOffset,		/* Byte offset within segment of first
7030				 * character to consider. */
7031    int maxX,			/* Chunk must not occupy pixels at this
7032				 * position or higher. */
7033    int maxBytes,		/* Chunk must not include more than this many
7034				 * characters. */
7035    int noCharsYet,		/* Non-zero means no characters have been
7036				 * assigned to this display line yet. */
7037    TkWrapMode wrapMode,	/* How to handle line wrapping:
7038				 * TEXT_WRAPMODE_CHAR, TEXT_WRAPMODE_NONE, or
7039				 * TEXT_WRAPMODE_WORD. */
7040    register TkTextDispChunk *chunkPtr)
7041				/* Structure to fill in with information about
7042				 * this chunk. The x field has already been
7043				 * set by the caller. */
7044{
7045    Tk_Font tkfont;
7046    int nextX, bytesThatFit, count;
7047    CharInfo *ciPtr;
7048    char *p;
7049    TkTextSegment *nextPtr;
7050    Tk_FontMetrics fm;
7051#if TK_LAYOUT_WITH_BASE_CHUNKS
7052    const char *line;
7053    int lineOffset;
7054    BaseCharInfo *bciPtr;
7055    Tcl_DString *baseString;
7056#endif
7057
7058    /*
7059     * Figure out how many characters will fit in the space we've got. Include
7060     * the next character, even though it won't fit completely, if any of the
7061     * following is true:
7062     *	 (a) the chunk contains no characters and the display line contains no
7063     *	     characters yet (i.e. the line isn't wide enough to hold even a
7064     *	     single character).
7065     *	 (b) at least one pixel of the character is visible, we have not
7066     *	     already exceeded the character limit, and the next character is a
7067     *	     white space character.
7068     */
7069
7070    p = segPtr->body.chars + byteOffset;
7071    tkfont = chunkPtr->stylePtr->sValuePtr->tkfont;
7072
7073#if TK_LAYOUT_WITH_BASE_CHUNKS
7074    if (baseCharChunkPtr == NULL) {
7075	baseCharChunkPtr = chunkPtr;
7076	bciPtr = (BaseCharInfo *) ckalloc(sizeof(BaseCharInfo));
7077	baseString = &bciPtr->baseChars;
7078	Tcl_DStringInit(baseString);
7079	bciPtr->width = 0;
7080
7081	ciPtr = &bciPtr->ci;
7082    } else {
7083	bciPtr = (BaseCharInfo *) baseCharChunkPtr->clientData;
7084	ciPtr = (CharInfo *) ckalloc(sizeof(CharInfo));
7085	baseString = &bciPtr->baseChars;
7086    }
7087
7088    lineOffset = Tcl_DStringLength(baseString);
7089    line = Tcl_DStringAppend(baseString,p,maxBytes);
7090
7091    chunkPtr->clientData = (ClientData) ciPtr;
7092    ciPtr->baseChunkPtr = baseCharChunkPtr;
7093    ciPtr->baseOffset = lineOffset;
7094    ciPtr->chars = NULL;
7095    ciPtr->numBytes = 0;
7096
7097    bytesThatFit = CharChunkMeasureChars(chunkPtr, line,
7098	    lineOffset + maxBytes, lineOffset, -1, chunkPtr->x, maxX,
7099	    TK_ISOLATE_END, &nextX);
7100#else /* !TK_LAYOUT_WITH_BASE_CHUNKS */
7101    bytesThatFit = CharChunkMeasureChars(chunkPtr, p, maxBytes, 0, -1,
7102	    chunkPtr->x, maxX, TK_ISOLATE_END, &nextX);
7103#endif /* TK_LAYOUT_WITH_BASE_CHUNKS */
7104
7105    if (bytesThatFit < maxBytes) {
7106	if ((bytesThatFit == 0) && noCharsYet) {
7107	    Tcl_UniChar ch;
7108	    int chLen = Tcl_UtfToUniChar(p, &ch);
7109
7110#if TK_LAYOUT_WITH_BASE_CHUNKS
7111	    bytesThatFit = CharChunkMeasureChars(chunkPtr, line,
7112		    lineOffset+chLen, lineOffset, -1, chunkPtr->x, -1, 0,
7113		    &nextX);
7114#else /* !TK_LAYOUT_WITH_BASE_CHUNKS */
7115	    bytesThatFit = CharChunkMeasureChars(chunkPtr, p, chLen, 0, -1,
7116		    chunkPtr->x, -1, 0, &nextX);
7117#endif /* TK_LAYOUT_WITH_BASE_CHUNKS */
7118	}
7119	if ((nextX < maxX) && ((p[bytesThatFit] == ' ')
7120		|| (p[bytesThatFit] == '\t'))) {
7121	    /*
7122	     * Space characters are funny, in that they are considered to fit
7123	     * if there is at least one pixel of space left on the line. Just
7124	     * give the space character whatever space is left.
7125	     */
7126
7127	    nextX = maxX;
7128	    bytesThatFit++;
7129	}
7130	if (p[bytesThatFit] == '\n') {
7131	    /*
7132	     * A newline character takes up no space, so if the previous
7133	     * character fits then so does the newline.
7134	     */
7135
7136	    bytesThatFit++;
7137	}
7138	if (bytesThatFit == 0) {
7139#if TK_LAYOUT_WITH_BASE_CHUNKS
7140	    chunkPtr->clientData = NULL;
7141	    if (chunkPtr == baseCharChunkPtr) {
7142		baseCharChunkPtr = NULL;
7143		Tcl_DStringFree(baseString);
7144	    } else {
7145		Tcl_DStringSetLength(baseString,lineOffset);
7146	    }
7147	    ckfree((char *) ciPtr);
7148#endif /* TK_LAYOUT_WITH_BASE_CHUNKS */
7149	    return 0;
7150	}
7151    }
7152
7153    Tk_GetFontMetrics(tkfont, &fm);
7154
7155    /*
7156     * Fill in the chunk structure and allocate and initialize a CharInfo
7157     * structure. If the last character is a newline then don't bother to
7158     * display it.
7159     */
7160
7161    chunkPtr->displayProc = CharDisplayProc;
7162    chunkPtr->undisplayProc = CharUndisplayProc;
7163    chunkPtr->measureProc = CharMeasureProc;
7164    chunkPtr->bboxProc = CharBboxProc;
7165    chunkPtr->numBytes = bytesThatFit;
7166    chunkPtr->minAscent = fm.ascent + chunkPtr->stylePtr->sValuePtr->offset;
7167    chunkPtr->minDescent = fm.descent - chunkPtr->stylePtr->sValuePtr->offset;
7168    chunkPtr->minHeight = 0;
7169    chunkPtr->width = nextX - chunkPtr->x;
7170    chunkPtr->breakIndex = -1;
7171
7172#if !TK_LAYOUT_WITH_BASE_CHUNKS
7173    ciPtr = (CharInfo *)
7174	    ckalloc((unsigned) bytesThatFit + Tk_Offset(CharInfo, chars) + 1);
7175    chunkPtr->clientData = (ClientData) ciPtr;
7176    memcpy(ciPtr->chars, p, (unsigned) bytesThatFit);
7177#endif /* TK_LAYOUT_WITH_BASE_CHUNKS */
7178
7179    ciPtr->numBytes = bytesThatFit;
7180    if (p[bytesThatFit - 1] == '\n') {
7181	ciPtr->numBytes--;
7182    }
7183
7184#if TK_LAYOUT_WITH_BASE_CHUNKS
7185    /*
7186     * Final update for the current base chunk data.
7187     */
7188
7189    Tcl_DStringSetLength(baseString,lineOffset+ciPtr->numBytes);
7190    bciPtr->width = nextX - baseCharChunkPtr->x;
7191
7192    /*
7193     * Finalize the base chunk if this chunk ends in a tab, which definitly
7194     * breaks the context and needs to be handled on a higher level.
7195     */
7196
7197    if (ciPtr->numBytes > 0 && p[ciPtr->numBytes - 1] == '\t') {
7198	FinalizeBaseChunk(chunkPtr);
7199    }
7200#endif /* TK_LAYOUT_WITH_BASE_CHUNKS */
7201
7202    /*
7203     * Compute a break location. If we're in word wrap mode, a break can occur
7204     * after any space character, or at the end of the chunk if the next
7205     * segment (ignoring those with zero size) is not a character segment.
7206     */
7207
7208    if (wrapMode != TEXT_WRAPMODE_WORD) {
7209	chunkPtr->breakIndex = chunkPtr->numBytes;
7210    } else {
7211	for (count = bytesThatFit, p += bytesThatFit - 1; count > 0;
7212		count--, p--) {
7213	    if (UCHAR(*p) < 0x80 && isspace(UCHAR(*p))) {
7214		chunkPtr->breakIndex = count;
7215		break;
7216	    }
7217	}
7218	if ((bytesThatFit + byteOffset) == segPtr->size) {
7219	    for (nextPtr = segPtr->nextPtr; nextPtr != NULL;
7220		    nextPtr = nextPtr->nextPtr) {
7221		if (nextPtr->size != 0) {
7222		    if (nextPtr->typePtr != &tkTextCharType) {
7223			chunkPtr->breakIndex = chunkPtr->numBytes;
7224		    }
7225		    break;
7226		}
7227	    }
7228	}
7229    }
7230    return 1;
7231}
7232
7233/*
7234 *---------------------------------------------------------------------------
7235 *
7236 * CharChunkMeasureChars --
7237 *
7238 *	Determine the number of characters from a char chunk that will fit in
7239 *	the given horizontal span.
7240 *
7241 *	This is the same as MeasureChars (which see), but in the context of a
7242 *	char chunk, i.e. on a higher level of abstraction. Use this function
7243 *	whereever possible instead of plain MeasureChars, so that the right
7244 *	context is used automatically.
7245 *
7246 * Results:
7247 *	The return value is the number of bytes from the range of start to end
7248 *	in source that fit in the span given by startX and maxX. *nextXPtr is
7249 *	filled in with the x-coordinate at which the first character that
7250 *	didn't fit would be drawn, if it were to be drawn.
7251 *
7252 * Side effects:
7253 *	None.
7254 *--------------------------------------------------------------
7255 */
7256
7257static int
7258CharChunkMeasureChars(
7259    TkTextDispChunk *chunkPtr,	/* Chunk from which to measure. */
7260    const char *chars,		/* Chars to use, instead of the chunk's own.
7261				 * Used by the layoutproc during chunk setup.
7262				 * All other callers use NULL. Not
7263				 * NUL-terminated. */
7264    int charsLen,		/* Length of the "chars" parameter. */
7265    int start, int end,		/* The range of chars to measure inside the
7266				 * chunk (or inside the additional chars). */
7267    int startX,			/* Starting x coordinate where the measured
7268				 * span will begin. */
7269    int maxX,			/* Maximum pixel width of the span. May be -1
7270				 * for unlimited. */
7271    int flags,			/* Flags to pass to MeasureChars. */
7272    int *nextXPtr)		/* The function puts the newly calculated
7273				 * right border x-position of the span
7274				 * here. */
7275{
7276    Tk_Font tkfont = chunkPtr->stylePtr->sValuePtr->tkfont;
7277    CharInfo *ciPtr = (CharInfo *) chunkPtr->clientData;
7278
7279#if !TK_LAYOUT_WITH_BASE_CHUNKS
7280    if (chars == NULL) {
7281	chars = ciPtr->chars;
7282	charsLen = ciPtr->numBytes;
7283    }
7284    if (end == -1) {
7285	end = charsLen;
7286    }
7287
7288    return MeasureChars(tkfont, chars, charsLen, start, end-start,
7289	    startX, maxX, flags, nextXPtr);
7290#else
7291    {
7292	int xDisplacement;
7293	int fit, bstart = start, bend = end;
7294
7295	if (chars == NULL) {
7296	    Tcl_DString *baseChars = &((BaseCharInfo *)
7297		    ciPtr->baseChunkPtr->clientData)->baseChars;
7298
7299	    chars = Tcl_DStringValue(baseChars);
7300	    charsLen = Tcl_DStringLength(baseChars);
7301	    bstart += ciPtr->baseOffset;
7302	    if (bend == -1) {
7303		bend = ciPtr->baseOffset + ciPtr->numBytes;
7304	    } else {
7305		bend += ciPtr->baseOffset;
7306	    }
7307	} else if (bend == -1) {
7308	    bend = charsLen;
7309	}
7310
7311	if (bstart == ciPtr->baseOffset) {
7312	    xDisplacement = startX - chunkPtr->x;
7313	} else {
7314	    int widthUntilStart = 0;
7315
7316	    MeasureChars(tkfont, chars, charsLen, 0, bstart,
7317		    0, -1, 0, &widthUntilStart);
7318	    xDisplacement = startX - widthUntilStart - chunkPtr->x;
7319	}
7320
7321	fit = MeasureChars(tkfont, chars, charsLen, 0, bend,
7322		ciPtr->baseChunkPtr->x + xDisplacement, maxX, flags, nextXPtr);
7323
7324	if (fit < bstart) {
7325	    return 0;
7326	} else {
7327	    return fit - bstart;
7328	}
7329    }
7330#endif
7331}
7332
7333/*
7334 *--------------------------------------------------------------
7335 *
7336 * CharDisplayProc --
7337 *
7338 *	This function is called to display a character chunk on the screen or
7339 *	in an off-screen pixmap.
7340 *
7341 * Results:
7342 *	None.
7343 *
7344 * Side effects:
7345 *	Graphics are drawn.
7346 *
7347 *--------------------------------------------------------------
7348 */
7349
7350static void
7351CharDisplayProc(
7352    TkText *textPtr,
7353    TkTextDispChunk *chunkPtr,	/* Chunk that is to be drawn. */
7354    int x,			/* X-position in dst at which to draw this
7355				 * chunk (may differ from the x-position in
7356				 * the chunk because of scrolling). */
7357    int y,			/* Y-position at which to draw this chunk in
7358				 * dst. */
7359    int height,			/* Total height of line. */
7360    int baseline,		/* Offset of baseline from y. */
7361    Display *display,		/* Display to use for drawing. */
7362    Drawable dst,		/* Pixmap or window in which to draw chunk. */
7363    int screenY)		/* Y-coordinate in text window that
7364				 * corresponds to y. */
7365{
7366    CharInfo *ciPtr = (CharInfo *) chunkPtr->clientData;
7367    const char *string;
7368    TextStyle *stylePtr;
7369    StyleValues *sValuePtr;
7370    int numBytes, offsetBytes, offsetX;
7371#if TK_DRAW_IN_CONTEXT
7372    BaseCharInfo *bciPtr;
7373#endif /* TK_DRAW_IN_CONTEXT */
7374
7375    if ((x + chunkPtr->width) <= 0) {
7376	/*
7377	 * The chunk is off-screen.
7378	 */
7379
7380	return;
7381    }
7382
7383#if TK_DRAW_IN_CONTEXT
7384    bciPtr = (BaseCharInfo *) ciPtr->baseChunkPtr->clientData;
7385    numBytes = Tcl_DStringLength(&bciPtr->baseChars);
7386    string = Tcl_DStringValue(&bciPtr->baseChars);
7387
7388#elif TK_LAYOUT_WITH_BASE_CHUNKS
7389    if (ciPtr->baseChunkPtr != chunkPtr) {
7390	/*
7391	 * Without context drawing only base chunks display their foreground.
7392	 */
7393
7394	return;
7395    }
7396
7397    numBytes = Tcl_DStringLength(&((BaseCharInfo *) ciPtr)->baseChars);
7398    string = ciPtr->chars;
7399
7400#else /* !TK_LAYOUT_WITH_BASE_CHUNKS */
7401    numBytes = ciPtr->numBytes;
7402    string = ciPtr->chars;
7403#endif /* TK_LAYOUT_WITH_BASE_CHUNKS */
7404
7405    stylePtr = chunkPtr->stylePtr;
7406    sValuePtr = stylePtr->sValuePtr;
7407
7408    /*
7409     * If the text sticks out way to the left of the window, skip over the
7410     * characters that aren't in the visible part of the window. This is
7411     * essential if x is very negative (such as less than 32K); otherwise
7412     * overflow problems will occur in servers that use 16-bit arithmetic,
7413     * like X.
7414     */
7415
7416    offsetX = x;
7417    offsetBytes = 0;
7418    if (x < 0) {
7419	offsetBytes = CharChunkMeasureChars(chunkPtr, NULL, 0, 0, -1,
7420		x, 0, 0, &offsetX);
7421    }
7422
7423    /*
7424     * Draw the text, underline, and overstrike for this chunk.
7425     */
7426
7427    if (!sValuePtr->elide && (numBytes > offsetBytes)
7428	    && (stylePtr->fgGC != None)) {
7429#if TK_DRAW_IN_CONTEXT
7430	int start = ciPtr->baseOffset + offsetBytes;
7431	int len = ciPtr->numBytes - offsetBytes;
7432	int xDisplacement = x - chunkPtr->x;
7433
7434	if ((len > 0) && (string[start + len - 1] == '\t')) {
7435	    len--;
7436	}
7437	if (len <= 0) {
7438	    return;
7439	}
7440
7441	TkpDrawCharsInContext(display, dst, stylePtr->fgGC, sValuePtr->tkfont,
7442		string, numBytes, start, len,
7443		ciPtr->baseChunkPtr->x + xDisplacement,
7444		y + baseline - sValuePtr->offset);
7445
7446	if (sValuePtr->underline) {
7447	    TkUnderlineCharsInContext(display, dst, stylePtr->fgGC,
7448		    sValuePtr->tkfont, string, numBytes,
7449		    ciPtr->baseChunkPtr->x + xDisplacement,
7450		    y + baseline - sValuePtr->offset,
7451		    start, start+len);
7452	}
7453	if (sValuePtr->overstrike) {
7454	    Tk_FontMetrics fm;
7455
7456	    Tk_GetFontMetrics(sValuePtr->tkfont, &fm);
7457	    TkUnderlineCharsInContext(display, dst, stylePtr->fgGC,
7458		    sValuePtr->tkfont, string, numBytes,
7459		    ciPtr->baseChunkPtr->x + xDisplacement,
7460		    y + baseline - sValuePtr->offset
7461			    - fm.descent - (fm.ascent * 3) / 10,
7462		    start, start+len);
7463	}
7464#else /* !TK_DRAW_IN_CONTEXT */
7465	string += offsetBytes;
7466	numBytes -= offsetBytes;
7467
7468	if ((numBytes > 0) && (string[numBytes - 1] == '\t')) {
7469	    numBytes--;
7470	}
7471	Tk_DrawChars(display, dst, stylePtr->fgGC, sValuePtr->tkfont, string,
7472		numBytes, offsetX, y + baseline - sValuePtr->offset);
7473	if (sValuePtr->underline) {
7474	    Tk_UnderlineChars(display, dst, stylePtr->fgGC, sValuePtr->tkfont,
7475		    string, offsetX,
7476		    y + baseline - sValuePtr->offset,
7477		    0, numBytes);
7478
7479	}
7480	if (sValuePtr->overstrike) {
7481	    Tk_FontMetrics fm;
7482
7483	    Tk_GetFontMetrics(sValuePtr->tkfont, &fm);
7484	    Tk_UnderlineChars(display, dst, stylePtr->fgGC, sValuePtr->tkfont,
7485		    string, offsetX,
7486		    y + baseline - sValuePtr->offset
7487			    - fm.descent - (fm.ascent * 3) / 10,
7488		    0, numBytes);
7489	}
7490#endif /* TK_DRAW_IN_CONTEXT */
7491    }
7492}
7493
7494/*
7495 *--------------------------------------------------------------
7496 *
7497 * CharUndisplayProc --
7498 *
7499 *	This function is called when a character chunk is no longer going to
7500 *	be displayed. It frees up resources that were allocated to display the
7501 *	chunk.
7502 *
7503 * Results:
7504 *	None.
7505 *
7506 * Side effects:
7507 *	Memory and other resources get freed.
7508 *
7509 *--------------------------------------------------------------
7510 */
7511
7512static void
7513CharUndisplayProc(
7514    TkText *textPtr,		/* Overall information about text widget. */
7515    TkTextDispChunk *chunkPtr)	/* Chunk that is about to be freed. */
7516{
7517    CharInfo *ciPtr = (CharInfo *) chunkPtr->clientData;
7518
7519    if (ciPtr) {
7520#if TK_LAYOUT_WITH_BASE_CHUNKS
7521	if (chunkPtr == ciPtr->baseChunkPtr) {
7522	    /*
7523	     * Basechunks are undisplayed first, when DLines are freed or
7524	     * partially freed, so this makes sure we don't access their data
7525	     * any more.
7526	     */
7527
7528	    FreeBaseChunk(chunkPtr);
7529	} else if (ciPtr->baseChunkPtr != NULL) {
7530	    /*
7531	     * When other char chunks are undisplayed, drop their characters
7532	     * from the base chunk. This usually happens, when they are last
7533	     * in a line and need to be re-layed out.
7534	     */
7535
7536	    RemoveFromBaseChunk(chunkPtr);
7537	}
7538
7539	ciPtr->baseChunkPtr = NULL;
7540	ciPtr->chars = NULL;
7541	ciPtr->numBytes = 0;
7542#endif /* TK_LAYOUT_WITH_BASE_CHUNKS */
7543
7544	ckfree((char *) ciPtr);
7545	chunkPtr->clientData = NULL;
7546    }
7547}
7548
7549/*
7550 *--------------------------------------------------------------
7551 *
7552 * CharMeasureProc --
7553 *
7554 *	This function is called to determine which character in a character
7555 *	chunk lies over a given x-coordinate.
7556 *
7557 * Results:
7558 *	The return value is the index *within the chunk* of the character that
7559 *	covers the position given by "x".
7560 *
7561 * Side effects:
7562 *	None.
7563 *
7564 *--------------------------------------------------------------
7565 */
7566
7567static int
7568CharMeasureProc(
7569    TkTextDispChunk *chunkPtr,	/* Chunk containing desired coord. */
7570    int x)			/* X-coordinate, in same coordinate system as
7571				 * chunkPtr->x. */
7572{
7573    int endX;
7574
7575    return CharChunkMeasureChars(chunkPtr, NULL, 0, 0, chunkPtr->numBytes-1,
7576	    chunkPtr->x, x, 0, &endX); /* CHAR OFFSET */
7577}
7578
7579/*
7580 *--------------------------------------------------------------
7581 *
7582 * CharBboxProc --
7583 *
7584 *	This function is called to compute the bounding box of the area
7585 *	occupied by a single character.
7586 *
7587 * Results:
7588 *	There is no return value. *xPtr and *yPtr are filled in with the
7589 *	coordinates of the upper left corner of the character, and *widthPtr
7590 *	and *heightPtr are filled in with the dimensions of the character in
7591 *	pixels. Note: not all of the returned bbox is necessarily visible on
7592 *	the screen (the rightmost part might be off-screen to the right, and
7593 *	the bottommost part might be off-screen to the bottom).
7594 *
7595 * Side effects:
7596 *	None.
7597 *
7598 *--------------------------------------------------------------
7599 */
7600
7601static void
7602CharBboxProc(
7603    TkText *textPtr,
7604    TkTextDispChunk *chunkPtr,	/* Chunk containing desired char. */
7605    int byteIndex,		/* Byte offset of desired character within the
7606				 * chunk. */
7607    int y,			/* Topmost pixel in area allocated for this
7608				 * line. */
7609    int lineHeight,		/* Height of line, in pixels. */
7610    int baseline,		/* Location of line's baseline, in pixels
7611				 * measured down from y. */
7612    int *xPtr, int *yPtr,	/* Gets filled in with coords of character's
7613				 * upper-left pixel. X-coord is in same
7614				 * coordinate system as chunkPtr->x. */
7615    int *widthPtr,		/* Gets filled in with width of character, in
7616				 * pixels. */
7617    int *heightPtr)		/* Gets filled in with height of character, in
7618				 * pixels. */
7619{
7620    CharInfo *ciPtr = (CharInfo *) chunkPtr->clientData;
7621    int maxX;
7622
7623    maxX = chunkPtr->width + chunkPtr->x;
7624    CharChunkMeasureChars(chunkPtr, NULL, 0, 0, byteIndex,
7625	    chunkPtr->x, -1, 0, xPtr);
7626
7627    if (byteIndex == ciPtr->numBytes) {
7628	/*
7629	 * This situation only happens if the last character in a line is a
7630	 * space character, in which case it absorbs all of the extra space in
7631	 * the line (see TkTextCharLayoutProc).
7632	 */
7633
7634	*widthPtr = maxX - *xPtr;
7635    } else if ((ciPtr->chars[byteIndex] == '\t')
7636	    && (byteIndex == ciPtr->numBytes - 1)) {
7637	/*
7638	 * The desired character is a tab character that terminates a chunk;
7639	 * give it all the space left in the chunk.
7640	 */
7641
7642	*widthPtr = maxX - *xPtr;
7643    } else {
7644	CharChunkMeasureChars(chunkPtr, NULL, 0, byteIndex, byteIndex+1,
7645		*xPtr, -1, 0, widthPtr);
7646	if (*widthPtr > maxX) {
7647	    *widthPtr = maxX - *xPtr;
7648	} else {
7649	    *widthPtr -= *xPtr;
7650	}
7651    }
7652    *yPtr = y + baseline - chunkPtr->minAscent;
7653    *heightPtr = chunkPtr->minAscent + chunkPtr->minDescent;
7654}
7655
7656/*
7657 *----------------------------------------------------------------------
7658 *
7659 * AdjustForTab --
7660 *
7661 *	This function is called to move a series of chunks right in order to
7662 *	align them with a tab stop.
7663 *
7664 * Results:
7665 *	None.
7666 *
7667 * Side effects:
7668 *	The width of chunkPtr gets adjusted so that it absorbs the extra space
7669 *	due to the tab. The x locations in all the chunks after chunkPtr are
7670 *	adjusted rightward to align with the tab stop given by tabArrayPtr and
7671 *	index.
7672 *
7673 *----------------------------------------------------------------------
7674 */
7675
7676static void
7677AdjustForTab(
7678    TkText *textPtr,		/* Information about the text widget as a
7679				 * whole. */
7680    TkTextTabArray *tabArrayPtr,/* Information about the tab stops that apply
7681				 * to this line. May be NULL to indicate
7682				 * default tabbing (every 8 chars). */
7683    int index,			/* Index of current tab stop. */
7684    TkTextDispChunk *chunkPtr)	/* Chunk whose last character is the tab; the
7685				 * following chunks contain information to be
7686				 * shifted right. */
7687{
7688    int x, desired, delta, width, decimal, i, gotDigit;
7689    TkTextDispChunk *chunkPtr2, *decimalChunkPtr;
7690    CharInfo *ciPtr;
7691    int tabX, spaceWidth;
7692    const char *p;
7693    TkTextTabAlign alignment;
7694
7695    if (chunkPtr->nextPtr == NULL) {
7696	/*
7697	 * Nothing after the actual tab; just return.
7698	 */
7699
7700	return;
7701    }
7702
7703    x = chunkPtr->nextPtr->x;
7704
7705    /*
7706     * If no tab information has been given, assuming tab stops are at 8
7707     * average-sized characters. Still ensure we respect the tabular versus
7708     * wordprocessor tab style.
7709     */
7710
7711    if ((tabArrayPtr == NULL) || (tabArrayPtr->numTabs == 0)) {
7712	/*
7713	 * No tab information has been given, so use the default
7714	 * interpretation of tabs.
7715	 */
7716
7717	if (textPtr->tabStyle == TK_TEXT_TABSTYLE_TABULAR) {
7718	    int tabWidth = Tk_TextWidth(textPtr->tkfont, "0", 1) * 8;
7719	    if (tabWidth == 0) {
7720		tabWidth = 1;
7721	    }
7722
7723	    desired = tabWidth * (index + 1);
7724	} else {
7725	    desired = NextTabStop(textPtr->tkfont, x, 0);
7726	}
7727
7728	goto update;
7729    }
7730
7731    if (index < tabArrayPtr->numTabs) {
7732	alignment = tabArrayPtr->tabs[index].alignment;
7733	tabX = tabArrayPtr->tabs[index].location;
7734    } else {
7735	/*
7736	 * Ran out of tab stops; compute a tab position by extrapolating from
7737	 * the last two tab positions.
7738	 */
7739
7740	tabX = (int) (tabArrayPtr->lastTab +
7741		(index + 1 - tabArrayPtr->numTabs)*tabArrayPtr->tabIncrement +
7742		0.5);
7743	alignment = tabArrayPtr->tabs[tabArrayPtr->numTabs-1].alignment;
7744    }
7745
7746    if (alignment == LEFT) {
7747	desired = tabX;
7748	goto update;
7749    }
7750
7751    if ((alignment == CENTER) || (alignment == RIGHT)) {
7752	/*
7753	 * Compute the width of all the information in the tab group, then use
7754	 * it to pick a desired location.
7755	 */
7756
7757	width = 0;
7758	for (chunkPtr2 = chunkPtr->nextPtr; chunkPtr2 != NULL;
7759		chunkPtr2 = chunkPtr2->nextPtr) {
7760	    width += chunkPtr2->width;
7761	}
7762	if (alignment == CENTER) {
7763	    desired = tabX - width/2;
7764	} else {
7765	    desired = tabX - width;
7766	}
7767	goto update;
7768    }
7769
7770    /*
7771     * Must be numeric alignment. Search through the text to be tabbed,
7772     * looking for the last , or . before the first character that isn't a
7773     * number, comma, period, or sign.
7774     */
7775
7776    decimalChunkPtr = NULL;
7777    decimal = gotDigit = 0;
7778    for (chunkPtr2 = chunkPtr->nextPtr; chunkPtr2 != NULL;
7779	    chunkPtr2 = chunkPtr2->nextPtr) {
7780	if (chunkPtr2->displayProc != CharDisplayProc) {
7781	    continue;
7782	}
7783	ciPtr = (CharInfo *) chunkPtr2->clientData;
7784	for (p = ciPtr->chars, i = 0; i < ciPtr->numBytes; p++, i++) {
7785	    if (isdigit(UCHAR(*p))) {
7786		gotDigit = 1;
7787	    } else if ((*p == '.') || (*p == ',')) {
7788		decimal = p-ciPtr->chars;
7789		decimalChunkPtr = chunkPtr2;
7790	    } else if (gotDigit) {
7791		if (decimalChunkPtr == NULL) {
7792		    decimal = p-ciPtr->chars;
7793		    decimalChunkPtr = chunkPtr2;
7794		}
7795		goto endOfNumber;
7796	    }
7797	}
7798    }
7799
7800  endOfNumber:
7801    if (decimalChunkPtr != NULL) {
7802	int curX;
7803
7804	ciPtr = (CharInfo *) decimalChunkPtr->clientData;
7805	CharChunkMeasureChars(decimalChunkPtr, NULL, 0, 0, decimal,
7806		decimalChunkPtr->x, -1, 0, &curX);
7807	desired = tabX - (curX - x);
7808	goto update;
7809    } else {
7810	/*
7811	 * There wasn't a decimal point. Right justify the text.
7812	 */
7813
7814	width = 0;
7815	for (chunkPtr2 = chunkPtr->nextPtr; chunkPtr2 != NULL;
7816		chunkPtr2 = chunkPtr2->nextPtr) {
7817	    width += chunkPtr2->width;
7818	}
7819	desired = tabX - width;
7820    }
7821
7822    /*
7823     * Shift all of the chunks to the right so that the left edge is at the
7824     * desired location, then expand the chunk containing the tab. Be sure
7825     * that the tab occupies at least the width of a space character.
7826     */
7827
7828  update:
7829    delta = desired - x;
7830    MeasureChars(textPtr->tkfont, " ", 1, 0, 1, 0, -1, 0, &spaceWidth);
7831    if (delta < spaceWidth) {
7832	delta = spaceWidth;
7833    }
7834    for (chunkPtr2 = chunkPtr->nextPtr; chunkPtr2 != NULL;
7835	    chunkPtr2 = chunkPtr2->nextPtr) {
7836	chunkPtr2->x += delta;
7837    }
7838    chunkPtr->width += delta;
7839}
7840
7841/*
7842 *----------------------------------------------------------------------
7843 *
7844 * SizeOfTab --
7845 *
7846 *	This returns an estimate of the amount of white space that will be
7847 *	consumed by a tab.
7848 *
7849 * Results:
7850 *	The return value is the minimum number of pixels that will be occupied
7851 *	by the next tab of tabArrayPtr, assuming that the current position on
7852 *	the line is x and the end of the line is maxX. The 'next tab' is
7853 *	determined by a combination of the current position (x) which it must
7854 *	be equal to or beyond, and the tab count in indexPtr.
7855 *
7856 *	For numeric tabs, this is a conservative estimate. The return value is
7857 *	always >= 0.
7858 *
7859 * Side effects:
7860 *	None.
7861 *
7862 *----------------------------------------------------------------------
7863 */
7864
7865static int
7866SizeOfTab(
7867    TkText *textPtr,		/* Information about the text widget as a
7868				 * whole. */
7869    int tabStyle,		/* One of TK_TEXT_TABSTYLE_TABULAR
7870				 * or TK_TEXT_TABSTYLE_WORDPROCESSOR. */
7871    TkTextTabArray *tabArrayPtr,/* Information about the tab stops that apply
7872				 * to this line. NULL means use default
7873				 * tabbing (every 8 chars.) */
7874    int *indexPtr,		/* Contains index of previous tab stop, will
7875				 * be updated to reflect the number of stops
7876				 * used. */
7877    int x,			/* Current x-location in line. */
7878    int maxX)			/* X-location of pixel just past the right
7879				 * edge of the line. */
7880{
7881    int tabX, result, index, spaceWidth, tabWidth;
7882    TkTextTabAlign alignment;
7883
7884    index = *indexPtr;
7885
7886    if ((tabArrayPtr == NULL) || (tabArrayPtr->numTabs == 0)) {
7887	/*
7888	 * We're using a default tab spacing of 8 characters.
7889	 */
7890
7891	tabWidth = Tk_TextWidth(textPtr->tkfont, "0", 1) * 8;
7892	if (tabWidth == 0) {
7893	    tabWidth = 1;
7894	}
7895    } else {
7896	tabWidth = 0;		/* Avoid compiler error. */
7897    }
7898
7899    do {
7900	/*
7901	 * We were given the count before this tab, so increment it first.
7902	 */
7903
7904	index++;
7905
7906	if ((tabArrayPtr == NULL) || (tabArrayPtr->numTabs == 0)) {
7907	    /*
7908	     * We're using a default tab spacing calculated above.
7909	     */
7910
7911	    tabX = tabWidth * (index + 1);
7912	    alignment = LEFT;
7913	} else if (index < tabArrayPtr->numTabs) {
7914	    tabX = tabArrayPtr->tabs[index].location;
7915	    alignment = tabArrayPtr->tabs[index].alignment;
7916	} else {
7917	    /*
7918	     * Ran out of tab stops; compute a tab position by extrapolating.
7919	     */
7920
7921	    tabX = (int) (tabArrayPtr->lastTab
7922		    + (index + 1 - tabArrayPtr->numTabs)
7923		    * tabArrayPtr->tabIncrement + 0.5);
7924	    alignment = tabArrayPtr->tabs[tabArrayPtr->numTabs-1].alignment;
7925	}
7926
7927	/*
7928	 * If this tab stop is before the current x position, then we have two
7929	 * cases:
7930	 *
7931	 * With 'wordprocessor' style tabs, we must obviously continue until
7932	 * we reach the text tab stop.
7933	 *
7934	 * With 'tabular' style tabs, we always use the index'th tab stop.
7935	 */
7936    } while (tabX <= x && (tabStyle == TK_TEXT_TABSTYLE_WORDPROCESSOR));
7937
7938    /*
7939     * Inform our caller of how many tab stops we've used up.
7940     */
7941
7942    *indexPtr = index;
7943
7944    if (alignment == CENTER) {
7945	/*
7946	 * Be very careful in the arithmetic below, because maxX may be the
7947	 * largest positive number: watch out for integer overflow.
7948	 */
7949
7950	if ((maxX-tabX) < (tabX - x)) {
7951	    result = (maxX - x) - 2*(maxX - tabX);
7952	} else {
7953	    result = 0;
7954	}
7955	goto done;
7956    }
7957    if (alignment == RIGHT) {
7958	result = 0;
7959	goto done;
7960    }
7961
7962    /*
7963     * Note: this treats NUMERIC alignment the same as LEFT alignment, which
7964     * is somewhat conservative. However, it's pretty tricky at this point to
7965     * figure out exactly where the damn decimal point will be.
7966     */
7967
7968    if (tabX > x) {
7969	result = tabX - x;
7970    } else {
7971	result = 0;
7972    }
7973
7974  done:
7975    MeasureChars(textPtr->tkfont, " ", 1, 0, 1, 0, -1, 0, &spaceWidth);
7976    if (result < spaceWidth) {
7977	result = spaceWidth;
7978    }
7979    return result;
7980}
7981
7982/*
7983 *---------------------------------------------------------------------------
7984 *
7985 * NextTabStop --
7986 *
7987 *	Given the current position, determine where the next default tab stop
7988 *	would be located. This function is called when the current chunk in
7989 *	the text has no tabs defined and so the default tab spacing for the
7990 *	font should be used, provided we are using wordprocessor style tabs.
7991 *
7992 * Results:
7993 *	The location in pixels of the next tab stop.
7994 *
7995 * Side effects:
7996 *	None.
7997 *
7998 *---------------------------------------------------------------------------
7999 */
8000
8001static int
8002NextTabStop(
8003    Tk_Font tkfont,		/* Font in which chunk that contains tab stop
8004				 * will be drawn. */
8005    int x,			/* X-position in pixels where last character
8006				 * was drawn. The next tab stop occurs
8007				 * somewhere after this location. */
8008    int tabOrigin)		/* The origin for tab stops. May be non-zero
8009				 * if text has been scrolled. */
8010{
8011    int tabWidth, rem;
8012
8013    tabWidth = Tk_TextWidth(tkfont, "0", 1) * 8;
8014    if (tabWidth == 0) {
8015	tabWidth = 1;
8016    }
8017
8018    x += tabWidth;
8019    rem = (x - tabOrigin) % tabWidth;
8020    if (rem < 0) {
8021	rem += tabWidth;
8022    }
8023    x -= rem;
8024    return x;
8025}
8026
8027/*
8028 *---------------------------------------------------------------------------
8029 *
8030 * MeasureChars --
8031 *
8032 *	Determine the number of characters from the string that will fit in
8033 *	the given horizontal span. The measurement is done under the
8034 *	assumption that Tk_DrawChars will be used to actually display the
8035 *	characters.
8036 *
8037 *	If tabs are encountered in the string, they will be ignored (they
8038 *	should only occur as last character of the string anyway).
8039 *
8040 *	If a newline is encountered in the string, the line will be broken at
8041 *	that point.
8042 *
8043 * Results:
8044 *	The return value is the number of bytes from the range of start to end
8045 *	in source that fit in the span given by startX and maxX. *nextXPtr is
8046 *	filled in with the x-coordinate at which the first character that
8047 *	didn't fit would be drawn, if it were to be drawn.
8048 *
8049 * Side effects:
8050 *	None.
8051 *
8052 *--------------------------------------------------------------
8053 */
8054
8055static int
8056MeasureChars(
8057    Tk_Font tkfont,		/* Font in which to draw characters. */
8058    CONST char *source,		/* Characters to be displayed. Need not be
8059				 * NULL-terminated. */
8060    int maxBytes,		/* Maximum # of bytes to consider from
8061				 * source. */
8062    int rangeStart, int rangeLength,
8063				/* Range of bytes to consider in source.*/
8064    int startX,			/* X-position at which first character will be
8065				 * drawn. */
8066    int maxX,			/* Don't consider any character that would
8067				 * cross this x-position. */
8068    int flags,			/* Flags to pass to Tk_MeasureChars. */
8069    int *nextXPtr)		/* Return x-position of terminating character
8070				 * here. */
8071{
8072    int curX, width, ch;
8073    CONST char *special, *end, *start;
8074
8075    ch = 0;			/* lint. */
8076    curX = startX;
8077    start = source + rangeStart;
8078    end = start + rangeLength;
8079    special = start;
8080    while (start < end) {
8081	if (start >= special) {
8082	    /*
8083	     * Find the next special character in the string.
8084	     */
8085
8086	    for (special = start; special < end; special++) {
8087		ch = *special;
8088		if ((ch == '\t') || (ch == '\n')) {
8089		    break;
8090		}
8091	    }
8092	}
8093
8094	/*
8095	 * Special points at the next special character (or the end of the
8096	 * string). Process characters between start and special.
8097	 */
8098
8099	if ((maxX >= 0) && (curX >= maxX)) {
8100	    break;
8101	}
8102#if TK_DRAW_IN_CONTEXT
8103	start += TkpMeasureCharsInContext(tkfont, source, maxBytes,
8104		start - source, special - start,
8105		maxX >= 0 ? maxX - curX : -1, flags, &width);
8106#else
8107	(void) maxBytes;
8108	start += Tk_MeasureChars(tkfont, start, special - start,
8109		maxX >= 0 ? maxX - curX : -1, flags, &width);
8110#endif /* TK_DRAW_IN_CONTEXT */
8111	curX += width;
8112	if (start < special) {
8113	    /*
8114	     * No more chars fit in line.
8115	     */
8116
8117	    break;
8118	}
8119	if (special < end) {
8120	    if (ch == '\t') {
8121		start++;
8122	    } else {
8123		break;
8124	    }
8125	}
8126    }
8127
8128    *nextXPtr = curX;
8129    return start - (source+rangeStart);
8130}
8131
8132/*
8133 *----------------------------------------------------------------------
8134 *
8135 * TextGetScrollInfoObj --
8136 *
8137 *	This function is invoked to parse "xview" and "yview" scrolling
8138 *	commands for text widgets using the new scrolling command syntax
8139 *	("moveto" or "scroll" options). It extends the public
8140 *	Tk_GetScrollInfoObj function with the addition of "pixels" as a valid
8141 *	unit alongside "pages" and "units". It is a shame the core API isn't
8142 *	more flexible in this regard.
8143 *
8144 * Results:
8145 *	The return value is either TKTEXT_SCROLL_MOVETO, TKTEXT_SCROLL_PAGES,
8146 *	TKTEXT_SCROLL_UNITS, TKTEXT_SCROLL_PIXELS or TKTEXT_SCROLL_ERROR. This
8147 *	indicates whether the command was successfully parsed and what form
8148 *	the command took. If TKTEXT_SCROLL_MOVETO, *dblPtr is filled in with
8149 *	the desired position; if TKTEXT_SCROLL_PAGES, TKTEXT_SCROLL_PIXELS or
8150 *	TKTEXT_SCROLL_UNITS, *intPtr is filled in with the number of
8151 *	pages/pixels/lines to move (may be negative); if TKTEXT_SCROLL_ERROR,
8152 *	the interp's result contains an error message.
8153 *
8154 * Side effects:
8155 *	None.
8156 *
8157 *----------------------------------------------------------------------
8158 */
8159
8160static int
8161TextGetScrollInfoObj(
8162    Tcl_Interp *interp,		/* Used for error reporting. */
8163    TkText *textPtr,		/* Information about the text widget. */
8164    int objc,			/* # arguments for command. */
8165    Tcl_Obj *CONST objv[],	/* Arguments for command. */
8166    double *dblPtr,		/* Filled in with argument "moveto" option, if
8167				 * any. */
8168    int *intPtr)		/* Filled in with number of pages or lines or
8169				 * pixels to scroll, if any. */
8170{
8171    static CONST char *subcommands[] = {
8172	"moveto", "scroll", NULL
8173    };
8174    enum viewSubcmds {
8175	VIEW_MOVETO, VIEW_SCROLL
8176    };
8177    static CONST char *units[] = {
8178	"units", "pages", "pixels", NULL
8179    };
8180    enum viewUnits {
8181	VIEW_SCROLL_UNITS, VIEW_SCROLL_PAGES, VIEW_SCROLL_PIXELS
8182    };
8183    int index;
8184
8185    if (Tcl_GetIndexFromObj(interp, objv[2], subcommands, "option", 0,
8186	    &index) != TCL_OK) {
8187	return TKTEXT_SCROLL_ERROR;
8188    }
8189
8190    switch ((enum viewSubcmds) index) {
8191    case VIEW_MOVETO:
8192	if (objc != 4) {
8193	    Tcl_WrongNumArgs(interp, 3, objv, "fraction");
8194	    return TKTEXT_SCROLL_ERROR;
8195	}
8196	if (Tcl_GetDoubleFromObj(interp, objv[3], dblPtr) != TCL_OK) {
8197	    return TKTEXT_SCROLL_ERROR;
8198	}
8199	return TKTEXT_SCROLL_MOVETO;
8200    case VIEW_SCROLL:
8201	if (objc != 5) {
8202	    Tcl_WrongNumArgs(interp, 3, objv, "number units|pages|pixels");
8203	    return TKTEXT_SCROLL_ERROR;
8204	}
8205	if (Tcl_GetIndexFromObj(interp, objv[4], units, "argument", 0,
8206		&index) != TCL_OK) {
8207	    return TKTEXT_SCROLL_ERROR;
8208	}
8209	switch ((enum viewUnits) index) {
8210	case VIEW_SCROLL_PAGES:
8211	    if (Tcl_GetIntFromObj(interp, objv[3], intPtr) != TCL_OK) {
8212		return TKTEXT_SCROLL_ERROR;
8213	    }
8214	    return TKTEXT_SCROLL_PAGES;
8215	case VIEW_SCROLL_PIXELS:
8216	    if (Tk_GetPixelsFromObj(interp, textPtr->tkwin, objv[3],
8217		    intPtr) != TCL_OK) {
8218		return TKTEXT_SCROLL_ERROR;
8219	    }
8220	    return TKTEXT_SCROLL_PIXELS;
8221	case VIEW_SCROLL_UNITS:
8222	    if (Tcl_GetIntFromObj(interp, objv[3], intPtr) != TCL_OK) {
8223		return TKTEXT_SCROLL_ERROR;
8224	    }
8225	    return TKTEXT_SCROLL_UNITS;
8226	}
8227    }
8228    Tcl_Panic("unexpected switch fallthrough");
8229    return TKTEXT_SCROLL_ERROR;
8230}
8231
8232#if TK_LAYOUT_WITH_BASE_CHUNKS
8233/*
8234 *----------------------------------------------------------------------
8235 *
8236 * FinalizeBaseChunk --
8237 *
8238 *	This procedure makes sure that all the chunks of the stretch are
8239 *	up-to-date. It is invoked when the LayoutProc has been called for all
8240 *	chunks and the base chunk is stable.
8241 *
8242 * Results:
8243 *	None.
8244 *
8245 * Side effects:
8246 *	The CharInfo.chars of all dependent chunks point into
8247 *	BaseCharInfo.baseChars for easy access (and compatibility).
8248 *
8249 *----------------------------------------------------------------------
8250 */
8251
8252static void
8253FinalizeBaseChunk(
8254    TkTextDispChunk *addChunkPtr)
8255				/* An additional chunk to add to the stretch,
8256				 * even though it may not be in the linked
8257				 * list yet. Used by the LayoutProc, otherwise
8258				 * NULL. */
8259{
8260    const char *baseChars;
8261    TkTextDispChunk *chunkPtr;
8262    CharInfo *ciPtr;
8263#if TK_DRAW_IN_CONTEXT
8264    int widthAdjust = 0;
8265    int newwidth;
8266#endif /* TK_DRAW_IN_CONTEXT */
8267
8268    if (baseCharChunkPtr == NULL) {
8269	return;
8270    }
8271
8272    baseChars = Tcl_DStringValue(
8273	    &((BaseCharInfo *) baseCharChunkPtr->clientData)->baseChars);
8274
8275    for (chunkPtr = baseCharChunkPtr; chunkPtr != NULL;
8276	    chunkPtr = chunkPtr->nextPtr) {
8277#if TK_DRAW_IN_CONTEXT
8278	chunkPtr->x += widthAdjust;
8279#endif /* TK_DRAW_IN_CONTEXT */
8280
8281	if (chunkPtr->displayProc != CharDisplayProc) {
8282	    continue;
8283	}
8284	ciPtr = (CharInfo *)chunkPtr->clientData;
8285	if (ciPtr->baseChunkPtr != baseCharChunkPtr) {
8286	    break;
8287	}
8288	ciPtr->chars = baseChars + ciPtr->baseOffset;
8289
8290#if TK_DRAW_IN_CONTEXT
8291	newwidth = 0;
8292	CharChunkMeasureChars(chunkPtr, NULL, 0, 0, -1, 0, -1, 0, &newwidth);
8293	if (newwidth != chunkPtr->width) {
8294	    widthAdjust += newwidth - chunkPtr->width;
8295	    chunkPtr->width = newwidth;
8296	}
8297#endif /* TK_DRAW_IN_CONTEXT */
8298    }
8299
8300    if (addChunkPtr != NULL) {
8301	ciPtr = (CharInfo *)addChunkPtr->clientData;
8302	ciPtr->chars = baseChars + ciPtr->baseOffset;
8303
8304#if TK_DRAW_IN_CONTEXT
8305	addChunkPtr->x += widthAdjust;
8306	CharChunkMeasureChars(addChunkPtr, NULL, 0, 0, -1, 0, -1, 0,
8307		&addChunkPtr->width);
8308#endif /* TK_DRAW_IN_CONTEXT */
8309    }
8310
8311    baseCharChunkPtr = NULL;
8312}
8313
8314/*
8315 *----------------------------------------------------------------------
8316 *
8317 * FreeBaseChunk --
8318 *
8319 *	This procedure makes sure that all the chunks of the stretch are
8320 *	disconnected from the base chunk and the base chunk specific data is
8321 *	freed. It is invoked from the UndisplayProc. The procedure doesn't
8322 *	ckfree the base chunk clientData itself, that's up to the main
8323 *	UndisplayProc.
8324 *
8325 * Results:
8326 *	None.
8327 *
8328 * Side effects:
8329 *	The CharInfo.chars of all dependent chunks are set to NULL. Memory
8330 *	that belongs specifically to the base chunk is freed.
8331 *
8332 *----------------------------------------------------------------------
8333 */
8334
8335static void
8336FreeBaseChunk(
8337    TkTextDispChunk *baseChunkPtr)
8338				/* The base chunk of the stretch and head of
8339				 * the linked list. */
8340{
8341    TkTextDispChunk *chunkPtr;
8342    CharInfo *ciPtr;
8343
8344    if (baseCharChunkPtr == baseChunkPtr) {
8345	baseCharChunkPtr = NULL;
8346    }
8347
8348    for (chunkPtr=baseChunkPtr; chunkPtr!=NULL; chunkPtr=chunkPtr->nextPtr) {
8349	if (chunkPtr->undisplayProc != CharUndisplayProc) {
8350	    continue;
8351	}
8352	ciPtr = (CharInfo *) chunkPtr->clientData;
8353	if (ciPtr->baseChunkPtr != baseChunkPtr) {
8354	    break;
8355	}
8356
8357	ciPtr->baseChunkPtr = NULL;
8358	ciPtr->chars = NULL;
8359    }
8360
8361    Tcl_DStringFree(&((BaseCharInfo *) baseChunkPtr->clientData)->baseChars);
8362}
8363
8364/*
8365 *----------------------------------------------------------------------
8366 *
8367 * IsSameFGStyle --
8368 *
8369 *	Compare the foreground attributes of two styles. Specifically must
8370 *	consider: foreground color, font, font style and font decorations,
8371 *	elide, "offset" and foreground stipple. Do *not* consider: background
8372 *	color, border, relief or background stipple.
8373 *
8374 *	If we use TkpDrawCharsInContext(), we also don't need to check
8375 *	foreground color, font decorations, elide, offset and foreground
8376 *	stipple, so all that is left is font (including font size and font
8377 *	style) and "offset".
8378 *
8379 * Results:
8380 *	1 if the two styles match, 0 otherwise.
8381 *
8382 * Side effects:
8383 *	None.
8384 *
8385 *----------------------------------------------------------------------
8386 */
8387
8388static int
8389IsSameFGStyle(
8390    TextStyle *style1,
8391    TextStyle *style2)
8392{
8393    StyleValues *sv1;
8394    StyleValues *sv2;
8395
8396    if (style1 == style2) {
8397	return 1;
8398    }
8399
8400#if !TK_DRAW_IN_CONTEXT
8401    if (
8402#ifdef MAC_OSX_TK
8403	    !TkMacOSXCompareColors(style1->fgGC->foreground,
8404		    style2->fgGC->foreground)
8405#else
8406	    style1->fgGC->foreground != style2->fgGC->foreground
8407#endif
8408	    ) {
8409	return 0;
8410    }
8411#endif /* !TK_DRAW_IN_CONTEXT */
8412
8413    sv1 = style1->sValuePtr;
8414    sv2 = style2->sValuePtr;
8415
8416#if TK_DRAW_IN_CONTEXT
8417    return sv1->tkfont == sv2->tkfont && sv1->offset == sv2->offset;
8418#else
8419    return sv1->tkfont == sv2->tkfont
8420	    && sv1->underline == sv2->underline
8421	    && sv1->overstrike == sv2->overstrike
8422	    && sv1->elide == sv2->elide
8423	    && sv1->offset == sv2->offset
8424	    && sv1->fgStipple == sv1->fgStipple;
8425#endif /* TK_DRAW_IN_CONTEXT */
8426}
8427
8428/*
8429 *----------------------------------------------------------------------
8430 *
8431 * RemoveFromBaseChunk --
8432 *
8433 *	This procedure removes a chunk from the stretch as a result of
8434 *	UndisplayProc. The chunk in question should be the last in a stretch.
8435 *	This happens during re-layouting of the break position.
8436 *
8437 * Results:
8438 *	None.
8439 *
8440 * Side effects:
8441 *	The characters that belong to this chunk are removed from the base
8442 *	chunk. It is assumed that LayoutProc and FinalizeBaseChunk are called
8443 *	next to repair any damage that this causes to the integrity of the
8444 *	stretch and the other chunks. For that reason the base chunk is also
8445 *	put into baseCharChunkPtr automatically, so that LayoutProc can resume
8446 *	correctly.
8447 *
8448 *----------------------------------------------------------------------
8449 */
8450
8451static void
8452RemoveFromBaseChunk(
8453    TkTextDispChunk *chunkPtr)	/* The chunk to remove from the end of the
8454				 * stretch. */
8455{
8456    CharInfo *ciPtr;
8457    BaseCharInfo *bciPtr;
8458
8459    if (chunkPtr->displayProc != CharDisplayProc) {
8460#ifdef DEBUG_LAYOUT_WITH_BASE_CHUNKS
8461	fprintf(stderr,"RemoveFromBaseChunk called with wrong chunk type\n");
8462#endif
8463	return;
8464    }
8465
8466    /*
8467     * Reinstitute this base chunk for re-layout.
8468     */
8469
8470    ciPtr = (CharInfo *) chunkPtr->clientData;
8471    baseCharChunkPtr = ciPtr->baseChunkPtr;
8472
8473    /*
8474     * Remove the chunk data from the base chunk data.
8475     */
8476
8477    bciPtr = (BaseCharInfo *) baseCharChunkPtr->clientData;
8478
8479    if ((ciPtr->baseOffset + ciPtr->numBytes)
8480	    != Tcl_DStringLength(&bciPtr->baseChars)) {
8481#ifdef DEBUG_LAYOUT_WITH_BASE_CHUNKS
8482	fprintf(stderr,"RemoveFromBaseChunk called with wrong chunk "
8483		"(not last)\n");
8484#endif
8485    }
8486
8487    Tcl_DStringSetLength(&bciPtr->baseChars, ciPtr->baseOffset);
8488
8489    /*
8490     * Invalidate the stored pixel width of the base chunk.
8491     */
8492
8493    bciPtr->width = -1;
8494}
8495#endif /* TK_LAYOUT_WITH_BASE_CHUNKS */
8496
8497/*
8498 * Local Variables:
8499 * mode: c
8500 * c-basic-offset: 4
8501 * fill-column: 78
8502 * End:
8503 */
8504