Version 6 of timeentry

Updated 2005-11-10 07:19:16

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 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