summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorfvogel <fvogelnew1@free.fr>2016-01-13 17:56:02 (GMT)
committerfvogel <fvogelnew1@free.fr>2016-01-13 17:56:02 (GMT)
commit2cabee819956e5a5b0293e228faa026d217a621a (patch)
tree389817d38d519b5fef209a81035602b81558669f
parent8f3dfc99087716125f466209364f3d24d98c07be (diff)
parentb69c5f3b5f46c17361a5cbf3676cad5ba0ac03be (diff)
downloadtk-2cabee819956e5a5b0293e228faa026d217a621a.zip
tk-2cabee819956e5a5b0293e228faa026d217a621a.tar.gz
tk-2cabee819956e5a5b0293e228faa026d217a621a.tar.bz2
TIP #438 (Ensure Line Metrics are Up-to-Date) accepted by vote
-rw-r--r--doc/text.n115
-rw-r--r--generic/tkText.c103
-rw-r--r--generic/tkText.h3
-rw-r--r--generic/tkTextDisp.c98
-rw-r--r--tests/text.test182
-rw-r--r--tests/textDisp.test41
6 files changed, 504 insertions, 38 deletions
diff --git a/doc/text.n b/doc/text.n
index 976503b..ac7803c 100644
--- a/doc/text.n
+++ b/doc/text.n
@@ -917,6 +917,83 @@ affected.
.PP
See below for the \fIpathName \fBpeer\fR widget command that controls the
creation of peer widgets.
+.SH "ASYNCHRONOUS UPDATE OF LINE HEIGHTS"
+.PP
+In order to maintain a responsive user-experience, the text widget calculates
+lines metrics (line heights in pixels) asynchronously. Because of this, some
+commands of the text widget may return wrong results if the asynchronous
+calculations are not finished at the time of calling. This applies to
+\fIpathName \fBcount -ypixels\fR and \fIpathName \fByview\fR.
+.PP
+Again for performance reasons, it would not be appropriate to let these
+commands always wait for the end of the update calculation each time they are
+called. In most use cases of these commands a more or less inaccurate result
+does not really matter compared to execution speed.
+.PP
+In case accurate result is needed (and if the text widget is managed by a
+geometry manager), one can resort to \fIpathName \fBsync\fR and \fIpathName
+\fBpendingsync\fR to control the synchronization of the view of text widgets.
+.PP
+The \fB<<WidgetViewSync>>\fR virtual event fires when the line heights of the
+text widget becomes obsolete (due to some editing command or configuration
+change), and again when the internal data of the text widget are back in sync
+with the widget view. The detail field (%d substitution) is either true (when
+the widget is in sync) or false (when it is not).
+.PP
+\fIpathName \fBsync\fR, \fIpathName \fBpendingsync\fR and
+\fB<<WidgetViewSync>>\fR apply to each text widget independently of its peers.
+.PP
+Examples of use:
+.CS
+## Example 1:
+# immediately complete line metrics at any cost (GUI unresponsive)
+$w sync
+$w yview moveto $fraction
+
+## Example 2:
+# synchronously wait for up-to-date line metrics (GUI responsive)
+# before executing the scheduled command, but don't block execution flow
+$w sync -command [list $w yview moveto $fraction]
+
+## Example 3:
+# init
+set yud($w) 0
+proc updateaction w {
+\&set ::yud($w) 1
+\&# any other update action here...
+}
+# runtime, synchronously wait for up-to-date line metrics (GUI responsive)
+$w sync -command [list updateaction $w]
+vwait yud($w)
+$w yview moveto $fraction
+
+## Example 4:
+# init
+set todo($w) {}
+proc updateaction w {
+\&foreach cmd $::todo($w) {uplevel #0 $cmd}
+\&set todo($w) {}
+}
+# runtime
+lappend todo($w) [list $w yview moveto $fraction]
+$w sync -command [list updateaction $w]
+
+## Example 5:
+# init
+set todo($w) {}
+bind $w <<WidgetViewSync>> {
+\&if {%d} {
+\&\&foreach cmd $todo(%W) {eval $cmd}
+\&\&set todo(%W) {}
+\&}
+}
+# runtime
+if {![$w pendingsync]} {
+\&$w yview moveto $fraction
+} else {
+\&lappend todo($w) [list $w yview moveto $fraction]
+}
+.CE
.SH "WIDGET COMMAND"
.PP
The \fBtext\fR command creates a new Tcl command whose name is the same as the
@@ -981,7 +1058,9 @@ if the text widget is managed by a geometry manager), then all subsequent
options ensure that any possible out of date information is recalculated.
This currently only has any effect for the \fB\-ypixels\fR count (which, if
\fB\-update\fR is not given, will use the text widget's current cached value
-for each line). The count options are interpreted as follows:
+for each line). This \fB\-update\fR option is obsoleted by \fIpathName
+\fBsync\fR, \fIpathName \fBpendingsync\fR and \fB<<WidgetViewSync>>\fR. The
+count options are interpreted as follows:
.RS
.IP \fB\-chars\fR
count all characters, whether elided or not. Do not count embedded windows or
@@ -1344,13 +1423,16 @@ Returns a list of peers of this widget (this does not include the widget
itself). The order within this list is undefined.
.RE
.TP
-\fIpathName \fBreplace \fIindex1 index2 chars\fR ?\fItagList chars tagList ...\fR?
-.
-Replaces the range of characters between \fIindex1\fR and \fIindex2\fR with
-the given characters and tags. See the section on \fIpathName \fBinsert\fR for
-an explanation of the handling of the \fItagList...\fR arguments, and the
-section on \fIpathName \fBdelete\fR for an explanation of the handling of the
-indices. If \fIindex2\fR corresponds to an index earlier in the text than
+\fIpathName \fBpendingsync\fR
+Returns 1 if the line heights calculations are not up-to-date, 0 otherwise.
+.TP
+\fIpathName \fBreplace\fR \fIindex1 index2 chars\fR ?\fItagList chars tagList ...\fR?
+Replaces the range of characters between \fIindex1\fR and \fIindex2\fR
+with the given characters and tags. See the section on \fIpathName
+\fBinsert\fR for an explanation of the handling of the \fItagList...\fR
+arguments, and the section on \fIpathName
+\fBdelete\fR for an explanation of the handling of the indices. If
+\fIindex2\fR corresponds to an index earlier in the text than
\fIindex1\fR, an error will be generated.
.RS
.PP
@@ -1523,6 +1605,23 @@ the view just enough to make \fIindex\fR visible at the edge of the window.
If \fIindex\fR is far out of view, then the command centers \fIindex\fR in the
window.
.TP
+\fIpathName \fBsync\fR ?\fB-command \fIcommand\fR?
+Controls the synchronization of the view of the text widget.
+.RS
+.TP
+\fIpathName \fBsync\fR
+Immediately brings the line metrics up-to-date by forcing computation of any
+outdated line heights. The command returns immediately if there is no such
+outdated line heights, otherwise it returns only at the end of the computation.
+The command returns an empty string.
+.TP
+\fIpathName \fBsync -command \fIcommand\fR
+Schedules \fIcommand\fR to be executed (by the event loop) exactly once as soon
+as all line heights are up-to-date. If there are no pending line metrics
+calculations, the scheduling is immediate. The command returns the empty
+string. \fBbgerror\fR is called on \fIcommand\fR failure.
+.RE
+.TP
\fIpathName \fBtag \fIoption \fR?\fIarg arg ...\fR?
.
This command is used to manipulate tags. The exact behavior of the command
diff --git a/generic/tkText.c b/generic/tkText.c
index a713e7e..3079417 100644
--- a/generic/tkText.c
+++ b/generic/tkText.c
@@ -402,6 +402,7 @@ static Tcl_Obj * TextGetText(const TkText *textPtr,
const TkTextIndex *index2, int visibleOnly);
static void GenerateModifiedEvent(TkText *textPtr);
static void UpdateDirtyFlag(TkSharedText *sharedPtr);
+static void RunAfterSyncCmd(ClientData clientData);
static void TextPushUndoAction(TkText *textPtr,
Tcl_Obj *undoString, int insert,
const TkTextIndex *index1Ptr,
@@ -702,15 +703,16 @@ TextWidgetObjCmd(
static const char *const optionStrings[] = {
"bbox", "cget", "compare", "configure", "count", "debug", "delete",
"dlineinfo", "dump", "edit", "get", "image", "index", "insert",
- "mark", "peer", "replace", "scan", "search", "see", "tag", "window",
- "xview", "yview", NULL
+ "mark", "peer", "pendingsync", "replace", "scan", "search",
+ "see", "sync", "tag", "window", "xview", "yview", NULL
};
enum options {
TEXT_BBOX, TEXT_CGET, TEXT_COMPARE, TEXT_CONFIGURE, TEXT_COUNT,
TEXT_DEBUG, TEXT_DELETE, TEXT_DLINEINFO, TEXT_DUMP, TEXT_EDIT,
TEXT_GET, TEXT_IMAGE, TEXT_INDEX, TEXT_INSERT, TEXT_MARK,
- TEXT_PEER, TEXT_REPLACE, TEXT_SCAN, TEXT_SEARCH, TEXT_SEE,
- TEXT_TAG, TEXT_WINDOW, TEXT_XVIEW, TEXT_YVIEW
+ TEXT_PEER, TEXT_PENDINGSYNC, TEXT_REPLACE, TEXT_SCAN,
+ TEXT_SEARCH, TEXT_SEE, TEXT_SYNC, TEXT_TAG, TEXT_WINDOW,
+ TEXT_XVIEW, TEXT_YVIEW
};
if (objc < 2) {
@@ -1392,6 +1394,16 @@ TextWidgetObjCmd(
case TEXT_PEER:
result = TextPeerCmd(textPtr, interp, objc, objv);
break;
+ case TEXT_PENDINGSYNC: {
+ if (objc != 2) {
+ Tcl_WrongNumArgs(interp, 2, objv, NULL);
+ result = TCL_ERROR;
+ goto done;
+ }
+ Tcl_SetObjResult(interp,
+ Tcl_NewBooleanObj(TkTextPendingsync(textPtr)));
+ break;
+ }
case TEXT_REPLACE: {
const TkTextIndex *indexFromPtr, *indexToPtr;
@@ -1506,6 +1518,39 @@ TextWidgetObjCmd(
case TEXT_SEE:
result = TkTextSeeCmd(textPtr, interp, objc, objv);
break;
+ case TEXT_SYNC: {
+ if (objc == 4) {
+ Tcl_Obj *cmd = objv[3];
+ const char *option = Tcl_GetString(objv[2]);
+ if (strncmp(option, "-command", objv[2]->length)) {
+ Tcl_AppendResult(interp, "wrong option \"", option, "\": should be \"-command\"", NULL);
+ result = TCL_ERROR;
+ goto done;
+ }
+ Tcl_IncrRefCount(cmd);
+ if (TkTextPendingsync(textPtr)) {
+ if (textPtr->afterSyncCmd) {
+ Tcl_DecrRefCount(textPtr->afterSyncCmd);
+ }
+ textPtr->afterSyncCmd = cmd;
+ } else {
+ textPtr->afterSyncCmd = cmd;
+ Tcl_DoWhenIdle(RunAfterSyncCmd, (ClientData) textPtr);
+ }
+ break;
+ } else if (objc != 2) {
+ Tcl_WrongNumArgs(interp, 2, objv, "?-command command?");
+ result = TCL_ERROR;
+ goto done;
+ }
+ if (textPtr->afterSyncCmd) {
+ Tcl_DecrRefCount(textPtr->afterSyncCmd);
+ }
+ textPtr->afterSyncCmd = NULL;
+ TkTextUpdateLineMetrics(textPtr, 1,
+ TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr), -1);
+ break;
+ }
case TEXT_TAG:
result = TkTextTagCmd(textPtr, interp, objc, objv);
break;
@@ -1995,6 +2040,10 @@ DestroyText(
textPtr->tkwin = NULL;
textPtr->refCount--;
Tcl_DeleteCommandFromToken(textPtr->interp, textPtr->widgetCmd);
+ if (textPtr->afterSyncCmd){
+ Tcl_DecrRefCount(textPtr->afterSyncCmd);
+ textPtr->afterSyncCmd = NULL;
+ }
if (textPtr->refCount == 0) {
ckfree(textPtr);
}
@@ -5368,6 +5417,52 @@ UpdateDirtyFlag(
/*
*----------------------------------------------------------------------
*
+ * RunAfterSyncCmd --
+ *
+ * This function is called by the event loop and executes the command
+ * scheduled by [.text sync -command $cmd].
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Anything may happen, depending on $cmd contents.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+RunAfterSyncCmd(
+ ClientData clientData) /* Information about text widget. */
+{
+ register TkText *textPtr = (TkText *) clientData;
+ int code;
+
+ if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)) {
+ /*
+ * The widget has been deleted. Don't do anything.
+ */
+
+ if (--textPtr->refCount == 0) {
+ ckfree((char *) textPtr);
+ }
+ return;
+ }
+
+ Tcl_Preserve((ClientData) textPtr->interp);
+ code = Tcl_EvalObjEx(textPtr->interp, textPtr->afterSyncCmd, TCL_EVAL_GLOBAL);
+ if (code == TCL_ERROR) {
+ Tcl_AddErrorInfo(textPtr->interp, "\n (text sync)");
+ Tcl_BackgroundError(textPtr->interp);
+ }
+ Tcl_Release((ClientData) textPtr->interp);
+ Tcl_DecrRefCount(textPtr->afterSyncCmd);
+ textPtr->afterSyncCmd = NULL;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
* SearchPerform --
*
* Overall control of search process. Is given a pattern, a starting
diff --git a/generic/tkText.h b/generic/tkText.h
index 78a99a9..fc92644 100644
--- a/generic/tkText.h
+++ b/generic/tkText.h
@@ -785,6 +785,8 @@ typedef struct TkText {
* statements. */
int autoSeparators; /* Non-zero means the separators will be
* inserted automatically. */
+ Tcl_Obj *afterSyncCmd; /* Command to be executed when lines are up to
+ * date */
} TkText;
/*
@@ -1109,6 +1111,7 @@ MODULE_SCOPE int TkTextMarkNameToIndex(TkText *textPtr,
MODULE_SCOPE void TkTextMarkSegToIndex(TkText *textPtr,
TkTextSegment *markPtr, TkTextIndex *indexPtr);
MODULE_SCOPE void TkTextEventuallyRepick(TkText *textPtr);
+MODULE_SCOPE Bool TkTextPendingsync(TkText *textPtr);
MODULE_SCOPE void TkTextPickCurrent(TkText *textPtr, XEvent *eventPtr);
MODULE_SCOPE void TkTextPixelIndex(TkText *textPtr, int x, int y,
TkTextIndex *indexPtr, int *nearest);
diff --git a/generic/tkTextDisp.c b/generic/tkTextDisp.c
index a57c24b..18b373f 100644
--- a/generic/tkTextDisp.c
+++ b/generic/tkTextDisp.c
@@ -591,6 +591,7 @@ static int TextGetScrollInfoObj(Tcl_Interp *interp,
Tcl_Obj *const objv[], double *dblPtr,
int *intPtr);
static void AsyncUpdateLineMetrics(ClientData clientData);
+static void GenerateWidgetViewSyncEvent(TkText *textPtr, Bool InSync);
static void AsyncUpdateYScrollbar(ClientData clientData);
static int IsStartOfNotMergedLine(TkText *textPtr,
CONST TkTextIndex *indexPtr);
@@ -2941,6 +2942,8 @@ AsyncUpdateLineMetrics(
lineNum = TkTextUpdateLineMetrics(textPtr, lineNum,
dInfoPtr->lastMetricUpdateLine, 256);
+ dInfoPtr->currentMetricUpdateLine = lineNum;
+
if (tkTextDebug) {
char buffer[2 * TCL_INTEGER_SPACE + 1];
@@ -2958,16 +2961,37 @@ AsyncUpdateLineMetrics(
/*
* 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).
+ * above). If there is a registered aftersync command, run that first.
*/
+ if (textPtr->afterSyncCmd) {
+ int code;
+ Tcl_Preserve((ClientData) textPtr->interp);
+ code = Tcl_EvalObjEx(textPtr->interp, textPtr->afterSyncCmd,
+ TCL_EVAL_GLOBAL);
+ if (code == TCL_ERROR) {
+ Tcl_AddErrorInfo(textPtr->interp, "\n (text sync)");
+ Tcl_BackgroundError(textPtr->interp);
+ }
+ Tcl_Release((ClientData) textPtr->interp);
+ Tcl_DecrRefCount(textPtr->afterSyncCmd);
+ textPtr->afterSyncCmd = NULL;
+ }
+
+ /*
+ * Fire the <<WidgetViewSync>> event since the widget view is in sync
+ * with its internal data (actually it will be after the next trip
+ * through the event loop, because the widget redraws at idle-time).
+ */
+
+ GenerateWidgetViewSyncEvent(textPtr, 1);
+
textPtr->refCount--;
if (textPtr->refCount == 0) {
ckfree(textPtr);
}
return;
}
- dInfoPtr->currentMetricUpdateLine = lineNum;
/*
* Re-arm the timer. We already have a refCount on the text widget so no
@@ -2981,6 +3005,45 @@ AsyncUpdateLineMetrics(
/*
*----------------------------------------------------------------------
*
+ * GenerateWidgetViewSyncEvent --
+ *
+ * Send the <<WidgetViewSync>> event related to the text widget
+ * line metrics asynchronous update.
+ * This is equivalent to:
+ * event generate $textWidget <<WidgetViewSync>> -detail $s
+ * where $s is the sync status: true (when the widget view is in
+ * sync with its internal data) or false (when it is not).
+ *
+ * Results:
+ * None
+ *
+ * Side effects:
+ * If corresponding bindings are present, they will trigger.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+GenerateWidgetViewSyncEvent(
+ TkText *textPtr, /* Information about text widget. */
+ Bool InSync) /* True if in sync, false otherwise */
+{
+ union {XEvent general; XVirtualEvent virtual;} event;
+
+ memset(&event, 0, sizeof(event));
+ event.general.xany.type = VirtualEvent;
+ event.general.xany.serial = NextRequest(Tk_Display(textPtr->tkwin));
+ event.general.xany.send_event = False;
+ event.general.xany.window = Tk_WindowId(textPtr->tkwin);
+ event.general.xany.display = Tk_Display(textPtr->tkwin);
+ event.virtual.name = Tk_GetUid("WidgetViewSync");
+ event.virtual.user_data = Tcl_NewBooleanObj(InSync);
+ Tk_HandleEvent(&event.general);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
* TkTextUpdateLineMetrics --
*
* This function updates the pixel height calculations of a range of
@@ -3353,6 +3416,7 @@ TextInvalidateLineMetrics(
textPtr->refCount++;
dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1,
AsyncUpdateLineMetrics, textPtr);
+ GenerateWidgetViewSyncEvent(textPtr, 0);
}
}
@@ -5064,6 +5128,7 @@ TkTextRelayoutWindow(
textPtr->refCount++;
dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1,
AsyncUpdateLineMetrics, textPtr);
+ GenerateWidgetViewSyncEvent(textPtr, 0);
}
}
}
@@ -6066,6 +6131,35 @@ TkTextYviewCmd(
/*
*--------------------------------------------------------------
*
+ * TkTextPendingsync --
+ *
+ * This function checks if any line heights are not up-to-date.
+ *
+ * Results:
+ * Returns a boolean true if it is the case, or false if all line
+ * heights are up-to-date.
+ *
+ * Side effects:
+ * None.
+ *
+ *--------------------------------------------------------------
+ */
+
+Bool
+TkTextPendingsync(
+ TkText *textPtr) /* Information about text widget. */
+{
+ TextDInfo *dInfoPtr = textPtr->dInfoPtr;
+
+ return (
+ ((dInfoPtr->metricEpoch == -1) &&
+ (dInfoPtr->lastMetricUpdateLine == dInfoPtr->currentMetricUpdateLine)) ?
+ 0 : 1);
+}
+
+/*
+ *--------------------------------------------------------------
+ *
* TkTextScanCmd --
*
* This function is invoked to process the "scan" option for the widget
diff --git a/tests/text.test b/tests/text.test
index 7ade29a..52a21af 100644
--- a/tests/text.test
+++ b/tests/text.test
@@ -925,7 +925,7 @@ test text-3.2 {TextWidgetCmd procedure} -setup {
.t gorp 1.0 z 1.2
} -cleanup {
destroy .t
-} -returnCodes {error} -result {bad option "gorp": must be bbox, cget, compare, configure, count, debug, delete, dlineinfo, dump, edit, get, image, index, insert, mark, peer, replace, scan, search, see, tag, window, xview, or yview}
+} -returnCodes {error} -result {bad option "gorp": must be bbox, cget, compare, configure, count, debug, delete, dlineinfo, dump, edit, get, image, index, insert, mark, peer, pendingsync, replace, scan, search, see, sync, tag, window, xview, or yview}
test text-4.1 {TextWidgetCmd procedure, "bbox" option} -setup {
text .t
@@ -1147,7 +1147,7 @@ Line 7"
.t co 1.0 z 1.2
} -cleanup {
destroy .t
-} -returnCodes {error} -result {ambiguous option "co": must be bbox, cget, compare, configure, count, debug, delete, dlineinfo, dump, edit, get, image, index, insert, mark, peer, replace, scan, search, see, tag, window, xview, or yview}
+} -returnCodes {error} -result {ambiguous option "co": must be bbox, cget, compare, configure, count, debug, delete, dlineinfo, dump, edit, get, image, index, insert, mark, peer, pendingsync, replace, scan, search, see, sync, tag, window, xview, or yview}
# "configure" option is already covered above
test text-7.1 {TextWidgetCmd procedure, "debug" option} -setup {
@@ -1163,7 +1163,7 @@ test text-7.2 {TextWidgetCmd procedure, "debug" option} -setup {
.t de 0 1
} -cleanup {
destroy .t
-} -returnCodes {error} -result {ambiguous option "de": must be bbox, cget, compare, configure, count, debug, delete, dlineinfo, dump, edit, get, image, index, insert, mark, peer, replace, scan, search, see, tag, window, xview, or yview}
+} -returnCodes {error} -result {ambiguous option "de": must be bbox, cget, compare, configure, count, debug, delete, dlineinfo, dump, edit, get, image, index, insert, mark, peer, pendingsync, replace, scan, search, see, sync, tag, window, xview, or yview}
test text-7.3 {TextWidgetCmd procedure, "debug" option} -setup {
text .t
} -body {
@@ -2686,7 +2686,7 @@ test text-9.2.47 {TextWidgetCmd procedure, "count" option} -setup {
# next line to be fully sure that asynchronous line heights calculation is
# up-to-date otherwise this test may fail (depending on the computer
# performance), especially when the . toplevel has small height
- .t count -update -ypixels 1.0 end
+ .t sync
set y1 [lindex [.t yview] 1]
.t count -displaylines 5.0 11.0
set y2 [lindex [.t yview] 1]
@@ -2882,6 +2882,176 @@ test text-11.9 {counting with tag priority eliding} -setup {
destroy .t
} -result {1 0 0 1 0 2.0 4.0 4.0 4.0 3.0 3.0 3.0 2.0 1.0 1.0}
+test text-11a.1 {TextWidgetCmd procedure, "pendingsync" option} -setup {
+ destroy .yt
+} -body {
+ text .yt
+ list [catch {.yt pendingsync mytext} msg] $msg
+} -cleanup {
+ destroy .yt
+} -result {1 {wrong # args: should be ".yt pendingsync"}}
+test text-11a.2 {TextWidgetCmd procedure, "pendingsync" option} -setup {
+ destroy .top.yt .top
+} -body {
+ toplevel .top
+ pack [text .top.yt]
+ set content {}
+ for {set i 1} {$i < 300} {incr i} {
+ append content [string repeat "$i " 15] \n
+ }
+ .top.yt insert 1.0 $content
+ # wait for end of line metrics calculation to get correct $fraction1
+ # as a reference
+ while {[.top.yt pendingsync]} {update}
+ .top.yt yview moveto 1
+ set fraction1 [lindex [.top.yt yview] 0]
+ set res [expr {$fraction1 > 0}]
+ .top.yt delete 1.0 end
+ .top.yt insert 1.0 $content
+ # ensure the test is relevant
+ lappend res [.top.yt pendingsync]
+ # asynchronously wait for completion of line metrics calculation
+ while {[.top.yt pendingsync]} {update}
+ .top.yt yview moveto $fraction1
+ set fraction2 [lindex [.top.yt yview] 0]
+ lappend res [expr {$fraction1 == $fraction2}]
+} -cleanup {
+ destroy .top.yt .top
+} -result {1 1 1}
+
+test text-11a.11 {TextWidgetCmd procedure, "sync" option} -setup {
+ destroy .yt
+} -body {
+ text .yt
+ list [catch {.yt sync mytext} msg] $msg
+} -cleanup {
+ destroy .yt
+} -result {1 {wrong # args: should be ".yt sync ?-command command?"}}
+test text-11a.12 {TextWidgetCmd procedure, "sync" option} -setup {
+ destroy .top.yt .top
+} -body {
+ toplevel .top
+ pack [text .top.yt]
+ set content {}
+ for {set i 1} {$i < 30} {incr i} {
+ append content [string repeat "$i " 15] \n
+ }
+ .top.yt insert 1.0 $content
+ # wait for end of line metrics calculation to get correct $fraction1
+ # as a reference
+ .top.yt sync
+ .top.yt yview moveto 1
+ set fraction1 [lindex [.top.yt yview] 0]
+ set res [expr {$fraction1 > 0}]
+ # first case: do not wait for completion of line metrics calculation
+ .top.yt delete 1.0 end
+ .top.yt insert 1.0 $content
+ .top.yt yview moveto $fraction1
+ set fraction2 [lindex [.top.yt yview] 0]
+ lappend res [expr {$fraction1 == $fraction2}]
+ # second case: wait for completion of line metrics calculation
+ .top.yt delete 1.0 end
+ .top.yt insert 1.0 $content
+ .top.yt sync
+ .top.yt yview moveto $fraction1
+ set fraction2 [lindex [.top.yt yview] 0]
+ lappend res [expr {$fraction1 == $fraction2}]
+} -cleanup {
+ destroy .top.yt .top
+} -result {1 0 1}
+
+test text-11a.21 {TextWidgetCmd procedure, "sync" option with -command} -setup {
+ destroy .yt
+} -body {
+ text .yt
+ list [catch {.yt sync -comx foo} msg] $msg
+} -cleanup {
+ destroy .yt
+} -result {1 {wrong option "-comx": should be "-command"}}
+test text-11a.22 {TextWidgetCmd procedure, "sync" option with -command} -setup {
+ destroy .top.yt .top
+} -body {
+ set res {}
+ set ::x 0
+ toplevel .top
+ pack [text .top.yt]
+ set content {}
+ for {set i 1} {$i < 30} {incr i} {
+ append content [string repeat "$i " 15] \n
+ }
+ .top.yt insert 1.0 $content
+ # first case: line metrics calculation still running when launching 'sync -command'
+ lappend res [.top.yt pendingsync]
+ .top.yt sync -command [list set ::x 1]
+ lappend res $::x
+ # now finish line metrics calculations
+ while {[.top.yt pendingsync]} {update}
+ lappend res [.top.yt pendingsync] $::x
+ # second case: line metrics calculation completed when launching 'sync -command'
+ .top.yt sync -command [list set ::x 2]
+ lappend res $::x
+ vwait ::x
+ lappend res $::x
+} -cleanup {
+ destroy .top.yt .top
+} -result {1 0 0 1 1 2}
+
+test text-11a.31 {"<<WidgetViewSync>>" event} -setup {
+ destroy .top.yt .top
+} -body {
+ toplevel .top
+ pack [text .top.yt]
+ set content {}
+ for {set i 1} {$i < 300} {incr i} {
+ append content [string repeat "$i " 15] \n
+ }
+ .top.yt insert 1.0 $content
+ update
+ bind .top.yt <<WidgetViewSync>> { if {%d} {set yud(%W) 1} }
+ # wait for end of line metrics calculation to get correct $fraction1
+ # as a reference
+ if {[.top.yt pendingsync]} {vwait yud(.top.yt)}
+ .top.yt yview moveto 1
+ set fraction1 [lindex [.top.yt yview] 0]
+ set res [expr {$fraction1 > 0}]
+ .top.yt delete 1.0 end
+ .top.yt insert 1.0 $content
+ # synchronously wait for completion of line metrics calculation
+ # and ensure the test is relevant
+ set waited 0
+ if {[.top.yt pendingsync]} {set waited 1 ; vwait yud(.top.yt)}
+ lappend res $waited
+ .top.yt yview moveto $fraction1
+ set fraction2 [lindex [.top.yt yview] 0]
+ lappend res [expr {$fraction1 == $fraction2}]
+} -cleanup {
+ destroy .top.yt .top
+} -result {1 1 1}
+
+test text-11a.41 {"sync" "pendingsync" and <<WidgetViewSync>>} -setup {
+ destroy .top.yt .top
+} -body {
+ set res {}
+ toplevel .top
+ pack [text .top.yt]
+ set content {}
+ for {set i 1} {$i < 300} {incr i} {
+ append content [string repeat "$i " 50] \n
+ }
+ bind .top.yt <<WidgetViewSync>> {lappend res Sync:%d}
+ .top.yt insert 1.0 $content
+ vwait res ; # event dealt with by the event loop, with %d==0 i.e. we're out of sync
+ # ensure the test is relevant
+ lappend res "Pending:[.top.yt pendingsync]"
+ # - <<WidgetViewSync>> fires when sync returns if there was pending syncs
+ # - there is no more any pending sync after running 'sync'
+ .top.yt sync
+ vwait res ; # event dealt with by the event loop, with %d==1 i.e. we're in sync again
+ lappend res "Pending:[.top.yt pendingsync]"
+ set res
+} -cleanup {
+ destroy .top.yt .top
+} -result {Sync:0 Pending:1 Sync:1 Pending:0}
test text-12.1 {TextWidgetCmd procedure, "index" option} -setup {
text .t
@@ -2903,7 +3073,7 @@ test text-12.3 {TextWidgetCmd procedure, "index" option} -setup {
.t in a b
} -cleanup {
destroy .t
-} -returnCodes {error} -result {ambiguous option "in": must be bbox, cget, compare, configure, count, debug, delete, dlineinfo, dump, edit, get, image, index, insert, mark, peer, replace, scan, search, see, tag, window, xview, or yview}
+} -returnCodes {error} -result {ambiguous option "in": must be bbox, cget, compare, configure, count, debug, delete, dlineinfo, dump, edit, get, image, index, insert, mark, peer, pendingsync, replace, scan, search, see, sync, tag, window, xview, or yview}
test text-12.4 {TextWidgetCmd procedure, "index" option} -setup {
text .t
} -body {
@@ -6710,7 +6880,7 @@ test text-33.2 {TextWidgetCmd procedure, "peer" option} -setup {
test text-33.3 {TextWidgetCmd procedure, "peer" option} -setup {
text .t
} -body {
- .t p names
+ .t pee names
} -cleanup {
destroy .t
} -returnCodes {ok} -result {}
diff --git a/tests/textDisp.test b/tests/textDisp.test
index ac3aee0..caba769 100644
--- a/tests/textDisp.test
+++ b/tests/textDisp.test
@@ -4197,29 +4197,34 @@ test textDisp-33.5 {bold or italic fonts} win {
} {italic font measurement ok}
destroy .tt
-test textDisp-34.1 {Text widgets multi-scrolling problem: Bug 2677890} -setup {
- pack [text .t1 -width 10 -yscrollcommand {.sy set}] \
- [ttk::scrollbar .sy -orient vertical -command {.t1 yview}] \
- -side left -fill both
- bindtags .sy {}; # No clicky!
+test textDisp-34.1 {Line heights recalculation problem: bug 2677890} -setup {
+ pack [text .t1] -expand 1 -fill both
set txt ""
- for {set i 0} {$i < 99} {incr i} {
- lappend txt "$i" [list pc $i] "\n" ""
+ for {set i 1} {$i < 100} {incr i} {
+ append txt "Line $i\n"
}
set result {}
} -body {
- .t1 insert end {*}$txt
- update
- lappend result [.sy get]
- .t1 replace 6.0 6.0+1c "*"
- lappend result [.sy get]
- after 0 {lappend result [.sy get]}
- after 1000 {lappend result [.sy get]}
- vwait result;vwait result
- return $result
+ .t1 insert end $txt
+ .t1 debug 1
+ set ge [winfo geometry .]
+ scan $ge "%dx%d+%d+%d" width height left top
+ update
+ .t1 sync
+ set negative 0
+ bind .t1 <<WidgetViewSync>> { if {%d < 0} {set negative 1} }
+ # Without the fix for bug 2677890, changing the width of the toplevel
+ # will launch recomputation of the line heights, but will produce negative
+ # number of still remaining outdated lines, which is obviously wrong.
+ # Thus we use this way to check for regression regarding bug 2677890,
+ # i.e. to check that the fix for this bug really is still in.
+ wm geometry . "[expr {$width * 2}]x$height+$left+$top"
+ update
+ .t1 sync
+ set negative
} -cleanup {
- destroy .t1 .sy
-} -result {{0.0 0.24} {0.0 0.24} {0.0 0.24} {0.0 0.24}}
+ destroy .t1
+} -result {0}
test textDisp-35.1 {Init value of charHeight - Dancing scrollbar bug 1499165} -setup {
pack [text .t1] -fill both -expand y -side left