/* * tkTextDisp.c (CTk) -- * * 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-1995 Sun Microsystems, Inc. * Copyright (c) 1995 Cleveland Clinic Foundation * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * * @(#) $Id: ctk.shar,v 1.50 1996/01/15 14:47:16 andrewm Exp andrewm $ */ #include "tkPort.h" #include "tkInt.h" #include "tkText.h" /* * 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 { 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 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. */ Tk_Uid wrapMode; /* How to handle wrap-around for this tag. * One of tkTextCharUid, tkTextNoneUid, * or tkTextWordUid. */ } StyleValues; /* * The following structure extends the StyleValues structure above with * the CTk style used to actually draw the characters. The entries * in dInfoPtr->styleTable point to structures of this type. */ typedef struct Style { int refCount; /* Number of times this structure is * referenced in Chunks. */ Ctk_Style ctkStyle; StyleValues *sValuePtr; /* Raw information from which GCs were * derived. */ Tcl_HashEntry *hPtr; /* Pointer to entry in styleTable. Used * to delete entry. */ } Style; /* * 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 count; /* Number of characters 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 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: * * 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. */ #define NEW_LAYOUT 2 #define TOP_LINE 4 #define BOTTOM_LINE 8 /* * Overall display information for a text widget: */ typedef struct DInfo { Tcl_HashTable styleTable; /* Hash table that maps from StyleValues * to Styles for this widget. */ DLine *dLinePtr; /* First in list of all display lines for * this widget, in order from top to bottom. */ 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 newCharOffset; /* 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. */ /* * 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. */ } DInfo; /* * In TkTextDispChunk structures for character segments, the clientData * field points to one of the following structures: */ typedef struct CharInfo { int numChars; /* Number of characters to display. */ char chars[4]; /* Characters to display. Actual size * will be numChars, not 4. THIS MUST BE * THE LAST FIELD IN THE STRUCTURE. */ } CharInfo; /* * Flag values for DInfo 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. */ #define DINFO_OUT_OF_DATE 1 #define REDRAW_PENDING 2 #define REDRAW_BORDERS 4 /* * 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. */ /* * 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 *xPtr, int *yPtr, int *widthPtr, int *heightPtr)); static void CharDisplayProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr, int x, int y, Tk_Window win)); static int CharMeasureProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr, int x)); static void CharUndisplayProc _ANSI_ARGS_((TkText *textPtr, TkTextDispChunk *chunkPtr)); static void DisplayDLine _ANSI_ARGS_((TkText *textPtr, DLine *dlPtr, DLine *prevPtr)); 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, Style *stylePtr)); static Style * 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 void MeasureUp _ANSI_ARGS_((TkText *textPtr, TkTextIndex *srcPtr, int distance, TkTextIndex *dstPtr)); 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)); /* *---------------------------------------------------------------------- * * 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 DInfo data structure is allocated and initialized and attached * to textPtr. * *---------------------------------------------------------------------- */ void TkTextCreateDInfo(textPtr) TkText *textPtr; /* Overall information for text widget. */ { register DInfo *dInfoPtr; dInfoPtr = (DInfo *) ckalloc(sizeof(DInfo)); Tcl_InitHashTable(&dInfoPtr->styleTable, sizeof(StyleValues)/sizeof(int)); dInfoPtr->dLinePtr = NULL; dInfoPtr->topOfEof = 0; dInfoPtr->newCharOffset = 0; dInfoPtr->curPixelOffset = 0; dInfoPtr->maxLength = 0; dInfoPtr->xScrollFirst = -1; dInfoPtr->xScrollLast = -1; dInfoPtr->yScrollFirst = -1; dInfoPtr->yScrollLast = -1; 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 DInfo *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->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 Style structure that * corresponds to *sValuePtr. * * Side effects: * A new entry may be created in the style table for the widget. * *---------------------------------------------------------------------- */ static Style * 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; Style *stylePtr; Tcl_HashEntry *hPtr; int numTags, new, i; /* * The variables below keep track of the highest-priority specification * that has occurred for each of the various fields of the StyleValues. */ int underlinePrio, justifyPrio, offsetPrio; int lMargin1Prio, lMargin2Prio, rMarginPrio; int spacing1Prio, spacing2Prio, spacing3Prio; int 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); underlinePrio = justifyPrio = offsetPrio = -1; lMargin1Prio = lMargin2Prio = rMarginPrio = -1; spacing1Prio = spacing2Prio = spacing3Prio = -1; tabPrio = wrapPrio = -1; memset((VOID *) &styleValues, 0, sizeof(StyleValues)); 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; for (i = 0 ; i < numTags; i++) { tagPtr = tagPtrs[i]; 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->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->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 = (Style *) Tcl_GetHashValue(hPtr); stylePtr->refCount++; return stylePtr; } /* * No existing style matched. Make a new one. */ stylePtr = (Style *) ckalloc(sizeof(Style)); stylePtr->refCount = 1; stylePtr->ctkStyle = styleValues.underline ? CTK_UNDERLINE_STYLE : CTK_PLAIN_STYLE; 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 Style 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 Style *stylePtr; /* Information about style to be freed. */ { stylePtr->refCount--; if (stylePtr->refCount == 0) { 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 breakCharOffset; /* 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 first character in line. */ int jIndent; /* Additional indentation (beyond * margins) due to justification. */ int rMargin; /* Right margin width for line. */ Tk_Uid 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 maxChars; /* Maximum number of characters to * include in this chunk. */ TkTextTabArray *tabArrayPtr; /* Tab stops for line; taken from * style for first character on line. */ int tabSize; /* Number of pixels consumed by current * tab stop. */ TkTextDispChunk *lastCharChunkPtr; /* Pointer to last chunk in display * lines with numChars > 0. Used to * drop 0-sized chunks from the end * of the line. */ int offset, code; StyleValues *sValuePtr; /* * Create and initialize a new DLine structure. */ dlPtr = (DLine *) ckalloc(sizeof(DLine)); dlPtr->index = *indexPtr; dlPtr->count = 0; dlPtr->y = 0; dlPtr->oldY = -1; dlPtr->height = 0; dlPtr->chunkPtr = NULL; dlPtr->nextPtr = NULL; dlPtr->flags = NEW_LAYOUT; /* * 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; breakChunkPtr = NULL; breakCharOffset = 0; justify = TK_JUSTIFY_LEFT; tabIndex = -1; tabChunkPtr = NULL; tabArrayPtr = NULL; rMargin = 0; wrapMode = tkTextCharUid; 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 (offset = curIndex.charIndex, segPtr = curIndex.linePtr->segPtr; (offset > 0) && (offset >= segPtr->size); offset -= segPtr->size, segPtr = segPtr->nextPtr) { /* Empty loop body. */ } while (segPtr != NULL) { if (segPtr->typePtr->layoutProc == NULL) { segPtr = segPtr->nextPtr; offset = 0; continue; } if (chunkPtr == NULL) { chunkPtr = (TkTextDispChunk *) ckalloc(sizeof(TkTextDispChunk)); chunkPtr->nextPtr = NULL; } chunkPtr->stylePtr = GetStyle(textPtr, &curIndex); /* * 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.charIndex == 0) ? chunkPtr->stylePtr->sValuePtr->lMargin1 : chunkPtr->stylePtr->sValuePtr->lMargin2); if (wrapMode == tkTextNoneUid) { maxX = INT_MAX; } 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; maxChars = segPtr->size - offset; if (justify == TK_JUSTIFY_LEFT) { if (segPtr->typePtr == &tkTextCharType) { char *p; for (p = segPtr->body.chars + offset; *p != 0; p++) { if (*p == '\t') { maxChars = (p + 1 - segPtr->body.chars) - offset; gotTab = 1; break; } } } } chunkPtr->x = x; code = (*segPtr->typePtr->layoutProc)(textPtr, &curIndex, segPtr, offset, maxX-tabSize, maxChars, 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; offset = 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->numChars > 0) { noCharsYet = 0; lastCharChunkPtr = chunkPtr; } if (lastChunkPtr == NULL) { dlPtr->chunkPtr = chunkPtr; } else { lastChunkPtr->nextPtr = chunkPtr; } lastChunkPtr = chunkPtr; x += chunkPtr->width; if (chunkPtr->breakIndex > 0) { breakCharOffset = chunkPtr->breakIndex; breakIndex = curIndex; breakChunkPtr = chunkPtr; } if (chunkPtr->numChars != maxChars) { 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 (tabSize >= (maxX - x)) { break; } } curIndex.charIndex += chunkPtr->numChars; offset += chunkPtr->numChars; if (offset >= segPtr->size) { offset = 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; breakCharOffset = breakChunkPtr->numChars; } if ((breakChunkPtr != NULL) && ((lastChunkPtr != breakChunkPtr) || (breakCharOffset != lastChunkPtr->numChars))) { while (1) { chunkPtr = breakChunkPtr->nextPtr; if (chunkPtr == NULL) { break; } breakChunkPtr->nextPtr = chunkPtr->nextPtr; (*chunkPtr->undisplayProc)(textPtr, chunkPtr); ckfree((char *) chunkPtr); } if (breakCharOffset != breakChunkPtr->numChars) { (*breakChunkPtr->undisplayProc)(textPtr, breakChunkPtr); segPtr = TkTextIndexToSeg(&breakIndex, &offset); (*segPtr->typePtr->layoutProc)(textPtr, &breakIndex, segPtr, offset, maxX, breakCharOffset, 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 characters. 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 == tkTextNoneUid) { 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; } for (chunkPtr = dlPtr->chunkPtr; chunkPtr != NULL; chunkPtr = chunkPtr->nextPtr) { chunkPtr->x += jIndent; dlPtr->count += chunkPtr->numChars; if (chunkPtr->minHeight > dlPtr->height) { dlPtr->height = chunkPtr->minHeight; } } sValuePtr = dlPtr->chunkPtr->stylePtr->sValuePtr; if (dlPtr->index.charIndex == 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 = 1 + dlPtr->spaceAbove + dlPtr->spaceBelow; /* * 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 DInfo *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; } newPtr->nextPtr = dlPtr; dlPtr = newPtr; } else { /* * DlPtr refers to the line we want. Next check the * index within the line. */ if (index.charIndex == dlPtr->index.charIndex) { /* * Case (a) -- can use existing display line as-is. */ goto lineOK; } if (index.charIndex < dlPtr->index.charIndex) { 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; TkTextIndexForwChars(&index, dlPtr->count, &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, charsToCount; 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); charsToCount = dInfoPtr->dLinePtr->index.charIndex; if (charsToCount == 0) { charsToCount = INT_MAX; lineNum--; } for ( ; (lineNum >= 0) && (spaceLeft > 0); lineNum--) { index.linePtr = TkBTreeFindLine(textPtr->tree, lineNum); index.charIndex = 0; lowestPtr = NULL; do { dlPtr = LayoutDLine(textPtr, &index); dlPtr->nextPtr = lowestPtr; lowestPtr = dlPtr; TkTextIndexForwChars(&index, dlPtr->count, &index); charsToCount -= dlPtr->count; } while ((charsToCount > 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); charsToCount = 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; while (1) { if (dlPtr->nextPtr == NULL) { 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); if (dInfoPtr->newCharOffset > maxOffset) { dInfoPtr->newCharOffset = maxOffset; } if (dInfoPtr->newCharOffset < 0) { dInfoPtr->newCharOffset = 0; } pixelOffset = dInfoPtr->newCharOffset; 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) 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. */ { register Tk_Window win = textPtr->tkwin; register TkTextDispChunk *chunkPtr; DInfo *dInfoPtr = textPtr->dInfoPtr; int height, x; /* * First, clear the area of the line to the background color for the * text widget. */ height = dlPtr->height; if ((height + dlPtr->y) > dInfoPtr->maxY) { height = dInfoPtr->maxY - dlPtr->y; } Ctk_FillRect(win, dInfoPtr->x, dlPtr->y, dInfoPtr->maxX, dlPtr->y+height, CTK_PLAIN_STYLE, ' '); /* * 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) { 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). */ (*chunkPtr->displayProc)(chunkPtr, -chunkPtr->width, dlPtr->y + dlPtr->spaceAbove, win); } else { (*chunkPtr->displayProc)(chunkPtr, x, dlPtr->y + dlPtr->spaceAbove, win); } if (dInfoPtr->dLinesInvalidated) { return; } } linesRedrawn++; } /* *---------------------------------------------------------------------- * * 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; DInfo *dInfoPtr = textPtr->dInfoPtr; register DLine *dlPtr; DLine *prevPtr; int bottomY = 0; /* Initialization needed only to stop * compiler warnings. */ if (textPtr->tkwin == NULL) { /* * The widget has been deleted. Don't do anything. */ return; } if (tkTextDebug) { Tcl_SetVar2(textPtr->interp, "tk_textRelayout", (char *) NULL, "", TCL_GLOBAL_ONLY); } if (!Tk_IsMapped(textPtr->tkwin) || (dInfoPtr->maxX <= dInfoPtr->x) || (dInfoPtr->maxY <= dInfoPtr->y)) { UpdateDisplayInfo(textPtr); dInfoPtr->flags &= ~REDRAW_PENDING; goto doScrollbars; } numRedisplays++; if (tkTextDebug) { Tcl_SetVar2(textPtr->interp, "tk_textRedraw", (char *) NULL, "", TCL_GLOBAL_ONLY); } /* * First recompute what's supposed to be displayed. */ UpdateDisplayInfo(textPtr); dInfoPtr->dLinesInvalidated = 0; /* * 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(textPtr->interp, "tk_textRedraw", (char *) NULL, "borders", TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT); } Ctk_DrawBorder(textPtr->tkwin, CTK_PLAIN_STYLE, (char *) NULL); dInfoPtr->flags &= ~REDRAW_BORDERS; } /* * Now redraw the lines. */ for (prevPtr = NULL, dlPtr = textPtr->dInfoPtr->dLinePtr; (dlPtr != NULL) && (dlPtr->y < dInfoPtr->maxY); prevPtr = dlPtr, dlPtr = dlPtr->nextPtr) { 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); if (dInfoPtr->dLinesInvalidated) { return; } dlPtr->oldY = dlPtr->y; dlPtr->flags &= ~NEW_LAYOUT; } bottomY = dlPtr->y + dlPtr->height; } for ( ; dlPtr != NULL; dlPtr = dlPtr->nextPtr) { bottomY = dlPtr->y + dlPtr->height; } /* * See if we need to refresh the part of the window below the * last line of text (if there is any such area). */ 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); } Ctk_FillRect(textPtr->tkwin, dInfoPtr->x, bottomY, dInfoPtr->maxX, dInfoPtr->topOfEof, CTK_PLAIN_STYLE, ' '); } 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); } /* * Update the horizontal scrollbar, if any. */ if (textPtr->xScrollCmd != NULL) { GetXView(textPtr->interp, textPtr, 1); } } } /* *---------------------------------------------------------------------- * * 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. */ { register DLine *dlPtr; DInfo *dInfoPtr = textPtr->dInfoPtr; int maxY, inset; /* * Find all lines that overlap the given region and mark them for * redisplay. */ maxY = y + height; for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) { if (((dlPtr->y + dlPtr->height) > y) && (dlPtr->y < maxY)) { dlPtr->oldY = -1; } } if (dInfoPtr->topOfEof < maxY) { dInfoPtr->topOfEof = maxY; } /* * Schedule the redisplay operation if there isn't one already * scheduled. */ if (!(dInfoPtr->flags & REDRAW_PENDING)) { dInfoPtr->flags |= REDRAW_PENDING; Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr); } inset = textPtr->borderWidth; if ((x < inset) || (y < inset) || ((x + width) > (Tk_Width(textPtr->tkwin) - inset)) || (maxY > (Tk_Height(textPtr->tkwin) - inset))) { 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. */ { DInfo *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; /* * 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.charIndex = 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; DInfo *dInfoPtr = textPtr->dInfoPtr; 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 = TkTextMakeIndex(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); tagOn = TkBTreeCharTagged(index1Ptr, tagPtr); if (tagOn != withTag) { if (!TkBTreeNextTag(&search)) { return; } } /* * 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; /* * 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 (search.curIndex.charIndex == 0) { dlPtr = FindDLine(dlPtr, &search.curIndex); } else { TkTextIndex tmp; tmp = search.curIndex; tmp.charIndex -= 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 { endIndexPtr = &search.curIndex; } endPtr = FindDLine(dlPtr, endIndexPtr); if ((endPtr != NULL) && (endPtr->index.linePtr == endIndexPtr->linePtr) && (endPtr->index.charIndex < endIndexPtr->charIndex)) { 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. */ { DInfo *dInfoPtr = textPtr->dInfoPtr; /* * 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; /* * 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. */ dInfoPtr->x = textPtr->borderWidth + textPtr->padX; dInfoPtr->y = textPtr->borderWidth + textPtr->padY; dInfoPtr->maxX = Tk_Width(textPtr->tkwin) - textPtr->borderWidth - textPtr->padX; if (dInfoPtr->maxX <= dInfoPtr->x) { dInfoPtr->maxX = dInfoPtr->x + 1; } dInfoPtr->maxY = Tk_Height(textPtr->tkwin) - 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.charIndex != 0) { MeasureUp(textPtr, &textPtr->topIndex, 0, &textPtr->topIndex); } } /* *---------------------------------------------------------------------- * * 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. */ { DInfo *dInfoPtr = textPtr->dInfoPtr; register DLine *dlPtr; int bottomY, close, lineIndex; TkTextIndex tmpIndex, rounded; /* * 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->charIndex == 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.charIndex <= indexPtr->charIndex)) { return; } } /* * The desired line isn't already on-screen. * 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. */ bottomY = (dInfoPtr->y + dInfoPtr->maxY + 1)/2; close = (dInfoPtr->maxY - dInfoPtr->y)/3; if (close < 3) { close = 3; } close += 1; 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; } /* *-------------------------------------------------------------- * * 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 charsToCount; /* Maximum number of characters 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; charsToCount = srcPtr->charIndex + 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 (charsToCount 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.charIndex = 0; lowestPtr = NULL; do { dlPtr = LayoutDLine(textPtr, &index); dlPtr->nextPtr = lowestPtr; lowestPtr = dlPtr; TkTextIndexForwChars(&index, dlPtr->count, &index); charsToCount -= dlPtr->count; } while ((charsToCount > 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; } charsToCount = INT_MAX; /* Consider all chars. in next line. */ } /* * Ran off the beginning of the text. Return the first character * in the text. */ TkTextMakeIndex(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". */ { DInfo *dInfoPtr = textPtr->dInfoPtr; TkTextIndex index; int x, y, width, height, lineWidth, charCount, 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); charCount = index.charIndex - dlPtr->index.charIndex; for (chunkPtr = dlPtr->chunkPtr; ; chunkPtr = chunkPtr->nextPtr) { if (charCount < chunkPtr->numChars) { break; } charCount -= chunkPtr->numChars; } /* * Call a chunk-specific procedure to find the horizontal range of * the character within the chunk. */ (*chunkPtr->bboxProc)(chunkPtr, charCount, dlPtr->y + dlPtr->spaceAbove, &x, &y, &width, &height); delta = x - dInfoPtr->curPixelOffset; oneThird = lineWidth/3; if (delta < 0) { if (delta < -oneThird) { dInfoPtr->newCharOffset = x - lineWidth/2; } else { dInfoPtr->newCharOffset -= -delta; } } else { delta -= (lineWidth - width); if (delta > 0) { if (delta > oneThird) { dInfoPtr->newCharOffset = x - lineWidth/2; } else { dInfoPtr->newCharOffset += delta ; } } 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". */ { DInfo *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->newCharOffset; 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 = (fraction * dInfoPtr->maxLength) + 0.5; newOffset = fraction * dInfoPtr->maxLength; break; case TK_SCROLL_PAGES: charsPerPage = (dInfoPtr->maxX - dInfoPtr->x) - 2; if (charsPerPage < 1) { charsPerPage = 1; } newOffset += charsPerPage*count; break; case TK_SCROLL_UNITS: newOffset += count; break; } dInfoPtr->newCharOffset = 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, charsToCount, lineNum; TkTextIndex new, index; TkTextLine *lastLinePtr; DInfo *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. */ charsToCount = textPtr->topIndex.charIndex + 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.charIndex = 0; lowestPtr = NULL; do { dlPtr = LayoutDLine(textPtr, &index); dlPtr->nextPtr = lowestPtr; lowestPtr = dlPtr; TkTextIndexForwChars(&index, dlPtr->count, &index); charsToCount -= dlPtr->count; } while ((charsToCount > 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; } charsToCount = INT_MAX; } /* * Ran off the beginning of the text. Return the first character * in the text. */ TkTextMakeIndex(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); dlPtr->nextPtr = NULL; TkTextIndexForwChars(&textPtr->topIndex, dlPtr->count, &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; } /* *-------------------------------------------------------------- * * 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". */ { DInfo *dInfoPtr = textPtr->dInfoPtr; int pickPlace, lineNum, type, lineHeight; 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) { TkTextMakeIndex(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 = fraction; TkTextMakeIndex(textPtr->tree, lineNum, 0, &index); index.charIndex = TkBTreeCharsInLine(index.linePtr) * (fraction-lineNum) + 0.5; 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. */ lineHeight = 1; if (count < 0) { pixels = (dInfoPtr->maxY - 2*lineHeight - dInfoPtr->y)*(-count) + lineHeight; 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*lineHeight - dInfoPtr->y)*count; lastLinePtr = TkBTreeFindLine(textPtr->tree, TkBTreeNumLines(textPtr->tree)); do { dlPtr = LayoutDLine(textPtr, &textPtr->topIndex); dlPtr->nextPtr = NULL; TkTextIndexForwChars(&textPtr->topIndex, dlPtr->count, &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; break; case TK_SCROLL_UNITS: scrollByLines: ScrollByLines(textPtr, count); break; } 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 interp->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 interp->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 interp->result. */ TkText *textPtr; /* Information about text widget. */ int report; /* Non-zero means report info to * scrollbar if it has changed. */ { DInfo *dInfoPtr = textPtr->dInfoPtr; char buffer[200]; 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) { char buffer[60]; 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 interp->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 interp->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 interp->result. */ TkText *textPtr; /* Information about text widget. */ int report; /* Non-zero means report info to * scrollbar if it has changed. */ { DInfo *dInfoPtr = textPtr->dInfoPtr; char buffer[200]; 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.charIndex) / (TkBTreeCharsInLine(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->count; break; } dlPtr = dlPtr->nextPtr; } last = ((double) TkBTreeLineIndex(dlPtr->index.linePtr)) + ((double) (dlPtr->index.charIndex + count)) / (TkBTreeCharsInLine(dlPtr->index.linePtr)); last /= totalLines; if (!report) { char buffer[60]; 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->charIndex >= (dlPtr->index.charIndex + dlPtr->count)) { 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). */ { DInfo *dInfoPtr = textPtr->dInfoPtr; register DLine *dlPtr; 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 = dInfoPtr->dLinePtr; y >= (dlPtr->y + dlPtr->height); dlPtr = dlPtr->nextPtr) { 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; } } /* * 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->charIndex += chunkPtr->numChars, chunkPtr = chunkPtr->nextPtr) { if (chunkPtr->nextPtr == NULL) { indexPtr->charIndex += chunkPtr->numChars - 1; return; } } /* * If the chunk has more than one character in it, ask it which * character is at the desired location. */ if (chunkPtr->numChars > 1) { indexPtr->charIndex += (*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. */ { DInfo *dInfoPtr = textPtr->dInfoPtr; DLine *dlPtr; register TkTextDispChunk *chunkPtr; int index; /* * 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. */ index = indexPtr->charIndex - dlPtr->index.charIndex; for (chunkPtr = dlPtr->chunkPtr; ; chunkPtr = chunkPtr->nextPtr) { if (chunkPtr == NULL) { return -1; } if (index < chunkPtr->numChars) { break; } index -= chunkPtr->numChars; } /* * 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, index, dlPtr->y + dlPtr->spaceAbove, xPtr, yPtr, widthPtr, heightPtr); *xPtr = *xPtr + dInfoPtr->x - dInfoPtr->curPixelOffset; if ((index == (chunkPtr->numChars-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. */ { DInfo *dInfoPtr = textPtr->dInfoPtr; DLine *dlPtr; /* * 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; } *xPtr = dInfoPtr->x - dInfoPtr->curPixelOffset + dlPtr->chunkPtr->x; *widthPtr = dlPtr->length - dlPtr->chunkPtr->x; *yPtr = dlPtr->y; if ((dlPtr->y + dlPtr->height) > dInfoPtr->maxY) { *heightPtr = dInfoPtr->maxY - dlPtr->y; } else { *heightPtr = dlPtr->height; } *basePtr = dlPtr->spaceAbove; return 0; } /* *-------------------------------------------------------------- * * TkTextCharLayoutProc -- * * This procedure is the "layoutProc" for character segments. * * Results: * If there is something to display for the chunk then a * non-zero value is returned and the fields of chunkPtr * will be filled in (see the declaration of TkTextDispChunk * in tkText.h for details). If zero is returned it means * that no characters from this chunk fit in the window. * If -1 is returned it means that this segment just doesn't * need to be displayed (never happens for text). * * Side effects: * Memory is allocated to hold additional information about * the chunk. * *-------------------------------------------------------------- */ int TkTextCharLayoutProc(textPtr, indexPtr, segPtr, offset, maxX, maxChars, 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 offset; /* Offset within segment of first character * to consider. */ int maxX; /* Chunk must not occupy pixels at this * position or higher. */ int maxChars; /* Chunk must not include more than this * many characters. */ int noCharsYet; /* Non-zero means no characters have been * assigned to this display line yet. */ Tk_Uid wrapMode; /* How to handle line wrapping: tkTextCharUid, * tkTextNoneUid, or tkTextWordUid. */ register TkTextDispChunk *chunkPtr; /* Structure to fill in with information * about this chunk. The x field has already * been set by the caller. */ { int nextX, charsThatFit, count; CharInfo *ciPtr; char *p; TkTextSegment *nextPtr; /* * 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 + offset; charsThatFit = TkMeasureChars(p, maxChars, chunkPtr->x, maxX, 0, TK_IGNORE_TABS, &nextX); if (charsThatFit < maxChars) { if ((charsThatFit == 0) && noCharsYet) { charsThatFit = 1; TkMeasureChars(p, 1, chunkPtr->x, INT_MAX, 0, TK_IGNORE_TABS, &nextX); } if (p[charsThatFit] == '\n') { /* * A newline character takes up no space, so if the previous * character fits then so does the newline. */ charsThatFit++; } else if ((nextX < maxX) && (isspace(UCHAR(p[charsThatFit])))) { /* * 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; charsThatFit++; } if (charsThatFit == 0) { return 0; } } /* * 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->numChars = charsThatFit; chunkPtr->minHeight = 0; chunkPtr->width = nextX - chunkPtr->x; chunkPtr->breakIndex = -1; ciPtr = (CharInfo *) ckalloc((unsigned) (sizeof(CharInfo) - 3 + charsThatFit)); chunkPtr->clientData = (ClientData) ciPtr; ciPtr->numChars = charsThatFit; strncpy(ciPtr->chars, p, (size_t) charsThatFit); if (p[charsThatFit-1] == '\n') { ciPtr->numChars--; } /* * 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 != tkTextWordUid) { chunkPtr->breakIndex = chunkPtr->numChars; } else { for (count = charsThatFit, p += charsThatFit-1; count > 0; count--, p--) { if (isspace(UCHAR(*p))) { chunkPtr->breakIndex = count; break; } } if ((charsThatFit+offset) == segPtr->size) { for (nextPtr = segPtr->nextPtr; nextPtr != NULL; nextPtr = nextPtr->nextPtr) { if (nextPtr->size != 0) { if (nextPtr->typePtr != &tkTextCharType) { chunkPtr->breakIndex = chunkPtr->numChars; } 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, win) TkTextDispChunk *chunkPtr; /* Chunk that is to be drawn. */ int x; /* X-position in win 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 win. */ Tk_Window win; /* Window in which to draw * chunk. */ { CharInfo *ciPtr = (CharInfo *) chunkPtr->clientData; Style *stylePtr; StyleValues *sValuePtr; if ((x + chunkPtr->width) <= 0) { /* * The chunk is off-screen. */ return; } stylePtr = chunkPtr->stylePtr; sValuePtr = stylePtr->sValuePtr; /* * Draw the text and underline for this chunk. */ if (ciPtr->numChars > 0) { TkDisplayChars(win, stylePtr->ctkStyle, ciPtr->chars, ciPtr->numChars, x, y + - sValuePtr->offset, x - chunkPtr->x, TK_IGNORE_TABS); } } /* *-------------------------------------------------------------- * * 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 TkMeasureChars(ciPtr->chars, chunkPtr->numChars-1, chunkPtr->x, x, 0, TK_IGNORE_TABS, &endX); } /* *-------------------------------------------------------------- * * 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, index, y, 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 *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; TkMeasureChars(ciPtr->chars, index, chunkPtr->x, 1000000, 0, TK_IGNORE_TABS, xPtr); if (index == ciPtr->numChars) { /* * 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[index] == '\t') && (index == (ciPtr->numChars-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 { TkMeasureChars(ciPtr->chars + index, 1, *xPtr, 1000000, 0, TK_IGNORE_TABS, widthPtr); if (*widthPtr > maxX) { *widthPtr = maxX - *xPtr; } else { *widthPtr -= *xPtr; } } *yPtr = y; *heightPtr = 1; } /* *---------------------------------------------------------------------- * * 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; TkTextTab *tabPtr; CharInfo *ciPtr = NULL; /* Initialization needed only to * prevent compiler warnings. */ int tabX, prev; 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. */ TkMeasureChars("\t", 1, x, INT_MAX, 0, 0, &desired); 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); } tabPtr = &tabArrayPtr->tabs[index]; 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->numChars; 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; TkMeasureChars(ciPtr->chars, decimal, decimalChunkPtr->x, 1000000, 0, TK_IGNORE_TABS, &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 containin the * tab. Be sure that the tab occupies at least the width of a * space character. */ update: delta = desired - x; if (delta < 1) { delta = 1; } 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; TkTextTabAlign alignment; if ((tabArrayPtr == NULL) || (tabArrayPtr->numTabs == 0)) { TkMeasureChars("\t", 1, x, INT_MAX, 0, 0, &tabX); 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: if (result < 1) { result = 1; } return result; }