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 (c) 1992-1994 The Regents of the University of California. 9 * Copyright (c) 1994-1997 Sun Microsystems, Inc. 10 * 11 * See the file "license.terms" for information on usage and redistribution 12 * of this file, and for a DISCLAIMER OF ALL WARRANTIES. 13 * 14 * RCS: @(#) $Id: tkTextDisp.c,v 1.14.2.5 2007/04/29 02:24:02 das Exp $ 15 */ 16 17#include "tkPort.h" 18#include "tkInt.h" 19#include "tkText.h" 20 21#ifdef __WIN32__ 22#include "tkWinInt.h" 23#endif 24 25#ifdef TK_NO_DOUBLE_BUFFERING 26#ifdef MAC_OSX_TK 27#include "tkMacOSXInt.h" 28#endif 29#endif /* TK_NO_DOUBLE_BUFFERING */ 30 31/* 32 * The following structure describes how to display a range of characters. 33 * The information is generated by scanning all of the tags associated 34 * with the characters and combining that with default information for 35 * the overall widget. These structures form the hash keys for 36 * dInfoPtr->styleTable. 37 */ 38 39typedef struct StyleValues { 40 Tk_3DBorder border; /* Used for drawing background under text. 41 * NULL means use widget background. */ 42 int borderWidth; /* Width of 3-D border for background. */ 43 int relief; /* 3-D relief for background. */ 44 Pixmap bgStipple; /* Stipple bitmap for background. None 45 * means draw solid. */ 46 XColor *fgColor; /* Foreground color for text. */ 47 Tk_Font tkfont; /* Font for displaying text. */ 48 Pixmap fgStipple; /* Stipple bitmap for text and other 49 * foreground stuff. None means draw 50 * solid.*/ 51 int justify; /* Justification style for text. */ 52 int lMargin1; /* Left margin, in pixels, for first display 53 * line of each text line. */ 54 int lMargin2; /* Left margin, in pixels, for second and 55 * later display lines of each text line. */ 56 int offset; /* Offset in pixels of baseline, relative to 57 * baseline of line. */ 58 int overstrike; /* Non-zero means draw overstrike through 59 * text. */ 60 int rMargin; /* Right margin, in pixels. */ 61 int spacing1; /* Spacing above first dline in text line. */ 62 int spacing2; /* Spacing between lines of dline. */ 63 int spacing3; /* Spacing below last dline in text line. */ 64 TkTextTabArray *tabArrayPtr;/* Locations and types of tab stops (may 65 * be NULL). */ 66 int underline; /* Non-zero means draw underline underneath 67 * text. */ 68 int elide; /* Non-zero means draw text */ 69 TkWrapMode wrapMode; /* How to handle wrap-around for this tag. 70 * One of TEXT_WRAPMODE_CHAR, 71 * TEXT_WRAPMODE_NONE or TEXT_WRAPMODE_WORD.*/ 72} StyleValues; 73 74/* 75 * The following structure extends the StyleValues structure above with 76 * graphics contexts used to actually draw the characters. The entries 77 * in dInfoPtr->styleTable point to structures of this type. 78 */ 79 80typedef struct TextStyle { 81 int refCount; /* Number of times this structure is 82 * referenced in Chunks. */ 83 GC bgGC; /* Graphics context for background. None 84 * means use widget background. */ 85 GC fgGC; /* Graphics context for foreground. */ 86 StyleValues *sValuePtr; /* Raw information from which GCs were 87 * derived. */ 88 Tcl_HashEntry *hPtr; /* Pointer to entry in styleTable. Used 89 * to delete entry. */ 90} TextStyle; 91 92/* 93 * The following macro determines whether two styles have the same 94 * background so that, for example, no beveled border should be drawn 95 * between them. 96 */ 97 98#define SAME_BACKGROUND(s1, s2) \ 99 (((s1)->sValuePtr->border == (s2)->sValuePtr->border) \ 100 && ((s1)->sValuePtr->borderWidth == (s2)->sValuePtr->borderWidth) \ 101 && ((s1)->sValuePtr->relief == (s2)->sValuePtr->relief) \ 102 && ((s1)->sValuePtr->bgStipple == (s2)->sValuePtr->bgStipple)) 103 104/* 105 * The following macro is used to compare two floating-point numbers 106 * to within a certain degree of scale. Direct comparison fails on 107 * processors where the processor and memory representations of FP 108 * numbers of a particular precision is different (e.g. Intel) 109 */ 110 111#define FP_EQUAL_SCALE(double1, double2, scaleFactor) \ 112 (fabs((double1)-(double2))*((scaleFactor)+1.0) < 0.3) 113 114/* 115 * The following structure describes one line of the display, which may 116 * be either part or all of one line of the text. 117 */ 118 119typedef struct DLine { 120 TkTextIndex index; /* Identifies first character in text 121 * that is displayed on this line. */ 122 int byteCount; /* Number of bytes accounted for by this 123 * display line, including a trailing space 124 * or newline that isn't actually displayed. */ 125 int y; /* Y-position at which line is supposed to 126 * be drawn (topmost pixel of rectangular 127 * area occupied by line). */ 128 int oldY; /* Y-position at which line currently 129 * appears on display. -1 means line isn't 130 * currently visible on display and must be 131 * redrawn. This is used to move lines by 132 * scrolling rather than re-drawing. */ 133 int height; /* Height of line, in pixels. */ 134 int baseline; /* Offset of text baseline from y, in 135 * pixels. */ 136 int spaceAbove; /* How much extra space was added to the 137 * top of the line because of spacing 138 * options. This is included in height 139 * and baseline. */ 140 int spaceBelow; /* How much extra space was added to the 141 * bottom of the line because of spacing 142 * options. This is included in height. */ 143 int length; /* Total length of line, in pixels. */ 144 TkTextDispChunk *chunkPtr; /* Pointer to first chunk in list of all 145 * of those that are displayed on this 146 * line of the screen. */ 147 struct DLine *nextPtr; /* Next in list of all display lines for 148 * this window. The list is sorted in 149 * order from top to bottom. Note: the 150 * next DLine doesn't always correspond 151 * to the next line of text: (a) can have 152 * multiple DLines for one text line, and 153 * (b) can have gaps where DLine's have been 154 * deleted because they're out of date. */ 155 int flags; /* Various flag bits: see below for values. */ 156} DLine; 157 158/* 159 * Flag bits for DLine structures: 160 * 161 * HAS_3D_BORDER - Non-zero means that at least one of the 162 * chunks in this line has a 3D border, so 163 * it potentially interacts with 3D borders 164 * in neighboring lines (see 165 * DisplayLineBackground). 166 * NEW_LAYOUT - Non-zero means that the line has been 167 * re-layed out since the last time the 168 * display was updated. 169 * TOP_LINE - Non-zero means that this was the top line 170 * in the window the last time that the window 171 * was laid out. This is important because 172 * a line may be displayed differently if its 173 * at the top or bottom than if it's in the 174 * middle (e.g. beveled edges aren't displayed 175 * for middle lines if the adjacent line has 176 * a similar background). 177 * BOTTOM_LINE - Non-zero means that this was the bottom line 178 * in the window the last time that the window 179 * was laid out. 180 * IS_DISABLED - This Dline cannot be edited. 181 */ 182 183#define HAS_3D_BORDER 1 184#define NEW_LAYOUT 2 185#define TOP_LINE 4 186#define BOTTOM_LINE 8 187#define IS_DISABLED 16 188 189/* 190 * Overall display information for a text widget: 191 */ 192 193typedef struct TextDInfo { 194 Tcl_HashTable styleTable; /* Hash table that maps from StyleValues 195 * to TextStyles for this widget. */ 196 DLine *dLinePtr; /* First in list of all display lines for 197 * this widget, in order from top to bottom. */ 198 GC copyGC; /* Graphics context for copying from off- 199 * screen pixmaps onto screen. */ 200 GC scrollGC; /* Graphics context for copying from one place 201 * in the window to another (scrolling): 202 * differs from copyGC in that we need to get 203 * GraphicsExpose events. */ 204 int x; /* First x-coordinate that may be used for 205 * actually displaying line information. 206 * Leaves space for border, etc. */ 207 int y; /* First y-coordinate that may be used for 208 * actually displaying line information. 209 * Leaves space for border, etc. */ 210 int maxX; /* First x-coordinate to right of available 211 * space for displaying lines. */ 212 int maxY; /* First y-coordinate below available 213 * space for displaying lines. */ 214 int topOfEof; /* Top-most pixel (lowest y-value) that has 215 * been drawn in the appropriate fashion for 216 * the portion of the window after the last 217 * line of the text. This field is used to 218 * figure out when to redraw part or all of 219 * the eof field. */ 220 221 /* 222 * Information used for scrolling: 223 */ 224 225 int newByteOffset; /* Desired x scroll position, measured as the 226 * number of average-size characters off-screen 227 * to the left for a line with no left 228 * margin. */ 229 int curPixelOffset; /* Actual x scroll position, measured as the 230 * number of pixels off-screen to the left. */ 231 int maxLength; /* Length in pixels of longest line that's 232 * visible in window (length may exceed window 233 * size). If there's no wrapping, this will 234 * be zero. */ 235 double xScrollFirst, xScrollLast; 236 /* Most recent values reported to horizontal 237 * scrollbar; used to eliminate unnecessary 238 * reports. */ 239 double yScrollFirst, yScrollLast; 240 /* Most recent values reported to vertical 241 * scrollbar; used to eliminate unnecessary 242 * reports. */ 243 244 /* 245 * The following information is used to implement scanning: 246 */ 247 248 int scanMarkIndex; /* Byte index of character that was at the 249 * left edge of the window when the scan 250 * started. */ 251 int scanMarkX; /* X-position of mouse at time scan started. */ 252 int scanTotalScroll; /* Total scrolling (in screen lines) that has 253 * occurred since scanMarkY was set. */ 254 int scanMarkY; /* Y-position of mouse at time scan started. */ 255 256 /* 257 * Miscellaneous information: 258 */ 259 260 int dLinesInvalidated; /* This value is set to 1 whenever something 261 * happens that invalidates information in 262 * DLine structures; if a redisplay 263 * is in progress, it will see this and 264 * abort the redisplay. This is needed 265 * because, for example, an embedded window 266 * could change its size when it is first 267 * displayed, invalidating the DLine that 268 * is currently being displayed. If redisplay 269 * continues, it will use freed memory and 270 * could dump core. */ 271 int flags; /* Various flag values: see below for 272 * definitions. */ 273} TextDInfo; 274 275/* 276 * In TkTextDispChunk structures for character segments, the clientData 277 * field points to one of the following structures: 278 */ 279 280typedef struct CharInfo { 281 int numBytes; /* Number of bytes to display. */ 282 char chars[4]; /* UTF characters to display. Actual size 283 * will be numBytes, not 4. THIS MUST BE 284 * THE LAST FIELD IN THE STRUCTURE. */ 285} CharInfo; 286 287/* 288 * Flag values for TextDInfo structures: 289 * 290 * DINFO_OUT_OF_DATE: Non-zero means that the DLine structures 291 * for this window are partially or completely 292 * out of date and need to be recomputed. 293 * REDRAW_PENDING: Means that a when-idle handler has been 294 * scheduled to update the display. 295 * REDRAW_BORDERS: Means window border or pad area has 296 * potentially been damaged and must be redrawn. 297 * REPICK_NEEDED: 1 means that the widget has been modified 298 * in a way that could change the current 299 * character (a different character might be 300 * under the mouse cursor now). Need to 301 * recompute the current character before 302 * the next redisplay. 303 */ 304 305#define DINFO_OUT_OF_DATE 1 306#define REDRAW_PENDING 2 307#define REDRAW_BORDERS 4 308#define REPICK_NEEDED 8 309 310/* 311 * The following counters keep statistics about redisplay that can be 312 * checked to see how clever this code is at reducing redisplays. 313 */ 314 315static int numRedisplays; /* Number of calls to DisplayText. */ 316static int linesRedrawn; /* Number of calls to DisplayDLine. */ 317static int numCopies; /* Number of calls to XCopyArea to copy part 318 * of the screen. */ 319 320/* 321 * Forward declarations for procedures defined later in this file: 322 */ 323 324static void AdjustForTab _ANSI_ARGS_((TkText *textPtr, 325 TkTextTabArray *tabArrayPtr, int index, 326 TkTextDispChunk *chunkPtr)); 327static void CharBboxProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr, 328 int index, int y, int lineHeight, int baseline, 329 int *xPtr, int *yPtr, int *widthPtr, 330 int *heightPtr)); 331static void CharDisplayProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr, 332 int x, int y, int height, int baseline, 333 Display *display, Drawable dst, int screenY)); 334static int CharMeasureProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr, 335 int x)); 336static void CharUndisplayProc _ANSI_ARGS_((TkText *textPtr, 337 TkTextDispChunk *chunkPtr)); 338 339/* 340 Definitions of elided procs. 341 Compiler can't inline these since we use pointers to these functions. 342 ElideDisplayProc, ElideUndisplayProc special-cased for speed, 343 as potentially many elided DLine chunks if large, tag toggle-filled 344 elided region. 345*/ 346static void ElideBboxProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr, 347 int index, int y, int lineHeight, int baseline, 348 int *xPtr, int *yPtr, int *widthPtr, 349 int *heightPtr)); 350static int ElideMeasureProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr, 351 int x)); 352 353static void DisplayDLine _ANSI_ARGS_((TkText *textPtr, 354 DLine *dlPtr, DLine *prevPtr, Pixmap pixmap)); 355static void DisplayLineBackground _ANSI_ARGS_((TkText *textPtr, 356 DLine *dlPtr, DLine *prevPtr, Pixmap pixmap)); 357static void DisplayText _ANSI_ARGS_((ClientData clientData)); 358static DLine * FindDLine _ANSI_ARGS_((DLine *dlPtr, 359 TkTextIndex *indexPtr)); 360static void FreeDLines _ANSI_ARGS_((TkText *textPtr, 361 DLine *firstPtr, DLine *lastPtr, int unlink)); 362static void FreeStyle _ANSI_ARGS_((TkText *textPtr, 363 TextStyle *stylePtr)); 364static TextStyle * GetStyle _ANSI_ARGS_((TkText *textPtr, 365 TkTextIndex *indexPtr)); 366static void GetXView _ANSI_ARGS_((Tcl_Interp *interp, 367 TkText *textPtr, int report)); 368static void GetYView _ANSI_ARGS_((Tcl_Interp *interp, 369 TkText *textPtr, int report)); 370static DLine * LayoutDLine _ANSI_ARGS_((TkText *textPtr, 371 TkTextIndex *indexPtr)); 372static int MeasureChars _ANSI_ARGS_((Tk_Font tkfont, 373 CONST char *source, int maxBytes, int startX, 374 int maxX, int tabOrigin, int *nextXPtr)); 375static void MeasureUp _ANSI_ARGS_((TkText *textPtr, 376 TkTextIndex *srcPtr, int distance, 377 TkTextIndex *dstPtr)); 378static int NextTabStop _ANSI_ARGS_((Tk_Font tkfont, int x, 379 int tabOrigin)); 380static void UpdateDisplayInfo _ANSI_ARGS_((TkText *textPtr)); 381static void ScrollByLines _ANSI_ARGS_((TkText *textPtr, 382 int offset)); 383static int SizeOfTab _ANSI_ARGS_((TkText *textPtr, 384 TkTextTabArray *tabArrayPtr, int index, int x, 385 int maxX)); 386static void TextInvalidateRegion _ANSI_ARGS_((TkText *textPtr, 387 TkRegion region)); 388 389 390/* 391 *---------------------------------------------------------------------- 392 * 393 * TkTextCreateDInfo -- 394 * 395 * This procedure is called when a new text widget is created. 396 * Its job is to set up display-related information for the widget. 397 * 398 * Results: 399 * None. 400 * 401 * Side effects: 402 * A TextDInfo data structure is allocated and initialized and attached 403 * to textPtr. 404 * 405 *---------------------------------------------------------------------- 406 */ 407 408void 409TkTextCreateDInfo(textPtr) 410 TkText *textPtr; /* Overall information for text widget. */ 411{ 412 register TextDInfo *dInfoPtr; 413 XGCValues gcValues; 414 415 dInfoPtr = (TextDInfo *) ckalloc(sizeof(TextDInfo)); 416 Tcl_InitHashTable(&dInfoPtr->styleTable, sizeof(StyleValues)/sizeof(int)); 417 dInfoPtr->dLinePtr = NULL; 418 dInfoPtr->copyGC = None; 419 gcValues.graphics_exposures = True; 420 dInfoPtr->scrollGC = Tk_GetGC(textPtr->tkwin, GCGraphicsExposures, 421 &gcValues); 422 dInfoPtr->topOfEof = 0; 423 dInfoPtr->newByteOffset = 0; 424 dInfoPtr->curPixelOffset = 0; 425 dInfoPtr->maxLength = 0; 426 dInfoPtr->xScrollFirst = -1; 427 dInfoPtr->xScrollLast = -1; 428 dInfoPtr->yScrollFirst = -1; 429 dInfoPtr->yScrollLast = -1; 430 dInfoPtr->scanMarkIndex = 0; 431 dInfoPtr->scanMarkX = 0; 432 dInfoPtr->scanTotalScroll = 0; 433 dInfoPtr->scanMarkY = 0; 434 dInfoPtr->dLinesInvalidated = 0; 435 dInfoPtr->flags = DINFO_OUT_OF_DATE; 436 textPtr->dInfoPtr = dInfoPtr; 437} 438 439/* 440 *---------------------------------------------------------------------- 441 * 442 * TkTextFreeDInfo -- 443 * 444 * This procedure is called to free up all of the private display 445 * information kept by this file for a text widget. 446 * 447 * Results: 448 * None. 449 * 450 * Side effects: 451 * Lots of resources get freed. 452 * 453 *---------------------------------------------------------------------- 454 */ 455 456void 457TkTextFreeDInfo(textPtr) 458 TkText *textPtr; /* Overall information for text widget. */ 459{ 460 register TextDInfo *dInfoPtr = textPtr->dInfoPtr; 461 462 /* 463 * Be careful to free up styleTable *after* freeing up all the 464 * DLines, so that the hash table is still intact to free up the 465 * style-related information from the lines. Once the lines are 466 * all free then styleTable will be empty. 467 */ 468 469 FreeDLines(textPtr, dInfoPtr->dLinePtr, (DLine *) NULL, 1); 470 Tcl_DeleteHashTable(&dInfoPtr->styleTable); 471 if (dInfoPtr->copyGC != None) { 472 Tk_FreeGC(textPtr->display, dInfoPtr->copyGC); 473 } 474 Tk_FreeGC(textPtr->display, dInfoPtr->scrollGC); 475 if (dInfoPtr->flags & REDRAW_PENDING) { 476 Tcl_CancelIdleCall(DisplayText, (ClientData) textPtr); 477 } 478 ckfree((char *) dInfoPtr); 479} 480 481/* 482 *---------------------------------------------------------------------- 483 * 484 * GetStyle -- 485 * 486 * This procedure creates all the information needed to display 487 * text at a particular location. 488 * 489 * Results: 490 * The return value is a pointer to a TextStyle structure that 491 * corresponds to *sValuePtr. 492 * 493 * Side effects: 494 * A new entry may be created in the style table for the widget. 495 * 496 *---------------------------------------------------------------------- 497 */ 498 499static TextStyle * 500GetStyle(textPtr, indexPtr) 501 TkText *textPtr; /* Overall information about text widget. */ 502 TkTextIndex *indexPtr; /* The character in the text for which 503 * display information is wanted. */ 504{ 505 TkTextTag **tagPtrs; 506 register TkTextTag *tagPtr; 507 StyleValues styleValues; 508 TextStyle *stylePtr; 509 Tcl_HashEntry *hPtr; 510 int numTags, new, i; 511 XGCValues gcValues; 512 unsigned long mask; 513 514 /* 515 * The variables below keep track of the highest-priority specification 516 * that has occurred for each of the various fields of the StyleValues. 517 */ 518 519 int borderPrio, borderWidthPrio, reliefPrio, bgStipplePrio; 520 int fgPrio, fontPrio, fgStipplePrio; 521 int underlinePrio, elidePrio, justifyPrio, offsetPrio; 522 int lMargin1Prio, lMargin2Prio, rMarginPrio; 523 int spacing1Prio, spacing2Prio, spacing3Prio; 524 int overstrikePrio, tabPrio, wrapPrio; 525 526 /* 527 * Find out what tags are present for the character, then compute 528 * a StyleValues structure corresponding to those tags (scan 529 * through all of the tags, saving information for the highest- 530 * priority tag). 531 */ 532 533 tagPtrs = TkBTreeGetTags(indexPtr, &numTags); 534 borderPrio = borderWidthPrio = reliefPrio = bgStipplePrio = -1; 535 fgPrio = fontPrio = fgStipplePrio = -1; 536 underlinePrio = elidePrio = justifyPrio = offsetPrio = -1; 537 lMargin1Prio = lMargin2Prio = rMarginPrio = -1; 538 spacing1Prio = spacing2Prio = spacing3Prio = -1; 539 overstrikePrio = tabPrio = wrapPrio = -1; 540 memset((VOID *) &styleValues, 0, sizeof(StyleValues)); 541 styleValues.relief = TK_RELIEF_FLAT; 542 styleValues.fgColor = textPtr->fgColor; 543 styleValues.tkfont = textPtr->tkfont; 544 styleValues.justify = TK_JUSTIFY_LEFT; 545 styleValues.spacing1 = textPtr->spacing1; 546 styleValues.spacing2 = textPtr->spacing2; 547 styleValues.spacing3 = textPtr->spacing3; 548 styleValues.tabArrayPtr = textPtr->tabArrayPtr; 549 styleValues.wrapMode = textPtr->wrapMode; 550 styleValues.elide = 0; 551 for (i = 0 ; i < numTags; i++) { 552 tagPtr = tagPtrs[i]; 553 554 /* 555 * Skip the selection tag if we don't have focus, 556 * unless we always want to show the selection. 557 */ 558 559 if ( 560#ifndef MAC_OSX_TK 561 !TkpAlwaysShowSelection(textPtr->tkwin) 562#else 563 /* Don't show inactive selection in disabled widgets. */ 564 textPtr->state == TK_STATE_DISABLED 565#endif 566 && (tagPtr == textPtr->selTagPtr) 567 && !(textPtr->flags & GOT_FOCUS)) { 568 continue; 569 } 570 571 if ((tagPtr->border != NULL) && (tagPtr->priority > borderPrio)) { 572 styleValues.border = tagPtr->border; 573 borderPrio = tagPtr->priority; 574 } 575 if ((tagPtr->bdString != NULL) 576 && (tagPtr->priority > borderWidthPrio)) { 577 styleValues.borderWidth = tagPtr->borderWidth; 578 borderWidthPrio = tagPtr->priority; 579 } 580 if ((tagPtr->reliefString != NULL) 581 && (tagPtr->priority > reliefPrio)) { 582 if (styleValues.border == NULL) { 583 styleValues.border = textPtr->border; 584 } 585 styleValues.relief = tagPtr->relief; 586 reliefPrio = tagPtr->priority; 587 } 588 if ((tagPtr->bgStipple != None) 589 && (tagPtr->priority > bgStipplePrio)) { 590 styleValues.bgStipple = tagPtr->bgStipple; 591 bgStipplePrio = tagPtr->priority; 592 } 593 if ((tagPtr->fgColor != None) && (tagPtr->priority > fgPrio)) { 594 styleValues.fgColor = tagPtr->fgColor; 595 fgPrio = tagPtr->priority; 596 } 597 if ((tagPtr->tkfont != None) && (tagPtr->priority > fontPrio)) { 598 styleValues.tkfont = tagPtr->tkfont; 599 fontPrio = tagPtr->priority; 600 } 601 if ((tagPtr->fgStipple != None) 602 && (tagPtr->priority > fgStipplePrio)) { 603 styleValues.fgStipple = tagPtr->fgStipple; 604 fgStipplePrio = tagPtr->priority; 605 } 606 if ((tagPtr->justifyString != NULL) 607 && (tagPtr->priority > justifyPrio)) { 608 styleValues.justify = tagPtr->justify; 609 justifyPrio = tagPtr->priority; 610 } 611 if ((tagPtr->lMargin1String != NULL) 612 && (tagPtr->priority > lMargin1Prio)) { 613 styleValues.lMargin1 = tagPtr->lMargin1; 614 lMargin1Prio = tagPtr->priority; 615 } 616 if ((tagPtr->lMargin2String != NULL) 617 && (tagPtr->priority > lMargin2Prio)) { 618 styleValues.lMargin2 = tagPtr->lMargin2; 619 lMargin2Prio = tagPtr->priority; 620 } 621 if ((tagPtr->offsetString != NULL) 622 && (tagPtr->priority > offsetPrio)) { 623 styleValues.offset = tagPtr->offset; 624 offsetPrio = tagPtr->priority; 625 } 626 if ((tagPtr->overstrikeString != NULL) 627 && (tagPtr->priority > overstrikePrio)) { 628 styleValues.overstrike = tagPtr->overstrike; 629 overstrikePrio = tagPtr->priority; 630 } 631 if ((tagPtr->rMarginString != NULL) 632 && (tagPtr->priority > rMarginPrio)) { 633 styleValues.rMargin = tagPtr->rMargin; 634 rMarginPrio = tagPtr->priority; 635 } 636 if ((tagPtr->spacing1String != NULL) 637 && (tagPtr->priority > spacing1Prio)) { 638 styleValues.spacing1 = tagPtr->spacing1; 639 spacing1Prio = tagPtr->priority; 640 } 641 if ((tagPtr->spacing2String != NULL) 642 && (tagPtr->priority > spacing2Prio)) { 643 styleValues.spacing2 = tagPtr->spacing2; 644 spacing2Prio = tagPtr->priority; 645 } 646 if ((tagPtr->spacing3String != NULL) 647 && (tagPtr->priority > spacing3Prio)) { 648 styleValues.spacing3 = tagPtr->spacing3; 649 spacing3Prio = tagPtr->priority; 650 } 651 if ((tagPtr->tabString != NULL) 652 && (tagPtr->priority > tabPrio)) { 653 styleValues.tabArrayPtr = tagPtr->tabArrayPtr; 654 tabPrio = tagPtr->priority; 655 } 656 if ((tagPtr->underlineString != NULL) 657 && (tagPtr->priority > underlinePrio)) { 658 styleValues.underline = tagPtr->underline; 659 underlinePrio = tagPtr->priority; 660 } 661 if ((tagPtr->elideString != NULL) 662 && (tagPtr->priority > elidePrio)) { 663 styleValues.elide = tagPtr->elide; 664 elidePrio = tagPtr->priority; 665 } 666 if ((tagPtr->wrapMode != TEXT_WRAPMODE_NULL) 667 && (tagPtr->priority > wrapPrio)) { 668 styleValues.wrapMode = tagPtr->wrapMode; 669 wrapPrio = tagPtr->priority; 670 } 671 } 672 if (tagPtrs != NULL) { 673 ckfree((char *) tagPtrs); 674 } 675 676 /* 677 * Use an existing style if there's one around that matches. 678 */ 679 680 hPtr = Tcl_CreateHashEntry(&textPtr->dInfoPtr->styleTable, 681 (char *) &styleValues, &new); 682 if (!new) { 683 stylePtr = (TextStyle *) Tcl_GetHashValue(hPtr); 684 stylePtr->refCount++; 685 return stylePtr; 686 } 687 688 /* 689 * No existing style matched. Make a new one. 690 */ 691 692 stylePtr = (TextStyle *) ckalloc(sizeof(TextStyle)); 693 stylePtr->refCount = 1; 694 if (styleValues.border != NULL) { 695 gcValues.foreground = Tk_3DBorderColor(styleValues.border)->pixel; 696 mask = GCForeground; 697 if (styleValues.bgStipple != None) { 698 gcValues.stipple = styleValues.bgStipple; 699 gcValues.fill_style = FillStippled; 700 mask |= GCStipple|GCFillStyle; 701 } 702 stylePtr->bgGC = Tk_GetGC(textPtr->tkwin, mask, &gcValues); 703 } else { 704 stylePtr->bgGC = None; 705 } 706 mask = GCFont; 707 gcValues.font = Tk_FontId(styleValues.tkfont); 708 mask |= GCForeground; 709 gcValues.foreground = styleValues.fgColor->pixel; 710 if (styleValues.fgStipple != None) { 711 gcValues.stipple = styleValues.fgStipple; 712 gcValues.fill_style = FillStippled; 713 mask |= GCStipple|GCFillStyle; 714 } 715 stylePtr->fgGC = Tk_GetGC(textPtr->tkwin, mask, &gcValues); 716 stylePtr->sValuePtr = (StyleValues *) 717 Tcl_GetHashKey(&textPtr->dInfoPtr->styleTable, hPtr); 718 stylePtr->hPtr = hPtr; 719 Tcl_SetHashValue(hPtr, stylePtr); 720 return stylePtr; 721} 722 723/* 724 *---------------------------------------------------------------------- 725 * 726 * FreeStyle -- 727 * 728 * This procedure is called when a TextStyle structure is no longer 729 * needed. It decrements the reference count and frees up the 730 * space for the style structure if the reference count is 0. 731 * 732 * Results: 733 * None. 734 * 735 * Side effects: 736 * The storage and other resources associated with the style 737 * are freed up if no-one's still using it. 738 * 739 *---------------------------------------------------------------------- 740 */ 741 742static void 743FreeStyle(textPtr, stylePtr) 744 TkText *textPtr; /* Information about overall widget. */ 745 register TextStyle *stylePtr; /* Information about style to free. */ 746 747{ 748 stylePtr->refCount--; 749 if (stylePtr->refCount == 0) { 750 if (stylePtr->bgGC != None) { 751 Tk_FreeGC(textPtr->display, stylePtr->bgGC); 752 } 753 if (stylePtr->fgGC != None) { 754 Tk_FreeGC(textPtr->display, stylePtr->fgGC); 755 } 756 Tcl_DeleteHashEntry(stylePtr->hPtr); 757 ckfree((char *) stylePtr); 758 } 759} 760 761/* 762 *---------------------------------------------------------------------- 763 * 764 * LayoutDLine -- 765 * 766 * This procedure generates a single DLine structure for a display 767 * line whose leftmost character is given by indexPtr. 768 * 769 * Results: 770 * The return value is a pointer to a DLine structure desribing the 771 * display line. All fields are filled in and correct except for 772 * y and nextPtr. 773 * 774 * Side effects: 775 * Storage is allocated for the new DLine. 776 * 777 *---------------------------------------------------------------------- 778 */ 779 780static DLine * 781LayoutDLine(textPtr, indexPtr) 782 TkText *textPtr; /* Overall information about text widget. */ 783 TkTextIndex *indexPtr; /* Beginning of display line. May not 784 * necessarily point to a character segment. */ 785{ 786 register DLine *dlPtr; /* New display line. */ 787 TkTextSegment *segPtr; /* Current segment in text. */ 788 TkTextDispChunk *lastChunkPtr; /* Last chunk allocated so far 789 * for line. */ 790 TkTextDispChunk *chunkPtr; /* Current chunk. */ 791 TkTextIndex curIndex; 792 TkTextDispChunk *breakChunkPtr; /* Chunk containing best word break 793 * point, if any. */ 794 TkTextIndex breakIndex; /* Index of first character in 795 * breakChunkPtr. */ 796 int breakByteOffset; /* Byte offset of character within 797 * breakChunkPtr just to right of best 798 * break point. */ 799 int noCharsYet; /* Non-zero means that no characters 800 * have been placed on the line yet. */ 801 int justify; /* How to justify line: taken from 802 * style for the first character in 803 * line. */ 804 int jIndent; /* Additional indentation (beyond 805 * margins) due to justification. */ 806 int rMargin; /* Right margin width for line. */ 807 TkWrapMode wrapMode; /* Wrap mode to use for this line. */ 808 int x = 0, maxX = 0; /* Initializations needed only to 809 * stop compiler warnings. */ 810 int wholeLine; /* Non-zero means this display line 811 * runs to the end of the text line. */ 812 int tabIndex; /* Index of the current tab stop. */ 813 int gotTab; /* Non-zero means the current chunk 814 * contains a tab. */ 815 TkTextDispChunk *tabChunkPtr; /* Pointer to the chunk containing 816 * the previous tab stop. */ 817 int maxBytes; /* Maximum number of bytes to 818 * include in this chunk. */ 819 TkTextTabArray *tabArrayPtr; /* Tab stops for line; taken from 820 * style for the first character on 821 * line. */ 822 int tabSize; /* Number of pixels consumed by current 823 * tab stop. */ 824 TkTextDispChunk *lastCharChunkPtr; /* Pointer to last chunk in display 825 * lines with numBytes > 0. Used to 826 * drop 0-sized chunks from the end 827 * of the line. */ 828 int byteOffset, ascent, descent, code, elide, elidesize; 829 StyleValues *sValuePtr; 830 831 /* 832 * Create and initialize a new DLine structure. 833 */ 834 835 dlPtr = (DLine *) ckalloc(sizeof(DLine)); 836 dlPtr->index = *indexPtr; 837 dlPtr->byteCount = 0; 838 dlPtr->y = 0; 839 dlPtr->oldY = -1; 840 dlPtr->height = 0; 841 dlPtr->baseline = 0; 842 dlPtr->chunkPtr = NULL; 843 dlPtr->nextPtr = NULL; 844 dlPtr->flags = NEW_LAYOUT; 845 846 /* 847 * Special case entirely elide line as there may be 1000s or more 848 */ 849 elide = TkTextIsElided(textPtr, indexPtr); /* save a malloc */ 850 if (elide && indexPtr->byteIndex==0) { 851 maxBytes = 0; 852 for (segPtr = indexPtr->linePtr->segPtr; 853 elide && (segPtr != NULL); 854 segPtr = segPtr->nextPtr) { 855 if ((elidesize = segPtr->size) > 0) { 856 maxBytes += elidesize; 857 /* 858 * If have we have a tag toggle, there is a chance 859 * that invisibility state changed, so bail out 860 */ 861 } else if ((segPtr->typePtr == &tkTextToggleOffType) 862 || (segPtr->typePtr == &tkTextToggleOnType)) { 863 if (segPtr->body.toggle.tagPtr->elideString != NULL) { 864 elide = (segPtr->typePtr == &tkTextToggleOffType) 865 ^ segPtr->body.toggle.tagPtr->elide; 866 } 867 } 868 } 869 870 if (elide) { 871 dlPtr->byteCount = maxBytes; 872 dlPtr->spaceAbove = dlPtr->spaceBelow = dlPtr->length = 0; 873 return dlPtr; 874 } 875 } 876 877 /* 878 * Each iteration of the loop below creates one TkTextDispChunk for 879 * the new display line. The line will always have at least one 880 * chunk (for the newline character at the end, if there's nothing 881 * else available). 882 */ 883 884 curIndex = *indexPtr; 885 lastChunkPtr = NULL; 886 chunkPtr = NULL; 887 noCharsYet = 1; 888 elide = 0; 889 breakChunkPtr = NULL; 890 breakByteOffset = 0; 891 justify = TK_JUSTIFY_LEFT; 892 tabIndex = -1; 893 tabChunkPtr = NULL; 894 tabArrayPtr = NULL; 895 rMargin = 0; 896 wrapMode = TEXT_WRAPMODE_CHAR; 897 tabSize = 0; 898 lastCharChunkPtr = NULL; 899 900 /* 901 * Find the first segment to consider for the line. Can't call 902 * TkTextIndexToSeg for this because it won't return a segment 903 * with zero size (such as the insertion cursor's mark). 904 */ 905 906 for (byteOffset = curIndex.byteIndex, segPtr = curIndex.linePtr->segPtr; 907 (byteOffset > 0) && (byteOffset >= segPtr->size); 908 byteOffset -= segPtr->size, segPtr = segPtr->nextPtr) { 909 /* Empty loop body. */ 910 } 911 912 while (segPtr != NULL) { 913 /* 914 * Every line still gets at least one chunk due to expectations 915 * in the rest of the code, but we are able to skip elided portions 916 * of the line quickly. 917 * If current chunk is elided and last chunk was too, coalese 918 */ 919 if (elide && (lastChunkPtr != NULL) 920 && (lastChunkPtr->displayProc == NULL /*ElideDisplayProc*/)) { 921 if ((elidesize = segPtr->size - byteOffset) > 0) { 922 curIndex.byteIndex += elidesize; 923 lastChunkPtr->numBytes += elidesize; 924 breakByteOffset = lastChunkPtr->breakIndex = lastChunkPtr->numBytes; 925 /* 926 * If have we have a tag toggle, there is a chance 927 * that invisibility state changed, so bail out 928 */ 929 } else if ((segPtr->typePtr == &tkTextToggleOffType) 930 || (segPtr->typePtr == &tkTextToggleOnType)) { 931 if (segPtr->body.toggle.tagPtr->elideString != NULL) { 932 elide = (segPtr->typePtr == &tkTextToggleOffType) 933 ^ segPtr->body.toggle.tagPtr->elide; 934 } 935 } 936 937 byteOffset = 0; 938 segPtr = segPtr->nextPtr; 939 if (segPtr == NULL && chunkPtr != NULL) { 940 ckfree((char *) chunkPtr); 941 } 942 continue; 943 } 944 945 if (segPtr->typePtr->layoutProc == NULL) { 946 segPtr = segPtr->nextPtr; 947 byteOffset = 0; 948 continue; 949 } 950 if (chunkPtr == NULL) { 951 chunkPtr = (TkTextDispChunk *) ckalloc(sizeof(TkTextDispChunk)); 952 chunkPtr->nextPtr = NULL; 953 } 954 chunkPtr->stylePtr = GetStyle(textPtr, &curIndex); 955 elide = chunkPtr->stylePtr->sValuePtr->elide; 956 957 /* 958 * Save style information such as justification and indentation, 959 * up until the first character is encountered, then retain that 960 * information for the rest of the line. 961 */ 962 963 if (noCharsYet) { 964 tabArrayPtr = chunkPtr->stylePtr->sValuePtr->tabArrayPtr; 965 justify = chunkPtr->stylePtr->sValuePtr->justify; 966 rMargin = chunkPtr->stylePtr->sValuePtr->rMargin; 967 wrapMode = chunkPtr->stylePtr->sValuePtr->wrapMode; 968 x = ((curIndex.byteIndex == 0) 969 ? chunkPtr->stylePtr->sValuePtr->lMargin1 970 : chunkPtr->stylePtr->sValuePtr->lMargin2); 971 if (wrapMode == TEXT_WRAPMODE_NONE) { 972 maxX = -1; 973 } else { 974 maxX = textPtr->dInfoPtr->maxX - textPtr->dInfoPtr->x 975 - rMargin; 976 if (maxX < x) { 977 maxX = x; 978 } 979 } 980 } 981 982 /* 983 * See if there is a tab in the current chunk; if so, only 984 * layout characters up to (and including) the tab. 985 */ 986 987 gotTab = 0; 988 maxBytes = segPtr->size - byteOffset; 989 if (!elide && justify == TK_JUSTIFY_LEFT) { 990 if (segPtr->typePtr == &tkTextCharType) { 991 char *p; 992 993 for (p = segPtr->body.chars + byteOffset; *p != 0; p++) { 994 if (*p == '\t') { 995 maxBytes = (p + 1 - segPtr->body.chars) - byteOffset; 996 gotTab = 1; 997 break; 998 } 999 } 1000 } 1001 } 1002 chunkPtr->x = x; 1003 if (elide && maxBytes) { 1004 /* don't free style here, as other code expects to be able to do that */ 1005 /*breakByteOffset =*/ chunkPtr->breakIndex = chunkPtr->numBytes = maxBytes; 1006 chunkPtr->width = 0; 1007 chunkPtr->minAscent = chunkPtr->minDescent = chunkPtr->minHeight = 0; 1008 1009 /* would just like to point to canonical empty chunk */ 1010 chunkPtr->displayProc = (Tk_ChunkDisplayProc *) NULL; 1011 chunkPtr->undisplayProc = (Tk_ChunkUndisplayProc *) NULL; 1012 chunkPtr->measureProc = ElideMeasureProc; 1013 chunkPtr->bboxProc = ElideBboxProc; 1014 1015 code = 1; 1016 } else 1017 code = (*segPtr->typePtr->layoutProc)(textPtr, &curIndex, segPtr, 1018 byteOffset, maxX-tabSize, maxBytes, noCharsYet, wrapMode, 1019 chunkPtr); 1020 if (code <= 0) { 1021 FreeStyle(textPtr, chunkPtr->stylePtr); 1022 if (code < 0) { 1023 /* 1024 * This segment doesn't wish to display itself (e.g. most 1025 * marks). 1026 */ 1027 1028 segPtr = segPtr->nextPtr; 1029 byteOffset = 0; 1030 continue; 1031 } 1032 1033 /* 1034 * No characters from this segment fit in the window: this 1035 * means we're at the end of the display line. 1036 */ 1037 1038 if (chunkPtr != NULL) { 1039 ckfree((char *) chunkPtr); 1040 } 1041 break; 1042 } 1043 if (chunkPtr->numBytes > 0) { 1044 noCharsYet = 0; 1045 lastCharChunkPtr = chunkPtr; 1046 } 1047 if (lastChunkPtr == NULL) { 1048 dlPtr->chunkPtr = chunkPtr; 1049 } else { 1050 lastChunkPtr->nextPtr = chunkPtr; 1051 } 1052 lastChunkPtr = chunkPtr; 1053 x += chunkPtr->width; 1054 if (chunkPtr->breakIndex > 0) { 1055 breakByteOffset = chunkPtr->breakIndex; 1056 breakIndex = curIndex; 1057 breakChunkPtr = chunkPtr; 1058 } 1059 if (chunkPtr->numBytes != maxBytes) { 1060 break; 1061 } 1062 1063 /* 1064 * If we're at a new tab, adjust the layout for all the chunks 1065 * pertaining to the previous tab. Also adjust the amount of 1066 * space left in the line to account for space that will be eaten 1067 * up by the tab. 1068 */ 1069 1070 if (gotTab) { 1071 if (tabIndex >= 0) { 1072 AdjustForTab(textPtr, tabArrayPtr, tabIndex, tabChunkPtr); 1073 x = chunkPtr->x + chunkPtr->width; 1074 } 1075 tabIndex++; 1076 tabChunkPtr = chunkPtr; 1077 tabSize = SizeOfTab(textPtr, tabArrayPtr, tabIndex, x, maxX); 1078 if ((maxX >= 0) && (tabSize >= maxX - x)) { 1079 break; 1080 } 1081 } 1082 curIndex.byteIndex += chunkPtr->numBytes; 1083 byteOffset += chunkPtr->numBytes; 1084 if (byteOffset >= segPtr->size) { 1085 byteOffset = 0; 1086 segPtr = segPtr->nextPtr; 1087 } 1088 1089 chunkPtr = NULL; 1090 } 1091 if (noCharsYet) { 1092 panic("LayoutDLine couldn't place any characters on a line"); 1093 } 1094 wholeLine = (segPtr == NULL); 1095 1096 /* 1097 * We're at the end of the display line. Throw away everything 1098 * after the most recent word break, if there is one; this may 1099 * potentially require the last chunk to be layed out again. 1100 */ 1101 1102 if (breakChunkPtr == NULL) { 1103 /* 1104 * This code makes sure that we don't accidentally display 1105 * chunks with no characters at the end of the line (such as 1106 * the insertion cursor). These chunks belong on the next 1107 * line. So, throw away everything after the last chunk that 1108 * has characters in it. 1109 */ 1110 1111 breakChunkPtr = lastCharChunkPtr; 1112 breakByteOffset = breakChunkPtr->numBytes; 1113 } 1114 if ((breakChunkPtr != NULL) && ((lastChunkPtr != breakChunkPtr) 1115 || (breakByteOffset != lastChunkPtr->numBytes))) { 1116 while (1) { 1117 chunkPtr = breakChunkPtr->nextPtr; 1118 if (chunkPtr == NULL) { 1119 break; 1120 } 1121 FreeStyle(textPtr, chunkPtr->stylePtr); 1122 breakChunkPtr->nextPtr = chunkPtr->nextPtr; 1123 (*chunkPtr->undisplayProc)(textPtr, chunkPtr); 1124 ckfree((char *) chunkPtr); 1125 } 1126 if (breakByteOffset != breakChunkPtr->numBytes) { 1127 (*breakChunkPtr->undisplayProc)(textPtr, breakChunkPtr); 1128 segPtr = TkTextIndexToSeg(&breakIndex, &byteOffset); 1129 (*segPtr->typePtr->layoutProc)(textPtr, &breakIndex, 1130 segPtr, byteOffset, maxX, breakByteOffset, 0, 1131 wrapMode, breakChunkPtr); 1132 } 1133 lastChunkPtr = breakChunkPtr; 1134 wholeLine = 0; 1135 } 1136 1137 1138 /* 1139 * Make tab adjustments for the last tab stop, if there is one. 1140 */ 1141 1142 if ((tabIndex >= 0) && (tabChunkPtr != NULL)) { 1143 AdjustForTab(textPtr, tabArrayPtr, tabIndex, tabChunkPtr); 1144 } 1145 1146 /* 1147 * Make one more pass over the line to recompute various things 1148 * like its height, length, and total number of bytes. Also 1149 * modify the x-locations of chunks to reflect justification. 1150 * If we're not wrapping, I'm not sure what is the best way to 1151 * handle left and center justification: should the total length, 1152 * for purposes of justification, be (a) the window width, (b) 1153 * the length of the longest line in the window, or (c) the length 1154 * of the longest line in the text? (c) isn't available, (b) seems 1155 * weird, since it can change with vertical scrolling, so (a) is 1156 * what is implemented below. 1157 */ 1158 1159 if (wrapMode == TEXT_WRAPMODE_NONE) { 1160 maxX = textPtr->dInfoPtr->maxX - textPtr->dInfoPtr->x - rMargin; 1161 } 1162 dlPtr->length = lastChunkPtr->x + lastChunkPtr->width; 1163 if (justify == TK_JUSTIFY_LEFT) { 1164 jIndent = 0; 1165 } else if (justify == TK_JUSTIFY_RIGHT) { 1166 jIndent = maxX - dlPtr->length; 1167 } else { 1168 jIndent = (maxX - dlPtr->length)/2; 1169 } 1170 ascent = descent = 0; 1171 for (chunkPtr = dlPtr->chunkPtr; chunkPtr != NULL; 1172 chunkPtr = chunkPtr->nextPtr) { 1173 chunkPtr->x += jIndent; 1174 dlPtr->byteCount += chunkPtr->numBytes; 1175 if (chunkPtr->minAscent > ascent) { 1176 ascent = chunkPtr->minAscent; 1177 } 1178 if (chunkPtr->minDescent > descent) { 1179 descent = chunkPtr->minDescent; 1180 } 1181 if (chunkPtr->minHeight > dlPtr->height) { 1182 dlPtr->height = chunkPtr->minHeight; 1183 } 1184 sValuePtr = chunkPtr->stylePtr->sValuePtr; 1185 if ((sValuePtr->borderWidth > 0) 1186 && (sValuePtr->relief != TK_RELIEF_FLAT)) { 1187 dlPtr->flags |= HAS_3D_BORDER; 1188 } 1189 } 1190 if (dlPtr->height < (ascent + descent)) { 1191 dlPtr->height = ascent + descent; 1192 dlPtr->baseline = ascent; 1193 } else { 1194 dlPtr->baseline = ascent + (dlPtr->height - ascent - descent)/2; 1195 } 1196 sValuePtr = dlPtr->chunkPtr->stylePtr->sValuePtr; 1197 if (dlPtr->index.byteIndex == 0) { 1198 dlPtr->spaceAbove = sValuePtr->spacing1; 1199 } else { 1200 dlPtr->spaceAbove = sValuePtr->spacing2 - sValuePtr->spacing2/2; 1201 } 1202 if (wholeLine) { 1203 dlPtr->spaceBelow = sValuePtr->spacing3; 1204 } else { 1205 dlPtr->spaceBelow = sValuePtr->spacing2/2; 1206 } 1207 dlPtr->height += dlPtr->spaceAbove + dlPtr->spaceBelow; 1208 dlPtr->baseline += dlPtr->spaceAbove; 1209 1210 /* 1211 * Recompute line length: may have changed because of justification. 1212 */ 1213 1214 dlPtr->length = lastChunkPtr->x + lastChunkPtr->width; 1215 return dlPtr; 1216} 1217 1218/* 1219 *---------------------------------------------------------------------- 1220 * 1221 * UpdateDisplayInfo -- 1222 * 1223 * This procedure is invoked to recompute some or all of the 1224 * DLine structures for a text widget. At the time it is called 1225 * the DLine structures still left in the widget are guaranteed 1226 * to be correct except that (a) the y-coordinates aren't 1227 * necessarily correct, (b) there may be missing structures 1228 * (the DLine structures get removed as soon as they are potentially 1229 * out-of-date), and (c) DLine structures that don't start at the 1230 * beginning of a line may be incorrect if previous information in 1231 * the same line changed size in a way that moved a line boundary 1232 * (DLines for any info that changed will have been deleted, but 1233 * not DLines for unchanged info in the same text line). 1234 * 1235 * Results: 1236 * None. 1237 * 1238 * Side effects: 1239 * Upon return, the DLine information for textPtr correctly reflects 1240 * the positions where characters will be displayed. However, this 1241 * procedure doesn't actually bring the display up-to-date. 1242 * 1243 *---------------------------------------------------------------------- 1244 */ 1245 1246static void 1247UpdateDisplayInfo(textPtr) 1248 TkText *textPtr; /* Text widget to update. */ 1249{ 1250 register TextDInfo *dInfoPtr = textPtr->dInfoPtr; 1251 register DLine *dlPtr, *prevPtr; 1252 TkTextIndex index; 1253 TkTextLine *lastLinePtr; 1254 int y, maxY, pixelOffset, maxOffset; 1255 1256 if (!(dInfoPtr->flags & DINFO_OUT_OF_DATE)) { 1257 return; 1258 } 1259 dInfoPtr->flags &= ~DINFO_OUT_OF_DATE; 1260 1261 /* 1262 * Delete any DLines that are now above the top of the window. 1263 */ 1264 1265 index = textPtr->topIndex; 1266 dlPtr = FindDLine(dInfoPtr->dLinePtr, &index); 1267 if ((dlPtr != NULL) && (dlPtr != dInfoPtr->dLinePtr)) { 1268 FreeDLines(textPtr, dInfoPtr->dLinePtr, dlPtr, 1); 1269 } 1270 1271 /* 1272 *-------------------------------------------------------------- 1273 * Scan through the contents of the window from top to bottom, 1274 * recomputing information for lines that are missing. 1275 *-------------------------------------------------------------- 1276 */ 1277 1278 lastLinePtr = TkBTreeFindLine(textPtr->tree, 1279 TkBTreeNumLines(textPtr->tree)); 1280 dlPtr = dInfoPtr->dLinePtr; 1281 prevPtr = NULL; 1282 y = dInfoPtr->y; 1283 maxY = dInfoPtr->maxY; 1284 while (1) { 1285 register DLine *newPtr; 1286 1287 if (index.linePtr == lastLinePtr) { 1288 break; 1289 } 1290 1291 /* 1292 * There are three possibilities right now: 1293 * (a) the next DLine (dlPtr) corresponds exactly to the next 1294 * information we want to display: just use it as-is. 1295 * (b) the next DLine corresponds to a different line, or to 1296 * a segment that will be coming later in the same line: 1297 * leave this DLine alone in the hopes that we'll be able 1298 * to use it later, then create a new DLine in front of 1299 * it. 1300 * (c) the next DLine corresponds to a segment in the line we 1301 * want, but it's a segment that has already been processed 1302 * or will never be processed. Delete the DLine and try 1303 * again. 1304 * 1305 * One other twist on all this. It's possible for 3D borders 1306 * to interact between lines (see DisplayLineBackground) so if 1307 * a line is relayed out and has styles with 3D borders, its 1308 * neighbors have to be redrawn if they have 3D borders too, 1309 * since the interactions could have changed (the neighbors 1310 * don't have to be relayed out, just redrawn). 1311 */ 1312 1313 if ((dlPtr == NULL) || (dlPtr->index.linePtr != index.linePtr)) { 1314 /* 1315 * Case (b) -- must make new DLine. 1316 */ 1317 1318 makeNewDLine: 1319 if (tkTextDebug) { 1320 char string[TK_POS_CHARS]; 1321 1322 /* 1323 * Debugging is enabled, so keep a log of all the lines 1324 * that were re-layed out. The test suite uses this 1325 * information. 1326 */ 1327 1328 TkTextPrintIndex(&index, string); 1329 Tcl_SetVar2(textPtr->interp, "tk_textRelayout", (char *) NULL, 1330 string, 1331 TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT); 1332 } 1333 newPtr = LayoutDLine(textPtr, &index); 1334 if (prevPtr == NULL) { 1335 dInfoPtr->dLinePtr = newPtr; 1336 } else { 1337 prevPtr->nextPtr = newPtr; 1338 if (prevPtr->flags & HAS_3D_BORDER) { 1339 prevPtr->oldY = -1; 1340 } 1341 } 1342 newPtr->nextPtr = dlPtr; 1343 dlPtr = newPtr; 1344 } else { 1345 /* 1346 * DlPtr refers to the line we want. Next check the 1347 * index within the line. 1348 */ 1349 1350 if (index.byteIndex == dlPtr->index.byteIndex) { 1351 /* 1352 * Case (a) -- can use existing display line as-is. 1353 */ 1354 1355 if ((dlPtr->flags & HAS_3D_BORDER) && (prevPtr != NULL) 1356 && (prevPtr->flags & (NEW_LAYOUT))) { 1357 dlPtr->oldY = -1; 1358 } 1359 goto lineOK; 1360 } 1361 if (index.byteIndex < dlPtr->index.byteIndex) { 1362 goto makeNewDLine; 1363 } 1364 1365 /* 1366 * Case (c) -- dlPtr is useless. Discard it and start 1367 * again with the next display line. 1368 */ 1369 1370 newPtr = dlPtr->nextPtr; 1371 FreeDLines(textPtr, dlPtr, newPtr, 0); 1372 dlPtr = newPtr; 1373 if (prevPtr != NULL) { 1374 prevPtr->nextPtr = newPtr; 1375 } else { 1376 dInfoPtr->dLinePtr = newPtr; 1377 } 1378 continue; 1379 } 1380 1381 /* 1382 * Advance to the start of the next line. 1383 */ 1384 1385 lineOK: 1386 dlPtr->y = y; 1387 y += dlPtr->height; 1388 TkTextIndexForwBytes(&index, dlPtr->byteCount, &index); 1389 prevPtr = dlPtr; 1390 dlPtr = dlPtr->nextPtr; 1391 1392 /* 1393 * If we switched text lines, delete any DLines left for the 1394 * old text line. 1395 */ 1396 1397 if (index.linePtr != prevPtr->index.linePtr) { 1398 register DLine *nextPtr; 1399 1400 nextPtr = dlPtr; 1401 while ((nextPtr != NULL) 1402 && (nextPtr->index.linePtr == prevPtr->index.linePtr)) { 1403 nextPtr = nextPtr->nextPtr; 1404 } 1405 if (nextPtr != dlPtr) { 1406 FreeDLines(textPtr, dlPtr, nextPtr, 0); 1407 prevPtr->nextPtr = nextPtr; 1408 dlPtr = nextPtr; 1409 } 1410 } 1411 1412 /* 1413 * It's important to have the following check here rather than in 1414 * the while statement for the loop, so that there's always at least 1415 * one DLine generated, regardless of how small the window is. This 1416 * keeps a lot of other code from breaking. 1417 */ 1418 1419 if (y >= maxY) { 1420 break; 1421 } 1422 } 1423 1424 /* 1425 * Delete any DLine structures that don't fit on the screen. 1426 */ 1427 1428 FreeDLines(textPtr, dlPtr, (DLine *) NULL, 1); 1429 1430 /* 1431 *-------------------------------------------------------------- 1432 * If there is extra space at the bottom of the window (because 1433 * we've hit the end of the text), then bring in more lines at 1434 * the top of the window, if there are any, to fill in the view. 1435 *-------------------------------------------------------------- 1436 */ 1437 1438 if (y < maxY) { 1439 int lineNum, spaceLeft, bytesToCount; 1440 DLine *lowestPtr; 1441 1442 /* 1443 * Layout an entire text line (potentially > 1 display line), 1444 * then link in as many display lines as fit without moving 1445 * the bottom line out of the window. Repeat this until 1446 * all the extra space has been used up or we've reached the 1447 * beginning of the text. 1448 */ 1449 1450 spaceLeft = maxY - y; 1451 lineNum = TkBTreeLineIndex(dInfoPtr->dLinePtr->index.linePtr); 1452 bytesToCount = dInfoPtr->dLinePtr->index.byteIndex; 1453 if (bytesToCount == 0) { 1454 bytesToCount = INT_MAX; 1455 lineNum--; 1456 } 1457 for ( ; (lineNum >= 0) && (spaceLeft > 0); lineNum--) { 1458 index.linePtr = TkBTreeFindLine(textPtr->tree, lineNum); 1459 index.byteIndex = 0; 1460 lowestPtr = NULL; 1461 1462 do { 1463 dlPtr = LayoutDLine(textPtr, &index); 1464 dlPtr->nextPtr = lowestPtr; 1465 lowestPtr = dlPtr; 1466 if (dlPtr->length == 0 && dlPtr->height == 0) { bytesToCount--; break; } /* elide */ 1467 TkTextIndexForwBytes(&index, dlPtr->byteCount, &index); 1468 bytesToCount -= dlPtr->byteCount; 1469 } while ((bytesToCount > 0) 1470 && (index.linePtr == lowestPtr->index.linePtr)); 1471 1472 /* 1473 * Scan through the display lines from the bottom one up to 1474 * the top one. 1475 */ 1476 1477 while (lowestPtr != NULL) { 1478 dlPtr = lowestPtr; 1479 spaceLeft -= dlPtr->height; 1480 if (spaceLeft < 0) { 1481 break; 1482 } 1483 lowestPtr = dlPtr->nextPtr; 1484 dlPtr->nextPtr = dInfoPtr->dLinePtr; 1485 dInfoPtr->dLinePtr = dlPtr; 1486 if (tkTextDebug) { 1487 char string[TK_POS_CHARS]; 1488 1489 TkTextPrintIndex(&dlPtr->index, string); 1490 Tcl_SetVar2(textPtr->interp, "tk_textRelayout", 1491 (char *) NULL, string, 1492 TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT); 1493 } 1494 } 1495 FreeDLines(textPtr, lowestPtr, (DLine *) NULL, 0); 1496 bytesToCount = INT_MAX; 1497 } 1498 1499 /* 1500 * Now we're all done except that the y-coordinates in all the 1501 * DLines are wrong and the top index for the text is wrong. 1502 * Update them. 1503 */ 1504 1505 textPtr->topIndex = dInfoPtr->dLinePtr->index; 1506 y = dInfoPtr->y; 1507 for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; 1508 dlPtr = dlPtr->nextPtr) { 1509 if (y > dInfoPtr->maxY) { 1510 panic("Added too many new lines in UpdateDisplayInfo"); 1511 } 1512 dlPtr->y = y; 1513 y += dlPtr->height; 1514 } 1515 } 1516 1517 /* 1518 *-------------------------------------------------------------- 1519 * If the old top or bottom line has scrolled elsewhere on the 1520 * screen, we may not be able to re-use its old contents by 1521 * copying bits (e.g., a beveled edge that was drawn when it was 1522 * at the top or bottom won't be drawn when the line is in the 1523 * middle and its neighbor has a matching background). Similarly, 1524 * if the new top or bottom line came from somewhere else on the 1525 * screen, we may not be able to copy the old bits. 1526 *-------------------------------------------------------------- 1527 */ 1528 1529 dlPtr = dInfoPtr->dLinePtr; 1530 if ((dlPtr->flags & HAS_3D_BORDER) && !(dlPtr->flags & TOP_LINE)) { 1531 dlPtr->oldY = -1; 1532 } 1533 while (1) { 1534 if ((dlPtr->flags & TOP_LINE) && (dlPtr != dInfoPtr->dLinePtr) 1535 && (dlPtr->flags & HAS_3D_BORDER)) { 1536 dlPtr->oldY = -1; 1537 } 1538 if ((dlPtr->flags & BOTTOM_LINE) && (dlPtr->nextPtr != NULL) 1539 && (dlPtr->flags & HAS_3D_BORDER)) { 1540 dlPtr->oldY = -1; 1541 } 1542 if (dlPtr->nextPtr == NULL) { 1543 if ((dlPtr->flags & HAS_3D_BORDER) 1544 && !(dlPtr->flags & BOTTOM_LINE)) { 1545 dlPtr->oldY = -1; 1546 } 1547 dlPtr->flags &= ~TOP_LINE; 1548 dlPtr->flags |= BOTTOM_LINE; 1549 break; 1550 } 1551 dlPtr->flags &= ~(TOP_LINE|BOTTOM_LINE); 1552 dlPtr = dlPtr->nextPtr; 1553 } 1554 dInfoPtr->dLinePtr->flags |= TOP_LINE; 1555 1556 /* 1557 * Arrange for scrollbars to be updated. 1558 */ 1559 1560 textPtr->flags |= UPDATE_SCROLLBARS; 1561 1562 /* 1563 *-------------------------------------------------------------- 1564 * Deal with horizontal scrolling: 1565 * 1. If there's empty space to the right of the longest line, 1566 * shift the screen to the right to fill in the empty space. 1567 * 2. If the desired horizontal scroll position has changed, 1568 * force a full redisplay of all the lines in the widget. 1569 * 3. If the wrap mode isn't "none" then re-scroll to the base 1570 * position. 1571 *-------------------------------------------------------------- 1572 */ 1573 1574 dInfoPtr->maxLength = 0; 1575 for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; 1576 dlPtr = dlPtr->nextPtr) { 1577 if (dlPtr->length > dInfoPtr->maxLength) { 1578 dInfoPtr->maxLength = dlPtr->length; 1579 } 1580 } 1581 maxOffset = (dInfoPtr->maxLength - (dInfoPtr->maxX - dInfoPtr->x) 1582 + textPtr->charWidth - 1)/textPtr->charWidth; 1583 if (dInfoPtr->newByteOffset > maxOffset) { 1584 dInfoPtr->newByteOffset = maxOffset; 1585 } 1586 if (dInfoPtr->newByteOffset < 0) { 1587 dInfoPtr->newByteOffset = 0; 1588 } 1589 pixelOffset = dInfoPtr->newByteOffset * textPtr->charWidth; 1590 if (pixelOffset != dInfoPtr->curPixelOffset) { 1591 dInfoPtr->curPixelOffset = pixelOffset; 1592 for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; 1593 dlPtr = dlPtr->nextPtr) { 1594 dlPtr->oldY = -1; 1595 } 1596 } 1597} 1598 1599/* 1600 *---------------------------------------------------------------------- 1601 * 1602 * FreeDLines -- 1603 * 1604 * This procedure is called to free up all of the resources 1605 * associated with one or more DLine structures. 1606 * 1607 * Results: 1608 * None. 1609 * 1610 * Side effects: 1611 * Memory gets freed and various other resources are released. 1612 * 1613 *---------------------------------------------------------------------- 1614 */ 1615 1616static void 1617FreeDLines(textPtr, firstPtr, lastPtr, unlink) 1618 TkText *textPtr; /* Information about overall text 1619 * widget. */ 1620 register DLine *firstPtr; /* Pointer to first DLine to free up. */ 1621 DLine *lastPtr; /* Pointer to DLine just after last 1622 * one to free (NULL means everything 1623 * starting with firstPtr). */ 1624 int unlink; /* 1 means DLines are currently linked 1625 * into the list rooted at 1626 * textPtr->dInfoPtr->dLinePtr and 1627 * they have to be unlinked. 0 means 1628 * just free without unlinking. */ 1629{ 1630 register TkTextDispChunk *chunkPtr, *nextChunkPtr; 1631 register DLine *nextDLinePtr; 1632 1633 if (unlink) { 1634 if (textPtr->dInfoPtr->dLinePtr == firstPtr) { 1635 textPtr->dInfoPtr->dLinePtr = lastPtr; 1636 } else { 1637 register DLine *prevPtr; 1638 for (prevPtr = textPtr->dInfoPtr->dLinePtr; 1639 prevPtr->nextPtr != firstPtr; prevPtr = prevPtr->nextPtr) { 1640 /* Empty loop body. */ 1641 } 1642 prevPtr->nextPtr = lastPtr; 1643 } 1644 } 1645 while (firstPtr != lastPtr) { 1646 nextDLinePtr = firstPtr->nextPtr; 1647 for (chunkPtr = firstPtr->chunkPtr; chunkPtr != NULL; 1648 chunkPtr = nextChunkPtr) { 1649 if (chunkPtr->undisplayProc != NULL) { 1650 (*chunkPtr->undisplayProc)(textPtr, chunkPtr); 1651 } 1652 FreeStyle(textPtr, chunkPtr->stylePtr); 1653 nextChunkPtr = chunkPtr->nextPtr; 1654 ckfree((char *) chunkPtr); 1655 } 1656 ckfree((char *) firstPtr); 1657 firstPtr = nextDLinePtr; 1658 } 1659 textPtr->dInfoPtr->dLinesInvalidated = 1; 1660} 1661 1662/* 1663 *---------------------------------------------------------------------- 1664 * 1665 * DisplayDLine -- 1666 * 1667 * This procedure is invoked to draw a single line on the 1668 * screen. 1669 * 1670 * Results: 1671 * None. 1672 * 1673 * Side effects: 1674 * The line given by dlPtr is drawn at its correct position in 1675 * textPtr's window. Note that this is one *display* line, not 1676 * one *text* line. 1677 * 1678 *---------------------------------------------------------------------- 1679 */ 1680 1681static void 1682DisplayDLine(textPtr, dlPtr, prevPtr, pixmap) 1683 TkText *textPtr; /* Text widget in which to draw line. */ 1684 register DLine *dlPtr; /* Information about line to draw. */ 1685 DLine *prevPtr; /* Line just before one to draw, or NULL 1686 * if dlPtr is the top line. */ 1687 Pixmap pixmap; /* Pixmap to use for double-buffering. 1688 * Caller must make sure it's large enough 1689 * to hold line. */ 1690{ 1691 register TkTextDispChunk *chunkPtr; 1692 TextDInfo *dInfoPtr = textPtr->dInfoPtr; 1693 Display *display; 1694 int height, x; 1695#ifndef TK_NO_DOUBLE_BUFFERING 1696 const int y = 0; 1697#else 1698 const int y = dlPtr->y; 1699#endif /* TK_NO_DOUBLE_BUFFERING */ 1700 1701 if (dlPtr->chunkPtr == NULL) return; 1702 1703 display = Tk_Display(textPtr->tkwin); 1704 1705 height = dlPtr->height; 1706 if ((height + dlPtr->y) > dInfoPtr->maxY) { 1707 height = dInfoPtr->maxY - dlPtr->y; 1708 } 1709 1710#ifdef TK_NO_DOUBLE_BUFFERING 1711 TkpClipDrawableToRect(display, pixmap, dInfoPtr->x, y, 1712 dInfoPtr->maxX - dInfoPtr->x, height); 1713#endif /* TK_NO_DOUBLE_BUFFERING */ 1714 1715 /* 1716 * First, clear the area of the line to the background color for the 1717 * text widget. 1718 */ 1719 1720 Tk_Fill3DRectangle(textPtr->tkwin, pixmap, textPtr->border, 0, y, 1721 Tk_Width(textPtr->tkwin), dlPtr->height, 0, TK_RELIEF_FLAT); 1722 1723 /* 1724 * Next, draw background information for the whole line. 1725 */ 1726 1727 DisplayLineBackground(textPtr, dlPtr, prevPtr, pixmap); 1728 1729 /* 1730 * Make another pass through all of the chunks to redraw the 1731 * insertion cursor, if it is visible on this line. Must do 1732 * it here rather than in the foreground pass below because 1733 * otherwise a wide insertion cursor will obscure the character 1734 * to its left. 1735 */ 1736 1737 if (textPtr->state == TK_STATE_NORMAL) { 1738 for (chunkPtr = dlPtr->chunkPtr; (chunkPtr != NULL); 1739 chunkPtr = chunkPtr->nextPtr) { 1740 x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curPixelOffset; 1741 if (chunkPtr->displayProc == TkTextInsertDisplayProc) { 1742 (*chunkPtr->displayProc)(chunkPtr, x, y + dlPtr->spaceAbove, 1743 dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow, 1744 dlPtr->baseline - dlPtr->spaceAbove, display, pixmap, 1745 dlPtr->y + dlPtr->spaceAbove); 1746 } 1747 } 1748 } 1749 1750 /* 1751 * Make yet another pass through all of the chunks to redraw all of 1752 * foreground information. Note: we have to call the displayProc 1753 * even for chunks that are off-screen. This is needed, for 1754 * example, so that embedded windows can be unmapped in this case. 1755 * Conve 1756 */ 1757 1758 for (chunkPtr = dlPtr->chunkPtr; (chunkPtr != NULL); 1759 chunkPtr = chunkPtr->nextPtr) { 1760 if (chunkPtr->displayProc == TkTextInsertDisplayProc) { 1761 /* 1762 * Already displayed the insertion cursor above. Don't 1763 * do it again here. 1764 */ 1765 1766 continue; 1767 } 1768 x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curPixelOffset; 1769 if ((x + chunkPtr->width <= 0) || (x >= dInfoPtr->maxX)) { 1770 /* 1771 * Note: we have to call the displayProc even for chunks 1772 * that are off-screen. This is needed, for example, so 1773 * that embedded windows can be unmapped in this case. 1774 * Display the chunk at a coordinate that can be clearly 1775 * identified by the displayProc as being off-screen to 1776 * the left (the displayProc may not be able to tell if 1777 * something is off to the right). 1778 */ 1779 1780 if (chunkPtr->displayProc != NULL) 1781 (*chunkPtr->displayProc)(chunkPtr, -chunkPtr->width, 1782 y + dlPtr->spaceAbove, 1783 dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow, 1784 dlPtr->baseline - dlPtr->spaceAbove, display, pixmap, 1785 dlPtr->y + dlPtr->spaceAbove); 1786 } else { 1787 /* don't call if elide. This tax ok since not very many visible DLine's in 1788 an area, but potentially many elide ones */ 1789 if (chunkPtr->displayProc != NULL) 1790 (*chunkPtr->displayProc)(chunkPtr, x, y + dlPtr->spaceAbove, 1791 dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow, 1792 dlPtr->baseline - dlPtr->spaceAbove, display, pixmap, 1793 dlPtr->y + dlPtr->spaceAbove); 1794 } 1795 if (dInfoPtr->dLinesInvalidated) { 1796 return; 1797 } 1798 } 1799 1800#ifndef TK_NO_DOUBLE_BUFFERING 1801 /* 1802 * Copy the pixmap onto the screen. If this is the last line on 1803 * the screen then copy a piece of the line, so that it doesn't 1804 * overflow into the border area. Another special trick: copy the 1805 * padding area to the left of the line; this is because the 1806 * insertion cursor sometimes overflows onto that area and we want 1807 * to get as much of the cursor as possible. 1808 */ 1809 1810 XCopyArea(display, pixmap, Tk_WindowId(textPtr->tkwin), dInfoPtr->copyGC, 1811 dInfoPtr->x, y, (unsigned) (dInfoPtr->maxX - dInfoPtr->x), 1812 (unsigned) height, dInfoPtr->x, dlPtr->y); 1813#else 1814 TkpClipDrawableToRect(display, pixmap, 0, 0, -1, -1); 1815#endif /* TK_NO_DOUBLE_BUFFERING */ 1816 linesRedrawn++; 1817} 1818 1819/* 1820 *-------------------------------------------------------------- 1821 * 1822 * DisplayLineBackground -- 1823 * 1824 * This procedure is called to fill in the background for 1825 * a display line. It draws 3D borders cleverly so that 1826 * adjacent chunks with the same style (whether on the same 1827 * line or different lines) have a single 3D border around 1828 * the whole region. 1829 * 1830 * Results: 1831 * There is no return value. Pixmap is filled in with background 1832 * information for dlPtr. 1833 * 1834 * Side effects: 1835 * None. 1836 * 1837 *-------------------------------------------------------------- 1838 */ 1839 1840static void 1841DisplayLineBackground(textPtr, dlPtr, prevPtr, pixmap) 1842 TkText *textPtr; /* Text widget containing line. */ 1843 register DLine *dlPtr; /* Information about line to draw. */ 1844 DLine *prevPtr; /* Line just above dlPtr, or NULL if dlPtr 1845 * is the top-most line in the window. */ 1846 Pixmap pixmap; /* Pixmap to use for double-buffering. 1847 * Caller must make sure it's large enough 1848 * to hold line. Caller must also have 1849 * filled it with the background color for 1850 * the widget. */ 1851{ 1852 TextDInfo *dInfoPtr = textPtr->dInfoPtr; 1853 TkTextDispChunk *chunkPtr; /* Pointer to chunk in the current line. */ 1854 TkTextDispChunk *chunkPtr2; /* Pointer to chunk in the line above or 1855 * below the current one. NULL if we're to 1856 * the left of or to the right of the chunks 1857 * in the line. */ 1858 TkTextDispChunk *nextPtr2; /* Next chunk after chunkPtr2 (it's not the 1859 * same as chunkPtr2->nextPtr in the case 1860 * where chunkPtr2 is NULL because the line 1861 * is indented). */ 1862 int leftX; /* The left edge of the region we're 1863 * currently working on. */ 1864 int leftXIn; /* 1 means beveled edge at leftX slopes right 1865 * as it goes down, 0 means it slopes left 1866 * as it goes down. */ 1867 int rightX; /* Right edge of chunkPtr. */ 1868 int rightX2; /* Right edge of chunkPtr2. */ 1869 int matchLeft; /* Does the style of this line match that 1870 * of its neighbor just to the left of 1871 * the current x coordinate? */ 1872 int matchRight; /* Does line's style match its neighbor 1873 * just to the right of the current x-coord? */ 1874 int minX, maxX, xOffset; 1875 StyleValues *sValuePtr; 1876 Display *display; 1877#ifndef TK_NO_DOUBLE_BUFFERING 1878 const int y = 0; 1879#else 1880 const int y = dlPtr->y; 1881#endif /* TK_NO_DOUBLE_BUFFERING */ 1882 1883 /* 1884 * Pass 1: scan through dlPtr from left to right. For each range of 1885 * chunks with the same style, draw the main background for the style 1886 * plus the vertical parts of the 3D borders (the left and right 1887 * edges). 1888 */ 1889 1890 display = Tk_Display(textPtr->tkwin); 1891 minX = dInfoPtr->curPixelOffset; 1892 xOffset = dInfoPtr->x - minX; 1893 maxX = minX + dInfoPtr->maxX - dInfoPtr->x; 1894 chunkPtr = dlPtr->chunkPtr; 1895 1896 /* 1897 * Note A: in the following statement, and a few others later in 1898 * this file marked with "See Note A above", the right side of the 1899 * assignment was replaced with 0 on 6/18/97. This has the effect 1900 * of highlighting the empty space to the left of a line whenever 1901 * the leftmost character of the line is highlighted. This way, 1902 * multi-line highlights always line up along their left edges. 1903 * However, this may look funny in the case where a single word is 1904 * highlighted. To undo the change, replace "leftX = 0" with "leftX 1905 * = chunkPtr->x" and "rightX2 = 0" with "rightX2 = nextPtr2->x" 1906 * here and at all the marked points below. This restores the old 1907 * behavior where empty space to the left of a line is not 1908 * highlighted, leaving a ragged left edge for multi-line 1909 * highlights. 1910 */ 1911 1912 leftX = 0; 1913 for (; leftX < maxX; chunkPtr = chunkPtr->nextPtr) { 1914 if ((chunkPtr->nextPtr != NULL) 1915 && SAME_BACKGROUND(chunkPtr->nextPtr->stylePtr, 1916 chunkPtr->stylePtr)) { 1917 continue; 1918 } 1919 sValuePtr = chunkPtr->stylePtr->sValuePtr; 1920 rightX = chunkPtr->x + chunkPtr->width; 1921 if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) { 1922 rightX = maxX; 1923 } 1924 if (chunkPtr->stylePtr->bgGC != None) { 1925 /* Not visible - bail out now */ 1926 if (rightX + xOffset <= 0) { 1927 leftX = rightX; 1928 continue; 1929 } 1930 1931 /* 1932 * Trim the start position for drawing to be no further away than 1933 * -borderWidth. The reason is that on many X servers drawing from 1934 * -32768 (or less) to +something simply does not display 1935 * correctly. [Patch #541999] 1936 */ 1937 if ((leftX + xOffset) < -(sValuePtr->borderWidth)) { 1938 leftX = -sValuePtr->borderWidth - xOffset; 1939 } 1940 if ((rightX - leftX) > 32767) { 1941 rightX = leftX + 32767; 1942 } 1943 1944 XFillRectangle(display, pixmap, chunkPtr->stylePtr->bgGC, 1945 leftX + xOffset, y, (unsigned int) (rightX - leftX), 1946 (unsigned int) dlPtr->height); 1947 if (sValuePtr->relief != TK_RELIEF_FLAT) { 1948 Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border, 1949 leftX + xOffset, y, sValuePtr->borderWidth, 1950 dlPtr->height, 1, sValuePtr->relief); 1951 Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border, 1952 rightX - sValuePtr->borderWidth + xOffset, 1953 y, sValuePtr->borderWidth, dlPtr->height, 0, 1954 sValuePtr->relief); 1955 } 1956 } 1957 leftX = rightX; 1958 } 1959 1960 /* 1961 * Pass 2: draw the horizontal bevels along the top of the line. To 1962 * do this, scan through dlPtr from left to right while simultaneously 1963 * scanning through the line just above dlPtr. ChunkPtr2 and nextPtr2 1964 * refer to two adjacent chunks in the line above. 1965 */ 1966 1967 chunkPtr = dlPtr->chunkPtr; 1968 leftX = 0; /* See Note A above. */ 1969 leftXIn = 1; 1970 rightX = chunkPtr->x + chunkPtr->width; 1971 if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) { 1972 rightX = maxX; 1973 } 1974 chunkPtr2 = NULL; 1975 if (prevPtr != NULL && prevPtr->chunkPtr != NULL) { 1976 /* 1977 * Find the chunk in the previous line that covers leftX. 1978 */ 1979 1980 nextPtr2 = prevPtr->chunkPtr; 1981 rightX2 = 0; /* See Note A above. */ 1982 while (rightX2 <= leftX) { 1983 chunkPtr2 = nextPtr2; 1984 if (chunkPtr2 == NULL) { 1985 break; 1986 } 1987 nextPtr2 = chunkPtr2->nextPtr; 1988 rightX2 = chunkPtr2->x + chunkPtr2->width; 1989 if (nextPtr2 == NULL) { 1990 rightX2 = INT_MAX; 1991 } 1992 } 1993 } else { 1994 nextPtr2 = NULL; 1995 rightX2 = INT_MAX; 1996 } 1997 1998 while (leftX < maxX) { 1999 matchLeft = (chunkPtr2 != NULL) 2000 && SAME_BACKGROUND(chunkPtr2->stylePtr, chunkPtr->stylePtr); 2001 sValuePtr = chunkPtr->stylePtr->sValuePtr; 2002 if (rightX <= rightX2) { 2003 /* 2004 * The chunk in our line is about to end. If its style 2005 * changes then draw the bevel for the current style. 2006 */ 2007 2008 if ((chunkPtr->nextPtr == NULL) 2009 || !SAME_BACKGROUND(chunkPtr->stylePtr, 2010 chunkPtr->nextPtr->stylePtr)) { 2011 if (!matchLeft && (sValuePtr->relief != TK_RELIEF_FLAT)) { 2012 Tk_3DHorizontalBevel(textPtr->tkwin, pixmap, 2013 sValuePtr->border, leftX + xOffset, y, 2014 rightX - leftX, sValuePtr->borderWidth, leftXIn, 2015 1, 1, sValuePtr->relief); 2016 } 2017 leftX = rightX; 2018 leftXIn = 1; 2019 2020 /* 2021 * If the chunk in the line above is also ending at 2022 * the same point then advance to the next chunk in 2023 * that line. 2024 */ 2025 2026 if ((rightX == rightX2) && (chunkPtr2 != NULL)) { 2027 goto nextChunk2; 2028 } 2029 } 2030 chunkPtr = chunkPtr->nextPtr; 2031 if (chunkPtr == NULL) { 2032 break; 2033 } 2034 rightX = chunkPtr->x + chunkPtr->width; 2035 if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) { 2036 rightX = maxX; 2037 } 2038 continue; 2039 } 2040 2041 /* 2042 * The chunk in the line above is ending at an x-position where 2043 * there is no change in the style of the current line. If the 2044 * style above matches the current line on one side of the change 2045 * but not on the other, we have to draw an L-shaped piece of 2046 * bevel. 2047 */ 2048 2049 matchRight = (nextPtr2 != NULL) 2050 && SAME_BACKGROUND(nextPtr2->stylePtr, chunkPtr->stylePtr); 2051 if (matchLeft && !matchRight) { 2052 if (sValuePtr->relief != TK_RELIEF_FLAT) { 2053 Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border, 2054 rightX2 - sValuePtr->borderWidth + xOffset, y, 2055 sValuePtr->borderWidth, sValuePtr->borderWidth, 0, 2056 sValuePtr->relief); 2057 } 2058 leftX = rightX2 - sValuePtr->borderWidth; 2059 leftXIn = 0; 2060 } else if (!matchLeft && matchRight 2061 && (sValuePtr->relief != TK_RELIEF_FLAT)) { 2062 Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border, 2063 rightX2 + xOffset, y, sValuePtr->borderWidth, 2064 sValuePtr->borderWidth, 1, sValuePtr->relief); 2065 Tk_3DHorizontalBevel(textPtr->tkwin, pixmap, sValuePtr->border, 2066 leftX + xOffset, y, rightX2 + sValuePtr->borderWidth - 2067 leftX, sValuePtr->borderWidth, leftXIn, 0, 1, 2068 sValuePtr->relief); 2069 } 2070 2071 nextChunk2: 2072 chunkPtr2 = nextPtr2; 2073 if (chunkPtr2 == NULL) { 2074 rightX2 = INT_MAX; 2075 } else { 2076 nextPtr2 = chunkPtr2->nextPtr; 2077 rightX2 = chunkPtr2->x + chunkPtr2->width; 2078 if (nextPtr2 == NULL) { 2079 rightX2 = INT_MAX; 2080 } 2081 } 2082 } 2083 /* 2084 * Pass 3: draw the horizontal bevels along the bottom of the line. 2085 * This uses the same approach as pass 2. 2086 */ 2087 2088 chunkPtr = dlPtr->chunkPtr; 2089 leftX = 0; /* See Note A above. */ 2090 leftXIn = 0; 2091 rightX = chunkPtr->x + chunkPtr->width; 2092 if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) { 2093 rightX = maxX; 2094 } 2095 chunkPtr2 = NULL; 2096 if (dlPtr->nextPtr != NULL && dlPtr->nextPtr->chunkPtr != NULL) { 2097 /* 2098 * Find the chunk in the previous line that covers leftX. 2099 */ 2100 2101 nextPtr2 = dlPtr->nextPtr->chunkPtr; 2102 rightX2 = 0; /* See Note A above. */ 2103 while (rightX2 <= leftX) { 2104 chunkPtr2 = nextPtr2; 2105 if (chunkPtr2 == NULL) { 2106 break; 2107 } 2108 nextPtr2 = chunkPtr2->nextPtr; 2109 rightX2 = chunkPtr2->x + chunkPtr2->width; 2110 if (nextPtr2 == NULL) { 2111 rightX2 = INT_MAX; 2112 } 2113 } 2114 } else { 2115 nextPtr2 = NULL; 2116 rightX2 = INT_MAX; 2117 } 2118 2119 while (leftX < maxX) { 2120 matchLeft = (chunkPtr2 != NULL) 2121 && SAME_BACKGROUND(chunkPtr2->stylePtr, chunkPtr->stylePtr); 2122 sValuePtr = chunkPtr->stylePtr->sValuePtr; 2123 if (rightX <= rightX2) { 2124 if ((chunkPtr->nextPtr == NULL) 2125 || !SAME_BACKGROUND(chunkPtr->stylePtr, 2126 chunkPtr->nextPtr->stylePtr)) { 2127 if (!matchLeft && (sValuePtr->relief != TK_RELIEF_FLAT)) { 2128 Tk_3DHorizontalBevel(textPtr->tkwin, pixmap, 2129 sValuePtr->border, leftX + xOffset, 2130 y + dlPtr->height - sValuePtr->borderWidth, 2131 rightX - leftX, sValuePtr->borderWidth, leftXIn, 2132 0, 0, sValuePtr->relief); 2133 } 2134 leftX = rightX; 2135 leftXIn = 0; 2136 if ((rightX == rightX2) && (chunkPtr2 != NULL)) { 2137 goto nextChunk2b; 2138 } 2139 } 2140 chunkPtr = chunkPtr->nextPtr; 2141 if (chunkPtr == NULL) { 2142 break; 2143 } 2144 rightX = chunkPtr->x + chunkPtr->width; 2145 if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) { 2146 rightX = maxX; 2147 } 2148 continue; 2149 } 2150 2151 matchRight = (nextPtr2 != NULL) 2152 && SAME_BACKGROUND(nextPtr2->stylePtr, chunkPtr->stylePtr); 2153 if (matchLeft && !matchRight) { 2154 if (sValuePtr->relief != TK_RELIEF_FLAT) { 2155 Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border, 2156 rightX2 - sValuePtr->borderWidth + xOffset, 2157 y + dlPtr->height - sValuePtr->borderWidth, 2158 sValuePtr->borderWidth, sValuePtr->borderWidth, 0, 2159 sValuePtr->relief); 2160 } 2161 leftX = rightX2 - sValuePtr->borderWidth; 2162 leftXIn = 1; 2163 } else if (!matchLeft && matchRight 2164 && (sValuePtr->relief != TK_RELIEF_FLAT)) { 2165 Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border, 2166 rightX2 + xOffset, y + dlPtr->height - 2167 sValuePtr->borderWidth, sValuePtr->borderWidth, 2168 sValuePtr->borderWidth, 1, sValuePtr->relief); 2169 Tk_3DHorizontalBevel(textPtr->tkwin, pixmap, sValuePtr->border, 2170 leftX + xOffset, y + dlPtr->height - 2171 sValuePtr->borderWidth, rightX2 + sValuePtr->borderWidth - 2172 leftX, sValuePtr->borderWidth, leftXIn, 1, 0, 2173 sValuePtr->relief); 2174 } 2175 2176 nextChunk2b: 2177 chunkPtr2 = nextPtr2; 2178 if (chunkPtr2 == NULL) { 2179 rightX2 = INT_MAX; 2180 } else { 2181 nextPtr2 = chunkPtr2->nextPtr; 2182 rightX2 = chunkPtr2->x + chunkPtr2->width; 2183 if (nextPtr2 == NULL) { 2184 rightX2 = INT_MAX; 2185 } 2186 } 2187 } 2188} 2189 2190/* 2191 *---------------------------------------------------------------------- 2192 * 2193 * DisplayText -- 2194 * 2195 * This procedure is invoked as a when-idle handler to update the 2196 * display. It only redisplays the parts of the text widget that 2197 * are out of date. 2198 * 2199 * Results: 2200 * None. 2201 * 2202 * Side effects: 2203 * Information is redrawn on the screen. 2204 * 2205 *---------------------------------------------------------------------- 2206 */ 2207 2208static void 2209DisplayText(clientData) 2210 ClientData clientData; /* Information about widget. */ 2211{ 2212 register TkText *textPtr = (TkText *) clientData; 2213 TextDInfo *dInfoPtr = textPtr->dInfoPtr; 2214 Tk_Window tkwin; 2215 register DLine *dlPtr; 2216 DLine *prevPtr; 2217 Pixmap pixmap; 2218 int maxHeight, borders; 2219 int bottomY = 0; /* Initialization needed only to stop 2220 * compiler warnings. */ 2221 Tcl_Interp *interp; 2222 2223 if (textPtr->tkwin == NULL) { 2224 2225 /* 2226 * The widget has been deleted. Don't do anything. 2227 */ 2228 2229 return; 2230 } 2231 2232 interp = textPtr->interp; 2233 Tcl_Preserve((ClientData) interp); 2234 2235 if (tkTextDebug) { 2236 Tcl_SetVar2(interp, "tk_textRelayout", (char *) NULL, "", 2237 TCL_GLOBAL_ONLY); 2238 } 2239 2240 if (textPtr->tkwin == NULL) { 2241 2242 /* 2243 * The widget has been deleted. Don't do anything. 2244 */ 2245 2246 goto end; 2247 } 2248 2249 if (!Tk_IsMapped(textPtr->tkwin) || (dInfoPtr->maxX <= dInfoPtr->x) 2250 || (dInfoPtr->maxY <= dInfoPtr->y)) { 2251 UpdateDisplayInfo(textPtr); 2252 dInfoPtr->flags &= ~REDRAW_PENDING; 2253 goto doScrollbars; 2254 } 2255 numRedisplays++; 2256 if (tkTextDebug) { 2257 Tcl_SetVar2(interp, "tk_textRedraw", (char *) NULL, "", 2258 TCL_GLOBAL_ONLY); 2259 } 2260 2261 if (textPtr->tkwin == NULL) { 2262 2263 /* 2264 * The widget has been deleted. Don't do anything. 2265 */ 2266 2267 goto end; 2268 } 2269 2270 /* 2271 * Choose a new current item if that is needed (this could cause 2272 * event handlers to be invoked, hence the preserve/release calls 2273 * and the loop, since the handlers could conceivably necessitate 2274 * yet another current item calculation). The tkwin check is because 2275 * the whole window could go away in the Tcl_Release call. 2276 */ 2277 2278 while (dInfoPtr->flags & REPICK_NEEDED) { 2279 Tcl_Preserve((ClientData) textPtr); 2280 dInfoPtr->flags &= ~REPICK_NEEDED; 2281 TkTextPickCurrent(textPtr, &textPtr->pickEvent); 2282 tkwin = textPtr->tkwin; 2283 Tcl_Release((ClientData) textPtr); 2284 if (tkwin == NULL) { 2285 goto end; 2286 } 2287 } 2288 2289 /* 2290 * First recompute what's supposed to be displayed. 2291 */ 2292 2293 UpdateDisplayInfo(textPtr); 2294 dInfoPtr->dLinesInvalidated = 0; 2295 2296 /* 2297 * See if it's possible to bring some parts of the screen up-to-date 2298 * by scrolling (copying from other parts of the screen). 2299 */ 2300 2301 for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) { 2302 register DLine *dlPtr2; 2303 int offset, height, y, oldY; 2304 TkRegion damageRgn; 2305 2306 if ((dlPtr->oldY == -1) || (dlPtr->y == dlPtr->oldY) 2307 || ((dlPtr->oldY + dlPtr->height) > dInfoPtr->maxY)) { 2308 continue; 2309 } 2310 2311 /* 2312 * This line is already drawn somewhere in the window so it only 2313 * needs to be copied to its new location. See if there's a group 2314 * of lines that can all be copied together. 2315 */ 2316 2317 offset = dlPtr->y - dlPtr->oldY; 2318 height = dlPtr->height; 2319 y = dlPtr->y; 2320 for (dlPtr2 = dlPtr->nextPtr; dlPtr2 != NULL; 2321 dlPtr2 = dlPtr2->nextPtr) { 2322 if ((dlPtr2->oldY == -1) 2323 || ((dlPtr2->oldY + offset) != dlPtr2->y) 2324 || ((dlPtr2->oldY + dlPtr2->height) > dInfoPtr->maxY)) { 2325 break; 2326 } 2327 height += dlPtr2->height; 2328 } 2329 2330 /* 2331 * Reduce the height of the area being copied if necessary to 2332 * avoid overwriting the border area. 2333 */ 2334 2335 if ((y + height) > dInfoPtr->maxY) { 2336 height = dInfoPtr->maxY -y; 2337 } 2338 oldY = dlPtr->oldY; 2339 2340 /* 2341 * Update the lines we are going to scroll to show that they 2342 * have been copied. 2343 */ 2344 2345 while (1) { 2346 dlPtr->oldY = dlPtr->y; 2347 if (dlPtr->nextPtr == dlPtr2) { 2348 break; 2349 } 2350 dlPtr = dlPtr->nextPtr; 2351 } 2352 2353 /* 2354 * Scan through the lines following the copied ones to see if 2355 * we are going to overwrite them with the copy operation. 2356 * If so, mark them for redisplay. 2357 */ 2358 2359 for ( ; dlPtr2 != NULL; dlPtr2 = dlPtr2->nextPtr) { 2360 if ((dlPtr2->oldY != -1) 2361 && ((dlPtr2->oldY + dlPtr2->height) > y) 2362 && (dlPtr2->oldY < (y + height))) { 2363 dlPtr2->oldY = -1; 2364 } 2365 } 2366 2367 /* 2368 * Now scroll the lines. This may generate damage which we 2369 * handle by calling TextInvalidateRegion to mark the display 2370 * blocks as stale. 2371 */ 2372 2373 damageRgn = TkCreateRegion(); 2374 if (TkScrollWindow(textPtr->tkwin, dInfoPtr->scrollGC, 2375 dInfoPtr->x, oldY, 2376 (dInfoPtr->maxX - dInfoPtr->x), height, 2377 0, y - oldY, damageRgn)) { 2378 TextInvalidateRegion(textPtr, damageRgn); 2379 } 2380 numCopies++; 2381 TkDestroyRegion(damageRgn); 2382 } 2383 2384 /* 2385 * Clear the REDRAW_PENDING flag here. This is actually pretty 2386 * tricky. We want to wait until *after* doing the scrolling, 2387 * since that could generate more areas to redraw and don't 2388 * want to reschedule a redisplay for them. On the other hand, 2389 * we can't wait until after all the redisplaying, because the 2390 * act of redisplaying could actually generate more redisplays 2391 * (e.g. in the case of a nested window with event bindings triggered 2392 * by redisplay). 2393 */ 2394 2395 dInfoPtr->flags &= ~REDRAW_PENDING; 2396 2397 /* 2398 * Redraw the borders if that's needed. 2399 */ 2400 2401 if (dInfoPtr->flags & REDRAW_BORDERS) { 2402 if (tkTextDebug) { 2403 Tcl_SetVar2(interp, "tk_textRedraw", (char *) NULL, "borders", 2404 TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT); 2405 } 2406 2407 if (textPtr->tkwin == NULL) { 2408 2409 /* 2410 * The widget has been deleted. Don't do anything. 2411 */ 2412 2413 goto end; 2414 } 2415 2416 Tk_Draw3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin), 2417 textPtr->border, textPtr->highlightWidth, 2418 textPtr->highlightWidth, 2419 Tk_Width(textPtr->tkwin) - 2*textPtr->highlightWidth, 2420 Tk_Height(textPtr->tkwin) - 2*textPtr->highlightWidth, 2421 textPtr->borderWidth, textPtr->relief); 2422 if (textPtr->highlightWidth != 0) { 2423 GC fgGC, bgGC; 2424 2425 bgGC = Tk_GCForColor(textPtr->highlightBgColorPtr, 2426 Tk_WindowId(textPtr->tkwin)); 2427 if (textPtr->flags & GOT_FOCUS) { 2428 fgGC = Tk_GCForColor(textPtr->highlightColorPtr, 2429 Tk_WindowId(textPtr->tkwin)); 2430 TkpDrawHighlightBorder(textPtr->tkwin, fgGC, bgGC, 2431 textPtr->highlightWidth, Tk_WindowId(textPtr->tkwin)); 2432 } else { 2433 TkpDrawHighlightBorder(textPtr->tkwin, bgGC, bgGC, 2434 textPtr->highlightWidth, Tk_WindowId(textPtr->tkwin)); 2435 } 2436 } 2437 borders = textPtr->borderWidth + textPtr->highlightWidth; 2438 if (textPtr->padY > 0) { 2439 Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin), 2440 textPtr->border, borders, borders, 2441 Tk_Width(textPtr->tkwin) - 2*borders, textPtr->padY, 2442 0, TK_RELIEF_FLAT); 2443 Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin), 2444 textPtr->border, borders, 2445 Tk_Height(textPtr->tkwin) - borders - textPtr->padY, 2446 Tk_Width(textPtr->tkwin) - 2*borders, 2447 textPtr->padY, 0, TK_RELIEF_FLAT); 2448 } 2449 if (textPtr->padX > 0) { 2450 Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin), 2451 textPtr->border, borders, borders + textPtr->padY, 2452 textPtr->padX, 2453 Tk_Height(textPtr->tkwin) - 2*borders -2*textPtr->padY, 2454 0, TK_RELIEF_FLAT); 2455 Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin), 2456 textPtr->border, 2457 Tk_Width(textPtr->tkwin) - borders - textPtr->padX, 2458 borders + textPtr->padY, textPtr->padX, 2459 Tk_Height(textPtr->tkwin) - 2*borders -2*textPtr->padY, 2460 0, TK_RELIEF_FLAT); 2461 } 2462 dInfoPtr->flags &= ~REDRAW_BORDERS; 2463 } 2464 2465 /* 2466 * Now we have to redraw the lines that couldn't be updated by 2467 * scrolling. First, compute the height of the largest line and 2468 * allocate an off-screen pixmap to use for double-buffered 2469 * displays. 2470 */ 2471 2472 maxHeight = -1; 2473 for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; 2474 dlPtr = dlPtr->nextPtr) { 2475 if ((dlPtr->height > maxHeight) && (dlPtr->oldY != dlPtr->y)) { 2476 maxHeight = dlPtr->height; 2477 } 2478 bottomY = dlPtr->y + dlPtr->height; 2479 } 2480 if (maxHeight > dInfoPtr->maxY) { 2481 maxHeight = dInfoPtr->maxY; 2482 } 2483 if (maxHeight > 0) { 2484#ifndef TK_NO_DOUBLE_BUFFERING 2485 pixmap = Tk_GetPixmap(Tk_Display(textPtr->tkwin), 2486 Tk_WindowId(textPtr->tkwin), Tk_Width(textPtr->tkwin), 2487 maxHeight, Tk_Depth(textPtr->tkwin)); 2488#else 2489 pixmap = Tk_WindowId(textPtr->tkwin); 2490#endif /* TK_NO_DOUBLE_BUFFERING */ 2491 for (prevPtr = NULL, dlPtr = textPtr->dInfoPtr->dLinePtr; 2492 (dlPtr != NULL) && (dlPtr->y < dInfoPtr->maxY); 2493 prevPtr = dlPtr, dlPtr = dlPtr->nextPtr) { 2494 if (dlPtr->chunkPtr == NULL) continue; 2495 if (dlPtr->oldY != dlPtr->y) { 2496 if (tkTextDebug) { 2497 char string[TK_POS_CHARS]; 2498 TkTextPrintIndex(&dlPtr->index, string); 2499 Tcl_SetVar2(textPtr->interp, "tk_textRedraw", 2500 (char *) NULL, string, 2501 TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT); 2502 } 2503 DisplayDLine(textPtr, dlPtr, prevPtr, pixmap); 2504 if (dInfoPtr->dLinesInvalidated) { 2505#ifndef TK_NO_DOUBLE_BUFFERING 2506 Tk_FreePixmap(Tk_Display(textPtr->tkwin), pixmap); 2507#endif /* TK_NO_DOUBLE_BUFFERING */ 2508 return; 2509 } 2510 dlPtr->oldY = dlPtr->y; 2511 dlPtr->flags &= ~NEW_LAYOUT; 2512 } 2513 /*prevPtr = dlPtr;*/ 2514 } 2515#ifndef TK_NO_DOUBLE_BUFFERING 2516 Tk_FreePixmap(Tk_Display(textPtr->tkwin), pixmap); 2517#endif /* TK_NO_DOUBLE_BUFFERING */ 2518 } 2519 2520 /* 2521 * See if we need to refresh the part of the window below the 2522 * last line of text (if there is any such area). Refresh the 2523 * padding area on the left too, since the insertion cursor might 2524 * have been displayed there previously). 2525 */ 2526 2527 if (dInfoPtr->topOfEof > dInfoPtr->maxY) { 2528 dInfoPtr->topOfEof = dInfoPtr->maxY; 2529 } 2530 if (bottomY < dInfoPtr->topOfEof) { 2531 if (tkTextDebug) { 2532 Tcl_SetVar2(textPtr->interp, "tk_textRedraw", 2533 (char *) NULL, "eof", 2534 TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT); 2535 } 2536 2537 if (textPtr->tkwin == NULL) { 2538 2539 /* 2540 * The widget has been deleted. Don't do anything. 2541 */ 2542 2543 goto end; 2544 } 2545 2546 Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin), 2547 textPtr->border, dInfoPtr->x - textPtr->padX, bottomY, 2548 dInfoPtr->maxX - (dInfoPtr->x - textPtr->padX), 2549 dInfoPtr->topOfEof-bottomY, 0, TK_RELIEF_FLAT); 2550 } 2551 dInfoPtr->topOfEof = bottomY; 2552 2553 doScrollbars: 2554 2555 /* 2556 * Update the vertical scrollbar, if there is one. Note: it's 2557 * important to clear REDRAW_PENDING here, just in case the 2558 * scroll procedure does something that requires redisplay. 2559 */ 2560 2561 if (textPtr->flags & UPDATE_SCROLLBARS) { 2562 textPtr->flags &= ~UPDATE_SCROLLBARS; 2563 if (textPtr->yScrollCmd != NULL) { 2564 GetYView(textPtr->interp, textPtr, 1); 2565 } 2566 2567 if (textPtr->tkwin == NULL) { 2568 2569 /* 2570 * The widget has been deleted. Don't do anything. 2571 */ 2572 2573 goto end; 2574 } 2575 2576 /* 2577 * Update the horizontal scrollbar, if any. 2578 */ 2579 2580 if (textPtr->xScrollCmd != NULL) { 2581 GetXView(textPtr->interp, textPtr, 1); 2582 } 2583 } 2584 2585end: 2586 Tcl_Release((ClientData) interp); 2587} 2588 2589/* 2590 *---------------------------------------------------------------------- 2591 * 2592 * TkTextEventuallyRepick -- 2593 * 2594 * This procedure is invoked whenever something happens that 2595 * could change the current character or the tags associated 2596 * with it. 2597 * 2598 * Results: 2599 * None. 2600 * 2601 * Side effects: 2602 * A repick is scheduled as an idle handler. 2603 * 2604 *---------------------------------------------------------------------- 2605 */ 2606 2607 /* ARGSUSED */ 2608void 2609TkTextEventuallyRepick(textPtr) 2610 TkText *textPtr; /* Widget record for text widget. */ 2611{ 2612 TextDInfo *dInfoPtr = textPtr->dInfoPtr; 2613 2614 dInfoPtr->flags |= REPICK_NEEDED; 2615 if (!(dInfoPtr->flags & REDRAW_PENDING)) { 2616 dInfoPtr->flags |= REDRAW_PENDING; 2617 Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr); 2618 } 2619} 2620 2621/* 2622 *---------------------------------------------------------------------- 2623 * 2624 * TkTextRedrawRegion -- 2625 * 2626 * This procedure is invoked to schedule a redisplay for a given 2627 * region of a text widget. The redisplay itself may not occur 2628 * immediately: it's scheduled as a when-idle handler. 2629 * 2630 * Results: 2631 * None. 2632 * 2633 * Side effects: 2634 * Information will eventually be redrawn on the screen. 2635 * 2636 *---------------------------------------------------------------------- 2637 */ 2638 2639 /* ARGSUSED */ 2640void 2641TkTextRedrawRegion(textPtr, x, y, width, height) 2642 TkText *textPtr; /* Widget record for text widget. */ 2643 int x, y; /* Coordinates of upper-left corner of area 2644 * to be redrawn, in pixels relative to 2645 * textPtr's window. */ 2646 int width, height; /* Width and height of area to be redrawn. */ 2647{ 2648 TextDInfo *dInfoPtr = textPtr->dInfoPtr; 2649 TkRegion damageRgn = TkCreateRegion(); 2650 XRectangle rect; 2651 2652 rect.x = x; 2653 rect.y = y; 2654 rect.width = width; 2655 rect.height = height; 2656 TkUnionRectWithRegion(&rect, damageRgn, damageRgn); 2657 2658 TextInvalidateRegion(textPtr, damageRgn); 2659 2660 if (!(dInfoPtr->flags & REDRAW_PENDING)) { 2661 dInfoPtr->flags |= REDRAW_PENDING; 2662 Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr); 2663 } 2664 TkDestroyRegion(damageRgn); 2665} 2666 2667/* 2668 *---------------------------------------------------------------------- 2669 * 2670 * TextInvalidateRegion -- 2671 * 2672 * Mark a region of text as invalid. 2673 * 2674 * Results: 2675 * None. 2676 * 2677 * Side effects: 2678 * Updates the display information for the text widget. 2679 * 2680 *---------------------------------------------------------------------- 2681 */ 2682 2683static void 2684TextInvalidateRegion(textPtr, region) 2685 TkText *textPtr; /* Widget record for text widget. */ 2686 TkRegion region; /* Region of area to redraw. */ 2687{ 2688 register DLine *dlPtr; 2689 TextDInfo *dInfoPtr = textPtr->dInfoPtr; 2690 int maxY, inset; 2691 XRectangle rect; 2692 2693 /* 2694 * Find all lines that overlap the given region and mark them for 2695 * redisplay. 2696 */ 2697 2698 TkClipBox(region, &rect); 2699 maxY = rect.y + rect.height; 2700 for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; 2701 dlPtr = dlPtr->nextPtr) { 2702 if ((dlPtr->oldY != -1) && (TkRectInRegion(region, rect.x, dlPtr->y, 2703 rect.width, (unsigned int) dlPtr->height) != RectangleOut)) { 2704 dlPtr->oldY = -1; 2705 } 2706 } 2707 if (dInfoPtr->topOfEof < maxY) { 2708 dInfoPtr->topOfEof = maxY; 2709 } 2710 2711 /* 2712 * Schedule the redisplay operation if there isn't one already 2713 * scheduled. 2714 */ 2715 2716 inset = textPtr->borderWidth + textPtr->highlightWidth; 2717 if ((rect.x < (inset + textPtr->padX)) 2718 || (rect.y < (inset + textPtr->padY)) 2719 || ((int) (rect.x + rect.width) > (Tk_Width(textPtr->tkwin) 2720 - inset - textPtr->padX)) 2721 || (maxY > (Tk_Height(textPtr->tkwin) - inset - textPtr->padY))) { 2722 dInfoPtr->flags |= REDRAW_BORDERS; 2723 } 2724} 2725 2726/* 2727 *---------------------------------------------------------------------- 2728 * 2729 * TkTextChanged -- 2730 * 2731 * This procedure is invoked when info in a text widget is about 2732 * to be modified in a way that changes how it is displayed (e.g. 2733 * characters were inserted or deleted, or tag information was 2734 * changed). This procedure must be called *before* a change is 2735 * made, so that indexes in the display information are still 2736 * valid. 2737 * 2738 * Results: 2739 * None. 2740 * 2741 * Side effects: 2742 * The range of character between index1Ptr (inclusive) and 2743 * index2Ptr (exclusive) will be redisplayed at some point in the 2744 * future (the actual redisplay is scheduled as a when-idle handler). 2745 * 2746 *---------------------------------------------------------------------- 2747 */ 2748 2749void 2750TkTextChanged(textPtr, index1Ptr, index2Ptr) 2751 TkText *textPtr; /* Widget record for text widget. */ 2752 TkTextIndex *index1Ptr; /* Index of first character to redisplay. */ 2753 TkTextIndex *index2Ptr; /* Index of character just after last one 2754 * to redisplay. */ 2755{ 2756 TextDInfo *dInfoPtr = textPtr->dInfoPtr; 2757 DLine *firstPtr, *lastPtr; 2758 TkTextIndex rounded; 2759 2760 /* 2761 * Schedule both a redisplay and a recomputation of display information. 2762 * It's done here rather than the end of the procedure for two reasons: 2763 * 2764 * 1. If there are no display lines to update we'll want to return 2765 * immediately, well before the end of the procedure. 2766 * 2. It's important to arrange for the redisplay BEFORE calling 2767 * FreeDLines. The reason for this is subtle and has to do with 2768 * embedded windows. The chunk delete procedure for an embedded 2769 * window will schedule an idle handler to unmap the window. 2770 * However, we want the idle handler for redisplay to be called 2771 * first, so that it can put the embedded window back on the screen 2772 * again (if appropriate). This will prevent the window from ever 2773 * being unmapped, and thereby avoid flashing. 2774 */ 2775 2776 if (!(dInfoPtr->flags & REDRAW_PENDING)) { 2777 Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr); 2778 } 2779 dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED; 2780 2781 /* 2782 * Find the DLines corresponding to index1Ptr and index2Ptr. There 2783 * is one tricky thing here, which is that we have to relayout in 2784 * units of whole text lines: round index1Ptr back to the beginning 2785 * of its text line, and include all the display lines after index2, 2786 * up to the end of its text line. This is necessary because the 2787 * indices stored in the display lines will no longer be valid. It's 2788 * also needed because any edit could change the way lines wrap. 2789 */ 2790 2791 rounded = *index1Ptr; 2792 rounded.byteIndex = 0; 2793 firstPtr = FindDLine(dInfoPtr->dLinePtr, &rounded); 2794 if (firstPtr == NULL) { 2795 return; 2796 } 2797 lastPtr = FindDLine(dInfoPtr->dLinePtr, index2Ptr); 2798 while ((lastPtr != NULL) 2799 && (lastPtr->index.linePtr == index2Ptr->linePtr)) { 2800 lastPtr = lastPtr->nextPtr; 2801 } 2802 2803 /* 2804 * Delete all the DLines from firstPtr up to but not including lastPtr. 2805 */ 2806 2807 FreeDLines(textPtr, firstPtr, lastPtr, 1); 2808} 2809 2810/* 2811 *---------------------------------------------------------------------- 2812 * 2813 * TkTextRedrawTag -- 2814 * 2815 * This procedure is invoked to request a redraw of all characters 2816 * in a given range that have a particular tag on or off. It's 2817 * called, for example, when tag options change. 2818 * 2819 * Results: 2820 * None. 2821 * 2822 * Side effects: 2823 * Information on the screen may be redrawn, and the layout of 2824 * the screen may change. 2825 * 2826 *---------------------------------------------------------------------- 2827 */ 2828 2829void 2830TkTextRedrawTag(textPtr, index1Ptr, index2Ptr, tagPtr, withTag) 2831 TkText *textPtr; /* Widget record for text widget. */ 2832 TkTextIndex *index1Ptr; /* First character in range to consider 2833 * for redisplay. NULL means start at 2834 * beginning of text. */ 2835 TkTextIndex *index2Ptr; /* Character just after last one to consider 2836 * for redisplay. NULL means process all 2837 * the characters in the text. */ 2838 TkTextTag *tagPtr; /* Information about tag. */ 2839 int withTag; /* 1 means redraw characters that have the 2840 * tag, 0 means redraw those without. */ 2841{ 2842 register DLine *dlPtr; 2843 DLine *endPtr; 2844 int tagOn; 2845 TkTextSearch search; 2846 TextDInfo *dInfoPtr = textPtr->dInfoPtr; 2847 TkTextIndex *curIndexPtr; 2848 TkTextIndex endOfText, *endIndexPtr; 2849 2850 /* 2851 * Round up the starting position if it's before the first line 2852 * visible on the screen (we only care about what's on the screen). 2853 */ 2854 2855 dlPtr = dInfoPtr->dLinePtr; 2856 if (dlPtr == NULL) { 2857 return; 2858 } 2859 if ((index1Ptr == NULL) || (TkTextIndexCmp(&dlPtr->index, index1Ptr) > 0)) { 2860 index1Ptr = &dlPtr->index; 2861 } 2862 2863 /* 2864 * Set the stopping position if it wasn't specified. 2865 */ 2866 2867 if (index2Ptr == NULL) { 2868 index2Ptr = TkTextMakeByteIndex(textPtr->tree, 2869 TkBTreeNumLines(textPtr->tree), 0, &endOfText); 2870 } 2871 2872 /* 2873 * Initialize a search through all transitions on the tag, starting 2874 * with the first transition where the tag's current state is different 2875 * from what it will eventually be. 2876 */ 2877 2878 TkBTreeStartSearch(index1Ptr, index2Ptr, tagPtr, &search); 2879 /* 2880 * Make our own curIndex because at this point search.curIndex 2881 * may not equal index1Ptr->curIndex in the case the first tag toggle 2882 * comes after index1Ptr (See the use of FindTagStart in TkBTreeStartSearch) 2883 */ 2884 curIndexPtr = index1Ptr; 2885 tagOn = TkBTreeCharTagged(index1Ptr, tagPtr); 2886 if (tagOn != withTag) { 2887 if (!TkBTreeNextTag(&search)) { 2888 return; 2889 } 2890 curIndexPtr = &search.curIndex; 2891 } 2892 2893 /* 2894 * Schedule a redisplay and layout recalculation if they aren't 2895 * already pending. This has to be done before calling FreeDLines, 2896 * for the reason given in TkTextChanged. 2897 */ 2898 2899 if (!(dInfoPtr->flags & REDRAW_PENDING)) { 2900 Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr); 2901 } 2902 dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED; 2903 2904 /* 2905 * Each loop through the loop below is for one range of characters 2906 * where the tag's current state is different than its eventual 2907 * state. At the top of the loop, search contains information about 2908 * the first character in the range. 2909 */ 2910 2911 while (1) { 2912 /* 2913 * Find the first DLine structure in the range. Note: if the 2914 * desired character isn't the first in its text line, then look 2915 * for the character just before it instead. This is needed to 2916 * handle the case where the first character of a wrapped 2917 * display line just got smaller, so that it now fits on the 2918 * line before: need to relayout the line containing the 2919 * previous character. 2920 */ 2921 2922 if (curIndexPtr->byteIndex == 0) { 2923 dlPtr = FindDLine(dlPtr, curIndexPtr); 2924 } else { 2925 TkTextIndex tmp; 2926 2927 tmp = *curIndexPtr; 2928 tmp.byteIndex -= 1; 2929 dlPtr = FindDLine(dlPtr, &tmp); 2930 } 2931 if (dlPtr == NULL) { 2932 break; 2933 } 2934 2935 /* 2936 * Find the first DLine structure that's past the end of the range. 2937 */ 2938 2939 if (!TkBTreeNextTag(&search)) { 2940 endIndexPtr = index2Ptr; 2941 } else { 2942 curIndexPtr = &search.curIndex; 2943 endIndexPtr = curIndexPtr; 2944 } 2945 endPtr = FindDLine(dlPtr, endIndexPtr); 2946 if ((endPtr != NULL) && (endPtr->index.linePtr == endIndexPtr->linePtr) 2947 && (endPtr->index.byteIndex < endIndexPtr->byteIndex)) { 2948 endPtr = endPtr->nextPtr; 2949 } 2950 2951 /* 2952 * Delete all of the display lines in the range, so that they'll 2953 * be re-layed out and redrawn. 2954 */ 2955 2956 FreeDLines(textPtr, dlPtr, endPtr, 1); 2957 dlPtr = endPtr; 2958 2959 /* 2960 * Find the first text line in the next range. 2961 */ 2962 2963 if (!TkBTreeNextTag(&search)) { 2964 break; 2965 } 2966 } 2967} 2968 2969/* 2970 *---------------------------------------------------------------------- 2971 * 2972 * TkTextRelayoutWindow -- 2973 * 2974 * This procedure is called when something has happened that 2975 * invalidates the whole layout of characters on the screen, such 2976 * as a change in a configuration option for the overall text 2977 * widget or a change in the window size. It causes all display 2978 * information to be recomputed and the window to be redrawn. 2979 * 2980 * Results: 2981 * None. 2982 * 2983 * Side effects: 2984 * All the display information will be recomputed for the window 2985 * and the window will be redrawn. 2986 * 2987 *---------------------------------------------------------------------- 2988 */ 2989 2990void 2991TkTextRelayoutWindow(textPtr) 2992 TkText *textPtr; /* Widget record for text widget. */ 2993{ 2994 TextDInfo *dInfoPtr = textPtr->dInfoPtr; 2995 GC new; 2996 XGCValues gcValues; 2997 2998 /* 2999 * Schedule the window redisplay. See TkTextChanged for the 3000 * reason why this has to be done before any calls to FreeDLines. 3001 */ 3002 3003 if (!(dInfoPtr->flags & REDRAW_PENDING)) { 3004 Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr); 3005 } 3006 dInfoPtr->flags |= REDRAW_PENDING|REDRAW_BORDERS|DINFO_OUT_OF_DATE 3007 |REPICK_NEEDED; 3008 3009 /* 3010 * (Re-)create the graphics context for drawing the traversal 3011 * highlight. 3012 */ 3013 3014 gcValues.graphics_exposures = False; 3015 new = Tk_GetGC(textPtr->tkwin, GCGraphicsExposures, &gcValues); 3016 if (dInfoPtr->copyGC != None) { 3017 Tk_FreeGC(textPtr->display, dInfoPtr->copyGC); 3018 } 3019 dInfoPtr->copyGC = new; 3020 3021 /* 3022 * Throw away all the current layout information. 3023 */ 3024 3025 FreeDLines(textPtr, dInfoPtr->dLinePtr, (DLine *) NULL, 1); 3026 dInfoPtr->dLinePtr = NULL; 3027 3028 /* 3029 * Recompute some overall things for the layout. Even if the 3030 * window gets very small, pretend that there's at least one 3031 * pixel of drawing space in it. 3032 */ 3033 3034 if (textPtr->highlightWidth < 0) { 3035 textPtr->highlightWidth = 0; 3036 } 3037 dInfoPtr->x = textPtr->highlightWidth + textPtr->borderWidth 3038 + textPtr->padX; 3039 dInfoPtr->y = textPtr->highlightWidth + textPtr->borderWidth 3040 + textPtr->padY; 3041 dInfoPtr->maxX = Tk_Width(textPtr->tkwin) - textPtr->highlightWidth 3042 - textPtr->borderWidth - textPtr->padX; 3043 if (dInfoPtr->maxX <= dInfoPtr->x) { 3044 dInfoPtr->maxX = dInfoPtr->x + 1; 3045 } 3046 dInfoPtr->maxY = Tk_Height(textPtr->tkwin) - textPtr->highlightWidth 3047 - textPtr->borderWidth - textPtr->padY; 3048 if (dInfoPtr->maxY <= dInfoPtr->y) { 3049 dInfoPtr->maxY = dInfoPtr->y + 1; 3050 } 3051 dInfoPtr->topOfEof = dInfoPtr->maxY; 3052 3053 /* 3054 * If the upper-left character isn't the first in a line, recompute 3055 * it. This is necessary because a change in the window's size 3056 * or options could change the way lines wrap. 3057 */ 3058 3059 if (textPtr->topIndex.byteIndex != 0) { 3060 MeasureUp(textPtr, &textPtr->topIndex, 0, &textPtr->topIndex); 3061 } 3062 3063 /* 3064 * Invalidate cached scrollbar positions, so that scrollbars 3065 * sliders will be udpated. 3066 */ 3067 3068 dInfoPtr->xScrollFirst = dInfoPtr->xScrollLast = -1; 3069 dInfoPtr->yScrollFirst = dInfoPtr->yScrollLast = -1; 3070} 3071 3072/* 3073 *---------------------------------------------------------------------- 3074 * 3075 * TkTextSetYView -- 3076 * 3077 * This procedure is called to specify what lines are to be 3078 * displayed in a text widget. 3079 * 3080 * Results: 3081 * None. 3082 * 3083 * Side effects: 3084 * The display will (eventually) be updated so that the position 3085 * given by "indexPtr" is visible on the screen at the position 3086 * determined by "pickPlace". 3087 * 3088 *---------------------------------------------------------------------- 3089 */ 3090 3091void 3092TkTextSetYView(textPtr, indexPtr, pickPlace) 3093 TkText *textPtr; /* Widget record for text widget. */ 3094 TkTextIndex *indexPtr; /* Position that is to appear somewhere 3095 * in the view. */ 3096 int pickPlace; /* 0 means topLine must appear at top of 3097 * screen. 1 means we get to pick where it 3098 * appears: minimize screen motion or else 3099 * display line at center of screen. */ 3100{ 3101 TextDInfo *dInfoPtr = textPtr->dInfoPtr; 3102 register DLine *dlPtr; 3103 int bottomY, close, lineIndex; 3104 TkTextIndex tmpIndex, rounded; 3105 Tk_FontMetrics fm; 3106 3107 /* 3108 * If the specified position is the extra line at the end of the 3109 * text, round it back to the last real line. 3110 */ 3111 3112 lineIndex = TkBTreeLineIndex(indexPtr->linePtr); 3113 if (lineIndex == TkBTreeNumLines(indexPtr->tree)) { 3114 TkTextIndexBackChars(indexPtr, 1, &rounded); 3115 indexPtr = &rounded; 3116 } 3117 3118 if (!pickPlace) { 3119 /* 3120 * The specified position must go at the top of the screen. 3121 * Just leave all the DLine's alone: we may be able to reuse 3122 * some of the information that's currently on the screen 3123 * without redisplaying it all. 3124 */ 3125 3126 if (indexPtr->byteIndex == 0) { 3127 textPtr->topIndex = *indexPtr; 3128 } else { 3129 MeasureUp(textPtr, indexPtr, 0, &textPtr->topIndex); 3130 } 3131 goto scheduleUpdate; 3132 } 3133 3134 /* 3135 * We have to pick where to display the index. First, bring 3136 * the display information up to date and see if the index will be 3137 * completely visible in the current screen configuration. If so 3138 * then there's nothing to do. 3139 */ 3140 3141 if (dInfoPtr->flags & DINFO_OUT_OF_DATE) { 3142 UpdateDisplayInfo(textPtr); 3143 } 3144 dlPtr = FindDLine(dInfoPtr->dLinePtr, indexPtr); 3145 if (dlPtr != NULL) { 3146 if ((dlPtr->y + dlPtr->height) > dInfoPtr->maxY) { 3147 /* 3148 * Part of the line hangs off the bottom of the screen; 3149 * pretend the whole line is off-screen. 3150 */ 3151 3152 dlPtr = NULL; 3153 } else if ((dlPtr->index.linePtr == indexPtr->linePtr) 3154 && (dlPtr->index.byteIndex <= indexPtr->byteIndex)) { 3155 return; 3156 } 3157 } 3158 3159 /* 3160 * The desired line isn't already on-screen. Figure out what 3161 * it means to be "close" to the top or bottom of the screen. 3162 * Close means within 1/3 of the screen height or within three 3163 * lines, whichever is greater. Add one extra line also, to 3164 * account for the way MeasureUp rounds. 3165 */ 3166 3167 Tk_GetFontMetrics(textPtr->tkfont, &fm); 3168 bottomY = (dInfoPtr->y + dInfoPtr->maxY + fm.linespace)/2; 3169 close = (dInfoPtr->maxY - dInfoPtr->y)/3; 3170 if (close < 3*fm.linespace) { 3171 close = 3*fm.linespace; 3172 } 3173 close += fm.linespace; 3174 if (dlPtr != NULL) { 3175 /* 3176 * The desired line is above the top of screen. If it is 3177 * "close" to the top of the window then make it the top 3178 * line on the screen. 3179 */ 3180 3181 MeasureUp(textPtr, &textPtr->topIndex, close, &tmpIndex); 3182 if (TkTextIndexCmp(&tmpIndex, indexPtr) <= 0) { 3183 MeasureUp(textPtr, indexPtr, 0, &textPtr->topIndex); 3184 goto scheduleUpdate; 3185 } 3186 } else { 3187 /* 3188 * The desired line is below the bottom of the screen. If it is 3189 * "close" to the bottom of the screen then position it at the 3190 * bottom of the screen. 3191 */ 3192 3193 MeasureUp(textPtr, indexPtr, close, &tmpIndex); 3194 if (FindDLine(dInfoPtr->dLinePtr, &tmpIndex) != NULL) { 3195 bottomY = dInfoPtr->maxY - dInfoPtr->y; 3196 } 3197 } 3198 3199 /* 3200 * Our job now is to arrange the display so that indexPtr appears 3201 * as low on the screen as possible but with its bottom no lower 3202 * than bottomY. BottomY is the bottom of the window if the 3203 * desired line is just below the current screen, otherwise it 3204 * is a half-line lower than the center of the window. 3205 */ 3206 3207 MeasureUp(textPtr, indexPtr, bottomY, &textPtr->topIndex); 3208 3209 scheduleUpdate: 3210 if (!(dInfoPtr->flags & REDRAW_PENDING)) { 3211 Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr); 3212 } 3213 dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED; 3214} 3215 3216/* 3217 *-------------------------------------------------------------- 3218 * 3219 * MeasureUp -- 3220 * 3221 * Given one index, find the index of the first character 3222 * on the highest display line that would be displayed no more 3223 * than "distance" pixels above the given index. 3224 * 3225 * Results: 3226 * *dstPtr is filled in with the index of the first character 3227 * on a display line. The display line is found by measuring 3228 * up "distance" pixels above the pixel just below an imaginary 3229 * display line that contains srcPtr. If the display line 3230 * that covers this coordinate actually extends above the 3231 * coordinate, then return the index of the next lower line 3232 * instead (i.e. the returned index will be completely visible 3233 * at or below the given y-coordinate). 3234 * 3235 * Side effects: 3236 * None. 3237 * 3238 *-------------------------------------------------------------- 3239 */ 3240 3241static void 3242MeasureUp(textPtr, srcPtr, distance, dstPtr) 3243 TkText *textPtr; /* Text widget in which to measure. */ 3244 TkTextIndex *srcPtr; /* Index of character from which to start 3245 * measuring. */ 3246 int distance; /* Vertical distance in pixels measured 3247 * from the pixel just below the lowest 3248 * one in srcPtr's line. */ 3249 TkTextIndex *dstPtr; /* Index to fill in with result. */ 3250{ 3251 int lineNum; /* Number of current line. */ 3252 int bytesToCount; /* Maximum number of bytes to measure in 3253 * current line. */ 3254 TkTextIndex bestIndex = {NULL, NULL, 0}; /* Best candidate seen so far for 3255 * result. Silence gcc 4 warning */ 3256 TkTextIndex index; 3257 DLine *dlPtr, *lowestPtr; 3258 int noBestYet; /* 1 means bestIndex hasn't been set. */ 3259 3260 noBestYet = 1; 3261 bytesToCount = srcPtr->byteIndex + 1; 3262 index.tree = srcPtr->tree; 3263 for (lineNum = TkBTreeLineIndex(srcPtr->linePtr); lineNum >= 0; 3264 lineNum--) { 3265 /* 3266 * Layout an entire text line (potentially > 1 display line). 3267 * For the first line, which contains srcPtr, only layout the 3268 * part up through srcPtr (bytesToCount is non-infinite to 3269 * accomplish this). Make a list of all the display lines 3270 * in backwards order (the lowest DLine on the screen is first 3271 * in the list). 3272 */ 3273 3274 index.linePtr = TkBTreeFindLine(srcPtr->tree, lineNum); 3275 index.byteIndex = 0; 3276 lowestPtr = NULL; 3277 do { 3278 dlPtr = LayoutDLine(textPtr, &index); 3279 dlPtr->nextPtr = lowestPtr; 3280 lowestPtr = dlPtr; 3281 TkTextIndexForwBytes(&index, dlPtr->byteCount, &index); 3282 bytesToCount -= dlPtr->byteCount; 3283 } while ((bytesToCount > 0) && (index.linePtr == dlPtr->index.linePtr)); 3284 3285 /* 3286 * Scan through the display lines to see if we've covered enough 3287 * vertical distance. If so, save the starting index for the 3288 * line at the desired location. 3289 */ 3290 3291 for (dlPtr = lowestPtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) { 3292 distance -= dlPtr->height; 3293 if (distance < 0) { 3294 *dstPtr = (noBestYet) ? dlPtr->index : bestIndex; 3295 break; 3296 } 3297 bestIndex = dlPtr->index; 3298 noBestYet = 0; 3299 } 3300 3301 /* 3302 * Discard the display lines, then either return or prepare 3303 * for the next display line to lay out. 3304 */ 3305 3306 FreeDLines(textPtr, lowestPtr, (DLine *) NULL, 0); 3307 if (distance < 0) { 3308 return; 3309 } 3310 bytesToCount = INT_MAX; /* Consider all chars. in next line. */ 3311 } 3312 3313 /* 3314 * Ran off the beginning of the text. Return the first character 3315 * in the text. 3316 */ 3317 3318 TkTextMakeByteIndex(textPtr->tree, 0, 0, dstPtr); 3319} 3320 3321/* 3322 *-------------------------------------------------------------- 3323 * 3324 * TkTextSeeCmd -- 3325 * 3326 * This procedure is invoked to process the "see" option for 3327 * the widget command for text widgets. See the user documentation 3328 * for details on what it does. 3329 * 3330 * Results: 3331 * A standard Tcl result. 3332 * 3333 * Side effects: 3334 * See the user documentation. 3335 * 3336 *-------------------------------------------------------------- 3337 */ 3338 3339int 3340TkTextSeeCmd(textPtr, interp, argc, argv) 3341 TkText *textPtr; /* Information about text widget. */ 3342 Tcl_Interp *interp; /* Current interpreter. */ 3343 int argc; /* Number of arguments. */ 3344 CONST char **argv; /* Argument strings. Someone else has already 3345 * parsed this command enough to know that 3346 * argv[1] is "see". */ 3347{ 3348 TextDInfo *dInfoPtr = textPtr->dInfoPtr; 3349 TkTextIndex index; 3350 int x, y, width, height, lineWidth, byteCount, oneThird, delta; 3351 DLine *dlPtr; 3352 TkTextDispChunk *chunkPtr; 3353 3354 if (argc != 3) { 3355 Tcl_AppendResult(interp, "wrong # args: should be \"", 3356 argv[0], " see index\"", (char *) NULL); 3357 return TCL_ERROR; 3358 } 3359 if (TkTextGetIndex(interp, textPtr, argv[2], &index) != TCL_OK) { 3360 return TCL_ERROR; 3361 } 3362 3363 /* 3364 * If the specified position is the extra line at the end of the 3365 * text, round it back to the last real line. 3366 */ 3367 3368 if (TkBTreeLineIndex(index.linePtr) == TkBTreeNumLines(index.tree)) { 3369 TkTextIndexBackChars(&index, 1, &index); 3370 } 3371 3372 /* 3373 * First get the desired position into the vertical range of the window. 3374 */ 3375 3376 TkTextSetYView(textPtr, &index, 1); 3377 3378 /* 3379 * Now make sure that the character is in view horizontally. 3380 */ 3381 3382 if (dInfoPtr->flags & DINFO_OUT_OF_DATE) { 3383 UpdateDisplayInfo(textPtr); 3384 } 3385 lineWidth = dInfoPtr->maxX - dInfoPtr->x; 3386 if (dInfoPtr->maxLength < lineWidth) { 3387 return TCL_OK; 3388 } 3389 3390 /* 3391 * Find the chunk that contains the desired index. 3392 * dlPtr may be NULL if the widget is not mapped. [Bug #641778] 3393 */ 3394 3395 dlPtr = FindDLine(dInfoPtr->dLinePtr, &index); 3396 if (dlPtr == NULL) { 3397 return TCL_OK; 3398 } 3399 3400 byteCount = index.byteIndex - dlPtr->index.byteIndex; 3401 for (chunkPtr = dlPtr->chunkPtr; chunkPtr != NULL ; 3402 chunkPtr = chunkPtr->nextPtr) { 3403 if (byteCount < chunkPtr->numBytes) { 3404 break; 3405 } 3406 byteCount -= chunkPtr->numBytes; 3407 } 3408 3409 /* 3410 * Call a chunk-specific procedure to find the horizontal range of 3411 * the character within the chunk. 3412 * chunkPtr is NULL if trying to see in elided region. 3413 */ 3414 3415 if (chunkPtr != NULL) { 3416 (*chunkPtr->bboxProc)(chunkPtr, byteCount, 3417 dlPtr->y + dlPtr->spaceAbove, 3418 dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow, 3419 dlPtr->baseline - dlPtr->spaceAbove, &x, &y, &width, 3420 &height); 3421 delta = x - dInfoPtr->curPixelOffset; 3422 oneThird = lineWidth/3; 3423 if (delta < 0) { 3424 if (delta < -oneThird) { 3425 dInfoPtr->newByteOffset = (x - lineWidth/2) 3426 / textPtr->charWidth; 3427 } else { 3428 dInfoPtr->newByteOffset -= ((-delta) + textPtr->charWidth - 1) 3429 / textPtr->charWidth; 3430 } 3431 } else { 3432 delta -= (lineWidth - width); 3433 if (delta > 0) { 3434 if (delta > oneThird) { 3435 dInfoPtr->newByteOffset = (x - lineWidth/2) 3436 / textPtr->charWidth; 3437 } else { 3438 dInfoPtr->newByteOffset += (delta + textPtr->charWidth - 1) 3439 / textPtr->charWidth; 3440 } 3441 } else { 3442 return TCL_OK; 3443 } 3444 } 3445 } 3446 dInfoPtr->flags |= DINFO_OUT_OF_DATE; 3447 if (!(dInfoPtr->flags & REDRAW_PENDING)) { 3448 dInfoPtr->flags |= REDRAW_PENDING; 3449 Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr); 3450 } 3451 return TCL_OK; 3452} 3453 3454/* 3455 *-------------------------------------------------------------- 3456 * 3457 * TkTextXviewCmd -- 3458 * 3459 * This procedure is invoked to process the "xview" option for 3460 * the widget command for text widgets. See the user documentation 3461 * for details on what it does. 3462 * 3463 * Results: 3464 * A standard Tcl result. 3465 * 3466 * Side effects: 3467 * See the user documentation. 3468 * 3469 *-------------------------------------------------------------- 3470 */ 3471 3472int 3473TkTextXviewCmd(textPtr, interp, argc, argv) 3474 TkText *textPtr; /* Information about text widget. */ 3475 Tcl_Interp *interp; /* Current interpreter. */ 3476 int argc; /* Number of arguments. */ 3477 CONST char **argv; /* Argument strings. Someone else has already 3478 * parsed this command enough to know that 3479 * argv[1] is "xview". */ 3480{ 3481 TextDInfo *dInfoPtr = textPtr->dInfoPtr; 3482 int type, charsPerPage, count, newOffset; 3483 double fraction; 3484 3485 if (dInfoPtr->flags & DINFO_OUT_OF_DATE) { 3486 UpdateDisplayInfo(textPtr); 3487 } 3488 3489 if (argc == 2) { 3490 GetXView(interp, textPtr, 0); 3491 return TCL_OK; 3492 } 3493 3494 newOffset = dInfoPtr->newByteOffset; 3495 type = Tk_GetScrollInfo(interp, argc, argv, &fraction, &count); 3496 switch (type) { 3497 case TK_SCROLL_ERROR: 3498 return TCL_ERROR; 3499 case TK_SCROLL_MOVETO: 3500 if (fraction > 1.0) { 3501 fraction = 1.0; 3502 } 3503 if (fraction < 0) { 3504 fraction = 0; 3505 } 3506 newOffset = (int) (((fraction * dInfoPtr->maxLength) / textPtr->charWidth) 3507 + 0.5); 3508 break; 3509 case TK_SCROLL_PAGES: 3510 charsPerPage = ((dInfoPtr->maxX - dInfoPtr->x) / textPtr->charWidth) 3511 - 2; 3512 if (charsPerPage < 1) { 3513 charsPerPage = 1; 3514 } 3515 newOffset += charsPerPage * count; 3516 break; 3517 case TK_SCROLL_UNITS: 3518 newOffset += count; 3519 break; 3520 } 3521 3522 dInfoPtr->newByteOffset = newOffset; 3523 dInfoPtr->flags |= DINFO_OUT_OF_DATE; 3524 if (!(dInfoPtr->flags & REDRAW_PENDING)) { 3525 dInfoPtr->flags |= REDRAW_PENDING; 3526 Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr); 3527 } 3528 return TCL_OK; 3529} 3530 3531/* 3532 *---------------------------------------------------------------------- 3533 * 3534 * ScrollByLines -- 3535 * 3536 * This procedure is called to scroll a text widget up or down 3537 * by a given number of lines. 3538 * 3539 * Results: 3540 * None. 3541 * 3542 * Side effects: 3543 * The view in textPtr's window changes to reflect the value 3544 * of "offset". 3545 * 3546 *---------------------------------------------------------------------- 3547 */ 3548 3549static void 3550ScrollByLines(textPtr, offset) 3551 TkText *textPtr; /* Widget to scroll. */ 3552 int offset; /* Amount by which to scroll, in *screen* 3553 * lines. Positive means that information 3554 * later in text becomes visible, negative 3555 * means that information earlier in the 3556 * text becomes visible. */ 3557{ 3558 int i, bytesToCount, lineNum; 3559 TkTextIndex new, index; 3560 TkTextLine *lastLinePtr; 3561 TextDInfo *dInfoPtr = textPtr->dInfoPtr; 3562 DLine *dlPtr, *lowestPtr; 3563 3564 if (offset < 0) { 3565 /* 3566 * Must scroll up (to show earlier information in the text). 3567 * The code below is similar to that in MeasureUp, except that 3568 * it counts lines instead of pixels. 3569 */ 3570 3571 bytesToCount = textPtr->topIndex.byteIndex + 1; 3572 index.tree = textPtr->tree; 3573 offset--; /* Skip line containing topIndex. */ 3574 for (lineNum = TkBTreeLineIndex(textPtr->topIndex.linePtr); 3575 lineNum >= 0; lineNum--) { 3576 index.linePtr = TkBTreeFindLine(textPtr->tree, lineNum); 3577 index.byteIndex = 0; 3578 lowestPtr = NULL; 3579 do { 3580 dlPtr = LayoutDLine(textPtr, &index); 3581 dlPtr->nextPtr = lowestPtr; 3582 lowestPtr = dlPtr; 3583 TkTextIndexForwBytes(&index, dlPtr->byteCount, &index); 3584 bytesToCount -= dlPtr->byteCount; 3585 } while ((bytesToCount > 0) 3586 && (index.linePtr == dlPtr->index.linePtr)); 3587 3588 for (dlPtr = lowestPtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) { 3589 offset++; 3590 if (offset == 0) { 3591 textPtr->topIndex = dlPtr->index; 3592 break; 3593 } 3594 } 3595 3596 /* 3597 * Discard the display lines, then either return or prepare 3598 * for the next display line to lay out. 3599 */ 3600 3601 FreeDLines(textPtr, lowestPtr, (DLine *) NULL, 0); 3602 if (offset >= 0) { 3603 goto scheduleUpdate; 3604 } 3605 bytesToCount = INT_MAX; 3606 } 3607 3608 /* 3609 * Ran off the beginning of the text. Return the first character 3610 * in the text. 3611 */ 3612 3613 TkTextMakeByteIndex(textPtr->tree, 0, 0, &textPtr->topIndex); 3614 } else { 3615 /* 3616 * Scrolling down, to show later information in the text. 3617 * Just count lines from the current top of the window. 3618 */ 3619 3620 lastLinePtr = TkBTreeFindLine(textPtr->tree, 3621 TkBTreeNumLines(textPtr->tree)); 3622 for (i = 0; i < offset; i++) { 3623 dlPtr = LayoutDLine(textPtr, &textPtr->topIndex); 3624 if (dlPtr->length == 0 && dlPtr->height == 0) offset++; 3625 dlPtr->nextPtr = NULL; 3626 TkTextIndexForwBytes(&textPtr->topIndex, dlPtr->byteCount, &new); 3627 FreeDLines(textPtr, dlPtr, (DLine *) NULL, 0); 3628 if (new.linePtr == lastLinePtr) { 3629 break; 3630 } 3631 textPtr->topIndex = new; 3632 } 3633 } 3634 3635 scheduleUpdate: 3636 if (!(dInfoPtr->flags & REDRAW_PENDING)) { 3637 Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr); 3638 } 3639 dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED; 3640} 3641 3642/* 3643 *-------------------------------------------------------------- 3644 * 3645 * TkTextYviewCmd -- 3646 * 3647 * This procedure is invoked to process the "yview" option for 3648 * the widget command for text widgets. See the user documentation 3649 * for details on what it does. 3650 * 3651 * Results: 3652 * A standard Tcl result. 3653 * 3654 * Side effects: 3655 * See the user documentation. 3656 * 3657 *-------------------------------------------------------------- 3658 */ 3659 3660int 3661TkTextYviewCmd(textPtr, interp, argc, argv) 3662 TkText *textPtr; /* Information about text widget. */ 3663 Tcl_Interp *interp; /* Current interpreter. */ 3664 int argc; /* Number of arguments. */ 3665 CONST char **argv; /* Argument strings. Someone else has already 3666 * parsed this command enough to know that 3667 * argv[1] is "yview". */ 3668{ 3669 TextDInfo *dInfoPtr = textPtr->dInfoPtr; 3670 int pickPlace, lineNum, type, bytesInLine; 3671 Tk_FontMetrics fm; 3672 int pixels, count; 3673 size_t switchLength; 3674 double fraction; 3675 TkTextIndex index, new; 3676 TkTextLine *lastLinePtr; 3677 DLine *dlPtr; 3678 3679 if (dInfoPtr->flags & DINFO_OUT_OF_DATE) { 3680 UpdateDisplayInfo(textPtr); 3681 } 3682 3683 if (argc == 2) { 3684 GetYView(interp, textPtr, 0); 3685 return TCL_OK; 3686 } 3687 3688 /* 3689 * Next, handle the old syntax: "pathName yview ?-pickplace? where" 3690 */ 3691 3692 pickPlace = 0; 3693 if (argv[2][0] == '-') { 3694 switchLength = strlen(argv[2]); 3695 if ((switchLength >= 2) 3696 && (strncmp(argv[2], "-pickplace", switchLength) == 0)) { 3697 pickPlace = 1; 3698 if (argc != 4) { 3699 Tcl_AppendResult(interp, "wrong # args: should be \"", 3700 argv[0], " yview -pickplace lineNum|index\"", 3701 (char *) NULL); 3702 return TCL_ERROR; 3703 } 3704 } 3705 } 3706 if ((argc == 3) || pickPlace) { 3707 if (Tcl_GetInt(interp, argv[2+pickPlace], &lineNum) == TCL_OK) { 3708 TkTextMakeByteIndex(textPtr->tree, lineNum, 0, &index); 3709 TkTextSetYView(textPtr, &index, 0); 3710 return TCL_OK; 3711 } 3712 3713 /* 3714 * The argument must be a regular text index. 3715 */ 3716 3717 Tcl_ResetResult(interp); 3718 if (TkTextGetIndex(interp, textPtr, argv[2+pickPlace], 3719 &index) != TCL_OK) { 3720 return TCL_ERROR; 3721 } 3722 TkTextSetYView(textPtr, &index, pickPlace); 3723 return TCL_OK; 3724 } 3725 3726 /* 3727 * New syntax: dispatch based on argv[2]. 3728 */ 3729 3730 type = Tk_GetScrollInfo(interp, argc, argv, &fraction, &count); 3731 switch (type) { 3732 case TK_SCROLL_ERROR: 3733 return TCL_ERROR; 3734 case TK_SCROLL_MOVETO: 3735 if (fraction > 1.0) { 3736 fraction = 1.0; 3737 } 3738 if (fraction < 0) { 3739 fraction = 0; 3740 } 3741 fraction *= TkBTreeNumLines(textPtr->tree); 3742 lineNum = (int) fraction; 3743 TkTextMakeByteIndex(textPtr->tree, lineNum, 0, &index); 3744 bytesInLine = TkBTreeBytesInLine(index.linePtr); 3745 index.byteIndex = (int)((bytesInLine * (fraction-lineNum)) + 0.5); 3746 if (index.byteIndex >= bytesInLine) { 3747 TkTextMakeByteIndex(textPtr->tree, lineNum + 1, 0, &index); 3748 } 3749 TkTextSetYView(textPtr, &index, 0); 3750 break; 3751 case TK_SCROLL_PAGES: 3752 /* 3753 * Scroll up or down by screenfuls. Actually, use the 3754 * window height minus two lines, so that there's some 3755 * overlap between adjacent pages. 3756 */ 3757 3758 Tk_GetFontMetrics(textPtr->tkfont, &fm); 3759 if (count < 0) { 3760 pixels = (dInfoPtr->maxY - 2*fm.linespace - dInfoPtr->y)*(-count) 3761 + fm.linespace; 3762 MeasureUp(textPtr, &textPtr->topIndex, pixels, &new); 3763 if (TkTextIndexCmp(&textPtr->topIndex, &new) == 0) { 3764 /* 3765 * A page of scrolling ended up being less than one line. 3766 * Scroll one line anyway. 3767 */ 3768 3769 count = -1; 3770 goto scrollByLines; 3771 } 3772 textPtr->topIndex = new; 3773 } else { 3774 /* 3775 * Scrolling down by pages. Layout lines starting at the 3776 * top index and count through the desired vertical distance. 3777 */ 3778 3779 pixels = (dInfoPtr->maxY - 2*fm.linespace - dInfoPtr->y)*count; 3780 lastLinePtr = TkBTreeFindLine(textPtr->tree, 3781 TkBTreeNumLines(textPtr->tree)); 3782 do { 3783 dlPtr = LayoutDLine(textPtr, &textPtr->topIndex); 3784 dlPtr->nextPtr = NULL; 3785 TkTextIndexForwBytes(&textPtr->topIndex, dlPtr->byteCount, 3786 &new); 3787 pixels -= dlPtr->height; 3788 FreeDLines(textPtr, dlPtr, (DLine *) NULL, 0); 3789 if (new.linePtr == lastLinePtr) { 3790 break; 3791 } 3792 textPtr->topIndex = new; 3793 } while (pixels > 0); 3794 } 3795 if (!(dInfoPtr->flags & REDRAW_PENDING)) { 3796 Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr); 3797 } 3798 dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED; 3799 break; 3800 case TK_SCROLL_UNITS: 3801 scrollByLines: 3802 ScrollByLines(textPtr, count); 3803 break; 3804 } 3805 return TCL_OK; 3806} 3807 3808/* 3809 *-------------------------------------------------------------- 3810 * 3811 * TkTextScanCmd -- 3812 * 3813 * This procedure is invoked to process the "scan" option for 3814 * the widget command for text widgets. See the user documentation 3815 * for details on what it does. 3816 * 3817 * Results: 3818 * A standard Tcl result. 3819 * 3820 * Side effects: 3821 * See the user documentation. 3822 * 3823 *-------------------------------------------------------------- 3824 */ 3825 3826int 3827TkTextScanCmd(textPtr, interp, argc, argv) 3828 register TkText *textPtr; /* Information about text widget. */ 3829 Tcl_Interp *interp; /* Current interpreter. */ 3830 int argc; /* Number of arguments. */ 3831 CONST char **argv; /* Argument strings. Someone else has already 3832 * parsed this command enough to know that 3833 * argv[1] is "scan". */ 3834{ 3835 TextDInfo *dInfoPtr = textPtr->dInfoPtr; 3836 TkTextIndex index; 3837 int c, x, y, totalScroll, newByte, maxByte, gain=10; 3838 Tk_FontMetrics fm; 3839 size_t length; 3840 3841 if ((argc != 5) && (argc != 6)) { 3842 Tcl_AppendResult(interp, "wrong # args: should be \"", 3843 argv[0], " scan mark x y\" or \"", 3844 argv[0], " scan dragto x y ?gain?\"", (char *) NULL); 3845 return TCL_ERROR; 3846 } 3847 if (Tcl_GetInt(interp, argv[3], &x) != TCL_OK) { 3848 return TCL_ERROR; 3849 } 3850 if (Tcl_GetInt(interp, argv[4], &y) != TCL_OK) { 3851 return TCL_ERROR; 3852 } 3853 if ((argc == 6) && (Tcl_GetInt(interp, argv[5], &gain) != TCL_OK)) 3854 return TCL_ERROR; 3855 c = argv[2][0]; 3856 length = strlen(argv[2]); 3857 if ((c == 'd') && (strncmp(argv[2], "dragto", length) == 0)) { 3858 /* 3859 * Amplify the difference between the current position and the 3860 * mark position to compute how much the view should shift, then 3861 * update the mark position to correspond to the new view. If we 3862 * run off the edge of the text, reset the mark point so that the 3863 * current position continues to correspond to the edge of the 3864 * window. This means that the picture will start dragging as 3865 * soon as the mouse reverses direction (without this reset, might 3866 * have to slide mouse a long ways back before the picture starts 3867 * moving again). 3868 */ 3869 3870 newByte = dInfoPtr->scanMarkIndex + (gain*(dInfoPtr->scanMarkX - x)) 3871 / (textPtr->charWidth); 3872 maxByte = 1 + (dInfoPtr->maxLength - (dInfoPtr->maxX - dInfoPtr->x) 3873 + textPtr->charWidth - 1)/textPtr->charWidth; 3874 if (newByte < 0) { 3875 newByte = 0; 3876 dInfoPtr->scanMarkIndex = 0; 3877 dInfoPtr->scanMarkX = x; 3878 } else if (newByte > maxByte) { 3879 newByte = maxByte; 3880 dInfoPtr->scanMarkIndex = maxByte; 3881 dInfoPtr->scanMarkX = x; 3882 } 3883 dInfoPtr->newByteOffset = newByte; 3884 3885 Tk_GetFontMetrics(textPtr->tkfont, &fm); 3886 totalScroll = (gain*(dInfoPtr->scanMarkY - y)) / fm.linespace; 3887 if (totalScroll != dInfoPtr->scanTotalScroll) { 3888 index = textPtr->topIndex; 3889 ScrollByLines(textPtr, totalScroll-dInfoPtr->scanTotalScroll); 3890 dInfoPtr->scanTotalScroll = totalScroll; 3891 if ((index.linePtr == textPtr->topIndex.linePtr) && 3892 (index.byteIndex == textPtr->topIndex.byteIndex)) { 3893 dInfoPtr->scanTotalScroll = 0; 3894 dInfoPtr->scanMarkY = y; 3895 } 3896 } 3897 } else if ((c == 'm') && (strncmp(argv[2], "mark", length) == 0)) { 3898 dInfoPtr->scanMarkIndex = dInfoPtr->newByteOffset; 3899 dInfoPtr->scanMarkX = x; 3900 dInfoPtr->scanTotalScroll = 0; 3901 dInfoPtr->scanMarkY = y; 3902 } else { 3903 Tcl_AppendResult(interp, "bad scan option \"", argv[2], 3904 "\": must be mark or dragto", (char *) NULL); 3905 return TCL_ERROR; 3906 } 3907 dInfoPtr->flags |= DINFO_OUT_OF_DATE; 3908 if (!(dInfoPtr->flags & REDRAW_PENDING)) { 3909 dInfoPtr->flags |= REDRAW_PENDING; 3910 Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr); 3911 } 3912 return TCL_OK; 3913} 3914 3915/* 3916 *---------------------------------------------------------------------- 3917 * 3918 * GetXView -- 3919 * 3920 * This procedure computes the fractions that indicate what's 3921 * visible in a text window and, optionally, evaluates a 3922 * Tcl script to report them to the text's associated scrollbar. 3923 * 3924 * Results: 3925 * If report is zero, then the interp's result is filled in with 3926 * two real numbers separated by a space, giving the position of 3927 * the left and right edges of the window as fractions from 0 to 3928 * 1, where 0 means the left edge of the text and 1 means the right 3929 * edge. If report is non-zero, then the interp's result isn't modified 3930 * directly, but instead a script is evaluated in interp to report 3931 * the new horizontal scroll position to the scrollbar (if the scroll 3932 * position hasn't changed then no script is invoked). 3933 * 3934 * Side effects: 3935 * None. 3936 * 3937 *---------------------------------------------------------------------- 3938 */ 3939 3940static void 3941GetXView(interp, textPtr, report) 3942 Tcl_Interp *interp; /* If "report" is FALSE, string 3943 * describing visible range gets 3944 * stored in the interp's result. */ 3945 TkText *textPtr; /* Information about text widget. */ 3946 int report; /* Non-zero means report info to 3947 * scrollbar if it has changed. */ 3948{ 3949 TextDInfo *dInfoPtr = textPtr->dInfoPtr; 3950 char buffer[TCL_DOUBLE_SPACE * 2 + 1]; 3951 double first, last; 3952 int code; 3953 3954 if (dInfoPtr->maxLength > 0) { 3955 first = ((double) dInfoPtr->curPixelOffset) 3956 / dInfoPtr->maxLength; 3957 last = first + ((double) (dInfoPtr->maxX - dInfoPtr->x)) 3958 / dInfoPtr->maxLength; 3959 if (last > 1.0) { 3960 last = 1.0; 3961 } 3962 } else { 3963 first = 0; 3964 last = 1.0; 3965 } 3966 if (!report) { 3967 sprintf(buffer, "%g %g", first, last); 3968 Tcl_SetResult(interp, buffer, TCL_VOLATILE); 3969 return; 3970 } 3971 if (FP_EQUAL_SCALE(first, dInfoPtr->xScrollFirst, dInfoPtr->maxLength) && 3972 FP_EQUAL_SCALE(last, dInfoPtr->xScrollLast, dInfoPtr->maxLength)) { 3973 return; 3974 } 3975 dInfoPtr->xScrollFirst = first; 3976 dInfoPtr->xScrollLast = last; 3977 sprintf(buffer, " %g %g", first, last); 3978 code = Tcl_VarEval(interp, textPtr->xScrollCmd, 3979 buffer, (char *) NULL); 3980 if (code != TCL_OK) { 3981 Tcl_AddErrorInfo(interp, 3982 "\n (horizontal scrolling command executed by text)"); 3983 Tcl_BackgroundError(interp); 3984 } 3985} 3986 3987/* 3988 *---------------------------------------------------------------------- 3989 * 3990 * GetYView -- 3991 * 3992 * This procedure computes the fractions that indicate what's 3993 * visible in a text window and, optionally, evaluates a 3994 * Tcl script to report them to the text's associated scrollbar. 3995 * 3996 * Results: 3997 * If report is zero, then the interp's result is filled in with 3998 * two real numbers separated by a space, giving the position of 3999 * the top and bottom of the window as fractions from 0 to 1, where 4000 * 0 means the beginning of the text and 1 means the end. If 4001 * report is non-zero, then the interp's result isn't modified directly, 4002 * but a script is evaluated in interp to report the new scroll 4003 * position to the scrollbar (if the scroll position hasn't changed 4004 * then no script is invoked). 4005 * 4006 * Side effects: 4007 * None. 4008 * 4009 *---------------------------------------------------------------------- 4010 */ 4011 4012static void 4013GetYView(interp, textPtr, report) 4014 Tcl_Interp *interp; /* If "report" is FALSE, string 4015 * describing visible range gets 4016 * stored in the interp's result. */ 4017 TkText *textPtr; /* Information about text widget. */ 4018 int report; /* Non-zero means report info to 4019 * scrollbar if it has changed. */ 4020{ 4021 TextDInfo *dInfoPtr = textPtr->dInfoPtr; 4022 char buffer[TCL_DOUBLE_SPACE * 2 + 1]; 4023 double first, last; 4024 DLine *dlPtr; 4025 int totalLines, code, count; 4026 4027 dlPtr = dInfoPtr->dLinePtr; 4028 totalLines = TkBTreeNumLines(textPtr->tree); 4029 first = (double) TkBTreeLineIndex(dlPtr->index.linePtr) 4030 + (double) dlPtr->index.byteIndex 4031 / TkBTreeBytesInLine(dlPtr->index.linePtr); 4032 first /= totalLines; 4033 while (1) { 4034 if ((dlPtr->y + dlPtr->height) > dInfoPtr->maxY) { 4035 /* 4036 * The last line is only partially visible, so don't 4037 * count its characters in what's visible. 4038 */ 4039 count = 0; 4040 break; 4041 } 4042 if (dlPtr->nextPtr == NULL) { 4043 count = dlPtr->byteCount; 4044 break; 4045 } 4046 dlPtr = dlPtr->nextPtr; 4047 } 4048 last = ((double) TkBTreeLineIndex(dlPtr->index.linePtr)) 4049 + ((double) (dlPtr->index.byteIndex + count)) 4050 / (TkBTreeBytesInLine(dlPtr->index.linePtr)); 4051 last /= totalLines; 4052 if (!report) { 4053 sprintf(buffer, "%g %g", first, last); 4054 Tcl_SetResult(interp, buffer, TCL_VOLATILE); 4055 return; 4056 } 4057 if (FP_EQUAL_SCALE(first, dInfoPtr->yScrollFirst, totalLines) && 4058 FP_EQUAL_SCALE(last, dInfoPtr->yScrollLast, totalLines)) { 4059 return; 4060 } 4061 dInfoPtr->yScrollFirst = first; 4062 dInfoPtr->yScrollLast = last; 4063 sprintf(buffer, " %g %g", first, last); 4064 code = Tcl_VarEval(interp, textPtr->yScrollCmd, buffer, (char *) NULL); 4065 if (code != TCL_OK) { 4066 Tcl_AddErrorInfo(interp, 4067 "\n (vertical scrolling command executed by text)"); 4068 Tcl_BackgroundError(interp); 4069 } 4070} 4071 4072/* 4073 *---------------------------------------------------------------------- 4074 * 4075 * FindDLine -- 4076 * 4077 * This procedure is called to find the DLine corresponding to a 4078 * given text index. 4079 * 4080 * Results: 4081 * The return value is a pointer to the first DLine found in the 4082 * list headed by dlPtr that displays information at or after the 4083 * specified position. If there is no such line in the list then 4084 * NULL is returned. 4085 * 4086 * Side effects: 4087 * None. 4088 * 4089 *---------------------------------------------------------------------- 4090 */ 4091 4092static DLine * 4093FindDLine(dlPtr, indexPtr) 4094 register DLine *dlPtr; /* Pointer to first in list of DLines 4095 * to search. */ 4096 TkTextIndex *indexPtr; /* Index of desired character. */ 4097{ 4098 TkTextLine *linePtr; 4099 4100 if (dlPtr == NULL) { 4101 return NULL; 4102 } 4103 if (TkBTreeLineIndex(indexPtr->linePtr) 4104 < TkBTreeLineIndex(dlPtr->index.linePtr)) { 4105 /* 4106 * The first display line is already past the desired line. 4107 */ 4108 return dlPtr; 4109 } 4110 4111 /* 4112 * Find the first display line that covers the desired text line. 4113 */ 4114 4115 linePtr = dlPtr->index.linePtr; 4116 while (linePtr != indexPtr->linePtr) { 4117 while (dlPtr->index.linePtr == linePtr) { 4118 dlPtr = dlPtr->nextPtr; 4119 if (dlPtr == NULL) { 4120 return NULL; 4121 } 4122 } 4123 linePtr = TkBTreeNextLine(linePtr); 4124 if (linePtr == NULL) { 4125 panic("FindDLine reached end of text"); 4126 } 4127 } 4128 if (indexPtr->linePtr != dlPtr->index.linePtr) { 4129 return dlPtr; 4130 } 4131 4132 /* 4133 * Now get to the right position within the text line. 4134 */ 4135 4136 while (indexPtr->byteIndex >= (dlPtr->index.byteIndex + dlPtr->byteCount)) { 4137 dlPtr = dlPtr->nextPtr; 4138 if ((dlPtr == NULL) || (dlPtr->index.linePtr != indexPtr->linePtr)) { 4139 break; 4140 } 4141 } 4142 return dlPtr; 4143} 4144 4145/* 4146 *---------------------------------------------------------------------- 4147 * 4148 * TkTextPixelIndex -- 4149 * 4150 * Given an (x,y) coordinate on the screen, find the location of 4151 * the character closest to that location. 4152 * 4153 * Results: 4154 * The index at *indexPtr is modified to refer to the character 4155 * on the display that is closest to (x,y). 4156 * 4157 * Side effects: 4158 * None. 4159 * 4160 *---------------------------------------------------------------------- 4161 */ 4162 4163void 4164TkTextPixelIndex(textPtr, x, y, indexPtr) 4165 TkText *textPtr; /* Widget record for text widget. */ 4166 int x, y; /* Pixel coordinates of point in widget's 4167 * window. */ 4168 TkTextIndex *indexPtr; /* This index gets filled in with the 4169 * index of the character nearest to (x,y). */ 4170{ 4171 TextDInfo *dInfoPtr = textPtr->dInfoPtr; 4172 register DLine *dlPtr, *validdlPtr; 4173 register TkTextDispChunk *chunkPtr; 4174 4175 /* 4176 * Make sure that all of the layout information about what's 4177 * displayed where on the screen is up-to-date. 4178 */ 4179 4180 if (dInfoPtr->flags & DINFO_OUT_OF_DATE) { 4181 UpdateDisplayInfo(textPtr); 4182 } 4183 4184 /* 4185 * If the coordinates are above the top of the window, then adjust 4186 * them to refer to the upper-right corner of the window. If they're 4187 * off to one side or the other, then adjust to the closest side. 4188 */ 4189 4190 if (y < dInfoPtr->y) { 4191 y = dInfoPtr->y; 4192 x = dInfoPtr->x; 4193 } 4194 if (x >= dInfoPtr->maxX) { 4195 x = dInfoPtr->maxX - 1; 4196 } 4197 if (x < dInfoPtr->x) { 4198 x = dInfoPtr->x; 4199 } 4200 4201 /* 4202 * Find the display line containing the desired y-coordinate. 4203 */ 4204 4205 for (dlPtr = validdlPtr = dInfoPtr->dLinePtr; y >= (dlPtr->y + dlPtr->height); 4206 dlPtr = dlPtr->nextPtr) { 4207 if (dlPtr->chunkPtr !=NULL) validdlPtr = dlPtr; 4208 if (dlPtr->nextPtr == NULL) { 4209 /* 4210 * Y-coordinate is off the bottom of the displayed text. 4211 * Use the last character on the last line. 4212 */ 4213 4214 x = dInfoPtr->maxX - 1; 4215 break; 4216 } 4217 } 4218 if (dlPtr->chunkPtr == NULL) dlPtr = validdlPtr; 4219 4220 *indexPtr = dlPtr->index; 4221 4222 /* 4223 * If it is still empty, we have nothing to access. [Bug 1442102] 4224 */ 4225 4226 if (dlPtr->chunkPtr == NULL) { 4227 return; 4228 } 4229 4230 /* 4231 * Scan through the line's chunks to find the one that contains 4232 * the desired x-coordinate. Before doing this, translate the 4233 * x-coordinate from the coordinate system of the window to the 4234 * coordinate system of the line (to take account of x-scrolling). 4235 */ 4236 4237 x = x - dInfoPtr->x + dInfoPtr->curPixelOffset; 4238 for (chunkPtr = dlPtr->chunkPtr; x >= (chunkPtr->x + chunkPtr->width); 4239 indexPtr->byteIndex += chunkPtr->numBytes, 4240 chunkPtr = chunkPtr->nextPtr) { 4241 if (chunkPtr->nextPtr == NULL) { 4242 indexPtr->byteIndex += chunkPtr->numBytes; 4243 TkTextIndexBackChars(indexPtr, 1, indexPtr); 4244 return; 4245 } 4246 } 4247 4248 /* 4249 * If the chunk has more than one byte in it, ask it which 4250 * character is at the desired location. 4251 */ 4252 4253 if (chunkPtr->numBytes > 1) { 4254 indexPtr->byteIndex += (*chunkPtr->measureProc)(chunkPtr, x); 4255 } 4256} 4257 4258/* 4259 *---------------------------------------------------------------------- 4260 * 4261 * TkTextCharBbox -- 4262 * 4263 * Given an index, find the bounding box of the screen area 4264 * occupied by that character. 4265 * 4266 * Results: 4267 * Zero is returned if the character is on the screen. -1 4268 * means the character isn't on the screen. If the return value 4269 * is 0, then the bounding box of the part of the character that's 4270 * visible on the screen is returned to *xPtr, *yPtr, *widthPtr, 4271 * and *heightPtr. 4272 * 4273 * Side effects: 4274 * None. 4275 * 4276 *---------------------------------------------------------------------- 4277 */ 4278 4279int 4280TkTextCharBbox(textPtr, indexPtr, xPtr, yPtr, widthPtr, heightPtr) 4281 TkText *textPtr; /* Widget record for text widget. */ 4282 TkTextIndex *indexPtr; /* Index of character whose bounding 4283 * box is desired. */ 4284 int *xPtr, *yPtr; /* Filled with character's upper-left 4285 * coordinate. */ 4286 int *widthPtr, *heightPtr; /* Filled in with character's dimensions. */ 4287{ 4288 TextDInfo *dInfoPtr = textPtr->dInfoPtr; 4289 DLine *dlPtr; 4290 register TkTextDispChunk *chunkPtr; 4291 int byteIndex; 4292 4293 /* 4294 * Make sure that all of the screen layout information is up to date. 4295 */ 4296 4297 if (dInfoPtr->flags & DINFO_OUT_OF_DATE) { 4298 UpdateDisplayInfo(textPtr); 4299 } 4300 4301 /* 4302 * Find the display line containing the desired index. 4303 */ 4304 4305 dlPtr = FindDLine(dInfoPtr->dLinePtr, indexPtr); 4306 if ((dlPtr == NULL) || (TkTextIndexCmp(&dlPtr->index, indexPtr) > 0)) { 4307 return -1; 4308 } 4309 4310 /* 4311 * Find the chunk within the line that contains the desired 4312 * index. 4313 */ 4314 4315 byteIndex = indexPtr->byteIndex - dlPtr->index.byteIndex; 4316 for (chunkPtr = dlPtr->chunkPtr; ; chunkPtr = chunkPtr->nextPtr) { 4317 if (chunkPtr == NULL) { 4318 return -1; 4319 } 4320 if (byteIndex < chunkPtr->numBytes) { 4321 break; 4322 } 4323 byteIndex -= chunkPtr->numBytes; 4324 } 4325 4326 /* 4327 * Call a chunk-specific procedure to find the horizontal range of 4328 * the character within the chunk, then fill in the vertical range. 4329 * The x-coordinate returned by bboxProc is a coordinate within a 4330 * line, not a coordinate on the screen. Translate it to reflect 4331 * horizontal scrolling. 4332 */ 4333 4334 (*chunkPtr->bboxProc)(chunkPtr, byteIndex, dlPtr->y + dlPtr->spaceAbove, 4335 dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow, 4336 dlPtr->baseline - dlPtr->spaceAbove, xPtr, yPtr, widthPtr, 4337 heightPtr); 4338 *xPtr = *xPtr + dInfoPtr->x - dInfoPtr->curPixelOffset; 4339 if ((byteIndex == (chunkPtr->numBytes - 1)) && (chunkPtr->nextPtr == NULL)) { 4340 /* 4341 * Last character in display line. Give it all the space up to 4342 * the line. 4343 */ 4344 4345 if (*xPtr > dInfoPtr->maxX) { 4346 *xPtr = dInfoPtr->maxX; 4347 } 4348 *widthPtr = dInfoPtr->maxX - *xPtr; 4349 } 4350 if ((*xPtr + *widthPtr) <= dInfoPtr->x) { 4351 return -1; 4352 } 4353 if ((*xPtr + *widthPtr) > dInfoPtr->maxX) { 4354 *widthPtr = dInfoPtr->maxX - *xPtr; 4355 if (*widthPtr <= 0) { 4356 return -1; 4357 } 4358 } 4359 if ((*yPtr + *heightPtr) > dInfoPtr->maxY) { 4360 *heightPtr = dInfoPtr->maxY - *yPtr; 4361 if (*heightPtr <= 0) { 4362 return -1; 4363 } 4364 } 4365 return 0; 4366} 4367 4368/* 4369 *---------------------------------------------------------------------- 4370 * 4371 * TkTextDLineInfo -- 4372 * 4373 * Given an index, return information about the display line 4374 * containing that character. 4375 * 4376 * Results: 4377 * Zero is returned if the character is on the screen. -1 4378 * means the character isn't on the screen. If the return value 4379 * is 0, then information is returned in the variables pointed 4380 * to by xPtr, yPtr, widthPtr, heightPtr, and basePtr. 4381 * 4382 * Side effects: 4383 * None. 4384 * 4385 *---------------------------------------------------------------------- 4386 */ 4387 4388int 4389TkTextDLineInfo(textPtr, indexPtr, xPtr, yPtr, widthPtr, heightPtr, basePtr) 4390 TkText *textPtr; /* Widget record for text widget. */ 4391 TkTextIndex *indexPtr; /* Index of character whose bounding 4392 * box is desired. */ 4393 int *xPtr, *yPtr; /* Filled with line's upper-left 4394 * coordinate. */ 4395 int *widthPtr, *heightPtr; /* Filled in with line's dimensions. */ 4396 int *basePtr; /* Filled in with the baseline position, 4397 * measured as an offset down from *yPtr. */ 4398{ 4399 TextDInfo *dInfoPtr = textPtr->dInfoPtr; 4400 DLine *dlPtr; 4401 int dlx; 4402 4403 /* 4404 * Make sure that all of the screen layout information is up to date. 4405 */ 4406 4407 if (dInfoPtr->flags & DINFO_OUT_OF_DATE) { 4408 UpdateDisplayInfo(textPtr); 4409 } 4410 4411 /* 4412 * Find the display line containing the desired index. 4413 */ 4414 4415 dlPtr = FindDLine(dInfoPtr->dLinePtr, indexPtr); 4416 if ((dlPtr == NULL) || (TkTextIndexCmp(&dlPtr->index, indexPtr) > 0)) { 4417 return -1; 4418 } 4419 4420 dlx = (dlPtr->chunkPtr != NULL? dlPtr->chunkPtr->x: 0); 4421 *xPtr = dInfoPtr->x - dInfoPtr->curPixelOffset + dlx; 4422 *widthPtr = dlPtr->length - dlx; 4423 *yPtr = dlPtr->y; 4424 if ((dlPtr->y + dlPtr->height) > dInfoPtr->maxY) { 4425 *heightPtr = dInfoPtr->maxY - dlPtr->y; 4426 } else { 4427 *heightPtr = dlPtr->height; 4428 } 4429 *basePtr = dlPtr->baseline; 4430 return 0; 4431} 4432 4433static void 4434ElideBboxProc(chunkPtr, index, y, lineHeight, baseline, xPtr, yPtr, 4435 widthPtr, heightPtr) 4436 TkTextDispChunk *chunkPtr; /* Chunk containing desired char. */ 4437 int index; /* Index of desired character within 4438 * the chunk. */ 4439 int y; /* Topmost pixel in area allocated 4440 * for this line. */ 4441 int lineHeight; /* Height of line, in pixels. */ 4442 int baseline; /* Location of line's baseline, in 4443 * pixels measured down from y. */ 4444 int *xPtr, *yPtr; /* Gets filled in with coords of 4445 * character's upper-left pixel. 4446 * X-coord is in same coordinate 4447 * system as chunkPtr->x. */ 4448 int *widthPtr; /* Gets filled in with width of 4449 * character, in pixels. */ 4450 int *heightPtr; /* Gets filled in with height of 4451 * character, in pixels. */ 4452{ 4453 *xPtr = chunkPtr->x; 4454 *yPtr = y; 4455 *widthPtr = *heightPtr = 0; 4456} 4457 4458 4459static int 4460ElideMeasureProc(chunkPtr, x) 4461 TkTextDispChunk *chunkPtr; /* Chunk containing desired coord. */ 4462 int x; /* X-coordinate, in same coordinate 4463 * system as chunkPtr->x. */ 4464{ 4465 return 0 /*chunkPtr->numBytes - 1*/; 4466} 4467 4468/* 4469 *-------------------------------------------------------------- 4470 * 4471 * TkTextCharLayoutProc -- 4472 * 4473 * This procedure is the "layoutProc" for character segments. 4474 * 4475 * Results: 4476 * If there is something to display for the chunk then a 4477 * non-zero value is returned and the fields of chunkPtr 4478 * will be filled in (see the declaration of TkTextDispChunk 4479 * in tkText.h for details). If zero is returned it means 4480 * that no characters from this chunk fit in the window. 4481 * If -1 is returned it means that this segment just doesn't 4482 * need to be displayed (never happens for text). 4483 * 4484 * Side effects: 4485 * Memory is allocated to hold additional information about 4486 * the chunk. 4487 * 4488 *-------------------------------------------------------------- 4489 */ 4490 4491int 4492TkTextCharLayoutProc(textPtr, indexPtr, segPtr, byteOffset, maxX, maxBytes, 4493 noCharsYet, wrapMode, chunkPtr) 4494 TkText *textPtr; /* Text widget being layed out. */ 4495 TkTextIndex *indexPtr; /* Index of first character to lay out 4496 * (corresponds to segPtr and offset). */ 4497 TkTextSegment *segPtr; /* Segment being layed out. */ 4498 int byteOffset; /* Byte offset within segment of first 4499 * character to consider. */ 4500 int maxX; /* Chunk must not occupy pixels at this 4501 * position or higher. */ 4502 int maxBytes; /* Chunk must not include more than this 4503 * many characters. */ 4504 int noCharsYet; /* Non-zero means no characters have been 4505 * assigned to this display line yet. */ 4506 TkWrapMode wrapMode; /* How to handle line wrapping: TEXT_WRAPMODE_CHAR, 4507 * TEXT_WRAPMODE_NONE, or TEXT_WRAPMODE_WORD. */ 4508 register TkTextDispChunk *chunkPtr; 4509 /* Structure to fill in with information 4510 * about this chunk. The x field has already 4511 * been set by the caller. */ 4512{ 4513 Tk_Font tkfont; 4514 int nextX, bytesThatFit, count; 4515 CharInfo *ciPtr; 4516 char *p; 4517 TkTextSegment *nextPtr; 4518 Tk_FontMetrics fm; 4519 4520 /* 4521 * Figure out how many characters will fit in the space we've got. 4522 * Include the next character, even though it won't fit completely, 4523 * if any of the following is true: 4524 * (a) the chunk contains no characters and the display line contains 4525 * no characters yet (i.e. the line isn't wide enough to hold 4526 * even a single character). 4527 * (b) at least one pixel of the character is visible, we haven't 4528 * already exceeded the character limit, and the next character 4529 * is a white space character. 4530 */ 4531 4532 p = segPtr->body.chars + byteOffset; 4533 tkfont = chunkPtr->stylePtr->sValuePtr->tkfont; 4534 bytesThatFit = MeasureChars(tkfont, p, maxBytes, chunkPtr->x, maxX, 0, 4535 &nextX); 4536 if (bytesThatFit < maxBytes) { 4537 if ((bytesThatFit == 0) && noCharsYet) { 4538 Tcl_UniChar ch; 4539 4540 bytesThatFit = MeasureChars(tkfont, p, Tcl_UtfToUniChar(p, &ch), 4541 chunkPtr->x, -1, 0, &nextX); 4542 } 4543 if ((nextX < maxX) && ((p[bytesThatFit] == ' ') 4544 || (p[bytesThatFit] == '\t'))) { 4545 /* 4546 * Space characters are funny, in that they are considered 4547 * to fit if there is at least one pixel of space left on the 4548 * line. Just give the space character whatever space is left. 4549 */ 4550 4551 nextX = maxX; 4552 bytesThatFit++; 4553 } 4554 if (p[bytesThatFit] == '\n') { 4555 /* 4556 * A newline character takes up no space, so if the previous 4557 * character fits then so does the newline. 4558 */ 4559 4560 bytesThatFit++; 4561 } 4562 if (bytesThatFit == 0) { 4563 return 0; 4564 } 4565 } 4566 4567 Tk_GetFontMetrics(tkfont, &fm); 4568 4569 /* 4570 * Fill in the chunk structure and allocate and initialize a 4571 * CharInfo structure. If the last character is a newline 4572 * then don't bother to display it. 4573 */ 4574 4575 chunkPtr->displayProc = CharDisplayProc; 4576 chunkPtr->undisplayProc = CharUndisplayProc; 4577 chunkPtr->measureProc = CharMeasureProc; 4578 chunkPtr->bboxProc = CharBboxProc; 4579 chunkPtr->numBytes = bytesThatFit; 4580 chunkPtr->minAscent = fm.ascent + chunkPtr->stylePtr->sValuePtr->offset; 4581 chunkPtr->minDescent = fm.descent - chunkPtr->stylePtr->sValuePtr->offset; 4582 chunkPtr->minHeight = 0; 4583 chunkPtr->width = nextX - chunkPtr->x; 4584 chunkPtr->breakIndex = -1; 4585 ciPtr = (CharInfo *) ckalloc((unsigned) 4586 (sizeof(CharInfo) - 3 + bytesThatFit)); 4587 chunkPtr->clientData = (ClientData) ciPtr; 4588 ciPtr->numBytes = bytesThatFit; 4589 strncpy(ciPtr->chars, p, (size_t) bytesThatFit); 4590 if (p[bytesThatFit - 1] == '\n') { 4591 ciPtr->numBytes--; 4592 } 4593 4594 /* 4595 * Compute a break location. If we're in word wrap mode, a 4596 * break can occur after any space character, or at the end of 4597 * the chunk if the next segment (ignoring those with zero size) 4598 * is not a character segment. 4599 */ 4600 4601 if (wrapMode != TEXT_WRAPMODE_WORD) { 4602 chunkPtr->breakIndex = chunkPtr->numBytes; 4603 } else { 4604 for (count = bytesThatFit, p += bytesThatFit - 1; count > 0; 4605 count--, p--) { 4606 if (isspace(UCHAR(*p))) { 4607 chunkPtr->breakIndex = count; 4608 break; 4609 } 4610 } 4611 if ((bytesThatFit + byteOffset) == segPtr->size) { 4612 for (nextPtr = segPtr->nextPtr; nextPtr != NULL; 4613 nextPtr = nextPtr->nextPtr) { 4614 if (nextPtr->size != 0) { 4615 if (nextPtr->typePtr != &tkTextCharType) { 4616 chunkPtr->breakIndex = chunkPtr->numBytes; 4617 } 4618 break; 4619 } 4620 } 4621 } 4622 } 4623 return 1; 4624} 4625 4626/* 4627 *-------------------------------------------------------------- 4628 * 4629 * CharDisplayProc -- 4630 * 4631 * This procedure is called to display a character chunk on 4632 * the screen or in an off-screen pixmap. 4633 * 4634 * Results: 4635 * None. 4636 * 4637 * Side effects: 4638 * Graphics are drawn. 4639 * 4640 *-------------------------------------------------------------- 4641 */ 4642 4643static void 4644CharDisplayProc(chunkPtr, x, y, height, baseline, display, dst, screenY) 4645 TkTextDispChunk *chunkPtr; /* Chunk that is to be drawn. */ 4646 int x; /* X-position in dst at which to 4647 * draw this chunk (may differ from 4648 * the x-position in the chunk because 4649 * of scrolling). */ 4650 int y; /* Y-position at which to draw this 4651 * chunk in dst. */ 4652 int height; /* Total height of line. */ 4653 int baseline; /* Offset of baseline from y. */ 4654 Display *display; /* Display to use for drawing. */ 4655 Drawable dst; /* Pixmap or window in which to draw 4656 * chunk. */ 4657 int screenY; /* Y-coordinate in text window that 4658 * corresponds to y. */ 4659{ 4660 CharInfo *ciPtr = (CharInfo *) chunkPtr->clientData; 4661 TextStyle *stylePtr; 4662 StyleValues *sValuePtr; 4663 int offsetBytes, offsetX; 4664 4665 if ((x + chunkPtr->width) <= 0) { 4666 /* 4667 * The chunk is off-screen. 4668 */ 4669 4670 return; 4671 } 4672 4673 stylePtr = chunkPtr->stylePtr; 4674 sValuePtr = stylePtr->sValuePtr; 4675 4676 /* 4677 * If the text sticks out way to the left of the window, skip 4678 * over the characters that aren't in the visible part of the 4679 * window. This is essential if x is very negative (such as 4680 * less than 32K); otherwise overflow problems will occur 4681 * in servers that use 16-bit arithmetic, like X. 4682 */ 4683 4684 offsetX = x; 4685 offsetBytes = 0; 4686 if (x < 0) { 4687 offsetBytes = MeasureChars(sValuePtr->tkfont, ciPtr->chars, 4688 ciPtr->numBytes, x, 0, x - chunkPtr->x, &offsetX); 4689 } 4690 4691 /* 4692 * Draw the text, underline, and overstrike for this chunk. 4693 */ 4694 4695 if (!sValuePtr->elide && (ciPtr->numBytes > offsetBytes) && (stylePtr->fgGC != None)) { 4696 int numBytes = ciPtr->numBytes - offsetBytes; 4697 char *string = ciPtr->chars + offsetBytes; 4698 4699 if ((numBytes > 0) && (string[numBytes - 1] == '\t')) { 4700 numBytes--; 4701 } 4702 Tk_DrawChars(display, dst, stylePtr->fgGC, sValuePtr->tkfont, string, 4703 numBytes, offsetX, y + baseline - sValuePtr->offset); 4704 if (sValuePtr->underline) { 4705 Tk_UnderlineChars(display, dst, stylePtr->fgGC, sValuePtr->tkfont, 4706 ciPtr->chars + offsetBytes, offsetX, 4707 y + baseline - sValuePtr->offset, 0, numBytes); 4708 4709 } 4710 if (sValuePtr->overstrike) { 4711 Tk_FontMetrics fm; 4712 4713 Tk_GetFontMetrics(sValuePtr->tkfont, &fm); 4714 Tk_UnderlineChars(display, dst, stylePtr->fgGC, sValuePtr->tkfont, 4715 ciPtr->chars + offsetBytes, offsetX, 4716 y + baseline - sValuePtr->offset 4717 - fm.descent - (fm.ascent * 3) / 10, 4718 0, numBytes); 4719 } 4720 } 4721} 4722 4723/* 4724 *-------------------------------------------------------------- 4725 * 4726 * CharUndisplayProc -- 4727 * 4728 * This procedure is called when a character chunk is no 4729 * longer going to be displayed. It frees up resources 4730 * that were allocated to display the chunk. 4731 * 4732 * Results: 4733 * None. 4734 * 4735 * Side effects: 4736 * Memory and other resources get freed. 4737 * 4738 *-------------------------------------------------------------- 4739 */ 4740 4741static void 4742CharUndisplayProc(textPtr, chunkPtr) 4743 TkText *textPtr; /* Overall information about text 4744 * widget. */ 4745 TkTextDispChunk *chunkPtr; /* Chunk that is about to be freed. */ 4746{ 4747 CharInfo *ciPtr = (CharInfo *) chunkPtr->clientData; 4748 4749 ckfree((char *) ciPtr); 4750} 4751 4752/* 4753 *-------------------------------------------------------------- 4754 * 4755 * CharMeasureProc -- 4756 * 4757 * This procedure is called to determine which character in 4758 * a character chunk lies over a given x-coordinate. 4759 * 4760 * Results: 4761 * The return value is the index *within the chunk* of the 4762 * character that covers the position given by "x". 4763 * 4764 * Side effects: 4765 * None. 4766 * 4767 *-------------------------------------------------------------- 4768 */ 4769 4770static int 4771CharMeasureProc(chunkPtr, x) 4772 TkTextDispChunk *chunkPtr; /* Chunk containing desired coord. */ 4773 int x; /* X-coordinate, in same coordinate 4774 * system as chunkPtr->x. */ 4775{ 4776 CharInfo *ciPtr = (CharInfo *) chunkPtr->clientData; 4777 int endX; 4778 4779 return MeasureChars(chunkPtr->stylePtr->sValuePtr->tkfont, ciPtr->chars, 4780 chunkPtr->numBytes - 1, chunkPtr->x, x, 0, &endX); 4781 /* CHAR OFFSET */ 4782} 4783 4784/* 4785 *-------------------------------------------------------------- 4786 * 4787 * CharBboxProc -- 4788 * 4789 * This procedure is called to compute the bounding box of 4790 * the area occupied by a single character. 4791 * 4792 * Results: 4793 * There is no return value. *xPtr and *yPtr are filled in 4794 * with the coordinates of the upper left corner of the 4795 * character, and *widthPtr and *heightPtr are filled in with 4796 * the dimensions of the character in pixels. Note: not all 4797 * of the returned bbox is necessarily visible on the screen 4798 * (the rightmost part might be off-screen to the right, 4799 * and the bottommost part might be off-screen to the bottom). 4800 * 4801 * Side effects: 4802 * None. 4803 * 4804 *-------------------------------------------------------------- 4805 */ 4806 4807static void 4808CharBboxProc(chunkPtr, byteIndex, y, lineHeight, baseline, xPtr, yPtr, 4809 widthPtr, heightPtr) 4810 TkTextDispChunk *chunkPtr; /* Chunk containing desired char. */ 4811 int byteIndex; /* Byte offset of desired character 4812 * within the chunk. */ 4813 int y; /* Topmost pixel in area allocated 4814 * for this line. */ 4815 int lineHeight; /* Height of line, in pixels. */ 4816 int baseline; /* Location of line's baseline, in 4817 * pixels measured down from y. */ 4818 int *xPtr, *yPtr; /* Gets filled in with coords of 4819 * character's upper-left pixel. 4820 * X-coord is in same coordinate 4821 * system as chunkPtr->x. */ 4822 int *widthPtr; /* Gets filled in with width of 4823 * character, in pixels. */ 4824 int *heightPtr; /* Gets filled in with height of 4825 * character, in pixels. */ 4826{ 4827 CharInfo *ciPtr = (CharInfo *) chunkPtr->clientData; 4828 int maxX; 4829 4830 maxX = chunkPtr->width + chunkPtr->x; 4831 MeasureChars(chunkPtr->stylePtr->sValuePtr->tkfont, ciPtr->chars, 4832 byteIndex, chunkPtr->x, -1, 0, xPtr); 4833 4834 if (byteIndex == ciPtr->numBytes) { 4835 /* 4836 * This situation only happens if the last character in a line 4837 * is a space character, in which case it absorbs all of the 4838 * extra space in the line (see TkTextCharLayoutProc). 4839 */ 4840 4841 *widthPtr = maxX - *xPtr; 4842 } else if ((ciPtr->chars[byteIndex] == '\t') 4843 && (byteIndex == ciPtr->numBytes - 1)) { 4844 /* 4845 * The desired character is a tab character that terminates a 4846 * chunk; give it all the space left in the chunk. 4847 */ 4848 4849 *widthPtr = maxX - *xPtr; 4850 } else { 4851 MeasureChars(chunkPtr->stylePtr->sValuePtr->tkfont, 4852 ciPtr->chars + byteIndex, 1, *xPtr, -1, 0, widthPtr); 4853 if (*widthPtr > maxX) { 4854 *widthPtr = maxX - *xPtr; 4855 } else { 4856 *widthPtr -= *xPtr; 4857 } 4858 } 4859 *yPtr = y + baseline - chunkPtr->minAscent; 4860 *heightPtr = chunkPtr->minAscent + chunkPtr->minDescent; 4861} 4862 4863/* 4864 *---------------------------------------------------------------------- 4865 * 4866 * AdjustForTab -- 4867 * 4868 * This procedure is called to move a series of chunks right 4869 * in order to align them with a tab stop. 4870 * 4871 * Results: 4872 * None. 4873 * 4874 * Side effects: 4875 * The width of chunkPtr gets adjusted so that it absorbs the 4876 * extra space due to the tab. The x locations in all the chunks 4877 * after chunkPtr are adjusted rightward to align with the tab 4878 * stop given by tabArrayPtr and index. 4879 * 4880 *---------------------------------------------------------------------- 4881 */ 4882 4883static void 4884AdjustForTab(textPtr, tabArrayPtr, index, chunkPtr) 4885 TkText *textPtr; /* Information about the text widget as 4886 * a whole. */ 4887 TkTextTabArray *tabArrayPtr; /* Information about the tab stops 4888 * that apply to this line. May be 4889 * NULL to indicate default tabbing 4890 * (every 8 chars). */ 4891 int index; /* Index of current tab stop. */ 4892 TkTextDispChunk *chunkPtr; /* Chunk whose last character is 4893 * the tab; the following chunks 4894 * contain information to be shifted 4895 * right. */ 4896 4897{ 4898 int x, desired, delta, width, decimal, i, gotDigit; 4899 TkTextDispChunk *chunkPtr2, *decimalChunkPtr; 4900 CharInfo *ciPtr; 4901 int tabX, prev, spaceWidth; 4902 char *p; 4903 TkTextTabAlign alignment; 4904 4905 if (chunkPtr->nextPtr == NULL) { 4906 /* 4907 * Nothing after the actual tab; just return. 4908 */ 4909 4910 return; 4911 } 4912 4913 /* 4914 * If no tab information has been given, do the usual thing: 4915 * round up to the next boundary of 8 average-sized characters. 4916 */ 4917 4918 x = chunkPtr->nextPtr->x; 4919 if ((tabArrayPtr == NULL) || (tabArrayPtr->numTabs == 0)) { 4920 /* 4921 * No tab information has been given, so use the default 4922 * interpretation of tabs. 4923 */ 4924 4925 desired = NextTabStop(textPtr->tkfont, x, 0); 4926 goto update; 4927 } 4928 4929 if (index < tabArrayPtr->numTabs) { 4930 alignment = tabArrayPtr->tabs[index].alignment; 4931 tabX = tabArrayPtr->tabs[index].location; 4932 } else { 4933 /* 4934 * Ran out of tab stops; compute a tab position by extrapolating 4935 * from the last two tab positions. 4936 */ 4937 4938 if (tabArrayPtr->numTabs > 1) { 4939 prev = tabArrayPtr->tabs[tabArrayPtr->numTabs-2].location; 4940 } else { 4941 prev = 0; 4942 } 4943 alignment = tabArrayPtr->tabs[tabArrayPtr->numTabs-1].alignment; 4944 tabX = tabArrayPtr->tabs[tabArrayPtr->numTabs-1].location 4945 + (index + 1 - tabArrayPtr->numTabs) 4946 * (tabArrayPtr->tabs[tabArrayPtr->numTabs-1].location - prev); 4947 } 4948 4949 if (alignment == LEFT) { 4950 desired = tabX; 4951 goto update; 4952 } 4953 4954 if ((alignment == CENTER) || (alignment == RIGHT)) { 4955 /* 4956 * Compute the width of all the information in the tab group, 4957 * then use it to pick a desired location. 4958 */ 4959 4960 width = 0; 4961 for (chunkPtr2 = chunkPtr->nextPtr; chunkPtr2 != NULL; 4962 chunkPtr2 = chunkPtr2->nextPtr) { 4963 width += chunkPtr2->width; 4964 } 4965 if (alignment == CENTER) { 4966 desired = tabX - width/2; 4967 } else { 4968 desired = tabX - width; 4969 } 4970 goto update; 4971 } 4972 4973 /* 4974 * Must be numeric alignment. Search through the text to be 4975 * tabbed, looking for the last , or . before the first character 4976 * that isn't a number, comma, period, or sign. 4977 */ 4978 4979 decimalChunkPtr = NULL; 4980 decimal = gotDigit = 0; 4981 for (chunkPtr2 = chunkPtr->nextPtr; chunkPtr2 != NULL; 4982 chunkPtr2 = chunkPtr2->nextPtr) { 4983 if (chunkPtr2->displayProc != CharDisplayProc) { 4984 continue; 4985 } 4986 ciPtr = (CharInfo *) chunkPtr2->clientData; 4987 for (p = ciPtr->chars, i = 0; i < ciPtr->numBytes; p++, i++) { 4988 if (isdigit(UCHAR(*p))) { 4989 gotDigit = 1; 4990 } else if ((*p == '.') || (*p == ',')) { 4991 decimal = p-ciPtr->chars; 4992 decimalChunkPtr = chunkPtr2; 4993 } else if (gotDigit) { 4994 if (decimalChunkPtr == NULL) { 4995 decimal = p-ciPtr->chars; 4996 decimalChunkPtr = chunkPtr2; 4997 } 4998 goto endOfNumber; 4999 } 5000 } 5001 } 5002 endOfNumber: 5003 if (decimalChunkPtr != NULL) { 5004 int curX; 5005 5006 ciPtr = (CharInfo *) decimalChunkPtr->clientData; 5007 MeasureChars(decimalChunkPtr->stylePtr->sValuePtr->tkfont, 5008 ciPtr->chars, decimal, decimalChunkPtr->x, -1, 0, &curX); 5009 desired = tabX - (curX - x); 5010 goto update; 5011 } else { 5012 /* 5013 * There wasn't a decimal point. Right justify the text. 5014 */ 5015 5016 width = 0; 5017 for (chunkPtr2 = chunkPtr->nextPtr; chunkPtr2 != NULL; 5018 chunkPtr2 = chunkPtr2->nextPtr) { 5019 width += chunkPtr2->width; 5020 } 5021 desired = tabX - width; 5022 } 5023 5024 /* 5025 * Shift all of the chunks to the right so that the left edge is 5026 * at the desired location, then expand the chunk containing the 5027 * tab. Be sure that the tab occupies at least the width of a 5028 * space character. 5029 */ 5030 5031 update: 5032 delta = desired - x; 5033 MeasureChars(textPtr->tkfont, " ", 1, 0, -1, 0, &spaceWidth); 5034 if (delta < spaceWidth) { 5035 delta = spaceWidth; 5036 } 5037 for (chunkPtr2 = chunkPtr->nextPtr; chunkPtr2 != NULL; 5038 chunkPtr2 = chunkPtr2->nextPtr) { 5039 chunkPtr2->x += delta; 5040 } 5041 chunkPtr->width += delta; 5042} 5043 5044/* 5045 *---------------------------------------------------------------------- 5046 * 5047 * SizeOfTab -- 5048 * 5049 * This returns an estimate of the amount of white space that will 5050 * be consumed by a tab. 5051 * 5052 * Results: 5053 * The return value is the minimum number of pixels that will 5054 * be occupied by the index'th tab of tabArrayPtr, assuming that 5055 * the current position on the line is x and the end of the 5056 * line is maxX. For numeric tabs, this is a conservative 5057 * estimate. The return value is always >= 0. 5058 * 5059 * Side effects: 5060 * None. 5061 * 5062 *---------------------------------------------------------------------- 5063 */ 5064 5065static int 5066SizeOfTab(textPtr, tabArrayPtr, index, x, maxX) 5067 TkText *textPtr; /* Information about the text widget as 5068 * a whole. */ 5069 TkTextTabArray *tabArrayPtr; /* Information about the tab stops 5070 * that apply to this line. NULL 5071 * means use default tabbing (every 5072 * 8 chars.) */ 5073 int index; /* Index of current tab stop. */ 5074 int x; /* Current x-location in line. Only 5075 * used if tabArrayPtr == NULL. */ 5076 int maxX; /* X-location of pixel just past the 5077 * right edge of the line. */ 5078{ 5079 int tabX, prev, result, spaceWidth; 5080 TkTextTabAlign alignment; 5081 5082 if ((tabArrayPtr == NULL) || (tabArrayPtr->numTabs == 0)) { 5083 tabX = NextTabStop(textPtr->tkfont, x, 0); 5084 return tabX - x; 5085 } 5086 if (index < tabArrayPtr->numTabs) { 5087 tabX = tabArrayPtr->tabs[index].location; 5088 alignment = tabArrayPtr->tabs[index].alignment; 5089 } else { 5090 /* 5091 * Ran out of tab stops; compute a tab position by extrapolating 5092 * from the last two tab positions. 5093 */ 5094 5095 if (tabArrayPtr->numTabs > 1) { 5096 prev = tabArrayPtr->tabs[tabArrayPtr->numTabs-2].location; 5097 } else { 5098 prev = 0; 5099 } 5100 tabX = tabArrayPtr->tabs[tabArrayPtr->numTabs-1].location 5101 + (index + 1 - tabArrayPtr->numTabs) 5102 * (tabArrayPtr->tabs[tabArrayPtr->numTabs-1].location - prev); 5103 alignment = tabArrayPtr->tabs[tabArrayPtr->numTabs-1].alignment; 5104 } 5105 if (alignment == CENTER) { 5106 /* 5107 * Be very careful in the arithmetic below, because maxX may 5108 * be the largest positive number: watch out for integer 5109 * overflow. 5110 */ 5111 5112 if ((maxX-tabX) < (tabX - x)) { 5113 result = (maxX - x) - 2*(maxX - tabX); 5114 } else { 5115 result = 0; 5116 } 5117 goto done; 5118 } 5119 if (alignment == RIGHT) { 5120 result = 0; 5121 goto done; 5122 } 5123 5124 /* 5125 * Note: this treats NUMERIC alignment the same as LEFT 5126 * alignment, which is somewhat conservative. However, it's 5127 * pretty tricky at this point to figure out exactly where 5128 * the damn decimal point will be. 5129 */ 5130 5131 if (tabX > x) { 5132 result = tabX - x; 5133 } else { 5134 result = 0; 5135 } 5136 5137 done: 5138 MeasureChars(textPtr->tkfont, " ", 1, 0, -1, 0, &spaceWidth); 5139 if (result < spaceWidth) { 5140 result = spaceWidth; 5141 } 5142 return result; 5143} 5144 5145/* 5146 *--------------------------------------------------------------------------- 5147 * 5148 * NextTabStop -- 5149 * 5150 * Given the current position, determine where the next default 5151 * tab stop would be located. This procedure is called when the 5152 * current chunk in the text has no tabs defined and so the default 5153 * tab spacing for the font should be used. 5154 * 5155 * Results: 5156 * The location in pixels of the next tab stop. 5157 * 5158 * Side effects: 5159 * None. 5160 * 5161 *--------------------------------------------------------------------------- 5162 */ 5163 5164static int 5165NextTabStop(tkfont, x, tabOrigin) 5166 Tk_Font tkfont; /* Font in which chunk that contains tab 5167 * stop will be drawn. */ 5168 int x; /* X-position in pixels where last 5169 * character was drawn. The next tab stop 5170 * occurs somewhere after this location. */ 5171 int tabOrigin; /* The origin for tab stops. May be 5172 * non-zero if text has been scrolled. */ 5173{ 5174 int tabWidth, rem; 5175 5176 tabWidth = Tk_TextWidth(tkfont, "0", 1) * 8; 5177 if (tabWidth == 0) { 5178 tabWidth = 1; 5179 } 5180 5181 x += tabWidth; 5182 rem = (x - tabOrigin) % tabWidth; 5183 if (rem < 0) { 5184 rem += tabWidth; 5185 } 5186 x -= rem; 5187 return x; 5188} 5189 5190/* 5191 *--------------------------------------------------------------------------- 5192 * 5193 * MeasureChars -- 5194 * 5195 * Determine the number of characters from the string that will fit 5196 * in the given horizontal span. The measurement is done under the 5197 * assumption that Tk_DrawTextLayout will be used to actually display 5198 * the characters. 5199 * 5200 * If tabs are encountered in the string, they will be expanded 5201 * to the next tab stop, unless the TK_IGNORE_TABS flag is specified. 5202 * 5203 * If a newline is encountered in the string, the line will be 5204 * broken at that point, unless the TK_NEWSLINES_NOT_SPECIAL flag 5205 * is specified. 5206 * 5207 * Results: 5208 * The return value is the number of bytes from source 5209 * that fit in the span given by startX and maxX. *nextXPtr 5210 * is filled in with the x-coordinate at which the first 5211 * character that didn't fit would be drawn, if it were to 5212 * be drawn. 5213 * 5214 * Side effects: 5215 * None. 5216 * 5217 *-------------------------------------------------------------- 5218 */ 5219 5220static int 5221MeasureChars(tkfont, source, maxBytes, startX, maxX, tabOrigin, nextXPtr) 5222 Tk_Font tkfont; /* Font in which to draw characters. */ 5223 CONST char *source; /* Characters to be displayed. Need not 5224 * be NULL-terminated. */ 5225 int maxBytes; /* Maximum # of bytes to consider from 5226 * source. */ 5227 int startX; /* X-position at which first character will 5228 * be drawn. */ 5229 int maxX; /* Don't consider any character that would 5230 * cross this x-position. */ 5231 int tabOrigin; /* X-location that serves as "origin" for 5232 * tab stops. */ 5233 int *nextXPtr; /* Return x-position of terminating 5234 * character here. */ 5235{ 5236 int curX, width, ch; 5237 CONST char *special, *end, *start; 5238 5239 ch = 0; /* lint. */ 5240 curX = startX; 5241 special = source; 5242 end = source + maxBytes; 5243 for (start = source; start < end; ) { 5244 if (start >= special) { 5245 /* 5246 * Find the next special character in the string. 5247 */ 5248 5249 for (special = start; special < end; special++) { 5250 ch = *special; 5251 if ((ch == '\t') || (ch == '\n')) { 5252 break; 5253 } 5254 } 5255 } 5256 5257 /* 5258 * Special points at the next special character (or the end of the 5259 * string). Process characters between start and special. 5260 */ 5261 5262 if ((maxX >= 0) && (curX >= maxX)) { 5263 break; 5264 } 5265 start += Tk_MeasureChars(tkfont, start, special - start, maxX - curX, 5266 0, &width); 5267 curX += width; 5268 if (start < special) { 5269 /* 5270 * No more chars fit in line. 5271 */ 5272 5273 break; 5274 } 5275 if (special < end) { 5276 if (ch == '\t') { 5277 start++; 5278 } else { 5279 break; 5280 } 5281 } 5282 } 5283 5284 *nextXPtr = curX; 5285 return start - source; 5286} 5287