Version 7 of timeentry

Updated 2005-11-10 08:26:55

http://incrtcl.sourceforge.net/iwidgets/iwidgets/timeentry.gif

Docs can be found at http://incrtcl.sourceforge.net/iwidgets/iwidgets/timeentry.html and http://purl.org/tcl/home/man/iwidgets3.0/timeentry.n.html


This is a spinbox widget that only allows valid times to be entered. I tried to make this as much like the spinbox in the Windows "Date and Time" dialog. This is only for a 24 hour clock, but it wouldn´t take too much modification to support a 12 hour format. There´s probably an easier way to do this, so please edit this page if you know of one. -Paul Walton

 proc validateTime {spinbox originalString newString index adjust} {


        # Check if the colons are present.
        if { [string match {*:*:*} $newString] == 0 } {
                return 0
        }

        # Split up each field into its own variable.
        set time [split $newString :]
        set hour         [lindex $time 0]
        set minutes [lindex $time 1]
        set seconds [lindex $time 2]


        # Check if valid digits were entered in each field.
        if { $hour != ""  &&  ![string match {[0-9]} $hour]  &&  ![string match {[0-1][0-9]} $hour]  &&  ![string match {2[0-3]} $hour] } {
                #invalid hour
                return 0
        }
        if { $minutes != ""  &&  ![string match {[0-9]} $minutes]  &&  ![string match {[0-5][0-9]} $minutes] } {
                #invalid minutes
                return 0
        }
        if { $seconds != ""  &&  ![string match {[0-9]} $seconds]  &&  ![string match {[0-5][0-9]} $seconds] } {
                #invalid seconds
                return 0
        }





        # Adjust the index position of the cursor appropiately.
        set diff [expr { [string length $hour:$minutes:$seconds] - [string length $originalString] }]
        set newIndex $index
        if { $diff > 0 } {
                incr newIndex $diff
        }



        switch -- $adjust {
                up                 {set adjust 1}
                down                 {set adjust -1}
                default         {set adjust 0}
        }



        set indexColon1 [string first {:} $newString]
        set indexColon2 [string last {:} $newString]
        if { $index <= $indexColon1 } {
                set field hour
                if { $adjust != 0 } {
                        if { $hour == "" } {
                                set hour 0
                        }
                        scan $hour %d hour
                        incr hour $adjust

                        if { $hour == 24 } {
                                set hour 0
                        }
                        if { $hour == -1 } {
                                set hour 23
                        } 
                }
        }
        if { $index > $indexColon1  &&  $index <= $indexColon2 } { 
                set field minutes
                if { $adjust != 0 } {
                        if { $minutes == "" } {
                                set minutes 0
                        }
                        scan $minutes %d minutes
                        incr minutes $adjust

                        if { $minutes == 60 } {
                                set minutes 0
                        }
                        if { $minutes == -1 } {
                                set minutes 59
                        } 
                }
        }
        if { $index > $indexColon2 } {
                set field seconds
                if { $adjust != 0 } {
                        if { $seconds == "" } {
                                set seconds 0
                        }
                        scan $seconds %d seconds
                        incr seconds $adjust

                        if { $seconds == 60 } {
                                set seconds 0
                        }
                        if { $seconds == -1 } {
                                set seconds 59
                        } 
                }
        }
        if { $index == -1 } {
                set field none
        }

        if { $field != "hour"  ||  $adjust != 0 } {
                set increaseBy [expr {abs([string length $hour]-2)}]
                set hour [string repeat 0 $increaseBy]$hour
                incr newIndex $increaseBy

        }
        if { $field != "minutes"  ||  $adjust != 0 } {
                set increaseBy [expr {abs([string length $minutes]-2)}]
                set minutes [string repeat 0 $increaseBy]$minutes
                if { $indexColon2 < $index } {
                        incr newIndex $increaseBy
                }
        }
        if { $field != "seconds"  ||  $adjust != 0 } {
                set increaseBy [expr {abs([string length $seconds]-2)}]
                set seconds [string repeat 0 $increaseBy]$seconds
        }


        if { $adjust != 0 } {
                switch -- $field {
                        hour                 {set newIndex 2}
                        minutes         {set newIndex [string last {:} $hour:$minutes:$seconds]}
                        seconds         {set newIndex end}
                }
        }


        # Write the validated data with possible corrections to the spinbox.
        $spinbox configure -validate none
        $spinbox delete 0 end
        $spinbox insert 0 $hour:$minutes:$seconds
        $spinbox icursor $newIndex
        after idle [list $spinbox configure -validate all] 


        return 0
 }




 spinbox .spin -width 8 -validatecommand {validateTime %W %s %P %i ""} -validate all -command {validateTime %W %s %s [%W index insert] %d}
 .spin insert 0 00:00:00
 pack .spin

MG Nov 10th 2005 - I found a few problems with the code above, most notably that the spinner only effects the seconds. (The spin buttons effect the hours, minutes, and seconds, depending on where the insertion cursor is. This is also how the Windows "Date and Time" spinbox works. -Paul Walton) The simplest way for doing this seems to be using a constructed list of valid values:

 for {set h 0} {$h <= 23} {incr h} {
     set hour [format "%02d" $h]
      for {set m 0} {$m <= 59} {incr m} {
           set minute [format "%02d" $m]
           for {set s 0} {$s <= 59} {incr s} {
                set second [format "%02d" $s]
                lappend values "$hour:$minute:$second"
               }
            }
  }

Though that doesn't do validation if you type text into the box. I also have the code below, which works well, with the one exception that the spinbox buttons don't do a bloody thing. I only discovered that after I'd written all the validation code, though, so thought I'd post it anyway - hopefully someone can spot why they don't work (but even if not, the code works great with an entry widget instead of a spinbox). I may even get it working myself, another time, when it's not 6am ;)

 proc validateTimeMG {W V v S s P i d} {

 if { $d == "-1"} {
       return 1;
    } elseif { ![string match {[0-9]} $S] || [string range $s $i $i] == ":"} {
       return 0;
    }
 if { $d == "0" } {
      set new [string replace $s $i $i 0]
    } else {
      set new [string replace $s $i $i $S]
    }
 set parts [split $new :]
 foreach {hours minutes seconds} $parts {break}
 foreach x {hours minutes seconds} {
    set $x [format %2d [set $x]]
 }
 set num "$hours$minutes$seconds"

 if { $num > 235959 || $hours > 23 || $minutes > 59 || $seconds > 59} {
       return 0;
    }
 upvar 1 [$W cget -textvariable] textvar
 if { $d == "0" } {
      set textvar [string replace $s $i $i 0]
      $W config -validate $v
    } elseif { $d == "1" } {
      if { [string range $s $i $i] == ":" } {
           return 0;
         } else {
           set textvar $new
           $W config -validate $v
           set cursor [expr {$i + 1}]
           if { [string range $s $cursor $cursor] == ":" } {
                incr cursor
              }
           $W icursor $cursor
         }
    }
 return 0;
 }

 set myvar "15:23:48"
 pack [spinbox .s -validatecommand {validateTimeMG %W %V %v %S %s %P %i %d} -validate all -textvariable myvar]


Category Command, a part of incr Widgets