#
# Selectionbox
# ----------------------------------------------------------------------
# Implements a selection box composed of a scrolled list of items and
# a selection entry field.  The user may choose any of the items displayed
# in the scrolled list of alternatives and the selection field will be
# filled with the choice.  The user is also free to enter a new value in
# the selection entry field.  Both the list and entry areas have labels.
# A child site is also provided in which the user may create other widgets
# to be used in conjunction with the selection box.
# 
# ----------------------------------------------------------------------
#  AUTHOR: Mark L. Ulferts              EMAIL: mulferts@austin.dsccc.com
#
#  @(#) $Id: selectionbox.itk,v 1.2 2001/08/07 19:56:48 smithc Exp $
# ----------------------------------------------------------------------
#            Copyright (c) 1995 DSC Technologies Corporation
# ======================================================================
# Permission to use, copy, modify, distribute and license this software 
# and its documentation for any purpose, and without fee or written 
# agreement with DSC, is hereby granted, provided that the above copyright 
# notice appears in all copies and that both the copyright notice and 
# warranty disclaimer below appear in supporting documentation, and that 
# the names of DSC Technologies Corporation or DSC Communications 
# Corporation not be used in advertising or publicity pertaining to the 
# software without specific, written prior permission.
# 
# DSC DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING 
# ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, AND NON-
# INFRINGEMENT. THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, AND THE
# AUTHORS AND DISTRIBUTORS HAVE NO OBLIGATION TO PROVIDE MAINTENANCE, 
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. IN NO EVENT SHALL 
# DSC BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR 
# ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, 
# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTUOUS ACTION,
# ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS 
# SOFTWARE.
# ======================================================================

#
# Usual options.
#
itk::usual Selectionbox {
    keep -activebackground -activerelief -background -borderwidth -cursor \
	 -elementborderwidth -foreground -highlightcolor -highlightthickness \
	 -insertbackground -insertborderwidth -insertofftime -insertontime \
	 -insertwidth -jump -labelfont -selectbackground -selectborderwidth \
	 -selectforeground -textbackground -textfont -troughcolor
}

# ------------------------------------------------------------------
#                            SELECTIONBOX
# ------------------------------------------------------------------
itcl::class iwidgets::Selectionbox {
    inherit itk::Widget

    constructor {args} {}
    destructor {}

    itk_option define -childsitepos childSitePos Position center
    itk_option define -margin margin Margin 7
    itk_option define -itemson itemsOn ItemsOn true
    itk_option define -selectionon selectionOn SelectionOn true
    itk_option define -width width Width 260
    itk_option define -height height Height 320

    public method childsite {}
    public method get {}
    public method curselection {}
    public method clear {component}
    public method insert {component index args}
    public method delete {first {last {}}}
    public method size {}
    public method scan {option args}
    public method nearest {y}
    public method index {index}
    public method selection {option args}
    public method selectitem {}

    private method _packComponents {{when later}}

    private variable _repacking {}     ;# non-null => _packComponents pending
}

#
# Provide a lowercased access method for the Selectionbox class.
# 
proc ::iwidgets::selectionbox {pathName args} {
    uplevel ::iwidgets::Selectionbox $pathName $args
}

#
# Use option database to override default resources of base classes.
#
option add *Selectionbox.itemsLabel Items widgetDefault
option add *Selectionbox.selectionLabel Selection widgetDefault
option add *Selectionbox.width 260 widgetDefault
option add *Selectionbox.height 320 widgetDefault

# ------------------------------------------------------------------
#                        CONSTRUCTOR
# ------------------------------------------------------------------
itcl::body iwidgets::Selectionbox::constructor {args} {
    #
    # Set the borderwidth to zero and add width and height options
    # back to the hull.
    #
    component hull configure -borderwidth 0
    itk_option add hull.width hull.height
    
    #
    # Create the child site widget.
    #
    itk_component add -protected sbchildsite {
	frame $itk_interior.sbchildsite
    } 
    
    #
    # Create the items list.
    #
    itk_component add items {
	iwidgets::Scrolledlistbox $itk_interior.items -selectmode single \
		-visibleitems 20x10 -labelpos nw -vscrollmode static \
		-hscrollmode none 
    } {
	usual
	keep -dblclickcommand -exportselection 
	
	rename -labeltext -itemslabel itemsLabel Text
	rename -selectioncommand -itemscommand itemsCommand Command
    }
    configure -itemscommand [itcl::code $this selectitem]
    
    #
    # Create the selection entry.
    #
    itk_component add selection {
	iwidgets::Entryfield $itk_interior.selection -labelpos nw
    } {
	usual

	keep -exportselection 
	
	rename -labeltext -selectionlabel selectionLabel Text
	rename -command -selectioncommand selectionCommand Command
    }
    
    #
    # Set the interior to the childsite for derived classes.
    #
    set itk_interior $itk_component(sbchildsite)

    #
    # Initialize the widget based on the command line options.
    #
    eval itk_initialize $args

    # 
    # When idle, pack the components.
    #
    _packComponents
}   

# ------------------------------------------------------------------
#                           DESTRUCTOR
# ------------------------------------------------------------------
itcl::body iwidgets::Selectionbox::destructor {} {
    if {$_repacking != ""} {after cancel $_repacking}
}

# ------------------------------------------------------------------
#                             OPTIONS
# ------------------------------------------------------------------

# ------------------------------------------------------------------
# OPTION: -childsitepos
#
# Specifies the position of the child site in the selection box.
# ------------------------------------------------------------------
itcl::configbody iwidgets::Selectionbox::childsitepos {
    _packComponents 
}

# ------------------------------------------------------------------
# OPTION: -margin
#
# Specifies distance between the items list and selection entry.
# ------------------------------------------------------------------
itcl::configbody iwidgets::Selectionbox::margin {
    _packComponents 
}

# ------------------------------------------------------------------
# OPTION: -itemson
#
# Specifies whether or not to display the items list.
# ------------------------------------------------------------------
itcl::configbody iwidgets::Selectionbox::itemson {
    _packComponents 
}

# ------------------------------------------------------------------
# OPTION: -selectionon
#
# Specifies whether or not to display the selection entry widget.
# ------------------------------------------------------------------
itcl::configbody iwidgets::Selectionbox::selectionon {
    _packComponents
}

# ------------------------------------------------------------------
# OPTION: -width
#
# Specifies the width of the hull.  The value may be specified in 
# any of the forms acceptable to Tk_GetPixels.  A value of zero 
# causes the width to be adjusted to the required value based on 
# the size requests of the components.  Otherwise, the width is 
# fixed.
# ------------------------------------------------------------------
itcl::configbody iwidgets::Selectionbox::width {
    #
    # The width option was added to the hull in the constructor.
    # So, any width value given is passed automatically to the
    # hull.  All we have to do is play with the propagation.
    #
    if {$itk_option(-width) != 0} {
	set propagate 0
    } else {
	set propagate 1
    }

    #
    # Due to a bug in the tk4.2 grid, we have to check the 
    # propagation before setting it.  Setting it to the same
    # value it already is will cause it to toggle.
    #
    if {[grid propagate $itk_component(hull)] != $propagate} {
	grid propagate $itk_component(hull) $propagate
    }
}

# ------------------------------------------------------------------
# OPTION: -height
#
# Specifies the height of the hull.  The value may be specified in 
# any of the forms acceptable to Tk_GetPixels.  A value of zero 
# causes the height to be adjusted to the required value based on 
# the size requests of the components. Otherwise, the height is 
# fixed.
# ------------------------------------------------------------------
itcl::configbody iwidgets::Selectionbox::height {
    #
    # The height option was added to the hull in the constructor.
    # So, any height value given is passed automatically to the
    # hull.  All we have to do is play with the propagation.
    #
    if {$itk_option(-height) != 0} {
	set propagate 0
    } else {
	set propagate 1
    }

    #
    # Due to a bug in the tk4.2 grid, we have to check the 
    # propagation before setting it.  Setting it to the same
    # value it already is will cause it to toggle.
    #
    if {[grid propagate $itk_component(hull)] != $propagate} {
	grid propagate $itk_component(hull) $propagate
    }
}

# ------------------------------------------------------------------
#                            METHODS
# ------------------------------------------------------------------

# ------------------------------------------------------------------
# METHOD: childsite
#
# Returns the path name of the child site widget.
# ------------------------------------------------------------------
itcl::body iwidgets::Selectionbox::childsite {} {
    return $itk_component(sbchildsite)
}

# ------------------------------------------------------------------
# METHOD: get 
#
# Returns the current selection.
# ------------------------------------------------------------------
itcl::body iwidgets::Selectionbox::get {} {
    return [$itk_component(selection) get]
}

# ------------------------------------------------------------------
# METHOD: curselection
#
# Returns the current selection index.
# ------------------------------------------------------------------
itcl::body iwidgets::Selectionbox::curselection {} {
    return [$itk_component(items) curselection]
}

# ------------------------------------------------------------------
# METHOD: clear component
#
# Delete the contents of either the selection entry widget or items
# list.
# ------------------------------------------------------------------
itcl::body iwidgets::Selectionbox::clear {component} {
    switch $component {
	selection {
	    $itk_component(selection) clear
	}
	
	items {
	    delete 0 end
	}
	
	default {
	    error "bad clear argument \"$component\": should be\
		   selection or items"
	}
    }
}

# ------------------------------------------------------------------
# METHOD: insert component index args
#
# Insert element(s) into either the selection or items list widget.
# ------------------------------------------------------------------
itcl::body iwidgets::Selectionbox::insert {component index args} {
    switch $component {
	selection {
	    eval $itk_component(selection) insert $index $args
	}
	
	items {
	    eval $itk_component(items) insert $index $args
	}
	
	default {
	    error "bad insert argument \"$component\": should be\
		   selection or items"
	}
    }
}

# ------------------------------------------------------------------
# METHOD: delete first ?last?
#
# Delete one or more elements from the items list box.  The default 
# is to delete by indexed range. If an item is to be removed by name, 
# it must be preceeded by the keyword "item". Only index numbers can 
# be used to delete a range of items. 
# ------------------------------------------------------------------
itcl::body iwidgets::Selectionbox::delete {first {last {}}} {
    set first [index $first]
    
    if {$last != {}} {
	set last [index $last]
    } else {
	set last $first
    }
    
    if {$first <= $last} {
	eval $itk_component(items) delete $first $last
    } else {
	error "first index must not be greater than second"
    }
}

# ------------------------------------------------------------------
# METHOD: size 
#
# Returns a decimal string indicating the total number of elements 
# in the items list.
# ------------------------------------------------------------------
itcl::body iwidgets::Selectionbox::size {} {
    return [$itk_component(items) size]
}

# ------------------------------------------------------------------
# METHOD: scan option args 
#
# Implements scanning on items list.
# ------------------------------------------------------------------
itcl::body iwidgets::Selectionbox::scan {option args} {
    eval $itk_component(items) scan $option $args
}

# ------------------------------------------------------------------
# METHOD: nearest y
#
# Returns the index to the nearest listbox item given a y coordinate.
# ------------------------------------------------------------------
itcl::body iwidgets::Selectionbox::nearest {y} {
    return [$itk_component(items) nearest $y]
}

# ------------------------------------------------------------------
# METHOD: index index
#
# Returns the decimal string giving the integer index corresponding 
# to index.
# ------------------------------------------------------------------
itcl::body iwidgets::Selectionbox::index {index} {
    return [$itk_component(items) index $index]
}

# ------------------------------------------------------------------
# METHOD: selection option args
#
# Adjusts the selection within the items list.
# ------------------------------------------------------------------
itcl::body iwidgets::Selectionbox::selection {option args} {
    eval $itk_component(items) selection $option $args

    selectitem
}

# ------------------------------------------------------------------
# METHOD: selectitem
#
# Replace the selection entry field contents with the currently 
# selected items value.
# ------------------------------------------------------------------
itcl::body iwidgets::Selectionbox::selectitem {} {
    $itk_component(selection) clear
    set numSelected [$itk_component(items) selecteditemcount]

    if {$numSelected == 1} {
	$itk_component(selection) insert end \
	    [$itk_component(items) getcurselection]
    } elseif {$numSelected > 1} {
	$itk_component(selection) insert end \
	    [lindex [$itk_component(items) getcurselection] 0]
    }

    $itk_component(selection) icursor end
}

# ------------------------------------------------------------------
# PRIVATE METHOD: _packComponents ?when?
#
# Pack the selection, items, and child site widgets based on options.
# If "when" is "now", the change is applied immediately.  If it is 
# "later" or it is not specified, then the change is applied later, 
# when the application is idle.
# ------------------------------------------------------------------
itcl::body iwidgets::Selectionbox::_packComponents {{when later}} {
    if {$when == "later"} {
	if {$_repacking == ""} {
	    set _repacking [after idle [itcl::code $this _packComponents now]]
	}
	return
    } elseif {$when != "now"} {
	error "bad option \"$when\": should be now or later"
    }

    set _repacking ""

    set parent [winfo parent $itk_component(sbchildsite)]
    set margin [winfo pixels $itk_component(hull) $itk_option(-margin)]

    switch $itk_option(-childsitepos) {
	n {
	    grid $itk_component(sbchildsite) -row 0 -column 0 \
		    -sticky nsew -rowspan 1
	    grid $itk_component(items) -row 1 -column 0 -sticky nsew
	    grid $itk_component(selection) -row 3 -column 0 -sticky ew
	    
	    grid rowconfigure $parent 0 -weight 0 -minsize 0
	    grid rowconfigure $parent 1 -weight 1 -minsize 0
	    grid rowconfigure $parent 2 -weight 0 -minsize $margin
	    grid rowconfigure $parent 3 -weight 0 -minsize 0

	    grid columnconfigure $parent 0 -weight 1 -minsize 0
	    grid columnconfigure $parent 1 -weight 0 -minsize 0
	}
	
	w {
	    grid $itk_component(sbchildsite) -row 0 -column 0 \
		    -sticky nsew -rowspan 3
	    grid $itk_component(items) -row 0 -column 1 -sticky nsew
	    grid $itk_component(selection) -row 2 -column 1 -sticky ew
	    
	    grid rowconfigure $parent 0 -weight 1 -minsize 0
	    grid rowconfigure $parent 1 -weight 0 -minsize $margin
	    grid rowconfigure $parent 2 -weight 0 -minsize 0
	    grid rowconfigure $parent 3 -weight 0 -minsize 0

	    grid columnconfigure $parent 0 -weight 0 -minsize 0
	    grid columnconfigure $parent 1 -weight 1 -minsize 0
	}
	
	s {
	    grid $itk_component(items) -row 0 -column 0 -sticky nsew
	    grid $itk_component(selection) -row 2 -column 0 -sticky ew
	    grid $itk_component(sbchildsite) -row 3 -column 0 \
		    -sticky nsew -rowspan 1
	    
	    grid rowconfigure $parent 0 -weight 1 -minsize 0
	    grid rowconfigure $parent 1 -weight 0 -minsize $margin
	    grid rowconfigure $parent 2 -weight 0 -minsize 0
	    grid rowconfigure $parent 3 -weight 0 -minsize 0

	    grid columnconfigure $parent 0 -weight 1 -minsize 0
	    grid columnconfigure $parent 1 -weight 0 -minsize 0
	}
	
	e {
	    grid $itk_component(items) -row 0 -column 0 -sticky nsew
	    grid $itk_component(selection) -row 2 -column 0 -sticky ew
	    grid $itk_component(sbchildsite) -row 0 -column 1 \
		    -sticky nsew -rowspan 3
	    
	    grid rowconfigure $parent 0 -weight 1 -minsize 0
	    grid rowconfigure $parent 1 -weight 0 -minsize $margin
	    grid rowconfigure $parent 2 -weight 0 -minsize 0
	    grid rowconfigure $parent 3 -weight 0 -minsize 0

	    grid columnconfigure $parent 0 -weight 1 -minsize 0
	    grid columnconfigure $parent 1 -weight 0 -minsize 0
	}
	
	center {
	    grid $itk_component(items) -row 0 -column 0 -sticky nsew
	    grid $itk_component(sbchildsite) -row 1 -column 0 \
		    -sticky nsew -rowspan 1
	    grid $itk_component(selection) -row 3 -column 0 -sticky ew
	    
	    grid rowconfigure $parent 0 -weight 1 -minsize 0
	    grid rowconfigure $parent 1 -weight 0 -minsize 0
	    grid rowconfigure $parent 2 -weight 0 -minsize $margin
	    grid rowconfigure $parent 3 -weight 0 -minsize 0

	    grid columnconfigure $parent 0 -weight 1 -minsize 0
	    grid columnconfigure $parent 1 -weight 0 -minsize 0
	}
	
	default {
	    error "bad childsitepos option \"$itk_option(-childsitepos)\":\
		   should be n, e, s, w, or center"
	}
    }
    
    if {$itk_option(-itemson)} {
    } else {
	grid forget $itk_component(items)
    }
    
    if {$itk_option(-selectionon)} {
    } else {
	grid forget $itk_component(selection)
    }
    
    raise $itk_component(sbchildsite)
}

