#
# Combobox
# ----------------------------------------------------------------------
# Implements a Combobox widget. A Combobox has 2 basic styles: simple and
# dropdown. Dropdowns display an entry field with an arrow button to the 
# right of it. When the arrow button is pressed a selectable list of items 
# is popped up. A simple Combobox displays an entry field and a listbox 
# just beneath it which is always displayed. In both types, if the user 
# selects an item in the listbox, the contents of the entry field are 
# replaced with the text from the selected item. If the Combobox is 
# editable, the user can type in the entry field and when <Return> is pressed
# the item will be inserted into the list.
#
# WISH LIST:
#   This section lists possible future enhancements.  
#
#     Combobox 1.x:
#         - convert bindings to bindtags.
#         - determine clean way to have a state for listbox/canvas.
#         - add completion functionality when typing in the entry.
#         - add grab option: global, local, none.
#
# ----------------------------------------------------------------------
#  AUTHOR: John S. Sigler               EMAIL: jsigler@spd.dsccc.com
#                                              sigler@onramp.net
#  @(#) $Id: combobox.itk,v 1.1.1.1 1999/03/31 20:39:40 damon Exp $
# ----------------------------------------------------------------------
#                   Copyright (c) 1995  John S. Sigler
# ======================================================================
# Permission is hereby granted, without written agreement and without
# license or royalty fees, to use, copy, modify, and distribute this
# software and its documentation for any purpose, provided that the
# above copyright notice and the following two paragraphs appear in
# all copies of this software.
# 
# IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
# DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 
# ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN 
# IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 
# DAMAGE.
#
# THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, 
# BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
# FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
# ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
# PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
# ======================================================================

#
# Default resources.
#
option add *Combobox.borderWidth 2 widgetDefault
option add *Combobox.labelPos w widgetDefault
option add *Combobox.listHeight 150 widgetDefault
option add *Combobox.hscrollMode dynamic widgetDefault
option add *Combobox.vscrollMode dynamic widgetDefault

#
# Usual options.
#
itk::usual Combobox {
    keep -background -borderwidth -cursor -foreground -highlightcolor \
	 -highlightthickness -insertbackground -insertborderwidth \
	 -insertofftime -insertontime -insertwidth -labelfont -popupcursor \
	 -selectbackground -selectborderwidth -selectforeground \
	 -textbackground -textfont
}

# ------------------------------------------------------------------
#                            COMBOBOX
# ------------------------------------------------------------------
class iwidgets::Combobox {
    inherit iwidgets::Entryfield
    
    constructor {args} {}
    destructor {}

    itk_option define -autoclear autoClear AutoClear true
    itk_option define -arrowrelief arrowRelief Relief raised
    itk_option define -dropdown dropdown Dropdown true
    itk_option define -editable editable Editable true
    itk_option define -fliparrow flipArrow FlipArrow false
    itk_option define -items items Items ""
    itk_option define -labelpos labelPos Position w
    itk_option define -listheight listHeight Height 150
    itk_option define -margin margin Margin 1
    itk_option define -popupcursor popupCursor Cursor arrow
    itk_option define -selectioncommand selectionCommand SelectionCommand {}
    itk_option define -unique unique Unique true

    public method clear {{component all}}
    public method curselection {}
    public method delete {component first {last {}}}
    public method get {{index {}}}
    public method getcurselection {}
    public method insert {component index args}
    public method invoke {}
    public method justify {direction}
    public method see {index}
    public method selection {option first {last {}}}
    public method size {}
    public method sort {{mode ascending}}
    public method xview {args}
    public method yview {args}

    protected method _addToList {}
    protected method _createComponents {}
    protected method _deleteList {first {last {}}}
    protected method _deleteText {first {last {}}}
    protected method _doLayout {{when later}}
    protected method _drawArrow {{mode normal}}
    protected method _dropdownBtnRelease {{window {}} {x 1} {y 1}}
    protected method _ignoreNextBtnRelease {ignore}
    protected method _insertText {index string}
    protected method _next {}
    protected method _packArrow {labelpos}
    protected method _packComponents {{when later}}
    protected method _positionList {}
    protected method _postList {}
    protected method _previous {}
    protected method _resizeArrow {}
    protected method _selectCmd {}
    protected method _toggleList {}
    protected method _unpostList {}
    protected method _commonBindings {}
    protected method _dropdownBindings {}
    protected method _simpleBindings {}
    protected method _listShowing {{val ""}}

    private method _slbListbox {}

    private   variable _currItem {};           ;# current selected item.
    private   variable _ignoreRelease true     ;# next button release ignored.
    private   variable _isPosted false;        ;# is the dropdown popped up.
    private   variable _repacking {}    ;# non-null => _packComponents pending.
    private   common _listShowing
}    

#
# Provide a lowercase access method for the Combobox class.
# 
proc ::iwidgets::combobox {pathName args} {
    uplevel ::iwidgets::Combobox $pathName $args
}

# ------------------------------------------------------------------
#                       CONSTRUCTOR
# ------------------------------------------------------------------
body iwidgets::Combobox::constructor {args} {
    set _listShowing($this) 0

    # combobox is different as all components are created 
    # after determining what the dropdown style is...

    # configure args
    eval itk_initialize $args
    
    # create components that are dependent on options 
    # (Scrolledlistbox, arrow button) and pack them.
    _doLayout
}
    
# ------------------------------------------------------------------
#                          DESTRUCTOR
# ------------------------------------------------------------------
body iwidgets::Combobox::destructor {} {
    # catch any repacking that may be waiting for idle time
    if {$_repacking != ""} {
	after cancel $_repacking
    }
}
    
# ================================================================
#                           OPTIONS
# ================================================================

# --------------------------------------------------------------------
# OPTION:  -autoclear
#
# Specifies wheather or not to clear the entry field as items are
# added to the list.  The default is true.
# --------------------------------------------------------------------
configbody iwidgets::Combobox::autoclear {}

# --------------------------------------------------------------------
# OPTION:  -arrowrelief
#
# Relief style used on the arrow button.
# --------------------------------------------------------------------
configbody iwidgets::Combobox::arrowrelief {}

# --------------------------------------------------------------------
# OPTION:  -dropdown  
#
# Boolean which determines the Combobox style: dropdown or simple.
# Because the two style's lists reside in different toplevel widgets
# this is more complicated than it should be.
# --------------------------------------------------------------------
configbody iwidgets::Combobox::dropdown {
    if {$itk_option(-dropdown)} {
	if {[winfo exists $itk_interior.list]} {
	    destroy $itk_component(list)
	    _doLayout
	}
    } else {
	if {[winfo exists $itk_interior.popup.list]} {
	    destroy $itk_component(margin)
	    destroy $itk_component(arrowBtn)
	    destroy $itk_component(popup)  ;# this deletes the list too
	    _doLayout
	}
    }
}

# --------------------------------------------------------------------
# OPTION: -editable  
#
# Boolean which allows/disallows user input to the entry field area.
# --------------------------------------------------------------------
configbody iwidgets::Combobox::editable {
    if {$itk_option(-editable)} {
	configure -state normal -command [code $this _addToList]
	bind $itk_component(entry) <1> ""
    } else {
	configure -state disabled
	bind $itk_component(entry) <1> [code $this _toggleList]
    }
}

# --------------------------------------------------------------------
# OPTION: -fliparrow
#
# Boolean which causes dropdown comboboxes to have their arrow point
# up when the listbox is displayed.
# --------------------------------------------------------------------
configbody iwidgets::Combobox::fliparrow {
    # boolean error check
    if {$itk_option(-fliparrow)} {
    }
}

# --------------------------------------------------------------------
# OPTION: -items  
#
# Set/get the current list of items in the listbox.
# --------------------------------------------------------------------
configbody iwidgets::Combobox::items {
    # clear out what was in entry field before
    if {$itk_option(-autoclear)} {
	delete entry 0 end
    }

    if {[winfo exists $itk_interior.popup.list]} {
	_drawArrow normal
    }
}

# --------------------------------------------------------------------
# OPTION: -labelpos
#
# Labelpos. Overrides the labelpos of the labeledwidget. This doesn't
# work as positionalLabel seems to get called after this routine is called!
# --------------------------------------------------------------------
configbody iwidgets::Combobox::labelpos {
    iwidgets::Labeledwidget::_positionLabel 

    switch -- $itk_option(-labelpos) {
	e -
	w {
	    pack configure $itk_component(label) -anchor n
	}
    }
}

# --------------------------------------------------------------------
# OPTION: -listheight  
#
# Listbox height in pixels. (Need to integrate the scrolledlistbox
# -visibleitems option here - at least for simple listbox.)
# --------------------------------------------------------------------
configbody iwidgets::Combobox::listheight {}

# --------------------------------------------------------------------
# OPTION:  -margin
#
# Spacer between the entry field and arrow button of dropdown style
# Comboboxes.
# --------------------------------------------------------------------
configbody iwidgets::Combobox::margin {}

# --------------------------------------------------------------------
# OPTION:  -popupcursor
#
# Set the cursor for the popup list.
# --------------------------------------------------------------------
configbody iwidgets::Combobox::popupcursor {}

# --------------------------------------------------------------------
# OPTION:  -selectioncommand
#
# Defines the proc to be called when an item is selected in the list.
# --------------------------------------------------------------------
configbody iwidgets::Combobox::selectioncommand {}

# --------------------------------------------------------------------
# OPTION: -unique  
#
# Boolean which disallows/allows adding duplicate items to the listbox.
# --------------------------------------------------------------------
configbody iwidgets::Combobox::unique {
    # boolean error check
    if {$itk_option(-unique)} {
    }
}

# =================================================================
#                            METHODS
# =================================================================

# ------------------------------------------------------
#  PUBLIC METHOD: clear ?component?
#
#  Remove all elements from the listbox, all contents
#  from the entry component, or both (if all).
#
# ------------------------------------------------------
body iwidgets::Combobox::clear {{component all}} {
    switch -- $component {
	entry {
	    iwidgets::Entryfield::clear
	}
	list {
	    delete list 0 end
	}
	all {
	    delete list 0 end
	    iwidgets::Entryfield::clear
	}
	default {
	    error "bad Combobox component \"$component\": must be entry, list,\
or all."
	}
    }
    return
}

# ------------------------------------------------------
# PUBLIC METHOD: curselection
#
# Return the current selection index.
#
# ------------------------------------------------------
body iwidgets::Combobox::curselection {} {
    return [$itk_component(list) curselection]
}

# ------------------------------------------------------
# PUBLIC METHOD: delete component first ?last?
#
# Delete an item or items from the listbox OR delete
# text from the entry field. First argument determines
# which component deletion occurs in - valid values are
# entry or list.
#
# ------------------------------------------------------
body iwidgets::Combobox::delete {component first {last {}}} {
    switch -- $component {
	entry {
	    iwidgets::Entryfield::delete $first $last
	}
	list {
	    _deleteList $first $last
	}
	default {
           error "bad Combobox component \"$component\": must be entry or list."
	}
    }
}

# ------------------------------------------------------
# PUBLIC METHOD: get ?index?
#
#
# Retrieve entry contents if no args OR use args as list 
# index and retrieve list item at index .
#
# ------------------------------------------------------
body iwidgets::Combobox::get {{index {}}} {
    # no args means to get the current text in the entry field area
    if {$index == {}} {
	iwidgets::Entryfield::get
    } else {
	eval $itk_component(list) get $index
    }
}

# ------------------------------------------------------
# PUBLIC METHOD: getcurselection
#
# Return currently selected item in the listbox. Shortcut
# version of get curselection command combination.
#
# ------------------------------------------------------
body iwidgets::Combobox::getcurselection {} {
    return [$itk_component(list) getcurselection]
}

# ------------------------------------------------------------------
# PUBLIC METHOD: ivoke
#
# Pops up or down a dropdown combobox.
# 
# ------------------------------------------------------------------
body iwidgets::Combobox::invoke {} {
    if {$itk_option(-dropdown)} {
	return [_toggleList]
    }
    return 
}

# ------------------------------------------------------------
# PUBLIC METHOD: insert comonent index string ?string ...?
#
# Insert an item into the listbox OR text into the entry area.
# Valid component names are entry or list.
#
# ------------------------------------------------------------
body iwidgets::Combobox::insert {component index args} {
    if {$args == {}} {
	error "no value given for parameter \"string\" in function\
\"Combobox::insert\""
    } 

    switch -- $component {
	entry {
	    if { [llength $args] > 1} {
		error "called function \"Combobox::insert entry\" with too\
many arguments"
	    } else {
		eval iwidgets::Entryfield::insert $index $args
	    }
	}
	list {
	    configure -items [eval linsert {$itk_option(-items)} $index $args]
	}
	default {
           error "bad Combobox component \"$component\": must be entry or list."
	}
    }
}

# ------------------------------------------------------
# PUBLIC METHOD: justify direction
#
# Wrapper for justifying the listbox items in one of
# 4 directions:  top, bottom, left, or right.
#
# ------------------------------------------------------
body iwidgets::Combobox::justify {direction} {
    return [$itk_component(list) justify $direction]
}

# ------------------------------------------------------------------
# PUBLIC METHOD: see index
#
# Adjusts the view such that the element given by index is visible.
# ------------------------------------------------------------------
body iwidgets::Combobox::see {index} {
    return [$itk_component(list) see $index]
}

# ------------------------------------------------------------------
# PUBLIC METHOD: selection option first ?last?
#
# Adjusts the selection within the listbox and changes the contents
# of the entry component to be the value of the selected list item.
# ------------------------------------------------------------------
body iwidgets::Combobox::selection {option first {last {}}} {
    # thin wrap
    set rtn [eval $itk_component(list) selection $option $first $last]
    set _currItem $first

    # combobox additions
    set theText [getcurselection]
    if {$theText != [$itk_component(entry) get]} {
	clear entry
	if {$theText != ""} {
	    insert entry 0 $theText
	}
    }
    return $rtn
}

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

# ------------------------------------------------------
# PUBLIC METHOD: sort ?mode?
#
# Sort the current list in either "ascending" or "descending" order.
#
#   jss: how should i handle selected items?
#
# ------------------------------------------------------
body iwidgets::Combobox::sort {{mode ascending}} {
    return [$itk_component(list) sort $mode]
}


# ------------------------------------------------------------------
# PUBLIC METHOD: xview ?arg arg ...?
#
# Change or query the vertical position of the text in the list box.
# ------------------------------------------------------------------
body iwidgets::Combobox::xview {args} {
    return [eval $itk_component(list) xview $args]
}

# ------------------------------------------------------------------
# PUBLIC METHOD: yview ?arg arg ...?
#
# Change or query the horizontal position of the text in the list box.
# ------------------------------------------------------------------
body iwidgets::Combobox::yview {args} {
    return [eval $itk_component(list) yview $args]
}

# ------------------------------------------------------
# PROTECTED METHOD: _addToList
#
# Add the current item in the entry to the listbox.
#
# ------------------------------------------------------
body iwidgets::Combobox::_addToList {} {
    set input [get]
    if {$input != ""} {
	if {$itk_option(-unique)} {
	    # if item is already in list, select it and exit
	    set item [lsearch -exact $itk_option(-items) $input]
	    if {$item != -1} {
		selection clear 0 end
		if {$item != {}} {
		    selection set $item $item
		    set _currItem $item
		}
		return
	    }
	}
	# add the item to end of list and clear the text area
	insert list end $input

	if {$itk_option(-autoclear)} {
	    delete entry 0 end
	}
    }
}

# ------------------------------------------------------
# PROTECTED METHOD:   _createComponents
#
# Create deferred combobox components and add bindings.
#
# ------------------------------------------------------
body iwidgets::Combobox::_createComponents {} {
    if {$itk_option(-dropdown)} {
	# --- build a dropdown combobox ---

	# make the arrow and margin childsite's be on the right hand side
	configure -childsitepos e

	# add spacer between arrow and the entry component
	itk_component add margin {
	    frame $itk_interior.margin -width $itk_option(-margin)
	} {
	    rename -width -margin margin Margin
	}
	
	# arrow button to popup the list
	itk_component add arrowBtn {
	    canvas $itk_interior.arrowBtn -borderwidth 2 \
		-width 16 -height 16
	} {
	    keep -background -borderwidth -cursor  \
		-highlightcolor -highlightthickness
	    rename -relief -arrowrelief arrowRelief Relief
	    rename -highlightbackground -background background Background
	}
	
	# popup list container
	itk_component add popup {
	    toplevel $itk_interior.popup
	} {
	    keep -background -cursor
	}
	wm withdraw $itk_interior.popup
	wm overrideredirect $itk_interior.popup 1
	
	# the listbox
	itk_component add list {
	    iwidgets::Scrolledlistbox $itk_interior.popup.list -exportselection no \
		-vscrollmode dynamic -hscrollmode dynamic 
	} {
	    keep -background -borderwidth -cursor -foreground \
		-highlightcolor -highlightthickness \
		-hscrollmode -items -selectbackground \
		-selectborderwidth -selectforeground -textbackground \
		-textfont -vscrollmode
	    rename -height -listheight listHeight Height
	    rename -cursor -popupcursor popupCursor Cursor
	}
	# mode specific bindings
	_dropdownBindings

	# Ugly hack to avoid tk buglet revealed in _dropdownBtnRelease where 
	# relief is used but not set in scrollbar.tcl. 
	global tkPriv
	set tkPriv(relief) raise

    } else {
	# --- build a simple combobox ---
	configure -childsitepos s
	itk_component add list {
	    iwidgets::Scrolledlistbox $itk_interior.list -exportselection no \
		-vscrollmode dynamic -hscrollmode dynamic 
	} {
	    keep -background -borderwidth -cursor -foreground \
		-highlightcolor -highlightthickness \
		-hscrollmode -items -selectbackground \
		-selectborderwidth -selectforeground -textbackground \
		-textfont -visibleitems -vscrollmode 
	    rename -height -listheight listHeight Height
	}
	# add mode specific bindings
	_simpleBindings
    }

    # popup cursor applies only to the list within the combobox
    configure -popupcursor $itk_option(-popupcursor)

    # add mode independent bindings
    _commonBindings
}

# ------------------------------------------------------
# PROTECTED METHOD: _deleteList first ?last?
#
# Delete an item or items from the listbox. Called via 
# "delete list args".
#
# ------------------------------------------------------
body iwidgets::Combobox::_deleteList {first {last {}}} {

    if {$last == {}} {
	set last $first
    }
    configure -items [eval lreplace {$itk_option(-items)} $first $last]

    # remove the item if it is no longer in the list
    set text [$this get]
    if {$text != ""} {
	set index [lsearch -exact $itk_option(-items) $text ]
	if {$index == -1} {
	    clear entry
	}
    }
    return
}

# ------------------------------------------------------
# PROTECTED METHOD: _deleteText first ?last?
#
# Renamed Entryfield delete method. Called via "delete entry args".
#
# ------------------------------------------------------
body iwidgets::Combobox::_deleteText {first {last {}}} {
    $itk_component(entry) configure -state normal 
    set rtrn [delete $first $last]
    if {$itk_option(-editable)} {
    } else {
	$itk_component(entry) configure -state disabled
    }
    return $rtrn
}

# ------------------------------------------------------
# PROTECTED METHOD:   _doLayout ?when?
#
# Call methods to create and pack the Combobox components.
#
# ------------------------------------------------------
body iwidgets::Combobox::_doLayout {{when later}} {
    _createComponents
    _packComponents $when
}


# ------------------------------------------------------
# PROTECTED METHOD:   _drawArrow ?mode?
#
# Draw the arrow button. Determines packing according to
# -labelpos.
#
# ------------------------------------------------------
body iwidgets::Combobox::_drawArrow {{mode normal}} {
    set flip false
    switch -- $mode {
	normal {
	    set fg [cget -foreground]
	    $itk_component(arrowBtn) configure -relief $itk_option(-arrowrelief)
	    bind $itk_component(arrowBtn) <1> [code $this _toggleList]
	    if {$_isPosted} {
		if {$itk_option(-fliparrow)} {
		    set flip true
		}
	    }
	}
	disabled {
	    set fg grey65
	    $itk_component(arrowBtn) configure -relief $itk_option(-arrowrelief)
	    bind $itk_component(arrowBtn) <1> ""
	}
	depressed {
	    set fg [cget -foreground]
	    $itk_component(arrowBtn) configure -relief sunken
#	    update
	}
    }

    # undraw the old arrow polygon
    $itk_component(arrowBtn) delete arrow

    # draw new arrow
    set bw [expr ([$itk_component(arrowBtn) cget -borderwidth] + \
		      [$itk_component(arrowBtn) cget -highlightthickness]) / 2]
    set h [expr [$itk_component(arrowBtn) cget -height] + (2*$bw)]
    set w [expr [$itk_component(arrowBtn) cget -width] + (2*$bw)]

    if {$flip} {
	$itk_component(arrowBtn) create polygon \
	    [expr .25*$w+$bw] [expr .75*$h+$bw] \
	    [expr .75*$w+$bw] [expr .75*$h+$bw] \
	    [expr .5*$w+$bw] [expr .25*$h+$bw-1] -fill $fg -tag arrow
    } else {
	$itk_component(arrowBtn) create polygon \
	    [expr .25*$w+$bw] [expr .25*$h+$bw] \
	    [expr .75*$w+$bw] [expr .25*$h+$bw] \
	    [expr .5*$w+$bw] [expr .75*$h+$bw] \
	    -fill $fg -tag arrow
    }
}

# ------------------------------------------------------
# PROTECTED METHOD: _dropdownBtnRelease window x y
#
# Event handler for button releases while a dropdown list
# is posted.
#
# ------------------------------------------------------
body iwidgets::Combobox::_dropdownBtnRelease {{window {}} {x 1} {y 1}} {

    # if it's a scrollbar then ignore the release
    if {($window == [$itk_component(list) component vertsb]) ||
	($window == [$itk_component(list) component horizsb])} {
	return
    }

    # 1st release allows list to stay up unless we are in listbox
    if {$_ignoreRelease} {
	_ignoreNextBtnRelease false
	return
    }
    
    # should I use just the listbox or also include the scrollbars
    if {($x >= 0) && ($x < [winfo width [_slbListbox]]) && 
	($y >= 0) && ($y < [winfo height [_slbListbox]])} {
	_selectCmd
    }
    
    _unpostList
}

# ------------------------------------------------------
# PROTECTED METHOD: _ignoreNextBtnRelease ignore
#
# Set private variable _ignoreRelease. If this variable
# is true then the next button release will not remove
# a dropdown list.
#
# ------------------------------------------------------
body iwidgets::Combobox::_ignoreNextBtnRelease {ignore} {
    set _ignoreRelease $ignore
}

# ------------------------------------------------------
# PROTECTED METHOD:   _next
#
# Select the next item in the list.
#
# ------------------------------------------------------
body iwidgets::Combobox::_next {} {
    if {[$this size] <= 1} {
	return
    }
    set i [$this curselection]
    if {($i == {}) || ($i == [expr [$this size]-1]) } {
	set i 0
    } else {
	incr i
    }
    $this selection clear 0 end
    $this selection set $i $i
    $this see $i
    set _currItem $i
}

# ------------------------------------------------------
# PROTECTED METHOD:   _packArrow labelpos
#
# Pack the arrowbutton according to the label position.
#
# ------------------------------------------------------
body iwidgets::Combobox::_packArrow {labelpos} {
    pack $itk_component(arrowBtn) -side right -anchor center
}

# ------------------------------------------------------
# PROTECTED METHOD:   _packComponents ?when?
#
# Pack the components of the combobox and add bindings.
#
# ------------------------------------------------------
body iwidgets::Combobox::_packComponents {{when later}} {
    if {$when == "later"} {
	if {$_repacking == ""} {
	    set _repacking [after idle [code $this _packComponents now]]
	    return
	}
    } elseif {$when != "now"} {
	error "bad option \"$when\": should be now or later"
    }

    if {$itk_option(-dropdown)} {
	pack $itk_component(margin) -side left
	pack configure $itk_component(list) -expand yes -fill both
	_resizeArrow
	pack $itk_component(arrowBtn) -side right
    } else {
	# size and pack list hack
	set w [winfo reqwidth $itk_component(hull)]
	$itk_component(list) configure -width $w
	pack configure $itk_component(entry) -fill x -side top -expand no
	pack configure $itk_component(efchildsite) -side top \
	    -after $itk_component(entry) -fill both -expand yes
	pack configure $itk_component(list) -side top -fill both -expand yes
    }
    
    set _repacking ""
}

# ------------------------------------------------------
# PROTECTED METHOD:   _positionList
#
# Determine the position (geometry) for the popped up list
# and map it to the screen.
#
# ------------------------------------------------------
body iwidgets::Combobox::_positionList {} {

    set x [winfo rootx $itk_component(entry) ]
    set y [expr [winfo rooty $itk_component(entry) ] + \
	       [winfo height $itk_component(entry) ]]
    set w [winfo width $itk_component(entry) ]
    set h [winfo height [_slbListbox] ]
    set sh [winfo screenheight .]

    if {([expr $y+$h] > $sh) && ($y > [expr $sh/2])} {
	set y [expr [winfo rooty $itk_component(entry) ] - $h]
    }
    
    $itk_component(list) configure -width $w
    wm geometry $itk_component(popup) +$x+$y
}

# ------------------------------------------------------
# PROTECTED METHOD:   _postList
#
# Pop up the list in a dropdown style Combobox.
#
# ------------------------------------------------------
body iwidgets::Combobox::_postList {} {

    if {[$itk_component(list) size] == ""} {
	return
    }

    set _isPosted true
    _drawArrow depressed      ;# sad button
    _positionList

    # map window and do a grab
    wm deiconify $itk_component(popup)
    _listShowing -wait
    grab -global $itk_component(popup) 
    raise $itk_component(popup)
    focus $itk_component(popup)

    _drawArrow normal
    set _ignoreRelease true
}

# ------------------------------------------------------
# PROTECTED METHOD:    _previous
#
# Select the previous item in the list. Wraps at front
# and end of list. 
#
# ------------------------------------------------------
body iwidgets::Combobox::_previous {} {
    if {[$this size] <= 1} {
	return
    }
    set i [$this curselection]
    if {$i == "" || $i == 0} {
	set i [expr [$this size] - 1]
    } else {
	incr i -1
    }
    $this selection clear 0 end
    $this selection set $i $i
    $this see $i
    set _currItem $i
}

# ------------------------------------------------------
# PROTECTED METHOD:   _resizeArrow
#
# Recalculate the arrow button size and then redraw it.
#
# ------------------------------------------------------
body iwidgets::Combobox::_resizeArrow {} {
    set bw [expr [$itk_component(arrowBtn) cget -borderwidth]+ \
		[$itk_component(arrowBtn) cget -highlightthickness]]
    set newHeight [expr [winfo reqheight $itk_component(entry) ]-(2*$bw)]
    $itk_component(arrowBtn) configure -width $newHeight -height $newHeight
    _drawArrow
}

# ------------------------------------------------------
# PROTECTED METHOD:   _selectCmd
#
# Called when list item is selected to insert new text 
# in entry, and call user -command callback if defined.
#
# ------------------------------------------------------
body iwidgets::Combobox::_selectCmd {} {
    $itk_component(entry) configure -state normal
    
    set _currItem [$itk_component(list) curselection]
    set item [$itk_component(list) getcurselection]
    clear entry
    $itk_component(entry) insert 0 $item
    if {$itk_option(-editable)} {
    } else {
	$itk_component(entry) configure -state disabled
    }

    # execute user command
    if {$itk_option(-selectioncommand) != ""} {
	uplevel #0 $itk_option(-selectioncommand)
    }
}

# ------------------------------------------------------
# PROTECTED METHOD:  _toggleList
#
# Post or unpost the dropdown listbox (toggle).
#
# ------------------------------------------------------
body iwidgets::Combobox::_toggleList {} {
    if {[winfo ismapped $itk_component(popup)] } {
	_unpostList
    } else {
	_postList
    }
}

# ------------------------------------------------------
# PROTECTED METHOD:   _unpostList
#
# Unmap the listbox (pop it down).
#
# ------------------------------------------------------
body iwidgets::Combobox::_unpostList {} {
    # Determine if event occured in the scrolledlistbox and, if it did, 
    # don't unpost it. (A selection in the list unposts it correctly and 
    # in the scrollbar we don't want to unpost it.)
    set x [winfo x $itk_component(list)]
    set y [winfo y $itk_component(list)]
    set w [winfo width $itk_component(list)]
    set h [winfo height $itk_component(list)]

    wm withdraw $itk_component(popup)
    grab release $itk_component(popup)	
    
    set _isPosted false
    _drawArrow normal
    
    $itk_component(list) selection clear 0 end
    if {$_currItem != {}} {
	$itk_component(list) selection set $_currItem $_currItem
	$itk_component(list) activate $_currItem
    }

    if {$itk_option(-editable)} {
	$itk_component(entry) configure -state normal
    } else {
	$itk_component(entry) configure -state disabled
    }
}

# ------------------------------------------------------
# PROTECTED METHOD:   _commonBindings
#
# Bindings that are used by both simple and dropdown
# style Comboboxes.
#
# ------------------------------------------------------
body iwidgets::Combobox::_commonBindings {} {
    bind $itk_component(entry) <Down>       [code $this _next]
    bind $itk_component(entry) <Up>         [code $this _previous]
    bind $itk_component(entry) <Control-n>  [code $this _next]
    bind $itk_component(entry) <Control-p>  [code $this _previous]
    bind [_slbListbox]         <Control-n>  [code $this _next]
    bind [_slbListbox]         <Control-p>  [code $this _previous]
}


# ------------------------------------------------------
# PROTECTED METHOD: _dropdownBindings
#
# Bindings used only by the dropdown type Combobox.
#
# ------------------------------------------------------
body iwidgets::Combobox::_dropdownBindings {} {
    bind $itk_component(popup)  <Escape> [code $this _unpostList]
    bind $itk_component(popup)  <space>  \
	"[code $this _selectCmd]; [code $this _unpostList]"
    bind $itk_component(popup)  <Return> \
	"[code $this _selectCmd]; [code $this _unpostList]"
    bind $itk_component(popup)  <ButtonRelease-1> \
	[code $this _dropdownBtnRelease %W %x %y]

    bind $itk_component(list)  <Map> \
	[code $this _listShowing 1]
    bind $itk_component(list)  <Unmap> \
	[code $this _listShowing 0]

    # once in the listbox, we drop on the next release (unless in scrollbar)
    bind [_slbListbox]   <Enter>   \
	[code $this _ignoreNextBtnRelease false]

    bind $itk_component(arrowBtn) <Configure>  [code $this _drawArrow]
    bind $itk_component(arrowBtn) <3>          [code $this _next]
    bind $itk_component(arrowBtn) <Shift-3>    [code $this _previous]
    bind $itk_component(arrowBtn) <Down>       [code $this _next]
    bind $itk_component(arrowBtn) <Up>         [code $this _previous]
    bind $itk_component(arrowBtn) <Control-n>  [code $this _next]
    bind $itk_component(arrowBtn) <Control-p>  [code $this _previous]
    bind $itk_component(arrowBtn) <Shift-Down> [code $this _toggleList]
    bind $itk_component(arrowBtn) <Shift-Up>   [code $this _toggleList]
    bind $itk_component(arrowBtn) <Return>     [code $this _toggleList]
    bind $itk_component(arrowBtn) <space>      [code $this _toggleList]

    bind $itk_component(entry)    <Configure>  [code $this _resizeArrow]
    bind $itk_component(entry)    <Shift-Down> [code $this _toggleList]
    bind $itk_component(entry)    <Shift-Up>   [code $this _toggleList]
}

# ------------------------------------------------------
# PROTECTED METHOD: _simpleBindings
#
# Bindings used only by the simple type Comboboxes.
#
# ------------------------------------------------------
body iwidgets::Combobox::_simpleBindings {} {
    bind [_slbListbox]         <ButtonRelease-1> [code $this _selectCmd]
    bind [_slbListbox]         <space>     [code $this _selectCmd]
    bind [_slbListbox]         <Return>    [code $this _selectCmd]
    bind $itk_component(entry) <Escape>     ""
    bind $itk_component(entry) <Shift-Down> ""
    bind $itk_component(entry) <Shift-Up>   ""
    bind $itk_component(entry) <Configure>  ""
}

# ------------------------------------------------------
# PROTECTED METHOD: _listShowing ?val?
#
# Used instead of "tkwait visibility" to make sure that
# the dropdown list is visible.  Whenever the list gets
# mapped or unmapped, this method is called to keep
# track of it.  When it is called with the value "-wait",
# it waits for the list to be mapped.
# ------------------------------------------------------
body iwidgets::Combobox::_listShowing {{val ""}} {
    if {$val == ""} {
        return $_listShowing($this)
    } elseif {$val == "-wait"} {
        while {!$_listShowing($this)} {
            tkwait variable [scope _listShowing($this)]
        }
        return
    }
    set _listShowing($this) $val
}

# ------------------------------------------------------
# PRIVATE METHOD:    _slbListbox
#
# Access the tk listbox window out of the scrolledlistbox.
#
# ------------------------------------------------------
body iwidgets::Combobox::_slbListbox {} {
    return [$itk_component(list) component listbox]
}

# ... emacs mode recognition ...
# Local Variables:
# mode: tcl
# End:

