Tk Source Code

Changes On Branch tip-438
Login
Bounty program for improvements to Tcl and certain Tcl packages.

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Changes In Branch tip-438 Excluding Merge-Ins

This is equivalent to a diff from 3736983e to 9f400abe

2016-01-13
17:56
TIP #438 (Ensure Line Metrics are Up-to-Date) accepted by vote check-in: 15b7aa7e user: fvogel tags: trunk
2016-01-07
17:20
Prefix "system" of all Windows System Colors was documented check-in: 28b2e86a user: oehhar tags: trunk
13:08
rebase "tip-438" branch to latest trunk. Closed-Leaf check-in: 9f400abe user: jan.nijtmans tags: tip-438
2016-01-06
20:30
Fixed bug [3102228] - <<ListboxSelect>> doesn't fire when selection lost check-in: 3736983e user: fvogel tags: trunk
20:28
Fixed bug [3102228] - <<ListboxSelect>> doesn't fire when selection lost check-in: 142932e3 user: fvogel tags: core-8-5-branch
20:18
Fixed bug [1288433] - LisboxSelect event triggers when listbox state is disabled check-in: 087e635d user: fvogel tags: trunk
2016-01-05
17:00
Harmonized use of NULL for textPtr->afterSyncCmd check-in: 0eb8f1f5 user: fvogel tags: tip-438

Changes to doc/text.n.

   913    913   Note that peers are really peers. Deleting the
   914    914   .QW original
   915    915   text widget will not cause any other peers to be deleted, or otherwise
   916    916   affected.
   917    917   .PP
   918    918   See below for the \fIpathName \fBpeer\fR widget command that controls the
   919    919   creation of peer widgets.
          920  +.SH "ASYNCHRONOUS UPDATE OF LINE HEIGHTS"
          921  +.PP
          922  +In order to maintain a responsive user-experience, the text widget calculates
          923  +lines metrics (line heights in pixels) asynchronously. Because of this, some
          924  +commands of the text widget may return wrong results if the asynchronous
          925  +calculations are not finished at the time of calling. This applies to
          926  +\fIpathName \fBcount -ypixels\fR and \fIpathName \fByview\fR.
          927  +.PP
          928  +Again for performance reasons, it would not be appropriate to let these
          929  +commands always wait for the end of the update calculation each time they are
          930  +called. In most use cases of these commands a more or less inaccurate result
          931  +does not really matter compared to execution speed.
          932  +.PP
          933  +In case accurate result is needed (and if the text widget is managed by a
          934  +geometry manager), one can resort to \fIpathName \fBsync\fR and \fIpathName
          935  +\fBpendingsync\fR to control the synchronization of the view of text widgets.
          936  +.PP
          937  +The \fB<<WidgetViewSync>>\fR virtual event fires when the line heights of the
          938  +text widget becomes obsolete (due to some editing command or configuration
          939  +change), and again when the internal data of the text widget are back in sync
          940  +with the widget view. The detail field (%d substitution) is either true (when
          941  +the widget is in sync) or false (when it is not).
          942  +.PP
          943  +\fIpathName \fBsync\fR, \fIpathName \fBpendingsync\fR and
          944  +\fB<<WidgetViewSync>>\fR apply to each text widget independently of its peers.
          945  +.PP
          946  +Examples of use:
          947  +.CS
          948  +## Example 1:
          949  +# immediately complete line metrics at any cost (GUI unresponsive)
          950  +$w sync
          951  +$w yview moveto $fraction
          952  +
          953  +## Example 2:
          954  +# synchronously wait for up-to-date line metrics (GUI responsive)
          955  +# before executing the scheduled command, but don't block execution flow
          956  +$w sync -command [list $w yview moveto $fraction]
          957  +
          958  +## Example 3:
          959  +# init
          960  +set yud($w) 0
          961  +proc updateaction w {
          962  +\&set ::yud($w) 1
          963  +\&# any other update action here...
          964  +}
          965  +# runtime, synchronously wait for up-to-date line metrics (GUI responsive)
          966  +$w sync -command [list updateaction $w]
          967  +vwait yud($w)
          968  +$w yview moveto $fraction
          969  +
          970  +## Example 4:
          971  +# init
          972  +set todo($w) {}
          973  +proc updateaction w {
          974  +\&foreach cmd $::todo($w) {uplevel #0 $cmd}
          975  +\&set todo($w) {}
          976  +}
          977  +# runtime
          978  +lappend todo($w) [list $w yview moveto $fraction]
          979  +$w sync -command [list updateaction $w]
          980  +
          981  +## Example 5:
          982  +# init
          983  +set todo($w) {}
          984  +bind $w <<WidgetViewSync>> {
          985  +\&if {%d} {
          986  +\&\&foreach cmd $todo(%W) {eval $cmd}
          987  +\&\&set todo(%W) {}
          988  +\&}
          989  +}
          990  +# runtime
          991  +if {![$w pendingsync]} {
          992  +\&$w yview moveto $fraction
          993  +} else {
          994  +\&lappend todo($w) [list $w yview moveto $fraction]
          995  +}
          996  +.CE
   920    997   .SH "WIDGET COMMAND"
   921    998   .PP
   922    999   The \fBtext\fR command creates a new Tcl command whose name is the same as the
   923   1000   path name of the text's window. This command may be used to invoke various
   924   1001   operations on the widget. It has the following general form:
   925   1002   .CS
   926   1003   \fIpathName option \fR?\fIarg arg ...\fR?
................................................................................
   977   1054   \fB\-indices\fR, \fB\-lines\fR, \fB\-xpixels\fR and \fB\-ypixels\fR. The
   978   1055   default value, if no option is specified, is \fB\-indices\fR. There is an
   979   1056   additional possible option \fB\-update\fR which is a modifier. If given (and
   980   1057   if the text widget is managed by a geometry manager), then all subsequent
   981   1058   options ensure that any possible out of date information is recalculated.
   982   1059   This currently only has any effect for the \fB\-ypixels\fR count (which, if
   983   1060   \fB\-update\fR is not given, will use the text widget's current cached value
   984         -for each line). The count options are interpreted as follows:
         1061  +for each line). This \fB\-update\fR option is obsoleted by \fIpathName
         1062  +\fBsync\fR, \fIpathName \fBpendingsync\fR and \fB<<WidgetViewSync>>\fR.  The
         1063  +count options are interpreted as follows:
   985   1064   .RS
   986   1065   .IP \fB\-chars\fR
   987   1066   count all characters, whether elided or not. Do not count embedded windows or
   988   1067   images.
   989   1068   .IP \fB\-displaychars\fR
   990   1069   count all non-elided characters.
   991   1070   .IP \fB\-displayindices\fR
................................................................................
  1340   1419   .TP
  1341   1420   \fIpathName \fBpeer names\fR
  1342   1421   .
  1343   1422   Returns a list of peers of this widget (this does not include the widget
  1344   1423   itself). The order within this list is undefined.
  1345   1424   .RE
  1346   1425   .TP
  1347         -\fIpathName \fBreplace \fIindex1 index2 chars\fR ?\fItagList chars tagList ...\fR?
  1348         -.
  1349         -Replaces the range of characters between \fIindex1\fR and \fIindex2\fR with
  1350         -the given characters and tags. See the section on \fIpathName \fBinsert\fR for
  1351         -an explanation of the handling of the \fItagList...\fR arguments, and the
  1352         -section on \fIpathName \fBdelete\fR for an explanation of the handling of the
  1353         -indices. If \fIindex2\fR corresponds to an index earlier in the text than
         1426  +\fIpathName \fBpendingsync\fR
         1427  +Returns 1 if the line heights calculations are not up-to-date, 0 otherwise.
         1428  +.TP
         1429  +\fIpathName \fBreplace\fR \fIindex1 index2 chars\fR ?\fItagList chars tagList ...\fR?
         1430  +Replaces the range of characters between \fIindex1\fR and \fIindex2\fR
         1431  +with the given characters and tags.  See the section on \fIpathName
         1432  +\fBinsert\fR for an explanation of the handling of the \fItagList...\fR
         1433  +arguments, and the section on \fIpathName
         1434  +\fBdelete\fR for an explanation of the handling of the indices.  If
         1435  +\fIindex2\fR corresponds to an index earlier in the text than
  1354   1436   \fIindex1\fR, an error will be generated.
  1355   1437   .RS
  1356   1438   .PP
  1357   1439   The deletion and insertion are arranged so that no unnecessary scrolling of
  1358   1440   the window or movement of insertion cursor occurs. In addition the undo/redo
  1359   1441   stack are correctly modified, if undo operations are active in the text
  1360   1442   widget. The command returns an empty string.
................................................................................
  1518   1600   .
  1519   1601   Adjusts the view in the window so that the character given by \fIindex\fR is
  1520   1602   completely visible. If \fIindex\fR is already visible then the command does
  1521   1603   nothing. If \fIindex\fR is a short distance out of view, the command adjusts
  1522   1604   the view just enough to make \fIindex\fR visible at the edge of the window.
  1523   1605   If \fIindex\fR is far out of view, then the command centers \fIindex\fR in the
  1524   1606   window.
         1607  +.TP
         1608  +\fIpathName \fBsync\fR ?\fB-command \fIcommand\fR?
         1609  +Controls the synchronization of the view of the text widget.
         1610  +.RS
         1611  +.TP
         1612  +\fIpathName \fBsync\fR
         1613  +Immediately brings the line metrics up-to-date by forcing computation of any
         1614  +outdated line heights. The command returns immediately if there is no such
         1615  +outdated line heights, otherwise it returns only at the end of the computation.
         1616  +The command returns an empty string.
         1617  +.TP
         1618  +\fIpathName \fBsync -command \fIcommand\fR
         1619  +Schedules \fIcommand\fR to be executed (by the event loop) exactly once as soon
         1620  +as all line heights are up-to-date. If there are no pending line metrics
         1621  +calculations, the scheduling is immediate. The command returns the empty
         1622  +string. \fBbgerror\fR is called on \fIcommand\fR failure.
         1623  +.RE
  1525   1624   .TP
  1526   1625   \fIpathName \fBtag \fIoption \fR?\fIarg arg ...\fR?
  1527   1626   .
  1528   1627   This command is used to manipulate tags. The exact behavior of the command
  1529   1628   depends on the \fIoption\fR argument that follows the \fBtag\fR argument. The
  1530   1629   following forms of the command are currently supported:
  1531   1630   .RS

Changes to generic/tkText.c.

   398    398   static int		TextEditUndo(TkText *textPtr);
   399    399   static int		TextEditRedo(TkText *textPtr);
   400    400   static Tcl_Obj *	TextGetText(const TkText *textPtr,
   401    401   			    const TkTextIndex *index1,
   402    402   			    const TkTextIndex *index2, int visibleOnly);
   403    403   static void		GenerateModifiedEvent(TkText *textPtr);
   404    404   static void		UpdateDirtyFlag(TkSharedText *sharedPtr);
          405  +static void		RunAfterSyncCmd(ClientData clientData);
   405    406   static void		TextPushUndoAction(TkText *textPtr,
   406    407   			    Tcl_Obj *undoString, int insert,
   407    408   			    const TkTextIndex *index1Ptr,
   408    409   			    const TkTextIndex *index2Ptr);
   409    410   static int		TextSearchIndexInLine(const SearchSpec *searchSpecPtr,
   410    411   			    TkTextLine *linePtr, int byteIndex);
   411    412   static int		TextPeerCmd(TkText *textPtr, Tcl_Interp *interp,
................................................................................
   698    699       register TkText *textPtr = clientData;
   699    700       int result = TCL_OK;
   700    701       int index;
   701    702   
   702    703       static const char *const optionStrings[] = {
   703    704   	"bbox", "cget", "compare", "configure", "count", "debug", "delete",
   704    705   	"dlineinfo", "dump", "edit", "get", "image", "index", "insert",
   705         -	"mark", "peer", "replace", "scan", "search", "see", "tag", "window",
   706         -	"xview", "yview", NULL
          706  +	"mark", "peer", "pendingsync", "replace", "scan", "search",
          707  +	"see", "sync", "tag", "window", "xview", "yview", NULL
   707    708       };
   708    709       enum options {
   709    710   	TEXT_BBOX, TEXT_CGET, TEXT_COMPARE, TEXT_CONFIGURE, TEXT_COUNT,
   710    711   	TEXT_DEBUG, TEXT_DELETE, TEXT_DLINEINFO, TEXT_DUMP, TEXT_EDIT,
   711    712   	TEXT_GET, TEXT_IMAGE, TEXT_INDEX, TEXT_INSERT, TEXT_MARK,
   712         -	TEXT_PEER, TEXT_REPLACE, TEXT_SCAN, TEXT_SEARCH, TEXT_SEE,
   713         -	TEXT_TAG, TEXT_WINDOW, TEXT_XVIEW, TEXT_YVIEW
          713  +	TEXT_PEER, TEXT_PENDINGSYNC, TEXT_REPLACE, TEXT_SCAN,
          714  +	TEXT_SEARCH, TEXT_SEE, TEXT_SYNC, TEXT_TAG, TEXT_WINDOW,
          715  +	TEXT_XVIEW, TEXT_YVIEW
   714    716       };
   715    717   
   716    718       if (objc < 2) {
   717    719   	Tcl_WrongNumArgs(interp, 1, objv, "option ?arg ...?");
   718    720   	return TCL_ERROR;
   719    721       }
   720    722   
................................................................................
  1388   1390       }
  1389   1391       case TEXT_MARK:
  1390   1392   	result = TkTextMarkCmd(textPtr, interp, objc, objv);
  1391   1393   	break;
  1392   1394       case TEXT_PEER:
  1393   1395   	result = TextPeerCmd(textPtr, interp, objc, objv);
  1394   1396   	break;
         1397  +    case TEXT_PENDINGSYNC: {
         1398  +        if (objc != 2) {
         1399  +            Tcl_WrongNumArgs(interp, 2, objv, NULL);
         1400  +            result = TCL_ERROR;
         1401  +            goto done;
         1402  +        }
         1403  +        Tcl_SetObjResult(interp,
         1404  +                Tcl_NewBooleanObj(TkTextPendingsync(textPtr)));
         1405  +        break;
         1406  +    }
  1395   1407       case TEXT_REPLACE: {
  1396   1408   	const TkTextIndex *indexFromPtr, *indexToPtr;
  1397   1409   
  1398   1410   	if (objc < 5) {
  1399   1411   	    Tcl_WrongNumArgs(interp, 2, objv,
  1400   1412   		    "index1 index2 chars ?tagList chars tagList ...?");
  1401   1413   	    result = TCL_ERROR;
................................................................................
  1502   1514   	break;
  1503   1515       case TEXT_SEARCH:
  1504   1516   	result = TextSearchCmd(textPtr, interp, objc, objv);
  1505   1517   	break;
  1506   1518       case TEXT_SEE:
  1507   1519   	result = TkTextSeeCmd(textPtr, interp, objc, objv);
  1508   1520   	break;
         1521  +    case TEXT_SYNC: {
         1522  +	if (objc == 4) {
         1523  +	    Tcl_Obj *cmd = objv[3];
         1524  +	    const char *option = Tcl_GetString(objv[2]);
         1525  +	    if (strncmp(option, "-command", objv[2]->length)) {
         1526  +		Tcl_AppendResult(interp, "wrong option \"", option, "\": should be \"-command\"", NULL);
         1527  +		result = TCL_ERROR;
         1528  +		goto done;
         1529  +	    }
         1530  +	    Tcl_IncrRefCount(cmd);
         1531  +	    if (TkTextPendingsync(textPtr)) {
         1532  +		if (textPtr->afterSyncCmd) {
         1533  +		    Tcl_DecrRefCount(textPtr->afterSyncCmd);
         1534  +		}
         1535  +		textPtr->afterSyncCmd = cmd;
         1536  +	    } else {
         1537  +		textPtr->afterSyncCmd = cmd;
         1538  +                Tcl_DoWhenIdle(RunAfterSyncCmd, (ClientData) textPtr);
         1539  +	    }
         1540  +	    break;
         1541  +	} else if (objc != 2) {
         1542  +	    Tcl_WrongNumArgs(interp, 2, objv, "?-command command?");
         1543  +	    result = TCL_ERROR;
         1544  +	    goto done;
         1545  +	}
         1546  +	if (textPtr->afterSyncCmd) {
         1547  +	    Tcl_DecrRefCount(textPtr->afterSyncCmd);
         1548  +	}
         1549  +	textPtr->afterSyncCmd = NULL;
         1550  +	TkTextUpdateLineMetrics(textPtr, 1,
         1551  +		TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr), -1);
         1552  +	break;
         1553  +    }
  1509   1554       case TEXT_TAG:
  1510   1555   	result = TkTextTagCmd(textPtr, interp, objc, objv);
  1511   1556   	break;
  1512   1557       case TEXT_WINDOW:
  1513   1558   	result = TkTextWindowCmd(textPtr, interp, objc, objv);
  1514   1559   	break;
  1515   1560       case TEXT_XVIEW:
................................................................................
  1991   2036       if (textPtr->insertBlinkHandler != NULL) {
  1992   2037   	Tcl_DeleteTimerHandler(textPtr->insertBlinkHandler);
  1993   2038       }
  1994   2039   
  1995   2040       textPtr->tkwin = NULL;
  1996   2041       textPtr->refCount--;
  1997   2042       Tcl_DeleteCommandFromToken(textPtr->interp, textPtr->widgetCmd);
         2043  +    if (textPtr->afterSyncCmd){
         2044  +	Tcl_DecrRefCount(textPtr->afterSyncCmd);
         2045  +	textPtr->afterSyncCmd = NULL;
         2046  +    }
  1998   2047       if (textPtr->refCount == 0) {
  1999   2048   	ckfree(textPtr);
  2000   2049       }
  2001   2050   }
  2002   2051   
  2003   2052   /*
  2004   2053    *----------------------------------------------------------------------
................................................................................
  5360   5409       if (sharedTextPtr->isDirty == 0 || oldDirtyFlag == 0) {
  5361   5410   	for (textPtr = sharedTextPtr->peers; textPtr != NULL;
  5362   5411   		textPtr = textPtr->next) {
  5363   5412   	    GenerateModifiedEvent(textPtr);
  5364   5413   	}
  5365   5414       }
  5366   5415   }
         5416  +
         5417  +/*
         5418  + *----------------------------------------------------------------------
         5419  + *
         5420  + * RunAfterSyncCmd --
         5421  + *
         5422  + *	This function is called by the event loop and executes the command
         5423  + *      scheduled by [.text sync -command $cmd].
         5424  + *
         5425  + * Results:
         5426  + *	None.
         5427  + *
         5428  + * Side effects:
         5429  + *	Anything may happen, depending on $cmd contents.
         5430  + *
         5431  + *----------------------------------------------------------------------
         5432  + */
         5433  +
         5434  +static void
         5435  +RunAfterSyncCmd(
         5436  +    ClientData clientData)		/* Information about text widget. */
         5437  +{
         5438  +    register TkText *textPtr = (TkText *) clientData;
         5439  +    int code;
         5440  +
         5441  +    if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)) {
         5442  +        /*
         5443  +        * The widget has been deleted. Don't do anything.
         5444  +        */
         5445  +
         5446  +        if (--textPtr->refCount == 0) {
         5447  +            ckfree((char *) textPtr);
         5448  +        }
         5449  +        return;
         5450  +    }
         5451  +
         5452  +    Tcl_Preserve((ClientData) textPtr->interp);
         5453  +    code = Tcl_EvalObjEx(textPtr->interp, textPtr->afterSyncCmd, TCL_EVAL_GLOBAL);
         5454  +    if (code == TCL_ERROR) {
         5455  +        Tcl_AddErrorInfo(textPtr->interp, "\n    (text sync)");
         5456  +        Tcl_BackgroundError(textPtr->interp);
         5457  +    }
         5458  +    Tcl_Release((ClientData) textPtr->interp);
         5459  +    Tcl_DecrRefCount(textPtr->afterSyncCmd);
         5460  +    textPtr->afterSyncCmd = NULL;
         5461  +}
  5367   5462   
  5368   5463   /*
  5369   5464    *----------------------------------------------------------------------
  5370   5465    *
  5371   5466    * SearchPerform --
  5372   5467    *
  5373   5468    *	Overall control of search process. Is given a pattern, a starting

Changes to generic/tkText.h.

   781    781       int undo;			/* Non-zero means the undo/redo behaviour is
   782    782   				 * enabled. */
   783    783       int maxUndo;		/* The maximum depth of the undo stack
   784    784   				 * expressed as the maximum number of compound
   785    785   				 * statements. */
   786    786       int autoSeparators;		/* Non-zero means the separators will be
   787    787   				 * inserted automatically. */
          788  +    Tcl_Obj *afterSyncCmd;	/* Command to be executed when lines are up to
          789  +                                 * date */
   788    790   } TkText;
   789    791   
   790    792   /*
   791    793    * Flag values for TkText records:
   792    794    *
   793    795    * GOT_SELECTION:		Non-zero means we've already claimed the
   794    796    *				selection.
................................................................................
  1105   1107   MODULE_SCOPE int	TkTextMarkCmd(TkText *textPtr, Tcl_Interp *interp,
  1106   1108   			    int objc, Tcl_Obj *const objv[]);
  1107   1109   MODULE_SCOPE int	TkTextMarkNameToIndex(TkText *textPtr,
  1108   1110   			    const char *name, TkTextIndex *indexPtr);
  1109   1111   MODULE_SCOPE void	TkTextMarkSegToIndex(TkText *textPtr,
  1110   1112   			    TkTextSegment *markPtr, TkTextIndex *indexPtr);
  1111   1113   MODULE_SCOPE void	TkTextEventuallyRepick(TkText *textPtr);
         1114  +MODULE_SCOPE Bool	TkTextPendingsync(TkText *textPtr);
  1112   1115   MODULE_SCOPE void	TkTextPickCurrent(TkText *textPtr, XEvent *eventPtr);
  1113   1116   MODULE_SCOPE void	TkTextPixelIndex(TkText *textPtr, int x, int y,
  1114   1117   			    TkTextIndex *indexPtr, int *nearest);
  1115   1118   MODULE_SCOPE Tcl_Obj *	TkTextNewIndexObj(TkText *textPtr,
  1116   1119   			    const TkTextIndex *indexPtr);
  1117   1120   MODULE_SCOPE void	TkTextRedrawRegion(TkText *textPtr, int x, int y,
  1118   1121   			    int width, int height);

Changes to generic/tkTextDisp.c.

   587    587   static int		DlineXOfIndex(TkText *textPtr,
   588    588   			    DLine *dlPtr, int byteIndex);
   589    589   static int		TextGetScrollInfoObj(Tcl_Interp *interp,
   590    590   			    TkText *textPtr, int objc,
   591    591   			    Tcl_Obj *const objv[], double *dblPtr,
   592    592   			    int *intPtr);
   593    593   static void		AsyncUpdateLineMetrics(ClientData clientData);
          594  +static void		GenerateWidgetViewSyncEvent(TkText *textPtr, Bool InSync);
   594    595   static void		AsyncUpdateYScrollbar(ClientData clientData);
   595    596   static int              IsStartOfNotMergedLine(TkText *textPtr,
   596    597                               CONST TkTextIndex *indexPtr);
   597    598   
   598    599   /*
   599    600    * Result values returned by TextGetScrollInfoObj:
   600    601    */
................................................................................
  2937   2938        * Update the lines in blocks of about 24 recalculations, or 250+ lines
  2938   2939        * examined, so we pass in 256 for 'doThisMuch'.
  2939   2940        */
  2940   2941   
  2941   2942       lineNum = TkTextUpdateLineMetrics(textPtr, lineNum,
  2942   2943   	    dInfoPtr->lastMetricUpdateLine, 256);
  2943   2944   
         2945  +    dInfoPtr->currentMetricUpdateLine = lineNum;
         2946  +
  2944   2947       if (tkTextDebug) {
  2945   2948   	char buffer[2 * TCL_INTEGER_SPACE + 1];
  2946   2949   
  2947   2950   	sprintf(buffer, "%d %d", lineNum, dInfoPtr->lastMetricUpdateLine);
  2948   2951   	LOG("tk_textInvalidateLine", buffer);
  2949   2952       }
  2950   2953   
................................................................................
  2954   2957        */
  2955   2958   
  2956   2959       if (dInfoPtr->metricEpoch == -1
  2957   2960   	    && lineNum == dInfoPtr->lastMetricUpdateLine) {
  2958   2961   	/*
  2959   2962   	 * We have looped over all lines, so we're done. We must release our
  2960   2963   	 * refCount on the widget (the timer token was already set to NULL
  2961         -	 * above).
         2964  +	 * above). If there is a registered aftersync command, run that first.
  2962   2965   	 */
  2963   2966   
         2967  +        if (textPtr->afterSyncCmd) {
         2968  +            int code;
         2969  +            Tcl_Preserve((ClientData) textPtr->interp);
         2970  +            code = Tcl_EvalObjEx(textPtr->interp, textPtr->afterSyncCmd,
         2971  +                    TCL_EVAL_GLOBAL);
         2972  +	    if (code == TCL_ERROR) {
         2973  +                Tcl_AddErrorInfo(textPtr->interp, "\n    (text sync)");
         2974  +                Tcl_BackgroundError(textPtr->interp);
         2975  +	    }
         2976  +            Tcl_Release((ClientData) textPtr->interp);
         2977  +            Tcl_DecrRefCount(textPtr->afterSyncCmd);
         2978  +            textPtr->afterSyncCmd = NULL;
         2979  +	}
         2980  +
         2981  +        /*
         2982  +         * Fire the <<WidgetViewSync>> event since the widget view is in sync
         2983  +         * with its internal data (actually it will be after the next trip
         2984  +         * through the event loop, because the widget redraws at idle-time).
         2985  +         */
         2986  +
         2987  +        GenerateWidgetViewSyncEvent(textPtr, 1);
         2988  +
  2964   2989   	textPtr->refCount--;
  2965   2990   	if (textPtr->refCount == 0) {
  2966   2991   	    ckfree(textPtr);
  2967   2992   	}
  2968   2993   	return;
  2969   2994       }
  2970         -    dInfoPtr->currentMetricUpdateLine = lineNum;
  2971   2995   
  2972   2996       /*
  2973   2997        * Re-arm the timer. We already have a refCount on the text widget so no
  2974   2998        * need to adjust that.
  2975   2999        */
  2976   3000   
  2977   3001       dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1,
  2978   3002   	    AsyncUpdateLineMetrics, textPtr);
  2979   3003   }
         3004  +
         3005  +/*
         3006  + *----------------------------------------------------------------------
         3007  + *
         3008  + * GenerateWidgetViewSyncEvent --
         3009  + *
         3010  + *      Send the <<WidgetViewSync>> event related to the text widget
         3011  + *      line metrics asynchronous update.
         3012  + *      This is equivalent to:
         3013  + *         event generate $textWidget <<WidgetViewSync>> -detail $s
         3014  + *      where $s is the sync status: true (when the widget view is in
         3015  + *      sync with its internal data) or false (when it is not).
         3016  + *
         3017  + * Results:
         3018  + *      None
         3019  + *
         3020  + * Side effects:
         3021  + *      If corresponding bindings are present, they will trigger.
         3022  + *
         3023  + *----------------------------------------------------------------------
         3024  + */
         3025  +
         3026  +static void
         3027  +GenerateWidgetViewSyncEvent(
         3028  +    TkText *textPtr,		/* Information about text widget. */
         3029  +    Bool InSync)                /* True if in sync, false otherwise */
         3030  +{
         3031  +    union {XEvent general; XVirtualEvent virtual;} event;
         3032  +
         3033  +    memset(&event, 0, sizeof(event));
         3034  +    event.general.xany.type = VirtualEvent;
         3035  +    event.general.xany.serial = NextRequest(Tk_Display(textPtr->tkwin));
         3036  +    event.general.xany.send_event = False;
         3037  +    event.general.xany.window = Tk_WindowId(textPtr->tkwin);
         3038  +    event.general.xany.display = Tk_Display(textPtr->tkwin);
         3039  +    event.virtual.name = Tk_GetUid("WidgetViewSync");
         3040  +    event.virtual.user_data = Tcl_NewBooleanObj(InSync);
         3041  +    Tk_HandleEvent(&event.general);
         3042  +}
  2980   3043   
  2981   3044   /*
  2982   3045    *----------------------------------------------------------------------
  2983   3046    *
  2984   3047    * TkTextUpdateLineMetrics --
  2985   3048    *
  2986   3049    *	This function updates the pixel height calculations of a range of
................................................................................
  3349   3412        * Now re-set the current update calculations.
  3350   3413        */
  3351   3414   
  3352   3415       if (dInfoPtr->lineUpdateTimer == NULL) {
  3353   3416   	textPtr->refCount++;
  3354   3417   	dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1,
  3355   3418   		AsyncUpdateLineMetrics, textPtr);
         3419  +        GenerateWidgetViewSyncEvent(textPtr, 0);
  3356   3420       }
  3357   3421   }
  3358   3422   
  3359   3423   /*
  3360   3424    *----------------------------------------------------------------------
  3361   3425    *
  3362   3426    * TkTextFindDisplayLineEnd --
................................................................................
  5060   5124   
  5061   5125   	dInfoPtr->metricEpoch = -1;
  5062   5126   
  5063   5127   	if (dInfoPtr->lineUpdateTimer == NULL) {
  5064   5128   	    textPtr->refCount++;
  5065   5129   	    dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1,
  5066   5130   		    AsyncUpdateLineMetrics, textPtr);
         5131  +            GenerateWidgetViewSyncEvent(textPtr, 0);
  5067   5132   	}
  5068   5133       }
  5069   5134   }
  5070   5135   
  5071   5136   /*
  5072   5137    *----------------------------------------------------------------------
  5073   5138    *
................................................................................
  6058   6123   	break;
  6059   6124       case TKTEXT_SCROLL_UNITS:
  6060   6125   	YScrollByLines(textPtr, count);
  6061   6126   	break;
  6062   6127       }
  6063   6128       return TCL_OK;
  6064   6129   }
         6130  +
         6131  +/*
         6132  + *--------------------------------------------------------------
         6133  + *
         6134  + * TkTextPendingsync --
         6135  + *
         6136  + *	This function checks if any line heights are not up-to-date.
         6137  + *
         6138  + * Results:
         6139  + *	Returns a boolean true if it is the case, or false if all line
         6140  + *      heights are up-to-date.
         6141  + *
         6142  + * Side effects:
         6143  + *	None.
         6144  + *
         6145  + *--------------------------------------------------------------
         6146  + */
         6147  +
         6148  +Bool
         6149  +TkTextPendingsync(
         6150  +    TkText *textPtr)		/* Information about text widget. */
         6151  +{
         6152  +    TextDInfo *dInfoPtr = textPtr->dInfoPtr;
         6153  +
         6154  +    return (
         6155  +        ((dInfoPtr->metricEpoch == -1) &&
         6156  +         (dInfoPtr->lastMetricUpdateLine == dInfoPtr->currentMetricUpdateLine)) ?
         6157  +        0 : 1);
         6158  +}
  6065   6159   
  6066   6160   /*
  6067   6161    *--------------------------------------------------------------
  6068   6162    *
  6069   6163    * TkTextScanCmd --
  6070   6164    *
  6071   6165    *	This function is invoked to process the "scan" option for the widget

Changes to tests/text.test.

   921    921   } -returnCodes {error} -result {wrong # args: should be ".t option ?arg ...?"}
   922    922   test text-3.2 {TextWidgetCmd procedure} -setup {
   923    923       text .t
   924    924   } -body {
   925    925       .t gorp 1.0 z 1.2
   926    926   } -cleanup {
   927    927       destroy .t
   928         -} -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}
          928  +} -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}
   929    929   
   930    930   test text-4.1 {TextWidgetCmd procedure, "bbox" option} -setup {
   931    931       text .t
   932    932   } -body {
   933    933       .t bbox
   934    934   } -cleanup {
   935    935       destroy .t
................................................................................
  1143   1143   Line 4
  1144   1144   bOy GIrl .#@? x_yz
  1145   1145   [email protected]#$%
  1146   1146   Line 7"
  1147   1147       .t co 1.0 z 1.2
  1148   1148   } -cleanup {
  1149   1149       destroy .t
  1150         -} -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}
         1150  +} -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}
  1151   1151   # "configure" option is already covered above
  1152   1152   
  1153   1153   test text-7.1 {TextWidgetCmd procedure, "debug" option} -setup {
  1154   1154       text .t
  1155   1155   } -body {
  1156   1156       .t debug 0 1
  1157   1157   } -cleanup {
................................................................................
  1159   1159   } -returnCodes {error} -result {wrong # args: should be ".t debug boolean"}
  1160   1160   test text-7.2 {TextWidgetCmd procedure, "debug" option} -setup {
  1161   1161       text .t
  1162   1162   } -body {
  1163   1163       .t de 0 1
  1164   1164   } -cleanup {
  1165   1165       destroy .t
  1166         -} -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}
         1166  +} -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}
  1167   1167   test text-7.3 {TextWidgetCmd procedure, "debug" option} -setup {
  1168   1168       text .t
  1169   1169   } -body {
  1170   1170       .t debug true
  1171   1171       .t deb
  1172   1172   } -cleanup {
  1173   1173       destroy .t
................................................................................
  2682   2682       }
  2683   2683       .t tag configure hidden -elide true
  2684   2684       .t tag add hidden 5.7 11.0
  2685   2685       update
  2686   2686       # next line to be fully sure that asynchronous line heights calculation is
  2687   2687       # up-to-date otherwise this test may fail (depending on the computer
  2688   2688       # performance), especially when the . toplevel has small height
  2689         -    .t count -update -ypixels 1.0 end
         2689  +    .t sync
  2690   2690       set y1 [lindex [.t yview] 1]
  2691   2691       .t count -displaylines 5.0 11.0
  2692   2692       set y2 [lindex [.t yview] 1]
  2693   2693       .t count -displaylines 5.0 12.0
  2694   2694       set y3 [lindex [.t yview] 1]
  2695   2695       list [expr {$y1 == $y2}] [expr {$y1 == $y3}]
  2696   2696   } -cleanup {
................................................................................
  2878   2878       lappend res [.t index "end -2 indices"]
  2879   2879       lappend res [.t index "end -2 display indices"]
  2880   2880       lappend res [.t index "end -2 display chars"]
  2881   2881   } -cleanup {
  2882   2882       destroy .t
  2883   2883   } -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}
  2884   2884   
         2885  +test text-11a.1 {TextWidgetCmd procedure, "pendingsync" option} -setup {
         2886  +    destroy .yt
         2887  +} -body {
         2888  +    text .yt
         2889  +    list [catch {.yt pendingsync mytext} msg] $msg
         2890  +} -cleanup {
         2891  +    destroy .yt
         2892  +} -result {1 {wrong # args: should be ".yt pendingsync"}}
         2893  +test text-11a.2 {TextWidgetCmd procedure, "pendingsync" option} -setup {
         2894  +    destroy .top.yt .top
         2895  +} -body {
         2896  +    toplevel .top
         2897  +    pack [text .top.yt]
         2898  +    set content {}
         2899  +    for {set i 1} {$i < 300} {incr i} {
         2900  +        append content [string repeat "$i " 15] \n
         2901  +    }
         2902  +    .top.yt insert 1.0 $content
         2903  +    # wait for end of line metrics calculation to get correct $fraction1
         2904  +    # as a reference
         2905  +    while {[.top.yt pendingsync]} {update}
         2906  +    .top.yt yview moveto 1
         2907  +    set fraction1 [lindex [.top.yt yview] 0]
         2908  +    set res [expr {$fraction1 > 0}]
         2909  +    .top.yt delete 1.0 end
         2910  +    .top.yt insert 1.0 $content
         2911  +    # ensure the test is relevant
         2912  +    lappend res [.top.yt pendingsync]
         2913  +    # asynchronously wait for completion of line metrics calculation
         2914  +    while {[.top.yt pendingsync]} {update}
         2915  +    .top.yt yview moveto $fraction1
         2916  +    set fraction2 [lindex [.top.yt yview] 0]
         2917  +    lappend res [expr {$fraction1 == $fraction2}]
         2918  +} -cleanup {
         2919  +    destroy .top.yt .top
         2920  +} -result {1 1 1}
         2921  +
         2922  +test text-11a.11 {TextWidgetCmd procedure, "sync" option} -setup {
         2923  +    destroy .yt
         2924  +} -body {
         2925  +    text .yt
         2926  +    list [catch {.yt sync mytext} msg] $msg
         2927  +} -cleanup {
         2928  +    destroy .yt
         2929  +} -result {1 {wrong # args: should be ".yt sync ?-command command?"}}
         2930  +test text-11a.12 {TextWidgetCmd procedure, "sync" option} -setup {
         2931  +    destroy .top.yt .top
         2932  +} -body {
         2933  +    toplevel .top
         2934  +    pack [text .top.yt]
         2935  +    set content {}
         2936  +    for {set i 1} {$i < 30} {incr i} {
         2937  +        append content [string repeat "$i " 15] \n
         2938  +    }
         2939  +    .top.yt insert 1.0 $content
         2940  +    # wait for end of line metrics calculation to get correct $fraction1
         2941  +    # as a reference
         2942  +    .top.yt sync
         2943  +    .top.yt yview moveto 1
         2944  +    set fraction1 [lindex [.top.yt yview] 0]
         2945  +    set res [expr {$fraction1 > 0}]
         2946  +    # first case: do not wait for completion of line metrics calculation
         2947  +    .top.yt delete 1.0 end
         2948  +    .top.yt insert 1.0 $content
         2949  +    .top.yt yview moveto $fraction1
         2950  +    set fraction2 [lindex [.top.yt yview] 0]
         2951  +    lappend res [expr {$fraction1 == $fraction2}]
         2952  +    # second case: wait for completion of line metrics calculation
         2953  +    .top.yt delete 1.0 end
         2954  +    .top.yt insert 1.0 $content
         2955  +    .top.yt sync
         2956  +    .top.yt yview moveto $fraction1
         2957  +    set fraction2 [lindex [.top.yt yview] 0]
         2958  +    lappend res [expr {$fraction1 == $fraction2}]
         2959  +} -cleanup {
         2960  +    destroy .top.yt .top
         2961  +} -result {1 0 1}
         2962  +
         2963  +test text-11a.21 {TextWidgetCmd procedure, "sync" option with -command} -setup {
         2964  +    destroy .yt
         2965  +} -body {
         2966  +    text .yt
         2967  +    list [catch {.yt sync -comx foo} msg] $msg
         2968  +} -cleanup {
         2969  +    destroy .yt
         2970  +} -result {1 {wrong option "-comx": should be "-command"}}
         2971  +test text-11a.22 {TextWidgetCmd procedure, "sync" option with -command} -setup {
         2972  +    destroy .top.yt .top
         2973  +} -body {
         2974  +    set res {}
         2975  +    set ::x 0
         2976  +    toplevel .top
         2977  +    pack [text .top.yt]
         2978  +    set content {}
         2979  +    for {set i 1} {$i < 30} {incr i} {
         2980  +        append content [string repeat "$i " 15] \n
         2981  +    }
         2982  +    .top.yt insert 1.0 $content
         2983  +    # first case: line metrics calculation still running when launching 'sync -command'
         2984  +    lappend res [.top.yt pendingsync]
         2985  +    .top.yt sync -command [list set ::x 1]
         2986  +    lappend res $::x
         2987  +    # now finish line metrics calculations
         2988  +    while {[.top.yt pendingsync]} {update}
         2989  +    lappend res [.top.yt pendingsync] $::x
         2990  +    # second case: line metrics calculation completed when launching 'sync -command'
         2991  +    .top.yt sync -command [list set ::x 2]
         2992  +    lappend res $::x
         2993  +    vwait ::x
         2994  +    lappend res $::x
         2995  +} -cleanup {
         2996  +    destroy .top.yt .top
         2997  +} -result {1 0 0 1 1 2}
         2998  +
         2999  +test text-11a.31 {"<<WidgetViewSync>>" event} -setup {
         3000  +    destroy .top.yt .top
         3001  +} -body {
         3002  +    toplevel .top
         3003  +    pack [text .top.yt]
         3004  +    set content {}
         3005  +    for {set i 1} {$i < 300} {incr i} {
         3006  +        append content [string repeat "$i " 15] \n
         3007  +    }
         3008  +    .top.yt insert 1.0 $content
         3009  +    update
         3010  +    bind .top.yt <<WidgetViewSync>> { if {%d} {set yud(%W) 1} }
         3011  +    # wait for end of line metrics calculation to get correct $fraction1
         3012  +    # as a reference
         3013  +    if {[.top.yt pendingsync]} {vwait yud(.top.yt)}
         3014  +    .top.yt yview moveto 1
         3015  +    set fraction1 [lindex [.top.yt yview] 0]
         3016  +    set res [expr {$fraction1 > 0}]
         3017  +    .top.yt delete 1.0 end
         3018  +    .top.yt insert 1.0 $content
         3019  +    # synchronously wait for completion of line metrics calculation
         3020  +    # and ensure the test is relevant
         3021  +    set waited 0
         3022  +    if {[.top.yt pendingsync]} {set waited 1 ; vwait yud(.top.yt)}
         3023  +    lappend res $waited
         3024  +    .top.yt yview moveto $fraction1
         3025  +    set fraction2 [lindex [.top.yt yview] 0]
         3026  +    lappend res [expr {$fraction1 == $fraction2}]
         3027  +} -cleanup {
         3028  +    destroy .top.yt .top
         3029  +} -result {1 1 1}
         3030  +
         3031  +test text-11a.41 {"sync" "pendingsync" and <<WidgetViewSync>>} -setup {
         3032  +    destroy .top.yt .top
         3033  +} -body {
         3034  +    set res {}
         3035  +    toplevel .top
         3036  +    pack [text .top.yt]
         3037  +    set content {}
         3038  +    for {set i 1} {$i < 300} {incr i} {
         3039  +        append content [string repeat "$i " 50] \n
         3040  +    }
         3041  +    bind .top.yt <<WidgetViewSync>> {lappend res Sync:%d}
         3042  +    .top.yt insert 1.0 $content
         3043  +    vwait res  ; # event dealt with by the event loop, with %d==0 i.e. we're out of sync
         3044  +    # ensure the test is relevant
         3045  +    lappend res "Pending:[.top.yt pendingsync]"
         3046  +    # - <<WidgetViewSync>> fires when sync returns if there was pending syncs 
         3047  +    # - there is no more any pending sync after running 'sync'
         3048  +    .top.yt sync
         3049  +    vwait res  ; # event dealt with by the event loop, with %d==1 i.e. we're in sync again
         3050  +    lappend res "Pending:[.top.yt pendingsync]"
         3051  +    set res
         3052  +} -cleanup {
         3053  +    destroy .top.yt .top
         3054  +} -result {Sync:0 Pending:1 Sync:1 Pending:0}
  2885   3055   
  2886   3056   test text-12.1 {TextWidgetCmd procedure, "index" option} -setup {
  2887   3057       text .t
  2888   3058   } -body {
  2889   3059       .t index
  2890   3060   } -cleanup {
  2891   3061       destroy .t
................................................................................
  2899   3069   } -returnCodes {error} -result {wrong # args: should be ".t index index"}
  2900   3070   test text-12.3 {TextWidgetCmd procedure, "index" option} -setup {
  2901   3071       text .t
  2902   3072   } -body {
  2903   3073       .t in a b
  2904   3074   } -cleanup {
  2905   3075       destroy .t
  2906         -} -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}
         3076  +} -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}
  2907   3077   test text-12.4 {TextWidgetCmd procedure, "index" option} -setup {
  2908   3078       text .t
  2909   3079   } -body {
  2910   3080       .t index @xyz
  2911   3081   } -cleanup {
  2912   3082       destroy .t
  2913   3083   } -returnCodes {error} -result {bad text index "@xyz"}
................................................................................
  6706   6876       .t peer names foo
  6707   6877   } -cleanup {
  6708   6878       destroy .t
  6709   6879   } -returnCodes {error} -result {wrong # args: should be ".t peer names"}
  6710   6880   test text-33.3 {TextWidgetCmd procedure, "peer" option} -setup {
  6711   6881       text .t
  6712   6882   } -body {
  6713         -    .t p names
         6883  +    .t pee names
  6714   6884   } -cleanup {
  6715   6885       destroy .t
  6716   6886   } -returnCodes {ok} -result {}
  6717   6887   test text-33.4 {TextWidgetCmd procedure, "peer" option} -setup {
  6718   6888       text .t
  6719   6889   } -body {
  6720   6890       .t peer names

Changes to tests/textDisp.test.

  4193   4193   	set result "italic font has much too much space"
  4194   4194       } else {
  4195   4195   	set result "italic font measurement ok"
  4196   4196       }
  4197   4197   } {italic font measurement ok}
  4198   4198   destroy .tt
  4199   4199   
  4200         -test textDisp-34.1 {Text widgets multi-scrolling problem: Bug 2677890} -setup {
  4201         -    pack [text .t1 -width 10 -yscrollcommand {.sy set}] \
  4202         -	[ttk::scrollbar .sy -orient vertical -command {.t1 yview}] \
  4203         -	-side left -fill both
  4204         -    bindtags .sy {}; # No clicky!
         4200  +test textDisp-34.1 {Line heights recalculation problem: bug 2677890} -setup {
         4201  +    pack [text .t1] -expand 1 -fill both
  4205   4202       set txt ""
  4206         -    for {set i 0} {$i < 99} {incr i} {
  4207         -	lappend txt "$i" [list pc $i] "\n" ""
         4203  +    for {set i 1} {$i < 100} {incr i} {
         4204  +        append txt "Line $i\n"
  4208   4205       }
  4209   4206       set result {}
  4210   4207   } -body {
  4211         -    .t1 insert end {*}$txt
         4208  +    .t1 insert end $txt
         4209  +    .t1 debug 1
         4210  +    set ge [winfo geometry .]
         4211  +    scan $ge "%dx%d+%d+%d" width height left top
         4212  +    update
         4213  +    .t1 sync
         4214  +    set negative 0
         4215  +    bind .t1 <<WidgetViewSync>> { if {%d < 0} {set negative 1} }
         4216  +    # Without the fix for bug 2677890, changing the width of the toplevel
         4217  +    # will launch recomputation of the line heights, but will produce negative
         4218  +    # number of still remaining outdated lines, which is obviously wrong.
         4219  +    # Thus we use this way to check for regression regarding bug 2677890,
         4220  +    # i.e. to check that the fix for this bug really is still in.
         4221  +    wm geometry . "[expr {$width * 2}]x$height+$left+$top"
  4212   4222       update
  4213         -    lappend result [.sy get]
  4214         -    .t1 replace 6.0 6.0+1c "*"
  4215         -    lappend result [.sy get]
  4216         -    after 0 {lappend result [.sy get]}
  4217         -    after 1000 {lappend result [.sy get]}
  4218         -    vwait result;vwait result
  4219         -    return $result
         4223  +    .t1 sync
         4224  +    set negative
  4220   4225   } -cleanup {
  4221         -    destroy .t1 .sy
  4222         -} -result {{0.0 0.24} {0.0 0.24} {0.0 0.24} {0.0 0.24}}
         4226  +    destroy .t1
         4227  +} -result {0}
  4223   4228   
  4224   4229   test textDisp-35.1 {Init value of charHeight - Dancing scrollbar bug 1499165} -setup {
  4225   4230       pack [text .t1] -fill both -expand y -side left
  4226   4231       .t insert end "[string repeat a\nb\nc\n 500000]THE END\n"
  4227   4232       set res {}
  4228   4233   } -body {
  4229   4234       .t see 10000.0