stk/Tk/generic/tkTextDisp.c

5015 lines
149 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* tkTextDisp.c --
*
* This module provides facilities to display text widgets. It is
* the only place where information is kept about the screen layout
* of text widgets.
*
* Copyright (c) 1992-1994 The Regents of the University of California.
* Copyright (c) 1994-1997 Sun Microsystems, Inc.
*
* See the file "license.terms" for information on usage and redistribution
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
*
* SCCS: @(#) tkTextDisp.c 1.124 97/07/11 18:01:03
*/
#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 {
Tk_3DBorder border; /* Used for drawing background under text.
* NULL means use widget background. */
int borderWidth; /* Width of 3-D border for background. */
int relief; /* 3-D relief for background. */
Pixmap bgStipple; /* Stipple bitmap for background. None
* means draw solid. */
XColor *fgColor; /* Foreground color for text. */
Tk_Font tkfont; /* Font for displaying text. */
Pixmap fgStipple; /* Stipple bitmap for text and other
* foreground stuff. None means draw
* solid.*/
int justify; /* Justification style for text. */
int lMargin1; /* Left margin, in pixels, for first display
* line of each text line. */
int lMargin2; /* Left margin, in pixels, for second and
* later display lines of each text line. */
int offset; /* Offset in pixels of baseline, relative to
* baseline of line. */
int overstrike; /* Non-zero means draw overstrike through
* text. */
int rMargin; /* Right margin, in pixels. */
int spacing1; /* Spacing above first dline in text line. */
int spacing2; /* Spacing between lines of dline. */
int spacing3; /* Spacing below last dline in text line. */
TkTextTabArray *tabArrayPtr;/* Locations and types of tab stops (may
* be NULL). */
int underline; /* Non-zero means draw underline underneath
* text. */
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
* graphics contexts used to actually draw the characters. The entries
* in dInfoPtr->styleTable point to structures of this type.
*/
typedef struct TextStyle {
int refCount; /* Number of times this structure is
* referenced in Chunks. */
GC bgGC; /* Graphics context for background. None
* means use widget background. */
GC fgGC; /* Graphics context for foreground. */
StyleValues *sValuePtr; /* Raw information from which GCs were
* derived. */
Tcl_HashEntry *hPtr; /* Pointer to entry in styleTable. Used
* to delete entry. */
} TextStyle;
/*
* The following macro determines whether two styles have the same
* background so that, for example, no beveled border should be drawn
* between them.
*/
#define SAME_BACKGROUND(s1, s2) \
(((s1)->sValuePtr->border == (s2)->sValuePtr->border) \
&& ((s1)->sValuePtr->borderWidth == (s2)->sValuePtr->borderWidth) \
&& ((s1)->sValuePtr->relief == (s2)->sValuePtr->relief) \
&& ((s1)->sValuePtr->bgStipple == (s2)->sValuePtr->bgStipple))
/*
* The following structure describes one line of the display, which may
* be either part or all of one line of the text.
*/
typedef struct DLine {
TkTextIndex index; /* Identifies first character in text
* that is displayed on this line. */
int 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 baseline; /* Offset of text baseline from y, in
* pixels. */
int spaceAbove; /* How much extra space was added to the
* top of the line because of spacing
* options. This is included in height
* and baseline. */
int spaceBelow; /* How much extra space was added to the
* bottom of the line because of spacing
* options. This is included in height. */
int length; /* Total length of line, in pixels. */
TkTextDispChunk *chunkPtr; /* Pointer to first chunk in list of all
* of those that are displayed on this
* line of the screen. */
struct DLine *nextPtr; /* Next in list of all display lines for
* this window. The list is sorted in
* order from top to bottom. Note: the
* next DLine doesn't always correspond
* to the next line of text: (a) can have
* multiple DLines for one text line, and
* (b) can have gaps where DLine's have been
* deleted because they're out of date. */
int flags; /* Various flag bits: see below for values. */
} DLine;
/*
* Flag bits for DLine structures:
*
* HAS_3D_BORDER - Non-zero means that at least one of the
* chunks in this line has a 3D border, so
* it potentially interacts with 3D borders
* in neighboring lines (see
* DisplayLineBackground).
* NEW_LAYOUT - Non-zero means that the line has been
* re-layed out since the last time the
* display was updated.
* TOP_LINE - Non-zero means that this was the top line
* in the window the last time that the window
* was laid out. This is important because
* a line may be displayed differently if its
* at the top or bottom than if it's in the
* middle (e.g. beveled edges aren't displayed
* for middle lines if the adjacent line has
* a similar background).
* BOTTOM_LINE - Non-zero means that this was the bottom line
* in the window the last time that the window
* was laid out.
*/
#define HAS_3D_BORDER 1
#define NEW_LAYOUT 2
#define TOP_LINE 4
#define BOTTOM_LINE 8
/*
* Overall display information for a text widget:
*/
typedef struct TextDInfo {
Tcl_HashTable styleTable; /* Hash table that maps from StyleValues
* to TextStyles for this widget. */
DLine *dLinePtr; /* First in list of all display lines for
* this widget, in order from top to bottom. */
GC copyGC; /* Graphics context for copying from off-
* screen pixmaps onto screen. */
GC scrollGC; /* Graphics context for copying from one place
* in the window to another (scrolling):
* differs from copyGC in that we need to get
* GraphicsExpose events. */
int x; /* First x-coordinate that may be used for
* actually displaying line information.
* Leaves space for border, etc. */
int y; /* First y-coordinate that may be used for
* actually displaying line information.
* Leaves space for border, etc. */
int maxX; /* First x-coordinate to right of available
* space for displaying lines. */
int maxY; /* First y-coordinate below available
* space for displaying lines. */
int topOfEof; /* Top-most pixel (lowest y-value) that has
* been drawn in the appropriate fashion for
* the portion of the window after the last
* line of the text. This field is used to
* figure out when to redraw part or all of
* the eof field. */
/*
* Information used for scrolling:
*/
int 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. */
/*
* The following information is used to implement scanning:
*/
int scanMarkChar; /* Character that was at the left edge of
* the window when the scan started. */
int scanMarkX; /* X-position of mouse at time scan started. */
int scanTotalScroll; /* Total scrolling (in screen lines) that has
* occurred since scanMarkY was set. */
int scanMarkY; /* Y-position of mouse at time scan started. */
/*
* Miscellaneous information:
*/
int dLinesInvalidated; /* This value is set to 1 whenever something
* happens that invalidates information in
* DLine structures; if a redisplay
* is in progress, it will see this and
* abort the redisplay. This is needed
* because, for example, an embedded window
* could change its size when it is first
* displayed, invalidating the DLine that
* is currently being displayed. If redisplay
* continues, it will use freed memory and
* could dump core. */
int flags; /* Various flag values: see below for
* definitions. */
} TextDInfo;
/*
* In TkTextDispChunk structures for character segments, the clientData
* field points to one of the following structures:
*/
typedef struct CharInfo {
int 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 TextDInfo structures:
*
* DINFO_OUT_OF_DATE: Non-zero means that the DLine structures
* for this window are partially or completely
* out of date and need to be recomputed.
* REDRAW_PENDING: Means that a when-idle handler has been
* scheduled to update the display.
* REDRAW_BORDERS: Means window border or pad area has
* potentially been damaged and must be redrawn.
* REPICK_NEEDED: 1 means that the widget has been modified
* in a way that could change the current
* character (a different character might be
* under the mouse cursor now). Need to
* recompute the current character before
* the next redisplay.
*/
#define DINFO_OUT_OF_DATE 1
#define REDRAW_PENDING 2
#define REDRAW_BORDERS 4
#define REPICK_NEEDED 8
/*
* The following counters keep statistics about redisplay that can be
* checked to see how clever this code is at reducing redisplays.
*/
static int numRedisplays; /* Number of calls to DisplayText. */
static int linesRedrawn; /* Number of calls to DisplayDLine. */
static int numCopies; /* Number of calls to XCopyArea to copy part
* of the screen. */
/*
* Forward declarations for procedures defined later in this file:
*/
static void AdjustForTab _ANSI_ARGS_((TkText *textPtr,
TkTextTabArray *tabArrayPtr, int index,
TkTextDispChunk *chunkPtr));
static void CharBboxProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr,
int index, int y, int lineHeight, int baseline,
int *xPtr, int *yPtr, int *widthPtr,
int *heightPtr));
static void CharDisplayProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr,
int x, int y, int height, int baseline,
Display *display, Drawable dst, int screenY));
static int CharMeasureProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr,
int x));
static void CharUndisplayProc _ANSI_ARGS_((TkText *textPtr,
TkTextDispChunk *chunkPtr));
static void DisplayDLine _ANSI_ARGS_((TkText *textPtr,
DLine *dlPtr, DLine *prevPtr, Pixmap pixmap));
static void DisplayLineBackground _ANSI_ARGS_((TkText *textPtr,
DLine *dlPtr, DLine *prevPtr, Pixmap pixmap));
static void DisplayText _ANSI_ARGS_((ClientData clientData));
static DLine * FindDLine _ANSI_ARGS_((DLine *dlPtr,
TkTextIndex *indexPtr));
static void FreeDLines _ANSI_ARGS_((TkText *textPtr,
DLine *firstPtr, DLine *lastPtr, int unlink));
static void FreeStyle _ANSI_ARGS_((TkText *textPtr,
TextStyle *stylePtr));
static TextStyle * GetStyle _ANSI_ARGS_((TkText *textPtr,
TkTextIndex *indexPtr));
static void GetXView _ANSI_ARGS_((Tcl_Interp *interp,
TkText *textPtr, int report));
static void GetYView _ANSI_ARGS_((Tcl_Interp *interp,
TkText *textPtr, int report));
static DLine * LayoutDLine _ANSI_ARGS_((TkText *textPtr,
TkTextIndex *indexPtr));
static int MeasureChars _ANSI_ARGS_((Tk_Font tkfont,
CONST char *source, int maxChars, int startX,
int maxX, int tabOrigin, int *nextXPtr));
static void MeasureUp _ANSI_ARGS_((TkText *textPtr,
TkTextIndex *srcPtr, int distance,
TkTextIndex *dstPtr));
static int NextTabStop _ANSI_ARGS_((Tk_Font tkfont, int x,
int tabOrigin));
static void UpdateDisplayInfo _ANSI_ARGS_((TkText *textPtr));
static void ScrollByLines _ANSI_ARGS_((TkText *textPtr,
int offset));
static int SizeOfTab _ANSI_ARGS_((TkText *textPtr,
TkTextTabArray *tabArrayPtr, int index, int x,
int maxX));
static void TextInvalidateRegion _ANSI_ARGS_((TkText *textPtr,
TkRegion region));
/*
*----------------------------------------------------------------------
*
* TkTextCreateDInfo --
*
* This procedure is called when a new text widget is created.
* Its job is to set up display-related information for the widget.
*
* Results:
* None.
*
* Side effects:
* A TextDInfo data structure is allocated and initialized and attached
* to textPtr.
*
*----------------------------------------------------------------------
*/
void
TkTextCreateDInfo(textPtr)
TkText *textPtr; /* Overall information for text widget. */
{
register TextDInfo *dInfoPtr;
XGCValues gcValues;
dInfoPtr = (TextDInfo *) ckalloc(sizeof(TextDInfo));
Tcl_InitHashTable(&dInfoPtr->styleTable, sizeof(StyleValues)/sizeof(int));
dInfoPtr->dLinePtr = NULL;
dInfoPtr->copyGC = None;
gcValues.graphics_exposures = True;
dInfoPtr->scrollGC = Tk_GetGC(textPtr->tkwin, GCGraphicsExposures,
&gcValues);
dInfoPtr->topOfEof = 0;
dInfoPtr->newCharOffset = 0;
dInfoPtr->curPixelOffset = 0;
dInfoPtr->maxLength = 0;
dInfoPtr->xScrollFirst = -1;
dInfoPtr->xScrollLast = -1;
dInfoPtr->yScrollFirst = -1;
dInfoPtr->yScrollLast = -1;
dInfoPtr->scanMarkChar = 0;
dInfoPtr->scanMarkX = 0;
dInfoPtr->scanTotalScroll = 0;
dInfoPtr->scanMarkY = 0;
dInfoPtr->dLinesInvalidated = 0;
dInfoPtr->flags = DINFO_OUT_OF_DATE;
textPtr->dInfoPtr = dInfoPtr;
}
/*
*----------------------------------------------------------------------
*
* TkTextFreeDInfo --
*
* This procedure is called to free up all of the private display
* information kept by this file for a text widget.
*
* Results:
* None.
*
* Side effects:
* Lots of resources get freed.
*
*----------------------------------------------------------------------
*/
void
TkTextFreeDInfo(textPtr)
TkText *textPtr; /* Overall information for text widget. */
{
register TextDInfo *dInfoPtr = textPtr->dInfoPtr;
/*
* Be careful to free up styleTable *after* freeing up all the
* DLines, so that the hash table is still intact to free up the
* style-related information from the lines. Once the lines are
* all free then styleTable will be empty.
*/
FreeDLines(textPtr, dInfoPtr->dLinePtr, (DLine *) NULL, 1);
Tcl_DeleteHashTable(&dInfoPtr->styleTable);
if (dInfoPtr->copyGC != None) {
Tk_FreeGC(textPtr->display, dInfoPtr->copyGC);
}
Tk_FreeGC(textPtr->display, dInfoPtr->scrollGC);
if (dInfoPtr->flags & REDRAW_PENDING) {
Tcl_CancelIdleCall(DisplayText, (ClientData) textPtr);
}
ckfree((char *) dInfoPtr);
}
/*
*----------------------------------------------------------------------
*
* GetStyle --
*
* This procedure creates all the information needed to display
* text at a particular location.
*
* Results:
* The return value is a pointer to a TextStyle structure that
* corresponds to *sValuePtr.
*
* Side effects:
* A new entry may be created in the style table for the widget.
*
*----------------------------------------------------------------------
*/
static TextStyle *
GetStyle(textPtr, indexPtr)
TkText *textPtr; /* Overall information about text widget. */
TkTextIndex *indexPtr; /* The character in the text for which
* display information is wanted. */
{
TkTextTag **tagPtrs;
register TkTextTag *tagPtr;
StyleValues styleValues;
TextStyle *stylePtr;
Tcl_HashEntry *hPtr;
int numTags, new, i;
XGCValues gcValues;
unsigned long mask;
/*
* The variables below keep track of the highest-priority specification
* that has occurred for each of the various fields of the StyleValues.
*/
int borderPrio, borderWidthPrio, reliefPrio, bgStipplePrio;
int fgPrio, fontPrio, fgStipplePrio;
int underlinePrio, justifyPrio, offsetPrio;
int lMargin1Prio, lMargin2Prio, rMarginPrio;
int spacing1Prio, spacing2Prio, spacing3Prio;
int overstrikePrio, tabPrio, wrapPrio;
/*
* Find out what tags are present for the character, then compute
* a StyleValues structure corresponding to those tags (scan
* through all of the tags, saving information for the highest-
* priority tag).
*/
tagPtrs = TkBTreeGetTags(indexPtr, &numTags);
borderPrio = borderWidthPrio = reliefPrio = bgStipplePrio = -1;
fgPrio = fontPrio = fgStipplePrio = -1;
underlinePrio = justifyPrio = offsetPrio = -1;
lMargin1Prio = lMargin2Prio = rMarginPrio = -1;
spacing1Prio = spacing2Prio = spacing3Prio = -1;
overstrikePrio = tabPrio = wrapPrio = -1;
memset((VOID *) &styleValues, 0, sizeof(StyleValues));
styleValues.relief = TK_RELIEF_FLAT;
styleValues.fgColor = textPtr->fgColor;
styleValues.tkfont = textPtr->tkfont;
styleValues.justify = TK_JUSTIFY_LEFT;
styleValues.spacing1 = textPtr->spacing1;
styleValues.spacing2 = textPtr->spacing2;
styleValues.spacing3 = textPtr->spacing3;
styleValues.tabArrayPtr = textPtr->tabArrayPtr;
styleValues.wrapMode = textPtr->wrapMode;
for (i = 0 ; i < numTags; i++) {
tagPtr = tagPtrs[i];
/*
* On Windows and Mac, we need to skip the selection tag if
* we don't have focus.
*/
#ifndef ALWAYS_SHOW_SELECTION
if ((tagPtr == textPtr->selTagPtr) && !(textPtr->flags & GOT_FOCUS)) {
continue;
}
#endif
if ((tagPtr->border != NULL) && (tagPtr->priority > borderPrio)) {
styleValues.border = tagPtr->border;
borderPrio = tagPtr->priority;
}
if ((tagPtr->bdString != NULL)
&& (tagPtr->priority > borderWidthPrio)) {
styleValues.borderWidth = tagPtr->borderWidth;
borderWidthPrio = tagPtr->priority;
}
if ((tagPtr->reliefString != NULL)
&& (tagPtr->priority > reliefPrio)) {
if (styleValues.border == NULL) {
styleValues.border = textPtr->border;
}
styleValues.relief = tagPtr->relief;
reliefPrio = tagPtr->priority;
}
if ((tagPtr->bgStipple != None)
&& (tagPtr->priority > bgStipplePrio)) {
styleValues.bgStipple = tagPtr->bgStipple;
bgStipplePrio = tagPtr->priority;
}
if ((tagPtr->fgColor != None) && (tagPtr->priority > fgPrio)) {
styleValues.fgColor = tagPtr->fgColor;
fgPrio = tagPtr->priority;
}
if ((tagPtr->tkfont != None) && (tagPtr->priority > fontPrio)) {
styleValues.tkfont = tagPtr->tkfont;
fontPrio = tagPtr->priority;
}
if ((tagPtr->fgStipple != None)
&& (tagPtr->priority > fgStipplePrio)) {
styleValues.fgStipple = tagPtr->fgStipple;
fgStipplePrio = tagPtr->priority;
}
if ((tagPtr->justifyString != NULL)
&& (tagPtr->priority > justifyPrio)) {
styleValues.justify = tagPtr->justify;
justifyPrio = tagPtr->priority;
}
if ((tagPtr->lMargin1String != NULL)
&& (tagPtr->priority > lMargin1Prio)) {
styleValues.lMargin1 = tagPtr->lMargin1;
lMargin1Prio = tagPtr->priority;
}
if ((tagPtr->lMargin2String != NULL)
&& (tagPtr->priority > lMargin2Prio)) {
styleValues.lMargin2 = tagPtr->lMargin2;
lMargin2Prio = tagPtr->priority;
}
if ((tagPtr->offsetString != NULL)
&& (tagPtr->priority > offsetPrio)) {
styleValues.offset = tagPtr->offset;
offsetPrio = tagPtr->priority;
}
if ((tagPtr->overstrikeString != NULL)
&& (tagPtr->priority > overstrikePrio)) {
styleValues.overstrike = tagPtr->overstrike;
overstrikePrio = tagPtr->priority;
}
if ((tagPtr->rMarginString != NULL)
&& (tagPtr->priority > rMarginPrio)) {
styleValues.rMargin = tagPtr->rMargin;
rMarginPrio = tagPtr->priority;
}
if ((tagPtr->spacing1String != NULL)
&& (tagPtr->priority > spacing1Prio)) {
styleValues.spacing1 = tagPtr->spacing1;
spacing1Prio = tagPtr->priority;
}
if ((tagPtr->spacing2String != NULL)
&& (tagPtr->priority > spacing2Prio)) {
styleValues.spacing2 = tagPtr->spacing2;
spacing2Prio = tagPtr->priority;
}
if ((tagPtr->spacing3String != NULL)
&& (tagPtr->priority > spacing3Prio)) {
styleValues.spacing3 = tagPtr->spacing3;
spacing3Prio = tagPtr->priority;
}
if ((tagPtr->tabString != NULL)
&& (tagPtr->priority > tabPrio)) {
styleValues.tabArrayPtr = tagPtr->tabArrayPtr;
tabPrio = tagPtr->priority;
}
if ((tagPtr->underlineString != NULL)
&& (tagPtr->priority > underlinePrio)) {
styleValues.underline = tagPtr->underline;
underlinePrio = tagPtr->priority;
}
if ((tagPtr->wrapMode != NULL)
&& (tagPtr->priority > wrapPrio)) {
styleValues.wrapMode = tagPtr->wrapMode;
wrapPrio = tagPtr->priority;
}
}
if (tagPtrs != NULL) {
ckfree((char *) tagPtrs);
}
/*
* Use an existing style if there's one around that matches.
*/
hPtr = Tcl_CreateHashEntry(&textPtr->dInfoPtr->styleTable,
(char *) &styleValues, &new);
if (!new) {
stylePtr = (TextStyle *) Tcl_GetHashValue(hPtr);
stylePtr->refCount++;
return stylePtr;
}
/*
* No existing style matched. Make a new one.
*/
stylePtr = (TextStyle *) ckalloc(sizeof(TextStyle));
stylePtr->refCount = 1;
if (styleValues.border != NULL) {
gcValues.foreground = Tk_3DBorderColor(styleValues.border)->pixel;
mask = GCForeground;
if (styleValues.bgStipple != None) {
gcValues.stipple = styleValues.bgStipple;
gcValues.fill_style = FillStippled;
mask |= GCStipple|GCFillStyle;
}
stylePtr->bgGC = Tk_GetGC(textPtr->tkwin, mask, &gcValues);
} else {
stylePtr->bgGC = None;
}
mask = GCForeground|GCFont;
gcValues.foreground = styleValues.fgColor->pixel;
gcValues.font = Tk_FontId(styleValues.tkfont);
if (styleValues.fgStipple != None) {
gcValues.stipple = styleValues.fgStipple;
gcValues.fill_style = FillStippled;
mask |= GCStipple|GCFillStyle;
}
stylePtr->fgGC = Tk_GetGC(textPtr->tkwin, mask, &gcValues);
stylePtr->sValuePtr = (StyleValues *)
Tcl_GetHashKey(&textPtr->dInfoPtr->styleTable, hPtr);
stylePtr->hPtr = hPtr;
Tcl_SetHashValue(hPtr, stylePtr);
return stylePtr;
}
/*
*----------------------------------------------------------------------
*
* FreeStyle --
*
* This procedure is called when a TextStyle structure is no longer
* needed. It decrements the reference count and frees up the
* space for the style structure if the reference count is 0.
*
* Results:
* None.
*
* Side effects:
* The storage and other resources associated with the style
* are freed up if no-one's still using it.
*
*----------------------------------------------------------------------
*/
static void
FreeStyle(textPtr, stylePtr)
TkText *textPtr; /* Information about overall widget. */
register TextStyle *stylePtr; /* Information about style to free. */
{
stylePtr->refCount--;
if (stylePtr->refCount == 0) {
if (stylePtr->bgGC != None) {
Tk_FreeGC(textPtr->display, stylePtr->bgGC);
}
Tk_FreeGC(textPtr->display, stylePtr->fgGC);
Tcl_DeleteHashEntry(stylePtr->hPtr);
ckfree((char *) stylePtr);
}
}
/*
*----------------------------------------------------------------------
*
* LayoutDLine --
*
* This procedure generates a single DLine structure for a display
* line whose leftmost character is given by indexPtr.
*
* Results:
* The return value is a pointer to a DLine structure desribing the
* display line. All fields are filled in and correct except for
* y and nextPtr.
*
* Side effects:
* Storage is allocated for the new DLine.
*
*----------------------------------------------------------------------
*/
static DLine *
LayoutDLine(textPtr, indexPtr)
TkText *textPtr; /* Overall information about text widget. */
TkTextIndex *indexPtr; /* Beginning of display line. May not
* necessarily point to a character segment. */
{
register DLine *dlPtr; /* New display line. */
TkTextSegment *segPtr; /* Current segment in text. */
TkTextDispChunk *lastChunkPtr; /* Last chunk allocated so far
* for line. */
TkTextDispChunk *chunkPtr; /* Current chunk. */
TkTextIndex curIndex;
TkTextDispChunk *breakChunkPtr; /* Chunk containing best word break
* point, if any. */
TkTextIndex breakIndex; /* Index of first character in
* breakChunkPtr. */
int 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, ascent, descent, 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->baseline = 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;
}
FreeStyle(textPtr, chunkPtr->stylePtr);
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;
}
ascent = descent = 0;
for (chunkPtr = dlPtr->chunkPtr; chunkPtr != NULL;
chunkPtr = chunkPtr->nextPtr) {
chunkPtr->x += jIndent;
dlPtr->count += chunkPtr->numChars;
if (chunkPtr->minAscent > ascent) {
ascent = chunkPtr->minAscent;
}
if (chunkPtr->minDescent > descent) {
descent = chunkPtr->minDescent;
}
if (chunkPtr->minHeight > dlPtr->height) {
dlPtr->height = chunkPtr->minHeight;
}
sValuePtr = chunkPtr->stylePtr->sValuePtr;
if ((sValuePtr->borderWidth > 0)
&& (sValuePtr->relief != TK_RELIEF_FLAT)) {
dlPtr->flags |= HAS_3D_BORDER;
}
}
if (dlPtr->height < (ascent + descent)) {
dlPtr->height = ascent + descent;
dlPtr->baseline = ascent;
} else {
dlPtr->baseline = ascent + (dlPtr->height - ascent - descent)/2;
}
sValuePtr = dlPtr->chunkPtr->stylePtr->sValuePtr;
if (dlPtr->index.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 += dlPtr->spaceAbove + dlPtr->spaceBelow;
dlPtr->baseline += dlPtr->spaceAbove;
/*
* Recompute line length: may have changed because of justification.
*/
dlPtr->length = lastChunkPtr->x + lastChunkPtr->width;
return dlPtr;
}
/*
*----------------------------------------------------------------------
*
* UpdateDisplayInfo --
*
* This procedure is invoked to recompute some or all of the
* DLine structures for a text widget. At the time it is called
* the DLine structures still left in the widget are guaranteed
* to be correct except that (a) the y-coordinates aren't
* necessarily correct, (b) there may be missing structures
* (the DLine structures get removed as soon as they are potentially
* out-of-date), and (c) DLine structures that don't start at the
* beginning of a line may be incorrect if previous information in
* the same line changed size in a way that moved a line boundary
* (DLines for any info that changed will have been deleted, but
* not DLines for unchanged info in the same text line).
*
* Results:
* None.
*
* Side effects:
* Upon return, the DLine information for textPtr correctly reflects
* the positions where characters will be displayed. However, this
* procedure doesn't actually bring the display up-to-date.
*
*----------------------------------------------------------------------
*/
static void
UpdateDisplayInfo(textPtr)
TkText *textPtr; /* Text widget to update. */
{
register TextDInfo *dInfoPtr = textPtr->dInfoPtr;
register DLine *dlPtr, *prevPtr;
TkTextIndex index;
TkTextLine *lastLinePtr;
int y, maxY, pixelOffset, maxOffset;
if (!(dInfoPtr->flags & DINFO_OUT_OF_DATE)) {
return;
}
dInfoPtr->flags &= ~DINFO_OUT_OF_DATE;
/*
* Delete any DLines that are now above the top of the window.
*/
index = textPtr->topIndex;
dlPtr = FindDLine(dInfoPtr->dLinePtr, &index);
if ((dlPtr != NULL) && (dlPtr != dInfoPtr->dLinePtr)) {
FreeDLines(textPtr, dInfoPtr->dLinePtr, dlPtr, 1);
}
/*
*--------------------------------------------------------------
* Scan through the contents of the window from top to bottom,
* recomputing information for lines that are missing.
*--------------------------------------------------------------
*/
lastLinePtr = TkBTreeFindLine(textPtr->tree,
TkBTreeNumLines(textPtr->tree));
dlPtr = dInfoPtr->dLinePtr;
prevPtr = NULL;
y = dInfoPtr->y;
maxY = dInfoPtr->maxY;
while (1) {
register DLine *newPtr;
if (index.linePtr == lastLinePtr) {
break;
}
/*
* There are three possibilities right now:
* (a) the next DLine (dlPtr) corresponds exactly to the next
* information we want to display: just use it as-is.
* (b) the next DLine corresponds to a different line, or to
* a segment that will be coming later in the same line:
* leave this DLine alone in the hopes that we'll be able
* to use it later, then create a new DLine in front of
* it.
* (c) the next DLine corresponds to a segment in the line we
* want, but it's a segment that has already been processed
* or will never be processed. Delete the DLine and try
* again.
*
* One other twist on all this. It's possible for 3D borders
* to interact between lines (see DisplayLineBackground) so if
* a line is relayed out and has styles with 3D borders, its
* neighbors have to be redrawn if they have 3D borders too,
* since the interactions could have changed (the neighbors
* don't have to be relayed out, just redrawn).
*/
if ((dlPtr == NULL) || (dlPtr->index.linePtr != index.linePtr)) {
/*
* Case (b) -- must make new DLine.
*/
makeNewDLine:
if (tkTextDebug) {
char string[TK_POS_CHARS];
/*
* Debugging is enabled, so keep a log of all the lines
* that were re-layed out. The test suite uses this
* information.
*/
TkTextPrintIndex(&index, string);
Tcl_SetVar2(textPtr->interp, "tk_textRelayout", (char *) NULL,
string,
TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT);
}
newPtr = LayoutDLine(textPtr, &index);
if (prevPtr == NULL) {
dInfoPtr->dLinePtr = newPtr;
} else {
prevPtr->nextPtr = newPtr;
if (prevPtr->flags & HAS_3D_BORDER) {
prevPtr->oldY = -1;
}
}
newPtr->nextPtr = dlPtr;
dlPtr = newPtr;
} else {
/*
* DlPtr refers to the line we want. Next check the
* index within the line.
*/
if (index.charIndex == dlPtr->index.charIndex) {
/*
* Case (a) -- can use existing display line as-is.
*/
if ((dlPtr->flags & HAS_3D_BORDER) && (prevPtr != NULL)
&& (prevPtr->flags & (NEW_LAYOUT))) {
dlPtr->oldY = -1;
}
goto lineOK;
}
if (index.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;
if ((dlPtr->flags & HAS_3D_BORDER) && !(dlPtr->flags & TOP_LINE)) {
dlPtr->oldY = -1;
}
while (1) {
if ((dlPtr->flags & TOP_LINE) && (dlPtr != dInfoPtr->dLinePtr)
&& (dlPtr->flags & HAS_3D_BORDER)) {
dlPtr->oldY = -1;
}
if ((dlPtr->flags & BOTTOM_LINE) && (dlPtr->nextPtr != NULL)
&& (dlPtr->flags & HAS_3D_BORDER)) {
dlPtr->oldY = -1;
}
if (dlPtr->nextPtr == NULL) {
if ((dlPtr->flags & HAS_3D_BORDER)
&& !(dlPtr->flags & BOTTOM_LINE)) {
dlPtr->oldY = -1;
}
dlPtr->flags &= ~TOP_LINE;
dlPtr->flags |= BOTTOM_LINE;
break;
}
dlPtr->flags &= ~(TOP_LINE|BOTTOM_LINE);
dlPtr = dlPtr->nextPtr;
}
dInfoPtr->dLinePtr->flags |= TOP_LINE;
/*
* Arrange for scrollbars to be updated.
*/
textPtr->flags |= UPDATE_SCROLLBARS;
/*
*--------------------------------------------------------------
* Deal with horizontal scrolling:
* 1. If there's empty space to the right of the longest line,
* shift the screen to the right to fill in the empty space.
* 2. If the desired horizontal scroll position has changed,
* force a full redisplay of all the lines in the widget.
* 3. If the wrap mode isn't "none" then re-scroll to the base
* position.
*--------------------------------------------------------------
*/
dInfoPtr->maxLength = 0;
for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
dlPtr = dlPtr->nextPtr) {
if (dlPtr->length > dInfoPtr->maxLength) {
dInfoPtr->maxLength = dlPtr->length;
}
}
maxOffset = (dInfoPtr->maxLength - (dInfoPtr->maxX - dInfoPtr->x)
+ textPtr->charWidth - 1)/textPtr->charWidth;
if (dInfoPtr->newCharOffset > maxOffset) {
dInfoPtr->newCharOffset = maxOffset;
}
if (dInfoPtr->newCharOffset < 0) {
dInfoPtr->newCharOffset = 0;
}
pixelOffset = dInfoPtr->newCharOffset * textPtr->charWidth;
if (pixelOffset != dInfoPtr->curPixelOffset) {
dInfoPtr->curPixelOffset = pixelOffset;
for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
dlPtr = dlPtr->nextPtr) {
dlPtr->oldY = -1;
}
}
}
/*
*----------------------------------------------------------------------
*
* FreeDLines --
*
* This procedure is called to free up all of the resources
* associated with one or more DLine structures.
*
* Results:
* None.
*
* Side effects:
* Memory gets freed and various other resources are released.
*
*----------------------------------------------------------------------
*/
static void
FreeDLines(textPtr, firstPtr, lastPtr, unlink)
TkText *textPtr; /* Information about overall text
* widget. */
register DLine *firstPtr; /* Pointer to first DLine to free up. */
DLine *lastPtr; /* Pointer to DLine just after last
* one to free (NULL means everything
* starting with firstPtr). */
int unlink; /* 1 means DLines are currently linked
* into the list rooted at
* textPtr->dInfoPtr->dLinePtr and
* they have to be unlinked. 0 means
* just free without unlinking. */
{
register TkTextDispChunk *chunkPtr, *nextChunkPtr;
register DLine *nextDLinePtr;
if (unlink) {
if (textPtr->dInfoPtr->dLinePtr == firstPtr) {
textPtr->dInfoPtr->dLinePtr = lastPtr;
} else {
register DLine *prevPtr;
for (prevPtr = textPtr->dInfoPtr->dLinePtr;
prevPtr->nextPtr != firstPtr; prevPtr = prevPtr->nextPtr) {
/* Empty loop body. */
}
prevPtr->nextPtr = lastPtr;
}
}
while (firstPtr != lastPtr) {
nextDLinePtr = firstPtr->nextPtr;
for (chunkPtr = firstPtr->chunkPtr; chunkPtr != NULL;
chunkPtr = nextChunkPtr) {
if (chunkPtr->undisplayProc != NULL) {
(*chunkPtr->undisplayProc)(textPtr, chunkPtr);
}
FreeStyle(textPtr, chunkPtr->stylePtr);
nextChunkPtr = chunkPtr->nextPtr;
ckfree((char *) chunkPtr);
}
ckfree((char *) firstPtr);
firstPtr = nextDLinePtr;
}
textPtr->dInfoPtr->dLinesInvalidated = 1;
}
/*
*----------------------------------------------------------------------
*
* DisplayDLine --
*
* This procedure is invoked to draw a single line on the
* screen.
*
* Results:
* None.
*
* Side effects:
* The line given by dlPtr is drawn at its correct position in
* textPtr's window. Note that this is one *display* line, not
* one *text* line.
*
*----------------------------------------------------------------------
*/
static void
DisplayDLine(textPtr, dlPtr, prevPtr, pixmap)
TkText *textPtr; /* Text widget in which to draw line. */
register DLine *dlPtr; /* Information about line to draw. */
DLine *prevPtr; /* Line just before one to draw, or NULL
* if dlPtr is the top line. */
Pixmap pixmap; /* Pixmap to use for double-buffering.
* Caller must make sure it's large enough
* to hold line. */
{
register TkTextDispChunk *chunkPtr;
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
Display *display;
int height, x;
/*
* First, clear the area of the line to the background color for the
* text widget.
*/
display = Tk_Display(textPtr->tkwin);
Tk_Fill3DRectangle(textPtr->tkwin, pixmap, textPtr->border, 0, 0,
Tk_Width(textPtr->tkwin), dlPtr->height, 0, TK_RELIEF_FLAT);
/*
* Next, draw background information for the whole line.
*/
DisplayLineBackground(textPtr, dlPtr, prevPtr, pixmap);
/*
* Make another pass through all of the chunks to redraw the
* insertion cursor, if it is visible on this line. Must do
* it here rather than in the foreground pass below because
* otherwise a wide insertion cursor will obscure the character
* to its left.
*/
if (textPtr->state == tkNormalUid) {
for (chunkPtr = dlPtr->chunkPtr; (chunkPtr != NULL);
chunkPtr = chunkPtr->nextPtr) {
x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curPixelOffset;
if (chunkPtr->displayProc == TkTextInsertDisplayProc) {
(*chunkPtr->displayProc)(chunkPtr, x, dlPtr->spaceAbove,
dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
dlPtr->baseline - dlPtr->spaceAbove, display, pixmap,
dlPtr->y + dlPtr->spaceAbove);
}
}
}
/*
* Make yet another pass through all of the chunks to redraw all of
* foreground information. Note: we have to call the displayProc
* even for chunks that are off-screen. This is needed, for
* example, so that embedded windows can be unmapped in this case.
* Conve
*/
for (chunkPtr = dlPtr->chunkPtr; (chunkPtr != NULL);
chunkPtr = chunkPtr->nextPtr) {
if (chunkPtr->displayProc == TkTextInsertDisplayProc) {
/*
* Already displayed the insertion cursor above. Don't
* do it again here.
*/
continue;
}
x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curPixelOffset;
if ((x + chunkPtr->width <= 0) || (x >= dInfoPtr->maxX)) {
/*
* Note: we have to call the displayProc even for chunks
* that are off-screen. This is needed, for example, so
* that embedded windows can be unmapped in this case.
* Display the chunk at a coordinate that can be clearly
* identified by the displayProc as being off-screen to
* the left (the displayProc may not be able to tell if
* something is off to the right).
*/
(*chunkPtr->displayProc)(chunkPtr, -chunkPtr->width,
dlPtr->spaceAbove,
dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
dlPtr->baseline - dlPtr->spaceAbove, display, pixmap,
dlPtr->y + dlPtr->spaceAbove);
} else {
(*chunkPtr->displayProc)(chunkPtr, x, dlPtr->spaceAbove,
dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
dlPtr->baseline - dlPtr->spaceAbove, display, pixmap,
dlPtr->y + dlPtr->spaceAbove);
}
if (dInfoPtr->dLinesInvalidated) {
return;
}
}
/*
* Copy the pixmap onto the screen. If this is the last line on
* the screen then copy a piece of the line, so that it doesn't
* overflow into the border area. Another special trick: copy the
* padding area to the left of the line; this is because the
* insertion cursor sometimes overflows onto that area and we want
* to get as much of the cursor as possible.
*/
height = dlPtr->height;
if ((height + dlPtr->y) > dInfoPtr->maxY) {
height = dInfoPtr->maxY - dlPtr->y;
}
XCopyArea(display, pixmap, Tk_WindowId(textPtr->tkwin), dInfoPtr->copyGC,
dInfoPtr->x, 0, (unsigned) (dInfoPtr->maxX - dInfoPtr->x),
(unsigned) height, dInfoPtr->x, dlPtr->y);
linesRedrawn++;
}
/*
*--------------------------------------------------------------
*
* DisplayLineBackground --
*
* This procedure is called to fill in the background for
* a display line. It draws 3D borders cleverly so that
* adjacent chunks with the same style (whether on the same
* line or different lines) have a single 3D border around
* the whole region.
*
* Results:
* There is no return value. Pixmap is filled in with background
* information for dlPtr.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
static void
DisplayLineBackground(textPtr, dlPtr, prevPtr, pixmap)
TkText *textPtr; /* Text widget containing line. */
register DLine *dlPtr; /* Information about line to draw. */
DLine *prevPtr; /* Line just above dlPtr, or NULL if dlPtr
* is the top-most line in the window. */
Pixmap pixmap; /* Pixmap to use for double-buffering.
* Caller must make sure it's large enough
* to hold line. Caller must also have
* filled it with the background color for
* the widget. */
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
TkTextDispChunk *chunkPtr; /* Pointer to chunk in the current line. */
TkTextDispChunk *chunkPtr2; /* Pointer to chunk in the line above or
* below the current one. NULL if we're to
* the left of or to the right of the chunks
* in the line. */
TkTextDispChunk *nextPtr2; /* Next chunk after chunkPtr2 (it's not the
* same as chunkPtr2->nextPtr in the case
* where chunkPtr2 is NULL because the line
* is indented). */
int leftX; /* The left edge of the region we're
* currently working on. */
int leftXIn; /* 1 means beveled edge at leftX slopes right
* as it goes down, 0 means it slopes left
* as it goes down. */
int rightX; /* Right edge of chunkPtr. */
int rightX2; /* Right edge of chunkPtr2. */
int matchLeft; /* Does the style of this line match that
* of its neighbor just to the left of
* the current x coordinate? */
int matchRight; /* Does line's style match its neighbor
* just to the right of the current x-coord? */
int minX, maxX, xOffset;
StyleValues *sValuePtr;
Display *display;
/*
* Pass 1: scan through dlPtr from left to right. For each range of
* chunks with the same style, draw the main background for the style
* plus the vertical parts of the 3D borders (the left and right
* edges).
*/
display = Tk_Display(textPtr->tkwin);
minX = dInfoPtr->curPixelOffset;
xOffset = dInfoPtr->x - minX;
maxX = minX + dInfoPtr->maxX - dInfoPtr->x;
chunkPtr = dlPtr->chunkPtr;
/*
* Note A: in the following statement, and a few others later in
* this file marked with "See Note A above", the right side of the
* assignment was replaced with 0 on 6/18/97. This has the effect
* of highlighting the empty space to the left of a line whenever
* the leftmost character of the line is highlighted. This way,
* multi-line highlights always line up along their left edges.
* However, this may look funny in the case where a single word is
* highlighted. To undo the change, replace "leftX = 0" with "leftX
* = chunkPtr->x" and "rightX2 = 0" with "rightX2 = nextPtr2->x"
* here and at all the marked points below. This restores the old
* behavior where empty space to the left of a line is not
* highlighted, leaving a ragged left edge for multi-line
* highlights.
*/
leftX = 0;
for (; leftX < maxX; chunkPtr = chunkPtr->nextPtr) {
if ((chunkPtr->nextPtr != NULL)
&& SAME_BACKGROUND(chunkPtr->nextPtr->stylePtr,
chunkPtr->stylePtr)) {
continue;
}
sValuePtr = chunkPtr->stylePtr->sValuePtr;
rightX = chunkPtr->x + chunkPtr->width;
if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) {
rightX = maxX;
}
if (chunkPtr->stylePtr->bgGC != None) {
XFillRectangle(display, pixmap, chunkPtr->stylePtr->bgGC,
leftX + xOffset, 0, (unsigned int) (rightX - leftX),
(unsigned int) dlPtr->height);
if (sValuePtr->relief != TK_RELIEF_FLAT) {
Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
leftX + xOffset, 0, sValuePtr->borderWidth,
dlPtr->height, 1, sValuePtr->relief);
Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
rightX - sValuePtr->borderWidth + xOffset,
0, sValuePtr->borderWidth, dlPtr->height, 0,
sValuePtr->relief);
}
}
leftX = rightX;
}
/*
* Pass 2: draw the horizontal bevels along the top of the line. To
* do this, scan through dlPtr from left to right while simultaneously
* scanning through the line just above dlPtr. ChunkPtr2 and nextPtr2
* refer to two adjacent chunks in the line above.
*/
chunkPtr = dlPtr->chunkPtr;
leftX = 0; /* See Note A above. */
leftXIn = 1;
rightX = chunkPtr->x + chunkPtr->width;
if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) {
rightX = maxX;
}
chunkPtr2 = NULL;
if (prevPtr != NULL) {
/*
* Find the chunk in the previous line that covers leftX.
*/
nextPtr2 = prevPtr->chunkPtr;
rightX2 = 0; /* See Note A above. */
while (rightX2 <= leftX) {
chunkPtr2 = nextPtr2;
if (chunkPtr2 == NULL) {
break;
}
nextPtr2 = chunkPtr2->nextPtr;
rightX2 = chunkPtr2->x + chunkPtr2->width;
if (nextPtr2 == NULL) {
rightX2 = INT_MAX;
}
}
} else {
nextPtr2 = NULL;
rightX2 = INT_MAX;
}
while (leftX < maxX) {
matchLeft = (chunkPtr2 != NULL)
&& SAME_BACKGROUND(chunkPtr2->stylePtr, chunkPtr->stylePtr);
sValuePtr = chunkPtr->stylePtr->sValuePtr;
if (rightX <= rightX2) {
/*
* The chunk in our line is about to end. If its style
* changes then draw the bevel for the current style.
*/
if ((chunkPtr->nextPtr == NULL)
|| !SAME_BACKGROUND(chunkPtr->stylePtr,
chunkPtr->nextPtr->stylePtr)) {
if (!matchLeft && (sValuePtr->relief != TK_RELIEF_FLAT)) {
Tk_3DHorizontalBevel(textPtr->tkwin, pixmap,
sValuePtr->border, leftX + xOffset, 0,
rightX - leftX, sValuePtr->borderWidth, leftXIn,
1, 1, sValuePtr->relief);
}
leftX = rightX;
leftXIn = 1;
/*
* If the chunk in the line above is also ending at
* the same point then advance to the next chunk in
* that line.
*/
if ((rightX == rightX2) && (chunkPtr2 != NULL)) {
goto nextChunk2;
}
}
chunkPtr = chunkPtr->nextPtr;
if (chunkPtr == NULL) {
break;
}
rightX = chunkPtr->x + chunkPtr->width;
if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) {
rightX = maxX;
}
continue;
}
/*
* The chunk in the line above is ending at an x-position where
* there is no change in the style of the current line. If the
* style above matches the current line on one side of the change
* but not on the other, we have to draw an L-shaped piece of
* bevel.
*/
matchRight = (nextPtr2 != NULL)
&& SAME_BACKGROUND(nextPtr2->stylePtr, chunkPtr->stylePtr);
if (matchLeft && !matchRight) {
if (sValuePtr->relief != TK_RELIEF_FLAT) {
Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
rightX2 - sValuePtr->borderWidth + xOffset, 0,
sValuePtr->borderWidth, sValuePtr->borderWidth, 0,
sValuePtr->relief);
}
leftX = rightX2 - sValuePtr->borderWidth;
leftXIn = 0;
} else if (!matchLeft && matchRight
&& (sValuePtr->relief != TK_RELIEF_FLAT)) {
Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
rightX2 + xOffset, 0, sValuePtr->borderWidth,
sValuePtr->borderWidth, 1, sValuePtr->relief);
Tk_3DHorizontalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
leftX + xOffset, 0, rightX2 + sValuePtr->borderWidth -leftX,
sValuePtr->borderWidth, leftXIn, 0, 1,
sValuePtr->relief);
}
nextChunk2:
chunkPtr2 = nextPtr2;
if (chunkPtr2 == NULL) {
rightX2 = INT_MAX;
} else {
nextPtr2 = chunkPtr2->nextPtr;
rightX2 = chunkPtr2->x + chunkPtr2->width;
if (nextPtr2 == NULL) {
rightX2 = INT_MAX;
}
}
}
/*
* Pass 3: draw the horizontal bevels along the bottom of the line.
* This uses the same approach as pass 2.
*/
chunkPtr = dlPtr->chunkPtr;
leftX = 0; /* See Note A above. */
leftXIn = 0;
rightX = chunkPtr->x + chunkPtr->width;
if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) {
rightX = maxX;
}
chunkPtr2 = NULL;
if (dlPtr->nextPtr != NULL) {
/*
* Find the chunk in the previous line that covers leftX.
*/
nextPtr2 = dlPtr->nextPtr->chunkPtr;
rightX2 = 0; /* See Note A above. */
while (rightX2 <= leftX) {
chunkPtr2 = nextPtr2;
if (chunkPtr2 == NULL) {
break;
}
nextPtr2 = chunkPtr2->nextPtr;
rightX2 = chunkPtr2->x + chunkPtr2->width;
if (nextPtr2 == NULL) {
rightX2 = INT_MAX;
}
}
} else {
nextPtr2 = NULL;
rightX2 = INT_MAX;
}
while (leftX < maxX) {
matchLeft = (chunkPtr2 != NULL)
&& SAME_BACKGROUND(chunkPtr2->stylePtr, chunkPtr->stylePtr);
sValuePtr = chunkPtr->stylePtr->sValuePtr;
if (rightX <= rightX2) {
if ((chunkPtr->nextPtr == NULL)
|| !SAME_BACKGROUND(chunkPtr->stylePtr,
chunkPtr->nextPtr->stylePtr)) {
if (!matchLeft && (sValuePtr->relief != TK_RELIEF_FLAT)) {
Tk_3DHorizontalBevel(textPtr->tkwin, pixmap,
sValuePtr->border, leftX + xOffset,
dlPtr->height - sValuePtr->borderWidth,
rightX - leftX, sValuePtr->borderWidth, leftXIn,
0, 0, sValuePtr->relief);
}
leftX = rightX;
leftXIn = 0;
if ((rightX == rightX2) && (chunkPtr2 != NULL)) {
goto nextChunk2b;
}
}
chunkPtr = chunkPtr->nextPtr;
if (chunkPtr == NULL) {
break;
}
rightX = chunkPtr->x + chunkPtr->width;
if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) {
rightX = maxX;
}
continue;
}
matchRight = (nextPtr2 != NULL)
&& SAME_BACKGROUND(nextPtr2->stylePtr, chunkPtr->stylePtr);
if (matchLeft && !matchRight) {
if (sValuePtr->relief != TK_RELIEF_FLAT) {
Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
rightX2 - sValuePtr->borderWidth + xOffset,
dlPtr->height - sValuePtr->borderWidth,
sValuePtr->borderWidth, sValuePtr->borderWidth, 0,
sValuePtr->relief);
}
leftX = rightX2 - sValuePtr->borderWidth;
leftXIn = 1;
} else if (!matchLeft && matchRight
&& (sValuePtr->relief != TK_RELIEF_FLAT)) {
Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
rightX2 + xOffset, dlPtr->height - sValuePtr->borderWidth,
sValuePtr->borderWidth, sValuePtr->borderWidth,
1, sValuePtr->relief);
Tk_3DHorizontalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
leftX + xOffset, dlPtr->height - sValuePtr->borderWidth,
rightX2 + sValuePtr->borderWidth - leftX,
sValuePtr->borderWidth, leftXIn, 1, 0, sValuePtr->relief);
}
nextChunk2b:
chunkPtr2 = nextPtr2;
if (chunkPtr2 == NULL) {
rightX2 = INT_MAX;
} else {
nextPtr2 = chunkPtr2->nextPtr;
rightX2 = chunkPtr2->x + chunkPtr2->width;
if (nextPtr2 == NULL) {
rightX2 = INT_MAX;
}
}
}
}
/*
*----------------------------------------------------------------------
*
* DisplayText --
*
* This procedure is invoked as a when-idle handler to update the
* display. It only redisplays the parts of the text widget that
* are out of date.
*
* Results:
* None.
*
* Side effects:
* Information is redrawn on the screen.
*
*----------------------------------------------------------------------
*/
static void
DisplayText(clientData)
ClientData clientData; /* Information about widget. */
{
register TkText *textPtr = (TkText *) clientData;
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
Tk_Window tkwin;
register DLine *dlPtr;
DLine *prevPtr;
Pixmap pixmap;
int maxHeight, borders;
int bottomY = 0; /* Initialization needed only to stop
* compiler warnings. */
Tcl_Interp *interp;
if (textPtr->tkwin == NULL) {
/*
* The widget has been deleted. Don't do anything.
*/
return;
}
interp = textPtr->interp;
Tcl_Preserve((ClientData) interp);
if (tkTextDebug) {
Tcl_SetVar2(interp, "tk_textRelayout", (char *) NULL, "",
TCL_GLOBAL_ONLY);
}
if (textPtr->tkwin == NULL) {
/*
* The widget has been deleted. Don't do anything.
*/
goto end;
}
if (!Tk_IsMapped(textPtr->tkwin) || (dInfoPtr->maxX <= dInfoPtr->x)
|| (dInfoPtr->maxY <= dInfoPtr->y)) {
UpdateDisplayInfo(textPtr);
dInfoPtr->flags &= ~REDRAW_PENDING;
goto doScrollbars;
}
numRedisplays++;
if (tkTextDebug) {
Tcl_SetVar2(interp, "tk_textRedraw", (char *) NULL, "",
TCL_GLOBAL_ONLY);
}
if (textPtr->tkwin == NULL) {
/*
* The widget has been deleted. Don't do anything.
*/
goto end;
}
/*
* Choose a new current item if that is needed (this could cause
* event handlers to be invoked, hence the preserve/release calls
* and the loop, since the handlers could conceivably necessitate
* yet another current item calculation). The tkwin check is because
* the whole window could go away in the Tcl_Release call.
*/
while (dInfoPtr->flags & REPICK_NEEDED) {
Tcl_Preserve((ClientData) textPtr);
dInfoPtr->flags &= ~REPICK_NEEDED;
TkTextPickCurrent(textPtr, &textPtr->pickEvent);
tkwin = textPtr->tkwin;
Tcl_Release((ClientData) textPtr);
if (tkwin == NULL) {
goto end;
}
}
/*
* First recompute what's supposed to be displayed.
*/
UpdateDisplayInfo(textPtr);
dInfoPtr->dLinesInvalidated = 0;
/*
* See if it's possible to bring some parts of the screen up-to-date
* by scrolling (copying from other parts of the screen).
*/
for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) {
register DLine *dlPtr2;
int offset, height, y, oldY;
TkRegion damageRgn;
if ((dlPtr->oldY == -1) || (dlPtr->y == dlPtr->oldY)
|| ((dlPtr->oldY + dlPtr->height) > dInfoPtr->maxY)) {
continue;
}
/*
* This line is already drawn somewhere in the window so it only
* needs to be copied to its new location. See if there's a group
* of lines that can all be copied together.
*/
offset = dlPtr->y - dlPtr->oldY;
height = dlPtr->height;
y = dlPtr->y;
for (dlPtr2 = dlPtr->nextPtr; dlPtr2 != NULL;
dlPtr2 = dlPtr2->nextPtr) {
if ((dlPtr2->oldY == -1)
|| ((dlPtr2->oldY + offset) != dlPtr2->y)
|| ((dlPtr2->oldY + dlPtr2->height) > dInfoPtr->maxY)) {
break;
}
height += dlPtr2->height;
}
/*
* Reduce the height of the area being copied if necessary to
* avoid overwriting the border area.
*/
if ((y + height) > dInfoPtr->maxY) {
height = dInfoPtr->maxY -y;
}
oldY = dlPtr->oldY;
/*
* Update the lines we are going to scroll to show that they
* have been copied.
*/
while (1) {
dlPtr->oldY = dlPtr->y;
if (dlPtr->nextPtr == dlPtr2) {
break;
}
dlPtr = dlPtr->nextPtr;
}
/*
* Scan through the lines following the copied ones to see if
* we are going to overwrite them with the copy operation.
* If so, mark them for redisplay.
*/
for ( ; dlPtr2 != NULL; dlPtr2 = dlPtr2->nextPtr) {
if ((dlPtr2->oldY != -1)
&& ((dlPtr2->oldY + dlPtr2->height) > y)
&& (dlPtr2->oldY < (y + height))) {
dlPtr2->oldY = -1;
}
}
/*
* Now scroll the lines. This may generate damage which we
* handle by calling TextInvalidateRegion to mark the display
* blocks as stale.
*/
damageRgn = TkCreateRegion();
if (TkScrollWindow(textPtr->tkwin, dInfoPtr->scrollGC,
dInfoPtr->x, oldY,
(dInfoPtr->maxX - dInfoPtr->x), height,
0, y - oldY, damageRgn)) {
TextInvalidateRegion(textPtr, damageRgn);
}
numCopies++;
TkDestroyRegion(damageRgn);
}
/*
* Clear the REDRAW_PENDING flag here. This is actually pretty
* tricky. We want to wait until *after* doing the scrolling,
* since that could generate more areas to redraw and don't
* want to reschedule a redisplay for them. On the other hand,
* we can't wait until after all the redisplaying, because the
* act of redisplaying could actually generate more redisplays
* (e.g. in the case of a nested window with event bindings triggered
* by redisplay).
*/
dInfoPtr->flags &= ~REDRAW_PENDING;
/*
* Redraw the borders if that's needed.
*/
if (dInfoPtr->flags & REDRAW_BORDERS) {
if (tkTextDebug) {
Tcl_SetVar2(interp, "tk_textRedraw", (char *) NULL, "borders",
TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT);
}
if (textPtr->tkwin == NULL) {
/*
* The widget has been deleted. Don't do anything.
*/
goto end;
}
Tk_Draw3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),
textPtr->border, textPtr->highlightWidth,
textPtr->highlightWidth,
Tk_Width(textPtr->tkwin) - 2*textPtr->highlightWidth,
Tk_Height(textPtr->tkwin) - 2*textPtr->highlightWidth,
textPtr->borderWidth, textPtr->relief);
if (textPtr->highlightWidth != 0) {
GC gc;
if (textPtr->flags & GOT_FOCUS) {
gc = Tk_GCForColor(textPtr->highlightColorPtr,
Tk_WindowId(textPtr->tkwin));
} else {
gc = Tk_GCForColor(textPtr->highlightBgColorPtr,
Tk_WindowId(textPtr->tkwin));
}
Tk_DrawFocusHighlight(textPtr->tkwin, gc, textPtr->highlightWidth,
Tk_WindowId(textPtr->tkwin));
}
borders = textPtr->borderWidth + textPtr->highlightWidth;
if (textPtr->padY > 0) {
Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),
textPtr->border, borders, borders,
Tk_Width(textPtr->tkwin) - 2*borders, textPtr->padY,
0, TK_RELIEF_FLAT);
Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),
textPtr->border, borders,
Tk_Height(textPtr->tkwin) - borders - textPtr->padY,
Tk_Width(textPtr->tkwin) - 2*borders,
textPtr->padY, 0, TK_RELIEF_FLAT);
}
if (textPtr->padX > 0) {
Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),
textPtr->border, borders, borders + textPtr->padY,
textPtr->padX,
Tk_Height(textPtr->tkwin) - 2*borders -2*textPtr->padY,
0, TK_RELIEF_FLAT);
Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),
textPtr->border,
Tk_Width(textPtr->tkwin) - borders - textPtr->padX,
borders + textPtr->padY, textPtr->padX,
Tk_Height(textPtr->tkwin) - 2*borders -2*textPtr->padY,
0, TK_RELIEF_FLAT);
}
dInfoPtr->flags &= ~REDRAW_BORDERS;
}
/*
* Now we have to redraw the lines that couldn't be updated by
* scrolling. First, compute the height of the largest line and
* allocate an off-screen pixmap to use for double-buffered
* displays.
*/
maxHeight = -1;
for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
dlPtr = dlPtr->nextPtr) {
if ((dlPtr->height > maxHeight) && (dlPtr->oldY != dlPtr->y)) {
maxHeight = dlPtr->height;
}
bottomY = dlPtr->y + dlPtr->height;
}
if (maxHeight > dInfoPtr->maxY) {
maxHeight = dInfoPtr->maxY;
}
if (maxHeight > 0) {
pixmap = Tk_GetPixmap(Tk_Display(textPtr->tkwin),
Tk_WindowId(textPtr->tkwin), Tk_Width(textPtr->tkwin),
maxHeight, Tk_Depth(textPtr->tkwin));
for (prevPtr = NULL, dlPtr = textPtr->dInfoPtr->dLinePtr;
(dlPtr != NULL) && (dlPtr->y < dInfoPtr->maxY);
prevPtr = dlPtr, dlPtr = dlPtr->nextPtr) {
if (dlPtr->oldY != dlPtr->y) {
if (tkTextDebug) {
char string[TK_POS_CHARS];
TkTextPrintIndex(&dlPtr->index, string);
Tcl_SetVar2(textPtr->interp, "tk_textRedraw",
(char *) NULL, string,
TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT);
}
DisplayDLine(textPtr, dlPtr, prevPtr, pixmap);
if (dInfoPtr->dLinesInvalidated) {
Tk_FreePixmap(Tk_Display(textPtr->tkwin), pixmap);
return;
}
dlPtr->oldY = dlPtr->y;
dlPtr->flags &= ~NEW_LAYOUT;
}
}
Tk_FreePixmap(Tk_Display(textPtr->tkwin), pixmap);
}
/*
* See if we need to refresh the part of the window below the
* last line of text (if there is any such area). Refresh the
* padding area on the left too, since the insertion cursor might
* have been displayed there previously).
*/
if (dInfoPtr->topOfEof > dInfoPtr->maxY) {
dInfoPtr->topOfEof = dInfoPtr->maxY;
}
if (bottomY < dInfoPtr->topOfEof) {
if (tkTextDebug) {
Tcl_SetVar2(textPtr->interp, "tk_textRedraw",
(char *) NULL, "eof",
TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT);
}
if (textPtr->tkwin == NULL) {
/*
* The widget has been deleted. Don't do anything.
*/
goto end;
}
Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),
textPtr->border, dInfoPtr->x - textPtr->padX, bottomY,
dInfoPtr->maxX - (dInfoPtr->x - textPtr->padX),
dInfoPtr->topOfEof-bottomY, 0, TK_RELIEF_FLAT);
}
dInfoPtr->topOfEof = bottomY;
doScrollbars:
/*
* Update the vertical scrollbar, if there is one. Note: it's
* important to clear REDRAW_PENDING here, just in case the
* scroll procedure does something that requires redisplay.
*/
if (textPtr->flags & UPDATE_SCROLLBARS) {
textPtr->flags &= ~UPDATE_SCROLLBARS;
if (textPtr->yScrollCmd != NULL) {
GetYView(textPtr->interp, textPtr, 1);
}
if (textPtr->tkwin == NULL) {
/*
* The widget has been deleted. Don't do anything.
*/
goto end;
}
/*
* Update the horizontal scrollbar, if any.
*/
if (textPtr->xScrollCmd != NULL) {
GetXView(textPtr->interp, textPtr, 1);
}
}
end:
Tcl_Release((ClientData) interp);
}
/*
*----------------------------------------------------------------------
*
* TkTextEventuallyRepick --
*
* This procedure is invoked whenever something happens that
* could change the current character or the tags associated
* with it.
*
* Results:
* None.
*
* Side effects:
* A repick is scheduled as an idle handler.
*
*----------------------------------------------------------------------
*/
/* ARGSUSED */
void
TkTextEventuallyRepick(textPtr)
TkText *textPtr; /* Widget record for text widget. */
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
dInfoPtr->flags |= REPICK_NEEDED;
if (!(dInfoPtr->flags & REDRAW_PENDING)) {
dInfoPtr->flags |= REDRAW_PENDING;
Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);
}
}
/*
*----------------------------------------------------------------------
*
* TkTextRedrawRegion --
*
* This procedure is invoked to schedule a redisplay for a given
* region of a text widget. The redisplay itself may not occur
* immediately: it's scheduled as a when-idle handler.
*
* Results:
* None.
*
* Side effects:
* Information will eventually be redrawn on the screen.
*
*----------------------------------------------------------------------
*/
/* ARGSUSED */
void
TkTextRedrawRegion(textPtr, x, y, width, height)
TkText *textPtr; /* Widget record for text widget. */
int x, y; /* Coordinates of upper-left corner of area
* to be redrawn, in pixels relative to
* textPtr's window. */
int width, height; /* Width and height of area to be redrawn. */
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
TkRegion damageRgn = TkCreateRegion();
XRectangle rect;
rect.x = x;
rect.y = y;
rect.width = width;
rect.height = height;
TkUnionRectWithRegion(&rect, damageRgn, damageRgn);
TextInvalidateRegion(textPtr, damageRgn);
if (!(dInfoPtr->flags & REDRAW_PENDING)) {
dInfoPtr->flags |= REDRAW_PENDING;
Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);
}
TkDestroyRegion(damageRgn);
}
/*
*----------------------------------------------------------------------
*
* TextInvalidateRegion --
*
* Mark a region of text as invalid.
*
* Results:
* None.
*
* Side effects:
* Updates the display information for the text widget.
*
*----------------------------------------------------------------------
*/
static void
TextInvalidateRegion(textPtr, region)
TkText *textPtr; /* Widget record for text widget. */
TkRegion region; /* Region of area to redraw. */
{
register DLine *dlPtr;
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
int maxY, inset;
XRectangle rect;
/*
* Find all lines that overlap the given region and mark them for
* redisplay.
*/
TkClipBox(region, &rect);
maxY = rect.y + rect.height;
for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
dlPtr = dlPtr->nextPtr) {
if ((dlPtr->oldY != -1) && (TkRectInRegion(region, rect.x, dlPtr->y,
rect.width, (unsigned int) dlPtr->height) != RectangleOut)) {
dlPtr->oldY = -1;
}
}
if (dInfoPtr->topOfEof < maxY)