A Little Meeting Organisation Helper

MNO: I often find myself trying to organise via email meetings of friends for drinks/dinner/weekends away.

In the end I started using emails looking like the following (assuming my friends had the initials ABC DEF GHI JKL)

 if 0 {
             A  D  G  J  M  
             B  E  H  K  N  
             C  F  I  L  O  

 Mon 22 Aug  ?  ?  ?  ?  ?  
 Tue 23 Aug  ?  ?  ?  ?  ?  
 Wed 24 Aug  ?  ?  ?  ?  ?  
 Thu 25 Aug  ?  ?  ?  ?  ?  
 Fri 26 Aug  ?  ?  ?  ?  ?  

 Mon 29 Aug  ?  ?  ?  ?  ?  
 Tue 30 Aug  ?  ?  ?  ?  ?  
 Wed 31 Aug  ?  ?  ?  ?  ?  
 Thu 01 Sep  ?  ?  ?  ?  ?  
 Fri 02 Sep  ?  ?  ?  ?  ?  
 }

The idea being that everyone changes the "?"'s under their name to "Y" or "N" to indicate availability and recirculates the email.

At first I generated these by hand, but eventually put together a short Tcl script to do it. Last weekend, I decided to rewrite it as a Tk GUI app which is included below in case anyone else would find it useful.

http://www.aowp08.dsl.pipex.com/img/mmm.png

There isn't much in the way of error handling included yet.

The generate button will write the matrix into the text widget in the GUI and also copy the matrix to the clipboard.

Start and end dates are anything recognised by Tcl's [clock scan] command.

Tested under Windows with 8.4.3 and under linux, but the [clipboard] part didn't seem to work on Linux - perhaps I need to be more explicit about which X selection is used? A windows executable (starpack) version is here [1 ].

I did try to add a little padding inside the labelframes using the option database to e.g. separate the generate button from the edge of the labelframe, but I couldn't find the correct option syntax (the last thing I tried is commented out near the start of the buildGUI proc, I also tried various capitalisations, dots/asterisks etc. of the form (pseudo-regexp) \*?[Ll]abel[Ff]rame(\*|\.)[Pp]ad(XY)? ).

MNO - fixed a small buglet in [generate] with $bodystr not being initialised if the user doesn't enter any names.

CJL - You were very close with your option statement. It should be:

 if 0 {
 option add *Labelframe.padX 5
 }

To find the correct window class (Labelframe in this case), use:

 if 0 {
 winfo class $myWidget
 }

And to get the correct property name and case, use the second column from the output of:

 if 0 {
 foreach o [$myWidget configure] { puts $o }
 }

(except for any output lines that only contain two columns as these are just short aliases to the full name)

MNO -- Cool, thanks! Script corrected accordingly.


#!/usr/bin/env tclsh
#
# mmm.tcl -- generate meeting matrix suitable for sending out in 
# an email to organise drinks, weekends away etc.  The idea is that
# each person then changes the "?" to a "Y" or "N" as appropriate
# and recirculates the message until all have been filled in and
# hopefully a number of common available dates are identifiable.
#
# Author: Mark Oakden https://wiki.tcl-lang.org/MNO
#
# e.g. for 5 people, with initials ABC, DEF, GHI, JKL and MNO a
# matrix generated for two weeks from today (18th Aug 2005) looks 
# like this:-
#
#
#             A  D  G  J  M  
#             B  E  H  K  N  
#             C  F  I  L  O  
# 
# Thu 18 Aug  ?  ?  ?  ?  ?  
# Fri 19 Aug  ?  ?  ?  ?  ?  
# 
# Mon 22 Aug  ?  ?  ?  ?  ?  
# Tue 23 Aug  ?  ?  ?  ?  ?  
# Wed 24 Aug  ?  ?  ?  ?  ?  
# Thu 25 Aug  ?  ?  ?  ?  ?  
# Fri 26 Aug  ?  ?  ?  ?  ?  
# 
# Mon 29 Aug  ?  ?  ?  ?  ?  
# Tue 30 Aug  ?  ?  ?  ?  ?  
# Wed 31 Aug  ?  ?  ?  ?  ?  
# 

 package require Tk
 wm withdraw .
 # daylist determines the order of the days as displayed in the GUI
 # 0 == sunday 1 == monday ... 6 == saturday
 set ::daylist [list 1 2 3 4 5 6 0]
 # daynamelist should have the short daynames in order 0 .. 6 since
 # we will use lindex to pull out the appropriate dayname.
 # I'd prefer to use the fact that [clock] knows the mapping 0 - 6 
 # --> local day name abbreviations (%a in [clock format]) but for
 # the purposes of this short script don't want to jump through the
 # hoops to extract these
 set ::daynamelist [list Sun Mon Tue Wed Thu Fri Sat]
 #
 set ::start today
 set ::end "+ 2 weeks"
 #
 proc buildGUI {} {
     if { [info exists .mmmGUI] } {
         return
     }
     option add *Labelframe.padX 3
     option add *Labelframe.padY 3
     set t [toplevel .mmmGUI]
     # attendee pane
     set attf [labelframe $t.attf -borderwidth 2 -relief groove \
                   -text "Attendees"]
     set namesl [label $attf.namesl -text "Names/Initials:"]
     set namese [entry $attf.namese -width 36 -textvariable ::att]
     set namesinfo [label $attf.namesinfo -text "Please enter below your attendee names/initials, separated by spaces."]
     grid $namesinfo - -sticky w
     grid $namesl $namese -sticky ew
     grid columnconfigure $attf 1 -weight 1
     pack $attf -side top -expand true -fill x
     # day selection pane
     set dayf [labelframe $t.dayf -borderwidth 2 -relief groove -text "Days"]
     set dayinfo [label $dayf.dayinfo -text "Select below the days to be included"]
     set daysf [frame $dayf.inner]
     foreach day $::daylist {
         set dl [label $daysf.dl$day -text [lindex $::daynamelist $day]]
         set dc [checkbutton $daysf.dc$day -variable ::include($day)]
         pack $dl -side left -anchor e
         pack $dc -side left -anchor w
     }
     set daybuttonf [frame $dayf.buttons]
     set b1 [button $daybuttonf.weekdays \
                 -text "Toggle Weekdays" -command {toggleDays 1 2 3 4 5}]
     set b2 [button $daybuttonf.weekends \
                 -text "Toggle Weekends" -command {toggleDays 0 6}]
     set b3 [button $daybuttonf.alldays \
                 -text "Toggle All" -command {toggleDays 0 1 2 3 4 5 6}]
     pack $b1 $b2 $b3 -side left
     pack $dayinfo -side top -anchor w
     pack $daysf -side top -expand true -fill x
     pack $dayf -side top -expand true -fill x
     pack $daybuttonf -side top -expand true -fill x
     # date range pane
     set datef [labelframe $t.datef -borderwidth 2 -relief groove \
                    -text "Date Range"]
     set datestartl [label $datef.startl -text "Start:"]
     set datestarte [entry $datef.starte -textvariable ::start]
     set dateendl [label $datef.endl -text "End:"]
     set dateende [entry $datef.ende -textvariable ::end]
     pack $datestartl $datestarte $dateendl $dateende -side left
     pack $datef -side top -expand true -fill x
     # output pane
     set outf [labelframe $t.outf -borderwidth 2 -relief groove \
                    -text "Meeting Matrix"]
     # use a var from global namespace here - we will manipulate the text 
     # widget outside this proc
     set ::outt [text $outf.t -font {Courier 10} -width 48 \
                     -height 20 -wrap none \
                     -xscrollcommand [list $outf.xs set] \
                     -yscrollcommand [list $outf.ys set]]
     scrollbar $outf.xs -orient horizontal -command [list $::outt xview]
     scrollbar $outf.ys -orient vertical -command [list $::outt yview]
     set genbut [button $outf.genbut -command generate \
                     -text "Generate the Meeting Matrix"]
     grid $::outt $outf.ys -sticky nsew
     grid $outf.xs x -sticky ew
     grid $genbut - -sticky ew
     grid columnconfigure $outf 0 -weight 1
     grid rowconfigure $outf 0 -weight 1
     pack $outf -side top -expand true -fill both
 }
 
 proc toggleDays { args } {
     foreach d $args {
         set ::include($d) [expr {! $::include($d)}]
     }
 }
 
 proc normaliseNamelist { names } {
     set longest 0
     foreach n $names {
         if { [set l [string length $n]] > $longest } {
             set longest $l
         }
     }
     set norm [list]
     foreach n [lsort $names] {
         set i [string length $n]
         set s [expr {$longest - $i}]
         lappend norm [string repeat " " $s]$n
     }
     return $norm
 }
 
 proc generate {} {
     $::outt delete 1.0 end
     set namelist [normaliseNamelist $::att]
     set titleprefix "            "
     # write the title
     set index 0
     set bodystr ""
     foreach char [split [lindex $namelist 0] {}] {
         set str $titleprefix
         foreach name $namelist {
             append str "[string index $name $index]  "
            if { $index == 0 } {
                 append bodystr "?  "
            }
         }
         $::outt insert end "$str\n"
         incr index
     }
     $::outt insert end "\n"
     set now [clock seconds]
     # start relative to now
     set current [clock scan $::start -base $now]
     # end relative to start
     set _end [clock scan $::end -base $current]
     while { $current < $_end } {
         set daynum [clock format $current -format "%w"]
         if { $::include($daynum) } {
             $::outt insert end [clock format $current -format "%a %d %b  "]
             $::outt insert end "$bodystr\n"
         }
         if { $daynum == 0 } {
             $::outt insert end "\n"
         }
         set current [clock scan "+1 day" -base $current]
     }
     clipboard clear
     clipboard append [$::outt get 1.0 end]
 }
 
 buildGUI
 # That's all, folks.