]> www.wagner.pp.ru Git - oss/ck.git/blob - ckTextDisp.c
Ck console graphics toolkit
[oss/ck.git] / ckTextDisp.c
1 /* 
2  * ckTextDisp.c --
3  *
4  *      This module provides facilities to display text widgets.  It is
5  *      the only place where information is kept about the screen layout
6  *      of text widgets.
7  *
8  * Copyright (c) 1992-1994 The Regents of the University of California.
9  * Copyright (c) 1994-1995 Sun Microsystems, Inc.
10  * Copyright (c) 1995 Christian Werner
11  *
12  * See the file "license.terms" for information on usage and redistribution
13  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
14  */
15
16 #include "ckPort.h"
17 #include "ck.h"
18 #include "ckText.h"
19
20 /*
21  * The following structure describes how to display a range of characters.
22  * The information is generated by scanning all of the tags associated
23  * with the characters and combining that with default information for
24  * the overall widget.  These structures form the hash keys for
25  * dInfoPtr->styleTable.
26  */
27
28 typedef struct StyleValues {
29     int fg, bg, attr;
30     int justify;                /* Justification style for text. */
31     int lMargin1;               /* Left margin, in pixels, for first display
32                                  * line of each text line. */
33     int lMargin2;               /* Left margin, in pixels, for second and
34                                  * later display lines of each text line. */
35     int rMargin;                /* Right margin, in pixels. */
36     CkTextTabArray *tabArrayPtr;/* Locations and types of tab stops (may
37                                  * be NULL). */
38     Ck_Uid wrapMode;            /* How to handle wrap-around for this tag.
39                                  * One of ckTextCharUid, ckTextNoneUid,
40                                  * or ckTextWordUid. */
41 } StyleValues;
42
43 /*
44  * The following structure extends the StyleValues structure above with
45  * graphics contexts used to actually draw the characters.  The entries
46  * in dInfoPtr->styleTable point to structures of this type.
47  */
48
49 typedef struct Style {
50     int refCount;               /* Number of times this structure is
51                                  * referenced in Chunks. */
52     StyleValues *sValuePtr;     /* Raw information from which GCs were
53                                  * derived. */
54     Tcl_HashEntry *hPtr;        /* Pointer to entry in styleTable.  Used
55                                  * to delete entry. */
56 } Style;
57
58 /*
59  * The following macro determines whether two styles have the same
60  * background so that, for example, no beveled border should be drawn
61  * between them.
62  */
63
64 #define SAME_BACKGROUND(s1, s2) \
65     (((s1)->sValuePtr->border == (s2)->sValuePtr->border) \
66         && ((s1)->sValuePtr->borderWidth == (s2)->sValuePtr->borderWidth) \
67         && ((s1)->sValuePtr->relief == (s2)->sValuePtr->relief) \
68         && ((s1)->sValuePtr->bgStipple == (s2)->sValuePtr->bgStipple))
69
70 /*
71  * The following structure describes one line of the display, which may
72  * be either part or all of one line of the text.
73  */
74
75 typedef struct DLine {
76     CkTextIndex index;          /* Identifies first character in text
77                                  * that is displayed on this line. */
78     int count;                  /* Number of characters accounted for by this
79                                  * display line, including a trailing space
80                                  * or newline that isn't actually displayed. */
81     int y;                      /* Y-position at which line is supposed to
82                                  * be drawn (topmost pixel of rectangular
83                                  * area occupied by line). */
84     int oldY;                   /* Y-position at which line currently
85                                  * appears on display.  -1 means line isn't
86                                  * currently visible on display and must be
87                                  * redrawn.  This is used to move lines by
88                                  * scrolling rather than re-drawing. */
89     int height;                 /* Height of line, in chars. */
90     int length;                 /* Total length of line, in chars. */
91     CkTextDispChunk *chunkPtr;  /* Pointer to first chunk in list of all
92                                  * of those that are displayed on this
93                                  * line of the screen. */
94     struct DLine *nextPtr;      /* Next in list of all display lines for
95                                  * this window.   The list is sorted in
96                                  * order from top to bottom.  Note:  the
97                                  * next DLine doesn't always correspond
98                                  * to the next line of text:  (a) can have
99                                  * multiple DLines for one text line, and
100                                  * (b) can have gaps where DLine's have been
101                                  * deleted because they're out of date. */
102     int flags;                  /* Various flag bits:  see below for values. */
103 } DLine;
104
105 /*
106  * Flag bits for DLine structures:
107  *
108  * NEW_LAYOUT -                 Non-zero means that the line has been
109  *                              re-layed out since the last time the
110  *                              display was updated.
111  * TOP_LINE -                   Non-zero means that this was the top line
112  *                              in the window the last time that the window
113  *                              was laid out.  This is important because
114  *                              a line may be displayed differently if its
115  *                              at the top or bottom than if it's in the
116  *                              middle (e.g. beveled edges aren't displayed
117  *                              for middle lines if the adjacent line has
118  *                              a similar background).
119  * BOTTOM_LINE -                Non-zero means that this was the bottom line
120  *                              in the window the last time that the window
121  *                              was laid out.
122  */
123
124 #define NEW_LAYOUT      2
125 #define TOP_LINE        4
126 #define BOTTOM_LINE     8
127
128 /*
129  * Overall display information for a text widget:
130  */
131
132 typedef struct DInfo {
133     Tcl_HashTable styleTable;   /* Hash table that maps from StyleValues
134                                  * to Styles for this widget. */
135     DLine *dLinePtr;            /* First in list of all display lines for
136                                  * this widget, in order from top to bottom. */
137     int x;                      /* First x-coordinate that may be used for
138                                  * actually displaying line information.
139                                  * Leaves space for border, etc. */
140     int y;                      /* First y-coordinate that may be used for
141                                  * actually displaying line information.
142                                  * Leaves space for border, etc. */
143     int maxX;                   /* First x-coordinate to right of available
144                                  * space for displaying lines. */
145     int maxY;                   /* First y-coordinate below available
146                                  * space for displaying lines. */
147     int topOfEof;               /* Top-most pixel (lowest y-value) that has
148                                  * been drawn in the appropriate fashion for
149                                  * the portion of the window after the last
150                                  * line of the text.  This field is used to
151                                  * figure out when to redraw part or all of
152                                  * the eof field. */
153
154     /*
155      * Information used for scrolling:
156      */
157
158     int newCharOffset;          /* Desired x scroll position, measured as the
159                                  * number of average-size characters off-screen
160                                  * to the left for a line with no left
161                                  * margin. */
162     int curOffset;              /* Actual x scroll position, measured as the
163                                  * number of chars off-screen to the left. */
164     int maxLength;              /* Length in chars of longest line that's
165                                  * visible in window (length may exceed window
166                                  * size).  If there's no wrapping, this will
167                                  * be zero. */
168     double xScrollFirst, xScrollLast;
169                                 /* Most recent values reported to horizontal
170                                  * scrollbar;  used to eliminate unnecessary
171                                  * reports. */
172     double yScrollFirst, yScrollLast;
173                                 /* Most recent values reported to vertical
174                                  * scrollbar;  used to eliminate unnecessary
175                                  * reports. */
176
177     /*
178      * Miscellaneous information:
179      */
180
181     int dLinesInvalidated;      /* This value is set to 1 whenever something
182                                  * happens that invalidates information in
183                                  * DLine structures;  if a redisplay
184                                  * is in progress, it will see this and
185                                  * abort the redisplay.  This is needed
186                                  * because, for example, an embedded window
187                                  * could change its size when it is first
188                                  * displayed, invalidating the DLine that
189                                  * is currently being displayed.  If redisplay
190                                  * continues, it will use freed memory and
191                                  * could dump core. */
192     int flags;                  /* Various flag values:  see below for
193                                  * definitions. */
194 } DInfo;
195
196 /*
197  * In CkTextDispChunk structures for character segments, the clientData
198  * field points to one of the following structures:
199  */
200
201 typedef struct CharInfo {
202     int numChars;               /* Number of characters to display. */
203     CkWindow *winPtr;           /* For Ck_SetWindowAttr. */
204     char chars[4];              /* Characters to display.  Actual size
205                                  * will be numChars, not 4.  THIS MUST BE
206                                  * THE LAST FIELD IN THE STRUCTURE. */
207 } CharInfo;
208
209 /*
210  * Flag values for DInfo structures:
211  *
212  * DINFO_OUT_OF_DATE:           Non-zero means that the DLine structures
213  *                              for this window are partially or completely
214  *                              out of date and need to be recomputed.
215  * REDRAW_PENDING:              Means that a when-idle handler has been
216  *                              scheduled to update the display.
217  * REDRAW_BORDERS:              Means window border or pad area has
218  *                              potentially been damaged and must be redrawn.
219  * REPICK_NEEDED:               1 means that the widget has been modified
220  *                              in a way that could change the current
221  *                              character (a different character might be
222  *                              under the mouse cursor now).  Need to
223  *                              recompute the current character before
224  *                              the next redisplay.
225  */
226
227 #define DINFO_OUT_OF_DATE       1
228 #define REDRAW_PENDING          2
229 #define REDRAW_BORDERS          4
230 #define REPICK_NEEDED           8
231
232 /*
233  * The following counters keep statistics about redisplay that can be
234  * checked to see how clever this code is at reducing redisplays.
235  */
236
237 static int numRedisplays;       /* Number of calls to DisplayText. */
238 static int linesRedrawn;        /* Number of calls to DisplayDLine. */
239
240 /*
241  * Forward declarations for procedures defined later in this file:
242  */
243
244 static void             AdjustForTab _ANSI_ARGS_((CkText *textPtr,
245                             CkTextTabArray *tabArrayPtr, int index,
246                             CkTextDispChunk *chunkPtr));
247 static void             CharBboxProc _ANSI_ARGS_((CkTextDispChunk *chunkPtr,
248                             int index, int y, int lineHeight, int baseline,
249                             int *xPtr, int *yPtr, int *widthPtr,
250                             int *heightPtr));
251 static void             CharDisplayProc _ANSI_ARGS_((CkTextDispChunk *chunkPtr,
252                             int x, int y, int height, int baseline,
253                             WINDOW *window, int screenY));
254 static int              CharMeasureProc _ANSI_ARGS_((CkTextDispChunk *chunkPtr,
255                             int x));
256 static void             CharUndisplayProc _ANSI_ARGS_((CkText *textPtr,
257                             CkTextDispChunk *chunkPtr));
258 static void             DisplayDLine _ANSI_ARGS_((CkText *textPtr,
259                             DLine *dlPtr, DLine *prevPtr, WINDOW *window));
260 static void             DisplayText _ANSI_ARGS_((ClientData clientData));
261 static DLine *          FindDLine _ANSI_ARGS_((DLine *dlPtr,
262                             CkTextIndex *indexPtr));
263 static void             FreeDLines _ANSI_ARGS_((CkText *textPtr,
264                             DLine *firstPtr, DLine *lastPtr, int unlink));
265 static void             FreeStyle _ANSI_ARGS_((CkText *textPtr,
266                             Style *stylePtr));
267 static Style *          GetStyle _ANSI_ARGS_((CkText *textPtr,
268                             CkTextIndex *indexPtr));
269 static void             GetXView _ANSI_ARGS_((Tcl_Interp *interp,
270                             CkText *textPtr, int report));
271 static void             GetYView _ANSI_ARGS_((Tcl_Interp *interp,
272                             CkText *textPtr, int report));
273 static DLine *          LayoutDLine _ANSI_ARGS_((CkText *textPtr,
274                             CkTextIndex *indexPtr));
275 static void             MeasureUp _ANSI_ARGS_((CkText *textPtr,
276                             CkTextIndex *srcPtr, int distance,
277                             CkTextIndex *dstPtr));
278 static void             UpdateDisplayInfo _ANSI_ARGS_((CkText *textPtr));
279 static void             ScrollByLines _ANSI_ARGS_((CkText *textPtr,
280                             int offset));
281 static int              SizeOfTab _ANSI_ARGS_((CkText *textPtr,
282                             CkTextTabArray *tabArrayPtr, int index, int x,
283                             int maxX));
284 \f
285 /*
286  *----------------------------------------------------------------------
287  *
288  * CkTextCreateDInfo --
289  *
290  *      This procedure is called when a new text widget is created.
291  *      Its job is to set up display-related information for the widget.
292  *
293  * Results:
294  *      None.
295  *
296  * Side effects:
297  *      A DInfo data structure is allocated and initialized and attached
298  *      to textPtr.
299  *
300  *----------------------------------------------------------------------
301  */
302
303 void
304 CkTextCreateDInfo(textPtr)
305     CkText *textPtr;            /* Overall information for text widget. */
306 {
307     register DInfo *dInfoPtr;
308
309     dInfoPtr = (DInfo *) ckalloc(sizeof(DInfo));
310     Tcl_InitHashTable(&dInfoPtr->styleTable, sizeof(StyleValues)/sizeof(int));
311     dInfoPtr->dLinePtr = NULL;
312     dInfoPtr->topOfEof = 0;
313     dInfoPtr->newCharOffset = 0;
314     dInfoPtr->curOffset = 0;
315     dInfoPtr->maxLength = 0;
316     dInfoPtr->xScrollFirst = -1;
317     dInfoPtr->xScrollLast = -1;
318     dInfoPtr->yScrollFirst = -1;
319     dInfoPtr->yScrollLast = -1;
320     dInfoPtr->dLinesInvalidated = 0;
321     dInfoPtr->flags = DINFO_OUT_OF_DATE;
322     textPtr->dInfoPtr = dInfoPtr;
323 }
324 \f
325 /*
326  *----------------------------------------------------------------------
327  *
328  * CkTextFreeDInfo --
329  *
330  *      This procedure is called to free up all of the private display
331  *      information kept by this file for a text widget.
332  *
333  * Results:
334  *      None.
335  *
336  * Side effects:
337  *      Lots of resources get freed.
338  *
339  *----------------------------------------------------------------------
340  */
341
342 void
343 CkTextFreeDInfo(textPtr)
344     CkText *textPtr;            /* Overall information for text widget. */
345 {
346     register DInfo *dInfoPtr = textPtr->dInfoPtr;
347
348     /*
349      * Be careful to free up styleTable *after* freeing up all the
350      * DLines, so that the hash table is still intact to free up the
351      * style-related information from the lines.  Once the lines are
352      * all free then styleTable will be empty.
353      */
354
355     FreeDLines(textPtr, dInfoPtr->dLinePtr, (DLine *) NULL, 1);
356     Tcl_DeleteHashTable(&dInfoPtr->styleTable);
357     if (dInfoPtr->flags & REDRAW_PENDING) {
358         Tk_CancelIdleCall(DisplayText, (ClientData) textPtr);
359     }
360     ckfree((char *) dInfoPtr);
361 }
362 \f
363 /*
364  *----------------------------------------------------------------------
365  *
366  * GetStyle --
367  *
368  *      This procedure creates all the information needed to display
369  *      text at a particular location.
370  *
371  * Results:
372  *      The return value is a pointer to a Style structure that
373  *      corresponds to *sValuePtr.
374  *
375  * Side effects:
376  *      A new entry may be created in the style table for the widget.
377  *
378  *----------------------------------------------------------------------
379  */
380
381 static Style *
382 GetStyle(textPtr, indexPtr)
383     CkText *textPtr;            /* Overall information about text widget. */
384     CkTextIndex *indexPtr;      /* The character in the text for which
385                                  * display information is wanted. */
386 {
387     CkTextTag **tagPtrs;
388     register CkTextTag *tagPtr;
389     StyleValues styleValues;
390     Style *stylePtr;
391     Tcl_HashEntry *hPtr;
392     int numTags, new, i;
393
394     /*
395      * The variables below keep track of the highest-priority specification
396      * that has occurred for each of the various fields of the StyleValues.
397      */
398
399     int bgPrio, fgPrio, attrPrio, justifyPrio;
400     int lMargin1Prio, lMargin2Prio, rMarginPrio;
401     int tabPrio, wrapPrio;
402
403     /*
404      * Find out what tags are present for the character, then compute
405      * a StyleValues structure corresponding to those tags (scan
406      * through all of the tags, saving information for the highest-
407      * priority tag).
408      */
409
410     tagPtrs = CkBTreeGetTags(indexPtr, &numTags);
411     bgPrio = fgPrio = attrPrio = justifyPrio = -1;
412     lMargin1Prio = lMargin2Prio = rMarginPrio = -1;
413     tabPrio = wrapPrio = -1;
414     memset((VOID *) &styleValues, 0, sizeof(StyleValues));
415     styleValues.fg = textPtr->fg;
416     styleValues.bg = textPtr->bg;
417     styleValues.attr = textPtr->attr;
418     styleValues.justify = CK_JUSTIFY_LEFT;
419     styleValues.tabArrayPtr = textPtr->tabArrayPtr;
420     styleValues.wrapMode = textPtr->wrapMode;
421     for (i = 0 ; i < numTags; i++) {
422         tagPtr = tagPtrs[i];
423         if ((tagPtr->bg != -1) && (tagPtr->priority > bgPrio)) {
424             styleValues.bg = tagPtr->bg;
425             bgPrio = tagPtr->priority;
426         }
427         if ((tagPtr->fg != -1) && (tagPtr->priority > fgPrio)) {
428             styleValues.fg = tagPtr->fg;
429             fgPrio = tagPtr->priority;
430         }
431         if ((tagPtr->attr != -1) && (tagPtr->priority > attrPrio)) {
432             styleValues.attr = tagPtr->attr;
433             attrPrio = tagPtr->priority;
434         }
435         if ((tagPtr->justifyString != NULL)
436                 && (tagPtr->priority > justifyPrio)) {
437             styleValues.justify = tagPtr->justify;
438             justifyPrio = tagPtr->priority;
439         }
440         if ((tagPtr->lMargin1String != NULL)
441                 && (tagPtr->priority > lMargin1Prio)) {
442             styleValues.lMargin1 = tagPtr->lMargin1;
443             lMargin1Prio = tagPtr->priority;
444         }
445         if ((tagPtr->lMargin2String != NULL)
446                 && (tagPtr->priority > lMargin2Prio)) {
447             styleValues.lMargin2 = tagPtr->lMargin2;
448             lMargin2Prio = tagPtr->priority;
449         }
450         if ((tagPtr->rMarginString != NULL)
451                 && (tagPtr->priority > rMarginPrio)) {
452             styleValues.rMargin = tagPtr->rMargin;
453             rMarginPrio = tagPtr->priority;
454         }
455         if ((tagPtr->tabString != NULL)
456                 && (tagPtr->priority > tabPrio)) {
457             styleValues.tabArrayPtr = tagPtr->tabArrayPtr;
458             tabPrio = tagPtr->priority;
459         }
460         if ((tagPtr->wrapMode != NULL)
461                 && (tagPtr->priority > wrapPrio)) {
462             styleValues.wrapMode = tagPtr->wrapMode;
463             wrapPrio = tagPtr->priority;
464         }
465     }
466     if (tagPtrs != NULL) {
467         ckfree((char *) tagPtrs);
468     }
469
470     /*
471      * Use an existing style if there's one around that matches.
472      */
473
474     hPtr = Tcl_CreateHashEntry(&textPtr->dInfoPtr->styleTable,
475             (char *) &styleValues, &new);
476     if (!new) {
477         stylePtr = (Style *) Tcl_GetHashValue(hPtr);
478         stylePtr->refCount++;
479         return stylePtr;
480     }
481
482     /*
483      * No existing style matched.  Make a new one.
484      */
485
486     stylePtr = (Style *) ckalloc(sizeof(Style));
487     stylePtr->refCount = 1;
488     stylePtr->sValuePtr = (StyleValues *)
489             Tcl_GetHashKey(&textPtr->dInfoPtr->styleTable, hPtr);
490     stylePtr->hPtr = hPtr;
491     Tcl_SetHashValue(hPtr, stylePtr);
492     return stylePtr;
493 }
494 \f
495 /*
496  *----------------------------------------------------------------------
497  *
498  * FreeStyle --
499  *
500  *      This procedure is called when a Style structure is no longer
501  *      needed.  It decrements the reference count and frees up the
502  *      space for the style structure if the reference count is 0.
503  *
504  * Results:
505  *      None.
506  *
507  * Side effects:
508  *      The storage and other resources associated with the style
509  *      are freed up if no-one's still using it.
510  *
511  *----------------------------------------------------------------------
512  */
513
514 static void
515 FreeStyle(textPtr, stylePtr)
516     CkText *textPtr;            /* Information about overall widget. */
517     register Style *stylePtr;   /* Information about style to be freed. */
518
519 {
520     stylePtr->refCount--;
521     if (stylePtr->refCount == 0) {
522         Tcl_DeleteHashEntry(stylePtr->hPtr);
523         ckfree((char *) stylePtr);
524     }
525 }
526 \f
527 /*
528  *----------------------------------------------------------------------
529  *
530  * LayoutDLine --
531  *
532  *      This procedure generates a single DLine structure for a display
533  *      line whose leftmost character is given by indexPtr.
534  *      
535  * Results:
536  *      The return value is a pointer to a DLine structure desribing the
537  *      display line.  All fields are filled in and correct except for
538  *      y and nextPtr.
539  *
540  * Side effects:
541  *      Storage is allocated for the new DLine.
542  *
543  *----------------------------------------------------------------------
544  */
545
546 static DLine *
547 LayoutDLine(textPtr, indexPtr)
548     CkText *textPtr;            /* Overall information about text widget. */
549     CkTextIndex *indexPtr;      /* Beginning of display line.  May not
550                                  * necessarily point to a character segment. */
551 {
552     register DLine *dlPtr;              /* New display line. */
553     CkTextSegment *segPtr;              /* Current segment in text. */
554     CkTextDispChunk *lastChunkPtr;      /* Last chunk allocated so far
555                                          * for line. */
556     CkTextDispChunk *chunkPtr;          /* Current chunk. */
557     CkTextIndex curIndex;
558     CkTextDispChunk *breakChunkPtr;     /* Chunk containing best word break
559                                          * point, if any. */
560     CkTextIndex breakIndex;             /* Index of first character in
561                                          * breakChunkPtr. */
562     int breakCharOffset;                /* Character within breakChunkPtr just
563                                          * to right of best break point. */
564     int noCharsYet;                     /* Non-zero means that no characters
565                                          * have been placed on the line yet. */
566     int justify;                        /* How to justify line: taken from
567                                          * style for first character in line. */
568     int jIndent;                        /* Additional indentation (beyond
569                                          * margins) due to justification. */
570     int rMargin;                        /* Right margin width for line. */
571     Ck_Uid wrapMode;                    /* Wrap mode to use for this line. */
572     int x = 0, maxX = 0;                /* Initializations needed only to
573                                          * stop compiler warnings. */
574     int wholeLine;                      /* Non-zero means this display line
575                                          * runs to the end of the text line. */
576     int tabIndex;                       /* Index of the current tab stop. */
577     int gotTab;                         /* Non-zero means the current chunk
578                                          * contains a tab. */
579     CkTextDispChunk *tabChunkPtr;       /* Pointer to the chunk containing
580                                          * the previous tab stop. */
581     int maxChars;                       /* Maximum number of characters to
582                                          * include in this chunk. */
583     CkTextTabArray *tabArrayPtr;        /* Tab stops for line;  taken from
584                                          * style for first character on line. */
585     int tabSize;                        /* Number of pixels consumed by current
586                                          * tab stop. */
587     int offset, code;
588     StyleValues *sValuePtr;
589
590     /*
591      * Create and initialize a new DLine structure.
592      */
593
594     dlPtr = (DLine *) ckalloc(sizeof(DLine));
595     dlPtr->index = *indexPtr;
596     dlPtr->count = 0;
597     dlPtr->y = 0;
598     dlPtr->oldY = -1;
599     dlPtr->height = 0;
600     dlPtr->chunkPtr = NULL;
601     dlPtr->nextPtr = NULL;
602     dlPtr->flags = NEW_LAYOUT;
603
604     /*
605      * Each iteration of the loop below creates one CkTextDispChunk for
606      * the new display line.  The line will always have at least one
607      * chunk (for the newline character at the end, if there's nothing
608      * else available).
609      */
610
611     curIndex = *indexPtr;
612     lastChunkPtr = NULL;
613     chunkPtr = NULL;
614     noCharsYet = 1;
615     breakChunkPtr = NULL;
616     breakCharOffset = 0;
617     justify = CK_JUSTIFY_LEFT;
618     tabIndex = -1;
619     tabChunkPtr = NULL;
620     tabArrayPtr = NULL;
621     rMargin = 0;
622     wrapMode = ckTextCharUid;
623     tabSize = 0;
624
625     /*
626      * Find the first segment to consider for the line.  Can't call
627      * CkTextIndexToSeg for this because it won't return a segment
628      * with zero size (such as the insertion cursor's mark).
629      */
630
631     for (offset = curIndex.charIndex, segPtr = curIndex.linePtr->segPtr;
632             (offset > 0) && (offset >= segPtr->size);
633             offset -= segPtr->size, segPtr = segPtr->nextPtr) {
634         /* Empty loop body. */
635     }
636
637     while (segPtr != NULL) {
638         if (segPtr->typePtr->layoutProc == NULL) {
639             segPtr = segPtr->nextPtr;
640             offset = 0;
641             continue;
642         }
643         if (chunkPtr == NULL) {
644             chunkPtr = (CkTextDispChunk *) ckalloc(sizeof(CkTextDispChunk));
645             chunkPtr->nextPtr = NULL;
646         }
647         chunkPtr->stylePtr = GetStyle(textPtr, &curIndex);
648
649         /*
650          * Save style information such as justification and indentation,
651          * up until the first character is encountered, then retain that
652          * information for the rest of the line.
653          */
654
655         if (noCharsYet) {
656             tabArrayPtr = chunkPtr->stylePtr->sValuePtr->tabArrayPtr;
657             justify = chunkPtr->stylePtr->sValuePtr->justify;
658             rMargin = chunkPtr->stylePtr->sValuePtr->rMargin;
659             wrapMode = chunkPtr->stylePtr->sValuePtr->wrapMode;
660             x = ((curIndex.charIndex == 0)
661                     ? chunkPtr->stylePtr->sValuePtr->lMargin1
662                     : chunkPtr->stylePtr->sValuePtr->lMargin2);
663             if (wrapMode == ckTextNoneUid) {
664                 maxX = INT_MAX;
665             } else {
666                 maxX = textPtr->dInfoPtr->maxX - textPtr->dInfoPtr->x
667                         - rMargin;
668                 if (maxX < x) {
669                     maxX = x;
670                 }
671             }
672         }
673
674         /*
675          * See if there is a tab in the current chunk; if so, only
676          * layout characters up to (and including) the tab.
677          */
678
679         gotTab = 0;
680         maxChars = segPtr->size - offset;
681         if (justify == CK_JUSTIFY_LEFT) {
682             if (segPtr->typePtr == &ckTextCharType) {
683                 char *p;
684
685                 for (p = segPtr->body.chars  + offset; *p != 0; p++) {
686                     if (*p == '\t') {
687                         maxChars = (p + 1 - segPtr->body.chars) - offset;
688                         gotTab = 1;
689                         break;
690                     }
691                 }
692             }
693         }
694
695         chunkPtr->x = x;
696         code = (*segPtr->typePtr->layoutProc)(textPtr, &curIndex, segPtr,
697                 offset, maxX-tabSize, maxChars, noCharsYet, wrapMode,
698                 chunkPtr);
699         if (code <= 0) {
700             FreeStyle(textPtr, chunkPtr->stylePtr);
701             if (code < 0) {
702                 /*
703                  * This segment doesn't wish to display itself (e.g. most
704                  * marks).
705                  */
706
707                 segPtr = segPtr->nextPtr;
708                 offset = 0;
709                 continue;
710             }
711
712             /*
713              * No characters from this segment fit in the window: this
714              * means we're at the end of the display line.
715              */
716
717             if (chunkPtr != NULL) {
718                 ckfree((char *) chunkPtr);
719             }
720             break;
721         }
722         if (chunkPtr->numChars > 0) {
723             noCharsYet = 0;
724         }
725         if (lastChunkPtr == NULL) {
726             dlPtr->chunkPtr = chunkPtr;
727         } else {
728             lastChunkPtr->nextPtr = chunkPtr;
729         }
730         lastChunkPtr = chunkPtr;
731         x += chunkPtr->width;
732         if (chunkPtr->breakIndex > 0) {
733             breakCharOffset = chunkPtr->breakIndex;
734             breakIndex = curIndex;
735             breakChunkPtr = chunkPtr;
736         }
737         if (chunkPtr->numChars != maxChars) {
738             break;
739         }
740
741         /*
742          * If we're at a new tab, adjust the layout for all the chunks
743          * pertaining to the previous tab.  Also adjust the amount of
744          * space left in the line to account for space that will be eaten
745          * up by the tab.
746          */
747
748         if (gotTab) {
749             if (tabIndex >= 0) {
750                 AdjustForTab(textPtr, tabArrayPtr, tabIndex, tabChunkPtr);
751                 x = chunkPtr->x + chunkPtr->width;
752             }
753             tabIndex++;
754             tabChunkPtr = chunkPtr;
755             tabSize = SizeOfTab(textPtr, tabArrayPtr, tabIndex, x, maxX);
756             if (tabSize >= (maxX - x)) {
757                 break;
758             }
759         }
760         curIndex.charIndex += chunkPtr->numChars;
761         offset += chunkPtr->numChars;
762         if (offset >= segPtr->size) {
763             offset = 0;
764             segPtr = segPtr->nextPtr;
765         }
766         chunkPtr = NULL;
767     }
768     if (noCharsYet) {
769         panic("LayoutDLine couldn't place any characters on a line");
770     }
771     wholeLine = (segPtr == NULL);
772
773     /*
774      * We're at the end of the display line.  Throw away everything
775      * after the most recent word break, if there is one;  this may
776      * potentially require the last chunk to be layed out again.
777      */
778
779     if ((breakChunkPtr != NULL) && ((lastChunkPtr != breakChunkPtr)
780             || (breakCharOffset != lastChunkPtr->numChars))) {
781         while (1) {
782             chunkPtr = breakChunkPtr->nextPtr;
783             if (chunkPtr == NULL) {
784                 break;
785             }
786             FreeStyle(textPtr, chunkPtr->stylePtr);
787             breakChunkPtr->nextPtr = chunkPtr->nextPtr;
788             (*chunkPtr->undisplayProc)(textPtr, chunkPtr);
789             ckfree((char *) chunkPtr);
790         }
791         if (breakCharOffset != breakChunkPtr->numChars) {
792             (*breakChunkPtr->undisplayProc)(textPtr, breakChunkPtr);
793             segPtr = CkTextIndexToSeg(&breakIndex, &offset);
794             (*segPtr->typePtr->layoutProc)(textPtr, &breakIndex,
795                     segPtr, offset, maxX, breakCharOffset, 0, 
796                     wrapMode, breakChunkPtr);
797         }
798         lastChunkPtr = breakChunkPtr;
799         wholeLine = 0;
800     }
801
802     /*
803      * Make tab adjustments for the last tab stop, if there is one.
804      */
805
806     if ((tabIndex >= 0) && (tabChunkPtr != NULL)) {
807         AdjustForTab(textPtr, tabArrayPtr, tabIndex, tabChunkPtr);
808     }
809
810     /*
811      * Make one more pass over the line to recompute various things
812      * like its height, length, and total number of characters.  Also
813      * modify the x-locations of chunks to reflect justification.
814      * If we're not wrapping, I'm not sure what is the best way to
815      * handle left and center justification:  should the total length,
816      * for purposes of justification, be (a) the window width, (b)
817      * the length of the longest line in the window, or (c) the length
818      * of the longest line in the text?  (c) isn't available, (b) seems
819      * weird, since it can change with vertical scrolling, so (a) is
820      * what is implemented below.
821      */
822
823     if (wrapMode == ckTextNoneUid) {
824         maxX = textPtr->dInfoPtr->maxX - textPtr->dInfoPtr->x - rMargin;
825     }
826     dlPtr->length = lastChunkPtr->x + lastChunkPtr->width;
827     if (justify == CK_JUSTIFY_LEFT) {
828         jIndent = 0;
829     } else if (justify == CK_JUSTIFY_RIGHT) {
830         jIndent = maxX - dlPtr->length;
831     } else {
832         jIndent = (maxX - dlPtr->length)/2;
833     }
834     for (chunkPtr = dlPtr->chunkPtr; chunkPtr != NULL;
835             chunkPtr = chunkPtr->nextPtr) {
836         chunkPtr->x += jIndent;
837         dlPtr->count += chunkPtr->numChars;
838         if (chunkPtr->minHeight > dlPtr->height) {
839             dlPtr->height = chunkPtr->minHeight;
840         }
841         sValuePtr = chunkPtr->stylePtr->sValuePtr;
842     }
843     sValuePtr = dlPtr->chunkPtr->stylePtr->sValuePtr;
844
845     /*
846      * Recompute line length:  may have changed because of justification.
847      */
848
849     dlPtr->length = lastChunkPtr->x + lastChunkPtr->width;
850     return dlPtr;
851 }
852 \f
853 /*
854  *----------------------------------------------------------------------
855  *
856  * UpdateDisplayInfo --
857  *
858  *      This procedure is invoked to recompute some or all of the
859  *      DLine structures for a text widget.  At the time it is called
860  *      the DLine structures still left in the widget are guaranteed
861  *      to be correct except that (a) the y-coordinates aren't
862  *      necessarily correct, (b) there may be missing structures
863  *      (the DLine structures get removed as soon as they are potentially
864  *      out-of-date), and (c) DLine structures that don't start at the
865  *      beginning of a line may be incorrect if previous information in
866  *      the same line changed size in a way that moved a line boundary
867  *      (DLines for any info that changed will have been deleted, but
868  *      not DLines for unchanged info in the same text line).
869  *
870  * Results:
871  *      None.
872  *
873  * Side effects:
874  *      Upon return, the DLine information for textPtr correctly reflects
875  *      the positions where characters will be displayed.  However, this
876  *      procedure doesn't actually bring the display up-to-date.
877  *
878  *----------------------------------------------------------------------
879  */
880
881 static void
882 UpdateDisplayInfo(textPtr)
883     CkText *textPtr;                    /* Text widget to update. */
884 {
885     register DInfo *dInfoPtr = textPtr->dInfoPtr;
886     register DLine *dlPtr, *prevPtr;
887     CkTextIndex index;
888     CkTextLine *lastLinePtr;
889     int y, maxY, maxOffset;
890
891     if (!(dInfoPtr->flags & DINFO_OUT_OF_DATE)) {
892         return;
893     }
894     dInfoPtr->flags &= ~DINFO_OUT_OF_DATE;
895
896     /*
897      * Delete any DLines that are now above the top of the window.
898      */
899
900     index = textPtr->topIndex;
901     dlPtr = FindDLine(dInfoPtr->dLinePtr, &index);
902     if ((dlPtr != NULL) && (dlPtr != dInfoPtr->dLinePtr)) {
903         FreeDLines(textPtr, dInfoPtr->dLinePtr, dlPtr, 1);
904     }
905
906     /*
907      *--------------------------------------------------------------
908      * Scan through the contents of the window from top to bottom,
909      * recomputing information for lines that are missing.
910      *--------------------------------------------------------------
911      */
912
913     lastLinePtr = CkBTreeFindLine(textPtr->tree,
914             CkBTreeNumLines(textPtr->tree));
915     dlPtr = dInfoPtr->dLinePtr;
916     prevPtr = NULL;
917     y = dInfoPtr->y;
918     maxY = dInfoPtr->maxY;
919     while (1) {
920         register DLine *newPtr;
921
922         if (index.linePtr == lastLinePtr) {
923             break;
924         }
925
926         /*
927          * There are three possibilities right now:
928          * (a) the next DLine (dlPtr) corresponds exactly to the next
929          *     information we want to display: just use it as-is.
930          * (b) the next DLine corresponds to a different line, or to
931          *     a segment that will be coming later in the same line:
932          *     leave this DLine alone in the hopes that we'll be able
933          *     to use it later, then create a new DLine in front of
934          *     it.
935          * (c) the next DLine corresponds to a segment in the line we
936          *     want, but it's a segment that has already been processed
937          *     or will never be processed.  Delete the DLine and try
938          *     again.
939          *
940          * One other twist on all this.  It's possible for 3D borders
941          * to interact between lines (see DisplayLineBackground) so if
942          * a line is relayed out and has styles with 3D borders, its
943          * neighbors have to be redrawn if they have 3D borders too,
944          * since the interactions could have changed (the neighbors
945          * don't have to be relayed out, just redrawn).
946          */
947
948         if ((dlPtr == NULL) || (dlPtr->index.linePtr != index.linePtr)) {
949             /*
950              * Case (b) -- must make new DLine.
951              */
952
953             makeNewDLine:
954             if (ckTextDebug) {
955                 char string[TK_POS_CHARS];
956
957                 /*
958                  * Debugging is enabled, so keep a log of all the lines
959                  * that were re-layed out.  The test suite uses this
960                  * information.
961                  */
962
963                 CkTextPrintIndex(&index, string);
964                 Tcl_SetVar2(textPtr->interp, "ck_textRelayout", (char *) NULL,
965                         string,
966                         TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT);
967             }
968             newPtr = LayoutDLine(textPtr, &index);
969             if (prevPtr == NULL) {
970                 dInfoPtr->dLinePtr = newPtr;
971             } else {
972                 prevPtr->nextPtr = newPtr;
973             }
974             newPtr->nextPtr = dlPtr;
975             dlPtr = newPtr;
976         } else {
977             /*
978              * DlPtr refers to the line we want.  Next check the
979              * index within the line.
980              */
981
982             if (index.charIndex == dlPtr->index.charIndex) {
983                 /*
984                  * Case (a) -- can use existing display line as-is.
985                  */
986                 goto lineOK;
987             }
988             if (index.charIndex < dlPtr->index.charIndex) {
989                 goto makeNewDLine;
990             }
991
992             /*
993              * Case (c) -- dlPtr is useless.  Discard it and start
994              * again with the next display line.
995              */
996
997             newPtr = dlPtr->nextPtr;
998             FreeDLines(textPtr, dlPtr, newPtr, 0);
999             dlPtr = newPtr;
1000             continue;
1001         }
1002
1003         /*
1004          * Advance to the start of the next line.
1005          */
1006
1007         lineOK:
1008         dlPtr->y = y;
1009         y += dlPtr->height;
1010 #if CK_USE_UTF
1011         CkTextIndexForwBytes(&index, dlPtr->count, &index);
1012 #else
1013         CkTextIndexForwChars(&index, dlPtr->count, &index);
1014 #endif
1015         prevPtr = dlPtr;
1016         dlPtr = dlPtr->nextPtr;
1017
1018         /*
1019          * If we switched text lines, delete any DLines left for the
1020          * old text line.
1021          */
1022
1023         if (index.linePtr != prevPtr->index.linePtr) {
1024             register DLine *nextPtr;
1025
1026             nextPtr = dlPtr;
1027             while ((nextPtr != NULL)
1028                     && (nextPtr->index.linePtr == prevPtr->index.linePtr)) {
1029                 nextPtr = nextPtr->nextPtr;
1030             }
1031             if (nextPtr != dlPtr) {
1032                 FreeDLines(textPtr, dlPtr, nextPtr, 0);
1033                 prevPtr->nextPtr = nextPtr;
1034                 dlPtr = nextPtr;
1035             }
1036         }
1037
1038         /*
1039          * It's important to have the following check here rather than in
1040          * the while statement for the loop, so that there's always at least
1041          * one DLine generated, regardless of how small the window is.  This
1042          * keeps a lot of other code from breaking.
1043          */
1044
1045         if (y >= maxY) {
1046             break;
1047         }
1048     }
1049
1050     /*
1051      * Delete any DLine structures that don't fit on the screen.
1052      */
1053
1054     FreeDLines(textPtr, dlPtr, (DLine *) NULL, 1);
1055
1056     /*
1057      *--------------------------------------------------------------
1058      * If there is extra space at the bottom of the window (because
1059      * we've hit the end of the text), then bring in more lines at
1060      * the top of the window, if there are any, to fill in the view.
1061      *--------------------------------------------------------------
1062      */
1063
1064     if (y < maxY) {
1065         int lineNum, spaceLeft, charsToCount;
1066         DLine *lowestPtr;
1067
1068         /*
1069          * Layout an entire text line (potentially > 1 display line),
1070          * then link in as many display lines as fit without moving
1071          * the bottom line out of the window.  Repeat this until
1072          * all the extra space has been used up or we've reached the
1073          * beginning of the text.
1074          */
1075
1076         spaceLeft = maxY - y;
1077         lineNum = CkBTreeLineIndex(dInfoPtr->dLinePtr->index.linePtr);
1078         charsToCount = dInfoPtr->dLinePtr->index.charIndex;
1079         if (charsToCount == 0) {
1080             charsToCount = INT_MAX;
1081             lineNum--;
1082         }
1083         for ( ; (lineNum >= 0) && (spaceLeft > 0); lineNum--) {
1084             index.linePtr = CkBTreeFindLine(textPtr->tree, lineNum);
1085             index.charIndex = 0;
1086             lowestPtr = NULL;
1087             do {
1088                 dlPtr = LayoutDLine(textPtr, &index);
1089                 dlPtr->nextPtr = lowestPtr;
1090                 lowestPtr = dlPtr;
1091 #if CK_USE_UTF
1092                 if (dlPtr->length == 0 && dlPtr->height == 0) {
1093                     charsToCount--;
1094                     break;
1095                 }
1096                 CkTextIndexForwBytes(&index, dlPtr->count, &index);
1097 #else
1098                 CkTextIndexForwChars(&index, dlPtr->count, &index);
1099 #endif
1100                 charsToCount -= dlPtr->count;
1101             } while ((charsToCount > 0)
1102                     && (index.linePtr == lowestPtr->index.linePtr));
1103
1104             /*
1105              * Scan through the display lines from the bottom one up to
1106              * the top one.
1107              */
1108
1109             while (lowestPtr != NULL) {
1110                 dlPtr = lowestPtr;
1111                 spaceLeft -= dlPtr->height;
1112                 if (spaceLeft < 0) {
1113                     break;
1114                 }
1115                 lowestPtr = dlPtr->nextPtr;
1116                 dlPtr->nextPtr = dInfoPtr->dLinePtr;
1117                 dInfoPtr->dLinePtr = dlPtr;
1118                 if (ckTextDebug) {
1119                     char string[TK_POS_CHARS];
1120
1121                     CkTextPrintIndex(&dlPtr->index, string);
1122                     Tcl_SetVar2(textPtr->interp, "ck_textRelayout",
1123                             (char *) NULL, string,
1124                             TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT);
1125                 }
1126             }
1127             FreeDLines(textPtr, lowestPtr, (DLine *) NULL, 0);
1128             charsToCount = INT_MAX;
1129         }
1130
1131         /*
1132          * Now we're all done except that the y-coordinates in all the
1133          * DLines are wrong and the top index for the text is wrong.
1134          * Update them.
1135          */
1136
1137         textPtr->topIndex = dInfoPtr->dLinePtr->index;
1138         y = dInfoPtr->y;
1139         for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
1140                 dlPtr = dlPtr->nextPtr) {
1141             if (y > dInfoPtr->maxY) {
1142                 panic("Added too many new lines in UpdateDisplayInfo");
1143             }
1144             dlPtr->y = y;
1145             y += dlPtr->height; 
1146         }
1147     }
1148
1149     /*
1150      *--------------------------------------------------------------
1151      * If the old top or bottom line has scrolled elsewhere on the
1152      * screen, we may not be able to re-use its old contents by
1153      * copying bits (e.g., a beveled edge that was drawn when it was
1154      * at the top or bottom won't be drawn when the line is in the
1155      * middle and its neighbor has a matching background).  Similarly,
1156      * if the new top or bottom line came from somewhere else on the
1157      * screen, we may not be able to copy the old bits.
1158      *--------------------------------------------------------------
1159      */
1160
1161     dlPtr = dInfoPtr->dLinePtr;
1162     while (1) {
1163         if (dlPtr->nextPtr == NULL) {
1164             dlPtr->flags &= ~TOP_LINE;
1165             dlPtr->flags |= BOTTOM_LINE;
1166             break;
1167         }
1168         dlPtr->flags &= ~(TOP_LINE|BOTTOM_LINE);
1169         dlPtr = dlPtr->nextPtr;
1170     }
1171     dInfoPtr->dLinePtr->flags |= TOP_LINE;
1172
1173     /*
1174      * Arrange for scrollbars to be updated.
1175      */
1176
1177     textPtr->flags |= UPDATE_SCROLLBARS;
1178
1179     /*
1180      *--------------------------------------------------------------
1181      * Deal with horizontal scrolling:
1182      * 1. If there's empty space to the right of the longest line,
1183      *    shift the screen to the right to fill in the empty space.
1184      * 2. If the desired horizontal scroll position has changed,
1185      *    force a full redisplay of all the lines in the widget.
1186      * 3. If the wrap mode isn't "none" then re-scroll to the base
1187      *    position.
1188      *--------------------------------------------------------------
1189      */
1190
1191     dInfoPtr->maxLength = 0;
1192     for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
1193             dlPtr = dlPtr->nextPtr) {
1194         if (dlPtr->length > dInfoPtr->maxLength) {
1195             dInfoPtr->maxLength = dlPtr->length;
1196         }
1197     }
1198
1199     maxOffset = dInfoPtr->maxLength - (dInfoPtr->maxX - dInfoPtr->x);
1200     if (dInfoPtr->newCharOffset >= maxOffset) {
1201         dInfoPtr->newCharOffset = maxOffset != 0 ? maxOffset + 1 : 0;
1202     }
1203     if (dInfoPtr->newCharOffset < 0) {
1204         dInfoPtr->newCharOffset = 0;
1205     }
1206     if (dInfoPtr->newCharOffset != dInfoPtr->curOffset) {
1207         dInfoPtr->curOffset = dInfoPtr->newCharOffset;
1208         for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
1209                 dlPtr = dlPtr->nextPtr) {
1210             dlPtr->oldY = -1;
1211         }
1212     }
1213 }
1214 \f
1215 /*
1216  *----------------------------------------------------------------------
1217  *
1218  * FreeDLines --
1219  *
1220  *      This procedure is called to free up all of the resources
1221  *      associated with one or more DLine structures.
1222  *
1223  * Results:
1224  *      None.
1225  *
1226  * Side effects:
1227  *      Memory gets freed and various other resources are released.
1228  *
1229  *----------------------------------------------------------------------
1230  */
1231
1232 static void
1233 FreeDLines(textPtr, firstPtr, lastPtr, unlink)
1234     CkText *textPtr;                    /* Information about overall text
1235                                          * widget. */
1236     register DLine *firstPtr;           /* Pointer to first DLine to free up. */
1237     DLine *lastPtr;                     /* Pointer to DLine just after last
1238                                          * one to free (NULL means everything
1239                                          * starting with firstPtr). */
1240     int unlink;                         /* 1 means DLines are currently linked
1241                                          * into the list rooted at
1242                                          * textPtr->dInfoPtr->dLinePtr and
1243                                          * they have to be unlinked.  0 means
1244                                          * just free without unlinking. */
1245 {
1246     register CkTextDispChunk *chunkPtr, *nextChunkPtr;
1247     register DLine *nextDLinePtr;
1248
1249     if (unlink) {
1250         if (textPtr->dInfoPtr->dLinePtr == firstPtr) {
1251             textPtr->dInfoPtr->dLinePtr = lastPtr;
1252         } else {
1253             register DLine *prevPtr;
1254             for (prevPtr = textPtr->dInfoPtr->dLinePtr;
1255                     prevPtr->nextPtr != firstPtr; prevPtr = prevPtr->nextPtr) {
1256                 /* Empty loop body. */
1257             }
1258             prevPtr->nextPtr = lastPtr;
1259         }
1260     }
1261     while (firstPtr != lastPtr) {
1262         nextDLinePtr = firstPtr->nextPtr;
1263         for (chunkPtr = firstPtr->chunkPtr; chunkPtr != NULL;
1264                 chunkPtr = nextChunkPtr) {
1265             if (chunkPtr->undisplayProc != NULL) {
1266                 (*chunkPtr->undisplayProc)(textPtr, chunkPtr);
1267             }
1268             FreeStyle(textPtr, chunkPtr->stylePtr);
1269             nextChunkPtr = chunkPtr->nextPtr;
1270             ckfree((char *) chunkPtr);
1271         }
1272         ckfree((char *) firstPtr);
1273         firstPtr = nextDLinePtr;
1274     }
1275     textPtr->dInfoPtr->dLinesInvalidated = 1;
1276 }
1277 \f
1278 /*
1279  *----------------------------------------------------------------------
1280  *
1281  * DisplayDLine --
1282  *
1283  *      This procedure is invoked to draw a single line on the
1284  *      screen.
1285  *
1286  * Results:
1287  *      None.
1288  *
1289  * Side effects:
1290  *      The line given by dlPtr is drawn at its correct position in
1291  *      textPtr's window.  Note that this is one *display* line, not
1292  *      one *text* line.
1293  *
1294  *----------------------------------------------------------------------
1295  */
1296
1297 static void
1298 DisplayDLine(textPtr, dlPtr, prevPtr, window)
1299     CkText *textPtr;            /* Text widget in which to draw line. */
1300     register DLine *dlPtr;      /* Information about line to draw. */
1301     DLine *prevPtr;             /* Line just before one to draw, or NULL
1302                                  * if dlPtr is the top line. */
1303     WINDOW *window;
1304 {
1305     register CkTextDispChunk *chunkPtr;
1306     DInfo *dInfoPtr = textPtr->dInfoPtr;
1307     int x;
1308
1309     /*
1310      * First, clear the area of the line to the background color for the
1311      * text widget.
1312      */
1313
1314     Ck_SetWindowAttr(textPtr->winPtr, textPtr->fg, textPtr->bg, textPtr->attr);
1315     Ck_ClearToEol(textPtr->winPtr, 0, dlPtr->y);
1316
1317     /*
1318      * Make yet another pass through all of the chunks to redraw all of
1319      * foreground information.  Note:  we have to call the displayProc
1320      * even for chunks that are off-screen.  This is needed, for
1321      * example, so that embedded windows can be unmapped in this case.
1322      */
1323
1324     for (chunkPtr = dlPtr->chunkPtr; (chunkPtr != NULL);
1325             chunkPtr = chunkPtr->nextPtr) {
1326         x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curOffset;
1327         if (chunkPtr->displayProc == CkTextInsertDisplayProc) {
1328             (*chunkPtr->displayProc)(chunkPtr, x, 0, dlPtr->height,
1329                  0, window, dlPtr->y);
1330             continue;
1331         }
1332         if ((x + chunkPtr->width <= 0) || (x >= dInfoPtr->maxX)) {
1333             /*
1334              * Note:  we have to call the displayProc even for chunks
1335              * that are off-screen.  This is needed, for example, so
1336              * that embedded windows can be unmapped in this case.
1337              * Display the chunk at a coordinate that can be clearly
1338              * identified by the displayProc as being off-screen to
1339              * the left (the displayProc may not be able to tell if
1340              * something is off to the right).
1341              */
1342
1343             (*chunkPtr->displayProc)(chunkPtr, -chunkPtr->width,
1344                     0, dlPtr->height, 0, window, dlPtr->y);
1345         } else {
1346             (*chunkPtr->displayProc)(chunkPtr, x, 0,
1347                     dlPtr->height, 0, window, dlPtr->y);
1348         }
1349         if (dInfoPtr->dLinesInvalidated) {
1350             return;
1351         }
1352     }
1353     linesRedrawn++;
1354 }
1355 \f
1356 /*
1357  *----------------------------------------------------------------------
1358  *
1359  * DisplayText --
1360  *
1361  *      This procedure is invoked as a when-idle handler to update the
1362  *      display.  It only redisplays the parts of the text widget that
1363  *      are out of date.
1364  *
1365  * Results:
1366  *      None.
1367  *
1368  * Side effects:
1369  *      Information is redrawn on the screen.
1370  *
1371  *----------------------------------------------------------------------
1372  */
1373
1374 static void
1375 DisplayText(clientData)
1376     ClientData clientData;      /* Information about widget. */
1377 {
1378     register CkText *textPtr = (CkText *) clientData;
1379     DInfo *dInfoPtr = textPtr->dInfoPtr;
1380     CkWindow *winPtr;
1381     register DLine *dlPtr;
1382     DLine *prevPtr;
1383     int maxHeight;
1384     int bottomY = 0;            /* Initialization needed only to stop
1385                                  * compiler warnings. */
1386
1387     if (textPtr->winPtr == NULL) {
1388         /*
1389          * The widget has been deleted.  Don't do anything.
1390          */
1391
1392         return;
1393     }
1394
1395     if (ckTextDebug) {
1396         Tcl_SetVar2(textPtr->interp, "ck_textRelayout", (char *) NULL,
1397                 "", TCL_GLOBAL_ONLY);
1398     }
1399
1400     if (!(textPtr->winPtr->flags & CK_MAPPED) ||
1401         (dInfoPtr->maxX <= dInfoPtr->x) || (dInfoPtr->maxY <= dInfoPtr->y)) {
1402         UpdateDisplayInfo(textPtr);
1403         dInfoPtr->flags &= ~REDRAW_PENDING;
1404         goto doScrollbars;
1405     }
1406     numRedisplays++;
1407     if (ckTextDebug) {
1408         Tcl_SetVar2(textPtr->interp, "ck_textRedraw", (char *) NULL,
1409                 "", TCL_GLOBAL_ONLY);
1410     }
1411
1412     /*
1413      * Choose a new current item if that is needed (this could cause
1414      * event handlers to be invoked, hence the preserve/release calls
1415      * and the loop, since the handlers could conceivably necessitate
1416      * yet another current item calculation).  The ckwin check is because
1417      * the whole window could go away in the Ck_Release call.
1418      */
1419
1420     while (dInfoPtr->flags & REPICK_NEEDED) {
1421         Ck_Preserve((ClientData) textPtr);
1422         dInfoPtr->flags &= ~REPICK_NEEDED;
1423         CkTextPickCurrent(textPtr, &textPtr->pickEvent);
1424         winPtr = textPtr->winPtr;
1425         Ck_Release((ClientData) textPtr);
1426         if (winPtr == NULL) {
1427             return;
1428         }
1429     }
1430
1431     /*
1432      * First recompute what's supposed to be displayed.
1433      */
1434
1435     UpdateDisplayInfo(textPtr);
1436     dInfoPtr->dLinesInvalidated = 0;
1437     dInfoPtr->flags &= ~REDRAW_PENDING;
1438
1439     maxHeight = -1;
1440     for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) {
1441         if ((dlPtr->height > maxHeight) && (dlPtr->oldY != dlPtr->y)) {
1442             maxHeight = dlPtr->height;
1443         }
1444         bottomY = dlPtr->y + dlPtr->height;
1445     }
1446     if (maxHeight > dInfoPtr->maxY) {
1447         maxHeight = dInfoPtr->maxY;
1448     }
1449
1450
1451     /*
1452      * Now we have to redraw the lines that couldn't be updated by
1453      * scrolling.  First, compute the height of the largest line and
1454      * allocate an off-screen pixmap to use for double-buffered
1455      * displays.
1456      */
1457
1458     if (maxHeight > 0) {
1459         for (prevPtr = NULL, dlPtr = textPtr->dInfoPtr->dLinePtr;
1460                 (dlPtr != NULL) && (dlPtr->y < dInfoPtr->maxY);
1461                 prevPtr = dlPtr, dlPtr = dlPtr->nextPtr) {
1462             if (dlPtr->oldY != dlPtr->y) {
1463                 if (ckTextDebug) {
1464                     char string[TK_POS_CHARS];
1465                     CkTextPrintIndex(&dlPtr->index, string);
1466                     Tcl_SetVar2(textPtr->interp, "ck_textRedraw",
1467                             (char *) NULL, string,
1468                             TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT);
1469                 }
1470                 DisplayDLine(textPtr, dlPtr, prevPtr, textPtr->winPtr->window);
1471                 if (dInfoPtr->dLinesInvalidated) {
1472                     goto done;
1473                 }
1474                 dlPtr->oldY = dlPtr->y;
1475                 dlPtr->flags &= ~NEW_LAYOUT;
1476             }
1477         }
1478     }
1479
1480     /*
1481      * See if we need to refresh the part of the window below the
1482      * last line of text (if there is any such area).
1483      */
1484
1485     if (dInfoPtr->topOfEof > dInfoPtr->maxY) {
1486         dInfoPtr->topOfEof = dInfoPtr->maxY;
1487     }
1488     if (bottomY < dInfoPtr->topOfEof) {
1489         if (ckTextDebug) {
1490             Tcl_SetVar2(textPtr->interp, "ck_textRedraw",
1491                     (char *) NULL, "eof",
1492                     TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT);
1493         }
1494         Ck_SetWindowAttr(textPtr->winPtr, textPtr->fg, textPtr->bg,
1495             textPtr->attr);
1496         Ck_ClearToBot(textPtr->winPtr, 0, bottomY);
1497     }
1498     dInfoPtr->topOfEof = bottomY;
1499
1500 doScrollbars:
1501
1502     /*
1503      * Update the vertical scrollbar, if there is one.  Note: it's
1504      * important to clear REDRAW_PENDING here, just in case the
1505      * scroll procedure does something that requires redisplay.
1506      */
1507
1508     if (textPtr->flags & UPDATE_SCROLLBARS) {
1509         textPtr->flags &= ~UPDATE_SCROLLBARS;
1510         if (textPtr->yScrollCmd != NULL) {
1511             GetYView(textPtr->interp, textPtr, 1);
1512         }
1513
1514         /*
1515          * Update the horizontal scrollbar, if any.
1516          */
1517
1518         if (textPtr->xScrollCmd != NULL) {
1519             GetXView(textPtr->interp, textPtr, 1);
1520         }
1521     }
1522 done:
1523     if (textPtr->insertX >= 0 &&
1524         textPtr->insertX < textPtr->winPtr->width &&
1525         textPtr->insertY >= 0 &&
1526         textPtr->insertY < textPtr->winPtr->height &&
1527         textPtr->winPtr->window != NULL) {
1528         wmove(textPtr->winPtr->window, textPtr->insertY, textPtr->insertX);
1529         Ck_SetHWCursor(textPtr->winPtr, 1);
1530     } else
1531         Ck_SetHWCursor(textPtr->winPtr, 0);
1532     Ck_EventuallyRefresh(textPtr->winPtr);
1533 }
1534 \f
1535 /*
1536  *----------------------------------------------------------------------
1537  *
1538  * CkTextEventuallyRepick --
1539  *
1540  *      This procedure is invoked whenever something happens that
1541  *      could change the current character or the tags associated
1542  *      with it.
1543  *
1544  * Results:
1545  *      None.
1546  *
1547  * Side effects:
1548  *      A repick is scheduled as an idle handler.
1549  *
1550  *----------------------------------------------------------------------
1551  */
1552
1553         /* ARGSUSED */
1554 void
1555 CkTextEventuallyRepick(textPtr)
1556     CkText *textPtr;            /* Widget record for text widget. */
1557 {
1558     DInfo *dInfoPtr = textPtr->dInfoPtr;
1559
1560     dInfoPtr->flags |= REPICK_NEEDED;
1561     if (!(dInfoPtr->flags & REDRAW_PENDING)) {
1562         dInfoPtr->flags |= REDRAW_PENDING;
1563         Tk_DoWhenIdle(DisplayText, (ClientData) textPtr);
1564     }
1565 }
1566 \f
1567 /*
1568  *----------------------------------------------------------------------
1569  *
1570  * CkTextRedrawRegion --
1571  *
1572  *      This procedure is invoked to schedule a redisplay for a given
1573  *      region of a text widget.  The redisplay itself may not occur
1574  *      immediately:  it's scheduled as a when-idle handler.
1575  *
1576  * Results:
1577  *      None.
1578  *
1579  * Side effects:
1580  *      Information will eventually be redrawn on the screen.
1581  *
1582  *----------------------------------------------------------------------
1583  */
1584
1585         /* ARGSUSED */
1586 void
1587 CkTextRedrawRegion(textPtr, x, y, width, height)
1588     CkText *textPtr;            /* Widget record for text widget. */
1589     int x, y;                   /* Coordinates of upper-left corner of area
1590                                  * to be redrawn, in pixels relative to
1591                                  * textPtr's window. */
1592     int width, height;          /* Width and height of area to be redrawn. */
1593 {
1594     register DLine *dlPtr;
1595     DInfo *dInfoPtr = textPtr->dInfoPtr;
1596     int maxY;
1597
1598     /*
1599      * Find all lines that overlap the given region and mark them for
1600      * redisplay.
1601      */
1602
1603     maxY = y + height;
1604     for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
1605             dlPtr = dlPtr->nextPtr) {
1606         if (((dlPtr->y + dlPtr->height) > y) && (dlPtr->y < maxY)) {
1607             dlPtr->oldY = -1;
1608         }
1609     }
1610     if (dInfoPtr->topOfEof < maxY) {
1611         dInfoPtr->topOfEof = maxY;
1612     }
1613
1614     /*
1615      * Schedule the redisplay operation if there isn't one already
1616      * scheduled.
1617      */
1618
1619     if (!(dInfoPtr->flags & REDRAW_PENDING)) {
1620         dInfoPtr->flags |= REDRAW_PENDING;
1621         Tk_DoWhenIdle(DisplayText, (ClientData) textPtr);
1622     }
1623 }
1624 \f
1625 /*
1626  *----------------------------------------------------------------------
1627  *
1628  * CkTextChanged --
1629  *
1630  *      This procedure is invoked when info in a text widget is about
1631  *      to be modified in a way that changes how it is displayed (e.g.
1632  *      characters were inserted or deleted, or tag information was
1633  *      changed).  This procedure must be called *before* a change is
1634  *      made, so that indexes in the display information are still
1635  *      valid.
1636  *
1637  * Results:
1638  *      None.
1639  *
1640  * Side effects:
1641  *      The range of character between index1Ptr (inclusive) and
1642  *      index2Ptr (exclusive) will be redisplayed at some point in the
1643  *      future (the actual redisplay is scheduled as a when-idle handler).
1644  *
1645  *----------------------------------------------------------------------
1646  */
1647
1648 void
1649 CkTextChanged(textPtr, index1Ptr, index2Ptr)
1650     CkText *textPtr;            /* Widget record for text widget. */
1651     CkTextIndex *index1Ptr;     /* Index of first character to redisplay. */
1652     CkTextIndex *index2Ptr;     /* Index of character just after last one
1653                                  * to redisplay. */
1654 {
1655     DInfo *dInfoPtr = textPtr->dInfoPtr;
1656     DLine *firstPtr, *lastPtr;
1657     CkTextIndex rounded;
1658
1659     /*
1660      * Schedule both a redisplay and a recomputation of display information.
1661      * It's done here rather than the end of the procedure for two reasons:
1662      *
1663      * 1. If there are no display lines to update we'll want to return
1664      *    immediately, well before the end of the procedure.
1665      * 2. It's important to arrange for the redisplay BEFORE calling
1666      *    FreeDLines.  The reason for this is subtle and has to do with
1667      *    embedded windows.  The chunk delete procedure for an embedded
1668      *    window will schedule an idle handler to unmap the window.
1669      *    However, we want the idle handler for redisplay to be called
1670      *    first, so that it can put the embedded window back on the screen
1671      *    again (if appropriate).  This will prevent the window from ever
1672      *    being unmapped, and thereby avoid flashing.
1673      */
1674
1675     if (!(dInfoPtr->flags & REDRAW_PENDING)) {
1676         Tk_DoWhenIdle(DisplayText, (ClientData) textPtr);
1677     }
1678     dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;
1679
1680     /*
1681      * Find the DLines corresponding to index1Ptr and index2Ptr.  There
1682      * is one tricky thing here, which is that we have to relayout in
1683      * units of whole text lines:  round index1Ptr back to the beginning
1684      * of its text line, and include all the display lines after index2,
1685      * up to the end of its text line.  This is necessary because the
1686      * indices stored in the display lines will no longer be valid.  It's
1687      * also needed because any edit could change the way lines wrap.
1688      */
1689
1690     rounded = *index1Ptr;
1691     rounded.charIndex = 0;
1692     firstPtr = FindDLine(dInfoPtr->dLinePtr, &rounded);
1693     if (firstPtr == NULL) {
1694         return;
1695     }
1696     lastPtr = FindDLine(dInfoPtr->dLinePtr, index2Ptr);
1697     while ((lastPtr != NULL)
1698             && (lastPtr->index.linePtr == index2Ptr->linePtr)) {
1699         lastPtr = lastPtr->nextPtr;
1700     }
1701
1702     /*
1703      * Delete all the DLines from firstPtr up to but not including lastPtr.
1704      */
1705
1706     FreeDLines(textPtr, firstPtr, lastPtr, 1);
1707 }
1708 \f
1709 /*
1710  *----------------------------------------------------------------------
1711  *
1712  * CkTextRedrawTag --
1713  *
1714  *      This procedure is invoked to request a redraw of all characters
1715  *      in a given range that have a particular tag on or off.  It's
1716  *      called, for example, when tag options change.
1717  *
1718  * Results:
1719  *      None.
1720  *
1721  * Side effects:
1722  *      Information on the screen may be redrawn, and the layout of
1723  *      the screen may change.
1724  *
1725  *----------------------------------------------------------------------
1726  */
1727
1728 void
1729 CkTextRedrawTag(textPtr, index1Ptr, index2Ptr, tagPtr, withTag)
1730     CkText *textPtr;            /* Widget record for text widget. */
1731     CkTextIndex *index1Ptr;     /* First character in range to consider
1732                                  * for redisplay.  NULL means start at
1733                                  * beginning of text. */
1734     CkTextIndex *index2Ptr;     /* Character just after last one to consider
1735                                  * for redisplay.  NULL means process all
1736                                  * the characters in the text. */
1737     CkTextTag *tagPtr;          /* Information about tag. */
1738     int withTag;                /* 1 means redraw characters that have the
1739                                  * tag, 0 means redraw those without. */
1740 {
1741     register DLine *dlPtr;
1742     DLine *endPtr;
1743     int tagOn;
1744     CkTextSearch search;
1745     DInfo *dInfoPtr = textPtr->dInfoPtr;
1746     CkTextIndex endOfText, *endIndexPtr;
1747
1748     /*
1749      * Round up the starting position if it's before the first line
1750      * visible on the screen (we only care about what's on the screen).
1751      */
1752
1753     dlPtr = dInfoPtr->dLinePtr;
1754     if (dlPtr == NULL) {
1755         return;
1756     }
1757     if ((index1Ptr == NULL) || (CkTextIndexCmp(&dlPtr->index, index1Ptr) > 0)) {
1758         index1Ptr = &dlPtr->index;
1759     }
1760
1761     /*
1762      * Set the stopping position if it wasn't specified.
1763      */
1764
1765     if (index2Ptr == NULL) {
1766 #if CK_USE_UTF
1767         index2Ptr = CkTextMakeByteIndex(textPtr->tree,
1768                 CkBTreeNumLines(textPtr->tree), 0, &endOfText);
1769 #else
1770         index2Ptr = CkTextMakeIndex(textPtr->tree,
1771                 CkBTreeNumLines(textPtr->tree), 0, &endOfText);
1772 #endif
1773     }
1774
1775     /* 
1776      * Initialize a search through all transitions on the tag, starting
1777      * with the first transition where the tag's current state is different
1778      * from what it will eventually be.
1779      */
1780
1781     CkBTreeStartSearch(index1Ptr, index2Ptr, tagPtr, &search);
1782     tagOn = CkBTreeCharTagged(index1Ptr, tagPtr);
1783     if (tagOn != withTag) {
1784         if (!CkBTreeNextTag(&search)) {
1785             return;
1786         }
1787     }
1788
1789     /*
1790      * Schedule a redisplay and layout recalculation if they aren't
1791      * already pending.  This has to be done before calling FreeDLines,
1792      * for the reason given in CkTextChanged.
1793      */
1794
1795     if (!(dInfoPtr->flags & REDRAW_PENDING)) {
1796         Tk_DoWhenIdle(DisplayText, (ClientData) textPtr);
1797     }
1798     dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;
1799
1800     /*
1801      * Each loop through the loop below is for one range of characters
1802      * where the tag's current state is different than its eventual
1803      * state.  At the top of the loop, search contains information about
1804      * the first character in the range.
1805      */
1806
1807     while (1) {
1808         /*
1809          * Find the first DLine structure in the range.  Note: if the
1810          * desired character isn't the first in its text line, then look
1811          * for the character just before it instead.  This is needed to
1812          * handle the case where the first character of a wrapped
1813          * display line just got smaller, so that it now fits on the
1814          * line before:  need to relayout the line containing the
1815          * previous character.
1816          */
1817
1818         if (search.curIndex.charIndex == 0) {
1819             dlPtr = FindDLine(dlPtr, &search.curIndex);
1820         } else {
1821             CkTextIndex tmp;
1822
1823             tmp = search.curIndex;
1824             tmp.charIndex -= 1;
1825             dlPtr = FindDLine(dlPtr, &tmp);
1826         }
1827         if (dlPtr == NULL) {
1828             break;
1829         }
1830
1831         /*
1832          * Find the first DLine structure that's past the end of the range.
1833          */
1834
1835         if (!CkBTreeNextTag(&search)) {
1836             endIndexPtr = index2Ptr;
1837         } else {
1838             endIndexPtr = &search.curIndex;
1839         }
1840         endPtr = FindDLine(dlPtr, endIndexPtr);
1841         if ((endPtr != NULL) && (endPtr->index.linePtr == endIndexPtr->linePtr)
1842                 && (endPtr->index.charIndex < endIndexPtr->charIndex)) {
1843             endPtr = endPtr->nextPtr;
1844         }
1845
1846         /*
1847          * Delete all of the display lines in the range, so that they'll
1848          * be re-layed out and redrawn.
1849          */
1850
1851         FreeDLines(textPtr, dlPtr, endPtr, 1);
1852         dlPtr = endPtr;
1853
1854         /*
1855          * Find the first text line in the next range.
1856          */
1857
1858         if (!CkBTreeNextTag(&search)) {
1859             break;
1860         }
1861     }
1862 }
1863 \f
1864 /*
1865  *----------------------------------------------------------------------
1866  *
1867  * CkTextRelayoutWindow --
1868  *
1869  *      This procedure is called when something has happened that
1870  *      invalidates the whole layout of characters on the screen, such
1871  *      as a change in a configuration option for the overall text
1872  *      widget or a change in the window size.  It causes all display
1873  *      information to be recomputed and the window to be redrawn.
1874  *
1875  * Results:
1876  *      None.
1877  *
1878  * Side effects:
1879  *      All the display information will be recomputed for the window
1880  *      and the window will be redrawn.
1881  *
1882  *----------------------------------------------------------------------
1883  */
1884
1885 void
1886 CkTextRelayoutWindow(textPtr)
1887     CkText *textPtr;            /* Widget record for text widget. */
1888 {
1889     DInfo *dInfoPtr = textPtr->dInfoPtr;
1890
1891     /*
1892      * Schedule the window redisplay.  See CkTextChanged for the
1893      * reason why this has to be done before any calls to FreeDLines.
1894      */
1895
1896     if (!(dInfoPtr->flags & REDRAW_PENDING)) {
1897         Tk_DoWhenIdle(DisplayText, (ClientData) textPtr);
1898     }
1899     dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;
1900
1901     /*
1902      * Throw away all the current layout information.
1903      */
1904
1905     FreeDLines(textPtr, dInfoPtr->dLinePtr, (DLine *) NULL, 1);
1906     dInfoPtr->dLinePtr = NULL;
1907
1908     /*
1909      * Recompute some overall things for the layout.  Even if the
1910      * window gets very small, pretend that there's at least one
1911      * pixel of drawing space in it.
1912      */
1913
1914     dInfoPtr->x = 0;
1915     dInfoPtr->y = 0;
1916     dInfoPtr->maxX = textPtr->winPtr->width;
1917     dInfoPtr->maxY = textPtr->winPtr->height;
1918     dInfoPtr->topOfEof = dInfoPtr->maxY;
1919
1920     /*
1921      * If the upper-left character isn't the first in a line, recompute
1922      * it.  This is necessary because a change in the window's size
1923      * or options could change the way lines wrap.
1924      */
1925
1926     if (textPtr->topIndex.charIndex != 0) {
1927         MeasureUp(textPtr, &textPtr->topIndex, 0, &textPtr->topIndex);
1928     }
1929 }
1930 \f
1931 /*
1932  *----------------------------------------------------------------------
1933  *
1934  * CkTextSetYView --
1935  *
1936  *      This procedure is called to specify what lines are to be
1937  *      displayed in a text widget.
1938  *
1939  * Results:
1940  *      None.
1941  *
1942  * Side effects:
1943  *      The display will (eventually) be updated so that the position
1944  *      given by "indexPtr" is visible on the screen at the position
1945  *      determined by "pickPlace".
1946  *
1947  *----------------------------------------------------------------------
1948  */
1949
1950 void
1951 CkTextSetYView(textPtr, indexPtr, pickPlace)
1952     CkText *textPtr;            /* Widget record for text widget. */
1953     CkTextIndex *indexPtr;      /* Position that is to appear somewhere
1954                                  * in the view. */
1955     int pickPlace;              /* 0 means topLine must appear at top of
1956                                  * screen.  1 means we get to pick where it
1957                                  * appears:  minimize screen motion or else
1958                                  * display line at center of screen. */
1959 {
1960     DInfo *dInfoPtr = textPtr->dInfoPtr;
1961     register DLine *dlPtr;
1962     int bottomY, close, lineIndex;
1963     CkTextIndex tmpIndex, rounded;
1964
1965     /*
1966      * If the specified position is the extra line at the end of the
1967      * text, round it back to the last real line.
1968      */
1969
1970     lineIndex = CkBTreeLineIndex(indexPtr->linePtr);
1971     if (lineIndex == CkBTreeNumLines(indexPtr->tree)) {
1972         CkTextIndexBackChars(indexPtr, 1, &rounded);
1973         indexPtr = &rounded;
1974     }
1975
1976     if (!pickPlace) {
1977         /*
1978          * The specified position must go at the top of the screen.
1979          * Just leave all the DLine's alone: we may be able to reuse
1980          * some of the information that's currently on the screen
1981          * without redisplaying it all.
1982          */
1983
1984         if (indexPtr->charIndex == 0) {
1985             textPtr->topIndex = *indexPtr;
1986         } else {
1987             MeasureUp(textPtr, indexPtr, 0, &textPtr->topIndex);
1988         }
1989         goto scheduleUpdate;
1990     }
1991
1992     /*
1993      * We have to pick where to display the index.  First, bring
1994      * the display information up to date and see if the index will be
1995      * completely visible in the current screen configuration.  If so
1996      * then there's nothing to do.
1997      */
1998
1999     if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
2000         UpdateDisplayInfo(textPtr);
2001     }
2002     dlPtr = FindDLine(dInfoPtr->dLinePtr, indexPtr);
2003     if (dlPtr != NULL) {
2004         if ((dlPtr->y + dlPtr->height) > dInfoPtr->maxY) {
2005             /*
2006              * Part of the line hangs off the bottom of the screen;
2007              * pretend the whole line is off-screen.
2008              */
2009
2010             dlPtr = NULL;
2011         } else if ((dlPtr->index.linePtr == indexPtr->linePtr)
2012                 && (dlPtr->index.charIndex <= indexPtr->charIndex)) {
2013             return;
2014         }
2015     }
2016
2017     /*
2018      * The desired line isn't already on-screen.
2019      */
2020
2021     bottomY = (dInfoPtr->y + dInfoPtr->maxY)/2;
2022     close = (dInfoPtr->maxY - dInfoPtr->y)/3;
2023     if (dlPtr != NULL) {
2024         /*
2025          * The desired line is above the top of screen.  If it is
2026          * "close" to the top of the window then make it the top
2027          * line on the screen.
2028          */
2029
2030         MeasureUp(textPtr, &textPtr->topIndex, close, &tmpIndex);
2031         if (CkTextIndexCmp(&tmpIndex, indexPtr) <= 0) {
2032             MeasureUp(textPtr, indexPtr, 0, &textPtr->topIndex);
2033             goto scheduleUpdate;
2034         }
2035     } else {
2036         /*
2037          * The desired line is below the bottom of the screen.  If it is
2038          * "close" to the bottom of the screen then position it at the
2039          * bottom of the screen.
2040          */
2041
2042         MeasureUp(textPtr, indexPtr, close, &tmpIndex);
2043         if (FindDLine(dInfoPtr->dLinePtr, &tmpIndex) != NULL) {
2044             bottomY = dInfoPtr->maxY - dInfoPtr->y;
2045         }
2046     }
2047
2048     /*
2049      * Our job now is to arrange the display so that indexPtr appears
2050      * as low on the screen as possible but with its bottom no lower
2051      * than bottomY.  BottomY is the bottom of the window if the
2052      * desired line is just below the current screen, otherwise it
2053      * is the center of the window.
2054      */
2055
2056     MeasureUp(textPtr, indexPtr, bottomY, &textPtr->topIndex);
2057
2058     scheduleUpdate:
2059     if (!(dInfoPtr->flags & REDRAW_PENDING)) {
2060         Tk_DoWhenIdle(DisplayText, (ClientData) textPtr);
2061     }
2062     dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;
2063 }
2064 \f
2065 /*
2066  *--------------------------------------------------------------
2067  *
2068  * MeasureUp --
2069  *
2070  *      Given one index, find the index of the first character
2071  *      on the highest display line that would be displayed no more
2072  *      than "distance" pixels above the given index.
2073  *
2074  * Results:
2075  *      *dstPtr is filled in with the index of the first character
2076  *      on a display line.  The display line is found by measuring
2077  *      up "distance" pixels above the pixel just below an imaginary
2078  *      display line that contains srcPtr.  If the display line
2079  *      that covers this coordinate actually extends above the 
2080  *      coordinate, then return the index of the next lower line
2081  *      instead (i.e. the returned index will be completely visible
2082  *      at or below the given y-coordinate).
2083  *
2084  * Side effects:
2085  *      None.
2086  *
2087  *--------------------------------------------------------------
2088  */
2089
2090 static void
2091 MeasureUp(textPtr, srcPtr, distance, dstPtr)
2092     CkText *textPtr;            /* Text widget in which to measure. */
2093     CkTextIndex *srcPtr;        /* Index of character from which to start
2094                                  * measuring. */
2095     int distance;               /* Vertical distance in pixels measured
2096                                  * from the pixel just below the lowest
2097                                  * one in srcPtr's line. */
2098     CkTextIndex *dstPtr;        /* Index to fill in with result. */
2099 {
2100     int lineNum;                /* Number of current line. */
2101     int charsToCount;           /* Maximum number of characters to measure
2102                                  * in current line. */
2103     CkTextIndex bestIndex;      /* Best candidate seen so far for result. */
2104     CkTextIndex index;
2105     DLine *dlPtr, *lowestPtr;
2106     int noBestYet;              /* 1 means bestIndex hasn't been set. */
2107
2108     noBestYet = 1;
2109     charsToCount = srcPtr->charIndex + 1;
2110     index.tree = srcPtr->tree;
2111     for (lineNum = CkBTreeLineIndex(srcPtr->linePtr); lineNum >= 0;
2112             lineNum--) {
2113         /*
2114          * Layout an entire text line (potentially > 1 display line).
2115          * For the first line, which contains srcPtr, only layout the
2116          * part up through srcPtr (charsToCount is non-infinite to
2117          * accomplish this).  Make a list of all the display lines
2118          * in backwards order (the lowest DLine on the screen is first
2119          * in the list).
2120          */
2121
2122         index.linePtr = CkBTreeFindLine(srcPtr->tree, lineNum);
2123         index.charIndex = 0;
2124         lowestPtr = NULL;
2125         do {
2126             dlPtr = LayoutDLine(textPtr, &index);
2127             dlPtr->nextPtr = lowestPtr;
2128             lowestPtr = dlPtr;
2129 #if CK_USE_UTF
2130             CkTextIndexForwBytes(&index, dlPtr->count, &index);
2131 #else
2132             CkTextIndexForwChars(&index, dlPtr->count, &index);
2133 #endif
2134             charsToCount -= dlPtr->count;
2135         } while ((charsToCount > 0) && (index.linePtr == dlPtr->index.linePtr));
2136
2137         /*
2138          * Scan through the display lines to see if we've covered enough
2139          * vertical distance.  If so, save the starting index for the
2140          * line at the desired location.
2141          */
2142
2143         for (dlPtr = lowestPtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) {
2144             distance -= dlPtr->height;
2145             if (distance < 0) {
2146                 *dstPtr = (noBestYet) ? dlPtr->index : bestIndex;
2147                 break;
2148             }
2149             bestIndex = dlPtr->index;
2150             noBestYet = 0;
2151         }
2152
2153         /*
2154          * Discard the display lines, then either return or prepare
2155          * for the next display line to lay out.
2156          */
2157
2158         FreeDLines(textPtr, lowestPtr, (DLine *) NULL, 0);
2159         if (distance < 0) {
2160             return;
2161         }
2162         charsToCount = INT_MAX;         /* Consider all chars. in next line. */
2163     }
2164
2165     /*
2166      * Ran off the beginning of the text.  Return the first character
2167      * in the text.
2168      */
2169 #if CK_USE_UTF
2170     CkTextMakeByteIndex(textPtr->tree, 0, 0, dstPtr);
2171 #else
2172     CkTextMakeIndex(textPtr->tree, 0, 0, dstPtr);
2173 #endif
2174 }
2175 \f
2176 /*
2177  *--------------------------------------------------------------
2178  *
2179  * CkTextSeeCmd --
2180  *
2181  *      This procedure is invoked to process the "see" option for
2182  *      the widget command for text widgets. See the user documentation
2183  *      for details on what it does.
2184  *
2185  * Results:
2186  *      A standard Tcl result.
2187  *
2188  * Side effects:
2189  *      See the user documentation.
2190  *
2191  *--------------------------------------------------------------
2192  */
2193
2194 int
2195 CkTextSeeCmd(textPtr, interp, argc, argv)
2196     CkText *textPtr;            /* Information about text widget. */
2197     Tcl_Interp *interp;         /* Current interpreter. */
2198     int argc;                   /* Number of arguments. */
2199     char **argv;                /* Argument strings.  Someone else has already
2200                                  * parsed this command enough to know that
2201                                  * argv[1] is "see". */
2202 {
2203     DInfo *dInfoPtr = textPtr->dInfoPtr;
2204     CkTextIndex index;
2205     int x, y, width, height, lineWidth, charCount, oneThird, delta;
2206     DLine *dlPtr;
2207     CkTextDispChunk *chunkPtr;
2208
2209     if (argc != 3) {
2210         Tcl_AppendResult(interp, "wrong # args: should be \"",
2211                 argv[0], " see index\"", (char *) NULL);
2212         return TCL_ERROR;
2213     }
2214     if (CkTextGetIndex(interp, textPtr, argv[2], &index) != TCL_OK) {
2215         return TCL_ERROR;
2216     }
2217
2218     /*
2219      * If the specified position is the extra line at the end of the
2220      * text, round it back to the last real line.
2221      */
2222
2223     if (CkBTreeLineIndex(index.linePtr) == CkBTreeNumLines(index.tree)) {
2224         CkTextIndexBackChars(&index, 1, &index);
2225     }
2226
2227     /*
2228      * First get the desired position into the vertical range of the window.
2229      */
2230
2231     CkTextSetYView(textPtr, &index, 1);
2232
2233     /*
2234      * Now make sure that the character is in view horizontally.
2235      */
2236
2237     if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
2238         UpdateDisplayInfo(textPtr);
2239     }
2240     lineWidth = dInfoPtr->maxX - dInfoPtr->x;
2241     if (dInfoPtr->maxLength < lineWidth) {
2242         return TCL_OK;
2243     }
2244
2245     /*
2246      * Find the chunk that contains the desired index.
2247      */
2248
2249     dlPtr = FindDLine(dInfoPtr->dLinePtr, &index);
2250     charCount = index.charIndex - dlPtr->index.charIndex;
2251     for (chunkPtr = dlPtr->chunkPtr; ; chunkPtr = chunkPtr->nextPtr) {
2252         if (charCount < chunkPtr->numChars) {
2253             break;
2254         }
2255         charCount -= chunkPtr->numChars;
2256     }
2257
2258     /*
2259      * Call a chunk-specific procedure to find the horizontal range of
2260      * the character within the chunk.
2261      */
2262
2263     (*chunkPtr->bboxProc)(chunkPtr, charCount, dlPtr->y,
2264             dlPtr->height, 0, &x, &y, &width, &height);
2265     delta = x - dInfoPtr->curOffset;
2266     oneThird = lineWidth/3;
2267     if (delta < 0) {
2268         if (delta < -oneThird) {
2269             dInfoPtr->newCharOffset = (x - lineWidth/2);
2270         } else {
2271             dInfoPtr->newCharOffset -= -delta;
2272         }
2273     } else {
2274         delta -= (lineWidth - width);
2275         if (delta >= 0) {
2276             if (delta > oneThird) {
2277                 dInfoPtr->newCharOffset = (x - lineWidth/2);
2278             } else {
2279                 dInfoPtr->newCharOffset += delta + 1;
2280             }
2281         } else {
2282             return TCL_OK;
2283         }
2284     }
2285     dInfoPtr->flags |= DINFO_OUT_OF_DATE;
2286     if (!(dInfoPtr->flags & REDRAW_PENDING)) {
2287         dInfoPtr->flags |= REDRAW_PENDING;
2288         Tk_DoWhenIdle(DisplayText, (ClientData) textPtr);
2289     }
2290     return TCL_OK;
2291 }
2292 \f
2293 /*
2294  *--------------------------------------------------------------
2295  *
2296  * CkTextXviewCmd --
2297  *
2298  *      This procedure is invoked to process the "xview" option for
2299  *      the widget command for text widgets. See the user documentation
2300  *      for details on what it does.
2301  *
2302  * Results:
2303  *      A standard Tcl result.
2304  *
2305  * Side effects:
2306  *      See the user documentation.
2307  *
2308  *--------------------------------------------------------------
2309  */
2310
2311 int
2312 CkTextXviewCmd(textPtr, interp, argc, argv)
2313     CkText *textPtr;            /* Information about text widget. */
2314     Tcl_Interp *interp;         /* Current interpreter. */
2315     int argc;                   /* Number of arguments. */
2316     char **argv;                /* Argument strings.  Someone else has already
2317                                  * parsed this command enough to know that
2318                                  * argv[1] is "xview". */
2319 {
2320     DInfo *dInfoPtr = textPtr->dInfoPtr;
2321     int type, charsPerPage, count, newOffset;
2322     double fraction;
2323
2324     if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
2325         UpdateDisplayInfo(textPtr);
2326     }
2327
2328     if (argc == 2) {
2329         GetXView(interp, textPtr, 0);
2330         return TCL_OK;
2331     }
2332
2333     newOffset = dInfoPtr->newCharOffset;
2334     type = Ck_GetScrollInfo(interp, argc, argv, &fraction, &count);
2335     switch (type) {
2336         case CK_SCROLL_ERROR:
2337             return TCL_ERROR;
2338         case CK_SCROLL_MOVETO:
2339             newOffset = (int) (fraction * dInfoPtr->maxLength);
2340             break;
2341         case CK_SCROLL_PAGES:
2342             charsPerPage = dInfoPtr->maxX - dInfoPtr->x - 2;
2343             if (charsPerPage < 1) {
2344                 charsPerPage = 1;
2345             }
2346             newOffset += charsPerPage*count;
2347             break;
2348         case CK_SCROLL_UNITS:
2349             newOffset += count;
2350             break;
2351     }
2352
2353     dInfoPtr->newCharOffset = newOffset;
2354     dInfoPtr->flags |= DINFO_OUT_OF_DATE;
2355     if (!(dInfoPtr->flags & REDRAW_PENDING)) {
2356         dInfoPtr->flags |= REDRAW_PENDING;
2357         Tk_DoWhenIdle(DisplayText, (ClientData) textPtr);
2358     }
2359     return TCL_OK;
2360 }
2361 \f
2362 /*
2363  *----------------------------------------------------------------------
2364  *
2365  * ScrollByLines --
2366  *
2367  *      This procedure is called to scroll a text widget up or down
2368  *      by a given number of lines.
2369  *
2370  * Results:
2371  *      None.
2372  *
2373  * Side effects:
2374  *      The view in textPtr's window changes to reflect the value
2375  *      of "offset".
2376  *
2377  *----------------------------------------------------------------------
2378  */
2379
2380 static void
2381 ScrollByLines(textPtr, offset)
2382     CkText *textPtr;            /* Widget to scroll. */
2383     int offset;                 /* Amount by which to scroll, in *screen*
2384                                  * lines.  Positive means that information
2385                                  * later in text becomes visible, negative
2386                                  * means that information earlier in the
2387                                  * text becomes visible. */
2388 {
2389     int i, charsToCount, lineNum;
2390     CkTextIndex new, index;
2391     CkTextLine *lastLinePtr;
2392     DInfo *dInfoPtr = textPtr->dInfoPtr;
2393     DLine *dlPtr, *lowestPtr;
2394
2395     if (offset < 0) {
2396         /*
2397          * Must scroll up (to show earlier information in the text).
2398          * The code below is similar to that in MeasureUp, except that
2399          * it counts lines instead of pixels.
2400          */
2401
2402         charsToCount = textPtr->topIndex.charIndex + 1;
2403         index.tree = textPtr->tree;
2404         offset--;                       /* Skip line containing topIndex. */
2405         for (lineNum = CkBTreeLineIndex(textPtr->topIndex.linePtr);
2406                 lineNum >= 0; lineNum--) {
2407             index.linePtr = CkBTreeFindLine(textPtr->tree, lineNum);
2408             index.charIndex = 0;
2409             lowestPtr = NULL;
2410             do {
2411                 dlPtr = LayoutDLine(textPtr, &index);
2412                 dlPtr->nextPtr = lowestPtr;
2413                 lowestPtr = dlPtr;
2414 #if CK_USE_UTF
2415                 CkTextIndexForwBytes(&index, dlPtr->count, &index);
2416 #else
2417                 CkTextIndexForwChars(&index, dlPtr->count, &index);
2418 #endif
2419                 charsToCount -= dlPtr->count;
2420             } while ((charsToCount > 0)
2421                     && (index.linePtr == dlPtr->index.linePtr));
2422
2423             for (dlPtr = lowestPtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) {
2424                 offset++;
2425                 if (offset == 0) {
2426                     textPtr->topIndex = dlPtr->index;
2427                     break;
2428                 }
2429             }
2430     
2431             /*
2432              * Discard the display lines, then either return or prepare
2433              * for the next display line to lay out.
2434              */
2435     
2436             FreeDLines(textPtr, lowestPtr, (DLine *) NULL, 0);
2437             if (offset >= 0) {
2438                 goto scheduleUpdate;
2439             }
2440             charsToCount = INT_MAX;
2441         }
2442     
2443         /*
2444          * Ran off the beginning of the text.  Return the first character
2445          * in the text.
2446          */
2447 #if CK_USE_UTF
2448         CkTextMakeByteIndex(textPtr->tree, 0, 0, &textPtr->topIndex);
2449 #else
2450         CkTextMakeIndex(textPtr->tree, 0, 0, &textPtr->topIndex);
2451 #endif
2452     } else {
2453         /*
2454          * Scrolling down, to show later information in the text.
2455          * Just count lines from the current top of the window.
2456          */
2457
2458         lastLinePtr = CkBTreeFindLine(textPtr->tree,
2459                 CkBTreeNumLines(textPtr->tree));
2460         for (i = 0; i < offset; i++) {
2461             dlPtr = LayoutDLine(textPtr, &textPtr->topIndex);
2462             dlPtr->nextPtr = NULL;
2463 #if CK_USE_UTF
2464             if (dlPtr->length == 0 && dlPtr->height == 0) {
2465                 offset++;
2466             }
2467             CkTextIndexForwBytes(&textPtr->topIndex, dlPtr->count, &new);
2468 #else
2469             CkTextIndexForwChars(&textPtr->topIndex, dlPtr->count, &new);
2470 #endif
2471             FreeDLines(textPtr, dlPtr, (DLine *) NULL, 0);
2472             if (new.linePtr == lastLinePtr) {
2473                 break;
2474             }
2475             textPtr->topIndex = new;
2476         }
2477     }
2478
2479     scheduleUpdate:
2480     if (!(dInfoPtr->flags & REDRAW_PENDING)) {
2481         Tk_DoWhenIdle(DisplayText, (ClientData) textPtr);
2482     }
2483     dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;
2484 }
2485 \f
2486 /*
2487  *--------------------------------------------------------------
2488  *
2489  * CkTextYviewCmd --
2490  *
2491  *      This procedure is invoked to process the "yview" option for
2492  *      the widget command for text widgets. See the user documentation
2493  *      for details on what it does.
2494  *
2495  * Results:
2496  *      A standard Tcl result.
2497  *
2498  * Side effects:
2499  *      See the user documentation.
2500  *
2501  *--------------------------------------------------------------
2502  */
2503
2504 int
2505 CkTextYviewCmd(textPtr, interp, argc, argv)
2506     CkText *textPtr;            /* Information about text widget. */
2507     Tcl_Interp *interp;         /* Current interpreter. */
2508     int argc;                   /* Number of arguments. */
2509     char **argv;                /* Argument strings.  Someone else has already
2510                                  * parsed this command enough to know that
2511                                  * argv[1] is "yview". */
2512 {
2513     DInfo *dInfoPtr = textPtr->dInfoPtr;
2514     int pickPlace, lineNum, type, lineHeight;
2515     int pixels, count;
2516     size_t switchLength;
2517     double fraction;
2518     CkTextIndex index, new;
2519     CkTextLine *lastLinePtr;
2520     DLine *dlPtr;
2521 #if CK_USE_UTF
2522     int bytesInLine;
2523 #endif
2524
2525     if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
2526         UpdateDisplayInfo(textPtr);
2527     }
2528
2529     if (argc == 2) {
2530         GetYView(interp, textPtr, 0);
2531         return TCL_OK;
2532     }
2533
2534     /*
2535      * Next, handle the old syntax: "pathName yview ?-pickplace? where"
2536      */
2537
2538     pickPlace = 0;
2539     if (argv[2][0] == '-') {
2540         switchLength = strlen(argv[2]);
2541         if ((switchLength >= 2)
2542                 && (strncmp(argv[2], "-pickplace", switchLength) == 0)) {
2543             pickPlace = 1;
2544             if (argc != 4) {
2545                 Tcl_AppendResult(interp, "wrong # args: should be \"",
2546                         argv[0], " yview -pickplace lineNum|index\"",
2547                         (char *) NULL);
2548                 return TCL_ERROR;
2549             }
2550         }
2551     }
2552     if ((argc == 3) || pickPlace) {
2553         if (Tcl_GetInt(interp, argv[2+pickPlace], &lineNum) == TCL_OK) {
2554 #if CK_USE_UTF
2555             CkTextMakeByteIndex(textPtr->tree, lineNum, 0, &index);
2556 #else
2557             CkTextMakeIndex(textPtr->tree, lineNum, 0, &index);
2558 #endif
2559             CkTextSetYView(textPtr, &index, 0);
2560             return TCL_OK;
2561         }
2562     
2563         /*
2564          * The argument must be a regular text index.
2565          */
2566     
2567         Tcl_ResetResult(interp);
2568         if (CkTextGetIndex(interp, textPtr, argv[2+pickPlace],
2569                 &index) != TCL_OK) {
2570             return TCL_ERROR;
2571         }
2572         CkTextSetYView(textPtr, &index, pickPlace);
2573         return TCL_OK;
2574     }
2575
2576     /*
2577      * New syntax: dispatch based on argv[2].
2578      */
2579
2580     type = Ck_GetScrollInfo(interp, argc, argv, &fraction, &count);
2581     switch (type) {
2582         case CK_SCROLL_ERROR:
2583             return TCL_ERROR;
2584         case CK_SCROLL_MOVETO:
2585 #if CK_USE_UTF
2586             if (fraction > 1.0) {
2587                 fraction = 1.0;
2588             }
2589             if (fraction < 0) {
2590                 fraction = 0;
2591             }
2592             fraction *= CkBTreeNumLines(textPtr->tree);
2593             lineNum = (int) fraction;
2594             CkTextMakeByteIndex(textPtr->tree, lineNum, 0, &index);
2595             bytesInLine = CkBTreeCharsInLine(index.linePtr);
2596             index.charIndex = (int)((bytesInLine * (fraction-lineNum)) + 0.5);
2597             if (index.charIndex >= bytesInLine) {
2598                 CkTextMakeByteIndex(textPtr->tree, lineNum + 1, 0, &index);
2599             }
2600 #else
2601             fraction *= CkBTreeNumLines(textPtr->tree);
2602             lineNum = (int) fraction;
2603             CkTextMakeIndex(textPtr->tree, lineNum+1, 0, &index);
2604             CkTextIndexBackChars(&index, 1, &index);
2605             index.charIndex = (int) ((index.charIndex+1)*(fraction-lineNum));
2606 #endif
2607             CkTextSetYView(textPtr, &index, 0);
2608             break;
2609         case CK_SCROLL_PAGES:
2610             /*
2611              * Scroll up or down by screenfulls.  Actually, use the
2612              * window height minus two lines, so that there's some
2613              * overlap between adjacent pages.
2614              */
2615
2616             lineHeight = 1;
2617             if (count < 0) {
2618                 pixels = (dInfoPtr->maxY - 2*lineHeight - dInfoPtr->y)*(-count)
2619                         + lineHeight;
2620                 MeasureUp(textPtr, &textPtr->topIndex, pixels, &new);
2621                 if (CkTextIndexCmp(&textPtr->topIndex, &new) == 0) {
2622                     /*
2623                      * A page of scrolling ended up being less than one line.
2624                      * Scroll one line anyway.
2625                      */
2626
2627                     count = -1;
2628                     goto scrollByLines;
2629                 }
2630                 textPtr->topIndex = new;
2631             } else {
2632                 /*
2633                  * Scrolling down by pages.  Layout lines starting at the
2634                  * top index and count through the desired vertical distance.
2635                  */
2636
2637                 pixels = (dInfoPtr->maxY - 2*lineHeight - dInfoPtr->y)*count;
2638                 lastLinePtr = CkBTreeFindLine(textPtr->tree,
2639                         CkBTreeNumLines(textPtr->tree));
2640                 do {
2641                     dlPtr = LayoutDLine(textPtr, &textPtr->topIndex);
2642                     dlPtr->nextPtr = NULL;
2643 #if CK_USE_UTF
2644                     CkTextIndexForwBytes(&textPtr->topIndex, dlPtr->count,
2645                             &new);
2646 #else
2647                     CkTextIndexForwChars(&textPtr->topIndex, dlPtr->count,
2648                             &new);
2649 #endif
2650                     pixels -= dlPtr->height;
2651                     FreeDLines(textPtr, dlPtr, (DLine *) NULL, 0);
2652                     if (new.linePtr == lastLinePtr) {
2653                         break;
2654                     }
2655                     textPtr->topIndex = new;
2656                 } while (pixels > 0);
2657             }
2658             if (!(dInfoPtr->flags & REDRAW_PENDING)) {
2659                 Tk_DoWhenIdle(DisplayText, (ClientData) textPtr);
2660             }
2661             dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;
2662             break;
2663         case CK_SCROLL_UNITS:
2664             scrollByLines:
2665             ScrollByLines(textPtr, count);
2666             break;
2667     }
2668     return TCL_OK;
2669 }
2670 \f
2671 /*
2672  *----------------------------------------------------------------------
2673  *
2674  * GetXView --
2675  *
2676  *      This procedure computes the fractions that indicate what's
2677  *      visible in a text window and, optionally, evaluates a
2678  *      Tcl script to report them to the text's associated scrollbar.
2679  *
2680  * Results:
2681  *      If report is zero, then interp->result is filled in with
2682  *      two real numbers separated by a space, giving the position of
2683  *      the left and right edges of the window as fractions from 0 to
2684  *      1, where 0 means the left edge of the text and 1 means the right
2685  *      edge.  If report is non-zero, then interp->result isn't modified
2686  *      directly, but instead a script is evaluated in interp to report
2687  *      the new horizontal scroll position to the scrollbar (if the scroll
2688  *      position hasn't changed then no script is invoked).
2689  *
2690  * Side effects:
2691  *      None.
2692  *
2693  *----------------------------------------------------------------------
2694  */
2695
2696 static void
2697 GetXView(interp, textPtr, report)
2698     Tcl_Interp *interp;                 /* If "report" is FALSE, string
2699                                          * describing visible range gets
2700                                          * stored in interp->result. */
2701     CkText *textPtr;                    /* Information about text widget. */
2702     int report;                         /* Non-zero means report info to
2703                                          * scrollbar if it has changed. */
2704 {
2705     DInfo *dInfoPtr = textPtr->dInfoPtr;
2706     char buffer[200];
2707     double first, last;
2708     int code;
2709
2710     if (dInfoPtr->maxLength > 0) {
2711         first = ((double) dInfoPtr->curOffset)
2712                 / dInfoPtr->maxLength;
2713         last = first + ((double) (dInfoPtr->maxX - dInfoPtr->x))
2714                 / dInfoPtr->maxLength;
2715         if (last > 1.0) {
2716             last = 1.0;
2717         }
2718     } else {
2719         first = 0;
2720         last = 1.0;
2721     }
2722     if (!report) {
2723         sprintf(interp->result, "%g %g", first, last);
2724         return;
2725     }
2726     if ((first == dInfoPtr->xScrollFirst) && (last == dInfoPtr->xScrollLast)) {
2727         return;
2728     }
2729     dInfoPtr->xScrollFirst = first;
2730     dInfoPtr->xScrollLast = last;
2731     sprintf(buffer, " %g %g", first, last);
2732     code = Tcl_VarEval(interp, textPtr->xScrollCmd,
2733             buffer, (char *) NULL);
2734     if (code != TCL_OK) {
2735         Tcl_AddErrorInfo(interp,
2736                 "\n    (horizontal scrolling command executed by text)");
2737         Tk_BackgroundError(interp);
2738     }
2739 }
2740 \f
2741 /*
2742  *----------------------------------------------------------------------
2743  *
2744  * GetYView --
2745  *
2746  *      This procedure computes the fractions that indicate what's
2747  *      visible in a text window and, optionally, evaluates a
2748  *      Tcl script to report them to the text's associated scrollbar.
2749  *
2750  * Results:
2751  *      If report is zero, then interp->result is filled in with
2752  *      two real numbers separated by a space, giving the position of
2753  *      the top and bottom of the window as fractions from 0 to 1, where
2754  *      0 means the beginning of the text and 1 means the end.  If
2755  *      report is non-zero, then interp->result isn't modified directly,
2756  *      but a script is evaluated in interp to report the new scroll
2757  *      position to the scrollbar (if the scroll position hasn't changed
2758  *      then no script is invoked).
2759  *
2760  * Side effects:
2761  *      None.
2762  *
2763  *----------------------------------------------------------------------
2764  */
2765
2766 static void
2767 GetYView(interp, textPtr, report)
2768     Tcl_Interp *interp;                 /* If "report" is FALSE, string
2769                                          * describing visible range gets
2770                                          * stored in interp->result. */
2771     CkText *textPtr;                    /* Information about text widget. */
2772     int report;                         /* Non-zero means report info to
2773                                          * scrollbar if it has changed. */
2774 {
2775     DInfo *dInfoPtr = textPtr->dInfoPtr;
2776     char buffer[200];
2777     double first, last;
2778     DLine *dlPtr;
2779     int totalLines, code, count;
2780
2781     dlPtr = dInfoPtr->dLinePtr;
2782     totalLines = CkBTreeNumLines(textPtr->tree);
2783     first = ((double) CkBTreeLineIndex(dlPtr->index.linePtr))
2784             + ((double) dlPtr->index.charIndex)
2785             / (CkBTreeCharsInLine(dlPtr->index.linePtr));
2786     first /= totalLines;
2787     while (1) {
2788         if ((dlPtr->y + dlPtr->height) > dInfoPtr->maxY) {
2789             /*
2790              * The last line is only partially visible, so don't
2791              * count its characters in what's visible.
2792              */
2793             count = 0;
2794             break;
2795         }
2796         if (dlPtr->nextPtr == NULL) {
2797             count = dlPtr->count;
2798             break;
2799         }
2800         dlPtr = dlPtr->nextPtr;
2801     }
2802     last = ((double) CkBTreeLineIndex(dlPtr->index.linePtr))
2803             + ((double) (dlPtr->index.charIndex + count))
2804             / (CkBTreeCharsInLine(dlPtr->index.linePtr));
2805     last /= totalLines;
2806     if (!report) {
2807         sprintf(interp->result, "%g %g", first, last);
2808         return;
2809     }
2810     if ((first == dInfoPtr->yScrollFirst) && (last == dInfoPtr->yScrollLast)) {
2811         return;
2812     }
2813     dInfoPtr->yScrollFirst = first;
2814     dInfoPtr->yScrollLast = last;
2815     sprintf(buffer, " %g %g", first, last);
2816     code = Tcl_VarEval(interp, textPtr->yScrollCmd,
2817             buffer, (char *) NULL);
2818     if (code != TCL_OK) {
2819         Tcl_AddErrorInfo(interp,
2820                 "\n    (vertical scrolling command executed by text)");
2821         Tk_BackgroundError(interp);
2822     }
2823 }
2824 \f
2825 /*
2826  *----------------------------------------------------------------------
2827  *
2828  * FindDLine --
2829  *
2830  *      This procedure is called to find the DLine corresponding to a
2831  *      given text index.
2832  *
2833  * Results:
2834  *      The return value is a pointer to the first DLine found in the
2835  *      list headed by dlPtr that displays information at or after the
2836  *      specified position.  If there is no such line in the list then
2837  *      NULL is returned.
2838  *
2839  * Side effects:
2840  *      None.
2841  *
2842  *----------------------------------------------------------------------
2843  */
2844
2845 static DLine *
2846 FindDLine(dlPtr, indexPtr)
2847     register DLine *dlPtr;      /* Pointer to first in list of DLines
2848                                  * to search. */
2849     CkTextIndex *indexPtr;      /* Index of desired character. */
2850 {
2851     CkTextLine *linePtr;
2852
2853     if (dlPtr == NULL) {
2854         return NULL;
2855     }
2856     if (CkBTreeLineIndex(indexPtr->linePtr)
2857             < CkBTreeLineIndex(dlPtr->index.linePtr)) {
2858         /*
2859          * The first display line is already past the desired line.
2860          */
2861         return dlPtr;
2862     }
2863
2864     /*
2865      * Find the first display line that covers the desired text line.
2866      */
2867
2868     linePtr = dlPtr->index.linePtr;
2869     while (linePtr != indexPtr->linePtr) {
2870         while (dlPtr->index.linePtr == linePtr) {
2871             dlPtr = dlPtr->nextPtr;
2872             if (dlPtr == NULL) {
2873                 return NULL;
2874             }
2875         }
2876         linePtr = CkBTreeNextLine(linePtr);
2877         if (linePtr == NULL) {
2878             panic("FindDLine reached end of text");
2879         }
2880     }
2881     if (indexPtr->linePtr != dlPtr->index.linePtr) {
2882         return dlPtr;
2883     }
2884
2885     /*
2886      * Now get to the right position within the text line.
2887      */
2888
2889     while (indexPtr->charIndex >= (dlPtr->index.charIndex + dlPtr->count)) {
2890         dlPtr = dlPtr->nextPtr;
2891         if ((dlPtr == NULL) || (dlPtr->index.linePtr != indexPtr->linePtr)) {
2892             break;
2893         }
2894     }
2895     return dlPtr;
2896 }
2897 \f
2898 /*
2899  *----------------------------------------------------------------------
2900  *
2901  * CkTextPixelIndex --
2902  *
2903  *      Given an (x,y) coordinate on the screen, find the location of
2904  *      the character closest to that location.
2905  *
2906  * Results:
2907  *      The index at *indexPtr is modified to refer to the character
2908  *      on the display that is closest to (x,y).
2909  *
2910  * Side effects:
2911  *      None.
2912  *
2913  *----------------------------------------------------------------------
2914  */
2915
2916 void
2917 CkTextPixelIndex(textPtr, x, y, indexPtr)
2918     CkText *textPtr;            /* Widget record for text widget. */
2919     int x, y;                   /* Pixel coordinates of point in widget's
2920                                  * window. */
2921     CkTextIndex *indexPtr;      /* This index gets filled in with the
2922                                  * index of the character nearest to (x,y). */
2923 {
2924     DInfo *dInfoPtr = textPtr->dInfoPtr;
2925     register DLine *dlPtr;
2926     register CkTextDispChunk *chunkPtr;
2927
2928     /*
2929      * Make sure that all of the layout information about what's
2930      * displayed where on the screen is up-to-date.
2931      */
2932
2933     if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
2934         UpdateDisplayInfo(textPtr);
2935     }
2936
2937     /*
2938      * If the coordinates are above the top of the window, then adjust
2939      * them to refer to the upper-right corner of the window.  If they're
2940      * off to one side or the other, then adjust to the closest side.
2941      */
2942
2943     if (y < dInfoPtr->y) {
2944         y = dInfoPtr->y;
2945         x = dInfoPtr->x;
2946     }
2947     if (x >= dInfoPtr->maxX) {
2948         x = dInfoPtr->maxX - 1;
2949     }
2950     if (x < dInfoPtr->x) {
2951         x = dInfoPtr->x;
2952     }
2953
2954     /*
2955      * Find the display line containing the desired y-coordinate.
2956      */
2957
2958     for (dlPtr = dInfoPtr->dLinePtr; y >= (dlPtr->y + dlPtr->height);
2959             dlPtr = dlPtr->nextPtr) {
2960         if (dlPtr->nextPtr == NULL) {
2961             /*
2962              * Y-coordinate is off the bottom of the displayed text.
2963              * Use the last character on the last line.
2964              */
2965
2966             x = dInfoPtr->maxX - 1;
2967             break;
2968         }
2969     }
2970
2971     /*
2972      * Scan through the line's chunks to find the one that contains
2973      * the desired x-coordinate.  Before doing this, translate the
2974      * x-coordinate from the coordinate system of the window to the
2975      * coordinate system of the line (to take account of x-scrolling).
2976      */
2977
2978     *indexPtr = dlPtr->index;
2979     x = x - dInfoPtr->x + dInfoPtr->curOffset;
2980     for (chunkPtr = dlPtr->chunkPtr; x >= (chunkPtr->x + chunkPtr->width);
2981             indexPtr->charIndex += chunkPtr->numChars,
2982             chunkPtr = chunkPtr->nextPtr) {
2983         if (chunkPtr->nextPtr == NULL) {
2984 #if CK_USE_UTF
2985             indexPtr->charIndex += chunkPtr->numChars;
2986             CkTextIndexBackChars(indexPtr, 1, indexPtr);
2987 #else
2988             indexPtr->charIndex += chunkPtr->numChars - 1;
2989 #endif
2990             return;
2991         }
2992     }
2993
2994     /*
2995      * If the chunk has more than one character in it, ask it which
2996      * character is at the desired location.
2997      */
2998
2999     if (chunkPtr->numChars > 1) {
3000         indexPtr->charIndex += (*chunkPtr->measureProc)(chunkPtr, x);
3001     }
3002 }
3003 \f
3004 /*
3005  *----------------------------------------------------------------------
3006  *
3007  * CkTextCharBbox --
3008  *
3009  *      Given an index, find the bounding box of the screen area
3010  *      occupied by that character.
3011  *
3012  * Results:
3013  *      Zero is returned if the character is on the screen.  -1
3014  *      means the character isn't on the screen.  If the return value
3015  *      is 0, then the bounding box of the part of the character that's
3016  *      visible on the screen is returned to *xPtr, *yPtr, *widthPtr,
3017  *      and *heightPtr.
3018  *
3019  * Side effects:
3020  *      None.
3021  *
3022  *----------------------------------------------------------------------
3023  */
3024
3025 int
3026 CkTextCharBbox(textPtr, indexPtr, xPtr, yPtr, widthPtr, heightPtr)
3027     CkText *textPtr;            /* Widget record for text widget. */
3028     CkTextIndex *indexPtr;      /* Index of character whose bounding
3029                                  * box is desired. */
3030     int *xPtr, *yPtr;           /* Filled with character's upper-left
3031                                  * coordinate. */
3032     int *widthPtr, *heightPtr;  /* Filled in with character's dimensions. */
3033 {
3034     DInfo *dInfoPtr = textPtr->dInfoPtr;
3035     DLine *dlPtr;
3036     register CkTextDispChunk *chunkPtr;
3037     int index;
3038
3039     /*
3040      * Make sure that all of the screen layout information is up to date.
3041      */
3042
3043     if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
3044         UpdateDisplayInfo(textPtr);
3045     }
3046
3047     /*
3048      * Find the display line containing the desired index.
3049      */
3050
3051     dlPtr = FindDLine(dInfoPtr->dLinePtr, indexPtr);
3052     if ((dlPtr == NULL) || (CkTextIndexCmp(&dlPtr->index, indexPtr) > 0)) {
3053         return -1;
3054     }
3055
3056     /*
3057      * Find the chunk within the line that contains the desired
3058      * index.
3059      */
3060
3061     index = indexPtr->charIndex - dlPtr->index.charIndex;
3062     for (chunkPtr = dlPtr->chunkPtr; ; chunkPtr = chunkPtr->nextPtr) {
3063         if (chunkPtr == NULL) {
3064             return -1;
3065         }
3066         if (index < chunkPtr->numChars) {
3067             break;
3068         }
3069         index -= chunkPtr->numChars;
3070     }
3071
3072     /*
3073      * Call a chunk-specific procedure to find the horizontal range of
3074      * the character within the chunk, then fill in the vertical range.
3075      * The x-coordinate returned by bboxProc is a coordinate within a
3076      * line, not a coordinate on the screen.  Translate it to reflect
3077      * horizontal scrolling.
3078      */
3079
3080     (*chunkPtr->bboxProc)(chunkPtr, index, dlPtr->y,
3081             dlPtr->height, 0, xPtr, yPtr, widthPtr,
3082             heightPtr);
3083     *xPtr = *xPtr + dInfoPtr->x - dInfoPtr->curOffset;
3084     if ((index == (chunkPtr->numChars-1)) && (chunkPtr->nextPtr == NULL)) {
3085         /*
3086          * Last character in display line.  Give it all the space up to
3087          * the line.
3088          */
3089
3090         if (*xPtr > dInfoPtr->maxX) {
3091             *xPtr = dInfoPtr->maxX;
3092         }
3093         *widthPtr = dInfoPtr->maxX - *xPtr;
3094     }
3095     if ((*xPtr + *widthPtr) <= dInfoPtr->x) {
3096         return -1;
3097     }
3098     if ((*xPtr + *widthPtr) > dInfoPtr->maxX) {
3099         *widthPtr = dInfoPtr->maxX - *xPtr;
3100         if (*widthPtr <= 0) {
3101             return -1;
3102         }
3103     }
3104     if ((*yPtr + *heightPtr) > dInfoPtr->maxY) {
3105         *heightPtr = dInfoPtr->maxY - *yPtr;
3106         if (*heightPtr <= 0) {
3107             return -1;
3108         }
3109     }
3110     return 0;
3111 }
3112 \f
3113 /*
3114  *----------------------------------------------------------------------
3115  *
3116  * CkTextDLineInfo --
3117  *
3118  *      Given an index, return information about the display line
3119  *      containing that character.
3120  *
3121  * Results:
3122  *      Zero is returned if the character is on the screen.  -1
3123  *      means the character isn't on the screen.  If the return value
3124  *      is 0, then information is returned in the variables pointed
3125  *      to by xPtr, yPtr, widthPtr, heightPtr, and basePtr.
3126  *
3127  * Side effects:
3128  *      None.
3129  *
3130  *----------------------------------------------------------------------
3131  */
3132
3133 int
3134 CkTextDLineInfo(textPtr, indexPtr, xPtr, yPtr, widthPtr, heightPtr, basePtr)
3135     CkText *textPtr;            /* Widget record for text widget. */
3136     CkTextIndex *indexPtr;      /* Index of character whose bounding
3137                                  * box is desired. */
3138     int *xPtr, *yPtr;           /* Filled with line's upper-left
3139                                  * coordinate. */
3140     int *widthPtr, *heightPtr;  /* Filled in with line's dimensions. */
3141     int *basePtr;               /* Filled in with the baseline position,
3142                                  * measured as an offset down from *yPtr. */
3143 {
3144     DInfo *dInfoPtr = textPtr->dInfoPtr;
3145     DLine *dlPtr;
3146
3147     /*
3148      * Make sure that all of the screen layout information is up to date.
3149      */
3150
3151     if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
3152         UpdateDisplayInfo(textPtr);
3153     }
3154
3155     /*
3156      * Find the display line containing the desired index.
3157      */
3158
3159     dlPtr = FindDLine(dInfoPtr->dLinePtr, indexPtr);
3160     if ((dlPtr == NULL) || (CkTextIndexCmp(&dlPtr->index, indexPtr) > 0)) {
3161         return -1;
3162     }
3163
3164     *xPtr = dInfoPtr->x - dInfoPtr->curOffset + dlPtr->chunkPtr->x;
3165     *widthPtr = dlPtr->length - dlPtr->chunkPtr->x;
3166     *yPtr = dlPtr->y;
3167     if ((dlPtr->y + dlPtr->height) > dInfoPtr->maxY) {
3168         *heightPtr = dInfoPtr->maxY - dlPtr->y;
3169     } else {
3170         *heightPtr = dlPtr->height;
3171     }
3172     *basePtr = 0;
3173     return 0;
3174 }
3175 \f
3176 /*
3177  *--------------------------------------------------------------
3178  *
3179  * CkTextCharLayoutProc --
3180  *
3181  *      This procedure is the "layoutProc" for character segments.
3182  *
3183  * Results:
3184  *      If there is something to display for the chunk then a
3185  *      non-zero value is returned and the fields of chunkPtr
3186  *      will be filled in (see the declaration of CkTextDispChunk
3187  *      in ckText.h for details).  If zero is returned it means
3188  *      that no characters from this chunk fit in the window.
3189  *      If -1 is returned it means that this segment just doesn't
3190  *      need to be displayed (never happens for text).
3191  *
3192  * Side effects:
3193  *      Memory is allocated to hold additional information about
3194  *      the chunk.
3195  *
3196  *--------------------------------------------------------------
3197  */
3198
3199 int
3200 CkTextCharLayoutProc(textPtr, indexPtr, segPtr, offset, maxX, maxChars,
3201         noCharsYet, wrapMode, chunkPtr)
3202     CkText *textPtr;            /* Text widget being layed out. */
3203     CkTextIndex *indexPtr;      /* Index of first character to lay out
3204                                  * (corresponds to segPtr and offset). */
3205     CkTextSegment *segPtr;      /* Segment being layed out. */
3206     int offset;                 /* Offset within segment of first character
3207                                  * to consider. */
3208     int maxX;                   /* Chunk must not occupy pixels at this
3209                                  * position or higher. */
3210     int maxChars;               /* Chunk must not include more than this
3211                                  * many characters. */
3212     int noCharsYet;             /* Non-zero means no characters have been
3213                                  * assigned to this display line yet. */
3214     Ck_Uid wrapMode;            /* How to handle line wrapping: ckTextCharUid,
3215                                  * ckTextNoneUid, or ckTextWordUid. */
3216     register CkTextDispChunk *chunkPtr;
3217                                 /* Structure to fill in with information
3218                                  * about this chunk.  The x field has already
3219                                  * been set by the caller. */
3220 {
3221     int nextX, charsThatFit, count, dummy;
3222     CharInfo *ciPtr;
3223     char *p;
3224     CkTextSegment *nextPtr;
3225     CkWindow *winPtr = textPtr->winPtr;
3226
3227     /*
3228      * Figure out how many characters will fit in the space we've got.
3229      * Include the next character, even though it won't fit completely,
3230      * if any of the following is true:
3231      *   (a) the chunk contains no characters and the display line contains
3232      *       no characters yet (i.e. the line isn't wide enough to hold
3233      *       even a single character).
3234      *   (b) at least one pixel of the character is visible, we haven't
3235      *       already exceeded the character limit, and the next character
3236      *       is a white space character.
3237      */
3238
3239     p = segPtr->body.chars + offset;
3240     CkMeasureChars(winPtr->mainPtr, p, maxChars, chunkPtr->x,
3241         maxX, 0, CK_IGNORE_TABS, &nextX, &charsThatFit);
3242     if (charsThatFit < maxChars) {
3243         if ((charsThatFit == 0) && noCharsYet) {
3244             charsThatFit = 1;
3245             CkMeasureChars(winPtr->mainPtr, p, 1, chunkPtr->x, INT_MAX, 0,
3246                 CK_IGNORE_TABS, &nextX, &dummy);
3247         }
3248         if (p[charsThatFit] == '\n') {
3249             /*
3250              * A newline character takes up no space, so if the previous
3251              * character fits then so does the newline.
3252              */
3253
3254             charsThatFit++;
3255         }
3256         if (charsThatFit == 0) {
3257             return 0;
3258         }
3259     }
3260
3261     /*
3262      * Fill in the chunk structure and allocate and initialize a
3263      * CharInfo structure.  If the last character is a newline
3264      * then don't bother to display it.
3265      */
3266
3267     chunkPtr->displayProc = CharDisplayProc;
3268     chunkPtr->undisplayProc = CharUndisplayProc;
3269     chunkPtr->measureProc = CharMeasureProc;
3270     chunkPtr->bboxProc = CharBboxProc;
3271     chunkPtr->numChars = charsThatFit;
3272     chunkPtr->minHeight = 1;
3273     chunkPtr->width = nextX - chunkPtr->x;
3274     chunkPtr->breakIndex = -1;
3275     ciPtr = (CharInfo *) ckalloc((unsigned)
3276             (sizeof(CharInfo) - 3 + charsThatFit));
3277     chunkPtr->clientData = (ClientData) ciPtr;
3278     ciPtr->numChars = charsThatFit;
3279     ciPtr->winPtr = textPtr->winPtr;
3280     strncpy(ciPtr->chars, p, (size_t) charsThatFit);
3281     if (p[charsThatFit-1] == '\n') {
3282         ciPtr->numChars--;
3283     }
3284
3285     /*
3286      * Compute a break location.  If we're in word wrap mode, a
3287      * break can occur after any space character, or at the end of
3288      * the chunk if the next segment (ignoring those with zero size)
3289      * is not a character segment.
3290      */
3291
3292     if (wrapMode != ckTextWordUid) {
3293         chunkPtr->breakIndex = chunkPtr->numChars;
3294     } else {
3295         for (count = charsThatFit, p += charsThatFit-1; count > 0;
3296                 count--, p--) {
3297             if (isspace((unsigned char) *p)) {
3298                 chunkPtr->breakIndex = count;
3299                 break;
3300             }
3301         }
3302         if ((charsThatFit+offset) == segPtr->size) {
3303             for (nextPtr = segPtr->nextPtr; nextPtr != NULL;
3304                     nextPtr = nextPtr->nextPtr) {
3305                 if (nextPtr->size != 0) {
3306                     if (nextPtr->typePtr != &ckTextCharType) {
3307                         chunkPtr->breakIndex = chunkPtr->numChars;
3308                     }
3309                     break;
3310                 }
3311             }
3312         }
3313     }
3314     return 1;
3315 }
3316 \f
3317 /*
3318  *--------------------------------------------------------------
3319  *
3320  * CharDisplayProc --
3321  *
3322  *      This procedure is called to display a character chunk on
3323  *      the screen or in an off-screen pixmap.
3324  *
3325  * Results:
3326  *      None.
3327  *
3328  * Side effects:
3329  *      Graphics are drawn.
3330  *
3331  *--------------------------------------------------------------
3332  */
3333
3334 static void
3335 CharDisplayProc(chunkPtr, x, y, height, baseline, window, screenY)
3336     CkTextDispChunk *chunkPtr;          /* Chunk that is to be drawn. */
3337     int x;                              /* X-position in dst at which to
3338                                          * draw this chunk (may differ from
3339                                          * the x-position in the chunk because
3340                                          * of scrolling). */
3341     int y;                              /* Y-position at which to draw this
3342                                          * chunk in dst. */
3343     int height;                         /* Total height of line. */
3344     int baseline;                       /* Offset of baseline from y. */
3345     WINDOW *window;
3346     int screenY;                        /* Y-coordinate in text window that
3347                                          * corresponds to y. */
3348 {
3349     CharInfo *ciPtr = (CharInfo *) chunkPtr->clientData;
3350     Style *stylePtr;
3351     StyleValues *sValuePtr;
3352
3353     if ((x + chunkPtr->width) <= 0) {
3354         /*
3355          * The chunk is off-screen.
3356          */
3357
3358         return;
3359     }
3360
3361     stylePtr = chunkPtr->stylePtr;
3362     sValuePtr = stylePtr->sValuePtr;
3363
3364     /*
3365      * Draw the text for this chunk.
3366      */
3367
3368     if (ciPtr->numChars > 0) {
3369         Ck_SetWindowAttr(ciPtr->winPtr, sValuePtr->fg, sValuePtr->bg,
3370             sValuePtr->attr);
3371         CkDisplayChars(ciPtr->winPtr->mainPtr, window, ciPtr->chars,
3372             ciPtr->numChars, x,
3373             screenY + baseline, x - chunkPtr->x, CK_IGNORE_TABS);
3374     }
3375 }
3376 \f
3377 /*
3378  *--------------------------------------------------------------
3379  *
3380  * CharUndisplayProc --
3381  *
3382  *      This procedure is called when a character chunk is no
3383  *      longer going to be displayed.  It frees up resources
3384  *      that were allocated to display the chunk.
3385  *
3386  * Results:
3387  *      None.
3388  *
3389  * Side effects:
3390  *      Memory and other resources get freed.
3391  *
3392  *--------------------------------------------------------------
3393  */
3394
3395 static void
3396 CharUndisplayProc(textPtr, chunkPtr)
3397     CkText *textPtr;                    /* Overall information about text
3398                                          * widget. */
3399     CkTextDispChunk *chunkPtr;          /* Chunk that is about to be freed. */
3400 {
3401     CharInfo *ciPtr = (CharInfo *) chunkPtr->clientData;
3402
3403     ckfree((char *) ciPtr);
3404 }
3405 \f
3406 /*
3407  *--------------------------------------------------------------
3408  *
3409  * CharMeasureProc --
3410  *
3411  *      This procedure is called to determine which character in
3412  *      a character chunk lies over a given x-coordinate.
3413  *
3414  * Results:
3415  *      The return value is the index *within the chunk* of the
3416  *      character that covers the position given by "x".
3417  *
3418  * Side effects:
3419  *      None.
3420  *
3421  *--------------------------------------------------------------
3422  */
3423
3424 static int
3425 CharMeasureProc(chunkPtr, x)
3426     CkTextDispChunk *chunkPtr;          /* Chunk containing desired coord. */
3427     int x;                              /* X-coordinate, in same coordinate
3428                                          * system as chunkPtr->x. */
3429 {
3430     CharInfo *ciPtr = (CharInfo *) chunkPtr->clientData;
3431     int endX, charX;
3432
3433     CkMeasureChars(ciPtr->winPtr->mainPtr,
3434             ciPtr->chars, chunkPtr->numChars-1, chunkPtr->x,
3435             x, 0, CK_IGNORE_TABS, &endX, &charX);
3436     return charX;
3437 }
3438 \f
3439 /*
3440  *--------------------------------------------------------------
3441  *
3442  * CharBboxProc --
3443  *
3444  *      This procedure is called to compute the bounding box of
3445  *      the area occupied by a single character.
3446  *
3447  * Results:
3448  *      There is no return value.  *xPtr and *yPtr are filled in
3449  *      with the coordinates of the upper left corner of the
3450  *      character, and *widthPtr and *heightPtr are filled in with
3451  *      the dimensions of the character in pixels.  Note:  not all
3452  *      of the returned bbox is necessarily visible on the screen
3453  *      (the rightmost part might be off-screen to the right,
3454  *      and the bottommost part might be off-screen to the bottom).
3455  *
3456  * Side effects:
3457  *      None.
3458  *
3459  *--------------------------------------------------------------
3460  */
3461
3462 static void
3463 CharBboxProc(chunkPtr, index, y, lineHeight, baseline, xPtr, yPtr,
3464         widthPtr, heightPtr)
3465     CkTextDispChunk *chunkPtr;          /* Chunk containing desired char. */
3466     int index;                          /* Index of desired character within
3467                                          * the chunk. */
3468     int y;                              /* Topmost pixel in area allocated
3469                                          * for this line. */
3470     int lineHeight;                     /* Height of line, in pixels. */
3471     int baseline;                       /* Location of line's baseline, in
3472                                          * pixels measured down from y. */
3473     int *xPtr, *yPtr;                   /* Gets filled in with coords of
3474                                          * character's upper-left pixel. 
3475                                          * X-coord is in same coordinate
3476                                          * system as chunkPtr->x. */
3477     int *widthPtr;                      /* Gets filled in with width of
3478                                          * character, in pixels. */
3479     int *heightPtr;                     /* Gets filled in with height of
3480                                          * character, in pixels. */
3481 {
3482     CharInfo *ciPtr = (CharInfo *) chunkPtr->clientData;
3483     CkWindow *winPtr = ciPtr->winPtr;
3484     int maxX, dummy;
3485
3486     maxX = chunkPtr->width + chunkPtr->x;
3487     CkMeasureChars(winPtr->mainPtr,
3488         ciPtr->chars, index, chunkPtr->x, 1000000, 0,
3489         CK_IGNORE_TABS, xPtr, &dummy);
3490     if (index == ciPtr->numChars) {
3491         /*
3492          * This situation only happens if the last character in a line
3493          * is a space character, in which case it absorbs all of the
3494          * extra space in the line (see CkTextCharLayoutProc).
3495          */
3496
3497         *widthPtr = maxX - *xPtr;
3498     } else if ((ciPtr->chars[index] == '\t')
3499             && (index == (ciPtr->numChars-1))) {
3500         /*
3501          * The desired character is a tab character that terminates a
3502          * chunk;  give it all the space left in the chunk.
3503          */
3504
3505         *widthPtr = maxX - *xPtr;
3506     } else {
3507         CkMeasureChars(winPtr->mainPtr,
3508             ciPtr->chars + index, 1, *xPtr, 1000000, 0,
3509             CK_IGNORE_TABS, widthPtr, &dummy);
3510         if (*widthPtr > maxX) {
3511             *widthPtr = maxX - *xPtr;
3512         } else {
3513             *widthPtr -= *xPtr;
3514         }
3515     }
3516     *yPtr = y + baseline;
3517     *heightPtr = 1;
3518 }
3519 \f
3520 /*
3521  *----------------------------------------------------------------------
3522  *
3523  * AdjustForTab --
3524  *
3525  *      This procedure is called to move a series of chunks right
3526  *      in order to align them with a tab stop.
3527  *
3528  * Results:
3529  *      None.
3530  *
3531  * Side effects:
3532  *      The width of chunkPtr gets adjusted so that it absorbs the
3533  *      extra space due to the tab.  The x locations in all the chunks
3534  *      after chunkPtr are adjusted rightward to align with the tab
3535  *      stop given by tabArrayPtr and index.
3536  *
3537  *----------------------------------------------------------------------
3538  */
3539
3540 static void
3541 AdjustForTab(textPtr, tabArrayPtr, index, chunkPtr)
3542     CkText *textPtr;                    /* Information about the text widget as
3543                                          * a whole. */
3544     CkTextTabArray *tabArrayPtr;        /* Information about the tab stops
3545                                          * that apply to this line.  May be
3546                                          * NULL to indicate default tabbing
3547                                          * (every 8 chars). */
3548     int index;                          /* Index of current tab stop. */
3549     CkTextDispChunk *chunkPtr;          /* Chunk whose last character is
3550                                          * the tab;  the following chunks
3551                                          * contain information to be shifted
3552                                          * right. */
3553
3554 {
3555     int x, desired, delta, width, decimal, i, gotDigit;
3556     CkTextDispChunk *chunkPtr2, *decimalChunkPtr;
3557     CkTextTab *tabPtr;
3558     CharInfo *ciPtr = NULL;             /* Initialization needed only to
3559                                          * prevent compiler warnings. */
3560     int tabX, prev, spaceWidth, dummy;
3561     char *p;
3562     CkTextTabAlign alignment;
3563
3564     if (chunkPtr->nextPtr == NULL) {
3565         /*
3566          * Nothing after the actual tab;  just return.
3567          */
3568
3569         return;
3570     }
3571
3572     /*
3573      * If no tab information has been given, do the usual thing:
3574      * round up to the next boundary of 8 average-sized characters.
3575      */
3576
3577     x = chunkPtr->nextPtr->x;
3578     if ((tabArrayPtr == NULL) || (tabArrayPtr->numTabs == 0)) {
3579         /*
3580          * No tab information has been given, so use the default
3581          * interpretation of tabs.
3582          */
3583
3584         CkMeasureChars(textPtr->winPtr->mainPtr,
3585             "\t", 1, x, INT_MAX, 0, 0, &desired, &dummy);
3586         goto update;
3587     }
3588
3589     if (index < tabArrayPtr->numTabs) {
3590         alignment = tabArrayPtr->tabs[index].alignment;
3591         tabX = tabArrayPtr->tabs[index].location;
3592     } else {
3593         /*
3594          * Ran out of tab stops;  compute a tab position by extrapolating
3595          * from the last two tab positions.
3596          */
3597
3598         if (tabArrayPtr->numTabs > 1) {
3599             prev = tabArrayPtr->tabs[tabArrayPtr->numTabs-2].location;
3600         } else {
3601             prev = 0;
3602         }
3603         alignment = tabArrayPtr->tabs[tabArrayPtr->numTabs-1].alignment;
3604         tabX = tabArrayPtr->tabs[tabArrayPtr->numTabs-1].location
3605                 + (index + 1 - tabArrayPtr->numTabs)
3606                 * (tabArrayPtr->tabs[tabArrayPtr->numTabs-1].location - prev);
3607     }
3608
3609     tabPtr = &tabArrayPtr->tabs[index];
3610     if (alignment == LEFT) {
3611         desired = tabX;
3612         goto update;
3613     }
3614
3615     if ((alignment == CENTER) || (alignment == RIGHT)) {
3616         /*
3617          * Compute the width of all the information in the tab group,
3618          * then use it to pick a desired location.
3619          */
3620
3621         width = 0;
3622         for (chunkPtr2 = chunkPtr->nextPtr; chunkPtr2 != NULL;
3623                 chunkPtr2 = chunkPtr2->nextPtr) {
3624             width += chunkPtr2->width;
3625         }
3626         if (alignment == CENTER) {
3627             desired = tabX - width/2;
3628         } else {
3629             desired = tabX - width;
3630         }
3631         goto update;
3632     }
3633
3634     /*
3635      * Must be numeric alignment.  Search through the text to be
3636      * tabbed, looking for the last , or . before the first character
3637      * that isn't a number, comma, period, or sign.
3638      */
3639
3640     decimalChunkPtr = NULL;
3641     decimal = gotDigit = 0;
3642     for (chunkPtr2 = chunkPtr->nextPtr; chunkPtr2 != NULL;
3643             chunkPtr2 = chunkPtr2->nextPtr) {
3644         if (chunkPtr2->displayProc != CharDisplayProc) {
3645             continue;
3646         }
3647         ciPtr = (CharInfo *) chunkPtr2->clientData;
3648         for (p = ciPtr->chars, i = 0; i < ciPtr->numChars; p++, i++) {
3649             if (isdigit((unsigned char) *p)) {
3650                 gotDigit = 1;
3651             } else if ((*p == '.') || (*p == ',')) {
3652                 decimal = p-ciPtr->chars;
3653                 decimalChunkPtr = chunkPtr2;
3654             } else if (gotDigit) {
3655                 if (decimalChunkPtr == NULL) {
3656                     decimal = p-ciPtr->chars;
3657                     decimalChunkPtr = chunkPtr2;
3658                 }
3659                 goto endOfNumber;
3660             }
3661         }
3662     }
3663     endOfNumber:
3664     if (decimalChunkPtr != NULL) {
3665         int curX;
3666
3667         ciPtr = (CharInfo *) decimalChunkPtr->clientData;
3668         CkMeasureChars(ciPtr->winPtr->mainPtr,
3669             ciPtr->chars, decimal, decimalChunkPtr->x, 1000000, 0,
3670             CK_IGNORE_TABS, &curX, &dummy);
3671         desired = tabX - (curX - x);
3672         goto update;
3673     } else {
3674         /*
3675          * There wasn't a decimal point.  Right justify the text.
3676          */
3677     
3678         width = 0;
3679         for (chunkPtr2 = chunkPtr->nextPtr; chunkPtr2 != NULL;
3680                 chunkPtr2 = chunkPtr2->nextPtr) {
3681             width += chunkPtr2->width;
3682         }
3683         desired = tabX - width;
3684     }
3685
3686     /*
3687      * Shift all of the chunks to the right so that the left edge is
3688      * at the desired location, then expand the chunk containing the
3689      * tab.  Be sure that the tab occupies at least the width of a
3690      * space character.
3691      */
3692
3693     update:
3694     delta = desired - x;
3695     CkMeasureChars(textPtr->winPtr->mainPtr, " ", 1, 0, INT_MAX,
3696          0, 0, &spaceWidth, &dummy);
3697     if (delta < spaceWidth) {
3698         delta = spaceWidth;
3699     }
3700     for (chunkPtr2 = chunkPtr->nextPtr; chunkPtr2 != NULL;
3701             chunkPtr2 = chunkPtr2->nextPtr) {
3702         chunkPtr2->x += delta;
3703     }
3704     chunkPtr->width += delta;
3705 }
3706 \f
3707 /*
3708  *----------------------------------------------------------------------
3709  *
3710  * SizeOfTab --
3711  *
3712  *      This returns an estimate of the amount of white space that will
3713  *      be consumed by a tab.
3714  *
3715  * Results:
3716  *      The return value is the minimum number of pixels that will
3717  *      be occupied by the index'th tab of tabArrayPtr, assuming that
3718  *      the current position on the line is x and the end of the
3719  *      line is maxX.  For numeric tabs, this is a conservative
3720  *      estimate.  The return value is always >= 0.
3721  *
3722  * Side effects:
3723  *      None.
3724  *
3725  *----------------------------------------------------------------------
3726  */
3727
3728 static int
3729 SizeOfTab(textPtr, tabArrayPtr, index, x, maxX)
3730     CkText *textPtr;                    /* Information about the text widget as
3731                                          * a whole. */
3732     CkTextTabArray *tabArrayPtr;        /* Information about the tab stops
3733                                          * that apply to this line.  NULL
3734                                          * means use default tabbing (every
3735                                          * 8 chars.) */
3736     int index;                          /* Index of current tab stop. */
3737     int x;                              /* Current x-location in line. Only
3738                                          * used if tabArrayPtr == NULL. */
3739     int maxX;                           /* X-location of pixel just past the
3740                                          * right edge of the line. */
3741 {
3742     int tabX, prev, result, spaceWidth, dummy;
3743     CkTextTabAlign alignment;
3744     CkWindow *winPtr = textPtr->winPtr;
3745
3746     if ((tabArrayPtr == NULL) || (tabArrayPtr->numTabs == 0)) {
3747         CkMeasureChars(winPtr->mainPtr, "\t", 1, x, INT_MAX,
3748             0, 0, &tabX, &dummy);
3749         return tabX - x;
3750     }
3751     if (index < tabArrayPtr->numTabs) {
3752         tabX = tabArrayPtr->tabs[index].location;
3753         alignment = tabArrayPtr->tabs[index].alignment;
3754     } else {
3755         /*
3756          * Ran out of tab stops;  compute a tab position by extrapolating
3757          * from the last two tab positions.
3758          */
3759
3760         if (tabArrayPtr->numTabs > 1) {
3761             prev = tabArrayPtr->tabs[tabArrayPtr->numTabs-2].location;
3762         } else {
3763             prev = 0;
3764         }
3765         tabX = tabArrayPtr->tabs[tabArrayPtr->numTabs-1].location
3766                 + (index + 1 - tabArrayPtr->numTabs)
3767                 * (tabArrayPtr->tabs[tabArrayPtr->numTabs-1].location - prev);
3768         alignment = tabArrayPtr->tabs[tabArrayPtr->numTabs-1].alignment;
3769     }
3770     if (alignment == CENTER) {
3771         /*
3772          * Be very careful in the arithmetic below, because maxX may
3773          * be the largest positive number:  watch out for integer
3774          * overflow.
3775          */
3776
3777         if ((maxX-tabX) < (tabX - x)) {
3778             result = (maxX - x) - 2*(maxX - tabX);
3779         } else {
3780             result = 0;
3781         }
3782         goto done;
3783     }
3784     if (alignment == RIGHT) {
3785         result = 0;
3786         goto done;
3787     }
3788
3789     /*
3790      * Note: this treats NUMERIC alignment the same as LEFT
3791      * alignment, which is somewhat conservative.  However, it's
3792      * pretty tricky at this point to figure out exactly where
3793      * the damn decimal point will be.
3794      */
3795
3796     if (tabX > x) {
3797         result = tabX - x;
3798     } else {
3799         result = 0;
3800     }
3801
3802     done:
3803     CkMeasureChars(winPtr->mainPtr, " ", 1, 0, INT_MAX,
3804         0, 0, &spaceWidth, &dummy);
3805     if (result < spaceWidth) {
3806         result = spaceWidth;
3807     }
3808     return result;
3809 }