[EKB] This is a simple task timer I built using [clock.tcl] (last updated 15 Dec. 2005). Here's what it looks like: [http://www.kb-creative.net/screenshots/Timer.gif] It's set to be [always on top], at least on Windows. It combines a clock with a timer. The "copy to clipboard" feature pastes the date, start time, and time elapsed (in hours) to the clipboard, separated by tabs, then resets the timer: Tue, 13 Sep 2005 17:30 0.25 The reason for this is that it can then be pasted into a table (e.g., [TkTable], Excel, OpenOffice.org Calc) for easy manipulation. It can also be pasted straight into a text file. I then type in the project & task information, and keep going. I use this to keep track of my billed time. Here's a typical workflow: 1. Click the "start/pause/stop" button when starting a task. 1. Click the "start/pause/stop" to pause & restart when taking a break. 1. When done with the task, click "paste to clipboard" and paste into log. If there's an error, then click "reset" to reset the timer. Here's the code: ###################################################### ## ## Always on top, no resize ## ###################################################### wm attributes . -topmost 1 wm resizable . 0 0 ###################################################### ## ## Buttons ## ###################################################### image create photo appclock16 -data { R0lGODlhEAAPAIQAAPwCBARCZKze7Kza5IzK3ITC1KTS5Hy2zGyuxGSmvFyW tFyavEyStEyOrESGrDx+pDRynCRqlBxejAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAAALAAAAAAQAA8AAAVOICCKQTCe KBAIgpmiwUAM7ksORUHbpHH8LV6AgCgidq/AIcFkImEJhWIxXdROAQWD0eA2 rqNAw0Emg8OPdPochrgh7HBkHg9LJHVs3R4CACH+aENyZWF0ZWQgYnkgQk1Q VG9HSUYgUHJvIHZlcnNpb24gMi41DQqpIERldmVsQ29yIDE5OTcsMTk5OC4g QWxsIHJpZ2h0cyByZXNlcnZlZC4NCmh0dHA6Ly93d3cuZGV2ZWxjb3IuY29t ADs= } image create photo apppause16 -data { R0lGODlhDwAPAIMAAPwCBARCZKze7JTO3Hy2zITC1GyqxGSmvFyavGSixEyO tESGrDx+pDRynAAAAAAAACH5BAEAAAAALAAAAAAPAA8AAARFEMhJq7016KlD FqAXgILHDQQhoioVFIYhwrJLGId4566BICIf0JVQKETFowuxWIiYTpeCwRBN qy5GoyHScjMbSQdDLl8iACH+aENyZWF0ZWQgYnkgQk1QVG9HSUYgUHJvIHZl cnNpb24gMi41DQqpIERldmVsQ29yIDE5OTcsMTk5OC4gQWxsIHJpZ2h0cyBy ZXNlcnZlZC4NCmh0dHA6Ly93d3cuZGV2ZWxjb3IuY29tADs= } image create photo record16 -data { R0lGODlhEAAQAIUAAIx+bIx6bIR6bIRyZHxyZHxuXHRqXPwCBOTe3Nze3HRm XIR2ZPz+/GxiVPz29GRaTGReVFxWTHxuZPzy5Ly2rFxSRPTq3OzexLyulFRK RKyqrPTizLyihExKRMTCxGxeVOzWvERCPFxSTOzSrLyabLSytGRaVOTKpEQ+ NLy+vMS+tMS2nLymhDw2NExGPMSulMSqjDw6NDQ2LAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAcALAAAAAAQABAAAAal QEBAMCAUDIekUhlAJBJOhWK5XCQYWEbD8aAmB9csBBKJeCVhhmOSoFQeR6Uh vLZcMBlNQppUINQTFhsXHB0eDB9cBw0JdYMgHCGHHw8PIh8JgRcXICMkkmsJ JRUmCYKcIyckKCmBG3gRKhQrGBy2qyWnLB0iFRkdHSEhKC0tJYMjkV4uGikl JajKVBGmj6mrXrErKy8wLCQkMV4VFcDCKCEtMn5BACH+aENyZWF0ZWQgYnkg Qk1QVG9HSUYgUHJvIHZlcnNpb24gMi41DQqpIERldmVsQ29yIDE5OTcsMTk5 OC4gQWxsIHJpZ2h0cyByZXNlcnZlZC4NCmh0dHA6Ly93d3cuZGV2ZWxjb3Iu Y29tADs= } ###################################################### ## ## "Minimalist" balloon help from Tcler's Wiki ## ## With modest change so that there isn't much delay ## while browsing over buttons. ## ###################################################### namespace eval balloon { variable long_delay 750 variable short_delay 50 variable delay $long_delay variable family {} } bind . { if {$balloon::family != ""} { if {[lsearch -exact $balloon::family %W] == -1} { set balloon::family {} set balloon::delay $balloon::long_delay } } } proc set_balloon {w help} { bind $w "+after \$balloon::delay [list balloon::show %W [list $help]]; set balloon::delay $balloon::short_delay; set balloon::family \[balloon::getwfamily %W\]" bind $w "+destroy %W.balloon" } # Add these to the namespace proc balloon::getwfamily {w} { return [winfo children [winfo parent $w]] } proc balloon::show {w arg} { if {[eval winfo containing [winfo pointerxy .]]!=$w} {return} set top $w.balloon catch {destroy $top} toplevel $top -bd 1 -bg black wm attributes $top -topmost 1 wm overrideredirect $top 1 if {$::tcl_platform(platform) == "macintosh"} { unsupported1 style $top floating sideTitlebar } pack [message $top.txt -aspect 10000 -bg lightyellow -padx 1 -pady 0 \ -text $arg] set wmx [expr [winfo rootx $w]+5] set wmy [expr [winfo rooty $w]+[winfo height $w]+7] wm geometry $top \ [winfo reqwidth $top.txt]x[winfo reqheight $top.txt]+$wmx+$wmy raise $top } ###################################################### ## ## Timer procs ## ###################################################### proc every {ms body} { eval $body after $ms [list every $ms $body] } proc timeElapsed {sec} { set hours [expr {$sec / 3600}] set mins [expr {($sec / 60) % 60}] return [format "%02d:%02d" $hours $mins] } # Are we timing, or not? set timer(state) 0 # Has the timer been reset? set timer(reset) 1 # Store previous time elapsed set timer(prev) 0 proc toggleTimer {b} { if {$::timer(state)} { $b config -relief flat $b config -image appclock16 set ::timer(state) 0 set ::timer(prev) [expr {$::timer(prev) + [clock sec] - $::timer(init)}] } else { $b config -relief sunken $b config -image apppause16 if {$::timer(reset)} { set ::timer(reset) 0 } set ::timer(init) [clock sec] set ::timer(state) 1 } } proc resetTimer {b} { set ::timer(reset) 1 set ::timer(elapsed) "00:00" set ::timer(init) [clock sec] set ::timer(prev) 0 } proc recordTime {b} { # Stop timing if {$::timer(state)} { toggleTimer $b } # Save info set date [clock format [clock sec] -format "%a, %d %b %Y"] set start "--:--" if [info exists ::timer(init)] { set start [fmtTime $::timer(init)] } set elapsed [unfmtTime $::timer(elapsed)] # Reset the timer resetTimer $b clipboard clear clipboard append -type STRING "$date\t$start\t$elapsed" } proc fmtTime {sec} { return [clock format $sec -format %H:%M] } # unfmtTime: Take H:M and return decimal hours proc unfmtTime {s} { set t [split $s ":"] set hr [string trimleft [lindex $t 0] "0"] if {$hr == ""} {set hr 0} set min [string trimleft [lindex $t 1] "0"] if {$min == ""} {set min 0} set hr [expr {0.01 * round(100 * ($hr + (1.0 * $min)/60))}] return [format "%.2f" $hr] } proc updateTimer {} { set ::timer(now) [fmtTime [clock sec]] if {$::timer(state)} { set elapsed_sec [expr {$::timer(prev) + [clock sec] - $::timer(init)}] set ::timer(elapsed) [timeElapsed $elapsed_sec] } } ## -- Top frame pack [frame .top] -side top pack [label .top.l -textvar timer(now) -font {Tahoma 18}] -side left -padx 6 # Buttons pack [frame .top.buttons] -side right -padx 6 set clockbutton [button .top.buttons.t -image appclock16 -relief flat \ -width 16] pack $clockbutton -side left set recordbutton [button .top.buttons.rec -image record16 -relief flat] pack $recordbutton -side left ## -- Bottom frame pack [frame .bottom] -side bottom pack [label .bottom.l -textvar timer(elapsed) -font {Tahoma 10}] $clockbutton config -command "toggleTimer $clockbutton" set_balloon $clockbutton "Start/stop timer" $recordbutton config -command "recordTime $clockbutton" set_balloon $recordbutton "Clear and save to clipboard" ###################################################### ## ## Start program! ## ###################################################### set timer(elapsed) "00:00" every 1000 updateTimer ---- [EKB] With the magic of FreeWrap, this is now available to the world as a Windows executable: [http://tasktimer.kb-creative.net/]