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.25The 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:
- Click the "start/pause/stop" button when starting a task.
- Click the "start/pause/stop" to pause & restart when taking a break.
- When done with the task, click "paste to clipboard" and paste into log.
######################################################
##
## 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 updateTimerEKB With the magic of Freewrap, this is now available to the world as a Windows executable: [1]
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:33Hello, 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.diffHere 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 http://wiki.tcl.tk/13772 +#Its code can be found at http://wiki.tcl.tk/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:44So 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.

