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