#!/usr/bin/wishx -f
###############################################################################
# timers
#
# Usage: timers [RcFilename]
#
# 1.2   01 Jun 1996  Ken Neighbors  add capability for countdown timers
#                                   (suggested by Benjamin Peikes)
#                                   process optional argument RcFilename
# 1.1   02 May 1996  Ken Neighbors  modify to work in Tk 4.0
# 1.0.3 26 Feb 1996  Ken Neighbors  fix bug with destroy clocks maybe this time
# 1.0.2 21 Feb 1996  Ken Neighbors  fix bug with destroy clocks
# 1.0.1 19 Jan 1996  Ken Neighbors  fix bug with bitmaps
# 1.0   18 Jan 1996  Ken Neighbors  released
###############################################################################
#  Copyright (C) 1996  W. Ken Neighbors III - ken@best.com
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
###############################################################################

set Version "Version 1.2"
set Date "1 Jun 1996"

###############################################################################
# Defaults:

# file to store timing information
set RcFilename "~/.timers"

# set location of up and down arrow bitmaps
regsub "\[^/\]*$" "@$argv0" "timers-up.xbm" UpBitmap
regsub "\[^/\]*$" "@$argv0" "timers-dn.xbm" DnBitmap
#set UpBitmap "@/home/ken/bin/timers-up.xbm"
#set DnBitmap "@/home/ken/bin/timers-dn.xbm"

set FontSize 120
set FontSize 140

set MenuFont "*-helvetica-medium-r-normal-*-*-$FontSize-*"
set ButtonFont "*-helvetica-medium-r-normal-*-*-$FontSize-*"
set TimeFont "*-courier-bold-r-normal-*-*-$FontSize-*-*-*-*-*-*"
set NameFont "*-helvetica-medium-r-normal-*-*-$FontSize-*"

# DisplayOrder contains the order of buttons and text in each clock.
# The names of these elements are:
#   select name goStop time addSubtract colonPeriod reset
set DisplayOrder {select name goStop reset colonPeriod time addSubtract}
set DisplayOrderLength [llength $DisplayOrder]
set DisplayOrder1 {select name}
set DisplayOrder2 {goStop reset colonPeriod time addSubtract}
set DisplayOrderLength1 [llength $DisplayOrder1]
set DisplayOrderLength2 [llength $DisplayOrder2]

# what widgets to show by default on start-up
set Rows 1
set Show(goStop) 1
set Show(addSubtract) 1
set Show(colonPeriod) 1
set Show(reset) 1

# these should always be shown
set Show(time) 1
set Show(name) 1
set Show(select) 1

###############################################################################
# Initialization

set Clocks {}
set DestroyedClocks {}
set UpdateCounter 0
set DN 0

wm title . "timers"

###############################################################################
# The Menu Bar

frame .mbar -relief raised -bd 2
pack .mbar -side top -fill x

menubutton .mbar.action -text "Action" -underline 0 -menu .mbar.action.menu \
    -font $MenuFont
menubutton .mbar.clock -text "Clock" -underline 0 -menu .mbar.clock.menu \
    -font $MenuFont
menubutton .mbar.display -text "Display" -underline 0 -menu .mbar.display.menu \
    -font $MenuFont
menubutton .mbar.help -text "Help" -underline 0 -menu .mbar.help.menu \
    -font $MenuFont
pack .mbar.clock .mbar.action .mbar.display -side left
pack .mbar.help -side right

#------------------------------------------------------------------------------
# Clock menu
menu .mbar.clock.menu
.mbar.clock.menu add command -label "New" -underline 0 \
    -font $MenuFont -accelerator "Ctrl+n" -command MakeClock
bind all <Control-n> MakeClock
.mbar.clock.menu add command -label "Destroy" -underline 0 \
    -font $MenuFont -accelerator "Ctrl+d" -command DestroyClocks
bind all <Control-d> DestroyClocks
.mbar.clock.menu add command -label "Undestroy" -underline 0 \
    -font $MenuFont -accelerator "Ctrl+u" -command UndestroyClocks
bind all <Control-u> UndestroyClocks
.mbar.clock.menu add separator

.mbar.clock.menu add command -label "Move Back" -underline 5 \
    -font $MenuFont -accelerator "Ctrl+b" -command {MoveClocks up}
bind all <Control-b> {MoveClocks up}
.mbar.clock.menu add command -label "Move Forward" -underline 5 \
    -font $MenuFont -accelerator "Ctrl+f" -command {MoveClocks down}
bind all <Control-f> {MoveClocks down}

.mbar.clock.menu add separator
.mbar.clock.menu add command -label "Hide" -underline 0 \
    -font $MenuFont -command HideClocks
bind all <Control-h> HideClocks
.mbar.clock.menu add cascade -label "Show" -underline 0 \
    -menu .mbar.clock.menu.show -font $MenuFont -command MakeShowMenu
.mbar.clock.menu add separator

.mbar.clock.menu add command -label "Select All" -underline 7 \
    -font $MenuFont -command SelectAll
.mbar.clock.menu add command -label "Deselect All" -underline 10 \
    -font $MenuFont -command DeselectAll

.mbar.clock.menu add separator
.mbar.clock.menu add command -label "Exit" -underline 1 -command exit \
    -font $MenuFont -accelerator "Ctrl+x"
bind all <Control-x> exit

menu .mbar.clock.menu.show

proc MakeShowMenu {} {
    global MenuFont Clock Clocks

    .mbar.clock.menu.show delete 0 last
    .mbar.clock.menu.show add command -label "all clocks" -underline 0 \
	-command ShowAllClocks -font $MenuFont

    foreach CN $Clocks {
	if { ! [winfo exists .clock$CN] } {
	    .mbar.clock.menu.show add command -label $Clock($CN,Name) \
		-font $MenuFont -command "ShowClock $CN"
	}
    }
}

#------------------------------------------------------------------------------
# Action menu
menu .mbar.action.menu
.mbar.action.menu add command -label "Go" -underline 0 \
    -font $MenuFont -accelerator "Ctrl+g" -command GoClocks
bind all <Control-g> GoClocks
.mbar.action.menu add command -label "Stop" -underline 0 \
    -font $MenuFont -accelerator "Ctrl+s" -command StopClocks
bind all <Control-s> StopClocks
.mbar.action.menu add command -label "Reset" -underline 0 \
    -font $MenuFont -command ResetClocks
.mbar.action.menu add separator

.mbar.action.menu add cascade -label "Add" -underline 0 \
    -menu .mbar.action.menu.add -font $MenuFont
menu .mbar.action.menu.add
.mbar.action.menu.add add command -label "5 minutes" -underline 0 \
    -command {AddSubtractClocks 5} -font $MenuFont
.mbar.action.menu.add add command -label "10 minutes" -underline 0 \
    -command {AddSubtractClocks 10} -font $MenuFont
.mbar.action.menu.add add command -label "30 minutes" -underline 0 \
    -command {AddSubtractClocks 30} -font $MenuFont
.mbar.action.menu.add add command -label "60 minutes" -underline 0 \
    -command {AddSubtractClocks 60} -font $MenuFont

.mbar.action.menu add cascade -label "Subtract" -underline 0 \
    -menu .mbar.action.menu.subtract -font $MenuFont
menu .mbar.action.menu.subtract
.mbar.action.menu.subtract add command -label "5 minutes" -underline 0 \
    -command {AddSubtractClocks -5} -font $MenuFont
.mbar.action.menu.subtract add command -label "10 minutes" -underline 0 \
    -command {AddSubtractClocks -10} -font $MenuFont
.mbar.action.menu.subtract add command -label "30 minutes" -underline 0 \
    -command {AddSubtractClocks -30} -font $MenuFont
.mbar.action.menu.subtract add command -label "60 minutes" -underline 0 \
    -command {AddSubtractClocks -60} -font $MenuFont

.mbar.action.menu add cascade -label "Format" -underline 0 \
    -menu .mbar.action.menu.colonPeriod -font $MenuFont
menu .mbar.action.menu.colonPeriod
.mbar.action.menu.colonPeriod add command -label "Colon" -underline 0 \
    -command {ColonPeriodClocks ":"} -font $MenuFont
.mbar.action.menu.colonPeriod add command -label "Period" -underline 0 \
    -command {ColonPeriodClocks "."} -font $MenuFont

#------------------------------------------------------------------------------
# Display menu
menu .mbar.display.menu
.mbar.display.menu add checkbutton -label "Go/Stop Button" -underline 0 \
    -variable Show(goStop) -font $MenuFont \
    -command {UpdateWidgets goStop}
.mbar.display.menu add checkbutton -label "Add/Subtract Buttons" -underline 0 \
    -variable Show(addSubtract) -font $MenuFont \
    -command {UpdateWidgets addSubtract}
.mbar.display.menu add checkbutton -label "Colon/Period Button" -underline 0 \
    -variable Show(colonPeriod) -font $MenuFont \
    -command {UpdateWidgets colonPeriod}
.mbar.display.menu add checkbutton -label "Reset Button" -underline 0 \
    -variable Show(reset) -font $MenuFont \
    -command {UpdateWidgets reset}
.mbar.display.menu add separator
.mbar.display.menu add radiobutton -label "One Row" -underline 0 \
    -variable Rows -value 1 -font $MenuFont -command UpdateDisplay
.mbar.display.menu add radiobutton -label "Two Row" -underline 0 \
    -variable Rows -value 2 -font $MenuFont -command UpdateDisplay

#------------------------------------------------------------------------------
# Help menu
menu .mbar.help.menu
.mbar.help.menu add command -label "About" -underline 0 -command MakeAboutBox \
    -font $MenuFont

#------------------------------------------------------------------------------
tk_menuBar .mbar .mbar.clock .mbar.action .mbar.display .mbar.help
focus .mbar

#------------------------------------------------------------------------------
proc UpdateMenuStates {} {
    global Clock Clocks

    set AffectedMenus {}
    lappend AffectedMenus {.mbar.clock.menu entryconfigure "Destroy"}
    lappend AffectedMenus {.mbar.clock.menu entryconfigure "Move Back"}
    lappend AffectedMenus {.mbar.clock.menu entryconfigure "Move Forward"}
    lappend AffectedMenus {.mbar.clock.menu entryconfigure "Hide"}
    lappend AffectedMenus {.mbar.action.menu entryconfigure "Go"}
    lappend AffectedMenus {.mbar.action.menu entryconfigure "Stop"}
    lappend AffectedMenus {.mbar.action.menu entryconfigure "Reset"}
    lappend AffectedMenus {.mbar.action.menu entryconfigure "Format"}
    lappend AffectedMenus {.mbar.action.menu entryconfigure "Add"}
    lappend AffectedMenus {.mbar.action.menu entryconfigure "Subtract"}

    set Enable 0
    foreach CN $Clocks {
	if { $Clock($CN,Selected) } {
	    set Enable 1
	}
    }
    foreach Menu $AffectedMenus {
	if { $Enable } {
	    eval $Menu -state normal
	} else {
	    eval $Menu -state disabled
	}
    }

    # find out if "Show" menu should be enabled or disabled
    set Disabled 1
    foreach CN $Clocks {
	if { ! [winfo exists .clock$CN] } {
	    set Disabled 0
	}
    }
    if { $Disabled } {
	.mbar.clock.menu entryconfigure "Show" -state disabled
    } else {
	.mbar.clock.menu entryconfigure "Show" -state normal
    }

}

###############################################################################
# Window Display Procedures

#------------------------------------------------------------------------------
proc MakeAboutBox {} {
    global Version Date ButtonFont FontSize

    toplevel .about
    wm title .about "About timers"
    wm transient .about .
    frame .about.msg -relief ridge -bd 2
    message .about.msg1 -width 5c -justify center -bg lightgreen \
	-text "Timers  $Version" -font "*-times-bold-r-normal-*-*-$FontSize-*"
    message .about.msg2 -width 5c -justify center -bg lightgreen \
	-text "$Date" -font "*-times-medium-r-normal-*-*-$FontSize-*"
    message .about.msg3 -width 5c -justify center -bg lightgreen \
	-text "W. Ken Neighbors III" \
	-font "*-times-medium-i-normal-*-*-$FontSize-*"

    button .about.exit -text Ok -command {destroy .about} \
	-font $ButtonFont -bg lightblue
    pack .about.msg1 .about.msg2 .about.msg3 -side top -in .about.msg -fill x
    pack .about.msg .about.exit -side top -padx 2m -pady 2m -fill x

    grab set .about

    tkwait window .about
}

#------------------------------------------------------------------------------
# make new clock
proc MakeClock { {Name ""} } {
    global Clock Clocks DestroyedClocks

    # find unused clock number
    set CN 0
    set testCN 1
    set UsedClocks  [concat $Clocks $DestroyedClocks]
    while { ! $CN } {
	if { [lsearch -exact $UsedClocks $testCN] == -1 } {
	    set CN $testCN
	    unset testCN
	} else {
	    incr testCN
	}
    }
    set Clocks [linsert $Clocks 0 $CN]

    # initialize variables
    if { $Name == "" } {
	set Clock($CN,Name) "Clock $CN"
    } else {
	set Clock($CN,Name) $Name
    }
    set Clock($CN,Time) " 00:00:00"
    set Clock($CN,GoStop) "S"
    set Clock($CN,ColonPeriod) ":"
    set Clock($CN,Selected) 0
    set Clock($CN,Elapsed) 0
    set Clock($CN,Start) 0

    ShowClock $CN
    focus .clock$CN.name
    UpdateFile
}

#------------------------------------------------------------------------------
# show clock
proc ShowClock { CN {UpdateFile "FileUpdate"}} {
    global Clock Clocks Rows Show
    global DisplayOrder DisplayOrder1 DisplayOrder2

    # if clock is already displayed, destroy its window
    if { [winfo exists .clock$CN] } {
	destroy .clock$CN
    }

    # create widgets and pack them
    frame .clock$CN -relief raised -bd 1
    if { $Rows == 1 } {
	foreach Widget $DisplayOrder {
	    if { $Show($Widget) } {
		CreateWidget $CN $Widget
		PackWidget $CN $Widget
	    }
	}
    } else {
	frame .clock$CN.top
	lower .clock$CN.top
	foreach Widget $DisplayOrder1 {
	    if { $Show($Widget) } {
		CreateWidget $CN $Widget
		PackWidget $CN $Widget
	    }
	}
	frame .clock$CN.bottom
	lower .clock$CN.bottom
	foreach Widget $DisplayOrder2 {
	    if { $Show($Widget) } {
		CreateWidget $CN $Widget
		PackWidget $CN $Widget
	    }
	}
	pack .clock$CN.top .clock$CN.bottom -side top -anchor nw \
	    -fill x -expand 1
    }

    # find out where this clock goes in the list
    set ClockCount [llength $Clocks]
    set BeforeIndex [lsearch -exact $Clocks $CN]
    if { $BeforeIndex != -1 } {
	incr BeforeIndex
	set BeforeClock [lindex $Clocks $BeforeIndex]
    } else {
	set BeforeIndex [expr $ClockCount + 1]
    }
    while { ! [winfo exists .clock$BeforeClock] && \
		$BeforeIndex < $ClockCount } {
	incr BeforeIndex
	set BeforeClock [lindex $Clocks $BeforeIndex]
    }
    if { $BeforeIndex < $ClockCount } {
	set Before "-before .clock$BeforeClock"
    } else {
	set Before ""
    }

    eval pack .clock$CN -side top -fill x -expand 1 $Before

    UpdateMenuStates
    if { $UpdateFile == "FileUpdate" } {
	UpdateFile
    }
    return 1
}

#------------------------------------------------------------------------------
# create a widget for clock number CN
proc CreateWidget { CN Widget } {
    global tk_version
    global Show Clock
    global TimeFont NameFont UpBitmap DnBitmap

    if { $tk_version < 4.0 } {
	set ButtonWidth "2"
    } else {
	set ButtonWidth "1"
    }

    if { $Widget == "goStop" } {
	button .clock$CN.goStop -width $ButtonWidth \
	    -text [Opposite $Clock($CN,GoStop)] \
	    -command "GoStop $CN" -bg [Color [Opposite $Clock($CN,GoStop)]]
    } elseif { $Widget == "time" } {
	entry .clock$CN.time -width 9 -textvariable Clock($CN,Time) \
	    -relief sunken -font $TimeFont -bg [Color $Clock($CN,GoStop)]
	if { $tk_version < 4.0 } {
	    bind .clock$CN.time <Button-1> \
		"%W icursor @%x
                 %W select from @%x
                 if {\[lindex \[%W config -state\] 4\] == \"normal\"} {
                     focus %W
                 }"
	}
	bind .clock$CN.time <Button-1> \
             "+set Clock($CN,RememberGoStop) \$Clock($CN,GoStop)
             set Clock($CN,GoStop) \"S\""
	SetBindings .clock$CN.time
    } elseif { $Widget == "addSubtract" } {
	frame .clock$CN.addSubtract
	frame .clock$CN.addSubtract.hour
	frame .clock$CN.addSubtract.min
	button .clock$CN.addSubtract.upHour -width 1 \
	    -bitmap $UpBitmap -command "AddSubtractClock $CN 60"
	button .clock$CN.addSubtract.downHour -width 1  \
	    -bitmap $DnBitmap -command "AddSubtractClock $CN -60"
	button .clock$CN.addSubtract.upMin -width 1  \
	    -bitmap $UpBitmap -command "AddSubtractClock $CN 1"
	button .clock$CN.addSubtract.downMin -width 1  \
	    -bitmap $DnBitmap -command "AddSubtractClock $CN -1"
	pack .clock$CN.addSubtract.upHour .clock$CN.addSubtract.downHour \
	    -in .clock$CN.addSubtract.hour -ipadx 2 -side top
	pack .clock$CN.addSubtract.upMin .clock$CN.addSubtract.downMin \
	    -in .clock$CN.addSubtract.min -ipadx 2 -side top
	pack .clock$CN.addSubtract.hour .clock$CN.addSubtract.min \
	    -in .clock$CN.addSubtract -side left
    } elseif { $Widget == "colonPeriod" } {
	button .clock$CN.colonPeriod -width $ButtonWidth \
	    -text [Opposite $Clock($CN,ColonPeriod)] \
	    -command "ColonPeriodClock $CN" -bg thistle
    } elseif { $Widget == "reset" } {
	button .clock$CN.reset -width $ButtonWidth -text "0" \
	    -command "ResetClock $CN" -bg lightgoldenrod
    } elseif { $Widget == "select" } {
	checkbutton .clock$CN.select -variable Clock($CN,Selected) \
	    -relief flat
	bind .clock$CN.select <Button-1> \
	    "if \$Clock($CN,Selected) {
                 set Clock($CN,Selected) 0
             } else {
                 foreach CN \$Clocks {
                     set Clock(\$CN,Selected) 0
                 }
                 set Clock($CN,Selected) 1
             }
             UpdateMenuStates
             UpdateFile"
	bind .clock$CN.select <Control-Button-1> \
	    "%W invoke
             UpdateMenuStates
             UpdateFile"
	if { $tk_version >= 4.0 } {
	    # enforce Tk pre-4.0 behavior of overriding old bindings
	    bindtags .clock$CN.select ".clock$CN.select"
	}
    } elseif { $Widget == "name" } {
	entry .clock$CN.name -width 9 -textvariable Clock($CN,Name) \
	    -relief sunken -font $NameFont -bg lightblue
	SetBindings .clock$CN.name
    } else {
	return 0
    }

    return 1
}

#------------------------------------------------------------------------------
# put widget in the display of clock number CN where it belongs
proc PackWidget { CN Widget } {
    global tk_version
    global Rows DisplayOrder DisplayOrderLength
    global DisplayOrder1 DisplayOrderLength1
    global DisplayOrder2 DisplayOrderLength2

    if { $Rows == 1 } {
	set DisplayOrderN $DisplayOrder
	set DisplayOrderLengthN $DisplayOrderLength
	set Where ""
    } else {
	if { [lsearch -exact $DisplayOrder1 $Widget] != -1 } {
	    set DisplayOrderN $DisplayOrder1
	    set DisplayOrderLengthN $DisplayOrderLength1
	    set Where "-in .clock$CN.top"
	} else {
	    set DisplayOrderN $DisplayOrder2
	    set DisplayOrderLengthN $DisplayOrderLength2
	    set Where "-in .clock$CN.bottom"
	}
    }

    set BeforeIndex [lsearch -exact $DisplayOrderN $Widget]

    if { $BeforeIndex != -1 } {
	incr BeforeIndex
	set BeforeWidget [lindex $DisplayOrderN $BeforeIndex]
    } else {
	set BeforeIndex [expr $DisplayOrderLengthN + 1]
    }

    while { ! [winfo exists .clock$CN.$BeforeWidget] && \
		$BeforeIndex < $DisplayOrderLengthN } {
	incr BeforeIndex
	set BeforeWidget [lindex $DisplayOrderN $BeforeIndex]
    }

    if { $BeforeIndex < $DisplayOrderLengthN } {
	set Before "-before .clock$CN.$BeforeWidget"
    } else {
	set Before ""
    }

    # if we know which widget to put it before, we don't need to know where
    if { $Before != "" } {
	set Where ""
    }

    if { $Widget == "name" } {
	set Expand "-fill x -expand 1"
    } else {
	set Expand ""
    }

    if { $tk_version < 4.0 } {
	set Padding "-padx 1m -pady 1m"
    } else {
	set Padding "-pady 1"
    }

    eval pack .clock$CN.$Widget $Before -side left $Padding $Where $Expand
}

#------------------------------------------------------------------------------
# update display of widget in all clocks    
proc UpdateWidgets { Widget } {
    global Show Clocks

    foreach CN $Clocks {
	if { [winfo exists .clock$CN] } {
	    if { $Show($Widget) } {
		CreateWidget $CN $Widget
		PackWidget $CN $Widget
	    } else {
		destroy .clock$CN.$Widget
	    }
	}
    }
    UpdateFile
}

#------------------------------------------------------------------------------
# update display of all clocks
proc UpdateDisplay {} {
    global Clocks

    foreach CN $Clocks {
	if { [winfo exists .clock$CN] } {
	    ShowClock $CN
	}
    }
    UpdateFile
}

#------------------------------------------------------------------------------
# 
proc UserUpdate { Widget } {
    global tk_version
    global Clock

    if { [string match "*.time" $Widget] } {
	# set time to new entry
	regexp {.clock([^.]*).time} $Widget junk CN
	set BadFormat 0
	if { [regexp "^(-)?(\[0-9\]+)?:?(\[0-9\]*)?:?(\[0-9\]*)?$" \
		  $Clock($CN,Time) junk Sign Hours Minutes Seconds] } {
	    if { $Sign == "" } { set Sign 1 }
	    if { $Sign == "-" } { set Sign -1 }
	    if { $Hours == "" } { set Hours 0 }
	    if { $Minutes == "" } { set Minutes 0 }
	    if { $Seconds == "" } { set Seconds 0 }
	    set Time [expr $Sign*int((($Hours*60) + $Minutes)*60 + $Seconds)]
	} elseif { [regexp "^(\[-0-9\]*\\\.?\[0-9\]*)$" \
			$Clock($CN,Time) junk Hours] } {
	    if { $Hours == "." } {
		set Time 0
	    } elseif { $Hours == "" } {
		set Time 0
	    } else {
		set Time [expr round($Hours*3600)]
	    }
	} else {
	    Dialog "`$Clock($CN,Time)' is badly formatted."
	    set BadFormat 1
	}
	if { $BadFormat } {
	    return
	}
	if { $Clock($CN,RememberGoStop) == "G" } {
	    set Clock($CN,Start) [expr [getclock] - $Time]
	} else {
	    set Clock($CN,Elapsed) $Time
	}
	UpdateClockTime $CN
	set Clock($CN,GoStop) $Clock($CN,RememberGoStop)
    }
    if { $tk_version < 4.0 } {
	$Widget select clear
	$Widget view 0
    } else {
	$Widget selection clear
	$Widget xview 0
    }
    focus .mbar
    UpdateFile
}

#------------------------------------------------------------------------------
# set bindings for an entry widget
proc SetBindings { Widget } {
    global tk_version

    bind $Widget <Return> "UserUpdate $Widget"
    bind $Widget <Control-x> "UserUpdate $Widget; exit"
    if { $tk_version >= 4.0 } {
	bind $Widget <Control-u> "$Widget delete 0 end"
	# do not inherit bindings for "all"
	bindtags $Widget "$Widget Entry ."
	return
    }
    bind $Widget <Control-a> \
        "$Widget icursor 0
         tk_entrySeeCaret %W"
    bind $Widget <Control-e> \
        "$Widget icursor end
         tk_entrySeeCaret %W"
    bind $Widget <Control-d> "$Widget delete insert"
    bind $Widget <Control-b> \
	"set pos \[$Widget index insert\]
	 set left \[$Widget index @0\]
         set newpos \[expr \$pos - 1\]
         if { \$pos > 0 } {$Widget icursor \$newpos}
         tk_entrySeeCaret %W"
    bind $Widget <Control-f> \
	"set pos \[$Widget index insert\]
         set end \[$Widget index end\]
         set newpos \[expr \$pos + 1\]
         if { \$pos < \$end } {$Widget icursor \$newpos}
         tk_entrySeeCaret %W"
    bind $Widget <Button-2> \
	"catch { $Widget insert insert \[selection get\] }"
}

#------------------------------------------------------------------------------
# hide selected clocks
proc HideClocks {} {
    global Clock Clocks

    set UpdateMenu 0
    foreach CN $Clocks {
	if { $Clock($CN,Selected) } {
	    destroy .clock$CN
	    set Clock($CN,Selected) 0
	    set UpdateMenu 1
	}
    }

    # we may need to enable "Show" menu
    if { $UpdateMenu } {
	.mbar.clock.menu entryconfigure "Show" -state normal
    }

    UpdateMenuStates
    UpdateFile
}

#------------------------------------------------------------------------------
# show all clocks
proc ShowAllClocks {} {
    global Clocks

    foreach CN $Clocks {
	if { ! [winfo exists .clock$CN] } {
	    ShowClock $CN NoUpdateFile
	}
    }
    UpdateFile
}

#------------------------------------------------------------------------------
# destroy selected clocks
proc DestroyClocks {} {
    global Clock Clocks DestroyedClocks

    # get rid of old saved destroyed clocks
    foreach CN $DestroyedClocks {
	unset Clock($CN,Selected)
	unset Clock($CN,Name)
	unset Clock($CN,Time)
	unset Clock($CN,GoStop)
	unset Clock($CN,ColonPeriod)
    }
    set DestroyedClocks {}

    # move selected clocks to "destroyed" list
    foreach CN $Clocks {
	if { $Clock($CN,Selected) } {
	    lappend DestroyedClocks $CN
	} else {
	    lappend RemainingClocks $CN
	}
    }

    # reset list of active clocks
    set Clocks $RemainingClocks

    # "destroy" selected clocks
    foreach CN $DestroyedClocks {
	destroy .clock$CN
    }

    UpdateMenuStates
    UpdateFile
}

#------------------------------------------------------------------------------
# restore destroyed clocks
proc UndestroyClocks {} {
    global Clock Clocks DestroyedClocks

    # unselect any selected clocks
    foreach CN $Clocks {
	if { $Clock($CN,Selected) } {
	    set Clock($CN,Selected) 0
	}
    }

    # undestroy clocks
    foreach CN $DestroyedClocks {
	lappend Clocks $CN
	ShowClock $CN NoUpdateFile
    }

    set DestroyedClocks {}
    UpdateMenuStates
    UpdateFile
}

#------------------------------------------------------------------------------
# move selected clocks up or down
proc MoveClocks { Where } {
    global Clock Clocks

    set FirstClocks {}
    if { $Where == "up" } {
	set Position "-before"
	set LastClocks $Clocks
    } else {
	set Position "-after"
	set LastClocks {}
	# reverse order of lists of clocks
	foreach CN $Clocks {
	    set LastClocks [linsert $LastClocks 0 $CN]
	}
    }

    # move clocks that are selected and first in list
    # from LastClocks to FirstClocks
    set CN [lindex $LastClocks 0]
    while { $Clock($CN,Selected) || ! [winfo exists .clock$CN] } {
	lappend FirstClocks $CN
	set LastClocks [lreplace $LastClocks 0 0]
	set CN [lindex $LastClocks 0]
    }

    set MiddleClocks {}

    # move the remaining clocks in the list up (or down)
    foreach CN $LastClocks {
	if { $Clock($CN,Selected) } {
	    # move this clock up (or down)
	    pack .clock$CN $Position .clock$ReferenceCN
	    lappend FirstClocks $CN
	} elseif { ! [winfo exists .clock$CN] } {
	    # this clock is hidden; append to MiddleClocks list
	    lappend MiddleClocks $CN
	} else {
	    # this clock is not hidden and it is not selected
	    # it becomes the new reference position
	    set FirstClocks [concat $FirstClocks $MiddleClocks]
	    set MiddleClocks {}
	    lappend MiddleClocks $CN
	    set ReferenceCN $CN
	}
    }
    set FirstClocks [concat $FirstClocks $MiddleClocks]

    # set clocks to new order
    if { $Where == "up" } {
	set Clocks $FirstClocks
    } else {
	set Clocks {}
	foreach CN $FirstClocks {
	    set Clocks [linsert $Clocks 0 $CN]
	}
    }
    UpdateFile
}

#------------------------------------------------------------------------------
# select all shown clocks
proc SelectAll {} {
    global Clock Clocks

    foreach CN $Clocks {
	if { [winfo exists .clock$CN] } {
	    set Clock($CN,Selected) 1
	}
    }
    UpdateMenuStates
    UpdateFile
}

#------------------------------------------------------------------------------
# deselect all shown clocks
proc DeselectAll {} {
    global Clock Clocks

    foreach CN $Clocks {
	if { [winfo exists .clock$CN] } {
	    set Clock($CN,Selected) 0
	}
    }
    UpdateMenuStates
    UpdateFile
}

#------------------------------------------------------------------------------
# return color for running or stopped stopwatch
proc Color { GoStop } {
    if { $GoStop == "G" } {
	return "lightgreen"
    } else {
	return "darksalmon"
    }
}

#------------------------------------------------------------------------------
# show a dialog box with a message
proc Dialog { String {BlinkDing 0} } {
    global MenuFont ButtonFont DN

    incr DN
    toplevel .dialog$DN
    wm title .dialog$DN "Message"
    wm transient .dialog$DN .
    frame .dialog$DN.msg -relief ridge -bd 2
    message .dialog$DN.msg1 -justify center -text $String -font $MenuFont
    button .dialog$DN.exit -text Ok -command "destroy .dialog$DN" \
	-font $ButtonFont -bg lightblue
    pack .dialog$DN.msg1 -side top -in .dialog$DN.msg -fill x
    pack .dialog$DN.msg .dialog$DN.exit -side top -padx 2m -pady 2m -fill x

    if { $BlinkDing == "BlinkDing" || $BlinkDing != 0 } {
	blinkding .dialog$DN.msg1 -bg red green 1000
    }

    #grab set .dialog$DN
    #tkwait window .dialog$DN
}

#------------------------------------------------------------------------------
# blink attribute, ring bell for first 10 blinks
proc blinkding {w option value1 value2 interval} {
    global BlinkDingCount

    if { [winfo exists $w] } {
	if { [info exists BlinkDingCount($w)] } {
	    incr BlinkDingCount($w)
	} else {
	    set BlinkDingCount($w) 1
	}
	$w config $option $value1
	if { $BlinkDingCount($w) < 10 } {
	    puts -nonewline "\007"
	    flush stdout
	}
	after $interval [list blinkding $w $option $value2 $value1 $interval]
    } else {
	unset BlinkDingCount($w)
    }
}

###############################################################################
# Timer Update Procedures

#------------------------------------------------------------------------------
# update the displayed time based on Clock($CN,Elapsed)
proc UpdateClockTime { CN } {
    global Clock

    if { $Clock($CN,ColonPeriod) == "." } {
	set Clock($CN,Time) [format "%4.4f" [expr $Clock($CN,Elapsed)/3600.0]]
    } else {
	if { $Clock($CN,Elapsed) < 0 } {
	    set Sign -1;
	} else {
	    set Sign 1;
	}
	set Clock($CN,Seconds) \
	    [expr round(fmod($Sign*$Clock($CN,Elapsed),60.0))]
	set Clock($CN,Minutes) \
	    [expr int(fmod(floor($Sign*$Clock($CN,Elapsed)/60.0),60))]
	set Clock($CN,Hours) \
	    [expr int(floor($Sign*$Clock($CN,Elapsed)/3600.0))]
	set Sign [expr {($Sign < 0) ? "-" : " "}]
	set Clock($CN,Time) [format "%s%02d:%02d:%02d" $Sign \
				 $Clock($CN,Hours) \
				 $Clock($CN,Minutes) $Clock($CN,Seconds)]
    }
}

#------------------------------------------------------------------------------
# update all the clocks
proc UpdateClocks {} {
    global Clock Clocks

    after 1000 UpdateClocks

    foreach CN $Clocks {
	if { $Clock($CN,GoStop) == "G" } {
	    set OldElapsed $Clock($CN,Elapsed)
	    set Clock($CN,Elapsed) [expr [getclock] - $Clock($CN,Start)]
	    UpdateClockTime $CN
	    if { $OldElapsed < 0 && $Clock($CN,Elapsed) >= 0 } {
		# time just passed zero
		RaiseAlarm $CN
	    }
	}
    }
}

#------------------------------------------------------------------------------
proc RaiseAlarm { CN } {
    global Clock

    Dialog "Time for `$Clock($CN,Name)' has elapsed." BlinkDing
}

#------------------------------------------------------------------------------
proc Opposite { State } {
    if { $State == ":" } {
	return "."
    } elseif { $State == "." } {
	return ":"
    } elseif { $State == "G" } {
	return "S"
    } elseif { $State == "S" } {
	return "G"
    }
}

###############################################################################
# Timer Action Procedures

#------------------------------------------------------------------------------
proc GoStop { CN } {
    global Clock

    if { $Clock($CN,GoStop) == "S" } {
	GoClock $CN
    } else {
	StopClock $CN
    }
}

#------------------------------------------------------------------------------
proc GoClocks {} {
    global Clock Clocks

    foreach CN $Clocks {
	if { $Clock($CN,Selected) } {
	    GoClock $CN NoFileUpdate
	}
    }
    UpdateFile
}

#------------------------------------------------------------------------------
proc GoClock { CN {UpdateFile "FileUpdate"}} {
    global Clock

    set Clock($CN,Start) [expr [getclock] -$Clock($CN,Elapsed)]
    set Clock($CN,GoStop) "G"
    .clock$CN.goStop configure -text "S" -bg [Color "S"]
    .clock$CN.time configure -bg [Color "G"]
    if { $UpdateFile == "FileUpdate" } {
	UpdateFile
    }
}

#------------------------------------------------------------------------------
proc StopClocks {} {
    global Clock Clocks

    foreach CN $Clocks {
	if { $Clock($CN,Selected) } {
	    StopClock $CN NoFileUpdate
	}
    }
    UpdateFile
}

#------------------------------------------------------------------------------
proc StopClock { CN {UpdateFile "FileUpdate"}} {
    global Clock

    set Clock($CN,GoStop) "S"
    .clock$CN.goStop configure -text "G" -bg [Color "G"]
    .clock$CN.time configure -bg [Color "S"]
    if { $UpdateFile == "FileUpdate" } {
	UpdateFile
    }
}

#------------------------------------------------------------------------------
proc ResetClocks {} {
    global Clock Clocks

    foreach CN $Clocks {
	if { $Clock($CN,Selected) } {
	    ResetClock $CN NoFileUpdate
	}
    }
    UpdateFile
}

#------------------------------------------------------------------------------
proc ResetClock { CN {UpdateFile "FileUpdate"}} {
    global Clock

    set Clock($CN,Start) [getclock]
    set Clock($CN,Elapsed) 0
    UpdateClockTime $CN
    if { $UpdateFile == "FileUpdate" } {
	UpdateFile
    }
}

#------------------------------------------------------------------------------
proc ColonPeriodClocks { Which } {
    global Clock Clocks

    foreach CN $Clocks {
	if { $Clock($CN,Selected) && $Clock($CN,ColonPeriod) != $Which } {
	    ColonPeriodClock $CN NoFileUpdate
	}
    }
    UpdateFile
}

#------------------------------------------------------------------------------
proc ColonPeriodClock { CN {UpdateFile "FileUpdate"}} {
    global Clock

    .clock$CN.colonPeriod configure -text $Clock($CN,ColonPeriod)
    set Clock($CN,ColonPeriod) [Opposite $Clock($CN,ColonPeriod)]
    UpdateClockTime $CN
    if { $UpdateFile == "FileUpdate" } {
	UpdateFile
    }
}

#------------------------------------------------------------------------------
proc AddSubtractClocks { Amount } {
    global Clock Clocks

    foreach CN $Clocks {
	if { $Clock($CN,Selected) } {
	    AddSubtractClock $CN $Amount NoFileUpdate
	}
    }
    UpdateFile
}

#------------------------------------------------------------------------------
proc AddSubtractClock { CN Amount {UpdateFile "FileUpdate"}} {
    global Clock

    set Amount60 [expr $Amount*60]
    if { $Clock($CN,Start) - $Amount60 >= 0 } {
	set Clock($CN,Start) [expr $Clock($CN,Start) - $Amount60]
    }
    if { $Clock($CN,Elapsed) + $Amount60 >= 0 } {
	set Clock($CN,Elapsed) [expr $Clock($CN,Elapsed) + $Amount60]
    }
    UpdateClockTime $CN
    if { $UpdateFile == "FileUpdate" } {
	UpdateFile
    }
}

###############################################################################
# File Saving Procedures

proc ReadFile {} {
    global Rows Show Clock Clocks RcFilename

    catch {source $RcFilename}
    foreach CN $Clocks {
	if { $Clock($CN,GoStop) == "G" } {
	    set Clock($CN,Elapsed) [expr [getclock] - $Clock($CN,Start)]
	}
    }
}

proc UpdateFile {} {
    global Rows Show Clock Clocks RcFilename

    set RcFile [open $RcFilename w]
    puts $RcFile "########################################################"
    puts $RcFile "# Timer Settings"
    puts $RcFile "########################################################"
    puts $RcFile "# Display Settings:"
    puts $RcFile "########################################################"
    puts $RcFile "set Rows $Rows"
    puts $RcFile "set Show(addSubtract) $Show(addSubtract)"
    puts $RcFile "set Show(colonPeriod) $Show(colonPeriod)"
    puts $RcFile "set Show(goStop) $Show(goStop)"
    puts $RcFile "set Show(reset) $Show(reset)"
    puts $RcFile "########################################################"
    puts $RcFile "# Clock Settings:"
    puts $RcFile "########################################################"
    puts $RcFile "set Clocks {}"
    foreach CN $Clocks {
	puts $RcFile "########################################################"
	puts $RcFile "# Clock Number $CN (\"$Clock($CN,Name)\"):"
	puts $RcFile "########################################################"
	puts $RcFile "lappend Clocks $CN"
	puts $RcFile "set Clock($CN,ColonPeriod) \"$Clock($CN,ColonPeriod)\""
	puts $RcFile "set Clock($CN,Elapsed) $Clock($CN,Elapsed)"
	puts $RcFile "set Clock($CN,GoStop) \"$Clock($CN,GoStop)\""
	puts $RcFile "set Clock($CN,Name) \"$Clock($CN,Name)\""
	puts $RcFile "set Clock($CN,Selected) $Clock($CN,Selected)"
	puts $RcFile "set Clock($CN,Start) $Clock($CN,Start)"
	puts $RcFile "set Clock($CN,Time) \"$Clock($CN,Time)\""
	if { [winfo exists .clock$CN] } {
	    puts $RcFile "ShowClock $CN"
	}
    }
    close $RcFile
}

###############################################################################
# Main

if { $argc == 1 } {
    set RcFilename $argv
}

ReadFile
UpdateMenuStates
UpdateClocks
