
Motivation:

The BWidget ListBox insert command isn't a speed demon. This is not
too noticable for small lists, but it gets rather slow for big ones
E.g. inserting 500 entries with just -text and -image attributes set 
     takes approx. 1 second on a Pentium III 550MHz.

Problem analysis:

The bulk of the time in ListBox::insert gets eaten by a call to
Widget::init. In Widget::init, most of the time is used by
initializing and parsing from standard options database. The example
from above uses ~1250msec for building up the std. options and
~250msec to apply specific options for the item.

Proposed patch:

Two functions to speed up inserts of many items that are all almost
equal. 

First, Widget::copyinit. This works almost like the normal init, but
saves time by copying standard option initialization from a given
template. It then applies specific item options (just like init).

Second, ListBox::multipleinsert. This also works like its pendant
ListBox::insert, but knows about the aforementioned Widget::copyinit
function. I shortly thought of changing the original 'insert', but the
effects of a change there would not be backward compatible.

Sideeffects:
None to existing code. A widget function more to document for the user
(ListBox::multipleinsert).

Results:
The speedup of using multipleinsert is a factor of 2 when used almost
exactly like a multiple call to insert.
That is:

    set l
    foreach n {name1 name2 name3 ... namen} {
        lappend l $n "-text $n -image something.gif"
    }
    $list multipleinsert end $l

is 2 times faster than its pendant

    foreach n {name1 name2 name3 ... namen} {
        $list insert end $n -text $n -image something.gif
    }

Assuming that, e.g. all images used are the same (as in the example
above, the speedup is factor 3 by using:
    set l
    foreach n {name1 name2 name3 ... namen} {
        if {[llength $l] == 0} {
            lappend l $n "-text $n -image something.gif"
	} else {
            lappend l $n "-text $n"
        }
    $list multipleinsert end $l


Code:

In listbox.tcl:

# Bastien Chevreux (bach@mwgdna.com)
# The multipleinsert command performs inserts several items at once into
#  the list. It is faster than calling insert multiple times as it uses the
#  Widget::copyinit command for initializing all items after the 1st. The 
#  speedup factor is between 2 and 3 for typical usage, but could be higher
#  for inserts with many options.
#
# Syntax: path and index are as in the insert command
#         args is a list of even numbered elements where the 1st of each pair
#          corresponds to the item of 'insert' and the second to args of 'insert'.
# ------------------------------------------------------------------------------
#  Command ListBox::multipleinsert
# ------------------------------------------------------------------------------
proc ListBox::multipleinsert { path index args } {
    variable $path
    upvar 0  $path data

    # If we got only one list as arg, take the first element as args
    # This enables callers to use 
    #   $list multipleinsert index $thelist
    # instead of
    #   eval $list multipleinsert index $thelist

    if {[llength $args] == 1} {
	set args [lindex $args 0]
    }

    set count 0
    foreach {item iargs} $args {
	if { [lsearch $data(items) $item] != -1 } {
	    return -code error "item \"$item\" already exists"
	}
	
	if {$count==0} {
	    Widget::init ListBox::Item $path.$item $iargs
	    set firstpath $path.$item
	} else {
	    Widget::copyinit ListBox::Item $firstpath $path.$item $iargs
	}

	if { ![string compare $index "end"] } {
	    eval lappend data(items) $item
	} else {
	    set data(items) [linsert $data(items) $index $item]
	}
	set data(upd,create,$item) $item

	incr count
    }

    _redraw_idle $path 2
    return $item
}

       

In widget.tcl:

# Bastien Chevreux (bach@mwgdna.com)
#
# copyinit performs basically the same job as init, but it uses a
#  existing template to initialize its values. So, first a perferct copy
#  from the template is made just to be altered by any existing options
#  afterwards.
# But this still saves time as the first initialization parsing block is
#  skipped.
# As additional bonus, items that differ in just a few options can be
#  initialized faster by leaving out the options that are equal.

# This function is currently used only by ListBox::multipleinsert, but other
#  calls should follow :)

# ------------------------------------------------------------------------------
#  Command Widget::copyinit
# ------------------------------------------------------------------------------
proc Widget::copyinit { class templatepath path options } {
    upvar 0 ${class}::opt classopt
    upvar 0 ${class}::$path:opt  pathopt
    upvar 0 ${class}::$path:mod  pathmod
    upvar 0 ${class}::$path:init pathinit

    upvar 0 ${class}::$templatepath:opt   templatepathopt
    upvar 0 ${class}::$templatepath:mod   templatepathmod
    upvar 0 ${class}::$templatepath:init  templatepathinit

    if { [info exists pathopt] } {
	unset pathopt
    }
    if { [info exists pathmod] } {
	unset pathmod
    }

    # We use the template widget for option db copying, but it has to exist!
    array set pathmod  [array get templatepathmod]
    array set pathopt  [array get templatepathopt]
    array set pathinit [array get templatepathinit]

    set Widget::_class($path) $class
    foreach {option value} $options {
        if { ![info exists classopt($option)] } {
            unset pathopt
            unset pathmod
            return -code error "unknown option \"$option\""
        }
        set optdesc $classopt($option)
        set type    [lindex $optdesc 0]
        if { ![string compare $type "Synonym"] } {
            set option  [lindex $optdesc 1]
            set optdesc $classopt($option)
            set type    [lindex $optdesc 0]
        }
        set pathopt($option) [$Widget::_optiontype($type) $option $value [lindex $optdesc 3]]
	set pathinit($option) $pathopt($option)
    }
}
