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


TIP:438
Title:Ensure Line Metrics are Up-to-Date
Version:$Revision: 1.16 $
Authors: François Vogel <fvogelnew1 at free dot fr>
Jan Nijtmans <jan dot nijtmans at gmail dot com>
State:Final
Type:Project
Tcl-Version:8.6.5
Vote:Done
Created:Sunday, 01 November 2015
Keywords:Tk, text

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.


Powered by Tcl[Index] [History] [HTML Format] [Source Format] [LaTeX Format] [Text Format] [XML Format] [*roff Format (experimental)] [RTF Format (experimental)]

TIP AutoGenerator - written by Donal K. Fellows