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