--- projs/trunk/shared_source/c_tk_base_7_5_w_mods/tktextdisp.c 2016/11/05 10:54:17 69 +++ projs/trunk/shared_source/c_tk_base_7_5_w_mods/tktextdisp.c 2016/11/05 11:07:06 71 @@ -1,5198 +1,5198 @@ -/* $Header$ */ - -/* - * 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. - * - * 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. - * - * RCS: @(#) $Id: tktextdisp.c,v 1.1.1.1 2001/06/13 05:10:16 dtashley Exp $ - */ - -#include "tkPort.h" -#include "tkInt.h" -#include "tkText.h" - -#ifdef __WIN32__ -#include "tkWinInt.h" -#endif - -/* - * 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. */ - int offset; /* Offset in pixels of baseline, relative to - * baseline of line. */ - int overstrike; /* Non-zero means draw overstrike through - * text. */ - int rMargin; /* Right margin, in pixels. */ - 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 underline; /* Non-zero means draw underline underneath - * text. */ - int elide; /* Non-zero means draw text */ - 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. */ - 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 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 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. -1 means line isn't - * currently visible on display and must be - * redrawn. This is used to move lines by - * scrolling rather than re-drawing. */ - 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. */ - 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, and - * (b) 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 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. - * IS_DISABLED - This Dline cannot be edited. - */ - -#define HAS_3D_BORDER 1 -#define NEW_LAYOUT 2 -#define TOP_LINE 4 -#define BOTTOM_LINE 8 -#define IS_DISABLED 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. */ - 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 newByteOffset; /* Desired x scroll position, measured as the - * number of average-size characters off-screen - * to the left for a line with no left - * margin. */ - int curPixelOffset; /* 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 scanMarkIndex; /* Byte index of character that was at the - * left edge of the window when the scan - * started. */ - int scanMarkX; /* X-position of mouse at time scan started. */ - int scanTotalScroll; /* Total scrolling (in screen lines) 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. */ -} TextDInfo; - -/* - * In TkTextDispChunk structures for character segments, the clientData - * field points to one of the following structures: - */ - -typedef struct CharInfo { - int numBytes; /* Number of bytes to display. */ - char chars[4]; /* UTF characters to display. Actual size - * will be numBytes, not 4. THIS MUST BE - * THE LAST FIELD IN THE STRUCTURE. */ -} CharInfo; - -/* - * 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 - -/* - * 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. */ - -/* - * Forward declarations for procedures defined later in this file: - */ - -static void AdjustForTab _ANSI_ARGS_((TkText *textPtr, - TkTextTabArray *tabArrayPtr, int index, - TkTextDispChunk *chunkPtr)); -static void CharBboxProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr, - int index, int y, int lineHeight, int baseline, - int *xPtr, int *yPtr, int *widthPtr, - int *heightPtr)); -static void CharDisplayProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr, - int x, int y, int height, int baseline, - Display *display, Drawable dst, int screenY)); -static int CharMeasureProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr, - int x)); -static void CharUndisplayProc _ANSI_ARGS_((TkText *textPtr, - TkTextDispChunk *chunkPtr)); - -/* - Definitions of elided procs. - Compiler can't inline these since we use pointers to these functions. - ElideDisplayProc, ElideUndisplayProc special-cased for speed, - as potentially many elided DLine chunks if large, tag toggle-filled - elided region. -*/ -static void ElideBboxProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr, - int index, int y, int lineHeight, int baseline, - int *xPtr, int *yPtr, int *widthPtr, - int *heightPtr)); -static int ElideMeasureProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr, - int x)); - -static void DisplayDLine _ANSI_ARGS_((TkText *textPtr, - DLine *dlPtr, DLine *prevPtr, Pixmap pixmap)); -static void DisplayLineBackground _ANSI_ARGS_((TkText *textPtr, - DLine *dlPtr, DLine *prevPtr, Pixmap pixmap)); -static void DisplayText _ANSI_ARGS_((ClientData clientData)); -static DLine * FindDLine _ANSI_ARGS_((DLine *dlPtr, - TkTextIndex *indexPtr)); -static void FreeDLines _ANSI_ARGS_((TkText *textPtr, - DLine *firstPtr, DLine *lastPtr, int unlink)); -static void FreeStyle _ANSI_ARGS_((TkText *textPtr, - TextStyle *stylePtr)); -static TextStyle * GetStyle _ANSI_ARGS_((TkText *textPtr, - TkTextIndex *indexPtr)); -static void GetXView _ANSI_ARGS_((Tcl_Interp *interp, - TkText *textPtr, int report)); -static void GetYView _ANSI_ARGS_((Tcl_Interp *interp, - TkText *textPtr, int report)); -static DLine * LayoutDLine _ANSI_ARGS_((TkText *textPtr, - TkTextIndex *indexPtr)); -static int MeasureChars _ANSI_ARGS_((Tk_Font tkfont, - CONST char *source, int maxBytes, int startX, - int maxX, int tabOrigin, int *nextXPtr)); -static void MeasureUp _ANSI_ARGS_((TkText *textPtr, - TkTextIndex *srcPtr, int distance, - TkTextIndex *dstPtr)); -static int NextTabStop _ANSI_ARGS_((Tk_Font tkfont, int x, - int tabOrigin)); -static void UpdateDisplayInfo _ANSI_ARGS_((TkText *textPtr)); -static void ScrollByLines _ANSI_ARGS_((TkText *textPtr, - int offset)); -static int SizeOfTab _ANSI_ARGS_((TkText *textPtr, - TkTextTabArray *tabArrayPtr, int index, int x, - int maxX)); -static void TextInvalidateRegion _ANSI_ARGS_((TkText *textPtr, - TkRegion region)); - - -/* - *---------------------------------------------------------------------- - * - * TkTextCreateDInfo -- - * - * This procedure 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(textPtr) - TkText *textPtr; /* Overall information for text widget. */ -{ - register TextDInfo *dInfoPtr; - XGCValues gcValues; - - dInfoPtr = (TextDInfo *) 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->newByteOffset = 0; - dInfoPtr->curPixelOffset = 0; - dInfoPtr->maxLength = 0; - dInfoPtr->xScrollFirst = -1; - dInfoPtr->xScrollLast = -1; - dInfoPtr->yScrollFirst = -1; - dInfoPtr->yScrollLast = -1; - dInfoPtr->scanMarkIndex = 0; - dInfoPtr->scanMarkX = 0; - dInfoPtr->scanTotalScroll = 0; - dInfoPtr->scanMarkY = 0; - dInfoPtr->dLinesInvalidated = 0; - dInfoPtr->flags = DINFO_OUT_OF_DATE; - textPtr->dInfoPtr = dInfoPtr; -} - -/* - *---------------------------------------------------------------------- - * - * TkTextFreeDInfo -- - * - * This procedure 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(textPtr) - 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, (DLine *) NULL, 1); - 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, (ClientData) textPtr); - } - ckfree((char *) dInfoPtr); -} - -/* - *---------------------------------------------------------------------- - * - * GetStyle -- - * - * This procedure 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(textPtr, indexPtr) - TkText *textPtr; /* Overall information about text widget. */ - 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, new, i; - 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 spacing1Prio, spacing2Prio, spacing3Prio; - int overstrikePrio, tabPrio, 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, &numTags); - borderPrio = borderWidthPrio = reliefPrio = bgStipplePrio = -1; - fgPrio = fontPrio = fgStipplePrio = -1; - underlinePrio = elidePrio = justifyPrio = offsetPrio = -1; - lMargin1Prio = lMargin2Prio = rMarginPrio = -1; - spacing1Prio = spacing2Prio = spacing3Prio = -1; - overstrikePrio = tabPrio = wrapPrio = -1; - memset((VOID *) &styleValues, 0, sizeof(StyleValues)); - styleValues.relief = TK_RELIEF_FLAT; - styleValues.fgColor = 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.wrapMode = textPtr->wrapMode; - styleValues.elide = 0; - for (i = 0 ; i < numTags; i++) { - tagPtr = tagPtrs[i]; - - /* - * On Windows and Mac, we need to skip the selection tag if - * we don't have focus. - */ - -#ifndef ALWAYS_SHOW_SELECTION - if ((tagPtr == textPtr->selTagPtr) && !(textPtr->flags & GOT_FOCUS)) { - continue; - } -#endif - - if ((tagPtr->border != NULL) && (tagPtr->priority > borderPrio)) { - styleValues.border = tagPtr->border; - borderPrio = tagPtr->priority; - } - if ((tagPtr->bdString != NULL) - && (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 ((tagPtr->fgColor != None) && (tagPtr->priority > fgPrio)) { - styleValues.fgColor = tagPtr->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->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->rMarginString != NULL) - && (tagPtr->priority > rMarginPrio)) { - styleValues.rMargin = tagPtr->rMargin; - rMarginPrio = 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->tabString != NULL) - && (tagPtr->priority > tabPrio)) { - styleValues.tabArrayPtr = tagPtr->tabArrayPtr; - tabPrio = tagPtr->priority; - } - if ((tagPtr->underlineString != NULL) - && (tagPtr->priority > underlinePrio)) { - styleValues.underline = tagPtr->underline; - underlinePrio = tagPtr->priority; - } - 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((char *) tagPtrs); - } - - /* - * Use an existing style if there's one around that matches. - */ - - hPtr = Tcl_CreateHashEntry(&textPtr->dInfoPtr->styleTable, - (char *) &styleValues, &new); - if (!new) { - stylePtr = (TextStyle *) Tcl_GetHashValue(hPtr); - stylePtr->refCount++; - return stylePtr; - } - - /* - * No existing style matched. Make a new one. - */ - - stylePtr = (TextStyle *) 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); - stylePtr->sValuePtr = (StyleValues *) - Tcl_GetHashKey(&textPtr->dInfoPtr->styleTable, hPtr); - stylePtr->hPtr = hPtr; - Tcl_SetHashValue(hPtr, stylePtr); - return stylePtr; -} - -/* - *---------------------------------------------------------------------- - * - * FreeStyle -- - * - * This procedure 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(textPtr, stylePtr) - 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); - } - Tcl_DeleteHashEntry(stylePtr->hPtr); - ckfree((char *) stylePtr); - } -} - -/* - *---------------------------------------------------------------------- - * - * LayoutDLine -- - * - * This procedure 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 desribing the - * display line. All fields are filled in and correct except for - * y and nextPtr. - * - * Side effects: - * Storage is allocated for the new DLine. - * - *---------------------------------------------------------------------- - */ - -static DLine * -LayoutDLine(textPtr, indexPtr) - TkText *textPtr; /* Overall information about text widget. */ - 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 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 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; - - /* - * Create and initialize a new DLine structure. - */ - - dlPtr = (DLine *) ckalloc(sizeof(DLine)); - dlPtr->index = *indexPtr; - dlPtr->byteCount = 0; - dlPtr->y = 0; - dlPtr->oldY = -1; - dlPtr->height = 0; - dlPtr->baseline = 0; - dlPtr->chunkPtr = NULL; - dlPtr->nextPtr = NULL; - dlPtr->flags = NEW_LAYOUT; - - /* - * Special case entirely elide line as there may be 1000s or more - */ - elide = TkTextIsElided(textPtr, indexPtr); /* save a malloc */ - if (elide && indexPtr->byteIndex==0) { - maxBytes = 0; - for (segPtr = indexPtr->linePtr->segPtr; - elide && (segPtr != NULL); - segPtr = segPtr->nextPtr) { - if ((elidesize = segPtr->size) > 0) { - maxBytes += elidesize; - /* - * 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; - } - } - } - - if (elide) { - dlPtr->byteCount = maxBytes; - dlPtr->spaceAbove = dlPtr->spaceBelow = dlPtr->length = 0; - return dlPtr; - } - } - - /* - * 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; - 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). - */ - - for (byteOffset = curIndex.byteIndex, segPtr = curIndex.linePtr->segPtr; - (byteOffset > 0) && (byteOffset >= segPtr->size); - byteOffset -= segPtr->size, segPtr = segPtr->nextPtr) { - /* Empty loop body. */ - } - - while (segPtr != NULL) { - /* - * Every 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 - */ - if (elide && (lastChunkPtr != NULL) - && (lastChunkPtr->displayProc == NULL /*ElideDisplayProc*/)) { - if ((elidesize = segPtr->size - byteOffset) > 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 && chunkPtr != NULL) { - ckfree((char *) chunkPtr); - } - continue; - } - - if (segPtr->typePtr->layoutProc == NULL) { - segPtr = segPtr->nextPtr; - byteOffset = 0; - continue; - } - if (chunkPtr == NULL) { - chunkPtr = (TkTextDispChunk *) ckalloc(sizeof(TkTextDispChunk)); - chunkPtr->nextPtr = 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 (noCharsYet) { - tabArrayPtr = chunkPtr->stylePtr->sValuePtr->tabArrayPtr; - justify = chunkPtr->stylePtr->sValuePtr->justify; - rMargin = chunkPtr->stylePtr->sValuePtr->rMargin; - wrapMode = chunkPtr->stylePtr->sValuePtr->wrapMode; - x = ((curIndex.byteIndex == 0) - ? chunkPtr->stylePtr->sValuePtr->lMargin1 - : chunkPtr->stylePtr->sValuePtr->lMargin2); - if (wrapMode == TEXT_WRAPMODE_NONE) { - maxX = -1; - } else { - maxX = textPtr->dInfoPtr->maxX - textPtr->dInfoPtr->x - - rMargin; - if (maxX < x) { - maxX = x; - } - } - } - - /* - * See if there is a tab in the current chunk; if so, only - * layout characters up to (and including) the tab. - */ - - gotTab = 0; - maxBytes = segPtr->size - byteOffset; - if (!elide && justify == TK_JUSTIFY_LEFT) { - if (segPtr->typePtr == &tkTextCharType) { - char *p; - - for (p = segPtr->body.chars + byteOffset; *p != 0; p++) { - if (*p == '\t') { - maxBytes = (p + 1 - segPtr->body.chars) - byteOffset; - gotTab = 1; - break; - } - } - } - } - 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 = (Tk_ChunkDisplayProc *) NULL; - chunkPtr->undisplayProc = (Tk_ChunkUndisplayProc *) 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((char *) chunkPtr); - } - break; - } - if (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; - } - tabIndex++; - tabChunkPtr = chunkPtr; - tabSize = SizeOfTab(textPtr, 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; - } - - chunkPtr = NULL; - } - if (noCharsYet) { - panic("LayoutDLine couldn't place any characters on a line"); - } - 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; - (*chunkPtr->undisplayProc)(textPtr, chunkPtr); - ckfree((char *) chunkPtr); - } - if (breakByteOffset != breakChunkPtr->numBytes) { - (*breakChunkPtr->undisplayProc)(textPtr, breakChunkPtr); - segPtr = TkTextIndexToSeg(&breakIndex, &byteOffset); - (*segPtr->typePtr->layoutProc)(textPtr, &breakIndex, - segPtr, byteOffset, maxX, breakByteOffset, 0, - wrapMode, breakChunkPtr); - } - 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 left 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; - - /* - * Recompute line length: may have changed because of justification. - */ - - dlPtr->length = lastChunkPtr->x + lastChunkPtr->width; - return dlPtr; -} - -/* - *---------------------------------------------------------------------- - * - * UpdateDisplayInfo -- - * - * This procedure 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 - * procedure doesn't actually bring the display up-to-date. - * - *---------------------------------------------------------------------- - */ - -static void -UpdateDisplayInfo(textPtr) - TkText *textPtr; /* Text widget to update. */ -{ - register TextDInfo *dInfoPtr = textPtr->dInfoPtr; - register DLine *dlPtr, *prevPtr; - TkTextIndex index; - TkTextLine *lastLinePtr; - int y, maxY, pixelOffset, maxOffset; - - 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(dInfoPtr->dLinePtr, &index); - if ((dlPtr != NULL) && (dlPtr != dInfoPtr->dLinePtr)) { - FreeDLines(textPtr, dInfoPtr->dLinePtr, dlPtr, 1); - } - - /* - *-------------------------------------------------------------- - * Scan through the contents of the window from top to bottom, - * recomputing information for lines that are missing. - *-------------------------------------------------------------- - */ - - lastLinePtr = TkBTreeFindLine(textPtr->tree, - TkBTreeNumLines(textPtr->tree)); - dlPtr = dInfoPtr->dLinePtr; - prevPtr = NULL; - y = dInfoPtr->y; - 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(&index, string); - Tcl_SetVar2(textPtr->interp, "tk_textRelayout", (char *) NULL, - string, - TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT); - } - newPtr = LayoutDLine(textPtr, &index); - if (prevPtr == NULL) { - dInfoPtr->dLinePtr = newPtr; - } else { - prevPtr->nextPtr = newPtr; - if (prevPtr->flags & HAS_3D_BORDER) { - prevPtr->oldY = -1; - } - } - 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->oldY = -1; - } - 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, 0); - 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; - TkTextIndexForwBytes(&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, 0); - prevPtr->nextPtr = nextPtr; - dlPtr = nextPtr; - } - } - - /* - * 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, (DLine *) NULL, 1); - - /* - *-------------------------------------------------------------- - * 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. - *-------------------------------------------------------------- - */ - - if (y < maxY) { - int lineNum, spaceLeft, bytesToCount; - DLine *lowestPtr; - - /* - * 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; - lineNum = TkBTreeLineIndex(dInfoPtr->dLinePtr->index.linePtr); - bytesToCount = dInfoPtr->dLinePtr->index.byteIndex; - if (bytesToCount == 0) { - bytesToCount = INT_MAX; - lineNum--; - } - for ( ; (lineNum >= 0) && (spaceLeft > 0); lineNum--) { - index.linePtr = TkBTreeFindLine(textPtr->tree, lineNum); - index.byteIndex = 0; - lowestPtr = NULL; - - do { - dlPtr = LayoutDLine(textPtr, &index); - dlPtr->nextPtr = lowestPtr; - lowestPtr = dlPtr; - if (dlPtr->length == 0 && dlPtr->height == 0) { bytesToCount--; break; } /* elide */ - TkTextIndexForwBytes(&index, dlPtr->byteCount, &index); - bytesToCount -= dlPtr->byteCount; - } while ((bytesToCount > 0) - && (index.linePtr == lowestPtr->index.linePtr)); - - /* - * Scan through the display lines from the bottom one up to - * the top one. - */ - - while (lowestPtr != NULL) { - dlPtr = lowestPtr; - spaceLeft -= dlPtr->height; - if (spaceLeft < 0) { - break; - } - lowestPtr = dlPtr->nextPtr; - dlPtr->nextPtr = dInfoPtr->dLinePtr; - dInfoPtr->dLinePtr = dlPtr; - if (tkTextDebug) { - char string[TK_POS_CHARS]; - - TkTextPrintIndex(&dlPtr->index, string); - Tcl_SetVar2(textPtr->interp, "tk_textRelayout", - (char *) NULL, string, - TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT); - } - } - FreeDLines(textPtr, lowestPtr, (DLine *) NULL, 0); - bytesToCount = INT_MAX; - } - - /* - * 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. - */ - - textPtr->topIndex = dInfoPtr->dLinePtr->index; - y = dInfoPtr->y; - for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; - dlPtr = dlPtr->nextPtr) { - if (y > dInfoPtr->maxY) { - 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->flags & HAS_3D_BORDER) && !(dlPtr->flags & TOP_LINE)) { - dlPtr->oldY = -1; - } - while (1) { - if ((dlPtr->flags & TOP_LINE) && (dlPtr != dInfoPtr->dLinePtr) - && (dlPtr->flags & HAS_3D_BORDER)) { - dlPtr->oldY = -1; - } - if ((dlPtr->flags & BOTTOM_LINE) && (dlPtr->nextPtr != NULL) - && (dlPtr->flags & HAS_3D_BORDER)) { - dlPtr->oldY = -1; - } - if (dlPtr->nextPtr == NULL) { - if ((dlPtr->flags & HAS_3D_BORDER) - && !(dlPtr->flags & BOTTOM_LINE)) { - dlPtr->oldY = -1; - } - dlPtr->flags &= ~TOP_LINE; - dlPtr->flags |= BOTTOM_LINE; - break; - } - dlPtr->flags &= ~(TOP_LINE|BOTTOM_LINE); - dlPtr = dlPtr->nextPtr; - } - dInfoPtr->dLinePtr->flags |= TOP_LINE; - - /* - * 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) - + textPtr->charWidth - 1)/textPtr->charWidth; - if (dInfoPtr->newByteOffset > maxOffset) { - dInfoPtr->newByteOffset = maxOffset; - } - if (dInfoPtr->newByteOffset < 0) { - dInfoPtr->newByteOffset = 0; - } - pixelOffset = dInfoPtr->newByteOffset * textPtr->charWidth; - if (pixelOffset != dInfoPtr->curPixelOffset) { - dInfoPtr->curPixelOffset = pixelOffset; - for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; - dlPtr = dlPtr->nextPtr) { - dlPtr->oldY = -1; - } - } -} - -/* - *---------------------------------------------------------------------- - * - * FreeDLines -- - * - * This procedure 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(textPtr, firstPtr, lastPtr, unlink) - 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 unlink; /* 1 means DLines are currently linked - * into the list rooted at - * textPtr->dInfoPtr->dLinePtr and - * they have to be unlinked. 0 means - * just free without unlinking. */ -{ - register TkTextDispChunk *chunkPtr, *nextChunkPtr; - register DLine *nextDLinePtr; - - if (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((char *) chunkPtr); - } - ckfree((char *) firstPtr); - firstPtr = nextDLinePtr; - } - textPtr->dInfoPtr->dLinesInvalidated = 1; -} - -/* - *---------------------------------------------------------------------- - * - * DisplayDLine -- - * - * This procedure 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(textPtr, dlPtr, prevPtr, pixmap) - 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, x; - - if (dlPtr->chunkPtr == NULL) return; - - /* - * First, clear the area of the line to the background color for the - * text widget. - */ - - display = Tk_Display(textPtr->tkwin); - Tk_Fill3DRectangle(textPtr->tkwin, pixmap, textPtr->border, 0, 0, - Tk_Width(textPtr->tkwin), dlPtr->height, 0, TK_RELIEF_FLAT); - - /* - * Next, draw background information for the whole line. - */ - - DisplayLineBackground(textPtr, dlPtr, prevPtr, pixmap); - - /* - * 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_STATE_NORMAL) { - for (chunkPtr = dlPtr->chunkPtr; (chunkPtr != NULL); - chunkPtr = chunkPtr->nextPtr) { - x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curPixelOffset; - if (chunkPtr->displayProc == TkTextInsertDisplayProc) { - (*chunkPtr->displayProc)(chunkPtr, x, 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. - * Conve - */ - - 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; - } - x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curPixelOffset; - 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). - */ - - if (chunkPtr->displayProc != NULL) - (*chunkPtr->displayProc)(chunkPtr, -chunkPtr->width, - dlPtr->spaceAbove, - dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow, - dlPtr->baseline - dlPtr->spaceAbove, display, pixmap, - dlPtr->y + dlPtr->spaceAbove); - } else { - /* don't call if elide. This tax ok since not very many visible DLine's in - an area, but potentially many elide ones */ - if (chunkPtr->displayProc != NULL) - (*chunkPtr->displayProc)(chunkPtr, x, dlPtr->spaceAbove, - dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow, - dlPtr->baseline - dlPtr->spaceAbove, display, pixmap, - dlPtr->y + dlPtr->spaceAbove); - } - if (dInfoPtr->dLinesInvalidated) { - return; - } - } - - /* - * Copy the pixmap onto the screen. If this is the 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. - */ - - height = dlPtr->height; - if ((height + dlPtr->y) > dInfoPtr->maxY) { - height = dInfoPtr->maxY - dlPtr->y; - } - XCopyArea(display, pixmap, Tk_WindowId(textPtr->tkwin), dInfoPtr->copyGC, - dInfoPtr->x, 0, (unsigned) (dInfoPtr->maxX - dInfoPtr->x), - (unsigned) height, dInfoPtr->x, dlPtr->y); - linesRedrawn++; -} - -/* - *-------------------------------------------------------------- - * - * DisplayLineBackground -- - * - * This procedure 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(textPtr, dlPtr, prevPtr, pixmap) - 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; - StyleValues *sValuePtr; - Display *display; - - - /* - * 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->curPixelOffset; - 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) { - XFillRectangle(display, pixmap, chunkPtr->stylePtr->bgGC, - leftX + xOffset, 0, (unsigned int) (rightX - leftX), - (unsigned int) dlPtr->height); - if (sValuePtr->relief != TK_RELIEF_FLAT) { - Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border, - leftX + xOffset, 0, sValuePtr->borderWidth, - dlPtr->height, 1, sValuePtr->relief); - Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border, - rightX - sValuePtr->borderWidth + xOffset, - 0, sValuePtr->borderWidth, 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, 0, - 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) { - if (sValuePtr->relief != TK_RELIEF_FLAT) { - Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border, - rightX2 - sValuePtr->borderWidth + xOffset, 0, - sValuePtr->borderWidth, sValuePtr->borderWidth, 0, - sValuePtr->relief); - } - leftX = rightX2 - sValuePtr->borderWidth; - leftXIn = 0; - } else if (!matchLeft && matchRight - && (sValuePtr->relief != TK_RELIEF_FLAT)) { - Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border, - rightX2 + xOffset, 0, sValuePtr->borderWidth, - sValuePtr->borderWidth, 1, sValuePtr->relief); - Tk_3DHorizontalBevel(textPtr->tkwin, pixmap, sValuePtr->border, - leftX + xOffset, 0, rightX2 + sValuePtr->borderWidth -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 previous 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, - 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) { - if (sValuePtr->relief != TK_RELIEF_FLAT) { - Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border, - rightX2 - sValuePtr->borderWidth + xOffset, - dlPtr->height - sValuePtr->borderWidth, - sValuePtr->borderWidth, sValuePtr->borderWidth, 0, - sValuePtr->relief); - } - leftX = rightX2 - sValuePtr->borderWidth; - leftXIn = 1; - } else if (!matchLeft && matchRight - && (sValuePtr->relief != TK_RELIEF_FLAT)) { - Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border, - rightX2 + xOffset, dlPtr->height - sValuePtr->borderWidth, - sValuePtr->borderWidth, sValuePtr->borderWidth, - 1, sValuePtr->relief); - Tk_3DHorizontalBevel(textPtr->tkwin, pixmap, sValuePtr->border, - leftX + xOffset, dlPtr->height - sValuePtr->borderWidth, - rightX2 + sValuePtr->borderWidth - 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; - } - } - } -} - -/* - *---------------------------------------------------------------------- - * - * DisplayText -- - * - * This procedure 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 clientData; /* Information about widget. */ -{ - register TkText *textPtr = (TkText *) clientData; - TextDInfo *dInfoPtr = textPtr->dInfoPtr; - Tk_Window tkwin; - register DLine *dlPtr; - DLine *prevPtr; - Pixmap pixmap; - int maxHeight, borders; - int bottomY = 0; /* Initialization needed only to stop - * compiler warnings. */ - Tcl_Interp *interp; - - if (textPtr->tkwin == NULL) { - - /* - * The widget has been deleted. Don't do anything. - */ - - return; - } - - interp = textPtr->interp; - Tcl_Preserve((ClientData) interp); - - if (tkTextDebug) { - Tcl_SetVar2(interp, "tk_textRelayout", (char *) NULL, "", - TCL_GLOBAL_ONLY); - } - - if (textPtr->tkwin == NULL) { - - /* - * The widget has been deleted. Don't do anything. - */ - - goto end; - } - - 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", (char *) NULL, "", - TCL_GLOBAL_ONLY); - } - - if (textPtr->tkwin == NULL) { - - /* - * The widget has been deleted. Don't do anything. - */ - - goto end; - } - - /* - * 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) { - Tcl_Preserve((ClientData) textPtr); - dInfoPtr->flags &= ~REPICK_NEEDED; - TkTextPickCurrent(textPtr, &textPtr->pickEvent); - tkwin = textPtr->tkwin; - Tcl_Release((ClientData) textPtr); - if (tkwin == NULL) { - 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). - */ - - for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) { - register DLine *dlPtr2; - int offset, height, y, oldY; - TkRegion damageRgn; - - if ((dlPtr->oldY == -1) || (dlPtr->y == dlPtr->oldY) - || ((dlPtr->oldY + dlPtr->height) > dInfoPtr->maxY)) { - 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->oldY == -1) - || ((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; - - /* - * Update the lines we are going to scroll to show that they - * have been copied. - */ - - while (1) { - 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->oldY != -1) - && ((dlPtr2->oldY + dlPtr2->height) > y) - && (dlPtr2->oldY < (y + height))) { - dlPtr2->oldY = -1; - } - } - - /* - * 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) { - Tcl_SetVar2(interp, "tk_textRedraw", (char *) NULL, "borders", - TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT); - } - - 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->oldY != dlPtr->y)) { - maxHeight = dlPtr->height; - } - bottomY = dlPtr->y + dlPtr->height; - } - if (maxHeight > dInfoPtr->maxY) { - maxHeight = dInfoPtr->maxY; - } - if (maxHeight > 0) { - pixmap = Tk_GetPixmap(Tk_Display(textPtr->tkwin), - Tk_WindowId(textPtr->tkwin), Tk_Width(textPtr->tkwin), - maxHeight, Tk_Depth(textPtr->tkwin)); - 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->oldY != dlPtr->y) { - if (tkTextDebug) { - char string[TK_POS_CHARS]; - TkTextPrintIndex(&dlPtr->index, string); - Tcl_SetVar2(textPtr->interp, "tk_textRedraw", - (char *) NULL, string, - TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT); - } - DisplayDLine(textPtr, dlPtr, prevPtr, pixmap); - if (dInfoPtr->dLinesInvalidated) { - Tk_FreePixmap(Tk_Display(textPtr->tkwin), pixmap); - return; - } - dlPtr->oldY = dlPtr->y; - dlPtr->flags &= ~NEW_LAYOUT; - } - /*prevPtr = dlPtr;*/ - } - Tk_FreePixmap(Tk_Display(textPtr->tkwin), pixmap); - } - - /* - * 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) { - Tcl_SetVar2(textPtr->interp, "tk_textRedraw", - (char *) NULL, "eof", - TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT); - } - - if (textPtr->tkwin == NULL) { - - /* - * 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; - - doScrollbars: - - /* - * Update the vertical scrollbar, if there is one. Note: it's - * important to clear REDRAW_PENDING here, just in case the - * scroll procedure does something that requires redisplay. - */ - - if (textPtr->flags & UPDATE_SCROLLBARS) { - textPtr->flags &= ~UPDATE_SCROLLBARS; - if (textPtr->yScrollCmd != NULL) { - GetYView(textPtr->interp, textPtr, 1); - } - - if (textPtr->tkwin == NULL) { - - /* - * 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((ClientData) interp); -} - -/* - *---------------------------------------------------------------------- - * - * TkTextEventuallyRepick -- - * - * This procedure 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. - * - *---------------------------------------------------------------------- - */ - - /* ARGSUSED */ -void -TkTextEventuallyRepick(textPtr) - 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, (ClientData) textPtr); - } -} - -/* - *---------------------------------------------------------------------- - * - * TkTextRedrawRegion -- - * - * This procedure 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. - * - *---------------------------------------------------------------------- - */ - - /* ARGSUSED */ -void -TkTextRedrawRegion(textPtr, x, y, width, height) - TkText *textPtr; /* Widget record for text widget. */ - int x, y; /* Coordinates of upper-left corner of area - * to be redrawn, in pixels relative to - * textPtr's window. */ - int width, 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, (ClientData) 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(textPtr, region) - 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->oldY != -1) && (TkRectInRegion(region, rect.x, dlPtr->y, - rect.width, (unsigned int) dlPtr->height) != RectangleOut)) { - dlPtr->oldY = -1; - } - } - 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 -- - * - * This procedure 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 procedure must be called *before* a change is - * made, so that indexes in the display information are still - * valid. - * - * 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(textPtr, index1Ptr, index2Ptr) - TkText *textPtr; /* Widget record for text widget. */ - TkTextIndex *index1Ptr; /* Index of first character to redisplay. */ - TkTextIndex *index2Ptr; /* Index of character just after last one - * to redisplay. */ -{ - TextDInfo *dInfoPtr = textPtr->dInfoPtr; - DLine *firstPtr, *lastPtr; - TkTextIndex rounded; - - /* - * Schedule both a redisplay and a recomputation of display information. - * It's done here rather than the end of the procedure for two reasons: - * - * 1. If there are no display lines to update we'll want to return - * immediately, well before the end of the procedure. - * 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 procedure 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, (ClientData) 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: round index1Ptr back to the beginning - * of its text line, and include all the display lines after index2, - * up to the end of its text line. 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. - */ - - rounded = *index1Ptr; - rounded.byteIndex = 0; - firstPtr = FindDLine(dInfoPtr->dLinePtr, &rounded); - if (firstPtr == NULL) { - return; - } - lastPtr = FindDLine(dInfoPtr->dLinePtr, index2Ptr); - while ((lastPtr != NULL) - && (lastPtr->index.linePtr == index2Ptr->linePtr)) { - lastPtr = lastPtr->nextPtr; - } - - /* - * Delete all the DLines from firstPtr up to but not including lastPtr. - */ - - FreeDLines(textPtr, firstPtr, lastPtr, 1); -} - -/* - *---------------------------------------------------------------------- - * - * TkTextRedrawTag -- - * - * This procedure 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(textPtr, index1Ptr, index2Ptr, tagPtr, withTag) - 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; - - /* - * 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) { - index2Ptr = TkTextMakeByteIndex(textPtr->tree, - TkBTreeNumLines(textPtr->tree), 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, (ClientData) 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 (curIndexPtr->byteIndex == 0) { - dlPtr = FindDLine(dlPtr, curIndexPtr); - } else { - TkTextIndex tmp; - - tmp = *curIndexPtr; - tmp.byteIndex -= 1; - dlPtr = FindDLine(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(dlPtr, endIndexPtr); - if ((endPtr != NULL) && (endPtr->index.linePtr == endIndexPtr->linePtr) - && (endPtr->index.byteIndex < endIndexPtr->byteIndex)) { - 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, 1); - dlPtr = endPtr; - - /* - * Find the first text line in the next range. - */ - - if (!TkBTreeNextTag(&search)) { - break; - } - } -} - -/* - *---------------------------------------------------------------------- - * - * TkTextRelayoutWindow -- - * - * This procedure 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(textPtr) - TkText *textPtr; /* Widget record for text widget. */ -{ - TextDInfo *dInfoPtr = textPtr->dInfoPtr; - GC new; - 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, (ClientData) 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; - new = Tk_GetGC(textPtr->tkwin, GCGraphicsExposures, &gcValues); - if (dInfoPtr->copyGC != None) { - Tk_FreeGC(textPtr->display, dInfoPtr->copyGC); - } - dInfoPtr->copyGC = new; - - /* - * Throw away all the current layout information. - */ - - FreeDLines(textPtr, dInfoPtr->dLinePtr, (DLine *) NULL, 1); - 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; - } - 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 (textPtr->topIndex.byteIndex != 0) { - MeasureUp(textPtr, &textPtr->topIndex, 0, &textPtr->topIndex); - } - - /* - * Invalidate cached scrollbar positions, so that scrollbars - * sliders will be udpated. - */ - - dInfoPtr->xScrollFirst = dInfoPtr->xScrollLast = -1; - dInfoPtr->yScrollFirst = dInfoPtr->yScrollLast = -1; -} - -/* - *---------------------------------------------------------------------- - * - * TkTextSetYView -- - * - * This procedure 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(textPtr, indexPtr, pickPlace) - TkText *textPtr; /* Widget record for text widget. */ - TkTextIndex *indexPtr; /* Position that is to appear somewhere - * in the view. */ - int pickPlace; /* 0 means topLine must appear at top of - * screen. 1 means we get to pick where it - * appears: minimize screen motion or else - * display line at center of screen. */ -{ - TextDInfo *dInfoPtr = textPtr->dInfoPtr; - register DLine *dlPtr; - int bottomY, close, lineIndex; - TkTextIndex tmpIndex, rounded; - Tk_FontMetrics fm; - - /* - * If the specified position is the extra line at the end of the - * text, round it back to the last real line. - */ - - lineIndex = TkBTreeLineIndex(indexPtr->linePtr); - if (lineIndex == TkBTreeNumLines(indexPtr->tree)) { - TkTextIndexBackChars(indexPtr, 1, &rounded); - indexPtr = &rounded; - } - - if (!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. - */ - - if (indexPtr->byteIndex == 0) { - textPtr->topIndex = *indexPtr; - } else { - MeasureUp(textPtr, indexPtr, 0, &textPtr->topIndex); - } - 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(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 ((dlPtr->index.linePtr == indexPtr->linePtr) - && (dlPtr->index.byteIndex <= indexPtr->byteIndex)) { - 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. Add one extra line also, to - * account for the way MeasureUp rounds. - */ - - Tk_GetFontMetrics(textPtr->tkfont, &fm); - bottomY = (dInfoPtr->y + dInfoPtr->maxY + fm.linespace)/2; - close = (dInfoPtr->maxY - dInfoPtr->y)/3; - if (close < 3*fm.linespace) { - close = 3*fm.linespace; - } - close += fm.linespace; - if (dlPtr != NULL) { - /* - * 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(textPtr, &textPtr->topIndex, close, &tmpIndex); - if (TkTextIndexCmp(&tmpIndex, indexPtr) <= 0) { - MeasureUp(textPtr, indexPtr, 0, &textPtr->topIndex); - goto scheduleUpdate; - } - } else { - /* - * 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, &tmpIndex); - if (FindDLine(dInfoPtr->dLinePtr, &tmpIndex) != NULL) { - bottomY = dInfoPtr->maxY - dInfoPtr->y; - } - } - - /* - * 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); - - scheduleUpdate: - if (!(dInfoPtr->flags & REDRAW_PENDING)) { - Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr); - } - dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED; -} - -/* - *-------------------------------------------------------------- - * - * 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. - * - * 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 the index of the next lower line - * instead (i.e. the returned index will be completely visible - * at or below the given y-coordinate). - * - * Side effects: - * None. - * - *-------------------------------------------------------------- - */ - -static void -MeasureUp(textPtr, srcPtr, distance, dstPtr) - 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 pixel just below the lowest - * one in srcPtr's line. */ - TkTextIndex *dstPtr; /* Index to fill in with result. */ -{ - int lineNum; /* Number of current line. */ - int bytesToCount; /* Maximum number of bytes to measure in - * current line. */ - TkTextIndex bestIndex; /* Best candidate seen so far for result. */ - TkTextIndex index; - DLine *dlPtr, *lowestPtr; - int noBestYet; /* 1 means bestIndex hasn't been set. */ - - noBestYet = 1; - bytesToCount = srcPtr->byteIndex + 1; - index.tree = srcPtr->tree; - for (lineNum = TkBTreeLineIndex(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, lineNum); - index.byteIndex = 0; - lowestPtr = NULL; - do { - dlPtr = LayoutDLine(textPtr, &index); - dlPtr->nextPtr = lowestPtr; - lowestPtr = dlPtr; - TkTextIndexForwBytes(&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. - */ - - for (dlPtr = lowestPtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) { - distance -= dlPtr->height; - if (distance < 0) { - *dstPtr = (noBestYet) ? dlPtr->index : bestIndex; - break; - } - bestIndex = dlPtr->index; - noBestYet = 0; - } - - /* - * Discard the display lines, then either return or prepare - * for the next display line to lay out. - */ - - FreeDLines(textPtr, lowestPtr, (DLine *) NULL, 0); - 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->tree, 0, 0, dstPtr); -} - -/* - *-------------------------------------------------------------- - * - * TkTextSeeCmd -- - * - * This procedure 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(textPtr, interp, argc, argv) - TkText *textPtr; /* Information about text widget. */ - Tcl_Interp *interp; /* Current interpreter. */ - int argc; /* Number of arguments. */ - char **argv; /* Argument strings. Someone else has already - * parsed this command enough to know that - * argv[1] is "see". */ -{ - TextDInfo *dInfoPtr = textPtr->dInfoPtr; - TkTextIndex index; - int x, y, width, height, lineWidth, byteCount, oneThird, delta; - DLine *dlPtr; - TkTextDispChunk *chunkPtr; - - if (argc != 3) { - Tcl_AppendResult(interp, "wrong # args: should be \"", - argv[0], " see index\"", (char *) NULL); - return TCL_ERROR; - } - if (TkTextGetIndex(interp, textPtr, argv[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 (TkBTreeLineIndex(index.linePtr) == TkBTreeNumLines(index.tree)) { - TkTextIndexBackChars(&index, 1, &index); - } - - /* - * First get the desired position into the vertical range of the window. - */ - - TkTextSetYView(textPtr, &index, 1); - - /* - * 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 chunk that contains the desired index. - */ - - dlPtr = FindDLine(dInfoPtr->dLinePtr, &index); - byteCount = index.byteIndex - dlPtr->index.byteIndex; - for (chunkPtr = dlPtr->chunkPtr; chunkPtr!=NULL ; chunkPtr = chunkPtr->nextPtr) { - if (byteCount < chunkPtr->numBytes) { - break; - } - byteCount -= chunkPtr->numBytes; - } - - /* - * Call a chunk-specific procedure to find the horizontal range of - * the character within the chunk. - */ - - if (chunkPtr!=NULL) { /* chunkPtr==NULL iff trying to see in elided region */ - (*chunkPtr->bboxProc)(chunkPtr, byteCount, dlPtr->y + dlPtr->spaceAbove, - dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow, - dlPtr->baseline - dlPtr->spaceAbove, &x, &y, &width, - &height); - delta = x - dInfoPtr->curPixelOffset; - oneThird = lineWidth/3; - if (delta < 0) { - if (delta < -oneThird) { - dInfoPtr->newByteOffset = (x - lineWidth/2)/textPtr->charWidth; - } else { - dInfoPtr->newByteOffset -= ((-delta) + textPtr->charWidth - 1) - / textPtr->charWidth; - } - } else { - delta -= (lineWidth - width); - if (delta > 0) { - if (delta > oneThird) { - dInfoPtr->newByteOffset = (x - lineWidth/2)/textPtr->charWidth; - } else { - dInfoPtr->newByteOffset += (delta + textPtr->charWidth - 1) - / textPtr->charWidth; - } - } else { - return TCL_OK; - } - }} - dInfoPtr->flags |= DINFO_OUT_OF_DATE; - if (!(dInfoPtr->flags & REDRAW_PENDING)) { - dInfoPtr->flags |= REDRAW_PENDING; - Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr); - } - return TCL_OK; -} - -/* - *-------------------------------------------------------------- - * - * TkTextXviewCmd -- - * - * This procedure 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(textPtr, interp, argc, argv) - TkText *textPtr; /* Information about text widget. */ - Tcl_Interp *interp; /* Current interpreter. */ - int argc; /* Number of arguments. */ - char **argv; /* Argument strings. Someone else has already - * parsed this command enough to know that - * argv[1] is "xview". */ -{ - TextDInfo *dInfoPtr = textPtr->dInfoPtr; - int type, charsPerPage, count, newOffset; - double fraction; - - if (dInfoPtr->flags & DINFO_OUT_OF_DATE) { - UpdateDisplayInfo(textPtr); - } - - if (argc == 2) { - GetXView(interp, textPtr, 0); - return TCL_OK; - } - - newOffset = dInfoPtr->newByteOffset; - type = Tk_GetScrollInfo(interp, argc, argv, &fraction, &count); - switch (type) { - case TK_SCROLL_ERROR: - return TCL_ERROR; - case TK_SCROLL_MOVETO: - if (fraction > 1.0) { - fraction = 1.0; - } - if (fraction < 0) { - fraction = 0; - } - newOffset = (int) (((fraction * dInfoPtr->maxLength) / textPtr->charWidth) - + 0.5); - break; - case TK_SCROLL_PAGES: - charsPerPage = ((dInfoPtr->maxX - dInfoPtr->x) / textPtr->charWidth) - - 2; - if (charsPerPage < 1) { - charsPerPage = 1; - } - newOffset += charsPerPage * count; - break; - case TK_SCROLL_UNITS: - newOffset += count; - break; - } - - dInfoPtr->newByteOffset = newOffset; - dInfoPtr->flags |= DINFO_OUT_OF_DATE; - if (!(dInfoPtr->flags & REDRAW_PENDING)) { - dInfoPtr->flags |= REDRAW_PENDING; - Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr); - } - return TCL_OK; -} - -/* - *---------------------------------------------------------------------- - * - * ScrollByLines -- - * - * This procedure 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 -ScrollByLines(textPtr, offset) - TkText *textPtr; /* Widget to scroll. */ - int offset; /* Amount by which to scroll, in *screen* - * 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 new, 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->tree; - offset--; /* Skip line containing topIndex. */ - for (lineNum = TkBTreeLineIndex(textPtr->topIndex.linePtr); - lineNum >= 0; lineNum--) { - index.linePtr = TkBTreeFindLine(textPtr->tree, lineNum); - index.byteIndex = 0; - lowestPtr = NULL; - do { - dlPtr = LayoutDLine(textPtr, &index); - dlPtr->nextPtr = lowestPtr; - lowestPtr = dlPtr; - TkTextIndexForwBytes(&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; - break; - } - } - - /* - * Discard the display lines, then either return or prepare - * for the next display line to lay out. - */ - - FreeDLines(textPtr, lowestPtr, (DLine *) NULL, 0); - if (offset >= 0) { - goto scheduleUpdate; - } - bytesToCount = INT_MAX; - } - - /* - * Ran off the beginning of the text. Return the first character - * in the text. - */ - - TkTextMakeByteIndex(textPtr->tree, 0, 0, &textPtr->topIndex); - } else { - /* - * Scrolling down, to show later information in the text. - * Just count lines from the current top of the window. - */ - - lastLinePtr = TkBTreeFindLine(textPtr->tree, - TkBTreeNumLines(textPtr->tree)); - for (i = 0; i < offset; i++) { - dlPtr = LayoutDLine(textPtr, &textPtr->topIndex); - if (dlPtr->length == 0 && dlPtr->height == 0) offset++; - dlPtr->nextPtr = NULL; - TkTextIndexForwBytes(&textPtr->topIndex, dlPtr->byteCount, &new); - FreeDLines(textPtr, dlPtr, (DLine *) NULL, 0); - if (new.linePtr == lastLinePtr) { - break; - } - textPtr->topIndex = new; - } - } - - scheduleUpdate: - if (!(dInfoPtr->flags & REDRAW_PENDING)) { - Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr); - } - dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED; -} - -/* - *-------------------------------------------------------------- - * - * TkTextYviewCmd -- - * - * This procedure 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(textPtr, interp, argc, argv) - TkText *textPtr; /* Information about text widget. */ - Tcl_Interp *interp; /* Current interpreter. */ - int argc; /* Number of arguments. */ - char **argv; /* Argument strings. Someone else has already - * parsed this command enough to know that - * argv[1] is "yview". */ -{ - TextDInfo *dInfoPtr = textPtr->dInfoPtr; - int pickPlace, lineNum, type, bytesInLine; - Tk_FontMetrics fm; - int pixels, count; - size_t switchLength; - double fraction; - TkTextIndex index, new; - TkTextLine *lastLinePtr; - DLine *dlPtr; - - if (dInfoPtr->flags & DINFO_OUT_OF_DATE) { - UpdateDisplayInfo(textPtr); - } - - if (argc == 2) { - GetYView(interp, textPtr, 0); - return TCL_OK; - } - - /* - * Next, handle the old syntax: "pathName yview ?-pickplace? where" - */ - - pickPlace = 0; - if (argv[2][0] == '-') { - switchLength = strlen(argv[2]); - if ((switchLength >= 2) - && (strncmp(argv[2], "-pickplace", switchLength) == 0)) { - pickPlace = 1; - if (argc != 4) { - Tcl_AppendResult(interp, "wrong # args: should be \"", - argv[0], " yview -pickplace lineNum|index\"", - (char *) NULL); - return TCL_ERROR; - } - } - } - if ((argc == 3) || pickPlace) { - if (Tcl_GetInt(interp, argv[2+pickPlace], &lineNum) == TCL_OK) { - TkTextMakeByteIndex(textPtr->tree, lineNum, 0, &index); - TkTextSetYView(textPtr, &index, 0); - return TCL_OK; - } - - /* - * The argument must be a regular text index. - */ - - Tcl_ResetResult(interp); - if (TkTextGetIndex(interp, textPtr, argv[2+pickPlace], - &index) != TCL_OK) { - return TCL_ERROR; - } - TkTextSetYView(textPtr, &index, pickPlace); - return TCL_OK; - } - - /* - * New syntax: dispatch based on argv[2]. - */ - - type = Tk_GetScrollInfo(interp, argc, argv, &fraction, &count); - switch (type) { - case TK_SCROLL_ERROR: - return TCL_ERROR; - case TK_SCROLL_MOVETO: - if (fraction > 1.0) { - fraction = 1.0; - } - if (fraction < 0) { - fraction = 0; - } - fraction *= TkBTreeNumLines(textPtr->tree); - lineNum = (int) fraction; - TkTextMakeByteIndex(textPtr->tree, lineNum, 0, &index); - bytesInLine = TkBTreeBytesInLine(index.linePtr); - index.byteIndex = (int)((bytesInLine * (fraction-lineNum)) + 0.5); - if (index.byteIndex >= bytesInLine) { - TkTextMakeByteIndex(textPtr->tree, lineNum + 1, 0, &index); - } - TkTextSetYView(textPtr, &index, 0); - break; - case TK_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. - */ - - Tk_GetFontMetrics(textPtr->tkfont, &fm); - if (count < 0) { - pixels = (dInfoPtr->maxY - 2*fm.linespace - dInfoPtr->y)*(-count) - + fm.linespace; - MeasureUp(textPtr, &textPtr->topIndex, pixels, &new); - if (TkTextIndexCmp(&textPtr->topIndex, &new) == 0) { - /* - * A page of scrolling ended up being less than one line. - * Scroll one line anyway. - */ - - count = -1; - goto scrollByLines; - } - textPtr->topIndex = new; - } else { - /* - * Scrolling down by pages. Layout lines starting at the - * top index and count through the desired vertical distance. - */ - - pixels = (dInfoPtr->maxY - 2*fm.linespace - dInfoPtr->y)*count; - lastLinePtr = TkBTreeFindLine(textPtr->tree, - TkBTreeNumLines(textPtr->tree)); - do { - dlPtr = LayoutDLine(textPtr, &textPtr->topIndex); - dlPtr->nextPtr = NULL; - TkTextIndexForwBytes(&textPtr->topIndex, dlPtr->byteCount, - &new); - pixels -= dlPtr->height; - FreeDLines(textPtr, dlPtr, (DLine *) NULL, 0); - if (new.linePtr == lastLinePtr) { - break; - } - textPtr->topIndex = new; - } while (pixels > 0); - } - if (!(dInfoPtr->flags & REDRAW_PENDING)) { - Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr); - } - dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED; - break; - case TK_SCROLL_UNITS: - scrollByLines: - ScrollByLines(textPtr, count); - break; - } - return TCL_OK; -} - -/* - *-------------------------------------------------------------- - * - * TkTextScanCmd -- - * - * This procedure 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(textPtr, interp, argc, argv) - register TkText *textPtr; /* Information about text widget. */ - Tcl_Interp *interp; /* Current interpreter. */ - int argc; /* Number of arguments. */ - char **argv; /* Argument strings. Someone else has already - * parsed this command enough to know that - * argv[1] is "scan". */ -{ - TextDInfo *dInfoPtr = textPtr->dInfoPtr; - TkTextIndex index; - int c, x, y, totalScroll, newByte, maxByte, gain=10; - Tk_FontMetrics fm; - size_t length; - - if ((argc != 5) && (argc != 6)) { - Tcl_AppendResult(interp, "wrong # args: should be \"", - argv[0], " scan mark x y\" or \"", - argv[0], " scan dragto x y ?gain?\"", (char *) NULL); - return TCL_ERROR; - } - if (Tcl_GetInt(interp, argv[3], &x) != TCL_OK) { - return TCL_ERROR; - } - if (Tcl_GetInt(interp, argv[4], &y) != TCL_OK) { - return TCL_ERROR; - } - if ((argc == 6) && (Tcl_GetInt(interp, argv[5], &gain) != TCL_OK)) - return TCL_ERROR; - c = argv[2][0]; - length = strlen(argv[2]); - if ((c == 'd') && (strncmp(argv[2], "dragto", length) == 0)) { - /* - * 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). - */ - - newByte = dInfoPtr->scanMarkIndex + (gain*(dInfoPtr->scanMarkX - x)) - / (textPtr->charWidth); - maxByte = 1 + (dInfoPtr->maxLength - (dInfoPtr->maxX - dInfoPtr->x) - + textPtr->charWidth - 1)/textPtr->charWidth; - if (newByte < 0) { - newByte = 0; - dInfoPtr->scanMarkIndex = 0; - dInfoPtr->scanMarkX = x; - } else if (newByte > maxByte) { - newByte = maxByte; - dInfoPtr->scanMarkIndex = maxByte; - dInfoPtr->scanMarkX = x; - } - dInfoPtr->newByteOffset = newByte; - - Tk_GetFontMetrics(textPtr->tkfont, &fm); - totalScroll = (gain*(dInfoPtr->scanMarkY - y)) / fm.linespace; - if (totalScroll != dInfoPtr->scanTotalScroll) { - index = textPtr->topIndex; - ScrollByLines(textPtr, totalScroll-dInfoPtr->scanTotalScroll); - dInfoPtr->scanTotalScroll = totalScroll; - if ((index.linePtr == textPtr->topIndex.linePtr) && - (index.byteIndex == textPtr->topIndex.byteIndex)) { - dInfoPtr->scanTotalScroll = 0; - dInfoPtr->scanMarkY = y; - } - } - } else if ((c == 'm') && (strncmp(argv[2], "mark", length) == 0)) { - dInfoPtr->scanMarkIndex = dInfoPtr->newByteOffset; - dInfoPtr->scanMarkX = x; - dInfoPtr->scanTotalScroll = 0; - dInfoPtr->scanMarkY = y; - } else { - Tcl_AppendResult(interp, "bad scan option \"", argv[2], - "\": must be mark or dragto", (char *) NULL); - return TCL_ERROR; - } - dInfoPtr->flags |= DINFO_OUT_OF_DATE; - if (!(dInfoPtr->flags & REDRAW_PENDING)) { - dInfoPtr->flags |= REDRAW_PENDING; - Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr); - } - return TCL_OK; -} - -/* - *---------------------------------------------------------------------- - * - * GetXView -- - * - * This procedure 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(interp, textPtr, report) - 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; - char buffer[TCL_DOUBLE_SPACE * 2]; - double first, last; - int code; - - if (dInfoPtr->maxLength > 0) { - first = ((double) dInfoPtr->curPixelOffset) - / dInfoPtr->maxLength; - last = first + ((double) (dInfoPtr->maxX - dInfoPtr->x)) - / dInfoPtr->maxLength; - if (last > 1.0) { - last = 1.0; - } - } else { - first = 0; - last = 1.0; - } - if (!report) { - sprintf(buffer, "%g %g", first, last); - Tcl_SetResult(interp, buffer, TCL_VOLATILE); - return; - } - if ((first == dInfoPtr->xScrollFirst) && (last == dInfoPtr->xScrollLast)) { - return; - } - dInfoPtr->xScrollFirst = first; - dInfoPtr->xScrollLast = last; - sprintf(buffer, " %g %g", first, last); - code = Tcl_VarEval(interp, textPtr->xScrollCmd, - buffer, (char *) NULL); - if (code != TCL_OK) { - Tcl_AddErrorInfo(interp, - "\n (horizontal scrolling command executed by text)"); - Tcl_BackgroundError(interp); - } -} - -/* - *---------------------------------------------------------------------- - * - * GetYView -- - * - * This procedure 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(interp, textPtr, report) - 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; - char buffer[TCL_DOUBLE_SPACE * 2]; - double first, last; - DLine *dlPtr; - int totalLines, code, count; - - dlPtr = dInfoPtr->dLinePtr; - totalLines = TkBTreeNumLines(textPtr->tree); - first = (double) TkBTreeLineIndex(dlPtr->index.linePtr) - + (double) dlPtr->index.byteIndex - / TkBTreeBytesInLine(dlPtr->index.linePtr); - first /= totalLines; - while (1) { - if ((dlPtr->y + dlPtr->height) > dInfoPtr->maxY) { - /* - * The last line is only partially visible, so don't - * count its characters in what's visible. - */ - count = 0; - break; - } - if (dlPtr->nextPtr == NULL) { - count = dlPtr->byteCount; - break; - } - dlPtr = dlPtr->nextPtr; - } - last = ((double) TkBTreeLineIndex(dlPtr->index.linePtr)) - + ((double) (dlPtr->index.byteIndex + count)) - / (TkBTreeBytesInLine(dlPtr->index.linePtr)); - last /= totalLines; - if (!report) { - sprintf(buffer, "%g %g", first, last); - Tcl_SetResult(interp, buffer, TCL_VOLATILE); - return; - } - if ((first == dInfoPtr->yScrollFirst) && (last == dInfoPtr->yScrollLast)) { - return; - } - dInfoPtr->yScrollFirst = first; - dInfoPtr->yScrollLast = last; - sprintf(buffer, " %g %g", first, last); - code = Tcl_VarEval(interp, textPtr->yScrollCmd, buffer, (char *) NULL); - if (code != TCL_OK) { - Tcl_AddErrorInfo(interp, - "\n (vertical scrolling command executed by text)"); - Tcl_BackgroundError(interp); - } -} - -/* - *---------------------------------------------------------------------- - * - * FindDLine -- - * - * This procedure 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(dlPtr, indexPtr) - register DLine *dlPtr; /* Pointer to first in list of DLines - * to search. */ - TkTextIndex *indexPtr; /* Index of desired character. */ -{ - TkTextLine *linePtr; - - if (dlPtr == NULL) { - return NULL; - } - if (TkBTreeLineIndex(indexPtr->linePtr) - < TkBTreeLineIndex(dlPtr->index.linePtr)) { - /* - * The first display line is already past the desired line. - */ - return dlPtr; - } - - /* - * Find the first display line that covers the desired text line. - */ - - linePtr = dlPtr->index.linePtr; - while (linePtr != indexPtr->linePtr) { - while (dlPtr->index.linePtr == linePtr) { - dlPtr = dlPtr->nextPtr; - if (dlPtr == NULL) { - return NULL; - } - } - linePtr = TkBTreeNextLine(linePtr); - if (linePtr == NULL) { - panic("FindDLine reached end of text"); - } - } - if (indexPtr->linePtr != dlPtr->index.linePtr) { - return dlPtr; - } - - /* - * Now get to the right position within the text line. - */ - - while (indexPtr->byteIndex >= (dlPtr->index.byteIndex + dlPtr->byteCount)) { - dlPtr = dlPtr->nextPtr; - if ((dlPtr == NULL) || (dlPtr->index.linePtr != indexPtr->linePtr)) { - break; - } - } - return dlPtr; -} - -/* - *---------------------------------------------------------------------- - * - * 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(textPtr, x, y, indexPtr) - TkText *textPtr; /* Widget record for text widget. */ - int x, 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). */ -{ - TextDInfo *dInfoPtr = textPtr->dInfoPtr; - register DLine *dlPtr, *validdlPtr; - register TkTextDispChunk *chunkPtr; - - /* - * 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; - } - if (x >= dInfoPtr->maxX) { - x = dInfoPtr->maxX - 1; - } - if (x < dInfoPtr->x) { - x = dInfoPtr->x; - } - - /* - * Find the display line containing the desired y-coordinate. - */ - - 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; - break; - } - } - if (dlPtr->chunkPtr == NULL) dlPtr = validdlPtr; - - - /* - * 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->curPixelOffset; - for (chunkPtr = dlPtr->chunkPtr; x >= (chunkPtr->x + chunkPtr->width); - indexPtr->byteIndex += chunkPtr->numBytes, - chunkPtr = chunkPtr->nextPtr) { - if (chunkPtr->nextPtr == NULL) { - indexPtr->byteIndex += chunkPtr->numBytes; - TkTextIndexBackChars(indexPtr, 1, indexPtr); - return; - } - } - - /* - * If the chunk has more than one byte in it, ask it which - * character is at the desired location. - */ - - if (chunkPtr->numBytes > 1) { - indexPtr->byteIndex += (*chunkPtr->measureProc)(chunkPtr, x); - } -} - -/* - *---------------------------------------------------------------------- - * - * TkTextCharBbox -- - * - * Given an index, find the bounding box of the screen area - * occupied by 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 the bounding box of the part of the character that's - * visible on the screen is returned to *xPtr, *yPtr, *widthPtr, - * and *heightPtr. - * - * Side effects: - * None. - * - *---------------------------------------------------------------------- - */ - -int -TkTextCharBbox(textPtr, indexPtr, xPtr, yPtr, widthPtr, heightPtr) - TkText *textPtr; /* Widget record for text widget. */ - TkTextIndex *indexPtr; /* Index of character whose bounding - * box is desired. */ - int *xPtr, *yPtr; /* Filled with character's upper-left - * coordinate. */ - int *widthPtr, *heightPtr; /* Filled in with character's dimensions. */ -{ - TextDInfo *dInfoPtr = textPtr->dInfoPtr; - DLine *dlPtr; - register TkTextDispChunk *chunkPtr; - int byteIndex; - - /* - * 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(dInfoPtr->dLinePtr, indexPtr); - if ((dlPtr == NULL) || (TkTextIndexCmp(&dlPtr->index, indexPtr) > 0)) { - return -1; - } - - /* - * Find the chunk within the line that contains the desired - * index. - */ - - byteIndex = indexPtr->byteIndex - dlPtr->index.byteIndex; - for (chunkPtr = dlPtr->chunkPtr; ; chunkPtr = chunkPtr->nextPtr) { - if (chunkPtr == NULL) { - return -1; - } - if (byteIndex < chunkPtr->numBytes) { - break; - } - byteIndex -= chunkPtr->numBytes; - } - - /* - * Call a chunk-specific procedure 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)(chunkPtr, byteIndex, dlPtr->y + dlPtr->spaceAbove, - dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow, - dlPtr->baseline - dlPtr->spaceAbove, xPtr, yPtr, widthPtr, - heightPtr); - *xPtr = *xPtr + dInfoPtr->x - dInfoPtr->curPixelOffset; - if ((byteIndex == (chunkPtr->numBytes - 1)) && (chunkPtr->nextPtr == NULL)) { - /* - * Last character in display line. Give it all the space up to - * the line. - */ - - if (*xPtr > dInfoPtr->maxX) { - *xPtr = dInfoPtr->maxX; - } - *widthPtr = dInfoPtr->maxX - *xPtr; - } - 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(textPtr, indexPtr, xPtr, yPtr, widthPtr, heightPtr, basePtr) - TkText *textPtr; /* Widget record for text widget. */ - TkTextIndex *indexPtr; /* Index of character whose bounding - * box is desired. */ - int *xPtr, *yPtr; /* Filled with line's upper-left - * coordinate. */ - int *widthPtr, *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(dInfoPtr->dLinePtr, indexPtr); - if ((dlPtr == NULL) || (TkTextIndexCmp(&dlPtr->index, indexPtr) > 0)) { - return -1; - } - - dlx = (dlPtr->chunkPtr != NULL? dlPtr->chunkPtr->x: 0); - *xPtr = dInfoPtr->x - dInfoPtr->curPixelOffset + 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; -} - -static void -ElideBboxProc(chunkPtr, index, y, lineHeight, baseline, xPtr, yPtr, - widthPtr, heightPtr) - 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, *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; -} - - -static int -ElideMeasureProc(chunkPtr, x) - TkTextDispChunk *chunkPtr; /* Chunk containing desired coord. */ - int x; /* X-coordinate, in same coordinate - * system as chunkPtr->x. */ -{ - return 0 /*chunkPtr->numBytes - 1*/; -} - -/* - *-------------------------------------------------------------- - * - * TkTextCharLayoutProc -- - * - * This procedure is the "layoutProc" for character segments. - * -n * 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(textPtr, indexPtr, segPtr, byteOffset, maxX, maxBytes, - noCharsYet, wrapMode, chunkPtr) - 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; - - /* - * 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 haven't - * already exceeded the character limit, and the next character - * is a white space character. - */ - - p = segPtr->body.chars + byteOffset; - tkfont = chunkPtr->stylePtr->sValuePtr->tkfont; - bytesThatFit = MeasureChars(tkfont, p, maxBytes, chunkPtr->x, maxX, 0, - &nextX); - if (bytesThatFit < maxBytes) { - if ((bytesThatFit == 0) && noCharsYet) { - Tcl_UniChar ch; - - bytesThatFit = MeasureChars(tkfont, p, Tcl_UtfToUniChar(p, &ch), - chunkPtr->x, -1, 0, &nextX); - } - 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) { - 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; - ciPtr = (CharInfo *) ckalloc((unsigned) - (sizeof(CharInfo) - 3 + bytesThatFit)); - chunkPtr->clientData = (ClientData) ciPtr; - ciPtr->numBytes = bytesThatFit; - strncpy(ciPtr->chars, p, (size_t) bytesThatFit); - if (p[bytesThatFit - 1] == '\n') { - ciPtr->numBytes--; - } - - /* - * 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--) { - if (isspace(UCHAR(*p))) { - chunkPtr->breakIndex = count; - break; - } - } - 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; -} - -/* - *-------------------------------------------------------------- - * - * CharDisplayProc -- - * - * This procedure 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(chunkPtr, x, y, height, baseline, display, dst, screenY) - 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 = (CharInfo *) chunkPtr->clientData; - TextStyle *stylePtr; - StyleValues *sValuePtr; - int offsetBytes, offsetX; - - if ((x + chunkPtr->width) <= 0) { - /* - * The chunk is off-screen. - */ - - return; - } - - 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 = MeasureChars(sValuePtr->tkfont, ciPtr->chars, - ciPtr->numBytes, x, 0, x - chunkPtr->x, &offsetX); - } - - /* - * Draw the text, underline, and overstrike for this chunk. - */ - - if (!sValuePtr->elide && (ciPtr->numBytes > offsetBytes) && (stylePtr->fgGC != None)) { - int numBytes = ciPtr->numBytes - offsetBytes; - char *string = ciPtr->chars + 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->fgGC, sValuePtr->tkfont, - ciPtr->chars + offsetBytes, offsetX, - y + baseline - sValuePtr->offset, 0, numBytes); - - } - if (sValuePtr->overstrike) { - Tk_FontMetrics fm; - - Tk_GetFontMetrics(sValuePtr->tkfont, &fm); - Tk_UnderlineChars(display, dst, stylePtr->fgGC, sValuePtr->tkfont, - ciPtr->chars + offsetBytes, offsetX, - y + baseline - sValuePtr->offset - - fm.descent - (fm.ascent * 3) / 10, - 0, numBytes); - } - } -} - -/* - *-------------------------------------------------------------- - * - * CharUndisplayProc -- - * - * This procedure 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(textPtr, chunkPtr) - TkText *textPtr; /* Overall information about text - * widget. */ - TkTextDispChunk *chunkPtr; /* Chunk that is about to be freed. */ -{ - CharInfo *ciPtr = (CharInfo *) chunkPtr->clientData; - - ckfree((char *) ciPtr); -} - -/* - *-------------------------------------------------------------- - * - * CharMeasureProc -- - * - * This procedure 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(chunkPtr, x) - TkTextDispChunk *chunkPtr; /* Chunk containing desired coord. */ - int x; /* X-coordinate, in same coordinate - * system as chunkPtr->x. */ -{ - CharInfo *ciPtr = (CharInfo *) chunkPtr->clientData; - int endX; - - return MeasureChars(chunkPtr->stylePtr->sValuePtr->tkfont, ciPtr->chars, - chunkPtr->numBytes - 1, chunkPtr->x, x, 0, &endX); - /* CHAR OFFSET */ -} - -/* - *-------------------------------------------------------------- - * - * CharBboxProc -- - * - * This procedure 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(chunkPtr, byteIndex, y, lineHeight, baseline, xPtr, yPtr, - widthPtr, heightPtr) - 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, *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 = (CharInfo *) chunkPtr->clientData; - int maxX; - - maxX = chunkPtr->width + chunkPtr->x; - MeasureChars(chunkPtr->stylePtr->sValuePtr->tkfont, ciPtr->chars, - 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 { - MeasureChars(chunkPtr->stylePtr->sValuePtr->tkfont, - ciPtr->chars + 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 procedure 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(textPtr, tabArrayPtr, index, chunkPtr) - 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, prev, spaceWidth; - char *p; - TkTextTabAlign alignment; - - if (chunkPtr->nextPtr == NULL) { - /* - * Nothing after the actual tab; just return. - */ - - return; - } - - /* - * If no tab information has been given, do the usual thing: - * round up to the next boundary of 8 average-sized characters. - */ - - x = chunkPtr->nextPtr->x; - if ((tabArrayPtr == NULL) || (tabArrayPtr->numTabs == 0)) { - /* - * No tab information has been given, so use the default - * interpretation of tabs. - */ - - 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. - */ - - if (tabArrayPtr->numTabs > 1) { - prev = tabArrayPtr->tabs[tabArrayPtr->numTabs-2].location; - } else { - prev = 0; - } - alignment = tabArrayPtr->tabs[tabArrayPtr->numTabs-1].alignment; - tabX = tabArrayPtr->tabs[tabArrayPtr->numTabs-1].location - + (index + 1 - tabArrayPtr->numTabs) - * (tabArrayPtr->tabs[tabArrayPtr->numTabs-1].location - prev); - } - - 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 = (CharInfo *) 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 = (CharInfo *) decimalChunkPtr->clientData; - MeasureChars(decimalChunkPtr->stylePtr->sValuePtr->tkfont, - ciPtr->chars, decimal, decimalChunkPtr->x, -1, 0, &curX); - desired = tabX - (curX - x); - goto update; - } else { - /* - * 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, &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 index'th tab of tabArrayPtr, assuming that - * the current position on the line is x and the end of the - * line is maxX. For numeric tabs, this is a conservative - * estimate. The return value is always >= 0. - * - * Side effects: - * None. - * - *---------------------------------------------------------------------- - */ - -static int -SizeOfTab(textPtr, tabArrayPtr, index, x, maxX) - TkText *textPtr; /* Information about the text widget as - * a whole. */ - TkTextTabArray *tabArrayPtr; /* Information about the tab stops - * that apply to this line. NULL - * means use default tabbing (every - * 8 chars.) */ - int index; /* Index of current tab stop. */ - int x; /* Current x-location in line. Only - * used if tabArrayPtr == NULL. */ - int maxX; /* X-location of pixel just past the - * right edge of the line. */ -{ - int tabX, prev, result, spaceWidth; - TkTextTabAlign alignment; - - if ((tabArrayPtr == NULL) || (tabArrayPtr->numTabs == 0)) { - tabX = NextTabStop(textPtr->tkfont, x, 0); - return tabX - x; - } - 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 - * from the last two tab positions. - */ - - if (tabArrayPtr->numTabs > 1) { - prev = tabArrayPtr->tabs[tabArrayPtr->numTabs-2].location; - } else { - prev = 0; - } - tabX = tabArrayPtr->tabs[tabArrayPtr->numTabs-1].location - + (index + 1 - tabArrayPtr->numTabs) - * (tabArrayPtr->tabs[tabArrayPtr->numTabs-1].location - prev); - alignment = tabArrayPtr->tabs[tabArrayPtr->numTabs-1].alignment; - } - 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, &spaceWidth); - if (result < spaceWidth) { - result = spaceWidth; - } - return result; -} - -/* - *--------------------------------------------------------------------------- - * - * NextTabStop -- - * - * Given the current position, determine where the next default - * tab stop would be located. This procedure 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. - * - * Results: - * The location in pixels of the next tab stop. - * - * Side effects: - * None. - * - *--------------------------------------------------------------------------- - */ - -static int -NextTabStop(tkfont, x, tabOrigin) - 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_DrawTextLayout will be used to actually display - * the characters. - * - * If tabs are encountered in the string, they will be expanded - * to the next tab stop, unless the TK_IGNORE_TABS flag is specified. - * - * If a newline is encountered in the string, the line will be - * broken at that point, unless the TK_NEWSLINES_NOT_SPECIAL flag - * is specified. - * - * Results: - * The return value is the number of bytes from 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(tkfont, source, maxBytes, startX, maxX, tabOrigin, nextXPtr) - 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 startX; /* X-position at which first character will - * be drawn. */ - int maxX; /* Don't consider any character that would - * cross this x-position. */ - int tabOrigin; /* X-location that serves as "origin" for - * tab stops. */ - int *nextXPtr; /* Return x-position of terminating - * character here. */ -{ - int curX, width, ch; - CONST char *special, *end, *start; - - ch = 0; /* lint. */ - curX = startX; - special = source; - end = source + maxBytes; - for (start = source; 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; - } - start += Tk_MeasureChars(tkfont, start, special - start, maxX - curX, - 0, &width); - curX += width; - if (start < special) { - /* - * No more chars fit in line. - */ - - break; - } - if (special < end) { - if (ch == '\t') { - start++; - } else { - break; - } - } - } - - *nextXPtr = curX; - return start - source; -} - -/* End of tktextdisp.c */ +/* $Header$ */ + +/* + * 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. + * + * 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. + * + * RCS: @(#) $Id: tktextdisp.c,v 1.1.1.1 2001/06/13 05:10:16 dtashley Exp $ + */ + +#include "tkPort.h" +#include "tkInt.h" +#include "tkText.h" + +#ifdef __WIN32__ +#include "tkWinInt.h" +#endif + +/* + * 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. */ + int offset; /* Offset in pixels of baseline, relative to + * baseline of line. */ + int overstrike; /* Non-zero means draw overstrike through + * text. */ + int rMargin; /* Right margin, in pixels. */ + 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 underline; /* Non-zero means draw underline underneath + * text. */ + int elide; /* Non-zero means draw text */ + 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. */ + 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 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 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. -1 means line isn't + * currently visible on display and must be + * redrawn. This is used to move lines by + * scrolling rather than re-drawing. */ + 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. */ + 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, and + * (b) 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 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. + * IS_DISABLED - This Dline cannot be edited. + */ + +#define HAS_3D_BORDER 1 +#define NEW_LAYOUT 2 +#define TOP_LINE 4 +#define BOTTOM_LINE 8 +#define IS_DISABLED 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. */ + 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 newByteOffset; /* Desired x scroll position, measured as the + * number of average-size characters off-screen + * to the left for a line with no left + * margin. */ + int curPixelOffset; /* 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 scanMarkIndex; /* Byte index of character that was at the + * left edge of the window when the scan + * started. */ + int scanMarkX; /* X-position of mouse at time scan started. */ + int scanTotalScroll; /* Total scrolling (in screen lines) 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. */ +} TextDInfo; + +/* + * In TkTextDispChunk structures for character segments, the clientData + * field points to one of the following structures: + */ + +typedef struct CharInfo { + int numBytes; /* Number of bytes to display. */ + char chars[4]; /* UTF characters to display. Actual size + * will be numBytes, not 4. THIS MUST BE + * THE LAST FIELD IN THE STRUCTURE. */ +} CharInfo; + +/* + * 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 + +/* + * 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. */ + +/* + * Forward declarations for procedures defined later in this file: + */ + +static void AdjustForTab _ANSI_ARGS_((TkText *textPtr, + TkTextTabArray *tabArrayPtr, int index, + TkTextDispChunk *chunkPtr)); +static void CharBboxProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr, + int index, int y, int lineHeight, int baseline, + int *xPtr, int *yPtr, int *widthPtr, + int *heightPtr)); +static void CharDisplayProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr, + int x, int y, int height, int baseline, + Display *display, Drawable dst, int screenY)); +static int CharMeasureProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr, + int x)); +static void CharUndisplayProc _ANSI_ARGS_((TkText *textPtr, + TkTextDispChunk *chunkPtr)); + +/* + Definitions of elided procs. + Compiler can't inline these since we use pointers to these functions. + ElideDisplayProc, ElideUndisplayProc special-cased for speed, + as potentially many elided DLine chunks if large, tag toggle-filled + elided region. +*/ +static void ElideBboxProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr, + int index, int y, int lineHeight, int baseline, + int *xPtr, int *yPtr, int *widthPtr, + int *heightPtr)); +static int ElideMeasureProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr, + int x)); + +static void DisplayDLine _ANSI_ARGS_((TkText *textPtr, + DLine *dlPtr, DLine *prevPtr, Pixmap pixmap)); +static void DisplayLineBackground _ANSI_ARGS_((TkText *textPtr, + DLine *dlPtr, DLine *prevPtr, Pixmap pixmap)); +static void DisplayText _ANSI_ARGS_((ClientData clientData)); +static DLine * FindDLine _ANSI_ARGS_((DLine *dlPtr, + TkTextIndex *indexPtr)); +static void FreeDLines _ANSI_ARGS_((TkText *textPtr, + DLine *firstPtr, DLine *lastPtr, int unlink)); +static void FreeStyle _ANSI_ARGS_((TkText *textPtr, + TextStyle *stylePtr)); +static TextStyle * GetStyle _ANSI_ARGS_((TkText *textPtr, + TkTextIndex *indexPtr)); +static void GetXView _ANSI_ARGS_((Tcl_Interp *interp, + TkText *textPtr, int report)); +static void GetYView _ANSI_ARGS_((Tcl_Interp *interp, + TkText *textPtr, int report)); +static DLine * LayoutDLine _ANSI_ARGS_((TkText *textPtr, + TkTextIndex *indexPtr)); +static int MeasureChars _ANSI_ARGS_((Tk_Font tkfont, + CONST char *source, int maxBytes, int startX, + int maxX, int tabOrigin, int *nextXPtr)); +static void MeasureUp _ANSI_ARGS_((TkText *textPtr, + TkTextIndex *srcPtr, int distance, + TkTextIndex *dstPtr)); +static int NextTabStop _ANSI_ARGS_((Tk_Font tkfont, int x, + int tabOrigin)); +static void UpdateDisplayInfo _ANSI_ARGS_((TkText *textPtr)); +static void ScrollByLines _ANSI_ARGS_((TkText *textPtr, + int offset)); +static int SizeOfTab _ANSI_ARGS_((TkText *textPtr, + TkTextTabArray *tabArrayPtr, int index, int x, + int maxX)); +static void TextInvalidateRegion _ANSI_ARGS_((TkText *textPtr, + TkRegion region)); + + +/* + *---------------------------------------------------------------------- + * + * TkTextCreateDInfo -- + * + * This procedure 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(textPtr) + TkText *textPtr; /* Overall information for text widget. */ +{ + register TextDInfo *dInfoPtr; + XGCValues gcValues; + + dInfoPtr = (TextDInfo *) 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->newByteOffset = 0; + dInfoPtr->curPixelOffset = 0; + dInfoPtr->maxLength = 0; + dInfoPtr->xScrollFirst = -1; + dInfoPtr->xScrollLast = -1; + dInfoPtr->yScrollFirst = -1; + dInfoPtr->yScrollLast = -1; + dInfoPtr->scanMarkIndex = 0; + dInfoPtr->scanMarkX = 0; + dInfoPtr->scanTotalScroll = 0; + dInfoPtr->scanMarkY = 0; + dInfoPtr->dLinesInvalidated = 0; + dInfoPtr->flags = DINFO_OUT_OF_DATE; + textPtr->dInfoPtr = dInfoPtr; +} + +/* + *---------------------------------------------------------------------- + * + * TkTextFreeDInfo -- + * + * This procedure 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(textPtr) + 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, (DLine *) NULL, 1); + 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, (ClientData) textPtr); + } + ckfree((char *) dInfoPtr); +} + +/* + *---------------------------------------------------------------------- + * + * GetStyle -- + * + * This procedure 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(textPtr, indexPtr) + TkText *textPtr; /* Overall information about text widget. */ + 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, new, i; + 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 spacing1Prio, spacing2Prio, spacing3Prio; + int overstrikePrio, tabPrio, 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, &numTags); + borderPrio = borderWidthPrio = reliefPrio = bgStipplePrio = -1; + fgPrio = fontPrio = fgStipplePrio = -1; + underlinePrio = elidePrio = justifyPrio = offsetPrio = -1; + lMargin1Prio = lMargin2Prio = rMarginPrio = -1; + spacing1Prio = spacing2Prio = spacing3Prio = -1; + overstrikePrio = tabPrio = wrapPrio = -1; + memset((VOID *) &styleValues, 0, sizeof(StyleValues)); + styleValues.relief = TK_RELIEF_FLAT; + styleValues.fgColor = 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.wrapMode = textPtr->wrapMode; + styleValues.elide = 0; + for (i = 0 ; i < numTags; i++) { + tagPtr = tagPtrs[i]; + + /* + * On Windows and Mac, we need to skip the selection tag if + * we don't have focus. + */ + +#ifndef ALWAYS_SHOW_SELECTION + if ((tagPtr == textPtr->selTagPtr) && !(textPtr->flags & GOT_FOCUS)) { + continue; + } +#endif + + if ((tagPtr->border != NULL) && (tagPtr->priority > borderPrio)) { + styleValues.border = tagPtr->border; + borderPrio = tagPtr->priority; + } + if ((tagPtr->bdString != NULL) + && (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 ((tagPtr->fgColor != None) && (tagPtr->priority > fgPrio)) { + styleValues.fgColor = tagPtr->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->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->rMarginString != NULL) + && (tagPtr->priority > rMarginPrio)) { + styleValues.rMargin = tagPtr->rMargin; + rMarginPrio = 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->tabString != NULL) + && (tagPtr->priority > tabPrio)) { + styleValues.tabArrayPtr = tagPtr->tabArrayPtr; + tabPrio = tagPtr->priority; + } + if ((tagPtr->underlineString != NULL) + && (tagPtr->priority > underlinePrio)) { + styleValues.underline = tagPtr->underline; + underlinePrio = tagPtr->priority; + } + 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((char *) tagPtrs); + } + + /* + * Use an existing style if there's one around that matches. + */ + + hPtr = Tcl_CreateHashEntry(&textPtr->dInfoPtr->styleTable, + (char *) &styleValues, &new); + if (!new) { + stylePtr = (TextStyle *) Tcl_GetHashValue(hPtr); + stylePtr->refCount++; + return stylePtr; + } + + /* + * No existing style matched. Make a new one. + */ + + stylePtr = (TextStyle *) 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); + stylePtr->sValuePtr = (StyleValues *) + Tcl_GetHashKey(&textPtr->dInfoPtr->styleTable, hPtr); + stylePtr->hPtr = hPtr; + Tcl_SetHashValue(hPtr, stylePtr); + return stylePtr; +} + +/* + *---------------------------------------------------------------------- + * + * FreeStyle -- + * + * This procedure 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(textPtr, stylePtr) + 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); + } + Tcl_DeleteHashEntry(stylePtr->hPtr); + ckfree((char *) stylePtr); + } +} + +/* + *---------------------------------------------------------------------- + * + * LayoutDLine -- + * + * This procedure 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 desribing the + * display line. All fields are filled in and correct except for + * y and nextPtr. + * + * Side effects: + * Storage is allocated for the new DLine. + * + *---------------------------------------------------------------------- + */ + +static DLine * +LayoutDLine(textPtr, indexPtr) + TkText *textPtr; /* Overall information about text widget. */ + 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 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 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; + + /* + * Create and initialize a new DLine structure. + */ + + dlPtr = (DLine *) ckalloc(sizeof(DLine)); + dlPtr->index = *indexPtr; + dlPtr->byteCount = 0; + dlPtr->y = 0; + dlPtr->oldY = -1; + dlPtr->height = 0; + dlPtr->baseline = 0; + dlPtr->chunkPtr = NULL; + dlPtr->nextPtr = NULL; + dlPtr->flags = NEW_LAYOUT; + + /* + * Special case entirely elide line as there may be 1000s or more + */ + elide = TkTextIsElided(textPtr, indexPtr); /* save a malloc */ + if (elide && indexPtr->byteIndex==0) { + maxBytes = 0; + for (segPtr = indexPtr->linePtr->segPtr; + elide && (segPtr != NULL); + segPtr = segPtr->nextPtr) { + if ((elidesize = segPtr->size) > 0) { + maxBytes += elidesize; + /* + * 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; + } + } + } + + if (elide) { + dlPtr->byteCount = maxBytes; + dlPtr->spaceAbove = dlPtr->spaceBelow = dlPtr->length = 0; + return dlPtr; + } + } + + /* + * 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; + 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). + */ + + for (byteOffset = curIndex.byteIndex, segPtr = curIndex.linePtr->segPtr; + (byteOffset > 0) && (byteOffset >= segPtr->size); + byteOffset -= segPtr->size, segPtr = segPtr->nextPtr) { + /* Empty loop body. */ + } + + while (segPtr != NULL) { + /* + * Every 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 + */ + if (elide && (lastChunkPtr != NULL) + && (lastChunkPtr->displayProc == NULL /*ElideDisplayProc*/)) { + if ((elidesize = segPtr->size - byteOffset) > 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 && chunkPtr != NULL) { + ckfree((char *) chunkPtr); + } + continue; + } + + if (segPtr->typePtr->layoutProc == NULL) { + segPtr = segPtr->nextPtr; + byteOffset = 0; + continue; + } + if (chunkPtr == NULL) { + chunkPtr = (TkTextDispChunk *) ckalloc(sizeof(TkTextDispChunk)); + chunkPtr->nextPtr = 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 (noCharsYet) { + tabArrayPtr = chunkPtr->stylePtr->sValuePtr->tabArrayPtr; + justify = chunkPtr->stylePtr->sValuePtr->justify; + rMargin = chunkPtr->stylePtr->sValuePtr->rMargin; + wrapMode = chunkPtr->stylePtr->sValuePtr->wrapMode; + x = ((curIndex.byteIndex == 0) + ? chunkPtr->stylePtr->sValuePtr->lMargin1 + : chunkPtr->stylePtr->sValuePtr->lMargin2); + if (wrapMode == TEXT_WRAPMODE_NONE) { + maxX = -1; + } else { + maxX = textPtr->dInfoPtr->maxX - textPtr->dInfoPtr->x + - rMargin; + if (maxX < x) { + maxX = x; + } + } + } + + /* + * See if there is a tab in the current chunk; if so, only + * layout characters up to (and including) the tab. + */ + + gotTab = 0; + maxBytes = segPtr->size - byteOffset; + if (!elide && justify == TK_JUSTIFY_LEFT) { + if (segPtr->typePtr == &tkTextCharType) { + char *p; + + for (p = segPtr->body.chars + byteOffset; *p != 0; p++) { + if (*p == '\t') { + maxBytes = (p + 1 - segPtr->body.chars) - byteOffset; + gotTab = 1; + break; + } + } + } + } + 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 = (Tk_ChunkDisplayProc *) NULL; + chunkPtr->undisplayProc = (Tk_ChunkUndisplayProc *) 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((char *) chunkPtr); + } + break; + } + if (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; + } + tabIndex++; + tabChunkPtr = chunkPtr; + tabSize = SizeOfTab(textPtr, 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; + } + + chunkPtr = NULL; + } + if (noCharsYet) { + panic("LayoutDLine couldn't place any characters on a line"); + } + 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; + (*chunkPtr->undisplayProc)(textPtr, chunkPtr); + ckfree((char *) chunkPtr); + } + if (breakByteOffset != breakChunkPtr->numBytes) { + (*breakChunkPtr->undisplayProc)(textPtr, breakChunkPtr); + segPtr = TkTextIndexToSeg(&breakIndex, &byteOffset); + (*segPtr->typePtr->layoutProc)(textPtr, &breakIndex, + segPtr, byteOffset, maxX, breakByteOffset, 0, + wrapMode, breakChunkPtr); + } + 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 left 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; + + /* + * Recompute line length: may have changed because of justification. + */ + + dlPtr->length = lastChunkPtr->x + lastChunkPtr->width; + return dlPtr; +} + +/* + *---------------------------------------------------------------------- + * + * UpdateDisplayInfo -- + * + * This procedure 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 + * procedure doesn't actually bring the display up-to-date. + * + *---------------------------------------------------------------------- + */ + +static void +UpdateDisplayInfo(textPtr) + TkText *textPtr; /* Text widget to update. */ +{ + register TextDInfo *dInfoPtr = textPtr->dInfoPtr; + register DLine *dlPtr, *prevPtr; + TkTextIndex index; + TkTextLine *lastLinePtr; + int y, maxY, pixelOffset, maxOffset; + + 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(dInfoPtr->dLinePtr, &index); + if ((dlPtr != NULL) && (dlPtr != dInfoPtr->dLinePtr)) { + FreeDLines(textPtr, dInfoPtr->dLinePtr, dlPtr, 1); + } + + /* + *-------------------------------------------------------------- + * Scan through the contents of the window from top to bottom, + * recomputing information for lines that are missing. + *-------------------------------------------------------------- + */ + + lastLinePtr = TkBTreeFindLine(textPtr->tree, + TkBTreeNumLines(textPtr->tree)); + dlPtr = dInfoPtr->dLinePtr; + prevPtr = NULL; + y = dInfoPtr->y; + 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(&index, string); + Tcl_SetVar2(textPtr->interp, "tk_textRelayout", (char *) NULL, + string, + TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT); + } + newPtr = LayoutDLine(textPtr, &index); + if (prevPtr == NULL) { + dInfoPtr->dLinePtr = newPtr; + } else { + prevPtr->nextPtr = newPtr; + if (prevPtr->flags & HAS_3D_BORDER) { + prevPtr->oldY = -1; + } + } + 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->oldY = -1; + } + 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, 0); + 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; + TkTextIndexForwBytes(&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, 0); + prevPtr->nextPtr = nextPtr; + dlPtr = nextPtr; + } + } + + /* + * 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, (DLine *) NULL, 1); + + /* + *-------------------------------------------------------------- + * 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. + *-------------------------------------------------------------- + */ + + if (y < maxY) { + int lineNum, spaceLeft, bytesToCount; + DLine *lowestPtr; + + /* + * 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; + lineNum = TkBTreeLineIndex(dInfoPtr->dLinePtr->index.linePtr); + bytesToCount = dInfoPtr->dLinePtr->index.byteIndex; + if (bytesToCount == 0) { + bytesToCount = INT_MAX; + lineNum--; + } + for ( ; (lineNum >= 0) && (spaceLeft > 0); lineNum--) { + index.linePtr = TkBTreeFindLine(textPtr->tree, lineNum); + index.byteIndex = 0; + lowestPtr = NULL; + + do { + dlPtr = LayoutDLine(textPtr, &index); + dlPtr->nextPtr = lowestPtr; + lowestPtr = dlPtr; + if (dlPtr->length == 0 && dlPtr->height == 0) { bytesToCount--; break; } /* elide */ + TkTextIndexForwBytes(&index, dlPtr->byteCount, &index); + bytesToCount -= dlPtr->byteCount; + } while ((bytesToCount > 0) + && (index.linePtr == lowestPtr->index.linePtr)); + + /* + * Scan through the display lines from the bottom one up to + * the top one. + */ + + while (lowestPtr != NULL) { + dlPtr = lowestPtr; + spaceLeft -= dlPtr->height; + if (spaceLeft < 0) { + break; + } + lowestPtr = dlPtr->nextPtr; + dlPtr->nextPtr = dInfoPtr->dLinePtr; + dInfoPtr->dLinePtr = dlPtr; + if (tkTextDebug) { + char string[TK_POS_CHARS]; + + TkTextPrintIndex(&dlPtr->index, string); + Tcl_SetVar2(textPtr->interp, "tk_textRelayout", + (char *) NULL, string, + TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT); + } + } + FreeDLines(textPtr, lowestPtr, (DLine *) NULL, 0); + bytesToCount = INT_MAX; + } + + /* + * 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. + */ + + textPtr->topIndex = dInfoPtr->dLinePtr->index; + y = dInfoPtr->y; + for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; + dlPtr = dlPtr->nextPtr) { + if (y > dInfoPtr->maxY) { + 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->flags & HAS_3D_BORDER) && !(dlPtr->flags & TOP_LINE)) { + dlPtr->oldY = -1; + } + while (1) { + if ((dlPtr->flags & TOP_LINE) && (dlPtr != dInfoPtr->dLinePtr) + && (dlPtr->flags & HAS_3D_BORDER)) { + dlPtr->oldY = -1; + } + if ((dlPtr->flags & BOTTOM_LINE) && (dlPtr->nextPtr != NULL) + && (dlPtr->flags & HAS_3D_BORDER)) { + dlPtr->oldY = -1; + } + if (dlPtr->nextPtr == NULL) { + if ((dlPtr->flags & HAS_3D_BORDER) + && !(dlPtr->flags & BOTTOM_LINE)) { + dlPtr->oldY = -1; + } + dlPtr->flags &= ~TOP_LINE; + dlPtr->flags |= BOTTOM_LINE; + break; + } + dlPtr->flags &= ~(TOP_LINE|BOTTOM_LINE); + dlPtr = dlPtr->nextPtr; + } + dInfoPtr->dLinePtr->flags |= TOP_LINE; + + /* + * 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) + + textPtr->charWidth - 1)/textPtr->charWidth; + if (dInfoPtr->newByteOffset > maxOffset) { + dInfoPtr->newByteOffset = maxOffset; + } + if (dInfoPtr->newByteOffset < 0) { + dInfoPtr->newByteOffset = 0; + } + pixelOffset = dInfoPtr->newByteOffset * textPtr->charWidth; + if (pixelOffset != dInfoPtr->curPixelOffset) { + dInfoPtr->curPixelOffset = pixelOffset; + for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; + dlPtr = dlPtr->nextPtr) { + dlPtr->oldY = -1; + } + } +} + +/* + *---------------------------------------------------------------------- + * + * FreeDLines -- + * + * This procedure 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(textPtr, firstPtr, lastPtr, unlink) + 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 unlink; /* 1 means DLines are currently linked + * into the list rooted at + * textPtr->dInfoPtr->dLinePtr and + * they have to be unlinked. 0 means + * just free without unlinking. */ +{ + register TkTextDispChunk *chunkPtr, *nextChunkPtr; + register DLine *nextDLinePtr; + + if (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((char *) chunkPtr); + } + ckfree((char *) firstPtr); + firstPtr = nextDLinePtr; + } + textPtr->dInfoPtr->dLinesInvalidated = 1; +} + +/* + *---------------------------------------------------------------------- + * + * DisplayDLine -- + * + * This procedure 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(textPtr, dlPtr, prevPtr, pixmap) + 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, x; + + if (dlPtr->chunkPtr == NULL) return; + + /* + * First, clear the area of the line to the background color for the + * text widget. + */ + + display = Tk_Display(textPtr->tkwin); + Tk_Fill3DRectangle(textPtr->tkwin, pixmap, textPtr->border, 0, 0, + Tk_Width(textPtr->tkwin), dlPtr->height, 0, TK_RELIEF_FLAT); + + /* + * Next, draw background information for the whole line. + */ + + DisplayLineBackground(textPtr, dlPtr, prevPtr, pixmap); + + /* + * 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_STATE_NORMAL) { + for (chunkPtr = dlPtr->chunkPtr; (chunkPtr != NULL); + chunkPtr = chunkPtr->nextPtr) { + x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curPixelOffset; + if (chunkPtr->displayProc == TkTextInsertDisplayProc) { + (*chunkPtr->displayProc)(chunkPtr, x, 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. + * Conve + */ + + 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; + } + x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curPixelOffset; + 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). + */ + + if (chunkPtr->displayProc != NULL) + (*chunkPtr->displayProc)(chunkPtr, -chunkPtr->width, + dlPtr->spaceAbove, + dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow, + dlPtr->baseline - dlPtr->spaceAbove, display, pixmap, + dlPtr->y + dlPtr->spaceAbove); + } else { + /* don't call if elide. This tax ok since not very many visible DLine's in + an area, but potentially many elide ones */ + if (chunkPtr->displayProc != NULL) + (*chunkPtr->displayProc)(chunkPtr, x, dlPtr->spaceAbove, + dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow, + dlPtr->baseline - dlPtr->spaceAbove, display, pixmap, + dlPtr->y + dlPtr->spaceAbove); + } + if (dInfoPtr->dLinesInvalidated) { + return; + } + } + + /* + * Copy the pixmap onto the screen. If this is the 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. + */ + + height = dlPtr->height; + if ((height + dlPtr->y) > dInfoPtr->maxY) { + height = dInfoPtr->maxY - dlPtr->y; + } + XCopyArea(display, pixmap, Tk_WindowId(textPtr->tkwin), dInfoPtr->copyGC, + dInfoPtr->x, 0, (unsigned) (dInfoPtr->maxX - dInfoPtr->x), + (unsigned) height, dInfoPtr->x, dlPtr->y); + linesRedrawn++; +} + +/* + *-------------------------------------------------------------- + * + * DisplayLineBackground -- + * + * This procedure 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(textPtr, dlPtr, prevPtr, pixmap) + 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; + StyleValues *sValuePtr; + Display *display; + + + /* + * 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->curPixelOffset; + 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) { + XFillRectangle(display, pixmap, chunkPtr->stylePtr->bgGC, + leftX + xOffset, 0, (unsigned int) (rightX - leftX), + (unsigned int) dlPtr->height); + if (sValuePtr->relief != TK_RELIEF_FLAT) { + Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border, + leftX + xOffset, 0, sValuePtr->borderWidth, + dlPtr->height, 1, sValuePtr->relief); + Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border, + rightX - sValuePtr->borderWidth + xOffset, + 0, sValuePtr->borderWidth, 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, 0, + 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) { + if (sValuePtr->relief != TK_RELIEF_FLAT) { + Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border, + rightX2 - sValuePtr->borderWidth + xOffset, 0, + sValuePtr->borderWidth, sValuePtr->borderWidth, 0, + sValuePtr->relief); + } + leftX = rightX2 - sValuePtr->borderWidth; + leftXIn = 0; + } else if (!matchLeft && matchRight + && (sValuePtr->relief != TK_RELIEF_FLAT)) { + Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border, + rightX2 + xOffset, 0, sValuePtr->borderWidth, + sValuePtr->borderWidth, 1, sValuePtr->relief); + Tk_3DHorizontalBevel(textPtr->tkwin, pixmap, sValuePtr->border, + leftX + xOffset, 0, rightX2 + sValuePtr->borderWidth -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 previous 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, + 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) { + if (sValuePtr->relief != TK_RELIEF_FLAT) { + Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border, + rightX2 - sValuePtr->borderWidth + xOffset, + dlPtr->height - sValuePtr->borderWidth, + sValuePtr->borderWidth, sValuePtr->borderWidth, 0, + sValuePtr->relief); + } + leftX = rightX2 - sValuePtr->borderWidth; + leftXIn = 1; + } else if (!matchLeft && matchRight + && (sValuePtr->relief != TK_RELIEF_FLAT)) { + Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border, + rightX2 + xOffset, dlPtr->height - sValuePtr->borderWidth, + sValuePtr->borderWidth, sValuePtr->borderWidth, + 1, sValuePtr->relief); + Tk_3DHorizontalBevel(textPtr->tkwin, pixmap, sValuePtr->border, + leftX + xOffset, dlPtr->height - sValuePtr->borderWidth, + rightX2 + sValuePtr->borderWidth - 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; + } + } + } +} + +/* + *---------------------------------------------------------------------- + * + * DisplayText -- + * + * This procedure 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 clientData; /* Information about widget. */ +{ + register TkText *textPtr = (TkText *) clientData; + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + Tk_Window tkwin; + register DLine *dlPtr; + DLine *prevPtr; + Pixmap pixmap; + int maxHeight, borders; + int bottomY = 0; /* Initialization needed only to stop + * compiler warnings. */ + Tcl_Interp *interp; + + if (textPtr->tkwin == NULL) { + + /* + * The widget has been deleted. Don't do anything. + */ + + return; + } + + interp = textPtr->interp; + Tcl_Preserve((ClientData) interp); + + if (tkTextDebug) { + Tcl_SetVar2(interp, "tk_textRelayout", (char *) NULL, "", + TCL_GLOBAL_ONLY); + } + + if (textPtr->tkwin == NULL) { + + /* + * The widget has been deleted. Don't do anything. + */ + + goto end; + } + + 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", (char *) NULL, "", + TCL_GLOBAL_ONLY); + } + + if (textPtr->tkwin == NULL) { + + /* + * The widget has been deleted. Don't do anything. + */ + + goto end; + } + + /* + * 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) { + Tcl_Preserve((ClientData) textPtr); + dInfoPtr->flags &= ~REPICK_NEEDED; + TkTextPickCurrent(textPtr, &textPtr->pickEvent); + tkwin = textPtr->tkwin; + Tcl_Release((ClientData) textPtr); + if (tkwin == NULL) { + 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). + */ + + for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) { + register DLine *dlPtr2; + int offset, height, y, oldY; + TkRegion damageRgn; + + if ((dlPtr->oldY == -1) || (dlPtr->y == dlPtr->oldY) + || ((dlPtr->oldY + dlPtr->height) > dInfoPtr->maxY)) { + 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->oldY == -1) + || ((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; + + /* + * Update the lines we are going to scroll to show that they + * have been copied. + */ + + while (1) { + 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->oldY != -1) + && ((dlPtr2->oldY + dlPtr2->height) > y) + && (dlPtr2->oldY < (y + height))) { + dlPtr2->oldY = -1; + } + } + + /* + * 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) { + Tcl_SetVar2(interp, "tk_textRedraw", (char *) NULL, "borders", + TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT); + } + + 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->oldY != dlPtr->y)) { + maxHeight = dlPtr->height; + } + bottomY = dlPtr->y + dlPtr->height; + } + if (maxHeight > dInfoPtr->maxY) { + maxHeight = dInfoPtr->maxY; + } + if (maxHeight > 0) { + pixmap = Tk_GetPixmap(Tk_Display(textPtr->tkwin), + Tk_WindowId(textPtr->tkwin), Tk_Width(textPtr->tkwin), + maxHeight, Tk_Depth(textPtr->tkwin)); + 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->oldY != dlPtr->y) { + if (tkTextDebug) { + char string[TK_POS_CHARS]; + TkTextPrintIndex(&dlPtr->index, string); + Tcl_SetVar2(textPtr->interp, "tk_textRedraw", + (char *) NULL, string, + TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT); + } + DisplayDLine(textPtr, dlPtr, prevPtr, pixmap); + if (dInfoPtr->dLinesInvalidated) { + Tk_FreePixmap(Tk_Display(textPtr->tkwin), pixmap); + return; + } + dlPtr->oldY = dlPtr->y; + dlPtr->flags &= ~NEW_LAYOUT; + } + /*prevPtr = dlPtr;*/ + } + Tk_FreePixmap(Tk_Display(textPtr->tkwin), pixmap); + } + + /* + * 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) { + Tcl_SetVar2(textPtr->interp, "tk_textRedraw", + (char *) NULL, "eof", + TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT); + } + + if (textPtr->tkwin == NULL) { + + /* + * 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; + + doScrollbars: + + /* + * Update the vertical scrollbar, if there is one. Note: it's + * important to clear REDRAW_PENDING here, just in case the + * scroll procedure does something that requires redisplay. + */ + + if (textPtr->flags & UPDATE_SCROLLBARS) { + textPtr->flags &= ~UPDATE_SCROLLBARS; + if (textPtr->yScrollCmd != NULL) { + GetYView(textPtr->interp, textPtr, 1); + } + + if (textPtr->tkwin == NULL) { + + /* + * 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((ClientData) interp); +} + +/* + *---------------------------------------------------------------------- + * + * TkTextEventuallyRepick -- + * + * This procedure 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. + * + *---------------------------------------------------------------------- + */ + + /* ARGSUSED */ +void +TkTextEventuallyRepick(textPtr) + 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, (ClientData) textPtr); + } +} + +/* + *---------------------------------------------------------------------- + * + * TkTextRedrawRegion -- + * + * This procedure 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. + * + *---------------------------------------------------------------------- + */ + + /* ARGSUSED */ +void +TkTextRedrawRegion(textPtr, x, y, width, height) + TkText *textPtr; /* Widget record for text widget. */ + int x, y; /* Coordinates of upper-left corner of area + * to be redrawn, in pixels relative to + * textPtr's window. */ + int width, 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, (ClientData) 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(textPtr, region) + 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->oldY != -1) && (TkRectInRegion(region, rect.x, dlPtr->y, + rect.width, (unsigned int) dlPtr->height) != RectangleOut)) { + dlPtr->oldY = -1; + } + } + 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 -- + * + * This procedure 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 procedure must be called *before* a change is + * made, so that indexes in the display information are still + * valid. + * + * 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(textPtr, index1Ptr, index2Ptr) + TkText *textPtr; /* Widget record for text widget. */ + TkTextIndex *index1Ptr; /* Index of first character to redisplay. */ + TkTextIndex *index2Ptr; /* Index of character just after last one + * to redisplay. */ +{ + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + DLine *firstPtr, *lastPtr; + TkTextIndex rounded; + + /* + * Schedule both a redisplay and a recomputation of display information. + * It's done here rather than the end of the procedure for two reasons: + * + * 1. If there are no display lines to update we'll want to return + * immediately, well before the end of the procedure. + * 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 procedure 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, (ClientData) 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: round index1Ptr back to the beginning + * of its text line, and include all the display lines after index2, + * up to the end of its text line. 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. + */ + + rounded = *index1Ptr; + rounded.byteIndex = 0; + firstPtr = FindDLine(dInfoPtr->dLinePtr, &rounded); + if (firstPtr == NULL) { + return; + } + lastPtr = FindDLine(dInfoPtr->dLinePtr, index2Ptr); + while ((lastPtr != NULL) + && (lastPtr->index.linePtr == index2Ptr->linePtr)) { + lastPtr = lastPtr->nextPtr; + } + + /* + * Delete all the DLines from firstPtr up to but not including lastPtr. + */ + + FreeDLines(textPtr, firstPtr, lastPtr, 1); +} + +/* + *---------------------------------------------------------------------- + * + * TkTextRedrawTag -- + * + * This procedure 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(textPtr, index1Ptr, index2Ptr, tagPtr, withTag) + 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; + + /* + * 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) { + index2Ptr = TkTextMakeByteIndex(textPtr->tree, + TkBTreeNumLines(textPtr->tree), 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, (ClientData) 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 (curIndexPtr->byteIndex == 0) { + dlPtr = FindDLine(dlPtr, curIndexPtr); + } else { + TkTextIndex tmp; + + tmp = *curIndexPtr; + tmp.byteIndex -= 1; + dlPtr = FindDLine(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(dlPtr, endIndexPtr); + if ((endPtr != NULL) && (endPtr->index.linePtr == endIndexPtr->linePtr) + && (endPtr->index.byteIndex < endIndexPtr->byteIndex)) { + 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, 1); + dlPtr = endPtr; + + /* + * Find the first text line in the next range. + */ + + if (!TkBTreeNextTag(&search)) { + break; + } + } +} + +/* + *---------------------------------------------------------------------- + * + * TkTextRelayoutWindow -- + * + * This procedure 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(textPtr) + TkText *textPtr; /* Widget record for text widget. */ +{ + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + GC new; + 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, (ClientData) 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; + new = Tk_GetGC(textPtr->tkwin, GCGraphicsExposures, &gcValues); + if (dInfoPtr->copyGC != None) { + Tk_FreeGC(textPtr->display, dInfoPtr->copyGC); + } + dInfoPtr->copyGC = new; + + /* + * Throw away all the current layout information. + */ + + FreeDLines(textPtr, dInfoPtr->dLinePtr, (DLine *) NULL, 1); + 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; + } + 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 (textPtr->topIndex.byteIndex != 0) { + MeasureUp(textPtr, &textPtr->topIndex, 0, &textPtr->topIndex); + } + + /* + * Invalidate cached scrollbar positions, so that scrollbars + * sliders will be udpated. + */ + + dInfoPtr->xScrollFirst = dInfoPtr->xScrollLast = -1; + dInfoPtr->yScrollFirst = dInfoPtr->yScrollLast = -1; +} + +/* + *---------------------------------------------------------------------- + * + * TkTextSetYView -- + * + * This procedure 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(textPtr, indexPtr, pickPlace) + TkText *textPtr; /* Widget record for text widget. */ + TkTextIndex *indexPtr; /* Position that is to appear somewhere + * in the view. */ + int pickPlace; /* 0 means topLine must appear at top of + * screen. 1 means we get to pick where it + * appears: minimize screen motion or else + * display line at center of screen. */ +{ + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + register DLine *dlPtr; + int bottomY, close, lineIndex; + TkTextIndex tmpIndex, rounded; + Tk_FontMetrics fm; + + /* + * If the specified position is the extra line at the end of the + * text, round it back to the last real line. + */ + + lineIndex = TkBTreeLineIndex(indexPtr->linePtr); + if (lineIndex == TkBTreeNumLines(indexPtr->tree)) { + TkTextIndexBackChars(indexPtr, 1, &rounded); + indexPtr = &rounded; + } + + if (!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. + */ + + if (indexPtr->byteIndex == 0) { + textPtr->topIndex = *indexPtr; + } else { + MeasureUp(textPtr, indexPtr, 0, &textPtr->topIndex); + } + 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(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 ((dlPtr->index.linePtr == indexPtr->linePtr) + && (dlPtr->index.byteIndex <= indexPtr->byteIndex)) { + 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. Add one extra line also, to + * account for the way MeasureUp rounds. + */ + + Tk_GetFontMetrics(textPtr->tkfont, &fm); + bottomY = (dInfoPtr->y + dInfoPtr->maxY + fm.linespace)/2; + close = (dInfoPtr->maxY - dInfoPtr->y)/3; + if (close < 3*fm.linespace) { + close = 3*fm.linespace; + } + close += fm.linespace; + if (dlPtr != NULL) { + /* + * 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(textPtr, &textPtr->topIndex, close, &tmpIndex); + if (TkTextIndexCmp(&tmpIndex, indexPtr) <= 0) { + MeasureUp(textPtr, indexPtr, 0, &textPtr->topIndex); + goto scheduleUpdate; + } + } else { + /* + * 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, &tmpIndex); + if (FindDLine(dInfoPtr->dLinePtr, &tmpIndex) != NULL) { + bottomY = dInfoPtr->maxY - dInfoPtr->y; + } + } + + /* + * 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); + + scheduleUpdate: + if (!(dInfoPtr->flags & REDRAW_PENDING)) { + Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr); + } + dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED; +} + +/* + *-------------------------------------------------------------- + * + * 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. + * + * 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 the index of the next lower line + * instead (i.e. the returned index will be completely visible + * at or below the given y-coordinate). + * + * Side effects: + * None. + * + *-------------------------------------------------------------- + */ + +static void +MeasureUp(textPtr, srcPtr, distance, dstPtr) + 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 pixel just below the lowest + * one in srcPtr's line. */ + TkTextIndex *dstPtr; /* Index to fill in with result. */ +{ + int lineNum; /* Number of current line. */ + int bytesToCount; /* Maximum number of bytes to measure in + * current line. */ + TkTextIndex bestIndex; /* Best candidate seen so far for result. */ + TkTextIndex index; + DLine *dlPtr, *lowestPtr; + int noBestYet; /* 1 means bestIndex hasn't been set. */ + + noBestYet = 1; + bytesToCount = srcPtr->byteIndex + 1; + index.tree = srcPtr->tree; + for (lineNum = TkBTreeLineIndex(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, lineNum); + index.byteIndex = 0; + lowestPtr = NULL; + do { + dlPtr = LayoutDLine(textPtr, &index); + dlPtr->nextPtr = lowestPtr; + lowestPtr = dlPtr; + TkTextIndexForwBytes(&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. + */ + + for (dlPtr = lowestPtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) { + distance -= dlPtr->height; + if (distance < 0) { + *dstPtr = (noBestYet) ? dlPtr->index : bestIndex; + break; + } + bestIndex = dlPtr->index; + noBestYet = 0; + } + + /* + * Discard the display lines, then either return or prepare + * for the next display line to lay out. + */ + + FreeDLines(textPtr, lowestPtr, (DLine *) NULL, 0); + 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->tree, 0, 0, dstPtr); +} + +/* + *-------------------------------------------------------------- + * + * TkTextSeeCmd -- + * + * This procedure 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(textPtr, interp, argc, argv) + TkText *textPtr; /* Information about text widget. */ + Tcl_Interp *interp; /* Current interpreter. */ + int argc; /* Number of arguments. */ + char **argv; /* Argument strings. Someone else has already + * parsed this command enough to know that + * argv[1] is "see". */ +{ + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + TkTextIndex index; + int x, y, width, height, lineWidth, byteCount, oneThird, delta; + DLine *dlPtr; + TkTextDispChunk *chunkPtr; + + if (argc != 3) { + Tcl_AppendResult(interp, "wrong # args: should be \"", + argv[0], " see index\"", (char *) NULL); + return TCL_ERROR; + } + if (TkTextGetIndex(interp, textPtr, argv[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 (TkBTreeLineIndex(index.linePtr) == TkBTreeNumLines(index.tree)) { + TkTextIndexBackChars(&index, 1, &index); + } + + /* + * First get the desired position into the vertical range of the window. + */ + + TkTextSetYView(textPtr, &index, 1); + + /* + * 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 chunk that contains the desired index. + */ + + dlPtr = FindDLine(dInfoPtr->dLinePtr, &index); + byteCount = index.byteIndex - dlPtr->index.byteIndex; + for (chunkPtr = dlPtr->chunkPtr; chunkPtr!=NULL ; chunkPtr = chunkPtr->nextPtr) { + if (byteCount < chunkPtr->numBytes) { + break; + } + byteCount -= chunkPtr->numBytes; + } + + /* + * Call a chunk-specific procedure to find the horizontal range of + * the character within the chunk. + */ + + if (chunkPtr!=NULL) { /* chunkPtr==NULL iff trying to see in elided region */ + (*chunkPtr->bboxProc)(chunkPtr, byteCount, dlPtr->y + dlPtr->spaceAbove, + dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow, + dlPtr->baseline - dlPtr->spaceAbove, &x, &y, &width, + &height); + delta = x - dInfoPtr->curPixelOffset; + oneThird = lineWidth/3; + if (delta < 0) { + if (delta < -oneThird) { + dInfoPtr->newByteOffset = (x - lineWidth/2)/textPtr->charWidth; + } else { + dInfoPtr->newByteOffset -= ((-delta) + textPtr->charWidth - 1) + / textPtr->charWidth; + } + } else { + delta -= (lineWidth - width); + if (delta > 0) { + if (delta > oneThird) { + dInfoPtr->newByteOffset = (x - lineWidth/2)/textPtr->charWidth; + } else { + dInfoPtr->newByteOffset += (delta + textPtr->charWidth - 1) + / textPtr->charWidth; + } + } else { + return TCL_OK; + } + }} + dInfoPtr->flags |= DINFO_OUT_OF_DATE; + if (!(dInfoPtr->flags & REDRAW_PENDING)) { + dInfoPtr->flags |= REDRAW_PENDING; + Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr); + } + return TCL_OK; +} + +/* + *-------------------------------------------------------------- + * + * TkTextXviewCmd -- + * + * This procedure 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(textPtr, interp, argc, argv) + TkText *textPtr; /* Information about text widget. */ + Tcl_Interp *interp; /* Current interpreter. */ + int argc; /* Number of arguments. */ + char **argv; /* Argument strings. Someone else has already + * parsed this command enough to know that + * argv[1] is "xview". */ +{ + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + int type, charsPerPage, count, newOffset; + double fraction; + + if (dInfoPtr->flags & DINFO_OUT_OF_DATE) { + UpdateDisplayInfo(textPtr); + } + + if (argc == 2) { + GetXView(interp, textPtr, 0); + return TCL_OK; + } + + newOffset = dInfoPtr->newByteOffset; + type = Tk_GetScrollInfo(interp, argc, argv, &fraction, &count); + switch (type) { + case TK_SCROLL_ERROR: + return TCL_ERROR; + case TK_SCROLL_MOVETO: + if (fraction > 1.0) { + fraction = 1.0; + } + if (fraction < 0) { + fraction = 0; + } + newOffset = (int) (((fraction * dInfoPtr->maxLength) / textPtr->charWidth) + + 0.5); + break; + case TK_SCROLL_PAGES: + charsPerPage = ((dInfoPtr->maxX - dInfoPtr->x) / textPtr->charWidth) + - 2; + if (charsPerPage < 1) { + charsPerPage = 1; + } + newOffset += charsPerPage * count; + break; + case TK_SCROLL_UNITS: + newOffset += count; + break; + } + + dInfoPtr->newByteOffset = newOffset; + dInfoPtr->flags |= DINFO_OUT_OF_DATE; + if (!(dInfoPtr->flags & REDRAW_PENDING)) { + dInfoPtr->flags |= REDRAW_PENDING; + Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr); + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * ScrollByLines -- + * + * This procedure 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 +ScrollByLines(textPtr, offset) + TkText *textPtr; /* Widget to scroll. */ + int offset; /* Amount by which to scroll, in *screen* + * 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 new, 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->tree; + offset--; /* Skip line containing topIndex. */ + for (lineNum = TkBTreeLineIndex(textPtr->topIndex.linePtr); + lineNum >= 0; lineNum--) { + index.linePtr = TkBTreeFindLine(textPtr->tree, lineNum); + index.byteIndex = 0; + lowestPtr = NULL; + do { + dlPtr = LayoutDLine(textPtr, &index); + dlPtr->nextPtr = lowestPtr; + lowestPtr = dlPtr; + TkTextIndexForwBytes(&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; + break; + } + } + + /* + * Discard the display lines, then either return or prepare + * for the next display line to lay out. + */ + + FreeDLines(textPtr, lowestPtr, (DLine *) NULL, 0); + if (offset >= 0) { + goto scheduleUpdate; + } + bytesToCount = INT_MAX; + } + + /* + * Ran off the beginning of the text. Return the first character + * in the text. + */ + + TkTextMakeByteIndex(textPtr->tree, 0, 0, &textPtr->topIndex); + } else { + /* + * Scrolling down, to show later information in the text. + * Just count lines from the current top of the window. + */ + + lastLinePtr = TkBTreeFindLine(textPtr->tree, + TkBTreeNumLines(textPtr->tree)); + for (i = 0; i < offset; i++) { + dlPtr = LayoutDLine(textPtr, &textPtr->topIndex); + if (dlPtr->length == 0 && dlPtr->height == 0) offset++; + dlPtr->nextPtr = NULL; + TkTextIndexForwBytes(&textPtr->topIndex, dlPtr->byteCount, &new); + FreeDLines(textPtr, dlPtr, (DLine *) NULL, 0); + if (new.linePtr == lastLinePtr) { + break; + } + textPtr->topIndex = new; + } + } + + scheduleUpdate: + if (!(dInfoPtr->flags & REDRAW_PENDING)) { + Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr); + } + dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED; +} + +/* + *-------------------------------------------------------------- + * + * TkTextYviewCmd -- + * + * This procedure 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(textPtr, interp, argc, argv) + TkText *textPtr; /* Information about text widget. */ + Tcl_Interp *interp; /* Current interpreter. */ + int argc; /* Number of arguments. */ + char **argv; /* Argument strings. Someone else has already + * parsed this command enough to know that + * argv[1] is "yview". */ +{ + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + int pickPlace, lineNum, type, bytesInLine; + Tk_FontMetrics fm; + int pixels, count; + size_t switchLength; + double fraction; + TkTextIndex index, new; + TkTextLine *lastLinePtr; + DLine *dlPtr; + + if (dInfoPtr->flags & DINFO_OUT_OF_DATE) { + UpdateDisplayInfo(textPtr); + } + + if (argc == 2) { + GetYView(interp, textPtr, 0); + return TCL_OK; + } + + /* + * Next, handle the old syntax: "pathName yview ?-pickplace? where" + */ + + pickPlace = 0; + if (argv[2][0] == '-') { + switchLength = strlen(argv[2]); + if ((switchLength >= 2) + && (strncmp(argv[2], "-pickplace", switchLength) == 0)) { + pickPlace = 1; + if (argc != 4) { + Tcl_AppendResult(interp, "wrong # args: should be \"", + argv[0], " yview -pickplace lineNum|index\"", + (char *) NULL); + return TCL_ERROR; + } + } + } + if ((argc == 3) || pickPlace) { + if (Tcl_GetInt(interp, argv[2+pickPlace], &lineNum) == TCL_OK) { + TkTextMakeByteIndex(textPtr->tree, lineNum, 0, &index); + TkTextSetYView(textPtr, &index, 0); + return TCL_OK; + } + + /* + * The argument must be a regular text index. + */ + + Tcl_ResetResult(interp); + if (TkTextGetIndex(interp, textPtr, argv[2+pickPlace], + &index) != TCL_OK) { + return TCL_ERROR; + } + TkTextSetYView(textPtr, &index, pickPlace); + return TCL_OK; + } + + /* + * New syntax: dispatch based on argv[2]. + */ + + type = Tk_GetScrollInfo(interp, argc, argv, &fraction, &count); + switch (type) { + case TK_SCROLL_ERROR: + return TCL_ERROR; + case TK_SCROLL_MOVETO: + if (fraction > 1.0) { + fraction = 1.0; + } + if (fraction < 0) { + fraction = 0; + } + fraction *= TkBTreeNumLines(textPtr->tree); + lineNum = (int) fraction; + TkTextMakeByteIndex(textPtr->tree, lineNum, 0, &index); + bytesInLine = TkBTreeBytesInLine(index.linePtr); + index.byteIndex = (int)((bytesInLine * (fraction-lineNum)) + 0.5); + if (index.byteIndex >= bytesInLine) { + TkTextMakeByteIndex(textPtr->tree, lineNum + 1, 0, &index); + } + TkTextSetYView(textPtr, &index, 0); + break; + case TK_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. + */ + + Tk_GetFontMetrics(textPtr->tkfont, &fm); + if (count < 0) { + pixels = (dInfoPtr->maxY - 2*fm.linespace - dInfoPtr->y)*(-count) + + fm.linespace; + MeasureUp(textPtr, &textPtr->topIndex, pixels, &new); + if (TkTextIndexCmp(&textPtr->topIndex, &new) == 0) { + /* + * A page of scrolling ended up being less than one line. + * Scroll one line anyway. + */ + + count = -1; + goto scrollByLines; + } + textPtr->topIndex = new; + } else { + /* + * Scrolling down by pages. Layout lines starting at the + * top index and count through the desired vertical distance. + */ + + pixels = (dInfoPtr->maxY - 2*fm.linespace - dInfoPtr->y)*count; + lastLinePtr = TkBTreeFindLine(textPtr->tree, + TkBTreeNumLines(textPtr->tree)); + do { + dlPtr = LayoutDLine(textPtr, &textPtr->topIndex); + dlPtr->nextPtr = NULL; + TkTextIndexForwBytes(&textPtr->topIndex, dlPtr->byteCount, + &new); + pixels -= dlPtr->height; + FreeDLines(textPtr, dlPtr, (DLine *) NULL, 0); + if (new.linePtr == lastLinePtr) { + break; + } + textPtr->topIndex = new; + } while (pixels > 0); + } + if (!(dInfoPtr->flags & REDRAW_PENDING)) { + Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr); + } + dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED; + break; + case TK_SCROLL_UNITS: + scrollByLines: + ScrollByLines(textPtr, count); + break; + } + return TCL_OK; +} + +/* + *-------------------------------------------------------------- + * + * TkTextScanCmd -- + * + * This procedure 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(textPtr, interp, argc, argv) + register TkText *textPtr; /* Information about text widget. */ + Tcl_Interp *interp; /* Current interpreter. */ + int argc; /* Number of arguments. */ + char **argv; /* Argument strings. Someone else has already + * parsed this command enough to know that + * argv[1] is "scan". */ +{ + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + TkTextIndex index; + int c, x, y, totalScroll, newByte, maxByte, gain=10; + Tk_FontMetrics fm; + size_t length; + + if ((argc != 5) && (argc != 6)) { + Tcl_AppendResult(interp, "wrong # args: should be \"", + argv[0], " scan mark x y\" or \"", + argv[0], " scan dragto x y ?gain?\"", (char *) NULL); + return TCL_ERROR; + } + if (Tcl_GetInt(interp, argv[3], &x) != TCL_OK) { + return TCL_ERROR; + } + if (Tcl_GetInt(interp, argv[4], &y) != TCL_OK) { + return TCL_ERROR; + } + if ((argc == 6) && (Tcl_GetInt(interp, argv[5], &gain) != TCL_OK)) + return TCL_ERROR; + c = argv[2][0]; + length = strlen(argv[2]); + if ((c == 'd') && (strncmp(argv[2], "dragto", length) == 0)) { + /* + * 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). + */ + + newByte = dInfoPtr->scanMarkIndex + (gain*(dInfoPtr->scanMarkX - x)) + / (textPtr->charWidth); + maxByte = 1 + (dInfoPtr->maxLength - (dInfoPtr->maxX - dInfoPtr->x) + + textPtr->charWidth - 1)/textPtr->charWidth; + if (newByte < 0) { + newByte = 0; + dInfoPtr->scanMarkIndex = 0; + dInfoPtr->scanMarkX = x; + } else if (newByte > maxByte) { + newByte = maxByte; + dInfoPtr->scanMarkIndex = maxByte; + dInfoPtr->scanMarkX = x; + } + dInfoPtr->newByteOffset = newByte; + + Tk_GetFontMetrics(textPtr->tkfont, &fm); + totalScroll = (gain*(dInfoPtr->scanMarkY - y)) / fm.linespace; + if (totalScroll != dInfoPtr->scanTotalScroll) { + index = textPtr->topIndex; + ScrollByLines(textPtr, totalScroll-dInfoPtr->scanTotalScroll); + dInfoPtr->scanTotalScroll = totalScroll; + if ((index.linePtr == textPtr->topIndex.linePtr) && + (index.byteIndex == textPtr->topIndex.byteIndex)) { + dInfoPtr->scanTotalScroll = 0; + dInfoPtr->scanMarkY = y; + } + } + } else if ((c == 'm') && (strncmp(argv[2], "mark", length) == 0)) { + dInfoPtr->scanMarkIndex = dInfoPtr->newByteOffset; + dInfoPtr->scanMarkX = x; + dInfoPtr->scanTotalScroll = 0; + dInfoPtr->scanMarkY = y; + } else { + Tcl_AppendResult(interp, "bad scan option \"", argv[2], + "\": must be mark or dragto", (char *) NULL); + return TCL_ERROR; + } + dInfoPtr->flags |= DINFO_OUT_OF_DATE; + if (!(dInfoPtr->flags & REDRAW_PENDING)) { + dInfoPtr->flags |= REDRAW_PENDING; + Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr); + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * GetXView -- + * + * This procedure 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(interp, textPtr, report) + 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; + char buffer[TCL_DOUBLE_SPACE * 2]; + double first, last; + int code; + + if (dInfoPtr->maxLength > 0) { + first = ((double) dInfoPtr->curPixelOffset) + / dInfoPtr->maxLength; + last = first + ((double) (dInfoPtr->maxX - dInfoPtr->x)) + / dInfoPtr->maxLength; + if (last > 1.0) { + last = 1.0; + } + } else { + first = 0; + last = 1.0; + } + if (!report) { + sprintf(buffer, "%g %g", first, last); + Tcl_SetResult(interp, buffer, TCL_VOLATILE); + return; + } + if ((first == dInfoPtr->xScrollFirst) && (last == dInfoPtr->xScrollLast)) { + return; + } + dInfoPtr->xScrollFirst = first; + dInfoPtr->xScrollLast = last; + sprintf(buffer, " %g %g", first, last); + code = Tcl_VarEval(interp, textPtr->xScrollCmd, + buffer, (char *) NULL); + if (code != TCL_OK) { + Tcl_AddErrorInfo(interp, + "\n (horizontal scrolling command executed by text)"); + Tcl_BackgroundError(interp); + } +} + +/* + *---------------------------------------------------------------------- + * + * GetYView -- + * + * This procedure 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(interp, textPtr, report) + 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; + char buffer[TCL_DOUBLE_SPACE * 2]; + double first, last; + DLine *dlPtr; + int totalLines, code, count; + + dlPtr = dInfoPtr->dLinePtr; + totalLines = TkBTreeNumLines(textPtr->tree); + first = (double) TkBTreeLineIndex(dlPtr->index.linePtr) + + (double) dlPtr->index.byteIndex + / TkBTreeBytesInLine(dlPtr->index.linePtr); + first /= totalLines; + while (1) { + if ((dlPtr->y + dlPtr->height) > dInfoPtr->maxY) { + /* + * The last line is only partially visible, so don't + * count its characters in what's visible. + */ + count = 0; + break; + } + if (dlPtr->nextPtr == NULL) { + count = dlPtr->byteCount; + break; + } + dlPtr = dlPtr->nextPtr; + } + last = ((double) TkBTreeLineIndex(dlPtr->index.linePtr)) + + ((double) (dlPtr->index.byteIndex + count)) + / (TkBTreeBytesInLine(dlPtr->index.linePtr)); + last /= totalLines; + if (!report) { + sprintf(buffer, "%g %g", first, last); + Tcl_SetResult(interp, buffer, TCL_VOLATILE); + return; + } + if ((first == dInfoPtr->yScrollFirst) && (last == dInfoPtr->yScrollLast)) { + return; + } + dInfoPtr->yScrollFirst = first; + dInfoPtr->yScrollLast = last; + sprintf(buffer, " %g %g", first, last); + code = Tcl_VarEval(interp, textPtr->yScrollCmd, buffer, (char *) NULL); + if (code != TCL_OK) { + Tcl_AddErrorInfo(interp, + "\n (vertical scrolling command executed by text)"); + Tcl_BackgroundError(interp); + } +} + +/* + *---------------------------------------------------------------------- + * + * FindDLine -- + * + * This procedure 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(dlPtr, indexPtr) + register DLine *dlPtr; /* Pointer to first in list of DLines + * to search. */ + TkTextIndex *indexPtr; /* Index of desired character. */ +{ + TkTextLine *linePtr; + + if (dlPtr == NULL) { + return NULL; + } + if (TkBTreeLineIndex(indexPtr->linePtr) + < TkBTreeLineIndex(dlPtr->index.linePtr)) { + /* + * The first display line is already past the desired line. + */ + return dlPtr; + } + + /* + * Find the first display line that covers the desired text line. + */ + + linePtr = dlPtr->index.linePtr; + while (linePtr != indexPtr->linePtr) { + while (dlPtr->index.linePtr == linePtr) { + dlPtr = dlPtr->nextPtr; + if (dlPtr == NULL) { + return NULL; + } + } + linePtr = TkBTreeNextLine(linePtr); + if (linePtr == NULL) { + panic("FindDLine reached end of text"); + } + } + if (indexPtr->linePtr != dlPtr->index.linePtr) { + return dlPtr; + } + + /* + * Now get to the right position within the text line. + */ + + while (indexPtr->byteIndex >= (dlPtr->index.byteIndex + dlPtr->byteCount)) { + dlPtr = dlPtr->nextPtr; + if ((dlPtr == NULL) || (dlPtr->index.linePtr != indexPtr->linePtr)) { + break; + } + } + return dlPtr; +} + +/* + *---------------------------------------------------------------------- + * + * 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(textPtr, x, y, indexPtr) + TkText *textPtr; /* Widget record for text widget. */ + int x, 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). */ +{ + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + register DLine *dlPtr, *validdlPtr; + register TkTextDispChunk *chunkPtr; + + /* + * 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; + } + if (x >= dInfoPtr->maxX) { + x = dInfoPtr->maxX - 1; + } + if (x < dInfoPtr->x) { + x = dInfoPtr->x; + } + + /* + * Find the display line containing the desired y-coordinate. + */ + + 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; + break; + } + } + if (dlPtr->chunkPtr == NULL) dlPtr = validdlPtr; + + + /* + * 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->curPixelOffset; + for (chunkPtr = dlPtr->chunkPtr; x >= (chunkPtr->x + chunkPtr->width); + indexPtr->byteIndex += chunkPtr->numBytes, + chunkPtr = chunkPtr->nextPtr) { + if (chunkPtr->nextPtr == NULL) { + indexPtr->byteIndex += chunkPtr->numBytes; + TkTextIndexBackChars(indexPtr, 1, indexPtr); + return; + } + } + + /* + * If the chunk has more than one byte in it, ask it which + * character is at the desired location. + */ + + if (chunkPtr->numBytes > 1) { + indexPtr->byteIndex += (*chunkPtr->measureProc)(chunkPtr, x); + } +} + +/* + *---------------------------------------------------------------------- + * + * TkTextCharBbox -- + * + * Given an index, find the bounding box of the screen area + * occupied by 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 the bounding box of the part of the character that's + * visible on the screen is returned to *xPtr, *yPtr, *widthPtr, + * and *heightPtr. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +int +TkTextCharBbox(textPtr, indexPtr, xPtr, yPtr, widthPtr, heightPtr) + TkText *textPtr; /* Widget record for text widget. */ + TkTextIndex *indexPtr; /* Index of character whose bounding + * box is desired. */ + int *xPtr, *yPtr; /* Filled with character's upper-left + * coordinate. */ + int *widthPtr, *heightPtr; /* Filled in with character's dimensions. */ +{ + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + DLine *dlPtr; + register TkTextDispChunk *chunkPtr; + int byteIndex; + + /* + * 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(dInfoPtr->dLinePtr, indexPtr); + if ((dlPtr == NULL) || (TkTextIndexCmp(&dlPtr->index, indexPtr) > 0)) { + return -1; + } + + /* + * Find the chunk within the line that contains the desired + * index. + */ + + byteIndex = indexPtr->byteIndex - dlPtr->index.byteIndex; + for (chunkPtr = dlPtr->chunkPtr; ; chunkPtr = chunkPtr->nextPtr) { + if (chunkPtr == NULL) { + return -1; + } + if (byteIndex < chunkPtr->numBytes) { + break; + } + byteIndex -= chunkPtr->numBytes; + } + + /* + * Call a chunk-specific procedure 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)(chunkPtr, byteIndex, dlPtr->y + dlPtr->spaceAbove, + dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow, + dlPtr->baseline - dlPtr->spaceAbove, xPtr, yPtr, widthPtr, + heightPtr); + *xPtr = *xPtr + dInfoPtr->x - dInfoPtr->curPixelOffset; + if ((byteIndex == (chunkPtr->numBytes - 1)) && (chunkPtr->nextPtr == NULL)) { + /* + * Last character in display line. Give it all the space up to + * the line. + */ + + if (*xPtr > dInfoPtr->maxX) { + *xPtr = dInfoPtr->maxX; + } + *widthPtr = dInfoPtr->maxX - *xPtr; + } + 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(textPtr, indexPtr, xPtr, yPtr, widthPtr, heightPtr, basePtr) + TkText *textPtr; /* Widget record for text widget. */ + TkTextIndex *indexPtr; /* Index of character whose bounding + * box is desired. */ + int *xPtr, *yPtr; /* Filled with line's upper-left + * coordinate. */ + int *widthPtr, *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(dInfoPtr->dLinePtr, indexPtr); + if ((dlPtr == NULL) || (TkTextIndexCmp(&dlPtr->index, indexPtr) > 0)) { + return -1; + } + + dlx = (dlPtr->chunkPtr != NULL? dlPtr->chunkPtr->x: 0); + *xPtr = dInfoPtr->x - dInfoPtr->curPixelOffset + 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; +} + +static void +ElideBboxProc(chunkPtr, index, y, lineHeight, baseline, xPtr, yPtr, + widthPtr, heightPtr) + 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, *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; +} + + +static int +ElideMeasureProc(chunkPtr, x) + TkTextDispChunk *chunkPtr; /* Chunk containing desired coord. */ + int x; /* X-coordinate, in same coordinate + * system as chunkPtr->x. */ +{ + return 0 /*chunkPtr->numBytes - 1*/; +} + +/* + *-------------------------------------------------------------- + * + * TkTextCharLayoutProc -- + * + * This procedure is the "layoutProc" for character segments. + * +n * 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(textPtr, indexPtr, segPtr, byteOffset, maxX, maxBytes, + noCharsYet, wrapMode, chunkPtr) + 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; + + /* + * 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 haven't + * already exceeded the character limit, and the next character + * is a white space character. + */ + + p = segPtr->body.chars + byteOffset; + tkfont = chunkPtr->stylePtr->sValuePtr->tkfont; + bytesThatFit = MeasureChars(tkfont, p, maxBytes, chunkPtr->x, maxX, 0, + &nextX); + if (bytesThatFit < maxBytes) { + if ((bytesThatFit == 0) && noCharsYet) { + Tcl_UniChar ch; + + bytesThatFit = MeasureChars(tkfont, p, Tcl_UtfToUniChar(p, &ch), + chunkPtr->x, -1, 0, &nextX); + } + 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) { + 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; + ciPtr = (CharInfo *) ckalloc((unsigned) + (sizeof(CharInfo) - 3 + bytesThatFit)); + chunkPtr->clientData = (ClientData) ciPtr; + ciPtr->numBytes = bytesThatFit; + strncpy(ciPtr->chars, p, (size_t) bytesThatFit); + if (p[bytesThatFit - 1] == '\n') { + ciPtr->numBytes--; + } + + /* + * 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--) { + if (isspace(UCHAR(*p))) { + chunkPtr->breakIndex = count; + break; + } + } + 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; +} + +/* + *-------------------------------------------------------------- + * + * CharDisplayProc -- + * + * This procedure 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(chunkPtr, x, y, height, baseline, display, dst, screenY) + 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 = (CharInfo *) chunkPtr->clientData; + TextStyle *stylePtr; + StyleValues *sValuePtr; + int offsetBytes, offsetX; + + if ((x + chunkPtr->width) <= 0) { + /* + * The chunk is off-screen. + */ + + return; + } + + 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 = MeasureChars(sValuePtr->tkfont, ciPtr->chars, + ciPtr->numBytes, x, 0, x - chunkPtr->x, &offsetX); + } + + /* + * Draw the text, underline, and overstrike for this chunk. + */ + + if (!sValuePtr->elide && (ciPtr->numBytes > offsetBytes) && (stylePtr->fgGC != None)) { + int numBytes = ciPtr->numBytes - offsetBytes; + char *string = ciPtr->chars + 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->fgGC, sValuePtr->tkfont, + ciPtr->chars + offsetBytes, offsetX, + y + baseline - sValuePtr->offset, 0, numBytes); + + } + if (sValuePtr->overstrike) { + Tk_FontMetrics fm; + + Tk_GetFontMetrics(sValuePtr->tkfont, &fm); + Tk_UnderlineChars(display, dst, stylePtr->fgGC, sValuePtr->tkfont, + ciPtr->chars + offsetBytes, offsetX, + y + baseline - sValuePtr->offset + - fm.descent - (fm.ascent * 3) / 10, + 0, numBytes); + } + } +} + +/* + *-------------------------------------------------------------- + * + * CharUndisplayProc -- + * + * This procedure 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(textPtr, chunkPtr) + TkText *textPtr; /* Overall information about text + * widget. */ + TkTextDispChunk *chunkPtr; /* Chunk that is about to be freed. */ +{ + CharInfo *ciPtr = (CharInfo *) chunkPtr->clientData; + + ckfree((char *) ciPtr); +} + +/* + *-------------------------------------------------------------- + * + * CharMeasureProc -- + * + * This procedure 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(chunkPtr, x) + TkTextDispChunk *chunkPtr; /* Chunk containing desired coord. */ + int x; /* X-coordinate, in same coordinate + * system as chunkPtr->x. */ +{ + CharInfo *ciPtr = (CharInfo *) chunkPtr->clientData; + int endX; + + return MeasureChars(chunkPtr->stylePtr->sValuePtr->tkfont, ciPtr->chars, + chunkPtr->numBytes - 1, chunkPtr->x, x, 0, &endX); + /* CHAR OFFSET */ +} + +/* + *-------------------------------------------------------------- + * + * CharBboxProc -- + * + * This procedure 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(chunkPtr, byteIndex, y, lineHeight, baseline, xPtr, yPtr, + widthPtr, heightPtr) + 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, *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 = (CharInfo *) chunkPtr->clientData; + int maxX; + + maxX = chunkPtr->width + chunkPtr->x; + MeasureChars(chunkPtr->stylePtr->sValuePtr->tkfont, ciPtr->chars, + 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 { + MeasureChars(chunkPtr->stylePtr->sValuePtr->tkfont, + ciPtr->chars + 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 procedure 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(textPtr, tabArrayPtr, index, chunkPtr) + 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, prev, spaceWidth; + char *p; + TkTextTabAlign alignment; + + if (chunkPtr->nextPtr == NULL) { + /* + * Nothing after the actual tab; just return. + */ + + return; + } + + /* + * If no tab information has been given, do the usual thing: + * round up to the next boundary of 8 average-sized characters. + */ + + x = chunkPtr->nextPtr->x; + if ((tabArrayPtr == NULL) || (tabArrayPtr->numTabs == 0)) { + /* + * No tab information has been given, so use the default + * interpretation of tabs. + */ + + 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. + */ + + if (tabArrayPtr->numTabs > 1) { + prev = tabArrayPtr->tabs[tabArrayPtr->numTabs-2].location; + } else { + prev = 0; + } + alignment = tabArrayPtr->tabs[tabArrayPtr->numTabs-1].alignment; + tabX = tabArrayPtr->tabs[tabArrayPtr->numTabs-1].location + + (index + 1 - tabArrayPtr->numTabs) + * (tabArrayPtr->tabs[tabArrayPtr->numTabs-1].location - prev); + } + + 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 = (CharInfo *) 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 = (CharInfo *) decimalChunkPtr->clientData; + MeasureChars(decimalChunkPtr->stylePtr->sValuePtr->tkfont, + ciPtr->chars, decimal, decimalChunkPtr->x, -1, 0, &curX); + desired = tabX - (curX - x); + goto update; + } else { + /* + * 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, &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 index'th tab of tabArrayPtr, assuming that + * the current position on the line is x and the end of the + * line is maxX. For numeric tabs, this is a conservative + * estimate. The return value is always >= 0. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +SizeOfTab(textPtr, tabArrayPtr, index, x, maxX) + TkText *textPtr; /* Information about the text widget as + * a whole. */ + TkTextTabArray *tabArrayPtr; /* Information about the tab stops + * that apply to this line. NULL + * means use default tabbing (every + * 8 chars.) */ + int index; /* Index of current tab stop. */ + int x; /* Current x-location in line. Only + * used if tabArrayPtr == NULL. */ + int maxX; /* X-location of pixel just past the + * right edge of the line. */ +{ + int tabX, prev, result, spaceWidth; + TkTextTabAlign alignment; + + if ((tabArrayPtr == NULL) || (tabArrayPtr->numTabs == 0)) { + tabX = NextTabStop(textPtr->tkfont, x, 0); + return tabX - x; + } + 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 + * from the last two tab positions. + */ + + if (tabArrayPtr->numTabs > 1) { + prev = tabArrayPtr->tabs[tabArrayPtr->numTabs-2].location; + } else { + prev = 0; + } + tabX = tabArrayPtr->tabs[tabArrayPtr->numTabs-1].location + + (index + 1 - tabArrayPtr->numTabs) + * (tabArrayPtr->tabs[tabArrayPtr->numTabs-1].location - prev); + alignment = tabArrayPtr->tabs[tabArrayPtr->numTabs-1].alignment; + } + 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, &spaceWidth); + if (result < spaceWidth) { + result = spaceWidth; + } + return result; +} + +/* + *--------------------------------------------------------------------------- + * + * NextTabStop -- + * + * Given the current position, determine where the next default + * tab stop would be located. This procedure 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. + * + * Results: + * The location in pixels of the next tab stop. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ + +static int +NextTabStop(tkfont, x, tabOrigin) + 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_DrawTextLayout will be used to actually display + * the characters. + * + * If tabs are encountered in the string, they will be expanded + * to the next tab stop, unless the TK_IGNORE_TABS flag is specified. + * + * If a newline is encountered in the string, the line will be + * broken at that point, unless the TK_NEWSLINES_NOT_SPECIAL flag + * is specified. + * + * Results: + * The return value is the number of bytes from 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(tkfont, source, maxBytes, startX, maxX, tabOrigin, nextXPtr) + 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 startX; /* X-position at which first character will + * be drawn. */ + int maxX; /* Don't consider any character that would + * cross this x-position. */ + int tabOrigin; /* X-location that serves as "origin" for + * tab stops. */ + int *nextXPtr; /* Return x-position of terminating + * character here. */ +{ + int curX, width, ch; + CONST char *special, *end, *start; + + ch = 0; /* lint. */ + curX = startX; + special = source; + end = source + maxBytes; + for (start = source; 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; + } + start += Tk_MeasureChars(tkfont, start, special - start, maxX - curX, + 0, &width); + curX += width; + if (start < special) { + /* + * No more chars fit in line. + */ + + break; + } + if (special < end) { + if (ch == '\t') { + start++; + } else { + break; + } + } + } + + *nextXPtr = curX; + return start - source; +} + +/* End of tktextdisp.c */