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