dotfiles/.bin/OLD/tkremind

4200 lines
130 KiB
Tcl
Executable File

#!/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-2022 Dianne Skoll
#
#--------------------------------------------------------------
# the next line restarts using wish \
exec /usr/local/bin/wish8.6 "$0" ${1+"$@"}
# We need at least version 8.5 because of {*} list expansion operator
if {[catch {package require Tcl 8.5}]} {
puts stderr "This program requires Tcl 8.5 or higher."
puts stderr "You have version [info tclversion]"
exit 1
}
wm withdraw .
set Hostname [exec hostname]
# Our icon photo
catch {
image create photo rpicon -data {
R0lGODlhFwAgAOecABUTERYTERYUERcVEhgWExkXFBkXFRoXFRsZFhwZFxwa
GB0bGR4cGR4cGh8dGiAeHCEfHCEfHSIgHSIgHiQiHyYkISknJCooJispJywq
Jy4sKTIwLjUzMDUzMTo4Njs5Nzs5ODw7ODw7OT07OT48OkE/PUJAPkNBP0RC
QEVDQUVEQkdFQ0lIRkpJR01LSU5MSlBPTVFQTlNSUFRSUFRSUVVTUlVUUllY
VltZV1xaWF1cWmBfXmJgX2RiYGZlY2dmZGppZ2tqaG1ram9tbHFwb3Jwb3Rz
cXV0c3Z0c3Z1c3Z1dHd1dHh2dXh3dnt5eHx7eXx7en18en59e4B/foGAf4KB
f4SDgYWEgoWEg4eGhIiHhouKiI2Mio6Ni46NjJCQj5KRkJSTkZeWlpiXlpmY
l5qZmJybmp6dnKCfnqGgoKKhoKOioaSjoqinp6qpqKurqq+urbCvrrCwr7Gw
r7OysbW1tLi3tri3t7u6ur28vMTDw8TEw8XFxMbFxcfGxsfHxsrJycrKyczM
y83My83MzM3NzdDQz9LR0dPS0tPT09fX19jY19ra2dvb29zc29zc3Ojn5+jo
6Orq6uzs7O/v7/T09PX19fb29vf39/r6+vv7+/7+/v//////////////////
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
/////////////////////yH5BAEKAP8ALAAAAAAXACAAAAj+AP8JHEiwoMGD
CAcusRAAQEKDBQIcEBAAwUODAQJAsBGAwsWCBzJuUBLgI0ENGVM2dACg5UWV
KU+Y/JfRQBknPoq8ATQz4wxOQIFa6vMx5ZSgQetczJDSClKgcF6mFDEnE9I2
D0fADOChUdA1D7dmTBEUTditDQRQAnomIQaxICpoAmomoUoAGS2YIBIUDEIu
YndI8FAJaBaEMlIuSEkloxugUBBOSLkh44AvGfkAPYJQpYqMLIQEILB205DO
KW9kJHMhQAmgkaKgzsgjggM5GbEAxaNmdoAPOoz8CCAgEVAtg3wPEPMnQQAU
QWsg5AAzDZSMbIBeaoHwAUwSDAI2XMAENA8ThAPEBvAStEkc3yonrOW0aUMk
+BkBVAlaKATC8Fsp8Igid5ABgxMHtaTgggy6ZFBAADs= }
wm iconphoto . -default rpicon
}
proc missing_tcllib { pkg } {
catch { puts stderr "Could not find the '$pkg' package -- you must install tcllib.\nPlease see http://tcllib.sourceforge.net/" }
tk_dialog .err "Error: tcllib not installed" "Could not find the '$pkg' package -- you must install tcllib. Please see http://tcllib.sourceforge.net/" error 0 OK
exit 1
}
if {[catch {package require mime}]} {
missing_tcllib mime
}
if {[catch {package require smtp}]} {
missing_tcllib smtp
}
if {[catch {package require json}]} {
missing_tcllib json
}
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(DayAnchor) "center"
set OptDescr(DayAnchor) "(w/center/e) Anchor the day number to the left (w), center or right (e) of its container"
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"
set Option(MailAddr) ""
set OptDescr(MailAddr) "(String) Specify an e-mail address to which reminders should be sent if the popup window is not manually dismissed"
set Option(SMTPServer) "127.0.0.1"
set OptDescr(SMTPServer) "(String) IP address or host name of SMTP server to use for sending e-mail"
set Option(ExtraRemindArgs) ""
set OptDescr(ExtraRemindArgs) "(String) Extra arguments when invoking remind"
set Option(CalboxFont) [font actual TkFixedFont]
set OptDescr(CalboxFont) "Font to use in calendar boxes in Tk font format"
set Option(HeadingFont) [font actual TkDefaultFont]
set OptDescr(HeadingFont) "Font to use in calendar headings in Tk font format"
set Option(BackgroundColor) "#d9d9d9"
set OptDescr(BackgroundColor) "Default background color of calendar boxes"
set Option(TextColor) "#000000"
set OptDescr(TextColor) "Default text color in calendar boxes"
set Option(TodayColor) "#00C0C0"
set OptDescr(TodayColor) "Background color for today heading"
set Option(LineColor) "#000000"
set OptDescr(LineColor) "Color of gridlines on calendar"
set Option(LabelColor) "#000000"
set OptDescr(LabelColor) "Default label color for headings"
set Option(WinBackground) "#d9d9d9"
set OptDescr(WinBackground) "Background color of calendar window"
set TimerUpdateForChanges ""
# Remind program to execute -- supply full path if you want
set Remind "remind"
# Rem2PS program to execute -- supply full path if you want
set Rem2PS "rem2ps"
# Rem2PDF program to execute -- supply full path if you want
set Rem2PDF "rem2pdf"
# Check if we have Rem2PDF
set HaveRem2PDF 0
set a [exec sh -c "$Rem2PDF < /dev/null 2>&1 || true"]
if {[string match "rem2pdf:*" "$a"]} {
set HaveRem2PDF 1
}
# Reminder file to source -- default
set ReminderFile {NOSUCHFILE}
set ReminderFile [file nativename "~/.reminders"]
# Default options file
set ConfigFile ""
set EditorPid -1
# Inotify file
set InotifyFP ""
# Errors from last remind run
set RemindErrors ""
# Reminder file to append to -- default
set AppendFile {NOSUCHFILE}
catch {set AppendFile $ReminderFile}
# Array of tags -> JSON dicts
array unset TagToObj
set SetFontsWorked 0
#---------------- DON'T CHANGE STUFF BELOW HERE ------------------
# 24-hour clock mode
set TwentyFourHourMode 0
# 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 name in English
set EnglishDayNames {Sunday Monday Tuesday Wednesday Thursday Friday Saturday}
# Day names in Remind's pre-selected language - will be overwritten
set DayNames $EnglishDayNames
# 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 now [clock seconds]
set TodayMonth [expr [string trim [clock format $now -format %N]] - 1]
set TodayYear [clock format $now -format %Y]
set TodayDay [string trim [clock format $now -format %e]]
set CurMonth $TodayMonth
set CurYear $TodayYear
# 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 OptDescr(PrintDest) "Print destination: file or command"
set Option(PrintDest) file
set OptDescr(PrintSize) "Page size: a4 or letter"
set Option(PrintSize) letter
set OptDescr(PrintOrient) "Page orientation: portrait or landscape"
set Option(PrintOrient) landscape
set OptDescr(PrintFill) "(0/1) If 1, fill entire page when printing"
set Option(PrintFill) 1
set OptDescr(PrintDaysRight) "(0/1) If 1, put day numbers in the top-right of each calendar box"
set Option(PrintDaysRight) 1
set OptDescr(PrintEncoding) "(0/1) If 1, apply ISO-8859-1 encoding to PostScript output"
set Option(PrintEncoding) 0
set OptDescr(PrintMargins) "Print margins: One of 24pt, 36pt or 48pt"
set Option(PrintMargins) 36pt
set OptDescr(PrintSmallCalendars) "(0/1) If 1, print small calendars in PostScript output"
set Option(PrintSmallCalendars) 1
set OptDescr(PrintFormat) "Print format: pdf or ps"
set Option(PrintFormat) ps
set WarningHeaders [list "# Lines staring with REM TAG TKTAGnnn ... were created by tkremind" "# Do not edit them by hand or results may be unpredictable."]
# Highest tag seen so far. Array of tags is stored in ReminderTags()
set HighestTagSoFar 0
proc get_weekday { yyyymmdd } {
global EnglishDayNames
return [lindex $EnglishDayNames [clock format [clock scan $yyyymmdd] -format %w -locale C]]
}
proc write_warning_headers { out } {
global WarningHeaders
foreach h $WarningHeaders {
puts $out $h
}
}
proc is_warning_header { line } {
global WarningHeaders
foreach h $WarningHeaders {
if {"$line" == "$h"} {
return 1
}
}
return 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 TwentyFourHourMode ReminderFileModTime
global TodayDay TodayMonth TodayYear
global Option ConfigFile
# In case date has rolled over, recalculate Today* values
set now [clock seconds]
set TodayMonth [expr [string trim [clock format $now -format %N]] - 1]
set TodayYear [clock format $now -format %Y]
set TodayDay [string trim [clock format $now -format %e]]
set CommandLine "|$Remind -itkremind=1 -pp -y -l EXTRA"
set PSCmd "$Remind -itkremind=1 -itkprint=1 -pp -l EXTRA"
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
}
if {"[lindex $argv $i]" == "-b1"} {
set TwentyFourHourMode 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
if {$i < $argc} {
set ConfigFile [lindex $argv $i]
incr i
}
}
}
# If reminder file is a directory and appendfile is the same as
# reminder file, choose append file to be $ReminderFile/100-tkremind.rem
if {[file isdirectory $ReminderFile]} {
if {"$ReminderFile" == "$AppendFile"} {
set AppendFile [file join $ReminderFile "100-tkremind.rem"]
}
}
# 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 {
set out [open $ReminderFile w]
write_warning_headers $out
puts $out ""
close $out
}
}
if {! [file readable $ReminderFile]} {
tk_dialog .error "TkRemind: Error" "Could not create reminder file `$ReminderFile'" error 0 "Exit"
exit 1
}
if {[file isdirectory $ReminderFile] && ! [file exists $AppendFile]} {
if {![catch {
set out [open $AppendFile "a"]
write_warning_headers $out
puts $out ""
close $out}]} {
tk_dialog .error "Created File" "Created blank file `$AppendFile'" info 0 "OK"
}
}
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"
# Get modification time of ReminderFile
set ReminderFileModTime -1
catch {
set ReminderFileModTime [file mtime $ReminderFile]
}
MonitorReminderFile
# puts "CommandLine: $CommandLine"
}
#---------------------------------------------------------------------------
# MonitorReminderFile
# If Reminder File modtime changes, restart daemon
#---------------------------------------------------------------------------
proc MonitorReminderFile {} {
global ReminderFileModTime ReminderFile
if {$ReminderFileModTime < 0} {
# Could not stat file
return
}
set mtime -1
catch {
set mtime [file mtime $ReminderFile]
}
if {$mtime < 0} {
# Doh!
return
}
# Run ourselves again
after 60000 MonitorReminderFile
# Redraw calendar and restart daemon if needed
if {$ReminderFileModTime < $mtime} {
set ReminderFileModTime $mtime
ScheduleUpdateForChanges
}
}
#***********************************************************************
# %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
global SetFontsWorked
global Option
global MondayFirst
set h [winfo screenheight .]
if {$h <= 480} {
if {$SetFontsWorked} {
set h 3
} else {
set h 2
}
} elseif {$h <= 600} {
set h 4
} else {
set h 5
}
frame $w -background $Option(LineColor)
for {set i 0} {$i < 7} {incr i} {
if {$MondayFirst} {
set index [expr ($i+1)%7]
} else {
set index $i
}
label $w.day$i -bd 0 -text [lindex $dayNames $index] -justify center -font HeadingFont -foreground $Option(LabelColor) -background $Option(WinBackground) -highlightthickness 0
grid configure $w.day$i -row 0 -column $i -sticky ew -padx 1 -pady 1
}
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 "" -anchor $Option(DayAnchor) \
-state disabled -relief flat -bd 0 -padx 0 -pady 0 -font HeadingFont -highlightthickness 1
text $w.t$f -width 12 -height $h -bd 0 -spacing3 3 -wrap word -relief flat \
-state disabled -takefocus 0 -cursor {} -font CalboxFont -foreground $Option(TextColor) -background $Option(BackgroundColor) \
-highlightthickness 0
frame $w.f$f -padx 0 -pady 0 -highlightthickness 0 -relief flat -bd 0
$w.t$f tag bind TAGGED <ButtonPress-1> "EditTaggedReminder $w.t$f"
$w.t$f tag bind REM <ButtonPress-3> "FireEditor $w.t$f"
pack $w.l$f -in $w.f$f -side top -expand 0 -fill x
pack $w.t$f -in $w.f$f -side top -expand 1 -fill both
grid configure $w.f$f -row [expr $i+1] -column $j -sticky nsew -padx 1 -pady 1
}
}
for {set i 0} {$i < 7} {incr i} {
grid columnconfigure $w $i -weight 1
}
for {set i 1} {$i < 7} {incr i} {
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
global tk_version Option
CreateMoonWindows
set offset [CalEntryOffset $firstDay]
set first [expr $offset+1]
set last [expr $offset+$numDays]
for {set i 0} {$i < $first} {incr i} {
grid $w.f$i
pack $w.l$i -in $w.f$i -side top -expand 0 -fill x
pack $w.t$i -in $w.f$i -side top -expand 1 -fill both
raise $w.l$i
raise $w.t$i
$w.l$i configure -text "" -command "" -state normal -relief flat -foreground $Option(LabelColor) -background $Option(WinBackground) -highlightcolor $Option(LineColor) -highlightbackground $Option(WinBackground)
$w.l$i configure -state disabled
balloon_add_help $w.l$i ""
$w.t$i configure -relief flat -takefocus 0 -state normal -background $Option(WinBackground)
$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 <ButtonPress-1> "EditTaggedReminder $w.t$i"
$w.t$i tag bind REM <ButtonPress-3> "FireEditor $w.t$i"
$w.t$i configure -state disabled -takefocus 0
}
for {set i $first} {$i <= $last} {incr i} {
set row [expr ($i/7)+1]
grid $w.f$i
grid rowconfigure $w $row -weight 1
pack $w.l$i -in $w.f$i -side top -expand 0 -fill x
pack $w.t$i -in $w.f$i -side top -expand 1 -fill both
raise $w.l$i
raise $w.t$i
set d [expr $i-$first+1]
$w.l$i configure -text $d -state normal -relief flat \
-command "ModifyDay $d $firstDay" -foreground $Option(LabelColor) -background $Option(WinBackground) -highlightcolor $Option(LineColor) -highlightbackground $Option(WinBackground)
balloon_add_help $w.l$i "Add a reminder..."
$w.t$i configure -relief sunken -takefocus 1 -state normal -foreground $Option(TextColor) -background $Option(BackgroundColor)
$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 <ButtonPress-1> "EditTaggedReminder $w.t$i"
$w.t$i tag bind REM <ButtonPress-3> "FireEditor $w.t$i"
$w.t$i configure -state disabled -takefocus 0
}
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)+1]
if {$forgetIt} {
grid remove $w.f$i
grid rowconfigure $w $row -weight 0
grid rowconfigure $w [expr $row+1] -weight 0
} else {
grid $w.f$i
pack $w.l$i -in $w.f$i -side top -expand 0 -fill x
pack $w.t$i -in $w.f$i -side top -expand 1 -fill both
raise $w.l$i
raise $w.t$i
grid rowconfigure $w $row -weight 1
}
$w.l$i configure -text "" -command "" -state normal -relief flat -foreground $Option(LabelColor) -background $Option(WinBackground) -highlightcolor $Option(LineColor) -highlightbackground $Option(WinBackground)
$w.l$i configure -state disabled
balloon_add_help $w.l$i ""
$w.t$i configure -relief flat -takefocus 0 -state normal -background $Option(WinBackground)
$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 <ButtonPress-1> "EditTaggedReminder $w.t$i"
$w.t$i tag bind REM <ButtonPress-3> "FireEditor $w.t$i"
$w.t$i configure -state disabled -takefocus 0
}
if { $CurMonth == $TodayMonth && $CurYear == $TodayYear } {
set n [expr $TodayDay + $offset]
$w.l$n configure -background $Option(TodayColor)
}
}
proc DoQueue {} {
global DaemonFile
puts $DaemonFile "JSONQUEUE"
flush $DaemonFile
}
#---------------------------------------------------------------------------
# CreateCalWindow -- create the calendar window.
# Arguments:
# dayNames -- names of weekdays in current language {Sun .. Sat}
#---------------------------------------------------------------------------
proc CreateCalWindow { dayNames } {
global Option
frame .h -background $Option(LineColor)
label .h.title -text "" -justify center -pady 2 -bd 0 -relief flat -font HeadingFont -background $Option(WinBackground) -foreground $Option(LabelColor)
pack .h.title -side top -fill x -pady 1 -padx 1
pack .h -side top -expand 0 -fill x
. configure -background $Option(LineColor)
CreateCalFrame .cal $dayNames
frame .b -background $Option(LineColor)
button .b.prev -text "\u2b9c" -command {MoveMonth -1} -bd 0 -foreground $Option(LabelColor) -background $Option(WinBackground) -highlightthickness 1 -highlightcolor $Option(LineColor) -highlightbackground $Option(WinBackground)
balloon_add_help .b.prev "Go to previous month"
button .b.this -text {Today} -command {ThisMonth} -bd 0 -foreground $Option(LabelColor) -background $Option(WinBackground) -highlightthickness 1 -highlightcolor $Option(LineColor) -highlightbackground $Option(WinBackground)
balloon_add_help .b.this "Go to this month"
button .b.next -text "\u2b9e" -command {MoveMonth 1} -bd 0 -foreground $Option(LabelColor) -background $Option(WinBackground) -highlightthickness 1 -highlightcolor $Option(LineColor) -highlightbackground $Option(WinBackground)
balloon_add_help .b.next "Go to next month"
button .b.goto -text {Go To Date...} -command {GotoDialog} -bd 0 -foreground $Option(LabelColor) -background $Option(WinBackground) -highlightthickness 1 -highlightcolor $Option(LineColor) -highlightbackground $Option(WinBackground)
balloon_add_help .b.goto "Go to a specific date"
button .b.print -text {Print...} -command {DoPrint} -bd 0 -foreground $Option(LabelColor) -background $Option(WinBackground) -highlightthickness 1 -highlightcolor $Option(LineColor) -highlightbackground $Option(WinBackground)
balloon_add_help .b.print "Print a PostScript or PDF calendar"
button .b.options -text {Options...} -command EditOptions -bd 0 -foreground $Option(LabelColor) -background $Option(WinBackground) -highlightthickness 1 -highlightcolor $Option(LineColor) -highlightbackground $Option(WinBackground)
balloon_add_help .b.options "Set TkRemind options"
button .b.queue -text {Queue...} -command {DoQueue} -bd 0 -foreground $Option(LabelColor) -background $Option(WinBackground) -highlightthickness 1 -highlightcolor $Option(LineColor) -highlightbackground $Option(WinBackground)
balloon_add_help .b.queue "See the queue of pending reminders (debugging purposes only)"
button .b.quit -text {Quit} -command {Quit} -bd 0 -foreground $Option(LabelColor) -background $Option(WinBackground) -highlightthickness 1 -highlightcolor $Option(LineColor) -highlightbackground $Option(WinBackground)
balloon_add_help .b.quit "Quit TkRemind"
label .b.status -text "" -width 25 -relief flat -bd 0 -foreground $Option(LabelColor) -background $Option(WinBackground) -highlightthickness 0
label .b.nqueued -text "" -width 20 -relief flat -bd 0 -foreground $Option(LabelColor) -background $Option(WinBackground) -highlightthickness 0
pack .b.prev .b.this .b.next .b.goto .b.print .b.options .b.queue .b.quit -side left -fill both -padx 1
pack .b.status -side left -fill both -expand 1 -padx 1
pack .b.nqueued -side left -fill both -padx 1
pack .b -side bottom -fill x -expand 0 -pady 1
pack .cal -side top -fill both -expand 1
wm title . "TkRemind"
wm iconname . ""
wm protocol . WM_DELETE_WINDOW Quit
wm deiconify .
bind . <Control-KeyPress-q> Quit
bind . <KeyPress-Left> ".b.prev flash; .b.prev invoke"
bind . <KeyPress-Right> ".b.next flash; .b.next invoke"
bind . <KeyPress-Prior> ".b.prev flash; .b.prev invoke"
bind . <KeyPress-Next> ".b.next flash; .b.next invoke"
bind . <KeyPress-Home> ".b.this flash; .b.this invoke"
. configure -background $Option(WinBackground)
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)
frame $w.sep3 -bd 1 -relief sunken
# E-mail reminder if popup not dismissed
frame $w.eml
label $w.lab_email_address -text "E-mail reminders here if popup not dismissed:" -anchor w -justify left
entry $w.email_address -width 30
pack $w.lab_email_address -in $w.eml -side left -expand 0 -fill none
pack $w.email_address -in $w.eml -side left -expand 1 -fill x
$w.email_address insert 0 $tmpOpt(MailAddr)
frame $w.fsmtp
label $w.lab_smtp -text "Name or IP address of SMTP server:" -anchor w -justify left
entry $w.smtp -width 30
pack $w.lab_smtp -in $w.fsmtp -side left -expand 0 -fill none
pack $w.smtp -in $w.fsmtp -side left -expand 1 -fill x
$w.smtp insert 0 $tmpOpt(SMTPServer)
# 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)
# extra args
frame $w.eaf
label $w.eal -text "Extra Arguments for Remind:" -anchor w -justify left
entry $w.extraargs -width 30
pack $w.eal -in $w.eaf -side left -expand 0 -fill none
pack $w.extraargs -in $w.eaf -side left -expand 1 -fill x
$w.extraargs insert 0 $tmpOpt(ExtraRemindArgs)
# Fonts
frame $w.fframe
button $w.font -text "Change entry font..." -command "ChooseCalboxFont"
button $w.hfont -text "Change heading font..." -command "ChooseHeadingFont"
pack $w.font $w.hfont -in $w.fframe -side left -expand 1 -fill x
# Colors
frame $w.colors1
label $w.textcolor -text "Text Color:"
button $w.btextcolor -background $Option(TextColor) -command [list PickColor TextColor $w.btextcolor] -text ...
label $w.bgcolor -text " Background color:"
button $w.bbgcolor -background $Option(BackgroundColor) -command [list PickColor BackgroundColor $w.bbgcolor] -text ...
label $w.tbgcolor -text "Color for highlighting \"today\":"
button $w.tbbgcolor -background $Option(TodayColor) -command [list PickColor TodayColor $w.tbbgcolor] -text ...
label $w.gridcolor -text " Gridline color:"
button $w.gridbcolor -background $Option(LineColor) -command [list PickColor LineColor $w.gridbcolor] -text ...
grid $w.textcolor $w.btextcolor $w.bgcolor $w.bbgcolor -in $w.colors1
grid $w.bgcolor $w.bbgcolor -in $w.colors1
label $w.headcolor -text "Heading Color:"
button $w.bheadcolor -background $Option(LabelColor) -command [list PickColor LabelColor $w.bheadcolor] -text ...
label $w.wincolor -text " Window color:"
button $w.bwincolor -background $Option(WinBackground) -command [list PickColor WinBackground $w.bwincolor] -text ...
grid $w.headcolor $w.bheadcolor $w.wincolor $w.bwincolor -in $w.colors1
grid $w.tbgcolor $w.tbbgcolor $w.gridcolor $w.gridbcolor -in $w.colors1
grid columnconfigure $w.colors1 0 -weight 1
grid columnconfigure $w.colors1 2 -weight 1
frame $w.sep1 -bd 1 -relief sunken
frame $w.sep2 -bd 1 -relief sunken
checkbutton $w.feed \
-text "Feed popped-up reminder to command's standard input" \
-variable tmpOpt(FeedReminder) -anchor w -justify left
frame $w.ancFrame
label $w.ancLabel -text "Anchor day numbers to:"
radiobutton $w.ancLeft \
-text "Left" \
-variable tmpOpt(DayAnchor) -value "w" -anchor w -justify left
radiobutton $w.ancCenter \
-text "Center" \
-variable tmpOpt(DayAnchor) -value "center" -anchor w -justify left
radiobutton $w.ancRight \
-text "Right" \
-variable tmpOpt(DayAnchor) -value "e" -anchor w -justify left
pack $w.ancLabel $w.ancLeft $w.ancCenter $w.ancRight -in $w.ancFrame -side 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.ancFrame -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.sep3 -in $w.f -side top -expand 0 -fill x -ipady 1
pack $w.eml -in $w.f -side top -expand 0 -fill x
pack $w.fsmtp -in $w.f -side top -expand 0 -fill x
pack $w.ef -in $w.f -side top -expand 0 -fill x
pack $w.eaf -in $w.f -side top -expand 0 -fill x
pack $w.fframe -in $w.f -side top -expand 0 -fill x
pack $w.colors1 -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.default -text "Light Theme" -command [list set_default_colors $w]
button $w.dark -text "Dark Theme" -command [list set_dark_colors $w]
button $w.save -text "Save Options" -command "SaveOptions $w; destroy $w"
button $w.cancel -text "Cancel" -command "CancelOptions; destroy $w"
wm protocol $w WM_DELETE_WINDOW "CancelOptions; destroy $w"
pack $w.default $w.dark $w.save $w.cancel -in $w.b -side left -expand 0 -fill x
CenterWindow $w .
}
proc CancelOptions { } {
global Option
font configure CalboxFont {*}$Option(CalboxFont)
font configure HeadingFont {*}$Option(HeadingFont)
}
#***********************************************************************
# %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]
set tmpOpt(ExtraRemindArgs) [$w.extraargs get]
set tmpOpt(MailAddr) [$w.email_address get]
set tmpOpt(SMTPServer) [$w.smtp get]
set need_restart 0
if {"$tmpOpt(ExtraRemindArgs)" != "$Option(ExtraRemindArgs)"} {
set need_restart 1
}
# Copy working copy to real option set
foreach name [array names tmpOpt] {
set Option($name) $tmpOpt($name)
}
if {$need_restart != 0} {
FillCalWindow
StopBackgroundRemindDaemon
StartBackgroundRemindDaemon
}
}
#***********************************************************************
# %PROCEDURE: SaveOptions
# %ARGUMENTS:
# w -- edit options window path
# %RETURNS:
# Nothing
# %DESCRIPTION:
# Saves options in specified config file
#***********************************************************************
proc SaveOptions { w } {
global Option OptDescr
ApplyOptions $w
WriteOptionsToFile
FillCalWindow
.h.title configure -background $Option(WinBackground) -foreground $Option(LabelColor)
for {set i 0} {$i < 7} {incr i} {
.cal.day$i configure -foreground $Option(LabelColor) -background $Option(WinBackground)
}
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]
.cal.l$f configure -anchor $Option(DayAnchor);
}
}
.b.status configure -foreground $Option(LabelColor) -background $Option(WinBackground)
.b.nqueued configure -foreground $Option(LabelColor) -background $Option(WinBackground)
.b configure -background $Option(WinBackground)
.b.prev configure -foreground $Option(LabelColor) -background $Option(WinBackground)
.b.this configure -foreground $Option(LabelColor) -background $Option(WinBackground)
.b.next configure -foreground $Option(LabelColor) -background $Option(WinBackground)
.b.goto configure -foreground $Option(LabelColor) -background $Option(WinBackground)
.b.print configure -foreground $Option(LabelColor) -background $Option(WinBackground)
.b.queue configure -foreground $Option(LabelColor) -background $Option(WinBackground)
.b.quit configure -foreground $Option(LabelColor) -background $Option(WinBackground)
.b.options configure -foreground $Option(LabelColor) -background $Option(WinBackground)
. configure -background $Option(LineColor);
.h configure -background $Option(LineColor);
.cal configure -background $Option(LineColor)
.b configure -background $Option(LineColor)
}
proc WriteOptionsToFile {} {
global ConfigFile
global Option OptDescr
set problem [catch {set f [open "$ConfigFile.tmp" "w"]} err]
if {$problem} {
tk_dialog .error Error "Can't write $ConfigFile.tmp: $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
file rename -force "$ConfigFile.tmp" $ConfigFile
}
#***********************************************************************
# %PROCEDURE: LoadOptions
# %ARGUMENTS:
# None
# %RETURNS:
# Nothing
# %DESCRIPTION:
# Loads options from $ConfigFile
#***********************************************************************
proc LoadOptions {} {
global Option ConfigFile
global MondayFirst
set problem [catch {set f [open "$ConfigFile" "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 stderr "Unknown option in $ConfigFile: $key"
continue
}
set Option($key) $val
}
close $f
if {[regexp -- {-m.*} $Option(ExtraRemindArgs)]} {
set MondayFirst 1
}
font configure CalboxFont {*}$Option(CalboxFont)
font configure HeadingFont {*}$Option(HeadingFont)
}
#***********************************************************************
# %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 } {
global Hostname
.h.title configure -text "$month $year"
wm title . "$month $year - TkRemind on $Hostname"
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 Option TagToObj RemindErrors MondayFirst
array unset TagToObj
Status "Firing off Remind..."
set_button_to_queue
set month [lindex $MonthNames $CurMonth]
set cmd [regsub EXTRA $CommandLine $Option(ExtraRemindArgs)]
set file [open "$cmd $month $CurYear" r]
# Look for # rem2ps2 begin line
while { [gets $file line] >= 0 } {
if { [string compare "$line" "# rem2ps2 begin"] == 0 } { break }
}
if { [string compare "$line" "# rem2ps2 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
set monthName [regsub -all {_} $monthName " "]
# Get the day names
gets $file line
set DayNames {}
foreach day $line {
set day [regsub -all {_} $day " "];
lappend DayNames $day
}
ConfigureCalWindow $monthName $year $firstWkday $daysInMonth
# Update the day names in the calendar window
for {set i 0} {$i < 7} {incr i} {
if {$MondayFirst} {
set index [expr ($i+1)%7]
} else {
set index $i
}
.cal.day$i configure -text [lindex $DayNames $index]
}
set offset [CalEntryOffset $firstWkday]
while { [gets $file line] >= 0 } {
set fntag "x"
# Ignore unless begins with left brace
if { ! [string match "\{*" $line]} {
continue
}
if {[catch {set obj [::json::json2dict $line]}]} {
continue
}
if {[dict exists $obj filename]} {
set fname [dict get $obj filename]
# Don't make INCLUDECMD output editable
if {![string match "*|" $fname]} {
set fntag [string cat "FILE_" [dict get $obj lineno] "_" $fname]
}
}
set date [dict get $obj date]
regexp {^([0-9][0-9][0-9][0-9]).([0-9][0-9]).([0-9][0-9])} $date all year month day
if {[dict exists $obj passthru]} {
set type [dict get $obj passthru]
} else {
set type "*"
}
if {[dict exist $obj tags]} {
set tag [dict get $obj tags]
} else {
set tag "*"
}
if {[dict exists $obj calendar_body]} {
set stuff [dict get $obj calendar_body]
} elseif {[dict exists $obj plain_body]} {
set stuff [dict get $obj plain_body]
} else {
set stuff [dict get $obj body]
}
set day [string trimleft $day 0]
set n [expr $day+$offset]
set month [string trimleft $month 0]
set extratags ""
switch -nocase -- $type {
"WEEK" {
set stuff [string trimleft $stuff]
set stuff [string trimright $stuff]
set offset [CalEntryOffset $firstWkday]
set label [expr $offset + $day]
.cal.l$label configure -text "$day $stuff"
continue
}
"SHADE" {
DoShadeSpecial $n [dict get $obj r] [dict get $obj g] [dict get $obj b]
continue
}
"MOON" {
DoMoonSpecial $n $stuff $fntag $day
continue
}
"COLOUR" -
"COLOR" {
set r [dict get $obj r]
set g [dict get $obj g]
set b [dict get $obj b]
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 -takefocus 0
set stuff $stuff
set type "COLOR"
}
}
if { $type != "*" && $type != "COLOR" && $type != "COLOUR"} {
continue
}
.cal.t$n configure -state normal
if {[regexp {TKTAG([0-9]+)} $tag all tagno] && "$fntag" != "x"} {
.cal.t$n insert end [string trim $stuff] [list REM TAGGED "TKTAG$tagno" "date_$date" $extratags $fntag]
.cal.t$n tag bind "TKTAG$tagno" <Enter> "TaggedEnter .cal.t$n"
.cal.t$n tag bind "TKTAG$tagno" <Leave> "TaggedLeave .cal.t$n"
set TagToObj(TKTAG$tagno) $obj
} else {
if {"$fntag" == "x" } {
.cal.t$n insert end [string trim $stuff] [list REM $extratags]
} else {
.cal.t$n insert end [string trim $stuff] [list REM $extratags $fntag]
.cal.t$n tag bind $fntag <Enter> "EditableEnter .cal.t$n"
.cal.t$n tag bind $fntag <Leave> "EditableLeave .cal.t$n"
.cal.t$n tag bind $fntag <ButtonPress-1> "FireEditor .cal.t$n"
}
}
.cal.t$n insert end "\n"
.cal.t$n configure -state disabled -takefocus 0
}
set problem [catch { close $file } errmsg]
if {$problem} {
set RemindErrors [unique_lines $errmsg]
set_button_to_errors
}
DisplayTime
}
proc unique_lines { s } {
set l [split $s "\n"]
foreach line $l {
if {"$line" != ""} {
dict set d $line 1
}
}
return [join [dict keys $d] "\n"]
}
#---------------------------------------------------------------------------
# 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 Rem2PS Rem2PDF HaveRem2PDF PSCmd Option PrintStatus
global CurMonth CurYear MonthNames
catch {destroy .p}
toplevel .p
wm title .p "TkRemind Print..."
wm iconname .p "Print..."
frame .p.f1 -relief sunken -bd 2
frame .p.f11
frame .p.f12
frame .p.f2 -relief sunken -bd 2
frame .p.f2a -relief sunken -bd 2
frame .p.f3 -relief sunken -bd 2
frame .p.f3a -relief sunken -bd 2
frame .p.f4
radiobutton .p.tofile -text "To file: " -variable Option(PrintDest) -value file
entry .p.filename
button .p.browse -text "Browse..." -command PrintFileBrowse
radiobutton .p.tocmd -text "To command: " -variable Option(PrintDest) -value command
entry .p.command
.p.command insert end "lpr"
if { $HaveRem2PDF } {
frame .p.ff -relief sunken -bd 2
label .p.format -text "Output Format:"
radiobutton .p.pdf -text "PDF" -variable Option(PrintFormat) -value pdf
radiobutton .p.ps -text "PostScript" -variable Option(PrintFormat) -value ps
}
label .p.size -text "Paper Size:"
radiobutton .p.letter -text "Letter" -variable Option(PrintSize) -value letter
radiobutton .p.a4 -text "A4" -variable Option(PrintSize) -value a4
label .p.margin -text "Margins:"
radiobutton .p.24pt -text "24pt margins" -variable Option(PrintMargins) -value 24pt
radiobutton .p.36pt -text "36pt margins" -variable Option(PrintMargins) -value 36pt
radiobutton .p.48pt -text "48pt margins" -variable Option(PrintMargins) -value 48pt
label .p.orient -text "Orientation:"
radiobutton .p.landscape -text "Landscape" -variable Option(PrintOrient) -value landscape
radiobutton .p.portrait -text "Portrait" -variable Option(PrintOrient) -value portrait
checkbutton .p.fill -text "Fill page" -variable Option(PrintFill)
checkbutton .p.right -text "Day numbers at top-right" -variable Option(PrintDaysRight)
checkbutton .p.encoding -text "ISO 8859-1 PostScript encoding" -variable Option(PrintEncoding)
checkbutton .p.calendars -text "Print small calendars" -variable Option(PrintSmallCalendars)
button .p.print -text "Print" -command {set PrintStatus print}
button .p.cancel -text "Cancel" -command {set PrintStatus cancel}
if {$HaveRem2PDF} {
pack .p.f1 .p.ff .p.f2 .p.f2a .p.f3 .p.f3a \
-side top -fill both -expand 1 -anchor w
} else {
pack .p.f1 .p.f2 .p.f2a .p.f3 .p.f3a \
-side top -fill both -expand 1 -anchor w
}
pack .p.fill .p.right .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
if { $HaveRem2PDF } {
pack .p.format .p.pdf .p.ps -in .p.ff -side top -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
}
WriteOptionsToFile
if {$Option(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"
}
if {$HaveRem2PDF && $Option(PrintFormat) == "pdf"} {
set p [regsub EXTRA $PSCmd "-itkpdf=1 $Option(ExtraRemindArgs)"]
set cmd "$p 1 [lindex $MonthNames $CurMonth] $CurYear | $Rem2PDF"
} else {
set p [regsub EXTRA $PSCmd $Option(ExtraRemindArgs)]
set cmd "$p 1 [lindex $MonthNames $CurMonth] $CurYear | $Rem2PS"
set Option(PrintFormat) ps
}
if {$Option(PrintSize) == "letter"} {
if {$Option(PrintFormat) == "ps"} {
append cmd " -m Letter"
} else {
append cmd " --media=Letter"
}
} else {
if {$Option(PrintFormat) == "ps"} {
append cmd " -m A4"
} else {
append cmd " --media=A4"
}
}
if {$Option(PrintMargins) == "24pt"} {
if {$Option(PrintFormat) == "ps"} {
append cmd " -or 24 -ol 24 -ot 24 -ob 24"
} else {
append cmd " --margin-right=24 --margin-left=24 --margin-top=24 --margin-bottom=24"
}
} elseif {$Option(PrintMargins) == "36pt"} {
if {$Option(PrintFormat) == "ps"} {
append cmd " -or 36 -ol 36 -ot 36 -ob 36"
} else {
append cmd " --margin-right=36 --margin-left=36 --margin-top=36 --margin-bottom=36"
}
} else {
if {$Option(PrintFormat) == "ps"} {
append cmd " -or 48 -ol 48 -ot 48 -ob 48"
} else {
append cmd " --margin-right=48 --margin-left=48 --margin-top=48 --margin-bottom=48"
}
}
if {$Option(PrintOrient) == "landscape"} {
append cmd " -l"
}
if {$Option(PrintFill)} {
append cmd " -e"
}
if {!$Option(PrintDaysRight)} {
append cmd " -x"
}
if {$Option(PrintEncoding)} {
if {$Option(PrintFormat) == "ps"} {
append cmd " -i"
}
}
if {$Option(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
balloon_add_help .g.mon "Select a month"
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
balloon_add_help .g.y.e "Enter a year"
.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}
balloon_add_help .g.b.go "Go to specified month and year"
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-5990)} error 0 Ok
return
}
if { $year < 1990 || $year > 5990 } {
tk_dialog .error Error {Illegal year specified (1990-5990)} 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
global InotifyFP
if { !$Option(ConfirmQuit) } {
destroy .
StopBackgroundRemindDaemon
catch { exec kill [pid $InotifyFP] }
catch { close $InotifyFP }
exit 0
}
if { [tk_dialog .question "Confirm..." {Really quit?} question 0 No Yes] } {
destroy .
StopBackgroundRemindDaemon
catch { exec kill [pid $InotifyFP] }
catch { close $InotifyFP }
exit 0
}
}
#---------------------------------------------------------------------------
# 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 TwentyFourHourMode
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 -bd 4 -relief ridge
frame $w.o1 -bd 4
frame $w.o2 -bd 4
frame $w.o3 -bd 4
frame $w.exp -bd 4
frame $w.adv -bd 4
frame $w.weekend -bd 4
frame $w.durationbox -bd 4
frame $w.time -bd 4
frame $w.hol -bd 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.durationbox $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
balloon_add_help $w.day1 "Select a day"
CreateDayMenu $w.day1
menubutton $w.mon1 -text $month -relief raised -menu $w.mon1.menu
balloon_add_help $w.mon1 "Select a month"
CreateMonthMenu $w.mon1
menubutton $w.year1 -text $year -relief raised -menu $w.year1.menu
balloon_add_help $w.year1 "Select a year"
CreateYearMenu $w.year1
checkbutton $w.repbut -text "and repeating every"
balloon_add_help $w.repbut "Select to enable a recurring reminder"
$w.repbut deselect
menubutton $w.repdays -text 1 -relief raised -menu $w.repdays.menu
balloon_add_help $w.repdays "Select the repeat interval in days"
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
balloon_add_help $w.wkday2 "Select a day of the week"
CreateWeekdayMenu $w.wkday2
label $w.label2b -text "on or after"
menubutton $w.day2 -text $day -relief raised -menu $w.day2.menu
balloon_add_help $w.day2 "Select a day"
CreateDayMenu $w.day2 1 31 0
menubutton $w.mon2 -text $month -relief raised -menu $w.mon2.menu
balloon_add_help $w.mon2 "Select a month"
CreateMonthMenu $w.mon2
menubutton $w.year2 -text $year -relief raised -menu $w.year2.menu
balloon_add_help $w.year2 "Select a year"
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
balloon_add_help $w.ordinal "Select the first, second, etc. weekday in a month"
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
balloon_add_help $w.wkday3 "Select a day of the week"
CreateWeekdayMenu $w.wkday3
label $w.label3 -text "in"
menubutton $w.mon3 -text $month -relief raised -menu $w.mon3.menu
balloon_add_help $w.mon3 "Select a month"
CreateMonthMenu $w.mon3
menubutton $w.year3 -text $year -relief raised -menu $w.year3.menu
balloon_add_help $w.year3 "Select a year"
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"
balloon_add_help $w.expbut "Select to enable an expiry date"
$w.expbut deselect
menubutton $w.expday -text $day -relief raised -menu $w.expday.menu
balloon_add_help $w.expday "Select expiry day"
CreateDayMenu $w.expday 1 31 0
menubutton $w.expmon -text $month -relief raised -menu $w.expmon.menu
balloon_add_help $w.expmon "Select expiry month"
CreateMonthMenu $w.expmon 0
menubutton $w.expyear -text $year -relief raised -menu $w.expyear.menu
balloon_add_help $w.expyear "Select expiry year"
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"
balloon_add_help $w.advbut "Select to enable advance notification"
$w.advbut deselect
menubutton $w.advdays -text 3 -menu $w.advdays.menu -relief raised
balloon_add_help $w.advdays "Select number of days of advance warning"
CreateDayMenu $w.advdays 1 10 0
label $w.advlab -text "day(s) in advance"
checkbutton $w.advcount -text "not counting holidays/weekend"
balloon_add_help $w.advcount "Select to avoid counting holidays/weekend as in advance warning days"
$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
balloon_add_help $w.d$d "Select to include $d in the definition of \"Weekend\""
$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"
balloon_add_help $w.timebut "Select if this event starts at a specific time"
$w.timebut deselect
menubutton $w.timehour -text "12" -menu $w.timehour.menu -relief raised
balloon_add_help $w.timehour "Select the starting time's hour"
if {$TwentyFourHourMode} {
CreateDayMenu $w.timehour 0 23 0
} else {
CreateDayMenu $w.timehour 1 12 0
}
menubutton $w.timemin -text "00" -menu $w.timemin.menu -relief raised
balloon_add_help $w.timemin "Select the starting time's minute"
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"
}
if {!$TwentyFourHourMode} {
menubutton $w.ampm -text "PM" -menu $w.ampm.menu -relief raised
balloon_add_help $w.ampm "Select whether the time is AM or PM"
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"
balloon_add_help $w.timeadvbut "Select to be given advance warning prior to the start time"
$w.timeadvbut deselect
menubutton $w.timeadv -text "15" -menu $w.timeadv.menu -relief raised
balloon_add_help $w.timeadv "Select the number of minutes of advance warning"
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"
balloon_add_help $w.timerepbut "Select to repeat the advance notice"
$w.timerepbut deselect
menubutton $w.timerep -text "5" -menu $w.timerep.menu -relief raised
balloon_add_help $w.timerep "Select how often to repeat the advance notice"
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"
if {$TwentyFourHourMode} {
pack $w.timebut $w.timehour $w.timemin $w.timeadvbut $w.timeadv $w.timelab1 $w.timerepbut $w.timerep $w.timelab2 -side left -anchor w -in $w.time
} else {
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
}
# DURATION
checkbutton $w.durationbut -text "Duration"
balloon_add_help $w.durationbut "Select if this event has a specific duration"
$w.durationbut deselect
menubutton $w.durationh -text "1" -menu $w.durationh.menu -relief raised
balloon_add_help $w.durationh "Select how many hours the event lasts"
menu $w.durationh.menu -tearoff 0
foreach i {0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24} {
$w.durationh.menu add command -label $i -command "$w.durationh configure -text $i"
}
label $w.durationcolon -text ":"
menubutton $w.durationm -text "00" -menu $w.durationm.menu -relief raised
balloon_add_help $w.durationm "Select how many minutes the event lasts (in addition to the hours)"
menu $w.durationm.menu -tearoff 0
foreach i {00 15 30 45} {
$w.durationm.menu add command -label $i -command "$w.durationm configure -text $i"
}
pack $w.durationbut $w.durationh $w.durationcolon $w.durationm -side left -anchor w -in $w.durationbox
# 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
balloon_add_help $w.entry "Enter the text of the reminder"
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 flash; $w.but1 invoke"
if {$nbut >= 2} {
bind $w.entry <KeyPress-Return> "$w.but2 flash; $w.but2 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 durationbut
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" [string map -nocase {"\n" " "} [$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 TwentyFourHourMode durationbut
global dSaturday dSunday dMonday dTuesday dWednesday dThursday dFriday
set hour ""
set ampm ""
foreach {flag value} $opts {
switch -glob -- $flag {
"-text-*" {
set win [string range $flag 6 end]
catch { $w.$win configure -text $value }
if {"$flag" == "-text-ampm"} {
set ampm $value
} elseif {"$flag" == "-text-timehour"} {
set hour $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
}
}
}
if {"$hour" != ""} {
if {$TwentyFourHourMode} {
if {"$ampm" != ""} {
if {"$ampm" == "PM" && $hour < 12} {
incr hour 12
$w.timehour configure -text $hour
}
}
} else {
if {$hour > 12} {
incr hour -12
$w.timehour configure -text $hour
$w.ampm configure -text "PM"
} else {
if {"$ampm" == ""} {
$w.ampm configure -text "AM"
}
}
}
}
}
#---------------------------------------------------------------------------
# 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
raise .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
ScheduleUpdateForChanges
return 0
}
}
}
#---------------------------------------------------------------------------
# CenterWindow -- center a window on the screen or over a parent.
# Stolen from tk_dialog code
# Arguments:
# w -- window to center
# parent -- window over which to center. Defaults to screen if not supplied.
#---------------------------------------------------------------------------
proc CenterWindow {w {parent {}}} {
wm withdraw $w
update idletasks
if {"$parent" == ""} {
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]]]
} else {
set x [expr [winfo rootx $parent] + [winfo width $parent]/2 - [winfo reqwidth $w]/2]
set y [expr [winfo rooty $parent] + [winfo height $parent]/2 - [winfo reqheight $w]/2]
}
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 TwentyFourHourMode
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 durationbut
set rem [CreateReminder$OptionType $w]
# Do the "until" part
if {$expbut} {
append rem " UNTIL "
append rem [consolidate [$w.expyear cget -text] [$w.expmon cget -text] [$w.expday 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 {!$TwentyFourHourMode} {
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]"
}
if {$durationbut} {
append rem " DURATION [$w.durationh cget -text]:[$w.durationm 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 - 2>@1" 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 " [string map -nocase {"\n" " "} $body]
return $rem
}
# We used to return YYYY-MM-DD, but reverted to
# day monthname year because this lets Remind produce
# much better error messages.
proc consolidate {y m d} {
global MonthNames
if {![regexp {^[0-9]+$} $m]} {
set m [lsearch -exact $MonthNames $m]
incr m
}
set mname [lindex $MonthNames [expr $m-1]]
return "$d $mname $y"
}
#---------------------------------------------------------------------------
# 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]
set m [$w.mon1 cget -text]
set y [$w.year1 cget -text]
if {"$d" != "every day" && "$m" != "every month" && $y != "every year"} {
set gotDay 1
set gotMon 1
set gotYear 1
append rem " "
append rem [consolidate $y $m $d]
} else {
if {"$d" != "every day"} {
append rem " $d"
set gotDay 1
}
if {"$m" != "every month"} {
append rem " $m"
set gotMon 1
}
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]
if {$mon != "every month" && $year != "every year"} {
set rem "REM $wkday "
append rem [consolidate $year $mon $day]
} else {
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 flash; .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 Option TwentyFourHourMode
if {$TwentyFourHourMode} {
set problem [catch { set DaemonFile [open "|$Remind -b1 -z0 -itkremind=1 $Option(ExtraRemindArgs) $ReminderFile" "r+"] } err]
} else {
set problem [catch { set DaemonFile [open "|$Remind -z0 -itkremind=1 $Option(ExtraRemindArgs) $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 ReminderFile ReminderFileModTime
# Don't let the background handler trigger another reread
catch {
set mtime [file mtime $ReminderFile]
set ReminderFileModTime $mtime
}
catch {
puts $DaemonFile "REREAD"
flush $DaemonFile
}
}
#---------------------------------------------------------------------------
# ShowQueue
# Arguments:
# file -- file channel that is readable
# Returns:
# nothing
# Description:
# Dumps the debugging queue listing
#---------------------------------------------------------------------------
proc ShowQueue { file } {
set w .queuedbg
catch { destroy $w }
toplevel $w
wm title $w "Queue (Debugging Output)"
wm iconname $w "Queue Dbg"
text $w.t -width 80 -height 30 -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.t -row 0 -column 0 -sticky nsew
grid $w.sb -row 0 -column 1 -sticky ns
grid $w.ok -row 1 -column 0 -sticky w
grid columnconfigure $w 0 -weight 1
grid columnconfigure $w 1 -weight 0
grid rowconfigure $w 0 -weight 1
grid rowconfigure $w 1 -weight 0
CenterWindow $w .
while (1) {
# We should only get one line
gets $file line
if {$line == "NOTE ENDJSONQUEUE"} {
break
}
if {[catch {set obj [::json::json2dict $line]}]} {
continue;
}
set obj [lsort -command sort_q $obj]
foreach q $obj {
$w.t insert end "$q\n"
}
}
$w.t configure -state disabled
}
proc sort_q { a b } {
set a_ttime [dict get $a nextttime]
set b_ttime [dict get $b nextttime]
if {$a_ttime < $b_ttime} {
return -1
}
if {$a_ttime > $b_ttime} {
return 1
}
return 0
}
#---------------------------------------------------------------------------
# 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 JSONQUEUE" {
ShowQueue $file
}
"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 stderr "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
# Automatically shut down window after a minute if option says so
set after_token [after 60000 [list ClosePopup $w "" $Option(MailAddr) $Option(AutoClose) "" $tag $msg $time]]
wm protocol $w WM_DELETE_WINDOW [list ClosePopup $w $after_token "" 1 "" $tag $msg $time]
button $w.ok -text "OK" -command [list ClosePopup $w $after_token "" 1 "" $tag $msg $time]
if {$tag != "*"} {
button $w.nomore -text "Don't remind me again today" -command [list ClosePopup $w $after_token "" 1 "ignore" $tag $msg $time]
button $w.kill -text "Delete this reminder completely" -command [list ClosePopup $w $after_token "" 1 "kill" $tag $msg $time]
}
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 .
update
if {$Option(RingBell)} {
bell
}
if {$Option(RunCmd) != ""} {
if {$Option(FeedReminder)} {
FeedReminderToCommand $Option(RunCmd) "$time: $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 {} {
global ConfigFile
font create CalboxFont {*}[font actual TkFixedFont]
font create HeadingFont {*}[font actual TkDefaultFont]
font configure TkTextFont -size 6
global AppendFile HighestTagSoFar DayNames
catch {
puts "\nTkRemind Copyright (C) 1996-2021 Dianne Skoll"
}
catch { SetFonts }
Initialize
# If no $ConfigFile file, create an empty one
if {![file exists $ConfigFile]} {
catch {
set f [open $ConfigFile "w"]
close $f
}
}
FindConfigFile
LoadOptions
ShowTodaysReminders
ScanForTags $AppendFile
CreateCalWindow $DayNames
FillCalWindow
StartBackgroundRemindDaemon
SetupInotify
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 {
{^REM TAG TKTAG[0-9]+} {
regexp {^REM TAG 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
# date -- today's date
# %RETURNS:
# A list of options for the dialog box for the tagged reminder
# %DESCRIPTION:
# Converts the JSON dictionary to a list of options for dialog box
#***********************************************************************
proc ReadTaggedOptions { tag date } {
global TagToObj MonthNames EnglishDayNames TwentyFourHourMode
if {![info exists TagToObj($tag)]} {
return ""
}
set obj $TagToObj($tag)
set ans ""
regexp {^([0-9][0-9][0-9][0-9]).([0-9][0-9]).([0-9][0-9])} $date all y m d
set m [string trimleft $m 0]
set d [string trimleft $d 0]
set y [string trimleft $y 0]
if {![dict exists $obj skip]} {
lappend ans -global-SkipType 1
} else {
set s [dict get $obj skip]
if {"$s" == "SKIP"} {
lappend ans -global-SkipType 2
} elseif {"$s" == "BEFORE"} {
lappend ans -global-SkipType 3
} elseif {"$s" == "AFTER"} {
lappend ans -global-SkipType 4
} else {
lappend ans -global-SkipType 1
}
}
if {[dict exists $obj d]} {
lappend ans -text-day1 [dict get $obj d]
lappend ans -text-day2 [dict get $obj d]
} else {
lappend ans -text-day1 {every day}
lappend ans -text-day2 $d
}
if {[dict exists $obj m]} {
set m [dict get $obj m]
set mm [string trimleft $m 0]
lappend ans -text-mon1 [lindex $MonthNames [expr $mm -1]]
lappend ans -text-mon2 [lindex $MonthNames [expr $mm -1]]
lappend ans -text-mon3 [lindex $MonthNames [expr $mm -1]]
} else {
lappend ans -text-mon1 {every month}
lappend ans -text-mon2 {every month}
lappend ans -text-mon3 {every month}
}
if {[dict exists $obj y]} {
lappend ans -text-year1 [dict get $obj y]
lappend ans -text-year2 [dict get $obj y]
lappend ans -text-year3 [dict get $obj y]
} else {
lappend ans -text-year1 {every year}
lappend ans -text-year2 {every year}
lappend ans -text-year3 {every year}
}
set wd {}
if {[dict exists $obj wd]} {
set wd [dict get $obj wd]
if {[llength $wd] == 1} {
lappend ans -text-wkday2 [lindex $wd 0]
lappend ans -text-wkday3 [lindex $wd 0]
} elseif {"$wd" == "Monday Tuesday Wednesday Thursday Friday"} {
lappend ans -text-wkday2 weekday
lappend ans -text-wkday3 weekday
}
} else {
lappend ans -text-wkday2 [get_weekday $date]
lappend ans -text-wkday3 [get_weekday $date]
}
if {[llength $wd] > 0} {
if {[dict exists $obj d]} {
set day [dict get $obj d]
if {$day < 8} {
if {[dict exists $obj back]} {
lappend ans -text-ordinal Last
# Adjust month down and possibly year?
if {[dict exists $obj m]} {
set mm [string trimleft [dict get $obj m] 0]
set idx [expr $mm -1]
if {$idx <= 0} {
set idx 12
}
lappend ans -text-mon1 [lindex $MonthNames [expr $idx -1]]
lappend ans -text-mon2 [lindex $MonthNames [expr $idx -1]]
lappend ans -text-mon3 [lindex $MonthNames [expr $idx -1]]
if {[dict exists $obj y]} {
set year [dict get $obj y]
if {$idx == 12} {
lappend ans -text-year1 [expr $year - 1]
lappend ans -text-year2 [expr $year - 1]
lappend ans -text-year3 [expr $year - 1]
}
}
}
} else {
lappend ans -text-ordinal First
}
} elseif {$day < 15} {
lappend ans -text-ordinal Second
} elseif {$day < 22} {
lappend ans -text-ordinal Third
} else {
lappend ans -text-ordinal Fourth
}
} else {
lappend ans -text-ordinal Every
}
} else {
if {$d < 8} {
lappend ans -text-ordinal First
} elseif {$d < 15} {
lappend ans -text-ordinal Second
} elseif {$d < 22} {
lappend ans -text-ordinal Third
} elseif {$d < 29} {
lappend ans -text-ordinal Fourth
} else {
lappend ans -text-ordinal Last
}
}
if {[dict exists $obj until]} {
set u [dict get $obj until]
regexp {^([0-9][0-9][0-9][0-9]).([0-9][0-9]).([0-9][0-9])} $u all yu mu du
# Trim leading zeros, or Tcl complains
set mu [string trimleft $mu 0]
lappend ans -global-expbut 1
lappend ans -text-expday $du
lappend ans -text-expmon [lindex $MonthNames [expr $mu-1]]
lappend ans -text-expyear $yu
} else {
set mm [string trimleft $m 0]
lappend ans -global-expbut 0
lappend ans -text-expday $d
lappend ans -text-expmon [lindex $MonthNames [expr $mm-1]]
lappend ans -text-expyear $y
}
if {[dict exists $obj delta]} {
set delta [dict get $obj delta]
if {$delta == 0} {
lappend ans -global-advbut 0
lappend ans -text-advdays 3
lappend ans -global-advcount 1
} elseif {$delta < 0} {
set delta [expr abs($delta)]
lappend ans -global-advbut 1
lappend ans -text-advdays $delta
lappend ans -global-advcount 0
} else {
lappend ans -global-advbut 1
lappend ans -text-advdays $delta
lappend ans -global-advcount 1
}
} else {
lappend ans -global-advbut 0
lappend ans -text-advdays 3
lappend ans -global-advcount 1
}
if {[dict exists $obj localomit]} {
set lo [dict get $obj localomit]
foreach w $EnglishDayNames {
if {[lsearch -exact $lo $w] >= 0} {
lappend ans "-global-d$w" 1
} else {
lappend ans "-global-d$w" 0
}
}
} else {
lappend ans -global-dSunday 1
lappend ans -global-dMonday 0
lappend ans -global-dTuesday 0
lappend ans -global-dWednesday 0
lappend ans -global-dThursday 0
lappend ans -global-dFriday 0
lappend ans -global-dSaturday 1
}
if {[dict exists $obj rep]} {
lappend ans -global-repbut 1
lappend ans -text-repdays [dict get $obj rep]
} else {
lappend ans -global-repbut 0
lappend ans -text-repdays 1
}
if {[dict exists $obj time]} {
set t [dict get $obj time]
lappend ans -global-timebut 1
set hour [expr $t / 60]
set minute [format %02d [expr $t % 60]]
if {$hour == 0 && !$TwentyFourHourMode} {
lappend ans -text-timehour 12
lappend ans -text-ampm AM
} else {
if {$TwentyFourHourMode} {
lappend ans -text-timehour $hour
} else {
if {$hour >= 12} {
incr $hour -12
lappend ans -text-timehour $hour
lappend ans -text-ampm PM
} else {
lappend ans -text-timehour $hour
lappend ans -text-ampm AM
}
}
}
lappend ans -text-timemin $minute
if {[dict exists $obj tdelta]} {
lappend ans -global-timeadvbut 1
lappend ans -text-timeadv [dict get $obj tdelta]
} else {
lappend ans -global-timeadvbut 0
lappend ans -text-timeadv 15
}
if {[dict exists $obj trep]} {
lappend ans -global-timerepbut 1
lappend ans -text-timerep [dict get $obj trep]
} else {
lappend ans -global-timerepbut 0
lappend ans -text-timerep 5
}
if {[dict exists $obj duration]} {
lappend ans -global-durationbut 1
set dur [dict get $obj duration]
lappend ans -text-durationh [expr $dur / 60]
lappend ans -text-durationm [format %02d [expr $dur % 60]]
} else {
lappend ans -global-durationbut 0
lappend ans -text-durationh 1
lappend ans -text-durationm 00
}
} else {
lappend ans -global-timebut 0
lappend ans -text-timehour 12
lappend ans -text-timemin 00
lappend ans -text-timeadv 15
lappend ans -global-timerepbut 0
lappend ans -text-timerep 5
lappend ans -global-durationbut 0
lappend ans -text-durationh 1
lappend ans -text-durationm 00
}
if {[dict exists $obj rawbody]} {
lappend ans -entry-entry [dict get $obj rawbody]
} else {
lappend ans -entry-entry [dict get $obj body]
}
# Figure out the reminder type
if {[dict exists $obj rep]} {
# Repeat must be type 1
lappend ans -global-OptionType 1
lappend ans -text-day2 $d
lappend ans -text-mon2 [lindex $MonthNames [expr $m - 1]]
lappend ans -text-mon3 [lindex $MonthNames [expr $m - 1]]
lappend ans -text-year2 $y
lappend ans -text-year3 $y
} elseif {![dict exists $obj wd]} {
# No weekdays - must be type 1
lappend ans -global-OptionType 1
lappend ans -text-day2 $d
lappend ans -text-mon2 [lindex $MonthNames [expr $m - 1]]
lappend ans -text-mon3 [lindex $MonthNames [expr $m - 1]]
lappend ans -text-year2 $y
lappend ans -text-year3 $y
} elseif {![dict exists $obj d]} {
# No day... must be "every wkday in ..."
lappend ans -global-OptionType 3
lappend ans -text-day1 $d
lappend ans -text-mon1 [lindex $MonthNames [expr $m - 1]]
lappend ans -text-year1 $y
lappend ans -text-day2 $d
lappend ans -text-mon2 [lindex $MonthNames [expr $m - 1]]
lappend ans -text-year2 $y
} else {
set day [dict get $obj d]
# Take a guess based on day
if {$day == 1 || $day == 8 || $day == 15 || $day == 22} {
lappend ans -global-OptionType 3
lappend ans -text-day1 $d
lappend ans -text-mon1 [lindex $MonthNames [expr $m - 1]]
lappend ans -text-year1 $y
lappend ans -text-day2 $d
lappend ans -text-mon2 [lindex $MonthNames [expr $m - 1]]
lappend ans -text-year2 $y
} else {
lappend ans -global-OptionType 2
lappend ans -text-day1 $d
lappend ans -text-mon1 [lindex $MonthNames [expr $m - 1]]
lappend ans -text-year1 $y
lappend ans -text-mon3 [lindex $MonthNames [expr $m - 1]]
lappend ans -text-year3 $y
}
}
return $ans
}
proc FireEditor { w {fntag ""}} {
global Option
global EditorPid
if {"$fntag" == ""} {
set tags [$w tag names current]
set index [lsearch -glob $tags "FILE_*"]
if {$index < 0} {
return
}
set fntag [lindex $tags $index]
}
if {![regexp {^FILE_([0-9]+)_(.*)} $fntag 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
}
}
Status "Firing up editor..."
after 1500 DisplayTime
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 } {
global Option
set tag [GetCurrentReminder $w]
if {$tag != ""} {
set tags [$w tag names current]
set index [lsearch -glob $tags "clr*"]
if {$index < 0} {
set fg $Option(TextColor)
} else {
set fg [string range [lindex $tags $index] 3 end]
set fg "#$fg"
}
$w tag configure $tag -foreground $fg
}
}
proc EditableEnter { w } {
set tags [$w tag names current]
set index [lsearch -glob $tags "FILE_*"]
if {$index < 0} {
return
}
set tag [lindex $tags $index]
set c ""
set index [lsearch -glob $tags "clr*"]
if {$index >= 0} {
set ctag [lindex $tags $index]
set c [$w tag cget $ctag -foreground]
}
if {"$c" != ""} {
$w tag configure $tag -underline 1 -underlinefg $c
} else {
$w tag configure $tag -underline 1
}
}
proc EditableLeave { w } {
set tags [$w tag names current]
set index [lsearch -glob $tags "FILE_*"]
if {$index < 0} {
return
}
set tag [lindex $tags $index]
$w tag configure $tag -underline 0
}
#***********************************************************************
# %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
}
# Get the date
set index [lsearch -glob [$w tag names current] "date_*"]
if {$index < 0} {
return
}
set date [string range [lindex [$w tag names current] $index] 5 end]
# Read in options
set opts [ReadTaggedOptions $tag $date]
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} {
raise .mod
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
}
ScheduleUpdateForChanges
return 0
}
}
#***********************************************************************
# %PROCEDURE: UpdateForChanges
# Updates the calendar window and restarts background daemon because
# something has changed.
# %ARGUMENTS:
# None
# %RETURNS:
# Nothing
#***********************************************************************
proc UpdateForChanges {} {
global TimerUpdateForChanges
catch { after cancel $TimerUpdateForChanges }
FillCalWindow
RestartBackgroundRemindDaemon
}
# Schedule an update for 100ms in the future.
# That way, if we get a rapid succession of
# change notifications, we (probably) only
# end up doing one call to UpdateForChanges
proc ScheduleUpdateForChanges {} {
global TimerUpdateForChanges
catch { after cancel $TimerUpdateForChanges }
set TimerUpdateForChanges [after 100 UpdateForChanges]
}
#***********************************************************************
# %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
global HighestTagSoFar
set tmpfile [UniqueFileName $AppendFile]
set out [open $tmpfile "w"]
write_warning_headers $out
set in [open $AppendFile "r"]
set found 0
set tagno 0
while {[gets $in line] >= 0} {
if {[is_warning_header $line]} {
continue
}
if {[string match "REM TAG $tag *" $line]} {
set found 1
continue
}
# Delete the old comment lines
if {[string match "# TKTAG*" $line]} {
continue
}
if {[string match "# -global-OptionType *" $line]} {
continue
}
if {[string match "# TKEND" $line]} {
continue
}
# Renumber tags
if {[regexp {^REM TAG TKTAG([0-9]+) (.*)$} $line all oldtag rest]} {
incr tagno
puts $out "REM TAG TKTAG$tagno $rest"
} else {
puts $out $line
}
}
if {! $found } {
close $in
close $out
file delete $tmpfile
error "Did not find reminder with tag $tag"
}
set HighestTagSoFar $tagno
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"]
write_warning_headers $out
set in [open $AppendFile "r"]
set found 0
while {[gets $in line] >= 0} {
if {[is_warning_header $line]} {
continue
}
if {[string match "REM TAG $tag *" $line]} {
# Write the new reminder
WriteReminder $out $tag $rem $opts
set found 1
} else {
# Delete the old comment lines
if {[string match "# TKTAG*" $line]} {
continue
}
if {[string match "# -global-OptionType *" $line]} {
continue
}
if {[string match "# TKEND" $line]} {
continue
}
puts $out $line
}
}
if {! $found} {
close $in
close $out
file delete $tmpfile
error "Did not find reminder with tag $tag"
}
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
# r, g, b -- colour components
# %RETURNS:
# Nothing
# %DESCRIPTION:
# Handles the "SHADE" special -- shades a box.
#***********************************************************************
proc DoShadeSpecial { n r g b } {
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
# fntag - filename tag, if any
# %RETURNS:
# Nothing
# %DESCRIPTION:
# Handles the "MOON" special -- draws a moon symbol
#***********************************************************************
proc DoMoonSpecial { n stuff fntag day } {
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 win .moon_new }
1 { set win .moon_first }
2 { set win .moon_full }
3 { set win .moon_last }
}
# We need two sets of moon phase windows. There can be
# two of a given phase in the same month, but Tk does
# not allow the same embedded window in two separate
# text boxes. So we use this hack to make sure
# we use a different window if the same moon phase
# happens twice in a month.
if {$day > 16} {
append win "2"
}
.cal.t$n configure -state normal
.cal.t$n window create 1.0 -window $win
if {$msg != ""} {
if {"$fntag" == "x"} {
.cal.t$n insert 1.1 " $msg\n"
} else {
.cal.t$n insert 1.1 " $msg\n" [list REM $fntag]
.cal.t$n tag bind $fntag <Enter> "EditableEnter .cal.t$n"
.cal.t$n tag bind $fntag <Leave> "EditableLeave .cal.t$n"
.cal.t$n tag bind $fntag <ButtonPress-1> "FireEditor .cal.t$n $fntag"
bind $win <ButtonPress-1> "FireEditor .cal.t$n $fntag"
bind $win <ButtonPress-3> "FireEditor .cal.t$n $fntag"
}
} else {
if {"$fntag" == "x"} {
.cal.t$n insert 1.1 "\n"
} else {
.cal.t$n insert 1.1 "\n" [list REM $fntag]
.cal.t$n tag bind $fntag <Enter> "EditableEnter .cal.t$n"
.cal.t$n tag bind $fntag <Leave> "EditableLeave .cal.t$n"
.cal.t$n tag bind $fntag <ButtonPress-1> "FireEditor .cal.t$n $fntag"
}
}
.cal.t$n configure -state disabled -takefocus 0
}
#***********************************************************************
# %PROCEDURE: DisplayTime
# %ARGUMENTS:
# None
# %RETURNS:
# Nothing
# %DESCRIPTION:
# Displays current date and time in status window
#***********************************************************************
proc DisplayTime {} {
global TwentyFourHourMode
if {$TwentyFourHourMode} {
set msg [clock format [clock seconds] -format "%e %b %Y %H:%M"]
} else {
set msg [clock format [clock seconds] -format "%e %b %Y %I:%M%p"]
}
Status $msg
}
#***********************************************************************
# %PROCEDURE: CreateMoonWindows
# %ARGUMENTS:
# None
# %RETURNS:
# Nothing
# %DESCRIPTION:
# Creates the moon windows .moon_new, .moon_first, .moon_full and
# .moon_last
#***********************************************************************
proc CreateMoonWindows {} {
global Option;
catch { destroy .moon_new }
catch { destroy .moon_first }
catch { destroy .moon_full }
catch { destroy .moon_last }
catch { destroy .moon_new2 }
catch { destroy .moon_first2}
catch { destroy .moon_full2 }
catch { destroy .moon_last2 }
set extra 1
set wid [font metrics CalboxFont -ascent]
set orig_wid $wid
incr wid $extra
incr wid $extra
incr wid $extra
incr wid $extra
incr orig_wid $extra
incr orig_wid $extra
set w [expr $extra+$orig_wid]
foreach win {.moon_new .moon_new2 } {
canvas $win -background $Option(BackgroundColor) -width $wid -height $wid -borderwidth 0 -highlightthickness 0
$win create oval $extra $extra $w $w -outline $Option(TextColor) -width 1
balloon_add_help $win "New Moon"
}
foreach win {.moon_first .moon_first2 } {
canvas $win -background $Option(BackgroundColor) -width $wid -height $wid -borderwidth 0 -highlightthickness 0
$win create oval $extra $extra $w $w -outline $Option(TextColor) -width 1
$win create arc $extra $extra $w $w -outline $Option(TextColor) -fill $Option(TextColor) -start 90 -extent 180 -outline {}
balloon_add_help $win "First Quarter"
}
foreach win {.moon_full .moon_full2 } {
canvas $win -background $Option(BackgroundColor) -width $wid -height $wid -borderwidth 0 -highlightthickness 0
$win create oval $extra $extra $w $w -outline $Option(TextColor) -fill $Option(TextColor) -width 1
balloon_add_help $win "Full Moon"
}
foreach win {.moon_last .moon_last2 } {
canvas $win -background $Option(BackgroundColor) -width $wid -height $wid -borderwidth 0 -highlightthickness 0
$win create oval $extra $extra $w $w -outline $Option(TextColor) -width 1
$win create arc $extra $extra $w $w -outline $Option(TextColor) -fill $Option(TextColor) -start 270 -extent 180 -outline {}
balloon_add_help $win "Last Quarter"
}
}
#***********************************************************************
# %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
global TwentyFourHourMode
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 ""
set cmdline "|$Remind -itkremind=1 -g -q -r "
if {$TwentyFourHourMode} {
append cmdline "-b1 "
}
append cmdline $Option(ExtraRemindArgs);
append cmdline " $ReminderFile 2>/dev/null"
set f [open $cmdline r]
while {[gets $f line] >= 0} {
append stuff "$line\n"
}
close $f
$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
ScheduleUpdateForChanges
}
}
proc SendMail { recipient subject body } {
global Option
if {"$Option(MailAddr)" == ""} {
return
}
if {[catch {set token [mime::initialize -canonical text/plain -string $body]
mime::setheader $token Subject $subject
mime::setheader $token From "Reminder Service <>"
mime::setheader $token To "<$recipient>"
mime::setheader $token Auto-Submitted "auto-generated"
smtp::sendmessage $token -originator "" -servers $Option(SMTPServer) -recipients $Option(MailAddr)} err]} {
puts stderr "ERROR sending mail: $err"
}
}
proc ClosePopup { w after_token mail_addr close_win ignore_or_kill tag reminder rem_time } {
global Ignore
if {"$after_token" != ""} {
catch { after cancel $after_token }
}
if {$close_win} {
catch { destroy $w }
}
if {"$mail_addr" != ""} {
SendMail $mail_addr "Reminder for $rem_time" "Hello,\n\nThe following reminder is scheduled for $rem_time:\n\n$reminder\nRegards,\n\nTkRemind\n"
}
if {"$ignore_or_kill" == "ignore"} {
set Ignore($tag) 1
}
if {"$ignore_or_kill" == "kill"} {
InteractiveDeleteReminder $tag
}
}
# Adjust font defaults for screen size
proc SetFonts {} {
global SetFontsWorked
set h [winfo screenheight .]
if {$h <= 480} {
# Small screen (maybe eeepc?)
font configure TkDefaultFont -size 6
font configure TkFixedFont -size 6
font configure TkTextFont -size 6
}
if {$h >= 1080} {
# HIDPI screen
font configure TkDefaultFont -size 12
font configure TkFixedFont -size 12
font configure TkTextFont -size 12
}
set SetFontsWorked 1
}
# Set up inotify to watch for changes to reminder file/directory
proc SetupInotify {} {
global InotifyFP
global ReminderFile
set failed [catch {set InotifyFP [open "|inotifywait -r -q -m -e close_write -e move -e create -e delete $ReminderFile < /dev/null 2>/dev/null" "r"] } ]
if {$failed} {
# inotifywait probably not available... meh.
return
}
fileevent $InotifyFP readable [list InotifyReadable $InotifyFP]
}
# Called when inotifywait reports an event. Schedule a calendar update
# and daemon reload.
proc InotifyReadable { fp } {
catch { set num [gets $fp line] }
if {$num < 0} {
catch { exec kill [pid $fp] }
close $fp
return
}
ScheduleUpdateForChanges
}
### Balloon help
set Balloon(HelpTime) 400
set Balloon(StayTime) 3500
set Balloon(Font) fixed
set Balloon(MustLeave) 0
proc balloon_reset_timer { w } {
balloon_destroy_help_window
balloon_cancel_timer
balloon_schedule_help $w
}
proc balloon_destroy_help_window {} {
catch { destroy .balloonhelp }
}
proc balloon_cancel_timer {} {
global Balloon
catch { after cancel $Balloon(HelpId) }
}
proc balloon_schedule_help { w } {
global Balloon
if { $Balloon(MustLeave) } {
return
}
set Balloon(HelpId) [ after $Balloon(HelpTime) "balloon_popup_help $w" ]
}
proc balloon_popup_help { w } {
global Balloon
if {![info exists Balloon(helptext$w)]} {
return
}
if {[string compare [winfo containing [winfo pointerx .] [winfo pointery .]] $w]} {
return
}
set h .balloonhelp
toplevel $h -bg #000000
label $h.l -text $Balloon(helptext$w) -wraplength 200 -justify left -bg #FFFFC0 -bd 0
pack $h.l -padx 1 -pady 1 -ipadx 2 -ipady 1
wm overrideredirect $h 1
set geom [balloon_calculate_geometry $h]
wm geometry $h $geom
set Balloon(HelpId) [after $Balloon(StayTime) "catch { destroy $h }"]
set Balloon(MustLeave) 1
}
bind Balloon <Leave> {
set Balloon(MustLeave) 0
balloon_destroy_help_window
balloon_cancel_timer
}
bind Balloon <Enter> {
set Balloon(MustLeave) 0
balloon_reset_timer %W
}
bind Balloon <Any-Motion> "balloon_reset_timer %W"
bind Balloon <Any-ButtonPress> {
set Balloon(MustLeave) 1
balloon_reset_timer %W
}
bind Balloon <Destroy> {
balloon_destroy_help_window
balloon_cancel_timer
catch { unset Balloon(helptext%W) }
}
proc balloon_add_help { w txt } {
global Balloon
if {"$txt" == ""} {
catch { unset Balloon(helptext$w) }
return
}
set Balloon(helptext$w) $txt
bindtags $w "Balloon [bindtags $w]"
}
proc balloon_calculate_geometry { w } {
set x [winfo pointerx $w]
set y [winfo pointery $w]
set mx [winfo screenwidth $w]
set my [winfo screenheight $w]
# Adjust for padding
set wid [expr [winfo reqwidth $w.l] + 6]
set h [expr [winfo reqheight $w.l] + 4]
# Try above-right of pointer
set tx [expr $x+3]
set ty [expr $y-3-$h]
if {$ty >= 0 && ($tx+$wid) <= $mx} {
return "+$tx+$ty"
}
# Try above-left of pointer
set tx [expr $x-3-$wid]
set ty [expr $y-3-$h]
if {$ty >= 0 && $tx >= 0} {
return "+$tx+$ty"
}
# Try below-right of pointer
set tx [expr $x+3]
set ty [expr $y+3]
if {$ty+$h <= $my && ($tx+$wid) <= $mx} {
return "+$tx+$ty"
}
# Darn... must be below-left
set tx [expr $x-3-$wid]
set ty [expr $y+3]
return "+$tx+$ty"
}
proc ChooseCalboxFont {} {
tk fontchooser show
tk fontchooser configure -font [font actual CalboxFont]
tk fontchooser configure -command SetCalboxFont
}
proc SetCalboxFont {font} {
global tmpOpt
font configure CalboxFont {*}[font actual $font]
set tmpOpt(CalboxFont) [font actual $font]
raise .opt
}
proc ChooseHeadingFont {} {
tk fontchooser show
tk fontchooser configure -font [font actual HeadingFont]
tk fontchooser configure -command SetHeadingFont
}
proc SetHeadingFont {font} {
global tmpOpt
font configure HeadingFont {*}[font actual $font]
set tmpOpt(HeadingFont) [font actual $font]
raise .opt
}
proc PickColor {index button} {
global tmpOpt
set x [tk_chooseColor -initialcolor $tmpOpt($index)]
if {"$x" != ""} {
set tmpOpt($index) $x
$button configure -background $x
}
raise .opt
}
proc FindConfigFile {} {
global ConfigFile
# If it was set on the command line, use that
if {"$ConfigFile" != ""} {
return
}
set confighome ""
if {[info exists env(XDG_CONFIG_HOME)]} {
set confighome $env(XDG_CONFIG_HOME)
}
if {"$confighome" == ""} {
set confighome "~/.config"
}
# If $confighome does not exist, attempt to
# create it
if {![file exists $confighome]} {
catch { file mkdir $confighome }
}
if {[file isdirectory $confighome]} {
# Migrate .tkremindrc to $confighome/tkremindrc
if {[file exists "~/.tkremindrc"]} {
if {![file exists "$confighome/tkreminderc"]} {
puts "Migrating ~/.tkremindrc to $confighome/tkremindrc"
if {[catch { file copy "~/.tkremindrc" "$confighome/tkremindrc"}]} {
puts "FAILED!\n"
set ConfigFile "~/.tkremindrc"
return
}
catch { file delete "~/.tkremindrc" }
}
set ConfigFile "$confighome/tkremindrc"
return
}
set ConfigFile "$confighome/tkremindrc"
return
}
set ConfigFile "~/.tkremindrc"
}
proc set_default_colors { w } {
global tmpOpt
set tmpOpt(BackgroundColor) "#d9d9d9"
set tmpOpt(LabelColor) "#000000"
set tmpOpt(LineColor) "#000000"
set tmpOpt(TextColor) "#000000"
set tmpOpt(TodayColor) "#00C0C0"
set tmpOpt(WinBackground) "#d9d9d9"
update_color_buttons $w
}
proc set_dark_colors { w } {
global tmpOpt
set tmpOpt(BackgroundColor) "#000000"
set tmpOpt(LabelColor) "#00ffff"
set tmpOpt(LineColor) "#0080fc"
set tmpOpt(TextColor) "#ffffff"
set tmpOpt(TodayColor) "#b000b6"
set tmpOpt(WinBackground) "#000000"
update_color_buttons $w
}
proc update_color_buttons { w } {
global tmpOpt
$w.bbgcolor configure -background $tmpOpt(BackgroundColor)
$w.bheadcolor configure -background $tmpOpt(LabelColor)
$w.gridbcolor configure -background $tmpOpt(LineColor)
$w.btextcolor configure -background $tmpOpt(TextColor)
$w.tbbgcolor configure -background $tmpOpt(TodayColor)
$w.bwincolor configure -background $tmpOpt(WinBackground)
}
proc set_button_to_queue {} {
.b.queue configure -text {Queue...} -command {DoQueue}
}
proc set_button_to_errors {} {
.b.queue configure -text {Errors...} -command {ShowErrors}
}
proc ShowErrors {} {
global RemindErrors
set w ".errors"
catch { destroy $w }
toplevel $w
text $w.t -width 80 -height 30 -wrap word -yscrollcommand "$w.sb set"
scrollbar $w.sb -orient vertical -command "$w.t yview"
button $w.ok -text OK -command DoneShowingErrors
grid $w.t -row 0 -column 0 -sticky nsew
grid $w.sb -row 0 -column 1 -sticky ns
grid $w.ok -row 1 -column 0 -stick w
grid columnconfigure $w 0 -weight 1
grid columnconfigure $w 1 -weight 0
grid rowconfigure $w 0 -weight 1
grid rowconfigure $w 1 -weight 0
$w.t insert end $RemindErrors
$w.t configure -state disabled
CenterWindow $w .
}
proc DoneShowingErrors {} {
global RemindErrors
set RemindErrors {}
set_button_to_queue
destroy .errors
}
# Rem2PS program to execute -- supply full path if you want
main