summaryrefslogtreecommitdiff
path: root/itcl/iwidgets/generic/datefield.itk
blob: 22c885d00fc6010bc5bf87e3397fe4ef290cf2ff (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
#
# Datefield
# ----------------------------------------------------------------------
# Implements a date entry field with adjustable built-in intelligence
# levels.
# ----------------------------------------------------------------------
#   AUTHOR:  Mark L. Ulferts          E-mail: mulferts@austin.dsccc.com
#
#   @(#) $Id$
# ----------------------------------------------------------------------
#            Copyright (c) 1997 DSC Technologies Corporation
# ======================================================================
#
# Permission to use, copy, modify, distribute and license this software 
# and its documentation for any purpose, and without fee or written 
# agreement with DSC, is hereby granted, provided that the above copyright 
# notice appears in all copies and that both the copyright notice and 
# warranty disclaimer below appear in supporting documentation, and that 
# the names of DSC Technologies Corporation or DSC Communications 
# Corporation not be used in advertising or publicity pertaining to the 
# software without specific, written prior permission.
# 
# DSC DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING 
# ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, AND NON-
# INFRINGEMENT. THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, AND THE
# AUTHORS AND DISTRIBUTORS HAVE NO OBLIGATION TO PROVIDE MAINTENANCE, 
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. IN NO EVENT SHALL 
# DSC BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR 
# ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, 
# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTUOUS ACTION,
# ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS 
# SOFTWARE.
# ======================================================================

#
# Usual options.
#
itk::usual Datefield {
    keep -background -borderwidth -cursor -foreground -highlightcolor \
     -highlightthickness -labelfont -textbackground -textfont 
}

# ------------------------------------------------------------------
#                               DATEFIELD
# ------------------------------------------------------------------
itcl::class iwidgets::Datefield {
    inherit iwidgets::Labeledwidget 
    
    constructor {args} {}

    itk_option define -childsitepos childSitePos Position e
    itk_option define -command command Command {}
    itk_option define -iq iq Iq high
    itk_option define -gmt gmt GMT no
    itk_option define -int int DateFormat no
    
    public method get {{format "-string"}}
    public method isvalid {}
    public method show {{date now}}

    protected method _backward {}
    protected method _focusIn {}
    protected method _forward {}
    protected method _keyPress {char sym state}
    protected method _lastDay {month year}
    protected method _moveField {direction}
    protected method _setField {field}
    protected method _whichField {}

    protected variable _cfield "month"
    protected variable _fields {month day year}
}


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

#
# Use option database to override default resources of base classes.
#
option add *Datefield.justify center widgetDefault

# ------------------------------------------------------------------
#                        CONSTRUCTOR
# ------------------------------------------------------------------
itcl::body iwidgets::Datefield::constructor {args} {
    component hull configure -borderwidth 0
    
    #   
    # Create an entry field for entering the date.
    #   
    itk_component add date {
    entry $itk_interior.date -width 10
    } {
    keep -borderwidth -cursor -exportselection \
        -foreground -highlightcolor -highlightthickness \
        -insertbackground -justify -relief -state
    
    rename -font -textfont textFont Font
    rename -highlightbackground -background background Background
    rename -background -textbackground textBackground Background
    }

    #
    # Create the child site widget.
    #
    itk_component add -protected dfchildsite {
    frame $itk_interior.dfchildsite
    } 
    set itk_interior $itk_component(dfchildsite)
    
    #
    # Add datefield event bindings for focus in and keypress events.
    #
    bind $itk_component(date) <FocusIn> [itcl::code $this _focusIn]
    bind $itk_component(date) <KeyPress> [itcl::code $this _keyPress %A %K %s]
    
    #
    # Disable some mouse button event bindings:
    #   Button Motion
    #   Double-Clicks
    #   Triple-Clicks
    #   Button2
    #
    bind $itk_component(date) <Button1-Motion>  break
    bind $itk_component(date) <Button2-Motion>  break
    bind $itk_component(date) <Double-Button>   break
    bind $itk_component(date) <Triple-Button>   break
    bind $itk_component(date) <2>       break

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

    #
    # Initialize the date to the current date.
    #
    $itk_component(date) delete 0 end

    show now
}

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

# ------------------------------------------------------------------
# OPTION: -childsitepos
#
# Specifies the position of the child site in the widget.  Valid
# locations are n, s, e, and w.
# ------------------------------------------------------------------
itcl::configbody iwidgets::Datefield::childsitepos {
    set parent [winfo parent $itk_component(date)]

    switch $itk_option(-childsitepos) {
    n {
        grid $itk_component(dfchildsite) -row 0 -column 0 -sticky ew
        grid $itk_component(date) -row 1 -column 0 -sticky nsew

        grid rowconfigure $parent 0 -weight 0
        grid rowconfigure $parent 1 -weight 1
        grid columnconfigure $parent 0 -weight 1
        grid columnconfigure $parent 1 -weight 0
    }
    
    e {
        grid $itk_component(dfchildsite) -row 0 -column 1 -sticky ns
        grid $itk_component(date) -row 0 -column 0 -sticky nsew

        grid rowconfigure $parent 0 -weight 1
        grid rowconfigure $parent 1 -weight 0
        grid columnconfigure $parent 0 -weight 1
        grid columnconfigure $parent 1 -weight 0
    }
    
    s {
        grid $itk_component(dfchildsite) -row 1 -column 0 -sticky ew
        grid $itk_component(date) -row 0 -column 0 -sticky nsew

        grid rowconfigure $parent 0 -weight 1
        grid rowconfigure $parent 1 -weight 0
        grid columnconfigure $parent 0 -weight 1
        grid columnconfigure $parent 1 -weight 0
    }
    
    w {
        grid $itk_component(dfchildsite) -row 0 -column 0 -sticky ns
        grid $itk_component(date) -row 0 -column 1 -sticky nsew

        grid rowconfigure $parent 0 -weight 1
        grid rowconfigure $parent 1 -weight 0
        grid columnconfigure $parent 0 -weight 0
        grid columnconfigure $parent 1 -weight 1
    }
    
    default {
        error "bad childsite option\
            \"$itk_option(-childsitepos)\":\
            should be n, e, s, or w"
    }
    }
}

# ------------------------------------------------------------------
# OPTION: -command
#
# Command invoked upon detection of return key press event.
# ------------------------------------------------------------------
itcl::configbody iwidgets::Datefield::command {}

# ------------------------------------------------------------------
# OPTION: -iq
#
# Specifies the level of intelligence to be shown in the actions
# taken by the date field during the processing of keypress events.
# Valid settings include high, average, and low.  With a high iq,
# the date prevents the user from typing in an invalid date.  For 
# example, if the current date is 05/31/1997 and the user changes
# the month to 04, then the day will be instantly modified for them 
# to be 30.  In addition, leap years are fully taken into account.
# With average iq, the month is limited to the values of 01-12, but
# it is possible to type in an invalid day.  A setting of low iq
# instructs the widget to do no validity checking at all during
# date entry.  With both average and low iq levels, it is assumed
# that the validity will be determined at a later time using the
# date's isvalid command.
# ------------------------------------------------------------------
itcl::configbody iwidgets::Datefield::iq {
    switch $itk_option(-iq) {
    high - average - low {
    }
    default {
        error "bad iq option \"$itk_option(-iq)\":\
                   should be high, average or low"
    }
    }
}

# ------------------------------------------------------------------
# OPTION: -int 
#
# Added by Mark Alston 2001/10/21
#
# Allows for the use of dates in "international" format: YYYY-MM-DD.
# It must be a boolean value.
# ------------------------------------------------------------------
itcl::configbody iwidgets::Datefield::int { 
    switch $itk_option(-int) {
    1 - yes - true - on {
        set _cfield "year"
        set _fields {year month day}
    }
    0 - no - false - off { }
    default {
        error "bad int option \"$itk_option(-int)\": should be boolean"
    }
    }
    show [get]
}

# ------------------------------------------------------------------
# OPTION: -gmt
#
# This option is used for GMT time.  Must be a boolean value.
# ------------------------------------------------------------------
itcl::configbody iwidgets::Datefield::gmt {
  switch $itk_option(-gmt) {
    0 - no - false - off { }
    1 - yes - true - on { }
    default {
      error "bad gmt option \"$itk_option(-gmt)\": should be boolean"
    }
  }
}

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

# ------------------------------------------------------------------
# PUBLIC METHOD: get ?format?
#
# Return the current contents of the datefield in one of two formats
# string or as an integer clock value using the -string and -clicks
# options respectively.  The default is by string.  Reference the 
# clock command for more information on obtaining dates and their 
# formats.
# ------------------------------------------------------------------
itcl::body iwidgets::Datefield::get {{format "-string"}} {
    set datestr [$itk_component(date) get]

    switch -- $format {
    "-string" {
        return $datestr
    }
    "-clicks" {
        return [clock scan $datestr]
    }
    default {
        error "bad format option \"$format\":\
                   should be -string or -clicks"
    }
    }
}

# ------------------------------------------------------------------
# PUBLIC METHOD: show date
#
# Changes the currently displayed date to be that of the date 
# argument.  The date may be specified either as a string or an
# integer clock value.  Reference the clock command for more 
# information on obtaining dates and their formats.
# ------------------------------------------------------------------
itcl::body iwidgets::Datefield::show {{date "now"}} {
    $itk_component(date) delete 0 end
    if {$itk_option(-int)} {
        set format {%Y-%m-%d}
    } else {
        set format {%m/%d/%Y}
    }

    if {$date == "now"} {
        set seconds [::clock seconds]
        $itk_component(date) insert end \
            [clock format $seconds -format "$format" -gmt $itk_option(-gmt)]

    } elseif { $itk_option(-iq) != "low" } {
        if {[catch {::clock format $date}] == 0} {
            set seconds $date
        } elseif {[catch {set seconds [::clock scan $date -gmt \
                $itk_option(-gmt)]}] != 0} {
            error "bad date: \"$date\", must be a valid date\
            string, clock clicks value or the keyword now"
        }
        $itk_component(date) insert end \
            [clock format $seconds -format "$format" -gmt $itk_option(-gmt)]
    } else {
        # Note that it doesn't matter what -int is set to.
        $itk_component(date) insert end $date
    }

    if {$itk_option(-int)} {
        _setField year
    } else {
        _setField month
    }

    return
}

# ------------------------------------------------------------------
# PUBLIC METHOD: isvalid
#
# Returns a boolean indication of the validity of the currently
# displayed date value.  For example, 3/3/1960 is valid whereas
# 02/29/1997 is invalid.
# ------------------------------------------------------------------
itcl::body iwidgets::Datefield::isvalid {} {
    if {[catch {clock scan [$itk_component(date) get]}] != 0} {
        return 0
    } else {
        return 1
    }
}

# ------------------------------------------------------------------
# PROTECTED METHOD: _focusIn
#
# This method is bound to the <FocusIn> event.  It resets the 
# insert cursor and field settings to be back to their last known
# positions.
# ------------------------------------------------------------------
itcl::body iwidgets::Datefield::_focusIn {} {
    _setField $_cfield
}

# ------------------------------------------------------------------
# PROTECTED METHOD: _keyPress 
#
# This method is the workhorse of the class.  It is bound to the
# <KeyPress> event and controls the processing of all key strokes.
# ------------------------------------------------------------------
itcl::body iwidgets::Datefield::_keyPress {char sym state} {
    #
    #  Determine which field we are in currently.  This is needed
    # since the user may have moved to this position via a mouse
    # selection and so it would not be in the position we last 
    # knew it to be.
    #
    _whichField 

    #
    # If we are using an international date the split char is "-" 
    # otherwise it is "/".
    #
    if {$itk_option(-int)} {
        set split_char "-"
    } else {
        set split_char "/"
    }


    #
    # Set up a few basic variables we'll be needing throughout the
    # rest of the method such as the position of the insert cursor
    # and the currently displayed day, month, and year.
    #
    set icursor [$itk_component(date) index insert]
    set splist [split [$itk_component(date) get] "$split_char"]


    # A bunch of added variables to allow for the use of int dates
    if {$itk_option(-int)} {
        set order {year month day}
        set year [lindex $splist 0]
        set month [lindex $splist 1]
        set day [lindex $splist 2]
        set year_start_pos 0
        set year_second_pos 1
        set year_third_pos 2
        set year_fourth_pos 3
        set year_end_pos 4
        set month_start_pos 5
        set month_second_pos 6
        set month_end_pos 7
        set day_start_pos 8
        set day_second_pos 9
        set day_end_pos 10
    } else {
        set order {month day year}
        set month [lindex $splist 0]
        set day [lindex $splist 1]
        set year [lindex $splist 2]
        set month_start_pos 0
        set month_second_pos 1
        set month_end_pos 2
        set day_start_pos 3
        set day_second_pos 4
        set day_end_pos 5
        set year_start_pos 6
        set year_second_pos 7
        set year_third_pos 8
        set year_fourth_pos 9
        set year_end_pos 10
    }


    #
    # Process numeric keystrokes.  This involes a fair amount of 
    # processing with step one being to check and make sure we
    # aren't attempting to insert more that 10 characters.  If
    # so ring the bell and break.
    #
    if {[regexp {[0-9]} $char]} {
        if {[$itk_component(date) index insert] == 10} {
            bell
            return -code break
        }

        #
        # If we are currently in the month field then we process the
        # number entered based on the cursor position.  If we are at
        # at the first position and our iq is low, then accept any 
        # input.  
        #
        if {$_cfield == "month"} {

            if {[$itk_component(date) index insert] == $month_start_pos} {
                if {$itk_option(-iq) == "low"} {
                $itk_component(date) delete $month_start_pos
                $itk_component(date) insert $month_start_pos $char
            } else {            
                #
                # Otherwise, we're slightly smarter.  If the number
                # is less than two insert it at position zero.  If 
                # this makes the month greater than twelve, set the 
                # number at position one to zero which makes in 
                # effect puts the month back in range.  
                #
                regsub {([0-9])([0-9])} $month "$char\\2" month2b

                if {$char < 2} {
                    $itk_component(date) delete $month_start_pos
                    $itk_component(date) insert $month_start_pos $char

                    if {$month2b > 12} {
                        $itk_component(date) delete $month_second_pos
                        $itk_component(date) insert $month_second_pos 0
                        $itk_component(date) icursor $month_second_pos
                    } elseif {$month2b == "00"} {
                        $itk_component(date) delete $month_second_pos
                        $itk_component(date) insert $month_second_pos 1
                        $itk_component(date) icursor $month_second_pos
                    }               

                    #
                    # Finally, if the number is greater than one we'll 
                    # assume that they really mean to be entering a zero
                    # followed by their number, do so for them, and 
                    # proceed to skip to the next field which is the 
                    # day field.
                    #
                } else {
                    $itk_component(date) delete $month_start_pos $month_end_pos
                    $itk_component(date) insert $month_start_pos 0$char
                    _setField day
                }
            }
            
            #
            # Else, we're at the second month position.  Again, if we aren't
            # too smart, let them enter anything.  Otherwise, if the 
            # number makes the month exceed twelve, set the month to
            # zero followed by their number to get it back into range.
            #
        } else {
            regsub {([0-9])([0-9])} $month "\\1$char" month2b
        
            if {$itk_option(-iq) == "low"} {
                $itk_component(date) delete $month_second_pos
                $itk_component(date) insert $month_second_pos $char
            } else {
                if {$month2b > 12} {
                    $itk_component(date) delete $month_start_pos $month_end_pos
                    $itk_component(date) insert $month_start_pos 0$char
                } elseif {$month2b == "00"} {
                    bell
                    return -code break
                } else {
                    $itk_component(date) delete $month_second_pos
                    $itk_component(date) insert $month_second_pos $char
                }           
            }
            _setField day
        }

        # 
        # Now, the month processing is complete and if we're of a
        # high level of intelligence, then we'll make sure that the
        # current value for the day is valid for this month.  If
        # it is beyond the last day for this month, change it to
        # be the last day of the new month.
        #
        if {$itk_option(-iq) == "high"} {
            set splist [split [$itk_component(date) get] "$split_char"]
            set month [lindex $splist [lsearch $order month]]
            if {$day > [set endday [_lastDay $month $year]]} {
                set icursor [$itk_component(date) index insert]
                $itk_component(date) delete $day_start_pos $day_end_pos
                $itk_component(date) insert $day_start_pos $endday
                $itk_component(date) icursor $icursor
            }
        }
        
        #
        # Finally, return with a code of break to stop any normal
        # processing in that we've done all that is necessary.
        #
        return -code break
    }

    #
    # This next block of code is for processing of the day field
    # which is quite similar is strategy to that of the month.
    #
    if {$_cfield == "day"} {
        if {$itk_option(-iq) == "high"} {
            set endofMonth [_lastDay $month $year]
        } else {
            set endofMonth 31
        }

        #
        # If we are at the first cursor position for the day 
        # we are processing 
        # the first character of the day field.  If we have an iq 
        # of low accept any input.
        #
        if {[$itk_component(date) index insert] == $day_start_pos} {
            if {$itk_option(-iq) == "low"} {
                $itk_component(date) delete $day_start_pos
                $itk_component(date) insert $day_start_pos $char
            
            } else {

                #
                # If the day to be is double zero, then make the
                # day be the first.
                #
                regsub {([0-9])([0-9])} $day "$char\\2" day2b

                if {$day2b == "00"} {
                    $itk_component(date) delete $day_start_pos $day_end_pos
                    $itk_component(date) insert $day_start_pos 01
                    $itk_component(date) icursor $day_second_pos
                    #
                    # Otherwise, if the character is less than four 
                    # and the month is not Feburary, insert the number 
                    # and if this makes the day be beyond the valid 
                    # range for this month, than set to be back in 
                    # range.  
                    #
                } elseif {($char < 4) && ($month != "02")} {
                    $itk_component(date) delete $day_start_pos
                    $itk_component(date) insert $day_start_pos $char
            
                    if {$day2b > $endofMonth} {
                        $itk_component(date) delete $day_second_pos
                        $itk_component(date) insert $day_second_pos 0
                        $itk_component(date) icursor $day_second_pos
                    } 
            
                    #
                    # For Feburary with a number to be entered of 
                    # less than three, make sure the number doesn't 
                    # make the day be greater than the correct range
                    # and if so adjust the input. 
                    #
                } elseif {$char < 3} {
                    $itk_component(date) delete $day_start_pos
                    $itk_component(date) insert $day_start_pos $char
                    if {$day2b > $endofMonth} {
                        $itk_component(date) delete $day_start_pos $day_end_pos
                        $itk_component(date) insert $day_start_pos $endofMonth
                        $itk_component(date) icursor $day_second_pos
                    } 

                    #
                    # Finally, if the number is greater than three,
                    # set the day to be zero followed by the number 
                    # entered and proceed to the year field or end.
                    #
                } else {
                    $itk_component(date) delete $day_start_pos $day_end_pos
                    $itk_component(date) insert $day_start_pos 0$char
                    $itk_component(date) icursor $day_end_pos
                    if {!$itk_option(-int)} {
                        _setField year
                    }
                }
            }
            #
            # Else, we're dealing with the second number in the day
            # field.  If we're not too bright accept anything, otherwise
            # if the day is beyond the range for this month or equal to
            # zero then ring the bell.
            #
        } else {
            regsub {([0-9])([0-9])} $day "\\1$char" day2b
        
            if {($itk_option(-iq) != "low") && \
                (($day2b > $endofMonth) || ($day2b == "00"))} {
                bell
            } else {
                $itk_component(date) delete $day_second_pos
                $itk_component(date) insert $day_second_pos $char
                $itk_component(date) icursor $day_end_pos
                if {!$itk_option(-int)} {
                    _setField year
                }
            }
        }

        #
        # Return with a code of break to prevent normal processing. 
        #
        return -code break
    }

    #
    # This month and day we're tough, the code for the year is 
    # comparitively simple.  Accept any input and if we are really
    # sharp, then make sure the day is correct for the month
    # given the year.  In short, handle leap years.
    #
    if {$_cfield == "year"} {
        if {$itk_option(-iq) == "low"} {
            $itk_component(date) delete $icursor
            $itk_component(date) insert $icursor $char
        } else {
            set prevdate [get]
            if {[$itk_component(date) index insert] == $year_start_pos} {
                set yrdgt [lindex [split [lindex \
                [split $prevdate "$split_char"] [lsearch $order year]] ""] 0]
                if {$char != $yrdgt} {
                    if {$char == 1} {
                        $itk_component(date) delete $icursor $year_end_pos
                        $itk_component(date) insert $icursor 1999
                    } elseif {$char == 2} {
                        $itk_component(date) delete $icursor $year_end_pos
                        $itk_component(date) insert $icursor 2000
                    } else {
                        bell
                        return -code break
                    }
                }

                $itk_component(date) icursor $year_second_pos
                return -code break
            }
        
            $itk_component(date) delete $icursor
            $itk_component(date) insert $icursor $char


            if {[catch {clock scan [get]}] != 0} {
                $itk_component(date) delete $year_start_pos $year_end_pos
                $itk_component(date) insert $year_start_pos \
                [lindex [split $prevdate "$split_char"] [lsearch $order year]]
                $itk_component(date) icursor $icursor

                bell
                return -code break
            }

            if {$itk_option(-iq) == "high"} {
                set splist [split [$itk_component(date) get] "$split_char"]
                set year [lindex $splist [lsearch $order year]]

                if {$day > [set endday [_lastDay $month $year]]} {
                    set icursor [$itk_component(date) index insert]
                    $itk_component(date) delete $day_start_pos $day_end_pos
                    $itk_component(date) insert $day_start_pos $endday
                    $itk_component(date) icursor $icursor
                }
            }
        }
        if {$itk_option(-int)} {
            if {$icursor == $year_fourth_pos } {
                _setField month
            }
        }
        return -code break
    }
    
    #
    # Process the plus and the up arrow keys.  They both yeild the same
    # effect, they increment the day by one.
    #
    } elseif {($sym == "plus") || ($sym == "Up")} {
        if {[catch {show [clock scan "1 day" -base [get -clicks]]}] != 0} {
            bell
        }
        return -code break
    
        #
        # Process the minus and the down arrow keys which decrement the day.
        #
    } elseif {($sym == "minus") || ($sym == "Down")} {
        if {[catch {show [clock scan "-1 day" -base [get -clicks]]}] != 0} {
            bell
        }
        return -code break

        #
        # A tab key moves the day/month/year (or year/month/day) field
        # forward by one unless
        # the current field is the last field.  In that case we'll let tab
        # do what is supposed to and pass the focus onto the next widget.
        #
    } elseif {($sym == "Tab") && ($state == 0)} {
        if {$_cfield != "[lindex $order 2]"} {
            _moveField forward
            return -code break
        } else {
            _setField "[lindex $order 0]"
            return -code continue
        }

        #
        # A ctrl-tab key moves the day/month/year field backwards by one 
        # unless the current field is the the first field.  In that case we'll
        # let tab take the focus to a previous widget.
        #
    } elseif {($sym == "Tab") && ($state == 4)} {
        if {$_cfield != "[lindex $order 0]"} {
            _moveField backward
            return -code break
        } else {
            set _cfield "[lindex $order 0]"
            return -code continue
        }

        #
        # A right arrow key moves the insert cursor to the right one.
        #
    } elseif {$sym == "Right"} {
        _forward
        return -code break

        #
        # A left arrow, backspace, or delete key moves the insert cursor 
        # to the left one.  This is what you expect for the left arrow
        # and since the whole widget always operates in overstrike mode,
        # it makes the most sense for backspace and delete to do the same.
        #
    } elseif {$sym == "Left" || $sym == "BackSpace" || $sym == "Delete"} {
        _backward
        return -code break

    } elseif {($sym == "Control_L") || ($sym == "Shift_L") || \
            ($sym == "Control_R") || ($sym == "Shift_R")} {
        return -code break

        #
        # A Return key invokes the optionally specified command option.
        #
    } elseif {$sym == "Return"} {
        uplevel #0 $itk_option(-command)
        return -code break 
    } else {
        bell
        return -code break
    }
}

# ------------------------------------------------------------------
# PROTECTED METHOD: _setField field
#
# Internal method which adjusts the field to be that of the 
# argument, setting the insert cursor appropriately.
# ------------------------------------------------------------------
itcl::body iwidgets::Datefield::_setField {field} {
    set _cfield $field
    
    if {$itk_option(-int)} {
        set year_pos 2
        set month_pos 5
        set day_pos 8
    } else {
        set month_pos 0
        set day_pos 3
        set year_pos 8
    }
        
    
    switch $field {
        "month" {
            $itk_component(date) icursor $month_pos
        }
        "day" {
            $itk_component(date) icursor $day_pos
        }
        "year" {
            $itk_component(date) icursor $year_pos
        }
        default {
            error "bad field: \"$field\", must be month, day or year"
        }
    }
}

# ------------------------------------------------------------------
# PROTECTED METHOD: _moveField
#
# Internal method for moving the field forward or backward by one.
# ------------------------------------------------------------------
itcl::body iwidgets::Datefield::_moveField {direction} {

    set index [lsearch $_fields $_cfield]

    if {$direction == "forward"} {
        set newIndex [expr {$index + 1}]
    } else {
        set newIndex [expr {$index - 1}]
    }

    if {$newIndex == [llength $_fields]} {
        set newIndex 0
    }
    if {$newIndex < 0} {
        set newIndex [expr {[llength $_fields] - 1}]
    }

    _setField [lindex $_fields $newIndex]

    return
}

# ------------------------------------------------------------------
# PROTECTED METHOD: _whichField
#
# Internal method which returns the current field that the cursor
# is currently within.
# ------------------------------------------------------------------
itcl::body iwidgets::Datefield::_whichField {} {
    set icursor [$itk_component(date) index insert]

    if {$itk_option(-int)} {
        switch $icursor {
            0 - 1 - 2 - 3 {
                set _cfield "year"
            }
            5 - 6 {
                set _cfield "month"
            }
            8 - 9 {
                set _cfield "day"
            }
        }
    } else {
        switch $icursor {
            0 - 1 {
            set _cfield "month"
            }
            3 - 4 {
            set _cfield "day"
            }
            6 - 7 - 8 - 9 {
            set _cfield "year"
            }
        }
    }
}

# ------------------------------------------------------------------
# PROTECTED METHOD: _forward
#
# Internal method which moves the cursor forward by one character
# jumping over the slashes and wrapping.
# ------------------------------------------------------------------
itcl::body iwidgets::Datefield::_forward {} {
    set icursor [$itk_component(date) index insert]

    if {$itk_option(-int)} {
        switch $icursor {
            3 {
                _setField month
            }
            6 {
            _setField day
            }
            9 - 10 {
            _setField year
            }
            default {
            $itk_component(date) icursor [expr {$icursor + 1}]
            }
        }
    } else {
        switch $icursor {
            1 {
            _setField day
            }
            4 {
            _setField year
            }
            9 - 10 {
            _setField month
            }
            default {
            $itk_component(date) icursor [expr {$icursor + 1}]
            }
        }
    }
}

# ------------------------------------------------------------------
# PROTECTED METHOD: _backward
#
# Internal method which moves the cursor backward by one character
# jumping over the slashes and wrapping.
# ------------------------------------------------------------------
itcl::body iwidgets::Datefield::_backward {} {
    set icursor [$itk_component(date) index insert]
    if {$itk_option(-int)} {
        switch $icursor {
            8 {
            _setField month
            }
            5 {
            _setField year
            }
            0 {
            _setField day
            }
            default {
            $itk_component(date) icursor [expr {$icursor -1}]
            }
        }
    } else {
        switch $icursor {
            6 {
                _setField day
            }
            3 {
                _setField month
            }
            0 {
                _setField year
            }
            default {
                $itk_component(date) icursor [expr {$icursor -1}]
            }
        }
    }
}

# ------------------------------------------------------------------
# PROTECTED METHOD: _lastDay month year
#
# Internal method which determines the last day of the month for
# the given month and year.  We start at 28 and go forward till
# we fail.  Crude but effective.
# ------------------------------------------------------------------
itcl::body iwidgets::Datefield::_lastDay {month year} {
    set lastone 28

    for {set lastone 28} {$lastone < 32} {incr lastone} {
        set nextone [expr $lastone + 1]
        if {[catch {clock scan $month/$nextone/$year}] != 0} {
            return $lastone
        }
    }
}