/* * tkTextDisp.c -- * * This module provides facilities to display text widgets. It is * the only place where information is kept about the screen layout * of text widgets. * * Copyright (c) 1992-1993 The Regents of the University of California. * All rights reserved. * * Permission is hereby granted, without written agreement and without * license or royalty fees, to use, copy, modify, and distribute this * software and its documentation for any purpose, provided that the * above copyright notice and the following two paragraphs appear in * all copies of this software. * * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. */ #ifndef lint static char rcsid[] = "$Header: /user6/ouster/wish/RCS/tkTextDisp.c,v 1.38 93/11/01 15:05:54 ouster Exp $ SPRITE (Berkeley)"; #endif #include "tkConfig.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 { Tk_3DBorder border; /* Used for drawing background under text. * NULL means use widget background. */ int borderWidth; /* Width of 3-D border for background. */ int relief; /* 3-D relief for background. */ Pixmap bgStipple; /* Stipple bitmap for background. None * means draw solid. */ XColor *fgColor; /* Foreground color for text. */ XFontStruct *fontPtr; /* Font for displaying text. */ Pixmap fgStipple; /* Stipple bitmap for text and other * foreground stuff. None means draw * solid.*/ int underline; /* Non-zero means draw underline underneath * text. */ } StyleValues; /* * The following structure extends the StyleValues structure above with * graphics contexts used to actually draw the characters. The entries * in dInfoPtr->styleTable point to structures of this type. */ typedef struct Style { int refCount; /* Number of times this structure is * referenced in Chunks. */ GC bgGC; /* Graphics context for background. None * unless background is stippled. */ GC fgGC; /* Graphics context for foreground. */ StyleValues *sValuePtr; /* Raw information from which GCs were * derived. */ Tcl_HashEntry *hPtr; /* Pointer to entry in styleTable. Used * to delete entry. */ } Style; /* * The following structure describes a range of characters, all on the * same line of the display (which also means the same line of the text * widget) and all having the same display attributes. */ typedef struct Chunk { char *text; /* Characters to display. */ int numChars; /* Number of characters to display. */ Style *stylePtr; /* Style information used to display * characters. */ int x; /* X-coordinate of pixel at which to display * the characters. */ struct Chunk *nextPtr; /* Next in list of all chunks displayed on the * same display line. */ } Chunk; /* * 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 { TkTextLine *linePtr; /* Pointer to structure in B-tree that * contains characters displayed in this * line. */ 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. This is * used to move lines by scrolling rather * than re-drawing. */ int height; /* Height of line, in pixels. */ int baseline; /* Offset of text baseline from y. */ Chunk *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. */ } DLine; /* * 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. */ GC copyGC; /* Graphics context for copying from off- * screen pixmaps onto screen. */ GC scrollGC; /* Graphics context for copying from one place * in the window to another (scrolling): * differs from copyGC in that we need to get * GraphicsExpose events. */ int x; /* First x-coordinate that may be used for * actually displaying line information. * Leaves space for border, etc. */ int y; /* First y-coordinate that may be used for * actually displaying line information. * Leaves space for border, etc. */ int maxX; /* First x-coordinate to right of available * space for displaying lines. */ int maxY; /* First y-coordinate below available * space for displaying lines. */ int topOfEof; /* Top-most pixel (lowest y-value) that has * been drawn in the appropriate fashion for * the portion of the window after the last * line of the text. This field is used to * figure out when to redraw part or all of * the eof field. */ int flags; /* Various flag values: see below for * definitions. */ } DInfo; /* * 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. * REPICK_NEEDED: 1 means that the widget has been modified * in a way that could change the current * character (a different character might be * under the mouse cursor now). Need to * recompute the current character before * the next redisplay. */ #define DINFO_OUT_OF_DATE 1 #define REDRAW_PENDING 2 #define REDRAW_BORDERS 4 #define REPICK_NEEDED 8 /* * Structures of the type defined below are used to keep track of * tags while scanning through the text to create DLine structures. */ typedef struct TagInfo { int numTags; /* Number of tags currently active (the first * entries at *tagPtr). */ int arraySize; /* Total number of entries at *tagPtr. We * over-allocate the array to avoid continual * reallocations. */ TkTextTag **tagPtrs; /* Pointer to array of pointers to active tags. * Array has space for arraySize tags, and * the first numTags are slots identify the * active tags. Malloc'ed (but may be NULL). */ TkTextSearch search; /* Used to scan for tag transitions. Current * state identifies next tag transition. */ } TagInfo; /* * The following counters keep statistics about redisplay that can be * checked to see how clever this code is at reducing redisplays. */ static int numRedisplays; /* Number of calls to DisplayText. */ static int linesRedrawn; /* Number of calls to DisplayDLine. */ static int numCopies; /* Number of calls to XCopyArea to copy part * of the screen. */ static int damagedCopies; /* Number of times that XCopyAreas didn't * completely work because some of the source * information was damaged. */ /* * Forward declarations for procedures defined later in this file: */ static void ComputeStyleValues _ANSI_ARGS_((TkText *textPtr, int numTags, TkTextTag **tagPtr, StyleValues *sValuePtr)); static void DisplayDLine _ANSI_ARGS_((TkText *textPtr, DLine *dlPtr, Pixmap pixmap)); static void DisplayText _ANSI_ARGS_((ClientData clientData)); static DLine * FindDLine _ANSI_ARGS_((DLine *dlPtr, int line)); 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, StyleValues *sValuePtr)); static DLine * LayoutLine _ANSI_ARGS_((TkText *textPtr, int line, TkTextLine *linePtr, TagInfo *tInfoPtr)); static void ToggleTag _ANSI_ARGS_((TagInfo *tInfoPtr, TkTextTag *tagPtr)); static void UpdateDisplayInfo _ANSI_ARGS_((TkText *textPtr)); /* *---------------------------------------------------------------------- * * 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; XGCValues gcValues; dInfoPtr = (DInfo *) ckalloc(sizeof(DInfo)); Tcl_InitHashTable(&dInfoPtr->styleTable, sizeof(StyleValues)/sizeof(int)); dInfoPtr->dLinePtr = NULL; gcValues.graphics_exposures = False; dInfoPtr->copyGC = Tk_GetGC(textPtr->tkwin, GCGraphicsExposures, &gcValues); gcValues.graphics_exposures = True; dInfoPtr->scrollGC = Tk_GetGC(textPtr->tkwin, GCGraphicsExposures, &gcValues); dInfoPtr->topOfEof = 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); Tk_FreeGC(textPtr->display, dInfoPtr->copyGC); Tk_FreeGC(textPtr->display, dInfoPtr->scrollGC); if (dInfoPtr->flags & REDRAW_PENDING) { Tk_CancelIdleCall(DisplayText, (ClientData) textPtr); } ckfree((char *) dInfoPtr); } /* *---------------------------------------------------------------------- * * GetStyle -- * * This procedure creates graphics contexts needed to display * text in a particular style, determined by "sValuePtr". It * attempts to share style information as much as possible. * * 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, sValuePtr) TkText *textPtr; /* Overall information about text widget. */ StyleValues *sValuePtr; /* Information about desired style. */ { Style *stylePtr; Tcl_HashEntry *hPtr; int new; XGCValues gcValues; unsigned long mask; /* * Use an existing style if there's one around that matches. */ hPtr = Tcl_CreateHashEntry(&textPtr->dInfoPtr->styleTable, (char *) sValuePtr, &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; if ((sValuePtr->border != NULL) && (sValuePtr->bgStipple != None)) { gcValues.foreground = Tk_3DBorderColor(sValuePtr->border)->pixel; gcValues.stipple = sValuePtr->bgStipple; gcValues.fill_style = FillStippled; stylePtr->bgGC = Tk_GetGC(textPtr->tkwin, GCForeground|GCStipple|GCFillStyle, &gcValues); } else { stylePtr->bgGC = None; } mask = GCForeground|GCFont; gcValues.foreground = sValuePtr->fgColor->pixel; gcValues.font = sValuePtr->fontPtr->fid; if (sValuePtr->fgStipple != None) { gcValues.stipple = sValuePtr->fgStipple; gcValues.fill_style = FillStippled; mask |= GCStipple|GCFillStyle; } stylePtr->fgGC = Tk_GetGC(textPtr->tkwin, mask, &gcValues); stylePtr->sValuePtr = (StyleValues *) Tcl_GetHashKey(&textPtr->dInfoPtr->styleTable, hPtr); stylePtr->hPtr = hPtr; Tcl_SetHashValue(hPtr, stylePtr); return stylePtr; } /* *---------------------------------------------------------------------- * * FreeStyle -- * * This procedure is called when a 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) { if (stylePtr->bgGC != None) { Tk_FreeGC(textPtr->display, stylePtr->bgGC); } Tk_FreeGC(textPtr->display, stylePtr->fgGC); Tcl_DeleteHashEntry(stylePtr->hPtr); ckfree((char *) stylePtr); } } /* *---------------------------------------------------------------------- * * ComputeStyleValues -- * * Given a list of tags that apply at a particular point, compute * the StyleValues that correspond to that set of tags. * * Results: * All of the fields of *sValuePtr get filled in to hold the * appropriate display information for the given set of tags * in the given widget. * * Side effects: * None. * *---------------------------------------------------------------------- */ static void ComputeStyleValues(textPtr, numTags, tagPtrPtr, sValuePtr) TkText *textPtr; /* Overall information for widget. */ int numTags; /* Number of tags at *tagPtr. */ register TkTextTag **tagPtrPtr; /* Pointer to array of tag pointers. */ register StyleValues *sValuePtr; /* Pointer to structure to fill in. */ { register TkTextTag *tagPtr; /* * The variables below keep track of the highest-priority specification * that has occurred for each of the various fields of the StyleValues. */ int borderPrio, bgStipplePrio; int fgPrio, fontPrio, fgStipplePrio; borderPrio = bgStipplePrio = -1; fgPrio = fontPrio = fgStipplePrio = -1; memset((VOID *) sValuePtr, 0, sizeof(StyleValues)); sValuePtr->fgColor = textPtr->fgColor; sValuePtr->fontPtr = textPtr->fontPtr; /* * Scan through all of the tags, updating the StyleValues to hold * the highest-priority information. */ for ( ; numTags > 0; tagPtrPtr++, numTags--) { tagPtr = *tagPtrPtr; if ((tagPtr->border != NULL) && (tagPtr->priority > borderPrio)) { sValuePtr->border = tagPtr->border; sValuePtr->borderWidth = tagPtr->borderWidth; sValuePtr->relief = tagPtr->relief; borderPrio = tagPtr->priority; } if ((tagPtr->bgStipple != None) && (tagPtr->priority > bgStipplePrio)) { sValuePtr->bgStipple = tagPtr->bgStipple; bgStipplePrio = tagPtr->priority; } if ((tagPtr->fgColor != None) && (tagPtr->priority > fgPrio)) { sValuePtr->fgColor = tagPtr->fgColor; fgPrio = tagPtr->priority; } if ((tagPtr->fontPtr != None) && (tagPtr->priority > fontPrio)) { sValuePtr->fontPtr = tagPtr->fontPtr; fontPrio = tagPtr->priority; } if ((tagPtr->fgStipple != None) && (tagPtr->priority > fgStipplePrio)) { sValuePtr->fgStipple = tagPtr->fgStipple; fgStipplePrio = tagPtr->priority; } if (tagPtr->underline) { sValuePtr->underline = 1; } } } /* *---------------------------------------------------------------------- * * LayoutLine -- * * This procedure generates a linked list of one or more DLine * structures, which describe how to display everything in one * line of the text. * * Results: * The return value is a pointer to one or more DLine structures * linked into a linked list. The structures are completely filled * in except for the y field, which the caller must supply. Also, * the information at *tInfoPtr gets updated to refer to the state * just after the last character of the line. * * Side effects: * None. * *---------------------------------------------------------------------- */ static DLine * LayoutLine(textPtr, line, linePtr, tInfoPtr) TkText *textPtr; /* Overall information about text widget. */ int line; /* Index of line to layout. */ TkTextLine *linePtr; /* Line to layout (corresponds to line). */ TagInfo *tInfoPtr; /* Information to help keep track of tags. * Caller must have initialized to correspond * to state just before start of line. */ { DLine *firstLinePtr; DLine *lastLinePtr = NULL; /* Initializations needed only to stop */ Chunk *lastChunkPtr = NULL; /* compiler warnings. */ register DLine *dlPtr; register Chunk *chunkPtr; StyleValues styleValues; int ch, charsThatFit, ascent, descent, x, maxX; firstLinePtr = NULL; /* * Each iteration of the loop below creates one DLine structure. */ ch = 0; while (1) { /* * Create and initialize a new DLine structure. */ dlPtr = (DLine *) ckalloc(sizeof(DLine)); dlPtr->linePtr = linePtr; dlPtr->y = 0; dlPtr->oldY = -1; dlPtr->chunkPtr = NULL; dlPtr->nextPtr = NULL; if (firstLinePtr == NULL) { firstLinePtr = dlPtr; } else { lastLinePtr->nextPtr = dlPtr; } lastLinePtr = dlPtr; /* * Each iteration of the loop below creates one Chunk for the * new display line. Be sure always to create at least one chunk. */ x = textPtr->dInfoPtr->x; maxX = textPtr->dInfoPtr->maxX; ascent = descent = 0; do { chunkPtr = (Chunk *) ckalloc(sizeof(Chunk)); chunkPtr->numChars = linePtr->numBytes - ch; chunkPtr->text = linePtr->bytes + ch; chunkPtr->x = x; chunkPtr->nextPtr = NULL; if (dlPtr->chunkPtr == NULL) { dlPtr->chunkPtr = chunkPtr; } else { lastChunkPtr->nextPtr = chunkPtr; } lastChunkPtr = chunkPtr; /* * Update the tag array to include any tag transitions up * through the current position, then find the next position * with a transition on a tag that impacts the way things are * displayed. */ while (1) { int affectsDisplay; TkTextTag *tagPtr; if ((tInfoPtr->search.linePtr == NULL) || (tInfoPtr->search.line1 > line)) { break; } tagPtr = tInfoPtr->search.tagPtr; affectsDisplay = TK_TAG_AFFECTS_DISPLAY(tagPtr); if ((tInfoPtr->search.line1 < line) || (tInfoPtr->search.ch1 <= ch)) { if (affectsDisplay) { ToggleTag(tInfoPtr, tagPtr); } } else { if (affectsDisplay) { chunkPtr->numChars = tInfoPtr->search.ch1 - ch; break; } } (void) TkBTreeNextTag(&tInfoPtr->search); } /* * Create style information for this chunk. */ ComputeStyleValues(textPtr, tInfoPtr->numTags, tInfoPtr->tagPtrs, &styleValues); chunkPtr->stylePtr = GetStyle(textPtr, &styleValues); /* * See how many characters will fit on the line. If they don't * all fit, then a number of compensations may have to be made. * * 1. Make sure that at least one character is displayed on * each line. * 2. In wrap mode "none", allow a partial character to be * displayed at the end of an incomplete line. * 3. In wrap mode "word", search back to find the last space * character, and terminate the line just after that space * character. This involves a couple of extra complexities: * - the last space may be several chunks back; in this * case, delete all the chunks that are after the * space. * - if no words fit at all, then use character-wrap for * this DLine. * - have to reinitialize the tag search information, since * we may back up over tag toggles (they'll need to be * reconsidered on the next DLine). */ charsThatFit = TkMeasureChars(styleValues.fontPtr, chunkPtr->text, chunkPtr->numChars, chunkPtr->x, maxX, 0, &x); if ((charsThatFit < chunkPtr->numChars) || (x >= maxX)) { x = maxX; chunkPtr->numChars = charsThatFit; ch += charsThatFit; if (ch < (linePtr->numBytes - 1)) { if ((charsThatFit == 0) && (chunkPtr == dlPtr->chunkPtr)) { chunkPtr->numChars = 1; ch++; } else if (textPtr->wrapMode == tkTextWordUid) { if (isspace(UCHAR(chunkPtr->text[charsThatFit]))) { ch += 1; /* Include space on this line. */ } else { register Chunk *chunkPtr2; register char *p; Chunk *spaceChunkPtr; int count, space; spaceChunkPtr = NULL; space = 0; for (chunkPtr2 = dlPtr->chunkPtr; chunkPtr2 != NULL; chunkPtr2 = chunkPtr2->nextPtr) { for (count = chunkPtr2->numChars - 1, p = chunkPtr2->text + count; count >= 0; count--, p--) { if (isspace(UCHAR(*p))) { spaceChunkPtr = chunkPtr2; space = count; break; } } } if (spaceChunkPtr != NULL) { spaceChunkPtr->numChars = space; ch = (spaceChunkPtr->text + space + 1) - linePtr->bytes; if (chunkPtr != spaceChunkPtr) { chunkPtr = spaceChunkPtr; if (tInfoPtr->tagPtrs != NULL) { ckfree((char *) tInfoPtr->tagPtrs); } tInfoPtr->tagPtrs = TkBTreeGetTags( textPtr->tree, dlPtr->linePtr, ch, &tInfoPtr->numTags); TkBTreeStartSearch(textPtr->tree, line, ch+1, TkBTreeNumLines(textPtr->tree), 0, (TkTextTag *) NULL, &tInfoPtr->search); (void) TkBTreeNextTag(&tInfoPtr->search); tInfoPtr->arraySize = tInfoPtr->numTags; while (chunkPtr->nextPtr != NULL) { chunkPtr2 = chunkPtr->nextPtr; chunkPtr->nextPtr = chunkPtr2->nextPtr; FreeStyle(textPtr, chunkPtr2->stylePtr); ckfree((char *) chunkPtr2); } } } } } else if (textPtr->wrapMode == tkTextNoneUid) { chunkPtr->numChars++; ch++; } } } else { ch += chunkPtr->numChars; } /* * Update height information for use later in computing * line's overall height and baseline. */ if (styleValues.fontPtr->ascent > ascent) { ascent = styleValues.fontPtr->ascent; } if (styleValues.fontPtr->descent > descent) { descent = styleValues.fontPtr->descent; } } while (x < maxX); dlPtr->height = ascent + descent; dlPtr->baseline = ascent; /* * Quit when every character but the last character (the newline) * has been accounted for. Also quit if the wrap mode is "none": * this ignores all the characters that don't fit on the first * line. */ if ((ch >= (linePtr->numBytes-1)) || (textPtr->wrapMode == tkTextNoneUid)) { break; } } return firstLinePtr; } /* *---------------------------------------------------------------------- * * ToggleTag -- * * Update information about tags to reflect a transition on a * particular tag. * * Results: * The array at *tInfoPtr is modified to include tagPtr if it * didn't already or to exclude it if it used to include it. * The array will be reallocated to a larger size if needed. * * Side effects: * None. * *---------------------------------------------------------------------- */ static void ToggleTag(tInfoPtr, tagPtr) register TagInfo *tInfoPtr; /* Tag information to be updated. */ TkTextTag *tagPtr; /* Tag to be toggled into or out of * *tInfoPtr. */ { register TkTextTag **tagPtrPtr; int i; for (i = tInfoPtr->numTags, tagPtrPtr = tInfoPtr->tagPtrs; i > 0; i--, tagPtrPtr++) { if (*tagPtrPtr == tagPtr) { tInfoPtr->numTags--; *tagPtrPtr = tInfoPtr->tagPtrs[tInfoPtr->numTags]; return; } } /* * Tag not currently in array. Grow the array if necessary, then * add the tag to it. */ if (tInfoPtr->numTags == tInfoPtr->arraySize) { TkTextTag **newPtrs; newPtrs = (TkTextTag **) ckalloc((unsigned) ((tInfoPtr->arraySize+10) * sizeof(TkTextTag *))); if (tInfoPtr->tagPtrs != NULL) { memcpy((VOID *) newPtrs, (VOID *) tInfoPtr->tagPtrs, tInfoPtr->arraySize * sizeof(TkTextTag *)); ckfree((char *) tInfoPtr->tagPtrs); } tInfoPtr->tagPtrs = newPtrs; tInfoPtr->arraySize += 10; } tInfoPtr->tagPtrs[tInfoPtr->numTags] = tagPtr; tInfoPtr->numTags++; } /* *---------------------------------------------------------------------- * * 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 for their y-coordinates), but there may * be missing structures (the DLine structures get removed as * soon as they are potentially out-of-date). * * 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, *dlPtr2; TkTextLine *linePtr; TagInfo tagInfo; int line, y, maxY; if (!(dInfoPtr->flags & DINFO_OUT_OF_DATE)) { return; } dInfoPtr->flags &= ~DINFO_OUT_OF_DATE; linePtr = textPtr->topLinePtr; dlPtr = dInfoPtr->dLinePtr; tagInfo.tagPtrs = TkBTreeGetTags(textPtr->tree, linePtr, 0, &tagInfo.numTags); tagInfo.arraySize = tagInfo.numTags; /* * Tricky point: initialize the tag search just *after* the first * character in the line, since the tagInfo structure already has all * the tags for the first character. */ line = TkBTreeLineIndex(linePtr); TkBTreeStartSearch(textPtr->tree, line, 1, TkBTreeNumLines(textPtr->tree), 0, (TkTextTag *) NULL, &tagInfo.search); TkBTreeNextTag(&tagInfo.search); prevPtr = NULL; y = dInfoPtr->y; maxY = dInfoPtr->maxY; while (linePtr != NULL) { register DLine *newPtr; /* * See if the next DLine matches the next line we want to * appear on the screen. If so then we can just use its * information. If not then create new DLine structures * for the desired line and insert them into the list. */ if ((dlPtr == NULL) || (dlPtr->linePtr != linePtr)) { newPtr = LayoutLine(textPtr, line, linePtr, &tagInfo); if (prevPtr == NULL) { dInfoPtr->dLinePtr = newPtr; } else { prevPtr->nextPtr = newPtr; } for (dlPtr2 = newPtr; dlPtr2->nextPtr != NULL; dlPtr2 = dlPtr2->nextPtr) { /* Empty loop body. */ } dlPtr2->nextPtr = dlPtr; dlPtr = newPtr; } /* * Skip to the next line, and update the y-position while * skipping. */ do { dlPtr->y = y; y += dlPtr->height; prevPtr = dlPtr; dlPtr = dlPtr->nextPtr; } while ((dlPtr != NULL) && (dlPtr->linePtr == linePtr)); linePtr = TkBTreeNextLine(linePtr); line++; /* * 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 and free * up the tag array. */ FreeDLines(textPtr, dlPtr, (DLine *) NULL, 1); if (tagInfo.tagPtrs != NULL) { ckfree((char *) tagInfo.tagPtrs); } /* * Arrange for scrollbars to be updated. */ textPtr->flags |= UPDATE_SCROLLBARS; } /* *---------------------------------------------------------------------- * * 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 Chunk *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) { FreeStyle(textPtr, chunkPtr->stylePtr); nextChunkPtr = chunkPtr->nextPtr; ckfree((char *) chunkPtr); } ckfree((char *) firstPtr); firstPtr = nextDLinePtr; } } /* *---------------------------------------------------------------------- * * 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, pixmap) TkText *textPtr; /* Text widget in which to draw line. */ register DLine *dlPtr; /* Information about line to draw. */ Pixmap pixmap; /* Pixmap to use for double-buffering. * Caller must make sure it's large enough * to hold line. */ { register Style *stylePtr; register StyleValues *sValuePtr; register Chunk *chunkPtr; DInfo *dInfoPtr = textPtr->dInfoPtr; Display *display; int width, height, count, x; XFontStruct *fontPtr; /* * First, clear the area of the line to the background color for the * text widget. */ display = Tk_Display(textPtr->tkwin); Tk_Fill3DRectangle(display, pixmap, textPtr->border, 0, 0, Tk_Width(textPtr->tkwin), dlPtr->height, 0, TK_RELIEF_FLAT); /* * Next, cycle through all of the chunks in the line displaying * backgrounds. We need to do two passes, one for the backgrounds * and one for the characters, because some characters (e.g. italics * with heavy slants) may cross background boundaries. If some * backgrounds are drawn after some text, the later backgrounds may * obliterate parts of earlier characters. */ for (chunkPtr = dlPtr->chunkPtr; chunkPtr != NULL; chunkPtr = chunkPtr->nextPtr) { /* * Draw a special background for this chunk if one is specified * in its style. Two tricks here: * 1. if this is the last chunk in the line then extend the * background across to the end of the line. * 2. if the background is stippled, then we have to draw the * stippled part specially, since Tk_Fill3DRectangle doesn't * do stipples. */ stylePtr = chunkPtr->stylePtr; sValuePtr = stylePtr->sValuePtr; if (sValuePtr->border != NULL) { if (chunkPtr->nextPtr != NULL) { width = chunkPtr->nextPtr->x - chunkPtr->x; } else { width = Tk_Width(textPtr->tkwin) - chunkPtr->x; } if (stylePtr->bgGC != None) { XFillRectangle(display, pixmap, stylePtr->bgGC, chunkPtr->x, 0, (unsigned int) width, (unsigned int) dlPtr->height); Tk_Draw3DRectangle(display, pixmap, sValuePtr->border, chunkPtr->x, 0, width, dlPtr->height, sValuePtr->borderWidth, sValuePtr->relief); } else { Tk_Fill3DRectangle(display, pixmap, sValuePtr->border, chunkPtr->x, 0, width, dlPtr->height, sValuePtr->borderWidth, sValuePtr->relief); } } } /* * If the insertion cursor is displayed on this line, then draw it * now, on top of the background but before the text. As a special * hack to keep the cursor visible on mono displays, write the default * background in the cursor area (instead of nothing) when the cursor * isn't on. Otherwise the selection would hide the cursor. */ if ((textPtr->insertAnnotPtr->linePtr == dlPtr->linePtr) && (textPtr->state == tkTextNormalUid) && (textPtr->flags & GOT_FOCUS)) { for (chunkPtr = dlPtr->chunkPtr; chunkPtr != NULL; chunkPtr = chunkPtr->nextPtr) { count = textPtr->insertAnnotPtr->ch - (chunkPtr->text - dlPtr->linePtr->bytes); if (count < 0) { break; } if (count > chunkPtr->numChars) { continue; } /* * Deciding whether to display the cursor just after the last * character in a line is tricky because of various wrap * modes. Do it unless we're in character wrap mode and * this line wraps, in which case it's better to display the * cursor on the next line. For word wrap, there's an * undisplayed space character that the user must be able to * position the cursor in front of. For no wrap, there's no * next line on which to display the cursor. */ if ((count == chunkPtr->numChars) && (textPtr->wrapMode == tkTextCharUid) && (chunkPtr->text[count] != '\n')) { continue; } fontPtr = chunkPtr->stylePtr->sValuePtr->fontPtr; TkMeasureChars(fontPtr, chunkPtr->text, count, chunkPtr->x, (int) 1000000, 0, &x); if (textPtr->flags & INSERT_ON) { Tk_Fill3DRectangle(display, pixmap, textPtr->insertBorder, x - textPtr->insertWidth/2, dlPtr->baseline - fontPtr->ascent, textPtr->insertWidth, fontPtr->ascent + fontPtr->descent, textPtr->insertBorderWidth, TK_RELIEF_RAISED); } else if (Tk_GetColorModel(textPtr->tkwin) != TK_COLOR) { Tk_Fill3DRectangle(display, pixmap, textPtr->border, x - textPtr->insertWidth/2, dlPtr->baseline - fontPtr->ascent, textPtr->insertWidth, fontPtr->ascent + fontPtr->descent, 0, TK_RELIEF_FLAT); } } } /* * Make another pass through all of the chunks to redraw all of * the text (and underlines, etc., if they're wanted). */ for (chunkPtr = dlPtr->chunkPtr; chunkPtr != NULL; chunkPtr = chunkPtr->nextPtr) { stylePtr = chunkPtr->stylePtr; sValuePtr = stylePtr->sValuePtr; if (chunkPtr->numChars > 0) { TkDisplayChars(display, pixmap, stylePtr->fgGC, sValuePtr->fontPtr, chunkPtr->text, chunkPtr->numChars, chunkPtr->x, dlPtr->baseline, 0); if (sValuePtr->underline) { TkUnderlineChars(display, pixmap, stylePtr->fgGC, sValuePtr->fontPtr, chunkPtr->text, chunkPtr->x, dlPtr->baseline, 0, 0, chunkPtr->numChars-1); } } } /* * Copy the pixmap onto the screen. If this is the last line on * the screen, only copy a piece of the line, so that it doesn't * overflow into the border area. Another special trick: copy the * padding area to the left of the line; this is because the * insertion cursor sometimes overflows onto that area and we want * to get as much of the cursor as possible. */ height = dlPtr->height; if ((height + dlPtr->y) > dInfoPtr->maxY) { height = dInfoPtr->maxY - dlPtr->y; } XCopyArea(display, pixmap, Tk_WindowId(textPtr->tkwin), dInfoPtr->copyGC, dInfoPtr->x - textPtr->padX, 0, dInfoPtr->maxX - (dInfoPtr->x - textPtr->padX), height, dInfoPtr->x - textPtr->padX, dlPtr->y); 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; Tk_Window tkwin; register DLine *dlPtr; Pixmap pixmap; int maxHeight; int bottomY = 0; /* Initialization needed only to stop * compiler warnings. */ if ((textPtr->tkwin == NULL) || !Tk_IsMapped(textPtr->tkwin) || (dInfoPtr->maxX <= dInfoPtr->x) || (dInfoPtr->maxY <= dInfoPtr->y)) { UpdateDisplayInfo(textPtr); goto doScrollbars; } numRedisplays++; /* * Choose a new current item if that is needed (this could cause * event handlers to be invoked, hence the preserve/release calls * and the loop, since the handlers could conceivably necessitate * yet another current item calculation). The tkwin check is because * the whole window could go away in the Tk_Release call. */ while (dInfoPtr->flags & REPICK_NEEDED) { Tk_Preserve((ClientData) textPtr); dInfoPtr->flags &= ~REPICK_NEEDED; TkTextPickCurrent(textPtr, &textPtr->pickEvent); tkwin = textPtr->tkwin; Tk_Release((ClientData) textPtr); if (tkwin == NULL) { return; } } /* * First recompute what's supposed to be displayed. */ UpdateDisplayInfo(textPtr); /* * Redraw the borders if that's needed. */ if (dInfoPtr->flags & REDRAW_BORDERS) { Tk_Draw3DRectangle(Tk_Display(textPtr->tkwin), Tk_WindowId(textPtr->tkwin), textPtr->border, 0, 0, Tk_Width(textPtr->tkwin), Tk_Height(textPtr->tkwin), textPtr->borderWidth, textPtr->relief); } /* * See if it's possible to bring some parts of the screen up-to-date * by scrolling (copying from other parts of the screen). */ for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) { register DLine *dlPtr2; int offset, height, y; if ((dlPtr->oldY == -1) || (dlPtr->y == dlPtr->oldY) || ((dlPtr->oldY + dlPtr->height) > dInfoPtr->maxY)) { continue; } /* * This line is already drawn somewhere in the window so it only * needs to be copied to its new location. See if there's a group * of lines that can all be copied together. */ offset = dlPtr->y - dlPtr->oldY; height = dlPtr->height; y = dlPtr->y; for (dlPtr2 = dlPtr->nextPtr; dlPtr2 != NULL; dlPtr2 = dlPtr2->nextPtr) { if ((dlPtr2->oldY == -1) || ((dlPtr2->oldY + offset) != dlPtr2->y) || ((dlPtr2->oldY + dlPtr2->height) > dInfoPtr->maxY)) { break; } height += dlPtr2->height; } /* * Copy the information and update the lines to show that they've * been copied. Reduce the height of the area being copied if * necessary to avoid overwriting the border area. */ if ((y + height) > dInfoPtr->maxY) { height = dInfoPtr->maxY -y; } XCopyArea(Tk_Display(textPtr->tkwin), Tk_WindowId(textPtr->tkwin), Tk_WindowId(textPtr->tkwin), dInfoPtr->scrollGC, dInfoPtr->x - textPtr->padX, dlPtr->oldY, dInfoPtr->maxX - (dInfoPtr->x - textPtr->padX), height, dInfoPtr->x - textPtr->padX, y); numCopies++; while (1) { dlPtr->oldY = dlPtr->y; if (dlPtr->nextPtr == dlPtr2) { break; } dlPtr = dlPtr->nextPtr; } /* * Scan through the lines following the copied ones to see if * we just overwrote them with the copy operation. If so, mark * them for redisplay. */ for ( ; dlPtr2 != NULL; dlPtr2 = dlPtr2->nextPtr) { if ((dlPtr2->oldY != -1) && ((dlPtr2->oldY + dlPtr2->height) > y) && (dlPtr2->oldY < (y + height))) { dlPtr2->oldY = -1; } } /* * It's possible that part of the area copied above was obscured. * To handle this situation, read expose-related events generated * during the XCopyArea operation. */ while (1) { XEvent event; XWindowEvent(Tk_Display(textPtr->tkwin), Tk_WindowId(textPtr->tkwin), ExposureMask, &event); if (event.type == NoExpose) { break; } else if (event.type == GraphicsExpose) { TkTextRedrawRegion(textPtr, event.xgraphicsexpose.x, event.xgraphicsexpose.y, event.xgraphicsexpose.width, event.xgraphicsexpose.height); if (event.xgraphicsexpose.count == 0) { damagedCopies++; break; } } else if (event.type == Expose) { /* * A tricky situation. This event must already have been * queued up before the XCopyArea was issued. If the area * in this event overlaps the area copied, then some of the * bits that were copied were bogus. The easiest way to * handle this is to issue two redisplays: one for the * original area and one for the area shifted as if it was * in the copied area. */ TkTextRedrawRegion(textPtr, event.xexpose.x, event.xexpose.y, event.xexpose.width, event.xexpose.height); TkTextRedrawRegion(textPtr, event.xexpose.x, event.xexpose.y + offset, event.xexpose.width, event.xexpose.height); } else { panic("DisplayText received unknown exposure event"); } } } /* * Now we have to redraw the lines that couldn't be updated by * scrolling. First, compute the height of the largest line and * allocate an off-screen pixmap to use for double-buffered * displays. */ maxHeight = -1; for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) { if ((dlPtr->height > maxHeight) && (dlPtr->oldY != dlPtr->y)) { maxHeight = dlPtr->height; } bottomY = dlPtr->y + dlPtr->height; } if (maxHeight > dInfoPtr->maxY) { maxHeight = dInfoPtr->maxY; } if (maxHeight >= 0) { pixmap = XCreatePixmap(Tk_Display(textPtr->tkwin), Tk_WindowId(textPtr->tkwin), Tk_Width(textPtr->tkwin), maxHeight, Tk_Depth(textPtr->tkwin)); for (dlPtr = textPtr->dInfoPtr->dLinePtr; (dlPtr != NULL) && (dlPtr->y < dInfoPtr->maxY); dlPtr = dlPtr->nextPtr) { if (dlPtr->oldY != dlPtr->y) { DisplayDLine(textPtr, dlPtr, pixmap); dlPtr->oldY = dlPtr->y; } } XFreePixmap(Tk_Display(textPtr->tkwin), pixmap); } /* * Lastly, see if we need to refresh the part of the window below * the last line of text (if there is any such area). Refresh the * padding area on the left too, since the insertion cursor might * have been displayed there previously). */ if (dInfoPtr->topOfEof > dInfoPtr->maxY) { dInfoPtr->topOfEof = dInfoPtr->maxY; } if (bottomY < dInfoPtr->topOfEof) { Tk_Fill3DRectangle(Tk_Display(textPtr->tkwin), Tk_WindowId(textPtr->tkwin), textPtr->border, dInfoPtr->x - textPtr->padX, bottomY, dInfoPtr->maxX - (dInfoPtr->x - textPtr->padX), dInfoPtr->topOfEof-bottomY, 0, TK_RELIEF_FLAT); } dInfoPtr->topOfEof = bottomY; if (dInfoPtr->topOfEof > dInfoPtr->maxY) { dInfoPtr->topOfEof = dInfoPtr->maxY; } doScrollbars: /* * Update the vertical scrollbar, if there is one. */ if ((textPtr->flags & UPDATE_SCROLLBARS) && (textPtr->yScrollCmd != NULL)) { int numLines, first, result, maxY, height; TkTextLine *linePtr; char string[60]; /* * Count the number of text lines on the screen. */ textPtr->flags &= ~UPDATE_SCROLLBARS; maxY = 0; for (numLines = 0, linePtr = NULL, dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) { if (dlPtr->linePtr != linePtr) { numLines++; linePtr = dlPtr->linePtr; } maxY = dlPtr->y + dlPtr->height; } /* * If the screen isn't completely full, then estimate the number of * lines that would fit on it if it were full. */ height = dInfoPtr->maxY - dInfoPtr->y; if (numLines == 0) { numLines = height / (textPtr->fontPtr->ascent + textPtr->fontPtr->descent); } else if (maxY < height) { numLines = (numLines * height)/maxY; } first = TkBTreeLineIndex(dInfoPtr->dLinePtr->linePtr); sprintf(string, " %d %d %d %d", TkBTreeNumLines(textPtr->tree), numLines, first, first+numLines-1); result = Tcl_VarEval(textPtr->interp, textPtr->yScrollCmd, string, (char *) NULL); if (result != TCL_OK) { Tcl_AddErrorInfo(textPtr->interp, "\n (horizontal scrolling command executed by text)"); Tk_BackgroundError(textPtr->interp); } } dInfoPtr->flags &= ~(REDRAW_PENDING|REDRAW_BORDERS); } /* *---------------------------------------------------------------------- * * 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; /* * 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; Tk_DoWhenIdle(DisplayText, (ClientData) textPtr); } if ((x < dInfoPtr->x) || (y < dInfoPtr->y) || ((x + width) > dInfoPtr->maxX) || (maxY > dInfoPtr->maxY)) { dInfoPtr->flags |= REDRAW_BORDERS; } } /* *---------------------------------------------------------------------- * * TkTextLinesChanged -- * * This procedure is invoked when lines in a text widget are about * to be modified in a way that changes how they are displayed (e.g. * characters were inserted, the line was deleted, or tag information * was changed). This procedure must be called *before* a change is * made, so that pointers to TkTextLines in the display information * are still valid. * * Results: * None. * * Side effects: * The indicated lines will be redisplayed at some point in the * future (the actual redisplay is scheduled as a when-idle handler). * *---------------------------------------------------------------------- */ void TkTextLinesChanged(textPtr, first, last) TkText *textPtr; /* Widget record for text widget. */ int first; /* Index of first line that must be * redisplayed. */ int last; /* Index of last line to redisplay. */ { DInfo *dInfoPtr = textPtr->dInfoPtr; DLine *firstPtr, *lastPtr; /* * Find the DLines corresponding to first and last+1. */ firstPtr = FindDLine(dInfoPtr->dLinePtr, first); if (firstPtr == NULL) { return; } lastPtr = FindDLine(dInfoPtr->dLinePtr, last+1); if (firstPtr == lastPtr) { return; } /* * Delete all the DLines from first up through last (but not including * lastPtr, which points to the first line *outside* the range). */ FreeDLines(textPtr, firstPtr, lastPtr, 1); /* * Schedule both a redisplay and a recomputation of display information. */ if (!(dInfoPtr->flags & REDRAW_PENDING)) { Tk_DoWhenIdle(DisplayText, (ClientData) textPtr); } dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED; } /* *---------------------------------------------------------------------- * * TkTextRedrawTag -- * * This procedure is invoked to request a redraw of all characters * in a given range of characters that have a particular tag on or * off. It's called, for example, when characters are tagged or * untagged, or 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, line1, ch1, line2, ch2, tagPtr, withTag) TkText *textPtr; /* Widget record for text widget. */ int line1, ch1; /* Index of first character in range of * interest. */ int line2, ch2; /* Index of character just after last one * in range of interest. */ 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 topLine, tagOn; TkTextSearch search; DInfo *dInfoPtr = textPtr->dInfoPtr; /* * 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; } topLine = TkBTreeLineIndex(dlPtr->linePtr); if (topLine > line1) { line1 = topLine; ch1 = 0; } /* * 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(textPtr->tree, line1, ch1+1, line2, ch2, tagPtr, &search); if (search.linePtr == NULL) { return; } tagOn = TkBTreeCharTagged(search.linePtr, ch1, tagPtr); if (tagOn != withTag) { if (!TkBTreeNextTag(&search)) { return; } } /* * 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. */ dlPtr = FindDLine(dlPtr, search.line1); if (dlPtr == NULL) { break; } /* * Find the first DLine structure that's past the end of the range. */ if (TkBTreeNextTag(&search)) { endPtr = FindDLine(dlPtr, (search.ch1 > 0) ? (search.line1 + 1) : search.line1); } else { endPtr = FindDLine(dlPtr, (ch2 > 0) ? (search.line2 + 1) : search.line2); } /* * 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; } } /* * Lastly, schedule a redisplay and layout recalculation if they * aren't already pending. */ if (!(dInfoPtr->flags & REDRAW_PENDING)) { Tk_DoWhenIdle(DisplayText, (ClientData) textPtr); } dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED; } /* *---------------------------------------------------------------------- * * 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; /* * Throw away all the current layout information. */ FreeDLines(textPtr, dInfoPtr->dLinePtr, (DLine *) NULL, 1); dInfoPtr->dLinePtr = NULL; /* * Recompute some overall things for the layout. */ dInfoPtr->x = textPtr->borderWidth + textPtr->padX; dInfoPtr->y = textPtr->borderWidth + textPtr->padY; dInfoPtr->maxX = Tk_Width(textPtr->tkwin) - dInfoPtr->x; dInfoPtr->maxY = Tk_Height(textPtr->tkwin) - dInfoPtr->y; dInfoPtr->topOfEof = dInfoPtr->maxY; if (!(dInfoPtr->flags & REDRAW_PENDING)) { Tk_DoWhenIdle(DisplayText, (ClientData) textPtr); } dInfoPtr->flags |= REDRAW_PENDING|REDRAW_BORDERS|DINFO_OUT_OF_DATE |REPICK_NEEDED; } /* *---------------------------------------------------------------------- * * TkTextSetView -- * * 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 line * given by "line" is visible on the screen at the position * determined by "pickPlace". * *---------------------------------------------------------------------- */ void TkTextSetView(textPtr, line, pickPlace) TkText *textPtr; /* Widget record for text widget. */ int line; /* Number of line that is to appear somewhere * in the window. */ 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, *dlPtr2; TkTextLine *linePtr; int curTopLine, curBotLine, numLines; int bottomY; TagInfo tagInfo; #define CLOSE_LINES 5 numLines = TkBTreeNumLines(textPtr->tree); if (line >= numLines) { line = numLines-1; } if (line < 0) { line = 0; } linePtr = TkBTreeFindLine(textPtr->tree, line); if (!pickPlace) { /* * The line must go at the top of the screen. See if the new * topmost line is already somewhere on the screen. If so then * delete all the DLine structures ahead of it. Otherwise just * leave all the DLine's alone (if the new topmost line is above * the top of the current window, i.e. we're scrolling back towards * the beginning of the file we may be able to reuse some of the * information that's currently on the screen without redisplaying * it all. */ dlPtr = FindDLine(dInfoPtr->dLinePtr, line); if ((dlPtr != NULL) && (dlPtr != dInfoPtr->dLinePtr)) { FreeDLines(textPtr, dInfoPtr->dLinePtr, dlPtr, 1); } textPtr->topLinePtr = linePtr; goto scheduleUpdate; } /* * We have to pick where to display the given line. First, bring * the display information up to date and see if the line 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); } for (dlPtr = dInfoPtr->dLinePtr; ; dlPtr = dlPtr->nextPtr) { if (dlPtr->nextPtr == NULL) { break; } if ((dlPtr->linePtr == linePtr) && (dlPtr->nextPtr->linePtr != linePtr)) { break; } } if ((dlPtr->linePtr == linePtr) && ((dlPtr->y + dlPtr->height) <= dInfoPtr->maxY)) { return; } /* * The desired line isn't already on-screen. See if it is within * a few lines of the top of the window. If so then just make it * the top line on the screen. */ bottomY = (dInfoPtr->y + dInfoPtr->maxY)/2; curTopLine = TkBTreeLineIndex(dInfoPtr->dLinePtr->linePtr); if (line < curTopLine) { if (line >= (curTopLine-CLOSE_LINES)) { textPtr->topLinePtr = TkBTreeFindLine(textPtr->tree, line); goto scheduleUpdate; } } else { /* * The desired line is below the bottom of the screen. If it is * within a few lines of the bottom of the screen then position * it at the bottom of the screen. (At this point dlPtr points to * the last line on the screen) */ curBotLine = TkBTreeLineIndex(dlPtr->linePtr); if (line <= (curBotLine+5)) { bottomY = dInfoPtr->maxY; } } /* * Our job now is arrange the display so that "line" 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 the center of the window. Work upwards (through smaller * line numbers) computing how much space lines take, until we * fine the line that should be at the top of the screen. */ for (textPtr->topLinePtr = linePtr = TkBTreeFindLine(textPtr->tree, line); ; line--, textPtr->topLinePtr = linePtr, linePtr = TkBTreeFindLine(textPtr->tree, line)) { tagInfo.tagPtrs = TkBTreeGetTags(textPtr->tree, linePtr, 0, &tagInfo.numTags); tagInfo.arraySize = tagInfo.numTags; TkBTreeStartSearch(textPtr->tree, line, 1, line+1, 0, (TkTextTag *) NULL, &tagInfo.search); TkBTreeNextTag(&tagInfo.search); dlPtr = LayoutLine(textPtr, line, linePtr, &tagInfo); for (dlPtr2 = dlPtr; dlPtr2 != NULL; dlPtr2 = dlPtr2->nextPtr) { bottomY -= dlPtr2->height; } FreeDLines(textPtr, dlPtr, (DLine *) NULL, 0); if (tagInfo.tagPtrs != NULL) { ckfree((char *) tagInfo.tagPtrs); } if ((bottomY <= 0) || (line <= 0)) { break; } } scheduleUpdate: if (!(dInfoPtr->flags & REDRAW_PENDING)) { Tk_DoWhenIdle(DisplayText, (ClientData) textPtr); } dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED; } /* *---------------------------------------------------------------------- * * FindDLine -- * * This procedure is called to find the DLine corresponding to a * given text line. * * Results: * The return value is a pointer to the first DLine found in the * list headed by dlPtr whose line number is greater or equal to * line. If there is no such line in the list then NULL is returned. * * Side effects: * None. * *---------------------------------------------------------------------- */ static DLine * FindDLine(dlPtr, line) register DLine *dlPtr; /* Pointer to first in list of DLines * to search. */ int line; /* Line number in text that is desired. */ { TkTextLine *linePtr; int thisLine; if (dlPtr == NULL) { return NULL; } thisLine = TkBTreeLineIndex(dlPtr->linePtr); while (thisLine < line) { /* * This DLine isn't the right one. Go on to the next DLine * (skipping multiple DLine's for the same text line). */ linePtr = dlPtr->linePtr; do { dlPtr = dlPtr->nextPtr; if (dlPtr == NULL) { return NULL; } } while (dlPtr->linePtr == linePtr); /* * Step through text lines, keeping track of the line number * we're on, until we catch up to dlPtr (remember, there could * be gaps in the DLine list where DLine's have been deleted). */ do { linePtr = TkBTreeNextLine(linePtr); thisLine++; if (linePtr == NULL) { panic("FindDLine reached end of text"); } } while (linePtr != dlPtr->linePtr); } return dlPtr; } /* *---------------------------------------------------------------------- * * TkTextCharAtLoc -- * * Given an (x,y) coordinate on the screen, find the location of * the closest character to that location. * * Results: * The return value is a pointer to the text line containing the * character displayed closest to (x,y). The value at *chPtr is * overwritten with the index with that line of the closest * character. * * Side effects: * None. * *---------------------------------------------------------------------- */ TkTextLine * TkTextCharAtLoc(textPtr, x, y, chPtr) TkText *textPtr; /* Widget record for text widget. */ int x, y; /* Pixel coordinates of point in widget's * window. */ int *chPtr; /* Place to store index-within-line of * closest character. */ { DInfo *dInfoPtr = textPtr->dInfoPtr; register DLine *dlPtr; register Chunk *chunkPtr; int count; int endX; /* * 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 (y < dInfoPtr->y) { y = dInfoPtr->y; x = dInfoPtr->x; } else if (y >= dInfoPtr->topOfEof) { y = dInfoPtr->topOfEof; x = dInfoPtr->maxX; } for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) { if (y > (dlPtr->y + dlPtr->height)) { if (dlPtr->nextPtr != NULL) { continue; } /* * The coordinates are off the bottom of the window. Adjust * them to refer to the lower-right character on the window. */ y = dlPtr->y; x = dInfoPtr->maxX; } for (chunkPtr = dlPtr->chunkPtr; ; chunkPtr = chunkPtr->nextPtr) { if ((chunkPtr->nextPtr == NULL) || (chunkPtr->nextPtr->x > x)) { break; } } count = TkMeasureChars(chunkPtr->stylePtr->sValuePtr->fontPtr, chunkPtr->text, chunkPtr->numChars, chunkPtr->x, x, 0, &endX); if (count >= chunkPtr->numChars) { /* * The point is off the end of the line. Return the character * after the last one that fit, unless that character appears * as the first character on the next DLine or unless the last * one that fit extends beyond the edge of the window. */ if ((dlPtr->nextPtr != NULL) && (dlPtr->nextPtr->chunkPtr->text == (chunkPtr->text + chunkPtr->numChars))) { count = chunkPtr->numChars-1; } if (endX >= dInfoPtr->maxX) { count = chunkPtr->numChars-1; } } *chPtr = count + (chunkPtr->text - dlPtr->linePtr->bytes); return dlPtr->linePtr; } panic("TkTextCharAtLoc ran out of lines"); return (TkTextLine *) NULL; }