################################################################################
# RAMSAN: to recreate from package tablelist, use:

#set files "tablelist.tcl scripts/mwutil.tcl    scripts/tablelistBind.tcl scripts/tablelistConfig.tcl  \
#           scripts/tablelistEdit.tcl    scripts/tablelistMove.tcl scripts/tablelistSort.tcl\
#            scripts/tablelistUtil.tcl    scripts/tablelistWidget.tcl"
# set fout [open ~/GiD\ project/scripts/tablelist.tcl w]
# package require fileutil
# foreach i $files {
#    puts $fout [fileutil::cat $i]
# }
# close $fout
################################################################################

#==============================================================================
# Main Tablelist package module.
#
# Copyright (c) 2000-2003  Csaba Nemethi (E-mail: csaba.nemethi@t-online.de)
#==============================================================================

package require Tcl 8
package require Tk  8

namespace eval tablelist {
    #
    # Public variables:
    #
    variable version	3.3
    if {[string compare $::tcl_platform(platform) macintosh] != 0} {
	#
	# On the Macintosh, the tablelist::library variable is
	# set in the file pkgIndex.tcl, because of a bug in
	# [info script] in some Tcl releases for that platform.
	#
	variable library	[file dirname [info script]]
    }

    #
    # Creates a new tablelist widget:
    #
    namespace export	tablelist

    #
    # Sorts the items of a tablelist widget based on one of its columns:
    #
    namespace export	sortByColumn
}

package provide Tablelist $tablelist::version
package provide tablelist $tablelist::version

lappend auto_path [file join $tablelist::library scripts]

#==============================================================================
# Contains utility procedures for mega-widgets.
#
# Structure of the module:
#   - Namespace initialization
#   - Public utility procedures
#
# Copyright (c) 2000-2003  Csaba Nemethi (E-mail: csaba.nemethi@t-online.de)
#==============================================================================

package require Tcl 8
package require Tk  8

#
# Namespace initialization
# ========================
#

namespace eval mwutil {
    #
    # Public variables:
    #
    variable version	1.6
    variable library	[file dirname [info script]]

    #
    # Public procedures:
    #
    namespace export	wrongNumArgs defineKeyNav generateEvent \
			configure fullConfigOpt fullOpt enumOpts \
			setConfigVals configSubCmd attribSubCmd 
}

#
# Public utility procedures
# =========================
#

#------------------------------------------------------------------------------
# mwutil::wrongNumArgs
#
# Generates a "wrong # args" error message.
#------------------------------------------------------------------------------
proc mwutil::wrongNumArgs args {
    set optList {}
    foreach arg $args {
	lappend optList \"$arg\"
    }
    return -code error "wrong # args: should be [enumOpts $optList]"
}

#------------------------------------------------------------------------------
# mwutil::defineKeyNav
#
# For a given mega-widget class, the procedure defines the binding tag
# ${class}KeyNav as a partial replacement for "all", by substituting the
# scripts bound to the events <Key-Tab>, <Shift-Key-Tab>, and <<PrevWindow>>
# with new ones which propagate these events to the mega-widget of the given
# class containing the widget to which the event was reported.  (The event
# <Shift-Key-Tab> was replaced with <<PrevWindow>> in Tk 8.3.0.)  This tag is
# designed to be inserted before "all" in the list of binding tags of a
# descendant of a mega-widget of the specified class.
#------------------------------------------------------------------------------
proc mwutil::defineKeyNav class {
    foreach event {<Key-Tab> <Shift-Key-Tab> <<PrevWindow>>} {
	bind ${class}KeyNav $event \
	     [list mwutil::generateEvent %W $class $event]
    }
}

#------------------------------------------------------------------------------
# mwutil::generateEvent
#
# This procedure generates the given event for the mega-widget of the specified
# class containing the widget w if that mega-widget is not the only widget
# receiving the focus during keyboard traversal within its top-level widget.
#------------------------------------------------------------------------------
proc mwutil::generateEvent {w class event} {
    while {[string compare [winfo class $w] $class] != 0} {
	set w [winfo parent $w]
    }

    if {[string compare [tk_focusNext $w] $w] != 0} {
	focus $w				;# necessary on Windows
	event generate $w $event
    }

    return -code break ""
}

#------------------------------------------------------------------------------
# mwutil::configure
#
# Configures the widget win by processing the command-line arguments specified
# in optValPairs and, if the value of initialize is true, also those database
# options that don't match any command-line arguments.
#------------------------------------------------------------------------------
proc mwutil::configure {win configSpecsName configValsName \
			configCmd optValPairs initialize} {
    upvar $configSpecsName configSpecs
    upvar $configValsName configVals

    #
    # Process the command-line arguments
    #
    set cmdLineOpts {}
    set savedVals {}
    set failed 0
    set count [llength $optValPairs]
    foreach {opt val} $optValPairs {
	if {[catch {fullConfigOpt $opt configSpecs} result] != 0} {
	    set failed 1
	    break
	}
	if {$count == 1} {
	    set result "value for \"$opt\" missing"
	    set failed 1
	    break
	}
	set opt $result
	lappend cmdLineOpts $opt
	lappend savedVals $configVals($opt)
	if {[catch {eval $configCmd [list $win $opt $val]} result] != 0} {
	    set failed 1
	    break
	}
	incr count -2
    }

    if {$failed} {
	#
	# Restore the saved values
	#
	foreach opt $cmdLineOpts val $savedVals {
	    eval $configCmd [list $win $opt $val]
	}

	return -code error $result
    }

    if {$initialize} {
	#
	# Process those configuration options that were not
	# given as command-line arguments; use the corresponding
	# values from the option database if available
	#
	foreach opt [lsort [array names configSpecs]] {
	    if {[llength $configSpecs($opt)] == 1 ||
		[lsearch -exact $cmdLineOpts $opt] >= 0} {
		continue
	    }
	    set dbName [lindex $configSpecs($opt) 0]
	    set dbClass [lindex $configSpecs($opt) 1]
	    set dbValue [option get $win $dbName $dbClass]
	    if {[string compare $dbValue ""] != 0} {
		eval $configCmd [list $win $opt $dbValue]
	    } else {
		set default [lindex $configSpecs($opt) 3]
		eval $configCmd [list $win $opt $default]
	    }
	}
    }

    return ""
}

#------------------------------------------------------------------------------
# mwutil::fullConfigOpt
#
# Returns the full configuration option corresponding to the possibly
# abbreviated option opt.
#------------------------------------------------------------------------------
proc mwutil::fullConfigOpt {opt configSpecsName} {
    upvar $configSpecsName configSpecs

    if {[info exists configSpecs($opt)]} {
	if {[llength $configSpecs($opt)] == 1} {
	    return $configSpecs($opt)
	} else {
	    return $opt
	}
    }

    set optList [lsort [array names configSpecs]]
    set count 0
    foreach elem $optList {
	if {[string first $opt $elem] == 0} {
	    incr count
	    if {$count == 1} {
		set option $elem
	    } else {
		break
	    }
	}
    }

    switch $count {
	0 {
	    ### return -code error "unknown option \"$opt\""
	    return -code error \
		   "bad option \"$opt\": must be [enumOpts $optList]"
	}

	1 {
	    if {[llength $configSpecs($option)] == 1} {
		return $configSpecs($option)
	    } else {
		return $option
	    }
	}

	default {
	    ### return -code error "unknown option \"$opt\""
	    return -code error \
		   "ambiguous option \"$opt\": must be [enumOpts $optList]"
	}
    }
}

#------------------------------------------------------------------------------
# mwutil::fullOpt
#
# Returns the full option corresponding to the possibly abbreviated option opt.
#------------------------------------------------------------------------------
proc mwutil::fullOpt {kind opt optList} {
    if {[lsearch -exact $optList $opt] >= 0} {
	return $opt
    }

    set count 0
    foreach elem $optList {
	if {[string first $opt $elem] == 0} {
	    incr count
	    if {$count == 1} {
		set option $elem
	    } else {
		break
	    }
	}
    }

    switch $count {
	0 {
	    return -code error \
		   "bad $kind \"$opt\": must be [enumOpts $optList]"
	}

	1 {
	    return $option
	}

	default {
	    return -code error \
		   "ambiguous $kind \"$opt\": must be [enumOpts $optList]"
	}
    }
}

#------------------------------------------------------------------------------
# mwutil::enumOpts
#
# Returns a string consisting of the elements of the given list, separated by
# commas and spaces.
#------------------------------------------------------------------------------
proc mwutil::enumOpts optList {
    set optCount [llength $optList]
    set n 1
    foreach opt $optList {
	if {$n == 1} {
	    set str $opt
	} elseif {$n < $optCount} {
	    append str ", $opt"
	} else {
	    if {$optCount > 2} {
		append str ","
	    }
	    append str " or $opt"
	}

	incr n
    }

    return $str
}

#------------------------------------------------------------------------------
# mwutil::setConfigVals
#
# Sets the elements of the array specified by configValsName to the values
# returned by passing the widget name win and the relevant options to the
# command given by cgetCmd.
#------------------------------------------------------------------------------
proc mwutil::setConfigVals {win configSpecsName configValsName
			    cgetCmd argList} {
    upvar $configSpecsName configSpecs
    upvar $configValsName configVals

    set optList {}
    if {[llength $argList] == 0} {
	foreach opt [array names configSpecs] {
	    if {[llength $configSpecs($opt)] > 1} {
		lappend optList $opt
	    }
	}
    } else {
	foreach {opt val} $argList {
	    lappend optList [fullConfigOpt $opt configSpecs]
	}
    }

    foreach opt $optList {
	set configVals($opt) [eval $cgetCmd [list $win $opt]]
    }
}

#------------------------------------------------------------------------------
# mwutil::configSubCmd
#
# This procedure is invoked to process configuration subcommands.
#------------------------------------------------------------------------------
proc mwutil::configSubCmd {win configSpecsName configValsName
			   configCmd argList} {
    upvar $configSpecsName configSpecs
    upvar $configValsName configVals

    switch [llength $argList] {
	0 {
	    #
	    # Return a list describing all available configuration options
	    #
	    foreach opt [lsort [array names configSpecs]] {
		if {[llength $configSpecs($opt)] == 1} {
		    set alias $configSpecs($opt)
		    if {$::tk_version < 8.1} {
			set dbName [lindex $configSpecs($alias) 0]
			lappend result [list $opt $dbName]
		    } else {
			lappend result [list $opt $alias]
		    }
		} else {
		    set dbName [lindex $configSpecs($opt) 0]
		    set dbClass [lindex $configSpecs($opt) 1]
		    set default [lindex $configSpecs($opt) 3]
		    lappend result [list $opt $dbName $dbClass $default \
				    $configVals($opt)]
		}
	    }
	    return $result
	}

	1 {
	    #
	    # Return the description of the specified configuration option
	    #
	    set opt [fullConfigOpt [lindex $argList 0] configSpecs]
	    set dbName [lindex $configSpecs($opt) 0]
	    set dbClass [lindex $configSpecs($opt) 1]
	    set default [lindex $configSpecs($opt) 3]
	    return [list $opt $dbName $dbClass $default $configVals($opt)]
	}

	default {
	    #
	    # Set the specified configuration options to the given values
	    #
	    return [configure $win configSpecs configVals $configCmd $argList 0]
	}
    }
}

#------------------------------------------------------------------------------
# mwutil::attribSubCmd
#
# This procedure is invoked to process the attrib subcommand.
#------------------------------------------------------------------------------
proc mwutil::attribSubCmd {win argList} {
    set classNs [string tolower [winfo class $win]]
    upvar ::${classNs}::ns${win}::attribVals attribVals

    set argCount [llength $argList]
    switch $argCount {
	0 {
	    #
	    # Return the current list of attribute names and values
	    #
	    set result {}
	    foreach attr [lsort [array names attribVals]] {
		lappend result [list $attr $attribVals($attr)]
	    }
	    return $result
	}

	1 {
	    #
	    # Return the value of the specified attribute
	    #
	    set attr [lindex $argList 0]
	    if {[info exists attribVals($attr)]} {
		return $attribVals($attr)
	    } else {
		return ""
	    }
	}

	default {
	    #
	    # Set the specified attributes to the given values
	    #
	    if {$argCount % 2 != 0} {
		return -code error "value for \"[lindex $argList end]\" missing"
	    }
	    array set attribVals $argList
	    return ""
	}
    }
}

#==============================================================================
# Contains private procedures used in tablelist bindings.
#
# Copyright (c) 2000-2003  Csaba Nemethi (E-mail: csaba.nemethi@t-online.de)
#==============================================================================

#
# Binding tag Tablelist
# =====================
#

#------------------------------------------------------------------------------
# tablelist::addActiveTag
#
# This procedure is invoked when the tablelist widget win gains the keyboard
# focus.  It adds the "active" tag to the line that displays the active element
# of the widget in its body text child.
#------------------------------------------------------------------------------
proc tablelist::addActiveTag win {
    upvar ::tablelist::ns${win}::data data

    set line [expr {$data(activeIdx) + 1}]
    $data(body) tag add active $line.0 $line.end

    set data(ownsFocus) 1
}

#------------------------------------------------------------------------------
# tablelist::removeActiveTag
#
# This procedure is invoked when the tablelist widget win loses the keyboard
# focus.  It removes the "active" tag from the line that displays the active
# element of the widget in its body text child.
#------------------------------------------------------------------------------
proc tablelist::removeActiveTag win {
    upvar ::tablelist::ns${win}::data data

    set line [expr {$data(activeIdx) + 1}]
    $data(body) tag remove active $line.0 $line.end

    set data(ownsFocus) 0
}

#------------------------------------------------------------------------------
# tablelist::cleanup
#
# This procedure is invoked when the tablelist widget win is destroyed.  It
# executes some cleanup operations.
#------------------------------------------------------------------------------
proc tablelist::cleanup win {
    upvar ::tablelist::ns${win}::data data

    #
    # Cancel the execution of all delayed adjustSeps, makeStripes,
    # stretchColumns, synchronize, and redisplay commands
    #
    foreach afterId {sepsId stripesId stretchId syncId redispId} {
	if {[info exists data($afterId)]} {
	    after cancel $data($afterId)
	}
    }

    #
    # If there is a list variable associated with the
    # widget then remove the trace set on this variable
    #
    if {$data(hasListVar)} {
	trace vdelete ::$data(-listvariable) wu $data(listVarTraceCmd)
    }

    namespace delete ::tablelist::ns$win
    catch {rename ::$win ""}
}

#
# Binding tag TablelistBody
# =========================
#

#------------------------------------------------------------------------------
# tablelist::defineTablelistBody
#
# Defines the binding tag TablelistBody to have the same events as Listbox and
# the binding scripts obtained from those of Listbox by replacing the widget %W
# with its parent as well as the %x and %y fields with the corresponding
# coordinates relative to the parent.
#------------------------------------------------------------------------------
proc tablelist::defineTablelistBody {} {
    bind TablelistBody <Button-1> {
	if {[winfo exists %W]} {
	    tablelist::condEditContainingCell %W %x %y
	}
    }

    foreach event [bind Listbox] {
	set script [strMap {
	    %W $tablelist::W  %x $tablelist::x  %y $tablelist::y
	    tkListboxAutoScan   tablelist::tablelistAutoScan
	    tk::ListboxAutoScan tablelist::tablelistAutoScan
	} [bind Listbox $event]]

	bind TablelistBody $event +[format {
	    if {[winfo exists %%W]} {
		set tablelist::W [winfo parent %%W]
		set tablelist::x [expr {%%x + [winfo x %%W]}]
		set tablelist::y [expr {%%y + [winfo y %%W]}]
		%s
	    }
	} $script]
    }
}

#------------------------------------------------------------------------------
# tablelist::tablelistAutoScan
#
# This is a modified version of the Tk library procedure tk(::)ListboxAutoScan.
# It is invoked when the mouse leaves the body text child of a tablelist
# widget.  It scrolls the child and reschedules itself as an after command so
# that the child continues to scroll until the mouse moves back into the window
# or the mouse button is released.
#------------------------------------------------------------------------------
proc tablelist::tablelistAutoScan win {
    if {![winfo exists $win]} {
	return ""
    }

    if {[array exists ::tk::Priv]} {
	set x $::tk::Priv(x)
	set y $::tk::Priv(y)
    } else {
	set x $::tkPriv(x)
	set y $::tkPriv(y)
    }

    set w [::$win bodypath]
    set _x [expr {$x - [winfo x $w]}]
    set _y [expr {$y - [winfo y $w]}]

    if {$_y >= [winfo height $w]} {
	yviewSubCmd $win {scroll 1 units}
    } elseif {$_y < 0} {
	yviewSubCmd $win {scroll -1 units}
    } elseif {$_x >= [winfo width $w]} {
	xviewSubCmd $win {scroll 2 units}
    } elseif {$_x < 0} {
	xviewSubCmd $win {scroll -2 units}
    } else {
	return ""
    }

    if {[array exists ::tk::Priv]} {
	tk::ListboxMotion $win [rowIndex $win @$x,$y 1]
	set ::tk::Priv(afterId) \
	    [after 50 [list tablelist::tablelistAutoScan $win]]
    } else {
	tkListboxMotion $win [rowIndex $win @$x,$y 1]
	set ::tkPriv(afterId) \
	    [after 50 [list tablelist::tablelistAutoScan $win]]
    }
}

#------------------------------------------------------------------------------
# tablelist::condEditContainingCell
#
# This procedure is invoked when mouse button 1 is pressed in the body w of a
# tablelist widget or in one of its separator frames.  If the mouse click
# occurred inside an editable cell and the latter is not already being edited,
# then the procedure starts the interactive editing in that cell.  Otherwise it
# finishes a possibly active cell editing.
#------------------------------------------------------------------------------
proc tablelist::condEditContainingCell {w x y} {
    set win [winfo parent $w]
    upvar ::tablelist::ns${win}::data data
    synchronize $win

    #
    # Get the containing cell from the coordinates relative to the parent
    #
    incr x [winfo x $w]
    incr y [winfo y $w]
    set row [containingSubCmd $win $y]
    set col [containingcolumnSubCmd $win $x]

    if {$row >= 0 && $col >= 0 && [isCellEditable $win $row $col]} {
	#
	# Get the coordinates relative to the
	# tablelist body and invoke editcellSubCmd
	#
	set w $data(body)
	incr x -[winfo x $w]
	incr y -[winfo y $w]
	scan [$w index @$x,$y] %d.%d line charPos
	editcellSubCmd $win $row $col 0 $charPos
    } else {
	#
	# Finish a possibly active cell editing
	#
	if {$data(editRow) >= 0} {
	    finisheditingSubCmd $win
	}
    }
}

#
# Binding tags TablelistLabel, TablelistSubLabel, and TablelistArrow
# ==================================================================
#

#------------------------------------------------------------------------------
# tablelist::defineTablelistSubLabel
#
# Defines the binding tag TablelistSubLabel (for children of tablelist labels)
# to have the same events as TablelistLabel and the binding scripts obtained
# from those of TablelistLabel by replacing the widget %W with its parent as
# well as the %x and %y fields with the corresponding coordinates relative to
# the parent.
#------------------------------------------------------------------------------
proc tablelist::defineTablelistSubLabel {} {
    foreach event [bind TablelistLabel] {
	set script [strMap {
	    %W $tablelist::W  %x $tablelist::x  %y $tablelist::y
	} [bind TablelistLabel $event]]

	bind TablelistSubLabel $event [format {
	    set tablelist::W [winfo parent %%W]
	    set tablelist::x [expr {%%x + [winfo x %%W]}]
	    set tablelist::y [expr {%%y + [winfo y %%W]}]
	    %s
	} $script]
    }
}

#------------------------------------------------------------------------------
# tablelist::defineTablelistSubLabel
#
# Defines the binding tag TablelistArrow to have the same events as
# TablelistLabel and the binding scripts obtained from those of TablelistLabel
# by replacing the widget %W with the containing label as well as the %x and %y
# fields with the corresponding coordinates relative to the label
#------------------------------------------------------------------------------
proc tablelist::defineTablelistArrow {} {
    foreach event [bind TablelistLabel] {
	set script [strMap {
	    %W $tablelist::W  %x $tablelist::x  %y $tablelist::y
	} [bind TablelistLabel $event]]

	bind TablelistArrow $event [format {
	    if {$::tk_version < 8.4} {
		regexp {^.+ -in (.+)$} [place info %%W] \
		       tablelist::dummy tablelist::W
	    } else {
		set tablelist::W [lindex [place configure %%W -in] end]
	    }
	    set tablelist::x \
		[expr {%%x + [winfo x %%W] - [winfo x $tablelist::W]}]
	    set tablelist::y \
		[expr {%%y + [winfo y %%W] - [winfo y $tablelist::W]}]
	    %s
	} $script]
    }
}

#------------------------------------------------------------------------------
# tablelist::labelEnter
#
# This procedure is invoked when the mouse pointer enters the header label w of
# a tablelist widget, or is moving within that label.  It updates the cursor,
# depending on whether the pointer is on the right border of the label or not.
#------------------------------------------------------------------------------
proc tablelist::labelEnter {w x} {
    parseLabelPath $w win col
    upvar ::tablelist::ns${win}::data data

    configLabel $w -cursor $data(-cursor)
    if {$data(isDisabled)} {
	return ""
    }

    if {$data(-resizablecolumns) && $data($col-resizable) &&
	$x >= [winfo width $w] - [$w cget -borderwidth] - 4} {
	configLabel $w -cursor $data(-resizecursor)
    }
}

#------------------------------------------------------------------------------
# tablelist::labelB1Down
#
# This procedure is invoked when mouse button 1 is pressed in the header label
# w of a tablelist widget.  If the pointer is on the right border of the label
# then the procedure records its x-coordinate relative to the label, the width
# of the column, and some other data needed later.  Otherwise it saves the
# label's relief so it can be restored later, and changes the relief to sunken.
#------------------------------------------------------------------------------
proc tablelist::labelB1Down {w x} {
    parseLabelPath $w win col
    upvar ::tablelist::ns${win}::data data

    if {$data(isDisabled) ||
	[info exists data(x)]} {		;# resize operation in progress
	return ""
    }

    set data(labelClicked) 1
    set labelWidth [winfo width $w]

    if {$data(-resizablecolumns) && $data($col-resizable) &&
	$x >= $labelWidth - [$w cget -borderwidth] - 4} {
	set data(x) $x

	set data(oldStretchedColWidth) [expr {$labelWidth - 2*$data(charWidth)}]
	set data(oldColDelta) $data($col-delta)
	set data(configColWidth) [lindex $data(-columns) [expr {3*$col}]]

	if {$col == $data(arrowCol)} {
	    set data(minColWidth) $data(arrowWidth)
	} else {
	    set data(minColWidth) 1
	}

	set topWin [winfo toplevel $win]
	set data(topBindEsc) [bind $topWin <Escape>]
	bind $topWin <Escape> [list tablelist::escape [strMap {% %%} $win] $col]
    } else {
	set data(inClickedLabel) 1
	set data(relief) [$w cget -relief]

	if {[info exists data($col-labelcommand)] ||
	    [string compare $data(-labelcommand) ""] != 0} {
	    set data(changeRelief) 1
	    configLabel $w -relief sunken
	} else {
	    set data(changeRelief) 0
	}

	if {$data(-movablecolumns)} {
	    set topWin [winfo toplevel $win]
	    set data(topBindEsc) [bind $topWin <Escape>]
	    bind $topWin <Escape> \
		 [list tablelist::escape [strMap {% %%} $win] $col]
	}
    }
}

#------------------------------------------------------------------------------
# tablelist::labelB1Motion
#
# This procedure is invoked to process mouse motion events in the header label
# w of a tablelist widget while button 1 is down.  If this event occured during
# a column resize operation then the procedure computes the difference between
# the pointer's new x-coordinate relative to that label and the one recorded by
# the last invocation of labelB1Down, and adjusts the width of the
# corresponding column accordingly.  Otherwise a horizontal scrolling is
# performed if needed, and the would-be target position of the clicked label is
# visualized if the columns are movable.
#------------------------------------------------------------------------------
proc tablelist::labelB1Motion {w x y} {
    parseLabelPath $w win col
    upvar ::tablelist::ns${win}::data data

    if {!$data(labelClicked)} {
	return ""
    }

    if {[info exists data(x)]} {		;# resize operation in progress
	set width [expr {$data(oldStretchedColWidth) + $x - $data(x)}]
	if {$width >= $data(minColWidth)} {
	    set idx [expr {3*$col}]
	    set data(-columns) [lreplace $data(-columns) $idx $idx -$width]
	    set idx [expr {2*$col}]
	    set data(colList) [lreplace $data(colList) $idx $idx $width]
	    set data($col-delta) 0
	    adjustColumns $win {} 0
	    update idletasks
	    redisplayCol $win $col [rowIndex $win @0,0 0] \
				   [rowIndex $win @0,[winfo height $win] 0]
	}
    } else {
	#
	# Scroll the window horizontally if needed
	#
	set scroll 0
	set X [expr {[winfo rootx $w] + $x}]
	set hdrX [winfo rootx $data(hdr)]
	set rightX [expr {$hdrX + [winfo width $data(hdr)]}]
	if {($X >= $rightX) &&
	    (![info exists data(X)] || $data(X) < $rightX)} {
	    set scroll 1
	} elseif {($X < $hdrX) &&
		  (![info exists data(X)] || $data(X) >= $hdrX)} {
	    set scroll 1
	}
	set data(X) $X
	if ($scroll) {
	    horizAutoScan $win
	}

	if {$x >= 0 && $x < [winfo width $w] &&
	    $y >= 0 && $y < [winfo height $w]} {
	    #
	    # The following code is needed because the event can also
	    # occur in the canvas displaying an up- or down-arrow
	    #
	    set data(inClickedLabel) 1
	    $data(hdrTxtFrCanv) configure -cursor $data(-cursor)
	    configLabel $w -cursor $data(-cursor)
	    if {$data(changeRelief)} {
		configLabel $w -relief sunken
	    }

	    place forget $data(hdrGap)
	} else {
	    #
	    # The following code is needed because the event can also
	    # occur in the canvas displaying an up- or down-arrow
	    #
	    set data(inClickedLabel) 0
	    configLabel $w -relief $data(relief)

	    if {$data(-movablecolumns)} {
		$data(hdrTxtFrCanv) configure -cursor $data(-movecolumncursor)
		configLabel $w -cursor $data(-movecolumncursor)

		#
		# Get the target column index and visualize the
		# would-be target position of the clicked label
		#
		set contW [winfo containing -displayof $w $X [winfo rooty $w]]
		parseLabelPath $contW dummy targetCol
		if {[info exists targetCol]} {
		    set master $contW
		    if {$X < [winfo rootx $contW] + [winfo width $contW]/2} {
			set relx 0.0
		    } else {
			incr targetCol
			set relx 1.0
		    }
		} elseif {[string compare $contW $data(hdrGap)] == 0} {
		    set targetCol $data(targetCol)
		    set master $data(master)
		    set relx $data(relx)
		} elseif {$X < [winfo rootx $w]} {
		    for {set targetCol 0} {$targetCol < $data(colCount)} \
			{incr targetCol} {
			if {!$data($targetCol-hide)} {
			    break
			}
		    }
		    set master $data(hdrTxtFr)
		    set relx 0.0
		} else {
		    for {set targetCol $data(lastCol)} {$targetCol >= 0} \
			{incr targetCol -1} {
			if {!$data($targetCol-hide)} {
			    break
			}
		    }
		    incr targetCol
		    set master $data(hdrTxtFr)
		    set relx 1.0
		}
		set data(targetCol) $targetCol
		set data(master) $master
		set data(relx) $relx
		$data(hdrTxtFrCanv) configure -cursor $data(-movecolumncursor)
		configLabel $w -cursor $data(-movecolumncursor)
		place $data(hdrGap) -in $master -anchor n -bordermode outside \
				    -relheight 1.0 -relx $relx
	    }
	}
    }
}

#------------------------------------------------------------------------------
# tablelist::labelB1Enter
#
# This procedure is invoked when the mouse pointer enters the header label w of
# a tablelist widget while mouse button 1 is down.  If the label was not
# previously clicked then nothing happens.  Otherwise, if this event occured
# during a column resize operation then the procedure updates the mouse cursor
# accordingly.  Otherwise it changes the label's relief to sunken.
#------------------------------------------------------------------------------
proc tablelist::labelB1Enter w {
    parseLabelPath $w win col
    upvar ::tablelist::ns${win}::data data

    if {!$data(labelClicked)} {
	return ""
    }

    configLabel $w -cursor $data(-cursor)

    if {[info exists data(x)]} {		;# resize operation in progress
	configLabel $w -cursor $data(-resizecursor)
    } else {
	set data(inClickedLabel) 1
	if {$data(changeRelief)} {
	    configLabel $w -relief sunken
	}
    }
}

#------------------------------------------------------------------------------
# tablelist::labelB1Leave
#
# This procedure is invoked when the mouse pointer leaves the header label w of
# a tablelist widget while mouse button 1 is down.  If the label was not
# previously clicked then nothing happens.  Otherwise, if no column resize
# operation is in progress then the procedure restores the label's relief, and,
# if the columns are movable, then it changes the mouse cursor, too.
#------------------------------------------------------------------------------
proc tablelist::labelB1Leave {w x y} {
    parseLabelPath $w win col
    upvar ::tablelist::ns${win}::data data

    if {!$data(labelClicked) ||
	[info exists data(x)]} {		;# resize operation in progress
	return ""
    }

    #
    # The following code is needed because the event can also
    # occur in the canvas displaying an up- or down-arrow
    #
    if {$x >= 0 && $x < [winfo width $w] &&
	$y >= 0 && $y < [winfo height $w]} {
	return ""
    }

    set data(inClickedLabel) 0
    configLabel $w -relief $data(relief)
}

#------------------------------------------------------------------------------
# tablelist::labelB1Up
#
# This procedure is invoked when mouse button 1 is released, if it was
# previously clicked in a label of the tablelist widget win.  If this event
# occured during a column resize operation then the procedure redisplays the
# columns and stretches the stretchable ones.  Otherwise, if the mouse button
# was released in the previously clicked label then the procedure restores the
# label's relief and invokes the command specified with the -labelcommand
# configuration option, passing to it the widget name and the column number as
# arguments.  Otherwise the column of the previously clicked label is moved
# before the column containing the mouse cursor or to its right, if the columns
# are movable.
#------------------------------------------------------------------------------
proc tablelist::labelB1Up {w X} {
    parseLabelPath $w win col
    upvar ::tablelist::ns${win}::data data

    if {!$data(labelClicked)} {
	return ""
    }

    if {[info exists data(x)]} {		;# resize operation in progress
	configLabel $w -cursor $data(-cursor)
	bind [winfo toplevel $win] <Escape> $data(topBindEsc)
	redisplayWhenIdle $win
	if {$data(-width) <= 0} {
	    $data(hdr) configure -width $data(hdrPixels)
	} elseif {[info exists data(stretchableCols)] &&
		  [lsearch -exact $data(stretchableCols) $col] >= 0} {
	    set oldColWidth \
		[expr {$data(oldStretchedColWidth) - $data(oldColDelta)}]
	    set stretchedColWidth \
		[expr {[winfo width $w] - 2*$data(charWidth)}]
	    if {$oldColWidth < $data(stretchablePxls) &&
		$stretchedColWidth < $oldColWidth + $data(delta)} {
		#
		# Compute the new column width, using the following equations:
		#
		# $stretchedColWidth = $colWidth + $colDelta
		# $colDelta =
		#    ($data(delta) - $colWidth + $oldColWidth) * $colWidth /
		#    ($data(stretchablePxls) + $colWidth - $oldColWidth)
		#
		set colWidth [expr {
		    $stretchedColWidth *
		    ($data(stretchablePxls) - $oldColWidth) /
		    ($data(stretchablePxls) + $data(delta) - $stretchedColWidth)
		}]
		if {$colWidth < 1} {
		    set colWidth 1
		}
		set idx [expr {3*$col}]
		set data(-columns) \
		    [lreplace $data(-columns) $idx $idx -$colWidth]
		set idx [expr {2*$col}]
		set data(colList) [lreplace $data(colList) $idx $idx $colWidth]
		set data($col-delta) [expr {$stretchedColWidth - $colWidth}]
	    }
	}
	stretchColumns $win $col
	unset data(x)
    } else {
	if {[info exists data(X)]} {
	    unset data(X)
	}
    	if {$data(-movablecolumns)} {
	    bind [winfo toplevel $win] <Escape> $data(topBindEsc)
	    place forget $data(hdrGap)
	}
	if {$data(inClickedLabel)} {
	    configLabel $w -relief $data(relief)
	    if {[info exists data($col-labelcommand)]} {
		uplevel #0 $data($col-labelcommand) [list $win $col]
	    } elseif {[string compare $data(-labelcommand) ""] != 0} {
		uplevel #0 $data(-labelcommand) [list $win $col]
	    }
	} elseif {$data(-movablecolumns)} {
	    $data(hdrTxtFrCanv) configure -cursor $data(-cursor)
	    if {$data(targetCol) != $col && $data(targetCol) != $col + 1} {
		movecolumnSubCmd $win $col $data(targetCol)
	    }
	}
    }

    set data(labelClicked) 0
}

#------------------------------------------------------------------------------
# tablelist::labelB3Down
#
# This procedure is invoked when mouse button 3 is pressed in the header label
# w of a tablelist widget.  It configures the width of the given column to be
# just large enough to hold all the elements (including the label).
#------------------------------------------------------------------------------
proc tablelist::labelB3Down w {
    parseLabelPath $w win col
    upvar ::tablelist::ns${win}::data data

    if {!$data(isDisabled) &&
	$data(-resizablecolumns) && $data($col-resizable)} {
	doColConfig $col $win -width 0
    }
}

#------------------------------------------------------------------------------
# tablelist::escape
#
# This procedure is invoked to process <Escape> events in the top-level window
# containing the tablelist widget win during a column resize or move operation.
# The procedure cancels the action in progress and, in case of column resizing,
# it restores the initial width of the respective column.
#------------------------------------------------------------------------------
proc tablelist::escape {win col} {
    upvar ::tablelist::ns${win}::data data

    set w $data(hdrTxtFrLbl)$col
    if {[info exists data(x)]} {		;# resize operation in progress
	configLabel $w -cursor $data(-cursor)
	update idletasks
	bind [winfo toplevel $win] <Escape> $data(topBindEsc)
	set idx [expr {3*$col}]
	setupColumns $win [lreplace $data(-columns) $idx $idx \
				    $data(configColWidth)] 0
	adjustColumns $win $col 1
	redisplayCol $win $col [rowIndex $win @0,0 0] \
			       [rowIndex $win @0,[winfo height $win] 0]
	unset data(x)
	set data(labelClicked) 0
    } elseif {!$data(inClickedLabel)} {
	configLabel $w -cursor $data(-cursor)
	$data(hdrTxtFrCanv) configure -cursor $data(-cursor)
	bind [winfo toplevel $win] <Escape> $data(topBindEsc)
	place forget $data(hdrGap)
	if {[info exists data(X)]} {
	    unset data(X)
	}
	set data(labelClicked) 0
    }
}

#------------------------------------------------------------------------------
# tablelist::parseLabelPath
#
# Extracts the path name of the tablelist widget as well as the column number
# from the path name w of a header label.
#------------------------------------------------------------------------------
proc tablelist::parseLabelPath {w winName colName} {
    upvar $winName win $colName col

    regexp {^(.+)\.hdr\.t\.f\.l([0-9]+)$} $w dummy win col
}

#------------------------------------------------------------------------------
# tablelist::redisplayCol
#
# Redisplays the elements of the col'th column of the tablelist widget win, in
# the range specified by first and last.
#------------------------------------------------------------------------------
proc tablelist::redisplayCol {win col first last} {
    upvar ::tablelist::ns${win}::data data

    if {$data($col-hide) || $first < 0} {
	return ""
    }

    set snipStr $data(-snipstring)
    set fmtCmdFlag [lindex $data(fmtCmdFlagList) $col]
    set colFont [lindex $data(colFontList) $col]

    set w $data(body)
    set pixels [lindex $data(colList) [expr {2*$col}]]
    if {$pixels != 0} {				;# convention: static width
	incr pixels $data($col-delta)
    }
    set alignment [lindex $data(colList) [expr {2*$col + 1}]]

    for {set idx $first; set line [expr {$first + 1}]} {$idx <= $last} \
	{incr idx; incr line} {
	if {$idx == $data(editRow) && $col == $data(editCol)} {
	    continue
	}

	#
	# Adjust the cell text and the image width
	#
	set item [lindex $data(itemList) $idx]
	set text [lindex $item $col]
	if {$fmtCmdFlag} {
	    set text [uplevel #0 $data($col-formatcommand) [list $text]]
	}
	set text [strToDispStr $text]
	set key [lindex $item end]
	if {[info exists data($key-$col-image)]} {
	    set image $data($key-$col-image)
	    set imageWidth [image width $image]
	} else {
	    set image ""
	    set imageWidth 0
	}
	if {[info exists data($key-$col-font)]} {
	    set cellFont $data($key-$col-font)
	} elseif {[info exists data($key-font)]} {
	    set cellFont $data($key-font)
	} else {
	    set cellFont $colFont
	}
	adjustElem $win text imageWidth $cellFont \
		   $pixels $alignment $snipStr

	#
	# Delete the old cell contents between the
	# two tabs, and insert the text and the image
	#
	findCellTabs $win $line $col tabIdx1 tabIdx2
	$w delete $tabIdx1+1c $tabIdx2
	insertElem $w $tabIdx1+1c $text $image $imageWidth $alignment
    }
}

#------------------------------------------------------------------------------
# tablelist::horizAutoScan
#
# This procedure is invoked when the mouse leaves the header frame of a
# tablelist widget.  It scrolls the child and reschedules itself as an after
# command so that the child continues to scroll until the mouse moves back into
# the window or the mouse button is released.
#------------------------------------------------------------------------------
proc tablelist::horizAutoScan win {
    upvar ::tablelist::ns${win}::data data

    if {![winfo exists $win] || ![info exists data(X)]} {
	return ""
    }

    set X $data(X)
    set hdrX [winfo rootx $data(hdr)]
    set rightX [expr {$hdrX + [winfo width $data(hdr)]}]

    if {$X >= $rightX} {
	xviewSubCmd $win {scroll 2 units}
    } elseif {$X < $hdrX} {
	xviewSubCmd $win {scroll -2 units}
    } else {
	return ""
    }

    after 50 [list tablelist::horizAutoScan $win]
}

#==============================================================================
# Contains private configuration procedures for tablelist widgets.
#
# Copyright (c) 2000-2003  Csaba Nemethi (E-mail: csaba.nemethi@t-online.de)
#==============================================================================

#------------------------------------------------------------------------------
# tablelist::extendConfigSpecs
#
# Extends the elements of the array configSpecs.
#------------------------------------------------------------------------------
proc tablelist::extendConfigSpecs {} {
    variable helpLabel
    variable configSpecs
    variable winSys

    #
    # Append the default values of the configuration options
    # of a temporary, invisible listbox widget to the values
    # of the corresponding elements of the array configSpecs
    #
    set helpListbox .__helpListbox
    for {set n 0} {[winfo exists $helpListbox]} {incr n} {
	set helpListbox .__helpListbox$n
    }
    listbox $helpListbox
    foreach configSet [$helpListbox configure] {
	if {[llength $configSet] != 2} {
	    set opt [lindex $configSet 0]
	    if {[info exists configSpecs($opt)]} {
		lappend configSpecs($opt) [lindex $configSet 3]
	    }
	}
    }
    destroy $helpListbox

    #
    # Append the default values of some configuration options
    # of an invisible label widget to the values of the
    # corresponding -label* elements of the array configSpecs
    #
    set helpLabel .__helpLabel
    for {set n 0} {[winfo exists $helpLabel]} {incr n} {
	set helpLabel .__helpLabel$n
    }
    label $helpLabel
    foreach optTail {font height} {
	set configSet [$helpLabel config -$optTail]
	lappend configSpecs(-label$optTail) [lindex $configSet 3]
    }
    if {[catch {$helpLabel config -state disabled}] == 0 &&
	[catch {$helpLabel config -state normal}] == 0 &&
	[catch {$helpLabel config -disabledforeground} configSet] == 0} {
	lappend configSpecs(-labeldisabledforeground) [lindex $configSet 3]
    } else {
	unset configSpecs(-labeldisabledforeground)
    }
    if {[string compare $winSys win32] == 0} {
	lappend configSpecs(-labelpady) 0
    } else {
	set configSet [$helpLabel config -pady]
	lappend configSpecs(-labelpady) [lindex $configSet 3]
    }

    #
    # Steal the default values of some configuration
    # options from a temporary, invisible button widget
    #
    set helpButton .__helpButton
    for {set n 0} {[winfo exists $helpButton]} {incr n} {
	set helpButton .__helpButton$n
    }
    button $helpButton
    foreach opt {-disabledforeground -state} {
	set configSet [$helpButton config $opt]
	lappend configSpecs($opt) [lindex $configSet 3]
    }
    foreach optTail {background foreground} {
	set configSet [$helpButton config -$optTail]
	lappend configSpecs(-label$optTail) [lindex $configSet 3]
    }
    if {[string compare $winSys classic] == 0 ||
	[string compare $winSys aqua] == 0} {
	lappend configSpecs(-labelborderwidth) 1
    } else {
	set configSet [$helpButton config -borderwidth]
	lappend configSpecs(-labelborderwidth) [lindex $configSet 3]
    }
    destroy $helpButton

    #
    # Extend the remaining elements of the array configSpecs
    #
    lappend configSpecs(-activestyle)		underline
    lappend configSpecs(-arrowcolor)		{}
    lappend configSpecs(-arrowdisabledcolor)	{}
    lappend configSpecs(-columns)		{}
    lappend configSpecs(-editendcommand)	{}
    lappend configSpecs(-editstartcommand)	{}
    lappend configSpecs(-incrarrowtype)		up
    lappend configSpecs(-labelcommand)		{}
    lappend configSpecs(-labelrelief)		raised
    lappend configSpecs(-listvariable)		{}
    lappend configSpecs(-movablecolumns)	0
    lappend configSpecs(-movecolumncursor)	icon
    lappend configSpecs(-resizablecolumns)	1
    lappend configSpecs(-resizecursor)		sb_h_double_arrow
    lappend configSpecs(-showarrow)		1
    lappend configSpecs(-showlabels)		1
    lappend configSpecs(-showseparators)	0
    lappend configSpecs(-snipstring)		...
    lappend configSpecs(-sortcommand)		{}
    lappend configSpecs(-stretch)		{}
    lappend configSpecs(-stripebackground)	{}
    lappend configSpecs(-stripeforeground)	{}
    lappend configSpecs(-stripeheight)		1
    lappend configSpecs(-targetcolor)		black
}

#------------------------------------------------------------------------------
# tablelist::doConfig
#
# Applies the value val of the configuration option opt to the tablelist widget
# win.
#------------------------------------------------------------------------------
proc tablelist::doConfig {win opt val} {
    variable helpLabel
    variable configSpecs
    upvar ::tablelist::ns${win}::data data

    #
    # Apply the value to the widget(s) corresponding to the given option
    #
    switch [lindex $configSpecs($opt) 2] {
	c {
	    #
	    # Apply the value to all children and save the
	    # properly formatted value of val in data($opt)
	    #
	    foreach w [winfo children $win] {
		if {[regexp {^(body|hdr|sep[0-9]+)$} [winfo name $w]]} {
		    $w configure $opt $val
		}
	    }
	    $data(hdrTxt) configure $opt $val
	    $data(hdrLbl) configure $opt $val
	    foreach w [winfo children $data(hdrTxtFr)] {
		$w configure $opt $val
		foreach c [winfo children $w] {
		    $c configure $opt $val
		}
	    }
	    set data($opt) [$data(hdrLbl) cget $opt]
	}

	b {
	    #
	    # Apply the value to the body text widget and save
	    # the properly formatted value of val in data($opt)
	    #
	    set w $data(body)
	    $w configure $opt $val
	    set data($opt) [$w cget $opt]

	    switch -- $opt {
		-background {
		    #
		    # Apply the value to the frame (because of
		    # the shadow colors of its 3-D border), to
		    # the separators, and to the "disabled" tag
		    #
		    $win configure $opt $val
		    foreach c [winfo children $win] {
			if {[regexp {^sep[0-9]+$} [winfo name $c]]} {
			    $c configure $opt $val
			}
		    }
		    $w tag configure disabled $opt $val
		}
		-font {
		    #
		    # Apply the value to the listbox child, rebuild the
		    # lists of the column fonts and tag names, configure
		    # the entry widget if present, adjust the columns, and
		    # make sure the items will be redisplayed at idle time
		    #
		    $data(lb) configure $opt $val
		    set data(charWidth) [font measure $val -displayof $win 0]
		    makeColFontAndTagLists $win
		    if {$data(editRow) >= 0} {
			set item [lindex $data(itemList) $data(editRow)]
			set key [lindex $item end]
			$data(bodyFrEnt) configure -font \
			    [cellFont $win $key $data(editCol)]
		    }
		    adjustColumns $win allCols 1
		    redisplayWhenIdle $win
		}
		-foreground {
		    #
		    # Apply the value to the "disabled" tag if needed
		    #
		    if {[string compare $data(-disabledforeground) ""] == 0} {
			$w tag configure disabled $opt $val
		    }
		}
	    }
	}

	h {
	    #
	    # Apply the value to the header text widget and save
	    # the properly formatted value of val in data($opt)
	    #
	    set w $data(hdrTxt)
	    $w configure $opt $val
	    set data($opt) [$w cget $opt]
	}

	l {
	    #
	    # Apply the value to all not individually configured labels
	    # and save the properly formatted value of val in data($opt)
	    #
	    set optTail [string range $opt 6 end]	;# remove the -label
	    $data(hdrLbl) configure -$optTail $val
	    for {set col 0} {$col < $data(colCount)} {incr col} {
		set w $data(hdrTxtFrLbl)$col
		if {![info exists data($col$opt)]} {
		    configLabel $w -$optTail $val
		}
	    }
	    set data($opt) [$data(hdrLbl) cget -$optTail]

	    switch -- $opt {
		-labelbackground {
		    #
		    # Apply the value to the children of the header frame and
		    # conditionally both to the children of the labels (if
		    # any) and to the canvas displaying an up- or down-arrow
		    #
		    $data(hdrTxt) configure -$optTail $val
		    for {set col 0} {$col < $data(colCount)} {incr col} {
			set w $data(hdrTxtFrLbl)$col
			if {![info exists data($col$opt)]} {
			    foreach c [winfo children $w] {
				$c configure -$optTail $val
			    }
			}
		    }
		    if {$data(arrowCol) >= 0 &&
			![info exists data($data(arrowCol)$opt)]} {
			configCanvas $win
		    }
		}
		-labelborderwidth {
		    #
		    # Adjust the columns (including
		    # the height of the header frame)
		    #
		    adjustColumns $win allLabels 1
		}
		-labeldisabledforeground {
		    #
		    # Apply the value to the children of the labels (if any)
		    #
		    foreach w [winfo children $data(hdrTxtFr)] {
			foreach c [winfo children $w] {
			    $c configure $opt $val
			}
		    }
		}
		-labelfont {
		    #
		    # Conditionally apply the value to the children of
		    # the labels (if any), conditionally resize the canvas
		    # displaying an up- or down-arrow, and adjust the
		    # columns (including the height of the header frame)
		    #
		    for {set col 0} {$col < $data(colCount)} {incr col} {
			set w $data(hdrTxtFrLbl)$col
			if {![info exists data($col$opt)]} {
			    foreach c [winfo children $w] {
				$c configure -$optTail $val
			    }
			}
		    }
		    if {$data(arrowCol) >= 0 &&
			![info exists data($data(arrowCol)$opt)]} {
			configCanvas $win
			drawArrows $win
		    }
		    adjustColumns $win allLabels 1
		}
		-labelforeground {
		    #
		    # Conditionally apply the value to
		    # the children of the labels (if any)
		    #
		    for {set col 0} {$col < $data(colCount)} {incr col} {
			set w $data(hdrTxtFrLbl)$col
			if {![info exists data($col$opt)]} {
			    foreach c [winfo children $w] {
				$c configure -$optTail $val
			    }
			}
		    }
		}
		-labelheight -
		-labelpady {
		    #
		    # Adjust the height of the header frame
		    #
		    adjustHeaderHeight $win
		}
	    }
	}

	f {
	    #
	    # Apply the value to the frame and save the
	    # properly formatted value of val in data($opt)
	    #
	    $win configure $opt $val
	    set data($opt) [$win cget $opt]
	}

	w {
	    switch -- $opt {
		-activestyle {
		    #
		    # Configure the "active" tag and save the
		    # properly formatted value of val in data($opt)
		    #
		    variable activeStyles
		    set val [mwutil::fullOpt "active style" $val $activeStyles]
		    set w $data(body)
		    switch $val {
			frame {
			    $w tag configure active -relief solid -underline 0
			}
			none {
			    $w tag configure active -relief flat -underline 0
			}
			underline {
			    $w tag configure active -relief flat -underline 1
			}
		    }
		    set data($opt) $val
		}
		-arrowcolor {
		    #
		    # Set the color of the normal arrow and save the
		    # properly formatted value of val in data($opt)
		    #
		    set data($opt) [fillArrow $data(hdrTxtFrCanv) normal $val]
		}
		-arrowdisabledcolor {
		    #
		    # Set the color of the disabled arrow and save the
		    # properly formatted value of val in data($opt)
		    #
		    set data($opt) [fillArrow $data(hdrTxtFrCanv) disabled $val]
		}
		-columns {
		    #
		    # Set up and adjust the columns, rebuild
		    # the lists of the column fonts and tag
		    # names, and redisplay the items
		    #
		    setupColumns $win $val 1
		    adjustColumns $win allCols 1
		    makeColFontAndTagLists $win
		    redisplay $win
		}
		-disabledforeground {
		    #
		    # Configure the "disabled" tag in the body text widget and
		    # save the properly formatted value of val in data($opt)
		    #
		    set w $data(body)
		    if {[string compare $val ""] == 0} {
			$w tag configure disabled -fgstipple gray50 \
				-foreground $data(-foreground)
			set data($opt) ""
		    } else {
			$w tag configure disabled -fgstipple "" \
				-foreground $val
			set data($opt) [$w tag cget disabled -foreground]
		    }
		}
		-editendcommand -
		-editstartcommand -
		-labelcommand -
		-selectmode -
		-sortcommand {
		    #
		    # Save val in data($opt)
		    #
		    set data($opt) $val
		}
		-exportselection {
		    #
		    # Save the boolean value specified by val in
		    # data($opt).  In addition, if the selection is
		    # exported and there are any selected rows in the
		    # widget then make win the new owner of the PRIMARY
		    # selection and register a callback to be invoked
		    # when it loses ownership of the PRIMARY selection
		    #
		    set data($opt) [expr {$val ? 1 : 0}]
		    if {$val &&
			[llength [$data(body) tag nextrange select 1.0]] != 0} {
			selection own -command \
				[list ::tablelist::lostSelection $win] $win
		    }
		}
		-height {
		    #
		    # Adjust the height of the body text widget and save
		    # the properly formatted value of val in data($opt)
		    #
		    set val [format %d $val]	;# integer check with error msg
		    if {$val <= 0} {
			$data(body) configure $opt $data(itemCount)
		    } else {
			$data(body) configure $opt $val
		    }
		    set data($opt) $val
		}
		-incrarrowtype {
		    #
		    # Save the properly formatted value of val
		    # in data($opt) and draw the arrows if
		    # the canvas widget is presently mapped
		    #
		    variable arrowTypes
		    set data($opt) \
			[mwutil::fullOpt "arrow type" $val $arrowTypes]
		    if {$data(arrowCol) >= 0} {
			drawArrows $win
		    }
		}
		-listvariable {
		    #
		    # Associate val as list variable with the
		    # given widget and save it in data($opt)
		    #
		    makeListVar $win $val
		    set data($opt) $val
		    if {[string compare $val ""] == 0} {
			set data(hasListVar) 0
		    } else {
			set data(hasListVar) 1
		    }
		}
		-movablecolumns -
		-resizablecolumns {
		    #
		    # Save the boolean value specified by val in data($opt)
		    #
		    set data($opt) [expr {$val ? 1 : 0}]
		}
		-movecolumncursor -
		-resizecursor {
		    #
		    # Save the properly formatted value of val in data($opt)
		    #
		    $helpLabel configure -cursor $val
		    set data($opt) [$helpLabel cget -cursor]
		}
		-selectbackground -
		-selectforeground {
		    #
		    # Configure the "select" tag in the body text widget
		    # and save the properly formatted value of val in
		    # data($opt).  Don't use the built-in "sel" tag
		    # because on Windows the selection in a text widget only
		    # becomes visible when the window gets the input focus.
		    #
		    set w $data(body)
		    set optTail [string range $opt 7 end] ;# remove the -select
		    $w tag configure select -$optTail $val
		    set data($opt) [$w tag cget select -$optTail]
		}
		-selectborderwidth {
		    #
		    # Configure the "select" tag in the body text widget
		    # and save the properly formatted value of val in
		    # data($opt).  Don't use the built-in "sel" tag
		    # because on Windows the selection in a text widget only
		    # becomes visible when the window gets the input focus.
		    # In addition, adjust the line spacing accordingly and
		    # apply the value to the listbox child, too.
		    #
		    set w $data(body)
		    set optTail [string range $opt 7 end] ;# remove the -select
		    $w tag configure select -$optTail $val
		    set data($opt) [$w tag cget select -$optTail]
		    if {$val < 0} {
			set val 0
		    }
		    $w configure -spacing1 $val -spacing3 [expr {$val + 1}]
		    $data(lb) configure $opt $val
		}
		-setgrid {
		    #
		    # Apply the value to the listbox child and save
		    # the properly formatted value of val in data($opt)
		    #
		    $data(lb) configure $opt $val
		    set data($opt) [$data(lb) cget $opt]
		}
		-showarrow {
		    #
		    # Save the boolean value specified by val in
		    # data($opt) and conditionally unmanage the
		    # canvas displaying an up- or down-arrow
		    #
		    set data($opt) [expr {$val ? 1 : 0}]
		    if {!$data($opt) && $data(arrowCol) >= 0} {
			place forget $data(hdrTxtFrCanv)
			set oldArrowCol $data(arrowCol)
			set data(arrowCol) -1
			adjustColumns $win l$oldArrowCol 1
		    }
		}
		-showlabels {
		    #
		    # Save the boolean value specified by val in data($opt)
		    # and adjust the height of the header frame
		    #
		    set data($opt) [expr {$val ? 1 : 0}]
		    adjustHeaderHeight $win
		}
		-showseparators {
		    #
		    # Save the boolean value specified by val in data($opt),
		    # and create or destroy the separators if needed
		    #
		    set oldVal $data($opt)
		    set data($opt) [expr {$val ? 1 : 0}]
		    if {!$oldVal && $data($opt)} {
			createSeps $win
		    } elseif {$oldVal && !$data($opt)} {
			foreach w [winfo children $win] {
			    if {[regexp {^sep[0-9]+$} [winfo name $w]]} {
				destroy $w
			    }
			}
		    }
		}
		-snipstring {
		    #
		    # Save val in data($opt), adjust the columns, and make
		    # sure the items will be redisplayed at idle time
		    #
		    set data($opt) $val
		    adjustColumns $win {} 0
		    redisplayWhenIdle $win
		}
		-state {
		    #
		    # Apply the value to all labels and their children
		    # (if any), as well as to the entry widget (if
		    # present), raise the corresponding arrow in the
		    # canvas, add/remove the "disabled" tag to/from the
		    # contents of the body text widget, configure the
		    # borderwidth of the "active" and "select" tags, and
		    # save the properly formatted value of val in data($opt)
		    #
		    variable states
		    set val [mwutil::fullOpt "state" $val $states]
		    catch {
			foreach w [winfo children $data(hdrTxtFr)] {
			    $w configure $opt $val
			    foreach c [winfo children $w] {
				$c configure $opt $val
			    }
			}
		    }
		    if {$data(editRow) >= 0} {
			$data(bodyFrEnt) configure $opt $val
		    }
		    raiseArrow $data(hdrTxtFrCanv) $val
		    set w $data(body)
		    switch $val {
			disabled {
			    $w tag add disabled 1.0 end
			    $w tag configure active -borderwidth 0
			    $w tag configure select -borderwidth 0
			    set data(isDisabled) 1
			}
			normal {
			    $w tag remove disabled 1.0 end
			    $w tag configure active -borderwidth 1
			    $w tag configure select -borderwidth \
				    $data(-selectborderwidth)
			    set data(isDisabled) 0
			}
		    }
		    set data($opt) $val
		}
		-stretch {
		    #
		    # Save the properly formatted value of val in
		    # data($opt) and stretch the stretchable columns
		    #
		    if {[string first $val all] == 0} {
			set data($opt) all
		    } else {
			set data($opt) $val
			sortStretchableColList $win
		    }
		    set data(forceAdjust) 1
		    stretchColumnsWhenIdle $win
		}
		-stripebackground -
		-stripeforeground {
		    #
		    # Configure the "stripe" tag in the body text
		    # widget, save the properly formatted value of val
		    # in data($opt), and draw the stripes if necessary
		    #
		    set w $data(body)
		    set optTail [string range $opt 7 end] ;# remove the -stripe
		    $w tag configure stripe -$optTail $val
		    set data($opt) [$w tag cget stripe -$optTail]
		    makeStripesWhenIdle $win
		}
		-stripeheight {
		    #
		    # Save the properly formatted value of val val
		    # data($opt) and draw the stripes if necessary
		    #
		    set val [format %d $val]	;# integer check with error msg
		    set data($opt) $val
		    makeStripesWhenIdle $win
		}
		-targetcolor {
		    #
		    # Set the color of the header gap and save the
		    # properly formatted value of val in data($opt)
		    #
		    $data(hdrGap) configure -background $val
		    set data($opt) [$data(hdrGap) cget -background]
		}
		-width {
		    #
		    # Adjust the widths of the body text widget
		    # and of the header frame, and save the
		    # properly formatted value of val in data($opt)
		    #
		    set val [format %d $val]	;# integer check with error msg
		    $data(body) configure $opt $val
		    if {$val <= 0} {
			$data(hdr) configure $opt $data(hdrPixels)
		    } else {
			$data(hdr) configure $opt 0
		    }
		    set data($opt) $val
		}
	    }
	}
    }
}

#------------------------------------------------------------------------------
# tablelist::doColConfig
#
# Applies the value val of the column configuration option opt to the col'th
# column of the tablelist widget win.
#------------------------------------------------------------------------------
proc tablelist::doColConfig {col win opt val} {
    upvar ::tablelist::ns${win}::data data

    switch -- $opt {
	-align {
	    #
	    # Set up and adjust the columns, and make sure
	    # the items will be redisplayed at idle time
	    #
	    set idx [expr {3*$col + 2}]
	    setupColumns $win [lreplace $data(-columns) $idx $idx $val] 0
	    adjustColumns $win {} 0
	    redisplayWhenIdle $win
	}

	-background -
	-foreground {
	    set w $data(body)
	    set tag $col$opt
	    if {[string compare $val ""] == 0} {
		if {[info exists data($tag)]} {
		    $w tag delete $tag
		    unset data($tag)
		}
	    } else {
		#
		# Configure the tag $tag in the body text widget
		#
		$w tag configure $tag $opt $val
		$w tag raise $tag stripe

		if {!$data($col-hide)} {
		    #
		    # Apply the tag to the elements of the given column
		    #
		    for {set idx 0; set line 1} {$idx < $data(itemCount)} \
			{incr idx; incr line} {
			if {[lsearch -exact [$w tag names $line.0] select]
			    < 0} {
			    findCellTabs $win $line $col tabIdx1 tabIdx2
			    $w tag add $tag $tabIdx1 $tabIdx2+1c
			}
		    }
		}

		#
		# Save the properly formatted value of val in data($tag)
		#
		set data($tag) [$w tag cget $tag $opt]
	    }

	    #
	    # Rebuild the lists of the column fonts and tag names
	    #
	    makeColFontAndTagLists $win
	}

	-editable -
	-resizable {
	    #
	    # Save the boolean value specified by val in data($col$opt)
	    #
	    set data($col$opt) [expr {$val ? 1 : 0}]
	}

	-font {
	    set w $data(body)
	    set tag $col$opt
	    if {[string compare $val ""] == 0} {
		if {[info exists data($tag)]} {
		    $w tag delete $tag
		    unset data($tag)
		}
	    } else {
		#
		# Configure the tag $tag in the body text widget
		#
		$w tag configure $tag $opt $val
		$w tag lower $tag

		if {!$data($col-hide)} {
		    #
		    # Apply the tag to the elements of the given column
		    #
		    for {set idx 0; set line 1} {$idx < $data(itemCount)} \
			{incr idx; incr line} {
			findCellTabs $win $line $col tabIdx1 tabIdx2
			$w tag add $tag $tabIdx1 $tabIdx2+1c
		    }
		}

		#
		# Save the properly formatted value of val in data($tag)
		#
		set data($tag) [$w tag cget $tag $opt]
	    }

	    #
	    # Rebuild the lists of the column fonts and tag names
	    #
	    makeColFontAndTagLists $win

	    #
	    # Adjust the columns, and make sure the items
	    # will be redisplayed at idle time if needed
	    #
	    adjustColumns $win $col 1
	    set pixels [lindex $data(colList) [expr {2*$col}]]
	    if {$pixels != 0} {			;# convention: static width
		redisplayWhenIdle $win
	    }

	    if {$col == $data(editCol)} {
		#
		# Configure the entry widget
		#
		set item [lindex $data(itemList) $data(editRow)]
		set key [lindex $item end]
		$data(bodyFrEnt) configure -font [cellFont $win $key $col]
	    }
	}

	-formatcommand {
	    if {[string compare $val ""] == 0} {
		if {[info exists data($col$opt)]} {
		    unset data($col$opt)
		}
		set fmtCmdFlag 0
	    } else {
		#
		# Save val in data($col$opt)
		#
		set data($col$opt) $val
		set fmtCmdFlag 1
	    }

	    #
	    # Update the corresponding element of the list data(fmtCmdFlagList)
	    #
	    set data(fmtCmdFlagList) \
		[lreplace $data(fmtCmdFlagList) $col $col $fmtCmdFlag]

	    #
	    # Adjust the columns and make sure the
	    # items will be redisplayed at idle time
	    #
	    adjustColumns $win $col 1
	    redisplayWhenIdle $win
	}

	-hide {
	    #
	    # Save the boolean value specified by val in data($col$opt),
	    # adjust the columns, and redisplay the items
	    #
	    set oldVal $data($col$opt)
	    set data($col$opt) [expr {$val ? 1 : 0}]
	    adjustColumns $win $col 1
	    if {$data($col$opt) != $oldVal} {
		redisplay $win
	    }
	}

	-labelalign {
	    if {[string compare $val ""] == 0} {
		#
		# Unset data($col$opt)
		#
		set alignment [lindex $data(colList) [expr {2*$col + 1}]]
		if {[info exists data($col$opt)]} {
		    unset data($col$opt)
		}
	    } else {
		#
		# Save the properly formatted value of val in data($col$opt)
		#
		variable alignments
		set val [mwutil::fullOpt "label alignment" $val $alignments]
		set alignment $val
		set data($col$opt) $val
	    }

	    #
	    # Adjust the col`th label
	    #
	    set pixels [lindex $data(colList) [expr {2*$col}]]
	    if {$pixels != 0} {			;# convention: static width
		incr pixels $data($col-delta)
	    }
	    adjustLabel $win $col $pixels $alignment
	}

	-labelbackground {
	    set w $data(hdrTxtFrLbl)$col
	    set optTail [string range $opt 6 end]	;# remove the -label
	    if {[string compare $val ""] == 0} {
		#
		# Apply the value of the corresponding widget configuration
		# option to the col'th label and its children (if any)
		# and conditionally to the canvas displaying an up-
		# or down-arrow, and unset data($col$opt)
		#
		$w configure -$optTail $data($opt)
		foreach c [winfo children $w] {
		    $c configure -$optTail $data($opt)
		}
		if {$col == $data(arrowCol)} {
		    configCanvas $win
		}
		if {[info exists data($col$opt)]} {
		    unset data($col$opt)
		}
	    } else {
		#
		# Apply the given value to the col'th label and its
		# children (if any) and conditionally to the canvas
		# displaying an up- or down-arrow, and save the
		# properly formatted value of val in data($col$opt)
		#
		$w configure -$optTail $val
		foreach c [winfo children $w] {
		    $c configure -$optTail $val
		}
		if {$col == $data(arrowCol)} {
		    configCanvas $win
		}
		set data($col$opt) [$w cget -$optTail]
	    }
	}

	-labelborderwidth {
	    set w $data(hdrTxtFrLbl)$col
	    set optTail [string range $opt 6 end]	;# remove the -label
	    if {[string compare $val ""] == 0} {
		#
		# Apply the value of the corresponding widget configuration
		# option to the col'th label and unset data($col$opt)
		#
		configLabel $w -$optTail $data($opt)
		if {[info exists data($col$opt)]} {
		    unset data($col$opt)
		}
	    } else {
		#
		# Apply the given value to the col'th label and save the
		# properly formatted value of val in data($col$opt)
		#
		configLabel $w -$optTail $val
		set data($col$opt) [$w cget -$optTail]
	    }

	    #
	    # Adjust the columns (including the height of the header frame)
	    #
	    adjustColumns $win l$col 1
	}

	-labelcommand -
	-name -
	-sortcommand {
	    if {[string compare $val ""] == 0} {
		if {[info exists data($col$opt)]} {
		    unset data($col$opt)
		}
	    } else {
		#
		# Save val in data($col$opt)
		#
		set data($col$opt) $val
	    }
	}

	-labelfont {
	    set w $data(hdrTxtFrLbl)$col
	    set optTail [string range $opt 6 end]	;# remove the -label
	    if {[string compare $val ""] == 0} {
		#
		# Apply the value of the corresponding widget
		# configuration option to the col'th label and
		# its children (if any), and unset data($col$opt)
		#
		$w configure -$optTail $data($opt)
		foreach c [winfo children $w] {
		    $c configure -$optTail $data($opt)
		}
		if {[info exists data($col$opt)]} {
		    unset data($col$opt)
		}
	    } else {
		#
		# Apply the given value to the col'th label and
		# its children (if any), and save the properly
		# formatted value of val in data($col$opt)
		#
		$w configure -$optTail $val
		foreach c [winfo children $w] {
		    $c configure -$optTail $val
		}
		set data($col$opt) [$w cget -$optTail]
	    }

	    #
	    # Conditionally resize the canvas displaying an up- or down-arrow
	    # and adjust the columns (including the height of the header frame)
	    #
	    if {$col == $data(arrowCol)} {
		configCanvas $win
		drawArrows $win
	    }
	    adjustColumns $win l$col 1
	}

	-labelforeground {
	    set w $data(hdrTxtFrLbl)$col
	    set optTail [string range $opt 6 end]	;# remove the -label
	    if {[string compare $val ""] == 0} {
		#
		# Apply the value of the corresponding widget
		# configuration option to the col'th label and
		# its children (if any), and unset data($col$opt)
		#
		$w configure -$optTail $data($opt)
		foreach c [winfo children $w] {
		    $c configure -$optTail $data($opt)
		}
		if {[info exists data($col$opt)]} {
		    unset data($col$opt)
		}
	    } else {
		#
		# Apply the given value to the col'th label and
		# its children (if any), and save the properly
		# formatted value of val in data($col$opt)
		#
		$w configure -$optTail $val
		foreach c [winfo children $w] {
		    $c configure -$optTail $val
		}
		set data($col$opt) [$w cget -$optTail]
	    }
	}

	-labelheight -
	-labelpady {
	    set w $data(hdrTxtFrLbl)$col
	    set optTail [string range $opt 6 end]	;# remove the -label
	    if {[string compare $val ""] == 0} {
		#
		# Apply the value of the corresponding widget configuration
		# option to the col'th label and unset data($col$opt)
		#
		configLabel $w -$optTail $data($opt)
		if {[info exists data($col$opt)]} {
		    unset data($col$opt)
		}
	    } else {
		#
		# Apply the given value to the col'th label and save the
		# properly formatted value of val in data($col$opt)
		#
		configLabel $w -$optTail $val
		set data($col$opt) [$w cget -$optTail]
	    }

	    #
	    # Adjust the height of the header frame
	    #
	    adjustHeaderHeight $win
	}

	-labelimage {
	    set w $data(hdrTxtFrLbl)$col
	    if {[string compare $val ""] == 0} {
		foreach c [winfo children $w] {
		    destroy $c
		}
		if {[info exists data($col$opt)]} {
		    unset data($col$opt)
		}
	    } else {
		if {![winfo exists $w.il]} {
		    variable configSpecs
		    variable configOpts
		    foreach c [list $w.il $w.tl] {	;# image and text labels
			#
			# Create the label $c
			#
			label $c -borderwidth 0 -height 0 \
				 -highlightthickness 0 -padx 0 \
				 -pady 0 -takefocus 0 -width 0

			#
			# Apply to it the current configuration options
			#
			foreach opt2 $configOpts {
			    if {[string compare \
				 [lindex $configSpecs($opt2) 2] c] == 0} {
				$c configure $opt2 $data($opt2)
			    }
			}
			foreach opt2 {-background -font -foreground} {
			    $c configure $opt2 [$w cget $opt2]
			}
			foreach opt2 {-disabledforeground -state} {
			    catch {$c configure $opt2 [$w cget $opt2]}
			}

			#
			# Replace the binding tag Label with TablelistSubLabel
			# in the list of binding tags of the label $c
			#
			bindtags $c \
				 [lreplace [bindtags $c] 1 1 TablelistSubLabel]
		    }
		}

		#
		# Display the specified image in the label
		# $w.il and save val in data($col$opt)
		#
		$w.il configure -image $val
		set data($col$opt) $val
	    }

	    #
	    # Adjust the columns (including the height of the header frame)
	    #
	    adjustColumns $win l$col 1
	}

	-labelrelief {
	    set w $data(hdrTxtFrLbl)$col
	    set optTail [string range $opt 6 end]	;# remove the -label
	    if {[string compare $val ""] == 0} {
		#
		# Apply the value of the corresponding widget configuration
		# option to the col'th label and unset data($col$opt)
		#
		configLabel $w -$optTail $data($opt)
		if {[info exists data($col$opt)]} {
		    unset data($col$opt)
		}
	    } else {
		#
		# Apply the given value to the col'th label and save the
		# properly formatted value of val in data($col$opt)
		#
		configLabel $w -$optTail $val
		set data($col$opt) [$w cget -$optTail]
	    }
	}

	-selectbackground -
	-selectforeground {
	    set w $data(body)
	    set tag $col$opt
	    if {[string compare $val ""] == 0} {
		if {[info exists data($tag)]} {
		    $w tag delete $tag
		    unset data($tag)
		}
	    } else {
		#
		# Configure the tag $tag in the body text widget
		#
		set optTail [string range $opt 7 end]	;# remove the -select
		$w tag configure $tag -$optTail $val
		$w tag raise $tag select

		if {!$data($col-hide)} {
		    #
		    # Apply the tag to the elements of the given
		    # column in all currently selected rows
		    #
		    set selRange [$w tag nextrange select 1.0]
		    while {[llength $selRange] != 0} {
			set selStart [lindex $selRange 0]
			set selEnd [lindex $selRange 1]

			findCellTabs $win [expr {int($selStart)}] $col \
				     tabIdx1 tabIdx2
			$w tag add $tag $tabIdx1 $tabIdx2+1c

			set selRange [$w tag nextrange select $selEnd]
		    }
		}

		#
		# Save the properly formatted value of val in data($tag)
		#
		set data($tag) [$w tag cget $tag -$optTail]
	    }
	}

	-showarrow {
	    #
	    # Save the boolean value specified by val in data($col$opt) and
	    # conditionally unmanage the canvas displaying an up- or down-arrow
	    #
	    set data($col$opt) [expr {$val ? 1 : 0}]
	    if {!$data($col$opt) && $col == $data(arrowCol)} {
		place forget $data(hdrTxtFrCanv)
		set data(arrowCol) -1
		adjustColumns $win l$col 1
	    }
	}

	-sortmode {
	    #
	    # Save the properly formatted value of val in data($col$opt)
	    #
	    variable sortModes
	    set data($col$opt) [mwutil::fullOpt "sort mode" $val $sortModes]
	}

	-text {
	    if {$data(isDisabled)} {
		return ""
	    }

	    #
	    # Replace the column's contents in the internal list
	    #
	    set newItemList {}
	    set row 0
	    foreach item $data(itemList) text [lrange $val 0 $data(itemCount)] {
		set item [lreplace $item $col $col $text]
		lappend newItemList $item
	    }
	    set data(itemList) $newItemList

	    #
	    # Update the list variable if present
	    #
	    condUpdateListVar $win

	    #
	    # Adjust the columns and make sure the
	    # items will be redisplayed at idle time
	    #
	    adjustColumns $win $col 1
	    redisplayWhenIdle $win
	}

	-title {
	    #
	    # Save the given value in the corresponding
	    # element of data(-columns) and adjust the columns
	    #
	    set idx [expr {3*$col + 1}]
	    set data(-columns) [lreplace $data(-columns) $idx $idx $val]
	    adjustColumns $win l$col 1
	}

	-width {
	    #
	    # Set up and adjust the columns, and make sure
	    # the items will be redisplayed at idle time
	    #
	    set idx [expr {3*$col}]
	    setupColumns $win [lreplace $data(-columns) $idx $idx $val] 0
	    adjustColumns $win $col 1
	    redisplayWhenIdle $win
	}
    }
}

#------------------------------------------------------------------------------
# tablelist::doColCget
#
# Returns the value of the column configuration option opt for the col'th
# column of the tablelist widget win.
#------------------------------------------------------------------------------
proc tablelist::doColCget {col win opt} {
    upvar ::tablelist::ns${win}::data data

    switch -- $opt {
	-align {
	    return [lindex $data(-columns) [expr {3*$col + 2}]]
	}

	-text {
	    set result {}
	    foreach item $data(itemList) {
		lappend result [lindex $item $col]
	    }
	    return $result
	}

	-title {
	    return [lindex $data(-columns) [expr {3*$col + 1}]]
	}

	-width {
	    return [lindex $data(-columns) [expr {3*$col}]]
	}

	default {
	    if {[info exists data($col$opt)]} {
		return $data($col$opt)
	    } else {
		return ""
	    }
	}
    }
}

#------------------------------------------------------------------------------
# tablelist::doRowConfig
#
# Applies the value val of the row configuration option opt to the row'th row
# of the tablelist widget win.
#------------------------------------------------------------------------------
proc tablelist::doRowConfig {row win opt val} {
    upvar ::tablelist::ns${win}::data data

    set w $data(body)

    switch -- $opt {
	-background -
	-foreground {
	    set item [lindex $data(itemList) $row]
	    set key [lindex $item end]
	    set tag $key$opt

	    if {[string compare $val ""] == 0} {
		if {[info exists data($tag)]} {
		    $w tag delete $tag
		    unset data($tag)
		    incr data(tagCount) -1
		}
	    } else {
		#
		# Configure the tag $tag in the body text widget and
		# apply it to the given row if it is not selected
		#
		$w tag configure $tag $opt $val
		$w tag lower $tag disabled
		for {set col 0} {$col < $data(colCount)} {incr col} {
		    if {[info exists data($key-$col$opt)]} {
			$w tag lower $key-$col$opt disabled
		    }
		}
		set line [expr {$row + 1}]
		if {[lsearch -exact [$w tag names $line.0] select] < 0} {
		    $w tag add $tag $line.0 $line.end
		}

		#
		# Save the properly formatted value of val in data($tag)
		#
		if {![info exists data($tag)]} {
		    incr data(tagCount)
		}
		set data($tag) [$w tag cget $tag $opt]
	    }
	}

	-font {
	    #
	    # Save the current cell fonts in a temporary array
	    #
	    set item [lindex $data(itemList) $row]
	    set key [lindex $item end]
	    for {set col 0} {$col < $data(colCount)} {incr col} {
		set oldCellFonts($col) [cellFont $win $key $col]
	    }

	    set tag $key$opt
	    if {[string compare $val ""] == 0} {
		if {[info exists data($tag)]} {
		    $w tag delete $tag
		    unset data($tag)
		    incr data(tagCount) -1
		}
	    } else {
		#
		# Configure the tag $tag in the body text
		# widget and apply it to the given row
		#
		$w tag configure $tag $opt $val
		for {set col 0} {$col < $data(colCount)} {incr col} {
		    if {[info exists data($key-$col$opt)]} {
			$w tag raise $key-$col$opt
		    }
		}
		set line [expr {$row + 1}]
		$w tag add $tag $line.0 $line.end

		#
		# Save the properly formatted value of val in data($tag)
		#
		if {![info exists data($tag)]} {
		    incr data(tagCount)
		}
		set data($tag) [$w tag cget $tag $opt]
	    }

	    set dispItem [strToDispStr $item]
	    set colWidthsChanged 0
	    set colList {}
	    set line [expr {$row + 1}]
	    set textIdx1 $line.1
	    set col 0
	    foreach text [lrange $dispItem 0 $data(lastCol)] \
		    fmtCmdFlag $data(fmtCmdFlagList) \
		    {pixels alignment} $data(colList) {
		if {$data($col-hide)} {
		    incr col
		    continue
		}

		#
		# Adjust the cell text and the image width
		#
		if {$fmtCmdFlag} {
		    set text [uplevel #0 $data($col-formatcommand) \
			      [list [lindex $item $col]]]
		    set text [strToDispStr $text]
		}
		if {[info exists data($key-$col-image)]} {
		    set image $data($key-$col-image)
		    set imageWidth [image width $image]
		} else {
		    set image ""
		    set imageWidth 0
		}
		set cellFont [cellFont $win $key $col]
		if {$pixels != 0} {		;# convention: static width
		    incr pixels $data($col-delta)
		}
		adjustElem $win text imageWidth $cellFont \
			   $pixels $alignment $data(-snipstring)

		if {$row == $data(editRow) && $col == $data(editCol)} {
		    #
		    # Configure the entry widget
		    #
		    $data(bodyFrEnt) configure -font [cellFont $win $key $col]
		} else {
		    #
		    # Delete the old cell contents between the
		    # two tabs, and insert the text and the image
		    #
		    set textIdx2 [$w search \t $textIdx1 $line.end]
		    $w delete $textIdx1 $textIdx2
		    insertElem $w $textIdx1 $text $image $imageWidth $alignment
		}

		if {$pixels == 0} {		;# convention: dynamic width
		    #
		    # Check whether the width of the current column has changed
		    #
		    set textWidth [font measure $cellFont -displayof $win $text]
		    set newElemWidth [expr {$imageWidth + $textWidth}]
		    if {$newElemWidth > $data($col-elemWidth)} {
			set data($col-elemWidth) $newElemWidth
			set data($col-widestCount) 1
			if {$newElemWidth > $data($col-width)} {
			    set data($col-width) $newElemWidth
			    set colWidthsChanged 1
			}
		    } else {
			set oldTextWidth [font measure $oldCellFonts($col) \
					  -displayof $win $text]
			set oldElemWidth [expr {$imageWidth + $oldTextWidth}]
			if {$oldElemWidth < $data($col-elemWidth) &&
			    $newElemWidth == $data($col-elemWidth)} {
			    incr data($col-widestCount)
			} elseif {$oldElemWidth == $data($col-elemWidth) &&
				  $newElemWidth < $oldElemWidth &&
				  [incr data($col-widestCount) -1] == 0} {
			    set colWidthsChanged 1
			    lappend colList $col
			}
		    }
		}

		set textIdx1 [$w search \t $textIdx1 $line.end]+2c
		incr col
	    }

	    #
	    # Adjust the columns if necessary
	    #
	    if {$colWidthsChanged} {
		adjustColumns $win $colList 1
	    }
	}

	-selectable {
	    set val [expr {$val ? 1 : 0}]
	    set item [lindex $data(itemList) $row]
	    set key [lindex $item end]

	    if {$val} {
		if {[info exists data($key$opt)]} {
		    unset data($key$opt)
		}
	    } else {
		#
		# Set data($key$opt) to 0 and deselect the row
		#
		set data($key$opt) 0
		selectionSubCmd $win clear $row $row
	    }
	}

	-selectbackground -
	-selectforeground {
	    set item [lindex $data(itemList) $row]
	    set key [lindex $item end]
	    set tag $key$opt

	    if {[string compare $val ""] == 0} {
		if {[info exists data($tag)]} {
		    $w tag delete $tag
		    unset data($tag)
		}
	    } else {
		#
		# Configure the tag $tag in the body text widget
		# and apply it to the given row if it is selected
		#
		set optTail [string range $opt 7 end]	;# remove the -select
		$w tag configure $tag -$optTail $val
		$w tag lower $tag disabled
		for {set col 0} {$col < $data(colCount)} {incr col} {
		    if {[info exists data($key-$col$opt)]} {
			$w tag lower $key-$col$opt disabled
		    }
		}
		set line [expr {$row + 1}]
		if {[lsearch -exact [$w tag names $line.0] select] >= 0} {
		    $w tag add $tag $line.0 $line.end
		}

		#
		# Save the properly formatted value of val in data($tag)
		#
		set data($tag) [$w tag cget $tag -$optTail]
	    }
	}

	-text {
	    if {$data(isDisabled)} {
		return ""
	    }

	    set colWidthsChanged 0
	    set colList {}
	    set oldItem [lindex $data(itemList) $row]
	    set key [lindex $oldItem end]
	    set newItem [adjustItem $val $data(colCount)]
	    set line [expr {$row + 1}]
	    set textIdx1 $line.1
	    set col 0
	    foreach text [strToDispStr $newItem] \
		    fmtCmdFlag $data(fmtCmdFlagList) \
		    {pixels alignment} $data(colList) {
		if {$data($col-hide)} {
		    incr col
		    continue
		}

		#
		# Adjust the cell text and the image width
		#
		if {$fmtCmdFlag} {
		    set text [uplevel #0 $data($col-formatcommand) \
			      [list [lindex $newItem $col]]]
		    set text [strToDispStr $text]
		}
		if {[info exists data($key-$col-image)]} {
		    set image $data($key-$col-image)
		    set imageWidth [image width $image]
		} else {
		    set image ""
		    set imageWidth 0
		}
		set cellFont [cellFont $win $key $col]
		if {$pixels != 0} {		;# convention: static width
		    incr pixels $data($col-delta)
		}
		adjustElem $win text imageWidth $cellFont \
			   $pixels $alignment $data(-snipstring)

		if {$row != $data(editRow) || $col != $data(editCol)} {
		    #
		    # Delete the old cell contents between the
		    # two tabs, and insert the text and the image
		    #
		    set textIdx2 [$w search \t $textIdx1 $line.end]
		    $w delete $textIdx1 $textIdx2
		    insertElem $w $textIdx1 $text $image $imageWidth $alignment
		}

		if {$pixels == 0} {		;# convention: dynamic width
		    #
		    # Check whether the width of the current column has changed
		    #
		    set textWidth [font measure $cellFont -displayof $win $text]
		    set newElemWidth [expr {$imageWidth + $textWidth}]
		    if {$newElemWidth > $data($col-elemWidth)} {
			set data($col-elemWidth) $newElemWidth
			set data($col-widestCount) 1
			if {$newElemWidth > $data($col-width)} {
			    set data($col-width) $newElemWidth
			    set colWidthsChanged 1
			}
		    } else {
			set oldText [lindex $oldItem $col]
			if {$fmtCmdFlag} {
			    set oldText [uplevel #0 $data($col-formatcommand) \
					 [list $oldText]]
			}
			set oldText [strToDispStr $oldText]
			adjustElem $win oldText imageWidth $cellFont \
				   $pixels $alignment $data(-snipstring)
			set oldTextWidth \
			    [font measure $cellFont -displayof $win $oldText]
			set oldElemWidth [expr {$imageWidth + $oldTextWidth}]
			if {$oldElemWidth < $data($col-elemWidth) &&
			    $newElemWidth == $data($col-elemWidth)} {
			    incr data($col-widestCount)
			} elseif {$oldElemWidth == $data($col-elemWidth) &&
				  $newElemWidth < $oldElemWidth &&
				  [incr data($col-widestCount) -1] == 0} {
			    set colWidthsChanged 1
			    lappend colList $col
			}
		    }
		}

		set textIdx1 [$w search \t $textIdx1 $line.end]+2c
		incr col
	    }

	    #
	    # Replace the row contents in the list variable if present
	    #
	    if {$data(hasListVar)} {
		trace vdelete ::$data(-listvariable) wu $data(listVarTraceCmd)
		upvar #0 $data(-listvariable) var
		set var [lreplace $var $row $row $newItem]
		trace variable ::$data(-listvariable) wu $data(listVarTraceCmd)
	    }

	    #
	    # Replace the row contents in the internal list
	    #
	    lappend newItem [lindex $oldItem end]
	    set data(itemList) [lreplace $data(itemList) $row $row $newItem]

	    #
	    # Adjust the columns if necessary
	    #
	    if {$colWidthsChanged} {
		adjustColumns $win $colList 1
	    }
	}
    }
}

#------------------------------------------------------------------------------
# tablelist::doRowCget
#
# Returns the value of the row configuration option opt for the row'th row of
# the tablelist widget win.
#------------------------------------------------------------------------------
proc tablelist::doRowCget {row win opt} {
    upvar ::tablelist::ns${win}::data data

    #
    # Return the value of the specified row configuration option
    #
    switch -- $opt {
	-text {
	    set item [lindex $data(itemList) $row]
	    return [lrange $item 0 $data(lastCol)]
	}

	-selectable {
	    set item [lindex $data(itemList) $row]
	    set key [lindex $item end]
	    if {[info exists data($key$opt)]} {
		return $data($key$opt)
	    } else {
		return 1
	    }
	}

	default {
	    set item [lindex $data(itemList) $row]
	    set key [lindex $item end]
	    if {[info exists data($key$opt)]} {
		return $data($key$opt)
	    } else {
		return ""
	    }
	}
    }
}

#------------------------------------------------------------------------------
# tablelist::doCellConfig
#
# Applies the value val of the cell configuration option opt to the cell
# row,col of the tablelist widget win.
#------------------------------------------------------------------------------
proc tablelist::doCellConfig {row col win opt val} {
    upvar ::tablelist::ns${win}::data data

    set w $data(body)

    switch -- $opt {
	-background -
	-foreground {
	    set item [lindex $data(itemList) $row]
	    set key [lindex $item end]
	    set tag $key-$col$opt

	    if {[string compare $val ""] == 0} {
		if {[info exists data($tag)]} {
		    $w tag delete $tag
		    unset data($tag)
		    incr data(tagCount) -1
		}
	    } else {
		#
		# Configure the tag $tag in the body text widget
		#
		$w tag configure $tag $opt $val
		$w tag lower $tag disabled

		if {!$data($col-hide)} {
		    #
		    # Apply the tag to the given cell if it is not selected
		    #
		    set line [expr {$row + 1}]
		    if {[lsearch -exact [$w tag names $line.0] select] < 0} {
			findCellTabs $win $line $col tabIdx1 tabIdx2
			$w tag add $tag $tabIdx1 $tabIdx2+1c
		    }
		}

		#
		# Save the properly formatted value of val in data($tag)
		#
		if {![info exists data($tag)]} {
		    incr data(tagCount)
		}
		set data($tag) [$w tag cget $tag $opt]
	    }
	}

	-editable {
	    #
	    # Save the boolean value specified by val in data($key-$col$opt)
	    #
	    set item [lindex $data(itemList) $row]
	    set key [lindex $item end]
	    set data($key-$col$opt) [expr {$val ? 1 : 0}]
	}

	-font {
	    #
	    # Save the current cell font
	    #
	    set item [lindex $data(itemList) $row]
	    set key [lindex $item end]
	    set oldCellFont [cellFont $win $key $col]

	    set tag $key-$col$opt
	    if {[string compare $val ""] == 0} {
		if {[info exists data($tag)]} {
		    $w tag delete $tag
		    unset data($tag)
		    incr data(tagCount) -1
		}
	    } else {
		#
		# Configure the tag $tag in the body text widget
		#
		$w tag configure $tag $opt $val
		$w tag raise $tag

		if {!$data($col-hide)} {
		    #
		    # Apply the tag to the given cell
		    #
		    findCellTabs $win [expr {$row + 1}] $col tabIdx1 tabIdx2
		    $w tag add $tag $tabIdx1 $tabIdx2+1c
		}

		#
		# Save the properly formatted value of val in data($tag)
		#
		if {![info exists data($tag)]} {
		    incr data(tagCount)
		}
		set data($tag) [$w tag cget $tag $opt]
	    }

	    set pixels [lindex $data(colList) [expr {2*$col}]]
	    if {$pixels != 0} {			;# convention: static width
		incr pixels $data($col-delta)
	    }
	    set alignment [lindex $data(colList) [expr {2*$col + 1}]]

	    #
	    # Adjust the cell text and the image width
	    #
	    set text [lindex $item $col]
	    if {[lindex $data(fmtCmdFlagList) $col]} {
		set text [uplevel #0 $data($col-formatcommand) [list $text]]
	    }
	    set text [strToDispStr $text]
	    if {[info exists data($key-$col-image)]} {
		set image $data($key-$col-image)
		set imageWidth [image width $image]
	    } else {
		set image ""
		set imageWidth 0
	    }
	    set cellFont [cellFont $win $key $col]
	    adjustElem $win text imageWidth $cellFont \
		       $pixels $alignment $data(-snipstring)

	    if {!$data($col-hide)} {
		if {$row == $data(editRow) && $col == $data(editCol)} {
		    #
		    # Configure the entry widget
		    #
		    $data(bodyFrEnt) configure -font [cellFont $win $key $col]
		} else {
		    #
		    # Delete the old cell contents between the
		    # two tabs, and insert the text and the image
		    #
		    findCellTabs $win [expr {$row + 1}] $col tabIdx1 tabIdx2
		    $w delete $tabIdx1+1c $tabIdx2
		    insertElem $w $tabIdx1+1c $text $image $imageWidth \
			       $alignment
		}
	    }

	    #
	    # Adjust the columns if necessary
	    #
	    if {$pixels == 0} {			;# convention: dynamic width
		set textWidth [font measure $cellFont -displayof $win $text]
		set newElemWidth [expr {$imageWidth + $textWidth}]
		if {$newElemWidth > $data($col-elemWidth)} {
		    set data($col-elemWidth) $newElemWidth
		    set data($col-widestCount) 1
		    if {$newElemWidth > $data($col-width)} {
			set data($col-width) $newElemWidth
			adjustColumns $win {} 1
		    }
		} else {
		    set oldTextWidth \
			[font measure $oldCellFont -displayof $win $text]
		    set oldElemWidth [expr {$imageWidth + $oldTextWidth}]
		    if {$oldElemWidth < $data($col-elemWidth) &&
			$newElemWidth == $data($col-elemWidth)} {
			incr data($col-widestCount)
		    } elseif {$oldElemWidth == $data($col-elemWidth) &&
			      $newElemWidth < $oldElemWidth &&
			      [incr data($col-widestCount) -1] == 0} {
			adjustColumns $win $col 1
		    }
		}
	    }
	}

	-image {
	    if {$data(isDisabled)} {
		return ""
	    }

	    set pixels [lindex $data(colList) [expr {2*$col}]]
	    if {$pixels != 0} {			;# convention: static width
		incr pixels $data($col-delta)
	    }
	    set alignment [lindex $data(colList) [expr {2*$col + 1}]]

	    #
	    # Adjust the cell text and the image width
	    #
	    set item [lindex $data(itemList) $row]
	    set text [lindex $item $col]
	    if {[lindex $data(fmtCmdFlagList) $col]} {
		set text [uplevel #0 $data($col-formatcommand) [list $text]]
	    }
	    set text [strToDispStr $text]
	    set oldText $text			;# will be needed later
	    set image $val
	    if {[string compare $image ""] == 0} {
		set imageWidth 0
	    } else {
		set imageWidth [image width $image]
	    }
	    set key [lindex $item end]
	    set cellFont [cellFont $win $key $col]
	    adjustElem $win text imageWidth $cellFont \
		       $pixels $alignment $data(-snipstring)

	    if {!$data($col-hide) &&
		!($row == $data(editRow) && $col == $data(editCol))} {
		#
		# Delete the old cell contents between the
		# two tabs, and insert the text and the image
		#
		findCellTabs $win [expr {$row + 1}] $col tabIdx1 tabIdx2
		$w delete $tabIdx1+1c $tabIdx2
		insertElem $w $tabIdx1+1c $text $image $imageWidth $alignment
	    }

	    #
	    # Save the old image width
	    #
	    set name $key-$col$opt
	    if {[info exists data($name)]} {
		set oldImageWidth [image width $data($name)]
	    } else {
		set oldImageWidth 0
	    }

	    #
	    # Delete data($name) or save the specified value in it
	    #
	    if {[string compare $val ""] == 0} {
		if {[info exists data($name)]} {
		    unset data($name)
		    incr data(imgCount) -1
		}
	    } else {
		if {![info exists data($name)]} {
		    incr data(imgCount)
		}
		set data($name) $val
	    }

	    #
	    # Adjust the columns if necessary
	    #
	    if {$pixels == 0} {			;# convention: dynamic width
		set textWidth [font measure $cellFont -displayof $win $text]
		set newElemWidth [expr {$imageWidth + $textWidth}]
		if {$newElemWidth > $data($col-elemWidth)} {
		    set data($col-elemWidth) $newElemWidth
		    set data($col-widestCount) 1
		    if {$newElemWidth > $data($col-width)} {
			set data($col-width) $newElemWidth
			adjustColumns $win {} 1
		    }
		} else {
		    adjustElem $win oldText oldImageWidth $cellFont \
			       $pixels $alignment $data(-snipstring)
		    set oldTextWidth \
			[font measure $cellFont -displayof $win $oldText]
		    set oldElemWidth [expr {$oldImageWidth + $oldTextWidth}]
		    if {$oldElemWidth < $data($col-elemWidth) &&
			$newElemWidth == $data($col-elemWidth)} {
			incr data($col-widestCount)
		    } elseif {$oldElemWidth == $data($col-elemWidth) &&
			      $newElemWidth < $oldElemWidth &&
			      [incr data($col-widestCount) -1] == 0} {
			adjustColumns $win $col 1
		    }
		}
	    }
	}

	-selectbackground -
	-selectforeground {
	    set item [lindex $data(itemList) $row]
	    set key [lindex $item end]
	    set tag $key-$col$opt

	    if {[string compare $val ""] == 0} {
		if {[info exists data($tag)]} {
		    $w tag delete $tag
		    unset data($tag)
		}
	    } else {
		#
		# Configure the tag $tag in the body text widget
		#
		set optTail [string range $opt 7 end]	;# remove the -select
		$w tag configure $tag -$optTail $val
		$w tag lower $tag disabled

		if {!$data($col-hide)} {
		    #
		    # Apply the tag to the given cell if it is selected
		    #
		    set line [expr {$row + 1}]
		    if {[lsearch -exact [$w tag names $line.0] select] >= 0} {
			findCellTabs $win $line $col tabIdx1 tabIdx2
			$w tag add $tag $tabIdx1 $tabIdx2+1c
		    }
		}

		#
		# Save the properly formatted value of val in data($tag)
		#
		set data($tag) [$w tag cget $tag -$optTail]
	    }
	}

	-text {
	    if {$data(isDisabled)} {
		return ""
	    }

	    set pixels [lindex $data(colList) [expr {2*$col}]]
	    if {$pixels != 0} {			;# convention: static width
		incr pixels $data($col-delta)
	    }
	    set alignment [lindex $data(colList) [expr {2*$col + 1}]]

	    #
	    # Adjust the cell text and the image width
	    #
	    set text $val
	    set fmtCmdFlag [lindex $data(fmtCmdFlagList) $col]
	    if {$fmtCmdFlag} {
		set text [uplevel #0 $data($col-formatcommand) [list $text]]
	    }
	    set text [strToDispStr $text]
	    set oldItem [lindex $data(itemList) $row]
	    set key [lindex $oldItem end]
	    if {[info exists data($key-$col-image)]} {
		set image $data($key-$col-image)
		set imageWidth [image width $image]
	    } else {
		set image ""
		set imageWidth 0
	    }
	    set cellFont [cellFont $win $key $col]
	    adjustElem $win text imageWidth $cellFont \
		       $pixels $alignment $data(-snipstring)

	    if {!$data($col-hide) &&
		!($row == $data(editRow) && $col == $data(editCol))} {
		#
		# Delete the old cell contents between the
		# two tabs, and insert the text and the image
		#
		findCellTabs $win [expr {$row + 1}] $col tabIdx1 tabIdx2
		$w delete $tabIdx1+1c $tabIdx2
		insertElem $w $tabIdx1+1c $text $image $imageWidth $alignment
	    }

	    #
	    # Replace the cell contents in the internal list
	    #
	    set newItem [lreplace $oldItem $col $col $val]
	    set data(itemList) [lreplace $data(itemList) $row $row $newItem]

	    #
	    # Replace the cell contents in the list variable if present
	    #
	    if {$data(hasListVar)} {
		trace vdelete ::$data(-listvariable) wu $data(listVarTraceCmd)
		upvar #0 $data(-listvariable) var
		set var [lreplace $var $row $row \
			 [lrange $newItem 0 $data(lastCol)]]
		trace variable ::$data(-listvariable) wu $data(listVarTraceCmd)
	    }

	    #
	    # Adjust the columns if necessary
	    #
	    if {$pixels == 0} {			;# convention: dynamic width
		set textWidth [font measure $cellFont -displayof $win $text]
		set newElemWidth [expr {$imageWidth + $textWidth}]
		if {$newElemWidth > $data($col-elemWidth)} {
		    set data($col-elemWidth) $newElemWidth
		    set data($col-widestCount) 1
		    if {$newElemWidth > $data($col-width)} {
			set data($col-width) $newElemWidth
			adjustColumns $win {} 1
		    }
		} else {
		    set oldText [lindex $oldItem $col]
		    if {$fmtCmdFlag} {
			set oldText [uplevel #0 $data($col-formatcommand) \
				     [list $oldText]]
		    }
		    set oldText [strToDispStr $oldText]
		    adjustElem $win oldText imageWidth $cellFont \
			       $pixels $alignment $data(-snipstring)
		    set oldTextWidth \
			[font measure $cellFont -displayof $win $oldText]
		    set oldElemWidth [expr {$imageWidth + $oldTextWidth}]
		    if {$oldElemWidth < $data($col-elemWidth) &&
			$newElemWidth == $data($col-elemWidth)} {
			incr data($col-widestCount)
		    } elseif {$oldElemWidth == $data($col-elemWidth) &&
			      $newElemWidth < $oldElemWidth &&
			      [incr data($col-widestCount) -1] == 0} {
			adjustColumns $win $col 1
		    }
		}
	    }
	}
    }
}

#------------------------------------------------------------------------------
# tablelist::doCellCget
#
# Returns the value of the cell configuration option opt for the cell row,col
# of the tablelist widget win.
#------------------------------------------------------------------------------
proc tablelist::doCellCget {row col win opt} {
    upvar ::tablelist::ns${win}::data data

    #
    # Return the value of the specified cell configuration option
    #
    switch -- $opt {
	-editable {
	    return [isCellEditable $win $row $col]
	}

	-text {
	    set item [lindex $data(itemList) $row]
	    return [lindex $item $col]
	}

	default {
	    set item [lindex $data(itemList) $row]
	    set key [lindex $item end]
	    if {[info exists data($key-$col$opt)]} {
		return $data($key-$col$opt)
	    } else {
		return ""
	    }
	}
    }
}

#------------------------------------------------------------------------------
# tablelist::makeListVar
#
# Arranges for the global variable specified by varName to become the list
# variable associated with the tablelist widget win.
#------------------------------------------------------------------------------
proc tablelist::makeListVar {win varName} {
    upvar ::tablelist::ns${win}::data data

    if {[string compare $varName ""] == 0} {
	#
	# If there is an old list variable associated with the
	# widget then remove the trace set on this variable
	#
	if {$data(hasListVar)} {
	    synchronize $win
	    trace vdelete ::$data(-listvariable) wu $data(listVarTraceCmd)
	}
	return ""
    }

    #
    # The list variable may be an array element but must not be an array
    #
    if {![regexp {^(.*)\((.*)\)$} $varName dummy name1 name2]} {
	if {[array exists ::$varName]} {
	    return -code error "variable \"$varName\" is array"
	}
	set name1 $varName
	set name2 ""
    }

    if {[info exists ::$varName]} {
	#
	# Invoke the trace procedure associated with the new list variable
	#
	listVarTrace $win $name1 $name2 w
    } else {
	#
	# Set ::$varName according to the value of data(itemList)
	#
	set ::$varName {}
	foreach item $data(itemList) {
	    lappend ::$varName [lrange $item 0 $data(lastCol)]
	}
    }

    #
    # If there is an old list variable associated with the
    # widget then remove the trace set on this variable,
    # and in any case set a trace on the new list variable
    #
    if {$data(hasListVar)} {
	trace vdelete ::$data(-listvariable) wu $data(listVarTraceCmd)
    }
    trace variable ::$varName wu $data(listVarTraceCmd)
}

#==============================================================================
# Contains the implementation of interactive cell editing in tablelist widgets.
#
# Structure of the module:
#   - Namespace initialization
#   - Private procedures implementing the interactive cell editing
#   - Private procedures used in bindings related to interactive cell editing
#   - Private utility procedures related to interactive cell editing
#
# Copyright (c) 2003  Csaba Nemethi (E-mail: csaba.nemethi@t-online.de)
#==============================================================================

#
# Namespace initialization
# ========================
#

namespace eval tablelist {
    #
    # Define some bindings for the binding tag TablelistEntry
    #
    bind TablelistEntry <Control-i>        { tablelist::entryInsert  %W \t }
    bind TablelistEntry <Control-j>        { tablelist::entryInsert  %W \n }
    bind TablelistEntry <Control-Return>   { tablelist::entryInsert  %W \n }
    bind TablelistEntry <Control-KP_Enter> { tablelist::entryInsert  %W \n }
    bind TablelistEntry <Escape>	   { tablelist::cancelEditing   %W }
    bind TablelistEntry <Return>	   { tablelist::finishEditing   %W }
    bind TablelistEntry <KP_Enter>	   { tablelist::finishEditing   %W }
    bind TablelistEntry <Tab>	           { tablelist::moveToNext      %W }
    bind TablelistEntry <Shift-Tab>	   { tablelist::moveToPrev      %W }
    bind TablelistEntry <<PrevWindow>>     { tablelist::moveToPrev      %W }
    bind TablelistEntry <Alt-Left>         { tablelist::moveLeft        %W }
    bind TablelistEntry <Meta-Left>        { tablelist::moveLeft        %W }
    bind TablelistEntry <Alt-Right>        { tablelist::moveRight       %W }
    bind TablelistEntry <Meta-Right>       { tablelist::moveRight       %W }
    bind TablelistEntry <Up>	           { tablelist::moveOneLineUp   %W }
    bind TablelistEntry <Down>	           { tablelist::moveOneLineDown %W }
    bind TablelistEntry <Prior>            { tablelist::moveOnePageUp   %W }
    bind TablelistEntry <Next>             { tablelist::moveOnePageDown %W }
    bind TablelistEntry <Control-Home>     { tablelist::moveToNext %W 0 -1 }
    bind TablelistEntry <Control-End>      { tablelist::moveToPrev %W 0  0 }
    bind TablelistEntry <Control-Tab> {
	mwutil::generateEvent %W Tablelist <Tab>
    }
    bind TablelistEntry <Meta-Tab> {
	mwutil::generateEvent %W Tablelist <Tab>
    }
    bind TablelistEntry <Control-Shift-Tab> {
	mwutil::generateEvent %W Tablelist <Shift-Tab>
    }
    bind TablelistEntry <Meta-Shift-Tab> {
	mwutil::generateEvent %W Tablelist <Shift-Tab>
    }
    bind TablelistEntry <Destroy> {
	array set tablelist::ns[tablelist::parseEntryPath %W]::data \
		  {editRow -1  editCol -1}
    }

    #
    # Define some emacs-like key bindings for the binding tag TablelistEntry
    #
    bind TablelistEntry <Meta-b> {
	if {!$tk_strictMotif} {
	    tablelist::moveLeft %W
	}
    }
    bind TablelistEntry <Meta-f> {
	if {!$tk_strictMotif} {
	    tablelist::moveRight %W
	}
    }
    bind TablelistEntry <Control-p> {
	if {!$tk_strictMotif} {
	    tablelist::moveOneLineUp %W
	}
    }
    bind TablelistEntry <Control-n> {
	if {!$tk_strictMotif} {
	    tablelist::moveOneLineDown %W
	}
    }
    bind TablelistEntry <Meta-less> {
	if {!$tk_strictMotif} {
	    tablelist::moveToNext %W 0 -1
	}
    }
    bind TablelistEntry <Meta-greater> {
	if {!$tk_strictMotif} {
	    tablelist::moveToPrev %W 0 0
	}
    }

    #
    # Define some bindings for the binding tag TablelistEntry that
    # propagate the mousewheel events to the tablelist's body
    #
    bind TablelistEntry <MouseWheel> {
	tablelist::genMouseWheelEvent [winfo parent [winfo parent %W]] %D
    }
    bind TablelistEntry <Button-4> {
	event generate [winfo parent [winfo parent %W]] <Button-4>
    }
    bind TablelistEntry <Button-5> {
	event generate [winfo parent [winfo parent %W]] <Button-5>
    }
}

#
# Private procedures implementing the interactive cell editing
# ============================================================
#

#------------------------------------------------------------------------------
# tablelist::editcellSubCmd
#
# This procedure is invoked to process the tablelist editcell subcommand.
# charPos stands for the character position component of the index in the body
# text widget of the character underneath the mouse cursor if this command was
# invoked by clicking mouse button 1 in the body of the tablelist widget.
#------------------------------------------------------------------------------
proc tablelist::editcellSubCmd {win row col restore {charPos -1}} {
    upvar ::tablelist::ns${win}::data data

    if {$data(isDisabled) || $data($col-hide) ||
	![isCellEditable $win $row $col]} {
	return ""
    }
    if {$data(editRow) == $row && $data(editCol) == $col} {
	return ""
    }
    if {$data(editRow) >= 0 && ![finisheditingSubCmd $win]} {
	return ""
    }

    #
    # Replace the cell contents between the two tabs with an embedded frame
    #
    set w $data(body)
    set item [lindex $data(itemList) $row]
    set key [lindex $item end]
    findCellTabs $win [expr {$row + 1}] $col tabIdx1 tabIdx2
    $w delete $tabIdx1+1c $tabIdx2
    set pixels [lindex $data(colList) [expr {2*$col}]]
    if {$pixels == 0} {				;# convention: dynamic width
	set pixels $data($col-width)
    }
    set f $data(bodyFr)
    frame $f -borderwidth 0 -container 0 -height 0 \
	     -highlightthickness 0 -relief flat -takefocus 0 \
	     -width [expr {$pixels + $data($col-delta) + 6}]
    $w window create $tabIdx1+1c -padx -3 -pady -2 -stretch 1 -window $f

    #
    # Create an entry widget as a child of the above frame
    #
    set e $data(bodyFrEnt)
    set alignment [lindex $data(colList) [expr {2*$col + 1}]]
    entry $e -borderwidth 2 -font [cellFont $win $key $col] \
	     -highlightthickness 0 -justify $alignment \
	     -relief ridge -takefocus 0
    place $e -relheight 1.0 -relwidth 1.0
    set data(editKey) $key
    set data(editRow) $row
    set data(editCol) $col

    #
    # Insert the binding tag TablelistEntry before
    # Entry in the list of binding tags of the entry
    #
    bindtags $e [linsert [bindtags $e] 1 TablelistEntry]

    #
    # Restore or initialize some of the entry's data
    #
    if {$restore} {
	restoreEntryData $win
    } else {
	set data(canceled) 0
	set text [lindex $item $col]
	if {[lindex $data(fmtCmdFlagList) $col]} {
	    set text [uplevel #0 $data($col-formatcommand) [list $text]]
	}
	$e insert 0 $text
	if {[string compare $data(-editstartcommand) ""] != 0} {
	    set text [uplevel #0 $data(-editstartcommand) \
		      [list $win $row $col $text]]
	    if {$data(canceled)} {
		return ""
	    }
	}

	$e delete 0 end
	$e insert 0 $text
	if {$charPos >= 0} {
	    #
	    # Determine the position of the insertion cursor
	    #
	    set image [doCellCget $row $col $win -image]
	    if {[string compare $alignment right] == 0} {
		scan $tabIdx2 %d.%d line tabCharIdx2
		set entryIdx [expr {[$e index end] - $tabCharIdx2 + $charPos}]
		if {[string compare $image ""] != 0} {
		    incr entryIdx 2
		}
	    } else {
		scan $tabIdx1 %d.%d line tabCharIdx1
		set entryIdx [expr {$charPos - $tabCharIdx1 - 1}]
		if {[string compare $image ""] != 0} {
		    incr entryIdx -2
		}
	    }
	    $e icursor $entryIdx
	} else {
	    $e icursor end
	    $e selection range 0 end
	}

	seecellSubCmd $win $row $col
	focus $e
	set data(rejected) 0
	set data(origEditText) $text
    }

    return ""
}

#------------------------------------------------------------------------------
# tablelist::canceleditingSubCmd
#
# This procedure is invoked to process the tablelist cancelediting subcommand.
# Aborts the interactive cell editing and restores the cell's contents after
# destroying the embedded entry widget.
#------------------------------------------------------------------------------
proc tablelist::canceleditingSubCmd win {
    upvar ::tablelist::ns${win}::data data

    if {[set row $data(editRow)] < 0} {
	return ""
    }
    set col $data(editCol)

    destroy $data(bodyFr)
    set item [lindex $data(itemList) $row]
    doCellConfig $row $col $win -text [lindex $item $col]
    focus $data(body)
    set data(canceled) 1
    return ""
}

#------------------------------------------------------------------------------
# tablelist::finisheditingSubCmd
#
# This procedure is invoked to process the tablelist finishediting subcommand.
# Invokes the command specified by the -editendcommand option if needed, and
# updates the element just edited after destroying the embedded entry widget if
# the latter's contents was not rejected.  Returns 1 on normal termination and
# 0 otherwise.
#------------------------------------------------------------------------------
proc tablelist::finisheditingSubCmd win {
    upvar ::tablelist::ns${win}::data data

    if {[set row $data(editRow)] < 0} {
	return 1
    }
    set col $data(editCol)

    set w $data(bodyFrEnt)
    set text [$w get]
    if {[string compare $text $data(origEditText)] == 0} {
	set item [lindex $data(itemList) $row]
	set text [lindex $item $col]
    } elseif {[string compare $data(-editendcommand) ""] != 0} {
	set text [uplevel #0 $data(-editendcommand) [list $win $row $col $text]]
    }

    if {$data(rejected)} {
	seecellSubCmd $win $row $col
	focus $w
	set data(rejected) 0
	return 0
    } else {
	destroy $data(bodyFr)
	doCellConfig $row $col $win -text $text
	focus $data(body)
	return 1
    }
}

#
# Private procedures used in bindings related to interactive cell editing
# =======================================================================
#

#------------------------------------------------------------------------------
# tablelist::entryInsert
#
# Inserts the string str into the entry widget w at the point of the insertion
# cursor.
#------------------------------------------------------------------------------
proc tablelist::entryInsert {w str} {
    if {[string compare [info procs ::tkEntryInsert] ::tkEntryInsert] == 0} {
	tkEntryInsert $w $str
    } else {
	tk::EntryInsert $w $str
    }
}

#------------------------------------------------------------------------------
# tablelist::cancelEditing
#
# Invokes the canceleditingSubCmd procedure.
#------------------------------------------------------------------------------
proc tablelist::cancelEditing w {
    canceleditingSubCmd [parseEntryPath $w]
}

#------------------------------------------------------------------------------
# tablelist::finishEditing
#
# Invokes the finisheditingSubCmd procedure.
#------------------------------------------------------------------------------
proc tablelist::finishEditing w {
    finisheditingSubCmd [parseEntryPath $w]
}

#------------------------------------------------------------------------------
# tablelist::moveToNext
#
# Moves the embedded entry widget w into the next editable cell different from
# the one indicated by the given row and column, if there is such a cell.
#------------------------------------------------------------------------------
proc tablelist::moveToNext {w {row -1} {col -1}} {
    set win [parseEntryPath $w]
    upvar ::tablelist::ns${win}::data data

    if {$row == -1 && $col == -1} {
	set row $data(editRow)
	set col $data(editCol)
    }

    set oldRow $row
    set oldCol $col

    while 1 {
	incr col
	if {$col > $data(lastCol)} {
	    incr row
	    if {$row > $data(lastRow)} {
		set row 0
	    }
	    set col 0
	}

	if {$row == $oldRow && $col == $oldCol} {
	    return -code break ""
	} elseif {!$data($col-hide) && [isCellEditable $win $row $col]} {
	    editcellSubCmd $win $row $col 0
	    return -code break ""
	}
    }
}

#------------------------------------------------------------------------------
# tablelist::moveToPrev
#
# Moves the embedded entry widget w into the previous editable cell different
# from the one indicated by the given row and column, if there is such a cell.
#------------------------------------------------------------------------------
proc tablelist::moveToPrev {w {row -1} {col -1}} {
    set win [parseEntryPath $w]
    upvar ::tablelist::ns${win}::data data

    if {$row == -1 && $col == -1} {
	set row $data(editRow)
	set col $data(editCol)
    }

    set oldRow $row
    set oldCol $col

    while 1 {
	incr col -1
	if {$col < 0} {
	    incr row -1
	    if {$row < 0} {
		set row $data(lastRow)
	    }
	    set col $data(lastCol)
	}

	if {$row == $oldRow && $col == $oldCol} {
	    return -code break ""
	} elseif {!$data($col-hide) && [isCellEditable $win $row $col]} {
	    editcellSubCmd $win $row $col 0
	    return -code break ""
	}
    }
}

#------------------------------------------------------------------------------
# tablelist::moveLeft
#
# Moves the embedded entry widget w into the previous editable cell of the
# current row if the cell being edited is not the first editable one within
# that row.  Otherwise sets the insertion cursor to the beginning of the entry
# and clears the selection in it.
#------------------------------------------------------------------------------
proc tablelist::moveLeft w {
    set win [parseEntryPath $w]
    upvar ::tablelist::ns${win}::data data

    set row $data(editRow)
    set col $data(editCol)

    while 1 {
	incr col -1
	if {$col < 0} {
	    #
	    # On Windows the "event generate" command does not behave
	    # as expected if a Tk version older than 8.2.2 is used.
	    #
	    if {[string compare $::tk_patchLevel 8.2.2] < 0} {
		tkEntrySetCursor $w 0
	    } else {
		event generate $w <Home>
	    }
	    return -code break ""
	} elseif {!$data($col-hide) && [isCellEditable $win $row $col]} {
	    editcellSubCmd $win $row $col 0
	    return -code break ""
	}
    }
}

#------------------------------------------------------------------------------
# tablelist::moveRight
#
# Moves the embedded entry widget w into the next editable cell of the current
# row if the cell being edited is not the last editable one within that row.
# Otherwise sets the insertion cursor to the end of the entry and clears the
# selection in it.
#------------------------------------------------------------------------------
proc tablelist::moveRight w  {
    set win [parseEntryPath $w]
    upvar ::tablelist::ns${win}::data data

    set row $data(editRow)
    set col $data(editCol)

    while 1 {
	incr col
	if {$col > $data(lastCol)} {
	    #
	    # On Windows the "event generate" command does not behave
	    # as expected if a Tk version older than 8.2.2 is used.
	    #
	    if {[string compare $::tk_patchLevel 8.2.2] < 0} {
		tkEntrySetCursor $w end
	    } else {
		event generate $w <End>
	    }
	    return -code break ""
	} elseif {!$data($col-hide) && [isCellEditable $win $row $col]} {
	    editcellSubCmd $win $row $col 0
	    return -code break ""
	}
    }
}

#------------------------------------------------------------------------------
# tablelist::moveOneLineUp
#
# Moves the embedded entry widget w into the last editable cell that is located
# in the current column and has a row index less than the given one, if there
# is such a cell.
#------------------------------------------------------------------------------
proc tablelist::moveOneLineUp {w {row -1}} {
    set win [parseEntryPath $w]
    upvar ::tablelist::ns${win}::data data

    if {$row == -1} {
	set row $data(editRow)
    }

    set col $data(editCol)

    while 1 {
	incr row -1
	if {$row < 0} {
	    return 0
	} elseif {[isCellEditable $win $row $col]} {
	    editcellSubCmd $win $row $col 0
	    return 1
	}
    }
}

#------------------------------------------------------------------------------
# tablelist::moveOneLineDown
#
# Moves the embedded entry widget w into the first editable cell that is
# located in the current column and has a row index greater than the given one,
# if there is such a cell.
#------------------------------------------------------------------------------
proc tablelist::moveOneLineDown {w {row -1}} {
    set win [parseEntryPath $w]
    upvar ::tablelist::ns${win}::data data

    if {$row == -1} {
	set row $data(editRow)
    }

    set col $data(editCol)

    while 1 {
	incr row
	if {$row > $data(lastRow)} {
	    return 0
	} elseif {[isCellEditable $win $row $col]} {
	    editcellSubCmd $win $row $col 0
	    return 1
	}
    }
}

#------------------------------------------------------------------------------
# tablelist::moveOnePageUp
#
# Moves the embedded entry widget w up by one page within the current column if
# the cell being edited is not the first editable one within that column.
#------------------------------------------------------------------------------
proc tablelist::moveOnePageUp w {
    set win [parseEntryPath $w]
    upvar ::tablelist::ns${win}::data data

    #
    # Check whether there is any editable cell
    # above the current one, in the same column
    #
    set row $data(editRow)
    set col $data(editCol)
    while 1 {
	incr row -1
	if {$row < 0} {
	    return ""
	} elseif {[isCellEditable $win $row $col]} {
	    break
	}
    }

    #
    # Scroll up the view by one page and get the corresponding row index
    #
    set row $data(editRow)
    seeSubCmd $win $row
    set bbox [bboxSubCmd $win $row]
    yviewSubCmd $win {scroll -1 pages}
    set newRow [rowIndex $win @0,[lindex $bbox 1] 0]

    if {$newRow < $row} {
	if {![moveOneLineUp $w [expr {$newRow + 1}]]} {
	    moveOneLineDown $w $newRow
	}
    } else {
	moveOneLineDown $w -1
    }
}

#------------------------------------------------------------------------------
# tablelist::moveOnePageDown
#
# Moves the embedded entry widget w down by one page within the current column
# if the cell being edited is not the last editable one within that column.
#------------------------------------------------------------------------------
proc tablelist::moveOnePageDown w {
    set win [parseEntryPath $w]
    upvar ::tablelist::ns${win}::data data

    #
    # Check whether there is any editable cell
    # below the current one, in the same column
    #
    set row $data(editRow)
    set col $data(editCol)
    while 1 {
	incr row
	if {$row > $data(lastRow)} {
	    return ""
	} elseif {[isCellEditable $win $row $col]} {
	    break
	}
    }

    #
    # Scroll down the view by one page and get the corresponding row index
    #
    set row $data(editRow)
    seeSubCmd $win $row
    set bbox [bboxSubCmd $win $row]
    yviewSubCmd $win {scroll 1 pages}
    set newRow [rowIndex $win @0,[lindex $bbox 1] 0]

    if {$newRow > $row} {
	if {![moveOneLineDown $w [expr {$newRow - 1}]]} {
	    moveOneLineUp $w $newRow
	}
    } else {
	moveOneLineUp $w $data(itemCount)
    }
}

#------------------------------------------------------------------------------
# tablelist::genMouseWheelEvent
#
# Generates a <MouseWheel> event with the given delta on the widget w.
#------------------------------------------------------------------------------
proc tablelist::genMouseWheelEvent {w delta} {
    set focus [focus -displayof $w]
    focus $w
    event generate $w <MouseWheel> -delta $delta
    focus $focus
}

#
# Private utility procedures related to interactive cell editing
# ==============================================================
#

#------------------------------------------------------------------------------
# tablelist::saveEntryData
#
# Saves some data of the entry widget associated with the tablelist win.
#------------------------------------------------------------------------------
proc tablelist::saveEntryData win {
    upvar ::tablelist::ns${win}::data data

    #
    # Configuration options
    #
    set w $data(bodyFrEnt)
    foreach configSet [$w configure] {
	if {[llength $configSet] != 2} {
	    set opt [lindex $configSet 0]
	    set data(entry$opt) [lindex $configSet 4]
	}
    }

    #
    # Widget callbacks
    #
    if {[info exists ::wcb::version]} {
	foreach when {before after} {
	    foreach opt {insert delete motion} {
		set data(entryCb-$when-$opt) [::wcb::callback $w $when $opt]
	    }
	}
    }

    #
    # Other data
    #
    set data(entryText) [$w get]
    set data(entryPos)  [$w index insert]
    if {[set data(entryHadSel) [$w selection present]]} {
	set data(entrySelFrom) [$w index sel.first]
	set data(entrySelTo)   [$w index sel.last]
    }
    set data(entryHadFocus) \
	[expr {[string compare [focus -lastfor $w] $w] == 0}]
}

#------------------------------------------------------------------------------
# tablelist::restoreEntryData
#
# Saves some data of the entry widget associated with the tablelist win.
#------------------------------------------------------------------------------
proc tablelist::restoreEntryData win {
    upvar ::tablelist::ns${win}::data data

    #
    # Configuration options
    #
    set w $data(bodyFrEnt)
    foreach name [array names data entry-*] {
	set opt [string range $name 5 end]
	$w configure $opt $data($name)
    }

    #
    # Widget callbacks
    #
    if {[info exists ::wcb::version]} {
	foreach when {before after} {
	    foreach opt {insert delete motion} {
		eval [list ::wcb::callback $w $when $opt] \
		     $data(entryCb-$when-$opt)
	    }
	}
    }

    #
    # Other data
    #
    $w insert 0 $data(entryText)
    $w icursor $data(entryPos)
    if {$data(entryHadSel)} {
	$w selection range $data(entrySelFrom) $data(entrySelTo)
    }
    if {$data(entryHadFocus)} {
	focus $w
    }
}

#------------------------------------------------------------------------------
# tablelist::parseEntryPath
#
# Extracts the path name of the tablelist widget from the path name w of the
# embedded entry widget.
#------------------------------------------------------------------------------
proc tablelist::parseEntryPath w {
    regexp {^(.+)\.body\.f\.e$} $w dummy win
    return $win
}

#==============================================================================
# Contains the implementation of the tablelist move and movecolumn subcommands.
#
# Copyright (c) 2003  Csaba Nemethi (E-mail: csaba.nemethi@t-online.de)
#==============================================================================

#------------------------------------------------------------------------------
# tablelist::moveSubCmd
#
# This procedure is invoked to process the tablelist move subcommand.
#------------------------------------------------------------------------------
proc tablelist::moveSubCmd {win source target} {
    upvar ::tablelist::ns${win}::data data

    if {$data(isDisabled) || $data(itemCount) == 0} {
	return ""
    }

    #
    # Adjust the indices to fit within the existing items and check them
    #
    if {$source > $data(lastRow)} {
	set source $data(lastRow)
    } elseif {$source < 0} {
	set source 0
    }
    if {$target > $data(itemCount)} {
	set target $data(itemCount)
    } elseif {$target < 0} {
	set target 0
    }
    if {$target == $source} {
	return -code error \
	       "cannot move item with index \"$source\" before itself"
    } elseif {$target == $source + 1} {
	return ""
    }

    #
    # Save some data of the entry widget if present
    #
    if {[set editCol $data(editCol)] >= 0} {
	set editRow $data(editRow)
	set editKey $data(editKey)
	saveEntryData $win
    }

    #
    # Check whether the source line is selected and then delete it
    #
    set w $data(body)
    set textIdx1 [expr {double($source + 1)}]
    set textIdx2 [expr {double($source + 2)}]
    if {[lsearch -exact [$w tag names $textIdx1] select] >= 0} {
	set selected 1
    } else {
	set selected 0
    }
    $w delete $textIdx1 $textIdx2

    #
    # Insert the source item before the target one
    #
    set target1 $target
    if {$source < $target} {
	incr target1 -1
    }
    set targetLine [expr {$target1 + 1}]
    $w insert $targetLine.0 \n
    set snipStr $data(-snipstring)
    set sourceItem [lindex $data(itemList) $source]
    set dispItem [strToDispStr $sourceItem]
    set key [lindex $sourceItem end]
    array set tagData [array get data $key*-\[bf\]*]		;# for speed
    set rowTags [array names tagData $key-\[bf\]*]
    set col 0
    foreach text [lrange $dispItem 0 $data(lastCol)] \
	    colFont $data(colFontList) \
	    colTags $data(colTagsList) \
	    fmtCmdFlag $data(fmtCmdFlagList) \
	    {pixels alignment} $data(colList) {
	if {$data($col-hide)} {
	    incr col
	    continue
	}

	#
	# Adjust the cell text and the image width
	#
	if {$fmtCmdFlag} {
	    set text [uplevel #0 $data($col-formatcommand) \
		      [list [lindex $sourceItem $col]]]
	    set text [strToDispStr $text]
	}
	if {[info exists data($key-$col-image)]} {
	    set image $data($key-$col-image)
	    set imageWidth [image width $image]
	} else {
	    set image ""
	    set imageWidth 0
	}
	if {[info exists data($key-$col-font)]} {
	    set cellFont $data($key-$col-font)
	} elseif {[info exists data($key-font)]} {
	    set cellFont $data($key-font)
	} else {
	    set cellFont $colFont
	}
	if {$pixels != 0} {			;# convention: static width
	    incr pixels $data($col-delta)
	}
	adjustElem $win text imageWidth $cellFont \
		   $pixels $alignment $snipStr

	#
	# Insert the text and the image
	#
	set cellTags [array names tagData $key-$col-\[bf\]*]
	set tagNames [concat $colTags $rowTags $cellTags]
	if {$imageWidth == 0} {
	    $w insert $targetLine.end \t$text\t $tagNames
	} else {
	    $w insert $targetLine.end \t\t $tagNames
	    insertElem $w $targetLine.end-1c $text $image $imageWidth $alignment
	}

	incr col
    }

    #
    # Update the item list
    #
    set data(itemList) [lreplace $data(itemList) $source $source]
    if {$target == $data(itemCount)} {
	lappend data(itemList) $sourceItem	;# this works much faster
    } else {
	set data(itemList) [linsert $data(itemList) $target1 $sourceItem]
    }

    #
    # Update the list variable if present
    #
    if {$data(hasListVar)} {
	trace vdelete ::$data(-listvariable) wu $data(listVarTraceCmd)
	upvar #0 $data(-listvariable) var
	set var [lreplace $var $source $source]
	set pureSourceItem [lrange $sourceItem 0 $data(lastCol)]
	if {$target == $data(itemCount)} {
	    lappend var $pureSourceItem		;# this works much faster
	} else {
	    set var [linsert $var $target1 $pureSourceItem]
	}
	trace variable ::$data(-listvariable) wu $data(listVarTraceCmd)
    }

    #
    # Update anchorIdx and activeIdx if needed
    #
    if {$data(anchorIdx) == $source} {
	set data(anchorIdx) $target1
    }
    if {$data(activeIdx) == $source} {
	set data(activeIdx) $target1
    }

    #
    # Restore the stripes in the body text widget
    #
    makeStripesWhenIdle $win

    #
    # Select the source item if it was selected before
    #
    if {$selected} {
	selectionSubCmd $win set $target1 $target1
    }

    #
    # Restore the entry widget if it was present before
    #
    if {$editCol >= 0} {
	if {$editRow == $source} {
	    editcellSubCmd $win $target1 $editCol 1
	} else {
	    set data(editRow) [lsearch $data(itemList) "* $editKey"]
	}
    }

    return ""
}

#------------------------------------------------------------------------------
# tablelist::movecolumnSubCmd
#
# This procedure is invoked to process the tablelist movecolumn subcommand.
#------------------------------------------------------------------------------
proc tablelist::movecolumnSubCmd {win source target} {
    upvar ::tablelist::ns${win}::data data

    if {$data(isDisabled)} {
	return ""
    }

    #
    # Check the indices
    #
    if {$target == $source} {
	return -code error \
	       "cannot move column with index \"$source\" before itself"
    } elseif {$target == $source + 1} {
	return ""
    }

    #
    # Update the column list
    #
    set source3 [expr {3*$source}]
    set source3Plus2 [expr {$source3 + 2}]
    set target1 $target
    set target3 [expr {3*$target}]
    if {$source < $target} {
	incr target1 -1
	incr target3 -3
    }
    set sourceRange [lrange $data(-columns) $source3 $source3Plus2]
    set data(-columns) [lreplace $data(-columns) $source3 $source3Plus2]
    set data(-columns) [eval linsert {$data(-columns)} $target3 $sourceRange]

    #
    # Save some elements of data corresponding to source
    #
    array set tmp [array get data $source-*]
    array set tmp [array get data k*-$source-*]
    set tmp(editCol) $data(editCol)
    set tmp(arrowCol) $data(arrowCol)
    set tmp(sortCol) $data(sortCol)

    #
    # Remove source from the list of stretchable columns
    # if it was explicitly specified as stretchable
    #
    if {[string first $data(-stretch) all] != 0} {
	set sourceIsStretchable 0
	set stretchableCols {}
	foreach elem $data(-stretch) {
	    if {[string first $elem end] != 0 && $elem == $source} {
		set sourceIsStretchable 1
	    } else {
		lappend stretchableCols $elem
	    }
	}
	set data(-stretch) $stretchableCols
    }

    #
    # Build two lists of column numbers, neeeded
    # for shifting some elements of the data array
    #
    if {$source < $target} {
	for {set n $source} {$n < $target1} {incr n} {
	    lappend oldCols [expr {$n + 1}]
	    lappend newCols $n
	}
    } else {
	for {set n $source} {$n > $target} {incr n -1} {
	    lappend oldCols [expr {$n - 1}]
	    lappend newCols $n
	}
    }

    #
    # Move the elements of data corresponding to the columns in oldCols to the
    # elements corresponding to the columns with the same indices in newCols
    #
    foreach oldCol $oldCols newCol $newCols {
	moveColData $win data data imgs $oldCol $newCol
    }

    #
    # Move the elements of data corresponding to
    # source to the elements corresponding to target1
    #
    moveColData $win tmp data imgs $source $target1

    #
    # If the column given by source was explicitly specified as
    # stretchable then add target1 to the list of stretchable columns
    #
    if {[string first $data(-stretch) all] != 0 && $sourceIsStretchable} {
	lappend data(-stretch) $target1
	sortStretchableColList $win
    }

    #
    # Update the item list
    #
    set newItemList {}
    foreach item $data(itemList) {
	set sourceText [lindex $item $source]
	set item [lreplace $item $source $source]
	set item [linsert $item $target1 $sourceText]
	lappend newItemList $item
    }
    set data(itemList) $newItemList

    #
    # Update the list variable if present
    #
    condUpdateListVar $win

    #
    # Set up and adjust the columns, and rebuild
    # the lists of the column fonts and tag names
    #
    setupColumns $win $data(-columns) 0
    makeColFontAndTagLists $win
    adjustColumns $win {} 0

    #
    # Reconfigure the relevant column labels
    #
    foreach col [lappend newCols $target1] {
	reconfigColLabels $win imgs $col
    }

    #
    # Make sure the items will be redisplayed at idle time
    #
    redisplayWhenIdle $win
    return ""
}

#==============================================================================
# Contains the implementation of the tablelist::sortByColumn command as well as
# of the tablelist sort and sortbycolumn subcommands.
#
# Copyright (c) 2000-2003  Csaba Nemethi (E-mail: csaba.nemethi@t-online.de)
#==============================================================================

#------------------------------------------------------------------------------
# tablelist::sortByColumn
#
# Sorts the contents of the tablelist widget win by its col'th column.  Returns
# the sorting order (increasing or decreasing).
#------------------------------------------------------------------------------
proc tablelist::sortByColumn {win col} {
    #
    # Check the arguments
    #
    if {![winfo exists $win]} {
	return -code error "bad window path name \"$win\""
    }
    if {[string compare [winfo class $win] Tablelist] != 0} {
	return -code error "window \"$win\" is not a tablelist widget"
    }
    if {[catch {::$win columnindex $col} result] != 0} {
	return -code error $result
    }
    if {$result < 0 || $result >= [::$win columncount]} {
	return -code error "column index \"$col\" out of range"
    }

    #
    # Determine the sorting order
    #
    set col $result
    if {$col == [::$win sortcolumn] &&
	[string compare [::$win sortorder] increasing] == 0} {
	set sortOrder decreasing
    } else {
	set sortOrder increasing
    }

    #
    # Sort the widget's contents based on the given column
    #
    if {[catch {::$win sortbycolumn $col -$sortOrder} result] == 0} {
	return $sortOrder
    } else {
	return -code error $result
    }
}

#------------------------------------------------------------------------------
# tablelist::sortSubCmd
#
# This procedure is invoked to process the tablelist sort and sortbycolumn
# subcommands.
#------------------------------------------------------------------------------
proc tablelist::sortSubCmd {win col order} {
    upvar ::tablelist::ns${win}::data data

    #
    # Save the keys corresponding to anchorIdx and activeIdx 
    #
    foreach type {anchor active} {
	set item [lindex $data(itemList) $data(${type}Idx)]
	set ${type}Key [lindex $item end]
    }

    #
    # Save the keys of the selected items
    #
    set selKeys {}
    set w $data(body)
    set selRange [$w tag nextrange select 1.0]
    while {[llength $selRange] != 0} {
	set selStart [lindex $selRange 0]
	set selEnd [lindex $selRange 1]
	set item [lindex $data(itemList) [expr {int($selStart) - 1}]]
	lappend selKeys [lindex $item end]

	set selRange [$w tag nextrange select $selEnd]
    }

    #
    # Save some data of the entry widget if present
    #
    if {[set editCol $data(editCol)] >= 0} {
	set editKey $data(editKey)
	saveEntryData $win
    }

    #
    # Sort the item list and update the sort info
    #
    if {$col < 0} {				;# not sorting by a column
	if {[string compare $data(-sortcommand) ""] == 0} {
	    return -code error \
		   "value of the -sortcommand option is empty"
	}

	set data(itemList) \
	    [lsort $order -command $data(-sortcommand) $data(itemList)]
    } else {					;# sorting by a column
	if {[string compare $data($col-sortmode) command] == 0} {
	    if {[info exists data($col-sortcommand)]} {
		set data(itemList) \
		    [lsort $order -index $col \
		     -command $data($col-sortcommand) $data(itemList)]
	    } else {
		return -code error \
		       "value of the -sortcommand option for\
			column $col is missing or empty"
	    }
	} else {
	    set data(itemList) \
		[lsort $order -index $col \
		 -$data($col-sortmode) $data(itemList)]
	}
    }
    set data(sortCol) $col
    set data(sortOrder) [string range $order 1 end]

    #
    # Replace the contents of the list variable if present
    #
    if {$data(hasListVar)} {
	trace vdelete ::$data(-listvariable) wu $data(listVarTraceCmd)
	upvar #0 $data(-listvariable) var
	set var {}
	foreach item $data(itemList) {
	    lappend var [lrange $item 0 $data(lastCol)]
	}
	trace variable ::$data(-listvariable) wu $data(listVarTraceCmd)
    }

    #
    # Update anchorIdx and activeIdx
    #
    foreach type {anchor active} {
	upvar 0 ${type}Key key
	if {[string compare $key ""] != 0} {
	    set data(${type}Idx) [lsearch $data(itemList) "* $key"]
	}
    }

    #
    # Unmanage the canvas and adjust the columns
    #
    set canvas $data(hdrTxtFrCanv)
    place forget $canvas
    set oldArrowCol $data(arrowCol)
    set data(arrowCol) -1
    adjustColumns $win l$oldArrowCol 1

    #
    # Check whether an up- or down-arrow is to be displayed
    #
    if {$col >= 0 && $data(-showarrow) && $data($col-showarrow)} {
	#
	# Configure the canvas and draw the arrows
	#
	set data(arrowCol) $col
	configCanvas $win
	drawArrows $win

	#
	# Make sure the arrow will fit into the column
	#
	set idx [expr {2*$col}]
	set pixels [lindex $data(colList) $idx]
	if {$pixels != 0 && $pixels < $data(arrowWidth)} {
	    set data(colList) \
		[lreplace $data(colList) $idx $idx $data(arrowWidth)]
	    set idx [expr {3*$col}]
	    set data(-columns) \
		[lreplace $data(-columns) $idx $idx -$data(arrowWidth)]
	}

	#
	# Adjust the columns; this will also place the canvas into the label
	#
	adjustColumns $win l$col 1
    }

    #
    # Delete the items from the body text widget and insert the sorted ones.
    # Interestingly, for a large number of items it is much more efficient
    # to empty each line individually than to invoke a global delete command.
    #
    set widgetFont $data(-font)
    set snipStr $data(-snipstring)
    set isSimple [expr {$data(tagCount) == 0 && $data(imgCount) == 0 &&
			!$data(hasColTags)}]
    set line 1
    foreach item $data(itemList) {
	#
	# Empty the line, clip the elements if necessary,
	# and insert them with the corresponding tags
	#
	$w delete $line.0 $line.end
	set dispItem [strToDispStr $item]
	set col 0
	if {$isSimple} {
	    set insertStr ""
	    foreach text [lrange $dispItem 0 $data(lastCol)] \
		    fmtCmdFlag $data(fmtCmdFlagList) \
		    {pixels alignment} $data(colList) {
		if {$data($col-hide)} {
		    incr col
		    continue
		}

		#
		# Clip the element if necessary
		#
		if {$fmtCmdFlag} {
		    set text [uplevel #0 $data($col-formatcommand) \
			      [list [lindex $item $col]]]
		    set text [strToDispStr $text]
		}
		if {$pixels != 0} {		;# convention: static width
		    incr pixels $data($col-delta)
		    set text [strRangeExt $win $text $widgetFont \
			      $pixels $alignment $snipStr]
		}

		append insertStr \t$text\t
		incr col
	    }

	    #
	    # Insert the item into the body text widget
	    #
	    $w insert $line.0 $insertStr

	} else {
	    set key [lindex $item end]
	    array set tagData [array get data $key*-\[bf\]*]	;# for speed

	    set rowTags [array names tagData $key-\[bf\]*]
	    foreach text [lrange $dispItem 0 $data(lastCol)] \
		    colFont $data(colFontList) \
		    colTags $data(colTagsList) \
		    fmtCmdFlag $data(fmtCmdFlagList) \
		    {pixels alignment} $data(colList) {
		if {$data($col-hide)} {
		    incr col
		    continue
		}

		#
		# Adjust the cell text and the image width
		#
		if {$fmtCmdFlag} {
		    set text [uplevel #0 $data($col-formatcommand) \
			      [list [lindex $item $col]]]
		    set text [strToDispStr $text]
		}
		if {[info exists data($key-$col-image)]} {
		    set image $data($key-$col-image)
		    set imageWidth [image width $image]
		} else {
		    set image ""
		    set imageWidth 0
		}
		if {[info exists data($key-$col-font)]} {
		    set cellFont $data($key-$col-font)
		} elseif {[info exists data($key-font)]} {
		    set cellFont $data($key-font)
		} else {
		    set cellFont $colFont
		}
		if {$pixels != 0} {		;# convention: static width
		    incr pixels $data($col-delta)
		}
		adjustElem $win text imageWidth $cellFont \
			   $pixels $alignment $snipStr

		#
		# Insert the text and the image
		#
		set cellTags [array names tagData $key-$col-\[bf\]*]
		set tagNames [concat $colTags $rowTags $cellTags]
		if {$imageWidth == 0} {
		    $w insert $line.end \t$text\t $tagNames
		} else {
		    $w insert $line.end \t\t $tagNames
		    insertElem $w $line.end-1c $text $image $imageWidth \
			       $alignment
		}

		incr col
	    }

	    unset tagData
	}

	incr line
    }

    #
    # Restore the stripes in the body text widget
    #
    makeStripes $win

    #
    # Select the items that were selected before
    #
    foreach key $selKeys {
	set idx [lsearch $data(itemList) "* $key"]
	selectionSubCmd $win set $idx $idx
    }

    #
    # Restore the entry widget if it was present before
    #
    if {$editCol >= 0} {
	set editRow [lsearch $data(itemList) "* $editKey"]
	editcellSubCmd $win $editRow $editCol 1
    }

    #
    # Disable the body text widget if it was disabled before
    #
    if {$data(isDisabled)} {
	$w tag add disabled 1.0 end
	$w tag configure select -borderwidth 0
    }

    return ""
}

#==============================================================================
# Contains private utility procedures for tablelist widgets.
#
# Copyright (c) 2000-2003  Csaba Nemethi (E-mail: csaba.nemethi@t-online.de)
#==============================================================================

#------------------------------------------------------------------------------
# tablelist::rowIndex
#
# Checks the row index idx and returns either its numerical value or an error.
# endIsSize must be a boolean value: if true, end refers to the number of items
# in the tablelist, i.e., to the element just after the last one; if false, end
# refers to 1 less than the number of items, i.e., to the last element in the
# tablelist.
#------------------------------------------------------------------------------
proc tablelist::rowIndex {win idx endIsSize} {
    upvar ::tablelist::ns${win}::data data

    set idxLen [string length $idx]
    if {[string first $idx active] == 0 && $idxLen >= 2} {
	return $data(activeIdx)
    } elseif {[string first $idx anchor] == 0 && $idxLen >= 2} {
	return $data(anchorIdx)
    } elseif {[string first $idx end] == 0} {
	if {$endIsSize} {
	    return $data(itemCount)
	} else {
	    return $data(lastRow)
	}
    } elseif {[string compare [string index $idx 0] @] == 0} {
	if {[catch {$data(body) index $idx}] == 0} {
	    if {$data(itemCount) == 0} {
		return -1
	    } else {
		scan $idx @%d,%d x y
		incr x -[winfo x $data(body)]
		incr y -[winfo y $data(body)]
		set textIdx [$data(body) index @$x,$y]
		return [expr {int($textIdx) - 1}]
	    }
	} else {
	    return -code error \
		   "bad row index \"$idx\": must be active, anchor,\
		    end, @x,y, a number, or a full key"
	}
    } elseif {[string compare [string index $idx 0] k] == 0} {
	if {[set index [lsearch $data(itemList) "* $idx"]] >= 0} {
	    return $index
	} else {
	    return -code error \
		   "bad row index \"$idx\": must be active, anchor,\
		    end, @x,y, a number, or a full key"
	}
    } elseif {[catch {format %d $idx} index] == 0} {
	return $index
    } else {
	return -code error \
	       "bad row index \"$idx\": must be active, anchor,\
	        end, @x,y, a number, or a full key"
    }
}

#------------------------------------------------------------------------------
# tablelist::colIndex
#
# Checks the column index idx and returns either its numerical value or an
# error.  checkRange must be a boolean value: if true, it is additionally
# checked whether the numerical value corresponding to idx is within the
# allowed range.
#------------------------------------------------------------------------------
proc tablelist::colIndex {win idx checkRange} {
    upvar ::tablelist::ns${win}::data data

    if {[string first $idx end] == 0} {
	return $data(lastCol)
    } elseif {[string compare [string index $idx 0] @] == 0 &&
	      [catch {$data(body) index $idx}] == 0} {
	scan $idx @%d x
	incr x -[winfo x $data(body)]
	set bodyWidth [winfo width $data(body)]
	if {$x >= $bodyWidth} {
	    set x [expr {$bodyWidth - 1}]
	} elseif {$x < 0} {
	    set x 0
	}
	set x [expr {$x + [winfo rootx $data(body)]}]

	set lastVisibleCol -1
	for {set col 0} {$col < $data(colCount)} {incr col} {
	    if {$data($col-hide)} {
		continue
	    }

	    set lastVisibleCol $col
	    set w $data(hdrTxtFrLbl)$col
	    set wX [winfo rootx $w]
	    if {$x >= $wX && $x < $wX + [winfo width $w]} {
		return $col
	    }
	}
	set index $lastVisibleCol
    } elseif {[catch {format %d $idx} index] != 0} {
	for {set col 0} {$col < $data(colCount)} {incr col} {
	    set hasName [info exists data($col-name)]
	    if {$hasName && [string compare $idx $data($col-name)] == 0 ||
		!$hasName && [string compare $idx ""] == 0} {
		set index $col
		break
	    }
	}
	if {$col == $data(colCount)} {
	    return -code error \
		   "bad column index \"$idx\": must be\
		    end, @x,y, a number, or a name"
	}
    }

    if {$checkRange && ($index < 0 || $index > $data(lastCol))} {
	return -code error "column index \"$idx\" out of range"
    } else {
	return $index
    }
}

#------------------------------------------------------------------------------
# tablelist::cellIndex
#
# Checks the cell index idx and returns either its value in the form row,col or
# an error.  checkRange must be a boolean value: if true, it is additionally
# checked whether the two numerical values corresponding to idx are within the
# respective allowed ranges.
#------------------------------------------------------------------------------
proc tablelist::cellIndex {win idx checkRange} {
    upvar ::tablelist::ns${win}::data data

    if {[string first $idx end] == 0} {
	set row [rowIndex $win $idx 0]
	set col [colIndex $win $idx 0]
    } elseif {[string compare [string index $idx 0] @] == 0} {
	if {[catch {rowIndex $win $idx 0} row] != 0 ||
	    [catch {colIndex $win $idx 0} col] != 0} {
	    return -code error \
		   "bad cell index \"$idx\": must be end, @x,y, or row,col,\
		    where row must be active, anchor, end, a number, or\
		    a full key, and col must be end, a number, or a name"
	}
    } else {
	set lst [split $idx ,]
	if {[llength $lst] != 2 ||
	    [catch {rowIndex $win [lindex $lst 0] 0} row] != 0 ||
	    [catch {colIndex $win [lindex $lst 1] 0} col] != 0} {
	    return -code error \
		   "bad cell index \"$idx\": must be end, @x,y, or row,col,\
		    where row must be active, anchor, end, a number, or\
		    a full key, and col must be end, a number, or a name"
	}
    }

    if {$checkRange && ($row < 0 || $row > $data(lastRow) || \
	$col < 0 || $col > $data(lastCol))} {
	return -code error "cell index \"$idx\" out of range"
    } else {
	return $row,$col
    }
}

#------------------------------------------------------------------------------
# tablelist::findCellTabs
#
# Searches for the tab characters within the col'th cell in the given line of
# the body text child of the tablelist widget win.  Assigns the index of the
# first tab to $idx1Name and the index of the second tab to $idx2Name.
#------------------------------------------------------------------------------
proc tablelist::findCellTabs {win line col idx1Name idx2Name} {
    upvar ::tablelist::ns${win}::data data
    upvar $idx1Name idx1 $idx2Name idx2

    set w $data(body)
    set idx1 $line.0
    set endIdx $line.end
    for {set n 0} {$n < $col} {incr n} {
	if {!$data($n-hide)} {
	    set idx1 [$w search \t $idx1+1c $endIdx]+1c
	}
    }
    set idx1 [$w index $idx1]
    set idx2 [$w search \t $idx1+1c $endIdx]

    return ""
}

#------------------------------------------------------------------------------
# tablelist::cellFont
#
# Returns the font to be used in the tablelist cell specified by win, key, and
# col.
#------------------------------------------------------------------------------
proc tablelist::cellFont {win key col} {
    upvar ::tablelist::ns${win}::data data

    if {[info exists data($key-$col-font)]} {
	return $data($key-$col-font)
    } elseif {[info exists data($key-font)]} {
	return $data($key-font)
    } else {
	return [lindex $data(colFontList) $col]
    }
}

#------------------------------------------------------------------------------
# tablelist::sortStretchableColList
#
# Replaces the column indices different from end in the list of the stretchable
# columns of the tablelist widget win with their numerical equivalents and
# sorts the resulting list.
#------------------------------------------------------------------------------
proc tablelist::sortStretchableColList win {
    upvar ::tablelist::ns${win}::data data

    if {[llength $data(-stretch)] == 0 ||
	[string first $data(-stretch) all] == 0} {
	return ""
    }

    set containsEnd 0
    foreach elem $data(-stretch) {
	if {[string first $elem end] == 0} {
	    set containsEnd 1
	} else {
	    set tmp([colIndex $win $elem 0]) ""
	}
    }

    set data(-stretch) [lsort -integer [array names tmp]]
    if {$containsEnd} {
	lappend data(-stretch) end
    }
}

#------------------------------------------------------------------------------
# tablelist::deleteColData
#
# Cleans up the data associated with the col'th column of the tablelist widget
# win.
#------------------------------------------------------------------------------
proc tablelist::deleteColData {win col} {
    upvar ::tablelist::ns${win}::data data

    if {$data(editCol) == $col} {
	set data(editCol) -1
	set data(editRow) -1
    }
    if {$data(arrowCol) == $col} {
	set data(arrowCol) -1
    }
    if {$data(sortCol) == $col} {
	set data(sortCol) -1
    }

    #
    # Remove the elements with names of the form $col-*
    #
    set w $data(body)
    foreach name [array names data $col-*] {
	unset data($name)
	$w tag delete $name
    }

    #
    # Remove the elements with names of the form k*-$col-*
    #
    foreach name [array names data k*-$col-*] {
	unset data($name)
	$w tag delete $name
	if {[string match k*-$col-\[bf\]* $name]} {
	    incr data(tagCount) -1
	} elseif {[string match k*-$col-image $name]} {
	    incr data(imgCount) -1
	}
    }

    #
    # Remove col from the list of stretchable columns if explicitly specified
    #
    if {[string first $data(-stretch) all] != 0} {
	set stretchableCols {}
	foreach elem $data(-stretch) {
	    if {[string first $elem end] == 0 || $elem != $col} {
		lappend stretchableCols $elem
	    }
	}
	set data(-stretch) $stretchableCols
    }
}

#------------------------------------------------------------------------------
# tablelist::moveColData
#
# Moves the elements of oldArrName corresponding to oldCol to those of
# newArrName corresponding to newCol.
#------------------------------------------------------------------------------
proc tablelist::moveColData {win oldArrName newArrName imgArrName
			     oldCol newCol} {
    upvar $oldArrName oldArr $newArrName newArr $imgArrName imgArr

    #
    # Move the elements of oldArr with names of the form $oldCol-*
    # to those of newArr with names of the form $newCol-*
    #
    set w $newArr(body)
    foreach newName [array names newArr $newCol-*] {
	unset newArr($newName)
	$w tag delete $newName
    }
    if {$newCol < $newArr(colCount)} {
	foreach c [winfo children $newArr(hdrTxtFrLbl)$newCol] {
	    destroy $c
	}
	set newArr(fmtCmdFlagList) \
	    [lreplace $newArr(fmtCmdFlagList) $newCol $newCol 0]
    }
    if {$oldArr(editCol) == $oldCol} {
	set newArr(editCol) $newCol
    }
    if {$oldArr(arrowCol) == $oldCol} {
	set newArr(arrowCol) $newCol
    }
    if {$oldArr(sortCol) == $oldCol} {
	set newArr(sortCol) $newCol
    }
    foreach oldName [array names oldArr $oldCol-*] {
	regsub $oldCol- $oldName $newCol- newName
	set newArr($newName) $oldArr($oldName)

	unset oldArr($oldName)
	$w tag delete $oldName

	set tail [lindex [split $newName -] 1]
	switch $tail {
	    background -
	    foreground {
		$w tag configure $newName -$tail $newArr($newName)
		$w tag raise $newName stripe
	    }
	    font {
		$w tag configure $newName -$tail $newArr($newName)
		$w tag lower $newName
	    }
	    formatcommand {
		if {$newCol < $newArr(colCount)} {
		    set newArr(fmtCmdFlagList) \
			[lreplace $newArr(fmtCmdFlagList) $newCol $newCol 1]
		}
	    }
	    labelimage {
		set imgArr($newCol-$tail) $newArr($newName)
		unset newArr($newName)
	    }
	    selectbackground -
	    selectforeground {
		set tail [string range $tail 6 end]	;# remove the select
		$w tag configure $newName -$tail $newArr($newName)
		$w tag raise $newName select
	    }
	}
    }

    #
    # Move the elements of oldArr with names of the form k*-$oldCol-*
    # to those of newArr with names of the form k*-$newCol-*
    #
    foreach newName [array names newArr k*-$newCol-*] {
	unset newArr($newName)
	$w tag delete $newName
    }
    foreach oldName [array names oldArr k*-$oldCol-*] {
	regsub -- -$oldCol- $oldName -$newCol- newName
	set newArr($newName) $oldArr($oldName)

	unset oldArr($oldName)
	$w tag delete $oldName

	set tail [lindex [split $newName -] 2]
	switch $tail {
	    background -
	    foreground {
		$w tag configure $newName -$tail $newArr($newName)
		$w tag lower $newName disabled
	    }
	    font {
		$w tag configure $newName -$tail $newArr($newName)
		$w tag raise $newName
	    }
	    selectbackground -
	    selectforeground {
		set tail [string range $tail 6 end]	;# remove the select
		$w tag configure $newName -$tail $newArr($newName)
		$w tag lower $newName disabled
	    }
	}
    }

    #
    # Replace oldCol with newCol in the list of
    # stretchable columns if explicitly specified
    #
    if {[info exists oldArr(-stretch)] &&
	[string first $oldArr(-stretch) all] != 0} {
	set stretchableCols {}
	foreach elem $oldArr(-stretch) {
	    if {[string first $elem end] != 0 && $elem == $oldCol} {
		lappend stretchableCols $newCol
	    } else {
		lappend stretchableCols $elem
	    }
	}
	set newArr(-stretch) $stretchableCols
    }
}

#------------------------------------------------------------------------------
# tablelist::condUpdateListVar
#
# Updates the list variable of the tablelist widget win if present.
#------------------------------------------------------------------------------
proc tablelist::condUpdateListVar win {
    upvar ::tablelist::ns${win}::data data

    if {$data(hasListVar)} {
	trace vdelete ::$data(-listvariable) wu $data(listVarTraceCmd)
	upvar #0 $data(-listvariable) var
	set var {}
	foreach item $data(itemList) {
	    lappend var [lrange $item 0 $data(lastCol)]
	}
	trace variable ::$data(-listvariable) wu $data(listVarTraceCmd)
    }
}

#------------------------------------------------------------------------------
# tablelist::reconfigColLabels
#
# Reconfigures the labels of the col'th column of the tablelist widget win.
#------------------------------------------------------------------------------
proc tablelist::reconfigColLabels {win imgArrName col} {
    upvar ::tablelist::ns${win}::data data
    upvar $imgArrName imgArr

    foreach opt {-labelalign -labelbackground -labelborderwidth -labelfont
		 -labelforeground -labelheight -labelpady -labelrelief} {
	if {[info exists data($col$opt)]} {
	    doColConfig $col $win $opt $data($col$opt)
	} else {
	    doColConfig $col $win $opt ""
	}
    }

    if {[info exists imgArr($col-labelimage)]} {
	doColConfig $col $win -labelimage $imgArr($col-labelimage)
    }
}

#------------------------------------------------------------------------------
# tablelist::strRange
#
# Returns the largest initial (for alignment = left or center) or final (for
# alignment = right) range of characters from str whose width, when displayed
# in the given font, is no greater than pixels.
#------------------------------------------------------------------------------
proc tablelist::strRange {win str font pixels alignment} {
    if {[font measure $font -displayof $win $str] <= $pixels} {
	return $str
    }

    set halfLen [expr {[string length $str] / 2}]
    if {$halfLen == 0} {
	return ""
    }

    if {[string compare $alignment right] == 0} {
	set rightStr [string range $str $halfLen end]
	set width [font measure $font -displayof $win $rightStr]
	if {$width == $pixels} {
	    return $rightStr
	} elseif {$width > $pixels} {
	    return [strRange $win $rightStr $font $pixels $alignment]
	} else {
	    set str [string range $str 0 [expr {$halfLen - 1}]]
	    return [strRange $win $str $font \
		    [expr {$pixels - $width}] $alignment]$rightStr
	}
    } else {
	set leftStr [string range $str 0 [expr {$halfLen - 1}]]
	set width [font measure $font -displayof $win $leftStr]
	if {$width == $pixels} {
	    return $leftStr
	} elseif {$width > $pixels} {
	    return [strRange $win $leftStr $font $pixels $alignment]
	} else {
	    set str [string range $str $halfLen end]
	    return $leftStr[strRange $win $str $font \
			    [expr {$pixels - $width}] $alignment]
	}
    }
}

#------------------------------------------------------------------------------
# tablelist::strRangeExt
#
# Invokes strRange with the given arguments and returns a string obtained by
# appending (for alignment = left or center) or prepending (for alignment =
# right) (part of) the snip string to (part of) its result.
#------------------------------------------------------------------------------
proc tablelist::strRangeExt {win str font pixels alignment snipStr} {
    set subStr [strRange $win $str $font $pixels $alignment]
    set len [string length $subStr]
    if {$pixels < 0 || $len == [string length $str] ||
	[string compare $snipStr ""] == 0} {
	return $subStr
    }

    if {[string compare $alignment right] == 0} {
	set extSubStr $snipStr$subStr
	while {[font measure $font -displayof $win $extSubStr] > $pixels} {
	    if {$len > 0} {
		set subStr [string range $subStr 1 end]
		incr len -1
		set extSubStr $snipStr$subStr
	    } else {
		set extSubStr [string range $extSubStr 1 end]
	    }
	}
    } else {
	set last [expr {$len - 1}]
	set extSubStr $subStr$snipStr
	while {[font measure $font -displayof $win $extSubStr] > $pixels} {
	    if {$last >= 0} {
		incr last -1
		set subStr [string range $subStr 0 $last]
		set extSubStr $subStr$snipStr
	    } else {
		set extSubStr [string range $extSubStr 1 end]
	    }
	}
    }

    return $extSubStr
}

#------------------------------------------------------------------------------
# tablelist::adjustItem
#
# Returns the list obtained by adjusting the list specified by item to the
# length expLen.
#------------------------------------------------------------------------------
proc tablelist::adjustItem {item expLen} {
    set len [llength $item]
    if {$len == $expLen} {
	return $item
    } elseif {$len < $expLen} {
	for {set n $len} {$n < $expLen} {incr n} {
	    lappend item ""
	}
	return $item
    } else {
	return [lrange $item 0 [expr {$expLen - 1}]]
    }
}

#------------------------------------------------------------------------------
# tablelist::adjustElem
#
# Prepares the text specified by $textName and the image width specified by
# $imageWidthName for insertion into a cell of the tablelist widget win.
#------------------------------------------------------------------------------
proc tablelist::adjustElem {win textName imageWidthName font
			    pixels alignment snipStr} {
    upvar $textName text $imageWidthName imageWidth

    if {$pixels == 0} {				;# convention: dynamic width
	if {$imageWidth != 0 && [string compare $text ""] != 0} {
	    if {[string compare $alignment right] == 0} {
		set text "$text "
	    } else {
		set text " $text"
	    }
	}
    } elseif {$imageWidth == 0} {		;# no image
	set text [strRangeExt $win $text $font $pixels $alignment $snipStr]
    } elseif {[string compare $text ""] == 0} {	;# image w/o text
	if {$imageWidth > $pixels} {
	    set imageWidth 0			;# can't display the image
	}
    } else {					;# both image and text
	set gap [font measure $font -displayof $win " "]
	if {$imageWidth + $gap <= $pixels} {
	    incr pixels -[expr {$imageWidth + $gap}]
	    set text [strRangeExt $win $text $font $pixels $alignment $snipStr]
	    if {[string compare $alignment right] == 0} {
		set text "$text "
	    } else {
		set text " $text"
	    }
	} elseif {$imageWidth <= $pixels} {
	    set text ""				;# can't display the text
	} else {
	    set imageWidth 0			;# can't display the image
	    set text ""				;# can't display the text
	}
    }
}

#------------------------------------------------------------------------------
# tablelist::insertElem
#
# Inserts the given text and image into the text widget w, just before the
# character position specified by index.  The image will follow the text if
# alignment is "right", and will precede it otherwise.
#------------------------------------------------------------------------------
proc tablelist::insertElem {w index text image imageWidth alignment} {
    set index [$w index $index]

    if {$imageWidth == 0} {
	$w insert $index $text
    } elseif {[string compare $alignment right] == 0} {
	$w image create $index -image $image
	$w insert $index $text
    } else {
	$w insert $index $text
	$w image create $index -image $image
    }
}

#------------------------------------------------------------------------------
# tablelist::isCharVisible
#
# Checks whether the given text widget character is visible.  It is assumed
# that the line containing the character is visible.
#------------------------------------------------------------------------------
proc tablelist::isCharVisible {w textIdx} {
    set lineEnd [$w index "$textIdx lineend"]
    if {[string compare $textIdx $lineEnd] == 0} {
	return [expr {[lindex [$w xview] 1] == 1.0}]
    } else {
	return [expr {[string compare [$w bbox $textIdx] ""] != 0}]
    }
}

#------------------------------------------------------------------------------
# tablelist::makeColFontAndTagLists
#
# Builds the lists data(colFontList) of the column fonts and data(colTagsList)
# of the column tag names.
#------------------------------------------------------------------------------
proc tablelist::makeColFontAndTagLists win {
    upvar ::tablelist::ns${win}::data data

    set widgetFont $data(-font)
    set data(colFontList) {}
    set data(colTagsList) {}
    set data(hasColTags) 0

    for {set col 0} {$col < $data(colCount)} {incr col} {
	set tagNames {}

	if {[info exists data($col-font)]} {
	    lappend data(colFontList) $data($col-font)
	    lappend tagNames $col-font
	    set data(hasColTags) 1
	} else {
	    lappend data(colFontList) $widgetFont
	}

	foreach opt {-background -foreground} {
	    if {[info exists data($col$opt)]} {
		lappend tagNames $col$opt
		set data(hasColTags) 1
	    }
	}

	lappend data(colTagsList) $tagNames
    }
}

#------------------------------------------------------------------------------
# tablelist::setupColumns
#
# Updates the value of the -colums configuration option for the tablelist
# widget win by using the width, title, and alignment specifications given in
# the columns argument, and creates the corresponding label (and separator)
# widgets if createLabels is true.
#------------------------------------------------------------------------------
proc tablelist::setupColumns {win columns createLabels} {
    variable configSpecs
    variable configOpts
    variable alignments
    upvar ::tablelist::ns${win}::data data

    set argCount [llength $columns]
    set colConfigVals {}

    #
    # Check the syntax of columns before performing any changes
    #
    for {set n 0} {$n < $argCount} {incr n} {
	#
	# Get the column width
	#
	set width [lindex $columns $n]
	set width [format %d $width]	;# integer check with error message

	#
	# Get the column title
	#
	if {[incr n] == $argCount} {
	    return -code error "column title missing"
	}
	set title [lindex $columns $n]

	#
	# Get the column alignment
	#
	set alignment left
	if {[incr n] < $argCount} {
	    set next [lindex $columns $n]
	    if {[catch {format %d $next}] == 0} {	;# integer check
		incr n -1
	    } else {
		set alignment [mwutil::fullOpt "alignment" $next $alignments]
	    }
	}

	#
	# Append the properly formatted values of width,
	# title, and alignment to the list colConfigVals
	#
	lappend colConfigVals $width $title $alignment
    }

    #
    # Save the value of colConfigVals in data(-columns)
    #
    set data(-columns) $colConfigVals

    #
    # Delete the labels and separators if requested
    #
    if {$createLabels} {
	set children [winfo children $data(hdrTxtFr)]
	foreach w [lrange [lsort $children] 1 end] {
	    destroy $w
	}
	foreach w [winfo children $win] {
	    if {[regexp {^sep[0-9]+$} [winfo name $w]]} {
		destroy $w
	    }
	}
	set data(fmtCmdFlagList) {}
    }

    #
    # Build the list data(colList), and create the labels if requested
    #
    set widgetFont $data(-font)
    set data(colList) {}
    set col 0
    foreach {width title alignment} $data(-columns) {
	#
	# Append the width in pixels and the
	# alignment to the list data(colList)
	#
	if {$width > 0} {		;# convention: width in characters
	    ### set str [string repeat 0 $count]
	    set str ""
	    for {set n 0} {$n < $width} {incr n} {
		append str 0
	    }
	    set pixels [font measure $widgetFont -displayof $win $str]
	} elseif {$width < 0} {		;# convention: width in pixels
	    set pixels [expr {(-1)*$width}]
	} else {			;# convention: dynamic width
	    set pixels 0
	}
	lappend data(colList) $pixels $alignment

	if {$createLabels} {
	    if {![info exists data($col-delta)]} {
		set data($col-delta) 0
	    }
	    if {![info exists data($col-editable)]} {
		set data($col-editable) 0
	    }
	    if {![info exists data($col-hide)]} {
		set data($col-hide) 0
	    }
	    if {![info exists data($col-resizable)]} {
		set data($col-resizable) 1
	    }
	    if {![info exists data($col-showarrow)]} {
		set data($col-showarrow) 1
	    }
	    if {![info exists data($col-sortmode)]} {
		set data($col-sortmode) ascii
	    }
	    lappend data(fmtCmdFlagList) [info exists data($col-formatcommand)]

	    #
	    # Create the label
	    #
	    set w $data(hdrTxtFrLbl)$col
	    label $w -bitmap "" -highlightthickness 0 -image "" -takefocus 0 \
		     -text "" -textvariable "" -underline -1 -wraplength 0

	    #
	    # Apply to it the current configuration options
	    #
	    foreach opt $configOpts {
		set optGrp [lindex $configSpecs($opt) 2]
		if {[string compare $optGrp l] == 0} {
		    set optTail [string range $opt 6 end]
		    if {[info exists data($col$opt)]} {
			$w configure -$optTail $data($col$opt)
		    } else {
			$w configure -$optTail $data($opt)
		    }
		} elseif {[string compare $optGrp c] == 0} {
		    $w configure $opt $data($opt)
		}
	    }
	    catch {$w configure -state $data(-state)}

	    #
	    # Replace the binding tag Label with TablelistLabel
	    # in the list of binding tags of the label
	    #
	    bindtags $w [lreplace [bindtags $w] 1 1 TablelistLabel]

	    if {[info exists data($col-labelimage)]} {
		doColConfig $col $win -labelimage $data($col-labelimage)
	    }
	}

	#
	# Configure the entry widget if present
	#
	if {$col == $data(editCol)} {
	    $data(bodyFrEnt) configure -justify $alignment
	}

	incr col
    }

    #
    # Save the number of columns in data(colCount)
    #
    set oldColCount $data(colCount)
    set data(colCount) $col
    set data(lastCol) [expr {$col - 1}]

    #
    # Clean up the data associated with the deleted columns
    #
    for {set col $data(colCount)} {$col < $oldColCount} {incr col} {
	deleteColData $win $col
    }

    #
    # Create the separators if needed
    #
    if {$createLabels && $data(-showseparators)} {
	createSeps $win
    }
}

#------------------------------------------------------------------------------
# tablelist::createSeps
#
# Creates and manages the separator frames in the tablelist widget win.
#------------------------------------------------------------------------------
proc tablelist::createSeps win {
    upvar ::tablelist::ns${win}::data data

    for {set col 0} {$col < $data(colCount)} {incr col} {
	#
	# Create the col'th separator frame and attach it
	# to the right edge of the col'th header label
	#
	set w $data(sep)$col
	frame $w -background $data(-background) -borderwidth 1 -container 0 \
		 -highlightthickness 0 -relief raised -takefocus 0 -width 2
	place $w -in $data(hdrTxtFrLbl)$col -anchor ne -bordermode outside \
		 -relx 1.0

	#
	# Replace the binding tag Frame with TablelistBody
	# in the list of binding tags of the separator frame
	#
	bindtags $w [lreplace [bindtags $w] 1 1 TablelistBody]
    }
    
    adjustSepsWhenIdle $win
}

#------------------------------------------------------------------------------
# tablelist::adjustSepsWhenIdle
#
# Arranges for the height and vertical position of each separator frame in the
# tablelist widget win to be adjusted at idle time.
#------------------------------------------------------------------------------
proc tablelist::adjustSepsWhenIdle win {
    upvar ::tablelist::ns${win}::data data

    if {[info exists data(sepsId)]} {
	return ""
    }

    set data(sepsId) [after idle [list tablelist::adjustSeps $win]]
}

#------------------------------------------------------------------------------
# tablelist::adjustSeps
#
# Adjusts the height and vertical position of each separator frame in the
# tablelist widget win.
#------------------------------------------------------------------------------
proc tablelist::adjustSeps win {
    upvar ::tablelist::ns${win}::data data

    if {[info exists data(sepsId)]} {
	after cancel $data(sepsId)
	unset data(sepsId)
    }

    #
    # Get the height to be applied to the separator frames
    #
    set w $data(body)
    set textIdx [$w index @0,[winfo height $w]]
    set dlineinfo [$w dlineinfo $textIdx]
    if {$data(itemCount) == 0 || [string compare $dlineinfo ""] == 0} {
	set sepHeight 1
    } else {
	foreach {x y width height baselinePos} $dlineinfo {
	    set sepHeight [expr {$y + $height}]
	}
    }

    #
    # Set the height and vertical position of each separator frame
    #
    foreach w [winfo children $win] {
	if {[regexp {^sep[0-9]+$} [winfo name $w]]} {
	    $w configure -height $sepHeight
	    if {$data(-showlabels)} {
		place configure $w -rely 1.0 -y 0
	    } else {
		place configure $w -rely 0.0 -y 1
	    }
	}
    }
}

#------------------------------------------------------------------------------
# tablelist::adjustColumns
#
# Applies some configuration options to the labels of the tablelist widget win,
# places them in the header frame, computes and sets the tab stops for the body
# text widget, and adjusts the width and height of the header frame.  The
# whichWidths argument specifies the dynamic-width columns or labels whose
# widths are to be computed when performing these operations.  The stretchCols
# argument specifies whether to stretch the stretchable columns.
#------------------------------------------------------------------------------
proc tablelist::adjustColumns {win whichWidths stretchCols} {
    upvar ::tablelist::ns${win}::data data

    set compAllColWidths [expr {[string compare $whichWidths allCols] == 0}]
    set compAllLabelWidths [expr {[string compare $whichWidths allLabels] == 0}]

    #
    # Configure the labels, place them in the header frame, and compute
    # the positions of the tab stops to be set in the body text widget
    #
    set data(hdrPixels) 0
    set tabs {}
    set col 0
    set x 0
    foreach {pixels alignment} $data(colList) {
	set w $data(hdrTxtFrLbl)$col
	if {$data($col-hide)} {
	    place forget $w
	    incr col
	    continue
	}

	#
	# Adjust the col'th label
	#
	if {$pixels != 0} {
	    incr pixels $data($col-delta)
	}
	if {[info exists data($col-labelalign)]} {
	    set labelAlignment $data($col-labelalign)
	} else {
	    set labelAlignment $alignment
	}
	adjustLabel $win $col $pixels $labelAlignment

	if {$pixels == 0} {			;# convention: dynamic width
	    #
	    # Compute the column or label width if requested
	    #
	    if {$compAllColWidths} {
		computeColWidth $win $col
	    } elseif {$compAllLabelWidths} {
		computeLabelWidth $win $col
	    } elseif {[lsearch -exact $whichWidths $col] >= 0} {
		computeColWidth $win $col
	    } elseif {[lsearch -exact $whichWidths l$col] >= 0} {
		computeLabelWidth $win $col
	    }

	    set pixels $data($col-width)
	    incr pixels $data($col-delta)
	}

	if {$col == $data(editCol)} {
	    #
	    # Adjust the width of the frame containing the entry widget
	    #
	    $data(bodyFr) configure -width [expr {$pixels + 6}]
	}

	if {$col == $data(arrowCol)} {
	    #
	    # Place the canvas to the left side of the label if the
	    # latter is right-justified and to its right side otherwise
	    #
	    set canvas $data(hdrTxtFrCanv)
	    if {[string compare $labelAlignment right] == 0} {
		place $canvas -in $w -anchor w -bordermode outside \
			      -relx 0.0 -x $data(charWidth) -rely 0.5
	    } else {
		place $canvas -in $w -anchor e -bordermode outside \
			      -relx 1.0 -x -$data(charWidth) -rely 0.5
	    }
	    raise $canvas
	}

	#
	# Place the label in the header frame
	#
	set labelPixels [expr {$pixels + 2*$data(charWidth)}]
	place $w -x $x -relheight 1.0 -width $labelPixels
	incr x $labelPixels

	#
	# Append a tab stop and the alignment to the tabs list
	#
	incr data(hdrPixels) $data(charWidth)
	switch $alignment {
	    left {
		lappend tabs $data(hdrPixels) left
		incr data(hdrPixels) $pixels
	    }
	    right {
		incr data(hdrPixels) $pixels
		lappend tabs $data(hdrPixels) right
	    }
	    center {
		lappend tabs [expr {$data(hdrPixels) + $pixels/2}] center
		incr data(hdrPixels) $pixels
	    }
	}
	incr data(hdrPixels) $data(charWidth)
	lappend tabs $data(hdrPixels) left

	incr col
    }
    place $data(hdrLbl) -x $data(hdrPixels)

    #
    # Apply the value of tabs to the body text widget
    #
    $data(body) configure -tabs $tabs

    #
    # Adjust the width and height of the frames data(hdrTxtFr) and data(hdr)
    #
    $data(hdrTxtFr) configure -width $data(hdrPixels)
    if {$data(-width) <= 0} {
	if {$stretchCols} {
	    $data(hdr) configure -width $data(hdrPixels)
	}
    } else {
	$data(hdr) configure -width 0
    }
    adjustHeaderHeight $win

    #
    # Stretch the stretchable columns if requested
    #
    if {$stretchCols} {
	stretchColumnsWhenIdle $win
    }
}

#------------------------------------------------------------------------------
# tablelist::adjustLabel
#
# Applies some configuration options to the col'th label of the tablelist
# widget win as well as to the label's children (if any), and places the
# children.
#------------------------------------------------------------------------------
proc tablelist::adjustLabel {win col pixels alignment} {
    upvar ::tablelist::ns${win}::data data

    #
    # Apply some configuration options to the label and its children (if any)
    #
    set w $data(hdrTxtFrLbl)$col
    switch $alignment {
	left	{ set anchor w }
	right	{ set anchor e }
	center	{ set anchor center }
    }
    set padX [expr {$data(charWidth) - [$w cget -borderwidth]}]
    $w configure -anchor $anchor -justify $alignment -padx $padX
    if {[info exists data($col-labelimage)]} {
	set imageWidth [image width $data($col-labelimage)]
	if {[string compare $alignment right] == 0} {
	    $w.il configure -anchor e -width 0
	} else {
	    $w.il configure -anchor w -width 0
	}
	$w.tl configure -anchor $anchor -justify $alignment
    } else {
	set imageWidth 0
    }

    #
    # Make room for the canvas displaying an an up- or down-arrow if needed
    #
    set title [lindex $data(-columns) [expr {3*$col + 1}]]
    set labelFont [$w cget -font]
    if {$col == $data(arrowCol)} {
	if {[font metrics $labelFont -displayof $w -fixed]} {
	    set spaces "   "				;# 3 spaces
	} else {
	    set spaces "     "				;# 5 spaces
	}
    } else {
	set spaces ""
    }
    set spacePixels [font measure $labelFont -displayof $w $spaces]

    if {$pixels == 0} {				;# convention: dynamic width
	#
	# Set the label text
	#
	if {$imageWidth == 0} {				;# no image
	    if {[string compare $title ""] == 0} {
		set text $spaces
	    } else {
		set lines {}
		foreach line [split $title \n] {
		    if {[string compare $alignment right] == 0} {
			lappend lines $spaces$line
		    } else {
			lappend lines $line$spaces
		    }
		}
		set text [join $lines \n]
	    }
	    $w configure -text $text
	} elseif {[string compare $title ""] == 0} {	;# image w/o text
	    $w configure -text ""
	    set text ""
	    $w.il configure -width [expr {$imageWidth + $spacePixels}]
	} else {					;# both image and text
	    $w configure -text ""
	    set lines {}
	    foreach line [split $title \n] {
		if {[string compare $alignment right] == 0} {
		    lappend lines $spaces$line
		} else {
		    lappend lines $line$spaces
		}
	    }
	    set text [join $lines \n]
	    $w.tl configure -text $text
	    set colFont [lindex $data(colFontList) $col]
	    set gap [font measure $colFont -displayof $win " "]
	    $w.il configure -width [expr {$imageWidth + $gap}]
	}
    } else {
	#
	# Clip each line of title according to pixels and alignment
	#
	set lessPixels [expr {$pixels - $spacePixels}]
	if {$imageWidth == 0} {				;# no image
	    if {[string compare $title ""] == 0} {
		set text $spaces
	    } else {
		set lines {}
		foreach line [split $title \n] {
		    set line [strRangeExt $win $line $labelFont \
			      $lessPixels $alignment $data(-snipstring)]
		    if {[string compare $alignment right] == 0} {
			lappend lines $spaces$line
		    } else {
			lappend lines $line$spaces
		    }
		}
		set text [join $lines \n]
	    }
	    $w configure -text $text
	} elseif {[string compare $title ""] == 0} {	;# image w/o text
	    $w configure -text ""
	    set text ""
	    if {$imageWidth <= $lessPixels} {
		$w.il configure -width [expr {$imageWidth + $spacePixels}]
	    } else {
		set imageWidth 0		;# can't display the image
	    }
	} else {					;# both image and text
	    $w configure -text ""
	    set colFont [lindex $data(colFontList) $col]
	    set gap [font measure $colFont -displayof $win " "]
	    if {$imageWidth + $gap <= $lessPixels} {
		incr lessPixels -[expr {$imageWidth + $gap}]
		set lines {}
		foreach line [split $title \n] {
		    set line [strRangeExt $win $line $labelFont \
			      $lessPixels $alignment $data(-snipstring)]
		    if {[string compare $alignment right] == 0} {
			lappend lines $spaces$line
		    } else {
			lappend lines $line$spaces
		    }
		}
		set text [join $lines \n]
		$w.tl configure -text $text
		$w.il configure -width [expr {$imageWidth + $gap}]
	    } elseif {$imageWidth <= $lessPixels} {	
		set text ""			;# can't display the text
		$w.il configure -width [expr {$imageWidth + $spacePixels}]
	    } else {
		set imageWidth 0		;# can't display the image
		set text ""			;# can't display the text
	    }
	}
    }

    #
    # Place the label's children (if any)
    #
    if {$imageWidth == 0} {
	if {[info exists data($col-labelimage)]} {
	    place forget $w.il
	    place forget $w.tl
	}
    } else {
	if {[string compare $text ""] == 0} {
	    place forget $w.tl
	}

	switch $alignment {
	    left {
		place $w.il -anchor nw -relx 0.0 -x $padX -relheight 1.0
		if {[string compare $text ""] != 0} {
		    set textX [expr {$padX + [winfo reqwidth $w.il]}]
		    place $w.tl -anchor nw -relx 0.0 -x $textX -relheight 1.0
		}
	    }

	    right {
		place $w.il -anchor ne -relx 1.0 -x -$padX -relheight 1.0
		if {[string compare $text ""] != 0} {
		    set textX [expr {-$padX - [winfo reqwidth $w.il]}]
		    place $w.tl -anchor ne -relx 1.0 -x $textX -relheight 1.0
		}
	    }

	    center {
		if {[string compare $text ""] == 0} {
		    place $w.il -anchor n -relx 0.5 -x 0 -relheight 1.0
		} else {
		    set halfWidth [expr {([winfo reqwidth $w.il] + \
					  [winfo reqwidth $w.tl]) / 2}]
		    place $w.il -anchor nw -relx 0.5 -x -$halfWidth \
				-relheight 1.0
		    place $w.tl -anchor ne -relx 0.5 -x $halfWidth \
				-relheight 1.0
		}
	    }
	}
    }
}

#------------------------------------------------------------------------------
# tablelist::computeColWidth
#
# Computes the width of the col'th column of the tablelist widget win to be just
# large enough to hold all the elements of the column (including its label).
#------------------------------------------------------------------------------
proc tablelist::computeColWidth {win col} {
    upvar ::tablelist::ns${win}::data data

    set fmtCmdFlag [lindex $data(fmtCmdFlagList) $col]
    set colFont [lindex $data(colFontList) $col]

    set data($col-elemWidth) 0
    set data($col-widestCount) 0

    #
    # Column elements
    #
    foreach item $data(itemList) {
	if {$col >= [llength $item] - 1} {
	    continue
	}

	set text [lindex $item $col]
	if {$fmtCmdFlag} {
	    set text [uplevel #0 $data($col-formatcommand) [list $text]]
	}
	set text [strToDispStr $text]
	set key [lindex $item end]
	if {[info exists data($key-$col-image)]} {
	    set imageWidth [image width $data($key-$col-image)]
	} else {
	    set imageWidth 0
	}
	if {[info exists data($key-$col-font)]} {
	    set cellFont $data($key-$col-font)
	} elseif {[info exists data($key-font)]} {
	    set cellFont $data($key-font)
	} else {
	    set cellFont $colFont
	}
	adjustElem $win text imageWidth $cellFont 0 left ""
	set textWidth [font measure $cellFont -displayof $win $text]
	set elemWidth [expr {$imageWidth + $textWidth}]
	if {$elemWidth == $data($col-elemWidth)} {
	    incr data($col-widestCount)
	} elseif {$elemWidth > $data($col-elemWidth)} {
	    set data($col-elemWidth) $elemWidth
	    set data($col-widestCount) 1
	}
    }
    set data($col-width) $data($col-elemWidth)

    #
    # Column label
    #
    computeLabelWidth $win $col
}

#------------------------------------------------------------------------------
# tablelist::computeLabelWidth
#
# Computes the width of the col'th label of the tablelist widget win and
# adjusts the column's width accordingly.
#------------------------------------------------------------------------------
proc tablelist::computeLabelWidth {win col} {
    upvar ::tablelist::ns${win}::data data

    set w $data(hdrTxtFrLbl)$col
    if {[info exists data($col-labelimage)]} {
	set title [lindex $data(-columns) [expr {3*$col + 1}]]
	if {[string compare $title ""] == 0} {		;# image w/o text
	    set netLabelWidth [winfo reqwidth $w.il]
	} else {					;# both image and text
	    set netLabelWidth [expr {[winfo reqwidth $w.il] +
				     [winfo reqwidth $w.tl]}]
	}
    } else {						;# no image
	set netLabelWidth [expr {[winfo reqwidth $w] - 2*$data(charWidth)}]
    }

    if {$netLabelWidth < $data($col-elemWidth)} {
	set data($col-width) $data($col-elemWidth)
    } else {
	set data($col-width) $netLabelWidth
    }
}

#------------------------------------------------------------------------------
# tablelist::adjustHeaderHeight
#
# Sets the height of the header frame of the tablelist widget win to the max.
# height of its children.
#------------------------------------------------------------------------------
proc tablelist::adjustHeaderHeight win {
    upvar ::tablelist::ns${win}::data data

    #
    # Compute the max. label height
    #
    set maxLabelHeight [winfo reqheight $data(hdrLbl)]
    set children [winfo children $data(hdrTxtFr)]
    foreach w [lrange [lsort $children] 1 end] {
	if {[string compare [winfo manager $w] ""] == 0} {
	    continue
	}

	set reqHeight [winfo reqheight $w]
	if {$reqHeight > $maxLabelHeight} {
	    set maxLabelHeight $reqHeight
	}

	foreach c [winfo children $w] {
	    if {[string compare [winfo manager $c] ""] == 0} {
		continue
	    }

	    set reqHeight \
		[expr {[winfo reqheight $c] + 2*[$w cget -borderwidth]}]
	    if {$reqHeight > $maxLabelHeight} {
		set maxLabelHeight $reqHeight
	    }
	}
    }

    #
    # Set the height of the header frame and adjust the separators
    #
    $data(hdrTxtFr) configure -height $maxLabelHeight
    if {$data(-showlabels)} {
	$data(hdr) configure -height $maxLabelHeight
    } else {
	$data(hdr) configure -height 1
    }
    adjustSepsWhenIdle $win
}

#------------------------------------------------------------------------------
# tablelist::stretchColumnsWhenIdle
#
# Arranges for the stretchable columns of the tablelist widget win to be
# stretched at idle time.
#------------------------------------------------------------------------------
proc tablelist::stretchColumnsWhenIdle win {
    upvar ::tablelist::ns${win}::data data

    if {[info exists data(stretchId)]} {
	return ""
    }

    set data(stretchId) [after idle [list tablelist::stretchColumns $win -1]]
}

#------------------------------------------------------------------------------
# tablelist::stretchColumns
#
# Stretches the stretchable columns to fill the tablelist window win
# horizontally.  The colOfFixedDelta argument specifies the column for which
# the stretching is to be made using a precomputed amount of pixels.
#------------------------------------------------------------------------------
proc tablelist::stretchColumns {win colOfFixedDelta} {
    upvar ::tablelist::ns${win}::data data

    if {[info exists data(stretchId)]} {
	after cancel data(stretchId)
	unset data(stretchId)
    }

    set forceAdjust $data(forceAdjust)
    set data(forceAdjust) 0

    if {$data(hdrPixels) == 0 || $data(-width) <= 0} {
	return ""
    }

    #
    # Get the list data(stretchableCols) of the
    # numerical indices of the stretchable columns
    #
    set data(stretchableCols) {}
    if {[string first $data(-stretch) all] == 0} {
	for {set col 0} {$col < $data(colCount)} {incr col} {
	    lappend data(stretchableCols) $col
	}
    } else {
	foreach col $data(-stretch) {
	    lappend data(stretchableCols) [colIndex $win $col 0]
	}
    }

    #
    # Compute the total number data(delta) of pixels by which the
    # columns are to be stretched and the total amount
    # data(stretchablePxls) of stretchable column widths in pixels
    #
    set data(delta) [winfo width $data(hdr)]
    set data(stretchablePxls) 0
    set lastColToStretch -1
    set col 0
    foreach {pixels alignment} $data(colList) {
	if {$data($col-hide)} {
	    incr col
	    continue
	}

	if {$pixels == 0} {			;# convention: dynamic width
	    set pixels $data($col-width)
	}
	incr data(delta) -[expr {$pixels + 2*$data(charWidth)}]
	if {[lsearch -exact $data(stretchableCols) $col] >= 0} {
	    incr data(stretchablePxls) $pixels
	    set lastColToStretch $col
	}

	incr col
    }
    if {$data(delta) < 0} {
	set delta 0
    } else {
	set delta $data(delta)
    }
    if {$data(stretchablePxls) == 0 && !$forceAdjust} {
	return ""
    }

    #
    # Distribute the value of delta to the stretchable
    # columns, proportionally to their widths in pixels
    #
    set rest $delta
    set col 0
    foreach {pixels alignment} $data(colList) {
	if {$data($col-hide) ||
	    [lsearch -exact $data(stretchableCols) $col] < 0} {
	    set data($col-delta) 0
	} else {
	    set oldDelta $data($col-delta)
	    if {$pixels == 0} {			;# convention: dynamic width
		set dynamic 1
		set pixels $data($col-width)
	    } else {
		set dynamic 0
	    }
	    if {$data(stretchablePxls) == 0} {
		set data($col-delta) 0
	    } else {
		if {$col != $colOfFixedDelta} {
		    set data($col-delta) \
			[expr {$delta*$pixels/$data(stretchablePxls)}]
		}
		incr rest -$data($col-delta)
	    }
	    if {$col == $lastColToStretch} {
		incr data($col-delta) $rest
	    }
	    if {!$dynamic && $data($col-delta) != $oldDelta} {
		redisplayWhenIdle $win
	    }
	}

	incr col
    }

    #
    # Adjust the columns
    #
    adjustColumns $win {} 0
    update idletasks
}

#------------------------------------------------------------------------------
# tablelist::redisplayWhenIdle
#
# Arranges for the items of the tablelist widget win to be redisplayed at idle
# time.
#------------------------------------------------------------------------------
proc tablelist::redisplayWhenIdle win {
    upvar ::tablelist::ns${win}::data data

    if {[info exists data(redispId)] || $data(itemCount) == 0} {
	return ""
    }

    set data(redispId) [after idle [list tablelist::redisplay $win]]
}

#------------------------------------------------------------------------------
# tablelist::redisplay
#
# Redisplays the items of the tablelist widget win.
#------------------------------------------------------------------------------
proc tablelist::redisplay win {
    upvar ::tablelist::ns${win}::data data

    if {[info exists data(redispId)]} {
	after cancel $data(redispId)
	unset data(redispId)
    }

    #
    # Save some data of the entry widget if present
    #
    if {[set editCol $data(editCol)] >= 0} {
	set editRow $data(editRow)
	saveEntryData $win
    }

    set w $data(body)
    set widgetFont $data(-font)
    set snipStr $data(-snipstring)
    set isSimple [expr {$data(tagCount) == 0 && $data(imgCount) == 0 &&
			!$data(hasColTags)}]
    set newItemList {}
    set idx 0
    set line 1
    foreach item $data(itemList) {
	#
	# Check whether the line is selected
	#
	set tagNames [$w tag names $line.0]
	if {[lsearch -exact $tagNames select] >= 0} {
	    set selected 1
	} else {
	    set selected 0
	}

	#
	# Empty the line, clip the elements if necessary,
	# and insert them with the corresponding tags
	#
	$w delete $line.0 $line.end
	set keyIdx [expr {[llength $item] - 1}]
	set key [lindex $item end]
	set newItem {}
	set col 0
	if {$isSimple} {
	    set insertStr ""
	    foreach fmtCmdFlag $data(fmtCmdFlagList) \
		    {pixels alignment} $data(colList) {
		if {$col < $keyIdx} {
		    set text [lindex $item $col]
		} else {
		    set text ""
		}
		lappend newItem $text

		if {$data($col-hide)} {
		    incr col
		    continue
		}

		#
		# Clip the element if necessary
		#
		if {$fmtCmdFlag} {
		    set text [uplevel #0 $data($col-formatcommand) [list $text]]
		}
		set text [strToDispStr $text]
		if {$pixels != 0} {		;# convention: static width
		    incr pixels $data($col-delta)
		    set text [strRangeExt $win $text $widgetFont \
			      $pixels $alignment $snipStr]
		}

		append insertStr \t$text\t
		incr col
	    }

	    #
	    # Insert the item into the body text widget
	    #
	    $w insert $line.0 $insertStr

	}  else {
	    array set tagData [array get data $key*-\[bf\]*]	;# for speed

	    set rowTags [array names tagData $key-\[bf\]*]
	    foreach colFont $data(colFontList) \
		    colTags $data(colTagsList) \
		    fmtCmdFlag $data(fmtCmdFlagList) \
		    {pixels alignment} $data(colList) {
		if {$col < $keyIdx} {
		    set text [lindex $item $col]
		} else {
		    set text ""
		}
		lappend newItem $text

		if {$data($col-hide)} {
		    incr col
		    continue
		}

		#
		# Adjust the cell text and the image width
		#
		if {$fmtCmdFlag} {
		    set text [uplevel #0 $data($col-formatcommand) [list $text]]
		}
		set text [strToDispStr $text]
		if {[info exists data($key-$col-image)]} {
		    set image $data($key-$col-image)
		    set imageWidth [image width $image]
		} else {
		    set image ""
		    set imageWidth 0
		}
		if {[info exists data($key-$col-font)]} {
		    set cellFont $data($key-$col-font)
		} elseif {[info exists data($key-font)]} {
		    set cellFont $data($key-font)
		} else {
		    set cellFont $colFont
		}
		if {$pixels != 0} {		;# convention: static width
		    incr pixels $data($col-delta)
		}
		adjustElem $win text imageWidth $cellFont \
			   $pixels $alignment $snipStr

		#
		# Insert the text and the image
		#
		set cellTags [array names tagData $key-$col-\[bf\]*]
		set tagNames [concat $colTags $rowTags $cellTags]
		if {$imageWidth == 0} {
		    $w insert $line.end \t$text\t $tagNames
		} else {
		    $w insert $line.end \t\t $tagNames
		    insertElem $w $line.end-1c $text $image $imageWidth \
			       $alignment
		}

		incr col
	    }

	    unset tagData
	}
	lappend newItem $key
	lappend newItemList $newItem

	#
	# Select the item if it was selected before
	#
	if {$selected} {
	    selectionSubCmd $win set $idx $idx
	}

	incr idx
	incr line
    }

    set data(itemList) $newItemList

    #
    # Restore the stripes in the body text widget
    #
    makeStripes $win

    #
    # Restore the entry widget if it was present before
    #
    if {$editCol >= 0} {
	editcellSubCmd $win $editRow $editCol 1
    }
}

#------------------------------------------------------------------------------
# tablelist::makeStripesWhenIdle
#
# Arranges for the stripes in the body of the tablelist widget win to be
# redrawn at idle time.
#------------------------------------------------------------------------------
proc tablelist::makeStripesWhenIdle win {
    upvar ::tablelist::ns${win}::data data

    if {[info exists data(stripesId)] || $data(itemCount) == 0} {
	return ""
    }

    set data(stripesId) [after idle [list tablelist::makeStripes $win]]
}

#------------------------------------------------------------------------------
# tablelist::makeStripes
#
# Redraws the stripes in the body of the tablelist widget win.
#------------------------------------------------------------------------------
proc tablelist::makeStripes win {
    upvar ::tablelist::ns${win}::data data

    if {[info exists data(stripesId)]} {
	after cancel $data(stripesId)
	unset data(stripesId)
    }

    set w $data(body)
    $w tag remove stripe 1.0 end
    if {[string compare $data(-stripebackground) ""] == 0 &&
	[string compare $data(-stripeforeground) ""] == 0} {
	return ""
    }

    set step [expr {2*$data(-stripeheight)}]
    for {set n [expr {$data(-stripeheight) + 1}]} {$n <= $step} {incr n} {
	for {set line $n} {$line <= $data(itemCount)} {incr line $step} {
	    $w tag add stripe $line.0 $line.end
	}
    }
}

#------------------------------------------------------------------------------
# tablelist::synchronize
#
# This procedure is invoked either as an idle callback after the list variable
# associated with the tablelist widget win was written, or directly, upon
# execution of some widget commands.  It makes sure that the contents of the
# widget is synchronized with the value of the list variable.
#------------------------------------------------------------------------------
proc tablelist::synchronize win {
    upvar ::tablelist::ns${win}::data data

    #
    # Nothing to do if the list variable was not written
    #
    if {![info exists data(syncId)]} {
	return ""
    }

    #
    # Here we are in the case that the procedure was scheduled for
    # execution at idle time.  However, it might have been invoked
    # directly, before the idle time occured; in this case we should
    # cancel the execution of the previously scheduled idle callback.
    #
    after cancel $data(syncId)	;# no harm if data(syncId) is no longer valid
    unset data(syncId)

    upvar #0 $data(-listvariable) var
    set newCount [llength $var]
    if {$newCount < $data(itemCount)} {
	#
	# Delete the items with indices >= newCount from the widget
	#
	set updateCount $newCount
	deleteRows $win $newCount $data(lastRow) 0
    } elseif {$newCount > $data(itemCount)} {
	#
	# Insert the items of var with indices
	# >= data(itemCount) into the widget
	#
	set updateCount $data(itemCount)
	insertSubCmd $win $data(itemCount) \
		     [lrange $var $data(itemCount) end] 0
    } else {
	set updateCount $newCount
    }

    #
    # Update the first updateCount items of the internal list
    #
    set itemsChanged 0
    for {set idx 0} {$idx < $updateCount} {incr idx} {
	set oldItem [lindex $data(itemList) $idx]
	set newItem [adjustItem [lindex $var $idx] $data(colCount)]
	lappend newItem [lindex $oldItem end]

	if {[string compare $oldItem $newItem] != 0} {
	    set data(itemList) [lreplace $data(itemList) $idx $idx $newItem]
	    set itemsChanged 1
	}
    }

    #
    # If necessary, adjust the columns and make sure
    # that the items will be redisplayed at idle time
    #
    if {$itemsChanged} {
	adjustColumns $win allCols 1
	redisplayWhenIdle $win
    }
}

#------------------------------------------------------------------------------
# tablelist::configLabel
#
# This procedure configures the label widget w according to the options and
# their values given in args.  It is needed for label widgets with children,
# managed by the place geometry manager, because - strangely enough - by just
# configuring the label causes its children to become invisible on Windows (but
# not on UNIX).  The procedure solves this problem by using a trick: after
# configuring the label, it applies a constant configuration value to its
# children, which makes them visible again.
#------------------------------------------------------------------------------
proc tablelist::configLabel {w args} {
    eval [list $w configure] $args

    foreach c [winfo children $w] {
	$c configure -borderwidth 0
    }
}

#------------------------------------------------------------------------------
# tablelist::create3DArrows
#
# Creates the items to be used later for drawing two up- or down-arrows with
# sunken relief and 3-D borders in the canvas w.
#------------------------------------------------------------------------------
proc tablelist::create3DArrows w {
    foreach state {normal disabled} {
	$w create polygon 0 0 0 0 0 0 -tags ${state}Triangle
	$w create line    0 0 0 0     -tags ${state}DarkLine
	$w create line    0 0 0 0     -tags ${state}LightLine
    }
}

#------------------------------------------------------------------------------
# tablelist::configCanvas
#
# Sets the background, width, and height of the canvas displaying an up- or
# down-arrow, fills the two arrows contained in the canvas, and saves its width
# in data(arrowWidth).
#------------------------------------------------------------------------------
proc tablelist::configCanvas win {
    upvar ::tablelist::ns${win}::data data

    set w $data(hdrTxtFrLbl)$data(arrowCol)
    set labelBg [$w cget -background]
    set labelFont [$w cget -font]
    if {[font metrics $labelFont -displayof $w -fixed]} {
	set spaces " "
    } else {
	set spaces "  "
    }

    set size [expr {[font measure $labelFont -displayof $w $spaces] + 2}]
    if {$size % 2 == 0} {
	incr size
    }

    set w $data(hdrTxtFrCanv)
    $w configure -background $labelBg -height $size -width $size
    fillArrow $w normal   $data(-arrowcolor)
    fillArrow $w disabled $data(-arrowdisabledcolor)

    set data(arrowWidth) $size
}

#------------------------------------------------------------------------------
# tablelist::drawArrows
#
# Draws the two arrows contained in the canvas associated with the tablelist
# widget win.
#------------------------------------------------------------------------------
proc tablelist::drawArrows win {
    upvar ::tablelist::ns${win}::data data

    switch $data(-incrarrowtype) {
	up {
	    switch $data(sortOrder) {
		increasing { set arrowType up }
		decreasing { set arrowType down }
	    }
	}

	down {
	    switch $data(sortOrder) {
		increasing { set arrowType down }
		decreasing { set arrowType up }
	    }
	}
    }

    set w $data(hdrTxtFrCanv)
    set maxX [expr {[$w cget -width] - 1}]
    set maxY [expr {[$w cget -height] - 1}]
    set midX [expr {$maxX / 2}]

    switch $arrowType {
	up {
	    foreach state {normal disabled} {
		$w coords ${state}Triangle  0 $maxY $maxX $maxY $midX 0
		$w coords ${state}DarkLine  $midX 0 0 $maxY
		$w coords ${state}LightLine 0 $maxY $maxX $maxY $midX 0
	    }
	}

	down {
	    foreach state {normal disabled} {
		$w coords ${state}Triangle  $maxX 0 0 0 $midX $maxY
		$w coords ${state}DarkLine  $maxX 0 0 0 $midX $maxY
		$w coords ${state}LightLine $midX $maxY $maxX 0
	    }
	}
    }
}

#------------------------------------------------------------------------------
# tablelist::fillArrow
#
# Fills one of the two arrows contained in the canvas w with the given color,
# or with (a slightly darker color than) the background color of the canvas if
# color is an empty string.  Also fills the arrow's borders with the
# corresponding 3-D shadow colors.  The state argument specifies the arrow to
# be processed.  Returns the properly formatted value of color.
#------------------------------------------------------------------------------
proc tablelist::fillArrow {w state color} {
    if {[string compare $color ""] == 0} {
	set origColor $color
	set color [$w cget -background]

	#
	# To get a better contrast, make the color slightly
	# darker by cutting 5% from each of its components
	#
	set maxIntens [lindex [winfo rgb $w white] 0]
	set len [string length [format %x $maxIntens]]
	foreach comp [winfo rgb $w $color] {
	    lappend rgb [expr {95*$comp/100}]
	}
	set color [eval format "#%0${len}x%0${len}x%0${len}x" $rgb]
    }

    getShadows $w $color darkColor lightColor

    $w itemconfigure ${state}Triangle  -fill $color
    $w itemconfigure ${state}DarkLine  -fill $darkColor
    $w itemconfigure ${state}LightLine -fill $lightColor

    if {[info exists origColor]} {
	return $origColor
    } else {
	return [$w itemcget ${state}Triangle -fill]
    }
}

#------------------------------------------------------------------------------
# tablelist::getShadows
#
# Computes the shadow colors for a 3-D border from a given (background) color.
# This is a modified Tcl-counterpart of the function TkpGetShadows() in the
# Tk distribution file unix/tkUnix3d.c.
#------------------------------------------------------------------------------
proc tablelist::getShadows {w color darkColorName lightColorName} {
    upvar $darkColorName darkColor $lightColorName lightColor

    set maxIntens [lindex [winfo rgb $w white] 0]
    set len [string length [format %x $maxIntens]]

    set rgb [winfo rgb $w $color]
    foreach {r g b} $rgb {}

    #
    # Compute the dark shadow color
    #
    if {[string compare $::tk_patchLevel 8.3.1] >= 0 &&
	$r*0.5*$r + $g*1.0*$g + $b*0.28*$b < $maxIntens*0.05*$maxIntens} {
	#
	# The background is already very dark: make the dark
	# color a little lighter than the background by increasing
	# each color component 1/4th of the way to $maxIntens
	#
	foreach comp $rgb {
	    lappend darkRGB [expr {($maxIntens + 3*$comp)/4}]
	}
    } else {
	#
	# Compute the dark color by cutting 45% from
	# each of the background color components.
	#
	foreach comp $rgb {
	    lappend darkRGB [expr {55*$comp/100}]
	}
    }
    set darkColor [eval format "#%0${len}x%0${len}x%0${len}x" $darkRGB]

    #
    # Compute the light shadow color
    #
    if {[string compare $::tk_patchLevel 8.3.1] >= 0 && $g > $maxIntens*0.95} {
	#
	# The background is already very bright: make the
	# light color a little darker than the background
	# by reducing each color component by 10%
	#
	foreach comp $rgb {
	    lappend lightRGB [expr {9*$comp/10}]
	}
    } else {
	#
	# Compute the light color by boosting each background
	# color component by 45% or half-way to white, whichever
	# is greater (the first approach works better for
	# unsaturated colors, the second for saturated ones)
	#
	foreach comp $rgb {
	    set comp1 [expr {145*$comp/100}]
	    if {$comp1 > $maxIntens} {
		set comp1 $maxIntens
	    }
	    set comp2 [expr {($maxIntens + $comp)/2}]
	    lappend lightRGB [expr {($comp1 > $comp2) ? $comp1 : $comp2}]
	}
    }
    set lightColor [eval format "#%0${len}x%0${len}x%0${len}x" $lightRGB]
}

#------------------------------------------------------------------------------
# tablelist::raiseArrow
#
# Raises one of the two arrows contained in the canvas w, according to the
# state argument.
#------------------------------------------------------------------------------
proc tablelist::raiseArrow {w state} {
    $w raise ${state}Triangle
    $w raise ${state}DarkLine
    $w raise ${state}LightLine
}

#------------------------------------------------------------------------------
# tablelist::isCellEditable
#
# Checks whether the given cell of the tablelist widget win is editable.
#------------------------------------------------------------------------------
proc tablelist::isCellEditable {win row col} {
    upvar ::tablelist::ns${win}::data data

    set item [lindex $data(itemList) $row]
    set key [lindex $item end]
    if {[info exists data($key-$col-editable)]} {
	return $data($key-$col-editable)
    } else {
	return $data($col-editable)
    }
}

#==============================================================================
# Contains the implementation of the tablelist widget.
#
# Structure of the module:
#   - Namespace initialization
#   - Public procedure
#   - Private configuration procedures
#   - Private procedures implementing the tablelist widget command
#   - Private callback procedures
#   - Private procedures used in bindings
#   - Private utility procedures
#
# Copyright (c) 2000-2003  Csaba Nemethi (E-mail: csaba.nemethi@t-online.de)
#==============================================================================

#
# Namespace initialization
# ========================
#

namespace eval tablelist {
    #
    # The array configSpecs is used to handle configuration options.  The
    # names of its elements are the configuration options for the Tablelist
    # class.  The value of an array element is either an alias name or a list
    # containing the database name and class as well as an indicator specifying
    # the widget(s) to which the option applies: c stands for all children
    # (text widgets and labels), b for the body text widget, h for the header
    # text widget, l for the labels, f for the frame, and w for the widget
    # itself.
    #
    #	Command-Line Name	 {Database Name		  Database Class      W}
    #	------------------------------------------------------------------------
    #
    variable configSpecs
    array set configSpecs {
	-activestyle		 {activeStyle		  ActiveStyle	      w}
	-arrowcolor		 {arrowColor		  ArrowColor	      w}
	-arrowdisabledcolor	 {arrowDisabledColor	  ArrowDisabledColor  w}
	-background		 {background		  Background	      b}
	-bg			 -background
	-borderwidth		 {borderWidth		  BorderWidth	      f}
	-bd			 -borderwidth
	-columns		 {columns		  Columns	      w}
	-cursor			 {cursor		  Cursor	      c}
	-disabledforeground	 {disabledForeground	  DisabledForeground  w}
	-editendcommand		 {editEndCommand	  EditEndCommand      w}
	-editstartcommand	 {editStartCommand	  EditStartCommand    w}
	-exportselection	 {exportSelection	  ExportSelection     w}
	-font			 {font			  Font		      b}
	-foreground		 {foreground		  Foreground	      b}
	-fg			 -foreground
	-height			 {height		  Height	      w}
	-highlightbackground	 {highlightBackground	  HighlightBackground f}
	-highlightcolor		 {highlightColor	  HighlightColor      f}
	-highlightthickness	 {highlightThickness	  HighlightThickness  f}
	-incrarrowtype		 {incrArrowType		  IncrArrowType	      w}
	-labelbackground	 {labelBackground	  Background	      l}
	-labelbg		 -labelbackground
	-labelborderwidth	 {labelBorderWidth	  BorderWidth	      l}
	-labelbd		 -labelborderwidth
	-labelcommand		 {labelCommand		  LabelCommand	      w}
	-labeldisabledforeground {labelDisabledForeground DisabledForeground  l}
	-labelfont		 {labelFont		  Font		      l}
	-labelforeground	 {labelForeground	  Foreground	      l}
	-labelfg		 -labelforeground
	-labelheight		 {labelHeight		  Height	      l}
	-labelpady		 {labelPadY		  Pad		      l}
	-labelrelief		 {labelRelief		  Relief	      l}
	-listvariable		 {listVariable		  Variable	      w}
	-movablecolumns	 	 {movableColumns	  MovableColumns      w}
	-movecolumncursor	 {moveColumnCursor	  MoveColumnCursor    w}
	-relief			 {relief		  Relief	      f}
	-resizablecolumns	 {resizableColumns	  ResizableColumns    w}
	-resizecursor		 {resizeCursor		  ResizeCursor	      w}
	-selectbackground	 {selectBackground	  Foreground	      w}
	-selectborderwidth	 {selectBorderWidth	  BorderWidth	      w}
	-selectforeground	 {selectForeground	  Background	      w}
	-selectmode		 {selectMode		  SelectMode	      w}
	-setgrid		 {setGrid		  SetGrid	      w}
	-showarrow		 {showArrow		  ShowArrow	      w}
	-showlabels		 {showLabels		  ShowLabels	      w}
	-showseparators		 {showSeparators	  ShowSeparators      w}
	-snipstring		 {snipString		  SnipString	      w}
	-sortcommand		 {sortCommand		  SortCommand	      w}
	-state			 {state			  State		      w}
	-stretch		 {stretch		  Stretch	      w}
	-stripebackground	 {stripeBackground	  Background	      w}
	-stripebg		 -stripebackground
	-stripeforeground	 {stripeForeground	  Foreground	      w}
	-stripefg		 -stripeforeground
	-stripeheight		 {stripeHeight		  StripeHeight	      w}
	-takefocus		 {takeFocus		  TakeFocus	      f}
	-targetcolor		 {targetColor		  TargetColor	      w}
	-width			 {width			  Width		      w}
	-xscrollcommand		 {xScrollCommand	  ScrollCommand	      h}
	-yscrollcommand		 {yScrollCommand	  ScrollCommand	      b}
    }

    #
    # Get the current windowing system ("x11", "win32", "classic", or "aqua")
    #
    variable winSys
    if {[catch {tk windowingsystem} winSys] != 0} {
	switch $::tcl_platform(platform) {
	    unix	{ set winSys x11 }
	    windows	{ set winSys win32 }
	    macintosh	{ set winSys classic }
	}
    }

    #
    # Extend the elements of the array configSpecs
    #
    extendConfigSpecs 

    variable configOpts [lsort [array names configSpecs]]

    #
    # The array colConfigSpecs is used to handle column configuration options.
    # The names of its elements are the column configuration options for the
    # Tablelist widget class.  The value of an array element is either an alias
    # name or a list containing the database name and class.
    #
    #	Command-Line Name	{Database Name		Database Class	}
    #	-----------------------------------------------------------------
    #
    variable colConfigSpecs
    array set colConfigSpecs {
	-align			{align			Align		}
	-background		{background		Background	}
	-bg			-background
	-editable		{editable		Editable	}
	-font			{font			Font		}
	-foreground		{foreground		Foreground	}
	-fg			-foreground
	-formatcommand		{formatCommand		FormatCommand	}
	-hide			{hide			Hide		}
	-labelalign		{labelAlign		Align		}
	-labelbackground	{labelBackground	Background	}
	-labelbg		-labelbackground
	-labelborderwidth	{labelBorderWidth	BorderWidth	}
	-labelbd		-labelborderwidth
	-labelcommand		{labelCommand		LabelCommand	}
	-labelfont		{labelFont		Font		}
	-labelforeground	{labelForeground	Foreground	}
	-labelfg		-labelforeground
	-labelheight		{labelHeight		Height		}
	-labelimage		{labelImage		Image		}
	-labelpady		{labelPadY		Pad		}
	-labelrelief		{labelRelief		Relief		}
	-name			{name			Name		}
	-resizable		{resizable		Resizable	}
	-selectbackground	{selectBackground	Foreground	}
	-selectforeground	{selectForeground	Background	}
	-showarrow		{showArrow		ShowArrow	}
	-sortcommand		{sortCommand		SortCommand	}
	-sortmode		{sortMode		SortMode	}
	-text			{text			Text		}
	-title			{title			Title		}
	-width			{width			Width		}
    }

    #
    # Extend some elements of the array colConfigSpecs
    #
    lappend colConfigSpecs(-align)	- left
    lappend colConfigSpecs(-editable)	- 0
    lappend colConfigSpecs(-hide)	- 0
    lappend colConfigSpecs(-resizable)	- 1
    lappend colConfigSpecs(-showarrow)	- 1
    lappend colConfigSpecs(-sortmode)	- ascii
    lappend colConfigSpecs(-width)	- 0

    #
    # The array rowConfigSpecs is used to handle row configuration options.
    # The names of its elements are the row configuration options for the
    # Tablelist widget class.  The value of an array element is either an alias
    # name or a list containing the database name and class.
    #
    #	Command-Line Name	{Database Name		Database Class	}
    #	-----------------------------------------------------------------
    #
    variable rowConfigSpecs
    array set rowConfigSpecs {
	-background		{background		Background	}
	-bg			-background
	-font			{font			Font		}
	-foreground		{foreground		Foreground	}
	-fg			-foreground
	-selectable		{selectable		Selectable	}
	-selectbackground	{selectBackground	Foreground	}
	-selectforeground	{selectForeground	Background	}
	-text			{text			Text		}
    }

    #
    # Extend some elements of the array rowConfigSpecs
    #
    lappend rowConfigSpecs(-selectable) - 1

    #
    # The array cellConfigSpecs is used to handle cell configuration options.
    # The names of its elements are the cell configuration options for the
    # Tablelist widget class.  The value of an array element is either an alias
    # name or a list containing the database name and class.
    #
    #	Command-Line Name	{Database Name		Database Class	}
    #	-----------------------------------------------------------------
    #
    variable cellConfigSpecs
    array set cellConfigSpecs {
	-background		{background		Background	}
	-bg			-background
	-editable		{editable		Editable	}
	-font			{font			Font		}
	-foreground		{foreground		Foreground	}
	-fg			-foreground
	-image			{image			Image		}
	-selectbackground	{selectBackground	Foreground	}
	-selectforeground	{selectForeground	Background	}
	-text			{text			Text		}
    }

    #
    # Extend some elements of the array cellConfigSpecs
    #
    lappend cellConfigSpecs(-editable) - 1

    #
    # Use a list to facilitate the handling of the command options 
    #
    variable cmdOpts [list \
	activate attrib bbox bodypath cancelediting cellcget cellconfigure \
	cellindex cget columncget columnconfigure columncount columnindex \
	configure containing containingcell containingcolumn curselection \
	delete deletecolumns editcell entrypath fillcolumn finishediting \
	get getcolumns getkeys index insert insertcolumnlist insertcolumns \
	insertlist labelpath labels move movecolumn nearest nearestcell \
	nearestcolumn rejectinput resetsortinfo rowcget rowconfigure scan \
	see seecell seecolumn selection separatorpath separators size sort \
	sortbycolumn sortcolumn sortorder xview yview]

    #
    # Use lists to facilitate the handling of miscellaneous options
    #
    variable activeStyles	[list frame none underline]
    variable alignments		[list left right center]
    variable arrowTypes		[list up down]
    variable states		[list disabled normal]
    variable sortModes		[list ascii command dictionary integer real]
    variable sortOrders		[list -increasing -decreasing]
    variable scanCmdOpts	[list mark dragto]
    variable selCmdOpts		[list anchor clear includes set]

    #
    # Define the procedure strToDispStr, which returns the string obtained
    # by replacing all \t and \n characters in its argument with \\t and
    # \\n, respectively, as well as the procedure strMap, needed because
    # the "string map" command is not available in Tcl 8.0 and 8.1.0.
    #
    if {[catch {string map {} ""}] == 0} {
	proc strToDispStr str {
	    if {[string first \t $str] >= 0 || [string first \n $str] >= 0} {
		return [string map {\t \\t  \n \\n} $str]
	    } else {
		return $str
	    }
	}

	proc strMap {charMap str} {
	    return [string map $charMap $str]
	}
    } else {
	proc strToDispStr str {
	    if {[string first \t $str] >= 0} {
		regsub -all \t $str \\t str
	    }
	    if {[string first \n $str] >= 0} {
		regsub -all \n $str \\n str
	    }

	    return $str
	}

	proc strMap {charMap str} {
	    foreach {key val} $charMap {
		#
		# We will only need this for the key values %W, %x, %y, and %
		#
		regsub -all $key $str $val str
	    }

	    return $str
	}
    }

    #
    # Define some Tablelist class bindings
    #
    bind Tablelist <KeyPress> continue
    bind Tablelist <FocusIn> {
	tablelist::addActiveTag %W

	if {[string compare [focus -lastfor %W] %W] == 0} {
	    if {[winfo exists [%W entrypath]]} {
		focus [%W entrypath]
	    } else {
		focus [%W bodypath]
	    }
	}
    }
    bind Tablelist <FocusOut> {
	tablelist::removeActiveTag %W
    }
    bind Tablelist <Destroy> {
	tablelist::cleanup %W
    }

    #
    # Define the binding tags TablelistKeyNav and TablelistBody
    #
    mwutil::defineKeyNav Tablelist
    defineTablelistBody 

    #
    # Define the virtual event <<Button3>>
    #
    event add <<Button3>> <Button-3>
    if {[string compare $winSys classic] == 0 ||
	[string compare $winSys aqua] == 0} {
	event add <<Button3>> <Control-Button-1>
    }

    #
    # Define some mouse bindings for the binding tag TablelistLabel
    #
    bind TablelistLabel <Enter>		  { tablelist::labelEnter    %W %x }
    bind TablelistLabel <Motion>	  { tablelist::labelEnter    %W %x }
    bind TablelistLabel <Button-1>	  { tablelist::labelB1Down   %W %x }
    bind TablelistLabel <B1-Motion>	  { tablelist::labelB1Motion %W %x %y }
    bind TablelistLabel <B1-Enter>	  { tablelist::labelB1Enter  %W }
    bind TablelistLabel <B1-Leave>	  { tablelist::labelB1Leave  %W %x %y }
    bind TablelistLabel <ButtonRelease-1> { tablelist::labelB1Up     %W %X}
    bind TablelistLabel <<Button3>>	  { tablelist::labelB3Down   %W }

    #
    # Define the binding tags TablelistSubLabel and TablelistArrow
    #
    defineTablelistSubLabel 
    defineTablelistArrow 
}

#
# Public procedure
# ================
#

#------------------------------------------------------------------------------
# tablelist::tablelist
#
# Creates a new tablelist widget whose name is specified as the first command-
# line argument, and configures it according to the options and their values
# given on the command line.  Returns the name of the newly created widget.
#------------------------------------------------------------------------------
proc tablelist::tablelist args {
    variable configSpecs
    variable configOpts

    if {[llength $args] == 0} {
	mwutil::wrongNumArgs "tablelist pathName ?options?"
    }

    #
    # Create a frame of the class Tablelist
    #
    set win [lindex $args 0]
    if {[catch {
	    frame $win -class Tablelist -container 0 -height 0 -width 0
	} result] != 0} {
	return -code error $result
    }

    #
    # Create a namespace within the current one to hold the data of the widget
    #
    namespace eval ns$win {
	#
	# The folowing array holds various data for this widget
	#
	variable data
	array set data {
	    hasListVar		 0
	    isDisabled		 0
	    ownsFocus		 0
	    charWidth		 1
	    hdrPixels		 0
	    oldActiveIdx	 0
	    activeIdx		 0
	    anchorIdx		 0
	    seqNum		-1
	    itemList		 {}
	    itemCount		 0
	    lastRow		-1
	    colList		 {}
	    colCount		 0
	    lastCol		-1
	    tagCount		 0
	    imgCount		 0
	    labelClicked	 0
	    arrowCol		-1
	    sortCol		-1
	    sortOrder		 {}
	    editRow		-1
	    editCol		-1
	    forceAdjust		 0
	    fmtCmdFlagList	 {}
	}

	#
	# The following array is used to hold arbitrary
	# attributes and their values for this widget
	#
	variable attribVals
    }

    #
    # Initialize some further components of data
    #
    upvar ::tablelist::ns${win}::data data
    foreach opt $configOpts {
	set data($opt) [lindex $configSpecs($opt) 3]
    }
    set data(colFontList)	[list $data(-font)]
    set data(listVarTraceCmd)	[list tablelist::listVarTrace $win]
    set data(body)		$win.body
    set data(bodyFr)		$data(body).f
    set data(bodyFrEnt)		$data(bodyFr).e
    set data(hdr)		$win.hdr
    set data(hdrTxt)		$data(hdr).t
    set data(hdrTxtFr)		$data(hdrTxt).f
    set data(hdrTxtFrCanv)	$data(hdrTxtFr).c
    set data(hdrTxtFrLbl)	$data(hdrTxtFr).l
    set data(hdrLbl)		$data(hdr).l
    set data(hdrGap)		$data(hdr).g
    set data(lb)		$win.lb
    set data(sep)		$win.sep

    #
    # Create a child hierarchy used to hold the column labels.  The
    # labels will be created as children of the frame data(hdrTxtFr),
    # which is embedded into the text widget data(hdrTxt) (in order
    # to make it scrollable), which in turn fills the frame data(hdr)
    # (whose width and height can be set arbitrarily in pixels).
    #
    set w $data(hdr)			;# header frame
    frame $w -borderwidth 0 -container 0 -height 0 -highlightthickness 0 \
	     -relief flat -takefocus 0 -width 0
    bind $w <Configure> { tablelist::stretchColumnsWhenIdle [winfo parent %W] }
    pack $w -fill x
    set w $data(hdrTxt)			;# text widget within the header frame
    text $w -borderwidth 0 -highlightthickness 0 -insertwidth 0 \
	    -padx 0 -pady 0 -state normal -takefocus 0 -wrap none
    place $w -relheight 1.0 -relwidth 1.0
    bindtags $w [lreplace [bindtags $w] 1 1]
    frame $data(hdrTxtFr) -borderwidth 0 -container 0 -height 0 \
			  -highlightthickness 0 -relief flat -takefocus 0 \
			  -width 0
    $w window create 1.0 -window $data(hdrTxtFr)
    label $data(hdrTxtFrLbl)0 
    set w $data(hdrLbl)			;# filler label within the header frame
    label $w -bitmap "" -highlightthickness 0 -image "" -takefocus 0 \
	     -text "" -textvariable "" -underline -1 -wraplength 0
    place $w -relheight 1.0 -relwidth 1.0

    #
    # Create a canvas as a child of the frame data(hdrTxtFr),
    # needed for displaying an up- or down-arrow when
    # sorting the items by a column.   Set its width and
    # height to temporary values and create two 3-D arrows
    #
    set w $data(hdrTxtFrCanv)
    set size 9
    canvas $w -borderwidth 0 -height $size -highlightthickness 0 \
	      -relief flat -takefocus 0 -width $size
    create3DArrows $w

    #
    # Replace the binding tag Canvas with TablelistArrow
    # in the list of binding tags of the canvas
    #
    bindtags $w [lreplace [bindtags $w] 1 1 TablelistArrow]

    #
    # Create a frame used to display a gap between two
    # consecutive columns when moving a column interactively
    #
    frame $data(hdrGap) -borderwidth 1 -container 0 -highlightthickness 0 \
			-relief sunken -takefocus 0 -width 4

    #
    # Create the body text widget within the main frame
    #
    set w $data(body)
    text $w -borderwidth 0 -exportselection 0 -highlightthickness 0 \
	    -insertwidth 0 -padx 0 -pady 0 -state normal -takefocus 0 -wrap none
    bind $w <Configure> { tablelist::adjustSepsWhenIdle [winfo parent %W] }
    pack $w -expand 1 -fill both

    #
    # Modify the list of binding tags of the body text widget
    #
    bindtags $w [list $w TablelistBody [winfo toplevel $w] TablelistKeyNav all]

    #
    # Create the "active", "stripe", "select", and "disabled" tags
    # in the body text widget.  Don't use the built-in "sel" tag
    # because on Windows the selection in a text widget only
    # becomes visible when the window gets the input focus.
    #
    $w tag configure stripe -background "" -foreground ""
    $w tag configure active -borderwidth 1 -underline 1
    $w tag configure select -relief raised
    $w tag configure disabled -underline 0

    #
    # Create an unmanaged listbox child, used to handle the -setgrid option
    #
    listbox $data(lb)

    #
    # Configure the widget according to the command-line
    # arguments and to the available database options
    #
    if {[catch {
	    mwutil::configure $win configSpecs data tablelist::doConfig \
			      [lrange $args 1 end] 1
	} result] != 0} {
	destroy $win
	return -code error $result
    }

    #
    # Move the original widget command into the current namespace
    # and build a new widget procedure in the global one
    #
    rename ::$win $win
    proc ::$win args [format {
	if {[catch {tablelist::tablelistWidgetCmd %s $args} result] == 0} {
	    return $result
	} else {
	    return -code error $result
	}
    } [list $win]]

    #
    # Register a callback to be invoked whenever the PRIMARY selection is
    # owned by the window win and someone attempts to retrieve it as a
    # UTF8_STRING or STRING (the type UTF8_STRING is only needed to work
    # around a bug in Tk 8.4.0 and 8.4.1 causing crashes under KDE 3.0)
    #
    selection handle -type UTF8_STRING $win \
	[list ::tablelist::fetchSelection $win]
    selection handle -type STRING $win \
	[list ::tablelist::fetchSelection $win]

    #
    # Set a trace on the array element data(activeIdx)
    #
    trace variable data(activeIdx) w [list tablelist::activeIdxTrace $win]

    return $win
}

#
# Private configuration procedures
# ================================
#
# See the module "tablelistConfig.tcl"
#

#
# Private procedures implementing the tablelist widget command
# ============================================================
#

#------------------------------------------------------------------------------
# tablelist::tablelistWidgetCmd
#
# This procedure is invoked to process the Tcl command corresponding to a
# tablelist widget.
#------------------------------------------------------------------------------
proc tablelist::tablelistWidgetCmd {win argList} {
    variable cmdOpts
    upvar ::tablelist::ns${win}::data data

    set argCount [llength $argList]
    if {$argCount == 0} {
	mwutil::wrongNumArgs "$win option ?arg arg ...?"
    }

    set cmd [mwutil::fullOpt "option" [lindex $argList 0] $cmdOpts]
    switch $cmd {
	activate -
	bbox -
	see {
	    if {$argCount != 2} {
		mwutil::wrongNumArgs "$win $cmd index"
	    }

	    synchronize $win
	    set index [rowIndex $win [lindex $argList 1] 0]
	    return [${cmd}SubCmd $win $index]
	}

	attrib {
	    return [mwutil::attribSubCmd $win [lrange $argList 1 end]]
	}

	bodypath {
	    if {$argCount != 1} {
		mwutil::wrongNumArgs "$win $cmd"
	    }

	    return $data(body)
	}

	cancelediting -
	curselection -
	finishediting {
	    if {$argCount != 1} {
		mwutil::wrongNumArgs "$win $cmd"
	    }

	    synchronize $win
	    return [${cmd}SubCmd $win]
	}

	cellcget {
	    if {$argCount != 3} {
		mwutil::wrongNumArgs "$win $cmd cellIndex option"
	    }

	    synchronize $win
	    scan [cellIndex $win [lindex $argList 1] 1] %d,%d row col
	    variable cellConfigSpecs
	    set opt [mwutil::fullConfigOpt [lindex $argList 2] cellConfigSpecs]
	    return [doCellCget $row $col $win $opt]
	}

	cellconfigure {
	    if {$argCount < 2} {
		mwutil::wrongNumArgs "$win $cmd cellIndex ?option? ?value?\
				      ?option value ...?"
	    }

	    synchronize $win
	    scan [cellIndex $win [lindex $argList 1] 1] %d,%d row col
	    variable cellConfigSpecs
	    set argList [lrange $argList 2 end]
	    mwutil::setConfigVals $win cellConfigSpecs cellConfigVals \
				  "tablelist::doCellCget $row $col" $argList
	    return [mwutil::configSubCmd $win cellConfigSpecs cellConfigVals \
		    "tablelist::doCellConfig $row $col" $argList]
	}

	cellindex {
	    if {$argCount != 2} {
		mwutil::wrongNumArgs "$win $cmd cellIndex"
	    }

	    synchronize $win
	    return [cellIndex $win [lindex $argList 1] 0]
	}

	cget {
	    if {$argCount != 2} {
		mwutil::wrongNumArgs "$win $cmd option"
	    }

	    #
	    # Return the value of the specified configuration option
	    #
	    variable configSpecs
	    set opt [mwutil::fullConfigOpt [lindex $argList 1] configSpecs]
	    return $data($opt)
	}

	columncget {
	    if {$argCount != 3} {
		mwutil::wrongNumArgs "$win $cmd columnIndex option"
	    }

	    synchronize $win
	    set col [colIndex $win [lindex $argList 1] 1]
	    variable colConfigSpecs
	    set opt [mwutil::fullConfigOpt [lindex $argList 2] colConfigSpecs]
	    return [doColCget $col $win $opt]
	}

	columnconfigure {
	    if {$argCount < 2} {
		mwutil::wrongNumArgs "$win $cmd columnIndex ?option? ?value?\
				      ?option value ...?"
	    }

	    synchronize $win
	    set col [colIndex $win [lindex $argList 1] 1]
	    variable colConfigSpecs
	    set argList [lrange $argList 2 end]
	    mwutil::setConfigVals $win colConfigSpecs colConfigVals \
				  "tablelist::doColCget $col" $argList
	    return [mwutil::configSubCmd $win colConfigSpecs colConfigVals \
		    "tablelist::doColConfig $col" $argList]
	}

	columncount {
	    if {$argCount != 1} {
		mwutil::wrongNumArgs "$win $cmd"
	    }

	    return $data(colCount)
	}

	columnindex {
	    if {$argCount != 2} {
		mwutil::wrongNumArgs "$win $cmd columnIndex"
	    }

	    synchronize $win
	    return [colIndex $win [lindex $argList 1] 0]
	}

	configure {
	    variable configSpecs
	    return [mwutil::configSubCmd $win configSpecs data \
		    tablelist::doConfig [lrange $argList 1 end]]
	}

	containing {
	    if {$argCount != 2} {
		mwutil::wrongNumArgs "$win $cmd y"
	    }

	    set y [lindex $argList 1]
	    format %d $y		;# integer check with error message
	    synchronize $win
	    return [containingSubCmd $win $y]
	}

	containingcell {
	    if {$argCount != 3} {
		mwutil::wrongNumArgs "$win $cmd x y"
	    }

	    set x [lindex $argList 1]
	    format %d $x		;# integer check with error message
	    set y [lindex $argList 2]
	    format %d $y		;# integer check with error message
	    synchronize $win
	    return [containingSubCmd $win $y],[containingcolumnSubCmd $win $x]
	}

	containingcolumn {
	    if {$argCount != 2} {
		mwutil::wrongNumArgs "$win $cmd x"
	    }

	    set x [lindex $argList 1]
	    format %d $x		;# integer check with error message
	    synchronize $win
	    return [containingcolumnSubCmd $win $x]
	}

	delete -
	get -
	getkeys {
	    if {$argCount < 2 || $argCount > 3} {
		mwutil::wrongNumArgs "$win $cmd firstIndex lastIndex" \
				     "$win $cmd indexList"
	    }

	    synchronize $win
	    set first [lindex $argList 1]
	    if {$argCount == 3} {
		set last [lindex $argList 2]
	    } else {
		set last $first
	    }
	    incr argCount -1
	    return [${cmd}SubCmd $win $first $last $argCount]
	}

	deletecolumns -
	getcolumns {
	    if {$argCount < 2 || $argCount > 3} {
		mwutil::wrongNumArgs "$win $cmd firstColumnIndex\
				      lastColumnIndex" \
				     "$win $cmd columnIndexList"
	    }

	    synchronize $win
	    set first [lindex $argList 1]
	    if {$argCount == 3} {
		set last [lindex $argList 2]
	    } else {
		set last $first
	    }
	    incr argCount -1
	    return [${cmd}SubCmd $win $first $last $argCount]
	}

	editcell {
	    if {$argCount != 2} {
		mwutil::wrongNumArgs "$win $cmd cellIndex"
	    }

	    synchronize $win
	    scan [cellIndex $win [lindex $argList 1] 1] %d,%d row col
	    return [${cmd}SubCmd $win $row $col 0]
	}

	entrypath {
	    if {$argCount != 1} {
		mwutil::wrongNumArgs "$win $cmd"
	    }

	    if {[winfo exists $data(bodyFrEnt)]} {
		return $data(bodyFrEnt)
	    } else {
		return ""
	    }
	}

	fillcolumn {
	    if {$argCount != 3} {
		mwutil::wrongNumArgs "$win $cmd columnIndex text"
	    }

	    synchronize $win
	    set col [colIndex $win [lindex $argList 1] 1]
	    return [fillcolumnSubCmd $win $col [lindex $argList 2]]
	}

	index {
	    if {$argCount != 2} {
		mwutil::wrongNumArgs "$win $cmd index"
	    }

	    synchronize $win
	    return [rowIndex $win [lindex $argList 1] 1]
	}

	insert {
	    if {$argCount < 2} {
		mwutil::wrongNumArgs "$win $cmd index ?item item ...?"
	    }

	    synchronize $win
	    set index [rowIndex $win [lindex $argList 1] 1]
	    return [insertSubCmd $win $index [lrange $argList 2 end] \
		    $data(hasListVar)]
	}

	insertcolumnlist {
	    if {$argCount != 3} {
		mwutil::wrongNumArgs "$win $cmd columnIndex columnList"
	    }

	    synchronize $win
	    set arg1 [lindex $argList 1]
	    if {[string first $arg1 end] == 0 || $arg1 == $data(colCount)} {
		set col $data(colCount)
	    } else {
		set col [colIndex $win $arg1 1]
	    }
	    return [insertcolumnsSubCmd $win $col [lindex $argList 2]]
	}

	insertcolumns {
	    if {$argCount < 2} {
		mwutil::wrongNumArgs "$win $cmd columnIndex\
				      ?width title ?alignment?\
				       width title ?alignment? ...?"
	    }

	    synchronize $win
	    set arg1 [lindex $argList 1]
	    if {[string first $arg1 end] == 0 || $arg1 == $data(colCount)} {
		set col $data(colCount)
	    } else {
		set col [colIndex $win $arg1 1]
	    }
	    return [insertcolumnsSubCmd $win $col [lrange $argList 2 end]]
	}

	insertlist {
	    if {$argCount != 3} {
		mwutil::wrongNumArgs "$win $cmd index list"
	    }

	    synchronize $win
	    set index [rowIndex $win [lindex $argList 1] 1]
	    return [insertSubCmd $win $index [lindex $argList 2] \
		    $data(hasListVar)]
	}

	labelpath {
	    if {$argCount != 2} {
		mwutil::wrongNumArgs "$win $cmd columnIndex"
	    }

	    synchronize $win
	    set col [colIndex $win [lindex $argList 1] 1]
	    return $data(hdrTxtFrLbl)$col
	}

	labels {
	    if {$argCount != 1} {
		mwutil::wrongNumArgs "$win $cmd"
	    }

	    set children [winfo children $data(hdrTxtFr)]
	    return [lrange [lsort $children] 1 end]
	}

	move {
	    if {$argCount != 3} {
		mwutil::wrongNumArgs "$win $cmd sourceIndex targetIndex"
	    }

	    synchronize $win
	    set source [rowIndex $win [lindex $argList 1] 0]
	    set target [rowIndex $win [lindex $argList 2] 1]
	    return [moveSubCmd $win $source $target]
	}

	movecolumn {
	    if {$argCount != 3} {
		mwutil::wrongNumArgs "$win $cmd sourceColumnIndex\
				      targetColumnIndex"
	    }

	    synchronize $win
	    set arg1 [lindex $argList 1]
	    set source [colIndex $win $arg1 1]
	    set arg2 [lindex $argList 2]
	    if {[string first $arg2 end] == 0 || $arg2 == $data(colCount)} {
		set target $data(colCount)
	    } else {
		set target [colIndex $win $arg2 1]
	    }
	    return [movecolumnSubCmd $win $source $target]
	}

	nearest {
	    if {$argCount != 2} {
		mwutil::wrongNumArgs "$win $cmd y"
	    }

	    set y [lindex $argList 1]
	    format %d $y		;# integer check with error message
	    synchronize $win
	    return [rowIndex $win @0,$y 0]
	}

	nearestcell {
	    if {$argCount != 3} {
		mwutil::wrongNumArgs "$win $cmd x y"
	    }

	    set x [lindex $argList 1]
	    format %d $x		;# integer check with error message
	    set y [lindex $argList 2]
	    format %d $y		;# integer check with error message
	    synchronize $win
	    return [cellIndex $win @$x,$y 0]
	}

	nearestcolumn {
	    if {$argCount != 2} {
		mwutil::wrongNumArgs "$win $cmd x"
	    }

	    set x [lindex $argList 1]
	    format %d $x		;# integer check with error message
	    synchronize $win
	    return [colIndex $win @$x,0 0]
	}

	rejectinput {
	    if {$argCount != 1} {
		mwutil::wrongNumArgs "$win $cmd"
	    }

	    set data(rejected) 1
	}

	resetsortinfo {
	    if {$argCount != 1} {
		mwutil::wrongNumArgs "$win $cmd"
	    }

	    set data(sortCol) -1
	    set data(sortOrder) {}

	    place forget $data(hdrTxtFrCanv)
	    set oldArrowCol $data(arrowCol)
	    set data(arrowCol) -1
	    synchronize $win
	    adjustColumns $win l$oldArrowCol 1
	    return ""
	}

	rowcget {
	    if {$argCount != 3} {
		mwutil::wrongNumArgs "$win $cmd index option"
	    }

	    #
	    # Check the row index
	    #
	    synchronize $win
	    set rowArg [lindex $argList 1]
	    set row [rowIndex $win $rowArg 0]
	    if {$row < 0 || $row > $data(lastRow)} {
		return -code error \
		       "row index \"$rowArg\" out of range"
	    }

	    variable rowConfigSpecs
	    set opt [mwutil::fullConfigOpt [lindex $argList 2] rowConfigSpecs]
	    return [doRowCget $row $win $opt]
	}

	rowconfigure {
	    if {$argCount < 2} {
		mwutil::wrongNumArgs "$win $cmd index ?option? ?value?\
				      ?option value ...?"
	    }

	    #
	    # Check the row index
	    #
	    synchronize $win
	    set rowArg [lindex $argList 1]
	    set row [rowIndex $win $rowArg 0]
	    if {$row < 0 || $row > $data(lastRow)} {
		return -code error \
		       "row index \"$rowArg\" out of range"
	    }

	    variable rowConfigSpecs
	    set argList [lrange $argList 2 end]
	    mwutil::setConfigVals $win rowConfigSpecs rowConfigVals \
				  "tablelist::doRowCget $row" $argList
	    return [mwutil::configSubCmd $win rowConfigSpecs rowConfigVals \
		    "tablelist::doRowConfig $row" $argList]
	}

	scan {
	    if {$argCount != 4} {
		mwutil::wrongNumArgs "$win $cmd mark|dragto x y"
	    }

	    set x [lindex $argList 2]
	    set y [lindex $argList 3]
	    format %d $x		;# integer check with error message
	    format %d $y		;# integer check with error message
	    variable scanCmdOpts
	    set opt [mwutil::fullOpt "option" [lindex $argList 1] $scanCmdOpts]
	    synchronize $win
	    return [scanSubCmd $win $opt $x $y]
	}

	seecell {
	    if {$argCount != 2} {
		mwutil::wrongNumArgs "$win $cmd cellIndex"
	    }

	    synchronize $win
	    scan [cellIndex $win [lindex $argList 1] 1] %d,%d row col
	    return [${cmd}SubCmd $win $row $col]
	}

	seecolumn {
	    if {$argCount != 2} {
		mwutil::wrongNumArgs "$win $cmd columnIndex"
	    }

	    synchronize $win
	    set col [colIndex $win [lindex $argList 1] 1]
	    return [seecellSubCmd $win [rowIndex $win @0,0 0] $col]
	}

	selection {
	    if {$argCount < 3 || $argCount > 4} {
		mwutil::wrongNumArgs "$win $cmd option firstIndex lastIndex" \
				     "$win $cmd indexList"
	    }

	    synchronize $win
	    variable selCmdOpts
	    set opt [mwutil::fullOpt "option" [lindex $argList 1] $selCmdOpts]
	    set first [lindex $argList 2]
	    switch $opt {
		anchor -
		includes {
		    if {$argCount != 3} {
			mwutil::wrongNumArgs "$win selection $opt index"
		    }
		    set index [rowIndex $win $first 0]
		    return [selectionSubCmd $win $opt $index $index]
		}
		clear -
		set {
		    if {$argCount == 3} {
			foreach elem $first {
			    set index [rowIndex $win $elem 0]
			    selectionSubCmd $win $opt $index $index
			}
			return ""
		    } else {
			set first [rowIndex $win $first 0]
			set last [rowIndex $win [lindex $argList 3] 0]
			return [selectionSubCmd $win $opt $first $last]
		    }
		}
	    }
	}

	separatorpath {
	    if {$argCount != 2} {
		mwutil::wrongNumArgs "$win $cmd columnIndex"
	    }

	    synchronize $win
	    set col [colIndex $win [lindex $argList 1] 1]
	    if {$data(-showseparators)} {
		return $data(sep)$col
	    } else {
		return ""
	    }
	}

	separators {
	    if {$argCount != 1} {
		mwutil::wrongNumArgs "$win $cmd"
	    }

	    set sepList {}
	    foreach w [winfo children $win] {
		if {[regexp {^sep[0-9]+$} [winfo name $w]]} {
		    lappend sepList $w
		}
	    }
	    return $sepList
	}

	size {
	    if {$argCount != 1} {
		mwutil::wrongNumArgs "$win $cmd"
	    }

	    synchronize $win
	    return $data(itemCount)
	}

	sort {
	    if {$argCount < 1 || $argCount > 2} {
		mwutil::wrongNumArgs "$win $cmd  ?-increasing|-decreasing?"
	    }

	    if {$argCount == 1} {
		set order -increasing
	    } else {
		variable sortOrders
		set order [mwutil::fullOpt "option" \
			   [lindex $argList 2] $sortOrders]
	    }
	    synchronize $win
	    return [sortSubCmd $win -1 $order]
	}

	sortbycolumn {
	    if {$argCount < 2 || $argCount > 3} {
		mwutil::wrongNumArgs "$win $cmd columnIndex\
				      ?-increasing|-decreasing?"
	    }

	    synchronize $win
	    set col [colIndex $win [lindex $argList 1] 1]
	    if {$argCount == 2} {
		set order -increasing
	    } else {
		variable sortOrders
		set order [mwutil::fullOpt "option" \
			   [lindex $argList 2] $sortOrders]
	    }
	    return [sortSubCmd $win $col $order]
	}

	sortcolumn {
	    if {$argCount != 1} {
		mwutil::wrongNumArgs "$win $cmd"
	    }

	    return $data(sortCol)
	}

	sortorder {
	    if {$argCount != 1} {
		mwutil::wrongNumArgs "$win $cmd"
	    }

	    return $data(sortOrder)
	}

	xview -
	yview {
	    synchronize $win
	    return [${cmd}SubCmd $win [lrange $argList 1 end]]
	}
    }
}

#------------------------------------------------------------------------------
# tablelist::activateSubCmd
#
# This procedure is invoked to process the tablelist activate subcommand.
#------------------------------------------------------------------------------
proc tablelist::activateSubCmd {win index} {
    upvar ::tablelist::ns${win}::data data

    if {$data(isDisabled)} {
	return ""
    }

    #
    # Adjust the index to fit within the existing items
    #
    if {$index > $data(lastRow)} {
	set index $data(lastRow)
    }
    if {$index < 0} {
	set index 0
    }

    set data(activeIdx) $index
    return ""
}

#------------------------------------------------------------------------------
# tablelist::bboxSubCmd
#
# This procedure is invoked to process the tablelist bbox subcommand.
#------------------------------------------------------------------------------
proc tablelist::bboxSubCmd {win index} {
    upvar ::tablelist::ns${win}::data data

    set w $data(body)
    set dlineinfo [$w dlineinfo [expr {double($index + 1)}]]
    if {$data(itemCount) == 0 || [string compare $dlineinfo ""] == 0} {
	return {}
    }

    foreach {x y width height baselinePos} $dlineinfo {
	lappend bbox [expr {$x + [winfo x $w]}] \
		     [expr {$y + [winfo y $w] + $data(-selectborderwidth)}] \
		     $width [expr {$height - 2*$data(-selectborderwidth) - 1}]
    }
    return $bbox
}

#------------------------------------------------------------------------------
# tablelist::containingSubCmd
#
# This procedure is invoked to process the tablelist containing subcommand.
#------------------------------------------------------------------------------
proc tablelist::containingSubCmd {win y} {
    upvar ::tablelist::ns${win}::data data

    set row [rowIndex $win @0,$y 0]

    set w $data(body)
    incr y -[winfo y $w]
    set dlineinfo [$w dlineinfo [expr {double($row + 1)}]]
    if {$y < [lindex $dlineinfo 1] + [lindex $dlineinfo 3]} {
	return $row
    } else {
	return -1
    }
}

#------------------------------------------------------------------------------
# tablelist::containingcolumnSubCmd
#
# This procedure is invoked to process the tablelist containingcolumn
# subcommand.
#------------------------------------------------------------------------------
proc tablelist::containingcolumnSubCmd {win x} {
    upvar ::tablelist::ns${win}::data data

    set col [colIndex $win @$x,0 0]

    set lbl $data(hdrTxtFrLbl)$col
    if {$x + [winfo rootx $win] < [winfo width $lbl] + [winfo rootx $lbl]} {
	return $col
    } else {
	return -1
    }
}

#------------------------------------------------------------------------------
# tablelist::curselectionSubCmd
#
# This procedure is invoked to process the tablelist curselection subcommand.
#------------------------------------------------------------------------------
proc tablelist::curselectionSubCmd win {
    upvar ::tablelist::ns${win}::data data

    #
    # Find the selected lines of the body text widget
    #
    set result {}
    set w $data(body)
    set selRange [$w tag nextrange select 1.0]
    while {[llength $selRange] != 0} {
	set selStart [lindex $selRange 0]
	set selEnd [lindex $selRange 1]
	lappend result [expr {int($selStart) - 1}]

	set selRange [$w tag nextrange select $selEnd]
    }
    return $result
}

#------------------------------------------------------------------------------
# tablelist::deleteSubCmd
#
# This procedure is invoked to process the tablelist delete subcommand.
#------------------------------------------------------------------------------
proc tablelist::deleteSubCmd {win first last argCount} {
    upvar ::tablelist::ns${win}::data data

    if {$data(isDisabled)} {
	return ""
    }

    if {$argCount == 1} {
	if {[llength $first] == 1} {			;# just to save time
	    set index [rowIndex $win [lindex $first 0] 0]
	    return [deleteRows $win $index $index $data(hasListVar)]
	} elseif {$data(itemCount) == 0} {		;# no items present
	    return ""
	} else {					;# a bit more work
	    #
	    # Sort the numerical equivalents of the
	    # specified indices in decreasing order
	    #
	    set indexList {}
	    foreach elem $first {
		set index [rowIndex $win $elem 0]
		if {$index < 0} {
		    set index 0
		} elseif {$index > $data(lastRow)} {
		    set index $data(lastRow)
		}
		lappend indexList $index
	    }
	    set indexList [lsort -integer -decreasing $indexList]

	    #
	    # Traverse the sorted index list and ignore any duplicates
	    #
	    set prevIndex -1
	    foreach index $indexList {
		if {$index != $prevIndex} {
		    deleteRows $win $index $index $data(hasListVar)
		    set prevIndex $index
		}
	    }
	    return ""
	}
    } else {
	set first [rowIndex $win $first 0]
	set last [rowIndex $win $last 0]
	return [deleteRows $win $first $last $data(hasListVar)]
    }
}

#------------------------------------------------------------------------------
# tablelist::deleteRows
#
# Deletes a given range of rows of a tablelist widget.
#------------------------------------------------------------------------------
proc tablelist::deleteRows {win first last updateListVar} {
    upvar ::tablelist::ns${win}::data data

    #
    # Adjust the range to fit within the existing items
    #
    if {$first < 0} {
	set first 0
    }
    if {$last > $data(lastRow)} {
	set last $data(lastRow)
    }
    set count [expr {$last - $first + 1}]
    if {$count <= 0} {
	return ""
    }

    #
    # Check whether the width of any dynamic-width
    # column might be affected by the deletion
    #
    set w $data(body)
    set itemListRange [lrange $data(itemList) $first $last]
    if {$count == $data(itemCount)} {
	set colWidthsChanged 1				;# just to save time
    } else {
	set colWidthsChanged 0
	set snipStr $data(-snipstring)
	foreach item $itemListRange {
	    set dispItem [strToDispStr $item]
	    set key [lindex $item end]
	    set col 0
	    foreach text [lrange $dispItem 0 $data(lastCol)] \
		    colFont $data(colFontList) \
		    fmtCmdFlag $data(fmtCmdFlagList) \
		    {pixels alignment} $data(colList) {
		if {$data($col-hide) || $pixels != 0} {
		    incr col
		    continue
		}

		if {$fmtCmdFlag} {
		    set text [uplevel #0 $data($col-formatcommand) \
			      [list [lindex $item $col]]]
		    set text [strToDispStr $text]
		}
		if {[info exists data($key-$col-image)]} {
		    set imageWidth [image width $data($key-$col-image)]
		} else {
		    set imageWidth 0
		}
		if {[info exists data($key-$col-font)]} {
		    set cellFont $data($key-$col-font)
		} elseif {[info exists data($key-font)]} {
		    set cellFont $data($key-font)
		} else {
		    set cellFont $colFont
		}
		adjustElem $win text imageWidth $cellFont \
			   $pixels $alignment $snipStr
		set textWidth [font measure $cellFont -displayof $win $text]
		set elemWidth [expr {$imageWidth + $textWidth}]
		if {$elemWidth == $data($col-elemWidth) &&
		    [incr data($col-widestCount) -1] == 0} {
		    set colWidthsChanged 1
		    break
		}

		incr col
	    }

	    if {$colWidthsChanged} {
		break
	    }
	}
    }

    #
    # Delete the given items and their tags from the body text widget.
    # Interestingly, for a large number of items it is much more efficient to
    # delete each line individually than to invoke a global delete command.
    #
    set textIdx1 [expr {double($first + 1)}]
    set textIdx2 [expr {double($first + 2)}]
    foreach item $itemListRange {
	$w delete $textIdx1 $textIdx2

	set key [lindex $item end]
	array set tagData [array get data $key*-\[bfs\]*]	;# for speed

	foreach tag [array names tagData $key-\[bfs\]*] {
	    $w tag delete $tag
	    unset data($tag)
	    if {[string match $key-\[bf\]* $tag]} {
		incr data(tagCount) -1
	    }
	}

	for {set col 0} {$col < $data(colCount)} {incr col} {
	    foreach tag [array names tagData $key-$col-\[bfs\]*] {
		$w tag delete $tag
		unset data($tag)
		if {[string match $key-$col-\[bf\]* $tag]} {
		    incr data(tagCount) -1
		}
	    }
	    if {[info exists data($key-$col-image)]} {
		unset data($key-$col-image)
		incr data(imgCount) -1
	    }
	}

	unset tagData
    }

    #
    # Delete the given items from the internal list
    #
    set data(itemList) [lreplace $data(itemList) $first $last]
    incr data(itemCount) -$count
    incr data(lastRow) -$count

    #
    # Delete the given items from the list variable if needed
    #
    if {$updateListVar} {
	trace vdelete ::$data(-listvariable) wu $data(listVarTraceCmd)
	upvar #0 $data(-listvariable) var
	set var [lreplace $var $first $last]
	trace variable ::$data(-listvariable) wu $data(listVarTraceCmd)
    }

    #
    # Adjust the height of the body text widget if necessary
    #
    if {$data(-height) <= 0} {
	$w configure -height $data(itemCount)
    }

    #
    # Adjust the columns if necessary, adjust the separators,
    # and redraw the stripes in the body text widget
    #
    if {$colWidthsChanged} {
	adjustColumns $win allCols 1
    }
    adjustSepsWhenIdle $win
    makeStripesWhenIdle $win

    #
    # Update the indices anchorIdx and activeIdx
    #
    if {$first <= $data(anchorIdx)} {
	incr data(anchorIdx) -$count
	if {$data(anchorIdx) < $first} {
	    set data(anchorIdx) $first
	}
    }
    if {$last < $data(activeIdx)} {
	incr data(activeIdx) -$count
    } elseif {$first <= $data(activeIdx)} {
	set data(activeIdx) $first
	if {$data(activeIdx) > $data(lastRow) && $data(lastRow) >= 0} {
	    set data(activeIdx) $data(lastRow)
	}
    }

    #
    # Update data(editRow) if the entry widget is present
    #
    if {$data(editRow) >= 0} {
	set data(editRow) [lsearch $data(itemList) "* $data(editKey)"]
    }

    return ""
}

#------------------------------------------------------------------------------
# tablelist::deletecolumnsSubCmd
#
# This procedure is invoked to process the tablelist deletecolumns subcommand.
#------------------------------------------------------------------------------
proc tablelist::deletecolumnsSubCmd {win first last argCount} {
    upvar ::tablelist::ns${win}::data data

    if {$data(isDisabled)} {
	return ""
    }

    if {$argCount == 1} {
	if {[llength $first] == 1} {			;# just to save time
	    set col [colIndex $win [lindex $first 0] 1]
	    deleteCols $win $col $col
	    redisplay $win
	} elseif {$data(colCount) == 0} {		;# no columns present
	    return ""
	} else {					;# a bit more work
	    #
	    # Sort the numerical equivalents of the
	    # specified column indices in decreasing order
	    #
	    set colList {}
	    foreach elem $first {
		lappend colList [colIndex $win $elem 1]
	    }
	    set colList [lsort -integer -decreasing $colList]

	    #
	    # Traverse the sorted column index
	    # list and ignore any duplicates
	    #
	    set deleted 0
	    set prevCol -1
	    foreach col $colList {
		if {$col != $prevCol} {
		    deleteCols $win $col $col
		    set deleted 1
		    set prevCol $col
		}
	    }
	    if {$deleted} {
		redisplay $win
	    }
	}
    } else {
	set first [colIndex $win $first 1]
	set last [colIndex $win $last 1]
	if {$first <= $last} {
	    deleteCols $win $first $last
	    redisplay $win
	}
    }

    return ""
}

#------------------------------------------------------------------------------
# tablelist::deleteCols
#
# Deletes a given range of columns of a tablelist widget.
#------------------------------------------------------------------------------
proc tablelist::deleteCols {win first last} {
    upvar ::tablelist::ns${win}::data data

    #
    # Delete the data corresponding to the given range
    #
    for {set col $first} {$col <= $last} {incr col} {
	deleteColData $win $col
    }

    #
    # Shift the elements of data corresponding to the column
    # indices > last to the left by last - first + 1 positions
    #
    for {set oldCol [expr {$last + 1}]; set newCol $first} \
	{$oldCol < $data(colCount)} {incr oldCol; incr newCol} {
	moveColData $win data data imgs $oldCol $newCol
    }

    #
    # Update the item list
    #
    set newItemList {}
    foreach item $data(itemList) {
	set item [lreplace $item $first $last]
	lappend newItemList $item
    }
    set data(itemList) $newItemList

    #
    # Update the list variable if present
    #
    condUpdateListVar $win

    #
    # Set up and adjust the columns, and rebuild
    # the lists of the column fonts and tag names
    #
    setupColumns $win \
	[lreplace $data(-columns) [expr {3*$first}] [expr {3*$last + 2}]] 1
    makeColFontAndTagLists $win
    adjustColumns $win {} 1

    #
    # Reconfigure the relevant column labels
    #
    for {set col $first} {$col < $data(colCount)} {incr col} {
	reconfigColLabels $win imgs $col
    }
}

#------------------------------------------------------------------------------
# tablelist::fillcolumnSubCmd
#
# This procedure is invoked to process the tablelist fillcolumn subcommand.
#------------------------------------------------------------------------------
proc tablelist::fillcolumnSubCmd {win colIdx text} {
    upvar ::tablelist::ns${win}::data data

    if {$data(isDisabled)} {
	return ""
    }

    #
    # Update the item list
    #
    set newItemList {}
    foreach item $data(itemList) {
	set item [lreplace $item $colIdx $colIdx $text]
	lappend newItemList $item
    }
    set data(itemList) $newItemList

    #
    # Update the list variable if present
    #
    condUpdateListVar $win

    #
    # Adjust the columns and make sure the
    # items will be redisplayed at idle time
    #
    adjustColumns $win $colIdx 1
    redisplayWhenIdle $win
    return ""
}

#------------------------------------------------------------------------------
# tablelist::getSubCmd
#
# This procedure is invoked to process the tablelist get subcommand.
#------------------------------------------------------------------------------
proc tablelist::getSubCmd {win first last argCount} {
    upvar ::tablelist::ns${win}::data data

    #
    # Get the specified items from the internal list
    #
    set result {}
    if {$argCount == 1} {
	foreach elem $first {
	    set index [rowIndex $win $elem 0]
	    if {$index >= 0 && $index < $data(itemCount)} {
		set item [lindex $data(itemList) $index]
		lappend result [lrange $item 0 $data(lastCol)]
	    }
	}

	if {[llength $first] == 1} {
	    return [lindex $result 0]
	} else {
	    return $result
	}
    } else {
	set first [rowIndex $win $first 0]
	set last [rowIndex $win $last 0]

	#
	# Adjust the range to fit within the existing items
	#
	if {$first > $data(lastRow)} {
	    return {}
	}
	if {$last > $data(lastRow)} {
	    set last $data(lastRow)
	}
	if {$first < 0} {
	    set first 0
	}

	foreach item [lrange $data(itemList) $first $last] {
	    lappend result [lrange $item 0 $data(lastCol)]
	}
	return $result
    }
}

#------------------------------------------------------------------------------
# tablelist::getcolumnsSubCmd
#
# This procedure is invoked to process the tablelist getcolumns subcommand.
#------------------------------------------------------------------------------
proc tablelist::getcolumnsSubCmd {win first last argCount} {
    upvar ::tablelist::ns${win}::data data

    #
    # Get the specified columns from the internal list
    #
    set result {}
    if {$argCount == 1} {
	foreach elem $first {
	    set col [colIndex $win $elem 1]
	    set colResult {}
	    foreach item $data(itemList) {
		lappend colResult [lindex $item $col]
	    }
	    lappend result $colResult
	}

	if {[llength $first] == 1} {
	    return [lindex $result 0]
	} else {
	    return $result
	}
    } else {
	set first [colIndex $win $first 1]
	set last [colIndex $win $last 1]

	if {$first > $last} {
	    return {}
	}

	for {set col $first} {$col <= $last} {incr col} {
	    set colResult {}
	    foreach item $data(itemList) {
		lappend colResult [lindex $item $col]
	    }
	    lappend result $colResult
	}
	return $result
    }
}

#------------------------------------------------------------------------------
# tablelist::getkeysSubCmd
#
# This procedure is invoked to process the tablelist getkeys subcommand.
#------------------------------------------------------------------------------
proc tablelist::getkeysSubCmd {win first last argCount} {
    upvar ::tablelist::ns${win}::data data

    #
    # Get the specified keys from the internal list
    #
    set result {}
    if {$argCount == 1} {
	foreach elem $first {
	    set index [rowIndex $win $elem 0]
	    if {$index >= 0 && $index < $data(itemCount)} {
		set item [lindex $data(itemList) $index]
		lappend result [string range [lindex $item end] 1 end]
	    }
	}

	if {[llength $first] == 1} {
	    return [lindex $result 0]
	} else {
	    return $result
	}
    } else {
	set first [rowIndex $win $first 0]
	set last [rowIndex $win $last 0]

	#
	# Adjust the range to fit within the existing items
	#
	if {$first > $data(lastRow)} {
	    return {}
	}
	if {$last > $data(lastRow)} {
	    set last $data(lastRow)
	}
	if {$first < 0} {
	    set first 0
	}

	foreach item [lrange $data(itemList) $first $last] {
	    lappend result [string range [lindex $item end] 1 end]
	}
	return $result
    }
}

#------------------------------------------------------------------------------
# tablelist::insertSubCmd
#
# This procedure is invoked to process the tablelist insert and insertlist
# subcommands.
#------------------------------------------------------------------------------
proc tablelist::insertSubCmd {win index argList updateListVar} {
    upvar ::tablelist::ns${win}::data data

    if {$data(isDisabled)} {
	return ""
    }

    set argCount [llength $argList]
    if {$argCount == 0} {
	return ""
    }

    if {$index < 0} {
	set index 0
    }

    #
    # Insert the items into the body text widget and into the internal list
    #
    set w $data(body)
    set widgetFont $data(-font)
    set snipStr $data(-snipstring)
    set savedCount $data(itemCount)
    set colWidthsChanged 0
    set idx $index
    set line [expr {$index + 1}]
    foreach item $argList {
	set item [adjustItem $item $data(colCount)]
	if {$data(itemCount) != 0} {
	    $w insert $line.0 \n
	}
	set col 0

	if {$data(hasColTags)} {
	    set insertArgs {}
	    foreach text [strToDispStr $item] \
		    colFont $data(colFontList) \
		    colTags $data(colTagsList) \
		    fmtCmdFlag $data(fmtCmdFlagList) \
		    {pixels alignment} $data(colList) {
		if {$data($col-hide)} {
		    incr col
		    continue
		}

		#
		# Update the column width or clip the element if necessary
		#
		if {$fmtCmdFlag} {
		    set text [uplevel #0 $data($col-formatcommand) \
			      [list [lindex $item $col]]]
		    set text [strToDispStr $text]
		}
		if {$pixels == 0} {		;# convention: dynamic width
		    set textWidth \
			[font measure $colFont -displayof $win $text]
		    if {$textWidth == $data($col-elemWidth)} {
			incr data($col-widestCount)
		    } elseif {$textWidth > $data($col-elemWidth)} {
			set data($col-elemWidth) $textWidth
			set data($col-widestCount) 1
			if {$textWidth > $data($col-width)} {
			    set data($col-width) $textWidth
			    set colWidthsChanged 1
			}
		    }
		} else {
		    incr pixels $data($col-delta)
		    set text [strRangeExt $win $text $colFont \
			      $pixels $alignment $snipStr]
		}

		lappend insertArgs \t$text\t $colTags
		incr col
	    }

	    #
	    # Insert the item into the body text widget
	    #
	    if {[llength $insertArgs] != 0} {
		eval [list $w insert $line.0] $insertArgs
	    }

	} else {
	    set insertStr ""
	    foreach text [strToDispStr $item] \
		    fmtCmdFlag $data(fmtCmdFlagList) \
		    {pixels alignment} $data(colList) {
		if {$data($col-hide)} {
		    incr col
		    continue
		}

		#
		# Update the column width or clip the element if necessary
		#
		if {$fmtCmdFlag} {
		    set text [uplevel #0 $data($col-formatcommand) \
			      [list [lindex $item $col]]]
		    set text [strToDispStr $text]
		}
		if {$pixels == 0} {		;# convention: dynamic width
		    set textWidth \
			[font measure $widgetFont -displayof $win $text]
		    if {$textWidth == $data($col-elemWidth)} {
			incr data($col-widestCount)
		    } elseif {$textWidth > $data($col-elemWidth)} {
			set data($col-elemWidth) $textWidth
			set data($col-widestCount) 1
			if {$textWidth > $data($col-width)} {
			    set data($col-width) $textWidth
			    set colWidthsChanged 1
			}
		    }
		} else {
		    incr pixels $data($col-delta)
		    set text [strRangeExt $win $text $widgetFont \
			      $pixels $alignment $snipStr]
		}

		append insertStr \t$text\t
		incr col
	    }

	    #
	    # Insert the item into the body text widget
	    #
	    $w insert $line.0 $insertStr
	}

	#
	# Insert the item into the list variable if needed
	#
	if {$updateListVar} {
	    trace vdelete ::$data(-listvariable) wu $data(listVarTraceCmd)
	    upvar #0 $data(-listvariable) var
	    if {$idx == $data(itemCount)} {
		lappend var $item		;# this works much faster
	    } else {
		set var [linsert $var $idx $item]
	    }
	    trace variable ::$data(-listvariable) wu $data(listVarTraceCmd)
	}

	#
	# Insert the item into the internal list
	#
	lappend item k[incr data(seqNum)]
	if {$idx == $data(itemCount)} {
	    lappend data(itemList) $item	;# this works much faster
	} else {
	    set data(itemList) [linsert $data(itemList) $idx $item]
	}

	incr idx
	incr line
	incr data(itemCount)
    }
    set data(lastRow) [expr {$data(itemCount) - 1}]

    #
    # Adjust the height of the body text widget if necessary
    #
    if {$data(-height) <= 0} {
	$w configure -height $data(itemCount)
    }

    #
    # Adjust the horizontal view in the body text 
    # widget if the tablelist was previously empty
    #
    if {$savedCount == 0} {
	$w xview moveto [lindex [$data(hdrTxt) xview] 0]
    }

    #
    # Adjust the columns if necessary, adjust the separators,
    # and redraw the stripes in the body text widget
    #
    if {$colWidthsChanged} {
	adjustColumns $win {} 1
    }
    adjustSepsWhenIdle $win
    makeStripesWhenIdle $win

    #
    # Update the indices anchorIdx and activeIdx
    #
    if {$index <= $data(anchorIdx)} {
	incr data(anchorIdx) $argCount
    }
    if {$index <= $data(activeIdx)} {
	incr data(activeIdx) $argCount
	if {$data(activeIdx) > $data(lastRow) && $data(lastRow) >= 0} {
	    set data(activeIdx) $data(lastRow)
	}
    }

    #
    # Update data(editRow) if the entry widget is present
    #
    if {$data(editRow) >= 0} {
	set data(editRow) [lsearch $data(itemList) "* $data(editKey)"]
    }

    return ""
}

#------------------------------------------------------------------------------
# tablelist::insertcolumnsSubCmd
#
# This procedure is invoked to process the tablelist insertcolumns and
# insertcolumnlist subcommands.
#------------------------------------------------------------------------------
proc tablelist::insertcolumnsSubCmd {win colIdx argList} {
    variable alignments
    upvar ::tablelist::ns${win}::data data

    if {$data(isDisabled)} {
	return ""
    }

    #
    # Check the syntax of argList and get the number of columns to be inserted
    #
    set count 0
    set argCount [llength $argList]
    for {set n 0} {$n < $argCount} {incr n} {
	#
	# Check the column width
	#
	format %d [lindex $argList $n]	;# integer check with error message

	#
	# Check whether the column title is present
	#
	if {[incr n] == $argCount} {
	    return -code error "column title missing"
	}

	#
	# Check the column alignment
	#
	set alignment left
	if {[incr n] < $argCount} {
	    set next [lindex $argList $n]
	    if {[catch {format %d $next}] == 0} {	;# integer check
		incr n -1
	    } else {
		mwutil::fullOpt "alignment" $next $alignments
	    }
	}

	incr count
    }

    #
    # Shift the elements of data corresponding to the column
    # indices >= colIdx to the right by count positions
    #
    for {set oldCol $data(lastCol); set newCol [expr {$oldCol + $count}]} \
	{$oldCol >= $colIdx} {incr oldCol -1; incr newCol -1} {
	moveColData $win data data imgs $oldCol $newCol
    }

    #
    # Update the item list
    #
    set emptyStrs {}
    for {set n 0} {$n < $count} {incr n} {
	lappend emptyStrs ""
    }
    set newItemList {}
    foreach item $data(itemList) {
	set item [eval [list linsert $item $colIdx] $emptyStrs]
	lappend newItemList $item
    }
    set data(itemList) $newItemList

    #
    # Update the list variable if present
    #
    condUpdateListVar $win

    #
    # Set up and adjust the columns, and rebuild
    # the lists of the column fonts and tag names
    #
    setupColumns $win \
	[eval [list linsert $data(-columns) [expr {3*$colIdx}]] $argList] 1
    makeColFontAndTagLists $win
    set limit [expr {$colIdx + $count}]
    set cols {}
    for {set col $colIdx} {$col < $limit} {incr col} {
	lappend cols $col
    }
    adjustColumns $win $cols 1

    #
    # Reconfigure the relevant column labels
    #
    for {set col $limit} {$col < $data(colCount)} {incr col} {
	reconfigColLabels $win imgs $col
    }

    #
    # Redisplay the columns
    #
    redisplay $win
    return ""
}

#------------------------------------------------------------------------------
# tablelist::scanSubCmd
#
# This procedure is invoked to process the tablelist scan subcommand.
#------------------------------------------------------------------------------
proc tablelist::scanSubCmd {win opt x y} {
    upvar ::tablelist::ns${win}::data data

    set w $data(body)
    incr x -[winfo x $w]
    incr y -[winfo y $w]

    $w scan $opt $x $y
    $data(hdrTxt) scan $opt $x $y
    return ""
}

#------------------------------------------------------------------------------
# tablelist::seeSubCmd
#
# This procedure is invoked to process the tablelist see subcommand.
#------------------------------------------------------------------------------
proc tablelist::seeSubCmd {win index} {
    upvar ::tablelist::ns${win}::data data

    #
    # Adjust the view in the body text widget
    #
    set w $data(body)
    set fraction [lindex [$w xview] 0]
    $w see [expr {double($index + 1)}]
    $w xview moveto $fraction
    return ""
}

#------------------------------------------------------------------------------
# tablelist::seecellSubCmd
#
# This procedure is invoked to process the tablelist seecell subcommand.
#------------------------------------------------------------------------------
proc tablelist::seecellSubCmd {win row col} {
    upvar ::tablelist::ns${win}::data data

    if {$data($col-hide)} {
	return ""
    }

    set alignment [lindex $data(colList) [expr {2*$col + 1}]]
    set w $data(body)
    findCellTabs $win [expr {$row + 1}] $col tabIdx1 tabIdx2
    set nextIdx [$w index $tabIdx2+1c]

    if {[string compare $alignment right] == 0} {
	$w see $nextIdx

	#
	# Shift the view in the body text widget until the first tab
	# becomes visible but finish the scrolling before the character
	# (\t or \n) at the position nextIdx would become invisible
	#
	if {![isCharVisible $w $tabIdx1]} {
	    while 1 {
		$w xview scroll -1 units
		if {![isCharVisible $w $nextIdx]} {
		    $w xview scroll 1 units
		    break
		} elseif {[isCharVisible $w $tabIdx1]} {
		    break
		}
	    }
	}
    } else {
	$w see $tabIdx1

	#
	# Shift the view in the body text widget until the character
	# (\t or \n) at the position nextIdx becomes visible but finish
	# the scrolling before the first tab would become invisible
	#
	if {![isCharVisible $w $nextIdx]} {
	    while 1 {
		$w xview scroll 1 units
		if {![isCharVisible $w $tabIdx1]} {
		    $w xview scroll -1 units
		    break
		} elseif {[isCharVisible $w $nextIdx]} {
		    break
		}
	    }
	}
    }

    $data(hdrTxt) xview moveto [lindex [$w xview] 0]
    return ""
}

#------------------------------------------------------------------------------
# tablelist::selectionSubCmd
#
# This procedure is invoked to process the tablelist selection subcommand.
#------------------------------------------------------------------------------
proc tablelist::selectionSubCmd {win opt first last} {
    upvar ::tablelist::ns${win}::data data

    if {$data(isDisabled) && [string compare $opt includes] != 0} {
	return ""
    }

    switch $opt {
	anchor {
	    #
	    # Adjust the index to fit within the existing items
	    #
	    if {$first > $data(lastRow)} {
		set first $data(lastRow)
	    }
	    if {$first < 0} {
		set first 0
	    }

	    set data(anchorIdx) $first
	    return ""
	}

	clear {
	    #
	    # Swap the indices if necessary
	    #
	    if {$last < $first} {
		set tmp $first
		set first $last
		set last $tmp
	    }

	    #
	    # Find the selected lines of the body text widget
	    # in the text range specified by the two indices
	    #
	    set w $data(body)
	    set firstTextIdx [expr {$first + 1}].0
	    set lastTextIdx [expr {$last + 1}].end
	    set selRange [$w tag nextrange select $firstTextIdx $lastTextIdx]
	    while {[llength $selRange] != 0} {
		set selStart [lindex $selRange 0]
		set selEnd [lindex $selRange 1]

		$w tag remove select $selStart $selEnd

		#
		# Handle the -(select)background and -(select)foreground cell
		# and column configuration options for each element of the row
		#
		set item [lindex $data(itemList) [expr {int($selStart) - 1}]]
		set key [lindex $item end]
		set textIdx1 $selStart
		for {set col 0} {$col < $data(colCount)} {incr col} {
		    if {$data($col-hide)} {
			continue
		    }

		    set textIdx2 \
			[$w search \t $textIdx1+1c "$selStart lineend"]+1c
		    foreach optTail {background foreground} {
			foreach tag [list $col-select$optTail \
				     $key-select$optTail \
				     $key-$col-select$optTail] {
			    if {[info exists data($tag)]} {
				$w tag remove $tag $textIdx1 $textIdx2
			    }
			}
			foreach tag [list $col-$optTail $key-$optTail \
				     $key-$col-$optTail] {
			    if {[info exists data($tag)]} {
				$w tag add $tag $textIdx1 $textIdx2
			    }
			}
		    }
		    set textIdx1 $textIdx2
		}

		set selRange [$w tag nextrange select $selEnd $lastTextIdx]
	    }

	    return ""
	}

	includes {
	    set tagNames [$data(body) tag names [expr {double($first + 1)}]]
	    if {[lsearch -exact $tagNames select] >= 0} {
		return 1
	    } else {
		return 0
	    }
	}

	set {
	    #
	    # Swap the indices if necessary and adjust
	    # the range to fit within the existing items
	    #
	    if {$last < $first} {
		set tmp $first
		set first $last
		set last $tmp
	    }
	    if {$first < 0} {
		set first 0
	    }
	    if {$last > $data(lastRow)} {
		set last $data(lastRow)
	    }

	    set w $data(body)
	    for {set idx $first; set line [expr {$first + 1}]} \
		{$idx <= $last} {incr idx; incr line} {
		#
		# Nothing to do if the row is already selected
		#
		if {[lsearch -exact [$w tag names $line.0] select] >= 0} {
		    continue
		}

		#
		# Check whether the row is selectable
		#
		set item [lindex $data(itemList) $idx]
		set key [lindex $item end]
		if {[info exists data($key-selectable)]} {     ;# <==> not sel.
		    continue
		}

		$w tag add select $line.0 $line.end

		#
		# Handle the -(select)background and -(select)foreground cell
		# and column configuration options for each element of the row
		#
		set textIdx1 $line.0
		for {set col 0} {$col < $data(colCount)} {incr col} {
		    if {$data($col-hide)} {
			continue
		    }

		    set textIdx2 [$w search \t $textIdx1+1c $line.end]+1c
		    foreach optTail {background foreground} {
			foreach tag [list $col-select$optTail \
				     $key-select$optTail \
				     $key-$col-select$optTail] {
			    if {[info exists data($tag)]} {
				$w tag add $tag $textIdx1 $textIdx2
			    }
			}
			foreach tag [list $col-$optTail $key-$optTail \
				     $key-$col-$optTail] {
			    if {[info exists data($tag)]} {
				$w tag remove $tag $textIdx1 $textIdx2
			    }
			}
		    }
		    set textIdx1 $textIdx2
		}
	    }

	    #
	    # If the selection is exported and there are any selected
	    # rows in the widget then make win the new owner of the
	    # PRIMARY selection and register a callback to be invoked
	    # when it loses ownership of the PRIMARY selection
	    #
	    if {$data(-exportselection) &&
		[llength [$w tag nextrange select 1.0]] != 0} {
		selection own -command \
			[list ::tablelist::lostSelection $win] $win
	    }

	    return ""
	}
    }
}

#------------------------------------------------------------------------------
# tablelist::xviewSubCmd
#
# This procedure is invoked to process the tablelist xview subcommand.
#------------------------------------------------------------------------------
proc tablelist::xviewSubCmd {win argList} {
    variable winSys
    upvar ::tablelist::ns${win}::data data

    switch [llength $argList] {
	0 {
	    #
	    # Command: $win xview
	    #
	    return [$data(hdrTxt) xview]
	}

	1 {
	    #
	    # Command: $win xview units
	    #
	    set units [lindex $argList 0]
	    format %d $units		;# integer check with error message
	    foreach w [list $data(hdrTxt) $data(body)] {
		$w xview moveto 0
		$w xview scroll $units units
	    }
	    if {[string compare $winSys aqua] == 0} {
		update			;# because of a Tk bug on Mac OS X Aqua
	    }
	    return ""
	}

	default {
	    #
	    # Command: $win xview moveto fraction
	    #	       $win xview scroll number what
	    #
	    foreach w [list $data(hdrTxt) $data(body)] {
		eval [list $w xview] $argList
	    }
	    if {[string compare $winSys aqua] == 0} {
		update			;# because of a Tk bug on Mac OS X Aqua
	    }
	    return ""
	}
    }
}

#------------------------------------------------------------------------------
# tablelist::yviewSubCmd
#
# This procedure is invoked to process the tablelist yview subcommand.
#------------------------------------------------------------------------------
proc tablelist::yviewSubCmd {win argList} {
    upvar ::tablelist::ns${win}::data data
    variable winSys

    set w $data(body)
    set argCount [llength $argList]
    switch $argCount {
	0 {
	    #
	    # Command: $win yview
	    #
	    return [$w yview]
	}

	1 {
	    #
	    # Command: $win yview index
	    #
	    set index [rowIndex $win [lindex $argList 0] 0]
	    $w yview $index
	    $w xview moveto [lindex [$w xview] 0]
	    if {[string compare $winSys aqua] == 0} {
		update			;# because of a Tk bug on Mac OS X Aqua
	    }
	    return ""
	}

	default {
	    #
	    # Command: $win yview moveto fraction
	    #	       $win yview scroll number what
	    #
	    set opt [lindex $argList 0]
	    if {[string first $opt moveto] == 0} {
		eval [list $w yview] $argList
	    } elseif {[string first $opt scroll] == 0} {
		if {$argCount != 3} {
		    #
		    # Let Tk report the error
		    #
		    return [eval [list $w yview] $argList]
		}

		set number [lindex $argList 1]
		set number [format %d $number]	;# integer check with error msg
		set what [lindex $argList 2]
		if {[string first $what units] == 0} {
		    $w yview scroll $number units
		} elseif {[string first $what pages] == 0} {
		    if {$number < 0} {
			$w yview scroll $number pages
		    } else {
			#
			# The following loop is needed because "$w yview scroll
			# $number pages" doesn't produce the expected effect.
			#
			for {set n 0} {$n < $number} {incr n} {
			    $w yview scroll 1 pages
			    if {[lindex [$w yview] 1] < 1.0} {
				$w yview scroll -1 units
			    }
			}
		    }
		} else {
		    #
		    # Let Tk report the error
		    #
		    return [eval [list $w yview] $argList]
		}
	    } else {
		return -code error \
		       "unknown option \"$opt\": must be moveto or scroll"
	    }
	    if {[string compare $winSys aqua] == 0} {
		update			;# because of a Tk bug on Mac OS X Aqua
	    }
	    return ""
	}
    }
}

#
# Private callback procedures
# ===========================
#

#------------------------------------------------------------------------------
# tablelist::fetchSelection
#
# This procedure is invoked when the PRIMARY selection is owned by the
# tablelist widget win and someone attempts to retrieve it as a STRING.  It
# returns part or all of the selection, as given by offset and maxChars.  The
# string which is to be (partially) returned is built by joining all of the
# visible elements of the selected rows together with tabs and the rows
# themselves with newlines.
#------------------------------------------------------------------------------
proc tablelist::fetchSelection {win offset maxChars} {
    upvar ::tablelist::ns${win}::data data

    if {!$data(-exportselection)} {
	return ""
    }

    set selection ""
    set gotItem 0
    foreach idx [curselectionSubCmd $win] {
	if {$gotItem} {
	    append selection \n
	}

	set item [lindex $data(itemList) $idx]
	set gotText 0
	for {set col 0} {$col < $data(colCount)} {incr col} {
	    if {$data($col-hide)} {
		continue
	    }

	    set text [lindex $item $col]
	    if {[lindex $data(fmtCmdFlagList) $col]} {
		set text [uplevel #0 $data($col-formatcommand) [list $text]]
	    }

	    if {$gotText} {
		append selection \t
	    }
	    append selection $text

	    set gotText 1
	}

	set gotItem 1
    }

    return [string range $selection $offset [expr {$offset + $maxChars - 1}]]
}

#------------------------------------------------------------------------------
# tablelist::lostSelection
#
# This procedure is invoked when the tablelist widget win loses ownership of
# the PRIMARY selection.  It deselects all items of the widget with the aid of
# the selectionSubCmd procedure if the selection is exported.
#------------------------------------------------------------------------------
proc tablelist::lostSelection win {
    upvar ::tablelist::ns${win}::data data

    if {$data(-exportselection)} {
	selectionSubCmd $win clear 0 $data(lastRow)
    }
}

#------------------------------------------------------------------------------
# tablelist::activeIdxTrace
#
# This procedure is executed whenever the array element data(activeIdx) is
# written.  It moves the "active" tag to the line that displays the active
# element of the widget in its body text child if the latter has the focus.
#------------------------------------------------------------------------------
proc tablelist::activeIdxTrace {win varName index op} {
    upvar ::tablelist::ns${win}::data data

    set w $data(body)
    if {$data(ownsFocus)} {
	set line [expr {$data(oldActiveIdx) + 1}]
	$w tag remove active $line.0 $line.end

	set line [expr {$data(activeIdx) + 1}]
	$w tag add active $line.0 $line.end
    }

    set data(oldActiveIdx) $data(activeIdx)
}

#------------------------------------------------------------------------------
# tablelist::listVarTrace
#
# This procedure is executed whenever the global variable specified by varName
# is written or unset.  It makes sure that the contents of the widget will be
# synchronized with the value of the variable at idle time, and that the
# variable is recreated if it was unset.
#------------------------------------------------------------------------------
proc tablelist::listVarTrace {win varName index op} {
    upvar ::tablelist::ns${win}::data data

    switch $op {
	w {
	    if {![info exists data(syncId)]} {
		#
		# Arrange for the contents of the widget to be synchronized
		# with the value of the variable ::$varName at idle time
		#
		set data(syncId) [after idle [list tablelist::synchronize $win]]

		#
		# Cancel the execution of all delayed redisplay
		# commands, to make sure that the synchronize command
		# will be invoked first; the latter will then schedule
		# a redisplay command for execution at idle time
		#
		if {[info exists data(redispId)]} {
		    after cancel $data(redispId)
		}
	    }
	}

	u {
	    #
	    # Recreate the variable ::$varName by setting it according to
	    # the value of data(itemList), and set the trace on it again
	    #
	    if {[string compare $index ""] != 0} {
		set varName ${varName}($index)
	    }
	    set ::$varName {}
	    foreach item $data(itemList) {
		lappend ::$varName [lrange $item 0 $data(lastCol)]
	    }
	    trace variable ::$varName wu $data(listVarTraceCmd)
	}
    }
}

#
# Private procedures used in bindings
# ===================================
#
# See the module "tablelistBind.tcl".
#

#
# Private utility procedures
# ==========================
#
# See the module "tablelistUtil.tcl"
#

