TIP 438: Ensure Line Metrics are Up-to-Date

Login
Author:         François Vogel <[email protected]>
Author:         Jan Nijtmans <[email protected]>
State:          Final
Type:           Project
Vote:           Done
Created:        01-Nov-2015
Post-History:   
Keywords:       Tk,text
Tcl-Version:    8.6.5
Tk-Branch:      tip-438

Abstract

The text widget calculates line metrics asynchronously, for performance reasons. Because of this, some commands of the text widget may return wrong results if the asynchronous calculations are not over. This TIP is about providing the user with ways to ensure that line metrics are up-to-date.

Rationale

The text widget features asynchronous calculation of the display height of logical lines. The reasons for this and the details of the implementation are explained at the beginning of tkTextDisp.c.

This approach has definite advantages among which responsivity of the text widget is important. Yet, there are drawbacks in the fact the calculation is asynchronous. Some commands of the text widget may return wrong results if the asynchronous calculations are not finished at the time these commands are called. For example this is the case of .text count -ypixels, which was solved by adding a modifier -update allowing the user to be sure any possible out of date line height information is recalculated.

It appears that aside of .text count -ypixels there are several other cases where wrong results can be produced by text widget commands. These cases are illustrated in several bug reports:

In all these cases, forcing the update by calling .text count -update -ypixels 1.0 end before calling .text yview, or .text yview moveto solves the issue presented in the ticket. This has however a performance cost, of course, but the above tickets show that there are cases where the programmer needs accurate results, be it at the cost of the time needed to get the line heights calculations up-to-date.

This TIP is about providing the user/programmer with (better) ways to ensure that line metrics are up-to-date.

Indeed it is not appropriate to let the concerned commands always force update of the line metrics or wait for the end of the update calculation each time they are called: performance impact would be way too large.

Also, it has to be noted that the update command is of no help here since the line metrics calculation is done within the event loop in a chained sequence of [after 1] handlers.

Proposed Change

It is proposed to add two new commands to the text widget:

pathName sync ?-command command?

pathName pendingsync

Also a new virtual event <<WidgetViewSync>> will be added.

Description:

pathName sync

Immediately brings the line metrics up-to-date by forcing computation of
any outdated line pixel heights. Indeed, to maintain a responsive
user-experience, the text widget caches line heights and re-calculates them
in the background. 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.

Implementation details: The command executes:

    TkTextUpdateLineMetrics(textPtr, 1,
	      TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr), -1);

pathName sync -command command

Schedule _command_ to be executed exactly once as soon as all line
calculations are up-to-date. If there are no pending line metrics
calculations, the scheduling is immediate. The command returns the empty
string. **bgerror** is called on _command_ failure.

pathName pendingsync

Returns 1 if the line calculations are not up-to-date, 0 otherwise.

<<WidgetViewSync>>

A widget can have a period of time during which the internal data model is
not in sync with the view. The **sync** method forces the view to be in
sync with the data. The **<<WidgetViewSync>>** virtual event fires when
the internal data model starts to be out of sync with the widget view, and
also when it becomes again in sync with the widget view. For the text
widget, it fires when line metrics become outdated, and when they are
up-to-date again. Note that this means it fires in particular when
_pathName_ **sync** returns \(if there was pending updates\). The detail
field \(%d substitution\) is either true \(when the widget is in sync\) or
false \(when it is not\).

All sync, pendingsync and <<WidgetViewSync>> apply to each text widget independently of its peers.

The names sync, pendingsync and <<WidgetViewSync>> are chosen because of the potential for generalization to other widgets they have.

The text widget documentation will be augmented by a short section describing the asynchronous update of line metrics, the reasons for that background update, the drawbacks regarding possibly wrong results in .text yview or .text yview moveto, and the way to solve these issues by using the new commands. Example code as below will be provided in the documentation, since this code will not be included in the library (i.e. in text.tcl)).

The existing -update modifier switch of .text count will become obsolete. It will be declared as deprecated in the text widget documentation page while being still supported for backwards compatibility reasons.

Using the new commands, ways to ensure accurate results in .text yview, or .text yview moveto are as in the following example:

    ## Example 1:

    # runtime, immediately complete line metrics at any cost (GUI unresponsive)
    $w sync
    $w yview moveto $fraction

    ## Example 2:

    # runtime, synchronously wait for up-to-date line metrics (GUI responsive)
    $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]
    }

Rejected alternatives

Copyright

This document has been placed in the public domain.