#!/bin/sh
# -*-Mode: TCL;-*-

#--------------------------------------------------------------
#   TKREMIND
#
#   A cheesy graphical front/back end for Remind using Tcl/Tk
#
#   This file is part of REMIND.
#   Copyright (C) 1992-1998 David F. Skoll
#   Copyright (C) 1999-2005 Roaring Penguin Software Inc.
#
#--------------------------------------------------------------

# $Id: tkremind,v 1.40 2005/04/14 17:34:34 dfs Exp $

# the next line restarts using wish \
exec wish8.4 "$0" "$@"

wm withdraw .

# Check that we have the right version of wish
if {$tcl_version < 8.0} {
    tk_dialog .error Error "You need wish version 8.0 or higher to run TkRemind; you have $tcl_version" error 0 OK
    exit 1
}

if {$tcl_platform(platform) == "windows"} {
    tk_dialog .error Error "Please do not port Remind to Windows" error 0 OK
    exit 1
}

#---------------------------------------------------------------------------
# GLOBAL VARIABLES
#---------------------------------------------------------------------------

set Option(ConfirmQuit) 0
set OptDescr(ConfirmQuit) "(0/1) If 1, TkRemind prompts you to confirm 'Quit' operation"
set Option(AutoClose) 1
set OptDescr(AutoClose) "(0/1) If 1, TkRemind automatically closes pop-up reminders after a minute"
set Option(RingBell) 0
set OptDescr(RingBell) "(0/1) If 1, TkRemind beeps the terminal when a pop-up reminder appears"

set Option(StartIconified) 0
set OptDescr(StartIconified) "(0/1) If 1, TkRemind starts up in the iconified state"

set Option(Deiconify) 0
set OptDescr(Deiconify) "(0/1) If 1, TkRemind deiconifies the calendar window when a reminder pops up"

set Option(ShowTodaysReminders) 1
set OptDescr(ShowTodaysReminders) "(0/1) If 1, TkRemind shows all of today's non-timed reminders in a window at startup and when the date changes"

set Option(RunCmd) ""
set OptDescr(RunCmd) "(String) If non-blank, run specified command when a pop-up reminder appears"
set Option(FeedReminder) 0
set OptDescr(FeedReminder) "(0/1) If 1, feed the reminder to RunCmd on standard input (see RunCmd option)"

set Option(Editor) "emacs +%d %s"
set OptDescr(Editor) "(String) Specify command to edit a file.  %d is replaced with line number and %s with filename"

# Remind program to execute -- supply full path if you want
set Remind "remind"
#set Remind "/home/dfs/Remind/src/remind"

# Rem2PS program to execute -- supply full path if you want
set Rem2PS "rem2ps"

# Reminder file to source -- default
set ReminderFile {NOSUCHFILE}
set ReminderFile [file nativename "~/.reminders"]

set EditorPid -1

# Reminder file to append to -- default
set AppendFile {NOSUCHFILE}
catch {set AppendFile $ReminderFile}

#---------------- DON'T CHANGE STUFF BELOW HERE ------------------

# Is Monday in first column?
set MondayFirst 0

# Month names in English
set MonthNames {January February March April May June July August September October November December}

# Day names in Remind's pre-selected language
set DayNames {}

# Day name in English
set EnglishDayNames {Sunday Monday Tuesday Wednesday Thursday Friday Saturday}

# Current month and year -- will be set by Initialize procedure
set CurMonth -1
set CurYear -1

# Background reminder counter
set BgCounter 0

# Absolutely today -- unlike the CurMonth and CurYear, these won't change
set TodayMonth -1
set TodayYear -1
set TodayDay -1

# Reminder option types and skip types
set OptionType 1
set SkipType 1

# Remind command line
set CommandLine {}
set PSCmd {}

# Print options -- destination file; letter-size; landscape; fill page; default
# encoding; 36pt margins; print small calendars
set PrintDest file
set PrintSize letter
set PrintOrient landscape
set PrintFill 1
set PrintEncoding 0
set PrintMargins 36pt
set PrintSmallCalendars 1

# Highest tag seen so far.  Array of tags is stored in ReminderTags()
set HighestTagSoFar 0

#***********************************************************************
# %PROCEDURE: Initialize
# %ARGUMENTS:
#  None
# %RETURNS:
#  Nothing
# %DESCRIPTION:
#  Initializes TkRemind -- sets day names, Remind command line,
#  MondayFirst flag, current date, etc.
#***********************************************************************
proc Initialize {} {

    global DayNames argc argv CommandLine ReminderFile AppendFile Remind PSCmd
    global MondayFirst

    set CommandLine "|$Remind -itkremind=1 -p -l"
    set PSCmd "$Remind -p -l"
    set i 0
    while {$i < $argc} {
	if {[regexp -- {-[bgxim].*} [lindex $argv $i]]} {
	    append CommandLine " [lindex $argv $i]"
	    append PSCmd " [lindex $argv $i]"
	    if {[regexp -- {m} [lindex $argv $i]]} {
		set MondayFirst 1
	    }
	} else {
	    break
	}
	incr i
    }
    if {$i < $argc} {
	set ReminderFile [lindex $argv $i]
	set AppendFile $ReminderFile
	incr i
	if {$i < $argc} {
	    set AppendFile [lindex $argv $i]
	    incr i
	}
    }

    # Check system sanity
    if {! [file readable $ReminderFile]} {
	set ans [tk_dialog .error "TkRemind: Warning" "Can't read reminder file `$ReminderFile'" warning 0 "Create it and continue" "Exit"]
	if {$ans != 0} {
	    exit 1
	}
	catch {close [open "$ReminderFile" w]}
    }
    if {! [file readable $ReminderFile]} {
	tk_dialog .error "TkRemind: Error" "Could not create reminder file `$ReminderFile'" error 0 "Exit"
	exit 1
    }

    if {! [file writable $AppendFile]} {
	tk_dialog .error Error "Can't write reminder file `$AppendFile'" error 0 Ok
	exit 1
    }

    append CommandLine " $ReminderFile"
    append PSCmd " $ReminderFile"
    set DayNames [GetWeekdayNames]
#   puts "CommandLine: $CommandLine"
}

#---------------------------------------------------------------------------
# GetWeekdayNames - Spawn a remind process and get the names of the weekdays
# Also sets CurMonth and CurYear.
#---------------------------------------------------------------------------
proc GetWeekdayNames {} {
    global CurMonth CurYear TodayYear TodayMonth TodayDay Remind
    set f [open "|$Remind - 2>/dev/null" r+]
    puts $f "banner %"
    set i 0
    while { $i < 7 } {
	puts $f "msg \[wkday($i)\]%"
	incr i
    }

    # Get current month and year as long as we're running Remind
    puts $f "msg %n%"
    puts $f "msg %y%"
    puts $f "msg %d%"
    puts $f "FLUSH"
    flush $f
    set ans {}
    set i 0
    while { $i < 7 } {
	lappend ans [gets $f]
	incr i
    }
    set CurMonth [expr [gets $f] - 1]
    set CurYear [gets $f]
    set TodayDay [gets $f]
    set TodayMonth $CurMonth
    set TodayYear $CurYear
    close $f
    return $ans
}

#***********************************************************************
# %PROCEDURE: CalEntryOffset
# %ARGUMENTS:
#  firstDay -- first day of month (0=Sunday, 6=Saturday)
# %RETURNS:
#  Offset mapping day numbers (1-31) to window numbers (0-41)
# %DESCRIPTION:
#  Computes offset from day number to window number
#***********************************************************************
proc CalEntryOffset { firstDay } {
    global MondayFirst
    if {$MondayFirst} {
	incr firstDay -1
	if {$firstDay < 0} {
	    set firstDay 6
	}
    }
    return [expr $firstDay-1]
}

#***********************************************************************
# %PROCEDURE: CreateCalFrame
# %ARGUMENTS:
#  w -- name of frame window
#  dayNames -- names of weekdays
# %RETURNS:
#  Nothing
# %DESCRIPTION:
#  Creates a frame holding a grid of labels and a grid of text entries
#***********************************************************************
proc CreateCalFrame { w dayNames } {
    # Figure out reasonable height for text frames
    set h [winfo screenheight .]
    if {$h <= 480} {
	set h 2
    } elseif {$h <= 600} {
	set h 4
    } else {
	set h 5
    }

    global MondayFirst
    frame $w
    for {set i 0} {$i < 7} {incr i} {
	if {$MondayFirst} {
	    set index [expr ($i+1)%7]
	} else {
	    set index $i
	}

	label $w.day$i -text [lindex $dayNames $index] -justify center
	grid configure $w.day$i -row 0 -column $i -sticky ew
    }
    for {set i 0} {$i < 6} {incr i} {
	set n [expr $i*7]
	for {set j 0} {$j < 7} {incr j} {
	    set f [expr $n+$j]
	    button $w.l$f -text "" -justify center -command "" \
		    -state disabled -relief flat
	    text $w.t$f -width 12 -height $h -wrap word -relief flat \
		    -state disabled -takefocus 0 -cursor {}
	    $w.t$f tag bind TAGGED <Enter> "TaggedEnter $w.t$f"
	    $w.t$f tag bind TAGGED <Leave> "TaggedLeave $w.t$f"
	    $w.t$f tag bind TAGGED <ButtonPress-1> "EditTaggedReminder $w.t$f"
	    $w.t$f tag bind REM <ButtonPress-3> "FireEditor $w.t$f"

	    grid configure $w.l$f -row [expr $i*2+1] -column $j -sticky ew
	    grid configure $w.t$f -row [expr $i*2+2] -column $j -sticky nsew
	}
    }

    for {set i 0} {$i < 7} {incr i} {
	grid columnconfigure $w $i -weight 1
    }
    for {set i 2} {$i < 14} {incr i 2} {
	grid rowconfigure $w $i -weight 1
    }
}

#***********************************************************************
# %PROCEDURE: ConfigureCalFrame
# %ARGUMENTS:
#  w -- window name of calendar frame
#  firstDay -- first weekday of month
#  numDays -- number of days in month
# %RETURNS:
#  Nothing
# %DESCRIPTION:
#  Sets up button labels; configures text justification
#***********************************************************************
proc ConfigureCalFrame { w firstDay numDays } {
    global CurMonth CurYear TodayMonth TodayYear TodayDay
    set offset [CalEntryOffset $firstDay]
    set first [expr $offset+1]
    set last [expr $offset+$numDays]

    for {set i 0} {$i < $first} {incr i} {
	grid $w.l$i $w.t$i
	$w.l$i configure -text "" -command "" -state disabled -relief flat
	$w.t$i configure -relief flat -takefocus 0 -state normal
	$w.t$i delete 1.0 end
	foreach t [$w.t$i tag names] {
	    $w.t$i tag delete $t
	}
	$w.t$i tag bind TAGGED <Enter> "TaggedEnter $w.t$i"
	$w.t$i tag bind TAGGED <Leave> "TaggedLeave $w.t$i"
	$w.t$i tag bind TAGGED <ButtonPress-1> "EditTaggedReminder $w.t$i"
	$w.t$i tag bind REM <ButtonPress-3> "FireEditor $w.t$i"
	$w.t$i configure -state disabled
	$w.t$i configure -background [lindex [$w.t$i configure -background] 3]
	$w.l$i configure -background [lindex [$w.l$i configure -background] 3]
    }
    for {set i $first} {$i <= $last} {incr i} {
	grid $w.l$i $w.t$i
	set d [expr $i-$first+1]
	$w.l$i configure -text $d -state normal -relief flat \
		-command "ModifyDay $d $firstDay"
	$w.t$i configure -relief sunken -takefocus 1 -state normal
	$w.t$i delete 1.0 end
	foreach t [$w.t$i tag names] {
	    $w.t$i tag delete $t
	}
	$w.t$i tag bind TAGGED <Enter> "TaggedEnter $w.t$i"
	$w.t$i tag bind TAGGED <Leave> "TaggedLeave $w.t$i"
	$w.t$i tag bind TAGGED <ButtonPress-1> "EditTaggedReminder $w.t$i"
	$w.t$i tag bind REM <ButtonPress-3> "FireEditor $w.t$i"
	$w.t$i configure -state disabled
	$w.t$i configure -background [lindex [$w.t$i configure -background] 3]
	$w.l$i configure -background [lindex [$w.l$i configure -background] 3]
    }
    set forgetIt 0
    for {set i [expr $last+1]} {$i < 42} {incr i} {
	if {$i%7 == 0} {
	    set forgetIt 1
	}
	set row [expr ($i/7)*2+1]
	if {$forgetIt} {
	    grid remove $w.l$i $w.t$i
	    grid rowconfigure $w $row -weight 0
	    grid rowconfigure $w [expr $row+1] -weight 0
	} else {
	    grid rowconfigure $w [expr $row+1] -weight 1
	}
	$w.l$i configure -text "" -command "" -state disabled -relief flat
	$w.t$i configure -relief flat -takefocus 0 -state normal
	$w.t$i delete 1.0 end
	foreach t [$w.t$i tag names] {
	    $w.t$i tag delete $t
	}
	$w.t$i tag bind TAGGED <Enter> "TaggedEnter $w.t$i"
	$w.t$i tag bind TAGGED <Leave> "TaggedLeave $w.t$i"
	$w.t$i tag bind TAGGED <ButtonPress-1> "EditTaggedReminder $w.t$i"
	$w.t$i tag bind REM <ButtonPress-3> "FireEditor $w.t$i"
	$w.t$i configure -state disabled
	$w.t$i configure -background [lindex [$w.t$i configure -background] 3]
	$w.l$i configure -background [lindex [$w.l$i configure -background] 3]
    }
    if { $CurMonth == $TodayMonth && $CurYear == $TodayYear } {
	set n [expr $TodayDay + $offset]
	$w.l$n configure -background "#00c0c0"
    }
}

#---------------------------------------------------------------------------
# CreateCalWindow -- create the calendar window.
# Arguments:
#   dayNames -- names of weekdays in current language {Sun .. Sat}
#---------------------------------------------------------------------------
proc CreateCalWindow { dayNames } {
    global Option
    frame .h
    label .h.title -text "" -justify center -pady 2 -relief raised
    pack .h.title -side top -fill x
    pack .h -side top -expand 0 -fill x

    CreateCalFrame .cal $dayNames

    frame .b
    button .b.prev -text {<-} -command {MoveMonth -1}
    button .b.this -text {Today} -command {ThisMonth}
    button .b.next -text {->} -command {MoveMonth 1}
    button .b.goto -text {Go To Date...} -command {GotoDialog}
    button .b.print -text {Print...} -command {DoPrint}
    button .b.quit -text {Quit} -command {Quit}
    button .b.options -text {Options...} -command EditOptions
    label .b.status -text "" -width 25 -relief sunken
    label .b.nqueued -text "" -width 20 -relief sunken
    pack .b.prev .b.this .b.next .b.goto .b.print .b.options .b.quit -side left -fill x
    pack .b.status -side left -fill x -expand 1
    pack .b.nqueued -side left -fill x
    pack .b -side bottom -fill x -expand 0
    pack .cal -side top -fill both -expand 1
    wm title . "TkRemind"
    wm iconname . ""
    wm protocol . WM_DELETE_WINDOW Quit
    wm deiconify .
    if {$Option(StartIconified)} {
	wm iconify .
    }
    update
    grid propagate .cal 0
}

#***********************************************************************
# %PROCEDURE: EditOptions
# %ARGUMENTS:
#  None
# %RETURNS:
#  Nothing
# %DESCRIPTION:
#  Lets user edit options
#***********************************************************************
proc EditOptions {} {
    global Option tmpOpt

    # Make a working copy of current option set
    foreach name [array names Option] {
	set tmpOpt($name) $Option($name)
    }

    set w .opt
    catch { destroy $w }
    toplevel $w
    wm title $w "TkRemind Options"
    wm iconname $w "Options"
    frame $w.f
    frame $w.b
    pack $w.f -side top -expand 1 -fill both
    pack $w.b -side top -expand 0 -fill x

    # Start iconified
    checkbutton $w.startIconified -text "Start up Iconified" \
	    -anchor w -justify left \
	    -variable tmpOpt(StartIconified)

    # Show today's reminders on startup
    checkbutton $w.showTodays -text "Show Today's Reminders on Startup" \
	    -anchor w -justify left \
	    -variable tmpOpt(ShowTodaysReminders)

    # Confirm quit
    checkbutton $w.confirmQuit -text "Confirm Quit" -anchor w -justify left \
	    -variable tmpOpt(ConfirmQuit)

    # Bring down reminder windows after one minute
    checkbutton $w.bringDown \
	    -text "Automatically close pop-up reminders after a minute" \
	    -anchor w -justify left -variable tmpOpt(AutoClose)

    # Ring bell when popping up reminder
    checkbutton $w.ring -text "Beep terminal when popping up a reminder" \
	    -anchor w -justify left -variable tmpOpt(RingBell)

    checkbutton $w.deic -text "Deiconify calendar window when popping up a reminder" \
	    -anchor w -justify left -variable tmpOpt(Deiconify)

    # Run command when popping up reminder
    frame $w.rf
    label $w.rl -text "Run command when popping up reminder:" -anchor w \
	    -justify left
    entry $w.cmd -width 30
    pack $w.rl -in $w.rf -side left -expand 0 -fill none
    pack $w.cmd -in $w.rf -side left -expand 1 -fill x
    $w.cmd insert 0 $tmpOpt(RunCmd)

    # Editor
    frame $w.ef
    label $w.el -text "Text Editor:" -anchor w -justify left
    entry $w.editor -width 30
    pack $w.el -in $w.ef -side left -expand 0 -fill none
    pack $w.editor -in $w.ef -side left -expand 1 -fill x
    $w.editor insert 0 $tmpOpt(Editor)

    frame $w.sep1 -border 1 -relief sunken
    frame $w.sep2 -border 1 -relief sunken

    checkbutton $w.feed \
	    -text "Feed popped-up reminder to command's standard input" \
	    -variable tmpOpt(FeedReminder) -anchor w -justify left

    pack $w.startIconified -in $w.f -side top -expand 0 -fill x
    pack $w.showTodays -in $w.f -side top -expand 0 -fill x
    pack $w.confirmQuit -in $w.f -side top -expand 0 -fill x
    pack $w.bringDown -in $w.f -side top -expand 0 -fill x
    pack $w.ring -in $w.f -side top -expand 0 -fill x
    pack $w.deic -in $w.f -side top -expand 0 -fill x
    pack $w.sep1 -in $w.f -side top -expand 0 -fill x -ipady 1
    pack $w.rf -in $w.f -side top -expand 0 -fill x
    pack $w.feed -in $w.f -side top -expand 0 -fill x
    pack $w.ef -in $w.f -side top -expand 0 -fill x
    pack $w.sep2 -in $w.f -side top -expand 0 -fill x -ipady 1

    button $w.apply -text "Apply Options" -command "ApplyOptions $w; destroy $w"
    button $w.save -text "Save Options" -command "SaveOptions $w; destroy $w"
    button $w.cancel -text "Cancel" -command "destroy $w"

    pack $w.apply $w.save $w.cancel -in $w.b -side left -expand 0 -fill x
    CenterWindow $w
}

#***********************************************************************
# %PROCEDURE: ApplyOptions
# %ARGUMENTS:
#  w -- edit options window path
# %RETURNS:
#  Nothing
# %DESCRIPTION:
#  Applies options set in the edit options box.
#***********************************************************************
proc ApplyOptions { w } {
    global Option tmpOpt
    set tmpOpt(RunCmd) [$w.cmd get]
    set tmpOpt(Editor) [$w.editor get]
    # Copy working copy to real option set
    foreach name [array names tmpOpt] {
	set Option($name) $tmpOpt($name)
    }
}

#***********************************************************************
# %PROCEDURE: SaveOptions
# %ARGUMENTS:
#  w -- edit options window path
# %RETURNS:
#  Nothing
# %DESCRIPTION:
#  Saves options in $HOME/.tkremindrc
#***********************************************************************
proc SaveOptions { w } {
    global Option OptDescr
    ApplyOptions $w

    set problem [catch {set f [open ~/.tkremindrc "w"]} err]
    if {$problem} {
	tk_dialog .error Error "Can't write ~/.tkremindrc: $err" 0 OK
	return
    }

    puts $f "# TkRemind option file -- created automatically"
    puts $f "# [clock format [clock seconds]]"
    puts $f "# Format of each line is 'key value' where 'key'"
    puts $f "# specifies the option name, and 'value' is a"
    puts $f "# *legal Tcl list element* specifying the option value."
    foreach name [lsort [array names Option]] {
	puts $f ""
	puts $f "# $OptDescr($name)"
	puts $f [list $name $Option($name)]
    }
    puts $f ""
    close $f
}

#***********************************************************************
# %PROCEDURE: LoadOptions
# %ARGUMENTS:
#  None
# %RETURNS:
#  Nothing
# %DESCRIPTION:
#  Loads options from ~/.tkremindrc
#***********************************************************************
proc LoadOptions {} {
    global Option
    set problem [catch {set f [open "~/.tkremindrc" "r"]}]
    if {$problem} {
	return
    }
    while {[gets $f line] >= 0} {
	if {[string match "#*" $line]} { continue }
	if {$line == ""} { continue }
	foreach {key val} $line {}
	if {![info exists Option($key)]} {
	    puts "Unknown option in ~/.tkremindrc: $key"
	    continue
	}
	set Option($key) $val
    }
    close $f
}



#***********************************************************************
# %PROCEDURE: ConfigureCalWindow
# %ARGUMENTS:
#  month -- month name
#  year -- the year
#  firstDay -- first day in month
#  numDays -- number of days in month
# %RETURNS:
#  Nothing
# %DESCRIPTION:
#  Configures the calendar window for a month and year
# %PRECONDITIONS:
#  Any preconditions
# %POSTCONDITIONS:
#  Any postconditions
# %SIDE EFFECTS:
#  Any side effects
#***********************************************************************
proc ConfigureCalWindow { month year firstDay numDays } {
    .h.title configure -text "$month $year"
    wm title . "TkRemind - $month $year"
    wm iconname . "$month $year"
    ConfigureCalFrame .cal $firstDay $numDays
}

#---------------------------------------------------------------------------
# FillCalWindow -- Fill in the calendar for global CurMonth and CurYear.
#---------------------------------------------------------------------------
proc FillCalWindow {} {
    set FileName ""
    set LineNo 0
    global DayNames CurYear CurMonth MonthNames CommandLine

    Status "Firing off Remind..."
    set month [lindex $MonthNames $CurMonth]

    set file [open "$CommandLine $month $CurYear" r]
    # Look for # rem2ps begin line
    while { [gets $file line] >= 0 } {
	if { [string compare "$line" "# rem2ps begin"] == 0 } { break }
    }

    if { [string compare "$line" "# rem2ps begin"] != 0 } {
	Status "Problem reading results from Remind!"
	after 5000 DisplayTime
	catch { close $file }
	return 0
    }

    # Read month name, year, number of days in month, first weekday, Mon flag
    gets $file line
    regexp {^([^ ]*) ([0-9][0-9][0-9][0-9]) ([0-9][0-9]?) ([0-9]) ([0-9])} $line dummy monthName year daysInMonth firstWkday mondayFirst

    # Skip day names -- we already have them
    gets $file line
    ConfigureCalWindow $monthName $year $firstWkday $daysInMonth
    set offset [CalEntryOffset $firstWkday]

    for {set i 1} {$i <= 31} {incr i} {
	set didSomething($i) 0
    }

    set fntag "x"
    while { [gets $file line] >= 0 } {
	# File info
	if { [ string match "# fileinfo *" $line ] } {
	    regexp {fileinfo ([0-9]+) (.*)} $line all LineNo Filename
	    set fntag "FILE_${LineNo}_${Filename}"
	}

	# Skip comments
	if { [string match "#*" $line] } {
	    continue
	}
	if { [regexp {^([0-9][0-9][0-9][0-9])/([0-9][0-9])/([0-9][0-9]) +([^ ]+) +([^ ]+) +[^ ]+ +[^ ]+(.*)} $line all year month day type tag stuff] == 0 } {
	    continue
	}
	set day [string trimleft $day 0]
	set n [expr $day+$offset]
	set month [string trimleft $month 0]
	set extratags ""
	switch -exact -- $type {
	    "SHADE" {
		DoShadeSpecial $n $stuff
		continue
	    }
	    "MOON" {
		DoMoonSpecial $n $stuff
		continue
	    }
	    "COLOR" {
		if {[regexp {^ *([0-9]+) +([0-9]+) +([0-9]+) +(.*)$} $stuff all r g b rest]} {
		    if {$r > 255} {
			set r 255
		    } elseif {$r < 0} {
			set r 0
		    }
		    if {$g > 255} {
			set g 255
		    } elseif {$g < 0} {
			set g 0
		    }
		    if {$b > 255} {
			set b 255
		    } elseif {$b < 0} {
			set b 0
		    }
		    set color [format "%02X%02X%02X" $r $g $b]
		    set extratags "clr$color"
		    .cal.t$n configure -state normal
		    .cal.t$n tag configure $extratags -foreground "#$color"
		    .cal.t$n configure -state disabled
		    set stuff $rest
		}
	    }
	}
	if { $type != "*" && $type != "COLOR"} {
	    continue
	}
	.cal.t$n configure -state normal
	if { $didSomething($day) } {
	    .cal.t$n insert end " .....\n"
	}
	set didSomething($day) 1
	if {[regexp {TKTAG([0-9]+)} $tag all tagno]} {
	    .cal.t$n insert end [string trim $stuff] [list REM TAGGED "TKTAG$tagno" $extratags $fntag]
	} else {
	    .cal.t$n insert end [string trim $stuff] [list REM $extratags $fntag]
	}
	.cal.t$n insert end "\n"
	.cal.t$n configure -state disabled

    }
    set problem [catch { close $file } errmsg]
    if {$problem} {
	tk_dialog .error Error "There was a problem running Remind: $errmsg" error 0 OK
    }
    DisplayTime
}

#---------------------------------------------------------------------------
# MoveMonth -- move by +1 or -1 months
# Arguments:
#    delta -- +1 or -1 -- months to move.
#---------------------------------------------------------------------------
proc MoveMonth {delta} {
    global CurMonth CurYear
    set CurMonth [expr $CurMonth + $delta]
    if {$CurMonth < 0} {
	set CurMonth 11
	set CurYear [expr $CurYear-1]
    }

    if {$CurMonth > 11} {
	set CurMonth 0
	incr CurYear
    }

    FillCalWindow
}

#---------------------------------------------------------------------------
# ThisMonth -- move to current month
#---------------------------------------------------------------------------
proc ThisMonth {} {
    global CurMonth CurYear TodayMonth TodayYear

    # Do nothing if already there
    if { $CurMonth == $TodayMonth && $CurYear == $TodayYear } {
	return 0;
    }
    set CurMonth $TodayMonth
    set CurYear $TodayYear
    FillCalWindow
}

#---------------------------------------------------------------------------
# Status -- set status string
# Arguments:
#   stuff -- what to set string to.
#---------------------------------------------------------------------------
proc Status { stuff } {
    catch { .b.status configure -text $stuff }
    update idletasks
}

#---------------------------------------------------------------------------
# DoPrint -- print a calendar
# Arguments:
#   None
#---------------------------------------------------------------------------
proc DoPrint {} {
    global PrintDest PrintSize PrintMargins PrintOrient PrintFill PrintEncoding PrintSmallCalendars PrintStatus Rem2PS PSCmd
    global CurMonth CurYear MonthNames
    catch {destroy .p}
    toplevel .p
    wm title .p "TkRemind Print..."
    wm iconname .p "Print..."
    frame .p.f1 -relief sunken -border 2
    frame .p.f11
    frame .p.f12
    frame .p.f2 -relief sunken -border 2
    frame .p.f2a -relief sunken -border 2
    frame .p.f3 -relief sunken -border 2
    frame .p.f3a -relief sunken -border 2
    frame .p.f4

    radiobutton .p.tofile -text "To file: " -variable PrintDest -value file
    entry .p.filename
    button .p.browse -text "Browse..." -command PrintFileBrowse
    radiobutton .p.tocmd -text "To command: " -variable PrintDest -value command
    entry .p.command
    .p.command insert end "lpr"

    label .p.size -text "Paper Size:"
    radiobutton .p.letter -text "Letter" -variable PrintSize -value letter
    radiobutton .p.a4 -text "A4" -variable PrintSize -value a4

    label .p.margin -text "Margins:"
    radiobutton .p.24pt -text "24pt margins" -variable PrintMargins -value 24pt
    radiobutton .p.36pt -text "36pt margins" -variable PrintMargins -value 36pt
    radiobutton .p.48pt -text "48pt margins" -variable PrintMargins -value 48pt

    label .p.orient -text "Orientation:"
    radiobutton .p.landscape -text "Landscape" -variable PrintOrient -value landscape
    radiobutton .p.portrait -text "Portrait" -variable PrintOrient -value portrait

    checkbutton .p.fill -text "Fill page" -variable PrintFill
    checkbutton .p.encoding -text "ISO 8859-1 PostScript encoding" -variable PrintEncoding
    checkbutton .p.calendars -text "Print small calendars" -variable PrintSmallCalendars

    button .p.print -text "Print" -command {set PrintStatus print}
    button .p.cancel -text "Cancel" -command {set PrintStatus cancel}

    pack .p.f1 .p.f2 .p.f2a .p.f3 .p.f3a \
	    -side top -fill both -expand 1 -anchor w
    pack .p.fill .p.encoding .p.calendars -in .p.f3a \
	    -side top -anchor w -fill none -expand 0
    pack .p.f4 -side top -fill both -expand 1 -anchor w
    pack .p.f11 .p.f12 -in .p.f1 -side top -fill none -expand 0 -anchor w
    pack .p.tofile .p.filename .p.browse -in .p.f11 -side left -fill none -expand 0 -anchor w
    pack .p.tocmd .p.command -in .p.f12 -side left -fill none -expand 0 -anchor w
    pack .p.size .p.letter .p.a4 -in .p.f2 -side top -fill none -expand 0 -anchor w
    pack .p.margin .p.24pt .p.36pt .p.48pt -in .p.f2a -side top -anchor w -fill none -expand 0
    pack .p.orient .p.landscape .p.portrait -in .p.f3 -side top -fill none -expand 0 -anchor w
    pack .p.print .p.cancel -in .p.f4 -side left -fill none -expand 0

    bind .p <KeyPress-Escape> ".p.cancel flash; .p.cancel invoke"
    bind .p <KeyPress-Return> ".p.print flash; .p.print invoke"
    set PrintStatus 2
    CenterWindow .p
    tkwait visibility .p
    set oldFocus [focus]
    focus .p.filename
    grab .p
    tkwait variable PrintStatus
    catch {focus $oldFocus}
    set fname [.p.filename get]
    set cmd [.p.command get]
    destroy .p
    if {$PrintStatus == "cancel"} {
	return
    }
    if {$PrintDest == "file"} {
	if {$fname == ""} {
	    tk_dialog .error Error "No filename specified" error 0 Ok
	    return
	}
	if {[file isdirectory $fname]} {
	    tk_dialog .error Error "$fname is a directory" error 0 Ok
	    return
	}
	if {[file readable $fname]} {
	    set ans [tk_dialog .error Overwrite? "Overwrite $fname?" question 0 No Yes]
	    if {$ans == 0} {
		return
	    }
	}
	set fname "> $fname"
    } else {
	set fname "| $cmd"
    }

    # Build the command line
    set cmd "$PSCmd 1 [lindex $MonthNames $CurMonth] $CurYear | $Rem2PS"
    if {$PrintSize == "letter"} {
	append cmd " -m Letter"
    } else {
	append cmd " -m A4"
    }

    if {$PrintMargins == "24pt"} {
	append cmd " -or 24 -ol 24 -ot 24 -ob 24"
    } elseif {$PrintMargins == "36pt"} {
	append cmd " -or 36 -ol 36 -ot 36 -ob 36"
    } else {
	append cmd " -or 48 -ol 48 -ot 48 -ob 48"
    }

    if {$PrintOrient == "landscape"} {
	append cmd " -l"
    }

    if {$PrintFill} {
	append cmd " -e"
    }

    if {$PrintEncoding} {
	append cmd " -i"
    }

    if {$PrintSmallCalendars} {
	append cmd " -c3"
    } else {
	append cmd " -c0"
    }

    append cmd " $fname"
    Status "Printing..."
    if {[catch {eval "exec $cmd"} err]} {
	tk_dialog .error Error "Error during printing: $err" error 0 Ok
    }
    DisplayTime
}

#---------------------------------------------------------------------------
# PrintFileBrowse -- browse for a filename for Print dialog
# Arguments: none
#---------------------------------------------------------------------------
proc PrintFileBrowse {} {
    set ans [BrowseForFile .filebrowse "Print to file..." "Ok" 0 "*.ps"]
    if {$ans != ""} {
	.p.filename delete 0 end
	.p.filename insert end $ans
	.p.filename icursor end
	.p.filename xview end
    }
}

#---------------------------------------------------------------------------
# GotoDialog -- Do the "Goto..." dialog
#---------------------------------------------------------------------------
proc GotoDialog {} {
    global CurMonth MonthNames CurYear
    catch { destroy .g }

    set month [lindex $MonthNames $CurMonth]
    toplevel .g
    wm title .g "Go To Date"
    menubutton .g.mon -text "$month" -menu .g.mon.menu -relief raised
    menu .g.mon.menu -tearoff 0

    foreach m $MonthNames {
	.g.mon.menu add command -label $m -command ".g.mon configure -text $m"
    }

    frame .g.y
    label .g.y.lab -text "Year: "
    entry .g.y.e -width 4
    .g.y.e insert end $CurYear
    bind .g.y.e <Return> ".g.b.go flash; .g.b.go invoke"
    frame .g.b
    button .g.b.go -text "Go" -command {DoGoto}
    button .g.b.cancel -text "Cancel" -command { destroy .g }
    pack .g.b.go .g.b.cancel -expand 1 -fill x -side left
    pack .g.mon -fill x -expand 1

    pack .g.y.lab -side left
    pack .g.y.e -side left -fill x -expand 1
    pack .g.y -expand 1 -fill x
    pack .g.b -expand 1 -fill x
    bind .g <KeyPress-Escape> ".g.b.cancel flash; .g.b.cancel invoke"
    CenterWindow .g
    set oldFocus [focus]
    grab .g
    focus .g.y.e
    tkwait window .g
    catch {focus $oldFocus}
}

#---------------------------------------------------------------------------
# DoGoto -- go to specified date
#---------------------------------------------------------------------------
proc DoGoto {} {
    global CurYear CurMonth MonthNames
    set year [.g.y.e get]
    if { ! [regexp {^[0-9]+$} $year] } {
	tk_dialog .error Error {Illegal year specified (1990-2078)} error 0 Ok
	return
    }
    if { $year < 1990 || $year > 2078 } {
	tk_dialog .error Error {Illegal year specified (1990-2078)} error 0 Ok
	return
    }
    set month [lsearch -exact $MonthNames [.g.mon cget -text]]
    set CurMonth $month
    set CurYear $year
    destroy .g
    FillCalWindow
}

#---------------------------------------------------------------------------
# Quit -- handle the Quit button
#---------------------------------------------------------------------------
proc Quit {} {
    global Option
    if { !$Option(ConfirmQuit) } {
	destroy .
	StopBackgroundRemindDaemon
	exit
    }
    if { [tk_dialog .question "Confirm..." {Really quit?} question 0 No Yes] } {
	destroy .
	StopBackgroundRemindDaemon
	exit
    }
}

#---------------------------------------------------------------------------
# CreateModifyDialog -- create dialog for adding a reminder
# Arguments:
#    w -- path of parent window
#    day -- day number of month
#    firstDay -- day number of first day of month
#    args -- buttons to add to bottom frame.  First sets result to 1, next
#            to 2, and so on.  FIRST BUTTON MUST BE "Cancel"
#---------------------------------------------------------------------------
proc CreateModifyDialog {w day firstDay args} {

    # Set up: Year, Month, Day, WeekdayName
    global CurYear CurMonth EnglishDayNames MonthNames OptionType SkipType
    global ModifyDialogResult

    set OptionType 1
    set SkipType 1

    set year $CurYear
    set month [lindex $MonthNames $CurMonth]
    set wkday [lindex $EnglishDayNames [expr ($day+$firstDay-1) % 7]]

    frame $w.o -border 4 -relief ridge
    frame $w.o1 -border 4
    frame $w.o2 -border 4
    frame $w.o3 -border 4
    frame $w.exp -border 4
    frame $w.adv -border 4
    frame $w.weekend -border 4
    frame $w.time -border 4
    frame $w.hol -border 4
    frame $w.msg
    frame $w.buttons
    pack $w.o1 $w.o2 $w.o3  -side top -anchor w -in $w.o
    pack $w.o $w.exp $w.adv $w.weekend $w.time $w.hol $w.msg -side top -anchor w -pady 4 -expand 1 -fill both
    pack $w.buttons -side top -anchor w -pady 4 -expand 1 -fill x

    # TYPE 1 REMINDER
    radiobutton $w.type1 -variable OptionType -value 1
    menubutton $w.day1 -text $day -relief raised -menu $w.day1.menu
    CreateDayMenu $w.day1
    menubutton $w.mon1 -text $month -relief raised -menu $w.mon1.menu
    CreateMonthMenu $w.mon1
    menubutton $w.year1 -text $year -relief raised -menu $w.year1.menu
    CreateYearMenu $w.year1
    checkbutton $w.repbut -text "and repeating every"
    $w.repbut deselect
    menubutton $w.repdays -text 1 -relief raised -menu $w.repdays.menu
    CreateDayMenu $w.repdays 1 28 0
    label $w.label1a -text "day(s) thereafter"
    pack $w.type1 $w.day1 $w.mon1 $w.year1 $w.repbut $w.repbut $w.repdays $w.label1a -side left -anchor w -in $w.o1

    # TYPE 2 REMINDER
    radiobutton $w.type2 -variable OptionType -value 2
    label $w.label2a -text First
    menubutton $w.wkday2 -text $wkday -relief raised -menu $w.wkday2.menu
    CreateWeekdayMenu $w.wkday2
    label $w.label2b -text "on or after"
    menubutton $w.day2 -text $day -relief raised -menu $w.day2.menu
    CreateDayMenu $w.day2 1 31 0
    menubutton $w.mon2 -text $month -relief raised -menu $w.mon2.menu
    CreateMonthMenu $w.mon2
    menubutton $w.year2 -text $year -relief raised -menu $w.year2.menu
    CreateYearMenu $w.year2
    pack $w.type2 $w.label2a $w.wkday2 $w.label2b $w.day2 $w.mon2 $w.year2 -side left -anchor w -in $w.o2

    # TYPE 3 REMINDER
    if { $day <= 7 } {
	set which "First"
    } elseif {$day <= 14} {
	set which "Second"
    } elseif {$day <= 21} {
	set which "Third"
    } elseif {$day <= 28} {
	set which "Fourth"
    } else {
	set which "Last"
    }
    radiobutton $w.type3 -variable OptionType -value 3
    menubutton $w.ordinal -text $which -relief raised -menu $w.ordinal.menu
    menu $w.ordinal.menu -tearoff 0
    $w.ordinal.menu add command -label "First" -command "$w.ordinal configure -text First"
    $w.ordinal.menu add command -label "Second" -command "$w.ordinal configure -text Second"
    $w.ordinal.menu add command -label "Third" -command "$w.ordinal configure -text Third"
    $w.ordinal.menu add command -label "Fourth" -command "$w.ordinal configure -text Fourth"
    $w.ordinal.menu add command -label "Last" -command "$w.ordinal configure -text Last"
    $w.ordinal.menu add command -label "Every" -command "$w.ordinal configure -text Every"
    menubutton $w.wkday3 -text $wkday -relief raised -menu $w.wkday3.menu
    CreateWeekdayMenu $w.wkday3
    label $w.label3 -text "in"
    menubutton $w.mon3 -text $month -relief raised -menu $w.mon3.menu
    CreateMonthMenu $w.mon3
    menubutton $w.year3 -text $year -relief raised -menu $w.year3.menu
    CreateYearMenu $w.year3
    pack $w.type3 $w.ordinal $w.wkday3 $w.label3 $w.mon3 $w.year3 -side left -anchor w -in $w.o3

    # EXPIRY DATE
    checkbutton $w.expbut -text "Expire after"
    $w.expbut deselect
    menubutton $w.expday -text $day -relief raised -menu $w.expday.menu
    CreateDayMenu $w.expday 1 31 0
    menubutton $w.expmon -text $month -relief raised -menu $w.expmon.menu
    CreateMonthMenu $w.expmon 0
    menubutton $w.expyear -text $year -relief raised -menu $w.expyear.menu
    CreateYearMenu $w.expyear 0

    pack $w.expbut $w.expday $w.expmon $w.expyear -side left -anchor w -in $w.exp

    # ADVANCE NOTICE
    checkbutton $w.advbut -text "Issue"
    $w.advbut deselect
    menubutton $w.advdays -text 3 -menu $w.advdays.menu -relief raised
    CreateDayMenu $w.advdays 1 10 0
    label $w.advlab -text "day(s) in advance"
    checkbutton $w.advcount -text "not counting holidays/weekend"
    $w.advcount select
    pack $w.advbut $w.advdays $w.advlab $w.advcount -side left -anchor w -in $w.adv

    # WEEKEND
    label $w.weeklab -text "Weekend is: "
    pack $w.weeklab -side left -anchor w -in $w.weekend
    foreach d $EnglishDayNames {
	checkbutton $w.d$d -text $d
	$w.d$d deselect
	pack $w.d$d -side left -anchor w -in $w.weekend
    }
    $w.dSaturday select
    $w.dSunday select

    # TIMED REMINDER
    checkbutton $w.timebut -text "Timed reminder at"
    $w.timebut deselect
    menubutton $w.timehour -text "12" -menu $w.timehour.menu -relief raised
    CreateDayMenu $w.timehour 1 12 0
    menubutton $w.timemin -text "00" -menu $w.timemin.menu -relief raised
    menu $w.timemin.menu -tearoff 0
    foreach i {00 05 10 15 20 25 30 35 40 45 50 55} {
	$w.timemin.menu add command -label $i \
		-command "$w.timemin configure -text $i"
    }

    menubutton $w.ampm -text "PM" -menu $w.ampm.menu -relief raised
    menu $w.ampm.menu -tearoff 0
    $w.ampm.menu add command -label "AM" -command "$w.ampm configure -text {AM}"
    $w.ampm.menu add command -label "PM" -command "$w.ampm configure -text {PM}"

    checkbutton $w.timeadvbut -text "with"
    $w.timeadvbut deselect
    menubutton $w.timeadv -text "15" -menu $w.timeadv.menu -relief raised
    menu $w.timeadv.menu -tearoff 0
    foreach i {5 10 15 30 45 60} {
	$w.timeadv.menu add command -label $i -command "$w.timeadv configure -text $i"
    }
    label $w.timelab1 -text "minutes advance notice"

    checkbutton $w.timerepbut -text "repeated every"
    $w.timerepbut deselect
    menubutton $w.timerep -text "5" -menu $w.timerep.menu -relief raised
    menu $w.timerep.menu -tearoff 0
    foreach i {3 5 10 15 30} {
	$w.timerep.menu add command -label $i -command "$w.timerep configure -text $i"
    }
    label $w.timelab2 -text "minutes"
    pack $w.timebut $w.timehour $w.timemin $w.ampm $w.timeadvbut $w.timeadv $w.timelab1 $w.timerepbut $w.timerep $w.timelab2 -side left -anchor w -in $w.time

    # SKIP TYPE
    label $w.labhol -text "On holidays or weekends:"
    radiobutton $w.issue -variable SkipType -value 1 -text "Issue reminder as usual"
    radiobutton $w.skip -variable SkipType -value 2 -text "Skip reminder"
    radiobutton $w.before -variable SkipType -value 3 -text "Move reminder before holiday or weekend"
    radiobutton $w.after -variable SkipType -value 4 -text "Move reminder after holiday or weekend"
    pack $w.labhol $w.issue $w.skip $w.before $w.after -side top -anchor w -in $w.hol

    # TEXT ENTRY
    label $w.msglab -text "Body:"
    entry $w.entry
    pack $w.msglab -side left -anchor w -in $w.msg
    pack $w.entry -side left -anchor w -expand 1 -fill x -in $w.msg

    # BUTTONS
    set nbut 0
    foreach but $args {
	incr nbut
	button $w.but$nbut -text $but -command "set ModifyDialogResult $nbut"
	pack $w.but$nbut -side left -anchor w -in $w.buttons -expand 1 -fill x
    }

    bind $w <KeyPress-Escape> "$w.but1 invoke"
    set ModifyDialogResult 0

    # Center the window on the root
    CenterWindow $w
}

#***********************************************************************
# %PROCEDURE: RemindDialogToOptions
# %ARGUMENTS:
#  w -- dialog window
# %RETURNS:
#  A list of flag/value pairs representing the current state of
#  the "create reminder" dialog.
#***********************************************************************
proc RemindDialogToOptions { w } {
    global OptionType SkipType repbut expbut advbut advcount
    global timebut timeadvbut timerepbut
    global dSaturday dSunday dMonday dTuesday dWednesday dThursday dFriday
    set ans {}
    lappend ans "-global-OptionType" $OptionType
    lappend ans "-global-SkipType" $SkipType
    foreach win [winfo children $w] {
	set winstem [winfo name $win]
	switch -exact -- [winfo class $win] {
	    "Menubutton" {
		lappend ans "-text-$winstem" [$win cget -text]
	    }
	    "Checkbutton" {
		lappend ans "-global-$winstem" [eval set $winstem]
	    }
	    "Entry" {
		lappend ans "-entry-$winstem" [$win get]
	    }
	}
    }
    return $ans
}

#***********************************************************************
# %PROCEDURE: OptionsToRemindDialog
# %ARGUMENTS:
#  w -- Remind dialog window
#  opts -- option list set by RemindDialogToOptions
# %RETURNS:
#  Nothing
# %DESCRIPTION:
#  Sets parameters in the dialog box according to saved options.
#***********************************************************************
proc OptionsToRemindDialog { w opts } {
    global OptionType SkipType repbut expbut advbut advcount
    global timebut timeadvbut timerepbut
    global dSaturday dSunday dMonday dTuesday dWednesday dThursday dFriday
    foreach {flag value} $opts {
	switch -glob -- $flag {
	    "-text-*" {
		set win [string range $flag 6 end]
		$w.$win configure -text $value
	    }
	    "-global-*" {
		set win [string range $flag 8 end]
		set $win $value
	    }
	    "-entry-*" {
		set win [string range $flag 7 end]
		$w.$win delete 0 end
		$w.$win insert end $value
	    }
	}
    }
}

#---------------------------------------------------------------------------
# CreateMonthMenu -- create a menu with all the months of the year
# Arguments:
#    w -- menu button -- becomes parent of menu
#    every -- if true, include an "every month" entry
#---------------------------------------------------------------------------
proc CreateMonthMenu {w {every 1}} {
    global MonthNames
    menu $w.menu -tearoff 0

    if {$every} {
	$w.menu add command -label "every month" -command "$w configure -text {every month}"
    }

    foreach month $MonthNames {
	$w.menu add command -label $month -command "$w configure -text $month"
    }
}

#---------------------------------------------------------------------------
# CreateWeekdayMenu -- create a menu with all the weekdays
# Arguments:
#    w -- menu button -- becomes parent of menu
#---------------------------------------------------------------------------
proc CreateWeekdayMenu {w} {
    global EnglishDayNames
    menu $w.menu -tearoff 0

    foreach d $EnglishDayNames {
	$w.menu add command -label $d -command "$w configure -text $d"
    }
    $w.menu add command -label "weekday" -command "$w configure -text weekday"
}

#---------------------------------------------------------------------------
# CreateDayMenu -- create a menu with entries 1-31 and possibly "every day"
# Arguments:
#    w -- menu button -- becomes parent of menu
#    min -- minimum day to start from.
#    max -- maximum day to go up to
#    every -- if true, include an "every day" entry
#---------------------------------------------------------------------------
proc CreateDayMenu {w {min 1} {max 31} {every 1}} {
    menu $w.menu -tearoff 0
    if {$every} {
	$w.menu add command -label "every day" -command "$w configure -text {every day}"
    }
    set d $min
    while { $d <= $max } {
	$w.menu add command -label $d -command "$w configure -text $d"
	incr d
    }
}

#---------------------------------------------------------------------------
# CreateYearMenu -- create a menu with entries from this year to this year+10
#                   and possibly "every year"
# Arguments:
#    w -- menu button -- becomes parent of menu
#    every -- if true, include an "every year" entry
#---------------------------------------------------------------------------
proc CreateYearMenu {w {every 1}} {
    menu $w.menu -tearoff 0
    if {$every} {
	$w.menu add command -label "every year" -command "$w configure -text {every year}"
    }
    global CurYear
    set d $CurYear
    while { $d < [expr $CurYear + 11] } {
	$w.menu add command -label $d -command "$w configure -text $d"
	incr d
    }
}

#---------------------------------------------------------------------------
# ModifyDay -- bring up dialog for adding reminder.
# Arguments:
#    d -- which day to modify
#    firstDay -- first weekday in month (0-6)
#---------------------------------------------------------------------------
proc ModifyDay {d firstDay} {
    global ModifyDialogResult AppendFile HighestTagSoFar ReminderTags
    catch {destroy .mod}
    toplevel .mod
    CreateModifyDialog .mod $d $firstDay "Cancel" "Add to reminder file" "Preview reminder"
    wm title .mod "TkRemind Add Reminder..."
    wm iconname .mod "Add Reminder"
    tkwait visibility .mod
    set oldFocus [focus]
    while {1} {
	grab .mod
	focus .mod.entry
	set ModifyDialogResult -1
	tkwait variable ModifyDialogResult
	if {$ModifyDialogResult == 1} {
	    catch {focus $oldFocus}
	    destroy .mod
	    return 0
	}
	set problem [catch {set rem [CreateReminder .mod]} err]
	if {$problem} {
	    tk_dialog .error Error "$err" error 0 Ok
	} else {
	    if {$ModifyDialogResult == 3} {
		set rem [EditReminder $rem Cancel "Add reminder"]
		if {$ModifyDialogResult == 1} {
		    continue
		}
	    }
	    set opts [RemindDialogToOptions .mod]
	    catch {focus $oldFocus}
	    destroy .mod
	    Status "Writing reminder..."
	    set f [open $AppendFile a]
	    incr HighestTagSoFar
	    set ReminderTags($HighestTagSoFar) 1

	    WriteReminder $f TKTAG$HighestTagSoFar $rem $opts
	    close $f

	    FillCalWindow
	    RestartBackgroundRemindDaemon
	    return 0
	}
    }
}

#---------------------------------------------------------------------------
# CenterWindow -- center a window on the screen.  Stolen from tk_dialog code
# Arguments:
#    w -- window to center
#---------------------------------------------------------------------------
proc CenterWindow {w} {
    wm withdraw $w
    update idletasks
    set x [expr [winfo screenwidth $w]/2 - [winfo reqwidth $w]/2 \
	    - [winfo vrootx [winfo parent $w]]]
    set y [expr [winfo screenheight $w]/2 - [winfo reqheight $w]/2 \
	    - [winfo vrooty [winfo parent $w]]]
    wm geom $w +$x+$y
    wm deiconify $w
}

#---------------------------------------------------------------------------
# CreateReminder -- create the reminder
# Arguments:
#    w -- the window containing the add reminder dialog box.
# Returns:
#    The reminder as a string.
#---------------------------------------------------------------------------
proc CreateReminder {w} {
    global DidOmit

    set body [string trim [$w.entry get]]

    if {"$body" == ""} {
	error "Blank body in reminder"
    }

    set DidOmit 0
    set needOmit 0
    # Delegate the first part to CreateReminder1, CreateReminder2, or
    # CreateReminder3
    global OptionType SkipType repbut expbut advbut advcount
    global timebut timeadvbut timerepbut

    set rem [CreateReminder$OptionType $w]

    # Do the "until" part
    if {$expbut} {
	append rem " UNTIL [$w.expday cget -text] [$w.expmon cget -text] [$w.expyear cget -text]"
    }

    # Advance warning
    if {$advbut} {
	append rem " +"
	if {!$advcount} {
	    append rem "+"
	} else {
	    set needOmit 1
	}
	append rem [$w.advdays cget -text]
    }

    # Timed reminder
    if {$timebut} {
	set hour [$w.timehour cget -text]
	set min [$w.timemin cget -text]
	if {[$w.ampm cget -text] == "PM"} {
	    if {$hour < 12} {
		incr hour 12
	    }
	} else {
	    if {$hour == 12} {
		set hour 0
	    }
	}
	append rem " AT $hour:$min"
	if {$timeadvbut} {
	    append rem " +[$w.timeadv cget -text]"
	}
	if {$timerepbut} {
	    append rem " *[$w.timerep cget -text]"
	}
    }

    global SkipType
    if {$SkipType == 2} {
	append rem " SKIP"
	set needOmit 1
    } elseif {$SkipType == 3} {
	append rem " BEFORE"
	set needOmit 1
    } elseif {$SkipType == 4} {
	append rem " AFTER"
	set needOmit 1
    }

    if {$needOmit && !$DidOmit} {
	append rem " OMIT [GetWeekend $w 1]"
    }


    # Check it out!
    global Remind
    set f [open "|$Remind -arq -e -" r+]
    puts $f "BANNER %"
    puts $f "$rem MSG %"
    puts $f "MSG %_%_%_%_"
    puts $f "FLUSH"
    flush $f
    set err {}
    catch {set err [gets $f]}
    catch {close $f}
    if {"$err" != ""} {
	# Clean up the message a bit
	regsub -- {^-stdin-\([0-9]*\): } $err {} err
	error "Error from Remind: $err"
    }
    append rem " MSG $body"
    return $rem
}

#---------------------------------------------------------------------------
# CreateReminder1 -- Create the first part of a type-1 reminder
# Arguments:
#    w -- add reminder dialog window
# Returns: first part of reminder
#---------------------------------------------------------------------------
proc CreateReminder1 {w} {

    global repbut

    set rem "REM"
    set gotDay 0
    set gotMon 0
    set gotYear 0
    set d [$w.day1 cget -text]
    if {"$d" != "every day"} {
	append rem " $d"
	set gotDay 1
    }
    set m [$w.mon1 cget -text]
    if {"$m" != "every month"} {
	append rem " $m"
	set gotMon 1
    }
    set y [$w.year1 cget -text]
    if {"$y" != "every year"} {
	append rem " $y"
	set gotYear 1
    }

    # Check for repetition
    if {$repbut} {
	if {!$gotDay || !$gotMon || !$gotYear} {
	    error "All components of a date must be specified if you wish to use the repeat feature."
	}
	append rem " *[$w.repdays cget -text]"
    }

    return $rem
}

#---------------------------------------------------------------------------
# CreateReminder2 -- Create the first part of a type-2 reminder
# Arguments:
#    w -- add reminder dialog window
# Returns: first part of reminder
#---------------------------------------------------------------------------
proc CreateReminder2 {w} {
    set wkday [$w.wkday2 cget -text]
    if {"$wkday" == "weekday"} {
	set wkday [GetWeekend $w 0]
    }
    set day [$w.day2 cget -text]
    set mon [$w.mon2 cget -text]
    set year [$w.year2 cget -text]
    set rem "REM $wkday $day"
    if {$mon != "every month"} {
	append rem " $mon"
    }
    if {$year != "every year"} {
	append rem " $year"
    }
    return $rem
}

#---------------------------------------------------------------------------
# CreateReminder3 -- Create the first part of a type-3 reminder
# Arguments:
#    w -- add reminder dialog window
# Returns: first part of reminder
#---------------------------------------------------------------------------
proc CreateReminder3 {w} {
    global MonthNames DidOmit
    set which [$w.ordinal cget -text]
    set day [$w.wkday3 cget -text]
    set mon [$w.mon3 cget -text]
    set year [$w.year3 cget -text]
    set rem "REM"
    if {$which != "Last"} {
	if {$which == "First"} {
	    append rem " 1"
	} elseif {$which == "Second"} {
	    append rem " 8"
	} elseif {$which == "Third"} {
	    append rem " 15"
	} elseif {$which == "Fourth"} {
	    append rem " 22"
	}
	if {$day != "weekday"} {
	    append rem " $day"
	} else {
	    append rem " [GetWeekend $w 0]"
	}
	if {$mon != "every month"} {
	    append rem " $mon"
	}
	if {$year != "every year"} {
	    append rem " $year"
	}
    } else {
	if {$day != "weekday"} {
	    append rem " $day 1 --7"
	} else {
	    append rem " 1 -1 OMIT [GetWeekend $w 1]"
	    set DidOmit 1
	}
	if {$mon != "every month"} {
	    set i [lsearch -exact $MonthNames $mon]
	    if {$i == 11} {
		set i 0
	    } else {
		incr i
	    }
	    append rem " [lindex $MonthNames $i]"
	}
	if {$year != "every year"} {
	    if {$mon == "December"} {
		incr year
	    }
	    append rem " $year"
	}
    }
    return $rem
}

#---------------------------------------------------------------------------
# GetWeekend -- returns a list of weekdays or weekend days
# Arguments:
#    w -- add reminder dialog window
#    wkend -- if 1, we want weekend.  If 0, we want weekdays.
# Returns:
#    list of weekdays or weekend-days
#---------------------------------------------------------------------------
proc GetWeekend {w wkend} {
    global dSaturday dSunday dMonday dTuesday dWednesday dThursday dFriday
    global EnglishDayNames
    set ret {}
    foreach d $EnglishDayNames {
	set v [set d$d]
	if {$v == $wkend} {
	    lappend ret $d
	}
    }
    return $ret
}

#---------------------------------------------------------------------------
# EditReminder -- allow user to edit what gets put in reminder file
# Arguments:
#    rem -- current reminder
#    args -- buttons to add to bottom
# Returns:
#    edited version of rem
#---------------------------------------------------------------------------
proc EditReminder {rem args} {
    catch {destroy .edit}
    global ModifyDialogResult
    toplevel .edit
    wm title .edit "TkRemind Preview reminder"
    wm iconname .edit "Preview reminder"
    text .edit.t -width 80 -height 5 -relief sunken
    .edit.t insert end $rem
    frame .edit.f
    set n 0
    foreach but $args {
	incr n
	button .edit.but$n -text $but -command "set ModifyDialogResult $n"
	pack .edit.but$n -in .edit.f -side left -fill x -expand 1
    }
    pack .edit.t -side top -fill both -expand 1
    pack .edit.f -side top -fill x -expand 1
    bind .edit <KeyPress-Escape> ".edit.but1 invoke"
    set ModifyDialogResult 0
    CenterWindow .edit
    tkwait visibility .edit
    set oldFocus [focus]
    focus .edit.t
    grab .edit
    tkwait variable ModifyDialogResult
    catch {focus $oldFocus}
    set rem [.edit.t get 1.0 end]
    catch {destroy .edit}
    return $rem
}

#---------------------------------------------------------------------------
# SetWinAttr -- sets an attribute for a window
# Arguments:
#    w -- window name
#    attr -- attribute name
#    val -- value to set it to
# Returns:
#    $val
#---------------------------------------------------------------------------
proc SetWinAttr {w attr val} {
    global attrPriv
    set attrPriv($w-$attr) $val
}

#---------------------------------------------------------------------------
# GetWinAttr -- gets an attribute for a window
# Arguments:
#    w -- window name
#    attr -- attribute name
# Returns:
#    Value of attribute
#---------------------------------------------------------------------------
proc GetWinAttr {w attr} {
    global attrPriv
    return $attrPriv($w-$attr)
}

#---------------------------------------------------------------------------
# WaitWinAttr -- wait for a window attribute to change
# Arguments:
#    w -- window name
#    attr -- attribute name
# Returns:
#    Value of attribute
#---------------------------------------------------------------------------
proc WaitWinAttr {w attr} {
    global attrPriv
    tkwait variable attrPriv($w-$attr)
    return $attrPriv($w-$attr)
}

#---------------------------------------------------------------------------
# BrowseForFile -- creates and operates a file browser dialog.
# Arguments:
#    w -- dialog window.
#    title -- dialog title
#    oktext -- text for "OK" button
#    showdots -- if non-zero, shows "dot" files as well.
# Returns:
#    complete path of filename chosen, or "" if Cancel pressed.
#---------------------------------------------------------------------------
proc BrowseForFile {w title {oktext "OK"} {showdots 0} {filter "*"}} {
    catch {destroy $w}
    toplevel $w
    wm title $w $title

    # Global array to hold window attributes
    global a${w}

    SetWinAttr $w status busy
    SetWinAttr $w showdots $showdots

    frame $w.fileframe
    frame $w.butframe
    label $w.cwd -text [pwd]
    entry $w.entry
    label $w.masklab -text "Match: "
    listbox $w.list -yscrollcommand "$w.scroll set"
    scrollbar $w.scroll -command "$w.list yview"
    button $w.ok -text $oktext -command "BrowseForFileOK $w"
    button $w.cancel -text "Cancel" -command "BrowseForFileCancel $w"
    entry $w.filter -width 7
    $w.filter insert end $filter

    pack $w.cwd $w.entry -side top -expand 0 -fill x
    pack $w.fileframe -side top -expand 1 -fill both
    pack $w.butframe -side top -expand 0 -fill x
    pack $w.list -in $w.fileframe -side left -expand 1 -fill both
    pack $w.scroll -in $w.fileframe -side left -expand 0 -fill y
    pack $w.ok -in $w.butframe -side left -expand 1 -fill x
    pack $w.cancel -in $w.butframe -side left -expand 1 -fill x
    pack $w.masklab -in $w.butframe -side left -expand 0
    pack $w.filter -in $w.butframe -side left -expand 1 -fill x

    # Fill in the box and wait for status to change
    BrowseForFileRead $w [pwd]

    bind $w <KeyPress-Escape> "$w.cancel flash; $w.cancel invoke"
    bind $w.list <Button-1> "$w.entry delete 0 end; $w.entry insert 0 \[selection get\]"
    bind $w.list <Double-Button-1> "$w.ok flash; $w.ok invoke"
    bind $w.list <Return> "$w.entry delete 0 end; $w.entry insert 0 \[selection get\]; $w.ok flash; $w.ok invoke"
    bind $w.entry <Return> "$w.ok flash; $w.ok invoke"
    bind $w.filter <Return> "BrowseForFileRead $w"
    bind $w.entry <KeyPress> "CompleteFile $w"
    bind $w.entry <KeyPress-space> "ExpandFile $w"
    bindtags $w.entry "Entry $w.entry $w all"

    bindtags $w.list "Listbox $w.list $w all"
    CenterWindow $w
    set oldFocus [focus]
    tkwait visibility $w
    focus $w.entry
    set oldGrab [grab current $w]
    grab set $w
    WaitWinAttr $w status
    catch {focus $oldFocus}
    catch {grab set $oldGrab}
    set ans [GetWinAttr $w status]
    destroy $w
    return $ans
}

proc CompleteFile {w} {
    set index [lsearch [$w.list get 0 end] [$w.entry get]* ]
    if {$index > -1} {
	$w.list see $index
	$w.list selection clear 0 end
	$w.list selection set $index
    }
}

proc ExpandFile {w} {
    set stuff [$w.list curselection]
    if {[string compare $stuff ""]} {
	$w.entry delete 0 end
	$w.entry insert end [$w.list get $stuff]
    }
}

proc BrowseForFileCancel {w} {
    SetWinAttr $w status {}
}

proc BrowseForFileOK {w} {
    set fname [$w.entry get]
    if {[string compare $fname ""]} {
	# If it starts with a slash, handle it specially.
	if {[string match "/*" $fname]} {
	    if {[file isdirectory $fname]} {
		BrowseForFileRead $w $fname
		return
	    } else {
		SetWinAttr $w status $fname
		return
	    }
	}
	if {[string match */ $fname]} {
	    set fname [string trimright $fname /]
	}
	if {[$w.cwd cget -text] == "/"} {
	    set fname "/$fname"
	} else {
	    set fname "[$w.cwd cget -text]/$fname"
	}
	# If it's a directory, change directories
	if {[file isdirectory $fname]} {
	    BrowseForFileRead $w $fname
	} else {
	    SetWinAttr $w status $fname
	}
    }
}

#---------------------------------------------------------------------------
# BrowseForFileRead -- read the current directory into the file browser
# Arguments:
#    w -- window name
#    dir -- directory
# Returns:
#    nothing
#---------------------------------------------------------------------------
proc BrowseForFileRead {w {dir ""}} {
    # Save working dir
    set cwd [pwd]
    if {$dir == ""} {
	set dir [$w.cwd cget -text]
    }
    if {[catch "cd $dir" err]} {
	tk_dialog .error Error "$err" error 0 Ok
	return
    }
    $w.cwd configure -text [pwd]
    if {[GetWinAttr $w showdots]} {
	set flist [glob -nocomplain .* *]
    } else {
	set flist [glob -nocomplain *]
    }
    set flist [lsort $flist]
    set filter [$w.filter get]
    if {$filter == ""} {
	set filter "*"
    }
    $w.list delete 0 end
    foreach item $flist {
	if {$item != "." && $item != ".."} {
	    if {[file isdirectory $item]} {
		$w.list insert end "$item/"
	    } else {
		if {[string match $filter $item]} {
		    $w.list insert end $item
		}
	    }
	}
    }
    if {[pwd] != "/"} {
	$w.list insert 0 "../"
    }
    cd $cwd
    $w.entry delete 0 end
}

#---------------------------------------------------------------------------
# StartBackgroundRemindDaemon
# Arguments:
#    none
# Returns:
#    nothing
# Description:
#    Starts a background Remind daemon to handle timed reminders
#---------------------------------------------------------------------------
proc StartBackgroundRemindDaemon {} {
    global Remind DaemonFile ReminderFile
    set problem [catch { set DaemonFile [open "|$Remind -z0 $ReminderFile" "r+"] } err]
    if {$problem} {
	tk_dialog .error Error "Can't start Remind daemon in background: $err" error 0 OK
    } else {
	fileevent $DaemonFile readable "DaemonReadable $DaemonFile"
	puts $DaemonFile "STATUS"
	flush $DaemonFile
    }
}

#---------------------------------------------------------------------------
# StopBackgroundRemindDaemon
# Arguments:
#    none
# Returns:
#    nothing
# Description:
#    Stops the background Remind daemon
#---------------------------------------------------------------------------
proc StopBackgroundRemindDaemon {} {
    global DaemonFile
    catch {
	puts $DaemonFile "EXIT"
	flush $DaemonFile
	close $DaemonFile
    }
}

#---------------------------------------------------------------------------
# RestartBackgroundRemindDaemon
# Arguments:
#    none
# Returns:
#    nothing
# Description:
#    Restarts the background Remind daemon
#---------------------------------------------------------------------------
proc RestartBackgroundRemindDaemon {} {
    global DaemonFile
    catch {
	puts $DaemonFile "REREAD"
	flush $DaemonFile
    }
}

#---------------------------------------------------------------------------
# DaemonReadable
# Arguments:
#    file -- file channel that is readable
# Returns:
#    nothing
# Description:
#    Reads data from the Remind daemon and handles it appropriately
#---------------------------------------------------------------------------
proc DaemonReadable { file } {
    global Ignore
    set line ""
    catch { set num [gets $file line] }
    if {$num < 0} {
	catch { close $file }
	return
    }
    switch -glob -- $line {
	"NOTE reminder*" {
	    scan $line "NOTE reminder %s %s %s" time now tag
	    IssueBackgroundReminder $file $time $now $tag
	}
	"NOTE newdate" {
	    # Date has rolled over -- clear "ignore" list
	    catch { unset Ignore}
	    Initialize
	    FillCalWindow
	    ShowTodaysReminders
	}
	"NOTE reread" {
	    puts $file "STATUS"
	    flush $file
	}
	"NOTE queued*" {
	    scan $line "NOTE queued %d" n
	    if {$n == 1} {
		.b.nqueued configure -text "1 reminder queued"
	    } else {
		.b.nqueued configure -text "$n reminders queued"
	    }
	}
	default {
	    puts "Unknown message from daemon: $line\n"
	}
    }
}

#---------------------------------------------------------------------------
# IssueBackgroundReminder
# Arguments:
#    file -- file channel that is readable
#    time -- time of reminder
#    now -- current time according to Remind daemon
#    tag -- tag for reminder, or "*" if no tag
# Returns:
#    nothing
# Description:
#    Reads a background reminder from daemon and pops up window.
#---------------------------------------------------------------------------
proc IssueBackgroundReminder { file time now tag } {
    global BgCounter Option Ignore
    if {$Option(Deiconify)} {
	wm deiconify .
    }

    set msg ""
    set line ""
    while (1) {
	gets $file line
	if {$line == "NOTE endreminder"} {
	    break
	}
	if {$msg != ""} {
	    append msg "\n";
	}
	append msg $line
    }
    # Do nothing if it's blank -- was probably a RUN-type reminder.
    if {$msg == ""} {
	return
    }

    # Do nothing if user told us to ignore this reminder
    if {[info exists Ignore($tag)]} {
	return
    }

    incr BgCounter
    set w .bg$BgCounter
    toplevel $w
    wm iconname $w "Reminder"
    wm title $w "Timed reminder ($time)"
    label $w.l -text "Reminder for $time issued at $now"
    message $w.msg -width 6i -text $msg
    frame $w.b
    button $w.ok -text "OK" -command "destroy $w"
    if {$tag != "*"} {
	button $w.nomore -text "Don't remind me again today" -command \
		"destroy $w; set Ignore($tag) 1"
	button $w.kill -text "Delete this reminder completely" -command \
		"destroy $w; InteractiveDeleteReminder $tag"
    }
    pack $w.l -side top
    pack $w.msg -side top -expand 1 -fill both
    pack $w.b -side top
    pack $w.ok -in $w.b -side left
    if {$tag != "*"} {
	pack $w.nomore $w.kill -in $w.b -side left
    }

    CenterWindow $w

    # Automatically shut down window after a minute if option says so
    if {$Option(AutoClose)} {
	after 60000 "catch { destroy $w }"
    }

    update
    if {$Option(RingBell)} {
	bell
    }
    if {$Option(RunCmd) != ""} {
	if {$Option(FeedReminder)} {
	    FeedReminderToCommand $Option(RunCmd) $msg
	} else {
	    exec "/bin/sh" "-c" $Option(RunCmd) "&"
	}
    }

    # reread status
    if {$file != "stdin"} {
	puts $file "STATUS"
	flush $file
    }
}

#***********************************************************************
# %PROCEDURE: FeedReminderToCommand
# %ARGUMENTS:
#  cmd -- command to execute
#  msg -- what to feed it
# %RETURNS:
#  Nothing
# %DESCRIPTION:
#  Feeds "$msg" to a command.
#***********************************************************************
proc FeedReminderToCommand { cmd msg } {
    catch {
	set f [open "|$cmd" "w"]
	fconfigure $f -blocking 0
	fileevent $f writable [list CommandWritable $f $msg]
    }
}

#***********************************************************************
# %PROCEDURE: CommandWritable
# %ARGUMENTS:
#  f -- file which is writable
#  msg -- message to write
# %RETURNS:
#  Nothing
# %DESCRIPTION:
#  Writes $msg to $f; closes $f.
#***********************************************************************
proc CommandWritable { f msg } {
    puts $f $msg
    flush $f
    close $f
}


proc main {} {
    # If no ~/.tkremindrc file, create an empty one
    if {![file exists ~/.tkremindrc]} {
	catch {
	    set f [open ~/.tkremindrc "w"]
	    close $f
	}
    }

    global AppendFile HighestTagSoFar DayNames
    puts "\nTkRemind Copyright (C) 1996-1998 David F. Skoll"
    puts "Copyright (C) 1999-2000 Roaring Penguin Software Inc."
    LoadOptions
    CreateMoonImages
    Initialize
    ShowTodaysReminders
    ScanForTags $AppendFile
    CreateCalWindow $DayNames
    FillCalWindow
    StartBackgroundRemindDaemon
    DisplayTimeContinuously
}

#***********************************************************************
# %PROCEDURE: ScanForTags
# %ARGUMENTS:
#  fname -- name of file to scan
# %RETURNS:
#  Nothing
# %DESCRIPTION:
#  Scans the file for all tags of the form "TKTAGnnnn" and builds
#  the tag array.  Also adjusts HighestTagSoFar
#***********************************************************************
proc ScanForTags { fname } {
    global HighestTagSoFar ReminderTags
    if {[catch { set f [open $fname "r"]}]} {
	return
    }
    while {[gets $f line] >= 0} {
	switch -regexp -- $line {
	    {^# TKTAG[0-9]+} {
		regexp {^# TKTAG([0-9]+)} $line dummy tagno
		if {$tagno > $HighestTagSoFar} {
		    set HighestTagSoFar $tagno
		}
		set ReminderTags($tagno) 1
	    }
	}
    }
    close $f
}

#***********************************************************************
# %PROCEDURE: ReadTaggedOptions
# %ARGUMENTS:
#  tag -- tag to match
# %RETURNS:
#  A list of options for the dialog box for the tagged reminder
# %DESCRIPTION:
#  Scans the file for specified tag and returns the "options" list for the
#  reminder.
#***********************************************************************
proc ReadTaggedOptions { tag } {
    global AppendFile
    if {[catch { set f [open $AppendFile "r"]}]} {
	return ""
    }
    while {[gets $f line] >= 0} {
	if {[string match "# $tag *" $line]} {
	    gets $f line
	    close $f
	    return [string range $line 2 end]
	}
    }
    close $f
    return ""
}

proc FireEditor { w } {
    global Option
    global EditorPid
    set tags [$w tag names current]
    set index [lsearch -glob $tags "FILE_*"]
    if {$index < 0} {
	return
    }
    set tag [lindex $tags $index]
    if {![regexp {^FILE_([0-9]+)_(.*)} $tag all line file]} {
	return
    }
    set editor $Option(Editor)
    regsub -all "%s" $editor $file editor
    regsub -all "%d" $editor $line editor

    # Don't fire up a second editor if first is running
    if {$EditorPid >= 0} {
	if {![catch {exec kill -0 $EditorPid}]} {
	    Status "Editor already active!"
	    after 2500 DisplayTime
	    bell
	    return
	}
    }
    set EditorPid [exec sh -c $editor &]
}

#***********************************************************************
# %PROCEDURE: GetCurrentReminder
# %ARGUMENTS:
#  w -- text window
# %RETURNS:
#  The tag (TKTAGnnnn) for current editable reminder, or "" if no
#  current editable reminder.
#***********************************************************************
proc GetCurrentReminder { w } {
    set tags [$w tag names current]
    set index [lsearch -glob $tags "TKTAG*"]
    if {$index < 0} {
	return ""
    }
    set tag [lindex $tags $index]
    return $tag
}

#***********************************************************************
# %PROCEDURE: TaggedEnter
# %ARGUMENTS:
#  w -- text window
# %RETURNS:
#  Nothing
# %DESCRIPTION:
#  Highlights an "editable" reminder as mouse moves into it
#***********************************************************************
proc TaggedEnter { w } {
    set tag [GetCurrentReminder $w]
    if {$tag != ""} {
	$w tag configure $tag -foreground #FF0000
    }
}
#***********************************************************************
# %PROCEDURE: TaggedLeave
# %ARGUMENTS:
#  w -- text window
# %RETURNS:
#  Nothing
# %DESCRIPTION:
#  Removes highlight from an "editable" reminder as mouse leaves it
#***********************************************************************
proc TaggedLeave { w } {
    set tag [GetCurrentReminder $w]
    if {$tag != ""} {
	set tags [$w tag names current]
	set index [lsearch -glob $tags "clr*"]
	if {$index < 0} {
	    set fg "#000000"
	} else {
	    set fg [string range [lindex $tags $index] 3 end]
	    set fg "#$fg"
	}
	$w tag configure $tag -foreground $fg
    }
}

#***********************************************************************
# %PROCEDURE: EditTaggedReminder
# %ARGUMENTS:
#  w -- text window
# %RETURNS:
#  Nothing
# %DESCRIPTION:
#  Opens a dialog box to edit the current editable reminder
#***********************************************************************
proc EditTaggedReminder { w } {
    global ModifyDialogResult
    set tag [GetCurrentReminder $w]
    if {$tag == ""} {
	return
    }

    # Read in options
    set opts [ReadTaggedOptions $tag]
    if {$opts == ""} {
	return
    }

    toplevel .mod
    CreateModifyDialog .mod 1 0 "Cancel" "Replace reminder" "Delete reminder" "Preview reminder"
    wm title .mod "TkRemind Edit Reminder..."
    wm iconname .mod "Edit Reminder"
    OptionsToRemindDialog .mod $opts

    tkwait visibility .mod
    set oldFocus [focus]
    while {1} {
	grab .mod
	focus .mod.entry
	set ModifyDialogResult -1
	tkwait variable ModifyDialogResult
	if {$ModifyDialogResult == 1} {
	    catch {focus $oldFocus}
	    destroy .mod
	    return 0
	}
	set problem [catch {set rem [CreateReminder .mod]} err]
	if {$problem} {
	    tk_dialog .error Error "$err" error 0 Ok
	    continue
	}
	if {$ModifyDialogResult == 4} {
	    set rem [EditReminder $rem "Cancel" "Replace reminder"]
	    if {$ModifyDialogResult == 1} {
		continue
	    }
	}
	set opts [RemindDialogToOptions .mod]
	catch {focus $oldFocus}
	destroy .mod
	set problem [catch {
	    if {$ModifyDialogResult == 2} {
		ReplaceTaggedReminder $tag $rem $opts
	    } else {
		DeleteTaggedReminder $tag
	    }
	} err]
	if {$problem} {
	    tk_dialog .error Error "Error: $err" error 0 Ok
	    return 1
	}

	FillCalWindow
	RestartBackgroundRemindDaemon
	return 0
    }
}

#***********************************************************************
# %PROCEDURE: UniqueFileName
# %ARGUMENTS:
#  stem -- base name of file
# %RETURNS:
#  A filename of the form "stem.xxx" which does not exist
#***********************************************************************
proc UniqueFileName { stem } {
    set n 1
    while {[file exists $stem.$n]} {
	incr n
    }
    return $stem.$n
}


#***********************************************************************
# %PROCEDURE: DeleteTaggedReminder
# %ARGUMENTS:
#  tag -- tag of reminder to delete
# %RETURNS:
#  Nothing
# %DESCRIPTION:
#  Deletes tagged reminder from reminder file
#***********************************************************************
proc DeleteTaggedReminder { tag } {
    global AppendFile

    set tmpfile [UniqueFileName $AppendFile]
    set out [open $tmpfile "w"]
    set in [open $AppendFile "r"]

    set foundStart 0
    set foundEnd 0

    while {[gets $in line] >= 0} {
	if {[string match "# $tag *" $line]} {
	    set foundStart 1
	    break
	}
	puts $out $line
    }

    if {! $foundStart} {
	close $in
	close $out
	file delete $tmpfile
	error "Did not find start of reminder with tag $tag"
    }

    while {[gets $in line] >= 0} {
	if { $line == "# TKEND"} {
	    set foundEnd 1
	    break
	}
    }

    if {! $foundEnd} {
	close $in
	close $out
	file delete $tmpfile
	error "Did not find end of reminder with tag $tag"
    }

    while {[gets $in line] >= 0} {
	puts $out $line
    }
    close $in
    close $out
    file rename -force -- $tmpfile $AppendFile
}

#***********************************************************************
# %PROCEDURE: ReplaceTaggedReminder
# %ARGUMENTS:
#  tag -- tag of reminder to replace
#  rem -- text to replace it with
#  opts -- edit options
# %RETURNS:
#  Nothing
# %DESCRIPTION:
#  Replaces a tagged reminder in the reminder file
#***********************************************************************
proc ReplaceTaggedReminder { tag rem opts } {
    global AppendFile

    set tmpfile [UniqueFileName $AppendFile]
    set out [open $tmpfile "w"]
    set in [open $AppendFile "r"]

    set foundStart 0
    set foundEnd 0

    while {[gets $in line] >= 0} {
	if {[string match "# $tag *" $line]} {
	    set foundStart 1
	    break
	}
	puts $out $line
    }

    if {! $foundStart} {
	close $in
	close $out
	file delete $tmpfile
	error "Did not find start of reminder with tag $tag"
    }

    # Consume the old reminder
    while {[gets $in line] >= 0} {
	if { $line == "# TKEND"} {
	    set foundEnd 1
	    break
	}
    }

    if {! $foundEnd} {
	close $in
	close $out
	file delete $tmpfile
	error "Did not find end of reminder with tag $tag"
    }

    # Write the new reminder
    WriteReminder $out $tag $rem $opts

    # Copy rest of file over
    while {[gets $in line] >= 0} {
	puts $out $line
    }
    close $in
    close $out
    file rename -force -- $tmpfile $AppendFile
}

#***********************************************************************
# %PROCEDURE: WriteReminder
# %ARGUMENTS:
#  out -- file to write to
#  tag -- reminder tag
#  rem -- reminder body
#  opts -- edit options
# %RETURNS:
#  Nothing
# %DESCRIPTION:
#  Writes a reminder to a file
#***********************************************************************
proc WriteReminder { out tag rem opts } {
    puts $out "# $tag Next reminder was created with TkRemind.  DO NOT EDIT"
    puts $out "# $opts"
    if {[string range $rem 0 3] == "REM "} {
	puts $out "REM TAG $tag [string range $rem 4 end]"
    } else {
	puts $out $rem
    }
    puts $out "# TKEND"
}

#***********************************************************************
# %PROCEDURE: DoShadeSpecial
# %ARGUMENTS:
#  n -- calendar box to shade
#  stuff -- Remind command line
# %RETURNS:
#  Nothing
# %DESCRIPTION:
#  Handles the "SHADE" special -- shades a box.
#***********************************************************************
proc DoShadeSpecial { n stuff } {
    set num [scan $stuff "%d %d %d" r g b]
    if {$num == 1} {
	set g $r
	set b $r
    } elseif {$num != 3} {
	return
    }
    if {$r < 0 || $r > 255 || $g < 0 || $g > 255 || $b < 0 || $b > 255} {
	return
    }
    set bg [format "#%02x%02x%02x" $r $g $b]
    .cal.t$n configure -background $bg
}

#***********************************************************************
# %PROCEDURE: DoMoonSpecial
# %ARGUMENTS:
#  n -- calendar box for moon
#  stuff -- Remind command line
# %RETURNS:
#  Nothing
# %DESCRIPTION:
#  Handles the "MOON" special -- draws a moon symbol
#***********************************************************************
proc DoMoonSpecial { n stuff } {
    set msg ""
    set num [scan $stuff "%d %d %d %s" phase junk1 junk2 msg]
    if {$num < 1} {
	return
    }
    if {$phase < 0 || $phase > 3} {
	return
    }
    switch -exact -- $phase {
	0 { set image new }
	1 { set image first }
	2 { set image full }
	3 { set image last }
    }
    .cal.t$n configure -state normal
    .cal.t$n image create end -image $image

    if {$msg != ""} {
	.cal.t$n insert end " $msg"
    }
    .cal.t$n insert end "\n"
    .cal.t$n configure -state disabled
}
#***********************************************************************
# %PROCEDURE: DisplayTime
# %ARGUMENTS:
#  None
# %RETURNS:
#  Nothing
# %DESCRIPTION:
#  Displays current date and time in status window
#***********************************************************************
proc DisplayTime {} {
    set msg [clock format [clock seconds] -format "%e %b %Y %I:%M%p"]
    Status $msg
}

#***********************************************************************
# %PROCEDURE: CreateMoonImages
# %ARGUMENTS:
#  None
# %RETURNS:
#  Nothing
# %DESCRIPTION:
#  Creates the moon images "new", "first", "full" and "last"
#***********************************************************************
proc CreateMoonImages {} {
    image create bitmap full -foreground black \
	    -data "#define newmoon_width 16\n#define newmoon_height 16\nstatic unsigned char newmoon_bits[] = {\n0x00, 0x00, 0xc0, 0x01, 0x30, 0x06, 0x0c, 0x18, 0x04, 0x10, 0x02, 0x20,\n0x02, 0x20, 0x01, 0x40, 0x01, 0x40, 0x01, 0x40, 0x02, 0x20, 0x02, 0x20,\n0x04, 0x10, 0x0c, 0x18, 0x30, 0x06, 0xc0, 0x01};"
    image create bitmap first -foreground black \
	    -data "#define firstquarter_width 16\n#define firstquarter_height 16\nstatic unsigned char firstquarter_bits[] = {\n0x00, 0x00, 0xc0, 0x01, 0xf0, 0x06, 0xfc, 0x18, 0xfc, 0x10, 0xfe, 0x20,\n0xfe, 0x20, 0xff, 0x40, 0xff, 0x40, 0xff, 0x40, 0xfe, 0x20, 0xfe, 0x20,\n0xfc, 0x10, 0xfc, 0x18, 0xf0, 0x06, 0xc0, 0x01};"
    image create bitmap new -foreground black \
	    -data "#define fullmoon_width 16\n#define fullmoon_height 16\nstatic unsigned char fullmoon_bits[] = {\n0x00, 0x00, 0xc0, 0x01, 0xf0, 0x07, 0xfc, 0x1f, 0xfc, 0x1f, 0xfe, 0x3f,\n0xfe, 0x3f, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0xfe, 0x3f, 0xfe, 0x3f,\n0xfc, 0x1f, 0xfc, 0x1f, 0xf0, 0x07, 0xc0, 0x01};"
    image create bitmap last -foreground black \
	    -data "#define lastquarter_width 16\n#define lastquarter_height 16\nstatic unsigned char lastquarter_bits[] = {\n0x00, 0x00, 0xc0, 0x01, 0xb0, 0x07, 0x8c, 0x1f, 0x84, 0x1f, 0x82, 0x3f,\n0x82, 0x3f, 0x81, 0x7f, 0x81, 0x7f, 0x81, 0x7f, 0x82, 0x3f, 0x82, 0x3f,\n0x84, 0x1f, 0x8c, 0x1f, 0xb0, 0x07, 0xc0, 0x01};"
}

#***********************************************************************
# %PROCEDURE: DisplayTimeContinuously
# %ARGUMENTS:
#  None
# %RETURNS:
#  Nothing
# %DESCRIPTION:
#  Continuously displays current date and time in status window,
#  updating once a minute
#***********************************************************************
proc DisplayTimeContinuously {} {
    DisplayTime
    set secs [clock format [clock seconds] -format "%S"]
    # Doh -- don't interpret as an octal number if leading zero
    scan $secs "%d" decSecs
    set decSecs [expr 60 - $decSecs]
    after [expr $decSecs * 1000] DisplayTimeContinuously
}


#***********************************************************************
# %PROCEDURE: ShowTodaysReminders
# %ARGUMENTS:
#  None
# %RETURNS:
#  Nothing
# %DESCRIPTION:
#  Shows all of today's non-timed reminders in a window
#***********************************************************************
proc ShowTodaysReminders {} {
    global Option
    global Remind
    global ReminderFile
    if {!$Option(ShowTodaysReminders)} {
	return
    }

    set w .today
    catch { destroy $w }
    toplevel $w
    wm title $w "Today's Reminders"
    wm iconname $w "Reminders"
    text $w.text -width 80 -height 20 -wrap word -yscrollcommand "$w.sb set"
    scrollbar $w.sb -orient vertical -command "$w.text yview"
    button $w.ok -text "OK" -command "destroy $w"

    grid $w.text -row 0 -column 0 -sticky nsew
    grid $w.sb -row 0 -column 1 -sticky ns
    grid $w.ok -row 1 -column 0 -sticky w

    CenterWindow $w

    # Grab the reminders
    set stuff [exec -keepnewline $Remind -q -a -r $ReminderFile 2>/dev/null]
    $w.text insert end $stuff
    $w.text configure -state disabled
}

#***********************************************************************
# %PROCEDURE: InteractiveDeleteReminder
# %ARGUMENTS:
#  tag -- tag of reminder to delete
# %RETURNS:
#  Nothing
# %DESCRIPTION:
#  Prompts for confirmation; then deletes reminder
#***********************************************************************
proc InteractiveDeleteReminder { tag } {
    set ans [tk_dialog .error "Really Delete" "Really delete reminder?" warning 0 No Yes]
    if {$ans == 1} {
	DeleteTaggedReminder $tag
	FillCalWindow
	RestartBackgroundRemindDaemon
    }
}

main
