A Little Countdown Clock

A small application written for a friend who wanted to see the seconds remaining until his contract finished. Posted here because it (nearly) answers a question on the Date and Time Issues page. MNO

LV What would be appropriate for improvements - add them in place, or create a new page?

MNO In place would be best - there is plenty of scope for improvement!


 #!/bin/sh
 # the next line restarts with wish \
        exec tclsh $0 ${1+"$@"}

 # Original Author: [MNO] at https://wiki.tcl-lang.org/ 
 # Update Author: [LV]
 # Version 2 - a series of small nits clarified

 package require Tk
 #
 ###############################################################################
 #
 # **************************
 # *** tunable parameters ***
 # **************************

 # It would be nice if these were in the option database and if command line
 # arguments were parsed and processed as well

 #
 # interval specifies how often to update the time displayed (i.e. what units
 # we are counting down in) units: ms (100 = tenths of second etc.)
 #
 set interval 10 ;# specified in milliseconds
 #
 # resync interval is how often we correct the timer back to real clock
 # seconds.  It is specified in seconds.  The default is every 15 seconds...
 #
 set resync 15 ;# seconds
 #
 # endtime can also be any date understood by tcl's [clock scan] command
 # e.g. "15 August 2003"
 #
 set endtime "Jan 18, 2038 22:14" ;# anything understood by Tcl's [clock scan] command
 #
 # tickerfont is the font used for the counter (duh!)
 #
 set tickerfont [font create -family Courier -size 18]
 #
 # counteronly=1 will cause the start button and entry field to disappear once 
 # countdown has started
 #
 set counteronly 1
 #
 # nodecorations=1 will cause the window manager decorations to disappear once
 # countdown has been started, set to 0 to keep the decorations.
 #
 set nodecorations 1
 #
 # *********************************
 # *** end of tunable parameters ***
 # *********************************

 ###############################################################################
 #
 # drag handle code - allow a window with no decoration to be moved
 #
 array set __dragdata {}
 proc init_drag { wd x y } {
     set w [winfo toplevel $wd]
     set ::__dragdata($w,x) $x
     set ::__dragdata($w,y) $y
 }
 
 proc do_drag { wd x y } {
     set w [winfo toplevel $wd]
     if { ! [info exists ::__dragdata($w,x)] } {
        init_drag $wd $x $y
     }
     set dx [expr {$x - $::__dragdata($w,x)}]
     set dy [expr {$y - $::__dragdata($w,y)}]
     regexp -- {([0-9]+)x([0-9]+)([-+][0-9]+)([-+][0-9]+)} \
            [wm geometry $w] junk ox oy gx gy
     set ngx [expr {$gx + $dx}]
     if {[string match {[0-9]*} $ngx]} {
        set ngx "+${ngx}"
     }
     set ngy [expr {$gy + $dy}]
     if {[string match {[0-9]*} $ngy]} {
        set ngy "+${ngy}"
     }
     wm geometry $w ${ox}x${oy}${ngx}${ngy}
     update idletasks
 }
 
 proc end_drag { wd } {
     set w [winfo toplevel $wd]
     catch {unset ::__dragdata($w,x)}
     catch {unset ::__dragdata($w,y)}
 }
 
 # make_drag handle makes a given widget w into a drag handle for its toplevel
 # i.e. an area that can be used to move the window around if e.g. it doesn't
 # have Window Manager Decorations.
 #
 proc make_drag_handle { w } {
     bind $w <ButtonPress-1> +[list init_drag %W %x %y]
     bind $w <B1-Motion> +[list do_drag %W %x %y]
     bind $w <ButtonRelease-1> +[list end_drag %W]
 }

 ###############################################################################
 #
 # 
 proc maybeRaise { w state } {
     switch -exact -- $state {
        "VisibilityFullyObscured" { raise $w ; update }
        "VisibilityPartiallyObscured" { raise $w ; update}
        default { ; }
     }
 }
 #
 ###############################################################################
 # 999999999 is j.random.value for initial display (gets reset once
 # the start button is pressed)
 set tleft 999999999
 #
 bind . <Visibility> +[list maybeRaise . %s]
 frame .t
 label .t.x -font $tickerfont -text " " -relief raised -borderwidth 2
 label .t.l -font $tickerfont -textvariable tleft -relief groove -borderwidth 2
 pack .t.l .t.x -side right
 
 bind .t.l <ButtonRelease-1> +startStop
 make_drag_handle .t.x
 
 pack .t
 # build the gui
 frame .f
 entry .f.e -textvariable endtime
 set running 0
 button .f.s -text "Start" -command startStop
 pack .f.e .f.s -side right
 #
 pack .f
 #
 # set and start the clock, or stop it
 proc startStop {} {
     global running endtime interval tleft nodecorations counteronly
     set running [expr { 1 - $running} ]
     if { $running == 0 } {
        .f.s configure -text "Start"
        .f.e configure -state normal
        if { $counteronly } {
            pack .f
        }
        if { $nodecorations }  {
            wm overrideredirect . 0
            wm withdraw .
            wm deiconify .
            update
        }
        raise .
        update
     } else {
        .f.s configure -text "Stop"
        .f.e configure -state disabled
        if { $counteronly } {
            pack forget .f
        }
        if { $nodecorations } {
            wm overrideredirect . 1
            wm withdraw .
            wm deiconify .
            update
        }
        raise .
        update
     }
     if { $running } {
        set tleft [expr { ( [clock scan $endtime] - [clock seconds] ) * \
                ( 1000 / $interval ) } ]
        doUpdate
        doResync
     }
 }
 # update the clock and register anpother update event...
 proc doUpdate {} {
     global interval running tleft
     incr tleft -1
     if { $running == 0 } { 
        return 
     }
     if { $tleft > 0 } {
        after $interval doUpdate
     } else {
        startStop
        return
     }
 }
 # resync the clock and schedule another resync event
 proc doResync {} {
     global resync interval running tleft endtime
     if { $running == 0 } { 
        return 
     }
     set tleft [expr { ( [clock scan $endtime] - [clock seconds] ) * \
            ( 1000 / $interval ) } ]
     after [expr { $resync * 1000 } ] doResync
 }