home *** CD-ROM | disk | FTP | other *** search
/ PC World 2005 June / PCWorld_2005-06_cd.bin / software / vyzkuste / firewally / firewally.exe / framework-2.3.exe / datefield.itk < prev    next >
Text File  |  2003-09-01  |  35KB  |  1,022 lines

  1. #
  2. # Datefield
  3. # ----------------------------------------------------------------------
  4. # Implements a date entry field with adjustable built-in intelligence
  5. # levels.
  6. # ----------------------------------------------------------------------
  7. #   AUTHOR:  Mark L. Ulferts          E-mail: mulferts@austin.dsccc.com
  8. #
  9. #   @(#) $Id: datefield.itk,v 1.5 2002/02/25 04:45:02 mgbacke Exp $
  10. # ----------------------------------------------------------------------
  11. #            Copyright (c) 1997 DSC Technologies Corporation
  12. # ======================================================================
  13. #
  14. # Permission to use, copy, modify, distribute and license this software 
  15. # and its documentation for any purpose, and without fee or written 
  16. # agreement with DSC, is hereby granted, provided that the above copyright 
  17. # notice appears in all copies and that both the copyright notice and 
  18. # warranty disclaimer below appear in supporting documentation, and that 
  19. # the names of DSC Technologies Corporation or DSC Communications 
  20. # Corporation not be used in advertising or publicity pertaining to the 
  21. # software without specific, written prior permission.
  22. # DSC DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING 
  23. # ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, AND NON-
  24. # INFRINGEMENT. THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, AND THE
  25. # AUTHORS AND DISTRIBUTORS HAVE NO OBLIGATION TO PROVIDE MAINTENANCE, 
  26. # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. IN NO EVENT SHALL 
  27. # DSC BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR 
  28. # ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, 
  29. # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTUOUS ACTION,
  30. # ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS 
  31. # SOFTWARE.
  32. # ======================================================================
  33.  
  34. #
  35. # Usual options.
  36. #
  37. itk::usual Datefield {
  38.     keep -background -borderwidth -cursor -foreground -highlightcolor \
  39.      -highlightthickness -labelfont -textbackground -textfont 
  40. }
  41.  
  42. # ------------------------------------------------------------------
  43. #                               DATEFIELD
  44. # ------------------------------------------------------------------
  45. itcl::class iwidgets::Datefield {
  46.     inherit iwidgets::Labeledwidget 
  47.     
  48.     constructor {args} {}
  49.  
  50.     itk_option define -childsitepos childSitePos Position e
  51.     itk_option define -command command Command {}
  52.     itk_option define -iq iq Iq high
  53.     itk_option define -gmt gmt GMT no
  54.     itk_option define -int int DateFormat no
  55.     
  56.     public method get {{format "-string"}}
  57.     public method isvalid {}
  58.     public method show {{date now}}
  59.  
  60.     protected method _backward {}
  61.     protected method _focusIn {}
  62.     protected method _forward {}
  63.     protected method _keyPress {char sym state}
  64.     protected method _lastDay {month year}
  65.     protected method _moveField {direction}
  66.     protected method _setField {field}
  67.     protected method _whichField {}
  68.  
  69.     protected variable _cfield "month"
  70.     protected variable _fields {month day year}
  71. }
  72.  
  73.  
  74. #
  75. # Provide a lowercased access method for the datefield class.
  76. proc ::iwidgets::datefield {pathName args} {
  77.     uplevel ::iwidgets::Datefield $pathName $args
  78. }
  79.  
  80. #
  81. # Use option database to override default resources of base classes.
  82. #
  83. option add *Datefield.justify center widgetDefault
  84.  
  85. # ------------------------------------------------------------------
  86. #                        CONSTRUCTOR
  87. # ------------------------------------------------------------------
  88. itcl::body iwidgets::Datefield::constructor {args} {
  89.     component hull configure -borderwidth 0
  90.     
  91.     #   
  92.     # Create an entry field for entering the date.
  93.     #   
  94.     itk_component add date {
  95.     entry $itk_interior.date -width 10
  96.     } {
  97.     keep -borderwidth -cursor -exportselection \
  98.         -foreground -highlightcolor -highlightthickness \
  99.         -insertbackground -justify -relief -state
  100.     
  101.     rename -font -textfont textFont Font
  102.     rename -highlightbackground -background background Background
  103.     rename -background -textbackground textBackground Background
  104.     }
  105.  
  106.     #
  107.     # Create the child site widget.
  108.     #
  109.     itk_component add -protected dfchildsite {
  110.     frame $itk_interior.dfchildsite
  111.     } 
  112.     set itk_interior $itk_component(dfchildsite)
  113.     
  114.     #
  115.     # Add datefield event bindings for focus in and keypress events.
  116.     #
  117.     bind $itk_component(date) <FocusIn> [itcl::code $this _focusIn]
  118.     bind $itk_component(date) <KeyPress> [itcl::code $this _keyPress %A %K %s]
  119.     
  120.     #
  121.     # Disable some mouse button event bindings:
  122.     #   Button Motion
  123.     #   Double-Clicks
  124.     #   Triple-Clicks
  125.     #   Button2
  126.     #
  127.     bind $itk_component(date) <Button1-Motion>  break
  128.     bind $itk_component(date) <Button2-Motion>  break
  129.     bind $itk_component(date) <Double-Button>   break
  130.     bind $itk_component(date) <Triple-Button>   break
  131.     bind $itk_component(date) <2>       break
  132.  
  133.     #
  134.     # Initialize the widget based on the command line options.
  135.     #
  136.     eval itk_initialize $args
  137.  
  138.     #
  139.     # Initialize the date to the current date.
  140.     #
  141.     $itk_component(date) delete 0 end
  142.  
  143.     show now
  144. }
  145.  
  146. # ------------------------------------------------------------------
  147. #                             OPTIONS
  148. # ------------------------------------------------------------------
  149.  
  150. # ------------------------------------------------------------------
  151. # OPTION: -childsitepos
  152. #
  153. # Specifies the position of the child site in the widget.  Valid
  154. # locations are n, s, e, and w.
  155. # ------------------------------------------------------------------
  156. itcl::configbody iwidgets::Datefield::childsitepos {
  157.     set parent [winfo parent $itk_component(date)]
  158.  
  159.     switch $itk_option(-childsitepos) {
  160.     n {
  161.         grid $itk_component(dfchildsite) -row 0 -column 0 -sticky ew
  162.         grid $itk_component(date) -row 1 -column 0 -sticky nsew
  163.  
  164.         grid rowconfigure $parent 0 -weight 0
  165.         grid rowconfigure $parent 1 -weight 1
  166.         grid columnconfigure $parent 0 -weight 1
  167.         grid columnconfigure $parent 1 -weight 0
  168.     }
  169.     
  170.     e {
  171.         grid $itk_component(dfchildsite) -row 0 -column 1 -sticky ns
  172.         grid $itk_component(date) -row 0 -column 0 -sticky nsew
  173.  
  174.         grid rowconfigure $parent 0 -weight 1
  175.         grid rowconfigure $parent 1 -weight 0
  176.         grid columnconfigure $parent 0 -weight 1
  177.         grid columnconfigure $parent 1 -weight 0
  178.     }
  179.     
  180.     s {
  181.         grid $itk_component(dfchildsite) -row 1 -column 0 -sticky ew
  182.         grid $itk_component(date) -row 0 -column 0 -sticky nsew
  183.  
  184.         grid rowconfigure $parent 0 -weight 1
  185.         grid rowconfigure $parent 1 -weight 0
  186.         grid columnconfigure $parent 0 -weight 1
  187.         grid columnconfigure $parent 1 -weight 0
  188.     }
  189.     
  190.     w {
  191.         grid $itk_component(dfchildsite) -row 0 -column 0 -sticky ns
  192.         grid $itk_component(date) -row 0 -column 1 -sticky nsew
  193.  
  194.         grid rowconfigure $parent 0 -weight 1
  195.         grid rowconfigure $parent 1 -weight 0
  196.         grid columnconfigure $parent 0 -weight 0
  197.         grid columnconfigure $parent 1 -weight 1
  198.     }
  199.     
  200.     default {
  201.         error "bad childsite option\
  202.             \"$itk_option(-childsitepos)\":\
  203.             should be n, e, s, or w"
  204.     }
  205.     }
  206. }
  207.  
  208. # ------------------------------------------------------------------
  209. # OPTION: -command
  210. #
  211. # Command invoked upon detection of return key press event.
  212. # ------------------------------------------------------------------
  213. itcl::configbody iwidgets::Datefield::command {}
  214.  
  215. # ------------------------------------------------------------------
  216. # OPTION: -iq
  217. #
  218. # Specifies the level of intelligence to be shown in the actions
  219. # taken by the date field during the processing of keypress events.
  220. # Valid settings include high, average, and low.  With a high iq,
  221. # the date prevents the user from typing in an invalid date.  For 
  222. # example, if the current date is 05/31/1997 and the user changes
  223. # the month to 04, then the day will be instantly modified for them 
  224. # to be 30.  In addition, leap years are fully taken into account.
  225. # With average iq, the month is limited to the values of 01-12, but
  226. # it is possible to type in an invalid day.  A setting of low iq
  227. # instructs the widget to do no validity checking at all during
  228. # date entry.  With both average and low iq levels, it is assumed
  229. # that the validity will be determined at a later time using the
  230. # date's isvalid command.
  231. # ------------------------------------------------------------------
  232. itcl::configbody iwidgets::Datefield::iq {
  233.     switch $itk_option(-iq) {
  234.     high - average - low {
  235.     }
  236.     default {
  237.         error "bad iq option \"$itk_option(-iq)\":\
  238.                    should be high, average or low"
  239.     }
  240.     }
  241. }
  242.  
  243. # ------------------------------------------------------------------
  244. # OPTION: -int 
  245. #
  246. # Added by Mark Alston 2001/10/21
  247. #
  248. # Allows for the use of dates in "international" format: YYYY-MM-DD.
  249. # It must be a boolean value.
  250. # ------------------------------------------------------------------
  251. itcl::configbody iwidgets::Datefield::int { 
  252.     switch $itk_option(-int) {
  253.     1 - yes - true - on {
  254.         set _cfield "year"
  255.         set _fields {year month day}
  256.     }
  257.     0 - no - false - off { }
  258.     default {
  259.         error "bad int option \"$itk_option(-int)\": should be boolean"
  260.     }
  261.     }
  262.     show [get]
  263. }
  264.  
  265. # ------------------------------------------------------------------
  266. # OPTION: -gmt
  267. #
  268. # This option is used for GMT time.  Must be a boolean value.
  269. # ------------------------------------------------------------------
  270. itcl::configbody iwidgets::Datefield::gmt {
  271.   switch $itk_option(-gmt) {
  272.     0 - no - false - off { }
  273.     1 - yes - true - on { }
  274.     default {
  275.       error "bad gmt option \"$itk_option(-gmt)\": should be boolean"
  276.     }
  277.   }
  278. }
  279.  
  280. # ------------------------------------------------------------------
  281. #                            METHODS
  282. # ------------------------------------------------------------------
  283.  
  284. # ------------------------------------------------------------------
  285. # PUBLIC METHOD: get ?format?
  286. #
  287. # Return the current contents of the datefield in one of two formats
  288. # string or as an integer clock value using the -string and -clicks
  289. # options respectively.  The default is by string.  Reference the 
  290. # clock command for more information on obtaining dates and their 
  291. # formats.
  292. # ------------------------------------------------------------------
  293. itcl::body iwidgets::Datefield::get {{format "-string"}} {
  294.     set datestr [$itk_component(date) get]
  295.  
  296.     switch -- $format {
  297.     "-string" {
  298.         return $datestr
  299.     }
  300.     "-clicks" {
  301.         return [clock scan $datestr]
  302.     }
  303.     default {
  304.         error "bad format option \"$format\":\
  305.                    should be -string or -clicks"
  306.     }
  307.     }
  308. }
  309.  
  310. # ------------------------------------------------------------------
  311. # PUBLIC METHOD: show date
  312. #
  313. # Changes the currently displayed date to be that of the date 
  314. # argument.  The date may be specified either as a string or an
  315. # integer clock value.  Reference the clock command for more 
  316. # information on obtaining dates and their formats.
  317. # ------------------------------------------------------------------
  318. itcl::body iwidgets::Datefield::show {{date "now"}} {
  319.     $itk_component(date) delete 0 end
  320.     if {$itk_option(-int)} {
  321.         set format {%Y-%m-%d}
  322.     } else {
  323.         set format {%m/%d/%Y}
  324.     }
  325.  
  326.     if {$date == "now"} {
  327.         set seconds [::clock seconds]
  328.         $itk_component(date) insert end \
  329.             [clock format $seconds -format "$format" -gmt $itk_option(-gmt)]
  330.  
  331.     } elseif { $itk_option(-iq) != "low" } {
  332.         if {[catch {::clock format $date}] == 0} {
  333.             set seconds $date
  334.         } elseif {[catch {set seconds [::clock scan $date -gmt \
  335.                 $itk_option(-gmt)]}] != 0} {
  336.             error "bad date: \"$date\", must be a valid date\
  337.             string, clock clicks value or the keyword now"
  338.         }
  339.         $itk_component(date) insert end \
  340.             [clock format $seconds -format "$format" -gmt $itk_option(-gmt)]
  341.     } else {
  342.         # Note that it doesn't matter what -int is set to.
  343.         $itk_component(date) insert end $date
  344.     }
  345.  
  346.     if {$itk_option(-int)} {
  347.         _setField year
  348.     } else {
  349.         _setField month
  350.     }
  351.  
  352.     return
  353. }
  354.  
  355. # ------------------------------------------------------------------
  356. # PUBLIC METHOD: isvalid
  357. #
  358. # Returns a boolean indication of the validity of the currently
  359. # displayed date value.  For example, 3/3/1960 is valid whereas
  360. # 02/29/1997 is invalid.
  361. # ------------------------------------------------------------------
  362. itcl::body iwidgets::Datefield::isvalid {} {
  363.     if {[catch {clock scan [$itk_component(date) get]}] != 0} {
  364.         return 0
  365.     } else {
  366.         return 1
  367.     }
  368. }
  369.  
  370. # ------------------------------------------------------------------
  371. # PROTECTED METHOD: _focusIn
  372. #
  373. # This method is bound to the <FocusIn> event.  It resets the 
  374. # insert cursor and field settings to be back to their last known
  375. # positions.
  376. # ------------------------------------------------------------------
  377. itcl::body iwidgets::Datefield::_focusIn {} {
  378.     _setField $_cfield
  379. }
  380.  
  381. # ------------------------------------------------------------------
  382. # PROTECTED METHOD: _keyPress 
  383. #
  384. # This method is the workhorse of the class.  It is bound to the
  385. # <KeyPress> event and controls the processing of all key strokes.
  386. # ------------------------------------------------------------------
  387. itcl::body iwidgets::Datefield::_keyPress {char sym state} {
  388.     #
  389.     #  Determine which field we are in currently.  This is needed
  390.     # since the user may have moved to this position via a mouse
  391.     # selection and so it would not be in the position we last 
  392.     # knew it to be.
  393.     #
  394.     _whichField 
  395.  
  396.     #
  397.     # If we are using an international date the split char is "-" 
  398.     # otherwise it is "/".
  399.     #
  400.     if {$itk_option(-int)} {
  401.         set split_char "-"
  402.     } else {
  403.         set split_char "/"
  404.     }
  405.  
  406.  
  407.     #
  408.     # Set up a few basic variables we'll be needing throughout the
  409.     # rest of the method such as the position of the insert cursor
  410.     # and the currently displayed day, month, and year.
  411.     #
  412.     set icursor [$itk_component(date) index insert]
  413.     set splist [split [$itk_component(date) get] "$split_char"]
  414.  
  415.  
  416.     # A bunch of added variables to allow for the use of int dates
  417.     if {$itk_option(-int)} {
  418.         set order {year month day}
  419.         set year [lindex $splist 0]
  420.         set month [lindex $splist 1]
  421.         set day [lindex $splist 2]
  422.         set year_start_pos 0
  423.         set year_second_pos 1
  424.         set year_third_pos 2
  425.         set year_fourth_pos 3
  426.         set year_end_pos 4
  427.         set month_start_pos 5
  428.         set month_second_pos 6
  429.         set month_end_pos 7
  430.         set day_start_pos 8
  431.         set day_second_pos 9
  432.         set day_end_pos 10
  433.     } else {
  434.         set order {month day year}
  435.         set month [lindex $splist 0]
  436.         set day [lindex $splist 1]
  437.         set year [lindex $splist 2]
  438.         set month_start_pos 0
  439.         set month_second_pos 1
  440.         set month_end_pos 2
  441.         set day_start_pos 3
  442.         set day_second_pos 4
  443.         set day_end_pos 5
  444.         set year_start_pos 6
  445.         set year_second_pos 7
  446.         set year_third_pos 8
  447.         set year_fourth_pos 9
  448.         set year_end_pos 10
  449.     }
  450.  
  451.  
  452.     #
  453.     # Process numeric keystrokes.  This involes a fair amount of 
  454.     # processing with step one being to check and make sure we
  455.     # aren't attempting to insert more that 10 characters.  If
  456.     # so ring the bell and break.
  457.     #
  458.     if {[regexp {[0-9]} $char]} {
  459.         if {[$itk_component(date) index insert] == 10} {
  460.             bell
  461.             return -code break
  462.         }
  463.  
  464.         #
  465.         # If we are currently in the month field then we process the
  466.         # number entered based on the cursor position.  If we are at
  467.         # at the first position and our iq is low, then accept any 
  468.         # input.  
  469.         #
  470.         if {$_cfield == "month"} {
  471.  
  472.             if {[$itk_component(date) index insert] == $month_start_pos} {
  473.                 if {$itk_option(-iq) == "low"} {
  474.                 $itk_component(date) delete $month_start_pos
  475.                 $itk_component(date) insert $month_start_pos $char
  476.             } else {            
  477.                 #
  478.                 # Otherwise, we're slightly smarter.  If the number
  479.                 # is less than two insert it at position zero.  If 
  480.                 # this makes the month greater than twelve, set the 
  481.                 # number at position one to zero which makes in 
  482.                 # effect puts the month back in range.  
  483.                 #
  484.                 regsub {([0-9])([0-9])} $month "$char\\2" month2b
  485.  
  486.                 if {$char < 2} {
  487.                     $itk_component(date) delete $month_start_pos
  488.                     $itk_component(date) insert $month_start_pos $char
  489.  
  490.                     if {$month2b > 12} {
  491.                         $itk_component(date) delete $month_second_pos
  492.                         $itk_component(date) insert $month_second_pos 0
  493.                         $itk_component(date) icursor $month_second_pos
  494.                     } elseif {$month2b == "00"} {
  495.                         $itk_component(date) delete $month_second_pos
  496.                         $itk_component(date) insert $month_second_pos 1
  497.                         $itk_component(date) icursor $month_second_pos
  498.                     }               
  499.  
  500.                     #
  501.                     # Finally, if the number is greater than one we'll 
  502.                     # assume that they really mean to be entering a zero
  503.                     # followed by their number, do so for them, and 
  504.                     # proceed to skip to the next field which is the 
  505.                     # day field.
  506.                     #
  507.                 } else {
  508.                     $itk_component(date) delete $month_start_pos $month_end_pos
  509.                     $itk_component(date) insert $month_start_pos 0$char
  510.                     _setField day
  511.                 }
  512.             }
  513.             
  514.             #
  515.             # Else, we're at the second month position.  Again, if we aren't
  516.             # too smart, let them enter anything.  Otherwise, if the 
  517.             # number makes the month exceed twelve, set the month to
  518.             # zero followed by their number to get it back into range.
  519.             #
  520.         } else {
  521.             regsub {([0-9])([0-9])} $month "\\1$char" month2b
  522.         
  523.             if {$itk_option(-iq) == "low"} {
  524.                 $itk_component(date) delete $month_second_pos
  525.                 $itk_component(date) insert $month_second_pos $char
  526.             } else {
  527.                 if {$month2b > 12} {
  528.                     $itk_component(date) delete $month_start_pos $month_end_pos
  529.                     $itk_component(date) insert $month_start_pos 0$char
  530.                 } elseif {$month2b == "00"} {
  531.                     bell
  532.                     return -code break
  533.                 } else {
  534.                     $itk_component(date) delete $month_second_pos
  535.                     $itk_component(date) insert $month_second_pos $char
  536.                 }           
  537.             }
  538.             _setField day
  539.         }
  540.  
  541.         # 
  542.         # Now, the month processing is complete and if we're of a
  543.         # high level of intelligence, then we'll make sure that the
  544.         # current value for the day is valid for this month.  If
  545.         # it is beyond the last day for this month, change it to
  546.         # be the last day of the new month.
  547.         #
  548.         if {$itk_option(-iq) == "high"} {
  549.             set splist [split [$itk_component(date) get] "$split_char"]
  550.             set month [lindex $splist [lsearch $order month]]
  551.             if {$day > [set endday [_lastDay $month $year]]} {
  552.                 set icursor [$itk_component(date) index insert]
  553.                 $itk_component(date) delete $day_start_pos $day_end_pos
  554.                 $itk_component(date) insert $day_start_pos $endday
  555.                 $itk_component(date) icursor $icursor
  556.             }
  557.         }
  558.         
  559.         #
  560.         # Finally, return with a code of break to stop any normal
  561.         # processing in that we've done all that is necessary.
  562.         #
  563.         return -code break
  564.     }
  565.  
  566.     #
  567.     # This next block of code is for processing of the day field
  568.     # which is quite similar is strategy to that of the month.
  569.     #
  570.     if {$_cfield == "day"} {
  571.         if {$itk_option(-iq) == "high"} {
  572.             set endofMonth [_lastDay $month $year]
  573.         } else {
  574.             set endofMonth 31
  575.         }
  576.  
  577.         #
  578.         # If we are at the first cursor position for the day 
  579.         # we are processing 
  580.         # the first character of the day field.  If we have an iq 
  581.         # of low accept any input.
  582.         #
  583.         if {[$itk_component(date) index insert] == $day_start_pos} {
  584.             if {$itk_option(-iq) == "low"} {
  585.                 $itk_component(date) delete $day_start_pos
  586.                 $itk_component(date) insert $day_start_pos $char
  587.             
  588.             } else {
  589.  
  590.                 #
  591.                 # If the day to be is double zero, then make the
  592.                 # day be the first.
  593.                 #
  594.                 regsub {([0-9])([0-9])} $day "$char\\2" day2b
  595.  
  596.                 if {$day2b == "00"} {
  597.                     $itk_component(date) delete $day_start_pos $day_end_pos
  598.                     $itk_component(date) insert $day_start_pos 01
  599.                     $itk_component(date) icursor $day_second_pos
  600.                     #
  601.                     # Otherwise, if the character is less than four 
  602.                     # and the month is not Feburary, insert the number 
  603.                     # and if this makes the day be beyond the valid 
  604.                     # range for this month, than set to be back in 
  605.                     # range.  
  606.                     #
  607.                 } elseif {($char < 4) && ($month != "02")} {
  608.                     $itk_component(date) delete $day_start_pos
  609.                     $itk_component(date) insert $day_start_pos $char
  610.             
  611.                     if {$day2b > $endofMonth} {
  612.                         $itk_component(date) delete $day_second_pos
  613.                         $itk_component(date) insert $day_second_pos 0
  614.                         $itk_component(date) icursor $day_second_pos
  615.                     } 
  616.             
  617.                     #
  618.                     # For Feburary with a number to be entered of 
  619.                     # less than three, make sure the number doesn't 
  620.                     # make the day be greater than the correct range
  621.                     # and if so adjust the input. 
  622.                     #
  623.                 } elseif {$char < 3} {
  624.                     $itk_component(date) delete $day_start_pos
  625.                     $itk_component(date) insert $day_start_pos $char
  626.                     if {$day2b > $endofMonth} {
  627.                         $itk_component(date) delete $day_start_pos $day_end_pos
  628.                         $itk_component(date) insert $day_start_pos $endofMonth
  629.                         $itk_component(date) icursor $day_second_pos
  630.                     } 
  631.  
  632.                     #
  633.                     # Finally, if the number is greater than three,
  634.                     # set the day to be zero followed by the number 
  635.                     # entered and proceed to the year field or end.
  636.                     #
  637.                 } else {
  638.                     $itk_component(date) delete $day_start_pos $day_end_pos
  639.                     $itk_component(date) insert $day_start_pos 0$char
  640.                     $itk_component(date) icursor $day_end_pos
  641.                     if {!$itk_option(-int)} {
  642.                         _setField year
  643.                     }
  644.                 }
  645.             }
  646.             #
  647.             # Else, we're dealing with the second number in the day
  648.             # field.  If we're not too bright accept anything, otherwise
  649.             # if the day is beyond the range for this month or equal to
  650.             # zero then ring the bell.
  651.             #
  652.         } else {
  653.             regsub {([0-9])([0-9])} $day "\\1$char" day2b
  654.         
  655.             if {($itk_option(-iq) != "low") && \
  656.                 (($day2b > $endofMonth) || ($day2b == "00"))} {
  657.                 bell
  658.             } else {
  659.                 $itk_component(date) delete $day_second_pos
  660.                 $itk_component(date) insert $day_second_pos $char
  661.                 $itk_component(date) icursor $day_end_pos
  662.                 if {!$itk_option(-int)} {
  663.                     _setField year
  664.                 }
  665.             }
  666.         }
  667.  
  668.         #
  669.         # Return with a code of break to prevent normal processing. 
  670.         #
  671.         return -code break
  672.     }
  673.  
  674.     #
  675.     # This month and day we're tough, the code for the year is 
  676.     # comparitively simple.  Accept any input and if we are really
  677.     # sharp, then make sure the day is correct for the month
  678.     # given the year.  In short, handle leap years.
  679.     #
  680.     if {$_cfield == "year"} {
  681.         if {$itk_option(-iq) == "low"} {
  682.             $itk_component(date) delete $icursor
  683.             $itk_component(date) insert $icursor $char
  684.         } else {
  685.             set prevdate [get]
  686.             if {[$itk_component(date) index insert] == $year_start_pos} {
  687.                 set yrdgt [lindex [split [lindex \
  688.                 [split $prevdate "$split_char"] [lsearch $order year]] ""] 0]
  689.                 if {$char != $yrdgt} {
  690.                     if {$char == 1} {
  691.                         $itk_component(date) delete $icursor $year_end_pos
  692.                         $itk_component(date) insert $icursor 1999
  693.                     } elseif {$char == 2} {
  694.                         $itk_component(date) delete $icursor $year_end_pos
  695.                         $itk_component(date) insert $icursor 2000
  696.                     } else {
  697.                         bell
  698.                         return -code break
  699.                     }
  700.                 }
  701.  
  702.                 $itk_component(date) icursor $year_second_pos
  703.                 return -code break
  704.             }
  705.         
  706.             $itk_component(date) delete $icursor
  707.             $itk_component(date) insert $icursor $char
  708.  
  709.  
  710.             if {[catch {clock scan [get]}] != 0} {
  711.                 $itk_component(date) delete $year_start_pos $year_end_pos
  712.                 $itk_component(date) insert $year_start_pos \
  713.                 [lindex [split $prevdate "$split_char"] [lsearch $order year]]
  714.                 $itk_component(date) icursor $icursor
  715.  
  716.                 bell
  717.                 return -code break
  718.             }
  719.  
  720.             if {$itk_option(-iq) == "high"} {
  721.                 set splist [split [$itk_component(date) get] "$split_char"]
  722.                 set year [lindex $splist [lsearch $order year]]
  723.  
  724.                 if {$day > [set endday [_lastDay $month $year]]} {
  725.                     set icursor [$itk_component(date) index insert]
  726.                     $itk_component(date) delete $day_start_pos $day_end_pos
  727.                     $itk_component(date) insert $day_start_pos $endday
  728.                     $itk_component(date) icursor $icursor
  729.                 }
  730.             }
  731.         }
  732.         if {$itk_option(-int)} {
  733.             if {$icursor == $year_fourth_pos } {
  734.                 _setField month
  735.             }
  736.         }
  737.         return -code break
  738.     }
  739.     
  740.     #
  741.     # Process the plus and the up arrow keys.  They both yeild the same
  742.     # effect, they increment the day by one.
  743.     #
  744.     } elseif {($sym == "plus") || ($sym == "Up")} {
  745.         if {[catch {show [clock scan "1 day" -base [get -clicks]]}] != 0} {
  746.             bell
  747.         }
  748.         return -code break
  749.     
  750.         #
  751.         # Process the minus and the down arrow keys which decrement the day.
  752.         #
  753.     } elseif {($sym == "minus") || ($sym == "Down")} {
  754.         if {[catch {show [clock scan "-1 day" -base [get -clicks]]}] != 0} {
  755.             bell
  756.         }
  757.         return -code break
  758.  
  759.         #
  760.         # A tab key moves the day/month/year (or year/month/day) field
  761.         # forward by one unless
  762.         # the current field is the last field.  In that case we'll let tab
  763.         # do what is supposed to and pass the focus onto the next widget.
  764.         #
  765.     } elseif {($sym == "Tab") && ($state == 0)} {
  766.         if {$_cfield != "[lindex $order 2]"} {
  767.             _moveField forward
  768.             return -code break
  769.         } else {
  770.             _setField "[lindex $order 0]"
  771.             return -code continue
  772.         }
  773.  
  774.         #
  775.         # A ctrl-tab key moves the day/month/year field backwards by one 
  776.         # unless the current field is the the first field.  In that case we'll
  777.         # let tab take the focus to a previous widget.
  778.         #
  779.     } elseif {($sym == "Tab") && ($state == 4)} {
  780.         if {$_cfield != "[lindex $order 0]"} {
  781.             _moveField backward
  782.             return -code break
  783.         } else {
  784.             set _cfield "[lindex $order 0]"
  785.             return -code continue
  786.         }
  787.  
  788.         #
  789.         # A right arrow key moves the insert cursor to the right one.
  790.         #
  791.     } elseif {$sym == "Right"} {
  792.         _forward
  793.         return -code break
  794.  
  795.         #
  796.         # A left arrow, backspace, or delete key moves the insert cursor 
  797.         # to the left one.  This is what you expect for the left arrow
  798.         # and since the whole widget always operates in overstrike mode,
  799.         # it makes the most sense for backspace and delete to do the same.
  800.         #
  801.     } elseif {$sym == "Left" || $sym == "BackSpace" || $sym == "Delete"} {
  802.         _backward
  803.         return -code break
  804.  
  805.     } elseif {($sym == "Control_L") || ($sym == "Shift_L") || \
  806.             ($sym == "Control_R") || ($sym == "Shift_R")} {
  807.         return -code break
  808.  
  809.         #
  810.         # A Return key invokes the optionally specified command option.
  811.         #
  812.     } elseif {$sym == "Return"} {
  813.         uplevel #0 $itk_option(-command)
  814.         return -code break 
  815.     } else {
  816.         bell
  817.         return -code break
  818.     }
  819. }
  820.  
  821. # ------------------------------------------------------------------
  822. # PROTECTED METHOD: _setField field
  823. #
  824. # Internal method which adjusts the field to be that of the 
  825. # argument, setting the insert cursor appropriately.
  826. # ------------------------------------------------------------------
  827. itcl::body iwidgets::Datefield::_setField {field} {
  828.     set _cfield $field
  829.     
  830.     if {$itk_option(-int)} {
  831.         set year_pos 2
  832.         set month_pos 5
  833.         set day_pos 8
  834.     } else {
  835.         set month_pos 0
  836.         set day_pos 3
  837.         set year_pos 8
  838.     }
  839.         
  840.     
  841.     switch $field {
  842.         "month" {
  843.             $itk_component(date) icursor $month_pos
  844.         }
  845.         "day" {
  846.             $itk_component(date) icursor $day_pos
  847.         }
  848.         "year" {
  849.             $itk_component(date) icursor $year_pos
  850.         }
  851.         default {
  852.             error "bad field: \"$field\", must be month, day or year"
  853.         }
  854.     }
  855. }
  856.  
  857. # ------------------------------------------------------------------
  858. # PROTECTED METHOD: _moveField
  859. #
  860. # Internal method for moving the field forward or backward by one.
  861. # ------------------------------------------------------------------
  862. itcl::body iwidgets::Datefield::_moveField {direction} {
  863.  
  864.     set index [lsearch $_fields $_cfield]
  865.  
  866.     if {$direction == "forward"} {
  867.         set newIndex [expr {$index + 1}]
  868.     } else {
  869.         set newIndex [expr {$index - 1}]
  870.     }
  871.  
  872.     if {$newIndex == [llength $_fields]} {
  873.         set newIndex 0
  874.     }
  875.     if {$newIndex < 0} {
  876.         set newIndex [expr {[llength $_fields] - 1}]
  877.     }
  878.  
  879.     _setField [lindex $_fields $newIndex]
  880.  
  881.     return
  882. }
  883.  
  884. # ------------------------------------------------------------------
  885. # PROTECTED METHOD: _whichField
  886. #
  887. # Internal method which returns the current field that the cursor
  888. # is currently within.
  889. # ------------------------------------------------------------------
  890. itcl::body iwidgets::Datefield::_whichField {} {
  891.     set icursor [$itk_component(date) index insert]
  892.  
  893.     if {$itk_option(-int)} {
  894.         switch $icursor {
  895.             0 - 1 - 2 - 3 {
  896.                 set _cfield "year"
  897.             }
  898.             5 - 6 {
  899.                 set _cfield "month"
  900.             }
  901.             8 - 9 {
  902.                 set _cfield "day"
  903.             }
  904.         }
  905.     } else {
  906.         switch $icursor {
  907.             0 - 1 {
  908.             set _cfield "month"
  909.             }
  910.             3 - 4 {
  911.             set _cfield "day"
  912.             }
  913.             6 - 7 - 8 - 9 {
  914.             set _cfield "year"
  915.             }
  916.         }
  917.     }
  918. }
  919.  
  920. # ------------------------------------------------------------------
  921. # PROTECTED METHOD: _forward
  922. #
  923. # Internal method which moves the cursor forward by one character
  924. # jumping over the slashes and wrapping.
  925. # ------------------------------------------------------------------
  926. itcl::body iwidgets::Datefield::_forward {} {
  927.     set icursor [$itk_component(date) index insert]
  928.  
  929.     if {$itk_option(-int)} {
  930.         switch $icursor {
  931.             3 {
  932.                 _setField month
  933.             }
  934.             6 {
  935.             _setField day
  936.             }
  937.             9 - 10 {
  938.             _setField year
  939.             }
  940.             default {
  941.             $itk_component(date) icursor [expr {$icursor + 1}]
  942.             }
  943.         }
  944.     } else {
  945.         switch $icursor {
  946.             1 {
  947.             _setField day
  948.             }
  949.             4 {
  950.             _setField year
  951.             }
  952.             9 - 10 {
  953.             _setField month
  954.             }
  955.             default {
  956.             $itk_component(date) icursor [expr {$icursor + 1}]
  957.             }
  958.         }
  959.     }
  960. }
  961.  
  962. # ------------------------------------------------------------------
  963. # PROTECTED METHOD: _backward
  964. #
  965. # Internal method which moves the cursor backward by one character
  966. # jumping over the slashes and wrapping.
  967. # ------------------------------------------------------------------
  968. itcl::body iwidgets::Datefield::_backward {} {
  969.     set icursor [$itk_component(date) index insert]
  970.     if {$itk_option(-int)} {
  971.         switch $icursor {
  972.             8 {
  973.             _setField month
  974.             }
  975.             5 {
  976.             _setField year
  977.             }
  978.             0 {
  979.             _setField day
  980.             }
  981.             default {
  982.             $itk_component(date) icursor [expr {$icursor -1}]
  983.             }
  984.         }
  985.     } else {
  986.         switch $icursor {
  987.             6 {
  988.                 _setField day
  989.             }
  990.             3 {
  991.                 _setField month
  992.             }
  993.             0 {
  994.                 _setField year
  995.             }
  996.             default {
  997.                 $itk_component(date) icursor [expr {$icursor -1}]
  998.             }
  999.         }
  1000.     }
  1001. }
  1002.  
  1003. # ------------------------------------------------------------------
  1004. # PROTECTED METHOD: _lastDay month year
  1005. #
  1006. # Internal method which determines the last day of the month for
  1007. # the given month and year.  We start at 28 and go forward till
  1008. # we fail.  Crude but effective.
  1009. # ------------------------------------------------------------------
  1010. itcl::body iwidgets::Datefield::_lastDay {month year} {
  1011.     set lastone 28
  1012.  
  1013.     for {set lastone 28} {$lastone < 32} {incr lastone} {
  1014.         set nextone [expr $lastone + 1]
  1015.         if {[catch {clock scan $month/$nextone/$year}] != 0} {
  1016.             return $lastone
  1017.         }
  1018.     }
  1019. }
  1020.