[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 } # Write the validated data with possible corrections to the spinbox. $spinbox configure -validate none $spinbox delete 0 end $spinbox insert 0 $hour:$minutes:$seconds if { $adjust != 0 } { switch -- $field { hour { set newIndex 2 $spinbox selection from 0 $spinbox selection to 2 } minutes { set newIndex [string last {:} $hour:$minutes:$seconds] $spinbox selection from 3 $spinbox selection to 5 } seconds { set newIndex end $spinbox selection from 6 $spinbox selection to end } default { $spinbox selection from 6 $spinbox selection to end } } } $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 bind .spin [list .spin icursor 0] pack .spin [MG] Nov 10th 2005 - I found a few problems with the code above, most notably that the spinner only effects the seconds. (Paul Walton: The spin buttons (and keyboard arrows) do effect the hours, minutes, and seconds, depending on where the insertion cursor is. This is also how the Windows "Date and Time" spinbox works. What other problems did you have with it?) [MG] Ahh, I see. I was assuming that spinning "up" one from 00:00:59 would take it to 00:01:00, and so on. The only other "problem" I had with it was the way in which it automatically pads numbers with 0's, to get them up to two characters. In the Windows clock, you can't move beyond the separating ':' with the cursor keys, you have to tab to the next one (and that's when the padding occurs). I was just suprised with this one when it suddenly padded my previous change with 0's while I was still just editing the time (as it's just one field, from the way the key bindings for movement work, whereas the Windows one is more clearly three fields). I guess that largely just comes down to user preference, the same as the confusion on how the spin buttons worked, though :) [MG] The simplest way for doing a basic version of 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. [MG] The code below now seems to work fully, including the spinbox buttons. It's a little different from the code above - inserting a character overwrites the character currently there, and moves the cursor on (past a colon, if necessary). The spinbox buttons increment (or decrement) whichever part of the time you're in, hours, minutes or seconds, but when the seconds go over 59, the minutes go up on, and ditto with the minutes, and with going down one. To use it, just use: timebox $widget $varname ?$args? where $widget is the widget path name, $varname is the name of a variable to store the value in (ala -textvariable), and $args are any other args accepted by a spinbox widget. (This could be improved so it doesn't ''have'' to use a textvariable, without a whole lot of trouble, but I don't have the time right now.) proc validateTimeMG {w validate edit current index type} { if { $type == "-1"} { return 1; } elseif { (![string match {[0-9]} $edit] || [string range $current $index $index] == ":") && ($type != "up" && $type != "down") } { return 0; } if { $type == "0" } { set new [string replace $current $index $index 0] } elseif { $type == "1" } { set new [string replace $current $index $index $edit] } elseif { $type == "up" || $type == "down" } { set new $current } else { return 0; # something broke } set parts [split $new :] foreach {hours minutes seconds} $parts {break} foreach x {hours minutes seconds} { scan [set $x] %d $x } if { $type == "up" || $type == "down" } { set incrby [expr {$type == "up" ? 1 : -1}] # find out where we are, and go up/down one if { $index > 5 } { # we're in seconds incr seconds $incrby if { $seconds < 0 } { set seconds 59 incr minutes -1 } elseif { $seconds > 59 } { set seconds 0 incr minutes 1 } } if { $index > 2 } { if { $index < 6 } { incr minutes $incrby ;# we're in minutes, not seconds } if { $minutes < 0 } { set minutes 59 incr hours -1 } elseif { $minutes > 59 } { set minutes 0 incr hours 1 } } if { $index < 3 } { incr hours $incrby ;# we're in hours, not minutes/seconds } if { $hours < 0 } { set hours 23 } elseif { $hours > 23 } { set hours 0 } set new [format %02d:%02d:%02d $hours $minutes $seconds] } set num [format %02d%02d%02d $hours $minutes $seconds] if { $num > 235959 || $hours > 23 || $minutes > 59 || $seconds > 59} { return 0; } upvar 1 [$w cget -textvariable] textvar if { $type != "1" } { set textvar $new $w config -validate $validate } elseif { $type == "1" } { if { [string range $current $index $index] == ":" } { return 0; } else { set textvar $new $w config -validate $validate set cursor [expr {$index + 1}] if { [string range $current $cursor $cursor] == ":" } { incr cursor } $w icursor $cursor } } return 0; } proc timebox {w var args} { upvar 1 $var textvar set textvar "00:00:00" uplevel 1 spinbox $w $args [list -textvariable $var -command "validateTimeMG %W \[%W cget -validate\] {} %s \[%W index insert\] %d" -validate key -validatecommand [list validateTimeMG %W %v %S %s %i %d]] $w icursor end set w } # demo pack [timebox .t myTime -foreground red] ---- Paul W: Very nice, it works well. I like the feel of it. It´s nice to be able to type in the time without having to manually move the insertion cursor. ---- [Category Command], a part of [incr Widgets]