]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * tkTextDisp.c -- | |
3 | * | |
4 | * This module provides facilities to display text widgets. It is | |
5 | * the only place where information is kept about the screen layout | |
6 | * of text widgets. | |
7 | * | |
8 | * Copyright 1992 Regents of the University of California. | |
9 | * Permission to use, copy, modify, and distribute this | |
10 | * software and its documentation for any purpose and without | |
11 | * fee is hereby granted, provided that the above copyright | |
12 | * notice appear in all copies. The University of California | |
13 | * makes no representations about the suitability of this | |
14 | * software for any purpose. It is provided "as is" without | |
15 | * express or implied warranty. | |
16 | */ | |
17 | ||
18 | #ifndef lint | |
19 | static char rcsid[] = "$Header: /user6/ouster/wish/RCS/tkTextDisp.c,v 1.20 92/08/24 09:24:18 ouster Exp $ SPRITE (Berkeley)"; | |
20 | #endif | |
21 | ||
22 | #include "tkconfig.h" | |
23 | #include "tkint.h" | |
24 | #include "tktext.h" | |
25 | ||
26 | #include <assert.h> | |
27 | ||
28 | /* | |
29 | * The following structure describes how to display a range of characters. | |
30 | * The information is generated by scanning all of the tags associated | |
31 | * with the characters and combining that with default information for | |
32 | * the overall widget. These structures form the hash keys for | |
33 | * dInfoPtr->styleTable. | |
34 | */ | |
35 | ||
36 | typedef struct StyleValues { | |
37 | Tk_3DBorder border; /* Used for drawing background under text. | |
38 | * NULL means use widget background. */ | |
39 | int borderWidth; /* Width of 3-D border for background. */ | |
40 | int relief; /* 3-D relief for background. */ | |
41 | Pixmap bgStipple; /* Stipple bitmap for background. None | |
42 | * means draw solid. */ | |
43 | XColor *fgColor; /* Foreground color for text. */ | |
44 | XFontStruct *fontPtr; /* Font for displaying text. */ | |
45 | Pixmap fgStipple; /* Stipple bitmap for text and other | |
46 | * foreground stuff. None means draw | |
47 | * solid.*/ | |
48 | int underline; /* Non-zero means draw underline underneath | |
49 | * text. */ | |
50 | } StyleValues; | |
51 | ||
52 | /* | |
53 | * The following structure extends the StyleValues structure above with | |
54 | * graphics contexts used to actually draw the characters. The entries | |
55 | * in dInfoPtr->styleTable point to structures of this type. | |
56 | */ | |
57 | ||
58 | typedef struct Style { | |
59 | int refCount; /* Number of times this structure is | |
60 | * referenced in Chunks. */ | |
61 | GC bgGC; /* Graphics context for background. None | |
62 | * unless background is stippled. */ | |
63 | GC fgGC; /* Graphics context for foreground. */ | |
64 | StyleValues *sValuePtr; /* Raw information from which GCs were | |
65 | * derived. */ | |
66 | Tcl_HashEntry *hPtr; /* Pointer to entry in styleTable. Used | |
67 | * to delete entry. */ | |
68 | } Style; | |
69 | ||
70 | /* | |
71 | * The following structure describes a range of characters, all on the | |
72 | * same line of the display (which also means the same line of the text | |
73 | * widget) and all having the same display attributes. | |
74 | */ | |
75 | ||
76 | typedef struct Chunk { | |
77 | char *text; /* Characters to display. */ | |
78 | int numChars; /* Number of characters to display. */ | |
79 | Style *stylePtr; /* Style information used to display | |
80 | * characters. */ | |
81 | int x; /* X-coordinate of pixel at which to display | |
82 | * the characters. */ | |
83 | struct Chunk *nextPtr; /* Next in list of all chunks displayed on the | |
84 | * same display line. */ | |
85 | } Chunk; | |
86 | ||
87 | /* | |
88 | * The following structure describes one line of the display, which may | |
89 | * be either part or all of one line of the text. | |
90 | */ | |
91 | ||
92 | typedef struct DLine { | |
93 | TkTextLine *linePtr; /* Pointer to structure in B-tree that | |
94 | * contains characters displayed in this | |
95 | * line. */ | |
96 | int y; /* Y-position at which line is supposed to | |
97 | * be drawn (topmost pixel of rectangular | |
98 | * area occupied by line). */ | |
99 | int oldY; /* Y-position at which line currently | |
100 | * appears on display. -1 means line isn't | |
101 | * currently visible on display. This is | |
102 | * used to move lines by scrolling rather | |
103 | * than re-drawing. */ | |
104 | int height; /* Height of line, in pixels. */ | |
105 | int baseline; /* Offset of text baseline from y. */ | |
106 | Chunk *chunkPtr; /* Pointer to first chunk in list of all | |
107 | * of those that are displayed on this | |
108 | * line of the screen. */ | |
109 | struct DLine *nextPtr; /* Next in list of all display lines for | |
110 | * this window. The list is sorted in | |
111 | * order from top to bottom. Note: the | |
112 | * next DLine doesn't always correspond | |
113 | * to the next line of text: (a) can have | |
114 | * multiple DLines for one text line, and | |
115 | * (b) can have gaps where DLine's have been | |
116 | * deleted because they're out of date. */ | |
117 | } DLine; | |
118 | ||
119 | /* | |
120 | * Overall display information for a text widget: | |
121 | */ | |
122 | ||
123 | typedef struct DInfo { | |
124 | Tcl_HashTable styleTable; /* Hash table that maps from StyleValues to | |
125 | * Styles for this widget. */ | |
126 | DLine *dLinePtr; /* First in list of all display lines for | |
127 | * this widget, in order from top to bottom. */ | |
128 | GC copyGC; /* Graphics context for copying from off- | |
129 | * screen pixmaps onto screen. */ | |
130 | GC scrollGC; /* Graphics context for copying from one place | |
131 | * in the window to another (scrolling): | |
132 | * differs from copyGC in that we need to get | |
133 | * GraphicsExpose events. */ | |
134 | int x; /* First x-coordinate that may be used for | |
135 | * actually displaying line information. | |
136 | * Leaves space for border, etc. */ | |
137 | int y; /* First y-coordinate that may be used for | |
138 | * actually displaying line information. | |
139 | * Leaves space for border, etc. */ | |
140 | int maxX; /* First x-coordinate to right of available | |
141 | * space for displaying lines. */ | |
142 | int maxY; /* First y-coordinate to bottom of available | |
143 | * space for displaying lines. */ | |
144 | int topOfEof; /* Top-most pixel (lowest y-value) that has | |
145 | * been drawn in the appropriate fashion for | |
146 | * the portion of the window after the last | |
147 | * line of the text. This field is used to | |
148 | * figure out when to redraw part or all of | |
149 | * the eof field. */ | |
150 | int flags; /* Various flag values: see below for | |
151 | * definitions. */ | |
152 | } DInfo; | |
153 | ||
154 | /* | |
155 | * Flag values for DInfo structures: | |
156 | * | |
157 | * DINFO_OUT_OF_DATE: Non-zero means that the DLine structures | |
158 | * for this window are partially or completely | |
159 | * out of date and need to be recomputed. | |
160 | * REDRAW_PENDING: Means that a when-idle handler has been | |
161 | * scheduled to update the display. | |
162 | * REDRAW_BORDERS: Means window border or pad area has | |
163 | * potentially been damaged and must be redrawn. | |
164 | * REPICK_NEEDED: 1 means that the widget has been modified | |
165 | * in a way that could change the current | |
166 | * character (a different character might be | |
167 | * under the mouse cursor now). Need to | |
168 | * recompute the current character before | |
169 | * the next redisplay. | |
170 | */ | |
171 | ||
172 | #define DINFO_OUT_OF_DATE 1 | |
173 | #define REDRAW_PENDING 2 | |
174 | #define REDRAW_BORDERS 4 | |
175 | #define REPICK_NEEDED 8 | |
176 | ||
177 | /* | |
178 | * Structures of the type defined below are used to keep track of | |
179 | * tags while scanning through the text to create DLine structures. | |
180 | */ | |
181 | ||
182 | typedef struct TagInfo { | |
183 | int numTags; /* Number of tags currently active (the first | |
184 | * entries at *tagPtr). */ | |
185 | int arraySize; /* Total number of entries at *tagPtr. We | |
186 | * over-allocate the array to avoid continual | |
187 | * reallocations. */ | |
188 | TkTextTag **tagPtrs; /* Pointer to array of pointers to active tags. | |
189 | * Array has space for arraySize tags, and | |
190 | * the first numTags are slots identify the | |
191 | * active tags. Malloc'ed (but may be NULL). */ | |
192 | TkTextSearch search; /* Used to scan for tag transitions. Current | |
193 | * state identifies next tag transition. */ | |
194 | } TagInfo; | |
195 | ||
196 | /* | |
197 | * The following counters keep statistics about redisplay that can be | |
198 | * checked to see how clever this code is at reducing redisplays. | |
199 | */ | |
200 | ||
201 | static int numRedisplays; /* Number of calls to DisplayText. */ | |
202 | static int linesRedrawn; /* Number of calls to DisplayDLine. */ | |
203 | static int numCopies; /* Number of calls to XCopyArea to copy part | |
204 | * of the screen. */ | |
205 | static int damagedCopies; /* Number of times that XCopyAreas didn't | |
206 | * completely work because some of the source | |
207 | * information was damaged. */ | |
208 | static int TextUpdateTime = 100; // Added by Don. | |
209 | ||
210 | /* | |
211 | * Forward declarations for procedures defined later in this file: | |
212 | */ | |
213 | ||
214 | static void ComputeStyleValues _ANSI_ARGS_((TkText *textPtr, | |
215 | int numTags, TkTextTag **tagPtr, | |
216 | StyleValues *sValuePtr)); | |
217 | static void DisplayDLine _ANSI_ARGS_((TkText *textPtr, | |
218 | DLine *dlPtr, Pixmap pixmap)); | |
219 | static void DisplayText _ANSI_ARGS_((ClientData clientData)); | |
220 | static DLine * FindDLine _ANSI_ARGS_((DLine *dlPtr, int line)); | |
221 | static void FreeDLines _ANSI_ARGS_((TkText *textPtr, | |
222 | DLine *firstPtr, DLine *lastPtr, int unlink)); | |
223 | static void FreeStyle _ANSI_ARGS_((Style *stylePtr)); | |
224 | static Style * GetStyle _ANSI_ARGS_((TkText *textPtr, | |
225 | StyleValues *sValuePtr)); | |
226 | static DLine * LayoutLine _ANSI_ARGS_((TkText *textPtr, int line, | |
227 | TkTextLine *linePtr, TagInfo *tInfoPtr)); | |
228 | static void ToggleTag _ANSI_ARGS_((TagInfo *tInfoPtr, | |
229 | TkTextTag *tagPtr)); | |
230 | static void UpdateDisplayInfo _ANSI_ARGS_((TkText *textPtr)); | |
231 | \f | |
232 | /* | |
233 | *---------------------------------------------------------------------- | |
234 | * | |
235 | * TkTextCreateDInfo -- | |
236 | * | |
237 | * This procedure is called when a new text widget is created. | |
238 | * Its job is to set up display-related information for the widget. | |
239 | * | |
240 | * Results: | |
241 | * None. | |
242 | * | |
243 | * Side effects: | |
244 | * A DInfo data structure is allocated and initialized and attached | |
245 | * to textPtr. | |
246 | * | |
247 | *---------------------------------------------------------------------- | |
248 | */ | |
249 | ||
250 | void | |
251 | TkTextCreateDInfo(textPtr) | |
252 | TkText *textPtr; /* Overall information for text widget. */ | |
253 | { | |
254 | register DInfo *dInfoPtr; | |
255 | XGCValues gcValues; | |
256 | ||
257 | dInfoPtr = (DInfo *) ckalloc(sizeof(DInfo)); | |
258 | Tcl_InitHashTable(&dInfoPtr->styleTable, sizeof(StyleValues)/sizeof(int)); | |
259 | dInfoPtr->dLinePtr = NULL; | |
260 | gcValues.graphics_exposures = False; | |
261 | dInfoPtr->copyGC = Tk_GetGC(textPtr->tkwin, GCGraphicsExposures, &gcValues); | |
262 | gcValues.graphics_exposures = True; | |
263 | dInfoPtr->scrollGC = Tk_GetGC(textPtr->tkwin, GCGraphicsExposures, | |
264 | &gcValues); | |
265 | dInfoPtr->topOfEof = 0; | |
266 | dInfoPtr->flags = DINFO_OUT_OF_DATE; | |
267 | textPtr->dInfoPtr = dInfoPtr; | |
268 | } | |
269 | \f | |
270 | /* | |
271 | *---------------------------------------------------------------------- | |
272 | * | |
273 | * TkTextFreeDInfo -- | |
274 | * | |
275 | * This procedure is called to free up all of the private display | |
276 | * information kept by this file for a text widget. | |
277 | * | |
278 | * Results: | |
279 | * None. | |
280 | * | |
281 | * Side effects: | |
282 | * Lots of resources get freed. | |
283 | * | |
284 | *---------------------------------------------------------------------- | |
285 | */ | |
286 | ||
287 | void | |
288 | TkTextFreeDInfo(textPtr) | |
289 | TkText *textPtr; /* Overall information for text widget. */ | |
290 | { | |
291 | register DInfo *dInfoPtr = textPtr->dInfoPtr; | |
292 | ||
293 | /* | |
294 | * Be careful to free up styleTable *after* freeing up all the | |
295 | * DLines, so that the hash table is still intact to free up the | |
296 | * style-related information from the lines. Once the lines are | |
297 | * all free then styleTable will be empty. | |
298 | */ | |
299 | ||
300 | FreeDLines(textPtr, dInfoPtr->dLinePtr, (DLine *) NULL, 1); | |
301 | Tcl_DeleteHashTable(&dInfoPtr->styleTable); | |
302 | Tk_FreeGC(dInfoPtr->copyGC); | |
303 | Tk_FreeGC(dInfoPtr->scrollGC); | |
304 | if (dInfoPtr->flags & REDRAW_PENDING) { | |
305 | // Tk_CancelIdleCall(DisplayText, (ClientData) textPtr); | |
306 | assert(textPtr->updateTimerToken != NULL); | |
307 | if (textPtr->updateTimerToken != NULL) { | |
308 | Tk_DeleteTimerHandler(textPtr->updateTimerToken); | |
309 | textPtr->updateTimerToken = NULL; | |
310 | } | |
311 | } | |
312 | ckfree((char *) dInfoPtr); | |
313 | } | |
314 | \f | |
315 | /* | |
316 | *---------------------------------------------------------------------- | |
317 | * | |
318 | * GetStyle -- | |
319 | * | |
320 | * This procedure creates graphics contexts needed to display | |
321 | * text in a particular style, determined by "sValuePtr". It | |
322 | * attempts to share style information as much as possible. | |
323 | * | |
324 | * Results: | |
325 | * The return value is a pointer to a Style structure that | |
326 | * corresponds to *sValuePtr. | |
327 | * | |
328 | * Side effects: | |
329 | * A new entry may be created in the style table for the widget. | |
330 | * | |
331 | *---------------------------------------------------------------------- | |
332 | */ | |
333 | ||
334 | static Style * | |
335 | GetStyle(textPtr, sValuePtr) | |
336 | TkText *textPtr; /* Overall information about text widget. */ | |
337 | StyleValues *sValuePtr; /* Information about desired style. */ | |
338 | { | |
339 | Style *stylePtr; | |
340 | Tcl_HashEntry *hPtr; | |
341 | int new; | |
342 | XGCValues gcValues; | |
343 | unsigned long mask; | |
344 | ||
345 | /* | |
346 | * Use an existing style if there's one around that matches. | |
347 | */ | |
348 | ||
349 | hPtr = Tcl_CreateHashEntry(&textPtr->dInfoPtr->styleTable, | |
350 | (char *) sValuePtr, &new); | |
351 | if (!new) { | |
352 | stylePtr = (Style *) Tcl_GetHashValue(hPtr); | |
353 | stylePtr->refCount++; | |
354 | return stylePtr; | |
355 | } | |
356 | ||
357 | /* | |
358 | * No existing style matched. Make a new one. | |
359 | */ | |
360 | ||
361 | stylePtr = (Style *) ckalloc(sizeof(Style)); | |
362 | stylePtr->refCount = 1; | |
363 | if ((sValuePtr->border != NULL) && (sValuePtr->bgStipple != None)) { | |
364 | gcValues.foreground = Tk_3DBorderColor(sValuePtr->border)->pixel; | |
365 | gcValues.stipple = sValuePtr->bgStipple; | |
366 | gcValues.fill_style = FillStippled; | |
367 | stylePtr->bgGC = Tk_GetGC(textPtr->tkwin, | |
368 | GCForeground|GCStipple|GCFillStyle, &gcValues); | |
369 | } else { | |
370 | stylePtr->bgGC = None; | |
371 | } | |
372 | mask = GCForeground|GCFont; | |
373 | gcValues.foreground = sValuePtr->fgColor->pixel; | |
374 | gcValues.font = sValuePtr->fontPtr->fid; | |
375 | if (sValuePtr->fgStipple != None) { | |
376 | gcValues.stipple = sValuePtr->fgStipple; | |
377 | gcValues.fill_style = FillStippled; | |
378 | mask |= GCStipple|GCFillStyle; | |
379 | } | |
380 | stylePtr->fgGC = Tk_GetGC(textPtr->tkwin, mask, &gcValues); | |
381 | stylePtr->sValuePtr = (StyleValues *) | |
382 | Tcl_GetHashKey(&textPtr->dInfoPtr->styleTable, hPtr); | |
383 | stylePtr->hPtr = hPtr; | |
384 | Tcl_SetHashValue(hPtr, stylePtr); | |
385 | return stylePtr; | |
386 | } | |
387 | \f | |
388 | /* | |
389 | *---------------------------------------------------------------------- | |
390 | * | |
391 | * FreeStyle -- | |
392 | * | |
393 | * This procedure is called when a Style structure is no longer | |
394 | * needed. It decrements the reference count and frees up the | |
395 | * space for the style structure if the reference count is 0. | |
396 | * | |
397 | * Results: | |
398 | * None. | |
399 | * | |
400 | * Side effects: | |
401 | * The storage and other resources associated with the style | |
402 | * are freed up if no-one's still using it. | |
403 | * | |
404 | *---------------------------------------------------------------------- | |
405 | */ | |
406 | ||
407 | static void | |
408 | FreeStyle(stylePtr) | |
409 | register Style *stylePtr; /* Information about style to be freed. */ | |
410 | ||
411 | { | |
412 | stylePtr->refCount--; | |
413 | if (stylePtr->refCount == 0) { | |
414 | if (stylePtr->bgGC != None) { | |
415 | Tk_FreeGC(stylePtr->bgGC); | |
416 | } | |
417 | Tk_FreeGC(stylePtr->fgGC); | |
418 | Tcl_DeleteHashEntry(stylePtr->hPtr); | |
419 | ckfree((char *) stylePtr); | |
420 | } | |
421 | } | |
422 | \f | |
423 | /* | |
424 | *---------------------------------------------------------------------- | |
425 | * | |
426 | * ComputeStyleValues -- | |
427 | * | |
428 | * Given a list of tags that apply at a particular point, compute | |
429 | * the StyleValues that correspond to that set of tags. | |
430 | * | |
431 | * Results: | |
432 | * All of the fields of *sValuePtr get filled in to hold the | |
433 | * appropriate display information for the given set of tags | |
434 | * in the given widget. | |
435 | * | |
436 | * Side effects: | |
437 | * None. | |
438 | * | |
439 | *---------------------------------------------------------------------- | |
440 | */ | |
441 | ||
442 | static void | |
443 | ComputeStyleValues(textPtr, numTags, tagPtrPtr, sValuePtr) | |
444 | TkText *textPtr; /* Overall information for widget. */ | |
445 | int numTags; /* Number of tags at *tagPtr. */ | |
446 | register TkTextTag **tagPtrPtr; /* Pointer to array of tag pointers. */ | |
447 | register StyleValues *sValuePtr; /* Pointer to structure to fill in. */ | |
448 | { | |
449 | register TkTextTag *tagPtr; | |
450 | ||
451 | /* | |
452 | * The variables below keep track of the highest-priority specification | |
453 | * that has occurred for each of the various fields of the StyleValues. | |
454 | */ | |
455 | ||
456 | int borderPrio, bgStipplePrio; | |
457 | int fgPrio, fontPrio, fgStipplePrio; | |
458 | ||
459 | borderPrio = bgStipplePrio = -1; | |
460 | fgPrio = fontPrio = fgStipplePrio = -1; | |
461 | memset((VOID *) sValuePtr, 0, sizeof(StyleValues)); | |
462 | sValuePtr->fgColor = textPtr->fgColor; | |
463 | sValuePtr->fontPtr = textPtr->fontPtr; | |
464 | ||
465 | /* | |
466 | * Scan through all of the tags, updating the StyleValues to hold | |
467 | * the highest-priority information. | |
468 | */ | |
469 | ||
470 | for ( ; numTags > 0; tagPtrPtr++, numTags--) { | |
471 | tagPtr = *tagPtrPtr; | |
472 | if ((tagPtr->border != NULL) && (tagPtr->priority > borderPrio)) { | |
473 | sValuePtr->border = tagPtr->border; | |
474 | sValuePtr->borderWidth = tagPtr->borderWidth; | |
475 | sValuePtr->relief = tagPtr->relief; | |
476 | borderPrio = tagPtr->priority; | |
477 | } | |
478 | if ((tagPtr->bgStipple != None) | |
479 | && (tagPtr->priority > bgStipplePrio)) { | |
480 | sValuePtr->bgStipple = tagPtr->bgStipple; | |
481 | bgStipplePrio = tagPtr->priority; | |
482 | } | |
483 | if ((tagPtr->fgColor != None) && (tagPtr->priority > fgPrio)) { | |
484 | sValuePtr->fgColor = tagPtr->fgColor; | |
485 | fgPrio = tagPtr->priority; | |
486 | } | |
487 | if ((tagPtr->fontPtr != None) && (tagPtr->priority > fontPrio)) { | |
488 | sValuePtr->fontPtr = tagPtr->fontPtr; | |
489 | fontPrio = tagPtr->priority; | |
490 | } | |
491 | if ((tagPtr->fgStipple != None) | |
492 | && (tagPtr->priority > fgStipplePrio)) { | |
493 | sValuePtr->fgStipple = tagPtr->fgStipple; | |
494 | fgStipplePrio = tagPtr->priority; | |
495 | } | |
496 | if (tagPtr->underline) { | |
497 | sValuePtr->underline = 1; | |
498 | } | |
499 | } | |
500 | } | |
501 | \f | |
502 | /* | |
503 | *---------------------------------------------------------------------- | |
504 | * | |
505 | * LayoutLine -- | |
506 | * | |
507 | * This procedure generates a linked list of one or more DLine | |
508 | * structures, which describe how to display everything in one | |
509 | * line of the text. | |
510 | * | |
511 | * Results: | |
512 | * The return value is a pointer to one or more DLine structures | |
513 | * linked into a linked list. The structures are completely filled | |
514 | * in except for the y field, which the caller must supply. Also, | |
515 | * the information at *tInfoPtr gets updated to refer to the state | |
516 | * just after the last character of the line. | |
517 | * | |
518 | * Side effects: | |
519 | * None. | |
520 | * | |
521 | *---------------------------------------------------------------------- | |
522 | */ | |
523 | ||
524 | static DLine * | |
525 | LayoutLine(textPtr, line, linePtr, tInfoPtr) | |
526 | TkText *textPtr; /* Overall information about text widget. */ | |
527 | int line; /* Index of line to layout. */ | |
528 | TkTextLine *linePtr; /* Line to layout (corresponds to line). */ | |
529 | TagInfo *tInfoPtr; /* Information to help keep track of tags. | |
530 | * Caller must have initialized to correspond | |
531 | * to state just before start of line. */ | |
532 | { | |
533 | DLine *firstLinePtr; | |
534 | DLine *lastLinePtr = NULL; /* Initializations needed only to stop */ | |
535 | Chunk *lastChunkPtr = NULL; /* compiler warnings. */ | |
536 | register DLine *dlPtr; | |
537 | register Chunk *chunkPtr; | |
538 | StyleValues styleValues; | |
539 | int ch, charsThatFit, ascent, descent, x, maxX; | |
540 | ||
541 | firstLinePtr = NULL; | |
542 | ||
543 | /* | |
544 | * Each iteration of the loop below creates one DLine structure. | |
545 | */ | |
546 | ||
547 | ch = 0; | |
548 | while (1) { | |
549 | ||
550 | /* | |
551 | * Create and initialize a new DLine structure. | |
552 | */ | |
553 | ||
554 | dlPtr = (DLine *) ckalloc(sizeof(DLine)); | |
555 | dlPtr->linePtr = linePtr; | |
556 | dlPtr->y = 0; | |
557 | dlPtr->oldY = -1; | |
558 | dlPtr->chunkPtr = NULL; | |
559 | dlPtr->nextPtr = NULL; | |
560 | if (firstLinePtr == NULL) { | |
561 | firstLinePtr = dlPtr; | |
562 | } else { | |
563 | lastLinePtr->nextPtr = dlPtr; | |
564 | } | |
565 | lastLinePtr = dlPtr; | |
566 | ||
567 | /* | |
568 | * Each iteration of the loop below creates one Chunk for the | |
569 | * new display line. | |
570 | */ | |
571 | ||
572 | x = textPtr->dInfoPtr->x; | |
573 | maxX = textPtr->dInfoPtr->maxX; | |
574 | ascent = descent = 0; | |
575 | while (x < maxX) { | |
576 | chunkPtr = (Chunk *) ckalloc(sizeof(Chunk)); | |
577 | chunkPtr->numChars = linePtr->numBytes - ch; | |
578 | chunkPtr->text = linePtr->bytes + ch; | |
579 | chunkPtr->x = x; | |
580 | chunkPtr->nextPtr = NULL; | |
581 | if (dlPtr->chunkPtr == NULL) { | |
582 | dlPtr->chunkPtr = chunkPtr; | |
583 | } else { | |
584 | lastChunkPtr->nextPtr = chunkPtr; | |
585 | } | |
586 | lastChunkPtr = chunkPtr; | |
587 | ||
588 | /* | |
589 | * Update the tag array to include any tag transitions up | |
590 | * through the current position, then find the next position | |
591 | * with a transition on a tag that impacts the way things are | |
592 | * displayed. | |
593 | */ | |
594 | ||
595 | while (1) { | |
596 | int affectsDisplay; | |
597 | TkTextTag *tagPtr; | |
598 | ||
599 | if ((tInfoPtr->search.linePtr == NULL) | |
600 | || (tInfoPtr->search.line1 > line)) { | |
601 | break; | |
602 | } | |
603 | tagPtr = tInfoPtr->search.tagPtr; | |
604 | affectsDisplay = TK_TAG_AFFECTS_DISPLAY(tagPtr); | |
605 | if ((tInfoPtr->search.line1 < line) | |
606 | || (tInfoPtr->search.ch1 <= ch)) { | |
607 | if (affectsDisplay) { | |
608 | ToggleTag(tInfoPtr, tagPtr); | |
609 | } | |
610 | } else { | |
611 | if (affectsDisplay) { | |
612 | chunkPtr->numChars = tInfoPtr->search.ch1 - ch; | |
613 | break; | |
614 | } | |
615 | } | |
616 | (void) TkBTreeNextTag(&tInfoPtr->search); | |
617 | } | |
618 | ||
619 | /* | |
620 | * Create style information for this chunk. | |
621 | */ | |
622 | ||
623 | ComputeStyleValues(textPtr, tInfoPtr->numTags, tInfoPtr->tagPtrs, | |
624 | &styleValues); | |
625 | chunkPtr->stylePtr = GetStyle(textPtr, &styleValues); | |
626 | ||
627 | /* | |
628 | * See how many characters will fit on the line. If they don't | |
629 | * all fit, then a number of compensations may have to be made. | |
630 | * | |
631 | * 1. Make sure that at least one character is displayed on | |
632 | * each line. | |
633 | * 2. In wrap mode "none", allow a partial character to be | |
634 | * displayed at the end of an incomplete line. | |
635 | * 3. In wrap mode "word", search back to find the last space | |
636 | * character, and terminate the line just after that space | |
637 | * character. This involves a couple of extra complexities: | |
638 | * - the last space may be several chunks back; in this | |
639 | * case, delete all the chunks that are after the | |
640 | * space. | |
641 | * - if no words fit at all, then use character-wrap for | |
642 | * this DLine. | |
643 | * - have to reinitialize the tag search information, since | |
644 | * we may back up over tag toggles (they'll need to be | |
645 | * reconsidered on the next DLine). | |
646 | */ | |
647 | ||
648 | charsThatFit = TkMeasureChars(styleValues.fontPtr, | |
649 | chunkPtr->text, chunkPtr->numChars, chunkPtr->x, | |
650 | maxX, 0, &x); | |
651 | if ((charsThatFit < chunkPtr->numChars) || (x >= maxX)) { | |
652 | x = maxX; | |
653 | chunkPtr->numChars = charsThatFit; | |
654 | ch += charsThatFit; | |
655 | if (ch < (linePtr->numBytes - 1)) { | |
656 | if ((charsThatFit == 0) && (chunkPtr == dlPtr->chunkPtr)) { | |
657 | chunkPtr->numChars = 1; | |
658 | ch++; | |
659 | } else if (textPtr->wrapMode == tkTextWordUid) { | |
660 | if (isspace(chunkPtr->text[charsThatFit])) { | |
661 | ch += 1; /* Include space on this line. */ | |
662 | } else { | |
663 | register Chunk *chunkPtr2; | |
664 | register char *p; | |
665 | Chunk *spaceChunkPtr; | |
666 | int count, space; | |
667 | ||
668 | spaceChunkPtr = NULL; | |
669 | space = 0; | |
670 | for (chunkPtr2 = dlPtr->chunkPtr; | |
671 | chunkPtr2 != NULL; | |
672 | chunkPtr2 = chunkPtr2->nextPtr) { | |
673 | for (count = chunkPtr2->numChars - 1, | |
674 | p = chunkPtr2->text + count; | |
675 | count >= 0; count--, p--) { | |
676 | if (isspace(*p)) { | |
677 | spaceChunkPtr = chunkPtr2; | |
678 | space = count; | |
679 | break; | |
680 | } | |
681 | } | |
682 | } | |
683 | if (spaceChunkPtr != NULL) { | |
684 | spaceChunkPtr->numChars = space; | |
685 | ch = (spaceChunkPtr->text + space + 1) | |
686 | - linePtr->bytes; | |
687 | if (chunkPtr != spaceChunkPtr) { | |
688 | chunkPtr = spaceChunkPtr; | |
689 | if (tInfoPtr->tagPtrs != NULL) { | |
690 | ckfree((char *) tInfoPtr->tagPtrs); | |
691 | } | |
692 | tInfoPtr->tagPtrs = TkBTreeGetTags( | |
693 | textPtr->tree, dlPtr->linePtr, ch, | |
694 | &tInfoPtr->numTags); | |
695 | TkBTreeStartSearch(textPtr->tree, line, | |
696 | ch+1, | |
697 | TkBTreeNumLines(textPtr->tree), 0, | |
698 | (TkTextTag *) NULL, | |
699 | &tInfoPtr->search); | |
700 | (void) TkBTreeNextTag(&tInfoPtr->search); | |
701 | tInfoPtr->arraySize = tInfoPtr->numTags; | |
702 | while (chunkPtr->nextPtr != NULL) { | |
703 | chunkPtr2 = chunkPtr->nextPtr; | |
704 | chunkPtr->nextPtr = chunkPtr2->nextPtr; | |
705 | FreeStyle(chunkPtr2->stylePtr); | |
706 | ckfree((char *) chunkPtr2); | |
707 | } | |
708 | } | |
709 | } | |
710 | } | |
711 | } else if (textPtr->wrapMode == tkTextNoneUid) { | |
712 | chunkPtr->numChars++; | |
713 | ch++; | |
714 | } | |
715 | } | |
716 | } else { | |
717 | ch += chunkPtr->numChars; | |
718 | } | |
719 | ||
720 | /* | |
721 | * Update height information for use later in computing | |
722 | * line's overall height and baseline. | |
723 | */ | |
724 | ||
725 | if (styleValues.fontPtr->ascent > ascent) { | |
726 | ascent = styleValues.fontPtr->ascent; | |
727 | } | |
728 | if (styleValues.fontPtr->descent > descent) { | |
729 | descent = styleValues.fontPtr->descent; | |
730 | } | |
731 | } | |
732 | ||
733 | dlPtr->height = ascent + descent; | |
734 | dlPtr->baseline = ascent; | |
735 | ||
736 | /* | |
737 | * Quit when every character but the last character (the newline) | |
738 | * has been accounted for. Also quit if the wrap mode is "none": | |
739 | * this ignores all the characters that don't fit on the first | |
740 | * line. | |
741 | */ | |
742 | ||
743 | if ((ch >= (linePtr->numBytes-1)) | |
744 | || (textPtr->wrapMode == tkTextNoneUid)) { | |
745 | break; | |
746 | } | |
747 | } | |
748 | return firstLinePtr; | |
749 | } | |
750 | \f | |
751 | /* | |
752 | *---------------------------------------------------------------------- | |
753 | * | |
754 | * ToggleTag -- | |
755 | * | |
756 | * Update information about tags to reflect a transition on a | |
757 | * particular tag. | |
758 | * | |
759 | * Results: | |
760 | * The array at *tInfoPtr is modified to include tagPtr if it | |
761 | * didn't already or to exclude it if it used to include it. | |
762 | * The array will be reallocated to a larger size if needed. | |
763 | * | |
764 | * Side effects: | |
765 | * None. | |
766 | * | |
767 | *---------------------------------------------------------------------- | |
768 | */ | |
769 | ||
770 | static void | |
771 | ToggleTag(tInfoPtr, tagPtr) | |
772 | register TagInfo *tInfoPtr; /* Tag information to be updated. */ | |
773 | TkTextTag *tagPtr; /* Tag to be toggled into or out of | |
774 | * *tInfoPtr. */ | |
775 | { | |
776 | register TkTextTag **tagPtrPtr; | |
777 | int i; | |
778 | ||
779 | for (i = tInfoPtr->numTags, tagPtrPtr = tInfoPtr->tagPtrs; | |
780 | i > 0; i--, tagPtrPtr++) { | |
781 | if (*tagPtrPtr == tagPtr) { | |
782 | tInfoPtr->numTags--; | |
783 | *tagPtrPtr = tInfoPtr->tagPtrs[tInfoPtr->numTags]; | |
784 | return; | |
785 | } | |
786 | } | |
787 | ||
788 | /* | |
789 | * Tag not currently in array. Grow the array if necessary, then | |
790 | * add the tag to it. | |
791 | */ | |
792 | ||
793 | if (tInfoPtr->numTags == tInfoPtr->arraySize) { | |
794 | TkTextTag **newPtrs; | |
795 | ||
796 | newPtrs = (TkTextTag **) ckalloc((unsigned) | |
797 | ((tInfoPtr->arraySize+10) * sizeof(TkTextTag *))); | |
798 | if (tInfoPtr->tagPtrs != NULL) { | |
799 | memcpy((VOID *) newPtrs, (VOID *) tInfoPtr->tagPtrs, | |
800 | tInfoPtr->arraySize * sizeof(TkTextTag *)); | |
801 | ckfree((char *) tInfoPtr->tagPtrs); | |
802 | } | |
803 | tInfoPtr->tagPtrs = newPtrs; | |
804 | tInfoPtr->arraySize += 10; | |
805 | } | |
806 | tInfoPtr->tagPtrs[tInfoPtr->numTags] = tagPtr; | |
807 | tInfoPtr->numTags++; | |
808 | } | |
809 | \f | |
810 | /* | |
811 | *---------------------------------------------------------------------- | |
812 | * | |
813 | * UpdateDisplayInfo -- | |
814 | * | |
815 | * This procedure is invoked to recompute some or all of the | |
816 | * DLine structures for a text widget. At the time it is called | |
817 | * the DLine structures still left in the widget are guaranteed | |
818 | * to be correct (except for their y-coordinates), but there may | |
819 | * be missing structures (the DLine structures get removed as | |
820 | * soon as they are potentially out-of-date). | |
821 | * | |
822 | * Results: | |
823 | * None. | |
824 | * | |
825 | * Side effects: | |
826 | * Upon return, the DLine information for textPtr correctly reflects | |
827 | * the positions where characters will be displayed. However, this | |
828 | * procedure doesn't actually bring the display up-to-date. | |
829 | * | |
830 | *---------------------------------------------------------------------- | |
831 | */ | |
832 | ||
833 | static void | |
834 | UpdateDisplayInfo(textPtr) | |
835 | TkText *textPtr; /* Text widget to update. */ | |
836 | { | |
837 | register DInfo *dInfoPtr = textPtr->dInfoPtr; | |
838 | register DLine *dlPtr, *prevPtr, *dlPtr2; | |
839 | TkTextLine *linePtr; | |
840 | TagInfo tagInfo; | |
841 | int line, y, maxY; | |
842 | ||
843 | if (!(dInfoPtr->flags & DINFO_OUT_OF_DATE)) { | |
844 | return; | |
845 | } | |
846 | dInfoPtr->flags &= ~DINFO_OUT_OF_DATE; | |
847 | ||
848 | linePtr = textPtr->topLinePtr; | |
849 | dlPtr = dInfoPtr->dLinePtr; | |
850 | tagInfo.tagPtrs = TkBTreeGetTags(textPtr->tree, linePtr, 0, | |
851 | &tagInfo.numTags); | |
852 | tagInfo.arraySize = tagInfo.numTags; | |
853 | ||
854 | /* | |
855 | * Tricky point: initialize the tag search just *after* the first | |
856 | * character in the line, since the tagInfo structure already has all | |
857 | * the tags for the first character. | |
858 | */ | |
859 | ||
860 | line = TkBTreeLineIndex(linePtr); | |
861 | TkBTreeStartSearch(textPtr->tree, line, 1, TkBTreeNumLines(textPtr->tree), | |
862 | 0, (TkTextTag *) NULL, &tagInfo.search); | |
863 | TkBTreeNextTag(&tagInfo.search); | |
864 | prevPtr = NULL; | |
865 | y = dInfoPtr->y; | |
866 | maxY = dInfoPtr->maxY; | |
867 | while ((linePtr != NULL) && (y < maxY)) { | |
868 | register DLine *newPtr; | |
869 | /* | |
870 | * See if the next DLine matches the next line we want to | |
871 | * appear on the screen. If so then we can just use its | |
872 | * information. If not then create new DLine structures | |
873 | * for the desired line and insert them into the list. | |
874 | */ | |
875 | ||
876 | if ((dlPtr == NULL) || (dlPtr->linePtr != linePtr)) { | |
877 | newPtr = LayoutLine(textPtr, line, linePtr, &tagInfo); | |
878 | if (prevPtr == NULL) { | |
879 | dInfoPtr->dLinePtr = newPtr; | |
880 | } else { | |
881 | prevPtr->nextPtr = newPtr; | |
882 | } | |
883 | for (dlPtr2 = newPtr; dlPtr2->nextPtr != NULL; | |
884 | dlPtr2 = dlPtr2->nextPtr) { | |
885 | /* Empty loop body. */ | |
886 | } | |
887 | dlPtr2->nextPtr = dlPtr; | |
888 | dlPtr = newPtr; | |
889 | } | |
890 | ||
891 | /* | |
892 | * Skip to the next line, and update the y-position while | |
893 | * skipping. | |
894 | */ | |
895 | ||
896 | do { | |
897 | dlPtr->y = y; | |
898 | y += dlPtr->height; | |
899 | prevPtr = dlPtr; | |
900 | dlPtr = dlPtr->nextPtr; | |
901 | } while ((dlPtr != NULL) && (dlPtr->linePtr == linePtr)); | |
902 | linePtr = TkBTreeNextLine(linePtr); | |
903 | line++; | |
904 | } | |
905 | ||
906 | /* | |
907 | * Delete any DLine structures that don't fit on the screen and free | |
908 | * up the tag array. | |
909 | */ | |
910 | ||
911 | FreeDLines(textPtr, dlPtr, (DLine *) NULL, 1); | |
912 | if (tagInfo.tagPtrs != NULL) { | |
913 | ckfree((char *) tagInfo.tagPtrs); | |
914 | } | |
915 | ||
916 | /* | |
917 | * Update the vertical scrollbar, if there is one. | |
918 | */ | |
919 | ||
920 | if (textPtr->yScrollCmd != NULL) { | |
921 | int numLines, first, result, maxY, height; | |
922 | char string[60]; | |
923 | ||
924 | /* | |
925 | * Count the number of text lines on the screen. | |
926 | */ | |
927 | ||
928 | maxY = 0; | |
929 | for (numLines = 0, linePtr = NULL, dlPtr = dInfoPtr->dLinePtr; | |
930 | dlPtr != NULL; dlPtr = dlPtr->nextPtr) { | |
931 | if (dlPtr->linePtr != linePtr) { | |
932 | numLines++; | |
933 | linePtr = dlPtr->linePtr; | |
934 | } | |
935 | maxY = dlPtr->y + dlPtr->height; | |
936 | } | |
937 | ||
938 | /* | |
939 | * If the screen isn't completely full, then estimate the number of | |
940 | * lines that would fit on it if it were full. | |
941 | */ | |
942 | ||
943 | height = dInfoPtr->maxY - dInfoPtr->y; | |
944 | if (numLines == 0) { | |
945 | numLines = height / | |
946 | (textPtr->fontPtr->ascent + textPtr->fontPtr->descent); | |
947 | } else if (maxY < height) { | |
948 | numLines = (numLines * height)/maxY; | |
949 | } | |
950 | /* DEH: be reasonable if dLinePtr is null */ | |
951 | if (dInfoPtr->dLinePtr == NULL) { | |
952 | sprintf(string, " 0 0 0 0"); | |
953 | } else { | |
954 | first = TkBTreeLineIndex(dInfoPtr->dLinePtr->linePtr); | |
955 | sprintf(string, " %d %d %d %d", TkBTreeNumLines(textPtr->tree), | |
956 | numLines, first, first+numLines-1); | |
957 | } | |
958 | result = Tcl_VarEval(textPtr->interp, textPtr->yScrollCmd, string, | |
959 | (char *) NULL); | |
960 | if (result != TCL_OK) { | |
961 | TkBindError(textPtr->interp); | |
962 | } | |
963 | } | |
964 | } | |
965 | \f | |
966 | /* | |
967 | *---------------------------------------------------------------------- | |
968 | * | |
969 | * FreeDLines -- | |
970 | * | |
971 | * This procedure is called to free up all of the resources | |
972 | * associated with one or more DLine structures. | |
973 | * | |
974 | * Results: | |
975 | * None. | |
976 | * | |
977 | * Side effects: | |
978 | * Memory gets freed and various other resources are released. | |
979 | * | |
980 | *---------------------------------------------------------------------- | |
981 | */ | |
982 | ||
983 | static void | |
984 | FreeDLines(textPtr, firstPtr, lastPtr, unlink) | |
985 | TkText *textPtr; /* Information about overall text | |
986 | * widget. */ | |
987 | register DLine *firstPtr; /* Pointer to first DLine to free up. */ | |
988 | DLine *lastPtr; /* Pointer to DLine just after last | |
989 | * one to free (NULL means everything | |
990 | * starting with firstPtr). */ | |
991 | int unlink; /* 1 means DLines are currently linked | |
992 | * into the list rooted at | |
993 | * textPtr->dInfoPtr->dLinePtr and | |
994 | * they have to be unlinked. 0 means | |
995 | * just free without unlinking. */ | |
996 | { | |
997 | register Chunk *chunkPtr, *nextChunkPtr; | |
998 | register DLine *nextDLinePtr; | |
999 | ||
1000 | if (unlink) { | |
1001 | if (textPtr->dInfoPtr->dLinePtr == firstPtr) { | |
1002 | textPtr->dInfoPtr->dLinePtr = lastPtr; | |
1003 | } else { | |
1004 | register DLine *prevPtr; | |
1005 | for (prevPtr = textPtr->dInfoPtr->dLinePtr; | |
1006 | prevPtr->nextPtr != firstPtr; prevPtr = prevPtr->nextPtr) { | |
1007 | /* Empty loop body. */ | |
1008 | } | |
1009 | prevPtr->nextPtr = lastPtr; | |
1010 | } | |
1011 | } | |
1012 | while (firstPtr != lastPtr) { | |
1013 | nextDLinePtr = firstPtr->nextPtr; | |
1014 | for (chunkPtr = firstPtr->chunkPtr; chunkPtr != NULL; | |
1015 | chunkPtr = nextChunkPtr) { | |
1016 | FreeStyle(chunkPtr->stylePtr); | |
1017 | nextChunkPtr = chunkPtr->nextPtr; | |
1018 | ckfree((char *) chunkPtr); | |
1019 | } | |
1020 | ckfree((char *) firstPtr); | |
1021 | firstPtr = nextDLinePtr; | |
1022 | } | |
1023 | } | |
1024 | \f | |
1025 | /* | |
1026 | *---------------------------------------------------------------------- | |
1027 | * | |
1028 | * DisplayDLine -- | |
1029 | * | |
1030 | * This procedure is invoked to draw a single line on the | |
1031 | * screen. | |
1032 | * | |
1033 | * Results: | |
1034 | * None. | |
1035 | * | |
1036 | * Side effects: | |
1037 | * The line given by dlPtr is drawn at its correct position in | |
1038 | * textPtr's window. Note that this is one *display* line, not | |
1039 | * one *text* line. | |
1040 | * | |
1041 | *---------------------------------------------------------------------- | |
1042 | */ | |
1043 | ||
1044 | static void | |
1045 | DisplayDLine(textPtr, dlPtr, pixmap) | |
1046 | TkText *textPtr; /* Text widget in which to draw line. */ | |
1047 | register DLine *dlPtr; /* Information about line to draw. */ | |
1048 | Pixmap pixmap; /* Pixmap to use for double-buffering. | |
1049 | * Caller must make sure it's large enough | |
1050 | * to hold line. */ | |
1051 | { | |
1052 | register Style *stylePtr; | |
1053 | register StyleValues *sValuePtr; | |
1054 | register Chunk *chunkPtr; | |
1055 | DInfo *dInfoPtr = textPtr->dInfoPtr; | |
1056 | Display *display; | |
1057 | int width, height, count, x; | |
1058 | XFontStruct *fontPtr; | |
1059 | ||
1060 | /* | |
1061 | * First, clear the area of the line to the background color for the | |
1062 | * text widget. | |
1063 | */ | |
1064 | ||
1065 | display = Tk_Display(textPtr->tkwin); | |
1066 | Tk_Fill3DRectangle(display, pixmap, textPtr->border, 0, 0, | |
1067 | Tk_Width(textPtr->tkwin), dlPtr->height, 0, TK_RELIEF_FLAT); | |
1068 | ||
1069 | /* | |
1070 | * Next, cycle through all of the chunks in the line displaying | |
1071 | * backgrounds. We need to do two passes, one for the backgrounds | |
1072 | * and one for the characters, because some characters (e.g. italics | |
1073 | * with heavy slants) may cross background boundaries. If some | |
1074 | * backgrounds are drawn after some text, the later backgrounds may | |
1075 | * obliterate parts of earlier characters. | |
1076 | */ | |
1077 | ||
1078 | for (chunkPtr = dlPtr->chunkPtr; chunkPtr != NULL; | |
1079 | chunkPtr = chunkPtr->nextPtr) { | |
1080 | ||
1081 | /* | |
1082 | * Draw a special background for this chunk if one is specified | |
1083 | * in its style. Two tricks here: | |
1084 | * 1. if this is the last chunk in the line then extend the | |
1085 | * background across to the end of the line. | |
1086 | * 2. if the background is stippled, then we have to draw the | |
1087 | * stippled part specially, since Tk_Fill3DRectangle doesn't | |
1088 | * do stipples. | |
1089 | */ | |
1090 | ||
1091 | stylePtr = chunkPtr->stylePtr; | |
1092 | sValuePtr = stylePtr->sValuePtr; | |
1093 | if (sValuePtr->border != NULL) { | |
1094 | if (chunkPtr->nextPtr != NULL) { | |
1095 | width = chunkPtr->nextPtr->x - chunkPtr->x; | |
1096 | } else { | |
1097 | width = Tk_Width(textPtr->tkwin) - chunkPtr->x; | |
1098 | } | |
1099 | if (stylePtr->bgGC != NULL) { | |
1100 | XFillRectangle(display, pixmap, stylePtr->bgGC, chunkPtr->x, | |
1101 | 0, (unsigned int) width, (unsigned int) dlPtr->height); | |
1102 | Tk_Draw3DRectangle(display, pixmap, sValuePtr->border, | |
1103 | chunkPtr->x, 0, width, dlPtr->height, | |
1104 | sValuePtr->borderWidth, sValuePtr->relief); | |
1105 | } else { | |
1106 | Tk_Fill3DRectangle(display, pixmap, sValuePtr->border, | |
1107 | chunkPtr->x, 0, width, dlPtr->height, | |
1108 | sValuePtr->borderWidth, sValuePtr->relief); | |
1109 | } | |
1110 | } | |
1111 | } | |
1112 | ||
1113 | /* | |
1114 | * If the insertion cursor is displayed on this line, then draw it | |
1115 | * now, on top of the background but before the text. As a special | |
1116 | * workaround to keep the cursor visible on mono displays, write the default | |
1117 | * background in the cursor area (instead of nothing) when the cursor | |
1118 | * isn't on. Otherwise the selection would hide the cursor. | |
1119 | */ | |
1120 | ||
1121 | if ((textPtr->insertAnnotPtr->linePtr == dlPtr->linePtr) | |
1122 | && (textPtr->state == tkTextNormalUid) | |
1123 | && (textPtr->flags & GOT_FOCUS)) { | |
1124 | for (chunkPtr = dlPtr->chunkPtr; chunkPtr != NULL; | |
1125 | chunkPtr = chunkPtr->nextPtr) { | |
1126 | count = textPtr->insertAnnotPtr->ch | |
1127 | - (chunkPtr->text - dlPtr->linePtr->bytes); | |
1128 | if (count < 0) { | |
1129 | break; | |
1130 | } | |
1131 | if (count > chunkPtr->numChars) { | |
1132 | continue; | |
1133 | } | |
1134 | ||
1135 | /* | |
1136 | * Deciding whether to display the cursor just after the last | |
1137 | * character in a line is tricky because of various wrap | |
1138 | * modes. Do it unless we're in character wrap mode and | |
1139 | * this line wraps, in which case it's better to display the | |
1140 | * cursor on the next line. For word wrap, there's an | |
1141 | * undisplayed space character that the user must be able to | |
1142 | * position the cursor in front of. For no wrap, there's no | |
1143 | * next line on which to display the cursor. | |
1144 | */ | |
1145 | if ((count == chunkPtr->numChars) | |
1146 | && (textPtr->wrapMode == tkTextCharUid) | |
1147 | && (chunkPtr->text[count] != '\n')) { | |
1148 | continue; | |
1149 | } | |
1150 | fontPtr = chunkPtr->stylePtr->sValuePtr->fontPtr; | |
1151 | TkMeasureChars(fontPtr, chunkPtr->text, count, chunkPtr->x, | |
1152 | (int) 1000000, 0, &x); | |
1153 | if (textPtr->flags & INSERT_ON) { | |
1154 | Tk_Fill3DRectangle(display, pixmap, textPtr->insertBorder, | |
1155 | x - textPtr->insertWidth/2, | |
1156 | dlPtr->baseline - fontPtr->ascent, | |
1157 | textPtr->insertWidth, | |
1158 | fontPtr->ascent + fontPtr->descent, | |
1159 | textPtr->insertBorderWidth, TK_RELIEF_RAISED); | |
1160 | } else if (Tk_DefaultDepth(Tk_Screen(textPtr->tkwin)) == 1) { | |
1161 | Tk_Fill3DRectangle(display, pixmap, textPtr->border, | |
1162 | x - textPtr->insertWidth/2, | |
1163 | dlPtr->baseline - fontPtr->ascent, | |
1164 | textPtr->insertWidth, | |
1165 | fontPtr->ascent + fontPtr->descent, | |
1166 | 0, TK_RELIEF_FLAT); | |
1167 | } | |
1168 | ||
1169 | } | |
1170 | } | |
1171 | ||
1172 | /* | |
1173 | * Make another pass through all of the chunks to redraw all of | |
1174 | * the text (and underlines, etc., if they're wanted). | |
1175 | */ | |
1176 | ||
1177 | for (chunkPtr = dlPtr->chunkPtr; chunkPtr != NULL; | |
1178 | chunkPtr = chunkPtr->nextPtr) { | |
1179 | stylePtr = chunkPtr->stylePtr; | |
1180 | sValuePtr = stylePtr->sValuePtr; | |
1181 | if (chunkPtr->numChars > 0) { | |
1182 | TkDisplayChars(display, pixmap, stylePtr->fgGC, sValuePtr->fontPtr, | |
1183 | chunkPtr->text, chunkPtr->numChars, chunkPtr->x, | |
1184 | dlPtr->baseline, 0); | |
1185 | if (sValuePtr->underline) { | |
1186 | TkUnderlineChars(display, pixmap, stylePtr->fgGC, | |
1187 | sValuePtr->fontPtr, chunkPtr->text, chunkPtr->x, | |
1188 | dlPtr->baseline, 0, 0, chunkPtr->numChars-1); | |
1189 | } | |
1190 | } | |
1191 | } | |
1192 | ||
1193 | /* | |
1194 | * Copy the pixmap onto the screen. If this is the last line on | |
1195 | * the screen, only copy a piece of the line, so that it doesn't | |
1196 | * overflow into the border area. Another special trick: copy the | |
1197 | * padding area to the left of the line; this is because the | |
1198 | * insertion cursor sometimes overflows onto that area and we want | |
1199 | * to get as much of the cursor as possible. | |
1200 | */ | |
1201 | ||
1202 | height = dlPtr->height; | |
1203 | if ((height + dlPtr->y) > dInfoPtr->maxY) { | |
1204 | height = dInfoPtr->maxY - dlPtr->y; | |
1205 | } | |
1206 | XCopyArea(display, pixmap, Tk_WindowId(textPtr->tkwin), | |
1207 | dInfoPtr->copyGC, dInfoPtr->x - textPtr->padX, 0, | |
1208 | dInfoPtr->maxX - (dInfoPtr->x - textPtr->padX), | |
1209 | height, dInfoPtr->x - textPtr->padX, dlPtr->y); | |
1210 | linesRedrawn++; | |
1211 | } | |
1212 | \f | |
1213 | /* | |
1214 | *---------------------------------------------------------------------- | |
1215 | * | |
1216 | * DisplayText -- | |
1217 | * | |
1218 | * This procedure is invoked as a when-idle handler to update the | |
1219 | * display. It only redisplays the parts of the text widget that | |
1220 | * are out of date. | |
1221 | * | |
1222 | * Results: | |
1223 | * None. | |
1224 | * | |
1225 | * Side effects: | |
1226 | * Information is redrawn on the screen. | |
1227 | * | |
1228 | *---------------------------------------------------------------------- | |
1229 | */ | |
1230 | ||
1231 | static void | |
1232 | DisplayText(clientData) | |
1233 | ClientData clientData; /* Information about widget. */ | |
1234 | { | |
1235 | register TkText *textPtr = (TkText *) clientData; | |
1236 | DInfo *dInfoPtr = textPtr->dInfoPtr; | |
1237 | Tk_Window tkwin; | |
1238 | register DLine *dlPtr; | |
1239 | Pixmap pixmap; | |
1240 | int maxHeight; | |
1241 | int bottomY = 0; /* Initialization needed only to stop | |
1242 | * compiler warnings. */ | |
1243 | ||
1244 | assert(textPtr->updateTimerToken != NULL); | |
1245 | ||
1246 | textPtr->updateTimerToken = 0; | |
1247 | ||
1248 | if ((textPtr->tkwin == NULL) || !Tk_IsMapped(textPtr->tkwin) | |
1249 | || (dInfoPtr->maxX <= dInfoPtr->x) | |
1250 | || (dInfoPtr->maxY <= dInfoPtr->y)) { | |
1251 | goto done; | |
1252 | } | |
1253 | numRedisplays++; | |
1254 | ||
1255 | /* | |
1256 | * Choose a new current item if that is needed (this could cause | |
1257 | * event handlers to be invoked, hence the preserve/release calls | |
1258 | * and the loop, since the handlers could conceivably necessitate | |
1259 | * yet another current item calculation). The tkwin check is because | |
1260 | * the whole window could go away in the Tk_Release call. | |
1261 | */ | |
1262 | ||
1263 | while (dInfoPtr->flags & REPICK_NEEDED) { | |
1264 | Tk_Preserve((ClientData) textPtr); | |
1265 | dInfoPtr->flags &= ~REPICK_NEEDED; | |
1266 | TkTextPickCurrent(textPtr, &textPtr->pickEvent); | |
1267 | tkwin = textPtr->tkwin; | |
1268 | Tk_Release((ClientData) textPtr); | |
1269 | if (tkwin == NULL) { | |
1270 | return; | |
1271 | } | |
1272 | } | |
1273 | ||
1274 | /* | |
1275 | * First recompute what's supposed to be displayed. | |
1276 | */ | |
1277 | ||
1278 | UpdateDisplayInfo(textPtr); | |
1279 | ||
1280 | /* | |
1281 | * Redraw the borders if that's needed. | |
1282 | */ | |
1283 | ||
1284 | if (dInfoPtr->flags & REDRAW_BORDERS) { | |
1285 | Tk_Draw3DRectangle(Tk_Display(textPtr->tkwin), | |
1286 | Tk_WindowId(textPtr->tkwin), textPtr->border, | |
1287 | 0, 0, Tk_Width(textPtr->tkwin), Tk_Height(textPtr->tkwin), | |
1288 | textPtr->borderWidth, textPtr->relief); | |
1289 | } | |
1290 | ||
1291 | /* | |
1292 | * See if it's possible to bring some parts of the screen up-to-date | |
1293 | * by scrolling (copying from other parts of the screen). | |
1294 | */ | |
1295 | ||
1296 | for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) { | |
1297 | register DLine *dlPtr2; | |
1298 | int offset, height; | |
1299 | ||
1300 | if ((dlPtr->oldY == -1) || (dlPtr->y == dlPtr->oldY) | |
1301 | || ((dlPtr->oldY + dlPtr->height) > dInfoPtr->maxY)) { | |
1302 | continue; | |
1303 | } | |
1304 | ||
1305 | /* | |
1306 | * This line is already drawn somewhere in the window so it only | |
1307 | * needs to be copied to its new location. See if there's a group | |
1308 | * of lines that can all be copied together. | |
1309 | */ | |
1310 | ||
1311 | offset = dlPtr->y - dlPtr->oldY; | |
1312 | height = dlPtr->height; | |
1313 | for (dlPtr2 = dlPtr->nextPtr; dlPtr2 != NULL; | |
1314 | dlPtr2 = dlPtr2->nextPtr) { | |
1315 | if ((dlPtr2->oldY == -1) | |
1316 | || ((dlPtr2->oldY + offset) != dlPtr2->y) | |
1317 | || ((dlPtr2->oldY + dlPtr2->height) > dInfoPtr->maxY)) { | |
1318 | break; | |
1319 | } | |
1320 | height += dlPtr2->height; | |
1321 | } | |
1322 | ||
1323 | /* | |
1324 | * Copy the information and update the lines to show that they've | |
1325 | * been copied. Reduce the height of the area being copied if | |
1326 | * necessary to avoid overwriting the border area. | |
1327 | */ | |
1328 | ||
1329 | if ((dlPtr->y + height) > dInfoPtr->maxY) { | |
1330 | height = dInfoPtr->maxY - dlPtr->y; | |
1331 | } | |
1332 | XCopyArea(Tk_Display(textPtr->tkwin), Tk_WindowId(textPtr->tkwin), | |
1333 | Tk_WindowId(textPtr->tkwin), dInfoPtr->scrollGC, | |
1334 | dInfoPtr->x - textPtr->padX, dlPtr->oldY, | |
1335 | dInfoPtr->maxX - (dInfoPtr->x - textPtr->padX), | |
1336 | height, dInfoPtr->x - textPtr->padX, dlPtr->y); | |
1337 | numCopies++; | |
1338 | while (1) { | |
1339 | dlPtr->oldY = dlPtr->y; | |
1340 | if (dlPtr->nextPtr == dlPtr2) { | |
1341 | break; | |
1342 | } | |
1343 | dlPtr = dlPtr->nextPtr; | |
1344 | } | |
1345 | ||
1346 | /* | |
1347 | * It's possible that part of the area copied above was obscured. | |
1348 | * To handle this situation, read expose-related events generated | |
1349 | * during the XCopyArea operation. | |
1350 | */ | |
1351 | ||
1352 | while (1) { | |
1353 | XEvent event; | |
1354 | ||
1355 | XWindowEvent(Tk_Display(textPtr->tkwin), | |
1356 | Tk_WindowId(textPtr->tkwin), ExposureMask, &event); | |
1357 | if (event.type == NoExpose) { | |
1358 | break; | |
1359 | } else if (event.type == GraphicsExpose) { | |
1360 | TkTextRedrawRegion(textPtr, event.xgraphicsexpose.x, | |
1361 | event.xgraphicsexpose.y, event.xgraphicsexpose.width, | |
1362 | event.xgraphicsexpose.height); | |
1363 | if (event.xgraphicsexpose.count == 0) { | |
1364 | damagedCopies++; | |
1365 | break; | |
1366 | } | |
1367 | } else if (event.type == Expose) { | |
1368 | /* | |
1369 | * A tricky situation. This event must already have been | |
1370 | * queued up before the XCopyArea was issued. If the area | |
1371 | * in this event overlaps the area copied, then some of the | |
1372 | * bits that were copied were bogus. The easiest way to | |
1373 | * handle this is to issue two redisplays: one for the | |
1374 | * original area and one for the area shifted as if it was | |
1375 | * in the copied area. | |
1376 | */ | |
1377 | ||
1378 | TkTextRedrawRegion(textPtr, event.xexpose.x, | |
1379 | event.xexpose.y, event.xexpose.width, | |
1380 | event.xexpose.height); | |
1381 | TkTextRedrawRegion(textPtr, event.xexpose.x, | |
1382 | event.xexpose.y + offset, event.xexpose.width, | |
1383 | event.xexpose.height); | |
1384 | } else { | |
1385 | panic("DisplayText received unknown exposure event"); | |
1386 | } | |
1387 | } | |
1388 | } | |
1389 | ||
1390 | /* | |
1391 | * Now we have to redraw the lines that couldn't be updated by | |
1392 | * scrolling. First, compute the height of the largest line and | |
1393 | * allocate an off-screen pixmap to use for double-buffered | |
1394 | * displays. | |
1395 | */ | |
1396 | ||
1397 | maxHeight = -1; | |
1398 | for (dlPtr = textPtr->dInfoPtr->dLinePtr; dlPtr != NULL; | |
1399 | dlPtr = dlPtr->nextPtr) { | |
1400 | if ((dlPtr->height > maxHeight) && (dlPtr->oldY != dlPtr->y)) { | |
1401 | maxHeight = dlPtr->height; | |
1402 | } | |
1403 | bottomY = dlPtr->y + dlPtr->height; | |
1404 | } | |
1405 | if (maxHeight >= 0) { | |
1406 | pixmap = XCreatePixmap(Tk_Display(textPtr->tkwin), | |
1407 | Tk_WindowId(textPtr->tkwin), Tk_Width(textPtr->tkwin), | |
1408 | maxHeight, Tk_DefaultDepth(Tk_Screen(textPtr->tkwin))); | |
1409 | for (dlPtr = textPtr->dInfoPtr->dLinePtr; dlPtr != NULL; | |
1410 | dlPtr = dlPtr->nextPtr) { | |
1411 | if (dlPtr->oldY != dlPtr->y) { | |
1412 | DisplayDLine(textPtr, dlPtr, pixmap); | |
1413 | dlPtr->oldY = dlPtr->y; | |
1414 | } | |
1415 | } | |
1416 | XFreePixmap(Tk_Display(textPtr->tkwin), pixmap); | |
1417 | } | |
1418 | ||
1419 | /* | |
1420 | * Lastly, see if we need to refresh the part of the window below | |
1421 | * the last line of text (if there is any such area). | |
1422 | */ | |
1423 | ||
1424 | if (dInfoPtr->topOfEof > dInfoPtr->maxY) { | |
1425 | dInfoPtr->topOfEof = dInfoPtr->maxY; | |
1426 | } | |
1427 | if (bottomY < dInfoPtr->topOfEof) { | |
1428 | Tk_Fill3DRectangle(Tk_Display(textPtr->tkwin), | |
1429 | Tk_WindowId(textPtr->tkwin), textPtr->border, | |
1430 | dInfoPtr->x, bottomY, dInfoPtr->maxX - dInfoPtr->x, | |
1431 | dInfoPtr->topOfEof-bottomY, 0, TK_RELIEF_FLAT); | |
1432 | } | |
1433 | dInfoPtr->topOfEof = bottomY; | |
1434 | if (dInfoPtr->topOfEof > dInfoPtr->maxY) { | |
1435 | dInfoPtr->topOfEof = dInfoPtr->maxY; | |
1436 | } | |
1437 | ||
1438 | done: | |
1439 | dInfoPtr->flags &= ~(REDRAW_PENDING|REDRAW_BORDERS); | |
1440 | } | |
1441 | \f | |
1442 | /* | |
1443 | *---------------------------------------------------------------------- | |
1444 | * | |
1445 | * TkTextRedrawRegion -- | |
1446 | * | |
1447 | * This procedure is invoked to schedule a redisplay for a given | |
1448 | * region of a text widget. The redisplay itself may not occur | |
1449 | * immediately: it's scheduled as a when-idle handler. | |
1450 | * | |
1451 | * Results: | |
1452 | * None. | |
1453 | * | |
1454 | * Side effects: | |
1455 | * Information will eventually be redrawn on the screen. | |
1456 | * | |
1457 | *---------------------------------------------------------------------- | |
1458 | */ | |
1459 | ||
1460 | /* ARGSUSED */ | |
1461 | void | |
1462 | TkTextRedrawRegion(textPtr, x, y, width, height) | |
1463 | TkText *textPtr; /* Widget record for text widget. */ | |
1464 | int x, y; /* Coordinates of upper-left corner of area | |
1465 | * to be redrawn, in pixels relative to | |
1466 | * textPtr's window. */ | |
1467 | int width, height; /* Width and height of area to be redrawn. */ | |
1468 | { | |
1469 | register DLine *dlPtr; | |
1470 | DInfo *dInfoPtr = textPtr->dInfoPtr; | |
1471 | int maxY; | |
1472 | ||
1473 | /* | |
1474 | * Find all lines that overlap the given region and mark them for | |
1475 | * redisplay. | |
1476 | */ | |
1477 | ||
1478 | maxY = y + height; | |
1479 | for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; | |
1480 | dlPtr = dlPtr->nextPtr) { | |
1481 | if (((dlPtr->y + dlPtr->height) > y) && (dlPtr->y < maxY)) { | |
1482 | dlPtr->oldY = -1; | |
1483 | } | |
1484 | } | |
1485 | if (dInfoPtr->topOfEof < maxY) { | |
1486 | dInfoPtr->topOfEof = maxY; | |
1487 | } | |
1488 | ||
1489 | /* | |
1490 | * Schedule the redisplay operation if there isn't one already | |
1491 | * scheduled. | |
1492 | */ | |
1493 | ||
1494 | if (!(dInfoPtr->flags & REDRAW_PENDING)) { | |
1495 | dInfoPtr->flags |= REDRAW_PENDING; | |
1496 | // Tk_DoWhenIdle(DisplayText, (ClientData) textPtr); | |
1497 | assert(textPtr->updateTimerToken == NULL); | |
1498 | if (textPtr->updateTimerToken == NULL) { | |
1499 | textPtr->updateTimerToken = | |
1500 | Tk_CreateTimerHandler( | |
1501 | TextUpdateTime, | |
1502 | DisplayText, | |
1503 | (ClientData) textPtr); | |
1504 | } | |
1505 | } | |
1506 | if ((x < dInfoPtr->x) || (y < dInfoPtr->y) | |
1507 | || ((x + width) > dInfoPtr->maxX) || (maxY > dInfoPtr->maxY)) { | |
1508 | dInfoPtr->flags |= REDRAW_BORDERS; | |
1509 | } | |
1510 | } | |
1511 | \f | |
1512 | /* | |
1513 | *---------------------------------------------------------------------- | |
1514 | * | |
1515 | * TkTextLinesChanged -- | |
1516 | * | |
1517 | * This procedure is invoked when lines in a text widget are about | |
1518 | * to be modified in a way that changes how they are displayed (e.g. | |
1519 | * characters were inserted, the line was deleted, or tag information | |
1520 | * was changed). This procedure must be called *before* a change is | |
1521 | * made, so that pointers to TkTextLines in the display information | |
1522 | * are still valid. | |
1523 | * | |
1524 | * Results: | |
1525 | * None. | |
1526 | * | |
1527 | * Side effects: | |
1528 | * The indicated lines will be redisplayed at some point in the | |
1529 | * future (the actual redisplay is scheduled as a when-idle handler). | |
1530 | * | |
1531 | *---------------------------------------------------------------------- | |
1532 | */ | |
1533 | ||
1534 | void | |
1535 | TkTextLinesChanged(textPtr, first, last) | |
1536 | TkText *textPtr; /* Widget record for text widget. */ | |
1537 | int first; /* Index of first line that must be | |
1538 | * redisplayed. */ | |
1539 | int last; /* Index of last line to redisplay. */ | |
1540 | { | |
1541 | DInfo *dInfoPtr = textPtr->dInfoPtr; | |
1542 | DLine *firstPtr, *lastPtr; | |
1543 | ||
1544 | /* | |
1545 | * Find the DLines corresponding to first and last+1. | |
1546 | */ | |
1547 | ||
1548 | firstPtr = FindDLine(dInfoPtr->dLinePtr, first); | |
1549 | if (firstPtr == NULL) { | |
1550 | return; | |
1551 | } | |
1552 | lastPtr = FindDLine(dInfoPtr->dLinePtr, last+1); | |
1553 | if (firstPtr == lastPtr) { | |
1554 | return; | |
1555 | } | |
1556 | ||
1557 | /* | |
1558 | * Delete all the DLines from first up through last (but not including | |
1559 | * lastPtr, which points to the first line *outside* the range). | |
1560 | */ | |
1561 | ||
1562 | FreeDLines(textPtr, firstPtr, lastPtr, 1); | |
1563 | ||
1564 | /* | |
1565 | * Schedule both a redisplay and a recomputation of display information. | |
1566 | */ | |
1567 | ||
1568 | if (!(dInfoPtr->flags & REDRAW_PENDING)) { | |
1569 | // Tk_DoWhenIdle(DisplayText, (ClientData) textPtr); | |
1570 | assert(textPtr->updateTimerToken == NULL); | |
1571 | if (textPtr->updateTimerToken == NULL) { | |
1572 | textPtr->updateTimerToken = | |
1573 | Tk_CreateTimerHandler( | |
1574 | TextUpdateTime, | |
1575 | DisplayText, | |
1576 | (ClientData) textPtr); | |
1577 | } | |
1578 | } | |
1579 | dInfoPtr->flags |= REDRAW_PENDING | DINFO_OUT_OF_DATE | REPICK_NEEDED; | |
1580 | } | |
1581 | \f | |
1582 | /* | |
1583 | *---------------------------------------------------------------------- | |
1584 | * | |
1585 | * TkTextRedrawTag -- | |
1586 | * | |
1587 | * This procedure is invoked to request a redraw of all characters | |
1588 | * in a given range of characters that have a particular tag on or | |
1589 | * off. It's called, for example, when characters are tagged or | |
1590 | * untagged, or when tag options change. | |
1591 | * | |
1592 | * Results: | |
1593 | * None. | |
1594 | * | |
1595 | * Side effects: | |
1596 | * Information on the screen may be redrawn, and the layout of | |
1597 | * the screen may change. | |
1598 | * | |
1599 | *---------------------------------------------------------------------- | |
1600 | */ | |
1601 | ||
1602 | void | |
1603 | TkTextRedrawTag(textPtr, line1, ch1, line2, ch2, tagPtr, withTag) | |
1604 | TkText *textPtr; /* Widget record for text widget. */ | |
1605 | int line1, ch1; /* Index of first character in range of | |
1606 | * interest. */ | |
1607 | int line2, ch2; /* Index of character just after last one | |
1608 | * in range of interest. */ | |
1609 | TkTextTag *tagPtr; /* Information about tag. */ | |
1610 | int withTag; /* 1 means redraw characters that have the | |
1611 | * tag, 0 means redraw those without. */ | |
1612 | { | |
1613 | register DLine *dlPtr; | |
1614 | DLine *endPtr; | |
1615 | int topLine, tagOn; | |
1616 | TkTextSearch search; | |
1617 | DInfo *dInfoPtr = textPtr->dInfoPtr; | |
1618 | ||
1619 | /* | |
1620 | * Round up the starting position if it's before the first line | |
1621 | * visible on the screen (we only care about what's on the screen). | |
1622 | */ | |
1623 | ||
1624 | dlPtr = dInfoPtr->dLinePtr; | |
1625 | if (dlPtr == NULL) { | |
1626 | return; | |
1627 | } | |
1628 | topLine = TkBTreeLineIndex(dlPtr->linePtr); | |
1629 | if (topLine > line1) { | |
1630 | line1 = topLine; | |
1631 | ch1 = 0; | |
1632 | } | |
1633 | ||
1634 | /* | |
1635 | * Initialize a search through all transitions on the tag, starting | |
1636 | * with the first transition where the tag's current state is different | |
1637 | * from what it will eventually be. | |
1638 | */ | |
1639 | ||
1640 | TkBTreeStartSearch(textPtr->tree, line1, ch1+1, line2, ch2, | |
1641 | tagPtr, &search); | |
1642 | tagOn = TkBTreeCharTagged(search.linePtr, ch1, tagPtr); | |
1643 | if (tagOn != withTag) { | |
1644 | if (!TkBTreeNextTag(&search)) { | |
1645 | return; | |
1646 | } | |
1647 | } | |
1648 | ||
1649 | /* | |
1650 | * Each loop through the loop below is for one range of characters | |
1651 | * where the tag's current state is different than its eventual | |
1652 | * state. At the top of the loop, search contains information about | |
1653 | * the first character in the range. | |
1654 | */ | |
1655 | ||
1656 | while (1) { | |
1657 | /* | |
1658 | * Find the first DLine structure in the range. | |
1659 | */ | |
1660 | ||
1661 | dlPtr = FindDLine(dlPtr, search.line1); | |
1662 | if (dlPtr == NULL) { | |
1663 | break; | |
1664 | } | |
1665 | ||
1666 | /* | |
1667 | * Find the first DLine structure that's past the end of the range. | |
1668 | */ | |
1669 | ||
1670 | if (TkBTreeNextTag(&search)) { | |
1671 | endPtr = FindDLine(dlPtr, | |
1672 | (search.ch1 > 0) ? (search.line1 + 1) : search.line1); | |
1673 | } else { | |
1674 | endPtr = FindDLine(dlPtr, | |
1675 | (ch2 > 0) ? (search.line2 + 1) : search.line2); | |
1676 | } | |
1677 | ||
1678 | /* | |
1679 | * Delete all of the display lines in the range, so that they'll | |
1680 | * be re-layed out and redrawn. | |
1681 | */ | |
1682 | ||
1683 | FreeDLines(textPtr, dlPtr, endPtr, 1); | |
1684 | dlPtr = endPtr; | |
1685 | ||
1686 | /* | |
1687 | * Find the first text line in the next range. | |
1688 | */ | |
1689 | ||
1690 | if (!TkBTreeNextTag(&search)) { | |
1691 | break; | |
1692 | } | |
1693 | } | |
1694 | ||
1695 | /* | |
1696 | * Lastly, schedule a redisplay and layout recalculation if they | |
1697 | * aren't already pending. | |
1698 | */ | |
1699 | ||
1700 | if (!(dInfoPtr->flags & REDRAW_PENDING)) { | |
1701 | // Tk_DoWhenIdle(DisplayText, (ClientData) textPtr); | |
1702 | assert(textPtr->updateTimerToken == NULL); | |
1703 | if (textPtr->updateTimerToken == NULL) { | |
1704 | textPtr->updateTimerToken = | |
1705 | Tk_CreateTimerHandler( | |
1706 | TextUpdateTime, | |
1707 | DisplayText, | |
1708 | (ClientData) textPtr); | |
1709 | } | |
1710 | } | |
1711 | dInfoPtr->flags |= REDRAW_PENDING | DINFO_OUT_OF_DATE | REPICK_NEEDED; | |
1712 | } | |
1713 | \f | |
1714 | /* | |
1715 | *---------------------------------------------------------------------- | |
1716 | * | |
1717 | * TkTextRelayoutWindow -- | |
1718 | * | |
1719 | * This procedure is called when something has happened that | |
1720 | * invalidates the whole layout of characters on the screen, such | |
1721 | * as a change in a configuration option for the overall text | |
1722 | * widget or a change in the window size. It causes all display | |
1723 | * information to be recomputed and the window to be redrawn. | |
1724 | * | |
1725 | * Results: | |
1726 | * None. | |
1727 | * | |
1728 | * Side effects: | |
1729 | * All the display information will be recomputed for the window | |
1730 | * and the window will be redrawn. | |
1731 | * | |
1732 | *---------------------------------------------------------------------- | |
1733 | */ | |
1734 | ||
1735 | void | |
1736 | TkTextRelayoutWindow(textPtr) | |
1737 | TkText *textPtr; /* Widget record for text widget. */ | |
1738 | { | |
1739 | DInfo *dInfoPtr = textPtr->dInfoPtr; | |
1740 | ||
1741 | /* | |
1742 | * Throw away all the current layout information. | |
1743 | */ | |
1744 | ||
1745 | FreeDLines(textPtr, dInfoPtr->dLinePtr, (DLine *) NULL, 1); | |
1746 | dInfoPtr->dLinePtr = NULL; | |
1747 | ||
1748 | /* | |
1749 | * Recompute some overall things for the layout. | |
1750 | */ | |
1751 | ||
1752 | dInfoPtr->x = textPtr->borderWidth + textPtr->padX; | |
1753 | dInfoPtr->y = textPtr->borderWidth + textPtr->padY; | |
1754 | dInfoPtr->maxX = Tk_Width(textPtr->tkwin) - dInfoPtr->x; | |
1755 | dInfoPtr->maxY = Tk_Height(textPtr->tkwin) - dInfoPtr->y; | |
1756 | dInfoPtr->topOfEof = dInfoPtr->maxY; | |
1757 | ||
1758 | if (!(dInfoPtr->flags & REDRAW_PENDING)) { | |
1759 | // Tk_DoWhenIdle(DisplayText, (ClientData) textPtr); | |
1760 | assert(textPtr->updateTimerToken == NULL); | |
1761 | if (textPtr->updateTimerToken == NULL) { | |
1762 | textPtr->updateTimerToken = | |
1763 | Tk_CreateTimerHandler( | |
1764 | TextUpdateTime, | |
1765 | DisplayText, | |
1766 | (ClientData) textPtr); | |
1767 | } | |
1768 | } | |
1769 | dInfoPtr->flags |= REDRAW_PENDING | REDRAW_BORDERS | DINFO_OUT_OF_DATE | REPICK_NEEDED; | |
1770 | } | |
1771 | \f | |
1772 | /* | |
1773 | *---------------------------------------------------------------------- | |
1774 | * | |
1775 | * TkTextSetView -- | |
1776 | * | |
1777 | * This procedure is called to specify what lines are to be | |
1778 | * displayed in a text widget. | |
1779 | * | |
1780 | * Results: | |
1781 | * None. | |
1782 | * | |
1783 | * Side effects: | |
1784 | * The display will (eventually) be updated so that the line | |
1785 | * given by "line" is visible on the screen at the position | |
1786 | * determined by "pickPlace". | |
1787 | * | |
1788 | *---------------------------------------------------------------------- | |
1789 | */ | |
1790 | ||
1791 | void | |
1792 | TkTextSetView(textPtr, line, pickPlace) | |
1793 | TkText *textPtr; /* Widget record for text widget. */ | |
1794 | int line; /* Number of line that is to appear somewhere | |
1795 | * in the window. This line number must | |
1796 | * be a valid one in the file. */ | |
1797 | int pickPlace; /* 0 means topLine must appear at top of | |
1798 | * screen. 1 means we get to pick where it | |
1799 | * appears: minimize screen motion or else | |
1800 | * display line at center of screen. */ | |
1801 | { | |
1802 | DInfo *dInfoPtr = textPtr->dInfoPtr; | |
1803 | register DLine *dlPtr, *dlPtr2; | |
1804 | TkTextLine *linePtr; | |
1805 | int curTopLine, curBotLine; | |
1806 | int bottomY; | |
1807 | TagInfo tagInfo; | |
1808 | #define CLOSE_LINES 5 | |
1809 | ||
1810 | if (!pickPlace) { | |
1811 | /* | |
1812 | * The line must go at the top of the screen. See if the new | |
1813 | * topmost line is already somewhere on the screen. If so then | |
1814 | * delete all the DLine structures ahead of it. Otherwise just | |
1815 | * leave all the DLine's alone (if the new topmost line is above | |
1816 | * the top of the current window, i.e. we're scrolling back towards | |
1817 | * the beginning of the file we may be able to reuse some of the | |
1818 | * information that's currently on the screen without redisplaying | |
1819 | * it all. | |
1820 | */ | |
1821 | ||
1822 | dlPtr = FindDLine(dInfoPtr->dLinePtr, line); | |
1823 | if ((dlPtr != NULL) && (dlPtr != dInfoPtr->dLinePtr)) { | |
1824 | FreeDLines(textPtr, dInfoPtr->dLinePtr, dlPtr, 1); | |
1825 | } | |
1826 | ||
1827 | textPtr->topLinePtr = TkBTreeFindLine(textPtr->tree, line); | |
1828 | goto scheduleUpdate; | |
1829 | } | |
1830 | ||
1831 | /* | |
1832 | * We have to pick where to display the given line. First, bring | |
1833 | * the display information up to date and see if the line will be | |
1834 | * completely visible in the current screen configuration. If so | |
1835 | * then there's nothing to do. | |
1836 | */ | |
1837 | ||
1838 | if (dInfoPtr->flags & DINFO_OUT_OF_DATE) { | |
1839 | UpdateDisplayInfo(textPtr); | |
1840 | } | |
1841 | linePtr = TkBTreeFindLine(textPtr->tree, line); | |
1842 | /* DEH: return if dlPtr is null */ | |
1843 | if ((dlPtr = dInfoPtr->dLinePtr) == NULL) | |
1844 | return; | |
1845 | for (; ; dlPtr = dlPtr->nextPtr) { | |
1846 | if (dlPtr->nextPtr == NULL) { | |
1847 | break; | |
1848 | } | |
1849 | if ((dlPtr->linePtr == linePtr) | |
1850 | && (dlPtr->nextPtr->linePtr != linePtr)) { | |
1851 | break; | |
1852 | } | |
1853 | } | |
1854 | if ((dlPtr->linePtr == linePtr) | |
1855 | && ((dlPtr->y + dlPtr->height) <= dInfoPtr->maxY)) { | |
1856 | return; | |
1857 | } | |
1858 | ||
1859 | /* | |
1860 | * The desired line isn't already on-screen. See if it is within | |
1861 | * a few lines of the top of the window. If so then just make it | |
1862 | * the top line on the screen. | |
1863 | */ | |
1864 | ||
1865 | bottomY = (dInfoPtr->y + dInfoPtr->maxY)/2; | |
1866 | curTopLine = TkBTreeLineIndex(dInfoPtr->dLinePtr->linePtr); | |
1867 | if (line < curTopLine) { | |
1868 | if (line >= (curTopLine-CLOSE_LINES)) { | |
1869 | textPtr->topLinePtr = TkBTreeFindLine(textPtr->tree, line); | |
1870 | goto scheduleUpdate; | |
1871 | } | |
1872 | } else { | |
1873 | /* | |
1874 | * The desired line is below the bottom of the screen. If it is | |
1875 | * within a few lines of the bottom of the screen then position | |
1876 | * it at the bottom of the screen. (At this point dlPtr points to | |
1877 | * the last line on the screen) | |
1878 | */ | |
1879 | ||
1880 | curBotLine = TkBTreeLineIndex(dlPtr->linePtr); | |
1881 | if (line <= (curBotLine+5)) { | |
1882 | bottomY = dInfoPtr->maxY; | |
1883 | } | |
1884 | } | |
1885 | ||
1886 | /* | |
1887 | * Our job now is arrange the display so that "line" appears as | |
1888 | * low on the screen as possible but with its bottom no lower | |
1889 | * than bottomY (bottomY is the bottom of the window if the | |
1890 | * desired line is just below the current screen, otherwise it | |
1891 | * is the center of the window. Work upwards (through smaller | |
1892 | * line numbers) computing how much space lines take, until we | |
1893 | * fine the line that should be at the top of the screen. | |
1894 | */ | |
1895 | ||
1896 | for (textPtr->topLinePtr = linePtr = TkBTreeFindLine(textPtr->tree, line); | |
1897 | ; line--, textPtr->topLinePtr = linePtr, | |
1898 | linePtr = TkBTreeFindLine(textPtr->tree, line)) { | |
1899 | tagInfo.tagPtrs = TkBTreeGetTags(textPtr->tree, linePtr, 0, | |
1900 | &tagInfo.numTags); | |
1901 | tagInfo.arraySize = tagInfo.numTags; | |
1902 | TkBTreeStartSearch(textPtr->tree, line, 1, line+1, 0, | |
1903 | (TkTextTag *) NULL, &tagInfo.search); | |
1904 | TkBTreeNextTag(&tagInfo.search); | |
1905 | dlPtr = LayoutLine(textPtr, line, linePtr, &tagInfo); | |
1906 | for (dlPtr2 = dlPtr; dlPtr2 != NULL; dlPtr2 = dlPtr2->nextPtr) { | |
1907 | bottomY -= dlPtr2->height; | |
1908 | } | |
1909 | FreeDLines(textPtr, dlPtr, (DLine *) NULL, 0); | |
1910 | if (tagInfo.tagPtrs != NULL) { | |
1911 | ckfree((char *) tagInfo.tagPtrs); | |
1912 | } | |
1913 | if ((bottomY <= 0) || (line <= 0)) { | |
1914 | break; | |
1915 | } | |
1916 | } | |
1917 | ||
1918 | scheduleUpdate: | |
1919 | if (!(dInfoPtr->flags & REDRAW_PENDING)) { | |
1920 | // Tk_DoWhenIdle(DisplayText, (ClientData) textPtr); | |
1921 | assert(textPtr->updateTimerToken == NULL); | |
1922 | if (textPtr->updateTimerToken == NULL) { | |
1923 | textPtr->updateTimerToken = | |
1924 | Tk_CreateTimerHandler( | |
1925 | TextUpdateTime, | |
1926 | DisplayText, | |
1927 | (ClientData) textPtr); | |
1928 | } | |
1929 | } | |
1930 | dInfoPtr->flags |= REDRAW_PENDING | DINFO_OUT_OF_DATE | REPICK_NEEDED; | |
1931 | } | |
1932 | \f | |
1933 | /* | |
1934 | *---------------------------------------------------------------------- | |
1935 | * | |
1936 | * FindDLine -- | |
1937 | * | |
1938 | * This procedure is called to find the DLine corresponding to a | |
1939 | * given text line. | |
1940 | * | |
1941 | * Results: | |
1942 | * The return value is a pointer to the first DLine found in the | |
1943 | * list headed by dlPtr whose line number is greater or equal to | |
1944 | * line. If there is no such line in the list then NULL is returned. | |
1945 | * | |
1946 | * Side effects: | |
1947 | * None. | |
1948 | * | |
1949 | *---------------------------------------------------------------------- | |
1950 | */ | |
1951 | ||
1952 | static DLine * | |
1953 | FindDLine(dlPtr, line) | |
1954 | register DLine *dlPtr; /* Pointer to first in list of DLines | |
1955 | * to search. */ | |
1956 | int line; /* Line number in text that is desired. */ | |
1957 | { | |
1958 | TkTextLine *linePtr; | |
1959 | int thisLine; | |
1960 | ||
1961 | if (dlPtr == NULL) { | |
1962 | return NULL; | |
1963 | } | |
1964 | thisLine = TkBTreeLineIndex(dlPtr->linePtr); | |
1965 | while (thisLine < line) { | |
1966 | /* | |
1967 | * This DLine isn't the right one. Go on to the next DLine | |
1968 | * (skipping multiple DLine's for the same text line). | |
1969 | */ | |
1970 | ||
1971 | linePtr = dlPtr->linePtr; | |
1972 | do { | |
1973 | dlPtr = dlPtr->nextPtr; | |
1974 | if (dlPtr == NULL) { | |
1975 | return NULL; | |
1976 | } | |
1977 | } while (dlPtr->linePtr == linePtr); | |
1978 | ||
1979 | /* | |
1980 | * Step through text lines, keeping track of the line number | |
1981 | * we're on, until we catch up to dlPtr (remember, there could | |
1982 | * be gaps in the DLine list where DLine's have been deleted). | |
1983 | */ | |
1984 | ||
1985 | do { | |
1986 | linePtr = TkBTreeNextLine(linePtr); | |
1987 | thisLine++; | |
1988 | if (linePtr == NULL) { | |
1989 | panic("FindDLine reached end of text"); | |
1990 | } | |
1991 | } while (linePtr != dlPtr->linePtr); | |
1992 | } | |
1993 | return dlPtr; | |
1994 | } | |
1995 | \f | |
1996 | /* | |
1997 | *---------------------------------------------------------------------- | |
1998 | * | |
1999 | * TkTextCharAtLoc -- | |
2000 | * | |
2001 | * Given an (x,y) coordinate on the screen, find the location of | |
2002 | * the closest character to that location. | |
2003 | * | |
2004 | * Results: | |
2005 | * The return value is a pointer to the text line containing the | |
2006 | * character displayed closest to (x,y). The value at *chPtr is | |
2007 | * overwritten with the index with that line of the closest | |
2008 | * character. | |
2009 | * | |
2010 | * Side effects: | |
2011 | * None. | |
2012 | * | |
2013 | *---------------------------------------------------------------------- | |
2014 | */ | |
2015 | ||
2016 | TkTextLine * | |
2017 | TkTextCharAtLoc(textPtr, x, y, chPtr) | |
2018 | TkText *textPtr; /* Widget record for text widget. */ | |
2019 | int x, y; /* Pixel coordinates of point in widget's | |
2020 | * window. */ | |
2021 | int *chPtr; /* Place to store index-within-line of | |
2022 | * closest character. */ | |
2023 | { | |
2024 | DInfo *dInfoPtr = textPtr->dInfoPtr; | |
2025 | register DLine *dlPtr; | |
2026 | register Chunk *chunkPtr; | |
2027 | int count; | |
2028 | int endX; | |
2029 | ||
2030 | /* | |
2031 | * Make sure that all of the layout information about what's | |
2032 | * displayed where on the screen is up-to-date. | |
2033 | */ | |
2034 | ||
2035 | if (dInfoPtr->flags & DINFO_OUT_OF_DATE) { | |
2036 | UpdateDisplayInfo(textPtr); | |
2037 | } | |
2038 | ||
2039 | /* | |
2040 | * If the coordinates are above the top of the window, then adjust | |
2041 | * them to refer to the upper-right corner of the window. | |
2042 | */ | |
2043 | ||
2044 | if (y < dInfoPtr->y) { | |
2045 | y = dInfoPtr->y; | |
2046 | x = dInfoPtr->x; | |
2047 | } else if (y >= dInfoPtr->topOfEof) { | |
2048 | y = dInfoPtr->topOfEof; | |
2049 | x = dInfoPtr->maxX; | |
2050 | } | |
2051 | for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) { | |
2052 | if (y > (dlPtr->y + dlPtr->height)) { | |
2053 | if (dlPtr->nextPtr != NULL) { | |
2054 | continue; | |
2055 | } | |
2056 | ||
2057 | /* | |
2058 | * The coordinates are off the bottom of the window. Adjust | |
2059 | * them to refer to the lower-right character on the window. | |
2060 | */ | |
2061 | ||
2062 | y = dlPtr->y; | |
2063 | x = dInfoPtr->maxX; | |
2064 | } | |
2065 | for (chunkPtr = dlPtr->chunkPtr; ; chunkPtr = chunkPtr->nextPtr) { | |
2066 | if ((chunkPtr->nextPtr == NULL) || (chunkPtr->nextPtr->x > x)) { | |
2067 | break; | |
2068 | } | |
2069 | } | |
2070 | count = TkMeasureChars(chunkPtr->stylePtr->sValuePtr->fontPtr, | |
2071 | chunkPtr->text, chunkPtr->numChars, chunkPtr->x, x, 0, &endX); | |
2072 | if (count >= chunkPtr->numChars) { | |
2073 | /* | |
2074 | * The point is off the end of the line. Return the character | |
2075 | * after the last one that fit, unless that character appears | |
2076 | * as the first character on the next DLine or unless the last | |
2077 | * one that fit extends beyond the edge of the window. | |
2078 | */ | |
2079 | ||
2080 | if ((dlPtr->nextPtr != NULL) | |
2081 | && (dlPtr->nextPtr->chunkPtr->text | |
2082 | == (chunkPtr->text + chunkPtr->numChars))) { | |
2083 | count = chunkPtr->numChars-1; | |
2084 | } | |
2085 | if (endX >= dInfoPtr->maxX) { | |
2086 | count = chunkPtr->numChars-1; | |
2087 | } | |
2088 | } | |
2089 | *chPtr = count + (chunkPtr->text - dlPtr->linePtr->bytes); | |
2090 | return dlPtr->linePtr; | |
2091 | } | |
2092 | panic("TkTextCharAtLoc ran out of lines"); | |
2093 | return (TkTextLine *) NULL; | |
2094 | } |