Parent Directory | Revision Log | Patch
revision 44 by dashley, Fri Oct 14 02:09:58 2016 UTC | revision 71 by dashley, Sat Nov 5 11:07:06 2016 UTC | |
---|---|---|
# | Line 1 | Line 1 |
/* $Header: /cvsroot/esrg/sfesrg/esrgpcpj/shared/tk_base/tktextdisp.c,v 1.1.1.1 2001/06/13 05:10:16 dtashley Exp $ */ | ||
/* | ||
* tkTextDisp.c -- | ||
* | ||
* This module provides facilities to display text widgets. It is | ||
* the only place where information is kept about the screen layout | ||
* of text widgets. | ||
* | ||
* Copyright (c) 1992-1994 The Regents of the University of California. | ||
* Copyright (c) 1994-1997 Sun Microsystems, Inc. | ||
* | ||
* See the file "license.terms" for information on usage and redistribution | ||
* of this file, and for a DISCLAIMER OF ALL WARRANTIES. | ||
* | ||
* RCS: @(#) $Id: tktextdisp.c,v 1.1.1.1 2001/06/13 05:10:16 dtashley Exp $ | ||
*/ | ||
#include "tkPort.h" | ||
#include "tkInt.h" | ||
#include "tkText.h" | ||
#ifdef __WIN32__ | ||
#include "tkWinInt.h" | ||
#endif | ||
/* | ||
* The following structure describes how to display a range of characters. | ||
* The information is generated by scanning all of the tags associated | ||
* with the characters and combining that with default information for | ||
* the overall widget. These structures form the hash keys for | ||
* dInfoPtr->styleTable. | ||
*/ | ||
typedef struct StyleValues { | ||
Tk_3DBorder border; /* Used for drawing background under text. | ||
* NULL means use widget background. */ | ||
int borderWidth; /* Width of 3-D border for background. */ | ||
int relief; /* 3-D relief for background. */ | ||
Pixmap bgStipple; /* Stipple bitmap for background. None | ||
* means draw solid. */ | ||
XColor *fgColor; /* Foreground color for text. */ | ||
Tk_Font tkfont; /* Font for displaying text. */ | ||
Pixmap fgStipple; /* Stipple bitmap for text and other | ||
* foreground stuff. None means draw | ||
* solid.*/ | ||
int justify; /* Justification style for text. */ | ||
int lMargin1; /* Left margin, in pixels, for first display | ||
* line of each text line. */ | ||
int lMargin2; /* Left margin, in pixels, for second and | ||
* later display lines of each text line. */ | ||
int offset; /* Offset in pixels of baseline, relative to | ||
* baseline of line. */ | ||
int overstrike; /* Non-zero means draw overstrike through | ||
* text. */ | ||
int rMargin; /* Right margin, in pixels. */ | ||
int spacing1; /* Spacing above first dline in text line. */ | ||
int spacing2; /* Spacing between lines of dline. */ | ||
int spacing3; /* Spacing below last dline in text line. */ | ||
TkTextTabArray *tabArrayPtr;/* Locations and types of tab stops (may | ||
* be NULL). */ | ||
int underline; /* Non-zero means draw underline underneath | ||
* text. */ | ||
int elide; /* Non-zero means draw text */ | ||
TkWrapMode wrapMode; /* How to handle wrap-around for this tag. | ||
* One of TEXT_WRAPMODE_CHAR, | ||
* TEXT_WRAPMODE_NONE or TEXT_WRAPMODE_WORD.*/ | ||
} StyleValues; | ||
/* | ||
* The following structure extends the StyleValues structure above with | ||
* graphics contexts used to actually draw the characters. The entries | ||
* in dInfoPtr->styleTable point to structures of this type. | ||
*/ | ||
typedef struct TextStyle { | ||
int refCount; /* Number of times this structure is | ||
* referenced in Chunks. */ | ||
GC bgGC; /* Graphics context for background. None | ||
* means use widget background. */ | ||
GC fgGC; /* Graphics context for foreground. */ | ||
StyleValues *sValuePtr; /* Raw information from which GCs were | ||
* derived. */ | ||
Tcl_HashEntry *hPtr; /* Pointer to entry in styleTable. Used | ||
* to delete entry. */ | ||
} TextStyle; | ||
/* | ||
* The following macro determines whether two styles have the same | ||
* background so that, for example, no beveled border should be drawn | ||
* between them. | ||
*/ | ||
#define SAME_BACKGROUND(s1, s2) \ | ||
(((s1)->sValuePtr->border == (s2)->sValuePtr->border) \ | ||
&& ((s1)->sValuePtr->borderWidth == (s2)->sValuePtr->borderWidth) \ | ||
&& ((s1)->sValuePtr->relief == (s2)->sValuePtr->relief) \ | ||
&& ((s1)->sValuePtr->bgStipple == (s2)->sValuePtr->bgStipple)) | ||
/* | ||
* The following structure describes one line of the display, which may | ||
* be either part or all of one line of the text. | ||
*/ | ||
typedef struct DLine { | ||
TkTextIndex index; /* Identifies first character in text | ||
* that is displayed on this line. */ | ||
int byteCount; /* Number of bytes accounted for by this | ||
* display line, including a trailing space | ||
* or newline that isn't actually displayed. */ | ||
int y; /* Y-position at which line is supposed to | ||
* be drawn (topmost pixel of rectangular | ||
* area occupied by line). */ | ||
int oldY; /* Y-position at which line currently | ||
* appears on display. -1 means line isn't | ||
* currently visible on display and must be | ||
* redrawn. This is used to move lines by | ||
* scrolling rather than re-drawing. */ | ||
int height; /* Height of line, in pixels. */ | ||
int baseline; /* Offset of text baseline from y, in | ||
* pixels. */ | ||
int spaceAbove; /* How much extra space was added to the | ||
* top of the line because of spacing | ||
* options. This is included in height | ||
* and baseline. */ | ||
int spaceBelow; /* How much extra space was added to the | ||
* bottom of the line because of spacing | ||
* options. This is included in height. */ | ||
int length; /* Total length of line, in pixels. */ | ||
TkTextDispChunk *chunkPtr; /* Pointer to first chunk in list of all | ||
* of those that are displayed on this | ||
* line of the screen. */ | ||
struct DLine *nextPtr; /* Next in list of all display lines for | ||
* this window. The list is sorted in | ||
* order from top to bottom. Note: the | ||
* next DLine doesn't always correspond | ||
* to the next line of text: (a) can have | ||
* multiple DLines for one text line, and | ||
* (b) can have gaps where DLine's have been | ||
* deleted because they're out of date. */ | ||
int flags; /* Various flag bits: see below for values. */ | ||
} DLine; | ||
/* | ||
* Flag bits for DLine structures: | ||
* | ||
* HAS_3D_BORDER - Non-zero means that at least one of the | ||
* chunks in this line has a 3D border, so | ||
* it potentially interacts with 3D borders | ||
* in neighboring lines (see | ||
* DisplayLineBackground). | ||
* NEW_LAYOUT - Non-zero means that the line has been | ||
* re-layed out since the last time the | ||
* display was updated. | ||
* TOP_LINE - Non-zero means that this was the top line | ||
* in the window the last time that the window | ||
* was laid out. This is important because | ||
* a line may be displayed differently if its | ||
* at the top or bottom than if it's in the | ||
* middle (e.g. beveled edges aren't displayed | ||
* for middle lines if the adjacent line has | ||
* a similar background). | ||
* BOTTOM_LINE - Non-zero means that this was the bottom line | ||
* in the window the last time that the window | ||
* was laid out. | ||
* IS_DISABLED - This Dline cannot be edited. | ||
*/ | ||
#define HAS_3D_BORDER 1 | ||
#define NEW_LAYOUT 2 | ||
#define TOP_LINE 4 | ||
#define BOTTOM_LINE 8 | ||
#define IS_DISABLED 16 | ||
/* | ||
* Overall display information for a text widget: | ||
*/ | ||
typedef struct TextDInfo { | ||
Tcl_HashTable styleTable; /* Hash table that maps from StyleValues | ||
* to TextStyles for this widget. */ | ||
DLine *dLinePtr; /* First in list of all display lines for | ||
* this widget, in order from top to bottom. */ | ||
GC copyGC; /* Graphics context for copying from off- | ||
* screen pixmaps onto screen. */ | ||
GC scrollGC; /* Graphics context for copying from one place | ||
* in the window to another (scrolling): | ||
* differs from copyGC in that we need to get | ||
* GraphicsExpose events. */ | ||
int x; /* First x-coordinate that may be used for | ||
* actually displaying line information. | ||
* Leaves space for border, etc. */ | ||
int y; /* First y-coordinate that may be used for | ||
* actually displaying line information. | ||
* Leaves space for border, etc. */ | ||
int maxX; /* First x-coordinate to right of available | ||
* space for displaying lines. */ | ||
int maxY; /* First y-coordinate below available | ||
* space for displaying lines. */ | ||
int topOfEof; /* Top-most pixel (lowest y-value) that has | ||
* been drawn in the appropriate fashion for | ||
* the portion of the window after the last | ||
* line of the text. This field is used to | ||
* figure out when to redraw part or all of | ||
* the eof field. */ | ||
/* | ||
* Information used for scrolling: | ||
*/ | ||
int newByteOffset; /* Desired x scroll position, measured as the | ||
* number of average-size characters off-screen | ||
* to the left for a line with no left | ||
* margin. */ | ||
int curPixelOffset; /* Actual x scroll position, measured as the | ||
* number of pixels off-screen to the left. */ | ||
int maxLength; /* Length in pixels of longest line that's | ||
* visible in window (length may exceed window | ||
* size). If there's no wrapping, this will | ||
* be zero. */ | ||
double xScrollFirst, xScrollLast; | ||
/* Most recent values reported to horizontal | ||
* scrollbar; used to eliminate unnecessary | ||
* reports. */ | ||
double yScrollFirst, yScrollLast; | ||
/* Most recent values reported to vertical | ||
* scrollbar; used to eliminate unnecessary | ||
* reports. */ | ||
/* | ||
* The following information is used to implement scanning: | ||
*/ | ||
int scanMarkIndex; /* Byte index of character that was at the | ||
* left edge of the window when the scan | ||
* started. */ | ||
int scanMarkX; /* X-position of mouse at time scan started. */ | ||
int scanTotalScroll; /* Total scrolling (in screen lines) that has | ||
* occurred since scanMarkY was set. */ | ||
int scanMarkY; /* Y-position of mouse at time scan started. */ | ||
/* | ||
* Miscellaneous information: | ||
*/ | ||
int dLinesInvalidated; /* This value is set to 1 whenever something | ||
* happens that invalidates information in | ||
* DLine structures; if a redisplay | ||
* is in progress, it will see this and | ||
* abort the redisplay. This is needed | ||
* because, for example, an embedded window | ||
* could change its size when it is first | ||
* displayed, invalidating the DLine that | ||
* is currently being displayed. If redisplay | ||
* continues, it will use freed memory and | ||
* could dump core. */ | ||
int flags; /* Various flag values: see below for | ||
* definitions. */ | ||
} TextDInfo; | ||
/* | ||
* In TkTextDispChunk structures for character segments, the clientData | ||
* field points to one of the following structures: | ||
*/ | ||
typedef struct CharInfo { | ||
int numBytes; /* Number of bytes to display. */ | ||
char chars[4]; /* UTF characters to display. Actual size | ||
* will be numBytes, not 4. THIS MUST BE | ||
* THE LAST FIELD IN THE STRUCTURE. */ | ||
} CharInfo; | ||
/* | ||
* Flag values for TextDInfo structures: | ||
* | ||
* DINFO_OUT_OF_DATE: Non-zero means that the DLine structures | ||
* for this window are partially or completely | ||
* out of date and need to be recomputed. | ||
* REDRAW_PENDING: Means that a when-idle handler has been | ||
* scheduled to update the display. | ||
* REDRAW_BORDERS: Means window border or pad area has | ||
* potentially been damaged and must be redrawn. | ||
* REPICK_NEEDED: 1 means that the widget has been modified | ||
* in a way that could change the current | ||
* character (a different character might be | ||
* under the mouse cursor now). Need to | ||
* recompute the current character before | ||
* the next redisplay. | ||
*/ | ||
#define DINFO_OUT_OF_DATE 1 | ||
#define REDRAW_PENDING 2 | ||
#define REDRAW_BORDERS 4 | ||
#define REPICK_NEEDED 8 | ||
/* | ||
* The following counters keep statistics about redisplay that can be | ||
* checked to see how clever this code is at reducing redisplays. | ||
*/ | ||
static int numRedisplays; /* Number of calls to DisplayText. */ | ||
static int linesRedrawn; /* Number of calls to DisplayDLine. */ | ||
static int numCopies; /* Number of calls to XCopyArea to copy part | ||
* of the screen. */ | ||
/* | ||
* Forward declarations for procedures defined later in this file: | ||
*/ | ||
static void AdjustForTab _ANSI_ARGS_((TkText *textPtr, | ||
TkTextTabArray *tabArrayPtr, int index, | ||
TkTextDispChunk *chunkPtr)); | ||
static void CharBboxProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr, | ||
int index, int y, int lineHeight, int baseline, | ||
int *xPtr, int *yPtr, int *widthPtr, | ||
int *heightPtr)); | ||
static void CharDisplayProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr, | ||
int x, int y, int height, int baseline, | ||
Display *display, Drawable dst, int screenY)); | ||
static int CharMeasureProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr, | ||
int x)); | ||
static void CharUndisplayProc _ANSI_ARGS_((TkText *textPtr, | ||
TkTextDispChunk *chunkPtr)); | ||
/* | ||
Definitions of elided procs. | ||
Compiler can't inline these since we use pointers to these functions. | ||
ElideDisplayProc, ElideUndisplayProc special-cased for speed, | ||
as potentially many elided DLine chunks if large, tag toggle-filled | ||
elided region. | ||
*/ | ||
static void ElideBboxProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr, | ||
int index, int y, int lineHeight, int baseline, | ||
int *xPtr, int *yPtr, int *widthPtr, | ||
int *heightPtr)); | ||
static int ElideMeasureProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr, | ||
int x)); | ||
static void DisplayDLine _ANSI_ARGS_((TkText *textPtr, | ||
DLine *dlPtr, DLine *prevPtr, Pixmap pixmap)); | ||
static void DisplayLineBackground _ANSI_ARGS_((TkText *textPtr, | ||
DLine *dlPtr, DLine *prevPtr, Pixmap pixmap)); | ||
static void DisplayText _ANSI_ARGS_((ClientData clientData)); | ||
static DLine * FindDLine _ANSI_ARGS_((DLine *dlPtr, | ||
TkTextIndex *indexPtr)); | ||
static void FreeDLines _ANSI_ARGS_((TkText *textPtr, | ||
DLine *firstPtr, DLine *lastPtr, int unlink)); | ||
static void FreeStyle _ANSI_ARGS_((TkText *textPtr, | ||
TextStyle *stylePtr)); | ||
static TextStyle * GetStyle _ANSI_ARGS_((TkText *textPtr, | ||
TkTextIndex *indexPtr)); | ||
static void GetXView _ANSI_ARGS_((Tcl_Interp *interp, | ||
TkText *textPtr, int report)); | ||
static void GetYView _ANSI_ARGS_((Tcl_Interp *interp, | ||
TkText *textPtr, int report)); | ||
static DLine * LayoutDLine _ANSI_ARGS_((TkText *textPtr, | ||
TkTextIndex *indexPtr)); | ||
static int MeasureChars _ANSI_ARGS_((Tk_Font tkfont, | ||
CONST char *source, int maxBytes, int startX, | ||
int maxX, int tabOrigin, int *nextXPtr)); | ||
static void MeasureUp _ANSI_ARGS_((TkText *textPtr, | ||
TkTextIndex *srcPtr, int distance, | ||
TkTextIndex *dstPtr)); | ||
static int NextTabStop _ANSI_ARGS_((Tk_Font tkfont, int x, | ||
int tabOrigin)); | ||
static void UpdateDisplayInfo _ANSI_ARGS_((TkText *textPtr)); | ||
static void ScrollByLines _ANSI_ARGS_((TkText *textPtr, | ||
int offset)); | ||
static int SizeOfTab _ANSI_ARGS_((TkText *textPtr, | ||
TkTextTabArray *tabArrayPtr, int index, int x, | ||
int maxX)); | ||
static void TextInvalidateRegion _ANSI_ARGS_((TkText *textPtr, | ||
TkRegion region)); | ||
/* | ||
*---------------------------------------------------------------------- | ||
* | ||
* TkTextCreateDInfo -- | ||
* | ||
* This procedure is called when a new text widget is created. | ||
* Its job is to set up display-related information for the widget. | ||
* | ||
* Results: | ||
* None. | ||
* | ||
* Side effects: | ||
* A TextDInfo data structure is allocated and initialized and attached | ||
* to textPtr. | ||
* | ||
*---------------------------------------------------------------------- | ||
*/ | ||
void | ||
TkTextCreateDInfo(textPtr) | ||
TkText *textPtr; /* Overall information for text widget. */ | ||
{ | ||
register TextDInfo *dInfoPtr; | ||
XGCValues gcValues; | ||
dInfoPtr = (TextDInfo *) ckalloc(sizeof(TextDInfo)); | ||
Tcl_InitHashTable(&dInfoPtr->styleTable, sizeof(StyleValues)/sizeof(int)); | ||
dInfoPtr->dLinePtr = NULL; | ||
dInfoPtr->copyGC = None; | ||
gcValues.graphics_exposures = True; | ||
dInfoPtr->scrollGC = Tk_GetGC(textPtr->tkwin, GCGraphicsExposures, | ||
&gcValues); | ||
dInfoPtr->topOfEof = 0; | ||
dInfoPtr->newByteOffset = 0; | ||
dInfoPtr->curPixelOffset = 0; | ||
dInfoPtr->maxLength = 0; | ||
dInfoPtr->xScrollFirst = -1; | ||
dInfoPtr->xScrollLast = -1; | ||
dInfoPtr->yScrollFirst = -1; | ||
dInfoPtr->yScrollLast = -1; | ||
dInfoPtr->scanMarkIndex = 0; | ||
dInfoPtr->scanMarkX = 0; | ||
dInfoPtr->scanTotalScroll = 0; | ||
dInfoPtr->scanMarkY = 0; | ||
dInfoPtr->dLinesInvalidated = 0; | ||
dInfoPtr->flags = DINFO_OUT_OF_DATE; | ||
textPtr->dInfoPtr = dInfoPtr; | ||
} | ||
/* | ||
*---------------------------------------------------------------------- | ||
* | ||
* TkTextFreeDInfo -- | ||
* | ||
* This procedure is called to free up all of the private display | ||
* information kept by this file for a text widget. | ||
* | ||
* Results: | ||
* None. | ||
* | ||
* Side effects: | ||
* Lots of resources get freed. | ||
* | ||
*---------------------------------------------------------------------- | ||
*/ | ||
void | ||
TkTextFreeDInfo(textPtr) | ||
TkText *textPtr; /* Overall information for text widget. */ | ||
{ | ||
register TextDInfo *dInfoPtr = textPtr->dInfoPtr; | ||
/* | ||
* Be careful to free up styleTable *after* freeing up all the | ||
* DLines, so that the hash table is still intact to free up the | ||
* style-related information from the lines. Once the lines are | ||
* all free then styleTable will be empty. | ||
*/ | ||
FreeDLines(textPtr, dInfoPtr->dLinePtr, (DLine *) NULL, 1); | ||
Tcl_DeleteHashTable(&dInfoPtr->styleTable); | ||
if (dInfoPtr->copyGC != None) { | ||
Tk_FreeGC(textPtr->display, dInfoPtr->copyGC); | ||
} | ||
Tk_FreeGC(textPtr->display, dInfoPtr->scrollGC); | ||
if (dInfoPtr->flags & REDRAW_PENDING) { | ||
Tcl_CancelIdleCall(DisplayText, (ClientData) textPtr); | ||
} | ||
ckfree((char *) dInfoPtr); | ||
} | ||
/* | ||
*---------------------------------------------------------------------- | ||
* | ||
* GetStyle -- | ||
* | ||
* This procedure creates all the information needed to display | ||
* text at a particular location. | ||
* | ||
* Results: | ||
* The return value is a pointer to a TextStyle structure that | ||
* corresponds to *sValuePtr. | ||
* | ||
* Side effects: | ||
* A new entry may be created in the style table for the widget. | ||
* | ||
*---------------------------------------------------------------------- | ||
*/ | ||
static TextStyle * | ||
GetStyle(textPtr, indexPtr) | ||
TkText *textPtr; /* Overall information about text widget. */ | ||
TkTextIndex *indexPtr; /* The character in the text for which | ||
* display information is wanted. */ | ||
{ | ||
TkTextTag **tagPtrs; | ||
register TkTextTag *tagPtr; | ||
StyleValues styleValues; | ||
TextStyle *stylePtr; | ||
Tcl_HashEntry *hPtr; | ||
int numTags, new, i; | ||
XGCValues gcValues; | ||
unsigned long mask; | ||
/* | ||
* The variables below keep track of the highest-priority specification | ||
* that has occurred for each of the various fields of the StyleValues. | ||
*/ | ||
int borderPrio, borderWidthPrio, reliefPrio, bgStipplePrio; | ||
int fgPrio, fontPrio, fgStipplePrio; | ||
int underlinePrio, elidePrio, justifyPrio, offsetPrio; | ||
int lMargin1Prio, lMargin2Prio, rMarginPrio; | ||
int spacing1Prio, spacing2Prio, spacing3Prio; | ||
int overstrikePrio, tabPrio, wrapPrio; | ||
/* | ||
* Find out what tags are present for the character, then compute | ||
* a StyleValues structure corresponding to those tags (scan | ||
* through all of the tags, saving information for the highest- | ||
* priority tag). | ||
*/ | ||
tagPtrs = TkBTreeGetTags(indexPtr, &numTags); | ||
borderPrio = borderWidthPrio = reliefPrio = bgStipplePrio = -1; | ||
fgPrio = fontPrio = fgStipplePrio = -1; | ||
underlinePrio = elidePrio = justifyPrio = offsetPrio = -1; | ||
lMargin1Prio = lMargin2Prio = rMarginPrio = -1; | ||
spacing1Prio = spacing2Prio = spacing3Prio = -1; | ||
overstrikePrio = tabPrio = wrapPrio = -1; | ||
memset((VOID *) &styleValues, 0, sizeof(StyleValues)); | ||
styleValues.relief = TK_RELIEF_FLAT; | ||
styleValues.fgColor = textPtr->fgColor; | ||
styleValues.tkfont = textPtr->tkfont; | ||
styleValues.justify = TK_JUSTIFY_LEFT; | ||
styleValues.spacing1 = textPtr->spacing1; | ||
styleValues.spacing2 = textPtr->spacing2; | ||
styleValues.spacing3 = textPtr->spacing3; | ||
styleValues.tabArrayPtr = textPtr->tabArrayPtr; | ||
styleValues.wrapMode = textPtr->wrapMode; | ||
styleValues.elide = 0; | ||
for (i = 0 ; i < numTags; i++) { | ||
tagPtr = tagPtrs[i]; | ||
/* | ||
* On Windows and Mac, we need to skip the selection tag if | ||
* we don't have focus. | ||
*/ | ||
#ifndef ALWAYS_SHOW_SELECTION | ||
if ((tagPtr == textPtr->selTagPtr) && !(textPtr->flags & GOT_FOCUS)) { | ||
continue; | ||
} | ||
#endif | ||
if ((tagPtr->border != NULL) && (tagPtr->priority > borderPrio)) { | ||
styleValues.border = tagPtr->border; | ||
borderPrio = tagPtr->priority; | ||
} | ||
if ((tagPtr->bdString != NULL) | ||
&& (tagPtr->priority > borderWidthPrio)) { | ||
styleValues.borderWidth = tagPtr->borderWidth; | ||
borderWidthPrio = tagPtr->priority; | ||
} | ||
if ((tagPtr->reliefString != NULL) | ||
&& (tagPtr->priority > reliefPrio)) { | ||
if (styleValues.border == NULL) { | ||
styleValues.border = textPtr->border; | ||
} | ||
styleValues.relief = tagPtr->relief; | ||
reliefPrio = tagPtr->priority; | ||
} | ||
if ((tagPtr->bgStipple != None) | ||
&& (tagPtr->priority > bgStipplePrio)) { | ||
styleValues.bgStipple = tagPtr->bgStipple; | ||
bgStipplePrio = tagPtr->priority; | ||
} | ||
if ((tagPtr->fgColor != None) && (tagPtr->priority > fgPrio)) { | ||
styleValues.fgColor = tagPtr->fgColor; | ||
fgPrio = tagPtr->priority; | ||
} | ||
if ((tagPtr->tkfont != None) && (tagPtr->priority > fontPrio)) { | ||
styleValues.tkfont = tagPtr->tkfont; | ||
fontPrio = tagPtr->priority; | ||
} | ||
if ((tagPtr->fgStipple != None) | ||
&& (tagPtr->priority > fgStipplePrio)) { | ||
styleValues.fgStipple = tagPtr->fgStipple; | ||
fgStipplePrio = tagPtr->priority; | ||
} | ||
if ((tagPtr->justifyString != NULL) | ||
&& (tagPtr->priority > justifyPrio)) { | ||
styleValues.justify = tagPtr->justify; | ||
justifyPrio = tagPtr->priority; | ||
} | ||
if ((tagPtr->lMargin1String != NULL) | ||
&& (tagPtr->priority > lMargin1Prio)) { | ||
styleValues.lMargin1 = tagPtr->lMargin1; | ||
lMargin1Prio = tagPtr->priority; | ||
} | ||
if ((tagPtr->lMargin2String != NULL) | ||
&& (tagPtr->priority > lMargin2Prio)) { | ||
styleValues.lMargin2 = tagPtr->lMargin2; | ||
lMargin2Prio = tagPtr->priority; | ||
} | ||
if ((tagPtr->offsetString != NULL) | ||
&& (tagPtr->priority > offsetPrio)) { | ||
styleValues.offset = tagPtr->offset; | ||
offsetPrio = tagPtr->priority; | ||
} | ||
if ((tagPtr->overstrikeString != NULL) | ||
&& (tagPtr->priority > overstrikePrio)) { | ||
styleValues.overstrike = tagPtr->overstrike; | ||
overstrikePrio = tagPtr->priority; | ||
} | ||
if ((tagPtr->rMarginString != NULL) | ||
&& (tagPtr->priority > rMarginPrio)) { | ||
styleValues.rMargin = tagPtr->rMargin; | ||
rMarginPrio = tagPtr->priority; | ||
} | ||
if ((tagPtr->spacing1String != NULL) | ||
&& (tagPtr->priority > spacing1Prio)) { | ||
styleValues.spacing1 = tagPtr->spacing1; | ||
spacing1Prio = tagPtr->priority; | ||
} | ||
if ((tagPtr->spacing2String != NULL) | ||
&& (tagPtr->priority > spacing2Prio)) { | ||
styleValues.spacing2 = tagPtr->spacing2; | ||
spacing2Prio = tagPtr->priority; | ||
} | ||
if ((tagPtr->spacing3String != NULL) | ||
&& (tagPtr->priority > spacing3Prio)) { | ||
styleValues.spacing3 = tagPtr->spacing3; | ||
spacing3Prio = tagPtr->priority; | ||
} | ||
if ((tagPtr->tabString != NULL) | ||
&& (tagPtr->priority > tabPrio)) { | ||
styleValues.tabArrayPtr = tagPtr->tabArrayPtr; | ||
tabPrio = tagPtr->priority; | ||
} | ||
if ((tagPtr->underlineString != NULL) | ||
&& (tagPtr->priority > underlinePrio)) { | ||
styleValues.underline = tagPtr->underline; | ||
underlinePrio = tagPtr->priority; | ||
} | ||
if ((tagPtr->elideString != NULL) | ||
&& (tagPtr->priority > elidePrio)) { | ||
styleValues.elide = tagPtr->elide; | ||
elidePrio = tagPtr->priority; | ||
} | ||
if ((tagPtr->wrapMode != TEXT_WRAPMODE_NULL) | ||
&& (tagPtr->priority > wrapPrio)) { | ||
styleValues.wrapMode = tagPtr->wrapMode; | ||
wrapPrio = tagPtr->priority; | ||
} | ||
} | ||
if (tagPtrs != NULL) { | ||
ckfree((char *) tagPtrs); | ||
} | ||
/* | ||
* Use an existing style if there's one around that matches. | ||
*/ | ||
hPtr = Tcl_CreateHashEntry(&textPtr->dInfoPtr->styleTable, | ||
(char *) &styleValues, &new); | ||
if (!new) { | ||
stylePtr = (TextStyle *) Tcl_GetHashValue(hPtr); | ||
stylePtr->refCount++; | ||
return stylePtr; | ||
} | ||
/* | ||
* No existing style matched. Make a new one. | ||
*/ | ||
stylePtr = (TextStyle *) ckalloc(sizeof(TextStyle)); | ||
stylePtr->refCount = 1; | ||
if (styleValues.border != NULL) { | ||
gcValues.foreground = Tk_3DBorderColor(styleValues.border)->pixel; | ||
mask = GCForeground; | ||
if (styleValues.bgStipple != None) { | ||
gcValues.stipple = styleValues.bgStipple; | ||
gcValues.fill_style = FillStippled; | ||
mask |= GCStipple|GCFillStyle; | ||
} | ||
stylePtr->bgGC = Tk_GetGC(textPtr->tkwin, mask, &gcValues); | ||
} else { | ||
stylePtr->bgGC = None; | ||
} | ||
mask = GCFont; | ||
gcValues.font = Tk_FontId(styleValues.tkfont); | ||
mask |= GCForeground; | ||
gcValues.foreground = styleValues.fgColor->pixel; | ||
if (styleValues.fgStipple != None) { | ||
gcValues.stipple = styleValues.fgStipple; | ||
gcValues.fill_style = FillStippled; | ||
mask |= GCStipple|GCFillStyle; | ||
} | ||
stylePtr->fgGC = Tk_GetGC(textPtr->tkwin, mask, &gcValues); | ||
stylePtr->sValuePtr = (StyleValues *) | ||
Tcl_GetHashKey(&textPtr->dInfoPtr->styleTable, hPtr); | ||
stylePtr->hPtr = hPtr; | ||
Tcl_SetHashValue(hPtr, stylePtr); | ||
return stylePtr; | ||
} | ||
/* | ||
*---------------------------------------------------------------------- | ||
* | ||
* FreeStyle -- | ||
* | ||
* This procedure is called when a TextStyle structure is no longer | ||
* needed. It decrements the reference count and frees up the | ||
* space for the style structure if the reference count is 0. | ||
* | ||
* Results: | ||
* None. | ||
* | ||
* Side effects: | ||
* The storage and other resources associated with the style | ||
* are freed up if no-one's still using it. | ||
* | ||
*---------------------------------------------------------------------- | ||
*/ | ||
static void | ||
FreeStyle(textPtr, stylePtr) | ||
TkText *textPtr; /* Information about overall widget. */ | ||
register TextStyle *stylePtr; /* Information about style to free. */ | ||
{ | ||
stylePtr->refCount--; | ||
if (stylePtr->refCount == 0) { | ||
if (stylePtr->bgGC != None) { | ||
Tk_FreeGC(textPtr->display, stylePtr->bgGC); | ||
} | ||
if (stylePtr->fgGC != None) { | ||
Tk_FreeGC(textPtr->display, stylePtr->fgGC); | ||
} | ||
Tcl_DeleteHashEntry(stylePtr->hPtr); | ||
ckfree((char *) stylePtr); | ||
} | ||
} | ||
/* | ||
*---------------------------------------------------------------------- | ||
* | ||
* LayoutDLine -- | ||
* | ||
* This procedure generates a single DLine structure for a display | ||
* line whose leftmost character is given by indexPtr. | ||
* | ||
* Results: | ||
* The return value is a pointer to a DLine structure desribing the | ||
* display line. All fields are filled in and correct except for | ||
* y and nextPtr. | ||
* | ||
* Side effects: | ||
* Storage is allocated for the new DLine. | ||
* | ||
*---------------------------------------------------------------------- | ||
*/ | ||
static DLine * | ||
LayoutDLine(textPtr, indexPtr) | ||
TkText *textPtr; /* Overall information about text widget. */ | ||
TkTextIndex *indexPtr; /* Beginning of display line. May not | ||
* necessarily point to a character segment. */ | ||
{ | ||
register DLine *dlPtr; /* New display line. */ | ||
TkTextSegment *segPtr; /* Current segment in text. */ | ||
TkTextDispChunk *lastChunkPtr; /* Last chunk allocated so far | ||
* for line. */ | ||
TkTextDispChunk *chunkPtr; /* Current chunk. */ | ||
TkTextIndex curIndex; | ||
TkTextDispChunk *breakChunkPtr; /* Chunk containing best word break | ||
* point, if any. */ | ||
TkTextIndex breakIndex; /* Index of first character in | ||
* breakChunkPtr. */ | ||
int breakByteOffset; /* Byte offset of character within | ||
* breakChunkPtr just to right of best | ||
* break point. */ | ||
int noCharsYet; /* Non-zero means that no characters | ||
* have been placed on the line yet. */ | ||
int justify; /* How to justify line: taken from | ||
* style for the first character in | ||
* line. */ | ||
int jIndent; /* Additional indentation (beyond | ||
* margins) due to justification. */ | ||
int rMargin; /* Right margin width for line. */ | ||
TkWrapMode wrapMode; /* Wrap mode to use for this line. */ | ||
int x = 0, maxX = 0; /* Initializations needed only to | ||
* stop compiler warnings. */ | ||
int wholeLine; /* Non-zero means this display line | ||
* runs to the end of the text line. */ | ||
int tabIndex; /* Index of the current tab stop. */ | ||
int gotTab; /* Non-zero means the current chunk | ||
* contains a tab. */ | ||
TkTextDispChunk *tabChunkPtr; /* Pointer to the chunk containing | ||
* the previous tab stop. */ | ||
int maxBytes; /* Maximum number of bytes to | ||
* include in this chunk. */ | ||
TkTextTabArray *tabArrayPtr; /* Tab stops for line; taken from | ||
* style for the first character on | ||
* line. */ | ||
int tabSize; /* Number of pixels consumed by current | ||
* tab stop. */ | ||
TkTextDispChunk *lastCharChunkPtr; /* Pointer to last chunk in display | ||
* lines with numBytes > 0. Used to | ||
* drop 0-sized chunks from the end | ||
* of the line. */ | ||
int byteOffset, ascent, descent, code, elide, elidesize; | ||
StyleValues *sValuePtr; | ||
/* | ||
* Create and initialize a new DLine structure. | ||
*/ | ||
dlPtr = (DLine *) ckalloc(sizeof(DLine)); | ||
dlPtr->index = *indexPtr; | ||
dlPtr->byteCount = 0; | ||
dlPtr->y = 0; | ||
dlPtr->oldY = -1; | ||
dlPtr->height = 0; | ||
dlPtr->baseline = 0; | ||
dlPtr->chunkPtr = NULL; | ||
dlPtr->nextPtr = NULL; | ||
dlPtr->flags = NEW_LAYOUT; | ||
/* | ||
* Special case entirely elide line as there may be 1000s or more | ||
*/ | ||
elide = TkTextIsElided(textPtr, indexPtr); /* save a malloc */ | ||
if (elide && indexPtr->byteIndex==0) { | ||
maxBytes = 0; | ||
for (segPtr = indexPtr->linePtr->segPtr; | ||
elide && (segPtr != NULL); | ||
segPtr = segPtr->nextPtr) { | ||
if ((elidesize = segPtr->size) > 0) { | ||
maxBytes += elidesize; | ||
/* | ||
* If have we have a tag toggle, there is a chance | ||
* that invisibility state changed, so bail out | ||
*/ | ||
} else if ((segPtr->typePtr == &tkTextToggleOffType) | ||
|| (segPtr->typePtr == &tkTextToggleOnType)) { | ||
if (segPtr->body.toggle.tagPtr->elideString != NULL) { | ||
elide = (segPtr->typePtr == &tkTextToggleOffType) | ||
^ segPtr->body.toggle.tagPtr->elide; | ||
} | ||
} | ||
} | ||
if (elide) { | ||
dlPtr->byteCount = maxBytes; | ||
dlPtr->spaceAbove = dlPtr->spaceBelow = dlPtr->length = 0; | ||
return dlPtr; | ||
} | ||
} | ||
/* | ||
* Each iteration of the loop below creates one TkTextDispChunk for | ||
* the new display line. The line will always have at least one | ||
* chunk (for the newline character at the end, if there's nothing | ||
* else available). | ||
*/ | ||
curIndex = *indexPtr; | ||
lastChunkPtr = NULL; | ||
chunkPtr = NULL; | ||
noCharsYet = 1; | ||
elide = 0; | ||
breakChunkPtr = NULL; | ||
breakByteOffset = 0; | ||
justify = TK_JUSTIFY_LEFT; | ||
tabIndex = -1; | ||
tabChunkPtr = NULL; | ||
tabArrayPtr = NULL; | ||
rMargin = 0; | ||
wrapMode = TEXT_WRAPMODE_CHAR; | ||
tabSize = 0; | ||
lastCharChunkPtr = NULL; | ||
/* | ||
* Find the first segment to consider for the line. Can't call | ||
* TkTextIndexToSeg for this because it won't return a segment | ||
* with zero size (such as the insertion cursor's mark). | ||
*/ | ||
for (byteOffset = curIndex.byteIndex, segPtr = curIndex.linePtr->segPtr; | ||
(byteOffset > 0) && (byteOffset >= segPtr->size); | ||
byteOffset -= segPtr->size, segPtr = segPtr->nextPtr) { | ||
/* Empty loop body. */ | ||
} | ||
while (segPtr != NULL) { | ||
/* | ||
* Every line still gets at least one chunk due to expectations | ||
* in the rest of the code, but we are able to skip elided portions | ||
* of the line quickly. | ||
* If current chunk is elided and last chunk was too, coalese | ||
*/ | ||
if (elide && (lastChunkPtr != NULL) | ||
&& (lastChunkPtr->displayProc == NULL /*ElideDisplayProc*/)) { | ||
if ((elidesize = segPtr->size - byteOffset) > 0) { | ||
curIndex.byteIndex += elidesize; | ||
lastChunkPtr->numBytes += elidesize; | ||
breakByteOffset = lastChunkPtr->breakIndex = lastChunkPtr->numBytes; | ||
/* | ||
* If have we have a tag toggle, there is a chance | ||
* that invisibility state changed, so bail out | ||
*/ | ||
} else if ((segPtr->typePtr == &tkTextToggleOffType) | ||
|| (segPtr->typePtr == &tkTextToggleOnType)) { | ||
if (segPtr->body.toggle.tagPtr->elideString != NULL) { | ||
elide = (segPtr->typePtr == &tkTextToggleOffType) | ||
^ segPtr->body.toggle.tagPtr->elide; | ||
} | ||
} | ||
byteOffset = 0; | ||
segPtr = segPtr->nextPtr; | ||
if (segPtr == NULL && chunkPtr != NULL) { | ||
ckfree((char *) chunkPtr); | ||
} | ||
continue; | ||
} | ||
if (segPtr->typePtr->layoutProc == NULL) { | ||
segPtr = segPtr->nextPtr; | ||
byteOffset = 0; | ||
continue; | ||
} | ||
if (chunkPtr == NULL) { | ||
chunkPtr = (TkTextDispChunk *) ckalloc(sizeof(TkTextDispChunk)); | ||
chunkPtr->nextPtr = NULL; | ||
} | ||
chunkPtr->stylePtr = GetStyle(textPtr, &curIndex); | ||
elide = chunkPtr->stylePtr->sValuePtr->elide; | ||
/* | ||
* Save style information such as justification and indentation, | ||
* up until the first character is encountered, then retain that | ||
* information for the rest of the line. | ||
*/ | ||
if (noCharsYet) { | ||
tabArrayPtr = chunkPtr->stylePtr->sValuePtr->tabArrayPtr; | ||
justify = chunkPtr->stylePtr->sValuePtr->justify; | ||
rMargin = chunkPtr->stylePtr->sValuePtr->rMargin; | ||
wrapMode = chunkPtr->stylePtr->sValuePtr->wrapMode; | ||
x = ((curIndex.byteIndex == 0) | ||
? chunkPtr->stylePtr->sValuePtr->lMargin1 | ||
: chunkPtr->stylePtr->sValuePtr->lMargin2); | ||
if (wrapMode == TEXT_WRAPMODE_NONE) { | ||
maxX = -1; | ||
} else { | ||
maxX = textPtr->dInfoPtr->maxX - textPtr->dInfoPtr->x | ||
- rMargin; | ||
if (maxX < x) { | ||
maxX = x; | ||
} | ||
} | ||
} | ||
/* | ||
* See if there is a tab in the current chunk; if so, only | ||
* layout characters up to (and including) the tab. | ||
*/ | ||
gotTab = 0; | ||
maxBytes = segPtr->size - byteOffset; | ||
if (!elide && justify == TK_JUSTIFY_LEFT) { | ||
if (segPtr->typePtr == &tkTextCharType) { | ||
char *p; | ||
for (p = segPtr->body.chars + byteOffset; *p != 0; p++) { | ||
if (*p == '\t') { | ||
maxBytes = (p + 1 - segPtr->body.chars) - byteOffset; | ||
gotTab = 1; | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
chunkPtr->x = x; | ||
if (elide && maxBytes) { | ||
/* don't free style here, as other code expects to be able to do that */ | ||
/*breakByteOffset =*/ chunkPtr->breakIndex = chunkPtr->numBytes = maxBytes; | ||
chunkPtr->width = 0; | ||
chunkPtr->minAscent = chunkPtr->minDescent = chunkPtr->minHeight = 0; | ||
/* would just like to point to canonical empty chunk */ | ||
chunkPtr->displayProc = (Tk_ChunkDisplayProc *) NULL; | ||
chunkPtr->undisplayProc = (Tk_ChunkUndisplayProc *) NULL; | ||
chunkPtr->measureProc = ElideMeasureProc; | ||
chunkPtr->bboxProc = ElideBboxProc; | ||
code = 1; | ||
} else | ||
code = (*segPtr->typePtr->layoutProc)(textPtr, &curIndex, segPtr, | ||
byteOffset, maxX-tabSize, maxBytes, noCharsYet, wrapMode, | ||
chunkPtr); | ||
if (code <= 0) { | ||
FreeStyle(textPtr, chunkPtr->stylePtr); | ||
if (code < 0) { | ||
/* | ||
* This segment doesn't wish to display itself (e.g. most | ||
* marks). | ||
*/ | ||
segPtr = segPtr->nextPtr; | ||
byteOffset = 0; | ||
continue; | ||
} | ||
/* | ||
* No characters from this segment fit in the window: this | ||
* means we're at the end of the display line. | ||
*/ | ||
if (chunkPtr != NULL) { | ||
ckfree((char *) chunkPtr); | ||
} | ||
break; | ||
} | ||
if (chunkPtr->numBytes > 0) { | ||
noCharsYet = 0; | ||
lastCharChunkPtr = chunkPtr; | ||
} | ||
if (lastChunkPtr == NULL) { | ||
dlPtr->chunkPtr = chunkPtr; | ||
} else { | ||
lastChunkPtr->nextPtr = chunkPtr; | ||
} | ||
lastChunkPtr = chunkPtr; | ||
x += chunkPtr->width; | ||
if (chunkPtr->breakIndex > 0) { | ||
breakByteOffset = chunkPtr->breakIndex; | ||
breakIndex = curIndex; | ||
breakChunkPtr = chunkPtr; | ||
} | ||
if (chunkPtr->numBytes != maxBytes) { | ||
break; | ||
} | ||
/* | ||
* If we're at a new tab, adjust the layout for all the chunks | ||
* pertaining to the previous tab. Also adjust the amount of | ||
* space left in the line to account for space that will be eaten | ||
* up by the tab. | ||
*/ | ||
if (gotTab) { | ||
if (tabIndex >= 0) { | ||
AdjustForTab(textPtr, tabArrayPtr, tabIndex, tabChunkPtr); | ||
x = chunkPtr->x + chunkPtr->width; | ||
} | ||
tabIndex++; | ||
tabChunkPtr = chunkPtr; | ||
tabSize = SizeOfTab(textPtr, tabArrayPtr, tabIndex, x, maxX); | ||
if ((maxX >= 0) && (tabSize >= maxX - x)) { | ||
break; | ||
} | ||
} | ||
curIndex.byteIndex += chunkPtr->numBytes; | ||
byteOffset += chunkPtr->numBytes; | ||
if (byteOffset >= segPtr->size) { | ||
byteOffset = 0; | ||
segPtr = segPtr->nextPtr; | ||
} | ||
chunkPtr = NULL; | ||
} | ||
if (noCharsYet) { | ||
panic("LayoutDLine couldn't place any characters on a line"); | ||
} | ||
wholeLine = (segPtr == NULL); | ||
/* | ||
* We're at the end of the display line. Throw away everything | ||
* after the most recent word break, if there is one; this may | ||
* potentially require the last chunk to be layed out again. | ||
*/ | ||
if (breakChunkPtr == NULL) { | ||
/* | ||
* This code makes sure that we don't accidentally display | ||
* chunks with no characters at the end of the line (such as | ||
* the insertion cursor). These chunks belong on the next | ||
* line. So, throw away everything after the last chunk that | ||
* has characters in it. | ||
*/ | ||
breakChunkPtr = lastCharChunkPtr; | ||
breakByteOffset = breakChunkPtr->numBytes; | ||
} | ||
if ((breakChunkPtr != NULL) && ((lastChunkPtr != breakChunkPtr) | ||
|| (breakByteOffset != lastChunkPtr->numBytes))) { | ||
while (1) { | ||
chunkPtr = breakChunkPtr->nextPtr; | ||
if (chunkPtr == NULL) { | ||
break; | ||
} | ||
FreeStyle(textPtr, chunkPtr->stylePtr); | ||
breakChunkPtr->nextPtr = chunkPtr->nextPtr; | ||
(*chunkPtr->undisplayProc)(textPtr, chunkPtr); | ||
ckfree((char *) chunkPtr); | ||
} | ||
if (breakByteOffset != breakChunkPtr->numBytes) { | ||
(*breakChunkPtr->undisplayProc)(textPtr, breakChunkPtr); | ||
segPtr = TkTextIndexToSeg(&breakIndex, &byteOffset); | ||
(*segPtr->typePtr->layoutProc)(textPtr, &breakIndex, | ||
segPtr, byteOffset, maxX, breakByteOffset, 0, | ||
wrapMode, breakChunkPtr); | ||
} | ||
lastChunkPtr = breakChunkPtr; | ||
wholeLine = 0; | ||
} | ||
/* | ||
* Make tab adjustments for the last tab stop, if there is one. | ||
*/ | ||
if ((tabIndex >= 0) && (tabChunkPtr != NULL)) { | ||
AdjustForTab(textPtr, tabArrayPtr, tabIndex, tabChunkPtr); | ||
} | ||
/* | ||
* Make one more pass over the line to recompute various things | ||
* like its height, length, and total number of bytes. Also | ||
* modify the x-locations of chunks to reflect justification. | ||
* If we're not wrapping, I'm not sure what is the best way to | ||
* handle left and center justification: should the total length, | ||
* for purposes of justification, be (a) the window width, (b) | ||
* the length of the longest line in the window, or (c) the length | ||
* of the longest line in the text? (c) isn't available, (b) seems | ||
* weird, since it can change with vertical scrolling, so (a) is | ||
* what is implemented below. | ||
*/ | ||
if (wrapMode == TEXT_WRAPMODE_NONE) { | ||
maxX = textPtr->dInfoPtr->maxX - textPtr->dInfoPtr->x - rMargin; | ||
} | ||
dlPtr->length = lastChunkPtr->x + lastChunkPtr->width; | ||
if (justify == TK_JUSTIFY_LEFT) { | ||
jIndent = 0; | ||
} else if (justify == TK_JUSTIFY_RIGHT) { | ||
jIndent = maxX - dlPtr->length; | ||
} else { | ||
jIndent = (maxX - dlPtr->length)/2; | ||
} | ||
ascent = descent = 0; | ||
for (chunkPtr = dlPtr->chunkPtr; chunkPtr != NULL; | ||
chunkPtr = chunkPtr->nextPtr) { | ||
chunkPtr->x += jIndent; | ||
dlPtr->byteCount += chunkPtr->numBytes; | ||
if (chunkPtr->minAscent > ascent) { | ||
ascent = chunkPtr->minAscent; | ||
} | ||
if (chunkPtr->minDescent > descent) { | ||
descent = chunkPtr->minDescent; | ||
} | ||
if (chunkPtr->minHeight > dlPtr->height) { | ||
dlPtr->height = chunkPtr->minHeight; | ||
} | ||
sValuePtr = chunkPtr->stylePtr->sValuePtr; | ||
if ((sValuePtr->borderWidth > 0) | ||
&& (sValuePtr->relief != TK_RELIEF_FLAT)) { | ||
dlPtr->flags |= HAS_3D_BORDER; | ||
} | ||
} | ||
if (dlPtr->height < (ascent + descent)) { | ||
dlPtr->height = ascent + descent; | ||
dlPtr->baseline = ascent; | ||
} else { | ||
dlPtr->baseline = ascent + (dlPtr->height - ascent - descent)/2; | ||
} | ||
sValuePtr = dlPtr->chunkPtr->stylePtr->sValuePtr; | ||
if (dlPtr->index.byteIndex == 0) { | ||
dlPtr->spaceAbove = sValuePtr->spacing1; | ||
} else { | ||
dlPtr->spaceAbove = sValuePtr->spacing2 - sValuePtr->spacing2/2; | ||
} | ||
if (wholeLine) { | ||
dlPtr->spaceBelow = sValuePtr->spacing3; | ||
} else { | ||
dlPtr->spaceBelow = sValuePtr->spacing2/2; | ||
} | ||
dlPtr->height += dlPtr->spaceAbove + dlPtr->spaceBelow; | ||
dlPtr->baseline += dlPtr->spaceAbove; | ||
/* | ||
* Recompute line length: may have changed because of justification. | ||
*/ | ||
dlPtr->length = lastChunkPtr->x + lastChunkPtr->width; | ||
return dlPtr; | ||
} | ||
/* | ||
*---------------------------------------------------------------------- | ||
* | ||
* UpdateDisplayInfo -- | ||
* | ||
* This procedure is invoked to recompute some or all of the | ||
* DLine structures for a text widget. At the time it is called | ||
* the DLine structures still left in the widget are guaranteed | ||
* to be correct except that (a) the y-coordinates aren't | ||
* necessarily correct, (b) there may be missing structures | ||
* (the DLine structures get removed as soon as they are potentially | ||
* out-of-date), and (c) DLine structures that don't start at the | ||
* beginning of a line may be incorrect if previous information in | ||
* the same line changed size in a way that moved a line boundary | ||
* (DLines for any info that changed will have been deleted, but | ||
* not DLines for unchanged info in the same text line). | ||
* | ||
* Results: | ||
* None. | ||
* | ||
* Side effects: | ||
* Upon return, the DLine information for textPtr correctly reflects | ||
* the positions where characters will be displayed. However, this | ||
* procedure doesn't actually bring the display up-to-date. | ||
* | ||
*---------------------------------------------------------------------- | ||
*/ | ||
static void | ||
UpdateDisplayInfo(textPtr) | ||
TkText *textPtr; /* Text widget to update. */ | ||
{ | ||
register TextDInfo *dInfoPtr = textPtr->dInfoPtr; | ||
register DLine *dlPtr, *prevPtr; | ||
TkTextIndex index; | ||
TkTextLine *lastLinePtr; | ||
int y, maxY, pixelOffset, maxOffset; | ||
if (!(dInfoPtr->flags & DINFO_OUT_OF_DATE)) { | ||
return; | ||
} | ||
dInfoPtr->flags &= ~DINFO_OUT_OF_DATE; | ||
/* | ||
* Delete any DLines that are now above the top of the window. | ||
*/ | ||
index = textPtr->topIndex; | ||
dlPtr = FindDLine(dInfoPtr->dLinePtr, &index); | ||
if ((dlPtr != NULL) && (dlPtr != dInfoPtr->dLinePtr)) { | ||
FreeDLines(textPtr, dInfoPtr->dLinePtr, dlPtr, 1); | ||
} | ||
/* | ||
*-------------------------------------------------------------- | ||
* Scan through the contents of the window from top to bottom, | ||
* recomputing information for lines that are missing. | ||
*-------------------------------------------------------------- | ||
*/ | ||
lastLinePtr = TkBTreeFindLine(textPtr->tree, | ||
TkBTreeNumLines(textPtr->tree)); | ||
dlPtr = dInfoPtr->dLinePtr; | ||
prevPtr = NULL; | ||
y = dInfoPtr->y; | ||
maxY = dInfoPtr->maxY; | ||
while (1) { | ||
register DLine *newPtr; | ||
if (index.linePtr == lastLinePtr) { | ||
break; | ||
} | ||
/* | ||
* There are three possibilities right now: | ||
* (a) the next DLine (dlPtr) corresponds exactly to the next | ||
* information we want to display: just use it as-is. | ||
* (b) the next DLine corresponds to a different line, or to | ||
* a segment that will be coming later in the same line: | ||
* leave this DLine alone in the hopes that we'll be able | ||
* to use it later, then create a new DLine in front of | ||
* it. | ||
* (c) the next DLine corresponds to a segment in the line we | ||
* want, but it's a segment that has already been processed | ||
* or will never be processed. Delete the DLine and try | ||
* again. | ||
* | ||
* One other twist on all this. It's possible for 3D borders | ||
* to interact between lines (see DisplayLineBackground) so if | ||
* a line is relayed out and has styles with 3D borders, its | ||
* neighbors have to be redrawn if they have 3D borders too, | ||
* since the interactions could have changed (the neighbors | ||
* don't have to be relayed out, just redrawn). | ||
*/ | ||
if ((dlPtr == NULL) || (dlPtr->index.linePtr != index.linePtr)) { | ||
/* | ||
* Case (b) -- must make new DLine. | ||
*/ | ||
makeNewDLine: | ||
if (tkTextDebug) { | ||
char string[TK_POS_CHARS]; | ||
/* | ||
* Debugging is enabled, so keep a log of all the lines | ||
* that were re-layed out. The test suite uses this | ||
* information. | ||
*/ | ||
TkTextPrintIndex(&index, string); | ||
Tcl_SetVar2(textPtr->interp, "tk_textRelayout", (char *) NULL, | ||
string, | ||
TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT); | ||
} | ||
newPtr = LayoutDLine(textPtr, &index); | ||
if (prevPtr == NULL) { | ||
dInfoPtr->dLinePtr = newPtr; | ||
} else { | ||
prevPtr->nextPtr = newPtr; | ||
if (prevPtr->flags & HAS_3D_BORDER) { | ||
prevPtr->oldY = -1; | ||
} | ||
} | ||
newPtr->nextPtr = dlPtr; | ||
dlPtr = newPtr; | ||
} else { | ||
/* | ||
* DlPtr refers to the line we want. Next check the | ||
* index within the line. | ||
*/ | ||
if (index.byteIndex == dlPtr->index.byteIndex) { | ||
/* | ||
* Case (a) -- can use existing display line as-is. | ||
*/ | ||
if ((dlPtr->flags & HAS_3D_BORDER) && (prevPtr != NULL) | ||
&& (prevPtr->flags & (NEW_LAYOUT))) { | ||
dlPtr->oldY = -1; | ||
} | ||
goto lineOK; | ||
} | ||
if (index.byteIndex < dlPtr->index.byteIndex) { | ||
goto makeNewDLine; | ||
} | ||
/* | ||
* Case (c) -- dlPtr is useless. Discard it and start | ||
* again with the next display line. | ||
*/ | ||
newPtr = dlPtr->nextPtr; | ||
FreeDLines(textPtr, dlPtr, newPtr, 0); | ||
dlPtr = newPtr; | ||
if (prevPtr != NULL) { | ||
prevPtr->nextPtr = newPtr; | ||
} else { | ||
dInfoPtr->dLinePtr = newPtr; | ||
} | ||
continue; | ||
} | ||
/* | ||
* Advance to the start of the next line. | ||
*/ | ||
lineOK: | ||
dlPtr->y = y; | ||
y += dlPtr->height; | ||
TkTextIndexForwBytes(&index, dlPtr->byteCount, &index); | ||
prevPtr = dlPtr; | ||
dlPtr = dlPtr->nextPtr; | ||
/* | ||
* If we switched text lines, delete any DLines left for the | ||
* old text line. | ||
*/ | ||
if (index.linePtr != prevPtr->index.linePtr) { | ||
register DLine *nextPtr; | ||
nextPtr = dlPtr; | ||
while ((nextPtr != NULL) | ||
&& (nextPtr->index.linePtr == prevPtr->index.linePtr)) { | ||
nextPtr = nextPtr->nextPtr; | ||
} | ||
if (nextPtr != dlPtr) { | ||
FreeDLines(textPtr, dlPtr, nextPtr, 0); | ||
prevPtr->nextPtr = nextPtr; | ||
dlPtr = nextPtr; | ||
} | ||
} | ||
/* | ||
* It's important to have the following check here rather than in | ||
* the while statement for the loop, so that there's always at least | ||
* one DLine generated, regardless of how small the window is. This | ||
* keeps a lot of other code from breaking. | ||
*/ | ||
if (y >= maxY) { | ||
break; | ||
} | ||
} | ||
/* | ||
* Delete any DLine structures that don't fit on the screen. | ||
*/ | ||
FreeDLines(textPtr, dlPtr, (DLine *) NULL, 1); | ||
/* | ||
*-------------------------------------------------------------- | ||
* If there is extra space at the bottom of the window (because | ||
* we've hit the end of the text), then bring in more lines at | ||
* the top of the window, if there are any, to fill in the view. | ||
*-------------------------------------------------------------- | ||
*/ | ||
if (y < maxY) { | ||
int lineNum, spaceLeft, bytesToCount; | ||
DLine *lowestPtr; | ||
/* | ||
* Layout an entire text line (potentially > 1 display line), | ||
* then link in as many display lines as fit without moving | ||
* the bottom line out of the window. Repeat this until | ||
* all the extra space has been used up or we've reached the | ||
* beginning of the text. | ||
*/ | ||
spaceLeft = maxY - y; | ||
lineNum = TkBTreeLineIndex(dInfoPtr->dLinePtr->index.linePtr); | ||
bytesToCount = dInfoPtr->dLinePtr->index.byteIndex; | ||
if (bytesToCount == 0) { | ||
bytesToCount = INT_MAX; | ||
lineNum--; | ||
} | ||
for ( ; (lineNum >= 0) && (spaceLeft > 0); lineNum--) { | ||
index.linePtr = TkBTreeFindLine(textPtr->tree, lineNum); | ||
index.byteIndex = 0; | ||
lowestPtr = NULL; | ||
do { | ||
dlPtr = LayoutDLine(textPtr, &index); | ||
dlPtr->nextPtr = lowestPtr; | ||
lowestPtr = dlPtr; | ||
if (dlPtr->length == 0 && dlPtr->height == 0) { bytesToCount--; break; } /* elide */ | ||
TkTextIndexForwBytes(&index, dlPtr->byteCount, &index); | ||
bytesToCount -= dlPtr->byteCount; | ||
} while ((bytesToCount > 0) | ||
&& (index.linePtr == lowestPtr->index.linePtr)); | ||
/* | ||
* Scan through the display lines from the bottom one up to | ||
* the top one. | ||
*/ | ||
while (lowestPtr != NULL) { | ||
dlPtr = lowestPtr; | ||
spaceLeft -= dlPtr->height; | ||
if (spaceLeft < 0) { | ||
break; | ||
} | ||
lowestPtr = dlPtr->nextPtr; | ||
dlPtr->nextPtr = dInfoPtr->dLinePtr; | ||
dInfoPtr->dLinePtr = dlPtr; | ||
if (tkTextDebug) { | ||
char string[TK_POS_CHARS]; | ||
TkTextPrintIndex(&dlPtr->index, string); | ||
Tcl_SetVar2(textPtr->interp, "tk_textRelayout", | ||
(char *) NULL, string, | ||
TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT); | ||
} | ||
} | ||
FreeDLines(textPtr, lowestPtr, (DLine *) NULL, 0); | ||
bytesToCount = INT_MAX; | ||
} | ||
/* | ||
* Now we're all done except that the y-coordinates in all the | ||
* DLines are wrong and the top index for the text is wrong. | ||
* Update them. | ||
*/ | ||
textPtr->topIndex = dInfoPtr->dLinePtr->index; | ||
y = dInfoPtr->y; | ||
for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; | ||
dlPtr = dlPtr->nextPtr) { | ||
if (y > dInfoPtr->maxY) { | ||
panic("Added too many new lines in UpdateDisplayInfo"); | ||
} | ||
dlPtr->y = y; | ||
y += dlPtr->height; | ||
} | ||
} | ||
/* | ||
*-------------------------------------------------------------- | ||
* If the old top or bottom line has scrolled elsewhere on the | ||
* screen, we may not be able to re-use its old contents by | ||
* copying bits (e.g., a beveled edge that was drawn when it was | ||
* at the top or bottom won't be drawn when the line is in the | ||
* middle and its neighbor has a matching background). Similarly, | ||
* if the new top or bottom line came from somewhere else on the | ||
* screen, we may not be able to copy the old bits. | ||
*-------------------------------------------------------------- | ||
*/ | ||
dlPtr = dInfoPtr->dLinePtr; | ||
if ((dlPtr->flags & HAS_3D_BORDER) && !(dlPtr->flags & TOP_LINE)) { | ||
dlPtr->oldY = -1; | ||
} | ||
while (1) { | ||
if ((dlPtr->flags & TOP_LINE) && (dlPtr != dInfoPtr->dLinePtr) | ||
&& (dlPtr->flags & HAS_3D_BORDER)) { | ||
dlPtr->oldY = -1; | ||
} | ||
if ((dlPtr->flags & BOTTOM_LINE) && (dlPtr->nextPtr != NULL) | ||
&& (dlPtr->flags & HAS_3D_BORDER)) { | ||
dlPtr->oldY = -1; | ||
} | ||
if (dlPtr->nextPtr == NULL) { | ||
if ((dlPtr->flags & HAS_3D_BORDER) | ||
&& !(dlPtr->flags & BOTTOM_LINE)) { | ||
dlPtr->oldY = -1; | ||
} | ||
dlPtr->flags &= ~TOP_LINE; | ||
dlPtr->flags |= BOTTOM_LINE; | ||
break; | ||
} | ||
dlPtr->flags &= ~(TOP_LINE|BOTTOM_LINE); | ||
dlPtr = dlPtr->nextPtr; | ||
} | ||
dInfoPtr->dLinePtr->flags |= TOP_LINE; | ||
/* | ||
* Arrange for scrollbars to be updated. | ||
*/ | ||
textPtr->flags |= UPDATE_SCROLLBARS; | ||
/* | ||
*-------------------------------------------------------------- | ||
* Deal with horizontal scrolling: | ||
* 1. If there's empty space to the right of the longest line, | ||
* shift the screen to the right to fill in the empty space. | ||
* 2. If the desired horizontal scroll position has changed, | ||
* force a full redisplay of all the lines in the widget. | ||
* 3. If the wrap mode isn't "none" then re-scroll to the base | ||
* position. | ||
*-------------------------------------------------------------- | ||
*/ | ||
dInfoPtr->maxLength = 0; | ||
for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; | ||
dlPtr = dlPtr->nextPtr) { | ||
if (dlPtr->length > dInfoPtr->maxLength) { | ||
dInfoPtr->maxLength = dlPtr->length; | ||
} | ||
} | ||
maxOffset = (dInfoPtr->maxLength - (dInfoPtr->maxX - dInfoPtr->x) | ||
+ textPtr->charWidth - 1)/textPtr->charWidth; | ||
if (dInfoPtr->newByteOffset > maxOffset) { | ||
dInfoPtr->newByteOffset = maxOffset; | ||
} | ||
if (dInfoPtr->newByteOffset < 0) { | ||
dInfoPtr->newByteOffset = 0; | ||
} | ||
pixelOffset = dInfoPtr->newByteOffset * textPtr->charWidth; | ||
if (pixelOffset != dInfoPtr->curPixelOffset) { | ||
dInfoPtr->curPixelOffset = pixelOffset; | ||
for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; | ||
dlPtr = dlPtr->nextPtr) { | ||
dlPtr->oldY = -1; | ||
} | ||
} | ||
} | ||
/* | ||
*---------------------------------------------------------------------- | ||
* | ||
* FreeDLines -- | ||
* | ||
* This procedure is called to free up all of the resources | ||
* associated with one or more DLine structures. | ||
* | ||
* Results: | ||
* None. | ||
* | ||
* Side effects: | ||
* Memory gets freed and various other resources are released. | ||
* | ||
*---------------------------------------------------------------------- | ||
*/ | ||
static void | ||
FreeDLines(textPtr, firstPtr, lastPtr, unlink) | ||
TkText *textPtr; /* Information about overall text | ||
* widget. */ | ||
register DLine *firstPtr; /* Pointer to first DLine to free up. */ | ||
DLine *lastPtr; /* Pointer to DLine just after last | ||
* one to free (NULL means everything | ||
* starting with firstPtr). */ | ||
int unlink; /* 1 means DLines are currently linked | ||
* into the list rooted at | ||
* textPtr->dInfoPtr->dLinePtr and | ||
* they have to be unlinked. 0 means | ||
* just free without unlinking. */ | ||
{ | ||
register TkTextDispChunk *chunkPtr, *nextChunkPtr; | ||
register DLine *nextDLinePtr; | ||
if (unlink) { | ||
if (textPtr->dInfoPtr->dLinePtr == firstPtr) { | ||
textPtr->dInfoPtr->dLinePtr = lastPtr; | ||
} else { | ||
register DLine *prevPtr; | ||
for (prevPtr = textPtr->dInfoPtr->dLinePtr; | ||
prevPtr->nextPtr != firstPtr; prevPtr = prevPtr->nextPtr) { | ||
/* Empty loop body. */ | ||
} | ||
prevPtr->nextPtr = lastPtr; | ||
} | ||
} | ||
while (firstPtr != lastPtr) { | ||
nextDLinePtr = firstPtr->nextPtr; | ||
for (chunkPtr = firstPtr->chunkPtr; chunkPtr != NULL; | ||
chunkPtr = nextChunkPtr) { | ||
if (chunkPtr->undisplayProc != NULL) { | ||
(*chunkPtr->undisplayProc)(textPtr, chunkPtr); | ||
} | ||
FreeStyle(textPtr, chunkPtr->stylePtr); | ||
nextChunkPtr = chunkPtr->nextPtr; | ||
ckfree((char *) chunkPtr); | ||
} | ||
ckfree((char *) firstPtr); | ||
firstPtr = nextDLinePtr; | ||
} | ||
textPtr->dInfoPtr->dLinesInvalidated = 1; | ||
} | ||
/* | ||
*---------------------------------------------------------------------- | ||
* | ||
* DisplayDLine -- | ||
* | ||
* This procedure is invoked to draw a single line on the | ||
* screen. | ||
* | ||
* Results: | ||
* None. | ||
* | ||
* Side effects: | ||
* The line given by dlPtr is drawn at its correct position in | ||
* textPtr's window. Note that this is one *display* line, not | ||
* one *text* line. | ||
* | ||
*---------------------------------------------------------------------- | ||
*/ | ||
static void | ||
DisplayDLine(textPtr, dlPtr, prevPtr, pixmap) | ||
TkText *textPtr; /* Text widget in which to draw line. */ | ||
register DLine *dlPtr; /* Information about line to draw. */ | ||
DLine *prevPtr; /* Line just before one to draw, or NULL | ||
* if dlPtr is the top line. */ | ||
Pixmap pixmap; /* Pixmap to use for double-buffering. | ||
* Caller must make sure it's large enough | ||
* to hold line. */ | ||
{ | ||
register TkTextDispChunk *chunkPtr; | ||
TextDInfo *dInfoPtr = textPtr->dInfoPtr; | ||
Display *display; | ||
int height, x; | ||
if (dlPtr->chunkPtr == NULL) return; | ||
/* | ||
* First, clear the area of the line to the background color for the | ||
* text widget. | ||
*/ | ||
display = Tk_Display(textPtr->tkwin); | ||
Tk_Fill3DRectangle(textPtr->tkwin, pixmap, textPtr->border, 0, 0, | ||
Tk_Width(textPtr->tkwin), dlPtr->height, 0, TK_RELIEF_FLAT); | ||
/* | ||
* Next, draw background information for the whole line. | ||
*/ | ||
DisplayLineBackground(textPtr, dlPtr, prevPtr, pixmap); | ||
/* | ||
* Make another pass through all of the chunks to redraw the | ||
* insertion cursor, if it is visible on this line. Must do | ||
* it here rather than in the foreground pass below because | ||
* otherwise a wide insertion cursor will obscure the character | ||
* to its left. | ||
*/ | ||
if (textPtr->state == TK_STATE_NORMAL) { | ||
for (chunkPtr = dlPtr->chunkPtr; (chunkPtr != NULL); | ||
chunkPtr = chunkPtr->nextPtr) { | ||
x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curPixelOffset; | ||
if (chunkPtr->displayProc == TkTextInsertDisplayProc) { | ||
(*chunkPtr->displayProc)(chunkPtr, x, dlPtr->spaceAbove, | ||
dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow, | ||
dlPtr->baseline - dlPtr->spaceAbove, display, pixmap, | ||
dlPtr->y + dlPtr->spaceAbove); | ||
} | ||
} | ||
} | ||
/* | ||
* Make yet another pass through all of the chunks to redraw all of | ||
* foreground information. Note: we have to call the displayProc | ||
* even for chunks that are off-screen. This is needed, for | ||
* example, so that embedded windows can be unmapped in this case. | ||
* Conve | ||
*/ | ||
for (chunkPtr = dlPtr->chunkPtr; (chunkPtr != NULL); | ||
chunkPtr = chunkPtr->nextPtr) { | ||
if (chunkPtr->displayProc == TkTextInsertDisplayProc) { | ||
/* | ||
* Already displayed the insertion cursor above. Don't | ||
* do it again here. | ||
*/ | ||
continue; | ||
} | ||
x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curPixelOffset; | ||
if ((x + chunkPtr->width <= 0) || (x >= dInfoPtr->maxX)) { | ||
/* | ||
* Note: we have to call the displayProc even for chunks | ||
* that are off-screen. This is needed, for example, so | ||
* that embedded windows can be unmapped in this case. | ||
* Display the chunk at a coordinate that can be clearly | ||
* identified by the displayProc as being off-screen to | ||
* the left (the displayProc may not be able to tell if | ||
* something is off to the right). | ||
*/ | ||
if (chunkPtr->displayProc != NULL) | ||
(*chunkPtr->displayProc)(chunkPtr, -chunkPtr->width, | ||
dlPtr->spaceAbove, | ||
dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow, | ||
dlPtr->baseline - dlPtr->spaceAbove, display, pixmap, | ||
dlPtr->y + dlPtr->spaceAbove); | ||
} else { | ||
/* don't call if elide. This tax ok since not very many visible DLine's in | ||
an area, but potentially many elide ones */ | ||
if (chunkPtr->displayProc != NULL) | ||
(*chunkPtr->displayProc)(chunkPtr, x, dlPtr->spaceAbove, | ||
dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow, | ||
dlPtr->baseline - dlPtr->spaceAbove, display, pixmap, | ||
dlPtr->y + dlPtr->spaceAbove); | ||
} | ||
if (dInfoPtr->dLinesInvalidated) { | ||
return; | ||
} | ||
} | ||
/* | ||
* Copy the pixmap onto the screen. If this is the last line on | ||
* the screen then copy a piece of the line, so that it doesn't | ||
* overflow into the border area. Another special trick: copy the | ||
* padding area to the left of the line; this is because the | ||
* insertion cursor sometimes overflows onto that area and we want | ||
* to get as much of the cursor as possible. | ||
*/ | ||
height = dlPtr->height; | ||
if ((height + dlPtr->y) > dInfoPtr->maxY) { | ||
height = dInfoPtr->maxY - dlPtr->y; | ||
} | ||
XCopyArea(display, pixmap, Tk_WindowId(textPtr->tkwin), dInfoPtr->copyGC, | ||
dInfoPtr->x, 0, (unsigned) (dInfoPtr->maxX - dInfoPtr->x), | ||
(unsigned) height, dInfoPtr->x, dlPtr->y); | ||
linesRedrawn++; | ||
} | ||
/* | ||
*-------------------------------------------------------------- | ||
* | ||
* DisplayLineBackground -- | ||
* | ||
* This procedure is called to fill in the background for | ||
* a display line. It draws 3D borders cleverly so that | ||
* adjacent chunks with the same style (whether on the same | ||
* line or different lines) have a single 3D border around | ||
* the whole region. | ||
* | ||
* Results: | ||
* There is no return value. Pixmap is filled in with background | ||
* information for dlPtr. | ||
* | ||
* Side effects: | ||
* None. | ||
* | ||
*-------------------------------------------------------------- | ||
*/ | ||
static void | ||
DisplayLineBackground(textPtr, dlPtr, prevPtr, pixmap) | ||
TkText *textPtr; /* Text widget containing line. */ | ||
register DLine *dlPtr; /* Information about line to draw. */ | ||
DLine *prevPtr; /* Line just above dlPtr, or NULL if dlPtr | ||
* is the top-most line in the window. */ | ||
Pixmap pixmap; /* Pixmap to use for double-buffering. | ||
* Caller must make sure it's large enough | ||
* to hold line. Caller must also have | ||
* filled it with the background color for | ||
* the widget. */ | ||
{ | ||
TextDInfo *dInfoPtr = textPtr->dInfoPtr; | ||
TkTextDispChunk *chunkPtr; /* Pointer to chunk in the current line. */ | ||
TkTextDispChunk *chunkPtr2; /* Pointer to chunk in the line above or | ||
* below the current one. NULL if we're to | ||
* the left of or to the right of the chunks | ||
* in the line. */ | ||
TkTextDispChunk *nextPtr2; /* Next chunk after chunkPtr2 (it's not the | ||
* same as chunkPtr2->nextPtr in the case | ||
* where chunkPtr2 is NULL because the line | ||
* is indented). */ | ||
int leftX; /* The left edge of the region we're | ||
* currently working on. */ | ||
int leftXIn; /* 1 means beveled edge at leftX slopes right | ||
* as it goes down, 0 means it slopes left | ||
* as it goes down. */ | ||
int rightX; /* Right edge of chunkPtr. */ | ||
int rightX2; /* Right edge of chunkPtr2. */ | ||
int matchLeft; /* Does the style of this line match that | ||
* of its neighbor just to the left of | ||
* the current x coordinate? */ | ||
int matchRight; /* Does line's style match its neighbor | ||
* just to the right of the current x-coord? */ | ||
int minX, maxX, xOffset; | ||
StyleValues *sValuePtr; | ||
Display *display; | ||
/* | ||
* Pass 1: scan through dlPtr from left to right. For each range of | ||
* chunks with the same style, draw the main background for the style | ||
* plus the vertical parts of the 3D borders (the left and right | ||
* edges). | ||
*/ | ||
display = Tk_Display(textPtr->tkwin); | ||
minX = dInfoPtr->curPixelOffset; | ||
xOffset = dInfoPtr->x - minX; | ||
maxX = minX + dInfoPtr->maxX - dInfoPtr->x; | ||
chunkPtr = dlPtr->chunkPtr; | ||
/* | ||
* Note A: in the following statement, and a few others later in | ||
* this file marked with "See Note A above", the right side of the | ||
* assignment was replaced with 0 on 6/18/97. This has the effect | ||
* of highlighting the empty space to the left of a line whenever | ||
* the leftmost character of the line is highlighted. This way, | ||
* multi-line highlights always line up along their left edges. | ||
* However, this may look funny in the case where a single word is | ||
* highlighted. To undo the change, replace "leftX = 0" with "leftX | ||
* = chunkPtr->x" and "rightX2 = 0" with "rightX2 = nextPtr2->x" | ||
* here and at all the marked points below. This restores the old | ||
* behavior where empty space to the left of a line is not | ||
* highlighted, leaving a ragged left edge for multi-line | ||
* highlights. | ||
*/ | ||
leftX = 0; | ||
for (; leftX < maxX; chunkPtr = chunkPtr->nextPtr) { | ||
if ((chunkPtr->nextPtr != NULL) | ||
&& SAME_BACKGROUND(chunkPtr->nextPtr->stylePtr, | ||
chunkPtr->stylePtr)) { | ||
continue; | ||
} | ||
sValuePtr = chunkPtr->stylePtr->sValuePtr; | ||
rightX = chunkPtr->x + chunkPtr->width; | ||
if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) { | ||
rightX = maxX; | ||
} | ||
if (chunkPtr->stylePtr->bgGC != None) { | ||
XFillRectangle(display, pixmap, chunkPtr->stylePtr->bgGC, | ||
leftX + xOffset, 0, (unsigned int) (rightX - leftX), | ||
(unsigned int) dlPtr->height); | ||
if (sValuePtr->relief != TK_RELIEF_FLAT) { | ||
Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border, | ||
leftX + xOffset, 0, sValuePtr->borderWidth, | ||
dlPtr->height, 1, sValuePtr->relief); | ||
Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border, | ||
rightX - sValuePtr->borderWidth + xOffset, | ||
0, sValuePtr->borderWidth, dlPtr->height, 0, | ||
sValuePtr->relief); | ||
} | ||
} | ||
leftX = rightX; | ||
} | ||
/* | ||
* Pass 2: draw the horizontal bevels along the top of the line. To | ||
* do this, scan through dlPtr from left to right while simultaneously | ||
* scanning through the line just above dlPtr. ChunkPtr2 and nextPtr2 | ||
* refer to two adjacent chunks in the line above. | ||
*/ | ||
chunkPtr = dlPtr->chunkPtr; | ||
leftX = 0; /* See Note A above. */ | ||
leftXIn = 1; | ||
rightX = chunkPtr->x + chunkPtr->width; | ||
if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) { | ||
rightX = maxX; | ||
} | ||
chunkPtr2 = NULL; | ||
if (prevPtr != NULL && prevPtr->chunkPtr != NULL) { | ||
/* | ||
* Find the chunk in the previous line that covers leftX. | ||
*/ | ||
nextPtr2 = prevPtr->chunkPtr; | ||
rightX2 = 0; /* See Note A above. */ | ||
while (rightX2 <= leftX) { | ||
chunkPtr2 = nextPtr2; | ||
if (chunkPtr2 == NULL) { | ||
break; | ||
} | ||
nextPtr2 = chunkPtr2->nextPtr; | ||
rightX2 = chunkPtr2->x + chunkPtr2->width; | ||
if (nextPtr2 == NULL) { | ||
rightX2 = INT_MAX; | ||
} | ||
} | ||
} else { | ||
nextPtr2 = NULL; | ||
rightX2 = INT_MAX; | ||
} | ||
while (leftX < maxX) { | ||
matchLeft = (chunkPtr2 != NULL) | ||
&& SAME_BACKGROUND(chunkPtr2->stylePtr, chunkPtr->stylePtr); | ||
sValuePtr = chunkPtr->stylePtr->sValuePtr; | ||
if (rightX <= rightX2) { | ||
/* | ||
* The chunk in our line is about to end. If its style | ||
* changes then draw the bevel for the current style. | ||
*/ | ||
if ((chunkPtr->nextPtr == NULL) | ||
|| !SAME_BACKGROUND(chunkPtr->stylePtr, | ||
chunkPtr->nextPtr->stylePtr)) { | ||
if (!matchLeft && (sValuePtr->relief != TK_RELIEF_FLAT)) { | ||
Tk_3DHorizontalBevel(textPtr->tkwin, pixmap, | ||
sValuePtr->border, leftX + xOffset, 0, | ||
rightX - leftX, sValuePtr->borderWidth, leftXIn, | ||
1, 1, sValuePtr->relief); | ||
} | ||
leftX = rightX; | ||
leftXIn = 1; | ||
/* | ||
* If the chunk in the line above is also ending at | ||
* the same point then advance to the next chunk in | ||
* that line. | ||
*/ | ||
if ((rightX == rightX2) && (chunkPtr2 != NULL)) { | ||
goto nextChunk2; | ||
} | ||
} | ||
chunkPtr = chunkPtr->nextPtr; | ||
if (chunkPtr == NULL) { | ||
break; | ||
} | ||
rightX = chunkPtr->x + chunkPtr->width; | ||
if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) { | ||
rightX = maxX; | ||
} | ||
continue; | ||
} | ||
/* | ||
* The chunk in the line above is ending at an x-position where | ||
* there is no change in the style of the current line. If the | ||
* style above matches the current line on one side of the change | ||
* but not on the other, we have to draw an L-shaped piece of | ||
* bevel. | ||
*/ | ||
matchRight = (nextPtr2 != NULL) | ||
&& SAME_BACKGROUND(nextPtr2->stylePtr, chunkPtr->stylePtr); | ||
if (matchLeft && !matchRight) { | ||
if (sValuePtr->relief != TK_RELIEF_FLAT) { | ||
Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border, | ||
rightX2 - sValuePtr->borderWidth + xOffset, 0, | ||
sValuePtr->borderWidth, sValuePtr->borderWidth, 0, | ||
sValuePtr->relief); | ||
} | ||
leftX = rightX2 - sValuePtr->borderWidth; | ||
leftXIn = 0; | ||
} else if (!matchLeft && matchRight | ||
&& (sValuePtr->relief != TK_RELIEF_FLAT)) { | ||
Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border, | ||
rightX2 + xOffset, 0, sValuePtr->borderWidth, | ||
sValuePtr->borderWidth, 1, sValuePtr->relief); | ||
Tk_3DHorizontalBevel(textPtr->tkwin, pixmap, sValuePtr->border, | ||
leftX + xOffset, 0, rightX2 + sValuePtr->borderWidth -leftX, | ||
sValuePtr->borderWidth, leftXIn, 0, 1, | ||
sValuePtr->relief); | ||
} | ||
nextChunk2: | ||
chunkPtr2 = nextPtr2; | ||
if (chunkPtr2 == NULL) { | ||
rightX2 = INT_MAX; | ||
} else { | ||
nextPtr2 = chunkPtr2->nextPtr; | ||
rightX2 = chunkPtr2->x + chunkPtr2->width; | ||
if (nextPtr2 == NULL) { | ||
rightX2 = INT_MAX; | ||
} | ||
} | ||
} | ||
/* | ||
* Pass 3: draw the horizontal bevels along the bottom of the line. | ||
* This uses the same approach as pass 2. | ||
*/ | ||
chunkPtr = dlPtr->chunkPtr; | ||
leftX = 0; /* See Note A above. */ | ||
leftXIn = 0; | ||
rightX = chunkPtr->x + chunkPtr->width; | ||
if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) { | ||
rightX = maxX; | ||
} | ||
chunkPtr2 = NULL; | ||
if (dlPtr->nextPtr != NULL && dlPtr->nextPtr->chunkPtr != NULL) { | ||
/* | ||
* Find the chunk in the previous line that covers leftX. | ||
*/ | ||
nextPtr2 = dlPtr->nextPtr->chunkPtr; | ||
rightX2 = 0; /* See Note A above. */ | ||
while (rightX2 <= leftX) { | ||
chunkPtr2 = nextPtr2; | ||
if (chunkPtr2 == NULL) { | ||
break; | ||
} | ||
nextPtr2 = chunkPtr2->nextPtr; | ||
rightX2 = chunkPtr2->x + chunkPtr2->width; | ||
if (nextPtr2 == NULL) { | ||
rightX2 = INT_MAX; | ||
} | ||
} | ||
} else { | ||
nextPtr2 = NULL; | ||
rightX2 = INT_MAX; | ||
} | ||
while (leftX < maxX) { | ||
matchLeft = (chunkPtr2 != NULL) | ||
&& SAME_BACKGROUND(chunkPtr2->stylePtr, chunkPtr->stylePtr); | ||
sValuePtr = chunkPtr->stylePtr->sValuePtr; | ||
if (rightX <= rightX2) { | ||
if ((chunkPtr->nextPtr == NULL) | ||
|| !SAME_BACKGROUND(chunkPtr->stylePtr, | ||
chunkPtr->nextPtr->stylePtr)) { | ||
if (!matchLeft && (sValuePtr->relief != TK_RELIEF_FLAT)) { | ||
Tk_3DHorizontalBevel(textPtr->tkwin, pixmap, | ||
sValuePtr->border, leftX + xOffset, | ||
dlPtr->height - sValuePtr->borderWidth, | ||
rightX - leftX, sValuePtr->borderWidth, leftXIn, | ||
0, 0, sValuePtr->relief); | ||
} | ||
leftX = rightX; | ||
leftXIn = 0; | ||
if ((rightX == rightX2) && (chunkPtr2 != NULL)) { | ||
goto nextChunk2b; | ||
} | ||
} | ||
chunkPtr = chunkPtr->nextPtr; | ||
if (chunkPtr == NULL) { | ||
break; | ||
} | ||
rightX = chunkPtr->x + chunkPtr->width; | ||
if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) { | ||
rightX = maxX; | ||
} | ||
continue; | ||
} | ||
matchRight = (nextPtr2 != NULL) | ||
&& SAME_BACKGROUND(nextPtr2->stylePtr, chunkPtr->stylePtr); | ||
if (matchLeft && !matchRight) { | ||
if (sValuePtr->relief != TK_RELIEF_FLAT) { | ||
Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border, | ||
rightX2 - sValuePtr->borderWidth + xOffset, | ||
dlPtr->height - sValuePtr->borderWidth, | ||
sValuePtr->borderWidth, sValuePtr->borderWidth, 0, | ||
sValuePtr->relief); | ||
} | ||
leftX = rightX2 - sValuePtr->borderWidth; | ||
leftXIn = 1; | ||
} else if (!matchLeft && matchRight | ||
&& (sValuePtr->relief != TK_RELIEF_FLAT)) { | ||
Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border, | ||
rightX2 + xOffset, dlPtr->height - sValuePtr->borderWidth, | ||
sValuePtr->borderWidth, sValuePtr->borderWidth, | ||
1, sValuePtr->relief); | ||
Tk_3DHorizontalBevel(textPtr->tkwin, pixmap, sValuePtr->border, | ||
leftX + xOffset, dlPtr->height - sValuePtr->borderWidth, | ||
rightX2 + sValuePtr->borderWidth - leftX, | ||
sValuePtr->borderWidth, leftXIn, 1, 0, sValuePtr->relief); | ||
} | ||
nextChunk2b: | ||
chunkPtr2 = nextPtr2; | ||
if (chunkPtr2 == NULL) { | ||
rightX2 = INT_MAX; | ||
} else { | ||
nextPtr2 = chunkPtr2->nextPtr; | ||
rightX2 = chunkPtr2->x + chunkPtr2->width; | ||
if (nextPtr2 == NULL) { | ||
rightX2 = INT_MAX; | ||
} | ||
} | ||
} | ||
} | ||
/* | ||
*---------------------------------------------------------------------- | ||
* | ||
* DisplayText -- | ||
* | ||
* This procedure is invoked as a when-idle handler to update the | ||
* display. It only redisplays the parts of the text widget that | ||
* are out of date. | ||
* | ||
* Results: | ||
* None. | ||
* | ||
* Side effects: | ||
* Information is redrawn on the screen. | ||
* | ||
*---------------------------------------------------------------------- | ||
*/ | ||
static void | ||
DisplayText(clientData) | ||
ClientData clientData; /* Information about widget. */ | ||
{ | ||
register TkText *textPtr = (TkText *) clientData; | ||
TextDInfo *dInfoPtr = textPtr->dInfoPtr; | ||
Tk_Window tkwin; | ||
register DLine *dlPtr; | ||
DLine *prevPtr; | ||
Pixmap pixmap; | ||
int maxHeight, borders; | ||
int bottomY = 0; /* Initialization needed only to stop | ||
* compiler warnings. */ | ||
Tcl_Interp *interp; | ||
if (textPtr->tkwin == NULL) { | ||
/* | ||
* The widget has been deleted. Don't do anything. | ||
*/ | ||
return; | ||
} | ||
interp = textPtr->interp; | ||
Tcl_Preserve((ClientData) interp); | ||
if (tkTextDebug) { | ||
Tcl_SetVar2(interp, "tk_textRelayout", (char *) NULL, "", | ||
TCL_GLOBAL_ONLY); | ||
} | ||
if (textPtr->tkwin == NULL) { | ||
/* | ||
* The widget has been deleted. Don't do anything. | ||
*/ | ||
goto end; | ||
} | ||
if (!Tk_IsMapped(textPtr->tkwin) || (dInfoPtr->maxX <= dInfoPtr->x) | ||
|| (dInfoPtr->maxY <= dInfoPtr->y)) { | ||
UpdateDisplayInfo(textPtr); | ||
dInfoPtr->flags &= ~REDRAW_PENDING; | ||
goto doScrollbars; | ||
} | ||
numRedisplays++; | ||
if (tkTextDebug) { | ||
Tcl_SetVar2(interp, "tk_textRedraw", (char *) NULL, "", | ||
TCL_GLOBAL_ONLY); | ||
} | ||
if (textPtr->tkwin == NULL) { | ||
/* | ||
* The widget has been deleted. Don't do anything. | ||
*/ | ||
goto end; | ||
} | ||
/* | ||
* Choose a new current item if that is needed (this could cause | ||
* event handlers to be invoked, hence the preserve/release calls | ||
* and the loop, since the handlers could conceivably necessitate | ||
* yet another current item calculation). The tkwin check is because | ||
* the whole window could go away in the Tcl_Release call. | ||
*/ | ||
while (dInfoPtr->flags & REPICK_NEEDED) { | ||
Tcl_Preserve((ClientData) textPtr); | ||
dInfoPtr->flags &= ~REPICK_NEEDED; | ||
TkTextPickCurrent(textPtr, &textPtr->pickEvent); | ||
tkwin = textPtr->tkwin; | ||
Tcl_Release((ClientData) textPtr); | ||
if (tkwin == NULL) { | ||
goto end; | ||
} | ||
} | ||
/* | ||
* First recompute what's supposed to be displayed. | ||
*/ | ||
UpdateDisplayInfo(textPtr); | ||
dInfoPtr->dLinesInvalidated = 0; | ||
/* | ||
* See if it's possible to bring some parts of the screen up-to-date | ||
* by scrolling (copying from other parts of the screen). | ||
*/ | ||
for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) { | ||
register DLine *dlPtr2; | ||
int offset, height, y, oldY; | ||
TkRegion damageRgn; | ||
if ((dlPtr->oldY == -1) || (dlPtr->y == dlPtr->oldY) | ||
|| ((dlPtr->oldY + dlPtr->height) > dInfoPtr->maxY)) { | ||
continue; | ||
} | ||
/* | ||
* This line is already drawn somewhere in the window so it only | ||
* needs to be copied to its new location. See if there's a group | ||
* of lines that can all be copied together. | ||
*/ | ||
offset = dlPtr->y - dlPtr->oldY; | ||
height = dlPtr->height; | ||
y = dlPtr->y; | ||
for (dlPtr2 = dlPtr->nextPtr; dlPtr2 != NULL; | ||
dlPtr2 = dlPtr2->nextPtr) { | ||
if ((dlPtr2->oldY == -1) | ||
|| ((dlPtr2->oldY + offset) != dlPtr2->y) | ||
|| ((dlPtr2->oldY + dlPtr2->height) > dInfoPtr->maxY)) { | ||
break; | ||
} | ||
height += dlPtr2->height; | ||
} | ||
/* | ||
* Reduce the height of the area being copied if necessary to | ||
* avoid overwriting the border area. | ||
*/ | ||
if ((y + height) > dInfoPtr->maxY) { | ||
height = dInfoPtr->maxY -y; | ||
} | ||
oldY = dlPtr->oldY; | ||
/* | ||
* Update the lines we are going to scroll to show that they | ||
* have been copied. | ||
*/ | ||
while (1) { | ||
dlPtr->oldY = dlPtr->y; | ||
if (dlPtr->nextPtr == dlPtr2) { | ||
break; | ||
} | ||
dlPtr = dlPtr->nextPtr; | ||
} | ||
/* | ||
* Scan through the lines following the copied ones to see if | ||
* we are going to overwrite them with the copy operation. | ||
* If so, mark them for redisplay. | ||
*/ | ||
for ( ; dlPtr2 != NULL; dlPtr2 = dlPtr2->nextPtr) { | ||
if ((dlPtr2->oldY != -1) | ||
&& ((dlPtr2->oldY + dlPtr2->height) > y) | ||
&& (dlPtr2->oldY < (y + height))) { | ||
dlPtr2->oldY = -1; | ||
} | ||
} | ||
/* | ||
* Now scroll the lines. This may generate damage which we | ||
* handle by calling TextInvalidateRegion to mark the display | ||
* blocks as stale. | ||
*/ | ||
damageRgn = TkCreateRegion(); | ||
if (TkScrollWindow(textPtr->tkwin, dInfoPtr->scrollGC, | ||
dInfoPtr->x, oldY, | ||
(dInfoPtr->maxX - dInfoPtr->x), height, | ||
0, y - oldY, damageRgn)) { | ||
TextInvalidateRegion(textPtr, damageRgn); | ||
} | ||
numCopies++; | ||
TkDestroyRegion(damageRgn); | ||
} | ||
/* | ||
* Clear the REDRAW_PENDING flag here. This is actually pretty | ||
* tricky. We want to wait until *after* doing the scrolling, | ||
* since that could generate more areas to redraw and don't | ||
* want to reschedule a redisplay for them. On the other hand, | ||
* we can't wait until after all the redisplaying, because the | ||
* act of redisplaying could actually generate more redisplays | ||
* (e.g. in the case of a nested window with event bindings triggered | ||
* by redisplay). | ||
*/ | ||
dInfoPtr->flags &= ~REDRAW_PENDING; | ||
/* | ||
* Redraw the borders if that's needed. | ||
*/ | ||
if (dInfoPtr->flags & REDRAW_BORDERS) { | ||
if (tkTextDebug) { | ||
Tcl_SetVar2(interp, "tk_textRedraw", (char *) NULL, "borders", | ||
TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT); | ||
} | ||
if (textPtr->tkwin == NULL) { | ||
/* | ||
* The widget has been deleted. Don't do anything. | ||
*/ | ||
goto end; | ||
} | ||
Tk_Draw3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin), | ||
textPtr->border, textPtr->highlightWidth, | ||
textPtr->highlightWidth, | ||
Tk_Width(textPtr->tkwin) - 2*textPtr->highlightWidth, | ||
Tk_Height(textPtr->tkwin) - 2*textPtr->highlightWidth, | ||
textPtr->borderWidth, textPtr->relief); | ||
if (textPtr->highlightWidth != 0) { | ||
GC fgGC, bgGC; | ||
bgGC = Tk_GCForColor(textPtr->highlightBgColorPtr, | ||
Tk_WindowId(textPtr->tkwin)); | ||
if (textPtr->flags & GOT_FOCUS) { | ||
fgGC = Tk_GCForColor(textPtr->highlightColorPtr, | ||
Tk_WindowId(textPtr->tkwin)); | ||
TkpDrawHighlightBorder(textPtr->tkwin, fgGC, bgGC, | ||
textPtr->highlightWidth, Tk_WindowId(textPtr->tkwin)); | ||
} else { | ||
TkpDrawHighlightBorder(textPtr->tkwin, bgGC, bgGC, | ||
textPtr->highlightWidth, Tk_WindowId(textPtr->tkwin)); | ||
} | ||
} | ||
borders = textPtr->borderWidth + textPtr->highlightWidth; | ||
if (textPtr->padY > 0) { | ||
Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin), | ||
textPtr->border, borders, borders, | ||
Tk_Width(textPtr->tkwin) - 2*borders, textPtr->padY, | ||
0, TK_RELIEF_FLAT); | ||
Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin), | ||
textPtr->border, borders, | ||
Tk_Height(textPtr->tkwin) - borders - textPtr->padY, | ||
Tk_Width(textPtr->tkwin) - 2*borders, | ||
textPtr->padY, 0, TK_RELIEF_FLAT); | ||
} | ||
if (textPtr->padX > 0) { | ||
Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin), | ||
textPtr->border, borders, borders + textPtr->padY, | ||
textPtr->padX, | ||
Tk_Height(textPtr->tkwin) - 2*borders -2*textPtr->padY, | ||
0, TK_RELIEF_FLAT); | ||
Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin), | ||
textPtr->border, | ||
Tk_Width(textPtr->tkwin) - borders - textPtr->padX, | ||
borders + textPtr->padY, textPtr->padX, | ||
Tk_Height(textPtr->tkwin) - 2*borders -2*textPtr->padY, | ||
0, TK_RELIEF_FLAT); | ||
} | ||
dInfoPtr->flags &= ~REDRAW_BORDERS; | ||
} | ||
/* | ||
* Now we have to redraw the lines that couldn't be updated by | ||
* scrolling. First, compute the height of the largest line and | ||
* allocate an off-screen pixmap to use for double-buffered | ||
* displays. | ||
*/ | ||
maxHeight = -1; | ||
for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; | ||
dlPtr = dlPtr->nextPtr) { | ||
if ((dlPtr->height > maxHeight) && (dlPtr->oldY != dlPtr->y)) { | ||
maxHeight = dlPtr->height; | ||
} | ||
bottomY = dlPtr->y + dlPtr->height; | ||
} | ||
if (maxHeight > dInfoPtr->maxY) { | ||
maxHeight = dInfoPtr->maxY; | ||
} | ||
if (maxHeight > 0) { | ||
pixmap = Tk_GetPixmap(Tk_Display(textPtr->tkwin), | ||
Tk_WindowId(textPtr->tkwin), Tk_Width(textPtr->tkwin), | ||
maxHeight, Tk_Depth(textPtr->tkwin)); | ||
for (prevPtr = NULL, dlPtr = textPtr->dInfoPtr->dLinePtr; | ||
(dlPtr != NULL) && (dlPtr->y < dInfoPtr->maxY); | ||
prevPtr = dlPtr, dlPtr = dlPtr->nextPtr) { | ||
if (dlPtr->chunkPtr == NULL) continue; | ||
if (dlPtr->oldY != dlPtr->y) { | ||
if (tkTextDebug) { | ||
char string[TK_POS_CHARS]; | ||
TkTextPrintIndex(&dlPtr->index, string); | ||
Tcl_SetVar2(textPtr->interp, "tk_textRedraw", | ||
(char *) NULL, string, | ||
TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT); | ||
} | ||
DisplayDLine(textPtr, dlPtr, prevPtr, pixmap); | ||
if (dInfoPtr->dLinesInvalidated) { | ||
Tk_FreePixmap(Tk_Display(textPtr->tkwin), pixmap); | ||
return; | ||
} | ||
dlPtr->oldY = dlPtr->y; | ||
dlPtr->flags &= ~NEW_LAYOUT; | ||
} | ||
/*prevPtr = dlPtr;*/ | ||
} | ||
Tk_FreePixmap(Tk_Display(textPtr->tkwin), pixmap); | ||
} | ||
/* | ||
* See if we need to refresh the part of the window below the | ||
* last line of text (if there is any such area). Refresh the | ||
* padding area on the left too, since the insertion cursor might | ||
* have been displayed there previously). | ||
*/ | ||
if (dInfoPtr->topOfEof > dInfoPtr->maxY) { | ||
dInfoPtr->topOfEof = dInfoPtr->maxY; | ||
} | ||
if (bottomY < dInfoPtr->topOfEof) { | ||
if (tkTextDebug) { | ||
Tcl_SetVar2(textPtr->interp, "tk_textRedraw", | ||
(char *) NULL, "eof", | ||
TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT); | ||
} | ||
if (textPtr->tkwin == NULL) { | ||
/* | ||
* The widget has been deleted. Don't do anything. | ||
*/ | ||
goto end; | ||
} | ||
Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin), | ||
textPtr->border, dInfoPtr->x - textPtr->padX, bottomY, | ||
dInfoPtr->maxX - (dInfoPtr->x - textPtr->padX), | ||
dInfoPtr->topOfEof-bottomY, 0, TK_RELIEF_FLAT); | ||
} | ||
dInfoPtr->topOfEof = bottomY; | ||
doScrollbars: | ||
/* | ||
* Update the vertical scrollbar, if there is one. Note: it's | ||
* important to clear REDRAW_PENDING here, just in case the | ||
* scroll procedure does something that requires redisplay. | ||
*/ | ||
if (textPtr->flags & UPDATE_SCROLLBARS) { | ||
textPtr->flags &= ~UPDATE_SCROLLBARS; | ||
if (textPtr->yScrollCmd != NULL) { | ||
GetYView(textPtr->interp, textPtr, 1); | ||
} | ||
if (textPtr->tkwin == NULL) { | ||
/* | ||
* The widget has been deleted. Don't do anything. | ||
*/ | ||
goto end; | ||
} | ||
/* | ||
* Update the horizontal scrollbar, if any. | ||
*/ | ||
if (textPtr->xScrollCmd != NULL) { | ||
GetXView(textPtr->interp, textPtr, 1); | ||
} | ||
} | ||
end: | ||
Tcl_Release((ClientData) interp); | ||
} | ||
/* | ||
*---------------------------------------------------------------------- | ||
* | ||
* TkTextEventuallyRepick -- | ||
* | ||
* This procedure is invoked whenever something happens that | ||
* could change the current character or the tags associated | ||
* with it. | ||
* | ||
* Results: | ||
* None. | ||
* | ||
* Side effects: | ||
* A repick is scheduled as an idle handler. | ||
* | ||
*---------------------------------------------------------------------- | ||
*/ | ||
/* ARGSUSED */ | ||
void | ||
TkTextEventuallyRepick(textPtr) | ||
TkText *textPtr; /* Widget record for text widget. */ | ||
{ | ||
TextDInfo *dInfoPtr = textPtr->dInfoPtr; | ||
dInfoPtr->flags |= REPICK_NEEDED; | ||
if (!(dInfoPtr->flags & REDRAW_PENDING)) { | ||
dInfoPtr->flags |= REDRAW_PENDING; | ||
Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr); | ||
} | ||
} | ||
/* | ||
*---------------------------------------------------------------------- | ||
* | ||
* TkTextRedrawRegion -- | ||
* | ||
* This procedure is invoked to schedule a redisplay for a given | ||
* region of a text widget. The redisplay itself may not occur | ||
* immediately: it's scheduled as a when-idle handler. | ||
* | ||
* Results: | ||
* None. | ||
* | ||
* Side effects: | ||
* Information will eventually be redrawn on the screen. | ||
* | ||
*---------------------------------------------------------------------- | ||
*/ | ||
/* ARGSUSED */ | ||
void | ||
TkTextRedrawRegion(textPtr, x, y, width, height) | ||
TkText *textPtr; /* Widget record for text widget. */ | ||
int x, y; /* Coordinates of upper-left corner of area | ||
* to be redrawn, in pixels relative to | ||
* textPtr's window. */ | ||
int width, height; /* Width and height of area to be redrawn. */ | ||
{ | ||
TextDInfo *dInfoPtr = textPtr->dInfoPtr; | ||
TkRegion damageRgn = TkCreateRegion(); | ||
XRectangle rect; | ||
rect.x = x; | ||
rect.y = y; | ||
rect.width = width; | ||
rect.height = height; | ||
TkUnionRectWithRegion(&rect, damageRgn, damageRgn); | ||
TextInvalidateRegion(textPtr, damageRgn); | ||
if (!(dInfoPtr->flags & REDRAW_PENDING)) { | ||
dInfoPtr->flags |= REDRAW_PENDING; | ||
Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr); | ||
} | ||
TkDestroyRegion(damageRgn); | ||
} | ||
/* | ||
*---------------------------------------------------------------------- | ||
* | ||
* TextInvalidateRegion -- | ||
* | ||
* Mark a region of text as invalid. | ||
* | ||
* Results: | ||
* None. | ||
* | ||
* Side effects: | ||
* Updates the display information for the text widget. | ||
* | ||
*---------------------------------------------------------------------- | ||
*/ | ||
static void | ||
TextInvalidateRegion(textPtr, region) | ||
TkText *textPtr; /* Widget record for text widget. */ | ||
TkRegion region; /* Region of area to redraw. */ | ||
{ | ||
register DLine *dlPtr; | ||
TextDInfo *dInfoPtr = textPtr->dInfoPtr; | ||
int maxY, inset; | ||
XRectangle rect; | ||
/* | ||
* Find all lines that overlap the given region and mark them for | ||
* redisplay. | ||
*/ | ||
TkClipBox(region, &rect); | ||
maxY = rect.y + rect.height; | ||
for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; | ||
dlPtr = dlPtr->nextPtr) { | ||
if ((dlPtr->oldY != -1) && (TkRectInRegion(region, rect.x, dlPtr->y, | ||
rect.width, (unsigned int) dlPtr->height) != RectangleOut)) { | ||
dlPtr->oldY = -1; | ||
} | ||
} | ||
if (dInfoPtr->topOfEof < maxY) { | ||
dInfoPtr->topOfEof = maxY; | ||
} | ||
/* | ||
* Schedule the redisplay operation if there isn't one already | ||
* scheduled. | ||
*/ | ||
inset = textPtr->borderWidth + textPtr->highlightWidth; | ||
if ((rect.x < (inset + textPtr->padX)) | ||
|| (rect.y < (inset + textPtr->padY)) | ||
|| ((int) (rect.x + rect.width) > (Tk_Width(textPtr->tkwin) | ||
- inset - textPtr->padX)) | ||
|| (maxY > (Tk_Height(textPtr->tkwin) - inset - textPtr->padY))) { | ||
dInfoPtr->flags |= REDRAW_BORDERS; | ||
} | ||
} | ||
/* | ||
*---------------------------------------------------------------------- | ||
* | ||
* TkTextChanged -- | ||
* | ||
* This procedure is invoked when info in a text widget is about | ||
* to be modified in a way that changes how it is displayed (e.g. | ||
* characters were inserted or deleted, or tag information was | ||
* changed). This procedure must be called *before* a change is | ||
* made, so that indexes in the display information are still | ||
* valid. | ||
* | ||
* Results: | ||
* None. | ||
* | ||
* Side effects: | ||
* The range of character between index1Ptr (inclusive) and | ||
* index2Ptr (exclusive) will be redisplayed at some point in the | ||
* future (the actual redisplay is scheduled as a when-idle handler). | ||
* | ||
*---------------------------------------------------------------------- | ||
*/ | ||
void | ||
TkTextChanged(textPtr, index1Ptr, index2Ptr) | ||
TkText *textPtr; /* Widget record for text widget. */ | ||
TkTextIndex *index1Ptr; /* Index of first character to redisplay. */ | ||
TkTextIndex *index2Ptr; /* Index of character just after last one | ||
* to redisplay. */ | ||
{ | ||
TextDInfo *dInfoPtr = textPtr->dInfoPtr; | ||
DLine *firstPtr, *lastPtr; | ||
TkTextIndex rounded; | ||
/* | ||
* Schedule both a redisplay and a recomputation of display information. | ||
* It's done here rather than the end of the procedure for two reasons: | ||
* | ||
* 1. If there are no display lines to update we'll want to return | ||
* immediately, well before the end of the procedure. | ||
* 2. It's important to arrange for the redisplay BEFORE calling | ||
* FreeDLines. The reason for this is subtle and has to do with | ||
* embedded windows. The chunk delete procedure for an embedded | ||
* window will schedule an idle handler to unmap the window. | ||
* However, we want the idle handler for redisplay to be called | ||
* first, so that it can put the embedded window back on the screen | ||
* again (if appropriate). This will prevent the window from ever | ||
* being unmapped, and thereby avoid flashing. | ||
*/ | ||
if (!(dInfoPtr->flags & REDRAW_PENDING)) { | ||
Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr); | ||
} | ||
dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED; | ||
/* | ||
* Find the DLines corresponding to index1Ptr and index2Ptr. There | ||
* is one tricky thing here, which is that we have to relayout in | ||
* units of whole text lines: round index1Ptr back to the beginning | ||
* of its text line, and include all the display lines after index2, | ||
* up to the end of its text line. This is necessary because the | ||
* indices stored in the display lines will no longer be valid. It's | ||
* also needed because any edit could change the way lines wrap. | ||
*/ | ||
rounded = *index1Ptr; | ||
rounded.byteIndex = 0; | ||
firstPtr = FindDLine(dInfoPtr->dLinePtr, &rounded); | ||
if (firstPtr == NULL) { | ||
return; | ||
} | ||
lastPtr = FindDLine(dInfoPtr->dLinePtr, index2Ptr); | ||
while ((lastPtr != NULL) | ||
&& (lastPtr->index.linePtr == index2Ptr->linePtr)) { | ||
lastPtr = lastPtr->nextPtr; | ||
} | ||
/* | ||
* Delete all the DLines from firstPtr up to but not including lastPtr. | ||
*/ | ||
FreeDLines(textPtr, firstPtr, lastPtr, 1); | ||
} | ||
/* | ||
*---------------------------------------------------------------------- | ||
* | ||
* TkTextRedrawTag -- | ||
* | ||
* This procedure is invoked to request a redraw of all characters | ||
* in a given range that have a particular tag on or off. It's | ||
* called, for example, when tag options change. | ||
* | ||
* Results: | ||
* None. | ||
* | ||
* Side effects: | ||
* Information on the screen may be redrawn, and the layout of | ||
* the screen may change. | ||
* | ||
*---------------------------------------------------------------------- | ||
*/ | ||
void | ||
TkTextRedrawTag(textPtr, index1Ptr, index2Ptr, tagPtr, withTag) | ||
TkText *textPtr; /* Widget record for text widget. */ | ||
TkTextIndex *index1Ptr; /* First character in range to consider | ||
* for redisplay. NULL means start at | ||
* beginning of text. */ | ||
TkTextIndex *index2Ptr; /* Character just after last one to consider | ||
* for redisplay. NULL means process all | ||
* the characters in the text. */ | ||
TkTextTag *tagPtr; /* Information about tag. */ | ||
int withTag; /* 1 means redraw characters that have the | ||
* tag, 0 means redraw those without. */ | ||
{ | ||
register DLine *dlPtr; | ||
DLine *endPtr; | ||
int tagOn; | ||
TkTextSearch search; | ||
TextDInfo *dInfoPtr = textPtr->dInfoPtr; | ||
TkTextIndex *curIndexPtr; | ||
TkTextIndex endOfText, *endIndexPtr; | ||
/* | ||
* Round up the starting position if it's before the first line | ||
* visible on the screen (we only care about what's on the screen). | ||
*/ | ||
dlPtr = dInfoPtr->dLinePtr; | ||
if (dlPtr == NULL) { | ||
return; | ||
} | ||
if ((index1Ptr == NULL) || (TkTextIndexCmp(&dlPtr->index, index1Ptr) > 0)) { | ||
index1Ptr = &dlPtr->index; | ||
} | ||
/* | ||
* Set the stopping position if it wasn't specified. | ||
*/ | ||
if (index2Ptr == NULL) { | ||
index2Ptr = TkTextMakeByteIndex(textPtr->tree, | ||
TkBTreeNumLines(textPtr->tree), 0, &endOfText); | ||
} | ||
/* | ||
* Initialize a search through all transitions on the tag, starting | ||
* with the first transition where the tag's current state is different | ||
* from what it will eventually be. | ||
*/ | ||
TkBTreeStartSearch(index1Ptr, index2Ptr, tagPtr, &search); | ||
/* | ||
* Make our own curIndex because at this point search.curIndex | ||
* may not equal index1Ptr->curIndex in the case the first tag toggle | ||
* comes after index1Ptr (See the use of FindTagStart in TkBTreeStartSearch) | ||
*/ | ||
curIndexPtr = index1Ptr; | ||
tagOn = TkBTreeCharTagged(index1Ptr, tagPtr); | ||
if (tagOn != withTag) { | ||
if (!TkBTreeNextTag(&search)) { | ||
return; | ||
} | ||
curIndexPtr = &search.curIndex; | ||
} | ||
/* | ||
* Schedule a redisplay and layout recalculation if they aren't | ||
* already pending. This has to be done before calling FreeDLines, | ||
* for the reason given in TkTextChanged. | ||
*/ | ||
if (!(dInfoPtr->flags & REDRAW_PENDING)) { | ||
Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr); | ||
} | ||
dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED; | ||
/* | ||
* Each loop through the loop below is for one range of characters | ||
* where the tag's current state is different than its eventual | ||
* state. At the top of the loop, search contains information about | ||
* the first character in the range. | ||
*/ | ||
while (1) { | ||
/* | ||
* Find the first DLine structure in the range. Note: if the | ||
* desired character isn't the first in its text line, then look | ||
* for the character just before it instead. This is needed to | ||
* handle the case where the first character of a wrapped | ||
* display line just got smaller, so that it now fits on the | ||
* line before: need to relayout the line containing the | ||
* previous character. | ||
*/ | ||
if (curIndexPtr->byteIndex == 0) { | ||
dlPtr = FindDLine(dlPtr, curIndexPtr); | ||
} else { | ||
TkTextIndex tmp; | ||
tmp = *curIndexPtr; | ||
tmp.byteIndex -= 1; | ||
dlPtr = FindDLine(dlPtr, &tmp); | ||
} | ||
if (dlPtr == NULL) { | ||
break; | ||
} | ||
/* | ||
* Find the first DLine structure that's past the end of the range. | ||
*/ | ||
if (!TkBTreeNextTag(&search)) { | ||
endIndexPtr = index2Ptr; | ||
} else { | ||
curIndexPtr = &search.curIndex; | ||
endIndexPtr = curIndexPtr; | ||
} | ||
endPtr = FindDLine(dlPtr, endIndexPtr); | ||
if ((endPtr != NULL) && (endPtr->index.linePtr == endIndexPtr->linePtr) | ||
&& (endPtr->index.byteIndex < endIndexPtr->byteIndex)) { | ||
endPtr = endPtr->nextPtr; | ||
} | ||
/* | ||
* Delete all of the display lines in the range, so that they'll | ||
* be re-layed out and redrawn. | ||
*/ | ||
FreeDLines(textPtr, dlPtr, endPtr, 1); | ||
dlPtr = endPtr; | ||
/* | ||
* Find the first text line in the next range. | ||
*/ | ||
if (!TkBTreeNextTag(&search)) { | ||
break; | ||
} | ||
} | ||
} | ||
/* | ||
*---------------------------------------------------------------------- | ||
* | ||
* TkTextRelayoutWindow -- | ||
* | ||
* This procedure is called when something has happened that | ||
* invalidates the whole layout of characters on the screen, such | ||
* as a change in a configuration option for the overall text | ||
* widget or a change in the window size. It causes all display | ||
* information to be recomputed and the window to be redrawn. | ||
* | ||
* Results: | ||
* None. | ||
* | ||
* Side effects: | ||
* All the display information will be recomputed for the window | ||
* and the window will be redrawn. | ||
* | ||
*---------------------------------------------------------------------- | ||
*/ | ||
void | ||
TkTextRelayoutWindow(textPtr) | ||
TkText *textPtr; /* Widget record for text widget. */ | ||
{ | ||
TextDInfo *dInfoPtr = textPtr->dInfoPtr; | ||
GC new; | ||
XGCValues gcValues; | ||
/* | ||
* Schedule the window redisplay. See TkTextChanged for the | ||
* reason why this has to be done before any calls to FreeDLines. | ||
*/ | ||
if (!(dInfoPtr->flags & REDRAW_PENDING)) { | ||
Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr); | ||
} | ||
dInfoPtr->flags |= REDRAW_PENDING|REDRAW_BORDERS|DINFO_OUT_OF_DATE | ||
|REPICK_NEEDED; | ||
/* | ||
* (Re-)create the graphics context for drawing the traversal | ||
* highlight. | ||
*/ | ||
gcValues.graphics_exposures = False; | ||
new = Tk_GetGC(textPtr->tkwin, GCGraphicsExposures, &gcValues); | ||
if (dInfoPtr->copyGC != None) { | ||
Tk_FreeGC(textPtr->display, dInfoPtr->copyGC); | ||
} | ||
dInfoPtr->copyGC = new; | ||
/* | ||
* Throw away all the current layout information. | ||
*/ | ||
FreeDLines(textPtr, dInfoPtr->dLinePtr, (DLine *) NULL, 1); | ||
dInfoPtr->dLinePtr = NULL; | ||
/* | ||
* Recompute some overall things for the layout. Even if the | ||
* window gets very small, pretend that there's at least one | ||
* pixel of drawing space in it. | ||
*/ | ||
if (textPtr->highlightWidth < 0) { | ||
textPtr->highlightWidth = 0; | ||
} | ||
dInfoPtr->x = textPtr->highlightWidth + textPtr->borderWidth | ||
+ textPtr->padX; | ||
dInfoPtr->y = textPtr->highlightWidth + textPtr->borderWidth | ||
+ textPtr->padY; | ||
dInfoPtr->maxX = Tk_Width(textPtr->tkwin) - textPtr->highlightWidth | ||
- textPtr->borderWidth - textPtr->padX; | ||
if (dInfoPtr->maxX <= dInfoPtr->x) { | ||
dInfoPtr->maxX = dInfoPtr->x + 1; | ||
} | ||
dInfoPtr->maxY = Tk_Height(textPtr->tkwin) - textPtr->highlightWidth | ||
- textPtr->borderWidth - textPtr->padY; | ||
if (dInfoPtr->maxY <= dInfoPtr->y) { | ||
dInfoPtr->maxY = dInfoPtr->y + 1; | ||
} | ||
dInfoPtr->topOfEof = dInfoPtr->maxY; | ||
/* | ||
* If the upper-left character isn't the first in a line, recompute | ||
* it. This is necessary because a change in the window's size | ||
* or options could change the way lines wrap. | ||
*/ | ||
if (textPtr->topIndex.byteIndex != 0) { | ||
MeasureUp(textPtr, &textPtr->topIndex, 0, &textPtr->topIndex); | ||
} | ||
/* | ||
* Invalidate cached scrollbar positions, so that scrollbars | ||
* sliders will be udpated. | ||
*/ | ||
dInfoPtr->xScrollFirst = dInfoPtr->xScrollLast = -1; | ||
dInfoPtr->yScrollFirst = dInfoPtr->yScrollLast = -1; | ||
} | ||
/* | ||
*---------------------------------------------------------------------- | ||
* | ||
* TkTextSetYView -- | ||
* | ||
* This procedure is called to specify what lines are to be | ||
* displayed in a text widget. | ||
* | ||
* Results: | ||
* None. | ||
* | ||
* Side effects: | ||
* The display will (eventually) be updated so that the position | ||
* given by "indexPtr" is visible on the screen at the position | ||
* determined by "pickPlace". | ||
* | ||
*---------------------------------------------------------------------- | ||
*/ | ||
void | ||
TkTextSetYView(textPtr, indexPtr, pickPlace) | ||
TkText *textPtr; /* Widget record for text widget. */ | ||
TkTextIndex *indexPtr; /* Position that is to appear somewhere | ||
* in the view. */ | ||
int pickPlace; /* 0 means topLine must appear at top of | ||
* screen. 1 means we get to pick where it | ||
* appears: minimize screen motion or else | ||
* display line at center of screen. */ | ||
{ | ||
TextDInfo *dInfoPtr = textPtr->dInfoPtr; | ||
register DLine *dlPtr; | ||
int bottomY, close, lineIndex; | ||
TkTextIndex tmpIndex, rounded; | ||
Tk_FontMetrics fm; | ||
/* | ||
* If the specified position is the extra line at the end of the | ||
* text, round it back to the last real line. | ||
*/ | ||
lineIndex = TkBTreeLineIndex(indexPtr->linePtr); | ||
if (lineIndex == TkBTreeNumLines(indexPtr->tree)) { | ||
TkTextIndexBackChars(indexPtr, 1, &rounded); | ||
indexPtr = &rounded; | ||
} | ||
if (!pickPlace) { | ||
/* | ||
* The specified position must go at the top of the screen. | ||
* Just leave all the DLine's alone: we may be able to reuse | ||
* some of the information that's currently on the screen | ||
* without redisplaying it all. | ||
*/ | ||
if (indexPtr->byteIndex == 0) { | ||
textPtr->topIndex = *indexPtr; | ||
} else { | ||
MeasureUp(textPtr, indexPtr, 0, &textPtr->topIndex); | ||
} | ||
goto scheduleUpdate; | ||
} | ||
/* | ||
* We have to pick where to display the index. First, bring | ||
* the display information up to date and see if the index will be | ||
* completely visible in the current screen configuration. If so | ||
* then there's nothing to do. | ||
*/ | ||
if (dInfoPtr->flags & DINFO_OUT_OF_DATE) { | ||
UpdateDisplayInfo(textPtr); | ||
} | ||
dlPtr = FindDLine(dInfoPtr->dLinePtr, indexPtr); | ||
if (dlPtr != NULL) { | ||
if ((dlPtr->y + dlPtr->height) > dInfoPtr->maxY) { | ||
/* | ||
* Part of the line hangs off the bottom of the screen; | ||
* pretend the whole line is off-screen. | ||
*/ | ||
dlPtr = NULL; | ||
} else if ((dlPtr->index.linePtr == indexPtr->linePtr) | ||
&& (dlPtr->index.byteIndex <= indexPtr->byteIndex)) { | ||
return; | ||
} | ||
} | ||
/* | ||
* The desired line isn't already on-screen. Figure out what | ||
* it means to be "close" to the top or bottom of the screen. | ||
* Close means within 1/3 of the screen height or within three | ||
* lines, whichever is greater. Add one extra line also, to | ||
* account for the way MeasureUp rounds. | ||
*/ | ||
Tk_GetFontMetrics(textPtr->tkfont, &fm); | ||
bottomY = (dInfoPtr->y + dInfoPtr->maxY + fm.linespace)/2; | ||
close = (dInfoPtr->maxY - dInfoPtr->y)/3; | ||
if (close < 3*fm.linespace) { | ||
close = 3*fm.linespace; | ||
} | ||
close += fm.linespace; | ||
if (dlPtr != NULL) { | ||
/* | ||
* The desired line is above the top of screen. If it is | ||
* "close" to the top of the window then make it the top | ||
* line on the screen. | ||
*/ | ||
MeasureUp(textPtr, &textPtr->topIndex, close, &tmpIndex); | ||
if (TkTextIndexCmp(&tmpIndex, indexPtr) <= 0) { | ||
MeasureUp(textPtr, indexPtr, 0, &textPtr->topIndex); | ||
goto scheduleUpdate; | ||
} | ||
} else { | ||
/* | ||
* The desired line is below the bottom of the screen. If it is | ||
* "close" to the bottom of the screen then position it at the | ||
* bottom of the screen. | ||
*/ | ||
MeasureUp(textPtr, indexPtr, close, &tmpIndex); | ||
if (FindDLine(dInfoPtr->dLinePtr, &tmpIndex) != NULL) { | ||
bottomY = dInfoPtr->maxY - dInfoPtr->y; | ||
} | ||
} | ||
/* | ||
* Our job now is to arrange the display so that indexPtr appears | ||
* as low on the screen as possible but with its bottom no lower | ||
* than bottomY. BottomY is the bottom of the window if the | ||
* desired line is just below the current screen, otherwise it | ||
* is a half-line lower than the center of the window. | ||
*/ | ||
MeasureUp(textPtr, indexPtr, bottomY, &textPtr->topIndex); | ||
scheduleUpdate: | ||
if (!(dInfoPtr->flags & REDRAW_PENDING)) { | ||
Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr); | ||
} | ||
dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED; | ||
} | ||
/* | ||
*-------------------------------------------------------------- | ||
* | ||
* MeasureUp -- | ||
* | ||
* Given one index, find the index of the first character | ||
* on the highest display line that would be displayed no more | ||
* than "distance" pixels above the given index. | ||
* | ||
* Results: | ||
* *dstPtr is filled in with the index of the first character | ||
* on a display line. The display line is found by measuring | ||
* up "distance" pixels above the pixel just below an imaginary | ||
* display line that contains srcPtr. If the display line | ||
* that covers this coordinate actually extends above the | ||
* coordinate, then return the index of the next lower line | ||
* instead (i.e. the returned index will be completely visible | ||
* at or below the given y-coordinate). | ||
* | ||
* Side effects: | ||
* None. | ||
* | ||
*-------------------------------------------------------------- | ||
*/ | ||
static void | ||
MeasureUp(textPtr, srcPtr, distance, dstPtr) | ||
TkText *textPtr; /* Text widget in which to measure. */ | ||
TkTextIndex *srcPtr; /* Index of character from which to start | ||
* measuring. */ | ||
int distance; /* Vertical distance in pixels measured | ||
* from the pixel just below the lowest | ||
* one in srcPtr's line. */ | ||
TkTextIndex *dstPtr; /* Index to fill in with result. */ | ||
{ | ||
int lineNum; /* Number of current line. */ | ||
int bytesToCount; /* Maximum number of bytes to measure in | ||
* current line. */ | ||
TkTextIndex bestIndex; /* Best candidate seen so far for result. */ | ||
TkTextIndex index; | ||
DLine *dlPtr, *lowestPtr; | ||
int noBestYet; /* 1 means bestIndex hasn't been set. */ | ||
noBestYet = 1; | ||
bytesToCount = srcPtr->byteIndex + 1; | ||
index.tree = srcPtr->tree; | ||
for (lineNum = TkBTreeLineIndex(srcPtr->linePtr); lineNum >= 0; | ||
lineNum--) { | ||
/* | ||
* Layout an entire text line (potentially > 1 display line). | ||
* For the first line, which contains srcPtr, only layout the | ||
* part up through srcPtr (bytesToCount is non-infinite to | ||
* accomplish this). Make a list of all the display lines | ||
* in backwards order (the lowest DLine on the screen is first | ||
* in the list). | ||
*/ | ||
index.linePtr = TkBTreeFindLine(srcPtr->tree, lineNum); | ||
index.byteIndex = 0; | ||
lowestPtr = NULL; | ||
do { | ||
dlPtr = LayoutDLine(textPtr, &index); | ||
dlPtr->nextPtr = lowestPtr; | ||
lowestPtr = dlPtr; | ||
TkTextIndexForwBytes(&index, dlPtr->byteCount, &index); | ||
bytesToCount -= dlPtr->byteCount; | ||
} while ((bytesToCount > 0) && (index.linePtr == dlPtr->index.linePtr)); | ||
/* | ||
* Scan through the display lines to see if we've covered enough | ||
* vertical distance. If so, save the starting index for the | ||
* line at the desired location. | ||
*/ | ||
for (dlPtr = lowestPtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) { | ||
distance -= dlPtr->height; | ||
if (distance < 0) { | ||
*dstPtr = (noBestYet) ? dlPtr->index : bestIndex; | ||
break; | ||
} | ||
bestIndex = dlPtr->index; | ||
noBestYet = 0; | ||
} | ||
/* | ||
* Discard the display lines, then either return or prepare | ||
* for the next display line to lay out. | ||
*/ | ||
FreeDLines(textPtr, lowestPtr, (DLine *) NULL, 0); | ||
if (distance < 0) { | ||
return; | ||
} | ||
bytesToCount = INT_MAX; /* Consider all chars. in next line. */ | ||
} | ||
/* | ||
* Ran off the beginning of the text. Return the first character | ||
* in the text. | ||
*/ | ||
TkTextMakeByteIndex(textPtr->tree, 0, 0, dstPtr); | ||
} | ||
/* | ||
*-------------------------------------------------------------- | ||
* | ||
* TkTextSeeCmd -- | ||
* | ||
* This procedure is invoked to process the "see" option for | ||
* the widget command for text widgets. See the user documentation | ||
* for details on what it does. | ||
* | ||
* Results: | ||
* A standard Tcl result. | ||
* | ||
* Side effects: | ||
* See the user documentation. | ||
* | ||
*-------------------------------------------------------------- | ||
*/ | ||
int | ||
TkTextSeeCmd(textPtr, interp, argc, argv) | ||
TkText *textPtr; /* Information about text widget. */ | ||
Tcl_Interp *interp; /* Current interpreter. */ | ||
int argc; /* Number of arguments. */ | ||
char **argv; /* Argument strings. Someone else has already | ||
* parsed this command enough to know that | ||
* argv[1] is "see". */ | ||
{ | ||
TextDInfo *dInfoPtr = textPtr->dInfoPtr; | ||
TkTextIndex index; | ||
int x, y, width, height, lineWidth, byteCount, oneThird, delta; | ||
DLine *dlPtr; | ||
TkTextDispChunk *chunkPtr; | ||
if (argc != 3) { | ||
Tcl_AppendResult(interp, "wrong # args: should be \"", | ||
argv[0], " see index\"", (char *) NULL); | ||
return TCL_ERROR; | ||
} | ||
if (TkTextGetIndex(interp, textPtr, argv[2], &index) != TCL_OK) { | ||
return TCL_ERROR; | ||
} | ||
/* | ||
* If the specified position is the extra line at the end of the | ||
* text, round it back to the last real line. | ||
*/ | ||
if (TkBTreeLineIndex(index.linePtr) == TkBTreeNumLines(index.tree)) { | ||
TkTextIndexBackChars(&index, 1, &index); | ||
} | ||
/* | ||
* First get the desired position into the vertical range of the window. | ||
*/ | ||
TkTextSetYView(textPtr, &index, 1); | ||
/* | ||
* Now make sure that the character is in view horizontally. | ||
*/ | ||
if (dInfoPtr->flags & DINFO_OUT_OF_DATE) { | ||
UpdateDisplayInfo(textPtr); | ||
} | ||
lineWidth = dInfoPtr->maxX - dInfoPtr->x; | ||
if (dInfoPtr->maxLength < lineWidth) { | ||
return TCL_OK; | ||
} | ||
/* | ||
* Find the chunk that contains the desired index. | ||
*/ | ||
dlPtr = FindDLine(dInfoPtr->dLinePtr, &index); | ||
byteCount = index.byteIndex - dlPtr->index.byteIndex; | ||
for (chunkPtr = dlPtr->chunkPtr; chunkPtr!=NULL ; chunkPtr = chunkPtr->nextPtr) { | ||
if (byteCount < chunkPtr->numBytes) { | ||
break; | ||
} | ||
byteCount -= chunkPtr->numBytes; | ||
} | ||
/* | ||
* Call a chunk-specific procedure to find the horizontal range of | ||
* the character within the chunk. | ||
*/ | ||
if (chunkPtr!=NULL) { /* chunkPtr==NULL iff trying to see in elided region */ | ||
(*chunkPtr->bboxProc)(chunkPtr, byteCount, dlPtr->y + dlPtr->spaceAbove, | ||
dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow, | ||
dlPtr->baseline - dlPtr->spaceAbove, &x, &y, &width, | ||
&height); | ||
delta = x - dInfoPtr->curPixelOffset; | ||
oneThird = lineWidth/3; | ||
if (delta < 0) { | ||
if (delta < -oneThird) { | ||
dInfoPtr->newByteOffset = (x - lineWidth/2)/textPtr->charWidth; | ||
} else { | ||
dInfoPtr->newByteOffset -= ((-delta) + textPtr->charWidth - 1) | ||
/ textPtr->charWidth; | ||
} | ||
} else { | ||
delta -= (lineWidth - width); | ||
if (delta > 0) { | ||
if (delta > oneThird) { | ||
dInfoPtr->newByteOffset = (x - lineWidth/2)/textPtr->charWidth; | ||
} else { | ||
dInfoPtr->newByteOffset += (delta + textPtr->charWidth - 1) | ||
/ textPtr->charWidth; | ||
} | ||
} else { | ||
return TCL_OK; | ||
} | ||
}} | ||
dInfoPtr->flags |= DINFO_OUT_OF_DATE; | ||
if (!(dInfoPtr->flags & REDRAW_PENDING)) { | ||
dInfoPtr->flags |= REDRAW_PENDING; | ||
Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr); | ||
} | ||
return TCL_OK; | ||
} | ||
/* | ||
*-------------------------------------------------------------- | ||
* | ||
* TkTextXviewCmd -- | ||
* | ||
* This procedure is invoked to process the "xview" option for | ||
* the widget command for text widgets. See the user documentation | ||
* for details on what it does. | ||
* | ||
* Results: | ||
* A standard Tcl result. | ||
* | ||
* Side effects: | ||
* See the user documentation. | ||
* | ||
*-------------------------------------------------------------- | ||
*/ | ||
int | ||
TkTextXviewCmd(textPtr, interp, argc, argv) | ||
TkText *textPtr; /* Information about text widget. */ | ||
Tcl_Interp *interp; /* Current interpreter. */ | ||
int argc; /* Number of arguments. */ | ||
char **argv; /* Argument strings. Someone else has already | ||
* parsed this command enough to know that | ||
* argv[1] is "xview". */ | ||
{ | ||
TextDInfo *dInfoPtr = textPtr->dInfoPtr; | ||
int type, charsPerPage, count, newOffset; | ||
double fraction; | ||
if (dInfoPtr->flags & DINFO_OUT_OF_DATE) { | ||
UpdateDisplayInfo(textPtr); | ||
} | ||
if (argc == 2) { | ||
GetXView(interp, textPtr, 0); | ||
return TCL_OK; | ||
} | ||
newOffset = dInfoPtr->newByteOffset; | ||
type = Tk_GetScrollInfo(interp, argc, argv, &fraction, &count); | ||
switch (type) { | ||
case TK_SCROLL_ERROR: | ||
return TCL_ERROR; | ||
case TK_SCROLL_MOVETO: | ||
if (fraction > 1.0) { | ||
fraction = 1.0; | ||
} | ||
if (fraction < 0) { | ||
fraction = 0; | ||
} | ||
newOffset = (int) (((fraction * dInfoPtr->maxLength) / textPtr->charWidth) | ||
+ 0.5); | ||
break; | ||
case TK_SCROLL_PAGES: | ||
charsPerPage = ((dInfoPtr->maxX - dInfoPtr->x) / textPtr->charWidth) | ||
- 2; | ||
if (charsPerPage < 1) { | ||
charsPerPage = 1; | ||
} | ||
newOffset += charsPerPage * count; | ||
break; | ||
case TK_SCROLL_UNITS: | ||
newOffset += count; | ||
break; | ||
} | ||
dInfoPtr->newByteOffset = newOffset; | ||
dInfoPtr->flags |= DINFO_OUT_OF_DATE; | ||
if (!(dInfoPtr->flags & REDRAW_PENDING)) { | ||
dInfoPtr->flags |= REDRAW_PENDING; | ||
Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr); | ||
} | ||
return TCL_OK; | ||
} | ||
/* | ||
*---------------------------------------------------------------------- | ||
* | ||
* ScrollByLines -- | ||
* | ||
* This procedure is called to scroll a text widget up or down | ||
* by a given number of lines. | ||
* | ||
* Results: | ||
* None. | ||
* | ||
* Side effects: | ||
* The view in textPtr's window changes to reflect the value | ||
* of "offset". | ||
* | ||
*---------------------------------------------------------------------- | ||
*/ | ||
static void | ||
ScrollByLines(textPtr, offset) | ||
TkText *textPtr; /* Widget to scroll. */ | ||
int offset; /* Amount by which to scroll, in *screen* | ||
* lines. Positive means that information | ||
* later in text becomes visible, negative | ||
* means that information earlier in the | ||
* text becomes visible. */ | ||
{ | ||
int i, bytesToCount, lineNum; | ||
TkTextIndex new, index; | ||
TkTextLine *lastLinePtr; | ||
TextDInfo *dInfoPtr = textPtr->dInfoPtr; | ||
DLine *dlPtr, *lowestPtr; | ||
if (offset < 0) { | ||
/* | ||
* Must scroll up (to show earlier information in the text). | ||
* The code below is similar to that in MeasureUp, except that | ||
* it counts lines instead of pixels. | ||
*/ | ||
bytesToCount = textPtr->topIndex.byteIndex + 1; | ||
index.tree = textPtr->tree; | ||
offset--; /* Skip line containing topIndex. */ | ||
for (lineNum = TkBTreeLineIndex(textPtr->topIndex.linePtr); | ||
lineNum >= 0; lineNum--) { | ||
index.linePtr = TkBTreeFindLine(textPtr->tree, lineNum); | ||
index.byteIndex = 0; | ||
lowestPtr = NULL; | ||
do { | ||
dlPtr = LayoutDLine(textPtr, &index); | ||
dlPtr->nextPtr = lowestPtr; | ||
lowestPtr = dlPtr; | ||
TkTextIndexForwBytes(&index, dlPtr->byteCount, &index); | ||
bytesToCount -= dlPtr->byteCount; | ||
} while ((bytesToCount > 0) | ||
&& (index.linePtr == dlPtr->index.linePtr)); | ||
for (dlPtr = lowestPtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) { | ||
offset++; | ||
if (offset == 0) { | ||
textPtr->topIndex = dlPtr->index; | ||
break; | ||
} | ||
} | ||
/* | ||
* Discard the display lines, then either return or prepare | ||
* for the next display line to lay out. | ||
*/ | ||
FreeDLines(textPtr, lowestPtr, (DLine *) NULL, 0); | ||
if (offset >= 0) { | ||
goto scheduleUpdate; | ||
} | ||
bytesToCount = INT_MAX; | ||
} | ||
/* | ||
* Ran off the beginning of the text. Return the first character | ||
* in the text. | ||
*/ | ||
TkTextMakeByteIndex(textPtr->tree, 0, 0, &textPtr->topIndex); | ||
} else { | ||
/* | ||
* Scrolling down, to show later information in the text. | ||
* Just count lines from the current top of the window. | ||
*/ | ||
lastLinePtr = TkBTreeFindLine(textPtr->tree, | ||
TkBTreeNumLines(textPtr->tree)); | ||
for (i = 0; i < offset; i++) { | ||
dlPtr = LayoutDLine(textPtr, &textPtr->topIndex); | ||
if (dlPtr->length == 0 && dlPtr->height == 0) offset++; | ||
dlPtr->nextPtr = NULL; | ||
TkTextIndexForwBytes(&textPtr->topIndex, dlPtr->byteCount, &new); | ||
FreeDLines(textPtr, dlPtr, (DLine *) NULL, 0); | ||
if (new.linePtr == lastLinePtr) { | ||
break; | ||
} | ||
textPtr->topIndex = new; | ||
} | ||
} | ||
scheduleUpdate: | ||
if (!(dInfoPtr->flags & REDRAW_PENDING)) { | ||
Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr); | ||
} | ||
dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED; | ||
} | ||
/* | ||
*-------------------------------------------------------------- | ||
* | ||
* TkTextYviewCmd -- | ||
* | ||
* This procedure is invoked to process the "yview" option for | ||
* the widget command for text widgets. See the user documentation | ||
* for details on what it does. | ||
* | ||
* Results: | ||
* A standard Tcl result. | ||
* | ||
* Side effects: | ||
* See the user documentation. | ||
* | ||
*-------------------------------------------------------------- | ||
*/ | ||
int | ||
TkTextYviewCmd(textPtr, interp, argc, argv) | ||
TkText *textPtr; /* Information about text widget. */ | ||
Tcl_Interp *interp; /* Current interpreter. */ | ||
int argc; /* Number of arguments. */ | ||
char **argv; /* Argument strings. Someone else has already | ||
* parsed this command enough to know that | ||
* argv[1] is "yview". */ | ||
{ | ||
TextDInfo *dInfoPtr = textPtr->dInfoPtr; | ||
int pickPlace, lineNum, type, bytesInLine; | ||
Tk_FontMetrics fm; | ||
int pixels, count; | ||
size_t switchLength; | ||
double fraction; | ||
TkTextIndex index, new; | ||
TkTextLine *lastLinePtr; | ||
DLine *dlPtr; | ||
if (dInfoPtr->flags & DINFO_OUT_OF_DATE) { | ||
UpdateDisplayInfo(textPtr); | ||
} | ||
if (argc == 2) { | ||
GetYView(interp, textPtr, 0); | ||
return TCL_OK; | ||
} | ||
/* | ||
* Next, handle the old syntax: "pathName yview ?-pickplace? where" | ||
*/ | ||
pickPlace = 0; | ||
if (argv[2][0] == '-') { | ||
switchLength = strlen(argv[2]); | ||
if ((switchLength >= 2) | ||
&& (strncmp(argv[2], "-pickplace", switchLength) == 0)) { | ||
pickPlace = 1; | ||
if (argc != 4) { | ||
Tcl_AppendResult(interp, "wrong # args: should be \"", | ||
argv[0], " yview -pickplace lineNum|index\"", | ||
(char *) NULL); | ||
return TCL_ERROR; | ||
} | ||
} | ||
} | ||
if ((argc == 3) || pickPlace) { | ||
if (Tcl_GetInt(interp, argv[2+pickPlace], &lineNum) == TCL_OK) { | ||
TkTextMakeByteIndex(textPtr->tree, lineNum, 0, &index); | ||
TkTextSetYView(textPtr, &index, 0); | ||
return TCL_OK; | ||
} | ||
/* | ||
* The argument must be a regular text index. | ||
*/ | ||
Tcl_ResetResult(interp); | ||
if (TkTextGetIndex(interp, textPtr, argv[2+pickPlace], | ||
&index) != TCL_OK) { | ||
return TCL_ERROR; | ||
} | ||
TkTextSetYView(textPtr, &index, pickPlace); | ||
return TCL_OK; | ||
} | ||
/* | ||
* New syntax: dispatch based on argv[2]. | ||
*/ | ||
type = Tk_GetScrollInfo(interp, argc, argv, &fraction, &count); | ||
switch (type) { | ||
case TK_SCROLL_ERROR: | ||
return TCL_ERROR; | ||
case TK_SCROLL_MOVETO: | ||
if (fraction > 1.0) { | ||
fraction = 1.0; | ||
} | ||
if (fraction < 0) { | ||
fraction = 0; | ||
} | ||
fraction *= TkBTreeNumLines(textPtr->tree); | ||
lineNum = (int) fraction; | ||
TkTextMakeByteIndex(textPtr->tree, lineNum, 0, &index); | ||
bytesInLine = TkBTreeBytesInLine(index.linePtr); | ||
index.byteIndex = (int)((bytesInLine * (fraction-lineNum)) + 0.5); | ||
if (index.byteIndex >= bytesInLine) { | ||
TkTextMakeByteIndex(textPtr->tree, lineNum + 1, 0, &index); | ||
} | ||
TkTextSetYView(textPtr, &index, 0); | ||
break; | ||
case TK_SCROLL_PAGES: | ||
/* | ||
* Scroll up or down by screenfuls. Actually, use the | ||
* window height minus two lines, so that there's some | ||
* overlap between adjacent pages. | ||
*/ | ||
Tk_GetFontMetrics(textPtr->tkfont, &fm); | ||
if (count < 0) { | ||
pixels = (dInfoPtr->maxY - 2*fm.linespace - dInfoPtr->y)*(-count) | ||
+ fm.linespace; | ||
MeasureUp(textPtr, &textPtr->topIndex, pixels, &new); | ||
if (TkTextIndexCmp(&textPtr->topIndex, &new) == 0) { | ||
/* | ||
* A page of scrolling ended up being less than one line. | ||
* Scroll one line anyway. | ||
*/ | ||
count = -1; | ||
goto scrollByLines; | ||
} | ||
textPtr->topIndex = new; | ||
} else { | ||
/* | ||
* Scrolling down by pages. Layout lines starting at the | ||
* top index and count through the desired vertical distance. | ||
*/ | ||
pixels = (dInfoPtr->maxY - 2*fm.linespace - dInfoPtr->y)*count; | ||
lastLinePtr = TkBTreeFindLine(textPtr->tree, | ||
TkBTreeNumLines(textPtr->tree)); | ||
do { | ||
dlPtr = LayoutDLine(textPtr, &textPtr->topIndex); | ||
dlPtr->nextPtr = NULL; | ||
TkTextIndexForwBytes(&textPtr->topIndex, dlPtr->byteCount, | ||
&new); | ||
pixels -= dlPtr->height; | ||
FreeDLines(textPtr, dlPtr, (DLine *) NULL, 0); | ||
if (new.linePtr == lastLinePtr) { | ||
break; | ||
} | ||
textPtr->topIndex = new; | ||
} while (pixels > 0); | ||
} | ||
if (!(dInfoPtr->flags & REDRAW_PENDING)) { | ||
Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr); | ||
} | ||
dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED; | ||
break; | ||
case TK_SCROLL_UNITS: | ||
scrollByLines: | ||
ScrollByLines(textPtr, count); | ||
break; | ||
} | ||
return TCL_OK; | ||
} | ||
/* | ||
*-------------------------------------------------------------- | ||
* | ||
* TkTextScanCmd -- | ||
* | ||
* This procedure is invoked to process the "scan" option for | ||
* the widget command for text widgets. See the user documentation | ||
* for details on what it does. | ||
* | ||
* Results: | ||
* A standard Tcl result. | ||
* | ||
* Side effects: | ||
* See the user documentation. | ||
* | ||
*-------------------------------------------------------------- | ||
*/ | ||
int | ||
TkTextScanCmd(textPtr, interp, argc, argv) | ||
register TkText *textPtr; /* Information about text widget. */ | ||
Tcl_Interp *interp; /* Current interpreter. */ | ||
int argc; /* Number of arguments. */ | ||
char **argv; /* Argument strings. Someone else has already | ||
* parsed this command enough to know that | ||
* argv[1] is "scan". */ | ||
{ | ||
TextDInfo *dInfoPtr = textPtr->dInfoPtr; | ||
TkTextIndex index; | ||
int c, x, y, totalScroll, newByte, maxByte, gain=10; | ||
Tk_FontMetrics fm; | ||
size_t length; | ||
if ((argc != 5) && (argc != 6)) { | ||
Tcl_AppendResult(interp, "wrong # args: should be \"", | ||
argv[0], " scan mark x y\" or \"", | ||
argv[0], " scan dragto x y ?gain?\"", (char *) NULL); | ||
return TCL_ERROR; | ||
} | ||
if (Tcl_GetInt(interp, argv[3], &x) != TCL_OK) { | ||
return TCL_ERROR; | ||
} | ||
if (Tcl_GetInt(interp, argv[4], &y) != TCL_OK) { | ||
return TCL_ERROR; | ||
} | ||
if ((argc == 6) && (Tcl_GetInt(interp, argv[5], &gain) != TCL_OK)) | ||
return TCL_ERROR; | ||
c = argv[2][0]; | ||
length = strlen(argv[2]); | ||
if ((c == 'd') && (strncmp(argv[2], "dragto", length) == 0)) { | ||
/* | ||
* Amplify the difference between the current position and the | ||
* mark position to compute how much the view should shift, then | ||
* update the mark position to correspond to the new view. If we | ||
* run off the edge of the text, reset the mark point so that the | ||
* current position continues to correspond to the edge of the | ||
* window. This means that the picture will start dragging as | ||
* soon as the mouse reverses direction (without this reset, might | ||
* have to slide mouse a long ways back before the picture starts | ||
* moving again). | ||
*/ | ||
newByte = dInfoPtr->scanMarkIndex + (gain*(dInfoPtr->scanMarkX - x)) | ||
/ (textPtr->charWidth); | ||
maxByte = 1 + (dInfoPtr->maxLength - (dInfoPtr->maxX - dInfoPtr->x) | ||
+ textPtr->charWidth - 1)/textPtr->charWidth; | ||
if (newByte < 0) { | ||
newByte = 0; | ||
dInfoPtr->scanMarkIndex = 0; | ||
dInfoPtr->scanMarkX = x; | ||
} else if (newByte > maxByte) { | ||
newByte = maxByte; | ||
dInfoPtr->scanMarkIndex = maxByte; | ||
dInfoPtr->scanMarkX = x; | ||
} | ||
dInfoPtr->newByteOffset = newByte; | ||
Tk_GetFontMetrics(textPtr->tkfont, &fm); | ||
totalScroll = (gain*(dInfoPtr->scanMarkY - y)) / fm.linespace; | ||
if (totalScroll != dInfoPtr->scanTotalScroll) { | ||
index = textPtr->topIndex; | ||
ScrollByLines(textPtr, totalScroll-dInfoPtr->scanTotalScroll); | ||
dInfoPtr->scanTotalScroll = totalScroll; | ||
if ((index.linePtr == textPtr->topIndex.linePtr) && | ||
(index.byteIndex == textPtr->topIndex.byteIndex)) { | ||
dInfoPtr->scanTotalScroll = 0; | ||
dInfoPtr->scanMarkY = y; | ||
} | ||
} | ||
} else if ((c == 'm') && (strncmp(argv[2], "mark", length) == 0)) { | ||
dInfoPtr->scanMarkIndex = dInfoPtr->newByteOffset; | ||
dInfoPtr->scanMarkX = x; | ||
dInfoPtr->scanTotalScroll = 0; | ||
dInfoPtr->scanMarkY = y; | ||
} else { | ||
Tcl_AppendResult(interp, "bad scan option \"", argv[2], | ||
"\": must be mark or dragto", (char *) NULL); | ||
return TCL_ERROR; | ||
} | ||
dInfoPtr->flags |= DINFO_OUT_OF_DATE; | ||
if (!(dInfoPtr->flags & REDRAW_PENDING)) { | ||
dInfoPtr->flags |= REDRAW_PENDING; | ||
Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr); | ||
} | ||
return TCL_OK; | ||
} | ||
/* | ||
*---------------------------------------------------------------------- | ||
* | ||
* GetXView -- | ||
* | ||
* This procedure computes the fractions that indicate what's | ||
* visible in a text window and, optionally, evaluates a | ||
* Tcl script to report them to the text's associated scrollbar. | ||
* | ||
* Results: | ||
* If report is zero, then the interp's result is filled in with | ||
* two real numbers separated by a space, giving the position of | ||
* the left and right edges of the window as fractions from 0 to | ||
* 1, where 0 means the left edge of the text and 1 means the right | ||
* edge. If report is non-zero, then the interp's result isn't modified | ||
* directly, but instead a script is evaluated in interp to report | ||
* the new horizontal scroll position to the scrollbar (if the scroll | ||
* position hasn't changed then no script is invoked). | ||
* | ||
* Side effects: | ||
* None. | ||
* | ||
*---------------------------------------------------------------------- | ||
*/ | ||
static void | ||
GetXView(interp, textPtr, report) | ||
Tcl_Interp *interp; /* If "report" is FALSE, string | ||
* describing visible range gets | ||
* stored in the interp's result. */ | ||
TkText *textPtr; /* Information about text widget. */ | ||
int report; /* Non-zero means report info to | ||
* scrollbar if it has changed. */ | ||
{ | ||
TextDInfo *dInfoPtr = textPtr->dInfoPtr; | ||
char buffer[TCL_DOUBLE_SPACE * 2]; | ||
double first, last; | ||
int code; | ||
if (dInfoPtr->maxLength > 0) { | ||
first = ((double) dInfoPtr->curPixelOffset) | ||
/ dInfoPtr->maxLength; | ||
last = first + ((double) (dInfoPtr->maxX - dInfoPtr->x)) | ||
/ dInfoPtr->maxLength; | ||
if (last > 1.0) { | ||
last = 1.0; | ||
} | ||
} else { | ||
first = 0; | ||
last = 1.0; | ||
} | ||
if (!report) { | ||
sprintf(buffer, "%g %g", first, last); | ||
Tcl_SetResult(interp, buffer, TCL_VOLATILE); | ||
return; | ||
} | ||
if ((first == dInfoPtr->xScrollFirst) && (last == dInfoPtr->xScrollLast)) { | ||
return; | ||
} | ||
dInfoPtr->xScrollFirst = first; | ||
dInfoPtr->xScrollLast = last; | ||
sprintf(buffer, " %g %g", first, last); | ||
code = Tcl_VarEval(interp, textPtr->xScrollCmd, | ||
buffer, (char *) NULL); | ||
if (code != TCL_OK) { | ||
Tcl_AddErrorInfo(interp, | ||
"\n (horizontal scrolling command executed by text)"); | ||
Tcl_BackgroundError(interp); | ||
} | ||
} | ||
/* | ||
*---------------------------------------------------------------------- | ||
* | ||
* GetYView -- | ||
* | ||
* This procedure computes the fractions that indicate what's | ||
* visible in a text window and, optionally, evaluates a | ||
* Tcl script to report them to the text's associated scrollbar. | ||
* | ||
* Results: | ||
* If report is zero, then the interp's result is filled in with | ||
* two real numbers separated by a space, giving the position of | ||
* the top and bottom of the window as fractions from 0 to 1, where | ||
* 0 means the beginning of the text and 1 means the end. If | ||
* report is non-zero, then the interp's result isn't modified directly, | ||
* but a script is evaluated in interp to report the new scroll | ||
* position to the scrollbar (if the scroll position hasn't changed | ||
* then no script is invoked). | ||
* | ||
* Side effects: | ||
* None. | ||
* | ||
*---------------------------------------------------------------------- | ||
*/ | ||
static void | ||
GetYView(interp, textPtr, report) | ||
Tcl_Interp *interp; /* If "report" is FALSE, string | ||
* describing visible range gets | ||
* stored in the interp's result. */ | ||
TkText *textPtr; /* Information about text widget. */ | ||
int report; /* Non-zero means report info to | ||
* scrollbar if it has changed. */ | ||
{ | ||
TextDInfo *dInfoPtr = textPtr->dInfoPtr; | ||
char buffer[TCL_DOUBLE_SPACE * 2]; | ||
double first, last; | ||
DLine *dlPtr; | ||
int totalLines, code, count; | ||
dlPtr = dInfoPtr->dLinePtr; | ||
totalLines = TkBTreeNumLines(textPtr->tree); | ||
first = (double) TkBTreeLineIndex(dlPtr->index.linePtr) | ||
+ (double) dlPtr->index.byteIndex | ||
/ TkBTreeBytesInLine(dlPtr->index.linePtr); | ||
first /= totalLines; | ||
while (1) { | ||
if ((dlPtr->y + dlPtr->height) > dInfoPtr->maxY) { | ||
/* | ||
* The last line is only partially visible, so don't | ||
* count its characters in what's visible. | ||
*/ | ||
count = 0; | ||
break; | ||
} | ||
if (dlPtr->nextPtr == NULL) { | ||
count = dlPtr->byteCount; | ||
break; | ||
} | ||
dlPtr = dlPtr->nextPtr; | ||
} | ||
last = ((double) TkBTreeLineIndex(dlPtr->index.linePtr)) | ||
+ ((double) (dlPtr->index.byteIndex + count)) | ||
/ (TkBTreeBytesInLine(dlPtr->index.linePtr)); | ||
last /= totalLines; | ||
if (!report) { | ||
sprintf(buffer, "%g %g", first, last); | ||
Tcl_SetResult(interp, buffer, TCL_VOLATILE); | ||
return; | ||
} | ||
if ((first == dInfoPtr->yScrollFirst) && (last == dInfoPtr->yScrollLast)) { | ||
return; | ||
} | ||
dInfoPtr->yScrollFirst = first; | ||
dInfoPtr->yScrollLast = last; | ||
sprintf(buffer, " %g %g", first, last); | ||
code = Tcl_VarEval(interp, textPtr->yScrollCmd, buffer, (char *) NULL); | ||
if (code != TCL_OK) { | ||
Tcl_AddErrorInfo(interp, | ||
"\n (vertical scrolling command executed by text)"); | ||
Tcl_BackgroundError(interp); | ||
} | ||
} | ||
/* | ||
*---------------------------------------------------------------------- | ||
* | ||
* FindDLine -- | ||
* | ||
* This procedure is called to find the DLine corresponding to a | ||
* given text index. | ||
* | ||
* Results: | ||
* The return value is a pointer to the first DLine found in the | ||
* list headed by dlPtr that displays information at or after the | ||
* specified position. If there is no such line in the list then | ||
* NULL is returned. | ||
* | ||
* Side effects: | ||
* None. | ||
* | ||
*---------------------------------------------------------------------- | ||
*/ | ||
static DLine * | ||
FindDLine(dlPtr, indexPtr) | ||
register DLine *dlPtr; /* Pointer to first in list of DLines | ||
* to search. */ | ||
TkTextIndex *indexPtr; /* Index of desired character. */ | ||
{ | ||
TkTextLine *linePtr; | ||
if (dlPtr == NULL) { | ||
return NULL; | ||
} | ||
if (TkBTreeLineIndex(indexPtr->linePtr) | ||
< TkBTreeLineIndex(dlPtr->index.linePtr)) { | ||
/* | ||
* The first display line is already past the desired line. | ||
*/ | ||
return dlPtr; | ||
} | ||
/* | ||
* Find the first display line that covers the desired text line. | ||
*/ | ||
linePtr = dlPtr->index.linePtr; | ||
while (linePtr != indexPtr->linePtr) { | ||
while (dlPtr->index.linePtr == linePtr) { | ||
dlPtr = dlPtr->nextPtr; | ||
if (dlPtr == NULL) { | ||
return NULL; | ||
} | ||
} | ||
linePtr = TkBTreeNextLine(linePtr); | ||
if (linePtr == NULL) { | ||
panic("FindDLine reached end of text"); | ||
} | ||
} | ||
if (indexPtr->linePtr != dlPtr->index.linePtr) { | ||
return dlPtr; | ||
} | ||
/* | ||
* Now get to the right position within the text line. | ||
*/ | ||
while (indexPtr->byteIndex >= (dlPtr->index.byteIndex + dlPtr->byteCount)) { | ||
dlPtr = dlPtr->nextPtr; | ||
if ((dlPtr == NULL) || (dlPtr->index.linePtr != indexPtr->linePtr)) { | ||
break; | ||
} | ||
} | ||
return dlPtr; | ||
} | ||
/* | ||
*---------------------------------------------------------------------- | ||
* | ||
* TkTextPixelIndex -- | ||
* | ||
* Given an (x,y) coordinate on the screen, find the location of | ||
* the character closest to that location. | ||
* | ||
* Results: | ||
* The index at *indexPtr is modified to refer to the character | ||
* on the display that is closest to (x,y). | ||
* | ||
* Side effects: | ||
* None. | ||
* | ||
*---------------------------------------------------------------------- | ||
*/ | ||
void | ||
TkTextPixelIndex(textPtr, x, y, indexPtr) | ||
TkText *textPtr; /* Widget record for text widget. */ | ||
int x, y; /* Pixel coordinates of point in widget's | ||
* window. */ | ||
TkTextIndex *indexPtr; /* This index gets filled in with the | ||
* index of the character nearest to (x,y). */ | ||
{ | ||
TextDInfo *dInfoPtr = textPtr->dInfoPtr; | ||
register DLine *dlPtr, *validdlPtr; | ||
register TkTextDispChunk *chunkPtr; | ||
/* | ||
* Make sure that all of the layout information about what's | ||
* displayed where on the screen is up-to-date. | ||
*/ | ||
if (dInfoPtr->flags & DINFO_OUT_OF_DATE) { | ||
UpdateDisplayInfo(textPtr); | ||
} | ||
/* | ||
* If the coordinates are above the top of the window, then adjust | ||
* them to refer to the upper-right corner of the window. If they're | ||
* off to one side or the other, then adjust to the closest side. | ||
*/ | ||
if (y < dInfoPtr->y) { | ||
y = dInfoPtr->y; | ||
x = dInfoPtr->x; | ||
} | ||
if (x >= dInfoPtr->maxX) { | ||
x = dInfoPtr->maxX - 1; | ||
} | ||
if (x < dInfoPtr->x) { | ||
x = dInfoPtr->x; | ||
} | ||
/* | ||
* Find the display line containing the desired y-coordinate. | ||
*/ | ||
for (dlPtr = validdlPtr = dInfoPtr->dLinePtr; y >= (dlPtr->y + dlPtr->height); | ||
dlPtr = dlPtr->nextPtr) { | ||
if (dlPtr->chunkPtr !=NULL) validdlPtr = dlPtr; | ||
if (dlPtr->nextPtr == NULL) { | ||
/* | ||
* Y-coordinate is off the bottom of the displayed text. | ||
* Use the last character on the last line. | ||
*/ | ||
x = dInfoPtr->maxX - 1; | ||
break; | ||
} | ||
} | ||
if (dlPtr->chunkPtr == NULL) dlPtr = validdlPtr; | ||
/* | ||
* Scan through the line's chunks to find the one that contains | ||
* the desired x-coordinate. Before doing this, translate the | ||
* x-coordinate from the coordinate system of the window to the | ||
* coordinate system of the line (to take account of x-scrolling). | ||
*/ | ||
*indexPtr = dlPtr->index; | ||
x = x - dInfoPtr->x + dInfoPtr->curPixelOffset; | ||
for (chunkPtr = dlPtr->chunkPtr; x >= (chunkPtr->x + chunkPtr->width); | ||
indexPtr->byteIndex += chunkPtr->numBytes, | ||
chunkPtr = chunkPtr->nextPtr) { | ||
if (chunkPtr->nextPtr == NULL) { | ||
indexPtr->byteIndex += chunkPtr->numBytes; | ||
TkTextIndexBackChars(indexPtr, 1, indexPtr); | ||
return; | ||
} | ||
} | ||
/* | ||
* If the chunk has more than one byte in it, ask it which | ||
* character is at the desired location. | ||
*/ | ||
if (chunkPtr->numBytes > 1) { | ||
indexPtr->byteIndex += (*chunkPtr->measureProc)(chunkPtr, x); | ||
} | ||
} | ||
/* | ||
*---------------------------------------------------------------------- | ||
* | ||
* TkTextCharBbox -- | ||
* | ||
* Given an index, find the bounding box of the screen area | ||
* occupied by that character. | ||
* | ||
* Results: | ||
* Zero is returned if the character is on the screen. -1 | ||
* means the character isn't on the screen. If the return value | ||
* is 0, then the bounding box of the part of the character that's | ||
* visible on the screen is returned to *xPtr, *yPtr, *widthPtr, | ||
* and *heightPtr. | ||
* | ||
* Side effects: | ||
* None. | ||
* | ||
*---------------------------------------------------------------------- | ||
*/ | ||
int | ||
TkTextCharBbox(textPtr, indexPtr, xPtr, yPtr, widthPtr, heightPtr) | ||
TkText *textPtr; /* Widget record for text widget. */ | ||
TkTextIndex *indexPtr; /* Index of character whose bounding | ||
* box is desired. */ | ||
int *xPtr, *yPtr; /* Filled with character's upper-left | ||
* coordinate. */ | ||
int *widthPtr, *heightPtr; /* Filled in with character's dimensions. */ | ||
{ | ||
TextDInfo *dInfoPtr = textPtr->dInfoPtr; | ||
DLine *dlPtr; | ||
register TkTextDispChunk *chunkPtr; | ||
int byteIndex; | ||
/* | ||
* Make sure that all of the screen layout information is up to date. | ||
*/ | ||
if (dInfoPtr->flags & DINFO_OUT_OF_DATE) { | ||
UpdateDisplayInfo(textPtr); | ||
} | ||
/* | ||
* Find the display line containing the desired index. | ||
*/ | ||
dlPtr = FindDLine(dInfoPtr->dLinePtr, indexPtr); | ||
if ((dlPtr == NULL) || (TkTextIndexCmp(&dlPtr->index, indexPtr) > 0)) { | ||
return -1; | ||
} | ||
/* | ||
* Find the chunk within the line that contains the desired | ||
* index. | ||
*/ | ||
byteIndex = indexPtr->byteIndex - dlPtr->index.byteIndex; | ||
for (chunkPtr = dlPtr->chunkPtr; ; chunkPtr = chunkPtr->nextPtr) { | ||
if (chunkPtr == NULL) { | ||
return -1; | ||
} | ||
if (byteIndex < chunkPtr->numBytes) { | ||
break; | ||
} | ||
byteIndex -= chunkPtr->numBytes; | ||
} | ||
/* | ||
* Call a chunk-specific procedure to find the horizontal range of | ||
* the character within the chunk, then fill in the vertical range. | ||
* The x-coordinate returned by bboxProc is a coordinate within a | ||
* line, not a coordinate on the screen. Translate it to reflect | ||
* horizontal scrolling. | ||
*/ | ||
(*chunkPtr->bboxProc)(chunkPtr, byteIndex, dlPtr->y + dlPtr->spaceAbove, | ||
dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow, | ||
dlPtr->baseline - dlPtr->spaceAbove, xPtr, yPtr, widthPtr, | ||
heightPtr); | ||
*xPtr = *xPtr + dInfoPtr->x - dInfoPtr->curPixelOffset; | ||
if ((byteIndex == (chunkPtr->numBytes - 1)) && (chunkPtr->nextPtr == NULL)) { | ||
/* | ||
* Last character in display line. Give it all the space up to | ||
* the line. | ||
*/ | ||
if (*xPtr > dInfoPtr->maxX) { | ||
*xPtr = dInfoPtr->maxX; | ||
} | ||
*widthPtr = dInfoPtr->maxX - *xPtr; | ||
} | ||
if ((*xPtr + *widthPtr) <= dInfoPtr->x) { | ||
return -1; | ||
} | ||
if ((*xPtr + *widthPtr) > dInfoPtr->maxX) { | ||
*widthPtr = dInfoPtr->maxX - *xPtr; | ||
if (*widthPtr <= 0) { | ||
return -1; | ||
} | ||
} | ||
if ((*yPtr + *heightPtr) > dInfoPtr->maxY) { | ||
*heightPtr = dInfoPtr->maxY - *yPtr; | ||
if (*heightPtr <= 0) { | ||
return -1; | ||
} | ||
} | ||
return 0; | ||
} | ||
/* | ||
*---------------------------------------------------------------------- | ||
* | ||
* TkTextDLineInfo -- | ||
* | ||
* Given an index, return information about the display line | ||
* containing that character. | ||
* | ||
* Results: | ||
* Zero is returned if the character is on the screen. -1 | ||
* means the character isn't on the screen. If the return value | ||
* is 0, then information is returned in the variables pointed | ||
* to by xPtr, yPtr, widthPtr, heightPtr, and basePtr. | ||
* | ||
* Side effects: | ||
* None. | ||
* | ||
*---------------------------------------------------------------------- | ||
*/ | ||
int | ||
TkTextDLineInfo(textPtr, indexPtr, xPtr, yPtr, widthPtr, heightPtr, basePtr) | ||
TkText *textPtr; /* Widget record for text widget. */ | ||
TkTextIndex *indexPtr; /* Index of character whose bounding | ||
* box is desired. */ | ||
int *xPtr, *yPtr; /* Filled with line's upper-left | ||
* coordinate. */ | ||
int *widthPtr, *heightPtr; /* Filled in with line's dimensions. */ | ||
int *basePtr; /* Filled in with the baseline position, | ||
* measured as an offset down from *yPtr. */ | ||
{ | ||
TextDInfo *dInfoPtr = textPtr->dInfoPtr; | ||
DLine *dlPtr; | ||
int dlx; | ||
/* | ||
* Make sure that all of the screen layout information is up to date. | ||
*/ | ||
if (dInfoPtr->flags & DINFO_OUT_OF_DATE) { | ||
UpdateDisplayInfo(textPtr); | ||
} | ||
/* | ||
* Find the display line containing the desired index. | ||
*/ | ||
dlPtr = FindDLine(dInfoPtr->dLinePtr, indexPtr); | ||
if ((dlPtr == NULL) || (TkTextIndexCmp(&dlPtr->index, indexPtr) > 0)) { | ||
return -1; | ||
} | ||
dlx = (dlPtr->chunkPtr != NULL? dlPtr->chunkPtr->x: 0); | ||
*xPtr = dInfoPtr->x - dInfoPtr->curPixelOffset + dlx; | ||
*widthPtr = dlPtr->length - dlx; | ||
*yPtr = dlPtr->y; | ||
if ((dlPtr->y + dlPtr->height) > dInfoPtr->maxY) { | ||
*heightPtr = dInfoPtr->maxY - dlPtr->y; | ||
} else { | ||
*heightPtr = dlPtr->height; | ||
} | ||
*basePtr = dlPtr->baseline; | ||
return 0; | ||
} | ||
static void | ||
ElideBboxProc(chunkPtr, index, y, lineHeight, baseline, xPtr, yPtr, | ||
widthPtr, heightPtr) | ||
TkTextDispChunk *chunkPtr; /* Chunk containing desired char. */ | ||
int index; /* Index of desired character within | ||
* the chunk. */ | ||
int y; /* Topmost pixel in area allocated | ||
* for this line. */ | ||
int lineHeight; /* Height of line, in pixels. */ | ||
int baseline; /* Location of line's baseline, in | ||
* pixels measured down from y. */ | ||
int *xPtr, *yPtr; /* Gets filled in with coords of | ||
* character's upper-left pixel. | ||
* X-coord is in same coordinate | ||
* system as chunkPtr->x. */ | ||
int *widthPtr; /* Gets filled in with width of | ||
* character, in pixels. */ | ||
int *heightPtr; /* Gets filled in with height of | ||
* character, in pixels. */ | ||
{ | ||
*xPtr = chunkPtr->x; | ||
*yPtr = y; | ||
*widthPtr = *heightPtr = 0; | ||
} | ||
static int | ||
ElideMeasureProc(chunkPtr, x) | ||
TkTextDispChunk *chunkPtr; /* Chunk containing desired coord. */ | ||
int x; /* X-coordinate, in same coordinate | ||
* system as chunkPtr->x. */ | ||
{ | ||
return 0 /*chunkPtr->numBytes - 1*/; | ||
} | ||
/* | ||
*-------------------------------------------------------------- | ||
* | ||
* TkTextCharLayoutProc -- | ||
* | ||
* This procedure is the "layoutProc" for character segments. | ||
* | ||
n * Results: | ||
* If there is something to display for the chunk then a | ||
* non-zero value is returned and the fields of chunkPtr | ||
* will be filled in (see the declaration of TkTextDispChunk | ||
* in tkText.h for details). If zero is returned it means | ||
* that no characters from this chunk fit in the window. | ||
* If -1 is returned it means that this segment just doesn't | ||
* need to be displayed (never happens for text). | ||
* | ||
* Side effects: | ||
* Memory is allocated to hold additional information about | ||
* the chunk. | ||
* | ||
*-------------------------------------------------------------- | ||
*/ | ||
int | ||
TkTextCharLayoutProc(textPtr, indexPtr, segPtr, byteOffset, maxX, maxBytes, | ||
noCharsYet, wrapMode, chunkPtr) | ||
TkText *textPtr; /* Text widget being layed out. */ | ||
TkTextIndex *indexPtr; /* Index of first character to lay out | ||
* (corresponds to segPtr and offset). */ | ||
TkTextSegment *segPtr; /* Segment being layed out. */ | ||
int byteOffset; /* Byte offset within segment of first | ||
* character to consider. */ | ||
int maxX; /* Chunk must not occupy pixels at this | ||
* position or higher. */ | ||
int maxBytes; /* Chunk must not include more than this | ||
* many characters. */ | ||
int noCharsYet; /* Non-zero means no characters have been | ||
* assigned to this display line yet. */ | ||
TkWrapMode wrapMode; /* How to handle line wrapping: TEXT_WRAPMODE_CHAR, | ||
* TEXT_WRAPMODE_NONE, or TEXT_WRAPMODE_WORD. */ | ||
register TkTextDispChunk *chunkPtr; | ||
/* Structure to fill in with information | ||
* about this chunk. The x field has already | ||
* been set by the caller. */ | ||
{ | ||
Tk_Font tkfont; | ||
int nextX, bytesThatFit, count; | ||
CharInfo *ciPtr; | ||
char *p; | ||
TkTextSegment *nextPtr; | ||
Tk_FontMetrics fm; | ||
/* | ||
* Figure out how many characters will fit in the space we've got. | ||
* Include the next character, even though it won't fit completely, | ||
* if any of the following is true: | ||
* (a) the chunk contains no characters and the display line contains | ||
* no characters yet (i.e. the line isn't wide enough to hold | ||
* even a single character). | ||
* (b) at least one pixel of the character is visible, we haven't | ||
* already exceeded the character limit, and the next character | ||
* is a white space character. | ||
*/ | ||
p = segPtr->body.chars + byteOffset; | ||
tkfont = chunkPtr->stylePtr->sValuePtr->tkfont; | ||
bytesThatFit = MeasureChars(tkfont, p, maxBytes, chunkPtr->x, maxX, 0, | ||
&nextX); | ||
if (bytesThatFit < maxBytes) { | ||
if ((bytesThatFit == 0) && noCharsYet) { | ||
Tcl_UniChar ch; | ||
bytesThatFit = MeasureChars(tkfont, p, Tcl_UtfToUniChar(p, &ch), | ||
chunkPtr->x, -1, 0, &nextX); | ||
} | ||
if ((nextX < maxX) && ((p[bytesThatFit] == ' ') | ||
|| (p[bytesThatFit] == '\t'))) { | ||
/* | ||
* Space characters are funny, in that they are considered | ||
* to fit if there is at least one pixel of space left on the | ||
* line. Just give the space character whatever space is left. | ||
*/ | ||
nextX = maxX; | ||
bytesThatFit++; | ||
} | ||
if (p[bytesThatFit] == '\n') { | ||
/* | ||
* A newline character takes up no space, so if the previous | ||
* character fits then so does the newline. | ||
*/ | ||
bytesThatFit++; | ||
} | ||
if (bytesThatFit == 0) { | ||
return 0; | ||
} | ||
} | ||
Tk_GetFontMetrics(tkfont, &fm); | ||
/* | ||
* Fill in the chunk structure and allocate and initialize a | ||
* CharInfo structure. If the last character is a newline | ||
* then don't bother to display it. | ||
*/ | ||
chunkPtr->displayProc = CharDisplayProc; | ||
chunkPtr->undisplayProc = CharUndisplayProc; | ||
chunkPtr->measureProc = CharMeasureProc; | ||
chunkPtr->bboxProc = CharBboxProc; | ||
chunkPtr->numBytes = bytesThatFit; | ||
chunkPtr->minAscent = fm.ascent + chunkPtr->stylePtr->sValuePtr->offset; | ||
chunkPtr->minDescent = fm.descent - chunkPtr->stylePtr->sValuePtr->offset; | ||
chunkPtr->minHeight = 0; | ||
chunkPtr->width = nextX - chunkPtr->x; | ||
chunkPtr->breakIndex = -1; | ||
ciPtr = (CharInfo *) ckalloc((unsigned) | ||
(sizeof(CharInfo) - 3 + bytesThatFit)); | ||
chunkPtr->clientData = (ClientData) ciPtr; | ||
ciPtr->numBytes = bytesThatFit; | ||
strncpy(ciPtr->chars, p, (size_t) bytesThatFit); | ||
if (p[bytesThatFit - 1] == '\n') { | ||
ciPtr->numBytes--; | ||
} | ||
/* | ||
* Compute a break location. If we're in word wrap mode, a | ||
* break can occur after any space character, or at the end of | ||
* the chunk if the next segment (ignoring those with zero size) | ||
* is not a character segment. | ||
*/ | ||
if (wrapMode != TEXT_WRAPMODE_WORD) { | ||
chunkPtr->breakIndex = chunkPtr->numBytes; | ||
} else { | ||
for (count = bytesThatFit, p += bytesThatFit - 1; count > 0; | ||
count--, p--) { | ||
if (isspace(UCHAR(*p))) { | ||
chunkPtr->breakIndex = count; | ||
break; | ||
} | ||
} | ||
if ((bytesThatFit + byteOffset) == segPtr->size) { | ||
for (nextPtr = segPtr->nextPtr; nextPtr != NULL; | ||
nextPtr = nextPtr->nextPtr) { | ||
if (nextPtr->size != 0) { | ||
if (nextPtr->typePtr != &tkTextCharType) { | ||
chunkPtr->breakIndex = chunkPtr->numBytes; | ||
} | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
return 1; | ||
} | ||
/* | ||
*-------------------------------------------------------------- | ||
* | ||
* CharDisplayProc -- | ||
* | ||
* This procedure is called to display a character chunk on | ||
* the screen or in an off-screen pixmap. | ||
* | ||
* Results: | ||
* None. | ||
* | ||
* Side effects: | ||
* Graphics are drawn. | ||
* | ||
*-------------------------------------------------------------- | ||
*/ | ||
static void | ||
CharDisplayProc(chunkPtr, x, y, height, baseline, display, dst, screenY) | ||
TkTextDispChunk *chunkPtr; /* Chunk that is to be drawn. */ | ||
int x; /* X-position in dst at which to | ||
* draw this chunk (may differ from | ||
* the x-position in the chunk because | ||
* of scrolling). */ | ||
int y; /* Y-position at which to draw this | ||
* chunk in dst. */ | ||
int height; /* Total height of line. */ | ||
int baseline; /* Offset of baseline from y. */ | ||
Display *display; /* Display to use for drawing. */ | ||
Drawable dst; /* Pixmap or window in which to draw | ||
* chunk. */ | ||
int screenY; /* Y-coordinate in text window that | ||
* corresponds to y. */ | ||
{ | ||
CharInfo *ciPtr = (CharInfo *) chunkPtr->clientData; | ||
TextStyle *stylePtr; | ||
StyleValues *sValuePtr; | ||
int offsetBytes, offsetX; | ||
if ((x + chunkPtr->width) <= 0) { | ||
/* | ||
* The chunk is off-screen. | ||
*/ | ||
return; | ||
} | ||
stylePtr = chunkPtr->stylePtr; | ||
sValuePtr = stylePtr->sValuePtr; | ||
/* | ||
* If the text sticks out way to the left of the window, skip | ||
* over the characters that aren't in the visible part of the | ||
* window. This is essential if x is very negative (such as | ||
* less than 32K); otherwise overflow problems will occur | ||
* in servers that use 16-bit arithmetic, like X. | ||
*/ | ||
offsetX = x; | ||
offsetBytes = 0; | ||
if (x < 0) { | ||
offsetBytes = MeasureChars(sValuePtr->tkfont, ciPtr->chars, | ||
ciPtr->numBytes, x, 0, x - chunkPtr->x, &offsetX); | ||
} | ||
/* | ||
* Draw the text, underline, and overstrike for this chunk. | ||
*/ | ||
if (!sValuePtr->elide && (ciPtr->numBytes > offsetBytes) && (stylePtr->fgGC != None)) { | ||
int numBytes = ciPtr->numBytes - offsetBytes; | ||
char *string = ciPtr->chars + offsetBytes; | ||
if ((numBytes > 0) && (string[numBytes - 1] == '\t')) { | ||
numBytes--; | ||
} | ||
Tk_DrawChars(display, dst, stylePtr->fgGC, sValuePtr->tkfont, string, | ||
numBytes, offsetX, y + baseline - sValuePtr->offset); | ||
if (sValuePtr->underline) { | ||
Tk_UnderlineChars(display, dst, stylePtr->fgGC, sValuePtr->tkfont, | ||
ciPtr->chars + offsetBytes, offsetX, | ||
y + baseline - sValuePtr->offset, 0, numBytes); | ||
} | ||
if (sValuePtr->overstrike) { | ||
Tk_FontMetrics fm; | ||
Tk_GetFontMetrics(sValuePtr->tkfont, &fm); | ||
Tk_UnderlineChars(display, dst, stylePtr->fgGC, sValuePtr->tkfont, | ||
ciPtr->chars + offsetBytes, offsetX, | ||
y + baseline - sValuePtr->offset | ||
- fm.descent - (fm.ascent * 3) / 10, | ||
0, numBytes); | ||
} | ||
} | ||
} | ||
/* | ||
*-------------------------------------------------------------- | ||
* | ||
* CharUndisplayProc -- | ||
* | ||
* This procedure is called when a character chunk is no | ||
* longer going to be displayed. It frees up resources | ||
* that were allocated to display the chunk. | ||
* | ||
* Results: | ||
* None. | ||
* | ||
* Side effects: | ||
* Memory and other resources get freed. | ||
* | ||
*-------------------------------------------------------------- | ||
*/ | ||
static void | ||
CharUndisplayProc(textPtr, chunkPtr) | ||
TkText *textPtr; /* Overall information about text | ||
* widget. */ | ||
TkTextDispChunk *chunkPtr; /* Chunk that is about to be freed. */ | ||
{ | ||
CharInfo *ciPtr = (CharInfo *) chunkPtr->clientData; | ||
ckfree((char *) ciPtr); | ||
} | ||
/* | ||
*-------------------------------------------------------------- | ||
* | ||
* CharMeasureProc -- | ||
* | ||
* This procedure is called to determine which character in | ||
* a character chunk lies over a given x-coordinate. | ||
* | ||
* Results: | ||
* The return value is the index *within the chunk* of the | ||
* character that covers the position given by "x". | ||
* | ||
* Side effects: | ||
* None. | ||
* | ||
*-------------------------------------------------------------- | ||
*/ | ||
static int | ||
CharMeasureProc(chunkPtr, x) | ||
TkTextDispChunk *chunkPtr; /* Chunk containing desired coord. */ | ||
int x; /* X-coordinate, in same coordinate | ||
* system as chunkPtr->x. */ | ||
{ | ||
CharInfo *ciPtr = (CharInfo *) chunkPtr->clientData; | ||
int endX; | ||
return MeasureChars(chunkPtr->stylePtr->sValuePtr->tkfont, ciPtr->chars, | ||
chunkPtr->numBytes - 1, chunkPtr->x, x, 0, &endX); | ||
/* CHAR OFFSET */ | ||
} | ||
/* | ||
*-------------------------------------------------------------- | ||
* | ||
* CharBboxProc -- | ||
* | ||
* This procedure is called to compute the bounding box of | ||
* the area occupied by a single character. | ||
* | ||
* Results: | ||
* There is no return value. *xPtr and *yPtr are filled in | ||
* with the coordinates of the upper left corner of the | ||
* character, and *widthPtr and *heightPtr are filled in with | ||
* the dimensions of the character in pixels. Note: not all | ||
* of the returned bbox is necessarily visible on the screen | ||
* (the rightmost part might be off-screen to the right, | ||
* and the bottommost part might be off-screen to the bottom). | ||
* | ||
* Side effects: | ||
* None. | ||
* | ||
*-------------------------------------------------------------- | ||
*/ | ||
static void | ||
CharBboxProc(chunkPtr, byteIndex, y, lineHeight, baseline, xPtr, yPtr, | ||
widthPtr, heightPtr) | ||
TkTextDispChunk *chunkPtr; /* Chunk containing desired char. */ | ||
int byteIndex; /* Byte offset of desired character | ||
* within the chunk. */ | ||
int y; /* Topmost pixel in area allocated | ||
* for this line. */ | ||
int lineHeight; /* Height of line, in pixels. */ | ||
int baseline; /* Location of line's baseline, in | ||
* pixels measured down from y. */ | ||
int *xPtr, *yPtr; /* Gets filled in with coords of | ||
* character's upper-left pixel. | ||
* X-coord is in same coordinate | ||
* system as chunkPtr->x. */ | ||
int *widthPtr; /* Gets filled in with width of | ||
* character, in pixels. */ | ||
int *heightPtr; /* Gets filled in with height of | ||
* character, in pixels. */ | ||
{ | ||
CharInfo *ciPtr = (CharInfo *) chunkPtr->clientData; | ||
int maxX; | ||
maxX = chunkPtr->width + chunkPtr->x; | ||
MeasureChars(chunkPtr->stylePtr->sValuePtr->tkfont, ciPtr->chars, | ||
byteIndex, chunkPtr->x, -1, 0, xPtr); | ||
if (byteIndex == ciPtr->numBytes) { | ||
/* | ||
* This situation only happens if the last character in a line | ||
* is a space character, in which case it absorbs all of the | ||
* extra space in the line (see TkTextCharLayoutProc). | ||
*/ | ||
*widthPtr = maxX - *xPtr; | ||
} else if ((ciPtr->chars[byteIndex] == '\t') | ||
&& (byteIndex == ciPtr->numBytes - 1)) { | ||
/* | ||
* The desired character is a tab character that terminates a | ||
* chunk; give it all the space left in the chunk. | ||
*/ | ||
*widthPtr = maxX - *xPtr; | ||
} else { | ||
MeasureChars(chunkPtr->stylePtr->sValuePtr->tkfont, | ||
ciPtr->chars + byteIndex, 1, *xPtr, -1, 0, widthPtr); | ||
if (*widthPtr > maxX) { | ||
*widthPtr = maxX - *xPtr; | ||
} else { | ||
*widthPtr -= *xPtr; | ||
} | ||
} | ||
*yPtr = y + baseline - chunkPtr->minAscent; | ||
*heightPtr = chunkPtr->minAscent + chunkPtr->minDescent; | ||
} | ||
/* | ||
*---------------------------------------------------------------------- | ||
* | ||
* AdjustForTab -- | ||
* | ||
* This procedure is called to move a series of chunks right | ||
* in order to align them with a tab stop. | ||
* | ||
* Results: | ||
* None. | ||
* | ||
* Side effects: | ||
* The width of chunkPtr gets adjusted so that it absorbs the | ||
* extra space due to the tab. The x locations in all the chunks | ||
* after chunkPtr are adjusted rightward to align with the tab | ||
* stop given by tabArrayPtr and index. | ||
* | ||
*---------------------------------------------------------------------- | ||
*/ | ||
static void | ||
AdjustForTab(textPtr, tabArrayPtr, index, chunkPtr) | ||
TkText *textPtr; /* Information about the text widget as | ||
* a whole. */ | ||
TkTextTabArray *tabArrayPtr; /* Information about the tab stops | ||
* that apply to this line. May be | ||
* NULL to indicate default tabbing | ||
* (every 8 chars). */ | ||
int index; /* Index of current tab stop. */ | ||
TkTextDispChunk *chunkPtr; /* Chunk whose last character is | ||
* the tab; the following chunks | ||
* contain information to be shifted | ||
* right. */ | ||
{ | ||
int x, desired, delta, width, decimal, i, gotDigit; | ||
TkTextDispChunk *chunkPtr2, *decimalChunkPtr; | ||
CharInfo *ciPtr; | ||
int tabX, prev, spaceWidth; | ||
char *p; | ||
TkTextTabAlign alignment; | ||
if (chunkPtr->nextPtr == NULL) { | ||
/* | ||
* Nothing after the actual tab; just return. | ||
*/ | ||
return; | ||
} | ||
/* | ||
* If no tab information has been given, do the usual thing: | ||
* round up to the next boundary of 8 average-sized characters. | ||
*/ | ||
x = chunkPtr->nextPtr->x; | ||
if ((tabArrayPtr == NULL) || (tabArrayPtr->numTabs == 0)) { | ||
/* | ||
* No tab information has been given, so use the default | ||
* interpretation of tabs. | ||
*/ | ||
desired = NextTabStop(textPtr->tkfont, x, 0); | ||
goto update; | ||
} | ||
if (index < tabArrayPtr->numTabs) { | ||
alignment = tabArrayPtr->tabs[index].alignment; | ||
tabX = tabArrayPtr->tabs[index].location; | ||
} else { | ||
/* | ||
* Ran out of tab stops; compute a tab position by extrapolating | ||
* from the last two tab positions. | ||
*/ | ||
if (tabArrayPtr->numTabs > 1) { | ||
prev = tabArrayPtr->tabs[tabArrayPtr->numTabs-2].location; | ||
} else { | ||
prev = 0; | ||
} | ||
alignment = tabArrayPtr->tabs[tabArrayPtr->numTabs-1].alignment; | ||
tabX = tabArrayPtr->tabs[tabArrayPtr->numTabs-1].location | ||
+ (index + 1 - tabArrayPtr->numTabs) | ||
* (tabArrayPtr->tabs[tabArrayPtr->numTabs-1].location - prev); | ||
} | ||
if (alignment == LEFT) { | ||
desired = tabX; | ||
goto update; | ||
} | ||
if ((alignment == CENTER) || (alignment == RIGHT)) { | ||
/* | ||
* Compute the width of all the information in the tab group, | ||
* then use it to pick a desired location. | ||
*/ | ||
width = 0; | ||
for (chunkPtr2 = chunkPtr->nextPtr; chunkPtr2 != NULL; | ||
chunkPtr2 = chunkPtr2->nextPtr) { | ||
width += chunkPtr2->width; | ||
} | ||
if (alignment == CENTER) { | ||
desired = tabX - width/2; | ||
} else { | ||
desired = tabX - width; | ||
} | ||
goto update; | ||
} | ||
/* | ||
* Must be numeric alignment. Search through the text to be | ||
* tabbed, looking for the last , or . before the first character | ||
* that isn't a number, comma, period, or sign. | ||
*/ | ||
decimalChunkPtr = NULL; | ||
decimal = gotDigit = 0; | ||
for (chunkPtr2 = chunkPtr->nextPtr; chunkPtr2 != NULL; | ||
chunkPtr2 = chunkPtr2->nextPtr) { | ||
if (chunkPtr2->displayProc != CharDisplayProc) { | ||
continue; | ||
} | ||
ciPtr = (CharInfo *) chunkPtr2->clientData; | ||
for (p = ciPtr->chars, i = 0; i < ciPtr->numBytes; p++, i++) { | ||
if (isdigit(UCHAR(*p))) { | ||
gotDigit = 1; | ||
} else if ((*p == '.') || (*p == ',')) { | ||
decimal = p-ciPtr->chars; | ||
decimalChunkPtr = chunkPtr2; | ||
} else if (gotDigit) { | ||
if (decimalChunkPtr == NULL) { | ||
decimal = p-ciPtr->chars; | ||
decimalChunkPtr = chunkPtr2; | ||
} | ||
goto endOfNumber; | ||
} | ||
} | ||
} | ||
endOfNumber: | ||
if (decimalChunkPtr != NULL) { | ||
int curX; | ||
ciPtr = (CharInfo *) decimalChunkPtr->clientData; | ||
MeasureChars(decimalChunkPtr->stylePtr->sValuePtr->tkfont, | ||
ciPtr->chars, decimal, decimalChunkPtr->x, -1, 0, &curX); | ||
desired = tabX - (curX - x); | ||
goto update; | ||
} else { | ||
/* | ||
* There wasn't a decimal point. Right justify the text. | ||
*/ | ||
width = 0; | ||
for (chunkPtr2 = chunkPtr->nextPtr; chunkPtr2 != NULL; | ||
chunkPtr2 = chunkPtr2->nextPtr) { | ||
width += chunkPtr2->width; | ||
} | ||
desired = tabX - width; | ||
} | ||
/* | ||
* Shift all of the chunks to the right so that the left edge is | ||
* at the desired location, then expand the chunk containing the | ||
* tab. Be sure that the tab occupies at least the width of a | ||
* space character. | ||
*/ | ||
update: | ||
delta = desired - x; | ||
MeasureChars(textPtr->tkfont, " ", 1, 0, -1, 0, &spaceWidth); | ||
if (delta < spaceWidth) { | ||
delta = spaceWidth; | ||
} | ||
for (chunkPtr2 = chunkPtr->nextPtr; chunkPtr2 != NULL; | ||
chunkPtr2 = chunkPtr2->nextPtr) { | ||
chunkPtr2->x += delta; | ||
} | ||
chunkPtr->width += delta; | ||
} | ||
/* | ||
*---------------------------------------------------------------------- | ||
* | ||
* SizeOfTab -- | ||
* | ||
* This returns an estimate of the amount of white space that will | ||
* be consumed by a tab. | ||
* | ||
* Results: | ||
* The return value is the minimum number of pixels that will | ||
* be occupied by the index'th tab of tabArrayPtr, assuming that | ||
* the current position on the line is x and the end of the | ||
* line is maxX. For numeric tabs, this is a conservative | ||
* estimate. The return value is always >= 0. | ||
* | ||
* Side effects: | ||
* None. | ||
* | ||
*---------------------------------------------------------------------- | ||
*/ | ||
static int | ||
SizeOfTab(textPtr, tabArrayPtr, index, x, maxX) | ||
TkText *textPtr; /* Information about the text widget as | ||
* a whole. */ | ||
TkTextTabArray *tabArrayPtr; /* Information about the tab stops | ||
* that apply to this line. NULL | ||
* means use default tabbing (every | ||
* 8 chars.) */ | ||
int index; /* Index of current tab stop. */ | ||
int x; /* Current x-location in line. Only | ||
* used if tabArrayPtr == NULL. */ | ||
int maxX; /* X-location of pixel just past the | ||
* right edge of the line. */ | ||
{ | ||
int tabX, prev, result, spaceWidth; | ||
TkTextTabAlign alignment; | ||
if ((tabArrayPtr == NULL) || (tabArrayPtr->numTabs == 0)) { | ||
tabX = NextTabStop(textPtr->tkfont, x, 0); | ||
return tabX - x; | ||
} | ||
if (index < tabArrayPtr->numTabs) { | ||
tabX = tabArrayPtr->tabs[index].location; | ||
alignment = tabArrayPtr->tabs[index].alignment; | ||
} else { | ||
/* | ||
* Ran out of tab stops; compute a tab position by extrapolating | ||
* from the last two tab positions. | ||
*/ | ||
if (tabArrayPtr->numTabs > 1) { | ||
prev = tabArrayPtr->tabs[tabArrayPtr->numTabs-2].location; | ||
} else { | ||
prev = 0; | ||
} | ||
tabX = tabArrayPtr->tabs[tabArrayPtr->numTabs-1].location | ||
+ (index + 1 - tabArrayPtr->numTabs) | ||
* (tabArrayPtr->tabs[tabArrayPtr->numTabs-1].location - prev); | ||
alignment = tabArrayPtr->tabs[tabArrayPtr->numTabs-1].alignment; | ||
} | ||
if (alignment == CENTER) { | ||
/* | ||
* Be very careful in the arithmetic below, because maxX may | ||
* be the largest positive number: watch out for integer | ||
* overflow. | ||
*/ | ||
if ((maxX-tabX) < (tabX - x)) { | ||
result = (maxX - x) - 2*(maxX - tabX); | ||
} else { | ||
result = 0; | ||
} | ||
goto done; | ||
} | ||
if (alignment == RIGHT) { | ||
result = 0; | ||
goto done; | ||
} | ||
/* | ||
* Note: this treats NUMERIC alignment the same as LEFT | ||
* alignment, which is somewhat conservative. However, it's | ||
* pretty tricky at this point to figure out exactly where | ||
* the damn decimal point will be. | ||
*/ | ||
if (tabX > x) { | ||
result = tabX - x; | ||
} else { | ||
result = 0; | ||
} | ||
done: | ||
MeasureChars(textPtr->tkfont, " ", 1, 0, -1, 0, &spaceWidth); | ||
if (result < spaceWidth) { | ||
result = spaceWidth; | ||
} | ||
return result; | ||
} | ||
/* | ||
*--------------------------------------------------------------------------- | ||
* | ||
* NextTabStop -- | ||
* | ||
* Given the current position, determine where the next default | ||
* tab stop would be located. This procedure is called when the | ||
* current chunk in the text has no tabs defined and so the default | ||
* tab spacing for the font should be used. | ||
* | ||
* Results: | ||
* The location in pixels of the next tab stop. | ||
* | ||
* Side effects: | ||
* None. | ||
* | ||
*--------------------------------------------------------------------------- | ||
*/ | ||
static int | ||
NextTabStop(tkfont, x, tabOrigin) | ||
Tk_Font tkfont; /* Font in which chunk that contains tab | ||
* stop will be drawn. */ | ||
int x; /* X-position in pixels where last | ||
* character was drawn. The next tab stop | ||
* occurs somewhere after this location. */ | ||
int tabOrigin; /* The origin for tab stops. May be | ||
* non-zero if text has been scrolled. */ | ||
{ | ||
int tabWidth, rem; | ||
tabWidth = Tk_TextWidth(tkfont, "0", 1) * 8; | ||
if (tabWidth == 0) { | ||
tabWidth = 1; | ||
} | ||
x += tabWidth; | ||
rem = (x - tabOrigin) % tabWidth; | ||
if (rem < 0) { | ||
rem += tabWidth; | ||
} | ||
x -= rem; | ||
return x; | ||
} | ||
/* | ||
*--------------------------------------------------------------------------- | ||
* | ||
* MeasureChars -- | ||
* | ||
* Determine the number of characters from the string that will fit | ||
* in the given horizontal span. The measurement is done under the | ||
* assumption that Tk_DrawTextLayout will be used to actually display | ||
* the characters. | ||
* | ||
* If tabs are encountered in the string, they will be expanded | ||
* to the next tab stop, unless the TK_IGNORE_TABS flag is specified. | ||
* | ||
* If a newline is encountered in the string, the line will be | ||
* broken at that point, unless the TK_NEWSLINES_NOT_SPECIAL flag | ||
* is specified. | ||
* | ||
* Results: | ||
* The return value is the number of bytes from source | ||
* that fit in the span given by startX and maxX. *nextXPtr | ||
* is filled in with the x-coordinate at which the first | ||
* character that didn't fit would be drawn, if it were to | ||
* be drawn. | ||
* | ||
* Side effects: | ||
* None. | ||
* | ||
*-------------------------------------------------------------- | ||
*/ | ||
static int | ||
MeasureChars(tkfont, source, maxBytes, startX, maxX, tabOrigin, nextXPtr) | ||
Tk_Font tkfont; /* Font in which to draw characters. */ | ||
CONST char *source; /* Characters to be displayed. Need not | ||
* be NULL-terminated. */ | ||
int maxBytes; /* Maximum # of bytes to consider from | ||
* source. */ | ||
int startX; /* X-position at which first character will | ||
* be drawn. */ | ||
int maxX; /* Don't consider any character that would | ||
* cross this x-position. */ | ||
int tabOrigin; /* X-location that serves as "origin" for | ||
* tab stops. */ | ||
int *nextXPtr; /* Return x-position of terminating | ||
* character here. */ | ||
{ | ||
int curX, width, ch; | ||
CONST char *special, *end, *start; | ||
ch = 0; /* lint. */ | ||
curX = startX; | ||
special = source; | ||
end = source + maxBytes; | ||
for (start = source; start < end; ) { | ||
if (start >= special) { | ||
/* | ||
* Find the next special character in the string. | ||
*/ | ||
for (special = start; special < end; special++) { | ||
ch = *special; | ||
if ((ch == '\t') || (ch == '\n')) { | ||
break; | ||
} | ||
} | ||
} | ||
/* | ||
* Special points at the next special character (or the end of the | ||
* string). Process characters between start and special. | ||
*/ | ||
if ((maxX >= 0) && (curX >= maxX)) { | ||
break; | ||
} | ||
start += Tk_MeasureChars(tkfont, start, special - start, maxX - curX, | ||
0, &width); | ||
curX += width; | ||
if (start < special) { | ||
/* | ||
* No more chars fit in line. | ||
*/ | ||
break; | ||
} | ||
if (special < end) { | ||
if (ch == '\t') { | ||
start++; | ||
} else { | ||
break; | ||
} | ||
} | ||
} | ||
*nextXPtr = curX; | ||
return start - source; | ||
} | ||
/* $History: tkTextDisp.c $ | ||
* | ||
* ***************** Version 1 ***************** | ||
* User: Dtashley Date: 1/02/01 Time: 3:07a | ||
* Created in $/IjuScripter, IjuConsole/Source/Tk Base | ||
* Initial check-in. | ||
*/ | ||
/* End of TKTEXTDISP.C */ | ||
1 | /* $Header$ */ | |
2 | ||
3 | /* | |
4 | * tkTextDisp.c -- | |
5 | * | |
6 | * This module provides facilities to display text widgets. It is | |
7 | * the only place where information is kept about the screen layout | |
8 | * of text widgets. | |
9 | * | |
10 | * Copyright (c) 1992-1994 The Regents of the University of California. | |
11 | * Copyright (c) 1994-1997 Sun Microsystems, Inc. | |
12 | * | |
13 | * See the file "license.terms" for information on usage and redistribution | |
14 | * of this file, and for a DISCLAIMER OF ALL WARRANTIES. | |
15 | * | |
16 | * RCS: @(#) $Id: tktextdisp.c,v 1.1.1.1 2001/06/13 05:10:16 dtashley Exp $ | |
17 | */ | |
18 | ||
19 | #include "tkPort.h" | |
20 | #include "tkInt.h" | |
21 | #include "tkText.h" | |
22 | ||
23 | #ifdef __WIN32__ | |
24 | #include "tkWinInt.h" | |
25 | #endif | |
26 | ||
27 | /* | |
28 | * The following structure describes how to display a range of characters. | |
29 | * The information is generated by scanning all of the tags associated | |
30 | * with the characters and combining that with default information for | |
31 | * the overall widget. These structures form the hash keys for | |
32 | * dInfoPtr->styleTable. | |
33 | */ | |
34 | ||
35 | typedef struct StyleValues { | |
36 | Tk_3DBorder border; /* Used for drawing background under text. | |
37 | * NULL means use widget background. */ | |
38 | int borderWidth; /* Width of 3-D border for background. */ | |
39 | int relief; /* 3-D relief for background. */ | |
40 | Pixmap bgStipple; /* Stipple bitmap for background. None | |
41 | * means draw solid. */ | |
42 | XColor *fgColor; /* Foreground color for text. */ | |
43 | Tk_Font tkfont; /* Font for displaying text. */ | |
44 | Pixmap fgStipple; /* Stipple bitmap for text and other | |
45 | * foreground stuff. None means draw | |
46 | * solid.*/ | |
47 | int justify; /* Justification style for text. */ | |
48 | int lMargin1; /* Left margin, in pixels, for first display | |
49 | * line of each text line. */ | |
50 | int lMargin2; /* Left margin, in pixels, for second and | |
51 | * later display lines of each text line. */ | |
52 | int offset; /* Offset in pixels of baseline, relative to | |
53 | * baseline of line. */ | |
54 | int overstrike; /* Non-zero means draw overstrike through | |
55 | * text. */ | |
56 | int rMargin; /* Right margin, in pixels. */ | |
57 | int spacing1; /* Spacing above first dline in text line. */ | |
58 | int spacing2; /* Spacing between lines of dline. */ | |
59 | int spacing3; /* Spacing below last dline in text line. */ | |
60 | TkTextTabArray *tabArrayPtr;/* Locations and types of tab stops (may | |
61 | * be NULL). */ | |
62 | int underline; /* Non-zero means draw underline underneath | |
63 | * text. */ | |
64 | int elide; /* Non-zero means draw text */ | |
65 | TkWrapMode wrapMode; /* How to handle wrap-around for this tag. | |
66 | * One of TEXT_WRAPMODE_CHAR, | |
67 | * TEXT_WRAPMODE_NONE or TEXT_WRAPMODE_WORD.*/ | |
68 | } StyleValues; | |
69 | ||
70 | /* | |
71 | * The following structure extends the StyleValues structure above with | |
72 | * graphics contexts used to actually draw the characters. The entries | |
73 | * in dInfoPtr->styleTable point to structures of this type. | |
74 | */ | |
75 | ||
76 | typedef struct TextStyle { | |
77 | int refCount; /* Number of times this structure is | |
78 | * referenced in Chunks. */ | |
79 | GC bgGC; /* Graphics context for background. None | |
80 | * means use widget background. */ | |
81 | GC fgGC; /* Graphics context for foreground. */ | |
82 | StyleValues *sValuePtr; /* Raw information from which GCs were | |
83 | * derived. */ | |
84 | Tcl_HashEntry *hPtr; /* Pointer to entry in styleTable. Used | |
85 | * to delete entry. */ | |
86 | } TextStyle; | |
87 | ||
88 | /* | |
89 | * The following macro determines whether two styles have the same | |
90 | * background so that, for example, no beveled border should be drawn | |
91 | * between them. | |
92 | */ | |
93 | ||
94 | #define SAME_BACKGROUND(s1, s2) \ | |
95 | (((s1)->sValuePtr->border == (s2)->sValuePtr->border) \ | |
96 | && ((s1)->sValuePtr->borderWidth == (s2)->sValuePtr->borderWidth) \ | |
97 | && ((s1)->sValuePtr->relief == (s2)->sValuePtr->relief) \ | |
98 | && ((s1)->sValuePtr->bgStipple == (s2)->sValuePtr->bgStipple)) | |
99 | ||
100 | /* | |
101 | * The following structure describes one line of the display, which may | |
102 | * be either part or all of one line of the text. | |
103 | */ | |
104 | ||
105 | typedef struct DLine { | |
106 | TkTextIndex index; /* Identifies first character in text | |
107 | * that is displayed on this line. */ | |
108 | int byteCount; /* Number of bytes accounted for by this | |
109 | * display line, including a trailing space | |
110 | * or newline that isn't actually displayed. */ | |
111 | int y; /* Y-position at which line is supposed to | |
112 | * be drawn (topmost pixel of rectangular | |
113 | * area occupied by line). */ | |
114 | int oldY; /* Y-position at which line currently | |
115 | * appears on display. -1 means line isn't | |
116 | * currently visible on display and must be | |
117 | * redrawn. This is used to move lines by | |
118 | * scrolling rather than re-drawing. */ | |
119 | int height; /* Height of line, in pixels. */ | |
120 | int baseline; /* Offset of text baseline from y, in | |
121 | * pixels. */ | |
122 | int spaceAbove; /* How much extra space was added to the | |
123 | * top of the line because of spacing | |
124 | * options. This is included in height | |
125 | * and baseline. */ | |
126 | int spaceBelow; /* How much extra space was added to the | |
127 | * bottom of the line because of spacing | |
128 | * options. This is included in height. */ | |
129 | int length; /* Total length of line, in pixels. */ | |
130 | TkTextDispChunk *chunkPtr; /* Pointer to first chunk in list of all | |
131 | * of those that are displayed on this | |
132 | * line of the screen. */ | |
133 | struct DLine *nextPtr; /* Next in list of all display lines for | |
134 | * this window. The list is sorted in | |
135 | * order from top to bottom. Note: the | |
136 | * next DLine doesn't always correspond | |
137 | * to the next line of text: (a) can have | |
138 | * multiple DLines for one text line, and | |
139 | * (b) can have gaps where DLine's have been | |
140 | * deleted because they're out of date. */ | |
141 | int flags; /* Various flag bits: see below for values. */ | |
142 | } DLine; | |
143 | ||
144 | /* | |
145 | * Flag bits for DLine structures: | |
146 | * | |
147 | * HAS_3D_BORDER - Non-zero means that at least one of the | |
148 | * chunks in this line has a 3D border, so | |
149 | * it potentially interacts with 3D borders | |
150 | * in neighboring lines (see | |
151 | * DisplayLineBackground). | |
152 | * NEW_LAYOUT - Non-zero means that the line has been | |
153 | * re-layed out since the last time the | |
154 | * display was updated. | |
155 | * TOP_LINE - Non-zero means that this was the top line | |
156 | * in the window the last time that the window | |
157 | * was laid out. This is important because | |
158 | * a line may be displayed differently if its | |
159 | * at the top or bottom than if it's in the | |
160 | * middle (e.g. beveled edges aren't displayed | |
161 | * for middle lines if the adjacent line has | |
162 | * a similar background). | |
163 | * BOTTOM_LINE - Non-zero means that this was the bottom line | |
164 | * in the window the last time that the window | |
165 | * was laid out. | |
166 | * IS_DISABLED - This Dline cannot be edited. | |
167 | */ | |
168 | ||
169 | #define HAS_3D_BORDER 1 | |
170 | #define NEW_LAYOUT 2 | |
171 | #define TOP_LINE 4 | |
172 | #define BOTTOM_LINE 8 | |
173 | #define IS_DISABLED 16 | |
174 | ||
175 | /* | |
176 | * Overall display information for a text widget: | |
177 | */ | |
178 | ||
179 | typedef struct TextDInfo { | |
180 | Tcl_HashTable styleTable; /* Hash table that maps from StyleValues | |
181 | * to TextStyles for this widget. */ | |
182 | DLine *dLinePtr; /* First in list of all display lines for | |
183 | * this widget, in order from top to bottom. */ | |
184 | GC copyGC; /* Graphics context for copying from off- | |
185 | * screen pixmaps onto screen. */ | |
186 | GC scrollGC; /* Graphics context for copying from one place | |
187 | * in the window to another (scrolling): | |
188 | * differs from copyGC in that we need to get | |
189 | * GraphicsExpose events. */ | |
190 | int x; /* First x-coordinate that may be used for | |
191 | * actually displaying line information. | |
192 | * Leaves space for border, etc. */ | |
193 | int y; /* First y-coordinate that may be used for | |
194 | * actually displaying line information. | |
195 | * Leaves space for border, etc. */ | |
196 | int maxX; /* First x-coordinate to right of available | |
197 | * space for displaying lines. */ | |
198 | int maxY; /* First y-coordinate below available | |
199 | * space for displaying lines. */ | |
200 | int topOfEof; /* Top-most pixel (lowest y-value) that has | |
201 | * been drawn in the appropriate fashion for | |
202 | * the portion of the window after the last | |
203 | * line of the text. This field is used to | |
204 | * figure out when to redraw part or all of | |
205 | * the eof field. */ | |
206 | ||
207 | /* | |
208 | * Information used for scrolling: | |
209 | */ | |
210 | ||
211 | int newByteOffset; /* Desired x scroll position, measured as the | |
212 | * number of average-size characters off-screen | |
213 | * to the left for a line with no left | |
214 | * margin. */ | |
215 | int curPixelOffset; /* Actual x scroll position, measured as the | |
216 | * number of pixels off-screen to the left. */ | |
217 | int maxLength; /* Length in pixels of longest line that's | |
218 | * visible in window (length may exceed window | |
219 | * size). If there's no wrapping, this will | |
220 | * be zero. */ | |
221 | double xScrollFirst, xScrollLast; | |
222 | /* Most recent values reported to horizontal | |
223 | * scrollbar; used to eliminate unnecessary | |
224 | * reports. */ | |
225 | double yScrollFirst, yScrollLast; | |
226 | /* Most recent values reported to vertical | |
227 | * scrollbar; used to eliminate unnecessary | |
228 | * reports. */ | |
229 | ||
230 | /* | |
231 | * The following information is used to implement scanning: | |
232 | */ | |
233 | ||
234 | int scanMarkIndex; /* Byte index of character that was at the | |
235 | * left edge of the window when the scan | |
236 | * started. */ | |
237 | int scanMarkX; /* X-position of mouse at time scan started. */ | |
238 | int scanTotalScroll; /* Total scrolling (in screen lines) that has | |
239 | * occurred since scanMarkY was set. */ | |
240 | int scanMarkY; /* Y-position of mouse at time scan started. */ | |
241 | ||
242 | /* | |
243 | * Miscellaneous information: | |
244 | */ | |
245 | ||
246 | int dLinesInvalidated; /* This value is set to 1 whenever something | |
247 | * happens that invalidates information in | |
248 | * DLine structures; if a redisplay | |
249 | * is in progress, it will see this and | |
250 | * abort the redisplay. This is needed | |
251 | * because, for example, an embedded window | |
252 | * could change its size when it is first | |
253 | * displayed, invalidating the DLine that | |
254 | * is currently being displayed. If redisplay | |
255 | * continues, it will use freed memory and | |
256 | * could dump core. */ | |
257 | int flags; /* Various flag values: see below for | |
258 | * definitions. */ | |
259 | } TextDInfo; | |
260 | ||
261 | /* | |
262 | * In TkTextDispChunk structures for character segments, the clientData | |
263 | * field points to one of the following structures: | |
264 | */ | |
265 | ||
266 | typedef struct CharInfo { | |
267 | int numBytes; /* Number of bytes to display. */ | |
268 | char chars[4]; /* UTF characters to display. Actual size | |
269 | * will be numBytes, not 4. THIS MUST BE | |
270 | * THE LAST FIELD IN THE STRUCTURE. */ | |
271 | } CharInfo; | |
272 | ||
273 | /* | |
274 | * Flag values for TextDInfo structures: | |
275 | * | |
276 | * DINFO_OUT_OF_DATE: Non-zero means that the DLine structures | |
277 | * for this window are partially or completely | |
278 | * out of date and need to be recomputed. | |
279 | * REDRAW_PENDING: Means that a when-idle handler has been | |
280 | * scheduled to update the display. | |
281 | * REDRAW_BORDERS: Means window border or pad area has | |
282 | * potentially been damaged and must be redrawn. | |
283 | * REPICK_NEEDED: 1 means that the widget has been modified | |
284 | * in a way that could change the current | |
285 | * character (a different character might be | |
286 | * under the mouse cursor now). Need to | |
287 | * recompute the current character before | |
288 | * the next redisplay. | |
289 | */ | |
290 | ||
291 | #define DINFO_OUT_OF_DATE 1 | |
292 | #define REDRAW_PENDING 2 | |
293 | #define REDRAW_BORDERS 4 | |
294 | #define REPICK_NEEDED 8 | |
295 | ||
296 | /* | |
297 | * The following counters keep statistics about redisplay that can be | |
298 | * checked to see how clever this code is at reducing redisplays. | |
299 | */ | |
300 | ||
301 | static int numRedisplays; /* Number of calls to DisplayText. */ | |
302 | static int linesRedrawn; /* Number of calls to DisplayDLine. */ | |
303 | static int numCopies; /* Number of calls to XCopyArea to copy part | |
304 | * of the screen. */ | |
305 | ||
306 | /* | |
307 | * Forward declarations for procedures defined later in this file: | |
308 | */ | |
309 | ||
310 | static void AdjustForTab _ANSI_ARGS_((TkText *textPtr, | |
311 | TkTextTabArray *tabArrayPtr, int index, | |
312 | TkTextDispChunk *chunkPtr)); | |
313 | static void CharBboxProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr, | |
314 | int index, int y, int lineHeight, int baseline, | |
315 | int *xPtr, int *yPtr, int *widthPtr, | |
316 | int *heightPtr)); | |
317 | static void CharDisplayProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr, | |
318 | int x, int y, int height, int baseline, | |
319 | Display *display, Drawable dst, int screenY)); | |
320 | static int CharMeasureProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr, | |
321 | int x)); | |
322 | static void CharUndisplayProc _ANSI_ARGS_((TkText *textPtr, | |
323 | TkTextDispChunk *chunkPtr)); | |
324 | ||
325 | /* | |
326 | Definitions of elided procs. | |
327 | Compiler can't inline these since we use pointers to these functions. | |
328 | ElideDisplayProc, ElideUndisplayProc special-cased for speed, | |
329 | as potentially many elided DLine chunks if large, tag toggle-filled | |
330 | elided region. | |
331 | */ | |
332 | static void ElideBboxProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr, | |
333 | int index, int y, int lineHeight, int baseline, | |
334 | int *xPtr, int *yPtr, int *widthPtr, | |
335 | int *heightPtr)); | |
336 | static int ElideMeasureProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr, | |
337 | int x)); | |
338 | ||
339 | static void DisplayDLine _ANSI_ARGS_((TkText *textPtr, | |
340 | DLine *dlPtr, DLine *prevPtr, Pixmap pixmap)); | |
341 | static void DisplayLineBackground _ANSI_ARGS_((TkText *textPtr, | |
342 | DLine *dlPtr, DLine *prevPtr, Pixmap pixmap)); | |
343 | static void DisplayText _ANSI_ARGS_((ClientData clientData)); | |
344 | static DLine * FindDLine _ANSI_ARGS_((DLine *dlPtr, | |
345 | TkTextIndex *indexPtr)); | |
346 | static void FreeDLines _ANSI_ARGS_((TkText *textPtr, | |
347 | DLine *firstPtr, DLine *lastPtr, int unlink)); | |
348 | static void FreeStyle _ANSI_ARGS_((TkText *textPtr, | |
349 | TextStyle *stylePtr)); | |
350 | static TextStyle * GetStyle _ANSI_ARGS_((TkText *textPtr, | |
351 | TkTextIndex *indexPtr)); | |
352 | static void GetXView _ANSI_ARGS_((Tcl_Interp *interp, | |
353 | TkText *textPtr, int report)); | |
354 | static void GetYView _ANSI_ARGS_((Tcl_Interp *interp, | |
355 | TkText *textPtr, int report)); | |
356 | static DLine * LayoutDLine _ANSI_ARGS_((TkText *textPtr, | |
357 | TkTextIndex *indexPtr)); | |
358 | static int MeasureChars _ANSI_ARGS_((Tk_Font tkfont, | |
359 | CONST char *source, int maxBytes, int startX, | |
360 | int maxX, int tabOrigin, int *nextXPtr)); | |
361 | static void MeasureUp _ANSI_ARGS_((TkText *textPtr, | |
362 | TkTextIndex *srcPtr, int distance, | |
363 | TkTextIndex *dstPtr)); | |
364 | static int NextTabStop _ANSI_ARGS_((Tk_Font tkfont, int x, | |
365 | int tabOrigin)); | |
366 | static void UpdateDisplayInfo _ANSI_ARGS_((TkText *textPtr)); | |
367 | static void ScrollByLines _ANSI_ARGS_((TkText *textPtr, | |
368 | int offset)); | |
369 | static int SizeOfTab _ANSI_ARGS_((TkText *textPtr, | |
370 | TkTextTabArray *tabArrayPtr, int index, int x, | |
371 | int maxX)); | |
372 | static void TextInvalidateRegion _ANSI_ARGS_((TkText *textPtr, | |
373 | TkRegion region)); | |
374 | ||
375 | ||
376 | /* | |
377 | *---------------------------------------------------------------------- | |
378 | * | |
379 | * TkTextCreateDInfo -- | |
380 | * | |
381 | * This procedure is called when a new text widget is created. | |
382 | * Its job is to set up display-related information for the widget. | |
383 | * | |
384 | * Results: | |
385 | * None. | |
386 | * | |
387 | * Side effects: | |
388 | * A TextDInfo data structure is allocated and initialized and attached | |
389 | * to textPtr. | |
390 | * | |
391 | *---------------------------------------------------------------------- | |
392 | */ | |
393 | ||
394 | void | |
395 | TkTextCreateDInfo(textPtr) | |
396 | TkText *textPtr; /* Overall information for text widget. */ | |
397 | { | |
398 | register TextDInfo *dInfoPtr; | |
399 | XGCValues gcValues; | |
400 | ||
401 | dInfoPtr = (TextDInfo *) ckalloc(sizeof(TextDInfo)); | |
402 | Tcl_InitHashTable(&dInfoPtr->styleTable, sizeof(StyleValues)/sizeof(int)); | |
403 | dInfoPtr->dLinePtr = NULL; | |
404 | dInfoPtr->copyGC = None; | |
405 | gcValues.graphics_exposures = True; | |
406 | dInfoPtr->scrollGC = Tk_GetGC(textPtr->tkwin, GCGraphicsExposures, | |
407 | &gcValues); | |
408 | dInfoPtr->topOfEof = 0; | |
409 | dInfoPtr->newByteOffset = 0; | |
410 | dInfoPtr->curPixelOffset = 0; | |
411 | dInfoPtr->maxLength = 0; | |
412 | dInfoPtr->xScrollFirst = -1; | |
413 | dInfoPtr->xScrollLast = -1; | |
414 | dInfoPtr->yScrollFirst = -1; | |
415 | dInfoPtr->yScrollLast = -1; | |
416 | dInfoPtr->scanMarkIndex = 0; | |
417 | dInfoPtr->scanMarkX = 0; | |
418 | dInfoPtr->scanTotalScroll = 0; | |
419 | dInfoPtr->scanMarkY = 0; | |
420 | dInfoPtr->dLinesInvalidated = 0; | |
421 | dInfoPtr->flags = DINFO_OUT_OF_DATE; | |
422 | textPtr->dInfoPtr = dInfoPtr; | |
423 | } | |
424 | ||
425 | /* | |
426 | *---------------------------------------------------------------------- | |
427 | * | |
428 | * TkTextFreeDInfo -- | |
429 | * | |
430 | * This procedure is called to free up all of the private display | |
431 | * information kept by this file for a text widget. | |
432 | * | |
433 | * Results: | |
434 | * None. | |
435 | * | |
436 | * Side effects: | |
437 | * Lots of resources get freed. | |
438 | * | |
439 | *---------------------------------------------------------------------- | |
440 | */ | |
441 | ||
442 | void | |
443 | TkTextFreeDInfo(textPtr) | |
444 | TkText *textPtr; /* Overall information for text widget. */ | |
445 | { | |
446 | register TextDInfo *dInfoPtr = textPtr->dInfoPtr; | |
447 | ||
448 | /* | |
449 | * Be careful to free up styleTable *after* freeing up all the | |
450 | * DLines, so that the hash table is still intact to free up the | |
451 | * style-related information from the lines. Once the lines are | |
452 | * all free then styleTable will be empty. | |
453 | */ | |
454 | ||
455 | FreeDLines(textPtr, dInfoPtr->dLinePtr, (DLine *) NULL, 1); | |
456 | Tcl_DeleteHashTable(&dInfoPtr->styleTable); | |
457 | if (dInfoPtr->copyGC != None) { | |
458 | Tk_FreeGC(textPtr->display, dInfoPtr->copyGC); | |
459 | } | |
460 | Tk_FreeGC(textPtr->display, dInfoPtr->scrollGC); | |
461 | if (dInfoPtr->flags & REDRAW_PENDING) { | |
462 | Tcl_CancelIdleCall(DisplayText, (ClientData) textPtr); | |
463 | } | |
464 | ckfree((char *) dInfoPtr); | |
465 | } | |
466 | ||
467 | /* | |
468 | *---------------------------------------------------------------------- | |
469 | * | |
470 | * GetStyle -- | |
471 | * | |
472 | * This procedure creates all the information needed to display | |
473 | * text at a particular location. | |
474 | * | |
475 | * Results: | |
476 | * The return value is a pointer to a TextStyle structure that | |
477 | * corresponds to *sValuePtr. | |
478 | * | |
479 | * Side effects: | |
480 | * A new entry may be created in the style table for the widget. | |
481 | * | |
482 | *---------------------------------------------------------------------- | |
483 | */ | |
484 | ||
485 | static TextStyle * | |
486 | GetStyle(textPtr, indexPtr) | |
487 | TkText *textPtr; /* Overall information about text widget. */ | |
488 | TkTextIndex *indexPtr; /* The character in the text for which | |
489 | * display information is wanted. */ | |
490 | { | |
491 | TkTextTag **tagPtrs; | |
492 | register TkTextTag *tagPtr; | |
493 | StyleValues styleValues; | |
494 | TextStyle *stylePtr; | |
495 | Tcl_HashEntry *hPtr; | |
496 | int numTags, new, i; | |
497 | XGCValues gcValues; | |
498 | unsigned long mask; | |
499 | ||
500 | /* | |
501 | * The variables below keep track of the highest-priority specification | |
502 | * that has occurred for each of the various fields of the StyleValues. | |
503 | */ | |
504 | ||
505 | int borderPrio, borderWidthPrio, reliefPrio, bgStipplePrio; | |
506 | int fgPrio, fontPrio, fgStipplePrio; | |
507 | int underlinePrio, elidePrio, justifyPrio, offsetPrio; | |
508 | int lMargin1Prio, lMargin2Prio, rMarginPrio; | |
509 | int spacing1Prio, spacing2Prio, spacing3Prio; | |
510 | int overstrikePrio, tabPrio, wrapPrio; | |
511 | ||
512 | /* | |
513 | * Find out what tags are present for the character, then compute | |
514 | * a StyleValues structure corresponding to those tags (scan | |
515 | * through all of the tags, saving information for the highest- | |
516 | * priority tag). | |
517 | */ | |
518 | ||
519 | tagPtrs = TkBTreeGetTags(indexPtr, &numTags); | |
520 | borderPrio = borderWidthPrio = reliefPrio = bgStipplePrio = -1; | |
521 | fgPrio = fontPrio = fgStipplePrio = -1; | |
522 | underlinePrio = elidePrio = justifyPrio = offsetPrio = -1; | |
523 | lMargin1Prio = lMargin2Prio = rMarginPrio = -1; | |
524 | spacing1Prio = spacing2Prio = spacing3Prio = -1; | |
525 | overstrikePrio = tabPrio = wrapPrio = -1; | |
526 | memset((VOID *) &styleValues, 0, sizeof(StyleValues)); | |
527 | styleValues.relief = TK_RELIEF_FLAT; | |
528 | styleValues.fgColor = textPtr->fgColor; | |
529 | styleValues.tkfont = textPtr->tkfont; | |
530 | styleValues.justify = TK_JUSTIFY_LEFT; | |
531 | styleValues.spacing1 = textPtr->spacing1; | |
532 | styleValues.spacing2 = textPtr->spacing2; | |
533 | styleValues.spacing3 = textPtr->spacing3; | |
534 | styleValues.tabArrayPtr = textPtr->tabArrayPtr; | |
535 | styleValues.wrapMode = textPtr->wrapMode; | |
536 | styleValues.elide = 0; | |
537 | for (i = 0 ; i < numTags; i++) { | |
538 | tagPtr = tagPtrs[i]; | |
539 | ||
540 | /* | |
541 | * On Windows and Mac, we need to skip the selection tag if | |
542 | * we don't have focus. | |
543 | */ | |
544 | ||
545 | #ifndef ALWAYS_SHOW_SELECTION | |
546 | if ((tagPtr == textPtr->selTagPtr) && !(textPtr->flags & GOT_FOCUS)) { | |
547 | continue; | |
548 | } | |