diff options
Diffstat (limited to 'tk8.6/generic/tkTextDisp.c')
-rw-r--r-- | tk8.6/generic/tkTextDisp.c | 8990 |
1 files changed, 8990 insertions, 0 deletions
diff --git a/tk8.6/generic/tkTextDisp.c b/tk8.6/generic/tkTextDisp.c new file mode 100644 index 0000000..a135084 --- /dev/null +++ b/tk8.6/generic/tkTextDisp.c @@ -0,0 +1,8990 @@ +/* + * tkTextDisp.c -- + * + * This module provides facilities to display text widgets. It is the + * only place where information is kept about the screen layout of text + * widgets. (Well, strictly, each TkTextLine and B-tree node caches its + * last observed pixel height, but that information originates here). + * + * Copyright (c) 1992-1994 The Regents of the University of California. + * Copyright (c) 1994-1997 Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution of + * this file, and for a DISCLAIMER OF ALL WARRANTIES. + */ + +#include "tkInt.h" +#include "tkText.h" + +#ifdef _WIN32 +#include "tkWinInt.h" +#elif defined(__CYGWIN__) +#include "tkUnixInt.h" +#endif + +#ifdef MAC_OSX_TK +#include "tkMacOSXInt.h" +#endif + +/* + * "Calculations of line pixel heights and the size of the vertical + * scrollbar." + * + * Given that tag, font and elide changes can happen to large numbers of + * diverse chunks in a text widget containing megabytes of text, it is not + * possible to recalculate all affected height information immediately any + * such change takes place and maintain a responsive user-experience. Yet, for + * an accurate vertical scrollbar to be drawn, we must know the total number + * of vertical pixels shown on display versus the number available to be + * displayed. + * + * The way the text widget solves this problem is by maintaining cached line + * pixel heights (in the BTree for each logical line), and having asynchronous + * timer callbacks (i) to iterate through the logical lines recalculating + * their heights, and (ii) to recalculate the vertical scrollbar's position + * and size. + * + * Typically this works well but there are some situations where the overall + * functional design of this file causes some problems. These problems can + * only arise because the calculations used to display lines on screen are not + * connected to those in the iterating-line- recalculation-process. + * + * The reason for this disconnect is that the display calculations operate in + * display lines, and the iteration and cache operates in logical lines. + * Given that the display calculations both need not contain complete logical + * lines (at top or bottom of display), and that they do not actually keep + * track of logical lines (for simplicity of code and historical design), this + * means a line may be known and drawn with a different pixel height to that + * which is cached in the BTree, and this might cause some temporary + * undesirable mismatch between display and the vertical scrollbar. + * + * All such mismatches should be temporary, however, since the asynchronous + * height calculations will always catch up eventually. + * + * For further details see the comments before and within the following + * functions below: LayoutDLine, AsyncUpdateLineMetrics, GetYView, + * GetYPixelCount, TkTextUpdateOneLine, TkTextUpdateLineMetrics. + * + * For details of the way in which the BTree keeps track of pixel heights, see + * tkTextBTree.c. Basically the BTree maintains two pieces of information: the + * logical line indices and the pixel height cache. + */ + +/* + * TK_LAYOUT_WITH_BASE_CHUNKS: + * + * With this macro set, collect all char chunks that have no holes + * between them, that are on the same line and use the same font and font + * size. Allocate the chars of all these chunks, the so-called "stretch", + * in a DString in the first chunk, the so-called "base chunk". Use the + * base chunk string for measuring and drawing, so that these actions are + * always performed with maximum context. + * + * This is necessary for text rendering engines that provide ligatures + * and sub-pixel layout, like ATSU on Mac. If we don't do this, the + * measuring will change all the time, leading to an ugly "tremble and + * shiver" effect. This is because of the continuous splitting and + * re-merging of chunks that goes on in a text widget, when the cursor or + * the selection move. + * + * Side effects: + * + * Memory management changes. Instead of attaching the character data to + * the clientData structures of the char chunks, an additional DString is + * used. The collection process will even lead to resizing this DString + * for large stretches (> TCL_DSTRING_STATIC_SIZE == 200). We could + * reduce the overall memory footprint by copying the result to a plain + * char array after the line breaking process, but that would complicate + * the code and make performance even worse speedwise. See also TODOs. + * + * TODOs: + * + * - Move the character collection process from the LayoutProc into + * LayoutDLine(), so that the collection can be done before actual + * layout. In this way measuring can look at the following text, too, + * right from the beginning. Memory handling can also be improved with + * this. Problem: We don't easily know which chunks are adjacent until + * all the other chunks have calculated their width. Apparently marks + * would return width==0. A separate char collection loop would have to + * know these things. + * + * - Use a new context parameter to pass the context from LayoutDLine() to + * the LayoutProc instead of using a global variable like now. Not + * pressing until the previous point gets implemented. + */ + +/* + * The following structure describes how to display a range of characters. + * The information is generated by scanning all of the tags associated with + * the characters and combining that with default information for the overall + * widget. These structures form the hash keys for dInfoPtr->styleTable. + */ + +typedef struct StyleValues { + Tk_3DBorder border; /* Used for drawing background under text. + * NULL means use widget background. */ + int borderWidth; /* Width of 3-D border for background. */ + int relief; /* 3-D relief for background. */ + Pixmap bgStipple; /* Stipple bitmap for background. None means + * draw solid. */ + XColor *fgColor; /* Foreground color for text. */ + Tk_Font tkfont; /* Font for displaying text. */ + Pixmap fgStipple; /* Stipple bitmap for text and other + * foreground stuff. None means draw solid.*/ + int justify; /* Justification style for text. */ + int lMargin1; /* Left margin, in pixels, for first display + * line of each text line. */ + int lMargin2; /* Left margin, in pixels, for second and + * later display lines of each text line. */ + Tk_3DBorder lMarginColor; /* Color of left margins (1 and 2). */ + int offset; /* Offset in pixels of baseline, relative to + * baseline of line. */ + int overstrike; /* Non-zero means draw overstrike through + * text. */ + XColor *overstrikeColor; /* Foreground color for overstrike through + * text. */ + int rMargin; /* Right margin, in pixels. */ + Tk_3DBorder rMarginColor; /* Color of right margin. */ + int spacing1; /* Spacing above first dline in text line. */ + int spacing2; /* Spacing between lines of dline. */ + int spacing3; /* Spacing below last dline in text line. */ + TkTextTabArray *tabArrayPtr;/* Locations and types of tab stops (may be + * NULL). */ + int tabStyle; /* One of TABULAR or WORDPROCESSOR. */ + int underline; /* Non-zero means draw underline underneath + * text. */ + XColor *underlineColor; /* Foreground color for underline underneath + * text. */ + int elide; /* Zero means draw text, otherwise not. */ + TkWrapMode wrapMode; /* How to handle wrap-around for this tag. + * One of TEXT_WRAPMODE_CHAR, + * TEXT_WRAPMODE_NONE or TEXT_WRAPMODE_WORD.*/ +} StyleValues; + +/* + * The following structure extends the StyleValues structure above with + * graphics contexts used to actually draw the characters. The entries in + * dInfoPtr->styleTable point to structures of this type. + */ + +typedef struct TextStyle { + int refCount; /* Number of times this structure is + * referenced in Chunks. */ + GC bgGC; /* Graphics context for background. None means + * use widget background. */ + GC fgGC; /* Graphics context for foreground. */ + GC ulGC; /* Graphics context for underline. */ + GC ovGC; /* Graphics context for overstrike. */ + StyleValues *sValuePtr; /* Raw information from which GCs were + * derived. */ + Tcl_HashEntry *hPtr; /* Pointer to entry in styleTable. Used to + * delete entry. */ +} TextStyle; + +/* + * The following macro determines whether two styles have the same background + * so that, for example, no beveled border should be drawn between them. + */ + +#define SAME_BACKGROUND(s1, s2) \ + (((s1)->sValuePtr->border == (s2)->sValuePtr->border) \ + && ((s1)->sValuePtr->borderWidth == (s2)->sValuePtr->borderWidth) \ + && ((s1)->sValuePtr->relief == (s2)->sValuePtr->relief) \ + && ((s1)->sValuePtr->bgStipple == (s2)->sValuePtr->bgStipple)) + +/* + * The following macro is used to compare two floating-point numbers to within + * a certain degree of scale. Direct comparison fails on processors where the + * processor and memory representations of FP numbers of a particular + * precision is different (e.g. Intel) + */ + +#define FP_EQUAL_SCALE(double1, double2, scaleFactor) \ + (fabs((double1)-(double2))*((scaleFactor)+1.0) < 0.3) + +/* + * Macro to make debugging/testing logging a little easier. + */ + +#define LOG(toVar,what) \ + Tcl_SetVar2(textPtr->interp, toVar, NULL, (what), \ + TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT) + +/* + * The following structure describes one line of the display, which may be + * either part or all of one line of the text. + */ + +typedef struct DLine { + TkTextIndex index; /* Identifies first character in text that is + * displayed on this line. */ + int byteCount; /* Number of bytes accounted for by this + * display line, including a trailing space or + * newline that isn't actually displayed. */ + int logicalLinesMerged; /* Number of extra logical lines merged into + * this one due to elided newlines. */ + int y; /* Y-position at which line is supposed to be + * drawn (topmost pixel of rectangular area + * occupied by line). */ + int oldY; /* Y-position at which line currently appears + * on display. This is used to move lines by + * scrolling rather than re-drawing. If + * 'flags' have the OLD_Y_INVALID bit set, + * then we will never examine this field + * (which means line isn't currently visible + * on display and must be redrawn). */ + int height; /* Height of line, in pixels. */ + int baseline; /* Offset of text baseline from y, in + * pixels. */ + int spaceAbove; /* How much extra space was added to the top + * of the line because of spacing options. + * This is included in height and baseline. */ + int spaceBelow; /* How much extra space was added to the + * bottom of the line because of spacing + * options. This is included in height. */ + Tk_3DBorder lMarginColor; /* Background color of the area corresponding + * to the left margin of the display line. */ + int lMarginWidth; /* Pixel width of the area corresponding to + * the left margin. */ + Tk_3DBorder rMarginColor; /* Background color of the area corresponding + * to the right margin of the display line. */ + int rMarginWidth; /* Pixel width of the area corresponding to + * the right margin. */ + int length; /* Total length of line, in pixels. */ + TkTextDispChunk *chunkPtr; /* Pointer to first chunk in list of all of + * those that are displayed on this line of + * the screen. */ + struct DLine *nextPtr; /* Next in list of all display lines for this + * window. The list is sorted in order from + * top to bottom. Note: the next DLine doesn't + * always correspond to the next line of text: + * (a) can have multiple DLines for one text + * line (wrapping), (b) can have elided newlines, + * and (c) can have gaps where DLine's + * have been deleted because they're out of + * date. */ + int flags; /* Various flag bits: see below for values. */ +} DLine; + +/* + * Flag bits for DLine structures: + * + * HAS_3D_BORDER - Non-zero means that at least one of the chunks + * in this line has a 3D border, so it + * potentially interacts with 3D borders in + * neighboring lines (see DisplayLineBackground). + * NEW_LAYOUT - Non-zero means that the line has been + * re-layed out since the last time the display + * was updated. + * TOP_LINE - Non-zero means that this was the top line in + * in the window the last time that the window + * was laid out. This is important because a line + * may be displayed differently if its at the top + * or bottom than if it's in the middle + * (e.g. beveled edges aren't displayed for + * middle lines if the adjacent line has a + * similar background). + * BOTTOM_LINE - Non-zero means that this was the bottom line + * in the window the last time that the window + * was laid out. + * OLD_Y_INVALID - The value of oldY in the structure is not + * valid or useful and should not be examined. + * 'oldY' is only useful when the DLine is + * currently displayed at a different position + * and we wish to re-display it via scrolling, so + * this means the DLine needs redrawing. + */ + +#define HAS_3D_BORDER 1 +#define NEW_LAYOUT 2 +#define TOP_LINE 4 +#define BOTTOM_LINE 8 +#define OLD_Y_INVALID 16 + +/* + * Overall display information for a text widget: + */ + +typedef struct TextDInfo { + Tcl_HashTable styleTable; /* Hash table that maps from StyleValues to + * TextStyles for this widget. */ + DLine *dLinePtr; /* First in list of all display lines for this + * widget, in order from top to bottom. */ + int topPixelOffset; /* Identifies first pixel in top display line + * to display in window. */ + int newTopPixelOffset; /* Desired first pixel in top display line to + * display in window. */ + GC copyGC; /* Graphics context for copying from off- + * screen pixmaps onto screen. */ + GC scrollGC; /* Graphics context for copying from one place + * in the window to another (scrolling): + * differs from copyGC in that we need to get + * GraphicsExpose events. */ + int x; /* First x-coordinate that may be used for + * actually displaying line information. + * Leaves space for border, etc. */ + int y; /* First y-coordinate that may be used for + * actually displaying line information. + * Leaves space for border, etc. */ + int maxX; /* First x-coordinate to right of available + * space for displaying lines. */ + int maxY; /* First y-coordinate below available space + * for displaying lines. */ + int topOfEof; /* Top-most pixel (lowest y-value) that has + * been drawn in the appropriate fashion for + * the portion of the window after the last + * line of the text. This field is used to + * figure out when to redraw part or all of + * the eof field. */ + + /* + * Information used for scrolling: + */ + + int newXPixelOffset; /* Desired x scroll position, measured as the + * number of pixels off-screen to the left for + * a line with no left margin. */ + int curXPixelOffset; /* Actual x scroll position, measured as the + * number of pixels off-screen to the left. */ + int maxLength; /* Length in pixels of longest line that's + * visible in window (length may exceed window + * size). If there's no wrapping, this will be + * zero. */ + double xScrollFirst, xScrollLast; + /* Most recent values reported to horizontal + * scrollbar; used to eliminate unnecessary + * reports. */ + double yScrollFirst, yScrollLast; + /* Most recent values reported to vertical + * scrollbar; used to eliminate unnecessary + * reports. */ + + /* + * The following information is used to implement scanning: + */ + + int scanMarkXPixel; /* Pixel index of left edge of the window when + * the scan started. */ + int scanMarkX; /* X-position of mouse at time scan started. */ + int scanTotalYScroll; /* Total scrolling (in screen pixels) that has + * occurred since scanMarkY was set. */ + int scanMarkY; /* Y-position of mouse at time scan started. */ + + /* + * Miscellaneous information: + */ + + int dLinesInvalidated; /* This value is set to 1 whenever something + * happens that invalidates information in + * DLine structures; if a redisplay is in + * progress, it will see this and abort the + * redisplay. This is needed because, for + * example, an embedded window could change + * its size when it is first displayed, + * invalidating the DLine that is currently + * being displayed. If redisplay continues, it + * will use freed memory and could dump + * core. */ + int flags; /* Various flag values: see below for + * definitions. */ + /* + * Information used to handle the asynchronous updating of the y-scrollbar + * and the vertical height calculations: + */ + + int lineMetricUpdateEpoch; /* Stores a number which is incremented each + * time the text widget changes in a + * significant way (e.g. resizing or + * geometry-influencing tag changes). */ + int currentMetricUpdateLine;/* Stores a counter which is used to iterate + * over the logical lines contained in the + * widget and update their geometry + * calculations, if they are out of date. */ + TkTextIndex metricIndex; /* If the current metric update line wraps + * into very many display lines, then this is + * used to keep track of what index we've got + * to so far... */ + int metricPixelHeight; /* ...and this is for the height calculation + * so far...*/ + int metricEpoch; /* ...and this for the epoch of the partial + * calculation so it can be cancelled if + * things change once more. This field will be + * -1 if there is no long-line calculation in + * progress, and take a non-negative value if + * there is such a calculation in progress. */ + int lastMetricUpdateLine; /* When the current update line reaches this + * line, we are done and should stop the + * asychronous callback mechanism. */ + Tcl_TimerToken lineUpdateTimer; + /* A token pointing to the current line metric + * update callback. */ + Tcl_TimerToken scrollbarTimer; + /* A token pointing to the current scrollbar + * update callback. */ +} TextDInfo; + +/* + * In TkTextDispChunk structures for character segments, the clientData field + * points to one of the following structures: + */ + +#if !TK_LAYOUT_WITH_BASE_CHUNKS + +typedef struct CharInfo { + int numBytes; /* Number of bytes to display. */ + char chars[1]; /* UTF characters to display. Actual size will + * be numBytes, not 1. THIS MUST BE THE LAST + * FIELD IN THE STRUCTURE. */ +} CharInfo; + +#else /* TK_LAYOUT_WITH_BASE_CHUNKS */ + +typedef struct CharInfo { + TkTextDispChunk *baseChunkPtr; + int baseOffset; /* Starting offset in base chunk + * baseChars. */ + int numBytes; /* Number of bytes that belong to this + * chunk. */ + const char *chars; /* UTF characters to display. Actually points + * into the baseChars of the base chunk. Only + * valid after FinalizeBaseChunk(). */ +} CharInfo; + +/* + * The BaseCharInfo is a CharInfo with some additional data added. + */ + +typedef struct BaseCharInfo { + CharInfo ci; + Tcl_DString baseChars; /* Actual characters for the stretch of text + * represented by this base chunk. */ + int width; /* Width in pixels of the whole string, if + * known, else -1. Valid during + * LayoutDLine(). */ +} BaseCharInfo; + +/* TODO: Thread safety */ +static TkTextDispChunk *baseCharChunkPtr = NULL; + +#endif /* TK_LAYOUT_WITH_BASE_CHUNKS */ + +/* + * Flag values for TextDInfo structures: + * + * DINFO_OUT_OF_DATE: Non-zero means that the DLine structures for + * this window are partially or completely out of + * date and need to be recomputed. + * REDRAW_PENDING: Means that a when-idle handler has been + * scheduled to update the display. + * REDRAW_BORDERS: Means window border or pad area has + * potentially been damaged and must be redrawn. + * REPICK_NEEDED: 1 means that the widget has been modified in a + * way that could change the current character (a + * different character might be under the mouse + * cursor now). Need to recompute the current + * character before the next redisplay. + */ + +#define DINFO_OUT_OF_DATE 1 +#define REDRAW_PENDING 2 +#define REDRAW_BORDERS 4 +#define REPICK_NEEDED 8 + +/* + * Action values for FreeDLines: + * + * DLINE_FREE: Free the lines, but no need to unlink them from the + * current list of actual display lines. + * DLINE_UNLINK: Free and unlink from current display. + * DLINE_FREE_TEMP: Free, but don't unlink, and also don't set + * 'dLinesInvalidated'. + */ + +#define DLINE_FREE 0 +#define DLINE_UNLINK 1 +#define DLINE_FREE_TEMP 2 + +/* + * The following counters keep statistics about redisplay that can be checked + * to see how clever this code is at reducing redisplays. + */ + +static int numRedisplays; /* Number of calls to DisplayText. */ +static int linesRedrawn; /* Number of calls to DisplayDLine. */ +static int numCopies; /* Number of calls to XCopyArea to copy part + * of the screen. */ +static int lineHeightsRecalculated; + /* Number of line layouts purely for height + * calculation purposes.*/ +/* + * Forward declarations for functions defined later in this file: + */ + +static void AdjustForTab(TkText *textPtr, + TkTextTabArray *tabArrayPtr, int index, + TkTextDispChunk *chunkPtr); +static void CharBboxProc(TkText *textPtr, + TkTextDispChunk *chunkPtr, int index, int y, + int lineHeight, int baseline, int *xPtr, + int *yPtr, int *widthPtr, int *heightPtr); +static int CharChunkMeasureChars(TkTextDispChunk *chunkPtr, + const char *chars, int charsLen, + int start, int end, int startX, int maxX, + int flags, int *nextX); +static void CharDisplayProc(TkText *textPtr, + TkTextDispChunk *chunkPtr, int x, int y, + int height, int baseline, Display *display, + Drawable dst, int screenY); +static int CharMeasureProc(TkTextDispChunk *chunkPtr, int x); +static void CharUndisplayProc(TkText *textPtr, + TkTextDispChunk *chunkPtr); +#if TK_LAYOUT_WITH_BASE_CHUNKS +static void FinalizeBaseChunk(TkTextDispChunk *additionalChunkPtr); +static void FreeBaseChunk(TkTextDispChunk *baseChunkPtr); +static int IsSameFGStyle(TextStyle *style1, TextStyle *style2); +static void RemoveFromBaseChunk(TkTextDispChunk *chunkPtr); +#endif +/* + * Definitions of elided procs. Compiler can't inline these since we use + * pointers to these functions. ElideDisplayProc and ElideUndisplayProc are + * special-cased for speed, as potentially many elided DLine chunks if large, + * tag toggle-filled elided region. + */ +static void ElideBboxProc(TkText *textPtr, + TkTextDispChunk *chunkPtr, int index, int y, + int lineHeight, int baseline, int *xPtr, + int *yPtr, int *widthPtr, int *heightPtr); +static int ElideMeasureProc(TkTextDispChunk *chunkPtr, int x); +static void DisplayDLine(TkText *textPtr, DLine *dlPtr, + DLine *prevPtr, Pixmap pixmap); +static void DisplayLineBackground(TkText *textPtr, DLine *dlPtr, + DLine *prevPtr, Pixmap pixmap); +static void DisplayText(ClientData clientData); +static DLine * FindDLine(TkText *textPtr, DLine *dlPtr, + const TkTextIndex *indexPtr); +static void FreeDLines(TkText *textPtr, DLine *firstPtr, + DLine *lastPtr, int action); +static void FreeStyle(TkText *textPtr, TextStyle *stylePtr); +static TextStyle * GetStyle(TkText *textPtr, const TkTextIndex *indexPtr); +static void GetXView(Tcl_Interp *interp, TkText *textPtr, + int report); +static void GetYView(Tcl_Interp *interp, TkText *textPtr, + int report); +static int GetYPixelCount(TkText *textPtr, DLine *dlPtr); +static DLine * LayoutDLine(TkText *textPtr, + const TkTextIndex *indexPtr); +static int MeasureChars(Tk_Font tkfont, const char *source, + int maxBytes, int rangeStart, int rangeLength, + int startX, int maxX, int flags, int *nextXPtr); +static void MeasureUp(TkText *textPtr, + const TkTextIndex *srcPtr, int distance, + TkTextIndex *dstPtr, int *overlap); +static int NextTabStop(Tk_Font tkfont, int x, int tabOrigin); +static void UpdateDisplayInfo(TkText *textPtr); +static void YScrollByLines(TkText *textPtr, int offset); +static void YScrollByPixels(TkText *textPtr, int offset); +static int SizeOfTab(TkText *textPtr, int tabStyle, + TkTextTabArray *tabArrayPtr, int *indexPtr, int x, + int maxX); +static void TextChanged(TkText *textPtr, + const TkTextIndex *index1Ptr, + const TkTextIndex *index2Ptr); +static void TextInvalidateRegion(TkText *textPtr, TkRegion region); +static void TextRedrawTag(TkText *textPtr, + TkTextIndex *index1Ptr, TkTextIndex *index2Ptr, + TkTextTag *tagPtr, int withTag); +static void TextInvalidateLineMetrics(TkText *textPtr, + TkTextLine *linePtr, int lineCount, int action); +static int CalculateDisplayLineHeight(TkText *textPtr, + const TkTextIndex *indexPtr, int *byteCountPtr, + int *mergedLinePtr); +static void DlineIndexOfX(TkText *textPtr, + DLine *dlPtr, int x, TkTextIndex *indexPtr); +static int DlineXOfIndex(TkText *textPtr, + DLine *dlPtr, int byteIndex); +static int TextGetScrollInfoObj(Tcl_Interp *interp, + TkText *textPtr, int objc, + Tcl_Obj *const objv[], double *dblPtr, + int *intPtr); +static void AsyncUpdateLineMetrics(ClientData clientData); +static void GenerateWidgetViewSyncEvent(TkText *textPtr, Bool InSync); +static void AsyncUpdateYScrollbar(ClientData clientData); +static int IsStartOfNotMergedLine(TkText *textPtr, + CONST TkTextIndex *indexPtr); + +/* + * Result values returned by TextGetScrollInfoObj: + */ + +#define TKTEXT_SCROLL_MOVETO 1 +#define TKTEXT_SCROLL_PAGES 2 +#define TKTEXT_SCROLL_UNITS 3 +#define TKTEXT_SCROLL_ERROR 4 +#define TKTEXT_SCROLL_PIXELS 5 + +/* + *---------------------------------------------------------------------- + * + * TkTextCreateDInfo -- + * + * This function is called when a new text widget is created. Its job is + * to set up display-related information for the widget. + * + * Results: + * None. + * + * Side effects: + * A TextDInfo data structure is allocated and initialized and attached + * to textPtr. + * + *---------------------------------------------------------------------- + */ + +void +TkTextCreateDInfo( + TkText *textPtr) /* Overall information for text widget. */ +{ + register TextDInfo *dInfoPtr; + XGCValues gcValues; + + dInfoPtr = ckalloc(sizeof(TextDInfo)); + Tcl_InitHashTable(&dInfoPtr->styleTable, sizeof(StyleValues)/sizeof(int)); + dInfoPtr->dLinePtr = NULL; + dInfoPtr->copyGC = None; + gcValues.graphics_exposures = True; + dInfoPtr->scrollGC = Tk_GetGC(textPtr->tkwin, GCGraphicsExposures, + &gcValues); + dInfoPtr->topOfEof = 0; + dInfoPtr->newXPixelOffset = 0; + dInfoPtr->curXPixelOffset = 0; + dInfoPtr->maxLength = 0; + dInfoPtr->xScrollFirst = -1; + dInfoPtr->xScrollLast = -1; + dInfoPtr->yScrollFirst = -1; + dInfoPtr->yScrollLast = -1; + dInfoPtr->scanMarkXPixel = 0; + dInfoPtr->scanMarkX = 0; + dInfoPtr->scanTotalYScroll = 0; + dInfoPtr->scanMarkY = 0; + dInfoPtr->dLinesInvalidated = 0; + dInfoPtr->flags = DINFO_OUT_OF_DATE; + dInfoPtr->topPixelOffset = 0; + dInfoPtr->newTopPixelOffset = 0; + dInfoPtr->currentMetricUpdateLine = -1; + dInfoPtr->lastMetricUpdateLine = -1; + dInfoPtr->lineMetricUpdateEpoch = 1; + dInfoPtr->metricEpoch = -1; + dInfoPtr->metricIndex.textPtr = NULL; + dInfoPtr->metricIndex.linePtr = NULL; + dInfoPtr->lineUpdateTimer = NULL; + dInfoPtr->scrollbarTimer = NULL; + + textPtr->dInfoPtr = dInfoPtr; +} + +/* + *---------------------------------------------------------------------- + * + * TkTextFreeDInfo -- + * + * This function is called to free up all of the private display + * information kept by this file for a text widget. + * + * Results: + * None. + * + * Side effects: + * Lots of resources get freed. + * + *---------------------------------------------------------------------- + */ + +void +TkTextFreeDInfo( + TkText *textPtr) /* Overall information for text widget. */ +{ + register TextDInfo *dInfoPtr = textPtr->dInfoPtr; + + /* + * Be careful to free up styleTable *after* freeing up all the DLines, so + * that the hash table is still intact to free up the style-related + * information from the lines. Once the lines are all free then styleTable + * will be empty. + */ + + FreeDLines(textPtr, dInfoPtr->dLinePtr, NULL, DLINE_UNLINK); + Tcl_DeleteHashTable(&dInfoPtr->styleTable); + if (dInfoPtr->copyGC != None) { + Tk_FreeGC(textPtr->display, dInfoPtr->copyGC); + } + Tk_FreeGC(textPtr->display, dInfoPtr->scrollGC); + if (dInfoPtr->flags & REDRAW_PENDING) { + Tcl_CancelIdleCall(DisplayText, textPtr); + } + if (dInfoPtr->lineUpdateTimer != NULL) { + Tcl_DeleteTimerHandler(dInfoPtr->lineUpdateTimer); + textPtr->refCount--; + dInfoPtr->lineUpdateTimer = NULL; + } + if (dInfoPtr->scrollbarTimer != NULL) { + Tcl_DeleteTimerHandler(dInfoPtr->scrollbarTimer); + textPtr->refCount--; + dInfoPtr->scrollbarTimer = NULL; + } + ckfree(dInfoPtr); +} + +/* + *---------------------------------------------------------------------- + * + * GetStyle -- + * + * This function creates all the information needed to display text at a + * particular location. + * + * Results: + * The return value is a pointer to a TextStyle structure that + * corresponds to *sValuePtr. + * + * Side effects: + * A new entry may be created in the style table for the widget. + * + *---------------------------------------------------------------------- + */ + +static TextStyle * +GetStyle( + TkText *textPtr, /* Overall information about text widget. */ + const TkTextIndex *indexPtr)/* The character in the text for which display + * information is wanted. */ +{ + TkTextTag **tagPtrs; + register TkTextTag *tagPtr; + StyleValues styleValues; + TextStyle *stylePtr; + Tcl_HashEntry *hPtr; + int numTags, isNew, i; + int isSelected; + XGCValues gcValues; + unsigned long mask; + /* + * The variables below keep track of the highest-priority specification + * that has occurred for each of the various fields of the StyleValues. + */ + int borderPrio, borderWidthPrio, reliefPrio, bgStipplePrio; + int fgPrio, fontPrio, fgStipplePrio; + int underlinePrio, elidePrio, justifyPrio, offsetPrio; + int lMargin1Prio, lMargin2Prio, rMarginPrio; + int lMarginColorPrio, rMarginColorPrio; + int spacing1Prio, spacing2Prio, spacing3Prio; + int overstrikePrio, tabPrio, tabStylePrio, wrapPrio; + + /* + * Find out what tags are present for the character, then compute a + * StyleValues structure corresponding to those tags (scan through all of + * the tags, saving information for the highest-priority tag). + */ + + tagPtrs = TkBTreeGetTags(indexPtr, textPtr, &numTags); + borderPrio = borderWidthPrio = reliefPrio = bgStipplePrio = -1; + fgPrio = fontPrio = fgStipplePrio = -1; + underlinePrio = elidePrio = justifyPrio = offsetPrio = -1; + lMargin1Prio = lMargin2Prio = rMarginPrio = -1; + lMarginColorPrio = rMarginColorPrio = -1; + spacing1Prio = spacing2Prio = spacing3Prio = -1; + overstrikePrio = tabPrio = tabStylePrio = wrapPrio = -1; + memset(&styleValues, 0, sizeof(StyleValues)); + styleValues.relief = TK_RELIEF_FLAT; + styleValues.fgColor = textPtr->fgColor; + styleValues.underlineColor = textPtr->fgColor; + styleValues.overstrikeColor = textPtr->fgColor; + styleValues.tkfont = textPtr->tkfont; + styleValues.justify = TK_JUSTIFY_LEFT; + styleValues.spacing1 = textPtr->spacing1; + styleValues.spacing2 = textPtr->spacing2; + styleValues.spacing3 = textPtr->spacing3; + styleValues.tabArrayPtr = textPtr->tabArrayPtr; + styleValues.tabStyle = textPtr->tabStyle; + styleValues.wrapMode = textPtr->wrapMode; + styleValues.elide = 0; + isSelected = 0; + + for (i = 0 ; i < numTags; i++) { + if (textPtr->selTagPtr == tagPtrs[i]) { + isSelected = 1; + break; + } + } + + for (i = 0 ; i < numTags; i++) { + Tk_3DBorder border; + XColor *fgColor; + + tagPtr = tagPtrs[i]; + border = tagPtr->border; + fgColor = tagPtr->fgColor; + + /* + * If this is the selection tag, and inactiveSelBorder is NULL (the + * default on Windows), then we need to skip it if we don't have the + * focus. + */ + + if ((tagPtr == textPtr->selTagPtr) && !(textPtr->flags & GOT_FOCUS)) { + if (textPtr->inactiveSelBorder == NULL +#ifdef MAC_OSX_TK + /* Don't show inactive selection in disabled widgets. */ + || textPtr->state == TK_TEXT_STATE_DISABLED +#endif + ) { + continue; + } + border = textPtr->inactiveSelBorder; + } + + if ((tagPtr->selBorder != NULL) && (isSelected)) { + border = tagPtr->selBorder; + } + + if ((tagPtr->selFgColor != None) && (isSelected)) { + fgColor = tagPtr->selFgColor; + } + + if ((border != NULL) && (tagPtr->priority > borderPrio)) { + styleValues.border = border; + borderPrio = tagPtr->priority; + } + if ((tagPtr->borderWidthPtr != NULL) + && (Tcl_GetString(tagPtr->borderWidthPtr)[0] != '\0') + && (tagPtr->priority > borderWidthPrio)) { + styleValues.borderWidth = tagPtr->borderWidth; + borderWidthPrio = tagPtr->priority; + } + if ((tagPtr->reliefString != NULL) + && (tagPtr->priority > reliefPrio)) { + if (styleValues.border == NULL) { + styleValues.border = textPtr->border; + } + styleValues.relief = tagPtr->relief; + reliefPrio = tagPtr->priority; + } + if ((tagPtr->bgStipple != None) + && (tagPtr->priority > bgStipplePrio)) { + styleValues.bgStipple = tagPtr->bgStipple; + bgStipplePrio = tagPtr->priority; + } + if ((fgColor != None) && (tagPtr->priority > fgPrio)) { + styleValues.fgColor = fgColor; + fgPrio = tagPtr->priority; + } + if ((tagPtr->tkfont != None) && (tagPtr->priority > fontPrio)) { + styleValues.tkfont = tagPtr->tkfont; + fontPrio = tagPtr->priority; + } + if ((tagPtr->fgStipple != None) + && (tagPtr->priority > fgStipplePrio)) { + styleValues.fgStipple = tagPtr->fgStipple; + fgStipplePrio = tagPtr->priority; + } + if ((tagPtr->justifyString != NULL) + && (tagPtr->priority > justifyPrio)) { + styleValues.justify = tagPtr->justify; + justifyPrio = tagPtr->priority; + } + if ((tagPtr->lMargin1String != NULL) + && (tagPtr->priority > lMargin1Prio)) { + styleValues.lMargin1 = tagPtr->lMargin1; + lMargin1Prio = tagPtr->priority; + } + if ((tagPtr->lMargin2String != NULL) + && (tagPtr->priority > lMargin2Prio)) { + styleValues.lMargin2 = tagPtr->lMargin2; + lMargin2Prio = tagPtr->priority; + } + if ((tagPtr->lMarginColor != NULL) + && (tagPtr->priority > lMarginColorPrio)) { + styleValues.lMarginColor = tagPtr->lMarginColor; + lMarginColorPrio = tagPtr->priority; + } + if ((tagPtr->offsetString != NULL) + && (tagPtr->priority > offsetPrio)) { + styleValues.offset = tagPtr->offset; + offsetPrio = tagPtr->priority; + } + if ((tagPtr->overstrikeString != NULL) + && (tagPtr->priority > overstrikePrio)) { + styleValues.overstrike = tagPtr->overstrike; + overstrikePrio = tagPtr->priority; + if (tagPtr->overstrikeColor != None) { + styleValues.overstrikeColor = tagPtr->overstrikeColor; + } else if (fgColor != None) { + styleValues.overstrikeColor = fgColor; + } + } + if ((tagPtr->rMarginString != NULL) + && (tagPtr->priority > rMarginPrio)) { + styleValues.rMargin = tagPtr->rMargin; + rMarginPrio = tagPtr->priority; + } + if ((tagPtr->rMarginColor != NULL) + && (tagPtr->priority > rMarginColorPrio)) { + styleValues.rMarginColor = tagPtr->rMarginColor; + rMarginColorPrio = tagPtr->priority; + } + if ((tagPtr->spacing1String != NULL) + && (tagPtr->priority > spacing1Prio)) { + styleValues.spacing1 = tagPtr->spacing1; + spacing1Prio = tagPtr->priority; + } + if ((tagPtr->spacing2String != NULL) + && (tagPtr->priority > spacing2Prio)) { + styleValues.spacing2 = tagPtr->spacing2; + spacing2Prio = tagPtr->priority; + } + if ((tagPtr->spacing3String != NULL) + && (tagPtr->priority > spacing3Prio)) { + styleValues.spacing3 = tagPtr->spacing3; + spacing3Prio = tagPtr->priority; + } + if ((tagPtr->tabStringPtr != NULL) + && (tagPtr->priority > tabPrio)) { + styleValues.tabArrayPtr = tagPtr->tabArrayPtr; + tabPrio = tagPtr->priority; + } + if ((tagPtr->tabStyle != TK_TEXT_TABSTYLE_NONE) + && (tagPtr->priority > tabStylePrio)) { + styleValues.tabStyle = tagPtr->tabStyle; + tabStylePrio = tagPtr->priority; + } + if ((tagPtr->underlineString != NULL) + && (tagPtr->priority > underlinePrio)) { + styleValues.underline = tagPtr->underline; + underlinePrio = tagPtr->priority; + if (tagPtr->underlineColor != None) { + styleValues.underlineColor = tagPtr->underlineColor; + } else if (fgColor != None) { + styleValues.underlineColor = fgColor; + } + } + if ((tagPtr->elideString != NULL) + && (tagPtr->priority > elidePrio)) { + styleValues.elide = tagPtr->elide; + elidePrio = tagPtr->priority; + } + if ((tagPtr->wrapMode != TEXT_WRAPMODE_NULL) + && (tagPtr->priority > wrapPrio)) { + styleValues.wrapMode = tagPtr->wrapMode; + wrapPrio = tagPtr->priority; + } + } + if (tagPtrs != NULL) { + ckfree(tagPtrs); + } + + /* + * Use an existing style if there's one around that matches. + */ + + hPtr = Tcl_CreateHashEntry(&textPtr->dInfoPtr->styleTable, + (char *) &styleValues, &isNew); + if (!isNew) { + stylePtr = Tcl_GetHashValue(hPtr); + stylePtr->refCount++; + return stylePtr; + } + + /* + * No existing style matched. Make a new one. + */ + + stylePtr = ckalloc(sizeof(TextStyle)); + stylePtr->refCount = 1; + if (styleValues.border != NULL) { + gcValues.foreground = Tk_3DBorderColor(styleValues.border)->pixel; + mask = GCForeground; + if (styleValues.bgStipple != None) { + gcValues.stipple = styleValues.bgStipple; + gcValues.fill_style = FillStippled; + mask |= GCStipple|GCFillStyle; + } + stylePtr->bgGC = Tk_GetGC(textPtr->tkwin, mask, &gcValues); + } else { + stylePtr->bgGC = None; + } + mask = GCFont; + gcValues.font = Tk_FontId(styleValues.tkfont); + mask |= GCForeground; + gcValues.foreground = styleValues.fgColor->pixel; + if (styleValues.fgStipple != None) { + gcValues.stipple = styleValues.fgStipple; + gcValues.fill_style = FillStippled; + mask |= GCStipple|GCFillStyle; + } + stylePtr->fgGC = Tk_GetGC(textPtr->tkwin, mask, &gcValues); + mask = GCForeground; + gcValues.foreground = styleValues.underlineColor->pixel; + stylePtr->ulGC = Tk_GetGC(textPtr->tkwin, mask, &gcValues); + gcValues.foreground = styleValues.overstrikeColor->pixel; + stylePtr->ovGC = Tk_GetGC(textPtr->tkwin, mask, &gcValues); + stylePtr->sValuePtr = (StyleValues *) + Tcl_GetHashKey(&textPtr->dInfoPtr->styleTable, hPtr); + stylePtr->hPtr = hPtr; + Tcl_SetHashValue(hPtr, stylePtr); + return stylePtr; +} + +/* + *---------------------------------------------------------------------- + * + * FreeStyle -- + * + * This function is called when a TextStyle structure is no longer + * needed. It decrements the reference count and frees up the space for + * the style structure if the reference count is 0. + * + * Results: + * None. + * + * Side effects: + * The storage and other resources associated with the style are freed up + * if no-one's still using it. + * + *---------------------------------------------------------------------- + */ + +static void +FreeStyle( + TkText *textPtr, /* Information about overall widget. */ + register TextStyle *stylePtr) + /* Information about style to free. */ +{ + stylePtr->refCount--; + if (stylePtr->refCount == 0) { + if (stylePtr->bgGC != None) { + Tk_FreeGC(textPtr->display, stylePtr->bgGC); + } + if (stylePtr->fgGC != None) { + Tk_FreeGC(textPtr->display, stylePtr->fgGC); + } + if (stylePtr->ulGC != None) { + Tk_FreeGC(textPtr->display, stylePtr->ulGC); + } + if (stylePtr->ovGC != None) { + Tk_FreeGC(textPtr->display, stylePtr->ovGC); + } + Tcl_DeleteHashEntry(stylePtr->hPtr); + ckfree(stylePtr); + } +} + +/* + *---------------------------------------------------------------------- + * + * LayoutDLine -- + * + * This function generates a single DLine structure for a display line + * whose leftmost character is given by indexPtr. + * + * Results: + * The return value is a pointer to a DLine structure describing the + * display line. All fields are filled in and correct except for y and + * nextPtr. + * + * Side effects: + * Storage is allocated for the new DLine. + * + * See the comments in 'GetYView' for some thoughts on what the side- + * effects of this call (or its callers) should be; the synchronisation + * of TkTextLine->pixelHeight with the sum of the results of this + * function operating on all display lines within each logical line. + * Ideally the code should be refactored to ensure the cached pixel + * height is never behind what is known when this function is called + * elsewhere. + * + * Unfortunately, this function is currently called from many different + * places, not just to layout a display line for actual display, but also + * simply to calculate some metric or other of one or more display lines + * (typically the height). It would be a good idea to do some profiling + * of typical text widget usage and the way in which this is called and + * see if some optimization could or should be done. + * + *---------------------------------------------------------------------- + */ + +static DLine * +LayoutDLine( + TkText *textPtr, /* Overall information about text widget. */ + const TkTextIndex *indexPtr)/* Beginning of display line. May not + * necessarily point to a character + * segment. */ +{ + register DLine *dlPtr; /* New display line. */ + TkTextSegment *segPtr; /* Current segment in text. */ + TkTextDispChunk *lastChunkPtr; + /* Last chunk allocated so far for line. */ + TkTextDispChunk *chunkPtr; /* Current chunk. */ + TkTextIndex curIndex; + TkTextDispChunk *breakChunkPtr; + /* Chunk containing best word break point, if + * any. */ + TkTextIndex breakIndex; /* Index of first character in + * breakChunkPtr. */ + int breakByteOffset; /* Byte offset of character within + * breakChunkPtr just to right of best break + * point. */ + int noCharsYet; /* Non-zero means that no characters have been + * placed on the line yet. */ + int paragraphStart; /* Non-zero means that we are on the first + * line of a paragraph (used to choose between + * lmargin1, lmargin2). */ + int justify; /* How to justify line: taken from style for + * the first character in line. */ + int jIndent; /* Additional indentation (beyond margins) due + * to justification. */ + int rMargin; /* Right margin width for line. */ + TkWrapMode wrapMode; /* Wrap mode to use for this line. */ + int x = 0, maxX = 0; /* Initializations needed only to stop + * compiler warnings. */ + int wholeLine; /* Non-zero means this display line runs to + * the end of the text line. */ + int tabIndex; /* Index of the current tab stop. */ + int gotTab; /* Non-zero means the current chunk contains a + * tab. */ + TkTextDispChunk *tabChunkPtr; + /* Pointer to the chunk containing the + * previous tab stop. */ + int maxBytes; /* Maximum number of bytes to include in this + * chunk. */ + TkTextTabArray *tabArrayPtr;/* Tab stops for line; taken from style for + * the first character on line. */ + int tabStyle; /* One of TABULAR or WORDPROCESSOR. */ + int tabSize; /* Number of pixels consumed by current tab + * stop. */ + TkTextDispChunk *lastCharChunkPtr; + /* Pointer to last chunk in display lines with + * numBytes > 0. Used to drop 0-sized chunks + * from the end of the line. */ + int byteOffset, ascent, descent, code, elide, elidesize; + StyleValues *sValuePtr; + TkTextElideInfo info; /* Keep track of elide state. */ + + /* + * Create and initialize a new DLine structure. + */ + + dlPtr = ckalloc(sizeof(DLine)); + dlPtr->index = *indexPtr; + dlPtr->byteCount = 0; + dlPtr->y = 0; + dlPtr->oldY = 0; /* Only set to avoid compiler warnings. */ + dlPtr->height = 0; + dlPtr->baseline = 0; + dlPtr->chunkPtr = NULL; + dlPtr->nextPtr = NULL; + dlPtr->flags = NEW_LAYOUT | OLD_Y_INVALID; + dlPtr->logicalLinesMerged = 0; + dlPtr->lMarginColor = NULL; + dlPtr->lMarginWidth = 0; + dlPtr->rMarginColor = NULL; + dlPtr->rMarginWidth = 0; + + /* + * This is not necessarily totally correct, where we have merged logical + * lines. Fixing this would require a quite significant overhaul, though, + * so currently we make do with this. + */ + + paragraphStart = (indexPtr->byteIndex == 0); + + /* + * Special case entirely elide line as there may be 1000s or more. + */ + + elide = TkTextIsElided(textPtr, indexPtr, &info); + if (elide && indexPtr->byteIndex == 0) { + maxBytes = 0; + for (segPtr = info.segPtr; segPtr != NULL; segPtr = segPtr->nextPtr) { + if (segPtr->size > 0) { + if (elide == 0) { + /* + * We toggled a tag and the elide state changed to + * visible, and we have something of non-zero size. + * Therefore we must bail out. + */ + + break; + } + maxBytes += segPtr->size; + + /* + * Reset tag elide priority, since we're on a new character. + */ + + } else if ((segPtr->typePtr == &tkTextToggleOffType) + || (segPtr->typePtr == &tkTextToggleOnType)) { + TkTextTag *tagPtr = segPtr->body.toggle.tagPtr; + + /* + * The elide state only changes if this tag is either the + * current highest priority tag (and is therefore being + * toggled off), or it's a new tag with higher priority. + */ + + if (tagPtr->elideString != NULL) { + info.tagCnts[tagPtr->priority]++; + if (info.tagCnts[tagPtr->priority] & 1) { + info.tagPtrs[tagPtr->priority] = tagPtr; + } + if (tagPtr->priority >= info.elidePriority) { + if (segPtr->typePtr == &tkTextToggleOffType) { + /* + * If it is being toggled off, and it has an elide + * string, it must actually be the current highest + * priority tag, so this check is redundant: + */ + + if (tagPtr->priority != info.elidePriority) { + Tcl_Panic("Bad tag priority being toggled off"); + } + + /* + * Find previous elide tag, if any (if not then + * elide will be zero, of course). + */ + + elide = 0; + while (--info.elidePriority > 0) { + if (info.tagCnts[info.elidePriority] & 1) { + elide = info.tagPtrs[info.elidePriority] + ->elide; + break; + } + } + } else { + elide = tagPtr->elide; + info.elidePriority = tagPtr->priority; + } + } + } + } + } + + if (elide) { + dlPtr->byteCount = maxBytes; + dlPtr->spaceAbove = dlPtr->spaceBelow = dlPtr->length = 0; + if (dlPtr->index.byteIndex == 0) { + /* + * Elided state goes from beginning to end of an entire + * logical line. This means we can update the line's pixel + * height, and bring its pixel calculation up to date. + */ + + TkBTreeLinePixelEpoch(textPtr, dlPtr->index.linePtr) + = textPtr->dInfoPtr->lineMetricUpdateEpoch; + + if (TkBTreeLinePixelCount(textPtr,dlPtr->index.linePtr) != 0) { + TkBTreeAdjustPixelHeight(textPtr, + dlPtr->index.linePtr, 0, 0); + } + } + TkTextFreeElideInfo(&info); + return dlPtr; + } + } + TkTextFreeElideInfo(&info); + + /* + * Each iteration of the loop below creates one TkTextDispChunk for the + * new display line. The line will always have at least one chunk (for the + * newline character at the end, if there's nothing else available). + */ + + curIndex = *indexPtr; + lastChunkPtr = NULL; + chunkPtr = NULL; + noCharsYet = 1; + elide = 0; + breakChunkPtr = NULL; + breakByteOffset = 0; + justify = TK_JUSTIFY_LEFT; + tabIndex = -1; + tabChunkPtr = NULL; + tabArrayPtr = NULL; + tabStyle = TK_TEXT_TABSTYLE_TABULAR; + rMargin = 0; + wrapMode = TEXT_WRAPMODE_CHAR; + tabSize = 0; + lastCharChunkPtr = NULL; + + /* + * Find the first segment to consider for the line. Can't call + * TkTextIndexToSeg for this because it won't return a segment with zero + * size (such as the insertion cursor's mark). + */ + + connectNextLogicalLine: + byteOffset = curIndex.byteIndex; + segPtr = curIndex.linePtr->segPtr; + while ((byteOffset > 0) && (byteOffset >= segPtr->size)) { + byteOffset -= segPtr->size; + segPtr = segPtr->nextPtr; + + if (segPtr == NULL) { + /* + * Two logical lines merged into one display line through eliding + * of a newline. + */ + + TkTextLine *linePtr = TkBTreeNextLine(NULL, curIndex.linePtr); + if (linePtr == NULL) { + break; + } + + dlPtr->logicalLinesMerged++; + curIndex.byteIndex = 0; + curIndex.linePtr = linePtr; + segPtr = curIndex.linePtr->segPtr; + } + } + + while (segPtr != NULL) { + /* + * Every logical line still gets at least one chunk due to + * expectations in the rest of the code, but we are able to skip + * elided portions of the line quickly. + * + * If current chunk is elided and last chunk was too, coalese. + * + * This also means that each logical line which is entirely elided + * still gets laid out into a DLine, but with zero height. This isn't + * particularly a problem, but it does seem somewhat unnecessary. We + * may wish to redesign the code to remove these zero height DLines in + * the future. + */ + + if (elide && (lastChunkPtr != NULL) + && (lastChunkPtr->displayProc == NULL /*ElideDisplayProc*/)) { + elidesize = segPtr->size - byteOffset; + if (elidesize > 0) { + curIndex.byteIndex += elidesize; + lastChunkPtr->numBytes += elidesize; + breakByteOffset = lastChunkPtr->breakIndex + = lastChunkPtr->numBytes; + + /* + * If have we have a tag toggle, there is a chance that + * invisibility state changed, so bail out. + */ + } else if ((segPtr->typePtr == &tkTextToggleOffType) + || (segPtr->typePtr == &tkTextToggleOnType)) { + if (segPtr->body.toggle.tagPtr->elideString != NULL) { + elide = (segPtr->typePtr == &tkTextToggleOffType) + ^ segPtr->body.toggle.tagPtr->elide; + } + } + + byteOffset = 0; + segPtr = segPtr->nextPtr; + + if (segPtr == NULL) { + /* + * Two logical lines merged into one display line through + * eliding of a newline. + */ + + TkTextLine *linePtr = TkBTreeNextLine(NULL, curIndex.linePtr); + + if (linePtr != NULL) { + dlPtr->logicalLinesMerged++; + curIndex.byteIndex = 0; + curIndex.linePtr = linePtr; + goto connectNextLogicalLine; + } + } + + /* + * Code no longer needed, now that we allow logical lines to merge + * into a single display line. + * + if (segPtr == NULL && chunkPtr != NULL) { + ckfree(chunkPtr); + chunkPtr = NULL; + } + */ + + continue; + } + + if (segPtr->typePtr->layoutProc == NULL) { + segPtr = segPtr->nextPtr; + byteOffset = 0; + continue; + } + if (chunkPtr == NULL) { + chunkPtr = ckalloc(sizeof(TkTextDispChunk)); + chunkPtr->nextPtr = NULL; + chunkPtr->clientData = NULL; + } + chunkPtr->stylePtr = GetStyle(textPtr, &curIndex); + elide = chunkPtr->stylePtr->sValuePtr->elide; + + /* + * Save style information such as justification and indentation, up + * until the first character is encountered, then retain that + * information for the rest of the line. + */ + + if (!elide && noCharsYet) { + tabArrayPtr = chunkPtr->stylePtr->sValuePtr->tabArrayPtr; + tabStyle = chunkPtr->stylePtr->sValuePtr->tabStyle; + justify = chunkPtr->stylePtr->sValuePtr->justify; + rMargin = chunkPtr->stylePtr->sValuePtr->rMargin; + wrapMode = chunkPtr->stylePtr->sValuePtr->wrapMode; + + /* + * See above - this test may not be entirely correct where we have + * partially elided lines (and therefore merged logical lines). + * In such a case a byteIndex of zero doesn't necessarily mean the + * beginning of a logical line. + */ + + if (paragraphStart) { + /* + * Beginning of logical line. + */ + + x = chunkPtr->stylePtr->sValuePtr->lMargin1; + } else { + /* + * Beginning of display line. + */ + + x = chunkPtr->stylePtr->sValuePtr->lMargin2; + } + dlPtr->lMarginWidth = x; + if (wrapMode == TEXT_WRAPMODE_NONE) { + maxX = -1; + } else { + maxX = textPtr->dInfoPtr->maxX - textPtr->dInfoPtr->x + - rMargin; + if (maxX < x) { + maxX = x; + } + } + } + + gotTab = 0; + maxBytes = segPtr->size - byteOffset; + if (segPtr->typePtr == &tkTextCharType) { + + /* + * See if there is a tab in the current chunk; if so, only layout + * characters up to (and including) the tab. + */ + + if (!elide && justify == TK_JUSTIFY_LEFT) { + char *p; + + for (p = segPtr->body.chars + byteOffset; *p != 0; p++) { + if (*p == '\t') { + maxBytes = (p + 1 - segPtr->body.chars) - byteOffset; + gotTab = 1; + break; + } + } + } + +#if TK_LAYOUT_WITH_BASE_CHUNKS + if (baseCharChunkPtr != NULL) { + int expectedX = + ((BaseCharInfo *) baseCharChunkPtr->clientData)->width + + baseCharChunkPtr->x; + + if ((expectedX != x) || !IsSameFGStyle( + baseCharChunkPtr->stylePtr, chunkPtr->stylePtr)) { + FinalizeBaseChunk(NULL); + } + } +#endif /* TK_LAYOUT_WITH_BASE_CHUNKS */ + } + chunkPtr->x = x; + if (elide /*&& maxBytes*/) { + /* + * Don't free style here, as other code expects to be able to do + * that. + */ + + /* breakByteOffset =*/ + chunkPtr->breakIndex = chunkPtr->numBytes = maxBytes; + chunkPtr->width = 0; + chunkPtr->minAscent = chunkPtr->minDescent + = chunkPtr->minHeight = 0; + + /* + * Would just like to point to canonical empty chunk. + */ + + chunkPtr->displayProc = NULL; + chunkPtr->undisplayProc = NULL; + chunkPtr->measureProc = ElideMeasureProc; + chunkPtr->bboxProc = ElideBboxProc; + + code = 1; + } else { + code = segPtr->typePtr->layoutProc(textPtr, &curIndex, segPtr, + byteOffset, maxX-tabSize, maxBytes, noCharsYet, wrapMode, + chunkPtr); + } + if (code <= 0) { + FreeStyle(textPtr, chunkPtr->stylePtr); + if (code < 0) { + /* + * This segment doesn't wish to display itself (e.g. most + * marks). + */ + + segPtr = segPtr->nextPtr; + byteOffset = 0; + continue; + } + + /* + * No characters from this segment fit in the window: this means + * we're at the end of the display line. + */ + + if (chunkPtr != NULL) { + ckfree(chunkPtr); + } + break; + } + + /* + * We currently say we have some characters (and therefore something + * from which to examine tag values for the first character of the + * line) even if those characters are actually elided. This behaviour + * is not well documented, and it might be more consistent to + * completely ignore such elided characters and their tags. To do so + * change this to: + * + * if (!elide && chunkPtr->numBytes > 0). + */ + + if (!elide && chunkPtr->numBytes > 0) { + noCharsYet = 0; + lastCharChunkPtr = chunkPtr; + } + if (lastChunkPtr == NULL) { + dlPtr->chunkPtr = chunkPtr; + } else { + lastChunkPtr->nextPtr = chunkPtr; + } + lastChunkPtr = chunkPtr; + x += chunkPtr->width; + if (chunkPtr->breakIndex > 0) { + breakByteOffset = chunkPtr->breakIndex; + breakIndex = curIndex; + breakChunkPtr = chunkPtr; + } + if (chunkPtr->numBytes != maxBytes) { + break; + } + + /* + * If we're at a new tab, adjust the layout for all the chunks + * pertaining to the previous tab. Also adjust the amount of space + * left in the line to account for space that will be eaten up by the + * tab. + */ + + if (gotTab) { + if (tabIndex >= 0) { + AdjustForTab(textPtr, tabArrayPtr, tabIndex, tabChunkPtr); + x = chunkPtr->x + chunkPtr->width; + } + tabChunkPtr = chunkPtr; + tabSize = SizeOfTab(textPtr, tabStyle, tabArrayPtr, &tabIndex, x, + maxX); + if ((maxX >= 0) && (tabSize >= maxX - x)) { + break; + } + } + curIndex.byteIndex += chunkPtr->numBytes; + byteOffset += chunkPtr->numBytes; + if (byteOffset >= segPtr->size) { + byteOffset = 0; + segPtr = segPtr->nextPtr; + if (elide && segPtr == NULL) { + /* + * An elided section started on this line, and carries on + * until the newline. Hence the newline is actually elided, + * and we want to merge the display of the next logical line + * with this one. + */ + + TkTextLine *linePtr = TkBTreeNextLine(NULL, curIndex.linePtr); + + if (linePtr != NULL) { + dlPtr->logicalLinesMerged++; + curIndex.byteIndex = 0; + curIndex.linePtr = linePtr; + chunkPtr = NULL; + goto connectNextLogicalLine; + } + } + } + + chunkPtr = NULL; + } +#if TK_LAYOUT_WITH_BASE_CHUNKS + FinalizeBaseChunk(NULL); +#endif /* TK_LAYOUT_WITH_BASE_CHUNKS */ + if (noCharsYet) { + dlPtr->spaceAbove = 0; + dlPtr->spaceBelow = 0; + dlPtr->length = 0; + + /* + * We used to Tcl_Panic here, saying that LayoutDLine couldn't place + * any characters on a line, but I believe a more appropriate response + * is to return a DLine with zero height. With elided lines, tag + * transitions and asynchronous line height calculations, it is hard + * to avoid this situation ever arising with the current code design. + */ + + return dlPtr; + } + wholeLine = (segPtr == NULL); + + /* + * We're at the end of the display line. Throw away everything after the + * most recent word break, if there is one; this may potentially require + * the last chunk to be layed out again. + */ + + if (breakChunkPtr == NULL) { + /* + * This code makes sure that we don't accidentally display chunks with + * no characters at the end of the line (such as the insertion + * cursor). These chunks belong on the next line. So, throw away + * everything after the last chunk that has characters in it. + */ + + breakChunkPtr = lastCharChunkPtr; + breakByteOffset = breakChunkPtr->numBytes; + } + if ((breakChunkPtr != NULL) && ((lastChunkPtr != breakChunkPtr) + || (breakByteOffset != lastChunkPtr->numBytes))) { + while (1) { + chunkPtr = breakChunkPtr->nextPtr; + if (chunkPtr == NULL) { + break; + } + FreeStyle(textPtr, chunkPtr->stylePtr); + breakChunkPtr->nextPtr = chunkPtr->nextPtr; + if (chunkPtr->undisplayProc != NULL) { + chunkPtr->undisplayProc(textPtr, chunkPtr); + } + ckfree(chunkPtr); + } + if (breakByteOffset != breakChunkPtr->numBytes) { + if (breakChunkPtr->undisplayProc != NULL) { + breakChunkPtr->undisplayProc(textPtr, breakChunkPtr); + } + segPtr = TkTextIndexToSeg(&breakIndex, &byteOffset); + segPtr->typePtr->layoutProc(textPtr, &breakIndex, segPtr, + byteOffset, maxX, breakByteOffset, 0, wrapMode, + breakChunkPtr); +#if TK_LAYOUT_WITH_BASE_CHUNKS + FinalizeBaseChunk(NULL); +#endif /* TK_LAYOUT_WITH_BASE_CHUNKS */ + } + lastChunkPtr = breakChunkPtr; + wholeLine = 0; + } + + /* + * Make tab adjustments for the last tab stop, if there is one. + */ + + if ((tabIndex >= 0) && (tabChunkPtr != NULL)) { + AdjustForTab(textPtr, tabArrayPtr, tabIndex, tabChunkPtr); + } + + /* + * Make one more pass over the line to recompute various things like its + * height, length, and total number of bytes. Also modify the x-locations + * of chunks to reflect justification. If we're not wrapping, I'm not sure + * what is the best way to handle right and center justification: should + * the total length, for purposes of justification, be (a) the window + * width, (b) the length of the longest line in the window, or (c) the + * length of the longest line in the text? (c) isn't available, (b) seems + * weird, since it can change with vertical scrolling, so (a) is what is + * implemented below. + */ + + if (wrapMode == TEXT_WRAPMODE_NONE) { + maxX = textPtr->dInfoPtr->maxX - textPtr->dInfoPtr->x - rMargin; + } + dlPtr->length = lastChunkPtr->x + lastChunkPtr->width; + if (justify == TK_JUSTIFY_LEFT) { + jIndent = 0; + } else if (justify == TK_JUSTIFY_RIGHT) { + jIndent = maxX - dlPtr->length; + } else { + jIndent = (maxX - dlPtr->length)/2; + } + ascent = descent = 0; + for (chunkPtr = dlPtr->chunkPtr; chunkPtr != NULL; + chunkPtr = chunkPtr->nextPtr) { + chunkPtr->x += jIndent; + dlPtr->byteCount += chunkPtr->numBytes; + if (chunkPtr->minAscent > ascent) { + ascent = chunkPtr->minAscent; + } + if (chunkPtr->minDescent > descent) { + descent = chunkPtr->minDescent; + } + if (chunkPtr->minHeight > dlPtr->height) { + dlPtr->height = chunkPtr->minHeight; + } + sValuePtr = chunkPtr->stylePtr->sValuePtr; + if ((sValuePtr->borderWidth > 0) + && (sValuePtr->relief != TK_RELIEF_FLAT)) { + dlPtr->flags |= HAS_3D_BORDER; + } + } + if (dlPtr->height < (ascent + descent)) { + dlPtr->height = ascent + descent; + dlPtr->baseline = ascent; + } else { + dlPtr->baseline = ascent + (dlPtr->height - ascent - descent)/2; + } + sValuePtr = dlPtr->chunkPtr->stylePtr->sValuePtr; + if (dlPtr->index.byteIndex == 0) { + dlPtr->spaceAbove = sValuePtr->spacing1; + } else { + dlPtr->spaceAbove = sValuePtr->spacing2 - sValuePtr->spacing2/2; + } + if (wholeLine) { + dlPtr->spaceBelow = sValuePtr->spacing3; + } else { + dlPtr->spaceBelow = sValuePtr->spacing2/2; + } + dlPtr->height += dlPtr->spaceAbove + dlPtr->spaceBelow; + dlPtr->baseline += dlPtr->spaceAbove; + dlPtr->lMarginColor = sValuePtr->lMarginColor; + dlPtr->rMarginColor = sValuePtr->rMarginColor; + if (wrapMode != TEXT_WRAPMODE_NONE) { + dlPtr->rMarginWidth = rMargin; + } + + /* + * Recompute line length: may have changed because of justification. + */ + + dlPtr->length = lastChunkPtr->x + lastChunkPtr->width; + + return dlPtr; +} + +/* + *---------------------------------------------------------------------- + * + * UpdateDisplayInfo -- + * + * This function is invoked to recompute some or all of the DLine + * structures for a text widget. At the time it is called the DLine + * structures still left in the widget are guaranteed to be correct + * except that (a) the y-coordinates aren't necessarily correct, (b) + * there may be missing structures (the DLine structures get removed as + * soon as they are potentially out-of-date), and (c) DLine structures + * that don't start at the beginning of a line may be incorrect if + * previous information in the same line changed size in a way that moved + * a line boundary (DLines for any info that changed will have been + * deleted, but not DLines for unchanged info in the same text line). + * + * Results: + * None. + * + * Side effects: + * Upon return, the DLine information for textPtr correctly reflects the + * positions where characters will be displayed. However, this function + * doesn't actually bring the display up-to-date. + * + *---------------------------------------------------------------------- + */ + +static void +UpdateDisplayInfo( + TkText *textPtr) /* Text widget to update. */ +{ + register TextDInfo *dInfoPtr = textPtr->dInfoPtr; + register DLine *dlPtr, *prevPtr; + TkTextIndex index; + TkTextLine *lastLinePtr; + int y, maxY, xPixelOffset, maxOffset, lineHeight; + + if (!(dInfoPtr->flags & DINFO_OUT_OF_DATE)) { + return; + } + dInfoPtr->flags &= ~DINFO_OUT_OF_DATE; + + /* + * Delete any DLines that are now above the top of the window. + */ + + index = textPtr->topIndex; + dlPtr = FindDLine(textPtr, dInfoPtr->dLinePtr, &index); + if ((dlPtr != NULL) && (dlPtr != dInfoPtr->dLinePtr)) { + FreeDLines(textPtr, dInfoPtr->dLinePtr, dlPtr, DLINE_UNLINK); + } + if (index.byteIndex == 0) { + lineHeight = 0; + } else { + lineHeight = -1; + } + + /* + * Scan through the contents of the window from top to bottom, recomputing + * information for lines that are missing. + */ + + lastLinePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr, + TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr)); + dlPtr = dInfoPtr->dLinePtr; + prevPtr = NULL; + y = dInfoPtr->y - dInfoPtr->newTopPixelOffset; + maxY = dInfoPtr->maxY; + while (1) { + register DLine *newPtr; + + if (index.linePtr == lastLinePtr) { + break; + } + + /* + * There are three possibilities right now: + * (a) the next DLine (dlPtr) corresponds exactly to the next + * information we want to display: just use it as-is. + * (b) the next DLine corresponds to a different line, or to a segment + * that will be coming later in the same line: leave this DLine + * alone in the hopes that we'll be able to use it later, then + * create a new DLine in front of it. + * (c) the next DLine corresponds to a segment in the line we want, + * but it's a segment that has already been processed or will + * never be processed. Delete the DLine and try again. + * + * One other twist on all this. It's possible for 3D borders to + * interact between lines (see DisplayLineBackground) so if a line is + * relayed out and has styles with 3D borders, its neighbors have to + * be redrawn if they have 3D borders too, since the interactions + * could have changed (the neighbors don't have to be relayed out, + * just redrawn). + */ + + if ((dlPtr == NULL) || (dlPtr->index.linePtr != index.linePtr)) { + /* + * Case (b) -- must make new DLine. + */ + + makeNewDLine: + if (tkTextDebug) { + char string[TK_POS_CHARS]; + + /* + * Debugging is enabled, so keep a log of all the lines that + * were re-layed out. The test suite uses this information. + */ + + TkTextPrintIndex(textPtr, &index, string); + LOG("tk_textRelayout", string); + } + newPtr = LayoutDLine(textPtr, &index); + if (prevPtr == NULL) { + dInfoPtr->dLinePtr = newPtr; + } else { + prevPtr->nextPtr = newPtr; + if (prevPtr->flags & HAS_3D_BORDER) { + prevPtr->flags |= OLD_Y_INVALID; + } + } + newPtr->nextPtr = dlPtr; + dlPtr = newPtr; + } else { + /* + * DlPtr refers to the line we want. Next check the index within + * the line. + */ + + if (index.byteIndex == dlPtr->index.byteIndex) { + /* + * Case (a) - can use existing display line as-is. + */ + + if ((dlPtr->flags & HAS_3D_BORDER) && (prevPtr != NULL) + && (prevPtr->flags & (NEW_LAYOUT))) { + dlPtr->flags |= OLD_Y_INVALID; + } + goto lineOK; + } + if (index.byteIndex < dlPtr->index.byteIndex) { + goto makeNewDLine; + } + + /* + * Case (c) - dlPtr is useless. Discard it and start again with + * the next display line. + */ + + newPtr = dlPtr->nextPtr; + FreeDLines(textPtr, dlPtr, newPtr, DLINE_FREE); + dlPtr = newPtr; + if (prevPtr != NULL) { + prevPtr->nextPtr = newPtr; + } else { + dInfoPtr->dLinePtr = newPtr; + } + continue; + } + + /* + * Advance to the start of the next line. + */ + + lineOK: + dlPtr->y = y; + y += dlPtr->height; + if (lineHeight != -1) { + lineHeight += dlPtr->height; + } + TkTextIndexForwBytes(textPtr, &index, dlPtr->byteCount, &index); + prevPtr = dlPtr; + dlPtr = dlPtr->nextPtr; + + /* + * If we switched text lines, delete any DLines left for the old text + * line. + */ + + if (index.linePtr != prevPtr->index.linePtr) { + register DLine *nextPtr; + + nextPtr = dlPtr; + while ((nextPtr != NULL) + && (nextPtr->index.linePtr == prevPtr->index.linePtr)) { + nextPtr = nextPtr->nextPtr; + } + if (nextPtr != dlPtr) { + FreeDLines(textPtr, dlPtr, nextPtr, DLINE_FREE); + prevPtr->nextPtr = nextPtr; + dlPtr = nextPtr; + } + + if ((lineHeight != -1) && (TkBTreeLinePixelCount(textPtr, + prevPtr->index.linePtr) != lineHeight)) { + /* + * The logical line height we just calculated is actually + * different to the currently cached height of the text line. + * That is fine (the text line heights are only calculated + * asynchronously), but we must update the cached height so + * that any counts made with DLine pointers are the same as + * counts made through the BTree. This helps to ensure that + * the scrollbar size corresponds accurately to that displayed + * contents, even as the window is re-sized. + */ + + TkBTreeAdjustPixelHeight(textPtr, prevPtr->index.linePtr, + lineHeight, 0); + + /* + * I believe we can be 100% sure that we started at the + * beginning of the logical line, so we can also adjust the + * 'pixelCalculationEpoch' to mark it as being up to date. + * There is a slight concern that we might not have got this + * right for the first line in the re-display. + */ + + TkBTreeLinePixelEpoch(textPtr, prevPtr->index.linePtr) = + dInfoPtr->lineMetricUpdateEpoch; + } + lineHeight = 0; + } + + /* + * It's important to have the following check here rather than in the + * while statement for the loop, so that there's always at least one + * DLine generated, regardless of how small the window is. This keeps + * a lot of other code from breaking. + */ + + if (y >= maxY) { + break; + } + } + + /* + * Delete any DLine structures that don't fit on the screen. + */ + + FreeDLines(textPtr, dlPtr, NULL, DLINE_UNLINK); + + /* + * If there is extra space at the bottom of the window (because we've hit + * the end of the text), then bring in more lines at the top of the + * window, if there are any, to fill in the view. + * + * Since the top line may only be partially visible, we try first to + * simply show more pixels from that line (newTopPixelOffset). If that + * isn't enough, we have to layout more lines. + */ + + if (y < maxY) { + /* + * This counts how many vertical pixels we have left to fill by + * pulling in more display pixels either from the first currently + * displayed, or the lines above it. + */ + + int spaceLeft = maxY - y; + + if (spaceLeft <= dInfoPtr->newTopPixelOffset) { + /* + * We can fill up all the needed space just by showing more of the + * current top line. + */ + + dInfoPtr->newTopPixelOffset -= spaceLeft; + y += spaceLeft; + spaceLeft = 0; + } else { + int lineNum, bytesToCount; + DLine *lowestPtr; + + /* + * Add in all of the current top line, which won't be enough to + * bring y up to maxY (if it was we would be in the 'if' block + * above). + */ + + y += dInfoPtr->newTopPixelOffset; + dInfoPtr->newTopPixelOffset = 0; + + /* + * Layout an entire text line (potentially > 1 display line), then + * link in as many display lines as fit without moving the bottom + * line out of the window. Repeat this until all the extra space + * has been used up or we've reached the beginning of the text. + */ + + spaceLeft = maxY - y; + if (dInfoPtr->dLinePtr == NULL) { + /* + * No lines have been laid out. This must be an empty peer + * widget. + */ + + lineNum = TkBTreeNumLines(textPtr->sharedTextPtr->tree, + textPtr) - 1; + bytesToCount = INT_MAX; + } else { + lineNum = TkBTreeLinesTo(textPtr, + dInfoPtr->dLinePtr->index.linePtr); + bytesToCount = dInfoPtr->dLinePtr->index.byteIndex; + if (bytesToCount == 0) { + bytesToCount = INT_MAX; + lineNum--; + } + } + for ( ; (lineNum >= 0) && (spaceLeft > 0); lineNum--) { + int pixelHeight = 0; + + index.linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, + textPtr, lineNum); + index.byteIndex = 0; + lowestPtr = NULL; + + do { + dlPtr = LayoutDLine(textPtr, &index); + pixelHeight += dlPtr->height; + dlPtr->nextPtr = lowestPtr; + lowestPtr = dlPtr; + if (dlPtr->length == 0 && dlPtr->height == 0) { + bytesToCount--; + break; + } /* elide */ + TkTextIndexForwBytes(textPtr, &index, dlPtr->byteCount, + &index); + bytesToCount -= dlPtr->byteCount; + } while ((bytesToCount > 0) + && (index.linePtr == lowestPtr->index.linePtr)); + + /* + * We may not have examined the entire line (depending on the + * value of 'bytesToCount', so we only want to set this if it + * is genuinely bigger). + */ + + if (pixelHeight > TkBTreeLinePixelCount(textPtr, + lowestPtr->index.linePtr)) { + TkBTreeAdjustPixelHeight(textPtr, + lowestPtr->index.linePtr, pixelHeight, 0); + if (index.linePtr != lowestPtr->index.linePtr) { + /* + * We examined the entire line, so can update the + * epoch. + */ + + TkBTreeLinePixelEpoch(textPtr, + lowestPtr->index.linePtr) = + dInfoPtr->lineMetricUpdateEpoch; + } + } + + /* + * Scan through the display lines from the bottom one up to + * the top one. + */ + + while (lowestPtr != NULL) { + dlPtr = lowestPtr; + spaceLeft -= dlPtr->height; + lowestPtr = dlPtr->nextPtr; + dlPtr->nextPtr = dInfoPtr->dLinePtr; + dInfoPtr->dLinePtr = dlPtr; + if (tkTextDebug) { + char string[TK_POS_CHARS]; + + TkTextPrintIndex(textPtr, &dlPtr->index, string); + LOG("tk_textRelayout", string); + } + if (spaceLeft <= 0) { + break; + } + } + FreeDLines(textPtr, lowestPtr, NULL, DLINE_FREE); + bytesToCount = INT_MAX; + } + + /* + * We've either filled in the space we wanted to or we've run out + * of display lines at the top of the text. Note that we already + * set dInfoPtr->newTopPixelOffset to zero above. + */ + + if (spaceLeft < 0) { + /* + * We've laid out a few too many vertical pixels at or above + * the first line. Therefore we only want to show part of the + * first displayed line, so that the last displayed line just + * fits in the window. + */ + + dInfoPtr->newTopPixelOffset = -spaceLeft; + if (dInfoPtr->newTopPixelOffset>=dInfoPtr->dLinePtr->height) { + /* + * Somehow the entire first line we laid out is shorter + * than the new offset. This should not occur and would + * indicate a bad problem in the logic above. + */ + + Tcl_Panic("Error in pixel height consistency while filling in spacesLeft"); + } + } + } + + /* + * Now we're all done except that the y-coordinates in all the DLines + * are wrong and the top index for the text is wrong. Update them. + */ + + if (dInfoPtr->dLinePtr != NULL) { + textPtr->topIndex = dInfoPtr->dLinePtr->index; + y = dInfoPtr->y - dInfoPtr->newTopPixelOffset; + for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; + dlPtr = dlPtr->nextPtr) { + if (y > dInfoPtr->maxY) { + Tcl_Panic("Added too many new lines in UpdateDisplayInfo"); + } + dlPtr->y = y; + y += dlPtr->height; + } + } + } + + /* + * If the old top or bottom line has scrolled elsewhere on the screen, we + * may not be able to re-use its old contents by copying bits (e.g., a + * beveled edge that was drawn when it was at the top or bottom won't be + * drawn when the line is in the middle and its neighbor has a matching + * background). Similarly, if the new top or bottom line came from + * somewhere else on the screen, we may not be able to copy the old bits. + */ + + dlPtr = dInfoPtr->dLinePtr; + if (dlPtr != NULL) { + if ((dlPtr->flags & HAS_3D_BORDER) && !(dlPtr->flags & TOP_LINE)) { + dlPtr->flags |= OLD_Y_INVALID; + } + while (1) { + if ((dlPtr->flags & TOP_LINE) && (dlPtr != dInfoPtr->dLinePtr) + && (dlPtr->flags & HAS_3D_BORDER)) { + dlPtr->flags |= OLD_Y_INVALID; + } + + /* + * If the old top-line was not completely showing (i.e. the + * pixelOffset is non-zero) and is no longer the top-line, then we + * must re-draw it. + */ + + if ((dlPtr->flags & TOP_LINE) && + dInfoPtr->topPixelOffset!=0 && dlPtr!=dInfoPtr->dLinePtr) { + dlPtr->flags |= OLD_Y_INVALID; + } + if ((dlPtr->flags & BOTTOM_LINE) && (dlPtr->nextPtr != NULL) + && (dlPtr->flags & HAS_3D_BORDER)) { + dlPtr->flags |= OLD_Y_INVALID; + } + if (dlPtr->nextPtr == NULL) { + if ((dlPtr->flags & HAS_3D_BORDER) + && !(dlPtr->flags & BOTTOM_LINE)) { + dlPtr->flags |= OLD_Y_INVALID; + } + dlPtr->flags &= ~TOP_LINE; + dlPtr->flags |= BOTTOM_LINE; + break; + } + dlPtr->flags &= ~(TOP_LINE|BOTTOM_LINE); + dlPtr = dlPtr->nextPtr; + } + dInfoPtr->dLinePtr->flags |= TOP_LINE; + dInfoPtr->topPixelOffset = dInfoPtr->newTopPixelOffset; + } + + /* + * Arrange for scrollbars to be updated. + */ + + textPtr->flags |= UPDATE_SCROLLBARS; + + /* + * Deal with horizontal scrolling: + * 1. If there's empty space to the right of the longest line, shift the + * screen to the right to fill in the empty space. + * 2. If the desired horizontal scroll position has changed, force a full + * redisplay of all the lines in the widget. + * 3. If the wrap mode isn't "none" then re-scroll to the base position. + */ + + dInfoPtr->maxLength = 0; + for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; + dlPtr = dlPtr->nextPtr) { + if (dlPtr->length > dInfoPtr->maxLength) { + dInfoPtr->maxLength = dlPtr->length; + } + } + maxOffset = dInfoPtr->maxLength - (dInfoPtr->maxX - dInfoPtr->x); + + xPixelOffset = dInfoPtr->newXPixelOffset; + if (xPixelOffset > maxOffset) { + xPixelOffset = maxOffset; + } + if (xPixelOffset < 0) { + xPixelOffset = 0; + } + + /* + * Here's a problem: see the tests textDisp-29.2.1-4 + * + * If the widget is being created, but has not yet been configured it will + * have a maxY of 1 above, and we won't have examined all the lines + * (just the first line, in fact), and so maxOffset will not be a true + * reflection of the widget's lines. Therefore we must not overwrite the + * original newXPixelOffset in this case. + */ + + if (!(((Tk_FakeWin *) (textPtr->tkwin))->flags & TK_NEED_CONFIG_NOTIFY)) { + dInfoPtr->newXPixelOffset = xPixelOffset; + } + + if (xPixelOffset != dInfoPtr->curXPixelOffset) { + dInfoPtr->curXPixelOffset = xPixelOffset; + for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; + dlPtr = dlPtr->nextPtr) { + dlPtr->flags |= OLD_Y_INVALID; + } + } +} + +/* + *---------------------------------------------------------------------- + * + * FreeDLines -- + * + * This function is called to free up all of the resources associated + * with one or more DLine structures. + * + * Results: + * None. + * + * Side effects: + * Memory gets freed and various other resources are released. + * + *---------------------------------------------------------------------- + */ + +static void +FreeDLines( + TkText *textPtr, /* Information about overall text widget. */ + register DLine *firstPtr, /* Pointer to first DLine to free up. */ + DLine *lastPtr, /* Pointer to DLine just after last one to + * free (NULL means everything starting with + * firstPtr). */ + int action) /* DLINE_UNLINK means DLines are currently + * linked into the list rooted at + * textPtr->dInfoPtr->dLinePtr and they have + * to be unlinked. DLINE_FREE means just free + * without unlinking. DLINE_FREE_TEMP means + * the DLine given is just a temporary one and + * we shouldn't invalidate anything for the + * overall widget. */ +{ + register TkTextDispChunk *chunkPtr, *nextChunkPtr; + register DLine *nextDLinePtr; + + if (action == DLINE_FREE_TEMP) { + lineHeightsRecalculated++; + if (tkTextDebug) { + char string[TK_POS_CHARS]; + + /* + * Debugging is enabled, so keep a log of all the lines whose + * height was recalculated. The test suite uses this information. + */ + + TkTextPrintIndex(textPtr, &firstPtr->index, string); + LOG("tk_textHeightCalc", string); + } + } else if (action == DLINE_UNLINK) { + if (textPtr->dInfoPtr->dLinePtr == firstPtr) { + textPtr->dInfoPtr->dLinePtr = lastPtr; + } else { + register DLine *prevPtr; + + for (prevPtr = textPtr->dInfoPtr->dLinePtr; + prevPtr->nextPtr != firstPtr; prevPtr = prevPtr->nextPtr) { + /* Empty loop body. */ + } + prevPtr->nextPtr = lastPtr; + } + } + while (firstPtr != lastPtr) { + nextDLinePtr = firstPtr->nextPtr; + for (chunkPtr = firstPtr->chunkPtr; chunkPtr != NULL; + chunkPtr = nextChunkPtr) { + if (chunkPtr->undisplayProc != NULL) { + chunkPtr->undisplayProc(textPtr, chunkPtr); + } + FreeStyle(textPtr, chunkPtr->stylePtr); + nextChunkPtr = chunkPtr->nextPtr; + ckfree(chunkPtr); + } + ckfree(firstPtr); + firstPtr = nextDLinePtr; + } + if (action != DLINE_FREE_TEMP) { + textPtr->dInfoPtr->dLinesInvalidated = 1; + } +} + +/* + *---------------------------------------------------------------------- + * + * DisplayDLine -- + * + * This function is invoked to draw a single line on the screen. + * + * Results: + * None. + * + * Side effects: + * The line given by dlPtr is drawn at its correct position in textPtr's + * window. Note that this is one *display* line, not one *text* line. + * + *---------------------------------------------------------------------- + */ + +static void +DisplayDLine( + TkText *textPtr, /* Text widget in which to draw line. */ + register DLine *dlPtr, /* Information about line to draw. */ + DLine *prevPtr, /* Line just before one to draw, or NULL if + * dlPtr is the top line. */ + Pixmap pixmap) /* Pixmap to use for double-buffering. Caller + * must make sure it's large enough to hold + * line. */ +{ + register TkTextDispChunk *chunkPtr; + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + Display *display; + int height, y_off; +#ifndef TK_NO_DOUBLE_BUFFERING + const int y = 0; +#else + const int y = dlPtr->y; +#endif /* TK_NO_DOUBLE_BUFFERING */ + + if (dlPtr->chunkPtr == NULL) return; + + display = Tk_Display(textPtr->tkwin); + + height = dlPtr->height; + if ((height + dlPtr->y) > dInfoPtr->maxY) { + height = dInfoPtr->maxY - dlPtr->y; + } + if (dlPtr->y < dInfoPtr->y) { + y_off = dInfoPtr->y - dlPtr->y; + height -= y_off; + } else { + y_off = 0; + } + +#ifdef TK_NO_DOUBLE_BUFFERING + TkpClipDrawableToRect(display, pixmap, dInfoPtr->x, y + y_off, + dInfoPtr->maxX - dInfoPtr->x, height); +#endif /* TK_NO_DOUBLE_BUFFERING */ + + /* + * First, clear the area of the line to the background color for the text + * widget. + */ + + Tk_Fill3DRectangle(textPtr->tkwin, pixmap, textPtr->border, 0, y, + Tk_Width(textPtr->tkwin), dlPtr->height, 0, TK_RELIEF_FLAT); + + /* + * Second, draw background information for the whole line. + */ + + DisplayLineBackground(textPtr, dlPtr, prevPtr, pixmap); + + /* + * Third, draw the background color of the left and right margins. + */ + if (dlPtr->lMarginColor != NULL) { + Tk_Fill3DRectangle(textPtr->tkwin, pixmap, dlPtr->lMarginColor, 0, y, + dlPtr->lMarginWidth + dInfoPtr->x - dInfoPtr->curXPixelOffset, + dlPtr->height, 0, TK_RELIEF_FLAT); + } + if (dlPtr->rMarginColor != NULL) { + Tk_Fill3DRectangle(textPtr->tkwin, pixmap, dlPtr->rMarginColor, + dInfoPtr->maxX - dlPtr->rMarginWidth + dInfoPtr->curXPixelOffset, + y, dlPtr->rMarginWidth, dlPtr->height, 0, TK_RELIEF_FLAT); + } + + /* + * Make another pass through all of the chunks to redraw the insertion + * cursor, if it is visible on this line. Must do it here rather than in + * the foreground pass below because otherwise a wide insertion cursor + * will obscure the character to its left. + */ + + if (textPtr->state == TK_TEXT_STATE_NORMAL) { + for (chunkPtr = dlPtr->chunkPtr; (chunkPtr != NULL); + chunkPtr = chunkPtr->nextPtr) { + if (chunkPtr->displayProc == TkTextInsertDisplayProc) { + int x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curXPixelOffset; + + chunkPtr->displayProc(textPtr, chunkPtr, x, + y + dlPtr->spaceAbove, + dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow, + dlPtr->baseline - dlPtr->spaceAbove, display, pixmap, + dlPtr->y + dlPtr->spaceAbove); + } + } + } + + /* + * Make yet another pass through all of the chunks to redraw all of + * foreground information. Note: we have to call the displayProc even for + * chunks that are off-screen. This is needed, for example, so that + * embedded windows can be unmapped in this case. + */ + + for (chunkPtr = dlPtr->chunkPtr; (chunkPtr != NULL); + chunkPtr = chunkPtr->nextPtr) { + if (chunkPtr->displayProc == TkTextInsertDisplayProc) { + /* + * Already displayed the insertion cursor above. Don't do it again + * here. + */ + + continue; + } + + /* + * Don't call if elide. This tax OK since not very many visible DLines + * in an area, but potentially many elide ones. + */ + + if (chunkPtr->displayProc != NULL) { + int x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curXPixelOffset; + + if ((x + chunkPtr->width <= 0) || (x >= dInfoPtr->maxX)) { + /* + * Note: we have to call the displayProc even for chunks that + * are off-screen. This is needed, for example, so that + * embedded windows can be unmapped in this case. Display the + * chunk at a coordinate that can be clearly identified by the + * displayProc as being off-screen to the left (the + * displayProc may not be able to tell if something is off to + * the right). + */ + + x = -chunkPtr->width; + } + chunkPtr->displayProc(textPtr, chunkPtr, x, + y + dlPtr->spaceAbove, dlPtr->height - dlPtr->spaceAbove - + dlPtr->spaceBelow, dlPtr->baseline - dlPtr->spaceAbove, + display, pixmap, dlPtr->y + dlPtr->spaceAbove); + } + + if (dInfoPtr->dLinesInvalidated) { + return; + } + } + +#ifndef TK_NO_DOUBLE_BUFFERING + /* + * Copy the pixmap onto the screen. If this is the first or last line on + * the screen then copy a piece of the line, so that it doesn't overflow + * into the border area. Another special trick: copy the padding area to + * the left of the line; this is because the insertion cursor sometimes + * overflows onto that area and we want to get as much of the cursor as + * possible. + */ + + XCopyArea(display, pixmap, Tk_WindowId(textPtr->tkwin), dInfoPtr->copyGC, + dInfoPtr->x, y + y_off, (unsigned) (dInfoPtr->maxX - dInfoPtr->x), + (unsigned) height, dInfoPtr->x, dlPtr->y + y_off); +#else + TkpClipDrawableToRect(display, pixmap, 0, 0, -1, -1); +#endif /* TK_NO_DOUBLE_BUFFERING */ + linesRedrawn++; +} + +/* + *-------------------------------------------------------------- + * + * DisplayLineBackground -- + * + * This function is called to fill in the background for a display line. + * It draws 3D borders cleverly so that adjacent chunks with the same + * style (whether on the same line or different lines) have a single 3D + * border around the whole region. + * + * Results: + * There is no return value. Pixmap is filled in with background + * information for dlPtr. + * + * Side effects: + * None. + * + *-------------------------------------------------------------- + */ + +static void +DisplayLineBackground( + TkText *textPtr, /* Text widget containing line. */ + register DLine *dlPtr, /* Information about line to draw. */ + DLine *prevPtr, /* Line just above dlPtr, or NULL if dlPtr is + * the top-most line in the window. */ + Pixmap pixmap) /* Pixmap to use for double-buffering. Caller + * must make sure it's large enough to hold + * line. Caller must also have filled it with + * the background color for the widget. */ +{ + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + TkTextDispChunk *chunkPtr; /* Pointer to chunk in the current line. */ + TkTextDispChunk *chunkPtr2; /* Pointer to chunk in the line above or below + * the current one. NULL if we're to the left + * of or to the right of the chunks in the + * line. */ + TkTextDispChunk *nextPtr2; /* Next chunk after chunkPtr2 (it's not the + * same as chunkPtr2->nextPtr in the case + * where chunkPtr2 is NULL because the line is + * indented). */ + int leftX; /* The left edge of the region we're currently + * working on. */ + int leftXIn; /* 1 means beveled edge at leftX slopes right + * as it goes down, 0 means it slopes left as + * it goes down. */ + int rightX; /* Right edge of chunkPtr. */ + int rightX2; /* Right edge of chunkPtr2. */ + int matchLeft; /* Does the style of this line match that of + * its neighbor just to the left of the + * current x coordinate? */ + int matchRight; /* Does line's style match its neighbor just + * to the right of the current x-coord? */ + int minX, maxX, xOffset, bw; + StyleValues *sValuePtr; + Display *display; +#ifndef TK_NO_DOUBLE_BUFFERING + const int y = 0; +#else + const int y = dlPtr->y; +#endif /* TK_NO_DOUBLE_BUFFERING */ + + /* + * Pass 1: scan through dlPtr from left to right. For each range of chunks + * with the same style, draw the main background for the style plus the + * vertical parts of the 3D borders (the left and right edges). + */ + + display = Tk_Display(textPtr->tkwin); + minX = dInfoPtr->curXPixelOffset; + xOffset = dInfoPtr->x - minX; + maxX = minX + dInfoPtr->maxX - dInfoPtr->x; + chunkPtr = dlPtr->chunkPtr; + + /* + * Note A: in the following statement, and a few others later in this file + * marked with "See Note A above", the right side of the assignment was + * replaced with 0 on 6/18/97. This has the effect of highlighting the + * empty space to the left of a line whenever the leftmost character of + * the line is highlighted. This way, multi-line highlights always line up + * along their left edges. However, this may look funny in the case where + * a single word is highlighted. To undo the change, replace "leftX = 0" + * with "leftX = chunkPtr->x" and "rightX2 = 0" with "rightX2 = + * nextPtr2->x" here and at all the marked points below. This restores the + * old behavior where empty space to the left of a line is not + * highlighted, leaving a ragged left edge for multi-line highlights. + */ + + leftX = 0; + for (; leftX < maxX; chunkPtr = chunkPtr->nextPtr) { + if ((chunkPtr->nextPtr != NULL) + && SAME_BACKGROUND(chunkPtr->nextPtr->stylePtr, + chunkPtr->stylePtr)) { + continue; + } + sValuePtr = chunkPtr->stylePtr->sValuePtr; + rightX = chunkPtr->x + chunkPtr->width; + if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) { + rightX = maxX; + } + if (chunkPtr->stylePtr->bgGC != None) { + /* + * Not visible - bail out now. + */ + + if (rightX + xOffset <= 0) { + leftX = rightX; + continue; + } + + /* + * Trim the start position for drawing to be no further away than + * -borderWidth. The reason is that on many X servers drawing from + * -32768 (or less) to +something simply does not display + * correctly. [Patch #541999] + */ + + if ((leftX + xOffset) < -(sValuePtr->borderWidth)) { + leftX = -sValuePtr->borderWidth - xOffset; + } + if ((rightX - leftX) > 32767) { + rightX = leftX + 32767; + } + + /* + * Prevent the borders from leaking on adjacent characters, + * which would happen for too large border width. + */ + + bw = sValuePtr->borderWidth; + if (leftX + sValuePtr->borderWidth > rightX) { + bw = rightX - leftX; + } + + XFillRectangle(display, pixmap, chunkPtr->stylePtr->bgGC, + leftX + xOffset, y, (unsigned int) (rightX - leftX), + (unsigned int) dlPtr->height); + if (sValuePtr->relief != TK_RELIEF_FLAT) { + Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border, + leftX + xOffset, y, bw, dlPtr->height, 1, + sValuePtr->relief); + Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border, + rightX - bw + xOffset, y, bw, dlPtr->height, 0, + sValuePtr->relief); + } + } + leftX = rightX; + } + + /* + * Pass 2: draw the horizontal bevels along the top of the line. To do + * this, scan through dlPtr from left to right while simultaneously + * scanning through the line just above dlPtr. ChunkPtr2 and nextPtr2 + * refer to two adjacent chunks in the line above. + */ + + chunkPtr = dlPtr->chunkPtr; + leftX = 0; /* See Note A above. */ + leftXIn = 1; + rightX = chunkPtr->x + chunkPtr->width; + if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) { + rightX = maxX; + } + chunkPtr2 = NULL; + if (prevPtr != NULL && prevPtr->chunkPtr != NULL) { + /* + * Find the chunk in the previous line that covers leftX. + */ + + nextPtr2 = prevPtr->chunkPtr; + rightX2 = 0; /* See Note A above. */ + while (rightX2 <= leftX) { + chunkPtr2 = nextPtr2; + if (chunkPtr2 == NULL) { + break; + } + nextPtr2 = chunkPtr2->nextPtr; + rightX2 = chunkPtr2->x + chunkPtr2->width; + if (nextPtr2 == NULL) { + rightX2 = INT_MAX; + } + } + } else { + nextPtr2 = NULL; + rightX2 = INT_MAX; + } + + while (leftX < maxX) { + matchLeft = (chunkPtr2 != NULL) + && SAME_BACKGROUND(chunkPtr2->stylePtr, chunkPtr->stylePtr); + sValuePtr = chunkPtr->stylePtr->sValuePtr; + if (rightX <= rightX2) { + /* + * The chunk in our line is about to end. If its style changes + * then draw the bevel for the current style. + */ + + if ((chunkPtr->nextPtr == NULL) + || !SAME_BACKGROUND(chunkPtr->stylePtr, + chunkPtr->nextPtr->stylePtr)) { + if (!matchLeft && (sValuePtr->relief != TK_RELIEF_FLAT)) { + Tk_3DHorizontalBevel(textPtr->tkwin, pixmap, + sValuePtr->border, leftX + xOffset, y, + rightX - leftX, sValuePtr->borderWidth, leftXIn, + 1, 1, sValuePtr->relief); + } + leftX = rightX; + leftXIn = 1; + + /* + * If the chunk in the line above is also ending at the same + * point then advance to the next chunk in that line. + */ + + if ((rightX == rightX2) && (chunkPtr2 != NULL)) { + goto nextChunk2; + } + } + chunkPtr = chunkPtr->nextPtr; + if (chunkPtr == NULL) { + break; + } + rightX = chunkPtr->x + chunkPtr->width; + if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) { + rightX = maxX; + } + continue; + } + + /* + * The chunk in the line above is ending at an x-position where there + * is no change in the style of the current line. If the style above + * matches the current line on one side of the change but not on the + * other, we have to draw an L-shaped piece of bevel. + */ + + matchRight = (nextPtr2 != NULL) + && SAME_BACKGROUND(nextPtr2->stylePtr, chunkPtr->stylePtr); + if (matchLeft && !matchRight) { + bw = sValuePtr->borderWidth; + if (rightX2 - sValuePtr->borderWidth < leftX) { + bw = rightX2 - leftX; + } + if (sValuePtr->relief != TK_RELIEF_FLAT) { + Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border, + rightX2 - bw + xOffset, y, bw, + sValuePtr->borderWidth, 0, sValuePtr->relief); + } + leftX = rightX2 - bw; + leftXIn = 0; + } else if (!matchLeft && matchRight + && (sValuePtr->relief != TK_RELIEF_FLAT)) { + bw = sValuePtr->borderWidth; + if (rightX2 + sValuePtr->borderWidth > rightX) { + bw = rightX - rightX2; + } + Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border, + rightX2 + xOffset, y, bw, sValuePtr->borderWidth, + 1, sValuePtr->relief); + Tk_3DHorizontalBevel(textPtr->tkwin, pixmap, sValuePtr->border, + leftX + xOffset, y, rightX2 + bw - leftX, + sValuePtr->borderWidth, leftXIn, 0, 1, + sValuePtr->relief); + } + + nextChunk2: + chunkPtr2 = nextPtr2; + if (chunkPtr2 == NULL) { + rightX2 = INT_MAX; + } else { + nextPtr2 = chunkPtr2->nextPtr; + rightX2 = chunkPtr2->x + chunkPtr2->width; + if (nextPtr2 == NULL) { + rightX2 = INT_MAX; + } + } + } + + /* + * Pass 3: draw the horizontal bevels along the bottom of the line. This + * uses the same approach as pass 2. + */ + + chunkPtr = dlPtr->chunkPtr; + leftX = 0; /* See Note A above. */ + leftXIn = 0; + rightX = chunkPtr->x + chunkPtr->width; + if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) { + rightX = maxX; + } + chunkPtr2 = NULL; + if (dlPtr->nextPtr != NULL && dlPtr->nextPtr->chunkPtr != NULL) { + /* + * Find the chunk in the next line that covers leftX. + */ + + nextPtr2 = dlPtr->nextPtr->chunkPtr; + rightX2 = 0; /* See Note A above. */ + while (rightX2 <= leftX) { + chunkPtr2 = nextPtr2; + if (chunkPtr2 == NULL) { + break; + } + nextPtr2 = chunkPtr2->nextPtr; + rightX2 = chunkPtr2->x + chunkPtr2->width; + if (nextPtr2 == NULL) { + rightX2 = INT_MAX; + } + } + } else { + nextPtr2 = NULL; + rightX2 = INT_MAX; + } + + while (leftX < maxX) { + matchLeft = (chunkPtr2 != NULL) + && SAME_BACKGROUND(chunkPtr2->stylePtr, chunkPtr->stylePtr); + sValuePtr = chunkPtr->stylePtr->sValuePtr; + if (rightX <= rightX2) { + if ((chunkPtr->nextPtr == NULL) + || !SAME_BACKGROUND(chunkPtr->stylePtr, + chunkPtr->nextPtr->stylePtr)) { + if (!matchLeft && (sValuePtr->relief != TK_RELIEF_FLAT)) { + Tk_3DHorizontalBevel(textPtr->tkwin, pixmap, + sValuePtr->border, leftX + xOffset, + y + dlPtr->height - sValuePtr->borderWidth, + rightX - leftX, sValuePtr->borderWidth, leftXIn, + 0, 0, sValuePtr->relief); + } + leftX = rightX; + leftXIn = 0; + if ((rightX == rightX2) && (chunkPtr2 != NULL)) { + goto nextChunk2b; + } + } + chunkPtr = chunkPtr->nextPtr; + if (chunkPtr == NULL) { + break; + } + rightX = chunkPtr->x + chunkPtr->width; + if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) { + rightX = maxX; + } + continue; + } + + matchRight = (nextPtr2 != NULL) + && SAME_BACKGROUND(nextPtr2->stylePtr, chunkPtr->stylePtr); + if (matchLeft && !matchRight) { + bw = sValuePtr->borderWidth; + if (rightX2 - sValuePtr->borderWidth < leftX) { + bw = rightX2 - leftX; + } + if (sValuePtr->relief != TK_RELIEF_FLAT) { + Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border, + rightX2 - bw + xOffset, + y + dlPtr->height - sValuePtr->borderWidth, + bw, sValuePtr->borderWidth, 0, sValuePtr->relief); + } + leftX = rightX2 - bw; + leftXIn = 1; + } else if (!matchLeft && matchRight + && (sValuePtr->relief != TK_RELIEF_FLAT)) { + bw = sValuePtr->borderWidth; + if (rightX2 + sValuePtr->borderWidth > rightX) { + bw = rightX - rightX2; + } + Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border, + rightX2 + xOffset, + y + dlPtr->height - sValuePtr->borderWidth, bw, + sValuePtr->borderWidth, 1, sValuePtr->relief); + Tk_3DHorizontalBevel(textPtr->tkwin, pixmap, sValuePtr->border, + leftX + xOffset, + y + dlPtr->height - sValuePtr->borderWidth, + rightX2 + bw - leftX, sValuePtr->borderWidth, leftXIn, + 1, 0, sValuePtr->relief); + } + + nextChunk2b: + chunkPtr2 = nextPtr2; + if (chunkPtr2 == NULL) { + rightX2 = INT_MAX; + } else { + nextPtr2 = chunkPtr2->nextPtr; + rightX2 = chunkPtr2->x + chunkPtr2->width; + if (nextPtr2 == NULL) { + rightX2 = INT_MAX; + } + } + } +} + +/* + *---------------------------------------------------------------------- + * + * AsyncUpdateLineMetrics -- + * + * This function is invoked as a background handler to update the pixel- + * height calculations of individual lines in an asychronous manner. + * + * Currently a timer-handler is used for this purpose, which continuously + * reschedules itself. It may well be better to use some other approach + * (e.g., a background thread). We can't use an idle-callback because of + * a known bug in Tcl/Tk in which idle callbacks are not allowed to + * re-schedule themselves. This just causes an effective infinite loop. + * + * Results: + * None. + * + * Side effects: + * Line heights may be recalculated. + * + *---------------------------------------------------------------------- + */ + +static void +AsyncUpdateLineMetrics( + ClientData clientData) /* Information about widget. */ +{ + register TkText *textPtr = clientData; + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + int lineNum; + + dInfoPtr->lineUpdateTimer = NULL; + + if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED) + || !Tk_IsMapped(textPtr->tkwin)) { + /* + * The widget has been deleted, or is not mapped. Don't do anything. + */ + + if (--textPtr->refCount == 0) { + ckfree(textPtr); + } + return; + } + + if (dInfoPtr->flags & REDRAW_PENDING) { + dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1, + AsyncUpdateLineMetrics, clientData); + return; + } + + /* + * Reify where we end or all hell breaks loose with the calculations when + * we try to update. [Bug 2677890] + */ + + lineNum = dInfoPtr->currentMetricUpdateLine; + if (dInfoPtr->lastMetricUpdateLine == -1) { + dInfoPtr->lastMetricUpdateLine = + TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr); + } + + /* + * Update the lines in blocks of about 24 recalculations, or 250+ lines + * examined, so we pass in 256 for 'doThisMuch'. + */ + + lineNum = TkTextUpdateLineMetrics(textPtr, lineNum, + dInfoPtr->lastMetricUpdateLine, 256); + + dInfoPtr->currentMetricUpdateLine = lineNum; + + if (tkTextDebug) { + char buffer[2 * TCL_INTEGER_SPACE + 1]; + + sprintf(buffer, "%d %d", lineNum, dInfoPtr->lastMetricUpdateLine); + LOG("tk_textInvalidateLine", buffer); + } + + /* + * If we're not in the middle of a long-line calculation (metricEpoch==-1) + * and we've reached the last line, then we're done. + */ + + if (dInfoPtr->metricEpoch == -1 + && lineNum == dInfoPtr->lastMetricUpdateLine) { + /* + * We have looped over all lines, so we're done. We must release our + * refCount on the widget (the timer token was already set to NULL + * above). If there is a registered aftersync command, run that first. + */ + + if (textPtr->afterSyncCmd) { + int code; + Tcl_Preserve((ClientData) textPtr->interp); + code = Tcl_EvalObjEx(textPtr->interp, textPtr->afterSyncCmd, + TCL_EVAL_GLOBAL); + if (code == TCL_ERROR) { + Tcl_AddErrorInfo(textPtr->interp, "\n (text sync)"); + Tcl_BackgroundError(textPtr->interp); + } + Tcl_Release((ClientData) textPtr->interp); + Tcl_DecrRefCount(textPtr->afterSyncCmd); + textPtr->afterSyncCmd = NULL; + } + + /* + * Fire the <<WidgetViewSync>> event since the widget view is in sync + * with its internal data (actually it will be after the next trip + * through the event loop, because the widget redraws at idle-time). + */ + + GenerateWidgetViewSyncEvent(textPtr, 1); + + textPtr->refCount--; + if (textPtr->refCount == 0) { + ckfree(textPtr); + } + return; + } + + /* + * Re-arm the timer. We already have a refCount on the text widget so no + * need to adjust that. + */ + + dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1, + AsyncUpdateLineMetrics, textPtr); +} + +/* + *---------------------------------------------------------------------- + * + * GenerateWidgetViewSyncEvent -- + * + * Send the <<WidgetViewSync>> event related to the text widget + * line metrics asynchronous update. + * This is equivalent to: + * event generate $textWidget <<WidgetViewSync>> -data $s + * where $s is the sync status: true (when the widget view is in + * sync with its internal data) or false (when it is not). + * + * Results: + * None + * + * Side effects: + * If corresponding bindings are present, they will trigger. + * + *---------------------------------------------------------------------- + */ + +static void +GenerateWidgetViewSyncEvent( + TkText *textPtr, /* Information about text widget. */ + Bool InSync) /* true if in sync, false otherwise */ +{ + TkSendVirtualEvent(textPtr->tkwin, "WidgetViewSync", + Tcl_NewBooleanObj(InSync)); +} + +/* + *---------------------------------------------------------------------- + * + * TkTextUpdateLineMetrics -- + * + * This function updates the pixel height calculations of a range of + * lines in the widget. The range is from lineNum to endLine, but, if + * doThisMuch is positive, then the function may return earlier, once a + * certain number of lines has been examined. The line counts are from 0. + * + * If doThisMuch is -1, then all lines in the range will be updated. This + * will potentially take quite some time for a large text widget. + * + * Note: with bad input for lineNum and endLine, this function can loop + * indefinitely. + * + * Results: + * The index of the last line examined (or -1 if we are about to wrap + * around from end to beginning of the widget, and the next line will be + * the first line). + * + * Side effects: + * Line heights may be recalculated. + * + *---------------------------------------------------------------------- + */ + +int +TkTextUpdateLineMetrics( + TkText *textPtr, /* Information about widget. */ + int lineNum, /* Start at this line. */ + int endLine, /* Go no further than this line. */ + int doThisMuch) /* How many lines to check, or how many 10s of + * lines to recalculate. If '-1' then do + * everything in the range (which may take a + * while). */ +{ + TkTextLine *linePtr = NULL; + int count = 0; + int totalLines = TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr); + + if (totalLines == 0) { + /* + * Empty peer widget. + */ + + return endLine; + } + + while (1) { + /* + * Get a suitable line. + */ + + if (lineNum == -1 && linePtr == NULL) { + lineNum = 0; + linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr, + lineNum); + } else { + if (lineNum == -1 || linePtr == NULL) { + if (lineNum == -1) { + lineNum = 0; + } + linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, + textPtr, lineNum); + } else { + lineNum++; + linePtr = TkBTreeNextLine(textPtr, linePtr); + } + + /* + * If we're in the middle of a partial-line height calculation, + * then we can't be done. + */ + + if (textPtr->dInfoPtr->metricEpoch == -1 && lineNum == endLine) { + /* + * We have looped over all lines, so we're done. + */ + + break; + } + } + + if (lineNum < totalLines) { + if (tkTextDebug) { + char buffer[4 * TCL_INTEGER_SPACE + 3]; + + sprintf(buffer, "%d %d %d %d", + lineNum, endLine, totalLines, count); + LOG("tk_textInvalidateLine", buffer); + } + + /* + * Now update the line's metrics if necessary. + */ + + if (TkBTreeLinePixelEpoch(textPtr, linePtr) + == textPtr->dInfoPtr->lineMetricUpdateEpoch) { + /* + * This line is already up to date. That means there's nothing + * to do here. + */ + } else if (doThisMuch == -1) { + count += 8 * TkTextUpdateOneLine(textPtr, linePtr, 0,NULL,0); + } else { + TkTextIndex index; + TkTextIndex *indexPtr; + int pixelHeight; + + /* + * If the metric epoch is the same as the widget's epoch, then + * we know that indexPtrs are still valid, and if the cached + * metricIndex (if any) is for the same line as we wish to + * examine, then we are looking at a long line wrapped many + * times, which we will examine in pieces. + */ + + if (textPtr->dInfoPtr->metricEpoch == + textPtr->sharedTextPtr->stateEpoch && + textPtr->dInfoPtr->metricIndex.linePtr==linePtr) { + indexPtr = &textPtr->dInfoPtr->metricIndex; + pixelHeight = textPtr->dInfoPtr->metricPixelHeight; + } else { + /* + * We must reset the partial line height calculation data + * here, so we don't use it when it is out of date. + */ + + textPtr->dInfoPtr->metricEpoch = -1; + index.tree = textPtr->sharedTextPtr->tree; + index.linePtr = linePtr; + index.byteIndex = 0; + index.textPtr = NULL; + indexPtr = &index; + pixelHeight = 0; + } + + /* + * Update the line and update the counter, counting 8 for each + * display line we actually re-layout. + */ + + count += 8 * TkTextUpdateOneLine(textPtr, linePtr, + pixelHeight, indexPtr, 1); + + if (indexPtr->linePtr == linePtr) { + /* + * We didn't complete the logical line, because it + * produced very many display lines, which must be because + * it must be a long line wrapped many times. So we must + * cache as far as we got for next time around. + */ + + if (pixelHeight == 0) { + /* + * These have already been stored, unless we just + * started the new line. + */ + + textPtr->dInfoPtr->metricIndex = index; + textPtr->dInfoPtr->metricEpoch = + textPtr->sharedTextPtr->stateEpoch; + } + textPtr->dInfoPtr->metricPixelHeight = + TkBTreeLinePixelCount(textPtr, linePtr); + break; + } + + /* + * We're done with this long line. + */ + + textPtr->dInfoPtr->metricEpoch = -1; + } + } else { + /* + * We must never recalculate the height of the last artificial + * line. It must stay at zero, and if we recalculate it, it will + * change. + */ + + if (endLine >= totalLines) { + lineNum = endLine; + break; + } + + /* + * Set things up for the next loop through. + */ + + lineNum = -1; + } + count++; + + if (doThisMuch != -1 && count >= doThisMuch) { + break; + } + } + if (doThisMuch == -1) { + /* + * If we were requested to provide a full update, then also update the + * scrollbar. + */ + + GetYView(textPtr->interp, textPtr, 1); + } + return lineNum; +} + +/* + *---------------------------------------------------------------------- + * + * TkTextInvalidateLineMetrics, TextInvalidateLineMetrics -- + * + * Mark a number of text lines as having invalid line metric + * calculations. Never call this with linePtr as the last (artificial) + * line in the text. Depending on 'action' which indicates whether the + * given lines are simply invalid or have been inserted or deleted, the + * pre-existing asynchronous line update range may need to be adjusted. + * + * If linePtr is NULL then 'lineCount' and 'action' are ignored and all + * lines are invalidated. + * + * Results: + * None. + * + * Side effects: + * May schedule an asychronous callback. + * + *---------------------------------------------------------------------- + */ + +void +TkTextInvalidateLineMetrics( + TkSharedText *sharedTextPtr,/* Shared widget section for all peers, or + * NULL. */ + TkText *textPtr, /* Widget record for text widget. */ + TkTextLine *linePtr, /* Invalidation starts from this line. */ + int lineCount, /* And includes this many following lines. */ + int action) /* Indicates what type of invalidation + * occurred (insert, delete, or simple). */ +{ + if (sharedTextPtr == NULL) { + TextInvalidateLineMetrics(textPtr, linePtr, lineCount, action); + } else { + textPtr = sharedTextPtr->peers; + while (textPtr != NULL) { + TextInvalidateLineMetrics(textPtr, linePtr, lineCount, action); + textPtr = textPtr->next; + } + } +} + +static void +TextInvalidateLineMetrics( + TkText *textPtr, /* Widget record for text widget. */ + TkTextLine *linePtr, /* Invalidation starts from this line. */ + int lineCount, /* And includes this many following lines. */ + int action) /* Indicates what type of invalidation + * occurred (insert, delete, or simple). */ +{ + int fromLine; + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + + if (linePtr != NULL) { + int counter = lineCount; + + fromLine = TkBTreeLinesTo(textPtr, linePtr); + + /* + * Invalidate the height calculations of each line in the given range. + */ + + TkBTreeLinePixelEpoch(textPtr, linePtr) = 0; + while (counter > 0 && linePtr != NULL) { + linePtr = TkBTreeNextLine(textPtr, linePtr); + if (linePtr != NULL) { + TkBTreeLinePixelEpoch(textPtr, linePtr) = 0; + } + counter--; + } + + /* + * Now schedule an examination of each line in the union of the old + * and new update ranges, including the (possibly empty) range in + * between. If that between range is not-empty, then we are examining + * more lines than is strictly necessary (but the examination of the + * extra lines should be quick, since their pixelCalculationEpoch will + * be up to date). However, to keep track of that would require more + * complex record-keeping than what we have. + */ + + if (dInfoPtr->lineUpdateTimer == NULL) { + dInfoPtr->currentMetricUpdateLine = fromLine; + if (action == TK_TEXT_INVALIDATE_DELETE) { + lineCount = 0; + } + dInfoPtr->lastMetricUpdateLine = fromLine + lineCount + 1; + } else { + int toLine = fromLine + lineCount + 1; + + if (action == TK_TEXT_INVALIDATE_DELETE) { + if (toLine <= dInfoPtr->currentMetricUpdateLine) { + dInfoPtr->currentMetricUpdateLine = fromLine; + if (dInfoPtr->lastMetricUpdateLine != -1) { + dInfoPtr->lastMetricUpdateLine -= lineCount; + } + } else if (fromLine <= dInfoPtr->currentMetricUpdateLine) { + dInfoPtr->currentMetricUpdateLine = fromLine; + if (toLine <= dInfoPtr->lastMetricUpdateLine) { + dInfoPtr->lastMetricUpdateLine -= lineCount; + } + } else { + if (dInfoPtr->lastMetricUpdateLine != -1) { + dInfoPtr->lastMetricUpdateLine = toLine; + } + } + } else if (action == TK_TEXT_INVALIDATE_INSERT) { + if (toLine <= dInfoPtr->currentMetricUpdateLine) { + dInfoPtr->currentMetricUpdateLine = fromLine; + if (dInfoPtr->lastMetricUpdateLine != -1) { + dInfoPtr->lastMetricUpdateLine += lineCount; + } + } else if (fromLine <= dInfoPtr->currentMetricUpdateLine) { + dInfoPtr->currentMetricUpdateLine = fromLine; + if (toLine <= dInfoPtr->lastMetricUpdateLine) { + dInfoPtr->lastMetricUpdateLine += lineCount; + } + if (toLine > dInfoPtr->lastMetricUpdateLine) { + dInfoPtr->lastMetricUpdateLine = toLine; + } + } else { + if (dInfoPtr->lastMetricUpdateLine != -1) { + dInfoPtr->lastMetricUpdateLine = toLine; + } + } + } else { + if (fromLine < dInfoPtr->currentMetricUpdateLine) { + dInfoPtr->currentMetricUpdateLine = fromLine; + } + if (dInfoPtr->lastMetricUpdateLine != -1 + && toLine > dInfoPtr->lastMetricUpdateLine) { + dInfoPtr->lastMetricUpdateLine = toLine; + } + } + } + } else { + /* + * This invalidates the height of all lines in the widget. + */ + + if ((++dInfoPtr->lineMetricUpdateEpoch) == 0) { + dInfoPtr->lineMetricUpdateEpoch++; + } + + /* + * This has the effect of forcing an entire new loop of update checks + * on all lines in the widget. + */ + + if (dInfoPtr->lineUpdateTimer == NULL) { + dInfoPtr->currentMetricUpdateLine = -1; + } + dInfoPtr->lastMetricUpdateLine = dInfoPtr->currentMetricUpdateLine; + } + + /* + * Now re-set the current update calculations. + */ + + if (dInfoPtr->lineUpdateTimer == NULL) { + textPtr->refCount++; + dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1, + AsyncUpdateLineMetrics, textPtr); + GenerateWidgetViewSyncEvent(textPtr, 0); + } +} + +/* + *---------------------------------------------------------------------- + * + * TkTextFindDisplayLineEnd -- + * + * This function is invoked to find the index of the beginning or end of + * the particular display line on which the given index sits, whether + * that line is displayed or not. + * + * If 'end' is zero, we look for the start, and if 'end' is one we look + * for the end. + * + * If the beginning of the current display line is elided, and we are + * looking for the start of the line, then the returned index will be the + * first elided index on the display line. + * + * Similarly if the end of the current display line is elided and we are + * looking for the end, then the returned index will be the last elided + * index on the display line. + * + * Results: + * Modifies indexPtr to point to the given end. + * + * If xOffset is non-NULL, it is set to the x-pixel offset of the given + * original index within the given display line. + * + * Side effects: + * The combination of 'LayoutDLine' and 'FreeDLines' seems like a rather + * time-consuming way of gathering the information we need, so this would + * be a good place to look to speed up the calculations. In particular + * these calls will map and unmap embedded windows respectively, which I + * would hope isn't exactly necessary! + * + *---------------------------------------------------------------------- + */ + +void +TkTextFindDisplayLineEnd( + TkText *textPtr, /* Widget record for text widget. */ + TkTextIndex *indexPtr, /* Index we will adjust to the display line + * start or end. */ + int end, /* 0 = start, 1 = end. */ + int *xOffset) /* NULL, or used to store the x-pixel offset + * of the original index within its display + * line. */ +{ + TkTextIndex index; + + if (!end && IsStartOfNotMergedLine(textPtr, indexPtr)) { + /* + * Nothing to do. + */ + + if (xOffset != NULL) { + *xOffset = 0; + } + return; + } + + index = *indexPtr; + index.byteIndex = 0; + index.textPtr = NULL; + + while (1) { + TkTextIndex endOfLastLine; + + if (TkTextIndexBackBytes(textPtr, &index, 1, &endOfLastLine)) { + /* + * Reached beginning of text. + */ + + break; + } + + if (!TkTextIsElided(textPtr, &endOfLastLine, NULL)) { + /* + * The eol is not elided, so 'index' points to the start of a + * display line (as well as logical line). + */ + + break; + } + + /* + * indexPtr's logical line is actually merged with the previous + * logical line whose eol is elided. Continue searching back to get a + * real line start. + */ + + index = endOfLastLine; + index.byteIndex = 0; + } + + while (1) { + DLine *dlPtr; + int byteCount; + TkTextIndex nextLineStart; + + dlPtr = LayoutDLine(textPtr, &index); + byteCount = dlPtr->byteCount; + + TkTextIndexForwBytes(textPtr, &index, byteCount, &nextLineStart); + + /* + * 'byteCount' goes up to the beginning of the next display line, so + * equality here says we need one more line. We try to perform a quick + * comparison which is valid for the case where the logical line is + * the same, but otherwise fall back on a full TkTextIndexCmp. + */ + + if (((index.linePtr == indexPtr->linePtr) + && (index.byteIndex + byteCount > indexPtr->byteIndex)) + || (dlPtr->logicalLinesMerged > 0 + && TkTextIndexCmp(&nextLineStart, indexPtr) > 0)) { + /* + * It's on this display line. + */ + + if (xOffset != NULL) { + /* + * This call takes a byte index relative to the start of the + * current _display_ line, not logical line. We are about to + * overwrite indexPtr->byteIndex, so we must do this now. + */ + + *xOffset = DlineXOfIndex(textPtr, dlPtr, + TkTextIndexCountBytes(textPtr, &dlPtr->index, + indexPtr)); + } + if (end) { + /* + * The index we want is one less than the number of bytes in + * the display line. + */ + + TkTextIndexBackBytes(textPtr, &nextLineStart, 1, indexPtr); + } else { + *indexPtr = index; + } + FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP); + return; + } + + FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP); + index = nextLineStart; + } +} + +/* + *---------------------------------------------------------------------- + * + * CalculateDisplayLineHeight -- + * + * This function is invoked to recalculate the height of the particular + * display line which starts with the given index, whether that line is + * displayed or not. + * + * This function does not, in itself, update any cached information about + * line heights. That should be done, where necessary, by its callers. + * + * The behaviour of this function is _undefined_ if indexPtr is not + * currently at the beginning of a display line. + * + * Results: + * The number of vertical pixels used by the display line. + * + * If 'byteCountPtr' is non-NULL, then returns in that pointer the number + * of byte indices on the given display line (which can be used to update + * indexPtr in a loop). + * + * If 'mergedLinePtr' is non-NULL, then returns in that pointer the + * number of extra logical lines merged into the given display line. + * + * Side effects: + * The combination of 'LayoutDLine' and 'FreeDLines' seems like a rather + * time-consuming way of gathering the information we need, so this would + * be a good place to look to speed up the calculations. In particular + * these calls will map and unmap embedded windows respectively, which I + * would hope isn't exactly necessary! + * + *---------------------------------------------------------------------- + */ + +static int +CalculateDisplayLineHeight( + TkText *textPtr, /* Widget record for text widget. */ + const TkTextIndex *indexPtr,/* The index at the beginning of the display + * line of interest. */ + int *byteCountPtr, /* NULL or used to return the number of byte + * indices on the given display line. */ + int *mergedLinePtr) /* NULL or used to return if the given display + * line merges with a following logical line + * (because the eol is elided). */ +{ + DLine *dlPtr; + int pixelHeight; + + if (tkTextDebug) { + int oldtkTextDebug = tkTextDebug; + /* + * Check that the indexPtr we are given really is at the start of a + * display line. The gymnastics with tkTextDebug is to prevent + * failure of a test suite test, that checks that lines are rendered + * exactly once. TkTextFindDisplayLineEnd is used here for checking + * indexPtr but it calls LayoutDLine/FreeDLine which makes the + * counting wrong. The debug mode shall therefore be switched off + * when calling TkTextFindDisplayLineEnd. + */ + + TkTextIndex indexPtr2 = *indexPtr; + tkTextDebug = 0; + TkTextFindDisplayLineEnd(textPtr, &indexPtr2, 0, NULL); + tkTextDebug = oldtkTextDebug; + if (TkTextIndexCmp(&indexPtr2,indexPtr) != 0) { + Tcl_Panic("CalculateDisplayLineHeight called with bad indexPtr"); + } + } + + /* + * Special case for artificial last line. May be better to move this + * inside LayoutDLine. + */ + + if (indexPtr->byteIndex == 0 + && TkBTreeNextLine(textPtr, indexPtr->linePtr) == NULL) { + if (byteCountPtr != NULL) { + *byteCountPtr = 0; + } + if (mergedLinePtr != NULL) { + *mergedLinePtr = 0; + } + return 0; + } + + /* + * Layout, find the information we need and then free the display-line we + * laid-out. We must use 'FreeDLines' because it will actually call the + * relevant code to unmap any embedded windows which were mapped in the + * LayoutDLine call! + */ + + dlPtr = LayoutDLine(textPtr, indexPtr); + pixelHeight = dlPtr->height; + if (byteCountPtr != NULL) { + *byteCountPtr = dlPtr->byteCount; + } + if (mergedLinePtr != NULL) { + *mergedLinePtr = dlPtr->logicalLinesMerged; + } + FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP); + + return pixelHeight; +} + +/* + *---------------------------------------------------------------------- + * + * TkTextIndexYPixels -- + * + * This function is invoked to calculate the number of vertical pixels + * between the first index of the text widget and the given index. The + * range from first logical line to given logical line is determined + * using the cached values, and the range inside the given logical line + * is calculated on the fly. + * + * Results: + * The pixel distance between first pixel in the widget and the + * top of the index's current display line (could be zero). + * + * Side effects: + * Just those of 'CalculateDisplayLineHeight'. + * + *---------------------------------------------------------------------- + */ + +int +TkTextIndexYPixels( + TkText *textPtr, /* Widget record for text widget. */ + const TkTextIndex *indexPtr)/* The index of which we want the pixel + * distance from top of logical line to top of + * index. */ +{ + int pixelHeight; + TkTextIndex index; + int alreadyStartOfLine = 1; + + /* + * Find the index denoting the closest position being at the same time + * the start of a logical line above indexPtr and the start of a display + * line. + */ + + index = *indexPtr; + while (1) { + TkTextFindDisplayLineEnd(textPtr, &index, 0, NULL); + if (index.byteIndex == 0) { + break; + } + TkTextIndexBackBytes(textPtr, &index, 1, &index); + alreadyStartOfLine = 0; + } + + pixelHeight = TkBTreePixelsTo(textPtr, index.linePtr); + + /* + * Shortcut to avoid layout of a superfluous display line. We know there + * is nothing more to add up to the height if the index we were given was + * already on the first display line of a logical line. + */ + + if (alreadyStartOfLine) { + return pixelHeight; + } + + /* + * Iterate through display lines, starting at the logical line belonging + * to index, adding up the pixel height of each such display line as we + * go along, until we go past 'indexPtr'. + */ + + while (1) { + int bytes, height, compare; + + /* + * Currently this call doesn't have many side-effects. However, if in + * the future we change the code so there are side-effects (such as + * adjusting linePtr->pixelHeight), then the code might not quite work + * as intended, specifically the 'linePtr->pixelHeight == pixelHeight' + * test below this while loop. + */ + + height = CalculateDisplayLineHeight(textPtr, &index, &bytes, NULL); + + TkTextIndexForwBytes(textPtr, &index, bytes, &index); + + compare = TkTextIndexCmp(&index,indexPtr); + if (compare > 0) { + return pixelHeight; + } + + if (height > 0) { + pixelHeight += height; + } + + if (compare == 0) { + return pixelHeight; + } + } +} + +/* + *---------------------------------------------------------------------- + * + * TkTextUpdateOneLine -- + * + * This function is invoked to recalculate the height of a particular + * logical line, whether that line is displayed or not. + * + * It must NEVER be called for the artificial last TkTextLine which is + * used internally for administrative purposes only. That line must + * retain its initial height of 0 otherwise the pixel height calculation + * maintained by the B-tree will be wrong. + * + * Results: + * The number of display lines in the logical line. This could be zero if + * the line is totally elided. + * + * Side effects: + * Line heights may be recalculated, and a timer to update the scrollbar + * may be installed. Also see the called function + * CalculateDisplayLineHeight for its side effects. + * + *---------------------------------------------------------------------- + */ + +int +TkTextUpdateOneLine( + TkText *textPtr, /* Widget record for text widget. */ + TkTextLine *linePtr, /* The line of which to calculate the + * height. */ + int pixelHeight, /* If indexPtr is non-NULL, then this is the + * number of pixels in the logical line + * linePtr, up to the index which has been + * given. */ + TkTextIndex *indexPtr, /* Either NULL or an index at the start of a + * display line belonging to linePtr, at which + * we wish to start (e.g. up to which we have + * already calculated). On return this will be + * set to the first index on the next line. */ + int partialCalc) /* Set to 1 if we are allowed to do partial + * height calculations of long-lines. In this + * case we'll only return what we know so + * far. */ +{ + TkTextIndex index; + int displayLines; + int mergedLines; + + if (indexPtr == NULL) { + index.tree = textPtr->sharedTextPtr->tree; + index.linePtr = linePtr; + index.byteIndex = 0; + index.textPtr = NULL; + indexPtr = &index; + pixelHeight = 0; + } + + /* + * CalculateDisplayLineHeight _must_ be called (below) with an index at + * the beginning of a display line. Force this to happen. This is needed + * when TkTextUpdateOneLine is called with a line that is merged with its + * previous line: the number of merged logical lines in a display line is + * calculated correctly only when CalculateDisplayLineHeight receives + * an index at the beginning of a display line. In turn this causes the + * merged lines to receive their correct zero pixel height in + * TkBTreeAdjustPixelHeight. + */ + + TkTextFindDisplayLineEnd(textPtr, indexPtr, 0, NULL); + linePtr = indexPtr->linePtr; + + /* + * Iterate through all display-lines corresponding to the single logical + * line 'linePtr' (and lines merged into this line due to eol elision), + * adding up the pixel height of each such display line as we go along. + * The final total is, therefore, the total height of all display lines + * made up by the logical line 'linePtr' and subsequent logical lines + * merged into this line. + */ + + displayLines = 0; + mergedLines = 0; + + while (1) { + int bytes, height, logicalLines; + + /* + * Currently this call doesn't have many side-effects. However, if in + * the future we change the code so there are side-effects (such as + * adjusting linePtr->pixelHeight), then the code might not quite work + * as intended, specifically the 'linePtr->pixelHeight == pixelHeight' + * test below this while loop. + */ + + height = CalculateDisplayLineHeight(textPtr, indexPtr, &bytes, + &logicalLines); + + if (height > 0) { + pixelHeight += height; + displayLines++; + } + + mergedLines += logicalLines; + + if (TkTextIndexForwBytes(textPtr, indexPtr, bytes, indexPtr)) { + break; + } + + if (mergedLines == 0) { + if (indexPtr->linePtr != linePtr) { + /* + * If we reached the end of the logical line, then either way + * we don't have a partial calculation. + */ + + partialCalc = 0; + break; + } + } else { + if (IsStartOfNotMergedLine(textPtr, indexPtr)) { + /* + * We've ended a logical line. + */ + + partialCalc = 0; + break; + } + + /* + * We must still be on the same wrapped line, on a new logical + * line merged with the logical line 'linePtr'. + */ + } + if (partialCalc && displayLines > 50 && mergedLines == 0) { + /* + * Only calculate 50 display lines at a time, to avoid huge + * delays. In any case it is very rare that a single line wraps 50 + * times! + * + * If we have any merged lines, we must complete the full logical + * line layout here and now, because the partial-calculation code + * isn't designed to handle merged logical lines. Hence the + * 'mergedLines == 0' check. + */ + + break; + } + } + + if (!partialCalc) { + int changed = 0; + + /* + * Cancel any partial line height calculation state. + */ + + textPtr->dInfoPtr->metricEpoch = -1; + + /* + * Mark the logical line as being up to date (caution: it isn't yet up + * to date, that will happen in TkBTreeAdjustPixelHeight just below). + */ + + TkBTreeLinePixelEpoch(textPtr, linePtr) + = textPtr->dInfoPtr->lineMetricUpdateEpoch; + if (TkBTreeLinePixelCount(textPtr, linePtr) != pixelHeight) { + changed = 1; + } + + if (mergedLines > 0) { + int i = mergedLines; + TkTextLine *mergedLinePtr; + + /* + * Loop over all merged logical lines, marking them up to date + * (again, the pixel count setting will actually happen in + * TkBTreeAdjustPixelHeight). + */ + + mergedLinePtr = linePtr; + while (i-- > 0) { + mergedLinePtr = TkBTreeNextLine(textPtr, mergedLinePtr); + TkBTreeLinePixelEpoch(textPtr, mergedLinePtr) + = textPtr->dInfoPtr->lineMetricUpdateEpoch; + if (TkBTreeLinePixelCount(textPtr, mergedLinePtr) != 0) { + changed = 1; + } + } + } + + if (!changed) { + /* + * If there's nothing to change, then we can already return. + */ + + return displayLines; + } + } + + /* + * We set the line's height, but the return value is now the height of the + * entire widget, which may be used just below for reporting/debugging + * purposes. + */ + + pixelHeight = TkBTreeAdjustPixelHeight(textPtr, linePtr, pixelHeight, + mergedLines); + + if (tkTextDebug) { + char buffer[2 * TCL_INTEGER_SPACE + 1]; + + if (TkBTreeNextLine(textPtr, linePtr) == NULL) { + Tcl_Panic("Mustn't ever update line height of last artificial line"); + } + + sprintf(buffer, "%d %d", TkBTreeLinesTo(textPtr,linePtr), pixelHeight); + LOG("tk_textNumPixels", buffer); + } + if (textPtr->dInfoPtr->scrollbarTimer == NULL) { + textPtr->refCount++; + textPtr->dInfoPtr->scrollbarTimer = Tcl_CreateTimerHandler(200, + AsyncUpdateYScrollbar, textPtr); + } + return displayLines; +} + +/* + *---------------------------------------------------------------------- + * + * DisplayText -- + * + * This function is invoked as a when-idle handler to update the display. + * It only redisplays the parts of the text widget that are out of date. + * + * Results: + * None. + * + * Side effects: + * Information is redrawn on the screen. + * + *---------------------------------------------------------------------- + */ + +static void +DisplayText( + ClientData clientData) /* Information about widget. */ +{ + register TkText *textPtr = clientData; + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + register DLine *dlPtr; + DLine *prevPtr; + Pixmap pixmap; + int maxHeight, borders; + int bottomY = 0; /* Initialization needed only to stop compiler + * warnings. */ + Tcl_Interp *interp; + +#ifdef MAC_OSX_TK + /* + * If drawing is disabled, all we need to do is + * clear the REDRAW_PENDING flag. + */ + TkWindow *winPtr = (TkWindow *)(textPtr->tkwin); + MacDrawable *macWin = winPtr->privatePtr; + if (macWin && (macWin->flags & TK_DO_NOT_DRAW)){ + dInfoPtr->flags &= ~REDRAW_PENDING; + return; + } +#endif + + if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)) { + /* + * The widget has been deleted. Don't do anything. + */ + + return; + } + + interp = textPtr->interp; + Tcl_Preserve(interp); + + if (tkTextDebug) { + Tcl_SetVar2(interp, "tk_textRelayout", NULL, "", TCL_GLOBAL_ONLY); + } + + if (!Tk_IsMapped(textPtr->tkwin) || (dInfoPtr->maxX <= dInfoPtr->x) + || (dInfoPtr->maxY <= dInfoPtr->y)) { + UpdateDisplayInfo(textPtr); + dInfoPtr->flags &= ~REDRAW_PENDING; + goto doScrollbars; + } + numRedisplays++; + if (tkTextDebug) { + Tcl_SetVar2(interp, "tk_textRedraw", NULL, "", TCL_GLOBAL_ONLY); + } + + /* + * Choose a new current item if that is needed (this could cause event + * handlers to be invoked, hence the preserve/release calls and the loop, + * since the handlers could conceivably necessitate yet another current + * item calculation). The tkwin check is because the whole window could go + * away in the Tcl_Release call. + */ + + while (dInfoPtr->flags & REPICK_NEEDED) { + textPtr->refCount++; + dInfoPtr->flags &= ~REPICK_NEEDED; + TkTextPickCurrent(textPtr, &textPtr->pickEvent); + if (--textPtr->refCount == 0) { + ckfree(textPtr); + goto end; + } + if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)) { + goto end; + } + } + + /* + * First recompute what's supposed to be displayed. + */ + + UpdateDisplayInfo(textPtr); + dInfoPtr->dLinesInvalidated = 0; + + /* + * See if it's possible to bring some parts of the screen up-to-date by + * scrolling (copying from other parts of the screen). We have to be + * particularly careful with the top and bottom lines of the display, + * since these may only be partially visible and therefore not helpful for + * some scrolling purposes. + */ + + for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) { + register DLine *dlPtr2; + int offset, height, y, oldY; + TkRegion damageRgn; + + /* + * These tests are, in order: + * + * 1. If the line is already marked as invalid + * 2. If the line hasn't moved + * 3. If the line overlaps the bottom of the window and we are + * scrolling up. + * 4. If the line overlaps the top of the window and we are scrolling + * down. + * + * If any of these tests are true, then we can't scroll this line's + * part of the display. + * + * Note that even if tests 3 or 4 aren't true, we may be able to + * scroll the line, but we still need to be sure to call embedded + * window display procs on top and bottom lines if they have any + * portion non-visible (see below). + */ + + if ((dlPtr->flags & OLD_Y_INVALID) + || (dlPtr->y == dlPtr->oldY) + || (((dlPtr->oldY + dlPtr->height) > dInfoPtr->maxY) + && (dlPtr->y < dlPtr->oldY)) + || ((dlPtr->oldY < dInfoPtr->y) && (dlPtr->y > dlPtr->oldY))) { + continue; + } + + /* + * This line is already drawn somewhere in the window so it only needs + * to be copied to its new location. See if there's a group of lines + * that can all be copied together. + */ + + offset = dlPtr->y - dlPtr->oldY; + height = dlPtr->height; + y = dlPtr->y; + for (dlPtr2 = dlPtr->nextPtr; dlPtr2 != NULL; + dlPtr2 = dlPtr2->nextPtr) { + if ((dlPtr2->flags & OLD_Y_INVALID) + || ((dlPtr2->oldY + offset) != dlPtr2->y) + || ((dlPtr2->oldY + dlPtr2->height) > dInfoPtr->maxY)) { + break; + } + height += dlPtr2->height; + } + + /* + * Reduce the height of the area being copied if necessary to avoid + * overwriting the border area. + */ + + if ((y + height) > dInfoPtr->maxY) { + height = dInfoPtr->maxY - y; + } + oldY = dlPtr->oldY; + if (y < dInfoPtr->y) { + /* + * Adjust if the area being copied is going to overwrite the top + * border of the window (so the top line is only half onscreen). + */ + + int y_off = dInfoPtr->y - dlPtr->y; + height -= y_off; + oldY += y_off; + y = dInfoPtr->y; + } + + /* + * Update the lines we are going to scroll to show that they have been + * copied. + */ + + while (1) { + /* + * The DLine already has OLD_Y_INVALID cleared. + */ + + dlPtr->oldY = dlPtr->y; + if (dlPtr->nextPtr == dlPtr2) { + break; + } + dlPtr = dlPtr->nextPtr; + } + + /* + * Scan through the lines following the copied ones to see if we are + * going to overwrite them with the copy operation. If so, mark them + * for redisplay. + */ + + for ( ; dlPtr2 != NULL; dlPtr2 = dlPtr2->nextPtr) { + if ((!(dlPtr2->flags & OLD_Y_INVALID)) + && ((dlPtr2->oldY + dlPtr2->height) > y) + && (dlPtr2->oldY < (y + height))) { + dlPtr2->flags |= OLD_Y_INVALID; + } + } + + /* + * Now scroll the lines. This may generate damage which we handle by + * calling TextInvalidateRegion to mark the display blocks as stale. + */ + + damageRgn = TkCreateRegion(); + if (TkScrollWindow(textPtr->tkwin, dInfoPtr->scrollGC, dInfoPtr->x, + oldY, dInfoPtr->maxX-dInfoPtr->x, height, 0, y-oldY, + damageRgn)) { + TextInvalidateRegion(textPtr, damageRgn); + + } + numCopies++; + TkDestroyRegion(damageRgn); + } + + /* + * Clear the REDRAW_PENDING flag here. This is actually pretty tricky. We + * want to wait until *after* doing the scrolling, since that could + * generate more areas to redraw and don't want to reschedule a redisplay + * for them. On the other hand, we can't wait until after all the + * redisplaying, because the act of redisplaying could actually generate + * more redisplays (e.g. in the case of a nested window with event + * bindings triggered by redisplay). + */ + + dInfoPtr->flags &= ~REDRAW_PENDING; + + /* + * Redraw the borders if that's needed. + */ + + if (dInfoPtr->flags & REDRAW_BORDERS) { + if (tkTextDebug) { + LOG("tk_textRedraw", "borders"); + } + + if (textPtr->tkwin == NULL) { + /* + * The widget has been deleted. Don't do anything. + */ + + goto end; + } + + Tk_Draw3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin), + textPtr->border, textPtr->highlightWidth, + textPtr->highlightWidth, + Tk_Width(textPtr->tkwin) - 2*textPtr->highlightWidth, + Tk_Height(textPtr->tkwin) - 2*textPtr->highlightWidth, + textPtr->borderWidth, textPtr->relief); + if (textPtr->highlightWidth != 0) { + GC fgGC, bgGC; + + bgGC = Tk_GCForColor(textPtr->highlightBgColorPtr, + Tk_WindowId(textPtr->tkwin)); + if (textPtr->flags & GOT_FOCUS) { + fgGC = Tk_GCForColor(textPtr->highlightColorPtr, + Tk_WindowId(textPtr->tkwin)); + TkpDrawHighlightBorder(textPtr->tkwin, fgGC, bgGC, + textPtr->highlightWidth, Tk_WindowId(textPtr->tkwin)); + } else { + TkpDrawHighlightBorder(textPtr->tkwin, bgGC, bgGC, + textPtr->highlightWidth, Tk_WindowId(textPtr->tkwin)); + } + } + borders = textPtr->borderWidth + textPtr->highlightWidth; + if (textPtr->padY > 0) { + Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin), + textPtr->border, borders, borders, + Tk_Width(textPtr->tkwin) - 2*borders, textPtr->padY, + 0, TK_RELIEF_FLAT); + Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin), + textPtr->border, borders, + Tk_Height(textPtr->tkwin) - borders - textPtr->padY, + Tk_Width(textPtr->tkwin) - 2*borders, + textPtr->padY, 0, TK_RELIEF_FLAT); + } + if (textPtr->padX > 0) { + Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin), + textPtr->border, borders, borders + textPtr->padY, + textPtr->padX, + Tk_Height(textPtr->tkwin) - 2*borders -2*textPtr->padY, + 0, TK_RELIEF_FLAT); + Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin), + textPtr->border, + Tk_Width(textPtr->tkwin) - borders - textPtr->padX, + borders + textPtr->padY, textPtr->padX, + Tk_Height(textPtr->tkwin) - 2*borders -2*textPtr->padY, + 0, TK_RELIEF_FLAT); + } + dInfoPtr->flags &= ~REDRAW_BORDERS; + } + + /* + * Now we have to redraw the lines that couldn't be updated by scrolling. + * First, compute the height of the largest line and allocate an off- + * screen pixmap to use for double-buffered displays. + */ + + maxHeight = -1; + for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; + dlPtr = dlPtr->nextPtr) { + if ((dlPtr->height > maxHeight) && + ((dlPtr->flags&OLD_Y_INVALID) || (dlPtr->oldY != dlPtr->y))) { + maxHeight = dlPtr->height; + } + bottomY = dlPtr->y + dlPtr->height; + } + + /* + * There used to be a line here which restricted 'maxHeight' to be no + * larger than 'dInfoPtr->maxY', but this is incorrect for the case where + * individual lines may be taller than the widget _and_ we have smooth + * scrolling. What we can do is restrict maxHeight to be no larger than + * 'dInfoPtr->maxY + dInfoPtr->topPixelOffset'. + */ + + if (maxHeight > (dInfoPtr->maxY + dInfoPtr->topPixelOffset)) { + maxHeight = (dInfoPtr->maxY + dInfoPtr->topPixelOffset); + } + + if (maxHeight > 0) { +#ifndef TK_NO_DOUBLE_BUFFERING + pixmap = Tk_GetPixmap(Tk_Display(textPtr->tkwin), + Tk_WindowId(textPtr->tkwin), Tk_Width(textPtr->tkwin), + maxHeight, Tk_Depth(textPtr->tkwin)); +#else + pixmap = Tk_WindowId(textPtr->tkwin); +#endif /* TK_NO_DOUBLE_BUFFERING */ + for (prevPtr = NULL, dlPtr = textPtr->dInfoPtr->dLinePtr; + (dlPtr != NULL) && (dlPtr->y < dInfoPtr->maxY); + prevPtr = dlPtr, dlPtr = dlPtr->nextPtr) { + if (dlPtr->chunkPtr == NULL) { + continue; + } + if ((dlPtr->flags & OLD_Y_INVALID) || dlPtr->oldY != dlPtr->y) { + if (tkTextDebug) { + char string[TK_POS_CHARS]; + + TkTextPrintIndex(textPtr, &dlPtr->index, string); + LOG("tk_textRedraw", string); + } + DisplayDLine(textPtr, dlPtr, prevPtr, pixmap); + if (dInfoPtr->dLinesInvalidated) { +#ifndef TK_NO_DOUBLE_BUFFERING + Tk_FreePixmap(Tk_Display(textPtr->tkwin), pixmap); +#endif /* TK_NO_DOUBLE_BUFFERING */ + return; + } + dlPtr->oldY = dlPtr->y; + dlPtr->flags &= ~(NEW_LAYOUT | OLD_Y_INVALID); + } else if (dlPtr->chunkPtr != NULL && ((dlPtr->y < 0) + || (dlPtr->y + dlPtr->height > dInfoPtr->maxY))) { + register TkTextDispChunk *chunkPtr; + + /* + * It's the first or last DLine which are also overlapping the + * top or bottom of the window, but we decided above it wasn't + * necessary to display them (we were able to update them by + * scrolling). This is fine, except that if the lines contain + * any embedded windows, we must still call the display proc + * on them because they might need to be unmapped or they + * might need to be moved to reflect their new position. + * Otherwise, everything else moves, but the embedded window + * doesn't! + * + * So, we loop through all the chunks, calling the display + * proc of embedded windows only. + */ + + for (chunkPtr = dlPtr->chunkPtr; (chunkPtr != NULL); + chunkPtr = chunkPtr->nextPtr) { + int x; + if (chunkPtr->displayProc != TkTextEmbWinDisplayProc) { + continue; + } + x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curXPixelOffset; + if ((x + chunkPtr->width <= 0) || (x >= dInfoPtr->maxX)) { + /* + * Note: we have to call the displayProc even for + * chunks that are off-screen. This is needed, for + * example, so that embedded windows can be unmapped + * in this case. Display the chunk at a coordinate + * that can be clearly identified by the displayProc + * as being off-screen to the left (the displayProc + * may not be able to tell if something is off to the + * right). + */ + + x = -chunkPtr->width; + } + TkTextEmbWinDisplayProc(textPtr, chunkPtr, x, + dlPtr->spaceAbove, + dlPtr->height-dlPtr->spaceAbove-dlPtr->spaceBelow, + dlPtr->baseline - dlPtr->spaceAbove, NULL, + (Drawable) None, dlPtr->y + dlPtr->spaceAbove); + } + + } + } +#ifndef TK_NO_DOUBLE_BUFFERING + Tk_FreePixmap(Tk_Display(textPtr->tkwin), pixmap); +#endif /* TK_NO_DOUBLE_BUFFERING */ + } + + /* + * See if we need to refresh the part of the window below the last line of + * text (if there is any such area). Refresh the padding area on the left + * too, since the insertion cursor might have been displayed there + * previously). + */ + + if (dInfoPtr->topOfEof > dInfoPtr->maxY) { + dInfoPtr->topOfEof = dInfoPtr->maxY; + } + if (bottomY < dInfoPtr->topOfEof) { + if (tkTextDebug) { + LOG("tk_textRedraw", "eof"); + } + + if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)) { + /* + * The widget has been deleted. Don't do anything. + */ + + goto end; + } + + Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin), + textPtr->border, dInfoPtr->x - textPtr->padX, bottomY, + dInfoPtr->maxX - (dInfoPtr->x - textPtr->padX), + dInfoPtr->topOfEof-bottomY, 0, TK_RELIEF_FLAT); + } + dInfoPtr->topOfEof = bottomY; + + /* + * Update the vertical scrollbar, if there is one. Note: it's important to + * clear REDRAW_PENDING here, just in case the scroll function does + * something that requires redisplay. + */ + + doScrollbars: + if (textPtr->flags & UPDATE_SCROLLBARS) { + textPtr->flags &= ~UPDATE_SCROLLBARS; + if (textPtr->yScrollCmd != NULL) { + GetYView(textPtr->interp, textPtr, 1); + } + + if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)) { + /* + * The widget has been deleted. Don't do anything. + */ + + goto end; + } + + /* + * Update the horizontal scrollbar, if any. + */ + + if (textPtr->xScrollCmd != NULL) { + GetXView(textPtr->interp, textPtr, 1); + } + } + + end: + Tcl_Release(interp); +} + +/* + *---------------------------------------------------------------------- + * + * TkTextEventuallyRepick -- + * + * This function is invoked whenever something happens that could change + * the current character or the tags associated with it. + * + * Results: + * None. + * + * Side effects: + * A repick is scheduled as an idle handler. + * + *---------------------------------------------------------------------- + */ + +void +TkTextEventuallyRepick( + TkText *textPtr) /* Widget record for text widget. */ +{ + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + + dInfoPtr->flags |= REPICK_NEEDED; + if (!(dInfoPtr->flags & REDRAW_PENDING)) { + dInfoPtr->flags |= REDRAW_PENDING; + Tcl_DoWhenIdle(DisplayText, textPtr); + } +} + +/* + *---------------------------------------------------------------------- + * + * TkTextRedrawRegion -- + * + * This function is invoked to schedule a redisplay for a given region of + * a text widget. The redisplay itself may not occur immediately: it's + * scheduled as a when-idle handler. + * + * Results: + * None. + * + * Side effects: + * Information will eventually be redrawn on the screen. + * + *---------------------------------------------------------------------- + */ + +void +TkTextRedrawRegion( + TkText *textPtr, /* Widget record for text widget. */ + int x, int y, /* Coordinates of upper-left corner of area to + * be redrawn, in pixels relative to textPtr's + * window. */ + int width, int height) /* Width and height of area to be redrawn. */ +{ + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + TkRegion damageRgn = TkCreateRegion(); + XRectangle rect; + + rect.x = x; + rect.y = y; + rect.width = width; + rect.height = height; + TkUnionRectWithRegion(&rect, damageRgn, damageRgn); + + TextInvalidateRegion(textPtr, damageRgn); + + if (!(dInfoPtr->flags & REDRAW_PENDING)) { + dInfoPtr->flags |= REDRAW_PENDING; + Tcl_DoWhenIdle(DisplayText, textPtr); + } + TkDestroyRegion(damageRgn); +} + +/* + *---------------------------------------------------------------------- + * + * TextInvalidateRegion -- + * + * Mark a region of text as invalid. + * + * Results: + * None. + * + * Side effects: + * Updates the display information for the text widget. + * + *---------------------------------------------------------------------- + */ + +static void +TextInvalidateRegion( + TkText *textPtr, /* Widget record for text widget. */ + TkRegion region) /* Region of area to redraw. */ +{ + register DLine *dlPtr; + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + int maxY, inset; + XRectangle rect; + + /* + * Find all lines that overlap the given region and mark them for + * redisplay. + */ + + TkClipBox(region, &rect); + maxY = rect.y + rect.height; + for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; + dlPtr = dlPtr->nextPtr) { + if ((!(dlPtr->flags & OLD_Y_INVALID)) + && (TkRectInRegion(region, rect.x, dlPtr->y, + rect.width, (unsigned int) dlPtr->height) != RectangleOut)) { + dlPtr->flags |= OLD_Y_INVALID; + } + } + if (dInfoPtr->topOfEof < maxY) { + dInfoPtr->topOfEof = maxY; + } + + /* + * Schedule the redisplay operation if there isn't one already scheduled. + */ + + inset = textPtr->borderWidth + textPtr->highlightWidth; + if ((rect.x < (inset + textPtr->padX)) + || (rect.y < (inset + textPtr->padY)) + || ((int) (rect.x + rect.width) > (Tk_Width(textPtr->tkwin) + - inset - textPtr->padX)) + || (maxY > (Tk_Height(textPtr->tkwin) - inset - textPtr->padY))) { + dInfoPtr->flags |= REDRAW_BORDERS; + } +} + +/* + *---------------------------------------------------------------------- + * + * TkTextChanged, TextChanged -- + * + * This function is invoked when info in a text widget is about to be + * modified in a way that changes how it is displayed (e.g. characters + * were inserted or deleted, or tag information was changed). This + * function must be called *before* a change is made, so that indexes in + * the display information are still valid. + * + * Note: if the range of indices may change geometry as well as simply + * requiring redisplay, then the caller should also call + * TkTextInvalidateLineMetrics. + * + * Results: + * None. + * + * Side effects: + * The range of character between index1Ptr (inclusive) and index2Ptr + * (exclusive) will be redisplayed at some point in the future (the + * actual redisplay is scheduled as a when-idle handler). + * + *---------------------------------------------------------------------- + */ + +void +TkTextChanged( + TkSharedText *sharedTextPtr,/* Shared widget section, or NULL. */ + TkText *textPtr, /* Widget record for text widget, or NULL. */ + const TkTextIndex*index1Ptr,/* Index of first character to redisplay. */ + const TkTextIndex*index2Ptr)/* Index of character just after last one to + * redisplay. */ +{ + if (sharedTextPtr == NULL) { + TextChanged(textPtr, index1Ptr, index2Ptr); + } else { + textPtr = sharedTextPtr->peers; + while (textPtr != NULL) { + TextChanged(textPtr, index1Ptr, index2Ptr); + textPtr = textPtr->next; + } + } +} + +static void +TextChanged( + TkText *textPtr, /* Widget record for text widget, or NULL. */ + const TkTextIndex*index1Ptr,/* Index of first character to redisplay. */ + const TkTextIndex*index2Ptr)/* Index of character just after last one to + * redisplay. */ +{ + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + DLine *firstPtr, *lastPtr; + TkTextIndex rounded; + TkTextLine *linePtr; + int notBegin; + + /* + * Schedule both a redisplay and a recomputation of display information. + * It's done here rather than the end of the function for two reasons: + * + * 1. If there are no display lines to update we'll want to return + * immediately, well before the end of the function. + * 2. It's important to arrange for the redisplay BEFORE calling + * FreeDLines. The reason for this is subtle and has to do with + * embedded windows. The chunk delete function for an embedded window + * will schedule an idle handler to unmap the window. However, we want + * the idle handler for redisplay to be called first, so that it can + * put the embedded window back on the screen again (if appropriate). + * This will prevent the window from ever being unmapped, and thereby + * avoid flashing. + */ + + if (!(dInfoPtr->flags & REDRAW_PENDING)) { + Tcl_DoWhenIdle(DisplayText, textPtr); + } + dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED; + + /* + * Find the DLines corresponding to index1Ptr and index2Ptr. There is one + * tricky thing here, which is that we have to relayout in units of whole + * text lines: This is necessary because the indices stored in the display + * lines will no longer be valid. It's also needed because any edit could + * change the way lines wrap. + * To relayout in units of whole text (logical) lines, round index1Ptr + * back to the beginning of its text line (or, if this line start is + * elided, to the beginning of the text line that starts the display line + * it is included in), and include all the display lines after index2Ptr, + * up to the end of its text line (or, if this line end is elided, up to + * the end of the first non elided text line after this line end). + */ + + rounded = *index1Ptr; + rounded.byteIndex = 0; + notBegin = 0; + while (!IsStartOfNotMergedLine(textPtr, &rounded) && notBegin) { + notBegin = !TkTextIndexBackBytes(textPtr, &rounded, 1, &rounded); + rounded.byteIndex = 0; + } + + /* + * 'rounded' now points to the start of a display line as well as the + * real (non elided) start of a logical line, and this index is the + * closest before index1Ptr. + */ + + firstPtr = FindDLine(textPtr, dInfoPtr->dLinePtr, &rounded); + + if (firstPtr == NULL) { + /* + * index1Ptr pertains to no display line, i.e this index is after + * the last display line. Since index2Ptr is after index1Ptr, there + * is no display line to free/redisplay and we can return early. + */ + + return; + } + + rounded = *index2Ptr; + linePtr = index2Ptr->linePtr; + do { + linePtr = TkBTreeNextLine(textPtr, linePtr); + if (linePtr == NULL) { + break; + } + rounded.linePtr = linePtr; + rounded.byteIndex = 0; + } while (!IsStartOfNotMergedLine(textPtr, &rounded)); + + if (linePtr == NULL) { + lastPtr = NULL; + } else { + /* + * 'rounded' now points to the start of a display line as well as the + * start of a logical line not merged with its previous line, and + * this index is the closest after index2Ptr. + */ + + lastPtr = FindDLine(textPtr, dInfoPtr->dLinePtr, &rounded); + + /* + * At least one display line is supposed to change. This makes the + * redisplay OK in case the display line we expect to get here was + * unlinked by a previous call to TkTextChanged and the text widget + * did not update before reaching this point. This happens for + * instance when moving the cursor up one line. + * Note that lastPtr != NULL here, otherwise we would have returned + * earlier when we tested for firstPtr being NULL. + */ + + if (lastPtr == firstPtr) { + lastPtr = lastPtr->nextPtr; + } + } + + /* + * Delete all the DLines from firstPtr up to but not including lastPtr. + */ + + FreeDLines(textPtr, firstPtr, lastPtr, DLINE_UNLINK); +} + +/* + *---------------------------------------------------------------------- + * + * TkTextRedrawTag, TextRedrawTag -- + * + * This function is invoked to request a redraw of all characters in a + * given range that have a particular tag on or off. It's called, for + * example, when tag options change. + * + * Results: + * None. + * + * Side effects: + * Information on the screen may be redrawn, and the layout of the screen + * may change. + * + *---------------------------------------------------------------------- + */ + +void +TkTextRedrawTag( + TkSharedText *sharedTextPtr,/* Shared widget section, or NULL. */ + TkText *textPtr, /* Widget record for text widget. */ + TkTextIndex *index1Ptr, /* First character in range to consider for + * redisplay. NULL means start at beginning of + * text. */ + TkTextIndex *index2Ptr, /* Character just after last one to consider + * for redisplay. NULL means process all the + * characters in the text. */ + TkTextTag *tagPtr, /* Information about tag. */ + int withTag) /* 1 means redraw characters that have the + * tag, 0 means redraw those without. */ +{ + if (sharedTextPtr == NULL) { + TextRedrawTag(textPtr, index1Ptr, index2Ptr, tagPtr, withTag); + } else { + textPtr = sharedTextPtr->peers; + while (textPtr != NULL) { + TextRedrawTag(textPtr, index1Ptr, index2Ptr, tagPtr, withTag); + textPtr = textPtr->next; + } + } +} + +static void +TextRedrawTag( + TkText *textPtr, /* Widget record for text widget. */ + TkTextIndex *index1Ptr, /* First character in range to consider for + * redisplay. NULL means start at beginning of + * text. */ + TkTextIndex *index2Ptr, /* Character just after last one to consider + * for redisplay. NULL means process all the + * characters in the text. */ + TkTextTag *tagPtr, /* Information about tag. */ + int withTag) /* 1 means redraw characters that have the + * tag, 0 means redraw those without. */ +{ + register DLine *dlPtr; + DLine *endPtr; + int tagOn; + TkTextSearch search; + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + TkTextIndex *curIndexPtr; + TkTextIndex endOfText, *endIndexPtr; + + /* + * Invalidate the pixel calculation of all lines in the given range. This + * may be a bit over-aggressive, so we could consider more subtle + * techniques here in the future. In particular, when we create a tag for + * the first time with '.t tag configure foo -font "Arial 20"', say, even + * though that obviously can't apply to anything at all (the tag didn't + * exist a moment ago), we invalidate every single line in the widget. + */ + + if (tagPtr->affectsDisplayGeometry) { + TkTextLine *startLine, *endLine; + int lineCount; + + if (index2Ptr == NULL) { + endLine = NULL; + lineCount = TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr); + } else { + endLine = index2Ptr->linePtr; + lineCount = TkBTreeLinesTo(textPtr, endLine); + } + if (index1Ptr == NULL) { + startLine = NULL; + } else { + startLine = index1Ptr->linePtr; + lineCount -= TkBTreeLinesTo(textPtr, startLine); + } + TkTextInvalidateLineMetrics(NULL, textPtr, startLine, lineCount, + TK_TEXT_INVALIDATE_ONLY); + } + + /* + * Round up the starting position if it's before the first line visible on + * the screen (we only care about what's on the screen). + */ + + dlPtr = dInfoPtr->dLinePtr; + if (dlPtr == NULL) { + return; + } + if ((index1Ptr == NULL) || (TkTextIndexCmp(&dlPtr->index, index1Ptr)>0)) { + index1Ptr = &dlPtr->index; + } + + /* + * Set the stopping position if it wasn't specified. + */ + + if (index2Ptr == NULL) { + int lastLine = TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr); + + index2Ptr = TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, + lastLine, 0, &endOfText); + } + + /* + * Initialize a search through all transitions on the tag, starting with + * the first transition where the tag's current state is different from + * what it will eventually be. + */ + + TkBTreeStartSearch(index1Ptr, index2Ptr, tagPtr, &search); + + /* + * Make our own curIndex because at this point search.curIndex may not + * equal index1Ptr->curIndex in the case the first tag toggle comes after + * index1Ptr (See the use of FindTagStart in TkBTreeStartSearch). + */ + + curIndexPtr = index1Ptr; + tagOn = TkBTreeCharTagged(index1Ptr, tagPtr); + if (tagOn != withTag) { + if (!TkBTreeNextTag(&search)) { + return; + } + curIndexPtr = &search.curIndex; + } + + /* + * Schedule a redisplay and layout recalculation if they aren't already + * pending. This has to be done before calling FreeDLines, for the reason + * given in TkTextChanged. + */ + + if (!(dInfoPtr->flags & REDRAW_PENDING)) { + Tcl_DoWhenIdle(DisplayText, textPtr); + } + dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED; + + /* + * Each loop through the loop below is for one range of characters where + * the tag's current state is different than its eventual state. At the + * top of the loop, search contains information about the first character + * in the range. + */ + + while (1) { + /* + * Find the first DLine structure in the range. Note: if the desired + * character isn't the first in its text line, then look for the + * character just before it instead. This is needed to handle the case + * where the first character of a wrapped display line just got + * smaller, so that it now fits on the line before: need to relayout + * the line containing the previous character. + */ + + if (IsStartOfNotMergedLine(textPtr, curIndexPtr)) { + dlPtr = FindDLine(textPtr, dlPtr, curIndexPtr); + } else { + TkTextIndex tmp = *curIndexPtr; + + TkTextIndexBackBytes(textPtr, &tmp, 1, &tmp); + dlPtr = FindDLine(textPtr, dlPtr, &tmp); + } + if (dlPtr == NULL) { + break; + } + + /* + * Find the first DLine structure that's past the end of the range. + */ + + if (!TkBTreeNextTag(&search)) { + endIndexPtr = index2Ptr; + } else { + curIndexPtr = &search.curIndex; + endIndexPtr = curIndexPtr; + } + endPtr = FindDLine(textPtr, dlPtr, endIndexPtr); + if ((endPtr != NULL) + && (TkTextIndexCmp(&endPtr->index,endIndexPtr) < 0)) { + endPtr = endPtr->nextPtr; + } + + /* + * Delete all of the display lines in the range, so that they'll be + * re-layed out and redrawn. + */ + + FreeDLines(textPtr, dlPtr, endPtr, DLINE_UNLINK); + dlPtr = endPtr; + + /* + * Find the first text line in the next range. + */ + + if (!TkBTreeNextTag(&search)) { + break; + } + } +} + +/* + *---------------------------------------------------------------------- + * + * TkTextRelayoutWindow -- + * + * This function is called when something has happened that invalidates + * the whole layout of characters on the screen, such as a change in a + * configuration option for the overall text widget or a change in the + * window size. It causes all display information to be recomputed and + * the window to be redrawn. + * + * Results: + * None. + * + * Side effects: + * All the display information will be recomputed for the window and the + * window will be redrawn. + * + *---------------------------------------------------------------------- + */ + +void +TkTextRelayoutWindow( + TkText *textPtr, /* Widget record for text widget. */ + int mask) /* OR'd collection of bits showing what has + * changed. */ +{ + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + GC newGC; + XGCValues gcValues; + + /* + * Schedule the window redisplay. See TkTextChanged for the reason why + * this has to be done before any calls to FreeDLines. + */ + + if (!(dInfoPtr->flags & REDRAW_PENDING)) { + Tcl_DoWhenIdle(DisplayText, textPtr); + } + dInfoPtr->flags |= REDRAW_PENDING|REDRAW_BORDERS|DINFO_OUT_OF_DATE + |REPICK_NEEDED; + + /* + * (Re-)create the graphics context for drawing the traversal highlight. + */ + + gcValues.graphics_exposures = False; + newGC = Tk_GetGC(textPtr->tkwin, GCGraphicsExposures, &gcValues); + if (dInfoPtr->copyGC != None) { + Tk_FreeGC(textPtr->display, dInfoPtr->copyGC); + } + dInfoPtr->copyGC = newGC; + + /* + * Throw away all the current layout information. + */ + + FreeDLines(textPtr, dInfoPtr->dLinePtr, NULL, DLINE_UNLINK); + dInfoPtr->dLinePtr = NULL; + + /* + * Recompute some overall things for the layout. Even if the window gets + * very small, pretend that there's at least one pixel of drawing space in + * it. + */ + + if (textPtr->highlightWidth < 0) { + textPtr->highlightWidth = 0; + } + dInfoPtr->x = textPtr->highlightWidth + textPtr->borderWidth + + textPtr->padX; + dInfoPtr->y = textPtr->highlightWidth + textPtr->borderWidth + + textPtr->padY; + dInfoPtr->maxX = Tk_Width(textPtr->tkwin) - textPtr->highlightWidth + - textPtr->borderWidth - textPtr->padX; + if (dInfoPtr->maxX <= dInfoPtr->x) { + dInfoPtr->maxX = dInfoPtr->x + 1; + } + + /* + * This is the only place where dInfoPtr->maxY is set. + */ + + dInfoPtr->maxY = Tk_Height(textPtr->tkwin) - textPtr->highlightWidth + - textPtr->borderWidth - textPtr->padY; + if (dInfoPtr->maxY <= dInfoPtr->y) { + dInfoPtr->maxY = dInfoPtr->y + 1; + } + dInfoPtr->topOfEof = dInfoPtr->maxY; + + /* + * If the upper-left character isn't the first in a line, recompute it. + * This is necessary because a change in the window's size or options + * could change the way lines wrap. + */ + + if (!IsStartOfNotMergedLine(textPtr, &textPtr->topIndex)) { + TkTextFindDisplayLineEnd(textPtr, &textPtr->topIndex, 0, NULL); + } + + /* + * Invalidate cached scrollbar positions, so that scrollbars sliders will + * be udpated. + */ + + dInfoPtr->xScrollFirst = dInfoPtr->xScrollLast = -1; + dInfoPtr->yScrollFirst = dInfoPtr->yScrollLast = -1; + + if (mask & TK_TEXT_LINE_GEOMETRY) { + /* + * Set up line metric recalculation. + * + * Avoid the special zero value, since that is used to mark individual + * lines as being out of date. + */ + + if ((++dInfoPtr->lineMetricUpdateEpoch) == 0) { + dInfoPtr->lineMetricUpdateEpoch++; + } + + dInfoPtr->currentMetricUpdateLine = -1; + + /* + * Also cancel any partial line-height calculations (for long-wrapped + * lines) in progress. + */ + + dInfoPtr->metricEpoch = -1; + + if (dInfoPtr->lineUpdateTimer == NULL) { + textPtr->refCount++; + dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1, + AsyncUpdateLineMetrics, textPtr); + GenerateWidgetViewSyncEvent(textPtr, 0); + } + } +} + +/* + *---------------------------------------------------------------------- + * + * TkTextSetYView -- + * + * This function is called to specify what lines are to be displayed in a + * text widget. + * + * Results: + * None. + * + * Side effects: + * The display will (eventually) be updated so that the position given by + * "indexPtr" is visible on the screen at the position determined by + * "pickPlace". + * + *---------------------------------------------------------------------- + */ + +void +TkTextSetYView( + TkText *textPtr, /* Widget record for text widget. */ + TkTextIndex *indexPtr, /* Position that is to appear somewhere in the + * view. */ + int pickPlace) /* 0 means the given index must appear exactly + * at the top of the screen. TK_TEXT_PICKPLACE + * (-1) means we get to pick where it appears: + * minimize screen motion or else display line + * at center of screen. TK_TEXT_NOPIXELADJUST + * (-2) indicates to make the given index the + * top line, but if it is already the top + * line, don't nudge it up or down by a few + * pixels just to make sure it is entirely + * displayed. Positive numbers indicate the + * number of pixels of the index's line which + * are to be off the top of the screen. */ +{ + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + register DLine *dlPtr; + int bottomY, close, lineIndex; + TkTextIndex tmpIndex, rounded; + int lineHeight; + + /* + * If the specified position is the extra line at the end of the text, + * round it back to the last real line. + */ + + lineIndex = TkBTreeLinesTo(textPtr, indexPtr->linePtr); + if (lineIndex == TkBTreeNumLines(indexPtr->tree, textPtr)) { + TkTextIndexBackChars(textPtr, indexPtr, 1, &rounded, COUNT_INDICES); + indexPtr = &rounded; + } + + if (pickPlace == TK_TEXT_NOPIXELADJUST) { + if (textPtr->topIndex.linePtr == indexPtr->linePtr + && textPtr->topIndex.byteIndex == indexPtr->byteIndex) { + pickPlace = dInfoPtr->topPixelOffset; + } else { + pickPlace = 0; + } + } + + if (pickPlace != TK_TEXT_PICKPLACE) { + /* + * The specified position must go at the top of the screen. Just leave + * all the DLine's alone: we may be able to reuse some of the + * information that's currently on the screen without redisplaying it + * all. + */ + + textPtr->topIndex = *indexPtr; + if (!IsStartOfNotMergedLine(textPtr, indexPtr)) { + TkTextFindDisplayLineEnd(textPtr, &textPtr->topIndex, 0, NULL); + } + dInfoPtr->newTopPixelOffset = pickPlace; + goto scheduleUpdate; + } + + /* + * We have to pick where to display the index. First, bring the display + * information up to date and see if the index will be completely visible + * in the current screen configuration. If so then there's nothing to do. + */ + + if (dInfoPtr->flags & DINFO_OUT_OF_DATE) { + UpdateDisplayInfo(textPtr); + } + dlPtr = FindDLine(textPtr, dInfoPtr->dLinePtr, indexPtr); + if (dlPtr != NULL) { + if ((dlPtr->y + dlPtr->height) > dInfoPtr->maxY) { + /* + * Part of the line hangs off the bottom of the screen; pretend + * the whole line is off-screen. + */ + + dlPtr = NULL; + } else { + if (TkTextIndexCmp(&dlPtr->index, indexPtr) <= 0) { + if (dInfoPtr->dLinePtr == dlPtr && dInfoPtr->topPixelOffset != 0) { + /* + * It is on the top line, but that line is hanging off the top + * of the screen. Change the top overlap to zero and update. + */ + + dInfoPtr->newTopPixelOffset = 0; + goto scheduleUpdate; + } + /* + * The line is already on screen, with no need to scroll. + */ + return; + } + } + } + + /* + * The desired line isn't already on-screen. Figure out what it means to + * be "close" to the top or bottom of the screen. Close means within 1/3 + * of the screen height or within three lines, whichever is greater. + * + * If the line is not close, place it in the center of the window. + */ + + tmpIndex = *indexPtr; + TkTextFindDisplayLineEnd(textPtr, &tmpIndex, 0, NULL); + lineHeight = CalculateDisplayLineHeight(textPtr, &tmpIndex, NULL, NULL); + + /* + * It would be better if 'bottomY' were calculated using the actual height + * of the given line, not 'textPtr->charHeight'. + */ + + bottomY = (dInfoPtr->y + dInfoPtr->maxY + lineHeight)/2; + close = (dInfoPtr->maxY - dInfoPtr->y)/3; + if (close < 3*textPtr->charHeight) { + close = 3*textPtr->charHeight; + } + if (dlPtr != NULL) { + int overlap; + + /* + * The desired line is above the top of screen. If it is "close" to + * the top of the window then make it the top line on the screen. + * MeasureUp counts from the bottom of the given index upwards, so we + * add an extra half line to be sure we count far enough. + */ + + MeasureUp(textPtr, &textPtr->topIndex, close + textPtr->charHeight/2, + &tmpIndex, &overlap); + if (TkTextIndexCmp(&tmpIndex, indexPtr) <= 0) { + textPtr->topIndex = *indexPtr; + TkTextFindDisplayLineEnd(textPtr, &textPtr->topIndex, 0, NULL); + dInfoPtr->newTopPixelOffset = 0; + goto scheduleUpdate; + } + } else { + int overlap; + + /* + * The desired line is below the bottom of the screen. If it is + * "close" to the bottom of the screen then position it at the bottom + * of the screen. + */ + + MeasureUp(textPtr, indexPtr, close + lineHeight + - textPtr->charHeight/2, &tmpIndex, &overlap); + if (FindDLine(textPtr, dInfoPtr->dLinePtr, &tmpIndex) != NULL) { + bottomY = dInfoPtr->maxY - dInfoPtr->y; + } + } + + /* + * If the window height is smaller than the line height, prefer to make + * the top of the line visible. + */ + + if (dInfoPtr->maxY - dInfoPtr->y < lineHeight) { + bottomY = lineHeight; + } + + /* + * Our job now is to arrange the display so that indexPtr appears as low + * on the screen as possible but with its bottom no lower than bottomY. + * BottomY is the bottom of the window if the desired line is just below + * the current screen, otherwise it is a half-line lower than the center + * of the window. + */ + + MeasureUp(textPtr, indexPtr, bottomY, &textPtr->topIndex, + &dInfoPtr->newTopPixelOffset); + + scheduleUpdate: + if (!(dInfoPtr->flags & REDRAW_PENDING)) { + Tcl_DoWhenIdle(DisplayText, textPtr); + } + dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED; +} + +/* + *-------------------------------------------------------------- + * + * TkTextMeasureDown -- + * + * Given one index, find the index of the first character on the highest + * display line that would be displayed no more than "distance" pixels + * below the top of the given index. + * + * Results: + * The srcPtr is manipulated in place to reflect the new position. We + * return the number of pixels by which 'distance' overlaps the srcPtr. + * + * Side effects: + * None. + * + *-------------------------------------------------------------- + */ + +int +TkTextMeasureDown( + TkText *textPtr, /* Text widget in which to measure. */ + TkTextIndex *srcPtr, /* Index of character from which to start + * measuring. */ + int distance) /* Vertical distance in pixels measured from + * the top pixel in srcPtr's logical line. */ +{ + TkTextLine *lastLinePtr; + DLine *dlPtr; + TkTextIndex loop; + + lastLinePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr, + TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr)); + + do { + dlPtr = LayoutDLine(textPtr, srcPtr); + dlPtr->nextPtr = NULL; + + if (distance < dlPtr->height) { + FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP); + break; + } + distance -= dlPtr->height; + TkTextIndexForwBytes(textPtr, srcPtr, dlPtr->byteCount, &loop); + FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP); + if (loop.linePtr == lastLinePtr) { + break; + } + *srcPtr = loop; + } while (distance > 0); + + return distance; +} + +/* + *-------------------------------------------------------------- + * + * MeasureUp -- + * + * Given one index, find the index of the first character on the highest + * display line that would be displayed no more than "distance" pixels + * above the given index. + * + * If this function is called with distance=0, it simply finds the first + * index on the same display line as srcPtr. However, there is a another + * function TkTextFindDisplayLineEnd designed just for that task which is + * probably better to use. + * + * Results: + * *dstPtr is filled in with the index of the first character on a + * display line. The display line is found by measuring up "distance" + * pixels above the pixel just below an imaginary display line that + * contains srcPtr. If the display line that covers this coordinate + * actually extends above the coordinate, then return any excess pixels + * in *overlap, if that is non-NULL. + * + * Side effects: + * None. + * + *-------------------------------------------------------------- + */ + +static void +MeasureUp( + TkText *textPtr, /* Text widget in which to measure. */ + const TkTextIndex *srcPtr, /* Index of character from which to start + * measuring. */ + int distance, /* Vertical distance in pixels measured from + * the pixel just below the lowest one in + * srcPtr's line. */ + TkTextIndex *dstPtr, /* Index to fill in with result. */ + int *overlap) /* Used to store how much of the final index + * returned was not covered by 'distance'. */ +{ + int lineNum; /* Number of current line. */ + int bytesToCount; /* Maximum number of bytes to measure in + * current line. */ + TkTextIndex index; + DLine *dlPtr, *lowestPtr; + + bytesToCount = srcPtr->byteIndex + 1; + index.tree = srcPtr->tree; + for (lineNum = TkBTreeLinesTo(textPtr, srcPtr->linePtr); lineNum >= 0; + lineNum--) { + /* + * Layout an entire text line (potentially > 1 display line). + * + * For the first line, which contains srcPtr, only layout the part up + * through srcPtr (bytesToCount is non-infinite to accomplish this). + * Make a list of all the display lines in backwards order (the lowest + * DLine on the screen is first in the list). + */ + + index.linePtr = TkBTreeFindLine(srcPtr->tree, textPtr, lineNum); + index.byteIndex = 0; + TkTextFindDisplayLineEnd(textPtr, &index, 0, NULL); + lineNum = TkBTreeLinesTo(textPtr, index.linePtr); + lowestPtr = NULL; + do { + dlPtr = LayoutDLine(textPtr, &index); + dlPtr->nextPtr = lowestPtr; + lowestPtr = dlPtr; + TkTextIndexForwBytes(textPtr, &index, dlPtr->byteCount, &index); + bytesToCount -= dlPtr->byteCount; + } while (bytesToCount>0 && index.linePtr==dlPtr->index.linePtr); + + /* + * Scan through the display lines to see if we've covered enough + * vertical distance. If so, save the starting index for the line at + * the desired location. If distance was zero to start with then we + * simply get the first index on the same display line as the original + * index. + */ + + for (dlPtr = lowestPtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) { + distance -= dlPtr->height; + if (distance <= 0) { + *dstPtr = dlPtr->index; + + /* + * dstPtr is the start of a display line that is or is not + * the start of a logical line. If it is the start of a + * logical line, we must check whether this line is merged + * with the previous logical line, and if so we must adjust + * dstPtr to the start of the display line since a display + * line start needs to be returned. + */ + if (!IsStartOfNotMergedLine(textPtr, dstPtr)) { + TkTextFindDisplayLineEnd(textPtr, dstPtr, 0, NULL); + } + + if (overlap != NULL) { + *overlap = -distance; + } + break; + } + } + + /* + * Discard the display lines, then either return or prepare for the + * next display line to lay out. + */ + + FreeDLines(textPtr, lowestPtr, NULL, DLINE_FREE); + if (distance <= 0) { + return; + } + bytesToCount = INT_MAX; /* Consider all chars. in next line. */ + } + + /* + * Ran off the beginning of the text. Return the first character in the + * text. + */ + + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, 0, 0, dstPtr); + if (overlap != NULL) { + *overlap = 0; + } +} + +/* + *-------------------------------------------------------------- + * + * TkTextSeeCmd -- + * + * This function is invoked to process the "see" option for the widget + * command for text widgets. See the user documentation for details on + * what it does. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * See the user documentation. + * + *-------------------------------------------------------------- + */ + +int +TkTextSeeCmd( + TkText *textPtr, /* Information about text widget. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument objects. Someone else has already + * parsed this command enough to know that + * objv[1] is "see". */ +{ + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + TkTextIndex index; + int x, y, width, height, lineWidth, byteCount, oneThird, delta; + DLine *dlPtr; + TkTextDispChunk *chunkPtr; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "index"); + return TCL_ERROR; + } + if (TkTextGetObjIndex(interp, textPtr, objv[2], &index) != TCL_OK) { + return TCL_ERROR; + } + + /* + * If the specified position is the extra line at the end of the text, + * round it back to the last real line. + */ + + if (TkBTreeLinesTo(textPtr, index.linePtr) + == TkBTreeNumLines(index.tree, textPtr)) { + TkTextIndexBackChars(textPtr, &index, 1, &index, COUNT_INDICES); + } + + /* + * First get the desired position into the vertical range of the window. + */ + + TkTextSetYView(textPtr, &index, TK_TEXT_PICKPLACE); + + /* + * Now make sure that the character is in view horizontally. + */ + + if (dInfoPtr->flags & DINFO_OUT_OF_DATE) { + UpdateDisplayInfo(textPtr); + } + lineWidth = dInfoPtr->maxX - dInfoPtr->x; + if (dInfoPtr->maxLength < lineWidth) { + return TCL_OK; + } + + /* + * Find the display line containing the desired index. dlPtr may be NULL + * if the widget is not mapped. [Bug #641778] + */ + + dlPtr = FindDLine(textPtr, dInfoPtr->dLinePtr, &index); + if (dlPtr == NULL) { + return TCL_OK; + } + + /* + * Find the chunk within the display line that contains the desired + * index. The chunks making the display line are skipped up to but not + * including the one crossing index. Skipping is done based on a + * byteCount offset possibly spanning several logical lines in case + * they are elided. + */ + + byteCount = TkTextIndexCountBytes(textPtr, &dlPtr->index, &index); + for (chunkPtr = dlPtr->chunkPtr; chunkPtr != NULL ; + chunkPtr = chunkPtr->nextPtr) { + if (byteCount < chunkPtr->numBytes) { + break; + } + byteCount -= chunkPtr->numBytes; + } + + /* + * Call a chunk-specific function to find the horizontal range of the + * character within the chunk. chunkPtr is NULL if trying to see in elided + * region. + */ + + if (chunkPtr != NULL) { + chunkPtr->bboxProc(textPtr, chunkPtr, byteCount, + dlPtr->y + dlPtr->spaceAbove, + dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow, + dlPtr->baseline - dlPtr->spaceAbove, &x, &y, &width, + &height); + delta = x - dInfoPtr->curXPixelOffset; + oneThird = lineWidth/3; + if (delta < 0) { + if (delta < -oneThird) { + dInfoPtr->newXPixelOffset = x - lineWidth/2; + } else { + dInfoPtr->newXPixelOffset += delta; + } + } else { + delta -= lineWidth - width; + if (delta <= 0) { + return TCL_OK; + } + if (delta > oneThird) { + dInfoPtr->newXPixelOffset = x - lineWidth/2; + } else { + dInfoPtr->newXPixelOffset += delta; + } + } + } + dInfoPtr->flags |= DINFO_OUT_OF_DATE; + if (!(dInfoPtr->flags & REDRAW_PENDING)) { + dInfoPtr->flags |= REDRAW_PENDING; + Tcl_DoWhenIdle(DisplayText, textPtr); + } + return TCL_OK; +} + +/* + *-------------------------------------------------------------- + * + * TkTextXviewCmd -- + * + * This function is invoked to process the "xview" option for the widget + * command for text widgets. See the user documentation for details on + * what it does. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * See the user documentation. + * + *-------------------------------------------------------------- + */ + +int +TkTextXviewCmd( + TkText *textPtr, /* Information about text widget. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument objects. Someone else has already + * parsed this command enough to know that + * objv[1] is "xview". */ +{ + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + int type, count; + double fraction; + + if (dInfoPtr->flags & DINFO_OUT_OF_DATE) { + UpdateDisplayInfo(textPtr); + } + + if (objc == 2) { + GetXView(interp, textPtr, 0); + return TCL_OK; + } + + type = TextGetScrollInfoObj(interp, textPtr, objc, objv, + &fraction, &count); + switch (type) { + case TKTEXT_SCROLL_ERROR: + return TCL_ERROR; + case TKTEXT_SCROLL_MOVETO: + if (fraction > 1.0) { + fraction = 1.0; + } + if (fraction < 0) { + fraction = 0; + } + dInfoPtr->newXPixelOffset = (int) + (fraction * dInfoPtr->maxLength + 0.5); + break; + case TKTEXT_SCROLL_PAGES: { + int pixelsPerPage; + + pixelsPerPage = (dInfoPtr->maxX-dInfoPtr->x) - 2*textPtr->charWidth; + if (pixelsPerPage < 1) { + pixelsPerPage = 1; + } + dInfoPtr->newXPixelOffset += pixelsPerPage * count; + break; + } + case TKTEXT_SCROLL_UNITS: + dInfoPtr->newXPixelOffset += count * textPtr->charWidth; + break; + case TKTEXT_SCROLL_PIXELS: + dInfoPtr->newXPixelOffset += count; + break; + } + + dInfoPtr->flags |= DINFO_OUT_OF_DATE; + if (!(dInfoPtr->flags & REDRAW_PENDING)) { + dInfoPtr->flags |= REDRAW_PENDING; + Tcl_DoWhenIdle(DisplayText, textPtr); + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * YScrollByPixels -- + * + * This function is called to scroll a text widget up or down by a given + * number of pixels. + * + * Results: + * None. + * + * Side effects: + * The view in textPtr's window changes to reflect the value of "offset". + * + *---------------------------------------------------------------------- + */ + +static void +YScrollByPixels( + TkText *textPtr, /* Widget to scroll. */ + int offset) /* Amount by which to scroll, in pixels. + * Positive means that information later in + * text becomes visible, negative means that + * information earlier in the text becomes + * visible. */ +{ + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + + if (offset < 0) { + /* + * Now we want to measure up this number of pixels from the top of the + * screen. But the top line may not be totally visible. Note that + * 'count' is negative here. + */ + + offset -= CalculateDisplayLineHeight(textPtr, + &textPtr->topIndex, NULL, NULL) - dInfoPtr->topPixelOffset; + MeasureUp(textPtr, &textPtr->topIndex, -offset, + &textPtr->topIndex, &dInfoPtr->newTopPixelOffset); + } else if (offset > 0) { + DLine *dlPtr; + TkTextLine *lastLinePtr; + TkTextIndex newIdx; + + /* + * Scrolling down by pixels. Layout lines starting at the top index + * and count through the desired vertical distance. + */ + + lastLinePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr, + TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr)); + offset += dInfoPtr->topPixelOffset; + dInfoPtr->newTopPixelOffset = 0; + while (offset > 0) { + dlPtr = LayoutDLine(textPtr, &textPtr->topIndex); + dlPtr->nextPtr = NULL; + TkTextIndexForwBytes(textPtr, &textPtr->topIndex, + dlPtr->byteCount, &newIdx); + if (offset <= dlPtr->height) { + /* + * Adjust the top overlap accordingly. + */ + + dInfoPtr->newTopPixelOffset = offset; + } + offset -= dlPtr->height; + FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP); + if (newIdx.linePtr == lastLinePtr || offset <= 0) { + break; + } + textPtr->topIndex = newIdx; + } + } else { + /* + * offset = 0, so no scrolling required. + */ + + return; + } + if (!(dInfoPtr->flags & REDRAW_PENDING)) { + Tcl_DoWhenIdle(DisplayText, textPtr); + } + dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED; +} + +/* + *---------------------------------------------------------------------- + * + * YScrollByLines -- + * + * This function is called to scroll a text widget up or down by a given + * number of lines. + * + * Results: + * None. + * + * Side effects: + * The view in textPtr's window changes to reflect the value of "offset". + * + *---------------------------------------------------------------------- + */ + +static void +YScrollByLines( + TkText *textPtr, /* Widget to scroll. */ + int offset) /* Amount by which to scroll, in display + * lines. Positive means that information + * later in text becomes visible, negative + * means that information earlier in the text + * becomes visible. */ +{ + int i, bytesToCount, lineNum; + TkTextIndex newIdx, index; + TkTextLine *lastLinePtr; + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + DLine *dlPtr, *lowestPtr; + + if (offset < 0) { + /* + * Must scroll up (to show earlier information in the text). The code + * below is similar to that in MeasureUp, except that it counts lines + * instead of pixels. + */ + + bytesToCount = textPtr->topIndex.byteIndex + 1; + index.tree = textPtr->sharedTextPtr->tree; + offset--; /* Skip line containing topIndex. */ + for (lineNum = TkBTreeLinesTo(textPtr, textPtr->topIndex.linePtr); + lineNum >= 0; lineNum--) { + index.linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, + textPtr, lineNum); + index.byteIndex = 0; + lowestPtr = NULL; + do { + dlPtr = LayoutDLine(textPtr, &index); + dlPtr->nextPtr = lowestPtr; + lowestPtr = dlPtr; + TkTextIndexForwBytes(textPtr, &index, dlPtr->byteCount,&index); + bytesToCount -= dlPtr->byteCount; + } while ((bytesToCount > 0) + && (index.linePtr == dlPtr->index.linePtr)); + + for (dlPtr = lowestPtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) { + offset++; + if (offset == 0) { + textPtr->topIndex = dlPtr->index; + + /* + * topIndex is the start of a logical line. However, if + * the eol of the previous logical line is elided, then + * topIndex may be elsewhere than the first character of + * a display line, which is unwanted. Adjust to the start + * of the display line, if needed. + * topIndex is the start of a display line that is or is + * not the start of a logical line. If it is the start of + * a logical line, we must check whether this line is + * merged with the previous logical line, and if so we + * must adjust topIndex to the start of the display line. + */ + if (!IsStartOfNotMergedLine(textPtr, &textPtr->topIndex)) { + TkTextFindDisplayLineEnd(textPtr, &textPtr->topIndex, + 0, NULL); + } + + break; + } + } + + /* + * Discard the display lines, then either return or prepare for + * the next display line to lay out. + */ + + FreeDLines(textPtr, lowestPtr, NULL, DLINE_FREE); + if (offset >= 0) { + goto scheduleUpdate; + } + bytesToCount = INT_MAX; + } + + /* + * Ran off the beginning of the text. Return the first character in + * the text, and make sure we haven't left anything overlapping the + * top window border. + */ + + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, 0, 0, + &textPtr->topIndex); + dInfoPtr->newTopPixelOffset = 0; + } else { + /* + * Scrolling down, to show later information in the text. Just count + * lines from the current top of the window. + */ + + lastLinePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr, + TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr)); + for (i = 0; i < offset; i++) { + dlPtr = LayoutDLine(textPtr, &textPtr->topIndex); + if (dlPtr->length == 0 && dlPtr->height == 0) { + offset++; + } + dlPtr->nextPtr = NULL; + TkTextIndexForwBytes(textPtr, &textPtr->topIndex, + dlPtr->byteCount, &newIdx); + FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE); + if (newIdx.linePtr == lastLinePtr) { + break; + } + textPtr->topIndex = newIdx; + } + } + + scheduleUpdate: + if (!(dInfoPtr->flags & REDRAW_PENDING)) { + Tcl_DoWhenIdle(DisplayText, textPtr); + } + dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED; +} + +/* + *-------------------------------------------------------------- + * + * TkTextYviewCmd -- + * + * This function is invoked to process the "yview" option for the widget + * command for text widgets. See the user documentation for details on + * what it does. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * See the user documentation. + * + *-------------------------------------------------------------- + */ + +int +TkTextYviewCmd( + TkText *textPtr, /* Information about text widget. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument objects. Someone else has already + * parsed this command enough to know that + * objv[1] is "yview". */ +{ + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + int pickPlace, type; + int pixels, count; + int switchLength; + double fraction; + TkTextIndex index; + + if (dInfoPtr->flags & DINFO_OUT_OF_DATE) { + UpdateDisplayInfo(textPtr); + } + + if (objc == 2) { + GetYView(interp, textPtr, 0); + return TCL_OK; + } + + /* + * Next, handle the old syntax: "pathName yview ?-pickplace? where" + */ + + pickPlace = 0; + if (Tcl_GetString(objv[2])[0] == '-') { + register const char *switchStr = + Tcl_GetStringFromObj(objv[2], &switchLength); + + if ((switchLength >= 2) && (strncmp(switchStr, "-pickplace", + (unsigned) switchLength) == 0)) { + pickPlace = 1; + if (objc != 4) { + Tcl_WrongNumArgs(interp, 3, objv, "lineNum|index"); + return TCL_ERROR; + } + } + } + if ((objc == 3) || pickPlace) { + int lineNum; + + if (Tcl_GetIntFromObj(interp, objv[2+pickPlace], &lineNum) == TCL_OK) { + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, + lineNum, 0, &index); + TkTextSetYView(textPtr, &index, 0); + return TCL_OK; + } + + /* + * The argument must be a regular text index. + */ + + Tcl_ResetResult(interp); + if (TkTextGetObjIndex(interp, textPtr, objv[2+pickPlace], + &index) != TCL_OK) { + return TCL_ERROR; + } + TkTextSetYView(textPtr, &index, (pickPlace ? TK_TEXT_PICKPLACE : 0)); + return TCL_OK; + } + + /* + * New syntax: dispatch based on objv[2]. + */ + + type = TextGetScrollInfoObj(interp, textPtr, objc,objv, &fraction, &count); + switch (type) { + case TKTEXT_SCROLL_ERROR: + return TCL_ERROR; + case TKTEXT_SCROLL_MOVETO: { + int numPixels = TkBTreeNumPixels(textPtr->sharedTextPtr->tree, + textPtr); + int topMostPixel; + + if (numPixels == 0) { + /* + * If the window is totally empty no scrolling is needed, and the + * TkTextMakePixelIndex call below will fail. + */ + + break; + } + if (fraction > 1.0) { + fraction = 1.0; + } + if (fraction < 0) { + fraction = 0; + } + + /* + * Calculate the pixel count for the new topmost pixel in the topmost + * line of the window. Note that the interpretation of 'fraction' is + * that it counts from 0 (top pixel in buffer) to 1.0 (one pixel past + * the last pixel in buffer). + */ + + topMostPixel = (int) (0.5 + fraction * numPixels); + if (topMostPixel >= numPixels) { + topMostPixel = numPixels -1; + } + + /* + * This function returns the number of pixels by which the given line + * should overlap the top of the visible screen. + * + * This is then used to provide smooth scrolling. + */ + + pixels = TkTextMakePixelIndex(textPtr, topMostPixel, &index); + TkTextSetYView(textPtr, &index, pixels); + break; + } + case TKTEXT_SCROLL_PAGES: { + /* + * Scroll up or down by screenfuls. Actually, use the window height + * minus two lines, so that there's some overlap between adjacent + * pages. + */ + + int height = dInfoPtr->maxY - dInfoPtr->y; + + if (textPtr->charHeight * 4 >= height) { + /* + * A single line is more than a quarter of the display. We choose + * to scroll by 3/4 of the height instead. + */ + + pixels = 3*height/4; + if (pixels < textPtr->charHeight) { + /* + * But, if 3/4 of the height is actually less than a single + * typical character height, then scroll by the minimum of the + * linespace or the total height. + */ + + if (textPtr->charHeight < height) { + pixels = textPtr->charHeight; + } else { + pixels = height; + } + } + pixels *= count; + } else { + pixels = (height - 2*textPtr->charHeight)*count; + } + YScrollByPixels(textPtr, pixels); + break; + } + case TKTEXT_SCROLL_PIXELS: + YScrollByPixels(textPtr, count); + break; + case TKTEXT_SCROLL_UNITS: + YScrollByLines(textPtr, count); + break; + } + return TCL_OK; +} + +/* + *-------------------------------------------------------------- + * + * TkTextPendingsync -- + * + * This function checks if any line heights are not up-to-date. + * + * Results: + * Returns a boolean true if it is the case, or false if all line + * heights are up-to-date. + * + * Side effects: + * None. + * + *-------------------------------------------------------------- + */ + +Bool +TkTextPendingsync( + TkText *textPtr) /* Information about text widget. */ +{ + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + + return ( + ((dInfoPtr->metricEpoch == -1) && + (dInfoPtr->lastMetricUpdateLine == dInfoPtr->currentMetricUpdateLine)) ? + 0 : 1); +} + +/* + *-------------------------------------------------------------- + * + * TkTextScanCmd -- + * + * This function is invoked to process the "scan" option for the widget + * command for text widgets. See the user documentation for details on + * what it does. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * See the user documentation. + * + *-------------------------------------------------------------- + */ + +int +TkTextScanCmd( + register TkText *textPtr, /* Information about text widget. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument objects. Someone else has already + * parsed this command enough to know that + * objv[1] is "scan". */ +{ + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + TkTextIndex index; + int c, x, y, totalScroll, gain=10; + size_t length; + + if ((objc != 5) && (objc != 6)) { + Tcl_WrongNumArgs(interp, 2, objv, "mark x y"); + Tcl_AppendResult(interp, " or \"", Tcl_GetString(objv[0]), + " scan dragto x y ?gain?\"", NULL); + /* + * Ought to be: + * Tcl_WrongNumArgs(interp, 2, objc, "dragto x y ?gain?"); + */ + return TCL_ERROR; + } + if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK) { + return TCL_ERROR; + } + if (Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) { + return TCL_ERROR; + } + if ((objc == 6) && (Tcl_GetIntFromObj(interp, objv[5], &gain) != TCL_OK)) { + return TCL_ERROR; + } + c = Tcl_GetString(objv[2])[0]; + length = strlen(Tcl_GetString(objv[2])); + if (c=='d' && strncmp(Tcl_GetString(objv[2]), "dragto", length)==0) { + int newX, maxX; + + /* + * Amplify the difference between the current position and the mark + * position to compute how much the view should shift, then update the + * mark position to correspond to the new view. If we run off the edge + * of the text, reset the mark point so that the current position + * continues to correspond to the edge of the window. This means that + * the picture will start dragging as soon as the mouse reverses + * direction (without this reset, might have to slide mouse a long + * ways back before the picture starts moving again). + */ + + newX = dInfoPtr->scanMarkXPixel + gain*(dInfoPtr->scanMarkX - x); + maxX = 1 + dInfoPtr->maxLength - (dInfoPtr->maxX - dInfoPtr->x); + if (newX < 0) { + newX = 0; + dInfoPtr->scanMarkXPixel = 0; + dInfoPtr->scanMarkX = x; + } else if (newX > maxX) { + newX = maxX; + dInfoPtr->scanMarkXPixel = maxX; + dInfoPtr->scanMarkX = x; + } + dInfoPtr->newXPixelOffset = newX; + + totalScroll = gain*(dInfoPtr->scanMarkY - y); + if (totalScroll != dInfoPtr->scanTotalYScroll) { + index = textPtr->topIndex; + YScrollByPixels(textPtr, totalScroll-dInfoPtr->scanTotalYScroll); + dInfoPtr->scanTotalYScroll = totalScroll; + if ((index.linePtr == textPtr->topIndex.linePtr) && + (index.byteIndex == textPtr->topIndex.byteIndex)) { + dInfoPtr->scanTotalYScroll = 0; + dInfoPtr->scanMarkY = y; + } + } + dInfoPtr->flags |= DINFO_OUT_OF_DATE; + if (!(dInfoPtr->flags & REDRAW_PENDING)) { + dInfoPtr->flags |= REDRAW_PENDING; + Tcl_DoWhenIdle(DisplayText, textPtr); + } + } else if (c=='m' && strncmp(Tcl_GetString(objv[2]), "mark", length)==0) { + dInfoPtr->scanMarkXPixel = dInfoPtr->newXPixelOffset; + dInfoPtr->scanMarkX = x; + dInfoPtr->scanTotalYScroll = 0; + dInfoPtr->scanMarkY = y; + } else { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "bad scan option \"%s\": must be mark or dragto", + Tcl_GetString(objv[2]))); + Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "INDEX", "scan option", + Tcl_GetString(objv[2]), NULL); + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * GetXView -- + * + * This function computes the fractions that indicate what's visible in a + * text window and, optionally, evaluates a Tcl script to report them to + * the text's associated scrollbar. + * + * Results: + * If report is zero, then the interp's result is filled in with two real + * numbers separated by a space, giving the position of the left and + * right edges of the window as fractions from 0 to 1, where 0 means the + * left edge of the text and 1 means the right edge. If report is + * non-zero, then the interp's result isn't modified directly, but + * instead a script is evaluated in interp to report the new horizontal + * scroll position to the scrollbar (if the scroll position hasn't + * changed then no script is invoked). + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static void +GetXView( + Tcl_Interp *interp, /* If "report" is FALSE, string describing + * visible range gets stored in the interp's + * result. */ + TkText *textPtr, /* Information about text widget. */ + int report) /* Non-zero means report info to scrollbar if + * it has changed. */ +{ + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + double first, last; + int code; + Tcl_Obj *listObj; + + if (dInfoPtr->maxLength > 0) { + first = ((double) dInfoPtr->curXPixelOffset) + / dInfoPtr->maxLength; + last = ((double) (dInfoPtr->curXPixelOffset + dInfoPtr->maxX + - dInfoPtr->x))/dInfoPtr->maxLength; + if (last > 1.0) { + last = 1.0; + } + } else { + first = 0; + last = 1.0; + } + if (!report) { + listObj = Tcl_NewListObj(0, NULL); + Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(first)); + Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(last)); + Tcl_SetObjResult(interp, listObj); + return; + } + if (FP_EQUAL_SCALE(first, dInfoPtr->xScrollFirst, dInfoPtr->maxLength) && + FP_EQUAL_SCALE(last, dInfoPtr->xScrollLast, dInfoPtr->maxLength)) { + return; + } + dInfoPtr->xScrollFirst = first; + dInfoPtr->xScrollLast = last; + if (textPtr->xScrollCmd != NULL) { + char buf1[TCL_DOUBLE_SPACE+1]; + char buf2[TCL_DOUBLE_SPACE+1]; + Tcl_DString buf; + + buf1[0] = ' '; + buf2[0] = ' '; + Tcl_PrintDouble(NULL, first, buf1+1); + Tcl_PrintDouble(NULL, last, buf2+1); + Tcl_DStringInit(&buf); + Tcl_DStringAppend(&buf, textPtr->xScrollCmd, -1); + Tcl_DStringAppend(&buf, buf1, -1); + Tcl_DStringAppend(&buf, buf2, -1); + code = Tcl_EvalEx(interp, Tcl_DStringValue(&buf), -1, 0); + Tcl_DStringFree(&buf); + if (code != TCL_OK) { + Tcl_AddErrorInfo(interp, + "\n (horizontal scrolling command executed by text)"); + Tcl_BackgroundException(interp, code); + } + } +} + +/* + *---------------------------------------------------------------------- + * + * GetYPixelCount -- + * + * How many pixels are there between the absolute top of the widget and + * the top of the given DLine. + * + * While this function will work for any valid DLine, it is only ever + * called when dlPtr is the first display line in the widget (by + * 'GetYView'). This means that usually this function is a very quick + * calculation, since it can use the pre-calculated linked-list of DLines + * for height information. + * + * The only situation where this breaks down is if dlPtr's logical line + * wraps enough times to fill the text widget's current view - in this + * case we won't have enough dlPtrs in the linked list to be able to + * subtract off what we want. + * + * Results: + * The number of pixels. + * + * This value has a valid range between '0' (the very top of the widget) + * and the number of pixels in the total widget minus the pixel-height of + * the last line. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +GetYPixelCount( + TkText *textPtr, /* Information about text widget. */ + DLine *dlPtr) /* Information about the layout of a given + * index. */ +{ + TkTextLine *linePtr = dlPtr->index.linePtr; + int count; + + /* + * Get the pixel count to the top of dlPtr's logical line. The rest of the + * function is then concerned with updating 'count' for any difference + * between the top of the logical line and the display line. + */ + + count = TkBTreePixelsTo(textPtr, linePtr); + + /* + * For the common case where this dlPtr is also the start of the logical + * line, we can return right away. + */ + + if (IsStartOfNotMergedLine(textPtr, &dlPtr->index)) { + return count; + } + + /* + * Add on the logical line's height to reach one pixel beyond the bottom + * of the logical line. And then subtract off the heights of all the + * display lines from dlPtr to the end of its logical line. + * + * A different approach would be to lay things out from the start of the + * logical line until we reach dlPtr, but since none of those are + * pre-calculated, it'll usually take a lot longer. (But there are cases + * where it would be more efficient: say if we're on the second of 1000 + * wrapped lines all from a single logical line - but that sort of + * optimization is left for the future). + */ + + count += TkBTreeLinePixelCount(textPtr, linePtr); + + do { + count -= dlPtr->height; + if (dlPtr->nextPtr == NULL) { + /* + * We've run out of pre-calculated display lines, so we have to + * lay them out ourselves until the end of the logical line. Here + * is where we could be clever and ask: what's faster, to layout + * all lines from here to line-end, or all lines from the original + * dlPtr to the line-start? We just assume the former. + */ + + TkTextIndex index; + int notFirst = 0; + + while (1) { + TkTextIndexForwBytes(textPtr, &dlPtr->index, + dlPtr->byteCount, &index); + if (notFirst) { + FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP); + } + if (index.linePtr != linePtr) { + break; + } + dlPtr = LayoutDLine(textPtr, &index); + + if (tkTextDebug) { + char string[TK_POS_CHARS]; + + /* + * Debugging is enabled, so keep a log of all the lines + * whose height was recalculated. The test suite uses this + * information. + */ + + TkTextPrintIndex(textPtr, &index, string); + LOG("tk_textHeightCalc", string); + } + count -= dlPtr->height; + notFirst = 1; + } + break; + } + dlPtr = dlPtr->nextPtr; + } while (dlPtr->index.linePtr == linePtr); + + return count; +} + +/* + *---------------------------------------------------------------------- + * + * GetYView -- + * + * This function computes the fractions that indicate what's visible in a + * text window and, optionally, evaluates a Tcl script to report them to + * the text's associated scrollbar. + * + * Results: + * If report is zero, then the interp's result is filled in with two real + * numbers separated by a space, giving the position of the top and + * bottom of the window as fractions from 0 to 1, where 0 means the + * beginning of the text and 1 means the end. If report is non-zero, then + * the interp's result isn't modified directly, but a script is evaluated + * in interp to report the new scroll position to the scrollbar (if the + * scroll position hasn't changed then no script is invoked). + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static void +GetYView( + Tcl_Interp *interp, /* If "report" is FALSE, string describing + * visible range gets stored in the interp's + * result. */ + TkText *textPtr, /* Information about text widget. */ + int report) /* Non-zero means report info to scrollbar if + * it has changed. */ +{ + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + double first, last; + DLine *dlPtr; + int totalPixels, code, count; + Tcl_Obj *listObj; + + dlPtr = dInfoPtr->dLinePtr; + + if (dlPtr == NULL) { + return; + } + + totalPixels = TkBTreeNumPixels(textPtr->sharedTextPtr->tree, textPtr); + + if (totalPixels == 0) { + first = 0.0; + last = 1.0; + } else { + /* + * Get the pixel count for the first visible pixel of the first + * visible line. If the first visible line is only partially visible, + * then we use 'topPixelOffset' to get the difference. + */ + + count = GetYPixelCount(textPtr, dlPtr); + first = (count + dInfoPtr->topPixelOffset) / (double) totalPixels; + + /* + * Add on the total number of visible pixels to get the count to one + * pixel _past_ the last visible pixel. This is how the 'yview' + * command is documented, and also explains why we are dividing by + * 'totalPixels' and not 'totalPixels-1'. + */ + + while (1) { + int extra; + + count += dlPtr->height; + extra = dlPtr->y + dlPtr->height - dInfoPtr->maxY; + if (extra > 0) { + /* + * This much of the last line is not visible, so don't count + * these pixels. Since we've reached the bottom of the window, + * we break out of the loop. + */ + + count -= extra; + break; + } + if (dlPtr->nextPtr == NULL) { + break; + } + dlPtr = dlPtr->nextPtr; + } + + if (count > totalPixels) { + /* + * It can be possible, if we do not update each line's pixelHeight + * cache when we lay out individual DLines that the count + * generated here is more up-to-date than that maintained by the + * BTree. In such a case, the best we can do here is to fix up + * 'count' and continue, which might result in small, temporary + * perturbations to the size of the scrollbar. This is basically + * harmless, but in a perfect world we would not have this + * problem. + * + * For debugging purposes, if anyone wishes to improve the text + * widget further, the following 'panic' can be activated. In + * principle it should be possible to ensure the BTree is always + * at least as up to date as the display, so in the future we + * might be able to leave the 'panic' in permanently when we + * believe we have resolved the cache synchronisation issue. + * + * However, to achieve that goal would, I think, require a fairly + * substantial refactorisation of the code in this file so that + * there is much more obvious and explicit coordination between + * calls to LayoutDLine and updating of each TkTextLine's + * pixelHeight. The complicated bit is that LayoutDLine deals with + * individual display lines, but pixelHeight is for a logical + * line. + */ + +#if 0 + Tcl_Panic("Counted more pixels (%d) than expected (%d) total " + "pixels in text widget scroll bar calculation.", count, + totalPixels); +#endif + count = totalPixels; + } + + last = ((double) count)/((double)totalPixels); + } + + if (!report) { + listObj = Tcl_NewListObj(0,NULL); + Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(first)); + Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(last)); + Tcl_SetObjResult(interp, listObj); + return; + } + + if (FP_EQUAL_SCALE(first, dInfoPtr->yScrollFirst, totalPixels) && + FP_EQUAL_SCALE(last, dInfoPtr->yScrollLast, totalPixels)) { + return; + } + + dInfoPtr->yScrollFirst = first; + dInfoPtr->yScrollLast = last; + if (textPtr->yScrollCmd != NULL) { + char buf1[TCL_DOUBLE_SPACE+1]; + char buf2[TCL_DOUBLE_SPACE+1]; + Tcl_DString buf; + + buf1[0] = ' '; + buf2[0] = ' '; + Tcl_PrintDouble(NULL, first, buf1+1); + Tcl_PrintDouble(NULL, last, buf2+1); + Tcl_DStringInit(&buf); + Tcl_DStringAppend(&buf, textPtr->yScrollCmd, -1); + Tcl_DStringAppend(&buf, buf1, -1); + Tcl_DStringAppend(&buf, buf2, -1); + code = Tcl_EvalEx(interp, Tcl_DStringValue(&buf), -1, 0); + Tcl_DStringFree(&buf); + if (code != TCL_OK) { + Tcl_AddErrorInfo(interp, + "\n (vertical scrolling command executed by text)"); + Tcl_BackgroundException(interp, code); + } + } +} + +/* + *---------------------------------------------------------------------- + * + * AsyncUpdateYScrollbar -- + * + * This function is called to update the vertical scrollbar asychronously + * as the pixel height calculations progress for lines in the widget. + * + * Results: + * None. + * + * Side effects: + * See 'GetYView'. In particular the scrollbar position and size may be + * changed. + * + *---------------------------------------------------------------------- + */ + +static void +AsyncUpdateYScrollbar( + ClientData clientData) /* Information about widget. */ +{ + register TkText *textPtr = clientData; + + textPtr->dInfoPtr->scrollbarTimer = NULL; + + if (!(textPtr->flags & DESTROYED)) { + GetYView(textPtr->interp, textPtr, 1); + } + + if (--textPtr->refCount == 0) { + ckfree(textPtr); + } +} + +/* + *---------------------------------------------------------------------- + * + * FindDLine -- + * + * This function is called to find the DLine corresponding to a given + * text index. + * + * Results: + * The return value is a pointer to the first DLine found in the list + * headed by dlPtr that displays information at or after the specified + * position. If there is no such line in the list then NULL is returned. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static DLine * +FindDLine( + TkText *textPtr, /* Widget record for text widget. */ + register DLine *dlPtr, /* Pointer to first in list of DLines to + * search. */ + const TkTextIndex *indexPtr)/* Index of desired character. */ +{ + DLine *dlPtrPrev; + TkTextIndex indexPtr2; + + if (dlPtr == NULL) { + return NULL; + } + if (TkBTreeLinesTo(NULL, indexPtr->linePtr) + < TkBTreeLinesTo(NULL, dlPtr->index.linePtr)) { + /* + * The first display line is already past the desired line. + */ + + return dlPtr; + } + + /* + * The display line containing the desired index is such that the index + * of the first character of this display line is at or before the + * desired index, and the index of the first character of the next + * display line is after the desired index. + */ + + while (TkTextIndexCmp(&dlPtr->index,indexPtr) < 0) { + dlPtrPrev = dlPtr; + dlPtr = dlPtr->nextPtr; + if (dlPtr == NULL) { + /* + * We're past the last display line, either because the desired + * index lies past the visible text, or because the desired index + * is on the last display line. + */ + indexPtr2 = dlPtrPrev->index; + TkTextIndexForwBytes(textPtr, &indexPtr2, dlPtrPrev->byteCount, + &indexPtr2); + if (TkTextIndexCmp(&indexPtr2,indexPtr) > 0) { + /* + * The desired index is on the last display line. + * --> return this display line. + */ + dlPtr = dlPtrPrev; + } else { + /* + * The desired index is past the visible text. There is no + * display line displaying something at the desired index. + * --> return NULL. + */ + } + break; + } + if (TkTextIndexCmp(&dlPtr->index,indexPtr) > 0) { + /* + * If we're here then we would normally expect that: + * dlPtrPrev->index <= indexPtr < dlPtr->index + * i.e. we have found the searched display line being dlPtr. + * However it is possible that some DLines were unlinked + * previously, leading to a situation where going through + * the list of display lines skips display lines that did + * exist just a moment ago. + */ + indexPtr2 = dlPtrPrev->index; + TkTextIndexForwBytes(textPtr, &indexPtr2, dlPtrPrev->byteCount, + &indexPtr2); + if (TkTextIndexCmp(&indexPtr2,indexPtr) > 0) { + /* + * Confirmed: + * dlPtrPrev->index <= indexPtr < dlPtr->index + * --> return dlPtrPrev. + */ + dlPtr = dlPtrPrev; + } else { + /* + * The last (rightmost) index shown by dlPtrPrev is still + * before the desired index. This may be because there was + * previously a display line between dlPtrPrev and dlPtr + * and this display line has been unlinked. + * --> return dlPtr. + */ + } + break; + } + } + + return dlPtr; +} + +/* + *---------------------------------------------------------------------- + * + * IsStartOfNotMergedLine -- + * + * This function checks whether the given index is the start of a + * logical line that is not merged with the previous logical line + * (due to elision of the eol of the previous line). + * + * Results: + * Returns whether the given index denotes the first index of a +* logical line not merged with its previous line. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +IsStartOfNotMergedLine( + TkText *textPtr, /* Widget record for text widget. */ + CONST TkTextIndex *indexPtr) /* Index to check. */ +{ + TkTextIndex indexPtr2; + + if (indexPtr->byteIndex != 0) { + /* + * Not the start of a logical line. + */ + return 0; + } + + if (TkTextIndexBackBytes(textPtr, indexPtr, 1, &indexPtr2)) { + /* + * indexPtr is the first index of the text widget. + */ + return 1; + } + + if (!TkTextIsElided(textPtr, &indexPtr2, NULL)) { + /* + * The eol of the line just before indexPtr is elided. + */ + return 1; + } + + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * TkTextPixelIndex -- + * + * Given an (x,y) coordinate on the screen, find the location of the + * character closest to that location. + * + * Results: + * The index at *indexPtr is modified to refer to the character on the + * display that is closest to (x,y). + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +void +TkTextPixelIndex( + TkText *textPtr, /* Widget record for text widget. */ + int x, int y, /* Pixel coordinates of point in widget's + * window. */ + TkTextIndex *indexPtr, /* This index gets filled in with the index of + * the character nearest to (x,y). */ + int *nearest) /* If non-NULL then gets set to 0 if (x,y) is + * actually over the returned index, and 1 if + * it is just nearby (e.g. if x,y is on the + * border of the widget). */ +{ + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + register DLine *dlPtr, *validDlPtr; + int nearby = 0; + + /* + * Make sure that all of the layout information about what's displayed + * where on the screen is up-to-date. + */ + + if (dInfoPtr->flags & DINFO_OUT_OF_DATE) { + UpdateDisplayInfo(textPtr); + } + + /* + * If the coordinates are above the top of the window, then adjust them to + * refer to the upper-right corner of the window. If they're off to one + * side or the other, then adjust to the closest side. + */ + + if (y < dInfoPtr->y) { + y = dInfoPtr->y; + x = dInfoPtr->x; + nearby = 1; + } + if (x >= dInfoPtr->maxX) { + x = dInfoPtr->maxX - 1; + nearby = 1; + } + if (x < dInfoPtr->x) { + x = dInfoPtr->x; + nearby = 1; + } + + /* + * Find the display line containing the desired y-coordinate. + */ + + if (dInfoPtr->dLinePtr == NULL) { + if (nearest != NULL) { + *nearest = 1; + } + *indexPtr = textPtr->topIndex; + return; + } + for (dlPtr = validDlPtr = dInfoPtr->dLinePtr; + y >= (dlPtr->y + dlPtr->height); + dlPtr = dlPtr->nextPtr) { + if (dlPtr->chunkPtr != NULL) { + validDlPtr = dlPtr; + } + if (dlPtr->nextPtr == NULL) { + /* + * Y-coordinate is off the bottom of the displayed text. Use the + * last character on the last line. + */ + + x = dInfoPtr->maxX - 1; + nearby = 1; + break; + } + } + if (dlPtr->chunkPtr == NULL) { + dlPtr = validDlPtr; + } + + if (nearest != NULL) { + *nearest = nearby; + } + + DlineIndexOfX(textPtr, dlPtr, x, indexPtr); +} + +/* + *---------------------------------------------------------------------- + * + * DlineIndexOfX -- + * + * Given an x coordinate in a display line, find the index of the + * character closest to that location. + * + * This is effectively the opposite of DlineXOfIndex. + * + * Results: + * The index at *indexPtr is modified to refer to the character on the + * display line that is closest to x. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static void +DlineIndexOfX( + TkText *textPtr, /* Widget record for text widget. */ + DLine *dlPtr, /* Display information for this display + * line. */ + int x, /* Pixel x coordinate of point in widget's + * window. */ + TkTextIndex *indexPtr) /* This index gets filled in with the index of + * the character nearest to x. */ +{ + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + register TkTextDispChunk *chunkPtr; + + /* + * Scan through the line's chunks to find the one that contains the + * desired x-coordinate. Before doing this, translate the x-coordinate + * from the coordinate system of the window to the coordinate system of + * the line (to take account of x-scrolling). + */ + + *indexPtr = dlPtr->index; + x = x - dInfoPtr->x + dInfoPtr->curXPixelOffset; + chunkPtr = dlPtr->chunkPtr; + + if (chunkPtr == NULL || x == 0) { + /* + * This may occur if everything is elided, or if we're simply already + * at the beginning of the line. + */ + + return; + } + + while (x >= (chunkPtr->x + chunkPtr->width)) { + /* + * Note that this forward then backward movement of the index can be + * problematic at the end of the buffer (we can't move forward, and + * then when we move backward, we do, leading to the wrong position). + * Hence when x == 0 we take special action above. + */ + + if (TkTextIndexForwBytes(NULL,indexPtr,chunkPtr->numBytes,indexPtr)) { + /* + * We've reached the end of the text. + */ + + TkTextIndexBackChars(NULL, indexPtr, 1, indexPtr, COUNT_INDICES); + return; + } + if (chunkPtr->nextPtr == NULL) { + /* + * We've reached the end of the display line. + */ + + TkTextIndexBackChars(NULL, indexPtr, 1, indexPtr, COUNT_INDICES); + return; + } + chunkPtr = chunkPtr->nextPtr; + } + + /* + * If the chunk has more than one byte in it, ask it which character is at + * the desired location. In this case we can manipulate + * 'indexPtr->byteIndex' directly, because we know we're staying inside a + * single logical line. + */ + + if (chunkPtr->numBytes > 1) { + indexPtr->byteIndex += chunkPtr->measureProc(chunkPtr, x); + } +} + +/* + *---------------------------------------------------------------------- + * + * TkTextIndexOfX -- + * + * Given a logical x coordinate (i.e. distance in pixels from the + * beginning of the display line, not taking into account any information + * about the window, scrolling etc.) on the display line starting with + * the given index, adjust that index to refer to the object under the x + * coordinate. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +void +TkTextIndexOfX( + TkText *textPtr, /* Widget record for text widget. */ + int x, /* The x coordinate for which we want the + * index. */ + TkTextIndex *indexPtr) /* Index of display line start, which will be + * adjusted to the index under the given x + * coordinate. */ +{ + DLine *dlPtr = LayoutDLine(textPtr, indexPtr); + DlineIndexOfX(textPtr, dlPtr, x + textPtr->dInfoPtr->x + - textPtr->dInfoPtr->curXPixelOffset, indexPtr); + FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP); +} + +/* + *---------------------------------------------------------------------- + * + * DlineXOfIndex -- + * + * Given a relative byte index on a given display line (i.e. the number + * of byte indices from the beginning of the given display line), find + * the x coordinate of that index within the abstract display line, + * without adjusting for the x-scroll state of the line. + * + * This is effectively the opposite of DlineIndexOfX. + * + * NB. The 'byteIndex' is relative to the display line, NOT the logical + * line. + * + * Results: + * The x coordinate. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +DlineXOfIndex( + TkText *textPtr, /* Widget record for text widget. */ + DLine *dlPtr, /* Display information for this display + * line. */ + int byteIndex) /* The byte index for which we want the + * coordinate. */ +{ + register TkTextDispChunk *chunkPtr = dlPtr->chunkPtr; + int x = 0; + + if (byteIndex == 0 || chunkPtr == NULL) { + return x; + } + + /* + * Scan through the line's chunks to find the one that contains the + * desired byte index. + */ + + chunkPtr = dlPtr->chunkPtr; + while (byteIndex > 0) { + if (byteIndex < chunkPtr->numBytes) { + int y, width, height; + + chunkPtr->bboxProc(textPtr, chunkPtr, byteIndex, + dlPtr->y + dlPtr->spaceAbove, + dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow, + dlPtr->baseline - dlPtr->spaceAbove, &x, &y, &width, + &height); + break; + } + byteIndex -= chunkPtr->numBytes; + if (chunkPtr->nextPtr == NULL || byteIndex == 0) { + x = chunkPtr->x + chunkPtr->width; + break; + } + chunkPtr = chunkPtr->nextPtr; + } + + return x; +} + +/* + *---------------------------------------------------------------------- + * + * TkTextIndexBbox -- + * + * Given an index, find the bounding box of the screen area occupied by + * the entity (character, window, image) at that index. + * + * Results: + * Zero is returned if the index is on the screen. -1 means the index is + * not on the screen. If the return value is 0, then the bounding box of + * the part of the index that's visible on the screen is returned to + * *xPtr, *yPtr, *widthPtr, and *heightPtr. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +int +TkTextIndexBbox( + TkText *textPtr, /* Widget record for text widget. */ + const TkTextIndex *indexPtr,/* Index whose bounding box is desired. */ + int *xPtr, int *yPtr, /* Filled with index's upper-left + * coordinate. */ + int *widthPtr, int *heightPtr, + /* Filled in with index's dimensions. */ + int *charWidthPtr) /* If the 'index' is at the end of a display + * line and therefore takes up a very large + * width, this is used to return the smaller + * width actually desired by the index. */ +{ + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + DLine *dlPtr; + register TkTextDispChunk *chunkPtr; + int byteCount; + + /* + * Make sure that all of the screen layout information is up to date. + */ + + if (dInfoPtr->flags & DINFO_OUT_OF_DATE) { + UpdateDisplayInfo(textPtr); + } + + /* + * Find the display line containing the desired index. + */ + + dlPtr = FindDLine(textPtr, dInfoPtr->dLinePtr, indexPtr); + + /* + * Two cases shall be trapped here because the logic later really + * needs dlPtr to be the display line containing indexPtr: + * 1. if no display line contains the desired index (NULL dlPtr) + * 2. if indexPtr is before the first display line, in which case + * dlPtr currently points to the first display line + */ + + if ((dlPtr == NULL) || (TkTextIndexCmp(&dlPtr->index, indexPtr) > 0)) { + return -1; + } + + /* + * Find the chunk within the display line that contains the desired + * index. The chunks making the display line are skipped up to but not + * including the one crossing indexPtr. Skipping is done based on + * a byteCount offset possibly spanning several logical lines in case + * they are elided. + */ + + byteCount = TkTextIndexCountBytes(textPtr, &dlPtr->index, indexPtr); + for (chunkPtr = dlPtr->chunkPtr; ; chunkPtr = chunkPtr->nextPtr) { + if (chunkPtr == NULL) { + return -1; + } + if (byteCount < chunkPtr->numBytes) { + break; + } + byteCount -= chunkPtr->numBytes; + } + + /* + * Call a chunk-specific function to find the horizontal range of the + * character within the chunk, then fill in the vertical range. The + * x-coordinate returned by bboxProc is a coordinate within a line, not a + * coordinate on the screen. Translate it to reflect horizontal scrolling. + */ + + chunkPtr->bboxProc(textPtr, chunkPtr, byteCount, + dlPtr->y + dlPtr->spaceAbove, + dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow, + dlPtr->baseline - dlPtr->spaceAbove, xPtr, yPtr, widthPtr, + heightPtr); + *xPtr = *xPtr + dInfoPtr->x - dInfoPtr->curXPixelOffset; + if ((byteCount == chunkPtr->numBytes-1) && (chunkPtr->nextPtr == NULL)) { + /* + * Last character in display line. Give it all the space up to the + * line. + */ + + if (charWidthPtr != NULL) { + *charWidthPtr = dInfoPtr->maxX - *xPtr; + if (*charWidthPtr > textPtr->charWidth) { + *charWidthPtr = textPtr->charWidth; + } + } + if (*xPtr > dInfoPtr->maxX) { + *xPtr = dInfoPtr->maxX; + } + *widthPtr = dInfoPtr->maxX - *xPtr; + } else { + if (charWidthPtr != NULL) { + *charWidthPtr = *widthPtr; + } + } + if (*widthPtr == 0) { + /* + * With zero width (e.g. elided text) we just need to make sure it is + * onscreen, where the '=' case here is ok. + */ + + if (*xPtr < dInfoPtr->x) { + return -1; + } + } else { + if ((*xPtr + *widthPtr) <= dInfoPtr->x) { + return -1; + } + } + if ((*xPtr + *widthPtr) > dInfoPtr->maxX) { + *widthPtr = dInfoPtr->maxX - *xPtr; + if (*widthPtr <= 0) { + return -1; + } + } + if ((*yPtr + *heightPtr) > dInfoPtr->maxY) { + *heightPtr = dInfoPtr->maxY - *yPtr; + if (*heightPtr <= 0) { + return -1; + } + } + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * TkTextDLineInfo -- + * + * Given an index, return information about the display line containing + * that character. + * + * Results: + * Zero is returned if the character is on the screen. -1 means the + * character isn't on the screen. If the return value is 0, then + * information is returned in the variables pointed to by xPtr, yPtr, + * widthPtr, heightPtr, and basePtr. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +int +TkTextDLineInfo( + TkText *textPtr, /* Widget record for text widget. */ + const TkTextIndex *indexPtr,/* Index of character whose bounding box is + * desired. */ + int *xPtr, int *yPtr, /* Filled with line's upper-left + * coordinate. */ + int *widthPtr, int *heightPtr, + /* Filled in with line's dimensions. */ + int *basePtr) /* Filled in with the baseline position, + * measured as an offset down from *yPtr. */ +{ + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + DLine *dlPtr; + int dlx; + + /* + * Make sure that all of the screen layout information is up to date. + */ + + if (dInfoPtr->flags & DINFO_OUT_OF_DATE) { + UpdateDisplayInfo(textPtr); + } + + /* + * Find the display line containing the desired index. + */ + + dlPtr = FindDLine(textPtr, dInfoPtr->dLinePtr, indexPtr); + + /* + * Two cases shall be trapped here because the logic later really + * needs dlPtr to be the display line containing indexPtr: + * 1. if no display line contains the desired index (NULL dlPtr) + * 2. if indexPtr is before the first display line, in which case + * dlPtr currently points to the first display line + */ + + if ((dlPtr == NULL) || (TkTextIndexCmp(&dlPtr->index, indexPtr) > 0)) { + return -1; + } + + dlx = (dlPtr->chunkPtr != NULL? dlPtr->chunkPtr->x: 0); + *xPtr = dInfoPtr->x - dInfoPtr->curXPixelOffset + dlx; + *widthPtr = dlPtr->length - dlx; + *yPtr = dlPtr->y; + if ((dlPtr->y + dlPtr->height) > dInfoPtr->maxY) { + *heightPtr = dInfoPtr->maxY - dlPtr->y; + } else { + *heightPtr = dlPtr->height; + } + *basePtr = dlPtr->baseline; + return 0; +} + +/* + * Get bounding-box information about an elided chunk. + */ + +static void +ElideBboxProc( + TkText *textPtr, + TkTextDispChunk *chunkPtr, /* Chunk containing desired char. */ + int index, /* Index of desired character within the + * chunk. */ + int y, /* Topmost pixel in area allocated for this + * line. */ + int lineHeight, /* Height of line, in pixels. */ + int baseline, /* Location of line's baseline, in pixels + * measured down from y. */ + int *xPtr, int *yPtr, /* Gets filled in with coords of character's + * upper-left pixel. X-coord is in same + * coordinate system as chunkPtr->x. */ + int *widthPtr, /* Gets filled in with width of character, in + * pixels. */ + int *heightPtr) /* Gets filled in with height of character, in + * pixels. */ +{ + *xPtr = chunkPtr->x; + *yPtr = y; + *widthPtr = *heightPtr = 0; +} + +/* + * Measure an elided chunk. + */ + +static int +ElideMeasureProc( + TkTextDispChunk *chunkPtr, /* Chunk containing desired coord. */ + int x) /* X-coordinate, in same coordinate system as + * chunkPtr->x. */ +{ + return 0 /*chunkPtr->numBytes - 1*/; +} + +/* + *-------------------------------------------------------------- + * + * TkTextCharLayoutProc -- + * + * This function is the "layoutProc" for character segments. + * + * Results: + * If there is something to display for the chunk then a non-zero value + * is returned and the fields of chunkPtr will be filled in (see the + * declaration of TkTextDispChunk in tkText.h for details). If zero is + * returned it means that no characters from this chunk fit in the + * window. If -1 is returned it means that this segment just doesn't need + * to be displayed (never happens for text). + * + * Side effects: + * Memory is allocated to hold additional information about the chunk. + * + *-------------------------------------------------------------- + */ + +int +TkTextCharLayoutProc( + TkText *textPtr, /* Text widget being layed out. */ + TkTextIndex *indexPtr, /* Index of first character to lay out + * (corresponds to segPtr and offset). */ + TkTextSegment *segPtr, /* Segment being layed out. */ + int byteOffset, /* Byte offset within segment of first + * character to consider. */ + int maxX, /* Chunk must not occupy pixels at this + * position or higher. */ + int maxBytes, /* Chunk must not include more than this many + * characters. */ + int noCharsYet, /* Non-zero means no characters have been + * assigned to this display line yet. */ + TkWrapMode wrapMode, /* How to handle line wrapping: + * TEXT_WRAPMODE_CHAR, TEXT_WRAPMODE_NONE, or + * TEXT_WRAPMODE_WORD. */ + register TkTextDispChunk *chunkPtr) + /* Structure to fill in with information about + * this chunk. The x field has already been + * set by the caller. */ +{ + Tk_Font tkfont; + int nextX, bytesThatFit, count; + CharInfo *ciPtr; + char *p; + TkTextSegment *nextPtr; + Tk_FontMetrics fm; +#if TK_LAYOUT_WITH_BASE_CHUNKS + const char *line; + int lineOffset; + BaseCharInfo *bciPtr; + Tcl_DString *baseString; +#endif + + /* + * Figure out how many characters will fit in the space we've got. Include + * the next character, even though it won't fit completely, if any of the + * following is true: + * (a) the chunk contains no characters and the display line contains no + * characters yet (i.e. the line isn't wide enough to hold even a + * single character). + * (b) at least one pixel of the character is visible, we have not + * already exceeded the character limit, and the next character is a + * white space character. + */ + + p = segPtr->body.chars + byteOffset; + tkfont = chunkPtr->stylePtr->sValuePtr->tkfont; + +#if TK_LAYOUT_WITH_BASE_CHUNKS + if (baseCharChunkPtr == NULL) { + baseCharChunkPtr = chunkPtr; + bciPtr = ckalloc(sizeof(BaseCharInfo)); + baseString = &bciPtr->baseChars; + Tcl_DStringInit(baseString); + bciPtr->width = 0; + + ciPtr = &bciPtr->ci; + } else { + bciPtr = baseCharChunkPtr->clientData; + ciPtr = ckalloc(sizeof(CharInfo)); + baseString = &bciPtr->baseChars; + } + + lineOffset = Tcl_DStringLength(baseString); + line = Tcl_DStringAppend(baseString,p,maxBytes); + + chunkPtr->clientData = ciPtr; + ciPtr->baseChunkPtr = baseCharChunkPtr; + ciPtr->baseOffset = lineOffset; + ciPtr->chars = NULL; + ciPtr->numBytes = 0; + + bytesThatFit = CharChunkMeasureChars(chunkPtr, line, + lineOffset + maxBytes, lineOffset, -1, chunkPtr->x, maxX, + TK_ISOLATE_END, &nextX); +#else /* !TK_LAYOUT_WITH_BASE_CHUNKS */ + bytesThatFit = CharChunkMeasureChars(chunkPtr, p, maxBytes, 0, -1, + chunkPtr->x, maxX, TK_ISOLATE_END, &nextX); +#endif /* TK_LAYOUT_WITH_BASE_CHUNKS */ + + if (bytesThatFit < maxBytes) { + if ((bytesThatFit == 0) && noCharsYet) { + Tcl_UniChar ch; + int chLen = Tcl_UtfToUniChar(p, &ch); + +#if TK_LAYOUT_WITH_BASE_CHUNKS + bytesThatFit = CharChunkMeasureChars(chunkPtr, line, + lineOffset+chLen, lineOffset, -1, chunkPtr->x, -1, 0, + &nextX); +#else /* !TK_LAYOUT_WITH_BASE_CHUNKS */ + bytesThatFit = CharChunkMeasureChars(chunkPtr, p, chLen, 0, -1, + chunkPtr->x, -1, 0, &nextX); +#endif /* TK_LAYOUT_WITH_BASE_CHUNKS */ + } + if ((nextX < maxX) && ((p[bytesThatFit] == ' ') + || (p[bytesThatFit] == '\t'))) { + /* + * Space characters are funny, in that they are considered to fit + * if there is at least one pixel of space left on the line. Just + * give the space character whatever space is left. + */ + + nextX = maxX; + bytesThatFit++; + } + if (p[bytesThatFit] == '\n') { + /* + * A newline character takes up no space, so if the previous + * character fits then so does the newline. + */ + + bytesThatFit++; + } + if (bytesThatFit == 0) { +#if TK_LAYOUT_WITH_BASE_CHUNKS + chunkPtr->clientData = NULL; + if (chunkPtr == baseCharChunkPtr) { + baseCharChunkPtr = NULL; + Tcl_DStringFree(baseString); + } else { + Tcl_DStringSetLength(baseString,lineOffset); + } + ckfree(ciPtr); +#endif /* TK_LAYOUT_WITH_BASE_CHUNKS */ + return 0; + } + } + + Tk_GetFontMetrics(tkfont, &fm); + + /* + * Fill in the chunk structure and allocate and initialize a CharInfo + * structure. If the last character is a newline then don't bother to + * display it. + */ + + chunkPtr->displayProc = CharDisplayProc; + chunkPtr->undisplayProc = CharUndisplayProc; + chunkPtr->measureProc = CharMeasureProc; + chunkPtr->bboxProc = CharBboxProc; + chunkPtr->numBytes = bytesThatFit; + chunkPtr->minAscent = fm.ascent + chunkPtr->stylePtr->sValuePtr->offset; + chunkPtr->minDescent = fm.descent - chunkPtr->stylePtr->sValuePtr->offset; + chunkPtr->minHeight = 0; + chunkPtr->width = nextX - chunkPtr->x; + chunkPtr->breakIndex = -1; + +#if !TK_LAYOUT_WITH_BASE_CHUNKS + ciPtr = ckalloc((Tk_Offset(CharInfo, chars) + 1) + bytesThatFit); + chunkPtr->clientData = ciPtr; + memcpy(ciPtr->chars, p, (unsigned) bytesThatFit); +#endif /* TK_LAYOUT_WITH_BASE_CHUNKS */ + + ciPtr->numBytes = bytesThatFit; + if (p[bytesThatFit - 1] == '\n') { + ciPtr->numBytes--; + } + +#if TK_LAYOUT_WITH_BASE_CHUNKS + /* + * Final update for the current base chunk data. + */ + + Tcl_DStringSetLength(baseString,lineOffset+ciPtr->numBytes); + bciPtr->width = nextX - baseCharChunkPtr->x; + + /* + * Finalize the base chunk if this chunk ends in a tab, which definitly + * breaks the context and needs to be handled on a higher level. + */ + + if (ciPtr->numBytes > 0 && p[ciPtr->numBytes - 1] == '\t') { + FinalizeBaseChunk(chunkPtr); + } +#endif /* TK_LAYOUT_WITH_BASE_CHUNKS */ + + /* + * Compute a break location. If we're in word wrap mode, a break can occur + * after any space character, or at the end of the chunk if the next + * segment (ignoring those with zero size) is not a character segment. + */ + + if (wrapMode != TEXT_WRAPMODE_WORD) { + chunkPtr->breakIndex = chunkPtr->numBytes; + } else { + for (count = bytesThatFit, p += bytesThatFit - 1; count > 0; + count--, p--) { + /* + * Don't use isspace(); effects are unpredictable and can lead to + * odd word-wrapping problems on some platforms. Also don't use + * Tcl_UniCharIsSpace here either, as it identifies non-breaking + * spaces as places to break. What we actually want is only the + * ASCII space characters, so use them explicitly... + */ + + switch (*p) { + case '\t': case '\n': case '\v': case '\f': case '\r': case ' ': + chunkPtr->breakIndex = count; + goto checkForNextChunk; + } + } + checkForNextChunk: + if ((bytesThatFit + byteOffset) == segPtr->size) { + for (nextPtr = segPtr->nextPtr; nextPtr != NULL; + nextPtr = nextPtr->nextPtr) { + if (nextPtr->size != 0) { + if (nextPtr->typePtr != &tkTextCharType) { + chunkPtr->breakIndex = chunkPtr->numBytes; + } + break; + } + } + } + } + return 1; +} + +/* + *--------------------------------------------------------------------------- + * + * CharChunkMeasureChars -- + * + * Determine the number of characters from a char chunk that will fit in + * the given horizontal span. + * + * This is the same as MeasureChars (which see), but in the context of a + * char chunk, i.e. on a higher level of abstraction. Use this function + * whereever possible instead of plain MeasureChars, so that the right + * context is used automatically. + * + * Results: + * The return value is the number of bytes from the range of start to end + * in source that fit in the span given by startX and maxX. *nextXPtr is + * filled in with the x-coordinate at which the first character that + * didn't fit would be drawn, if it were to be drawn. + * + * Side effects: + * None. + *-------------------------------------------------------------- + */ + +static int +CharChunkMeasureChars( + TkTextDispChunk *chunkPtr, /* Chunk from which to measure. */ + const char *chars, /* Chars to use, instead of the chunk's own. + * Used by the layoutproc during chunk setup. + * All other callers use NULL. Not + * NUL-terminated. */ + int charsLen, /* Length of the "chars" parameter. */ + int start, int end, /* The range of chars to measure inside the + * chunk (or inside the additional chars). */ + int startX, /* Starting x coordinate where the measured + * span will begin. */ + int maxX, /* Maximum pixel width of the span. May be -1 + * for unlimited. */ + int flags, /* Flags to pass to MeasureChars. */ + int *nextXPtr) /* The function puts the newly calculated + * right border x-position of the span + * here. */ +{ + Tk_Font tkfont = chunkPtr->stylePtr->sValuePtr->tkfont; + CharInfo *ciPtr = chunkPtr->clientData; + +#if !TK_LAYOUT_WITH_BASE_CHUNKS + if (chars == NULL) { + chars = ciPtr->chars; + charsLen = ciPtr->numBytes; + } + if (end == -1) { + end = charsLen; + } + + return MeasureChars(tkfont, chars, charsLen, start, end-start, + startX, maxX, flags, nextXPtr); +#else /* TK_LAYOUT_WITH_BASE_CHUNKS */ + { + int xDisplacement; + int fit, bstart = start, bend = end; + + if (chars == NULL) { + Tcl_DString *baseChars = &((BaseCharInfo *) + ciPtr->baseChunkPtr->clientData)->baseChars; + + chars = Tcl_DStringValue(baseChars); + charsLen = Tcl_DStringLength(baseChars); + bstart += ciPtr->baseOffset; + if (bend == -1) { + bend = ciPtr->baseOffset + ciPtr->numBytes; + } else { + bend += ciPtr->baseOffset; + } + } else if (bend == -1) { + bend = charsLen; + } + + if (bstart == ciPtr->baseOffset) { + xDisplacement = startX - chunkPtr->x; + } else { + int widthUntilStart = 0; + + MeasureChars(tkfont, chars, charsLen, 0, bstart, + 0, -1, 0, &widthUntilStart); + xDisplacement = startX - widthUntilStart - chunkPtr->x; + } + + fit = MeasureChars(tkfont, chars, charsLen, 0, bend, + ciPtr->baseChunkPtr->x + xDisplacement, maxX, flags, nextXPtr); + + if (fit < bstart) { + return 0; + } else { + return fit - bstart; + } + } +#endif /* TK_LAYOUT_WITH_BASE_CHUNKS */ +} + +/* + *-------------------------------------------------------------- + * + * CharDisplayProc -- + * + * This function is called to display a character chunk on the screen or + * in an off-screen pixmap. + * + * Results: + * None. + * + * Side effects: + * Graphics are drawn. + * + *-------------------------------------------------------------- + */ + +static void +CharDisplayProc( + TkText *textPtr, + TkTextDispChunk *chunkPtr, /* Chunk that is to be drawn. */ + int x, /* X-position in dst at which to draw this + * chunk (may differ from the x-position in + * the chunk because of scrolling). */ + int y, /* Y-position at which to draw this chunk in + * dst. */ + int height, /* Total height of line. */ + int baseline, /* Offset of baseline from y. */ + Display *display, /* Display to use for drawing. */ + Drawable dst, /* Pixmap or window in which to draw chunk. */ + int screenY) /* Y-coordinate in text window that + * corresponds to y. */ +{ + CharInfo *ciPtr = chunkPtr->clientData; + const char *string; + TextStyle *stylePtr; + StyleValues *sValuePtr; + int numBytes, offsetBytes, offsetX; +#if TK_DRAW_IN_CONTEXT + BaseCharInfo *bciPtr; +#endif /* TK_DRAW_IN_CONTEXT */ + + if ((x + chunkPtr->width) <= 0) { + /* + * The chunk is off-screen. + */ + + return; + } + +#if TK_DRAW_IN_CONTEXT + bciPtr = ciPtr->baseChunkPtr->clientData; + numBytes = Tcl_DStringLength(&bciPtr->baseChars); + string = Tcl_DStringValue(&bciPtr->baseChars); + +#elif TK_LAYOUT_WITH_BASE_CHUNKS + if (ciPtr->baseChunkPtr != chunkPtr) { + /* + * Without context drawing only base chunks display their foreground. + */ + + return; + } + + numBytes = Tcl_DStringLength(&((BaseCharInfo *) ciPtr)->baseChars); + string = ciPtr->chars; + +#else /* !TK_LAYOUT_WITH_BASE_CHUNKS */ + numBytes = ciPtr->numBytes; + string = ciPtr->chars; +#endif /* TK_LAYOUT_WITH_BASE_CHUNKS */ + + stylePtr = chunkPtr->stylePtr; + sValuePtr = stylePtr->sValuePtr; + + /* + * If the text sticks out way to the left of the window, skip over the + * characters that aren't in the visible part of the window. This is + * essential if x is very negative (such as less than 32K); otherwise + * overflow problems will occur in servers that use 16-bit arithmetic, + * like X. + */ + + offsetX = x; + offsetBytes = 0; + if (x < 0) { + offsetBytes = CharChunkMeasureChars(chunkPtr, NULL, 0, 0, -1, + x, 0, 0, &offsetX); + } + + /* + * Draw the text, underline, and overstrike for this chunk. + */ + + if (!sValuePtr->elide && (numBytes > offsetBytes) + && (stylePtr->fgGC != None)) { +#if TK_DRAW_IN_CONTEXT + int start = ciPtr->baseOffset + offsetBytes; + int len = ciPtr->numBytes - offsetBytes; + int xDisplacement = x - chunkPtr->x; + + if ((len > 0) && (string[start + len - 1] == '\t')) { + len--; + } + if (len <= 0) { + return; + } + + TkpDrawCharsInContext(display, dst, stylePtr->fgGC, sValuePtr->tkfont, + string, numBytes, start, len, + ciPtr->baseChunkPtr->x + xDisplacement, + y + baseline - sValuePtr->offset); + + if (sValuePtr->underline) { + TkUnderlineCharsInContext(display, dst, stylePtr->ulGC, + sValuePtr->tkfont, string, numBytes, + ciPtr->baseChunkPtr->x + xDisplacement, + y + baseline - sValuePtr->offset, + start, start+len); + } + if (sValuePtr->overstrike) { + Tk_FontMetrics fm; + + Tk_GetFontMetrics(sValuePtr->tkfont, &fm); + TkUnderlineCharsInContext(display, dst, stylePtr->ovGC, + sValuePtr->tkfont, string, numBytes, + ciPtr->baseChunkPtr->x + xDisplacement, + y + baseline - sValuePtr->offset + - fm.descent - (fm.ascent * 3) / 10, + start, start+len); + } +#else /* !TK_DRAW_IN_CONTEXT */ + string += offsetBytes; + numBytes -= offsetBytes; + + if ((numBytes > 0) && (string[numBytes - 1] == '\t')) { + numBytes--; + } + Tk_DrawChars(display, dst, stylePtr->fgGC, sValuePtr->tkfont, string, + numBytes, offsetX, y + baseline - sValuePtr->offset); + if (sValuePtr->underline) { + Tk_UnderlineChars(display, dst, stylePtr->ulGC, sValuePtr->tkfont, + string, offsetX, + y + baseline - sValuePtr->offset, + 0, numBytes); + + } + if (sValuePtr->overstrike) { + Tk_FontMetrics fm; + + Tk_GetFontMetrics(sValuePtr->tkfont, &fm); + Tk_UnderlineChars(display, dst, stylePtr->ovGC, sValuePtr->tkfont, + string, offsetX, + y + baseline - sValuePtr->offset + - fm.descent - (fm.ascent * 3) / 10, + 0, numBytes); + } +#endif /* TK_DRAW_IN_CONTEXT */ + } +} + +/* + *-------------------------------------------------------------- + * + * CharUndisplayProc -- + * + * This function is called when a character chunk is no longer going to + * be displayed. It frees up resources that were allocated to display the + * chunk. + * + * Results: + * None. + * + * Side effects: + * Memory and other resources get freed. + * + *-------------------------------------------------------------- + */ + +static void +CharUndisplayProc( + TkText *textPtr, /* Overall information about text widget. */ + TkTextDispChunk *chunkPtr) /* Chunk that is about to be freed. */ +{ + CharInfo *ciPtr = chunkPtr->clientData; + + if (ciPtr) { +#if TK_LAYOUT_WITH_BASE_CHUNKS + if (chunkPtr == ciPtr->baseChunkPtr) { + /* + * Basechunks are undisplayed first, when DLines are freed or + * partially freed, so this makes sure we don't access their data + * any more. + */ + + FreeBaseChunk(chunkPtr); + } else if (ciPtr->baseChunkPtr != NULL) { + /* + * When other char chunks are undisplayed, drop their characters + * from the base chunk. This usually happens, when they are last + * in a line and need to be re-layed out. + */ + + RemoveFromBaseChunk(chunkPtr); + } + + ciPtr->baseChunkPtr = NULL; + ciPtr->chars = NULL; + ciPtr->numBytes = 0; +#endif /* TK_LAYOUT_WITH_BASE_CHUNKS */ + + ckfree(ciPtr); + chunkPtr->clientData = NULL; + } +} + +/* + *-------------------------------------------------------------- + * + * CharMeasureProc -- + * + * This function is called to determine which character in a character + * chunk lies over a given x-coordinate. + * + * Results: + * The return value is the index *within the chunk* of the character that + * covers the position given by "x". + * + * Side effects: + * None. + * + *-------------------------------------------------------------- + */ + +static int +CharMeasureProc( + TkTextDispChunk *chunkPtr, /* Chunk containing desired coord. */ + int x) /* X-coordinate, in same coordinate system as + * chunkPtr->x. */ +{ + int endX; + + return CharChunkMeasureChars(chunkPtr, NULL, 0, 0, chunkPtr->numBytes-1, + chunkPtr->x, x, 0, &endX); /* CHAR OFFSET */ +} + +/* + *-------------------------------------------------------------- + * + * CharBboxProc -- + * + * This function is called to compute the bounding box of the area + * occupied by a single character. + * + * Results: + * There is no return value. *xPtr and *yPtr are filled in with the + * coordinates of the upper left corner of the character, and *widthPtr + * and *heightPtr are filled in with the dimensions of the character in + * pixels. Note: not all of the returned bbox is necessarily visible on + * the screen (the rightmost part might be off-screen to the right, and + * the bottommost part might be off-screen to the bottom). + * + * Side effects: + * None. + * + *-------------------------------------------------------------- + */ + +static void +CharBboxProc( + TkText *textPtr, + TkTextDispChunk *chunkPtr, /* Chunk containing desired char. */ + int byteIndex, /* Byte offset of desired character within the + * chunk. */ + int y, /* Topmost pixel in area allocated for this + * line. */ + int lineHeight, /* Height of line, in pixels. */ + int baseline, /* Location of line's baseline, in pixels + * measured down from y. */ + int *xPtr, int *yPtr, /* Gets filled in with coords of character's + * upper-left pixel. X-coord is in same + * coordinate system as chunkPtr->x. */ + int *widthPtr, /* Gets filled in with width of character, in + * pixels. */ + int *heightPtr) /* Gets filled in with height of character, in + * pixels. */ +{ + CharInfo *ciPtr = chunkPtr->clientData; + int maxX; + + maxX = chunkPtr->width + chunkPtr->x; + CharChunkMeasureChars(chunkPtr, NULL, 0, 0, byteIndex, + chunkPtr->x, -1, 0, xPtr); + + if (byteIndex == ciPtr->numBytes) { + /* + * This situation only happens if the last character in a line is a + * space character, in which case it absorbs all of the extra space in + * the line (see TkTextCharLayoutProc). + */ + + *widthPtr = maxX - *xPtr; + } else if ((ciPtr->chars[byteIndex] == '\t') + && (byteIndex == ciPtr->numBytes - 1)) { + /* + * The desired character is a tab character that terminates a chunk; + * give it all the space left in the chunk. + */ + + *widthPtr = maxX - *xPtr; + } else { + CharChunkMeasureChars(chunkPtr, NULL, 0, byteIndex, byteIndex+1, + *xPtr, -1, 0, widthPtr); + if (*widthPtr > maxX) { + *widthPtr = maxX - *xPtr; + } else { + *widthPtr -= *xPtr; + } + } + *yPtr = y + baseline - chunkPtr->minAscent; + *heightPtr = chunkPtr->minAscent + chunkPtr->minDescent; +} + +/* + *---------------------------------------------------------------------- + * + * AdjustForTab -- + * + * This function is called to move a series of chunks right in order to + * align them with a tab stop. + * + * Results: + * None. + * + * Side effects: + * The width of chunkPtr gets adjusted so that it absorbs the extra space + * due to the tab. The x locations in all the chunks after chunkPtr are + * adjusted rightward to align with the tab stop given by tabArrayPtr and + * index. + * + *---------------------------------------------------------------------- + */ + +static void +AdjustForTab( + TkText *textPtr, /* Information about the text widget as a + * whole. */ + TkTextTabArray *tabArrayPtr,/* Information about the tab stops that apply + * to this line. May be NULL to indicate + * default tabbing (every 8 chars). */ + int index, /* Index of current tab stop. */ + TkTextDispChunk *chunkPtr) /* Chunk whose last character is the tab; the + * following chunks contain information to be + * shifted right. */ +{ + int x, desired, delta, width, decimal, i, gotDigit; + TkTextDispChunk *chunkPtr2, *decimalChunkPtr; + CharInfo *ciPtr; + int tabX, spaceWidth; + const char *p; + TkTextTabAlign alignment; + + if (chunkPtr->nextPtr == NULL) { + /* + * Nothing after the actual tab; just return. + */ + + return; + } + + x = chunkPtr->nextPtr->x; + + /* + * If no tab information has been given, assuming tab stops are at 8 + * average-sized characters. Still ensure we respect the tabular versus + * wordprocessor tab style. + */ + + if ((tabArrayPtr == NULL) || (tabArrayPtr->numTabs == 0)) { + /* + * No tab information has been given, so use the default + * interpretation of tabs. + */ + + if (textPtr->tabStyle == TK_TEXT_TABSTYLE_TABULAR) { + int tabWidth = Tk_TextWidth(textPtr->tkfont, "0", 1) * 8; + if (tabWidth == 0) { + tabWidth = 1; + } + + desired = tabWidth * (index + 1); + } else { + desired = NextTabStop(textPtr->tkfont, x, 0); + } + + goto update; + } + + if (index < tabArrayPtr->numTabs) { + alignment = tabArrayPtr->tabs[index].alignment; + tabX = tabArrayPtr->tabs[index].location; + } else { + /* + * Ran out of tab stops; compute a tab position by extrapolating from + * the last two tab positions. + */ + + tabX = (int) (tabArrayPtr->lastTab + + (index + 1 - tabArrayPtr->numTabs)*tabArrayPtr->tabIncrement + + 0.5); + alignment = tabArrayPtr->tabs[tabArrayPtr->numTabs-1].alignment; + } + + if (alignment == LEFT) { + desired = tabX; + goto update; + } + + if ((alignment == CENTER) || (alignment == RIGHT)) { + /* + * Compute the width of all the information in the tab group, then use + * it to pick a desired location. + */ + + width = 0; + for (chunkPtr2 = chunkPtr->nextPtr; chunkPtr2 != NULL; + chunkPtr2 = chunkPtr2->nextPtr) { + width += chunkPtr2->width; + } + if (alignment == CENTER) { + desired = tabX - width/2; + } else { + desired = tabX - width; + } + goto update; + } + + /* + * Must be numeric alignment. Search through the text to be tabbed, + * looking for the last , or . before the first character that isn't a + * number, comma, period, or sign. + */ + + decimalChunkPtr = NULL; + decimal = gotDigit = 0; + for (chunkPtr2 = chunkPtr->nextPtr; chunkPtr2 != NULL; + chunkPtr2 = chunkPtr2->nextPtr) { + if (chunkPtr2->displayProc != CharDisplayProc) { + continue; + } + ciPtr = chunkPtr2->clientData; + for (p = ciPtr->chars, i = 0; i < ciPtr->numBytes; p++, i++) { + if (isdigit(UCHAR(*p))) { + gotDigit = 1; + } else if ((*p == '.') || (*p == ',')) { + decimal = p-ciPtr->chars; + decimalChunkPtr = chunkPtr2; + } else if (gotDigit) { + if (decimalChunkPtr == NULL) { + decimal = p-ciPtr->chars; + decimalChunkPtr = chunkPtr2; + } + goto endOfNumber; + } + } + } + + endOfNumber: + if (decimalChunkPtr != NULL) { + int curX; + + ciPtr = decimalChunkPtr->clientData; + CharChunkMeasureChars(decimalChunkPtr, NULL, 0, 0, decimal, + decimalChunkPtr->x, -1, 0, &curX); + desired = tabX - (curX - x); + goto update; + } + + /* + * There wasn't a decimal point. Right justify the text. + */ + + width = 0; + for (chunkPtr2 = chunkPtr->nextPtr; chunkPtr2 != NULL; + chunkPtr2 = chunkPtr2->nextPtr) { + width += chunkPtr2->width; + } + desired = tabX - width; + + /* + * Shift all of the chunks to the right so that the left edge is at the + * desired location, then expand the chunk containing the tab. Be sure + * that the tab occupies at least the width of a space character. + */ + + update: + delta = desired - x; + MeasureChars(textPtr->tkfont, " ", 1, 0, 1, 0, -1, 0, &spaceWidth); + if (delta < spaceWidth) { + delta = spaceWidth; + } + for (chunkPtr2 = chunkPtr->nextPtr; chunkPtr2 != NULL; + chunkPtr2 = chunkPtr2->nextPtr) { + chunkPtr2->x += delta; + } + chunkPtr->width += delta; +} + +/* + *---------------------------------------------------------------------- + * + * SizeOfTab -- + * + * This returns an estimate of the amount of white space that will be + * consumed by a tab. + * + * Results: + * The return value is the minimum number of pixels that will be occupied + * by the next tab of tabArrayPtr, assuming that the current position on + * the line is x and the end of the line is maxX. The 'next tab' is + * determined by a combination of the current position (x) which it must + * be equal to or beyond, and the tab count in indexPtr. + * + * For numeric tabs, this is a conservative estimate. The return value is + * always >= 0. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +SizeOfTab( + TkText *textPtr, /* Information about the text widget as a + * whole. */ + int tabStyle, /* One of TK_TEXT_TABSTYLE_TABULAR + * or TK_TEXT_TABSTYLE_WORDPROCESSOR. */ + TkTextTabArray *tabArrayPtr,/* Information about the tab stops that apply + * to this line. NULL means use default + * tabbing (every 8 chars.) */ + int *indexPtr, /* Contains index of previous tab stop, will + * be updated to reflect the number of stops + * used. */ + int x, /* Current x-location in line. */ + int maxX) /* X-location of pixel just past the right + * edge of the line. */ +{ + int tabX, result, index, spaceWidth, tabWidth; + TkTextTabAlign alignment; + + index = *indexPtr; + + if ((tabArrayPtr == NULL) || (tabArrayPtr->numTabs == 0)) { + /* + * We're using a default tab spacing of 8 characters. + */ + + tabWidth = Tk_TextWidth(textPtr->tkfont, "0", 1) * 8; + if (tabWidth == 0) { + tabWidth = 1; + } + } else { + tabWidth = 0; /* Avoid compiler error. */ + } + + do { + /* + * We were given the count before this tab, so increment it first. + */ + + index++; + + if ((tabArrayPtr == NULL) || (tabArrayPtr->numTabs == 0)) { + /* + * We're using a default tab spacing calculated above. + */ + + tabX = tabWidth * (index + 1); + alignment = LEFT; + } else if (index < tabArrayPtr->numTabs) { + tabX = tabArrayPtr->tabs[index].location; + alignment = tabArrayPtr->tabs[index].alignment; + } else { + /* + * Ran out of tab stops; compute a tab position by extrapolating. + */ + + tabX = (int) (tabArrayPtr->lastTab + + (index + 1 - tabArrayPtr->numTabs) + * tabArrayPtr->tabIncrement + 0.5); + alignment = tabArrayPtr->tabs[tabArrayPtr->numTabs-1].alignment; + } + + /* + * If this tab stop is before the current x position, then we have two + * cases: + * + * With 'wordprocessor' style tabs, we must obviously continue until + * we reach the text tab stop. + * + * With 'tabular' style tabs, we always use the index'th tab stop. + */ + } while (tabX <= x && (tabStyle == TK_TEXT_TABSTYLE_WORDPROCESSOR)); + + /* + * Inform our caller of how many tab stops we've used up. + */ + + *indexPtr = index; + + if (alignment == CENTER) { + /* + * Be very careful in the arithmetic below, because maxX may be the + * largest positive number: watch out for integer overflow. + */ + + if ((maxX-tabX) < (tabX - x)) { + result = (maxX - x) - 2*(maxX - tabX); + } else { + result = 0; + } + goto done; + } + if (alignment == RIGHT) { + result = 0; + goto done; + } + + /* + * Note: this treats NUMERIC alignment the same as LEFT alignment, which + * is somewhat conservative. However, it's pretty tricky at this point to + * figure out exactly where the damn decimal point will be. + */ + + if (tabX > x) { + result = tabX - x; + } else { + result = 0; + } + + done: + MeasureChars(textPtr->tkfont, " ", 1, 0, 1, 0, -1, 0, &spaceWidth); + if (result < spaceWidth) { + result = spaceWidth; + } + return result; +} + +/* + *--------------------------------------------------------------------------- + * + * NextTabStop -- + * + * Given the current position, determine where the next default tab stop + * would be located. This function is called when the current chunk in + * the text has no tabs defined and so the default tab spacing for the + * font should be used, provided we are using wordprocessor style tabs. + * + * Results: + * The location in pixels of the next tab stop. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ + +static int +NextTabStop( + Tk_Font tkfont, /* Font in which chunk that contains tab stop + * will be drawn. */ + int x, /* X-position in pixels where last character + * was drawn. The next tab stop occurs + * somewhere after this location. */ + int tabOrigin) /* The origin for tab stops. May be non-zero + * if text has been scrolled. */ +{ + int tabWidth, rem; + + tabWidth = Tk_TextWidth(tkfont, "0", 1) * 8; + if (tabWidth == 0) { + tabWidth = 1; + } + + x += tabWidth; + rem = (x - tabOrigin) % tabWidth; + if (rem < 0) { + rem += tabWidth; + } + x -= rem; + return x; +} + +/* + *--------------------------------------------------------------------------- + * + * MeasureChars -- + * + * Determine the number of characters from the string that will fit in + * the given horizontal span. The measurement is done under the + * assumption that Tk_DrawChars will be used to actually display the + * characters. + * + * If tabs are encountered in the string, they will be ignored (they + * should only occur as last character of the string anyway). + * + * If a newline is encountered in the string, the line will be broken at + * that point. + * + * Results: + * The return value is the number of bytes from the range of start to end + * in source that fit in the span given by startX and maxX. *nextXPtr is + * filled in with the x-coordinate at which the first character that + * didn't fit would be drawn, if it were to be drawn. + * + * Side effects: + * None. + * + *-------------------------------------------------------------- + */ + +static int +MeasureChars( + Tk_Font tkfont, /* Font in which to draw characters. */ + const char *source, /* Characters to be displayed. Need not be + * NULL-terminated. */ + int maxBytes, /* Maximum # of bytes to consider from + * source. */ + int rangeStart, int rangeLength, + /* Range of bytes to consider in source.*/ + int startX, /* X-position at which first character will be + * drawn. */ + int maxX, /* Don't consider any character that would + * cross this x-position. */ + int flags, /* Flags to pass to Tk_MeasureChars. */ + int *nextXPtr) /* Return x-position of terminating character + * here. */ +{ + int curX, width, ch; + const char *special, *end, *start; + + ch = 0; /* lint. */ + curX = startX; + start = source + rangeStart; + end = start + rangeLength; + special = start; + while (start < end) { + if (start >= special) { + /* + * Find the next special character in the string. + */ + + for (special = start; special < end; special++) { + ch = *special; + if ((ch == '\t') || (ch == '\n')) { + break; + } + } + } + + /* + * Special points at the next special character (or the end of the + * string). Process characters between start and special. + */ + + if ((maxX >= 0) && (curX >= maxX)) { + break; + } +#if TK_DRAW_IN_CONTEXT + start += TkpMeasureCharsInContext(tkfont, source, maxBytes, + start - source, special - start, + maxX >= 0 ? maxX - curX : -1, flags, &width); +#else + (void) maxBytes; + start += Tk_MeasureChars(tkfont, start, special - start, + maxX >= 0 ? maxX - curX : -1, flags, &width); +#endif /* TK_DRAW_IN_CONTEXT */ + curX += width; + if (start < special) { + /* + * No more chars fit in line. + */ + + break; + } + if (special < end) { + if (ch != '\t') { + break; + } + start++; + } + } + + *nextXPtr = curX; + return start - (source+rangeStart); +} + +/* + *---------------------------------------------------------------------- + * + * TextGetScrollInfoObj -- + * + * This function is invoked to parse "xview" and "yview" scrolling + * commands for text widgets using the new scrolling command syntax + * ("moveto" or "scroll" options). It extends the public + * Tk_GetScrollInfoObj function with the addition of "pixels" as a valid + * unit alongside "pages" and "units". It is a shame the core API isn't + * more flexible in this regard. + * + * Results: + * The return value is either TKTEXT_SCROLL_MOVETO, TKTEXT_SCROLL_PAGES, + * TKTEXT_SCROLL_UNITS, TKTEXT_SCROLL_PIXELS or TKTEXT_SCROLL_ERROR. This + * indicates whether the command was successfully parsed and what form + * the command took. If TKTEXT_SCROLL_MOVETO, *dblPtr is filled in with + * the desired position; if TKTEXT_SCROLL_PAGES, TKTEXT_SCROLL_PIXELS or + * TKTEXT_SCROLL_UNITS, *intPtr is filled in with the number of + * pages/pixels/lines to move (may be negative); if TKTEXT_SCROLL_ERROR, + * the interp's result contains an error message. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +TextGetScrollInfoObj( + Tcl_Interp *interp, /* Used for error reporting. */ + TkText *textPtr, /* Information about the text widget. */ + int objc, /* # arguments for command. */ + Tcl_Obj *const objv[], /* Arguments for command. */ + double *dblPtr, /* Filled in with argument "moveto" option, if + * any. */ + int *intPtr) /* Filled in with number of pages or lines or + * pixels to scroll, if any. */ +{ + static const char *const subcommands[] = { + "moveto", "scroll", NULL + }; + enum viewSubcmds { + VIEW_MOVETO, VIEW_SCROLL + }; + static const char *const units[] = { + "units", "pages", "pixels", NULL + }; + enum viewUnits { + VIEW_SCROLL_UNITS, VIEW_SCROLL_PAGES, VIEW_SCROLL_PIXELS + }; + int index; + + if (Tcl_GetIndexFromObjStruct(interp, objv[2], subcommands, + sizeof(char *), "option", 0, &index) != TCL_OK) { + return TKTEXT_SCROLL_ERROR; + } + + switch ((enum viewSubcmds) index) { + case VIEW_MOVETO: + if (objc != 4) { + Tcl_WrongNumArgs(interp, 3, objv, "fraction"); + return TKTEXT_SCROLL_ERROR; + } + if (Tcl_GetDoubleFromObj(interp, objv[3], dblPtr) != TCL_OK) { + return TKTEXT_SCROLL_ERROR; + } + return TKTEXT_SCROLL_MOVETO; + case VIEW_SCROLL: + if (objc != 5) { + Tcl_WrongNumArgs(interp, 3, objv, "number units|pages|pixels"); + return TKTEXT_SCROLL_ERROR; + } + if (Tcl_GetIndexFromObjStruct(interp, objv[4], units, + sizeof(char *), "argument", 0, &index) != TCL_OK) { + return TKTEXT_SCROLL_ERROR; + } + switch ((enum viewUnits) index) { + case VIEW_SCROLL_PAGES: + if (Tcl_GetIntFromObj(interp, objv[3], intPtr) != TCL_OK) { + return TKTEXT_SCROLL_ERROR; + } + return TKTEXT_SCROLL_PAGES; + case VIEW_SCROLL_PIXELS: + if (Tk_GetPixelsFromObj(interp, textPtr->tkwin, objv[3], + intPtr) != TCL_OK) { + return TKTEXT_SCROLL_ERROR; + } + return TKTEXT_SCROLL_PIXELS; + case VIEW_SCROLL_UNITS: + if (Tcl_GetIntFromObj(interp, objv[3], intPtr) != TCL_OK) { + return TKTEXT_SCROLL_ERROR; + } + return TKTEXT_SCROLL_UNITS; + } + } + Tcl_Panic("unexpected switch fallthrough"); + return TKTEXT_SCROLL_ERROR; +} + +#if TK_LAYOUT_WITH_BASE_CHUNKS +/* + *---------------------------------------------------------------------- + * + * FinalizeBaseChunk -- + * + * This procedure makes sure that all the chunks of the stretch are + * up-to-date. It is invoked when the LayoutProc has been called for all + * chunks and the base chunk is stable. + * + * Results: + * None. + * + * Side effects: + * The CharInfo.chars of all dependent chunks point into + * BaseCharInfo.baseChars for easy access (and compatibility). + * + *---------------------------------------------------------------------- + */ + +static void +FinalizeBaseChunk( + TkTextDispChunk *addChunkPtr) + /* An additional chunk to add to the stretch, + * even though it may not be in the linked + * list yet. Used by the LayoutProc, otherwise + * NULL. */ +{ + const char *baseChars; + TkTextDispChunk *chunkPtr; + CharInfo *ciPtr; +#if TK_DRAW_IN_CONTEXT + int widthAdjust = 0; + int newwidth; +#endif /* TK_DRAW_IN_CONTEXT */ + + if (baseCharChunkPtr == NULL) { + return; + } + + baseChars = Tcl_DStringValue( + &((BaseCharInfo *) baseCharChunkPtr->clientData)->baseChars); + + for (chunkPtr = baseCharChunkPtr; chunkPtr != NULL; + chunkPtr = chunkPtr->nextPtr) { +#if TK_DRAW_IN_CONTEXT + chunkPtr->x += widthAdjust; +#endif /* TK_DRAW_IN_CONTEXT */ + + if (chunkPtr->displayProc != CharDisplayProc) { + continue; + } + ciPtr = chunkPtr->clientData; + if (ciPtr->baseChunkPtr != baseCharChunkPtr) { + break; + } + ciPtr->chars = baseChars + ciPtr->baseOffset; + +#if TK_DRAW_IN_CONTEXT + newwidth = 0; + CharChunkMeasureChars(chunkPtr, NULL, 0, 0, -1, 0, -1, 0, &newwidth); + if (newwidth != chunkPtr->width) { + widthAdjust += newwidth - chunkPtr->width; + chunkPtr->width = newwidth; + } +#endif /* TK_DRAW_IN_CONTEXT */ + } + + if (addChunkPtr != NULL) { + ciPtr = addChunkPtr->clientData; + ciPtr->chars = baseChars + ciPtr->baseOffset; + +#if TK_DRAW_IN_CONTEXT + addChunkPtr->x += widthAdjust; + CharChunkMeasureChars(addChunkPtr, NULL, 0, 0, -1, 0, -1, 0, + &addChunkPtr->width); +#endif /* TK_DRAW_IN_CONTEXT */ + } + + baseCharChunkPtr = NULL; +} + +/* + *---------------------------------------------------------------------- + * + * FreeBaseChunk -- + * + * This procedure makes sure that all the chunks of the stretch are + * disconnected from the base chunk and the base chunk specific data is + * freed. It is invoked from the UndisplayProc. The procedure doesn't + * ckfree the base chunk clientData itself, that's up to the main + * UndisplayProc. + * + * Results: + * None. + * + * Side effects: + * The CharInfo.chars of all dependent chunks are set to NULL. Memory + * that belongs specifically to the base chunk is freed. + * + *---------------------------------------------------------------------- + */ + +static void +FreeBaseChunk( + TkTextDispChunk *baseChunkPtr) + /* The base chunk of the stretch and head of + * the linked list. */ +{ + TkTextDispChunk *chunkPtr; + CharInfo *ciPtr; + + if (baseCharChunkPtr == baseChunkPtr) { + baseCharChunkPtr = NULL; + } + + for (chunkPtr=baseChunkPtr; chunkPtr!=NULL; chunkPtr=chunkPtr->nextPtr) { + if (chunkPtr->undisplayProc != CharUndisplayProc) { + continue; + } + ciPtr = chunkPtr->clientData; + if (ciPtr->baseChunkPtr != baseChunkPtr) { + break; + } + + ciPtr->baseChunkPtr = NULL; + ciPtr->chars = NULL; + } + + if (baseChunkPtr) { + Tcl_DStringFree(&((BaseCharInfo *) baseChunkPtr->clientData)->baseChars); + } +} + +/* + *---------------------------------------------------------------------- + * + * IsSameFGStyle -- + * + * Compare the foreground attributes of two styles. Specifically must + * consider: foreground color, font, font style and font decorations, + * elide, "offset" and foreground stipple. Do *not* consider: background + * color, border, relief or background stipple. + * + * If we use TkpDrawCharsInContext(), we also don't need to check + * foreground color, font decorations, elide, offset and foreground + * stipple, so all that is left is font (including font size and font + * style) and "offset". + * + * Results: + * 1 if the two styles match, 0 otherwise. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +IsSameFGStyle( + TextStyle *style1, + TextStyle *style2) +{ + StyleValues *sv1; + StyleValues *sv2; + + if (style1 == style2) { + return 1; + } + +#if !TK_DRAW_IN_CONTEXT + if ( +#ifdef MAC_OSX_TK + !TkMacOSXCompareColors(style1->fgGC->foreground, + style2->fgGC->foreground) +#else + style1->fgGC->foreground != style2->fgGC->foreground +#endif + ) { + return 0; + } +#endif /* !TK_DRAW_IN_CONTEXT */ + + sv1 = style1->sValuePtr; + sv2 = style2->sValuePtr; + +#if TK_DRAW_IN_CONTEXT + return sv1->tkfont == sv2->tkfont && sv1->offset == sv2->offset; +#else + return sv1->tkfont == sv2->tkfont + && sv1->underline == sv2->underline + && sv1->overstrike == sv2->overstrike + && sv1->elide == sv2->elide + && sv1->offset == sv2->offset + && sv1->fgStipple == sv1->fgStipple; +#endif /* TK_DRAW_IN_CONTEXT */ +} + +/* + *---------------------------------------------------------------------- + * + * RemoveFromBaseChunk -- + * + * This procedure removes a chunk from the stretch as a result of + * UndisplayProc. The chunk in question should be the last in a stretch. + * This happens during re-layouting of the break position. + * + * Results: + * None. + * + * Side effects: + * The characters that belong to this chunk are removed from the base + * chunk. It is assumed that LayoutProc and FinalizeBaseChunk are called + * next to repair any damage that this causes to the integrity of the + * stretch and the other chunks. For that reason the base chunk is also + * put into baseCharChunkPtr automatically, so that LayoutProc can resume + * correctly. + * + *---------------------------------------------------------------------- + */ + +static void +RemoveFromBaseChunk( + TkTextDispChunk *chunkPtr) /* The chunk to remove from the end of the + * stretch. */ +{ + CharInfo *ciPtr; + BaseCharInfo *bciPtr; + + if (chunkPtr->displayProc != CharDisplayProc) { +#ifdef DEBUG_LAYOUT_WITH_BASE_CHUNKS + fprintf(stderr,"RemoveFromBaseChunk called with wrong chunk type\n"); +#endif + return; + } + + /* + * Reinstitute this base chunk for re-layout. + */ + + ciPtr = chunkPtr->clientData; + baseCharChunkPtr = ciPtr->baseChunkPtr; + + /* + * Remove the chunk data from the base chunk data. + */ + + bciPtr = baseCharChunkPtr->clientData; + + if ((ciPtr->baseOffset + ciPtr->numBytes) + != Tcl_DStringLength(&bciPtr->baseChars)) { +#ifdef DEBUG_LAYOUT_WITH_BASE_CHUNKS + fprintf(stderr,"RemoveFromBaseChunk called with wrong chunk " + "(not last)\n"); +#endif + } + + Tcl_DStringSetLength(&bciPtr->baseChars, ciPtr->baseOffset); + + /* + * Invalidate the stored pixel width of the base chunk. + */ + + bciPtr->width = -1; +} +#endif /* TK_LAYOUT_WITH_BASE_CHUNKS */ + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ |