chiark / gitweb /
ypp-chatlog-alerter: when replacing, always delete the row and shuffle others up
[ypp-sc-tools.main.git] / ypp-chatlog-alerter
1 #!/usr/bin/wish
2
3 proc manyset {list args} {
4     foreach val $list var $args {
5         upvar 1 $var my
6         set my $val
7     }
8 }
9
10 set progname ypp-chatlog-alerter
11 set height 5
12
13 proc menuent {w l a x} {
14     set c [list .mbar.$w add command -label $l -command $x]
15     if {[string length $a]} { lappend c -accel Command-$a }
16     eval $c
17 }
18 proc menus {} {
19     global height
20     
21     menu .mbar -tearoff 0
22     foreach w {file edit} l {File Edit} {
23         menu .mbar.$w -tearoff 0
24         .mbar add cascade -menu .mbar.$w -label $l
25     }
26     foreach l {Open Quit} a {O Q} x {newfile exit} {
27         menuent file $l $a $x
28     }
29     foreach l {Cut Copy Paste Clear} a {X C V {}} {
30         menuent edit $l $a [list event generate {[focus]} <<$l>>]]
31     }
32     . configure -menu .mbar
33 }
34
35 proc nonportability {} {
36     global progname defaultfile
37     
38     switch -exact [tk windowingsystem] {
39         aqua {
40             set defaultfile ~/Library/Preferences/$progname.prefs
41         }
42         x11 {
43             set defaultfile ~/.$progname.rc
44         }
45         default {
46             error ?
47         }
48     }
49 }
50
51 set lw_ls {times pirates messages}
52 set lw_ws {.time .pirate .msg}
53
54 proc widgets {} {
55     global height lw_ws prtimes
56     listbox .time   -width  5 -background black \
57         -listvariable prtimes -foreground white
58     listbox .pirate -width 14 -background black
59     listbox .msg    -width 80
60     for_lw {
61         $w configure -height $height -borderwidth 0 -activestyle none \
62             -highlightthickness 0
63     }
64     eval pack $lw_ws -side left
65     label .overlay -relief raised -foreground white
66 }
67
68 proc shownotice {colour message} {
69     .overlay configure -text $message -background $colour
70     place .overlay -relx 0.5 -rely 0.5 -anchor center
71 }
72 proc hidenotice {} {
73     place forget .overlay
74 }
75
76 proc newfile {} {
77     global currentfile defaultfile
78     
79     set newfile [tk_getOpenFile -multiple 0 -initialfile $currentfile \
80                      -title "Select YPP log to track"]
81     if {![string length $newfile]} return
82
83     set currentfile $newfile
84     set newdefaults [open $defaultfile.new w]
85     puts $newdefaults "[list set currentfile $currentfile]"
86     close $newdefaults
87     file rename -force $defaultfile.new $defaultfile
88
89     clearlists
90     pollfile
91 }
92
93 proc for_lw {args} {
94     global lw_ls lw_ws
95     set body [lindex $args end]
96     set args [lreplace $args end end]
97     uplevel 1 [list \
98                    foreach l $lw_ls \
99                            w $lw_ws] \
100                            $args \
101                          [list $body]
102 }
103
104 set e_life 120
105 set tint_switch 90
106 set tint_switched [expr { exp( -($tint_switch+0.0) / $e_life ) }]
107
108 proc retint {} {
109     global times e_life retint_after otherevent prtimes
110     catch { after cancel $retint_after }
111     set i 0
112     set now [clock seconds]
113     set latest $otherevent
114     set newprtimes {}
115     foreach time $times {
116         set age [expr {$now-$time}]
117         if {!$time} {
118             lappend newprtimes {}
119         } elseif {$age < 120} {
120             lappend newprtimes [format "%3ds" $age]
121         } else {
122             lappend newprtimes [format "%3dm" [expr {$age/120}]]
123         }
124         set latest [expr { $time > $latest ? $time : $latest }]
125         set tint [expr { exp( (-($age >= 0 ? $age : 0) + 0.0) / $e_life ) }]
126 puts "AGE $age LA $latest TI $tint"
127         tintentries .msg $i $tint
128         incr i
129     }
130     set prtimes $newprtimes
131     set next [expr { ($now - $latest < 10 ? 10 :
132                       $now - $latest > 3000 ? 3000 :
133                       $now - $latest
134                       ) * 10 }]
135 #puts "nexting $latest $now $next"
136     set retint_after [after $next retint]
137 }
138
139 proc tintentries {ws y tint} {
140     global tint_switched
141     #puts "$tint $tint_switched"
142     set yellow [format "%02x" [expr {round( 255 *
143         ( $tint >= $tint_switched ? $tint : 0 )
144                                             )}]]
145     set black [format "%02x" [expr {round( 255 *
146         ( $tint >= $tint_switched ? 0 : ($tint / $tint_switched)*0.75 + 0.25 )
147                                            )}]]
148     set fg [format "#${black}${black}${black}"]
149     set bg [format "#${yellow}${yellow}00"]
150     foreach w $ws { $w itemconfigure $y -foreground $fg -background $bg }
151 }
152
153 proc clearlists {} {
154     global height otherevent
155     global times prtimes pirates messages
156     set currentfile {}
157     
158     for_lw { $w delete 0 end; set $l {} }
159     set ntimes {}
160     set prtimes {}
161     for {set i 0} {$i<$height} {incr i} {
162         for_lw { lappend $l {}; $w insert end {} }
163         lappend ntimes 0
164         lappend prtimes {}
165     }
166     set times $ntimes
167     set otherevent [clock seconds]
168     retint
169 }
170
171 proc showtints {} {
172     global e_life
173     set divs 20
174     listbox .tints -width 60 -height [expr {$divs+1}]
175     for {set y 0} {$y <= $divs} {incr y} {
176         set tint [expr {($y+0.0)/$divs}]
177         .tints insert end \
178             "[format "#%2d   %6f   %4ds" $y $tint [expr {round(
179                 $tint > 0 ? -log($tint) * $e_life : "9999"
180             )}]]  The quick brown fox jumped over the lazy dog"
181         tintentries .tints $y $tint
182     }
183     pack .tints -side bottom
184 }
185
186 proc file-read-lines {lvar body} {
187     upvar 1 $lvar l
188     global logfile poll_after lastactivity bufdata
189     
190 #puts f-r-l
191     if {![info exists logfile]} {
192         return
193     }
194     while 1 {
195         if {[catch { read $logfile } got]} {
196             file-error $got
197             return
198         }
199 #puts "f-r-l [string length $got]"
200         if {![string length $got] && [eof $logfile]} {
201             set ago [expr { [clock seconds] - $lastactivity }]
202             set interval [expr {( $ago < 10 ? 10 :
203                                   $ago > 3000 ? 3000 :
204                                   $ago ) * 10}]
205 #puts "requeue filepoll $interval"
206             set poll_after [after $interval pollfile]
207             return
208         }
209         set lastactivity [clock seconds]
210
211         while {[regexp {^(.*?)[\r\n]+(.*)$} $got dummy lhs rhs]} {
212             set l "$bufdata$lhs"
213             set bufdata {}
214             set got $rhs
215 puts ">>$l<<"
216             uplevel 1 $body
217         }
218         append bufdata $got
219     }
220 }
221
222 proc file-error {emsg} {
223     global logfile
224     
225     shownotice red "Error reading logfile $currentfile:\n$emsg"
226     catch { close $logfile }
227     catch { unset logfile }
228 }       
229
230 proc pollfile {} {
231     global poll_after logfile currentfile
232     global errorCode errorInfo bufdata lastactivity
233
234     catch { after cancel $poll_after }
235     if {![string length $currentfile]} {
236         shownotice red "No log file selected.  Use File / Open."
237         return
238     }
239     if {![info exists logfile]} {
240         set bufdata {}
241         set lastactivity [clock seconds] 
242         if {[catch { set logfile [open $currentfile r] } emsg]} {
243             shownotice red "Error opening logfile $currentfile:\n$emsg"
244             return
245         }
246         shownotice \#000080 "Reading $currentfile"
247         if {[catch {
248             seek $logfile -1024 end
249         } emsg]} {
250             if {![string match {POSIX EINVAL *} $errorCode]} {
251                 file-error $emsg
252             }
253         }
254         file-read-lines l { }
255     }
256     file-read-lines l {
257         hidenotice
258         if {[regexp {^\[\d+:\d+:\d+\] (.*)} $l dummy rhs]} {
259 puts PROCLINE
260             process-line $rhs
261         }
262     }
263 }
264
265 proc process-line {l} {
266     if {[regexp {^(\w+) tells ye, \"(.*)\"$} $l dummy pirate msg]} {
267 puts "MESSAGE $l"
268         message $pirate $msg
269     }
270 }
271
272 proc message {pirate msg} {
273     global times pirates messages height
274     global lw_ls lw_ws
275     
276     set ix [lsearch -exact $pirates $pirate]
277     set now [clock seconds]
278     if {$ix < 0} {
279         set cix 0
280         set oldest $now
281         foreach time $times {
282             if {$time < $oldest} {
283                 set oldest $time
284                 set ix $cix
285             }
286             incr cix
287         }
288         for_lw {
289             set $l [lreplace [set $l] $ix $ix]
290             lappend $l {}
291             $w delete $ix
292             $w insert end {}
293         }
294         set ix [expr {$height-1}]
295     }
296     for_lw new [list $now $pirate $msg] {
297         set $l [lreplace [set $l] $ix $ix $new]
298         $w delete $ix
299         $w insert $ix $new
300     }
301 puts "TIMES $times"
302     .pirate itemconfigure $ix -foreground white
303     retint
304 }
305
306 proc parseargs {} {
307     global argv
308     foreach arg $argv {
309         if {![string compare $arg --test-tints]} {
310             showtints
311         } else {
312             error "unknown option $arg"
313         }
314     }
315 }
316
317 menus
318 nonportability
319 parseargs
320 widgets
321 clearlists
322
323 if {[file exists $defaultfile]} {
324     source $defaultfile
325 }
326
327 pollfile