Index: doc/text.n ================================================================== --- doc/text.n +++ doc/text.n @@ -915,10 +915,87 @@ text widget will not cause any other peers to be deleted, or otherwise 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<>\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<>\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 <> { +\&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 path name of the text's window. This command may be used to invoke various operations on the widget. It has the following general form: @@ -979,11 +1056,13 @@ additional possible option \fB\-update\fR which is a modifier. If given (and 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<>\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 images. .IP \fB\-displaychars\fR @@ -1342,17 +1421,20 @@ . 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 The deletion and insertion are arranged so that no unnecessary scrolling of the window or movement of insertion cursor occurs. In addition the undo/redo @@ -1520,10 +1602,27 @@ completely visible. If \fIindex\fR is already visible then the command does nothing. If \fIindex\fR is a short distance out of view, the command adjusts 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 depends on the \fIoption\fR argument that follows the \fBtag\fR argument. The Index: generic/tkText.c ================================================================== --- generic/tkText.c +++ generic/tkText.c @@ -400,10 +400,11 @@ static Tcl_Obj * TextGetText(const TkText *textPtr, const TkTextIndex *index1, 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, const TkTextIndex *index2Ptr); static int TextSearchIndexInLine(const SearchSpec *searchSpecPtr, @@ -700,19 +701,20 @@ int index; 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) { Tcl_WrongNumArgs(interp, 1, objv, "option ?arg ...?"); return TCL_ERROR; @@ -1390,10 +1392,20 @@ result = TkTextMarkCmd(textPtr, interp, objc, objv); break; 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; if (objc < 5) { Tcl_WrongNumArgs(interp, 2, objv, @@ -1504,10 +1516,43 @@ result = TextSearchCmd(textPtr, interp, objc, objv); break; 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; case TEXT_WINDOW: result = TkTextWindowCmd(textPtr, interp, objc, objv); @@ -1993,10 +2038,14 @@ } 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); } } @@ -5362,10 +5411,56 @@ textPtr = textPtr->next) { GenerateModifiedEvent(textPtr); } } } + +/* + *---------------------------------------------------------------------- + * + * 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 -- Index: generic/tkText.h ================================================================== --- generic/tkText.h +++ generic/tkText.h @@ -783,10 +783,12 @@ int maxUndo; /* The maximum depth of the undo stack * expressed as the maximum number of compound * 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; /* * Flag values for TkText records: * @@ -1107,10 +1109,11 @@ MODULE_SCOPE int TkTextMarkNameToIndex(TkText *textPtr, const char *name, TkTextIndex *indexPtr); 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); MODULE_SCOPE Tcl_Obj * TkTextNewIndexObj(TkText *textPtr, const TkTextIndex *indexPtr); Index: generic/tkTextDisp.c ================================================================== --- generic/tkTextDisp.c +++ generic/tkTextDisp.c @@ -589,10 +589,11 @@ static int TextGetScrollInfoObj(Tcl_Interp *interp, TkText *textPtr, int objc, 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); /* @@ -2939,10 +2940,12 @@ */ lineNum = TkTextUpdateLineMetrics(textPtr, lineNum, dInfoPtr->lastMetricUpdateLine, 256); + dInfoPtr->currentMetricUpdateLine = lineNum; + if (tkTextDebug) { char buffer[2 * TCL_INTEGER_SPACE + 1]; sprintf(buffer, "%d %d", lineNum, dInfoPtr->lastMetricUpdateLine); LOG("tk_textInvalidateLine", buffer); @@ -2956,29 +2959,89 @@ if (dInfoPtr->metricEpoch == -1 && lineNum == dInfoPtr->lastMetricUpdateLine) { /* * We have looped over all lines, so we're done. We must release our * refCount on the widget (the timer token was already set to NULL - * above). + * 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 <> 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 * need to adjust that. */ dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1, AsyncUpdateLineMetrics, textPtr); } + +/* + *---------------------------------------------------------------------- + * + * GenerateWidgetViewSyncEvent -- + * + * Send the <> event related to the text widget + * line metrics asynchronous update. + * This is equivalent to: + * event generate $textWidget <> -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 -- @@ -3351,10 +3414,11 @@ if (dInfoPtr->lineUpdateTimer == NULL) { textPtr->refCount++; dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1, AsyncUpdateLineMetrics, textPtr); + GenerateWidgetViewSyncEvent(textPtr, 0); } } /* *---------------------------------------------------------------------- @@ -5062,10 +5126,11 @@ if (dInfoPtr->lineUpdateTimer == NULL) { textPtr->refCount++; dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1, AsyncUpdateLineMetrics, textPtr); + GenerateWidgetViewSyncEvent(textPtr, 0); } } } /* @@ -6060,10 +6125,39 @@ YScrollByLines(textPtr, count); break; } return TCL_OK; } + +/* + *-------------------------------------------------------------- + * + * 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 -- Index: tests/text.test ================================================================== --- tests/text.test +++ tests/text.test @@ -923,11 +923,11 @@ text .t } -body { .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 } -body { .t bbox @@ -1145,11 +1145,11 @@ !@#$% 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 { text .t } -body { @@ -1161,11 +1161,11 @@ text .t } -body { .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 { .t debug true .t deb @@ -2684,11 +2684,11 @@ .t tag add hidden 5.7 11.0 update # 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] .t count -displaylines 5.0 12.0 set y3 [lindex [.t yview] 1] @@ -2880,10 +2880,180 @@ lappend res [.t index "end -2 display chars"] } -cleanup { 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 {"<>" 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 <> { 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 <>} -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 <> {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]" + # - <> 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 } -body { .t index @@ -2901,11 +3071,11 @@ text .t } -body { .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 { .t index @xyz } -cleanup { @@ -6708,11 +6878,11 @@ destroy .t } -returnCodes {error} -result {wrong # args: should be ".t peer names"} test text-33.3 {TextWidgetCmd procedure, "peer" option} -setup { text .t } -body { - .t p names + .t pee names } -cleanup { destroy .t } -returnCodes {ok} -result {} test text-33.4 {TextWidgetCmd procedure, "peer" option} -setup { text .t Index: tests/textDisp.test ================================================================== --- tests/textDisp.test +++ tests/textDisp.test @@ -4195,33 +4195,38 @@ set result "italic font measurement ok" } } {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 + .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 <> { 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 - 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 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 .t insert end "[string repeat a\nb\nc\n 500000]THE END\n" set res {}