From 6e1a009795f451583308dda6b41c6249b637933c Mon Sep 17 00:00:00 2001 From: vincentdarley Date: Mon, 14 Feb 2005 23:00:29 +0000 Subject: fix to newline eliding in text widget --- ChangeLog | 13 ++ doc/text.n | 11 +- generic/tkText.c | 24 +++- generic/tkText.h | 9 +- generic/tkTextBTree.c | 104 +++++++++++++--- generic/tkTextDisp.c | 327 +++++++++++++++++++++++++++++++++++++++----------- generic/tkTextIndex.c | 44 +++++-- tests/textDisp.test | 40 +++++- tests/textIndex.test | 3 +- 9 files changed, 452 insertions(+), 123 deletions(-) diff --git a/ChangeLog b/ChangeLog index 1a6e2e7..6330dd2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,16 @@ +2005-02-14 Vince Darley + + * generic/tkText.c: + * generic/tkText.h: + * generic/tkTextDisp.c: + * generic/tkTextIndex.c: + * generic/tkTextBTree.c: + * doc/text.n: + * tests/textDisp.test: + * tests/textIndex.test: fix of longstanding elide problem + when eliding a newline without eliding the entire logical + line. [Bug 443848] + 2005-02-14 Jeff Hobbs * doc/options.n: note -cursor {} behavior. [Bug 965618] diff --git a/doc/text.n b/doc/text.n index 1cdf301..c294f27 100644 --- a/doc/text.n +++ b/doc/text.n @@ -5,7 +5,7 @@ '\" See the file "license.terms" for information on usage and redistribution '\" of this file, and for a DISCLAIMER OF ALL WARRANTIES. '\" -'\" RCS: @(#) $Id: text.n,v 1.34 2005/02/11 21:04:37 hobbs Exp $ +'\" RCS: @(#) $Id: text.n,v 1.35 2005/02/14 23:02:52 vincentdarley Exp $ '\" .so man.macros .TH text n 8.5 Tk "Tk Built-In Commands" @@ -2160,15 +2160,6 @@ The display line with the insert cursor is redrawn each time the cursor blinks, which causes a steady stream of graphics traffic. Set the \fBinsertOffTime\fP attribute to 0 avoid this. .SH "KNOWN BUGS" -The \fB\-elide\fP tag attribute, introduced in Tk 8.3, has one known -limitation. Newlines which have this attribute are only actually elided -if their entire logical line is also elided. Where there is a logical -line which begins un-elided but terminates with an elided newline, that -newline will still actually cause a line-break in the display. The basic -limitation is that while a single logical line can result in multiple -display lines, a single display line cannot be derived from multiple -logical lines. This does mean, however, that logical lines which are -completely elided have no problems. .VS 8.5 .PP The \fBsearch \-regexp\fR sub-command attempts to perform sophisticated diff --git a/generic/tkText.c b/generic/tkText.c index 9f1f6ac..8067165 100644 --- a/generic/tkText.c +++ b/generic/tkText.c @@ -14,7 +14,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkText.c,v 1.55 2004/11/15 13:09:07 vincentdarley Exp $ + * RCS: @(#) $Id: tkText.c,v 1.56 2005/02/14 23:00:29 vincentdarley Exp $ */ #include "default.h" @@ -905,9 +905,25 @@ TextWidgetObjCmd(clientData, interp, objc, objv) * Caution: we must NEVER call TkTextUpdateOneLine * with the last artificial line in the widget. */ - while (fromPtr != indexToPtr->linePtr) { - value += TkTextUpdateOneLine(textPtr, fromPtr, 0, NULL); - fromPtr = TkBTreeNextLine(textPtr, fromPtr); + index = *indexFromPtr; + while (index.linePtr != indexToPtr->linePtr) { + value += TkTextUpdateOneLine(textPtr, fromPtr, + 0, &index, 0); + /* + * We might have skipped past indexToPtr, if we + * have multiple logical lines in a single + * display line. Therefore we iterate through + * each intermediate logical line, just to + * check. Another approach would be just to use + * TkTextIndexCmp on every while() iteration, + * but that would be less efficient. + */ + while (fromPtr != index.linePtr) { + fromPtr = TkBTreeNextLine(textPtr, fromPtr); + if (fromPtr == indexToPtr->linePtr) { + break; + } + } } /* * Now we need to adjust the count to add on the diff --git a/generic/tkText.h b/generic/tkText.h index 14b2a71..5d7ea0f 100644 --- a/generic/tkText.h +++ b/generic/tkText.h @@ -10,7 +10,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkText.h,v 1.25 2004/09/10 12:13:41 vincentdarley Exp $ + * RCS: @(#) $Id: tkText.h,v 1.26 2005/02/14 23:00:44 vincentdarley Exp $ */ #ifndef _TKTEXT @@ -1000,7 +1000,8 @@ EXTERN Tk_SegType tkTextToggleOffType; */ EXTERN int TkBTreeAdjustPixelHeight _ANSI_ARGS_((CONST TkText *textPtr, - TkTextLine *linePtr, int newPixelHeight)); + TkTextLine *linePtr, int newPixelHeight, + int mergedLogicalLines)); EXTERN int TkBTreeCharTagged _ANSI_ARGS_((CONST TkTextIndex *indexPtr, TkTextTag *tagPtr)); EXTERN void TkBTreeCheck _ANSI_ARGS_((TkTextBTree tree)); @@ -1095,7 +1096,7 @@ EXTERN TkTextTabArray * TkTextGetTabs _ANSI_ARGS_((Tcl_Interp *interp, EXTERN void TkTextFindDisplayLineEnd _ANSI_ARGS_(( TkText *textPtr, TkTextIndex *indexPtr, int end, int *xOffset)); -EXTERN void TkTextIndexBackBytes _ANSI_ARGS_((CONST TkText *textPtr, +EXTERN int TkTextIndexBackBytes _ANSI_ARGS_((CONST TkText *textPtr, CONST TkTextIndex *srcPtr, int count, TkTextIndex *dstPtr)); EXTERN void TkTextIndexBackChars _ANSI_ARGS_(( @@ -1150,7 +1151,7 @@ EXTERN int TkTextUpdateLineMetrics _ANSI_ARGS_((TkText *textPtr, int lineNum, int endLine, int doThisMuch)); EXTERN int TkTextUpdateOneLine _ANSI_ARGS_((TkText *textPtr, TkTextLine *linePtr, int pixelHeight, - TkTextIndex *indexPtr)); + TkTextIndex *indexPtr, int partialCalc)); EXTERN int TkTextMarkCmd _ANSI_ARGS_((TkText *textPtr, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); EXTERN int TkTextMarkNameToIndex _ANSI_ARGS_((TkText *textPtr, diff --git a/generic/tkTextBTree.c b/generic/tkTextBTree.c index 964dfcd..1919849 100644 --- a/generic/tkTextBTree.c +++ b/generic/tkTextBTree.c @@ -11,7 +11,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkTextBTree.c,v 1.15 2004/09/10 12:13:41 vincentdarley Exp $ + * RCS: @(#) $Id: tkTextBTree.c,v 1.16 2005/02/14 23:00:44 vincentdarley Exp $ */ #include "tkInt.h" @@ -919,11 +919,19 @@ DeleteSummaries(summaryPtr) */ int -TkBTreeAdjustPixelHeight(textPtr, linePtr, newPixelHeight) +TkBTreeAdjustPixelHeight(textPtr, linePtr, newPixelHeight, mergedLogicalLines) CONST TkText *textPtr; /* Client of the B-tree */ register TkTextLine *linePtr; /* The logical line to update */ int newPixelHeight; /* The line's known height * in pixels */ + int mergedLogicalLines; /* The number of extra logical + * lines which have been merged + * with this one (due to elided + * eols). They will have their + * pixel height set to zero, and + * the total pixel height + * associated with the given + * linePtr. */ { register Node *nodePtr; int changeToPixelCount; /* Counts change to total number of @@ -947,6 +955,17 @@ TkBTreeAdjustPixelHeight(textPtr, linePtr, newPixelHeight) linePtr->pixels[2*pixelReference] = newPixelHeight; + /* + * Any merged logical lines must have their height set to zero. + */ + while (mergedLogicalLines-- > 0) { + linePtr = TkBTreeNextLine(textPtr, linePtr); + TkBTreeAdjustPixelHeight(textPtr, linePtr, 0, 0); + } + + /* + * Return total number of pixels in the tree. + */ return nodePtr->numPixels[pixelReference]; } @@ -1161,11 +1180,14 @@ SplitSeg(indexPtr) * at which to split a segment. */ { TkTextSegment *prevPtr, *segPtr; - int count; - - for (count = indexPtr->byteIndex, prevPtr = NULL, - segPtr = indexPtr->linePtr->segPtr; segPtr != NULL; - count -= segPtr->size, prevPtr = segPtr, segPtr = segPtr->nextPtr) { + TkTextLine *linePtr; + + int count = indexPtr->byteIndex; + linePtr = indexPtr->linePtr; + prevPtr = NULL; + segPtr = linePtr->segPtr; + + while (segPtr != NULL) { if (segPtr->size > count) { if (count == 0) { return prevPtr; @@ -1181,6 +1203,21 @@ SplitSeg(indexPtr) && !segPtr->typePtr->leftGravity) { return prevPtr; } + + count -= segPtr->size; + prevPtr = segPtr; + segPtr = segPtr->nextPtr; + if (segPtr == NULL) { + /* + * Two logical lines merged into one display line + * through eliding of a newline + */ + linePtr = TkBTreeNextLine(NULL, linePtr); + if (linePtr == NULL) { + /* Reached end of the text */ + } + segPtr = linePtr->segPtr; + } } Tcl_Panic("SplitSeg reached end of line!"); return NULL; @@ -2014,9 +2051,18 @@ TkBTreeUnlinkSegment(segPtr, linePtr) if (linePtr->segPtr == segPtr) { linePtr->segPtr = segPtr->nextPtr; } else { - for (prevPtr = linePtr->segPtr; prevPtr->nextPtr != segPtr; - prevPtr = prevPtr->nextPtr) { - /* Empty loop body. */ + prevPtr = linePtr->segPtr; + while (prevPtr->nextPtr != segPtr) { + prevPtr = prevPtr->nextPtr; + + if (prevPtr == NULL) { + /* + * Two logical lines merged into one display line + * through eliding of a newline + */ + linePtr = TkBTreeNextLine(NULL, linePtr); + prevPtr = linePtr->segPtr; + } } prevPtr->nextPtr = segPtr->nextPtr; } @@ -3251,6 +3297,7 @@ TkBTreeGetTags(indexPtr, textPtr, numTagsPtr) register Node *nodePtr; register TkTextLine *siblingLinePtr; register TkTextSegment *segPtr; + TkTextLine *linePtr; int src, dst, index; TagInfo tagInfo; #define NUM_TAG_INFOS 10 @@ -3267,13 +3314,25 @@ TkBTreeGetTags(indexPtr, textPtr, numTagsPtr) * indexPtr. */ - for (index = 0, segPtr = indexPtr->linePtr->segPtr; - (index + segPtr->size) <= indexPtr->byteIndex; - index += segPtr->size, segPtr = segPtr->nextPtr) { + linePtr = indexPtr->linePtr; + index = 0; + segPtr = linePtr->segPtr; + while ((index + segPtr->size) <= indexPtr->byteIndex) { if ((segPtr->typePtr == &tkTextToggleOnType) || (segPtr->typePtr == &tkTextToggleOffType)) { IncCount(segPtr->body.toggle.tagPtr, 1, &tagInfo); } + index += segPtr->size; + segPtr = segPtr->nextPtr; + + if (segPtr == NULL) { + /* + * Two logical lines merged into one display line + * through eliding of a newline + */ + linePtr = TkBTreeNextLine(NULL, linePtr); + segPtr = linePtr->segPtr; + } } /* @@ -3389,6 +3448,7 @@ TkTextIsElided(textPtr, indexPtr, elideInfo) register TkTextTag *tagPtr = NULL; register int i, index; register TkTextElideInfo *infoPtr; + TkTextLine *linePtr; int elide; if (elideInfo == NULL) { @@ -3419,9 +3479,10 @@ TkTextIsElided(textPtr, indexPtr, elideInfo) * indexPtr. */ - for (index = 0, segPtr = indexPtr->linePtr->segPtr; - (index + segPtr->size) <= indexPtr->byteIndex; - index += segPtr->size, segPtr = segPtr->nextPtr) { + index = 0; + linePtr = indexPtr->linePtr; + segPtr = linePtr->segPtr; + while ((index + segPtr->size) <= indexPtr->byteIndex) { if ((segPtr->typePtr == &tkTextToggleOnType) || (segPtr->typePtr == &tkTextToggleOffType)) { tagPtr = segPtr->body.toggle.tagPtr; @@ -3430,6 +3491,17 @@ TkTextIsElided(textPtr, indexPtr, elideInfo) infoPtr->tagCnts[tagPtr->priority]++; } } + + index += segPtr->size; + segPtr = segPtr->nextPtr; + if (segPtr == NULL) { + /* + * Two logical lines merged into one display line + * through eliding of a newline + */ + linePtr = TkBTreeNextLine(NULL, linePtr); + segPtr = linePtr->segPtr; + } } /* * Store the first segPtr we haven't examined completely diff --git a/generic/tkTextDisp.c b/generic/tkTextDisp.c index b585534..bef5148 100644 --- a/generic/tkTextDisp.c +++ b/generic/tkTextDisp.c @@ -13,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.46 2005/01/11 16:00:14 vincentdarley Exp $ + * RCS: @(#) $Id: tkTextDisp.c,v 1.47 2005/02/14 23:00:44 vincentdarley Exp $ */ #include "tkPort.h" @@ -165,6 +165,8 @@ typedef struct DLine { int byteCount; /* Number of bytes accounted for by this * display line, including a trailing space * or newline that isn't actually displayed. */ + int logicalLinesMerged; /* Number of extra logical lines merged + * into this one due to elided newlines. */ int y; /* Y-position at which line is supposed to * be drawn (topmost pixel of rectangular * area occupied by line). */ @@ -508,7 +510,7 @@ static void TextInvalidateLineMetrics _ANSI_ARGS_((TkText *textPtr, TkTextLine *linePtr, int lineCount, int action)); static int CalculateDisplayLineHeight _ANSI_ARGS_(( TkText *textPtr, CONST TkTextIndex *indexPtr, - int *byteCountPtr)); + int *byteCountPtr, int *mergedLinePtr)); static void DlineIndexOfX _ANSI_ARGS_((TkText *textPtr, DLine *dlPtr, int x, TkTextIndex *indexPtr)); static int DlineXOfIndex _ANSI_ARGS_((TkText *textPtr, @@ -1031,7 +1033,8 @@ LayoutDLine(textPtr, indexPtr) dlPtr->chunkPtr = NULL; dlPtr->nextPtr = NULL; dlPtr->flags = NEW_LAYOUT | OLD_Y_INVALID; - + dlPtr->logicalLinesMerged = 0; + /* * Special case entirely elide line as there may be 1000s or more */ @@ -1115,7 +1118,7 @@ LayoutDLine(textPtr, indexPtr) if (TkBTreeLinePixelCount(textPtr, dlPtr->index.linePtr) != 0) { TkBTreeAdjustPixelHeight(textPtr, - dlPtr->index.linePtr, 0); + dlPtr->index.linePtr, 0, 0); } } TkTextFreeElideInfo(&info); @@ -1146,17 +1149,35 @@ LayoutDLine(textPtr, indexPtr) wrapMode = TEXT_WRAPMODE_CHAR; tabSize = 0; lastCharChunkPtr = NULL; - + /* * Find the first segment to consider for the line. Can't call * TkTextIndexToSeg for this because it won't return a segment * with zero size (such as the insertion cursor's mark). */ - for (byteOffset = curIndex.byteIndex, segPtr = curIndex.linePtr->segPtr; - (byteOffset > 0) && (byteOffset >= segPtr->size); - byteOffset -= segPtr->size, segPtr = segPtr->nextPtr) { - /* Empty loop body. */ + connectNextLogicalLine: + byteOffset = curIndex.byteIndex; + segPtr = curIndex.linePtr->segPtr; + while ((byteOffset > 0) && (byteOffset >= segPtr->size)) { + byteOffset -= segPtr->size; + segPtr = segPtr->nextPtr; + + if (segPtr == NULL) { + /* + * Two logical lines merged into one display line + * through eliding of a newline + */ + TkTextLine *linePtr = TkBTreeNextLine(NULL, curIndex.linePtr); + if (linePtr != NULL) { + dlPtr->logicalLinesMerged++; + curIndex.byteIndex = 0; + curIndex.linePtr = linePtr; + segPtr = curIndex.linePtr->segPtr; + } else { + break; + } + } } while (segPtr != NULL) { @@ -1168,11 +1189,10 @@ LayoutDLine(textPtr, indexPtr) * 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. + * elided still gets laid out into a DLine, but with zero + * height. This isn't particularly a problem, but it does seem + * somewhat unnecessary. We may wish to redesign the code to + * remove these zero height DLines in the future. */ if (elide && (lastChunkPtr != NULL) && (lastChunkPtr->displayProc == NULL /*ElideDisplayProc*/)) { @@ -1195,9 +1215,26 @@ LayoutDLine(textPtr, indexPtr) byteOffset = 0; segPtr = segPtr->nextPtr; + + if (segPtr == NULL) { + /* + * Two logical lines merged into one display line + * through eliding of a newline + */ + TkTextLine *linePtr = TkBTreeNextLine(NULL, curIndex.linePtr); + if (linePtr != NULL) { + dlPtr->logicalLinesMerged++; + curIndex.byteIndex = 0; + curIndex.linePtr = linePtr; + goto connectNextLogicalLine; + } + } + /* Code no longer needed, now that we allow logical lines + * to merge into a single display line. if (segPtr == NULL && chunkPtr != NULL) { ckfree((char *) chunkPtr); - } + chunkPtr = NULL; + }*/ continue; } @@ -1350,19 +1387,36 @@ LayoutDLine(textPtr, indexPtr) 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] + * until the newline. Hence the newline is actually + * elided, and we want to merge the display of the next + * logical line with this one. */ + TkTextLine *linePtr = TkBTreeNextLine(NULL, curIndex.linePtr); + if (linePtr != NULL) { + dlPtr->logicalLinesMerged++; + curIndex.byteIndex = 0; + curIndex.linePtr = linePtr; + chunkPtr = NULL; + goto connectNextLogicalLine; + } } } chunkPtr = NULL; } if (noCharsYet) { - Tcl_Panic("LayoutDLine couldn't place any characters on a line"); + dlPtr->spaceAbove = 0; + dlPtr->spaceBelow = 0; + dlPtr->length = 0; + /* + * We used to Tcl_Panic here, saying that LayoutDLine couldn't + * place any characters on a line, but I believe a more + * appropriate response is to return a DLine with zero height. + * With elided lines, tag transitions and asynchronous line + * height calculations, it is hard to avoid this situation ever + * arising with the current code design. + */ + return dlPtr; } wholeLine = (segPtr == NULL); @@ -1704,7 +1758,7 @@ UpdateDisplayInfo(textPtr) */ TkBTreeAdjustPixelHeight(textPtr, prevPtr->index.linePtr, - lineHeight); + lineHeight, 0); /* * I believe we can be 100% sure that we started at the * beginning of the logical line, so we can also adjust @@ -1826,7 +1880,7 @@ UpdateDisplayInfo(textPtr) lowestPtr->index.linePtr)) { TkBTreeAdjustPixelHeight(textPtr, lowestPtr->index.linePtr, - pixelHeight); + pixelHeight, 0); if (index.linePtr != lowestPtr->index.linePtr) { /* * We examined the entire line, so can update @@ -2796,7 +2850,7 @@ TkTextUpdateLineMetrics(textPtr, lineNum, endLine, doThisMuch) != textPtr->dInfoPtr->lineMetricUpdateEpoch) { if (doThisMuch == -1) { count += 8 * TkTextUpdateOneLine(textPtr, linePtr, - 0, NULL); + 0, NULL, 0); } else { TkTextIndex index; TkTextIndex *indexPtr; @@ -2834,7 +2888,7 @@ TkTextUpdateLineMetrics(textPtr, lineNum, endLine, doThisMuch) * 8 for each display line we actually re-layout. */ count += 8 * TkTextUpdateOneLine(textPtr, linePtr, - pixelHeight, indexPtr); + pixelHeight, indexPtr, 1); if (indexPtr->linePtr == linePtr) { /* @@ -3082,11 +3136,7 @@ TextInvalidateLineMetrics(textPtr, linePtr, lineCount, action) * * 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). + * be the last elided index on the display line. * * Results: * Modifies indexPtr to point to the given end. @@ -3127,17 +3177,51 @@ TkTextFindDisplayLineEnd(textPtr, indexPtr, end, xOffset) index.textPtr = NULL; while (1) { + TkTextIndex endOfLastLine; + + if (TkTextIndexBackBytes(textPtr, &index, 1, &endOfLastLine)) { + /* Reached beginning of text */ + break; + } + + if (!TkTextIsElided(textPtr, &endOfLastLine, NULL)) { + /* + * The eol is not elided, so 'index' points to + * the start of a display line (as well as logical + * line). + */ + break; + } + /* + * indexPtr's logical line is actually merged with + * the previous logical line whose eol is elided. + * Continue searching back to get a real line start. + */ + index = endOfLastLine; + index.byteIndex = 0; + } + + while (1) { DLine *dlPtr; int byteCount; + TkTextIndex nextLineStart; dlPtr = LayoutDLine(textPtr, &index); byteCount = dlPtr->byteCount; - + + TkTextIndexForwBytes(textPtr, &index, byteCount, &nextLineStart); + /* - * 'byteCount' goes up to the beginning of the next line, - * so equality here says we need one more line + * 'byteCount' goes up to the beginning of the next display + * line, so equality here says we need one more line. We + * try to perform a quick comparison which is valid for + * the case where the logical line is the same, but + * otherwise fall back on a full TkTextIndexCmp. */ - if (index.byteIndex + byteCount > indexPtr->byteIndex) { + if (((index.linePtr == indexPtr->linePtr) + && (index.byteIndex + byteCount > indexPtr->byteIndex)) + || (dlPtr->logicalLinesMerged > 0 + && TkTextIndexCmp(&nextLineStart, indexPtr) > 0)) { /* It's on this display line */ if (xOffset != NULL) { /* @@ -3149,19 +3233,20 @@ TkTextFindDisplayLineEnd(textPtr, indexPtr, end, xOffset) *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); + TkTextIndexBackBytes(textPtr, &nextLineStart, 1, indexPtr); + } else { + *indexPtr = index; } FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP); return; } FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP); - TkTextIndexForwBytes(textPtr, &index, byteCount, &index); + index = nextLineStart; } } } @@ -3188,6 +3273,10 @@ TkTextFindDisplayLineEnd(textPtr, indexPtr, end, xOffset) * 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). + * + * If 'mergedLinePtr' is non-NULL, then returns in that pointer + * the number of extra logical lines merged into the given + * display line. * * Side effects: * The combination of 'LayoutDLine' and 'FreeDLines' seems @@ -3201,18 +3290,37 @@ TkTextFindDisplayLineEnd(textPtr, indexPtr, end, xOffset) */ static int -CalculateDisplayLineHeight(textPtr, indexPtr, byteCountPtr) +CalculateDisplayLineHeight(textPtr, indexPtr, byteCountPtr, mergedLinePtr) 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. */ + int *mergedLinePtr; /* NULL or used to return if the given + * display line merges with a following + * logical line (because the eol is + * elided). */ { DLine *dlPtr; int pixelHeight; /* + * Special case for artificial last line. May be better to move + * this inside LayoutDLine. + */ + if (indexPtr->byteIndex == 0 + && TkBTreeNextLine(textPtr, indexPtr->linePtr) == NULL) { + if (byteCountPtr != NULL) { + *byteCountPtr = 0; + } + if (mergedLinePtr != NULL) { + *mergedLinePtr = 0; + } + return 0; + } + + /* * 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 @@ -3223,6 +3331,9 @@ CalculateDisplayLineHeight(textPtr, indexPtr, byteCountPtr) if (byteCountPtr != NULL) { *byteCountPtr = dlPtr->byteCount; } + if (mergedLinePtr != NULL) { + *mergedLinePtr = dlPtr->logicalLinesMerged; + } FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP); return pixelHeight; @@ -3288,7 +3399,7 @@ TkTextIndexYPixels(textPtr, indexPtr) * specifically the 'linePtr->pixelHeight == pixelHeight' test * below this while loop. */ - height = CalculateDisplayLineHeight(textPtr, &index, &bytes); + height = CalculateDisplayLineHeight(textPtr, &index, &bytes, NULL); index.byteIndex += bytes; @@ -3334,7 +3445,7 @@ TkTextIndexYPixels(textPtr, indexPtr) */ int -TkTextUpdateOneLine(textPtr, linePtr, pixelHeight, indexPtr) +TkTextUpdateOneLine(textPtr, linePtr, pixelHeight, indexPtr, partialCalc) TkText *textPtr; /* Widget record for text widget. */ TkTextLine *linePtr; /* The line of which to calculate the * height. */ @@ -3344,10 +3455,18 @@ TkTextUpdateOneLine(textPtr, linePtr, pixelHeight, indexPtr) * has been given. */ TkTextIndex *indexPtr; /* Either NULL or an index at the start of * a display line belonging to linePtr, - * up to which we have already calculated. */ + * at which we wish to start (e.g. up to + * which we have already calculated). On + * return this will be set to the first + * index on the next line. */ + int partialCalc; /* Set to 1 if we are allowed to do + * partial height calculations of + * long-lines. In this case we'll only + * return what we know so far. */ { TkTextIndex index; - int displayLines, partialCalc; + int displayLines; + int mergedLines; if (indexPtr == NULL) { index.tree = textPtr->sharedTextPtr->tree; @@ -3356,9 +3475,6 @@ TkTextUpdateOneLine(textPtr, linePtr, pixelHeight, indexPtr) index.textPtr = NULL; indexPtr = &index; pixelHeight = 0; - partialCalc = 0; - } else { - partialCalc = 1; } /* @@ -3369,9 +3485,10 @@ TkTextUpdateOneLine(textPtr, linePtr, pixelHeight, indexPtr) */ displayLines = 0; + mergedLines = 0; while (1) { - int bytes, height; + int bytes, height, logicalLines; /* * Currently this call doesn't have many side-effects. @@ -3381,36 +3498,71 @@ TkTextUpdateOneLine(textPtr, linePtr, pixelHeight, indexPtr) * specifically the 'linePtr->pixelHeight == pixelHeight' test * below this while loop. */ - height = CalculateDisplayLineHeight(textPtr, indexPtr, &bytes); + height = CalculateDisplayLineHeight(textPtr, indexPtr, &bytes, + &logicalLines); if (height > 0) { pixelHeight += height; displayLines++; } + mergedLines += logicalLines; + if (TkTextIndexForwBytes(textPtr, indexPtr, bytes, indexPtr)) { break; } - if (indexPtr->linePtr != linePtr) { - /* - * If we reached the end of the logical line, then - * either way we don't have a partial calculation. + if (logicalLines == 0) { + if (indexPtr->linePtr != linePtr) { + /* + * If we reached the end of the logical line, then + * either way we don't have a partial calculation. + */ + partialCalc = 0; + break; + } + } else if (indexPtr->byteIndex != 0) { + /* We must still be on the same wrapped line */ + } else { + /* + * Must check if indexPtr is really a new logical line + * which is not merged with the previous line. The only + * code that would really know this is LayoutDLine, which + * doesn't pass the information on, so we have to check + * manually here. */ - partialCalc = 0; - break; + TkTextIndex idx; + TkTextIndexBackChars(textPtr, indexPtr, 1, &idx, COUNT_INDICES); + if (!TkTextIsElided(textPtr, &idx, NULL)) { + /* We've ended a logical line */ + partialCalc = 0; + break; + } + /* We must still be on the same wrapped line */ } - if (partialCalc && displayLines > 50) { + if (partialCalc && displayLines > 50 && mergedLines == 0) { /* * Only calculate 50 display lines at a time, to * avoid huge delays. In any case it is very rare * that a single line wraps 50 times! + * + * If we have any merged lines, we must complete the full + * logical line layout here and now, because the + * partial-calculation code isn't designed to handle merged + * logical lines. Hence the 'mergedLines == 0' check. */ break; } } if (!partialCalc) { + int changed = 0; + + /* + * Cancel any partial line height calculation state. + */ + textPtr->dInfoPtr->metricEpoch = -1; + /* * Mark the logical line as being up to date (caution: it isn't * yet up to date, that will happen in TkBTreeAdjustPixelHeight @@ -3418,12 +3570,33 @@ TkTextUpdateOneLine(textPtr, linePtr, pixelHeight, indexPtr) */ TkBTreeLinePixelEpoch(textPtr, linePtr) = textPtr->dInfoPtr->lineMetricUpdateEpoch; - /* - * Also cancel any partial line height calculation state. - */ - textPtr->dInfoPtr->metricEpoch = -1; - - if (TkBTreeLinePixelCount(textPtr, linePtr) == pixelHeight) { + if (TkBTreeLinePixelCount(textPtr, linePtr) != pixelHeight) { + changed = 1; + } + + if (mergedLines > 0) { + int i = mergedLines; + TkTextLine *mergedLinePtr; + /* + * Loop over all merged logical lines, marking them up to date + * (again, the pixel count setting will actually happen in + * TkBTreeAdjustPixelHeight). + */ + mergedLinePtr = linePtr; + while (i-- > 0) { + mergedLinePtr = TkBTreeNextLine(textPtr, mergedLinePtr); + TkBTreeLinePixelEpoch(textPtr, mergedLinePtr) + = textPtr->dInfoPtr->lineMetricUpdateEpoch; + if (TkBTreeLinePixelCount(textPtr, mergedLinePtr) != 0) { + changed = 1; + } + } + } + + if (!changed) { + /* + * If there's nothing to change, then we can already return. + */ return displayLines; } } @@ -3433,8 +3606,8 @@ TkTextUpdateOneLine(textPtr, linePtr, pixelHeight, indexPtr) * of the entire widget, which may be used just below for * reporting/debugging purposes. */ - pixelHeight = TkBTreeAdjustPixelHeight(textPtr, - linePtr, pixelHeight); + pixelHeight = TkBTreeAdjustPixelHeight(textPtr, linePtr, + pixelHeight, mergedLines); if (tkTextDebug) { char buffer[2 * TCL_INTEGER_SPACE + 1]; @@ -4661,7 +4834,7 @@ TkTextSetYView(textPtr, indexPtr, pickPlace) * window. */ - lineHeight = CalculateDisplayLineHeight(textPtr, indexPtr, NULL); + lineHeight = CalculateDisplayLineHeight(textPtr, indexPtr, NULL, NULL); /* * It would be better if 'bottomY' were calculated using the * actual height of the given line, not 'textPtr->charHeight'. @@ -5139,7 +5312,7 @@ YScrollByPixels(textPtr, offset) * negative here. */ offset -= CalculateDisplayLineHeight(textPtr, - &textPtr->topIndex, NULL) - dInfoPtr->topPixelOffset; + &textPtr->topIndex, NULL, NULL) - dInfoPtr->topPixelOffset; MeasureUp(textPtr, &textPtr->topIndex, -offset, &textPtr->topIndex, &dInfoPtr->newTopPixelOffset); } else if (offset > 0) { @@ -6193,26 +6366,38 @@ DlineIndexOfX(textPtr, dlPtr, x, indexPtr) x = x - dInfoPtr->x + dInfoPtr->curXPixelOffset; chunkPtr = dlPtr->chunkPtr; - if (chunkPtr == NULL) { + if (chunkPtr == NULL || x == 0) { /* - * This may occur if everything is elided + * This may occur if everything is elided, or if we're simply + * already at the beginning of the line. */ return; } - for (; x >= (chunkPtr->x + chunkPtr->width); - indexPtr->byteIndex += chunkPtr->numBytes, - chunkPtr = chunkPtr->nextPtr) { + while (x >= (chunkPtr->x + chunkPtr->width)) { + /* + * Note that this forward then backward movement of the index + * can be problematic at the end of the buffer (we can't move + * forward, and then when we move backward, we do, leading to + * the wrong position). Hence when x == 0 we take special + * action above. + */ + if (TkTextIndexForwBytes(NULL, indexPtr, chunkPtr->numBytes, indexPtr)) { + /* We've reached the end of the text */ + return; + } if (chunkPtr->nextPtr == NULL) { - indexPtr->byteIndex += chunkPtr->numBytes; TkTextIndexBackChars(NULL, indexPtr, 1, indexPtr, COUNT_INDICES); return; } + chunkPtr = chunkPtr->nextPtr; } /* * If the chunk has more than one byte in it, ask it which - * character is at the desired location. + * character is at the desired location. In this case we + * can manipulate 'indexPtr->byteIndex' directly, because + * we know we're staying inside a single logical line. */ if (chunkPtr->numBytes > 1) { diff --git a/generic/tkTextIndex.c b/generic/tkTextIndex.c index ed52e98..e9469ee 100644 --- a/generic/tkTextIndex.c +++ b/generic/tkTextIndex.c @@ -10,7 +10,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkTextIndex.c,v 1.20 2004/10/05 01:26:10 hobbs Exp $ + * RCS: @(#) $Id: tkTextIndex.c,v 1.21 2005/02/14 23:00:46 vincentdarley Exp $ */ #include "default.h" @@ -1016,11 +1016,21 @@ TkTextPrintIndex(textPtr, indexPtr, string) * at least TK_POS_CHARS characters. */ { TkTextSegment *segPtr; + TkTextLine *linePtr; int numBytes, charIndex; numBytes = indexPtr->byteIndex; charIndex = 0; - for (segPtr = indexPtr->linePtr->segPtr; ; segPtr = segPtr->nextPtr) { + linePtr = indexPtr->linePtr; + for (segPtr = linePtr->segPtr; ; segPtr = segPtr->nextPtr) { + if (segPtr == NULL) { + /* + * Two logical lines merged into one display line + * through eliding of a newline + */ + linePtr = TkBTreeNextLine(NULL, linePtr); + segPtr = linePtr->segPtr; + } if (numBytes <= segPtr->size) { break; } @@ -1777,6 +1787,9 @@ TkTextIndexCount(textPtr, indexPtr1, indexPtr2, type) * *dstPtr is modified to refer to the character "count" bytes before * srcPtr, or to the first character in the TkText if there aren't * "count" bytes earlier than srcPtr. + * + * Returns 1 if we couldn't use all of 'byteCount' because we + * have run into the beginning or end of the text, and zero otherwise. * * Side effects: * None. @@ -1784,7 +1797,7 @@ TkTextIndexCount(textPtr, indexPtr1, indexPtr2, type) *--------------------------------------------------------------------------- */ -void +int TkTextIndexBackBytes(textPtr, srcPtr, byteCount, dstPtr) CONST TkText *textPtr; CONST TkTextIndex *srcPtr; /* Source index. */ @@ -1796,8 +1809,7 @@ TkTextIndexBackBytes(textPtr, srcPtr, byteCount, dstPtr) int lineIndex; if (byteCount < 0) { - TkTextIndexForwBytes(textPtr, srcPtr, -byteCount, dstPtr); - return; + return TkTextIndexForwBytes(textPtr, srcPtr, -byteCount, dstPtr); } *dstPtr = *srcPtr; @@ -1814,7 +1826,7 @@ TkTextIndexBackBytes(textPtr, srcPtr, byteCount, dstPtr) } if (lineIndex == 0) { dstPtr->byteIndex = 0; - return; + return 1; } lineIndex--; dstPtr->linePtr = TkBTreeFindLine(dstPtr->tree, textPtr, lineIndex); @@ -1828,6 +1840,7 @@ TkTextIndexBackBytes(textPtr, srcPtr, byteCount, dstPtr) dstPtr->byteIndex += segPtr->size; } } + return 0; } /* @@ -1895,7 +1908,16 @@ TkTextIndexBackChars(textPtr, srcPtr, charCount, dstPtr, type) segPtr = infoPtr->segPtr; segSize -= infoPtr->segOffset; } else { - for (segPtr = dstPtr->linePtr->segPtr; ; segPtr = segPtr->nextPtr) { + TkTextLine *linePtr = dstPtr->linePtr; + for (segPtr = linePtr->segPtr; ; segPtr = segPtr->nextPtr) { + if (segPtr == NULL) { + /* + * Two logical lines merged into one display line + * through eliding of a newline + */ + linePtr = TkBTreeNextLine(NULL, linePtr); + segPtr = linePtr->segPtr; + } if (segSize <= segPtr->size) { break; } @@ -1967,7 +1989,7 @@ TkTextIndexBackChars(textPtr, srcPtr, charCount, dstPtr, type) for (p = end; ; p = Tcl_UtfPrev(p, start)) { if (charCount == 0) { dstPtr->byteIndex -= (end - p); - goto backwadCharDone; + goto backwardCharDone; } if (p == start) { break; @@ -1978,7 +2000,7 @@ TkTextIndexBackChars(textPtr, srcPtr, charCount, dstPtr, type) if (type & COUNT_INDICES) { if (charCount <= segSize) { dstPtr->byteIndex -= charCount; - goto backwadCharDone; + goto backwardCharDone; } charCount -= segSize; } @@ -2009,7 +2031,7 @@ TkTextIndexBackChars(textPtr, srcPtr, charCount, dstPtr, type) } if (lineIndex == 0) { dstPtr->byteIndex = 0; - goto backwadCharDone; + goto backwardCharDone; } lineIndex--; dstPtr->linePtr = TkBTreeFindLine(dstPtr->tree, textPtr, lineIndex); @@ -2026,7 +2048,7 @@ TkTextIndexBackChars(textPtr, srcPtr, charCount, dstPtr, type) segPtr = oldPtr; segSize = segPtr->size; } - backwadCharDone: + backwardCharDone: if (infoPtr != NULL) { TkTextFreeElideInfo(infoPtr); ckfree((char*) infoPtr); diff --git a/tests/textDisp.test b/tests/textDisp.test index d0bb9a8..501a788 100644 --- a/tests/textDisp.test +++ b/tests/textDisp.test @@ -6,7 +6,7 @@ # Copyright (c) 1998-1999 by Scriptics Corporation. # All rights reserved. # -# RCS: @(#) $Id: textDisp.test,v 1.33 2004/12/04 00:04:41 dkf Exp $ +# RCS: @(#) $Id: textDisp.test,v 1.34 2005/02/14 23:03:25 vincentdarley Exp $ package require tcltest 2.1 eval tcltest::configure $argv @@ -2404,11 +2404,12 @@ test textDisp-19.11.22 {TextWidgetCmd procedure, "index +displaylines"} { test textDisp-19.11.23 {TextWidgetCmd procedure, "index +displaylines"} { .t tag remove elide 1.0 end .t tag add elide "12.3" "16.0 +1displaylines" - list [.t index "11.5 +2d lines"] \ + list [.t index "11.5 +1d lines"] [.t index "11.5 +2d lines"] \ + [.t index "12.0 +1d lines"] \ [.t index "12.0 +2d lines"] [.t index "11.0 +2d lines"] \ [.t index "13.0 +2d lines"] [.t index "13.0 +3d lines"] \ [.t index "13.0 +4d lines"] -} {16.21 16.33 16.16 16.50 16.67 17.0} +} {16.17 16.33 16.28 16.46 16.28 16.49 16.65 17.0} .t tag remove elide 1.0 end test textDisp-19.11.24 {TextWidgetCmd procedure, "index +/-displaylines"} { list [.t index "11.5 + -1 display lines"] \ @@ -3369,15 +3370,20 @@ test textDisp-29.3 {miscellaneous: lines wrap but are still too long} {textfonts update list [.t2.t xview] [winfo geom .t2.t.f] [.t2.t bbox 1.3] } [list {0.533333333333 1.0} 300x50+-155+[expr {$fixedDiff + 18}] {}] -test textDisp-30.1 {elidden text complications} {knownBug} { +test textDisp-30.1 {elidden text joining multiple logical lines} { .t2.t delete 1.0 end .t2.t insert 1.0 "1111\n2222\n3333" .t2.t tag configure elidden -elide 1 -background red .t2.t tag add elidden 1.2 3.2 - # Known Bug: the newline at 1.4 will not be elidden. - # Each logical line must have its own DLines .t2.t count -displaylines 1.0 end } {1} +test textDisp-30.2 {elidden text joining multiple logical lines} { + .t2.t delete 1.0 end + .t2.t insert 1.0 "1111\n2222\n3333" + .t2.t tag configure elidden -elide 1 -background red + .t2.t tag add elidden 1.2 2.2 + .t2.t count -displaylines 1.0 end +} {2} catch {destroy .t2} .t configure -height 1 @@ -3491,6 +3497,28 @@ test textDisp-31.6 {line update index shifting} { set res } [list [expr {100 + $fixedHeight * 6}] [expr {100 + $fixedHeight * 8}] [expr {$fixedHeight * 9}] [expr {$fixedHeight * 7}] [expr {100 + $fixedHeight * 6}]] +test textDisp-31.7 {line update index shifting, elided} { + # The 'update' and 'delay' must be long enough to ensure all + # asynchronous updates have been performed. + set res {} + .t delete 1.0 end + lappend res [.t count -update -ypixels 1.0 end] + .t insert 1.0 "abc\nabc" + .t insert 1.0 "abc\n" + lappend res [.t count -update -ypixels 1.0 end] + .t tag configure elide -elide 1 + .t tag add elide 1.3 2.1 + lappend res [.t count -ypixels 1.0 end] + update ; after 1000 ; update + lappend res [.t count -ypixels 1.0 end] + .t delete 1.0 3.0 + lappend res [.t count -ypixels 1.0 end] + update ; after 1000 ; update + lappend res [.t count -ypixels 1.0 end] + set res +} [list [expr {$fixedHeight * 1}] [expr {$fixedHeight * 3}] [expr {$fixedHeight * 3}] [expr {$fixedHeight * 2}] [expr {$fixedHeight * 1}] [expr {$fixedHeight * 1}]] +return + test textDisp-32.0 {everything elided} { # Must not crash pack [text .tt] diff --git a/tests/textIndex.test b/tests/textIndex.test index b4c7d11..6527349 100644 --- a/tests/textIndex.test +++ b/tests/textIndex.test @@ -6,11 +6,12 @@ # Copyright (c) 1998-1999 by Scriptics Corporation. # All rights reserved. # -# RCS: @(#) $Id: textIndex.test,v 1.14 2004/09/10 12:13:43 vincentdarley Exp $ +# RCS: @(#) $Id: textIndex.test,v 1.15 2005/02/14 23:03:26 vincentdarley Exp $ package require tcltest 2.1 eval tcltest::configure $argv tcltest::loadTestedCommands +namespace import -force tcltest::test catch {destroy .t} text .t -font {Courier -12} -width 20 -height 10 -- cgit v0.12