# Spindate 
# ----------------------------------------------------------------------
# Implements a Date spinner widget.  A date spinner contains three
# Spinner widgets:  one Spinner for months, one SpinInt for days,
# and one SpinInt for years.  Months can be specified as abbreviated
# strings, integers or a user-defined list.  Options exist to manage to 
# behavior, appearance, and format of each component spinner.
#
# ----------------------------------------------------------------------
#   AUTHOR:  Sue Yockey               Phone: (214) 519-2517
#                                     E-mail: syockey@spd.dsccc.com
#                                             yockey@actc.com
#
#   @(#) $Id: spindate.itk,v 1.1 1998/07/27 18:49:51 stanton 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.
# ======================================================================

#
# Default resources.
#
option add *Spindate.monthLabel "Month" widgetDefault
option add *Spindate.dayLabel "Day" widgetDefault
option add *Spindate.yearLabel "Year" widgetDefault
option add *Spindate.monthWidth 4 widgetDefault
option add *Spindate.dayWidth 4 widgetDefault
option add *Spindate.yearWidth 4 widgetDefault

#
# Usual options.
#
itk::usual Spindate {
    keep -background -cursor -foreground -highlightcolor -highlightthickness \
	 -labelfont -selectbackground -selectborderwidth -selectforeground \
	 -textbackground -textfont 
}

# ------------------------------------------------------------------
#                            SPINDATE
# ------------------------------------------------------------------
class iwidgets::Spindate {
    inherit itk::Widget 
    
    constructor {args} {}
    destructor {}
    
    itk_option define -labelpos labelPos Position w
    itk_option define -orient orient Orient vertical 
    itk_option define -monthon monthOn MonthOn true 
    itk_option define -dayon dayOn DayOn true 
    itk_option define -yearon yearOn YearOn true 
    itk_option define -datemargin dateMargin Margin 1 
    itk_option define -yeardigits yearDigits YearDigits 2 
    itk_option define -monthformat monthFormat MonthFormat integer 

    public method get {{component all}} 
    public method insert {component index string} 
    public method delete {args} 
    public method clear {} 

    protected method _packDate {{when later}} 

    private method _spinMonth {direction} 
    private method _blockInput {obj char} 

    common MONTH_STR {Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec}
    common MONTH_INT {1 2 3 4 5 6 7 8 9 10 11 12}
    
    protected variable _repack {}             ;# Reconfiguration flag.
    private variable MONTH_LIST {}            ;# Private copy of month list.
}

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

# ------------------------------------------------------------------
#                        CONSTRUCTOR
# ------------------------------------------------------------------
body iwidgets::Spindate::constructor {args} {
    #
    # Create Month Spinner
    #
    itk_component add month {
	iwidgets::Spinner $itk_interior.month  -fixed 2 \
		-decrement [code $this _spinMonth -1] \
		-increment [code $this _spinMonth 1] \
		-validate "$this _blockInput"
    } {
	keep -background -cursor -arroworient -foreground \
		-highlightcolor -highlightthickness \
		-labelfont -labelmargin -relief -selectbackground \
		-selectborderwidth -selectforeground -textbackground \
		-textfont -repeatdelay -repeatinterval

	rename -labeltext -monthlabel monthLabel Text
	rename -width -monthwidth monthWidth Width
    }
    pack $itk_component(month) -fill x
    $itk_component(month) insert 0 1
    set MONTH_LIST $MONTH_INT
    
    #
    # Create Margin Frame 
    #
    itk_component add mth_day_marg {
	frame $itk_interior.mth_day_marg
    } {
	keep -background -cursor
    }
    pack $itk_component(mth_day_marg)
    
    #
    # Create Day Spinner
    #
    itk_component add day {
	iwidgets::Spinint $itk_interior.day -range {1 31} -fixed 2
    } {
	keep -background -cursor -arroworient -foreground \
		-highlightcolor -highlightthickness \
		-labelfont -labelmargin -relief -step -selectbackground \
		-selectborderwidth -selectforeground -textbackground \
		-textfont -repeatdelay -repeatinterval

	rename -labeltext -daylabel dayLabel Text
	rename -width -daywidth dayWidth Width
    }
    pack $itk_component(day) -fill x
    
    #
    # Create Margin Frame 
    #
    itk_component add day_yr_marg {
	frame $itk_interior.day_yr_marg
    } {
	keep -background -cursor
    }
    pack $itk_component(day_yr_marg)
    
    #
    # Create Year Spinner
    #
    itk_component add year {
	iwidgets::Spinint $itk_interior.year -range {0 99} -fixed 2
    } {
	keep -background -cursor -arroworient -foreground \
		-highlightcolor -highlightthickness \
		-labelfont -labelmargin -relief -step -selectbackground \
		-selectborderwidth -selectforeground -textbackground \
		-textfont -repeatdelay -repeatinterval

	rename -labeltext -yearlabel yearLabel Text
	rename -width -yearwidth yearWidth Width
    }
    pack $itk_component(year) -fill x
    $itk_component(year) clear 
    $itk_component(year) insert 0 "95" 

    #
    # Explicitly handle configs that may have been ignored earlier.
    #
    eval itk_initialize $args
    # 

    # When idle, pack the date spinner.
    #
    _packDate
}

# ------------------------------------------------------------------
#                           DESTRUCTOR
# ------------------------------------------------------------------
body iwidgets::Spindate::destructor {} {
    if {$_repack != ""} {after cancel $_repack}
}

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

# ------------------------------------------------------------------
# OPTION: -labelpos
#
# Specifies the location of all 3 spinners' labels.
# ------------------------------------------------------------------
configbody iwidgets::Spindate::labelpos {
    switch $itk_option(-labelpos) {
	n {
	    $itk_component(month) configure -labelpos n
	    $itk_component(day) configure -labelpos n
	    $itk_component(year) configure -labelpos n
	    
	    #
	    # Un-align labels
	    #
	    $itk_component(month) configure -labelmargin 1
	    $itk_component(day) configure -labelmargin 1
	    $itk_component(year) configure -labelmargin 1 
	}
	
	s {
	    $itk_component(month) configure -labelpos s
	    $itk_component(day) configure -labelpos s
	    $itk_component(year) configure -labelpos s
	    
	    #
	    # Un-align labels
	    #
	    $itk_component(month) configure -labelmargin 1
	    $itk_component(day) configure -labelmargin 1
	    $itk_component(year) configure -labelmargin 1 
	}
	
	w {
	    $itk_component(month) configure -labelpos w
	    $itk_component(day) configure -labelpos w
	    $itk_component(year) configure -labelpos w
	}
	
	e {
	    $itk_component(month) configure -labelpos e
	    $itk_component(day) configure -labelpos e
	    $itk_component(year) configure -labelpos e
	    
	    #
	    # Un-align labels
	    #
	    $itk_component(month) configure -labelmargin 1
	    $itk_component(day) configure -labelmargin 1
	    $itk_component(year) configure -labelmargin 1 
	}
	
	default {
	    error "bad labelpos option \"$itk_option(-labelpos)\",\
		    should be n, s, w or e" 
	}
    }

    _packDate
}

# ------------------------------------------------------------------
# OPTION: -orient
# 
# Specifies the orientation of the 3 spinners for Month, Day 
# and year.
# ------------------------------------------------------------------
configbody iwidgets::Spindate::orient {
    _packDate
}

# ------------------------------------------------------------------
# OPTION: -monthon
# 
# Specifies whether or not to display the month spinner.
# ------------------------------------------------------------------
configbody iwidgets::Spindate::monthon {
    if {$itk_option(-monthon)} {
	_packDate
    } else {
	pack forget $itk_component(month)
    }
}

# ------------------------------------------------------------------
# OPTION: -dayon
# 
# Specifies whether or not to display the day spinner.
# ------------------------------------------------------------------
configbody iwidgets::Spindate::dayon {
    if {$itk_option(-dayon)} {
	_packDate
    } else {
	pack forget $itk_component(day)
    }
}

# ------------------------------------------------------------------
# OPTION: -yearon
# 
# Specifies whether or not to display the year spinner.
# ------------------------------------------------------------------
configbody iwidgets::Spindate::yearon {
    if {$itk_option(-yearon)} {
	_packDate
    } else {
	pack forget $itk_component(year)
    }
}

# ------------------------------------------------------------------
# OPTION: -datemargin
# 
# Specifies the margin space between the month and day spinners 
# and the day and year spinners. 
# ------------------------------------------------------------------
configbody iwidgets::Spindate::datemargin {
    _packDate
}

# ------------------------------------------------------------------
# OPTION: -yeardigits
#
# Number of digits for year display, 2 or 4 
# ------------------------------------------------------------------
configbody iwidgets::Spindate::yeardigits {
    switch $itk_option(-yeardigits) {
	"2" {
	    $itk_component(year) configure -width 4 -range {0 99} \
		    -fixed 2
	    $itk_component(year) clear
	    $itk_component(year) insert 0 95
	}
	
	"4" {
	    $itk_component(year) configure -width 4 \
		    -range {1900 9999} -fixed 4
	    $itk_component(year) clear
	    $itk_component(year) insert 0 1995
	}
	
	default {
	    error "bad yeardigits option \"$itk_option(-yeardigits)\",\
		    should be 2 or 4"
	}
    } 
}

# ------------------------------------------------------------------
# OPTION: -monthformat
#
# Format of month display, integers (1-12) or strings (Jan - Dec),
# or a user specified list of values.
# ------------------------------------------------------------------
configbody iwidgets::Spindate::monthformat {
    if {$itk_option(-monthformat) == "string"} {
	set MONTH_LIST $MONTH_STR
	
	$itk_component(month) configure -width 4 -fixed 4
	$itk_component(month) delete 0 end
	$itk_component(month) insert 0 [lindex $MONTH_LIST 0]
	
    } elseif {$itk_option(-monthformat) == "integer"} {
	set MONTH_LIST $MONTH_INT
	
	$itk_component(month) configure -width 4 -fixed 2 
	$itk_component(month) delete 0 end
	$itk_component(month) insert 0 [lindex $MONTH_LIST 0]
	
    } elseif {[llength $itk_option(-monthformat)] > 1} {
	set MONTH_LIST $itk_option(-monthformat) 
	
	set len [llength $MONTH_LIST]
	for {set i 0; set wid 0} {$i < $len} {incr i} {
	    set tmp [string length [lindex $MONTH_LIST $i]]
	    if { $tmp > $wid} { set wid $tmp }
	}
	if {$wid < 4} { set wid 4}
	
	$itk_component(month) configure -width $wid -fixed $wid
	$itk_component(month) delete 0 end
	$itk_component(month) insert 0 [lindex $MONTH_LIST 0]
	
    } else {
	error "bad monthformat option\
		\"$itk_option(-monthformat)\", should be\
		\"integer\", \"string\" or a list"
    }
}

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

# ------------------------------------------------------------------
# METHOD: get ?component?
#
# Get the value of the date spinner.  A specific component value
# may be obtained via component name month, day, or year.  Without
# component name the command returns the date in a list, formatted
# {MM DD YY[YY]}.
# ------------------------------------------------------------------
body iwidgets::Spindate::get {{component all}} { 
    switch $component {
	all {
	    return [list [$itk_component(month) get] \
		    [$itk_component(day) get] [$itk_component(year) get]]
	}
	
	month {
	    return [$itk_component(month) get]
	}
	
	day {
	    return [$itk_component(day) get]
	}
	
	year {
	    return [$itk_component(year) get]
	}
	
	default {
	    error "bad get argument \"$component\": should\
		    be month, day or year"
	}
    }
}

# ------------------------------------------------------------------
# METHOD: insert component index string
#
# Insert value into month, day or year EntryFields
# ------------------------------------------------------------------
body iwidgets::Spindate::insert {component index string} {
    switch $component {
	month {
	    return [$itk_component(month) insert $index $string]
	}
	day {
	    return [$itk_component(day) insert $index $string]
	}
	year {
	    return [$itk_component(year) insert $index $string]
	}
	default {
	    error "bad insert argument \"$component\": should\
		    be month, day or year"
	}
    }
}

# ------------------------------------------------------------------
# METHOD: delete component first ?last?
#
# Delete value in month, day or year EntryFields
# ------------------------------------------------------------------
body iwidgets::Spindate::delete {component first {last {}}} {
    if {$last == {}} {
	set last $first
    }
    
    switch $component {
	month {
	    return [$itk_component(month) delete $first $last] 
	}
	day {
	    return [$itk_component(day) delete $first $last]
	}
	year {
	    return [$itk_component(year) delete $first $last]
	}
	default {
	    error "bad delete argument \"$component\": should\
		    be month, day or year"
	}
    }
}

# ------------------------------------------------------------------
# METHOD: clear
#
# Clear the values in all spinner components.
# ------------------------------------------------------------------
body iwidgets::Spindate::clear {} {
    $itk_component(month) clear
    $itk_component(day) clear
    $itk_component(year) clear 
}

# ----------------------------------------------------------------
# PRIVATE METHOD: _spinMonth direction
#
# Increment or decrement month value.
# ----------------------------------------------------------------
body iwidgets::Spindate::_spinMonth {direction} {
    set mth [$itk_component(month) get]
    set index [expr [lsearch $MONTH_LIST $mth] + $direction]
    
    set length [llength $MONTH_LIST]
    if {$index < 0} { set index [expr $length - 1] }
    if {$index >= $length} { set index 0 } 
    
    $itk_component(month) delete 0 end
    $itk_component(month) insert 0 [lindex $MONTH_LIST $index]
}

# ----------------------------------------------------------------
# PRIVATE METHOD: _blockInput obj char
#
# Disable spinner input.
# ----------------------------------------------------------------
body iwidgets::Spindate::_blockInput {obj char} {
    return 0 
}

# ------------------------------------------------------------------
# PRIVATE METHOD: _packDate when
#
# Pack the components of the date spinner.  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.
# ------------------------------------------------------------------
body iwidgets::Spindate::_packDate {{when later}} {
    if {$when == "later"} {
	if {$_repack == ""} {
	    set _repack [after idle [code $this _packDate now]]
	}
	return
    } elseif {$when != "now"} {
	error "bad option \"$when\": should be now or later"
    }

    set _repack ""

    switch $itk_option(-orient) {
	vertical {
	    pack forget $itk_component(month) $itk_component(day)
	    pack forget $itk_component(year) $itk_component(mth_day_marg) 
	    pack forget $itk_component(day_yr_marg)
	    
	    if {$itk_option(-monthon)} {
		pack $itk_component(month) -side top -fill x
		pack $itk_component(mth_day_marg) -side top
		$itk_component(mth_day_marg) configure -height \
			$itk_option(-datemargin) -width 1
	    }
	    
	    if {$itk_option(-dayon)} {
		pack $itk_component(day) -side top  -fill x
		pack $itk_component(day_yr_marg) -side top
		$itk_component(day_yr_marg) configure -height \
			$itk_option(-datemargin) -width 1
	    }
	    
	    if {$itk_option(-yearon)} {
		pack $itk_component(year) -side top -fill x
	    }
	    
	    if {$itk_option(-labelpos) == "w"} {
		iwidgets::Labeledwidget::alignlabels $itk_component(month) \
			$itk_component(day) $itk_component(year)
	    }
	}
	
	horizontal {
	    pack forget $itk_component(month) $itk_component(day)
	    pack forget $itk_component(year) $itk_component(mth_day_marg)
	    pack forget $itk_component(day_yr_marg)
	    
	    if {$itk_option(-monthon)} {
		pack $itk_component(month) -side left -fill x
		pack $itk_component(mth_day_marg) -side left
		$itk_component(mth_day_marg) configure -height 1 \
			-width $itk_option(-datemargin)
	    }
	    
	    if {$itk_option(-dayon)} {
		pack $itk_component(day) -side left -fill x
		pack $itk_component(day_yr_marg) -side left
		$itk_component(day_yr_marg) configure -height 1 \
			-width $itk_option(-datemargin)
	    }
	    
	    if {$itk_option(-yearon)} {
		pack $itk_component(year) -side left -fill x
	    }
	    
	    #
	    # Un-align labels.
	    #
	    $itk_component(month) configure -labelmargin 1
	    $itk_component(day) configure -labelmargin 1
	    $itk_component(year) configure -labelmargin 1
	}
	
	default {
	    error "bad orient option \"$itk_option(-orient)\", should\
		    be \"vertical\" or \"horizontal\""
	} 
    }
} 
