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

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

Parent Directory Parent Directory | Revision Log Revision Log


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

dashley@gmail.com
ViewVC Help
Powered by ViewVC 1.1.25