summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorvincentdarley <vincentdarley>2005-02-14 23:00:29 (GMT)
committervincentdarley <vincentdarley>2005-02-14 23:00:29 (GMT)
commit6e1a009795f451583308dda6b41c6249b637933c (patch)
tree270430be093e44a3695b7cae46647d503a619044
parent496ad1df6abb7e07d1c616e01bb3715ad49ab695 (diff)
downloadtk-6e1a009795f451583308dda6b41c6249b637933c.zip
tk-6e1a009795f451583308dda6b41c6249b637933c.tar.gz
tk-6e1a009795f451583308dda6b41c6249b637933c.tar.bz2
fix to newline eliding in text widget
-rw-r--r--ChangeLog13
-rw-r--r--doc/text.n11
-rw-r--r--generic/tkText.c24
-rw-r--r--generic/tkText.h9
-rw-r--r--generic/tkTextBTree.c104
-rw-r--r--generic/tkTextDisp.c327
-rw-r--r--generic/tkTextIndex.c44
-rw-r--r--tests/textDisp.test40
-rw-r--r--tests/textIndex.test3
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 <vincentdarley@users.sourceforge.net>
+
+ * 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 <jeffh@ActiveState.com>
* 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