#!/bin/sh # -*-Mode: TCL;-*- #-------------------------------------------------------------- # TKREMIND # # A cheesy graphical front/back end for Remind using Tcl/Tk # # This file is part of REMIND. # Copyright (C) 1992-1998 by David F. Skoll # #-------------------------------------------------------------- # $Id: tkremind,v 1.16 1998-04-19 03:27:03 dfs Exp $ # the next line restarts using wish \ exec wish "$0" "$@" wm withdraw . # Check that we have the right version of wish if {$tcl_version < 8.0} { tk_dialog .error Error "You need wish version 8.0 or higher to run TkRemind; you have $tcl_version" error 0 OK exit 1 } if {$tcl_platform(platform) == "windows"} { tk_dialog .error Error "You are not allowed to run Remind under 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(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)" # Remind program to execute -- supply full path if you want set Remind "remind" #set Remind "/home/dfs/Remind/src/remind" # Rem2PS program to execute -- supply full path if you want set Rem2PS "rem2ps" # Reminder file to source -- default set ReminderFile {NOSUCHFILE} set ReminderFile [file nativename "~/.reminders"] # Reminder file to append to -- default set AppendFile {NOSUCHFILE} catch {set AppendFile $ReminderFile} #---------------- DON'T CHANGE STUFF BELOW HERE ------------------ # Is Monday in first column? set MondayFirst 0 # Month names in English set MonthNames {January February March April May June July August September October November December} # Day names in Remind's pre-selected language set DayNames {} # Day name in English set EnglishDayNames {Sunday Monday Tuesday Wednesday Thursday Friday Saturday} # Current month and year -- will be set by Initialize procedure set CurMonth -1 set CurYear -1 # Background reminder counter set BgCounter 0 # Absolutely today -- unlike the CurMonth and CurYear, these won't change set TodayMonth -1 set TodayYear -1 set TodayDay -1 # Reminder option types and skip types set OptionType 1 set SkipType 1 # Remind command line set CommandLine {} set PSCmd {} # Print options -- destination file; letter-size; landscape; fill page set PrintDest file set PrintSize letter set PrintOrient landscape set PrintFill 1 # Highest tag seen so far. Array of tags is stored in ReminderTags() set HighestTagSoFar 0 #*********************************************************************** # %PROCEDURE: Initialize # %ARGUMENTS: # None # %RETURNS: # Nothing # %DESCRIPTION: # Initializes TkRemind -- sets day names, Remind command line, # MondayFirst flag, current date, etc. #*********************************************************************** proc Initialize {} { global DayNames argc argv CommandLine ReminderFile AppendFile Remind PSCmd global MondayFirst set CommandLine "|$Remind -itkremind=1 -p" set PSCmd "$Remind -p" set i 0 while {$i < $argc} { if {[regexp -- {-[bgxim].*} [lindex $argv $i]]} { append CommandLine " [lindex $argv $i]" append PSCmd " [lindex $argv $i]" if {[regexp -- {m} [lindex $argv $i]]} { set MondayFirst 1 } } else { break } incr i } if {$i < $argc} { set ReminderFile [lindex $argv $i] set AppendFile $ReminderFile incr i if {$i < $argc} { set AppendFile [lindex $argv $i] incr i } } # Check system sanity if {! [file readable $ReminderFile]} { set ans [tk_dialog .error "TkRemind: Warning" "Can't read reminder file `$ReminderFile'" warning 0 "Create it and continue" "Exit"] if {$ans != 0} { exit 1 } catch {close [open "$ReminderFile" w]} } if {! [file readable $ReminderFile]} { tk_dialog .error "TkRemind: Error" "Could not create reminder file `$ReminderFile'" error 0 "Exit" exit 1 } if {! [file writable $AppendFile]} { tk_dialog .error Error "Can't write reminder file `$AppendFile'" error 0 Ok exit 1 } append CommandLine " $ReminderFile" append PSCmd " $ReminderFile" set DayNames [GetWeekdayNames] # puts "CommandLine: $CommandLine" } #--------------------------------------------------------------------------- # GetWeekdayNames - Spawn a remind process and get the names of the weekdays # Also sets CurMonth and CurYear. #--------------------------------------------------------------------------- proc GetWeekdayNames {} { global CurMonth CurYear TodayYear TodayMonth TodayDay Remind set f [open "|$Remind - 2>/dev/null" r+] puts $f "banner %" set i 0 while { $i < 7 } { puts $f "msg \[wkday($i)\]%" incr i } # Get current month and year as long as we're running Remind puts $f "msg %n%" puts $f "msg %y%" puts $f "msg %d%" puts $f "FLUSH" flush $f set ans {} set i 0 while { $i < 7 } { lappend ans [gets $f] incr i } set CurMonth [expr [gets $f] - 1] set CurYear [gets $f] set TodayDay [gets $f] set TodayMonth $CurMonth set TodayYear $CurYear close $f return $ans } #*********************************************************************** # %PROCEDURE: CalEntryOffset # %ARGUMENTS: # firstDay -- first day of month (0=Sunday, 6=Saturday) # %RETURNS: # Offset mapping day numbers (1-31) to window numbers (0-41) # %DESCRIPTION: # Computes offset from day number to window number #*********************************************************************** proc CalEntryOffset { firstDay } { global MondayFirst if {$MondayFirst} { incr firstDay -1 if {$firstDay < 0} { set firstDay 6 } } return [expr $firstDay-1] } #*********************************************************************** # %PROCEDURE: CreateCalFrame # %ARGUMENTS: # w -- name of frame window # dayNames -- names of weekdays # %RETURNS: # Nothing # %DESCRIPTION: # Creates a frame holding a grid of labels and a grid of text entries #*********************************************************************** proc CreateCalFrame { w dayNames } { global MondayFirst frame $w for {set i 0} {$i < 7} {incr i} { if {$MondayFirst} { set index [expr ($i+1)%7] } else { set index $i } label $w.day$i -text [lindex $dayNames $index] -justify center grid configure $w.day$i -row 0 -column $i -sticky ew } for {set i 0} {$i < 6} {incr i} { set n [expr $i*7] for {set j 0} {$j < 7} {incr j} { set f [expr $n+$j] button $w.l$f -text "" -justify center -command "" \ -state disabled -relief flat text $w.t$f -width 12 -height 5 -wrap word -relief flat \ -state disabled -takefocus 0 -cursor {} $w.t$f tag bind TAGGED "TaggedEnter $w.t$f" $w.t$f tag bind TAGGED "TaggedLeave $w.t$f" $w.t$f tag bind TAGGED "EditTaggedReminder $w.t$f" grid configure $w.l$f -row [expr $i*2+1] -column $j -sticky ew grid configure $w.t$f -row [expr $i*2+2] -column $j -sticky nsew } } for {set i 0} {$i < 7} {incr i} { grid columnconfigure $w $i -weight 1 } for {set i 2} {$i < 14} {incr i 2} { grid rowconfigure $w $i -weight 1 } } #*********************************************************************** # %PROCEDURE: ConfigureCalFrame # %ARGUMENTS: # w -- window name of calendar frame # firstDay -- first weekday of month # numDays -- number of days in month # %RETURNS: # Nothing # %DESCRIPTION: # Sets up button labels; configures text justification #*********************************************************************** proc ConfigureCalFrame { w firstDay numDays } { global CurMonth CurYear TodayMonth TodayYear TodayDay set offset [CalEntryOffset $firstDay] set first [expr $offset+1] set last [expr $offset+$numDays] for {set i 0} {$i < $first} {incr i} { grid $w.l$i $w.t$i $w.l$i configure -text "" -command "" -state disabled -relief flat $w.t$i configure -relief flat -takefocus 0 -state normal $w.t$i delete 1.0 end $w.t$i configure -state disabled $w.t$i configure -background [lindex [$w.t$i configure -background] 3] $w.l$i configure -background [lindex [$w.l$i configure -background] 3] } for {set i $first} {$i <= $last} {incr i} { grid $w.l$i $w.t$i set d [expr $i-$first+1] $w.l$i configure -text $d -state normal -relief flat \ -command "ModifyDay $d $firstDay" $w.t$i configure -relief sunken -takefocus 1 -state normal $w.t$i delete 1.0 end $w.t$i configure -state disabled $w.t$i configure -background [lindex [$w.t$i configure -background] 3] $w.l$i configure -background [lindex [$w.l$i configure -background] 3] } set forgetIt 0 for {set i [expr $last+1]} {$i < 42} {incr i} { if {$i%7 == 0} { set forgetIt 1 } set row [expr ($i/7)*2+1] if {$forgetIt} { grid remove $w.l$i $w.t$i grid rowconfigure $w $row -weight 0 grid rowconfigure $w [expr $row+1] -weight 0 } else { grid rowconfigure $w $row -weight 1 grid rowconfigure $w [expr $row+1] -weight 1 } $w.l$i configure -text "" -command "" -state disabled -relief flat $w.t$i configure -relief flat -takefocus 0 -state normal $w.t$i delete 1.0 end $w.t$i configure -state disabled $w.t$i configure -background [lindex [$w.t$i configure -background] 3] $w.l$i configure -background [lindex [$w.l$i configure -background] 3] } if { $CurMonth == $TodayMonth && $CurYear == $TodayYear } { set n [expr $TodayDay + $offset] $w.l$n configure -background "#00c0c0" } } #--------------------------------------------------------------------------- # CreateCalWindow -- create the calendar window. # Arguments: # dayNames -- names of weekdays in current language {Sun .. Sat} #--------------------------------------------------------------------------- proc CreateCalWindow { dayNames } { global Option frame .h label .h.title -text "" -justify center -pady 2 -relief raised pack .h.title -side top -fill x pack .h -side top -expand 0 -fill x CreateCalFrame .cal $dayNames pack .cal -side top -fill both -expand 1 frame .b button .b.prev -text {<-} -command {MoveMonth -1} button .b.this -text {Today} -command {ThisMonth} button .b.next -text {->} -command {MoveMonth 1} button .b.goto -text {Go To Date...} -command {GotoDialog} button .b.print -text {Print...} -command {DoPrint} button .b.quit -text {Quit} -command {Quit} button .b.options -text {Options...} -command EditOptions label .b.status -text "" -width 25 -relief sunken label .b.nqueued -text "" -width 20 -relief sunken pack .b.prev .b.this .b.next .b.goto .b.print .b.options .b.quit -side left -fill x pack .b.status -side left -fill x -expand 1 pack .b.nqueued -side left -fill x pack .b -side top -fill x -expand 0 wm title . "TkRemind" wm iconname . "" wm protocol . WM_DELETE_WINDOW Quit wm deiconify . if {$Option(StartIconified)} { wm iconify . } update grid propagate .cal 0 } #*********************************************************************** # %PROCEDURE: EditOptions # %ARGUMENTS: # None # %RETURNS: # Nothing # %DESCRIPTION: # Lets user edit options #*********************************************************************** proc EditOptions {} { global Option tmpOpt # Make a working copy of current option set foreach name [array names Option] { set tmpOpt($name) $Option($name) } set w .opt catch { destroy $w } toplevel $w wm title $w "TkRemind Options" wm iconname $w "Options" frame $w.f frame $w.b pack $w.f -side top -expand 1 -fill both pack $w.b -side top -expand 0 -fill x # Start iconified checkbutton $w.startIconified -text "Start up Iconified" \ -anchor w -justify left \ -variable tmpOpt(StartIconified) # 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.sep1 -border 1 -relief sunken frame $w.sep2 -border 1 -relief sunken checkbutton $w.feed \ -text "Feed popped-up reminder to command's standard input" \ -variable tmpOpt(FeedReminder) -anchor w -justify left pack $w.startIconified -in $w.f -side top -expand 0 -fill x pack $w.confirmQuit -in $w.f -side top -expand 0 -fill x pack $w.bringDown -in $w.f -side top -expand 0 -fill x pack $w.ring -in $w.f -side top -expand 0 -fill x pack $w.deic -in $w.f -side top -expand 0 -fill x pack $w.sep1 -in $w.f -side top -expand 0 -fill x -ipady 1 pack $w.rf -in $w.f -side top -expand 0 -fill x pack $w.feed -in $w.f -side top -expand 0 -fill x pack $w.sep2 -in $w.f -side top -expand 0 -fill x -ipady 1 button $w.apply -text "Apply Options" -command "ApplyOptions $w; destroy $w" button $w.save -text "Save Options" -command "SaveOptions $w; destroy $w" button $w.cancel -text "Cancel" -command "destroy $w" pack $w.apply $w.save $w.cancel -in $w.b -side left -expand 0 -fill x CenterWindow $w } #*********************************************************************** # %PROCEDURE: ApplyOptions # %ARGUMENTS: # w -- edit options window path # %RETURNS: # Nothing # %DESCRIPTION: # Applies options set in the edit options box. #*********************************************************************** proc ApplyOptions { w } { global Option tmpOpt set tmpOpt(RunCmd) [$w.cmd get] # Copy working copy to real option set foreach name [array names tmpOpt] { set Option($name) $tmpOpt($name) } } #*********************************************************************** # %PROCEDURE: SaveOptions # %ARGUMENTS: # w -- edit options window path # %RETURNS: # Nothing # %DESCRIPTION: # Saves options in $HOME/.tkremindrc #*********************************************************************** proc SaveOptions { w } { global Option OptDescr ApplyOptions $w set problem [catch {set f [open ~/.tkremindrc "w"]} err] if {$problem} { tk_dialog .error Error "Can't write ~/.tkremindrc: $err" 0 OK return } puts $f "# TkRemind option file -- created automatically" puts $f "# [clock format [clock seconds]]" puts $f "# Format of each line is 'key value' where 'key'" puts $f "# specifies the option name, and 'value' is a" puts $f "# *legal Tcl list element* specifying the option value." foreach name [lsort [array names Option]] { puts $f "" puts $f "# $OptDescr($name)" puts $f [list $name $Option($name)] } close $f } #*********************************************************************** # %PROCEDURE: LoadOptions # %ARGUMENTS: # None # %RETURNS: # Nothing # %DESCRIPTION: # Loads options from ~/.tkremindrc #*********************************************************************** proc LoadOptions {} { global Option set problem [catch {set f [open "~/.tkremindrc" "r"]}] if {$problem} { return } while {[gets $f line] >= 0} { if {[string match "#*" $line]} { continue } if {$line == ""} { continue } foreach {key val} $line {} if {![info exists Option($key)]} { puts "Unknown option in ~/.tkremindrc: $key" continue } set Option($key) $val } close $f } #*********************************************************************** # %PROCEDURE: ConfigureCalWindow # %ARGUMENTS: # month -- month name # year -- the year # firstDay -- first day in month # numDays -- number of days in month # %RETURNS: # Nothing # %DESCRIPTION: # Configures the calendar window for a month and year # %PRECONDITIONS: # Any preconditions # %POSTCONDITIONS: # Any postconditions # %SIDE EFFECTS: # Any side effects #*********************************************************************** proc ConfigureCalWindow { month year firstDay numDays } { .h.title configure -text "$month $year" wm title . "TkRemind - $month $year" wm iconname . "$month $year" ConfigureCalFrame .cal $firstDay $numDays } #--------------------------------------------------------------------------- # FillCalWindow -- Fill in the calendar for global CurMonth and CurYear. #--------------------------------------------------------------------------- proc FillCalWindow {} { global DayNames CurYear CurMonth MonthNames CommandLine Status "Firing off Remind..." set month [lindex $MonthNames $CurMonth] set file [open "$CommandLine $month $CurYear" r] # Look for # rem2ps begin line while { [gets $file line] >= 0 } { if { [string compare "$line" "# rem2ps begin"] == 0 } { break } } if { [string compare "$line" "# rem2ps begin"] != 0 } { Status "Problem reading results from Remind!" after 5000 DisplayTime catch { close $file } return 0 } # Read month name, year, number of days in month, first weekday, Mon flag gets $file line regexp {^([^ ]*) ([0-9][0-9][0-9][0-9]) ([0-9][0-9]?) ([0-9]) ([0-9])} $line dummy monthName year daysInMonth firstWkday mondayFirst # Skip day names -- we already have them gets $file line ConfigureCalWindow $monthName $year $firstWkday $daysInMonth set offset [CalEntryOffset $firstWkday] while { [gets $file line] >= 0 } { if { [regexp {^([0-9][0-9][0-9][0-9])/([0-9][0-9])/([0-9][0-9]) +([^ ]+) +([^ ]+) +[^ ]+ +[^ ]+(.*)} $line all year month day type tag stuff] == 0 } { continue } set day [string trimleft $day 0] set n [expr $day+$offset] set month [string trimleft $month 0] switch -exact -- $type { "SHADE" { DoShadeSpecial $n $stuff continue } "MOON" { DoMoonSpecial $n $stuff continue } } if { $type != "*"} { continue } .cal.t$n configure -state normal if { [string length [string trim [.cal.t$n get 1.0]]] != 0} { .cal.t$n insert end " .....\n" } if {[regexp {TKTAG([0-9])+} $tag all tagno]} { .cal.t$n insert end [string trim $stuff] "TAGGED TKTAG$tagno" } else { .cal.t$n insert end [string trim $stuff] } .cal.t$n insert end "\n" .cal.t$n configure -state disabled } set problem [catch { close $file } errmsg] if {$problem} { tk_dialog .error Error "There was a problem running Remind: $errmsg" error 0 OK exit 1 } DisplayTime } #--------------------------------------------------------------------------- # MoveMonth -- move by +1 or -1 months # Arguments: # delta -- +1 or -1 -- months to move. #--------------------------------------------------------------------------- proc MoveMonth {delta} { global CurMonth CurYear set CurMonth [expr $CurMonth + $delta] if {$CurMonth < 0} { set CurMonth 11 set CurYear [expr $CurYear-1] } if {$CurMonth > 11} { set CurMonth 0 incr CurYear } FillCalWindow } #--------------------------------------------------------------------------- # ThisMonth -- move to current month #--------------------------------------------------------------------------- proc ThisMonth {} { global CurMonth CurYear TodayMonth TodayYear # Do nothing if already there if { $CurMonth == $TodayMonth && $CurYear == $TodayYear } { return 0; } set CurMonth $TodayMonth set CurYear $TodayYear FillCalWindow } #--------------------------------------------------------------------------- # Status -- set status string # Arguments: # stuff -- what to set string to. #--------------------------------------------------------------------------- proc Status { stuff } { catch { .b.status configure -text $stuff } update idletasks } #--------------------------------------------------------------------------- # DoPrint -- print a calendar # Arguments: # None #--------------------------------------------------------------------------- proc DoPrint {} { global PrintDest PrintSize PrintOrient PrintFill PrintStatus Rem2PS PSCmd global CurMonth CurYear MonthNames catch {destroy .p} toplevel .p wm title .p "TkRemind Print..." wm iconname .p "Print..." frame .p.f1 -relief sunken -border 2 frame .p.f11 frame .p.f12 frame .p.f2 -relief sunken -border 2 frame .p.f3 -relief sunken -border 2 frame .p.f4 radiobutton .p.tofile -text "To file: " -variable PrintDest -value file entry .p.filename button .p.browse -text "Browse..." -command PrintFileBrowse radiobutton .p.tocmd -text "To command: " -variable PrintDest -value command entry .p.command .p.command insert end "lpr" label .p.size -text "Paper Size:" radiobutton .p.letter -text "Letter" -variable PrintSize -value letter radiobutton .p.a4 -text "A4" -variable PrintSize -value a4 label .p.orient -text "Orientation:" radiobutton .p.landscape -text "Landscape" -variable PrintOrient -value landscape radiobutton .p.portrait -text "Portrait" -variable PrintOrient -value portrait checkbutton .p.fill -text "Fill page" -variable PrintFill button .p.print -text "Print" -command {set PrintStatus print} button .p.cancel -text "Cancel" -command {set PrintStatus cancel} pack .p.f1 .p.f2 .p.f3 -side top -fill both -expand 1 -anchor w pack .p.fill -side top -anchor w -fill none -expand 0 pack .p.f4 -side top -fill both -expand 1 -anchor w pack .p.f11 .p.f12 -in .p.f1 -side top -fill none -expand 0 -anchor w pack .p.tofile .p.filename .p.browse -in .p.f11 -side left -fill none -expand 0 -anchor w pack .p.tocmd .p.command -in .p.f12 -side left -fill none -expand 0 -anchor w pack .p.size .p.letter .p.a4 -in .p.f2 -side top -fill none -expand 0 -anchor w pack .p.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 ".p.cancel flash; .p.cancel invoke" bind .p ".p.print flash; .p.print invoke" set PrintStatus 2 CenterWindow .p tkwait visibility .p set oldFocus [focus] focus .p.filename grab .p tkwait variable PrintStatus catch {focus $oldFocus} set fname [.p.filename get] set cmd [.p.command get] destroy .p if {$PrintStatus == "cancel"} { return } if {$PrintDest == "file"} { if {$fname == ""} { tk_dialog .error Error "No filename specified" error 0 Ok return } if {[file isdirectory $fname]} { tk_dialog .error Error "$fname is a directory" error 0 Ok return } if {[file readable $fname]} { set ans [tk_dialog .error Overwrite? "Overwrite $fname?" question 0 No Yes] if {$ans == 0} { return } } set fname "> $fname" } else { set fname "| $cmd" } # Build the command line set cmd "$PSCmd 1 [lindex $MonthNames $CurMonth] $CurYear | $Rem2PS -c3" if {$PrintSize == "letter"} { append cmd " -m Letter" } else { append cmd " -m A4" } if {$PrintOrient == "landscape"} { append cmd " -l" } if {$PrintFill} { append cmd " -e" } append cmd " $fname" Status "Printing..." if {[catch {eval "exec $cmd"} err]} { tk_dialog .error Error "Error during printing: $err" error 0 Ok } DisplayTime } #--------------------------------------------------------------------------- # PrintFileBrowse -- browse for a filename for Print dialog # Arguments: none #--------------------------------------------------------------------------- proc PrintFileBrowse {} { set ans [BrowseForFile .filebrowse "Print to file..." "Ok" 0 "*.ps"] if {$ans != ""} { .p.filename delete 0 end .p.filename insert end $ans .p.filename icursor end .p.filename xview end } } #--------------------------------------------------------------------------- # GotoDialog -- Do the "Goto..." dialog #--------------------------------------------------------------------------- proc GotoDialog {} { global CurMonth MonthNames CurYear catch { destroy .g } set month [lindex $MonthNames $CurMonth] toplevel .g wm title .g "Go To Date" menubutton .g.mon -text "$month" -menu .g.mon.menu -relief raised menu .g.mon.menu -tearoff 0 foreach m $MonthNames { .g.mon.menu add command -label $m -command ".g.mon configure -text $m" } frame .g.y label .g.y.lab -text "Year: " entry .g.y.e -width 4 .g.y.e insert end $CurYear bind .g.y.e ".g.b.go flash; .g.b.go invoke" frame .g.b button .g.b.go -text "Go" -command {DoGoto} button .g.b.cancel -text "Cancel" -command { destroy .g } pack .g.b.go .g.b.cancel -expand 1 -fill x -side left pack .g.mon -fill x -expand 1 pack .g.y.lab -side left pack .g.y.e -side left -fill x -expand 1 pack .g.y -expand 1 -fill x pack .g.b -expand 1 -fill x bind .g ".g.b.cancel flash; .g.b.cancel invoke" CenterWindow .g set oldFocus [focus] grab .g focus .g.y.e tkwait window .g catch {focus $oldFocus} } #--------------------------------------------------------------------------- # DoGoto -- go to specified date #--------------------------------------------------------------------------- proc DoGoto {} { global CurYear CurMonth MonthNames set year [.g.y.e get] if { ! [regexp {^[0-9]+$} $year] } { tk_dialog .error Error {Illegal year specified (1990-2078)} error 0 Ok return } if { $year < 1990 || $year > 2078 } { tk_dialog .error Error {Illegal year specified (1990-2078)} error 0 Ok return } set month [lsearch -exact $MonthNames [.g.mon cget -text]] set CurMonth $month set CurYear $year destroy .g FillCalWindow } #--------------------------------------------------------------------------- # Quit -- handle the Quit button #--------------------------------------------------------------------------- proc Quit {} { global Option if { !$Option(ConfirmQuit) } { destroy . StopBackgroundRemindDaemon exit } if { [tk_dialog .question "Confirm..." {Really quit?} question 0 No Yes] } { destroy . StopBackgroundRemindDaemon exit } } #--------------------------------------------------------------------------- # CreateModifyDialog -- create dialog for adding a reminder # Arguments: # w -- path of parent window # day -- day number of month # firstDay -- day number of first day of month # args -- buttons to add to bottom frame. First sets result to 1, next # to 2, and so on. FIRST BUTTON MUST BE "Cancel" #--------------------------------------------------------------------------- proc CreateModifyDialog {w day firstDay args} { # Set up: Year, Month, Day, WeekdayName global CurYear CurMonth EnglishDayNames MonthNames OptionType SkipType global ModifyDialogResult set OptionType 1 set SkipType 1 set year $CurYear set month [lindex $MonthNames $CurMonth] set wkday [lindex $EnglishDayNames [expr ($day+$firstDay-1) % 7]] frame $w.o -border 4 -relief ridge frame $w.o1 -border 4 frame $w.o2 -border 4 frame $w.o3 -border 4 frame $w.exp -border 4 frame $w.adv -border 4 frame $w.weekend -border 4 frame $w.time -border 4 frame $w.hol -border 4 frame $w.msg frame $w.buttons pack $w.o1 $w.o2 $w.o3 -side top -anchor w -in $w.o pack $w.o $w.exp $w.adv $w.weekend $w.time $w.hol $w.msg -side top -anchor w -pady 4 -expand 1 -fill both pack $w.buttons -side top -anchor w -pady 4 -expand 1 -fill x # TYPE 1 REMINDER radiobutton $w.type1 -variable OptionType -value 1 menubutton $w.day1 -text $day -relief raised -menu $w.day1.menu CreateDayMenu $w.day1 menubutton $w.mon1 -text $month -relief raised -menu $w.mon1.menu CreateMonthMenu $w.mon1 menubutton $w.year1 -text $year -relief raised -menu $w.year1.menu CreateYearMenu $w.year1 checkbutton $w.repbut -text "and repeating every" $w.repbut deselect menubutton $w.repdays -text 1 -relief raised -menu $w.repdays.menu CreateDayMenu $w.repdays 1 28 0 label $w.label1a -text "day(s) thereafter" pack $w.type1 $w.day1 $w.mon1 $w.year1 $w.repbut $w.repbut $w.repdays $w.label1a -side left -anchor w -in $w.o1 # TYPE 2 REMINDER radiobutton $w.type2 -variable OptionType -value 2 label $w.label2a -text First menubutton $w.wkday2 -text $wkday -relief raised -menu $w.wkday2.menu CreateWeekdayMenu $w.wkday2 label $w.label2b -text "on or after" menubutton $w.day2 -text $day -relief raised -menu $w.day2.menu CreateDayMenu $w.day2 1 31 0 menubutton $w.mon2 -text $month -relief raised -menu $w.mon2.menu CreateMonthMenu $w.mon2 menubutton $w.year2 -text $year -relief raised -menu $w.year2.menu CreateYearMenu $w.year2 pack $w.type2 $w.label2a $w.wkday2 $w.label2b $w.day2 $w.mon2 $w.year2 -side left -anchor w -in $w.o2 # TYPE 3 REMINDER if { $day <= 7 } { set which "First" } elseif {$day <= 14} { set which "Second" } elseif {$day <= 21} { set which "Third" } elseif {$day <= 28} { set which "Fourth" } else { set which "Last" } radiobutton $w.type3 -variable OptionType -value 3 menubutton $w.ordinal -text $which -relief raised -menu $w.ordinal.menu menu $w.ordinal.menu -tearoff 0 $w.ordinal.menu add command -label "First" -command "$w.ordinal configure -text First" $w.ordinal.menu add command -label "Second" -command "$w.ordinal configure -text Second" $w.ordinal.menu add command -label "Third" -command "$w.ordinal configure -text Third" $w.ordinal.menu add command -label "Fourth" -command "$w.ordinal configure -text Fourth" $w.ordinal.menu add command -label "Last" -command "$w.ordinal configure -text Last" $w.ordinal.menu add command -label "Every" -command "$w.ordinal configure -text Every" menubutton $w.wkday3 -text $wkday -relief raised -menu $w.wkday3.menu CreateWeekdayMenu $w.wkday3 label $w.label3 -text "in" menubutton $w.mon3 -text $month -relief raised -menu $w.mon3.menu CreateMonthMenu $w.mon3 menubutton $w.year3 -text $year -relief raised -menu $w.year3.menu CreateYearMenu $w.year3 pack $w.type3 $w.ordinal $w.wkday3 $w.label3 $w.mon3 $w.year3 -side left -anchor w -in $w.o3 # EXPIRY DATE checkbutton $w.expbut -text "Expire after" $w.expbut deselect menubutton $w.expday -text $day -relief raised -menu $w.expday.menu CreateDayMenu $w.expday 1 31 0 menubutton $w.expmon -text $month -relief raised -menu $w.expmon.menu CreateMonthMenu $w.expmon 0 menubutton $w.expyear -text $year -relief raised -menu $w.expyear.menu CreateYearMenu $w.expyear 0 pack $w.expbut $w.expday $w.expmon $w.expyear -side left -anchor w -in $w.exp # ADVANCE NOTICE checkbutton $w.advbut -text "Issue" $w.advbut deselect menubutton $w.advdays -text 3 -menu $w.advdays.menu -relief raised CreateDayMenu $w.advdays 1 10 0 label $w.advlab -text "day(s) in advance" checkbutton $w.advcount -text "not counting holidays/weekend" $w.advcount select pack $w.advbut $w.advdays $w.advlab $w.advcount -side left -anchor w -in $w.adv # WEEKEND label $w.weeklab -text "Weekend is: " pack $w.weeklab -side left -anchor w -in $w.weekend foreach d $EnglishDayNames { checkbutton $w.d$d -text $d $w.d$d deselect pack $w.d$d -side left -anchor w -in $w.weekend } $w.dSaturday select $w.dSunday select # TIMED REMINDER checkbutton $w.timebut -text "Timed reminder at" $w.timebut deselect menubutton $w.timehour -text "12" -menu $w.timehour.menu -relief raised CreateDayMenu $w.timehour 1 12 0 menubutton $w.timemin -text "00" -menu $w.timemin.menu -relief raised menu $w.timemin.menu -tearoff 0 $w.timemin.menu add command -label "00" -command "$w.timemin configure -text {00}" $w.timemin.menu add command -label "15" -command "$w.timemin configure -text {15}" $w.timemin.menu add command -label "30" -command "$w.timemin configure -text {30}" $w.timemin.menu add command -label "45" -command "$w.timemin configure -text {45}" menubutton $w.ampm -text "PM" -menu $w.ampm.menu -relief raised menu $w.ampm.menu -tearoff 0 $w.ampm.menu add command -label "AM" -command "$w.ampm configure -text {AM}" $w.ampm.menu add command -label "PM" -command "$w.ampm configure -text {PM}" checkbutton $w.timeadvbut -text "with" $w.timeadvbut deselect menubutton $w.timeadv -text "15" -menu $w.timeadv.menu -relief raised menu $w.timeadv.menu -tearoff 0 foreach i {5 10 15 30 45 60} { $w.timeadv.menu add command -label $i -command "$w.timeadv configure -text $i" } label $w.timelab1 -text "minutes advance notice" checkbutton $w.timerepbut -text "repeated every" $w.timerepbut deselect menubutton $w.timerep -text "5" -menu $w.timerep.menu -relief raised menu $w.timerep.menu -tearoff 0 foreach i {3 5 10 15 30} { $w.timerep.menu add command -label $i -command "$w.timerep configure -text $i" } label $w.timelab2 -text "minutes" pack $w.timebut $w.timehour $w.timemin $w.ampm $w.timeadvbut $w.timeadv $w.timelab1 $w.timerepbut $w.timerep $w.timelab2 -side left -anchor w -in $w.time # SKIP TYPE label $w.labhol -text "On holidays or weekends:" radiobutton $w.issue -variable SkipType -value 1 -text "Issue reminder as usual" radiobutton $w.skip -variable SkipType -value 2 -text "Skip reminder" radiobutton $w.before -variable SkipType -value 3 -text "Move reminder before holiday or weekend" radiobutton $w.after -variable SkipType -value 4 -text "Move reminder after holiday or weekend" pack $w.labhol $w.issue $w.skip $w.before $w.after -side top -anchor w -in $w.hol # TEXT ENTRY label $w.msglab -text "Body:" entry $w.entry pack $w.msglab -side left -anchor w -in $w.msg pack $w.entry -side left -anchor w -expand 1 -fill x -in $w.msg # BUTTONS set nbut 0 foreach but $args { incr nbut button $w.but$nbut -text $but -command "set ModifyDialogResult $nbut" pack $w.but$nbut -side left -anchor w -in $w.buttons -expand 1 -fill x } bind $w "$w.but1 invoke" set ModifyDialogResult 0 # Center the window on the root CenterWindow $w } #*********************************************************************** # %PROCEDURE: RemindDialogToOptions # %ARGUMENTS: # w -- dialog window # %RETURNS: # A list of flag/value pairs representing the current state of # the "create reminder" dialog. #*********************************************************************** proc RemindDialogToOptions { w } { global OptionType SkipType repbut expbut advbut advcount global timebut timeadvbut timerepbut global dSaturday dSunday dMonday dTuesday dWednesday dThursday dFriday set ans {} lappend ans "-global-OptionType" $OptionType lappend ans "-global-SkipType" $SkipType foreach win [winfo children $w] { set winstem [winfo name $win] switch -exact -- [winfo class $win] { "Menubutton" { lappend ans "-text-$winstem" [$win cget -text] } "Checkbutton" { lappend ans "-global-$winstem" [eval set $winstem] } "Entry" { lappend ans "-entry-$winstem" [$win get] } } } return $ans } #*********************************************************************** # %PROCEDURE: OptionsToRemindDialog # %ARGUMENTS: # w -- Remind dialog window # opts -- option list set by RemindDialogToOptions # %RETURNS: # Nothing # %DESCRIPTION: # Sets parameters in the dialog box according to saved options. #*********************************************************************** proc OptionsToRemindDialog { w opts } { global OptionType SkipType repbut expbut advbut advcount global timebut timeadvbut timerepbut global dSaturday dSunday dMonday dTuesday dWednesday dThursday dFriday foreach {flag value} $opts { switch -glob -- $flag { "-text-*" { set win [string range $flag 6 end] $w.$win configure -text $value } "-global-*" { set win [string range $flag 8 end] set $win $value } "-entry-*" { set win [string range $flag 7 end] $w.$win delete 0 end $w.$win insert end $value } } } } #--------------------------------------------------------------------------- # CreateMonthMenu -- create a menu with all the months of the year # Arguments: # w -- menu button -- becomes parent of menu # every -- if true, include an "every month" entry #--------------------------------------------------------------------------- proc CreateMonthMenu {w {every 1}} { global MonthNames menu $w.menu -tearoff 0 if {$every} { $w.menu add command -label "every month" -command "$w configure -text {every month}" } foreach month $MonthNames { $w.menu add command -label $month -command "$w configure -text $month" } } #--------------------------------------------------------------------------- # CreateWeekdayMenu -- create a menu with all the weekdays # Arguments: # w -- menu button -- becomes parent of menu #--------------------------------------------------------------------------- proc CreateWeekdayMenu {w} { global EnglishDayNames menu $w.menu -tearoff 0 foreach d $EnglishDayNames { $w.menu add command -label $d -command "$w configure -text $d" } $w.menu add command -label "weekday" -command "$w configure -text weekday" } #--------------------------------------------------------------------------- # CreateDayMenu -- create a menu with entries 1-31 and possibly "every day" # Arguments: # w -- menu button -- becomes parent of menu # min -- minimum day to start from. # max -- maximum day to go up to # every -- if true, include an "every day" entry #--------------------------------------------------------------------------- proc CreateDayMenu {w {min 1} {max 31} {every 1}} { menu $w.menu -tearoff 0 if {$every} { $w.menu add command -label "every day" -command "$w configure -text {every day}" } set d $min while { $d <= $max } { $w.menu add command -label $d -command "$w configure -text $d" incr d } } #--------------------------------------------------------------------------- # CreateYearMenu -- create a menu with entries from this year to this year+10 # and possibly "every year" # Arguments: # w -- menu button -- becomes parent of menu # every -- if true, include an "every year" entry #--------------------------------------------------------------------------- proc CreateYearMenu {w {every 1}} { menu $w.menu -tearoff 0 if {$every} { $w.menu add command -label "every year" -command "$w configure -text {every year}" } global CurYear set d $CurYear while { $d < [expr $CurYear + 11] } { $w.menu add command -label $d -command "$w configure -text $d" incr d } } #--------------------------------------------------------------------------- # ModifyDay -- bring up dialog for adding reminder. # Arguments: # d -- which day to modify # firstDay -- first weekday in month (0-6) #--------------------------------------------------------------------------- proc ModifyDay {d firstDay} { global ModifyDialogResult AppendFile HighestTagSoFar ReminderTags catch {destroy .mod} toplevel .mod CreateModifyDialog .mod $d $firstDay "Cancel" "Add to reminder file" "Preview reminder" wm title .mod "TkRemind Add Reminder..." wm iconname .mod "Add Reminder" tkwait visibility .mod set oldFocus [focus] while {1} { grab .mod focus .mod.entry set ModifyDialogResult -1 tkwait variable ModifyDialogResult if {$ModifyDialogResult == 1} { catch {focus $oldFocus} destroy .mod return 0 } set problem [catch {set rem [CreateReminder .mod]} err] if {$problem} { tk_dialog .error Error "$err" error 0 Ok } else { if {$ModifyDialogResult == 3} { set rem [EditReminder $rem Cancel "Add reminder"] if {$ModifyDialogResult == 1} { continue } } set opts [RemindDialogToOptions .mod] catch {focus $oldFocus} destroy .mod Status "Writing reminder..." set f [open $AppendFile a] incr HighestTagSoFar set ReminderTags($HighestTagSoFar) 1 WriteReminder $f TKTAG$HighestTagSoFar $rem $opts close $f FillCalWindow RestartBackgroundRemindDaemon return 0 } } } #--------------------------------------------------------------------------- # CenterWindow -- center a window on the screen. Stolen from tk_dialog code # Arguments: # w -- window to center #--------------------------------------------------------------------------- proc CenterWindow {w} { wm withdraw $w update idletasks set x [expr [winfo screenwidth $w]/2 - [winfo reqwidth $w]/2 \ - [winfo vrootx [winfo parent $w]]] set y [expr [winfo screenheight $w]/2 - [winfo reqheight $w]/2 \ - [winfo vrooty [winfo parent $w]]] wm geom $w +$x+$y wm deiconify $w } #--------------------------------------------------------------------------- # CreateReminder -- create the reminder # Arguments: # w -- the window containing the add reminder dialog box. # Returns: # The reminder as a string. #--------------------------------------------------------------------------- proc CreateReminder {w} { global DidOmit set body [string trim [$w.entry get]] if {"$body" == ""} { error "Blank body in reminder" } set DidOmit 0 set needOmit 0 # Delegate the first part to CreateReminder1, CreateReminder2, or # CreateReminder3 global OptionType SkipType repbut expbut advbut advcount global timebut timeadvbut timerepbut set rem [CreateReminder$OptionType $w] # Do the "until" part if {$expbut} { append rem " UNTIL [$w.expday cget -text] [$w.expmon cget -text] [$w.expyear cget -text]" } # Advance warning if {$advbut} { append rem " +" if {!$advcount} { append rem "+" } else { set needOmit 1 } append rem [$w.advdays cget -text] } # Timed reminder if {$timebut} { set hour [$w.timehour cget -text] set min [$w.timemin cget -text] if {[$w.ampm cget -text] == "PM"} { if {$hour < 12} { incr hour 12 } } else { if {$hour == 12} { set hour 0 } } append rem " AT $hour:$min" if {$timeadvbut} { append rem " +[$w.timeadv cget -text]" } if {$timerepbut} { append rem " *[$w.timerep cget -text]" } } global SkipType if {$SkipType == 2} { append rem " SKIP" set needOmit 1 } elseif {$SkipType == 3} { append rem " BEFORE" set needOmit 1 } elseif {$SkipType == 4} { append rem " AFTER" set needOmit 1 } if {$needOmit && !$DidOmit} { append rem " OMIT [GetWeekend $w 1]" } # Check it out! global Remind set f [open "|$Remind -arq -e -" r+] puts $f "BANNER %" puts $f "$rem MSG %" puts $f "MSG %_%_%_%_" puts $f "FLUSH" flush $f set err {} catch {set err [gets $f]} catch {close $f} if {"$err" != ""} { # Clean up the message a bit regsub -- {^-stdin-\([0-9]*\): } $err {} err error "Error from Remind: $err" } append rem " MSG $body" return $rem } #--------------------------------------------------------------------------- # CreateReminder1 -- Create the first part of a type-1 reminder # Arguments: # w -- add reminder dialog window # Returns: first part of reminder #--------------------------------------------------------------------------- proc CreateReminder1 {w} { global repbut set rem "REM" set gotDay 0 set gotMon 0 set gotYear 0 set d [$w.day1 cget -text] if {"$d" != "every day"} { append rem " $d" set gotDay 1 } set m [$w.mon1 cget -text] if {"$m" != "every month"} { append rem " $m" set gotMon 1 } set y [$w.year1 cget -text] if {"$y" != "every year"} { append rem " $y" set gotYear 1 } # Check for repetition if {$repbut} { if {!$gotDay || !$gotMon || !$gotYear} { error "All components of a date must be specified if you wish to use the repeat feature." } append rem " *[$w.repdays cget -text]" } return $rem } #--------------------------------------------------------------------------- # CreateReminder2 -- Create the first part of a type-2 reminder # Arguments: # w -- add reminder dialog window # Returns: first part of reminder #--------------------------------------------------------------------------- proc CreateReminder2 {w} { set wkday [$w.wkday2 cget -text] if {"$wkday" == "weekday"} { set wkday [GetWeekend $w 0] } set day [$w.day2 cget -text] set mon [$w.mon2 cget -text] set year [$w.year2 cget -text] set rem "REM $wkday $day" if {$mon != "every month"} { append rem " $mon" } if {$year != "every year"} { append rem " $year" } return $rem } #--------------------------------------------------------------------------- # CreateReminder3 -- Create the first part of a type-3 reminder # Arguments: # w -- add reminder dialog window # Returns: first part of reminder #--------------------------------------------------------------------------- proc CreateReminder3 {w} { global MonthNames DidOmit set which [$w.ordinal cget -text] set day [$w.wkday3 cget -text] set mon [$w.mon3 cget -text] set year [$w.year3 cget -text] set rem "REM" if {$which != "Last"} { if {$which == "First"} { append rem " 1" } elseif {$which == "Second"} { append rem " 8" } elseif {$which == "Third"} { append rem " 15" } elseif {$which == "Fourth"} { append rem " 22" } if {$day != "weekday"} { append rem " $day" } else { append rem " [GetWeekend $w 0]" } if {$mon != "every month"} { append rem " $mon" } if {$year != "every year"} { append rem " $year" } } else { if {$day != "weekday"} { append rem " $day 1 --7" } else { append rem " 1 -1 OMIT [GetWeekend $w 1]" set DidOmit 1 } if {$mon != "every month"} { set i [lsearch -exact $MonthNames $mon] if {$i == 11} { set i 0 } else { incr i } append rem " [lindex $MonthNames $i]" } if {$year != "every year"} { if {$mon == "December"} { incr year } append rem " $year" } } return $rem } #--------------------------------------------------------------------------- # GetWeekend -- returns a list of weekdays or weekend days # Arguments: # w -- add reminder dialog window # wkend -- if 1, we want weekend. If 0, we want weekdays. # Returns: # list of weekdays or weekend-days #--------------------------------------------------------------------------- proc GetWeekend {w wkend} { global dSaturday dSunday dMonday dTuesday dWednesday dThursday dFriday global EnglishDayNames set ret {} foreach d $EnglishDayNames { set v [set d$d] if {$v == $wkend} { lappend ret $d } } return $ret } #--------------------------------------------------------------------------- # EditReminder -- allow user to edit what gets put in reminder file # Arguments: # rem -- current reminder # args -- buttons to add to bottom # Returns: # edited version of rem #--------------------------------------------------------------------------- proc EditReminder {rem args} { catch {destroy .edit} global ModifyDialogResult toplevel .edit wm title .edit "TkRemind Preview reminder" wm iconname .edit "Preview reminder" text .edit.t -width 80 -height 5 -relief sunken .edit.t insert end $rem frame .edit.f set n 0 foreach but $args { incr n button .edit.but$n -text $but -command "set ModifyDialogResult $n" pack .edit.but$n -in .edit.f -side left -fill x -expand 1 } pack .edit.t -side top -fill both -expand 1 pack .edit.f -side top -fill x -expand 1 bind .edit ".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 "$w.cancel flash; $w.cancel invoke" bind $w.list "$w.entry delete 0 end; $w.entry insert 0 \[selection get\]" bind $w.list "$w.ok flash; $w.ok invoke" bind $w.list "$w.entry delete 0 end; $w.entry insert 0 \[selection get\]; $w.ok flash; $w.ok invoke" bind $w.entry "$w.ok flash; $w.ok invoke" bind $w.filter "BrowseForFileRead $w" bind $w.entry "CompleteFile $w" bind $w.entry "ExpandFile $w" bindtags $w.entry "Entry $w.entry $w all" bindtags $w.list "Listbox $w.list $w all" CenterWindow $w set oldFocus [focus] tkwait visibility $w focus $w.entry set oldGrab [grab current $w] grab set $w WaitWinAttr $w status catch {focus $oldFocus} catch {grab set $oldGrab} set ans [GetWinAttr $w status] destroy $w return $ans } proc CompleteFile {w} { set index [lsearch [$w.list get 0 end] [$w.entry get]* ] if {$index > -1} { $w.list see $index $w.list selection clear 0 end $w.list selection set $index } } proc ExpandFile {w} { set stuff [$w.list curselection] if {[string compare $stuff ""]} { $w.entry delete 0 end $w.entry insert end [$w.list get $stuff] } } proc BrowseForFileCancel {w} { SetWinAttr $w status {} } proc BrowseForFileOK {w} { set fname [$w.entry get] if {[string compare $fname ""]} { # If it starts with a slash, handle it specially. if {[string match "/*" $fname]} { if {[file isdirectory $fname]} { BrowseForFileRead $w $fname return } else { SetWinAttr $w status $fname return } } if {[string match */ $fname]} { set fname [string trimright $fname /] } if {[$w.cwd cget -text] == "/"} { set fname "/$fname" } else { set fname "[$w.cwd cget -text]/$fname" } # If it's a directory, change directories if {[file isdirectory $fname]} { BrowseForFileRead $w $fname } else { SetWinAttr $w status $fname } } } #--------------------------------------------------------------------------- # BrowseForFileRead -- read the current directory into the file browser # Arguments: # w -- window name # dir -- directory # Returns: # nothing #--------------------------------------------------------------------------- proc BrowseForFileRead {w {dir ""}} { # Save working dir set cwd [pwd] if {$dir == ""} { set dir [$w.cwd cget -text] } if {[catch "cd $dir" err]} { tk_dialog .error Error "$err" error 0 Ok return } $w.cwd configure -text [pwd] if {[GetWinAttr $w showdots]} { set flist [glob -nocomplain .* *] } else { set flist [glob -nocomplain *] } set flist [lsort $flist] set filter [$w.filter get] if {$filter == ""} { set filter "*" } $w.list delete 0 end foreach item $flist { if {$item != "." && $item != ".."} { if {[file isdirectory $item]} { $w.list insert end "$item/" } else { if {[string match $filter $item]} { $w.list insert end $item } } } } if {[pwd] != "/"} { $w.list insert 0 "../" } cd $cwd $w.entry delete 0 end } #--------------------------------------------------------------------------- # StartBackgroundRemindDaemon # Arguments: # none # Returns: # nothing # Description: # Starts a background Remind daemon to handle timed reminders #--------------------------------------------------------------------------- proc StartBackgroundRemindDaemon {} { global Remind DaemonFile ReminderFile set problem [catch { set DaemonFile [open "|$Remind -z0 $ReminderFile" "r+"] } err] if {$problem} { tk_dialog .error Error "Can't start Remind daemon in background: $err" error 0 OK } else { fileevent $DaemonFile readable "DaemonReadable $DaemonFile" puts $DaemonFile "STATUS" flush $DaemonFile } } #--------------------------------------------------------------------------- # StopBackgroundRemindDaemon # Arguments: # none # Returns: # nothing # Description: # Stops the background Remind daemon #--------------------------------------------------------------------------- proc StopBackgroundRemindDaemon {} { global DaemonFile catch { puts $DaemonFile "EXIT" flush $DaemonFile close $DaemonFile } } #--------------------------------------------------------------------------- # RestartBackgroundRemindDaemon # Arguments: # none # Returns: # nothing # Description: # Restarts the background Remind daemon #--------------------------------------------------------------------------- proc RestartBackgroundRemindDaemon {} { global DaemonFile catch { puts $DaemonFile "REREAD" flush $DaemonFile } } #--------------------------------------------------------------------------- # DaemonReadable # Arguments: # file -- file channel that is readable # Returns: # nothing # Description: # Reads data from the Remind daemon and handles it appropriately #--------------------------------------------------------------------------- proc DaemonReadable { file } { global Ignore set line "" catch { set num [gets $file line] } if {$num < 0} { catch { close $file } return } switch -glob -- $line { "NOTE reminder*" { scan $line "NOTE reminder %s %s %s" time now tag IssueBackgroundReminder $file $time $now $tag } "NOTE newdate" { # Date has rolled over -- clear "ignore" list catch { unset Ignore} Initialize FillCalWindow } "NOTE reread" { puts $file "STATUS" flush $file } "NOTE queued*" { scan $line "NOTE queued %d" n if {$n == 1} { .b.nqueued configure -text "1 reminder queued" } else { .b.nqueued configure -text "$n reminders queued" } } default { puts "Unknown message from daemon: $line\n" } } } #--------------------------------------------------------------------------- # IssueBackgroundReminder # Arguments: # file -- file channel that is readable # time -- time of reminder # now -- current time according to Remind daemon # tag -- tag for reminder, or "*" if no tag # Returns: # nothing # Description: # Reads a background reminder from daemon and pops up window. #--------------------------------------------------------------------------- proc IssueBackgroundReminder { file time now tag } { global BgCounter Option Ignore if {$Option(Deiconify)} { wm deiconify . } set msg "" set line "" while (1) { gets $file line if {$line == "NOTE endreminder"} { break } if {$msg != ""} { append msg "\n"; } append msg $line } # Do nothing if it's blank -- was probably a RUN-type reminder. if {$msg == ""} { return } # Do nothing if user told us to ignore this reminder if {[info exists Ignore($tag)]} { return } incr BgCounter set w .bg$BgCounter toplevel $w wm iconname $w "Reminder" wm title $w "Timed reminder ($time)" label $w.l -text "Reminder for $time issued at $now" message $w.msg -width 6i -text $msg frame $w.b button $w.ok -text "OK" -command "destroy $w" if {$tag != "*"} { button $w.nomore -text "Don't remind me again" -command \ "destroy $w; set Ignore($tag) 1" } 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 -in $w.b -side left } CenterWindow $w # Automatically shut down window after a minute if option says so if {$Option(AutoClose)} { after 60000 "catch { destroy $w }" } update if {$Option(RingBell)} { bell } if {$Option(RunCmd) != ""} { if {$Option(FeedReminder)} { FeedReminderToCommand $Option(RunCmd) $msg } else { exec "/bin/sh" "-c" $Option(RunCmd) "&" } } # reread status if {$file != "stdin"} { puts $file "STATUS" flush $file } } #*********************************************************************** # %PROCEDURE: FeedReminderToCommand # %ARGUMENTS: # cmd -- command to execute # msg -- what to feed it # %RETURNS: # Nothing # %DESCRIPTION: # Feeds "$msg" to a command. #*********************************************************************** proc FeedReminderToCommand { cmd msg } { catch { set f [open "|$cmd" "w"] fconfigure $f -blocking 0 fileevent $f writable [list CommandWritable $f $msg] } } #*********************************************************************** # %PROCEDURE: CommandWritable # %ARGUMENTS: # f -- file which is writable # msg -- message to write # %RETURNS: # Nothing # %DESCRIPTION: # Writes $msg to $f; closes $f. #*********************************************************************** proc CommandWritable { f msg } { puts $f $msg flush $f close $f } proc main {} { global AppendFile HighestTagSoFar DayNames puts "\nTkRemind Copyright (c) 1996-1998 by David F. Skoll\n" LoadOptions CreateMoonImages Initialize ScanForTags $AppendFile CreateCalWindow $DayNames FillCalWindow StartBackgroundRemindDaemon DisplayTimeContinuously } #*********************************************************************** # %PROCEDURE: ScanForTags # %ARGUMENTS: # fname -- name of file to scan # %RETURNS: # Nothing # %DESCRIPTION: # Scans the file for all tags of the form "TKTAGnnnn" and builds # the tag array. Also adjusts HighestTagSoFar #*********************************************************************** proc ScanForTags { fname } { global HighestTagSoFar ReminderTags if {[catch { set f [open $fname "r"]}]} { return } while {[gets $f line] >= 0} { switch -regexp -- $line { {^# TKTAG[0-9]+} { regexp {^# TKTAG([0-9]+)} $line dummy tagno if {$tagno > $HighestTagSoFar} { set HighestTagSoFar $tagno } set ReminderTags($tagno) 1 } } } close $f } #*********************************************************************** # %PROCEDURE: ReadTaggedOptions # %ARGUMENTS: # tag -- tag to match # %RETURNS: # A list of options for the dialog box for the tagged reminder # %DESCRIPTION: # Scans the file for specified tag and returns the "options" list for the # reminder. #*********************************************************************** proc ReadTaggedOptions { tag } { global AppendFile if {[catch { set f [open $AppendFile "r"]}]} { return "" } while {[gets $f line] >= 0} { if {[string match "# $tag *" $line]} { gets $f line close $f return [string range $line 2 end] } } close $f return "" } #*********************************************************************** # %PROCEDURE: GetCurrentReminder # %ARGUMENTS: # w -- text window # %RETURNS: # The tag (TKTAGnnnn) for current editable reminder, or "" if no # current editable reminder. #*********************************************************************** proc GetCurrentReminder { w } { set tags [$w tag names current] set index [lsearch -glob $tags "TKTAG*"] if {$index < 0} { return "" } set tag [lindex $tags $index] return $tag } #*********************************************************************** # %PROCEDURE: TaggedEnter # %ARGUMENTS: # w -- text window # %RETURNS: # Nothing # %DESCRIPTION: # Highlights an "editable" reminder as mouse moves into it #*********************************************************************** proc TaggedEnter { w } { set tag [GetCurrentReminder $w] if {$tag != ""} { $w tag configure $tag -foreground #FF0000 } } #*********************************************************************** # %PROCEDURE: TaggedLeave # %ARGUMENTS: # w -- text window # %RETURNS: # Nothing # %DESCRIPTION: # Removes highlight from an "editable" reminder as mouse leaves it #*********************************************************************** proc TaggedLeave { w } { set tag [GetCurrentReminder $w] if {$tag != ""} { $w tag configure $tag -foreground #000000 } } #*********************************************************************** # %PROCEDURE: EditTaggedReminder # %ARGUMENTS: # w -- text window # %RETURNS: # Nothing # %DESCRIPTION: # Opens a dialog box to edit the current editable reminder #*********************************************************************** proc EditTaggedReminder { w } { global ModifyDialogResult set tag [GetCurrentReminder $w] if {$tag == ""} { return } # Read in options set opts [ReadTaggedOptions $tag] if {$opts == ""} { return } toplevel .mod CreateModifyDialog .mod 1 0 "Cancel" "Replace reminder" "Delete reminder" "Preview reminder" wm title .mod "TkRemind Edit Reminder..." wm iconname .mod "Edit Reminder" OptionsToRemindDialog .mod $opts tkwait visibility .mod set oldFocus [focus] while {1} { grab .mod focus .mod.entry set ModifyDialogResult -1 tkwait variable ModifyDialogResult if {$ModifyDialogResult == 1} { catch {focus $oldFocus} destroy .mod return 0 } set problem [catch {set rem [CreateReminder .mod]} err] if {$problem} { tk_dialog .error Error "$err" error 0 Ok continue } if {$ModifyDialogResult == 4} { set rem [EditReminder $rem "Cancel" "Replace reminder"] if {$ModifyDialogResult == 1} { continue } } set opts [RemindDialogToOptions .mod] catch {focus $oldFocus} destroy .mod set problem [catch { if {$ModifyDialogResult == 2} { ReplaceTaggedReminder $tag $rem $opts } else { DeleteTaggedReminder $tag } } err] if {$problem} { tk_dialog .error Error "Error: $err" error 0 Ok return 1 } FillCalWindow RestartBackgroundRemindDaemon return 0 } } #*********************************************************************** # %PROCEDURE: UniqueFileName # %ARGUMENTS: # stem -- base name of file # %RETURNS: # A filename of the form "stem.xxx" which does not exist #*********************************************************************** proc UniqueFileName { stem } { set n 1 while {[file exists $stem.$n]} { incr n } return $stem.$n } #*********************************************************************** # %PROCEDURE: DeleteTaggedReminder # %ARGUMENTS: # tag -- tag of reminder to delete # %RETURNS: # Nothing # %DESCRIPTION: # Deletes tagged reminder from reminder file #*********************************************************************** proc DeleteTaggedReminder { tag } { global AppendFile set tmpfile [UniqueFileName $AppendFile] set out [open $tmpfile "w"] set in [open $AppendFile "r"] set foundStart 0 set foundEnd 0 while {[gets $in line] >= 0} { if {[string match "# $tag *" $line]} { set foundStart 1 break } puts $out $line } if {! $foundStart} { close $in close $out file delete $tmpfile error "Did not find start of reminder with tag $tag" } while {[gets $in line] >= 0} { if { $line == "# TKEND"} { set foundEnd 1 break } } if {! $foundEnd} { close $in close $out file delete $tmpfile error "Did not find end of reminder with tag $tag" } while {[gets $in line] >= 0} { puts $out $line } close $in close $out file rename -force -- $tmpfile $AppendFile } #*********************************************************************** # %PROCEDURE: ReplaceTaggedReminder # %ARGUMENTS: # tag -- tag of reminder to replace # rem -- text to replace it with # opts -- edit options # %RETURNS: # Nothing # %DESCRIPTION: # Replaces a tagged reminder in the reminder file #*********************************************************************** proc ReplaceTaggedReminder { tag rem opts } { global AppendFile set tmpfile [UniqueFileName $AppendFile] set out [open $tmpfile "w"] set in [open $AppendFile "r"] set foundStart 0 set foundEnd 0 while {[gets $in line] >= 0} { if {[string match "# $tag *" $line]} { set foundStart 1 break } puts $out $line } if {! $foundStart} { close $in close $out file delete $tmpfile error "Did not find start of reminder with tag $tag" } # Consume the old reminder while {[gets $in line] >= 0} { if { $line == "# TKEND"} { set foundEnd 1 break } } if {! $foundEnd} { close $in close $out file delete $tmpfile error "Did not find end of reminder with tag $tag" } # Write the new reminder WriteReminder $out $tag $rem $opts # Copy rest of file over while {[gets $in line] >= 0} { puts $out $line } close $in close $out file rename -force -- $tmpfile $AppendFile } #*********************************************************************** # %PROCEDURE: WriteReminder # %ARGUMENTS: # out -- file to write to # tag -- reminder tag # rem -- reminder body # opts -- edit options # %RETURNS: # Nothing # %DESCRIPTION: # Writes a reminder to a file #*********************************************************************** proc WriteReminder { out tag rem opts } { puts $out "# $tag Next reminder was created with TkRemind. DO NOT EDIT" puts $out "# $opts" if {[string range $rem 0 3] == "REM "} { puts $out "REM TAG $tag [string range $rem 4 end]" } else { puts $out $rem } puts $out "# TKEND" } #*********************************************************************** # %PROCEDURE: DoShadeSpecial # %ARGUMENTS: # n -- calendar box to shade # stuff -- Remind command line # %RETURNS: # Nothing # %DESCRIPTION: # Handles the "SHADE" special -- shades a box. #*********************************************************************** proc DoShadeSpecial { n stuff } { set num [scan $stuff "%d %d %d" r g b] if {$num == 1} { set g $r set b $r } elseif {$num != 3} { return } if {$r < 0 || $r > 255 || $g < 0 || $g > 255 || $b < 0 || $b > 255} { return } set bg [format "#%02x%02x%02x" $r $g $b] .cal.t$n configure -background $bg } #*********************************************************************** # %PROCEDURE: DoMoonSpecial # %ARGUMENTS: # n -- calendar box for moon # stuff -- Remind command line # %RETURNS: # Nothing # %DESCRIPTION: # Handles the "MOON" special -- draws a moon symbol #*********************************************************************** proc DoMoonSpecial { n stuff } { set msg "" set num [scan $stuff "%d %d %d %s" phase junk1 junk2 msg] if {$num < 1} { return } if {$phase < 0 || $phase > 3} { return } switch -exact -- $phase { 0 { set image new } 1 { set image first } 2 { set image full } 3 { set image last } } .cal.t$n configure -state normal .cal.t$n image create end -image $image if {$msg != ""} { .cal.t$n insert end " $msg" } .cal.t$n insert end "\n" .cal.t$n configure -state disabled } #*********************************************************************** # %PROCEDURE: DisplayTime # %ARGUMENTS: # None # %RETURNS: # Nothing # %DESCRIPTION: # Displays current date and time in status window #*********************************************************************** proc DisplayTime {} { set msg [clock format [clock seconds] -format "%e %b %Y %I:%M%p"] Status $msg } #*********************************************************************** # %PROCEDURE: CreateMoonImages # %ARGUMENTS: # None # %RETURNS: # Nothing # %DESCRIPTION: # Creates the moon images "new", "first", "full" and "last" #*********************************************************************** proc CreateMoonImages {} { image create bitmap new -foreground black \ -data "#define newmoon_width 16\n#define newmoon_height 16\nstatic unsigned char newmoon_bits[] = {\n0x00, 0x00, 0xc0, 0x01, 0x30, 0x06, 0x0c, 0x18, 0x04, 0x10, 0x02, 0x20,\n0x02, 0x20, 0x01, 0x40, 0x01, 0x40, 0x01, 0x40, 0x02, 0x20, 0x02, 0x20,\n0x04, 0x10, 0x0c, 0x18, 0x30, 0x06, 0xc0, 0x01};" image create bitmap first -foreground black \ -data "#define firstquarter_width 16\n#define firstquarter_height 16\nstatic unsigned char firstquarter_bits[] = {\n0x00, 0x00, 0xc0, 0x01, 0xf0, 0x06, 0xfc, 0x18, 0xfc, 0x10, 0xfe, 0x20,\n0xfe, 0x20, 0xff, 0x40, 0xff, 0x40, 0xff, 0x40, 0xfe, 0x20, 0xfe, 0x20,\n0xfc, 0x10, 0xfc, 0x18, 0xf0, 0x06, 0xc0, 0x01};" image create bitmap full -foreground black \ -data "#define fullmoon_width 16\n#define fullmoon_height 16\nstatic unsigned char fullmoon_bits[] = {\n0x00, 0x00, 0xc0, 0x01, 0xf0, 0x07, 0xfc, 0x1f, 0xfc, 0x1f, 0xfe, 0x3f,\n0xfe, 0x3f, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0xfe, 0x3f, 0xfe, 0x3f,\n0xfc, 0x1f, 0xfc, 0x1f, 0xf0, 0x07, 0xc0, 0x01};" image create bitmap last -foreground black \ -data "#define lastquarter_width 16\n#define lastquarter_height 16\nstatic unsigned char lastquarter_bits[] = {\n0x00, 0x00, 0xc0, 0x01, 0xb0, 0x07, 0x8c, 0x1f, 0x84, 0x1f, 0x82, 0x3f,\n0x82, 0x3f, 0x81, 0x7f, 0x81, 0x7f, 0x81, 0x7f, 0x82, 0x3f, 0x82, 0x3f,\n0x84, 0x1f, 0x8c, 0x1f, 0xb0, 0x07, 0xc0, 0x01};" } #*********************************************************************** # %PROCEDURE: DisplayTimeContinuously # %ARGUMENTS: # None # %RETURNS: # Nothing # %DESCRIPTION: # Continuously displays current date and time in status window, # updating once a minute #*********************************************************************** proc DisplayTimeContinuously {} { DisplayTime set secs [clock format [clock seconds] -format "%S"] # Doh -- don't interpret as an octal number if leading zero scan $secs "%d" decSecs set decSecs [expr 60 - $decSecs] after [expr $decSecs * 1000] DisplayTimeContinuously } main # For debugging only... #fileevent stdin readable "DaemonReadable stdin" #Initialize #CreateCalWindow $DayNames #ConfigureCalWindow March 1998 0 31