#!/bin/sh
# the next line restarts using tclsh \
exec tclsh "$0" ${1+"$@"}

# Copyright: 2018-2025 Paul Obermeier (obermeier@tcl3d.org)
# Distributed under BSD license.

namespace eval BawtLogViewer {
    variable ns [namespace current]

    namespace export ShowMainWin
    namespace export AppendLib
    namespace export SetLibWarning
    namespace export SetLibStatus
    namespace export SetLibStages
    namespace export SetLibUpdateCause
    namespace export SetLibUserConfig
    namespace export AppendLogMsg
    namespace export SetStatusMsg
    namespace export UpdateTitle
    namespace export ReadFile
    namespace export AutoUpdate
    namespace export SetLogFile
    namespace export LoadSettings
    namespace export SaveSettings
    namespace export ViewSettingsDir

    variable sTopLevel ".bawt"
    variable sCurLib   ""
    variable sSummarySection false
    variable sCompilerWarningErrorPatterns
    variable sCompilerErrorColor   "#FF8080"
    variable sCompilerWarningColor "#FFA0A0"

    proc SetAutosaveOnExit { onOff } {
        variable sGui

        set sGui(autosaveOnExit) $onOff
    }

    proc GetAutosaveOnExit {} {
        variable sGui

        return [list $sGui(autosaveOnExit)]
    }

    proc SetWindowPos { x y { w -1 } { h -1 } } {
        variable sGui

        set sGui(MainWin,x) $x
        set sGui(MainWin,y) $y
        set sGui(MainWin,w) $w
        set sGui(MainWin,h) $h
    }

    proc GetWindowPos {} {
        variable sTopLevel
        variable sGui

        if { [info exists sTopLevel] && \
            [winfo exists $sTopLevel] } {
            scan [wm geometry $sTopLevel] "%dx%d+%d+%d" w h x y
        } else {
            set x $sGui(MainWin,x)
            set y $sGui(MainWin,y)
            set w $sGui(MainWin,w)
            set h $sGui(MainWin,h)
        }
        return [list $x $y $w $h]
    }

    proc SetLastDir { dir } {
        variable sGui

        set sGui(LastDir) $dir
    }

    proc GetLastDir {} {
        variable sGui

        return [list $sGui(LastDir)]
    }

    proc SetDeadlockThreshold { thres } {
        variable sGui

        set sGui(Deadlock,Threshold) $thres
    }

    proc GetDeadlockThreshold {} {
        variable sGui

        return [list $sGui(Deadlock,Threshold)]
    }

    proc SetDeadlockBeep { useBeep } {
        variable sGui

        set sGui(Deadlock,UseBeep) $useBeep
    }

    proc GetDeadlockBeep {} {
        variable sGui

        return [list $sGui(Deadlock,UseBeep)]
    }

    proc SetKeyRepeatTime { repeatTimeInMilliSec } {
        variable sGui

        set sGui(KeyRepeatTime) $repeatTimeInMilliSec
    }

    proc GetKeyRepeatTime {} {
        variable sGui

        return [list $sGui(KeyRepeatTime)]
    }

    proc SetLineWrap { useWrap } {
        variable sGui

        set sGui(Text,LineWrap) $useWrap
    }

    proc GetLineWrap {} {
        variable sGui

        return [list $sGui(Text,LineWrap)]
    }

    proc _ToggleLineWrap {} {
        variable sGui

        if { $sGui(Text,LineWrap) } {
            $sGui(LogId) configure -wrap word
        } else {
            $sGui(LogId) configure -wrap none
        }
    }

    proc SetWarningDetectPatterns { args } {
        variable sGui

        set sGui(WarningDetectPatterns) [list]
        foreach arg $args {
            lappend sGui(WarningDetectPatterns) $arg
        }
    }

    proc GetWarningDetectPatterns {} {
        variable sGui

        return $sGui(WarningDetectPatterns)
    }

    proc SetErrorDetectPatterns { args } {
        variable sGui

        set sGui(ErrorDetectPatterns) [list]
        foreach arg $args {
            lappend sGui(ErrorDetectPatterns) $arg
        }
    }

    proc GetErrorDetectPatterns {} {
        variable sGui

        return $sGui(ErrorDetectPatterns)
    }

    proc LoadSettings {} {
        variable sCompilerWarningErrorPatterns
        variable sTestPatterns

        SetWindowPos 10 30
        SetLastDir   [pwd]
        SetDeadlockThreshold 0.25
        SetDeadlockBeep    1
        SetKeyRepeatTime   600
        SetLineWrap        0
        SetAutosaveOnExit  1
        #                        gcc            VisualStudio    gcc-German
        SetWarningDetectPatterns "*: warning:*" "*: warning*:*" "*: Warnung:*"
        SetErrorDetectPatterns   "*: error:*"   "*: error C*:*" "*: Fehler:*"

        set sTestPatterns(Start) "> Start Test_*"
        set sTestPatterns(End)   "> End Test_*"
        set sTestPatterns(Fail1) "==== * FAILED"
        set sTestPatterns(Fail2) [list "Error:*" "ERROR:*" "Test*failed:*" "ErrorCode"]
        set sTestPatterns(Error) "Test files exiting with errors*"
        set sTestPatterns(Scan)  "==== %s"

        set cfgFile [file normalize [poCfgFile GetCfgFilename [GetAppName] [GetCfgDir]]]
        poCfgFile EvalCfgFile $cfgFile

        set cfgFile [file normalize [poCfgFile GetCfgFilename "EstimatedBuildTimes" [GetCfgDir]]]
        poCfgFile EvalCfgFile $cfgFile

        foreach pattern [GetWarningDetectPatterns] {
            lappend sCompilerWarningErrorPatterns $pattern "C-Warning"
        }
        foreach pattern [GetErrorDetectPatterns] {
            lappend sCompilerWarningErrorPatterns $pattern "C-Error"
        }
    }

    proc PrintCmd { fp cmdName { ns "" } } {
        puts $fp "\n# Set${cmdName} [info args ${ns}::Set${cmdName}]"
        puts $fp "catch {${ns}::Set${cmdName} [${ns}::Get${cmdName}]}"
    }

    proc SaveSettings {} {
        variable ns
        variable sEstimatedTimes

        set cfgFile [file normalize [poCfgFile GetCfgFilename [GetAppName] [GetCfgDir]]]
        poCfgFile CreateBackupFile $cfgFile
        set retVal [catch {open $cfgFile w} fp]
        if { $retVal == 0 } {
            puts $fp "\n# SetWindowPos [info args SetWindowPos]"
            puts $fp "catch {${ns}::SetWindowPos [GetWindowPos]}"

            PrintCmd $fp "LastDir"           "$ns"
            PrintCmd $fp "DeadlockThreshold" "$ns"
            PrintCmd $fp "DeadlockBeep"      "$ns"
            PrintCmd $fp "KeyRepeatTime"     "$ns"
            PrintCmd $fp "LineWrap"          "$ns"

            PrintCmd $fp "WarningDetectPatterns" "$ns"
            PrintCmd $fp "ErrorDetectPatterns"   "$ns"

            PrintCmd $fp "RecentFileList" "::poAppearance"

            close $fp
            SetStatusMsg "Settings stored in file $cfgFile" "Ok"
        }

        set cfgFile [file normalize [poCfgFile GetCfgFilename "EstimatedBuildTimes" [GetCfgDir]]]
        poCfgFile CreateBackupFile $cfgFile
        set retVal [catch {open $cfgFile w} fp]
        if { $retVal == 0 } {
            puts $fp "\n# SetEstimatedBuildTime [info args SetEstimatedBuildTime]"
            if { [info exists sEstimatedTimes] } {
                foreach key [lsort -dictionary [array names sEstimatedTimes]] {
                    set libName     [lindex [split $key ","] 0]
                    set libCompiler [lindex [split $key ","] 1]
                    set estimatedTime [GetEstimatedBuildTime $libName $libCompiler]
                    puts $fp "catch {SetEstimatedBuildTime $libName $estimatedTime $libCompiler}"
                }
            }

            close $fp
            SetStatusMsg "Settings stored in file $cfgFile" "Ok"
        }
    }

    proc GetBuildOrTestString { { toTitle false } } {
        variable sGui

        if { $sGui(TestStage) } {
            set str "test"
        } else {
            set str "build"
        }
        if { $toTitle } {
            set str [string totitle $str]
        }
        return $str
    }

    proc GetCompilerOrTest { libCompiler } {
        variable sGui

        if { $sGui(TestStage) } {
            set str "Test"
        } else {
            set str $libCompiler
        }
        return $str
    }

    proc ViewSettingsDir {} {
        _StartFileBrowser [GetCfgDir]
    }

    proc _SaveBuildOrTestTime { tableId libName } {
        variable sGui

        set row [_GetSelectedRowNum $tableId]
        if { $row < 0 } {
            SetStatusMsg "No row selected." "Warning"
            return
        }
        set buildTime [poTablelistUtil GetCellValue $sGui(TableId) $row $sGui(Col,BuildTime)]
        if { $buildTime ne "" && $buildTime > 0.0 } {
            poTablelistUtil SetCellValue $sGui(TableId) $row $sGui(Col,EstimatedTime) $buildTime
            set libCompiler [poTablelistUtil GetCellValue $tableId $row $sGui(Col,LibCompiler)]
            SetEstimatedBuildTime $libName $buildTime [GetCompilerOrTest $libCompiler]
        }
    }

    proc SaveBuildTimes {} {
        variable sGui
        variable sEstimatedTimes

        set tableId $sGui(TableId)
        for { set row 0 } { $row < [$tableId size] } { incr row } {
            set libName      [poTablelistUtil GetCellValue $tableId $row $sGui(Col,LibName)]
            set buildTimeStr [poTablelistUtil GetCellValue $tableId $row $sGui(Col,BuildTime)]
            set libCompiler  [poTablelistUtil GetCellValue $tableId $row $sGui(Col,LibCompiler)]
            set res [scan $buildTimeStr "%f" buildTime]
            if { $res == 1 && [string is double -strict $buildTime] && $buildTime > 0.0 } {
                if { $libCompiler ne "" && ! [info exists sEstimatedTimes($libName,$libCompiler)] } {
                    SetEstimatedBuildTime $libName $buildTime [GetCompilerOrTest $libCompiler]
                } elseif { $buildTime > [GetEstimatedBuildTime $libName [GetCompilerOrTest $libCompiler]] } {
                    SetEstimatedBuildTime $libName $buildTime [GetCompilerOrTest $libCompiler]
                }
            }
        }
        SaveSettings
    }

    proc GetEstimatedBuildTime { libName { libCompiler "" } } {
        variable sEstimatedTimes

        if { $libCompiler ne "" } {
            if { [info exists sEstimatedTimes($libName,$libCompiler)] } {
                return $sEstimatedTimes($libName,$libCompiler)
            }
        }
        if { [info exists sEstimatedTimes($libName)] && $libCompiler ne "Test" } {
            return $sEstimatedTimes($libName)
        } else {
            return 0.0
        }
    }

    proc SetEstimatedBuildTime { libName timeInMinutes { libCompiler "" } } {
        variable sEstimatedTimes

        set t [expr {double($timeInMinutes)}]
        if { $libCompiler ne "" } {
            set sEstimatedTimes($libName,$libCompiler) $t
        } else {
            set sEstimatedTimes($libName) $t
        }
    }

    proc ViewEstimatedBuildTimes {} {
        variable ns
        variable sTopLevel
        variable sEstimatedTimes

        set top ${sTopLevel}_viewEstimatedBuildTimes
        set fr  $top.fr

        if { [winfo exists $top] } {
            poWin Raise $top
            return
        }

        toplevel $top
        ttk::frame $fr
        pack $fr -expand true -fill both
        wm title $top "BAWT - Estimated build and test Times"

        set tableId [poWin CreateScrolledTablelist $fr true "Estimated build and test times" \
            -height 20 -width 100 \
            -exportselection false \
            -columns { 3 "#"            "right"
                      18 "Library Name" "left"
                       0 "None"         "right"
                       0 "Test"         "right"
                       0 "gcc"          "right"
                       0 "vs2008"       "right"
                       0 "vs2010"       "right"
                       0 "vs2013"       "right"
                       0 "vs2015"       "right"
                       0 "vs2017"       "right"
                       0 "vs2019"       "right"
                       0 "vs2022"       "right" } \
            -setfocus true \
            -stretch 1  \
            -stripebackground #e0e8f0 \
            -selectmode single \
            -showseparators true]
        $tableId columnconfigure 0 -showlinenumbers true
        $tableId columnattrib    1 "LibName" 1
        set bodyTag [$tableId bodytag]
        bind $bodyTag <Any-KeyPress> [list ${ns}::_ShowLibByKey $tableId %K]
        $tableId configure -labelcommand tablelist::sortByColumn
        $tableId columnconfigure  1 -sortmode dictionary
        $tableId columnconfigure  2 -sortmode dictionary
        $tableId columnconfigure  3 -sortmode dictionary
        $tableId columnconfigure  4 -sortmode dictionary
        $tableId columnconfigure  5 -sortmode dictionary
        $tableId columnconfigure  6 -sortmode dictionary
        $tableId columnconfigure  7 -sortmode dictionary
        $tableId columnconfigure  8 -sortmode dictionary
        $tableId columnconfigure  9 -sortmode dictionary
        $tableId columnconfigure 10 -sortmode dictionary

        set col 2
        foreach type { None Test gcc vs2008 vs2010 vs2013 vs2015 vs2017 vs2019 vs2022 } {
            set cols($type) $col
            incr col
        }

        if { [info exists sEstimatedTimes] } {
            set curLib ""
            foreach key [lsort -dictionary [array names sEstimatedTimes]] {
                set libName     [lindex [split $key ","] 0]
                set libCompiler [lindex [split $key ","] 1]
                set estimatedTime [GetEstimatedBuildTime $libName $libCompiler]
                if { $libName ne $curLib } {
                    if { [info exists rowCont] } {
                        $tableId insert end $rowCont
                    }
                    # Got new libName
                    set rowCont [list "" $libName "" "" "" "" "" "" "" ""]
                    set curLib $libName
                }
                set colName $libCompiler
                if { $colName eq "" } {
                    set colName None
                }
                if { ! [string is double $estimatedTime] } {
                    set estimatedTime 0.0
                }
                lset rowCont $cols($colName) [format "%.2f" $estimatedTime]
                # puts "$libName $estimatedTime $libCompiler"

            }
        }
        bind $top <Escape> "destroy $top"
        wm protocol $top WM_DELETE_WINDOW "destroy $top"
        focus $tableId
    }

    proc _GetCompilerWarnings { logFile } {
        variable sCompilerWarningErrorPatterns

        set retVal [catch {open $logFile "r"} fp]
        if { $retVal != 0 } {
            return -1
        }
        if { [poMisc HaveTcl9OrNewer] } {
            fconfigure $fp -profile tcl8
        }

        set numWarnings 0
        while { [gets $fp line] >= 0 } {
            foreach { pattern tag } $sCompilerWarningErrorPatterns {
                if { [string match -nocase $pattern $line] } {
                    incr numWarnings
                    break
                }
            }
        }
        close $fp
        return $numWarnings
    }

    proc ViewCompilerWarnings {} {
        variable ns
        variable sGui
        variable sTopLevel

        set numLibs [$sGui(TableId) size]
        if { $numLibs == 0 } {
            SetStatusMsg "No log file loaded." "Error"
            return
        }

        set top ${sTopLevel}_viewCompilerWarnings
        set fr  $top.fr

        if { [winfo exists $top] } {
            poWin Raise $top
            return
        }

        toplevel $top
        ttk::frame $fr
        pack $fr -expand true -fill both
        wm title $top "BAWT - Compiler Warnings"

        set tableId [poWin CreateScrolledTablelist $fr true "-1 means no log file available" \
            -height 20 -width 50 \
            -exportselection false \
            -columns { 3 "#"            "right"
                      18 "Library Name" "left"
                       0 "Warnings"     "right" } \
            -setfocus true \
            -stretch 1  \
            -stripebackground #e0e8f0 \
            -selectmode single \
            -showseparators true]
        $tableId columnconfigure 0 -showlinenumbers true
        $tableId columnattrib    1 "LibName" 1
        set bodyTag [$tableId bodytag]
        bind $bodyTag <Double-1>        [list ${ns}::ViewLibLogFile $tableId]
        bind $bodyTag <KeyPress-Return> [list ${ns}::ViewLibLogFile $tableId]
        bind $bodyTag <Any-KeyPress>    [list ${ns}::_ShowLibByKey $tableId %K]
        $tableId configure -labelcommand tablelist::sortByColumn
        $tableId columnconfigure  1 -sortmode dictionary
        $tableId columnconfigure  2 -sortmode integer

        set col [_GetLibNameColumn $sGui(TableId)]
        for { set row 0 } { $row < $numLibs } { incr row } {
            set libName [poTablelistUtil GetCellValue $sGui(TableId) $row $col]
            set bgColor [$sGui(TableId) rowcget $row -background]
            set libLogFile [_GetLibLogFile $libName]
            if { $libLogFile ne "" } {
                set numWarnings [_GetCompilerWarnings $libLogFile]
            } else {
                set numWarnings -1
            }
            $tableId insert end [list "" $libName $numWarnings]
            $tableId rowconfigure end -background $bgColor
        }
        $tableId sortbycolumnlist [list $col] decreasing
        bind $top <Escape> "destroy $top"
        wm protocol $top WM_DELETE_WINDOW "destroy $top"
        focus $tableId
    }


    proc _GetTestFailures { logFile } {
        variable sTestPatterns

        set retVal [catch {open $logFile "r"} fp]
        if { $retVal != 0 } {
            return -1
        }
        if { [poMisc HaveTcl9OrNewer] } {
            fconfigure $fp -profile tcl8
        }

        set numFailures       0
        set foundTestStart    false
        set foundTestEnd      false
        set foundNonEmptyLine false
        set lastTestName      ""
        while { [gets $fp line] >= 0 && ! $foundTestEnd } {
            set line [string trim $line]
            if { ! $foundTestStart } {
                if { [string match $sTestPatterns(Start) $line] } {
                    set foundTestStart true
                }
            } elseif { ! $foundTestEnd } {
                if { $line ne "" } {
                    if { [string match $sTestPatterns(End) $line] } {
                        set foundTestEnd true
                    } elseif { [string match $sTestPatterns(Fail1) $line] } {
                        # tcltest failure message.
                        scan $line $sTestPatterns(Scan) testName
                        if { $testName ne $lastTestName } {
                            set lastTestName $testName
                            incr numFailures
                        }
                    } elseif { [string match $sTestPatterns(Error) $line] } {
                        incr numFailures
                    } else {
                        foreach pattern $sTestPatterns(Fail2) {
                            if { [string match $pattern $line] } {
                                incr numFailures
                            }
                        }
                    }
                    if { ! $foundTestEnd } {
                        set foundNonEmptyLine true
                    }
                }
            }
        }
        close $fp
        if { $foundTestEnd && $foundNonEmptyLine } {
            return $numFailures
        } else {
            return -1
        }
    }

    proc ViewTestFailures {} {
        variable ns
        variable sGui
        variable sTopLevel

        set numLibs [$sGui(TableId) size]
        if { $numLibs == 0 } {
            SetStatusMsg "No log file loaded." "Error"
            return
        }

        set top ${sTopLevel}_viewTestFailures
        set fr  $top.fr

        if { [winfo exists $top] } {
            poWin Raise $top
            return
        }

        toplevel $top
        ttk::frame $fr
        pack $fr -expand true -fill both
        wm title $top "BAWT - Test Failures"

        set tableId [poWin CreateScrolledTablelist $fr true "-1 means no tests performed" \
            -height 20 -width 50 \
            -exportselection false \
            -columns { 3 "#"            "right"
                      18 "Library Name" "left"
                       0 "Failures"     "right" } \
            -setfocus true \
            -stretch 1  \
            -stripebackground #e0e8f0 \
            -selectmode single \
            -showseparators true]
        $tableId columnconfigure 0 -showlinenumbers true
        $tableId columnattrib    1 "LibName" 1
        set bodyTag [$tableId bodytag]
        bind $bodyTag <Double-1>        [list ${ns}::ViewLibLogFile $tableId "Test"]
        bind $bodyTag <KeyPress-Return> [list ${ns}::ViewLibLogFile $tableId "Test"]
        bind $bodyTag <Any-KeyPress>    [list ${ns}::_ShowLibByKey $tableId %K]
        $tableId configure -labelcommand tablelist::sortByColumn
        $tableId columnconfigure  1 -sortmode dictionary
        $tableId columnconfigure  2 -sortmode integer

        set col [_GetLibNameColumn $sGui(TableId)]
        for { set row 0 } { $row < $numLibs } { incr row } {
            set libName [poTablelistUtil GetCellValue $sGui(TableId) $row $col]
            set bgColor [$sGui(TableId) rowcget $row -background]
            set libLogFile [_GetLibLogFile $libName "Test"]
            if { $libLogFile ne "" } {
                set numFailures [_GetTestFailures $libLogFile]
            } else {
                set numFailures -1
            }
            $tableId insert end [list "" $libName $numFailures]
            $tableId rowconfigure end -background $bgColor
        }
        $tableId sortbycolumnlist [list $col] decreasing
        bind $top <Escape> "destroy $top"
        wm protocol $top WM_DELETE_WINDOW "destroy $top"
        focus $tableId
    }

    proc _GetLibNameColumn { tableId } {
        for { set col 0 } { $col < [$tableId columncount] } { incr col } {
            if { [$tableId hascolumnattrib $col "LibName"] } {
                return $col
            }
        }
        return -1
    }

    proc _GetLibNames { tableId } {
        variable sGui

        set col [_GetLibNameColumn $tableId]
        return [$tableId getcolumns [list $col]]
    }

    proc _GetLibRow { tableId libName } {
        return [lsearch -exact [_GetLibNames $tableId] $libName]
    }

    proc _GetSelectedRowNum { tableId } {
        variable sGui

        set indList [$tableId curselection]
        if { [llength $indList] == 0 } {
            return -1
        }
        return [lindex $indList 0]
    }

    proc _GetLibNameFromTable { tableId } {
        variable sGui

        set indList [$tableId curselection]
        if { [llength $indList] == 0 } {
            return ""
        }
        set row [lindex $indList 0]
        set col [_GetLibNameColumn $tableId]
        set libName [poTablelistUtil GetCellValue $tableId $row $col]
        return $libName
    }

    proc _ShowWarningOrFailure { tableId textId } {
        set rowList [$tableId curselection]
        if { [llength $rowList] > 0 } {
            set row [lindex $rowList 0]
            set lineNum [$tableId rowattrib $row "Line"]
            $textId see $lineNum
        }
    }

    proc _OpenLogContextMenu { tableId x y libName } {
        variable ns
        variable sGui

        if { ! [info exists sGui(SetupFile)] } {
            return
        }
        set w .bawtLogViewer:logContextMenu
        catch { destroy $w }
        menu $w -tearoff false -disabledforeground white

        set rowList [$tableId curselection]
        if { [llength $rowList] > 0 } {
            set row [lindex $rowList 0]
            set msg [poTablelistUtil GetCellValue $tableId $row 2]
            set parts [split $msg ":"]
            if { [string length [lindex $parts 0]] == 1 } {
                set fileName "[lindex $parts 0]:[lindex $parts 1]"
            } else {
                set fileName [lindex $parts 0]
            }
            while { ! [string is alnum [string index $fileName 0]] } {
                set fileName [string range $fileName 1 end]
            }
            if { [string index $fileName end] eq ")" } {
                set pos [string last "(" $fileName]
                if { $pos >= 0 } {
                    set fileName [string range $fileName 0 [expr {$pos-1}]]
                }
            }
            set foundFile ""
            if { [file exists $fileName] } {
                set foundFile [file tail $fileName]
            }
            if { $foundFile eq "" } {
                # File name may be relative. Use build directory as path.
                if { $libName ne "" } {
                    set buildDirList [_GetLibDirs $libName "Build"]
                    set fileName [file join [lindex $buildDirList 0] $fileName]
                    if { [file exists $fileName] } {
                        set foundFile [file tail $fileName]
                    }
                }
            }
            if { $foundFile eq "" } {
                set menuTitle "No editable file selected"
            } else {
                set menuTitle "$foundFile selected"
            }
            $w add command -label "$menuTitle" -state disabled -background "#303030"
            if { $foundFile ne "" } {
                set editorProg  $sGui(Text,EditorEnvVarProg)
                set editorShort $sGui(Text,EditorEnvVarShort)
                $w add command -label "View file" \
                    -command [list ${ns}::_StartEditor "File with warning" "" $fileName $libName]
                if { $editorProg ne "" } {
                    $w add command -label "View file ($editorShort)" \
                        -command [list ${ns}::_StartEditor "File with warning" $editorProg $fileName $libName]
                }
            }
            tk_popup $w $x $y
        }
    }

    proc _StartEditor { type editorName fileName libName { logType "Build" } } {
        variable ns
        variable sGui
        variable sCompilerWarningErrorPatterns
        variable sCompilerErrorColor
        variable sCompilerWarningColor
        variable sTestPatterns
        variable sTopLevel
        variable sEditorCount

        if { $editorName ne "" } {
            eval exec $editorName [list $fileName] &
            return
        }

        if { ! [info exists sEditorCount] } {
            set sEditorCount 0
        }
        incr sEditorCount
        set top ${sTopLevel}_Editor_$sEditorCount
        toplevel $top

        set mainfr "$top.mainfr"
        ttk::frame $mainfr
        pack $mainfr -expand true -fill both

        set listfr $mainfr.listfr
        set editfr $mainfr.editfr
        ttk::frame $listfr
        ttk::frame $editfr

        grid $listfr -row 0 -column 0 -sticky news
        grid $editfr -row 1 -column 0 -sticky news
        grid rowconfigure    $mainfr 1 -weight 1
        grid columnconfigure $mainfr 0 -weight 1

        set tableId [poWin CreateScrolledTablelist $listfr true "" \
                    -width 60 \
                    -exportselection false \
                    -columns { 3 "#"        "right"
                               4 "Line"     "right"
                               0 "Messages" "left" } \
                    -setfocus 1 \
                    -stretch 2 \
                    -stripebackground #e0e8f0 \
                    -selectmode single \
                    -showseparators yes]
        $tableId columnconfigure 0 -showlinenumbers true
        
        set textId [poExtProg ShowSimpleTextEdit $fileName $editfr true -undo true]

        bind $tableId <<TablelistSelect>> "${ns}::_ShowWarningOrFailure $tableId $textId"
        set bodyTag [$tableId bodytag]
        bind $bodyTag <<RightButtonPress>> [list ${ns}::_OpenLogContextMenu $tableId %X %Y $libName]

        if { $logType eq "Build" } {
            $textId tag configure "C-Error"   -background $sCompilerErrorColor
            $textId tag configure "C-Warning" -background $sCompilerWarningColor
            set numLines [$textId count -lines 1.0 end]
            set numWarnings 0
            for { set l 1 } { $l <= $numLines } { incr l } {
                set line [$textId get $l.0 $l.end]
                foreach { pattern tag } $sCompilerWarningErrorPatterns {
                    if { [string match -nocase $pattern $line] } {
                        $textId tag add $tag $l.0 $l.end
                        $tableId insert end [list "" $l $line]
                        $tableId rowattrib end "Line" $l.0
                        incr numWarnings
                        break
                    }
                }
            }
            set titleStr [format "%s: %s (%d warnings)" $type [file tail $fileName] $numWarnings]
            if { $numWarnings == 0 } {
                destroy $tableId
            } elseif { $numWarnings < 10 } {
                $tableId configure -height [expr $numWarnings + 1]
            } else {
                $tableId configure -height 11
            }
        } elseif { $logType eq "Test" } {
            $textId tag configure "Failure" -background $sCompilerWarningColor
            set numLines [$textId count -lines 1.0 end]
            set numFailures       0
            set foundTestStart    false
            set foundTestEnd      false
            set foundNonEmptyLine false
            set lastTestName      ""
            for { set l 1 } { $l <= $numLines } { incr l } {
                set line [$textId get $l.0 $l.end]
                set line [string trim $line]
                if { ! $foundTestStart } {
                    if { [string match $sTestPatterns(Start) $line] } {
                        set foundTestStart true
                    }
                } elseif { ! $foundTestEnd } {
                    if { $line ne "" } {
                        if { [string match $sTestPatterns(End) $line] } {
                            set foundTestEnd true
                        } elseif { [string match $sTestPatterns(Fail1) $line] } {
                            # tcltest failure message.
                            scan $line $sTestPatterns(Scan) testName
                            if { $testName ne $lastTestName } {
                                set lastTestName $testName
                                $textId tag add "Failure" $l.0 $l.end
                                $tableId insert end [list "" $l $line]
                                $tableId rowattrib end "Line" $l.0
                                incr numFailures
                            }
                        } elseif { [string match $sTestPatterns(Error) $line] } {
                            $textId tag add "Failure" $l.0 $l.end
                            $tableId insert end [list "" $l $line]
                            $tableId rowattrib end "Line" $l.0
                            incr numFailures
                        } else {
                            foreach pattern $sTestPatterns(Fail2) {
                                if { [string match $pattern $line] } {
                                    $textId tag add "Failure" $l.0 $l.end
                                    $tableId insert end [list "" $l $line]
                                    $tableId rowattrib end "Line" $l.0
                                    incr numFailures
                                }
                            }
                        }
                        if { ! $foundTestEnd } {
                            set foundNonEmptyLine true
                        }
                    }
                }
            }
            if { $foundTestEnd && $foundNonEmptyLine } {
                set titleStr [format "%s: %s (%d failures)" $type [file tail $fileName] $numFailures]
            } else {
                set titleStr [format "%s: %s (No tests performed)" $type [file tail $fileName]]
            }
            if { $numFailures == 0 } {
                destroy $tableId
            } elseif { $numFailures < 10 } {
                $tableId configure -height [expr $numFailures + 1]
            } else {
                $tableId configure -height 11
            }
        }
        update
        $textId edit modified false
        wm title $top $titleStr
        bind $top <Key-Escape> "destroy $top"
        wm protocol $top WM_DELETE_WINDOW "destroy $top"
        focus $top
    }

    proc ViewLibLogFile { tableId { logType "Build" } } {
        variable sGui

        set libName [_GetLibNameFromTable $tableId]
        set libLogFile [_GetLibLogFile $libName $logType]
        if { $libLogFile ne "" } {
            _StartEditor "Library $logType Log" "" $libLogFile $libName $logType
        } else {
            SetStatusMsg "$logType Log file for library $libName not existent." "Error"
        }
    }

    proc ShowLib { tableId } {
        variable sGui
        variable sData

        set libName [_GetLibNameFromTable $tableId]
        if { $libName eq "" } {
            return
        }

        foreach tmpLibName [_GetLibNames $tableId] {
            $sGui(LogId) tag configure $tmpLibName -background white
        }

        if { [info exists sData(Tag,Start,$libName)] } {
            $sGui(LogId) see $sData(Tag,Start,$libName)
            $sGui(LogId) tag configure $libName -background yellow
        }
        $sGui(LogId) tag configure "Warning" -foreground red
    }

    proc AskOpenFile {} {
        variable sGui

        set fileName [tk_getOpenFile  \
                     -initialdir $sGui(LastDir) \
                     -title "Select file"]
        if { $fileName ne "" } {
            ClearGui
            set timeNeeded [ReadFile $fileName]
            SetLogFile $fileName $timeNeeded
        }
    }

    proc ExitApp { retVal } {
        variable sGui

        if { [info exists sGui(autosaveOnExit)] && $sGui(autosaveOnExit) } {
            SaveSettings
        }
        poMisc CleanTclkitDirs
        exit $retVal
    }

    proc UpdateTitle { setupFile { compilerVersions "" } } {
        variable sTopLevel

        if { $compilerVersions eq "" } {
            wm title $sTopLevel "BAWT - Setup file $setupFile"
        } else {
            wm title $sTopLevel "BAWT - Setup file $setupFile ($compilerVersions)"
        }
    }

    proc GetCopyright { { pureInfo false } } {
        if { $pureInfo } {
            return "2018-2025 Paul Obermeier"
        } else {
            return "Copyright 2018-2025 Paul Obermeier"
        }
    }

    proc GetAppName {} {
        return "BawtLogViewer"
    }

    proc GetCfgDir {} {
        return [file join [poMisc GetHomeDir] ".[GetAppName]"]
    }

    proc GetVersionNumber {} {
        return "3.1.0"
    }

    proc GetBuildInfo { { pureInfo false } } {
        set buildNumber "Developer"
        set buildDate   "N/A"
        if { [info procs GetBuildNumber] ne "" } {
            set buildNumber [GetBuildNumber]
        }
        if { [info procs GetBuildDate] ne "" } {
            set buildDate [GetBuildDate]
        }
        if { $pureInfo } {
            return [format "%s (Date: %s)" $buildNumber $buildDate]
        } else {
            return [format "Build: %s (Date: %s)" $buildNumber $buildDate]
        }
    }

    proc GetVersion {} {
        return "[GetAppName] [GetVersionNumber] ([poMisc GetOSBitsStr])"
    }

    proc _DestroyLogo { topWin } {
        variable sGui

        catch {image delete $sGui(logoImg)}
        destroy $topWin
    }

    proc HelpProg {} {
        variable ns
        variable sGui

        set version       [GetVersion]
        set buildInfo     [GetBuildInfo true]
        set copyright     [GetCopyright true]
        set url           "https://www.tcl3d.org/bawt/"
        set sGui(logoImg) [::poImgData::poLogo]

        set t ".bawtMineShowLogo"
        if { [winfo exists $t] } {
            poWin Raise $t
            return
        }

        toplevel $t
        ttk::frame $t.f1
        ttk::frame $t.f2
        pack $t.f1 $t.f2
        wm resizable $t false false
        wm title $t "BAWT Information"

        ttk::label $t.f1.l1 -anchor center -text "Paul Obermeier's Portable Software"
        pack $t.f1.l1 -fill x
        if { [tk windowingsystem] eq "aqua" } {
            ttk::button $t.f1.b -image $sGui(logoImg) -style Toolbutton
        } else {
            ttk::button $t.f1.b -image $sGui(logoImg)
        }
        poToolhelp AddBinding $t.f1.b $url
        bind $t.f1.b <Button-1> "poExtProg OpenUrl $url"
        pack $t.f1.b

        set row 0
        foreach labelStr { "Version:" \
                           "Build:" \
                           "Host:" \
                           "Copyright:" } {
            ttk::label $t.f2.l$row -text $labelStr
            grid  $t.f2.l$row -row $row -column 0 -sticky new
            incr row
        }

        ttk::label $t.f2.i0 -text $version
        ttk::label $t.f2.i1 -text $buildInfo
        ttk::label $t.f2.i2 -text "$::tcl_platform(os) $::tcl_platform(osVersion)"
        ttk::label $t.f2.i3 -text $copyright
        grid $t.f2.i0 -row 0 -column 1 -sticky news
        grid $t.f2.i1 -row 1 -column 1 -sticky news
        grid $t.f2.i2 -row 2 -column 1 -sticky news
        grid $t.f2.i3 -row 3 -column 1 -sticky news

        bind $t <KeyPress-Escape> "${ns}::_DestroyLogo $t"
        bind $t <KeyPress-Return> "${ns}::_DestroyLogo $t"
        wm protocol $t WM_DELETE_WINDOW "${ns}::_DestroyLogo $t"
        focus $t
        update idletasks
    }

    proc HelpTcl {} {
        set pkgs "Tcl Tk scrollutil_tile tablelist_tile tkdnd"
        poSoftLogo ShowTclLogo {*}$pkgs
    }

    proc _StartFileBrowser { dir } {
        set browserProg ""
        if { $::tcl_platform(platform) eq "windows" } {
            set browserProg "explorer"
        } elseif { $::tcl_platform(os) eq "Linux" } {
            foreach prog [list "dolphin" "konqueror" "nautilus" "pcmanfm"] {
                if { [auto_execok $prog] ne "" } {
                    set browserProg $prog
                    break
                }
            }
            if { $browserProg eq "" } {
                error "No file browser found. Looked for dolphin, konqueror, nautilus and pcmanfm."
            }
        } elseif { $::tcl_platform(os) eq "Darwin" } {
            set browserProg "open"
        }
        if { [file isdirectory $dir] } {
            eval exec $browserProg [list [file nativename $dir]] &
        }
    }

    proc StartShell { type dir } {
        variable sGui

        set cwd [pwd]
        cd $dir
        if { $type eq "dos" || $type eq "msys" } {
            if { $type eq "dos" } {
                eval exec cmd /c start &
            } else {
                if { ! [info exists sGui(MSysShell)] } {
                    error "No MSys shell found. You might be using an old BAWT log file."
                }
                set batchFile [file join $dir "_Bawt_StartMSysConsole.bat"]
                if { ! [file exists $batchFile] } {
                    set retVal [catch {open $batchFile "w"} fp]
                    if { $retVal != 0 } {
                        error "Cannot write to file $batchFile"
                    }
                    puts $fp "SET HOME=[file nativename $dir]"
                    puts $fp "$sGui(MSysShell)"
                    close $fp
                }
                eval exec $batchFile & 
            }
        } elseif { $type eq "Linux" } {
            set shellProg ""
            foreach prog [list "konsole" "gnome-terminal" "lxterminal" "ptyxis" "xterm"] {
                if { [auto_execok $prog] ne "" } {
                    set shellProg $prog
                    break
                }
            }
            if { $shellProg eq "" } {
                error "No shell found. Looked for konsole, gnome-terminal, lxterminal, ptyxis and xterm."
            }
            if { $shellProg eq "ptyxis" } {
                eval exec $shellProg -d "$dir" --new-window -s &
	    } else {
                eval exec $shellProg &
	    }
        } elseif { $type eq "Darwin" } {
            eval exec "open" "-a Terminal" [list $dir] &
        }
        cd $cwd
    }

    proc _ShowLibByKey { tableId key } {
        variable sCurLib
        variable sGui

        set key [string tolower $key]

        set indList [$tableId curselection]
        if { [llength $indList] == 0 } {
            set row 0
            $tableId see $row
            $tableId selection set $row
        } else {
            set row [lindex $indList 0]
        }

        if { $key eq "down" || $key eq "up" || $key eq "left" || $key eq "right" ||
             $key eq "control_l" || $key eq "control_r" || $key eq "return" } {
            set newRow -1
            if { $key eq "down" && $row < [$tableId size] - 1 } {
                set newRow [expr {$row + 1}]
            } elseif { $key eq "up" && $row > 0 } {
                set newRow [expr {$row - 1}]
            }
            if { $newRow >= 0 } {
                $tableId selection clear 0 end
                $tableId selection set $newRow
            }
            return
        }

        incr row
        if { ! [info exists sGui(KeyPressTime)] } {
            # First call of this procedure.
            set sGui(KeyPressTime) [clock milliseconds]
            set sGui(SearchString) $key
        } else {
            set curTime [clock milliseconds]
            if { $curTime - $sGui(KeyPressTime) < [GetKeyRepeatTime] && \
                 $key ne [string index $sGui(SearchString) end] } {
                append sGui(SearchString) $key
            } else {
                set sGui(SearchString) $key
            }
            set sGui(KeyPressTime) $curTime
        }

        set libList [_GetLibNames $tableId]
        set numLibs [llength $libList]
        for { set i 0 } { $i < $numLibs } { incr i } {
            set libName [lindex $libList $row]
            if { ( [string first $sGui(SearchString) [string tolower $libName]] == 0 ) || \
                 ( $sCurLib ne "" && $key eq "return" && $libName eq $sCurLib ) } {
                $tableId selection clear 0 end
                $tableId selection set $row $row
                event generate $tableId <<TablelistSelect>>
                $tableId activate $row
                $tableId see $row
                break
            }
            set row [expr {( $row + 1 ) % $numLibs }]
        }
    }

    proc _OpenContextMenu { tableId x y } {
        variable ns
        variable sGui

        if { ! [info exists sGui(SetupFile)] } {
            return
        }
        set w .bawtLogViewer:contextMenu
        catch { destroy $w }
        menu $w -tearoff false -disabledforeground white

        set libName [_GetLibNameFromTable $tableId]
        if { $libName eq "" } {
            set menuTitle "Nothing selected"
        } else {
            set menuTitle "$libName selected"
        }
        $w add command -label "$menuTitle" -state disabled -background "#303030"

        if { $libName ne "" } {
            $w add command -label "Save [GetBuildOrTestString] time" \
               -command "${ns}::_SaveBuildOrTestTime $sGui(TableId) $libName"
        }

        set logDir       [_GetLogDir]
        set devDir       [_GetDevDir]
        set buildDirList [_GetLibDirs $libName "Build"]
        set instDirList  [_GetLibDirs $libName "Install"]

        if { $logDir ne "" || $devDir ne "" || \
             [llength $buildDirList] >= 1 || [llength $instDirList] >= 1 } { 
            $w add cascade -label "Open directory" -menu $w.open
            menu $w.open -tearoff 0
        }
        if { $logDir ne "" } {
            $w.open add command -label "Logs" \
                    -command "${ns}::_StartFileBrowser $logDir"
        }
        if { $devDir ne "" } {
            $w.open add command -label "Development" \
                    -command "${ns}::_StartFileBrowser $devDir"
        }

        if { $libName ne "" } {
            if { [llength $buildDirList] >= 1 || [llength $instDirList] >= 1 } {
                $w.open add separator
            }
            foreach dir [concat $buildDirList $instDirList] {
                set pathList [lrange [file split $dir] end-2 end-1]
                set dirName  [join $pathList "/"]
                $w.open add command -label $dirName -command "${ns}::_StartFileBrowser $dir"
            }
        }

        $w add cascade -label "View file" -menu $w.view
        menu $w.view -tearoff 0

        set editorProg  $sGui(Text,EditorEnvVarProg)
        set editorShort $sGui(Text,EditorEnvVarShort)

        $w.view add command -label "BAWT Setup" \
                -command [list ${ns}::_StartEditor "BAWT Setup" "" $sGui(SetupFile) $libName]
        if { $editorProg ne "" } {
            $w.view add command -label "BAWT Setup ($editorShort)" \
                    -command [list ${ns}::_StartEditor "BAWT Setup" $editorProg $sGui(SetupFile) $libName]
        }
        $w.view add command -label "BAWT Log" \
                -command [list ${ns}::_StartEditor "BAWT Log" "" $sGui(LogFile) $libName]
        if { $editorProg ne "" } {
            $w.view add command -label "BAWT Log ($editorShort)" \
                    -command [list ${ns}::_StartEditor "BAWT Log" $editorProg $sGui(LogFile) $libName]
        }

        if { $libName ne "" } {
            $w.view add separator

            set libBuildLogFile [_GetLibLogFile $libName]
            if { $libBuildLogFile ne "" } {
                $w.view add command -label "Library Build Log" \
                        -command [list ${ns}::_StartEditor "Library Build Log" "" $libBuildLogFile $libName]
                if { $editorProg ne "" } {
                    $w.view add command -label "Library Build Log ($editorShort)" \
                            -command [list ${ns}::_StartEditor "Library Build Log" $editorProg $libBuildLogFile $libName]
                }
            }
            set libTestLogFile [_GetLibLogFile $libName "Test"]
            if { $libTestLogFile ne "" } {
                $w.view add command -label "Library Test Log" \
                        -command [list ${ns}::_StartEditor "Library Test Log" "" $libTestLogFile $libName "Test"]
                if { $editorProg ne "" } {
                    $w.view add command -label "Library Test Log ($editorShort)" \
                            -command [list ${ns}::_StartEditor "Library Test Log" $editorProg $libTestLogFile $libName "Test"]
                }
            }
            if { [info exists sGui(BuildFile,$libName)] && \
                 [file exists $sGui(BuildFile,$libName)] } {
                $w.view add command -label "Library build" \
                        -command [list ${ns}::_StartEditor "Library build" "" $sGui(BuildFile,$libName) $libName]
                if { $editorProg ne "" } {
                    $w.view add command -label "Library build ($editorShort)" \
                            -command [list ${ns}::_StartEditor "Library build" $editorProg $sGui(BuildFile,$libName) $libName]
                }
            }
            if { [info exists sGui(UserBuildFile,$libName)] && \
                 [file exists $sGui(UserBuildFile,$libName)] } {
                $w.view add command -label "Library user build" \
                        -command [list ${ns}::_StartEditor "Library user build" "" $sGui(UserBuildFile,$libName) $libName]
                if { $editorProg ne "" } {
                    $w.view add command -label "Library user build ($editorShort)" \
                            -command [list ${ns}::_StartEditor "Library user build" $editorProg $sGui(UserBuildFile,$libName) $libName]
                }
            }

            if { [llength $buildDirList] >= 1 } {
                $w add cascade -label "Open shell" -menu $w.shell
                menu $w.shell -tearoff 0

                foreach dir $buildDirList {
                    set pathList [lrange [file split $dir] end-2 end-1]
                    set dirName  [join $pathList "/"]
                    if { $::tcl_platform(platform) eq "windows" } {
                        $w.shell add command -label "${dirName}/DOS"  -command "${ns}::StartShell dos $dir"
                        $w.shell add command -label "${dirName}/MSys" -command "${ns}::StartShell msys $dir"
                    } elseif { $::tcl_platform(os) eq "Linux" } {
                        $w.shell add command -label "${dirName}" -command "${ns}::StartShell Linux $dir"
                    } elseif { $::tcl_platform(os) eq "Darwin" } {
                        $w.shell add command -label "${dirName}" -command "${ns}::StartShell Darwin $dir"
                    }
                }
            }
        }

        tk_popup $w $x $y
    }

    proc AddRecentFiles { menuId } {
        variable ns

        poMenu DeleteMenuEntries $menuId 3
        poMenu AddRecentFileList $menuId ${ns}::LoadRecentFile
    }

    proc ShowMainWin { { setupFile "None" } } {
        variable ns
        variable sTopLevel
        variable sGui
        variable sData
        variable sCompilerErrorColor
        variable sCompilerWarningColor

        if { [winfo exists $sTopLevel] } {
            wm deiconify $sTopLevel
            return
        }

        # Try to load settings file.
        if { ! [file isdirectory [GetCfgDir]] } {
            file mkdir [GetCfgDir]
        }
        LoadSettings
        set sGui(TestStage) false

        if { [info exists sTopLevel] && [winfo exists $sTopLevel] } {
            destroy $sTopLevel
        }

        set sGui(Text,EditorEnvVarProg)  ""
        set sGui(Text,EditorEnvVarShort) ""
        if { [info exists ::env(EDITOR)] } {
            set prog $::env(EDITOR)
            if { [auto_execok $prog] ne "" } {
                set sGui(Text,EditorEnvVarProg)  $prog
                set sGui(Text,EditorEnvVarShort) [file tail $prog]
            }
        }

        # If Tk could be loaded, create a toplevel window and generate
        # the master frame, where other widgets can be added dynamically.
        set sData(NumLibs) 0

        toplevel $sTopLevel -class [GetAppName]
        wm withdraw .
        UpdateTitle $setupFile

        if { $sGui(MainWin,w) <= 0 || $sGui(MainWin,h) <= 0 } {
            wm geometry $sTopLevel [format "+%d+%d" $sGui(MainWin,x) $sGui(MainWin,y)]
        } else {
            wm geometry $sTopLevel [format "%dx%d+%d+%d" \
                        $sGui(MainWin,w) $sGui(MainWin,h) \
                        $sGui(MainWin,x) $sGui(MainWin,y)]
        }

        set sGui(MasterFr) $sTopLevel.fr
        ttk::frame $sGui(MasterFr)
        pack $sGui(MasterFr) -side top -fill both -expand 1

        set workFr   $sGui(MasterFr).workFr
        set statusFr $sGui(MasterFr).statusFr
        ttk::frame $workFr
        ttk::frame $statusFr
        grid $workFr   -row 0 -column 0 -sticky news
        grid $statusFr -row 1 -column 0 -sticky news
        grid rowconfigure    $sGui(MasterFr) 0 -weight 1
        grid columnconfigure $sGui(MasterFr) 0 -weight 1

        set paneId $workFr.paneFr
        ttk::panedwindow $paneId -orient vertical
        pack $paneId -side top -expand 1 -fill both

        set tableFr $paneId.tableFr
        set logFr   $paneId.logFr
        ttk::frame $tableFr
        ttk::frame $logFr
        grid $tableFr -row 0 -column 0 -sticky news
        grid $logFr   -row 1 -column 0 -sticky news
        grid rowconfigure    $workFr 0 -weight 1
        grid rowconfigure    $workFr 1 -weight 1
        grid columnconfigure $workFr 0 -weight 1

        $paneId add $tableFr
        $paneId add $logFr

        set sGui(Col,LibName)        2
        set sGui(Col,UserBuild)      3
        set sGui(Col,LibCompiler)    5
        set sGui(Col,BuildTime)      6
        set sGui(Col,EstimatedTime)  7
        set sGui(Col,ModTime)        8
        set sGui(Col,UpdateCause)    9
        set sGui(Col,Stages)        10
        set sGui(Col,UserConfig)    11

        set sGui(TableId) [poWin CreateScrolledTablelist $tableFr true "Library list" \
            -height 10 -width 100 \
            -exportselection false \
            -columns { 3 "#"            "right"
                       8 "Build-#"      "right"
                      18 "Library Name" "left"
                       8 "User file"    "left"
                       8 "Version"      "left"
                       9 "Compiler"     "left"
                       0 "Exe. time"    "right"
                       0 "Est. time"    "right"
                       0 "Mod. time"    "left"
                       0 "Update cause" "left"
                       0 "Stages"       "left"
                       0 "User config"  "left" } \
            -setfocus true \
            -stretch $sGui(Col,UserConfig)  \
            -stripebackground #e0e8f0 \
            -selectmode single \
            -showseparators true]
        $sGui(TableId) columnconfigure 0 -showlinenumbers true
        $sGui(TableId) columnattrib    2 "LibName" 1
        bind $sGui(TableId) <<TablelistSelect>> "${ns}::ShowLib $sGui(TableId)"
        set bodyTag [$sGui(TableId) bodytag]
        bind $bodyTag <<RightButtonPress>> [list ${ns}::_OpenContextMenu $sGui(TableId) %X %Y]
        bind $bodyTag <Double-1>           [list ${ns}::ViewLibLogFile $sGui(TableId)]
        bind $bodyTag <KeyPress-Return>    [list ${ns}::ViewLibLogFile $sGui(TableId)]
        bind $bodyTag <Any-KeyPress>       [list ${ns}::_ShowLibByKey $sGui(TableId) %K]
        $sGui(TableId) columnconfigure  1 -sortmode integer
        $sGui(TableId) columnconfigure  2 -sortmode dictionary
        $sGui(TableId) columnconfigure  3 -sortmode dictionary
        $sGui(TableId) columnconfigure  4 -sortmode dictionary
        $sGui(TableId) columnconfigure  5 -sortmode dictionary
        $sGui(TableId) columnconfigure  6 -sortmode dictionary
        $sGui(TableId) columnconfigure  7 -sortmode dictionary
        $sGui(TableId) columnconfigure  8 -sortmode dictionary
        $sGui(TableId) columnconfigure  9 -sortmode dictionary
        $sGui(TableId) columnconfigure 10 -sortmode dictionary
        $sGui(TableId) columnconfigure 11 -sortmode dictionary

        set sGui(LogId) [poWin CreateScrolledText $logFr true "Log messages" \
            -height 20 -width 100]
        $sGui(LogId) tag configure "C-Error"   -background $sCompilerErrorColor
        $sGui(LogId) tag configure "C-Warning" -background $sCompilerWarningColor
        _ToggleLineWrap

        if { $sGui(Have,tkdnd) } {
            poDragAndDrop AddCanvasBinding $sGui(TableId) "${ns}::LoadFile"
            poDragAndDrop AddCanvasBinding $sGui(LogId)   "${ns}::LoadFile"
        }

        # Create widget for status messages with progress bar.
        set sGui(StatusWidget) [poWin CreateStatusWidget $statusFr -progress true]

        # Create menu.
        set hMenu $sTopLevel.menufr
        menu $hMenu -borderwidth 2 -relief sunken

        if { $::tcl_platform(os) eq "Darwin" } {
            $hMenu add cascade -menu $hMenu.apple -label [GetAppName]
            set appleMenu $hMenu.apple
            menu $appleMenu -tearoff 0
            poMenu AddCommand $appleMenu "About [GetAppName] ..."  ""   ${ns}::HelpProg
            poMenu AddCommand $appleMenu "About Tcl/Tk ..."        ""   ${ns}::HelpTcl
        }

        set fileMenu $hMenu.file
        set settMenu $hMenu.sett
        set helpMenu $hMenu.help
        $hMenu add cascade -menu $fileMenu -label "File"     -underline 0
        $hMenu add cascade -menu $settMenu -label "Settings" -underline 0
        $hMenu add cascade -menu $helpMenu -label "Help"     -underline 0

        # Menu File
        set sGui(AutoUpdate) 1
        menu $fileMenu -tearoff 0

        set openMenu $fileMenu.open
        $fileMenu add cascade -label "Open log file" -menu $openMenu

        menu $openMenu -tearoff 0 -postcommand "${ns}::AddRecentFiles $openMenu"
        poMenu AddCommand $openMenu "Select ..."    "Ctrl+O" ${ns}::AskOpenFile
        poMenu AddCommand $openMenu "Edit list ..." ""       "poAppearance EditRecentList recentFileList"
        $openMenu add separator

        poMenu AddCommand $fileMenu "Reload log file"  "F5" ${ns}::ReloadFile
        poMenu AddCheck   $fileMenu "Auto Update"      "F6" ${ns}::sGui(AutoUpdate) ""
        $fileMenu add separator

        poMenu AddCommand $fileMenu "Save build/test times" ""   ${ns}::SaveBuildTimes
        poMenu AddCommand $fileMenu "View est. times"       "F9" ${ns}::ViewEstimatedBuildTimes
        $fileMenu add separator

        poMenu AddCommand $fileMenu "View compiler warnings" "F7"  ${ns}::ViewCompilerWarnings
        poMenu AddCommand $fileMenu "View test failures"     "F8"  ${ns}::ViewTestFailures
        $fileMenu add separator

        if { $::tcl_platform(os) ne "Darwin" } {
            poMenu AddCommand $fileMenu "Quit" "Ctrl+Q" "${ns}::ExitApp 0"
        }

        bind $sTopLevel <Control-o> ${ns}::AskOpenFile
        bind $sTopLevel <F5>        ${ns}::ReloadFile
        bind $sTopLevel <F6>        ${ns}::ToggleAutoUpdate
        bind $sTopLevel <F7>        ${ns}::ViewCompilerWarnings
        bind $sTopLevel <F8>        ${ns}::ViewTestFailures
        bind $sTopLevel <F9>        ${ns}::ViewEstimatedBuildTimes
        bind $sTopLevel <Control-q> "${ns}::ExitApp 0"
        if { $::tcl_platform(platform) eq "windows" } {
            bind $sTopLevel <Alt-F4> "${ns}::ExitApp 0"
        }

        wm protocol $sTopLevel WM_DELETE_WINDOW "${ns}::ExitApp 0"

        # Menu Settings
        menu $settMenu -tearoff 0

        set thresMenu $fileMenu.thres
        $settMenu add cascade -label "Deadlock threshold" -menu $thresMenu
        menu $thresMenu -tearoff 0
        poMenu AddRadio $thresMenu "  0%"  "" ${ns}::sGui(Deadlock,Threshold) 0.0  ""
        poMenu AddRadio $thresMenu " 25%"  "" ${ns}::sGui(Deadlock,Threshold) 0.25 ""
        poMenu AddRadio $thresMenu " 50%"  "" ${ns}::sGui(Deadlock,Threshold) 0.5  ""
        poMenu AddRadio $thresMenu " 75%"  "" ${ns}::sGui(Deadlock,Threshold) 0.75 ""
        poMenu AddRadio $thresMenu "100%"  "" ${ns}::sGui(Deadlock,Threshold) 1.0  ""

        poMenu AddCheck $settMenu "Deadlock beep" "" ${ns}::sGui(Deadlock,UseBeep) ""

        $settMenu add separator
        set timeMenu $fileMenu.time
        $settMenu add cascade -label "Key repeat time" -menu $timeMenu
        menu $timeMenu -tearoff 0
        poMenu AddRadio $timeMenu " 200 ms"  "" ${ns}::sGui(KeyRepeatTime)  200  ""
        poMenu AddRadio $timeMenu " 400 ms"  "" ${ns}::sGui(KeyRepeatTime)  400 ""
        poMenu AddRadio $timeMenu " 600 ms"  "" ${ns}::sGui(KeyRepeatTime)  600  ""
        poMenu AddRadio $timeMenu " 800 ms"  "" ${ns}::sGui(KeyRepeatTime)  800 ""
        poMenu AddRadio $timeMenu "1000 ms"  "" ${ns}::sGui(KeyRepeatTime) 1000  ""

        $settMenu add separator
        poMenu AddCheck $settMenu "Line wrap"  "" ${ns}::sGui(Text,LineWrap) ${ns}::_ToggleLineWrap

        $settMenu add separator
        poMenu AddCheck   $settMenu "Save on exit"       "" ${ns}::sGui(autosaveOnExit) ""
        poMenu AddCommand $settMenu "View setting files" "" ${ns}::ViewSettingsDir
        poMenu AddCommand $settMenu "Save settings"      "" ${ns}::SaveSettings

        # Menu Help
        menu $helpMenu -tearoff 0
        if { $::tcl_platform(os) ne "Darwin" } {
            poMenu AddCommand $helpMenu "About [GetAppName] ..." "" ${ns}::HelpProg
            poMenu AddCommand $helpMenu "About Tcl/Tk ..."       "" ${ns}::HelpTcl
        }

        $sTopLevel configure -menu $hMenu

        update
    }

    proc ClearGui {} {
        variable sGui
        variable sData

        $sGui(LogId) delete 0.1 end
        $sGui(TableId) delete 0 end
        poWin SetScrolledTitle $sGui(LogId)   "Reading log file ..."
        poWin SetScrolledTitle $sGui(TableId) "Reading log file ..."
        catch { unset sData }
        set sData(NumLibs) 0
    }

    proc _GetLogDir {} {
        variable sGui

        if { [info exists sGui(LogFile)] } {
            set dir [file dirname $sGui(LogFile)]
            if { [file isdirectory $dir] } {
                return $dir
            }
            if { [info exists sGui(LastFile)] } {
                set dir [file dirname $sGui(LastFile)]
                if { [file isdirectory $dir] } {
                    return $dir
                }
            }
        }
        return ""
    }

    proc _GetRootDir {} {
        set logDir  [_GetLogDir]
        if { $logDir ne "" } {
            set rootDir [file dirname $logDir]
            if { [file isdirectory $rootDir] } {
                return $rootDir
            }
        }
        return ""
    }

    proc _GetDevDir {} {
        set rootDir [_GetRootDir]
        if { $rootDir ne "" } {
            set dir [file join $rootDir "Development"]
            if { $dir ne "" && [file isdirectory $dir] } {
                return $dir
            }
        }
        return ""
    }

    proc _GetLibLogFile { libName { logType "Build" } } {
        variable sGui

        set libLogFile ""
        set logDir [_GetLogDir]
        if { $libName ne "" && $logDir ne "" } {
            if { $logType eq "Build" } {
                set fileName [file join $logDir "${libName}.log"]
            } else {
                set fileName [file join $logDir "${libName}-Test.log"]
            }
            if { [file exists $fileName] } {
                set libLogFile $fileName
            }
        }
        return $libLogFile
    }

    proc _GetLibDirs { libName dirType } {
        set rootDir [_GetRootDir]

        set dirList [list]
        if { $rootDir ne "" } {
            foreach buildType { "Release" "Debug" } {
                set dir ""
                switch $dirType {
                    "Build"   { set dir [file join $rootDir $buildType "Build"   $libName] }
                    "Install" { set dir [file join $rootDir $buildType "Install" $libName] }
                }
                if { $dir ne "" && [file isdirectory $dir] } {
                    lappend dirList $dir
                }
            }
        }
        return $dirList
    }

    proc SetLibCompiler { libName libCompiler } {
        variable sLibCompiler

        set sLibCompiler($libName) $libCompiler
    }

    proc GetLibCompiler { libName } {
        variable sLibCompiler

        set libCompiler ""
        if { [info exists sLibCompiler($libName)] } {
            set libCompiler $sLibCompiler($libName)
        }
        return $libCompiler
    }

    proc AppendLib { libName libVersion libCompiler } {
        variable ns
        variable sGui
        variable sData

        set libLogFile [_GetLibLogFile $libName]
        set timeStr ""
        if { $libLogFile ne "" } {
            set timeStr [clock format [file mtime $libLogFile] -format "%Y-%m-%d %H:%M:%S"]
        }
        set estimatedTimeStr ""
        set estimatedTime [GetEstimatedBuildTime $libName [GetCompilerOrTest $libCompiler]]
        if { $estimatedTime > 0.0 } {
            set estimatedTimeStr [format "%.2f" $estimatedTime]
        }
        SetLibCompiler $libName $libCompiler
        set userBuild ""
        if { [info exists sGui(UserBuildFile,$libName)] } {
            set userBuild "Yes"
        }
        $sGui(TableId) insert end [list "" [expr {$sData(NumLibs) + 1}] $libName $userBuild $libVersion $libCompiler "" $estimatedTimeStr $timeStr "" "" ""]
        incr sData(NumLibs)
        poWin SetScrolledTitle $sGui(TableId) [format "Setup contains %d libraries" $sData(NumLibs)]
    }

    proc SetLibWarning { libName } {
        variable sGui

        set row [_GetLibRow $sGui(TableId) $libName]
        if { $row >= 0 } {
            $sGui(TableId) rowconfigure $row -foreground "red"
        }
	set tag [$sGui(LogId) index end]
	$sGui(LogId) tag add "Warning" "$tag - 2 lines" "$tag - 1 lines"
    }

    proc SetLibStatus { libName status msg } {
        variable sGui
        variable sData

        set col $sGui(Col,BuildTime)
        set row [_GetLibRow $sGui(TableId) $libName]
        if { $row >= 0 } {
            set color "grey"
            if { $status eq "Started" } {
                set color "yellow"
                set sData(Tag,Start,$libName) [$sGui(LogId) index end]
            } elseif { $status eq "Excluded" } {
                set col $sGui(Col,UpdateCause)
                set color "cyan"
                set sData(Tag,End,$libName) [$sGui(LogId) index end]
                $sGui(LogId) tag add $libName "$sData(Tag,Start,$libName) - 2 lines" "$sData(Tag,End,$libName) - 1 lines"
                poTablelistUtil SetCellValue $sGui(TableId) $row $sGui(Col,BuildTime) ""
            } elseif { $status eq "Finished" } {
                set color "lightgreen"
                set sData(Tag,End,$libName) [$sGui(LogId) index end]
                $sGui(LogId) tag add $libName "$sData(Tag,Start,$libName) - 2 lines" "$sData(Tag,End,$libName) - 1 lines"
            }
            $sGui(TableId) rowconfigure $row -background $color
            poTablelistUtil SetCellValue $sGui(TableId) $row $col $msg
            $sGui(TableId) see $row
            set sGui(SeeRow) $row
        }
    }

    proc SetLibStages { libName stages } {
        variable sGui

        set row [_GetLibRow $sGui(TableId) $libName]
        if { $row >= 0 } {
            poTablelistUtil SetCellValue $sGui(TableId) $row $sGui(Col,Stages) $stages
        }
    }

    proc SetLibUpdateCause { libName cause } {
        variable sGui

        set row [_GetLibRow $sGui(TableId) $libName]
        if { $row >= 0 } {
            poTablelistUtil SetCellValue $sGui(TableId) $row $sGui(Col,UpdateCause) $cause
        }
    }

    proc SetLibUserConfig { libName config } {
        variable sGui

        set row [_GetLibRow $sGui(TableId) $libName]
        if { $row >= 0 } {
            poTablelistUtil SetCellValue $sGui(TableId) $row $sGui(Col,UserConfig) $config
        }
    }

    proc AppendLogMsg { logMsg { tagList {} } } {
        variable sGui

        $sGui(LogId) insert end "$logMsg\n" $tagList
    }

    proc SetStatusMsg { statusMsg { icon "None" } } {
        variable sGui

        if { [info exists sGui(StatusWidget)] } {
            poWin WriteStatusMsg $sGui(StatusWidget) $statusMsg $icon
        }
    }

    proc SetLogFile { fileName timeToRead } {
        variable sGui

        poWin SetScrolledTitle $sGui(LogId) [format "Log file %s (%.2f seconds to read)" $fileName $timeToRead]
        set sGui(LogFile) $fileName
        set sGui(LastDir) [file dirname $fileName]
    }

    proc InitStatusProgress { par maxValue { mode "determinate" } } {
        # Copy of procedure from poTklib/poWin with call of update idletasks.
        if { [winfo exists $par.progress] } {
            $par.progress configure -maximum $maxValue -mode $mode
        }
    }

    proc ParseLine { line } {
        variable sGui
        variable sCurLib
        variable sSummarySection
        variable sCompilerWarningErrorPatterns

        set tagList [list]
        foreach { pattern tag } $sCompilerWarningErrorPatterns {
            if { [string match -nocase $pattern $line] } {
                lappend tagList $tag
                break
            }
        }
        AppendLogMsg $line $tagList

        if { [string match "* > Setup (File*" $line] } {
            set setupStr [string trim [lindex [split $line "("] 1] " )"]
            # Remove word "File"
            set sGui(SetupFile) [string trim [string range $setupStr 4 end]]
            UpdateTitle $sGui(SetupFile)
        } elseif { [string match "*Build file*:*" $line] } {
            set sGui(BuildFile,$sCurLib) [lindex [split [string trim $line]] end]
        } elseif { [string match "*User file*:*" $line] } {
            set sGui(UserBuildFile,$sCurLib) [lindex [split [string trim $line]] end]
        } elseif { [string match "* SetCompilerVersions *" $line] } {
            set sGui(CompilerVersions) [lrange [split [string trim $line]] 1 end]
            UpdateTitle $sGui(SetupFile) $sGui(CompilerVersions)
        } elseif { [string match "* SetStage *" $line] } {
            if { [lrange [split [string trim $line]] 1 end] eq "Test" } {
                set sGui(TestStage) true
            }
        } elseif { [string match "* WorkingSet *" $line] } {
            set libInfo [string trim [lindex [split $line ":"] 1]]
            set libName     $libInfo
            set libVersion  ""
            set libCompiler ""
            if { [llength $libInfo] == 3 } {
                lassign $libInfo libName libVersion libCompiler
            } elseif { [llength $libInfo] == 2 } {
                lassign $libInfo libName libVersion
            }
            AppendLib $libName $libVersion $libCompiler
        } elseif { [string match "* UserConfig *" $line] } {
            set libInfo [string trim [lindex [split $line ":"] 1]]
            lassign $libInfo libName libConfig
            SetLibUserConfig $libName $libConfig
        } elseif { [string match "* > Start *" $line] } {
            set libInfo [string trim [lindex [split $line ">"] 1]]
            lassign $libInfo dummy libName
            SetLibStatus $libName "Started" "Running"
            set sCurLib $libName
            set timeStr [string trim [lindex [split $line ">"] 0]]
            if { $timeStr ne "" } {
                set sGui($libName,StartTime) [clock scan $timeStr -format "%H:%M:%S"]
            } else {
                set sGui($libName,StartTime) [clock seconds]
            }
            if { $sGui(AutoUpdate) } {
                set estimatedTime [GetEstimatedBuildTime $sCurLib [GetCompilerOrTest [GetLibCompiler $sCurLib]]]
                if { $estimatedTime > 0.0 } {
                    InitStatusProgress $sGui(StatusWidget) [expr {int($estimatedTime * 100)}] "determinate"
                } else {
                    InitStatusProgress $sGui(StatusWidget) 30 "indeterminate"
                }
            }
        } elseif { [string match "* > End *" $line] } {
            set libInfo [string trim [lindex [split $line ">"] 1]]
            lassign $libInfo dummy libName
            set libName [string trim $libName " :"]
            set status [string trim [lindex [split $libInfo ":"] 1]]
            if { [string match "Excluded*" $status] } {
                SetLibStatus $libName "Excluded" $status
            } else {
                set res [scan $status "%f minutes" buildTime]
                if { $res == 1 && [string is double -strict $buildTime] } {
                    SetLibStatus $libName "Finished" [format "%.2f" $buildTime]
                } else {
                    SetLibStatus $libName "Finished" $status
                }
            }
            set sCurLib ""
        } elseif { [string match "*>*Setup *" $line] } {
            set sCurLib [lindex [split [string trim $line]] end]
        } elseif { [string match "*Warning > *" $line] || \
                   [string match "*Error > *" $line] || \
                   [string match "*FATAL > *" $line] } {
            SetLibWarning $sCurLib
        } elseif { [string match "* LogFile *" $line] } {
            set logFile [lindex [split [string trim $line]] 1]
            SetLogFile $logFile 0.0
        } elseif { [string match "* #  : Library *" $line] } {
            set sSummarySection true
        } elseif { [string match "* Total: *" $line] } {
            set sSummarySection false
            set sGui(AutoUpdate) 0
        } elseif { [string match "*Update cause: *" $line] } {
            set cause [string trim [lindex [split $line ":"] 1]]
            SetLibUpdateCause $sCurLib $cause
        } elseif { [string match "*MSys/MinGW available:*" $line] || \
                   [string match "*MinGW available:*" $line] } {
            set colon [expr [string first ":" $line] + 1]
            set sGui(MinGW) [string trim [string range $line $colon end]]
        } elseif { [string match "*MSys available:*" $line] || \
                   [string match "*MSys available :*" $line] } {
            set colon [expr [string first ":" $line] + 1]
            set sGui(MSysShell) [string trim [string range $line $colon end]]
        } elseif { [string match "*: *" $line] && $sSummarySection } {
            set libInfo [string trim [lindex [split $line ":"] 1]]
            set libName [lindex $libInfo 0]
            set mappedLibInfo [string map { minutes : Excluded : } $libInfo]
            set stages [string trim [lindex [split $mappedLibInfo ":"] 1]]
            SetLibStages $libName $stages
        }
    }

    proc _AdjustTableView {} {
        variable sGui

        if { [info exists sGui(SeeRow)] && $sGui(SeeRow) > 0 } {
            $sGui(TableId) yview scroll 1 units
        }
        focus $sGui(TableId)
    }

    proc ReadFile { fileName } {
        variable sGui

        SetStatusMsg "Reading log file $fileName ..." "Watch"
        update
        set startTime [clock milliseconds]
        set fp [open $fileName "r"]
        if { [poMisc HaveTcl9OrNewer] } {
            fconfigure $fp -profile tcl8
        }
        while { [gets $fp line] >= 0 } {
            ParseLine $line
        }
        close $fp
        $sGui(LogId) tag configure "Warning" -foreground red
        $sGui(LogId) see end
        SetStatusMsg ""
        $sGui(TableId) configure -labelcommand tablelist::sortByColumn
        set sGui(LastUpdateSize) [file size $fileName]
        set sGui(LastDir)  [file dirname $fileName]
        set sGui(LastFile) $fileName
        poAppearance AddToRecentFileList $fileName
        _AdjustTableView
        set endTime [clock milliseconds]
        return [expr { ($endTime - $startTime) / 1000.0 }]
    }

    proc LoadFile { w fileList } {
        variable sGui

        if { [llength $fileList] > 0 } {
            set fileName [lindex $fileList 0]
            ClearGui
            set timeNeeded [ReadFile $fileName]
            SetLogFile $fileName $timeNeeded
        }
    }

    proc LoadRecentFile { fileName } {
        ClearGui
        set timeNeeded [ReadFile $fileName]
        SetLogFile $fileName $timeNeeded
    }

    proc ReloadFile {} {
        variable sGui

        if { [info exists sGui(LastFile)] } {
            ClearGui
            set timeNeeded [ReadFile $sGui(LastFile)]
            SetLogFile $sGui(LastFile) $timeNeeded
        }
    }

    proc AutoUpdate {} {
        variable sGui
        variable sCurLib
        variable sData

        if { $sGui(AutoUpdate) } {
            set diffTimeStr ""
            if { [info exists sGui($sCurLib,StartTime)] } {
                set curTime [clock seconds]
                set diffTime [expr ($curTime - $sGui($sCurLib,StartTime)) / 60.0]
                set estTimeStr ""
                set estimatedTime [GetEstimatedBuildTime $sCurLib [GetCompilerOrTest [GetLibCompiler $sCurLib]]]
                set row [_GetLibRow $sGui(TableId) $sCurLib]
                if { $row >= 0 } {
                    set remainTime 0.0
                    set remainLibs 0
                    for { set r $row } { $r < [poTablelistUtil GetNumRows $sGui(TableId)] } { incr r } {
                        set cellValue [poTablelistUtil GetCellValue $sGui(TableId) $r $sGui(Col,EstimatedTime)]
                        if { $cellValue eq "" } {
                            set cellValue 0.0
                        }
                        set remainTime [expr { $remainTime + $cellValue }]
                        incr remainLibs
                    }
                    set remainTimeStr ""
                    if { $remainTime > 0.0 } {
                        set remainTime [expr { $remainTime - $diffTime }]
                        set remainTimeStr [format "Remaining estimated %s time: %.2f minutes." \
                            [GetBuildOrTestString] $remainTime]
                    }
                    poWin SetScrolledTitle $sGui(TableId) \
                          [format "Setup contains %d libraries. Remaining %d libraries. %s" \
                          $sData(NumLibs) $remainLibs $remainTimeStr]
                    if { $estimatedTime > 0.0 } {
                        set estTimeStr [format ". Estimated %s time %.2f minutes" \
                           [GetBuildOrTestString] $estimatedTime]
                        if { $diffTime > [expr {$estimatedTime + $estimatedTime * $sGui(Deadlock,Threshold)}] } {
                            $sGui(TableId) rowconfigure $row -background orange
                            if { $sGui(Deadlock,UseBeep) } {
                                bell
                            }
                        } else {
                            $sGui(TableId) rowconfigure $row -background yellow
                        }
                    }
                    poTablelistUtil SetCellValue $sGui(TableId) $row $sGui(Col,BuildTime) [format "%.2f" $diffTime]
                }
                set diffTimeStr [format "(Library %s running since %.2f minutes%s)" \
                                        $sCurLib $diffTime $estTimeStr]
                poWin UpdateStatusProgress $sGui(StatusWidget) [expr {int($diffTime * 100.0) }]
            }
            SetStatusMsg "Auto Update: ON $diffTimeStr"
        } else {
            SetStatusMsg "Auto Update: OFF"
            poWin InitStatusProgress $sGui(StatusWidget) 0
            poWin UpdateStatusProgress $sGui(StatusWidget) 0
        }
        if { $sGui(AutoUpdate) && \
             [info exists sGui(LastUpdateSize)] && \
             [info exists sGui(LastFile)] } {
            if { [file size $sGui(LastFile)] > $sGui(LastUpdateSize) } {
                ReloadFile
            }
        }
        after 3000 AutoUpdate
    }

    proc ToggleAutoUpdate {} {
        variable sGui

        if { $::tcl_platform(os) ne "Darwin" } {
            set sGui(AutoUpdate) [expr ! $sGui(AutoUpdate)]
        }
    }
}

# Start of main program.

namespace import BawtLogViewer::*

set retVal [catch {package require Tk} version]
if { $retVal != 0 } {
    puts "Tk could not be loaded. No GUI possible."
    BawtLogViewer::ExitApp 1
}
set retVal [catch {package require Img} version]
if { $retVal != 0 } {
    puts "Img could not be loaded. No GUI possible."
    BawtLogViewer::ExitApp 1
}
set retVal [catch {package require scrollutil_tile} version]
if { $retVal != 0 } {
    puts "scrollutil could not be loaded. No GUI possible."
    BawtLogViewer::ExitApp 1
}
set retVal [catch {package require tablelist_tile} version]
if { $retVal != 0 } {
    puts "tablelist could not be loaded. No GUI possible."
    BawtLogViewer::ExitApp 1
}
set retVal [catch {package require tkdnd} version]
set BawtLogViewer::sGui(Have,tkdnd) true
if { $retVal != 0 } {
    set BawtLogViewer::sGui(Have,tkdnd) false
}
set retVal [catch {package require poTcllib} version]
if { $retVal != 0 } {
    puts "poTcllib could not be loaded. No GUI possible."
    BawtLogViewer::ExitApp 1
}
set retVal [catch {package require poTklib} version]
if { $retVal != 0 } {
    puts "poTklib could not be loaded. No GUI possible."
    BawtLogViewer::ExitApp 1
}

set retVal [catch {package require rtext} version]
if { $retVal != 0 } {
    # The optional revised text widget is available only for Tcl9.
}

set optNoSaveOnExit false
set fileName ""

set curArg 0
while { $curArg < $argc } {
    set curParam [lindex $argv $curArg]
    if { [string compare -length 1 $curParam "-"]  == 0 || \
         [string compare -length 2 $curParam "--"] == 0 } {
        set curOpt [string tolower [string trimleft $curParam "-"]]
        if { $curOpt eq "nosaveonexit" } {
            set optNoSaveOnExit 1
        } elseif { [string first "-psn" $curParam] == 0 } {
            # Ignore this option. This parameter is supplied automatically
            # by OS X, when using this program as a starpack in a Mac APP.
        }
    } else {
        set fileName $curParam
    }
    incr curArg
}

if { $fileName ne "" } {
    if { ! [file isfile $fileName] } {
        puts "File $fileName not existent"
        BawtLogViewer::ExitApp 1
    }
}

if { $::tcl_platform(os) eq "Darwin" } {
    proc ::tk::mac::Quit {} {
        BawtLogViewer::ExitApp 0
    }
}

event add <<LeftButtonPress>>   <ButtonPress-1>
if { $::tcl_platform(os) eq "Darwin" && ! [poMisc HaveTcl87OrNewer] } {
    event add <<MiddleButtonPress>> <ButtonPress-3>
    event add <<RightButtonPress>>  <ButtonPress-2>
} else {
    event add <<MiddleButtonPress>> <ButtonPress-2>
    event add <<RightButtonPress>>  <ButtonPress-3>
}
if { $::tcl_platform(os) eq "Darwin" } {
    event add <<RightButtonPress>> <Control-ButtonPress-1>
}

ShowMainWin

if { $optNoSaveOnExit } {
    BawtLogViewer::SetAutosaveOnExit 0
}

if { [file readable $fileName] } {
    ReadFile $fileName
}

poAppearance UpdateRecentCaches
poAppearance SetUseMsgBox "Exit"    0
poAppearance SetUseMsgBox "Error"   0
poAppearance SetUseMsgBox "Warning" 0
poAppearance SetUseMsgBox "Notify"  0

AutoUpdate

