EKB This is a simple task timer I built using clock.tcl (last updated 15 Dec. 2005). Here's what it looks like:
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:
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 . <Enter> { 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 <Enter> "+after \$balloon::delay [list balloon::show %W [list $help]]; set balloon::delay $balloon::short_delay; set balloon::family \[balloon::getwfamily %W\]" bind $w <Leave> "+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: [L1 ]
Thanks for writing this app. I've found it really useful. It's just so cool how contributions feed on each other: some simple test code, someone expands it for usefulness, and then releases it to the world.
EKB That's great! Thanks for posting this.
see also multiple timers
bigote - 2009-07-07 20:05:33
Hello, I'm quite a beginner in Tcl and it's the first time I write here so please forgive me if I do something wrong :)
With your permission I would like to share my small modification made to the Task Timer. It's a cosmetic change but now it looks like a WindowMaker applet. Well, I suppose if there's something wrong with the font size on your system, it wouldn't, but it could be corrected easily changing font face and/or size in the lines
set cur_timer [label .top.l -textvar timer(now) -font {Arial 8 bold}] and pack [label .top.timer.l -textvar timer(elapsed) -font {Tahoma 8}]
I even use this newly baked applet instead of wmcalclock. I just added another tooltip that shows the current date when pointing the mouse at the current time. I also had to adapt the code to Linux environment (added command package require Tk and commented out two identic lines wm attributes . -topmost 1).
So here goes the diff. You can copy and paste it into a file (for instance, patch.diff) and then apply the patch to the source file (let it be tasktimer.tcl) using Unix patch command after placing patch.diff next to it:
patch -p0 < patch.diff
Here you have a Windows version of patch .
--- tasktimer.tcl 2009-07-08 01:43:56.000000000 +0200 +++ ttmr.tcl 2009-07-08 01:47:37.000000000 +0200 @@ -1,9 +1,25 @@ +#!/bin/sh +# the next line restarts using wish \ +exec wish "$0" "$@" $argv;# -geometry +800+0 + +#This program is written by EKB https://wiki.tcl-lang.org/13772 +#Its code can be found at https://wiki.tcl-lang.org/14707 + +#This modification is made by Serge Yudin (xmpp:[email protected]) +#and consists of geometry changes (now it looks like a wm-applet). +#Also a tooltip added that shows the current date when pointing +#the cursor at the current time. The window size could vary depending +#on font size. Please hack it yourself if it's too big or too small. + +package require Tk ###################################################### ## ## Always on top, no resize ## ###################################################### - wm attributes . -topmost 1 +#This line is commented out because it gave me an error +#when running in Linux. (SY) +# wm attributes . -topmost 1 wm resizable . 0 0 ###################################################### @@ -87,7 +103,7 @@ set top $w.balloon catch {destroy $top} toplevel $top -bd 1 -bg black - wm attributes $top -topmost 1 +# wm attributes $top -topmost 1 wm overrideredirect $top 1 if {$::tcl_platform(platform) == "macintosh"} { unsupported1 style $top floating sideTitlebar @@ -164,7 +180,7 @@ clipboard append -type STRING "$date\t$start\t$elapsed" } proc fmtTime {sec} { - return [clock format $sec -format %H:%M] + return [clock format $sec -format %H:%M:%S] } # unfmtTime: Take H:M and return decimal hours @@ -188,10 +204,10 @@ ## -- Top frame pack [frame .top] -side top - pack [label .top.l -textvar timer(now) -font {Tahoma 18}] -side left -padx 6 - + set cur_timer [label .top.l -textvar timer(now) -font {Arial 8 bold}] + pack $cur_timer -side top -padx 2 # Buttons - pack [frame .top.buttons] -side right -padx 6 + pack [frame .top.buttons] -side bottom -padx 6 set clockbutton [button .top.buttons.t -image appclock16 -relief flat \ -width 16] pack $clockbutton -side left @@ -199,13 +215,14 @@ pack $recordbutton -side left ## -- Bottom frame - pack [frame .bottom] -side bottom - pack [label .bottom.l -textvar timer(elapsed) -font {Tahoma 10}] + pack [frame .top.timer] -side top + pack [label .top.timer.l -textvar timer(elapsed) -font {Tahoma 8}] $clockbutton config -command "toggleTimer $clockbutton" set_balloon $clockbutton "Start/stop timer" $recordbutton config -command "recordTime $clockbutton" set_balloon $recordbutton "Clear and save to clipboard" + set_balloon $cur_timer "[clock format [clock seconds] -format {%A, %d of %B %Y}]" ###################################################### ##
LV - 2016-11-16 13:11:44
So I wanted to run the original timer code on this page on a Windows 7 system. I downloaded a recent tclkit from the tclkit building site. I copied the code from the top of this page into a text file. I invoked the tclkit and the text file from within Cygwin on my desktop. A widget appeared. It shows the correct time. But when I press the play button, the numbers where I would expect to see elapsed time does not change. I added a puts statement to output the $::timer(now) and $::timer(elapsed) values, and elapsed never changes even though now does. The timer state array entry changes when the play button is pressed, and the icon goes from flat to sunken. The calculation of $elapsed_sec is occurring as expected. But the surprise, to me, is that the timer only measures hours and minutes. You have to fiddle with the code to add seconds to the elapsed timer formatting in the widget.
Just in case someone tries the code and wonders why it doesn't appear to be doing anything initially.
EMJ 2016-11-16: It's for tracking billable time, seconds don't matter, the value calculated for ::timer(elapsed) rounds down in minutes, so it only changes after a minute has passed. In other words, works as designed.