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