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