summaryrefslogtreecommitdiffstats
path: root/generic/tkTextDisp.c
diff options
context:
space:
mode:
Diffstat (limited to 'generic/tkTextDisp.c')
-rw-r--r--generic/tkTextDisp.c2421
1 files changed, 2044 insertions, 377 deletions
diff --git a/generic/tkTextDisp.c b/generic/tkTextDisp.c
index 48f2963..701fe96 100644
--- a/generic/tkTextDisp.c
+++ b/generic/tkTextDisp.c
@@ -3,7 +3,9 @@
*
* This module provides facilities to display text widgets. It is
* the only place where information is kept about the screen layout
- * of text widgets.
+ * of text widgets. (Well, strictly, each TkTextLine caches its
+ * last observed pixel height, but that information is
+ * calculated here).
*
* Copyright (c) 1992-1994 The Regents of the University of California.
* Copyright (c) 1994-1997 Sun Microsystems, Inc.
@@ -11,7 +13,7 @@
* 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.17 2003/07/07 20:39:50 hobbs Exp $
+ * RCS: @(#) $Id: tkTextDisp.c,v 1.18 2003/10/31 09:02:10 vincentdarley Exp $
*/
#include "tkPort.h"
@@ -119,11 +121,15 @@ typedef struct DLine {
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 oldY; /* Y-position at which line
+ * currently appears on display. This
+ * is used to move lines by scrolling
+ * rather than re-drawing. If 'flags'
+ * have the OLD_Y_INVALID bit set, then
+ * we will never examine this field
+ * (which means line isn't currently
+ * visible on display and must be
+ * redrawn). */
int height; /* Height of line, in pixels. */
int baseline; /* Offset of text baseline from y, in
* pixels. */
@@ -171,14 +177,20 @@ typedef struct DLine {
* 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.
+ * OLD_Y_INVALID - The value of oldY in the structure is
+ * not valid or useful and should not be
+ * examined. 'oldY' is only useful when the
+ * DLine is currently displayed at a
+ * different position and we wish to
+ * re-display it via scrolling, so this
+ * means the DLine needs redrawing.
*/
#define HAS_3D_BORDER 1
#define NEW_LAYOUT 2
#define TOP_LINE 4
#define BOTTOM_LINE 8
-#define IS_DISABLED 16
+#define OLD_Y_INVALID 16
/*
* Overall display information for a text widget:
@@ -189,6 +201,10 @@ typedef struct TextDInfo {
* to TextStyles for this widget. */
DLine *dLinePtr; /* First in list of all display lines for
* this widget, in order from top to bottom. */
+ int topPixelOffset; /* Identifies first pixel in top display
+ * line to display in window. */
+ int newTopPixelOffset; /* Desired first pixel in top display
+ * line to display in window. */
GC copyGC; /* Graphics context for copying from off-
* screen pixmaps onto screen. */
GC scrollGC; /* Graphics context for copying from one place
@@ -216,11 +232,11 @@ typedef struct TextDInfo {
* Information used for scrolling:
*/
- int newByteOffset; /* Desired x scroll position, measured as the
+ int newXByteOffset; /* 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
+ int curXPixelOffset; /* 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
@@ -243,7 +259,7 @@ typedef struct TextDInfo {
* 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
+ int scanTotalYScroll; /* Total scrolling (in screen pixels) that has
* occurred since scanMarkY was set. */
int scanMarkY; /* Y-position of mouse at time scan started. */
@@ -264,6 +280,28 @@ typedef struct TextDInfo {
* could dump core. */
int flags; /* Various flag values: see below for
* definitions. */
+ /*
+ * Information used to handle the asynchronous updating of the
+ * y-scrollbar and the vertical height calculations:
+ */
+
+ int lineMetricUpdateEpoch; /* Stores a number which is incremented
+ * each time the text widget changes in
+ * a significant way (e.g. resizing or
+ * geometry-influencing tag changes). */
+ int currentMetricUpdateLine;/* Stores a counter which is used to
+ * iterate over the logical lines
+ * contained in the widget and update
+ * their geometry calculations, if they
+ * are out of date. */
+ int lastMetricUpdateLine; /* When the current update line reaches
+ * this line, we are done and should
+ * stop the asychronous callback
+ * mechanism. */
+ Tcl_TimerToken lineUpdateTimer; /* A token pointing to the current
+ * line metric update callback. */
+ Tcl_TimerToken scrollbarTimer; /* A token pointing to the current
+ * scrollbar update callback. */
} TextDInfo;
/*
@@ -302,6 +340,20 @@ typedef struct CharInfo {
#define REPICK_NEEDED 8
/*
+ * Action values for FreeDLines:
+ *
+ * DLINE_FREE: Free the lines, but no need to unlink them
+ * from the current list of actual display lines.
+ * DLINE_UNLINK: Free and unlink from current display.
+ * DLINE_FREE_TEMP: Free, but don't unlink, and also don't
+ * set 'dLinesInvalidated'.
+ */
+
+#define DLINE_FREE 0
+#define DLINE_UNLINK 1
+#define DLINE_FREE_TEMP 2
+
+/*
* The following counters keep statistics about redisplay that can be
* checked to see how clever this code is at reducing redisplays.
*/
@@ -352,33 +404,58 @@ static void DisplayText _ANSI_ARGS_((ClientData clientData));
static DLine * FindDLine _ANSI_ARGS_((DLine *dlPtr,
CONST TkTextIndex *indexPtr));
static void FreeDLines _ANSI_ARGS_((TkText *textPtr,
- DLine *firstPtr, DLine *lastPtr, int unlink));
+ DLine *firstPtr, DLine *lastPtr, int action));
static void FreeStyle _ANSI_ARGS_((TkText *textPtr,
TextStyle *stylePtr));
static TextStyle * GetStyle _ANSI_ARGS_((TkText *textPtr,
- TkTextIndex *indexPtr));
+ CONST 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 int GetPixelCount _ANSI_ARGS_((TkText *textPtr,
+ DLine *dlPtr));
static DLine * LayoutDLine _ANSI_ARGS_((TkText *textPtr,
- TkTextIndex *indexPtr));
+ CONST 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));
+ CONST TkTextIndex *srcPtr, int distance,
+ TkTextIndex *dstPtr, int *overlap));
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,
+static void YScrollByLines _ANSI_ARGS_((TkText *textPtr,
+ int offset));
+static void YScrollByPixels _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));
+static int TextCalculateDisplayLineHeight _ANSI_ARGS_((
+ TkText *textPtr, CONST TkTextIndex *indexPtr,
+ int *byteCountPtr));
+static void DlineIndexOfX _ANSI_ARGS_((TkText *textPtr,
+ DLine *dlPtr, int x, TkTextIndex *indexPtr));
+static int DlineXOfIndex _ANSI_ARGS_((TkText *textPtr,
+ DLine *dlPtr, int byteIndex));
+static int TextGetScrollInfoObj _ANSI_ARGS_((Tcl_Interp *interp,
+ TkText *textPtr, int objc,
+ Tcl_Obj *CONST objv[], double *dblPtr,
+ int *intPtr));
+
+/*
+ * Result values returned by TextGetScrollInfo:
+ */
+
+#define TKTEXT_SCROLL_MOVETO 1
+#define TKTEXT_SCROLL_PAGES 2
+#define TKTEXT_SCROLL_UNITS 3
+#define TKTEXT_SCROLL_ERROR 4
+#define TKTEXT_SCROLL_PIXELS 5
/*
@@ -412,10 +489,10 @@ TkTextCreateDInfo(textPtr)
dInfoPtr->copyGC = None;
gcValues.graphics_exposures = True;
dInfoPtr->scrollGC = Tk_GetGC(textPtr->tkwin, GCGraphicsExposures,
- &gcValues);
+ &gcValues);
dInfoPtr->topOfEof = 0;
- dInfoPtr->newByteOffset = 0;
- dInfoPtr->curPixelOffset = 0;
+ dInfoPtr->newXByteOffset = 0;
+ dInfoPtr->curXPixelOffset = 0;
dInfoPtr->maxLength = 0;
dInfoPtr->xScrollFirst = -1;
dInfoPtr->xScrollLast = -1;
@@ -423,10 +500,25 @@ TkTextCreateDInfo(textPtr)
dInfoPtr->yScrollLast = -1;
dInfoPtr->scanMarkIndex = 0;
dInfoPtr->scanMarkX = 0;
- dInfoPtr->scanTotalScroll = 0;
+ dInfoPtr->scanTotalYScroll = 0;
dInfoPtr->scanMarkY = 0;
dInfoPtr->dLinesInvalidated = 0;
dInfoPtr->flags = DINFO_OUT_OF_DATE;
+ dInfoPtr->topPixelOffset = 0;
+ dInfoPtr->newTopPixelOffset = 0;
+ dInfoPtr->currentMetricUpdateLine = -1;
+ dInfoPtr->lastMetricUpdateLine = -1;
+ dInfoPtr->lineMetricUpdateEpoch = 1;
+
+ /* Add a refCount for each of the idle call-backs */
+ textPtr->refCount++;
+ dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(0,
+ TkTextAsyncUpdateLineMetrics, (ClientData) textPtr);
+ textPtr->refCount++;
+ dInfoPtr->scrollbarTimer = Tcl_CreateTimerHandler(200,
+ TkTextUpdateYScrollbar, (ClientData) textPtr);
+
+
textPtr->dInfoPtr = dInfoPtr;
}
@@ -460,7 +552,7 @@ TkTextFreeDInfo(textPtr)
* all free then styleTable will be empty.
*/
- FreeDLines(textPtr, dInfoPtr->dLinePtr, (DLine *) NULL, 1);
+ FreeDLines(textPtr, dInfoPtr->dLinePtr, (DLine *) NULL, DLINE_UNLINK);
Tcl_DeleteHashTable(&dInfoPtr->styleTable);
if (dInfoPtr->copyGC != None) {
Tk_FreeGC(textPtr->display, dInfoPtr->copyGC);
@@ -469,6 +561,16 @@ TkTextFreeDInfo(textPtr)
if (dInfoPtr->flags & REDRAW_PENDING) {
Tcl_CancelIdleCall(DisplayText, (ClientData) textPtr);
}
+ if (dInfoPtr->lineUpdateTimer != NULL) {
+ Tcl_DeleteTimerHandler(dInfoPtr->lineUpdateTimer);
+ textPtr->refCount--;
+ dInfoPtr->lineUpdateTimer = NULL;
+ }
+ if (dInfoPtr->scrollbarTimer != NULL) {
+ Tcl_DeleteTimerHandler(dInfoPtr->scrollbarTimer);
+ textPtr->refCount--;
+ dInfoPtr->scrollbarTimer = NULL;
+ }
ckfree((char *) dInfoPtr);
}
@@ -493,7 +595,7 @@ TkTextFreeDInfo(textPtr)
static TextStyle *
GetStyle(textPtr, indexPtr)
TkText *textPtr; /* Overall information about text widget. */
- TkTextIndex *indexPtr; /* The character in the text for which
+ CONST TkTextIndex *indexPtr;/* The character in the text for which
* display information is wanted. */
{
TkTextTag **tagPtrs;
@@ -762,6 +864,22 @@ FreeStyle(textPtr, stylePtr)
*
* Side effects:
* Storage is allocated for the new DLine.
+ *
+ * See the comments in 'GetYView' for some thoughts on what the
+ * side-effects of this call (or its callers) should be -- the
+ * synchronisation of TkTextLine->pixelHeight with the sum of
+ * the results of this function operating on all display lines
+ * within each logical line. Ideally the code should be
+ * refactored to ensure the cached pixel height is never behind
+ * what is known when this function is called elsewhere.
+ *
+ * Unfortunately, this function is currently called from many
+ * different places, not just to layout a display line for actual
+ * display, but also simply to calculate some metric or other of one
+ * or more display lines (typically the height). It would be a good
+ * idea to do some profiling of typical text widget usage and the
+ * way in which this is called and see if some optimization could or
+ * should be done.
*
*----------------------------------------------------------------------
*/
@@ -769,7 +887,7 @@ FreeStyle(textPtr, stylePtr)
static DLine *
LayoutDLine(textPtr, indexPtr)
TkText *textPtr; /* Overall information about text widget. */
- TkTextIndex *indexPtr; /* Beginning of display line. May not
+ CONST TkTextIndex *indexPtr;/* Beginning of display line. May not
* necessarily point to a character segment. */
{
register DLine *dlPtr; /* New display line. */
@@ -825,12 +943,12 @@ LayoutDLine(textPtr, indexPtr)
dlPtr->index = *indexPtr;
dlPtr->byteCount = 0;
dlPtr->y = 0;
- dlPtr->oldY = -1;
+ dlPtr->oldY = 0; /* Only setting this to avoid compiler warnings */
dlPtr->height = 0;
dlPtr->baseline = 0;
dlPtr->chunkPtr = NULL;
dlPtr->nextPtr = NULL;
- dlPtr->flags = NEW_LAYOUT;
+ dlPtr->flags = NEW_LAYOUT | OLD_Y_INVALID;
/*
* Special case entirely elide line as there may be 1000s or more
@@ -900,17 +1018,26 @@ LayoutDLine(textPtr, indexPtr)
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
+ * Every logical 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.
+ *
+ * This also means that each logical line which is entirely
+ * elided still gets laid out into a DLine, but with zero height.
+ * This isn't particularly a problem, but it does seem somewhat
+ * unnecessary. If/when we fix [Tk Bug 443848] (see below)
+ * then we will probably have to remove such zero height DLines
+ * too.
*/
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;
+ breakByteOffset = lastChunkPtr->breakIndex
+ = lastChunkPtr->numBytes;
/*
* If have we have a tag toggle, there is a chance
* that invisibility state changed, so bail out
@@ -990,22 +1117,27 @@ LayoutDLine(textPtr, indexPtr)
}
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;
+ /*
+ * 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 */
+ /* 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,
+ } 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) {
@@ -1029,7 +1161,7 @@ LayoutDLine(textPtr, indexPtr)
}
break;
}
- if (chunkPtr->numBytes > 0) {
+ if (!elide && chunkPtr->numBytes > 0) {
noCharsYet = 0;
lastCharChunkPtr = chunkPtr;
}
@@ -1073,6 +1205,16 @@ LayoutDLine(textPtr, indexPtr)
if (byteOffset >= segPtr->size) {
byteOffset = 0;
segPtr = segPtr->nextPtr;
+ if (elide && segPtr == NULL) {
+ /*
+ * An elided section started on this line, and carries on
+ * until the newline. Currently this forces a new line
+ * anyway (i.e. even though the newline is elided it
+ * still takes effect). This is because the code
+ * currently doesn't allow two or more logical lines to
+ * appear on the same display line. [Tk Bug #443848]
+ */
+ }
}
chunkPtr = NULL;
@@ -1240,7 +1382,7 @@ UpdateDisplayInfo(textPtr)
register DLine *dlPtr, *prevPtr;
TkTextIndex index;
TkTextLine *lastLinePtr;
- int y, maxY, pixelOffset, maxOffset;
+ int y, maxY, xPixelOffset, maxOffset, lineHeight;
if (!(dInfoPtr->flags & DINFO_OUT_OF_DATE)) {
return;
@@ -1254,7 +1396,12 @@ UpdateDisplayInfo(textPtr)
index = textPtr->topIndex;
dlPtr = FindDLine(dInfoPtr->dLinePtr, &index);
if ((dlPtr != NULL) && (dlPtr != dInfoPtr->dLinePtr)) {
- FreeDLines(textPtr, dInfoPtr->dLinePtr, dlPtr, 1);
+ FreeDLines(textPtr, dInfoPtr->dLinePtr, dlPtr, DLINE_UNLINK);
+ }
+ if (index.byteIndex == 0) {
+ lineHeight = 0;
+ } else {
+ lineHeight = -1;
}
/*
@@ -1265,10 +1412,10 @@ UpdateDisplayInfo(textPtr)
*/
lastLinePtr = TkBTreeFindLine(textPtr->tree,
- TkBTreeNumLines(textPtr->tree));
+ TkBTreeNumLines(textPtr->tree));
dlPtr = dInfoPtr->dLinePtr;
prevPtr = NULL;
- y = dInfoPtr->y;
+ y = dInfoPtr->y - dInfoPtr->newTopPixelOffset;
maxY = dInfoPtr->maxY;
while (1) {
register DLine *newPtr;
@@ -1300,6 +1447,7 @@ UpdateDisplayInfo(textPtr)
*/
if ((dlPtr == NULL) || (dlPtr->index.linePtr != index.linePtr)) {
+
/*
* Case (b) -- must make new DLine.
*/
@@ -1325,7 +1473,7 @@ UpdateDisplayInfo(textPtr)
} else {
prevPtr->nextPtr = newPtr;
if (prevPtr->flags & HAS_3D_BORDER) {
- prevPtr->oldY = -1;
+ prevPtr->flags |= OLD_Y_INVALID;
}
}
newPtr->nextPtr = dlPtr;
@@ -1343,7 +1491,7 @@ UpdateDisplayInfo(textPtr)
if ((dlPtr->flags & HAS_3D_BORDER) && (prevPtr != NULL)
&& (prevPtr->flags & (NEW_LAYOUT))) {
- dlPtr->oldY = -1;
+ dlPtr->flags |= OLD_Y_INVALID;
}
goto lineOK;
}
@@ -1357,7 +1505,7 @@ UpdateDisplayInfo(textPtr)
*/
newPtr = dlPtr->nextPtr;
- FreeDLines(textPtr, dlPtr, newPtr, 0);
+ FreeDLines(textPtr, dlPtr, newPtr, DLINE_FREE);
dlPtr = newPtr;
if (prevPtr != NULL) {
prevPtr->nextPtr = newPtr;
@@ -1374,6 +1522,9 @@ UpdateDisplayInfo(textPtr)
lineOK:
dlPtr->y = y;
y += dlPtr->height;
+ if (lineHeight != -1) {
+ lineHeight += dlPtr->height;
+ }
TkTextIndexForwBytes(&index, dlPtr->byteCount, &index);
prevPtr = dlPtr;
dlPtr = dlPtr->nextPtr;
@@ -1392,10 +1543,36 @@ UpdateDisplayInfo(textPtr)
nextPtr = nextPtr->nextPtr;
}
if (nextPtr != dlPtr) {
- FreeDLines(textPtr, dlPtr, nextPtr, 0);
+ FreeDLines(textPtr, dlPtr, nextPtr, DLINE_FREE);
prevPtr->nextPtr = nextPtr;
dlPtr = nextPtr;
}
+
+ if ((lineHeight != -1)
+ && (lineHeight > prevPtr->index.linePtr->pixelHeight)) {
+ /*
+ * The logical line height we just calculated is actually
+ * larger than the currently cached height of the
+ * text line. That is fine (the text line heights
+ * are only calculated asynchronously), but we must
+ * update the cached height so that any counts made
+ * with DLine pointers do not exceed counts made
+ * through the BTree.
+ */
+ TkBTreeAdjustPixelHeight(prevPtr->index.linePtr,
+ lineHeight);
+ /*
+ * I believe we can be 100% sure that we started at the
+ * beginning of the logical line, so we can also adjust
+ * the 'pixelCalculationEpoch' to mark it as being up to
+ * date. There is a slight concern that we might not
+ * have got this right for the first line in the
+ * re-display.
+ */
+ prevPtr->index.linePtr->pixelCalculationEpoch =
+ dInfoPtr->lineMetricUpdateEpoch;
+ }
+ lineHeight = 0;
}
/*
@@ -1414,75 +1591,135 @@ UpdateDisplayInfo(textPtr)
* Delete any DLine structures that don't fit on the screen.
*/
- FreeDLines(textPtr, dlPtr, (DLine *) NULL, 1);
+ FreeDLines(textPtr, dlPtr, (DLine *) NULL, DLINE_UNLINK);
/*
*--------------------------------------------------------------
* 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.
+ *
+ * Since the top line may only be partially visible, we try first
+ * to simply show more pixels from that line (newTopPixelOffset).
+ * If that isn't enough, we have to layout more lines.
*--------------------------------------------------------------
*/
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));
+ int spaceLeft = maxY - y;
+
+ if (spaceLeft <= dInfoPtr->newTopPixelOffset) {
+ /*
+ * We can full up all the needed space just by showing
+ * more of the current top line
+ */
+ dInfoPtr->newTopPixelOffset -= spaceLeft;
+ y += spaceLeft;
+ spaceLeft = 0;
+ } else {
+ int lineNum, bytesToCount;
+ DLine *lowestPtr;
+
+ /*
+ * Add in all of the current top line, which won't
+ * be enough to bring y up to maxY (if it was we
+ * would be in the 'if' block above).
+ */
+ y += dInfoPtr->newTopPixelOffset;
+ dInfoPtr->newTopPixelOffset = 0;
/*
- * Scan through the display lines from the bottom one up to
- * the top one.
+ * 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.
*/
- while (lowestPtr != NULL) {
- dlPtr = lowestPtr;
- spaceLeft -= dlPtr->height;
- if (spaceLeft < 0) {
- break;
+ 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--) {
+ int pixelHeight = 0;
+
+ index.linePtr = TkBTreeFindLine(textPtr->tree, lineNum);
+ index.byteIndex = 0;
+ lowestPtr = NULL;
+
+ do {
+ dlPtr = LayoutDLine(textPtr, &index);
+ pixelHeight += dlPtr->height;
+ 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));
+
+ /*
+ * We may not have examined the entire line (depending
+ * on the value of 'bytesToCount', so we only want
+ * to set this if it is genuinely bigger).
+ */
+ if (pixelHeight > lowestPtr->index.linePtr->pixelHeight) {
+ TkBTreeAdjustPixelHeight(lowestPtr->index.linePtr,
+ pixelHeight);
+ if (index.linePtr != lowestPtr->index.linePtr) {
+ /*
+ * We examined the entire line, so can update
+ * the epoch.
+ */
+ lowestPtr->index.linePtr->pixelCalculationEpoch =
+ dInfoPtr->lineMetricUpdateEpoch;
+ }
}
- 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",
+ /*
+ * Scan through the display lines from the bottom one up to
+ * the top one.
+ */
+
+ while (lowestPtr != NULL) {
+ dlPtr = lowestPtr;
+ spaceLeft -= dlPtr->height;
+ 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);
+ }
+ if (spaceLeft <= 0) {
+ break;
+ }
+ }
+ FreeDLines(textPtr, lowestPtr, (DLine *) NULL, DLINE_FREE);
+ bytesToCount = INT_MAX;
+ }
+ /*
+ * We've filled in the space we wanted to, and we
+ * need to store any extra overlap we've just
+ * created for the top line.
+ */
+ if (lineNum >= 0) {
+ dInfoPtr->newTopPixelOffset = -spaceLeft;
+ if (spaceLeft > 0
+ || dInfoPtr->newTopPixelOffset >= dInfoPtr->dLinePtr->height) {
+ /* Bad situation */
+ panic("Pixel height problem while laying out text widget");
}
}
- FreeDLines(textPtr, lowestPtr, (DLine *) NULL, 0);
- bytesToCount = INT_MAX;
}
/*
@@ -1492,7 +1729,7 @@ UpdateDisplayInfo(textPtr)
*/
textPtr->topIndex = dInfoPtr->dLinePtr->index;
- y = dInfoPtr->y;
+ y = dInfoPtr->y - dInfoPtr->newTopPixelOffset;
for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
dlPtr = dlPtr->nextPtr) {
if (y > dInfoPtr->maxY) {
@@ -1517,21 +1754,30 @@ UpdateDisplayInfo(textPtr)
dlPtr = dInfoPtr->dLinePtr;
if ((dlPtr->flags & HAS_3D_BORDER) && !(dlPtr->flags & TOP_LINE)) {
- dlPtr->oldY = -1;
+ dlPtr->flags |= OLD_Y_INVALID;
}
while (1) {
if ((dlPtr->flags & TOP_LINE) && (dlPtr != dInfoPtr->dLinePtr)
&& (dlPtr->flags & HAS_3D_BORDER)) {
- dlPtr->oldY = -1;
+ dlPtr->flags |= OLD_Y_INVALID;
+ }
+ /*
+ * If the old top-line was not completely showing (i.e. the
+ * pixelOffset is non-zero) and is no longer the top-line, then
+ * we must re-draw it.
+ */
+ if ((dlPtr->flags & TOP_LINE)
+ && (dInfoPtr->topPixelOffset != 0) && (dlPtr != dInfoPtr->dLinePtr)) {
+ dlPtr->flags |= OLD_Y_INVALID;
}
if ((dlPtr->flags & BOTTOM_LINE) && (dlPtr->nextPtr != NULL)
&& (dlPtr->flags & HAS_3D_BORDER)) {
- dlPtr->oldY = -1;
+ dlPtr->flags |= OLD_Y_INVALID;
}
if (dlPtr->nextPtr == NULL) {
if ((dlPtr->flags & HAS_3D_BORDER)
&& !(dlPtr->flags & BOTTOM_LINE)) {
- dlPtr->oldY = -1;
+ dlPtr->flags |= OLD_Y_INVALID;
}
dlPtr->flags &= ~TOP_LINE;
dlPtr->flags |= BOTTOM_LINE;
@@ -1541,6 +1787,7 @@ UpdateDisplayInfo(textPtr)
dlPtr = dlPtr->nextPtr;
}
dInfoPtr->dLinePtr->flags |= TOP_LINE;
+ dInfoPtr->topPixelOffset = dInfoPtr->newTopPixelOffset;
/*
* Arrange for scrollbars to be updated.
@@ -1569,18 +1816,18 @@ UpdateDisplayInfo(textPtr)
}
maxOffset = (dInfoPtr->maxLength - (dInfoPtr->maxX - dInfoPtr->x)
+ textPtr->charWidth - 1)/textPtr->charWidth;
- if (dInfoPtr->newByteOffset > maxOffset) {
- dInfoPtr->newByteOffset = maxOffset;
+ if (dInfoPtr->newXByteOffset > maxOffset) {
+ dInfoPtr->newXByteOffset = maxOffset;
}
- if (dInfoPtr->newByteOffset < 0) {
- dInfoPtr->newByteOffset = 0;
+ if (dInfoPtr->newXByteOffset < 0) {
+ dInfoPtr->newXByteOffset = 0;
}
- pixelOffset = dInfoPtr->newByteOffset * textPtr->charWidth;
- if (pixelOffset != dInfoPtr->curPixelOffset) {
- dInfoPtr->curPixelOffset = pixelOffset;
+ xPixelOffset = dInfoPtr->newXByteOffset * textPtr->charWidth;
+ if (xPixelOffset != dInfoPtr->curXPixelOffset) {
+ dInfoPtr->curXPixelOffset = xPixelOffset;
for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
dlPtr = dlPtr->nextPtr) {
- dlPtr->oldY = -1;
+ dlPtr->flags |= OLD_Y_INVALID;
}
}
}
@@ -1603,23 +1850,29 @@ UpdateDisplayInfo(textPtr)
*/
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. */
+FreeDLines(textPtr, firstPtr, lastPtr, action)
+ 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 action; /* DLINE_UNLINK means DLines are
+ * currently linked into the list
+ * rooted at
+ * textPtr->dInfoPtr->dLinePtr and
+ * they have to be unlinked.
+ * DLINE_FREE means just free without
+ * unlinking. DLINE_FREE_TEMP means
+ * the DLine given is just a
+ * temporary one and we shouldn't
+ * invalidate anything for the
+ * overall widget. */
{
register TkTextDispChunk *chunkPtr, *nextChunkPtr;
register DLine *nextDLinePtr;
- if (unlink) {
+ if (action == DLINE_UNLINK) {
if (textPtr->dInfoPtr->dLinePtr == firstPtr) {
textPtr->dInfoPtr->dLinePtr = lastPtr;
} else {
@@ -1645,7 +1898,9 @@ FreeDLines(textPtr, firstPtr, lastPtr, unlink)
ckfree((char *) firstPtr);
firstPtr = nextDLinePtr;
}
- textPtr->dInfoPtr->dLinesInvalidated = 1;
+ if (action != DLINE_FREE_TEMP) {
+ textPtr->dInfoPtr->dLinesInvalidated = 1;
+ }
}
/*
@@ -1680,7 +1935,7 @@ DisplayDLine(textPtr, dlPtr, prevPtr, pixmap)
register TkTextDispChunk *chunkPtr;
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
Display *display;
- int height, x;
+ int height, y_off;
if (dlPtr->chunkPtr == NULL) return;
@@ -1710,8 +1965,8 @@ DisplayDLine(textPtr, dlPtr, prevPtr, pixmap)
if (textPtr->state == TK_TEXT_STATE_NORMAL) {
for (chunkPtr = dlPtr->chunkPtr; (chunkPtr != NULL);
chunkPtr = chunkPtr->nextPtr) {
- x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curPixelOffset;
if (chunkPtr->displayProc == TkTextInsertDisplayProc) {
+ int x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curXPixelOffset;
(*chunkPtr->displayProc)(chunkPtr, x, dlPtr->spaceAbove,
dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
dlPtr->baseline - dlPtr->spaceAbove, display, pixmap,
@@ -1725,7 +1980,6 @@ DisplayDLine(textPtr, dlPtr, prevPtr, pixmap)
* 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);
@@ -1738,54 +1992,59 @@ DisplayDLine(textPtr, dlPtr, prevPtr, pixmap)
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).
- */
+ /*
+ * 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) {
+ int x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curXPixelOffset;
- 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)
+ 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).
+ */
+ x = -chunkPtr->width;
+ }
(*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.
+ * Copy the pixmap onto the screen. If this is the first or 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;
}
+ if (dlPtr->y < dInfoPtr->y) {
+ y_off = dInfoPtr->y - dlPtr->y;
+ height -= y_off;
+ } else {
+ y_off = 0;
+ }
XCopyArea(display, pixmap, Tk_WindowId(textPtr->tkwin), dInfoPtr->copyGC,
- dInfoPtr->x, 0, (unsigned) (dInfoPtr->maxX - dInfoPtr->x),
- (unsigned) height, dInfoPtr->x, dlPtr->y);
+ dInfoPtr->x, y_off, (unsigned) (dInfoPtr->maxX - dInfoPtr->x),
+ (unsigned) height, dInfoPtr->x, dlPtr->y + y_off);
linesRedrawn++;
}
@@ -1857,7 +2116,7 @@ DisplayLineBackground(textPtr, dlPtr, prevPtr, pixmap)
*/
display = Tk_Display(textPtr->tkwin);
- minX = dInfoPtr->curPixelOffset;
+ minX = dInfoPtr->curXPixelOffset;
xOffset = dInfoPtr->x - minX;
maxX = minX + dInfoPtr->maxX - dInfoPtr->x;
chunkPtr = dlPtr->chunkPtr;
@@ -2158,6 +2417,692 @@ DisplayLineBackground(textPtr, dlPtr, prevPtr, pixmap)
/*
*----------------------------------------------------------------------
*
+ * TkTextAsyncUpdateLineMetrics --
+ *
+ * This procedure is invoked as a background handler to update the
+ * pixel-height calculations of individual lines in an
+ * asychronous manner.
+ *
+ * Currently a timer-handler is used for this purpose, which
+ * continuously reschedules itself. It may well be better to
+ * use some other approach (e.g. a background thread). We can't
+ * use an idle-callback because of a known bug in Tcl/Tk in
+ * which idle callbacks are not allowed to re-schedule
+ * themselves. This just causes an effective infinite loop.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Line heights may be recalculated.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkTextAsyncUpdateLineMetrics(clientData)
+ ClientData clientData; /* Information about widget. */
+{
+ register TkText *textPtr = (TkText *) clientData;
+ TextDInfo *dInfoPtr = textPtr->dInfoPtr;
+ int lineNum;
+
+ dInfoPtr->lineUpdateTimer = NULL;
+
+ if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)) {
+
+ /*
+ * The widget has been deleted. Don't do anything.
+ */
+
+ if (--textPtr->refCount == 0) {
+ ckfree((char *) textPtr);
+ }
+ return;
+ }
+
+ lineNum = dInfoPtr->currentMetricUpdateLine;
+ if (lineNum == -1) {
+ dInfoPtr->lastMetricUpdateLine = 0;
+ }
+ /*
+ * Update the lines in blocks of about 24 recalculations,
+ * or 250+ lines examined, so we pass in 256 for 'doThisMuch'.
+ */
+ lineNum = TkTextUpdateLineMetrics(textPtr, lineNum,
+ dInfoPtr->lastMetricUpdateLine, 256);
+ if (lineNum == dInfoPtr->lastMetricUpdateLine) {
+ /*
+ * We have looped over all lines, so we're done. We must
+ * release our refCount on the widget (the timer token
+ * was already set to NULL above).
+ */
+ textPtr->refCount--;
+ if (textPtr->refCount == 0) {
+ ckfree((char *) textPtr);
+ }
+ return;
+ }
+ dInfoPtr->currentMetricUpdateLine = lineNum;
+ /*
+ * Re-arm the timer. We already have a refCount on the text widget
+ * so no need to adjust that.
+ */
+ dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1,
+ TkTextAsyncUpdateLineMetrics, (ClientData) textPtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextUpdateLineMetrics --
+ *
+ * This procedure updates the pixel height calculations of a
+ * range of lines in the widget. The range is from lineNum to
+ * endLine, but, if doThisMuch is positive, then the procedure
+ * may return earlier, once a certain number of lines has been
+ * examined. The line counts are from 0.
+ *
+ * If doThisMuch is -1, then all lines in the range will be
+ * updated. This will potentially take quite some time for
+ * a large text widget.
+ *
+ * Note: with bad input for lineNum and endLine, this procedure can
+ * loop indefinitely.
+ *
+ * Results:
+ * The index of the last line examined (or -1 if we are about to
+ * wrap around from end to beginning of the widget, and the next
+ * line will be the first line).
+ *
+ * Side effects:
+ * Line heights may be recalculated.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+TkTextUpdateLineMetrics(textPtr, lineNum, endLine, doThisMuch)
+ TkText *textPtr; /* Information about widget. */
+ int lineNum; /* Start at this line */
+ int endLine; /* Go no further than this line */
+ int doThisMuch; /* How many lines to check, or how many 10s of
+ * lines to recalculate. If '-1' then do
+ * everything in the range (which may take a
+ * while). */
+{
+ TkTextLine *linePtr = NULL;
+ int count = 0;
+ int totalLines = TkBTreeNumLines(textPtr->tree);
+
+ while (1) {
+ /* Get a suitable line */
+ if (lineNum == -1 && linePtr == NULL) {
+ lineNum = 0;
+ linePtr = TkBTreeFindLine(textPtr->tree, lineNum);
+ } else {
+ if (lineNum == -1 || linePtr == NULL) {
+ if (lineNum == -1) {
+ lineNum = 0;
+ }
+ linePtr = TkBTreeFindLine(textPtr->tree, lineNum);
+ } else {
+ lineNum++;
+ linePtr = TkBTreeNextLine(linePtr);
+ }
+ if (lineNum == endLine) {
+ /*
+ * We have looped over all lines, so we're done.
+ */
+ break;
+ }
+ }
+
+ if (lineNum < totalLines) {
+ /* Now update the line's metrics if necessary */
+ if (linePtr->pixelCalculationEpoch
+ != textPtr->dInfoPtr->lineMetricUpdateEpoch) {
+ /*
+ * Update the line and update the counter, counting
+ * 10 for each line we actually re-layout.
+ */
+ TkTextUpdateOneLine(textPtr, linePtr);
+ count += 10;
+ }
+ } else {
+ /*
+ * We must never recalculate the height of the
+ * last artificial line. It must stay at zero, and
+ * if we recalculate it, it will change.
+ */
+
+ if (endLine >= totalLines) {
+ break;
+ }
+ /* Set things up for the next loop through */
+ lineNum = -1;
+ }
+ count++;
+
+ if (doThisMuch != -1 && count >= doThisMuch) {
+ break;
+ }
+ }
+ if (doThisMuch == -1) {
+ /*
+ * If we were requested to provide a full update,
+ * then also update the scrollbar.
+ */
+ GetYView(textPtr->interp, textPtr, 1);
+ }
+ return lineNum;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextInvalidateLineMetrics --
+ *
+ * Mark a number of text lines as having invalid line metric
+ * calculations. Never call this with linePtr as the last
+ * (artificial) line in the text. Depending on 'action' which
+ * indicates whether the given lines are simply invalid or have
+ * been inserted or deleted, the pre-existing asynchronous line
+ * update range may need to be adjusted.
+ *
+ * If linePtr is NULL then 'lineCount' and 'action' are ignored and
+ * all lines are invalidated.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * May schedule an asychronous callback.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkTextInvalidateLineMetrics(textPtr, linePtr, lineCount, action)
+ TkText *textPtr; /* Widget record for text widget. */
+ TkTextLine *linePtr; /* Invalidation starts from this line. */
+ int lineCount; /* And includes this many following
+ * lines. */
+ int action; /* Indicates what type of invalidation
+ * occurred (insert, delete, or
+ * simple). */
+{
+ int fromLine;
+ TextDInfo *dInfoPtr = textPtr->dInfoPtr;
+
+ if (linePtr != NULL) {
+ int counter = lineCount;
+
+ fromLine = TkBTreeLineIndex(linePtr);
+
+ /*
+ * Invalid the height calculations of each line in the
+ * given range.
+ */
+ linePtr->pixelCalculationEpoch = 0;
+ while (counter > 0 && linePtr != 0) {
+ linePtr = TkBTreeNextLine(linePtr);
+ if (linePtr != NULL) {
+ linePtr->pixelCalculationEpoch = 0;
+ }
+ counter--;
+ }
+ /*
+ * Now schedule an examination of each line in the union of the
+ * old and new update ranges, including the (possibly empty)
+ * range in between. If that between range is not-empty, then we
+ * are examining more lines than is strictly necessary (but the
+ * examination of the extra lines should be quick, since their
+ * pixelCalculationEpoch will be up to date). However, to keep
+ * track of that would require more complex record-keeping that
+ * what we have.
+ */
+ if (dInfoPtr->lineUpdateTimer == NULL) {
+ dInfoPtr->currentMetricUpdateLine = fromLine;
+ if (action == TK_TEXT_INVALIDATE_DELETE) {
+ lineCount = 0;
+ }
+ dInfoPtr->lastMetricUpdateLine = fromLine + lineCount + 1;
+ } else {
+ int toLine = fromLine + lineCount + 1;
+
+ if (action == TK_TEXT_INVALIDATE_DELETE) {
+ if (toLine <= dInfoPtr->currentMetricUpdateLine) {
+ dInfoPtr->currentMetricUpdateLine = fromLine;
+ if (dInfoPtr->lastMetricUpdateLine != -1) {
+ dInfoPtr->lastMetricUpdateLine -= lineCount;
+ }
+ } else if (fromLine <= dInfoPtr->currentMetricUpdateLine) {
+ dInfoPtr->currentMetricUpdateLine = fromLine;
+ if (toLine <= dInfoPtr->lastMetricUpdateLine) {
+ dInfoPtr->lastMetricUpdateLine -= lineCount;
+ }
+ } else {
+ if (dInfoPtr->lastMetricUpdateLine != -1) {
+ dInfoPtr->lastMetricUpdateLine = toLine;
+ }
+ }
+ } else if (action == TK_TEXT_INVALIDATE_INSERT) {
+ if (toLine <= dInfoPtr->currentMetricUpdateLine) {
+ dInfoPtr->currentMetricUpdateLine = fromLine;
+ if (dInfoPtr->lastMetricUpdateLine != -1) {
+ dInfoPtr->lastMetricUpdateLine += lineCount;
+ }
+ } else if (fromLine <= dInfoPtr->currentMetricUpdateLine) {
+ dInfoPtr->currentMetricUpdateLine = fromLine;
+ if (toLine <= dInfoPtr->lastMetricUpdateLine) {
+ dInfoPtr->lastMetricUpdateLine += lineCount;
+ }
+ if (toLine > dInfoPtr->lastMetricUpdateLine) {
+ dInfoPtr->lastMetricUpdateLine = toLine;
+ }
+ } else {
+ if (dInfoPtr->lastMetricUpdateLine != -1) {
+ dInfoPtr->lastMetricUpdateLine = toLine;
+ }
+ }
+ } else {
+ if (fromLine < dInfoPtr->currentMetricUpdateLine) {
+ dInfoPtr->currentMetricUpdateLine = fromLine;
+ }
+ if (dInfoPtr->lastMetricUpdateLine != -1
+ && toLine > dInfoPtr->lastMetricUpdateLine) {
+ dInfoPtr->lastMetricUpdateLine = toLine;
+ }
+ }
+ }
+ } else {
+ /*
+ * This invalidates the height of all lines in the widget.
+ */
+ if ((++dInfoPtr->lineMetricUpdateEpoch) == 0) {
+ dInfoPtr->lineMetricUpdateEpoch++;
+ }
+ /*
+ * This has the effect of forcing an entire new loop
+ * of update checks on all lines in the widget.
+ */
+ if (dInfoPtr->lineUpdateTimer == NULL) {
+ dInfoPtr->currentMetricUpdateLine = -1;
+ }
+ dInfoPtr->lastMetricUpdateLine = dInfoPtr->currentMetricUpdateLine;
+ }
+
+ /*
+ * Now re-set the current update calculations
+ */
+ if (dInfoPtr->lineUpdateTimer == NULL) {
+ textPtr->refCount++;
+ dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1,
+ TkTextAsyncUpdateLineMetrics, (ClientData) textPtr);
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextFindDisplayLineEnd --
+ *
+ * This procedure is invoked to find the index of the beginning or
+ * end of the particular display line on which the given index sits,
+ * whether that line is displayed or not.
+ *
+ * If 'end' is zero, we look for the start, and if 'end' is one
+ * we look for the end.
+ *
+ * If the beginning of the current display line is elided, and we
+ * are looking for the start of the line, then the returned index
+ * will be the first elided index on the display line.
+ *
+ * Similarly if the end of the current display line is elided
+ * and we are looking for the end, then the returned index will
+ * be the last elided index on the display line. (NB. This also
+ * highlights a current bug in the text widget that we cannot
+ * place two logical lines on a single display line -- even
+ * though the newline in this case is elided, it still causes
+ * a line break to be shown).
+ *
+ * Results:
+ * Modifies indexPtr to point to the given end.
+ *
+ * If xOffset is non-NULL, it is set to the x-pixel offset of the
+ * given original index within the given display line.
+ *
+ * Side effects:
+ * The combination of 'LayoutDLine' and 'FreeDLines' seems
+ * like a rather time-consuming way of gathering the information
+ * we need, so this would be a good place to look to speed up
+ * the calculations. In particular these calls will map and
+ * unmap embedded windows respectively, which I would hope isn't
+ * exactly necessary!
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkTextFindDisplayLineEnd(textPtr, indexPtr, end, xOffset)
+ TkText *textPtr; /* Widget record for text widget. */
+ TkTextIndex *indexPtr; /* Index we will adjust to the display
+ * line start or end. */
+ int end; /* 0 = start, 1 = end. */
+ int *xOffset; /* NULL, or used to store the x-pixel
+ * offset of the original index within
+ * its display line. */
+{
+ if (!end && indexPtr->byteIndex == 0) {
+ /* Nothing to do */
+ if (xOffset != NULL) {
+ *xOffset = 0;
+ }
+ return;
+ } else {
+ TkTextIndex index = *indexPtr;
+ index.byteIndex = 0;
+ index.textPtr = NULL;
+
+ while (1) {
+ DLine *dlPtr;
+ int byteCount;
+
+ dlPtr = LayoutDLine(textPtr, &index);
+ byteCount = dlPtr->byteCount;
+
+ /*
+ * 'byteCount' goes up to the beginning of the next line,
+ * so equality here says we need one more line
+ */
+ if (index.byteIndex + byteCount > indexPtr->byteIndex) {
+ /* It's on this display line */
+ if (xOffset != NULL) {
+ /*
+ * This call takes a byte index relative to the
+ * start of the current _display_ line, not
+ * logical line. We are about to overwrite
+ * indexPtr->byteIndex, so we must do this now.
+ */
+ *xOffset = DlineXOfIndex(textPtr, dlPtr,
+ indexPtr->byteIndex - dlPtr->index.byteIndex);
+ }
+ indexPtr->byteIndex = index.byteIndex;
+ if (end) {
+ /*
+ * The index we want is one less than the number
+ * of bytes in the display line.
+ */
+ indexPtr->byteIndex += byteCount - sizeof(char);
+ }
+ FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP);
+ return;
+ }
+ FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP);
+ TkTextIndexForwBytes(&index, byteCount, &index);
+ }
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TextCalculateDisplayLineHeight --
+ *
+ * This procedure is invoked to recalculate the height of the
+ * particular display line which starts with the given index,
+ * whether that line is displayed or not.
+ *
+ * This procedure does not, in itself, update any cached
+ * information about line heights. That should be done, where
+ * necessary, by its callers.
+ *
+ * The behaviour of this procedure is _undefined_ if indexPtr
+ * is not currently at the beginning of a display line.
+ *
+ * Results:
+ * The number of vertical pixels used by the display line.
+ *
+ * If 'byteCountPtr' is non-NULL, then returns in that pointer
+ * the number of byte indices on the given display line (which
+ * can be used to update indexPtr in a loop).
+ *
+ * Side effects:
+ * The combination of 'LayoutDLine' and 'FreeDLines' seems
+ * like a rather time-consuming way of gathering the information
+ * we need, so this would be a good place to look to speed up
+ * the calculations. In particular these calls will map and
+ * unmap embedded windows respectively, which I would hope isn't
+ * exactly necessary!
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+TextCalculateDisplayLineHeight(textPtr, indexPtr, byteCountPtr)
+ TkText *textPtr; /* Widget record for text widget. */
+ CONST TkTextIndex *indexPtr; /* The index at the beginning of the
+ * display line of interest. */
+ int *byteCountPtr; /* NULL or used to return the number of
+ * byte indices on the given display
+ * line. */
+{
+ DLine *dlPtr;
+ int pixelHeight;
+
+ /*
+ * Layout, find the information we need and then free the
+ * display-line we laid-out. We must use 'FreeDLines' because it
+ * will actually call the relevant code to unmap any embedded windows
+ * which were mapped in the LayoutDLine call!
+ */
+ dlPtr = LayoutDLine(textPtr, indexPtr);
+ pixelHeight = dlPtr->height;
+ if (byteCountPtr != NULL) {
+ *byteCountPtr = dlPtr->byteCount;
+ }
+ FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP);
+
+ return pixelHeight;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIndexYPixels --
+ *
+ * This procedure is invoked to calculate the number of vertical
+ * pixels between the first index of the text widget and the given
+ * index. The range from first logical line to given logical
+ * line is determined using the cached values, and the range
+ * inside the given logical line is calculated on the fly.
+ *
+ * Results:
+ * The pixel distance between first pixel in the widget and the
+ * top of the index's current display line (could be zero).
+ *
+ * Side effects:
+ * Just those of 'TextCalculateDisplayLineHeight'.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+TkTextIndexYPixels(textPtr, indexPtr)
+ TkText *textPtr; /* Widget record for text widget. */
+ CONST TkTextIndex *indexPtr; /* The index of which we want the pixel
+ * distance from top of logical line to
+ * top of index. */
+{
+ int pixelHeight;
+ TkTextIndex index;
+
+ pixelHeight = TkBTreePixels(indexPtr->linePtr);
+
+ /*
+ * Iterate through all display-lines corresponding to the single
+ * logical line belonging to indexPtr, adding up the pixel height of
+ * each such display line as we go along, until we go past
+ * 'indexPtr'.
+ */
+
+ if (indexPtr->byteIndex == 0) {
+ return pixelHeight;
+ }
+
+ index.tree = textPtr->tree;
+ index.linePtr = indexPtr->linePtr;
+ index.byteIndex = 0;
+ index.textPtr = NULL;
+
+ while (1) {
+ int bytes, height;
+
+ /*
+ * Currently this call doesn't have many side-effects.
+ * However, if in the future we change the code so there
+ * are side-effects (such as adjusting linePtr->pixelHeight),
+ * then the code might not quite work as intended,
+ * specifically the 'linePtr->pixelHeight == pixelHeight' test
+ * below this while loop.
+ */
+ height = TextCalculateDisplayLineHeight(textPtr, &index, &bytes);
+
+ index.byteIndex += bytes;
+
+ if (index.byteIndex > indexPtr->byteIndex) {
+ return pixelHeight;
+ }
+
+ if (height > 0) {
+ pixelHeight += height;
+ }
+
+ if (index.byteIndex == indexPtr->byteIndex) {
+ return pixelHeight;
+ }
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextUpdateOneLine --
+ *
+ * This procedure is invoked to recalculate the height of a
+ * particular logical line, whether that line is displayed or not.
+ *
+ * It must NEVER be called for the artificial last TkTextLine
+ * which is used internally for administrative purposes only.
+ * That line must retain its initial height of 0 otherwise
+ * the pixel height calculation maintained by the B-tree will
+ * be wrong.
+ *
+ * Results:
+ * The number of display lines in the logical line. This could
+ * be zero if the line is totally elided.
+ *
+ * Side effects:
+ * Line heights may be recalculated, and a timer to update
+ * the scrollbar may be installed. Also see the called
+ * function 'TextCalculateDisplayLineHeight' for its side
+ * effects.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+TkTextUpdateOneLine(textPtr, linePtr)
+ TkText *textPtr; /* Widget record for text widget. */
+ TkTextLine *linePtr; /* The line of which to calculate the
+ * height. */
+{
+ TkTextIndex index;
+ int pixelHeight, displayLines;
+
+ index.tree = textPtr->tree;
+ index.linePtr = linePtr;
+ index.byteIndex = 0;
+ index.textPtr = NULL;
+
+ /*
+ * Iterate through all display-lines corresponding to the
+ * single logical line 'linePtr', adding up the pixel height
+ * of each such display line as we go along. The final
+ * total is, therefore, the height of the logical line.
+ */
+
+ pixelHeight = 0;
+ displayLines = 0;
+
+ while (1) {
+ int bytes, height;
+
+ /*
+ * Currently this call doesn't have many side-effects.
+ * However, if in the future we change the code so there
+ * are side-effects (such as adjusting linePtr->pixelHeight),
+ * then the code might not quite work as intended,
+ * specifically the 'linePtr->pixelHeight == pixelHeight' test
+ * below this while loop.
+ */
+ height = TextCalculateDisplayLineHeight(textPtr, &index, &bytes);
+
+ if (height > 0) {
+ pixelHeight += height;
+ displayLines++;
+ }
+
+ if (TkTextIndexForwBytes(&index, bytes, &index)) {
+ break;
+ }
+
+ if (index.linePtr != linePtr) {
+ break;
+ }
+ }
+
+ /*
+ * Mark the logical line as being up to date (caution: it isn't
+ * yet up to date, that will happen in TkBTreeAdjustPixelHeight
+ * just below).
+ */
+ linePtr->pixelCalculationEpoch = textPtr->dInfoPtr->lineMetricUpdateEpoch;
+
+ if (linePtr->pixelHeight == pixelHeight) {
+ return displayLines;
+ }
+
+ /*
+ * We now use the resulting 'pixelHeight' to refer to the
+ * height of the entire widget, which may be used just below
+ * for reporting/debugging purposes
+ */
+ pixelHeight = TkBTreeAdjustPixelHeight(linePtr, pixelHeight);
+
+ if (tkTextDebug) {
+ char buffer[TCL_INTEGER_SPACE + 1];
+
+ if (TkBTreeNextLine(linePtr) == NULL) {
+ panic("Mustn't ever update line height of last artificial line");
+ }
+
+ sprintf(buffer, "%d", pixelHeight);
+ Tcl_SetVar2(textPtr->interp, "tk_textNumPixels", (char *) NULL,
+ buffer, TCL_GLOBAL_ONLY);
+ }
+ if (textPtr->dInfoPtr->scrollbarTimer == NULL) {
+ textPtr->refCount++;
+ textPtr->dInfoPtr->scrollbarTimer = Tcl_CreateTimerHandler(200,
+ TkTextUpdateYScrollbar, (ClientData) textPtr);
+ }
+ return displayLines;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
* DisplayText --
*
* This procedure is invoked as a when-idle handler to update the
@@ -2243,14 +3188,16 @@ DisplayText(clientData)
*/
while (dInfoPtr->flags & REPICK_NEEDED) {
- Tcl_Preserve((ClientData) textPtr);
+ textPtr->refCount++;
dInfoPtr->flags &= ~REPICK_NEEDED;
TkTextPickCurrent(textPtr, &textPtr->pickEvent);
+ if (--textPtr->refCount == 0) {
+ ckfree((char *) textPtr);
+ goto end;
+ }
if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)) {
- Tcl_Release((ClientData) textPtr);
goto end;
}
- Tcl_Release((ClientData) textPtr);
}
/*
@@ -2262,7 +3209,10 @@ DisplayText(clientData)
/*
* See if it's possible to bring some parts of the screen up-to-date
- * by scrolling (copying from other parts of the screen).
+ * by scrolling (copying from other parts of the screen). We have
+ * to be particularly careful with the top and bottom lines of the
+ * display, since these may only be partially visible and therefore
+ * not helpful for some scrolling purposes.
*/
for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) {
@@ -2270,8 +3220,30 @@ DisplayText(clientData)
int offset, height, y, oldY;
TkRegion damageRgn;
- if ((dlPtr->oldY == -1) || (dlPtr->y == dlPtr->oldY)
- || ((dlPtr->oldY + dlPtr->height) > dInfoPtr->maxY)) {
+ /*
+ * These tests are, in order:
+ *
+ * 1. If the line is already marked as invalid
+ * 2. If the line hasn't moved
+ * 3. If the line overlaps the bottom of the window and we
+ * are scrolling up
+ * 4. If the line overlaps the top of the window and we are
+ * scrolling down
+ *
+ * If any of these tests are true, then we can't scroll this
+ * line's part of the display.
+ *
+ * Note that even if tests 3 or 4 aren't true, we may be
+ * able to scroll the line, but we still need to be sure
+ * to call embedded window display procs on top and bottom
+ * lines if they have any portion non-visible (see below).
+ */
+ if ((dlPtr->flags & OLD_Y_INVALID)
+ || (dlPtr->y == dlPtr->oldY)
+ || (((dlPtr->oldY + dlPtr->height) > dInfoPtr->maxY)
+ && (dlPtr->y < dlPtr->oldY))
+ || ((dlPtr->oldY < dInfoPtr->y)
+ && (dlPtr->y > dlPtr->oldY))) {
continue;
}
@@ -2286,7 +3258,7 @@ DisplayText(clientData)
y = dlPtr->y;
for (dlPtr2 = dlPtr->nextPtr; dlPtr2 != NULL;
dlPtr2 = dlPtr2->nextPtr) {
- if ((dlPtr2->oldY == -1)
+ if ((dlPtr2->flags & OLD_Y_INVALID)
|| ((dlPtr2->oldY + offset) != dlPtr2->y)
|| ((dlPtr2->oldY + dlPtr2->height) > dInfoPtr->maxY)) {
break;
@@ -2298,18 +3270,29 @@ DisplayText(clientData)
* 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;
-
+ if (y < dInfoPtr->y) {
+ /*
+ * Adjust if the area being copied is going to overwrite
+ * the top border of the window (so the top line is only
+ * half onscreen).
+ */
+ int y_off = dInfoPtr->y - dlPtr->y;
+ height -= y_off;
+ oldY += y_off;
+ y = dInfoPtr->y;
+ }
+
/*
* Update the lines we are going to scroll to show that they
* have been copied.
*/
while (1) {
+ /* The DLine already has OLD_Y_INVALID cleared */
dlPtr->oldY = dlPtr->y;
if (dlPtr->nextPtr == dlPtr2) {
break;
@@ -2324,10 +3307,10 @@ DisplayText(clientData)
*/
for ( ; dlPtr2 != NULL; dlPtr2 = dlPtr2->nextPtr) {
- if ((dlPtr2->oldY != -1)
+ if ((!(dlPtr2->flags & OLD_Y_INVALID))
&& ((dlPtr2->oldY + dlPtr2->height) > y)
&& (dlPtr2->oldY < (y + height))) {
- dlPtr2->oldY = -1;
+ dlPtr2->flags |= OLD_Y_INVALID;
}
}
@@ -2439,14 +3422,23 @@ DisplayText(clientData)
maxHeight = -1;
for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
dlPtr = dlPtr->nextPtr) {
- if ((dlPtr->height > maxHeight) && (dlPtr->oldY != dlPtr->y)) {
+ if ((dlPtr->height > maxHeight)
+ && ((dlPtr->flags & OLD_Y_INVALID) || (dlPtr->oldY != dlPtr->y))) {
maxHeight = dlPtr->height;
}
bottomY = dlPtr->y + dlPtr->height;
}
- if (maxHeight > dInfoPtr->maxY) {
- maxHeight = dInfoPtr->maxY;
+ /*
+ * There used to be a line here which restricted 'maxHeight' to be no
+ * larger than 'dInfoPtr->maxY', but this is incorrect for the case
+ * where individual lines may be taller than the widget _and_ we have
+ * smooth scrolling. What we can do is restrict maxHeight to be
+ * no larger than 'dInfoPtr->maxY + dInfoPtr->topPixelOffset'.
+ */
+ if (maxHeight > (dInfoPtr->maxY + dInfoPtr->topPixelOffset)) {
+ maxHeight = (dInfoPtr->maxY + dInfoPtr->topPixelOffset);
}
+
if (maxHeight > 0) {
pixmap = Tk_GetPixmap(Tk_Display(textPtr->tkwin),
Tk_WindowId(textPtr->tkwin), Tk_Width(textPtr->tkwin),
@@ -2455,7 +3447,7 @@ DisplayText(clientData)
(dlPtr != NULL) && (dlPtr->y < dInfoPtr->maxY);
prevPtr = dlPtr, dlPtr = dlPtr->nextPtr) {
if (dlPtr->chunkPtr == NULL) continue;
- if (dlPtr->oldY != dlPtr->y) {
+ if ((dlPtr->flags & OLD_Y_INVALID) || dlPtr->oldY != dlPtr->y) {
if (tkTextDebug) {
char string[TK_POS_CHARS];
TkTextPrintIndex(&dlPtr->index, string);
@@ -2469,9 +3461,53 @@ DisplayText(clientData)
return;
}
dlPtr->oldY = dlPtr->y;
- dlPtr->flags &= ~NEW_LAYOUT;
+ dlPtr->flags &= ~(NEW_LAYOUT | OLD_Y_INVALID);
+ } else if (dlPtr->chunkPtr != NULL && ((dlPtr->y < 0)
+ || (dlPtr->y + dlPtr->height > dInfoPtr->maxY))) {
+ register TkTextDispChunk *chunkPtr;
+
+ /*
+ * It's the first or last DLine which are also
+ * overlapping the top or bottom of the window, but we
+ * decided above it wasn't necessary to display them (we
+ * were able to update them by scrolling). This is fine,
+ * except that if the lines contain any embedded windows,
+ * we must still call the display proc on them because
+ * they might need to be unmapped or they might need to
+ * be moved to reflect their new position. Otherwise,
+ * everything else moves, but the embedded window
+ * doesn't!
+ *
+ * So, we loop through all the chunks, calling the
+ * display proc of embedded windows only.
+ */
+ for (chunkPtr = dlPtr->chunkPtr; (chunkPtr != NULL);
+ chunkPtr = chunkPtr->nextPtr) {
+ int x;
+ if (chunkPtr->displayProc != TkTextEmbWinDisplayProc) {
+ continue;
+ }
+ x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curXPixelOffset;
+ 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).
+ */
+ x = -chunkPtr->width;
+ }
+ TkTextEmbWinDisplayProc(chunkPtr, x, dlPtr->spaceAbove,
+ dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
+ dlPtr->baseline - dlPtr->spaceAbove, (Display *) NULL,
+ (Drawable) None, dlPtr->y + dlPtr->spaceAbove);
+ }
+
}
- /*prevPtr = dlPtr;*/
}
Tk_FreePixmap(Tk_Display(textPtr->tkwin), pixmap);
}
@@ -2658,9 +3694,11 @@ TextInvalidateRegion(textPtr, region)
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 ((!(dlPtr->flags & OLD_Y_INVALID))
+ && (TkRectInRegion(region, rect.x, dlPtr->y,
+ rect.width, (unsigned int) dlPtr->height)
+ != RectangleOut)) {
+ dlPtr->flags |= OLD_Y_INVALID;
}
}
if (dInfoPtr->topOfEof < maxY) {
@@ -2693,6 +3731,10 @@ TextInvalidateRegion(textPtr, region)
* changed). This procedure must be called *before* a change is
* made, so that indexes in the display information are still
* valid.
+ *
+ * Note: if the range of indices may change geometry as well
+ * as simply requiring redisplay, then the caller should also
+ * call TkTextInvalidateLineMetrics.
*
* Results:
* None.
@@ -2707,10 +3749,11 @@ TextInvalidateRegion(textPtr, region)
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. */
+ TkText *textPtr; /* Widget record for text widget. */
+ CONST TkTextIndex *index1Ptr; /* Index of first character to
+ * redisplay. */
+ CONST TkTextIndex *index2Ptr; /* Index of character just after last one
+ * to redisplay. */
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
DLine *firstPtr, *lastPtr;
@@ -2763,7 +3806,7 @@ TkTextChanged(textPtr, index1Ptr, index2Ptr)
* Delete all the DLines from firstPtr up to but not including lastPtr.
*/
- FreeDLines(textPtr, firstPtr, lastPtr, 1);
+ FreeDLines(textPtr, firstPtr, lastPtr, DLINE_UNLINK);
}
/*
@@ -2806,6 +3849,31 @@ TkTextRedrawTag(textPtr, index1Ptr, index2Ptr, tagPtr, withTag)
TkTextIndex *curIndexPtr;
TkTextIndex endOfText, *endIndexPtr;
+ /*
+ * Invalidate the pixel calculation of all lines in the given range.
+ * This may be a bit over-aggressive, so we could consider more
+ * subtle techniques here in the future. In particular, when we
+ * create a tag for the first time with '.t tag configure foo -font
+ * "Arial 20"', say, even though that obviously can't apply to
+ * anything at all (the tag didn't exist a moment ago), we invalidate
+ * every single line in the widget.
+ */
+ if (tagPtr->affectsDisplayGeometry) {
+ TkTextLine *startLine, *endLine;
+ if (index1Ptr == NULL) {
+ startLine = NULL;
+ } else {
+ startLine = index1Ptr->linePtr;
+ }
+ if (index2Ptr == NULL) {
+ endLine = NULL;
+ } else {
+ endLine = index2Ptr->linePtr;
+ }
+ TkTextInvalidateLineMetrics(textPtr, startLine, endLine - startLine,
+ TK_TEXT_INVALIDATE_ONLY);
+ }
+
/*
* 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).
@@ -2912,7 +3980,7 @@ TkTextRedrawTag(textPtr, index1Ptr, index2Ptr, tagPtr, withTag)
* be re-layed out and redrawn.
*/
- FreeDLines(textPtr, dlPtr, endPtr, 1);
+ FreeDLines(textPtr, dlPtr, endPtr, DLINE_UNLINK);
dlPtr = endPtr;
/*
@@ -2947,8 +4015,10 @@ TkTextRedrawTag(textPtr, index1Ptr, index2Ptr, tagPtr, withTag)
*/
void
-TkTextRelayoutWindow(textPtr)
+TkTextRelayoutWindow(textPtr, mask)
TkText *textPtr; /* Widget record for text widget. */
+ int mask; /* OR'd collection of bits showing what
+ * has changed */
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
GC new;
@@ -2981,7 +4051,7 @@ TkTextRelayoutWindow(textPtr)
* Throw away all the current layout information.
*/
- FreeDLines(textPtr, dInfoPtr->dLinePtr, (DLine *) NULL, 1);
+ FreeDLines(textPtr, dInfoPtr->dLinePtr, (DLine *) NULL, DLINE_UNLINK);
dInfoPtr->dLinePtr = NULL;
/*
@@ -3016,7 +4086,7 @@ TkTextRelayoutWindow(textPtr)
*/
if (textPtr->topIndex.byteIndex != 0) {
- MeasureUp(textPtr, &textPtr->topIndex, 0, &textPtr->topIndex);
+ TkTextFindDisplayLineEnd(textPtr, &textPtr->topIndex, 0, NULL);
}
/*
@@ -3026,6 +4096,25 @@ TkTextRelayoutWindow(textPtr)
dInfoPtr->xScrollFirst = dInfoPtr->xScrollLast = -1;
dInfoPtr->yScrollFirst = dInfoPtr->yScrollLast = -1;
+
+ if (mask & TK_TEXT_LINE_GEOMETRY) {
+ /*
+ * Set up line metric recalculation.
+ *
+ * Avoid the special zero value, since that is used to
+ * mark individual lines as being out of date.
+ */
+ if ((++dInfoPtr->lineMetricUpdateEpoch) == 0) {
+ dInfoPtr->lineMetricUpdateEpoch++;
+ }
+ dInfoPtr->currentMetricUpdateLine = -1;
+
+ if (dInfoPtr->lineUpdateTimer == NULL) {
+ textPtr->refCount++;
+ dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1,
+ TkTextAsyncUpdateLineMetrics, (ClientData) textPtr);
+ }
+ }
}
/*
@@ -3052,17 +4141,27 @@ 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. */
+ int pickPlace; /* 0 means the given index must appear
+ * exactly at the top of the screen.
+ * TK_TEXT_PICKPLACE (-1) means we get to
+ * pick where it appears: minimize screen
+ * motion or else display line at center
+ * of screen. TK_TEXT_NOPIXELADJUST (-2)
+ * indicates to make the given index the
+ * top line, but if it is already the top
+ * line, don't nudge it up or down by a
+ * few pixels just to make sure it is
+ * entirely displayed. Positive numbers
+ * indicate the number of pixels of the
+ * index's line which are to be off the
+ * top of the screen. */
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
register DLine *dlPtr;
int bottomY, close, lineIndex;
TkTextIndex tmpIndex, rounded;
- Tk_FontMetrics fm;
-
+ int lineHeight;
+
/*
* If the specified position is the extra line at the end of the
* text, round it back to the last real line.
@@ -3070,11 +4169,20 @@ TkTextSetYView(textPtr, indexPtr, pickPlace)
lineIndex = TkBTreeLineIndex(indexPtr->linePtr);
if (lineIndex == TkBTreeNumLines(indexPtr->tree)) {
- TkTextIndexBackChars(indexPtr, 1, &rounded);
+ TkTextIndexBackChars(indexPtr, 1, &rounded, COUNT_INDICES);
indexPtr = &rounded;
}
- if (!pickPlace) {
+ if (pickPlace == -2) {
+ if (textPtr->topIndex.linePtr == indexPtr->linePtr
+ && textPtr->topIndex.byteIndex == indexPtr->byteIndex) {
+ pickPlace = dInfoPtr->topPixelOffset;
+ } else {
+ pickPlace = 0;
+ }
+ }
+
+ if (pickPlace != -1) {
/*
* The specified position must go at the top of the screen.
* Just leave all the DLine's alone: we may be able to reuse
@@ -3082,11 +4190,11 @@ TkTextSetYView(textPtr, indexPtr, pickPlace)
* without redisplaying it all.
*/
- if (indexPtr->byteIndex == 0) {
- textPtr->topIndex = *indexPtr;
- } else {
- MeasureUp(textPtr, indexPtr, 0, &textPtr->topIndex);
+ textPtr->topIndex = *indexPtr;
+ if (indexPtr->byteIndex != 0) {
+ TkTextFindDisplayLineEnd(textPtr, &textPtr->topIndex, 0, NULL);
}
+ dInfoPtr->newTopPixelOffset = pickPlace;
goto scheduleUpdate;
}
@@ -3111,6 +4219,16 @@ TkTextSetYView(textPtr, indexPtr, pickPlace)
dlPtr = NULL;
} else if ((dlPtr->index.linePtr == indexPtr->linePtr)
&& (dlPtr->index.byteIndex <= indexPtr->byteIndex)) {
+ if (dInfoPtr->dLinePtr == dlPtr
+ && dInfoPtr->topPixelOffset != 0) {
+ /*
+ * It is on the top line, but that line is hanging
+ * off the top of the screen. Change the top
+ * overlap to zero and update.
+ */
+ dInfoPtr->newTopPixelOffset = 0;
+ goto scheduleUpdate;
+ }
return;
}
}
@@ -3119,37 +4237,50 @@ TkTextSetYView(textPtr, indexPtr, pickPlace)
* 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.
+ * lines, whichever is greater.
+ *
+ * If the line is not close, place it in the center of the
+ * window.
*/
- Tk_GetFontMetrics(textPtr->tkfont, &fm);
- bottomY = (dInfoPtr->y + dInfoPtr->maxY + fm.linespace)/2;
+ lineHeight = TextCalculateDisplayLineHeight(textPtr, indexPtr, NULL);
+ /*
+ * It would be better if 'bottomY' were calculated using the
+ * actual height of the given line, not 'textPtr->charHeight'.
+ */
+ bottomY = (dInfoPtr->y + dInfoPtr->maxY + lineHeight)/2;
close = (dInfoPtr->maxY - dInfoPtr->y)/3;
- if (close < 3*fm.linespace) {
- close = 3*fm.linespace;
+ if (close < 3*textPtr->charHeight) {
+ close = 3*textPtr->charHeight;
}
- close += fm.linespace;
if (dlPtr != NULL) {
+ int overlap;
/*
* 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.
+ * line on the screen. MeasureUp counts from the bottom
+ * of the given index upwards, so we add an extra half line
+ * to be sure we count far enough.
*/
- MeasureUp(textPtr, &textPtr->topIndex, close, &tmpIndex);
+ MeasureUp(textPtr, &textPtr->topIndex, close + textPtr->charHeight/2,
+ &tmpIndex, &overlap);
if (TkTextIndexCmp(&tmpIndex, indexPtr) <= 0) {
- MeasureUp(textPtr, indexPtr, 0, &textPtr->topIndex);
+ textPtr->topIndex = *indexPtr;
+ TkTextFindDisplayLineEnd(textPtr, &textPtr->topIndex, 0, NULL);
+ dInfoPtr->newTopPixelOffset = 0;
goto scheduleUpdate;
}
} else {
+ int overlap;
/*
* 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);
+ MeasureUp(textPtr, indexPtr, close + lineHeight
+ - textPtr->charHeight/2, &tmpIndex, &overlap);
if (FindDLine(dInfoPtr->dLinePtr, &tmpIndex) != NULL) {
bottomY = dInfoPtr->maxY - dInfoPtr->y;
}
@@ -3163,7 +4294,8 @@ TkTextSetYView(textPtr, indexPtr, pickPlace)
* is a half-line lower than the center of the window.
*/
- MeasureUp(textPtr, indexPtr, bottomY, &textPtr->topIndex);
+ MeasureUp(textPtr, indexPtr, bottomY, &textPtr->topIndex,
+ &dInfoPtr->newTopPixelOffset);
scheduleUpdate:
if (!(dInfoPtr->flags & REDRAW_PENDING)) {
@@ -3175,11 +4307,72 @@ TkTextSetYView(textPtr, indexPtr, pickPlace)
/*
*--------------------------------------------------------------
*
- * MeasureUp --
+ * TkTextMeasureDown --
*
* 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.
+ * than "distance" pixels below the top of the given index.
+ *
+ * Results:
+ * The srcPtr is manipulated in place to reflect the new
+ * position. We return the number of pixels by which 'distance'
+ * overlaps the srcPtr.
+ *
+ * Side effects:
+ * None.
+ *
+ *--------------------------------------------------------------
+ */
+
+int
+TkTextMeasureDown(textPtr, srcPtr, distance)
+ 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 top pixel in srcPtr's
+ logical line. */
+{
+ TkTextLine *lastLinePtr;
+ DLine *dlPtr;
+ TkTextIndex loop;
+
+ lastLinePtr = TkBTreeFindLine(textPtr->tree,
+ TkBTreeNumLines(textPtr->tree));
+
+ do {
+ dlPtr = LayoutDLine(textPtr, srcPtr);
+ dlPtr->nextPtr = NULL;
+
+ if (distance < dlPtr->height) {
+ FreeDLines(textPtr, dlPtr, (DLine *) NULL, DLINE_FREE_TEMP);
+ break;
+ }
+ distance -= dlPtr->height;
+ TkTextIndexForwBytes(srcPtr, dlPtr->byteCount, &loop);
+ FreeDLines(textPtr, dlPtr, (DLine *) NULL, DLINE_FREE_TEMP);
+ if (loop.linePtr == lastLinePtr) {
+ break;
+ }
+ *srcPtr = loop;
+ } while (distance > 0);
+
+ return distance;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * 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.
+ *
+ * If this function is called with distance=0, it simply finds the
+ * first index on the same display line as srcPtr. However, there
+ * is a another function TkTextFindDisplayLineEnd designed just for
+ * that task which is probably better to use.
*
* Results:
* *dstPtr is filled in with the index of the first character
@@ -3187,9 +4380,8 @@ TkTextSetYView(textPtr, indexPtr, pickPlace)
* 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).
+ * coordinate, then return any excess pixels in *overlap, if
+ * that is non-NULL.
*
* Side effects:
* None.
@@ -3198,30 +4390,31 @@ TkTextSetYView(textPtr, indexPtr, pickPlace)
*/
static void
-MeasureUp(textPtr, srcPtr, distance, dstPtr)
+MeasureUp(textPtr, srcPtr, distance, dstPtr, overlap)
TkText *textPtr; /* Text widget in which to measure. */
- TkTextIndex *srcPtr; /* Index of character from which to start
+ CONST 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 *overlap; /* Used to store how much of the final
+ * index returned was not covered by
+ * 'distance'. */
{
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
@@ -3243,17 +4436,20 @@ MeasureUp(textPtr, srcPtr, distance, dstPtr)
/*
* 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.
+ * line at the desired location. If distance was zero to start
+ * with then we simply get the first index on the same display
+ * line as the original index.
*/
for (dlPtr = lowestPtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) {
distance -= dlPtr->height;
- if (distance < 0) {
- *dstPtr = (noBestYet) ? dlPtr->index : bestIndex;
+ if (distance <= 0) {
+ *dstPtr = dlPtr->index;
+ if (overlap != NULL) {
+ *overlap = -distance;
+ }
break;
}
- bestIndex = dlPtr->index;
- noBestYet = 0;
}
/*
@@ -3261,8 +4457,8 @@ MeasureUp(textPtr, srcPtr, distance, dstPtr)
* for the next display line to lay out.
*/
- FreeDLines(textPtr, lowestPtr, (DLine *) NULL, 0);
- if (distance < 0) {
+ FreeDLines(textPtr, lowestPtr, (DLine *) NULL, DLINE_FREE);
+ if (distance <= 0) {
return;
}
bytesToCount = INT_MAX; /* Consider all chars. in next line. */
@@ -3274,6 +4470,9 @@ MeasureUp(textPtr, srcPtr, distance, dstPtr)
*/
TkTextMakeByteIndex(textPtr->tree, 0, 0, dstPtr);
+ if (overlap != NULL) {
+ *overlap = 0;
+ }
}
/*
@@ -3310,8 +4509,7 @@ TkTextSeeCmd(textPtr, interp, objc, objv)
TkTextDispChunk *chunkPtr;
if (objc != 3) {
- Tcl_AppendResult(interp, "wrong # args: should be \"",
- Tcl_GetString(objv[0]), " see index\"", (char *) NULL);
+ Tcl_WrongNumArgs(interp, 2, objv, "index");
return TCL_ERROR;
}
if (TkTextGetObjIndex(interp, textPtr, objv[2], &index) != TCL_OK) {
@@ -3324,14 +4522,14 @@ TkTextSeeCmd(textPtr, interp, objc, objv)
*/
if (TkBTreeLineIndex(index.linePtr) == TkBTreeNumLines(index.tree)) {
- TkTextIndexBackChars(&index, 1, &index);
+ TkTextIndexBackChars(&index, 1, &index, COUNT_INDICES);
}
/*
* First get the desired position into the vertical range of the window.
*/
- TkTextSetYView(textPtr, &index, 1);
+ TkTextSetYView(textPtr, &index, TK_TEXT_PICKPLACE);
/*
* Now make sure that the character is in view horizontally.
@@ -3376,24 +4574,24 @@ TkTextSeeCmd(textPtr, interp, objc, objv)
dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
dlPtr->baseline - dlPtr->spaceAbove, &x, &y, &width,
&height);
- delta = x - dInfoPtr->curPixelOffset;
+ delta = x - dInfoPtr->curXPixelOffset;
oneThird = lineWidth/3;
if (delta < 0) {
if (delta < -oneThird) {
- dInfoPtr->newByteOffset = (x - lineWidth/2)
+ dInfoPtr->newXByteOffset = (x - lineWidth/2)
/ textPtr->charWidth;
} else {
- dInfoPtr->newByteOffset -= ((-delta) + textPtr->charWidth - 1)
+ dInfoPtr->newXByteOffset -= ((-delta) + textPtr->charWidth - 1)
/ textPtr->charWidth;
}
} else {
delta -= (lineWidth - width);
if (delta > 0) {
if (delta > oneThird) {
- dInfoPtr->newByteOffset = (x - lineWidth/2)
+ dInfoPtr->newXByteOffset = (x - lineWidth/2)
/ textPtr->charWidth;
} else {
- dInfoPtr->newByteOffset += (delta + textPtr->charWidth - 1)
+ dInfoPtr->newXByteOffset += (delta + textPtr->charWidth - 1)
/ textPtr->charWidth;
}
} else {
@@ -3449,7 +4647,7 @@ TkTextXviewCmd(textPtr, interp, objc, objv)
return TCL_OK;
}
- newOffset = dInfoPtr->newByteOffset;
+ newOffset = dInfoPtr->newXByteOffset;
type = Tk_GetScrollInfoObj(interp, objc, objv, &fraction, &count);
switch (type) {
case TK_SCROLL_ERROR:
@@ -3461,8 +4659,8 @@ TkTextXviewCmd(textPtr, interp, objc, objv)
if (fraction < 0) {
fraction = 0;
}
- newOffset = (int) (((fraction * dInfoPtr->maxLength) / textPtr->charWidth)
- + 0.5);
+ newOffset = (int) (((fraction * dInfoPtr->maxLength)
+ / textPtr->charWidth) + 0.5);
break;
case TK_SCROLL_PAGES:
charsPerPage = ((dInfoPtr->maxX - dInfoPtr->x) / textPtr->charWidth)
@@ -3477,7 +4675,7 @@ TkTextXviewCmd(textPtr, interp, objc, objv)
break;
}
- dInfoPtr->newByteOffset = newOffset;
+ dInfoPtr->newXByteOffset = newOffset;
dInfoPtr->flags |= DINFO_OUT_OF_DATE;
if (!(dInfoPtr->flags & REDRAW_PENDING)) {
dInfoPtr->flags |= REDRAW_PENDING;
@@ -3489,7 +4687,87 @@ TkTextXviewCmd(textPtr, interp, objc, objv)
/*
*----------------------------------------------------------------------
*
- * ScrollByLines --
+ * YScrollByPixels --
+ *
+ * This procedure is called to scroll a text widget up or down
+ * by a given number of pixels.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The view in textPtr's window changes to reflect the value
+ * of "offset".
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+YScrollByPixels(textPtr, offset)
+ TkText *textPtr; /* Widget to scroll. */
+ int offset; /* Amount by which to scroll, in
+ * pixels. Positive means that information
+ * later in text becomes visible, negative
+ * means that information earlier in the
+ * text becomes visible. */
+{
+ TextDInfo *dInfoPtr = textPtr->dInfoPtr;
+
+ if (offset < 0) {
+ /*
+ * Now we want to measure up this number of pixels
+ * from the top of the screen. But the top line may
+ * not be totally visible. Note that 'count' is
+ * negative here.
+ */
+ offset -= TextCalculateDisplayLineHeight(textPtr,
+ &textPtr->topIndex, NULL) - dInfoPtr->topPixelOffset;
+ MeasureUp(textPtr, &textPtr->topIndex, -offset,
+ &textPtr->topIndex, &dInfoPtr->newTopPixelOffset);
+ } else if (offset > 0) {
+ DLine *dlPtr;
+ TkTextLine *lastLinePtr;
+ TkTextIndex new;
+ /*
+ * Scrolling down by pixels. Layout lines starting at
+ * the top index and count through the desired vertical
+ * distance.
+ */
+
+ lastLinePtr = TkBTreeFindLine(textPtr->tree,
+ TkBTreeNumLines(textPtr->tree));
+ offset += dInfoPtr->topPixelOffset;
+ dInfoPtr->newTopPixelOffset = 0;
+ while (offset > 0) {
+ dlPtr = LayoutDLine(textPtr, &textPtr->topIndex);
+ dlPtr->nextPtr = NULL;
+ TkTextIndexForwBytes(&textPtr->topIndex, dlPtr->byteCount,
+ &new);
+ if (offset <= dlPtr->height) {
+ /* Adjust the top overlap accordingly */
+ dInfoPtr->newTopPixelOffset = offset;
+ }
+ offset -= dlPtr->height;
+ FreeDLines(textPtr, dlPtr, (DLine *) NULL, DLINE_FREE_TEMP);
+ if (new.linePtr == lastLinePtr || offset <= 0) {
+ break;
+ }
+ textPtr->topIndex = new;
+ }
+ } else {
+ /* offset = 0, so no scrolling required */
+ return;
+ }
+ if (!(dInfoPtr->flags & REDRAW_PENDING)) {
+ Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);
+ }
+ dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * YScrollByLines --
*
* This procedure is called to scroll a text widget up or down
* by a given number of lines.
@@ -3505,9 +4783,9 @@ TkTextXviewCmd(textPtr, interp, objc, objv)
*/
static void
-ScrollByLines(textPtr, offset)
+YScrollByLines(textPtr, offset)
TkText *textPtr; /* Widget to scroll. */
- int offset; /* Amount by which to scroll, in *screen*
+ int offset; /* Amount by which to scroll, in display
* lines. Positive means that information
* later in text becomes visible, negative
* means that information earlier in the
@@ -3556,7 +4834,7 @@ ScrollByLines(textPtr, offset)
* for the next display line to lay out.
*/
- FreeDLines(textPtr, lowestPtr, (DLine *) NULL, 0);
+ FreeDLines(textPtr, lowestPtr, (DLine *) NULL, DLINE_FREE);
if (offset >= 0) {
goto scheduleUpdate;
}
@@ -3565,10 +4843,12 @@ ScrollByLines(textPtr, offset)
/*
* Ran off the beginning of the text. Return the first character
- * in the text.
+ * in the text, and make sure we haven't left anything
+ * overlapping the top window border.
*/
TkTextMakeByteIndex(textPtr->tree, 0, 0, &textPtr->topIndex);
+ dInfoPtr->newTopPixelOffset = 0;
} else {
/*
* Scrolling down, to show later information in the text.
@@ -3582,7 +4862,7 @@ ScrollByLines(textPtr, offset)
if (dlPtr->length == 0 && dlPtr->height == 0) offset++;
dlPtr->nextPtr = NULL;
TkTextIndexForwBytes(&textPtr->topIndex, dlPtr->byteCount, &new);
- FreeDLines(textPtr, dlPtr, (DLine *) NULL, 0);
+ FreeDLines(textPtr, dlPtr, (DLine *) NULL, DLINE_FREE);
if (new.linePtr == lastLinePtr) {
break;
}
@@ -3625,14 +4905,11 @@ TkTextYviewCmd(textPtr, interp, objc, objv)
* objv[1] is "yview". */
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
- int pickPlace, lineNum, type, bytesInLine;
- Tk_FontMetrics fm;
+ int pickPlace, type;
int pixels, count;
size_t switchLength;
double fraction;
- TkTextIndex index, new;
- TkTextLine *lastLinePtr;
- DLine *dlPtr;
+ TkTextIndex index;
if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
UpdateDisplayInfo(textPtr);
@@ -3651,18 +4928,17 @@ TkTextYviewCmd(textPtr, interp, objc, objv)
if (Tcl_GetString(objv[2])[0] == '-') {
switchLength = strlen(Tcl_GetString(objv[2]));
if ((switchLength >= 2)
- && (strncmp(Tcl_GetString(objv[2]), "-pickplace", switchLength) == 0)) {
+ && (strncmp(Tcl_GetString(objv[2]),
+ "-pickplace", switchLength) == 0)) {
pickPlace = 1;
if (objc != 4) {
- Tcl_AppendResult(interp, "wrong # args: should be \"",
- Tcl_GetString(objv[0]),
- " yview -pickplace lineNum|index\"",
- (char *) NULL);
+ Tcl_WrongNumArgs(interp, 3, objv, "lineNum|index");
return TCL_ERROR;
}
}
}
if ((objc == 3) || pickPlace) {
+ int lineNum;
if (Tcl_GetIntFromObj(interp, objv[2+pickPlace], &lineNum) == TCL_OK) {
TkTextMakeByteIndex(textPtr->tree, lineNum, 0, &index);
TkTextSetYView(textPtr, &index, 0);
@@ -3678,7 +4954,7 @@ TkTextYviewCmd(textPtr, interp, objc, objv)
&index) != TCL_OK) {
return TCL_ERROR;
}
- TkTextSetYView(textPtr, &index, pickPlace);
+ TkTextSetYView(textPtr, &index, (pickPlace ? TK_TEXT_PICKPLACE : 0));
return TCL_OK;
}
@@ -3686,79 +4962,67 @@ TkTextYviewCmd(textPtr, interp, objc, objv)
* New syntax: dispatch based on objv[2].
*/
- type = Tk_GetScrollInfoObj(interp, objc, objv, &fraction, &count);
+ type = TextGetScrollInfoObj(interp, textPtr, objc, objv,
+ &fraction, &count);
switch (type) {
- case TK_SCROLL_ERROR:
+ case TKTEXT_SCROLL_ERROR:
return TCL_ERROR;
- case TK_SCROLL_MOVETO:
+ case TKTEXT_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);
+ fraction *= (TkBTreeNumPixels(textPtr->tree)-1);
+ /*
+ * This function returns the number of pixels by which the
+ * given line should overlap the top of the visible screen.
+ *
+ * This is then used to provide smooth scrolling.
+ */
+ pixels = TkTextMakePixelIndex(textPtr, (int) (0.5 + fraction),
+ &index);
+ TkTextSetYView(textPtr, &index, pixels);
break;
- case TK_SCROLL_PAGES:
+ case TKTEXT_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.
+ int height = dInfoPtr->maxY - dInfoPtr->y;
+ if (textPtr->charHeight * 4 >= height) {
+ /*
+ * A single line is more than a quarter of the
+ * display. We choose to scroll by 3/4 of the
+ * height instead.
+ */
+ pixels = 3*height/4;
+ if (pixels < textPtr->charHeight) {
+ /*
+ * But, if 3/4 of the height is actually less than a
+ * single typical character height, then scroll by
+ * the minimum of the linespace or the total height.
*/
-
- count = -1;
- goto scrollByLines;
+ if (textPtr->charHeight < height) {
+ pixels = textPtr->charHeight;
+ } else {
+ pixels = height;
+ }
}
- textPtr->topIndex = new;
+ pixels *= count;
} 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);
+ pixels = (height - 2*textPtr->charHeight)*count;
}
- if (!(dInfoPtr->flags & REDRAW_PENDING)) {
- Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);
- }
- dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;
+ YScrollByPixels(textPtr, pixels);
break;
- case TK_SCROLL_UNITS:
- scrollByLines:
- ScrollByLines(textPtr, count);
+ }
+ case TKTEXT_SCROLL_PIXELS:
+ YScrollByPixels(textPtr, count);
+ break;
+ case TKTEXT_SCROLL_UNITS:
+ YScrollByLines(textPtr, count);
break;
}
return TCL_OK;
@@ -3794,13 +5058,13 @@ TkTextScanCmd(textPtr, interp, objc, objv)
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
TkTextIndex index;
int c, x, y, totalScroll, newByte, maxByte, gain=10;
- Tk_FontMetrics fm;
size_t length;
if ((objc != 5) && (objc != 6)) {
Tcl_AppendResult(interp, "wrong # args: should be \"",
Tcl_GetString(objv[0]), " scan mark x y\" or \"",
- Tcl_GetString(objv[0]), " scan dragto x y ?gain?\"", (char *) NULL);
+ Tcl_GetString(objv[0]), " scan dragto x y ?gain?\"",
+ (char *) NULL);
return TCL_ERROR;
}
if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK) {
@@ -3809,8 +5073,9 @@ TkTextScanCmd(textPtr, interp, objc, objv)
if (Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
return TCL_ERROR;
}
- if ((objc == 6) && (Tcl_GetIntFromObj(interp, objv[5], &gain) != TCL_OK))
- return TCL_ERROR;
+ if ((objc == 6) && (Tcl_GetIntFromObj(interp, objv[5], &gain) != TCL_OK)) {
+ return TCL_ERROR;
+ }
c = Tcl_GetString(objv[2])[0];
length = strlen(Tcl_GetString(objv[2]));
if ((c == 'd') && (strncmp(Tcl_GetString(objv[2]), "dragto", length) == 0)) {
@@ -3839,24 +5104,24 @@ TkTextScanCmd(textPtr, interp, objc, objv)
dInfoPtr->scanMarkIndex = maxByte;
dInfoPtr->scanMarkX = x;
}
- dInfoPtr->newByteOffset = newByte;
+ dInfoPtr->newXByteOffset = newByte;
- Tk_GetFontMetrics(textPtr->tkfont, &fm);
- totalScroll = (gain*(dInfoPtr->scanMarkY - y)) / fm.linespace;
- if (totalScroll != dInfoPtr->scanTotalScroll) {
+ totalScroll = gain*(dInfoPtr->scanMarkY - y);
+ if (totalScroll != dInfoPtr->scanTotalYScroll) {
index = textPtr->topIndex;
- ScrollByLines(textPtr, totalScroll-dInfoPtr->scanTotalScroll);
- dInfoPtr->scanTotalScroll = totalScroll;
+ YScrollByPixels(textPtr, totalScroll-dInfoPtr->scanTotalYScroll);
+ dInfoPtr->scanTotalYScroll = totalScroll;
if ((index.linePtr == textPtr->topIndex.linePtr) &&
(index.byteIndex == textPtr->topIndex.byteIndex)) {
- dInfoPtr->scanTotalScroll = 0;
+ dInfoPtr->scanTotalYScroll = 0;
dInfoPtr->scanMarkY = y;
}
}
- } else if ((c == 'm') && (strncmp(Tcl_GetString(objv[2]), "mark", length) == 0)) {
- dInfoPtr->scanMarkIndex = dInfoPtr->newByteOffset;
+ } else if ((c == 'm')
+ && (strncmp(Tcl_GetString(objv[2]), "mark", length) == 0)) {
+ dInfoPtr->scanMarkIndex = dInfoPtr->newXByteOffset;
dInfoPtr->scanMarkX = x;
- dInfoPtr->scanTotalScroll = 0;
+ dInfoPtr->scanTotalYScroll = 0;
dInfoPtr->scanMarkY = y;
} else {
Tcl_AppendResult(interp, "bad scan option \"", Tcl_GetString(objv[2]),
@@ -3906,12 +5171,12 @@ GetXView(interp, textPtr, report)
* scrollbar if it has changed. */
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
- char buffer[TCL_DOUBLE_SPACE * 2 + 1];
double first, last;
int code;
-
+ Tcl_Obj *listObj;
+
if (dInfoPtr->maxLength > 0) {
- first = ((double) dInfoPtr->curPixelOffset)
+ first = ((double) dInfoPtr->curXPixelOffset)
/ dInfoPtr->maxLength;
last = first + ((double) (dInfoPtr->maxX - dInfoPtr->x))
/ dInfoPtr->maxLength;
@@ -3923,8 +5188,10 @@ GetXView(interp, textPtr, report)
last = 1.0;
}
if (!report) {
- sprintf(buffer, "%g %g", first, last);
- Tcl_SetResult(interp, buffer, TCL_VOLATILE);
+ listObj = Tcl_NewListObj(0,NULL);
+ Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(first));
+ Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(last));
+ Tcl_SetObjResult(interp, listObj);
return;
}
if (FP_EQUAL_SCALE(first, dInfoPtr->xScrollFirst, dInfoPtr->maxLength) &&
@@ -3933,19 +5200,67 @@ GetXView(interp, textPtr, report)
}
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,
+ if (textPtr->xScrollCmd != NULL) {
+ listObj = Tcl_NewStringObj(textPtr->xScrollCmd, -1);
+ code = Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(first));
+ if (code == TCL_OK) {
+ Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(last));
+ code = Tcl_EvalObjEx(interp, listObj,
+ TCL_EVAL_DIRECT | TCL_EVAL_GLOBAL);
+ }
+ if (code != TCL_OK) {
+ Tcl_AddErrorInfo(interp,
"\n (horizontal scrolling command executed by text)");
- Tcl_BackgroundError(interp);
+ Tcl_BackgroundError(interp);
+ }
}
}
/*
*----------------------------------------------------------------------
*
+ * GetPixelCount --
+ *
+ * How many pixels are there between the absolute top of the
+ * widget and the top of the given DLine.
+ *
+ * Results:
+ * The number of pixels.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+GetPixelCount(textPtr, dlPtr)
+ TkText *textPtr; /* Information about text widget. */
+ DLine *dlPtr; /* Information about the layout
+ * of a given index */
+{
+ TkTextLine *linePtr = dlPtr->index.linePtr;
+ /*
+ * Get the pixel count of one pixel beyond the
+ * botton of the given line.
+ */
+ int count = TkBTreePixels(linePtr) + linePtr->pixelHeight;
+
+ /*
+ * Now we have to subtract off the distance between the top of this
+ * dlPtr and the next logical line.
+ */
+ do {
+ count -= dlPtr->height;
+ dlPtr = dlPtr->nextPtr;
+ } while (dlPtr != NULL && (dlPtr->index.linePtr == linePtr));
+
+ return count;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
* GetYView --
*
* This procedure computes the fractions that indicate what's
@@ -3967,7 +5282,7 @@ GetXView(interp, textPtr, report)
*
*----------------------------------------------------------------------
*/
-
+
static void
GetYView(interp, textPtr, report)
Tcl_Interp *interp; /* If "report" is FALSE, string
@@ -3978,53 +5293,153 @@ GetYView(interp, textPtr, report)
* scrollbar if it has changed. */
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
- char buffer[TCL_DOUBLE_SPACE * 2 + 1];
double first, last;
DLine *dlPtr;
- int totalLines, code, count;
-
+ int totalPixels, code, count;
+ Tcl_Obj *listObj;
+
dlPtr = dInfoPtr->dLinePtr;
- totalLines = TkBTreeNumLines(textPtr->tree);
- first = (double) TkBTreeLineIndex(dlPtr->index.linePtr)
- + (double) dlPtr->index.byteIndex
- / TkBTreeBytesInLine(dlPtr->index.linePtr);
- first /= totalLines;
+
+ if (dlPtr == NULL) {
+ return;
+ }
+
+ totalPixels = TkBTreeNumPixels(textPtr->tree);
+
+ /*
+ * Get the pixel count for the first display visible pixel of the
+ * first visible line. If the first visible line is only partially
+ * visible, then we use 'topPixelOffset' to get the difference.
+ */
+ count = GetPixelCount(textPtr, dlPtr);
+ first = ((double) (count + dInfoPtr->topPixelOffset))/((double)totalPixels);
+
+ /*
+ * Add on the total number of visible pixels to get the count to
+ * the last visible pixel.
+ */
while (1) {
- if ((dlPtr->y + dlPtr->height) > dInfoPtr->maxY) {
+ int extra;
+ count += dlPtr->height;
+
+ extra = dlPtr->y + dlPtr->height - dInfoPtr->maxY;
+ if (extra > 0) {
/*
- * The last line is only partially visible, so don't
- * count its characters in what's visible.
+ * This much of the last line is not visible, so don't
+ * count these pixels. Since we've reached the bottom
+ * of the window, we break out of the loop.
*/
- count = 0;
+ count -= extra;
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 (count > totalPixels) {
+ /*
+ * It can be possible, if we do not update each line's
+ * pixelHeight cache when we lay out individual DLines that
+ * the count generated here is more up-to-date than that
+ * maintained by the BTree. In such a case, the best we can
+ * do here is to fix up 'count' and continue, which might
+ * result in small, temporary perturbations to the size of
+ * the scrollbar. This is basically harmless, but in a
+ * perfect world we would not have this problem.
+ *
+ * For debugging purposes, if anyone wishes to improve the text
+ * widget further, the following 'panic' can be activated. In
+ * principle it should be possible to ensure the BTree is always
+ * at least as up to date as the display, so in the future we
+ * might be able to leave the 'panic' in permanently when we
+ * believe we have resolved the cache synchronisation issue.
+ *
+ * However, to achieve that goal would, I think, require a
+ * fairly substantial refactorisation of the code in this
+ * file so that there is much more obvious and explicit
+ * coordination between calls to LayoutDLine and updating
+ * of each TkTextLine's pixelHeight. The complicated bit
+ * is that LayoutDLine deals with individual display lines,
+ * but pixelHeight is for a logical line.
+ */
+#if 0
+ char buffer[200];
+
+ sprintf(buffer,
+ "Counted more pixels (%d) than expected (%d) total pixels in text widget scroll bar calculation.",
+ count, totalPixels);
+ panic(buffer);
+#endif
+ count = totalPixels;
+ }
+
+ last = ((double) (count))/((double)totalPixels);
+
if (!report) {
- sprintf(buffer, "%g %g", first, last);
- Tcl_SetResult(interp, buffer, TCL_VOLATILE);
+ listObj = Tcl_NewListObj(0,NULL);
+ Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(first));
+ Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(last));
+ Tcl_SetObjResult(interp, listObj);
return;
}
- if (FP_EQUAL_SCALE(first, dInfoPtr->yScrollFirst, totalLines) &&
- FP_EQUAL_SCALE(last, dInfoPtr->yScrollLast, totalLines)) {
+ if (FP_EQUAL_SCALE(first, dInfoPtr->yScrollFirst, totalPixels) &&
+ FP_EQUAL_SCALE(last, dInfoPtr->yScrollLast, totalPixels)) {
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);
+ if (textPtr->yScrollCmd != NULL) {
+ listObj = Tcl_NewStringObj(textPtr->yScrollCmd, -1);
+ code = Tcl_ListObjAppendElement(interp, listObj,
+ Tcl_NewDoubleObj(first));
+ if (code == TCL_OK) {
+ Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(last));
+ code = Tcl_EvalObjEx(interp, listObj,
+ TCL_EVAL_DIRECT | TCL_EVAL_GLOBAL);
+ }
+ if (code != TCL_OK) {
+ Tcl_AddErrorInfo(interp,
+ "\n (vertical scrolling command executed by text)");
+ Tcl_BackgroundError(interp);
+ }
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextUpdateYScrollbar --
+ *
+ * This procedure is called to update the vertical scrollbar
+ * asychronously as the pixel height calculations progress for
+ * lines in the widget.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * See 'GetYView'. In particular the scrollbar position and size
+ * may be changed.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkTextUpdateYScrollbar(clientData)
+ ClientData clientData; /* Information about widget. */
+{
+ register TkText *textPtr = (TkText *) clientData;
+
+ textPtr->dInfoPtr->scrollbarTimer = NULL;
+
+ if (!(textPtr->flags & DESTROYED)) {
+ GetYView(textPtr->interp, textPtr, 1);
+ }
+
+ if (--textPtr->refCount == 0) {
+ ckfree((char *) textPtr);
}
}
@@ -4128,8 +5543,7 @@ TkTextPixelIndex(textPtr, x, y, indexPtr)
* index of the character nearest to (x,y). */
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
- register DLine *dlPtr, *validdlPtr;
- register TkTextDispChunk *chunkPtr;
+ register DLine *dlPtr, *validDlPtr;
/*
* Make sure that all of the layout information about what's
@@ -4161,9 +5575,10 @@ TkTextPixelIndex(textPtr, x, y, indexPtr)
* 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;
+ 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.
@@ -4174,8 +5589,43 @@ TkTextPixelIndex(textPtr, x, y, indexPtr)
break;
}
}
- if (dlPtr->chunkPtr == NULL) dlPtr = validdlPtr;
+ if (dlPtr->chunkPtr == NULL) dlPtr = validDlPtr;
+
+ DlineIndexOfX(textPtr, dlPtr, x, indexPtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * DlineIndexOfX --
+ *
+ * Given an x coordinate in a display line, find the index of
+ * the character closest to that location.
+ *
+ * This is effectively the opposite of DlineXOfIndex.
+ *
+ * Results:
+ * The index at *indexPtr is modified to refer to the character
+ * on the display line that is closest to x.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+static void
+DlineIndexOfX(textPtr, dlPtr, x, indexPtr)
+ TkText *textPtr; /* Widget record for text widget. */
+ DLine *dlPtr; /* Display information for this
+ * display line. */
+ int x; /* Pixel x coordinate of point in widget's
+ * window. */
+ TkTextIndex *indexPtr; /* This index gets filled in with the
+ * index of the character nearest to x. */
+{
+ TextDInfo *dInfoPtr = textPtr->dInfoPtr;
+ register TkTextDispChunk *chunkPtr;
/*
* Scan through the line's chunks to find the one that contains
@@ -4185,13 +5635,13 @@ TkTextPixelIndex(textPtr, x, y, indexPtr)
*/
*indexPtr = dlPtr->index;
- x = x - dInfoPtr->x + dInfoPtr->curPixelOffset;
+ x = x - dInfoPtr->x + dInfoPtr->curXPixelOffset;
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);
+ TkTextIndexBackChars(indexPtr, 1, indexPtr, COUNT_INDICES);
return;
}
}
@@ -4209,6 +5659,110 @@ TkTextPixelIndex(textPtr, x, y, indexPtr)
/*
*----------------------------------------------------------------------
*
+ * TkTextIndexOfX --
+ *
+ * Given a logical x coordinate (i.e. distance in pixels from the
+ * beginning of the display line, not taking into account any
+ * information about the window, scrolling etc.) on the display
+ * line starting with the given index, adjust that index to refer to
+ * the object under the x coordinate.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkTextIndexOfX(textPtr, x, indexPtr)
+ TkText *textPtr; /* Widget record for text widget. */
+ int x; /* The x coordinate for which we want
+ * the index */
+ TkTextIndex *indexPtr; /* Index of display line start, which
+ * will be adjusted to the index under the
+ * given x coordinate. */
+{
+ DLine *dlPtr = LayoutDLine(textPtr, indexPtr);
+ DlineIndexOfX(textPtr, dlPtr, x + textPtr->dInfoPtr->x
+ - textPtr->dInfoPtr->curXPixelOffset, indexPtr);
+ FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * DlineXOfIndex --
+ *
+ * Given a relative byte index on a given display line (i.e. the
+ * number of byte indices from the beginning of the given display
+ * line), find the x coordinate of that index within the abstract
+ * display line, without adjusting for the x-scroll state of the
+ * line.
+ *
+ * This is effectively the opposite of DlineIndexOfX.
+ *
+ * NB. The 'byteIndex' is relative to the display line, NOT the
+ * logical line.
+ *
+ * Results:
+ * The x coordinate.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+DlineXOfIndex(textPtr, dlPtr, byteIndex)
+ TkText *textPtr; /* Widget record for text widget. */
+ DLine *dlPtr; /* Display information for this
+ * display line. */
+ int byteIndex; /* The byte index for which we want the
+ * coordinate. */
+{
+ TextDInfo *dInfoPtr = textPtr->dInfoPtr;
+ register TkTextDispChunk *chunkPtr;
+ TkTextIndex index;
+ int x;
+
+ if (byteIndex == 0) return 0;
+
+ /*
+ * Scan through the line's chunks to find the one that contains
+ * the desired byte index.
+ */
+
+ index = dlPtr->index;
+ chunkPtr = dlPtr->chunkPtr;
+ while (byteIndex > 0) {
+ if (byteIndex < chunkPtr->numBytes) {
+ int y, width, height;
+ (*chunkPtr->bboxProc)(chunkPtr, byteIndex,
+ dlPtr->y + dlPtr->spaceAbove,
+ dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
+ dlPtr->baseline - dlPtr->spaceAbove, &x, &y, &width,
+ &height);
+ break;
+ } else {
+ byteIndex -= chunkPtr->numBytes;
+ }
+ if (chunkPtr->nextPtr == NULL || byteIndex == 0) {
+ x = chunkPtr->x + chunkPtr->width;
+ break;
+ }
+ chunkPtr = chunkPtr->nextPtr;
+ }
+
+ return x;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
* TkTextCharBbox --
*
* Given an index, find the bounding box of the screen area
@@ -4228,13 +5782,18 @@ TkTextPixelIndex(textPtr, x, y, indexPtr)
*/
int
-TkTextCharBbox(textPtr, indexPtr, xPtr, yPtr, widthPtr, heightPtr)
+TkTextCharBbox(textPtr, indexPtr, xPtr, yPtr, widthPtr, heightPtr, charWidthPtr)
TkText *textPtr; /* Widget record for text widget. */
CONST 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. */
+ int *charWidthPtr; /* If the 'character' isn't really a
+ * character (e.g. end of a line) and
+ * therefore takes up a very large
+ * width, this is used to return a
+ * smaller width */
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
DLine *dlPtr;
@@ -4286,17 +5845,24 @@ TkTextCharBbox(textPtr, indexPtr, xPtr, yPtr, widthPtr, heightPtr)
dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
dlPtr->baseline - dlPtr->spaceAbove, xPtr, yPtr, widthPtr,
heightPtr);
- *xPtr = *xPtr + dInfoPtr->x - dInfoPtr->curPixelOffset;
+ *xPtr = *xPtr + dInfoPtr->x - dInfoPtr->curXPixelOffset;
if ((byteIndex == (chunkPtr->numBytes - 1)) && (chunkPtr->nextPtr == NULL)) {
/*
* Last character in display line. Give it all the space up to
* the line.
*/
+ if (charWidthPtr != NULL) {
+ *charWidthPtr = dInfoPtr->maxX - *xPtr;
+ }
if (*xPtr > dInfoPtr->maxX) {
*xPtr = dInfoPtr->maxX;
}
*widthPtr = dInfoPtr->maxX - *xPtr;
+ } else {
+ if (charWidthPtr != NULL) {
+ *charWidthPtr = *widthPtr;
+ }
}
if ((*xPtr + *widthPtr) <= dInfoPtr->x) {
return -1;
@@ -4369,7 +5935,7 @@ TkTextDLineInfo(textPtr, indexPtr, xPtr, yPtr, widthPtr, heightPtr, basePtr)
}
dlx = (dlPtr->chunkPtr != NULL? dlPtr->chunkPtr->x: 0);
- *xPtr = dInfoPtr->x - dInfoPtr->curPixelOffset + dlx;
+ *xPtr = dInfoPtr->x - dInfoPtr->curXPixelOffset + dlx;
*widthPtr = dlPtr->length - dlx;
*yPtr = dlPtr->y;
if ((dlPtr->y + dlPtr->height) > dInfoPtr->maxY) {
@@ -4454,8 +6020,9 @@ TkTextCharLayoutProc(textPtr, indexPtr, segPtr, byteOffset, maxX, maxBytes,
* 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. */
+ 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
@@ -4643,7 +6210,8 @@ CharDisplayProc(chunkPtr, x, y, height, baseline, display, dst, screenY)
* Draw the text, underline, and overstrike for this chunk.
*/
- if (!sValuePtr->elide && (ciPtr->numBytes > offsetBytes) && (stylePtr->fgGC != None)) {
+ if (!sValuePtr->elide && (ciPtr->numBytes > offsetBytes)
+ && (stylePtr->fgGC != None)) {
int numBytes = ciPtr->numBytes - offsetBytes;
char *string = ciPtr->chars + offsetBytes;
@@ -4759,7 +6327,7 @@ 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
+ int byteIndex; /* Byte offset of desired character
* within the chunk. */
int y; /* Topmost pixel in area allocated
* for this line. */
@@ -5235,3 +6803,102 @@ MeasureChars(tkfont, source, maxBytes, startX, maxX, tabOrigin, nextXPtr)
*nextXPtr = curX;
return start - source;
}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TextGetScrollInfoObj --
+ *
+ * This procedure is invoked to parse "yview" scrolling commands for
+ * text widgets using the new scrolling command syntax ("moveto" or
+ * "scroll" options). It extends the public Tk_GetScrollInfoObj
+ * function with the addition of "pixels" as a valid unit alongside
+ * "pages" and "units". It is a shame the core API isn't more
+ * flexible in this regard.
+ *
+ * Results:
+ * The return value is either TKTEXT_SCROLL_MOVETO,
+ * TKTEXT_SCROLL_PAGES, TKTEXT_SCROLL_UNITS, TKTEXT_SCROLL_PIXELS or
+ * TKTEXT_SCROLL_ERROR. This indicates whether the command was
+ * successfully parsed and what form the command took. If
+ * TKTEXT_SCROLL_MOVETO, *dblPtr is filled in with the desired
+ * position; if TKTEXT_SCROLL_PAGES, TKTEXT_SCROLL_PIXELS or
+ * TKTEXT_SCROLL_UNITS, *intPtr is filled in with the number of
+ * pages/pixels/lines to move (may be negative); if
+ * TKTEXT_SCROLL_ERROR, the interp's result contains an error
+ * message.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+TextGetScrollInfoObj(interp, textPtr, objc, objv, dblPtr, intPtr)
+ Tcl_Interp *interp; /* Used for error reporting. */
+ TkText *textPtr; /* Information about the text
+ * widget. */
+ int objc; /* # arguments for command. */
+ Tcl_Obj *CONST objv[]; /* Arguments for command. */
+ double *dblPtr; /* Filled in with argument "moveto"
+ * option, if any. */
+ int *intPtr; /* Filled in with number of pages
+ * or lines or pixels to scroll,
+ * if any. */
+{
+ char c;
+ size_t length;
+ CONST char *arg2;
+
+ arg2 = Tcl_GetStringFromObj(objv[2], &length);
+ c = arg2[0];
+ if ((c == 'm') && (strncmp(arg2, "moveto", length) == 0)) {
+ if (objc != 4) {
+ Tcl_WrongNumArgs(interp, 2, objv, "moveto fraction");
+ return TKTEXT_SCROLL_ERROR;
+ }
+ if (Tcl_GetDoubleFromObj(interp, objv[3], dblPtr) != TCL_OK) {
+ return TKTEXT_SCROLL_ERROR;
+ }
+ return TKTEXT_SCROLL_MOVETO;
+ } else if ((c == 's') && (strncmp(arg2, "scroll", length) == 0)) {
+ CONST char *arg4;
+
+ if (objc != 5) {
+ Tcl_WrongNumArgs(interp, 2, objv,
+ "scroll number units|pages|pixels");
+ return TKTEXT_SCROLL_ERROR;
+ }
+ arg4 = Tcl_GetStringFromObj(objv[4], &length);
+ c = arg4[0];
+ if ((c == 'p') && (length == 1)) {
+ Tcl_AppendResult(interp, "ambiguous argument \"", arg4,
+ "\": must be units, pages or pixels", (char *) NULL);
+ return TKTEXT_SCROLL_ERROR;
+ } else if ((c == 'p') && (strncmp(arg4, "pages", length) == 0)) {
+ if (Tcl_GetIntFromObj(interp, objv[3], intPtr) != TCL_OK) {
+ return TKTEXT_SCROLL_ERROR;
+ }
+ return TKTEXT_SCROLL_PAGES;
+ } else if ((c == 'p') && (strncmp(arg4, "pixels", length) == 0)) {
+ if (Tk_GetPixelsFromObj(interp, textPtr->tkwin, objv[3],
+ intPtr) != TCL_OK) {
+ return TKTEXT_SCROLL_ERROR;
+ }
+ return TKTEXT_SCROLL_PIXELS;
+ } else if ((c == 'u') && (strncmp(arg4, "units", length) == 0)) {
+ if (Tcl_GetIntFromObj(interp, objv[3], intPtr) != TCL_OK) {
+ return TKTEXT_SCROLL_ERROR;
+ }
+ return TKTEXT_SCROLL_UNITS;
+ } else {
+ Tcl_AppendResult(interp, "bad argument \"", arg4,
+ "\": must be units, pages or pixels", (char *) NULL);
+ return TKTEXT_SCROLL_ERROR;
+ }
+ }
+ Tcl_AppendResult(interp, "unknown option \"", arg2,
+ "\": must be moveto or scroll", (char *) NULL);
+ return TKTEXT_SCROLL_ERROR;
+}