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