]> cvs.zerfleddert.de Git - micropolis/blob - src/tk/tktxdisp.c
Optimize redrawing so that the mouse is more responsive, from Marc Espie.
[micropolis] / src / tk / tktxdisp.c
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 }
Impressum, Datenschutz