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

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

Parent Directory Parent Directory | Revision Log Revision Log


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

Properties

Name Value
svn:eol-style native
svn:keywords Header

dashley@gmail.com
ViewVC Help
Powered by ViewVC 1.1.25