#!/usr/local/bin/wish8.4
#
#   SystemLog.tcl          30.10.2003   vfom    
#
#
#
package require msgcat

namespace eval sysLog {
variable sysLog_exitFlag	0	\
	background		black	\
	foreground		white	\
	selbackground		"#1f1f1f"	\
	selforeground		"#bfffbf"	\
#	tHeight			32	\
#	tWidth			100	\
	file			"" \
	fd			0	\
	encoding		""	\
	font			{Courier 10}	\
	size			0	\
	filter			""	\
	find			""	\
	start			0.0	\
	direction		"down"	\
	update_on		false	\
	bottom_anchor		0	\
	oldfocus		""	\
	forms_enable		1	\
	lastGeometry		"800x600"		
}
#
array set ::sysLog::ColorRegExp [ list errorTag {error} warnTag {warn} debugTag {puts} ]
#
proc systemLog_GUIconstructor { } {
    set w .sysLog
    set ff $w.filterFrame
    set fl $w.viewFrame
    set fx $w.exprFrame
    
    toplevel $w 
    wm title $w [ msgcat::mc "System Log"]
    wm withdraw $w 

    grid rowconfig  $w  1 -weight 1
    grid columnconfig  $w  0 -weight 1 -minsize 0
    
    frame $ff -relief raised
    grid  $ff -in $w -row 0 -column 0 -rowspan 1 -columnspan 1 -sticky news

    entry $ff.eflt -width 64
    $ff.eflt delete 0 end
    $ff.eflt insert 0 $::sysLog::filter 
    button $ff.bapply -text [msgcat::mc "Apply filter:"] \
	-command {
	    set sysLog::filter [ .sysLog.filterFrame.eflt get ]
	    set t .sysLog.viewFrame.txt
	    catch { $t tag delete filterOut } xx 
	    after 100 "ElideUnneededLog 0.0"
	    update
	}
    pack $ff.bapply $ff.eflt -padx 5 -pady 2 -side left 
    bind $ff.eflt <Return> "$ff.bapply invoke"

    frame $fl	-relief raised
    grid  $fl -in $w -row 1 -column 0 -rowspan 1 -columnspan 1 -sticky news
    
    grid rowconfig    $fl 0 -weight 1 -minsize 0
    grid columnconfig $fl 0 -weight 1 -minsize 0
    
    scrollbar $fl.hs -orient horiz -command "$fl.txt xview"	
    scrollbar $fl.vs  -command "$fl.txt yview"	
    checkbutton $fl.chk -variable ::sysLog::bottom_anchor
    
    text $fl.txt -state disabled -xscrollcommand "$fl.hs set" -yscrollcommand "$fl.vs set" \
	-background $::sysLog::background -foreground $::sysLog::foreground \
	-wrap none -font $::sysLog::font
    
	$fl.txt tag configure normalTag -foreground green 
	$fl.txt tag configure infoTag   -foreground blue 
	$fl.txt tag raise infoTag 	
	$fl.txt tag configure debugTag  -foreground grey75 
	$fl.txt tag raise debugTag 	
	$fl.txt tag configure warnTag   -foreground yellow 
	$fl.txt tag raise warnTag 	
	$fl.txt tag configure errorTag  -foreground red 
	$fl.txt tag raise errorTag 	
	$fl.txt tag configure sel  -background $::sysLog::selbackground  -foreground $::sysLog::selforeground 
	$fl.txt tag raise sel
    
    grid $fl.txt -in $fl \
	-row 0 -column 0 -rowspan 1 -columnspan 1 -sticky news
    grid $fl.hs -in $fl \
	-row 1 -column 0 -rowspan 1 -columnspan 1 -sticky news
    grid $fl.vs -in $fl \
	-row 0 -column 1 -rowspan 1 -columnspan 1 -sticky news
    grid $fl.chk -in $fl \
	-row 1 -column 1 -rowspan 1 -columnspan 1 -sticky news
    
    frame $fx -relief raised
#    pack $fx -padx 2 -pady 2  -anchor w
    grid  $fx -in $w -row 2 -column 0 -rowspan 1 -columnspan 1 -sticky news

    entry $fx.efind -width 64
    $fx.efind delete 0 end
    $fx.efind insert 0 $::sysLog::find 
    button $fx.bsearch -text [msgcat::mc "Find next:"] \
	-command {
	    set t .sysLog.viewFrame.txt
	    set ::sysLog::find [ .sysLog.exprFrame.efind get ]
	    if { $::sysLog::find != "" } { 
		set tmp_len [string length  $::sysLog::find]
		if { $::sysLog::direction == "up" } {
		    set find_index [ $t search -backwards $::sysLog::find $::sysLog::start]
		    if { $find_index != "" } {
			$t see $find_index
			set last [$t index "$find_index + $tmp_len chars" ]
			$t tag remove sel 0.0 $find_index
			$t tag add sel $find_index $last
			$t tag remove sel $last end
			set ::sysLog::start $find_index
		    } else { bell }
		} else {
		    set find_index [ $t search -forwards $::sysLog::find $::sysLog::start]
		    if { $find_index != "" } {
			$t see $find_index
			set last [$t index "$find_index + $tmp_len chars" ]
			$t tag remove sel 0.0 $find_index
			$t tag add sel $find_index $last
			$t tag remove sel $last end
			set ::sysLog::start $last
		    } else { bell }
		}
	    }	
	}
    radiobutton $fx.rbdn -text [msgcat::mc "Forward"] \
	-variable ::sysLog::direction -value "down"
    radiobutton $fx.rbup -text [msgcat::mc "Backward"] \
	-variable ::sysLog::direction -value "up"
        
    pack $fx.bsearch $fx.efind $fx.rbdn $fx.rbup -padx 5 -pady 2 -side left
    bind $fx.efind <Return> "$fx.bsearch invoke"
#
    if { $::sysLog::forms_enable==0 } {
	grid forget $ff
	grid forget $fx	
    }
#

    update idletasks
    if { $::sysLog::lastGeometry=="" } {
	set x [expr {[winfo screenwidth $w]/2 - [winfo reqwidth $w]/2  \
    	    - [winfo vrootx [winfo parent $w]]}]
	set y [expr {[winfo screenheight $w]/2 - [winfo reqheight $w]/2 \
	    - [winfo vrooty [winfo parent $w]]}]
	set wd [winfo reqwidth  $w ]
	set h  [winfo reqheight $w ]
	wm geom $w [format "%sx%s+%s+%s" $wd $h $x $y]
    } else {
	wm geom $w $::sysLog::lastGeometry
    }    
    bind $w <Destroy> {
	set ::sysLog::update_on false
	set ::sysLog::lastGeometry [ wm geometry .sysLog ]
    }
    bind $w <F1> systemLogHelp
    bind $w <F2> systemLog_saveSettings
    bind $w <F3> {
        set fl .sysLog.viewFrame
    	set f [ tk_getOpenFile -title "Select syslog file"   ]
    	if { ![string match $f "" ] } {     
		$fl.txt configure -state normal
		$fl.txt delete 0.0 end
	        $fl.txt configure -state disabled
		set ::sysLog::size 0
    		set ::sysLog::file $f
    	}
    }
    bind $w <Home> { 	.sysLog.viewFrame.txt see 0.0 }
    bind $w <End> { .sysLog.viewFrame.txt see end }
    bind $w <Up>  { .sysLog.viewFrame.txt yview scroll -1 units }
    bind $w <Down>  { .sysLog.viewFrame.txt yview scroll +1 units }
    bind $w <Control-Up>  { .sysLog.viewFrame.txt yview scroll -1 pages }
    bind $w <Control-Down>  { .sysLog.viewFrame.txt yview scroll +1 pages }
    bind $w <Prior>  { .sysLog.viewFrame.txt yview scroll -1 pages }
    bind $w <Next>  { .sysLog.viewFrame.txt yview scroll +1 pages }
    bind $w <Left>  { .sysLog.viewFrame.txt xview scroll -1 units }
    bind $w <Right>  { .sysLog.viewFrame.txt xview scroll +1 units }
    bind $w <Control-Left>  { .sysLog.viewFrame.txt xview scroll -1 pages }
    bind $w <Control-Right>  { .sysLog.viewFrame.txt xview scroll +1 pages }
    bind $w <Control-f>  { .sysLog.exprFrame.bsearch invoke }
    bind $w <Control-s> { 
	set w .sysLog
	set ff $w.filterFrame
	set fx $w.exprFrame
	if { [grid info $ff] == "" } {
	    set ::sysLog::forms_enable 1
	    grid  $ff -in $w -row 0 -column 0 -rowspan 1 -columnspan 1 -sticky news
	    grid  $fx -in $w -row 2 -column 0 -rowspan 1 -columnspan 1 -sticky news
	} else {
	    grid forget $ff
	    grid forget $fx
	    set ::sysLog::forms_enable 0
	}	
    }
}
#
proc systemLog { } {
    set w .sysLog
    set ff $w.filterFrame
    set fl $w.viewFrame

    if { ![winfo exists $w] } {
	systemLog_GUIconstructor
    }
    $ff.eflt delete 0 end
    $ff.eflt insert 0 $::sysLog::filter 
    $fl.txt configure -state normal
    $fl.txt delete 0.0 end
    $fl.txt configure -state disabled
    set ::sysLog::size 0
    if { !$::sysLog::update_on }  {
	set ::sysLog::update_on true
	after 10 updateSysLog
    }	    
    if { [string equal [wm state $w] withdrawn ] } {
        wm deiconify $w
    } 
    raise $w   
    focus $fl.txt
    return
}
#
#
#
proc updateSysLog { } {
    set w .sysLog
    set t .sysLog.viewFrame.txt
    if { ![ winfo exists $w ]} {
	return
    }
    if { !$::sysLog::update_on } {
	return
    }
    set l_file [file join $::sysLog::file ]
    if { [file readable $l_file  ] } {
        set new_size [file size $l_file ] 
    } else {
    	$t insert end  [msgcat::mc "Can't open logfile"]
	$t insert end  "$l_file : "
	$t insert end  "no read access."
	set ::sysLog::size 0
	return    
    }

    wm title $w [concat [msgcat::mc "System log: "] $::sysLog::file]

    if {$new_size < $::sysLog::size} {
	# file truncated
	set ::sysLog::size 0
    }
    if { $new_size > $::sysLog::size } {
	if {[catch { open $l_file r } ::sysLog::fd]} {
	    $t insert end  [msgcat::mc "Can't open logfile"]
	    $t insert end  "$l_file : "
	    $t insert end  "$::sysLog::fd"
	    set ::sysLog::fd 0
	    return
	}
	if { ![string match $::sysLog::encoding "" ] } {
		fconfigure $::sysLog::fd -encoding  $::sysLog::encoding
	}
	if { $::sysLog::size >0 } {
	    seek $::sysLog::fd $::sysLog::size
	}
	$t configure -state normal
	update
	$t insert end [ read $::sysLog::fd ] normalTag

#	if {$::sysLog::filter == "" } {
#	    while {! [eof $::sysLog::fd] } {
#		set str [gets $::sysLog::fd]
#		if { [regexp -- {^\s*$} $str ] } { continue }
#		$t insert end "$str\n" normalTag
#	    }
#	} else {
#	    while {! [eof $::sysLog::fd] } {
#		set str [gets $::sysLog::fd]
#		if { [regexp $::sysLog::filter $str]} {
#		   $t insert end "$str\n" normalTag
#		}
#	    }
#	}
	$t configure -state disabled
	close $::sysLog::fd 
	set ::sysLog::fd 0
	set ::sysLog::size $new_size	
	update
	after 100  "ColoriseLog 0.0"
    }
    if { $::sysLog::bottom_anchor > 0 } {
        $t see end
    }
    after 1000 updateSysLog
}
#
proc ColoriseLog { start } {
	set t .sysLog.viewFrame.txt
	set s [lindex [split $start '.'] 0 ]
	set x [ $t index end ]
	set cls [lindex [split $x '.'] 0 ]
	set j $s
	for { set i $s } { $i < $cls } { incr i } {
	    incr j
	    set str [ $t get $i.0 $j.0 ]
	    if { [ regexp $::sysLog::ColorRegExp(errorTag) $str ] } {
		$t tag add errorTag  $i.0 $j.0
		continue
	    }
	    if { [ regexp $::sysLog::ColorRegExp(warnTag) $str ] } {
		$t tag add warnTag  $i.0 $j.0
		continue
	    }
#	    if { [ regexp $::sysLog::ColorRegExp(infoTag) $str ] } {
#		$t tag add infoTag  $i.0 $j.0
#		continue
#	    }
	    if { [ regexp $::sysLog::ColorRegExp(debugTag) $str ] } {
		$t tag add debugTag  $i.0 $j.0
	    }
	}
}
#
proc ElideUnneededLog { start } {
	set t .sysLog.viewFrame.txt
	if { [ string match $::sysLog::filter "" ] } { return }
	set s [lindex [split $start '.'] 0 ]
	set x [ $t index end ]
	set cls [lindex [split $x '.'] 0 ]
	set j $s
	set matched 0
	for { set i $s } { $i < $cls } { incr i } {
	    incr j
	    set str [ $t get $i.0 $j.0 ]
	    if { ! [ regexp $::sysLog::filter $str ] } {
		$t tag add filterOut  $i.0 $j.0
	    } else {
		incr matched
	    }
	}
	if { $matched >0 } {
	$t tag configure filterOut -elide yes
	} else {
	    tk_dialog .sysLog.dlg [msgcat::mc "Filter failed"] \
		[msgcat::mc "Nothing matched for REGEXP \"$::sysLog::filter\""]  \
		 error 0 [msgcat::mc "Approve" ]
	}
} 

#
proc systemLog_saveSettings { } {
    set f [ tk_getSaveFile -title "Save configuration to file" -defaultextension .cfg  ]
    if { $f != "" } { 
	if [ catch { open $f "w" } fd ] {
	    error $fd    
	}
	set t [clock format [clock seconds]]
	set u $::tcl_platform(user)
	if { $::tcl_platform(platform) =="unix" } {
    	    set uname [ open "|uname -mnrs" r ]
    	    set h [read -nonewline $uname] ; close $uname
	} else {
	    set h "localhost"
	}
	puts $fd "#  This is configuration file for $::myName"
	puts $fd "#  Created by $u at $t with $::myName-$::appVersion"
	puts $fd "#          on $h\n#"
	puts $fd "set ::sysLog::lastGeometry	[ wm geometry .sysLog ]"
	puts $fd "set ::sysLog::file		\"$::sysLog::file\""
	puts $fd "set ::sysLog::encoding	\"$::sysLog::encoding\""
	puts $fd "set ::sysLog::filter		{$::sysLog::filter}"
	puts $fd "set ::sysLog::find		{$::sysLog::find}"
	puts $fd "set ::sysLog::direction	\"$::sysLog::direction\""
	puts $fd "set ::sysLog::bottom_anchor	$::sysLog::bottom_anchor"
	puts $fd "set ::sysLog::forms_enable	$::sysLog::forms_enable"
	puts $fd "set ::sysLog::background	\"$::sysLog::background\""
	puts $fd "set ::sysLog::foreground	\"$::sysLog::foreground\""
	puts $fd "set ::sysLog::selbackground	\"$::sysLog::selbackground\""
	puts $fd "set ::sysLog::selforeground	\"$::sysLog::selforeground\""
	puts $fd "set ::sysLog::font		\"$::sysLog::font\""
	puts $fd "#"
    	
	close $fd
    }
}
#
#
proc systemLogHelp_GUIconstructor {} {
    set w .sysLog.help
    if [ winfo exists $w ]  { 
	raise $w
	return
    }
    toplevel $w -relief raised -borderwidth 2  -class Dialog
    wm withdraw $w
    wm overrideredirect $w 1
    
    label $w.l1 -text "Hot keys"
    label $w.l2 -justify left -font {Arial 12} -text {
	F1 - This text.
	F2 - Save settings
	F3 - Open other logfile dialog
	F10, Control-x  - exit 
	Ctrl-f - find next 
	Ctrl-s - show\hide search expression form
	Home - move to begining
	End  - move to end
    }
    pack $w.l1  $w.l2 -side top  -pady 10   
    button $w.ok -text "Ok" -command {
	wm withdraw .sysLog.help
	focus $::sysLog::oldfocus
    }
    pack $w.ok -side bottom -padx 10 -pady 10
    bind $w <Escape> {
	wm withdraw .sysLog.help
	focus $::sysLog::oldfocus
    }
    set x [expr {[winfo screenwidth $w]/2 - [winfo reqwidth $w]/2 \
    	- [winfo vrootx [winfo parent $w]]}]
    set y [expr {[winfo screenheight $w]/2 - [winfo reqheight $w]/2 \
    	- [winfo vrooty [winfo parent $w]]}]
    wm geometry $w +$x+$y
}	 
#
proc systemLogHelp {} {
    set w .sysLog.help
    if { ![ winfo exists $w ] }  { 
	systemLogHelp_GUIconstructor
    }
    wm deiconify $w
    raise $w
    set ::sysLog::oldfocus [focus]
    focus $w.ok
    update idletasks
}
#

#
#
set app_path  [ file dirname [info script ] ]
#
set ::appName 		systemLog
set ::appVersion	0.1
#
#=============!!! Configuration section BEGIN !!!================
#
#  Load localised messages 
#
msgcat::mcload [file join $app_path ".." "locale"]
msgcat::mcload [file join $app_path "locale"]
#
# Load config file(s) and replace default configuration
#
foreach x $argv {
    source $x
}
#=============!!! Configuration section END !!!================
#
if { [info commands DestroyMain ]=="" } {
    proc DestroyMain {} { destroy . }
}
#
wm withdraw .
if { [string match $::sysLog::file "" ] } {
    set f [ tk_getOpenFile -title "Select syslog file"   ]
    if { [string match $f "" ] } { exit }    
    set ::sysLog::file $f
}
systemLog
#
bind .sysLog <Destroy>   	+DestroyMain
bind .sysLog <F10>		+DestroyMain
bind .sysLog <Control-x>	+DestroyMain
set ::myName 		[ file rootname [ file tail [ info script ] ] ]
bind . <F2> systemLog_saveSettings


