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