diff options
-rw-r--r-- | doc/text.n | 115 | ||||
-rw-r--r-- | generic/tkText.c | 103 | ||||
-rw-r--r-- | generic/tkText.h | 3 | ||||
-rw-r--r-- | generic/tkTextDisp.c | 98 | ||||
-rw-r--r-- | tests/text.test | 182 | ||||
-rw-r--r-- | tests/textDisp.test | 41 |
6 files changed, 504 insertions, 38 deletions
@@ -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 4edf652..436c9dd 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 |