"Fossies" - the Fresh Open Source Software Archive

Member "remind-03.03.09/scripts/tkremind" (15 Oct 2021, 121037 Bytes) of package /linux/misc/remind-03.03.09.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Bash source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. See also the last Fossies "Diffs" side-by-side code changes report for "tkremind": 03.03.07_vs_03.03.08.

    1 #!/bin/sh
    2 # -*-Mode: TCL;-*-
    3 
    4 #--------------------------------------------------------------
    5 #   TKREMIND
    6 #
    7 #   A cheesy graphical front/back end for Remind using Tcl/Tk
    8 #
    9 #   This file is part of REMIND.
   10 #   Copyright (C) 1992-2021 Dianne Skoll
   11 #
   12 #--------------------------------------------------------------
   13 
   14 # the next line restarts using wish \
   15 exec wish "$0" "$@"
   16 
   17 # We need at least version 8.5 because of {*} list expansion operator
   18 if {[catch {package require Tcl 8.5}]} {
   19     puts stderr "This program requires Tcl 8.5 or higher."
   20     puts stderr "You have version [info tclversion]"
   21     exit 1
   22 }
   23 
   24 # If it's 8.5, try using the Img or the TkPNG package to
   25 # get PNG support
   26 if {[info tclversion] == 8.5} {
   27     if {[catch {package require Img}]} {
   28         if {[catch {package require tkpng}]} {
   29             puts stderr "Tcl/Tk version 8.5 might require either the Img or tkpng"
   30             puts stderr "package to handle PNG images correctly.  TkRemind may"
   31             puts stderr "crash because neither of these packages was found."
   32         }
   33     }
   34 }
   35 wm withdraw .
   36 
   37 set Hostname [exec hostname]
   38 
   39 # Our icon photo
   40 catch {
   41     image create photo rpicon -data {
   42 R0lGODlhFwAgAOecABUTERYTERYUERcVEhgWExkXFBkXFRoXFRsZFhwZFxwa
   43 GB0bGR4cGR4cGh8dGiAeHCEfHCEfHSIgHSIgHiQiHyYkISknJCooJispJywq
   44 Jy4sKTIwLjUzMDUzMTo4Njs5Nzs5ODw7ODw7OT07OT48OkE/PUJAPkNBP0RC
   45 QEVDQUVEQkdFQ0lIRkpJR01LSU5MSlBPTVFQTlNSUFRSUFRSUVVTUlVUUllY
   46 VltZV1xaWF1cWmBfXmJgX2RiYGZlY2dmZGppZ2tqaG1ram9tbHFwb3Jwb3Rz
   47 cXV0c3Z0c3Z1c3Z1dHd1dHh2dXh3dnt5eHx7eXx7en18en59e4B/foGAf4KB
   48 f4SDgYWEgoWEg4eGhIiHhouKiI2Mio6Ni46NjJCQj5KRkJSTkZeWlpiXlpmY
   49 l5qZmJybmp6dnKCfnqGgoKKhoKOioaSjoqinp6qpqKurqq+urbCvrrCwr7Gw
   50 r7OysbW1tLi3tri3t7u6ur28vMTDw8TEw8XFxMbFxcfGxsfHxsrJycrKyczM
   51 y83My83MzM3NzdDQz9LR0dPS0tPT09fX19jY19ra2dvb29zc29zc3Ojn5+jo
   52 6Orq6uzs7O/v7/T09PX19fb29vf39/r6+vv7+/7+/v//////////////////
   53 ////////////////////////////////////////////////////////////
   54 ////////////////////////////////////////////////////////////
   55 ////////////////////////////////////////////////////////////
   56 ////////////////////////////////////////////////////////////
   57 ////////////////////////////////////////////////////////////
   58 ////////////////////////////////////////////////////////////
   59 /////////////////////yH5BAEKAP8ALAAAAAAXACAAAAj+AP8JHEiwoMGD
   60 CAcusRAAQEKDBQIcEBAAwUODAQJAsBGAwsWCBzJuUBLgI0ENGVM2dACg5UWV
   61 KU+Y/JfRQBknPoq8ATQz4wxOQIFa6vMx5ZSgQetczJDSClKgcF6mFDEnE9I2
   62 D0fADOChUdA1D7dmTBEUTditDQRQAnomIQaxICpoAmomoUoAGS2YIBIUDEIu
   63 YndI8FAJaBaEMlIuSEkloxugUBBOSLkh44AvGfkAPYJQpYqMLIQEILB205DO
   64 KW9kJHMhQAmgkaKgzsgjggM5GbEAxaNmdoAPOoz8CCAgEVAtg3wPEPMnQQAU
   65 QWsg5AAzDZSMbIBeaoHwAUwSDAI2XMAENA8ThAPEBvAStEkc3yonrOW0aUMk
   66 +BkBVAlaKATC8Fsp8Igid5ABgxMHtaTgggy6ZFBAADs=    }
   67 
   68     wm iconphoto . -default rpicon
   69 }
   70 
   71 # Left and right arrows
   72 image create photo leftarrow -data {
   73 iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAC3XpUWHRSYXcgcHJvZmlsZSB0eXBl
   74 IGV4aWYAAHja7ZdRkuMoDIbfOcUeAUkIieNgA1Vzgz3+/mDa6aR7pmpn52EfYioGK+IX6BNOd+h/
   75 /xjhL1xUJIak5rnkHHGlkgpXDDxe19VTTOt+Xefu6dke7i8YJkEv12Pu27/Cro8Jlrb9eLYH20Ls
   76 W+gj8haUGZkfK/EtJHzZaT+HsufV9Gk7+3O0bdOre31OhmQ0hZ5w4C4kEXefUQQrkCIVd8ZdZDnB
   77 WiUtC4l+n7twD1+Sd49echfrtstzKkLM2yG/5GjbSb/P3crQ5xXRI/LTFyZ3iC+5G6P5GP3aXU0Z
   78 mcphb+pjK2sExwOplDUtoxk+irGtVtAcWzxBrIHmgXYGKsTI9qBEjSoN6qs/6cQSE3c29Mwny7K5
   79 GBc+F5Q0Gw024GlBHDROUJPJ5V4LrbhlxTvJEbkRPJkgNol+aeE74++0W2iMWbpE0e9cYV08axrL
   80 mOTmHV4AQmPnVFd+Vwuf6iZ+AisgqCvNjg3WeFwSh9KjtmRxFvhpTCFeR4OsbQGkCLEViyEBgZhR
   81 zJQpGrMRIY8OPhUrZ0l8gACpcqMwwEYkA47zjI05RsuXlS8zXi0AoZLFgAYHCLBSUtSPJUcNVRVN
   82 QVWzmroWrVlyyppztjzfUdXEkqllM3MrVl08uXp2c/fitXARvMK05GKheCmlVgStkK6YXeFR68GH
   83 HOnQIx92+FGOeqJ8znTqmU87/Sxnbdyk4fi33Cw0b6XVTh2l1FPXnrt176XXgVobMtLQkYcNH2XU
   84 m9qm+kyNXsj9mhptapNYWn72oAaz2YcEzdeJTmYgxolA3CYBFDRPZtEpJZ7kJrNYGIdCGdRIJ5xG
   85 kxgIpk6sg252D3K/5BY0/Stu/DNyYaL7E+TCRLfJfeX2DbVW1y+KLEDzFM6cRhl4scGhe2Wv8zfp
   86 t/vwXwXeQm+ht9Bb6C30FnoL/X+EBv54wL+a4R9jg5HlHbplXQAAAAZiS0dEAP8A/wD/oL2nkwAA
   87 AAlwSFlzAAAOxAAADsQBlSsOGwAAAAd0SU1FB+MMHwEqD2+Y0soAAABKSURBVCjPY2DADuoZSAC1
   88 DAwM/0lVjFMDI5riJiR+Gz71yCYTwgx1JCj+z8TAwPCRgQyQT6wNjGiaJqDx8QUShk0kO48kUIhL
   89 AgCfBSz4satMtQAAAABJRU5ErkJggg==
   90 }
   91 
   92 image create photo rightarrow -data {
   93 iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAC3HpUWHRSYXcgcHJvZmlsZSB0eXBl
   94 IGV4aWYAAHja7ZdRkuMoDIbfOcUeAUkIieNgA1Vzgz3+/mDa6aR7pmpn52EfYipgK7IE/ydId+h/
   95 /xjhL1xUJIak5rnkHHGlkgpX3Hi8rmukmFZ/Xece6dke7i8YJsEo12Pu27/Cro8XLG378WwPtgOx
   96 70AfmXdAmZn5MRPfgYQvO+3nUPZ7NX1azv4cbdv0Gl6fk0GMpognHLgLSUTvM4tgBlKkomf0IssJ
   97 1ippW+R77cJ9+yLeffeiXazbLs9ShJi3Q37RaNtJv9duKfR5RvTI/PSFyZ3ii3ZjNB+jX6urKUOp
   98 HPaiPpay7uB4QMpLjYxm+CjubbWC5ljiCWINNA+0M1AhhtqDEjWqNKiv8aQTU0zc2TAynyzL5mJc
   99 +FxQ0mw02ICnBXHQOEFNJpd7LrTylpXvJEfmRvBkQrBJ9EsL3xl/p92BxpilSxT91grz4lnTmMYk
  100 N3t4AQiNrakufVcLn+omfgIrIKhLZscCazyuEIfSo7ZkcRb4aUwhXluDrO0AkAi5FZMhAYGYSZQy
  101 RWM2Iujo4FMxc5bEBwiQKjcKA2xEMuA4z9x4x2j5svJlxtECECpZDGiwgQArJUX9WHLUUFXRFFQ1
  102 q6lr0Zolp6w5Z8vzjKomlkwtm5lbseriydWzm7sXr4WL4AjTkouF4qWUWpG0InTF2xUetR58yJEO
  103 PfJhhx/lqCfK50ynnvm0089y1sZNGrZ/y81C81Za7dRRSj117blb9156Hai1ISMNHXnY8FFGvalt
  104 qs/U6IXcr6nRpjaJpeVnD2owm32EoHmc6GQGYpwIxG0SQEHzZBadUuJJbjKLhbEplEGNdMJpNImB
  105 YOrEOuhm9yD3S25B07/ixj8jFya6P0EuTHSb3Fdu31Brdf2iyAI0d+HUNMrAwQaH7pW9zt+k3x7D
  106 fw3wDvQO9A70DvQO9A70DvT/CTTwxwP+1Qz/AGOHkeWpKJljAAAABmJLR0QA/wD/AP+gvaeTAAAA
  107 CXBIWXMAAA7EAAAOxAGVKw4bAAAAB3RJTUUH4wwfASohs07fBQAAAFhJREFUKM9jYMAN6hlIBP8Z
  108 GBhqSdWAoYmFgYGhDSqBCzRB6WZ0kwjhWpgNxIImBgYGRiYSA+IjKU7KZ2BgYGCEMrB5eiISuwCN
  109 jzdY80mJh3xSPFiITRAA3qskpC7RIkIAAAAASUVORK5CYII=
  110 }
  111 proc missing_tcllib { pkg } {
  112     catch { puts stderr "Could not find the '$pkg' package -- you must install tcllib.\nPlease see http://tcllib.sourceforge.net/" }
  113     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
  114     exit 1
  115 }
  116 if {[catch {package require mime}]} {
  117     missing_tcllib mime
  118 }
  119 
  120 if {[catch {package require smtp}]} {
  121     missing_tcllib smtp
  122 }
  123 if {[catch {package require json}]} {
  124     missing_tcllib json
  125 }
  126 
  127 if {$tcl_platform(platform) == "windows"} {
  128     tk_dialog .error Error "Please do not port Remind to Windows" error 0 OK
  129     exit 1
  130 }
  131 
  132 #---------------------------------------------------------------------------
  133 # GLOBAL VARIABLES
  134 #---------------------------------------------------------------------------
  135 
  136 set Option(ConfirmQuit) 0
  137 set OptDescr(ConfirmQuit) "(0/1) If 1, TkRemind prompts you to confirm 'Quit' operation"
  138 set Option(AutoClose) 1
  139 set OptDescr(AutoClose) "(0/1) If 1, TkRemind automatically closes pop-up reminders after a minute"
  140 set Option(RingBell) 0
  141 set OptDescr(RingBell) "(0/1) If 1, TkRemind beeps the terminal when a pop-up reminder appears"
  142 
  143 set Option(StartIconified) 0
  144 set OptDescr(StartIconified) "(0/1) If 1, TkRemind starts up in the iconified state"
  145 
  146 set Option(Deiconify) 0
  147 set OptDescr(Deiconify) "(0/1) If 1, TkRemind deiconifies the calendar window when a reminder pops up"
  148 
  149 set Option(ShowTodaysReminders) 1
  150 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"
  151 
  152 set Option(RunCmd) ""
  153 set OptDescr(RunCmd) "(String) If non-blank, run specified command when a pop-up reminder appears"
  154 set Option(FeedReminder) 0
  155 set OptDescr(FeedReminder) "(0/1) If 1, feed the reminder to RunCmd on standard input (see RunCmd option)"
  156 
  157 set Option(Editor) "emacs +%d %s"
  158 set OptDescr(Editor) "(String) Specify command to edit a file.  %d is replaced with line number and %s with filename"
  159 
  160 set Option(MailAddr) ""
  161 set OptDescr(MailAddr) "(String) Specify an e-mail address to which reminders should be sent if the popup window is not manually dismissed"
  162 
  163 set Option(SMTPServer) "127.0.0.1"
  164 set OptDescr(SMTPServer) "(String) IP address or host name of SMTP server to use for sending e-mail"
  165 
  166 set Option(ExtraRemindArgs) ""
  167 set OptDescr(ExtraRemindArgs) "(String) Extra arguments when invoking remind"
  168 
  169 set Option(CalboxFont) [font actual TkFixedFont]
  170 set OptDescr(CalboxFont) "Font to use in calendar boxes in Tk font format"
  171 
  172 set Option(HeadingFont) [font actual TkDefaultFont]
  173 set OptDescr(HeadingFont) "Font to use in calendar headings in Tk font format"
  174 
  175 set Option(BackgroundColor) "#d9d9d9"
  176 set OptDescr(BackgroundColor) "Default background color of calendar boxes"
  177 
  178 set Option(TextColor) "#000000"
  179 set OptDescr(TextColor) "Default text color in calendar boxes"
  180 
  181 set Option(LabelColor) "#000000"
  182 set OptDescr(LabelColor) "Default label color for headings"
  183 
  184 set Option(WinBackground) "#d9d9d9"
  185 set OptDescr(WinBackground) "Background color of calendar window"
  186 
  187 set TimerUpdateForChanges ""
  188 
  189 # Remind program to execute -- supply full path if you want
  190 set Remind "remind"
  191 #set Remind "/home/dfs/Remind/src/remind"
  192 
  193 # Rem2PS program to execute -- supply full path if you want
  194 set Rem2PS "rem2ps"
  195 
  196 # Reminder file to source -- default
  197 set ReminderFile {NOSUCHFILE}
  198 set ReminderFile [file nativename "~/.reminders"]
  199 
  200 # Default options file
  201 set ConfigFile ~/.tkremindrc
  202 
  203 set EditorPid -1
  204 
  205 # Inotify file
  206 set InotifyFP ""
  207 
  208 # Reminder file to append to -- default
  209 set AppendFile {NOSUCHFILE}
  210 catch {set AppendFile $ReminderFile}
  211 
  212 # Array of tags -> JSON dicts
  213 array unset TagToObj
  214 
  215 set SetFontsWorked 0
  216 #---------------- DON'T CHANGE STUFF BELOW HERE ------------------
  217 
  218 # 24-hour clock mode
  219 set TwentyFourHourMode 0
  220 
  221 # Is Monday in first column?
  222 set MondayFirst 0
  223 
  224 # Month names in English
  225 set MonthNames {January February March April May June July August September October November December}
  226 
  227 # Day names in Remind's pre-selected language
  228 set DayNames {}
  229 
  230 # Day name in English
  231 set EnglishDayNames {Sunday Monday Tuesday Wednesday Thursday Friday Saturday}
  232 
  233 # Current month and year -- will be set by Initialize procedure
  234 set CurMonth -1
  235 set CurYear -1
  236 
  237 # Background reminder counter
  238 set BgCounter 0
  239 
  240 # Absolutely today -- unlike the CurMonth and CurYear, these won't change
  241 set TodayMonth -1
  242 set TodayYear -1
  243 set TodayDay -1
  244 
  245 # Reminder option types and skip types
  246 set OptionType 1
  247 set SkipType 1
  248 # Remind command line
  249 set CommandLine {}
  250 set PSCmd {}
  251 
  252 # Print options -- destination file; letter-size; landscape; fill page; default
  253 # encoding; 36pt margins; print small calendars
  254 set PrintDest file
  255 set PrintSize letter
  256 set PrintOrient landscape
  257 set PrintFill 1
  258 set PrintDaysRight 1
  259 set PrintEncoding 0
  260 set PrintMargins 36pt
  261 set PrintSmallCalendars 1
  262 
  263 set WarningHeaders [list "# Lines staring with REM TAG TKTAGnnn ... were created by tkremind" "# Do not edit them by hand or results may be unpredictable."]
  264 
  265 # Highest tag seen so far.  Array of tags is stored in ReminderTags()
  266 set HighestTagSoFar 0
  267 
  268 proc get_weekday { yyyymmdd } {
  269     global EnglishDayNames
  270     return [lindex $EnglishDayNames [clock format [clock scan $yyyymmdd] -format %w -locale C]]
  271 }
  272 
  273 proc write_warning_headers { out } {
  274     global WarningHeaders
  275     foreach h $WarningHeaders {
  276     puts $out $h
  277     }
  278 }
  279 
  280 proc is_warning_header { line } {
  281     global WarningHeaders
  282     foreach h $WarningHeaders {
  283     if {"$line" == "$h"} {
  284         return 1
  285     }
  286     }
  287     return 0
  288 }
  289 
  290 #***********************************************************************
  291 # %PROCEDURE: Initialize
  292 # %ARGUMENTS:
  293 #  None
  294 # %RETURNS:
  295 #  Nothing
  296 # %DESCRIPTION:
  297 #  Initializes TkRemind -- sets day names, Remind command line,
  298 #  MondayFirst flag, current date, etc.
  299 #***********************************************************************
  300 proc Initialize {} {
  301 
  302     global DayNames argc argv CommandLine ReminderFile AppendFile Remind PSCmd
  303     global MondayFirst TwentyFourHourMode ReminderFileModTime
  304     global Option ConfigFile
  305     set CommandLine "|$Remind -itkremind=1 -pp -y -l EXTRA"
  306     set PSCmd "$Remind -itkremind=1 -itkprint=1 -pp -l EXTRA"
  307     set i 0
  308     while {$i < $argc} {
  309     if {[regexp -- {-[bgxim].*} [lindex $argv $i]]} {
  310         append CommandLine " [lindex $argv $i]"
  311         append PSCmd " [lindex $argv $i]"
  312         if {[regexp -- {m} [lindex $argv $i]]} {
  313         set MondayFirst 1
  314         }
  315         if {"[lindex $argv $i]" == "-b1"} {
  316         set TwentyFourHourMode 1
  317         }
  318     } else {
  319         break
  320     }
  321     incr i
  322     }
  323     if {$i < $argc} {
  324     set ReminderFile [lindex $argv $i]
  325     set AppendFile $ReminderFile
  326     incr i
  327     if {$i < $argc} {
  328         set AppendFile [lindex $argv $i]
  329         incr i
  330             if {$i < $argc} {
  331                 set ConfigFile [lindex $argv $i]
  332                 incr i
  333             }
  334     }
  335     }
  336 
  337     # If reminder file is a directory and appendfile is the same as
  338     # reminder file, choose append file to be $ReminderFile/100-tkremind.rem
  339     if {[file isdirectory $ReminderFile]} {
  340     if {"$ReminderFile" == "$AppendFile"} {
  341         set AppendFile [file join $ReminderFile "100-tkremind.rem"]
  342     }
  343     }
  344 
  345     # Check system sanity
  346     if {! [file readable $ReminderFile]} {
  347     set ans [tk_dialog .error "TkRemind: Warning" "Can't read reminder file `$ReminderFile'" warning 0 "Create it and continue" "Exit"]
  348     if {$ans != 0} {
  349         exit 1
  350     }
  351     catch {
  352         set out [open $ReminderFile w]
  353         write_warning_headers $out
  354         puts $out ""
  355         close $out
  356     }
  357     }
  358     if {! [file readable $ReminderFile]} {
  359     tk_dialog .error "TkRemind: Error" "Could not create reminder file `$ReminderFile'" error 0 "Exit"
  360     exit 1
  361     }
  362 
  363     if {[file isdirectory $ReminderFile] && ! [file exists $AppendFile]} {
  364     if {![catch {
  365         set out [open $AppendFile "a"]
  366         write_warning_headers $out
  367         puts $out ""
  368         close $out}]} {
  369         tk_dialog .error "Created File" "Created blank file `$AppendFile'" info 0 "OK"
  370     }
  371     }
  372 
  373     if {! [file writable $AppendFile]} {
  374     tk_dialog .error Error "Can't write reminder file `$AppendFile'" error 0 Ok
  375     exit 1
  376     }
  377 
  378     append CommandLine " $ReminderFile"
  379     append PSCmd " $ReminderFile"
  380     set DayNames [GetWeekdayNames]
  381 
  382     # Get modification time of ReminderFile
  383     set ReminderFileModTime -1
  384     catch {
  385     set ReminderFileModTime [file mtime $ReminderFile]
  386     }
  387 
  388     MonitorReminderFile
  389 
  390 #   puts "CommandLine: $CommandLine"
  391 }
  392 
  393 #---------------------------------------------------------------------------
  394 # MonitorReminderFile
  395 # If Reminder File modtime changes, restart daemon
  396 #---------------------------------------------------------------------------
  397 proc MonitorReminderFile {} {
  398     global ReminderFileModTime ReminderFile
  399 
  400     if {$ReminderFileModTime < 0} {
  401     # Could not stat file
  402     return
  403     }
  404 
  405     set mtime -1
  406     catch {
  407     set mtime [file mtime $ReminderFile]
  408     }
  409     if {$mtime < 0} {
  410     # Doh!
  411     return
  412     }
  413 
  414     # Run ourselves again
  415     after 60000 MonitorReminderFile
  416 
  417     # Redraw calendar and restart daemon if needed
  418     if {$ReminderFileModTime < $mtime} {
  419     set ReminderFileModTime $mtime
  420         ScheduleUpdateForChanges
  421     }
  422 }
  423 #---------------------------------------------------------------------------
  424 # GetWeekdayNames - Spawn a remind process and get the names of the weekdays
  425 # Also sets CurMonth and CurYear.
  426 #---------------------------------------------------------------------------
  427 proc GetWeekdayNames {} {
  428     global CurMonth CurYear TodayYear TodayMonth TodayDay Remind
  429     set f [open "|$Remind - 2>/dev/null" r+]
  430     puts $f "banner %"
  431     set i 0
  432     while { $i < 7 } {
  433     puts $f "msg \[wkday($i)\]%"
  434     incr i
  435     }
  436 
  437     # Get current month and year as long as we're running Remind
  438     puts $f "msg %n%"
  439     puts $f "msg %y%"
  440     puts $f "msg %d%"
  441     puts $f "FLUSH"
  442     flush $f
  443     set ans {}
  444     set i 0
  445     while { $i < 7 } {
  446     lappend ans [gets $f]
  447     incr i
  448     }
  449     set CurMonth [expr [gets $f] - 1]
  450     set CurYear [gets $f]
  451     set TodayDay [gets $f]
  452     set TodayMonth $CurMonth
  453     set TodayYear $CurYear
  454     close $f
  455     return $ans
  456 }
  457 
  458 #***********************************************************************
  459 # %PROCEDURE: CalEntryOffset
  460 # %ARGUMENTS:
  461 #  firstDay -- first day of month (0=Sunday, 6=Saturday)
  462 # %RETURNS:
  463 #  Offset mapping day numbers (1-31) to window numbers (0-41)
  464 # %DESCRIPTION:
  465 #  Computes offset from day number to window number
  466 #***********************************************************************
  467 proc CalEntryOffset { firstDay } {
  468     global MondayFirst
  469     if {$MondayFirst} {
  470     incr firstDay -1
  471     if {$firstDay < 0} {
  472         set firstDay 6
  473     }
  474     }
  475     return [expr $firstDay-1]
  476 }
  477 
  478 #***********************************************************************
  479 # %PROCEDURE: CreateCalFrame
  480 # %ARGUMENTS:
  481 #  w -- name of frame window
  482 #  dayNames -- names of weekdays
  483 # %RETURNS:
  484 #  Nothing
  485 # %DESCRIPTION:
  486 #  Creates a frame holding a grid of labels and a grid of text entries
  487 #***********************************************************************
  488 proc CreateCalFrame { w dayNames } {
  489     # Figure out reasonable height for text frames
  490     global SetFontsWorked
  491     global Option
  492     set h [winfo screenheight .]
  493     if {$h <= 480} {
  494     if {$SetFontsWorked} {
  495         set h 3
  496     } else {
  497         set h 2
  498     }
  499     } elseif {$h <= 600} {
  500     set h 4
  501     } else {
  502     set h 5
  503     }
  504 
  505     global MondayFirst
  506     frame $w -background $Option(WinBackground)
  507     for {set i 0} {$i < 7} {incr i} {
  508     if {$MondayFirst} {
  509         set index [expr ($i+1)%7]
  510     } else {
  511         set index $i
  512     }
  513 
  514     label $w.day$i -border 1 -text [lindex $dayNames $index] -justify center -font HeadingFont -foreground $Option(LabelColor) -background $Option(WinBackground)
  515     grid configure $w.day$i -row 0 -column $i -sticky ew
  516     }
  517     for {set i 0} {$i < 6} {incr i} {
  518     set n [expr $i*7]
  519     for {set j 0} {$j < 7} {incr j} {
  520         set f [expr $n+$j]
  521         button $w.l$f -text "" -justify center -command "" \
  522         -state disabled -relief flat -border 0 -padx 0 -pady 0 -font HeadingFont
  523         text $w.t$f -width 12 -height $h -border 1 -spacing3 3 -wrap word -relief flat \
  524         -state disabled -takefocus 0 -cursor {} -font CalboxFont -foreground $Option(TextColor) -background $Option(BackgroundColor)
  525         $w.t$f tag bind TAGGED <ButtonPress-1> "EditTaggedReminder $w.t$f"
  526         $w.t$f tag bind REM <ButtonPress-3> "FireEditor $w.t$f"
  527 
  528         grid configure $w.l$f -row [expr $i*2+1] -column $j -sticky ew
  529         grid configure $w.t$f -row [expr $i*2+2] -column $j -sticky nsew
  530     }
  531     }
  532 
  533     for {set i 0} {$i < 7} {incr i} {
  534     grid columnconfigure $w $i -weight 1
  535     }
  536     for {set i 2} {$i < 14} {incr i 2} {
  537     grid rowconfigure $w $i -weight 1
  538     }
  539 }
  540 
  541 #***********************************************************************
  542 # %PROCEDURE: ConfigureCalFrame
  543 # %ARGUMENTS:
  544 #  w -- window name of calendar frame
  545 #  firstDay -- first weekday of month
  546 #  numDays -- number of days in month
  547 # %RETURNS:
  548 #  Nothing
  549 # %DESCRIPTION:
  550 #  Sets up button labels; configures text justification
  551 #***********************************************************************
  552 proc ConfigureCalFrame { w firstDay numDays } {
  553     global CurMonth CurYear TodayMonth TodayYear TodayDay
  554     global tk_version Option
  555     set offset [CalEntryOffset $firstDay]
  556     set first [expr $offset+1]
  557     set last [expr $offset+$numDays]
  558 
  559     for {set i 0} {$i < $first} {incr i} {
  560     grid $w.l$i $w.t$i
  561     $w.l$i configure -text "" -command "" -state normal -relief flat -foreground $Option(LabelColor) -background $Option(WinBackground)
  562         $w.l$i configure -state disabled
  563     balloon_add_help $w.l$i ""
  564     $w.t$i configure -relief flat -takefocus 0 -state normal -background $Option(WinBackground)
  565     $w.t$i delete 1.0 end
  566     foreach t [$w.t$i tag names] {
  567         $w.t$i tag delete $t
  568     }
  569     $w.t$i tag bind TAGGED <ButtonPress-1> "EditTaggedReminder $w.t$i"
  570     $w.t$i tag bind REM <ButtonPress-3> "FireEditor $w.t$i"
  571     $w.t$i configure -state disabled
  572     }
  573     for {set i $first} {$i <= $last} {incr i} {
  574     grid $w.l$i $w.t$i
  575     set d [expr $i-$first+1]
  576     $w.l$i configure -text $d -state normal -relief flat \
  577         -command "ModifyDay $d $firstDay" -foreground $Option(LabelColor) -background $Option(WinBackground)
  578     balloon_add_help $w.l$i "Add a reminder..."
  579     $w.t$i configure -relief sunken -takefocus 1 -state normal -foreground $Option(TextColor) -background $Option(BackgroundColor)
  580     $w.t$i delete 1.0 end
  581     foreach t [$w.t$i tag names] {
  582         $w.t$i tag delete $t
  583     }
  584     $w.t$i tag bind TAGGED <ButtonPress-1> "EditTaggedReminder $w.t$i"
  585     $w.t$i tag bind REM <ButtonPress-3> "FireEditor $w.t$i"
  586     $w.t$i configure -state disabled
  587     }
  588     set forgetIt 0
  589     for {set i [expr $last+1]} {$i < 42} {incr i} {
  590     if {$i%7 == 0} {
  591         set forgetIt 1
  592     }
  593     set row [expr ($i/7)*2+1]
  594     if {$forgetIt} {
  595         grid remove $w.l$i $w.t$i
  596         grid rowconfigure $w $row -weight 0
  597         grid rowconfigure $w [expr $row+1] -weight 0
  598     } else {
  599         grid $w.l$i $w.t$i
  600         grid rowconfigure $w [expr $row+1] -weight 1
  601     }
  602     $w.l$i configure -text "" -command "" -state normal -relief flat -foreground $Option(LabelColor) -background $Option(WinBackground)
  603         $w.l$i configure -state disabled
  604     balloon_add_help $w.l$i ""
  605     $w.t$i configure -relief flat -takefocus 0 -state normal -background $Option(WinBackground)
  606     $w.t$i delete 1.0 end
  607     foreach t [$w.t$i tag names] {
  608         $w.t$i tag delete $t
  609     }
  610     $w.t$i tag bind TAGGED <ButtonPress-1> "EditTaggedReminder $w.t$i"
  611     $w.t$i tag bind REM <ButtonPress-3> "FireEditor $w.t$i"
  612     $w.t$i configure -state disabled
  613     }
  614     if { $CurMonth == $TodayMonth && $CurYear == $TodayYear } {
  615     set n [expr $TodayDay + $offset]
  616     $w.l$n configure -background "#00c0c0"
  617     }
  618 }
  619 
  620 proc DoQueue {} {
  621     global DaemonFile
  622     puts $DaemonFile "JSONQUEUE"
  623     flush $DaemonFile
  624 }
  625 
  626 #---------------------------------------------------------------------------
  627 # CreateCalWindow -- create the calendar window.
  628 # Arguments:
  629 #   dayNames -- names of weekdays in current language {Sun .. Sat}
  630 #---------------------------------------------------------------------------
  631 proc CreateCalWindow { dayNames } {
  632     global Option
  633     frame .h -background $Option(WinBackground);
  634     label .h.title -text "" -justify center -pady 1 -border 1 -relief raised -font HeadingFont -background $Option(WinBackground) -foreground $Option(LabelColor)
  635     pack .h.title -side top -fill x
  636     pack .h -side top -expand 0 -fill x
  637 
  638     CreateCalFrame .cal $dayNames
  639 
  640     frame .b -background $Option(WinBackground);
  641     button .b.prev -image leftarrow -width 24 -command {MoveMonth -1} -border 1
  642     balloon_add_help .b.prev "Go to previous month"
  643     button .b.this -text {Today} -command {ThisMonth} -border 1
  644     balloon_add_help .b.this "Go to this month"
  645     button .b.next -image rightarrow -width 24 -command {MoveMonth 1} -border 1
  646     balloon_add_help .b.next "Go to next month"
  647     button .b.goto -text {Go To Date...} -command {GotoDialog} -border 1
  648     balloon_add_help .b.goto "Go to a specific date"
  649     button .b.print -text {Print...} -command {DoPrint} -border 1
  650     balloon_add_help .b.print "Print a PostScript calendar"
  651     button .b.queue -text {Queue...} -command {DoQueue} -border 1
  652     balloon_add_help .b.queue "See the queue of pending reminders (debugging purposes only)"
  653     button .b.quit -text {Quit} -command {Quit} -border 1
  654     balloon_add_help .b.quit "Quit TkRemind"
  655     button .b.options -text {Options...} -command EditOptions -border 1
  656     balloon_add_help .b.options "Set TkRemind options"
  657     label .b.status -text "" -width 25 -relief sunken -border 1
  658     label .b.nqueued -text "" -width 20 -relief sunken -border 1
  659     pack .b.prev .b.this .b.next .b.goto .b.print .b.options .b.queue .b.quit -side left -fill both
  660     pack .b.status -side left -fill x -expand 1
  661     pack .b.nqueued -side left -fill x
  662     pack .b -side bottom -fill x -expand 0
  663     pack .cal -side top -fill both -expand 1
  664     wm title . "TkRemind"
  665     wm iconname . ""
  666     wm protocol . WM_DELETE_WINDOW Quit
  667     wm deiconify .
  668     bind . <Control-KeyPress-q> Quit
  669     bind . <KeyPress-Left> ".b.prev flash; .b.prev invoke"
  670     bind . <KeyPress-Right> ".b.next flash; .b.next invoke"
  671     bind . <KeyPress-Prior> ".b.prev flash; .b.prev invoke"
  672     bind . <KeyPress-Next> ".b.next flash; .b.next invoke"
  673     bind . <KeyPress-Home> ".b.this flash; .b.this invoke"
  674 
  675     . configure -background $Option(WinBackground)
  676     if {$Option(StartIconified)} {
  677     wm iconify .
  678     }
  679     update
  680     grid propagate .cal 0
  681 }
  682 
  683 #***********************************************************************
  684 # %PROCEDURE: EditOptions
  685 # %ARGUMENTS:
  686 #  None
  687 # %RETURNS:
  688 #  Nothing
  689 # %DESCRIPTION:
  690 #  Lets user edit options
  691 #***********************************************************************
  692 proc EditOptions {} {
  693     global Option tmpOpt
  694 
  695     # Make a working copy of current option set
  696     foreach name [array names Option] {
  697     set tmpOpt($name) $Option($name)
  698     }
  699 
  700     set w .opt
  701     catch { destroy $w }
  702     toplevel $w
  703     wm title $w "TkRemind Options"
  704     wm iconname $w "Options"
  705     frame $w.f
  706     frame $w.b
  707     pack $w.f -side top -expand 1 -fill both
  708     pack $w.b -side top -expand 0 -fill x
  709 
  710     # Start iconified
  711     checkbutton $w.startIconified -text "Start up Iconified" \
  712         -anchor w -justify left \
  713         -variable tmpOpt(StartIconified)
  714 
  715     # Show today's reminders on startup
  716     checkbutton $w.showTodays -text "Show Today's Reminders on Startup" \
  717         -anchor w -justify left \
  718         -variable tmpOpt(ShowTodaysReminders)
  719 
  720     # Confirm quit
  721     checkbutton $w.confirmQuit -text "Confirm Quit" -anchor w -justify left \
  722         -variable tmpOpt(ConfirmQuit)
  723 
  724     # Bring down reminder windows after one minute
  725     checkbutton $w.bringDown \
  726         -text "Automatically close pop-up reminders after a minute" \
  727         -anchor w -justify left -variable tmpOpt(AutoClose)
  728 
  729     # Ring bell when popping up reminder
  730     checkbutton $w.ring -text "Beep terminal when popping up a reminder" \
  731         -anchor w -justify left -variable tmpOpt(RingBell)
  732 
  733     checkbutton $w.deic -text "Deiconify calendar window when popping up a reminder" \
  734         -anchor w -justify left -variable tmpOpt(Deiconify)
  735 
  736     # Run command when popping up reminder
  737     frame $w.rf
  738     label $w.rl -text "Run command when popping up reminder:" -anchor w \
  739         -justify left
  740     entry $w.cmd -width 30
  741     pack $w.rl -in $w.rf -side left -expand 0 -fill none
  742     pack $w.cmd -in $w.rf -side left -expand 1 -fill x
  743     $w.cmd insert 0 $tmpOpt(RunCmd)
  744 
  745     frame $w.sep3 -border 1 -relief sunken
  746     # E-mail reminder if popup not dismissed
  747     frame $w.eml
  748     label $w.lab_email_address -text "E-mail reminders here if popup not dismissed:" -anchor w -justify left
  749     entry $w.email_address -width 30
  750     pack $w.lab_email_address -in $w.eml -side left -expand 0 -fill none
  751     pack $w.email_address -in $w.eml -side left -expand 1 -fill x
  752     $w.email_address insert 0 $tmpOpt(MailAddr)
  753 
  754     frame $w.fsmtp
  755     label $w.lab_smtp -text "Name or IP address of SMTP server:" -anchor w -justify left
  756     entry $w.smtp -width 30
  757     pack $w.lab_smtp -in $w.fsmtp -side left -expand 0 -fill none
  758     pack $w.smtp -in $w.fsmtp -side left -expand 1 -fill x
  759     $w.smtp insert 0 $tmpOpt(SMTPServer)
  760 
  761     # Editor
  762     frame $w.ef
  763     label $w.el -text "Text Editor:" -anchor w -justify left
  764     entry $w.editor -width 30
  765     pack $w.el -in $w.ef -side left -expand 0 -fill none
  766     pack $w.editor -in $w.ef -side left -expand 1 -fill x
  767     $w.editor insert 0 $tmpOpt(Editor)
  768 
  769     # extra args
  770     frame $w.eaf
  771     label $w.eal -text "Extra Arguments for Remind:" -anchor w -justify left
  772     entry $w.extraargs -width 30
  773     pack $w.eal -in $w.eaf -side left -expand 0 -fill none
  774     pack $w.extraargs -in $w.eaf -side left -expand 1 -fill x
  775     $w.extraargs insert 0 $tmpOpt(ExtraRemindArgs)
  776 
  777     # Fonts
  778     frame $w.fframe
  779     button $w.font -text "Change entry font..." -command "ChooseCalboxFont"
  780     button $w.hfont -text "Change heading font..." -command "ChooseHeadingFont"
  781     pack $w.font $w.hfont -in $w.fframe -side left -expand 1 -fill x
  782 
  783     # Colors
  784     frame $w.colors1
  785     label $w.textcolor -text "Text Color:"
  786     button $w.btextcolor -background $Option(TextColor) -command [list PickColor TextColor $w.btextcolor] -text ...
  787     label $w.bgcolor -text "  Background color:"
  788     button $w.bbgcolor -background $Option(BackgroundColor) -command [list PickColor BackgroundColor $w.bbgcolor] -text ...
  789 
  790     grid $w.textcolor $w.btextcolor $w.bgcolor $w.bbgcolor -in $w.colors1
  791     grid $w.bgcolor $w.bbgcolor -in $w.colors1
  792 
  793     label $w.headcolor -text "Heading Color:"
  794     button $w.bheadcolor -background $Option(LabelColor) -command [list PickColor LabelColor $w.bheadcolor] -text ...
  795     label $w.wincolor -text "  Window color:"
  796     button $w.bwincolor -background $Option(WinBackground) -command [list PickColor WinBackground $w.bwincolor] -text ...
  797     grid $w.headcolor $w.bheadcolor $w.wincolor $w.bwincolor -in $w.colors1
  798 
  799     grid columnconfigure $w.colors1 0 -weight 1
  800     grid columnconfigure $w.colors1 2 -weight 1
  801     frame $w.sep1 -border 1 -relief sunken
  802     frame $w.sep2 -border 1 -relief sunken
  803 
  804     checkbutton $w.feed \
  805         -text "Feed popped-up reminder to command's standard input" \
  806         -variable tmpOpt(FeedReminder) -anchor w -justify left
  807 
  808     pack $w.startIconified -in $w.f -side top -expand 0 -fill x
  809     pack $w.showTodays -in $w.f -side top -expand 0 -fill x
  810     pack $w.confirmQuit -in $w.f -side top -expand 0 -fill x
  811     pack $w.bringDown -in $w.f -side top -expand 0 -fill x
  812     pack $w.ring -in $w.f -side top -expand 0 -fill x
  813     pack $w.deic -in $w.f -side top -expand 0 -fill x
  814     pack $w.sep1 -in $w.f -side top -expand 0 -fill x -ipady 1
  815     pack $w.rf -in $w.f -side top -expand 0 -fill x
  816     pack $w.feed -in $w.f -side top -expand 0 -fill x
  817     pack $w.sep3 -in $w.f -side top -expand 0 -fill x -ipady 1
  818     pack $w.eml -in $w.f -side top -expand 0 -fill x
  819     pack $w.fsmtp -in $w.f -side top -expand 0 -fill x
  820     pack $w.ef -in $w.f -side top -expand 0 -fill x
  821     pack $w.eaf -in $w.f -side top -expand 0 -fill x
  822     pack $w.fframe -in $w.f -side top -expand 0 -fill x
  823     pack $w.colors1 -in $w.f -side top -expand 0 -fill x
  824     pack $w.sep2 -in $w.f -side top -expand 0 -fill x -ipady 1
  825 
  826     button $w.save -text "Save Options" -command "SaveOptions $w; destroy $w"
  827     button $w.cancel -text "Cancel" -command "CancelOptions; destroy $w"
  828     wm protocol $w WM_DELETE_WINDOW "CancelOptions; destroy $w"
  829     pack $w.save $w.cancel -in $w.b -side left -expand 0 -fill x
  830     CenterWindow $w .
  831 }
  832 
  833 proc CancelOptions { } {
  834     global Option
  835     font configure CalboxFont {*}$Option(CalboxFont)
  836     font configure HeadingFont {*}$Option(HeadingFont)
  837 }
  838 
  839 #***********************************************************************
  840 # %PROCEDURE: ApplyOptions
  841 # %ARGUMENTS:
  842 #  w -- edit options window path
  843 # %RETURNS:
  844 #  Nothing
  845 # %DESCRIPTION:
  846 #  Applies options set in the edit options box.
  847 #***********************************************************************
  848 proc ApplyOptions { w } {
  849     global Option tmpOpt
  850 
  851     set tmpOpt(RunCmd) [$w.cmd get]
  852     set tmpOpt(Editor) [$w.editor get]
  853     set tmpOpt(ExtraRemindArgs) [$w.extraargs get]
  854     set tmpOpt(MailAddr) [$w.email_address get]
  855     set tmpOpt(SMTPServer) [$w.smtp get]
  856 
  857     set need_restart 0
  858     if {"$tmpOpt(ExtraRemindArgs)" != "$Option(ExtraRemindArgs)"} {
  859     set need_restart 1
  860     }
  861     # Copy working copy to real option set
  862     foreach name [array names tmpOpt] {
  863     set Option($name) $tmpOpt($name)
  864     }
  865     if {$need_restart != 0} {
  866     FillCalWindow
  867     StopBackgroundRemindDaemon
  868     StartBackgroundRemindDaemon
  869     }
  870 }
  871 
  872 #***********************************************************************
  873 # %PROCEDURE: SaveOptions
  874 # %ARGUMENTS:
  875 #  w -- edit options window path
  876 # %RETURNS:
  877 #  Nothing
  878 # %DESCRIPTION:
  879 #  Saves options in specified config file
  880 #***********************************************************************
  881 proc SaveOptions { w } {
  882     global Option OptDescr ConfigFile
  883     ApplyOptions $w
  884 
  885     set problem [catch {set f [open $ConfigFile "w"]} err]
  886     if {$problem} {
  887     tk_dialog .error Error "Can't write $ConfigFile: $err" 0 OK
  888     return
  889     }
  890 
  891     puts $f "# TkRemind option file -- created automatically"
  892     puts $f "# [clock format [clock seconds]]"
  893     puts $f "# Format of each line is 'key value' where 'key'"
  894     puts $f "# specifies the option name, and 'value' is a"
  895     puts $f "# *legal Tcl list element* specifying the option value."
  896     foreach name [lsort [array names Option]] {
  897     puts $f ""
  898     puts $f "# $OptDescr($name)"
  899     puts $f [list $name $Option($name)]
  900     }
  901     puts $f ""
  902     close $f
  903     FillCalWindow
  904     .h.title configure -background $Option(WinBackground) -foreground $Option(LabelColor)
  905     for {set i 0} {$i < 7} {incr i} {
  906         .cal.day$i configure  -foreground $Option(LabelColor) -background $Option(WinBackground)
  907     }
  908 }
  909 
  910 #***********************************************************************
  911 # %PROCEDURE: LoadOptions
  912 # %ARGUMENTS:
  913 #  None
  914 # %RETURNS:
  915 #  Nothing
  916 # %DESCRIPTION:
  917 #  Loads options from $ConfigFile
  918 #***********************************************************************
  919 proc LoadOptions {} {
  920     global Option ConfigFile
  921     set problem [catch {set f [open "$ConfigFile" "r"]}]
  922     if {$problem} {
  923     return
  924     }
  925     while {[gets $f line] >= 0} {
  926     if {[string match "#*" $line]} {
  927             continue
  928         }
  929         if {$line == ""} {
  930             continue
  931         }
  932         foreach {key val} $line {}
  933         if {![info exists Option($key)]} {
  934             puts stderr "Unknown option in $ConfigFile: $key"
  935             continue
  936         }
  937         set Option($key) $val
  938     }
  939     close $f
  940     font configure CalboxFont {*}$Option(CalboxFont)
  941     font configure HeadingFont {*}$Option(HeadingFont)
  942 }
  943 
  944 
  945 
  946 #***********************************************************************
  947 # %PROCEDURE: ConfigureCalWindow
  948 # %ARGUMENTS:
  949 #  month -- month name
  950 #  year -- the year
  951 #  firstDay -- first day in month
  952 #  numDays -- number of days in month
  953 # %RETURNS:
  954 #  Nothing
  955 # %DESCRIPTION:
  956 #  Configures the calendar window for a month and year
  957 # %PRECONDITIONS:
  958 #  Any preconditions
  959 # %POSTCONDITIONS:
  960 #  Any postconditions
  961 # %SIDE EFFECTS:
  962 #  Any side effects
  963 #***********************************************************************
  964 proc ConfigureCalWindow { month year firstDay numDays } {
  965     global Hostname
  966     .h.title configure -text "$month $year"
  967     wm title . "$month $year - TkRemind on $Hostname"
  968     wm iconname . "$month $year"
  969     ConfigureCalFrame .cal $firstDay $numDays
  970 }
  971 
  972 #---------------------------------------------------------------------------
  973 # FillCalWindow -- Fill in the calendar for global CurMonth and CurYear.
  974 #---------------------------------------------------------------------------
  975 proc FillCalWindow {} {
  976     set FileName ""
  977     set LineNo 0
  978     global DayNames CurYear CurMonth MonthNames CommandLine Option TagToObj
  979 
  980     array unset TagToObj
  981 
  982     Status "Firing off Remind..."
  983     set month [lindex $MonthNames $CurMonth]
  984 
  985     set cmd [regsub EXTRA $CommandLine $Option(ExtraRemindArgs)]
  986     set file [open "$cmd $month $CurYear" r]
  987     # Look for # rem2ps2 begin line
  988     while { [gets $file line] >= 0 } {
  989     if { [string compare "$line" "# rem2ps2 begin"] == 0 } { break }
  990     }
  991 
  992     if { [string compare "$line" "# rem2ps2 begin"] != 0 } {
  993     Status "Problem reading results from Remind!"
  994     after 5000 DisplayTime
  995     catch { close $file }
  996     return 0
  997     }
  998 
  999     # Read month name, year, number of days in month, first weekday, Mon flag
 1000     gets $file line
 1001     regexp {^([^ ]*) ([0-9][0-9][0-9][0-9]) ([0-9][0-9]?) ([0-9]) ([0-9])} $line dummy monthName year daysInMonth firstWkday mondayFirst
 1002 
 1003     # Skip day names -- we already have them
 1004     gets $file line
 1005     ConfigureCalWindow $monthName $year $firstWkday $daysInMonth
 1006     set offset [CalEntryOffset $firstWkday]
 1007 
 1008     while { [gets $file line] >= 0 } {
 1009         set fntag "x"
 1010     # Ignore unless begins with left brace
 1011     if { ! [string match "\{*" $line]} {
 1012         continue
 1013     }
 1014 
 1015     if {[catch {set obj [::json::json2dict $line]}]} {
 1016         continue
 1017     }
 1018 
 1019     if {[dict exists $obj filename]} {
 1020             set fname [dict get $obj filename]
 1021             # Don't make INCLUDECMD output editable
 1022             if {![string match "*|" $fname]} {
 1023                 set fntag [string cat "FILE_" [dict get $obj lineno] "_" $fname]
 1024             }
 1025     }
 1026 
 1027     set date [dict get $obj date]
 1028     regexp {^([0-9][0-9][0-9][0-9]).([0-9][0-9]).([0-9][0-9])} $date all year month day
 1029     if {[dict exists $obj passthru]} {
 1030         set type [dict get $obj passthru]
 1031     } else {
 1032         set type "*"
 1033     }
 1034     if {[dict exist $obj tags]} {
 1035         set tag [dict get $obj tags]
 1036     } else {
 1037         set tag "*"
 1038     }
 1039     set stuff [dict get $obj body]
 1040 
 1041     set day [string trimleft $day 0]
 1042     set n [expr $day+$offset]
 1043     set month [string trimleft $month 0]
 1044     set extratags ""
 1045     switch -nocase -- $type {
 1046         "WEEK" {
 1047         set stuff [string trimleft $stuff]
 1048         set stuff [string trimright $stuff]
 1049         set offset [CalEntryOffset $firstWkday]
 1050         set label [expr $offset + $day]
 1051         .cal.l$label configure -text "$day $stuff"
 1052         continue
 1053         }
 1054         "SHADE" {
 1055         DoShadeSpecial $n [dict get $obj r] [dict get $obj g] [dict get $obj b]
 1056         continue
 1057         }
 1058         "MOON" {
 1059         DoMoonSpecial $n $stuff
 1060         continue
 1061         }
 1062         "COLOUR" -
 1063         "COLOR" {
 1064         if {[regexp {^ *([0-9]+) +([0-9]+) +([0-9]+) +(.*)$} $stuff all r g b rest]} {
 1065             if {$r > 255} {
 1066             set r 255
 1067             } elseif {$r < 0} {
 1068             set r 0
 1069             }
 1070             if {$g > 255} {
 1071             set g 255
 1072             } elseif {$g < 0} {
 1073             set g 0
 1074             }
 1075             if {$b > 255} {
 1076             set b 255
 1077             } elseif {$b < 0} {
 1078             set b 0
 1079             }
 1080             set color [format "%02X%02X%02X" $r $g $b]
 1081             set extratags "clr$color"
 1082             .cal.t$n configure -state normal
 1083             .cal.t$n tag configure $extratags -foreground "#$color"
 1084             .cal.t$n configure -state disabled
 1085             set stuff $rest
 1086             set type "COLOR"
 1087         }
 1088         }
 1089     }
 1090     if { $type != "*" && $type != "COLOR" && $type != "COLOUR"} {
 1091         continue
 1092     }
 1093     .cal.t$n configure -state normal
 1094     if {[regexp {TKTAG([0-9]+)} $tag all tagno] && "$fntag" != "x"} {
 1095         .cal.t$n insert end [string trim $stuff] [list REM TAGGED "TKTAG$tagno" "date_$date" $extratags $fntag]
 1096         .cal.t$n tag bind "TKTAG$tagno" <Enter> "TaggedEnter .cal.t$n"
 1097         .cal.t$n tag bind "TKTAG$tagno" <Leave> "TaggedLeave .cal.t$n"
 1098         set TagToObj(TKTAG$tagno) $obj
 1099     } else {
 1100             if {"$fntag" == "x" } {
 1101                 .cal.t$n insert end [string trim $stuff] [list REM $extratags]
 1102             } else {
 1103                 .cal.t$n insert end [string trim $stuff] [list REM $extratags $fntag]
 1104             }
 1105     }
 1106     .cal.t$n insert end "\n"
 1107     .cal.t$n configure -state disabled
 1108 
 1109     }
 1110     set problem [catch { close $file } errmsg]
 1111     if {$problem} {
 1112     tk_dialog .error Error "There was a problem running Remind: $errmsg" error 0 OK
 1113     }
 1114     DisplayTime
 1115 }
 1116 
 1117 #---------------------------------------------------------------------------
 1118 # MoveMonth -- move by +1 or -1 months
 1119 # Arguments:
 1120 #    delta -- +1 or -1 -- months to move.
 1121 #---------------------------------------------------------------------------
 1122 proc MoveMonth {delta} {
 1123     global CurMonth CurYear
 1124     set CurMonth [expr $CurMonth + $delta]
 1125     if {$CurMonth < 0} {
 1126     set CurMonth 11
 1127     set CurYear [expr $CurYear-1]
 1128     }
 1129 
 1130     if {$CurMonth > 11} {
 1131     set CurMonth 0
 1132     incr CurYear
 1133     }
 1134 
 1135     FillCalWindow
 1136 }
 1137 
 1138 #---------------------------------------------------------------------------
 1139 # ThisMonth -- move to current month
 1140 #---------------------------------------------------------------------------
 1141 proc ThisMonth {} {
 1142     global CurMonth CurYear TodayMonth TodayYear
 1143 
 1144     # Do nothing if already there
 1145     if { $CurMonth == $TodayMonth && $CurYear == $TodayYear } {
 1146     return 0;
 1147     }
 1148     set CurMonth $TodayMonth
 1149     set CurYear $TodayYear
 1150     FillCalWindow
 1151 }
 1152 
 1153 #---------------------------------------------------------------------------
 1154 # Status -- set status string
 1155 # Arguments:
 1156 #   stuff -- what to set string to.
 1157 #---------------------------------------------------------------------------
 1158 proc Status { stuff } {
 1159     catch { .b.status configure -text $stuff }
 1160     update idletasks
 1161 }
 1162 
 1163 #---------------------------------------------------------------------------
 1164 # DoPrint -- print a calendar
 1165 # Arguments:
 1166 #   None
 1167 #---------------------------------------------------------------------------
 1168 proc DoPrint {} {
 1169     global PrintDest PrintSize PrintMargins PrintOrient PrintFill PrintDaysRight PrintEncoding PrintSmallCalendars PrintStatus Rem2PS PSCmd Option
 1170     global CurMonth CurYear MonthNames
 1171     catch {destroy .p}
 1172     toplevel .p
 1173     wm title .p "TkRemind Print..."
 1174     wm iconname .p "Print..."
 1175     frame .p.f1 -relief sunken -border 2
 1176     frame .p.f11
 1177     frame .p.f12
 1178     frame .p.f2 -relief sunken -border 2
 1179     frame .p.f2a -relief sunken -border 2
 1180     frame .p.f3 -relief sunken -border 2
 1181     frame .p.f3a -relief sunken -border 2
 1182     frame .p.f4
 1183 
 1184     radiobutton .p.tofile -text "To file: " -variable PrintDest -value file
 1185     entry .p.filename
 1186     button .p.browse -text "Browse..." -command PrintFileBrowse
 1187     radiobutton .p.tocmd -text "To command: " -variable PrintDest -value command
 1188     entry .p.command
 1189     .p.command insert end "lpr"
 1190 
 1191     label .p.size -text "Paper Size:"
 1192     radiobutton .p.letter -text "Letter" -variable PrintSize -value letter
 1193     radiobutton .p.a4 -text "A4" -variable PrintSize -value a4
 1194 
 1195     label .p.margin -text "Margins:"
 1196     radiobutton .p.24pt -text "24pt margins" -variable PrintMargins -value 24pt
 1197     radiobutton .p.36pt -text "36pt margins" -variable PrintMargins -value 36pt
 1198     radiobutton .p.48pt -text "48pt margins" -variable PrintMargins -value 48pt
 1199 
 1200     label .p.orient -text "Orientation:"
 1201     radiobutton .p.landscape -text "Landscape" -variable PrintOrient -value landscape
 1202     radiobutton .p.portrait -text "Portrait" -variable PrintOrient -value portrait
 1203 
 1204     checkbutton .p.fill -text "Fill page" -variable PrintFill
 1205     checkbutton .p.right -text "Day numbers at top-right" -variable PrintDaysRight
 1206     checkbutton .p.encoding -text "ISO 8859-1 PostScript encoding" -variable PrintEncoding
 1207     checkbutton .p.calendars -text "Print small calendars" -variable PrintSmallCalendars
 1208 
 1209     button .p.print -text "Print" -command {set PrintStatus print}
 1210     button .p.cancel -text "Cancel" -command {set PrintStatus cancel}
 1211 
 1212     pack .p.f1 .p.f2 .p.f2a .p.f3 .p.f3a \
 1213         -side top -fill both -expand 1 -anchor w
 1214     pack .p.fill .p.right .p.encoding .p.calendars -in .p.f3a \
 1215         -side top -anchor w -fill none -expand 0
 1216     pack .p.f4 -side top -fill both -expand 1 -anchor w
 1217     pack .p.f11 .p.f12 -in .p.f1 -side top -fill none -expand 0 -anchor w
 1218     pack .p.tofile .p.filename .p.browse -in .p.f11 -side left -fill none -expand 0 -anchor w
 1219     pack .p.tocmd .p.command -in .p.f12 -side left -fill none -expand 0 -anchor w
 1220     pack .p.size .p.letter .p.a4 -in .p.f2 -side top -fill none -expand 0 -anchor w
 1221     pack .p.margin .p.24pt .p.36pt .p.48pt -in .p.f2a -side top -anchor w -fill none -expand 0
 1222     pack .p.orient .p.landscape .p.portrait -in .p.f3 -side top -fill none -expand 0 -anchor w
 1223     pack .p.print .p.cancel -in .p.f4 -side left -fill none -expand 0
 1224 
 1225     bind .p <KeyPress-Escape> ".p.cancel flash; .p.cancel invoke"
 1226     bind .p <KeyPress-Return> ".p.print flash; .p.print invoke"
 1227     set PrintStatus 2
 1228     CenterWindow .p .
 1229     tkwait visibility .p
 1230     set oldFocus [focus]
 1231     focus .p.filename
 1232     grab .p
 1233     tkwait variable PrintStatus
 1234     catch {focus $oldFocus}
 1235     set fname [.p.filename get]
 1236     set cmd [.p.command get]
 1237     destroy .p
 1238     if {$PrintStatus == "cancel"} {
 1239     return
 1240     }
 1241     if {$PrintDest == "file"} {
 1242     if {$fname == ""} {
 1243         tk_dialog .error Error "No filename specified" error 0 Ok
 1244         return
 1245     }
 1246     if {[file isdirectory $fname]} {
 1247         tk_dialog .error Error "$fname is a directory" error 0 Ok
 1248         return
 1249     }
 1250     if {[file readable $fname]} {
 1251         set ans [tk_dialog .error Overwrite? "Overwrite $fname?" question 0 No Yes]
 1252         if {$ans == 0} {
 1253         return
 1254         }
 1255     }
 1256     set fname "> $fname"
 1257     } else {
 1258     set fname "| $cmd"
 1259     }
 1260 
 1261     # Build the command line
 1262     set p [regsub EXTRA $PSCmd $Option(ExtraRemindArgs)]
 1263     set cmd "$p 1 [lindex $MonthNames $CurMonth] $CurYear | $Rem2PS"
 1264     if {$PrintSize == "letter"} {
 1265     append cmd " -m Letter"
 1266     } else {
 1267     append cmd " -m A4"
 1268     }
 1269 
 1270     if {$PrintMargins == "24pt"} {
 1271     append cmd " -or 24 -ol 24 -ot 24 -ob 24"
 1272     } elseif {$PrintMargins == "36pt"} {
 1273     append cmd " -or 36 -ol 36 -ot 36 -ob 36"
 1274     } else {
 1275     append cmd " -or 48 -ol 48 -ot 48 -ob 48"
 1276     }
 1277 
 1278     if {$PrintOrient == "landscape"} {
 1279     append cmd " -l"
 1280     }
 1281 
 1282     if {$PrintFill} {
 1283     append cmd " -e"
 1284     }
 1285 
 1286     if {!$PrintDaysRight} {
 1287         append cmd " -x"
 1288     }
 1289     if {$PrintEncoding} {
 1290     append cmd " -i"
 1291     }
 1292 
 1293     if {$PrintSmallCalendars} {
 1294     append cmd " -c3"
 1295     } else {
 1296     append cmd " -c0"
 1297     }
 1298 
 1299     append cmd " $fname"
 1300     Status "Printing..."
 1301     if {[catch {eval "exec $cmd"} err]} {
 1302     tk_dialog .error Error "Error during printing: $err" error 0 Ok
 1303     }
 1304     DisplayTime
 1305 }
 1306 
 1307 #---------------------------------------------------------------------------
 1308 # PrintFileBrowse -- browse for a filename for Print dialog
 1309 # Arguments: none
 1310 #---------------------------------------------------------------------------
 1311 proc PrintFileBrowse {} {
 1312     set ans [BrowseForFile .filebrowse "Print to file..." "Ok" 0 "*.ps"]
 1313     if {$ans != ""} {
 1314     .p.filename delete 0 end
 1315     .p.filename insert end $ans
 1316     .p.filename icursor end
 1317     .p.filename xview end
 1318     }
 1319 }
 1320 
 1321 #---------------------------------------------------------------------------
 1322 # GotoDialog -- Do the "Goto..." dialog
 1323 #---------------------------------------------------------------------------
 1324 proc GotoDialog {} {
 1325     global CurMonth MonthNames CurYear
 1326     catch { destroy .g }
 1327 
 1328     set month [lindex $MonthNames $CurMonth]
 1329     toplevel .g
 1330     wm title .g "Go To Date"
 1331     menubutton .g.mon -text "$month" -menu .g.mon.menu -relief raised
 1332     balloon_add_help .g.mon "Select a month"
 1333     menu .g.mon.menu -tearoff 0
 1334 
 1335     foreach m $MonthNames {
 1336     .g.mon.menu add command -label $m -command ".g.mon configure -text $m"
 1337     }
 1338 
 1339     frame .g.y
 1340     label .g.y.lab -text "Year: "
 1341     entry .g.y.e -width 4
 1342     balloon_add_help .g.y.e "Enter a year"
 1343     .g.y.e insert end $CurYear
 1344     bind .g.y.e <Return> ".g.b.go flash; .g.b.go invoke"
 1345     frame .g.b
 1346     button .g.b.go -text "Go" -command {DoGoto}
 1347     balloon_add_help .g.b.go "Go to specified month and year"
 1348     button .g.b.cancel -text "Cancel" -command { destroy .g }
 1349     pack .g.b.go .g.b.cancel -expand 1 -fill x -side left
 1350     pack .g.mon -fill x -expand 1
 1351 
 1352     pack .g.y.lab -side left
 1353     pack .g.y.e -side left -fill x -expand 1
 1354     pack .g.y -expand 1 -fill x
 1355     pack .g.b -expand 1 -fill x
 1356     bind .g <KeyPress-Escape> ".g.b.cancel flash; .g.b.cancel invoke"
 1357     CenterWindow .g .
 1358     set oldFocus [focus]
 1359     grab .g
 1360     focus .g.y.e
 1361     tkwait window .g
 1362     catch {focus $oldFocus}
 1363 }
 1364 
 1365 #---------------------------------------------------------------------------
 1366 # DoGoto -- go to specified date
 1367 #---------------------------------------------------------------------------
 1368 proc DoGoto {} {
 1369     global CurYear CurMonth MonthNames
 1370     set year [.g.y.e get]
 1371     if { ! [regexp {^[0-9]+$} $year] } {
 1372     tk_dialog .error Error {Illegal year specified (1990-5990)} error 0 Ok
 1373     return
 1374     }
 1375     if { $year < 1990 || $year > 5990 } {
 1376     tk_dialog .error Error {Illegal year specified (1990-5990)} error 0 Ok
 1377     return
 1378     }
 1379     set month [lsearch -exact $MonthNames [.g.mon cget -text]]
 1380     set CurMonth $month
 1381     set CurYear $year
 1382     destroy .g
 1383     FillCalWindow
 1384 }
 1385 
 1386 #---------------------------------------------------------------------------
 1387 # Quit -- handle the Quit button
 1388 #---------------------------------------------------------------------------
 1389 proc Quit {} {
 1390     global Option
 1391     global InotifyFP
 1392     if { !$Option(ConfirmQuit) } {
 1393     destroy .
 1394     StopBackgroundRemindDaemon
 1395         catch { exec kill [pid $InotifyFP] }
 1396         catch { close $InotifyFP }
 1397     exit 0
 1398     }
 1399     if { [tk_dialog .question "Confirm..." {Really quit?} question 0 No Yes] } {
 1400     destroy .
 1401     StopBackgroundRemindDaemon
 1402         catch { exec kill [pid $InotifyFP] }
 1403         catch { close $InotifyFP }
 1404     exit 0
 1405     }
 1406 }
 1407 
 1408 #---------------------------------------------------------------------------
 1409 # CreateModifyDialog -- create dialog for adding a reminder
 1410 # Arguments:
 1411 #    w -- path of parent window
 1412 #    day -- day number of month
 1413 #    firstDay -- day number of first day of month
 1414 #    args -- buttons to add to bottom frame.  First sets result to 1, next
 1415 #            to 2, and so on.  FIRST BUTTON MUST BE "Cancel"
 1416 #---------------------------------------------------------------------------
 1417 proc CreateModifyDialog {w day firstDay args} {
 1418 
 1419     # Set up: Year, Month, Day, WeekdayName
 1420     global CurYear CurMonth EnglishDayNames MonthNames OptionType SkipType
 1421     global ModifyDialogResult TwentyFourHourMode
 1422 
 1423     set OptionType 1
 1424     set SkipType 1
 1425 
 1426     set year $CurYear
 1427     set month [lindex $MonthNames $CurMonth]
 1428     set wkday [lindex $EnglishDayNames [expr ($day+$firstDay-1) % 7]]
 1429 
 1430     frame $w.o -border 4 -relief ridge
 1431     frame $w.o1 -border 4
 1432     frame $w.o2 -border 4
 1433     frame $w.o3 -border 4
 1434     frame $w.exp -border 4
 1435     frame $w.adv -border 4
 1436     frame $w.weekend -border 4
 1437     frame $w.durationbox -border 4
 1438     frame $w.time -border 4
 1439     frame $w.hol -border 4
 1440     frame $w.msg
 1441     frame $w.buttons
 1442     pack $w.o1 $w.o2 $w.o3  -side top -anchor w -in $w.o
 1443     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
 1444     pack $w.buttons -side top -anchor w -pady 4 -expand 1 -fill x
 1445 
 1446     # TYPE 1 REMINDER
 1447     radiobutton $w.type1 -variable OptionType -value 1
 1448     menubutton $w.day1 -text $day -relief raised -menu $w.day1.menu
 1449     balloon_add_help $w.day1 "Select a day"
 1450     CreateDayMenu $w.day1
 1451     menubutton $w.mon1 -text $month -relief raised -menu $w.mon1.menu
 1452     balloon_add_help $w.mon1 "Select a month"
 1453     CreateMonthMenu $w.mon1
 1454     menubutton $w.year1 -text $year -relief raised -menu $w.year1.menu
 1455     balloon_add_help $w.year1 "Select a year"
 1456     CreateYearMenu $w.year1
 1457     checkbutton $w.repbut -text "and repeating every"
 1458     balloon_add_help $w.repbut "Select to enable a recurring reminder"
 1459     $w.repbut deselect
 1460     menubutton $w.repdays -text 1 -relief raised -menu $w.repdays.menu
 1461     balloon_add_help $w.repdays "Select the repeat interval in days"
 1462     CreateDayMenu $w.repdays 1 28 0
 1463     label $w.label1a -text "day(s) thereafter"
 1464     pack $w.type1 $w.day1 $w.mon1 $w.year1 $w.repbut $w.repbut $w.repdays $w.label1a -side left -anchor w -in $w.o1
 1465 
 1466     # TYPE 2 REMINDER
 1467     radiobutton $w.type2 -variable OptionType -value 2
 1468     label $w.label2a -text First
 1469     menubutton $w.wkday2 -text $wkday -relief raised -menu $w.wkday2.menu
 1470     balloon_add_help $w.wkday2 "Select a day of the week"
 1471     CreateWeekdayMenu $w.wkday2
 1472     label $w.label2b -text "on or after"
 1473     menubutton $w.day2 -text $day -relief raised -menu $w.day2.menu
 1474     balloon_add_help $w.day2 "Select a day"
 1475     CreateDayMenu $w.day2 1 31 0
 1476     menubutton $w.mon2 -text $month -relief raised -menu $w.mon2.menu
 1477     balloon_add_help $w.mon2 "Select a month"
 1478     CreateMonthMenu $w.mon2
 1479     menubutton $w.year2 -text $year -relief raised -menu $w.year2.menu
 1480     balloon_add_help $w.year2 "Select a year"
 1481     CreateYearMenu $w.year2
 1482     pack $w.type2 $w.label2a $w.wkday2 $w.label2b $w.day2 $w.mon2 $w.year2 -side left -anchor w -in $w.o2
 1483 
 1484     # TYPE 3 REMINDER
 1485     if { $day <= 7 } {
 1486     set which "First"
 1487     } elseif {$day <= 14} {
 1488     set which "Second"
 1489     } elseif {$day <= 21} {
 1490     set which "Third"
 1491     } elseif {$day <= 28} {
 1492     set which "Fourth"
 1493     } else {
 1494     set which "Last"
 1495     }
 1496     radiobutton $w.type3 -variable OptionType -value 3
 1497     menubutton $w.ordinal -text $which -relief raised -menu $w.ordinal.menu
 1498     balloon_add_help $w.ordinal "Select the first, second, etc. weekday in a month"
 1499     menu $w.ordinal.menu -tearoff 0
 1500     $w.ordinal.menu add command -label "First" -command "$w.ordinal configure -text First"
 1501     $w.ordinal.menu add command -label "Second" -command "$w.ordinal configure -text Second"
 1502     $w.ordinal.menu add command -label "Third" -command "$w.ordinal configure -text Third"
 1503     $w.ordinal.menu add command -label "Fourth" -command "$w.ordinal configure -text Fourth"
 1504     $w.ordinal.menu add command -label "Last" -command "$w.ordinal configure -text Last"
 1505     $w.ordinal.menu add command -label "Every" -command "$w.ordinal configure -text Every"
 1506     menubutton $w.wkday3 -text $wkday -relief raised -menu $w.wkday3.menu
 1507     balloon_add_help $w.wkday3 "Select a day of the week"
 1508     CreateWeekdayMenu $w.wkday3
 1509     label $w.label3 -text "in"
 1510     menubutton $w.mon3 -text $month -relief raised -menu $w.mon3.menu
 1511     balloon_add_help $w.mon3 "Select a month"
 1512     CreateMonthMenu $w.mon3
 1513     menubutton $w.year3 -text $year -relief raised -menu $w.year3.menu
 1514     balloon_add_help $w.year3 "Select a year"
 1515     CreateYearMenu $w.year3
 1516     pack $w.type3 $w.ordinal $w.wkday3 $w.label3 $w.mon3 $w.year3 -side left -anchor w -in $w.o3
 1517 
 1518     # EXPIRY DATE
 1519     checkbutton $w.expbut -text "Expire after"
 1520     balloon_add_help $w.expbut "Select to enable an expiry date"
 1521     $w.expbut deselect
 1522     menubutton $w.expday -text $day -relief raised -menu $w.expday.menu
 1523     balloon_add_help $w.expday "Select expiry day"
 1524     CreateDayMenu $w.expday 1 31 0
 1525     menubutton $w.expmon -text $month -relief raised -menu $w.expmon.menu
 1526     balloon_add_help $w.expmon "Select expiry month"
 1527     CreateMonthMenu $w.expmon 0
 1528     menubutton $w.expyear -text $year -relief raised -menu $w.expyear.menu
 1529     balloon_add_help $w.expyear "Select expiry year"
 1530     CreateYearMenu $w.expyear 0
 1531 
 1532     pack $w.expbut $w.expday $w.expmon $w.expyear -side left -anchor w -in $w.exp
 1533 
 1534     # ADVANCE NOTICE
 1535     checkbutton $w.advbut -text "Issue"
 1536     balloon_add_help $w.advbut "Select to enable advance notification"
 1537     $w.advbut deselect
 1538     menubutton $w.advdays -text 3 -menu $w.advdays.menu -relief raised
 1539     balloon_add_help $w.advdays "Select number of days of advance warning"
 1540     CreateDayMenu $w.advdays 1 10 0
 1541     label $w.advlab -text "day(s) in advance"
 1542     checkbutton $w.advcount -text "not counting holidays/weekend"
 1543     balloon_add_help $w.advcount "Select to avoid counting holidays/weekend as in advance warning days"
 1544     $w.advcount select
 1545     pack $w.advbut $w.advdays $w.advlab $w.advcount -side left -anchor w -in $w.adv
 1546 
 1547     # WEEKEND
 1548     label $w.weeklab -text "Weekend is: "
 1549     pack $w.weeklab -side left -anchor w -in $w.weekend
 1550     foreach d $EnglishDayNames {
 1551     checkbutton $w.d$d -text $d
 1552     balloon_add_help $w.d$d "Select to include $d in the definition of \"Weekend\""
 1553     $w.d$d deselect
 1554     pack $w.d$d -side left -anchor w -in $w.weekend
 1555     }
 1556     $w.dSaturday select
 1557     $w.dSunday select
 1558 
 1559     # TIMED REMINDER
 1560     checkbutton $w.timebut -text "Timed reminder at"
 1561     balloon_add_help $w.timebut "Select if this event starts at a specific time"
 1562     $w.timebut deselect
 1563     menubutton $w.timehour -text "12" -menu $w.timehour.menu -relief raised
 1564     balloon_add_help $w.timehour "Select the starting time's hour"
 1565     if {$TwentyFourHourMode} {
 1566     CreateDayMenu $w.timehour 0 23 0
 1567     } else {
 1568     CreateDayMenu $w.timehour 1 12 0
 1569     }
 1570     menubutton $w.timemin -text "00" -menu $w.timemin.menu -relief raised
 1571     balloon_add_help $w.timemin "Select the starting time's minute"
 1572     menu $w.timemin.menu -tearoff 0
 1573     foreach i {00 05 10 15 20 25 30 35 40 45 50 55} {
 1574     $w.timemin.menu add command -label $i \
 1575         -command "$w.timemin configure -text $i"
 1576     }
 1577 
 1578     if {!$TwentyFourHourMode} {
 1579     menubutton $w.ampm -text "PM" -menu $w.ampm.menu -relief raised
 1580     balloon_add_help $w.ampm "Select whether the time is AM or PM"
 1581     menu $w.ampm.menu -tearoff 0
 1582     $w.ampm.menu add command -label "AM" -command "$w.ampm configure -text {AM}"
 1583     $w.ampm.menu add command -label "PM" -command "$w.ampm configure -text {PM}"
 1584     }
 1585 
 1586     checkbutton $w.timeadvbut -text "with"
 1587     balloon_add_help $w.timeadvbut "Select to be given advance warning prior to the start time"
 1588     $w.timeadvbut deselect
 1589     menubutton $w.timeadv -text "15" -menu $w.timeadv.menu -relief raised
 1590     balloon_add_help $w.timeadv "Select the number of minutes of advance warning"
 1591     menu $w.timeadv.menu -tearoff 0
 1592     foreach i {5 10 15 30 45 60} {
 1593     $w.timeadv.menu add command -label $i -command "$w.timeadv configure -text $i"
 1594     }
 1595     label $w.timelab1 -text "minutes advance notice"
 1596 
 1597     checkbutton $w.timerepbut -text "repeated every"
 1598     balloon_add_help $w.timerepbut "Select to repeat the advance notice"
 1599     $w.timerepbut deselect
 1600     menubutton $w.timerep -text "5" -menu $w.timerep.menu -relief raised
 1601     balloon_add_help $w.timerep "Select how often to repeat the advance notice"
 1602     menu $w.timerep.menu -tearoff 0
 1603     foreach i {3 5 10 15 30} {
 1604     $w.timerep.menu add command -label $i -command "$w.timerep configure -text $i"
 1605     }
 1606     label $w.timelab2 -text "minutes"
 1607     if {$TwentyFourHourMode} {
 1608     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
 1609     } else {
 1610     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
 1611     }
 1612 
 1613     # DURATION
 1614     checkbutton $w.durationbut -text "Duration"
 1615     balloon_add_help $w.durationbut "Select if this event has a specific duration"
 1616     $w.durationbut deselect
 1617     menubutton $w.durationh -text "1" -menu $w.durationh.menu -relief raised
 1618     balloon_add_help $w.durationh "Select how many hours the event lasts"
 1619     menu $w.durationh.menu -tearoff 0
 1620     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} {
 1621     $w.durationh.menu add command -label $i -command "$w.durationh configure -text $i"
 1622     }
 1623     label $w.durationcolon -text ":"
 1624     menubutton $w.durationm -text "00" -menu $w.durationm.menu -relief raised
 1625     balloon_add_help $w.durationm "Select how many minutes the event lasts (in addition to the hours)"
 1626     menu $w.durationm.menu -tearoff 0
 1627     foreach i {00 15 30 45} {
 1628     $w.durationm.menu add command -label $i -command "$w.durationm configure -text $i"
 1629     }
 1630     pack $w.durationbut $w.durationh $w.durationcolon $w.durationm -side left -anchor w -in $w.durationbox
 1631 
 1632     # SKIP TYPE
 1633     label $w.labhol -text "On holidays or weekends:"
 1634     radiobutton $w.issue -variable SkipType -value 1 -text "Issue reminder as usual"
 1635     radiobutton $w.skip -variable SkipType -value 2 -text "Skip reminder"
 1636     radiobutton $w.before -variable SkipType -value 3 -text "Move reminder before holiday or weekend"
 1637     radiobutton $w.after -variable SkipType -value 4 -text "Move reminder after holiday or weekend"
 1638     pack $w.labhol $w.issue $w.skip $w.before $w.after -side top -anchor w -in $w.hol
 1639 
 1640     # TEXT ENTRY
 1641     label $w.msglab -text "Body:"
 1642     entry $w.entry
 1643     balloon_add_help $w.entry "Enter the text of the reminder"
 1644     pack $w.msglab -side left -anchor w -in $w.msg
 1645     pack $w.entry -side left -anchor w -expand 1 -fill x -in $w.msg
 1646 
 1647     # BUTTONS
 1648     set nbut 0
 1649     foreach but $args {
 1650     incr nbut
 1651     button $w.but$nbut -text $but -command "set ModifyDialogResult $nbut"
 1652     pack $w.but$nbut -side left -anchor w -in $w.buttons -expand 1 -fill x
 1653     }
 1654 
 1655     bind $w <KeyPress-Escape> "$w.but1 flash; $w.but1 invoke"
 1656     if {$nbut >= 2} {
 1657     bind $w.entry <KeyPress-Return> "$w.but2 flash; $w.but2 invoke"
 1658     }
 1659     set ModifyDialogResult 0
 1660 
 1661     # Center the window on the root
 1662     CenterWindow $w .
 1663 }
 1664 
 1665 #***********************************************************************
 1666 # %PROCEDURE: RemindDialogToOptions
 1667 # %ARGUMENTS:
 1668 #  w -- dialog window
 1669 # %RETURNS:
 1670 #  A list of flag/value pairs representing the current state of
 1671 #  the "create reminder" dialog.
 1672 #***********************************************************************
 1673 proc RemindDialogToOptions { w } {
 1674     global OptionType SkipType repbut expbut advbut advcount
 1675     global timebut timeadvbut timerepbut durationbut
 1676     global dSaturday dSunday dMonday dTuesday dWednesday dThursday dFriday
 1677     set ans {}
 1678     lappend ans "-global-OptionType" $OptionType
 1679     lappend ans "-global-SkipType" $SkipType
 1680     foreach win [winfo children $w] {
 1681     set winstem [winfo name $win]
 1682     switch -exact -- [winfo class $win] {
 1683         "Menubutton" {
 1684         lappend ans "-text-$winstem" [$win cget -text]
 1685         }
 1686         "Checkbutton" {
 1687         lappend ans "-global-$winstem" [eval set $winstem]
 1688         }
 1689         "Entry" {
 1690         lappend ans "-entry-$winstem" [string map -nocase {"\n" " "} [$win get]]
 1691         }
 1692     }
 1693     }
 1694     return $ans
 1695 }
 1696 
 1697 #***********************************************************************
 1698 # %PROCEDURE: OptionsToRemindDialog
 1699 # %ARGUMENTS:
 1700 #  w -- Remind dialog window
 1701 #  opts -- option list set by RemindDialogToOptions
 1702 # %RETURNS:
 1703 #  Nothing
 1704 # %DESCRIPTION:
 1705 #  Sets parameters in the dialog box according to saved options.
 1706 #***********************************************************************
 1707 proc OptionsToRemindDialog { w opts } {
 1708     global OptionType SkipType repbut expbut advbut advcount
 1709     global timebut timeadvbut timerepbut TwentyFourHourMode durationbut
 1710     global dSaturday dSunday dMonday dTuesday dWednesday dThursday dFriday
 1711     set hour ""
 1712     set ampm ""
 1713     foreach {flag value} $opts {
 1714     switch -glob -- $flag {
 1715         "-text-*" {
 1716         set win [string range $flag 6 end]
 1717         catch { $w.$win configure -text $value }
 1718         if {"$flag" == "-text-ampm"} {
 1719             set ampm $value
 1720         } elseif {"$flag" == "-text-timehour"} {
 1721             set hour $value
 1722         }
 1723         }
 1724         "-global-*" {
 1725         set win [string range $flag 8 end]
 1726         set $win $value
 1727         }
 1728         "-entry-*" {
 1729         set win [string range $flag 7 end]
 1730         $w.$win delete 0 end
 1731         $w.$win insert end $value
 1732         }
 1733     }
 1734     }
 1735     if {"$hour" != ""} {
 1736     if {$TwentyFourHourMode} {
 1737         if {"$ampm" != ""} {
 1738         if {"$ampm" == "PM" && $hour < 12} {
 1739             incr hour 12
 1740             $w.timehour configure -text $hour
 1741         }
 1742         }
 1743     } else {
 1744         if {$hour > 12} {
 1745         incr hour -12
 1746         $w.timehour configure -text $hour
 1747         $w.ampm configure -text "PM"
 1748         } else {
 1749         if {"$ampm" == ""} {
 1750             $w.ampm configure -text "AM"
 1751         }
 1752         }
 1753     }
 1754     }
 1755 }
 1756 
 1757 #---------------------------------------------------------------------------
 1758 # CreateMonthMenu -- create a menu with all the months of the year
 1759 # Arguments:
 1760 #    w -- menu button -- becomes parent of menu
 1761 #    every -- if true, include an "every month" entry
 1762 #---------------------------------------------------------------------------
 1763 proc CreateMonthMenu {w {every 1}} {
 1764     global MonthNames
 1765     menu $w.menu -tearoff 0
 1766 
 1767     if {$every} {
 1768     $w.menu add command -label "every month" -command "$w configure -text {every month}"
 1769     }
 1770 
 1771     foreach month $MonthNames {
 1772     $w.menu add command -label $month -command "$w configure -text $month"
 1773     }
 1774 }
 1775 
 1776 #---------------------------------------------------------------------------
 1777 # CreateWeekdayMenu -- create a menu with all the weekdays
 1778 # Arguments:
 1779 #    w -- menu button -- becomes parent of menu
 1780 #---------------------------------------------------------------------------
 1781 proc CreateWeekdayMenu {w} {
 1782     global EnglishDayNames
 1783     menu $w.menu -tearoff 0
 1784 
 1785     foreach d $EnglishDayNames {
 1786     $w.menu add command -label $d -command "$w configure -text $d"
 1787     }
 1788     $w.menu add command -label "weekday" -command "$w configure -text weekday"
 1789 }
 1790 
 1791 #---------------------------------------------------------------------------
 1792 # CreateDayMenu -- create a menu with entries 1-31 and possibly "every day"
 1793 # Arguments:
 1794 #    w -- menu button -- becomes parent of menu
 1795 #    min -- minimum day to start from.
 1796 #    max -- maximum day to go up to
 1797 #    every -- if true, include an "every day" entry
 1798 #---------------------------------------------------------------------------
 1799 proc CreateDayMenu {w {min 1} {max 31} {every 1}} {
 1800     menu $w.menu -tearoff 0
 1801     if {$every} {
 1802     $w.menu add command -label "every day" -command "$w configure -text {every day}"
 1803     }
 1804     set d $min
 1805     while { $d <= $max } {
 1806     $w.menu add command -label $d -command "$w configure -text $d"
 1807     incr d
 1808     }
 1809 }
 1810 
 1811 #---------------------------------------------------------------------------
 1812 # CreateYearMenu -- create a menu with entries from this year to this year+10
 1813 #                   and possibly "every year"
 1814 # Arguments:
 1815 #    w -- menu button -- becomes parent of menu
 1816 #    every -- if true, include an "every year" entry
 1817 #---------------------------------------------------------------------------
 1818 proc CreateYearMenu {w {every 1}} {
 1819     menu $w.menu -tearoff 0
 1820     if {$every} {
 1821     $w.menu add command -label "every year" -command "$w configure -text {every year}"
 1822     }
 1823     global CurYear
 1824     set d $CurYear
 1825     while { $d < [expr $CurYear + 11] } {
 1826     $w.menu add command -label $d -command "$w configure -text $d"
 1827     incr d
 1828     }
 1829 }
 1830 
 1831 #---------------------------------------------------------------------------
 1832 # ModifyDay -- bring up dialog for adding reminder.
 1833 # Arguments:
 1834 #    d -- which day to modify
 1835 #    firstDay -- first weekday in month (0-6)
 1836 #---------------------------------------------------------------------------
 1837 proc ModifyDay {d firstDay} {
 1838     global ModifyDialogResult AppendFile HighestTagSoFar ReminderTags
 1839     catch {destroy .mod}
 1840     toplevel .mod
 1841     CreateModifyDialog .mod $d $firstDay "Cancel" "Add to reminder file" "Preview reminder"
 1842     wm title .mod "TkRemind Add Reminder..."
 1843     wm iconname .mod "Add Reminder"
 1844     tkwait visibility .mod
 1845     set oldFocus [focus]
 1846     while {1} {
 1847     grab .mod
 1848         raise .mod
 1849     focus .mod.entry
 1850     set ModifyDialogResult -1
 1851     tkwait variable ModifyDialogResult
 1852     if {$ModifyDialogResult == 1} {
 1853         catch {focus $oldFocus}
 1854         destroy .mod
 1855         return 0
 1856     }
 1857     set problem [catch {set rem [CreateReminder .mod]} err]
 1858     if {$problem} {
 1859         tk_dialog .error Error "$err" error 0 Ok
 1860     } else {
 1861         if {$ModifyDialogResult == 3} {
 1862         set rem [EditReminder $rem Cancel "Add reminder"]
 1863         if {$ModifyDialogResult == 1} {
 1864             continue
 1865         }
 1866         }
 1867         set opts [RemindDialogToOptions .mod]
 1868         catch {focus $oldFocus}
 1869         destroy .mod
 1870         Status "Writing reminder..."
 1871         set f [open $AppendFile a]
 1872         incr HighestTagSoFar
 1873         set ReminderTags($HighestTagSoFar) 1
 1874 
 1875         WriteReminder $f TKTAG$HighestTagSoFar $rem $opts
 1876         close $f
 1877 
 1878             ScheduleUpdateForChanges
 1879         return 0
 1880     }
 1881     }
 1882 }
 1883 
 1884 #---------------------------------------------------------------------------
 1885 # CenterWindow -- center a window on the screen or over a parent.
 1886 #                 Stolen from tk_dialog code
 1887 # Arguments:
 1888 #    w -- window to center
 1889 #    parent -- window over which to center.  Defaults to screen if not supplied.
 1890 #---------------------------------------------------------------------------
 1891 proc CenterWindow {w {parent {}}} {
 1892     wm withdraw $w
 1893     update idletasks
 1894     if {"$parent" == ""} {
 1895     set x [expr [winfo screenwidth $w]/2 - [winfo reqwidth $w]/2 \
 1896            - [winfo vrootx [winfo parent $w]]]
 1897     set y [expr [winfo screenheight $w]/2 - [winfo reqheight $w]/2 \
 1898            - [winfo vrooty [winfo parent $w]]]
 1899     } else {
 1900     set x [expr [winfo rootx $parent] + [winfo width $parent]/2 - [winfo reqwidth $w]/2]
 1901     set y [expr [winfo rooty $parent] + [winfo height $parent]/2 - [winfo reqheight $w]/2]
 1902     }
 1903     wm geom $w +$x+$y
 1904     wm deiconify $w
 1905 }
 1906 
 1907 #---------------------------------------------------------------------------
 1908 # CreateReminder -- create the reminder
 1909 # Arguments:
 1910 #    w -- the window containing the add reminder dialog box.
 1911 # Returns:
 1912 #    The reminder as a string.
 1913 #---------------------------------------------------------------------------
 1914 proc CreateReminder {w} {
 1915     global DidOmit TwentyFourHourMode
 1916 
 1917     set body [string trim [$w.entry get]]
 1918 
 1919     if {"$body" == ""} {
 1920     error "Blank body in reminder"
 1921     }
 1922 
 1923     set DidOmit 0
 1924     set needOmit 0
 1925     # Delegate the first part to CreateReminder1, CreateReminder2, or
 1926     # CreateReminder3
 1927     global OptionType SkipType repbut expbut advbut advcount
 1928     global timebut timeadvbut timerepbut durationbut
 1929 
 1930     set rem [CreateReminder$OptionType $w]
 1931 
 1932     # Do the "until" part
 1933     if {$expbut} {
 1934     append rem " UNTIL "
 1935     append rem [consolidate [$w.expyear cget -text] [$w.expmon cget -text] [$w.expday cget -text]]
 1936     }
 1937 
 1938     # Advance warning
 1939     if {$advbut} {
 1940     append rem " +"
 1941     if {!$advcount} {
 1942         append rem "+"
 1943     } else {
 1944         set needOmit 1
 1945     }
 1946     append rem [$w.advdays cget -text]
 1947     }
 1948 
 1949     # Timed reminder
 1950     if {$timebut} {
 1951     set hour [$w.timehour cget -text]
 1952     set min [$w.timemin cget -text]
 1953     if {!$TwentyFourHourMode} {
 1954         if {[$w.ampm cget -text] == "PM"} {
 1955         if {$hour < 12} {
 1956             incr hour 12
 1957         }
 1958         } else {
 1959         if {$hour == 12} {
 1960             set hour 0
 1961         }
 1962         }
 1963     }
 1964     append rem " AT $hour:$min"
 1965     if {$timeadvbut} {
 1966         append rem " +[$w.timeadv cget -text]"
 1967     }
 1968     if {$timerepbut} {
 1969         append rem " *[$w.timerep cget -text]"
 1970     }
 1971     if {$durationbut} {
 1972         append rem " DURATION [$w.durationh cget -text]:[$w.durationm cget -text]"
 1973     }
 1974     }
 1975 
 1976     global SkipType
 1977     if {$SkipType == 2} {
 1978     append rem " SKIP"
 1979     set needOmit 1
 1980     } elseif {$SkipType == 3} {
 1981     append rem " BEFORE"
 1982     set needOmit 1
 1983     } elseif {$SkipType == 4} {
 1984     append rem " AFTER"
 1985     set needOmit 1
 1986     }
 1987 
 1988     if {$needOmit && !$DidOmit} {
 1989     append rem " OMIT [GetWeekend $w 1]"
 1990     }
 1991 
 1992 
 1993     # Check it out!
 1994     global Remind
 1995     set f [open "|$Remind -arq -e - 2>&1" r+]
 1996     puts $f "BANNER %"
 1997     puts $f "$rem MSG %"
 1998     puts $f "MSG %_%_%_%_"
 1999     puts $f "FLUSH"
 2000     flush $f
 2001     set err {}
 2002     catch {set err [gets $f]}
 2003     catch {close $f}
 2004     if {"$err" != ""} {
 2005     # Clean up the message a bit
 2006     regsub -- {^-stdin-\([0-9]*\): } $err {} err
 2007     error "Error from Remind: $err"
 2008     }
 2009     append rem " MSG " [string map -nocase {"\n" " "} $body]
 2010     return $rem
 2011 }
 2012 
 2013 # We used to return YYYY-MM-DD, but reverted to
 2014 # day monthname year because this lets Remind produce
 2015 # much better error messages.
 2016 proc consolidate {y m d} {
 2017     global MonthNames
 2018     if {![regexp {^[0-9]+$} $m]} {
 2019     set m [lsearch -exact $MonthNames $m]
 2020     incr m
 2021     }
 2022     set mname [lindex $MonthNames [expr $m-1]]
 2023     return "$d $mname $y"
 2024 }
 2025 
 2026 #---------------------------------------------------------------------------
 2027 # CreateReminder1 -- Create the first part of a type-1 reminder
 2028 # Arguments:
 2029 #    w -- add reminder dialog window
 2030 # Returns: first part of reminder
 2031 #---------------------------------------------------------------------------
 2032 proc CreateReminder1 {w} {
 2033 
 2034     global repbut
 2035 
 2036     set rem "REM"
 2037     set gotDay 0
 2038     set gotMon 0
 2039     set gotYear 0
 2040     set d [$w.day1 cget -text]
 2041     set m [$w.mon1 cget -text]
 2042     set y [$w.year1 cget -text]
 2043     if {"$d" != "every day" && "$m" != "every month" && $y != "every year"} {
 2044     set gotDay 1
 2045     set gotMon 1
 2046     set gotYear 1
 2047     append rem " "
 2048     append rem [consolidate $y $m $d]
 2049     } else {
 2050     if {"$d" != "every day"} {
 2051         append rem " $d"
 2052         set gotDay 1
 2053     }
 2054     if {"$m" != "every month"} {
 2055         append rem " $m"
 2056         set gotMon 1
 2057     }
 2058     if {"$y" != "every year"} {
 2059         append rem " $y"
 2060         set gotYear 1
 2061     }
 2062     }
 2063 
 2064     # Check for repetition
 2065     if {$repbut} {
 2066     if {!$gotDay || !$gotMon || !$gotYear} {
 2067         error "All components of a date must be specified if you wish to use the repeat feature."
 2068     }
 2069     append rem " *[$w.repdays cget -text]"
 2070     }
 2071 
 2072     return $rem
 2073 }
 2074 
 2075 #---------------------------------------------------------------------------
 2076 # CreateReminder2 -- Create the first part of a type-2 reminder
 2077 # Arguments:
 2078 #    w -- add reminder dialog window
 2079 # Returns: first part of reminder
 2080 #---------------------------------------------------------------------------
 2081 proc CreateReminder2 {w} {
 2082     set wkday [$w.wkday2 cget -text]
 2083     if {"$wkday" == "weekday"} {
 2084     set wkday [GetWeekend $w 0]
 2085     }
 2086     set day [$w.day2 cget -text]
 2087     set mon [$w.mon2 cget -text]
 2088     set year [$w.year2 cget -text]
 2089     if {$mon != "every month" && $year != "every year"} {
 2090     set rem "REM $wkday "
 2091     append rem [consolidate $year $mon $day]
 2092     } else {
 2093     set rem "REM $wkday $day"
 2094     if {$mon != "every month"} {
 2095         append rem " $mon"
 2096     }
 2097     if {$year != "every year"} {
 2098         append rem " $year"
 2099     }
 2100     }
 2101     return $rem
 2102 }
 2103 
 2104 #---------------------------------------------------------------------------
 2105 # CreateReminder3 -- Create the first part of a type-3 reminder
 2106 # Arguments:
 2107 #    w -- add reminder dialog window
 2108 # Returns: first part of reminder
 2109 #---------------------------------------------------------------------------
 2110 proc CreateReminder3 {w} {
 2111     global MonthNames DidOmit
 2112     set which [$w.ordinal cget -text]
 2113     set day [$w.wkday3 cget -text]
 2114     set mon [$w.mon3 cget -text]
 2115     set year [$w.year3 cget -text]
 2116     set rem "REM"
 2117     if {$which != "Last"} {
 2118     if {$which == "First"} {
 2119         append rem " 1"
 2120     } elseif {$which == "Second"} {
 2121         append rem " 8"
 2122     } elseif {$which == "Third"} {
 2123         append rem " 15"
 2124     } elseif {$which == "Fourth"} {
 2125         append rem " 22"
 2126     }
 2127     if {$day != "weekday"} {
 2128         append rem " $day"
 2129     } else {
 2130         append rem " [GetWeekend $w 0]"
 2131     }
 2132     if {$mon != "every month"} {
 2133         append rem " $mon"
 2134     }
 2135     if {$year != "every year"} {
 2136         append rem " $year"
 2137     }
 2138     } else {
 2139     if {$day != "weekday"} {
 2140         append rem " $day 1 --7"
 2141     } else {
 2142         append rem " 1 -1 OMIT [GetWeekend $w 1]"
 2143         set DidOmit 1
 2144     }
 2145     if {$mon != "every month"} {
 2146         set i [lsearch -exact $MonthNames $mon]
 2147         if {$i == 11} {
 2148         set i 0
 2149         } else {
 2150         incr i
 2151         }
 2152         append rem " [lindex $MonthNames $i]"
 2153     }
 2154     if {$year != "every year"} {
 2155         if {$mon == "December"} {
 2156         incr year
 2157         }
 2158         append rem " $year"
 2159     }
 2160     }
 2161     return $rem
 2162 }
 2163 
 2164 #---------------------------------------------------------------------------
 2165 # GetWeekend -- returns a list of weekdays or weekend days
 2166 # Arguments:
 2167 #    w -- add reminder dialog window
 2168 #    wkend -- if 1, we want weekend.  If 0, we want weekdays.
 2169 # Returns:
 2170 #    list of weekdays or weekend-days
 2171 #---------------------------------------------------------------------------
 2172 proc GetWeekend {w wkend} {
 2173     global dSaturday dSunday dMonday dTuesday dWednesday dThursday dFriday
 2174     global EnglishDayNames
 2175     set ret {}
 2176     foreach d $EnglishDayNames {
 2177     set v [set d$d]
 2178     if {$v == $wkend} {
 2179         lappend ret $d
 2180     }
 2181     }
 2182     return $ret
 2183 }
 2184 
 2185 #---------------------------------------------------------------------------
 2186 # EditReminder -- allow user to edit what gets put in reminder file
 2187 # Arguments:
 2188 #    rem -- current reminder
 2189 #    args -- buttons to add to bottom
 2190 # Returns:
 2191 #    edited version of rem
 2192 #---------------------------------------------------------------------------
 2193 proc EditReminder {rem args} {
 2194     catch {destroy .edit}
 2195     global ModifyDialogResult
 2196     toplevel .edit
 2197     wm title .edit "TkRemind Preview reminder"
 2198     wm iconname .edit "Preview reminder"
 2199     text .edit.t -width 80 -height 5 -relief sunken
 2200     .edit.t insert end $rem
 2201     frame .edit.f
 2202     set n 0
 2203     foreach but $args {
 2204     incr n
 2205     button .edit.but$n -text $but -command "set ModifyDialogResult $n"
 2206     pack .edit.but$n -in .edit.f -side left -fill x -expand 1
 2207     }
 2208     pack .edit.t -side top -fill both -expand 1
 2209     pack .edit.f -side top -fill x -expand 1
 2210     bind .edit <KeyPress-Escape> ".edit.but1 flash; .edit.but1 invoke"
 2211     set ModifyDialogResult 0
 2212     CenterWindow .edit .
 2213     tkwait visibility .edit
 2214     set oldFocus [focus]
 2215     focus .edit.t
 2216     grab .edit
 2217     tkwait variable ModifyDialogResult
 2218     catch {focus $oldFocus}
 2219     set rem [.edit.t get 1.0 end]
 2220     catch {destroy .edit}
 2221     return $rem
 2222 }
 2223 
 2224 #---------------------------------------------------------------------------
 2225 # SetWinAttr -- sets an attribute for a window
 2226 # Arguments:
 2227 #    w -- window name
 2228 #    attr -- attribute name
 2229 #    val -- value to set it to
 2230 # Returns:
 2231 #    $val
 2232 #---------------------------------------------------------------------------
 2233 proc SetWinAttr {w attr val} {
 2234     global attrPriv
 2235     set attrPriv($w-$attr) $val
 2236 }
 2237 
 2238 #---------------------------------------------------------------------------
 2239 # GetWinAttr -- gets an attribute for a window
 2240 # Arguments:
 2241 #    w -- window name
 2242 #    attr -- attribute name
 2243 # Returns:
 2244 #    Value of attribute
 2245 #---------------------------------------------------------------------------
 2246 proc GetWinAttr {w attr} {
 2247     global attrPriv
 2248     return $attrPriv($w-$attr)
 2249 }
 2250 
 2251 #---------------------------------------------------------------------------
 2252 # WaitWinAttr -- wait for a window attribute to change
 2253 # Arguments:
 2254 #    w -- window name
 2255 #    attr -- attribute name
 2256 # Returns:
 2257 #    Value of attribute
 2258 #---------------------------------------------------------------------------
 2259 proc WaitWinAttr {w attr} {
 2260     global attrPriv
 2261     tkwait variable attrPriv($w-$attr)
 2262     return $attrPriv($w-$attr)
 2263 }
 2264 
 2265 #---------------------------------------------------------------------------
 2266 # BrowseForFile -- creates and operates a file browser dialog.
 2267 # Arguments:
 2268 #    w -- dialog window.
 2269 #    title -- dialog title
 2270 #    oktext -- text for "OK" button
 2271 #    showdots -- if non-zero, shows "dot" files as well.
 2272 # Returns:
 2273 #    complete path of filename chosen, or "" if Cancel pressed.
 2274 #---------------------------------------------------------------------------
 2275 proc BrowseForFile {w title {oktext "OK"} {showdots 0} {filter "*"}} {
 2276     catch {destroy $w}
 2277     toplevel $w
 2278     wm title $w $title
 2279 
 2280     # Global array to hold window attributes
 2281     global a${w}
 2282 
 2283     SetWinAttr $w status busy
 2284     SetWinAttr $w showdots $showdots
 2285 
 2286     frame $w.fileframe
 2287     frame $w.butframe
 2288     label $w.cwd -text [pwd]
 2289     entry $w.entry
 2290     label $w.masklab -text "Match: "
 2291     listbox $w.list -yscrollcommand "$w.scroll set"
 2292     scrollbar $w.scroll -command "$w.list yview"
 2293     button $w.ok -text $oktext -command "BrowseForFileOK $w"
 2294     button $w.cancel -text "Cancel" -command "BrowseForFileCancel $w"
 2295     entry $w.filter -width 7
 2296     $w.filter insert end $filter
 2297 
 2298     pack $w.cwd $w.entry -side top -expand 0 -fill x
 2299     pack $w.fileframe -side top -expand 1 -fill both
 2300     pack $w.butframe -side top -expand 0 -fill x
 2301     pack $w.list -in $w.fileframe -side left -expand 1 -fill both
 2302     pack $w.scroll -in $w.fileframe -side left -expand 0 -fill y
 2303     pack $w.ok -in $w.butframe -side left -expand 1 -fill x
 2304     pack $w.cancel -in $w.butframe -side left -expand 1 -fill x
 2305     pack $w.masklab -in $w.butframe -side left -expand 0
 2306     pack $w.filter -in $w.butframe -side left -expand 1 -fill x
 2307 
 2308     # Fill in the box and wait for status to change
 2309     BrowseForFileRead $w [pwd]
 2310 
 2311     bind $w <KeyPress-Escape> "$w.cancel flash; $w.cancel invoke"
 2312     bind $w.list <Button-1> "$w.entry delete 0 end; $w.entry insert 0 \[selection get\]"
 2313     bind $w.list <Double-Button-1> "$w.ok flash; $w.ok invoke"
 2314     bind $w.list <Return> "$w.entry delete 0 end; $w.entry insert 0 \[selection get\]; $w.ok flash; $w.ok invoke"
 2315     bind $w.entry <Return> "$w.ok flash; $w.ok invoke"
 2316     bind $w.filter <Return> "BrowseForFileRead $w"
 2317     bind $w.entry <KeyPress> "CompleteFile $w"
 2318     bind $w.entry <KeyPress-space> "ExpandFile $w"
 2319     bindtags $w.entry "Entry $w.entry $w all"
 2320 
 2321     bindtags $w.list "Listbox $w.list $w all"
 2322     CenterWindow $w .
 2323     set oldFocus [focus]
 2324     tkwait visibility $w
 2325     focus $w.entry
 2326     set oldGrab [grab current $w]
 2327     grab set $w
 2328     WaitWinAttr $w status
 2329     catch {focus $oldFocus}
 2330     catch {grab set $oldGrab}
 2331     set ans [GetWinAttr $w status]
 2332     destroy $w
 2333     return $ans
 2334 }
 2335 
 2336 proc CompleteFile {w} {
 2337     set index [lsearch [$w.list get 0 end] [$w.entry get]* ]
 2338     if {$index > -1} {
 2339     $w.list see $index
 2340     $w.list selection clear 0 end
 2341     $w.list selection set $index
 2342     }
 2343 }
 2344 
 2345 proc ExpandFile {w} {
 2346     set stuff [$w.list curselection]
 2347     if {[string compare $stuff ""]} {
 2348     $w.entry delete 0 end
 2349     $w.entry insert end [$w.list get $stuff]
 2350     }
 2351 }
 2352 
 2353 proc BrowseForFileCancel {w} {
 2354     SetWinAttr $w status {}
 2355 }
 2356 
 2357 proc BrowseForFileOK {w} {
 2358     set fname [$w.entry get]
 2359     if {[string compare $fname ""]} {
 2360     # If it starts with a slash, handle it specially.
 2361     if {[string match "/*" $fname]} {
 2362         if {[file isdirectory $fname]} {
 2363         BrowseForFileRead $w $fname
 2364         return
 2365         } else {
 2366         SetWinAttr $w status $fname
 2367         return
 2368         }
 2369     }
 2370     if {[string match */ $fname]} {
 2371         set fname [string trimright $fname /]
 2372     }
 2373     if {[$w.cwd cget -text] == "/"} {
 2374         set fname "/$fname"
 2375     } else {
 2376         set fname "[$w.cwd cget -text]/$fname"
 2377     }
 2378     # If it's a directory, change directories
 2379     if {[file isdirectory $fname]} {
 2380         BrowseForFileRead $w $fname
 2381     } else {
 2382         SetWinAttr $w status $fname
 2383     }
 2384     }
 2385 }
 2386 
 2387 #---------------------------------------------------------------------------
 2388 # BrowseForFileRead -- read the current directory into the file browser
 2389 # Arguments:
 2390 #    w -- window name
 2391 #    dir -- directory
 2392 # Returns:
 2393 #    nothing
 2394 #---------------------------------------------------------------------------
 2395 proc BrowseForFileRead {w {dir ""}} {
 2396     # Save working dir
 2397     set cwd [pwd]
 2398     if {$dir == ""} {
 2399     set dir [$w.cwd cget -text]
 2400     }
 2401     if {[catch "cd $dir" err]} {
 2402     tk_dialog .error Error "$err" error 0 Ok
 2403     return
 2404     }
 2405     $w.cwd configure -text [pwd]
 2406     if {[GetWinAttr $w showdots]} {
 2407     set flist [glob -nocomplain .* *]
 2408     } else {
 2409     set flist [glob -nocomplain *]
 2410     }
 2411     set flist [lsort $flist]
 2412     set filter [$w.filter get]
 2413     if {$filter == ""} {
 2414     set filter "*"
 2415     }
 2416     $w.list delete 0 end
 2417     foreach item $flist {
 2418     if {$item != "." && $item != ".."} {
 2419         if {[file isdirectory $item]} {
 2420         $w.list insert end "$item/"
 2421         } else {
 2422         if {[string match $filter $item]} {
 2423             $w.list insert end $item
 2424         }
 2425         }
 2426     }
 2427     }
 2428     if {[pwd] != "/"} {
 2429     $w.list insert 0 "../"
 2430     }
 2431     cd $cwd
 2432     $w.entry delete 0 end
 2433 }
 2434 
 2435 #---------------------------------------------------------------------------
 2436 # StartBackgroundRemindDaemon
 2437 # Arguments:
 2438 #    none
 2439 # Returns:
 2440 #    nothing
 2441 # Description:
 2442 #    Starts a background Remind daemon to handle timed reminders
 2443 #---------------------------------------------------------------------------
 2444 proc StartBackgroundRemindDaemon {} {
 2445     global Remind DaemonFile ReminderFile Option TwentyFourHourMode
 2446     if {$TwentyFourHourMode} {
 2447         set problem [catch { set DaemonFile [open "|$Remind -b1 -z0 -itkremind=1 $Option(ExtraRemindArgs) $ReminderFile" "r+"] } err]
 2448     } else {
 2449         set problem [catch { set DaemonFile [open "|$Remind -z0 -itkremind=1 $Option(ExtraRemindArgs) $ReminderFile" "r+"] } err]
 2450     }
 2451     if {$problem} {
 2452     tk_dialog .error Error "Can't start Remind daemon in background: $err" error 0 OK
 2453     } else {
 2454     fileevent $DaemonFile readable "DaemonReadable $DaemonFile"
 2455     puts $DaemonFile "STATUS"
 2456     flush $DaemonFile
 2457     }
 2458 }
 2459 
 2460 #---------------------------------------------------------------------------
 2461 # StopBackgroundRemindDaemon
 2462 # Arguments:
 2463 #    none
 2464 # Returns:
 2465 #    nothing
 2466 # Description:
 2467 #    Stops the background Remind daemon
 2468 #---------------------------------------------------------------------------
 2469 proc StopBackgroundRemindDaemon {} {
 2470     global DaemonFile
 2471     catch {
 2472     puts $DaemonFile "EXIT"
 2473     flush $DaemonFile
 2474     close $DaemonFile
 2475     }
 2476 }
 2477 
 2478 #---------------------------------------------------------------------------
 2479 # RestartBackgroundRemindDaemon
 2480 # Arguments:
 2481 #    none
 2482 # Returns:
 2483 #    nothing
 2484 # Description:
 2485 #    Restarts the background Remind daemon
 2486 #---------------------------------------------------------------------------
 2487 proc RestartBackgroundRemindDaemon {} {
 2488     global DaemonFile ReminderFile ReminderFileModTime
 2489 
 2490     # Don't let the background handler trigger another reread
 2491     catch {
 2492     set mtime [file mtime $ReminderFile]
 2493     set ReminderFileModTime $mtime
 2494     }
 2495 
 2496     catch {
 2497     puts $DaemonFile "REREAD"
 2498     flush $DaemonFile
 2499     }
 2500 }
 2501 
 2502 #---------------------------------------------------------------------------
 2503 # ShowQueue
 2504 # Arguments:
 2505 #    file -- file channel that is readable
 2506 # Returns:
 2507 #    nothing
 2508 # Description:
 2509 #    Dumps the debugging queue listing
 2510 #---------------------------------------------------------------------------
 2511 proc ShowQueue { file } {
 2512     set w .queuedbg
 2513     catch { destroy $w }
 2514     toplevel $w
 2515     wm title $w "Queue (Debugging Output)"
 2516     wm iconname $w "Queue Dbg"
 2517     text $w.t -width 80 -height 30 -wrap word -yscrollcommand "$w.sb set"
 2518     scrollbar $w.sb -orient vertical -command "$w.text yview"
 2519     button $w.ok -text "OK" -command "destroy $w"
 2520     grid $w.t -row 0 -column 0 -sticky nsew
 2521     grid $w.sb -row 0 -column 1 -sticky ns
 2522     grid $w.ok -row 1 -column 0 -sticky w
 2523     grid columnconfigure $w 0 -weight 1
 2524     grid columnconfigure $w 1 -weight 0
 2525     grid rowconfigure $w 0 -weight 1
 2526     grid rowconfigure $w 1 -weight 0
 2527     CenterWindow $w .
 2528     while (1) {
 2529     # We should only get one line
 2530     gets $file line
 2531     if {$line == "NOTE ENDJSONQUEUE"} {
 2532         break
 2533     }
 2534     if {[catch {set obj [::json::json2dict $line]}]} {
 2535         continue;
 2536     }
 2537     foreach q $obj {
 2538         $w.t insert end "$q\n"
 2539     }
 2540     }
 2541     $w.t configure -state disabled
 2542 }
 2543 
 2544 #---------------------------------------------------------------------------
 2545 # DaemonReadable
 2546 # Arguments:
 2547 #    file -- file channel that is readable
 2548 # Returns:
 2549 #    nothing
 2550 # Description:
 2551 #    Reads data from the Remind daemon and handles it appropriately
 2552 #---------------------------------------------------------------------------
 2553 proc DaemonReadable { file } {
 2554     global Ignore
 2555     set line ""
 2556     catch { set num [gets $file line] }
 2557     if {$num < 0} {
 2558     catch { close $file }
 2559     return
 2560     }
 2561     switch -glob -- $line {
 2562     "NOTE reminder*" {
 2563         scan $line "NOTE reminder %s %s %s" time now tag
 2564         IssueBackgroundReminder $file $time $now $tag
 2565     }
 2566     "NOTE JSONQUEUE" {
 2567         ShowQueue $file
 2568     }
 2569     "NOTE newdate" {
 2570         # Date has rolled over -- clear "ignore" list
 2571         catch { unset Ignore}
 2572         Initialize
 2573         FillCalWindow
 2574         ShowTodaysReminders
 2575     }
 2576     "NOTE reread" {
 2577         puts $file "STATUS"
 2578         flush $file
 2579     }
 2580     "NOTE queued*" {
 2581         scan $line "NOTE queued %d" n
 2582         if {$n == 1} {
 2583         .b.nqueued configure -text "1 reminder queued"
 2584         } else {
 2585         .b.nqueued configure -text "$n reminders queued"
 2586         }
 2587     }
 2588     default {
 2589         puts stderr "Unknown message from daemon: $line\n"
 2590     }
 2591     }
 2592 }
 2593 
 2594 #---------------------------------------------------------------------------
 2595 # IssueBackgroundReminder
 2596 # Arguments:
 2597 #    file -- file channel that is readable
 2598 #    time -- time of reminder
 2599 #    now -- current time according to Remind daemon
 2600 #    tag -- tag for reminder, or "*" if no tag
 2601 # Returns:
 2602 #    nothing
 2603 # Description:
 2604 #    Reads a background reminder from daemon and pops up window.
 2605 #---------------------------------------------------------------------------
 2606 proc IssueBackgroundReminder { file time now tag } {
 2607     global BgCounter Option Ignore
 2608     if {$Option(Deiconify)} {
 2609     wm deiconify .
 2610     }
 2611 
 2612     set msg ""
 2613     set line ""
 2614     while (1) {
 2615     gets $file line
 2616     if {$line == "NOTE endreminder"} {
 2617         break
 2618     }
 2619     if {$msg != ""} {
 2620         append msg "\n";
 2621     }
 2622     append msg $line
 2623     }
 2624     # Do nothing if it's blank -- was probably a RUN-type reminder.
 2625     if {$msg == ""} {
 2626     return
 2627     }
 2628 
 2629     # Do nothing if user told us to ignore this reminder
 2630     if {[info exists Ignore($tag)]} {
 2631     return
 2632     }
 2633 
 2634     incr BgCounter
 2635     set w .bg$BgCounter
 2636     toplevel $w
 2637     wm iconname $w "Reminder"
 2638     wm title $w "Timed reminder ($time)"
 2639     label $w.l -text "Reminder for $time issued at $now"
 2640     message $w.msg -width 6i -text $msg
 2641     frame $w.b
 2642 
 2643     # Automatically shut down window after a minute if option says so
 2644     set after_token [after 60000 [list ClosePopup $w "" $Option(MailAddr) $Option(AutoClose) "" $tag $msg $time]]
 2645 
 2646     wm protocol $w WM_DELETE_WINDOW [list ClosePopup $w $after_token "" 1 "" $tag $msg $time]
 2647     button $w.ok -text "OK" -command [list ClosePopup $w $after_token "" 1 "" $tag $msg $time]
 2648     if {$tag != "*"} {
 2649     button $w.nomore -text "Don't remind me again today" -command [list ClosePopup $w $after_token "" 1 "ignore" $tag $msg $time]
 2650     button $w.kill -text "Delete this reminder completely" -command [list ClosePopup $w $after_token "" 1 "kill" $tag $msg $time]
 2651     }
 2652     pack $w.l -side top
 2653     pack $w.msg -side top -expand 1 -fill both
 2654     pack $w.b -side top
 2655     pack $w.ok -in $w.b -side left
 2656     if {$tag != "*"} {
 2657     pack $w.nomore $w.kill -in $w.b -side left
 2658     }
 2659 
 2660     CenterWindow $w .
 2661 
 2662     update
 2663     if {$Option(RingBell)} {
 2664     bell
 2665     }
 2666     if {$Option(RunCmd) != ""} {
 2667     if {$Option(FeedReminder)} {
 2668         FeedReminderToCommand $Option(RunCmd) "$time: $msg"
 2669     } else {
 2670         exec "/bin/sh" "-c" $Option(RunCmd) "&"
 2671     }
 2672     }
 2673 
 2674     # reread status
 2675     if {$file != "stdin"} {
 2676     puts $file "STATUS"
 2677     flush $file
 2678     }
 2679 }
 2680 
 2681 #***********************************************************************
 2682 # %PROCEDURE: FeedReminderToCommand
 2683 # %ARGUMENTS:
 2684 #  cmd -- command to execute
 2685 #  msg -- what to feed it
 2686 # %RETURNS:
 2687 #  Nothing
 2688 # %DESCRIPTION:
 2689 #  Feeds "$msg" to a command.
 2690 #***********************************************************************
 2691 proc FeedReminderToCommand { cmd msg } {
 2692     catch {
 2693     set f [open "|$cmd" "w"]
 2694     fconfigure $f -blocking 0
 2695     fileevent $f writable [list CommandWritable $f $msg]
 2696     }
 2697 }
 2698 
 2699 #***********************************************************************
 2700 # %PROCEDURE: CommandWritable
 2701 # %ARGUMENTS:
 2702 #  f -- file which is writable
 2703 #  msg -- message to write
 2704 # %RETURNS:
 2705 #  Nothing
 2706 # %DESCRIPTION:
 2707 #  Writes $msg to $f; closes $f.
 2708 #***********************************************************************
 2709 proc CommandWritable { f msg } {
 2710     puts $f $msg
 2711     flush $f
 2712     close $f
 2713 }
 2714 
 2715 
 2716 proc main {} {
 2717     global ConfigFile
 2718 
 2719     font create CalboxFont {*}[font actual TkFixedFont]
 2720     font create HeadingFont {*}[font actual TkDefaultFont]
 2721 
 2722     global AppendFile HighestTagSoFar DayNames
 2723     catch {
 2724     puts "\nTkRemind Copyright (C) 1996-2021 Dianne Skoll"
 2725     }
 2726     catch { SetFonts }
 2727     Initialize
 2728 
 2729     # If no $ConfigFile file, create an empty one
 2730     if {![file exists $ConfigFile]} {
 2731     catch {
 2732         set f [open $ConfigFile "w"]
 2733         close $f
 2734     }
 2735     }
 2736     LoadOptions
 2737     CreateMoonImages
 2738     ShowTodaysReminders
 2739     ScanForTags $AppendFile
 2740     CreateCalWindow $DayNames
 2741     FillCalWindow
 2742     StartBackgroundRemindDaemon
 2743     SetupInotify
 2744     DisplayTimeContinuously
 2745 }
 2746 
 2747 #***********************************************************************
 2748 # %PROCEDURE: ScanForTags
 2749 # %ARGUMENTS:
 2750 #  fname -- name of file to scan
 2751 # %RETURNS:
 2752 #  Nothing
 2753 # %DESCRIPTION:
 2754 #  Scans the file for all tags of the form "TKTAGnnnn" and builds
 2755 #  the tag array.  Also adjusts HighestTagSoFar
 2756 #***********************************************************************
 2757 proc ScanForTags { fname } {
 2758     global HighestTagSoFar ReminderTags
 2759     if {[catch { set f [open $fname "r"]}]} {
 2760     return
 2761     }
 2762     while {[gets $f line] >= 0} {
 2763     switch -regexp -- $line {
 2764         {^REM TAG TKTAG[0-9]+} {
 2765         regexp {^REM TAG TKTAG([0-9]+)} $line dummy tagno
 2766         if {$tagno > $HighestTagSoFar} {
 2767             set HighestTagSoFar $tagno
 2768         }
 2769         set ReminderTags($tagno) 1
 2770         }
 2771     }
 2772     }
 2773     close $f
 2774 }
 2775 
 2776 #***********************************************************************
 2777 # %PROCEDURE: ReadTaggedOptions
 2778 # %ARGUMENTS:
 2779 #  tag -- tag to match
 2780 #  date -- today's date
 2781 # %RETURNS:
 2782 #  A list of options for the dialog box for the tagged reminder
 2783 # %DESCRIPTION:
 2784 #  Converts the JSON dictionary to a list of options for dialog box
 2785 #***********************************************************************
 2786 proc ReadTaggedOptions { tag date } {
 2787     global TagToObj MonthNames EnglishDayNames TwentyFourHourMode
 2788     if {![info exists TagToObj($tag)]} {
 2789     return ""
 2790     }
 2791     set obj $TagToObj($tag)
 2792     set ans ""
 2793     regexp {^([0-9][0-9][0-9][0-9]).([0-9][0-9]).([0-9][0-9])} $date all y m d
 2794     set m [string trimleft $m 0]
 2795     set d [string trimleft $d 0]
 2796     set y [string trimleft $y 0]
 2797     if {![dict exists $obj skip]} {
 2798     lappend ans -global-SkipType 1
 2799     } else {
 2800     set s [dict get $obj skip]
 2801     if {"$s" == "SKIP"} {
 2802         lappend ans -global-SkipType 2
 2803     } elseif {"$s" == "BEFORE"} {
 2804         lappend ans -global-SkipType 3
 2805     } elseif {"$s" == "AFTER"} {
 2806         lappend ans -global-SkipType 4
 2807     } else {
 2808         lappend ans -global-SkipType 1
 2809     }
 2810     }
 2811 
 2812     if {[dict exists $obj d]} {
 2813     lappend ans -text-day1 [dict get $obj d]
 2814     lappend ans -text-day2 [dict get $obj d]
 2815     } else {
 2816     lappend ans -text-day1 {every day}
 2817     lappend ans -text-day2 $d
 2818     }
 2819     if {[dict exists $obj m]} {
 2820         set m [dict get $obj m]
 2821         set mm [string trimleft $m 0]
 2822     lappend ans -text-mon1 [lindex $MonthNames [expr $mm -1]]
 2823     lappend ans -text-mon2 [lindex $MonthNames [expr $mm -1]]
 2824     lappend ans -text-mon3 [lindex $MonthNames [expr $mm -1]]
 2825     } else {
 2826     lappend ans -text-mon1 {every month}
 2827     lappend ans -text-mon2 {every month}
 2828     lappend ans -text-mon3 {every month}
 2829     }
 2830     if {[dict exists $obj y]} {
 2831     lappend ans -text-year1 [dict get $obj y]
 2832     lappend ans -text-year2 [dict get $obj y]
 2833     lappend ans -text-year3 [dict get $obj y]
 2834     } else {
 2835     lappend ans -text-year1 {every year}
 2836     lappend ans -text-year2 {every year}
 2837     lappend ans -text-year3 {every year}
 2838     }
 2839 
 2840     set wd {}
 2841     if {[dict exists $obj wd]} {
 2842     set wd [dict get $obj wd]
 2843     if {[llength $wd] == 1} {
 2844         lappend ans -text-wkday2 [lindex $wd 0]
 2845         lappend ans -text-wkday3 [lindex $wd 0]
 2846     } elseif {"$wd" == "Monday Tuesday Wednesday Thursday Friday"} {
 2847         lappend ans -text-wkday2 weekday
 2848         lappend ans -text-wkday3 weekday
 2849     }
 2850     } else {
 2851     lappend ans -text-wkday2 [get_weekday $date]
 2852     lappend ans -text-wkday3 [get_weekday $date]
 2853     }
 2854 
 2855     if {[llength $wd] > 0} {
 2856     if {[dict exists $obj d]} {
 2857         set day [dict get $obj d]
 2858         if {$day < 8} {
 2859         if {[dict exists $obj back]} {
 2860             lappend ans -text-ordinal Last
 2861             # Adjust month down and possibly year?
 2862             if {[dict exists $obj m]} {
 2863                         set mm [string trimleft [dict get $obj m] 0]
 2864             set idx [expr $mm -1]
 2865             if {$idx <= 0} {
 2866                 set idx 12
 2867             }
 2868             lappend ans -text-mon1 [lindex $MonthNames [expr $idx -1]]
 2869             lappend ans -text-mon2 [lindex $MonthNames [expr $idx -1]]
 2870             lappend ans -text-mon3 [lindex $MonthNames [expr $idx -1]]
 2871             if {[dict exists $obj y]} {
 2872                 set year [dict get $obj y]
 2873                 if {$idx == 12} {
 2874                 lappend ans -text-year1 [expr $year - 1]
 2875                 lappend ans -text-year2 [expr $year - 1]
 2876                 lappend ans -text-year3 [expr $year - 1]
 2877                 }
 2878             }
 2879             }
 2880         } else {
 2881             lappend ans -text-ordinal First
 2882         }
 2883         } elseif {$day < 15} {
 2884         lappend ans -text-ordinal Second
 2885         } elseif {$day < 22} {
 2886         lappend ans -text-ordinal Third
 2887         } else  {
 2888         lappend ans -text-ordinal Fourth
 2889         }
 2890     } else {
 2891         lappend ans -text-ordinal Every
 2892     }
 2893     } else {
 2894     if {$d < 8} {
 2895         lappend ans -text-ordinal First
 2896     } elseif {$d < 15} {
 2897         lappend ans -text-ordinal Second
 2898     } elseif {$d < 22} {
 2899         lappend ans -text-ordinal Third
 2900     } elseif {$d < 29} {
 2901         lappend ans -text-ordinal Fourth
 2902     } else {
 2903         lappend ans -text-ordinal Last
 2904     }
 2905     }
 2906 
 2907     if {[dict exists $obj until]} {
 2908     set u [dict get $obj until]
 2909     regexp {^([0-9][0-9][0-9][0-9]).([0-9][0-9]).([0-9][0-9])} $u all yu mu du
 2910         # Trim leading zeros, or Tcl complains
 2911         set mu [string trimleft $mu 0]
 2912     lappend ans -global-expbut 1
 2913     lappend ans -text-expday $du
 2914     lappend ans -text-expmon [lindex $MonthNames [expr $mu-1]]
 2915     lappend ans -text-expyear $yu
 2916 
 2917     } else {
 2918         set mm [string trimleft $m 0]
 2919     lappend ans -global-expbut 0
 2920     lappend ans -text-expday $d
 2921     lappend ans -text-expmon [lindex $MonthNames [expr $mm-1]]
 2922     lappend ans -text-expyear $y
 2923     }
 2924 
 2925     if {[dict exists $obj delta]} {
 2926     set delta [dict get $obj delta]
 2927     if {$delta == 0} {
 2928         lappend ans -global-advbut 0
 2929         lappend ans -text-advdays 3
 2930         lappend ans -global-advcount 1
 2931     } elseif {$delta < 0} {
 2932         set delta [expr abs($delta)]
 2933         lappend ans -global-advbut 1
 2934         lappend ans -text-advdays $delta
 2935         lappend ans -global-advcount 0
 2936     } else {
 2937         lappend ans -global-advbut 1
 2938         lappend ans -text-advdays $delta
 2939         lappend ans -global-advcount 1
 2940     }
 2941     } else {
 2942     lappend ans -global-advbut 0
 2943     lappend ans -text-advdays 3
 2944     lappend ans -global-advcount 1
 2945     }
 2946 
 2947     if {[dict exists $obj localomit]} {
 2948     set lo [dict get $obj localomit]
 2949     foreach w $EnglishDayNames {
 2950         if {[lsearch -exact $lo $w] >= 0} {
 2951         lappend ans "-global-d$w" 1
 2952         } else {
 2953         lappend ans "-global-d$w" 0
 2954         }
 2955     }
 2956     } else {
 2957     lappend ans -global-dSunday 1
 2958     lappend ans -global-dMonday 0
 2959     lappend ans -global-dTuesday 0
 2960     lappend ans -global-dWednesday 0
 2961     lappend ans -global-dThursday 0
 2962     lappend ans -global-dFriday 0
 2963     lappend ans -global-dSaturday 1
 2964     }
 2965     if {[dict exists $obj rep]} {
 2966     lappend ans -global-repbut 1
 2967     lappend ans -text-repdays [dict get $obj rep]
 2968     } else {
 2969     lappend ans -global-repbut 0
 2970     lappend ans -text-repdays 1
 2971     }
 2972 
 2973     if {[dict exists $obj time]} {
 2974     set t [dict get $obj time]
 2975     lappend ans -global-timebut 1
 2976     set hour [expr $t / 60]
 2977     set minute [format %02d [expr $t % 60]]
 2978     if {$hour == 0 && !$TwentyFourHourMode} {
 2979         lappend ans -text-timehour 12
 2980         lappend ans -text-ampm AM
 2981     } else {
 2982         if {$TwentyFourHourMode} {
 2983         lappend ans -text-timehour $hour
 2984         } else {
 2985         if {$hour >= 12} {
 2986             incr $hour -12
 2987             lappend ans -text-timehour $hour
 2988             lappend ans -text-ampm PM
 2989         } else {
 2990             lappend ans -text-timehour $hour
 2991             lappend ans -text-ampm AM
 2992         }
 2993         }
 2994     }
 2995     lappend ans -text-timemin $minute
 2996     if {[dict exists $obj tdelta]} {
 2997         lappend ans -global-timeadvbut 1
 2998         lappend ans -text-timeadv [dict get $obj tdelta]
 2999     } else {
 3000         lappend ans -global-timeadvbut 0
 3001         lappend ans -text-timeadv 15
 3002     }
 3003     if {[dict exists $obj trep]} {
 3004         lappend ans -global-timerepbut 1
 3005         lappend ans -text-timerep [dict get $obj trep]
 3006     } else {
 3007         lappend ans -global-timerepbut 0
 3008         lappend ans -text-timerep 5
 3009     }
 3010     if {[dict exists $obj duration]} {
 3011         lappend ans -global-durationbut 1
 3012         set dur [dict get $obj duration]
 3013         lappend ans -text-durationh [expr $dur / 60]
 3014         lappend ans -text-durationm [format %02d [expr $dur % 60]]
 3015     } else {
 3016         lappend ans -global-durationbut 0
 3017         lappend ans -text-durationh 1
 3018         lappend ans -text-durationm 00
 3019     }
 3020     } else {
 3021     lappend ans -global-timebut 0
 3022     lappend ans -text-timehour 12
 3023     lappend ans -text-timemin 00
 3024     lappend ans -text-timeadv 15
 3025     lappend ans -global-timerepbut 0
 3026     lappend ans -text-timerep 5
 3027     lappend ans -global-durationbut 0
 3028     lappend ans -text-durationh 1
 3029     lappend ans -text-durationm 00
 3030     }
 3031     if {[dict exists $obj rawbody]} {
 3032     lappend ans -entry-entry [dict get $obj rawbody]
 3033     } else {
 3034     lappend ans -entry-entry [dict get $obj body]
 3035     }
 3036 
 3037     # Figure out the reminder type
 3038     if {[dict exists $obj rep]} {
 3039     # Repeat must be type 1
 3040     lappend ans -global-OptionType 1
 3041     lappend ans -text-day2 $d
 3042     lappend ans -text-mon2 [lindex $MonthNames [expr $m - 1]]
 3043     lappend ans -text-mon3 [lindex $MonthNames [expr $m - 1]]
 3044     lappend ans -text-year2 $y
 3045     lappend ans -text-year3 $y
 3046     } elseif {![dict exists $obj wd]} {
 3047     # No weekdays - must be type 1
 3048     lappend ans -global-OptionType 1
 3049     lappend ans -text-day2 $d
 3050     lappend ans -text-mon2 [lindex $MonthNames [expr $m - 1]]
 3051     lappend ans -text-mon3 [lindex $MonthNames [expr $m - 1]]
 3052     lappend ans -text-year2 $y
 3053     lappend ans -text-year3 $y
 3054     } elseif {![dict exists $obj d]} {
 3055     # No day... must be "every wkday in ..."
 3056     lappend ans -global-OptionType 3
 3057     lappend ans -text-day1 $d
 3058     lappend ans -text-mon1 [lindex $MonthNames [expr $m - 1]]
 3059     lappend ans -text-year1 $y
 3060     lappend ans -text-day2 $d
 3061     lappend ans -text-mon2 [lindex $MonthNames [expr $m - 1]]
 3062     lappend ans -text-year2 $y
 3063     } else {
 3064     set day [dict get $obj d]
 3065     # Take a guess based on day
 3066     if {$day == 1 || $day == 8 || $day == 15 || $day == 22} {
 3067         lappend ans -global-OptionType 3
 3068         lappend ans -text-day1 $d
 3069         lappend ans -text-mon1 [lindex $MonthNames [expr $m - 1]]
 3070         lappend ans -text-year1 $y
 3071         lappend ans -text-day2 $d
 3072         lappend ans -text-mon2 [lindex $MonthNames [expr $m - 1]]
 3073         lappend ans -text-year2 $y
 3074     } else {
 3075         lappend ans -global-OptionType 2
 3076         lappend ans -text-day1 $d
 3077         lappend ans -text-mon1 [lindex $MonthNames [expr $m - 1]]
 3078         lappend ans -text-year1 $y
 3079         lappend ans -text-mon3 [lindex $MonthNames [expr $m - 1]]
 3080         lappend ans -text-year3 $y
 3081     }
 3082     }
 3083     return $ans
 3084 }
 3085 proc FireEditor { w } {
 3086     global Option
 3087     global EditorPid
 3088     set tags [$w tag names current]
 3089     set index [lsearch -glob $tags "FILE_*"]
 3090     if {$index < 0} {
 3091     return
 3092     }
 3093     set tag [lindex $tags $index]
 3094     if {![regexp {^FILE_([0-9]+)_(.*)} $tag all line file]} {
 3095     return
 3096     }
 3097     set editor $Option(Editor)
 3098     regsub -all "%s" $editor $file editor
 3099     regsub -all "%d" $editor $line editor
 3100 
 3101     # Don't fire up a second editor if first is running
 3102     if {$EditorPid >= 0} {
 3103     if {![catch {exec kill -0 $EditorPid}]} {
 3104         Status "Editor already active!"
 3105         after 2500 DisplayTime
 3106         bell
 3107         return
 3108     }
 3109     }
 3110     Status "Firing up editor..."
 3111     after 1500 DisplayTime
 3112     set EditorPid [exec sh -c $editor &]
 3113 }
 3114 
 3115 #***********************************************************************
 3116 # %PROCEDURE: GetCurrentReminder
 3117 # %ARGUMENTS:
 3118 #  w -- text window
 3119 # %RETURNS:
 3120 #  The tag (TKTAGnnnn) for current editable reminder, or "" if no
 3121 #  current editable reminder.
 3122 #***********************************************************************
 3123 proc GetCurrentReminder { w } {
 3124     set tags [$w tag names current]
 3125     set index [lsearch -glob $tags "TKTAG*"]
 3126     if {$index < 0} {
 3127     return ""
 3128     }
 3129     set tag [lindex $tags $index]
 3130     return $tag
 3131 }
 3132 
 3133 #***********************************************************************
 3134 # %PROCEDURE: TaggedEnter
 3135 # %ARGUMENTS:
 3136 #  w -- text window
 3137 # %RETURNS:
 3138 #  Nothing
 3139 # %DESCRIPTION:
 3140 #  Highlights an "editable" reminder as mouse moves into it
 3141 #***********************************************************************
 3142 proc TaggedEnter { w } {
 3143     set tag [GetCurrentReminder $w]
 3144     if {$tag != ""} {
 3145     $w tag configure $tag -foreground #FF0000
 3146     }
 3147 }
 3148 #***********************************************************************
 3149 # %PROCEDURE: TaggedLeave
 3150 # %ARGUMENTS:
 3151 #  w -- text window
 3152 # %RETURNS:
 3153 #  Nothing
 3154 # %DESCRIPTION:
 3155 #  Removes highlight from an "editable" reminder as mouse leaves it
 3156 #***********************************************************************
 3157 proc TaggedLeave { w } {
 3158     global Option
 3159     set tag [GetCurrentReminder $w]
 3160     if {$tag != ""} {
 3161     set tags [$w tag names current]
 3162     set index [lsearch -glob $tags "clr*"]
 3163     if {$index < 0} {
 3164         set fg $Option(TextColor)
 3165     } else {
 3166         set fg [string range [lindex $tags $index] 3 end]
 3167         set fg "#$fg"
 3168     }
 3169     $w tag configure $tag -foreground $fg
 3170     }
 3171 }
 3172 
 3173 #***********************************************************************
 3174 # %PROCEDURE: EditTaggedReminder
 3175 # %ARGUMENTS:
 3176 #  w -- text window
 3177 # %RETURNS:
 3178 #  Nothing
 3179 # %DESCRIPTION:
 3180 #  Opens a dialog box to edit the current editable reminder
 3181 #***********************************************************************
 3182 proc EditTaggedReminder { w } {
 3183     global ModifyDialogResult
 3184     set tag [GetCurrentReminder $w]
 3185     if {$tag == ""} {
 3186     return
 3187     }
 3188 
 3189     # Get the date
 3190     set index [lsearch -glob [$w tag names current] "date_*"]
 3191     if {$index < 0} {
 3192     return
 3193     }
 3194     set date [string range [lindex [$w tag names current] $index] 5 end]
 3195     # Read in options
 3196     set opts [ReadTaggedOptions $tag $date]
 3197     if {$opts == ""} {
 3198     return
 3199     }
 3200 
 3201     toplevel .mod
 3202     CreateModifyDialog .mod 1 0 "Cancel" "Replace reminder" "Delete reminder" "Preview reminder"
 3203     wm title .mod "TkRemind Edit Reminder..."
 3204     wm iconname .mod "Edit Reminder"
 3205     OptionsToRemindDialog .mod $opts
 3206 
 3207     tkwait visibility .mod
 3208     set oldFocus [focus]
 3209     while {1} {
 3210         raise .mod
 3211     grab .mod
 3212     focus .mod.entry
 3213     set ModifyDialogResult -1
 3214     tkwait variable ModifyDialogResult
 3215     if {$ModifyDialogResult == 1} {
 3216         catch {focus $oldFocus}
 3217         destroy .mod
 3218         return 0
 3219     }
 3220     set problem [catch {set rem [CreateReminder .mod]} err]
 3221     if {$problem} {
 3222         tk_dialog .error Error "$err" error 0 Ok
 3223         continue
 3224     }
 3225     if {$ModifyDialogResult == 4} {
 3226         set rem [EditReminder $rem "Cancel" "Replace reminder"]
 3227         if {$ModifyDialogResult == 1} {
 3228         continue
 3229         }
 3230     }
 3231     set opts [RemindDialogToOptions .mod]
 3232     catch {focus $oldFocus}
 3233     destroy .mod
 3234     set problem [catch {
 3235         if {$ModifyDialogResult == 2} {
 3236         ReplaceTaggedReminder $tag $rem $opts
 3237         } else {
 3238         DeleteTaggedReminder $tag
 3239         }
 3240     } err]
 3241     if {$problem} {
 3242         tk_dialog .error Error "Error: $err" error 0 Ok
 3243         return 1
 3244     }
 3245 
 3246         ScheduleUpdateForChanges
 3247     return 0
 3248     }
 3249 }
 3250 
 3251 
 3252 #***********************************************************************
 3253 # %PROCEDURE: UpdateForChanges
 3254 #  Updates the calendar window and restarts background daemon because
 3255 #  something has changed.
 3256 # %ARGUMENTS:
 3257 #  None
 3258 # %RETURNS:
 3259 #  Nothing
 3260 #***********************************************************************
 3261 proc UpdateForChanges {} {
 3262     global TimerUpdateForChanges
 3263     catch { after cancel $TimerUpdateForChanges }
 3264     FillCalWindow
 3265     RestartBackgroundRemindDaemon
 3266 }
 3267 
 3268 # Schedule an update for 100ms in the future.
 3269 # That way, if we get a rapid succession of
 3270 # change notifications, we (probably) only
 3271 # end up doing one call to UpdateForChanges
 3272 proc ScheduleUpdateForChanges {} {
 3273     global TimerUpdateForChanges
 3274     catch { after cancel $TimerUpdateForChanges }
 3275     set TimerUpdateForChanges [after 100 UpdateForChanges]
 3276 }
 3277 
 3278 #***********************************************************************
 3279 # %PROCEDURE: UniqueFileName
 3280 # %ARGUMENTS:
 3281 #  stem -- base name of file
 3282 # %RETURNS:
 3283 #  A filename of the form "stem.xxx" which does not exist
 3284 #***********************************************************************
 3285 proc UniqueFileName { stem } {
 3286     set n 1
 3287     while {[file exists $stem.$n]} {
 3288     incr n
 3289     }
 3290     return $stem.$n
 3291 }
 3292 
 3293 
 3294 #***********************************************************************
 3295 # %PROCEDURE: DeleteTaggedReminder
 3296 # %ARGUMENTS:
 3297 #  tag -- tag of reminder to delete
 3298 # %RETURNS:
 3299 #  Nothing
 3300 # %DESCRIPTION:
 3301 #  Deletes tagged reminder from reminder file
 3302 #***********************************************************************
 3303 proc DeleteTaggedReminder { tag } {
 3304     global AppendFile
 3305     global HighestTagSoFar
 3306 
 3307     set tmpfile [UniqueFileName $AppendFile]
 3308     set out [open $tmpfile "w"]
 3309     write_warning_headers $out
 3310     set in [open $AppendFile "r"]
 3311 
 3312     set found 0
 3313 
 3314     set tagno 0
 3315     while {[gets $in line] >= 0} {
 3316     if {[is_warning_header $line]} {
 3317         continue
 3318     }
 3319     if {[string match "REM TAG $tag *" $line]} {
 3320         set found 1
 3321         continue
 3322     }
 3323     # Delete the old comment lines
 3324     if {[string match "# TKTAG*" $line]} {
 3325         continue
 3326     }
 3327     if {[string match "# -global-OptionType *" $line]} {
 3328         continue
 3329     }
 3330     if {[string match "# TKEND" $line]} {
 3331         continue
 3332     }
 3333 
 3334     # Renumber tags
 3335     if {[regexp {^REM TAG TKTAG([0-9]+) (.*)$} $line all oldtag rest]} {
 3336         incr tagno
 3337         puts $out "REM TAG TKTAG$tagno $rest"
 3338     } else {
 3339         puts $out $line
 3340     }
 3341     }
 3342 
 3343     if {! $found } {
 3344     close $in
 3345     close $out
 3346     file delete $tmpfile
 3347     error "Did not find reminder with tag $tag"
 3348     }
 3349 
 3350     set HighestTagSoFar $tagno
 3351     close $in
 3352     close $out
 3353     file rename -force -- $tmpfile $AppendFile
 3354 }
 3355 
 3356 #***********************************************************************
 3357 # %PROCEDURE: ReplaceTaggedReminder
 3358 # %ARGUMENTS:
 3359 #  tag -- tag of reminder to replace
 3360 #  rem -- text to replace it with
 3361 #  opts -- edit options
 3362 # %RETURNS:
 3363 #  Nothing
 3364 # %DESCRIPTION:
 3365 #  Replaces a tagged reminder in the reminder file
 3366 #***********************************************************************
 3367 proc ReplaceTaggedReminder { tag rem opts } {
 3368     global AppendFile
 3369 
 3370     set tmpfile [UniqueFileName $AppendFile]
 3371     set out [open $tmpfile "w"]
 3372     write_warning_headers $out
 3373     set in [open $AppendFile "r"]
 3374 
 3375     set found 0
 3376 
 3377     while {[gets $in line] >= 0} {
 3378     if {[is_warning_header $line]} {
 3379         continue
 3380     }
 3381     if {[string match "REM TAG $tag *" $line]} {
 3382         # Write the new reminder
 3383         WriteReminder $out $tag $rem $opts
 3384         set found 1
 3385     } else {
 3386         # Delete the old comment lines
 3387         if {[string match "# TKTAG*" $line]} {
 3388         continue
 3389         }
 3390         if {[string match "# -global-OptionType *" $line]} {
 3391         continue
 3392         }
 3393         if {[string match "# TKEND" $line]} {
 3394         continue
 3395         }
 3396         puts $out $line
 3397     }
 3398     }
 3399 
 3400     if {! $found} {
 3401     close $in
 3402     close $out
 3403     file delete $tmpfile
 3404     error "Did not find reminder with tag $tag"
 3405     }
 3406 
 3407     close $in
 3408     close $out
 3409     file rename -force -- $tmpfile $AppendFile
 3410 }
 3411 
 3412 #***********************************************************************
 3413 # %PROCEDURE: WriteReminder
 3414 # %ARGUMENTS:
 3415 #  out -- file to write to
 3416 #  tag -- reminder tag
 3417 #  rem -- reminder body
 3418 #  opts -- edit options
 3419 # %RETURNS:
 3420 #  Nothing
 3421 # %DESCRIPTION:
 3422 #  Writes a reminder to a file
 3423 #***********************************************************************
 3424 proc WriteReminder { out tag rem opts } {
 3425     #puts $out "# $tag Next reminder was created with TkRemind.  DO NOT EDIT"
 3426     #puts $out "# $opts"
 3427     if {[string range $rem 0 3] == "REM "} {
 3428     puts $out "REM TAG $tag [string range $rem 4 end]"
 3429     } else {
 3430     puts $out $rem
 3431     }
 3432     #puts $out "# TKEND"
 3433 }
 3434 
 3435 #***********************************************************************
 3436 # %PROCEDURE: DoShadeSpecial
 3437 # %ARGUMENTS:
 3438 #  n -- calendar box to shade
 3439 #  r, g, b -- colour components
 3440 # %RETURNS:
 3441 #  Nothing
 3442 # %DESCRIPTION:
 3443 #  Handles the "SHADE" special -- shades a box.
 3444 #***********************************************************************
 3445 proc DoShadeSpecial { n r g b } {
 3446     if {$r < 0 || $r > 255 || $g < 0 || $g > 255 || $b < 0 || $b > 255} {
 3447     return
 3448     }
 3449     set bg [format "#%02x%02x%02x" $r $g $b]
 3450     .cal.t$n configure -background $bg
 3451 }
 3452 
 3453 #***********************************************************************
 3454 # %PROCEDURE: DoMoonSpecial
 3455 # %ARGUMENTS:
 3456 #  n -- calendar box for moon
 3457 #  stuff -- Remind command line
 3458 # %RETURNS:
 3459 #  Nothing
 3460 # %DESCRIPTION:
 3461 #  Handles the "MOON" special -- draws a moon symbol
 3462 #***********************************************************************
 3463 proc DoMoonSpecial { n stuff } {
 3464     set msg ""
 3465     set num [scan $stuff "%d %d %d %s" phase junk1 junk2 msg]
 3466     if {$num < 1} {
 3467     return
 3468     }
 3469     if {$phase < 0 || $phase > 3} {
 3470     return
 3471     }
 3472     switch -exact -- $phase {
 3473     0 { set image new }
 3474     1 { set image first }
 3475     2 { set image full }
 3476     3 { set image last }
 3477     }
 3478     .cal.t$n configure -state normal
 3479     .cal.t$n image create 1.0 -image $image
 3480 
 3481     if {$msg != ""} {
 3482     .cal.t$n insert 1.1 " $msg\n"
 3483     } else {
 3484     .cal.t$n insert 1.1 "\n"
 3485     }
 3486     .cal.t$n configure -state disabled
 3487 }
 3488 #***********************************************************************
 3489 # %PROCEDURE: DisplayTime
 3490 # %ARGUMENTS:
 3491 #  None
 3492 # %RETURNS:
 3493 #  Nothing
 3494 # %DESCRIPTION:
 3495 #  Displays current date and time in status window
 3496 #***********************************************************************
 3497 proc DisplayTime {} {
 3498     global TwentyFourHourMode
 3499     if {$TwentyFourHourMode} {
 3500     set msg [clock format [clock seconds] -format "%e %b %Y %H:%M"]
 3501     } else {
 3502     set msg [clock format [clock seconds] -format "%e %b %Y %I:%M%p"]
 3503     }
 3504     Status $msg
 3505 }
 3506 
 3507 #***********************************************************************
 3508 # %PROCEDURE: CreateMoonImages
 3509 # %ARGUMENTS:
 3510 #  None
 3511 # %RETURNS:
 3512 #  Nothing
 3513 # %DESCRIPTION:
 3514 #  Creates the moon images "new", "first", "full" and "last"
 3515 #***********************************************************************
 3516 proc CreateMoonImages {} {
 3517     image create photo full -data "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAGQAAABkABchkaRQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAADlSURBVDiNrdNBUsJAEAXQlyw4hq4hwWPqTixET6ELkZ16CcAq7oFLqXExjaYgQVNlV/Viev7/6XT/4TjGuME7PiLXUatb8N8xwB12SFjiIXIZtU/MAntEfgvQE4YtHxhiHpjXQ5H7uLhEcaLLAleBvd0Xx9Ha/BdyU+Q5OBV5OKmj7a4YBWdSyNPe4aKHAHkzqcQZNj3JgnNexqE8heyIAulffuFF3kTfIVbBVeu/xoXGGsn2TLJJ/mqkafNiINszySYZdbS90GHlvcgsWktY4TFy7ecxTdvIzahxHQLbyFXUqkPwF2ASRNYgB/PXAAAAAElFTkSuQmCC"
 3518     image create photo first -data "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAGQAAABkABchkaRQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAADfSURBVDiNndM9TsNAFATgzy5yjZSAE85JBygETgENUPF3iBCitHAFQkcIhZ/Ryn9gRlrZmp2Z3ef3TBOHOMULPrDBMrhpi/4HI5xjix2+4nmJRbx/Yh7ahvkpRPVV4QDXwT3UQy46zGkAZDgK/iytefvHgCrkJsqZUH6cLnNbABSxd5Jhhf1IbkMXv8Qux7hH1Ic1xvk/jBWy6gavumvtwx7ectwZXkKh7MA95XgObeOtpI2U4zl0kGbpxgiPvwQUcXLrKFchc82f6Ur0PK49azOnmOI4TBu84zm4SV38DeIVYkrYJyNbAAAAAElFTkSuQmCC"
 3519     image create photo new -data "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAGQAAABkABchkaRQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAC6SURBVDiNpdNNbsIwFATgL0HKolchHKBX6yFaBOEyoPYUabvOIVKJRaCL2JX5TRNGGvnJ8ozGz89cYoElPvET+BX2yivn/1Bggw5HHMKa1h2qcPZC/JEIhvh+brIZIY6sorhMYo9hh3KGFzzfa84NZNjDt9OG/ZcH1BlaPE1IAG0+URhxzNGESKPFaHJs9Q0Ziww7HnvGeXSrJhis0jiFfjwnj3I0WRv+TKtr4hQl3lDrZ6QN9Wt654hfWfGDmBpUwDkAAAAASUVORK5CYII="
 3520     image create photo last -data "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAGQAAABkABchkaRQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAADmSURBVDiNndMxTsNAEIXhzy5yCyQ6FAgcE7oQheQWUAAl5BIkREoZrgB0GFNkHBl7bURGsryaee/3jHeXdpxjghU+8InXyI0S+n0MMEeBEi+4jfV3vAvMQtsyL0J0j2GtViaeRRMyj8IlsgY8BSijE2Kur/hy09wHKMJrEolhwtwHKDHOsI4OLnoAXfl1jiNsOkR9keE4P8D4q4scbzg5xIxtjie709f1E7siC+9+Gx/8fxvPKtEsklcJSBdgWhcN8ByFR5z+AWgd5QpyE+OUWOJO+zJNU+Z6jHAdgHe7K73CuD5zFT9nCmRDIssCaAAAAABJRU5ErkJggg=="
 3521 }
 3522 
 3523 #***********************************************************************
 3524 # %PROCEDURE: DisplayTimeContinuously
 3525 # %ARGUMENTS:
 3526 #  None
 3527 # %RETURNS:
 3528 #  Nothing
 3529 # %DESCRIPTION:
 3530 #  Continuously displays current date and time in status window,
 3531 #  updating once a minute
 3532 #***********************************************************************
 3533 proc DisplayTimeContinuously {} {
 3534     DisplayTime
 3535     set secs [clock format [clock seconds] -format "%S"]
 3536     # Doh -- don't interpret as an octal number if leading zero
 3537     scan $secs "%d" decSecs
 3538     set decSecs [expr 60 - $decSecs]
 3539     after [expr $decSecs * 1000] DisplayTimeContinuously
 3540 }
 3541 
 3542 
 3543 #***********************************************************************
 3544 # %PROCEDURE: ShowTodaysReminders
 3545 # %ARGUMENTS:
 3546 #  None
 3547 # %RETURNS:
 3548 #  Nothing
 3549 # %DESCRIPTION:
 3550 #  Shows all of today's non-timed reminders in a window
 3551 #***********************************************************************
 3552 proc ShowTodaysReminders {} {
 3553     global Option
 3554     global Remind
 3555     global ReminderFile
 3556     global TwentyFourHourMode
 3557     if {!$Option(ShowTodaysReminders)} {
 3558     return
 3559     }
 3560 
 3561     set w .today
 3562     catch { destroy $w }
 3563     toplevel $w
 3564     wm title $w "Today's Reminders"
 3565     wm iconname $w "Reminders"
 3566     text $w.text -width 80 -height 20 -wrap word -yscrollcommand "$w.sb set"
 3567     scrollbar $w.sb -orient vertical -command "$w.text yview"
 3568     button $w.ok -text "OK" -command "destroy $w"
 3569 
 3570     grid $w.text -row 0 -column 0 -sticky nsew
 3571     grid $w.sb -row 0 -column 1 -sticky ns
 3572     grid $w.ok -row 1 -column 0 -sticky w
 3573 
 3574     CenterWindow $w .
 3575 
 3576     # Grab the reminders
 3577     set stuff ""
 3578     set cmdline "|$Remind -itkremind=1 -g -q -r "
 3579     if {$TwentyFourHourMode} {
 3580         append cmdline "-b1 "
 3581     }
 3582     append cmdline $Option(ExtraRemindArgs);
 3583     append cmdline " $ReminderFile 2>/dev/null"
 3584     set f [open $cmdline r]
 3585     while {[gets $f line] >= 0} {
 3586     append stuff "$line\n"
 3587     }
 3588     close $f
 3589     $w.text insert end $stuff
 3590     $w.text configure -state disabled
 3591 }
 3592 
 3593 #***********************************************************************
 3594 # %PROCEDURE: InteractiveDeleteReminder
 3595 # %ARGUMENTS:
 3596 #  tag -- tag of reminder to delete
 3597 # %RETURNS:
 3598 #  Nothing
 3599 # %DESCRIPTION:
 3600 #  Prompts for confirmation; then deletes reminder
 3601 #***********************************************************************
 3602 proc InteractiveDeleteReminder { tag } {
 3603     set ans [tk_dialog .error "Really Delete" "Really delete reminder?" warning 0 No Yes]
 3604     if {$ans == 1} {
 3605     DeleteTaggedReminder $tag
 3606         ScheduleUpdateForChanges
 3607     }
 3608 }
 3609 
 3610 proc SendMail { recipient subject body } {
 3611     global Option
 3612 
 3613     if {"$Option(MailAddr)" == ""} {
 3614     return
 3615     }
 3616     if {[catch {set token [mime::initialize -canonical text/plain -string $body]
 3617     mime::setheader $token Subject $subject
 3618     mime::setheader $token From "Reminder Service <>"
 3619     mime::setheader $token To "<$recipient>"
 3620     mime::setheader $token Auto-Submitted "auto-generated"
 3621     smtp::sendmessage $token -originator "" -servers $Option(SMTPServer) -recipients $Option(MailAddr)} err]} {
 3622     puts stderr "ERROR sending mail: $err"
 3623     }
 3624 }
 3625 
 3626 proc ClosePopup { w after_token mail_addr close_win ignore_or_kill tag reminder rem_time } {
 3627     global Ignore
 3628     if {"$after_token" != ""} {
 3629     catch { after cancel $after_token }
 3630     }
 3631 
 3632     if {$close_win} {
 3633     catch { destroy $w }
 3634     }
 3635 
 3636     if {"$mail_addr" != ""} {
 3637     SendMail $mail_addr "Reminder for $rem_time" "Hello,\n\nThe following reminder is scheduled for $rem_time:\n\n$reminder\nRegards,\n\nTkRemind\n"
 3638     }
 3639     if {"$ignore_or_kill" == "ignore"} {
 3640     set Ignore($tag) 1
 3641     }
 3642     if {"$ignore_or_kill" == "kill"} {
 3643     InteractiveDeleteReminder $tag
 3644     }
 3645 }
 3646 
 3647 # Adjust font defaults for screen size
 3648 proc SetFonts {} {
 3649     global SetFontsWorked
 3650     set h [winfo screenheight .]
 3651     if {$h <= 480} {
 3652     # Small screen (maybe eeepc?)
 3653     font configure TkDefaultFont -size 6
 3654     font configure TkFixedFont -size 6
 3655     }
 3656     set SetFontsWorked 1
 3657 }
 3658 
 3659 # Set up inotify to watch for changes to reminder file/directory
 3660 proc SetupInotify {} {
 3661     global InotifyFP
 3662     global ReminderFile
 3663     set failed [catch {set InotifyFP [open "|inotifywait -q -m -e close_write -e move -e create -e delete $ReminderFile" "r"] } ]
 3664     if {$failed} {
 3665         # inotifywait probably not available... meh.
 3666         return
 3667     }
 3668     fileevent $InotifyFP readable [list InotifyReadable $InotifyFP]
 3669 }
 3670 
 3671 # Called when inotifywait reports an event.  Schedule a calendar update
 3672 # and daemon reload.
 3673 proc InotifyReadable { fp } {
 3674     catch { set num [gets $fp line] }
 3675     if {$num < 0} {
 3676         catch { exec kill [pid $fp] }
 3677         close $fp
 3678         return
 3679     }
 3680     ScheduleUpdateForChanges
 3681 }
 3682 
 3683 ### Balloon help
 3684 set Balloon(HelpTime) 400
 3685 set Balloon(StayTime) 3500
 3686 set Balloon(Font) fixed
 3687 set Balloon(MustLeave) 0
 3688 
 3689 proc balloon_reset_timer { w } {
 3690     balloon_destroy_help_window
 3691     balloon_cancel_timer
 3692     balloon_schedule_help $w
 3693 }
 3694 
 3695 proc balloon_destroy_help_window {} {
 3696     catch { destroy .balloonhelp }
 3697 }
 3698 
 3699 proc balloon_cancel_timer {} {
 3700     global Balloon
 3701     catch { after cancel $Balloon(HelpId) }
 3702 }
 3703 
 3704 proc balloon_schedule_help { w } {
 3705     global Balloon
 3706     if { $Balloon(MustLeave) } {
 3707     return
 3708     }
 3709     set Balloon(HelpId) [ after $Balloon(HelpTime) "balloon_popup_help $w" ]
 3710 }
 3711 
 3712 proc balloon_popup_help { w } {
 3713     global Balloon
 3714     if {![info exists Balloon(helptext$w)]} {
 3715     return
 3716     }
 3717 
 3718     if {[string compare [winfo containing [winfo pointerx .] [winfo pointery .]] $w]} {
 3719     return
 3720     }
 3721     set h .balloonhelp
 3722     toplevel $h -bg #000000
 3723     label $h.l -text $Balloon(helptext$w) -wraplength 200 -justify left -bg #FFFFC0 -bd 0
 3724     pack $h.l -padx 1 -pady 1 -ipadx 2 -ipady 1
 3725     wm overrideredirect $h 1
 3726     set geom [balloon_calculate_geometry $h]
 3727     wm geometry $h $geom
 3728     set Balloon(HelpId) [after $Balloon(StayTime) "catch { destroy $h }"]
 3729     set Balloon(MustLeave) 1
 3730 }
 3731 
 3732 bind Balloon <Leave> {
 3733     set Balloon(MustLeave) 0
 3734     balloon_destroy_help_window
 3735     balloon_cancel_timer
 3736 }
 3737 
 3738 bind Balloon <Enter> {
 3739     set Balloon(MustLeave) 0
 3740     balloon_reset_timer %W
 3741 }
 3742 
 3743 bind Balloon <Any-Motion> "balloon_reset_timer %W"
 3744 bind Balloon <Any-ButtonPress> {
 3745     set Balloon(MustLeave) 1
 3746     balloon_reset_timer %W
 3747 }
 3748 
 3749 bind Balloon <Destroy> {
 3750     balloon_destroy_help_window
 3751     balloon_cancel_timer
 3752     catch { unset Balloon(helptext%W) }
 3753 }
 3754 
 3755 proc balloon_add_help { w txt } {
 3756     global Balloon
 3757     if {"$txt" == ""} {
 3758     catch { unset Balloon(helptext$w) }
 3759     return
 3760     }
 3761     set Balloon(helptext$w) $txt
 3762     bindtags $w "Balloon [bindtags $w]"
 3763 }
 3764 
 3765 proc balloon_calculate_geometry { w } {
 3766     set x [winfo pointerx $w]
 3767     set y [winfo pointery $w]
 3768     set mx [winfo screenwidth $w]
 3769     set my [winfo screenheight $w]
 3770     # Adjust for padding
 3771     set wid [expr [winfo reqwidth $w.l] + 6]
 3772     set h [expr [winfo reqheight $w.l] + 4]
 3773 
 3774     # Try above-right of pointer
 3775     set tx [expr $x+3]
 3776     set ty [expr $y-3-$h]
 3777     if {$ty >= 0 && ($tx+$wid) <= $mx} {
 3778     return "+$tx+$ty"
 3779     }
 3780 
 3781     # Try above-left of pointer
 3782     set tx [expr $x-3-$wid]
 3783     set ty [expr $y-3-$h]
 3784     if {$ty >= 0 && $tx >= 0} {
 3785     return "+$tx+$ty"
 3786     }
 3787 
 3788     # Try below-right of pointer
 3789     set tx [expr $x+3]
 3790     set ty [expr $y+3]
 3791     if {$ty+$h <= $my && ($tx+$wid) <= $mx} {
 3792     return "+$tx+$ty"
 3793     }
 3794 
 3795     # Darn... must be below-left
 3796     set tx [expr $x-3-$wid]
 3797     set ty [expr $y+3]
 3798     return "+$tx+$ty"
 3799 }
 3800 
 3801 proc ChooseCalboxFont {} {
 3802     tk fontchooser show
 3803     tk fontchooser configure -font [font actual CalboxFont]
 3804     tk fontchooser configure -command SetCalboxFont
 3805 }
 3806 
 3807 proc SetCalboxFont {font} {
 3808     global tmpOpt
 3809     font configure CalboxFont {*}[font actual $font]
 3810     set tmpOpt(CalboxFont) [font actual $font]
 3811     raise .opt
 3812 }
 3813 
 3814 proc ChooseHeadingFont {} {
 3815     tk fontchooser show
 3816     tk fontchooser configure -font [font actual HeadingFont]
 3817     tk fontchooser configure -command SetHeadingFont
 3818 }
 3819 
 3820 proc SetHeadingFont {font} {
 3821     global tmpOpt
 3822     font configure HeadingFont {*}[font actual $font]
 3823     set tmpOpt(HeadingFont) [font actual $font]
 3824     raise .opt
 3825 }
 3826 
 3827 proc PickColor {index button} {
 3828     global tmpOpt
 3829     set x [tk_chooseColor -initialcolor $tmpOpt($index)]
 3830     if {"$x" != ""} {
 3831         set tmpOpt($index) $x
 3832         $button configure -background $x
 3833     }
 3834     raise .opt
 3835 }
 3836 
 3837 main