Version 10 of Task Timer

Updated 2005-12-17 01:34:58

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.
  2. Click the "start/pause/stop" to pause & restart when taking a break.
  3. 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 . <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 ]


Category Date and Time Category Application